gitnexus 1.6.3-rc.8 → 1.6.3

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 (285) hide show
  1. package/README.md +21 -5
  2. package/dist/_shared/graph/types.d.ts +16 -0
  3. package/dist/_shared/graph/types.d.ts.map +1 -1
  4. package/dist/_shared/index.d.ts +20 -2
  5. package/dist/_shared/index.d.ts.map +1 -1
  6. package/dist/_shared/index.js +11 -0
  7. package/dist/_shared/index.js.map +1 -1
  8. package/dist/_shared/scope-resolution/def-index.js +2 -2
  9. package/dist/_shared/scope-resolution/def-index.js.map +1 -1
  10. package/dist/_shared/scope-resolution/method-dispatch-index.d.ts +8 -0
  11. package/dist/_shared/scope-resolution/method-dispatch-index.d.ts.map +1 -1
  12. package/dist/_shared/scope-resolution/method-dispatch-index.js +2 -2
  13. package/dist/_shared/scope-resolution/method-dispatch-index.js.map +1 -1
  14. package/dist/_shared/scope-resolution/module-scope-index.d.ts +8 -0
  15. package/dist/_shared/scope-resolution/module-scope-index.d.ts.map +1 -1
  16. package/dist/_shared/scope-resolution/module-scope-index.js +10 -2
  17. package/dist/_shared/scope-resolution/module-scope-index.js.map +1 -1
  18. package/dist/_shared/scope-resolution/parsed-file.d.ts +76 -0
  19. package/dist/_shared/scope-resolution/parsed-file.d.ts.map +1 -0
  20. package/dist/_shared/scope-resolution/parsed-file.js +54 -0
  21. package/dist/_shared/scope-resolution/parsed-file.js.map +1 -0
  22. package/dist/_shared/scope-resolution/position-index.d.ts +12 -0
  23. package/dist/_shared/scope-resolution/position-index.d.ts.map +1 -1
  24. package/dist/_shared/scope-resolution/position-index.js +2 -2
  25. package/dist/_shared/scope-resolution/position-index.js.map +1 -1
  26. package/dist/_shared/scope-resolution/qualified-name-index.js +2 -2
  27. package/dist/_shared/scope-resolution/qualified-name-index.js.map +1 -1
  28. package/dist/_shared/scope-resolution/reference-site.d.ts +75 -0
  29. package/dist/_shared/scope-resolution/reference-site.d.ts.map +1 -0
  30. package/dist/_shared/scope-resolution/reference-site.js +24 -0
  31. package/dist/_shared/scope-resolution/reference-site.js.map +1 -0
  32. package/dist/_shared/scope-resolution/registries/class-registry.d.ts +27 -0
  33. package/dist/_shared/scope-resolution/registries/class-registry.d.ts.map +1 -0
  34. package/dist/_shared/scope-resolution/registries/class-registry.js +30 -0
  35. package/dist/_shared/scope-resolution/registries/class-registry.js.map +1 -0
  36. package/dist/_shared/scope-resolution/registries/context.d.ts +69 -0
  37. package/dist/_shared/scope-resolution/registries/context.d.ts.map +1 -0
  38. package/dist/_shared/scope-resolution/registries/context.js +44 -0
  39. package/dist/_shared/scope-resolution/registries/context.js.map +1 -0
  40. package/dist/_shared/scope-resolution/registries/evidence.d.ts +56 -0
  41. package/dist/_shared/scope-resolution/registries/evidence.d.ts.map +1 -0
  42. package/dist/_shared/scope-resolution/registries/evidence.js +150 -0
  43. package/dist/_shared/scope-resolution/registries/evidence.js.map +1 -0
  44. package/dist/_shared/scope-resolution/registries/field-registry.d.ts +26 -0
  45. package/dist/_shared/scope-resolution/registries/field-registry.d.ts.map +1 -0
  46. package/dist/_shared/scope-resolution/registries/field-registry.js +31 -0
  47. package/dist/_shared/scope-resolution/registries/field-registry.js.map +1 -0
  48. package/dist/_shared/scope-resolution/registries/lookup-core.d.ts +81 -0
  49. package/dist/_shared/scope-resolution/registries/lookup-core.d.ts.map +1 -0
  50. package/dist/_shared/scope-resolution/registries/lookup-core.js +332 -0
  51. package/dist/_shared/scope-resolution/registries/lookup-core.js.map +1 -0
  52. package/dist/_shared/scope-resolution/registries/lookup-qualified.d.ts +33 -0
  53. package/dist/_shared/scope-resolution/registries/lookup-qualified.d.ts.map +1 -0
  54. package/dist/_shared/scope-resolution/registries/lookup-qualified.js +56 -0
  55. package/dist/_shared/scope-resolution/registries/lookup-qualified.js.map +1 -0
  56. package/dist/_shared/scope-resolution/registries/method-registry.d.ts +36 -0
  57. package/dist/_shared/scope-resolution/registries/method-registry.d.ts.map +1 -0
  58. package/dist/_shared/scope-resolution/registries/method-registry.js +32 -0
  59. package/dist/_shared/scope-resolution/registries/method-registry.js.map +1 -0
  60. package/dist/_shared/scope-resolution/registries/tie-breaks.d.ts +43 -0
  61. package/dist/_shared/scope-resolution/registries/tie-breaks.d.ts.map +1 -0
  62. package/dist/_shared/scope-resolution/registries/tie-breaks.js +60 -0
  63. package/dist/_shared/scope-resolution/registries/tie-breaks.js.map +1 -0
  64. package/dist/_shared/scope-resolution/resolve-type-ref.d.ts +1 -10
  65. package/dist/_shared/scope-resolution/resolve-type-ref.d.ts.map +1 -1
  66. package/dist/_shared/scope-resolution/resolve-type-ref.js +6 -0
  67. package/dist/_shared/scope-resolution/resolve-type-ref.js.map +1 -1
  68. package/dist/_shared/scope-resolution/scope-tree.d.ts +4 -4
  69. package/dist/_shared/scope-resolution/scope-tree.d.ts.map +1 -1
  70. package/dist/_shared/scope-resolution/scope-tree.js +3 -2
  71. package/dist/_shared/scope-resolution/scope-tree.js.map +1 -1
  72. package/dist/_shared/scope-resolution/shadow/aggregate.d.ts +6 -2
  73. package/dist/_shared/scope-resolution/shadow/aggregate.d.ts.map +1 -1
  74. package/dist/_shared/scope-resolution/shadow/aggregate.js +5 -0
  75. package/dist/_shared/scope-resolution/shadow/aggregate.js.map +1 -1
  76. package/dist/_shared/scope-resolution/types.d.ts +11 -0
  77. package/dist/_shared/scope-resolution/types.d.ts.map +1 -1
  78. package/dist/cli/ai-context.js +35 -4
  79. package/dist/cli/analyze.d.ts +27 -0
  80. package/dist/cli/analyze.js +31 -1
  81. package/dist/cli/clean.js +19 -1
  82. package/dist/cli/group.js +73 -0
  83. package/dist/cli/index-repo.js +8 -1
  84. package/dist/cli/index.js +26 -1
  85. package/dist/cli/list.js +11 -1
  86. package/dist/cli/remove.d.ts +30 -0
  87. package/dist/cli/remove.js +99 -0
  88. package/dist/cli/setup.js +185 -57
  89. package/dist/cli/tool.d.ts +5 -0
  90. package/dist/cli/tool.js +42 -0
  91. package/dist/config/ignore-service.d.ts +9 -0
  92. package/dist/config/ignore-service.js +80 -13
  93. package/dist/core/embedding-mode.d.ts +30 -0
  94. package/dist/core/embedding-mode.js +30 -0
  95. package/dist/core/embeddings/ast-utils.js +22 -22
  96. package/dist/core/embeddings/chunker.js +30 -25
  97. package/dist/core/embeddings/embedding-pipeline.d.ts +6 -0
  98. package/dist/core/embeddings/embedding-pipeline.js +15 -6
  99. package/dist/core/embeddings/text-generator.d.ts +1 -1
  100. package/dist/core/embeddings/text-generator.js +33 -24
  101. package/dist/core/embeddings/types.d.ts +43 -1
  102. package/dist/core/embeddings/types.js +101 -29
  103. package/dist/core/git-staleness.d.ts +18 -0
  104. package/dist/core/git-staleness.js +108 -0
  105. package/dist/core/graph/graph.js +115 -20
  106. package/dist/core/graph/types.d.ts +12 -1
  107. package/dist/core/group/config-parser.d.ts +4 -0
  108. package/dist/core/group/config-parser.js +18 -1
  109. package/dist/core/group/cross-impact.d.ts +41 -0
  110. package/dist/core/group/cross-impact.js +441 -0
  111. package/dist/core/group/extractors/http-patterns/php.js +126 -18
  112. package/dist/core/group/group-path-utils.d.ts +17 -0
  113. package/dist/core/group/group-path-utils.js +40 -0
  114. package/dist/core/group/resolve-at-member.d.ts +10 -0
  115. package/dist/core/group/resolve-at-member.js +31 -0
  116. package/dist/core/group/service.d.ts +9 -0
  117. package/dist/core/group/service.js +259 -25
  118. package/dist/core/group/types.d.ts +30 -0
  119. package/dist/core/ingestion/ast-cache.d.ts +16 -1
  120. package/dist/core/ingestion/ast-cache.js +14 -2
  121. package/dist/core/ingestion/call-processor.js +9 -0
  122. package/dist/core/ingestion/emit-references.d.ts +88 -0
  123. package/dist/core/ingestion/emit-references.js +229 -0
  124. package/dist/core/ingestion/filesystem-walker.js +6 -4
  125. package/dist/core/ingestion/finalize-orchestrator.d.ts +63 -0
  126. package/dist/core/ingestion/finalize-orchestrator.js +139 -0
  127. package/dist/core/ingestion/framework-detection.js +6 -2
  128. package/dist/core/ingestion/import-processor.js +4 -0
  129. package/dist/core/ingestion/import-resolvers/python.js +9 -6
  130. package/dist/core/ingestion/import-target-adapter.d.ts +73 -0
  131. package/dist/core/ingestion/import-target-adapter.js +95 -0
  132. package/dist/core/ingestion/language-provider.d.ts +36 -33
  133. package/dist/core/ingestion/languages/csharp/accessor-unwrap.d.ts +21 -0
  134. package/dist/core/ingestion/languages/csharp/accessor-unwrap.js +56 -0
  135. package/dist/core/ingestion/languages/csharp/arity-metadata.d.ts +26 -0
  136. package/dist/core/ingestion/languages/csharp/arity-metadata.js +46 -0
  137. package/dist/core/ingestion/languages/csharp/arity.d.ts +23 -0
  138. package/dist/core/ingestion/languages/csharp/arity.js +37 -0
  139. package/dist/core/ingestion/languages/csharp/cache-stats.d.ts +15 -0
  140. package/dist/core/ingestion/languages/csharp/cache-stats.js +26 -0
  141. package/dist/core/ingestion/languages/csharp/captures.d.ts +19 -0
  142. package/dist/core/ingestion/languages/csharp/captures.js +249 -0
  143. package/dist/core/ingestion/languages/csharp/import-decomposer.d.ts +19 -0
  144. package/dist/core/ingestion/languages/csharp/import-decomposer.js +93 -0
  145. package/dist/core/ingestion/languages/csharp/import-target.d.ts +25 -0
  146. package/dist/core/ingestion/languages/csharp/import-target.js +123 -0
  147. package/dist/core/ingestion/languages/csharp/index.d.ts +82 -0
  148. package/dist/core/ingestion/languages/csharp/index.js +82 -0
  149. package/dist/core/ingestion/languages/csharp/interpret.d.ts +15 -0
  150. package/dist/core/ingestion/languages/csharp/interpret.js +132 -0
  151. package/dist/core/ingestion/languages/csharp/merge-bindings.d.ts +27 -0
  152. package/dist/core/ingestion/languages/csharp/merge-bindings.js +55 -0
  153. package/dist/core/ingestion/languages/csharp/namespace-siblings.d.ts +50 -0
  154. package/dist/core/ingestion/languages/csharp/namespace-siblings.js +374 -0
  155. package/dist/core/ingestion/languages/csharp/query.d.ts +35 -0
  156. package/dist/core/ingestion/languages/csharp/query.js +515 -0
  157. package/dist/core/ingestion/languages/csharp/receiver-binding.d.ts +31 -0
  158. package/dist/core/ingestion/languages/csharp/receiver-binding.js +135 -0
  159. package/dist/core/ingestion/languages/csharp/scope-resolver.d.ts +10 -0
  160. package/dist/core/ingestion/languages/csharp/scope-resolver.js +63 -0
  161. package/dist/core/ingestion/languages/csharp/simple-hooks.d.ts +53 -0
  162. package/dist/core/ingestion/languages/csharp/simple-hooks.js +76 -0
  163. package/dist/core/ingestion/languages/csharp.js +14 -0
  164. package/dist/core/ingestion/languages/python/arity-metadata.d.ts +24 -0
  165. package/dist/core/ingestion/languages/python/arity-metadata.js +45 -0
  166. package/dist/core/ingestion/languages/python/arity.d.ts +22 -0
  167. package/dist/core/ingestion/languages/python/arity.js +38 -0
  168. package/dist/core/ingestion/languages/python/cache-stats.d.ts +17 -0
  169. package/dist/core/ingestion/languages/python/cache-stats.js +28 -0
  170. package/dist/core/ingestion/languages/python/captures.d.ts +19 -0
  171. package/dist/core/ingestion/languages/python/captures.js +106 -0
  172. package/dist/core/ingestion/languages/python/import-decomposer.d.ts +15 -0
  173. package/dist/core/ingestion/languages/python/import-decomposer.js +112 -0
  174. package/dist/core/ingestion/languages/python/import-target.d.ts +21 -0
  175. package/dist/core/ingestion/languages/python/import-target.js +99 -0
  176. package/dist/core/ingestion/languages/python/index.d.ts +80 -0
  177. package/dist/core/ingestion/languages/python/index.js +80 -0
  178. package/dist/core/ingestion/languages/python/interpret.d.ts +15 -0
  179. package/dist/core/ingestion/languages/python/interpret.js +191 -0
  180. package/dist/core/ingestion/languages/python/merge-bindings.d.ts +16 -0
  181. package/dist/core/ingestion/languages/python/merge-bindings.js +44 -0
  182. package/dist/core/ingestion/languages/python/query.d.ts +9 -0
  183. package/dist/core/ingestion/languages/python/query.js +267 -0
  184. package/dist/core/ingestion/languages/python/receiver-binding.d.ts +21 -0
  185. package/dist/core/ingestion/languages/python/receiver-binding.js +116 -0
  186. package/dist/core/ingestion/languages/python/scope-resolver.d.ts +16 -0
  187. package/dist/core/ingestion/languages/python/scope-resolver.js +53 -0
  188. package/dist/core/ingestion/languages/python/simple-hooks.d.ts +23 -0
  189. package/dist/core/ingestion/languages/python/simple-hooks.js +35 -0
  190. package/dist/core/ingestion/languages/python.js +14 -0
  191. package/dist/core/ingestion/model/method-registry.d.ts +9 -0
  192. package/dist/core/ingestion/model/method-registry.js +4 -0
  193. package/dist/core/ingestion/model/scope-resolution-indexes.d.ts +59 -0
  194. package/dist/core/ingestion/model/scope-resolution-indexes.js +42 -0
  195. package/dist/core/ingestion/model/semantic-model.d.ts +64 -0
  196. package/dist/core/ingestion/model/semantic-model.js +55 -0
  197. package/dist/core/ingestion/mro-processor.js +38 -22
  198. package/dist/core/ingestion/parsing-processor.d.ts +18 -1
  199. package/dist/core/ingestion/parsing-processor.js +45 -11
  200. package/dist/core/ingestion/pipeline-phases/index.d.ts +1 -0
  201. package/dist/core/ingestion/pipeline-phases/index.js +1 -0
  202. package/dist/core/ingestion/pipeline-phases/parse-impl.d.ts +10 -0
  203. package/dist/core/ingestion/pipeline-phases/parse-impl.js +17 -2
  204. package/dist/core/ingestion/pipeline-phases/parse.d.ts +18 -0
  205. package/dist/core/ingestion/pipeline.js +2 -1
  206. package/dist/core/ingestion/registry-primary-flag.d.ts +86 -0
  207. package/dist/core/ingestion/registry-primary-flag.js +111 -0
  208. package/dist/core/ingestion/resolve-references.d.ts +63 -0
  209. package/dist/core/ingestion/resolve-references.js +175 -0
  210. package/dist/core/ingestion/scope-extractor-bridge.d.ts +32 -0
  211. package/dist/core/ingestion/scope-extractor-bridge.js +44 -0
  212. package/dist/core/ingestion/scope-extractor.d.ts +86 -0
  213. package/dist/core/ingestion/scope-extractor.js +758 -0
  214. package/dist/core/ingestion/scope-resolution/contract/scope-resolver.d.ts +372 -0
  215. package/dist/core/ingestion/scope-resolution/contract/scope-resolver.js +212 -0
  216. package/dist/core/ingestion/scope-resolution/graph-bridge/edges.d.ts +43 -0
  217. package/dist/core/ingestion/scope-resolution/graph-bridge/edges.js +79 -0
  218. package/dist/core/ingestion/scope-resolution/graph-bridge/ids.d.ts +57 -0
  219. package/dist/core/ingestion/scope-resolution/graph-bridge/ids.js +112 -0
  220. package/dist/core/ingestion/scope-resolution/graph-bridge/imports-to-edges.d.ts +17 -0
  221. package/dist/core/ingestion/scope-resolution/graph-bridge/imports-to-edges.js +46 -0
  222. package/dist/core/ingestion/scope-resolution/graph-bridge/method-dispatch.d.ts +19 -0
  223. package/dist/core/ingestion/scope-resolution/graph-bridge/method-dispatch.js +30 -0
  224. package/dist/core/ingestion/scope-resolution/graph-bridge/node-lookup.d.ts +37 -0
  225. package/dist/core/ingestion/scope-resolution/graph-bridge/node-lookup.js +113 -0
  226. package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.d.ts +38 -0
  227. package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.js +73 -0
  228. package/dist/core/ingestion/scope-resolution/passes/compound-receiver.d.ts +42 -0
  229. package/dist/core/ingestion/scope-resolution/passes/compound-receiver.js +198 -0
  230. package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.d.ts +27 -0
  231. package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.js +131 -0
  232. package/dist/core/ingestion/scope-resolution/passes/imported-return-types.d.ts +48 -0
  233. package/dist/core/ingestion/scope-resolution/passes/imported-return-types.js +130 -0
  234. package/dist/core/ingestion/scope-resolution/passes/mro.d.ts +42 -0
  235. package/dist/core/ingestion/scope-resolution/passes/mro.js +99 -0
  236. package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.d.ts +26 -0
  237. package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.js +61 -0
  238. package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.d.ts +46 -0
  239. package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +327 -0
  240. package/dist/core/ingestion/scope-resolution/pipeline/phase.d.ts +47 -0
  241. package/dist/core/ingestion/scope-resolution/pipeline/phase.js +130 -0
  242. package/dist/core/ingestion/scope-resolution/pipeline/reconcile-ownership.d.ts +68 -0
  243. package/dist/core/ingestion/scope-resolution/pipeline/reconcile-ownership.js +125 -0
  244. package/dist/core/ingestion/scope-resolution/pipeline/registry.d.ts +17 -0
  245. package/dist/core/ingestion/scope-resolution/pipeline/registry.js +21 -0
  246. package/dist/core/ingestion/scope-resolution/pipeline/run.d.ts +66 -0
  247. package/dist/core/ingestion/scope-resolution/pipeline/run.js +157 -0
  248. package/dist/core/ingestion/scope-resolution/scope/namespace-targets.d.ts +36 -0
  249. package/dist/core/ingestion/scope-resolution/scope/namespace-targets.js +52 -0
  250. package/dist/core/ingestion/scope-resolution/scope/walkers.d.ts +127 -0
  251. package/dist/core/ingestion/scope-resolution/scope/walkers.js +349 -0
  252. package/dist/core/ingestion/scope-resolution/workspace-index.d.ts +52 -0
  253. package/dist/core/ingestion/scope-resolution/workspace-index.js +61 -0
  254. package/dist/core/ingestion/shadow-harness.d.ts +113 -0
  255. package/dist/core/ingestion/shadow-harness.js +148 -0
  256. package/dist/core/ingestion/utils/ast-helpers.d.ts +19 -1
  257. package/dist/core/ingestion/utils/ast-helpers.js +70 -0
  258. package/dist/core/ingestion/utils/max-file-size.d.ts +20 -0
  259. package/dist/core/ingestion/utils/max-file-size.js +52 -0
  260. package/dist/core/ingestion/workers/parse-worker.d.ts +9 -0
  261. package/dist/core/ingestion/workers/parse-worker.js +57 -21
  262. package/dist/core/lbug/lbug-adapter.d.ts +22 -2
  263. package/dist/core/lbug/lbug-adapter.js +58 -14
  264. package/dist/core/lbug/pool-adapter.d.ts +17 -0
  265. package/dist/core/lbug/pool-adapter.js +24 -14
  266. package/dist/core/run-analyze.d.ts +32 -0
  267. package/dist/core/run-analyze.js +74 -19
  268. package/dist/core/search/bm25-index.d.ts +18 -0
  269. package/dist/core/search/bm25-index.js +125 -12
  270. package/dist/core/tree-sitter/parser-loader.js +6 -1
  271. package/dist/mcp/local/local-backend.d.ts +67 -3
  272. package/dist/mcp/local/local-backend.js +296 -34
  273. package/dist/mcp/resources.d.ts +31 -0
  274. package/dist/mcp/resources.js +100 -17
  275. package/dist/mcp/tools.d.ts +4 -1
  276. package/dist/mcp/tools.js +75 -54
  277. package/dist/server/api.js +6 -2
  278. package/dist/storage/git.d.ts +49 -0
  279. package/dist/storage/git.js +111 -0
  280. package/dist/storage/repo-manager.d.ts +246 -1
  281. package/dist/storage/repo-manager.js +391 -9
  282. package/package.json +7 -6
  283. package/scripts/bench-scope-resolution.ts +134 -0
  284. package/scripts/ci-list-migrated-languages.ts +24 -0
  285. package/skills/gitnexus-cli.md +1 -0
@@ -0,0 +1,42 @@
1
+ /**
2
+ * `ScopeResolutionIndexes` — the bundle of materialized indexes produced
3
+ * by the finalize-orchestrator (RFC #909 Ring 2 PKG #921) and attached
4
+ * to `MutableSemanticModel`.
5
+ *
6
+ * Produced by `finalizeScopeModel(parsedFiles, hooks)` in
7
+ * `finalize-orchestrator.ts`. Consumed by the resolution phase (future
8
+ * tickets) where `Registry.lookup` / `resolveTypeRef` query this bundle
9
+ * to answer call-resolution questions without re-walking any AST.
10
+ *
11
+ * ## Lifecycle
12
+ *
13
+ * 1. Pipeline collects `ParsedFile[]` from the parsing-processor (#920).
14
+ * 2. Pipeline invokes `finalizeScopeModel(parsedFiles, hooks)` →
15
+ * returns a `ScopeResolutionIndexes` (this interface).
16
+ * 3. Pipeline calls `model.attachScopeIndexes(indexes)` to stamp them
17
+ * onto the `MutableSemanticModel`. This is a **one-shot write**;
18
+ * subsequent calls throw. After attachment, the indexes are frozen
19
+ * at the type level (everything is `readonly`) and at runtime via
20
+ * `Object.freeze` on the bundle.
21
+ * 4. Resolution callers hold a `SemanticModel` reference and read
22
+ * `model.scopes` to query.
23
+ *
24
+ * ## Content
25
+ *
26
+ * - `scopeTree` / `moduleScopes` / `defs` / `qualifiedNames` — the
27
+ * four Ring 2 SHARED indexes built over per-file artifacts.
28
+ * - `methodDispatch` — MRO + implements materialized view (#914).
29
+ * - `imports` — finalized `ImportEdge[]` per module scope (`parsedImports`
30
+ * resolved through cross-file link + wildcard expansion).
31
+ * - `bindings` — merged bindings per module scope (local + import +
32
+ * wildcard + re-export), with the provider's precedence applied.
33
+ * - `referenceSites` — union of every file's pre-resolution usage
34
+ * facts. Consumed by the resolution phase (future) to emit
35
+ * `Reference` records into `ReferenceIndex`.
36
+ * - `stats` — coarse-grained counts from the shared finalize algorithm
37
+ * (total files/edges, linked vs unresolved, SCC topology).
38
+ *
39
+ * `ReferenceIndex` is deliberately NOT here — it is populated in a later
40
+ * phase (RFC §3.2 Phase 4 / Ring 2 PKG #925) and owned separately.
41
+ */
42
+ export {};
@@ -44,11 +44,51 @@
44
44
  * direct `createSymbolTable()` caller (e.g. an isolated unit test) gets
45
45
  * the pure, registry-free behavior — no surprises, no hidden side
46
46
  * effects.
47
+ *
48
+ * ## Single-source-of-truth invariant
49
+ *
50
+ * `SemanticModel` is the authoritative symbol store for the whole
51
+ * ingestion pipeline. Both the legacy Call-Resolution DAG and the
52
+ * new scope-resolution pipeline read symbol-keyed lookups from here
53
+ * exclusively — no parallel owner-keyed, name-keyed, or file-keyed
54
+ * symbol indexes exist outside this module. The scope-resolution
55
+ * pipeline does carry a small `WorkspaceResolutionIndex` for
56
+ * `Scope`-valued maps (`classScopeByDefId`, `moduleScopeByFile`) that
57
+ * `SemanticModel` structurally cannot hold, but nothing else.
58
+ *
59
+ * ## Write / read phase contract
60
+ *
61
+ * Writes to the model happen in three clearly-ordered phases during a
62
+ * single ingestion run:
63
+ *
64
+ * 1. **Legacy parse phase** (`parsing-processor`) calls
65
+ * `symbols.add(...)` per extracted symbol, which fans out via
66
+ * the dispatch table into `types` / `methods` / `fields`.
67
+ * 2. **Scope-resolution reconciliation** (`reconcileOwnership` in
68
+ * `scope-resolution/pipeline/reconcile-ownership.ts`) registers
69
+ * any `parsed.localDefs[i]` with a scope-resolution-corrected
70
+ * `ownerId` that the legacy pass missed (Python class-body
71
+ * methods are the canonical case). Idempotent.
72
+ * 3. **Finalize-orchestrator** calls `attachScopeIndexes(...)` to
73
+ * stamp the materialized `ScopeResolutionIndexes` bundle onto
74
+ * `model.scopes`. One-shot; throws on a second call.
75
+ *
76
+ * After these three phases, the model is effectively frozen:
77
+ * - `attachScopeIndexes` applied `Object.freeze` to its bundle.
78
+ * - Downstream passes receive the narrowed `SemanticModel` reader
79
+ * handle (not `MutableSemanticModel`), so `.register()` /
80
+ * `.clear()` / `attachScopeIndexes()` are structurally absent.
81
+ *
82
+ * See `scope-resolution/contract/scope-resolver.ts` Contract
83
+ * Invariant I9 for the scope-resolution-side rule and
84
+ * `ARCHITECTURE.md` § "Semantic-model source of truth" for the
85
+ * overall architecture.
47
86
  */
48
87
  import type { TypeRegistry, MutableTypeRegistry } from './type-registry.js';
49
88
  import type { MethodRegistry, MutableMethodRegistry } from './method-registry.js';
50
89
  import type { FieldRegistry, MutableFieldRegistry } from './field-registry.js';
51
90
  import type { SymbolTableReader, SymbolTableWriter } from './symbol-table.js';
91
+ import type { ScopeResolutionIndexes } from './scope-resolution-indexes.js';
52
92
  /**
53
93
  * Aggregated read-only view of the semantic registries plus the nested
54
94
  * file/callable SymbolTable.
@@ -70,6 +110,19 @@ export interface SemanticModel {
70
110
  readonly methods: MethodRegistry;
71
111
  readonly fields: FieldRegistry;
72
112
  readonly symbols: SymbolTableReader;
113
+ /**
114
+ * Materialized scope-resolution indexes from RFC #909 Ring 2 PKG #921.
115
+ *
116
+ * `undefined` until the finalize-orchestrator attaches them. While
117
+ * `undefined`, the legacy DAG is the sole resolution surface; once set,
118
+ * resolvers whose language has `REGISTRY_PRIMARY_<LANG>=true` consult
119
+ * these indexes instead.
120
+ *
121
+ * The attach is a one-shot write (see `MutableSemanticModel`). Callers
122
+ * holding a read-only `SemanticModel` handle see either `undefined` or
123
+ * the final frozen bundle — never a half-populated view.
124
+ */
125
+ readonly scopes?: ScopeResolutionIndexes;
73
126
  }
74
127
  /** Mutable variant — exposes the MutableX registries, a Writer-typed
75
128
  * `symbols` facade, and a full-cascade reset. This is the interface
@@ -82,5 +135,16 @@ export interface MutableSemanticModel extends SemanticModel {
82
135
  readonly symbols: SymbolTableWriter;
83
136
  /** Clear all registries AND the nested SymbolTable. */
84
137
  clear(): void;
138
+ /**
139
+ * Stamp the finalize-orchestrator's output onto this model.
140
+ *
141
+ * **One-shot write.** Throws when called a second time — the indexes are
142
+ * meant to be materialized once per ingestion run. `Object.freeze` is
143
+ * applied to the attached bundle so consumers cannot mutate after attach.
144
+ *
145
+ * `clear()` resets the attached bundle back to `undefined`, enabling a
146
+ * fresh re-ingestion to attach a new bundle.
147
+ */
148
+ attachScopeIndexes(indexes: ScopeResolutionIndexes): void;
85
149
  }
86
150
  export declare const createSemanticModel: () => MutableSemanticModel;
@@ -44,6 +44,45 @@
44
44
  * direct `createSymbolTable()` caller (e.g. an isolated unit test) gets
45
45
  * the pure, registry-free behavior — no surprises, no hidden side
46
46
  * effects.
47
+ *
48
+ * ## Single-source-of-truth invariant
49
+ *
50
+ * `SemanticModel` is the authoritative symbol store for the whole
51
+ * ingestion pipeline. Both the legacy Call-Resolution DAG and the
52
+ * new scope-resolution pipeline read symbol-keyed lookups from here
53
+ * exclusively — no parallel owner-keyed, name-keyed, or file-keyed
54
+ * symbol indexes exist outside this module. The scope-resolution
55
+ * pipeline does carry a small `WorkspaceResolutionIndex` for
56
+ * `Scope`-valued maps (`classScopeByDefId`, `moduleScopeByFile`) that
57
+ * `SemanticModel` structurally cannot hold, but nothing else.
58
+ *
59
+ * ## Write / read phase contract
60
+ *
61
+ * Writes to the model happen in three clearly-ordered phases during a
62
+ * single ingestion run:
63
+ *
64
+ * 1. **Legacy parse phase** (`parsing-processor`) calls
65
+ * `symbols.add(...)` per extracted symbol, which fans out via
66
+ * the dispatch table into `types` / `methods` / `fields`.
67
+ * 2. **Scope-resolution reconciliation** (`reconcileOwnership` in
68
+ * `scope-resolution/pipeline/reconcile-ownership.ts`) registers
69
+ * any `parsed.localDefs[i]` with a scope-resolution-corrected
70
+ * `ownerId` that the legacy pass missed (Python class-body
71
+ * methods are the canonical case). Idempotent.
72
+ * 3. **Finalize-orchestrator** calls `attachScopeIndexes(...)` to
73
+ * stamp the materialized `ScopeResolutionIndexes` bundle onto
74
+ * `model.scopes`. One-shot; throws on a second call.
75
+ *
76
+ * After these three phases, the model is effectively frozen:
77
+ * - `attachScopeIndexes` applied `Object.freeze` to its bundle.
78
+ * - Downstream passes receive the narrowed `SemanticModel` reader
79
+ * handle (not `MutableSemanticModel`), so `.register()` /
80
+ * `.clear()` / `attachScopeIndexes()` are structurally absent.
81
+ *
82
+ * See `scope-resolution/contract/scope-resolver.ts` Contract
83
+ * Invariant I9 for the scope-resolution-side rule and
84
+ * `ARCHITECTURE.md` § "Semantic-model source of truth" for the
85
+ * overall architecture.
47
86
  */
48
87
  import { createTypeRegistry } from './type-registry.js';
49
88
  import { createMethodRegistry } from './method-registry.js';
@@ -85,6 +124,17 @@ export const createSemanticModel = () => {
85
124
  }
86
125
  return def;
87
126
  };
127
+ // Scope-resolution bundle slot. Starts `undefined`; populated by a
128
+ // one-shot `attachScopeIndexes(...)` from the finalize-orchestrator.
129
+ // Held inside the factory closure so the returned `SemanticModel`
130
+ // surface exposes it as a plain `readonly` property without a setter.
131
+ let attachedScopes;
132
+ const attachScopeIndexes = (indexes) => {
133
+ if (attachedScopes !== undefined) {
134
+ throw new Error('SemanticModel: scope indexes already attached. ' + 'Call `clear()` before re-attaching.');
135
+ }
136
+ attachedScopes = Object.freeze(indexes);
137
+ };
88
138
  // Cascade clear: single source of truth for "reset the entire model".
89
139
  // Wired into both `model.clear()` AND `model.symbols.clear()` so that a
90
140
  // caller holding only a SymbolTable reference can't leave the
@@ -95,6 +145,7 @@ export const createSemanticModel = () => {
95
145
  methods.clear();
96
146
  fields.clear();
97
147
  rawSymbols.clear();
148
+ attachedScopes = undefined;
98
149
  };
99
150
  // Writer-typed facade: exposes reads + add, but NO `clear` field.
100
151
  // Callers holding a `SemanticModel.symbols` reference cannot desync
@@ -115,6 +166,10 @@ export const createSemanticModel = () => {
115
166
  methods,
116
167
  fields,
117
168
  symbols,
169
+ get scopes() {
170
+ return attachedScopes;
171
+ },
118
172
  clear: cascadeClear,
173
+ attachScopeIndexes,
119
174
  };
120
175
  };
@@ -32,30 +32,46 @@ function buildAdjacency(graph) {
32
32
  const methodMap = new Map();
33
33
  // Track which edge type each parent link came from
34
34
  const parentEdgeType = new Map();
35
- graph.forEachRelationship((rel) => {
36
- if (rel.type === 'EXTENDS' || rel.type === 'IMPLEMENTS') {
37
- let parents = parentMap.get(rel.sourceId);
38
- if (!parents) {
39
- parents = [];
40
- parentMap.set(rel.sourceId, parents);
41
- }
42
- parents.push(rel.targetId);
43
- let edgeTypes = parentEdgeType.get(rel.sourceId);
44
- if (!edgeTypes) {
45
- edgeTypes = new Map();
46
- parentEdgeType.set(rel.sourceId, edgeTypes);
47
- }
48
- edgeTypes.set(rel.targetId, rel.type);
35
+ // Three typed iterations replace one full-relationship-map scan
36
+ // with per-edge type checks. Each consumes only the edges of the
37
+ // type it cares about — see plan
38
+ // docs/plans/2026-04-20-002-perf-parse-heritage-mro-plan.md (Unit 2).
39
+ for (const rel of graph.iterRelationshipsByType('EXTENDS')) {
40
+ let parents = parentMap.get(rel.sourceId);
41
+ if (!parents) {
42
+ parents = [];
43
+ parentMap.set(rel.sourceId, parents);
49
44
  }
50
- if (rel.type === 'HAS_METHOD') {
51
- let methods = methodMap.get(rel.sourceId);
52
- if (!methods) {
53
- methods = [];
54
- methodMap.set(rel.sourceId, methods);
55
- }
56
- methods.push(rel.targetId);
45
+ parents.push(rel.targetId);
46
+ let edgeTypes = parentEdgeType.get(rel.sourceId);
47
+ if (!edgeTypes) {
48
+ edgeTypes = new Map();
49
+ parentEdgeType.set(rel.sourceId, edgeTypes);
50
+ }
51
+ edgeTypes.set(rel.targetId, 'EXTENDS');
52
+ }
53
+ for (const rel of graph.iterRelationshipsByType('IMPLEMENTS')) {
54
+ let parents = parentMap.get(rel.sourceId);
55
+ if (!parents) {
56
+ parents = [];
57
+ parentMap.set(rel.sourceId, parents);
57
58
  }
58
- });
59
+ parents.push(rel.targetId);
60
+ let edgeTypes = parentEdgeType.get(rel.sourceId);
61
+ if (!edgeTypes) {
62
+ edgeTypes = new Map();
63
+ parentEdgeType.set(rel.sourceId, edgeTypes);
64
+ }
65
+ edgeTypes.set(rel.targetId, 'IMPLEMENTS');
66
+ }
67
+ for (const rel of graph.iterRelationshipsByType('HAS_METHOD')) {
68
+ let methods = methodMap.get(rel.sourceId);
69
+ if (!methods) {
70
+ methods = [];
71
+ methodMap.set(rel.sourceId, methods);
72
+ }
73
+ methods.push(rel.targetId);
74
+ }
59
75
  return { parentMap, methodMap, parentEdgeType };
60
76
  }
61
77
  /** Resolve by MRO order — first ancestor in linearized order wins. */
@@ -1,6 +1,7 @@
1
1
  import { KnowledgeGraph } from '../graph/types.js';
2
2
  import type { SymbolTableWriter, ExtractedHeritage } from './model/index.js';
3
3
  import { ASTCache } from './ast-cache.js';
4
+ import type { ParsedFile } from '../../_shared/index.js';
4
5
  import { WorkerPool } from './workers/worker-pool.js';
5
6
  import type { ExtractedImport, ExtractedCall, ExtractedAssignment, ExtractedRoute, ExtractedFetchCall, ExtractedDecoratorRoute, ExtractedToolDef, FileConstructorBindings, FileScopeBindings, ExtractedORMQuery } from './workers/parse-worker.js';
6
7
  export type FileProgressCallback = (current: number, total: number, filePath: string) => void;
@@ -16,8 +17,24 @@ export interface WorkerExtractedData {
16
17
  ormQueries: ExtractedORMQuery[];
17
18
  constructorBindings: FileConstructorBindings[];
18
19
  fileScopeBindings: FileScopeBindings[];
20
+ /**
21
+ * Per-file `ParsedFile` artifacts from the new scope-based resolution
22
+ * pipeline (RFC #909 Ring 2). Empty until a provider implements
23
+ * `emitScopeCaptures` — additive to the legacy DAG path. Aggregated
24
+ * from every worker chunk; consumed downstream by #921's
25
+ * finalize-orchestrator.
26
+ */
27
+ parsedFiles: ParsedFile[];
19
28
  }
20
29
  export declare const processParsing: (graph: KnowledgeGraph, files: {
21
30
  path: string;
22
31
  content: string;
23
- }[], symbolTable: SymbolTableWriter, astCache: ASTCache, onFileProgress?: FileProgressCallback, workerPool?: WorkerPool) => Promise<WorkerExtractedData | null>;
32
+ }[], symbolTable: SymbolTableWriter, astCache: ASTCache,
33
+ /**
34
+ * Persistent tree cache (separate from `astCache`, which the caller
35
+ * clears between chunks). Sequential parses additionally write the
36
+ * Tree here so cross-phase consumers (scope-resolution) can read it.
37
+ * Worker-mode parses skip — Trees can't cross MessageChannels.
38
+ * Pass `undefined` if no consumer needs cross-phase access.
39
+ */
40
+ scopeTreeCache: ASTCache | undefined, onFileProgress?: FileProgressCallback, workerPool?: WorkerPool) => Promise<WorkerExtractedData | null>;
@@ -5,6 +5,7 @@ import { generateId } from '../../lib/utils.js';
5
5
  import { getLanguageFromFilename, SupportedLanguages } from '../../_shared/index.js';
6
6
  import { extractVueScript, isVueSetupTopLevel } from './vue-sfc-extractor.js';
7
7
  import { yieldToEventLoop } from './utils/event-loop.js';
8
+ import { isVerboseIngestionEnabled } from './utils/verbose.js';
8
9
  import { getDefinitionNodeFromCaptures, findEnclosingClassInfo, getLabelFromCaptures, CLASS_CONTAINER_TYPES, } from './utils/ast-helpers.js';
9
10
  import { detectFrameworkFromAST } from './framework-detection.js';
10
11
  import { buildTypeEnv } from './type-env.js';
@@ -34,6 +35,7 @@ const processParsingWithWorkers = async (graph, files, symbolTable, astCache, wo
34
35
  ormQueries: [],
35
36
  constructorBindings: [],
36
37
  fileScopeBindings: [],
38
+ parsedFiles: [],
37
39
  };
38
40
  const total = files.length;
39
41
  // Dispatch to worker pool — pool handles splitting into chunks and sub-batching
@@ -52,6 +54,7 @@ const processParsingWithWorkers = async (graph, files, symbolTable, astCache, wo
52
54
  const allORMQueries = [];
53
55
  const allConstructorBindings = [];
54
56
  const fileScopeBindingsByFile = [];
57
+ const allParsedFiles = [];
55
58
  for (const result of chunkResults) {
56
59
  for (const node of result.nodes) {
57
60
  graph.addNode({
@@ -98,6 +101,13 @@ const processParsingWithWorkers = async (graph, files, symbolTable, astCache, wo
98
101
  if (result.fileScopeBindings)
99
102
  for (const item of result.fileScopeBindings)
100
103
  fileScopeBindingsByFile.push(item);
104
+ // RFC #909 Ring 2: aggregate per-file scope artifacts. Tolerant of
105
+ // workers that don't emit the field yet (older worker builds or
106
+ // partial rollouts), since the additive contract means undefined =
107
+ // "this worker produced no ParsedFiles for this chunk".
108
+ if (result.parsedFiles)
109
+ for (const item of result.parsedFiles)
110
+ allParsedFiles.push(item);
101
111
  }
102
112
  // Merge and log skipped languages from workers
103
113
  const skippedLanguages = new Map();
@@ -126,6 +136,7 @@ const processParsingWithWorkers = async (graph, files, symbolTable, astCache, wo
126
136
  ormQueries: allORMQueries,
127
137
  constructorBindings: allConstructorBindings,
128
138
  fileScopeBindings: fileScopeBindingsByFile,
139
+ parsedFiles: allParsedFiles,
129
140
  };
130
141
  };
131
142
  // ============================================================================
@@ -210,10 +221,11 @@ function seqGetFieldInfo(classNode, provider, context) {
210
221
  seqFieldInfoCache.set(cacheKey, cached);
211
222
  return cached;
212
223
  }
213
- const processParsingSequential = async (graph, files, symbolTable, astCache, onFileProgress) => {
224
+ const processParsingSequential = async (graph, files, symbolTable, astCache, scopeTreeCache, onFileProgress) => {
214
225
  const parser = await loadParser();
215
226
  const total = files.length;
216
- const skippedLanguages = new Map();
227
+ const logSkipped = isVerboseIngestionEnabled();
228
+ const skippedByLang = logSkipped ? new Map() : null;
217
229
  for (let i = 0; i < files.length; i++) {
218
230
  const file = files[i];
219
231
  // Reset memoization before each new file (node refs are per-tree)
@@ -228,9 +240,10 @@ const processParsingSequential = async (graph, files, symbolTable, astCache, onF
228
240
  const language = getLanguageFromFilename(file.path);
229
241
  if (!language)
230
242
  continue;
231
- // Skip unsupported languages (e.g. Swift when tree-sitter-swift not installed)
232
243
  if (!isLanguageAvailable(language)) {
233
- skippedLanguages.set(language, (skippedLanguages.get(language) || 0) + 1);
244
+ if (skippedByLang) {
245
+ skippedByLang.set(language, (skippedByLang.get(language) ?? 0) + 1);
246
+ }
234
247
  continue;
235
248
  }
236
249
  // Skip files larger than the max tree-sitter buffer (32 MB)
@@ -266,6 +279,13 @@ const processParsingSequential = async (graph, files, symbolTable, astCache, onF
266
279
  }
267
280
  astCache.set(file.path, tree);
268
281
  const provider = getProvider(language);
282
+ // Mirror into the cross-phase cache only when the language has a
283
+ // scope-resolution consumer — otherwise we retain Trees no one
284
+ // reads. parse-impl clears `astCache` between chunks;
285
+ // `scopeTreeCache` survives until scope-resolution disposes it.
286
+ if (provider.emitScopeCaptures !== undefined) {
287
+ scopeTreeCache?.set(file.path, tree);
288
+ }
269
289
  const queryString = provider.treeSitterQueries;
270
290
  if (!queryString) {
271
291
  continue;
@@ -517,18 +537,32 @@ const processParsingSequential = async (graph, files, symbolTable, astCache, onF
517
537
  }
518
538
  });
519
539
  }
520
- if (skippedLanguages.size > 0) {
521
- const summary = Array.from(skippedLanguages.entries())
522
- .map(([lang, count]) => `${lang}: ${count}`)
523
- .join(', ');
524
- console.warn(` Skipped unsupported languages: ${summary}`);
540
+ if (skippedByLang && skippedByLang.size > 0) {
541
+ for (const [lang, count] of skippedByLang.entries()) {
542
+ console.warn(`[ingestion] Skipped ${count} ${lang} file(s) in parsing processing — ${lang} parser not available.`);
543
+ }
525
544
  }
526
545
  };
527
546
  // ============================================================================
528
547
  // Public API
529
548
  // ============================================================================
530
- export const processParsing = async (graph, files, symbolTable, astCache, onFileProgress, workerPool) => {
549
+ export const processParsing = async (graph, files, symbolTable, astCache,
550
+ /**
551
+ * Persistent tree cache (separate from `astCache`, which the caller
552
+ * clears between chunks). Sequential parses additionally write the
553
+ * Tree here so cross-phase consumers (scope-resolution) can read it.
554
+ * Worker-mode parses skip — Trees can't cross MessageChannels.
555
+ * Pass `undefined` if no consumer needs cross-phase access.
556
+ */
557
+ scopeTreeCache, onFileProgress, workerPool) => {
531
558
  if (workerPool) {
559
+ if (scopeTreeCache !== undefined && process.env.PROF_SCOPE_RESOLUTION === '1') {
560
+ // Trees can't cross MessageChannels, so worker-parsed files land
561
+ // in scope-resolution with an empty cache and get re-parsed.
562
+ // Surfacing this in PROF mode prevents silent perf cliffs when
563
+ // a repo crosses the worker-pool threshold.
564
+ console.warn(`[scope-resolution prof] worker pool engaged for ${files.length} files — cross-phase tree cache will be empty; scope-resolution re-parses.`);
565
+ }
532
566
  try {
533
567
  return await processParsingWithWorkers(graph, files, symbolTable, astCache, workerPool, onFileProgress);
534
568
  }
@@ -537,6 +571,6 @@ export const processParsing = async (graph, files, symbolTable, astCache, onFile
537
571
  }
538
572
  }
539
573
  // Fallback: sequential parsing (no pre-extracted data)
540
- await processParsingSequential(graph, files, symbolTable, astCache, onFileProgress);
574
+ await processParsingSequential(graph, files, symbolTable, astCache, scopeTreeCache, onFileProgress);
541
575
  return null;
542
576
  };
@@ -13,6 +13,7 @@ export { routesPhase, type RoutesOutput, type RouteEntry } from './routes.js';
13
13
  export { toolsPhase, type ToolsOutput, type ToolDef } from './tools.js';
14
14
  export { ormPhase, type ORMOutput } from './orm.js';
15
15
  export { crossFilePhase, type CrossFileOutput } from './cross-file.js';
16
+ export { scopeResolutionPhase, type ScopeResolutionOutput, } from '../scope-resolution/pipeline/phase.js';
16
17
  export { mroPhase, type MROOutput } from './mro.js';
17
18
  export { communitiesPhase, type CommunitiesOutput } from './communities.js';
18
19
  export { processesPhase, type ProcessesOutput } from './processes.js';
@@ -14,6 +14,7 @@ export { routesPhase } from './routes.js';
14
14
  export { toolsPhase } from './tools.js';
15
15
  export { ormPhase } from './orm.js';
16
16
  export { crossFilePhase } from './cross-file.js';
17
+ export { scopeResolutionPhase, } from '../scope-resolution/pipeline/phase.js';
17
18
  export { mroPhase } from './mro.js';
18
19
  export { communitiesPhase } from './communities.js';
19
20
  export { processesPhase } from './processes.js';
@@ -14,6 +14,7 @@
14
14
  import { BindingAccumulator } from '../binding-accumulator.js';
15
15
  import { type ExportedTypeMap } from '../call-processor.js';
16
16
  import { createResolutionContext } from '../model/resolution-context.js';
17
+ import { ASTCache } from '../ast-cache.js';
17
18
  import { type PipelineProgress } from '../../../_shared/index.js';
18
19
  import type { ExtractedDecoratorRoute, ExtractedFetchCall, ExtractedORMQuery, ExtractedRoute, ExtractedToolDef } from '../workers/parse-worker.js';
19
20
  import type { KnowledgeGraph } from '../../graph/types.js';
@@ -44,5 +45,14 @@ export declare function runChunkedParseAndResolve(graph: KnowledgeGraph, scanned
44
45
  bindingAccumulator: BindingAccumulator;
45
46
  resolutionContext: ReturnType<typeof createResolutionContext>;
46
47
  usedWorkerPool: boolean;
48
+ /** Cross-phase tree-sitter Tree cache populated by the sequential
49
+ * parse path. Distinct from the chunk-local `astCache` used inside
50
+ * the parse loop (that one is cleared between chunks). Empty when
51
+ * every chunk ran via the worker pool (workers can't return native
52
+ * tree-sitter Trees across the MessageChannel). Downstream phases
53
+ * (scope-resolution) read from this to skip re-parsing the same
54
+ * source. See plan
55
+ * docs/plans/2026-04-20-002-perf-parse-heritage-mro-plan.md (Unit 4). */
56
+ scopeTreeCache: ASTCache;
47
57
  }>;
48
58
  export {};
@@ -126,9 +126,18 @@ export async function runChunkedParseAndResolve(graph, scannedFiles, allPaths, t
126
126
  }
127
127
  }
128
128
  let filesParsedSoFar = 0;
129
- // AST cache sized for one chunk (sequential fallback uses it for import/call/heritage)
129
+ // Two caches with different lifetimes:
130
+ // - `astCache` (chunk-local, cleared between chunks) — call /
131
+ // heritage / import processors read it during parse to avoid
132
+ // re-parsing within the same chunk.
133
+ // - `scopeTreeCache` (total-parseable-sized, never cleared by
134
+ // parse-impl) — exposed via ParseOutput so scope-resolution can
135
+ // skip a second tree-sitter parse. Worker-mode parses don't
136
+ // populate either; consumers fall back to a fresh parse.
137
+ // See plan docs/plans/2026-04-20-002-perf-parse-heritage-mro-plan.md (Unit 4).
130
138
  const maxChunkFiles = chunks.reduce((max, c) => Math.max(max, c.length), 0);
131
139
  let astCache = createASTCache(maxChunkFiles);
140
+ const scopeTreeCache = createASTCache(Math.max(parseableScanned.length, 1));
132
141
  // Build import resolution context once — suffix index, file lists, resolve cache.
133
142
  const importCtx = buildImportResolutionContext(allPaths);
134
143
  const allPathObjects = allPaths.map((p) => ({ path: p }));
@@ -161,7 +170,7 @@ export async function runChunkedParseAndResolve(graph, scannedFiles, allPaths, t
161
170
  const chunkFiles = chunkPaths
162
171
  .filter((p) => chunkContents.has(p))
163
172
  .map((p) => ({ path: p, content: chunkContents.get(p) }));
164
- const chunkWorkerData = await processParsing(graph, chunkFiles, symbolTable, astCache, (current, _total, filePath) => {
173
+ const chunkWorkerData = await processParsing(graph, chunkFiles, symbolTable, astCache, scopeTreeCache, (current, _total, filePath) => {
165
174
  const globalCurrent = filesParsedSoFar + current;
166
175
  const parsingProgress = 20 + (globalCurrent / totalParseable) * 62;
167
176
  onProgress({
@@ -439,5 +448,11 @@ export async function runChunkedParseAndResolve(graph, scannedFiles, allPaths, t
439
448
  // sequential fallback handled every chunk (either due to `skipWorkers`,
440
449
  // the file-count/byte thresholds, or a pool-creation failure).
441
450
  usedWorkerPool: workerPool !== undefined,
451
+ // Surface the persistent scope cache so downstream phases
452
+ // (scope-resolution) can skip re-parsing files that the
453
+ // sequential path already parsed. Survives chunk boundaries; the
454
+ // chunk-local `astCache` above is intentionally NOT exposed
455
+ // because parse-impl clears it between chunks.
456
+ scopeTreeCache,
442
457
  };
443
458
  }
@@ -19,6 +19,7 @@ import type { PipelinePhase } from './types.js';
19
19
  import type { BindingAccumulator } from '../binding-accumulator.js';
20
20
  import type { ExtractedFetchCall, ExtractedRoute, ExtractedDecoratorRoute, ExtractedToolDef, ExtractedORMQuery } from '../workers/parse-worker.js';
21
21
  import type { createResolutionContext } from '../model/resolution-context.js';
22
+ import type { ASTCache } from '../ast-cache.js';
22
23
  export interface ParseOutput {
23
24
  /**
24
25
  * Read-only snapshot of exported type bindings keyed by file path.
@@ -52,5 +53,22 @@ export interface ParseOutput {
52
53
  * see `PipelineOptions.workerThresholdsForTest`.
53
54
  */
54
55
  readonly usedWorkerPool: boolean;
56
+ /**
57
+ * Cross-phase tree-sitter Tree cache populated by the sequential
58
+ * parse path. Separate from the chunk-local `astCache` used *inside*
59
+ * the parse phase (which is cleared between chunks) — this one
60
+ * survives the whole phase and hands Trees to scope-resolution so
61
+ * it can skip a second parse.
62
+ *
63
+ * Empty entries for files that ran through the worker pool
64
+ * (workers can't return native tree-sitter Trees across the
65
+ * MessageChannel). Cache miss is safe — consumers fall back to a
66
+ * fresh parse. See plan
67
+ * docs/plans/2026-04-20-002-perf-parse-heritage-mro-plan.md (Unit 4).
68
+ *
69
+ * Disposed by `scopeResolutionPhase` (the sole consumer) via
70
+ * `scopeTreeCache.clear()` after its extract loop finishes.
71
+ */
72
+ readonly scopeTreeCache: ASTCache;
55
73
  }
56
74
  export declare const parsePhase: PipelinePhase<ParseOutput>;
@@ -15,7 +15,7 @@
15
15
  * See ARCHITECTURE.md for the full phase dependency diagram.
16
16
  */
17
17
  import { createKnowledgeGraph } from '../graph/graph.js';
18
- import { runPipeline, getPhaseOutput, scanPhase, structurePhase, markdownPhase, cobolPhase, parsePhase, routesPhase, toolsPhase, ormPhase, crossFilePhase, mroPhase, communitiesPhase, processesPhase, } from './pipeline-phases/index.js';
18
+ import { runPipeline, getPhaseOutput, scanPhase, structurePhase, markdownPhase, cobolPhase, parsePhase, routesPhase, toolsPhase, ormPhase, crossFilePhase, scopeResolutionPhase, mroPhase, communitiesPhase, processesPhase, } from './pipeline-phases/index.js';
19
19
  // ── Phase registry ─────────────────────────────────────────────────────────
20
20
  /**
21
21
  * All pipeline phases with their dependency relationships.
@@ -39,6 +39,7 @@ function buildPhaseList(options) {
39
39
  toolsPhase,
40
40
  ormPhase,
41
41
  crossFilePhase,
42
+ scopeResolutionPhase,
42
43
  ];
43
44
  if (!options?.skipGraphPhases) {
44
45
  phases.push(mroPhase, communitiesPhase, processesPhase);