gitnexus 1.3.10 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/README.md +22 -2
  2. package/dist/cli/ai-context.d.ts +2 -1
  3. package/dist/cli/ai-context.js +33 -6
  4. package/dist/cli/analyze.d.ts +2 -0
  5. package/dist/cli/analyze.js +20 -2
  6. package/dist/cli/index.js +2 -0
  7. package/dist/cli/setup.js +17 -19
  8. package/dist/cli/skill-gen.d.ts +26 -0
  9. package/dist/cli/skill-gen.js +549 -0
  10. package/dist/core/graph/types.d.ts +5 -2
  11. package/dist/core/ingestion/call-processor.d.ts +5 -5
  12. package/dist/core/ingestion/call-processor.js +173 -260
  13. package/dist/core/ingestion/constants.d.ts +16 -0
  14. package/dist/core/ingestion/constants.js +16 -0
  15. package/dist/core/ingestion/entry-point-scoring.d.ts +2 -1
  16. package/dist/core/ingestion/entry-point-scoring.js +81 -22
  17. package/dist/core/ingestion/export-detection.d.ts +18 -0
  18. package/dist/core/ingestion/export-detection.js +230 -0
  19. package/dist/core/ingestion/framework-detection.d.ts +5 -1
  20. package/dist/core/ingestion/framework-detection.js +39 -8
  21. package/dist/core/ingestion/heritage-processor.d.ts +13 -4
  22. package/dist/core/ingestion/heritage-processor.js +92 -28
  23. package/dist/core/ingestion/import-processor.d.ts +17 -19
  24. package/dist/core/ingestion/import-processor.js +170 -695
  25. package/dist/core/ingestion/language-config.d.ts +46 -0
  26. package/dist/core/ingestion/language-config.js +167 -0
  27. package/dist/core/ingestion/mro-processor.d.ts +45 -0
  28. package/dist/core/ingestion/mro-processor.js +369 -0
  29. package/dist/core/ingestion/named-binding-extraction.d.ts +61 -0
  30. package/dist/core/ingestion/named-binding-extraction.js +363 -0
  31. package/dist/core/ingestion/parsing-processor.d.ts +1 -10
  32. package/dist/core/ingestion/parsing-processor.js +41 -177
  33. package/dist/core/ingestion/pipeline.js +41 -26
  34. package/dist/core/ingestion/process-processor.js +2 -1
  35. package/dist/core/ingestion/resolvers/csharp.d.ts +22 -0
  36. package/dist/core/ingestion/resolvers/csharp.js +109 -0
  37. package/dist/core/ingestion/resolvers/go.d.ts +19 -0
  38. package/dist/core/ingestion/resolvers/go.js +42 -0
  39. package/dist/core/ingestion/resolvers/index.d.ts +16 -0
  40. package/dist/core/ingestion/resolvers/index.js +11 -0
  41. package/dist/core/ingestion/resolvers/jvm.d.ts +23 -0
  42. package/dist/core/ingestion/resolvers/jvm.js +87 -0
  43. package/dist/core/ingestion/resolvers/php.d.ts +15 -0
  44. package/dist/core/ingestion/resolvers/php.js +35 -0
  45. package/dist/core/ingestion/resolvers/rust.d.ts +15 -0
  46. package/dist/core/ingestion/resolvers/rust.js +73 -0
  47. package/dist/core/ingestion/resolvers/standard.d.ts +28 -0
  48. package/dist/core/ingestion/resolvers/standard.js +145 -0
  49. package/dist/core/ingestion/resolvers/utils.d.ts +33 -0
  50. package/dist/core/ingestion/resolvers/utils.js +120 -0
  51. package/dist/core/ingestion/symbol-resolver.d.ts +32 -0
  52. package/dist/core/ingestion/symbol-resolver.js +83 -0
  53. package/dist/core/ingestion/symbol-table.d.ts +12 -1
  54. package/dist/core/ingestion/symbol-table.js +19 -12
  55. package/dist/core/ingestion/tree-sitter-queries.d.ts +11 -11
  56. package/dist/core/ingestion/tree-sitter-queries.js +114 -9
  57. package/dist/core/ingestion/type-env.d.ts +27 -0
  58. package/dist/core/ingestion/type-env.js +86 -0
  59. package/dist/core/ingestion/type-extractors/c-cpp.d.ts +2 -0
  60. package/dist/core/ingestion/type-extractors/c-cpp.js +60 -0
  61. package/dist/core/ingestion/type-extractors/csharp.d.ts +2 -0
  62. package/dist/core/ingestion/type-extractors/csharp.js +89 -0
  63. package/dist/core/ingestion/type-extractors/go.d.ts +2 -0
  64. package/dist/core/ingestion/type-extractors/go.js +105 -0
  65. package/dist/core/ingestion/type-extractors/index.d.ts +21 -0
  66. package/dist/core/ingestion/type-extractors/index.js +29 -0
  67. package/dist/core/ingestion/type-extractors/jvm.d.ts +3 -0
  68. package/dist/core/ingestion/type-extractors/jvm.js +121 -0
  69. package/dist/core/ingestion/type-extractors/php.d.ts +2 -0
  70. package/dist/core/ingestion/type-extractors/php.js +31 -0
  71. package/dist/core/ingestion/type-extractors/python.d.ts +2 -0
  72. package/dist/core/ingestion/type-extractors/python.js +41 -0
  73. package/dist/core/ingestion/type-extractors/rust.d.ts +2 -0
  74. package/dist/core/ingestion/type-extractors/rust.js +39 -0
  75. package/dist/core/ingestion/type-extractors/shared.d.ts +17 -0
  76. package/dist/core/ingestion/type-extractors/shared.js +97 -0
  77. package/dist/core/ingestion/type-extractors/swift.d.ts +2 -0
  78. package/dist/core/ingestion/type-extractors/swift.js +43 -0
  79. package/dist/core/ingestion/type-extractors/types.d.ts +14 -0
  80. package/dist/core/ingestion/type-extractors/types.js +1 -0
  81. package/dist/core/ingestion/type-extractors/typescript.d.ts +2 -0
  82. package/dist/core/ingestion/type-extractors/typescript.js +46 -0
  83. package/dist/core/ingestion/utils.d.ts +67 -0
  84. package/dist/core/ingestion/utils.js +691 -4
  85. package/dist/core/ingestion/workers/parse-worker.d.ts +20 -3
  86. package/dist/core/ingestion/workers/parse-worker.js +84 -345
  87. package/dist/core/ingestion/workers/worker-pool.js +8 -0
  88. package/dist/core/kuzu/csv-generator.js +19 -3
  89. package/dist/core/kuzu/kuzu-adapter.js +5 -2
  90. package/dist/core/kuzu/schema.d.ts +3 -3
  91. package/dist/core/kuzu/schema.js +16 -1
  92. package/dist/core/search/bm25-index.js +2 -1
  93. package/dist/mcp/core/kuzu-adapter.js +6 -18
  94. package/dist/mcp/tools.js +12 -3
  95. package/hooks/claude/gitnexus-hook.cjs +149 -66
  96. package/package.json +1 -1
  97. package/skills/gitnexus-cli.md +1 -1
@@ -1,3 +1,4 @@
1
+ import { SupportedLanguages } from '../../../config/supported-languages.js';
1
2
  interface ParsedNode {
2
3
  id: string;
3
4
  label: string;
@@ -6,18 +7,20 @@ interface ParsedNode {
6
7
  filePath: string;
7
8
  startLine: number;
8
9
  endLine: number;
9
- language: string;
10
+ language: SupportedLanguages;
10
11
  isExported: boolean;
11
12
  astFrameworkMultiplier?: number;
12
13
  astFrameworkReason?: string;
13
14
  description?: string;
15
+ parameterCount?: number;
16
+ returnType?: string;
14
17
  };
15
18
  }
16
19
  interface ParsedRelationship {
17
20
  id: string;
18
21
  sourceId: string;
19
22
  targetId: string;
20
- type: 'DEFINES';
23
+ type: 'DEFINES' | 'HAS_METHOD';
21
24
  confidence: number;
22
25
  reason: string;
23
26
  }
@@ -26,17 +29,31 @@ interface ParsedSymbol {
26
29
  name: string;
27
30
  nodeId: string;
28
31
  type: string;
32
+ parameterCount?: number;
33
+ ownerId?: string;
29
34
  }
30
35
  export interface ExtractedImport {
31
36
  filePath: string;
32
37
  rawImportPath: string;
33
- language: string;
38
+ language: SupportedLanguages;
39
+ /** Named bindings from the import (e.g., import {User as U} → [{local:'U', exported:'User'}]) */
40
+ namedBindings?: {
41
+ local: string;
42
+ exported: string;
43
+ }[];
34
44
  }
35
45
  export interface ExtractedCall {
36
46
  filePath: string;
37
47
  calledName: string;
38
48
  /** generateId of enclosing function, or generateId('File', filePath) for top-level */
39
49
  sourceId: string;
50
+ argCount?: number;
51
+ /** Discriminates free function calls from member/constructor calls */
52
+ callForm?: 'free' | 'member' | 'constructor';
53
+ /** Simple identifier of the receiver for member calls (e.g., 'user' in user.save()) */
54
+ receiverName?: string;
55
+ /** Resolved type name of the receiver (e.g., 'User' for user.save() when user: User) */
56
+ receiverTypeName?: string;
40
57
  }
41
58
  export interface ExtractedHeritage {
42
59
  filePath: string;
@@ -14,6 +14,7 @@ import PHP from 'tree-sitter-php';
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,13 @@ try {
21
22
  Swift = _require('tree-sitter-swift');
22
23
  }
23
24
  catch { }
24
- import { findSiblingChild, getLanguageFromFilename } from '../utils.js';
25
+ import { getLanguageFromFilename, FUNCTION_NODE_TYPES, extractFunctionName, isBuiltInOrNoise, getDefinitionNodeFromCaptures, findEnclosingClassId, extractMethodSignature, countCallArguments, inferCallForm, extractReceiverName } from '../utils.js';
26
+ import { buildTypeEnv, lookupTypeEnv } from '../type-env.js';
27
+ import { isNodeExported } from '../export-detection.js';
25
28
  import { detectFrameworkFromAST } from '../framework-detection.js';
26
29
  import { generateId } from '../../../lib/utils.js';
30
+ import { extractNamedBindings } from '../named-binding-extraction.js';
31
+ import { appendKotlinWildcard } from '../resolvers/index.js';
27
32
  // ============================================================================
28
33
  // Worker-local parser + language map
29
34
  // ============================================================================
@@ -52,307 +57,24 @@ const setLanguage = (language, filePath) => {
52
57
  throw new Error(`Unsupported language: ${language}`);
53
58
  parser.setLanguage(lang);
54
59
  };
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
- };
60
+ // isNodeExported imported from ../export-detection.js (shared module)
172
61
  // ============================================================================
173
62
  // Enclosing function detection (for call extraction)
174
63
  // ============================================================================
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
64
  /** Walk up AST to find enclosing function, return its generateId or null for top-level */
189
65
  const findEnclosingFunctionId = (node, filePath) => {
190
66
  let current = node.parent;
191
67
  while (current) {
192
68
  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
- }
69
+ const { funcName, label } = extractFunctionName(current);
236
70
  if (funcName) {
237
- const startLine = current.startPosition?.row ?? 0;
238
- return generateId(label, `${filePath}:${funcName}:${startLine}`);
71
+ return generateId(label, `${filePath}:${funcName}`);
239
72
  }
240
73
  }
241
74
  current = current.parent;
242
75
  }
243
76
  return null;
244
77
  };
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
78
  // ============================================================================
357
79
  // Label detection from capture map
358
80
  // ============================================================================
@@ -408,49 +130,7 @@ const getLabelFromCaptures = (captureMap) => {
408
130
  return 'Template';
409
131
  return 'CodeElement';
410
132
  };
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
- };
133
+ // DEFINITION_CAPTURE_KEYS and getDefinitionNodeFromCaptures imported from ../utils.js
454
134
  // ============================================================================
455
135
  // Process a batch of files
456
136
  // ============================================================================
@@ -988,27 +668,38 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
988
668
  const lang = parser.getLanguage();
989
669
  query = new Parser.Query(lang, queryString);
990
670
  }
991
- catch {
671
+ catch (err) {
672
+ const message = `Query compilation failed for ${language}: ${err instanceof Error ? err.message : String(err)}`;
673
+ if (parentPort) {
674
+ parentPort.postMessage({ type: 'warning', message });
675
+ }
676
+ else {
677
+ console.warn(message);
678
+ }
992
679
  return;
993
680
  }
994
681
  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)
682
+ // Skip files larger than the max tree-sitter buffer (32 MB)
683
+ if (file.content.length > TREE_SITTER_MAX_BUFFER)
997
684
  continue;
998
685
  let tree;
999
686
  try {
1000
- tree = parser.parse(file.content, undefined, { bufferSize: 1024 * 256 });
687
+ tree = parser.parse(file.content, undefined, { bufferSize: getTreeSitterBufferSize(file.content.length) });
1001
688
  }
1002
- catch {
689
+ catch (err) {
690
+ console.warn(`Failed to parse file ${file.path}: ${err instanceof Error ? err.message : String(err)}`);
1003
691
  continue;
1004
692
  }
1005
693
  result.fileCount++;
1006
694
  onFileProcessed?.();
695
+ // Build per-file TypeEnv from explicit type annotations (for receiver resolution)
696
+ const typeEnv = buildTypeEnv(tree, language);
1007
697
  let matches;
1008
698
  try {
1009
699
  matches = query.matches(tree.rootNode);
1010
700
  }
1011
- catch {
701
+ catch (err) {
702
+ console.warn(`Query execution failed for ${file.path}: ${err instanceof Error ? err.message : String(err)}`);
1012
703
  continue;
1013
704
  }
1014
705
  for (const match of matches) {
@@ -1021,10 +712,12 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
1021
712
  const rawImportPath = language === SupportedLanguages.Kotlin
1022
713
  ? appendKotlinWildcard(captureMap['import.source'].text.replace(/['"<>]/g, ''), captureMap['import'])
1023
714
  : captureMap['import.source'].text.replace(/['"<>]/g, '');
715
+ const namedBindings = extractNamedBindings(captureMap['import'], language);
1024
716
  result.imports.push({
1025
717
  filePath: file.path,
1026
718
  rawImportPath,
1027
719
  language: language,
720
+ ...(namedBindings ? { namedBindings } : {}),
1028
721
  });
1029
722
  continue;
1030
723
  }
@@ -1033,11 +726,22 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
1033
726
  const callNameNode = captureMap['call.name'];
1034
727
  if (callNameNode) {
1035
728
  const calledName = callNameNode.text;
1036
- if (!BUILT_INS.has(calledName)) {
729
+ if (!isBuiltInOrNoise(calledName)) {
1037
730
  const callNode = captureMap['call'];
1038
731
  const sourceId = findEnclosingFunctionId(callNode, file.path)
1039
732
  || generateId('File', file.path);
1040
- result.calls.push({ filePath: file.path, calledName, sourceId });
733
+ const callForm = inferCallForm(callNode, callNameNode);
734
+ const receiverName = callForm === 'member' ? extractReceiverName(callNameNode) : undefined;
735
+ const receiverTypeName = receiverName ? lookupTypeEnv(typeEnv, receiverName, callNode) : undefined;
736
+ result.calls.push({
737
+ filePath: file.path,
738
+ calledName,
739
+ sourceId,
740
+ argCount: countCallArguments(callNode),
741
+ ...(callForm !== undefined ? { callForm } : {}),
742
+ ...(receiverName !== undefined ? { receiverName } : {}),
743
+ ...(receiverTypeName !== undefined ? { receiverTypeName } : {}),
744
+ });
1041
745
  }
1042
746
  }
1043
747
  continue;
@@ -1045,12 +749,21 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
1045
749
  // Extract heritage (extends/implements)
1046
750
  if (captureMap['heritage.class']) {
1047
751
  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
- });
752
+ // Go struct embedding: the query matches ALL field_declarations with
753
+ // type_identifier, but only anonymous fields (no name) are embedded.
754
+ // Named fields like `Breed string` also match — skip them.
755
+ const extendsNode = captureMap['heritage.extends'];
756
+ const fieldDecl = extendsNode.parent;
757
+ const isNamedField = fieldDecl?.type === 'field_declaration'
758
+ && fieldDecl.childForFieldName('name');
759
+ if (!isNamedField) {
760
+ result.heritage.push({
761
+ filePath: file.path,
762
+ className: captureMap['heritage.class'].text,
763
+ parentName: captureMap['heritage.extends'].text,
764
+ kind: 'extends',
765
+ });
766
+ }
1054
767
  }
1055
768
  if (captureMap['heritage.implements']) {
1056
769
  result.heritage.push({
@@ -1082,7 +795,7 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
1082
795
  const nodeName = nameNode ? nameNode.text : 'init';
1083
796
  const definitionNode = getDefinitionNodeFromCaptures(captureMap);
1084
797
  const startLine = definitionNode ? definitionNode.startPosition.row : (nameNode ? nameNode.startPosition.row : 0);
1085
- const nodeId = generateId(nodeLabel, `${file.path}:${nodeName}:${startLine}`);
798
+ const nodeId = generateId(nodeLabel, `${file.path}:${nodeName}`);
1086
799
  let description;
1087
800
  if (language === SupportedLanguages.PHP) {
1088
801
  if (nodeLabel === 'Property' && captureMap['definition.property']) {
@@ -1095,6 +808,13 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
1095
808
  const frameworkHint = definitionNode
1096
809
  ? detectFrameworkFromAST(language, (definitionNode.text || '').slice(0, 300))
1097
810
  : null;
811
+ let parameterCount;
812
+ let returnType;
813
+ if (nodeLabel === 'Function' || nodeLabel === 'Method' || nodeLabel === 'Constructor') {
814
+ const sig = extractMethodSignature(definitionNode);
815
+ parameterCount = sig.parameterCount;
816
+ returnType = sig.returnType;
817
+ }
1098
818
  result.nodes.push({
1099
819
  id: nodeId,
1100
820
  label: nodeLabel,
@@ -1110,13 +830,21 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
1110
830
  astFrameworkReason: frameworkHint.reason,
1111
831
  } : {}),
1112
832
  ...(description !== undefined ? { description } : {}),
833
+ ...(parameterCount !== undefined ? { parameterCount } : {}),
834
+ ...(returnType !== undefined ? { returnType } : {}),
1113
835
  },
1114
836
  });
837
+ // Compute enclosing class for Method/Constructor/Property/Function — used for both ownerId and HAS_METHOD
838
+ // Function is included because Kotlin/Rust/Python capture class methods as Function nodes
839
+ const needsOwner = nodeLabel === 'Method' || nodeLabel === 'Constructor' || nodeLabel === 'Property' || nodeLabel === 'Function';
840
+ const enclosingClassId = needsOwner ? findEnclosingClassId(nameNode || definitionNode, file.path) : null;
1115
841
  result.symbols.push({
1116
842
  filePath: file.path,
1117
843
  name: nodeName,
1118
844
  nodeId,
1119
845
  type: nodeLabel,
846
+ ...(parameterCount !== undefined ? { parameterCount } : {}),
847
+ ...(enclosingClassId ? { ownerId: enclosingClassId } : {}),
1120
848
  });
1121
849
  const fileId = generateId('File', file.path);
1122
850
  const relId = generateId('DEFINES', `${fileId}->${nodeId}`);
@@ -1128,6 +856,17 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
1128
856
  confidence: 1.0,
1129
857
  reason: '',
1130
858
  });
859
+ // ── HAS_METHOD: link method/constructor/property to enclosing class ──
860
+ if (enclosingClassId) {
861
+ result.relationships.push({
862
+ id: generateId('HAS_METHOD', `${enclosingClassId}->${nodeId}`),
863
+ sourceId: enclosingClassId,
864
+ targetId: nodeId,
865
+ type: 'HAS_METHOD',
866
+ confidence: 1.0,
867
+ reason: '',
868
+ });
869
+ }
1131
870
  }
1132
871
  // Extract Laravel routes from route files via procedural AST walk
1133
872
  if (language === SupportedLanguages.PHP && (file.path.includes('/routes/') || file.path.startsWith('routes/')) && file.path.endsWith('.php')) {
@@ -1,5 +1,7 @@
1
1
  import { Worker } from 'node:worker_threads';
2
2
  import os from 'node:os';
3
+ import fs from 'node:fs';
4
+ import { fileURLToPath } from 'node:url';
3
5
  /**
4
6
  * Max files to send to a worker in a single postMessage.
5
7
  * Keeps structured-clone memory bounded per sub-batch.
@@ -12,6 +14,12 @@ const SUB_BATCH_TIMEOUT_MS = 30_000;
12
14
  * Create a pool of worker threads.
13
15
  */
14
16
  export const createWorkerPool = (workerUrl, poolSize) => {
17
+ // Validate worker script exists before spawning to prevent uncaught
18
+ // MODULE_NOT_FOUND crashes in worker threads (e.g. when running from src/ via vitest)
19
+ const workerPath = fileURLToPath(workerUrl);
20
+ if (!fs.existsSync(workerPath)) {
21
+ throw new Error(`Worker script not found: ${workerPath}`);
22
+ }
15
23
  const size = poolSize ?? Math.min(8, Math.max(1, os.cpus().length - 1));
16
24
  const workers = [];
17
25
  for (let i = 0; i < size; i++) {