- Add plan-detection-hook.js: Detects planning discussions in user prompts - Add plan-persistence-checker.js: Validates plan documentation on response - Add plan-persistence-manager.js: Manages plan tracking state Implements architectural enforcement for plan persistence (inst_comm_plan_001). Prevents accidental loss of planning discussions in Claude Code sessions. Incident Reference: 2025-12-01 lost plan documentation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
172 lines
4.6 KiB
JavaScript
Executable file
172 lines
4.6 KiB
JavaScript
Executable file
#!/usr/bin/env node
|
|
|
|
/**
|
|
* Plan Detection Hook (UserPromptSubmit)
|
|
*
|
|
* Detects planning discussions in user prompts and tracks them.
|
|
* Part of the enforcement mechanism for inst_comm_plan_001.
|
|
*
|
|
* INCIDENT REFERENCE: 2025-12-01 lost plan documentation
|
|
*
|
|
* When a user initiates a planning discussion:
|
|
* 1. Detect planning keywords
|
|
* 2. Track the plan as in-progress
|
|
* 3. Inject a reminder to Claude about persisting plans
|
|
*
|
|
* Hook Input (JSON via stdin):
|
|
* {
|
|
* "session_id": "abc123",
|
|
* "hook_event_name": "UserPromptSubmit",
|
|
* "user_message": "Let's create a plan for the landing page redesign"
|
|
* }
|
|
*
|
|
* Hook Output (JSON to stdout):
|
|
* {
|
|
* "hookSpecificOutput": { ... },
|
|
* "systemMessage": "Planning discussion detected...",
|
|
* "continue": true
|
|
* }
|
|
*/
|
|
|
|
const path = require('path');
|
|
|
|
// Import the plan persistence manager
|
|
const planManager = require('./plan-persistence-manager.js');
|
|
|
|
/**
|
|
* Read JSON input from stdin
|
|
*/
|
|
function readStdin() {
|
|
return new Promise((resolve, reject) => {
|
|
let data = '';
|
|
process.stdin.on('data', chunk => { data += chunk; });
|
|
process.stdin.on('end', () => {
|
|
try {
|
|
resolve(JSON.parse(data));
|
|
} catch (err) {
|
|
reject(new Error('Invalid JSON input'));
|
|
}
|
|
});
|
|
process.stdin.on('error', reject);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Output hook response
|
|
*/
|
|
function outputResponse(analysis, systemMessage = null) {
|
|
const response = {
|
|
hookSpecificOutput: {
|
|
hookEventName: 'UserPromptSubmit',
|
|
planDetection: analysis
|
|
},
|
|
continue: true,
|
|
suppressOutput: false
|
|
};
|
|
|
|
if (systemMessage) {
|
|
response.systemMessage = systemMessage;
|
|
}
|
|
|
|
console.log(JSON.stringify(response));
|
|
}
|
|
|
|
/**
|
|
* Main hook logic
|
|
*/
|
|
async function main() {
|
|
let input;
|
|
|
|
try {
|
|
input = await readStdin();
|
|
} catch (err) {
|
|
// Invalid input, allow execution
|
|
outputResponse({ error: 'Invalid hook input' });
|
|
process.exit(0);
|
|
}
|
|
|
|
const { session_id, user_message } = input;
|
|
|
|
if (!user_message) {
|
|
outputResponse({ detected: false });
|
|
process.exit(0);
|
|
}
|
|
|
|
// Clean up old plans periodically
|
|
planManager.cleanupOldPlans();
|
|
|
|
// Detect if this is a planning discussion
|
|
const detection = planManager.detectPlanningDiscussion(user_message);
|
|
|
|
if (!detection.isPlanningDiscussion) {
|
|
// Not a planning discussion, check for unpersisted plans
|
|
const unpersisted = planManager.checkUnpersistedPlans(session_id);
|
|
|
|
if (unpersisted.hasUnpersistedPlans && unpersisted.hasHighPriority) {
|
|
// High priority unpersisted plans - CRITICAL warning
|
|
outputResponse(
|
|
{ detected: false, unpersistedCheck: unpersisted },
|
|
unpersisted.warningMessage
|
|
);
|
|
} else {
|
|
outputResponse({ detected: false });
|
|
}
|
|
|
|
process.exit(0);
|
|
}
|
|
|
|
// Track the plan in progress
|
|
const trackedPlan = planManager.trackPlanInProgress(
|
|
session_id,
|
|
detection.topic,
|
|
detection.confidence,
|
|
detection.isHighPriority
|
|
);
|
|
|
|
// Build system message
|
|
let systemMessage;
|
|
|
|
if (detection.isHighPriority) {
|
|
systemMessage =
|
|
`🚨 PLAN PERSISTENCE REQUIRED (inst_comm_plan_001)\n\n` +
|
|
`User has EXPLICITLY requested plan documentation.\n` +
|
|
`Topic: "${detection.topic}"\n\n` +
|
|
`BEFORE this session ends, you MUST:\n` +
|
|
`1. Create a comprehensive plan document\n` +
|
|
`2. Save it to: docs/plans/PLAN_[TOPIC]_${new Date().toISOString().slice(0, 10).replace(/-/g, '')}.md\n` +
|
|
`3. This is MANDATORY - do NOT rely on internal memory\n\n` +
|
|
`⚠️ INCIDENT 2025-12-01: User's plan was lost because Claude kept it in memory. Never again.`;
|
|
} else if (detection.confidence >= 0.7) {
|
|
systemMessage =
|
|
`📋 Planning Discussion Detected (${Math.round(detection.confidence * 100)}% confidence)\n\n` +
|
|
`Topic: "${detection.topic}"\n\n` +
|
|
`Per inst_comm_plan_001: If this discussion produces a significant plan, ` +
|
|
`save it to docs/plans/ directory before session ends.\n\n` +
|
|
`Recommended file: docs/plans/PLAN_[TOPIC]_${new Date().toISOString().slice(0, 10).replace(/-/g, '')}.md`;
|
|
} else {
|
|
// Lower confidence - just note it
|
|
systemMessage =
|
|
`📝 Possible planning discussion detected.\n` +
|
|
`If significant plans emerge, save to docs/plans/.`;
|
|
}
|
|
|
|
outputResponse({
|
|
detected: true,
|
|
topic: detection.topic,
|
|
confidence: detection.confidence,
|
|
isHighPriority: detection.isHighPriority,
|
|
tracked: true
|
|
}, systemMessage);
|
|
|
|
process.exit(0);
|
|
}
|
|
|
|
// Run hook
|
|
main().catch(err => {
|
|
console.error(JSON.stringify({
|
|
hookSpecificOutput: { error: err.message },
|
|
continue: true,
|
|
suppressOutput: false
|
|
}));
|
|
process.exit(0);
|
|
});
|