scai 0.1.116 → 0.1.117
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/dist/db/functionExtractors/extractFromJs.js +94 -65
- package/dist/db/functionExtractors/extractFromTs.js +44 -54
- package/dist/db/sqlTemplates.js +2 -2
- package/dist/fileRules/builtins.js +14 -0
- package/dist/modelSetup.js +45 -6
- package/dist/scripts/dbcheck.js +150 -0
- package/dist/utils/buildContextualPrompt.js +22 -14
- package/dist/utils/sharedUtils.js +8 -0
- package/package.json +1 -1
|
@@ -6,6 +6,8 @@ import { log } from '../../utils/log.js';
|
|
|
6
6
|
import { markFileAsSkippedTemplate, markFileAsExtractedTemplate, markFileAsFailedTemplate, insertFunctionTemplate, insertGraphClassTemplate, insertEdgeTemplate, insertGraphEntityTagTemplate, insertGraphTagTemplate, selectGraphTagIdTemplate, } from '../sqlTemplates.js';
|
|
7
7
|
import { getDbForRepo } from '../client.js';
|
|
8
8
|
import { kgModule } from '../../pipeline/modules/kgModule.js';
|
|
9
|
+
import { BUILTINS } from '../../fileRules/builtins.js';
|
|
10
|
+
import { getUniqueId } from '../../utils/sharedUtils.js';
|
|
9
11
|
function getFunctionName(node, parent, fileName) {
|
|
10
12
|
if (node.id?.name)
|
|
11
13
|
return node.id.name;
|
|
@@ -45,76 +47,75 @@ export async function extractFromJS(filePath, content, fileId) {
|
|
|
45
47
|
FunctionDeclaration(node, ancestors) {
|
|
46
48
|
const parent = ancestors[ancestors.length - 2];
|
|
47
49
|
const name = getFunctionName(node, parent, path.basename(filePath));
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
: `${path.basename(filePath)}:<anon>@${normalizedPath}:${node.loc?.start.line}`;
|
|
50
|
+
const funcContent = content.slice(node.start, node.end);
|
|
51
|
+
const unique_id = getUniqueId(name, filePath, node.loc?.start.line ?? -1, node.start, funcContent);
|
|
51
52
|
functions.push({
|
|
52
53
|
name,
|
|
53
54
|
start_line: node.loc?.start.line ?? -1,
|
|
54
55
|
end_line: node.loc?.end.line ?? -1,
|
|
55
|
-
content:
|
|
56
|
-
|
|
56
|
+
content: funcContent,
|
|
57
|
+
unique_id,
|
|
57
58
|
});
|
|
58
59
|
},
|
|
59
60
|
FunctionExpression(node, ancestors) {
|
|
60
61
|
const parent = ancestors[ancestors.length - 2];
|
|
61
62
|
const name = getFunctionName(node, parent, path.basename(filePath));
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
: `${path.basename(filePath)}:<anon>@${normalizedPath}:${node.loc?.start.line}`;
|
|
63
|
+
const funcContent = content.slice(node.start, node.end);
|
|
64
|
+
const unique_id = getUniqueId(name, filePath, node.loc?.start.line ?? -1, node.start, funcContent);
|
|
65
65
|
functions.push({
|
|
66
66
|
name,
|
|
67
67
|
start_line: node.loc?.start.line ?? -1,
|
|
68
68
|
end_line: node.loc?.end.line ?? -1,
|
|
69
|
-
content:
|
|
70
|
-
|
|
69
|
+
content: funcContent,
|
|
70
|
+
unique_id,
|
|
71
71
|
});
|
|
72
72
|
},
|
|
73
73
|
ArrowFunctionExpression(node, ancestors) {
|
|
74
74
|
const parent = ancestors[ancestors.length - 2];
|
|
75
75
|
const name = getFunctionName(node, parent, path.basename(filePath));
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
: `${path.basename(filePath)}:<anon>@${normalizedPath}:${node.loc?.start.line}`;
|
|
76
|
+
const funcContent = content.slice(node.start, node.end);
|
|
77
|
+
const unique_id = getUniqueId(name, filePath, node.loc?.start.line ?? -1, node.start, funcContent);
|
|
79
78
|
functions.push({
|
|
80
79
|
name,
|
|
81
80
|
start_line: node.loc?.start.line ?? -1,
|
|
82
81
|
end_line: node.loc?.end.line ?? -1,
|
|
83
|
-
content:
|
|
84
|
-
|
|
82
|
+
content: funcContent,
|
|
83
|
+
unique_id,
|
|
85
84
|
});
|
|
86
85
|
},
|
|
87
86
|
ClassDeclaration(node) {
|
|
88
|
-
const className = node.id?.name ||
|
|
89
|
-
const
|
|
87
|
+
const className = node.id?.name || `${path.basename(filePath)}:<anon-class>`;
|
|
88
|
+
const classContent = content.slice(node.start, node.end);
|
|
89
|
+
const unique_id = node.id?.name
|
|
90
90
|
? `${className}@${normalizedPath}`
|
|
91
|
-
:
|
|
91
|
+
: getUniqueId(className, filePath, node.loc?.start.line ?? -1, node.start, classContent);
|
|
92
92
|
classes.push({
|
|
93
93
|
name: className,
|
|
94
94
|
start_line: node.loc?.start.line ?? -1,
|
|
95
95
|
end_line: node.loc?.end.line ?? -1,
|
|
96
|
-
content:
|
|
96
|
+
content: classContent,
|
|
97
97
|
superClass: node.superClass?.name ?? null,
|
|
98
|
-
|
|
98
|
+
unique_id,
|
|
99
99
|
});
|
|
100
100
|
},
|
|
101
101
|
ClassExpression(node) {
|
|
102
|
-
const className = node.id?.name ||
|
|
103
|
-
const
|
|
102
|
+
const className = node.id?.name || `${path.basename(filePath)}:<anon-class>`;
|
|
103
|
+
const classContent = content.slice(node.start, node.end);
|
|
104
|
+
const unique_id = node.id?.name
|
|
104
105
|
? `${className}@${normalizedPath}`
|
|
105
|
-
:
|
|
106
|
+
: getUniqueId(className, filePath, node.loc?.start.line ?? -1, node.start, classContent);
|
|
106
107
|
classes.push({
|
|
107
108
|
name: className,
|
|
108
109
|
start_line: node.loc?.start.line ?? -1,
|
|
109
110
|
end_line: node.loc?.end.line ?? -1,
|
|
110
|
-
content:
|
|
111
|
+
content: classContent,
|
|
111
112
|
superClass: node.superClass?.name ?? null,
|
|
112
|
-
|
|
113
|
+
unique_id,
|
|
113
114
|
});
|
|
114
115
|
},
|
|
115
116
|
});
|
|
116
117
|
if (functions.length === 0 && classes.length === 0) {
|
|
117
|
-
log(`⚠️ No functions/classes found in: ${filePath}`);
|
|
118
|
+
log(`⚠️ No functions/classes found in JS file: ${filePath}`);
|
|
118
119
|
db.prepare(markFileAsSkippedTemplate).run({ id: fileId });
|
|
119
120
|
return false;
|
|
120
121
|
}
|
|
@@ -123,12 +124,12 @@ export async function extractFromJS(filePath, content, fileId) {
|
|
|
123
124
|
try {
|
|
124
125
|
const kgInput = { fileId, filepath: filePath, summary: undefined };
|
|
125
126
|
const kgResult = await kgModule.run(kgInput, content);
|
|
126
|
-
if (kgResult.entities?.length
|
|
127
|
+
if (kgResult.entities?.length) {
|
|
127
128
|
const insertTag = db.prepare(insertGraphTagTemplate);
|
|
128
129
|
const getTagId = db.prepare(selectGraphTagIdTemplate);
|
|
129
130
|
const insertEntityTag = db.prepare(insertGraphEntityTagTemplate);
|
|
130
131
|
for (const entity of kgResult.entities) {
|
|
131
|
-
if (!entity.type || !Array.isArray(entity.tags) || entity.tags.length
|
|
132
|
+
if (!entity.type || !Array.isArray(entity.tags) || !entity.tags.length)
|
|
132
133
|
continue;
|
|
133
134
|
for (const tag of entity.tags) {
|
|
134
135
|
if (!tag || typeof tag !== 'string')
|
|
@@ -138,8 +139,8 @@ export async function extractFromJS(filePath, content, fileId) {
|
|
|
138
139
|
const tagRow = getTagId.get({ name: tag });
|
|
139
140
|
if (!tagRow)
|
|
140
141
|
continue;
|
|
141
|
-
const matchedUniqueId = functions.find(f => f.name === entity.name)?.
|
|
142
|
-
classes.find(c => c.name === entity.name)?.
|
|
142
|
+
const matchedUniqueId = functions.find(f => f.name === entity.name)?.unique_id ||
|
|
143
|
+
classes.find(c => c.name === entity.name)?.unique_id ||
|
|
143
144
|
`${entity.name}@${filePath}`;
|
|
144
145
|
insertEntityTag.run({
|
|
145
146
|
entity_type: entity.type,
|
|
@@ -158,7 +159,8 @@ export async function extractFromJS(filePath, content, fileId) {
|
|
|
158
159
|
catch (kgErr) {
|
|
159
160
|
console.warn(`⚠️ KG tagging failed for ${filePath}:`, kgErr instanceof Error ? kgErr.message : kgErr);
|
|
160
161
|
}
|
|
161
|
-
// --- Insert functions + edges ---
|
|
162
|
+
// --- Insert functions + call edges ---
|
|
163
|
+
const seenEdges = new Set();
|
|
162
164
|
for (const fn of functions) {
|
|
163
165
|
try {
|
|
164
166
|
const embedding = await generateEmbedding(fn.content);
|
|
@@ -170,27 +172,44 @@ export async function extractFromJS(filePath, content, fileId) {
|
|
|
170
172
|
content: fn.content,
|
|
171
173
|
embedding: JSON.stringify(embedding),
|
|
172
174
|
lang: 'js',
|
|
173
|
-
unique_id: fn.
|
|
174
|
-
});
|
|
175
|
-
db.prepare(insertEdgeTemplate).run({
|
|
176
|
-
source_type: 'file',
|
|
177
|
-
source_unique_id: normalizedPath,
|
|
178
|
-
target_type: 'function',
|
|
179
|
-
target_unique_id: fn.uniqueId,
|
|
180
|
-
relation: 'contains',
|
|
175
|
+
unique_id: fn.unique_id,
|
|
181
176
|
});
|
|
177
|
+
// File -> Function 'contains' edge
|
|
178
|
+
const containsEdgeKey = `file->${fn.unique_id}`;
|
|
179
|
+
if (!seenEdges.has(containsEdgeKey)) {
|
|
180
|
+
db.prepare(insertEdgeTemplate).run({
|
|
181
|
+
source_type: 'file',
|
|
182
|
+
source_unique_id: normalizedPath,
|
|
183
|
+
target_type: 'function',
|
|
184
|
+
target_unique_id: fn.unique_id,
|
|
185
|
+
relation: 'contains',
|
|
186
|
+
});
|
|
187
|
+
seenEdges.add(containsEdgeKey);
|
|
188
|
+
}
|
|
189
|
+
// Call edges
|
|
182
190
|
const fnAst = parse(fn.content, { ecmaVersion: 'latest', sourceType: 'module', locations: true });
|
|
183
191
|
walkAncestor(fnAst, {
|
|
184
192
|
CallExpression(node) {
|
|
185
|
-
const calleeName = node.callee?.name
|
|
193
|
+
const calleeName = node.callee?.name;
|
|
194
|
+
if (!calleeName || BUILTINS.has(calleeName))
|
|
195
|
+
return;
|
|
186
196
|
const targetUniqueId = `${calleeName}@${normalizedPath}`;
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
197
|
+
const edgeKey = `${fn.unique_id}->${targetUniqueId}`;
|
|
198
|
+
if (!seenEdges.has(edgeKey)) {
|
|
199
|
+
try {
|
|
200
|
+
db.prepare(insertEdgeTemplate).run({
|
|
201
|
+
source_type: 'function',
|
|
202
|
+
source_unique_id: fn.unique_id,
|
|
203
|
+
target_type: 'function',
|
|
204
|
+
target_unique_id: targetUniqueId,
|
|
205
|
+
relation: 'calls',
|
|
206
|
+
});
|
|
207
|
+
seenEdges.add(edgeKey);
|
|
208
|
+
}
|
|
209
|
+
catch (err) {
|
|
210
|
+
console.error('❌ Failed to persist call edge', { fn: fn.name, calleeName, error: err });
|
|
211
|
+
}
|
|
212
|
+
}
|
|
194
213
|
},
|
|
195
214
|
});
|
|
196
215
|
log(`📌 Indexed JS function: ${fn.name}`);
|
|
@@ -211,19 +230,19 @@ export async function extractFromJS(filePath, content, fileId) {
|
|
|
211
230
|
content: cls.content,
|
|
212
231
|
embedding: JSON.stringify(embedding),
|
|
213
232
|
lang: 'js',
|
|
214
|
-
unique_id: cls.
|
|
233
|
+
unique_id: cls.unique_id,
|
|
215
234
|
});
|
|
216
235
|
db.prepare(insertEdgeTemplate).run({
|
|
217
236
|
source_type: 'file',
|
|
218
237
|
source_unique_id: normalizedPath,
|
|
219
238
|
target_type: 'class',
|
|
220
|
-
target_unique_id: cls.
|
|
239
|
+
target_unique_id: cls.unique_id,
|
|
221
240
|
relation: 'contains',
|
|
222
241
|
});
|
|
223
242
|
if (cls.superClass) {
|
|
224
243
|
db.prepare(insertEdgeTemplate).run({
|
|
225
244
|
source_type: 'class',
|
|
226
|
-
source_unique_id: cls.
|
|
245
|
+
source_unique_id: cls.unique_id,
|
|
227
246
|
target_type: `unresolved:${cls.superClass}`,
|
|
228
247
|
relation: 'inherits',
|
|
229
248
|
});
|
|
@@ -237,22 +256,32 @@ export async function extractFromJS(filePath, content, fileId) {
|
|
|
237
256
|
}
|
|
238
257
|
// --- Imports / Exports edges ---
|
|
239
258
|
for (const imp of imports) {
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
259
|
+
try {
|
|
260
|
+
db.prepare(insertEdgeTemplate).run({
|
|
261
|
+
source_type: 'file',
|
|
262
|
+
source_unique_id: normalizedPath,
|
|
263
|
+
target_type: 'file',
|
|
264
|
+
target_unique_id: `file@${imp}`,
|
|
265
|
+
relation: 'imports',
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
catch (err) {
|
|
269
|
+
console.error('❌ Failed to persist import edge', { imp, error: err });
|
|
270
|
+
}
|
|
247
271
|
}
|
|
248
272
|
for (const exp of exports) {
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
273
|
+
try {
|
|
274
|
+
db.prepare(insertEdgeTemplate).run({
|
|
275
|
+
source_type: 'file',
|
|
276
|
+
source_unique_id: normalizedPath,
|
|
277
|
+
target_type: 'file',
|
|
278
|
+
target_unique_id: `file@${exp}`,
|
|
279
|
+
relation: 'exports',
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
catch (err) {
|
|
283
|
+
console.error('❌ Failed to persist export edge', { exp, error: err });
|
|
284
|
+
}
|
|
256
285
|
}
|
|
257
286
|
log(`📊 Extraction summary for ${filePath}: ${functions.length} functions, ${classes.length} classes, ${imports.length} imports, ${exports.length} exports`);
|
|
258
287
|
db.prepare(markFileAsExtractedTemplate).run({ id: fileId });
|
|
@@ -260,7 +289,7 @@ export async function extractFromJS(filePath, content, fileId) {
|
|
|
260
289
|
return true;
|
|
261
290
|
}
|
|
262
291
|
catch (err) {
|
|
263
|
-
log(`❌ Failed to extract from: ${filePath}`);
|
|
292
|
+
log(`❌ Failed to extract from JS file: ${filePath}`);
|
|
264
293
|
log(` ↳ ${err.message}`);
|
|
265
294
|
db.prepare(markFileAsFailedTemplate).run({ id: fileId });
|
|
266
295
|
return false;
|
|
@@ -5,6 +5,8 @@ import { log } from '../../utils/log.js';
|
|
|
5
5
|
import { getDbForRepo } from '../client.js';
|
|
6
6
|
import { markFileAsSkippedTemplate, markFileAsExtractedTemplate, markFileAsFailedTemplate, insertFunctionTemplate, insertGraphClassTemplate, insertEdgeTemplate, insertGraphEntityTagTemplate, insertGraphTagTemplate, selectGraphTagIdTemplate, } from '../sqlTemplates.js';
|
|
7
7
|
import { kgModule } from '../../pipeline/modules/kgModule.js';
|
|
8
|
+
import { BUILTINS } from '../../fileRules/builtins.js';
|
|
9
|
+
import { getUniqueId } from '../../utils/sharedUtils.js';
|
|
8
10
|
export async function extractFromTS(filePath, content, fileId) {
|
|
9
11
|
const db = getDbForRepo();
|
|
10
12
|
const normalizedPath = path.normalize(filePath).replace(/\\/g, '/');
|
|
@@ -14,7 +16,7 @@ export async function extractFromTS(filePath, content, fileId) {
|
|
|
14
16
|
// DTO arrays used to store into functions / graph_classes later (FTS index)
|
|
15
17
|
const functions = [];
|
|
16
18
|
const classes = [];
|
|
17
|
-
// --- Gather all function AST nodes
|
|
19
|
+
// --- Gather all function AST nodes ---
|
|
18
20
|
const allFuncNodes = [
|
|
19
21
|
...sourceFile.getDescendantsOfKind(SyntaxKind.FunctionDeclaration),
|
|
20
22
|
...sourceFile.getDescendantsOfKind(SyntaxKind.FunctionExpression),
|
|
@@ -25,26 +27,20 @@ export async function extractFromTS(filePath, content, fileId) {
|
|
|
25
27
|
...sourceFile.getDescendantsOfKind(SyntaxKind.ClassDeclaration),
|
|
26
28
|
...sourceFile.getDescendantsOfKind(SyntaxKind.ClassExpression),
|
|
27
29
|
];
|
|
28
|
-
// If nothing found, mark as skipped
|
|
29
30
|
if (allFuncNodes.length === 0 && allClassNodes.length === 0) {
|
|
30
31
|
log(`⚠️ No functions/classes found in TS file: ${filePath}`);
|
|
31
32
|
db.prepare(markFileAsSkippedTemplate).run({ id: fileId });
|
|
32
33
|
return false;
|
|
33
34
|
}
|
|
34
35
|
log(`🔍 Found ${allFuncNodes.length} functions and ${allClassNodes.length} classes in ${filePath}`);
|
|
35
|
-
// ---
|
|
36
|
+
// --- LLM tagging ---
|
|
36
37
|
try {
|
|
37
|
-
const kgInput = {
|
|
38
|
-
fileId,
|
|
39
|
-
filepath: filePath,
|
|
40
|
-
summary: undefined,
|
|
41
|
-
};
|
|
38
|
+
const kgInput = { fileId, filepath: filePath, summary: undefined };
|
|
42
39
|
const kgResult = await kgModule.run(kgInput, content);
|
|
43
40
|
if (kgResult.entities?.length > 0) {
|
|
44
41
|
const insertTag = db.prepare(insertGraphTagTemplate);
|
|
45
42
|
const getTagId = db.prepare(selectGraphTagIdTemplate);
|
|
46
43
|
const insertEntityTag = db.prepare(insertGraphEntityTagTemplate);
|
|
47
|
-
// We'll map entity names from the LLM to unique ids if we can (functions/classes); fallback to name@file
|
|
48
44
|
for (const entity of kgResult.entities) {
|
|
49
45
|
if (!entity.type || !Array.isArray(entity.tags) || entity.tags.length === 0)
|
|
50
46
|
continue;
|
|
@@ -56,9 +52,8 @@ export async function extractFromTS(filePath, content, fileId) {
|
|
|
56
52
|
const tagRow = getTagId.get({ name: tag });
|
|
57
53
|
if (!tagRow)
|
|
58
54
|
continue;
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
classes.find(c => c.name === entity.name)?.uniqueId ||
|
|
55
|
+
const matchedUniqueId = functions.find(f => f.name === entity.name)?.unique_id ||
|
|
56
|
+
classes.find(c => c.name === entity.name)?.unique_id ||
|
|
62
57
|
`${entity.name}@${filePath}`;
|
|
63
58
|
insertEntityTag.run({
|
|
64
59
|
entity_type: entity.type,
|
|
@@ -75,25 +70,17 @@ export async function extractFromTS(filePath, content, fileId) {
|
|
|
75
70
|
}
|
|
76
71
|
}
|
|
77
72
|
catch (kgErr) {
|
|
78
|
-
// tagging failure should not stop extraction; log and continue
|
|
79
73
|
console.warn(`⚠️ KG tagging failed for ${filePath}:`, kgErr instanceof Error ? kgErr.message : kgErr);
|
|
80
74
|
}
|
|
81
|
-
// --- Process functions
|
|
82
|
-
// inside the functions loop
|
|
75
|
+
// --- Process functions ---
|
|
83
76
|
for (const funcNode of allFuncNodes) {
|
|
84
|
-
// Resolve a reasonable function name
|
|
85
77
|
const symbolName = funcNode.getSymbol()?.getName();
|
|
86
78
|
const name = symbolName ?? '<anon>';
|
|
87
79
|
const start = funcNode.getStartLineNumber();
|
|
88
80
|
const end = funcNode.getEndLineNumber();
|
|
89
81
|
const code = funcNode.getText();
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
? `${symbolName}@${normalizedPath}`
|
|
93
|
-
: `${path.basename(filePath)}:<anon>@${normalizedPath}:${start}`;
|
|
94
|
-
// push DTO for later insertion into functions table
|
|
95
|
-
functions.push({ name, start_line: start, end_line: end, content: code, uniqueId });
|
|
96
|
-
// Persist function row
|
|
82
|
+
const unique_id = getUniqueId(name, filePath, start, funcNode.getStart(), code);
|
|
83
|
+
functions.push({ name, start_line: start, end_line: end, content: code, unique_id });
|
|
97
84
|
try {
|
|
98
85
|
const embedding = await generateEmbedding(code);
|
|
99
86
|
db.prepare(insertFunctionTemplate).run({
|
|
@@ -104,7 +91,7 @@ export async function extractFromTS(filePath, content, fileId) {
|
|
|
104
91
|
content: code,
|
|
105
92
|
embedding: JSON.stringify(embedding),
|
|
106
93
|
lang: 'ts',
|
|
107
|
-
unique_id
|
|
94
|
+
unique_id,
|
|
108
95
|
});
|
|
109
96
|
}
|
|
110
97
|
catch (err) {
|
|
@@ -116,16 +103,17 @@ export async function extractFromTS(filePath, content, fileId) {
|
|
|
116
103
|
source_type: 'file',
|
|
117
104
|
source_unique_id: normalizedPath,
|
|
118
105
|
target_type: 'function',
|
|
119
|
-
target_unique_id:
|
|
106
|
+
target_unique_id: unique_id,
|
|
120
107
|
relation: 'contains',
|
|
121
108
|
});
|
|
122
109
|
}
|
|
123
110
|
catch (err) {
|
|
124
111
|
console.error('❌ Failed to persist file->function edge', { filePath, name, error: err });
|
|
125
112
|
}
|
|
126
|
-
//
|
|
113
|
+
// AST-based calls
|
|
127
114
|
try {
|
|
128
115
|
const callExprs = funcNode.getDescendantsOfKind(SyntaxKind.CallExpression);
|
|
116
|
+
const edgeSet = new Set();
|
|
129
117
|
for (const callExpr of callExprs) {
|
|
130
118
|
let calleeName;
|
|
131
119
|
try {
|
|
@@ -135,36 +123,41 @@ export async function extractFromTS(filePath, content, fileId) {
|
|
|
135
123
|
catch {
|
|
136
124
|
calleeName = undefined;
|
|
137
125
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
126
|
+
if (!calleeName || BUILTINS.has(calleeName))
|
|
127
|
+
continue;
|
|
128
|
+
const targetUniqueId = `${calleeName}@${normalizedPath}`;
|
|
129
|
+
const edgeKey = `${unique_id}->${targetUniqueId}`;
|
|
130
|
+
if (!edgeSet.has(edgeKey)) {
|
|
131
|
+
edgeSet.add(edgeKey);
|
|
132
|
+
try {
|
|
133
|
+
db.prepare(insertEdgeTemplate).run({
|
|
134
|
+
source_type: 'function',
|
|
135
|
+
source_unique_id: unique_id,
|
|
136
|
+
target_type: 'function',
|
|
137
|
+
target_unique_id: targetUniqueId,
|
|
138
|
+
relation: 'calls',
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
catch (err) {
|
|
142
|
+
console.error('❌ Failed to persist call edge', { filePath, name, calleeName, error: err });
|
|
143
|
+
}
|
|
150
144
|
}
|
|
151
145
|
}
|
|
152
146
|
}
|
|
153
147
|
catch (err) {
|
|
154
148
|
console.warn(`⚠️ Failed to inspect call expressions for function ${name} in ${filePath}:`, err);
|
|
155
149
|
}
|
|
156
|
-
log(`📌 Indexed TS function: ${name} (
|
|
150
|
+
log(`📌 Indexed TS function: ${name} (unique_id=${unique_id})`);
|
|
157
151
|
}
|
|
158
|
-
// --- Process classes
|
|
152
|
+
// --- Process classes ---
|
|
159
153
|
for (const clsNode of allClassNodes) {
|
|
160
154
|
const name = clsNode.getName() ?? `${path.basename(filePath)}:<anon-class>`;
|
|
161
155
|
const start = clsNode.getStartLineNumber();
|
|
162
156
|
const end = clsNode.getEndLineNumber();
|
|
163
157
|
const code = clsNode.getText();
|
|
164
158
|
const superClass = clsNode.getExtends()?.getText() ?? null;
|
|
165
|
-
const
|
|
166
|
-
classes.push({ name, start_line: start, end_line: end, content: code, superClass,
|
|
167
|
-
// persist class row
|
|
159
|
+
const unique_id = getUniqueId(name, filePath, start, clsNode.getStart(), code);
|
|
160
|
+
classes.push({ name, start_line: start, end_line: end, content: code, superClass, unique_id });
|
|
168
161
|
try {
|
|
169
162
|
const embedding = await generateEmbedding(code);
|
|
170
163
|
db.prepare(insertGraphClassTemplate).run({
|
|
@@ -175,31 +168,29 @@ export async function extractFromTS(filePath, content, fileId) {
|
|
|
175
168
|
content: code,
|
|
176
169
|
embedding: JSON.stringify(embedding),
|
|
177
170
|
lang: 'ts',
|
|
178
|
-
unique_id
|
|
171
|
+
unique_id,
|
|
179
172
|
});
|
|
180
173
|
}
|
|
181
174
|
catch (err) {
|
|
182
175
|
console.error('❌ Failed to insert graph class row', { filePath, name, error: err });
|
|
183
176
|
}
|
|
184
|
-
// File -> Class 'contains' edge
|
|
185
177
|
try {
|
|
186
178
|
db.prepare(insertEdgeTemplate).run({
|
|
187
179
|
source_type: 'file',
|
|
188
180
|
source_unique_id: normalizedPath,
|
|
189
181
|
target_type: 'class',
|
|
190
|
-
target_unique_id:
|
|
182
|
+
target_unique_id: unique_id,
|
|
191
183
|
relation: 'contains',
|
|
192
184
|
});
|
|
193
185
|
}
|
|
194
186
|
catch (err) {
|
|
195
187
|
console.error('❌ Failed to persist file->class edge', { filePath, name, error: err });
|
|
196
188
|
}
|
|
197
|
-
// Inheritance edge (may be unresolved if superclass is in another file)
|
|
198
189
|
if (superClass) {
|
|
199
190
|
try {
|
|
200
191
|
db.prepare(insertEdgeTemplate).run({
|
|
201
192
|
source_type: 'class',
|
|
202
|
-
source_unique_id:
|
|
193
|
+
source_unique_id: unique_id,
|
|
203
194
|
target_type: 'class',
|
|
204
195
|
target_unique_id: `unresolved:${superClass}`,
|
|
205
196
|
relation: 'inherits',
|
|
@@ -210,16 +201,16 @@ export async function extractFromTS(filePath, content, fileId) {
|
|
|
210
201
|
}
|
|
211
202
|
log(`🔗 Class ${name} extends ${superClass} (edge stored for later resolution)`);
|
|
212
203
|
}
|
|
213
|
-
log(`🏷 Indexed TS class: ${name} (unique_id=${
|
|
214
|
-
}
|
|
215
|
-
// ---
|
|
204
|
+
log(`🏷 Indexed TS class: ${name} (unique_id=${unique_id})`);
|
|
205
|
+
}
|
|
206
|
+
// --- Imports ---
|
|
216
207
|
try {
|
|
217
208
|
const importDecls = sourceFile.getDescendantsOfKind(SyntaxKind.ImportDeclaration);
|
|
218
209
|
for (const imp of importDecls) {
|
|
219
210
|
const moduleSpecifier = imp.getModuleSpecifierValue();
|
|
220
211
|
if (!moduleSpecifier)
|
|
221
212
|
continue;
|
|
222
|
-
const targetUniqueId = `file@${moduleSpecifier}`;
|
|
213
|
+
const targetUniqueId = `file@${moduleSpecifier}`;
|
|
223
214
|
try {
|
|
224
215
|
db.prepare(insertEdgeTemplate).run({
|
|
225
216
|
source_type: 'file',
|
|
@@ -237,7 +228,7 @@ export async function extractFromTS(filePath, content, fileId) {
|
|
|
237
228
|
catch (err) {
|
|
238
229
|
console.warn(`⚠️ Import extraction failed for ${filePath}:`, err);
|
|
239
230
|
}
|
|
240
|
-
// ---
|
|
231
|
+
// --- Exports ---
|
|
241
232
|
try {
|
|
242
233
|
const exportDecls = sourceFile.getDescendantsOfKind(SyntaxKind.ExportDeclaration);
|
|
243
234
|
for (const exp of exportDecls) {
|
|
@@ -262,7 +253,6 @@ export async function extractFromTS(filePath, content, fileId) {
|
|
|
262
253
|
catch (err) {
|
|
263
254
|
console.warn(`⚠️ Export extraction failed for ${filePath}:`, err);
|
|
264
255
|
}
|
|
265
|
-
// --- Mark file as extracted and finish ---
|
|
266
256
|
db.prepare(markFileAsExtractedTemplate).run({ id: fileId });
|
|
267
257
|
log(`📊 Extraction summary for ${filePath}: ${functions.length} functions, ${classes.length} classes`);
|
|
268
258
|
log(`✅ Marked TS functions/classes as extracted for ${filePath}`);
|
package/dist/db/sqlTemplates.js
CHANGED
|
@@ -44,8 +44,8 @@ export const searchFilesTemplate = `
|
|
|
44
44
|
`;
|
|
45
45
|
// --- Functions ---
|
|
46
46
|
export const insertFunctionTemplate = `
|
|
47
|
-
INSERT INTO functions (file_id, name, start_line, end_line, content, embedding, lang)
|
|
48
|
-
VALUES (:file_id, :name, :start_line, :end_line, :content, :embedding, :lang)
|
|
47
|
+
INSERT INTO functions (file_id, name, start_line, end_line, content, embedding, lang, unique_id)
|
|
48
|
+
VALUES (:file_id, :name, :start_line, :end_line, :content, :embedding, :lang, :unique_id)
|
|
49
49
|
`;
|
|
50
50
|
// --- Graph ---
|
|
51
51
|
export const insertGraphClassTemplate = `
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export const BUILTINS = new Set([
|
|
2
|
+
// String methods
|
|
3
|
+
"trim", "toLowerCase", "toUpperCase", "includes", "startsWith", "endsWith", "replace", "match", "split",
|
|
4
|
+
// Array methods
|
|
5
|
+
"map", "forEach", "filter", "reduce", "some", "every", "find", "findIndex", "push", "pop", "shift", "unshift", "slice", "splice",
|
|
6
|
+
// Object methods
|
|
7
|
+
"keys", "values", "entries", "assign",
|
|
8
|
+
// Number/Math
|
|
9
|
+
"parseInt", "parseFloat", "isNaN", "isFinite", "floor", "ceil", "round", "abs", "max", "min", "random",
|
|
10
|
+
// JSON / Promise / RegExp
|
|
11
|
+
"stringify", "parse", "then", "catch", "finally", "test",
|
|
12
|
+
// Console
|
|
13
|
+
"log", "error", "warn", "info",
|
|
14
|
+
]);
|
package/dist/modelSetup.js
CHANGED
|
@@ -11,6 +11,7 @@ import { CONFIG_PATH } from './constants.js';
|
|
|
11
11
|
const MODEL_PORT = 11434;
|
|
12
12
|
const REQUIRED_MODELS = ['llama3:8b'];
|
|
13
13
|
const OLLAMA_URL = 'https://ollama.com/download';
|
|
14
|
+
const VSCODE_URL = 'https://code.visualstudio.com/download';
|
|
14
15
|
const isYesMode = process.argv.includes('--yes') || process.env.SCAI_YES === '1';
|
|
15
16
|
let ollamaChecked = false;
|
|
16
17
|
let ollamaAvailable = false;
|
|
@@ -38,7 +39,7 @@ function promptUser(question, timeout = 20000) {
|
|
|
38
39
|
return new Promise((resolve) => {
|
|
39
40
|
const timer = setTimeout(() => {
|
|
40
41
|
rl.close();
|
|
41
|
-
resolve('');
|
|
42
|
+
resolve('');
|
|
42
43
|
}, timeout);
|
|
43
44
|
rl.question(question, (answer) => {
|
|
44
45
|
clearTimeout(timer);
|
|
@@ -89,7 +90,7 @@ async function ensureOllamaRunning() {
|
|
|
89
90
|
windowsHide: true,
|
|
90
91
|
});
|
|
91
92
|
child.unref();
|
|
92
|
-
await new Promise((res) => setTimeout(res, 10000));
|
|
93
|
+
await new Promise((res) => setTimeout(res, 10000));
|
|
93
94
|
if (await isOllamaRunning()) {
|
|
94
95
|
console.log(chalk.green('✅ Ollama started successfully.'));
|
|
95
96
|
ollamaAvailable = true;
|
|
@@ -102,13 +103,11 @@ async function ensureOllamaRunning() {
|
|
|
102
103
|
process.exit(1);
|
|
103
104
|
}
|
|
104
105
|
}
|
|
105
|
-
// Ollama not detected; prompt user but allow continuing
|
|
106
106
|
console.log(chalk.red('❌ Ollama is not installed or not in PATH.'));
|
|
107
107
|
console.log(chalk.yellow(`📦 Ollama is required to run local AI models.`));
|
|
108
108
|
const answer = await promptUser(`🌐 Recommended model: ${REQUIRED_MODELS.join(', ')}\nOpen download page in browser? (y/N): `);
|
|
109
|
-
if (answer.toLowerCase() === 'y')
|
|
109
|
+
if (answer.toLowerCase() === 'y')
|
|
110
110
|
openBrowser(OLLAMA_URL);
|
|
111
|
-
}
|
|
112
111
|
await promptUser('⏳ Press Enter once Ollama is installed or to continue without it: ');
|
|
113
112
|
if (await isOllamaRunning()) {
|
|
114
113
|
console.log(chalk.green('✅ Ollama detected. Continuing...'));
|
|
@@ -116,7 +115,7 @@ async function ensureOllamaRunning() {
|
|
|
116
115
|
}
|
|
117
116
|
else {
|
|
118
117
|
console.log(chalk.yellow('⚠️ Ollama not running. Models will not be available until installed.'));
|
|
119
|
-
ollamaAvailable = false;
|
|
118
|
+
ollamaAvailable = false;
|
|
120
119
|
}
|
|
121
120
|
}
|
|
122
121
|
// 🧰 List installed models
|
|
@@ -159,9 +158,49 @@ async function ensureModelsDownloaded() {
|
|
|
159
158
|
}
|
|
160
159
|
}
|
|
161
160
|
}
|
|
161
|
+
// 🌟 Check if VSCode CLI is available
|
|
162
|
+
async function isVSCodeAvailable() {
|
|
163
|
+
try {
|
|
164
|
+
execSync('code --version', { stdio: 'ignore' });
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
// ⚡ Ensure VSCode CLI is installed
|
|
172
|
+
async function ensureVSCodeInstalled() {
|
|
173
|
+
if (await isVSCodeAvailable()) {
|
|
174
|
+
console.log(chalk.green('✅ VSCode CLI is available.'));
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
console.log(chalk.red('❌ VSCode CLI not found.'));
|
|
178
|
+
const answer = await promptUser('Do you want to open the VSCode download page? (y/N): ');
|
|
179
|
+
if (answer.toLowerCase() === 'y')
|
|
180
|
+
openBrowser(VSCODE_URL);
|
|
181
|
+
await promptUser('VSCode CLI was not found. If you want to use VSCode features, please install it manually. ' +
|
|
182
|
+
'Once installed, press Enter to continue. If you prefer to skip VSCode, just press Enter to continue without it: ');
|
|
183
|
+
if (await isVSCodeAvailable()) {
|
|
184
|
+
console.log(chalk.green('✅ VSCode CLI detected. Continuing...'));
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
console.log(chalk.yellow('⚠️ VSCode CLI still not found. Some features may be disabled.'));
|
|
188
|
+
}
|
|
189
|
+
}
|
|
162
190
|
// 🏁 Main bootstrap logic
|
|
163
191
|
export async function bootstrap() {
|
|
164
192
|
await autoInitIfNeeded();
|
|
165
193
|
await ensureOllamaRunning();
|
|
166
194
|
await ensureModelsDownloaded();
|
|
195
|
+
await ensureVSCodeInstalled();
|
|
196
|
+
}
|
|
197
|
+
// 🔗 Helper: open file or diff in VSCode
|
|
198
|
+
export function openInVSCode(filePath, options) {
|
|
199
|
+
const args = options?.diffWith ? ['--diff', options.diffWith, filePath] : [filePath];
|
|
200
|
+
try {
|
|
201
|
+
spawn('code', args, { stdio: 'inherit' });
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
console.log(chalk.red('❌ Failed to launch VSCode CLI. Make sure it is installed and in PATH.'));
|
|
205
|
+
}
|
|
167
206
|
}
|
package/dist/scripts/dbcheck.js
CHANGED
|
@@ -116,4 +116,154 @@ const sampleEntityTags = db.prepare(`
|
|
|
116
116
|
sampleEntityTags.forEach(et => {
|
|
117
117
|
console.log(` [${et.id}] ${et.entity_type}(${et.entity_id}) -> tag ${et.tag_id}`);
|
|
118
118
|
});
|
|
119
|
+
// === Function → Edges Check ===
|
|
120
|
+
console.log("\n🕸 Function Calls / CalledBy consistency");
|
|
121
|
+
// 1. Check for edges pointing to missing functions
|
|
122
|
+
const missingFuncs = db.prepare(`
|
|
123
|
+
SELECT e.id, e.source_unique_id, e.target_unique_id
|
|
124
|
+
FROM graph_edges e
|
|
125
|
+
WHERE e.source_type = 'function'
|
|
126
|
+
AND NOT EXISTS (SELECT 1 FROM functions f WHERE f.unique_id = e.source_unique_id)
|
|
127
|
+
UNION
|
|
128
|
+
SELECT e.id, e.source_unique_id, e.target_unique_id
|
|
129
|
+
FROM graph_edges e
|
|
130
|
+
WHERE e.target_type = 'function'
|
|
131
|
+
AND NOT EXISTS (SELECT 1 FROM functions f WHERE f.unique_id = e.target_unique_id)
|
|
132
|
+
LIMIT 10
|
|
133
|
+
`).all();
|
|
134
|
+
if (missingFuncs.length === 0) {
|
|
135
|
+
console.log("✅ All edges reference valid functions.");
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
console.log("❌ Found edges pointing to missing functions:");
|
|
139
|
+
missingFuncs.forEach(e => {
|
|
140
|
+
console.log(` Edge ${e.id}: ${e.source_unique_id} -> ${e.target_unique_id}`);
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
// 2. Functions with outgoing calls
|
|
144
|
+
const funcWithCalls = db.prepare(`
|
|
145
|
+
SELECT f.id, f.name, COUNT(e.id) AS callCount
|
|
146
|
+
FROM functions f
|
|
147
|
+
JOIN graph_edges e
|
|
148
|
+
ON e.source_type = 'function'
|
|
149
|
+
AND e.source_unique_id = f.unique_id
|
|
150
|
+
GROUP BY f.id
|
|
151
|
+
HAVING callCount > 0
|
|
152
|
+
ORDER BY callCount DESC
|
|
153
|
+
LIMIT 5
|
|
154
|
+
`).all();
|
|
155
|
+
funcWithCalls.forEach(f => {
|
|
156
|
+
console.log(` 🔹 Function [${f.id}] ${f.name} has ${f.callCount} outgoing calls`);
|
|
157
|
+
});
|
|
158
|
+
// 3. Functions with incoming calls
|
|
159
|
+
const funcWithCalledBy = db.prepare(`
|
|
160
|
+
SELECT f.id, f.name, COUNT(e.id) AS calledByCount
|
|
161
|
+
FROM functions f
|
|
162
|
+
JOIN graph_edges e
|
|
163
|
+
ON e.target_type = 'function'
|
|
164
|
+
AND e.target_unique_id = f.unique_id
|
|
165
|
+
GROUP BY f.id
|
|
166
|
+
HAVING calledByCount > 0
|
|
167
|
+
ORDER BY calledByCount DESC
|
|
168
|
+
LIMIT 5
|
|
169
|
+
`).all();
|
|
170
|
+
funcWithCalledBy.forEach(f => {
|
|
171
|
+
console.log(` 🔸 Function [${f.id}] ${f.name} is called by ${f.calledByCount} functions`);
|
|
172
|
+
});
|
|
173
|
+
// 4. Check for duplicate edges (same source→target)
|
|
174
|
+
const duplicateEdges = db.prepare(`
|
|
175
|
+
SELECT source_unique_id, target_unique_id, COUNT(*) as dupCount
|
|
176
|
+
FROM graph_edges
|
|
177
|
+
WHERE source_type = 'function' AND target_type = 'function'
|
|
178
|
+
GROUP BY source_unique_id, target_unique_id
|
|
179
|
+
HAVING dupCount > 1
|
|
180
|
+
LIMIT 5
|
|
181
|
+
`).all();
|
|
182
|
+
if (duplicateEdges.length > 0) {
|
|
183
|
+
console.log("⚠️ Duplicate function call edges found:");
|
|
184
|
+
duplicateEdges.forEach(d => {
|
|
185
|
+
console.log(` ${d.source_unique_id} -> ${d.target_unique_id} (x${d.dupCount})`);
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
console.log("✅ No duplicate function call edges.");
|
|
190
|
+
}
|
|
191
|
+
// === File-specific check (AskCmd.ts) ===
|
|
192
|
+
const targetPath = "/Users/rzs/dev/repos/scai/cli/src/commands/AskCmd.ts";
|
|
193
|
+
// --- MUST KEEP: find the file row ---
|
|
194
|
+
const targetFile = db.prepare(`
|
|
195
|
+
SELECT id, path, filename, processing_status
|
|
196
|
+
FROM files
|
|
197
|
+
WHERE path = ?
|
|
198
|
+
`).get(targetPath);
|
|
199
|
+
if (!targetFile) {
|
|
200
|
+
console.log(`❌ File not found in DB: ${targetPath}`);
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
console.log(`\n📂 File found: [${targetFile.id}] ${targetFile.filename} (${targetFile.processing_status})`);
|
|
204
|
+
// --- MUST KEEP: list functions in this file ---
|
|
205
|
+
const funcs = db.prepare(`
|
|
206
|
+
SELECT id, name, unique_id, start_line, end_line
|
|
207
|
+
FROM functions
|
|
208
|
+
WHERE file_id = ?
|
|
209
|
+
ORDER BY start_line
|
|
210
|
+
`).all(targetFile.id);
|
|
211
|
+
console.log(` Functions (${funcs.length}):`);
|
|
212
|
+
funcs.forEach((f) => {
|
|
213
|
+
console.log(` [${f.id}] ${f.name} (${f.unique_id}) lines ${f.start_line}-${f.end_line}`);
|
|
214
|
+
});
|
|
215
|
+
// --- OPTIONAL: outgoing calls from this file’s functions ---
|
|
216
|
+
const outgoing = db.prepare(`
|
|
217
|
+
SELECT f.name AS source_name, e.target_unique_id, e.relation
|
|
218
|
+
FROM functions f
|
|
219
|
+
JOIN graph_edges e
|
|
220
|
+
ON e.source_unique_id = f.unique_id
|
|
221
|
+
AND e.source_type = 'function'
|
|
222
|
+
WHERE f.file_id = ?
|
|
223
|
+
`).all(targetFile.id);
|
|
224
|
+
console.log(` Outgoing calls (${outgoing.length}):`);
|
|
225
|
+
outgoing.forEach((o) => {
|
|
226
|
+
console.log(` ${o.source_name} -[${o.relation}]-> ${o.target_unique_id}`);
|
|
227
|
+
});
|
|
228
|
+
// --- OPTIONAL: incoming calls to this file’s functions ---
|
|
229
|
+
const incoming = db.prepare(`
|
|
230
|
+
SELECT f.name AS target_name, e.source_unique_id, e.relation
|
|
231
|
+
FROM functions f
|
|
232
|
+
JOIN graph_edges e
|
|
233
|
+
ON e.target_unique_id = f.unique_id
|
|
234
|
+
AND e.target_type = 'function'
|
|
235
|
+
WHERE f.file_id = ?
|
|
236
|
+
`).all(targetFile.id);
|
|
237
|
+
console.log(` Incoming calls (${incoming.length}):`);
|
|
238
|
+
incoming.forEach((i) => {
|
|
239
|
+
console.log(` ${i.source_unique_id} -[${i.relation}]-> ${i.target_name}`);
|
|
240
|
+
});
|
|
241
|
+
// --- NICE TO HAVE: dangling edges check ---
|
|
242
|
+
const dangling = db.prepare(`
|
|
243
|
+
SELECT e.id, e.source_unique_id, e.target_unique_id, e.relation
|
|
244
|
+
FROM graph_edges e
|
|
245
|
+
WHERE e.source_unique_id IN (SELECT unique_id FROM functions WHERE file_id = ?)
|
|
246
|
+
OR e.target_unique_id IN (SELECT unique_id FROM functions WHERE file_id = ?)
|
|
247
|
+
`).all(targetFile.id, targetFile.id);
|
|
248
|
+
console.log(` Edges referencing this file's functions (dangling check): ${dangling.length}`);
|
|
249
|
+
dangling.forEach((d) => {
|
|
250
|
+
console.log(` Edge ${d.id}: ${d.source_unique_id} -[${d.relation}]-> ${d.target_unique_id}`);
|
|
251
|
+
});
|
|
252
|
+
// --- OPTIONAL: consistency check summary ---
|
|
253
|
+
if (funcs.length > 0 && outgoing.length === 0 && incoming.length === 0 && dangling.length === 0) {
|
|
254
|
+
console.log("⚠️ This file has functions but no graph edges. Possible causes:");
|
|
255
|
+
console.log(" - Edges were never extracted for this file, OR");
|
|
256
|
+
console.log(" - unique_id mismatch between functions and edges.");
|
|
257
|
+
}
|
|
258
|
+
else {
|
|
259
|
+
console.log("✅ Edges and functions are consistent for this file.");
|
|
260
|
+
}
|
|
261
|
+
const funcsDebug = db.prepare(`
|
|
262
|
+
SELECT id, name, unique_id
|
|
263
|
+
FROM functions
|
|
264
|
+
WHERE file_id = ?
|
|
265
|
+
`).all(targetFile.id);
|
|
266
|
+
console.log("Funcsdebug:\n");
|
|
267
|
+
console.log(funcsDebug);
|
|
268
|
+
}
|
|
119
269
|
console.log("\n✅ DB check completed.\n");
|
|
@@ -106,20 +106,24 @@ export async function buildContextualPrompt({ topFile, query, kgDepth = 3, }) {
|
|
|
106
106
|
const kgTree = buildFileTree({ id: topFile.id, path: topFile.path, summary: topFile.summary }, kgDepth);
|
|
107
107
|
promptSections.push(`**KG-Related Files (JSON tree, depth ${kgDepth}):**\n\`\`\`json\n${JSON.stringify(kgTree, null, 2)}\n\`\`\``);
|
|
108
108
|
const functionCallsAll = db.prepare(`
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
109
|
+
SELECT source_unique_id, target_unique_id
|
|
110
|
+
FROM graph_edges
|
|
111
|
+
WHERE source_type = 'function' AND relation = 'calls'
|
|
112
|
+
AND source_unique_id IN (
|
|
113
|
+
SELECT unique_id FROM functions WHERE file_id = ?
|
|
114
|
+
)
|
|
115
|
+
`).all(topFile.id);
|
|
116
116
|
const callsByFunction = {};
|
|
117
117
|
for (const fn of functionRows) {
|
|
118
118
|
const rows = functionCallsAll
|
|
119
119
|
.filter(r => r.source_unique_id === fn.unique_id)
|
|
120
120
|
.slice(0, FUNCTION_LIMIT);
|
|
121
|
+
// Truncate function content for preview
|
|
122
|
+
const lines = fn.content?.split("\n").map(l => l.trim()).filter(Boolean) || ["[no content]"];
|
|
123
|
+
const preview = lines.slice(0, 3).map(l => l.slice(0, 200) + (l.length > 200 ? "…" : "")).join(" | ");
|
|
121
124
|
callsByFunction[fn.name || fn.unique_id] = {
|
|
122
125
|
calls: rows.map(r => ({ unique_id: r.target_unique_id })),
|
|
126
|
+
preview,
|
|
123
127
|
};
|
|
124
128
|
}
|
|
125
129
|
if (Object.keys(callsByFunction).length > 0) {
|
|
@@ -127,20 +131,24 @@ export async function buildContextualPrompt({ topFile, query, kgDepth = 3, }) {
|
|
|
127
131
|
}
|
|
128
132
|
// --- Function-level "called by" overview (limited) ---
|
|
129
133
|
const calledByAll = db.prepare(`
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
134
|
+
SELECT source_unique_id, target_unique_id
|
|
135
|
+
FROM graph_edges
|
|
136
|
+
WHERE target_type = 'function' AND relation = 'calls'
|
|
137
|
+
AND target_unique_id IN (
|
|
138
|
+
SELECT unique_id FROM functions WHERE file_id = ?
|
|
139
|
+
)
|
|
140
|
+
`).all(topFile.id);
|
|
137
141
|
const calledByByFunction = {};
|
|
138
142
|
for (const fn of functionRows) {
|
|
139
143
|
const rows = calledByAll
|
|
140
144
|
.filter(r => r.target_unique_id === fn.unique_id)
|
|
141
145
|
.slice(0, FUNCTION_LIMIT);
|
|
146
|
+
// Reuse truncated preview
|
|
147
|
+
const lines = fn.content?.split("\n").map(l => l.trim()).filter(Boolean) || ["[no content]"];
|
|
148
|
+
const preview = lines.slice(0, 3).map(l => l.slice(0, 200) + (l.length > 200 ? "…" : "")).join(" | ");
|
|
142
149
|
calledByByFunction[fn.name || fn.unique_id] = {
|
|
143
150
|
calledBy: rows.map(r => ({ unique_id: r.source_unique_id })),
|
|
151
|
+
preview,
|
|
144
152
|
};
|
|
145
153
|
}
|
|
146
154
|
if (Object.keys(calledByByFunction).length > 0) {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import crypto from 'crypto';
|
|
3
|
+
// put this helper at top-level (or import from shared utils)
|
|
4
|
+
export function getUniqueId(name, filePath, startLine, startColumn, content) {
|
|
5
|
+
const normalizedPath = path.normalize(filePath).replace(/\\/g, '/');
|
|
6
|
+
const hash = crypto.createHash('md5').update(content).digest('hex').slice(0, 6);
|
|
7
|
+
return `${name}@${normalizedPath}:${startLine}:${startColumn}:${hash}`;
|
|
8
|
+
}
|