Skip to content

Refactor project structure and enhance identifier resolution for TS/Rust + Viz#19

Merged
SUP2Ak merged 355 commits into
mainfrom
dev
Jun 3, 2026
Merged

Refactor project structure and enhance identifier resolution for TS/Rust + Viz#19
SUP2Ak merged 355 commits into
mainfrom
dev

Conversation

@SUP2Ak

@SUP2Ak SUP2Ak commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

No description provided.

SUP2Ak added 30 commits May 17, 2026 23:15
VSCode-side follow-up to the Rust commits 791022b (RAG removal) and
ad22458 (Sessions removal). MCP shape no longer carries `rag` /
`chunk_refs` / `fetch_chunks` / session_* / usage_stats — ext must
stop wiring them.

Removed
-------
- `src/daemon/rag-flags.ts` (RagSettings + ragSpawnFlags)
- `src/daemon/rag-settings.ts` (readRagSettings/writeRagEnabled/etc.)
- `tests/rag-flags.test.ts`
- RAG fields/methods on McpClient (`ragSettings`, `ragDownloading`,
  `setRagSettings`, `ragSettingsSnapshot`, `scanRagMarkers`,
  `fetchChunks`, `usageStats`, model-download endpoint wait)
- RAG fields on LspClient + ragSpawnFlags splat in spawn args
- RAG settings/commands in extension.ts (`readRagSettings`,
  `watchRagSettings`, the daemon auto-restart-on-rag-change wiring)
- Command palette entries: `Standardoc.rag.toggle`,
  `.rag.switchEmbedder`, `.rag.rebuild`, `.showTokenSavings`,
  `.resetTokenSavings`
- StatusBar `RagSettings?` parameter (the `· RAG (embedder)` suffix)
- commands-render `formatUsageStats` + `UsageStatsJson`
- skill-template sections for `fetch_chunks`, `usage_stats`,
  `session_save` / `session_list` / `session_get` /
  `session_sync_in` / `session_sync_out` + the prose-retrieval
  workflow recipe + the session-handoff workflow recipe
- mcp/types `CurrentRevisionJson.rag` field
- README sections for RAG settings + RAG commands + token-savings

Preserved
---------
- `enrichment_description` on `SymbolContextJson` (the enrichments
  table is kept Rust-side as scaffolding for the future AI layer;
  ext just renders the field when it's `null` today)

Settings
--------
- `standardoc.ragEnabled`, `standardoc.ragEmbedder` removed from
  `package.json` (users' workspace settings with these keys will be
  silently ignored — VSCode warns about unknown contributed settings
  but doesn't error).

Tests
-----
- typecheck clean
- 180/180 bun tests pass
- `bun run build` bundles cleanly (1.39 MB, 379 modules)

Cleanup motivation: the Rust commits made the MCP server stop
serving `rag` / `fetch_chunks` / sessions / usage_stats. The
extension would otherwise spawn `--rag` flags the daemon rejects
and call tools that no longer exist.
Two tiny followups from the A1/A3 cleanup audit.

ir/signature.rs tests
---------------------
The compact_rust_tokens tests embedded the deleted RAG types as
literal strings to exercise tokenization. Inoffensive at compile time
but the names linger as dead concepts in greps:
- `standardoc_core::RagWatcherHandle` → `standardoc_core::WatcherHandle`
- `Arc<dyn Embedder>` → `Arc<dyn Logger>`

No semantic change — the tokenizer doesn't care which identifier it
sees, the test verifies whitespace-stripping shape only.

cli/main.rs
-----------
`confirm_destructive` was only ever called from `cmd_reset_usage`
(deleted with A1). Drop the dead helper.

cargo test --workspace green (714 + 110 + 95 + 13 + …).
Refonte du playground en visualiseur de graphe sémantique style Blueprint
Unreal Engine.

- payload: fetch_graph gagne projects[], language + project_id sur les
  symboles ; barre accent langage par chip (palette GitHub linguist)
- frames projet colorées par kind, imbriquées par rel_path
- semantic zoom : LodTier {Project, Module, Chip}, seuils 0.16 / 0.46
- layout en couches de dépendance (Sugiyama) à tous les tiers, câbles
  de dépendance Bézier persistants aux tiers Project/Module
- navigation : double-clic zoom-to-fit, minimap, breadcrumb live
- légende overlay repliable, profiler HUD déplacé bottom-left
Both variants were defined in the EdgeKind enum + persisted via SQL CHECK
constraint + parsed in MCP handler + colored in viz palette, but no
extractor ever emitted them in prod. `query::dependents` match treated
them as inbound deps but the branch was always cold.

R-Sem-1 of the IR refactor — opens the path for R-Sem-2 (promote
LocalDeclKind to RawSymbol.decl_kind).

Note: residual cleanup in viz palette/payload/playground will land with
the V1->V5 viz commit since those files carry uncommitted V1->V5 work.
…ary, find_symbol_fqdns, list_symbol_fqdns

Four additive MCP tools tailored for agent navigation patterns:

- `get_code(fqdn)`: agent variant of `get_body` with strip_attrs=true and
  strip_inline_comments=true by default. Returns pure code, no doc /
  attribute / inline-comment dilution. The leading description is already
  surfaced by `get_context.context.document_description` - duplicating it
  in the body is wasted context.

- `get_context_summary(fqdn)`: cheap mapping probe. Returns the symbol
  header plus per-direction neighbor counts (callers/callees/imports/
  imported_by/dependents/tests) instead of the full neighbor lists.
  Designed as a first-pass before deciding whether `get_context` is worth
  the round-trip.

- `find_symbol_fqdns(query, ...)`: FTS5 search returning `[{fqdn, kind}]`
  per match instead of the full RawSymbol. Same filters and did_you_mean
  envelope as `find_symbol`. Default discovery probe.

- `list_symbol_fqdns(...)`: filter-only listing returning `{items:
  [{fqdn, kind}], next_cursor}`. Same pagination as `list_symbols`.

R-A1 / R-A4 / R-A5 of the consumer-aware refactor (Vague A in the audit
plan). Zero breaking - existing tools unchanged.
…add relative_to projection on *_fqdns

Phase 1 of the FQDN audit (R-FQ-1 + R-FQ-4).

R-FQ-1 (centralize path-conventions):
  Move per-language compute_module_path bodies into a single helper
  walk_core::compute_module_path(conventions, package_relative) driven
  by a LanguagePathConventions struct (extensions / root_aliases /
  strip_src_prefix). Each lang-provider now exposes its conventions
  as a `const` and delegates its compute_module_path to the helper.

  Side-effect cleanup: Lua's compute_module_path no longer returns a
  dot-separated intermediate form that the caller had to remap to
  `::`; it returns `::`-joined directly. The legacy
  `.replace('.', "::")` at the FQDN composition site is now a no-op
  and was dropped. Same simplification for TS where the helper used
  to return `/`-separated and the caller did `.replace('/', "::")`.
  No FQDN output change at the symbol level (all four languages
  already emitted `crate_or_pkg::module::name` shapes); pure DRY.

R-FQ-4 (relative_to projection):
  Add an optional `relative_to: Option<String>` field to
  FindSymbolFqdnsParams + ListSymbolFqdnsParams. When set, matching
  FQDNs are returned in relative form (leading `::` marker, prefix
  stripped). FQDNs that don't share the anchor pass through verbatim.
  Boundary-aware: `foo::bar` does NOT shorten `foo::barista::x`.
  Helper `relative_fqdn(fqdn, anchor) -> String` carries five unit
  tests covering prefix/self-match/no-match/empty-anchor/boundary
  edge cases.

The two MCP tool descriptions advertise the new param so a forgetful
agent re-discovers it.
Phase 2 K (Kind hierarchy refonte) foundation. Adds the refined
declaration-kind axis alongside the coarse `Kind` bucket, with storage
round-trip and schema bump. All lang-providers leave `decl_kind: None`
at this step — population per language lands in K-Step-B.

- standardoc-ir::kinds::DeclKind: 22 built-in variants covering
  Module/Namespace/Crate, Struct/Enum/Union/Class/Interface/TypeAlias,
  Function/Method/Constructor/Getter/Setter, Const/Static/Var/Field/
  EnumVariant, DeclarativeMacro/ProcMacro/Decorator — plus
  `Custom { lang, tag }` escape hatch. `Impl` intentionally absent
  (Rust impl blocks are wrappers, not symbols); `Trait` collapses
  into `Interface`.
- RawSymbol gains `decl_kind: Option<DeclKind>` with
  `skip_serializing_if = "Option::is_none"` — legacy rows deserialize
  to `None`.
- storage: `symbols.decl_kind TEXT` column (nullable, no CHECK —
  values validated Rust-side via `decl_kind_to_sql_text` /
  `decl_kind_from_sql_text`). Flat encoding: built-ins as snake_case
  (`"method"`, `"enum_variant"`, …), Custom as
  `"custom:<lang>:<tag>"`. Wired into `insert_symbol` UPSERT and
  every SELECT path (`SYMBOL_COLUMNS` + 4 explicit selects).
- SUPPORTED_SCHEMA_VERSION bumped 0 → 1; cold-start triggers
  destructive rebuild on stale DBs (the index is a derived cache).

1630 tests passing, 0 failed.
…ctor

Phase 2 K-Step-B (1/4) — first language to gain DeclKind coverage.
All Rust-emitted symbols now carry a `Some(DeclKind::…)` matching
the syn item that produced them:

- `extract.rs` file-module symbol → `Module`
- `extract_fn` (top-level fn)     → `Function`
- `extract_struct`                → `Struct` (fields → `Field`)
- `extract_enum`                  → `Enum`   (variants → `EnumVariant`)
- `extract_union`                 → `Union`
- `extract_type_alias`            → `TypeAlias`
- `extract_trait`                 → `Interface` (trait collapses
                                     per Phase 2 §4.2)
- trait-method items              → `Method`
- impl-method items               → `Method`
- `extract_const` / `extract_static` → `Const` / `Static`
- `extract_macro_def`             → `DeclarativeMacro`
- `extract_use.rs` re-export      → stays `None` (phantom symbol,
                                     target kind unknown without
                                     cross-file resolution)

`type_def_symbol` / `value_def_symbol` helpers gain a typed
`decl_kind: DeclKind` parameter, threaded through 6 call sites.

8 new unit tests in `rust::walk::tests` verify the mapping, including
the "impl blocks emit methods, not impl-symbols" invariant first
documented in K-Step-A's MCP investigation.

1638 tests passing, 0 failed.
Phase 2 K-Step-B (2/4) — TypeScript joins Rust in DeclKind coverage.
17 emission sites in ts/walk.rs + the file-module symbol in
ts/extract.rs now carry a `Some(DeclKind::…)`:

- file-module symbol           → Module
- extract_fn_decl              → Function
- extract_class_inner          → Class
- extract_constructor          → Constructor
- extract_class_prop /
  extract_private_prop         → Field
- extract_method /
  extract_private_method       → Method / Getter / Setter
                                  (dispatched on swc MethodKind)
- extract_interface_decl       → Interface
  interface property           → Field
  interface method             → Method
  interface getter / setter    → Getter / Setter
- extract_enum_decl            → Enum
  enum_member                  → EnumVariant
- extract_type_alias_decl      → TypeAlias
- extract_var_decl             → Function if arrow/fn expr, else
                                  Const / Var per VarDeclKind
- process_export_default_decl  → Function or Interface (variant-aware)

`ts/ffi_tagger.rs` synthetic FFI imports stay `None` for the same
reason Rust `re_export` does — these are binding-discovery records,
not source-level declarations; the target's decl_kind is unknown
without cross-file resolution.

8 new unit tests in `ts::walk::tests` cover all variants including
the const-arrow-function-becomes-Function carve-out.

1646 tests passing, 0 failed.
Phase 2 K-Step-B (3/4) — C joins Rust and TS. 7 emission sites in
c/walk.rs + file-module symbol in c/extract.rs now carry a
`Some(DeclKind::…)`:

- file-module symbol               → Module
- fn definition (`fn`)             → Function
- fn prototype  (`fn_decl`)        → Function
- global var    (`global`)         → Static (C globals have static
                                     storage duration regardless of
                                     the `static` keyword — that only
                                     toggles linkage)
- struct                            → Struct
- union                             → Union
- typedef                           → TypeAlias
- enum + enumerators                → Enum + EnumVariant (both inline
                                       `enum { … }` and `typedef enum
                                       { … } Alias` paths)
- #define object-like macro         → DeclarativeMacro
- #define function-like macro       → DeclarativeMacro
                                     (C preprocessor maps to the same
                                      DeclKind as Rust `macro_rules!` —
                                      both are textual substitution;
                                      `language_kind` preserves the
                                      object-vs-fn distinction)

`push_symbol` + `emit_struct_like` helpers gain a typed
`decl_kind: DeclKind` parameter, threaded through every caller.

5 new unit tests in `c::tests` cover fn def + prototype, struct,
union, enum + variants, typedef, both macro flavors. Global-var
emission is not currently exercised by the C extractor (out of
scope, pre-existing limitation) — the wiring is in place for the
day it lands.

1651 tests passing, 0 failed.
Phase 2 K-Step-B (4/4) — Lua closes the foundation. All 5 Lua
extractor emission sites now carry a `Some(DeclKind::…)`:

- file-module symbol            → Module
- `local function foo()`        → Function
- `function foo()` / `function
   M.foo()`                     → Function
- `function M:bar()`            → Method (Lua's `:` colon-call form
                                  desugars to `self`-first arg —
                                  consistent with Rust impl methods)
- `local x = …`                 → Var (matches the LocalDeclKind::Let
                                  collapse policy in §4.5; mutability
                                  flag deferred to Phase 2 polish)
- `M.foo = function() end`      → Function (extract_assignment emits
                                  as Kind::Function when RHS is a
                                  function literal, so DeclKind
                                  matches the coarse axis)

`lua/ffi_tagger.rs` synthetic FFI imports stay `None` for the same
reason Rust re_export and TS FFI do — these are binding-discovery
records, not source-level declarations.

7 new unit tests in `lua::extract::tests` cover all 6 emission
shapes (file-module, local fn, global fn, dotted fn, colon-method,
local var, table-assigned fn).

1658 tests passing, 0 failed.

This closes Phase 2 K-Step-B for all four extractor languages
(Rust + TypeScript + C + Lua). DeclKind is now populated at the
source for every symbol that survives extraction.
Phase 2 K-Step-C — finalize the impl-block refonte. R-K-6 (drop
impl-block symbol emission) was already in place since pre-Phase-2;
this step lands R-K-4 (`implements_trait`) and R-K-5 (`receiver_type`)
on `RawSymbol`, persists them through storage, and populates them
for the Rust extractor.

IR (`standardoc-ir::symbol`):
- `pub implements_trait: Option<String>` — when a `Method` lives
  inside `impl Trait for Type { ... }`, carries the trait FQDN.
  Inherent impl methods and free functions stay `None`. Per-method
  mirror of the `EdgeKind::Implements` edge from Type → Trait.
- `pub receiver_type: Option<TypeRef>` — for `DeclKind::Method`
  symbols, the type the method dispatches on. Rust impl method
  → impl-er type; trait method definition → the trait itself
  (Self : Trait). Free functions stay `None`.

Both fields default to `None` and are `skip_serializing_if` —
legacy rows round-trip cleanly.

Storage:
- `symbols.implements_trait TEXT` and `symbols.receiver_type TEXT`
  columns (both nullable, no CHECK — values are unconstrained FQDN
  strings).
- `SUPPORTED_SCHEMA_VERSION` bumped 1 → 2; cold-start reset on
  mismatch.
- `insert_symbol` UPSERT writes both columns; `SYMBOL_COLUMNS` +
  `SymbolRowRaw` + `read_symbol_row` + `build_symbol` + the 4
  explicit SELECTs all updated; `context_for_symbol` row offsets
  shifted (15→17, 16→18).

Rust extractor (`extract_impl` + `extract_trait`):
- `extract_impl` impl_fn emission: `receiver_type = target_fqdn`,
  `implements_trait = trait_path string` (Some for `impl Trait
  for Type`, None for `impl Type`). The raw trait path is captured
  once and reused — same source the existing `Implements` edge
  consumes.
- `extract_trait` trait_fn emission: `receiver_type = trait_fqdn`
  (Self : Trait), `implements_trait = None`.

4 new tests in `rust::walk::tests` cover all 4 shapes:
inherent impl method, trait-impl method, trait method def,
free function. The existing `decl_kind_round_trip_method` test
in `standardoc-ir` now exercises the new fields end-to-end via
serde.

TS and Lua extractors carry the schema additions but stay at
`None` for both fields — populating receiver_type for TS class
methods and Lua colon-methods is a low-cost follow-up (K polish),
not blocking K-Step-D.

1662 tests passing, 0 failed.
Phase 2 K-Step-D — coarse-kind axis clarification. The `Kind::Function`
variant served as an umbrella for free functions, methods, constructors,
getters and setters; the new `Callable` name reflects that umbrella role
and stops conflating with `DeclKind::Function` (which now stays the
fine-grained "free function" refinement).

BREAKING (wire + storage):
- IR `Kind::Function` → `Kind::Callable` (serde renames `"function"` →
  `"callable"`).
- SQL `symbols.kind` CHECK constraint accepts `'callable'` instead of
  `'function'`. `SUPPORTED_SCHEMA_VERSION` bumped 2 → 3 — cold-start
  triggers destructive rebuild on the first boot after this lands.
- MCP `parse_kind("function")` now rejects; accepts `"callable"`.
- ext/vscode `SymbolKindJson` union updated; `formatSymbolHeader`
  test asserts `"callable"`.

No legacy alias retained — Phase 2 already requires re-extraction
(schema bump) so a clean break costs nothing extra. Consumers wanting
both names can adapt at the wire boundary if needed.

Mechanical scope:
- 219 `Kind::Function` callsites renamed across 37 Rust files
- `kind_to_sql_text` / `kind_from_sql_text` updated
- 5 SQL test fixtures with literal `'function'` updated to `'callable'`
- 1 graph-viz `Kind` enum (local to that crate) renamed
- Tests asserting JSON `"kind":"function"` updated to `"callable"`

DeclKind::Function STAYS unchanged — it's the refined "free function"
variant under Kind::Callable, distinct from Method/Constructor/Getter/
Setter siblings.

1662 tests passing, 0 failed. WASM build clean. ext/vscode bun tests
all green (180/180).
…ver_type across wire layers

Surface the K-Step-A/C nullable refinement fields on every consumer of the
symbol wire: the daemon's `GraphSymbol`, the viz crate's `SymbolEntry`,
the MCP `get_context_summary` payload, and the ext/vscode `RawSymbolJson`
type. Previously they were only reachable via `get_context` (which returns
the full `RawSymbol`); `fetch_graph` and the agent-tuned summary tools
silently dropped them.

- standardoc-core: `GraphSymbol` gains `decl_kind: Option<DeclKind>`,
  `implements_trait: Option<String>`, `receiver_type: Option<String>`
  (the `TypeRef.display` projection — wire economy, the TypeRef has
  nothing else today). `load_graph_symbols` SELECT now reads the three
  nullable columns; `GraphSymbolRowRaw` and `read_graph_symbol` extend
  in lockstep. New test asserts the JSON shape both when fields are
  NULL (skipped via `skip_serializing_if`) and when set.
- standardoc-graph-viz: `SymbolEntry` mirrors the same three fields as
  `Option<String>` (consistent with how `visibility` / `language_kind`
  are already stored as raw strings — K-Step-F will match on the
  snake_case values without pulling a local DeclKind enum).
- standardoc-server: `get_context_summary` JSON `symbol` block carries
  the three fields. `receiver_type` is projected to `t.display` for
  consistency with the `GraphSymbol` wire shape.
- ext/vscode: `RawSymbolJson` gains three optional fields; new typed
  union `DeclKindJson` covers the 22 built-in `snake_case` variants
  plus the `{ custom: { lang, tag } }` escape hatch (with paired
  `LanguageJson` union). `formatSymbolHeader` now appends the
  `decl_kind` to the coarse `kind` when present (e.g.
  `kind: callable (method)`); `formatDeclKind` helper renders the
  Custom variant as `custom:<lang>:<tag>`.

`find_symbol_fqdns` and `list_symbol_fqdns` are intentionally left as
`{fqdn, kind}` projections — the whole point of the `*_fqdns` variants
is a tight payload.

cargo workspace check ✓, wasm32 check ✓, tsc ✓, 1663 tests passed
(+1 new wire shape test), 180 ext/vscode bun tests passed.
…n + Rust/C first-pass classification (schema v4)

The viz pivot to "where does flow start, what does it touch?" needs the
daemon to classify each symbol as an entry-point or an internal node;
without a structured signal the renderer would have to re-derive
heuristics from name/visibility, violating the dumb-renderer contract.
This commit is the foundation layer: add the classification field,
persist it, and populate the unambiguous Rust + C cases.

- standardoc-ir: new `EntryPointKind` enum (snake_case serde) with three
  variants — `BinaryMain` (`fn main` at crate root / `int main`),
  `PublicApi` (deferred to a follow-up), `FfiExport` (`#[no_mangle]` in
  Rust, `luaopen_*` in C). `RawSymbol.entry_point: Option<EntryPointKind>`
  with `skip_serializing_if = "Option::is_none"` so legacy / internal
  rows stay JSON-cheap.
- standardoc-core: schema bumps v3 → v4 (`init_v0.sql` adds nullable
  `entry_point` column + SQL CHECK, `schema_meta` seed updated).
  `SYMBOL_COLUMNS` const + four hand-rolled `SELECT s.…` queries gain
  the column. `SymbolRowRaw` / `read_symbol_row` / `build_symbol` plumb
  through. `insert_symbol` upsert handles the new column on conflict.
  `context_for_symbol`'s indexed `row.get(N)` shifted to compensate
  for the wider `SYMBOL_COLUMNS`. New `entry_point_to_sql_text` /
  `entry_point_from_sql_text` helpers in `storage::conv`.
- lang-provider Rust: `extract_fn` now calls `classify_fn_entry_point`
  — name == "main" AND parent_fqdn has no `::` → `BinaryMain` (covers
  bin crates and `src/bin/*.rs` without needing to know the crate
  target); `#[no_mangle]` attribute → `FfiExport`. `PublicApi`
  classification needs the resolver's transitive `pub mod` chain and
  is deferred.
- lang-provider C: `emit_function_definition` calls
  `classify_c_fn_entry_point` — `main` → `BinaryMain`, `luaopen_*`
  prefix → `FfiExport`. `PublicApi` for plain non-`static` C functions
  is deferred (almost every non-static C fn is also cross-TU-callable,
  so blanket tagging would drown the signal). New `push_symbol_with_entry`
  wraps the existing `push_symbol` helper. Prototypes (`.h`
  declarations) intentionally NOT tagged — only definitions are the
  real flow root.
- Tests: round-trip on `EntryPointKind` serde shape, three Rust
  extractor tests (main detection, no_mangle detection, false-positive
  guard on nested `mod inner { fn main }`), two C extractor tests
  (main detection, luaopen_* detection). Existing tests updated for
  schema v4. 1670 tests passed / 0 failed (was 1662).

TS / Lua extractors and `PublicApi` follow in a later step (each
language needs its own signal extraction logic — TS export resolution
goes through `module_lookup`, Lua needs return-table walking). The
field is wire-side `None` for symbols extracted by those providers
until they catch up — non-breaking.
…yers

K-Step-E pattern, applied to the Phase 3 (Flow) entry-point field
shipped in 3.1. The viz / agent / ext layer now sees which symbols
are flow roots without a second round-trip through `get_context`.

- standardoc-core: `GraphSymbol` gains `entry_point: Option<EntryPointKind>`
  (typed enum — matches the `decl_kind` precedent). `load_graph_symbols`
  SELECT reads `s.entry_point`, `GraphSymbolRowRaw` + `read_graph_symbol`
  extended, mapping uses `entry_point_from_sql_text`. New test
  `wire_shape_carries_entry_point_when_set` asserts the snake_case
  wire shape; the existing flat-shape test now also asserts
  `entry_point` is skipped when NULL.
- standardoc-graph-viz: `SymbolEntry` mirrors the field as
  `Option<String>` — consistent with `decl_kind` / `visibility` /
  `language_kind`, the viz crate stays free of `standardoc_ir`
  dependency for K-Step-F flow shaping.
- standardoc-server: `get_context_summary` JSON `symbol` block carries
  `entry_point` so MCP consumers see the flow-root signal in the
  lightweight summary, not just in the full `get_context` payload.
- ext/vscode: new `EntryPointKindJson` union type
  (`binary_main | public_api | ffi_export`), `RawSymbolJson` gains the
  optional field. Mirrors `DeclKindJson` shape and gives TS autocomplete
  for any consumer that branches on entry-point kind.

cargo workspace check ✓, wasm32 check ✓, tsc ✓, 1671 tests passed
(+1 new wire shape test = 1670 from 3.1 → 1671 here), 180 ext/vscode
bun tests passed.
…viz refresh

Sub-step 3.3 ships the visual highlight for entry-points so the viz
answers "where does it start?" at first glance. A coloured glow halo
(cornflower blue for binary_main, amber for public_api, orange for
ffi_export) is painted behind each card in 2D and around each impostor
in 3D, with the silhouette tracking the shape (circle/hex) rather than
always being a circle.

Data flow plumbed end-to-end:
  RawSymbol.entry_point  (3.1)
  → wire GraphSymbol     (3.2)
  → SymbolEntry → TreeNode → Card → LevelNode
  → NodeInstance.halo
  → chip.wgsl

New palette helper entry_point_halo_color gates on Option so an unknown
wire tag simply paints no halo until the renderer learns about it.

2D: render::draw_cards paints two concentric rounded fills behind the
body (12px / 6px pad, alpha 0.18 / 0.35) mimicking the 3D shader's
soft falloff.

3D: NodeInstance gains halo: [f32; 4]. The vertex stage inflates the
billboard quad by 1.35× when halo.a > 0 so the impostor keeps its full
apparent size and the aura lives in the new margin. The fragment stage
splits each pixel by metric < shape_extent (impostor re-scaled into its
own [-1, 1] frame, delegated to existing shade_sphere / shade_cube) vs
metric ∈ [shape_extent, 1.0] (halo ring with smoothstep falloff).

This commit also bundles the V1→V5 viz refresh that had been sitting
uncommitted on dev across prior sessions (per this session's decision,
shipping as one commit rather than splitting the entangled tree):
cards-only refactor (hierarchy.rs deleted, scene + layout + render
rewritten level-only), hex isometric cube impostor, ghost ring with
cross-edges, label LOD threshold, gradient edges, 3D picking + hover,
test-symbol filter regex, virtual module nodes (ensure_virtual_path),
new force3d + camera modules, separate edge.wgsl shader.

Tests: 1671 passed / 0 failed (no regression vs Phase 3.2 baseline).
cargo check --workspace and --target wasm32-unknown-unknown both clean.
…y-point satellites

Sub-step 3.4 transforms the workspace-overview view from a scatter
plot of project cubes into an actual graph that answers two questions
the IR already knew the answer to but the renderer wasn't surfacing:

  1. "Who depends on whom?" — inter-project edges aggregated from the
     symbol-level cross-link set, with weight = count of underlying
     links and direction preserved (so std-cli→std-ir and std-ir→
     std-cli are separate edges when both exist).
  2. "What can I run?" — each project cube now orbits a small ring of
     entry-point satellite spheres (binary_main / public_api /
     ffi_export), halo-coloured the same as 3.3 cards. Cap at 5 per
     project; surplus collapses into a single "+N" overflow badge.

Pipeline changes:
  - `DrillTree.entry_points_by_project: HashMap<u32, Vec<u32>>` built
    once at `build()` from a local `project_of[]` parent walk. Sorted
    by fqdn so the satellite ring is stable rebuild-to-rebuild.
  - `DrillTree::level_edges()` returns `Vec<(u32, u32, u32)>` now —
    directed pairs with weight, no more `if a < b { swap }` dedup
    that flattened direction. Also exposes `is_root_level()` so
    workspace-only enrichments can gate.
  - `scene::Edge` gains `weight: u32`. The 2D `render::draw_edges`
    multiplies `set_line_width` by `(1 + 0.25·(weight-1))` capped at
    3×; the 3D edge.wgsl can't vary line topology width per segment,
    so weight modulates per-vertex `color.a` instead — single-link
    edges read at 0.4 alpha, saturating to 1.0 around weight 5.
  - `GraphEngine` gains `satellites: Vec<SatelliteSpec>` parallel to
    the post-ghost section of `build_level_nodes()` output. Each spec
    carries a parent primary index + orbit angle + EP tree_idx (or
    `u32::MAX` sentinel for overflow badges). Positions recompute
    each frame from the parent's current force-settled center so the
    ring tracks layout settling.
  - `pick()` extends to a third zone (satellites after primaries and
    ghosts); `drill_pick()` fires `focus_to(ep_fqdn)` on satellite
    click so the user lands inside the program at its natural starting
    point. Overflow badges are explicit no-ops on click.
  - `label_layout()` emits EP satellite names on hover (gated like
    primaries) and always shows the "+N" overflow text — there's at
    most one badge per cube and the count IS its whole point.

`LevelNode` gains `#[derive(Clone)]` so satellite generation can read
the parent slice and push new entries without borrow-checker gymnastics.

Deferred: 2D satellites. The current 2D grid layout has no natural
space for satellite chips without significant rework; the workspace
overview pain was sharpest in 3D (the screenshot the user pointed at)
and the edge-aggregation fix lands for both. 2D-satellite follow-up
documented in handoff #109.

Tests: 1671 passed / 0 failed (no regression vs Phase 3.3 baseline).
cargo check --workspace and --target wasm32-unknown-unknown both clean.
Picks up TS where Phase 3.1 left it (Rust + C first-pass landed in
38e7946, TS/Lua deferred). Adds a conservative `BinaryMain` heuristic
for TS function declarations so the playground's `main.ts::main`
finally gets a halo in the 3D viz instead of being indistinguishable
from any helper function.

Heuristic: a function named `main` whose `parent_fqdn` has at most
one `::` segment — i.e. it sits at the project root
(`<project>::main`) OR at the root of a top-level file
(`<project>::<file>::main`). Deeper paths
(`<project>::<dir>::<file>::main`) are NOT tagged — a `main` buried
inside a subfolder almost always means "the function in this module
that does the main thing", not the runtime entry-point.

Applied at both `extract_fn_decl` (regular `function main() {}`) and
`process_export_default_decl` (`export default function main() {}`).
Class methods named `main` are unaffected because they route through
`extract_method`, which still emits `entry_point: None` — guarded by
a regression test so a future drift doesn't accidentally start
tagging `class Runner { main() {} }`.

`PublicApi` is still deferred for TS — it would need export-graph
walking across the project (resolving the package barrel) which is
substantial scope. `FfiExport` has no clean TS equivalent (the
wasm-bindgen Rust glue is already covered by the Rust pass).

Lua skipped entirely for this pass — workspace audit found zero
\`main()\` conventions in pure Lua code (the only entry-point signal
is `luaopen_*` which is C-side and already covered). Re-evaluate
when a Lua project with an actual `main` convention shows up.

Tests: 1675 passed / 0 failed (+4 new in ts::walk::tests:
entry_point_main_at_module_root_tagged_binary_main,
entry_point_default_export_main_tagged_binary_main,
entry_point_non_main_function_is_none,
entry_point_main_helper_inside_class_not_tagged).
cargo check --workspace and --target wasm32-unknown-unknown both clean.
Mirror palette.rs kind + entry-point colours into CSS custom properties
so canvas-rendered and DOM-rendered surfaces share one visual identity.
Add spacing/typography scales, shell grid template variables, and
panel chrome tokens — fondations for the 4-panel layout (Explorer +
Overview + Focus Graph + Symbol Details). No existing tokens removed
or renamed; consumers fall back through var() so toolbar/hud/graph
remain unaffected.
…viz/mcp-client

Move the MCP client class + wire types out of the playground bootstrap
into a dedicated lib module so future shell consumers (Symbol Details
panel, Explorer file tree, Callers Graph popup, eventually the VSCode
webview) share one transport-agnostic surface.

Two static constructors: McpBrowse.connect(transport, info?) accepts any
SDK Transport so a future VSCode webview can pass a postMessage bridge;
McpBrowse.connectHttp(url, info?) keeps the playground's HTTP-streaming
case as a one-liner.

Adds @modelcontextprotocol/sdk as a peerDependency mirroring the
classigo/matchigo pattern, and installs it in lib/ so subpath imports
('/client', '/client/streamableHttp.js', '/shared/transport.js')
resolve under moduleResolution: bundler. Playground main.ts now imports
McpBrowse + the Browse* types from the lib instead of defining them
locally.
Minimal observable holding the shell's current focused FQDN plus a
capped MRU list. Every panel of the multi-panel shell (Symbol Details,
Focus Graph, Source View, Field Details, Callers Graph) is a lens on
this single value, so the store is intentionally trivial — one current,
one recents array, one subscriber set.

Persists snapshots to localStorage under 'standardoc:focus-state' so
the shell remembers the last focus across reloads, with a try/catch
fallback for restricted contexts (Safari private mode, sandboxed
iframes). Mirrors every mutation as a 'sd-focus-change' CustomEvent on
document for legacy components that still drive off DOM events.

Singleton 'focusStore' for the conventional shell case; the FocusStore
class is also exported so tests and embedded scenarios can isolate.
CSS-grid shell hosting five named slots (toolbar / explorer / overview
/ focus / details) per the multi-panel mockup. Children are placed by
data-slot attribute so the element itself stays logic-free — pure
styling host wrapped as a Web Component for tag-map typing uniformity.

Token-driven grid template lets the playground or VSCode webview
override --sd-shell-grid-cols / --sd-shell-grid-rows without forking
the SCSS. Ships a generic .panel chrome helper (header + body) for
pure-DOM panels to reuse. Spawnable popups (Field Details, Callers
Graph, Source View) are out of scope here — Phase 4.
Five stacked sections: search input, tree, entry points, recently
viewed, legend. Data injected via property setters (tree, entryPoints)
— element does not call MCP itself, host orchestrates the fetch and
hands shaped data in. Recents subscribe directly to the focusStore
singleton so the host doesn't need to plumb that update separately.

Click semantics: every clickable row (tree leaf with FQDN, entry point,
recent) calls focusStore.setFocus(fqdn) AND fires sd-explorer-select so
hosts can react beyond the focus change (telemetry, route sync, etc).
Tree click also toggles expand/collapse for nodes with children.

Search input is wired with a 150ms debounce emitting sd-explorer-search
— filter logic itself lives in the upcoming search component, not
inline here. Entry-point badges colour-mirror palette.rs entry-point
hue (binary_main / public_api / ffi_export). Legend is hardcoded to
the kind palette tokens for visual continuity with the canvas.
… panel

Header (name + tags + file:line + icon buttons) → tabs (Overview /
Fields / Methods / Source) → scrolling body → action footer. Data
injected via the 'symbol' property setter — host calls something like
'el.symbol = await mcp.getContext(fqdn)'-derived SymbolDetail.

Overview tab is fully wired: documentation with show-more truncation,
relations breakdown bucketed by edge kind (Used by / Uses types /
Calls / Imported by / Tested by / Implements / Extends), top-5 per
bucket with 'See all >' surfacing when total > 5, entry-point kind
badge mirroring the Explorer palette. Fields / Methods / Source tabs
render placeholders pointing to Phase 4 (Field Details + Source View
panels with Shiki/Monaco integration).

Relation-item click also calls focusStore.setFocus(target) so clicking
a 'Used by' entry shifts global focus — the upstream Explorer / Focus
Graph / future Source View all re-render off the same store. Element
also subscribes to focusStore directly so it can show the focused
FQDN in an empty-state header during the host's fetch window, instead
of going blank between focus change and symbol property arrival.
Standalone autocomplete input dropping anywhere in the host shell (top
toolbar, Explorer header, command-palette overlay). Wire contract: the
element emits sd-search-query (150ms debounced) as the user types and
expects the host to set 'results' via property setter — keeps the
element transport-agnostic so VSCode webview can route MCP through a
postMessage bridge later instead of HTTP.

Keyboard: optional Cmd/Ctrl+K global shortcut behind global-shortcut
attribute (no global side-effect when omitted), Esc clears + closes,
ArrowUp/Down navigates active item, Enter selects. Result click uses
mousedown so it fires before the input's blur-close. Selection calls
focusStore.setFocus AND emits sd-search-select so a host wrapping the
component in a command palette can react beyond the focus change.

'loading' property surfaces a search-in-flight status row so the
dropdown doesn't flicker between two empty renders during the host's
MCP fetch window.
…ojects / listSymbols / findSymbolsByPattern

Adds the four MCP endpoints the shell panels need beyond the
fetch_graph + current_revision baseline. Wire types stay one-to-one
with what the daemon emits — the playground host (and future VSCode
webview) is responsible for shaping these into UI-shaped data (the
Explorer's ExplorerTreeNode, the Symbol Details' SymbolDetail) so the
lib stays MCP-shape agnostic past this module.

getContext surfaces CALLS + IMPORTS edges by design (mirror of the
daemon-side get_context contract); the full edge breakdown needed by
the Symbol Details mockup (USES_TYPE / TESTS / IMPLEMENTS / EXTENDS /
REFERENCES) is reached by pairing with fetchNeighborhood(fqdn, ...).
That dual-fetch pattern is intentional — get_context returns
documentation and resolved-symbol enrichment that fetch_graph does
not, while fetch_graph carries every edge kind that get_context omits.
…round

Adds a second entry point on the playground dev server so the new
shell can be visually validated without breaking the legacy single-
canvas playground:

  GET /             → legacy index.html → main.js (main.ts bundle)
  GET /shell.html   → new shell.html    → shell.js (shell.ts bundle)

shell.ts wires the full multi-panel mockup against the daemon:
- panel-layout root with the 4 main slots + toolbar
- explorer: top-level project list from list_projects, entry points
  filtered client-side from list_symbols (no server-side filter yet)
- search: debounced find_symbols_by_pattern, ⌘K shortcut active
- overview: existing <standardoc-graph> driven by fetch_graph, click
  on a node feeds focusStore.setFocus
- focus slot: placeholder (Phase 3 owns the specialised FocusGraphCanvas)
- details: subscribes to focusStore — get_context + fetchNeighborhood
  pair to populate every relation bucket (CALLS + IMPORTS from
  get_context, USES_TYPE / TESTS / IMPLEMENTS / EXTENDS / REFERENCES
  from the focal neighborhood). Stale-fetch token guards focus shifts.

server.ts: bundleMain renamed bundleEntry(entry); /shell.js wired
through the same Bun.build pipeline with the scss-modules plugin so
the shell components' .module.scss imports hash + inject correctly.
Bun's resolver follows tsconfig 'paths' first and can't pivot from the
.ts-implying glob mapping ('@standarx/standardoc-viz/*' -> '../lib/src/*')
to a .scss target when the bare specifier carries no extension. So
'@standarx/standardoc-viz/styles/tokens' was unresolvable at bundle
time even though lib/package.json exports it correctly.

Drop the import and inline the :root token block in shell.html — it's
pure CSS (no SCSS features in this section) so the duplication is
mechanical. The SCSS source stays canonical for components that @use
it through relative paths; this inline copy mirrors it for the shell
chrome. Components themselves still work via their var() fallbacks
when the inline tokens are absent, but with them present the entry-
point badges and kind palette match palette.rs exactly.
Explorer gains 'expandable' + 'loading' flags on ExplorerTreeNode plus
a new 'sd-explorer-expand' event for the host to lazily populate
children on first expansion. Click on the twisty of an expandable node
with no children attached → event fires → host fetches → host mutates
the tree and re-sets the property, the panel re-renders.

shell.ts wires this for the project drill: list_symbols(module=label)
returns a project's root-level items (re-exports, top-level fns,
crate-root structs) which become leaf children with their FQDN
preserved so a click cascades to focusStore.setFocus and Symbol Details.

Deeper module navigation (intermediate module nodes) needs a different
IR query (no 'list children modules' on the daemon today) and is left
for Phase 3 alongside the Overview canvas. For now a project + one
level of root symbols is enough to validate the wiring + UX.
SUP2Ak added 28 commits June 2, 2026 17:09
- edge palette: the cross-edge kind colours/labels lived in two TS copies
  (overview.cross-edges CROSS_EDGE_KINDS + legend EDGE_ENTRIES) that had
  already drifted -- the legend omitted EXTENDS while the overview kept it.
  Hoist the canonical list into a leaf `src/edge-kinds.ts`; overview
  re-exports it, the legend derives its swatches from it (now shows every
  real edge kind, EXTENDS included). Still mirrors Rust cross_edge_style.
- kind family: search's bucketKind and symbol-details' kindFamilyTagClass
  each carried their own kind->family Set/switch with divergent
  vocabularies -- symbol-details was missing class_method / struct_field /
  class_property, so some TS members rendered untinted. Replace both with
  one leaf `src/kind-family.ts` (union vocabulary); bucketKind becomes
  kindFamily, kindFamilyTagClass maps the family to its CSS class.

No behaviour change beyond the legend now listing EXTENDS and the
kind-family fix tinting the previously-missed member kinds.
tsc --noEmit clean; playwright shell suite 3/3 logic tests green.
…reads them

Cross-layer dumb-renderer pass (lot 4). The graph shell re-derived three
things the daemon already knows:

- project_id: list_symbols now carries it via a dedicated ListSymbol
  projection (files JOIN), mirroring GraphSymbol on fetch_graph. Drops the
  shell's path-prefix inferProjectId (a copy of reconcile_files_project_id).
- is_test: list_symbols + fetch_graph stamp the symbol_looks_like_test
  verdict. Removes shell/symbols.ts::looksLikeTest and the symbol-details
  fields/methods filter; the get_context-fed relations filter keeps a
  slimmed fqdn-only looksLikeTest (follow-up lot).
- language: pruned from the fetch_graph wire (GraphSymbol) + BrowseSymbol
  (0 readers).

list_symbols exclude_tests reuses the projected is_test. Tests: new core
list_symbols_projected_* + suites green; tsc + playwright (3 logical) green.
…crash

Audit ext/vscode (daemon/supervisor + webview/MCP relay clusters), 3 findings:

- EXT-MCPCLIENT-DEAD-METHODS: drop McpClient.listSymbols/checkStale/
  currentRevision (never called - only getContext/findSymbol are wired via
  commands.ts) + their dead *Json types (ListSymbolsPageJson, CheckStaleJson,
  CheckStaleEntryJson, CurrentRevisionJson).
- EXT-RELAY-START-NORETRY: WebviewMcpRelay.ensureStarted memoised a rejected
  start() forever; reset this.started=null on failure so a later frame opens
  a fresh transport instead of wedging the relay.
- EXT-SUPERVISOR-MCP-CRASH-UNDETECTED: McpClient now fires onExit on an
  unexpected child death (stop() nulls this.child first to tell teardown
  apart); the supervisor restarts on it. A dead MCP daemon paired with a
  live LSP previously stayed stuck at `ready`.

typecheck clean; bun test 195 pass.
…runner

Lot 2 of the ext/vscode audit (quality only, no behaviour change):

- EXT-SYMBOLKIND-DEAD: remove symbolKind.ts + symbol-kind-map.ts (+ test).
  iconForSymbolKind was never called - a SymbolKind->themeIcon map kept
  alive only by its own test.
- EXT-HOVER-RUNPREVIEW-DUP: extract the shared spawn/timeout/cancel/parse
  plumbing into preview-runner.ts::runPreviewSubprocess; the stdignore +
  sxd hovers now call it (they already shared hover-render/PatternPreview).
- EXT-HOVER-DEBOUNCE-DEAD: drop HOVER_DEBOUNCE_MS + the unused
  __test_internals export (never imported, never applied).
- EXT-CMD-STRIPNEWLINE-MISNOMER: rename stripTrailingNewline ->
  ensureTrailingNewline (it normalises to one trailing newline, never strips).

typecheck clean; bun test 166 pass.
…SION-DEFAULT)

The daemon (DEFAULT_MCP_HTTP_PORT) and the proxy (DEFAULT_PROXY_PORT) both
defaulted to 7700; the daemon binds first, so the proxy hit EADDRINUSE and
crashed silently (best-effort) - it never ran. And .mcp.json pointed at the
daemon, bypassing the proxy entirely.

serve_mcp_http is already designed for `--http 0` ("typically the VSCode
supervisor passes 127.0.0.1:0"): ephemeral bind + writes the resolved URL to
mcp.endpoint + reuses the previous port for reconnect-safety. The extension
just wasn't wiring it. Restore the intended architecture:

- daemon: DEFAULT_MCP_HTTP_PORT 7700 -> 0 (ephemeral). Frees 7700.
- proxy: keeps 7700, now binds, watches mcp.endpoint, forwards.
- .mcp.json: points at the proxy route http://<bind>:<port>/ws/<id>/mcp
  (stable across daemon restarts + sibling windows). <id> via shelling out to
  `standardoc workspace-id` so it stays bit-identical to the proxy's hash.
- in-window consumers (webview, chat provider) still use the daemon directly
  via mcp.url().
- package.json: mcpHttpPort default 0 (ephemeral) + both settings clarified.

No Rust change (serve_mcp_http already handles port 0). typecheck clean;
bun test 166 pass.
…TS-GLOBALS)

There is no JsProvider — every .js/.jsx/.ts/.tsx/.vue/.svelte file flows
through TsProvider, which resolves builtins under Language::TypeScript
(build_ts_lookup hardcodes it). The TS registry omitted the ambient runtime
globals (console, window, parseInt, Proxy, Reflect, undefined, isNaN, …)
that only the JS registry carried, so every bare reference to them resolved
Unresolved — pure graph noise. The JS registry was itself write-only dead
data: no lookup ever passes Language::JavaScript.

Extract the ambient globals into a shared register_ambient_globals(reg, lang)
seeded by both js::register_all (JavaScript) and ts::register_all
(TypeScript). TS is now a strict superset of JS with zero duplication; the
JS registry stays honest for a future JsProvider.
…LTIN-C-DUP)

The C header block registered time/assert/errno/signal as Kind::Module,
then the same bare names as fn/macro/value. Both tiers are Edge, so both
reach the cold-start seeder, where insert_symbol is UPSERT-by-fqdn — the two
<builtin>::c::time entries collapsed to one row (last writer wins), silently
dropping the header's Module semantics. C never calls BuiltinRegistry::lookup,
so this was a seeding collision, not a lookup one.

Keep the .h in every header's builtin fqdn so headers form a namespace
distinct from same-named identifiers: #include <time.h> resolves to
<builtin>::c::time.h (Module), separate from time() at <builtin>::c::time
(Callable). The include emitter stops stripping .h; import-record local_name
still trims it so it stays the bare stem.
…BUILTIN-JS-ERR/KIND/INT)

- JS-ERR: seed the 4 ECMAScript error globals (SyntaxError, ReferenceError,
  EvalError, URIError) under JavaScript too, matching the TS registry.
- KIND: Math/JSON are namespace objects (member access, never constructed),
  so Kind::Module in TS — consistent with console and the JS registry
  instead of the prior JS-Module / TS-Type split.
- INT: split the signed-only abs and unsigned-only is_power_of_two /
  next_power_of_two out of the shared int-method sweep so we stop seeding
  phantom u8::abs / i8::is_power_of_two rows.
…(RUST-DEADCODE/TYPENAME-DOC)

- collect_globals: remove the `.or_else(|| { let _ = ty.span(); None })`
  no-op tail and the `use syn::spanned::Spanned;` it was the only consumer of.
- lookup: remove the `const _: Option<&ImplItem> = None;` keep-alive scaffold
  (and its `#[allow(dead_code)]`) plus the now-unused `ImplItem` import.
- type_name::substitute_template: the doc listed a closed container set for
  `T = args[0]`, but the `_` arm applies it to any single-type-param nominal
  — describe the catch-all honestly.
…UP-NESTED)

class / interface / enum / type-alias declarations nested in a function or
block body were never bound in the AOT ModuleLookup — only module-level
hoisting bound their names, so scope-local references to a nested type
resolved Unresolved. visit_fn_decl / visit_var_decl already mirror this by
binding when current_scope != ROOT; the four type-decl handlers now do the
same, binding the name at the enclosing scope before opening their own
type-container scope.
…-NSEXPORT)

The walker already emits the namespace re-export edge, but record_export_named
only handled ExportSpecifier::Named — `export * as ns from "mod"` left `ns`
unbound in the ModuleLookup (no binding, no ImportRecord), so refs to `ns`
resolved Unresolved and the cross-workspace flatten missed it. Mirror the
import-namespace shape: bind the alias to the whole source module with no
origin symbol.
…S-LOOKUP-SCOPELINE/NPM-1)

- ts::lookup::push_scope: document that ScopeRange.start_line/end_line carry
  swc byte offsets (not lines) here — nothing reads those fields, scope
  containment runs off the span_to_scope byte map. The rust builder fills
  real lines.
- externals::npm: add mts/cts to NPM_EXTENSIONS (the TS pair of mjs/cjs);
  TsProvider already maps them to TypeScript via language_for.
…t versions

The chained `assert!(reg.lookup_method(...).is_some())` form wraps differently
between rustfmt versions (chain_width heuristic). Bind each lookup to a local
first so the asserts stay single-line and format identically everywhere.
query.rs / graph.rs each had one line that didn't match `cargo fmt --all`
(method-chain and binary-op wrapping) — pre-existing on dev, not from this
branch's work. Format them so `cargo fmt --all -- --check` passes.
…EXTS/WARN-ISTERMINAL)

- targets_non_source_file: add vue/svelte to SOURCE_EXTS — the SFC extractor
  indexes them, so a Read should hit the MCP-first gate like any .ts/.rs file
  instead of being waved through as non-source.
- emit_warn: drop the dead `let _ = stderr.is_terminal();` (and the now-unused
  IsTerminal import) and correct the doc — lines are always emitted, never
  no-op'd on TTY/pipe state.
…ts (GV-FOCUS-INDIRECT-STALEDOC)

Three doc sites (module header, Bucket enum, layout()) claimed depth-2+
neighbours with no direct edge are "dropped/skipped entirely" — V0 behaviour.
bucket_for_neighbor actually falls them through to Bucket::Indirect (due
south) so multi-hop views surface them. Align the docs with the code.
…-LSP-SELECTOR/MCPCLIENT-NO-ISERROR)

- lsp/client documentSelector: add `lua` and `c` so editor LSP features
  (hover/diagnostics) activate on .lua/.c/.h files the daemon already indexes.
- mcp/client.callTool: check result.isError and throw instead of returning the
  daemon's error payload as if it were a successful text result.
…EXT-INIT-JSONC)

Claude Code tolerates comments and trailing commas in .claude/settings.json,
but init did a strict JSON.parse → such a file fell to `invalid` and init
declined to auto-merge. Add a string-aware stripJsonc (comments + trailing
commas, string-literal safe) and retry through it on parse failure, leaving
the plain-JSON path untouched.
…ALK-RENDER-DUP)

render_expr_name was functionally identical to render_member_expr_name and
render_ts_entity_name a pure pass-through. Drop both and point the call sites
at render_member_expr_name. No behaviour change (938 tests unchanged).
…(TS-EXTRACT-DUPS/PROPNAME-DUP)

prop_name_static (ffi_tagger) and method_name_string (extract_items) were
identical PropName->Option<String> extractors; ts_enum_member_id_name was
duplicated across extract_items and lookup. Consolidate into ts::helpers as
prop_name_static + ts_enum_member_id_name; drop the three copies. No behaviour
change (938 tests unchanged).
…e generic (TS-VISIT-FORUSE-REP)

The six near-identical visit_{type_ann,type_params,ts_type,pat,ts_fn_param,
ts_type_param_decl}_for_uses wrappers differed only in node type. Replace them
with a single visit_for_uses<N: for<'a,'b> VisitWith<CallVisitor<'a,'b>>> and
redirect the 13 call sites. CallVisitor becomes pub(crate) so it can appear in
the bound. No behaviour change (938 tests unchanged).
…ORD)

Extractors stamped columns in mixed units: TS used swc col_display
(tab-expanded), C used tree-sitter byte offsets, Lua used full_moon's
1-based char count, and the Module symbol used byte length. The LSP /
VSCode consumers forward the column straight into Position.character,
which is UTF-16 code units, so navigation drifted on tabs and on any
multibyte character before a symbol.

Normalize every extractor on UTF-16; consumers stay unchanged:
- utils::location: utf16_len / utf16_col / line_and_utf16_col helpers;
  content_extent now measures the last line in UTF-16.
- TS: read swc Loc.col, which is already UTF-16 (its multibyte adjustment
  maps a 4-byte char to two units), instead of the tab-expanded
  col_display; drop the now-dead clamp_col.
- C: convert tree-sitter byte columns against the source line.
- Lua: derive line + UTF-16 column from full_moon's absolute byte offset,
  which also corrects the previous 1-based off-by-one column.
- Rust: proc-macro2 exposes no source text here, so its char columns stay
  (equal to UTF-16 except for astral chars); documented on
  span_to_location.

Adds decisive non-ASCII column tests per provider.
…sx (TS-DTS-MINOR)

syntax_for only set dts:true for `.d.ts`, so the ambient-declaration
parsing leniency never reached the `.d.mts` / `.d.cts` / `.d.tsx`
variants already listed in TS_CONVENTIONS.
…UTF8)

The non-identifier branch copied a raw byte via `ch as char`, which
splits a multi-byte character into separate Latin-1 chars. Copy the whole
UTF-8 char instead and advance by its byte length. Latent today (the
templates are ASCII), hardened with a multibyte round-trip test.
…-COORD)

extract_sfc emitted each Vue/Svelte template-ref REFERENCES edge with a
Site column computed by byte_offset_to_line_col (byte columns) — the last
column producer still off the UTF-16 convention, so a multibyte char
before an interpolation drifted the column.

Point it at line_and_utf16_col (line + UTF-16 column from an absolute byte
offset) and remove the now-unused byte-column helper and its tests.
…ime (RUST-CRATENAME-CACHE-STALE)

The per-Cargo.toml crate-name cache never invalidated, so renaming
[package].name left stale FQDNs until a daemon restart. Key the cached
value on the Cargo.toml mtime and only count a hit when it still matches:
unchanged manifests keep the single-read fast path, a rename is picked up
on the next extraction. Fully local to resolve_crate_info — no
cross-crate invalidation plumbing.
…er_type

A `.method()` call on Arc/Rc/Box captured receiver_type as the pointer
("Arc") instead of the pointee, so e.g. `Arc<RwLock<T>>::read()` recorded
"Arc" and never resolved against RwLock. Peel the pure Deref wrappers
(Arc/Rc/Box) before taking the nominal, with a denylist for the pointers'
own inherent methods (clone/strong_count/downgrade/...) where the pointer
itself IS the receiver. Conservative: Mutex/RwLock/RefCell keep their
nominal because lock/read/borrow genuinely are their methods.

Direct helper tests + behavioral tests. The aggregate resolution delta is
not measured here (would need a live rescan); the full suite shows no
behavioral regression.
…fn + unused enum)

`TsProvider::extract_external_dts` was a public method whose body was just
`todo!()`, and the `TsExtractMode` enum carried `#[allow(dead_code)]` and was
never threaded through the visit layer: the external-package `.d.ts` indexing
feature was scaffolded but never wired (0 callers anywhere). A panicking
public method has no place in a release base — remove the dead scaffold. The
feature itself is tracked for a later release.
@SUP2Ak SUP2Ak merged commit 9b9729e into main Jun 3, 2026
19 checks passed
@SUP2Ak SUP2Ak deleted the dev branch June 3, 2026 18:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant