@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.
203 lines (202 loc) • 8.26 kB
JavaScript
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 chalk from "chalk";
import ora from "ora";
import * as fs from "fs/promises";
import * as path from "path";
import Database from "better-sqlite3";
import { existsSync } from "fs";
import { ClearSurvival } from "../../core/session/clear-survival.js";
import { FrameManager } from "../../core/context/index.js";
import { sessionManager } from "../../core/session/session-manager.js";
const clearCommand = new Command("clear").description("Manage context clearing with ledger preservation").option("--save", "Save continuity ledger before clearing").option("--restore", "Restore from continuity ledger").option("--check", "Check if clear is recommended").option("--auto", "Automatically save if needed and clear").option("--status", "Show current context usage").option("--show-ledger", "Display current ledger").action(async (options) => {
const spinner = ora();
try {
const projectRoot = process.cwd();
const dbPath = path.join(projectRoot, ".stackmemory", "context.db");
if (!existsSync(dbPath)) {
console.error(
chalk.red("\u2717 StackMemory not initialized in this directory")
);
console.log(chalk.yellow("Run: stackmemory init"));
process.exit(1);
}
const db = new Database(dbPath);
await sessionManager.initialize();
const session = await sessionManager.getOrCreateSession({
projectPath: projectRoot
});
const frameManager = new FrameManager(db, session.projectId);
const handoffGenerator = {
generateHandoff: () => Promise.resolve("Mock handoff"),
getHandoffPath: () => "mock.md"
};
const dbManager = {
getCurrentSessionId: () => Promise.resolve(session.id),
getSession: () => Promise.resolve(session)
};
const clearSurvival = new ClearSurvival(
frameManager,
dbManager,
handoffGenerator,
projectRoot
);
if (options.status) {
await showContextStatus(clearSurvival);
} else if (options.check) {
await checkIfClearRecommended(clearSurvival);
} else if (options.save) {
await saveLedger(clearSurvival, spinner);
} else if (options.restore) {
await restoreFromLedger(clearSurvival, spinner);
} else if (options.showLedger) {
await showLedger(projectRoot);
} else if (options.auto) {
await autoClear(clearSurvival, spinner);
} else {
await showContextStatus(clearSurvival);
console.log("\nOptions:");
console.log(" --status Show current context usage");
console.log(" --check Check if clear is recommended");
console.log(" --save Save continuity ledger");
console.log(" --restore Restore from ledger");
console.log(" --auto Auto-save if needed and clear");
}
} catch (error) {
console.error(chalk.red("Error: " + error.message));
if (process.env["NODE_ENV"] !== "test") {
process.exit(1);
}
}
});
async function showContextStatus(clearSurvival) {
const usage = await clearSurvival.getContextUsage();
const status = clearSurvival.assessContextStatus(usage);
console.log(chalk.bold("\n\u{1F4CA} Context Usage Status"));
console.log("\u2500".repeat(40));
const percentage = Math.round(usage.percentageUsed);
const statusColor = getStatusColor(status);
console.log(`Usage: ${percentage}% ${getProgressBar(percentage)}`);
console.log(`Status: ${statusColor}`);
console.log(`Active Frames: ${usage.activeFrames}`);
console.log(`Total Frames: ${usage.totalFrames}`);
console.log(`Sessions: ${usage.sessionCount}`);
if (status === "critical" || status === "saved") {
console.log(
chalk.yellow("\n\u26A0\uFE0F Consider clearing context to improve performance")
);
console.log(chalk.cyan("Run: stackmemory clear --save"));
}
}
async function checkIfClearRecommended(clearSurvival) {
const usage = await clearSurvival.getContextUsage();
const status = clearSurvival.assessContextStatus(usage);
if (status === "critical" || status === "saved") {
console.log(chalk.yellow("\u2713 Clear recommended"));
console.log(`Context usage: ${Math.round(usage.percentageUsed)}%`);
process.exit(0);
} else {
console.log(chalk.green("\u2717 Clear not needed"));
console.log(`Context usage: ${Math.round(usage.percentageUsed)}%`);
process.exit(1);
}
}
async function saveLedger(clearSurvival, spinner) {
spinner.start("Saving continuity ledger...");
const ledgerPath = await clearSurvival.saveContinuityLedger();
spinner.succeed(chalk.green("Continuity ledger saved"));
console.log(chalk.cyan(`Location: ${ledgerPath}`));
const ledger = JSON.parse(await fs.readFile(ledgerPath, "utf-8"));
console.log("\nSaved:");
console.log(` \u2022 ${ledger.activeFrames.length} active frames`);
console.log(` \u2022 ${ledger.decisions.length} key decisions`);
console.log(
` \u2022 ${ledger.context.importantTasks?.length || 0} important tasks`
);
}
async function restoreFromLedger(clearSurvival, spinner) {
spinner.start("Restoring from continuity ledger...");
const result = await clearSurvival.restoreFromLedger();
if (result.success) {
spinner.succeed(chalk.green("Context restored from ledger"));
console.log("\nRestored:");
console.log(` \u2022 ${result.restoredFrames} frames`);
console.log(` \u2022 ${result.restoredDecisions} decisions`);
} else {
spinner.fail(chalk.red("Failed to restore from ledger"));
console.log(chalk.yellow(result.message));
}
}
async function showLedger(projectRoot) {
const ledgerPath = path.join(projectRoot, ".stackmemory", "continuity.json");
if (!existsSync(ledgerPath)) {
console.log(chalk.yellow("No continuity ledger found"));
return;
}
const ledger = JSON.parse(await fs.readFile(ledgerPath, "utf-8"));
console.log(chalk.bold("\n\u{1F4D6} Continuity Ledger"));
console.log("\u2500".repeat(40));
console.log(`Created: ${new Date(ledger.timestamp).toLocaleString()}`);
console.log(`Active Frames: ${ledger.activeFrames.length}`);
console.log(`Key Decisions: ${ledger.decisions.length}`);
if (ledger.activeFrames.length > 0) {
console.log("\nActive Work:");
ledger.activeFrames.slice(0, 5).forEach((frame) => {
console.log(` \u2022 ${frame.name} (${frame.type})`);
});
}
if (ledger.decisions.length > 0) {
console.log("\nKey Decisions:");
ledger.decisions.slice(0, 3).forEach((decision) => {
console.log(` \u2022 ${decision.decision}`);
});
}
}
async function autoClear(clearSurvival, spinner) {
const usage = await clearSurvival.getContextUsage();
const status = clearSurvival.assessContextStatus(usage);
if (status === "critical" || status === "saved") {
spinner.start("Auto-saving ledger before clear...");
await clearSurvival.saveContinuityLedger();
spinner.succeed("Ledger saved");
spinner.start("Clearing context...");
await new Promise((resolve) => setTimeout(resolve, 1e3));
spinner.succeed("Context cleared successfully");
} else {
console.log(chalk.green("Context usage is healthy, no clear needed"));
}
}
function getProgressBar(percentage) {
const filled = Math.round(percentage / 5);
const empty = 20 - filled;
let bar = "[";
bar += chalk.green("\u25A0").repeat(Math.min(filled, 10));
bar += chalk.yellow("\u25A0").repeat(Math.max(0, Math.min(filled - 10, 5)));
bar += chalk.red("\u25A0").repeat(Math.max(0, filled - 15));
bar += chalk.gray("\u25A1").repeat(empty);
bar += "]";
return bar;
}
function getStatusColor(status) {
switch (status) {
case "healthy":
return chalk.green("\u2713 Healthy (<50%)");
case "moderate":
return chalk.blue("\u26A1 Moderate (50-70%)");
case "critical":
return chalk.yellow("\u26A0\uFE0F Critical (70-85%)");
case "saved":
return chalk.red("\u{1F4BE} Auto-saved (>85%)");
default:
return status;
}
}
var clear_default = clearCommand;
export {
clear_default as default
};
//# sourceMappingURL=clear.js.map