gitnexus 1.4.1 → 1.4.5

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 (169) hide show
  1. package/README.md +215 -194
  2. package/dist/cli/ai-context.d.ts +2 -1
  3. package/dist/cli/ai-context.js +117 -90
  4. package/dist/cli/analyze.d.ts +2 -0
  5. package/dist/cli/analyze.js +57 -30
  6. package/dist/cli/augment.js +1 -1
  7. package/dist/cli/eval-server.d.ts +1 -1
  8. package/dist/cli/eval-server.js +1 -1
  9. package/dist/cli/index.js +18 -25
  10. package/dist/cli/lazy-action.d.ts +6 -0
  11. package/dist/cli/lazy-action.js +18 -0
  12. package/dist/cli/mcp.js +1 -1
  13. package/dist/cli/setup.js +42 -32
  14. package/dist/cli/skill-gen.d.ts +26 -0
  15. package/dist/cli/skill-gen.js +549 -0
  16. package/dist/cli/status.js +13 -4
  17. package/dist/cli/tool.d.ts +1 -1
  18. package/dist/cli/tool.js +2 -2
  19. package/dist/cli/wiki.js +2 -2
  20. package/dist/config/ignore-service.d.ts +25 -0
  21. package/dist/config/ignore-service.js +76 -0
  22. package/dist/config/supported-languages.d.ts +1 -0
  23. package/dist/config/supported-languages.js +1 -1
  24. package/dist/core/augmentation/engine.js +99 -72
  25. package/dist/core/embeddings/embedder.d.ts +1 -1
  26. package/dist/core/embeddings/embedder.js +1 -1
  27. package/dist/core/embeddings/embedding-pipeline.d.ts +3 -3
  28. package/dist/core/embeddings/embedding-pipeline.js +74 -47
  29. package/dist/core/embeddings/types.d.ts +1 -1
  30. package/dist/core/graph/types.d.ts +5 -2
  31. package/dist/core/ingestion/ast-cache.js +3 -2
  32. package/dist/core/ingestion/call-processor.d.ts +6 -7
  33. package/dist/core/ingestion/call-processor.js +560 -282
  34. package/dist/core/ingestion/call-routing.d.ts +53 -0
  35. package/dist/core/ingestion/call-routing.js +108 -0
  36. package/dist/core/ingestion/cluster-enricher.js +16 -16
  37. package/dist/core/ingestion/constants.d.ts +16 -0
  38. package/dist/core/ingestion/constants.js +16 -0
  39. package/dist/core/ingestion/entry-point-scoring.d.ts +2 -1
  40. package/dist/core/ingestion/entry-point-scoring.js +94 -24
  41. package/dist/core/ingestion/export-detection.d.ts +18 -0
  42. package/dist/core/ingestion/export-detection.js +231 -0
  43. package/dist/core/ingestion/filesystem-walker.js +4 -3
  44. package/dist/core/ingestion/framework-detection.d.ts +5 -1
  45. package/dist/core/ingestion/framework-detection.js +48 -8
  46. package/dist/core/ingestion/heritage-processor.d.ts +13 -5
  47. package/dist/core/ingestion/heritage-processor.js +109 -55
  48. package/dist/core/ingestion/import-processor.d.ts +16 -20
  49. package/dist/core/ingestion/import-processor.js +202 -696
  50. package/dist/core/ingestion/language-config.d.ts +46 -0
  51. package/dist/core/ingestion/language-config.js +167 -0
  52. package/dist/core/ingestion/mro-processor.d.ts +45 -0
  53. package/dist/core/ingestion/mro-processor.js +369 -0
  54. package/dist/core/ingestion/named-binding-extraction.d.ts +61 -0
  55. package/dist/core/ingestion/named-binding-extraction.js +363 -0
  56. package/dist/core/ingestion/parsing-processor.d.ts +3 -11
  57. package/dist/core/ingestion/parsing-processor.js +82 -181
  58. package/dist/core/ingestion/pipeline.d.ts +5 -1
  59. package/dist/core/ingestion/pipeline.js +192 -116
  60. package/dist/core/ingestion/process-processor.js +2 -1
  61. package/dist/core/ingestion/resolution-context.d.ts +53 -0
  62. package/dist/core/ingestion/resolution-context.js +132 -0
  63. package/dist/core/ingestion/resolvers/csharp.d.ts +22 -0
  64. package/dist/core/ingestion/resolvers/csharp.js +109 -0
  65. package/dist/core/ingestion/resolvers/go.d.ts +19 -0
  66. package/dist/core/ingestion/resolvers/go.js +42 -0
  67. package/dist/core/ingestion/resolvers/index.d.ts +18 -0
  68. package/dist/core/ingestion/resolvers/index.js +13 -0
  69. package/dist/core/ingestion/resolvers/jvm.d.ts +23 -0
  70. package/dist/core/ingestion/resolvers/jvm.js +87 -0
  71. package/dist/core/ingestion/resolvers/php.d.ts +15 -0
  72. package/dist/core/ingestion/resolvers/php.js +35 -0
  73. package/dist/core/ingestion/resolvers/python.d.ts +19 -0
  74. package/dist/core/ingestion/resolvers/python.js +52 -0
  75. package/dist/core/ingestion/resolvers/ruby.d.ts +12 -0
  76. package/dist/core/ingestion/resolvers/ruby.js +15 -0
  77. package/dist/core/ingestion/resolvers/rust.d.ts +15 -0
  78. package/dist/core/ingestion/resolvers/rust.js +73 -0
  79. package/dist/core/ingestion/resolvers/standard.d.ts +28 -0
  80. package/dist/core/ingestion/resolvers/standard.js +123 -0
  81. package/dist/core/ingestion/resolvers/utils.d.ts +33 -0
  82. package/dist/core/ingestion/resolvers/utils.js +122 -0
  83. package/dist/core/ingestion/symbol-table.d.ts +15 -1
  84. package/dist/core/ingestion/symbol-table.js +20 -12
  85. package/dist/core/ingestion/tree-sitter-queries.d.ts +12 -11
  86. package/dist/core/ingestion/tree-sitter-queries.js +642 -485
  87. package/dist/core/ingestion/type-env.d.ts +49 -0
  88. package/dist/core/ingestion/type-env.js +559 -0
  89. package/dist/core/ingestion/type-extractors/c-cpp.d.ts +2 -0
  90. package/dist/core/ingestion/type-extractors/c-cpp.js +385 -0
  91. package/dist/core/ingestion/type-extractors/csharp.d.ts +2 -0
  92. package/dist/core/ingestion/type-extractors/csharp.js +369 -0
  93. package/dist/core/ingestion/type-extractors/go.d.ts +2 -0
  94. package/dist/core/ingestion/type-extractors/go.js +436 -0
  95. package/dist/core/ingestion/type-extractors/index.d.ts +22 -0
  96. package/dist/core/ingestion/type-extractors/index.js +31 -0
  97. package/dist/core/ingestion/type-extractors/jvm.d.ts +3 -0
  98. package/dist/core/ingestion/type-extractors/jvm.js +654 -0
  99. package/dist/core/ingestion/type-extractors/php.d.ts +2 -0
  100. package/dist/core/ingestion/type-extractors/php.js +411 -0
  101. package/dist/core/ingestion/type-extractors/python.d.ts +2 -0
  102. package/dist/core/ingestion/type-extractors/python.js +392 -0
  103. package/dist/core/ingestion/type-extractors/ruby.d.ts +2 -0
  104. package/dist/core/ingestion/type-extractors/ruby.js +389 -0
  105. package/dist/core/ingestion/type-extractors/rust.d.ts +2 -0
  106. package/dist/core/ingestion/type-extractors/rust.js +436 -0
  107. package/dist/core/ingestion/type-extractors/shared.d.ts +132 -0
  108. package/dist/core/ingestion/type-extractors/shared.js +571 -0
  109. package/dist/core/ingestion/type-extractors/swift.d.ts +2 -0
  110. package/dist/core/ingestion/type-extractors/swift.js +137 -0
  111. package/dist/core/ingestion/type-extractors/types.d.ts +95 -0
  112. package/dist/core/ingestion/type-extractors/types.js +1 -0
  113. package/dist/core/ingestion/type-extractors/typescript.d.ts +2 -0
  114. package/dist/core/ingestion/type-extractors/typescript.js +480 -0
  115. package/dist/core/ingestion/utils.d.ts +98 -0
  116. package/dist/core/ingestion/utils.js +1064 -9
  117. package/dist/core/ingestion/workers/parse-worker.d.ts +38 -4
  118. package/dist/core/ingestion/workers/parse-worker.js +248 -359
  119. package/dist/core/ingestion/workers/worker-pool.js +8 -0
  120. package/dist/core/{kuzu → lbug}/csv-generator.d.ts +1 -1
  121. package/dist/core/{kuzu → lbug}/csv-generator.js +20 -4
  122. package/dist/core/{kuzu/kuzu-adapter.d.ts → lbug/lbug-adapter.d.ts} +19 -19
  123. package/dist/core/{kuzu/kuzu-adapter.js → lbug/lbug-adapter.js} +82 -82
  124. package/dist/core/{kuzu → lbug}/schema.d.ts +4 -4
  125. package/dist/core/{kuzu → lbug}/schema.js +304 -289
  126. package/dist/core/search/bm25-index.d.ts +4 -4
  127. package/dist/core/search/bm25-index.js +17 -16
  128. package/dist/core/search/hybrid-search.d.ts +2 -2
  129. package/dist/core/search/hybrid-search.js +9 -9
  130. package/dist/core/tree-sitter/parser-loader.js +9 -2
  131. package/dist/core/wiki/generator.d.ts +4 -52
  132. package/dist/core/wiki/generator.js +53 -552
  133. package/dist/core/wiki/graph-queries.d.ts +4 -46
  134. package/dist/core/wiki/graph-queries.js +103 -282
  135. package/dist/core/wiki/html-viewer.js +192 -192
  136. package/dist/core/wiki/llm-client.js +11 -73
  137. package/dist/core/wiki/prompts.d.ts +8 -52
  138. package/dist/core/wiki/prompts.js +86 -200
  139. package/dist/mcp/compatible-stdio-transport.d.ts +25 -0
  140. package/dist/mcp/compatible-stdio-transport.js +200 -0
  141. package/dist/mcp/core/{kuzu-adapter.d.ts → lbug-adapter.d.ts} +7 -9
  142. package/dist/mcp/core/{kuzu-adapter.js → lbug-adapter.js} +77 -79
  143. package/dist/mcp/local/local-backend.d.ts +6 -6
  144. package/dist/mcp/local/local-backend.js +153 -146
  145. package/dist/mcp/resources.js +42 -42
  146. package/dist/mcp/server.js +18 -19
  147. package/dist/mcp/tools.js +103 -104
  148. package/dist/server/api.js +12 -12
  149. package/dist/server/mcp-http.d.ts +1 -1
  150. package/dist/server/mcp-http.js +1 -1
  151. package/dist/storage/repo-manager.d.ts +20 -2
  152. package/dist/storage/repo-manager.js +55 -1
  153. package/dist/types/pipeline.d.ts +1 -1
  154. package/hooks/claude/gitnexus-hook.cjs +238 -155
  155. package/hooks/claude/pre-tool-use.sh +79 -79
  156. package/hooks/claude/session-start.sh +42 -42
  157. package/package.json +98 -96
  158. package/scripts/patch-tree-sitter-swift.cjs +74 -74
  159. package/skills/gitnexus-cli.md +82 -82
  160. package/skills/gitnexus-debugging.md +89 -89
  161. package/skills/gitnexus-exploring.md +78 -78
  162. package/skills/gitnexus-guide.md +64 -64
  163. package/skills/gitnexus-impact-analysis.md +97 -97
  164. package/skills/gitnexus-pr-review.md +163 -163
  165. package/skills/gitnexus-refactoring.md +121 -121
  166. package/vendor/leiden/index.cjs +355 -355
  167. package/vendor/leiden/utils.cjs +392 -392
  168. package/dist/core/wiki/diagrams.d.ts +0 -27
  169. package/dist/core/wiki/diagrams.js +0 -163
@@ -9,11 +9,12 @@ import CPP from 'tree-sitter-cpp';
9
9
  import CSharp from 'tree-sitter-c-sharp';
10
10
  import Go from 'tree-sitter-go';
11
11
  import Rust from 'tree-sitter-rust';
12
- import Kotlin from 'tree-sitter-kotlin';
13
12
  import PHP from 'tree-sitter-php';
13
+ import Ruby from 'tree-sitter-ruby';
14
14
  import { createRequire } from 'node:module';
15
15
  import { SupportedLanguages } from '../../../config/supported-languages.js';
16
16
  import { LANGUAGE_QUERIES } from '../tree-sitter-queries.js';
17
+ import { getTreeSitterBufferSize, TREE_SITTER_MAX_BUFFER } from '../constants.js';
17
18
  // tree-sitter-swift is an optionalDependency — may not be installed
18
19
  const _require = createRequire(import.meta.url);
19
20
  let Swift = null;
@@ -21,9 +22,21 @@ try {
21
22
  Swift = _require('tree-sitter-swift');
22
23
  }
23
24
  catch { }
24
- import { findSiblingChild, getLanguageFromFilename } from '../utils.js';
25
+ // tree-sitter-kotlin is an optionalDependency may not be installed
26
+ let Kotlin = null;
27
+ try {
28
+ Kotlin = _require('tree-sitter-kotlin');
29
+ }
30
+ catch { }
31
+ import { getLanguageFromFilename, FUNCTION_NODE_TYPES, extractFunctionName, isBuiltInOrNoise, getDefinitionNodeFromCaptures, findEnclosingClassId, extractMethodSignature, countCallArguments, inferCallForm, extractReceiverName, extractReceiverNode, CALL_EXPRESSION_TYPES, extractCallChain, } from '../utils.js';
32
+ import { buildTypeEnv } from '../type-env.js';
33
+ import { isNodeExported } from '../export-detection.js';
25
34
  import { detectFrameworkFromAST } from '../framework-detection.js';
35
+ import { typeConfigs } from '../type-extractors/index.js';
26
36
  import { generateId } from '../../../lib/utils.js';
37
+ import { extractNamedBindings } from '../named-binding-extraction.js';
38
+ import { appendKotlinWildcard } from '../resolvers/index.js';
39
+ import { callRouters } from '../call-routing.js';
27
40
  // ============================================================================
28
41
  // Worker-local parser + language map
29
42
  // ============================================================================
@@ -39,10 +52,23 @@ const languageMap = {
39
52
  [SupportedLanguages.CSharp]: CSharp,
40
53
  [SupportedLanguages.Go]: Go,
41
54
  [SupportedLanguages.Rust]: Rust,
42
- [SupportedLanguages.Kotlin]: Kotlin,
55
+ ...(Kotlin ? { [SupportedLanguages.Kotlin]: Kotlin } : {}),
43
56
  [SupportedLanguages.PHP]: PHP.php_only,
57
+ [SupportedLanguages.Ruby]: Ruby,
44
58
  ...(Swift ? { [SupportedLanguages.Swift]: Swift } : {}),
45
59
  };
60
+ /**
61
+ * Check if a language grammar is available in this worker.
62
+ * Duplicated from parser-loader.ts because workers can't import from the main thread.
63
+ * Extra filePath parameter needed to distinguish .tsx from .ts (different grammars
64
+ * under the same SupportedLanguages.TypeScript key).
65
+ */
66
+ const isLanguageAvailable = (language, filePath) => {
67
+ const key = language === SupportedLanguages.TypeScript && filePath.endsWith('.tsx')
68
+ ? `${language}:tsx`
69
+ : language;
70
+ return key in languageMap && languageMap[key] != null;
71
+ };
46
72
  const setLanguage = (language, filePath) => {
47
73
  const key = language === SupportedLanguages.TypeScript && filePath.endsWith('.tsx')
48
74
  ? `${language}:tsx`
@@ -52,307 +78,24 @@ const setLanguage = (language, filePath) => {
52
78
  throw new Error(`Unsupported language: ${language}`);
53
79
  parser.setLanguage(lang);
54
80
  };
55
- // ============================================================================
56
- // Export detection (copied — needs AST parent traversal, can't cross threads)
57
- // ============================================================================
58
- const isNodeExported = (node, name, language) => {
59
- let current = node;
60
- switch (language) {
61
- case 'javascript':
62
- case 'typescript':
63
- while (current) {
64
- const type = current.type;
65
- if (type === 'export_statement' ||
66
- type === 'export_specifier' ||
67
- type === 'lexical_declaration' && current.parent?.type === 'export_statement') {
68
- return true;
69
- }
70
- if (current.text?.startsWith('export ')) {
71
- return true;
72
- }
73
- current = current.parent;
74
- }
75
- return false;
76
- case 'python':
77
- return !name.startsWith('_');
78
- case 'java':
79
- while (current) {
80
- if (current.parent) {
81
- const parent = current.parent;
82
- for (let i = 0; i < parent.childCount; i++) {
83
- const child = parent.child(i);
84
- if (child?.type === 'modifiers' && child.text?.includes('public')) {
85
- return true;
86
- }
87
- }
88
- if (parent.type === 'method_declaration' || parent.type === 'constructor_declaration') {
89
- if (parent.text?.trimStart().startsWith('public')) {
90
- return true;
91
- }
92
- }
93
- }
94
- current = current.parent;
95
- }
96
- return false;
97
- case 'csharp':
98
- while (current) {
99
- if (current.type === 'modifier' || current.type === 'modifiers') {
100
- if (current.text?.includes('public'))
101
- return true;
102
- }
103
- current = current.parent;
104
- }
105
- return false;
106
- case 'go':
107
- if (name.length === 0)
108
- return false;
109
- const first = name[0];
110
- return first === first.toUpperCase() && first !== first.toLowerCase();
111
- case 'rust':
112
- while (current) {
113
- if (current.type === 'visibility_modifier') {
114
- if (current.text?.includes('pub'))
115
- return true;
116
- }
117
- current = current.parent;
118
- }
119
- return false;
120
- // Kotlin: Default visibility is public (unlike Java)
121
- // visibility_modifier is inside modifiers, a sibling of the name node within the declaration
122
- case 'kotlin':
123
- while (current) {
124
- if (current.parent) {
125
- const visMod = findSiblingChild(current.parent, 'modifiers', 'visibility_modifier');
126
- if (visMod) {
127
- const text = visMod.text;
128
- if (text === 'private' || text === 'internal' || text === 'protected')
129
- return false;
130
- if (text === 'public')
131
- return true;
132
- }
133
- }
134
- current = current.parent;
135
- }
136
- // No visibility modifier = public (Kotlin default)
137
- return true;
138
- case 'c':
139
- case 'cpp':
140
- return false;
141
- case 'php':
142
- // Top-level classes/interfaces/traits are always accessible
143
- // Methods/properties are exported only if they have 'public' modifier
144
- while (current) {
145
- if (current.type === 'class_declaration' ||
146
- current.type === 'interface_declaration' ||
147
- current.type === 'trait_declaration' ||
148
- current.type === 'enum_declaration') {
149
- return true;
150
- }
151
- if (current.type === 'visibility_modifier') {
152
- return current.text === 'public';
153
- }
154
- current = current.parent;
155
- }
156
- // Top-level functions (no parent class) are globally accessible
157
- return true;
158
- case 'swift':
159
- while (current) {
160
- if (current.type === 'modifiers' || current.type === 'visibility_modifier') {
161
- const text = current.text || '';
162
- if (text.includes('public') || text.includes('open'))
163
- return true;
164
- }
165
- current = current.parent;
166
- }
167
- return false;
168
- default:
169
- return false;
170
- }
171
- };
81
+ // isNodeExported imported from ../export-detection.js (shared module)
172
82
  // ============================================================================
173
83
  // Enclosing function detection (for call extraction)
174
84
  // ============================================================================
175
- const FUNCTION_NODE_TYPES = new Set([
176
- 'function_declaration', 'arrow_function', 'function_expression',
177
- 'method_definition', 'generator_function_declaration',
178
- 'function_definition', 'async_function_declaration', 'async_arrow_function',
179
- 'method_declaration', 'constructor_declaration',
180
- 'local_function_statement', 'function_item', 'impl_item',
181
- // Kotlin
182
- 'lambda_literal',
183
- // PHP
184
- 'anonymous_function',
185
- // Swift initializers/deinitializers
186
- 'init_declaration', 'deinit_declaration',
187
- ]);
188
85
  /** Walk up AST to find enclosing function, return its generateId or null for top-level */
189
86
  const findEnclosingFunctionId = (node, filePath) => {
190
87
  let current = node.parent;
191
88
  while (current) {
192
89
  if (FUNCTION_NODE_TYPES.has(current.type)) {
193
- let funcName = null;
194
- let label = 'Function';
195
- if (current.type === 'init_declaration' || current.type === 'deinit_declaration') {
196
- const funcName = current.type === 'init_declaration' ? 'init' : 'deinit';
197
- const label = 'Constructor';
198
- const startLine = current.startPosition?.row ?? 0;
199
- return generateId(label, `${filePath}:${funcName}:${startLine}`);
200
- }
201
- if (['function_declaration', 'function_definition', 'async_function_declaration',
202
- 'generator_function_declaration', 'function_item'].includes(current.type)) {
203
- const nameNode = current.childForFieldName?.('name') ||
204
- current.children?.find((c) => c.type === 'identifier' || c.type === 'property_identifier');
205
- funcName = nameNode?.text;
206
- }
207
- else if (current.type === 'impl_item') {
208
- const funcItem = current.children?.find((c) => c.type === 'function_item');
209
- if (funcItem) {
210
- const nameNode = funcItem.childForFieldName?.('name') ||
211
- funcItem.children?.find((c) => c.type === 'identifier');
212
- funcName = nameNode?.text;
213
- label = 'Method';
214
- }
215
- }
216
- else if (current.type === 'method_definition') {
217
- const nameNode = current.childForFieldName?.('name') ||
218
- current.children?.find((c) => c.type === 'property_identifier');
219
- funcName = nameNode?.text;
220
- label = 'Method';
221
- }
222
- else if (current.type === 'method_declaration' || current.type === 'constructor_declaration') {
223
- const nameNode = current.childForFieldName?.('name') ||
224
- current.children?.find((c) => c.type === 'identifier');
225
- funcName = nameNode?.text;
226
- label = 'Method';
227
- }
228
- else if (current.type === 'arrow_function' || current.type === 'function_expression') {
229
- const parent = current.parent;
230
- if (parent?.type === 'variable_declarator') {
231
- const nameNode = parent.childForFieldName?.('name') ||
232
- parent.children?.find((c) => c.type === 'identifier');
233
- funcName = nameNode?.text;
234
- }
235
- }
90
+ const { funcName, label } = extractFunctionName(current);
236
91
  if (funcName) {
237
- const startLine = current.startPosition?.row ?? 0;
238
- return generateId(label, `${filePath}:${funcName}:${startLine}`);
92
+ return generateId(label, `${filePath}:${funcName}`);
239
93
  }
240
94
  }
241
95
  current = current.parent;
242
96
  }
243
97
  return null;
244
98
  };
245
- const BUILT_INS = new Set([
246
- // JavaScript/TypeScript
247
- 'console', 'log', 'warn', 'error', 'info', 'debug',
248
- 'setTimeout', 'setInterval', 'clearTimeout', 'clearInterval',
249
- 'parseInt', 'parseFloat', 'isNaN', 'isFinite',
250
- 'encodeURI', 'decodeURI', 'encodeURIComponent', 'decodeURIComponent',
251
- 'JSON', 'parse', 'stringify',
252
- 'Object', 'Array', 'String', 'Number', 'Boolean', 'Symbol', 'BigInt',
253
- 'Map', 'Set', 'WeakMap', 'WeakSet',
254
- 'Promise', 'resolve', 'reject', 'then', 'catch', 'finally',
255
- 'Math', 'Date', 'RegExp', 'Error',
256
- 'require', 'import', 'export', 'fetch', 'Response', 'Request',
257
- 'useState', 'useEffect', 'useCallback', 'useMemo', 'useRef', 'useContext',
258
- 'useReducer', 'useLayoutEffect', 'useImperativeHandle', 'useDebugValue',
259
- 'createElement', 'createContext', 'createRef', 'forwardRef', 'memo', 'lazy',
260
- 'map', 'filter', 'reduce', 'forEach', 'find', 'findIndex', 'some', 'every',
261
- 'includes', 'indexOf', 'slice', 'splice', 'concat', 'join', 'split',
262
- 'push', 'pop', 'shift', 'unshift', 'sort', 'reverse',
263
- 'keys', 'values', 'entries', 'assign', 'freeze', 'seal',
264
- 'hasOwnProperty', 'toString', 'valueOf',
265
- // Python
266
- 'print', 'len', 'range', 'str', 'int', 'float', 'list', 'dict', 'set', 'tuple',
267
- 'open', 'read', 'write', 'close', 'append', 'extend', 'update',
268
- 'super', 'type', 'isinstance', 'issubclass', 'getattr', 'setattr', 'hasattr',
269
- 'enumerate', 'zip', 'sorted', 'reversed', 'min', 'max', 'sum', 'abs',
270
- // Kotlin stdlib (IMPORTANT: keep in sync with call-processor.ts BUILT_IN_NAMES)
271
- 'println', 'print', 'readLine', 'require', 'requireNotNull', 'check', 'assert', 'lazy', 'error',
272
- 'listOf', 'mapOf', 'setOf', 'mutableListOf', 'mutableMapOf', 'mutableSetOf',
273
- 'arrayOf', 'sequenceOf', 'also', 'apply', 'run', 'with', 'takeIf', 'takeUnless',
274
- 'TODO', 'buildString', 'buildList', 'buildMap', 'buildSet',
275
- 'repeat', 'synchronized',
276
- // Kotlin coroutine builders & scope functions
277
- 'launch', 'async', 'runBlocking', 'withContext', 'coroutineScope',
278
- 'supervisorScope', 'delay',
279
- // Kotlin Flow operators
280
- 'flow', 'flowOf', 'collect', 'emit', 'onEach', 'catch',
281
- 'buffer', 'conflate', 'distinctUntilChanged',
282
- 'flatMapLatest', 'flatMapMerge', 'combine',
283
- 'stateIn', 'shareIn', 'launchIn',
284
- // Kotlin infix stdlib functions
285
- 'to', 'until', 'downTo', 'step',
286
- // C/C++ standard library
287
- 'printf', 'fprintf', 'sprintf', 'snprintf', 'vprintf', 'vfprintf', 'vsprintf', 'vsnprintf',
288
- 'scanf', 'fscanf', 'sscanf',
289
- 'malloc', 'calloc', 'realloc', 'free', 'memcpy', 'memmove', 'memset', 'memcmp',
290
- 'strlen', 'strcpy', 'strncpy', 'strcat', 'strncat', 'strcmp', 'strncmp', 'strstr', 'strchr', 'strrchr',
291
- 'atoi', 'atol', 'atof', 'strtol', 'strtoul', 'strtoll', 'strtoull', 'strtod',
292
- 'sizeof', 'offsetof', 'typeof',
293
- 'assert', 'abort', 'exit', '_exit',
294
- 'fopen', 'fclose', 'fread', 'fwrite', 'fseek', 'ftell', 'rewind', 'fflush', 'fgets', 'fputs',
295
- // Linux kernel common macros/helpers (not real call targets)
296
- 'likely', 'unlikely', 'BUG', 'BUG_ON', 'WARN', 'WARN_ON', 'WARN_ONCE',
297
- 'IS_ERR', 'PTR_ERR', 'ERR_PTR', 'IS_ERR_OR_NULL',
298
- 'ARRAY_SIZE', 'container_of', 'list_for_each_entry', 'list_for_each_entry_safe',
299
- 'min', 'max', 'clamp', 'abs', 'swap',
300
- 'pr_info', 'pr_warn', 'pr_err', 'pr_debug', 'pr_notice', 'pr_crit', 'pr_emerg',
301
- 'printk', 'dev_info', 'dev_warn', 'dev_err', 'dev_dbg',
302
- 'GFP_KERNEL', 'GFP_ATOMIC',
303
- 'spin_lock', 'spin_unlock', 'spin_lock_irqsave', 'spin_unlock_irqrestore',
304
- 'mutex_lock', 'mutex_unlock', 'mutex_init',
305
- 'kfree', 'kmalloc', 'kzalloc', 'kcalloc', 'krealloc', 'kvmalloc', 'kvfree',
306
- 'get', 'put',
307
- // PHP built-ins
308
- 'echo', 'isset', 'empty', 'unset', 'list', 'array', 'compact', 'extract',
309
- 'count', 'strlen', 'strpos', 'strrpos', 'substr', 'strtolower', 'strtoupper', 'trim',
310
- 'ltrim', 'rtrim', 'str_replace', 'str_contains', 'str_starts_with', 'str_ends_with',
311
- 'sprintf', 'vsprintf', 'printf', 'number_format',
312
- 'array_map', 'array_filter', 'array_reduce', 'array_push', 'array_pop', 'array_shift',
313
- 'array_unshift', 'array_slice', 'array_splice', 'array_merge', 'array_keys', 'array_values',
314
- 'array_key_exists', 'in_array', 'array_search', 'array_unique', 'usort', 'rsort',
315
- 'json_encode', 'json_decode', 'serialize', 'unserialize',
316
- 'intval', 'floatval', 'strval', 'boolval', 'is_null', 'is_string', 'is_int', 'is_array',
317
- 'is_object', 'is_numeric', 'is_bool', 'is_float',
318
- 'var_dump', 'print_r', 'var_export',
319
- 'date', 'time', 'strtotime', 'mktime', 'microtime',
320
- 'file_exists', 'file_get_contents', 'file_put_contents', 'is_file', 'is_dir',
321
- 'preg_match', 'preg_match_all', 'preg_replace', 'preg_split',
322
- 'header', 'session_start', 'session_destroy', 'ob_start', 'ob_end_clean', 'ob_get_clean',
323
- 'dd', 'dump',
324
- // Swift/iOS built-ins and standard library
325
- 'print', 'debugPrint', 'dump', 'fatalError', 'precondition', 'preconditionFailure',
326
- 'assert', 'assertionFailure', 'NSLog',
327
- 'abs', 'min', 'max', 'zip', 'stride', 'sequence', 'repeatElement',
328
- 'swap', 'withUnsafePointer', 'withUnsafeMutablePointer', 'withUnsafeBytes',
329
- 'autoreleasepool', 'unsafeBitCast', 'unsafeDowncast', 'numericCast',
330
- 'type', 'MemoryLayout',
331
- // Swift collection/string methods (common noise)
332
- 'map', 'flatMap', 'compactMap', 'filter', 'reduce', 'forEach', 'contains',
333
- 'first', 'last', 'prefix', 'suffix', 'dropFirst', 'dropLast',
334
- 'sorted', 'reversed', 'enumerated', 'joined', 'split',
335
- 'append', 'insert', 'remove', 'removeAll', 'removeFirst', 'removeLast',
336
- 'isEmpty', 'count', 'index', 'startIndex', 'endIndex',
337
- // UIKit/Foundation common methods (noise in call graph)
338
- 'addSubview', 'removeFromSuperview', 'layoutSubviews', 'setNeedsLayout',
339
- 'layoutIfNeeded', 'setNeedsDisplay', 'invalidateIntrinsicContentSize',
340
- 'addTarget', 'removeTarget', 'addGestureRecognizer',
341
- 'addConstraint', 'addConstraints', 'removeConstraint', 'removeConstraints',
342
- 'NSLocalizedString', 'Bundle',
343
- 'reloadData', 'reloadSections', 'reloadRows', 'performBatchUpdates',
344
- 'register', 'dequeueReusableCell', 'dequeueReusableSupplementaryView',
345
- 'beginUpdates', 'endUpdates', 'insertRows', 'deleteRows', 'insertSections', 'deleteSections',
346
- 'present', 'dismiss', 'pushViewController', 'popViewController', 'popToRootViewController',
347
- 'performSegue', 'prepare',
348
- // GCD / async
349
- 'DispatchQueue', 'async', 'sync', 'asyncAfter',
350
- 'Task', 'withCheckedContinuation', 'withCheckedThrowingContinuation',
351
- // Combine
352
- 'sink', 'store', 'assign', 'receive', 'subscribe',
353
- // Notification / KVO
354
- 'addObserver', 'removeObserver', 'post', 'NotificationCenter',
355
- ]);
356
99
  // ============================================================================
357
100
  // Label detection from capture map
358
101
  // ============================================================================
@@ -408,49 +151,7 @@ const getLabelFromCaptures = (captureMap) => {
408
151
  return 'Template';
409
152
  return 'CodeElement';
410
153
  };
411
- const DEFINITION_CAPTURE_KEYS = [
412
- 'definition.function',
413
- 'definition.class',
414
- 'definition.interface',
415
- 'definition.method',
416
- 'definition.struct',
417
- 'definition.enum',
418
- 'definition.namespace',
419
- 'definition.module',
420
- 'definition.trait',
421
- 'definition.impl',
422
- 'definition.type',
423
- 'definition.const',
424
- 'definition.static',
425
- 'definition.typedef',
426
- 'definition.macro',
427
- 'definition.union',
428
- 'definition.property',
429
- 'definition.record',
430
- 'definition.delegate',
431
- 'definition.annotation',
432
- 'definition.constructor',
433
- 'definition.template',
434
- ];
435
- const getDefinitionNodeFromCaptures = (captureMap) => {
436
- for (const key of DEFINITION_CAPTURE_KEYS) {
437
- if (captureMap[key])
438
- return captureMap[key];
439
- }
440
- return null;
441
- };
442
- /**
443
- * Append .* to a Kotlin import path if the AST has a wildcard_import sibling node.
444
- * Pure function — returns a new string without mutating the input.
445
- */
446
- const appendKotlinWildcard = (importPath, importNode) => {
447
- for (let i = 0; i < importNode.childCount; i++) {
448
- if (importNode.child(i)?.type === 'wildcard_import') {
449
- return importPath.endsWith('.*') ? importPath : `${importPath}.*`;
450
- }
451
- }
452
- return importPath;
453
- };
154
+ // DEFINITION_CAPTURE_KEYS and getDefinitionNodeFromCaptures imported from ../utils.js
454
155
  // ============================================================================
455
156
  // Process a batch of files
456
157
  // ============================================================================
@@ -463,6 +164,8 @@ const processBatch = (files, onProgress) => {
463
164
  calls: [],
464
165
  heritage: [],
465
166
  routes: [],
167
+ constructorBindings: [],
168
+ skippedLanguages: {},
466
169
  fileCount: 0,
467
170
  };
468
171
  // Group by language to minimize setLanguage calls
@@ -510,22 +213,32 @@ const processBatch = (files, onProgress) => {
510
213
  }
511
214
  // Process regular files for this language
512
215
  if (regularFiles.length > 0) {
513
- try {
514
- setLanguage(language, regularFiles[0].path);
515
- processFileGroup(regularFiles, language, queryString, result, onFileProcessed);
216
+ if (isLanguageAvailable(language, regularFiles[0].path)) {
217
+ try {
218
+ setLanguage(language, regularFiles[0].path);
219
+ processFileGroup(regularFiles, language, queryString, result, onFileProcessed);
220
+ }
221
+ catch {
222
+ // parser unavailable — skip this language group
223
+ }
516
224
  }
517
- catch {
518
- // parser unavailable skip this language group
225
+ else {
226
+ result.skippedLanguages[language] = (result.skippedLanguages[language] || 0) + regularFiles.length;
519
227
  }
520
228
  }
521
229
  // Process tsx files separately (different grammar)
522
230
  if (tsxFiles.length > 0) {
523
- try {
524
- setLanguage(language, tsxFiles[0].path);
525
- processFileGroup(tsxFiles, language, queryString, result, onFileProcessed);
231
+ if (isLanguageAvailable(language, tsxFiles[0].path)) {
232
+ try {
233
+ setLanguage(language, tsxFiles[0].path);
234
+ processFileGroup(tsxFiles, language, queryString, result, onFileProcessed);
235
+ }
236
+ catch {
237
+ // parser unavailable — skip this language group
238
+ }
526
239
  }
527
- catch {
528
- // parser unavailable skip this language group
240
+ else {
241
+ result.skippedLanguages[language] = (result.skippedLanguages[language] || 0) + tsxFiles.length;
529
242
  }
530
243
  }
531
244
  }
@@ -988,27 +701,43 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
988
701
  const lang = parser.getLanguage();
989
702
  query = new Parser.Query(lang, queryString);
990
703
  }
991
- catch {
704
+ catch (err) {
705
+ const message = `Query compilation failed for ${language}: ${err instanceof Error ? err.message : String(err)}`;
706
+ if (parentPort) {
707
+ parentPort.postMessage({ type: 'warning', message });
708
+ }
709
+ else {
710
+ console.warn(message);
711
+ }
992
712
  return;
993
713
  }
994
714
  for (const file of files) {
995
- // Skip very large files they can crash tree-sitter or cause OOM
996
- if (file.content.length > 512 * 1024)
715
+ // Skip files larger than the max tree-sitter buffer (32 MB)
716
+ if (file.content.length > TREE_SITTER_MAX_BUFFER)
997
717
  continue;
998
718
  let tree;
999
719
  try {
1000
- tree = parser.parse(file.content, undefined, { bufferSize: 1024 * 256 });
720
+ tree = parser.parse(file.content, undefined, { bufferSize: getTreeSitterBufferSize(file.content.length) });
1001
721
  }
1002
- catch {
722
+ catch (err) {
723
+ console.warn(`Failed to parse file ${file.path}: ${err instanceof Error ? err.message : String(err)}`);
1003
724
  continue;
1004
725
  }
1005
726
  result.fileCount++;
1006
727
  onFileProcessed?.();
728
+ // Build per-file type environment + constructor bindings in a single AST walk.
729
+ // Constructor bindings are verified against the SymbolTable in processCallsFromExtracted.
730
+ const typeEnv = buildTypeEnv(tree, language);
731
+ const callRouter = callRouters[language];
732
+ if (typeEnv.constructorBindings.length > 0) {
733
+ result.constructorBindings.push({ filePath: file.path, bindings: [...typeEnv.constructorBindings] });
734
+ }
1007
735
  let matches;
1008
736
  try {
1009
737
  matches = query.matches(tree.rootNode);
1010
738
  }
1011
- catch {
739
+ catch (err) {
740
+ console.warn(`Query execution failed for ${file.path}: ${err instanceof Error ? err.message : String(err)}`);
1012
741
  continue;
1013
742
  }
1014
743
  for (const match of matches) {
@@ -1021,10 +750,12 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
1021
750
  const rawImportPath = language === SupportedLanguages.Kotlin
1022
751
  ? appendKotlinWildcard(captureMap['import.source'].text.replace(/['"<>]/g, ''), captureMap['import'])
1023
752
  : captureMap['import.source'].text.replace(/['"<>]/g, '');
753
+ const namedBindings = extractNamedBindings(captureMap['import'], language);
1024
754
  result.imports.push({
1025
755
  filePath: file.path,
1026
756
  rawImportPath,
1027
757
  language: language,
758
+ ...(namedBindings ? { namedBindings } : {}),
1028
759
  });
1029
760
  continue;
1030
761
  }
@@ -1033,11 +764,122 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
1033
764
  const callNameNode = captureMap['call.name'];
1034
765
  if (callNameNode) {
1035
766
  const calledName = callNameNode.text;
1036
- if (!BUILT_INS.has(calledName)) {
767
+ // Dispatch: route language-specific calls (heritage, properties, imports)
768
+ const routed = callRouter(calledName, captureMap['call']);
769
+ if (routed) {
770
+ if (routed.kind === 'skip')
771
+ continue;
772
+ if (routed.kind === 'import') {
773
+ result.imports.push({
774
+ filePath: file.path,
775
+ rawImportPath: routed.importPath,
776
+ language,
777
+ });
778
+ continue;
779
+ }
780
+ if (routed.kind === 'heritage') {
781
+ for (const item of routed.items) {
782
+ result.heritage.push({
783
+ filePath: file.path,
784
+ className: item.enclosingClass,
785
+ parentName: item.mixinName,
786
+ kind: item.heritageKind,
787
+ });
788
+ }
789
+ continue;
790
+ }
791
+ if (routed.kind === 'properties') {
792
+ const propEnclosingClassId = findEnclosingClassId(captureMap['call'], file.path);
793
+ for (const item of routed.items) {
794
+ const nodeId = generateId('Property', `${file.path}:${item.propName}`);
795
+ result.nodes.push({
796
+ id: nodeId,
797
+ label: 'Property',
798
+ properties: {
799
+ name: item.propName,
800
+ filePath: file.path,
801
+ startLine: item.startLine,
802
+ endLine: item.endLine,
803
+ language,
804
+ isExported: true,
805
+ description: item.accessorType,
806
+ },
807
+ });
808
+ result.symbols.push({
809
+ filePath: file.path,
810
+ name: item.propName,
811
+ nodeId,
812
+ type: 'Property',
813
+ ...(propEnclosingClassId ? { ownerId: propEnclosingClassId } : {}),
814
+ });
815
+ const fileId = generateId('File', file.path);
816
+ const relId = generateId('DEFINES', `${fileId}->${nodeId}`);
817
+ result.relationships.push({
818
+ id: relId,
819
+ sourceId: fileId,
820
+ targetId: nodeId,
821
+ type: 'DEFINES',
822
+ confidence: 1.0,
823
+ reason: '',
824
+ });
825
+ if (propEnclosingClassId) {
826
+ result.relationships.push({
827
+ id: generateId('HAS_METHOD', `${propEnclosingClassId}->${nodeId}`),
828
+ sourceId: propEnclosingClassId,
829
+ targetId: nodeId,
830
+ type: 'HAS_METHOD',
831
+ confidence: 1.0,
832
+ reason: '',
833
+ });
834
+ }
835
+ }
836
+ continue;
837
+ }
838
+ // kind === 'call' — fall through to normal call processing below
839
+ }
840
+ if (!isBuiltInOrNoise(calledName)) {
1037
841
  const callNode = captureMap['call'];
1038
842
  const sourceId = findEnclosingFunctionId(callNode, file.path)
1039
843
  || generateId('File', file.path);
1040
- result.calls.push({ filePath: file.path, calledName, sourceId });
844
+ const callForm = inferCallForm(callNode, callNameNode);
845
+ let receiverName = callForm === 'member' ? extractReceiverName(callNameNode) : undefined;
846
+ let receiverTypeName = receiverName ? typeEnv.lookup(receiverName, callNode) : undefined;
847
+ let receiverCallChain;
848
+ // When the receiver is a call_expression (e.g. svc.getUser().save()),
849
+ // extractReceiverName returns undefined because it refuses complex expressions.
850
+ // Instead, walk the receiver node to build a call chain for deferred resolution.
851
+ // We capture the base receiver name so processCallsFromExtracted can look it up
852
+ // from constructor bindings. receiverTypeName is intentionally left unset here —
853
+ // the chain resolver in processCallsFromExtracted needs the base type as input and
854
+ // produces the final receiver type as output.
855
+ if (callForm === 'member' && receiverName === undefined && !receiverTypeName) {
856
+ const receiverNode = extractReceiverNode(callNameNode);
857
+ if (receiverNode && CALL_EXPRESSION_TYPES.has(receiverNode.type)) {
858
+ const extracted = extractCallChain(receiverNode);
859
+ if (extracted) {
860
+ receiverCallChain = extracted.chain;
861
+ // Set receiverName to the base object so Step 1 in processCallsFromExtracted
862
+ // can resolve it via constructor bindings to a base type for the chain.
863
+ receiverName = extracted.baseReceiverName;
864
+ // Also try the type environment immediately (covers explicitly-typed locals
865
+ // and annotated parameters like `fn process(svc: &UserService)`).
866
+ // This sets a base type that chain resolution (Step 2) will use as input.
867
+ if (receiverName) {
868
+ receiverTypeName = typeEnv.lookup(receiverName, callNode);
869
+ }
870
+ }
871
+ }
872
+ }
873
+ result.calls.push({
874
+ filePath: file.path,
875
+ calledName,
876
+ sourceId,
877
+ argCount: countCallArguments(callNode),
878
+ ...(callForm !== undefined ? { callForm } : {}),
879
+ ...(receiverName !== undefined ? { receiverName } : {}),
880
+ ...(receiverTypeName !== undefined ? { receiverTypeName } : {}),
881
+ ...(receiverCallChain !== undefined ? { receiverCallChain } : {}),
882
+ });
1041
883
  }
1042
884
  }
1043
885
  continue;
@@ -1045,12 +887,21 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
1045
887
  // Extract heritage (extends/implements)
1046
888
  if (captureMap['heritage.class']) {
1047
889
  if (captureMap['heritage.extends']) {
1048
- result.heritage.push({
1049
- filePath: file.path,
1050
- className: captureMap['heritage.class'].text,
1051
- parentName: captureMap['heritage.extends'].text,
1052
- kind: 'extends',
1053
- });
890
+ // Go struct embedding: the query matches ALL field_declarations with
891
+ // type_identifier, but only anonymous fields (no name) are embedded.
892
+ // Named fields like `Breed string` also match — skip them.
893
+ const extendsNode = captureMap['heritage.extends'];
894
+ const fieldDecl = extendsNode.parent;
895
+ const isNamedField = fieldDecl?.type === 'field_declaration'
896
+ && fieldDecl.childForFieldName('name');
897
+ if (!isNamedField) {
898
+ result.heritage.push({
899
+ filePath: file.path,
900
+ className: captureMap['heritage.class'].text,
901
+ parentName: captureMap['heritage.extends'].text,
902
+ kind: 'extends',
903
+ });
904
+ }
1054
905
  }
1055
906
  if (captureMap['heritage.implements']) {
1056
907
  result.heritage.push({
@@ -1082,7 +933,7 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
1082
933
  const nodeName = nameNode ? nameNode.text : 'init';
1083
934
  const definitionNode = getDefinitionNodeFromCaptures(captureMap);
1084
935
  const startLine = definitionNode ? definitionNode.startPosition.row : (nameNode ? nameNode.startPosition.row : 0);
1085
- const nodeId = generateId(nodeLabel, `${file.path}:${nodeName}:${startLine}`);
936
+ const nodeId = generateId(nodeLabel, `${file.path}:${nodeName}`);
1086
937
  let description;
1087
938
  if (language === SupportedLanguages.PHP) {
1088
939
  if (nodeLabel === 'Property' && captureMap['definition.property']) {
@@ -1095,6 +946,20 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
1095
946
  const frameworkHint = definitionNode
1096
947
  ? detectFrameworkFromAST(language, (definitionNode.text || '').slice(0, 300))
1097
948
  : null;
949
+ let parameterCount;
950
+ let returnType;
951
+ if (nodeLabel === 'Function' || nodeLabel === 'Method' || nodeLabel === 'Constructor') {
952
+ const sig = extractMethodSignature(definitionNode);
953
+ parameterCount = sig.parameterCount;
954
+ returnType = sig.returnType;
955
+ // Language-specific return type fallback (e.g. Ruby YARD @return [Type])
956
+ if (!returnType && definitionNode) {
957
+ const tc = typeConfigs[language];
958
+ if (tc?.extractReturnType) {
959
+ returnType = tc.extractReturnType(definitionNode);
960
+ }
961
+ }
962
+ }
1098
963
  result.nodes.push({
1099
964
  id: nodeId,
1100
965
  label: nodeLabel,
@@ -1110,13 +975,22 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
1110
975
  astFrameworkReason: frameworkHint.reason,
1111
976
  } : {}),
1112
977
  ...(description !== undefined ? { description } : {}),
978
+ ...(parameterCount !== undefined ? { parameterCount } : {}),
979
+ ...(returnType !== undefined ? { returnType } : {}),
1113
980
  },
1114
981
  });
982
+ // Compute enclosing class for Method/Constructor/Property/Function — used for both ownerId and HAS_METHOD
983
+ // Function is included because Kotlin/Rust/Python capture class methods as Function nodes
984
+ const needsOwner = nodeLabel === 'Method' || nodeLabel === 'Constructor' || nodeLabel === 'Property' || nodeLabel === 'Function';
985
+ const enclosingClassId = needsOwner ? findEnclosingClassId(nameNode || definitionNode, file.path) : null;
1115
986
  result.symbols.push({
1116
987
  filePath: file.path,
1117
988
  name: nodeName,
1118
989
  nodeId,
1119
990
  type: nodeLabel,
991
+ ...(parameterCount !== undefined ? { parameterCount } : {}),
992
+ ...(returnType !== undefined ? { returnType } : {}),
993
+ ...(enclosingClassId ? { ownerId: enclosingClassId } : {}),
1120
994
  });
1121
995
  const fileId = generateId('File', file.path);
1122
996
  const relId = generateId('DEFINES', `${fileId}->${nodeId}`);
@@ -1128,6 +1002,17 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
1128
1002
  confidence: 1.0,
1129
1003
  reason: '',
1130
1004
  });
1005
+ // ── HAS_METHOD: link method/constructor/property to enclosing class ──
1006
+ if (enclosingClassId) {
1007
+ result.relationships.push({
1008
+ id: generateId('HAS_METHOD', `${enclosingClassId}->${nodeId}`),
1009
+ sourceId: enclosingClassId,
1010
+ targetId: nodeId,
1011
+ type: 'HAS_METHOD',
1012
+ confidence: 1.0,
1013
+ reason: '',
1014
+ });
1015
+ }
1131
1016
  }
1132
1017
  // Extract Laravel routes from route files via procedural AST walk
1133
1018
  if (language === SupportedLanguages.PHP && (file.path.includes('/routes/') || file.path.startsWith('routes/')) && file.path.endsWith('.php')) {
@@ -1142,7 +1027,7 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
1142
1027
  /** Accumulated result across sub-batches */
1143
1028
  let accumulated = {
1144
1029
  nodes: [], relationships: [], symbols: [],
1145
- imports: [], calls: [], heritage: [], routes: [], fileCount: 0,
1030
+ imports: [], calls: [], heritage: [], routes: [], constructorBindings: [], skippedLanguages: {}, fileCount: 0,
1146
1031
  };
1147
1032
  let cumulativeProcessed = 0;
1148
1033
  const mergeResult = (target, src) => {
@@ -1153,6 +1038,10 @@ const mergeResult = (target, src) => {
1153
1038
  target.calls.push(...src.calls);
1154
1039
  target.heritage.push(...src.heritage);
1155
1040
  target.routes.push(...src.routes);
1041
+ target.constructorBindings.push(...src.constructorBindings);
1042
+ for (const [lang, count] of Object.entries(src.skippedLanguages)) {
1043
+ target.skippedLanguages[lang] = (target.skippedLanguages[lang] || 0) + count;
1044
+ }
1156
1045
  target.fileCount += src.fileCount;
1157
1046
  };
1158
1047
  parentPort.on('message', (msg) => {
@@ -1172,7 +1061,7 @@ parentPort.on('message', (msg) => {
1172
1061
  if (msg && msg.type === 'flush') {
1173
1062
  parentPort.postMessage({ type: 'result', data: accumulated });
1174
1063
  // Reset for potential reuse
1175
- accumulated = { nodes: [], relationships: [], symbols: [], imports: [], calls: [], heritage: [], routes: [], fileCount: 0 };
1064
+ accumulated = { nodes: [], relationships: [], symbols: [], imports: [], calls: [], heritage: [], routes: [], constructorBindings: [], skippedLanguages: {}, fileCount: 0 };
1176
1065
  cumulativeProcessed = 0;
1177
1066
  return;
1178
1067
  }