@kentwynn/kgraph 0.1.26 → 0.2.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 +3 -18
- package/dist/cli/commands/context.d.ts +2 -2
- package/dist/cli/commands/context.js +82 -23
- package/dist/cli/commands/init.js +2 -25
- package/dist/cli/commands/workflow.js +2 -2
- package/dist/cli/help.d.ts +1 -0
- package/dist/cli/help.js +4 -6
- package/dist/cli/index.js +0 -2
- package/dist/cli/init-prompt.d.ts +2 -7
- package/dist/cli/init-prompt.js +0 -63
- package/dist/cli/init-recommendations.d.ts +1 -12
- package/dist/cli/init-recommendations.js +0 -23
- package/dist/cli/init-summary.d.ts +2 -4
- package/dist/cli/init-summary.js +10 -35
- package/dist/config/config.js +0 -33
- package/dist/context/context-query.js +23 -0
- package/dist/scanner/c-symbol-extractor.d.ts +1 -1
- package/dist/scanner/c-symbol-extractor.js +108 -65
- package/dist/scanner/csharp-symbol-extractor.d.ts +1 -1
- package/dist/scanner/csharp-symbol-extractor.js +93 -67
- package/dist/scanner/go-symbol-extractor.d.ts +1 -1
- package/dist/scanner/go-symbol-extractor.js +75 -60
- package/dist/scanner/jvm-symbol-extractor.d.ts +1 -1
- package/dist/scanner/jvm-symbol-extractor.js +139 -71
- package/dist/scanner/python-symbol-extractor.d.ts +1 -1
- package/dist/scanner/python-symbol-extractor.js +92 -71
- package/dist/scanner/repo-scanner.js +65 -8
- package/dist/scanner/rust-symbol-extractor.d.ts +1 -1
- package/dist/scanner/rust-symbol-extractor.js +94 -89
- package/dist/scanner/tree-sitter-parser.d.ts +5 -0
- package/dist/scanner/tree-sitter-parser.js +55 -0
- package/dist/types/cognition.d.ts +3 -2
- package/dist/types/config.d.ts +0 -7
- package/dist/types/maps.d.ts +6 -5
- package/package.json +10 -1
- package/dist/cli/commands/extractor.d.ts +0 -2
- package/dist/cli/commands/extractor.js +0 -50
- package/dist/extractors/extractor-registry.d.ts +0 -11
- package/dist/extractors/extractor-registry.js +0 -70
- package/dist/extractors/extractor-store.d.ts +0 -10
- package/dist/extractors/extractor-store.js +0 -58
|
@@ -1,16 +1,17 @@
|
|
|
1
|
+
import { parseSource } from './tree-sitter-parser.js';
|
|
1
2
|
// Handles Java (.java) and Kotlin (.kt, .kts)
|
|
2
|
-
export function extractJvmSymbols(sourceText, filePath) {
|
|
3
|
-
const ext = filePath.endsWith('.kt') || filePath.endsWith('.kts') ? 'kotlin' : 'java';
|
|
4
|
-
const lines = sourceText.split('\n');
|
|
3
|
+
export async function extractJvmSymbols(sourceText, filePath) {
|
|
5
4
|
const symbols = [];
|
|
6
5
|
const dependencies = [];
|
|
7
6
|
const relationships = [];
|
|
8
7
|
const warnings = [];
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
-
|
|
8
|
+
if (!sourceText.trim()) {
|
|
9
|
+
return { symbols, dependencies, relationships, warnings };
|
|
10
|
+
}
|
|
11
|
+
const lang = filePath.endsWith('.kt') || filePath.endsWith('.kts') ? 'kotlin' : 'java';
|
|
12
|
+
const tree = await parseSource(sourceText, lang);
|
|
13
|
+
const addSymbol = (name, kind, startLine, endLine, exported, parentName) => {
|
|
14
|
+
const id = [filePath, kind, parentName, name, startLine]
|
|
14
15
|
.filter(Boolean)
|
|
15
16
|
.join('#');
|
|
16
17
|
symbols.push({
|
|
@@ -18,8 +19,8 @@ export function extractJvmSymbols(sourceText, filePath) {
|
|
|
18
19
|
name,
|
|
19
20
|
kind,
|
|
20
21
|
filePath,
|
|
21
|
-
startLine
|
|
22
|
-
endLine
|
|
22
|
+
startLine,
|
|
23
|
+
endLine,
|
|
23
24
|
exported,
|
|
24
25
|
parentName,
|
|
25
26
|
});
|
|
@@ -32,74 +33,141 @@ export function extractJvmSymbols(sourceText, filePath) {
|
|
|
32
33
|
confidence: 'high',
|
|
33
34
|
});
|
|
34
35
|
};
|
|
35
|
-
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
36
|
+
function hasModifier(node, modifier) {
|
|
37
|
+
const mods = node.childForFieldName('modifiers') ??
|
|
38
|
+
node.namedChildren.find((c) => c.type === 'modifiers');
|
|
39
|
+
if (!mods)
|
|
40
|
+
return false;
|
|
41
|
+
return mods.text.includes(modifier);
|
|
42
|
+
}
|
|
43
|
+
function walkJava(node, parentClassName) {
|
|
44
|
+
switch (node.type) {
|
|
45
|
+
case 'import_declaration': {
|
|
46
|
+
// Java import: scoped_identifier child
|
|
47
|
+
const scopedId = node.namedChildren.find((c) => c.type === 'scoped_identifier' || c.type === 'identifier');
|
|
48
|
+
if (scopedId) {
|
|
49
|
+
const specifier = scopedId.text.replace(/\.\*$/, '');
|
|
50
|
+
dependencies.push({ fromFile: filePath, specifier, kind: 'package' });
|
|
51
|
+
relationships.push({
|
|
52
|
+
sourceType: 'file',
|
|
53
|
+
sourceId: filePath,
|
|
54
|
+
targetType: 'package',
|
|
55
|
+
targetId: specifier,
|
|
56
|
+
relationshipType: 'import',
|
|
57
|
+
confidence: 'high',
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
case 'class_declaration':
|
|
63
|
+
case 'interface_declaration':
|
|
64
|
+
case 'enum_declaration':
|
|
65
|
+
case 'annotation_type_declaration': {
|
|
66
|
+
const nameNode = node.childForFieldName('name');
|
|
67
|
+
if (nameNode) {
|
|
68
|
+
const exported = hasModifier(node, 'public');
|
|
69
|
+
addSymbol(nameNode.text, 'class', node.startPosition.row + 1, node.endPosition.row + 1, exported, parentClassName);
|
|
70
|
+
// Walk class body for methods and nested types
|
|
71
|
+
const body = node.childForFieldName('body') ??
|
|
72
|
+
node.namedChildren.find((c) => c.type === 'class_body' ||
|
|
73
|
+
c.type === 'interface_body' ||
|
|
74
|
+
c.type === 'enum_body');
|
|
75
|
+
if (body) {
|
|
76
|
+
for (const child of body.namedChildren) {
|
|
77
|
+
walkJava(child, nameNode.text);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
case 'method_declaration':
|
|
84
|
+
case 'constructor_declaration': {
|
|
85
|
+
const nameNode = node.childForFieldName('name');
|
|
86
|
+
if (nameNode) {
|
|
87
|
+
const exported = hasModifier(node, 'public');
|
|
88
|
+
const kind = parentClassName
|
|
89
|
+
? 'method'
|
|
90
|
+
: 'function';
|
|
91
|
+
addSymbol(nameNode.text, kind, node.startPosition.row + 1, node.endPosition.row + 1, exported, parentClassName);
|
|
92
|
+
}
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
46
95
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
if (importMatch) {
|
|
50
|
-
const specifier = importMatch[1].replace(/\.\*$/, '');
|
|
51
|
-
dependencies.push({ fromFile: filePath, specifier, kind: 'package' });
|
|
52
|
-
relationships.push({
|
|
53
|
-
sourceType: 'file',
|
|
54
|
-
sourceId: filePath,
|
|
55
|
-
targetType: 'package',
|
|
56
|
-
targetId: specifier,
|
|
57
|
-
relationshipType: 'import',
|
|
58
|
-
confidence: 'high',
|
|
59
|
-
});
|
|
60
|
-
continue;
|
|
96
|
+
for (const child of node.namedChildren) {
|
|
97
|
+
walkJava(child, parentClassName);
|
|
61
98
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
99
|
+
}
|
|
100
|
+
function walkKotlin(node, parentClassName) {
|
|
101
|
+
switch (node.type) {
|
|
102
|
+
case 'import': {
|
|
103
|
+
// Kotlin import: qualified_identifier child
|
|
104
|
+
const qualId = node.namedChildren.find((c) => c.type === 'qualified_identifier' || c.type === 'identifier');
|
|
105
|
+
if (qualId) {
|
|
106
|
+
const specifier = qualId.text;
|
|
107
|
+
dependencies.push({ fromFile: filePath, specifier, kind: 'package' });
|
|
108
|
+
relationships.push({
|
|
109
|
+
sourceType: 'file',
|
|
110
|
+
sourceId: filePath,
|
|
111
|
+
targetType: 'package',
|
|
112
|
+
targetId: specifier,
|
|
113
|
+
relationshipType: 'import',
|
|
114
|
+
confidence: 'high',
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
return;
|
|
71
118
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
119
|
+
case 'class_declaration': {
|
|
120
|
+
const nameNode = node.childForFieldName('name') ??
|
|
121
|
+
node.namedChildren.find((c) => c.type === 'identifier');
|
|
122
|
+
if (nameNode) {
|
|
123
|
+
const exported = !hasModifier(node, 'private') && !hasModifier(node, 'internal');
|
|
124
|
+
addSymbol(nameNode.text, 'class', node.startPosition.row + 1, node.endPosition.row + 1, exported, parentClassName);
|
|
125
|
+
// Walk class body
|
|
126
|
+
const body = node.namedChildren.find((c) => c.type === 'class_body');
|
|
127
|
+
if (body) {
|
|
128
|
+
for (const child of body.namedChildren) {
|
|
129
|
+
walkKotlin(child, nameNode.text);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return;
|
|
81
134
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
135
|
+
case 'object_declaration': {
|
|
136
|
+
const nameNode = node.namedChildren.find((c) => c.type === 'identifier');
|
|
137
|
+
if (nameNode) {
|
|
138
|
+
const exported = !hasModifier(node, 'private') && !hasModifier(node, 'internal');
|
|
139
|
+
addSymbol(nameNode.text, 'class', node.startPosition.row + 1, node.endPosition.row + 1, exported, parentClassName);
|
|
140
|
+
const body = node.namedChildren.find((c) => c.type === 'class_body');
|
|
141
|
+
if (body) {
|
|
142
|
+
for (const child of body.namedChildren) {
|
|
143
|
+
walkKotlin(child, nameNode.text);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return;
|
|
92
148
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
149
|
+
case 'function_declaration': {
|
|
150
|
+
const nameNode = node.namedChildren.find((c) => c.type === 'identifier');
|
|
151
|
+
if (nameNode) {
|
|
152
|
+
const exported = !hasModifier(node, 'private') && !hasModifier(node, 'internal');
|
|
153
|
+
const kind = parentClassName
|
|
154
|
+
? 'method'
|
|
155
|
+
: 'function';
|
|
156
|
+
addSymbol(nameNode.text, kind, node.startPosition.row + 1, node.endPosition.row + 1, exported, parentClassName);
|
|
157
|
+
}
|
|
158
|
+
return;
|
|
101
159
|
}
|
|
102
160
|
}
|
|
161
|
+
for (const child of node.namedChildren) {
|
|
162
|
+
walkKotlin(child, parentClassName);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
if (lang === 'java') {
|
|
166
|
+
walkJava(tree.rootNode);
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
walkKotlin(tree.rootNode);
|
|
103
170
|
}
|
|
171
|
+
tree.delete();
|
|
104
172
|
return { symbols, dependencies, relationships, warnings };
|
|
105
173
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { SymbolExtractionResult } from './ts-symbol-extractor.js';
|
|
2
|
-
export declare function extractPythonSymbols(sourceText: string, filePath: string): SymbolExtractionResult
|
|
2
|
+
export declare function extractPythonSymbols(sourceText: string, filePath: string): Promise<SymbolExtractionResult>;
|
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { parseSource } from './tree-sitter-parser.js';
|
|
2
|
+
export async function extractPythonSymbols(sourceText, filePath) {
|
|
3
3
|
const symbols = [];
|
|
4
4
|
const dependencies = [];
|
|
5
5
|
const relationships = [];
|
|
6
6
|
const warnings = [];
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
if (!sourceText.trim()) {
|
|
8
|
+
return { symbols, dependencies, relationships, warnings };
|
|
9
|
+
}
|
|
10
|
+
const tree = await parseSource(sourceText, 'python');
|
|
11
|
+
const addSymbol = (name, kind, startLine, endLine, exported, parentName) => {
|
|
12
|
+
const id = [filePath, kind, parentName, name, startLine]
|
|
11
13
|
.filter(Boolean)
|
|
12
14
|
.join('#');
|
|
13
15
|
symbols.push({
|
|
@@ -15,9 +17,9 @@ export function extractPythonSymbols(sourceText, filePath) {
|
|
|
15
17
|
name,
|
|
16
18
|
kind,
|
|
17
19
|
filePath,
|
|
18
|
-
startLine
|
|
19
|
-
endLine
|
|
20
|
-
exported
|
|
20
|
+
startLine,
|
|
21
|
+
endLine,
|
|
22
|
+
exported,
|
|
21
23
|
parentName,
|
|
22
24
|
});
|
|
23
25
|
relationships.push({
|
|
@@ -29,70 +31,89 @@ export function extractPythonSymbols(sourceText, filePath) {
|
|
|
29
31
|
confidence: 'high',
|
|
30
32
|
});
|
|
31
33
|
};
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
34
|
+
function walk(node, parentClassName) {
|
|
35
|
+
switch (node.type) {
|
|
36
|
+
case 'class_definition': {
|
|
37
|
+
const nameNode = node.childForFieldName('name');
|
|
38
|
+
if (nameNode) {
|
|
39
|
+
const startLine = node.startPosition.row + 1;
|
|
40
|
+
const endLine = node.endPosition.row + 1;
|
|
41
|
+
addSymbol(nameNode.text, 'class', startLine, endLine, false, parentClassName);
|
|
42
|
+
// Walk children for nested classes/methods
|
|
43
|
+
const body = node.childForFieldName('body');
|
|
44
|
+
if (body) {
|
|
45
|
+
for (const child of body.namedChildren) {
|
|
46
|
+
walk(child, nameNode.text);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return; // Don't recurse further from here
|
|
51
|
+
}
|
|
52
|
+
case 'function_definition': {
|
|
53
|
+
const nameNode = node.childForFieldName('name');
|
|
54
|
+
if (nameNode) {
|
|
55
|
+
const startLine = node.startPosition.row + 1;
|
|
56
|
+
const endLine = node.endPosition.row + 1;
|
|
57
|
+
const kind = parentClassName
|
|
58
|
+
? 'method'
|
|
59
|
+
: 'function';
|
|
60
|
+
addSymbol(nameNode.text, kind, startLine, endLine, false, parentClassName);
|
|
61
|
+
}
|
|
62
|
+
return; // Don't recurse into function bodies
|
|
63
|
+
}
|
|
64
|
+
case 'import_statement': {
|
|
65
|
+
// import os / import os.path
|
|
66
|
+
const startLine = node.startPosition.row + 1;
|
|
67
|
+
for (const child of node.namedChildren) {
|
|
68
|
+
if (child.type === 'dotted_name' || child.type === 'aliased_import') {
|
|
69
|
+
const specifier = child.type === 'aliased_import'
|
|
70
|
+
? (child.childForFieldName('name')?.text ?? child.text)
|
|
71
|
+
: child.text;
|
|
72
|
+
dependencies.push({
|
|
73
|
+
fromFile: filePath,
|
|
74
|
+
specifier,
|
|
75
|
+
kind: 'package',
|
|
76
|
+
});
|
|
77
|
+
addSymbol(specifier, 'import', startLine, startLine, false);
|
|
78
|
+
relationships.push({
|
|
79
|
+
sourceType: 'file',
|
|
80
|
+
sourceId: filePath,
|
|
81
|
+
targetType: 'package',
|
|
82
|
+
targetId: specifier,
|
|
83
|
+
relationshipType: 'import',
|
|
84
|
+
confidence: 'high',
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
case 'import_from_statement': {
|
|
91
|
+
// from X import Y
|
|
92
|
+
const moduleNode = node.childForFieldName('module_name');
|
|
93
|
+
if (moduleNode) {
|
|
94
|
+
const specifier = moduleNode.text;
|
|
95
|
+
const kind = specifier.startsWith('.')
|
|
96
|
+
? 'local'
|
|
97
|
+
: 'package';
|
|
98
|
+
dependencies.push({ fromFile: filePath, specifier, kind });
|
|
99
|
+
relationships.push({
|
|
100
|
+
sourceType: 'file',
|
|
101
|
+
sourceId: filePath,
|
|
102
|
+
targetType: kind === 'local' ? 'file' : 'package',
|
|
103
|
+
targetId: specifier,
|
|
104
|
+
relationshipType: 'import',
|
|
105
|
+
confidence: kind === 'local' ? 'medium' : 'high',
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
78
110
|
}
|
|
79
|
-
//
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
const specifier = fromMatch[1];
|
|
83
|
-
const kind = specifier.startsWith('.')
|
|
84
|
-
? 'local'
|
|
85
|
-
: 'package';
|
|
86
|
-
dependencies.push({ fromFile: filePath, specifier, kind });
|
|
87
|
-
relationships.push({
|
|
88
|
-
sourceType: 'file',
|
|
89
|
-
sourceId: filePath,
|
|
90
|
-
targetType: kind === 'local' ? 'file' : 'package',
|
|
91
|
-
targetId: specifier,
|
|
92
|
-
relationshipType: 'import',
|
|
93
|
-
confidence: kind === 'local' ? 'medium' : 'high',
|
|
94
|
-
});
|
|
111
|
+
// Recurse into children for top-level statements
|
|
112
|
+
for (const child of node.namedChildren) {
|
|
113
|
+
walk(child, parentClassName);
|
|
95
114
|
}
|
|
96
115
|
}
|
|
116
|
+
walk(tree.rootNode);
|
|
117
|
+
tree.delete();
|
|
97
118
|
return { symbols, dependencies, relationships, warnings };
|
|
98
119
|
}
|
|
@@ -2,6 +2,7 @@ import fg from 'fast-glob';
|
|
|
2
2
|
import crypto from 'node:crypto';
|
|
3
3
|
import { readFile, stat } from 'node:fs/promises';
|
|
4
4
|
import path from 'node:path';
|
|
5
|
+
import { estimateTokens } from '../session/token-estimator.js';
|
|
5
6
|
import { extractCSymbols } from './c-symbol-extractor.js';
|
|
6
7
|
import { extractCSharpSymbols } from './csharp-symbol-extractor.js';
|
|
7
8
|
import { buildFastGlobIgnore, detectLanguage, isPreciseLanguage, readGitignorePatterns, shouldExclude, } from './file-classifier.js';
|
|
@@ -10,10 +11,9 @@ import { extractJvmSymbols } from './jvm-symbol-extractor.js';
|
|
|
10
11
|
import { extractPythonSymbols } from './python-symbol-extractor.js';
|
|
11
12
|
import { extractRustSymbols } from './rust-symbol-extractor.js';
|
|
12
13
|
import { extractTsSymbols } from './ts-symbol-extractor.js';
|
|
13
|
-
import { estimateTokens } from '../session/token-estimator.js';
|
|
14
14
|
const C_EXTS = new Set(['.c', '.h', '.cpp', '.cc', '.cxx', '.hpp', '.hxx']);
|
|
15
15
|
const JVM_EXTS = new Set(['.java', '.kt', '.kts']);
|
|
16
|
-
function extractSymbols(text, repoPath) {
|
|
16
|
+
async function extractSymbols(text, repoPath) {
|
|
17
17
|
const ext = path.extname(repoPath);
|
|
18
18
|
if (ext === '.py' || ext === '.pyw' || ext === '.pyi') {
|
|
19
19
|
return extractPythonSymbols(text, repoPath);
|
|
@@ -46,21 +46,71 @@ export async function scanRepository(rootPath, config, previous) {
|
|
|
46
46
|
unique: true,
|
|
47
47
|
ignore: buildFastGlobIgnore(allExcludes),
|
|
48
48
|
});
|
|
49
|
+
// Build lookup maps from previous scan for incremental skip
|
|
50
|
+
const prevFileByPath = new Map((previous?.files ?? []).map((f) => [f.path, f]));
|
|
51
|
+
const prevSymbolsByFile = new Map();
|
|
52
|
+
const prevDepsByFile = new Map();
|
|
53
|
+
const prevRelsBySource = new Map();
|
|
54
|
+
if (previous) {
|
|
55
|
+
for (const sym of previous.symbols) {
|
|
56
|
+
const arr = prevSymbolsByFile.get(sym.filePath) ?? [];
|
|
57
|
+
arr.push(sym);
|
|
58
|
+
prevSymbolsByFile.set(sym.filePath, arr);
|
|
59
|
+
}
|
|
60
|
+
for (const dep of previous.dependencies) {
|
|
61
|
+
const arr = prevDepsByFile.get(dep.fromFile) ?? [];
|
|
62
|
+
arr.push(dep);
|
|
63
|
+
prevDepsByFile.set(dep.fromFile, arr);
|
|
64
|
+
}
|
|
65
|
+
for (const rel of previous.relationships) {
|
|
66
|
+
if (rel.relationshipType !== 'import' &&
|
|
67
|
+
rel.relationshipType !== 'moved-from') {
|
|
68
|
+
const arr = prevRelsBySource.get(rel.sourceId) ?? [];
|
|
69
|
+
arr.push(rel);
|
|
70
|
+
prevRelsBySource.set(rel.sourceId, arr);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
49
74
|
const files = [];
|
|
50
75
|
const symbols = [];
|
|
51
76
|
const dependencies = [];
|
|
52
77
|
const relationships = [];
|
|
53
78
|
const warnings = [];
|
|
79
|
+
let skippedFiles = 0;
|
|
54
80
|
for (const repoPath of entries.sort()) {
|
|
55
81
|
if (shouldExclude(repoPath, mergedConfig)) {
|
|
56
82
|
continue;
|
|
57
83
|
}
|
|
58
84
|
const absolutePath = path.join(rootPath, repoPath);
|
|
59
85
|
try {
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
86
|
+
const info = await stat(absolutePath);
|
|
87
|
+
// Incremental skip: if mtime and size match previous, carry forward
|
|
88
|
+
const prevFile = prevFileByPath.get(repoPath);
|
|
89
|
+
if (prevFile &&
|
|
90
|
+
prevFile.sizeBytes === info.size &&
|
|
91
|
+
prevFile.modifiedAt === info.mtime.toISOString()) {
|
|
92
|
+
files.push({ ...prevFile, modifiedAt: info.mtime.toISOString() });
|
|
93
|
+
const prevSyms = prevSymbolsByFile.get(repoPath);
|
|
94
|
+
if (prevSyms)
|
|
95
|
+
symbols.push(...prevSyms);
|
|
96
|
+
const prevDeps = prevDepsByFile.get(repoPath);
|
|
97
|
+
if (prevDeps)
|
|
98
|
+
dependencies.push(...prevDeps);
|
|
99
|
+
const prevRels = prevRelsBySource.get(repoPath);
|
|
100
|
+
if (prevRels)
|
|
101
|
+
relationships.push(...prevRels);
|
|
102
|
+
// Also carry forward symbol-sourced relationships
|
|
103
|
+
if (prevSyms) {
|
|
104
|
+
for (const sym of prevSyms) {
|
|
105
|
+
const symRels = prevRelsBySource.get(sym.id);
|
|
106
|
+
if (symRels)
|
|
107
|
+
relationships.push(...symRels);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
skippedFiles++;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
const content = await readFile(absolutePath);
|
|
64
114
|
const text = content.toString('utf8');
|
|
65
115
|
const contentHash = crypto
|
|
66
116
|
.createHash('sha256')
|
|
@@ -79,7 +129,7 @@ export async function scanRepository(rootPath, config, previous) {
|
|
|
79
129
|
warnings: [],
|
|
80
130
|
};
|
|
81
131
|
if (isPreciseLanguage(repoPath, config)) {
|
|
82
|
-
const extracted = extractSymbols(text, repoPath);
|
|
132
|
+
const extracted = await extractSymbols(text, repoPath);
|
|
83
133
|
symbols.push(...extracted.symbols);
|
|
84
134
|
dependencies.push(...extracted.dependencies);
|
|
85
135
|
relationships.push(...extracted.relationships.filter((relationship) => relationship.relationshipType !== 'import'));
|
|
@@ -105,7 +155,14 @@ export async function scanRepository(rootPath, config, previous) {
|
|
|
105
155
|
resolveLocalDependencies(dependencies, files);
|
|
106
156
|
relationships.push(...buildImportRelationships(dependencies));
|
|
107
157
|
relationships.push(...detectMovedFiles(previous?.files ?? [], files));
|
|
108
|
-
return {
|
|
158
|
+
return {
|
|
159
|
+
files,
|
|
160
|
+
symbols,
|
|
161
|
+
dependencies,
|
|
162
|
+
relationships,
|
|
163
|
+
warnings,
|
|
164
|
+
skippedFiles,
|
|
165
|
+
};
|
|
109
166
|
}
|
|
110
167
|
const SOURCE_EXTENSIONS = [
|
|
111
168
|
'.ts',
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { SymbolExtractionResult } from './ts-symbol-extractor.js';
|
|
2
|
-
export declare function extractRustSymbols(sourceText: string, filePath: string): SymbolExtractionResult
|
|
2
|
+
export declare function extractRustSymbols(sourceText: string, filePath: string): Promise<SymbolExtractionResult>;
|