gitnexus 1.6.3-rc.8 → 1.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (285) hide show
  1. package/README.md +21 -5
  2. package/dist/_shared/graph/types.d.ts +16 -0
  3. package/dist/_shared/graph/types.d.ts.map +1 -1
  4. package/dist/_shared/index.d.ts +20 -2
  5. package/dist/_shared/index.d.ts.map +1 -1
  6. package/dist/_shared/index.js +11 -0
  7. package/dist/_shared/index.js.map +1 -1
  8. package/dist/_shared/scope-resolution/def-index.js +2 -2
  9. package/dist/_shared/scope-resolution/def-index.js.map +1 -1
  10. package/dist/_shared/scope-resolution/method-dispatch-index.d.ts +8 -0
  11. package/dist/_shared/scope-resolution/method-dispatch-index.d.ts.map +1 -1
  12. package/dist/_shared/scope-resolution/method-dispatch-index.js +2 -2
  13. package/dist/_shared/scope-resolution/method-dispatch-index.js.map +1 -1
  14. package/dist/_shared/scope-resolution/module-scope-index.d.ts +8 -0
  15. package/dist/_shared/scope-resolution/module-scope-index.d.ts.map +1 -1
  16. package/dist/_shared/scope-resolution/module-scope-index.js +10 -2
  17. package/dist/_shared/scope-resolution/module-scope-index.js.map +1 -1
  18. package/dist/_shared/scope-resolution/parsed-file.d.ts +76 -0
  19. package/dist/_shared/scope-resolution/parsed-file.d.ts.map +1 -0
  20. package/dist/_shared/scope-resolution/parsed-file.js +54 -0
  21. package/dist/_shared/scope-resolution/parsed-file.js.map +1 -0
  22. package/dist/_shared/scope-resolution/position-index.d.ts +12 -0
  23. package/dist/_shared/scope-resolution/position-index.d.ts.map +1 -1
  24. package/dist/_shared/scope-resolution/position-index.js +2 -2
  25. package/dist/_shared/scope-resolution/position-index.js.map +1 -1
  26. package/dist/_shared/scope-resolution/qualified-name-index.js +2 -2
  27. package/dist/_shared/scope-resolution/qualified-name-index.js.map +1 -1
  28. package/dist/_shared/scope-resolution/reference-site.d.ts +75 -0
  29. package/dist/_shared/scope-resolution/reference-site.d.ts.map +1 -0
  30. package/dist/_shared/scope-resolution/reference-site.js +24 -0
  31. package/dist/_shared/scope-resolution/reference-site.js.map +1 -0
  32. package/dist/_shared/scope-resolution/registries/class-registry.d.ts +27 -0
  33. package/dist/_shared/scope-resolution/registries/class-registry.d.ts.map +1 -0
  34. package/dist/_shared/scope-resolution/registries/class-registry.js +30 -0
  35. package/dist/_shared/scope-resolution/registries/class-registry.js.map +1 -0
  36. package/dist/_shared/scope-resolution/registries/context.d.ts +69 -0
  37. package/dist/_shared/scope-resolution/registries/context.d.ts.map +1 -0
  38. package/dist/_shared/scope-resolution/registries/context.js +44 -0
  39. package/dist/_shared/scope-resolution/registries/context.js.map +1 -0
  40. package/dist/_shared/scope-resolution/registries/evidence.d.ts +56 -0
  41. package/dist/_shared/scope-resolution/registries/evidence.d.ts.map +1 -0
  42. package/dist/_shared/scope-resolution/registries/evidence.js +150 -0
  43. package/dist/_shared/scope-resolution/registries/evidence.js.map +1 -0
  44. package/dist/_shared/scope-resolution/registries/field-registry.d.ts +26 -0
  45. package/dist/_shared/scope-resolution/registries/field-registry.d.ts.map +1 -0
  46. package/dist/_shared/scope-resolution/registries/field-registry.js +31 -0
  47. package/dist/_shared/scope-resolution/registries/field-registry.js.map +1 -0
  48. package/dist/_shared/scope-resolution/registries/lookup-core.d.ts +81 -0
  49. package/dist/_shared/scope-resolution/registries/lookup-core.d.ts.map +1 -0
  50. package/dist/_shared/scope-resolution/registries/lookup-core.js +332 -0
  51. package/dist/_shared/scope-resolution/registries/lookup-core.js.map +1 -0
  52. package/dist/_shared/scope-resolution/registries/lookup-qualified.d.ts +33 -0
  53. package/dist/_shared/scope-resolution/registries/lookup-qualified.d.ts.map +1 -0
  54. package/dist/_shared/scope-resolution/registries/lookup-qualified.js +56 -0
  55. package/dist/_shared/scope-resolution/registries/lookup-qualified.js.map +1 -0
  56. package/dist/_shared/scope-resolution/registries/method-registry.d.ts +36 -0
  57. package/dist/_shared/scope-resolution/registries/method-registry.d.ts.map +1 -0
  58. package/dist/_shared/scope-resolution/registries/method-registry.js +32 -0
  59. package/dist/_shared/scope-resolution/registries/method-registry.js.map +1 -0
  60. package/dist/_shared/scope-resolution/registries/tie-breaks.d.ts +43 -0
  61. package/dist/_shared/scope-resolution/registries/tie-breaks.d.ts.map +1 -0
  62. package/dist/_shared/scope-resolution/registries/tie-breaks.js +60 -0
  63. package/dist/_shared/scope-resolution/registries/tie-breaks.js.map +1 -0
  64. package/dist/_shared/scope-resolution/resolve-type-ref.d.ts +1 -10
  65. package/dist/_shared/scope-resolution/resolve-type-ref.d.ts.map +1 -1
  66. package/dist/_shared/scope-resolution/resolve-type-ref.js +6 -0
  67. package/dist/_shared/scope-resolution/resolve-type-ref.js.map +1 -1
  68. package/dist/_shared/scope-resolution/scope-tree.d.ts +4 -4
  69. package/dist/_shared/scope-resolution/scope-tree.d.ts.map +1 -1
  70. package/dist/_shared/scope-resolution/scope-tree.js +3 -2
  71. package/dist/_shared/scope-resolution/scope-tree.js.map +1 -1
  72. package/dist/_shared/scope-resolution/shadow/aggregate.d.ts +6 -2
  73. package/dist/_shared/scope-resolution/shadow/aggregate.d.ts.map +1 -1
  74. package/dist/_shared/scope-resolution/shadow/aggregate.js +5 -0
  75. package/dist/_shared/scope-resolution/shadow/aggregate.js.map +1 -1
  76. package/dist/_shared/scope-resolution/types.d.ts +11 -0
  77. package/dist/_shared/scope-resolution/types.d.ts.map +1 -1
  78. package/dist/cli/ai-context.js +35 -4
  79. package/dist/cli/analyze.d.ts +27 -0
  80. package/dist/cli/analyze.js +31 -1
  81. package/dist/cli/clean.js +19 -1
  82. package/dist/cli/group.js +73 -0
  83. package/dist/cli/index-repo.js +8 -1
  84. package/dist/cli/index.js +26 -1
  85. package/dist/cli/list.js +11 -1
  86. package/dist/cli/remove.d.ts +30 -0
  87. package/dist/cli/remove.js +99 -0
  88. package/dist/cli/setup.js +185 -57
  89. package/dist/cli/tool.d.ts +5 -0
  90. package/dist/cli/tool.js +42 -0
  91. package/dist/config/ignore-service.d.ts +9 -0
  92. package/dist/config/ignore-service.js +80 -13
  93. package/dist/core/embedding-mode.d.ts +30 -0
  94. package/dist/core/embedding-mode.js +30 -0
  95. package/dist/core/embeddings/ast-utils.js +22 -22
  96. package/dist/core/embeddings/chunker.js +30 -25
  97. package/dist/core/embeddings/embedding-pipeline.d.ts +6 -0
  98. package/dist/core/embeddings/embedding-pipeline.js +15 -6
  99. package/dist/core/embeddings/text-generator.d.ts +1 -1
  100. package/dist/core/embeddings/text-generator.js +33 -24
  101. package/dist/core/embeddings/types.d.ts +43 -1
  102. package/dist/core/embeddings/types.js +101 -29
  103. package/dist/core/git-staleness.d.ts +18 -0
  104. package/dist/core/git-staleness.js +108 -0
  105. package/dist/core/graph/graph.js +115 -20
  106. package/dist/core/graph/types.d.ts +12 -1
  107. package/dist/core/group/config-parser.d.ts +4 -0
  108. package/dist/core/group/config-parser.js +18 -1
  109. package/dist/core/group/cross-impact.d.ts +41 -0
  110. package/dist/core/group/cross-impact.js +441 -0
  111. package/dist/core/group/extractors/http-patterns/php.js +126 -18
  112. package/dist/core/group/group-path-utils.d.ts +17 -0
  113. package/dist/core/group/group-path-utils.js +40 -0
  114. package/dist/core/group/resolve-at-member.d.ts +10 -0
  115. package/dist/core/group/resolve-at-member.js +31 -0
  116. package/dist/core/group/service.d.ts +9 -0
  117. package/dist/core/group/service.js +259 -25
  118. package/dist/core/group/types.d.ts +30 -0
  119. package/dist/core/ingestion/ast-cache.d.ts +16 -1
  120. package/dist/core/ingestion/ast-cache.js +14 -2
  121. package/dist/core/ingestion/call-processor.js +9 -0
  122. package/dist/core/ingestion/emit-references.d.ts +88 -0
  123. package/dist/core/ingestion/emit-references.js +229 -0
  124. package/dist/core/ingestion/filesystem-walker.js +6 -4
  125. package/dist/core/ingestion/finalize-orchestrator.d.ts +63 -0
  126. package/dist/core/ingestion/finalize-orchestrator.js +139 -0
  127. package/dist/core/ingestion/framework-detection.js +6 -2
  128. package/dist/core/ingestion/import-processor.js +4 -0
  129. package/dist/core/ingestion/import-resolvers/python.js +9 -6
  130. package/dist/core/ingestion/import-target-adapter.d.ts +73 -0
  131. package/dist/core/ingestion/import-target-adapter.js +95 -0
  132. package/dist/core/ingestion/language-provider.d.ts +36 -33
  133. package/dist/core/ingestion/languages/csharp/accessor-unwrap.d.ts +21 -0
  134. package/dist/core/ingestion/languages/csharp/accessor-unwrap.js +56 -0
  135. package/dist/core/ingestion/languages/csharp/arity-metadata.d.ts +26 -0
  136. package/dist/core/ingestion/languages/csharp/arity-metadata.js +46 -0
  137. package/dist/core/ingestion/languages/csharp/arity.d.ts +23 -0
  138. package/dist/core/ingestion/languages/csharp/arity.js +37 -0
  139. package/dist/core/ingestion/languages/csharp/cache-stats.d.ts +15 -0
  140. package/dist/core/ingestion/languages/csharp/cache-stats.js +26 -0
  141. package/dist/core/ingestion/languages/csharp/captures.d.ts +19 -0
  142. package/dist/core/ingestion/languages/csharp/captures.js +249 -0
  143. package/dist/core/ingestion/languages/csharp/import-decomposer.d.ts +19 -0
  144. package/dist/core/ingestion/languages/csharp/import-decomposer.js +93 -0
  145. package/dist/core/ingestion/languages/csharp/import-target.d.ts +25 -0
  146. package/dist/core/ingestion/languages/csharp/import-target.js +123 -0
  147. package/dist/core/ingestion/languages/csharp/index.d.ts +82 -0
  148. package/dist/core/ingestion/languages/csharp/index.js +82 -0
  149. package/dist/core/ingestion/languages/csharp/interpret.d.ts +15 -0
  150. package/dist/core/ingestion/languages/csharp/interpret.js +132 -0
  151. package/dist/core/ingestion/languages/csharp/merge-bindings.d.ts +27 -0
  152. package/dist/core/ingestion/languages/csharp/merge-bindings.js +55 -0
  153. package/dist/core/ingestion/languages/csharp/namespace-siblings.d.ts +50 -0
  154. package/dist/core/ingestion/languages/csharp/namespace-siblings.js +374 -0
  155. package/dist/core/ingestion/languages/csharp/query.d.ts +35 -0
  156. package/dist/core/ingestion/languages/csharp/query.js +515 -0
  157. package/dist/core/ingestion/languages/csharp/receiver-binding.d.ts +31 -0
  158. package/dist/core/ingestion/languages/csharp/receiver-binding.js +135 -0
  159. package/dist/core/ingestion/languages/csharp/scope-resolver.d.ts +10 -0
  160. package/dist/core/ingestion/languages/csharp/scope-resolver.js +63 -0
  161. package/dist/core/ingestion/languages/csharp/simple-hooks.d.ts +53 -0
  162. package/dist/core/ingestion/languages/csharp/simple-hooks.js +76 -0
  163. package/dist/core/ingestion/languages/csharp.js +14 -0
  164. package/dist/core/ingestion/languages/python/arity-metadata.d.ts +24 -0
  165. package/dist/core/ingestion/languages/python/arity-metadata.js +45 -0
  166. package/dist/core/ingestion/languages/python/arity.d.ts +22 -0
  167. package/dist/core/ingestion/languages/python/arity.js +38 -0
  168. package/dist/core/ingestion/languages/python/cache-stats.d.ts +17 -0
  169. package/dist/core/ingestion/languages/python/cache-stats.js +28 -0
  170. package/dist/core/ingestion/languages/python/captures.d.ts +19 -0
  171. package/dist/core/ingestion/languages/python/captures.js +106 -0
  172. package/dist/core/ingestion/languages/python/import-decomposer.d.ts +15 -0
  173. package/dist/core/ingestion/languages/python/import-decomposer.js +112 -0
  174. package/dist/core/ingestion/languages/python/import-target.d.ts +21 -0
  175. package/dist/core/ingestion/languages/python/import-target.js +99 -0
  176. package/dist/core/ingestion/languages/python/index.d.ts +80 -0
  177. package/dist/core/ingestion/languages/python/index.js +80 -0
  178. package/dist/core/ingestion/languages/python/interpret.d.ts +15 -0
  179. package/dist/core/ingestion/languages/python/interpret.js +191 -0
  180. package/dist/core/ingestion/languages/python/merge-bindings.d.ts +16 -0
  181. package/dist/core/ingestion/languages/python/merge-bindings.js +44 -0
  182. package/dist/core/ingestion/languages/python/query.d.ts +9 -0
  183. package/dist/core/ingestion/languages/python/query.js +267 -0
  184. package/dist/core/ingestion/languages/python/receiver-binding.d.ts +21 -0
  185. package/dist/core/ingestion/languages/python/receiver-binding.js +116 -0
  186. package/dist/core/ingestion/languages/python/scope-resolver.d.ts +16 -0
  187. package/dist/core/ingestion/languages/python/scope-resolver.js +53 -0
  188. package/dist/core/ingestion/languages/python/simple-hooks.d.ts +23 -0
  189. package/dist/core/ingestion/languages/python/simple-hooks.js +35 -0
  190. package/dist/core/ingestion/languages/python.js +14 -0
  191. package/dist/core/ingestion/model/method-registry.d.ts +9 -0
  192. package/dist/core/ingestion/model/method-registry.js +4 -0
  193. package/dist/core/ingestion/model/scope-resolution-indexes.d.ts +59 -0
  194. package/dist/core/ingestion/model/scope-resolution-indexes.js +42 -0
  195. package/dist/core/ingestion/model/semantic-model.d.ts +64 -0
  196. package/dist/core/ingestion/model/semantic-model.js +55 -0
  197. package/dist/core/ingestion/mro-processor.js +38 -22
  198. package/dist/core/ingestion/parsing-processor.d.ts +18 -1
  199. package/dist/core/ingestion/parsing-processor.js +45 -11
  200. package/dist/core/ingestion/pipeline-phases/index.d.ts +1 -0
  201. package/dist/core/ingestion/pipeline-phases/index.js +1 -0
  202. package/dist/core/ingestion/pipeline-phases/parse-impl.d.ts +10 -0
  203. package/dist/core/ingestion/pipeline-phases/parse-impl.js +17 -2
  204. package/dist/core/ingestion/pipeline-phases/parse.d.ts +18 -0
  205. package/dist/core/ingestion/pipeline.js +2 -1
  206. package/dist/core/ingestion/registry-primary-flag.d.ts +86 -0
  207. package/dist/core/ingestion/registry-primary-flag.js +111 -0
  208. package/dist/core/ingestion/resolve-references.d.ts +63 -0
  209. package/dist/core/ingestion/resolve-references.js +175 -0
  210. package/dist/core/ingestion/scope-extractor-bridge.d.ts +32 -0
  211. package/dist/core/ingestion/scope-extractor-bridge.js +44 -0
  212. package/dist/core/ingestion/scope-extractor.d.ts +86 -0
  213. package/dist/core/ingestion/scope-extractor.js +758 -0
  214. package/dist/core/ingestion/scope-resolution/contract/scope-resolver.d.ts +372 -0
  215. package/dist/core/ingestion/scope-resolution/contract/scope-resolver.js +212 -0
  216. package/dist/core/ingestion/scope-resolution/graph-bridge/edges.d.ts +43 -0
  217. package/dist/core/ingestion/scope-resolution/graph-bridge/edges.js +79 -0
  218. package/dist/core/ingestion/scope-resolution/graph-bridge/ids.d.ts +57 -0
  219. package/dist/core/ingestion/scope-resolution/graph-bridge/ids.js +112 -0
  220. package/dist/core/ingestion/scope-resolution/graph-bridge/imports-to-edges.d.ts +17 -0
  221. package/dist/core/ingestion/scope-resolution/graph-bridge/imports-to-edges.js +46 -0
  222. package/dist/core/ingestion/scope-resolution/graph-bridge/method-dispatch.d.ts +19 -0
  223. package/dist/core/ingestion/scope-resolution/graph-bridge/method-dispatch.js +30 -0
  224. package/dist/core/ingestion/scope-resolution/graph-bridge/node-lookup.d.ts +37 -0
  225. package/dist/core/ingestion/scope-resolution/graph-bridge/node-lookup.js +113 -0
  226. package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.d.ts +38 -0
  227. package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.js +73 -0
  228. package/dist/core/ingestion/scope-resolution/passes/compound-receiver.d.ts +42 -0
  229. package/dist/core/ingestion/scope-resolution/passes/compound-receiver.js +198 -0
  230. package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.d.ts +27 -0
  231. package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.js +131 -0
  232. package/dist/core/ingestion/scope-resolution/passes/imported-return-types.d.ts +48 -0
  233. package/dist/core/ingestion/scope-resolution/passes/imported-return-types.js +130 -0
  234. package/dist/core/ingestion/scope-resolution/passes/mro.d.ts +42 -0
  235. package/dist/core/ingestion/scope-resolution/passes/mro.js +99 -0
  236. package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.d.ts +26 -0
  237. package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.js +61 -0
  238. package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.d.ts +46 -0
  239. package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +327 -0
  240. package/dist/core/ingestion/scope-resolution/pipeline/phase.d.ts +47 -0
  241. package/dist/core/ingestion/scope-resolution/pipeline/phase.js +130 -0
  242. package/dist/core/ingestion/scope-resolution/pipeline/reconcile-ownership.d.ts +68 -0
  243. package/dist/core/ingestion/scope-resolution/pipeline/reconcile-ownership.js +125 -0
  244. package/dist/core/ingestion/scope-resolution/pipeline/registry.d.ts +17 -0
  245. package/dist/core/ingestion/scope-resolution/pipeline/registry.js +21 -0
  246. package/dist/core/ingestion/scope-resolution/pipeline/run.d.ts +66 -0
  247. package/dist/core/ingestion/scope-resolution/pipeline/run.js +157 -0
  248. package/dist/core/ingestion/scope-resolution/scope/namespace-targets.d.ts +36 -0
  249. package/dist/core/ingestion/scope-resolution/scope/namespace-targets.js +52 -0
  250. package/dist/core/ingestion/scope-resolution/scope/walkers.d.ts +127 -0
  251. package/dist/core/ingestion/scope-resolution/scope/walkers.js +349 -0
  252. package/dist/core/ingestion/scope-resolution/workspace-index.d.ts +52 -0
  253. package/dist/core/ingestion/scope-resolution/workspace-index.js +61 -0
  254. package/dist/core/ingestion/shadow-harness.d.ts +113 -0
  255. package/dist/core/ingestion/shadow-harness.js +148 -0
  256. package/dist/core/ingestion/utils/ast-helpers.d.ts +19 -1
  257. package/dist/core/ingestion/utils/ast-helpers.js +70 -0
  258. package/dist/core/ingestion/utils/max-file-size.d.ts +20 -0
  259. package/dist/core/ingestion/utils/max-file-size.js +52 -0
  260. package/dist/core/ingestion/workers/parse-worker.d.ts +9 -0
  261. package/dist/core/ingestion/workers/parse-worker.js +57 -21
  262. package/dist/core/lbug/lbug-adapter.d.ts +22 -2
  263. package/dist/core/lbug/lbug-adapter.js +58 -14
  264. package/dist/core/lbug/pool-adapter.d.ts +17 -0
  265. package/dist/core/lbug/pool-adapter.js +24 -14
  266. package/dist/core/run-analyze.d.ts +32 -0
  267. package/dist/core/run-analyze.js +74 -19
  268. package/dist/core/search/bm25-index.d.ts +18 -0
  269. package/dist/core/search/bm25-index.js +125 -12
  270. package/dist/core/tree-sitter/parser-loader.js +6 -1
  271. package/dist/mcp/local/local-backend.d.ts +67 -3
  272. package/dist/mcp/local/local-backend.js +296 -34
  273. package/dist/mcp/resources.d.ts +31 -0
  274. package/dist/mcp/resources.js +100 -17
  275. package/dist/mcp/tools.d.ts +4 -1
  276. package/dist/mcp/tools.js +75 -54
  277. package/dist/server/api.js +6 -2
  278. package/dist/storage/git.d.ts +49 -0
  279. package/dist/storage/git.js +111 -0
  280. package/dist/storage/repo-manager.d.ts +246 -1
  281. package/dist/storage/repo-manager.js +391 -9
  282. package/package.json +7 -6
  283. package/scripts/bench-scope-resolution.ts +134 -0
  284. package/scripts/ci-list-migrated-languages.ts +24 -0
  285. package/skills/gitnexus-cli.md +1 -0
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Map MCP/CLI `@groupName` or `@groupName/memberPath` to a concrete member path in group.yaml.
3
+ */
4
+ import { loadGroupConfig } from './config-parser.js';
5
+ import { getDefaultGitnexusDir, getGroupDir } from './storage.js';
6
+ export async function resolveAtGroupMemberRepoPath(groupName, explicitMemberPath) {
7
+ const trimmed = groupName.trim();
8
+ if (!trimmed)
9
+ return { ok: false, error: 'Group name is empty.' };
10
+ try {
11
+ const groupDir = getGroupDir(getDefaultGitnexusDir(), trimmed);
12
+ const config = await loadGroupConfig(groupDir);
13
+ const keys = Object.keys(config.repos).sort((a, b) => a.localeCompare(b));
14
+ if (keys.length === 0) {
15
+ return { ok: false, error: `Group "${trimmed}" has no repos in group.yaml.` };
16
+ }
17
+ if (explicitMemberPath !== undefined && explicitMemberPath !== '') {
18
+ if (!(explicitMemberPath in config.repos)) {
19
+ return {
20
+ ok: false,
21
+ error: `Unknown member path "${explicitMemberPath}" in group "${trimmed}". Known paths: ${keys.join(', ')}`,
22
+ };
23
+ }
24
+ return { ok: true, repoPath: explicitMemberPath };
25
+ }
26
+ return { ok: true, repoPath: keys[0] };
27
+ }
28
+ catch (e) {
29
+ return { ok: false, error: e instanceof Error ? e.message : String(e) };
30
+ }
31
+ }
@@ -2,6 +2,7 @@
2
2
  * Group orchestration shared by MCP (LocalBackend) and CLI.
3
3
  * DB access is injected via GroupToolPort so this module stays free of LocalBackend private API.
4
4
  */
5
+ import type { GroupContextResult } from './types.js';
5
6
  export interface GroupRepoHandle {
6
7
  id: string;
7
8
  name: string;
@@ -34,6 +35,12 @@ export interface GroupToolPort {
34
35
  minConfidence: number;
35
36
  includeTests: boolean;
36
37
  }): Promise<unknown | null>;
38
+ context(repo: GroupRepoHandle, params: {
39
+ name?: string;
40
+ uid?: string;
41
+ file_path?: string;
42
+ include_content?: boolean;
43
+ }): Promise<unknown>;
37
44
  }
38
45
  export declare class GroupService {
39
46
  private readonly port;
@@ -41,6 +48,8 @@ export declare class GroupService {
41
48
  groupList(params: Record<string, unknown>): Promise<unknown>;
42
49
  groupSync(params: Record<string, unknown>): Promise<unknown>;
43
50
  groupContracts(params: Record<string, unknown>): Promise<unknown>;
51
+ groupImpact(params: Record<string, unknown>): Promise<unknown>;
52
+ groupContext(params: Record<string, unknown>): Promise<GroupContextResult>;
44
53
  groupQuery(params: Record<string, unknown>): Promise<unknown>;
45
54
  groupStatus(params: Record<string, unknown>): Promise<unknown>;
46
55
  }
@@ -2,15 +2,125 @@
2
2
  * Group orchestration shared by MCP (LocalBackend) and CLI.
3
3
  * DB access is injected via GroupToolPort so this module stays free of LocalBackend private API.
4
4
  */
5
+ import fsp from 'node:fs/promises';
6
+ import path from 'node:path';
5
7
  import { checkStaleness } from '../git-staleness.js';
6
- import { loadGroupConfig } from './config-parser.js';
8
+ import { GroupNotFoundError, loadGroupConfig } from './config-parser.js';
9
+ import { fileMatchesServicePrefix, normalizeServicePrefix, repoInSubgroup, } from './group-path-utils.js';
7
10
  import { getDefaultGitnexusDir, getGroupDir, listGroups, readContractRegistry } from './storage.js';
8
11
  import { syncGroup } from './sync.js';
9
- function repoInSubgroup(repoPath, subgroup) {
10
- if (!subgroup?.trim())
11
- return true;
12
- const s = subgroup.replace(/\/+$/, '');
13
- return repoPath === s || repoPath.startsWith(`${s}/`);
12
+ function isStoredContract(raw) {
13
+ if (!raw || typeof raw !== 'object')
14
+ return false;
15
+ const o = raw;
16
+ return (typeof o.contractId === 'string' &&
17
+ typeof o.type === 'string' &&
18
+ typeof o.repo === 'string' &&
19
+ typeof o.role === 'string' &&
20
+ (o.role === 'provider' || o.role === 'consumer') &&
21
+ typeof o.symbolUid === 'string' &&
22
+ typeof o.symbolName === 'string' &&
23
+ typeof o.confidence === 'number' &&
24
+ o.meta !== undefined &&
25
+ typeof o.meta === 'object' &&
26
+ o.meta !== null &&
27
+ o.symbolRef !== undefined &&
28
+ typeof o.symbolRef === 'object' &&
29
+ o.symbolRef !== null &&
30
+ typeof o.symbolRef.filePath === 'string' &&
31
+ typeof o.symbolRef.name === 'string');
32
+ }
33
+ function filterQueryByServicePrefix(queryResult, servicePrefix) {
34
+ const symbols = (queryResult.process_symbols || []).filter((s) => fileMatchesServicePrefix(typeof s.filePath === 'string' ? s.filePath : undefined, servicePrefix));
35
+ const allowed = new Set(symbols.map((s) => String(s.process_id ?? '')).filter(Boolean));
36
+ const processes = (queryResult.processes || []).filter((p) => allowed.has(String(p.id)));
37
+ return { processes, process_symbols: symbols };
38
+ }
39
+ function isCrossLink(raw) {
40
+ if (!raw || typeof raw !== 'object')
41
+ return false;
42
+ const o = raw;
43
+ const from = o.from;
44
+ const to = o.to;
45
+ if (!from || !to)
46
+ return false;
47
+ if (typeof from.repo !== 'string' || typeof to.repo !== 'string')
48
+ return false;
49
+ return typeof o.contractId === 'string' && typeof o.type === 'string';
50
+ }
51
+ async function loadContractRegistryResilient(groupDir) {
52
+ const filePath = path.join(groupDir, 'contracts.json');
53
+ let raw;
54
+ try {
55
+ raw = await fsp.readFile(filePath, 'utf-8');
56
+ }
57
+ catch (e) {
58
+ if (e.code === 'ENOENT') {
59
+ return { ok: false, error: `No contracts.json for this group. Run group_sync first.` };
60
+ }
61
+ return { ok: false, error: e instanceof Error ? e.message : String(e) };
62
+ }
63
+ let root;
64
+ try {
65
+ root = JSON.parse(raw);
66
+ }
67
+ catch {
68
+ return { ok: false, error: 'contracts.json is not valid JSON' };
69
+ }
70
+ if (!root || typeof root !== 'object' || Array.isArray(root)) {
71
+ return { ok: false, error: 'contracts.json has an invalid root object' };
72
+ }
73
+ const base = root;
74
+ const contractsRaw = base.contracts;
75
+ const crossRaw = base.crossLinks;
76
+ let skippedCorrupt = 0;
77
+ const contracts = [];
78
+ if (Array.isArray(contractsRaw)) {
79
+ for (const row of contractsRaw) {
80
+ try {
81
+ if (isStoredContract(row)) {
82
+ contracts.push(row);
83
+ }
84
+ else {
85
+ skippedCorrupt++;
86
+ console.warn('[group] skipping corrupt contract row in contracts.json');
87
+ }
88
+ }
89
+ catch {
90
+ skippedCorrupt++;
91
+ console.warn('[group] skipping corrupt contract row in contracts.json');
92
+ }
93
+ }
94
+ }
95
+ const crossLinks = [];
96
+ if (Array.isArray(crossRaw)) {
97
+ for (const row of crossRaw) {
98
+ try {
99
+ if (isCrossLink(row)) {
100
+ crossLinks.push(row);
101
+ }
102
+ else {
103
+ skippedCorrupt++;
104
+ console.warn('[group] skipping corrupt crossLinks row in contracts.json');
105
+ }
106
+ }
107
+ catch {
108
+ skippedCorrupt++;
109
+ console.warn('[group] skipping corrupt crossLinks row in contracts.json');
110
+ }
111
+ }
112
+ }
113
+ const registry = {
114
+ version: typeof base.version === 'number' ? base.version : 0,
115
+ generatedAt: typeof base.generatedAt === 'string' ? base.generatedAt : '',
116
+ repoSnapshots: base.repoSnapshots && typeof base.repoSnapshots === 'object' && base.repoSnapshots !== null
117
+ ? base.repoSnapshots
118
+ : {},
119
+ missingRepos: Array.isArray(base.missingRepos) ? base.missingRepos : [],
120
+ contracts,
121
+ crossLinks,
122
+ };
123
+ return { ok: true, registry, skippedCorrupt };
14
124
  }
15
125
  export class GroupService {
16
126
  port;
@@ -24,7 +134,15 @@ export class GroupService {
24
134
  return { groups };
25
135
  }
26
136
  const groupDir = getGroupDir(getDefaultGitnexusDir(), name);
27
- const config = await loadGroupConfig(groupDir);
137
+ let config;
138
+ try {
139
+ config = await loadGroupConfig(groupDir);
140
+ }
141
+ catch (err) {
142
+ if (err instanceof GroupNotFoundError)
143
+ return { error: `Group "${name}" not found. Run group_list to see configured groups.` };
144
+ throw err;
145
+ }
28
146
  return {
29
147
  name: config.name,
30
148
  description: config.description,
@@ -37,7 +155,15 @@ export class GroupService {
37
155
  if (!name)
38
156
  return { error: 'name is required' };
39
157
  const groupDir = getGroupDir(getDefaultGitnexusDir(), name);
40
- const config = await loadGroupConfig(groupDir);
158
+ let config;
159
+ try {
160
+ config = await loadGroupConfig(groupDir);
161
+ }
162
+ catch (err) {
163
+ if (err instanceof GroupNotFoundError)
164
+ return { error: `Group "${name}" not found. Run group_list to see configured groups.` };
165
+ throw err;
166
+ }
41
167
  const result = await syncGroup(config, {
42
168
  groupDir,
43
169
  exactOnly: Boolean(params.exactOnly),
@@ -57,10 +183,14 @@ export class GroupService {
57
183
  if (!name)
58
184
  return { error: 'name is required' };
59
185
  const groupDir = getGroupDir(getDefaultGitnexusDir(), name);
60
- const registry = await readContractRegistry(groupDir);
61
- if (!registry) {
62
- return { error: `No contracts.json for group "${name}". Run group_sync first.` };
186
+ const loaded = await loadContractRegistryResilient(groupDir);
187
+ if (loaded.ok === false) {
188
+ if (loaded.error.includes('No contracts.json')) {
189
+ return { error: `No contracts.json for group "${name}". Run group_sync first.` };
190
+ }
191
+ return { error: loaded.error };
63
192
  }
193
+ const { registry, skippedCorrupt } = loaded;
64
194
  let contracts = registry.contracts;
65
195
  if (params.type)
66
196
  contracts = contracts.filter((c) => c.type === params.type);
@@ -73,21 +203,117 @@ export class GroupService {
73
203
  ]));
74
204
  contracts = contracts.filter((c) => !matchedIds.has(`${c.repo}::${c.contractId}`));
75
205
  }
76
- return { contracts, crossLinks: registry.crossLinks };
206
+ const out = { contracts, crossLinks: registry.crossLinks };
207
+ if (skippedCorrupt > 0)
208
+ out.skippedCorrupt = skippedCorrupt;
209
+ return out;
210
+ }
211
+ async groupImpact(params) {
212
+ const { runGroupImpact } = await import('./cross-impact.js');
213
+ return runGroupImpact({ port: this.port, gitnexusDir: getDefaultGitnexusDir() }, params);
214
+ }
215
+ async groupContext(params) {
216
+ const name = String(params.name ?? '').trim();
217
+ const target = typeof params.target === 'string' ? params.target.trim() : '';
218
+ const uid = typeof params.uid === 'string' ? params.uid.trim() : undefined;
219
+ const file_path = typeof params.file_path === 'string' ? params.file_path : undefined;
220
+ const include_content = Boolean(params.include_content);
221
+ if (params.service !== undefined &&
222
+ params.service !== null &&
223
+ String(params.service).trim() === '') {
224
+ return { group: name || '', error: 'service must not be an empty string', results: [] };
225
+ }
226
+ const servicePrefix = normalizeServicePrefix(params.service);
227
+ const subgroup = typeof params.subgroup === 'string' ? params.subgroup : undefined;
228
+ const subgroupExact = params.subgroupExact === true;
229
+ if (!name) {
230
+ return { group: '', error: 'name is required', results: [] };
231
+ }
232
+ if (!uid && !target) {
233
+ return { group: name, error: 'target or uid is required', results: [] };
234
+ }
235
+ const groupDir = getGroupDir(getDefaultGitnexusDir(), name);
236
+ let config;
237
+ try {
238
+ config = await loadGroupConfig(groupDir);
239
+ }
240
+ catch (e) {
241
+ if (e instanceof GroupNotFoundError)
242
+ return {
243
+ group: name,
244
+ target: target || uid,
245
+ service: servicePrefix,
246
+ error: `Group "${name}" not found. Run group_list to see configured groups.`,
247
+ results: [],
248
+ };
249
+ return {
250
+ group: name,
251
+ target: target || uid,
252
+ service: servicePrefix,
253
+ error: e instanceof Error ? e.message : String(e),
254
+ results: [],
255
+ };
256
+ }
257
+ const memberEntries = Object.entries(config.repos).filter(([repoPath]) => repoInSubgroup(repoPath, subgroup, subgroupExact));
258
+ const results = await Promise.all(memberEntries.map(async ([repoPath, registryName]) => {
259
+ try {
260
+ const repoObj = await this.port.resolveRepo(registryName);
261
+ const payload = await this.port.context(repoObj, {
262
+ name: target || undefined,
263
+ uid,
264
+ file_path,
265
+ include_content,
266
+ });
267
+ if (servicePrefix) {
268
+ const st = payload?.status;
269
+ const sym = payload?.symbol;
270
+ if (st === 'found' && !fileMatchesServicePrefix(sym?.filePath, servicePrefix)) {
271
+ return { repoPath, registryName, payload: {} };
272
+ }
273
+ }
274
+ return { repoPath, registryName, payload };
275
+ }
276
+ catch (e) {
277
+ return {
278
+ repoPath,
279
+ registryName,
280
+ payload: { error: e instanceof Error ? e.message : String(e) },
281
+ };
282
+ }
283
+ }));
284
+ return {
285
+ group: name,
286
+ target: target || uid,
287
+ service: servicePrefix,
288
+ results,
289
+ };
77
290
  }
78
291
  async groupQuery(params) {
79
292
  const name = String(params.name ?? '').trim();
80
293
  const queryText = String(params.query ?? '').trim();
81
294
  if (!name || !queryText)
82
295
  return { error: 'name and query are required' };
296
+ if (params.service !== undefined &&
297
+ params.service !== null &&
298
+ String(params.service).trim() === '') {
299
+ return { error: 'service must not be an empty string' };
300
+ }
301
+ const servicePrefix = normalizeServicePrefix(params.service);
83
302
  const limit = typeof params.limit === 'number' && params.limit > 0 ? params.limit : 5;
84
303
  const subgroup = typeof params.subgroup === 'string' ? params.subgroup : undefined;
304
+ const subgroupExact = params.subgroupExact === true;
85
305
  const groupDir = getGroupDir(getDefaultGitnexusDir(), name);
86
- const config = await loadGroupConfig(groupDir);
87
- const perRepo = [];
88
- for (const [repoPath, registryName] of Object.entries(config.repos)) {
89
- if (!repoInSubgroup(repoPath, subgroup))
90
- continue;
306
+ let config;
307
+ try {
308
+ config = await loadGroupConfig(groupDir);
309
+ }
310
+ catch (err) {
311
+ if (err instanceof GroupNotFoundError)
312
+ return { error: `Group "${name}" not found. Run group_list to see configured groups.` };
313
+ throw err;
314
+ }
315
+ const memberEntries = Object.entries(config.repos).filter(([repoPath]) => repoInSubgroup(repoPath, subgroup, subgroupExact));
316
+ const perRepo = await Promise.all(memberEntries.map(async ([repoPath, registryName]) => {
91
317
  try {
92
318
  const repoObj = await this.port.resolveRepo(registryName);
93
319
  const queryResult = (await this.port.query(repoObj, {
@@ -96,18 +322,20 @@ export class GroupService {
96
322
  max_symbols: 10,
97
323
  include_content: false,
98
324
  }));
99
- const processes = queryResult.processes || [];
325
+ const processes = servicePrefix
326
+ ? filterQueryByServicePrefix(queryResult, servicePrefix).processes
327
+ : queryResult.processes || [];
100
328
  const scored = processes.map((p, idx) => ({
101
329
  ...p,
102
330
  _rrf_score: 1 / (idx + 1 + 60),
103
331
  _repo: repoPath,
104
332
  }));
105
- perRepo.push({ repo: repoPath, score: 0, processes: scored });
333
+ return { repo: repoPath, score: 0, processes: scored };
106
334
  }
107
335
  catch {
108
- perRepo.push({ repo: repoPath, score: 0, processes: [] });
336
+ return { repo: repoPath, score: 0, processes: [] };
109
337
  }
110
- }
338
+ }));
111
339
  const allProcesses = perRepo.flatMap((r) => r.processes);
112
340
  allProcesses.sort((a, b) => b._rrf_score - a._rrf_score);
113
341
  const topN = allProcesses.slice(0, limit);
@@ -123,15 +351,21 @@ export class GroupService {
123
351
  if (!name)
124
352
  return { error: 'name is required' };
125
353
  const groupDir = getGroupDir(getDefaultGitnexusDir(), name);
126
- const config = await loadGroupConfig(groupDir);
354
+ let config;
355
+ try {
356
+ config = await loadGroupConfig(groupDir);
357
+ }
358
+ catch (err) {
359
+ if (err instanceof GroupNotFoundError)
360
+ return { error: `Group "${name}" not found. Run group_list to see configured groups.` };
361
+ throw err;
362
+ }
127
363
  const registry = await readContractRegistry(groupDir);
128
364
  const repoStatuses = {};
129
- const fsp = await import('node:fs/promises');
130
- const pathMod = await import('node:path');
131
365
  for (const [repoPath, registryName] of Object.entries(config.repos)) {
132
366
  try {
133
367
  const repoObj = await this.port.resolveRepo(registryName);
134
- const metaPath = pathMod.join(repoObj.storagePath, 'meta.json');
368
+ const metaPath = path.join(repoObj.storagePath, 'meta.json');
135
369
  const metaRaw = await fsp.readFile(metaPath, 'utf-8').catch(() => '{}');
136
370
  const meta = JSON.parse(metaRaw);
137
371
  const staleness = meta.lastCommit
@@ -83,6 +83,8 @@ export interface RepoHandle {
83
83
  repoPath: string;
84
84
  storagePath: string;
85
85
  }
86
+ /** Why local impact or fan-out stopped early (e.g. wall-clock budget exhausted). */
87
+ export type GroupImpactTruncationReason = 'timeout' | 'partial';
86
88
  export interface GroupImpactResult {
87
89
  local: unknown;
88
90
  group: string;
@@ -97,6 +99,34 @@ export interface GroupImpactResult {
97
99
  cross_repo_hits: number;
98
100
  };
99
101
  risk: string;
102
+ /**
103
+ * Milliseconds budget applied to the **Phase 1 local impact** leg (`safeLocalImpact`).
104
+ * If the walk hits this wall first, expect `truncationReason: 'timeout'` and a partial `local` payload.
105
+ */
106
+ timeoutMs?: number;
107
+ /** Present when local impact or fan-out stopped early (timeout, graph cap, etc.). */
108
+ truncationReason?: GroupImpactTruncationReason;
109
+ /**
110
+ * Human-readable note when `crossDepth` was clamped (e.g. multi-hop not implemented yet).
111
+ */
112
+ crossDepthWarning?: string;
113
+ }
114
+ /** One repo’s `context` tool payload in a group-scoped context run. */
115
+ export interface GroupContextRepoEntry {
116
+ repoPath: string;
117
+ registryName: string;
118
+ payload: unknown;
119
+ }
120
+ /**
121
+ * Aggregated group `context`: explicit per-repo rows (no merged symbol payloads).
122
+ * Use top-level `error` only for unrecoverable failures, not for “no matches” or service scope misses.
123
+ */
124
+ export interface GroupContextResult {
125
+ group: string;
126
+ target?: string;
127
+ service?: string;
128
+ error?: string;
129
+ results: GroupContextRepoEntry[];
100
130
  }
101
131
  export interface CrossRepoImpact {
102
132
  repo: string;
@@ -1,5 +1,20 @@
1
1
  import Parser from 'tree-sitter';
2
- export interface ASTCache {
2
+ /**
3
+ * Minimal structural shape consumers need when reading Trees back
4
+ * through a phase-dependency boundary. Declared here so phases that
5
+ * receive ASTCache via `getPhaseOutput<...>` don't hand-roll their
6
+ * own inline structural types that silently drift when ASTCache's
7
+ * contract changes.
8
+ *
9
+ * Typed as `unknown` at the Tree boundary because consumers on the
10
+ * other side of the phase-output map don't share tree-sitter's type
11
+ * graph (e.g. COBOL's standalone processor).
12
+ */
13
+ export interface ASTCacheReader {
14
+ get(filePath: string): unknown;
15
+ clear(): void;
16
+ }
17
+ export interface ASTCache extends ASTCacheReader {
3
18
  get: (filePath: string) => Parser.Tree | undefined;
4
19
  set: (filePath: string, tree: Parser.Tree) => void;
5
20
  clear: () => void;
@@ -7,8 +7,20 @@ export const createASTCache = (maxSize = 50) => {
7
7
  max: effectiveMax,
8
8
  dispose: (tree) => {
9
9
  try {
10
- // NOTE: web-tree-sitter has tree.delete(); native tree-sitter trees are GC-managed.
11
- // Keep this try/catch so we don't crash on either runtime.
10
+ // NOTE: web-tree-sitter has tree.delete(); native tree-sitter
11
+ // trees are GC-managed and .delete is absent (no-op here).
12
+ //
13
+ // Single-owner invariant (load-bearing under WASM): a given
14
+ // Parser.Tree reference must live in AT MOST ONE ASTCache
15
+ // that disposes. The parse-phase chunk-local cache clears
16
+ // between chunks; the cross-phase `scopeTreeCache` (also an
17
+ // ASTCache today) holds the same Tree by reference. Under
18
+ // native tree-sitter this is benign (dispose is a no-op).
19
+ // If/when GitNexus adopts web-tree-sitter for sequential
20
+ // parsing, the cross-phase cache must either (a) skip
21
+ // writing Trees that are already owned by a disposing cache,
22
+ // or (b) use tree.copy() per entry. Failing to pick one
23
+ // will hand freed memory to scope-resolution.
12
24
  tree.delete?.();
13
25
  }
14
26
  catch (e) {
@@ -26,6 +26,7 @@ import { isLanguageAvailable, loadParser, loadLanguage } from '../tree-sitter/pa
26
26
  import { getProvider } from './languages/index.js';
27
27
  import { generateId } from '../../lib/utils.js';
28
28
  import { getLanguageFromFilename, SupportedLanguages } from '../../_shared/index.js';
29
+ import { isRegistryPrimary } from './registry-primary-flag.js';
29
30
  import { isVerboseIngestionEnabled } from './utils/verbose.js';
30
31
  import { yieldToEventLoop } from './utils/event-loop.js';
31
32
  import { FUNCTION_NODE_TYPES, findEnclosingClassId, findEnclosingClassInfo, genericFuncName, inferFunctionLabel, } from './utils/ast-helpers.js';
@@ -594,6 +595,9 @@ importedRawReturnTypesMap, heritageMap, bindingAccumulator) => {
594
595
  const language = getLanguageFromFilename(file.path);
595
596
  if (!language)
596
597
  continue;
598
+ // Registry-primary gate: scope-based phase owns CALLS for this lang.
599
+ if (isRegistryPrimary(language))
600
+ continue;
597
601
  if (!isLanguageAvailable(language)) {
598
602
  if (skippedByLang) {
599
603
  skippedByLang.set(language, (skippedByLang.get(language) ?? 0) + 1);
@@ -2162,6 +2166,11 @@ export const processCallsFromExtracted = async (graph, extractedCalls, ctx, onPr
2162
2166
  onProgress?.(filesProcessed, totalFiles);
2163
2167
  await yieldToEventLoop();
2164
2168
  }
2169
+ // Registry-primary gate: skip Python (etc.) entirely when the
2170
+ // scope-based phase owns CALLS for this language.
2171
+ const fileLanguage = getLanguageFromFilename(filePath);
2172
+ if (fileLanguage && isRegistryPrimary(fileLanguage))
2173
+ continue;
2165
2174
  ctx.enableCache(filePath);
2166
2175
  const widenCache = new Map();
2167
2176
  const receiverMap = fileReceiverTypes.get(filePath);
@@ -0,0 +1,88 @@
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
+ import type { ReferenceIndex } from '../../_shared/index.js';
48
+ import type { KnowledgeGraph } from '../graph/types.js';
49
+ import type { ScopeResolutionIndexes } from './model/scope-resolution-indexes.js';
50
+ export interface EmitStats {
51
+ readonly edgesEmitted: number;
52
+ /** References dropped because no caller def could be resolved. */
53
+ readonly skippedNoCaller: number;
54
+ /** References dropped because `toDef` was not found in the DefIndex. */
55
+ readonly skippedMissingTarget: number;
56
+ /** Scope nodes emitted — `0` unless `INGESTION_EMIT_SCOPES=1`. */
57
+ readonly scopeNodesEmitted: number;
58
+ /** Scope-tree structural edges emitted — `0` unless `INGESTION_EMIT_SCOPES=1`. */
59
+ readonly scopeEdgesEmitted: number;
60
+ }
61
+ export interface EmitReferencesInput {
62
+ readonly graph: KnowledgeGraph;
63
+ readonly scopes: ScopeResolutionIndexes;
64
+ readonly referenceIndex: ReferenceIndex;
65
+ /** Human-consumable label for the `reason` prefix. Defaults to `'scope-resolution'`. */
66
+ readonly sourceLabel?: string;
67
+ }
68
+ /**
69
+ * Drain `referenceIndex.bySourceScope` into graph edges.
70
+ *
71
+ * The scope-tree flush is controlled separately by
72
+ * `INGESTION_EMIT_SCOPES` — callers can run `emitReferencesToGraph`
73
+ * without scope-node emission or layer the two calls as needed.
74
+ */
75
+ export declare function emitReferencesToGraph(input: EmitReferencesInput): EmitStats;
76
+ /**
77
+ * Emit `Scope` nodes + `CONTAINS`/`DEFINES`/`IMPORTS` edges representing
78
+ * the lexical scope tree itself. Skipped unless `INGESTION_EMIT_SCOPES=1`
79
+ * at the public entry point; exported here for tests that want to
80
+ * exercise the path directly.
81
+ */
82
+ export declare function emitScopeGraph(input: {
83
+ readonly graph: KnowledgeGraph;
84
+ readonly scopes: ScopeResolutionIndexes;
85
+ }): {
86
+ readonly scopeNodesEmitted: number;
87
+ readonly scopeEdgesEmitted: number;
88
+ };