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.
- package/README.md +194 -214
- package/dist/cli/ai-context.d.ts +1 -2
- package/dist/cli/ai-context.js +90 -117
- package/dist/cli/analyze.d.ts +0 -2
- package/dist/cli/analyze.js +2 -20
- package/dist/cli/index.js +25 -17
- package/dist/cli/setup.js +19 -17
- package/dist/core/augmentation/engine.js +20 -20
- package/dist/core/embeddings/embedding-pipeline.js +26 -26
- package/dist/core/graph/types.d.ts +2 -5
- package/dist/core/ingestion/ast-cache.js +2 -3
- package/dist/core/ingestion/call-processor.d.ts +5 -5
- package/dist/core/ingestion/call-processor.js +258 -173
- package/dist/core/ingestion/cluster-enricher.js +16 -16
- package/dist/core/ingestion/entry-point-scoring.d.ts +1 -2
- package/dist/core/ingestion/entry-point-scoring.js +22 -81
- package/dist/core/ingestion/framework-detection.d.ts +1 -5
- package/dist/core/ingestion/framework-detection.js +8 -39
- package/dist/core/ingestion/heritage-processor.d.ts +4 -13
- package/dist/core/ingestion/heritage-processor.js +28 -92
- package/dist/core/ingestion/import-processor.d.ts +19 -17
- package/dist/core/ingestion/import-processor.js +695 -170
- package/dist/core/ingestion/parsing-processor.d.ts +10 -1
- package/dist/core/ingestion/parsing-processor.js +177 -41
- package/dist/core/ingestion/pipeline.js +26 -49
- package/dist/core/ingestion/process-processor.js +1 -2
- package/dist/core/ingestion/symbol-table.d.ts +1 -12
- package/dist/core/ingestion/symbol-table.js +12 -19
- package/dist/core/ingestion/tree-sitter-queries.d.ts +11 -11
- package/dist/core/ingestion/tree-sitter-queries.js +485 -590
- package/dist/core/ingestion/utils.d.ts +0 -67
- package/dist/core/ingestion/utils.js +9 -692
- package/dist/core/ingestion/workers/parse-worker.d.ts +3 -20
- package/dist/core/ingestion/workers/parse-worker.js +345 -84
- package/dist/core/ingestion/workers/worker-pool.js +0 -8
- package/dist/core/kuzu/csv-generator.js +3 -19
- package/dist/core/kuzu/kuzu-adapter.js +19 -14
- package/dist/core/kuzu/schema.d.ts +3 -3
- package/dist/core/kuzu/schema.js +288 -303
- package/dist/core/search/bm25-index.js +6 -7
- package/dist/core/search/hybrid-search.js +3 -3
- package/dist/core/wiki/diagrams.d.ts +27 -0
- package/dist/core/wiki/diagrams.js +163 -0
- package/dist/core/wiki/generator.d.ts +50 -2
- package/dist/core/wiki/generator.js +548 -49
- package/dist/core/wiki/graph-queries.d.ts +42 -0
- package/dist/core/wiki/graph-queries.js +276 -97
- package/dist/core/wiki/html-viewer.js +192 -192
- package/dist/core/wiki/llm-client.js +73 -11
- package/dist/core/wiki/prompts.d.ts +52 -8
- package/dist/core/wiki/prompts.js +200 -86
- package/dist/mcp/core/kuzu-adapter.d.ts +3 -1
- package/dist/mcp/core/kuzu-adapter.js +44 -13
- package/dist/mcp/local/local-backend.js +128 -128
- package/dist/mcp/resources.js +42 -42
- package/dist/mcp/server.js +19 -18
- package/dist/mcp/tools.js +104 -103
- package/hooks/claude/gitnexus-hook.cjs +155 -238
- package/hooks/claude/pre-tool-use.sh +79 -79
- package/hooks/claude/session-start.sh +42 -42
- package/package.json +96 -96
- package/scripts/patch-tree-sitter-swift.cjs +74 -74
- package/skills/gitnexus-cli.md +82 -82
- package/skills/gitnexus-debugging.md +89 -89
- package/skills/gitnexus-exploring.md +78 -78
- package/skills/gitnexus-guide.md +64 -64
- package/skills/gitnexus-impact-analysis.md +97 -97
- package/skills/gitnexus-pr-review.md +163 -163
- package/skills/gitnexus-refactoring.md +121 -121
- package/vendor/leiden/index.cjs +355 -355
- package/vendor/leiden/utils.cjs +392 -392
- package/dist/cli/lazy-action.d.ts +0 -6
- package/dist/cli/lazy-action.js +0 -18
- package/dist/cli/skill-gen.d.ts +0 -26
- package/dist/cli/skill-gen.js +0 -549
- package/dist/core/ingestion/constants.d.ts +0 -16
- package/dist/core/ingestion/constants.js +0 -16
- package/dist/core/ingestion/export-detection.d.ts +0 -18
- package/dist/core/ingestion/export-detection.js +0 -230
- package/dist/core/ingestion/language-config.d.ts +0 -46
- package/dist/core/ingestion/language-config.js +0 -167
- package/dist/core/ingestion/mro-processor.d.ts +0 -45
- package/dist/core/ingestion/mro-processor.js +0 -369
- package/dist/core/ingestion/named-binding-extraction.d.ts +0 -61
- package/dist/core/ingestion/named-binding-extraction.js +0 -363
- package/dist/core/ingestion/resolvers/csharp.d.ts +0 -22
- package/dist/core/ingestion/resolvers/csharp.js +0 -109
- package/dist/core/ingestion/resolvers/go.d.ts +0 -19
- package/dist/core/ingestion/resolvers/go.js +0 -42
- package/dist/core/ingestion/resolvers/index.d.ts +0 -16
- package/dist/core/ingestion/resolvers/index.js +0 -11
- package/dist/core/ingestion/resolvers/jvm.d.ts +0 -23
- package/dist/core/ingestion/resolvers/jvm.js +0 -87
- package/dist/core/ingestion/resolvers/php.d.ts +0 -15
- package/dist/core/ingestion/resolvers/php.js +0 -35
- package/dist/core/ingestion/resolvers/rust.d.ts +0 -15
- package/dist/core/ingestion/resolvers/rust.js +0 -73
- package/dist/core/ingestion/resolvers/standard.d.ts +0 -28
- package/dist/core/ingestion/resolvers/standard.js +0 -145
- package/dist/core/ingestion/resolvers/utils.d.ts +0 -33
- package/dist/core/ingestion/resolvers/utils.js +0 -120
- package/dist/core/ingestion/symbol-resolver.d.ts +0 -32
- package/dist/core/ingestion/symbol-resolver.js +0 -83
- package/dist/core/ingestion/type-env.d.ts +0 -27
- package/dist/core/ingestion/type-env.js +0 -86
- package/dist/core/ingestion/type-extractors/c-cpp.d.ts +0 -2
- package/dist/core/ingestion/type-extractors/c-cpp.js +0 -60
- package/dist/core/ingestion/type-extractors/csharp.d.ts +0 -2
- package/dist/core/ingestion/type-extractors/csharp.js +0 -89
- package/dist/core/ingestion/type-extractors/go.d.ts +0 -2
- package/dist/core/ingestion/type-extractors/go.js +0 -105
- package/dist/core/ingestion/type-extractors/index.d.ts +0 -21
- package/dist/core/ingestion/type-extractors/index.js +0 -29
- package/dist/core/ingestion/type-extractors/jvm.d.ts +0 -3
- package/dist/core/ingestion/type-extractors/jvm.js +0 -121
- package/dist/core/ingestion/type-extractors/php.d.ts +0 -2
- package/dist/core/ingestion/type-extractors/php.js +0 -31
- package/dist/core/ingestion/type-extractors/python.d.ts +0 -2
- package/dist/core/ingestion/type-extractors/python.js +0 -41
- package/dist/core/ingestion/type-extractors/rust.d.ts +0 -2
- package/dist/core/ingestion/type-extractors/rust.js +0 -39
- package/dist/core/ingestion/type-extractors/shared.d.ts +0 -17
- package/dist/core/ingestion/type-extractors/shared.js +0 -97
- package/dist/core/ingestion/type-extractors/swift.d.ts +0 -2
- package/dist/core/ingestion/type-extractors/swift.js +0 -43
- package/dist/core/ingestion/type-extractors/types.d.ts +0 -14
- package/dist/core/ingestion/type-extractors/types.js +0 -1
- package/dist/core/ingestion/type-extractors/typescript.d.ts +0 -2
- package/dist/core/ingestion/type-extractors/typescript.js +0 -46
- package/dist/mcp/compatible-stdio-transport.d.ts +0 -25
- 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 {
|
|
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,
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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:
|
|
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.
|
|
223
|
-
* B.
|
|
224
|
-
* C.
|
|
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
|
-
*
|
|
214
|
+
* Returns confidence score so agents know what to trust.
|
|
228
215
|
*/
|
|
229
|
-
const resolveCallTarget = (
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
//
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
|
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,
|
|
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
|
|
308
|
-
|
|
309
|
-
|
|
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
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
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:
|
|
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
|