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
|
@@ -6,75 +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
|
-
// --- Utility: Summarize text to a few words ---
|
|
10
9
|
function summarizeForPrompt(summary, maxWords = 30) {
|
|
11
10
|
if (!summary)
|
|
12
11
|
return undefined;
|
|
13
12
|
const words = summary.split(/\s+/);
|
|
14
13
|
return words.length <= maxWords ? summary.trim() : words.slice(0, maxWords).join(" ") + " …";
|
|
15
14
|
}
|
|
16
|
-
// ---
|
|
15
|
+
// --- Top file summary ---
|
|
17
16
|
if (topFile.summary) {
|
|
18
17
|
promptSections.push(`**Top file:** ${topFile.path}\n${topFile.summary}`);
|
|
19
18
|
seenPaths.add(topFile.path);
|
|
20
19
|
}
|
|
21
|
-
//
|
|
22
|
-
// SECTION A: Database queries
|
|
23
|
-
// ===========================
|
|
24
|
-
// --- Step 2a: KG entities/tags ---
|
|
20
|
+
// --- Knowledge Graph tags ---
|
|
25
21
|
const topEntitiesRows = db.prepare(`
|
|
26
|
-
SELECT et.entity_type, et.
|
|
27
|
-
FROM
|
|
28
|
-
JOIN
|
|
29
|
-
WHERE et.
|
|
30
|
-
`).all(topFile.
|
|
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
|
-
// ---
|
|
31
|
+
// --- Imports / Exports ---
|
|
32
|
+
const imports = db.prepare(`
|
|
33
|
+
SELECT e.target_unique_id AS imported
|
|
34
|
+
FROM graph_edges e
|
|
35
|
+
WHERE e.source_type = 'file'
|
|
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 ---
|
|
36
53
|
const functionRows = db
|
|
37
|
-
.prepare(`SELECT name, start_line, end_line, content FROM functions WHERE file_id = ? ORDER BY start_line`)
|
|
54
|
+
.prepare(`SELECT unique_id, name, start_line, end_line, content FROM functions WHERE file_id = ? ORDER BY start_line`)
|
|
38
55
|
.all(topFile.id);
|
|
39
|
-
const FUNCTION_LIMIT =
|
|
56
|
+
const FUNCTION_LIMIT = 5;
|
|
40
57
|
const hasMoreFunctions = functionRows.length > FUNCTION_LIMIT;
|
|
41
58
|
const functionsSummary = functionRows.slice(0, FUNCTION_LIMIT).map(f => {
|
|
42
59
|
const lines = f.content?.split("\n").map(l => l.trim()).filter(Boolean) || ["[no content]"];
|
|
43
|
-
const preview = lines.slice(0, 3)
|
|
44
|
-
.map(l => l.slice(0, 200) + (l.length > 200 ? "…" : ""))
|
|
45
|
-
.join(" | ");
|
|
60
|
+
const preview = lines.slice(0, 3).map(l => l.slice(0, 200) + (l.length > 200 ? "…" : "")).join(" | ");
|
|
46
61
|
return `- ${f.name || "[anonymous]"} (lines ${f.start_line}-${f.end_line}) — ${preview}`;
|
|
47
62
|
});
|
|
48
63
|
if (functionsSummary.length) {
|
|
49
64
|
promptSections.push(`**Functions in ${topFile.path} (showing ${functionsSummary.length}${hasMoreFunctions ? ` of ${functionRows.length}` : ""}):**\n${functionsSummary.join("\n")}`);
|
|
50
65
|
}
|
|
51
66
|
// ===============================
|
|
52
|
-
//
|
|
67
|
+
// Graph / KG traversal
|
|
53
68
|
// ===============================
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
FROM edges e
|
|
57
|
-
JOIN files f ON e.target_id = f.id
|
|
58
|
-
WHERE e.source_type = 'file'
|
|
59
|
-
AND e.target_type = 'file'
|
|
60
|
-
AND e.source_id = ?
|
|
61
|
-
`);
|
|
62
|
-
function getRelatedKGFiles(fileId, visited = new Set()) {
|
|
63
|
-
if (visited.has(fileId))
|
|
69
|
+
function getRelatedKGFiles(fileUniqueId, visited = new Set()) {
|
|
70
|
+
if (visited.has(fileUniqueId))
|
|
64
71
|
return [];
|
|
65
|
-
visited.add(
|
|
66
|
-
const rows =
|
|
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);
|
|
67
81
|
let results = [];
|
|
68
82
|
for (const row of rows) {
|
|
69
83
|
results.push(row);
|
|
70
|
-
results.push(...getRelatedKGFiles(row.
|
|
84
|
+
results.push(...getRelatedKGFiles(row.path, visited));
|
|
71
85
|
}
|
|
72
86
|
return results;
|
|
73
87
|
}
|
|
74
88
|
function buildFileTree(file, depth, visited = new Set()) {
|
|
75
|
-
if (visited.has(file.
|
|
89
|
+
if (visited.has(file.path))
|
|
76
90
|
return { id: file.id.toString(), path: file.path };
|
|
77
|
-
visited.add(file.
|
|
91
|
+
visited.add(file.path);
|
|
78
92
|
const maxWordsByDepth = depth >= 3 ? 30 : depth === 2 ? 15 : 0;
|
|
79
93
|
const node = {
|
|
80
94
|
id: file.id.toString(),
|
|
@@ -82,9 +96,7 @@ export async function buildContextualPrompt({ topFile, query, kgDepth = 3, }) {
|
|
|
82
96
|
summary: maxWordsByDepth > 0 ? summarizeForPrompt(file.summary, maxWordsByDepth) : undefined,
|
|
83
97
|
};
|
|
84
98
|
if (depth > 1) {
|
|
85
|
-
const relatedFiles = getRelatedKGFiles(file.id,
|
|
86
|
-
.map(f => ({ id: f.id, path: f.path, summary: f.summary }))
|
|
87
|
-
.slice(0, 5); // cap children
|
|
99
|
+
const relatedFiles = getRelatedKGFiles(file.path).map(f => ({ id: f.id, path: f.path, summary: f.summary })).slice(0, 5);
|
|
88
100
|
const relatedNodes = relatedFiles.map(f => buildFileTree(f, depth - 1, visited));
|
|
89
101
|
if (relatedNodes.length)
|
|
90
102
|
node.related = relatedNodes;
|
|
@@ -93,7 +105,56 @@ export async function buildContextualPrompt({ topFile, query, kgDepth = 3, }) {
|
|
|
93
105
|
}
|
|
94
106
|
const kgTree = buildFileTree({ id: topFile.id, path: topFile.path, summary: topFile.summary }, kgDepth);
|
|
95
107
|
promptSections.push(`**KG-Related Files (JSON tree, depth ${kgDepth}):**\n\`\`\`json\n${JSON.stringify(kgTree, null, 2)}\n\`\`\``);
|
|
96
|
-
|
|
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
|
+
// 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(" | ");
|
|
124
|
+
callsByFunction[fn.name || fn.unique_id] = {
|
|
125
|
+
calls: rows.map(r => ({ unique_id: r.target_unique_id })),
|
|
126
|
+
preview,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
if (Object.keys(callsByFunction).length > 0) {
|
|
130
|
+
promptSections.push(`**Function-level calls (limited, JSON):**\n\`\`\`json\n${JSON.stringify(callsByFunction, null, 2)}\n\`\`\``);
|
|
131
|
+
}
|
|
132
|
+
// --- Function-level "called by" overview (limited) ---
|
|
133
|
+
const calledByAll = db.prepare(`
|
|
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);
|
|
141
|
+
const calledByByFunction = {};
|
|
142
|
+
for (const fn of functionRows) {
|
|
143
|
+
const rows = calledByAll
|
|
144
|
+
.filter(r => r.target_unique_id === fn.unique_id)
|
|
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(" | ");
|
|
149
|
+
calledByByFunction[fn.name || fn.unique_id] = {
|
|
150
|
+
calledBy: rows.map(r => ({ unique_id: r.source_unique_id })),
|
|
151
|
+
preview,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
if (Object.keys(calledByByFunction).length > 0) {
|
|
155
|
+
promptSections.push(`**Function-level called-by (limited, JSON):**\n\`\`\`json\n${JSON.stringify(calledByByFunction, null, 2)}\n\`\`\``);
|
|
156
|
+
}
|
|
157
|
+
// --- Focused file tree (shallow) ---
|
|
97
158
|
try {
|
|
98
159
|
const fileTree = generateFocusedFileTree(topFile.path, 2);
|
|
99
160
|
if (fileTree) {
|
|
@@ -103,7 +164,7 @@ export async function buildContextualPrompt({ topFile, query, kgDepth = 3, }) {
|
|
|
103
164
|
catch (e) {
|
|
104
165
|
console.warn("⚠️ Could not generate file tree:", e);
|
|
105
166
|
}
|
|
106
|
-
// ---
|
|
167
|
+
// --- Optional code snippet ---
|
|
107
168
|
const MAX_LINES = 50;
|
|
108
169
|
const queryNeedsCode = /\b(code|implementation|function|snippet)\b/i.test(query);
|
|
109
170
|
if ((!topFile.summary || queryNeedsCode) && topFile.code) {
|
|
@@ -113,7 +174,7 @@ export async function buildContextualPrompt({ topFile, query, kgDepth = 3, }) {
|
|
|
113
174
|
snippet += "\n... [truncated]";
|
|
114
175
|
promptSections.push(`**Code Context (first ${MAX_LINES} lines):**\n\`\`\`\n${snippet}\n\`\`\``);
|
|
115
176
|
}
|
|
116
|
-
// ---
|
|
177
|
+
// --- User query ---
|
|
117
178
|
promptSections.push(`**Query:** ${query}`);
|
|
118
179
|
const promptText = promptSections.join("\n\n---\n\n");
|
|
119
180
|
log("✅ Contextual prompt built for:", topFile.path);
|
|
@@ -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
|
+
}
|
package/package.json
CHANGED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
// src/commands/MigrateCmd.ts
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import { pathToFileURL } from 'url';
|
|
4
|
-
import { fileURLToPath } from 'url';
|
|
5
|
-
export async function runMigrateCommand() {
|
|
6
|
-
const scriptPath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../dist/scripts/migrateDb.js' // compiled output
|
|
7
|
-
);
|
|
8
|
-
try {
|
|
9
|
-
await import(pathToFileURL(scriptPath).href);
|
|
10
|
-
}
|
|
11
|
-
catch (err) {
|
|
12
|
-
console.error('❌ Failed to run migration script:', err);
|
|
13
|
-
process.exit(1);
|
|
14
|
-
}
|
|
15
|
-
}
|