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 ā¤ļø.
105 lines (104 loc) ⢠4.41 kB
JavaScript
// File: src/daemon/runKgBatch.ts
import fs from 'fs/promises';
import fsSync from 'fs';
import path from 'path';
import { insertGraphTagTemplate, selectGraphTagIdTemplate, insertGraphEntityTagTemplate, } from '../db/sqlTemplates.js';
import { getDbForRepo } from '../db/client.js';
import { log } from '../utils/log.js';
import { kgModule } from '../pipeline/modules/kgModule.js';
import { KG_IGNORED_EXTENSIONS } from '../fileRules/ignoredExtensions.js';
import * as sqlTemplates from '../db/sqlTemplates.js'; // import the template
import { indexCodeForFile } from '../db/functionIndex.js';
const MAX_KG_FILES_PER_BATCH = 3;
export async function runKgBatch() {
const db = getDbForRepo();
const rows = db.prepare(`
SELECT id, path, summary
FROM files
WHERE functions_extracted_at IS NULL
AND processing_status NOT IN ('skipped', 'failed', 'kg_done')
LIMIT ?
`).all(MAX_KG_FILES_PER_BATCH);
if (rows.length === 0) {
log('š§ KG batch: no pending files');
return;
}
for (const row of rows) {
log(`\nš KG: Processing ${row.path}`);
if (!fsSync.existsSync(row.path)) {
log('ā ļø KG skipped: file missing');
db.prepare(sqlTemplates.markFileAsKgDone).run({ path: row.path });
continue;
}
// --- CODE EXTRACTION SECTION ---
log('\n');
log('š Code Extraction');
log('--------------------------------------------------------------------');
try {
const success = await indexCodeForFile(row.path, row.id);
if (success) {
log('ā
Indexed code');
}
else {
log('ā” No code elements extracted or unsupported file type');
}
}
catch (err) {
log(`ā Code extraction failed for ${row.path}:`, err);
}
// --- KNOWLEDGE GRAPH SECTION ---
log('\n');
log('š Knowledge Graph');
log('--------------------------------------------------------------------');
const ext = path.extname(row.path).toLowerCase();
if (KG_IGNORED_EXTENSIONS.includes(ext)) {
log(`ā ļø KG skipped (ignored extension): ${ext}`);
db.prepare(sqlTemplates.markFileAsKgDone).run({ path: row.path });
continue;
}
try {
const content = await fs.readFile(row.path, 'utf-8');
const kgInput = {
fileId: row.id,
filepath: row.path,
summary: row.summary || undefined,
};
const kgResult = await kgModule.run(kgInput, content);
log(`ā
KG built: ${kgResult.entities.length} entities, ${kgResult.edges.length} edges`);
if (kgResult.entities.length > 0) {
const insertTag = db.prepare(insertGraphTagTemplate);
const getTagId = db.prepare(selectGraphTagIdTemplate);
const insertEntityTag = db.prepare(insertGraphEntityTagTemplate);
for (const entity of kgResult.entities) {
if (!entity.type || !Array.isArray(entity.tags))
continue;
for (const tag of entity.tags) {
if (!tag)
continue;
try {
insertTag.run({ name: tag });
const tagRow = getTagId.get({ name: tag });
if (!tagRow)
continue;
insertEntityTag.run({
entity_type: entity.type,
entity_unique_id: `${entity.name}@${row.path}`,
tag_id: tagRow.id,
});
}
catch {
// ignore per-entity failures
}
}
}
}
// ā
Mark KG as done for this file
db.prepare(sqlTemplates.markFileAsKgDone).run({ path: row.path });
}
catch (err) {
log(`ā KG failed for ${row.path}:`, err);
// Still mark as done to avoid infinite retries
db.prepare(sqlTemplates.markFileAsKgDone).run({ path: row.path });
}
}
}