gitnexus 1.6.3-rc.9 → 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 (257) 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 +4 -2
  5. package/dist/_shared/index.d.ts.map +1 -1
  6. package/dist/_shared/index.js +2 -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/evidence.js +5 -0
  33. package/dist/_shared/scope-resolution/registries/evidence.js.map +1 -1
  34. package/dist/_shared/scope-resolution/registries/lookup-core.js +21 -5
  35. package/dist/_shared/scope-resolution/registries/lookup-core.js.map +1 -1
  36. package/dist/_shared/scope-resolution/resolve-type-ref.d.ts +1 -10
  37. package/dist/_shared/scope-resolution/resolve-type-ref.d.ts.map +1 -1
  38. package/dist/_shared/scope-resolution/resolve-type-ref.js +6 -0
  39. package/dist/_shared/scope-resolution/resolve-type-ref.js.map +1 -1
  40. package/dist/_shared/scope-resolution/scope-tree.d.ts +4 -4
  41. package/dist/_shared/scope-resolution/scope-tree.d.ts.map +1 -1
  42. package/dist/_shared/scope-resolution/scope-tree.js +3 -2
  43. package/dist/_shared/scope-resolution/scope-tree.js.map +1 -1
  44. package/dist/_shared/scope-resolution/shadow/aggregate.d.ts +6 -2
  45. package/dist/_shared/scope-resolution/shadow/aggregate.d.ts.map +1 -1
  46. package/dist/_shared/scope-resolution/shadow/aggregate.js +5 -0
  47. package/dist/_shared/scope-resolution/shadow/aggregate.js.map +1 -1
  48. package/dist/_shared/scope-resolution/types.d.ts +11 -0
  49. package/dist/_shared/scope-resolution/types.d.ts.map +1 -1
  50. package/dist/cli/ai-context.js +35 -4
  51. package/dist/cli/analyze.d.ts +27 -0
  52. package/dist/cli/analyze.js +31 -1
  53. package/dist/cli/clean.js +19 -1
  54. package/dist/cli/group.js +73 -0
  55. package/dist/cli/index-repo.js +8 -1
  56. package/dist/cli/index.js +26 -1
  57. package/dist/cli/list.js +11 -1
  58. package/dist/cli/remove.d.ts +30 -0
  59. package/dist/cli/remove.js +99 -0
  60. package/dist/cli/setup.js +185 -57
  61. package/dist/cli/tool.d.ts +5 -0
  62. package/dist/cli/tool.js +42 -0
  63. package/dist/config/ignore-service.d.ts +9 -0
  64. package/dist/config/ignore-service.js +80 -13
  65. package/dist/core/embedding-mode.d.ts +30 -0
  66. package/dist/core/embedding-mode.js +30 -0
  67. package/dist/core/embeddings/ast-utils.js +22 -22
  68. package/dist/core/embeddings/chunker.js +30 -25
  69. package/dist/core/embeddings/embedding-pipeline.d.ts +6 -0
  70. package/dist/core/embeddings/embedding-pipeline.js +15 -6
  71. package/dist/core/embeddings/text-generator.d.ts +1 -1
  72. package/dist/core/embeddings/text-generator.js +33 -24
  73. package/dist/core/embeddings/types.d.ts +43 -1
  74. package/dist/core/embeddings/types.js +101 -29
  75. package/dist/core/git-staleness.d.ts +18 -0
  76. package/dist/core/git-staleness.js +108 -0
  77. package/dist/core/graph/graph.js +115 -20
  78. package/dist/core/graph/types.d.ts +12 -1
  79. package/dist/core/group/config-parser.d.ts +4 -0
  80. package/dist/core/group/config-parser.js +18 -1
  81. package/dist/core/group/cross-impact.d.ts +41 -0
  82. package/dist/core/group/cross-impact.js +441 -0
  83. package/dist/core/group/extractors/http-patterns/php.js +126 -18
  84. package/dist/core/group/group-path-utils.d.ts +17 -0
  85. package/dist/core/group/group-path-utils.js +40 -0
  86. package/dist/core/group/resolve-at-member.d.ts +10 -0
  87. package/dist/core/group/resolve-at-member.js +31 -0
  88. package/dist/core/group/service.d.ts +9 -0
  89. package/dist/core/group/service.js +259 -25
  90. package/dist/core/group/types.d.ts +30 -0
  91. package/dist/core/ingestion/ast-cache.d.ts +16 -1
  92. package/dist/core/ingestion/ast-cache.js +14 -2
  93. package/dist/core/ingestion/call-processor.js +9 -0
  94. package/dist/core/ingestion/emit-references.d.ts +88 -0
  95. package/dist/core/ingestion/emit-references.js +229 -0
  96. package/dist/core/ingestion/filesystem-walker.js +6 -4
  97. package/dist/core/ingestion/finalize-orchestrator.d.ts +63 -0
  98. package/dist/core/ingestion/finalize-orchestrator.js +139 -0
  99. package/dist/core/ingestion/framework-detection.js +6 -2
  100. package/dist/core/ingestion/import-processor.js +4 -0
  101. package/dist/core/ingestion/import-resolvers/python.js +9 -6
  102. package/dist/core/ingestion/import-target-adapter.d.ts +73 -0
  103. package/dist/core/ingestion/import-target-adapter.js +95 -0
  104. package/dist/core/ingestion/language-provider.d.ts +36 -33
  105. package/dist/core/ingestion/languages/csharp/accessor-unwrap.d.ts +21 -0
  106. package/dist/core/ingestion/languages/csharp/accessor-unwrap.js +56 -0
  107. package/dist/core/ingestion/languages/csharp/arity-metadata.d.ts +26 -0
  108. package/dist/core/ingestion/languages/csharp/arity-metadata.js +46 -0
  109. package/dist/core/ingestion/languages/csharp/arity.d.ts +23 -0
  110. package/dist/core/ingestion/languages/csharp/arity.js +37 -0
  111. package/dist/core/ingestion/languages/csharp/cache-stats.d.ts +15 -0
  112. package/dist/core/ingestion/languages/csharp/cache-stats.js +26 -0
  113. package/dist/core/ingestion/languages/csharp/captures.d.ts +19 -0
  114. package/dist/core/ingestion/languages/csharp/captures.js +249 -0
  115. package/dist/core/ingestion/languages/csharp/import-decomposer.d.ts +19 -0
  116. package/dist/core/ingestion/languages/csharp/import-decomposer.js +93 -0
  117. package/dist/core/ingestion/languages/csharp/import-target.d.ts +25 -0
  118. package/dist/core/ingestion/languages/csharp/import-target.js +123 -0
  119. package/dist/core/ingestion/languages/csharp/index.d.ts +82 -0
  120. package/dist/core/ingestion/languages/csharp/index.js +82 -0
  121. package/dist/core/ingestion/languages/csharp/interpret.d.ts +15 -0
  122. package/dist/core/ingestion/languages/csharp/interpret.js +132 -0
  123. package/dist/core/ingestion/languages/csharp/merge-bindings.d.ts +27 -0
  124. package/dist/core/ingestion/languages/csharp/merge-bindings.js +55 -0
  125. package/dist/core/ingestion/languages/csharp/namespace-siblings.d.ts +50 -0
  126. package/dist/core/ingestion/languages/csharp/namespace-siblings.js +374 -0
  127. package/dist/core/ingestion/languages/csharp/query.d.ts +35 -0
  128. package/dist/core/ingestion/languages/csharp/query.js +515 -0
  129. package/dist/core/ingestion/languages/csharp/receiver-binding.d.ts +31 -0
  130. package/dist/core/ingestion/languages/csharp/receiver-binding.js +135 -0
  131. package/dist/core/ingestion/languages/csharp/scope-resolver.d.ts +10 -0
  132. package/dist/core/ingestion/languages/csharp/scope-resolver.js +63 -0
  133. package/dist/core/ingestion/languages/csharp/simple-hooks.d.ts +53 -0
  134. package/dist/core/ingestion/languages/csharp/simple-hooks.js +76 -0
  135. package/dist/core/ingestion/languages/csharp.js +14 -0
  136. package/dist/core/ingestion/languages/python/arity-metadata.d.ts +24 -0
  137. package/dist/core/ingestion/languages/python/arity-metadata.js +45 -0
  138. package/dist/core/ingestion/languages/python/arity.d.ts +22 -0
  139. package/dist/core/ingestion/languages/python/arity.js +38 -0
  140. package/dist/core/ingestion/languages/python/cache-stats.d.ts +17 -0
  141. package/dist/core/ingestion/languages/python/cache-stats.js +28 -0
  142. package/dist/core/ingestion/languages/python/captures.d.ts +19 -0
  143. package/dist/core/ingestion/languages/python/captures.js +106 -0
  144. package/dist/core/ingestion/languages/python/import-decomposer.d.ts +15 -0
  145. package/dist/core/ingestion/languages/python/import-decomposer.js +112 -0
  146. package/dist/core/ingestion/languages/python/import-target.d.ts +21 -0
  147. package/dist/core/ingestion/languages/python/import-target.js +99 -0
  148. package/dist/core/ingestion/languages/python/index.d.ts +80 -0
  149. package/dist/core/ingestion/languages/python/index.js +80 -0
  150. package/dist/core/ingestion/languages/python/interpret.d.ts +15 -0
  151. package/dist/core/ingestion/languages/python/interpret.js +191 -0
  152. package/dist/core/ingestion/languages/python/merge-bindings.d.ts +16 -0
  153. package/dist/core/ingestion/languages/python/merge-bindings.js +44 -0
  154. package/dist/core/ingestion/languages/python/query.d.ts +9 -0
  155. package/dist/core/ingestion/languages/python/query.js +267 -0
  156. package/dist/core/ingestion/languages/python/receiver-binding.d.ts +21 -0
  157. package/dist/core/ingestion/languages/python/receiver-binding.js +116 -0
  158. package/dist/core/ingestion/languages/python/scope-resolver.d.ts +16 -0
  159. package/dist/core/ingestion/languages/python/scope-resolver.js +53 -0
  160. package/dist/core/ingestion/languages/python/simple-hooks.d.ts +23 -0
  161. package/dist/core/ingestion/languages/python/simple-hooks.js +35 -0
  162. package/dist/core/ingestion/languages/python.js +14 -0
  163. package/dist/core/ingestion/model/method-registry.d.ts +9 -0
  164. package/dist/core/ingestion/model/method-registry.js +4 -0
  165. package/dist/core/ingestion/model/scope-resolution-indexes.d.ts +59 -0
  166. package/dist/core/ingestion/model/scope-resolution-indexes.js +42 -0
  167. package/dist/core/ingestion/model/semantic-model.d.ts +64 -0
  168. package/dist/core/ingestion/model/semantic-model.js +55 -0
  169. package/dist/core/ingestion/mro-processor.js +38 -22
  170. package/dist/core/ingestion/parsing-processor.d.ts +18 -1
  171. package/dist/core/ingestion/parsing-processor.js +45 -11
  172. package/dist/core/ingestion/pipeline-phases/index.d.ts +1 -0
  173. package/dist/core/ingestion/pipeline-phases/index.js +1 -0
  174. package/dist/core/ingestion/pipeline-phases/parse-impl.d.ts +10 -0
  175. package/dist/core/ingestion/pipeline-phases/parse-impl.js +17 -2
  176. package/dist/core/ingestion/pipeline-phases/parse.d.ts +18 -0
  177. package/dist/core/ingestion/pipeline.js +2 -1
  178. package/dist/core/ingestion/registry-primary-flag.d.ts +86 -0
  179. package/dist/core/ingestion/registry-primary-flag.js +111 -0
  180. package/dist/core/ingestion/resolve-references.d.ts +63 -0
  181. package/dist/core/ingestion/resolve-references.js +175 -0
  182. package/dist/core/ingestion/scope-extractor-bridge.d.ts +32 -0
  183. package/dist/core/ingestion/scope-extractor-bridge.js +44 -0
  184. package/dist/core/ingestion/scope-extractor.d.ts +86 -0
  185. package/dist/core/ingestion/scope-extractor.js +758 -0
  186. package/dist/core/ingestion/scope-resolution/contract/scope-resolver.d.ts +372 -0
  187. package/dist/core/ingestion/scope-resolution/contract/scope-resolver.js +212 -0
  188. package/dist/core/ingestion/scope-resolution/graph-bridge/edges.d.ts +43 -0
  189. package/dist/core/ingestion/scope-resolution/graph-bridge/edges.js +79 -0
  190. package/dist/core/ingestion/scope-resolution/graph-bridge/ids.d.ts +57 -0
  191. package/dist/core/ingestion/scope-resolution/graph-bridge/ids.js +112 -0
  192. package/dist/core/ingestion/scope-resolution/graph-bridge/imports-to-edges.d.ts +17 -0
  193. package/dist/core/ingestion/scope-resolution/graph-bridge/imports-to-edges.js +46 -0
  194. package/dist/core/ingestion/scope-resolution/graph-bridge/method-dispatch.d.ts +19 -0
  195. package/dist/core/ingestion/scope-resolution/graph-bridge/method-dispatch.js +30 -0
  196. package/dist/core/ingestion/scope-resolution/graph-bridge/node-lookup.d.ts +37 -0
  197. package/dist/core/ingestion/scope-resolution/graph-bridge/node-lookup.js +113 -0
  198. package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.d.ts +38 -0
  199. package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.js +73 -0
  200. package/dist/core/ingestion/scope-resolution/passes/compound-receiver.d.ts +42 -0
  201. package/dist/core/ingestion/scope-resolution/passes/compound-receiver.js +198 -0
  202. package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.d.ts +27 -0
  203. package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.js +131 -0
  204. package/dist/core/ingestion/scope-resolution/passes/imported-return-types.d.ts +48 -0
  205. package/dist/core/ingestion/scope-resolution/passes/imported-return-types.js +130 -0
  206. package/dist/core/ingestion/scope-resolution/passes/mro.d.ts +42 -0
  207. package/dist/core/ingestion/scope-resolution/passes/mro.js +99 -0
  208. package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.d.ts +26 -0
  209. package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.js +61 -0
  210. package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.d.ts +46 -0
  211. package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +327 -0
  212. package/dist/core/ingestion/scope-resolution/pipeline/phase.d.ts +47 -0
  213. package/dist/core/ingestion/scope-resolution/pipeline/phase.js +130 -0
  214. package/dist/core/ingestion/scope-resolution/pipeline/reconcile-ownership.d.ts +68 -0
  215. package/dist/core/ingestion/scope-resolution/pipeline/reconcile-ownership.js +125 -0
  216. package/dist/core/ingestion/scope-resolution/pipeline/registry.d.ts +17 -0
  217. package/dist/core/ingestion/scope-resolution/pipeline/registry.js +21 -0
  218. package/dist/core/ingestion/scope-resolution/pipeline/run.d.ts +66 -0
  219. package/dist/core/ingestion/scope-resolution/pipeline/run.js +157 -0
  220. package/dist/core/ingestion/scope-resolution/scope/namespace-targets.d.ts +36 -0
  221. package/dist/core/ingestion/scope-resolution/scope/namespace-targets.js +52 -0
  222. package/dist/core/ingestion/scope-resolution/scope/walkers.d.ts +127 -0
  223. package/dist/core/ingestion/scope-resolution/scope/walkers.js +349 -0
  224. package/dist/core/ingestion/scope-resolution/workspace-index.d.ts +52 -0
  225. package/dist/core/ingestion/scope-resolution/workspace-index.js +61 -0
  226. package/dist/core/ingestion/shadow-harness.d.ts +113 -0
  227. package/dist/core/ingestion/shadow-harness.js +148 -0
  228. package/dist/core/ingestion/utils/ast-helpers.d.ts +19 -1
  229. package/dist/core/ingestion/utils/ast-helpers.js +70 -0
  230. package/dist/core/ingestion/utils/max-file-size.d.ts +20 -0
  231. package/dist/core/ingestion/utils/max-file-size.js +52 -0
  232. package/dist/core/ingestion/workers/parse-worker.d.ts +9 -0
  233. package/dist/core/ingestion/workers/parse-worker.js +57 -21
  234. package/dist/core/lbug/lbug-adapter.d.ts +22 -2
  235. package/dist/core/lbug/lbug-adapter.js +58 -14
  236. package/dist/core/lbug/pool-adapter.d.ts +17 -0
  237. package/dist/core/lbug/pool-adapter.js +24 -14
  238. package/dist/core/run-analyze.d.ts +32 -0
  239. package/dist/core/run-analyze.js +74 -19
  240. package/dist/core/search/bm25-index.d.ts +18 -0
  241. package/dist/core/search/bm25-index.js +125 -12
  242. package/dist/core/tree-sitter/parser-loader.js +6 -1
  243. package/dist/mcp/local/local-backend.d.ts +67 -3
  244. package/dist/mcp/local/local-backend.js +296 -34
  245. package/dist/mcp/resources.d.ts +31 -0
  246. package/dist/mcp/resources.js +100 -17
  247. package/dist/mcp/tools.d.ts +4 -1
  248. package/dist/mcp/tools.js +75 -54
  249. package/dist/server/api.js +6 -2
  250. package/dist/storage/git.d.ts +49 -0
  251. package/dist/storage/git.js +111 -0
  252. package/dist/storage/repo-manager.d.ts +246 -1
  253. package/dist/storage/repo-manager.js +391 -9
  254. package/package.json +7 -6
  255. package/scripts/bench-scope-resolution.ts +134 -0
  256. package/scripts/ci-list-migrated-languages.ts +24 -0
  257. package/skills/gitnexus-cli.md +1 -0
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Build a per-file `localName → targetFilePath` map over the file's
3
+ * module-scope namespace-kind import edges.
4
+ *
5
+ * Namespace imports (`import X`, `import X as Y`) bind a name that can
6
+ * appear as a receiver in member calls (`X.foo()`, `Y.foo()`). Named
7
+ * imports (`from X import foo`) bind `foo` directly and are a different
8
+ * resolution path.
9
+ *
10
+ * Why not consult `scope.bindings` directly? For namespace imports
11
+ * where the target module has no self-named def,
12
+ * `finalize-algorithm.ts:540` skips binding creation entirely, so
13
+ * `scope.bindings.get('X')` returns undefined. We iterate
14
+ * `indexes.imports` to recover those targets.
15
+ *
16
+ * Next-consumer contract: any language with namespace-style imports
17
+ * (TypeScript `import * as X`, Java static import, Ruby `require`)
18
+ * uses this directly. `ParsedImport.kind === 'namespace'` is the
19
+ * cross-language hook.
20
+ *
21
+ * Scope-chain concern (verified 2026-04-21): `pythonImportOwningScope`
22
+ * documents that function-local and class-body imports bind to the
23
+ * inner scope, which would make a module-only read incomplete. In
24
+ * practice `finalize-algorithm` places ALL of a file's ImportEdges
25
+ * onto `indexes.imports[moduleScope]` regardless of where the
26
+ * `import` statement appears — the integration fixtures
27
+ * `python-function-local-namespace-import` and
28
+ * `python-class-body-namespace-import` both emit correct CALLS edges
29
+ * with reason "namespace-receiver", demonstrating that the module-
30
+ * scope read is sufficient today. If finalize routing ever changes to
31
+ * honor the hook's per-scope contract, this function must walk the
32
+ * reference-site scope chain (mirror `findExportedDefByName`).
33
+ */
34
+ export function collectNamespaceTargets(parsed, scopes) {
35
+ const out = new Map();
36
+ const moduleEdges = scopes.imports.get(parsed.moduleScope);
37
+ if (moduleEdges === undefined)
38
+ return out;
39
+ const namespaceLocals = new Set();
40
+ for (const imp of parsed.parsedImports) {
41
+ if (imp.kind === 'namespace')
42
+ namespaceLocals.add(imp.localName);
43
+ }
44
+ for (const edge of moduleEdges) {
45
+ if (edge.targetFile === null)
46
+ continue;
47
+ if (!namespaceLocals.has(edge.localName))
48
+ continue;
49
+ out.set(edge.localName, edge.targetFile);
50
+ }
51
+ return out;
52
+ }
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Scope-chain lookup primitives shared across language providers.
3
+ *
4
+ * Four functions:
5
+ * - `findReceiverTypeBinding` — walk scope.typeBindings up the chain
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.
11
+ * - `findOwnedMember` — find a method/field owned by a class def
12
+ * across all parsed files by (ownerId, simpleName).
13
+ * - `findExportedDef` — find a file-level exported def (top-of-module
14
+ * class / function) by simpleName.
15
+ *
16
+ * Next-consumer contract: every OO or module-capable language hits the
17
+ * same pre-finalize / post-finalize binding split and the same
18
+ * "resolve member on owner with MRO" pattern. All four are reusable
19
+ * as-is for TypeScript, Java, Kotlin, Ruby, etc.
20
+ */
21
+ import type { ParsedFile, ScopeId, SymbolDefinition, TypeRef } from '../../../../_shared/index.js';
22
+ import type { ScopeResolutionIndexes } from '../../model/scope-resolution-indexes.js';
23
+ import type { SemanticModel } from '../../model/semantic-model.js';
24
+ import type { WorkspaceResolutionIndex } from '../workspace-index.js';
25
+ /**
26
+ * True when a def's `type` names a class-like declaration — every kind
27
+ * that collapses to `@scope.class` in the scope-extractor query contract.
28
+ *
29
+ * Semantics widened historically from `'Class' | 'Interface'` to cover
30
+ * C#-shape languages (struct, record, enum, trait). Languages that emit
31
+ * only `'Class'` are unaffected — the extra kinds never appear in their
32
+ * parsed output.
33
+ */
34
+ export declare function isClassLike(t: string): boolean;
35
+ /**
36
+ * Walk the scope chain from `startScope` looking for a typeBinding
37
+ * named `receiverName`. Returns the TypeRef or undefined if no binding
38
+ * exists in the chain.
39
+ */
40
+ export declare function findReceiverTypeBinding(startScope: ScopeId, receiverName: string, scopes: ScopeResolutionIndexes): TypeRef | undefined;
41
+ /**
42
+ * Look up a class-like binding by name in the given scope's chain.
43
+ *
44
+ * "Class-like" covers `Class | Interface | Struct | Record | Enum |
45
+ * Trait` via the shared `isClassLike` predicate — every kind that
46
+ * collapses to `@scope.class` in the scope-extractor query contract.
47
+ *
48
+ * Walks the scope chain upward and consults TWO sources at each step:
49
+ * 1. `scope.bindings` — populated during scope-extraction Pass 2 with
50
+ * local declarations (`origin: 'local'`).
51
+ * 2. `indexes.bindings` — populated by the cross-file finalize pass
52
+ * with import/namespace/wildcard/reexport origins.
53
+ *
54
+ * Without (2) we'd miss every cross-file class-receiver call.
55
+ */
56
+ export declare function findClassBindingInScope(startScope: ScopeId, receiverName: string, scopes: ScopeResolutionIndexes): SymbolDefinition | undefined;
57
+ /**
58
+ * Look up a callable (Function/Method/Constructor) by name in the
59
+ * 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.
62
+ *
63
+ * Mirrors `findClassBindingInScope` exactly; only the accepted
64
+ * def-type predicate differs.
65
+ */
66
+ export declare function findCallableBindingInScope(startScope: ScopeId, callableName: string, scopes: ScopeResolutionIndexes): SymbolDefinition | undefined;
67
+ /**
68
+ * Populate `ownerId` on every def structurally owned by a Class
69
+ * scope — methods (defs in Function scopes whose parent is Class)
70
+ * and class-body fields (defs directly in Class scopes).
71
+ *
72
+ * Generic OO ownership rule. Languages that want richer ownership
73
+ * (e.g. inner-class qualification) can compose with this as a base
74
+ * step.
75
+ *
76
+ * Mutates `parsed.localDefs` in place via type cast — `SymbolDefinition`
77
+ * is `readonly` for consumers but the extractor returns plain objects.
78
+ * Defs are shared by reference between `localDefs` and `Scope.ownedDefs`,
79
+ * so this single mutation is visible from both sides.
80
+ */
81
+ export declare function populateClassOwnedMembers(parsed: ParsedFile): void;
82
+ /**
83
+ * Walk a scope chain upward looking for the innermost enclosing
84
+ * Class scope and return that class's def. Used by per-language
85
+ * `super` receiver branches to discover the dispatch base.
86
+ */
87
+ export declare function findEnclosingClassDef(startScope: ScopeId, scopes: ScopeResolutionIndexes): SymbolDefinition | undefined;
88
+ /**
89
+ * Find a free-function def by simple name across all parsed files,
90
+ * preferring scope-chain-visible bindings (import + finalized scope
91
+ * bindings) before falling back to a workspace-wide simple-name scan.
92
+ *
93
+ * The fallback scan is intentionally loose so per-language compound
94
+ * resolvers can find a callable target even when the binding chain
95
+ * doesn't surface it (e.g. cross-package re-exports the finalize
96
+ * pass missed). Strictly-typed languages may want to disable the
97
+ * fallback by simply not calling this helper from their compound
98
+ * resolver.
99
+ */
100
+ export declare function findExportedDefByName(name: string, inScope: ScopeId, scopes: ScopeResolutionIndexes, index: WorkspaceResolutionIndex): SymbolDefinition | undefined;
101
+ /**
102
+ * Find a member of a class by simple name — delegates to
103
+ * `SemanticModel.methods` (methods / functions / constructors) with a
104
+ * fallback to `SemanticModel.fields` (properties / fields /
105
+ * variables). After `runScopeResolution`'s reconciliation pass
106
+ * populates both registries from `parsed.localDefs[i].ownerId`
107
+ * (post-`populateOwners`), this is the single authoritative view of
108
+ * class membership — no parallel scope-resolution index needed.
109
+ *
110
+ * Returns the first-seen overload for methods without arity or
111
+ * return-type narrowing. Callers that need arity-aware dispatch use
112
+ * `lookupMethodByOwner(owner, name, argCount)` directly.
113
+ */
114
+ export declare function findOwnedMember(ownerDefId: string, memberName: string, model: SemanticModel): SymbolDefinition | undefined;
115
+ /**
116
+ * Find a file-level def (top-of-module class / function / variable)
117
+ * by simple name — consults the target file's Module scope's
118
+ * finalized bindings. Only defs bound at module-scope with
119
+ * `origin === 'local'` qualify, matching the historical
120
+ * "module-export-visible" semantics. Class methods and class-body
121
+ * fields bind at their containing class scope and are naturally
122
+ * excluded.
123
+ *
124
+ * Reads from `WorkspaceResolutionIndex.moduleScopeByFile` (scope-tied
125
+ * lookup that doesn't live on `SemanticModel`).
126
+ */
127
+ export declare function findExportedDef(targetFile: string, memberName: string, index: WorkspaceResolutionIndex): SymbolDefinition | undefined;
@@ -0,0 +1,349 @@
1
+ /**
2
+ * Scope-chain lookup primitives shared across language providers.
3
+ *
4
+ * Four functions:
5
+ * - `findReceiverTypeBinding` — walk scope.typeBindings up the chain
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.
11
+ * - `findOwnedMember` — find a method/field owned by a class def
12
+ * across all parsed files by (ownerId, simpleName).
13
+ * - `findExportedDef` — find a file-level exported def (top-of-module
14
+ * class / function) by simpleName.
15
+ *
16
+ * Next-consumer contract: every OO or module-capable language hits the
17
+ * same pre-finalize / post-finalize binding split and the same
18
+ * "resolve member on owner with MRO" pattern. All four are reusable
19
+ * as-is for TypeScript, Java, Kotlin, Ruby, etc.
20
+ */
21
+ /**
22
+ * True when a def's `type` names a class-like declaration — every kind
23
+ * that collapses to `@scope.class` in the scope-extractor query contract.
24
+ *
25
+ * Semantics widened historically from `'Class' | 'Interface'` to cover
26
+ * C#-shape languages (struct, record, enum, trait). Languages that emit
27
+ * only `'Class'` are unaffected — the extra kinds never appear in their
28
+ * parsed output.
29
+ */
30
+ export function isClassLike(t) {
31
+ return (t === 'Class' ||
32
+ t === 'Interface' ||
33
+ t === 'Struct' ||
34
+ t === 'Record' ||
35
+ t === 'Enum' ||
36
+ t === 'Trait');
37
+ }
38
+ /**
39
+ * Walk the scope chain from `startScope` looking for a typeBinding
40
+ * named `receiverName`. Returns the TypeRef or undefined if no binding
41
+ * exists in the chain.
42
+ */
43
+ export function findReceiverTypeBinding(startScope, receiverName, scopes) {
44
+ let currentId = startScope;
45
+ const visited = new Set();
46
+ while (currentId !== null) {
47
+ if (visited.has(currentId))
48
+ return undefined;
49
+ visited.add(currentId);
50
+ const scope = scopes.scopeTree.getScope(currentId);
51
+ if (scope === undefined)
52
+ return undefined;
53
+ const typeRef = scope.typeBindings.get(receiverName);
54
+ if (typeRef !== undefined)
55
+ return typeRef;
56
+ currentId = scope.parent;
57
+ }
58
+ return undefined;
59
+ }
60
+ /**
61
+ * Look up a class-like binding by name in the given scope's chain.
62
+ *
63
+ * "Class-like" covers `Class | Interface | Struct | Record | Enum |
64
+ * Trait` via the shared `isClassLike` predicate — every kind that
65
+ * collapses to `@scope.class` in the scope-extractor query contract.
66
+ *
67
+ * Walks the scope chain upward and consults TWO sources at each step:
68
+ * 1. `scope.bindings` — populated during scope-extraction Pass 2 with
69
+ * local declarations (`origin: 'local'`).
70
+ * 2. `indexes.bindings` — populated by the cross-file finalize pass
71
+ * with import/namespace/wildcard/reexport origins.
72
+ *
73
+ * Without (2) we'd miss every cross-file class-receiver call.
74
+ */
75
+ export function findClassBindingInScope(startScope, receiverName, scopes) {
76
+ let currentId = startScope;
77
+ const visited = new Set();
78
+ while (currentId !== null) {
79
+ if (visited.has(currentId))
80
+ return undefined;
81
+ visited.add(currentId);
82
+ const scope = scopes.scopeTree.getScope(currentId);
83
+ if (scope === undefined)
84
+ return undefined;
85
+ const localBindings = scope.bindings.get(receiverName);
86
+ if (localBindings !== undefined) {
87
+ for (const b of localBindings) {
88
+ if (isClassLike(b.def.type))
89
+ return b.def;
90
+ }
91
+ }
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
+ }
99
+ }
100
+ currentId = scope.parent;
101
+ }
102
+ return undefined;
103
+ }
104
+ /**
105
+ * Look up a callable (Function/Method/Constructor) by name in the
106
+ * 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.
109
+ *
110
+ * Mirrors `findClassBindingInScope` exactly; only the accepted
111
+ * def-type predicate differs.
112
+ */
113
+ export function findCallableBindingInScope(startScope, callableName, scopes) {
114
+ let currentId = startScope;
115
+ const visited = new Set();
116
+ while (currentId !== null) {
117
+ if (visited.has(currentId))
118
+ return undefined;
119
+ visited.add(currentId);
120
+ const scope = scopes.scopeTree.getScope(currentId);
121
+ if (scope === undefined)
122
+ return undefined;
123
+ const localBindings = scope.bindings.get(callableName);
124
+ if (localBindings !== undefined) {
125
+ for (const b of localBindings) {
126
+ if (b.def.type === 'Function' || b.def.type === 'Method' || b.def.type === 'Constructor') {
127
+ return b.def;
128
+ }
129
+ }
130
+ }
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
+ }
138
+ }
139
+ }
140
+ currentId = scope.parent;
141
+ }
142
+ return undefined;
143
+ }
144
+ /**
145
+ * Populate `ownerId` on every def structurally owned by a Class
146
+ * scope — methods (defs in Function scopes whose parent is Class)
147
+ * and class-body fields (defs directly in Class scopes).
148
+ *
149
+ * Generic OO ownership rule. Languages that want richer ownership
150
+ * (e.g. inner-class qualification) can compose with this as a base
151
+ * step.
152
+ *
153
+ * Mutates `parsed.localDefs` in place via type cast — `SymbolDefinition`
154
+ * is `readonly` for consumers but the extractor returns plain objects.
155
+ * Defs are shared by reference between `localDefs` and `Scope.ownedDefs`,
156
+ * so this single mutation is visible from both sides.
157
+ */
158
+ export function populateClassOwnedMembers(parsed) {
159
+ const scopesById = new Map();
160
+ for (const scope of parsed.scopes)
161
+ scopesById.set(scope.id, scope);
162
+ // Promote a def's qualifiedName from `methodName` to `ClassName.methodName`
163
+ // when the def sits inside a class. Without this, two classes in the
164
+ // same file that share a method name collide at the graph-bridge lookup
165
+ // (`node-lookup.ts` keys by (filePath, qualifiedName) and falls back to
166
+ // simple name only). Python's scope query doesn't emit
167
+ // `@declaration.qualified_name` for nested methods, so the finalized
168
+ // defs arrive here with simple names — we stamp the qualifier while
169
+ // we're already walking class scopes for ownerId.
170
+ const qualify = (def, classDef) => {
171
+ const q = def.qualifiedName;
172
+ if (q === undefined || q.length === 0)
173
+ return;
174
+ if (q.includes('.'))
175
+ return; // already qualified (dotted)
176
+ const classQ = classDef.qualifiedName;
177
+ if (classQ === undefined || classQ.length === 0)
178
+ return;
179
+ def.qualifiedName = `${classQ}.${q}`;
180
+ };
181
+ // Depth invariant (verified empirically against Python scope-extractor
182
+ // 2026-04-21): a nested `def helper` declared inside a method body
183
+ // lives in its OWN Function scope whose parent is the method's Function
184
+ // scope (not the Class scope). That means the `parentScope.kind ===
185
+ // 'Class'` branch below only matches DIRECT class-scope children —
186
+ // method defs themselves — and never stamps arbitrary nested defs with
187
+ // `ownerId = classDef.nodeId`. If an adversarial reviewer raises this
188
+ // as a potential false-attribution bug, verify first with a scope dump
189
+ // on `class U: def save(self): def helper(): ...` — helper.ownerId will
190
+ // remain undefined. The theoretical concern is real only if the
191
+ // extractor ever stops creating scopes for inner defs.
192
+ for (const scope of parsed.scopes) {
193
+ // Methods: function scope whose parent is a Class scope. Owner is
194
+ // the parent's class-like def.
195
+ if (scope.parent !== null) {
196
+ const parentScope = scopesById.get(scope.parent);
197
+ if (parentScope !== undefined && parentScope.kind === 'Class') {
198
+ const classDef = parentScope.ownedDefs.find((d) => isClassLike(d.type));
199
+ if (classDef !== undefined) {
200
+ for (const def of scope.ownedDefs) {
201
+ def.ownerId = classDef.nodeId;
202
+ qualify(def, classDef);
203
+ }
204
+ }
205
+ }
206
+ }
207
+ // Class-body fields: defs directly owned by a Class scope (the
208
+ // class-like def itself excluded).
209
+ if (scope.kind === 'Class') {
210
+ const classDef = scope.ownedDefs.find((d) => isClassLike(d.type));
211
+ if (classDef !== undefined) {
212
+ for (const def of scope.ownedDefs) {
213
+ if (def === classDef)
214
+ continue;
215
+ def.ownerId = classDef.nodeId;
216
+ qualify(def, classDef);
217
+ }
218
+ }
219
+ }
220
+ }
221
+ }
222
+ /**
223
+ * Walk a scope chain upward looking for the innermost enclosing
224
+ * Class scope and return that class's def. Used by per-language
225
+ * `super` receiver branches to discover the dispatch base.
226
+ */
227
+ export function findEnclosingClassDef(startScope, scopes) {
228
+ let currentId = startScope;
229
+ const visited = new Set();
230
+ while (currentId !== null) {
231
+ if (visited.has(currentId))
232
+ return undefined;
233
+ visited.add(currentId);
234
+ const scope = scopes.scopeTree.getScope(currentId);
235
+ if (scope === undefined)
236
+ return undefined;
237
+ if (scope.kind === 'Class') {
238
+ const cd = scope.ownedDefs.find((d) => isClassLike(d.type));
239
+ if (cd !== undefined)
240
+ return cd;
241
+ }
242
+ currentId = scope.parent;
243
+ }
244
+ return undefined;
245
+ }
246
+ /**
247
+ * Find a free-function def by simple name across all parsed files,
248
+ * preferring scope-chain-visible bindings (import + finalized scope
249
+ * bindings) before falling back to a workspace-wide simple-name scan.
250
+ *
251
+ * The fallback scan is intentionally loose so per-language compound
252
+ * resolvers can find a callable target even when the binding chain
253
+ * doesn't surface it (e.g. cross-package re-exports the finalize
254
+ * pass missed). Strictly-typed languages may want to disable the
255
+ * fallback by simply not calling this helper from their compound
256
+ * resolver.
257
+ */
258
+ export function findExportedDefByName(name, inScope, scopes, index) {
259
+ let currentId = inScope;
260
+ const visited = new Set();
261
+ while (currentId !== null) {
262
+ if (visited.has(currentId))
263
+ break;
264
+ visited.add(currentId);
265
+ const scope = scopes.scopeTree.getScope(currentId);
266
+ if (scope === undefined)
267
+ break;
268
+ const local = scope.bindings.get(name);
269
+ if (local !== undefined) {
270
+ for (const b of local) {
271
+ if (b.def.type === 'Function' || b.def.type === 'Method')
272
+ return b.def;
273
+ }
274
+ }
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
+ }
281
+ }
282
+ currentId = scope.parent;
283
+ }
284
+ // Workspace-wide fallback: iterate every file's Module scope (via
285
+ // the scope-tied `moduleScopeByFile` lookup) and return the first
286
+ // locally-declared callable binding matching `name`. First-seen-
287
+ // by-file wins; bindings filtered to `origin === 'local'` and the
288
+ // callable types Function/Method/Constructor. We walk scopes here
289
+ // rather than consult `SemanticModel.symbols.lookupCallableByName`
290
+ // because the `origin === 'local'` module-export-visibility filter
291
+ // is a scope concept the raw symbol index doesn't express.
292
+ for (const [, moduleScope] of index.moduleScopeByFile) {
293
+ const refs = moduleScope.bindings.get(name);
294
+ if (refs === undefined)
295
+ continue;
296
+ for (const ref of refs) {
297
+ if (ref.origin !== 'local')
298
+ continue;
299
+ const t = ref.def.type;
300
+ if (t === 'Function' || t === 'Method' || t === 'Constructor')
301
+ return ref.def;
302
+ }
303
+ }
304
+ return undefined;
305
+ }
306
+ /**
307
+ * Find a member of a class by simple name — delegates to
308
+ * `SemanticModel.methods` (methods / functions / constructors) with a
309
+ * fallback to `SemanticModel.fields` (properties / fields /
310
+ * variables). After `runScopeResolution`'s reconciliation pass
311
+ * populates both registries from `parsed.localDefs[i].ownerId`
312
+ * (post-`populateOwners`), this is the single authoritative view of
313
+ * class membership — no parallel scope-resolution index needed.
314
+ *
315
+ * Returns the first-seen overload for methods without arity or
316
+ * return-type narrowing. Callers that need arity-aware dispatch use
317
+ * `lookupMethodByOwner(owner, name, argCount)` directly.
318
+ */
319
+ export function findOwnedMember(ownerDefId, memberName, model) {
320
+ const method = model.methods.lookupAllByOwner(ownerDefId, memberName)[0];
321
+ if (method !== undefined)
322
+ return method;
323
+ return model.fields.lookupFieldByOwner(ownerDefId, memberName);
324
+ }
325
+ /**
326
+ * Find a file-level def (top-of-module class / function / variable)
327
+ * by simple name — consults the target file's Module scope's
328
+ * finalized bindings. Only defs bound at module-scope with
329
+ * `origin === 'local'` qualify, matching the historical
330
+ * "module-export-visible" semantics. Class methods and class-body
331
+ * fields bind at their containing class scope and are naturally
332
+ * excluded.
333
+ *
334
+ * Reads from `WorkspaceResolutionIndex.moduleScopeByFile` (scope-tied
335
+ * lookup that doesn't live on `SemanticModel`).
336
+ */
337
+ export function findExportedDef(targetFile, memberName, index) {
338
+ const moduleScope = index.moduleScopeByFile.get(targetFile);
339
+ if (moduleScope === undefined)
340
+ return undefined;
341
+ const refs = moduleScope.bindings.get(memberName);
342
+ if (refs === undefined)
343
+ return undefined;
344
+ for (const ref of refs) {
345
+ if (ref.origin === 'local')
346
+ return ref.def;
347
+ }
348
+ return undefined;
349
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * `WorkspaceResolutionIndex` — scope-tied lookup tables built ONCE
3
+ * per resolution run, after `populateOwners` and before any
4
+ * resolution pass.
5
+ *
6
+ * ## Scope (what lives here vs. what lives in `SemanticModel`)
7
+ *
8
+ * This index carries only the lookups that return a `Scope` — things
9
+ * `SemanticModel` structurally cannot provide:
10
+ *
11
+ * - `classScopeByDefId` — class def `nodeId` → `Scope`. Needed so
12
+ * passes can read `scope.bindings`, `scope.typeBindings`, and
13
+ * `scope.ownedDefs`. SemanticModel's `TypeRegistry` carries class
14
+ * metadata but not the `Scope`.
15
+ * - `classScopeIdToDefId` — inverse of `classScopeByDefId`. O(1)
16
+ * reverse lookup (Scope.id → class def nodeId) for the implicit-
17
+ * `this` overload picker.
18
+ * - `moduleScopeByFile` — file path → `Scope` of the root `Module`.
19
+ * Used by cross-file return-type propagation, `findExportedDef`,
20
+ * and `findExportedDefByName`'s workspace-wide fallback.
21
+ * SymbolTable indexes symbols, not scopes.
22
+ *
23
+ * Symbol lookups live on `SemanticModel`:
24
+ * - Owner-keyed method lookup → `model.methods.lookupAllByOwner`
25
+ * (populated by the legacy parse phase via `symbolTable.add` AND
26
+ * by scope-resolution's reconciliation pass in `runScopeResolution`,
27
+ * which adds `parsed.localDefs[i].ownerId` entries missed by the
28
+ * legacy extractor for registry-primary languages).
29
+ * - Name-keyed callable lookup → `model.methods.lookupMethodByName`
30
+ * and `model.symbols.lookupCallableByName`.
31
+ * - File-indexed symbol lookup → `model.symbols.lookupExactAll`.
32
+ *
33
+ * This split preserves the single-source-of-truth invariant
34
+ * documented in `ScopeResolver`'s contract file: symbol-indexed
35
+ * lookups live on `SemanticModel` for the whole codebase; only
36
+ * scope-shaped lookups (which `SemanticModel` doesn't carry) live
37
+ * here.
38
+ *
39
+ * Build cost is O(totalScopes). Read-only after construction.
40
+ */
41
+ import type { ParsedFile, Scope, ScopeId } from '../../../_shared/index.js';
42
+ export interface WorkspaceResolutionIndex {
43
+ /** Class def `nodeId` → that class's `Scope`. */
44
+ readonly classScopeByDefId: ReadonlyMap<string, Scope>;
45
+ /** Inverse of `classScopeByDefId`: class `Scope.id` → class def `nodeId`.
46
+ * Built in the same pass; used by the implicit-`this` overload picker
47
+ * in `free-call-fallback.ts` to skip an O(C) reverse scan. */
48
+ readonly classScopeIdToDefId: ReadonlyMap<ScopeId, string>;
49
+ /** Module scope by file path. */
50
+ readonly moduleScopeByFile: ReadonlyMap<string, Scope>;
51
+ }
52
+ export declare function buildWorkspaceResolutionIndex(parsedFiles: readonly ParsedFile[]): WorkspaceResolutionIndex;
@@ -0,0 +1,61 @@
1
+ /**
2
+ * `WorkspaceResolutionIndex` — scope-tied lookup tables built ONCE
3
+ * per resolution run, after `populateOwners` and before any
4
+ * resolution pass.
5
+ *
6
+ * ## Scope (what lives here vs. what lives in `SemanticModel`)
7
+ *
8
+ * This index carries only the lookups that return a `Scope` — things
9
+ * `SemanticModel` structurally cannot provide:
10
+ *
11
+ * - `classScopeByDefId` — class def `nodeId` → `Scope`. Needed so
12
+ * passes can read `scope.bindings`, `scope.typeBindings`, and
13
+ * `scope.ownedDefs`. SemanticModel's `TypeRegistry` carries class
14
+ * metadata but not the `Scope`.
15
+ * - `classScopeIdToDefId` — inverse of `classScopeByDefId`. O(1)
16
+ * reverse lookup (Scope.id → class def nodeId) for the implicit-
17
+ * `this` overload picker.
18
+ * - `moduleScopeByFile` — file path → `Scope` of the root `Module`.
19
+ * Used by cross-file return-type propagation, `findExportedDef`,
20
+ * and `findExportedDefByName`'s workspace-wide fallback.
21
+ * SymbolTable indexes symbols, not scopes.
22
+ *
23
+ * Symbol lookups live on `SemanticModel`:
24
+ * - Owner-keyed method lookup → `model.methods.lookupAllByOwner`
25
+ * (populated by the legacy parse phase via `symbolTable.add` AND
26
+ * by scope-resolution's reconciliation pass in `runScopeResolution`,
27
+ * which adds `parsed.localDefs[i].ownerId` entries missed by the
28
+ * legacy extractor for registry-primary languages).
29
+ * - Name-keyed callable lookup → `model.methods.lookupMethodByName`
30
+ * and `model.symbols.lookupCallableByName`.
31
+ * - File-indexed symbol lookup → `model.symbols.lookupExactAll`.
32
+ *
33
+ * This split preserves the single-source-of-truth invariant
34
+ * documented in `ScopeResolver`'s contract file: symbol-indexed
35
+ * lookups live on `SemanticModel` for the whole codebase; only
36
+ * scope-shaped lookups (which `SemanticModel` doesn't carry) live
37
+ * here.
38
+ *
39
+ * Build cost is O(totalScopes). Read-only after construction.
40
+ */
41
+ import { isClassLike } from './scope/walkers.js';
42
+ export function buildWorkspaceResolutionIndex(parsedFiles) {
43
+ const classScopeByDefId = new Map();
44
+ const classScopeIdToDefId = new Map();
45
+ const moduleScopeByFile = new Map();
46
+ for (const parsed of parsedFiles) {
47
+ const moduleScope = parsed.scopes.find((s) => s.kind === 'Module');
48
+ if (moduleScope !== undefined)
49
+ moduleScopeByFile.set(parsed.filePath, moduleScope);
50
+ for (const scope of parsed.scopes) {
51
+ if (scope.kind !== 'Class')
52
+ continue;
53
+ const cd = scope.ownedDefs.find((d) => isClassLike(d.type));
54
+ if (cd !== undefined) {
55
+ classScopeByDefId.set(cd.nodeId, scope);
56
+ classScopeIdToDefId.set(scope.id, cd.nodeId);
57
+ }
58
+ }
59
+ }
60
+ return { classScopeByDefId, classScopeIdToDefId, moduleScopeByFile };
61
+ }