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

192 lines (181 loc) 5.54 kB
import { fileURLToPath as __fileURLToPath } from 'url'; import { dirname as __pathDirname } from 'path'; const __filename = __fileURLToPath(import.meta.url); const __dirname = __pathDirname(__filename); import * as fs from "fs"; import * as path from "path"; import { execSync } from "child_process"; import { logger } from "../core/monitoring/logger.js"; const THEORY_FILE = "THEORY.MD"; const MIN_CONTENT_LENGTH = 100; const MAX_LINE_WARNING = 200; const TEMPLATE_SECTIONS = [ "## Problem", "## Operating Theory", "## Strategy", "## Key Discoveries", "## Open Questions" ]; function getGitRoot() { try { return execSync("git rev-parse --show-toplevel", { encoding: "utf-8", timeout: 5e3 }).trim(); } catch { return void 0; } } function generateTemplate(problemStatement) { return `# THEORY.MD ## Problem ${problemStatement} ## Operating Theory _What is your current mental model of how this system works or should work?_ ## Strategy _What approach are you taking and why?_ ## Key Discoveries _What have you learned that changed your thinking?_ ## Open Questions _What don't you know yet? What assumptions need validation?_ `; } class TheorySkill { constructor(context) { this.context = context; this.rootDir = getGitRoot() || process.cwd(); } rootDir; get theoryPath() { return path.join(this.rootDir, THEORY_FILE); } show() { if (!fs.existsSync(this.theoryPath)) { return { success: false, message: `No ${THEORY_FILE} found. Run \`theory init "<problem>"\` to create one.` }; } const content = fs.readFileSync(this.theoryPath, "utf-8"); return { success: true, message: content, data: { path: this.theoryPath, length: content.length } }; } init(problemStatement) { if (!problemStatement || problemStatement.trim().length === 0) { return { success: false, message: "A problem statement is required to initialize THEORY.MD." }; } if (fs.existsSync(this.theoryPath)) { return { success: false, message: `${THEORY_FILE} already exists at ${this.theoryPath}. Use \`theory update\` to modify it.` }; } const content = generateTemplate(problemStatement.trim()); fs.writeFileSync(this.theoryPath, content, "utf-8"); logger.info("Created THEORY.MD", { path: this.theoryPath }); return { success: true, message: `Created ${THEORY_FILE} at ${this.theoryPath}`, data: { path: this.theoryPath, sections: TEMPLATE_SECTIONS } }; } update(content) { if (!content || content.trim().length === 0) { return { success: false, message: "Content is required for update." }; } if (content.trim().length < MIN_CONTENT_LENGTH) { return { success: false, message: `Content too short (${content.trim().length} chars). THEORY.MD should be at least ${MIN_CONTENT_LENGTH} characters to be meaningful.` }; } const warnings = []; if (/\[[ x]\]/i.test(content)) { warnings.push( "Contains checkboxes \u2014 THEORY.MD is narrative, not a checklist." ); } if (/\d{4}-\d{2}-\d{2}/.test(content)) { warnings.push( "Contains dates \u2014 THEORY.MD captures current thinking, not a changelog." ); } const lineCount = content.split("\n").length; if (lineCount > MAX_LINE_WARNING) { warnings.push( `${lineCount} lines is long. Consider distilling to keep THEORY.MD focused.` ); } fs.writeFileSync(this.theoryPath, content, "utf-8"); if (this.context.frameManager) { try { const frameId = this.context.frameManager.createFrame( "write", "theory-update", { source: THEORY_FILE, length: content.length } ); this.context.frameManager.addEvent( "artifact", { type: "theory-update", path: this.theoryPath, length: content.length, lineCount }, frameId ); this.context.frameManager.closeFrame(frameId, { theory_updated: true }); } catch (err) { logger.warn("Failed to record theory update frame", { error: err }); } } logger.info("Updated THEORY.MD", { path: this.theoryPath, length: content.length }); const message = warnings.length > 0 ? `Updated ${THEORY_FILE}. Warnings: ${warnings.map((w) => ` - ${w}`).join("\n")}` : `Updated ${THEORY_FILE} (${lineCount} lines)`; return { success: true, message, data: { path: this.theoryPath, lineCount, warnings } }; } status() { if (!fs.existsSync(this.theoryPath)) { return { success: true, message: `No ${THEORY_FILE} found.`, data: { exists: false } }; } const content = fs.readFileSync(this.theoryPath, "utf-8"); const lines = content.split("\n"); const stat = fs.statSync(this.theoryPath); const sections = TEMPLATE_SECTIONS.filter((s) => content.includes(s)); return { success: true, message: `${THEORY_FILE}: ${lines.length} lines, ${sections.length}/${TEMPLATE_SECTIONS.length} sections`, data: { exists: true, path: this.theoryPath, lineCount: lines.length, charCount: content.length, sections, totalSections: TEMPLATE_SECTIONS.length, lastModified: stat.mtime.toISOString() } }; } } export { TheorySkill };