gitnexus 1.5.2 → 1.6.0

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 (207) hide show
  1. package/README.md +10 -0
  2. package/dist/_shared/graph/types.d.ts +1 -1
  3. package/dist/_shared/graph/types.d.ts.map +1 -1
  4. package/dist/_shared/index.d.ts +1 -0
  5. package/dist/_shared/index.d.ts.map +1 -1
  6. package/dist/_shared/language-detection.d.ts.map +1 -1
  7. package/dist/_shared/language-detection.js +2 -0
  8. package/dist/_shared/language-detection.js.map +1 -1
  9. package/dist/_shared/languages.d.ts +1 -0
  10. package/dist/_shared/languages.d.ts.map +1 -1
  11. package/dist/_shared/languages.js +1 -0
  12. package/dist/_shared/languages.js.map +1 -1
  13. package/dist/_shared/lbug/schema-constants.d.ts +1 -1
  14. package/dist/_shared/lbug/schema-constants.d.ts.map +1 -1
  15. package/dist/_shared/lbug/schema-constants.js +3 -1
  16. package/dist/_shared/lbug/schema-constants.js.map +1 -1
  17. package/dist/_shared/mro-strategy.d.ts +19 -0
  18. package/dist/_shared/mro-strategy.d.ts.map +1 -0
  19. package/dist/_shared/mro-strategy.js +2 -0
  20. package/dist/_shared/mro-strategy.js.map +1 -0
  21. package/dist/cli/ai-context.d.ts +1 -0
  22. package/dist/cli/ai-context.js +28 -4
  23. package/dist/cli/analyze.d.ts +2 -0
  24. package/dist/cli/analyze.js +2 -1
  25. package/dist/cli/group.d.ts +2 -0
  26. package/dist/cli/group.js +233 -0
  27. package/dist/cli/index.js +3 -0
  28. package/dist/cli/serve.js +4 -1
  29. package/dist/cli/setup.js +34 -3
  30. package/dist/cli/wiki.js +15 -44
  31. package/dist/config/ignore-service.js +8 -3
  32. package/dist/core/augmentation/engine.js +1 -1
  33. package/dist/core/git-staleness.d.ts +13 -0
  34. package/dist/core/git-staleness.js +29 -0
  35. package/dist/core/group/bridge-db.d.ts +82 -0
  36. package/dist/core/group/bridge-db.js +460 -0
  37. package/dist/core/group/bridge-schema.d.ts +27 -0
  38. package/dist/core/group/bridge-schema.js +55 -0
  39. package/dist/core/group/config-parser.d.ts +3 -0
  40. package/dist/core/group/config-parser.js +83 -0
  41. package/dist/core/group/contract-extractor.d.ts +7 -0
  42. package/dist/core/group/contract-extractor.js +1 -0
  43. package/dist/core/group/extractors/grpc-extractor.d.ts +16 -0
  44. package/dist/core/group/extractors/grpc-extractor.js +264 -0
  45. package/dist/core/group/extractors/http-route-extractor.d.ts +24 -0
  46. package/dist/core/group/extractors/http-route-extractor.js +428 -0
  47. package/dist/core/group/extractors/topic-extractor.d.ts +9 -0
  48. package/dist/core/group/extractors/topic-extractor.js +234 -0
  49. package/dist/core/group/matching.d.ts +13 -0
  50. package/dist/core/group/matching.js +198 -0
  51. package/dist/core/group/normalization.d.ts +3 -0
  52. package/dist/core/group/normalization.js +115 -0
  53. package/dist/core/group/service-boundary-detector.d.ts +8 -0
  54. package/dist/core/group/service-boundary-detector.js +155 -0
  55. package/dist/core/group/service.d.ts +46 -0
  56. package/dist/core/group/service.js +160 -0
  57. package/dist/core/group/storage.d.ts +9 -0
  58. package/dist/core/group/storage.js +91 -0
  59. package/dist/core/group/sync.d.ts +21 -0
  60. package/dist/core/group/sync.js +148 -0
  61. package/dist/core/group/types.d.ts +130 -0
  62. package/dist/core/group/types.js +1 -0
  63. package/dist/core/ingestion/binding-accumulator.d.ts +207 -0
  64. package/dist/core/ingestion/binding-accumulator.js +332 -0
  65. package/dist/core/ingestion/call-processor.d.ts +155 -24
  66. package/dist/core/ingestion/call-processor.js +1129 -247
  67. package/dist/core/ingestion/class-extractors/generic.d.ts +2 -0
  68. package/dist/core/ingestion/class-extractors/generic.js +135 -0
  69. package/dist/core/ingestion/class-types.d.ts +34 -0
  70. package/dist/core/ingestion/class-types.js +1 -0
  71. package/dist/core/ingestion/entry-point-scoring.d.ts +1 -0
  72. package/dist/core/ingestion/entry-point-scoring.js +1 -0
  73. package/dist/core/ingestion/field-extractors/configs/helpers.d.ts +5 -1
  74. package/dist/core/ingestion/field-extractors/configs/helpers.js +13 -3
  75. package/dist/core/ingestion/field-types.d.ts +2 -2
  76. package/dist/core/ingestion/filesystem-walker.js +8 -0
  77. package/dist/core/ingestion/framework-detection.d.ts +1 -0
  78. package/dist/core/ingestion/framework-detection.js +1 -0
  79. package/dist/core/ingestion/heritage-processor.d.ts +8 -15
  80. package/dist/core/ingestion/heritage-processor.js +15 -28
  81. package/dist/core/ingestion/import-processor.d.ts +1 -11
  82. package/dist/core/ingestion/import-processor.js +0 -12
  83. package/dist/core/ingestion/import-resolvers/utils.js +1 -0
  84. package/dist/core/ingestion/import-resolvers/vue.d.ts +8 -0
  85. package/dist/core/ingestion/import-resolvers/vue.js +9 -0
  86. package/dist/core/ingestion/language-provider.d.ts +6 -3
  87. package/dist/core/ingestion/languages/c-cpp.js +168 -1
  88. package/dist/core/ingestion/languages/csharp.js +20 -0
  89. package/dist/core/ingestion/languages/dart.js +26 -4
  90. package/dist/core/ingestion/languages/go.js +22 -0
  91. package/dist/core/ingestion/languages/index.d.ts +1 -0
  92. package/dist/core/ingestion/languages/index.js +2 -0
  93. package/dist/core/ingestion/languages/java.js +17 -0
  94. package/dist/core/ingestion/languages/kotlin.js +24 -1
  95. package/dist/core/ingestion/languages/php.js +23 -11
  96. package/dist/core/ingestion/languages/python.js +9 -0
  97. package/dist/core/ingestion/languages/ruby.js +28 -0
  98. package/dist/core/ingestion/languages/rust.js +38 -0
  99. package/dist/core/ingestion/languages/swift.js +31 -0
  100. package/dist/core/ingestion/languages/typescript.d.ts +1 -0
  101. package/dist/core/ingestion/languages/typescript.js +54 -1
  102. package/dist/core/ingestion/languages/vue.d.ts +13 -0
  103. package/dist/core/ingestion/languages/vue.js +81 -0
  104. package/dist/core/ingestion/method-extractors/configs/c-cpp.d.ts +3 -0
  105. package/dist/core/ingestion/method-extractors/configs/c-cpp.js +387 -0
  106. package/dist/core/ingestion/method-extractors/configs/csharp.js +5 -1
  107. package/dist/core/ingestion/method-extractors/configs/dart.d.ts +2 -0
  108. package/dist/core/ingestion/method-extractors/configs/dart.js +376 -0
  109. package/dist/core/ingestion/method-extractors/configs/go.d.ts +2 -0
  110. package/dist/core/ingestion/method-extractors/configs/go.js +176 -0
  111. package/dist/core/ingestion/method-extractors/configs/jvm.js +13 -4
  112. package/dist/core/ingestion/method-extractors/configs/php.d.ts +2 -0
  113. package/dist/core/ingestion/method-extractors/configs/php.js +304 -0
  114. package/dist/core/ingestion/method-extractors/configs/python.d.ts +2 -0
  115. package/dist/core/ingestion/method-extractors/configs/python.js +309 -0
  116. package/dist/core/ingestion/method-extractors/configs/ruby.d.ts +2 -0
  117. package/dist/core/ingestion/method-extractors/configs/ruby.js +285 -0
  118. package/dist/core/ingestion/method-extractors/configs/rust.d.ts +2 -0
  119. package/dist/core/ingestion/method-extractors/configs/rust.js +195 -0
  120. package/dist/core/ingestion/method-extractors/configs/swift.d.ts +2 -0
  121. package/dist/core/ingestion/method-extractors/configs/swift.js +277 -0
  122. package/dist/core/ingestion/method-extractors/configs/typescript-javascript.d.ts +3 -0
  123. package/dist/core/ingestion/method-extractors/configs/typescript-javascript.js +338 -0
  124. package/dist/core/ingestion/method-extractors/generic.js +38 -15
  125. package/dist/core/ingestion/method-types.d.ts +25 -0
  126. package/dist/core/ingestion/model/field-registry.d.ts +18 -0
  127. package/dist/core/ingestion/model/field-registry.js +22 -0
  128. package/dist/core/ingestion/model/heritage-map.d.ts +70 -0
  129. package/dist/core/ingestion/model/heritage-map.js +159 -0
  130. package/dist/core/ingestion/model/index.d.ts +20 -0
  131. package/dist/core/ingestion/model/index.js +41 -0
  132. package/dist/core/ingestion/model/method-registry.d.ts +62 -0
  133. package/dist/core/ingestion/model/method-registry.js +130 -0
  134. package/dist/core/ingestion/model/registration-table.d.ts +139 -0
  135. package/dist/core/ingestion/model/registration-table.js +224 -0
  136. package/dist/core/ingestion/model/resolution-context.d.ts +93 -0
  137. package/dist/core/ingestion/model/resolution-context.js +337 -0
  138. package/dist/core/ingestion/model/resolve.d.ts +56 -0
  139. package/dist/core/ingestion/model/resolve.js +242 -0
  140. package/dist/core/ingestion/model/semantic-model.d.ts +86 -0
  141. package/dist/core/ingestion/model/semantic-model.js +120 -0
  142. package/dist/core/ingestion/model/symbol-table.d.ts +222 -0
  143. package/dist/core/ingestion/model/symbol-table.js +206 -0
  144. package/dist/core/ingestion/model/type-registry.d.ts +39 -0
  145. package/dist/core/ingestion/model/type-registry.js +62 -0
  146. package/dist/core/ingestion/mro-processor.d.ts +4 -3
  147. package/dist/core/ingestion/mro-processor.js +310 -106
  148. package/dist/core/ingestion/parsing-processor.d.ts +5 -4
  149. package/dist/core/ingestion/parsing-processor.js +210 -85
  150. package/dist/core/ingestion/pipeline.d.ts +2 -0
  151. package/dist/core/ingestion/pipeline.js +192 -68
  152. package/dist/core/ingestion/tree-sitter-queries.d.ts +6 -6
  153. package/dist/core/ingestion/tree-sitter-queries.js +37 -0
  154. package/dist/core/ingestion/type-env.d.ts +15 -2
  155. package/dist/core/ingestion/type-env.js +163 -102
  156. package/dist/core/ingestion/type-extractors/csharp.js +17 -0
  157. package/dist/core/ingestion/type-extractors/jvm.js +11 -0
  158. package/dist/core/ingestion/type-extractors/php.js +0 -55
  159. package/dist/core/ingestion/type-extractors/ruby.js +0 -32
  160. package/dist/core/ingestion/type-extractors/swift.js +13 -0
  161. package/dist/core/ingestion/type-extractors/types.d.ts +8 -8
  162. package/dist/core/ingestion/type-extractors/typescript.js +66 -69
  163. package/dist/core/ingestion/utils/ast-helpers.d.ts +33 -43
  164. package/dist/core/ingestion/utils/ast-helpers.js +129 -565
  165. package/dist/core/ingestion/utils/method-props.d.ts +32 -0
  166. package/dist/core/ingestion/utils/method-props.js +147 -0
  167. package/dist/core/ingestion/vue-sfc-extractor.d.ts +44 -0
  168. package/dist/core/ingestion/vue-sfc-extractor.js +94 -0
  169. package/dist/core/ingestion/workers/parse-worker.d.ts +31 -19
  170. package/dist/core/ingestion/workers/parse-worker.js +463 -198
  171. package/dist/core/lbug/lbug-adapter.d.ts +6 -0
  172. package/dist/core/lbug/lbug-adapter.js +68 -3
  173. package/dist/core/lbug/pool-adapter.d.ts +76 -0
  174. package/dist/core/lbug/pool-adapter.js +522 -0
  175. package/dist/core/run-analyze.d.ts +2 -0
  176. package/dist/core/run-analyze.js +1 -1
  177. package/dist/core/search/bm25-index.js +1 -1
  178. package/dist/core/tree-sitter/parser-loader.js +1 -0
  179. package/dist/core/wiki/graph-queries.js +1 -1
  180. package/dist/core/wiki/html-viewer.js +6 -4
  181. package/dist/core/wiki/llm-client.js +4 -6
  182. package/dist/mcp/core/embedder.js +6 -5
  183. package/dist/mcp/core/lbug-adapter.d.ts +3 -63
  184. package/dist/mcp/core/lbug-adapter.js +3 -484
  185. package/dist/mcp/local/local-backend.d.ts +31 -2
  186. package/dist/mcp/local/local-backend.js +255 -46
  187. package/dist/mcp/resources.js +5 -4
  188. package/dist/mcp/staleness.d.ts +3 -13
  189. package/dist/mcp/staleness.js +2 -31
  190. package/dist/mcp/tools.js +80 -4
  191. package/dist/server/analyze-job.d.ts +2 -0
  192. package/dist/server/analyze-job.js +4 -0
  193. package/dist/server/api.d.ts +20 -1
  194. package/dist/server/api.js +306 -71
  195. package/dist/server/git-clone.d.ts +2 -1
  196. package/dist/server/git-clone.js +98 -5
  197. package/dist/storage/git.d.ts +13 -0
  198. package/dist/storage/git.js +25 -0
  199. package/dist/storage/repo-manager.js +1 -1
  200. package/package.json +8 -2
  201. package/scripts/patch-tree-sitter-swift.cjs +78 -0
  202. package/dist/core/ingestion/named-binding-processor.d.ts +0 -18
  203. package/dist/core/ingestion/named-binding-processor.js +0 -42
  204. package/dist/core/ingestion/resolution-context.d.ts +0 -58
  205. package/dist/core/ingestion/resolution-context.js +0 -135
  206. package/dist/core/ingestion/symbol-table.d.ts +0 -79
  207. package/dist/core/ingestion/symbol-table.js +0 -115
@@ -1,22 +1,24 @@
1
1
  import { createKnowledgeGraph } from '../graph/graph.js';
2
+ import { BindingAccumulator, enrichExportedTypeMap, } from './binding-accumulator.js';
2
3
  import { processStructure } from './structure-processor.js';
3
4
  import { processMarkdown } from './markdown-processor.js';
4
5
  import { processCobol, isCobolFile, isJclFile } from './cobol-processor.js';
5
6
  import { processParsing } from './parsing-processor.js';
6
7
  import { processImports, processImportsFromExtracted, buildImportResolutionContext, } from './import-processor.js';
7
8
  import { EMPTY_INDEX } from './import-resolvers/utils.js';
8
- import { processCalls, processCallsFromExtracted, processAssignmentsFromExtracted, processRoutesFromExtracted, processNextjsFetchRoutes, extractFetchCallsFromFiles, seedCrossFileReceiverTypes, buildImportedReturnTypes, buildImportedRawReturnTypes, buildExportedTypeMapFromGraph, buildImplementorMap, mergeImplementorMaps, } from './call-processor.js';
9
+ import { processCalls, processCallsFromExtracted, processAssignmentsFromExtracted, processRoutesFromExtracted, processNextjsFetchRoutes, extractFetchCallsFromFiles, seedCrossFileReceiverTypes, buildImportedReturnTypes, buildImportedRawReturnTypes, buildExportedTypeMapFromGraph, } from './call-processor.js';
10
+ import { buildHeritageMap } from './model/heritage-map.js';
9
11
  import { nextjsFileToRouteURL, normalizeFetchURL } from './route-extractors/nextjs.js';
10
12
  import { expoFileToRouteURL } from './route-extractors/expo.js';
11
13
  import { phpFileToRouteURL } from './route-extractors/php.js';
12
14
  import { extractResponseShapes, extractPHPResponseShapes, } from './route-extractors/response-shapes.js';
13
15
  import { extractMiddlewareChain, extractNextjsMiddlewareConfig, compileMatcher, compiledMatcherMatchesRoute, } from './route-extractors/middleware.js';
14
16
  import { generateId } from '../../lib/utils.js';
15
- import { processHeritage, processHeritageFromExtracted, extractExtractedHeritageFromFiles, } from './heritage-processor.js';
17
+ import { processHeritage, processHeritageFromExtracted, extractExtractedHeritageFromFiles, getHeritageStrategyForLanguage, } from './heritage-processor.js';
16
18
  import { computeMRO } from './mro-processor.js';
17
19
  import { processCommunities } from './community-processor.js';
18
20
  import { processProcesses } from './process-processor.js';
19
- import { createResolutionContext } from './resolution-context.js';
21
+ import { createResolutionContext } from './model/resolution-context.js';
20
22
  import { createASTCache } from './ast-cache.js';
21
23
  import { getLanguageFromFilename } from '../../_shared/index.js';
22
24
  import { walkRepositoryPaths, readFileContents } from './filesystem-walker.js';
@@ -252,7 +254,7 @@ async function runCrossFileBindingPropagation(graph, ctx, exportedTypeMap, allPa
252
254
  // For the worker path, buildTypeEnv runs inside workers without SymbolTable,
253
255
  // so exported bindings must be collected from graph + SymbolTable in main thread.
254
256
  if (exportedTypeMap.size === 0 && graph.nodeCount > 0) {
255
- const graphExports = buildExportedTypeMapFromGraph(graph, ctx.symbols);
257
+ const graphExports = buildExportedTypeMapFromGraph(graph, ctx.model.symbols);
256
258
  for (const [fp, exports] of graphExports)
257
259
  exportedTypeMap.set(fp, exports);
258
260
  }
@@ -278,7 +280,7 @@ async function runCrossFileBindingPropagation(graph, ctx, exportedTypeMap, allPa
278
280
  filesWithGaps++;
279
281
  break;
280
282
  }
281
- const def = ctx.symbols.lookupExactFull(binding.sourcePath, binding.exportedName);
283
+ const def = ctx.model.symbols.lookupExactFull(binding.sourcePath, binding.exportedName);
282
284
  if (def?.returnType) {
283
285
  filesWithGaps++;
284
286
  break;
@@ -321,8 +323,8 @@ async function runCrossFileBindingPropagation(graph, ctx, exportedTypeMap, allPa
321
323
  seeded.set(localName, type);
322
324
  }
323
325
  }
324
- const importedReturns = buildImportedReturnTypes(filePath, ctx.namedImportMap, ctx.symbols);
325
- const importedRawReturns = buildImportedRawReturnTypes(filePath, ctx.namedImportMap, ctx.symbols);
326
+ const importedReturns = buildImportedReturnTypes(filePath, ctx.namedImportMap, ctx.model.symbols);
327
+ const importedRawReturns = buildImportedRawReturnTypes(filePath, ctx.namedImportMap, ctx.model.symbols);
326
328
  if (seeded.size === 0 && importedReturns.size === 0)
327
329
  continue;
328
330
  if (!allPathSet.has(filePath))
@@ -480,8 +482,8 @@ async function runScanAndStructure(repoPath, graph, onProgress) {
480
482
  * Follow-up from PR review: MethodExtractor (FieldExtractor parity) and optional
481
483
  * METHOD_IMPLEMENTS graph edges to make dispatch queryable without an in-memory map.
482
484
  */
483
- async function runChunkedParseAndResolve(graph, ctx, scannedFiles, allPaths, totalFiles, repoPath, pipelineStart, onProgress) {
484
- const symbolTable = ctx.symbols;
485
+ async function runChunkedParseAndResolve(graph, ctx, scannedFiles, allPaths, totalFiles, repoPath, pipelineStart, onProgress, options) {
486
+ const symbolTable = ctx.model.symbols;
485
487
  const parseableScanned = scannedFiles.filter((f) => {
486
488
  const lang = getLanguageFromFilename(f.path);
487
489
  return lang && isLanguageAvailable(lang);
@@ -538,7 +540,8 @@ async function runChunkedParseAndResolve(graph, ctx, scannedFiles, allPaths, tot
538
540
  const totalBytes = parseableScanned.reduce((s, f) => s + f.size, 0);
539
541
  // Create worker pool once, reuse across chunks
540
542
  let workerPool;
541
- if (totalParseable >= MIN_FILES_FOR_WORKERS || totalBytes >= MIN_BYTES_FOR_WORKERS) {
543
+ if (!options?.skipWorkers &&
544
+ (totalParseable >= MIN_FILES_FOR_WORKERS || totalBytes >= MIN_BYTES_FOR_WORKERS)) {
542
545
  try {
543
546
  let workerUrl = new URL('./workers/parse-worker.js', import.meta.url);
544
547
  // When running under vitest, import.meta.url points to src/ where no .js exists.
@@ -578,7 +581,7 @@ async function runChunkedParseAndResolve(graph, ctx, scannedFiles, allPaths, tot
578
581
  // Phase 14: Collect exported type bindings for cross-file propagation
579
582
  const exportedTypeMap = new Map();
580
583
  // Accumulate file-scope TypeEnv bindings from workers (closes worker/sequential quality gap)
581
- const workerTypeEnvBindings = [];
584
+ const bindingAccumulator = new BindingAccumulator();
582
585
  // Accumulate fetch() calls from workers for Next.js route matching
583
586
  const allFetchCalls = [];
584
587
  // Accumulate framework-extracted routes (Laravel, etc.) for Route node creation
@@ -649,11 +652,15 @@ async function runChunkedParseAndResolve(graph, ctx, scannedFiles, allPaths, tot
649
652
  console.log(`🔗 E1: Seeded ${enrichedCount} cross-file receiver types (chunk ${chunkIdx + 1})`);
650
653
  }
651
654
  }
652
- deferredWorkerCalls.push(...chunkWorkerData.calls);
653
- deferredWorkerHeritage.push(...chunkWorkerData.heritage);
654
- deferredConstructorBindings.push(...chunkWorkerData.constructorBindings);
655
+ for (const _item of chunkWorkerData.calls)
656
+ deferredWorkerCalls.push(_item);
657
+ for (const _item of chunkWorkerData.heritage)
658
+ deferredWorkerHeritage.push(_item);
659
+ for (const _item of chunkWorkerData.constructorBindings)
660
+ deferredConstructorBindings.push(_item);
655
661
  if (chunkWorkerData.assignments?.length) {
656
- deferredAssignments.push(...chunkWorkerData.assignments);
662
+ for (const _item of chunkWorkerData.assignments)
663
+ deferredAssignments.push(_item);
657
664
  }
658
665
  // Heritage + Routes — calls deferred until all chunks have contributed heritage
659
666
  // (complete implementor map for interface dispatch).
@@ -685,25 +692,56 @@ async function runChunkedParseAndResolve(graph, ctx, scannedFiles, allPaths, tot
685
692
  });
686
693
  }),
687
694
  ]);
688
- // Collect TypeEnv file-scope bindings for exported type enrichment
689
- if (chunkWorkerData.typeEnvBindings?.length) {
690
- workerTypeEnvBindings.push(...chunkWorkerData.typeEnvBindings);
695
+ // Collect file-scope bindings into BindingAccumulator. The worker
696
+ // IPC payload carries only file-scope entries (`scope = ''`
697
+ // hardcoded here). See the FileScopeBindings JSDoc in
698
+ // parse-worker.ts for the rationale and Phase 9 reversion path.
699
+ //
700
+ // Defensive validation at the IPC boundary: silently skip entries
701
+ // with non-string varName/typeName. If a future worker regression
702
+ // (or a Phase 9 reversion mistake that emits 3-tuples into the
703
+ // 2-tuple consumer) produces malformed data, logging is better
704
+ // than silently writing `undefined` into the enrichment map.
705
+ if (chunkWorkerData.fileScopeBindings?.length) {
706
+ for (const { filePath, bindings } of chunkWorkerData.fileScopeBindings) {
707
+ if (typeof filePath !== 'string' || filePath.length === 0)
708
+ continue;
709
+ if (!Array.isArray(bindings))
710
+ continue;
711
+ const entries = [];
712
+ for (const tuple of bindings) {
713
+ if (!Array.isArray(tuple) || tuple.length !== 2)
714
+ continue;
715
+ const [varName, typeName] = tuple;
716
+ if (typeof varName !== 'string' || typeof typeName !== 'string')
717
+ continue;
718
+ entries.push({ scope: '', varName, typeName });
719
+ }
720
+ if (entries.length > 0) {
721
+ bindingAccumulator.appendFile(filePath, entries);
722
+ }
723
+ }
691
724
  }
692
725
  // Collect fetch() calls for Next.js route matching
693
726
  if (chunkWorkerData.fetchCalls?.length) {
694
- allFetchCalls.push(...chunkWorkerData.fetchCalls);
727
+ for (const _item of chunkWorkerData.fetchCalls)
728
+ allFetchCalls.push(_item);
695
729
  }
696
730
  if (chunkWorkerData.routes?.length) {
697
- allExtractedRoutes.push(...chunkWorkerData.routes);
731
+ for (const _item of chunkWorkerData.routes)
732
+ allExtractedRoutes.push(_item);
698
733
  }
699
734
  if (chunkWorkerData.decoratorRoutes?.length) {
700
- allDecoratorRoutes.push(...chunkWorkerData.decoratorRoutes);
735
+ for (const _item of chunkWorkerData.decoratorRoutes)
736
+ allDecoratorRoutes.push(_item);
701
737
  }
702
738
  if (chunkWorkerData.toolDefs?.length) {
703
- allToolDefs.push(...chunkWorkerData.toolDefs);
739
+ for (const _item of chunkWorkerData.toolDefs)
740
+ allToolDefs.push(_item);
704
741
  }
705
742
  if (chunkWorkerData.ormQueries?.length) {
706
- allORMQueries.push(...chunkWorkerData.ormQueries);
743
+ for (const _item of chunkWorkerData.ormQueries)
744
+ allORMQueries.push(_item);
707
745
  }
708
746
  }
709
747
  else {
@@ -715,10 +753,10 @@ async function runChunkedParseAndResolve(graph, ctx, scannedFiles, allPaths, tot
715
753
  astCache.clear();
716
754
  // chunkContents + chunkFiles + chunkWorkerData go out of scope → GC reclaims
717
755
  }
718
- // Complete implementor map from all worker heritage, then resolve CALLS once (interface dispatch).
719
- const fullWorkerImplementorMap = deferredWorkerHeritage.length > 0
720
- ? buildImplementorMap(deferredWorkerHeritage, ctx)
721
- : new Map();
756
+ // Build unified HeritageMap (parent lookup + implementor index) after all chunks.
757
+ const fullWorkerHeritageMap = deferredWorkerHeritage.length > 0
758
+ ? buildHeritageMap(deferredWorkerHeritage, ctx, getHeritageStrategyForLanguage)
759
+ : undefined;
722
760
  if (deferredWorkerCalls.length > 0) {
723
761
  await processCallsFromExtracted(graph, deferredWorkerCalls, ctx, (current, total) => {
724
762
  onProgress({
@@ -732,10 +770,23 @@ async function runChunkedParseAndResolve(graph, ctx, scannedFiles, allPaths, tot
732
770
  nodesCreated: graph.nodeCount,
733
771
  },
734
772
  });
735
- }, deferredConstructorBindings.length > 0 ? deferredConstructorBindings : undefined, fullWorkerImplementorMap);
773
+ }, deferredConstructorBindings.length > 0 ? deferredConstructorBindings : undefined, fullWorkerHeritageMap,
774
+ // Phase 9: pass the accumulator so processCallsFromExtracted can fall back
775
+ // to file-scope TypeEnv bindings when the SymbolTable lacks a return type
776
+ // for a cross-file callee (e.g. var x = getUser() → x: User).
777
+ //
778
+ // Lifecycle ordering: the accumulator is populated but NOT yet finalized
779
+ // at this seam. finalize() is called later (after the sequential-path
780
+ // processCalls which also appends via typeEnv.flush()). Moving finalize()
781
+ // before this call would break sequential-path repos. Pre-finalize reads
782
+ // are safe because finalize() is a write-lock-only operation with no side
783
+ // effects on stored data. All worker-path appendFile calls complete in the
784
+ // chunk loop above, so every worker-contributed binding is available via
785
+ // fileScopeGet().
786
+ bindingAccumulator);
736
787
  }
737
788
  if (deferredAssignments.length > 0) {
738
- processAssignmentsFromExtracted(graph, deferredAssignments, ctx, deferredConstructorBindings.length > 0 ? deferredConstructorBindings : undefined);
789
+ processAssignmentsFromExtracted(graph, deferredAssignments, ctx, deferredConstructorBindings.length > 0 ? deferredConstructorBindings : undefined, bindingAccumulator);
739
790
  }
740
791
  }
741
792
  finally {
@@ -746,18 +797,40 @@ async function runChunkedParseAndResolve(graph, ctx, scannedFiles, allPaths, tot
746
797
  // before any call resolution — same rationale as the worker-path inline synthesis.
747
798
  if (sequentialChunkPaths.length > 0)
748
799
  synthesizeWildcardImportBindings(graph, ctx);
749
- // Merge implementor-map deltas per chunk (O(heritage per chunk)), not O(|edges|) graph scans
750
- // per chunk mirrors worker-path deferred heritage without re-iterating all relationships.
751
- const sequentialImplementorMap = new Map();
800
+ // Pass 1: Extract heritage from all sequential chunks.
801
+ // Heritage must be fully accumulated BEFORE call resolution so the HeritageMap
802
+ // has the complete ancestor chain and implementor index (same constraint as
803
+ // the worker path).
804
+ //
805
+ // File contents are read once here and cached for Pass 2 to avoid a 2× I/O
806
+ // cost on the sequential path (ASTs are intentionally NOT cached — rebuilding
807
+ // them in Pass 2 keeps peak memory bounded to one chunk at a time).
808
+ const allSequentialHeritage = [];
809
+ const cachedSequentialChunkFiles = [];
752
810
  for (const chunkPaths of sequentialChunkPaths) {
753
811
  const chunkContents = await readFileContents(repoPath, chunkPaths);
754
812
  const chunkFiles = chunkPaths
755
813
  .filter((p) => chunkContents.has(p))
756
814
  .map((p) => ({ path: p, content: chunkContents.get(p) }));
815
+ cachedSequentialChunkFiles.push(chunkFiles);
757
816
  astCache = createASTCache(chunkFiles.length);
758
817
  const sequentialHeritage = await extractExtractedHeritageFromFiles(chunkFiles, astCache);
759
- mergeImplementorMaps(sequentialImplementorMap, buildImplementorMap(sequentialHeritage, ctx));
760
- const rubyHeritage = await processCalls(graph, chunkFiles, astCache, ctx, undefined, exportedTypeMap, undefined, undefined, undefined, sequentialImplementorMap);
818
+ // Manual loop (not spread) — `push(...arr)` blows the stack on very large
819
+ // arrays, see #650. Pay the explicit iteration cost for safety.
820
+ for (const h of sequentialHeritage)
821
+ allSequentialHeritage.push(h);
822
+ astCache.clear();
823
+ }
824
+ // Build unified HeritageMap from all sequential heritage (parent lookup + implementor index).
825
+ const sequentialHeritageMap = allSequentialHeritage.length > 0
826
+ ? buildHeritageMap(allSequentialHeritage, ctx, getHeritageStrategyForLanguage)
827
+ : undefined;
828
+ // Pass 2: Process calls, heritage edges, fetch calls, and ORM queries per chunk.
829
+ // Reuse the file contents cached in Pass 1 instead of re-reading from disk.
830
+ for (let chunkIdx = 0; chunkIdx < sequentialChunkPaths.length; chunkIdx++) {
831
+ const chunkFiles = cachedSequentialChunkFiles[chunkIdx];
832
+ astCache = createASTCache(chunkFiles.length);
833
+ const rubyHeritage = await processCalls(graph, chunkFiles, astCache, ctx, undefined, exportedTypeMap, undefined, undefined, undefined, sequentialHeritageMap, bindingAccumulator);
761
834
  await processHeritage(graph, chunkFiles, astCache, ctx);
762
835
  if (rubyHeritage.length > 0) {
763
836
  await processHeritageFromExtracted(graph, rubyHeritage, ctx);
@@ -765,13 +838,18 @@ async function runChunkedParseAndResolve(graph, ctx, scannedFiles, allPaths, tot
765
838
  // Extract fetch() calls for Next.js route matching (sequential path)
766
839
  const chunkFetchCalls = await extractFetchCallsFromFiles(chunkFiles, astCache);
767
840
  if (chunkFetchCalls.length > 0) {
768
- allFetchCalls.push(...chunkFetchCalls);
841
+ for (const _item of chunkFetchCalls)
842
+ allFetchCalls.push(_item);
769
843
  }
770
844
  // Extract ORM queries (sequential path)
771
845
  for (const f of chunkFiles) {
772
846
  extractORMQueriesInline(f.path, f.content, allORMQueries);
773
847
  }
774
848
  astCache.clear();
849
+ // Release cached chunk content as soon as Pass 2 finishes with it so the
850
+ // Pass-1 content map drains incrementally rather than being held for the
851
+ // full duration of Pass 2.
852
+ cachedSequentialChunkFiles[chunkIdx] = [];
775
853
  }
776
854
  // Log resolution cache stats
777
855
  if (isDev) {
@@ -780,36 +858,28 @@ async function runChunkedParseAndResolve(graph, ctx, scannedFiles, allPaths, tot
780
858
  const hitRate = total > 0 ? ((rcStats.cacheHits / total) * 100).toFixed(1) : '0';
781
859
  console.log(`🔍 Resolution cache: ${rcStats.cacheHits} hits, ${rcStats.cacheMisses} misses (${hitRate}% hit rate)`);
782
860
  }
783
- // ── Worker path quality enrichment: merge TypeEnv file-scope bindings into ExportedTypeMap ──
784
- // Workers return file-scope bindings from their TypeEnv fixpoint (includes inferred types
785
- // like `const config = getConfig()` Config). Filter by graph isExported to match
786
- // the sequential path's collectExportedBindings behavior.
787
- if (workerTypeEnvBindings.length > 0) {
788
- let enriched = 0;
789
- for (const { filePath, bindings } of workerTypeEnvBindings) {
790
- for (const [name, type] of bindings) {
791
- // Verify the symbol is exported via graph node
792
- const nodeId = `Function:${filePath}:${name}`;
793
- const varNodeId = `Variable:${filePath}:${name}`;
794
- const constNodeId = `Const:${filePath}:${name}`;
795
- const node = graph.getNode(nodeId) ?? graph.getNode(varNodeId) ?? graph.getNode(constNodeId);
796
- if (!node?.properties?.isExported)
797
- continue;
798
- let fileExports = exportedTypeMap.get(filePath);
799
- if (!fileExports) {
800
- fileExports = new Map();
801
- exportedTypeMap.set(filePath, fileExports);
802
- }
803
- // Don't overwrite existing entries (Tier 0 from SymbolTable is authoritative)
804
- if (!fileExports.has(name)) {
805
- fileExports.set(name, type);
806
- enriched++;
807
- }
808
- }
809
- }
810
- if (isDev && enriched > 0) {
811
- console.log(`🔗 Worker TypeEnv enrichment: ${enriched} fixpoint-inferred exports added to ExportedTypeMap`);
812
- }
861
+ // ── Finalize the accumulator before the read phase begins. All worker-path
862
+ // appends (line ~934) and sequential-path flushes (via `processCalls`
863
+ // `typeEnv.flush()` earlier in this function) have completed by here,
864
+ // so the finalize-write-lock is correct at this seam. Making the
865
+ // lifecycle contract explicit `append → finalize → consume → dispose`.
866
+ // Previously `finalize()` was called much later in `runPipelineFromRepo`
867
+ // after the enrichment loop had already read the mutable accumulator.
868
+ bindingAccumulator.finalize();
869
+ // ── Worker path quality enrichment: merge file-scope bindings into ExportedTypeMap ──
870
+ // Counterpart to `collectExportedBindings()` in call-processor.ts which
871
+ // handles the sequential path (main thread, full SymbolTable access).
872
+ // This call handles the worker path via the accumulator. Both sites
873
+ // populate the same `exportedTypeMap` with subtly different export-check
874
+ // semantics — sequential uses SymbolTable + graph lookup, `enrichExportedTypeMap`
875
+ // uses a three-candidate-ID graph lookup. They must stay in sync until
876
+ // Phase 9 unifies them. If you edit one, check the other.
877
+ //
878
+ // The enrichment loop itself lives in `binding-accumulator.ts` so tests
879
+ // can exercise the real production code instead of reimplementing it.
880
+ const enriched = enrichExportedTypeMap(bindingAccumulator, graph, exportedTypeMap);
881
+ if (isDev && enriched > 0) {
882
+ console.log(`🔗 Worker TypeEnv enrichment: ${enriched} fixpoint-inferred exports added to ExportedTypeMap`);
813
883
  }
814
884
  // ── Final synthesis pass for whole-module-import languages ──
815
885
  // Per-chunk synthesis (above) already ran incrementally. This final pass ensures
@@ -832,13 +902,14 @@ async function runChunkedParseAndResolve(graph, ctx, scannedFiles, allPaths, tot
832
902
  allDecoratorRoutes,
833
903
  allToolDefs,
834
904
  allORMQueries,
905
+ bindingAccumulator,
835
906
  };
836
907
  }
837
908
  /**
838
909
  * Post-parse graph analysis: MRO, community detection, process extraction.
839
910
  *
840
911
  * @reads graph (all nodes and relationships from parse + resolve phases)
841
- * @writes graph (Community nodes, Process nodes, MEMBER_OF edges, STEP_IN_PROCESS edges, OVERRIDES edges)
912
+ * @writes graph (Community nodes, Process nodes, MEMBER_OF edges, STEP_IN_PROCESS edges, METHOD_OVERRIDES edges)
842
913
  */
843
914
  async function runGraphAnalysisPhases(graph, totalFiles, onProgress, routeRegistry, toolDefs) {
844
915
  // ── Phase 4.5: Method Resolution Order ──────────────────────────────
@@ -850,7 +921,7 @@ async function runGraphAnalysisPhases(graph, totalFiles, onProgress, routeRegist
850
921
  });
851
922
  const mroResult = computeMRO(graph);
852
923
  if (isDev && mroResult.entries.length > 0) {
853
- console.log(`🔀 MRO: ${mroResult.entries.length} classes analyzed, ${mroResult.ambiguityCount} ambiguities found, ${mroResult.overrideEdges} OVERRIDES edges`);
924
+ console.log(`🔀 MRO: ${mroResult.entries.length} classes analyzed, ${mroResult.ambiguityCount} ambiguities, ${mroResult.overrideEdges} METHOD_OVERRIDES, ${mroResult.methodImplementsEdges} METHOD_IMPLEMENTS`);
854
925
  }
855
926
  // ── Phase 5: Communities ───────────────────────────────────────────
856
927
  onProgress({
@@ -1023,11 +1094,24 @@ export const runPipelineFromRepo = async (repoPath, onProgress, options) => {
1023
1094
  const graph = createKnowledgeGraph();
1024
1095
  const ctx = createResolutionContext();
1025
1096
  const pipelineStart = Date.now();
1097
+ // Hoisted reference for error-path cleanup. The accumulator is normally
1098
+ // disposed at the happy-path seam after the dev telemetry log, but if any
1099
+ // step between the runChunkedParseAndResolve return and that seam throws
1100
+ // (ORM processing, tool node creation, Phase 14, graph analysis), the
1101
+ // catch handler disposes it here so the heap footprint does not leak
1102
+ // through the rethrow. See binding-accumulator.ts dispose() JSDoc for the
1103
+ // lifecycle contract.
1104
+ let bindingAccumulatorForCleanup;
1026
1105
  try {
1027
1106
  // Phase 1+2: Scan paths, build structure, process markdown
1028
1107
  const { scannedFiles, allPaths, totalFiles } = await runScanAndStructure(repoPath, graph, onProgress);
1029
1108
  // Phase 3+4: Chunked parse + resolve (imports, calls, heritage, routes)
1030
- const { exportedTypeMap, allFetchCalls, allExtractedRoutes, allDecoratorRoutes, allToolDefs, allORMQueries, } = await runChunkedParseAndResolve(graph, ctx, scannedFiles, allPaths, totalFiles, repoPath, pipelineStart, onProgress);
1109
+ const { exportedTypeMap, allFetchCalls, allExtractedRoutes, allDecoratorRoutes, allToolDefs, allORMQueries, bindingAccumulator, } = await runChunkedParseAndResolve(graph, ctx, scannedFiles, allPaths, totalFiles, repoPath, pipelineStart, onProgress, options);
1110
+ // Track the accumulator for error-path cleanup — the happy-path dispose
1111
+ // is still at the post-telemetry seam below, this reference is only
1112
+ // consulted by the catch handler if any step between here and there
1113
+ // throws.
1114
+ bindingAccumulatorForCleanup = bindingAccumulator;
1031
1115
  const routeRegistry = new Map();
1032
1116
  // Detect Expo Router app/ roots vs Next.js app/ roots (monorepo-safe).
1033
1117
  const expoAppRoots = new Set();
@@ -1303,6 +1387,40 @@ export const runPipelineFromRepo = async (repoPath, onProgress, options) => {
1303
1387
  if (allORMQueries.length > 0) {
1304
1388
  processORMQueries(graph, allORMQueries, isDev);
1305
1389
  }
1390
+ // `bindingAccumulator.finalize()` was moved inside `runChunkedParseAndResolve`
1391
+ // to immediately precede the enrichment loop — see the comment there for
1392
+ // the ordering rationale. By the time execution
1393
+ // reaches this point, the accumulator has already been finalized, consumed
1394
+ // by the enrichment loop, and is ready for dispose() below after the dev
1395
+ // telemetry log captures peak state.
1396
+ if (isDev) {
1397
+ if (bindingAccumulator.totalBindings > 0) {
1398
+ const memKB = Math.round(bindingAccumulator.estimateMemoryBytes() / 1024);
1399
+ console.log(`📦 BindingAccumulator: ${bindingAccumulator.totalBindings} bindings across ${bindingAccumulator.fileCount} files (~${memKB} KB)`);
1400
+ }
1401
+ else if (totalFiles > 0) {
1402
+ // Zero-binding signal: if the pipeline parsed files but the
1403
+ // accumulator is empty, something upstream dropped all bindings.
1404
+ // Flag it so operators can spot a regression (e.g. a worker path
1405
+ // that accidentally emits empty fileScopeBindings arrays for every
1406
+ // file, or a TypeEnv build failure). Dev-mode only.
1407
+ console.log(`📦 BindingAccumulator: EMPTY — 0 bindings across 0 files despite ${totalFiles} parsed files. If the codebase has typed bindings, this indicates an upstream regression.`);
1408
+ }
1409
+ }
1410
+ // Release the accumulator's heap footprint now. Both consumers of the
1411
+ // accumulator have completed:
1412
+ // 1. ExportedTypeMap enrichment loop (enrichExportedTypeMap, above).
1413
+ // 2. Phase 9: processCallsFromExtracted in runChunkedParseAndResolve,
1414
+ // which uses the accumulator as a BindingAccumulator fallback for
1415
+ // cross-file return types when the SymbolTable has no returnType.
1416
+ // Phase 14 (runCrossFileBindingPropagation) and runGraphAnalysisPhases
1417
+ // do not read the accumulator — keeping it alive through those long-
1418
+ // running phases pins heap for no reason.
1419
+ bindingAccumulator.dispose();
1420
+ // Happy-path dispose completed — clear the cleanup ref so the catch
1421
+ // handler doesn't attempt a second (harmless but noisy) dispose if a
1422
+ // later phase throws.
1423
+ bindingAccumulatorForCleanup = undefined;
1306
1424
  // ── Phase 14: Cross-file binding propagation (topological level sort) ──
1307
1425
  await runCrossFileBindingPropagation(graph, ctx, exportedTypeMap, allPaths, totalFiles, repoPath, pipelineStart, onProgress);
1308
1426
  // Post-parse graph analysis (MRO, communities, processes)
@@ -1328,6 +1446,12 @@ export const runPipelineFromRepo = async (repoPath, onProgress, options) => {
1328
1446
  return { graph, repoPath, totalFileCount: totalFiles, communityResult, processResult };
1329
1447
  }
1330
1448
  catch (error) {
1449
+ // Error-path cleanup: dispose the accumulator if a step after the
1450
+ // destructure from runChunkedParseAndResolve but before the happy-path
1451
+ // dispose threw. The reference is cleared on the happy path, so this
1452
+ // is a no-op when the pipeline completed successfully and then threw
1453
+ // from an unrelated post-dispose step (e.g., future cleanup code).
1454
+ bindingAccumulatorForCleanup?.dispose();
1331
1455
  ctx.clear();
1332
1456
  throw error;
1333
1457
  }