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,229 @@
1
+ /**
2
+ * Phase 5 of the RFC #909 ingestion lifecycle: drain `ReferenceIndex`
3
+ * into the knowledge graph as labeled edges with `confidence` and
4
+ * `evidence` properties (Ring 2 PKG #925).
5
+ *
6
+ * The resolution phase (future PR) writes `Reference` records into
7
+ * `model.scopes.referenceSites`-derived `ReferenceIndex`; this module
8
+ * materializes those records as `GraphRelationship`s via
9
+ * `graph.addRelationship`. Every emitted edge carries:
10
+ *
11
+ * - `type`: one of `'CALLS' | 'ACCESSES' | 'INHERITS' | 'USES'`
12
+ * (mapped from `Reference.kind` — `'read'` and `'write'` both route
13
+ * to `ACCESSES`; `'type-reference'` and `'import-use'` route to
14
+ * `USES`; `'call'` stays `CALLS`; `'inherits'` stays `INHERITS`).
15
+ * - `confidence`: the pre-computed confidence from the Reference record.
16
+ * - `reason`: human-readable summary (`"scope-resolution: call | confidence 0.75"`).
17
+ * - `evidence`: the full `ResolutionEvidence[]` trace — additive graph
18
+ * property (see `GraphRelationship.evidence` in gitnexus-shared),
19
+ * so queries that don't know about it are unaffected.
20
+ * - `step`: carries the reference's access-kind discriminant when
21
+ * available (`1` for read, `2` for write) so `ACCESSES` edges retain
22
+ * the read/write distinction without forcing a new edge type.
23
+ *
24
+ * ## Optional scope-tree flush
25
+ *
26
+ * When `INGESTION_EMIT_SCOPES=1` is set, this module also emits:
27
+ *
28
+ * - `Scope` nodes for every `Scope` in the tree
29
+ * - `CONTAINS` edges from parent scope to child scope
30
+ * - `DEFINES` edges from scope to its `ownedDefs` members
31
+ * - `IMPORTS` edges from scope to `targetModuleScope` of each finalized
32
+ * `ImportEdge` that carries one
33
+ *
34
+ * Off by default — existing queries that don't know about `Scope` nodes
35
+ * continue to work, and the storage cost is opt-in.
36
+ *
37
+ * ## Source-of-truth: the caller def for a reference
38
+ *
39
+ * A `Reference` says "some code inside `fromScope` references `toDef`".
40
+ * The graph wants `(callerNodeId, calleeNodeId)`. We resolve the caller
41
+ * by walking up the scope tree from `fromScope` until we find a scope
42
+ * whose `ownedDefs` contains a Function-like def. If no such ancestor
43
+ * exists, the edge is attributed to the first def owned by the innermost
44
+ * ancestor scope, and if THAT produces nothing either the edge is
45
+ * skipped (with a count returned in `EmitStats.skippedNoCaller`).
46
+ */
47
+ /**
48
+ * Drain `referenceIndex.bySourceScope` into graph edges.
49
+ *
50
+ * The scope-tree flush is controlled separately by
51
+ * `INGESTION_EMIT_SCOPES` — callers can run `emitReferencesToGraph`
52
+ * without scope-node emission or layer the two calls as needed.
53
+ */
54
+ export function emitReferencesToGraph(input) {
55
+ const { graph, scopes, referenceIndex } = input;
56
+ const sourceLabel = input.sourceLabel ?? 'scope-resolution';
57
+ let edgesEmitted = 0;
58
+ let skippedNoCaller = 0;
59
+ let skippedMissingTarget = 0;
60
+ for (const [fromScope, refs] of referenceIndex.bySourceScope) {
61
+ for (const ref of refs) {
62
+ const targetDef = scopes.defs.get(ref.toDef);
63
+ if (targetDef === undefined) {
64
+ skippedMissingTarget++;
65
+ continue;
66
+ }
67
+ const callerId = resolveCallerNodeId(fromScope, scopes);
68
+ if (callerId === undefined) {
69
+ skippedNoCaller++;
70
+ continue;
71
+ }
72
+ graph.addRelationship(buildRelationship(ref, callerId, targetDef, sourceLabel));
73
+ edgesEmitted++;
74
+ }
75
+ }
76
+ const scopeStats = isScopeEmissionEnabled()
77
+ ? emitScopeGraph({ graph, scopes })
78
+ : { scopeNodesEmitted: 0, scopeEdgesEmitted: 0 };
79
+ return { edgesEmitted, skippedNoCaller, skippedMissingTarget, ...scopeStats };
80
+ }
81
+ /**
82
+ * Emit `Scope` nodes + `CONTAINS`/`DEFINES`/`IMPORTS` edges representing
83
+ * the lexical scope tree itself. Skipped unless `INGESTION_EMIT_SCOPES=1`
84
+ * at the public entry point; exported here for tests that want to
85
+ * exercise the path directly.
86
+ */
87
+ export function emitScopeGraph(input) {
88
+ const { graph, scopes } = input;
89
+ let scopeNodesEmitted = 0;
90
+ let scopeEdgesEmitted = 0;
91
+ for (const scope of scopes.scopeTree.byId.values()) {
92
+ graph.addNode({
93
+ id: scope.id,
94
+ label: 'CodeElement', // the generic bucket for non-symbol graph nodes
95
+ properties: {
96
+ name: scope.kind,
97
+ filePath: scope.filePath,
98
+ startLine: scope.range.startLine,
99
+ endLine: scope.range.endLine,
100
+ description: `Scope: ${scope.kind}`,
101
+ },
102
+ });
103
+ scopeNodesEmitted++;
104
+ if (scope.parent !== null) {
105
+ graph.addRelationship({
106
+ id: `rel:contains:${scope.parent}->${scope.id}`,
107
+ sourceId: scope.parent,
108
+ targetId: scope.id,
109
+ type: 'CONTAINS',
110
+ confidence: 1,
111
+ reason: 'scope-tree parent/child',
112
+ });
113
+ scopeEdgesEmitted++;
114
+ }
115
+ for (const def of scope.ownedDefs) {
116
+ graph.addRelationship({
117
+ id: `rel:defines:${scope.id}->${def.nodeId}`,
118
+ sourceId: scope.id,
119
+ targetId: def.nodeId,
120
+ type: 'DEFINES',
121
+ confidence: 1,
122
+ reason: 'scope.ownedDefs',
123
+ });
124
+ scopeEdgesEmitted++;
125
+ }
126
+ }
127
+ for (const [scopeId, edges] of scopes.imports) {
128
+ for (const edge of edges) {
129
+ if (edge.targetModuleScope === undefined)
130
+ continue;
131
+ graph.addRelationship({
132
+ id: `rel:imports:${scopeId}->${edge.targetModuleScope}:${edge.localName}`,
133
+ sourceId: scopeId,
134
+ targetId: edge.targetModuleScope,
135
+ type: 'IMPORTS',
136
+ confidence: edge.linkStatus === 'unresolved' ? 0.5 : 1,
137
+ reason: `import ${edge.kind} ${edge.localName}`,
138
+ });
139
+ scopeEdgesEmitted++;
140
+ }
141
+ }
142
+ return { scopeNodesEmitted, scopeEdgesEmitted };
143
+ }
144
+ // ─── Internal ───────────────────────────────────────────────────────────────
145
+ /** Accepted truthy values for `INGESTION_EMIT_SCOPES`. */
146
+ const TRUTHY = new Set(['true', '1', 'yes']);
147
+ function isScopeEmissionEnabled() {
148
+ const raw = process.env['INGESTION_EMIT_SCOPES'];
149
+ if (raw === undefined)
150
+ return false;
151
+ return TRUTHY.has(raw.trim().toLowerCase());
152
+ }
153
+ /**
154
+ * Walk up from `startScope` looking for the first ancestor scope whose
155
+ * `ownedDefs` contains a Function-like def (Function / Method /
156
+ * Constructor). Fall back to the innermost ancestor's first `ownedDef`
157
+ * if none is found; return `undefined` if all ancestors have no defs.
158
+ */
159
+ function resolveCallerNodeId(startScope, scopes) {
160
+ const tree = scopes.scopeTree;
161
+ let current = startScope;
162
+ const visited = new Set();
163
+ let firstOwnedFallback;
164
+ while (current !== null) {
165
+ if (visited.has(current))
166
+ break;
167
+ visited.add(current);
168
+ const scope = tree.getScope(current);
169
+ if (scope === undefined)
170
+ break;
171
+ // Prefer a Function-like owner.
172
+ const fnDef = scope.ownedDefs.find((d) => isFunctionLike(d.type));
173
+ if (fnDef !== undefined)
174
+ return fnDef.nodeId;
175
+ // Stash the first owned def we see as a conservative fallback.
176
+ if (firstOwnedFallback === undefined && scope.ownedDefs.length > 0) {
177
+ firstOwnedFallback = scope.ownedDefs[0].nodeId;
178
+ }
179
+ current = scope.parent;
180
+ }
181
+ return firstOwnedFallback;
182
+ }
183
+ function isFunctionLike(type) {
184
+ return type === 'Function' || type === 'Method' || type === 'Constructor';
185
+ }
186
+ function buildRelationship(ref, callerId, targetDef, sourceLabel) {
187
+ const type = mapKindToType(ref.kind);
188
+ const reason = `${sourceLabel}: ${ref.kind} | confidence ${ref.confidence.toFixed(3)}`;
189
+ // `step` encodes read/write discriminator for ACCESSES edges (1=read, 2=write).
190
+ // Other kinds omit `step`.
191
+ const step = ref.kind === 'read' ? 1 : ref.kind === 'write' ? 2 : undefined;
192
+ return {
193
+ id: `rel:${type}:${callerId}->${targetDef.nodeId}:${ref.atRange.startLine}:${ref.atRange.startCol}`,
194
+ sourceId: callerId,
195
+ targetId: targetDef.nodeId,
196
+ type,
197
+ confidence: ref.confidence,
198
+ reason,
199
+ evidence: ref.evidence.map(serializeEvidence),
200
+ ...(step !== undefined ? { step } : {}),
201
+ };
202
+ }
203
+ /**
204
+ * Map a `Reference.kind` to an existing `RelationshipType`. Read/write
205
+ * both fold into `ACCESSES`; `type-reference` + `import-use` both fold
206
+ * into `USES`. This keeps the graph schema additive — no new
207
+ * RelationshipType values are introduced by this module.
208
+ */
209
+ function mapKindToType(kind) {
210
+ switch (kind) {
211
+ case 'call':
212
+ return 'CALLS';
213
+ case 'read':
214
+ case 'write':
215
+ return 'ACCESSES';
216
+ case 'inherits':
217
+ return 'INHERITS';
218
+ case 'type-reference':
219
+ case 'import-use':
220
+ return 'USES';
221
+ }
222
+ }
223
+ function serializeEvidence(e) {
224
+ return {
225
+ kind: e.kind,
226
+ weight: e.weight,
227
+ ...(e.note !== undefined ? { note: e.note } : {}),
228
+ };
229
+ }
@@ -1,17 +1,17 @@
1
1
  import { isVerboseIngestionEnabled } from './utils/verbose.js';
2
+ import { DEFAULT_MAX_FILE_SIZE_BYTES, getMaxFileSizeBytes } from './utils/max-file-size.js';
2
3
  import fs from 'fs/promises';
3
4
  import path from 'path';
4
5
  import { glob } from 'glob';
5
6
  import { createIgnoreFilter } from '../../config/ignore-service.js';
6
7
  const READ_CONCURRENCY = 32;
7
- /** Skip files larger than 512KB — they're usually generated/vendored and crash tree-sitter */
8
- const MAX_FILE_SIZE = 512 * 1024;
9
8
  /**
10
9
  * Phase 1: Scan repository — stat files to get paths + sizes, no content loaded.
11
10
  * Memory: ~10MB for 100K files vs ~1GB+ with content.
12
11
  */
13
12
  export const walkRepositoryPaths = async (repoPath, onProgress) => {
14
13
  const ignoreFilter = await createIgnoreFilter(repoPath);
14
+ const maxFileSizeBytes = getMaxFileSizeBytes();
15
15
  const filtered = await glob('**/*', {
16
16
  cwd: repoPath,
17
17
  nodir: true,
@@ -27,7 +27,7 @@ export const walkRepositoryPaths = async (repoPath, onProgress) => {
27
27
  const results = await Promise.allSettled(batch.map(async (relativePath) => {
28
28
  const fullPath = path.join(repoPath, relativePath);
29
29
  const stat = await fs.stat(fullPath);
30
- if (stat.size > MAX_FILE_SIZE) {
30
+ if (stat.size > maxFileSizeBytes) {
31
31
  skippedLarge++;
32
32
  skippedLargePaths.push(relativePath.replace(/\\/g, '/'));
33
33
  return null;
@@ -46,7 +46,9 @@ export const walkRepositoryPaths = async (repoPath, onProgress) => {
46
46
  }
47
47
  }
48
48
  if (skippedLarge > 0) {
49
- console.warn(` Skipped ${skippedLarge} large files (>${MAX_FILE_SIZE / 1024}KB, likely generated/vendored)`);
49
+ const isDefault = maxFileSizeBytes === DEFAULT_MAX_FILE_SIZE_BYTES;
50
+ const suffix = isDefault ? ', likely generated/vendored' : '';
51
+ console.warn(` Skipped ${skippedLarge} large files (>${maxFileSizeBytes / 1024}KB${suffix})`);
50
52
  if (isVerboseIngestionEnabled()) {
51
53
  for (const p of skippedLargePaths) {
52
54
  console.warn(` - ${p}`);
@@ -0,0 +1,63 @@
1
+ /**
2
+ * `finalizeScopeModel` — turn a workspace's `ParsedFile[]` into a
3
+ * materialized `ScopeResolutionIndexes` (RFC §3.2 Phase 2; Ring 2 PKG #921).
4
+ *
5
+ * Thin integration glue, per issue #884's boundary: all algorithmic logic
6
+ * lives in `gitnexus-shared` (finalize algorithm #915, the four per-file
7
+ * indexes #913, the method-dispatch materialization #914, the scope tree
8
+ * #912). This file does three things only:
9
+ *
10
+ * 1. Map `ParsedFile[]` → `FinalizeInput` and call shared `finalize()`.
11
+ * 2. Build the four workspace-wide indexes from the union of per-file
12
+ * defs/scopes/modules/qualified-names.
13
+ * 3. Bundle the results into `ScopeResolutionIndexes` for
14
+ * `MutableSemanticModel.attachScopeIndexes(...)`.
15
+ *
16
+ * ## What this module is NOT responsible for
17
+ *
18
+ * - Invoking tree-sitter or running AST walks. That's the extractor (#919).
19
+ * - Per-language import-target resolution. Hooks are plumbed through
20
+ * but default to "unresolved" when no provider supplies them — the
21
+ * real adapters land with #922.
22
+ * - Populating `ReferenceIndex`. That's the resolution phase (#925).
23
+ * - Deciding which language uses registry-primary lookup. That's the
24
+ * flag reader (#924).
25
+ *
26
+ * ## Empty-input behavior
27
+ *
28
+ * When `parsedFiles` is empty (the common case today — no language has
29
+ * migrated yet), the orchestrator produces a valid but empty bundle: all
30
+ * indexes are zero-sized, the scope tree is empty, and
31
+ * `finalize.stats.totalFiles === 0`. This lets downstream consumers
32
+ * safely consult `model.scopes` without branching on presence.
33
+ */
34
+ import type { FinalizeHooks, ParsedFile, WorkspaceIndex } from '../../_shared/index.js';
35
+ import type { ScopeResolutionIndexes } from './model/scope-resolution-indexes.js';
36
+ /**
37
+ * Options forwarded to the orchestrator. All fields optional so callers
38
+ * that don't yet have per-language hooks (today) get sensible defaults;
39
+ * #922 will populate `hooks.resolveImportTarget` + friends per language.
40
+ */
41
+ export interface FinalizeOrchestratorOptions {
42
+ /**
43
+ * Hooks forwarded to shared `finalize()`. Any omitted field gets a
44
+ * no-op default: unresolved targets, empty wildcard expansion, append
45
+ * merge for bindings.
46
+ */
47
+ readonly hooks?: Partial<FinalizeHooks>;
48
+ /**
49
+ * Opaque workspace context forwarded to hooks. `undefined` today; Ring
50
+ * 2 PKG #922 populates this with a real cross-file index for the
51
+ * per-language resolvers.
52
+ */
53
+ readonly workspaceIndex?: WorkspaceIndex;
54
+ }
55
+ /**
56
+ * Produce a fully materialized `ScopeResolutionIndexes` from the
57
+ * workspace's per-file artifacts.
58
+ *
59
+ * Pure function (given pure hooks). No I/O, no globals consulted. The
60
+ * pipeline calls this once per ingestion run and hands the result to
61
+ * `MutableSemanticModel.attachScopeIndexes`.
62
+ */
63
+ export declare function finalizeScopeModel(parsedFiles: readonly ParsedFile[], options?: FinalizeOrchestratorOptions): ScopeResolutionIndexes;
@@ -0,0 +1,139 @@
1
+ /**
2
+ * `finalizeScopeModel` — turn a workspace's `ParsedFile[]` into a
3
+ * materialized `ScopeResolutionIndexes` (RFC §3.2 Phase 2; Ring 2 PKG #921).
4
+ *
5
+ * Thin integration glue, per issue #884's boundary: all algorithmic logic
6
+ * lives in `gitnexus-shared` (finalize algorithm #915, the four per-file
7
+ * indexes #913, the method-dispatch materialization #914, the scope tree
8
+ * #912). This file does three things only:
9
+ *
10
+ * 1. Map `ParsedFile[]` → `FinalizeInput` and call shared `finalize()`.
11
+ * 2. Build the four workspace-wide indexes from the union of per-file
12
+ * defs/scopes/modules/qualified-names.
13
+ * 3. Bundle the results into `ScopeResolutionIndexes` for
14
+ * `MutableSemanticModel.attachScopeIndexes(...)`.
15
+ *
16
+ * ## What this module is NOT responsible for
17
+ *
18
+ * - Invoking tree-sitter or running AST walks. That's the extractor (#919).
19
+ * - Per-language import-target resolution. Hooks are plumbed through
20
+ * but default to "unresolved" when no provider supplies them — the
21
+ * real adapters land with #922.
22
+ * - Populating `ReferenceIndex`. That's the resolution phase (#925).
23
+ * - Deciding which language uses registry-primary lookup. That's the
24
+ * flag reader (#924).
25
+ *
26
+ * ## Empty-input behavior
27
+ *
28
+ * When `parsedFiles` is empty (the common case today — no language has
29
+ * migrated yet), the orchestrator produces a valid but empty bundle: all
30
+ * indexes are zero-sized, the scope tree is empty, and
31
+ * `finalize.stats.totalFiles === 0`. This lets downstream consumers
32
+ * safely consult `model.scopes` without branching on presence.
33
+ */
34
+ import { buildDefIndex, buildMethodDispatchIndex, buildModuleScopeIndex, buildQualifiedNameIndex, buildScopeTree, finalize, } from '../../_shared/index.js';
35
+ /**
36
+ * Produce a fully materialized `ScopeResolutionIndexes` from the
37
+ * workspace's per-file artifacts.
38
+ *
39
+ * Pure function (given pure hooks). No I/O, no globals consulted. The
40
+ * pipeline calls this once per ingestion run and hands the result to
41
+ * `MutableSemanticModel.attachScopeIndexes`.
42
+ */
43
+ export function finalizeScopeModel(parsedFiles, options = {}) {
44
+ const hooks = withDefaultHooks(options.hooks ?? {});
45
+ const workspaceIndex = options.workspaceIndex ?? undefined;
46
+ // ── Step 1: Shared finalize — runs SCC-aware cross-file link + binding
47
+ // materialization. Returns linked imports + merged bindings per module
48
+ // scope + SCC condensation + stats.
49
+ const finalizeInput = {
50
+ files: parsedFiles.map(toFinalizeFile),
51
+ workspaceIndex,
52
+ };
53
+ const finalizeOut = finalize(finalizeInput, hooks);
54
+ // ── Step 2: Workspace-wide indexes built from the per-file unions.
55
+ // These are pure aggregations — no algorithm beyond what the builders
56
+ // in gitnexus-shared already encapsulate (first-write-wins, qname
57
+ // collision buckets, etc.).
58
+ const allScopes = [];
59
+ const allDefs = [];
60
+ const moduleEntries = [];
61
+ const allReferenceSites = [];
62
+ for (const file of parsedFiles) {
63
+ for (const s of file.scopes)
64
+ allScopes.push(s);
65
+ for (const d of file.localDefs)
66
+ allDefs.push(d);
67
+ moduleEntries.push({ filePath: file.filePath, moduleScopeId: file.moduleScope });
68
+ }
69
+ // References kept out of the loop above to centralize list-init.
70
+ allReferenceSites.push(...collectReferenceSites(parsedFiles));
71
+ const scopeTree = buildScopeTree(allScopes);
72
+ const defs = buildDefIndex(allDefs);
73
+ const qualifiedNames = buildQualifiedNameIndex(allDefs);
74
+ const moduleScopes = buildModuleScopeIndex(moduleEntries);
75
+ // ── Step 3: MethodDispatchIndex. Today we lack per-language MRO
76
+ // strategies wired into this orchestrator (that belongs with the
77
+ // HeritageMap bridge, a separate piece of work). Ship an EMPTY index
78
+ // so the bundle shape is consistent; the callbacks return `[]` for
79
+ // every owner and `implementsOf` returns `[]`. Populating this
80
+ // properly is tracked alongside the per-language provider hooks.
81
+ const methodDispatch = buildMethodDispatchIndex({
82
+ owners: [], // empty → no MRO entries; `mroFor(x)` returns the frozen empty array
83
+ computeMro: () => [],
84
+ implementsOf: () => [],
85
+ });
86
+ return {
87
+ scopeTree,
88
+ defs,
89
+ qualifiedNames,
90
+ moduleScopes,
91
+ methodDispatch,
92
+ imports: finalizeOut.imports,
93
+ bindings: finalizeOut.bindings,
94
+ referenceSites: Object.freeze([...allReferenceSites]),
95
+ sccs: finalizeOut.sccs,
96
+ stats: finalizeOut.stats,
97
+ };
98
+ }
99
+ // ─── Internal ───────────────────────────────────────────────────────────────
100
+ /** Shape-reduce a `ParsedFile` to the narrower `FinalizeFile` the shared
101
+ * algorithm reads. The subset is stable — `FinalizeFile` is a proper
102
+ * subset of `ParsedFile`. */
103
+ function toFinalizeFile(file) {
104
+ return {
105
+ filePath: file.filePath,
106
+ moduleScope: file.moduleScope,
107
+ parsedImports: file.parsedImports,
108
+ localDefs: file.localDefs,
109
+ };
110
+ }
111
+ /** Flatten every file's reference sites into one list. Order reflects
112
+ * input-file order, then capture order inside each file. Deterministic. */
113
+ function collectReferenceSites(parsedFiles) {
114
+ const out = [];
115
+ for (const file of parsedFiles) {
116
+ for (const site of file.referenceSites)
117
+ out.push(site);
118
+ }
119
+ return out;
120
+ }
121
+ /**
122
+ * Fill in no-op defaults for any omitted hook. Keeps `finalize()`
123
+ * behavior well-defined for the zero-provider case today:
124
+ *
125
+ * - `resolveImportTarget: () => null` — every import edge ends up
126
+ * `linkStatus: 'unresolved'` (or dynamic-unresolved pass-through).
127
+ * - `expandsWildcardTo: () => []` — wildcards don't materialize.
128
+ * - `mergeBindings: (existing, incoming) => [...existing, ...incoming]`
129
+ * — append without precedence; providers override to implement local-
130
+ * shadows-import and similar rules.
131
+ */
132
+ function withDefaultHooks(partial) {
133
+ return {
134
+ resolveImportTarget: partial.resolveImportTarget ?? (() => null),
135
+ expandsWildcardTo: partial.expandsWildcardTo ?? (() => []),
136
+ mergeBindings: partial.mergeBindings ??
137
+ ((existing, incoming) => [...existing, ...incoming]),
138
+ };
139
+ }
@@ -21,10 +21,14 @@ import { SupportedLanguages } from '../../_shared/index.js';
21
21
  */
22
22
  export function detectFrameworkFromPath(filePath) {
23
23
  // Normalize path separators and ensure leading slash for consistent matching
24
- let p = filePath.toLowerCase().replace(/\\/g, '/');
24
+ const originalPath = filePath.replace(/\\/g, '/');
25
+ let p = originalPath.toLowerCase();
25
26
  if (!p.startsWith('/')) {
26
27
  p = '/' + p; // Add leading slash so patterns like '/app/' match 'app/...'
27
28
  }
29
+ const originalPathWithLeadingSlash = originalPath.startsWith('/')
30
+ ? originalPath
31
+ : `/${originalPath}`;
28
32
  // ========== JAVASCRIPT / TYPESCRIPT FRAMEWORKS ==========
29
33
  // Next.js - Pages Router (high confidence)
30
34
  if (p.includes('/pages/') && !p.includes('/_') && !p.includes('/api/')) {
@@ -91,7 +95,7 @@ export function detectFrameworkFromPath(filePath) {
91
95
  if ((p.includes('/components/') || p.includes('/views/')) &&
92
96
  (p.endsWith('.tsx') || p.endsWith('.jsx'))) {
93
97
  // Only boost if PascalCase filename (likely a component, not util)
94
- const fileName = p.split('/').pop() || '';
98
+ const fileName = originalPathWithLeadingSlash.split('/').pop() || '';
95
99
  if (/^[A-Z]/.test(fileName)) {
96
100
  return { framework: 'react', entryPointMultiplier: 1.5, reason: 'react-component' };
97
101
  }
@@ -9,6 +9,7 @@ import { getTreeSitterBufferSize } from './constants.js';
9
9
  import { loadImportConfigs } from './language-config.js';
10
10
  import { buildSuffixIndex } from './import-resolvers/utils.js';
11
11
  import { isDev } from './utils/env.js';
12
+ import { isRegistryPrimary } from './registry-primary-flag.js';
12
13
  /** Group files by provider (only those with implicit import wiring), then call each wirer
13
14
  * with its own language's files. O(n) over files, O(1) per provider lookup. */
14
15
  function wireImplicitImports(files, importMap, addImportEdge, projectConfig) {
@@ -64,6 +65,9 @@ export function preprocessImportPath(sourceText, importNode, provider) {
64
65
  function createImportEdgeHelpers(graph, importMap) {
65
66
  let totalImportsResolved = 0;
66
67
  const addImportGraphEdge = (filePath, resolvedPath) => {
68
+ const language = getLanguageFromFilename(filePath);
69
+ if (language !== null && isRegistryPrimary(language))
70
+ return;
67
71
  const sourceId = generateId('File', filePath);
68
72
  const targetId = generateId('File', resolvedPath);
69
73
  const relId = generateId('IMPORTS', `${filePath}->${resolvedPath}`);
@@ -45,12 +45,15 @@ export function resolvePythonImportInternal(currentFile, importPath, allFiles) {
45
45
  return null;
46
46
  // Normalize for Windows backslashes
47
47
  const importerDir = currentFile.replace(/\\/g, '/').split('/').slice(0, -1).join('/');
48
- if (!importerDir)
49
- return null;
50
- if (allFiles.has(`${importerDir}/${pathLike}/__init__.py`))
51
- return `${importerDir}/${pathLike}/__init__.py`;
52
- if (allFiles.has(`${importerDir}/${pathLike}.py`))
53
- return `${importerDir}/${pathLike}.py`;
48
+ // Proximity check — only applies when the importer lives in a subdirectory.
49
+ // Root-level importers (importerDir === '') skip straight to the ancestor
50
+ // walk below, which handles the root case correctly (prefix becomes '').
51
+ if (importerDir) {
52
+ if (allFiles.has(`${importerDir}/${pathLike}/__init__.py`))
53
+ return `${importerDir}/${pathLike}/__init__.py`;
54
+ if (allFiles.has(`${importerDir}/${pathLike}.py`))
55
+ return `${importerDir}/${pathLike}.py`;
56
+ }
54
57
  // Ancestor directory walk — Python resolves bare imports against sys.path entries,
55
58
  // which typically includes the project root and package directories. Walk up from the
56
59
  // importer's directory to find the module in an ancestor, preferring the closest match.
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Bridge between CLI-package per-language `ImportResolverFn`s and the
3
+ * shared `FinalizeHooks.resolveImportTarget` contract
4
+ * (RFC §5.2; Ring 2 PKG #922).
5
+ *
6
+ * The shared finalize algorithm (#915) asks one question:
7
+ *
8
+ * resolveImportTarget(targetRaw, fromFile, workspaceIndex): string | null
9
+ *
10
+ * The CLI already has 16 language-specific resolvers satisfying a
11
+ * richer signature:
12
+ *
13
+ * ImportResolverFn(rawImportPath, filePath, resolveCtx): ImportResult
14
+ *
15
+ * This module builds a dispatch adapter — one FinalizeHook implementation
16
+ * that looks up the file's language from its path and delegates to the
17
+ * right per-language resolver. Callers package per-language resolvers +
18
+ * a shared `ResolveCtx` into an opaque `ImportTargetWorkspace` and pass
19
+ * it as `workspaceIndex` to `finalizeScopeModel`.
20
+ *
21
+ * ## What's deliberately NOT here
22
+ *
23
+ * - **Re-implementation of any per-language resolver.** We wrap the
24
+ * existing `importResolver` field on each `LanguageProvider` — the
25
+ * same code path the legacy DAG uses today.
26
+ * - **Dynamic-import handling.** The shared finalize algorithm short-
27
+ * circuits `ParsedImport { kind: 'dynamic-unresolved' }` before
28
+ * calling `resolveImportTarget`, so the adapter never sees those.
29
+ * - **`importPathPreprocessor`.** Preprocessing belongs inside the
30
+ * provider's `interpretImport` hook (which writes the final
31
+ * `ParsedImport.targetRaw`). By the time finalize passes a
32
+ * `targetRaw` to this adapter, it is the string the provider wants
33
+ * resolved verbatim.
34
+ */
35
+ import { type SupportedLanguages, type WorkspaceIndex } from '../../_shared/index.js';
36
+ import type { ImportResolverFn, ResolveCtx } from './import-resolvers/types.js';
37
+ import type { LanguageProvider } from './language-provider.js';
38
+ /** A single language's resolver bundled with the context it needs. */
39
+ export interface LanguageResolverEntry {
40
+ readonly resolver: ImportResolverFn;
41
+ readonly ctx: ResolveCtx;
42
+ }
43
+ /**
44
+ * The opaque `workspaceIndex` shape recognized by
45
+ * `resolveImportTargetAcrossLanguages`. Built once per ingestion run via
46
+ * `buildImportTargetWorkspace`, threaded through `finalizeScopeModel`.
47
+ */
48
+ export interface ImportTargetWorkspace {
49
+ readonly perLanguage: ReadonlyMap<SupportedLanguages, LanguageResolverEntry>;
50
+ }
51
+ /**
52
+ * Build the workspace index from a map of language → provider. Providers
53
+ * whose `importResolver` is absent are silently skipped (no language will
54
+ * ever hit that branch at dispatch time).
55
+ *
56
+ * The `resolveCtx` is shared across all languages. Callers assemble it
57
+ * once per run (the existing pipeline already does this for the legacy
58
+ * DAG) and hand it to both the legacy resolution path and this factory.
59
+ */
60
+ export declare function buildImportTargetWorkspace(providers: ReadonlyMap<SupportedLanguages, LanguageProvider>, resolveCtx: ResolveCtx): ImportTargetWorkspace;
61
+ /**
62
+ * The FinalizeHooks-compatible implementation. Dispatches on `fromFile`'s
63
+ * extension → per-language resolver. Returns the first resolved file,
64
+ * or `null` if the resolver returns `null` or doesn't know about the
65
+ * language.
66
+ *
67
+ * Picks the first entry of `files[]` for both `'files'` and `'package'`
68
+ * result kinds — the legacy pipeline uses the whole array, but the
69
+ * shared `finalize()` hook contract is single-file. If the workspace
70
+ * later needs richer semantics (split-target packages), this is the
71
+ * single site to extend.
72
+ */
73
+ export declare function resolveImportTargetAcrossLanguages(targetRaw: string, fromFile: string, workspaceIndex: WorkspaceIndex): string | null;