scai 0.1.117 β†’ 0.1.119

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.
Files changed (96) hide show
  1. package/README.md +88 -503
  2. package/dist/agents/MainAgent.js +255 -0
  3. package/dist/agents/contextReviewStep.js +104 -0
  4. package/dist/agents/finalPlanGenStep.js +123 -0
  5. package/dist/agents/infoPlanGenStep.js +126 -0
  6. package/dist/agents/planGeneratorStep.js +118 -0
  7. package/dist/agents/planResolverStep.js +95 -0
  8. package/dist/agents/planTargetFilesStep.js +48 -0
  9. package/dist/agents/preFileSearchCheckStep.js +95 -0
  10. package/dist/agents/selectRelevantSourcesStep.js +100 -0
  11. package/dist/agents/semanticAnalysisStep.js +144 -0
  12. package/dist/agents/structuralAnalysisStep.js +46 -0
  13. package/dist/agents/transformPlanGenStep.js +107 -0
  14. package/dist/agents/understandIntentStep.js +72 -0
  15. package/dist/agents/validationAnalysisStep.js +87 -0
  16. package/dist/commands/AskCmd.js +47 -116
  17. package/dist/commands/ChangeLogUpdateCmd.js +11 -5
  18. package/dist/commands/CommitSuggesterCmd.js +50 -75
  19. package/dist/commands/DaemonCmd.js +119 -29
  20. package/dist/commands/IndexCmd.js +41 -24
  21. package/dist/commands/InspectCmd.js +0 -1
  22. package/dist/commands/ReadlineSingleton.js +18 -0
  23. package/dist/commands/ResetDbCmd.js +20 -21
  24. package/dist/commands/ReviewCmd.js +89 -54
  25. package/dist/commands/SummaryCmd.js +12 -18
  26. package/dist/commands/WorkflowCmd.js +41 -0
  27. package/dist/commands/factory.js +254 -0
  28. package/dist/config.js +67 -15
  29. package/dist/constants.js +20 -4
  30. package/dist/context.js +10 -11
  31. package/dist/daemon/daemonQueues.js +63 -0
  32. package/dist/daemon/daemonWorker.js +40 -63
  33. package/dist/daemon/generateSummaries.js +58 -0
  34. package/dist/daemon/runFolderCapsuleBatch.js +247 -0
  35. package/dist/daemon/runIndexingBatch.js +147 -0
  36. package/dist/daemon/runKgBatch.js +104 -0
  37. package/dist/db/fileIndex.js +168 -63
  38. package/dist/db/functionExtractors/extractFromJava.js +210 -6
  39. package/dist/db/functionExtractors/extractFromJs.js +173 -214
  40. package/dist/db/functionExtractors/extractFromTs.js +159 -160
  41. package/dist/db/functionExtractors/index.js +7 -5
  42. package/dist/db/schema.js +55 -20
  43. package/dist/db/sqlTemplates.js +50 -19
  44. package/dist/fileRules/builtins.js +31 -14
  45. package/dist/fileRules/codeAllowedExtensions.js +4 -0
  46. package/dist/fileRules/fileExceptions.js +0 -13
  47. package/dist/fileRules/ignoredExtensions.js +10 -0
  48. package/dist/index.js +128 -325
  49. package/dist/lib/generate.js +37 -14
  50. package/dist/lib/generateFolderCapsules.js +109 -0
  51. package/dist/lib/spinner.js +12 -5
  52. package/dist/modelSetup.js +1 -11
  53. package/dist/pipeline/modules/changeLogModule.js +16 -19
  54. package/dist/pipeline/modules/chunkManagerModule.js +24 -0
  55. package/dist/pipeline/modules/cleanupModule.js +95 -91
  56. package/dist/pipeline/modules/codeTransformModule.js +208 -0
  57. package/dist/pipeline/modules/commentModule.js +20 -11
  58. package/dist/pipeline/modules/commitSuggesterModule.js +36 -14
  59. package/dist/pipeline/modules/contextReviewModule.js +52 -0
  60. package/dist/pipeline/modules/fileReaderModule.js +72 -0
  61. package/dist/pipeline/modules/fileSearchModule.js +136 -0
  62. package/dist/pipeline/modules/finalAnswerModule.js +53 -0
  63. package/dist/pipeline/modules/gatherInfoModule.js +176 -0
  64. package/dist/pipeline/modules/generateTestsModule.js +63 -54
  65. package/dist/pipeline/modules/kgModule.js +26 -11
  66. package/dist/pipeline/modules/preserveCodeModule.js +91 -49
  67. package/dist/pipeline/modules/refactorModule.js +19 -7
  68. package/dist/pipeline/modules/repairTestsModule.js +44 -36
  69. package/dist/pipeline/modules/reviewModule.js +23 -13
  70. package/dist/pipeline/modules/summaryModule.js +27 -35
  71. package/dist/pipeline/modules/writeFileModule.js +86 -0
  72. package/dist/pipeline/registry/moduleRegistry.js +38 -93
  73. package/dist/pipeline/runModulePipeline.js +22 -19
  74. package/dist/scripts/dbcheck.js +143 -228
  75. package/dist/utils/buildContextualPrompt.js +245 -172
  76. package/dist/utils/debugContext.js +24 -0
  77. package/dist/utils/fileTree.js +16 -6
  78. package/dist/utils/loadRelevantFolderCapsules.js +64 -0
  79. package/dist/utils/log.js +2 -0
  80. package/dist/utils/normalizeData.js +23 -0
  81. package/dist/utils/planActions.js +60 -0
  82. package/dist/utils/promptBuilderHelper.js +67 -0
  83. package/dist/utils/promptLogHelper.js +52 -0
  84. package/dist/utils/sanitizeQuery.js +20 -8
  85. package/dist/utils/sleep.js +3 -0
  86. package/dist/utils/splitCodeIntoChunk.js +65 -32
  87. package/dist/utils/vscode.js +49 -0
  88. package/dist/workflow/workflowResolver.js +14 -0
  89. package/dist/workflow/workflowRunner.js +103 -0
  90. package/package.json +6 -5
  91. package/dist/agent/agentManager.js +0 -39
  92. package/dist/agent/workflowManager.js +0 -95
  93. package/dist/commands/ModulePipelineCmd.js +0 -31
  94. package/dist/daemon/daemonBatch.js +0 -186
  95. package/dist/fileRules/scoreFiles.js +0 -71
  96. package/dist/lib/generateEmbedding.js +0 -22
@@ -1,269 +1,184 @@
1
1
  import Database from "better-sqlite3";
2
2
  import path from "path";
3
3
  import os from "os";
4
- import { Config } from "../config.js";
5
4
  import fs from "fs";
5
+ import { execSync } from "child_process";
6
+ import { Config } from "../config.js";
7
+ /* ───────────────────────── bootstrap ───────────────────────── */
6
8
  const cfg = Config.getRaw();
7
9
  const repoKey = cfg.activeRepo;
8
10
  if (!repoKey) {
9
- console.error("❌ No active repo found. Use `scai set-index` to set one.");
11
+ console.error("❌ No active repo found. Use `scai set-index`.");
10
12
  process.exit(1);
11
13
  }
12
14
  const repoName = path.basename(repoKey);
13
15
  const scaiRepoRoot = path.join(os.homedir(), ".scai", "repos", repoName);
14
16
  const dbPath = path.join(scaiRepoRoot, "db.sqlite");
15
17
  if (!fs.existsSync(dbPath)) {
16
- console.error(`❌ No database found at ${dbPath}`);
18
+ console.error(`❌ DB not found: ${dbPath}`);
17
19
  process.exit(1);
18
20
  }
19
21
  const db = new Database(dbPath);
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}`);
22
+ /* ───────────────────────── helpers ───────────────────────── */
23
+ function tableCount(table) {
24
+ return db.prepare(`SELECT COUNT(*) AS c FROM ${table}`).get().c;
29
25
  }
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}`);
26
+ function nonEmptyCount(table, col) {
27
+ return db.prepare(`SELECT COUNT(*) AS c FROM ${table} WHERE ${col} IS NOT NULL AND ${col} != ''`).get().c;
38
28
  }
39
- // === Example Files ===
40
- const exampleFiles = db.prepare(`
41
- SELECT id, filename, substr(summary,1,50) AS summary, substr(embedding,1,50) AS embedding
29
+ function header(title) {
30
+ console.log(`\n${title}`);
31
+ }
32
+ /* ───────────────────────── files ───────────────────────── */
33
+ header("πŸ—‚ files");
34
+ const totalFiles = tableCount("files");
35
+ const filesWithContent = nonEmptyCount("files", "content_text");
36
+ console.log(`πŸ“Š total files: ${totalFiles}`);
37
+ console.log(`πŸ“„ files with content: ${filesWithContent}`);
38
+ header("βš™οΈ processing_status");
39
+ const statuses = db.prepare(`
40
+ SELECT processing_status, COUNT(*) AS count
42
41
  FROM files
43
- LIMIT 5
44
- `).all();
45
- console.log("\n🧾 Example Files:");
46
- exampleFiles.forEach(f => {
47
- console.log(` [${f.id}] ${f.filename} | summary: "${f.summary}" | embedding: "${f.embedding}"`);
48
- });
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()
59
- LIMIT 5
42
+ GROUP BY processing_status
43
+ ORDER BY count DESC
60
44
  `).all();
61
- sampleFuncs.forEach(f => {
62
- console.log(` [${f.id}] ${f.name} (file ${f.file_id}): ${f.preview}`);
63
- });
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
70
- ORDER BY RANDOM()
71
- LIMIT 5
72
- `).all();
73
- sampleClasses.forEach(c => {
74
- console.log(` [${c.id}] ${c.name} (file ${c.file_id}): ${c.preview}`);
75
- });
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
90
- ORDER BY RANDOM()
91
- LIMIT 5
92
- `).all();
93
- sampleEdges.forEach(e => {
94
- console.log(` [${e.id}] ${e.source_type}(${e.source_id}) -[${e.relation}]-> ${e.target_type}(${e.target_id})`);
95
- });
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
45
+ statuses.forEach(s => console.log(` ${s.processing_status ?? "NULL"}: ${s.count}`));
46
+ /* ───────────────────────── FTS ───────────────────────── */
47
+ header("πŸ” files_fts");
48
+ const ftsExists = db
49
+ .prepare(`SELECT 1 FROM sqlite_master WHERE type='table' AND name='files_fts'`)
50
+ .get();
51
+ if (!ftsExists) {
52
+ console.log("❌ files_fts missing β€” search is broken");
53
+ }
54
+ else {
55
+ const cols = db.prepare(`PRAGMA table_info(files_fts)`).all();
56
+ const hasContentText = cols.some(c => c.name === "content_text");
57
+ if (!hasContentText) {
58
+ console.log("❌ files_fts.content_text missing");
59
+ }
60
+ else {
61
+ const indexed = nonEmptyCount("files_fts", "content_text");
62
+ console.log(`πŸ“¦ indexed rows with content_text: ${indexed}`);
63
+ const sample = db.prepare(`
64
+ SELECT filename, substr(content_text,1,80) AS preview
65
+ FROM files_fts
66
+ WHERE files_fts MATCH 'function'
67
+ LIMIT 3
68
+ `).all();
69
+ if (sample.length === 0) {
70
+ console.log("⚠️ no FTS hits for 'function'");
71
+ }
72
+ else {
73
+ console.log("βœ… FTS sample:");
74
+ sample.forEach(r => console.log(` ${r.filename} | "${r.preview}"`));
75
+ }
76
+ }
77
+ }
78
+ /* ───────────────────────── folder capsules ───────────────────────── */
79
+ header("πŸ“ folder_capsules");
80
+ console.log(`πŸ“Š total capsules: ${tableCount("folder_capsules")}`);
81
+ const emptyCapsules = db.prepare(`
82
+ SELECT COUNT(*) AS c
83
+ FROM folder_capsules
84
+ WHERE capsule_json IS NULL OR capsule_json = ''
85
+ `).get();
86
+ if (emptyCapsules.c > 0) {
87
+ console.log(`⚠️ ${emptyCapsules.c} capsules have empty JSON`);
88
+ }
89
+ const sampleCaps = db.prepare(`
90
+ SELECT path, depth, confidence, source_file_count, capsule_json
91
+ FROM folder_capsules
102
92
  ORDER BY RANDOM()
103
93
  LIMIT 5
104
94
  `).all();
105
- sampleTags.forEach(t => {
106
- console.log(` [${t.id}] ${t.name}`);
95
+ sampleCaps.forEach(c => {
96
+ let parsed = null;
97
+ try {
98
+ parsed = JSON.parse(c.capsule_json);
99
+ }
100
+ catch { }
101
+ console.log(` ${c.path}`);
102
+ console.log(` depth=${c.depth} confidence=${c.confidence} files=${c.source_file_count}`);
103
+ if (parsed) {
104
+ console.log(` roles=${parsed.roles?.length ?? 0} ` +
105
+ `concerns=${parsed.concerns?.length ?? 0} ` +
106
+ `keyFiles=${parsed.keyFiles?.length ?? 0}`);
107
+ }
107
108
  });
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
109
+ /* ───────────────────────── functions ───────────────────────── */
110
+ header("πŸ§‘β€πŸ’» functions");
111
+ console.log(`πŸ“Š total functions: ${tableCount("functions")}`);
112
+ const funcSamples = db.prepare(`
113
+ SELECT id, name, file_id, substr(content,1,60) AS preview
114
+ FROM functions
113
115
  ORDER BY RANDOM()
114
116
  LIMIT 5
115
117
  `).all();
116
- sampleEntityTags.forEach(et => {
117
- console.log(` [${et.id}] ${et.entity_type}(${et.entity_id}) -> tag ${et.tag_id}`);
118
- });
119
- // === Function β†’ Edges Check ===
120
- console.log("\nπŸ•Έ Function Calls / CalledBy consistency");
121
- // 1. Check for edges pointing to missing functions
122
- const missingFuncs = db.prepare(`
123
- SELECT e.id, e.source_unique_id, e.target_unique_id
118
+ funcSamples.forEach(f => console.log(` [${f.id}] ${f.name} (file ${f.file_id}) "${f.preview}"`));
119
+ /* ───────────────────────── graph (KG) ───────────────────────── */
120
+ header("🏷 graph_classes");
121
+ console.log(`πŸ“Š total classes: ${tableCount("graph_classes")}`);
122
+ header("πŸ”— graph_edges");
123
+ console.log(`πŸ“Š total edges: ${tableCount("graph_edges")}`);
124
+ const danglingFuncs = db.prepare(`
125
+ SELECT e.id
124
126
  FROM graph_edges e
125
- WHERE e.source_type = 'function'
127
+ WHERE e.source_type='function'
126
128
  AND NOT EXISTS (SELECT 1 FROM functions f WHERE f.unique_id = e.source_unique_id)
127
129
  UNION
128
- SELECT e.id, e.source_unique_id, e.target_unique_id
130
+ SELECT e.id
129
131
  FROM graph_edges e
130
- WHERE e.target_type = 'function'
132
+ WHERE e.target_type='function'
131
133
  AND NOT EXISTS (SELECT 1 FROM functions f WHERE f.unique_id = e.target_unique_id)
132
134
  LIMIT 10
133
135
  `).all();
134
- if (missingFuncs.length === 0) {
135
- console.log("βœ… All edges reference valid functions.");
136
+ if (danglingFuncs.length === 0) {
137
+ console.log("βœ… no dangling function edges");
136
138
  }
137
139
  else {
138
- console.log("❌ Found edges pointing to missing functions:");
139
- missingFuncs.forEach(e => {
140
- console.log(` Edge ${e.id}: ${e.source_unique_id} -> ${e.target_unique_id}`);
141
- });
140
+ console.log(`❌ dangling function edges: ${danglingFuncs.length}`);
142
141
  }
143
- // 2. Functions with outgoing calls
144
- const funcWithCalls = db.prepare(`
145
- SELECT f.id, f.name, COUNT(e.id) AS callCount
146
- FROM functions f
147
- JOIN graph_edges e
148
- ON e.source_type = 'function'
149
- AND e.source_unique_id = f.unique_id
150
- GROUP BY f.id
151
- HAVING callCount > 0
152
- ORDER BY callCount DESC
153
- LIMIT 5
154
- `).all();
155
- funcWithCalls.forEach(f => {
156
- console.log(` πŸ”Ή Function [${f.id}] ${f.name} has ${f.callCount} outgoing calls`);
157
- });
158
- // 3. Functions with incoming calls
159
- const funcWithCalledBy = db.prepare(`
160
- SELECT f.id, f.name, COUNT(e.id) AS calledByCount
161
- FROM functions f
162
- JOIN graph_edges e
163
- ON e.target_type = 'function'
164
- AND e.target_unique_id = f.unique_id
165
- GROUP BY f.id
166
- HAVING calledByCount > 0
167
- ORDER BY calledByCount DESC
168
- LIMIT 5
169
- `).all();
170
- funcWithCalledBy.forEach(f => {
171
- console.log(` πŸ”Έ Function [${f.id}] ${f.name} is called by ${f.calledByCount} functions`);
172
- });
173
- // 4. Check for duplicate edges (same source→target)
174
- const duplicateEdges = db.prepare(`
175
- SELECT source_unique_id, target_unique_id, COUNT(*) as dupCount
176
- FROM graph_edges
177
- WHERE source_type = 'function' AND target_type = 'function'
178
- GROUP BY source_unique_id, target_unique_id
179
- HAVING dupCount > 1
180
- LIMIT 5
181
- `).all();
182
- if (duplicateEdges.length > 0) {
183
- console.log("⚠️ Duplicate function call edges found:");
184
- duplicateEdges.forEach(d => {
185
- console.log(` ${d.source_unique_id} -> ${d.target_unique_id} (x${d.dupCount})`);
186
- });
187
- }
188
- else {
189
- console.log("βœ… No duplicate function call edges.");
190
- }
191
- // === File-specific check (AskCmd.ts) ===
192
- const targetPath = "/Users/rzs/dev/repos/scai/cli/src/commands/AskCmd.ts";
193
- // --- MUST KEEP: find the file row ---
194
- const targetFile = db.prepare(`
195
- SELECT id, path, filename, processing_status
196
- FROM files
197
- WHERE path = ?
198
- `).get(targetPath);
199
- if (!targetFile) {
200
- console.log(`❌ File not found in DB: ${targetPath}`);
201
- }
202
- else {
203
- console.log(`\nπŸ“‚ File found: [${targetFile.id}] ${targetFile.filename} (${targetFile.processing_status})`);
204
- // --- MUST KEEP: list functions in this file ---
205
- const funcs = db.prepare(`
206
- SELECT id, name, unique_id, start_line, end_line
207
- FROM functions
208
- WHERE file_id = ?
209
- ORDER BY start_line
210
- `).all(targetFile.id);
211
- console.log(` Functions (${funcs.length}):`);
212
- funcs.forEach((f) => {
213
- console.log(` [${f.id}] ${f.name} (${f.unique_id}) lines ${f.start_line}-${f.end_line}`);
214
- });
215
- // --- OPTIONAL: outgoing calls from this file’s functions ---
216
- const outgoing = db.prepare(`
217
- SELECT f.name AS source_name, e.target_unique_id, e.relation
218
- FROM functions f
219
- JOIN graph_edges e
220
- ON e.source_unique_id = f.unique_id
221
- AND e.source_type = 'function'
222
- WHERE f.file_id = ?
223
- `).all(targetFile.id);
224
- console.log(` Outgoing calls (${outgoing.length}):`);
225
- outgoing.forEach((o) => {
226
- console.log(` ${o.source_name} -[${o.relation}]-> ${o.target_unique_id}`);
227
- });
228
- // --- OPTIONAL: incoming calls to this file’s functions ---
229
- const incoming = db.prepare(`
230
- SELECT f.name AS target_name, e.source_unique_id, e.relation
231
- FROM functions f
232
- JOIN graph_edges e
233
- ON e.target_unique_id = f.unique_id
234
- AND e.target_type = 'function'
235
- WHERE f.file_id = ?
236
- `).all(targetFile.id);
237
- console.log(` Incoming calls (${incoming.length}):`);
238
- incoming.forEach((i) => {
239
- console.log(` ${i.source_unique_id} -[${i.relation}]-> ${i.target_name}`);
240
- });
241
- // --- NICE TO HAVE: dangling edges check ---
242
- const dangling = db.prepare(`
243
- SELECT e.id, e.source_unique_id, e.target_unique_id, e.relation
244
- FROM graph_edges e
245
- WHERE e.source_unique_id IN (SELECT unique_id FROM functions WHERE file_id = ?)
246
- OR e.target_unique_id IN (SELECT unique_id FROM functions WHERE file_id = ?)
247
- `).all(targetFile.id, targetFile.id);
248
- console.log(` Edges referencing this file's functions (dangling check): ${dangling.length}`);
249
- dangling.forEach((d) => {
250
- console.log(` Edge ${d.id}: ${d.source_unique_id} -[${d.relation}]-> ${d.target_unique_id}`);
251
- });
252
- // --- OPTIONAL: consistency check summary ---
253
- if (funcs.length > 0 && outgoing.length === 0 && incoming.length === 0 && dangling.length === 0) {
254
- console.log("⚠️ This file has functions but no graph edges. Possible causes:");
255
- console.log(" - Edges were never extracted for this file, OR");
256
- console.log(" - unique_id mismatch between functions and edges.");
142
+ /* ───────────────────────── Graphviz ───────────────────────── */
143
+ header("🧩 graphviz");
144
+ try {
145
+ const edges = db.prepare(`
146
+ SELECT source_type, source_unique_id, target_type, target_unique_id, relation
147
+ FROM graph_edges
148
+ LIMIT 300
149
+ `).all();
150
+ if (edges.length === 0) {
151
+ console.log("⚠️ no edges β€” skipping export");
257
152
  }
258
153
  else {
259
- console.log("βœ… Edges and functions are consistent for this file.");
154
+ const lines = [
155
+ "digraph G {",
156
+ " rankdir=LR;",
157
+ ' node [shape=box, style=filled, color="#eaeaea"];'
158
+ ];
159
+ for (const e of edges) {
160
+ const s = `${e.source_type}_${e.source_unique_id}`.replace(/[^a-zA-Z0-9_]/g, "_");
161
+ const t = `${e.target_type}_${e.target_unique_id}`.replace(/[^a-zA-Z0-9_]/g, "_");
162
+ const label = (e.relation ?? "").replace(/"/g, "'");
163
+ lines.push(` ${s} -> ${t} [label="${label}"];`);
164
+ }
165
+ lines.push("}");
166
+ const outDir = path.join(scaiRepoRoot, "graphs");
167
+ fs.mkdirSync(outDir, { recursive: true });
168
+ const dot = path.join(outDir, "graph-overview.dot");
169
+ const png = path.join(outDir, "graph-overview.png");
170
+ fs.writeFileSync(dot, lines.join("\n"));
171
+ console.log(`βœ… dot written: ${dot}`);
172
+ try {
173
+ execSync(`dot -Tpng "${dot}" -o "${png}"`);
174
+ console.log(`βœ… png written: ${png}`);
175
+ }
176
+ catch {
177
+ console.log("⚠️ graphviz not installed β€” png skipped");
178
+ }
260
179
  }
261
- const funcsDebug = db.prepare(`
262
- SELECT id, name, unique_id
263
- FROM functions
264
- WHERE file_id = ?
265
- `).all(targetFile.id);
266
- console.log("Funcsdebug:\n");
267
- console.log(funcsDebug);
268
180
  }
269
- console.log("\nβœ… DB check completed.\n");
181
+ catch (err) {
182
+ console.error("❌ graphviz export failed", err);
183
+ }
184
+ console.log("\nβœ… DB check complete\n");