scai 0.1.115 โ†’ 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.
@@ -104,6 +104,26 @@ export const cleanupModule = {
104
104
  break;
105
105
  }
106
106
  }
107
- return { content: jsonLines.join('\n').trim() };
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
  };
@@ -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
- // === Basic Stats ===
22
- const stats = {
23
- total: db.prepare('SELECT COUNT(*) as count FROM files').get().count,
24
- withSummary: db.prepare(`SELECT COUNT(*) as count FROM files WHERE summary IS NOT NULL AND summary != ''`).get().count,
25
- withoutSummary: db.prepare(`SELECT COUNT(*) as count FROM files WHERE summary IS NULL OR summary = ''`).get().count,
26
- withEmbedding: db.prepare(`SELECT COUNT(*) as count FROM files WHERE embedding IS NOT NULL AND embedding != ''`).get().count,
27
- withoutEmbedding: db.prepare(`SELECT COUNT(*) as count FROM files WHERE embedding IS NULL OR embedding = ''`).get().count,
28
- withProcessingStatusExtracted: db.prepare(`SELECT COUNT(*) as count FROM files WHERE processing_status = 'extracted'`).get().count,
29
- withoutProcessingStatusExtracted: db.prepare(`SELECT COUNT(*) as count FROM files WHERE processing_status IS NULL OR processing_status != 'extracted'`).get().count,
30
- withProcessingStatusSkipped: db.prepare(`SELECT COUNT(*) as count FROM files WHERE processing_status = 'skipped'`).get().count,
31
- withProcessingStatusFailed: db.prepare(`SELECT COUNT(*) as count FROM files WHERE processing_status = 'failed'`).get().count,
32
- withProcessingStatusUnprocessed: db.prepare(`SELECT COUNT(*) as count FROM files WHERE processing_status = 'unprocessed'`).get().count,
33
- };
34
- console.log("๐Ÿ“Š SQLite Stats for Table: files");
35
- console.log("-------------------------------------------");
36
- console.log(`๐Ÿ”ข Total rows: ${stats.total}`);
37
- console.log(`โœ… With summary: ${stats.withSummary}`);
38
- console.log(`โŒ Without summary: ${stats.withoutSummary}`);
39
- console.log(`โœ… With embedding: ${stats.withEmbedding}`);
40
- console.log(`โŒ Without embedding: ${stats.withoutEmbedding}`);
41
- console.log(`โœ… With processing_status = 'extracted': ${stats.withProcessingStatusExtracted}`);
42
- console.log(`โŒ Without processing_status = 'extracted': ${stats.withoutProcessingStatusExtracted}`);
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
- summaries.forEach(row => {
56
- console.log(`${row.id.toString().padEnd(4)} ${row.short_summary}`);
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
- console.log("\nExample embeddings (first 50 characters):");
59
- const embeddings = db.prepare(`
60
- SELECT id, substr(embedding, 1, 50) || '...' AS short_embedding
61
- FROM files
62
- WHERE embedding IS NOT NULL AND embedding != ''
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
- embeddings.forEach(row => {
66
- console.log(`${row.id.toString().padEnd(4)} ${row.short_embedding}`);
61
+ sampleFuncs.forEach(f => {
62
+ console.log(` [${f.id}] ${f.name} (file ${f.file_id}): ${f.preview}`);
67
63
  });
68
- // === FTS5 Check ===
69
- console.log("\n๐Ÿ” FTS5 Check");
70
- console.log("--------------------------");
71
- try {
72
- const ftsExists = db.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='files_fts'`).get();
73
- if (!ftsExists) {
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
- randomSummaries.forEach(row => {
194
- console.log(`๐Ÿ“„ [${row.id}] ${row.filename}: ${row.preview}`);
73
+ sampleClasses.forEach(c => {
74
+ console.log(` [${c.id}] ${c.name} (file ${c.file_id}): ${c.preview}`);
195
75
  });
196
- // === Random Functions Samples ===
197
- console.log('\n๐Ÿง‘โ€๐Ÿ’ป 20 Random Functions (ID, name, body):');
198
- console.log('-------------------------------------------');
199
- const randomFunctions = db.prepare(`
200
- SELECT id, name, file_id, substr(content, 1, 100) || '...' AS preview
201
- FROM functions
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
- randomFunctions.forEach(row => {
206
- console.log(`๐Ÿ”น [${row.id}] ${row.name} (file_id: ${row.file_id})`);
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
- // === Column View of Files ===
210
- console.log('\n๐Ÿ“Š Table View: Files');
211
- console.log('-------------------------------------------');
212
- const fileRows = db.prepare(`
213
- SELECT id, filename, type, processing_status, functions_extracted_at, length(summary) AS summary_len
214
- FROM files
215
- LIMIT 50
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
- console.table(fileRows);
218
- // === Column View of Functions ===
219
- console.log('\n๐Ÿ“Š Table View: Functions');
220
- console.log('-------------------------------------------');
221
- const functionRows = db.prepare(`
222
- SELECT id, file_id, name, start_line, end_line, length(content) AS length
223
- FROM functions
224
- LIMIT 50
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
- console.table(functionRows);
227
- // === Class Table Stats ===
228
- console.log('\n๐Ÿ“Š Stats for Table: classes');
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,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
- // --- Step 1: Top file summary ---
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.entity_id, tm.name AS tag
27
- FROM entity_tags et
28
- JOIN tags_master tm ON et.tag_id = tm.id
29
- WHERE et.entity_id = ?
30
- `).all(topFile.id);
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
- // --- Step 2b: Functions in this file ---
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 = 15;
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) // first 3 lines
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
- // SECTION B: Graph / KG traversal
67
+ // Graph / KG traversal
53
68
  // ===============================
54
- const kgRelatedStmt = db.prepare(`
55
- SELECT DISTINCT f.id, f.path, f.summary
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(fileId);
66
- const rows = kgRelatedStmt.all(fileId);
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.id, visited));
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.id))
89
+ if (visited.has(file.path))
76
90
  return { id: file.id.toString(), path: file.path };
77
- visited.add(file.id);
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, visited)
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,48 @@ 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
- // --- Step 3: File tree (shallow) ---
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) ---
97
150
  try {
98
151
  const fileTree = generateFocusedFileTree(topFile.path, 2);
99
152
  if (fileTree) {
@@ -103,7 +156,7 @@ export async function buildContextualPrompt({ topFile, query, kgDepth = 3, }) {
103
156
  catch (e) {
104
157
  console.warn("โš ๏ธ Could not generate file tree:", e);
105
158
  }
106
- // --- Step 4: Optional code snippet ---
159
+ // --- Optional code snippet ---
107
160
  const MAX_LINES = 50;
108
161
  const queryNeedsCode = /\b(code|implementation|function|snippet)\b/i.test(query);
109
162
  if ((!topFile.summary || queryNeedsCode) && topFile.code) {
@@ -113,7 +166,7 @@ export async function buildContextualPrompt({ topFile, query, kgDepth = 3, }) {
113
166
  snippet += "\n... [truncated]";
114
167
  promptSections.push(`**Code Context (first ${MAX_LINES} lines):**\n\`\`\`\n${snippet}\n\`\`\``);
115
168
  }
116
- // --- Step 5: User query ---
169
+ // --- User query ---
117
170
  promptSections.push(`**Query:** ${query}`);
118
171
  const promptText = promptSections.join("\n\n---\n\n");
119
172
  log("โœ… Contextual prompt built for:", topFile.path);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scai",
3
- "version": "0.1.115",
3
+ "version": "0.1.116",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "scai": "./dist/index.js"
@@ -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
- }