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
|
@@ -104,6 +104,26 @@ export const cleanupModule = {
|
|
|
104
104
|
break;
|
|
105
105
|
}
|
|
106
106
|
}
|
|
107
|
-
|
|
107
|
+
let finalContent = jsonLines.join('\n').trim();
|
|
108
|
+
// --- VALIDATE JSON ---
|
|
109
|
+
try {
|
|
110
|
+
JSON.parse(finalContent);
|
|
111
|
+
return { content: finalContent };
|
|
112
|
+
}
|
|
113
|
+
catch (err) {
|
|
114
|
+
console.warn(chalk.yellow(`[cleanupModule] Initial JSON.parse failed:`), chalk.red(err instanceof Error ? err.message : err));
|
|
115
|
+
// Attempt simple cleanup: remove trailing commas before } or ]
|
|
116
|
+
finalContent = finalContent.replace(/,\s*([}\]])/g, '$1');
|
|
117
|
+
try {
|
|
118
|
+
JSON.parse(finalContent);
|
|
119
|
+
console.log(chalk.green(`[cleanupModule] Fixed JSON by removing trailing commas.`));
|
|
120
|
+
return { content: finalContent };
|
|
121
|
+
}
|
|
122
|
+
catch (err2) {
|
|
123
|
+
console.error(chalk.red(`[cleanupModule] JSON still invalid after cleanup:`), chalk.yellow(err2 instanceof Error ? err2.message : err2));
|
|
124
|
+
// Return best-effort content even if invalid, so downstream can log/debug
|
|
125
|
+
return { content: finalContent };
|
|
126
|
+
}
|
|
127
|
+
}
|
|
108
128
|
}
|
|
109
129
|
};
|
package/dist/scripts/dbcheck.js
CHANGED
|
@@ -9,7 +9,6 @@ if (!repoKey) {
|
|
|
9
9
|
console.error("โ No active repo found. Use `scai set-index` to set one.");
|
|
10
10
|
process.exit(1);
|
|
11
11
|
}
|
|
12
|
-
// Get the basename (repo name) from the full repoKey (which is the path)
|
|
13
12
|
const repoName = path.basename(repoKey);
|
|
14
13
|
const scaiRepoRoot = path.join(os.homedir(), ".scai", "repos", repoName);
|
|
15
14
|
const dbPath = path.join(scaiRepoRoot, "db.sqlite");
|
|
@@ -18,307 +17,103 @@ if (!fs.existsSync(dbPath)) {
|
|
|
18
17
|
process.exit(1);
|
|
19
18
|
}
|
|
20
19
|
const db = new Database(dbPath);
|
|
21
|
-
//
|
|
22
|
-
|
|
23
|
-
total
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
console.log(
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
console.log(`โ
With processing_status = 'skipped': ${stats.withProcessingStatusSkipped}`);
|
|
44
|
-
console.log(`โ
With processing_status = 'failed': ${stats.withProcessingStatusFailed}`);
|
|
45
|
-
console.log(`โ
With processing_status = 'unprocessed': ${stats.withProcessingStatusUnprocessed}`);
|
|
46
|
-
// === Example Summaries ===
|
|
47
|
-
console.log("\n๐งพ Example summaries and embeddings:\n--------------------------");
|
|
48
|
-
console.log("Example summaries (first 50 characters):");
|
|
49
|
-
const summaries = db.prepare(`
|
|
50
|
-
SELECT id, substr(summary, 1, 50) || '...' AS short_summary
|
|
20
|
+
// Utility to log simple table stats
|
|
21
|
+
function tableStats(table, column) {
|
|
22
|
+
const total = db.prepare(`SELECT COUNT(*) AS count FROM ${table}`).get().count;
|
|
23
|
+
let extra = "";
|
|
24
|
+
if (column) {
|
|
25
|
+
const nonNull = db.prepare(`SELECT COUNT(*) AS count FROM ${table} WHERE ${column} IS NOT NULL AND ${column} != ''`).get().count;
|
|
26
|
+
extra = ` | Non-null ${column}: ${nonNull}`;
|
|
27
|
+
}
|
|
28
|
+
console.log(`๐ ${table}: ${total}${extra}`);
|
|
29
|
+
}
|
|
30
|
+
// === Files Table ===
|
|
31
|
+
console.log("\n๐ Table: files");
|
|
32
|
+
tableStats("files", "summary");
|
|
33
|
+
tableStats("files", "embedding");
|
|
34
|
+
const processingStatuses = ["extracted", "skipped", "failed", "unprocessed"];
|
|
35
|
+
for (const status of processingStatuses) {
|
|
36
|
+
const count = db.prepare(`SELECT COUNT(*) AS count FROM files WHERE processing_status = ?`).get(status).count;
|
|
37
|
+
console.log(` Status '${status}': ${count}`);
|
|
38
|
+
}
|
|
39
|
+
// === Example Files ===
|
|
40
|
+
const exampleFiles = db.prepare(`
|
|
41
|
+
SELECT id, filename, substr(summary,1,50) AS summary, substr(embedding,1,50) AS embedding
|
|
51
42
|
FROM files
|
|
52
|
-
WHERE summary IS NOT NULL AND summary != ''
|
|
53
43
|
LIMIT 5
|
|
54
44
|
`).all();
|
|
55
|
-
|
|
56
|
-
|
|
45
|
+
console.log("\n๐งพ Example Files:");
|
|
46
|
+
exampleFiles.forEach(f => {
|
|
47
|
+
console.log(` [${f.id}] ${f.filename} | summary: "${f.summary}" | embedding: "${f.embedding}"`);
|
|
57
48
|
});
|
|
58
|
-
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
49
|
+
// === FTS5 Check ===
|
|
50
|
+
const ftsExists = db.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='files_fts'`).get();
|
|
51
|
+
console.log("\n๐ FTS5 Table:", ftsExists ? "โ
exists" : "โ missing");
|
|
52
|
+
// === Functions Table ===
|
|
53
|
+
console.log("\n๐งโ๐ป Table: functions");
|
|
54
|
+
tableStats("functions");
|
|
55
|
+
const sampleFuncs = db.prepare(`
|
|
56
|
+
SELECT id, name, file_id, substr(content,1,50) AS preview
|
|
57
|
+
FROM functions
|
|
58
|
+
ORDER BY RANDOM()
|
|
63
59
|
LIMIT 5
|
|
64
60
|
`).all();
|
|
65
|
-
|
|
66
|
-
console.log(
|
|
61
|
+
sampleFuncs.forEach(f => {
|
|
62
|
+
console.log(` [${f.id}] ${f.name} (file ${f.file_id}): ${f.preview}`);
|
|
67
63
|
});
|
|
68
|
-
// ===
|
|
69
|
-
console.log("\n
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
console.log("โ No FTS5 table `files_fts` found in database.");
|
|
75
|
-
}
|
|
76
|
-
else {
|
|
77
|
-
const ftsRowCount = db.prepare(`SELECT COUNT(*) AS count FROM files_fts`).get().count;
|
|
78
|
-
console.log(`โ
files_fts table exists. Rows: ${ftsRowCount}`);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
catch (err) {
|
|
82
|
-
console.error("โ Error while accessing files_fts:", err.message);
|
|
83
|
-
}
|
|
84
|
-
// === Rebuild FTS Index ===
|
|
85
|
-
console.log("\n๐ง Rebuilding FTS5 index...");
|
|
86
|
-
try {
|
|
87
|
-
db.prepare(`INSERT INTO files_fts(files_fts) VALUES ('rebuild')`).run();
|
|
88
|
-
console.log(`โ
Rebuild completed.`);
|
|
89
|
-
}
|
|
90
|
-
catch (err) {
|
|
91
|
-
console.error("โ FTS5 rebuild failed:", err.message);
|
|
92
|
-
}
|
|
93
|
-
// === FTS Search Test ===
|
|
94
|
-
console.log('\n๐ Test MATCH query for "minimap":');
|
|
95
|
-
try {
|
|
96
|
-
const minimapMatches = db.prepare(`
|
|
97
|
-
SELECT f.id, f.path
|
|
98
|
-
FROM files f
|
|
99
|
-
JOIN files_fts fts ON f.id = fts.rowid
|
|
100
|
-
WHERE fts.files_fts MATCH 'minimap'
|
|
101
|
-
LIMIT 10
|
|
102
|
-
`).all();
|
|
103
|
-
if (minimapMatches.length === 0) {
|
|
104
|
-
console.warn('โ ๏ธ No matches for "minimap" in FTS index');
|
|
105
|
-
}
|
|
106
|
-
else {
|
|
107
|
-
minimapMatches.forEach(row => {
|
|
108
|
-
console.log(`๐ ${row.path}`);
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
catch (err) {
|
|
113
|
-
console.error('โ Error running MATCH query:', err.message);
|
|
114
|
-
}
|
|
115
|
-
// === Direct LIKE Fallback ===
|
|
116
|
-
console.log('\n๐ Direct LIKE query on path for "minimap":');
|
|
117
|
-
const likeMatches = db.prepare(`
|
|
118
|
-
SELECT id, path
|
|
119
|
-
FROM files
|
|
120
|
-
WHERE path LIKE '%minimap%'
|
|
121
|
-
LIMIT 10
|
|
122
|
-
`).all();
|
|
123
|
-
if (likeMatches.length === 0) {
|
|
124
|
-
console.warn('โ ๏ธ No file paths contain "minimap"');
|
|
125
|
-
}
|
|
126
|
-
else {
|
|
127
|
-
likeMatches.forEach(row => {
|
|
128
|
-
console.log(`๐ ${row.path}`);
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
// === Function Table Stats ===
|
|
132
|
-
console.log('\n๐ Stats for Table: functions');
|
|
133
|
-
console.log('-------------------------------------------');
|
|
134
|
-
try {
|
|
135
|
-
const funcCount = db.prepare(`SELECT COUNT(*) AS count FROM functions`).get().count;
|
|
136
|
-
const distinctFiles = db.prepare(`SELECT COUNT(DISTINCT file_id) AS count FROM functions`).get().count;
|
|
137
|
-
console.log(`๐ข Total functions: ${funcCount}`);
|
|
138
|
-
console.log(`๐ Distinct files: ${distinctFiles}`);
|
|
139
|
-
}
|
|
140
|
-
catch (err) {
|
|
141
|
-
console.error('โ Error accessing functions table:', err.message);
|
|
142
|
-
}
|
|
143
|
-
// === Example Functions ===
|
|
144
|
-
console.log('\n๐งช Example extracted functions:');
|
|
145
|
-
try {
|
|
146
|
-
const sampleFunctions = db.prepare(`
|
|
147
|
-
SELECT id, name, start_line, end_line, substr(content, 1, 100) || '...' AS short_body
|
|
148
|
-
FROM functions
|
|
149
|
-
ORDER BY id DESC
|
|
150
|
-
LIMIT 5
|
|
151
|
-
`).all();
|
|
152
|
-
sampleFunctions.forEach(fn => {
|
|
153
|
-
console.log(`๐น ID: ${fn.id}`);
|
|
154
|
-
console.log(` Name: ${fn.name}`);
|
|
155
|
-
console.log(` Lines: ${fn.start_line}-${fn.end_line}`);
|
|
156
|
-
console.log(` Body: ${fn.short_body}\n`);
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
catch (err) {
|
|
160
|
-
console.error('โ Error printing function examples:', err.message);
|
|
161
|
-
}
|
|
162
|
-
// === Function Calls Table Stats ===
|
|
163
|
-
console.log('\n๐ Stats for Table: function_calls');
|
|
164
|
-
console.log('-------------------------------------------');
|
|
165
|
-
try {
|
|
166
|
-
const callCount = db.prepare(`SELECT COUNT(*) AS count FROM function_calls`).get().count;
|
|
167
|
-
const topCallers = db.prepare(`
|
|
168
|
-
SELECT caller_id, COUNT(*) AS num_calls
|
|
169
|
-
FROM function_calls
|
|
170
|
-
GROUP BY caller_id
|
|
171
|
-
ORDER BY num_calls DESC
|
|
172
|
-
LIMIT 5
|
|
173
|
-
`).all();
|
|
174
|
-
console.log(`๐ Total function calls: ${callCount}`);
|
|
175
|
-
console.log('๐ Top callers:');
|
|
176
|
-
topCallers.forEach(row => {
|
|
177
|
-
console.log(` - Caller ${row.caller_id} made ${row.num_calls} calls`);
|
|
178
|
-
});
|
|
179
|
-
}
|
|
180
|
-
catch (err) {
|
|
181
|
-
console.error('โ Error accessing function_calls table:', err.message);
|
|
182
|
-
}
|
|
183
|
-
// === Random Summary Samples ===
|
|
184
|
-
console.log('\n๐งพ Random Summaries (ID + Preview):');
|
|
185
|
-
console.log('-------------------------------------------');
|
|
186
|
-
const randomSummaries = db.prepare(`
|
|
187
|
-
SELECT id, filename, substr(summary, 1, 1000) || '...' AS preview
|
|
188
|
-
FROM files
|
|
189
|
-
WHERE summary IS NOT NULL AND summary != ''
|
|
64
|
+
// === Graph Classes Table ===
|
|
65
|
+
console.log("\n๐ท Table: graph_classes");
|
|
66
|
+
tableStats("graph_classes");
|
|
67
|
+
const sampleClasses = db.prepare(`
|
|
68
|
+
SELECT id, name, file_id, substr(content,1,50) AS preview
|
|
69
|
+
FROM graph_classes
|
|
190
70
|
ORDER BY RANDOM()
|
|
191
71
|
LIMIT 5
|
|
192
72
|
`).all();
|
|
193
|
-
|
|
194
|
-
console.log(
|
|
73
|
+
sampleClasses.forEach(c => {
|
|
74
|
+
console.log(` [${c.id}] ${c.name} (file ${c.file_id}): ${c.preview}`);
|
|
195
75
|
});
|
|
196
|
-
// ===
|
|
197
|
-
console.log(
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
76
|
+
// === Graph Edges Table ===
|
|
77
|
+
console.log("\n๐ Table: graph_edges");
|
|
78
|
+
tableStats("graph_edges", "relation");
|
|
79
|
+
// Dynamically get column names
|
|
80
|
+
const columns = db.prepare(`PRAGMA table_info(graph_edges)`).all();
|
|
81
|
+
const colNames = columns.map(c => c.name);
|
|
82
|
+
// Determine source/target ID columns
|
|
83
|
+
const sourceCol = colNames.includes("source_id") ? "source_id" : "source_unique_id";
|
|
84
|
+
const targetCol = colNames.includes("target_id") ? "target_id" : "target_unique_id";
|
|
85
|
+
const sampleEdges = db.prepare(`
|
|
86
|
+
SELECT id, source_type, ${sourceCol} AS source_id,
|
|
87
|
+
target_type, ${targetCol} AS target_id,
|
|
88
|
+
relation
|
|
89
|
+
FROM graph_edges
|
|
202
90
|
ORDER BY RANDOM()
|
|
203
91
|
LIMIT 5
|
|
204
92
|
`).all();
|
|
205
|
-
|
|
206
|
-
console.log(
|
|
207
|
-
console.log(` ${row.preview}\n`);
|
|
93
|
+
sampleEdges.forEach(e => {
|
|
94
|
+
console.log(` [${e.id}] ${e.source_type}(${e.source_id}) -[${e.relation}]-> ${e.target_type}(${e.target_id})`);
|
|
208
95
|
});
|
|
209
|
-
// ===
|
|
210
|
-
console.log(
|
|
211
|
-
|
|
212
|
-
const
|
|
213
|
-
SELECT id,
|
|
214
|
-
FROM
|
|
215
|
-
|
|
96
|
+
// === Tags Tables ===
|
|
97
|
+
console.log("\n๐ท Table: graph_tags_master");
|
|
98
|
+
tableStats("graph_tags_master");
|
|
99
|
+
const sampleTags = db.prepare(`
|
|
100
|
+
SELECT id, name
|
|
101
|
+
FROM graph_tags_master
|
|
102
|
+
ORDER BY RANDOM()
|
|
103
|
+
LIMIT 5
|
|
216
104
|
`).all();
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
console.log(
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
105
|
+
sampleTags.forEach(t => {
|
|
106
|
+
console.log(` [${t.id}] ${t.name}`);
|
|
107
|
+
});
|
|
108
|
+
console.log("\n๐ Table: graph_entity_tags");
|
|
109
|
+
tableStats("graph_entity_tags");
|
|
110
|
+
const sampleEntityTags = db.prepare(`
|
|
111
|
+
SELECT id, entity_type, entity_unique_id AS entity_id, tag_id
|
|
112
|
+
FROM graph_entity_tags
|
|
113
|
+
ORDER BY RANDOM()
|
|
114
|
+
LIMIT 5
|
|
225
115
|
`).all();
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
console.log(
|
|
230
|
-
try {
|
|
231
|
-
const classCount = db.prepare(`SELECT COUNT(*) AS count FROM classes`).get().count;
|
|
232
|
-
const distinctClassFiles = db.prepare(`SELECT COUNT(DISTINCT file_id) AS count FROM classes`).get().count;
|
|
233
|
-
console.log(`๐ท Total classes: ${classCount}`);
|
|
234
|
-
console.log(`๐ Distinct files: ${distinctClassFiles}`);
|
|
235
|
-
}
|
|
236
|
-
catch (err) {
|
|
237
|
-
console.error('โ Error accessing classes table:', err.message);
|
|
238
|
-
}
|
|
239
|
-
// === Example Classes ===
|
|
240
|
-
console.log('\n๐งช Example extracted classes:');
|
|
241
|
-
try {
|
|
242
|
-
const sampleClasses = db.prepare(`
|
|
243
|
-
SELECT id, name, file_id, start_line, end_line, substr(content, 1, 100) || '...' AS short_body
|
|
244
|
-
FROM classes
|
|
245
|
-
ORDER BY id DESC
|
|
246
|
-
LIMIT 5
|
|
247
|
-
`).all();
|
|
248
|
-
sampleClasses.forEach(cls => {
|
|
249
|
-
console.log(`๐ท ID: ${cls.id}`);
|
|
250
|
-
console.log(` Name: ${cls.name}`);
|
|
251
|
-
console.log(` File: ${cls.file_id}`);
|
|
252
|
-
console.log(` Lines: ${cls.start_line}-${cls.end_line}`);
|
|
253
|
-
console.log(` Body: ${cls.short_body}\n`);
|
|
254
|
-
});
|
|
255
|
-
}
|
|
256
|
-
catch (err) {
|
|
257
|
-
console.error('โ Error printing class examples:', err.message);
|
|
258
|
-
}
|
|
259
|
-
// === Edge Table Stats ===
|
|
260
|
-
console.log('\n๐ Stats for Table: edges');
|
|
261
|
-
console.log('-------------------------------------------');
|
|
262
|
-
try {
|
|
263
|
-
const edgeCount = db.prepare(`SELECT COUNT(*) AS count FROM edges`).get().count;
|
|
264
|
-
const distinctRelations = db.prepare(`SELECT COUNT(DISTINCT relation) AS count FROM edges`).get().count;
|
|
265
|
-
console.log(`๐ Total edges: ${edgeCount}`);
|
|
266
|
-
console.log(`๐งฉ Distinct relations: ${distinctRelations}`);
|
|
267
|
-
}
|
|
268
|
-
catch (err) {
|
|
269
|
-
console.error('โ Error accessing edges table:', err.message);
|
|
270
|
-
}
|
|
271
|
-
// === Example Edges ===
|
|
272
|
-
console.log('\n๐งช Example edges:');
|
|
273
|
-
try {
|
|
274
|
-
const sampleEdges = db.prepare(`
|
|
275
|
-
SELECT id, source_id, target_id, relation
|
|
276
|
-
FROM edges
|
|
277
|
-
ORDER BY id DESC
|
|
278
|
-
LIMIT 10
|
|
279
|
-
`).all();
|
|
280
|
-
sampleEdges.forEach(e => {
|
|
281
|
-
console.log(`๐ Edge ${e.id}: ${e.source_id} -[${e.relation}]-> ${e.target_id}`);
|
|
282
|
-
});
|
|
283
|
-
}
|
|
284
|
-
catch (err) {
|
|
285
|
-
console.error('โ Error printing edge examples:', err.message);
|
|
286
|
-
}
|
|
287
|
-
// === Tags Master Stats ===
|
|
288
|
-
console.log('\n๐ Stats for Table: tags_master');
|
|
289
|
-
console.log('-------------------------------------------');
|
|
290
|
-
try {
|
|
291
|
-
const tagCount = db.prepare(`SELECT COUNT(*) AS count FROM tags_master`).get().count;
|
|
292
|
-
console.log(`๐ท Total tags: ${tagCount}`);
|
|
293
|
-
const sampleTags = db.prepare(`
|
|
294
|
-
SELECT id, name
|
|
295
|
-
FROM tags_master
|
|
296
|
-
ORDER BY id DESC
|
|
297
|
-
LIMIT 5
|
|
298
|
-
`).all();
|
|
299
|
-
sampleTags.forEach(tag => {
|
|
300
|
-
console.log(`๐ท Tag ${tag.id}: ${tag.name}`);
|
|
301
|
-
});
|
|
302
|
-
}
|
|
303
|
-
catch (err) {
|
|
304
|
-
console.error('โ Error accessing tags_master table:', err.message);
|
|
305
|
-
}
|
|
306
|
-
// === Entity Tags Stats ===
|
|
307
|
-
console.log('\n๐ Stats for Table: entity_tags');
|
|
308
|
-
console.log('-------------------------------------------');
|
|
309
|
-
try {
|
|
310
|
-
const entityTagCount = db.prepare(`SELECT COUNT(*) AS count FROM entity_tags`).get().count;
|
|
311
|
-
console.log(`๐ Total entity-tags: ${entityTagCount}`);
|
|
312
|
-
const sampleEntityTags = db.prepare(`
|
|
313
|
-
SELECT id, entity_type, entity_id, tag_id
|
|
314
|
-
FROM entity_tags
|
|
315
|
-
ORDER BY id DESC
|
|
316
|
-
LIMIT 10
|
|
317
|
-
`).all();
|
|
318
|
-
sampleEntityTags.forEach(et => {
|
|
319
|
-
console.log(`๐ EntityTag ${et.id}: ${et.entity_type} ${et.entity_id} -> tag ${et.tag_id}`);
|
|
320
|
-
});
|
|
321
|
-
}
|
|
322
|
-
catch (err) {
|
|
323
|
-
console.error('โ Error accessing entity_tags table:', err.message);
|
|
324
|
-
}
|
|
116
|
+
sampleEntityTags.forEach(et => {
|
|
117
|
+
console.log(` [${et.id}] ${et.entity_type}(${et.entity_id}) -> tag ${et.tag_id}`);
|
|
118
|
+
});
|
|
119
|
+
console.log("\nโ
DB check completed.\n");
|
|
@@ -6,68 +6,89 @@ export async function buildContextualPrompt({ topFile, query, kgDepth = 3, }) {
|
|
|
6
6
|
const log = (...args) => console.log("[buildContextualPrompt]", ...args);
|
|
7
7
|
const promptSections = [];
|
|
8
8
|
const seenPaths = new Set();
|
|
9
|
-
// โ๏ธ Word-based summarizer with progressive shortening
|
|
10
9
|
function summarizeForPrompt(summary, maxWords = 30) {
|
|
11
10
|
if (!summary)
|
|
12
11
|
return undefined;
|
|
13
12
|
const words = summary.split(/\s+/);
|
|
14
|
-
|
|
15
|
-
return summary.trim();
|
|
16
|
-
return words.slice(0, maxWords).join(" ") + " โฆ";
|
|
13
|
+
return words.length <= maxWords ? summary.trim() : words.slice(0, maxWords).join(" ") + " โฆ";
|
|
17
14
|
}
|
|
18
|
-
// ---
|
|
15
|
+
// --- Top file summary ---
|
|
19
16
|
if (topFile.summary) {
|
|
20
17
|
promptSections.push(`**Top file:** ${topFile.path}\n${topFile.summary}`);
|
|
21
18
|
seenPaths.add(topFile.path);
|
|
22
19
|
}
|
|
23
|
-
// ---
|
|
24
|
-
const
|
|
25
|
-
SELECT et.entity_type, et.
|
|
26
|
-
FROM
|
|
27
|
-
JOIN
|
|
28
|
-
WHERE et.
|
|
29
|
-
`);
|
|
30
|
-
const topEntitiesRows = topEntitiesStmt.all(topFile.id);
|
|
20
|
+
// --- Knowledge Graph tags ---
|
|
21
|
+
const topEntitiesRows = db.prepare(`
|
|
22
|
+
SELECT et.entity_type, et.entity_unique_id, tm.name AS tag
|
|
23
|
+
FROM graph_entity_tags et
|
|
24
|
+
JOIN graph_tags_master tm ON et.tag_id = tm.id
|
|
25
|
+
WHERE et.entity_unique_id = ?
|
|
26
|
+
`).all(topFile.path);
|
|
31
27
|
if (topEntitiesRows.length > 0) {
|
|
32
28
|
const tags = topEntitiesRows.map(r => `- **${r.entity_type}**: ${r.tag}`);
|
|
33
29
|
promptSections.push(`**Knowledge Graph context for ${topFile.path}:**\n${tags.join("\n")}`);
|
|
34
30
|
}
|
|
35
|
-
// ---
|
|
36
|
-
const
|
|
37
|
-
SELECT
|
|
38
|
-
FROM
|
|
39
|
-
JOIN files f ON e.target_id = f.id
|
|
31
|
+
// --- Imports / Exports ---
|
|
32
|
+
const imports = db.prepare(`
|
|
33
|
+
SELECT e.target_unique_id AS imported
|
|
34
|
+
FROM graph_edges e
|
|
40
35
|
WHERE e.source_type = 'file'
|
|
41
|
-
AND e.
|
|
42
|
-
AND e.
|
|
43
|
-
`);
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
36
|
+
AND e.relation = 'imports'
|
|
37
|
+
AND e.source_unique_id = ?
|
|
38
|
+
`).all(topFile.path);
|
|
39
|
+
if (imports.length) {
|
|
40
|
+
promptSections.push(`**Imports from ${topFile.path}:**\n` + imports.map(i => `- ${i.imported}`).join("\n"));
|
|
41
|
+
}
|
|
42
|
+
const exports = db.prepare(`
|
|
43
|
+
SELECT e.target_unique_id AS exported
|
|
44
|
+
FROM graph_edges e
|
|
45
|
+
WHERE e.source_type = 'file'
|
|
46
|
+
AND e.relation = 'exports'
|
|
47
|
+
AND e.source_unique_id = ?
|
|
48
|
+
`).all(topFile.path);
|
|
49
|
+
if (exports.length) {
|
|
50
|
+
promptSections.push(`**Exports from ${topFile.path}:**\n` + exports.map(e => `- ${e.exported}`).join("\n"));
|
|
51
|
+
}
|
|
52
|
+
// --- Functions in file ---
|
|
53
|
+
const functionRows = db
|
|
54
|
+
.prepare(`SELECT unique_id, name, start_line, end_line, content FROM functions WHERE file_id = ? ORDER BY start_line`)
|
|
55
|
+
.all(topFile.id);
|
|
56
|
+
const FUNCTION_LIMIT = 5;
|
|
57
|
+
const hasMoreFunctions = functionRows.length > FUNCTION_LIMIT;
|
|
58
|
+
const functionsSummary = functionRows.slice(0, FUNCTION_LIMIT).map(f => {
|
|
59
|
+
const lines = f.content?.split("\n").map(l => l.trim()).filter(Boolean) || ["[no content]"];
|
|
60
|
+
const preview = lines.slice(0, 3).map(l => l.slice(0, 200) + (l.length > 200 ? "โฆ" : "")).join(" | ");
|
|
61
|
+
return `- ${f.name || "[anonymous]"} (lines ${f.start_line}-${f.end_line}) โ ${preview}`;
|
|
62
|
+
});
|
|
63
|
+
if (functionsSummary.length) {
|
|
64
|
+
promptSections.push(`**Functions in ${topFile.path} (showing ${functionsSummary.length}${hasMoreFunctions ? ` of ${functionRows.length}` : ""}):**\n${functionsSummary.join("\n")}`);
|
|
65
|
+
}
|
|
66
|
+
// ===============================
|
|
67
|
+
// Graph / KG traversal
|
|
68
|
+
// ===============================
|
|
69
|
+
function getRelatedKGFiles(fileUniqueId, visited = new Set()) {
|
|
70
|
+
if (visited.has(fileUniqueId))
|
|
47
71
|
return [];
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
72
|
+
visited.add(fileUniqueId);
|
|
73
|
+
const rows = db.prepare(`
|
|
74
|
+
SELECT DISTINCT f.id, f.path, f.summary
|
|
75
|
+
FROM graph_edges e
|
|
76
|
+
JOIN files f ON e.target_unique_id = f.path
|
|
77
|
+
WHERE e.source_type = 'file'
|
|
78
|
+
AND e.target_type = 'file'
|
|
79
|
+
AND e.source_unique_id = ?
|
|
80
|
+
`).all(fileUniqueId);
|
|
57
81
|
let results = [];
|
|
58
82
|
for (const row of rows) {
|
|
59
83
|
results.push(row);
|
|
60
|
-
results.push(...getRelatedKGFiles(row.
|
|
84
|
+
results.push(...getRelatedKGFiles(row.path, visited));
|
|
61
85
|
}
|
|
62
86
|
return results;
|
|
63
87
|
}
|
|
64
88
|
function buildFileTree(file, depth, visited = new Set()) {
|
|
65
|
-
|
|
66
|
-
if (visited.has(file.id)) {
|
|
89
|
+
if (visited.has(file.path))
|
|
67
90
|
return { id: file.id.toString(), path: file.path };
|
|
68
|
-
|
|
69
|
-
visited.add(file.id);
|
|
70
|
-
// progressively shorten summaries, drop at depth <= 1
|
|
91
|
+
visited.add(file.path);
|
|
71
92
|
const maxWordsByDepth = depth >= 3 ? 30 : depth === 2 ? 15 : 0;
|
|
72
93
|
const node = {
|
|
73
94
|
id: file.id.toString(),
|
|
@@ -75,10 +96,7 @@ export async function buildContextualPrompt({ topFile, query, kgDepth = 3, }) {
|
|
|
75
96
|
summary: maxWordsByDepth > 0 ? summarizeForPrompt(file.summary, maxWordsByDepth) : undefined,
|
|
76
97
|
};
|
|
77
98
|
if (depth > 1) {
|
|
78
|
-
const relatedFiles = getRelatedKGFiles(file.id,
|
|
79
|
-
.map(f => ({ id: f.id, path: f.path, summary: f.summary }))
|
|
80
|
-
.slice(0, 5); // cap children
|
|
81
|
-
log(`File ${file.path} has ${relatedFiles.length} related files`);
|
|
99
|
+
const relatedFiles = getRelatedKGFiles(file.path).map(f => ({ id: f.id, path: f.path, summary: f.summary })).slice(0, 5);
|
|
82
100
|
const relatedNodes = relatedFiles.map(f => buildFileTree(f, depth - 1, visited));
|
|
83
101
|
if (relatedNodes.length)
|
|
84
102
|
node.related = relatedNodes;
|
|
@@ -86,12 +104,51 @@ export async function buildContextualPrompt({ topFile, query, kgDepth = 3, }) {
|
|
|
86
104
|
return node;
|
|
87
105
|
}
|
|
88
106
|
const kgTree = buildFileTree({ id: topFile.id, path: topFile.path, summary: topFile.summary }, kgDepth);
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
107
|
+
promptSections.push(`**KG-Related Files (JSON tree, depth ${kgDepth}):**\n\`\`\`json\n${JSON.stringify(kgTree, null, 2)}\n\`\`\``);
|
|
108
|
+
const functionCallsAll = db.prepare(`
|
|
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
|
+
const callsByFunction = {};
|
|
117
|
+
for (const fn of functionRows) {
|
|
118
|
+
const rows = functionCallsAll
|
|
119
|
+
.filter(r => r.source_unique_id === fn.unique_id)
|
|
120
|
+
.slice(0, FUNCTION_LIMIT);
|
|
121
|
+
callsByFunction[fn.name || fn.unique_id] = {
|
|
122
|
+
calls: rows.map(r => ({ unique_id: r.target_unique_id })),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
if (Object.keys(callsByFunction).length > 0) {
|
|
126
|
+
promptSections.push(`**Function-level calls (limited, JSON):**\n\`\`\`json\n${JSON.stringify(callsByFunction, null, 2)}\n\`\`\``);
|
|
127
|
+
}
|
|
128
|
+
// --- Function-level "called by" overview (limited) ---
|
|
129
|
+
const calledByAll = db.prepare(`
|
|
130
|
+
SELECT source_unique_id, target_unique_id
|
|
131
|
+
FROM graph_edges
|
|
132
|
+
WHERE target_type = 'function' AND relation = 'calls'
|
|
133
|
+
AND target_unique_id IN (
|
|
134
|
+
SELECT unique_id FROM functions WHERE file_id = ?
|
|
135
|
+
)
|
|
136
|
+
`).all(topFile.id);
|
|
137
|
+
const calledByByFunction = {};
|
|
138
|
+
for (const fn of functionRows) {
|
|
139
|
+
const rows = calledByAll
|
|
140
|
+
.filter(r => r.target_unique_id === fn.unique_id)
|
|
141
|
+
.slice(0, FUNCTION_LIMIT);
|
|
142
|
+
calledByByFunction[fn.name || fn.unique_id] = {
|
|
143
|
+
calledBy: rows.map(r => ({ unique_id: r.source_unique_id })),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
if (Object.keys(calledByByFunction).length > 0) {
|
|
147
|
+
promptSections.push(`**Function-level called-by (limited, JSON):**\n\`\`\`json\n${JSON.stringify(calledByByFunction, null, 2)}\n\`\`\``);
|
|
148
|
+
}
|
|
149
|
+
// --- Focused file tree (shallow) ---
|
|
93
150
|
try {
|
|
94
|
-
fileTree = generateFocusedFileTree(topFile.path, 2);
|
|
151
|
+
const fileTree = generateFocusedFileTree(topFile.path, 2);
|
|
95
152
|
if (fileTree) {
|
|
96
153
|
promptSections.push(`**Focused File Tree (depth 2):**\n\`\`\`\n${fileTree}\n\`\`\``);
|
|
97
154
|
}
|
|
@@ -99,21 +156,18 @@ export async function buildContextualPrompt({ topFile, query, kgDepth = 3, }) {
|
|
|
99
156
|
catch (e) {
|
|
100
157
|
console.warn("โ ๏ธ Could not generate file tree:", e);
|
|
101
158
|
}
|
|
102
|
-
// ---
|
|
103
|
-
// Only include raw code if no summary exists, or if the query explicitly asks for it
|
|
159
|
+
// --- Optional code snippet ---
|
|
104
160
|
const MAX_LINES = 50;
|
|
105
161
|
const queryNeedsCode = /\b(code|implementation|function|snippet)\b/i.test(query);
|
|
106
162
|
if ((!topFile.summary || queryNeedsCode) && topFile.code) {
|
|
107
163
|
const lines = topFile.code.split("\n").slice(0, MAX_LINES);
|
|
108
164
|
let snippet = lines.join("\n");
|
|
109
|
-
if (topFile.code.split("\n").length > MAX_LINES)
|
|
165
|
+
if (topFile.code.split("\n").length > MAX_LINES)
|
|
110
166
|
snippet += "\n... [truncated]";
|
|
111
|
-
}
|
|
112
167
|
promptSections.push(`**Code Context (first ${MAX_LINES} lines):**\n\`\`\`\n${snippet}\n\`\`\``);
|
|
113
168
|
}
|
|
114
|
-
// ---
|
|
169
|
+
// --- User query ---
|
|
115
170
|
promptSections.push(`**Query:** ${query}`);
|
|
116
|
-
// --- Step 7: Combine prompt ---
|
|
117
171
|
const promptText = promptSections.join("\n\n---\n\n");
|
|
118
172
|
log("โ
Contextual prompt built for:", topFile.path);
|
|
119
173
|
log("๐ Prompt preview:\n", promptText + "\n");
|