Crate layout

The workspace is a flat crates/* virtual workspace (matklad's "Large Rust Workspaces"), with thin bindings/{node,wasm} packages and an xtask/ build crate. Each crate depends only downward — lower crates know nothing about the layers above them. The authoritative version is in plans/01-ARCHITECTURE.md §1.

The layer table

CrateResponsibilityDepends onwasm-safe?
gdscript-basePOD types: FileId, TextSize/TextRange, LineIndex, position/range conversions, the serde result structs shared with clients. No logic.
gdscript-syntaxLexer (logos) + indentation pre-pass + hand-written recursive-descent parser → lossless cstree CST + typed AST. Error recovery.base
gdscript-apiThe Godot engine model generated from extension_api.json + doc XML: classes, inheritance, methods, properties, signals, enums, singletons, utility functions, builtins — plus the hand-authored GDScript layer (keywords, annotations, builtins) the dump omits.base
gdscript-scene.tscn/.tres text parser → node-tree model for $Path/%Unique node-path typing.base
gdscript-fmtA gdformat-compatible formatter: format/format_range, safe-by-construction (falls back to the original source if it can't prove the reformat is meaning-preserving).base, syntax
gdscript-dbInput layer: a virtual file system (FileId → text, injected, never std::fs), the project model, apply_change. Salsa inputs + tracked queries for incremental recompute.base, syntax, api
gdscript-hirSemantic layer: lower AST → HIR, scope tree, name resolution, gradual type inference, the GDScript warning checks (62 codes and counting).base, syntax, api, db
gdscript-ideThe feature layer and public API: AnalysisHost + immutable Analysis, one method per IDE feature, POD results. The crate external Rust consumers depend on, and the wasm-check target.all above
gdscript-sessionInternal URI-keyed layer over gdscript-ideserde_json::Value results (document lifecycle, JSON serialization). Unit-tested natively; not a stable API — consumers depend on gdscript-ide. Shared by the napi and wasm bindings so they can't drift apart.ide
gdscript-ffiThe napi-rs v3 Node binding (@gdscript-analyzer/core) — a thin #[napi] delegator over gdscript-session. publish = false (packaged via bindings/node).sessionn/a (is the binding)
gdscript-wasm (bindings/wasm)The wasm-bindgen browser binding (@gdscript-analyzer/wasm) — a thin #[wasm_bindgen] delegator over gdscript-session.sessionn/a (is the binding)
gdscript-lspA real, standalone, spec-compliant LSP server binary. The only place that knows lsp-types/JSON-RPC. publish = false.idenative
gdscript-clicheck/lint/format/symbols for CI/pre-commit. publish = false.idenative
xtaskBuild automation: codegen-api, fixtures, dist, release helpers, the local ci gate.native

Dependency direction

base ◀── syntax ◀── db ◀── hir ◀── ide ◀── session ◀── ffi ◀── (bindings/node)
  ▲         ▲       ▲       ▲       ▲          └────── wasm ◀── (bindings/wasm)
  ├── api ──┘───────┘───────┘       ├── lsp
  ├── scene ────────────────────────┘
  └── fmt ──────────────────────────┘── cli

A crate may only use crates to its left. Adding an upward edge is an architectural change and should be questioned in review.

Current state

Every crate above is real, shipped code — not a stub. The core crates compile, lint, test, and pass the wasm portability check with substantial domain logic behind them (parsing, inference, warnings, formatting, scene typing, incremental recompute); gdscript-ffi/gdscript-wasm/gdscript-lsp/gdscript-cli are published binaries/packages. See CLAUDE.md and plans/ROADMAP.md for the current phase-by-phase status.

Publishing note

Internal crate names use the gdscript- prefix. The public Rust crate is gdscript-ide. The npm scope is @gdscript-analyzer/*. Non-library crates (gdscript-ffi, gdscript-session, gdscript-lsp, gdscript-cli, the bindings) carry publish = false — they ship as binaries or npm packages instead of crates.io crates.