scai 0.1.114 → 0.1.115
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.
|
@@ -6,33 +6,51 @@ 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
|
-
//
|
|
9
|
+
// --- Utility: Summarize text to a few words ---
|
|
10
10
|
function summarizeForPrompt(summary, maxWords = 30) {
|
|
11
11
|
if (!summary)
|
|
12
12
|
return undefined;
|
|
13
13
|
const words = summary.split(/\s+/);
|
|
14
|
-
|
|
15
|
-
return summary.trim();
|
|
16
|
-
return words.slice(0, maxWords).join(" ") + " …";
|
|
14
|
+
return words.length <= maxWords ? summary.trim() : words.slice(0, maxWords).join(" ") + " …";
|
|
17
15
|
}
|
|
18
16
|
// --- Step 1: Top file summary ---
|
|
19
17
|
if (topFile.summary) {
|
|
20
18
|
promptSections.push(`**Top file:** ${topFile.path}\n${topFile.summary}`);
|
|
21
19
|
seenPaths.add(topFile.path);
|
|
22
20
|
}
|
|
23
|
-
//
|
|
24
|
-
|
|
21
|
+
// ===========================
|
|
22
|
+
// SECTION A: Database queries
|
|
23
|
+
// ===========================
|
|
24
|
+
// --- Step 2a: KG entities/tags ---
|
|
25
|
+
const topEntitiesRows = db.prepare(`
|
|
25
26
|
SELECT et.entity_type, et.entity_id, tm.name AS tag
|
|
26
27
|
FROM entity_tags et
|
|
27
28
|
JOIN tags_master tm ON et.tag_id = tm.id
|
|
28
29
|
WHERE et.entity_id = ?
|
|
29
|
-
`);
|
|
30
|
-
const topEntitiesRows = topEntitiesStmt.all(topFile.id);
|
|
30
|
+
`).all(topFile.id);
|
|
31
31
|
if (topEntitiesRows.length > 0) {
|
|
32
32
|
const tags = topEntitiesRows.map(r => `- **${r.entity_type}**: ${r.tag}`);
|
|
33
33
|
promptSections.push(`**Knowledge Graph context for ${topFile.path}:**\n${tags.join("\n")}`);
|
|
34
34
|
}
|
|
35
|
-
// --- Step
|
|
35
|
+
// --- Step 2b: Functions in this file ---
|
|
36
|
+
const functionRows = db
|
|
37
|
+
.prepare(`SELECT name, start_line, end_line, content FROM functions WHERE file_id = ? ORDER BY start_line`)
|
|
38
|
+
.all(topFile.id);
|
|
39
|
+
const FUNCTION_LIMIT = 15;
|
|
40
|
+
const hasMoreFunctions = functionRows.length > FUNCTION_LIMIT;
|
|
41
|
+
const functionsSummary = functionRows.slice(0, FUNCTION_LIMIT).map(f => {
|
|
42
|
+
const lines = f.content?.split("\n").map(l => l.trim()).filter(Boolean) || ["[no content]"];
|
|
43
|
+
const preview = lines.slice(0, 3) // first 3 lines
|
|
44
|
+
.map(l => l.slice(0, 200) + (l.length > 200 ? "…" : ""))
|
|
45
|
+
.join(" | ");
|
|
46
|
+
return `- ${f.name || "[anonymous]"} (lines ${f.start_line}-${f.end_line}) — ${preview}`;
|
|
47
|
+
});
|
|
48
|
+
if (functionsSummary.length) {
|
|
49
|
+
promptSections.push(`**Functions in ${topFile.path} (showing ${functionsSummary.length}${hasMoreFunctions ? ` of ${functionRows.length}` : ""}):**\n${functionsSummary.join("\n")}`);
|
|
50
|
+
}
|
|
51
|
+
// ===============================
|
|
52
|
+
// SECTION B: Graph / KG traversal
|
|
53
|
+
// ===============================
|
|
36
54
|
const kgRelatedStmt = db.prepare(`
|
|
37
55
|
SELECT DISTINCT f.id, f.path, f.summary
|
|
38
56
|
FROM edges e
|
|
@@ -42,18 +60,10 @@ export async function buildContextualPrompt({ topFile, query, kgDepth = 3, }) {
|
|
|
42
60
|
AND e.source_id = ?
|
|
43
61
|
`);
|
|
44
62
|
function getRelatedKGFiles(fileId, visited = new Set()) {
|
|
45
|
-
if (visited.has(fileId))
|
|
46
|
-
log(`🔹 Already visited fileId ${fileId}, skipping`);
|
|
63
|
+
if (visited.has(fileId))
|
|
47
64
|
return [];
|
|
48
|
-
}
|
|
49
65
|
visited.add(fileId);
|
|
50
66
|
const rows = kgRelatedStmt.all(fileId);
|
|
51
|
-
if (rows.length === 0) {
|
|
52
|
-
log(`⚠️ No edges found for fileId ${fileId}`);
|
|
53
|
-
}
|
|
54
|
-
else {
|
|
55
|
-
log(`🔹 Found ${rows.length} related files for fileId ${fileId}:`, rows.map(r => r.path));
|
|
56
|
-
}
|
|
57
67
|
let results = [];
|
|
58
68
|
for (const row of rows) {
|
|
59
69
|
results.push(row);
|
|
@@ -62,12 +72,9 @@ export async function buildContextualPrompt({ topFile, query, kgDepth = 3, }) {
|
|
|
62
72
|
return results;
|
|
63
73
|
}
|
|
64
74
|
function buildFileTree(file, depth, visited = new Set()) {
|
|
65
|
-
|
|
66
|
-
if (visited.has(file.id)) {
|
|
75
|
+
if (visited.has(file.id))
|
|
67
76
|
return { id: file.id.toString(), path: file.path };
|
|
68
|
-
}
|
|
69
77
|
visited.add(file.id);
|
|
70
|
-
// progressively shorten summaries, drop at depth <= 1
|
|
71
78
|
const maxWordsByDepth = depth >= 3 ? 30 : depth === 2 ? 15 : 0;
|
|
72
79
|
const node = {
|
|
73
80
|
id: file.id.toString(),
|
|
@@ -78,7 +85,6 @@ export async function buildContextualPrompt({ topFile, query, kgDepth = 3, }) {
|
|
|
78
85
|
const relatedFiles = getRelatedKGFiles(file.id, visited)
|
|
79
86
|
.map(f => ({ id: f.id, path: f.path, summary: f.summary }))
|
|
80
87
|
.slice(0, 5); // cap children
|
|
81
|
-
log(`File ${file.path} has ${relatedFiles.length} related files`);
|
|
82
88
|
const relatedNodes = relatedFiles.map(f => buildFileTree(f, depth - 1, visited));
|
|
83
89
|
if (relatedNodes.length)
|
|
84
90
|
node.related = relatedNodes;
|
|
@@ -86,12 +92,10 @@ export async function buildContextualPrompt({ topFile, query, kgDepth = 3, }) {
|
|
|
86
92
|
return node;
|
|
87
93
|
}
|
|
88
94
|
const kgTree = buildFileTree({ id: topFile.id, path: topFile.path, summary: topFile.summary }, kgDepth);
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
// --- Step 4: File tree (shallow, depth 2) ---
|
|
92
|
-
let fileTree = "";
|
|
95
|
+
promptSections.push(`**KG-Related Files (JSON tree, depth ${kgDepth}):**\n\`\`\`json\n${JSON.stringify(kgTree, null, 2)}\n\`\`\``);
|
|
96
|
+
// --- Step 3: File tree (shallow) ---
|
|
93
97
|
try {
|
|
94
|
-
fileTree = generateFocusedFileTree(topFile.path, 2);
|
|
98
|
+
const fileTree = generateFocusedFileTree(topFile.path, 2);
|
|
95
99
|
if (fileTree) {
|
|
96
100
|
promptSections.push(`**Focused File Tree (depth 2):**\n\`\`\`\n${fileTree}\n\`\`\``);
|
|
97
101
|
}
|
|
@@ -99,21 +103,18 @@ export async function buildContextualPrompt({ topFile, query, kgDepth = 3, }) {
|
|
|
99
103
|
catch (e) {
|
|
100
104
|
console.warn("⚠️ Could not generate file tree:", e);
|
|
101
105
|
}
|
|
102
|
-
// --- Step
|
|
103
|
-
// Only include raw code if no summary exists, or if the query explicitly asks for it
|
|
106
|
+
// --- Step 4: Optional code snippet ---
|
|
104
107
|
const MAX_LINES = 50;
|
|
105
108
|
const queryNeedsCode = /\b(code|implementation|function|snippet)\b/i.test(query);
|
|
106
109
|
if ((!topFile.summary || queryNeedsCode) && topFile.code) {
|
|
107
110
|
const lines = topFile.code.split("\n").slice(0, MAX_LINES);
|
|
108
111
|
let snippet = lines.join("\n");
|
|
109
|
-
if (topFile.code.split("\n").length > MAX_LINES)
|
|
112
|
+
if (topFile.code.split("\n").length > MAX_LINES)
|
|
110
113
|
snippet += "\n... [truncated]";
|
|
111
|
-
}
|
|
112
114
|
promptSections.push(`**Code Context (first ${MAX_LINES} lines):**\n\`\`\`\n${snippet}\n\`\`\``);
|
|
113
115
|
}
|
|
114
|
-
// --- Step
|
|
116
|
+
// --- Step 5: User query ---
|
|
115
117
|
promptSections.push(`**Query:** ${query}`);
|
|
116
|
-
// --- Step 7: Combine prompt ---
|
|
117
118
|
const promptText = promptSections.join("\n\n---\n\n");
|
|
118
119
|
log("✅ Contextual prompt built for:", topFile.path);
|
|
119
120
|
log("📄 Prompt preview:\n", promptText + "\n");
|