scai 0.1.115 → 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/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 +204 -109
- package/dist/db/functionExtractors/extractFromTs.js +228 -90
- package/dist/db/schema.js +28 -30
- package/dist/db/sqlTemplates.js +68 -40
- package/dist/fileRules/builtins.js +14 -0
- package/dist/index.js +0 -7
- package/dist/modelSetup.js +45 -6
- package/dist/pipeline/modules/cleanupModule.js +21 -1
- package/dist/scripts/dbcheck.js +222 -277
- package/dist/utils/buildContextualPrompt.js +100 -39
- package/dist/utils/sharedUtils.js +8 -0
- package/package.json +1 -1
- package/dist/commands/MigrateCmd.js +0 -15
|
@@ -3,120 +3,258 @@ 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';
|
|
8
|
+
import { BUILTINS } from '../../fileRules/builtins.js';
|
|
9
|
+
import { getUniqueId } from '../../utils/sharedUtils.js';
|
|
7
10
|
export async function extractFromTS(filePath, content, fileId) {
|
|
8
11
|
const db = getDbForRepo();
|
|
12
|
+
const normalizedPath = path.normalize(filePath).replace(/\\/g, '/');
|
|
9
13
|
try {
|
|
10
14
|
const project = new Project({ useInMemoryFileSystem: true });
|
|
11
15
|
const sourceFile = project.createSourceFile(filePath, content);
|
|
16
|
+
// DTO arrays used to store into functions / graph_classes later (FTS index)
|
|
12
17
|
const functions = [];
|
|
13
18
|
const classes = [];
|
|
14
|
-
|
|
19
|
+
// --- Gather all function AST nodes ---
|
|
20
|
+
const allFuncNodes = [
|
|
15
21
|
...sourceFile.getDescendantsOfKind(SyntaxKind.FunctionDeclaration),
|
|
16
22
|
...sourceFile.getDescendantsOfKind(SyntaxKind.FunctionExpression),
|
|
17
23
|
...sourceFile.getDescendantsOfKind(SyntaxKind.ArrowFunction),
|
|
18
24
|
];
|
|
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 = [
|
|
25
|
+
// --- Gather all class AST nodes ---
|
|
26
|
+
const allClassNodes = [
|
|
27
27
|
...sourceFile.getDescendantsOfKind(SyntaxKind.ClassDeclaration),
|
|
28
28
|
...sourceFile.getDescendantsOfKind(SyntaxKind.ClassExpression),
|
|
29
29
|
];
|
|
30
|
-
|
|
31
|
-
const name = cls.getName() ?? `${path.basename(filePath)}:<anon-class>`;
|
|
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) {
|
|
30
|
+
if (allFuncNodes.length === 0 && allClassNodes.length === 0) {
|
|
45
31
|
log(`⚠️ No functions/classes found in TS file: ${filePath}`);
|
|
46
32
|
db.prepare(markFileAsSkippedTemplate).run({ id: fileId });
|
|
47
33
|
return false;
|
|
48
34
|
}
|
|
49
|
-
log(`🔍 Found ${
|
|
50
|
-
//
|
|
51
|
-
|
|
52
|
-
const
|
|
53
|
-
const
|
|
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
|
-
|
|
35
|
+
log(`🔍 Found ${allFuncNodes.length} functions and ${allClassNodes.length} classes in ${filePath}`);
|
|
36
|
+
// --- LLM tagging ---
|
|
37
|
+
try {
|
|
38
|
+
const kgInput = { fileId, filepath: filePath, summary: undefined };
|
|
39
|
+
const kgResult = await kgModule.run(kgInput, content);
|
|
40
|
+
if (kgResult.entities?.length > 0) {
|
|
41
|
+
const insertTag = db.prepare(insertGraphTagTemplate);
|
|
42
|
+
const getTagId = db.prepare(selectGraphTagIdTemplate);
|
|
43
|
+
const insertEntityTag = db.prepare(insertGraphEntityTagTemplate);
|
|
44
|
+
for (const entity of kgResult.entities) {
|
|
45
|
+
if (!entity.type || !Array.isArray(entity.tags) || entity.tags.length === 0)
|
|
46
|
+
continue;
|
|
47
|
+
for (const tag of entity.tags) {
|
|
48
|
+
if (!tag || typeof tag !== 'string')
|
|
49
|
+
continue;
|
|
50
|
+
try {
|
|
51
|
+
insertTag.run({ name: tag });
|
|
52
|
+
const tagRow = getTagId.get({ name: tag });
|
|
53
|
+
if (!tagRow)
|
|
54
|
+
continue;
|
|
55
|
+
const matchedUniqueId = functions.find(f => f.name === entity.name)?.unique_id ||
|
|
56
|
+
classes.find(c => c.name === entity.name)?.unique_id ||
|
|
57
|
+
`${entity.name}@${filePath}`;
|
|
58
|
+
insertEntityTag.run({
|
|
59
|
+
entity_type: entity.type,
|
|
60
|
+
entity_unique_id: matchedUniqueId,
|
|
61
|
+
tag_id: tagRow.id,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
console.error('❌ Failed to persist entity/tag', { entity, tag, error: err });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
log(`🏷 Persisted LLM-generated tags for ${filePath}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch (kgErr) {
|
|
73
|
+
console.warn(`⚠️ KG tagging failed for ${filePath}:`, kgErr instanceof Error ? kgErr.message : kgErr);
|
|
74
|
+
}
|
|
75
|
+
// --- Process functions ---
|
|
76
|
+
for (const funcNode of allFuncNodes) {
|
|
77
|
+
const symbolName = funcNode.getSymbol()?.getName();
|
|
78
|
+
const name = symbolName ?? '<anon>';
|
|
79
|
+
const start = funcNode.getStartLineNumber();
|
|
80
|
+
const end = funcNode.getEndLineNumber();
|
|
81
|
+
const code = funcNode.getText();
|
|
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 });
|
|
84
|
+
try {
|
|
85
|
+
const embedding = await generateEmbedding(code);
|
|
86
|
+
db.prepare(insertFunctionTemplate).run({
|
|
87
|
+
file_id: fileId,
|
|
88
|
+
name,
|
|
89
|
+
start_line: start,
|
|
90
|
+
end_line: end,
|
|
91
|
+
content: code,
|
|
92
|
+
embedding: JSON.stringify(embedding),
|
|
93
|
+
lang: 'ts',
|
|
94
|
+
unique_id,
|
|
82
95
|
});
|
|
83
96
|
}
|
|
84
|
-
|
|
97
|
+
catch (err) {
|
|
98
|
+
console.error('❌ Failed to insert function row', { filePath, name, error: err });
|
|
99
|
+
}
|
|
100
|
+
// File -> Function 'contains' edge
|
|
101
|
+
try {
|
|
102
|
+
db.prepare(insertEdgeTemplate).run({
|
|
103
|
+
source_type: 'file',
|
|
104
|
+
source_unique_id: normalizedPath,
|
|
105
|
+
target_type: 'function',
|
|
106
|
+
target_unique_id: unique_id,
|
|
107
|
+
relation: 'contains',
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
console.error('❌ Failed to persist file->function edge', { filePath, name, error: err });
|
|
112
|
+
}
|
|
113
|
+
// AST-based calls
|
|
114
|
+
try {
|
|
115
|
+
const callExprs = funcNode.getDescendantsOfKind(SyntaxKind.CallExpression);
|
|
116
|
+
const edgeSet = new Set();
|
|
117
|
+
for (const callExpr of callExprs) {
|
|
118
|
+
let calleeName;
|
|
119
|
+
try {
|
|
120
|
+
const expr = callExpr.getExpression();
|
|
121
|
+
calleeName = expr.getSymbol()?.getName() ?? expr.getText();
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
calleeName = undefined;
|
|
125
|
+
}
|
|
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
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
catch (err) {
|
|
148
|
+
console.warn(`⚠️ Failed to inspect call expressions for function ${name} in ${filePath}:`, err);
|
|
149
|
+
}
|
|
150
|
+
log(`📌 Indexed TS function: ${name} (unique_id=${unique_id})`);
|
|
85
151
|
}
|
|
86
|
-
//
|
|
87
|
-
for (const
|
|
88
|
-
const
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
.run({
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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)`);
|
|
152
|
+
// --- Process classes ---
|
|
153
|
+
for (const clsNode of allClassNodes) {
|
|
154
|
+
const name = clsNode.getName() ?? `${path.basename(filePath)}:<anon-class>`;
|
|
155
|
+
const start = clsNode.getStartLineNumber();
|
|
156
|
+
const end = clsNode.getEndLineNumber();
|
|
157
|
+
const code = clsNode.getText();
|
|
158
|
+
const superClass = clsNode.getExtends()?.getText() ?? null;
|
|
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 });
|
|
161
|
+
try {
|
|
162
|
+
const embedding = await generateEmbedding(code);
|
|
163
|
+
db.prepare(insertGraphClassTemplate).run({
|
|
164
|
+
file_id: fileId,
|
|
165
|
+
name,
|
|
166
|
+
start_line: start,
|
|
167
|
+
end_line: end,
|
|
168
|
+
content: code,
|
|
169
|
+
embedding: JSON.stringify(embedding),
|
|
170
|
+
lang: 'ts',
|
|
171
|
+
unique_id,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
catch (err) {
|
|
175
|
+
console.error('❌ Failed to insert graph class row', { filePath, name, error: err });
|
|
115
176
|
}
|
|
116
|
-
|
|
177
|
+
try {
|
|
178
|
+
db.prepare(insertEdgeTemplate).run({
|
|
179
|
+
source_type: 'file',
|
|
180
|
+
source_unique_id: normalizedPath,
|
|
181
|
+
target_type: 'class',
|
|
182
|
+
target_unique_id: unique_id,
|
|
183
|
+
relation: 'contains',
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
catch (err) {
|
|
187
|
+
console.error('❌ Failed to persist file->class edge', { filePath, name, error: err });
|
|
188
|
+
}
|
|
189
|
+
if (superClass) {
|
|
190
|
+
try {
|
|
191
|
+
db.prepare(insertEdgeTemplate).run({
|
|
192
|
+
source_type: 'class',
|
|
193
|
+
source_unique_id: unique_id,
|
|
194
|
+
target_type: 'class',
|
|
195
|
+
target_unique_id: `unresolved:${superClass}`,
|
|
196
|
+
relation: 'inherits',
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
catch (err) {
|
|
200
|
+
console.error('❌ Failed to persist inherits edge', { filePath, name, superClass, error: err });
|
|
201
|
+
}
|
|
202
|
+
log(`🔗 Class ${name} extends ${superClass} (edge stored for later resolution)`);
|
|
203
|
+
}
|
|
204
|
+
log(`🏷 Indexed TS class: ${name} (unique_id=${unique_id})`);
|
|
205
|
+
}
|
|
206
|
+
// --- Imports ---
|
|
207
|
+
try {
|
|
208
|
+
const importDecls = sourceFile.getDescendantsOfKind(SyntaxKind.ImportDeclaration);
|
|
209
|
+
for (const imp of importDecls) {
|
|
210
|
+
const moduleSpecifier = imp.getModuleSpecifierValue();
|
|
211
|
+
if (!moduleSpecifier)
|
|
212
|
+
continue;
|
|
213
|
+
const targetUniqueId = `file@${moduleSpecifier}`;
|
|
214
|
+
try {
|
|
215
|
+
db.prepare(insertEdgeTemplate).run({
|
|
216
|
+
source_type: 'file',
|
|
217
|
+
source_unique_id: normalizedPath,
|
|
218
|
+
target_type: 'file',
|
|
219
|
+
target_unique_id: targetUniqueId,
|
|
220
|
+
relation: 'imports',
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
catch (err) {
|
|
224
|
+
console.error('❌ Failed to persist import edge', { filePath, moduleSpecifier, error: err });
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
catch (err) {
|
|
229
|
+
console.warn(`⚠️ Import extraction failed for ${filePath}:`, err);
|
|
230
|
+
}
|
|
231
|
+
// --- Exports ---
|
|
232
|
+
try {
|
|
233
|
+
const exportDecls = sourceFile.getDescendantsOfKind(SyntaxKind.ExportDeclaration);
|
|
234
|
+
for (const exp of exportDecls) {
|
|
235
|
+
const moduleSpecifier = exp.getModuleSpecifierValue();
|
|
236
|
+
if (!moduleSpecifier)
|
|
237
|
+
continue;
|
|
238
|
+
const targetUniqueId = `file@${moduleSpecifier}`;
|
|
239
|
+
try {
|
|
240
|
+
db.prepare(insertEdgeTemplate).run({
|
|
241
|
+
source_type: 'file',
|
|
242
|
+
source_unique_id: normalizedPath,
|
|
243
|
+
target_type: 'file',
|
|
244
|
+
target_unique_id: targetUniqueId,
|
|
245
|
+
relation: 'exports',
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
catch (err) {
|
|
249
|
+
console.error('❌ Failed to persist export edge', { filePath, moduleSpecifier, error: err });
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
catch (err) {
|
|
254
|
+
console.warn(`⚠️ Export extraction failed for ${filePath}:`, err);
|
|
117
255
|
}
|
|
118
|
-
log(`📊 Extraction summary for ${filePath}: ${functions.length} functions, ${classes.length} classes`);
|
|
119
256
|
db.prepare(markFileAsExtractedTemplate).run({ id: fileId });
|
|
257
|
+
log(`📊 Extraction summary for ${filePath}: ${functions.length} functions, ${classes.length} classes`);
|
|
120
258
|
log(`✅ Marked TS functions/classes as extracted for ${filePath}`);
|
|
121
259
|
return true;
|
|
122
260
|
}
|
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
|
-
INSERT INTO functions (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
|
+
`;
|
|
50
|
+
// --- Graph ---
|
|
51
|
+
export const insertGraphClassTemplate = `
|
|
52
|
+
INSERT INTO graph_classes (file_id, name, start_line, end_line, content, embedding, lang)
|
|
47
53
|
VALUES (:file_id, :name, :start_line, :end_line, :content, :embedding, :lang)
|
|
48
54
|
`;
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
+
)
|
|
53
70
|
`;
|
|
54
|
-
//
|
|
55
|
-
export const
|
|
56
|
-
|
|
57
|
-
SET processing_status = 'unprocessed',
|
|
58
|
-
functions_extracted_at = NULL
|
|
59
|
-
WHERE id = :id
|
|
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 = `
|
|
@@ -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/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')
|