UNPKG

scai

Version:

> AI-powered CLI tool for commit messages **and** pull request reviews โ€” using local models.

227 lines (226 loc) โ€ข 9.25 kB
import Database from "better-sqlite3"; import path from "path"; import os from "os"; import { Config } from "../config.js"; import fs from "fs"; const cfg = Config.getRaw(); const repoKey = cfg.activeRepo; if (!repoKey) { console.error("โŒ No active repo found. Use `scai set-index` to set one."); process.exit(1); } // Get the basename (repo name) from the full repoKey (which is the path) const repoName = path.basename(repoKey); const scaiRepoRoot = path.join(os.homedir(), ".scai", "repos", repoName); const dbPath = path.join(scaiRepoRoot, "db.sqlite"); if (!fs.existsSync(dbPath)) { console.error(`โŒ No database found at ${dbPath}`); process.exit(1); } const db = new Database(dbPath); // === Basic Stats === const stats = { total: db.prepare('SELECT COUNT(*) as count FROM files').get().count, withSummary: db.prepare(`SELECT COUNT(*) as count FROM files WHERE summary IS NOT NULL AND summary != ''`).get().count, withoutSummary: db.prepare(`SELECT COUNT(*) as count FROM files WHERE summary IS NULL OR summary = ''`).get().count, withEmbedding: db.prepare(`SELECT COUNT(*) as count FROM files WHERE embedding IS NOT NULL AND embedding != ''`).get().count, withoutEmbedding: db.prepare(`SELECT COUNT(*) as count FROM files WHERE embedding IS NULL OR embedding = ''`).get().count, withProcessingStatusExtracted: db.prepare(`SELECT COUNT(*) as count FROM files WHERE processing_status = 'extracted'`).get().count, withoutProcessingStatusExtracted: db.prepare(`SELECT COUNT(*) as count FROM files WHERE processing_status IS NULL OR processing_status != 'extracted'`).get().count, withProcessingStatusSkipped: db.prepare(`SELECT COUNT(*) as count FROM files WHERE processing_status = 'skipped'`).get().count, withProcessingStatusFailed: db.prepare(`SELECT COUNT(*) as count FROM files WHERE processing_status = 'failed'`).get().count, withProcessingStatusUnprocessed: db.prepare(`SELECT COUNT(*) as count FROM files WHERE processing_status = 'unprocessed'`).get().count, }; console.log("๐Ÿ“Š SQLite Stats for Table: files"); console.log("-------------------------------------------"); console.log(`๐Ÿ”ข Total rows: ${stats.total}`); console.log(`โœ… With summary: ${stats.withSummary}`); console.log(`โŒ Without summary: ${stats.withoutSummary}`); console.log(`โœ… With embedding: ${stats.withEmbedding}`); console.log(`โŒ Without embedding: ${stats.withoutEmbedding}`); console.log(`โœ… With processing_status = 'extracted': ${stats.withProcessingStatusExtracted}`); console.log(`โŒ Without processing_status = 'extracted': ${stats.withoutProcessingStatusExtracted}`); console.log(`โœ… With processing_status = 'skipped': ${stats.withProcessingStatusSkipped}`); console.log(`โœ… With processing_status = 'failed': ${stats.withProcessingStatusFailed}`); console.log(`โœ… With processing_status = 'unprocessed': ${stats.withProcessingStatusUnprocessed}`); // === Example Summaries === console.log("\n๐Ÿงพ Example summaries and embeddings:\n--------------------------"); console.log("Example summaries (first 50 characters):"); const summaries = db.prepare(` SELECT id, substr(summary, 1, 50) || '...' AS short_summary FROM files WHERE summary IS NOT NULL AND summary != '' LIMIT 10 `).all(); summaries.forEach(row => { console.log(`${row.id.toString().padEnd(4)} ${row.short_summary}`); }); console.log("\nExample embeddings (first 50 characters):"); const embeddings = db.prepare(` SELECT id, substr(embedding, 1, 50) || '...' AS short_embedding FROM files WHERE embedding IS NOT NULL AND embedding != '' LIMIT 10 `).all(); embeddings.forEach(row => { console.log(`${row.id.toString().padEnd(4)} ${row.short_embedding}`); }); // === FTS5 Check === console.log("\n๐Ÿ” FTS5 Check"); console.log("--------------------------"); try { const ftsExists = db.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='files_fts'`).get(); if (!ftsExists) { console.log("โŒ No FTS5 table `files_fts` found in database."); } else { const ftsRowCount = db.prepare(`SELECT COUNT(*) AS count FROM files_fts`).get().count; console.log(`โœ… files_fts table exists. Rows: ${ftsRowCount}`); } } catch (err) { console.error("โŒ Error while accessing files_fts:", err.message); } // === Rebuild FTS Index === console.log("\n๐Ÿ”ง Rebuilding FTS5 index..."); try { db.prepare(`INSERT INTO files_fts(files_fts) VALUES ('rebuild')`).run(); console.log(`โœ… Rebuild completed.`); } catch (err) { console.error("โŒ FTS5 rebuild failed:", err.message); } // === FTS Search Test === console.log('\n๐Ÿ” Test MATCH query for "minimap":'); try { const minimapMatches = db.prepare(` SELECT f.id, f.path FROM files f JOIN files_fts fts ON f.id = fts.rowid WHERE fts.files_fts MATCH 'minimap' LIMIT 10 `).all(); if (minimapMatches.length === 0) { console.warn('โš ๏ธ No matches for "minimap" in FTS index'); } else { minimapMatches.forEach(row => { console.log(`๐Ÿ“„ ${row.path}`); }); } } catch (err) { console.error('โŒ Error running MATCH query:', err.message); } // === Direct LIKE Fallback === console.log('\n๐Ÿ” Direct LIKE query on path for "minimap":'); const likeMatches = db.prepare(` SELECT id, path FROM files WHERE path LIKE '%minimap%' LIMIT 10 `).all(); if (likeMatches.length === 0) { console.warn('โš ๏ธ No file paths contain "minimap"'); } else { likeMatches.forEach(row => { console.log(`๐Ÿ“„ ${row.path}`); }); } // === Function Table Stats === console.log('\n๐Ÿ“Š Stats for Table: functions'); console.log('-------------------------------------------'); try { const funcCount = db.prepare(`SELECT COUNT(*) AS count FROM functions`).get().count; const distinctFiles = db.prepare(`SELECT COUNT(DISTINCT file_id) AS count FROM functions`).get().count; console.log(`๐Ÿ”ข Total functions: ${funcCount}`); console.log(`๐Ÿ“‚ Distinct files: ${distinctFiles}`); } catch (err) { console.error('โŒ Error accessing functions table:', err.message); } // === Example Functions === console.log('\n๐Ÿงช Example extracted functions:'); try { const sampleFunctions = db.prepare(` SELECT id, name, start_line, end_line, substr(content, 1, 100) || '...' AS short_body FROM functions ORDER BY id DESC LIMIT 5 `).all(); sampleFunctions.forEach(fn => { console.log(`๐Ÿ”น ID: ${fn.id}`); console.log(` Name: ${fn.name}`); console.log(` Lines: ${fn.start_line}-${fn.end_line}`); console.log(` Body: ${fn.short_body}\n`); }); } catch (err) { console.error('โŒ Error printing function examples:', err.message); } // === Function Calls Table Stats === console.log('\n๐Ÿ“Š Stats for Table: function_calls'); console.log('-------------------------------------------'); try { const callCount = db.prepare(`SELECT COUNT(*) AS count FROM function_calls`).get().count; const topCallers = db.prepare(` SELECT caller_id, COUNT(*) AS num_calls FROM function_calls GROUP BY caller_id ORDER BY num_calls DESC LIMIT 5 `).all(); console.log(`๐Ÿ” Total function calls: ${callCount}`); console.log('๐Ÿ“ž Top callers:'); topCallers.forEach(row => { console.log(` - Caller ${row.caller_id} made ${row.num_calls} calls`); }); } catch (err) { console.error('โŒ Error accessing function_calls table:', err.message); } // === Random Summary Samples === console.log('\n๐Ÿงพ 10 Random Summaries (ID + Preview):'); console.log('-------------------------------------------'); const randomSummaries = db.prepare(` SELECT id, filename, substr(summary, 1, 1000) || '...' AS preview FROM files WHERE summary IS NOT NULL AND summary != '' ORDER BY RANDOM() LIMIT 10 `).all(); randomSummaries.forEach(row => { console.log(`๐Ÿ“„ [${row.id}] ${row.filename}: ${row.preview}`); }); // === Random Functions Samples === console.log('\n๐Ÿง‘โ€๐Ÿ’ป 20 Random Functions (ID, name, body):'); console.log('-------------------------------------------'); const randomFunctions = db.prepare(` SELECT id, name, file_id, substr(content, 1, 100) || '...' AS preview FROM functions ORDER BY RANDOM() LIMIT 20 `).all(); randomFunctions.forEach(row => { console.log(`๐Ÿ”น [${row.id}] ${row.name} (file_id: ${row.file_id})`); console.log(` ${row.preview}\n`); }); // === Column View of 100 Files === console.log('\n๐Ÿ“Š Table View: First 500 Files'); console.log('-------------------------------------------'); const fileRows = db.prepare(` SELECT id, filename, type, processing_status, functions_extracted_at, length(summary) AS summary_len FROM files LIMIT 500 `).all(); console.table(fileRows); // === Column View of 100 Functions === console.log('\n๐Ÿ“Š Table View: First 500 Functions'); console.log('-------------------------------------------'); const functionRows = db.prepare(` SELECT id, file_id, name, start_line, end_line, length(content) AS length FROM functions LIMIT 500 `).all(); console.table(functionRows);