@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
101 lines (85 loc) • 2.37 kB
JavaScript
#!/usr/bin/env node
/**
* Theory Capture Hook (PostToolUse)
*
* Detects writes to THEORY.MD and:
* 1. Caches metadata to .stackmemory/theory-cache.json
* 2. Fire-and-forget: records a StackMemory frame via CLI
*
* Must complete in <50ms -- file I/O only, CLI exec is detached.
*/
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const { spawn } = require('child_process');
const WRITE_TOOLS = ['Edit', 'Write', 'MultiEdit'];
function ensureDir(dir) {
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
}
function safeWriteFile(filePath, content) {
const tmp = filePath + '.tmp';
fs.writeFileSync(tmp, content);
fs.renameSync(tmp, filePath);
}
function isTheoryPath(filePath) {
if (!filePath) return false;
const lower = filePath.toLowerCase();
return (
lower.endsWith('/theory.md') ||
lower.endsWith('\\theory.md') ||
lower === 'theory.md'
);
}
async function readInput() {
let input = '';
for await (const chunk of process.stdin) {
input += chunk;
}
return JSON.parse(input);
}
async function main() {
try {
const input = await readInput();
const { tool_name, tool_input } = input;
// Only care about file-write tools
if (!WRITE_TOOLS.includes(tool_name)) return;
const fp = tool_input?.file_path || '';
if (!isTheoryPath(fp)) return;
// Read current content
let content;
try {
content = fs.readFileSync(fp, 'utf-8');
} catch {
return; // File doesn't exist or unreadable
}
// Cache metadata for next session start
const cacheDir = path.join(process.cwd(), '.stackmemory');
ensureDir(cacheDir);
const lineCount = content.split('\n').length;
safeWriteFile(
path.join(cacheDir, 'theory-cache.json'),
JSON.stringify({
path: fp,
timestamp: new Date().toISOString(),
hash: crypto.createHash('md5').update(content).digest('hex'),
lineCount,
})
);
// Fire-and-forget: record as frame via CLI
const child = spawn(
'stackmemory',
['context', 'add', 'artifact', `THEORY.MD updated (${lineCount} lines)`],
{
cwd: process.cwd(),
stdio: 'ignore',
detached: true,
}
);
child.unref();
} catch {
// Silent fail -- never block the agent
}
}
main();