gitnexus 1.4.0 → 1.4.1

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 (131) hide show
  1. package/README.md +194 -214
  2. package/dist/cli/ai-context.d.ts +1 -2
  3. package/dist/cli/ai-context.js +90 -117
  4. package/dist/cli/analyze.d.ts +0 -2
  5. package/dist/cli/analyze.js +2 -20
  6. package/dist/cli/index.js +25 -17
  7. package/dist/cli/setup.js +19 -17
  8. package/dist/core/augmentation/engine.js +20 -20
  9. package/dist/core/embeddings/embedding-pipeline.js +26 -26
  10. package/dist/core/graph/types.d.ts +2 -5
  11. package/dist/core/ingestion/ast-cache.js +2 -3
  12. package/dist/core/ingestion/call-processor.d.ts +5 -5
  13. package/dist/core/ingestion/call-processor.js +258 -173
  14. package/dist/core/ingestion/cluster-enricher.js +16 -16
  15. package/dist/core/ingestion/entry-point-scoring.d.ts +1 -2
  16. package/dist/core/ingestion/entry-point-scoring.js +22 -81
  17. package/dist/core/ingestion/framework-detection.d.ts +1 -5
  18. package/dist/core/ingestion/framework-detection.js +8 -39
  19. package/dist/core/ingestion/heritage-processor.d.ts +4 -13
  20. package/dist/core/ingestion/heritage-processor.js +28 -92
  21. package/dist/core/ingestion/import-processor.d.ts +19 -17
  22. package/dist/core/ingestion/import-processor.js +695 -170
  23. package/dist/core/ingestion/parsing-processor.d.ts +10 -1
  24. package/dist/core/ingestion/parsing-processor.js +177 -41
  25. package/dist/core/ingestion/pipeline.js +26 -49
  26. package/dist/core/ingestion/process-processor.js +1 -2
  27. package/dist/core/ingestion/symbol-table.d.ts +1 -12
  28. package/dist/core/ingestion/symbol-table.js +12 -19
  29. package/dist/core/ingestion/tree-sitter-queries.d.ts +11 -11
  30. package/dist/core/ingestion/tree-sitter-queries.js +485 -590
  31. package/dist/core/ingestion/utils.d.ts +0 -67
  32. package/dist/core/ingestion/utils.js +9 -692
  33. package/dist/core/ingestion/workers/parse-worker.d.ts +3 -20
  34. package/dist/core/ingestion/workers/parse-worker.js +345 -84
  35. package/dist/core/ingestion/workers/worker-pool.js +0 -8
  36. package/dist/core/kuzu/csv-generator.js +3 -19
  37. package/dist/core/kuzu/kuzu-adapter.js +19 -14
  38. package/dist/core/kuzu/schema.d.ts +3 -3
  39. package/dist/core/kuzu/schema.js +288 -303
  40. package/dist/core/search/bm25-index.js +6 -7
  41. package/dist/core/search/hybrid-search.js +3 -3
  42. package/dist/core/wiki/diagrams.d.ts +27 -0
  43. package/dist/core/wiki/diagrams.js +163 -0
  44. package/dist/core/wiki/generator.d.ts +50 -2
  45. package/dist/core/wiki/generator.js +548 -49
  46. package/dist/core/wiki/graph-queries.d.ts +42 -0
  47. package/dist/core/wiki/graph-queries.js +276 -97
  48. package/dist/core/wiki/html-viewer.js +192 -192
  49. package/dist/core/wiki/llm-client.js +73 -11
  50. package/dist/core/wiki/prompts.d.ts +52 -8
  51. package/dist/core/wiki/prompts.js +200 -86
  52. package/dist/mcp/core/kuzu-adapter.d.ts +3 -1
  53. package/dist/mcp/core/kuzu-adapter.js +44 -13
  54. package/dist/mcp/local/local-backend.js +128 -128
  55. package/dist/mcp/resources.js +42 -42
  56. package/dist/mcp/server.js +19 -18
  57. package/dist/mcp/tools.js +104 -103
  58. package/hooks/claude/gitnexus-hook.cjs +155 -238
  59. package/hooks/claude/pre-tool-use.sh +79 -79
  60. package/hooks/claude/session-start.sh +42 -42
  61. package/package.json +96 -96
  62. package/scripts/patch-tree-sitter-swift.cjs +74 -74
  63. package/skills/gitnexus-cli.md +82 -82
  64. package/skills/gitnexus-debugging.md +89 -89
  65. package/skills/gitnexus-exploring.md +78 -78
  66. package/skills/gitnexus-guide.md +64 -64
  67. package/skills/gitnexus-impact-analysis.md +97 -97
  68. package/skills/gitnexus-pr-review.md +163 -163
  69. package/skills/gitnexus-refactoring.md +121 -121
  70. package/vendor/leiden/index.cjs +355 -355
  71. package/vendor/leiden/utils.cjs +392 -392
  72. package/dist/cli/lazy-action.d.ts +0 -6
  73. package/dist/cli/lazy-action.js +0 -18
  74. package/dist/cli/skill-gen.d.ts +0 -26
  75. package/dist/cli/skill-gen.js +0 -549
  76. package/dist/core/ingestion/constants.d.ts +0 -16
  77. package/dist/core/ingestion/constants.js +0 -16
  78. package/dist/core/ingestion/export-detection.d.ts +0 -18
  79. package/dist/core/ingestion/export-detection.js +0 -230
  80. package/dist/core/ingestion/language-config.d.ts +0 -46
  81. package/dist/core/ingestion/language-config.js +0 -167
  82. package/dist/core/ingestion/mro-processor.d.ts +0 -45
  83. package/dist/core/ingestion/mro-processor.js +0 -369
  84. package/dist/core/ingestion/named-binding-extraction.d.ts +0 -61
  85. package/dist/core/ingestion/named-binding-extraction.js +0 -363
  86. package/dist/core/ingestion/resolvers/csharp.d.ts +0 -22
  87. package/dist/core/ingestion/resolvers/csharp.js +0 -109
  88. package/dist/core/ingestion/resolvers/go.d.ts +0 -19
  89. package/dist/core/ingestion/resolvers/go.js +0 -42
  90. package/dist/core/ingestion/resolvers/index.d.ts +0 -16
  91. package/dist/core/ingestion/resolvers/index.js +0 -11
  92. package/dist/core/ingestion/resolvers/jvm.d.ts +0 -23
  93. package/dist/core/ingestion/resolvers/jvm.js +0 -87
  94. package/dist/core/ingestion/resolvers/php.d.ts +0 -15
  95. package/dist/core/ingestion/resolvers/php.js +0 -35
  96. package/dist/core/ingestion/resolvers/rust.d.ts +0 -15
  97. package/dist/core/ingestion/resolvers/rust.js +0 -73
  98. package/dist/core/ingestion/resolvers/standard.d.ts +0 -28
  99. package/dist/core/ingestion/resolvers/standard.js +0 -145
  100. package/dist/core/ingestion/resolvers/utils.d.ts +0 -33
  101. package/dist/core/ingestion/resolvers/utils.js +0 -120
  102. package/dist/core/ingestion/symbol-resolver.d.ts +0 -32
  103. package/dist/core/ingestion/symbol-resolver.js +0 -83
  104. package/dist/core/ingestion/type-env.d.ts +0 -27
  105. package/dist/core/ingestion/type-env.js +0 -86
  106. package/dist/core/ingestion/type-extractors/c-cpp.d.ts +0 -2
  107. package/dist/core/ingestion/type-extractors/c-cpp.js +0 -60
  108. package/dist/core/ingestion/type-extractors/csharp.d.ts +0 -2
  109. package/dist/core/ingestion/type-extractors/csharp.js +0 -89
  110. package/dist/core/ingestion/type-extractors/go.d.ts +0 -2
  111. package/dist/core/ingestion/type-extractors/go.js +0 -105
  112. package/dist/core/ingestion/type-extractors/index.d.ts +0 -21
  113. package/dist/core/ingestion/type-extractors/index.js +0 -29
  114. package/dist/core/ingestion/type-extractors/jvm.d.ts +0 -3
  115. package/dist/core/ingestion/type-extractors/jvm.js +0 -121
  116. package/dist/core/ingestion/type-extractors/php.d.ts +0 -2
  117. package/dist/core/ingestion/type-extractors/php.js +0 -31
  118. package/dist/core/ingestion/type-extractors/python.d.ts +0 -2
  119. package/dist/core/ingestion/type-extractors/python.js +0 -41
  120. package/dist/core/ingestion/type-extractors/rust.d.ts +0 -2
  121. package/dist/core/ingestion/type-extractors/rust.js +0 -39
  122. package/dist/core/ingestion/type-extractors/shared.d.ts +0 -17
  123. package/dist/core/ingestion/type-extractors/shared.js +0 -97
  124. package/dist/core/ingestion/type-extractors/swift.d.ts +0 -2
  125. package/dist/core/ingestion/type-extractors/swift.js +0 -43
  126. package/dist/core/ingestion/type-extractors/types.d.ts +0 -14
  127. package/dist/core/ingestion/type-extractors/types.js +0 -1
  128. package/dist/core/ingestion/type-extractors/typescript.d.ts +0 -2
  129. package/dist/core/ingestion/type-extractors/typescript.js +0 -46
  130. package/dist/mcp/compatible-stdio-transport.d.ts +0 -25
  131. package/dist/mcp/compatible-stdio-transport.js +0 -200
@@ -1,13 +1,44 @@
1
- import { isFileInPackageDir } from './import-processor.js';
2
- import { resolveSymbolInternal } from './symbol-resolver.js';
3
- import { walkBindingChain } from './named-binding-extraction.js';
4
1
  import Parser from 'tree-sitter';
5
- import { isLanguageAvailable, loadParser, loadLanguage } from '../tree-sitter/parser-loader.js';
2
+ import { loadParser, loadLanguage } from '../tree-sitter/parser-loader.js';
6
3
  import { LANGUAGE_QUERIES } from './tree-sitter-queries.js';
7
4
  import { generateId } from '../../lib/utils.js';
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';
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
+ ]);
11
42
  /**
12
43
  * Walk up the AST from a node to find the enclosing function/method.
13
44
  * Returns null if the call is at module/file level (top-level code).
@@ -16,22 +47,86 @@ const findEnclosingFunction = (node, filePath, symbolTable) => {
16
47
  let current = node.parent;
17
48
  while (current) {
18
49
  if (FUNCTION_NODE_TYPES.has(current.type)) {
19
- const { funcName, label } = extractFunctionName(current);
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
+ const startLine = current.startPosition?.row ?? 0;
58
+ return generateId('Constructor', `${filePath}:${funcName}:${startLine}`);
59
+ }
60
+ if (current.type === 'function_declaration' ||
61
+ current.type === 'function_definition' ||
62
+ current.type === 'async_function_declaration' ||
63
+ current.type === 'generator_function_declaration' ||
64
+ current.type === 'function_item') { // Rust function
65
+ // Named function: function foo() {}
66
+ const nameNode = current.childForFieldName?.('name') ||
67
+ current.children?.find((c) => c.type === 'identifier' || c.type === 'property_identifier');
68
+ funcName = nameNode?.text;
69
+ }
70
+ else if (current.type === 'impl_item') {
71
+ // Rust method inside impl block: wrapper around function_item or const_item
72
+ // We need to look inside for the function_item
73
+ const funcItem = current.children?.find((c) => c.type === 'function_item');
74
+ if (funcItem) {
75
+ const nameNode = funcItem.childForFieldName?.('name') ||
76
+ funcItem.children?.find((c) => c.type === 'identifier');
77
+ funcName = nameNode?.text;
78
+ label = 'Method';
79
+ }
80
+ }
81
+ else if (current.type === 'method_definition') {
82
+ // Method: foo() {} inside class (JS/TS)
83
+ const nameNode = current.childForFieldName?.('name') ||
84
+ current.children?.find((c) => c.type === 'property_identifier');
85
+ funcName = nameNode?.text;
86
+ label = 'Method';
87
+ }
88
+ else if (current.type === 'method_declaration') {
89
+ // Java method: public void foo() {}
90
+ const nameNode = current.childForFieldName?.('name') ||
91
+ current.children?.find((c) => c.type === 'identifier');
92
+ funcName = nameNode?.text;
93
+ label = 'Method';
94
+ }
95
+ else if (current.type === 'constructor_declaration') {
96
+ // Java constructor: public ClassName() {}
97
+ const nameNode = current.childForFieldName?.('name') ||
98
+ current.children?.find((c) => c.type === 'identifier');
99
+ funcName = nameNode?.text;
100
+ label = 'Method'; // Treat constructors as methods for process detection
101
+ }
102
+ else if (current.type === 'arrow_function' || current.type === 'function_expression') {
103
+ // Arrow/expression: const foo = () => {} - check parent variable declarator
104
+ const parent = current.parent;
105
+ if (parent?.type === 'variable_declarator') {
106
+ const nameNode = parent.childForFieldName?.('name') ||
107
+ parent.children?.find((c) => c.type === 'identifier');
108
+ funcName = nameNode?.text;
109
+ }
110
+ }
20
111
  if (funcName) {
112
+ // Look up the function in symbol table to get its node ID
113
+ // Try exact match first
21
114
  const nodeId = symbolTable.lookupExact(filePath, funcName);
22
115
  if (nodeId)
23
116
  return nodeId;
24
- return generateId(label, `${filePath}:${funcName}`);
117
+ // Try construct ID manually if lookup fails (common for non-exported internal functions)
118
+ // Format must match parsing-processor: "Label:path/to/file:funcName:startLine"
119
+ const startLine = current.startPosition?.row ?? 0;
120
+ return generateId(label, `${filePath}:${funcName}:${startLine}`);
25
121
  }
122
+ // Couldn't determine function name - try parent (might be nested)
26
123
  }
27
124
  current = current.parent;
28
125
  }
29
- return null;
126
+ return null; // Top-level call (not inside any function)
30
127
  };
31
- export const processCalls = async (graph, files, astCache, symbolTable, importMap, packageMap, onProgress, namedImportMap) => {
128
+ export const processCalls = async (graph, files, astCache, symbolTable, importMap, onProgress) => {
32
129
  const parser = await loadParser();
33
- const logSkipped = isVerboseIngestionEnabled();
34
- const skippedByLang = logSkipped ? new Map() : null;
35
130
  for (let i = 0; i < files.length; i++) {
36
131
  const file = files[i];
37
132
  onProgress?.(i + 1, files.length);
@@ -41,12 +136,6 @@ export const processCalls = async (graph, files, astCache, symbolTable, importMa
41
136
  const language = getLanguageFromFilename(file.path);
42
137
  if (!language)
43
138
  continue;
44
- if (!isLanguageAvailable(language)) {
45
- if (skippedByLang) {
46
- skippedByLang.set(language, (skippedByLang.get(language) ?? 0) + 1);
47
- }
48
- continue;
49
- }
50
139
  const queryStr = LANGUAGE_QUERIES[language];
51
140
  if (!queryStr)
52
141
  continue;
@@ -59,7 +148,7 @@ export const processCalls = async (graph, files, astCache, symbolTable, importMa
59
148
  // Cache Miss: Re-parse
60
149
  // Use larger bufferSize for files > 32KB
61
150
  try {
62
- tree = parser.parse(file.content, undefined, { bufferSize: getTreeSitterBufferSize(file.content.length) });
151
+ tree = parser.parse(file.content, undefined, { bufferSize: 1024 * 256 });
63
152
  }
64
153
  catch (parseError) {
65
154
  // Skip files that can't be parsed
@@ -80,9 +169,6 @@ export const processCalls = async (graph, files, astCache, symbolTable, importMa
80
169
  console.warn(`Query error for ${file.path}:`, queryError);
81
170
  continue;
82
171
  }
83
- // Build per-file TypeEnv for receiver resolution
84
- const lang = getLanguageFromFilename(file.path);
85
- const typeEnv = lang ? buildTypeEnv(tree, lang) : new Map();
86
172
  // 3. Process each call match
87
173
  matches.forEach(match => {
88
174
  const captureMap = {};
@@ -97,20 +183,12 @@ export const processCalls = async (graph, files, astCache, symbolTable, importMa
97
183
  // Skip common built-ins and noise
98
184
  if (isBuiltInOrNoise(calledName))
99
185
  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;
104
186
  // 4. Resolve the target using priority strategy (returns confidence)
105
- const resolved = resolveCallTarget({
106
- calledName,
107
- argCount: countCallArguments(callNode),
108
- callForm,
109
- receiverTypeName,
110
- }, file.path, symbolTable, importMap, packageMap, namedImportMap);
187
+ const resolved = resolveCallTarget(calledName, file.path, symbolTable, importMap);
111
188
  if (!resolved)
112
189
  return;
113
190
  // 5. Find the enclosing function (caller)
191
+ const callNode = captureMap['call'];
114
192
  const enclosingFuncId = findEnclosingFunction(callNode, file.path, symbolTable);
115
193
  // Use enclosing function as source, fallback to file for top-level calls
116
194
  const sourceId = enclosingFuncId || generateId('File', file.path);
@@ -126,137 +204,149 @@ export const processCalls = async (graph, files, astCache, symbolTable, importMa
126
204
  });
127
205
  // Tree is now owned by the LRU cache — no manual delete needed
128
206
  }
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' };
219
207
  };
220
208
  /**
221
209
  * Resolve a function call to its target node ID using priority strategy:
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
210
+ * A. Check imported files first (highest confidence)
211
+ * B. Check local file definitions
212
+ * C. Fuzzy global search (lowest confidence)
226
213
  *
227
- * If filtering still leaves multiple candidates, refuse to emit a CALLS edge.
214
+ * Returns confidence score so agents know what to trust.
228
215
  */
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);
216
+ const resolveCallTarget = (calledName, currentFile, symbolTable, importMap) => {
217
+ // Strategy B first (cheapest single map lookup): Check local file
218
+ const localNodeId = symbolTable.lookupExact(currentFile, calledName);
219
+ if (localNodeId) {
220
+ return { nodeId: localNodeId, confidence: 0.85, reason: 'same-file' };
221
+ }
222
+ // Strategy A: Check if any definition of calledName is in an imported file
223
+ // Reversed: instead of iterating all imports and checking each, get all definitions
224
+ // and check if any is imported. O(definitions) instead of O(imports).
225
+ const allDefs = symbolTable.lookupFuzzy(calledName);
226
+ if (allDefs.length > 0) {
227
+ const importedFiles = importMap.get(currentFile);
228
+ if (importedFiles) {
229
+ for (const def of allDefs) {
230
+ if (importedFiles.has(def.filePath)) {
231
+ return { nodeId: def.nodeId, confidence: 0.9, reason: 'import-resolved' };
232
+ }
243
233
  }
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;
248
234
  }
235
+ // Strategy C: Fuzzy global (no import match found)
236
+ const confidence = allDefs.length === 1 ? 0.5 : 0.3;
237
+ return { nodeId: allDefs[0].nodeId, confidence, reason: 'fuzzy-global' };
249
238
  }
250
- if (filteredCandidates.length !== 1)
251
- return null;
252
- return toResolveResult(filteredCandidates[0], tiered.tier);
239
+ return null;
253
240
  };
241
+ /**
242
+ * Filter out common built-in functions and noise
243
+ * that shouldn't be tracked as calls
244
+ */
245
+ /** Pre-built set (module-level singleton) to avoid re-creating per call */
246
+ const BUILT_IN_NAMES = new Set([
247
+ // JavaScript/TypeScript built-ins
248
+ 'console', 'log', 'warn', 'error', 'info', 'debug',
249
+ 'setTimeout', 'setInterval', 'clearTimeout', 'clearInterval',
250
+ 'parseInt', 'parseFloat', 'isNaN', 'isFinite',
251
+ 'encodeURI', 'decodeURI', 'encodeURIComponent', 'decodeURIComponent',
252
+ 'JSON', 'parse', 'stringify',
253
+ 'Object', 'Array', 'String', 'Number', 'Boolean', 'Symbol', 'BigInt',
254
+ 'Map', 'Set', 'WeakMap', 'WeakSet',
255
+ 'Promise', 'resolve', 'reject', 'then', 'catch', 'finally',
256
+ 'Math', 'Date', 'RegExp', 'Error',
257
+ 'require', 'import', 'export',
258
+ 'fetch', 'Response', 'Request',
259
+ // React hooks and common functions
260
+ 'useState', 'useEffect', 'useCallback', 'useMemo', 'useRef', 'useContext',
261
+ 'useReducer', 'useLayoutEffect', 'useImperativeHandle', 'useDebugValue',
262
+ 'createElement', 'createContext', 'createRef', 'forwardRef', 'memo', 'lazy',
263
+ // Common array/object methods
264
+ 'map', 'filter', 'reduce', 'forEach', 'find', 'findIndex', 'some', 'every',
265
+ 'includes', 'indexOf', 'slice', 'splice', 'concat', 'join', 'split',
266
+ 'push', 'pop', 'shift', 'unshift', 'sort', 'reverse',
267
+ 'keys', 'values', 'entries', 'assign', 'freeze', 'seal',
268
+ 'hasOwnProperty', 'toString', 'valueOf',
269
+ // Python built-ins
270
+ 'print', 'len', 'range', 'str', 'int', 'float', 'list', 'dict', 'set', 'tuple',
271
+ 'open', 'read', 'write', 'close', 'append', 'extend', 'update',
272
+ 'super', 'type', 'isinstance', 'issubclass', 'getattr', 'setattr', 'hasattr',
273
+ 'enumerate', 'zip', 'sorted', 'reversed', 'min', 'max', 'sum', 'abs',
274
+ // Kotlin stdlib (IMPORTANT: keep in sync with parse-worker.ts BUILT_IN_NAMES)
275
+ 'println', 'print', 'readLine', 'require', 'requireNotNull', 'check', 'assert', 'lazy', 'error',
276
+ 'listOf', 'mapOf', 'setOf', 'mutableListOf', 'mutableMapOf', 'mutableSetOf',
277
+ 'arrayOf', 'sequenceOf', 'also', 'apply', 'run', 'with', 'takeIf', 'takeUnless',
278
+ 'TODO', 'buildString', 'buildList', 'buildMap', 'buildSet',
279
+ 'repeat', 'synchronized',
280
+ // Kotlin coroutine builders & scope functions
281
+ 'launch', 'async', 'runBlocking', 'withContext', 'coroutineScope',
282
+ 'supervisorScope', 'delay',
283
+ // Kotlin Flow operators
284
+ 'flow', 'flowOf', 'collect', 'emit', 'onEach', 'catch',
285
+ 'buffer', 'conflate', 'distinctUntilChanged',
286
+ 'flatMapLatest', 'flatMapMerge', 'combine',
287
+ 'stateIn', 'shareIn', 'launchIn',
288
+ // Kotlin infix stdlib functions
289
+ 'to', 'until', 'downTo', 'step',
290
+ // C/C++ standard library and common kernel helpers
291
+ 'printf', 'fprintf', 'sprintf', 'snprintf', 'vprintf', 'vfprintf', 'vsprintf', 'vsnprintf',
292
+ 'scanf', 'fscanf', 'sscanf',
293
+ 'malloc', 'calloc', 'realloc', 'free', 'memcpy', 'memmove', 'memset', 'memcmp',
294
+ 'strlen', 'strcpy', 'strncpy', 'strcat', 'strncat', 'strcmp', 'strncmp', 'strstr', 'strchr', 'strrchr',
295
+ 'atoi', 'atol', 'atof', 'strtol', 'strtoul', 'strtoll', 'strtoull', 'strtod',
296
+ 'sizeof', 'offsetof', 'typeof',
297
+ 'assert', 'abort', 'exit', '_exit',
298
+ 'fopen', 'fclose', 'fread', 'fwrite', 'fseek', 'ftell', 'rewind', 'fflush', 'fgets', 'fputs',
299
+ // Linux kernel common macros/helpers (not real call targets)
300
+ 'likely', 'unlikely', 'BUG', 'BUG_ON', 'WARN', 'WARN_ON', 'WARN_ONCE',
301
+ 'IS_ERR', 'PTR_ERR', 'ERR_PTR', 'IS_ERR_OR_NULL',
302
+ 'ARRAY_SIZE', 'container_of', 'list_for_each_entry', 'list_for_each_entry_safe',
303
+ 'min', 'max', 'clamp', 'abs', 'swap',
304
+ 'pr_info', 'pr_warn', 'pr_err', 'pr_debug', 'pr_notice', 'pr_crit', 'pr_emerg',
305
+ 'printk', 'dev_info', 'dev_warn', 'dev_err', 'dev_dbg',
306
+ 'GFP_KERNEL', 'GFP_ATOMIC',
307
+ 'spin_lock', 'spin_unlock', 'spin_lock_irqsave', 'spin_unlock_irqrestore',
308
+ 'mutex_lock', 'mutex_unlock', 'mutex_init',
309
+ 'kfree', 'kmalloc', 'kzalloc', 'kcalloc', 'krealloc', 'kvmalloc', 'kvfree',
310
+ 'get', 'put',
311
+ // Swift/iOS built-ins and standard library
312
+ 'print', 'debugPrint', 'dump', 'fatalError', 'precondition', 'preconditionFailure',
313
+ 'assert', 'assertionFailure', 'NSLog',
314
+ 'abs', 'min', 'max', 'zip', 'stride', 'sequence', 'repeatElement',
315
+ 'swap', 'withUnsafePointer', 'withUnsafeMutablePointer', 'withUnsafeBytes',
316
+ 'autoreleasepool', 'unsafeBitCast', 'unsafeDowncast', 'numericCast',
317
+ 'type', 'MemoryLayout',
318
+ // Swift collection/string methods (common noise)
319
+ 'map', 'flatMap', 'compactMap', 'filter', 'reduce', 'forEach', 'contains',
320
+ 'first', 'last', 'prefix', 'suffix', 'dropFirst', 'dropLast',
321
+ 'sorted', 'reversed', 'enumerated', 'joined', 'split',
322
+ 'append', 'insert', 'remove', 'removeAll', 'removeFirst', 'removeLast',
323
+ 'isEmpty', 'count', 'index', 'startIndex', 'endIndex',
324
+ // UIKit/Foundation common methods (noise in call graph)
325
+ 'addSubview', 'removeFromSuperview', 'layoutSubviews', 'setNeedsLayout',
326
+ 'layoutIfNeeded', 'setNeedsDisplay', 'invalidateIntrinsicContentSize',
327
+ 'addTarget', 'removeTarget', 'addGestureRecognizer',
328
+ 'addConstraint', 'addConstraints', 'removeConstraint', 'removeConstraints',
329
+ 'NSLocalizedString', 'Bundle',
330
+ 'reloadData', 'reloadSections', 'reloadRows', 'performBatchUpdates',
331
+ 'register', 'dequeueReusableCell', 'dequeueReusableSupplementaryView',
332
+ 'beginUpdates', 'endUpdates', 'insertRows', 'deleteRows', 'insertSections', 'deleteSections',
333
+ 'present', 'dismiss', 'pushViewController', 'popViewController', 'popToRootViewController',
334
+ 'performSegue', 'prepare',
335
+ // GCD / async
336
+ 'DispatchQueue', 'async', 'sync', 'asyncAfter',
337
+ 'Task', 'withCheckedContinuation', 'withCheckedThrowingContinuation',
338
+ // Combine
339
+ 'sink', 'store', 'assign', 'receive', 'subscribe',
340
+ // Notification / KVO
341
+ 'addObserver', 'removeObserver', 'post', 'NotificationCenter',
342
+ ]);
343
+ const isBuiltInOrNoise = (name) => BUILT_IN_NAMES.has(name);
254
344
  /**
255
345
  * Fast path: resolve pre-extracted call sites from workers.
256
346
  * No AST parsing — workers already extracted calledName + sourceId.
257
347
  * This function only does symbol table lookups + graph mutations.
258
348
  */
259
- export const processCallsFromExtracted = async (graph, extractedCalls, symbolTable, importMap, packageMap, onProgress, namedImportMap) => {
349
+ export const processCallsFromExtracted = async (graph, extractedCalls, symbolTable, importMap, onProgress) => {
260
350
  // Group by file for progress reporting
261
351
  const byFile = new Map();
262
352
  for (const call of extractedCalls) {
@@ -276,7 +366,7 @@ export const processCallsFromExtracted = async (graph, extractedCalls, symbolTab
276
366
  await yieldToEventLoop();
277
367
  }
278
368
  for (const call of calls) {
279
- const resolved = resolveCallTarget(call, call.filePath, symbolTable, importMap, packageMap, namedImportMap);
369
+ const resolved = resolveCallTarget(call.calledName, call.filePath, symbolTable, importMap);
280
370
  if (!resolved)
281
371
  continue;
282
372
  const relId = generateId('CALLS', `${call.sourceId}:${call.calledName}->${resolved.nodeId}`);
@@ -295,7 +385,7 @@ export const processCallsFromExtracted = async (graph, extractedCalls, symbolTab
295
385
  /**
296
386
  * Resolve pre-extracted Laravel routes to CALLS edges from route files to controller methods.
297
387
  */
298
- export const processRoutesFromExtracted = async (graph, extractedRoutes, symbolTable, importMap, packageMap, onProgress) => {
388
+ export const processRoutesFromExtracted = async (graph, extractedRoutes, symbolTable, importMap, onProgress) => {
299
389
  for (let i = 0; i < extractedRoutes.length; i++) {
300
390
  const route = extractedRoutes[i];
301
391
  if (i % 50 === 0) {
@@ -304,16 +394,23 @@ export const processRoutesFromExtracted = async (graph, extractedRoutes, symbolT
304
394
  }
305
395
  if (!route.controllerName || !route.methodName)
306
396
  continue;
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)
397
+ // Resolve controller class in symbol table
398
+ const controllerDefs = symbolTable.lookupFuzzy(route.controllerName);
399
+ if (controllerDefs.length === 0)
311
400
  continue;
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;
401
+ // Prefer import-resolved match
402
+ const importedFiles = importMap.get(route.filePath);
403
+ let controllerDef = controllerDefs[0];
404
+ let confidence = controllerDefs.length === 1 ? 0.7 : 0.5;
405
+ if (importedFiles) {
406
+ for (const def of controllerDefs) {
407
+ if (importedFiles.has(def.filePath)) {
408
+ controllerDef = def;
409
+ confidence = 0.9;
410
+ break;
411
+ }
412
+ }
413
+ }
317
414
  // Find the method on the controller
318
415
  const methodId = symbolTable.lookupExact(controllerDef.filePath, route.methodName);
319
416
  const sourceId = generateId('File', route.filePath);
@@ -343,15 +440,3 @@ export const processRoutesFromExtracted = async (graph, extractedRoutes, symbolT
343
440
  }
344
441
  onProgress?.(extractedRoutes.length, extractedRoutes.length);
345
442
  };
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
- };
@@ -13,12 +13,12 @@ const buildEnrichmentPrompt = (members, heuristicLabel) => {
13
13
  const memberList = limitedMembers
14
14
  .map(m => `${m.name} (${m.type})`)
15
15
  .join(', ');
16
- return `Analyze this code cluster and provide a semantic name and short description.
17
-
18
- Heuristic: "${heuristicLabel}"
19
- Members: ${memberList}${members.length > 20 ? ` (+${members.length - 20} more)` : ''}
20
-
21
- Reply with JSON only:
16
+ return `Analyze this code cluster and provide a semantic name and short description.
17
+
18
+ Heuristic: "${heuristicLabel}"
19
+ Members: ${memberList}${members.length > 20 ? ` (+${members.length - 20} more)` : ''}
20
+
21
+ Reply with JSON only:
22
22
  {"name": "2-4 word semantic name", "description": "One sentence describing purpose"}`;
23
23
  };
24
24
  // ============================================================================
@@ -115,18 +115,18 @@ export const enrichClustersBatch = async (communities, memberMap, llmClient, bat
115
115
  const memberList = limitedMembers
116
116
  .map(m => `${m.name} (${m.type})`)
117
117
  .join(', ');
118
- return `Cluster ${idx + 1} (id: ${community.id}):
119
- Heuristic: "${community.heuristicLabel}"
118
+ return `Cluster ${idx + 1} (id: ${community.id}):
119
+ Heuristic: "${community.heuristicLabel}"
120
120
  Members: ${memberList}`;
121
121
  }).join('\n\n');
122
- const prompt = `Analyze these code clusters and generate semantic names, keywords, and descriptions.
123
-
124
- ${batchPrompt}
125
-
126
- Output JSON array:
127
- [
128
- {"id": "comm_X", "name": "...", "keywords": [...], "description": "..."},
129
- ...
122
+ const prompt = `Analyze these code clusters and generate semantic names, keywords, and descriptions.
123
+
124
+ ${batchPrompt}
125
+
126
+ Output JSON array:
127
+ [
128
+ {"id": "comm_X", "name": "...", "keywords": [...], "description": "..."},
129
+ ...
130
130
  ]`;
131
131
  try {
132
132
  const response = await llmClient.generate(prompt);
@@ -9,7 +9,6 @@
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';
13
12
  export interface EntryPointScoreResult {
14
13
  score: number;
15
14
  reasons: string[];
@@ -27,7 +26,7 @@ export interface EntryPointScoreResult {
27
26
  * @param calleeCount - Number of functions this function calls
28
27
  * @returns Score and array of reasons explaining the score
29
28
  */
30
- export declare function calculateEntryPointScore(name: string, language: SupportedLanguages, isExported: boolean, callerCount: number, calleeCount: number, filePath?: string): EntryPointScoreResult;
29
+ export declare function calculateEntryPointScore(name: string, language: string, isExported: boolean, callerCount: number, calleeCount: number, filePath?: string): EntryPointScoreResult;
31
30
  /**
32
31
  * Check if a file path is a test file (should be excluded from entry points)
33
32
  * Covers common test file patterns across all supported languages