UNPKG

scai

Version:

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

94 lines (93 loc) 3.37 kB
import fs from 'fs'; import path from 'path'; import { generateEmbedding } from '../lib/generateEmbedding.js'; import { sanitizeQueryForFts } from '../utils/sanitizeQuery.js'; import * as sqlTemplates from './sqlTemplates.js'; import { CANDIDATE_LIMIT } from '../constants.js'; import { getDbForRepo } from './client.js'; import { scoreFiles } from '../fileRules/scoreFiles.js'; // 👈 NEW export function indexFile(filePath, summary, type) { const stats = fs.statSync(filePath); const lastModified = stats.mtime.toISOString(); const indexedAt = new Date().toISOString(); const normalizedPath = path.normalize(filePath).replace(/\\/g, '/'); const fileName = path.basename(normalizedPath); const db = getDbForRepo(); db.prepare(sqlTemplates.upsertFileTemplate).run({ path: normalizedPath, filename: fileName, summary, type, lastModified, indexedAt, embedding: null }); db.prepare(` INSERT OR REPLACE INTO files_fts (rowid, filename, summary, path) VALUES ((SELECT id FROM files WHERE path = :path), :filename, :summary, :path) `).run({ path: normalizedPath, filename: fileName, summary, }); console.log(`📄 Indexed: ${normalizedPath}`); } export function queryFiles(safeQuery, limit = 10) { console.log(`Executing search query: ${safeQuery}`); const db = getDbForRepo(); return db.prepare(` SELECT f.id, f.path, f.filename, f.summary, f.type, f.last_modified, f.indexed_at FROM files f JOIN files_fts fts ON f.id = fts.rowid WHERE fts.files_fts MATCH ? LIMIT ? `).all(safeQuery, limit); } export async function searchFiles(query, topK = 5) { console.log(`🧠 Searching for query: "${query}"`); const embedding = await generateEmbedding(query); if (!embedding) { console.log('⚠️ Failed to generate embedding for query'); return []; } const safeQuery = sanitizeQueryForFts(query); console.log(`Executing search query in FTS5: ${safeQuery}`); const db = getDbForRepo(); const ftsResults = db.prepare(` SELECT fts.rowid AS id, f.path, f.filename, f.summary, f.type, bm25(files_fts) AS bm25Score, f.embedding FROM files f JOIN files_fts fts ON f.id = fts.rowid WHERE fts.files_fts MATCH ? ORDER BY bm25Score ASC LIMIT ? `).all(safeQuery, CANDIDATE_LIMIT); console.log(`FTS search returned ${ftsResults.length} results`); if (ftsResults.length === 0) return []; const scored = scoreFiles(query, embedding, ftsResults); return scored.slice(0, topK); } export function getFunctionsForFiles(fileIds) { if (!fileIds.length) return {}; const placeholders = fileIds.map(() => '?').join(','); const db = getDbForRepo(); const stmt = db.prepare(` SELECT f.file_id, f.name, f.start_line, f.end_line, f.content FROM functions f WHERE f.file_id IN (${placeholders}) `); const rows = stmt.all(...fileIds); const grouped = {}; for (const row of rows) { if (!grouped[row.file_id]) grouped[row.file_id] = []; grouped[row.file_id].push({ name: row.name, start_line: row.start_line, end_line: row.end_line, content: row.content, }); } return grouped; }