gitnexus 1.6.4-rc.2 → 1.6.4-rc.21

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 (243) hide show
  1. package/README.md +35 -0
  2. package/dist/_shared/index.d.ts +1 -1
  3. package/dist/_shared/index.d.ts.map +1 -1
  4. package/dist/_shared/index.js +1 -1
  5. package/dist/_shared/index.js.map +1 -1
  6. package/dist/_shared/scope-resolution/finalize-algorithm.d.ts +22 -14
  7. package/dist/_shared/scope-resolution/finalize-algorithm.d.ts.map +1 -1
  8. package/dist/_shared/scope-resolution/finalize-algorithm.js +298 -37
  9. package/dist/_shared/scope-resolution/finalize-algorithm.js.map +1 -1
  10. package/dist/_shared/scope-resolution/scope-tree.d.ts +23 -1
  11. package/dist/_shared/scope-resolution/scope-tree.d.ts.map +1 -1
  12. package/dist/_shared/scope-resolution/scope-tree.js +36 -2
  13. package/dist/_shared/scope-resolution/scope-tree.js.map +1 -1
  14. package/dist/_shared/scope-resolution/types.d.ts +47 -3
  15. package/dist/_shared/scope-resolution/types.d.ts.map +1 -1
  16. package/dist/_shared/scope-resolution/types.js +10 -2
  17. package/dist/_shared/scope-resolution/types.js.map +1 -1
  18. package/dist/cli/analyze.d.ts +6 -0
  19. package/dist/cli/analyze.js +35 -0
  20. package/dist/cli/doctor.d.ts +1 -0
  21. package/dist/cli/doctor.js +31 -0
  22. package/dist/cli/index.js +13 -0
  23. package/dist/cli/setup.js +2 -2
  24. package/dist/core/embeddings/config.d.ts +2 -0
  25. package/dist/core/embeddings/config.js +36 -0
  26. package/dist/core/embeddings/embedder.js +11 -6
  27. package/dist/core/embeddings/embedding-pipeline.d.ts +7 -1
  28. package/dist/core/embeddings/embedding-pipeline.js +93 -29
  29. package/dist/core/embeddings/exact-search.d.ts +15 -0
  30. package/dist/core/embeddings/exact-search.js +27 -0
  31. package/dist/core/embeddings/types.d.ts +4 -0
  32. package/dist/core/embeddings/types.js +2 -0
  33. package/dist/core/group/config-parser.js +2 -0
  34. package/dist/core/group/matching.d.ts +3 -3
  35. package/dist/core/group/matching.js +46 -6
  36. package/dist/core/group/storage.js +2 -0
  37. package/dist/core/group/sync.js +1 -1
  38. package/dist/core/group/types.d.ts +18 -0
  39. package/dist/core/ingestion/call-processor.d.ts +3 -3
  40. package/dist/core/ingestion/call-processor.js +58 -65
  41. package/dist/core/ingestion/constants.d.ts +4 -3
  42. package/dist/core/ingestion/constants.js +8 -3
  43. package/dist/core/ingestion/finalize-orchestrator.js +6 -3
  44. package/dist/core/ingestion/heritage-processor.js +2 -2
  45. package/dist/core/ingestion/import-processor.js +1 -1
  46. package/dist/core/ingestion/language-provider.d.ts +8 -0
  47. package/dist/core/ingestion/languages/csharp/captures.js +4 -1
  48. package/dist/core/ingestion/languages/csharp/namespace-siblings.d.ts +14 -13
  49. package/dist/core/ingestion/languages/csharp/namespace-siblings.js +62 -50
  50. package/dist/core/ingestion/languages/python/captures.js +9 -1
  51. package/dist/core/ingestion/languages/python/index.d.ts +1 -1
  52. package/dist/core/ingestion/languages/python/index.js +1 -1
  53. package/dist/core/ingestion/languages/python/simple-hooks.d.ts +3 -1
  54. package/dist/core/ingestion/languages/python/simple-hooks.js +8 -0
  55. package/dist/core/ingestion/languages/python.js +28 -1
  56. package/dist/core/ingestion/languages/swift.js +14 -0
  57. package/dist/core/ingestion/languages/typescript/arity-metadata.d.ts +59 -0
  58. package/dist/core/ingestion/languages/typescript/arity-metadata.js +103 -0
  59. package/dist/core/ingestion/languages/typescript/arity.d.ts +37 -0
  60. package/dist/core/ingestion/languages/typescript/arity.js +54 -0
  61. package/dist/core/ingestion/languages/typescript/cache-stats.d.ts +17 -0
  62. package/dist/core/ingestion/languages/typescript/cache-stats.js +28 -0
  63. package/dist/core/ingestion/languages/typescript/captures.d.ts +28 -0
  64. package/dist/core/ingestion/languages/typescript/captures.js +451 -0
  65. package/dist/core/ingestion/languages/typescript/import-decomposer.d.ts +49 -0
  66. package/dist/core/ingestion/languages/typescript/import-decomposer.js +371 -0
  67. package/dist/core/ingestion/languages/typescript/import-target.d.ts +50 -0
  68. package/dist/core/ingestion/languages/typescript/import-target.js +61 -0
  69. package/dist/core/ingestion/languages/typescript/index.d.ts +94 -0
  70. package/dist/core/ingestion/languages/typescript/index.js +94 -0
  71. package/dist/core/ingestion/languages/typescript/interpret.d.ts +35 -0
  72. package/dist/core/ingestion/languages/typescript/interpret.js +317 -0
  73. package/dist/core/ingestion/languages/typescript/merge-bindings.d.ts +62 -0
  74. package/dist/core/ingestion/languages/typescript/merge-bindings.js +158 -0
  75. package/dist/core/ingestion/languages/typescript/query.d.ts +77 -0
  76. package/dist/core/ingestion/languages/typescript/query.js +778 -0
  77. package/dist/core/ingestion/languages/typescript/receiver-binding.d.ts +59 -0
  78. package/dist/core/ingestion/languages/typescript/receiver-binding.js +171 -0
  79. package/dist/core/ingestion/languages/typescript/scope-resolver.d.ts +16 -0
  80. package/dist/core/ingestion/languages/typescript/scope-resolver.js +113 -0
  81. package/dist/core/ingestion/languages/typescript/simple-hooks.d.ts +71 -0
  82. package/dist/core/ingestion/languages/typescript/simple-hooks.js +131 -0
  83. package/dist/core/ingestion/languages/typescript.js +19 -0
  84. package/dist/core/ingestion/method-extractors/configs/swift.js +3 -4
  85. package/dist/core/ingestion/model/scope-resolution-indexes.d.ts +14 -1
  86. package/dist/core/ingestion/parsing-processor.js +20 -9
  87. package/dist/core/ingestion/pipeline-phases/processes.js +9 -4
  88. package/dist/core/ingestion/pipeline-phases/tools.d.ts +1 -0
  89. package/dist/core/ingestion/pipeline-phases/tools.js +10 -4
  90. package/dist/core/ingestion/registry-primary-flag.d.ts +3 -1
  91. package/dist/core/ingestion/registry-primary-flag.js +4 -1
  92. package/dist/core/ingestion/scope-extractor-bridge.d.ts +5 -2
  93. package/dist/core/ingestion/scope-extractor-bridge.js +7 -2
  94. package/dist/core/ingestion/scope-extractor.js +19 -18
  95. package/dist/core/ingestion/scope-resolution/contract/scope-resolver.d.ts +73 -11
  96. package/dist/core/ingestion/scope-resolution/contract/scope-resolver.js +48 -10
  97. package/dist/core/ingestion/scope-resolution/passes/compound-receiver.js +283 -14
  98. package/dist/core/ingestion/scope-resolution/passes/imported-return-types.d.ts +23 -2
  99. package/dist/core/ingestion/scope-resolution/passes/imported-return-types.js +109 -37
  100. package/dist/core/ingestion/scope-resolution/passes/mro.js +3 -1
  101. package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +13 -5
  102. package/dist/core/ingestion/scope-resolution/pipeline/phase.js +11 -2
  103. package/dist/core/ingestion/scope-resolution/pipeline/registry.js +2 -0
  104. package/dist/core/ingestion/scope-resolution/pipeline/run.d.ts +8 -0
  105. package/dist/core/ingestion/scope-resolution/pipeline/run.js +21 -5
  106. package/dist/core/ingestion/scope-resolution/pipeline/validate-bindings-immutability.d.ts +39 -0
  107. package/dist/core/ingestion/scope-resolution/pipeline/validate-bindings-immutability.js +65 -0
  108. package/dist/core/ingestion/scope-resolution/scope/walkers.d.ts +54 -11
  109. package/dist/core/ingestion/scope-resolution/scope/walkers.js +105 -30
  110. package/dist/core/ingestion/type-extractors/swift.js +7 -4
  111. package/dist/core/ingestion/utils/ast-helpers.d.ts +2 -0
  112. package/dist/core/ingestion/utils/ast-helpers.js +12 -0
  113. package/dist/core/ingestion/utils/env.d.ts +10 -0
  114. package/dist/core/ingestion/utils/env.js +14 -0
  115. package/dist/core/ingestion/workers/parse-worker.d.ts +1 -0
  116. package/dist/core/ingestion/workers/parse-worker.js +15 -9
  117. package/dist/core/ingestion/workers/worker-pool.d.ts +11 -4
  118. package/dist/core/ingestion/workers/worker-pool.js +244 -48
  119. package/dist/core/lbug/extension-loader.d.ts +86 -0
  120. package/dist/core/lbug/extension-loader.js +184 -0
  121. package/dist/core/lbug/lbug-adapter.d.ts +18 -17
  122. package/dist/core/lbug/lbug-adapter.js +45 -73
  123. package/dist/core/lbug/pool-adapter.js +10 -28
  124. package/dist/core/platform/capabilities.d.ts +24 -0
  125. package/dist/core/platform/capabilities.js +54 -0
  126. package/dist/core/run-analyze.js +36 -9
  127. package/dist/core/search/bm25-index.d.ts +0 -17
  128. package/dist/core/search/bm25-index.js +10 -118
  129. package/dist/core/search/fts-indexes.d.ts +1 -0
  130. package/dist/core/search/fts-indexes.js +7 -0
  131. package/dist/core/search/fts-schema.d.ts +6 -0
  132. package/dist/core/search/fts-schema.js +7 -0
  133. package/dist/mcp/core/embedder.js +11 -4
  134. package/dist/mcp/local/local-backend.js +50 -15
  135. package/dist/server/api.d.ts +5 -0
  136. package/dist/server/api.js +113 -0
  137. package/hooks/claude/gitnexus-hook.cjs +11 -1
  138. package/package.json +6 -5
  139. package/scripts/build-tree-sitter-dart.cjs +42 -0
  140. package/scripts/build-tree-sitter-proto.cjs +1 -1
  141. package/scripts/build.js +22 -2
  142. package/scripts/install-duckdb-extension.mjs +37 -0
  143. package/vendor/tree-sitter-dart/README.md +18 -0
  144. package/vendor/tree-sitter-dart/binding.gyp +31 -0
  145. package/vendor/tree-sitter-dart/bindings/node/binding.cc +20 -0
  146. package/vendor/tree-sitter-dart/bindings/node/index.d.ts +28 -0
  147. package/vendor/tree-sitter-dart/bindings/node/index.js +7 -0
  148. package/vendor/tree-sitter-dart/grammar.js +2895 -0
  149. package/vendor/tree-sitter-dart/package.json +18 -0
  150. package/vendor/tree-sitter-dart/queries/highlights.scm +246 -0
  151. package/vendor/tree-sitter-dart/queries/tags.scm +92 -0
  152. package/vendor/tree-sitter-dart/queries/test.scm +1 -0
  153. package/vendor/tree-sitter-dart/src/grammar.json +12459 -0
  154. package/vendor/tree-sitter-dart/src/node-types.json +15055 -0
  155. package/vendor/tree-sitter-dart/src/parser.c +196127 -0
  156. package/vendor/tree-sitter-dart/src/scanner.c +130 -0
  157. package/vendor/tree-sitter-dart/src/tree_sitter/alloc.h +54 -0
  158. package/vendor/tree-sitter-dart/src/tree_sitter/array.h +290 -0
  159. package/vendor/tree-sitter-dart/src/tree_sitter/parser.h +265 -0
  160. package/vendor/tree-sitter-swift/LICENSE +21 -0
  161. package/vendor/tree-sitter-swift/README.md +139 -0
  162. package/vendor/tree-sitter-swift/bindings/node/index.d.ts +28 -0
  163. package/vendor/tree-sitter-swift/bindings/node/index.js +7 -0
  164. package/vendor/tree-sitter-swift/package.json +28 -0
  165. package/vendor/tree-sitter-swift/prebuilds/darwin-arm64/tree-sitter-swift.node +0 -0
  166. package/vendor/tree-sitter-swift/prebuilds/darwin-x64/tree-sitter-swift.node +0 -0
  167. package/vendor/tree-sitter-swift/prebuilds/linux-arm64/tree-sitter-swift.node +0 -0
  168. package/vendor/tree-sitter-swift/prebuilds/linux-x64/tree-sitter-swift.node +0 -0
  169. package/vendor/tree-sitter-swift/prebuilds/win32-arm64/tree-sitter-swift.node +0 -0
  170. package/vendor/tree-sitter-swift/prebuilds/win32-x64/tree-sitter-swift.node +0 -0
  171. package/vendor/tree-sitter-swift/src/node-types.json +30694 -0
  172. package/web/assets/agent-DaprsFSX.js +597 -0
  173. package/web/assets/architecture-YZFGNWBL-S5CXDPWN-DEdGaPg2.js +1 -0
  174. package/web/assets/architectureDiagram-EMZXCZ2Q-Domyk_gO.js +36 -0
  175. package/web/assets/blockDiagram-IGV67L2C-B_2kD7tM.js +132 -0
  176. package/web/assets/c4Diagram-DFAF54RM-BhJJW8Gg.js +10 -0
  177. package/web/assets/chunk-3GS5O3IE-jlWIjPsl.js +231 -0
  178. package/web/assets/chunk-3YCYZ6SJ-Blq_IzZs.js +1 -0
  179. package/web/assets/chunk-6NTNNK5N-DyPc58pp.js +1 -0
  180. package/web/assets/chunk-7RZVMHOQ-BdIU-RGO.js +321 -0
  181. package/web/assets/chunk-A34GCYZU-BI2i_LdU.js +1 -0
  182. package/web/assets/chunk-AEOMTBSW-D7qjBMHW.js +1 -0
  183. package/web/assets/chunk-CilyBKbf.js +1 -0
  184. package/web/assets/chunk-DJ7UZH7F-i11ywiBl.js +1 -0
  185. package/web/assets/chunk-DKKBVRCY-1SffGI1N.js +4 -0
  186. package/web/assets/chunk-DU5LTGQ6-DaPeiwD5.js +1 -0
  187. package/web/assets/chunk-FXACKDTF-uhhi2PC2.js +159 -0
  188. package/web/assets/chunk-H3VCZNTA-IchcISDt.js +1 -0
  189. package/web/assets/chunk-HN6EAY2L-D7ZFMNrB.js +1 -0
  190. package/web/assets/chunk-KSICW3F5-C2tZmXwv.js +15 -0
  191. package/web/assets/chunk-O5ABG6QK-Bt-Km84H.js +1 -0
  192. package/web/assets/chunk-PK6DOVAG-ChlWY0BQ.js +206 -0
  193. package/web/assets/chunk-RNJOYNJ4-B724K7cW.js +1 -0
  194. package/web/assets/chunk-RWUO3TPN-DYn1XriD.js +1 -0
  195. package/web/assets/chunk-TBF5ZNIQ-DKtDz6ae.js +1 -0
  196. package/web/assets/chunk-TU3PZOEN-DE5Qhc0N.js +1 -0
  197. package/web/assets/chunk-TYMNRAUI-g1h33cq-.js +1 -0
  198. package/web/assets/chunk-VELTKBKT-C9dVN39o.js +1 -0
  199. package/web/assets/chunk-W7ZLLLMY-Du-Hb9yb.js +1 -0
  200. package/web/assets/chunk-WSB5WSVC-B123clsZ.js +1 -0
  201. package/web/assets/chunk-XGPFEOL4-BR7Eue38.js +1 -0
  202. package/web/assets/classDiagram-PPOCWD7C-BglfKSs_.js +1 -0
  203. package/web/assets/classDiagram-v2-23LJLIIU-BSzTM28O.js +1 -0
  204. package/web/assets/context-builder-CqQNhRj1.js +15 -0
  205. package/web/assets/cose-bilkent-PNC4W37J-DCfErU-A.js +1 -0
  206. package/web/assets/dagre-E77IOHMT-tDRRhDoN.js +4 -0
  207. package/web/assets/diagram-H7BISOXX-CUVHlmAh.js +43 -0
  208. package/web/assets/diagram-JC5VWROH-BoyOxulB.js +24 -0
  209. package/web/assets/diagram-LXUTUG65-osr9hb7N.js +10 -0
  210. package/web/assets/diagram-WEHSV5V5-d8nUqS39.js +24 -0
  211. package/web/assets/erDiagram-GCSMX5X6-b-IwOhPS.js +85 -0
  212. package/web/assets/flowDiagram-OTCZ4VVT-Ott2Q0AP.js +162 -0
  213. package/web/assets/ganttDiagram-MUNLMDZQ-BYtgN_5s.js +292 -0
  214. package/web/assets/gitGraph-7Q5UKJZL-54BCDZD5-CFyBIGZq.js +1 -0
  215. package/web/assets/gitGraphDiagram-3HKGZ4G3-CsVD2gn4.js +106 -0
  216. package/web/assets/index-BleGLU8S.css +2 -0
  217. package/web/assets/index-C_xK08EW.js +885 -0
  218. package/web/assets/info-OMHHGYJF-BF2H5H6G-yjAxKEzh.js +1 -0
  219. package/web/assets/infoDiagram-MN7RKWGX-DXK0Unn5.js +2 -0
  220. package/web/assets/ishikawaDiagram-YMYX4NHK-CXsnC2FA.js +70 -0
  221. package/web/assets/journeyDiagram-SO5T7YLQ-BzZ07B-X.js +139 -0
  222. package/web/assets/kanban-definition-LJHFXRCJ-C6_EpAd9.js +89 -0
  223. package/web/assets/katex-GD7MH7QM-CJiOjBBJ.js +261 -0
  224. package/web/assets/mindmap-definition-2EUWGEK5-CCYGWZ1m.js +96 -0
  225. package/web/assets/packet-4T2RLAQJ-EV4IVRXR-B8k4E3IT.js +1 -0
  226. package/web/assets/pie-ZZUOXDRM-N23DN5KN-DdvfY118.js +1 -0
  227. package/web/assets/pieDiagram-3IATQBI2-RyvRlQb4.js +30 -0
  228. package/web/assets/quadrantDiagram-E256RVCF-Bfb6sxCx.js +7 -0
  229. package/web/assets/radar-PYXPWWZC-P6TP7ZYP-1EEDC_yU.js +1 -0
  230. package/web/assets/requirementDiagram-M5DCFWZL-DjvHDyvN.js +84 -0
  231. package/web/assets/sankeyDiagram-L3NBLAOT-CBCbbl8s.js +10 -0
  232. package/web/assets/sequenceDiagram-ZOUHS735-BscU8TUR.js +157 -0
  233. package/web/assets/stateDiagram-MLPALWAM-CJusEK2D.js +1 -0
  234. package/web/assets/stateDiagram-v2-B5LQ5ZB2-DImJ3PXD.js +1 -0
  235. package/web/assets/timeline-definition-5SPVSISX-DigPA1X8.js +120 -0
  236. package/web/assets/treeView-SZITEDCU-5DXDK3XO-CzPDt3aG.js +1 -0
  237. package/web/assets/treemap-W4RFUUIX-WYLRDWKO-B9Iqiorr.js +1 -0
  238. package/web/assets/vennDiagram-IE5QUKF5-C91UkZIf.js +34 -0
  239. package/web/assets/wardley-RL74JXVD-BCRCBASE-x42Qw7hp.js +1 -0
  240. package/web/assets/wardleyDiagram-XU3VSMPF-DloBhI0U.js +20 -0
  241. package/web/assets/xychartDiagram-ZHJ5623Y-BGWJvgwI.js +7 -0
  242. package/web/index.html +21 -0
  243. package/scripts/patch-tree-sitter-swift.cjs +0 -78
@@ -11,6 +11,7 @@
11
11
  import { SupportedLanguages } from '../../../../_shared/index.js';
12
12
  import { pythonScopeResolver } from '../../languages/python/scope-resolver.js';
13
13
  import { csharpScopeResolver } from '../../languages/csharp/scope-resolver.js';
14
+ import { typescriptScopeResolver } from '../../languages/typescript/scope-resolver.js';
14
15
  /** Map of `SupportedLanguages` → `ScopeResolver`. The phase iterates
15
16
  * this map intersected with `MIGRATED_LANGUAGES` (the per-language
16
17
  * flag set) so adding a resolver here without flipping the flag is
@@ -18,4 +19,5 @@ import { csharpScopeResolver } from '../../languages/csharp/scope-resolver.js';
18
19
  export const SCOPE_RESOLVERS = new Map([
19
20
  [SupportedLanguages.Python, pythonScopeResolver],
20
21
  [SupportedLanguages.CSharp, csharpScopeResolver],
22
+ [SupportedLanguages.TypeScript, typescriptScopeResolver],
21
23
  ]);
@@ -53,6 +53,14 @@ interface RunScopeResolutionInput {
53
53
  readonly treeCache?: {
54
54
  get(filePath: string): unknown;
55
55
  };
56
+ /**
57
+ * Opaque per-language import-resolution config (e.g. tsconfig path
58
+ * aliases for TypeScript). Loaded once by the caller via
59
+ * `provider.loadResolutionConfig(repoPath)` and threaded into every
60
+ * `provider.resolveImportTarget` call. `undefined` when the
61
+ * provider doesn't supply a config loader.
62
+ */
63
+ readonly resolutionConfig?: unknown;
56
64
  }
57
65
  interface RunScopeResolutionStats {
58
66
  readonly filesProcessed: number;
@@ -23,6 +23,7 @@
23
23
  * Plan: `docs/plans/2026-04-20-001-refactor-emit-pipeline-generalization-plan.md`.
24
24
  */
25
25
  import { reconcileOwnership, validateOwnershipParity } from './reconcile-ownership.js';
26
+ import { validateBindingsImmutability } from './validate-bindings-immutability.js';
26
27
  import { extractParsedFile } from '../../scope-extractor-bridge.js';
27
28
  import { finalizeScopeModel } from '../../finalize-orchestrator.js';
28
29
  import { resolveReferenceSites } from '../../resolve-references.js';
@@ -81,9 +82,10 @@ export function runScopeResolution(input, provider) {
81
82
  const allFilePaths = new Set(parsedFiles.map((f) => f.filePath));
82
83
  const nodeLookup = buildGraphNodeLookup(graph);
83
84
  const mroByClassDefId = provider.buildMro(graph, parsedFiles, nodeLookup);
85
+ const resolutionConfig = input.resolutionConfig;
84
86
  const finalized = finalizeScopeModel(parsedFiles, {
85
87
  hooks: {
86
- resolveImportTarget: (targetRaw, fromFile) => provider.resolveImportTarget(targetRaw, fromFile, allFilePaths),
88
+ resolveImportTarget: (targetRaw, fromFile) => provider.resolveImportTarget(targetRaw, fromFile, allFilePaths, resolutionConfig),
87
89
  mergeBindings: (existing, incoming, scopeId) => provider.mergeBindings(existing, incoming, scopeId),
88
90
  },
89
91
  });
@@ -106,6 +108,8 @@ export function runScopeResolution(input, provider) {
106
108
  // Cross-file implicit-namespace visibility (C#). Must run before
107
109
  // propagateImportedReturnTypes so the latter pass sees siblings'
108
110
  // class bindings when chasing return-type chains across files.
111
+ // The hook writes to `bindingAugmentations` only; finalized
112
+ // `indexes.bindings` remains immutable post-finalize (I8).
109
113
  if (provider.populateNamespaceSiblings !== undefined) {
110
114
  const fileContents = new Map();
111
115
  for (const f of files)
@@ -115,12 +119,23 @@ export function runScopeResolution(input, provider) {
115
119
  treeCache,
116
120
  });
117
121
  }
122
+ const tFinalize = PROF ? process.hrtime.bigint() : 0n;
118
123
  // Cross-file return-type propagation (Contract Invariant I3 timing:
119
- // after finalize, before resolve).
124
+ // after finalize, before resolve). Split-timed separately so the
125
+ // SCC-ordered pass's cost is observable (PR #1050 made this O(files)
126
+ // with chain-follow per importer; quadratic regressions show up
127
+ // here, not in finalize).
120
128
  if (provider.propagatesReturnTypesAcrossImports !== false) {
121
129
  propagateImportedReturnTypes(parsedFiles, indexes, workspaceIndex);
122
130
  }
123
- const tFinalize = PROF ? process.hrtime.bigint() : 0n;
131
+ const tPropagate = PROF ? process.hrtime.bigint() : 0n;
132
+ // Opt-in I8 invariant guard. Runs once after all post-finalize hooks
133
+ // (`populateNamespaceSiblings`, `propagateImportedReturnTypes`) have
134
+ // had a chance to drift, so a single sweep covers the full
135
+ // post-finalize surface visible to `resolveReferenceSites`. No-op in
136
+ // default CLI runs; enabled by NODE_ENV=development or
137
+ // VALIDATE_SEMANTIC_MODEL=1.
138
+ validateBindingsImmutability(indexes, onWarn);
124
139
  // ── Phase 3: resolve references via Registry.lookup ────────────────────
125
140
  const registryProviders = {
126
141
  arityCompatibility: provider.arityCompatibility,
@@ -140,8 +155,9 @@ export function runScopeResolution(input, provider) {
140
155
  const tEnd = process.hrtime.bigint();
141
156
  const ns = (a, b) => Number(b - a) / 1_000_000;
142
157
  console.warn(`[scope-resolution prof] extract=${ns(tStart, tExtract).toFixed(0)}ms` +
143
- ` finalize+propagate=${ns(tExtract, tFinalize).toFixed(0)}ms` +
144
- ` resolve=${ns(tFinalize, tResolve).toFixed(0)}ms` +
158
+ ` finalize=${ns(tExtract, tFinalize).toFixed(0)}ms` +
159
+ ` propagate=${ns(tFinalize, tPropagate).toFixed(0)}ms` +
160
+ ` resolve=${ns(tPropagate, tResolve).toFixed(0)}ms` +
145
161
  ` emit=${ns(tResolve, tEnd).toFixed(0)}ms` +
146
162
  ` total=${ns(tStart, tEnd).toFixed(0)}ms` +
147
163
  ` (${parsedFiles.length} files)`);
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Dev-mode runtime validator for the two-channel binding lifecycle
3
+ * (Contract Invariant I8 in `contract/scope-resolver.ts`).
4
+ *
5
+ * The two channels:
6
+ * - `indexes.bindings` — finalize-output channel. After
7
+ * `finalizeScopeModel` returns, every inner `BindingRef[]` array
8
+ * here is deep-frozen by `materializeBindings`. NO post-finalize
9
+ * hook should ever mutate this map's inner arrays — drift here
10
+ * manifests at runtime as the `Cannot add property N, object is
11
+ * not extensible` crash (issue #1066) or, more insidiously, as
12
+ * a hook silently mutating one of the frozen arrays (a no-op in
13
+ * production where freezes can be elided, a `TypeError` in dev).
14
+ *
15
+ * - `indexes.bindingAugmentations` — post-finalize append-only
16
+ * channel. Inner arrays here are NEVER frozen; hooks like
17
+ * `populateNamespaceSiblings` `push()` directly. Walkers consult
18
+ * both channels via `lookupBindingsAt`.
19
+ *
20
+ * This validator runs after every post-finalize hook has executed
21
+ * (so the dev-mode envelope captures the FULL surface area visible
22
+ * to `resolveReferenceSites`) and asserts:
23
+ *
24
+ * 1. Every inner `BindingRef[]` array in `indexes.bindings` is
25
+ * `Object.isFrozen` — i.e. finalize produced a frozen bucket
26
+ * AND no hook accidentally `set()`-back a mutable replacement.
27
+ *
28
+ * 2. Every inner `BindingRef[]` array in
29
+ * `indexes.bindingAugmentations` is NOT frozen — i.e. the
30
+ * hook used the augmentation channel as designed (mutable
31
+ * `push()`) and didn't accidentally freeze its own scratch
32
+ * arrays. Self-documenting; mostly a sanity net.
33
+ *
34
+ * Mirrors `validateOwnershipParity` (#909): warns via `onWarn`,
35
+ * never throws, and is opt-in outside development. Gated by
36
+ * `isSemanticModelValidatorEnabled()` (`utils/env.ts`).
37
+ */
38
+ import type { ScopeResolutionIndexes } from '../../model/scope-resolution-indexes.js';
39
+ export declare function validateBindingsImmutability(indexes: ScopeResolutionIndexes, onWarn: (message: string) => void): number;
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Dev-mode runtime validator for the two-channel binding lifecycle
3
+ * (Contract Invariant I8 in `contract/scope-resolver.ts`).
4
+ *
5
+ * The two channels:
6
+ * - `indexes.bindings` — finalize-output channel. After
7
+ * `finalizeScopeModel` returns, every inner `BindingRef[]` array
8
+ * here is deep-frozen by `materializeBindings`. NO post-finalize
9
+ * hook should ever mutate this map's inner arrays — drift here
10
+ * manifests at runtime as the `Cannot add property N, object is
11
+ * not extensible` crash (issue #1066) or, more insidiously, as
12
+ * a hook silently mutating one of the frozen arrays (a no-op in
13
+ * production where freezes can be elided, a `TypeError` in dev).
14
+ *
15
+ * - `indexes.bindingAugmentations` — post-finalize append-only
16
+ * channel. Inner arrays here are NEVER frozen; hooks like
17
+ * `populateNamespaceSiblings` `push()` directly. Walkers consult
18
+ * both channels via `lookupBindingsAt`.
19
+ *
20
+ * This validator runs after every post-finalize hook has executed
21
+ * (so the dev-mode envelope captures the FULL surface area visible
22
+ * to `resolveReferenceSites`) and asserts:
23
+ *
24
+ * 1. Every inner `BindingRef[]` array in `indexes.bindings` is
25
+ * `Object.isFrozen` — i.e. finalize produced a frozen bucket
26
+ * AND no hook accidentally `set()`-back a mutable replacement.
27
+ *
28
+ * 2. Every inner `BindingRef[]` array in
29
+ * `indexes.bindingAugmentations` is NOT frozen — i.e. the
30
+ * hook used the augmentation channel as designed (mutable
31
+ * `push()`) and didn't accidentally freeze its own scratch
32
+ * arrays. Self-documenting; mostly a sanity net.
33
+ *
34
+ * Mirrors `validateOwnershipParity` (#909): warns via `onWarn`,
35
+ * never throws, and is opt-in outside development. Gated by
36
+ * `isSemanticModelValidatorEnabled()` (`utils/env.ts`).
37
+ */
38
+ import { isSemanticModelValidatorEnabled } from '../../utils/env.js';
39
+ export function validateBindingsImmutability(indexes, onWarn) {
40
+ if (!isSemanticModelValidatorEnabled())
41
+ return 0;
42
+ let violations = 0;
43
+ for (const [scopeId, bucketMap] of indexes.bindings) {
44
+ for (const [name, bucket] of bucketMap) {
45
+ if (!Object.isFrozen(bucket)) {
46
+ onWarn(`binding-immutability: indexes.bindings[${scopeId}][${name}] is NOT frozen — ` +
47
+ `finalize produced a mutable bucket OR a post-finalize hook replaced a frozen ` +
48
+ `bucket with a mutable one. Hooks must write to indexes.bindingAugmentations ` +
49
+ `instead. See ScopeResolver Invariant I8.`);
50
+ violations++;
51
+ }
52
+ }
53
+ }
54
+ for (const [scopeId, bucketMap] of indexes.bindingAugmentations) {
55
+ for (const [name, bucket] of bucketMap) {
56
+ if (Object.isFrozen(bucket)) {
57
+ onWarn(`binding-immutability: indexes.bindingAugmentations[${scopeId}][${name}] is FROZEN — ` +
58
+ `the augmentation channel is mutable by contract; freezing it defeats the ` +
59
+ `append-only purpose. See ScopeResolver Invariant I8.`);
60
+ violations++;
61
+ }
62
+ }
63
+ }
64
+ return violations;
65
+ }
@@ -1,13 +1,14 @@
1
1
  /**
2
2
  * Scope-chain lookup primitives shared across language providers.
3
3
  *
4
- * Four functions:
4
+ * Five functions:
5
5
  * - `findReceiverTypeBinding` — walk scope.typeBindings up the chain
6
6
  * for a receiver name.
7
- * - `findClassBindingInScope` — walk scope.bindings + indexes.bindings
8
- * (pre-finalize + post-finalize) for a class-kind binding. Dual-
9
- * source is required because the cross-file finalize pass produces
10
- * a separate bindings map that is not merged back into scope.bindings.
7
+ * - `lookupBindingsAt` — read finalized + augmented binding refs at
8
+ * one scope, deduped by `def.nodeId`. The dual-source-aware
9
+ * primitive every other binding lookup composes with.
10
+ * - `findClassBindingInScope` walk scope.bindings + the indexes via
11
+ * `lookupBindingsAt` for a class-kind binding.
11
12
  * - `findOwnedMember` — find a method/field owned by a class def
12
13
  * across all parsed files by (ownerId, simpleName).
13
14
  * - `findExportedDef` — find a file-level exported def (top-of-module
@@ -18,10 +19,44 @@
18
19
  * "resolve member on owner with MRO" pattern. All four are reusable
19
20
  * as-is for TypeScript, Java, Kotlin, Ruby, etc.
20
21
  */
21
- import type { ParsedFile, ScopeId, SymbolDefinition, TypeRef } from '../../../../_shared/index.js';
22
+ import type { BindingRef, ParsedFile, ScopeId, SymbolDefinition, TypeRef } from '../../../../_shared/index.js';
22
23
  import type { ScopeResolutionIndexes } from '../../model/scope-resolution-indexes.js';
23
24
  import type { SemanticModel } from '../../model/semantic-model.js';
24
25
  import type { WorkspaceResolutionIndex } from '../workspace-index.js';
26
+ /**
27
+ * Look up binding refs at `scopeId` for `name`, consulting both the
28
+ * finalize-owned `bindings` channel and the post-finalize
29
+ * `bindingAugmentations` channel (see invariant I8 in
30
+ * `contract/scope-resolver.ts`). Finalized refs come first; augmented
31
+ * refs append, deduped by `def.nodeId` so a sibling that's also
32
+ * explicitly imported doesn't double-emit.
33
+ *
34
+ * Returns a shared frozen empty array when neither channel has the
35
+ * name — callers can compare against `=== EMPTY_BINDINGS` if they
36
+ * want a fast-path miss check. The bucket arrays are returned by
37
+ * reference when only one channel populates them; the merged path
38
+ * allocates a fresh array.
39
+ *
40
+ * Walker primitives (`findClassBindingInScope`,
41
+ * `findCallableBindingInScope`, `findExportedDefByName`) and
42
+ * post-finalize passes that read finalized bindings (e.g.
43
+ * `propagateImportedReturnTypes`, `namespace-targets`) MUST go
44
+ * through this helper instead of `scopes.bindings.get(...)` directly,
45
+ * so the augmentation channel is always visible.
46
+ */
47
+ export declare function lookupBindingsAt(scopeId: ScopeId, name: string, scopes: ScopeResolutionIndexes): readonly BindingRef[];
48
+ /**
49
+ * Return the union of bound names at `scopeId` across both the
50
+ * finalized and augmented channels. Companion to `lookupBindingsAt`
51
+ * for callers that need to iterate every name at a scope (e.g.
52
+ * `propagateImportedReturnTypes`). Order is not guaranteed; callers
53
+ * that need stable iteration should sort externally.
54
+ *
55
+ * Fast paths (zero allocation) when at most one channel is populated:
56
+ * returns the underlying `Map.keys()` iterator directly. Only when both
57
+ * channels carry names do we materialize a `Set` for deduplication.
58
+ */
59
+ export declare function namesAtScope(scopeId: ScopeId, scopes: ScopeResolutionIndexes): Iterable<string>;
25
60
  /**
26
61
  * True when a def's `type` names a class-like declaration — every kind
27
62
  * that collapses to `@scope.class` in the scope-extractor query contract.
@@ -48,8 +83,10 @@ export declare function findReceiverTypeBinding(startScope: ScopeId, receiverNam
48
83
  * Walks the scope chain upward and consults TWO sources at each step:
49
84
  * 1. `scope.bindings` — populated during scope-extraction Pass 2 with
50
85
  * local declarations (`origin: 'local'`).
51
- * 2. `indexes.bindings` populated by the cross-file finalize pass
52
- * with import/namespace/wildcard/reexport origins.
86
+ * 2. The cross-file finalized + augmented bindings, via
87
+ * `lookupBindingsAt` (per I8: finalized = canonical immutable
88
+ * output; augmented = post-finalize hooks like
89
+ * `populateNamespaceSiblings`).
53
90
  *
54
91
  * Without (2) we'd miss every cross-file class-receiver call.
55
92
  */
@@ -57,8 +94,9 @@ export declare function findClassBindingInScope(startScope: ScopeId, receiverNam
57
94
  /**
58
95
  * Look up a callable (Function/Method/Constructor) by name in the
59
96
  * given scope's chain. Uses the dual-source pattern (scope.bindings +
60
- * indexes.bindings) so cross-file imports are visible — without it
61
- * free calls to imported functions never resolve via the post-pass.
97
+ * `lookupBindingsAt` for finalized + augmented) so cross-file
98
+ * imports are visible — without it free calls to imported functions
99
+ * never resolve via the post-pass.
62
100
  *
63
101
  * Mirrors `findClassBindingInScope` exactly; only the accepted
64
102
  * def-type predicate differs.
@@ -122,6 +160,11 @@ export declare function findOwnedMember(ownerDefId: string, memberName: string,
122
160
  * excluded.
123
161
  *
124
162
  * Reads from `WorkspaceResolutionIndex.moduleScopeByFile` (scope-tied
125
- * lookup that doesn't live on `SemanticModel`).
163
+ * lookup that doesn't live on `SemanticModel`). This intentionally
164
+ * does NOT call `lookupBindingsAt`: `findExportedDef` answers "what
165
+ * did the target file declare locally at module scope?", while
166
+ * `bindingAugmentations` models importer-side visibility created by
167
+ * post-finalize hooks. Callers that need importer-visible exports use
168
+ * `findExportedDefByName`, which is dual-channel aware.
126
169
  */
127
170
  export declare function findExportedDef(targetFile: string, memberName: string, index: WorkspaceResolutionIndex): SymbolDefinition | undefined;
@@ -1,13 +1,14 @@
1
1
  /**
2
2
  * Scope-chain lookup primitives shared across language providers.
3
3
  *
4
- * Four functions:
4
+ * Five functions:
5
5
  * - `findReceiverTypeBinding` — walk scope.typeBindings up the chain
6
6
  * for a receiver name.
7
- * - `findClassBindingInScope` — walk scope.bindings + indexes.bindings
8
- * (pre-finalize + post-finalize) for a class-kind binding. Dual-
9
- * source is required because the cross-file finalize pass produces
10
- * a separate bindings map that is not merged back into scope.bindings.
7
+ * - `lookupBindingsAt` — read finalized + augmented binding refs at
8
+ * one scope, deduped by `def.nodeId`. The dual-source-aware
9
+ * primitive every other binding lookup composes with.
10
+ * - `findClassBindingInScope` walk scope.bindings + the indexes via
11
+ * `lookupBindingsAt` for a class-kind binding.
11
12
  * - `findOwnedMember` — find a method/field owned by a class def
12
13
  * across all parsed files by (ownerId, simpleName).
13
14
  * - `findExportedDef` — find a file-level exported def (top-of-module
@@ -18,6 +19,80 @@
18
19
  * "resolve member on owner with MRO" pattern. All four are reusable
19
20
  * as-is for TypeScript, Java, Kotlin, Ruby, etc.
20
21
  */
22
+ const EMPTY_BINDINGS = Object.freeze([]);
23
+ /**
24
+ * Look up binding refs at `scopeId` for `name`, consulting both the
25
+ * finalize-owned `bindings` channel and the post-finalize
26
+ * `bindingAugmentations` channel (see invariant I8 in
27
+ * `contract/scope-resolver.ts`). Finalized refs come first; augmented
28
+ * refs append, deduped by `def.nodeId` so a sibling that's also
29
+ * explicitly imported doesn't double-emit.
30
+ *
31
+ * Returns a shared frozen empty array when neither channel has the
32
+ * name — callers can compare against `=== EMPTY_BINDINGS` if they
33
+ * want a fast-path miss check. The bucket arrays are returned by
34
+ * reference when only one channel populates them; the merged path
35
+ * allocates a fresh array.
36
+ *
37
+ * Walker primitives (`findClassBindingInScope`,
38
+ * `findCallableBindingInScope`, `findExportedDefByName`) and
39
+ * post-finalize passes that read finalized bindings (e.g.
40
+ * `propagateImportedReturnTypes`, `namespace-targets`) MUST go
41
+ * through this helper instead of `scopes.bindings.get(...)` directly,
42
+ * so the augmentation channel is always visible.
43
+ */
44
+ export function lookupBindingsAt(scopeId, name, scopes) {
45
+ const finalized = scopes.bindings.get(scopeId)?.get(name);
46
+ const augmented = scopes.bindingAugmentations.get(scopeId)?.get(name);
47
+ const fLen = finalized?.length ?? 0;
48
+ const aLen = augmented?.length ?? 0;
49
+ if (fLen === 0 && aLen === 0)
50
+ return EMPTY_BINDINGS;
51
+ if (aLen === 0)
52
+ return finalized;
53
+ if (fLen === 0)
54
+ return augmented;
55
+ const seen = new Set();
56
+ const out = [];
57
+ for (const r of finalized) {
58
+ seen.add(r.def.nodeId);
59
+ out.push(r);
60
+ }
61
+ for (const r of augmented) {
62
+ if (seen.has(r.def.nodeId))
63
+ continue;
64
+ out.push(r);
65
+ }
66
+ return out;
67
+ }
68
+ const EMPTY_NAMES = Object.freeze([]);
69
+ /**
70
+ * Return the union of bound names at `scopeId` across both the
71
+ * finalized and augmented channels. Companion to `lookupBindingsAt`
72
+ * for callers that need to iterate every name at a scope (e.g.
73
+ * `propagateImportedReturnTypes`). Order is not guaranteed; callers
74
+ * that need stable iteration should sort externally.
75
+ *
76
+ * Fast paths (zero allocation) when at most one channel is populated:
77
+ * returns the underlying `Map.keys()` iterator directly. Only when both
78
+ * channels carry names do we materialize a `Set` for deduplication.
79
+ */
80
+ export function namesAtScope(scopeId, scopes) {
81
+ const finalized = scopes.bindings.get(scopeId);
82
+ const augmented = scopes.bindingAugmentations.get(scopeId);
83
+ const fSize = finalized?.size ?? 0;
84
+ const aSize = augmented?.size ?? 0;
85
+ if (fSize === 0 && aSize === 0)
86
+ return EMPTY_NAMES;
87
+ if (aSize === 0)
88
+ return finalized.keys();
89
+ if (fSize === 0)
90
+ return augmented.keys();
91
+ const out = new Set(finalized.keys());
92
+ for (const name of augmented.keys())
93
+ out.add(name);
94
+ return out;
95
+ }
21
96
  /**
22
97
  * True when a def's `type` names a class-like declaration — every kind
23
98
  * that collapses to `@scope.class` in the scope-extractor query contract.
@@ -67,8 +142,10 @@ export function findReceiverTypeBinding(startScope, receiverName, scopes) {
67
142
  * Walks the scope chain upward and consults TWO sources at each step:
68
143
  * 1. `scope.bindings` — populated during scope-extraction Pass 2 with
69
144
  * local declarations (`origin: 'local'`).
70
- * 2. `indexes.bindings` populated by the cross-file finalize pass
71
- * with import/namespace/wildcard/reexport origins.
145
+ * 2. The cross-file finalized + augmented bindings, via
146
+ * `lookupBindingsAt` (per I8: finalized = canonical immutable
147
+ * output; augmented = post-finalize hooks like
148
+ * `populateNamespaceSiblings`).
72
149
  *
73
150
  * Without (2) we'd miss every cross-file class-receiver call.
74
151
  */
@@ -89,13 +166,10 @@ export function findClassBindingInScope(startScope, receiverName, scopes) {
89
166
  return b.def;
90
167
  }
91
168
  }
92
- const finalizedScopeBindings = scopes.bindings.get(currentId);
93
- const importedBindings = finalizedScopeBindings?.get(receiverName);
94
- if (importedBindings !== undefined) {
95
- for (const b of importedBindings) {
96
- if (isClassLike(b.def.type))
97
- return b.def;
98
- }
169
+ const importedBindings = lookupBindingsAt(currentId, receiverName, scopes);
170
+ for (const b of importedBindings) {
171
+ if (isClassLike(b.def.type))
172
+ return b.def;
99
173
  }
100
174
  currentId = scope.parent;
101
175
  }
@@ -104,8 +178,9 @@ export function findClassBindingInScope(startScope, receiverName, scopes) {
104
178
  /**
105
179
  * Look up a callable (Function/Method/Constructor) by name in the
106
180
  * given scope's chain. Uses the dual-source pattern (scope.bindings +
107
- * indexes.bindings) so cross-file imports are visible — without it
108
- * free calls to imported functions never resolve via the post-pass.
181
+ * `lookupBindingsAt` for finalized + augmented) so cross-file
182
+ * imports are visible — without it free calls to imported functions
183
+ * never resolve via the post-pass.
109
184
  *
110
185
  * Mirrors `findClassBindingInScope` exactly; only the accepted
111
186
  * def-type predicate differs.
@@ -128,13 +203,10 @@ export function findCallableBindingInScope(startScope, callableName, scopes) {
128
203
  }
129
204
  }
130
205
  }
131
- const finalizedScopeBindings = scopes.bindings.get(currentId);
132
- const importedBindings = finalizedScopeBindings?.get(callableName);
133
- if (importedBindings !== undefined) {
134
- for (const b of importedBindings) {
135
- if (b.def.type === 'Function' || b.def.type === 'Method' || b.def.type === 'Constructor') {
136
- return b.def;
137
- }
206
+ const importedBindings = lookupBindingsAt(currentId, callableName, scopes);
207
+ for (const b of importedBindings) {
208
+ if (b.def.type === 'Function' || b.def.type === 'Method' || b.def.type === 'Constructor') {
209
+ return b.def;
138
210
  }
139
211
  }
140
212
  currentId = scope.parent;
@@ -272,12 +344,10 @@ export function findExportedDefByName(name, inScope, scopes, index) {
272
344
  return b.def;
273
345
  }
274
346
  }
275
- const finalized = scopes.bindings.get(currentId)?.get(name);
276
- if (finalized !== undefined) {
277
- for (const b of finalized) {
278
- if (b.def.type === 'Function' || b.def.type === 'Method')
279
- return b.def;
280
- }
347
+ const finalized = lookupBindingsAt(currentId, name, scopes);
348
+ for (const b of finalized) {
349
+ if (b.def.type === 'Function' || b.def.type === 'Method')
350
+ return b.def;
281
351
  }
282
352
  currentId = scope.parent;
283
353
  }
@@ -332,7 +402,12 @@ export function findOwnedMember(ownerDefId, memberName, model) {
332
402
  * excluded.
333
403
  *
334
404
  * Reads from `WorkspaceResolutionIndex.moduleScopeByFile` (scope-tied
335
- * lookup that doesn't live on `SemanticModel`).
405
+ * lookup that doesn't live on `SemanticModel`). This intentionally
406
+ * does NOT call `lookupBindingsAt`: `findExportedDef` answers "what
407
+ * did the target file declare locally at module scope?", while
408
+ * `bindingAugmentations` models importer-side visibility created by
409
+ * post-finalize hooks. Callers that need importer-visible exports use
410
+ * `findExportedDefByName`, which is dual-channel aware.
336
411
  */
337
412
  export function findExportedDef(targetFile, memberName, index) {
338
413
  const moduleScope = index.moduleScopeByFile.get(targetFile);
@@ -21,6 +21,9 @@ function unwrapSwiftExpression(node) {
21
21
  }
22
22
  return node;
23
23
  }
24
+ function swiftNavigationSuffixName(node) {
25
+ return node?.type === 'navigation_suffix' ? node.lastNamedChild?.text : node?.text;
26
+ }
24
27
  /** Swift: let x: Foo = ... */
25
28
  const extractDeclaration = (node, env) => {
26
29
  // Swift property_declaration has pattern and type_annotation
@@ -99,8 +102,8 @@ const extractInitializer = (node, env, classNames) => {
99
102
  // Explicit init: User.init(name: "alice") — navigation_expression with .init suffix
100
103
  if (callee.type === 'navigation_expression') {
101
104
  const receiver = callee.firstNamedChild;
102
- const suffix = callee.lastNamedChild;
103
- if (receiver?.type === 'simple_identifier' && suffix?.text === 'init') {
105
+ if (receiver?.type === 'simple_identifier' &&
106
+ swiftNavigationSuffixName(callee.lastNamedChild) === 'init') {
104
107
  const calleeName = receiver.text;
105
108
  if (calleeName && classNames.has(calleeName)) {
106
109
  env.set(varName, calleeName);
@@ -114,7 +117,7 @@ const scanConstructorBinding = (node) => {
114
117
  return undefined;
115
118
  if (hasTypeAnnotation(node))
116
119
  return undefined;
117
- const pattern = node.childForFieldName('pattern');
120
+ const pattern = node.childForFieldName('pattern') ?? findChild(node, 'pattern');
118
121
  if (!pattern)
119
122
  return undefined;
120
123
  const varName = pattern.text;
@@ -147,7 +150,7 @@ const scanConstructorBinding = (node) => {
147
150
  if (callee.type === 'navigation_expression') {
148
151
  const receiver = callee.firstNamedChild;
149
152
  const suffix = callee.lastNamedChild;
150
- if (receiver?.type === 'simple_identifier' && suffix?.text === 'init') {
153
+ if (receiver?.type === 'simple_identifier' && swiftNavigationSuffixName(suffix) === 'init') {
151
154
  return { varName, calleeName: receiver.text };
152
155
  }
153
156
  // General qualified call: service.getUser() → extract method name.
@@ -31,6 +31,8 @@ export declare const FUNCTION_NODE_TYPES: Set<string>;
31
31
  */
32
32
  export declare const CLASS_CONTAINER_TYPES: Set<string>;
33
33
  export declare const CONTAINER_TYPE_TO_LABEL: Record<string, string>;
34
+ /** Return the first matching ancestor unless a boundary ancestor is reached first. */
35
+ export declare function findAncestorBeforeBoundary(node: SyntaxNode, targetTypes: ReadonlySet<string>, boundaryTypes: ReadonlySet<string>): SyntaxNode | null;
34
36
  /**
35
37
  * Determine the graph node label from a tree-sitter capture map.
36
38
  * Handles language-specific reclassification via the provider's labelOverride hook
@@ -153,6 +153,18 @@ export const CONTAINER_TYPE_TO_LABEL = {
153
153
  object_declaration: 'Class',
154
154
  companion_object: 'Class',
155
155
  };
156
+ /** Return the first matching ancestor unless a boundary ancestor is reached first. */
157
+ export function findAncestorBeforeBoundary(node, targetTypes, boundaryTypes) {
158
+ let current = node.parent;
159
+ while (current !== null) {
160
+ if (boundaryTypes.has(current.type))
161
+ return null;
162
+ if (targetTypes.has(current.type))
163
+ return current;
164
+ current = current.parent;
165
+ }
166
+ return null;
167
+ }
156
168
  /**
157
169
  * Determine the graph node label from a tree-sitter capture map.
158
170
  * Handles language-specific reclassification via the provider's labelOverride hook
@@ -8,3 +8,13 @@
8
8
  */
9
9
  /** Whether we're running in development mode (enables verbose console logging). */
10
10
  export declare const isDev: boolean;
11
+ /**
12
+ * Whether scope-resolution dev validators (e.g. `validateBindingsImmutability`)
13
+ * should run AND emit warnings. Off by default in CLI runs to avoid silent
14
+ * O(n) scans on large repos; on in `NODE_ENV=development` or when explicitly
15
+ * opted-in via `VALIDATE_SEMANTIC_MODEL=1`. `VALIDATE_SEMANTIC_MODEL=0` is the
16
+ * explicit off switch and wins over both.
17
+ *
18
+ * Read every call (not memoized) so test setups using `vi.stubEnv` work.
19
+ */
20
+ export declare const isSemanticModelValidatorEnabled: () => boolean;
@@ -8,3 +8,17 @@
8
8
  */
9
9
  /** Whether we're running in development mode (enables verbose console logging). */
10
10
  export const isDev = process.env.NODE_ENV === 'development';
11
+ /**
12
+ * Whether scope-resolution dev validators (e.g. `validateBindingsImmutability`)
13
+ * should run AND emit warnings. Off by default in CLI runs to avoid silent
14
+ * O(n) scans on large repos; on in `NODE_ENV=development` or when explicitly
15
+ * opted-in via `VALIDATE_SEMANTIC_MODEL=1`. `VALIDATE_SEMANTIC_MODEL=0` is the
16
+ * explicit off switch and wins over both.
17
+ *
18
+ * Read every call (not memoized) so test setups using `vi.stubEnv` work.
19
+ */
20
+ export const isSemanticModelValidatorEnabled = () => {
21
+ if (process.env.VALIDATE_SEMANTIC_MODEL === '0')
22
+ return false;
23
+ return process.env.NODE_ENV === 'development' || process.env.VALIDATE_SEMANTIC_MODEL === '1';
24
+ };
@@ -117,6 +117,7 @@ export interface ExtractedToolDef {
117
117
  toolName: string;
118
118
  description: string;
119
119
  lineNumber: number;
120
+ handlerNodeId?: string;
120
121
  }
121
122
  export interface ExtractedORMQuery {
122
123
  filePath: string;