scai
Version:
> **AI-powered CLI for local code analysis, commit message suggestions, and natural-language queries.** 100% local, private, GDPR-friendly, made in Denmark/EU with ❤️.
110 lines (106 loc) • 3.39 kB
JavaScript
import { getDbForRepo } from "../db/client.js";
import { generate } from "../lib/generate.js";
import { cleanupModule } from "../pipeline/modules/cleanupModule.js";
import path from "path";
export async function generateFolderCapsules() {
const db = getDbForRepo();
// --- Load files (paths only) ---
const files = db
.prepare(`SELECT path FROM files`)
.all();
// --- Group files by folder ---
const folders = new Map();
for (const { path: filePath } of files) {
const dir = path.dirname(filePath) || "/";
const depth = dir === "/" ? 0 : dir.split("/").length;
if (!folders.has(dir)) {
folders.set(dir, {
path: dir,
depth,
files: [],
});
}
folders.get(dir).files.push({
name: path.basename(filePath),
ext: path.extname(filePath).replace(".", "") || "unknown",
});
}
// --- Generate one capsule per folder ---
for (const folder of folders.values()) {
const byType = {};
for (const f of folder.files) {
byType[f.ext] = (byType[f.ext] ?? 0) + 1;
}
const inputPayload = {
path: folder.path,
depth: folder.depth,
fileCount: folder.files.length,
byType,
files: folder.files.map((f) => f.name),
};
const input = {
query: `Generate a FolderCapsule for ${folder.path}`,
content: `
You are generating a structured FolderCapsule describing the architectural role of a folder.
Return ONLY valid JSON matching this TypeScript interface:
interface FolderCapsule {
path: string;
depth: number;
stats: {
fileCount: number;
byType: Record<string, number>;
};
roles: string[];
concerns: string[];
keyFiles: { path: string; reason: string }[];
dependencies: {
importsFrom: string[];
usedBy: string[];
};
confidence: number;
}
Folder facts:
${JSON.stringify(inputPayload, null, 2)}
`.trim(),
};
const response = await generate(input);
// --- Cleanup model output ---
const cleaned = await cleanupModule.run({
query: input.query,
content: response.content,
});
let capsule = null;
try {
capsule = JSON.parse(String(cleaned.content));
}
catch {
console.warn(`⚠️ Failed to parse FolderCapsule for ${folder.path}`);
continue;
}
db.prepare(`
INSERT INTO folder_capsules
(path, depth, capsule_json, confidence, last_generated, source_file_count)
VALUES
(
@path,
@depth,
@capsule_json,
@confidence,
datetime('now'),
@source_file_count
)
ON CONFLICT(path) DO UPDATE SET
capsule_json = excluded.capsule_json,
confidence = excluded.confidence,
last_generated = datetime('now'),
source_file_count = excluded.source_file_count
`).run({
path: folder.path,
depth: folder.depth,
capsule_json: JSON.stringify(capsule),
confidence: capsule?.confidence ?? null,
source_file_count: folder.files.length,
});
console.log(`📦 Folder capsule generated for ${folder.path}`);
}
}