gitnexus 1.4.9 → 1.5.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 (186) hide show
  1. package/README.md +6 -5
  2. package/dist/cli/ai-context.d.ts +4 -1
  3. package/dist/cli/ai-context.js +19 -11
  4. package/dist/cli/analyze.d.ts +6 -0
  5. package/dist/cli/analyze.js +105 -251
  6. package/dist/cli/eval-server.js +20 -11
  7. package/dist/cli/index-repo.js +20 -22
  8. package/dist/cli/index.js +8 -7
  9. package/dist/cli/mcp.js +1 -1
  10. package/dist/cli/serve.js +29 -1
  11. package/dist/cli/setup.js +9 -9
  12. package/dist/cli/skill-gen.js +15 -9
  13. package/dist/cli/wiki.d.ts +2 -0
  14. package/dist/cli/wiki.js +141 -26
  15. package/dist/config/ignore-service.js +102 -22
  16. package/dist/config/supported-languages.d.ts +8 -42
  17. package/dist/config/supported-languages.js +8 -43
  18. package/dist/core/augmentation/engine.js +19 -7
  19. package/dist/core/embeddings/embedder.js +19 -15
  20. package/dist/core/embeddings/embedding-pipeline.js +6 -6
  21. package/dist/core/embeddings/http-client.js +3 -3
  22. package/dist/core/embeddings/text-generator.js +9 -24
  23. package/dist/core/embeddings/types.d.ts +1 -1
  24. package/dist/core/embeddings/types.js +1 -7
  25. package/dist/core/graph/graph.js +6 -2
  26. package/dist/core/graph/types.d.ts +9 -59
  27. package/dist/core/ingestion/ast-cache.js +3 -3
  28. package/dist/core/ingestion/call-processor.d.ts +20 -2
  29. package/dist/core/ingestion/call-processor.js +347 -144
  30. package/dist/core/ingestion/call-routing.js +10 -4
  31. package/dist/core/ingestion/call-sites/extract-language-call-site.d.ts +10 -0
  32. package/dist/core/ingestion/call-sites/extract-language-call-site.js +22 -0
  33. package/dist/core/ingestion/call-sites/java.d.ts +9 -0
  34. package/dist/core/ingestion/call-sites/java.js +30 -0
  35. package/dist/core/ingestion/cluster-enricher.js +6 -8
  36. package/dist/core/ingestion/cobol/cobol-copy-expander.js +10 -3
  37. package/dist/core/ingestion/cobol/cobol-preprocessor.js +287 -81
  38. package/dist/core/ingestion/cobol/jcl-parser.js +1 -1
  39. package/dist/core/ingestion/cobol/jcl-processor.js +1 -1
  40. package/dist/core/ingestion/cobol-processor.js +102 -56
  41. package/dist/core/ingestion/community-processor.js +21 -15
  42. package/dist/core/ingestion/entry-point-scoring.d.ts +1 -1
  43. package/dist/core/ingestion/entry-point-scoring.js +5 -6
  44. package/dist/core/ingestion/export-detection.js +32 -9
  45. package/dist/core/ingestion/field-extractor.d.ts +1 -1
  46. package/dist/core/ingestion/field-extractors/configs/c-cpp.js +8 -12
  47. package/dist/core/ingestion/field-extractors/configs/csharp.js +45 -2
  48. package/dist/core/ingestion/field-extractors/configs/dart.js +5 -3
  49. package/dist/core/ingestion/field-extractors/configs/go.js +3 -7
  50. package/dist/core/ingestion/field-extractors/configs/helpers.d.ts +5 -0
  51. package/dist/core/ingestion/field-extractors/configs/helpers.js +14 -0
  52. package/dist/core/ingestion/field-extractors/configs/jvm.js +7 -7
  53. package/dist/core/ingestion/field-extractors/configs/php.js +9 -11
  54. package/dist/core/ingestion/field-extractors/configs/python.js +1 -1
  55. package/dist/core/ingestion/field-extractors/configs/ruby.js +4 -3
  56. package/dist/core/ingestion/field-extractors/configs/rust.js +2 -5
  57. package/dist/core/ingestion/field-extractors/configs/swift.js +9 -7
  58. package/dist/core/ingestion/field-extractors/configs/typescript-javascript.js +2 -6
  59. package/dist/core/ingestion/field-extractors/generic.d.ts +5 -2
  60. package/dist/core/ingestion/field-extractors/generic.js +6 -0
  61. package/dist/core/ingestion/field-extractors/typescript.d.ts +1 -1
  62. package/dist/core/ingestion/field-extractors/typescript.js +1 -1
  63. package/dist/core/ingestion/field-types.d.ts +4 -2
  64. package/dist/core/ingestion/filesystem-walker.js +3 -3
  65. package/dist/core/ingestion/framework-detection.d.ts +1 -1
  66. package/dist/core/ingestion/framework-detection.js +355 -85
  67. package/dist/core/ingestion/heritage-processor.d.ts +24 -0
  68. package/dist/core/ingestion/heritage-processor.js +99 -8
  69. package/dist/core/ingestion/import-processor.js +44 -15
  70. package/dist/core/ingestion/import-resolvers/csharp.js +7 -3
  71. package/dist/core/ingestion/import-resolvers/dart.js +1 -1
  72. package/dist/core/ingestion/import-resolvers/go.js +4 -2
  73. package/dist/core/ingestion/import-resolvers/jvm.js +4 -4
  74. package/dist/core/ingestion/import-resolvers/php.js +4 -4
  75. package/dist/core/ingestion/import-resolvers/python.js +1 -1
  76. package/dist/core/ingestion/import-resolvers/rust.js +9 -3
  77. package/dist/core/ingestion/import-resolvers/standard.d.ts +1 -1
  78. package/dist/core/ingestion/import-resolvers/standard.js +6 -5
  79. package/dist/core/ingestion/import-resolvers/swift.js +2 -1
  80. package/dist/core/ingestion/import-resolvers/utils.js +26 -7
  81. package/dist/core/ingestion/language-config.js +5 -4
  82. package/dist/core/ingestion/language-provider.d.ts +7 -2
  83. package/dist/core/ingestion/languages/c-cpp.js +106 -21
  84. package/dist/core/ingestion/languages/cobol.js +1 -1
  85. package/dist/core/ingestion/languages/csharp.js +96 -19
  86. package/dist/core/ingestion/languages/dart.js +23 -7
  87. package/dist/core/ingestion/languages/go.js +1 -1
  88. package/dist/core/ingestion/languages/index.d.ts +1 -1
  89. package/dist/core/ingestion/languages/index.js +2 -3
  90. package/dist/core/ingestion/languages/java.js +4 -1
  91. package/dist/core/ingestion/languages/kotlin.js +60 -13
  92. package/dist/core/ingestion/languages/php.js +102 -25
  93. package/dist/core/ingestion/languages/python.js +28 -5
  94. package/dist/core/ingestion/languages/ruby.js +56 -14
  95. package/dist/core/ingestion/languages/rust.js +55 -11
  96. package/dist/core/ingestion/languages/swift.js +112 -27
  97. package/dist/core/ingestion/languages/typescript.js +95 -19
  98. package/dist/core/ingestion/markdown-processor.js +5 -5
  99. package/dist/core/ingestion/method-extractors/configs/csharp.d.ts +2 -0
  100. package/dist/core/ingestion/method-extractors/configs/csharp.js +283 -0
  101. package/dist/core/ingestion/method-extractors/configs/jvm.d.ts +3 -0
  102. package/dist/core/ingestion/method-extractors/configs/jvm.js +326 -0
  103. package/dist/core/ingestion/method-extractors/generic.d.ts +5 -0
  104. package/dist/core/ingestion/method-extractors/generic.js +137 -0
  105. package/dist/core/ingestion/method-types.d.ts +61 -0
  106. package/dist/core/ingestion/method-types.js +2 -0
  107. package/dist/core/ingestion/mro-processor.d.ts +1 -1
  108. package/dist/core/ingestion/mro-processor.js +12 -8
  109. package/dist/core/ingestion/named-binding-processor.js +2 -2
  110. package/dist/core/ingestion/named-bindings/rust.js +3 -1
  111. package/dist/core/ingestion/parsing-processor.js +74 -24
  112. package/dist/core/ingestion/pipeline.d.ts +2 -1
  113. package/dist/core/ingestion/pipeline.js +208 -102
  114. package/dist/core/ingestion/process-processor.js +12 -10
  115. package/dist/core/ingestion/resolution-context.js +3 -3
  116. package/dist/core/ingestion/route-extractors/middleware.js +31 -7
  117. package/dist/core/ingestion/route-extractors/php.js +2 -1
  118. package/dist/core/ingestion/route-extractors/response-shapes.js +8 -4
  119. package/dist/core/ingestion/structure-processor.d.ts +1 -1
  120. package/dist/core/ingestion/structure-processor.js +4 -4
  121. package/dist/core/ingestion/symbol-table.d.ts +1 -1
  122. package/dist/core/ingestion/symbol-table.js +22 -6
  123. package/dist/core/ingestion/tree-sitter-queries.d.ts +1 -1
  124. package/dist/core/ingestion/tree-sitter-queries.js +1 -1
  125. package/dist/core/ingestion/type-env.d.ts +2 -2
  126. package/dist/core/ingestion/type-env.js +75 -50
  127. package/dist/core/ingestion/type-extractors/c-cpp.js +33 -30
  128. package/dist/core/ingestion/type-extractors/csharp.js +24 -14
  129. package/dist/core/ingestion/type-extractors/dart.js +6 -8
  130. package/dist/core/ingestion/type-extractors/go.js +7 -6
  131. package/dist/core/ingestion/type-extractors/jvm.js +10 -21
  132. package/dist/core/ingestion/type-extractors/php.js +26 -13
  133. package/dist/core/ingestion/type-extractors/python.js +11 -15
  134. package/dist/core/ingestion/type-extractors/ruby.js +8 -3
  135. package/dist/core/ingestion/type-extractors/rust.js +6 -8
  136. package/dist/core/ingestion/type-extractors/shared.js +134 -50
  137. package/dist/core/ingestion/type-extractors/swift.js +16 -13
  138. package/dist/core/ingestion/type-extractors/typescript.js +23 -15
  139. package/dist/core/ingestion/utils/ast-helpers.d.ts +8 -8
  140. package/dist/core/ingestion/utils/ast-helpers.js +72 -35
  141. package/dist/core/ingestion/utils/call-analysis.d.ts +2 -0
  142. package/dist/core/ingestion/utils/call-analysis.js +96 -49
  143. package/dist/core/ingestion/utils/event-loop.js +1 -1
  144. package/dist/core/ingestion/workers/parse-worker.d.ts +7 -2
  145. package/dist/core/ingestion/workers/parse-worker.js +364 -84
  146. package/dist/core/ingestion/workers/worker-pool.js +5 -10
  147. package/dist/core/lbug/csv-generator.js +54 -15
  148. package/dist/core/lbug/lbug-adapter.d.ts +5 -0
  149. package/dist/core/lbug/lbug-adapter.js +86 -23
  150. package/dist/core/lbug/schema.d.ts +3 -6
  151. package/dist/core/lbug/schema.js +6 -30
  152. package/dist/core/run-analyze.d.ts +49 -0
  153. package/dist/core/run-analyze.js +257 -0
  154. package/dist/core/tree-sitter/parser-loader.d.ts +1 -1
  155. package/dist/core/tree-sitter/parser-loader.js +1 -1
  156. package/dist/core/wiki/cursor-client.js +2 -7
  157. package/dist/core/wiki/generator.js +38 -23
  158. package/dist/core/wiki/graph-queries.js +10 -10
  159. package/dist/core/wiki/html-viewer.js +7 -3
  160. package/dist/core/wiki/llm-client.d.ts +23 -2
  161. package/dist/core/wiki/llm-client.js +96 -26
  162. package/dist/core/wiki/prompts.js +7 -6
  163. package/dist/mcp/core/embedder.js +1 -1
  164. package/dist/mcp/core/lbug-adapter.d.ts +4 -1
  165. package/dist/mcp/core/lbug-adapter.js +17 -7
  166. package/dist/mcp/local/local-backend.js +247 -95
  167. package/dist/mcp/resources.js +14 -6
  168. package/dist/mcp/server.js +13 -5
  169. package/dist/mcp/staleness.js +5 -1
  170. package/dist/mcp/tools.js +100 -23
  171. package/dist/server/analyze-job.d.ts +53 -0
  172. package/dist/server/analyze-job.js +146 -0
  173. package/dist/server/analyze-worker.d.ts +13 -0
  174. package/dist/server/analyze-worker.js +59 -0
  175. package/dist/server/api.js +795 -44
  176. package/dist/server/git-clone.d.ts +25 -0
  177. package/dist/server/git-clone.js +91 -0
  178. package/dist/storage/git.js +1 -3
  179. package/dist/storage/repo-manager.d.ts +5 -2
  180. package/dist/storage/repo-manager.js +4 -4
  181. package/dist/types/pipeline.d.ts +1 -21
  182. package/dist/types/pipeline.js +1 -18
  183. package/hooks/claude/gitnexus-hook.cjs +52 -22
  184. package/package.json +13 -13
  185. package/dist/core/ingestion/utils/language-detection.d.ts +0 -9
  186. package/dist/core/ingestion/utils/language-detection.js +0 -70
@@ -12,7 +12,7 @@ import Rust from 'tree-sitter-rust';
12
12
  import PHP from 'tree-sitter-php';
13
13
  import Ruby from 'tree-sitter-ruby';
14
14
  import { createRequire } from 'node:module';
15
- import { SupportedLanguages } from '../../../config/supported-languages.js';
15
+ import { SupportedLanguages } from 'gitnexus-shared';
16
16
  import { getProvider } from '../languages/index.js';
17
17
  import { getTreeSitterBufferSize, TREE_SITTER_MAX_BUFFER } from '../constants.js';
18
18
  // tree-sitter-swift is an optionalDependency — may not be installed
@@ -34,9 +34,10 @@ try {
34
34
  Kotlin = _require('tree-sitter-kotlin');
35
35
  }
36
36
  catch { }
37
- import { getLanguageFromFilename } from '../utils/language-detection.js';
37
+ import { getLanguageFromFilename } from 'gitnexus-shared';
38
38
  import { FUNCTION_NODE_TYPES, extractFunctionName, getDefinitionNodeFromCaptures, findEnclosingClassId, getLabelFromCaptures, extractMethodSignature, findDescendant, extractStringContent, } from '../utils/ast-helpers.js';
39
- import { countCallArguments, inferCallForm, extractReceiverName, extractReceiverNode, extractMixedChain, } from '../utils/call-analysis.js';
39
+ import { countCallArguments, inferCallForm, extractReceiverName, extractReceiverNode, extractMixedChain, extractCallArgTypes, } from '../utils/call-analysis.js';
40
+ import { extractParsedCallSite } from '../call-sites/extract-language-call-site.js';
40
41
  import { buildTypeEnv } from '../type-env.js';
41
42
  import { detectFrameworkFromAST } from '../framework-detection.js';
42
43
  import { generateId } from '../../../lib/utils.js';
@@ -93,7 +94,13 @@ const setLanguage = (language, filePath) => {
93
94
  const classIdCache = new Map();
94
95
  const functionIdCache = new Map();
95
96
  const exportCache = new Map();
96
- const clearCaches = () => { classIdCache.clear(); functionIdCache.clear(); exportCache.clear(); fieldInfoCache.clear(); };
97
+ const clearCaches = () => {
98
+ classIdCache.clear();
99
+ functionIdCache.clear();
100
+ exportCache.clear();
101
+ fieldInfoCache.clear();
102
+ methodInfoCache.clear();
103
+ };
97
104
  // ============================================================================
98
105
  // FieldExtractor cache — extract field metadata once per class, reuse for each property.
99
106
  // Keyed by class node startIndex (unique per AST node within a file).
@@ -145,6 +152,34 @@ function getFieldInfo(classNode, provider, context) {
145
152
  fieldInfoCache.set(cacheKey, cached);
146
153
  return cached;
147
154
  }
155
+ // ============================================================================
156
+ // MethodExtractor cache — extract method metadata once per class, reuse for each method.
157
+ // Keyed by class node startIndex (unique per AST node within a file).
158
+ // ============================================================================
159
+ const methodInfoCache = new Map();
160
+ /**
161
+ * Get (or extract and cache) method info for a class node.
162
+ * Returns a "name:line" → MethodInfo map, or undefined if the provider has no method extractor
163
+ * or the class yielded no methods.
164
+ * Keyed by name:line (not name alone) to support overloaded methods in Java/Kotlin.
165
+ */
166
+ function getMethodInfo(classNode, provider, context) {
167
+ if (!provider.methodExtractor)
168
+ return undefined;
169
+ const cacheKey = classNode.startIndex;
170
+ let cached = methodInfoCache.get(cacheKey);
171
+ if (cached)
172
+ return cached;
173
+ const result = provider.methodExtractor.extract(classNode, context);
174
+ if (!result?.methods?.length)
175
+ return undefined;
176
+ cached = new Map();
177
+ for (const method of result.methods) {
178
+ cached.set(`${method.name}:${method.line}`, method);
179
+ }
180
+ methodInfoCache.set(cacheKey, cached);
181
+ return cached;
182
+ }
148
183
  /** Walk up AST to find enclosing function, return its generateId or null for top-level.
149
184
  * Applies provider.labelOverride so the label matches the definition phase (single source of truth). */
150
185
  const findEnclosingFunctionId = (node, filePath, provider) => {
@@ -248,13 +283,15 @@ const processBatch = (files, onProgress) => {
248
283
  let totalProcessed = 0;
249
284
  let lastReported = 0;
250
285
  const PROGRESS_INTERVAL = 100; // report every 100 files
251
- const onFileProcessed = onProgress ? () => {
252
- totalProcessed++;
253
- if (totalProcessed - lastReported >= PROGRESS_INTERVAL) {
254
- lastReported = totalProcessed;
255
- onProgress(totalProcessed);
286
+ const onFileProcessed = onProgress
287
+ ? () => {
288
+ totalProcessed++;
289
+ if (totalProcessed - lastReported >= PROGRESS_INTERVAL) {
290
+ lastReported = totalProcessed;
291
+ onProgress(totalProcessed);
292
+ }
256
293
  }
257
- } : undefined;
294
+ : undefined;
258
295
  for (const [language, langFiles] of byLanguage) {
259
296
  const provider = getProvider(language);
260
297
  const queryString = provider.treeSitterQueries;
@@ -288,7 +325,8 @@ const processBatch = (files, onProgress) => {
288
325
  }
289
326
  }
290
327
  else {
291
- result.skippedLanguages[language] = (result.skippedLanguages[language] || 0) + regularFiles.length;
328
+ result.skippedLanguages[language] =
329
+ (result.skippedLanguages[language] || 0) + regularFiles.length;
292
330
  }
293
331
  }
294
332
  // Process tsx files separately (different grammar)
@@ -303,18 +341,35 @@ const processBatch = (files, onProgress) => {
303
341
  }
304
342
  }
305
343
  else {
306
- result.skippedLanguages[language] = (result.skippedLanguages[language] || 0) + tsxFiles.length;
344
+ result.skippedLanguages[language] =
345
+ (result.skippedLanguages[language] || 0) + tsxFiles.length;
307
346
  }
308
347
  }
309
348
  }
310
349
  return result;
311
350
  };
312
351
  const ROUTE_HTTP_METHODS = new Set([
313
- 'get', 'post', 'put', 'patch', 'delete', 'options', 'any', 'match',
352
+ 'get',
353
+ 'post',
354
+ 'put',
355
+ 'patch',
356
+ 'delete',
357
+ 'options',
358
+ 'any',
359
+ 'match',
314
360
  ]);
315
361
  const ROUTE_RESOURCE_METHODS = new Set(['resource', 'apiResource']);
316
362
  // Express/Hono method names that register routes
317
- const EXPRESS_ROUTE_METHODS = new Set(['get', 'post', 'put', 'delete', 'patch', 'all', 'use', 'route']);
363
+ const EXPRESS_ROUTE_METHODS = new Set([
364
+ 'get',
365
+ 'post',
366
+ 'put',
367
+ 'delete',
368
+ 'patch',
369
+ 'all',
370
+ 'use',
371
+ 'route',
372
+ ]);
318
373
  // HTTP client methods that are ONLY used by clients, not Express route registration.
319
374
  // Methods like get/post/put/delete/patch overlap with Express — those are captured by
320
375
  // the express_route handler as route definitions, not consumers. The fetch() global
@@ -322,9 +377,23 @@ const EXPRESS_ROUTE_METHODS = new Set(['get', 'post', 'put', 'delete', 'patch',
322
377
  const HTTP_CLIENT_ONLY_METHODS = new Set(['head', 'options', 'request', 'ajax']);
323
378
  // Decorator names that indicate HTTP route handlers (NestJS, Flask, FastAPI, Spring)
324
379
  const ROUTE_DECORATOR_NAMES = new Set([
325
- 'Get', 'Post', 'Put', 'Delete', 'Patch', 'Route',
326
- 'get', 'post', 'put', 'delete', 'patch', 'route',
327
- 'RequestMapping', 'GetMapping', 'PostMapping', 'PutMapping', 'DeleteMapping',
380
+ 'Get',
381
+ 'Post',
382
+ 'Put',
383
+ 'Delete',
384
+ 'Patch',
385
+ 'Route',
386
+ 'get',
387
+ 'post',
388
+ 'put',
389
+ 'delete',
390
+ 'patch',
391
+ 'route',
392
+ 'RequestMapping',
393
+ 'GetMapping',
394
+ 'PostMapping',
395
+ 'PutMapping',
396
+ 'DeleteMapping',
328
397
  ]);
329
398
  const RESOURCE_ACTIONS = ['index', 'create', 'store', 'show', 'edit', 'update', 'destroy'];
330
399
  const API_RESOURCE_ACTIONS = ['index', 'store', 'show', 'update', 'destroy'];
@@ -337,8 +406,7 @@ function isRouteStaticCall(node) {
337
406
  }
338
407
  /** Get the method name from a scoped_call_expression or member_call_expression */
339
408
  function getCallMethodName(node) {
340
- const nameNode = node.childForFieldName?.('name') ??
341
- node.children?.find((c) => c.type === 'name');
409
+ const nameNode = node.childForFieldName?.('name') ?? node.children?.find((c) => c.type === 'name');
342
410
  return nameNode?.text ?? null;
343
411
  }
344
412
  /** Get the arguments node from a call expression */
@@ -352,17 +420,17 @@ function findClosureBody(argsNode) {
352
420
  for (const child of argsNode.children ?? []) {
353
421
  if (child.type === 'argument') {
354
422
  for (const inner of child.children ?? []) {
355
- if (inner.type === 'anonymous_function' ||
356
- inner.type === 'arrow_function') {
357
- return inner.childForFieldName?.('body') ??
358
- inner.children?.find((c) => c.type === 'compound_statement');
423
+ if (inner.type === 'anonymous_function' || inner.type === 'arrow_function') {
424
+ return (inner.childForFieldName?.('body') ??
425
+ inner.children?.find((c) => c.type === 'compound_statement') ??
426
+ null);
359
427
  }
360
428
  }
361
429
  }
362
- if (child.type === 'anonymous_function' ||
363
- child.type === 'arrow_function') {
364
- return child.childForFieldName?.('body') ??
365
- child.children?.find((c) => c.type === 'compound_statement');
430
+ if (child.type === 'anonymous_function' || child.type === 'arrow_function') {
431
+ return (child.childForFieldName?.('body') ??
432
+ child.children?.find((c) => c.type === 'compound_statement') ??
433
+ null);
366
434
  }
367
435
  }
368
436
  return null;
@@ -591,7 +659,9 @@ function extractLaravelRoutes(tree, filePath) {
591
659
  const actions = httpMethod === 'apiResource' ? API_RESOURCE_ACTIONS : RESOURCE_ACTIONS;
592
660
  for (const action of actions) {
593
661
  routes.push({
594
- filePath, httpMethod, routePath,
662
+ filePath,
663
+ httpMethod,
664
+ routePath,
595
665
  controllerName: target.controller ?? effective.controller,
596
666
  methodName: action,
597
667
  middleware: [...effective.middleware],
@@ -603,7 +673,9 @@ function extractLaravelRoutes(tree, filePath) {
603
673
  else {
604
674
  const target = extractControllerTarget(argsNode);
605
675
  routes.push({
606
- filePath, httpMethod, routePath,
676
+ filePath,
677
+ httpMethod,
678
+ routePath,
607
679
  controllerName: target.controller ?? effective.controller,
608
680
  methodName: target.method,
609
681
  middleware: [...effective.middleware],
@@ -653,7 +725,8 @@ function extractLaravelRoutes(tree, filePath) {
653
725
  }
654
726
  return;
655
727
  }
656
- if (ROUTE_HTTP_METHODS.has(chain.terminalMethod) || ROUTE_RESOURCE_METHODS.has(chain.terminalMethod)) {
728
+ if (ROUTE_HTTP_METHODS.has(chain.terminalMethod) ||
729
+ ROUTE_RESOURCE_METHODS.has(chain.terminalMethod)) {
657
730
  emitRoute(chain.terminalMethod, chain.terminalArgs, node.startPosition.row, groupStack, chain.attributes);
658
731
  return;
659
732
  }
@@ -736,7 +809,9 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
736
809
  clearCaches(); // Reset memoization before each new file
737
810
  let tree;
738
811
  try {
739
- tree = parser.parse(file.content, undefined, { bufferSize: getTreeSitterBufferSize(file.content.length) });
812
+ tree = parser.parse(file.content, undefined, {
813
+ bufferSize: getTreeSitterBufferSize(file.content.length),
814
+ });
740
815
  }
741
816
  catch (err) {
742
817
  console.warn(`Failed to parse file ${file.path}: ${err instanceof Error ? err.message : String(err)}`);
@@ -783,10 +858,16 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
783
858
  // Constructor bindings are verified against the SymbolTable in processCallsFromExtracted.
784
859
  const parentMap = fileParentMap;
785
860
  const provider = getProvider(language);
786
- const typeEnv = buildTypeEnv(tree, language, { parentMap, enclosingFunctionFinder: provider?.enclosingFunctionFinder });
861
+ const typeEnv = buildTypeEnv(tree, language, {
862
+ parentMap,
863
+ enclosingFunctionFinder: provider?.enclosingFunctionFinder,
864
+ });
787
865
  const callRouter = provider.callRouter;
788
866
  if (typeEnv.constructorBindings.length > 0) {
789
- result.constructorBindings.push({ filePath: file.path, bindings: [...typeEnv.constructorBindings] });
867
+ result.constructorBindings.push({
868
+ filePath: file.path,
869
+ bindings: [...typeEnv.constructorBindings],
870
+ });
790
871
  }
791
872
  // Extract file-scope bindings for ExportedTypeMap (closes worker/sequential quality gap).
792
873
  // Sequential path uses collectExportedBindings(typeEnv) directly; worker path serializes
@@ -821,12 +902,14 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
821
902
  continue;
822
903
  }
823
904
  // Extract assignment sites (field write access)
824
- if (captureMap['assignment'] && captureMap['assignment.receiver'] && captureMap['assignment.property']) {
905
+ if (captureMap['assignment'] &&
906
+ captureMap['assignment.receiver'] &&
907
+ captureMap['assignment.property']) {
825
908
  const receiverText = captureMap['assignment.receiver'].text;
826
909
  const propertyName = captureMap['assignment.property'].text;
827
910
  if (receiverText && propertyName) {
828
- const srcId = findEnclosingFunctionId(captureMap['assignment'], file.path, provider)
829
- || generateId('File', file.path);
911
+ const srcId = findEnclosingFunctionId(captureMap['assignment'], file.path, provider) ||
912
+ generateId('File', file.path);
830
913
  let receiverTypeName;
831
914
  if (typeEnv) {
832
915
  receiverTypeName = typeEnv.lookup(receiverText, captureMap['assignment']) ?? undefined;
@@ -848,11 +931,16 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
848
931
  const decoratorArg = captureMap['decorator.arg']?.text;
849
932
  const decoratorNode = captureMap['decorator'];
850
933
  // Store by the decorator's end line — the definition follows immediately after
851
- fileDecorators.set(decoratorNode.endPosition.row, { name: decoratorName, arg: decoratorArg });
934
+ fileDecorators.set(decoratorNode.endPosition.row, {
935
+ name: decoratorName,
936
+ arg: decoratorArg,
937
+ });
852
938
  if (ROUTE_DECORATOR_NAMES.has(decoratorName)) {
853
939
  const routePath = decoratorArg || '';
854
940
  const method = decoratorName.replace('Mapping', '').toUpperCase();
855
- const httpMethod = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'].includes(method) ? method : 'GET';
941
+ const httpMethod = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'].includes(method)
942
+ ? method
943
+ : 'GET';
856
944
  result.decoratorRoutes.push({
857
945
  filePath: file.path,
858
946
  routePath,
@@ -864,7 +952,11 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
864
952
  // MCP/RPC tool detection: @mcp.tool(), @app.tool(), @server.tool()
865
953
  if (decoratorName === 'tool') {
866
954
  // Re-store with isTool flag for the definition handler
867
- fileDecorators.set(decoratorNode.endPosition.row, { name: decoratorName, arg: decoratorArg, isTool: true });
955
+ fileDecorators.set(decoratorNode.endPosition.row, {
956
+ name: decoratorName,
957
+ arg: decoratorArg,
958
+ isTool: true,
959
+ });
868
960
  }
869
961
  continue;
870
962
  }
@@ -896,11 +988,15 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
896
988
  continue;
897
989
  }
898
990
  // Express/Hono route registration: app.get('/path', handler)
899
- if (captureMap['express_route'] && captureMap['express_route.method'] && captureMap['express_route.path']) {
991
+ if (captureMap['express_route'] &&
992
+ captureMap['express_route.method'] &&
993
+ captureMap['express_route.path']) {
900
994
  const method = captureMap['express_route.method'].text;
901
995
  const routePath = captureMap['express_route.path'].text;
902
996
  if (EXPRESS_ROUTE_METHODS.has(method) && routePath.startsWith('/')) {
903
- const httpMethod = method === 'all' || method === 'use' || method === 'route' ? 'GET' : method.toUpperCase();
997
+ const httpMethod = method === 'all' || method === 'use' || method === 'route'
998
+ ? 'GET'
999
+ : method.toUpperCase();
904
1000
  result.decoratorRoutes.push({
905
1001
  filePath: file.path,
906
1002
  routePath,
@@ -913,6 +1009,38 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
913
1009
  }
914
1010
  // Extract call sites
915
1011
  if (captureMap['call']) {
1012
+ const callNode0 = captureMap['call'];
1013
+ const languageSeed = extractParsedCallSite(language, callNode0);
1014
+ if (languageSeed) {
1015
+ if (!provider.isBuiltInName(languageSeed.calledName)) {
1016
+ const sourceId = findEnclosingFunctionId(callNode0, file.path, provider) ||
1017
+ generateId('File', file.path);
1018
+ const receiverName = languageSeed.callForm === 'member' ? languageSeed.receiverName : undefined;
1019
+ let receiverTypeName = receiverName
1020
+ ? typeEnv.lookup(receiverName, callNode0)
1021
+ : undefined;
1022
+ // Type-as-receiver (e.g. Java `User::getName`): no TypeEnv binding for the class name
1023
+ if (receiverName !== undefined &&
1024
+ receiverTypeName === undefined &&
1025
+ languageSeed.callForm === 'member' &&
1026
+ (language === SupportedLanguages.Java ||
1027
+ language === SupportedLanguages.CSharp ||
1028
+ language === SupportedLanguages.Kotlin)) {
1029
+ const c0 = receiverName.charCodeAt(0);
1030
+ if (c0 >= 65 && c0 <= 90)
1031
+ receiverTypeName = receiverName;
1032
+ }
1033
+ result.calls.push({
1034
+ filePath: file.path,
1035
+ calledName: languageSeed.calledName,
1036
+ sourceId,
1037
+ callForm: languageSeed.callForm,
1038
+ ...(receiverName !== undefined ? { receiverName } : {}),
1039
+ ...(receiverTypeName !== undefined ? { receiverTypeName } : {}),
1040
+ });
1041
+ }
1042
+ continue;
1043
+ }
916
1044
  const callNameNode = captureMap['call.name'];
917
1045
  if (callNameNode) {
918
1046
  const calledName = callNameNode.text;
@@ -948,7 +1076,10 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
948
1076
  const classNode = findEnclosingClassNode(captureMap['call']);
949
1077
  if (classNode) {
950
1078
  routedFieldMap = getFieldInfo(classNode, provider, {
951
- typeEnv, symbolTable: NOOP_SYMBOL_TABLE, filePath: file.path, language,
1079
+ typeEnv,
1080
+ symbolTable: NOOP_SYMBOL_TABLE,
1081
+ filePath: file.path,
1082
+ language,
952
1083
  });
953
1084
  }
954
1085
  }
@@ -966,10 +1097,20 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
966
1097
  language,
967
1098
  isExported: true,
968
1099
  description: item.accessorType,
969
- ...(item.declaredType ? { declaredType: item.declaredType } : routedFieldInfo?.type ? { declaredType: routedFieldInfo.type } : {}),
970
- ...(routedFieldInfo?.visibility !== undefined ? { visibility: routedFieldInfo.visibility } : {}),
971
- ...(routedFieldInfo?.isStatic !== undefined ? { isStatic: routedFieldInfo.isStatic } : {}),
972
- ...(routedFieldInfo?.isReadonly !== undefined ? { isReadonly: routedFieldInfo.isReadonly } : {}),
1100
+ ...(item.declaredType
1101
+ ? { declaredType: item.declaredType }
1102
+ : routedFieldInfo?.type
1103
+ ? { declaredType: routedFieldInfo.type }
1104
+ : {}),
1105
+ ...(routedFieldInfo?.visibility !== undefined
1106
+ ? { visibility: routedFieldInfo.visibility }
1107
+ : {}),
1108
+ ...(routedFieldInfo?.isStatic !== undefined
1109
+ ? { isStatic: routedFieldInfo.isStatic }
1110
+ : {}),
1111
+ ...(routedFieldInfo?.isReadonly !== undefined
1112
+ ? { isReadonly: routedFieldInfo.isReadonly }
1113
+ : {}),
973
1114
  },
974
1115
  });
975
1116
  result.symbols.push({
@@ -978,10 +1119,20 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
978
1119
  nodeId,
979
1120
  type: 'Property',
980
1121
  ...(propEnclosingClassId ? { ownerId: propEnclosingClassId } : {}),
981
- ...(item.declaredType ? { declaredType: item.declaredType } : routedFieldInfo?.type ? { declaredType: routedFieldInfo.type } : {}),
982
- ...(routedFieldInfo?.visibility !== undefined ? { visibility: routedFieldInfo.visibility } : {}),
983
- ...(routedFieldInfo?.isStatic !== undefined ? { isStatic: routedFieldInfo.isStatic } : {}),
984
- ...(routedFieldInfo?.isReadonly !== undefined ? { isReadonly: routedFieldInfo.isReadonly } : {}),
1122
+ ...(item.declaredType
1123
+ ? { declaredType: item.declaredType }
1124
+ : routedFieldInfo?.type
1125
+ ? { declaredType: routedFieldInfo.type }
1126
+ : {}),
1127
+ ...(routedFieldInfo?.visibility !== undefined
1128
+ ? { visibility: routedFieldInfo.visibility }
1129
+ : {}),
1130
+ ...(routedFieldInfo?.isStatic !== undefined
1131
+ ? { isStatic: routedFieldInfo.isStatic }
1132
+ : {}),
1133
+ ...(routedFieldInfo?.isReadonly !== undefined
1134
+ ? { isReadonly: routedFieldInfo.isReadonly }
1135
+ : {}),
985
1136
  });
986
1137
  const fileId = generateId('File', file.path);
987
1138
  const relId = generateId('DEFINES', `${fileId}->${nodeId}`);
@@ -1010,11 +1161,13 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
1010
1161
  }
1011
1162
  if (!provider.isBuiltInName(calledName)) {
1012
1163
  const callNode = captureMap['call'];
1013
- const sourceId = findEnclosingFunctionId(callNode, file.path, provider)
1014
- || generateId('File', file.path);
1164
+ const sourceId = findEnclosingFunctionId(callNode, file.path, provider) ||
1165
+ generateId('File', file.path);
1015
1166
  const callForm = inferCallForm(callNode, callNameNode);
1016
1167
  let receiverName = callForm === 'member' ? extractReceiverName(callNameNode) : undefined;
1017
- let receiverTypeName = receiverName ? typeEnv.lookup(receiverName, callNode) : undefined;
1168
+ let receiverTypeName = receiverName
1169
+ ? typeEnv.lookup(receiverName, callNode)
1170
+ : undefined;
1018
1171
  let receiverMixedChain;
1019
1172
  // When the receiver is a complex expression (call chain, field chain, or mixed),
1020
1173
  // extractReceiverName returns undefined. Walk the receiver node to build a unified
@@ -1034,6 +1187,14 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
1034
1187
  }
1035
1188
  }
1036
1189
  }
1190
+ const inferLiteralType = provider.typeConfig?.inferLiteralType;
1191
+ const argCountForOverloadHints = countCallArguments(callNode);
1192
+ // Skip when no arg list / zero args: nothing to infer for overload typing; saves AST walks + payload size.
1193
+ const argTypes = inferLiteralType &&
1194
+ argCountForOverloadHints !== undefined &&
1195
+ argCountForOverloadHints > 0
1196
+ ? extractCallArgTypes(callNode, inferLiteralType, (varName, cn) => typeEnv.lookup(varName, cn))
1197
+ : undefined;
1037
1198
  result.calls.push({
1038
1199
  filePath: file.path,
1039
1200
  calledName,
@@ -1043,6 +1204,7 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
1043
1204
  ...(receiverName !== undefined ? { receiverName } : {}),
1044
1205
  ...(receiverTypeName !== undefined ? { receiverTypeName } : {}),
1045
1206
  ...(receiverMixedChain !== undefined ? { receiverMixedChain } : {}),
1207
+ ...(argTypes !== undefined ? { argTypes } : {}),
1046
1208
  });
1047
1209
  }
1048
1210
  }
@@ -1056,8 +1218,7 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
1056
1218
  // Named fields like `Breed string` also match — skip them.
1057
1219
  const extendsNode = captureMap['heritage.extends'];
1058
1220
  const fieldDecl = extendsNode.parent;
1059
- const isNamedField = fieldDecl?.type === 'field_declaration'
1060
- && fieldDecl.childForFieldName('name');
1221
+ const isNamedField = fieldDecl?.type === 'field_declaration' && fieldDecl.childForFieldName('name');
1061
1222
  if (!isNamedField) {
1062
1223
  result.heritage.push({
1063
1224
  filePath: file.path,
@@ -1083,7 +1244,9 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
1083
1244
  kind: 'trait-impl',
1084
1245
  });
1085
1246
  }
1086
- if (captureMap['heritage.extends'] || captureMap['heritage.implements'] || captureMap['heritage.trait']) {
1247
+ if (captureMap['heritage.extends'] ||
1248
+ captureMap['heritage.implements'] ||
1249
+ captureMap['heritage.trait']) {
1087
1250
  continue;
1088
1251
  }
1089
1252
  }
@@ -1096,7 +1259,11 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
1096
1259
  continue;
1097
1260
  const nodeName = nameNode ? nameNode.text : 'init';
1098
1261
  const definitionNode = getDefinitionNodeFromCaptures(captureMap);
1099
- const startLine = definitionNode ? definitionNode.startPosition.row : (nameNode ? nameNode.startPosition.row : 0);
1262
+ const startLine = definitionNode
1263
+ ? definitionNode.startPosition.row
1264
+ : nameNode
1265
+ ? nameNode.startPosition.row
1266
+ : 0;
1100
1267
  const nodeId = generateId(nodeLabel, `${file.path}:${nodeName}`);
1101
1268
  const description = provider.descriptionExtractor?.(nodeLabel, nodeName, captureMap);
1102
1269
  let frameworkHint = definitionNode
@@ -1139,15 +1306,70 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
1139
1306
  let visibility;
1140
1307
  let isStatic;
1141
1308
  let isReadonly;
1309
+ let isAbstract;
1310
+ let isFinal;
1311
+ let isVirtual;
1312
+ let isOverride;
1313
+ let isAsync;
1314
+ let isPartial;
1315
+ let annotations;
1142
1316
  if (nodeLabel === 'Function' || nodeLabel === 'Method' || nodeLabel === 'Constructor') {
1143
- const sig = extractMethodSignature(definitionNode);
1144
- parameterCount = sig.parameterCount;
1145
- requiredParameterCount = sig.requiredParameterCount;
1146
- parameterTypes = sig.parameterTypes;
1147
- returnType = sig.returnType;
1317
+ // Try MethodExtractor first — it provides everything extractMethodSignature does, plus
1318
+ // isAbstract/isFinal/annotations. Only fall back to extractMethodSignature when no
1319
+ // MethodExtractor is available or the method isn't inside a class body.
1320
+ let enrichedByMethodExtractor = false;
1321
+ if (provider.methodExtractor && definitionNode) {
1322
+ const classNode = findEnclosingClassNode(definitionNode);
1323
+ if (classNode) {
1324
+ const methodMap = getMethodInfo(classNode, provider, {
1325
+ filePath: file.path,
1326
+ language,
1327
+ });
1328
+ const defLine = definitionNode.startPosition.row + 1;
1329
+ const info = methodMap?.get(`${nodeName}:${defLine}`);
1330
+ if (info) {
1331
+ enrichedByMethodExtractor = true;
1332
+ parameterCount = info.parameters.length;
1333
+ const types = [];
1334
+ let optionalCount = 0;
1335
+ for (const p of info.parameters) {
1336
+ if (p.type !== null)
1337
+ types.push(p.type);
1338
+ if (p.isOptional)
1339
+ optionalCount++;
1340
+ }
1341
+ parameterTypes = types.length > 0 ? types : undefined;
1342
+ requiredParameterCount =
1343
+ optionalCount > 0 ? parameterCount - optionalCount : undefined;
1344
+ returnType = info.returnType ?? undefined;
1345
+ visibility = info.visibility;
1346
+ isStatic = info.isStatic;
1347
+ isAbstract = info.isAbstract;
1348
+ isFinal = info.isFinal;
1349
+ if (info.isVirtual)
1350
+ isVirtual = info.isVirtual;
1351
+ if (info.isOverride)
1352
+ isOverride = info.isOverride;
1353
+ if (info.isAsync)
1354
+ isAsync = info.isAsync;
1355
+ if (info.isPartial)
1356
+ isPartial = info.isPartial;
1357
+ if (info.annotations.length > 0)
1358
+ annotations = info.annotations;
1359
+ }
1360
+ }
1361
+ }
1362
+ if (!enrichedByMethodExtractor) {
1363
+ const sig = extractMethodSignature(definitionNode);
1364
+ parameterCount = sig.parameterCount;
1365
+ requiredParameterCount = sig.requiredParameterCount;
1366
+ parameterTypes = sig.parameterTypes;
1367
+ returnType = sig.returnType;
1368
+ }
1148
1369
  // Language-specific return type fallback (e.g. Ruby YARD @return [Type])
1149
1370
  // Also upgrades uninformative AST types like PHP `array` with PHPDoc `@return User[]`
1150
- if ((!returnType || returnType === 'array' || returnType === 'iterable') && definitionNode) {
1371
+ if ((!returnType || returnType === 'array' || returnType === 'iterable') &&
1372
+ definitionNode) {
1151
1373
  const tc = provider.typeConfig;
1152
1374
  if (tc?.extractReturnType) {
1153
1375
  const docReturn = tc.extractReturnType(definitionNode);
@@ -1162,7 +1384,10 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
1162
1384
  const classNode = findEnclosingClassNode(definitionNode);
1163
1385
  if (classNode) {
1164
1386
  const fieldMap = getFieldInfo(classNode, provider, {
1165
- typeEnv, symbolTable: NOOP_SYMBOL_TABLE, filePath: file.path, language,
1387
+ typeEnv,
1388
+ symbolTable: NOOP_SYMBOL_TABLE,
1389
+ filePath: file.path,
1390
+ language,
1166
1391
  });
1167
1392
  const info = fieldMap?.get(nodeName);
1168
1393
  if (info) {
@@ -1184,10 +1409,12 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
1184
1409
  endLine: definitionNode ? definitionNode.endPosition.row : startLine,
1185
1410
  language: language,
1186
1411
  isExported: cachedExportCheck(provider.exportChecker, nameNode || definitionNode, nodeName),
1187
- ...(frameworkHint ? {
1188
- astFrameworkMultiplier: frameworkHint.entryPointMultiplier,
1189
- astFrameworkReason: frameworkHint.reason,
1190
- } : {}),
1412
+ ...(frameworkHint
1413
+ ? {
1414
+ astFrameworkMultiplier: frameworkHint.entryPointMultiplier,
1415
+ astFrameworkReason: frameworkHint.reason,
1416
+ }
1417
+ : {}),
1191
1418
  ...(description !== undefined ? { description } : {}),
1192
1419
  ...(parameterCount !== undefined ? { parameterCount } : {}),
1193
1420
  ...(requiredParameterCount !== undefined ? { requiredParameterCount } : {}),
@@ -1197,12 +1424,24 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
1197
1424
  ...(visibility !== undefined ? { visibility } : {}),
1198
1425
  ...(isStatic !== undefined ? { isStatic } : {}),
1199
1426
  ...(isReadonly !== undefined ? { isReadonly } : {}),
1427
+ ...(isAbstract !== undefined ? { isAbstract } : {}),
1428
+ ...(isFinal !== undefined ? { isFinal } : {}),
1429
+ ...(isVirtual !== undefined ? { isVirtual } : {}),
1430
+ ...(isOverride !== undefined ? { isOverride } : {}),
1431
+ ...(isAsync !== undefined ? { isAsync } : {}),
1432
+ ...(isPartial !== undefined ? { isPartial } : {}),
1433
+ ...(annotations !== undefined ? { annotations } : {}),
1200
1434
  },
1201
1435
  });
1202
1436
  // Compute enclosing class for Method/Constructor/Property/Function — used for both ownerId and HAS_METHOD
1203
1437
  // Function is included because Kotlin/Rust/Python capture class methods as Function nodes
1204
- const needsOwner = nodeLabel === 'Method' || nodeLabel === 'Constructor' || nodeLabel === 'Property' || nodeLabel === 'Function';
1205
- const enclosingClassId = needsOwner ? cachedFindEnclosingClassId(nameNode || definitionNode, file.path) : null;
1438
+ const needsOwner = nodeLabel === 'Method' ||
1439
+ nodeLabel === 'Constructor' ||
1440
+ nodeLabel === 'Property' ||
1441
+ nodeLabel === 'Function';
1442
+ const enclosingClassId = needsOwner
1443
+ ? cachedFindEnclosingClassId(nameNode || definitionNode, file.path)
1444
+ : null;
1206
1445
  result.symbols.push({
1207
1446
  filePath: file.path,
1208
1447
  name: nodeName,
@@ -1217,6 +1456,13 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
1217
1456
  ...(visibility !== undefined ? { visibility } : {}),
1218
1457
  ...(isStatic !== undefined ? { isStatic } : {}),
1219
1458
  ...(isReadonly !== undefined ? { isReadonly } : {}),
1459
+ ...(isAbstract !== undefined ? { isAbstract } : {}),
1460
+ ...(isFinal !== undefined ? { isFinal } : {}),
1461
+ ...(isVirtual !== undefined ? { isVirtual } : {}),
1462
+ ...(isOverride !== undefined ? { isOverride } : {}),
1463
+ ...(isAsync !== undefined ? { isAsync } : {}),
1464
+ ...(isPartial !== undefined ? { isPartial } : {}),
1465
+ ...(annotations !== undefined ? { annotations } : {}),
1220
1466
  });
1221
1467
  const fileId = generateId('File', file.path);
1222
1468
  const relId = generateId('DEFINES', `${fileId}->${nodeId}`);
@@ -1255,8 +1501,22 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
1255
1501
  // ============================================================================
1256
1502
  /** Accumulated result across sub-batches */
1257
1503
  let accumulated = {
1258
- nodes: [], relationships: [], symbols: [],
1259
- imports: [], calls: [], assignments: [], heritage: [], routes: [], fetchCalls: [], decoratorRoutes: [], toolDefs: [], ormQueries: [], constructorBindings: [], typeEnvBindings: [], skippedLanguages: {}, fileCount: 0,
1504
+ nodes: [],
1505
+ relationships: [],
1506
+ symbols: [],
1507
+ imports: [],
1508
+ calls: [],
1509
+ assignments: [],
1510
+ heritage: [],
1511
+ routes: [],
1512
+ fetchCalls: [],
1513
+ decoratorRoutes: [],
1514
+ toolDefs: [],
1515
+ ormQueries: [],
1516
+ constructorBindings: [],
1517
+ typeEnvBindings: [],
1518
+ skippedLanguages: {},
1519
+ fileCount: 0,
1260
1520
  };
1261
1521
  let cumulativeProcessed = 0;
1262
1522
  const mergeResult = (target, src) => {
@@ -1281,10 +1541,21 @@ const mergeResult = (target, src) => {
1281
1541
  };
1282
1542
  parentPort.on('message', (msg) => {
1283
1543
  try {
1544
+ // Legacy single-message mode (backward compat): array of files
1545
+ if (Array.isArray(msg)) {
1546
+ const result = processBatch(msg, (filesProcessed) => {
1547
+ parentPort.postMessage({ type: 'progress', filesProcessed });
1548
+ });
1549
+ parentPort.postMessage({ type: 'result', data: result });
1550
+ return;
1551
+ }
1284
1552
  // Sub-batch mode: { type: 'sub-batch', files: [...] }
1285
- if (msg && msg.type === 'sub-batch') {
1553
+ if (msg.type === 'sub-batch') {
1286
1554
  const result = processBatch(msg.files, (filesProcessed) => {
1287
- parentPort.postMessage({ type: 'progress', filesProcessed: cumulativeProcessed + filesProcessed });
1555
+ parentPort.postMessage({
1556
+ type: 'progress',
1557
+ filesProcessed: cumulativeProcessed + filesProcessed,
1558
+ });
1288
1559
  });
1289
1560
  cumulativeProcessed += result.fileCount;
1290
1561
  mergeResult(accumulated, result);
@@ -1293,21 +1564,30 @@ parentPort.on('message', (msg) => {
1293
1564
  return;
1294
1565
  }
1295
1566
  // Flush: send accumulated results
1296
- if (msg && msg.type === 'flush') {
1567
+ if (msg.type === 'flush') {
1297
1568
  parentPort.postMessage({ type: 'result', data: accumulated });
1298
1569
  // Reset for potential reuse
1299
- accumulated = { nodes: [], relationships: [], symbols: [], imports: [], calls: [], assignments: [], heritage: [], routes: [], fetchCalls: [], decoratorRoutes: [], toolDefs: [], ormQueries: [], constructorBindings: [], typeEnvBindings: [], skippedLanguages: {}, fileCount: 0 };
1570
+ accumulated = {
1571
+ nodes: [],
1572
+ relationships: [],
1573
+ symbols: [],
1574
+ imports: [],
1575
+ calls: [],
1576
+ assignments: [],
1577
+ heritage: [],
1578
+ routes: [],
1579
+ fetchCalls: [],
1580
+ decoratorRoutes: [],
1581
+ toolDefs: [],
1582
+ ormQueries: [],
1583
+ constructorBindings: [],
1584
+ typeEnvBindings: [],
1585
+ skippedLanguages: {},
1586
+ fileCount: 0,
1587
+ };
1300
1588
  cumulativeProcessed = 0;
1301
1589
  return;
1302
1590
  }
1303
- // Legacy single-message mode (backward compat): array of files
1304
- if (Array.isArray(msg)) {
1305
- const result = processBatch(msg, (filesProcessed) => {
1306
- parentPort.postMessage({ type: 'progress', filesProcessed });
1307
- });
1308
- parentPort.postMessage({ type: 'result', data: result });
1309
- return;
1310
- }
1311
1591
  }
1312
1592
  catch (err) {
1313
1593
  const message = err instanceof Error ? err.message : String(err);