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,44 +1,13 @@
1
+ import { isFileInPackageDir } from './import-processor.js';
2
+ import { resolveSymbolInternal } from './symbol-resolver.js';
3
+ import { walkBindingChain } from './named-binding-extraction.js';
1
4
  import Parser from 'tree-sitter';
2
- import { loadParser, loadLanguage } from '../tree-sitter/parser-loader.js';
5
+ import { isLanguageAvailable, loadParser, loadLanguage } from '../tree-sitter/parser-loader.js';
3
6
  import { LANGUAGE_QUERIES } from './tree-sitter-queries.js';
4
7
  import { generateId } from '../../lib/utils.js';
5
- import { getLanguageFromFilename, yieldToEventLoop } from './utils.js';
6
- /**
7
- * Node types that represent function/method definitions across languages.
8
- * Used to find the enclosing function for a call site.
9
- */
10
- const FUNCTION_NODE_TYPES = new Set([
11
- // TypeScript/JavaScript
12
- 'function_declaration',
13
- 'arrow_function',
14
- 'function_expression',
15
- 'method_definition',
16
- 'generator_function_declaration',
17
- // Python
18
- 'function_definition',
19
- // Common async variants
20
- 'async_function_declaration',
21
- 'async_arrow_function',
22
- // Java
23
- 'method_declaration',
24
- 'constructor_declaration',
25
- // C/C++
26
- // 'function_definition' already included above
27
- // Go
28
- // 'method_declaration' already included from Java
29
- // C#
30
- 'local_function_statement',
31
- // Rust
32
- 'function_item',
33
- 'impl_item', // Methods inside impl blocks
34
- // Kotlin (function_declaration already included above via JS/TS)
35
- 'anonymous_function',
36
- 'lambda_literal',
37
- // PHP — no additional node types needed
38
- // Swift
39
- 'init_declaration',
40
- 'deinit_declaration',
41
- ]);
8
+ import { getLanguageFromFilename, isVerboseIngestionEnabled, yieldToEventLoop, FUNCTION_NODE_TYPES, extractFunctionName, isBuiltInOrNoise, countCallArguments, inferCallForm, extractReceiverName, } from './utils.js';
9
+ import { buildTypeEnv, lookupTypeEnv } from './type-env.js';
10
+ import { getTreeSitterBufferSize } from './constants.js';
42
11
  /**
43
12
  * Walk up the AST from a node to find the enclosing function/method.
44
13
  * Returns null if the call is at module/file level (top-level code).
@@ -47,88 +16,22 @@ const findEnclosingFunction = (node, filePath, symbolTable) => {
47
16
  let current = node.parent;
48
17
  while (current) {
49
18
  if (FUNCTION_NODE_TYPES.has(current.type)) {
50
- // Found enclosing function - try to get its name
51
- let funcName = null;
52
- let label = 'Function';
53
- // Different node types have different name locations
54
- // Swift init/deinit — handle before generic cases (more specific)
55
- if (current.type === 'init_declaration' || current.type === 'deinit_declaration') {
56
- const funcName = current.type === 'init_declaration' ? 'init' : 'deinit';
57
- return generateId('Constructor', `${filePath}:${funcName}`);
58
- }
59
- if (current.type === 'function_declaration' ||
60
- current.type === 'function_definition' ||
61
- current.type === 'async_function_declaration' ||
62
- current.type === 'generator_function_declaration' ||
63
- current.type === 'function_item') { // Rust function
64
- // Named function: function foo() {}
65
- const nameNode = current.childForFieldName?.('name') ||
66
- current.children?.find((c) => c.type === 'identifier' || c.type === 'property_identifier');
67
- funcName = nameNode?.text;
68
- }
69
- else if (current.type === 'impl_item') {
70
- // Rust method inside impl block: wrapper around function_item or const_item
71
- // We need to look inside for the function_item
72
- const funcItem = current.children?.find((c) => c.type === 'function_item');
73
- if (funcItem) {
74
- const nameNode = funcItem.childForFieldName?.('name') ||
75
- funcItem.children?.find((c) => c.type === 'identifier');
76
- funcName = nameNode?.text;
77
- label = 'Method';
78
- }
79
- }
80
- else if (current.type === 'method_definition') {
81
- // Method: foo() {} inside class (JS/TS)
82
- const nameNode = current.childForFieldName?.('name') ||
83
- current.children?.find((c) => c.type === 'property_identifier');
84
- funcName = nameNode?.text;
85
- label = 'Method';
86
- }
87
- else if (current.type === 'method_declaration') {
88
- // Java method: public void foo() {}
89
- const nameNode = current.childForFieldName?.('name') ||
90
- current.children?.find((c) => c.type === 'identifier');
91
- funcName = nameNode?.text;
92
- label = 'Method';
93
- }
94
- else if (current.type === 'constructor_declaration') {
95
- // Java constructor: public ClassName() {}
96
- const nameNode = current.childForFieldName?.('name') ||
97
- current.children?.find((c) => c.type === 'identifier');
98
- funcName = nameNode?.text;
99
- label = 'Method'; // Treat constructors as methods for process detection
100
- }
101
- else if (current.type === 'arrow_function' || current.type === 'function_expression') {
102
- // Arrow/expression: const foo = () => {} - check parent variable declarator
103
- const parent = current.parent;
104
- if (parent?.type === 'variable_declarator') {
105
- const nameNode = parent.childForFieldName?.('name') ||
106
- parent.children?.find((c) => c.type === 'identifier');
107
- funcName = nameNode?.text;
108
- }
109
- }
19
+ const { funcName, label } = extractFunctionName(current);
110
20
  if (funcName) {
111
- // Look up the function in symbol table to get its node ID
112
- // Try exact match first
113
21
  const nodeId = symbolTable.lookupExact(filePath, funcName);
114
22
  if (nodeId)
115
23
  return nodeId;
116
- // Try construct ID manually if lookup fails (common for non-exported internal functions)
117
- // Format should match what parsing-processor generates: "Function:path/to/file:funcName"
118
- // Check if we already have a node with this ID in the symbol table to be safe
119
- const generatedId = generateId(label, `${filePath}:${funcName}`);
120
- // Ideally we should verify this ID exists, but strictly speaking if we are inside it,
121
- // it SHOULD exist. Returning it is better than falling back to File.
122
- return generatedId;
24
+ return generateId(label, `${filePath}:${funcName}`);
123
25
  }
124
- // Couldn't determine function name - try parent (might be nested)
125
26
  }
126
27
  current = current.parent;
127
28
  }
128
- return null; // Top-level call (not inside any function)
29
+ return null;
129
30
  };
130
- export const processCalls = async (graph, files, astCache, symbolTable, importMap, onProgress) => {
31
+ export const processCalls = async (graph, files, astCache, symbolTable, importMap, packageMap, onProgress, namedImportMap) => {
131
32
  const parser = await loadParser();
33
+ const logSkipped = isVerboseIngestionEnabled();
34
+ const skippedByLang = logSkipped ? new Map() : null;
132
35
  for (let i = 0; i < files.length; i++) {
133
36
  const file = files[i];
134
37
  onProgress?.(i + 1, files.length);
@@ -138,6 +41,12 @@ export const processCalls = async (graph, files, astCache, symbolTable, importMa
138
41
  const language = getLanguageFromFilename(file.path);
139
42
  if (!language)
140
43
  continue;
44
+ if (!isLanguageAvailable(language)) {
45
+ if (skippedByLang) {
46
+ skippedByLang.set(language, (skippedByLang.get(language) ?? 0) + 1);
47
+ }
48
+ continue;
49
+ }
141
50
  const queryStr = LANGUAGE_QUERIES[language];
142
51
  if (!queryStr)
143
52
  continue;
@@ -150,7 +59,7 @@ export const processCalls = async (graph, files, astCache, symbolTable, importMa
150
59
  // Cache Miss: Re-parse
151
60
  // Use larger bufferSize for files > 32KB
152
61
  try {
153
- tree = parser.parse(file.content, undefined, { bufferSize: 1024 * 256 });
62
+ tree = parser.parse(file.content, undefined, { bufferSize: getTreeSitterBufferSize(file.content.length) });
154
63
  }
155
64
  catch (parseError) {
156
65
  // Skip files that can't be parsed
@@ -171,6 +80,9 @@ export const processCalls = async (graph, files, astCache, symbolTable, importMa
171
80
  console.warn(`Query error for ${file.path}:`, queryError);
172
81
  continue;
173
82
  }
83
+ // Build per-file TypeEnv for receiver resolution
84
+ const lang = getLanguageFromFilename(file.path);
85
+ const typeEnv = lang ? buildTypeEnv(tree, lang) : new Map();
174
86
  // 3. Process each call match
175
87
  matches.forEach(match => {
176
88
  const captureMap = {};
@@ -185,12 +97,20 @@ export const processCalls = async (graph, files, astCache, symbolTable, importMa
185
97
  // Skip common built-ins and noise
186
98
  if (isBuiltInOrNoise(calledName))
187
99
  return;
100
+ const callNode = captureMap['call'];
101
+ const callForm = inferCallForm(callNode, nameNode);
102
+ const receiverName = callForm === 'member' ? extractReceiverName(nameNode) : undefined;
103
+ const receiverTypeName = receiverName ? lookupTypeEnv(typeEnv, receiverName, callNode) : undefined;
188
104
  // 4. Resolve the target using priority strategy (returns confidence)
189
- const resolved = resolveCallTarget(calledName, file.path, symbolTable, importMap);
105
+ const resolved = resolveCallTarget({
106
+ calledName,
107
+ argCount: countCallArguments(callNode),
108
+ callForm,
109
+ receiverTypeName,
110
+ }, file.path, symbolTable, importMap, packageMap, namedImportMap);
190
111
  if (!resolved)
191
112
  return;
192
113
  // 5. Find the enclosing function (caller)
193
- const callNode = captureMap['call'];
194
114
  const enclosingFuncId = findEnclosingFunction(callNode, file.path, symbolTable);
195
115
  // Use enclosing function as source, fallback to file for top-level calls
196
116
  const sourceId = enclosingFuncId || generateId('File', file.path);
@@ -206,149 +126,137 @@ export const processCalls = async (graph, files, astCache, symbolTable, importMa
206
126
  });
207
127
  // Tree is now owned by the LRU cache — no manual delete needed
208
128
  }
129
+ if (skippedByLang && skippedByLang.size > 0) {
130
+ for (const [lang, count] of skippedByLang.entries()) {
131
+ console.warn(`[ingestion] Skipped ${count} ${lang} file(s) in call processing — ${lang} parser not available.`);
132
+ }
133
+ }
134
+ };
135
+ const CALLABLE_SYMBOL_TYPES = new Set([
136
+ 'Function',
137
+ 'Method',
138
+ 'Constructor',
139
+ 'Macro',
140
+ 'Delegate',
141
+ ]);
142
+ const collectTieredCandidates = (calledName, currentFile, symbolTable, importMap, packageMap, namedImportMap) => {
143
+ const allDefs = symbolTable.lookupFuzzy(calledName);
144
+ // Tier 1: Same-file — highest priority, prevents imports from shadowing local defs
145
+ // (matches resolveSymbolInternal which checks lookupExactFull before named bindings)
146
+ const localDefs = allDefs.filter(def => def.filePath === currentFile);
147
+ if (localDefs.length > 0) {
148
+ return { candidates: localDefs, tier: 'same-file' };
149
+ }
150
+ // Tier 2a-named: Check named bindings with re-export chain following.
151
+ // Aliased imports (import { User as U }) mean lookupFuzzy('U') returns
152
+ // empty but we can resolve via the exported name.
153
+ // Re-exports (export { User } from './base') are followed up to 5 hops.
154
+ if (namedImportMap) {
155
+ const chainResult = resolveNamedBindingChainForCandidates(calledName, currentFile, symbolTable, namedImportMap, allDefs);
156
+ if (chainResult)
157
+ return chainResult;
158
+ }
159
+ if (allDefs.length === 0)
160
+ return null;
161
+ const importedFiles = importMap.get(currentFile);
162
+ if (importedFiles) {
163
+ const importedDefs = allDefs.filter(def => importedFiles.has(def.filePath));
164
+ if (importedDefs.length > 0) {
165
+ return { candidates: importedDefs, tier: 'import-scoped' };
166
+ }
167
+ }
168
+ const importedPackages = packageMap?.get(currentFile);
169
+ if (importedPackages) {
170
+ const packageDefs = allDefs.filter(def => {
171
+ for (const dirSuffix of importedPackages) {
172
+ if (isFileInPackageDir(def.filePath, dirSuffix))
173
+ return true;
174
+ }
175
+ return false;
176
+ });
177
+ if (packageDefs.length > 0) {
178
+ return { candidates: packageDefs, tier: 'import-scoped' };
179
+ }
180
+ }
181
+ // Tier 3: Global — pass all candidates through; filterCallableCandidates
182
+ // will narrow by kind/arity and resolveCallTarget only emits when exactly 1 remains.
183
+ return { candidates: allDefs, tier: 'unique-global' };
184
+ };
185
+ const CONSTRUCTOR_TARGET_TYPES = new Set(['Constructor', 'Class', 'Struct', 'Record']);
186
+ const filterCallableCandidates = (candidates, argCount, callForm) => {
187
+ let kindFiltered;
188
+ if (callForm === 'constructor') {
189
+ // For constructor calls, prefer Constructor > Class/Struct/Record > callable fallback
190
+ const constructors = candidates.filter(c => c.type === 'Constructor');
191
+ if (constructors.length > 0) {
192
+ kindFiltered = constructors;
193
+ }
194
+ else {
195
+ const types = candidates.filter(c => CONSTRUCTOR_TARGET_TYPES.has(c.type));
196
+ kindFiltered = types.length > 0 ? types : candidates.filter(c => CALLABLE_SYMBOL_TYPES.has(c.type));
197
+ }
198
+ }
199
+ else {
200
+ kindFiltered = candidates.filter(c => CALLABLE_SYMBOL_TYPES.has(c.type));
201
+ }
202
+ if (kindFiltered.length === 0)
203
+ return [];
204
+ if (argCount === undefined)
205
+ return kindFiltered;
206
+ const hasParameterMetadata = kindFiltered.some(candidate => candidate.parameterCount !== undefined);
207
+ if (!hasParameterMetadata)
208
+ return kindFiltered;
209
+ return kindFiltered.filter(candidate => candidate.parameterCount === undefined || candidate.parameterCount === argCount);
210
+ };
211
+ const toResolveResult = (definition, tier) => {
212
+ if (tier === 'same-file') {
213
+ return { nodeId: definition.nodeId, confidence: 0.95, reason: 'same-file' };
214
+ }
215
+ if (tier === 'import-scoped') {
216
+ return { nodeId: definition.nodeId, confidence: 0.9, reason: 'import-resolved' };
217
+ }
218
+ return { nodeId: definition.nodeId, confidence: 0.5, reason: 'unique-global' };
209
219
  };
210
220
  /**
211
221
  * Resolve a function call to its target node ID using priority strategy:
212
- * A. Check imported files first (highest confidence)
213
- * B. Check local file definitions
214
- * C. Fuzzy global search (lowest confidence)
222
+ * A. Narrow candidates by scope tier (same-file, import-scoped, unique-global)
223
+ * B. Filter to callable symbol kinds (constructor-aware when callForm is set)
224
+ * C. Apply arity filtering when parameter metadata is available
225
+ * D. Apply receiver-type filtering for member calls with typed receivers
215
226
  *
216
- * Returns confidence score so agents know what to trust.
227
+ * If filtering still leaves multiple candidates, refuse to emit a CALLS edge.
217
228
  */
218
- const resolveCallTarget = (calledName, currentFile, symbolTable, importMap) => {
219
- // Strategy B first (cheapest single map lookup): Check local file
220
- const localNodeId = symbolTable.lookupExact(currentFile, calledName);
221
- if (localNodeId) {
222
- return { nodeId: localNodeId, confidence: 0.85, reason: 'same-file' };
223
- }
224
- // Strategy A: Check if any definition of calledName is in an imported file
225
- // Reversed: instead of iterating all imports and checking each, get all definitions
226
- // and check if any is imported. O(definitions) instead of O(imports).
227
- const allDefs = symbolTable.lookupFuzzy(calledName);
228
- if (allDefs.length > 0) {
229
- const importedFiles = importMap.get(currentFile);
230
- if (importedFiles) {
231
- for (const def of allDefs) {
232
- if (importedFiles.has(def.filePath)) {
233
- return { nodeId: def.nodeId, confidence: 0.9, reason: 'import-resolved' };
234
- }
229
+ const resolveCallTarget = (call, currentFile, symbolTable, importMap, packageMap, namedImportMap) => {
230
+ const tiered = collectTieredCandidates(call.calledName, currentFile, symbolTable, importMap, packageMap, namedImportMap);
231
+ if (!tiered)
232
+ return null;
233
+ const filteredCandidates = filterCallableCandidates(tiered.candidates, call.argCount, call.callForm);
234
+ // D. Receiver-type filtering: for member calls with a known receiver type,
235
+ // filter candidates by ownerId matching the resolved type's nodeId
236
+ if (call.callForm === 'member' && call.receiverTypeName && filteredCandidates.length > 1) {
237
+ const typeDefs = symbolTable.lookupFuzzy(call.receiverTypeName);
238
+ if (typeDefs.length > 0) {
239
+ const typeNodeIds = new Set(typeDefs.map(d => d.nodeId));
240
+ const ownerFiltered = filteredCandidates.filter(c => c.ownerId && typeNodeIds.has(c.ownerId));
241
+ if (ownerFiltered.length === 1) {
242
+ return toResolveResult(ownerFiltered[0], tiered.tier);
235
243
  }
244
+ // If receiver filtering narrows to 0, fall through to name-only resolution
245
+ // If still 2+, refuse (don't guess)
246
+ if (ownerFiltered.length > 1)
247
+ return null;
236
248
  }
237
- // Strategy C: Fuzzy global (no import match found)
238
- const confidence = allDefs.length === 1 ? 0.5 : 0.3;
239
- return { nodeId: allDefs[0].nodeId, confidence, reason: 'fuzzy-global' };
240
249
  }
241
- return null;
250
+ if (filteredCandidates.length !== 1)
251
+ return null;
252
+ return toResolveResult(filteredCandidates[0], tiered.tier);
242
253
  };
243
- /**
244
- * Filter out common built-in functions and noise
245
- * that shouldn't be tracked as calls
246
- */
247
- /** Pre-built set (module-level singleton) to avoid re-creating per call */
248
- const BUILT_IN_NAMES = new Set([
249
- // JavaScript/TypeScript built-ins
250
- 'console', 'log', 'warn', 'error', 'info', 'debug',
251
- 'setTimeout', 'setInterval', 'clearTimeout', 'clearInterval',
252
- 'parseInt', 'parseFloat', 'isNaN', 'isFinite',
253
- 'encodeURI', 'decodeURI', 'encodeURIComponent', 'decodeURIComponent',
254
- 'JSON', 'parse', 'stringify',
255
- 'Object', 'Array', 'String', 'Number', 'Boolean', 'Symbol', 'BigInt',
256
- 'Map', 'Set', 'WeakMap', 'WeakSet',
257
- 'Promise', 'resolve', 'reject', 'then', 'catch', 'finally',
258
- 'Math', 'Date', 'RegExp', 'Error',
259
- 'require', 'import', 'export',
260
- 'fetch', 'Response', 'Request',
261
- // React hooks and common functions
262
- 'useState', 'useEffect', 'useCallback', 'useMemo', 'useRef', 'useContext',
263
- 'useReducer', 'useLayoutEffect', 'useImperativeHandle', 'useDebugValue',
264
- 'createElement', 'createContext', 'createRef', 'forwardRef', 'memo', 'lazy',
265
- // Common array/object methods
266
- 'map', 'filter', 'reduce', 'forEach', 'find', 'findIndex', 'some', 'every',
267
- 'includes', 'indexOf', 'slice', 'splice', 'concat', 'join', 'split',
268
- 'push', 'pop', 'shift', 'unshift', 'sort', 'reverse',
269
- 'keys', 'values', 'entries', 'assign', 'freeze', 'seal',
270
- 'hasOwnProperty', 'toString', 'valueOf',
271
- // Python built-ins
272
- 'print', 'len', 'range', 'str', 'int', 'float', 'list', 'dict', 'set', 'tuple',
273
- 'open', 'read', 'write', 'close', 'append', 'extend', 'update',
274
- 'super', 'type', 'isinstance', 'issubclass', 'getattr', 'setattr', 'hasattr',
275
- 'enumerate', 'zip', 'sorted', 'reversed', 'min', 'max', 'sum', 'abs',
276
- // Kotlin stdlib (IMPORTANT: keep in sync with parse-worker.ts BUILT_IN_NAMES)
277
- 'println', 'print', 'readLine', 'require', 'requireNotNull', 'check', 'assert', 'lazy', 'error',
278
- 'listOf', 'mapOf', 'setOf', 'mutableListOf', 'mutableMapOf', 'mutableSetOf',
279
- 'arrayOf', 'sequenceOf', 'also', 'apply', 'run', 'with', 'takeIf', 'takeUnless',
280
- 'TODO', 'buildString', 'buildList', 'buildMap', 'buildSet',
281
- 'repeat', 'synchronized',
282
- // Kotlin coroutine builders & scope functions
283
- 'launch', 'async', 'runBlocking', 'withContext', 'coroutineScope',
284
- 'supervisorScope', 'delay',
285
- // Kotlin Flow operators
286
- 'flow', 'flowOf', 'collect', 'emit', 'onEach', 'catch',
287
- 'buffer', 'conflate', 'distinctUntilChanged',
288
- 'flatMapLatest', 'flatMapMerge', 'combine',
289
- 'stateIn', 'shareIn', 'launchIn',
290
- // Kotlin infix stdlib functions
291
- 'to', 'until', 'downTo', 'step',
292
- // C/C++ standard library and common kernel helpers
293
- 'printf', 'fprintf', 'sprintf', 'snprintf', 'vprintf', 'vfprintf', 'vsprintf', 'vsnprintf',
294
- 'scanf', 'fscanf', 'sscanf',
295
- 'malloc', 'calloc', 'realloc', 'free', 'memcpy', 'memmove', 'memset', 'memcmp',
296
- 'strlen', 'strcpy', 'strncpy', 'strcat', 'strncat', 'strcmp', 'strncmp', 'strstr', 'strchr', 'strrchr',
297
- 'atoi', 'atol', 'atof', 'strtol', 'strtoul', 'strtoll', 'strtoull', 'strtod',
298
- 'sizeof', 'offsetof', 'typeof',
299
- 'assert', 'abort', 'exit', '_exit',
300
- 'fopen', 'fclose', 'fread', 'fwrite', 'fseek', 'ftell', 'rewind', 'fflush', 'fgets', 'fputs',
301
- // Linux kernel common macros/helpers (not real call targets)
302
- 'likely', 'unlikely', 'BUG', 'BUG_ON', 'WARN', 'WARN_ON', 'WARN_ONCE',
303
- 'IS_ERR', 'PTR_ERR', 'ERR_PTR', 'IS_ERR_OR_NULL',
304
- 'ARRAY_SIZE', 'container_of', 'list_for_each_entry', 'list_for_each_entry_safe',
305
- 'min', 'max', 'clamp', 'abs', 'swap',
306
- 'pr_info', 'pr_warn', 'pr_err', 'pr_debug', 'pr_notice', 'pr_crit', 'pr_emerg',
307
- 'printk', 'dev_info', 'dev_warn', 'dev_err', 'dev_dbg',
308
- 'GFP_KERNEL', 'GFP_ATOMIC',
309
- 'spin_lock', 'spin_unlock', 'spin_lock_irqsave', 'spin_unlock_irqrestore',
310
- 'mutex_lock', 'mutex_unlock', 'mutex_init',
311
- 'kfree', 'kmalloc', 'kzalloc', 'kcalloc', 'krealloc', 'kvmalloc', 'kvfree',
312
- 'get', 'put',
313
- // Swift/iOS built-ins and standard library
314
- 'print', 'debugPrint', 'dump', 'fatalError', 'precondition', 'preconditionFailure',
315
- 'assert', 'assertionFailure', 'NSLog',
316
- 'abs', 'min', 'max', 'zip', 'stride', 'sequence', 'repeatElement',
317
- 'swap', 'withUnsafePointer', 'withUnsafeMutablePointer', 'withUnsafeBytes',
318
- 'autoreleasepool', 'unsafeBitCast', 'unsafeDowncast', 'numericCast',
319
- 'type', 'MemoryLayout',
320
- // Swift collection/string methods (common noise)
321
- 'map', 'flatMap', 'compactMap', 'filter', 'reduce', 'forEach', 'contains',
322
- 'first', 'last', 'prefix', 'suffix', 'dropFirst', 'dropLast',
323
- 'sorted', 'reversed', 'enumerated', 'joined', 'split',
324
- 'append', 'insert', 'remove', 'removeAll', 'removeFirst', 'removeLast',
325
- 'isEmpty', 'count', 'index', 'startIndex', 'endIndex',
326
- // UIKit/Foundation common methods (noise in call graph)
327
- 'addSubview', 'removeFromSuperview', 'layoutSubviews', 'setNeedsLayout',
328
- 'layoutIfNeeded', 'setNeedsDisplay', 'invalidateIntrinsicContentSize',
329
- 'addTarget', 'removeTarget', 'addGestureRecognizer',
330
- 'addConstraint', 'addConstraints', 'removeConstraint', 'removeConstraints',
331
- 'NSLocalizedString', 'Bundle',
332
- 'reloadData', 'reloadSections', 'reloadRows', 'performBatchUpdates',
333
- 'register', 'dequeueReusableCell', 'dequeueReusableSupplementaryView',
334
- 'beginUpdates', 'endUpdates', 'insertRows', 'deleteRows', 'insertSections', 'deleteSections',
335
- 'present', 'dismiss', 'pushViewController', 'popViewController', 'popToRootViewController',
336
- 'performSegue', 'prepare',
337
- // GCD / async
338
- 'DispatchQueue', 'async', 'sync', 'asyncAfter',
339
- 'Task', 'withCheckedContinuation', 'withCheckedThrowingContinuation',
340
- // Combine
341
- 'sink', 'store', 'assign', 'receive', 'subscribe',
342
- // Notification / KVO
343
- 'addObserver', 'removeObserver', 'post', 'NotificationCenter',
344
- ]);
345
- const isBuiltInOrNoise = (name) => BUILT_IN_NAMES.has(name);
346
254
  /**
347
255
  * Fast path: resolve pre-extracted call sites from workers.
348
256
  * No AST parsing — workers already extracted calledName + sourceId.
349
257
  * This function only does symbol table lookups + graph mutations.
350
258
  */
351
- export const processCallsFromExtracted = async (graph, extractedCalls, symbolTable, importMap, onProgress) => {
259
+ export const processCallsFromExtracted = async (graph, extractedCalls, symbolTable, importMap, packageMap, onProgress, namedImportMap) => {
352
260
  // Group by file for progress reporting
353
261
  const byFile = new Map();
354
262
  for (const call of extractedCalls) {
@@ -368,7 +276,7 @@ export const processCallsFromExtracted = async (graph, extractedCalls, symbolTab
368
276
  await yieldToEventLoop();
369
277
  }
370
278
  for (const call of calls) {
371
- const resolved = resolveCallTarget(call.calledName, call.filePath, symbolTable, importMap);
279
+ const resolved = resolveCallTarget(call, call.filePath, symbolTable, importMap, packageMap, namedImportMap);
372
280
  if (!resolved)
373
281
  continue;
374
282
  const relId = generateId('CALLS', `${call.sourceId}:${call.calledName}->${resolved.nodeId}`);
@@ -387,7 +295,7 @@ export const processCallsFromExtracted = async (graph, extractedCalls, symbolTab
387
295
  /**
388
296
  * Resolve pre-extracted Laravel routes to CALLS edges from route files to controller methods.
389
297
  */
390
- export const processRoutesFromExtracted = async (graph, extractedRoutes, symbolTable, importMap, onProgress) => {
298
+ export const processRoutesFromExtracted = async (graph, extractedRoutes, symbolTable, importMap, packageMap, onProgress) => {
391
299
  for (let i = 0; i < extractedRoutes.length; i++) {
392
300
  const route = extractedRoutes[i];
393
301
  if (i % 50 === 0) {
@@ -396,23 +304,16 @@ export const processRoutesFromExtracted = async (graph, extractedRoutes, symbolT
396
304
  }
397
305
  if (!route.controllerName || !route.methodName)
398
306
  continue;
399
- // Resolve controller class in symbol table
400
- const controllerDefs = symbolTable.lookupFuzzy(route.controllerName);
401
- if (controllerDefs.length === 0)
307
+ // Resolve controller class using shared resolver (Tier 1: same file,
308
+ // Tier 2: import-scoped, Tier 3: unique global).
309
+ const resolution = resolveSymbolInternal(route.controllerName, route.filePath, symbolTable, importMap, packageMap);
310
+ if (!resolution)
402
311
  continue;
403
- // Prefer import-resolved match
404
- const importedFiles = importMap.get(route.filePath);
405
- let controllerDef = controllerDefs[0];
406
- let confidence = controllerDefs.length === 1 ? 0.7 : 0.5;
407
- if (importedFiles) {
408
- for (const def of controllerDefs) {
409
- if (importedFiles.has(def.filePath)) {
410
- controllerDef = def;
411
- confidence = 0.9;
412
- break;
413
- }
414
- }
415
- }
312
+ const controllerDef = resolution.definition;
313
+ // Derive confidence from the resolution tier
314
+ const confidence = resolution.tier === 'same-file' ? 0.95
315
+ : resolution.tier === 'import-scoped' ? 0.9
316
+ : 0.7;
416
317
  // Find the method on the controller
417
318
  const methodId = symbolTable.lookupExact(controllerDef.filePath, route.methodName);
418
319
  const sourceId = generateId('File', route.filePath);
@@ -442,3 +343,15 @@ export const processRoutesFromExtracted = async (graph, extractedRoutes, symbolT
442
343
  }
443
344
  onProgress?.(extractedRoutes.length, extractedRoutes.length);
444
345
  };
346
+ /**
347
+ * Follow re-export chains through NamedImportMap for call candidate collection.
348
+ * Delegates chain-walking to the shared walkBindingChain utility, then
349
+ * applies call-processor semantics: any number of matches accepted.
350
+ */
351
+ const resolveNamedBindingChainForCandidates = (calledName, currentFile, symbolTable, namedImportMap, allDefs) => {
352
+ const defs = walkBindingChain(calledName, currentFile, symbolTable, namedImportMap, allDefs);
353
+ if (defs && defs.length > 0) {
354
+ return { candidates: defs, tier: 'import-scoped' };
355
+ }
356
+ return null;
357
+ };
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Default minimum buffer size for tree-sitter parsing (512 KB).
3
+ * tree-sitter requires bufferSize >= file size in bytes.
4
+ */
5
+ export declare const TREE_SITTER_BUFFER_SIZE: number;
6
+ /**
7
+ * Maximum buffer size cap (32 MB) to prevent OOM on huge files.
8
+ * Also used as the file-size skip threshold — files larger than this are not parsed.
9
+ */
10
+ export declare const TREE_SITTER_MAX_BUFFER: number;
11
+ /**
12
+ * Compute adaptive buffer size for tree-sitter parsing.
13
+ * Uses 2× file size, clamped between 512 KB and 32 MB.
14
+ * Previous 256 KB fixed limit silently skipped files > ~200 KB (e.g., imgui.h at 411 KB).
15
+ */
16
+ export declare const getTreeSitterBufferSize: (contentLength: number) => number;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Default minimum buffer size for tree-sitter parsing (512 KB).
3
+ * tree-sitter requires bufferSize >= file size in bytes.
4
+ */
5
+ export const TREE_SITTER_BUFFER_SIZE = 512 * 1024;
6
+ /**
7
+ * Maximum buffer size cap (32 MB) to prevent OOM on huge files.
8
+ * Also used as the file-size skip threshold — files larger than this are not parsed.
9
+ */
10
+ export const TREE_SITTER_MAX_BUFFER = 32 * 1024 * 1024;
11
+ /**
12
+ * Compute adaptive buffer size for tree-sitter parsing.
13
+ * Uses 2× file size, clamped between 512 KB and 32 MB.
14
+ * Previous 256 KB fixed limit silently skipped files > ~200 KB (e.g., imgui.h at 411 KB).
15
+ */
16
+ export const getTreeSitterBufferSize = (contentLength) => Math.min(Math.max(contentLength * 2, TREE_SITTER_BUFFER_SIZE), TREE_SITTER_MAX_BUFFER);
@@ -9,6 +9,7 @@
9
9
  *
10
10
  * This module is language-agnostic - language-specific patterns are defined per language.
11
11
  */
12
+ import { SupportedLanguages } from '../../config/supported-languages.js';
12
13
  export interface EntryPointScoreResult {
13
14
  score: number;
14
15
  reasons: string[];
@@ -26,7 +27,7 @@ export interface EntryPointScoreResult {
26
27
  * @param calleeCount - Number of functions this function calls
27
28
  * @returns Score and array of reasons explaining the score
28
29
  */
29
- export declare function calculateEntryPointScore(name: string, language: string, isExported: boolean, callerCount: number, calleeCount: number, filePath?: string): EntryPointScoreResult;
30
+ export declare function calculateEntryPointScore(name: string, language: SupportedLanguages, isExported: boolean, callerCount: number, calleeCount: number, filePath?: string): EntryPointScoreResult;
30
31
  /**
31
32
  * Check if a file path is a test file (should be excluded from entry points)
32
33
  * Covers common test file patterns across all supported languages