@veewo/gitnexus 1.3.11 → 1.4.6-rc

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 (181) hide show
  1. package/README.md +37 -80
  2. package/dist/benchmark/agent-context/tool-runner.js +2 -2
  3. package/dist/benchmark/neonspark-candidates.js +3 -3
  4. package/dist/benchmark/tool-runner.js +2 -2
  5. package/dist/cli/ai-context.d.ts +2 -1
  6. package/dist/cli/ai-context.js +16 -12
  7. package/dist/cli/analyze.d.ts +2 -0
  8. package/dist/cli/analyze.js +68 -48
  9. package/dist/cli/augment.js +1 -1
  10. package/dist/cli/eval-server.d.ts +8 -1
  11. package/dist/cli/eval-server.js +30 -13
  12. package/dist/cli/index.js +28 -82
  13. package/dist/cli/lazy-action.d.ts +6 -0
  14. package/dist/cli/lazy-action.js +18 -0
  15. package/dist/cli/mcp.js +3 -1
  16. package/dist/cli/setup.js +87 -48
  17. package/dist/cli/setup.test.js +18 -13
  18. package/dist/cli/skill-gen.d.ts +26 -0
  19. package/dist/cli/skill-gen.js +549 -0
  20. package/dist/cli/status.js +13 -4
  21. package/dist/cli/tool.d.ts +3 -2
  22. package/dist/cli/tool.js +50 -16
  23. package/dist/cli/wiki.js +8 -4
  24. package/dist/config/ignore-service.d.ts +25 -0
  25. package/dist/config/ignore-service.js +76 -0
  26. package/dist/config/supported-languages.d.ts +4 -1
  27. package/dist/config/supported-languages.js +3 -2
  28. package/dist/core/augmentation/engine.js +94 -67
  29. package/dist/core/embeddings/embedder.d.ts +1 -1
  30. package/dist/core/embeddings/embedder.js +1 -1
  31. package/dist/core/embeddings/embedding-pipeline.d.ts +3 -3
  32. package/dist/core/embeddings/embedding-pipeline.js +52 -25
  33. package/dist/core/embeddings/types.d.ts +1 -1
  34. package/dist/core/graph/types.d.ts +7 -2
  35. package/dist/core/ingestion/ast-cache.js +3 -2
  36. package/dist/core/ingestion/call-processor.d.ts +8 -6
  37. package/dist/core/ingestion/call-processor.js +468 -206
  38. package/dist/core/ingestion/call-routing.d.ts +53 -0
  39. package/dist/core/ingestion/call-routing.js +108 -0
  40. package/dist/core/ingestion/constants.d.ts +16 -0
  41. package/dist/core/ingestion/constants.js +16 -0
  42. package/dist/core/ingestion/entry-point-scoring.d.ts +2 -1
  43. package/dist/core/ingestion/entry-point-scoring.js +116 -23
  44. package/dist/core/ingestion/export-detection.d.ts +18 -0
  45. package/dist/core/ingestion/export-detection.js +231 -0
  46. package/dist/core/ingestion/filesystem-walker.js +4 -3
  47. package/dist/core/ingestion/framework-detection.d.ts +19 -4
  48. package/dist/core/ingestion/framework-detection.js +182 -6
  49. package/dist/core/ingestion/heritage-processor.d.ts +13 -5
  50. package/dist/core/ingestion/heritage-processor.js +109 -55
  51. package/dist/core/ingestion/import-processor.d.ts +16 -20
  52. package/dist/core/ingestion/import-processor.js +199 -579
  53. package/dist/core/ingestion/language-config.d.ts +46 -0
  54. package/dist/core/ingestion/language-config.js +167 -0
  55. package/dist/core/ingestion/mro-processor.d.ts +45 -0
  56. package/dist/core/ingestion/mro-processor.js +369 -0
  57. package/dist/core/ingestion/named-binding-extraction.d.ts +61 -0
  58. package/dist/core/ingestion/named-binding-extraction.js +363 -0
  59. package/dist/core/ingestion/parsing-processor.d.ts +4 -1
  60. package/dist/core/ingestion/parsing-processor.js +107 -109
  61. package/dist/core/ingestion/pipeline.d.ts +6 -3
  62. package/dist/core/ingestion/pipeline.js +208 -114
  63. package/dist/core/ingestion/process-processor.js +8 -2
  64. package/dist/core/ingestion/resolution-context.d.ts +53 -0
  65. package/dist/core/ingestion/resolution-context.js +132 -0
  66. package/dist/core/ingestion/resolvers/csharp.d.ts +22 -0
  67. package/dist/core/ingestion/resolvers/csharp.js +109 -0
  68. package/dist/core/ingestion/resolvers/go.d.ts +19 -0
  69. package/dist/core/ingestion/resolvers/go.js +42 -0
  70. package/dist/core/ingestion/resolvers/index.d.ts +18 -0
  71. package/dist/core/ingestion/resolvers/index.js +13 -0
  72. package/dist/core/ingestion/resolvers/jvm.d.ts +23 -0
  73. package/dist/core/ingestion/resolvers/jvm.js +87 -0
  74. package/dist/core/ingestion/resolvers/php.d.ts +15 -0
  75. package/dist/core/ingestion/resolvers/php.js +35 -0
  76. package/dist/core/ingestion/resolvers/python.d.ts +19 -0
  77. package/dist/core/ingestion/resolvers/python.js +52 -0
  78. package/dist/core/ingestion/resolvers/ruby.d.ts +12 -0
  79. package/dist/core/ingestion/resolvers/ruby.js +15 -0
  80. package/dist/core/ingestion/resolvers/rust.d.ts +15 -0
  81. package/dist/core/ingestion/resolvers/rust.js +73 -0
  82. package/dist/core/ingestion/resolvers/standard.d.ts +28 -0
  83. package/dist/core/ingestion/resolvers/standard.js +123 -0
  84. package/dist/core/ingestion/resolvers/utils.d.ts +33 -0
  85. package/dist/core/ingestion/resolvers/utils.js +122 -0
  86. package/dist/core/ingestion/symbol-table.d.ts +21 -1
  87. package/dist/core/ingestion/symbol-table.js +40 -12
  88. package/dist/core/ingestion/tree-sitter-queries.d.ts +13 -10
  89. package/dist/core/ingestion/tree-sitter-queries.js +297 -7
  90. package/dist/core/ingestion/type-env.d.ts +49 -0
  91. package/dist/core/ingestion/type-env.js +611 -0
  92. package/dist/core/ingestion/type-extractors/c-cpp.d.ts +2 -0
  93. package/dist/core/ingestion/type-extractors/c-cpp.js +385 -0
  94. package/dist/core/ingestion/type-extractors/csharp.d.ts +2 -0
  95. package/dist/core/ingestion/type-extractors/csharp.js +383 -0
  96. package/dist/core/ingestion/type-extractors/go.d.ts +2 -0
  97. package/dist/core/ingestion/type-extractors/go.js +467 -0
  98. package/dist/core/ingestion/type-extractors/index.d.ts +22 -0
  99. package/dist/core/ingestion/type-extractors/index.js +31 -0
  100. package/dist/core/ingestion/type-extractors/jvm.d.ts +3 -0
  101. package/dist/core/ingestion/type-extractors/jvm.js +681 -0
  102. package/dist/core/ingestion/type-extractors/php.d.ts +2 -0
  103. package/dist/core/ingestion/type-extractors/php.js +549 -0
  104. package/dist/core/ingestion/type-extractors/python.d.ts +2 -0
  105. package/dist/core/ingestion/type-extractors/python.js +406 -0
  106. package/dist/core/ingestion/type-extractors/ruby.d.ts +2 -0
  107. package/dist/core/ingestion/type-extractors/ruby.js +389 -0
  108. package/dist/core/ingestion/type-extractors/rust.d.ts +2 -0
  109. package/dist/core/ingestion/type-extractors/rust.js +449 -0
  110. package/dist/core/ingestion/type-extractors/shared.d.ts +133 -0
  111. package/dist/core/ingestion/type-extractors/shared.js +703 -0
  112. package/dist/core/ingestion/type-extractors/swift.d.ts +2 -0
  113. package/dist/core/ingestion/type-extractors/swift.js +137 -0
  114. package/dist/core/ingestion/type-extractors/types.d.ts +127 -0
  115. package/dist/core/ingestion/type-extractors/typescript.d.ts +2 -0
  116. package/dist/core/ingestion/type-extractors/typescript.js +494 -0
  117. package/dist/core/ingestion/utils.d.ts +103 -0
  118. package/dist/core/ingestion/utils.js +1085 -4
  119. package/dist/core/ingestion/workers/parse-worker.d.ts +51 -4
  120. package/dist/core/ingestion/workers/parse-worker.js +634 -222
  121. package/dist/core/ingestion/workers/worker-pool.js +8 -0
  122. package/dist/core/{kuzu → lbug}/csv-generator.d.ts +12 -10
  123. package/dist/core/{kuzu → lbug}/csv-generator.js +82 -101
  124. package/dist/core/{kuzu/kuzu-adapter.d.ts → lbug/lbug-adapter.d.ts} +20 -25
  125. package/dist/core/{kuzu/kuzu-adapter.js → lbug/lbug-adapter.js} +150 -122
  126. package/dist/core/{kuzu → lbug}/schema.d.ts +4 -4
  127. package/dist/core/{kuzu → lbug}/schema.js +23 -22
  128. package/dist/core/lbug/schema.test.d.ts +1 -0
  129. package/dist/core/search/bm25-index.d.ts +4 -4
  130. package/dist/core/search/bm25-index.js +12 -11
  131. package/dist/core/search/hybrid-search.d.ts +2 -2
  132. package/dist/core/search/hybrid-search.js +6 -6
  133. package/dist/core/tree-sitter/parser-loader.d.ts +1 -0
  134. package/dist/core/tree-sitter/parser-loader.js +19 -0
  135. package/dist/core/wiki/generator.d.ts +2 -2
  136. package/dist/core/wiki/generator.js +6 -6
  137. package/dist/core/wiki/graph-queries.d.ts +4 -4
  138. package/dist/core/wiki/graph-queries.js +7 -7
  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} +11 -10
  142. package/dist/mcp/core/lbug-adapter.js +327 -0
  143. package/dist/mcp/local/local-backend.d.ts +21 -16
  144. package/dist/mcp/local/local-backend.js +306 -706
  145. package/dist/mcp/local/unity-parity-seed-loader.d.ts +6 -1
  146. package/dist/mcp/local/unity-parity-seed-loader.js +119 -9
  147. package/dist/mcp/local/unity-parity-seed-loader.test.js +95 -7
  148. package/dist/mcp/resources.js +2 -2
  149. package/dist/mcp/server.js +28 -13
  150. package/dist/mcp/staleness.js +2 -2
  151. package/dist/mcp/tools.js +12 -3
  152. package/dist/server/api.js +12 -12
  153. package/dist/server/mcp-http.d.ts +1 -1
  154. package/dist/server/mcp-http.js +1 -1
  155. package/dist/storage/git.js +4 -1
  156. package/dist/storage/repo-manager.d.ts +20 -2
  157. package/dist/storage/repo-manager.js +74 -4
  158. package/dist/types/pipeline.d.ts +1 -1
  159. package/hooks/claude/gitnexus-hook.cjs +149 -46
  160. package/hooks/claude/pre-tool-use.sh +2 -1
  161. package/hooks/claude/session-start.sh +0 -0
  162. package/package.json +20 -4
  163. package/scripts/patch-tree-sitter-swift.cjs +74 -0
  164. package/skills/gitnexus-cli.md +8 -8
  165. package/skills/gitnexus-debugging.md +1 -1
  166. package/skills/gitnexus-exploring.md +1 -1
  167. package/skills/gitnexus-guide.md +1 -1
  168. package/skills/gitnexus-impact-analysis.md +1 -1
  169. package/skills/gitnexus-pr-review.md +163 -0
  170. package/skills/gitnexus-refactoring.md +1 -1
  171. package/dist/cli/claude-hooks.d.ts +0 -22
  172. package/dist/cli/claude-hooks.js +0 -97
  173. package/dist/mcp/core/kuzu-adapter.js +0 -231
  174. /package/dist/core/{kuzu/csv-generator.test.d.ts → ingestion/type-extractors/types.js} +0 -0
  175. /package/dist/core/{kuzu/relationship-pair-buckets.test.d.ts → lbug/csv-generator.test.d.ts} +0 -0
  176. /package/dist/core/{kuzu → lbug}/csv-generator.test.js +0 -0
  177. /package/dist/core/{kuzu → lbug}/relationship-pair-buckets.d.ts +0 -0
  178. /package/dist/core/{kuzu → lbug}/relationship-pair-buckets.js +0 -0
  179. /package/dist/core/{kuzu/schema.test.d.ts → lbug/relationship-pair-buckets.test.d.ts} +0 -0
  180. /package/dist/core/{kuzu → lbug}/relationship-pair-buckets.test.js +0 -0
  181. /package/dist/core/{kuzu → lbug}/schema.test.js +0 -0
@@ -10,10 +10,33 @@ import CSharp from 'tree-sitter-c-sharp';
10
10
  import Go from 'tree-sitter-go';
11
11
  import Rust from 'tree-sitter-rust';
12
12
  import PHP from 'tree-sitter-php';
13
+ import Ruby from 'tree-sitter-ruby';
14
+ import { createRequire } from 'node:module';
13
15
  import { SupportedLanguages } from '../../../config/supported-languages.js';
14
16
  import { LANGUAGE_QUERIES } from '../tree-sitter-queries.js';
15
- import { getLanguageFromFilename } from '../utils.js';
17
+ import { getTreeSitterBufferSize, TREE_SITTER_MAX_BUFFER } from '../constants.js';
18
+ // tree-sitter-swift is an optionalDependency — may not be installed
19
+ const _require = createRequire(import.meta.url);
20
+ let Swift = null;
21
+ try {
22
+ Swift = _require('tree-sitter-swift');
23
+ }
24
+ catch { }
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';
34
+ import { detectFrameworkFromAST } from '../framework-detection.js';
35
+ import { typeConfigs } from '../type-extractors/index.js';
16
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';
17
40
  // ============================================================================
18
41
  // Worker-local parser + language map
19
42
  // ============================================================================
@@ -29,7 +52,22 @@ const languageMap = {
29
52
  [SupportedLanguages.CSharp]: CSharp,
30
53
  [SupportedLanguages.Go]: Go,
31
54
  [SupportedLanguages.Rust]: Rust,
55
+ ...(Kotlin ? { [SupportedLanguages.Kotlin]: Kotlin } : {}),
32
56
  [SupportedLanguages.PHP]: PHP.php_only,
57
+ [SupportedLanguages.Ruby]: Ruby,
58
+ ...(Swift ? { [SupportedLanguages.Swift]: Swift } : {}),
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;
33
71
  };
34
72
  const setLanguage = (language, filePath) => {
35
73
  const key = language === SupportedLanguages.TypeScript && filePath.endsWith('.tsx')
@@ -40,148 +78,16 @@ const setLanguage = (language, filePath) => {
40
78
  throw new Error(`Unsupported language: ${language}`);
41
79
  parser.setLanguage(lang);
42
80
  };
43
- // ============================================================================
44
- // Export detection (copied — needs AST parent traversal, can't cross threads)
45
- // ============================================================================
46
- const isNodeExported = (node, name, language) => {
47
- let current = node;
48
- switch (language) {
49
- case 'javascript':
50
- case 'typescript':
51
- while (current) {
52
- const type = current.type;
53
- if (type === 'export_statement' ||
54
- type === 'export_specifier' ||
55
- type === 'lexical_declaration' && current.parent?.type === 'export_statement') {
56
- return true;
57
- }
58
- if (current.text?.startsWith('export ')) {
59
- return true;
60
- }
61
- current = current.parent;
62
- }
63
- return false;
64
- case 'python':
65
- return !name.startsWith('_');
66
- case 'java':
67
- while (current) {
68
- if (current.parent) {
69
- const parent = current.parent;
70
- for (let i = 0; i < parent.childCount; i++) {
71
- const child = parent.child(i);
72
- if (child?.type === 'modifiers' && child.text?.includes('public')) {
73
- return true;
74
- }
75
- }
76
- if (parent.type === 'method_declaration' || parent.type === 'constructor_declaration') {
77
- if (parent.text?.trimStart().startsWith('public')) {
78
- return true;
79
- }
80
- }
81
- }
82
- current = current.parent;
83
- }
84
- return false;
85
- case 'csharp':
86
- while (current) {
87
- if (current.type === 'modifier' || current.type === 'modifiers') {
88
- if (current.text?.includes('public'))
89
- return true;
90
- }
91
- current = current.parent;
92
- }
93
- return false;
94
- case 'go':
95
- if (name.length === 0)
96
- return false;
97
- const first = name[0];
98
- return first === first.toUpperCase() && first !== first.toLowerCase();
99
- case 'rust':
100
- while (current) {
101
- if (current.type === 'visibility_modifier') {
102
- if (current.text?.includes('pub'))
103
- return true;
104
- }
105
- current = current.parent;
106
- }
107
- return false;
108
- case 'c':
109
- case 'cpp':
110
- return false;
111
- case 'php':
112
- // Top-level classes/interfaces/traits are always accessible
113
- // Methods/properties are exported only if they have 'public' modifier
114
- while (current) {
115
- if (current.type === 'class_declaration' ||
116
- current.type === 'interface_declaration' ||
117
- current.type === 'trait_declaration' ||
118
- current.type === 'enum_declaration') {
119
- return true;
120
- }
121
- if (current.type === 'visibility_modifier') {
122
- return current.text === 'public';
123
- }
124
- current = current.parent;
125
- }
126
- // Top-level functions (no parent class) are globally accessible
127
- return true;
128
- default:
129
- return false;
130
- }
131
- };
81
+ // isNodeExported imported from ../export-detection.js (shared module)
132
82
  // ============================================================================
133
83
  // Enclosing function detection (for call extraction)
134
84
  // ============================================================================
135
- const FUNCTION_NODE_TYPES = new Set([
136
- 'function_declaration', 'arrow_function', 'function_expression',
137
- 'method_definition', 'generator_function_declaration',
138
- 'function_definition', 'async_function_declaration', 'async_arrow_function',
139
- 'method_declaration', 'constructor_declaration',
140
- 'local_function_statement', 'function_item', 'impl_item',
141
- 'anonymous_function_creation_expression', // PHP anonymous functions
142
- ]);
143
85
  /** Walk up AST to find enclosing function, return its generateId or null for top-level */
144
86
  const findEnclosingFunctionId = (node, filePath) => {
145
87
  let current = node.parent;
146
88
  while (current) {
147
89
  if (FUNCTION_NODE_TYPES.has(current.type)) {
148
- let funcName = null;
149
- let label = 'Function';
150
- if (['function_declaration', 'function_definition', 'async_function_declaration',
151
- 'generator_function_declaration', 'function_item'].includes(current.type)) {
152
- const nameNode = current.childForFieldName?.('name') ||
153
- current.children?.find((c) => c.type === 'identifier' || c.type === 'property_identifier');
154
- funcName = nameNode?.text;
155
- }
156
- else if (current.type === 'impl_item') {
157
- const funcItem = current.children?.find((c) => c.type === 'function_item');
158
- if (funcItem) {
159
- const nameNode = funcItem.childForFieldName?.('name') ||
160
- funcItem.children?.find((c) => c.type === 'identifier');
161
- funcName = nameNode?.text;
162
- label = 'Method';
163
- }
164
- }
165
- else if (current.type === 'method_definition') {
166
- const nameNode = current.childForFieldName?.('name') ||
167
- current.children?.find((c) => c.type === 'property_identifier');
168
- funcName = nameNode?.text;
169
- label = 'Method';
170
- }
171
- else if (current.type === 'method_declaration' || current.type === 'constructor_declaration') {
172
- const nameNode = current.childForFieldName?.('name') ||
173
- current.children?.find((c) => c.type === 'identifier');
174
- funcName = nameNode?.text;
175
- label = 'Method';
176
- }
177
- else if (current.type === 'arrow_function' || current.type === 'function_expression') {
178
- const parent = current.parent;
179
- if (parent?.type === 'variable_declarator') {
180
- const nameNode = parent.childForFieldName?.('name') ||
181
- parent.children?.find((c) => c.type === 'identifier');
182
- funcName = nameNode?.text;
183
- }
184
- }
90
+ const { funcName, label } = extractFunctionName(current);
185
91
  if (funcName) {
186
92
  return generateId(label, `${filePath}:${funcName}`);
187
93
  }
@@ -190,70 +96,6 @@ const findEnclosingFunctionId = (node, filePath) => {
190
96
  }
191
97
  return null;
192
98
  };
193
- const BUILT_INS = new Set([
194
- // JavaScript/TypeScript
195
- 'console', 'log', 'warn', 'error', 'info', 'debug',
196
- 'setTimeout', 'setInterval', 'clearTimeout', 'clearInterval',
197
- 'parseInt', 'parseFloat', 'isNaN', 'isFinite',
198
- 'encodeURI', 'decodeURI', 'encodeURIComponent', 'decodeURIComponent',
199
- 'JSON', 'parse', 'stringify',
200
- 'Object', 'Array', 'String', 'Number', 'Boolean', 'Symbol', 'BigInt',
201
- 'Map', 'Set', 'WeakMap', 'WeakSet',
202
- 'Promise', 'resolve', 'reject', 'then', 'catch', 'finally',
203
- 'Math', 'Date', 'RegExp', 'Error',
204
- 'require', 'import', 'export', 'fetch', 'Response', 'Request',
205
- 'useState', 'useEffect', 'useCallback', 'useMemo', 'useRef', 'useContext',
206
- 'useReducer', 'useLayoutEffect', 'useImperativeHandle', 'useDebugValue',
207
- 'createElement', 'createContext', 'createRef', 'forwardRef', 'memo', 'lazy',
208
- 'map', 'filter', 'reduce', 'forEach', 'find', 'findIndex', 'some', 'every',
209
- 'includes', 'indexOf', 'slice', 'splice', 'concat', 'join', 'split',
210
- 'push', 'pop', 'shift', 'unshift', 'sort', 'reverse',
211
- 'keys', 'values', 'entries', 'assign', 'freeze', 'seal',
212
- 'hasOwnProperty', 'toString', 'valueOf',
213
- // Python
214
- 'print', 'len', 'range', 'str', 'int', 'float', 'list', 'dict', 'set', 'tuple',
215
- 'open', 'read', 'write', 'close', 'append', 'extend', 'update',
216
- 'super', 'type', 'isinstance', 'issubclass', 'getattr', 'setattr', 'hasattr',
217
- 'enumerate', 'zip', 'sorted', 'reversed', 'min', 'max', 'sum', 'abs',
218
- // C/C++ standard library
219
- 'printf', 'fprintf', 'sprintf', 'snprintf', 'vprintf', 'vfprintf', 'vsprintf', 'vsnprintf',
220
- 'scanf', 'fscanf', 'sscanf',
221
- 'malloc', 'calloc', 'realloc', 'free', 'memcpy', 'memmove', 'memset', 'memcmp',
222
- 'strlen', 'strcpy', 'strncpy', 'strcat', 'strncat', 'strcmp', 'strncmp', 'strstr', 'strchr', 'strrchr',
223
- 'atoi', 'atol', 'atof', 'strtol', 'strtoul', 'strtoll', 'strtoull', 'strtod',
224
- 'sizeof', 'offsetof', 'typeof',
225
- 'assert', 'abort', 'exit', '_exit',
226
- 'fopen', 'fclose', 'fread', 'fwrite', 'fseek', 'ftell', 'rewind', 'fflush', 'fgets', 'fputs',
227
- // Linux kernel common macros/helpers (not real call targets)
228
- 'likely', 'unlikely', 'BUG', 'BUG_ON', 'WARN', 'WARN_ON', 'WARN_ONCE',
229
- 'IS_ERR', 'PTR_ERR', 'ERR_PTR', 'IS_ERR_OR_NULL',
230
- 'ARRAY_SIZE', 'container_of', 'list_for_each_entry', 'list_for_each_entry_safe',
231
- 'min', 'max', 'clamp', 'abs', 'swap',
232
- 'pr_info', 'pr_warn', 'pr_err', 'pr_debug', 'pr_notice', 'pr_crit', 'pr_emerg',
233
- 'printk', 'dev_info', 'dev_warn', 'dev_err', 'dev_dbg',
234
- 'GFP_KERNEL', 'GFP_ATOMIC',
235
- 'spin_lock', 'spin_unlock', 'spin_lock_irqsave', 'spin_unlock_irqrestore',
236
- 'mutex_lock', 'mutex_unlock', 'mutex_init',
237
- 'kfree', 'kmalloc', 'kzalloc', 'kcalloc', 'krealloc', 'kvmalloc', 'kvfree',
238
- 'get', 'put',
239
- // PHP built-ins
240
- 'echo', 'isset', 'empty', 'unset', 'list', 'array', 'compact', 'extract',
241
- 'count', 'strlen', 'strpos', 'strrpos', 'substr', 'strtolower', 'strtoupper', 'trim',
242
- 'ltrim', 'rtrim', 'str_replace', 'str_contains', 'str_starts_with', 'str_ends_with',
243
- 'sprintf', 'vsprintf', 'printf', 'number_format',
244
- 'array_map', 'array_filter', 'array_reduce', 'array_push', 'array_pop', 'array_shift',
245
- 'array_unshift', 'array_slice', 'array_splice', 'array_merge', 'array_keys', 'array_values',
246
- 'array_key_exists', 'in_array', 'array_search', 'array_unique', 'usort', 'rsort',
247
- 'json_encode', 'json_decode', 'serialize', 'unserialize',
248
- 'intval', 'floatval', 'strval', 'boolval', 'is_null', 'is_string', 'is_int', 'is_array',
249
- 'is_object', 'is_numeric', 'is_bool', 'is_float',
250
- 'var_dump', 'print_r', 'var_export',
251
- 'date', 'time', 'strtotime', 'mktime', 'microtime',
252
- 'file_exists', 'file_get_contents', 'file_put_contents', 'is_file', 'is_dir',
253
- 'preg_match', 'preg_match_all', 'preg_replace', 'preg_split',
254
- 'header', 'session_start', 'session_destroy', 'ob_start', 'ob_end_clean', 'ob_get_clean',
255
- 'dd', 'dump',
256
- ]);
257
99
  // ============================================================================
258
100
  // Label detection from capture map
259
101
  // ============================================================================
@@ -309,6 +151,7 @@ const getLabelFromCaptures = (captureMap) => {
309
151
  return 'Template';
310
152
  return 'CodeElement';
311
153
  };
154
+ // DEFINITION_CAPTURE_KEYS and getDefinitionNodeFromCaptures imported from ../utils.js
312
155
  // ============================================================================
313
156
  // Process a batch of files
314
157
  // ============================================================================
@@ -320,6 +163,9 @@ const processBatch = (files, onProgress) => {
320
163
  imports: [],
321
164
  calls: [],
322
165
  heritage: [],
166
+ routes: [],
167
+ constructorBindings: [],
168
+ skippedLanguages: {},
323
169
  fileCount: 0,
324
170
  };
325
171
  // Group by language to minimize setLanguage calls
@@ -367,13 +213,33 @@ const processBatch = (files, onProgress) => {
367
213
  }
368
214
  // Process regular files for this language
369
215
  if (regularFiles.length > 0) {
370
- setLanguage(language, regularFiles[0].path);
371
- 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
+ }
224
+ }
225
+ else {
226
+ result.skippedLanguages[language] = (result.skippedLanguages[language] || 0) + regularFiles.length;
227
+ }
372
228
  }
373
229
  // Process tsx files separately (different grammar)
374
230
  if (tsxFiles.length > 0) {
375
- setLanguage(language, tsxFiles[0].path);
376
- 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
+ }
239
+ }
240
+ else {
241
+ result.skippedLanguages[language] = (result.skippedLanguages[language] || 0) + tsxFiles.length;
242
+ }
377
243
  }
378
244
  }
379
245
  return result;
@@ -482,33 +348,396 @@ function extractEloquentRelationDescription(methodNode) {
482
348
  return relType;
483
349
  return null;
484
350
  }
351
+ const ROUTE_HTTP_METHODS = new Set([
352
+ 'get', 'post', 'put', 'patch', 'delete', 'options', 'any', 'match',
353
+ ]);
354
+ const ROUTE_RESOURCE_METHODS = new Set(['resource', 'apiResource']);
355
+ const RESOURCE_ACTIONS = ['index', 'create', 'store', 'show', 'edit', 'update', 'destroy'];
356
+ const API_RESOURCE_ACTIONS = ['index', 'store', 'show', 'update', 'destroy'];
357
+ /** Check if node is a scoped_call_expression with object 'Route' */
358
+ function isRouteStaticCall(node) {
359
+ if (node.type !== 'scoped_call_expression')
360
+ return false;
361
+ const obj = node.childForFieldName?.('object') ?? node.children?.[0];
362
+ return obj?.text === 'Route';
363
+ }
364
+ /** Get the method name from a scoped_call_expression or member_call_expression */
365
+ function getCallMethodName(node) {
366
+ const nameNode = node.childForFieldName?.('name') ??
367
+ node.children?.find((c) => c.type === 'name');
368
+ return nameNode?.text ?? null;
369
+ }
370
+ /** Get the arguments node from a call expression */
371
+ function getArguments(node) {
372
+ return node.children?.find((c) => c.type === 'arguments') ?? null;
373
+ }
374
+ /** Find the closure body inside arguments */
375
+ function findClosureBody(argsNode) {
376
+ if (!argsNode)
377
+ return null;
378
+ for (const child of argsNode.children ?? []) {
379
+ if (child.type === 'argument') {
380
+ for (const inner of child.children ?? []) {
381
+ if (inner.type === 'anonymous_function' ||
382
+ inner.type === 'arrow_function') {
383
+ return inner.childForFieldName?.('body') ??
384
+ inner.children?.find((c) => c.type === 'compound_statement');
385
+ }
386
+ }
387
+ }
388
+ if (child.type === 'anonymous_function' ||
389
+ child.type === 'arrow_function') {
390
+ return child.childForFieldName?.('body') ??
391
+ child.children?.find((c) => c.type === 'compound_statement');
392
+ }
393
+ }
394
+ return null;
395
+ }
396
+ /** Extract first string argument from arguments node */
397
+ function extractFirstStringArg(argsNode) {
398
+ if (!argsNode)
399
+ return null;
400
+ for (const child of argsNode.children ?? []) {
401
+ const target = child.type === 'argument' ? child.children?.[0] : child;
402
+ if (!target)
403
+ continue;
404
+ if (target.type === 'string' || target.type === 'encapsed_string') {
405
+ return extractStringContent(target);
406
+ }
407
+ }
408
+ return null;
409
+ }
410
+ /** Extract middleware from arguments — handles string or array */
411
+ function extractMiddlewareArg(argsNode) {
412
+ if (!argsNode)
413
+ return [];
414
+ for (const child of argsNode.children ?? []) {
415
+ const target = child.type === 'argument' ? child.children?.[0] : child;
416
+ if (!target)
417
+ continue;
418
+ if (target.type === 'string' || target.type === 'encapsed_string') {
419
+ const val = extractStringContent(target);
420
+ return val ? [val] : [];
421
+ }
422
+ if (target.type === 'array_creation_expression') {
423
+ const items = [];
424
+ for (const el of target.children ?? []) {
425
+ if (el.type === 'array_element_initializer') {
426
+ const str = el.children?.find((c) => c.type === 'string' || c.type === 'encapsed_string');
427
+ const val = str ? extractStringContent(str) : null;
428
+ if (val)
429
+ items.push(val);
430
+ }
431
+ }
432
+ return items;
433
+ }
434
+ }
435
+ return [];
436
+ }
437
+ /** Extract Controller::class from arguments */
438
+ function extractClassArg(argsNode) {
439
+ if (!argsNode)
440
+ return null;
441
+ for (const child of argsNode.children ?? []) {
442
+ const target = child.type === 'argument' ? child.children?.[0] : child;
443
+ if (target?.type === 'class_constant_access_expression') {
444
+ return target.children?.find((c) => c.type === 'name')?.text ?? null;
445
+ }
446
+ }
447
+ return null;
448
+ }
449
+ /** Extract controller class name from arguments: [Controller::class, 'method'] or 'Controller@method' */
450
+ function extractControllerTarget(argsNode) {
451
+ if (!argsNode)
452
+ return { controller: null, method: null };
453
+ const args = [];
454
+ for (const child of argsNode.children ?? []) {
455
+ if (child.type === 'argument')
456
+ args.push(child.children?.[0]);
457
+ else if (child.type !== '(' && child.type !== ')' && child.type !== ',')
458
+ args.push(child);
459
+ }
460
+ // Second arg is the handler
461
+ const handlerNode = args[1];
462
+ if (!handlerNode)
463
+ return { controller: null, method: null };
464
+ // Array syntax: [UserController::class, 'index']
465
+ if (handlerNode.type === 'array_creation_expression') {
466
+ let controller = null;
467
+ let method = null;
468
+ const elements = [];
469
+ for (const el of handlerNode.children ?? []) {
470
+ if (el.type === 'array_element_initializer')
471
+ elements.push(el);
472
+ }
473
+ if (elements[0]) {
474
+ const classAccess = findDescendant(elements[0], 'class_constant_access_expression');
475
+ if (classAccess) {
476
+ controller = classAccess.children?.find((c) => c.type === 'name')?.text ?? null;
477
+ }
478
+ }
479
+ if (elements[1]) {
480
+ const str = findDescendant(elements[1], 'string');
481
+ method = str ? extractStringContent(str) : null;
482
+ }
483
+ return { controller, method };
484
+ }
485
+ // String syntax: 'UserController@index'
486
+ if (handlerNode.type === 'string' || handlerNode.type === 'encapsed_string') {
487
+ const text = extractStringContent(handlerNode);
488
+ if (text?.includes('@')) {
489
+ const [controller, method] = text.split('@');
490
+ return { controller, method };
491
+ }
492
+ }
493
+ // Class reference: UserController::class (invokable controller)
494
+ if (handlerNode.type === 'class_constant_access_expression') {
495
+ const controller = handlerNode.children?.find((c) => c.type === 'name')?.text ?? null;
496
+ return { controller, method: '__invoke' };
497
+ }
498
+ return { controller: null, method: null };
499
+ }
500
+ /**
501
+ * Unwrap a chained call like Route::middleware('auth')->prefix('api')->group(fn)
502
+ */
503
+ function unwrapRouteChain(node) {
504
+ if (node.type !== 'member_call_expression')
505
+ return null;
506
+ const terminalMethod = getCallMethodName(node);
507
+ if (!terminalMethod)
508
+ return null;
509
+ const terminalArgs = getArguments(node);
510
+ const attributes = [];
511
+ let current = node.children?.[0];
512
+ while (current) {
513
+ if (current.type === 'member_call_expression') {
514
+ const method = getCallMethodName(current);
515
+ const args = getArguments(current);
516
+ if (method)
517
+ attributes.unshift({ method, argsNode: args });
518
+ current = current.children?.[0];
519
+ }
520
+ else if (current.type === 'scoped_call_expression') {
521
+ const obj = current.childForFieldName?.('object') ?? current.children?.[0];
522
+ if (obj?.text !== 'Route')
523
+ return null;
524
+ const method = getCallMethodName(current);
525
+ const args = getArguments(current);
526
+ if (method)
527
+ attributes.unshift({ method, argsNode: args });
528
+ return { isRouteFacade: true, terminalMethod, attributes, terminalArgs, node };
529
+ }
530
+ else {
531
+ break;
532
+ }
533
+ }
534
+ return null;
535
+ }
536
+ /** Parse Route::group(['middleware' => ..., 'prefix' => ...], fn) array syntax */
537
+ function parseArrayGroupArgs(argsNode) {
538
+ const ctx = { middleware: [], prefix: null, controller: null };
539
+ if (!argsNode)
540
+ return ctx;
541
+ for (const child of argsNode.children ?? []) {
542
+ const target = child.type === 'argument' ? child.children?.[0] : child;
543
+ if (target?.type === 'array_creation_expression') {
544
+ for (const el of target.children ?? []) {
545
+ if (el.type !== 'array_element_initializer')
546
+ continue;
547
+ const children = el.children ?? [];
548
+ const arrowIdx = children.findIndex((c) => c.type === '=>');
549
+ if (arrowIdx === -1)
550
+ continue;
551
+ const key = extractStringContent(children[arrowIdx - 1]);
552
+ const val = children[arrowIdx + 1];
553
+ if (key === 'middleware') {
554
+ if (val?.type === 'string') {
555
+ const s = extractStringContent(val);
556
+ if (s)
557
+ ctx.middleware.push(s);
558
+ }
559
+ else if (val?.type === 'array_creation_expression') {
560
+ for (const item of val.children ?? []) {
561
+ if (item.type === 'array_element_initializer') {
562
+ const str = item.children?.find((c) => c.type === 'string');
563
+ const s = str ? extractStringContent(str) : null;
564
+ if (s)
565
+ ctx.middleware.push(s);
566
+ }
567
+ }
568
+ }
569
+ }
570
+ else if (key === 'prefix') {
571
+ ctx.prefix = extractStringContent(val) ?? null;
572
+ }
573
+ else if (key === 'controller') {
574
+ if (val?.type === 'class_constant_access_expression') {
575
+ ctx.controller = val.children?.find((c) => c.type === 'name')?.text ?? null;
576
+ }
577
+ }
578
+ }
579
+ }
580
+ }
581
+ return ctx;
582
+ }
583
+ function extractLaravelRoutes(tree, filePath) {
584
+ const routes = [];
585
+ function resolveStack(stack) {
586
+ const middleware = [];
587
+ let prefix = null;
588
+ let controller = null;
589
+ for (const ctx of stack) {
590
+ middleware.push(...ctx.middleware);
591
+ if (ctx.prefix)
592
+ prefix = prefix ? `${prefix}/${ctx.prefix}`.replace(/\/+/g, '/') : ctx.prefix;
593
+ if (ctx.controller)
594
+ controller = ctx.controller;
595
+ }
596
+ return { middleware, prefix, controller };
597
+ }
598
+ function emitRoute(httpMethod, argsNode, lineNumber, groupStack, chainAttrs) {
599
+ const effective = resolveStack(groupStack);
600
+ for (const attr of chainAttrs) {
601
+ if (attr.method === 'middleware')
602
+ effective.middleware.push(...extractMiddlewareArg(attr.argsNode));
603
+ if (attr.method === 'prefix') {
604
+ const p = extractFirstStringArg(attr.argsNode);
605
+ if (p)
606
+ effective.prefix = effective.prefix ? `${effective.prefix}/${p}` : p;
607
+ }
608
+ if (attr.method === 'controller') {
609
+ const cls = extractClassArg(attr.argsNode);
610
+ if (cls)
611
+ effective.controller = cls;
612
+ }
613
+ }
614
+ const routePath = extractFirstStringArg(argsNode);
615
+ if (ROUTE_RESOURCE_METHODS.has(httpMethod)) {
616
+ const target = extractControllerTarget(argsNode);
617
+ const actions = httpMethod === 'apiResource' ? API_RESOURCE_ACTIONS : RESOURCE_ACTIONS;
618
+ for (const action of actions) {
619
+ routes.push({
620
+ filePath, httpMethod, routePath,
621
+ controllerName: target.controller ?? effective.controller,
622
+ methodName: action,
623
+ middleware: [...effective.middleware],
624
+ prefix: effective.prefix,
625
+ lineNumber,
626
+ });
627
+ }
628
+ }
629
+ else {
630
+ const target = extractControllerTarget(argsNode);
631
+ routes.push({
632
+ filePath, httpMethod, routePath,
633
+ controllerName: target.controller ?? effective.controller,
634
+ methodName: target.method,
635
+ middleware: [...effective.middleware],
636
+ prefix: effective.prefix,
637
+ lineNumber,
638
+ });
639
+ }
640
+ }
641
+ function walk(node, groupStack) {
642
+ // Case 1: Simple Route::get(...), Route::post(...), etc.
643
+ if (isRouteStaticCall(node)) {
644
+ const method = getCallMethodName(node);
645
+ if (method && (ROUTE_HTTP_METHODS.has(method) || ROUTE_RESOURCE_METHODS.has(method))) {
646
+ emitRoute(method, getArguments(node), node.startPosition.row, groupStack, []);
647
+ return;
648
+ }
649
+ if (method === 'group') {
650
+ const argsNode = getArguments(node);
651
+ const groupCtx = parseArrayGroupArgs(argsNode);
652
+ const body = findClosureBody(argsNode);
653
+ if (body) {
654
+ groupStack.push(groupCtx);
655
+ walkChildren(body, groupStack);
656
+ groupStack.pop();
657
+ }
658
+ return;
659
+ }
660
+ }
661
+ // Case 2: Fluent chain — Route::middleware(...)->group(...) or Route::middleware(...)->get(...)
662
+ const chain = unwrapRouteChain(node);
663
+ if (chain) {
664
+ if (chain.terminalMethod === 'group') {
665
+ const groupCtx = { middleware: [], prefix: null, controller: null };
666
+ for (const attr of chain.attributes) {
667
+ if (attr.method === 'middleware')
668
+ groupCtx.middleware.push(...extractMiddlewareArg(attr.argsNode));
669
+ if (attr.method === 'prefix')
670
+ groupCtx.prefix = extractFirstStringArg(attr.argsNode);
671
+ if (attr.method === 'controller')
672
+ groupCtx.controller = extractClassArg(attr.argsNode);
673
+ }
674
+ const body = findClosureBody(chain.terminalArgs);
675
+ if (body) {
676
+ groupStack.push(groupCtx);
677
+ walkChildren(body, groupStack);
678
+ groupStack.pop();
679
+ }
680
+ return;
681
+ }
682
+ if (ROUTE_HTTP_METHODS.has(chain.terminalMethod) || ROUTE_RESOURCE_METHODS.has(chain.terminalMethod)) {
683
+ emitRoute(chain.terminalMethod, chain.terminalArgs, node.startPosition.row, groupStack, chain.attributes);
684
+ return;
685
+ }
686
+ }
687
+ // Default: recurse into children
688
+ walkChildren(node, groupStack);
689
+ }
690
+ function walkChildren(node, groupStack) {
691
+ for (const child of node.children ?? []) {
692
+ walk(child, groupStack);
693
+ }
694
+ }
695
+ walk(tree.rootNode, []);
696
+ return routes;
697
+ }
485
698
  const processFileGroup = (files, language, queryString, result, onFileProcessed) => {
486
699
  let query;
487
700
  try {
488
701
  const lang = parser.getLanguage();
489
702
  query = new Parser.Query(lang, queryString);
490
703
  }
491
- 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
+ }
492
712
  return;
493
713
  }
494
714
  for (const file of files) {
495
- // Skip very large files they can crash tree-sitter or cause OOM
496
- 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)
497
717
  continue;
498
718
  let tree;
499
719
  try {
500
- tree = parser.parse(file.content, undefined, { bufferSize: 1024 * 256 });
720
+ tree = parser.parse(file.content, undefined, { bufferSize: getTreeSitterBufferSize(file.content.length) });
501
721
  }
502
- catch {
722
+ catch (err) {
723
+ console.warn(`Failed to parse file ${file.path}: ${err instanceof Error ? err.message : String(err)}`);
503
724
  continue;
504
725
  }
505
726
  result.fileCount++;
506
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
+ }
507
735
  let matches;
508
736
  try {
509
737
  matches = query.matches(tree.rootNode);
510
738
  }
511
- catch {
739
+ catch (err) {
740
+ console.warn(`Query execution failed for ${file.path}: ${err instanceof Error ? err.message : String(err)}`);
512
741
  continue;
513
742
  }
514
743
  for (const match of matches) {
@@ -518,11 +747,15 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
518
747
  }
519
748
  // Extract import paths before skipping
520
749
  if (captureMap['import'] && captureMap['import.source']) {
521
- const rawImportPath = captureMap['import.source'].text.replace(/['"<>]/g, '');
750
+ const rawImportPath = language === SupportedLanguages.Kotlin
751
+ ? appendKotlinWildcard(captureMap['import.source'].text.replace(/['"<>]/g, ''), captureMap['import'])
752
+ : captureMap['import.source'].text.replace(/['"<>]/g, '');
753
+ const namedBindings = extractNamedBindings(captureMap['import'], language);
522
754
  result.imports.push({
523
755
  filePath: file.path,
524
756
  rawImportPath,
525
757
  language: language,
758
+ ...(namedBindings ? { namedBindings } : {}),
526
759
  });
527
760
  continue;
528
761
  }
@@ -531,11 +764,122 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
531
764
  const callNameNode = captureMap['call.name'];
532
765
  if (callNameNode) {
533
766
  const calledName = callNameNode.text;
534
- 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)) {
535
841
  const callNode = captureMap['call'];
536
842
  const sourceId = findEnclosingFunctionId(callNode, file.path)
537
843
  || generateId('File', file.path);
538
- 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
+ });
539
883
  }
540
884
  }
541
885
  continue;
@@ -543,12 +887,21 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
543
887
  // Extract heritage (extends/implements)
544
888
  if (captureMap['heritage.class']) {
545
889
  if (captureMap['heritage.extends']) {
546
- result.heritage.push({
547
- filePath: file.path,
548
- className: captureMap['heritage.class'].text,
549
- parentName: captureMap['heritage.extends'].text,
550
- kind: 'extends',
551
- });
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
+ }
552
905
  }
553
906
  if (captureMap['heritage.implements']) {
554
907
  result.heritage.push({
@@ -574,7 +927,12 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
574
927
  if (!nodeLabel)
575
928
  continue;
576
929
  const nameNode = captureMap['name'];
577
- const nodeName = nameNode.text;
930
+ // Synthesize name for constructors without explicit @name capture (e.g. Swift init)
931
+ if (!nameNode && nodeLabel !== 'Constructor')
932
+ continue;
933
+ const nodeName = nameNode ? nameNode.text : 'init';
934
+ const definitionNode = getDefinitionNodeFromCaptures(captureMap);
935
+ const startLine = definitionNode ? definitionNode.startPosition.row : (nameNode ? nameNode.startPosition.row : 0);
578
936
  const nodeId = generateId(nodeLabel, `${file.path}:${nodeName}`);
579
937
  let description;
580
938
  if (language === SupportedLanguages.PHP) {
@@ -585,6 +943,26 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
585
943
  description = extractEloquentRelationDescription(captureMap['definition.method']) ?? undefined;
586
944
  }
587
945
  }
946
+ const frameworkHint = definitionNode
947
+ ? detectFrameworkFromAST(language, (definitionNode.text || '').slice(0, 300))
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
+ // Also upgrades uninformative AST types like PHP `array` with PHPDoc `@return User[]`
957
+ if ((!returnType || returnType === 'array' || returnType === 'iterable') && definitionNode) {
958
+ const tc = typeConfigs[language];
959
+ if (tc?.extractReturnType) {
960
+ const docReturn = tc.extractReturnType(definitionNode);
961
+ if (docReturn)
962
+ returnType = docReturn;
963
+ }
964
+ }
965
+ }
588
966
  result.nodes.push({
589
967
  id: nodeId,
590
968
  label: nodeLabel,
@@ -594,15 +972,28 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
594
972
  startLine: nameNode.startPosition.row + 1,
595
973
  endLine: nameNode.endPosition.row + 1,
596
974
  language: language,
597
- isExported: isNodeExported(nameNode, nodeName, language),
975
+ isExported: isNodeExported(nameNode || definitionNode, nodeName, language),
976
+ ...(frameworkHint ? {
977
+ astFrameworkMultiplier: frameworkHint.entryPointMultiplier,
978
+ astFrameworkReason: frameworkHint.reason,
979
+ } : {}),
598
980
  ...(description !== undefined ? { description } : {}),
981
+ ...(parameterCount !== undefined ? { parameterCount } : {}),
982
+ ...(returnType !== undefined ? { returnType } : {}),
599
983
  },
600
984
  });
985
+ // Compute enclosing class for Method/Constructor/Property/Function — used for both ownerId and HAS_METHOD
986
+ // Function is included because Kotlin/Rust/Python capture class methods as Function nodes
987
+ const needsOwner = nodeLabel === 'Method' || nodeLabel === 'Constructor' || nodeLabel === 'Property' || nodeLabel === 'Function';
988
+ const enclosingClassId = needsOwner ? findEnclosingClassId(nameNode || definitionNode, file.path) : null;
601
989
  result.symbols.push({
602
990
  filePath: file.path,
603
991
  name: nodeName,
604
992
  nodeId,
605
993
  type: nodeLabel,
994
+ ...(parameterCount !== undefined ? { parameterCount } : {}),
995
+ ...(returnType !== undefined ? { returnType } : {}),
996
+ ...(enclosingClassId ? { ownerId: enclosingClassId } : {}),
606
997
  });
607
998
  const fileId = generateId('File', file.path);
608
999
  const relId = generateId('DEFINES', `${fileId}->${nodeId}`);
@@ -614,6 +1005,22 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
614
1005
  confidence: 1.0,
615
1006
  reason: '',
616
1007
  });
1008
+ // ── HAS_METHOD: link method/constructor/property to enclosing class ──
1009
+ if (enclosingClassId) {
1010
+ result.relationships.push({
1011
+ id: generateId('HAS_METHOD', `${enclosingClassId}->${nodeId}`),
1012
+ sourceId: enclosingClassId,
1013
+ targetId: nodeId,
1014
+ type: 'HAS_METHOD',
1015
+ confidence: 1.0,
1016
+ reason: '',
1017
+ });
1018
+ }
1019
+ }
1020
+ // Extract Laravel routes from route files via procedural AST walk
1021
+ if (language === SupportedLanguages.PHP && (file.path.includes('/routes/') || file.path.startsWith('routes/')) && file.path.endsWith('.php')) {
1022
+ const extractedRoutes = extractLaravelRoutes(tree, file.path);
1023
+ result.routes.push(...extractedRoutes);
617
1024
  }
618
1025
  }
619
1026
  };
@@ -623,7 +1030,7 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
623
1030
  /** Accumulated result across sub-batches */
624
1031
  let accumulated = {
625
1032
  nodes: [], relationships: [], symbols: [],
626
- imports: [], calls: [], heritage: [], fileCount: 0,
1033
+ imports: [], calls: [], heritage: [], routes: [], constructorBindings: [], skippedLanguages: {}, fileCount: 0,
627
1034
  };
628
1035
  let cumulativeProcessed = 0;
629
1036
  const mergeResult = (target, src) => {
@@ -633,6 +1040,11 @@ const mergeResult = (target, src) => {
633
1040
  target.imports.push(...src.imports);
634
1041
  target.calls.push(...src.calls);
635
1042
  target.heritage.push(...src.heritage);
1043
+ target.routes.push(...src.routes);
1044
+ target.constructorBindings.push(...src.constructorBindings);
1045
+ for (const [lang, count] of Object.entries(src.skippedLanguages)) {
1046
+ target.skippedLanguages[lang] = (target.skippedLanguages[lang] || 0) + count;
1047
+ }
636
1048
  target.fileCount += src.fileCount;
637
1049
  };
638
1050
  parentPort.on('message', (msg) => {
@@ -652,7 +1064,7 @@ parentPort.on('message', (msg) => {
652
1064
  if (msg && msg.type === 'flush') {
653
1065
  parentPort.postMessage({ type: 'result', data: accumulated });
654
1066
  // Reset for potential reuse
655
- accumulated = { nodes: [], relationships: [], symbols: [], imports: [], calls: [], heritage: [], fileCount: 0 };
1067
+ accumulated = { nodes: [], relationships: [], symbols: [], imports: [], calls: [], heritage: [], routes: [], constructorBindings: [], skippedLanguages: {}, fileCount: 0 };
656
1068
  cumulativeProcessed = 0;
657
1069
  return;
658
1070
  }