#!/usr/bin/env node /** * Rule #5 — Memory write requires memory-sync coverage. * * PreToolUse hook on Edit / Write / MultiEdit. If the file_path looks like a * memory store .md (memory/*.md or MEMORY.md), require the last assistant * message to declare `coverage: direct:memory-sync` OR `coverage: skill:*` for * a memory-related skill. Otherwise block with a re-announce instruction. * * Override phrase: `memory dump` in user's last prompt suppresses this rule. * * Spec: docs/superpowers/specs/2026-05-25-enforce-hard-rules-design.md */ import { readStdin, parseEventJson, readTranscript, lastUserPromptText, lastAssistantText, parseCoverageLine, findOverride, logOverride, exitDecision, isMemoryPath, } from './enforce-hook-helpers.mjs'; const RULE_KEY = 'memory-sync-coverage'; function isMemorySyncCoverage(cov) { if (!cov) return false; if (cov.channel === 'direct' && /memory-sync/i.test(cov.id)) return true; if (cov.channel === 'skill' && /memory/i.test(cov.id)) return true; return false; } export function decide({ toolName, filePath, transcriptEntries, override }) { if (!['Edit', 'Write', 'MultiEdit'].includes(toolName)) { return { block: false }; } if (!isMemoryPath(filePath)) return { block: false }; if (override) return { block: false }; const assistantText = lastAssistantText(transcriptEntries); const cov = parseCoverageLine(assistantText); if (isMemorySyncCoverage(cov)) return { block: false }; return { block: true, message: [ `[enforce-memory-coverage] Write to memory path requires memory-sync coverage tag.`, `Detected coverage: ${cov ? cov.channel + ':' + cov.id : 'NONE'} (stale or absent).`, ``, `Re-announce on a fresh assistant turn first:`, ` coverage: direct:memory-sync`, `Then retry the Edit/Write.`, ].join('\n'), }; } async function main() { try { const raw = await readStdin(); const event = parseEventJson(raw); const toolName = event.tool_name || ''; const filePath = (event.tool_input && (event.tool_input.file_path || event.tool_input.notebook_path)) || ''; const transcript = readTranscript(event.transcript_path); const userPrompt = lastUserPromptText(transcript); const override = findOverride(userPrompt, RULE_KEY); if (override) logOverride(RULE_KEY, override, event.session_id); const result = decide({ toolName, filePath, transcriptEntries: transcript, override }); exitDecision(result); } catch { // Fail-quiet on any internal error. exitDecision({ block: false }); } } const isCli = process.argv[1] && process.argv[1].replace(/\\/g, '/').endsWith('/enforce-memory-coverage.mjs'); if (isCli) main();