UNPKG

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
// 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 }); } } }