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
@@ -35,14 +35,15 @@ try {
35
35
  }
36
36
  catch { }
37
37
  import { getLanguageFromFilename } from '../../../_shared/index.js';
38
- import { FUNCTION_NODE_TYPES, extractFunctionName, getDefinitionNodeFromCaptures, findEnclosingClassId, getLabelFromCaptures, extractMethodSignature, findDescendant, extractStringContent, } from '../utils/ast-helpers.js';
38
+ import { FUNCTION_NODE_TYPES, getDefinitionNodeFromCaptures, findEnclosingClassInfo, getLabelFromCaptures, findDescendant, extractStringContent, genericFuncName, inferFunctionLabel, CLASS_CONTAINER_TYPES, } from '../utils/ast-helpers.js';
39
39
  import { countCallArguments, inferCallForm, extractReceiverName, extractReceiverNode, extractMixedChain, extractCallArgTypes, } from '../utils/call-analysis.js';
40
40
  import { extractParsedCallSite } from '../call-sites/extract-language-call-site.js';
41
41
  import { buildTypeEnv } from '../type-env.js';
42
42
  import { detectFrameworkFromAST } from '../framework-detection.js';
43
43
  import { generateId } from '../../../lib/utils.js';
44
44
  import { preprocessImportPath } from '../import-processor.js';
45
- import { CLASS_CONTAINER_TYPES } from '../utils/ast-helpers.js';
45
+ import { extractVueScript, extractTemplateComponents, isVueSetupTopLevel, } from '../vue-sfc-extractor.js';
46
+ import { buildMethodProps, arityForIdFromInfo, typeTagForId, constTagForId, buildCollisionGroups, } from '../utils/method-props.js';
46
47
  // ============================================================================
47
48
  // Worker-local parser + language map
48
49
  // ============================================================================
@@ -61,6 +62,7 @@ const languageMap = {
61
62
  ...(Kotlin ? { [SupportedLanguages.Kotlin]: Kotlin } : {}),
62
63
  [SupportedLanguages.PHP]: PHP.php_only,
63
64
  [SupportedLanguages.Ruby]: Ruby,
65
+ [SupportedLanguages.Vue]: TypeScript.typescript,
64
66
  ...(Dart ? { [SupportedLanguages.Dart]: Dart } : {}),
65
67
  ...(Swift ? { [SupportedLanguages.Swift]: Swift } : {}),
66
68
  };
@@ -114,21 +116,100 @@ function findEnclosingClassNode(node) {
114
116
  let current = node.parent;
115
117
  while (current) {
116
118
  if (CLASS_CONTAINER_TYPES.has(current.type)) {
119
+ // Return singleton_class directly so the method extractor sees it as
120
+ // the owner node and correctly marks methods as static. Name resolution
121
+ // for qualified names is handled separately by findEnclosingClassInfo.
117
122
  return current;
118
123
  }
119
124
  current = current.parent;
120
125
  }
121
126
  return null;
122
127
  }
128
+ /**
129
+ * For C++ out-of-class method definitions (e.g. `void Foo::bar() {}`), extract the
130
+ * class name from the qualified_identifier scope and find the class declaration in the
131
+ * file's AST. Returns the class SyntaxNode or null if not found.
132
+ *
133
+ * Handles pointer/reference return types where function_declarator is nested inside
134
+ * pointer_declarator or reference_declarator.
135
+ */
136
+ function findClassNodeByQualifiedName(node) {
137
+ const declarator = node.childForFieldName('declarator');
138
+ if (!declarator)
139
+ return null;
140
+ // Find the function_declarator, recursively unwrapping pointer_declarator /
141
+ // reference_declarator chains (e.g. int** Foo::bar() has
142
+ // pointer_declarator → pointer_declarator → function_declarator).
143
+ let funcDecl = null;
144
+ if (declarator.type === 'function_declarator') {
145
+ funcDecl = declarator;
146
+ }
147
+ else {
148
+ let current = declarator;
149
+ while (current && !funcDecl) {
150
+ for (let i = 0; i < current.namedChildCount; i++) {
151
+ const child = current.namedChild(i);
152
+ if (child?.type === 'function_declarator') {
153
+ funcDecl = child;
154
+ break;
155
+ }
156
+ }
157
+ if (!funcDecl) {
158
+ const next = current.namedChildren.find((c) => c.type === 'pointer_declarator' || c.type === 'reference_declarator');
159
+ current = next ?? null;
160
+ }
161
+ }
162
+ }
163
+ if (!funcDecl)
164
+ return null;
165
+ // Check if the inner declarator is a qualified_identifier (Foo::bar)
166
+ const innerDecl = funcDecl.childForFieldName('declarator');
167
+ if (!innerDecl || innerDecl.type !== 'qualified_identifier')
168
+ return null;
169
+ const scope = innerDecl.childForFieldName('scope');
170
+ if (!scope)
171
+ return null;
172
+ const className = scope.text;
173
+ // Search the file for a matching class/struct specifier, including inside
174
+ // namespace_definition blocks (the majority of production C++ uses namespaces).
175
+ const root = node.tree.rootNode;
176
+ const classTypes = new Set(['class_specifier', 'struct_specifier']);
177
+ const searchIn = (parent) => {
178
+ for (let i = 0; i < parent.namedChildCount; i++) {
179
+ const child = parent.namedChild(i);
180
+ if (!child)
181
+ continue;
182
+ if (classTypes.has(child.type)) {
183
+ const nameNode = child.childForFieldName('name');
184
+ if (nameNode?.text === className)
185
+ return child;
186
+ }
187
+ // Recurse into namespace blocks
188
+ if (child.type === 'namespace_definition') {
189
+ const found = searchIn(child);
190
+ if (found)
191
+ return found;
192
+ }
193
+ }
194
+ return null;
195
+ };
196
+ return searchIn(root);
197
+ }
123
198
  /**
124
199
  * Minimal no-op SymbolTable stub for FieldExtractorContext in the worker.
125
- * Field extraction only uses symbolTable.lookupExactAll for optional type resolution —
126
- * returning [] causes the extractor to use the raw type string, which is fine for us.
200
+ * Field extraction only uses symbolTable.lookupExactAll for optional type
201
+ * resolution — returning [] causes the extractor to use the raw type
202
+ * string, which is fine for us. Every other method is a no-op so the
203
+ * stub remains safe if a future FieldExtractor consults it through the
204
+ * full {@link SymbolTableReader} surface.
127
205
  */
128
206
  const NOOP_SYMBOL_TABLE = {
129
- lookupExactAll: () => [],
130
207
  lookupExact: () => undefined,
131
208
  lookupExactFull: () => undefined,
209
+ lookupExactAll: () => [],
210
+ lookupCallableByName: () => [],
211
+ getFiles: () => [][Symbol.iterator](),
212
+ getStats: () => ({ fileCount: 0 }),
132
213
  };
133
214
  /**
134
215
  * Get (or extract and cache) field info for a class node.
@@ -180,6 +261,9 @@ function getMethodInfo(classNode, provider, context) {
180
261
  methodInfoCache.set(cacheKey, cached);
181
262
  return cached;
182
263
  }
264
+ // ============================================================================
265
+ // Enclosing function detection (for call extraction) — cached
266
+ // ============================================================================
183
267
  /** Walk up AST to find enclosing function, return its generateId or null for top-level.
184
268
  * Applies provider.labelOverride so the label matches the definition phase (single source of truth). */
185
269
  const findEnclosingFunctionId = (node, filePath, provider) => {
@@ -189,7 +273,9 @@ const findEnclosingFunctionId = (node, filePath, provider) => {
189
273
  let current = node.parent;
190
274
  while (current) {
191
275
  if (FUNCTION_NODE_TYPES.has(current.type)) {
192
- const { funcName, label } = extractFunctionName(current);
276
+ const efnResult = provider.methodExtractor?.extractFunctionName?.(current);
277
+ const funcName = efnResult?.funcName ?? genericFuncName(current);
278
+ const label = efnResult?.label ?? inferFunctionLabel(current.type);
193
279
  if (funcName) {
194
280
  // Apply labelOverride so label matches definition phase (e.g., Kotlin Function→Method).
195
281
  // null means "skip as definition" — keep original label for scope identification.
@@ -199,7 +285,39 @@ const findEnclosingFunctionId = (node, filePath, provider) => {
199
285
  if (override !== null)
200
286
  finalLabel = override;
201
287
  }
202
- const result = generateId(finalLabel, `${filePath}:${funcName}`);
288
+ // Qualify with enclosing class to match definition-phase node IDs
289
+ const classInfo = cachedFindEnclosingClassInfo(current, filePath);
290
+ const qualifiedName = classInfo ? `${classInfo.className}.${funcName}` : funcName;
291
+ // Include #<arity> suffix to match definition-phase Method/Constructor IDs.
292
+ // Use the same MethodExtractor (getMethodInfo) as the definition phase.
293
+ // When same-arity collisions exist, also append ~type1,type2.
294
+ let arity;
295
+ let encTypeTag = '';
296
+ if (finalLabel === 'Method' || finalLabel === 'Constructor') {
297
+ const encLang = getLanguageFromFilename(filePath);
298
+ const classNode = findEnclosingClassNode(current) ?? findClassNodeByQualifiedName(current);
299
+ if (classNode && encLang) {
300
+ const methodMap = getMethodInfo(classNode, provider, {
301
+ filePath,
302
+ language: encLang,
303
+ });
304
+ const defLine = current.startPosition.row + 1;
305
+ const info = methodMap?.get(`${funcName}:${defLine}`);
306
+ if (info) {
307
+ arity = info.parameters.some((p) => p.isVariadic)
308
+ ? undefined
309
+ : info.parameters.length;
310
+ if (methodMap && arity !== undefined) {
311
+ const g = buildCollisionGroups(methodMap);
312
+ encTypeTag =
313
+ typeTagForId(methodMap, funcName, arity, info, encLang, g) +
314
+ constTagForId(methodMap, funcName, arity, info, g);
315
+ }
316
+ }
317
+ }
318
+ }
319
+ const arityTag = arity !== undefined ? `#${arity}${encTypeTag}` : '';
320
+ const result = generateId(finalLabel, `${filePath}:${qualifiedName}${arityTag}`);
203
321
  functionIdCache.set(node, result);
204
322
  return result;
205
323
  }
@@ -215,7 +333,41 @@ const findEnclosingFunctionId = (node, filePath, provider) => {
215
333
  if (override !== null)
216
334
  finalLabel = override;
217
335
  }
218
- const result = generateId(finalLabel, `${filePath}:${customResult.funcName}`);
336
+ // Qualify custom result with enclosing class
337
+ const classInfo = cachedFindEnclosingClassInfo(current.previousSibling ?? current, filePath);
338
+ const qualifiedName = classInfo
339
+ ? `${classInfo.className}.${customResult.funcName}`
340
+ : customResult.funcName;
341
+ // Include #<arity> suffix to match definition-phase Method/Constructor IDs.
342
+ // When same-arity collisions exist, also append ~type1,type2.
343
+ const sigNode = current.previousSibling ?? current;
344
+ let arity2;
345
+ let encTypeTag2 = '';
346
+ if (finalLabel === 'Method' || finalLabel === 'Constructor') {
347
+ const encLang2 = getLanguageFromFilename(filePath);
348
+ const classNode2 = findEnclosingClassNode(sigNode) ?? findClassNodeByQualifiedName(sigNode);
349
+ if (classNode2 && encLang2) {
350
+ const methodMap2 = getMethodInfo(classNode2, provider, {
351
+ filePath,
352
+ language: encLang2,
353
+ });
354
+ const defLine2 = sigNode.startPosition.row + 1;
355
+ const info2 = methodMap2?.get(`${customResult.funcName}:${defLine2}`);
356
+ if (info2) {
357
+ arity2 = info2.parameters.some((p) => p.isVariadic)
358
+ ? undefined
359
+ : info2.parameters.length;
360
+ if (methodMap2 && arity2 !== undefined) {
361
+ const g2 = buildCollisionGroups(methodMap2);
362
+ encTypeTag2 =
363
+ typeTagForId(methodMap2, customResult.funcName, arity2, info2, encLang2, g2) +
364
+ constTagForId(methodMap2, customResult.funcName, arity2, info2, g2);
365
+ }
366
+ }
367
+ }
368
+ }
369
+ const arityTag2 = arity2 !== undefined ? `#${arity2}${encTypeTag2}` : '';
370
+ const result = generateId(finalLabel, `${filePath}:${qualifiedName}${arityTag2}`);
219
371
  functionIdCache.set(node, result);
220
372
  return result;
221
373
  }
@@ -225,12 +377,12 @@ const findEnclosingFunctionId = (node, filePath, provider) => {
225
377
  functionIdCache.set(node, null);
226
378
  return null;
227
379
  };
228
- /** Cached wrapper for findEnclosingClassId — avoids repeated parent walks. */
229
- const cachedFindEnclosingClassId = (node, filePath) => {
380
+ /** Cached wrapper for findEnclosingClassInfo — avoids repeated parent walks. */
381
+ const cachedFindEnclosingClassInfo = (node, filePath) => {
230
382
  const cached = classIdCache.get(node);
231
383
  if (cached !== undefined)
232
384
  return cached;
233
- const result = findEnclosingClassId(node, filePath);
385
+ const result = findEnclosingClassInfo(node, filePath);
234
386
  classIdCache.set(node, result);
235
387
  return result;
236
388
  };
@@ -263,7 +415,7 @@ const processBatch = (files, onProgress) => {
263
415
  toolDefs: [],
264
416
  ormQueries: [],
265
417
  constructorBindings: [],
266
- typeEnvBindings: [],
418
+ fileScopeBindings: [],
267
419
  skippedLanguages: {},
268
420
  fileCount: 0,
269
421
  };
@@ -375,6 +527,27 @@ const EXPRESS_ROUTE_METHODS = new Set([
375
527
  // the express_route handler as route definitions, not consumers. The fetch() global
376
528
  // function is captured separately by the route.fetch query.
377
529
  const HTTP_CLIENT_ONLY_METHODS = new Set(['head', 'options', 'request', 'ajax']);
530
+ // Known HTTP client receivers u2014 skip these, they're API consumers not routes
531
+ const HTTP_CLIENT_RECEIVERS = new Set([
532
+ 'axios',
533
+ 'request',
534
+ 'fetch',
535
+ 'http',
536
+ 'https',
537
+ 'got',
538
+ 'ky',
539
+ 'superagent',
540
+ 'needle',
541
+ 'undici',
542
+ 'apiclient',
543
+ 'client',
544
+ 'httpclient',
545
+ 'api',
546
+ '$http',
547
+ 'session',
548
+ 'httpservice',
549
+ 'conn',
550
+ ]);
378
551
  // Decorator names that indicate HTTP route handlers (NestJS, Flask, FastAPI, Spring)
379
552
  const ROUTE_DECORATOR_NAMES = new Set([
380
553
  'Get',
@@ -684,24 +857,28 @@ function extractLaravelRoutes(tree, filePath) {
684
857
  });
685
858
  }
686
859
  }
687
- function walk(node, groupStack) {
860
+ const walkStack = [{ node: tree.rootNode, groupSnapshot: [] }];
861
+ while (walkStack.length > 0) {
862
+ const { node, groupSnapshot } = walkStack.pop();
688
863
  // Case 1: Simple Route::get(...), Route::post(...), etc.
689
864
  if (isRouteStaticCall(node)) {
690
865
  const method = getCallMethodName(node);
691
866
  if (method && (ROUTE_HTTP_METHODS.has(method) || ROUTE_RESOURCE_METHODS.has(method))) {
692
- emitRoute(method, getArguments(node), node.startPosition.row, groupStack, []);
693
- return;
867
+ emitRoute(method, getArguments(node), node.startPosition.row, groupSnapshot, []);
868
+ continue;
694
869
  }
695
870
  if (method === 'group') {
696
871
  const argsNode = getArguments(node);
697
872
  const groupCtx = parseArrayGroupArgs(argsNode);
698
873
  const body = findClosureBody(argsNode);
699
874
  if (body) {
700
- groupStack.push(groupCtx);
701
- walkChildren(body, groupStack);
702
- groupStack.pop();
875
+ const childSnapshot = [...groupSnapshot, groupCtx];
876
+ const children = body.children ?? [];
877
+ for (let i = children.length - 1; i >= 0; i--) {
878
+ walkStack.push({ node: children[i], groupSnapshot: childSnapshot });
879
+ }
703
880
  }
704
- return;
881
+ continue;
705
882
  }
706
883
  }
707
884
  // Case 2: Fluent chain — Route::middleware(...)->group(...) or Route::middleware(...)->get(...)
@@ -719,27 +896,26 @@ function extractLaravelRoutes(tree, filePath) {
719
896
  }
720
897
  const body = findClosureBody(chain.terminalArgs);
721
898
  if (body) {
722
- groupStack.push(groupCtx);
723
- walkChildren(body, groupStack);
724
- groupStack.pop();
899
+ const childSnapshot = [...groupSnapshot, groupCtx];
900
+ const children = body.children ?? [];
901
+ for (let i = children.length - 1; i >= 0; i--) {
902
+ walkStack.push({ node: children[i], groupSnapshot: childSnapshot });
903
+ }
725
904
  }
726
- return;
905
+ continue;
727
906
  }
728
907
  if (ROUTE_HTTP_METHODS.has(chain.terminalMethod) ||
729
908
  ROUTE_RESOURCE_METHODS.has(chain.terminalMethod)) {
730
- emitRoute(chain.terminalMethod, chain.terminalArgs, node.startPosition.row, groupStack, chain.attributes);
731
- return;
909
+ emitRoute(chain.terminalMethod, chain.terminalArgs, node.startPosition.row, groupSnapshot, chain.attributes);
910
+ continue;
732
911
  }
733
912
  }
734
- // Default: recurse into children
735
- walkChildren(node, groupStack);
736
- }
737
- function walkChildren(node, groupStack) {
738
- for (const child of node.children ?? []) {
739
- walk(child, groupStack);
913
+ // Default: push children in reverse so leftmost is processed first
914
+ const children = node.children ?? [];
915
+ for (let i = children.length - 1; i >= 0; i--) {
916
+ walkStack.push({ node: children[i], groupSnapshot });
740
917
  }
741
918
  }
742
- walk(tree.rootNode, []);
743
919
  return routes;
744
920
  }
745
921
  // ============================================================================
@@ -806,11 +982,23 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
806
982
  // Skip files larger than the max tree-sitter buffer (32 MB)
807
983
  if (file.content.length > TREE_SITTER_MAX_BUFFER)
808
984
  continue;
985
+ // Vue SFC preprocessing: extract <script> block content
986
+ let parseContent = file.content;
987
+ let lineOffset = 0;
988
+ let isVueSetup = false;
989
+ if (language === SupportedLanguages.Vue) {
990
+ const extracted = extractVueScript(file.content);
991
+ if (!extracted)
992
+ continue; // skip .vue files with no script block
993
+ parseContent = extracted.scriptContent;
994
+ lineOffset = extracted.lineOffset;
995
+ isVueSetup = extracted.isSetup;
996
+ }
809
997
  clearCaches(); // Reset memoization before each new file
810
998
  let tree;
811
999
  try {
812
- tree = parser.parse(file.content, undefined, {
813
- bufferSize: getTreeSitterBufferSize(file.content.length),
1000
+ tree = parser.parse(parseContent, undefined, {
1001
+ bufferSize: getTreeSitterBufferSize(parseContent.length),
814
1002
  });
815
1003
  }
816
1004
  catch (err) {
@@ -861,6 +1049,7 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
861
1049
  const typeEnv = buildTypeEnv(tree, language, {
862
1050
  parentMap,
863
1051
  enclosingFunctionFinder: provider?.enclosingFunctionFinder,
1052
+ extractFunctionName: provider?.methodExtractor?.extractFunctionName,
864
1053
  });
865
1054
  const callRouter = provider.callRouter;
866
1055
  if (typeEnv.constructorBindings.length > 0) {
@@ -869,15 +1058,23 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
869
1058
  bindings: [...typeEnv.constructorBindings],
870
1059
  });
871
1060
  }
872
- // Extract file-scope bindings for ExportedTypeMap (closes worker/sequential quality gap).
873
- // Sequential path uses collectExportedBindings(typeEnv) directly; worker path serializes
874
- // these bindings so the main thread can merge them into ExportedTypeMap.
1061
+ // Serialize file-scope bindings for BindingAccumulator. These feed the
1062
+ // ExportedTypeMap enrichment loop in pipeline.ts the only current
1063
+ // consumer of worker-path binding data.
1064
+ //
1065
+ // Historical note: we previously serialized all scopes
1066
+ // (`typeEnv.allScopes()`), which pushed ~4.9 MB of function-scope
1067
+ // bindings across the IPC boundary on every worker batch with zero
1068
+ // downstream readers. Narrowing to `fileScope()` recovers that cost.
1069
+ // See the `FileScopeBindings` JSDoc above for the Phase 9 reversion
1070
+ // path when a function-scope consumer lands.
875
1071
  const fileScope = typeEnv.fileScope();
876
1072
  if (fileScope.size > 0) {
877
- const bindings = [];
878
- for (const [name, type] of fileScope)
879
- bindings.push([name, type]);
880
- result.typeEnvBindings.push({ filePath: file.path, bindings });
1073
+ const scopeBindings = [];
1074
+ for (const [varName, typeName] of fileScope) {
1075
+ scopeBindings.push([varName, typeName]);
1076
+ }
1077
+ result.fileScopeBindings.push({ filePath: file.path, bindings: scopeBindings });
881
1078
  }
882
1079
  // Per-file map: decorator end-line → decorator info, for associating with definitions
883
1080
  const fileDecorators = new Map();
@@ -946,7 +1143,7 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
946
1143
  routePath,
947
1144
  httpMethod,
948
1145
  decoratorName,
949
- lineNumber: decoratorNode.startPosition.row,
1146
+ lineNumber: decoratorNode.startPosition.row + lineOffset,
950
1147
  });
951
1148
  }
952
1149
  // MCP/RPC tool detection: @mcp.tool(), @app.tool(), @server.tool()
@@ -967,7 +1164,7 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
967
1164
  result.fetchCalls.push({
968
1165
  filePath: file.path,
969
1166
  fetchURL: urlNode.text,
970
- lineNumber: captureMap['route.fetch'].startPosition.row,
1167
+ lineNumber: captureMap['route.fetch'].startPosition.row + lineOffset,
971
1168
  });
972
1169
  }
973
1170
  continue;
@@ -982,7 +1179,7 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
982
1179
  result.fetchCalls.push({
983
1180
  filePath: file.path,
984
1181
  fetchURL: url,
985
- lineNumber: captureMap['http_client'].startPosition.row,
1182
+ lineNumber: captureMap['http_client'].startPosition.row + lineOffset,
986
1183
  });
987
1184
  }
988
1185
  continue;
@@ -994,6 +1191,45 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
994
1191
  const method = captureMap['express_route.method'].text;
995
1192
  const routePath = captureMap['express_route.path'].text;
996
1193
  if (EXPRESS_ROUTE_METHODS.has(method) && routePath.startsWith('/')) {
1194
+ // Extract the receiver (the object the method is called on) to filter out
1195
+ // HTTP client calls like axios.get('/api/users') that match the same pattern
1196
+ // as Express route registrations.
1197
+ const callNode = captureMap['express_route'];
1198
+ const funcNode = callNode.childForFieldName?.('function') ?? callNode.children?.[0];
1199
+ // Walk through nested member_expressions and call_expressions to
1200
+ // reach the innermost receiver identifier. Handles chains like:
1201
+ // this.httpService.get('/path') -> member chain -> 'httpservice'
1202
+ // getClient().get('/path') -> call_expression -> 'getclient'
1203
+ // axios.get('/path') -> bare identifier -> 'axios'
1204
+ let receiverNode = funcNode?.childForFieldName?.('object') ?? funcNode?.children?.[0];
1205
+ while (receiverNode?.type === 'member_expression' ||
1206
+ receiverNode?.type === 'call_expression') {
1207
+ if (receiverNode.type === 'member_expression') {
1208
+ // Drill into the property (rightmost part) of the member expression
1209
+ const propNode = receiverNode.childForFieldName?.('property');
1210
+ if (propNode) {
1211
+ receiverNode = propNode;
1212
+ }
1213
+ else {
1214
+ break;
1215
+ }
1216
+ }
1217
+ else {
1218
+ // call_expression: unwrap to the function being called
1219
+ const innerFunc = receiverNode.childForFieldName?.('function') ?? receiverNode.children?.[0];
1220
+ if (innerFunc && innerFunc !== receiverNode) {
1221
+ receiverNode = innerFunc;
1222
+ }
1223
+ else {
1224
+ break;
1225
+ }
1226
+ }
1227
+ }
1228
+ const receiverText = receiverNode?.text?.toLowerCase() ?? '';
1229
+ if (HTTP_CLIENT_RECEIVERS.has(receiverText)) {
1230
+ // This is an HTTP client call, not a route definition u2014 skip it
1231
+ continue;
1232
+ }
997
1233
  const httpMethod = method === 'all' || method === 'use' || method === 'route'
998
1234
  ? 'GET'
999
1235
  : method.toUpperCase();
@@ -1002,7 +1238,7 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
1002
1238
  routePath,
1003
1239
  httpMethod,
1004
1240
  decoratorName: `express.${method}`,
1005
- lineNumber: captureMap['express_route'].startPosition.row,
1241
+ lineNumber: captureMap['express_route'].startPosition.row + lineOffset,
1006
1242
  });
1007
1243
  }
1008
1244
  continue;
@@ -1069,7 +1305,8 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
1069
1305
  continue;
1070
1306
  }
1071
1307
  if (routed.kind === 'properties') {
1072
- const propEnclosingClassId = cachedFindEnclosingClassId(captureMap['call'], file.path);
1308
+ const propEnclosingInfo = cachedFindEnclosingClassInfo(captureMap['call'], file.path);
1309
+ const propEnclosingClassId = propEnclosingInfo?.classId ?? null;
1073
1310
  // Enrich routed properties with FieldExtractor metadata
1074
1311
  let routedFieldMap;
1075
1312
  if (provider.fieldExtractor && typeEnv) {
@@ -1085,7 +1322,10 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
1085
1322
  }
1086
1323
  for (const item of routed.items) {
1087
1324
  const routedFieldInfo = routedFieldMap?.get(item.propName);
1088
- const nodeId = generateId('Property', `${file.path}:${item.propName}`);
1325
+ const propQualifiedName = propEnclosingInfo
1326
+ ? `${propEnclosingInfo.className}.${item.propName}`
1327
+ : item.propName;
1328
+ const nodeId = generateId('Property', `${file.path}:${propQualifiedName}`);
1089
1329
  result.nodes.push({
1090
1330
  id: nodeId,
1091
1331
  label: 'Property',
@@ -1250,25 +1490,119 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
1250
1490
  continue;
1251
1491
  }
1252
1492
  }
1253
- const nodeLabel = getLabelFromCaptures(captureMap, provider);
1254
- if (!nodeLabel)
1493
+ const definitionNode = getDefinitionNodeFromCaptures(captureMap);
1494
+ const defaultNodeLabel = getLabelFromCaptures(captureMap, provider);
1495
+ if (!defaultNodeLabel)
1255
1496
  continue;
1256
1497
  const nameNode = captureMap['name'];
1498
+ const extractedClassSymbol = definitionNode && provider.classExtractor?.isTypeDeclaration(definitionNode)
1499
+ ? provider.classExtractor.extract(definitionNode, {
1500
+ name: nameNode?.text,
1501
+ type: defaultNodeLabel,
1502
+ })
1503
+ : null;
1504
+ const nodeLabel = extractedClassSymbol?.type ?? defaultNodeLabel;
1257
1505
  // Synthesize name for constructors without explicit @name capture (e.g. Swift init)
1258
- if (!nameNode && nodeLabel !== 'Constructor')
1506
+ if (!nameNode && nodeLabel !== 'Constructor' && !extractedClassSymbol)
1259
1507
  continue;
1260
- const nodeName = nameNode ? nameNode.text : 'init';
1261
- const definitionNode = getDefinitionNodeFromCaptures(captureMap);
1508
+ const nodeName = extractedClassSymbol?.name ?? (nameNode ? nameNode.text : 'init');
1262
1509
  const startLine = definitionNode
1263
- ? definitionNode.startPosition.row
1510
+ ? definitionNode.startPosition.row + lineOffset
1264
1511
  : nameNode
1265
- ? nameNode.startPosition.row
1266
- : 0;
1267
- const nodeId = generateId(nodeLabel, `${file.path}:${nodeName}`);
1512
+ ? nameNode.startPosition.row + lineOffset
1513
+ : lineOffset;
1514
+ // Compute enclosing class BEFORE node ID — needed to qualify method IDs
1515
+ const needsOwner = nodeLabel === 'Method' ||
1516
+ nodeLabel === 'Constructor' ||
1517
+ nodeLabel === 'Property' ||
1518
+ nodeLabel === 'Function';
1519
+ const enclosingClassInfo = needsOwner
1520
+ ? cachedFindEnclosingClassInfo(nameNode || definitionNode, file.path)
1521
+ : null;
1522
+ const enclosingClassId = enclosingClassInfo?.classId ?? null;
1523
+ // Qualify method/property IDs with enclosing class name to avoid collisions
1524
+ const qualifiedName = enclosingClassInfo
1525
+ ? `${enclosingClassInfo.className}.${nodeName}`
1526
+ : nodeName;
1527
+ // Extract method metadata BEFORE generating node ID — parameterCount is needed
1528
+ // to disambiguate overloaded methods via #<arity> suffix in the ID.
1529
+ let declaredType;
1530
+ let methodProps = {};
1531
+ let arityForId; // raw param count for ID, even for variadic
1532
+ let defMethodMap;
1533
+ let defMethodInfo;
1534
+ if (nodeLabel === 'Function' || nodeLabel === 'Method' || nodeLabel === 'Constructor') {
1535
+ // Use MethodExtractor for method metadata — provides parameterCount, parameterTypes,
1536
+ // returnType, isAbstract/isFinal/annotations, visibility, and more.
1537
+ let enrichedByMethodExtractor = false;
1538
+ if (provider.methodExtractor && definitionNode) {
1539
+ const classNode = findEnclosingClassNode(definitionNode) ?? findClassNodeByQualifiedName(definitionNode);
1540
+ if (classNode) {
1541
+ const methodMap = getMethodInfo(classNode, provider, {
1542
+ filePath: file.path,
1543
+ language,
1544
+ });
1545
+ const defLine = definitionNode.startPosition.row + 1;
1546
+ const info = methodMap?.get(`${nodeName}:${defLine}`);
1547
+ if (info) {
1548
+ enrichedByMethodExtractor = true;
1549
+ arityForId = arityForIdFromInfo(info);
1550
+ methodProps = buildMethodProps(info);
1551
+ defMethodMap = methodMap;
1552
+ defMethodInfo = info;
1553
+ }
1554
+ }
1555
+ }
1556
+ // For top-level methods (e.g. Go method_declaration), try extractFromNode
1557
+ if (!enrichedByMethodExtractor &&
1558
+ provider.methodExtractor?.extractFromNode &&
1559
+ definitionNode) {
1560
+ const info = provider.methodExtractor.extractFromNode(definitionNode, {
1561
+ filePath: file.path,
1562
+ language,
1563
+ });
1564
+ if (info) {
1565
+ enrichedByMethodExtractor = true;
1566
+ arityForId = arityForIdFromInfo(info);
1567
+ methodProps = buildMethodProps(info);
1568
+ }
1569
+ }
1570
+ }
1571
+ // Append #<paramCount> to Method/Constructor IDs to disambiguate overloads.
1572
+ // Functions are not suffixed — they don't overload by name in the same scope.
1573
+ // When same-arity collisions exist, append ~type1,type2 for further disambiguation.
1574
+ const needsAritySuffix = nodeLabel === 'Method' || nodeLabel === 'Constructor';
1575
+ let arityTag = needsAritySuffix && arityForId !== undefined ? `#${arityForId}` : '';
1576
+ if (arityTag && defMethodMap && defMethodInfo) {
1577
+ const groups = buildCollisionGroups(defMethodMap);
1578
+ arityTag += typeTagForId(defMethodMap, nodeName, arityForId, defMethodInfo, language, groups);
1579
+ arityTag += constTagForId(defMethodMap, nodeName, arityForId, defMethodInfo, groups);
1580
+ }
1581
+ const nodeId = generateId(nodeLabel, `${file.path}:${qualifiedName}${arityTag}`);
1582
+ const classNodeForSymbol = definitionNode || nameNode;
1583
+ const qualifiedTypeName = extractedClassSymbol?.qualifiedName ??
1584
+ (classNodeForSymbol && provider.classExtractor?.isTypeDeclaration(classNodeForSymbol)
1585
+ ? (provider.classExtractor.extractQualifiedName(classNodeForSymbol, nodeName) ?? nodeName)
1586
+ : undefined);
1268
1587
  const description = provider.descriptionExtractor?.(nodeLabel, nodeName, captureMap);
1269
1588
  let frameworkHint = definitionNode
1270
1589
  ? detectFrameworkFromAST(language, (definitionNode.text || '').slice(0, 300))
1271
1590
  : null;
1591
+ // Suppress Spring framework hint for methods inside interfaces
1592
+ // (Feign clients, JAX-RS proxies are consumers, not providers)
1593
+ if (frameworkHint && definitionNode) {
1594
+ let classCheck = definitionNode.parent;
1595
+ while (classCheck) {
1596
+ if (classCheck.type === 'interface_declaration') {
1597
+ frameworkHint = null;
1598
+ break;
1599
+ }
1600
+ if (classCheck.type === 'class_declaration' || classCheck.type === 'program') {
1601
+ break;
1602
+ }
1603
+ classCheck = classCheck.parent;
1604
+ }
1605
+ }
1272
1606
  // Decorators appear on lines immediately before their definition; allow up to
1273
1607
  // MAX_DECORATOR_SCAN_LINES gap for blank lines / multi-line decorator stacks.
1274
1608
  const MAX_DECORATOR_SCAN_LINES = 5;
@@ -1291,94 +1625,15 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
1291
1625
  filePath: file.path,
1292
1626
  toolName: nodeName,
1293
1627
  description: dec.arg || '',
1294
- lineNumber: definitionNode.startPosition.row,
1628
+ lineNumber: definitionNode.startPosition.row + lineOffset,
1295
1629
  });
1296
1630
  }
1297
1631
  fileDecorators.delete(checkLine);
1298
1632
  }
1299
1633
  }
1300
1634
  }
1301
- let parameterCount;
1302
- let requiredParameterCount;
1303
- let parameterTypes;
1304
- let returnType;
1305
- let declaredType;
1306
- let visibility;
1307
- let isStatic;
1308
- let isReadonly;
1309
- let isAbstract;
1310
- let isFinal;
1311
- let isVirtual;
1312
- let isOverride;
1313
- let isAsync;
1314
- let isPartial;
1315
- let annotations;
1316
- if (nodeLabel === 'Function' || nodeLabel === 'Method' || nodeLabel === 'Constructor') {
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
- }
1369
- // Language-specific return type fallback (e.g. Ruby YARD @return [Type])
1370
- // Also upgrades uninformative AST types like PHP `array` with PHPDoc `@return User[]`
1371
- if ((!returnType || returnType === 'array' || returnType === 'iterable') &&
1372
- definitionNode) {
1373
- const tc = provider.typeConfig;
1374
- if (tc?.extractReturnType) {
1375
- const docReturn = tc.extractReturnType(definitionNode);
1376
- if (docReturn)
1377
- returnType = docReturn;
1378
- }
1379
- }
1380
- }
1381
- else if (nodeLabel === 'Property' && definitionNode) {
1635
+ // Property metadata extraction (not needed before nodeId — Properties don't overload)
1636
+ if (nodeLabel === 'Property' && definitionNode) {
1382
1637
  // FieldExtractor is the single source of truth when available
1383
1638
  if (provider.fieldExtractor && typeEnv) {
1384
1639
  const classNode = findEnclosingClassNode(definitionNode);
@@ -1392,9 +1647,9 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
1392
1647
  const info = fieldMap?.get(nodeName);
1393
1648
  if (info) {
1394
1649
  declaredType = info.type ?? undefined;
1395
- visibility = info.visibility;
1396
- isStatic = info.isStatic;
1397
- isReadonly = info.isReadonly;
1650
+ methodProps.visibility = info.visibility;
1651
+ methodProps.isStatic = info.isStatic;
1652
+ methodProps.isReadonly = info.isReadonly;
1398
1653
  }
1399
1654
  }
1400
1655
  }
@@ -1405,10 +1660,13 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
1405
1660
  properties: {
1406
1661
  name: nodeName,
1407
1662
  filePath: file.path,
1408
- startLine: definitionNode ? definitionNode.startPosition.row : startLine,
1409
- endLine: definitionNode ? definitionNode.endPosition.row : startLine,
1663
+ startLine: definitionNode ? definitionNode.startPosition.row + lineOffset : startLine,
1664
+ endLine: definitionNode ? definitionNode.endPosition.row + lineOffset : startLine,
1410
1665
  language: language,
1411
- isExported: cachedExportCheck(provider.exportChecker, nameNode || definitionNode, nodeName),
1666
+ isExported: language === SupportedLanguages.Vue && isVueSetup
1667
+ ? isVueSetupTopLevel(nameNode || definitionNode)
1668
+ : cachedExportCheck(provider.exportChecker, nameNode || definitionNode, nodeName),
1669
+ ...(qualifiedTypeName !== undefined ? { qualifiedName: qualifiedTypeName } : {}),
1412
1670
  ...(frameworkHint
1413
1671
  ? {
1414
1672
  astFrameworkMultiplier: frameworkHint.entryPointMultiplier,
@@ -1416,53 +1674,41 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
1416
1674
  }
1417
1675
  : {}),
1418
1676
  ...(description !== undefined ? { description } : {}),
1419
- ...(parameterCount !== undefined ? { parameterCount } : {}),
1420
- ...(requiredParameterCount !== undefined ? { requiredParameterCount } : {}),
1421
- ...(parameterTypes !== undefined ? { parameterTypes } : {}),
1422
- ...(returnType !== undefined ? { returnType } : {}),
1677
+ ...methodProps,
1423
1678
  ...(declaredType !== undefined ? { declaredType } : {}),
1424
- ...(visibility !== undefined ? { visibility } : {}),
1425
- ...(isStatic !== undefined ? { isStatic } : {}),
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 } : {}),
1434
1679
  },
1435
1680
  });
1436
- // Compute enclosing class for Method/Constructor/Property/Function used for both ownerId and HAS_METHOD
1437
- // Function is included because Kotlin/Rust/Python capture class methods as Function nodes
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;
1681
+ // enclosingClassId already computed above (before nodeId generation)
1445
1682
  result.symbols.push({
1446
1683
  filePath: file.path,
1447
1684
  name: nodeName,
1448
1685
  nodeId,
1449
1686
  type: nodeLabel,
1450
- ...(parameterCount !== undefined ? { parameterCount } : {}),
1451
- ...(requiredParameterCount !== undefined ? { requiredParameterCount } : {}),
1452
- ...(parameterTypes !== undefined ? { parameterTypes } : {}),
1453
- ...(returnType !== undefined ? { returnType } : {}),
1687
+ ...(qualifiedTypeName !== undefined ? { qualifiedName: qualifiedTypeName } : {}),
1688
+ parameterCount: methodProps.parameterCount,
1689
+ requiredParameterCount: methodProps.requiredParameterCount,
1690
+ parameterTypes: methodProps.parameterTypes,
1691
+ returnType: methodProps.returnType,
1454
1692
  ...(declaredType !== undefined ? { declaredType } : {}),
1455
1693
  ...(enclosingClassId ? { ownerId: enclosingClassId } : {}),
1456
- ...(visibility !== undefined ? { visibility } : {}),
1457
- ...(isStatic !== undefined ? { isStatic } : {}),
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 } : {}),
1694
+ visibility: methodProps.visibility,
1695
+ isStatic: methodProps.isStatic,
1696
+ isReadonly: methodProps.isReadonly,
1697
+ isAbstract: methodProps.isAbstract,
1698
+ isFinal: methodProps.isFinal,
1699
+ ...(methodProps.isVirtual !== undefined
1700
+ ? { isVirtual: methodProps.isVirtual }
1701
+ : {}),
1702
+ ...(methodProps.isOverride !== undefined
1703
+ ? { isOverride: methodProps.isOverride }
1704
+ : {}),
1705
+ ...(methodProps.isAsync !== undefined ? { isAsync: methodProps.isAsync } : {}),
1706
+ ...(methodProps.isPartial !== undefined
1707
+ ? { isPartial: methodProps.isPartial }
1708
+ : {}),
1709
+ ...(methodProps.annotations !== undefined
1710
+ ? { annotations: methodProps.annotations }
1711
+ : {}),
1466
1712
  });
1467
1713
  const fileId = generateId('File', file.path);
1468
1714
  const relId = generateId('DEFINES', `${fileId}->${nodeId}`);
@@ -1493,7 +1739,19 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
1493
1739
  result.routes.push(...extractedRoutes);
1494
1740
  }
1495
1741
  // Extract ORM queries (Prisma, Supabase)
1496
- extractORMQueries(file.path, file.content, result.ormQueries);
1742
+ extractORMQueries(file.path, parseContent, result.ormQueries);
1743
+ // Vue: emit CALLS edges for components used in <template>
1744
+ if (language === SupportedLanguages.Vue) {
1745
+ const templateComponents = extractTemplateComponents(file.content);
1746
+ for (const componentName of templateComponents) {
1747
+ result.calls.push({
1748
+ filePath: file.path,
1749
+ calledName: componentName,
1750
+ sourceId: generateId('File', file.path),
1751
+ callForm: 'free',
1752
+ });
1753
+ }
1754
+ }
1497
1755
  }
1498
1756
  };
1499
1757
  // ============================================================================
@@ -1514,26 +1772,33 @@ let accumulated = {
1514
1772
  toolDefs: [],
1515
1773
  ormQueries: [],
1516
1774
  constructorBindings: [],
1517
- typeEnvBindings: [],
1775
+ fileScopeBindings: [],
1518
1776
  skippedLanguages: {},
1519
1777
  fileCount: 0,
1520
1778
  };
1521
1779
  let cumulativeProcessed = 0;
1780
+ // Use a loop instead of push(...spread) to avoid hitting V8's argument limit
1781
+ // when merging large result sets (push(...arr) calls apply() under the hood
1782
+ // and blows the stack when arr has >~65k elements).
1783
+ const appendAll = (target, src) => {
1784
+ for (let i = 0; i < src.length; i++)
1785
+ target.push(src[i]);
1786
+ };
1522
1787
  const mergeResult = (target, src) => {
1523
- target.nodes.push(...src.nodes);
1524
- target.relationships.push(...src.relationships);
1525
- target.symbols.push(...src.symbols);
1526
- target.imports.push(...src.imports);
1527
- target.calls.push(...src.calls);
1528
- target.assignments.push(...src.assignments);
1529
- target.heritage.push(...src.heritage);
1530
- target.routes.push(...src.routes);
1531
- target.fetchCalls.push(...src.fetchCalls);
1532
- target.decoratorRoutes.push(...src.decoratorRoutes);
1533
- target.toolDefs.push(...src.toolDefs);
1534
- target.ormQueries.push(...src.ormQueries);
1535
- target.constructorBindings.push(...src.constructorBindings);
1536
- target.typeEnvBindings.push(...src.typeEnvBindings);
1788
+ appendAll(target.nodes, src.nodes);
1789
+ appendAll(target.relationships, src.relationships);
1790
+ appendAll(target.symbols, src.symbols);
1791
+ appendAll(target.imports, src.imports);
1792
+ appendAll(target.calls, src.calls);
1793
+ appendAll(target.assignments, src.assignments);
1794
+ appendAll(target.heritage, src.heritage);
1795
+ appendAll(target.routes, src.routes);
1796
+ appendAll(target.fetchCalls, src.fetchCalls);
1797
+ appendAll(target.decoratorRoutes, src.decoratorRoutes);
1798
+ appendAll(target.toolDefs, src.toolDefs);
1799
+ appendAll(target.ormQueries, src.ormQueries);
1800
+ appendAll(target.constructorBindings, src.constructorBindings);
1801
+ appendAll(target.fileScopeBindings, src.fileScopeBindings);
1537
1802
  for (const [lang, count] of Object.entries(src.skippedLanguages)) {
1538
1803
  target.skippedLanguages[lang] = (target.skippedLanguages[lang] || 0) + count;
1539
1804
  }
@@ -1581,7 +1846,7 @@ parentPort.on('message', (msg) => {
1581
1846
  toolDefs: [],
1582
1847
  ormQueries: [],
1583
1848
  constructorBindings: [],
1584
- typeEnvBindings: [],
1849
+ fileScopeBindings: [],
1585
1850
  skippedLanguages: {},
1586
1851
  fileCount: 0,
1587
1852
  };