scai
Version:
> AI-powered CLI tool for commit messages **and** pull request reviews โ using local models.
227 lines (226 loc) โข 9.25 kB
JavaScript
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);