UNPKG

@stackmemoryai/stackmemory

Version:

Lossless, project-scoped memory for AI coding tools. Durable context across sessions with 56 MCP tools, FTS5 search, conductor orchestrator, loop/watch monitoring, snapshot capture, pre-flight overlap checks, Claude/Codex/OpenCode wrappers, Linear sync, a

118 lines (117 loc) 4.56 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 { readdirSync, readFileSync, existsSync } from "fs"; import { join } from "path"; import { homedir } from "os"; import chalk from "chalk"; const DESIRE_DIR = join(homedir(), ".stackmemory", "desire-paths"); function loadEntries(since) { if (!existsSync(DESIRE_DIR)) return []; const files = readdirSync(DESIRE_DIR).filter((f) => f.startsWith("desire-") && f.endsWith(".jsonl")).sort(); const entries = []; for (const file of files) { const lines = readFileSync(join(DESIRE_DIR, file), "utf-8").split("\n").filter(Boolean); for (const line of lines) { try { const entry = JSON.parse(line); if (since && new Date(entry.ts).getTime() < since) continue; entries.push(entry); } catch { } } } return entries; } function createDesiresCommands() { const desires = new Command("desires").description( "Analyze desire path logs \u2014 failed tool calls and unmet agent needs" ); desires.command("summary").description("Show aggregated failure counts by tool").action(() => { const entries = loadEntries(); if (entries.length === 0) { console.log(chalk.yellow("No desire path data found.")); console.log(chalk.gray(` Logs dir: ${DESIRE_DIR}`)); return; } const byTool = /* @__PURE__ */ new Map(); for (const e of entries) { const existing = byTool.get(e.tool); if (!existing || e.ts > existing.lastSeen) { byTool.set(e.tool, { count: (existing?.count || 0) + 1, category: e.category, lastSeen: e.ts }); } else { existing.count++; } } const sorted = [...byTool.entries()].sort( (a, b) => b[1].count - a[1].count ); console.log(chalk.bold("Desire Path Summary")); console.log(chalk.gray(`Total failures: ${entries.length} `)); const toolW = 30; const countW = 7; const catW = 16; console.log( chalk.gray( `${"Tool".padEnd(toolW)} ${"Count".padStart(countW)} ${"Category".padEnd(catW)} Last Seen` ) ); console.log(chalk.gray("-".repeat(toolW + countW + catW + 22))); for (const [tool, data] of sorted) { const catColor = data.category === "unknown_tool" ? chalk.red : data.category === "invalid_params" ? chalk.yellow : chalk.gray; const lastDate = data.lastSeen.slice(0, 19).replace("T", " "); console.log( `${tool.padEnd(toolW)} ${String(data.count).padStart(countW)} ${catColor(data.category.padEnd(catW))} ${chalk.gray(lastDate)}` ); } const byCat = /* @__PURE__ */ new Map(); for (const e of entries) { byCat.set(e.category, (byCat.get(e.category) || 0) + 1); } console.log(chalk.bold("\nBy Category:")); for (const [cat, count] of [...byCat.entries()].sort( (a, b) => b[1] - a[1] )) { console.log(` ${cat}: ${count}`); } }); desires.command("list").description("Show recent failures with details").option("-l, --limit <n>", "Max entries to show", "20").option("-s, --since <epoch>", "Filter entries after this epoch (ms)").option("-u, --unknown-only", "Show only unknown_tool category").action( (options) => { const since = options.since ? parseInt(options.since, 10) : void 0; let entries = loadEntries(since); if (options.unknownOnly) { entries = entries.filter((e) => e.category === "unknown_tool"); } if (entries.length === 0) { console.log(chalk.yellow("No desire path data found.")); console.log(chalk.gray(` Logs dir: ${DESIRE_DIR}`)); return; } const limit = parseInt(options.limit, 10) || 20; const recent = entries.sort((a, b) => b.ts.localeCompare(a.ts)).slice(0, limit); console.log( chalk.bold(`Recent Desire Paths (${recent.length}/${entries.length})`) ); console.log(""); for (const e of recent) { const catColor = e.category === "unknown_tool" ? chalk.red : e.category === "invalid_params" ? chalk.yellow : chalk.gray; const ts = e.ts.slice(0, 19).replace("T", " "); console.log( `${chalk.gray(ts)} ${chalk.bold(e.tool)} ${catColor(`[${e.category}]`)}` ); console.log(` ${chalk.gray(e.error.slice(0, 120))}`); } } ); return desires; } export { createDesiresCommands };