scai 0.1.114 → 0.1.116
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/CHANGELOG.md +7 -1
- package/dist/commands/ResetDbCmd.js +1 -1
- package/dist/commands/ReviewCmd.js +19 -17
- package/dist/daemon/daemonBatch.js +51 -25
- package/dist/db/fileIndex.js +4 -20
- package/dist/db/functionExtractors/extractFromJs.js +169 -103
- package/dist/db/functionExtractors/extractFromTs.js +238 -90
- package/dist/db/schema.js +28 -30
- package/dist/db/sqlTemplates.js +67 -39
- package/dist/index.js +0 -7
- package/dist/pipeline/modules/cleanupModule.js +21 -1
- package/dist/scripts/dbcheck.js +83 -288
- package/dist/utils/buildContextualPrompt.js +108 -54
- package/package.json +1 -1
- package/dist/commands/MigrateCmd.js +0 -15
|
@@ -3,120 +3,268 @@ import path from 'path';
|
|
|
3
3
|
import { generateEmbedding } from '../../lib/generateEmbedding.js';
|
|
4
4
|
import { log } from '../../utils/log.js';
|
|
5
5
|
import { getDbForRepo } from '../client.js';
|
|
6
|
-
import { markFileAsSkippedTemplate, markFileAsExtractedTemplate, markFileAsFailedTemplate, } from '../sqlTemplates.js';
|
|
6
|
+
import { markFileAsSkippedTemplate, markFileAsExtractedTemplate, markFileAsFailedTemplate, insertFunctionTemplate, insertGraphClassTemplate, insertEdgeTemplate, insertGraphEntityTagTemplate, insertGraphTagTemplate, selectGraphTagIdTemplate, } from '../sqlTemplates.js';
|
|
7
|
+
import { kgModule } from '../../pipeline/modules/kgModule.js';
|
|
7
8
|
export async function extractFromTS(filePath, content, fileId) {
|
|
8
9
|
const db = getDbForRepo();
|
|
10
|
+
const normalizedPath = path.normalize(filePath).replace(/\\/g, '/');
|
|
9
11
|
try {
|
|
10
12
|
const project = new Project({ useInMemoryFileSystem: true });
|
|
11
13
|
const sourceFile = project.createSourceFile(filePath, content);
|
|
14
|
+
// DTO arrays used to store into functions / graph_classes later (FTS index)
|
|
12
15
|
const functions = [];
|
|
13
16
|
const classes = [];
|
|
14
|
-
|
|
17
|
+
// --- Gather all function AST nodes (so we can both index and analyze calls) ---
|
|
18
|
+
const allFuncNodes = [
|
|
15
19
|
...sourceFile.getDescendantsOfKind(SyntaxKind.FunctionDeclaration),
|
|
16
20
|
...sourceFile.getDescendantsOfKind(SyntaxKind.FunctionExpression),
|
|
17
21
|
...sourceFile.getDescendantsOfKind(SyntaxKind.ArrowFunction),
|
|
18
22
|
];
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const start = fn.getStartLineNumber();
|
|
22
|
-
const end = fn.getEndLineNumber();
|
|
23
|
-
const code = fn.getText();
|
|
24
|
-
functions.push({ name, start_line: start, end_line: end, content: code });
|
|
25
|
-
}
|
|
26
|
-
const allClasses = [
|
|
23
|
+
// --- Gather all class AST nodes ---
|
|
24
|
+
const allClassNodes = [
|
|
27
25
|
...sourceFile.getDescendantsOfKind(SyntaxKind.ClassDeclaration),
|
|
28
26
|
...sourceFile.getDescendantsOfKind(SyntaxKind.ClassExpression),
|
|
29
27
|
];
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const start = cls.getStartLineNumber();
|
|
33
|
-
const end = cls.getEndLineNumber();
|
|
34
|
-
const code = cls.getText();
|
|
35
|
-
const superClass = cls.getExtends()?.getText() ?? null;
|
|
36
|
-
classes.push({
|
|
37
|
-
name,
|
|
38
|
-
start_line: start,
|
|
39
|
-
end_line: end,
|
|
40
|
-
content: code,
|
|
41
|
-
superClass,
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
if (functions.length === 0 && classes.length === 0) {
|
|
28
|
+
// If nothing found, mark as skipped
|
|
29
|
+
if (allFuncNodes.length === 0 && allClassNodes.length === 0) {
|
|
45
30
|
log(`⚠️ No functions/classes found in TS file: ${filePath}`);
|
|
46
31
|
db.prepare(markFileAsSkippedTemplate).run({ id: fileId });
|
|
47
32
|
return false;
|
|
48
33
|
}
|
|
49
|
-
log(`🔍 Found ${
|
|
50
|
-
//
|
|
51
|
-
|
|
52
|
-
const
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
34
|
+
log(`🔍 Found ${allFuncNodes.length} functions and ${allClassNodes.length} classes in ${filePath}`);
|
|
35
|
+
// --- Optionally ask LLM (kgModule) for tags for the file, persist them and associate with entities if possible ---
|
|
36
|
+
try {
|
|
37
|
+
const kgInput = {
|
|
38
|
+
fileId,
|
|
39
|
+
filepath: filePath,
|
|
40
|
+
summary: undefined,
|
|
41
|
+
};
|
|
42
|
+
const kgResult = await kgModule.run(kgInput, content);
|
|
43
|
+
if (kgResult.entities?.length > 0) {
|
|
44
|
+
const insertTag = db.prepare(insertGraphTagTemplate);
|
|
45
|
+
const getTagId = db.prepare(selectGraphTagIdTemplate);
|
|
46
|
+
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
|
+
for (const entity of kgResult.entities) {
|
|
49
|
+
if (!entity.type || !Array.isArray(entity.tags) || entity.tags.length === 0)
|
|
50
|
+
continue;
|
|
51
|
+
for (const tag of entity.tags) {
|
|
52
|
+
if (!tag || typeof tag !== 'string')
|
|
53
|
+
continue;
|
|
54
|
+
try {
|
|
55
|
+
insertTag.run({ name: tag });
|
|
56
|
+
const tagRow = getTagId.get({ name: tag });
|
|
57
|
+
if (!tagRow)
|
|
58
|
+
continue;
|
|
59
|
+
// Try to match to an AST-extracted entity uniqueId, else fallback to name@filepath
|
|
60
|
+
const matchedUniqueId = functions.find(f => f.name === entity.name)?.uniqueId ||
|
|
61
|
+
classes.find(c => c.name === entity.name)?.uniqueId ||
|
|
62
|
+
`${entity.name}@${filePath}`;
|
|
63
|
+
insertEntityTag.run({
|
|
64
|
+
entity_type: entity.type,
|
|
65
|
+
entity_unique_id: matchedUniqueId,
|
|
66
|
+
tag_id: tagRow.id,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
console.error('❌ Failed to persist entity/tag', { entity, tag, error: err });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
log(`🏷 Persisted LLM-generated tags for ${filePath}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
catch (kgErr) {
|
|
78
|
+
// tagging failure should not stop extraction; log and continue
|
|
79
|
+
console.warn(`⚠️ KG tagging failed for ${filePath}:`, kgErr instanceof Error ? kgErr.message : kgErr);
|
|
80
|
+
}
|
|
81
|
+
// --- Process functions: build DTOs (for FTS) and emit call edges (KG) using AST nodes ---
|
|
82
|
+
// inside the functions loop
|
|
83
|
+
for (const funcNode of allFuncNodes) {
|
|
84
|
+
// Resolve a reasonable function name
|
|
85
|
+
const symbolName = funcNode.getSymbol()?.getName();
|
|
86
|
+
const name = symbolName ?? '<anon>';
|
|
87
|
+
const start = funcNode.getStartLineNumber();
|
|
88
|
+
const end = funcNode.getEndLineNumber();
|
|
89
|
+
const code = funcNode.getText();
|
|
90
|
+
// --- updated uniqueId: include line number to make anonymous functions unique ---
|
|
91
|
+
const uniqueId = symbolName
|
|
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
|
|
97
|
+
try {
|
|
98
|
+
const embedding = await generateEmbedding(code);
|
|
99
|
+
db.prepare(insertFunctionTemplate).run({
|
|
100
|
+
file_id: fileId,
|
|
101
|
+
name,
|
|
102
|
+
start_line: start,
|
|
103
|
+
end_line: end,
|
|
104
|
+
content: code,
|
|
105
|
+
embedding: JSON.stringify(embedding),
|
|
106
|
+
lang: 'ts',
|
|
107
|
+
unique_id: uniqueId,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
console.error('❌ Failed to insert function row', { filePath, name, error: err });
|
|
112
|
+
}
|
|
113
|
+
// File -> Function 'contains' edge
|
|
114
|
+
try {
|
|
115
|
+
db.prepare(insertEdgeTemplate).run({
|
|
116
|
+
source_type: 'file',
|
|
117
|
+
source_unique_id: normalizedPath,
|
|
118
|
+
target_type: 'function',
|
|
119
|
+
target_unique_id: uniqueId,
|
|
120
|
+
relation: 'contains',
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
catch (err) {
|
|
124
|
+
console.error('❌ Failed to persist file->function edge', { filePath, name, error: err });
|
|
125
|
+
}
|
|
126
|
+
// --- AST-based detection of calls inside the function ---
|
|
127
|
+
try {
|
|
128
|
+
const callExprs = funcNode.getDescendantsOfKind(SyntaxKind.CallExpression);
|
|
129
|
+
for (const callExpr of callExprs) {
|
|
130
|
+
let calleeName;
|
|
131
|
+
try {
|
|
132
|
+
const expr = callExpr.getExpression();
|
|
133
|
+
calleeName = expr.getSymbol()?.getName() ?? expr.getText();
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
calleeName = undefined;
|
|
137
|
+
}
|
|
138
|
+
const targetUniqueId = calleeName ? `${calleeName}@${normalizedPath}` : 'unresolved';
|
|
139
|
+
try {
|
|
140
|
+
db.prepare(insertEdgeTemplate).run({
|
|
141
|
+
source_type: 'function',
|
|
142
|
+
source_unique_id: uniqueId,
|
|
143
|
+
target_type: 'function',
|
|
144
|
+
target_unique_id: targetUniqueId,
|
|
145
|
+
relation: 'calls',
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
catch (err) {
|
|
149
|
+
console.error('❌ Failed to persist call edge', { filePath, name, calleeName, error: err });
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
catch (err) {
|
|
154
|
+
console.warn(`⚠️ Failed to inspect call expressions for function ${name} in ${filePath}:`, err);
|
|
155
|
+
}
|
|
156
|
+
log(`📌 Indexed TS function: ${name} (uniqueId=${uniqueId})`);
|
|
157
|
+
}
|
|
158
|
+
// --- Process classes (index + contains edge + inherits) ---
|
|
159
|
+
for (const clsNode of allClassNodes) {
|
|
160
|
+
const name = clsNode.getName() ?? `${path.basename(filePath)}:<anon-class>`;
|
|
161
|
+
const start = clsNode.getStartLineNumber();
|
|
162
|
+
const end = clsNode.getEndLineNumber();
|
|
163
|
+
const code = clsNode.getText();
|
|
164
|
+
const superClass = clsNode.getExtends()?.getText() ?? null;
|
|
165
|
+
const uniqueId = `${name}@${normalizedPath}`;
|
|
166
|
+
classes.push({ name, start_line: start, end_line: end, content: code, superClass, uniqueId });
|
|
167
|
+
// persist class row
|
|
168
|
+
try {
|
|
169
|
+
const embedding = await generateEmbedding(code);
|
|
170
|
+
db.prepare(insertGraphClassTemplate).run({
|
|
171
|
+
file_id: fileId,
|
|
172
|
+
name,
|
|
173
|
+
start_line: start,
|
|
174
|
+
end_line: end,
|
|
175
|
+
content: code,
|
|
176
|
+
embedding: JSON.stringify(embedding),
|
|
177
|
+
lang: 'ts',
|
|
178
|
+
unique_id: uniqueId,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
catch (err) {
|
|
182
|
+
console.error('❌ Failed to insert graph class row', { filePath, name, error: err });
|
|
183
|
+
}
|
|
184
|
+
// File -> Class 'contains' edge
|
|
185
|
+
try {
|
|
186
|
+
db.prepare(insertEdgeTemplate).run({
|
|
187
|
+
source_type: 'file',
|
|
188
|
+
source_unique_id: normalizedPath,
|
|
189
|
+
target_type: 'class',
|
|
190
|
+
target_unique_id: uniqueId,
|
|
191
|
+
relation: 'contains',
|
|
82
192
|
});
|
|
83
193
|
}
|
|
84
|
-
|
|
194
|
+
catch (err) {
|
|
195
|
+
console.error('❌ Failed to persist file->class edge', { filePath, name, error: err });
|
|
196
|
+
}
|
|
197
|
+
// Inheritance edge (may be unresolved if superclass is in another file)
|
|
198
|
+
if (superClass) {
|
|
199
|
+
try {
|
|
200
|
+
db.prepare(insertEdgeTemplate).run({
|
|
201
|
+
source_type: 'class',
|
|
202
|
+
source_unique_id: uniqueId,
|
|
203
|
+
target_type: 'class',
|
|
204
|
+
target_unique_id: `unresolved:${superClass}`,
|
|
205
|
+
relation: 'inherits',
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
catch (err) {
|
|
209
|
+
console.error('❌ Failed to persist inherits edge', { filePath, name, superClass, error: err });
|
|
210
|
+
}
|
|
211
|
+
log(`🔗 Class ${name} extends ${superClass} (edge stored for later resolution)`);
|
|
212
|
+
}
|
|
213
|
+
log(`🏷 Indexed TS class: ${name} (unique_id=${uniqueId})`);
|
|
214
|
+
} // end classes loop
|
|
215
|
+
// --- Handle imports (file-level) ---
|
|
216
|
+
try {
|
|
217
|
+
const importDecls = sourceFile.getDescendantsOfKind(SyntaxKind.ImportDeclaration);
|
|
218
|
+
for (const imp of importDecls) {
|
|
219
|
+
const moduleSpecifier = imp.getModuleSpecifierValue();
|
|
220
|
+
if (!moduleSpecifier)
|
|
221
|
+
continue;
|
|
222
|
+
const targetUniqueId = `file@${moduleSpecifier}`; // map to file unique id pattern
|
|
223
|
+
try {
|
|
224
|
+
db.prepare(insertEdgeTemplate).run({
|
|
225
|
+
source_type: 'file',
|
|
226
|
+
source_unique_id: normalizedPath,
|
|
227
|
+
target_type: 'file',
|
|
228
|
+
target_unique_id: targetUniqueId,
|
|
229
|
+
relation: 'imports',
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
catch (err) {
|
|
233
|
+
console.error('❌ Failed to persist import edge', { filePath, moduleSpecifier, error: err });
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
catch (err) {
|
|
238
|
+
console.warn(`⚠️ Import extraction failed for ${filePath}:`, err);
|
|
85
239
|
}
|
|
86
|
-
//
|
|
87
|
-
|
|
88
|
-
const
|
|
89
|
-
const
|
|
90
|
-
.
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
const classId = result.lastInsertRowid;
|
|
107
|
-
// file → class edge
|
|
108
|
-
db.prepare(`INSERT INTO edges (source_type, source_id, target_type, target_id, relation)
|
|
109
|
-
VALUES ('file', @source_id, 'class', @target_id, 'contains')`).run({ source_id: fileId, target_id: classId });
|
|
110
|
-
// superclass reference → store in helper table for later resolution
|
|
111
|
-
if (cls.superClass) {
|
|
112
|
-
db.prepare(`INSERT INTO class_inheritance (class_id, super_name)
|
|
113
|
-
VALUES (@class_id, @super_name)`).run({ class_id: classId, super_name: cls.superClass });
|
|
114
|
-
log(`🔗 Class ${cls.name} extends ${cls.superClass} (edge stored for later resolution)`);
|
|
240
|
+
// --- Handle exports (file-level) ---
|
|
241
|
+
try {
|
|
242
|
+
const exportDecls = sourceFile.getDescendantsOfKind(SyntaxKind.ExportDeclaration);
|
|
243
|
+
for (const exp of exportDecls) {
|
|
244
|
+
const moduleSpecifier = exp.getModuleSpecifierValue();
|
|
245
|
+
if (!moduleSpecifier)
|
|
246
|
+
continue;
|
|
247
|
+
const targetUniqueId = `file@${moduleSpecifier}`;
|
|
248
|
+
try {
|
|
249
|
+
db.prepare(insertEdgeTemplate).run({
|
|
250
|
+
source_type: 'file',
|
|
251
|
+
source_unique_id: normalizedPath,
|
|
252
|
+
target_type: 'file',
|
|
253
|
+
target_unique_id: targetUniqueId,
|
|
254
|
+
relation: 'exports',
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
catch (err) {
|
|
258
|
+
console.error('❌ Failed to persist export edge', { filePath, moduleSpecifier, error: err });
|
|
259
|
+
}
|
|
115
260
|
}
|
|
116
|
-
log(`🏷 Indexed TS class: ${cls.name} (id=${classId})`);
|
|
117
261
|
}
|
|
118
|
-
|
|
262
|
+
catch (err) {
|
|
263
|
+
console.warn(`⚠️ Export extraction failed for ${filePath}:`, err);
|
|
264
|
+
}
|
|
265
|
+
// --- Mark file as extracted and finish ---
|
|
119
266
|
db.prepare(markFileAsExtractedTemplate).run({ id: fileId });
|
|
267
|
+
log(`📊 Extraction summary for ${filePath}: ${functions.length} functions, ${classes.length} classes`);
|
|
120
268
|
log(`✅ Marked TS functions/classes as extracted for ${filePath}`);
|
|
121
269
|
return true;
|
|
122
270
|
}
|
package/dist/db/schema.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { getDbForRepo } from "./client.js";
|
|
2
2
|
export function initSchema() {
|
|
3
3
|
const db = getDbForRepo();
|
|
4
|
-
// ---
|
|
4
|
+
// --- Core tables ---
|
|
5
5
|
db.exec(`
|
|
6
6
|
CREATE TABLE IF NOT EXISTS files (
|
|
7
7
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
8
|
-
path TEXT UNIQUE,
|
|
8
|
+
path TEXT UNIQUE, -- full path
|
|
9
9
|
filename TEXT,
|
|
10
10
|
summary TEXT,
|
|
11
11
|
type TEXT,
|
|
@@ -24,6 +24,7 @@ export function initSchema() {
|
|
|
24
24
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
25
25
|
file_id INTEGER REFERENCES files(id),
|
|
26
26
|
name TEXT,
|
|
27
|
+
unique_id TEXT UNIQUE, -- e.g. "buildContextualPrompt@cli/src/utils/buildContextualPrompt.ts"
|
|
27
28
|
start_line INTEGER,
|
|
28
29
|
end_line INTEGER,
|
|
29
30
|
content TEXT,
|
|
@@ -31,20 +32,16 @@ export function initSchema() {
|
|
|
31
32
|
lang TEXT
|
|
32
33
|
);
|
|
33
34
|
|
|
34
|
-
CREATE INDEX IF NOT EXISTS
|
|
35
|
-
|
|
36
|
-
CREATE TABLE IF NOT EXISTS function_calls (
|
|
37
|
-
caller_id INTEGER REFERENCES functions(id),
|
|
38
|
-
callee_name TEXT
|
|
39
|
-
);
|
|
35
|
+
CREATE INDEX IF NOT EXISTS idx_functions_file_id ON functions(file_id);
|
|
36
|
+
CREATE INDEX IF NOT EXISTS idx_functions_unique_id ON functions(unique_id);
|
|
40
37
|
`);
|
|
41
|
-
// ---
|
|
42
|
-
// Classes table
|
|
38
|
+
// --- Graph-specific additions ---
|
|
43
39
|
db.exec(`
|
|
44
|
-
CREATE TABLE IF NOT EXISTS
|
|
40
|
+
CREATE TABLE IF NOT EXISTS graph_classes (
|
|
45
41
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
46
42
|
file_id INTEGER REFERENCES files(id),
|
|
47
43
|
name TEXT,
|
|
44
|
+
unique_id TEXT UNIQUE, -- e.g. "UserService@src/services/UserService.ts"
|
|
48
45
|
start_line INTEGER,
|
|
49
46
|
end_line INTEGER,
|
|
50
47
|
content TEXT,
|
|
@@ -52,40 +49,41 @@ export function initSchema() {
|
|
|
52
49
|
lang TEXT
|
|
53
50
|
);
|
|
54
51
|
|
|
55
|
-
CREATE INDEX IF NOT EXISTS
|
|
52
|
+
CREATE INDEX IF NOT EXISTS idx_graph_classes_file_id ON graph_classes(file_id);
|
|
53
|
+
CREATE INDEX IF NOT EXISTS idx_graph_classes_unique_id ON graph_classes(unique_id);
|
|
56
54
|
`);
|
|
57
|
-
// Edges table (
|
|
55
|
+
// Edges table (all relationships live here: calls, inherits, contains, imports, exports, tests, etc.)
|
|
58
56
|
db.exec(`
|
|
59
|
-
CREATE TABLE IF NOT EXISTS
|
|
57
|
+
CREATE TABLE IF NOT EXISTS graph_edges (
|
|
60
58
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
61
|
-
source_type TEXT NOT NULL, -- 'function' | 'class' | 'file'
|
|
62
|
-
|
|
59
|
+
source_type TEXT NOT NULL, -- 'function' | 'class' | 'file' | 'test'
|
|
60
|
+
source_unique_id TEXT NOT NULL, -- full unique_id (e.g., function@filePath, filePath)
|
|
63
61
|
target_type TEXT NOT NULL,
|
|
64
|
-
|
|
65
|
-
relation TEXT NOT NULL -- e.g., 'calls', '
|
|
62
|
+
target_unique_id TEXT NOT NULL,
|
|
63
|
+
relation TEXT NOT NULL -- e.g., 'calls', 'imports', 'exports', 'tests'
|
|
66
64
|
);
|
|
67
65
|
|
|
68
|
-
CREATE INDEX IF NOT EXISTS
|
|
69
|
-
CREATE INDEX IF NOT EXISTS
|
|
66
|
+
CREATE INDEX IF NOT EXISTS idx_graph_edges_source ON graph_edges(source_type, source_unique_id);
|
|
67
|
+
CREATE INDEX IF NOT EXISTS idx_graph_edges_target ON graph_edges(target_type, target_unique_id);
|
|
68
|
+
CREATE INDEX IF NOT EXISTS idx_graph_edges_relation ON graph_edges(relation);
|
|
70
69
|
`);
|
|
71
|
-
//
|
|
72
|
-
// Master tag table
|
|
70
|
+
// Tags setup
|
|
73
71
|
db.exec(`
|
|
74
|
-
CREATE TABLE IF NOT EXISTS
|
|
72
|
+
CREATE TABLE IF NOT EXISTS graph_tags_master (
|
|
75
73
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
76
74
|
name TEXT UNIQUE NOT NULL
|
|
77
75
|
);
|
|
78
76
|
|
|
79
|
-
CREATE TABLE IF NOT EXISTS
|
|
77
|
+
CREATE TABLE IF NOT EXISTS graph_entity_tags (
|
|
80
78
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
81
79
|
entity_type TEXT NOT NULL, -- 'function' | 'class' | 'file'
|
|
82
|
-
|
|
83
|
-
tag_id INTEGER NOT NULL REFERENCES
|
|
84
|
-
UNIQUE(entity_type,
|
|
80
|
+
entity_unique_id TEXT NOT NULL, -- match against functions.unique_id, classes.unique_id, or files.path
|
|
81
|
+
tag_id INTEGER NOT NULL REFERENCES graph_tags_master(id),
|
|
82
|
+
UNIQUE(entity_type, entity_unique_id, tag_id)
|
|
85
83
|
);
|
|
86
84
|
|
|
87
|
-
CREATE INDEX IF NOT EXISTS
|
|
88
|
-
CREATE INDEX IF NOT EXISTS
|
|
85
|
+
CREATE INDEX IF NOT EXISTS idx_graph_entity_tags_entity ON graph_entity_tags(entity_type, entity_unique_id);
|
|
86
|
+
CREATE INDEX IF NOT EXISTS idx_graph_entity_tags_tag ON graph_entity_tags(tag_id);
|
|
89
87
|
`);
|
|
90
|
-
console.log(
|
|
88
|
+
console.log("✅ Graph schema initialized (files, functions, classes, edges, tags with unique_ids)");
|
|
91
89
|
}
|
package/dist/db/sqlTemplates.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
// --- Files ---
|
|
1
2
|
// Upsert file metadata into `files`
|
|
2
3
|
export const upsertFileTemplate = `
|
|
3
4
|
INSERT INTO files (path, filename, summary, type, last_modified, indexed_at, embedding)
|
|
4
5
|
VALUES (:path, :filename, :summary, :type, :lastModified, :indexedAt, :embedding)
|
|
5
6
|
ON CONFLICT(path) DO UPDATE SET
|
|
6
|
-
filename = excluded.filename,
|
|
7
|
+
filename = excluded.filename,
|
|
7
8
|
summary = CASE
|
|
8
9
|
WHEN excluded.summary IS NOT NULL AND excluded.summary != files.summary
|
|
9
10
|
THEN excluded.summary
|
|
@@ -18,61 +19,88 @@ export const upsertFileTemplate = `
|
|
|
18
19
|
ELSE files.embedding
|
|
19
20
|
END
|
|
20
21
|
`;
|
|
21
|
-
//
|
|
22
|
-
export const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
`;
|
|
34
|
-
//
|
|
35
|
-
export const
|
|
36
|
-
SELECT f.path, f.filename, f.summary, f.type,
|
|
37
|
-
bm25(files_fts
|
|
38
|
-
FROM
|
|
39
|
-
JOIN
|
|
40
|
-
WHERE files_fts MATCH
|
|
41
|
-
ORDER BY
|
|
42
|
-
LIMIT
|
|
43
|
-
`;
|
|
44
|
-
//
|
|
22
|
+
// Insert or replace into FTS
|
|
23
|
+
export const upsertFileFtsTemplate = `
|
|
24
|
+
INSERT OR REPLACE INTO files_fts (rowid, filename, summary, path)
|
|
25
|
+
VALUES ((SELECT id FROM files WHERE path = :path), :filename, :summary, :path)
|
|
26
|
+
`;
|
|
27
|
+
// Simple file query (no scoring)
|
|
28
|
+
export const queryFilesTemplate = `
|
|
29
|
+
SELECT f.id, f.path, f.filename, f.summary, f.type, f.last_modified, f.indexed_at
|
|
30
|
+
FROM files f
|
|
31
|
+
JOIN files_fts fts ON f.id = fts.rowid
|
|
32
|
+
WHERE fts.files_fts MATCH ?
|
|
33
|
+
LIMIT ?
|
|
34
|
+
`;
|
|
35
|
+
// FTS search with bm25 scoring
|
|
36
|
+
export const searchFilesTemplate = `
|
|
37
|
+
SELECT fts.rowid AS id, f.path, f.filename, f.summary, f.type,
|
|
38
|
+
bm25(files_fts) AS bm25Score, f.embedding
|
|
39
|
+
FROM files f
|
|
40
|
+
JOIN files_fts fts ON f.id = fts.rowid
|
|
41
|
+
WHERE fts.files_fts MATCH ?
|
|
42
|
+
ORDER BY bm25Score ASC
|
|
43
|
+
LIMIT ?
|
|
44
|
+
`;
|
|
45
|
+
// --- Functions ---
|
|
45
46
|
export const insertFunctionTemplate = `
|
|
46
47
|
INSERT INTO functions (file_id, name, start_line, end_line, content, embedding, lang)
|
|
47
48
|
VALUES (:file_id, :name, :start_line, :end_line, :content, :embedding, :lang)
|
|
48
49
|
`;
|
|
49
|
-
//
|
|
50
|
-
export const
|
|
51
|
-
INSERT INTO
|
|
52
|
-
VALUES (:
|
|
50
|
+
// --- Graph ---
|
|
51
|
+
export const insertGraphClassTemplate = `
|
|
52
|
+
INSERT INTO graph_classes (file_id, name, start_line, end_line, content, embedding, lang)
|
|
53
|
+
VALUES (:file_id, :name, :start_line, :end_line, :content, :embedding, :lang)
|
|
53
54
|
`;
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
55
|
+
export const insertEdgeTemplate = `
|
|
56
|
+
INSERT INTO graph_edges (
|
|
57
|
+
source_type,
|
|
58
|
+
source_unique_id,
|
|
59
|
+
target_type,
|
|
60
|
+
target_unique_id,
|
|
61
|
+
relation
|
|
62
|
+
)
|
|
63
|
+
VALUES (
|
|
64
|
+
:source_type,
|
|
65
|
+
:source_unique_id,
|
|
66
|
+
:target_type,
|
|
67
|
+
:target_unique_id,
|
|
68
|
+
:relation
|
|
69
|
+
)
|
|
70
|
+
`;
|
|
71
|
+
// --- New graph tag templates ---
|
|
72
|
+
export const insertGraphTagTemplate = `
|
|
73
|
+
INSERT OR IGNORE INTO graph_tags_master (name) VALUES (:name)
|
|
60
74
|
`;
|
|
61
|
-
|
|
75
|
+
export const selectGraphTagIdTemplate = `
|
|
76
|
+
SELECT id FROM graph_tags_master WHERE name = :name
|
|
77
|
+
`;
|
|
78
|
+
export const insertGraphEntityTagTemplate = `
|
|
79
|
+
INSERT INTO graph_entity_tags (
|
|
80
|
+
entity_type,
|
|
81
|
+
entity_unique_id,
|
|
82
|
+
tag_id
|
|
83
|
+
)
|
|
84
|
+
VALUES (
|
|
85
|
+
:entity_type,
|
|
86
|
+
:entity_unique_id,
|
|
87
|
+
:tag_id
|
|
88
|
+
)
|
|
89
|
+
ON CONFLICT(entity_type, entity_unique_id, tag_id) DO NOTHING
|
|
90
|
+
`;
|
|
91
|
+
// --- File Processing Status ---
|
|
62
92
|
export const markFileAsExtractedTemplate = `
|
|
63
93
|
UPDATE files
|
|
64
94
|
SET processing_status = 'extracted',
|
|
65
95
|
functions_extracted_at = CURRENT_TIMESTAMP
|
|
66
96
|
WHERE id = :id
|
|
67
97
|
`;
|
|
68
|
-
// Mark a file as skipped (not extractable)
|
|
69
98
|
export const markFileAsSkippedTemplate = `
|
|
70
99
|
UPDATE files
|
|
71
100
|
SET processing_status = 'skipped',
|
|
72
101
|
functions_extracted_at = NULL
|
|
73
102
|
WHERE id = :id
|
|
74
103
|
`;
|
|
75
|
-
// Mark a file as failed
|
|
76
104
|
export const markFileAsFailedTemplate = `
|
|
77
105
|
UPDATE files
|
|
78
106
|
SET processing_status = 'failed',
|
|
@@ -89,7 +117,7 @@ export const selectUnprocessedFiles = `
|
|
|
89
117
|
export const markFileAsSkippedByPath = `
|
|
90
118
|
UPDATE files
|
|
91
119
|
SET processing_status = 'skipped',
|
|
92
|
-
|
|
120
|
+
functions_extracted_at = NULL
|
|
93
121
|
WHERE path = @path
|
|
94
122
|
`;
|
|
95
123
|
export const updateFileWithSummaryAndEmbedding = `
|
package/dist/index.js
CHANGED
|
@@ -15,7 +15,6 @@ import { startDaemon } from './commands/DaemonCmd.js';
|
|
|
15
15
|
import { runStopDaemonCommand } from "./commands/StopDaemonCmd.js";
|
|
16
16
|
import { runAskCommand } from './commands/AskCmd.js';
|
|
17
17
|
import { runBackupCommand } from './commands/BackupCmd.js';
|
|
18
|
-
import { runMigrateCommand } from "./commands/MigrateCmd.js";
|
|
19
18
|
import { runInspectCommand } from "./commands/InspectCmd.js";
|
|
20
19
|
import { reviewPullRequestCmd } from "./commands/ReviewCmd.js";
|
|
21
20
|
import { promptForToken } from "./github/token.js";
|
|
@@ -276,12 +275,6 @@ db
|
|
|
276
275
|
.action(async () => await withContext(async () => {
|
|
277
276
|
await resetDatabase();
|
|
278
277
|
}));
|
|
279
|
-
db
|
|
280
|
-
.command('migrate')
|
|
281
|
-
.description('Run DB migration scripts')
|
|
282
|
-
.action(async () => await withContext(async () => {
|
|
283
|
-
await runMigrateCommand();
|
|
284
|
-
}));
|
|
285
278
|
db
|
|
286
279
|
.command('inspect')
|
|
287
280
|
.argument('<filepath>', 'Path to the file to inspect')
|