@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
JavaScript
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
};