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
- // ✂️ Word-based summarizer with progressive shortening
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
- if (words.length <= maxWords)
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
- // --- Step 2: KG entities/tags for top file ---
24
- const topEntitiesStmt = db.prepare(`
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 3: Recursive KG traversal ---
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
- log(`buildFileTree - file=${file.path}, depth=${depth}`);
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
- const kgJson = JSON.stringify(kgTree, null, 2);
90
- promptSections.push(`**KG-Related Files (JSON tree, depth ${kgDepth}):**\n\`\`\`json\n${kgJson}\n\`\`\``);
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 5: Code snippet ---
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 6: User query ---
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");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scai",
3
- "version": "0.1.114",
3
+ "version": "0.1.115",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "scai": "./dist/index.js"