UNPKG

@stackmemoryai/stackmemory

Version:

Project-scoped memory for AI coding tools. Durable context across sessions with MCP integration, frames, smart retrieval, Claude Code skills, and automatic hooks.

249 lines (248 loc) 9.5 kB
import { fileURLToPath as __fileURLToPath } from 'url'; import { dirname as __pathDirname } from 'path'; const __filename = __fileURLToPath(import.meta.url); const __dirname = __pathDirname(__filename); import { Command } from "commander"; import Database from "better-sqlite3"; import { join } from "path"; import { existsSync } from "fs"; import chalk from "chalk"; import { RetrievalAuditStore } from "../../core/retrieval/retrieval-audit.js"; function createRetrievalCommands() { const retrieval = new Command("retrieval").alias("ret").description("Manage LLM-driven context retrieval"); retrieval.command("audit").description("View retrieval audit log").option("-l, --limit <n>", "Number of entries to show", "10").option("-q, --query <text>", "Filter by query text").option("-v, --verbose", "Show full details").action(async (options) => { const projectRoot = process.cwd(); const dbPath = join(projectRoot, ".stackmemory", "context.db"); if (!existsSync(dbPath)) { console.log( chalk.red("StackMemory not initialized in this directory.") ); console.log(chalk.gray('Run "stackmemory init" first.')); return; } const db = new Database(dbPath); try { let projectId = "default"; try { const row = db.prepare(`SELECT value FROM metadata WHERE key = 'project_id'`).get(); if (row?.value) projectId = row.value; } catch { } const auditStore = new RetrievalAuditStore(db, projectId); let entries; if (options.query) { entries = auditStore.searchByQuery( options.query, parseInt(options.limit) ); console.log( chalk.blue( ` Retrieval audit entries matching "${options.query}": ` ) ); } else { entries = auditStore.getRecent(parseInt(options.limit)); console.log(chalk.blue("\nRecent retrieval audit entries:\n")); } if (entries.length === 0) { console.log(chalk.gray("No audit entries found.")); console.log( chalk.gray( "\nRetrieval audit records decisions made by the LLM-driven" ) ); console.log( chalk.gray( "context retrieval system. Run some queries to generate entries." ) ); return; } for (const entry of entries) { const date = new Date(entry.timestamp).toLocaleString(); const providerIcon = entry.provider === "anthropic" ? chalk.green("LLM") : entry.provider === "cached" ? chalk.yellow("CACHE") : chalk.gray("HEUR"); console.log( `${chalk.cyan(entry.id.slice(0, 8))} ${chalk.gray(date)} [${providerIcon}]` ); console.log( ` Query: ${chalk.white(entry.query.slice(0, 60))}${entry.query.length > 60 ? "..." : ""}` ); console.log( ` Confidence: ${formatConfidence(entry.confidenceScore)} | Tokens: ${entry.tokensUsed}/${entry.tokenBudget} | Time: ${entry.analysisTimeMs}ms | Complexity: ${entry.queryComplexity}` ); if (options.verbose) { console.log( ` Frames: ${entry.framesRetrieved.join(", ") || "none"}` ); console.log( ` Reasoning: ${chalk.gray(entry.reasoning.slice(0, 200))}${entry.reasoning.length > 200 ? "..." : ""}` ); } console.log(""); } } finally { db.close(); } }); retrieval.command("stats").description("Show retrieval statistics").action(async () => { const projectRoot = process.cwd(); const dbPath = join(projectRoot, ".stackmemory", "context.db"); if (!existsSync(dbPath)) { console.log( chalk.red("StackMemory not initialized in this directory.") ); return; } const db = new Database(dbPath); try { let projectId = "default"; try { const row = db.prepare(`SELECT value FROM metadata WHERE key = 'project_id'`).get(); if (row?.value) projectId = row.value; } catch { } const auditStore = new RetrievalAuditStore(db, projectId); const stats = auditStore.getStats(); console.log(chalk.blue("\nRetrieval Statistics\n")); console.log(`Total retrievals: ${chalk.white(stats.totalRetrievals)}`); console.log( `Average confidence: ${formatConfidence(stats.avgConfidence)}` ); console.log( `Average tokens used: ${chalk.white(Math.round(stats.avgTokensUsed))}` ); console.log( `Average analysis time: ${chalk.white(Math.round(stats.avgAnalysisTime))}ms` ); console.log("\nProvider breakdown:"); for (const [provider, count] of Object.entries( stats.providerBreakdown )) { const pct = stats.totalRetrievals > 0 ? (count / stats.totalRetrievals * 100).toFixed(1) : "0"; const icon = provider === "anthropic" ? chalk.green("LLM") : provider === "cached" ? chalk.yellow("CACHE") : chalk.gray("HEUR"); console.log(` ${icon}: ${count} (${pct}%)`); } console.log(""); } finally { db.close(); } }); retrieval.command("reasoning <id>").description("Show detailed reasoning for a retrieval decision").action(async (id) => { const projectRoot = process.cwd(); const dbPath = join(projectRoot, ".stackmemory", "context.db"); if (!existsSync(dbPath)) { console.log( chalk.red("StackMemory not initialized in this directory.") ); return; } const db = new Database(dbPath); try { let projectId = "default"; try { const row = db.prepare(`SELECT value FROM metadata WHERE key = 'project_id'`).get(); if (row?.value) projectId = row.value; } catch { } const auditStore = new RetrievalAuditStore(db, projectId); const entries = auditStore.getRecent(100); const entry = entries.find((e) => e.id.startsWith(id)); if (!entry) { console.log( chalk.red(`No audit entry found with ID starting with "${id}"`) ); return; } console.log(chalk.blue("\nRetrieval Decision Details\n")); console.log(`ID: ${chalk.white(entry.id)}`); console.log( `Time: ${chalk.white(new Date(entry.timestamp).toLocaleString())}` ); console.log(`Provider: ${chalk.white(entry.provider)}`); console.log(`Query: ${chalk.white(entry.query)}`); console.log(`Complexity: ${chalk.white(entry.queryComplexity)}`); console.log(`Confidence: ${formatConfidence(entry.confidenceScore)}`); console.log( `Tokens: ${chalk.white(`${entry.tokensUsed}/${entry.tokenBudget}`)}` ); console.log( `Analysis Time: ${chalk.white(`${entry.analysisTimeMs}ms`)}` ); console.log(chalk.blue("\nReasoning:")); console.log(entry.reasoning); console.log(chalk.blue("\nFrames Retrieved:")); if (entry.framesRetrieved.length === 0) { console.log(chalk.gray(" (none)")); } else { for (const frameId of entry.framesRetrieved) { console.log(` - ${frameId}`); } } console.log(""); } finally { db.close(); } }); retrieval.command("cleanup").description("Remove old audit entries").option("-d, --days <n>", "Keep entries from last N days", "7").action(async (options) => { const projectRoot = process.cwd(); const dbPath = join(projectRoot, ".stackmemory", "context.db"); if (!existsSync(dbPath)) { console.log( chalk.red("StackMemory not initialized in this directory.") ); return; } const db = new Database(dbPath); try { let projectId = "default"; try { const row = db.prepare(`SELECT value FROM metadata WHERE key = 'project_id'`).get(); if (row?.value) projectId = row.value; } catch { } const auditStore = new RetrievalAuditStore(db, projectId); const days = parseInt(options.days); const maxAgeMs = days * 24 * 60 * 60 * 1e3; const deleted = auditStore.cleanup(maxAgeMs); console.log( chalk.green( `Cleaned up ${deleted} old audit entries (older than ${days} days)` ) ); } finally { db.close(); } }); retrieval.command("status").description("Show current retrieval system status").action(async () => { const hasApiKey = !!process.env["ANTHROPIC_API_KEY"]; const model = process.env["ANTHROPIC_MODEL"] || "claude-3-5-haiku-20241022"; console.log(chalk.blue("\nRetrieval System Status\n")); if (hasApiKey) { console.log(`LLM Provider: ${chalk.green("Anthropic (active)")}`); console.log(`Model: ${chalk.white(model)}`); } else { console.log( `LLM Provider: ${chalk.yellow("Heuristic fallback (no API key)")}` ); console.log( chalk.gray("Set ANTHROPIC_API_KEY to enable LLM-driven retrieval") ); } console.log(`Default Token Budget: ${chalk.white("8000")}`); console.log(`Confidence Threshold: ${chalk.white("0.6")}`); console.log(`Cache TTL: ${chalk.white("5 minutes")}`); console.log(""); }); return retrieval; } function formatConfidence(score) { if (score >= 0.8) return chalk.green(`${(score * 100).toFixed(0)}%`); if (score >= 0.6) return chalk.yellow(`${(score * 100).toFixed(0)}%`); return chalk.red(`${(score * 100).toFixed(0)}%`); } export { createRetrievalCommands }; //# sourceMappingURL=retrieval.js.map