scai 0.1.117 β 0.1.118
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/agents/MainAgent.js +255 -0
- package/dist/agents/contextReviewStep.js +104 -0
- package/dist/agents/finalPlanGenStep.js +123 -0
- package/dist/agents/infoPlanGenStep.js +126 -0
- package/dist/agents/planGeneratorStep.js +118 -0
- package/dist/agents/planResolverStep.js +95 -0
- package/dist/agents/planTargetFilesStep.js +48 -0
- package/dist/agents/preFileSearchCheckStep.js +95 -0
- package/dist/agents/selectRelevantSourcesStep.js +100 -0
- package/dist/agents/semanticAnalysisStep.js +144 -0
- package/dist/agents/structuralAnalysisStep.js +46 -0
- package/dist/agents/transformPlanGenStep.js +107 -0
- package/dist/agents/understandIntentStep.js +72 -0
- package/dist/agents/validationAnalysisStep.js +87 -0
- package/dist/commands/AskCmd.js +47 -116
- package/dist/commands/ChangeLogUpdateCmd.js +11 -5
- package/dist/commands/CommitSuggesterCmd.js +50 -75
- package/dist/commands/DaemonCmd.js +119 -29
- package/dist/commands/IndexCmd.js +41 -24
- package/dist/commands/InspectCmd.js +0 -1
- package/dist/commands/ReadlineSingleton.js +18 -0
- package/dist/commands/ResetDbCmd.js +20 -21
- package/dist/commands/ReviewCmd.js +89 -54
- package/dist/commands/SummaryCmd.js +12 -18
- package/dist/commands/WorkflowCmd.js +41 -0
- package/dist/commands/factory.js +254 -0
- package/dist/config.js +67 -15
- package/dist/constants.js +20 -4
- package/dist/context.js +10 -11
- package/dist/daemon/daemonQueues.js +63 -0
- package/dist/daemon/daemonWorker.js +40 -63
- package/dist/daemon/generateSummaries.js +58 -0
- package/dist/daemon/runFolderCapsuleBatch.js +247 -0
- package/dist/daemon/runIndexingBatch.js +147 -0
- package/dist/daemon/runKgBatch.js +104 -0
- package/dist/db/fileIndex.js +168 -63
- package/dist/db/functionExtractors/extractFromJava.js +210 -6
- package/dist/db/functionExtractors/extractFromJs.js +173 -214
- package/dist/db/functionExtractors/extractFromTs.js +159 -160
- package/dist/db/functionExtractors/index.js +7 -5
- package/dist/db/schema.js +55 -20
- package/dist/db/sqlTemplates.js +50 -19
- package/dist/fileRules/builtins.js +31 -14
- package/dist/fileRules/codeAllowedExtensions.js +4 -0
- package/dist/fileRules/fileExceptions.js +0 -13
- package/dist/fileRules/ignoredExtensions.js +10 -0
- package/dist/index.js +128 -325
- package/dist/lib/generate.js +37 -14
- package/dist/lib/generateFolderCapsules.js +109 -0
- package/dist/lib/spinner.js +12 -5
- package/dist/modelSetup.js +0 -10
- package/dist/pipeline/modules/changeLogModule.js +16 -19
- package/dist/pipeline/modules/chunkManagerModule.js +24 -0
- package/dist/pipeline/modules/cleanupModule.js +96 -91
- package/dist/pipeline/modules/codeTransformModule.js +208 -0
- package/dist/pipeline/modules/commentModule.js +20 -11
- package/dist/pipeline/modules/commitSuggesterModule.js +36 -14
- package/dist/pipeline/modules/contextReviewModule.js +52 -0
- package/dist/pipeline/modules/fileReaderModule.js +72 -0
- package/dist/pipeline/modules/fileSearchModule.js +136 -0
- package/dist/pipeline/modules/finalAnswerModule.js +53 -0
- package/dist/pipeline/modules/gatherInfoModule.js +176 -0
- package/dist/pipeline/modules/generateTestsModule.js +63 -54
- package/dist/pipeline/modules/kgModule.js +26 -11
- package/dist/pipeline/modules/preserveCodeModule.js +91 -49
- package/dist/pipeline/modules/refactorModule.js +19 -7
- package/dist/pipeline/modules/repairTestsModule.js +44 -36
- package/dist/pipeline/modules/reviewModule.js +23 -13
- package/dist/pipeline/modules/summaryModule.js +27 -35
- package/dist/pipeline/modules/writeFileModule.js +86 -0
- package/dist/pipeline/registry/moduleRegistry.js +38 -93
- package/dist/pipeline/runModulePipeline.js +22 -19
- package/dist/scripts/dbcheck.js +143 -228
- package/dist/utils/buildContextualPrompt.js +245 -172
- package/dist/utils/debugContext.js +24 -0
- package/dist/utils/fileTree.js +16 -6
- package/dist/utils/loadRelevantFolderCapsules.js +64 -0
- package/dist/utils/log.js +2 -0
- package/dist/utils/normalizeData.js +23 -0
- package/dist/utils/planActions.js +60 -0
- package/dist/utils/promptBuilderHelper.js +67 -0
- package/dist/utils/promptLogHelper.js +52 -0
- package/dist/utils/sanitizeQuery.js +20 -8
- package/dist/utils/sleep.js +3 -0
- package/dist/utils/splitCodeIntoChunk.js +65 -32
- package/dist/utils/vscode.js +49 -0
- package/dist/workflow/workflowResolver.js +14 -0
- package/dist/workflow/workflowRunner.js +103 -0
- package/package.json +6 -5
- package/dist/agent/agentManager.js +0 -39
- package/dist/agent/workflowManager.js +0 -95
- package/dist/commands/ModulePipelineCmd.js +0 -31
- package/dist/daemon/daemonBatch.js +0 -186
- package/dist/fileRules/scoreFiles.js +0 -71
- package/dist/lib/generateEmbedding.js +0 -22
package/dist/scripts/dbcheck.js
CHANGED
|
@@ -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
|
|
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(`β
|
|
18
|
+
console.error(`β DB not found: ${dbPath}`);
|
|
17
19
|
process.exit(1);
|
|
18
20
|
}
|
|
19
21
|
const db = new Database(dbPath);
|
|
20
|
-
|
|
21
|
-
function
|
|
22
|
-
|
|
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
|
-
|
|
31
|
-
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
44
|
-
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
|
|
106
|
-
|
|
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
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
const
|
|
123
|
-
SELECT e.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
|
|
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
|
|
130
|
+
SELECT e.id
|
|
129
131
|
FROM graph_edges e
|
|
130
|
-
WHERE e.target_type
|
|
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 (
|
|
135
|
-
console.log("β
|
|
136
|
+
if (danglingFuncs.length === 0) {
|
|
137
|
+
console.log("β
no dangling function edges");
|
|
136
138
|
}
|
|
137
139
|
else {
|
|
138
|
-
console.log(
|
|
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
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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
|
-
|
|
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
|
-
|
|
181
|
+
catch (err) {
|
|
182
|
+
console.error("β graphviz export failed", err);
|
|
183
|
+
}
|
|
184
|
+
console.log("\nβ
DB check complete\n");
|