gitnexus 1.6.3 → 1.6.4-rc.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/dist/_shared/index.d.ts +1 -1
  2. package/dist/_shared/index.d.ts.map +1 -1
  3. package/dist/_shared/index.js +1 -1
  4. package/dist/_shared/index.js.map +1 -1
  5. package/dist/_shared/scope-resolution/finalize-algorithm.d.ts +22 -14
  6. package/dist/_shared/scope-resolution/finalize-algorithm.d.ts.map +1 -1
  7. package/dist/_shared/scope-resolution/finalize-algorithm.js +298 -37
  8. package/dist/_shared/scope-resolution/finalize-algorithm.js.map +1 -1
  9. package/dist/_shared/scope-resolution/scope-tree.d.ts +23 -1
  10. package/dist/_shared/scope-resolution/scope-tree.d.ts.map +1 -1
  11. package/dist/_shared/scope-resolution/scope-tree.js +36 -2
  12. package/dist/_shared/scope-resolution/scope-tree.js.map +1 -1
  13. package/dist/_shared/scope-resolution/types.d.ts +47 -3
  14. package/dist/_shared/scope-resolution/types.d.ts.map +1 -1
  15. package/dist/_shared/scope-resolution/types.js +10 -2
  16. package/dist/_shared/scope-resolution/types.js.map +1 -1
  17. package/dist/core/embeddings/embedder.js +2 -1
  18. package/dist/core/ingestion/call-processor.js +2 -2
  19. package/dist/core/ingestion/constants.d.ts +4 -3
  20. package/dist/core/ingestion/constants.js +8 -3
  21. package/dist/core/ingestion/finalize-orchestrator.js +6 -3
  22. package/dist/core/ingestion/heritage-processor.js +2 -2
  23. package/dist/core/ingestion/import-processor.js +1 -1
  24. package/dist/core/ingestion/languages/csharp/captures.js +4 -1
  25. package/dist/core/ingestion/languages/csharp/namespace-siblings.d.ts +14 -13
  26. package/dist/core/ingestion/languages/csharp/namespace-siblings.js +62 -50
  27. package/dist/core/ingestion/languages/python/captures.js +9 -1
  28. package/dist/core/ingestion/languages/python/index.d.ts +1 -1
  29. package/dist/core/ingestion/languages/python/index.js +1 -1
  30. package/dist/core/ingestion/languages/python/simple-hooks.d.ts +3 -1
  31. package/dist/core/ingestion/languages/python/simple-hooks.js +8 -0
  32. package/dist/core/ingestion/languages/python.js +2 -1
  33. package/dist/core/ingestion/languages/typescript/arity-metadata.d.ts +59 -0
  34. package/dist/core/ingestion/languages/typescript/arity-metadata.js +103 -0
  35. package/dist/core/ingestion/languages/typescript/arity.d.ts +37 -0
  36. package/dist/core/ingestion/languages/typescript/arity.js +54 -0
  37. package/dist/core/ingestion/languages/typescript/cache-stats.d.ts +17 -0
  38. package/dist/core/ingestion/languages/typescript/cache-stats.js +28 -0
  39. package/dist/core/ingestion/languages/typescript/captures.d.ts +28 -0
  40. package/dist/core/ingestion/languages/typescript/captures.js +451 -0
  41. package/dist/core/ingestion/languages/typescript/import-decomposer.d.ts +49 -0
  42. package/dist/core/ingestion/languages/typescript/import-decomposer.js +371 -0
  43. package/dist/core/ingestion/languages/typescript/import-target.d.ts +50 -0
  44. package/dist/core/ingestion/languages/typescript/import-target.js +61 -0
  45. package/dist/core/ingestion/languages/typescript/index.d.ts +94 -0
  46. package/dist/core/ingestion/languages/typescript/index.js +94 -0
  47. package/dist/core/ingestion/languages/typescript/interpret.d.ts +35 -0
  48. package/dist/core/ingestion/languages/typescript/interpret.js +317 -0
  49. package/dist/core/ingestion/languages/typescript/merge-bindings.d.ts +62 -0
  50. package/dist/core/ingestion/languages/typescript/merge-bindings.js +158 -0
  51. package/dist/core/ingestion/languages/typescript/query.d.ts +77 -0
  52. package/dist/core/ingestion/languages/typescript/query.js +778 -0
  53. package/dist/core/ingestion/languages/typescript/receiver-binding.d.ts +59 -0
  54. package/dist/core/ingestion/languages/typescript/receiver-binding.js +171 -0
  55. package/dist/core/ingestion/languages/typescript/scope-resolver.d.ts +16 -0
  56. package/dist/core/ingestion/languages/typescript/scope-resolver.js +113 -0
  57. package/dist/core/ingestion/languages/typescript/simple-hooks.d.ts +71 -0
  58. package/dist/core/ingestion/languages/typescript/simple-hooks.js +131 -0
  59. package/dist/core/ingestion/languages/typescript.js +19 -0
  60. package/dist/core/ingestion/model/scope-resolution-indexes.d.ts +14 -1
  61. package/dist/core/ingestion/parsing-processor.js +3 -3
  62. package/dist/core/ingestion/registry-primary-flag.d.ts +3 -1
  63. package/dist/core/ingestion/registry-primary-flag.js +4 -1
  64. package/dist/core/ingestion/scope-extractor-bridge.d.ts +5 -2
  65. package/dist/core/ingestion/scope-extractor-bridge.js +7 -2
  66. package/dist/core/ingestion/scope-extractor.js +19 -18
  67. package/dist/core/ingestion/scope-resolution/contract/scope-resolver.d.ts +73 -11
  68. package/dist/core/ingestion/scope-resolution/contract/scope-resolver.js +48 -10
  69. package/dist/core/ingestion/scope-resolution/passes/compound-receiver.js +283 -14
  70. package/dist/core/ingestion/scope-resolution/passes/imported-return-types.d.ts +23 -2
  71. package/dist/core/ingestion/scope-resolution/passes/imported-return-types.js +109 -37
  72. package/dist/core/ingestion/scope-resolution/passes/mro.js +3 -1
  73. package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +13 -5
  74. package/dist/core/ingestion/scope-resolution/pipeline/phase.js +11 -2
  75. package/dist/core/ingestion/scope-resolution/pipeline/registry.js +2 -0
  76. package/dist/core/ingestion/scope-resolution/pipeline/run.d.ts +8 -0
  77. package/dist/core/ingestion/scope-resolution/pipeline/run.js +21 -5
  78. package/dist/core/ingestion/scope-resolution/pipeline/validate-bindings-immutability.d.ts +39 -0
  79. package/dist/core/ingestion/scope-resolution/pipeline/validate-bindings-immutability.js +65 -0
  80. package/dist/core/ingestion/scope-resolution/scope/walkers.d.ts +54 -11
  81. package/dist/core/ingestion/scope-resolution/scope/walkers.js +105 -30
  82. package/dist/core/ingestion/utils/ast-helpers.d.ts +2 -0
  83. package/dist/core/ingestion/utils/ast-helpers.js +12 -0
  84. package/dist/core/ingestion/utils/env.d.ts +10 -0
  85. package/dist/core/ingestion/utils/env.js +14 -0
  86. package/dist/core/ingestion/workers/parse-worker.js +3 -3
  87. package/dist/core/lbug/lbug-adapter.d.ts +3 -4
  88. package/dist/core/lbug/lbug-adapter.js +6 -9
  89. package/dist/core/run-analyze.js +4 -6
  90. package/dist/core/search/bm25-index.d.ts +0 -17
  91. package/dist/core/search/bm25-index.js +10 -118
  92. package/dist/core/search/fts-indexes.d.ts +1 -0
  93. package/dist/core/search/fts-indexes.js +7 -0
  94. package/dist/core/search/fts-schema.d.ts +6 -0
  95. package/dist/core/search/fts-schema.js +7 -0
  96. package/dist/mcp/core/embedder.js +3 -1
  97. package/package.json +1 -1
  98. package/skills/gitnexus-cli.md +1 -1
@@ -56,7 +56,9 @@ import { SupportedLanguages } from '../../_shared/index.js';
56
56
  * so this set also controls what gets silenced in the legacy DAG.
57
57
  *
58
58
  * Add a language here ONLY after shadow parity ≥ 99% fixtures / ≥ 98%
59
- * corpus per RFC §6.4. The parity CI gate will block the PR otherwise.
59
+ * corpus per RFC §6.4. TypeScript is temporarily accepted under the
60
+ * Ring 3 CI parity gate while corpus-level shadow-mode wiring is tracked
61
+ * in #927 for this migration.
60
62
  *
61
63
  * The set is intentionally a static TypeScript literal (not a JSON import,
62
64
  * not an env lookup) so CI can discover it via `tsx` without a build step
@@ -65,6 +67,7 @@ import { SupportedLanguages } from '../../_shared/index.js';
65
67
  export const MIGRATED_LANGUAGES = new Set([
66
68
  SupportedLanguages.Python,
67
69
  SupportedLanguages.CSharp,
70
+ SupportedLanguages.TypeScript,
68
71
  ]);
69
72
  /**
70
73
  * Return the env-var name that controls a given language's registry-
@@ -13,8 +13,11 @@
13
13
  * `emitScopeCaptures`. Returns `undefined`; zero work done. This is
14
14
  * the state of every language today — `ParsedFile` production stays
15
15
  * dormant until a language migrates.
16
- * 2. Invokes the hook + feeds its output to `ScopeExtractor.extract`.
17
- * 3. **Swallows exceptions from either side.** A failure here returns
16
+ * 2. Short-circuits empty / whitespace-only files. There is no scope
17
+ * content to extract, and some tree-sitter queries do not match an
18
+ * otherwise valid empty root node.
19
+ * 3. Invokes the hook + feeds its output to `ScopeExtractor.extract`.
20
+ * 4. **Swallows exceptions from either side.** A failure here returns
18
21
  * `undefined` and emits a warning via `onWarn`; legacy parsing on
19
22
  * the same file continues unaffected by the scope-extraction miss.
20
23
  * Scope-based resolution is the new path under construction — it
@@ -13,8 +13,11 @@
13
13
  * `emitScopeCaptures`. Returns `undefined`; zero work done. This is
14
14
  * the state of every language today — `ParsedFile` production stays
15
15
  * dormant until a language migrates.
16
- * 2. Invokes the hook + feeds its output to `ScopeExtractor.extract`.
17
- * 3. **Swallows exceptions from either side.** A failure here returns
16
+ * 2. Short-circuits empty / whitespace-only files. There is no scope
17
+ * content to extract, and some tree-sitter queries do not match an
18
+ * otherwise valid empty root node.
19
+ * 3. Invokes the hook + feeds its output to `ScopeExtractor.extract`.
20
+ * 4. **Swallows exceptions from either side.** A failure here returns
18
21
  * `undefined` and emits a warning via `onWarn`; legacy parsing on
19
22
  * the same file continues unaffected by the scope-extraction miss.
20
23
  * Scope-based resolution is the new path under construction — it
@@ -29,6 +32,8 @@ import { extract as extractScope } from './scope-extractor.js';
29
32
  export function extractParsedFile(provider, sourceText, filePath, onWarn, cachedTree) {
30
33
  if (provider.emitScopeCaptures === undefined)
31
34
  return undefined;
35
+ if (sourceText.trim().length === 0)
36
+ return undefined;
32
37
  try {
33
38
  const captures = provider.emitScopeCaptures(sourceText, filePath, cachedTree);
34
39
  return extractScope(captures, filePath, provider);
@@ -58,7 +58,7 @@
58
58
  * - `ParsedFile.localDefs` — flattened union of `Scope.ownedDefs`.
59
59
  * - `ParsedFile.referenceSites` — pre-resolution usage facts.
60
60
  */
61
- import { buildPositionIndex, buildScopeTree, makeScopeId } from '../../_shared/index.js';
61
+ import { buildPositionIndex, buildScopeTree, canParentScope, makeScopeId } from '../../_shared/index.js';
62
62
  // ─── Public entry point ─────────────────────────────────────────────────────
63
63
  /**
64
64
  * Drive the five extraction passes and return a `ParsedFile`.
@@ -210,7 +210,11 @@ function pass1BuildScopes(matches, filePath, provider) {
210
210
  candidates.push({ match, range: anchor.range, kind, id });
211
211
  }
212
212
  // Sort by (startLine, startCol) ASC, (endLine, endCol) DESC so outer
213
- // scopes appear before their children for parent-resolution.
213
+ // scopes appear before their children for parent-resolution. When two
214
+ // candidates have exactly equal ranges (e.g. a `compilation_unit` and
215
+ // the only top-level scope in the file — see `canParentScope`), Module
216
+ // sorts first so it lands on the stack ahead of the candidate that will
217
+ // claim it as parent.
214
218
  candidates.sort((a, b) => {
215
219
  if (a.range.startLine !== b.range.startLine)
216
220
  return a.range.startLine - b.range.startLine;
@@ -218,13 +222,23 @@ function pass1BuildScopes(matches, filePath, provider) {
218
222
  return a.range.startCol - b.range.startCol;
219
223
  if (a.range.endLine !== b.range.endLine)
220
224
  return b.range.endLine - a.range.endLine;
221
- return b.range.endCol - a.range.endCol;
225
+ if (a.range.endCol !== b.range.endCol)
226
+ return b.range.endCol - a.range.endCol;
227
+ if (a.kind === b.kind)
228
+ return 0;
229
+ if (a.kind === 'Module')
230
+ return -1;
231
+ if (b.kind === 'Module')
232
+ return 1;
233
+ return 0;
222
234
  });
223
235
  const drafts = [];
224
236
  const stack = []; // enclosing real scopes, outermost at [0]
225
237
  for (const cand of candidates) {
226
- // Pop the stack until the top strictly contains this candidate.
227
- while (stack.length > 0 && !rangeStrictlyContains(stack[stack.length - 1].range, cand.range)) {
238
+ // Pop the stack until the top can parent this candidate (strict
239
+ // containment, plus the equal-range Module carve-out).
240
+ while (stack.length > 0 &&
241
+ !canParentScope(stack[stack.length - 1].range, cand.range, stack[stack.length - 1].kind, cand.kind)) {
228
242
  stack.pop();
229
243
  }
230
244
  const parent = stack.length > 0 ? stack[stack.length - 1].id : null;
@@ -692,19 +706,6 @@ function rangesEqual(a, b) {
692
706
  a.endLine === b.endLine &&
693
707
  a.endCol === b.endCol);
694
708
  }
695
- function rangeStrictlyContains(outer, inner) {
696
- if (outer.startLine === inner.startLine &&
697
- outer.startCol === inner.startCol &&
698
- outer.endLine === inner.endLine &&
699
- outer.endCol === inner.endCol) {
700
- return false;
701
- }
702
- const startsBefore = outer.startLine < inner.startLine ||
703
- (outer.startLine === inner.startLine && outer.startCol <= inner.startCol);
704
- const endsAfter = outer.endLine > inner.endLine ||
705
- (outer.endLine === inner.endLine && outer.endCol >= inner.endCol);
706
- return startsBefore && endsAfter;
707
- }
708
709
  /**
709
710
  * Capture names that are never anchors — they are sub-tags nested inside a
710
711
  * larger anchor (e.g., the receiver expression inside a `@reference.call`
@@ -88,14 +88,21 @@
88
88
  * per-(caller, target) collapse semantics require multiple call
89
89
  * sites in the same caller body not produce multiple edges.
90
90
  *
91
- * - **I3 — `propagateImportedReturnTypes` mutation timing.** The
92
- * pass mutates `Scope.typeBindings` (a plain `new Map(...)` from
91
+ * - **I3 — `propagateImportedReturnTypes` mutation timing + ordering.**
92
+ * The pass mutates `Scope.typeBindings` (a plain `new Map(...)` from
93
93
  * `draftToScope`, NOT frozen). It MUST run AFTER `finalizeScopeModel`
94
94
  * (so `indexes.bindings` is populated) and BEFORE
95
95
  * `resolveReferenceSites` (so resolution sees the propagated types).
96
96
  * The pass also re-runs `followChainPostFinalize` on every scope's
97
97
  * typeBindings because scope-extractor's pass-4 already ran and
98
98
  * missed any chain whose terminal lives in a foreign file.
99
+ * Within the pass, files are walked in `indexes.sccs` reverse-
100
+ * topological order (leaves first) so multi-hop alias chains
101
+ * (e.g. `models.User → service.user → app.user`) collapse to the
102
+ * terminal class in a single pass — every importer sees its
103
+ * source's already-chain-followed typeBindings. Cyclic SCCs reach
104
+ * a partial fixpoint within a single pass without iterating to
105
+ * convergence; `ts-circular` only asserts pipeline-no-throw.
99
106
  *
100
107
  * - **I4 — `emitReceiverBoundCalls` case order.** Cases are evaluated
101
108
  * in this order; the FIRST that emits an edge wins:
@@ -129,14 +136,45 @@
129
136
  * once per workspace at resolve time), and merging would create a
130
137
  * god-interface that complicates future migrations.
131
138
  *
132
- * - **I8 — Post-finalize hooks may mutate `Scope.typeBindings` and
133
- * `indexes.bindings`.** `propagateImportedReturnTypes` and
134
- * `populateNamespaceSiblings` both write to these structures via
135
- * `as Map<...>` casts through `ReadonlyMap` facades. Downstream
136
- * consumers MUST NOT freeze or snapshot these maps before all
137
- * post-finalize hooks have run. The `ReadonlyMap<...>` type on
138
- * `ScopeResolutionIndexes` is a read-guidance surface for
139
- * consumers, NOT an immutability promise during the resolve phase.
139
+ * - **I8 — Two-channel binding lifecycle.**
140
+ * `indexes.bindings` is the **finalize-output channel**. After
141
+ * `finalizeScopeModel` returns, its inner `BindingRef[]` arrays
142
+ * are deep-frozen by `materializeBindings` and MUST NOT be
143
+ * mutated by any post-finalize hook. Treat `indexes.bindings` as
144
+ * immutable from the moment `finalizeScopeModel` returns.
145
+ *
146
+ * `indexes.bindingAugmentations` is the **post-finalize
147
+ * append-only channel**. Hooks like `populateNamespaceSiblings`
148
+ * append cross-file bindings synthesized after finalize (C#
149
+ * same-namespace visibility, `using static` member exposure)
150
+ * into this channel, NOT into `indexes.bindings`. Inner arrays
151
+ * here are NEVER frozen — hooks `push()` directly. Any consumer
152
+ * that reads post-finalize workspace bindings MUST query both
153
+ * index channels via `lookupBindingsAt`
154
+ * (`scope-resolution/scope/walkers.ts`); the helper returns
155
+ * finalized refs first, appends unique augmentation refs after,
156
+ * and dedupes by `def.nodeId` so finalized metadata wins on
157
+ * duplicate defs. Per-`Scope.bindings` local declarations are the
158
+ * lexical extraction channel and remain a separate first-tier
159
+ * lookup for local shadowing.
160
+ *
161
+ * `Scope.typeBindings` remains mutable post-finalize per I6 (it
162
+ * is intentionally not frozen at any point).
163
+ *
164
+ * The `ReadonlyMap<...>` types on `ScopeResolutionIndexes` are
165
+ * compile-time read-guidance for consumers; structural mutation
166
+ * of `bindingAugmentations` is performed via a deliberate
167
+ * `as Map<...>` cast inside the hook implementations and is the
168
+ * ONLY sanctioned channel for post-finalize binding fanout.
169
+ *
170
+ * The dev-mode runtime validator
171
+ * (`validateBindingsImmutability` in
172
+ * `scope-resolution/validate-bindings-immutability.ts`) surfaces
173
+ * any drift — i.e. a hook writing to `indexes.bindings` instead
174
+ * of `bindingAugmentations`, or producing a non-frozen finalized
175
+ * bucket — via `onWarn` when explicitly enabled by
176
+ * `NODE_ENV === 'development' || VALIDATE_SEMANTIC_MODEL === '1'`
177
+ * (`VALIDATE_SEMANTIC_MODEL=0` is an explicit off switch).
140
178
  *
141
179
  * - **I9 — `SemanticModel` is the single authoritative symbol store.**
142
180
  * Every symbol-indexed lookup (key = `nodeId | simpleName |
@@ -244,8 +282,32 @@ export interface ScopeResolver {
244
282
  * resolvers that must distinguish "this module exists in the repo"
245
283
  * from "this module is external" (Python's fallback resolver, for
246
284
  * example).
285
+ *
286
+ * `resolutionConfig` is the opaque value returned by
287
+ * `loadResolutionConfig` (loaded once per workspace pass by the
288
+ * orchestrator). TypeScript uses this to thread `tsconfig.json` path
289
+ * aliases through to the standard resolver. Languages that don't
290
+ * need any extra config ignore the parameter.
291
+ */
292
+ resolveImportTarget(targetRaw: string, fromFile: string, allFilePaths: ReadonlySet<string>, resolutionConfig?: unknown): string | null;
293
+ /**
294
+ * Optional one-shot loader for cross-file import-resolution config
295
+ * (e.g. tsconfig path aliases for TypeScript, go.mod paths for Go,
296
+ * composer.json autoload for PHP). The orchestrator calls this once
297
+ * per workspace pass with the repo root and threads the result into
298
+ * every subsequent `resolveImportTarget` call as the
299
+ * `resolutionConfig` parameter.
300
+ *
301
+ * Languages that don't need any per-workspace config leave this
302
+ * undefined; the orchestrator threads `undefined` to
303
+ * `resolveImportTarget` in that case. Returning `null` is also
304
+ * supported and equivalent to "no config available".
305
+ *
306
+ * May be sync or async — the orchestrator awaits the result. The
307
+ * shape is opaque to the orchestrator (`unknown`); the per-language
308
+ * `resolveImportTarget` casts it to the language's expected shape.
247
309
  */
248
- resolveImportTarget(targetRaw: string, fromFile: string, allFilePaths: ReadonlySet<string>): string | null;
310
+ loadResolutionConfig?(repoPath: string): Promise<unknown> | unknown;
249
311
  /**
250
312
  * Per-scope binding-merge precedence. The shared finalize pass
251
313
  * collects bindings from multiple sources (local declarations,
@@ -88,14 +88,21 @@
88
88
  * per-(caller, target) collapse semantics require multiple call
89
89
  * sites in the same caller body not produce multiple edges.
90
90
  *
91
- * - **I3 — `propagateImportedReturnTypes` mutation timing.** The
92
- * pass mutates `Scope.typeBindings` (a plain `new Map(...)` from
91
+ * - **I3 — `propagateImportedReturnTypes` mutation timing + ordering.**
92
+ * The pass mutates `Scope.typeBindings` (a plain `new Map(...)` from
93
93
  * `draftToScope`, NOT frozen). It MUST run AFTER `finalizeScopeModel`
94
94
  * (so `indexes.bindings` is populated) and BEFORE
95
95
  * `resolveReferenceSites` (so resolution sees the propagated types).
96
96
  * The pass also re-runs `followChainPostFinalize` on every scope's
97
97
  * typeBindings because scope-extractor's pass-4 already ran and
98
98
  * missed any chain whose terminal lives in a foreign file.
99
+ * Within the pass, files are walked in `indexes.sccs` reverse-
100
+ * topological order (leaves first) so multi-hop alias chains
101
+ * (e.g. `models.User → service.user → app.user`) collapse to the
102
+ * terminal class in a single pass — every importer sees its
103
+ * source's already-chain-followed typeBindings. Cyclic SCCs reach
104
+ * a partial fixpoint within a single pass without iterating to
105
+ * convergence; `ts-circular` only asserts pipeline-no-throw.
99
106
  *
100
107
  * - **I4 — `emitReceiverBoundCalls` case order.** Cases are evaluated
101
108
  * in this order; the FIRST that emits an edge wins:
@@ -129,14 +136,45 @@
129
136
  * once per workspace at resolve time), and merging would create a
130
137
  * god-interface that complicates future migrations.
131
138
  *
132
- * - **I8 — Post-finalize hooks may mutate `Scope.typeBindings` and
133
- * `indexes.bindings`.** `propagateImportedReturnTypes` and
134
- * `populateNamespaceSiblings` both write to these structures via
135
- * `as Map<...>` casts through `ReadonlyMap` facades. Downstream
136
- * consumers MUST NOT freeze or snapshot these maps before all
137
- * post-finalize hooks have run. The `ReadonlyMap<...>` type on
138
- * `ScopeResolutionIndexes` is a read-guidance surface for
139
- * consumers, NOT an immutability promise during the resolve phase.
139
+ * - **I8 — Two-channel binding lifecycle.**
140
+ * `indexes.bindings` is the **finalize-output channel**. After
141
+ * `finalizeScopeModel` returns, its inner `BindingRef[]` arrays
142
+ * are deep-frozen by `materializeBindings` and MUST NOT be
143
+ * mutated by any post-finalize hook. Treat `indexes.bindings` as
144
+ * immutable from the moment `finalizeScopeModel` returns.
145
+ *
146
+ * `indexes.bindingAugmentations` is the **post-finalize
147
+ * append-only channel**. Hooks like `populateNamespaceSiblings`
148
+ * append cross-file bindings synthesized after finalize (C#
149
+ * same-namespace visibility, `using static` member exposure)
150
+ * into this channel, NOT into `indexes.bindings`. Inner arrays
151
+ * here are NEVER frozen — hooks `push()` directly. Any consumer
152
+ * that reads post-finalize workspace bindings MUST query both
153
+ * index channels via `lookupBindingsAt`
154
+ * (`scope-resolution/scope/walkers.ts`); the helper returns
155
+ * finalized refs first, appends unique augmentation refs after,
156
+ * and dedupes by `def.nodeId` so finalized metadata wins on
157
+ * duplicate defs. Per-`Scope.bindings` local declarations are the
158
+ * lexical extraction channel and remain a separate first-tier
159
+ * lookup for local shadowing.
160
+ *
161
+ * `Scope.typeBindings` remains mutable post-finalize per I6 (it
162
+ * is intentionally not frozen at any point).
163
+ *
164
+ * The `ReadonlyMap<...>` types on `ScopeResolutionIndexes` are
165
+ * compile-time read-guidance for consumers; structural mutation
166
+ * of `bindingAugmentations` is performed via a deliberate
167
+ * `as Map<...>` cast inside the hook implementations and is the
168
+ * ONLY sanctioned channel for post-finalize binding fanout.
169
+ *
170
+ * The dev-mode runtime validator
171
+ * (`validateBindingsImmutability` in
172
+ * `scope-resolution/validate-bindings-immutability.ts`) surfaces
173
+ * any drift — i.e. a hook writing to `indexes.bindings` instead
174
+ * of `bindingAugmentations`, or producing a non-frozen finalized
175
+ * bucket — via `onWarn` when explicitly enabled by
176
+ * `NODE_ENV === 'development' || VALIDATE_SEMANTIC_MODEL === '1'`
177
+ * (`VALIDATE_SEMANTIC_MODEL=0` is an explicit off switch).
140
178
  *
141
179
  * - **I9 — `SemanticModel` is the single authoritative symbol store.**
142
180
  * Every symbol-indexed lookup (key = `nodeId | simpleName |