gitnexus 1.0.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.
- package/README.md +181 -0
- package/dist/cli/ai-context.d.ts +21 -0
- package/dist/cli/ai-context.js +219 -0
- package/dist/cli/analyze.d.ts +10 -0
- package/dist/cli/analyze.js +118 -0
- package/dist/cli/clean.d.ts +8 -0
- package/dist/cli/clean.js +29 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +42 -0
- package/dist/cli/list.d.ts +6 -0
- package/dist/cli/list.js +27 -0
- package/dist/cli/mcp.d.ts +7 -0
- package/dist/cli/mcp.js +85 -0
- package/dist/cli/serve.d.ts +3 -0
- package/dist/cli/serve.js +5 -0
- package/dist/cli/status.d.ts +6 -0
- package/dist/cli/status.js +27 -0
- package/dist/config/ignore-service.d.ts +1 -0
- package/dist/config/ignore-service.js +208 -0
- package/dist/config/supported-languages.d.ts +11 -0
- package/dist/config/supported-languages.js +15 -0
- package/dist/core/embeddings/embedder.d.ts +60 -0
- package/dist/core/embeddings/embedder.js +205 -0
- package/dist/core/embeddings/embedding-pipeline.d.ts +50 -0
- package/dist/core/embeddings/embedding-pipeline.js +321 -0
- package/dist/core/embeddings/index.d.ts +9 -0
- package/dist/core/embeddings/index.js +9 -0
- package/dist/core/embeddings/text-generator.d.ts +24 -0
- package/dist/core/embeddings/text-generator.js +182 -0
- package/dist/core/embeddings/types.d.ts +87 -0
- package/dist/core/embeddings/types.js +32 -0
- package/dist/core/graph/graph.d.ts +2 -0
- package/dist/core/graph/graph.js +61 -0
- package/dist/core/graph/types.d.ts +50 -0
- package/dist/core/graph/types.js +1 -0
- package/dist/core/ingestion/ast-cache.d.ts +11 -0
- package/dist/core/ingestion/ast-cache.js +34 -0
- package/dist/core/ingestion/call-processor.d.ts +8 -0
- package/dist/core/ingestion/call-processor.js +269 -0
- package/dist/core/ingestion/cluster-enricher.d.ts +38 -0
- package/dist/core/ingestion/cluster-enricher.js +170 -0
- package/dist/core/ingestion/community-processor.d.ts +39 -0
- package/dist/core/ingestion/community-processor.js +269 -0
- package/dist/core/ingestion/entry-point-scoring.d.ts +39 -0
- package/dist/core/ingestion/entry-point-scoring.js +235 -0
- package/dist/core/ingestion/filesystem-walker.d.ts +5 -0
- package/dist/core/ingestion/filesystem-walker.js +26 -0
- package/dist/core/ingestion/framework-detection.d.ts +38 -0
- package/dist/core/ingestion/framework-detection.js +183 -0
- package/dist/core/ingestion/heritage-processor.d.ts +14 -0
- package/dist/core/ingestion/heritage-processor.js +134 -0
- package/dist/core/ingestion/import-processor.d.ts +8 -0
- package/dist/core/ingestion/import-processor.js +490 -0
- package/dist/core/ingestion/parsing-processor.d.ts +8 -0
- package/dist/core/ingestion/parsing-processor.js +249 -0
- package/dist/core/ingestion/pipeline.d.ts +2 -0
- package/dist/core/ingestion/pipeline.js +228 -0
- package/dist/core/ingestion/process-processor.d.ts +51 -0
- package/dist/core/ingestion/process-processor.js +278 -0
- package/dist/core/ingestion/structure-processor.d.ts +2 -0
- package/dist/core/ingestion/structure-processor.js +36 -0
- package/dist/core/ingestion/symbol-table.d.ts +33 -0
- package/dist/core/ingestion/symbol-table.js +38 -0
- package/dist/core/ingestion/tree-sitter-queries.d.ts +11 -0
- package/dist/core/ingestion/tree-sitter-queries.js +319 -0
- package/dist/core/ingestion/utils.d.ts +10 -0
- package/dist/core/ingestion/utils.js +44 -0
- package/dist/core/kuzu/csv-generator.d.ts +22 -0
- package/dist/core/kuzu/csv-generator.js +272 -0
- package/dist/core/kuzu/kuzu-adapter.d.ts +81 -0
- package/dist/core/kuzu/kuzu-adapter.js +568 -0
- package/dist/core/kuzu/schema.d.ts +53 -0
- package/dist/core/kuzu/schema.js +380 -0
- package/dist/core/search/bm25-index.d.ts +22 -0
- package/dist/core/search/bm25-index.js +52 -0
- package/dist/core/search/hybrid-search.d.ts +49 -0
- package/dist/core/search/hybrid-search.js +118 -0
- package/dist/core/tree-sitter/parser-loader.d.ts +4 -0
- package/dist/core/tree-sitter/parser-loader.js +42 -0
- package/dist/lib/utils.d.ts +1 -0
- package/dist/lib/utils.js +3 -0
- package/dist/mcp/core/embedder.d.ts +27 -0
- package/dist/mcp/core/embedder.js +93 -0
- package/dist/mcp/core/kuzu-adapter.d.ts +23 -0
- package/dist/mcp/core/kuzu-adapter.js +62 -0
- package/dist/mcp/local/local-backend.d.ts +73 -0
- package/dist/mcp/local/local-backend.js +752 -0
- package/dist/mcp/resources.d.ts +31 -0
- package/dist/mcp/resources.js +279 -0
- package/dist/mcp/server.d.ts +12 -0
- package/dist/mcp/server.js +130 -0
- package/dist/mcp/staleness.d.ts +15 -0
- package/dist/mcp/staleness.js +29 -0
- package/dist/mcp/tools.d.ts +24 -0
- package/dist/mcp/tools.js +160 -0
- package/dist/server/api.d.ts +6 -0
- package/dist/server/api.js +156 -0
- package/dist/storage/git.d.ts +7 -0
- package/dist/storage/git.js +39 -0
- package/dist/storage/repo-manager.d.ts +61 -0
- package/dist/storage/repo-manager.js +106 -0
- package/dist/types/pipeline.d.ts +28 -0
- package/dist/types/pipeline.js +16 -0
- package/package.json +80 -0
- package/skills/debugging.md +104 -0
- package/skills/exploring.md +112 -0
- package/skills/impact-analysis.md +114 -0
- package/skills/refactoring.md +119 -0
- package/vendor/leiden/index.cjs +355 -0
- package/vendor/leiden/utils.cjs +392 -0
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import Parser from 'tree-sitter';
|
|
2
|
+
import { loadParser, loadLanguage } from '../tree-sitter/parser-loader.js';
|
|
3
|
+
import { LANGUAGE_QUERIES } from './tree-sitter-queries.js';
|
|
4
|
+
import { generateId } from '../../lib/utils.js';
|
|
5
|
+
import { getLanguageFromFilename, yieldToEventLoop } from './utils.js';
|
|
6
|
+
// ============================================================================
|
|
7
|
+
// EXPORT DETECTION - Language-specific visibility detection
|
|
8
|
+
// ============================================================================
|
|
9
|
+
/**
|
|
10
|
+
* Check if a symbol (function, class, etc.) is exported/public
|
|
11
|
+
* Handles all 9 supported languages with explicit logic
|
|
12
|
+
*
|
|
13
|
+
* @param node - The AST node for the symbol name
|
|
14
|
+
* @param name - The symbol name
|
|
15
|
+
* @param language - The programming language
|
|
16
|
+
* @returns true if the symbol is exported/public
|
|
17
|
+
*/
|
|
18
|
+
const isNodeExported = (node, name, language) => {
|
|
19
|
+
let current = node;
|
|
20
|
+
switch (language) {
|
|
21
|
+
// JavaScript/TypeScript: Check for export keyword in ancestors
|
|
22
|
+
case 'javascript':
|
|
23
|
+
case 'typescript':
|
|
24
|
+
while (current) {
|
|
25
|
+
const type = current.type;
|
|
26
|
+
if (type === 'export_statement' ||
|
|
27
|
+
type === 'export_specifier' ||
|
|
28
|
+
type === 'lexical_declaration' && current.parent?.type === 'export_statement') {
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
// Also check if text starts with 'export '
|
|
32
|
+
if (current.text?.startsWith('export ')) {
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
current = current.parent;
|
|
36
|
+
}
|
|
37
|
+
return false;
|
|
38
|
+
// Python: Public if no leading underscore (convention)
|
|
39
|
+
case 'python':
|
|
40
|
+
return !name.startsWith('_');
|
|
41
|
+
// Java: Check for 'public' modifier
|
|
42
|
+
// In tree-sitter Java, modifiers are siblings of the name node, not parents
|
|
43
|
+
case 'java':
|
|
44
|
+
while (current) {
|
|
45
|
+
// Check if this node or any sibling is a 'modifiers' node containing 'public'
|
|
46
|
+
if (current.parent) {
|
|
47
|
+
const parent = current.parent;
|
|
48
|
+
// Check all children of the parent for modifiers
|
|
49
|
+
for (let i = 0; i < parent.childCount; i++) {
|
|
50
|
+
const child = parent.child(i);
|
|
51
|
+
if (child?.type === 'modifiers' && child.text?.includes('public')) {
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// Also check if the parent's text starts with 'public' (fallback)
|
|
56
|
+
if (parent.type === 'method_declaration' || parent.type === 'constructor_declaration') {
|
|
57
|
+
if (parent.text?.trimStart().startsWith('public')) {
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
current = current.parent;
|
|
63
|
+
}
|
|
64
|
+
return false;
|
|
65
|
+
// C#: Check for 'public' modifier in ancestors
|
|
66
|
+
case 'csharp':
|
|
67
|
+
while (current) {
|
|
68
|
+
if (current.type === 'modifier' || current.type === 'modifiers') {
|
|
69
|
+
if (current.text?.includes('public'))
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
current = current.parent;
|
|
73
|
+
}
|
|
74
|
+
return false;
|
|
75
|
+
// Go: Uppercase first letter = exported
|
|
76
|
+
case 'go':
|
|
77
|
+
if (name.length === 0)
|
|
78
|
+
return false;
|
|
79
|
+
const first = name[0];
|
|
80
|
+
// Must be uppercase letter (not a number or symbol)
|
|
81
|
+
return first === first.toUpperCase() && first !== first.toLowerCase();
|
|
82
|
+
// Rust: Check for 'pub' visibility modifier
|
|
83
|
+
case 'rust':
|
|
84
|
+
while (current) {
|
|
85
|
+
if (current.type === 'visibility_modifier') {
|
|
86
|
+
if (current.text?.includes('pub'))
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
current = current.parent;
|
|
90
|
+
}
|
|
91
|
+
return false;
|
|
92
|
+
// C/C++: No native export concept at language level
|
|
93
|
+
// Entry points will be detected via name patterns (main, etc.)
|
|
94
|
+
case 'c':
|
|
95
|
+
case 'cpp':
|
|
96
|
+
return false;
|
|
97
|
+
default:
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
export const processParsing = async (graph, files, symbolTable, astCache, onFileProgress) => {
|
|
102
|
+
const parser = await loadParser();
|
|
103
|
+
const total = files.length;
|
|
104
|
+
for (let i = 0; i < files.length; i++) {
|
|
105
|
+
const file = files[i];
|
|
106
|
+
// Report progress for each file
|
|
107
|
+
onFileProgress?.(i + 1, total, file.path);
|
|
108
|
+
// Yield to event loop periodically so spinner can update
|
|
109
|
+
if (i % 20 === 0)
|
|
110
|
+
await yieldToEventLoop();
|
|
111
|
+
const language = getLanguageFromFilename(file.path);
|
|
112
|
+
if (!language)
|
|
113
|
+
continue;
|
|
114
|
+
await loadLanguage(language, file.path);
|
|
115
|
+
// 3. Parse the text content into an AST
|
|
116
|
+
// Use larger bufferSize for files > 32KB (default limit)
|
|
117
|
+
let tree;
|
|
118
|
+
try {
|
|
119
|
+
tree = parser.parse(file.content, undefined, { bufferSize: 1024 * 256 });
|
|
120
|
+
}
|
|
121
|
+
catch (parseError) {
|
|
122
|
+
// Skip files that can't be parsed (binary, encoding issues, etc.)
|
|
123
|
+
console.warn(`Skipping unparseable file: ${file.path}`);
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
// Store in cache immediately (this might evict an old one)
|
|
127
|
+
astCache.set(file.path, tree);
|
|
128
|
+
// 4. Get the specific query string for this language
|
|
129
|
+
const queryString = LANGUAGE_QUERIES[language];
|
|
130
|
+
if (!queryString) {
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
// 5. Run the query against the AST root node
|
|
134
|
+
// This looks for patterns like (function_declaration)
|
|
135
|
+
let query;
|
|
136
|
+
let matches;
|
|
137
|
+
try {
|
|
138
|
+
const language = parser.getLanguage();
|
|
139
|
+
query = new Parser.Query(language, queryString);
|
|
140
|
+
matches = query.matches(tree.rootNode);
|
|
141
|
+
}
|
|
142
|
+
catch (queryError) {
|
|
143
|
+
console.warn(`Query error for ${file.path}:`, queryError);
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
// 6. Process every match found
|
|
147
|
+
matches.forEach(match => {
|
|
148
|
+
const captureMap = {};
|
|
149
|
+
match.captures.forEach(c => {
|
|
150
|
+
captureMap[c.name] = c.node;
|
|
151
|
+
});
|
|
152
|
+
// Skip imports here - they are handled by import-processor.ts
|
|
153
|
+
// which creates proper File -> IMPORTS -> File relationships
|
|
154
|
+
if (captureMap['import']) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
// Skip call expressions - they are handled by call-processor.ts
|
|
158
|
+
if (captureMap['call']) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
const nameNode = captureMap['name'];
|
|
162
|
+
if (!nameNode)
|
|
163
|
+
return;
|
|
164
|
+
const nodeName = nameNode.text;
|
|
165
|
+
let nodeLabel = 'CodeElement';
|
|
166
|
+
// Core types
|
|
167
|
+
if (captureMap['definition.function'])
|
|
168
|
+
nodeLabel = 'Function';
|
|
169
|
+
else if (captureMap['definition.class'])
|
|
170
|
+
nodeLabel = 'Class';
|
|
171
|
+
else if (captureMap['definition.interface'])
|
|
172
|
+
nodeLabel = 'Interface';
|
|
173
|
+
else if (captureMap['definition.method'])
|
|
174
|
+
nodeLabel = 'Method';
|
|
175
|
+
// Struct types (C, C++, Go, Rust, C#)
|
|
176
|
+
else if (captureMap['definition.struct'])
|
|
177
|
+
nodeLabel = 'Struct';
|
|
178
|
+
// Enum types
|
|
179
|
+
else if (captureMap['definition.enum'])
|
|
180
|
+
nodeLabel = 'Enum';
|
|
181
|
+
// Namespace/Module (C++, C#, Rust)
|
|
182
|
+
else if (captureMap['definition.namespace'])
|
|
183
|
+
nodeLabel = 'Namespace';
|
|
184
|
+
else if (captureMap['definition.module'])
|
|
185
|
+
nodeLabel = 'Module';
|
|
186
|
+
// Rust-specific
|
|
187
|
+
else if (captureMap['definition.trait'])
|
|
188
|
+
nodeLabel = 'Trait';
|
|
189
|
+
else if (captureMap['definition.impl'])
|
|
190
|
+
nodeLabel = 'Impl';
|
|
191
|
+
else if (captureMap['definition.type'])
|
|
192
|
+
nodeLabel = 'TypeAlias';
|
|
193
|
+
else if (captureMap['definition.const'])
|
|
194
|
+
nodeLabel = 'Const';
|
|
195
|
+
else if (captureMap['definition.static'])
|
|
196
|
+
nodeLabel = 'Static';
|
|
197
|
+
// C-specific
|
|
198
|
+
else if (captureMap['definition.typedef'])
|
|
199
|
+
nodeLabel = 'Typedef';
|
|
200
|
+
else if (captureMap['definition.macro'])
|
|
201
|
+
nodeLabel = 'Macro';
|
|
202
|
+
else if (captureMap['definition.union'])
|
|
203
|
+
nodeLabel = 'Union';
|
|
204
|
+
// C#-specific
|
|
205
|
+
else if (captureMap['definition.property'])
|
|
206
|
+
nodeLabel = 'Property';
|
|
207
|
+
else if (captureMap['definition.record'])
|
|
208
|
+
nodeLabel = 'Record';
|
|
209
|
+
else if (captureMap['definition.delegate'])
|
|
210
|
+
nodeLabel = 'Delegate';
|
|
211
|
+
// Java-specific
|
|
212
|
+
else if (captureMap['definition.annotation'])
|
|
213
|
+
nodeLabel = 'Annotation';
|
|
214
|
+
else if (captureMap['definition.constructor'])
|
|
215
|
+
nodeLabel = 'Constructor';
|
|
216
|
+
// C++ template
|
|
217
|
+
else if (captureMap['definition.template'])
|
|
218
|
+
nodeLabel = 'Template';
|
|
219
|
+
const nodeId = generateId(nodeLabel, `${file.path}:${nodeName}`);
|
|
220
|
+
const node = {
|
|
221
|
+
id: nodeId,
|
|
222
|
+
label: nodeLabel,
|
|
223
|
+
properties: {
|
|
224
|
+
name: nodeName,
|
|
225
|
+
filePath: file.path,
|
|
226
|
+
startLine: nameNode.startPosition.row,
|
|
227
|
+
endLine: nameNode.endPosition.row,
|
|
228
|
+
language: language,
|
|
229
|
+
isExported: isNodeExported(nameNode, nodeName, language),
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
graph.addNode(node);
|
|
233
|
+
// Register in Symbol Table (only definitions, not imports)
|
|
234
|
+
symbolTable.add(file.path, nodeName, nodeId, nodeLabel);
|
|
235
|
+
const fileId = generateId('File', file.path);
|
|
236
|
+
const relId = generateId('DEFINES', `${fileId}->${nodeId}`);
|
|
237
|
+
const relationship = {
|
|
238
|
+
id: relId,
|
|
239
|
+
sourceId: fileId,
|
|
240
|
+
targetId: nodeId,
|
|
241
|
+
type: 'DEFINES',
|
|
242
|
+
confidence: 1.0,
|
|
243
|
+
reason: '',
|
|
244
|
+
};
|
|
245
|
+
graph.addRelationship(relationship);
|
|
246
|
+
});
|
|
247
|
+
// Don't delete tree here - LRU cache handles cleanup when evicted
|
|
248
|
+
}
|
|
249
|
+
};
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { createKnowledgeGraph } from '../graph/graph.js';
|
|
2
|
+
import { processStructure } from './structure-processor.js';
|
|
3
|
+
import { processParsing } from './parsing-processor.js';
|
|
4
|
+
import { processImports, createImportMap } from './import-processor.js';
|
|
5
|
+
import { processCalls } from './call-processor.js';
|
|
6
|
+
import { processHeritage } from './heritage-processor.js';
|
|
7
|
+
import { processCommunities } from './community-processor.js';
|
|
8
|
+
import { processProcesses } from './process-processor.js';
|
|
9
|
+
import { createSymbolTable } from './symbol-table.js';
|
|
10
|
+
import { createASTCache } from './ast-cache.js';
|
|
11
|
+
import { walkRepository } from './filesystem-walker.js';
|
|
12
|
+
const isDev = process.env.NODE_ENV !== 'production';
|
|
13
|
+
export const runPipelineFromRepo = async (repoPath, onProgress) => {
|
|
14
|
+
const graph = createKnowledgeGraph();
|
|
15
|
+
const fileContents = new Map();
|
|
16
|
+
const symbolTable = createSymbolTable();
|
|
17
|
+
const astCache = createASTCache(50);
|
|
18
|
+
const importMap = createImportMap();
|
|
19
|
+
const cleanup = () => {
|
|
20
|
+
astCache.clear();
|
|
21
|
+
symbolTable.clear();
|
|
22
|
+
};
|
|
23
|
+
try {
|
|
24
|
+
onProgress({
|
|
25
|
+
phase: 'extracting',
|
|
26
|
+
percent: 0,
|
|
27
|
+
message: 'Scanning repository...',
|
|
28
|
+
});
|
|
29
|
+
const files = await walkRepository(repoPath, (current, total, filePath) => {
|
|
30
|
+
const scanProgress = Math.round((current / total) * 15);
|
|
31
|
+
onProgress({
|
|
32
|
+
phase: 'extracting',
|
|
33
|
+
percent: scanProgress,
|
|
34
|
+
message: 'Scanning repository...',
|
|
35
|
+
detail: filePath,
|
|
36
|
+
stats: { filesProcessed: current, totalFiles: total, nodesCreated: graph.nodeCount },
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
files.forEach(f => fileContents.set(f.path, f.content));
|
|
40
|
+
onProgress({
|
|
41
|
+
phase: 'extracting',
|
|
42
|
+
percent: 15,
|
|
43
|
+
message: 'Repository scanned successfully',
|
|
44
|
+
stats: { filesProcessed: files.length, totalFiles: files.length, nodesCreated: graph.nodeCount },
|
|
45
|
+
});
|
|
46
|
+
onProgress({
|
|
47
|
+
phase: 'structure',
|
|
48
|
+
percent: 15,
|
|
49
|
+
message: 'Analyzing project structure...',
|
|
50
|
+
stats: { filesProcessed: 0, totalFiles: files.length, nodesCreated: graph.nodeCount },
|
|
51
|
+
});
|
|
52
|
+
const filePaths = files.map(f => f.path);
|
|
53
|
+
processStructure(graph, filePaths);
|
|
54
|
+
onProgress({
|
|
55
|
+
phase: 'structure',
|
|
56
|
+
percent: 30,
|
|
57
|
+
message: 'Project structure analyzed',
|
|
58
|
+
stats: { filesProcessed: files.length, totalFiles: files.length, nodesCreated: graph.nodeCount },
|
|
59
|
+
});
|
|
60
|
+
onProgress({
|
|
61
|
+
phase: 'parsing',
|
|
62
|
+
percent: 30,
|
|
63
|
+
message: 'Parsing code definitions...',
|
|
64
|
+
stats: { filesProcessed: 0, totalFiles: files.length, nodesCreated: graph.nodeCount },
|
|
65
|
+
});
|
|
66
|
+
await processParsing(graph, files, symbolTable, astCache, (current, total, filePath) => {
|
|
67
|
+
const parsingProgress = 30 + ((current / total) * 40);
|
|
68
|
+
onProgress({
|
|
69
|
+
phase: 'parsing',
|
|
70
|
+
percent: Math.round(parsingProgress),
|
|
71
|
+
message: 'Parsing code definitions...',
|
|
72
|
+
detail: filePath,
|
|
73
|
+
stats: { filesProcessed: current, totalFiles: total, nodesCreated: graph.nodeCount },
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
onProgress({
|
|
77
|
+
phase: 'imports',
|
|
78
|
+
percent: 70,
|
|
79
|
+
message: 'Resolving imports...',
|
|
80
|
+
stats: { filesProcessed: 0, totalFiles: files.length, nodesCreated: graph.nodeCount },
|
|
81
|
+
});
|
|
82
|
+
await processImports(graph, files, astCache, importMap, (current, total) => {
|
|
83
|
+
const importProgress = 70 + ((current / total) * 12);
|
|
84
|
+
onProgress({
|
|
85
|
+
phase: 'imports',
|
|
86
|
+
percent: Math.round(importProgress),
|
|
87
|
+
message: 'Resolving imports...',
|
|
88
|
+
stats: { filesProcessed: current, totalFiles: total, nodesCreated: graph.nodeCount },
|
|
89
|
+
});
|
|
90
|
+
}, repoPath);
|
|
91
|
+
if (isDev) {
|
|
92
|
+
const importsCount = graph.relationships.filter(r => r.type === 'IMPORTS').length;
|
|
93
|
+
console.log(`📊 Pipeline: After import phase, graph has ${importsCount} IMPORTS relationships (total: ${graph.relationshipCount})`);
|
|
94
|
+
}
|
|
95
|
+
onProgress({
|
|
96
|
+
phase: 'calls',
|
|
97
|
+
percent: 82,
|
|
98
|
+
message: 'Tracing function calls...',
|
|
99
|
+
stats: { filesProcessed: 0, totalFiles: files.length, nodesCreated: graph.nodeCount },
|
|
100
|
+
});
|
|
101
|
+
await processCalls(graph, files, astCache, symbolTable, importMap, (current, total) => {
|
|
102
|
+
const callProgress = 82 + ((current / total) * 10);
|
|
103
|
+
onProgress({
|
|
104
|
+
phase: 'calls',
|
|
105
|
+
percent: Math.round(callProgress),
|
|
106
|
+
message: 'Tracing function calls...',
|
|
107
|
+
stats: { filesProcessed: current, totalFiles: total, nodesCreated: graph.nodeCount },
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
onProgress({
|
|
111
|
+
phase: 'heritage',
|
|
112
|
+
percent: 92,
|
|
113
|
+
message: 'Extracting class inheritance...',
|
|
114
|
+
stats: { filesProcessed: 0, totalFiles: files.length, nodesCreated: graph.nodeCount },
|
|
115
|
+
});
|
|
116
|
+
await processHeritage(graph, files, astCache, symbolTable, (current, total) => {
|
|
117
|
+
const heritageProgress = 88 + ((current / total) * 4);
|
|
118
|
+
onProgress({
|
|
119
|
+
phase: 'heritage',
|
|
120
|
+
percent: Math.round(heritageProgress),
|
|
121
|
+
message: 'Extracting class inheritance...',
|
|
122
|
+
stats: { filesProcessed: current, totalFiles: total, nodesCreated: graph.nodeCount },
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
onProgress({
|
|
126
|
+
phase: 'communities',
|
|
127
|
+
percent: 92,
|
|
128
|
+
message: 'Detecting code communities...',
|
|
129
|
+
stats: { filesProcessed: files.length, totalFiles: files.length, nodesCreated: graph.nodeCount },
|
|
130
|
+
});
|
|
131
|
+
const communityResult = await processCommunities(graph, (message, progress) => {
|
|
132
|
+
const communityProgress = 92 + (progress * 0.06);
|
|
133
|
+
onProgress({
|
|
134
|
+
phase: 'communities',
|
|
135
|
+
percent: Math.round(communityProgress),
|
|
136
|
+
message,
|
|
137
|
+
stats: { filesProcessed: files.length, totalFiles: files.length, nodesCreated: graph.nodeCount },
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
if (isDev) {
|
|
141
|
+
console.log(`🏘️ Community detection: ${communityResult.stats.totalCommunities} communities found (modularity: ${communityResult.stats.modularity.toFixed(3)})`);
|
|
142
|
+
}
|
|
143
|
+
communityResult.communities.forEach(comm => {
|
|
144
|
+
graph.addNode({
|
|
145
|
+
id: comm.id,
|
|
146
|
+
label: 'Community',
|
|
147
|
+
properties: {
|
|
148
|
+
name: comm.label,
|
|
149
|
+
filePath: '',
|
|
150
|
+
heuristicLabel: comm.heuristicLabel,
|
|
151
|
+
cohesion: comm.cohesion,
|
|
152
|
+
symbolCount: comm.symbolCount,
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
communityResult.memberships.forEach(membership => {
|
|
157
|
+
graph.addRelationship({
|
|
158
|
+
id: `${membership.nodeId}_member_of_${membership.communityId}`,
|
|
159
|
+
type: 'MEMBER_OF',
|
|
160
|
+
sourceId: membership.nodeId,
|
|
161
|
+
targetId: membership.communityId,
|
|
162
|
+
confidence: 1.0,
|
|
163
|
+
reason: 'leiden-algorithm',
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
onProgress({
|
|
167
|
+
phase: 'processes',
|
|
168
|
+
percent: 98,
|
|
169
|
+
message: 'Detecting execution flows...',
|
|
170
|
+
stats: { filesProcessed: files.length, totalFiles: files.length, nodesCreated: graph.nodeCount },
|
|
171
|
+
});
|
|
172
|
+
const processResult = await processProcesses(graph, communityResult.memberships, (message, progress) => {
|
|
173
|
+
const processProgress = 98 + (progress * 0.01);
|
|
174
|
+
onProgress({
|
|
175
|
+
phase: 'processes',
|
|
176
|
+
percent: Math.round(processProgress),
|
|
177
|
+
message,
|
|
178
|
+
stats: { filesProcessed: files.length, totalFiles: files.length, nodesCreated: graph.nodeCount },
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
if (isDev) {
|
|
182
|
+
console.log(`🔄 Process detection: ${processResult.stats.totalProcesses} processes found (${processResult.stats.crossCommunityCount} cross-community)`);
|
|
183
|
+
}
|
|
184
|
+
processResult.processes.forEach(proc => {
|
|
185
|
+
graph.addNode({
|
|
186
|
+
id: proc.id,
|
|
187
|
+
label: 'Process',
|
|
188
|
+
properties: {
|
|
189
|
+
name: proc.label,
|
|
190
|
+
filePath: '',
|
|
191
|
+
heuristicLabel: proc.heuristicLabel,
|
|
192
|
+
processType: proc.processType,
|
|
193
|
+
stepCount: proc.stepCount,
|
|
194
|
+
communities: proc.communities,
|
|
195
|
+
entryPointId: proc.entryPointId,
|
|
196
|
+
terminalId: proc.terminalId,
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
processResult.steps.forEach(step => {
|
|
201
|
+
graph.addRelationship({
|
|
202
|
+
id: `${step.nodeId}_step_${step.step}_${step.processId}`,
|
|
203
|
+
type: 'STEP_IN_PROCESS',
|
|
204
|
+
sourceId: step.nodeId,
|
|
205
|
+
targetId: step.processId,
|
|
206
|
+
confidence: 1.0,
|
|
207
|
+
reason: 'trace-detection',
|
|
208
|
+
step: step.step,
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
onProgress({
|
|
212
|
+
phase: 'complete',
|
|
213
|
+
percent: 100,
|
|
214
|
+
message: `Graph complete! ${communityResult.stats.totalCommunities} communities, ${processResult.stats.totalProcesses} processes detected.`,
|
|
215
|
+
stats: {
|
|
216
|
+
filesProcessed: files.length,
|
|
217
|
+
totalFiles: files.length,
|
|
218
|
+
nodesCreated: graph.nodeCount
|
|
219
|
+
},
|
|
220
|
+
});
|
|
221
|
+
astCache.clear();
|
|
222
|
+
return { graph, fileContents, communityResult, processResult };
|
|
223
|
+
}
|
|
224
|
+
catch (error) {
|
|
225
|
+
cleanup();
|
|
226
|
+
throw error;
|
|
227
|
+
}
|
|
228
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Process Detection Processor
|
|
3
|
+
*
|
|
4
|
+
* Detects execution flows (Processes) in the code graph by:
|
|
5
|
+
* 1. Finding entry points (functions with no internal callers)
|
|
6
|
+
* 2. Tracing forward via CALLS edges (BFS)
|
|
7
|
+
* 3. Grouping and deduplicating similar paths
|
|
8
|
+
* 4. Labeling with heuristic names
|
|
9
|
+
*
|
|
10
|
+
* Processes help agents understand how features work through the codebase.
|
|
11
|
+
*/
|
|
12
|
+
import { KnowledgeGraph } from '../graph/types.js';
|
|
13
|
+
import { CommunityMembership } from './community-processor.js';
|
|
14
|
+
export interface ProcessDetectionConfig {
|
|
15
|
+
maxTraceDepth: number;
|
|
16
|
+
maxBranching: number;
|
|
17
|
+
maxProcesses: number;
|
|
18
|
+
minSteps: number;
|
|
19
|
+
}
|
|
20
|
+
export interface ProcessNode {
|
|
21
|
+
id: string;
|
|
22
|
+
label: string;
|
|
23
|
+
heuristicLabel: string;
|
|
24
|
+
processType: 'intra_community' | 'cross_community';
|
|
25
|
+
stepCount: number;
|
|
26
|
+
communities: string[];
|
|
27
|
+
entryPointId: string;
|
|
28
|
+
terminalId: string;
|
|
29
|
+
trace: string[];
|
|
30
|
+
}
|
|
31
|
+
export interface ProcessStep {
|
|
32
|
+
nodeId: string;
|
|
33
|
+
processId: string;
|
|
34
|
+
step: number;
|
|
35
|
+
}
|
|
36
|
+
export interface ProcessDetectionResult {
|
|
37
|
+
processes: ProcessNode[];
|
|
38
|
+
steps: ProcessStep[];
|
|
39
|
+
stats: {
|
|
40
|
+
totalProcesses: number;
|
|
41
|
+
crossCommunityCount: number;
|
|
42
|
+
avgStepCount: number;
|
|
43
|
+
entryPointsFound: number;
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Detect processes (execution flows) in the knowledge graph
|
|
48
|
+
*
|
|
49
|
+
* This runs AFTER community detection, using CALLS edges to trace flows.
|
|
50
|
+
*/
|
|
51
|
+
export declare const processProcesses: (knowledgeGraph: KnowledgeGraph, memberships: CommunityMembership[], onProgress?: (message: string, progress: number) => void, config?: Partial<ProcessDetectionConfig>) => Promise<ProcessDetectionResult>;
|