A Model Context Protocol (MCP) server that enforces custom workflow protocols before allowing AI agents to perform file operations.
Author: Jason Lusk [email protected] License: MIT Gist URL: https://gist.github.com/mpalpha/c2f1723868c86343e590ed38e80f264d
Universal gatekeeper for AI coding assistants supporting Model Context Protocol:
- ✅ Works with any MCP-compatible client (Claude Code, Cursor, Cline, Zed, Continue)
- ✅ Enforces custom protocol steps before planning/coding
- ✅ Tracks required checklist items specific to your project
- ✅ Records compliance violations over time
- ✅ Fully configurable - adapt to any workflow
- ✅ Runs from npx - no installation needed
MAJOR UPDATE: Complete v3.0 system with three-gate enforcement (TEACH → LEARN → REASON), automated installation, and enhanced protocol workflow.
Three-Gate Enforcement:
- TEACH Gate: Blocks new work if previous sessions have unrecorded experiences (
.protocol-recording-required-{sessionId}) - LEARN Gate: Blocks file operations until
search_experiencescalled (checks.protocol-search-completed-{sessionId}) - REASON Gate: Blocks file operations until
informed_reasoningcalled (checks.protocol-session-{sessionId}.json) - Enforcement Flow: pre-tool-use.cjs enforces all three gates before allowing Write/Edit/NotebookEdit
- Tool Exemptions: Research tools (Read, Glob, Grep, Bash, Task) and protocol tools (all MCP tools) always exempt
Actionable Error Messages:
- Clear Gate Messages: "⛔ LEARN gate blocking Write - call search_experiences first"
- Parameter Validation: All tools validate required parameters with helpful messages
- Token State Errors: Specific messages for invalid, expired, or already-used tokens
- Example Format: Each error explains what's wrong and how to fix it
Installation:
- AI-agent guided installation via README documentation
- Analysis-driven approach: Agents analyze environment before installing
- Token management: V3 uses session-specific tokens (old v2 tokens should be removed manually)
- State preservation: Safe to reconfigure multiple times
Enhanced Token System (v3):
- Session-Specific Naming: All tokens use
.protocol-*-{sessionId}format for session isolation - Session Persistence:
.protocol-current-session-idmaintains session across compaction (8-hour TTL) - TTL Management:
- Search tokens: 5 minutes
- Reasoning tokens: 60 minutes (up from 10 minutes)
- Recording tokens: 24 hours (previously infinite)
- Session ID: 8 hours
- Loop Prevention:
.protocol-stop-hook-reminders.jsontracks reminders to prevent spam
Hook Updates:
- user-prompt-submit.cjs: Creates search requirement token for task detection
- pre-tool-use.cjs: Enforces three-gate system with clear messages
- post-tool-use.cjs: Creates recording requirement token with session-specific naming
- stop.cjs: TEACH phase reminders with loop prevention
- session-start.cjs: Minimal setup (no prompt injection)
- pre-compact-handoff.cjs: Last chance reminder before compaction
Breaking Changes from v2:
- Token naming changed (session-specific:
.protocol-*-{sessionId}) - Three gates instead of two (added TEACH gate for unrecorded work)
- Recording tokens expire after 24 hours (not infinite)
- Hook implementations completely rewritten for v3 protocol
- Configuration file changes (see INSTALLATION.md)
Migration Path:
- V3 uses new token format (session-specific naming:
.protocol-*-{sessionId}) - Old v2 tokens are incompatible and should be removed manually
- First v3 workflow establishes new token format
Works with: memory-augmented-reasoning v3.0.0+
This guide is designed for AI agents to install and configure the MCP server with hooks automatically.
Agent must verify:
- Node.js: Run
node --version(require v14+) - npm: Run
npm --version - Workspace Path: Run
pwdto get absolute project path - Shell: Run
echo $SHELLto verify bash/zsh
# Detect OS
uname -s # Darwin (macOS), Linux, etc.
# Get absolute workspace path
WORKSPACE_PATH=$(pwd)
# Check Node.js version
node --version # Must be v14.0.0 or higher
# Check for existing configuration
ls ~/.claude.json 2>/dev/null && echo "Config exists" || echo "Config missing"
ls .mcp.json 2>/dev/null && echo ".mcp.json exists (WARN: known issues)" || echo "No .mcp.json"
ls .claude/settings.local.json 2>/dev/null && echo "Local settings exist" || echo "No local settings"
# Detect IDE/Hook Environment
ls .cursor/hooks/ 2>/dev/null && echo "Cursor IDE detected (argv hook pattern)" || echo "No Cursor hooks"
ls .claude/hooks/ 2>/dev/null && echo "Claude Code detected (stdin hook pattern)" || echo "No Claude Code hooks"CRITICAL: Hook Input Pattern Detection
Different IDEs use different hook invocation patterns:
| IDE | Hook Directory | Input Method | Pattern |
|---|---|---|---|
| Cursor | .cursor/hooks/ |
Command-line args | process.argv[2] |
| Claude Code | .claude/hooks/ |
stdin (standard input) | process.stdin |
Why This Matters: The hooks in this gist are designed for Cursor's argv pattern. If installing for Claude Code, you MUST adapt the input method while preserving all enforcement logic.
Decision Tree:
IF .cursor/hooks/ exists:
→ Use gist hooks AS-IS (no modification needed)
→ Cursor passes event data as process.argv[2]
ELSE IF .claude/hooks/ exists:
→ MUST adapt hooks to stdin pattern
→ Claude Code passes event data via stdin
→ See "Hook Adaptation for Claude Code" section below
ELSE:
→ Uncertain environment
→ Test hook invocation manually
→ Check IDE documentation
Goal: Convert gist hooks from argv pattern (Cursor) to stdin pattern (Claude Code) while preserving ALL features.
Features to Preserve:
- ✅ Three-gate system (TEACH/LEARN/REASON)
- ✅ Smart task detection (only remind for file operations)
- ✅ Tampering detection (checksum validation)
- ✅ Correct step ordering (analyze → search → integrate → reason → record)
- ✅ Session ID consistency (getSessionId() function)
- ✅ Token management (creation, cleanup, expiration)
Adaptation Pattern:
FROM (Cursor/Gist - argv pattern):
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
// Cursor: Event data passed as command-line argument
const event = JSON.parse(process.argv[2]);
const prompt = event.prompt || '';
// ... rest of hook logic ...
// Return result synchronously
const result = { shouldBlock: false };
console.log(JSON.stringify(result));TO (Claude Code - stdin pattern):
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
// Claude Code: Event data passed via stdin
let input = '';
process.stdin.setEncoding('utf8');
process.stdin.on('data', (chunk) => {
input += chunk;
});
process.stdin.on('end', () => {
try {
const event = JSON.parse(input);
const prompt = event.prompt || '';
// ... SAME hook logic as gist (preserve all features) ...
// Return result after async stdin processing
const result = { shouldBlock: false };
console.log(JSON.stringify(result));
} catch (err) {
console.error(`[hook-error] Failed to process stdin: ${err.message}`);
console.log(JSON.stringify({ shouldBlock: false }));
}
});Key Changes:
- Input Method: Replace
process.argv[2]with stdin event handlers - Async Pattern: Wrap logic in
process.stdin.on('end')callback - Error Handling: Catch JSON parse errors from stdin
- Everything Else: Keep EXACTLY the same (getSessionId, token logic, validation, etc.)
Validation After Adaptation:
# Test hook with stdin (Claude Code pattern)
echo '{"prompt": "test prompt", "tool": "Write"}' | node .claude/hooks/pre-tool-use.cjs
# Should output JSON response:
# {"shouldBlock": false} or {"shouldBlock": true, "reason": "..."}
# Check stderr for hook messages (token creation, validation, etc.)Common Mistakes to Avoid:
- ❌ Don't change token paths or session ID logic
- ❌ Don't modify feature behavior (three-gate system, etc.)
- ❌ Don't remove error handling or logging
- ❌ Don't change the hook result format
- ✅ ONLY change how event data is received (argv → stdin)
CRITICAL: Configure in ~/.claude.json, NOT .mcp.json
Reason: .mcp.json with enabledMcpjsonServers has known loading issues in Claude Code 2.1.17+
Configuration Location:
~/.claude.json → projects.{absolute_workspace_path}.mcpServers
Check if Already Configured:
# Read existing config
cat ~/.claude.json | grep -A 20 "$WORKSPACE_PATH"
# Look for existing mcpServers entryAgent should update existing configuration rather than duplicate:
// Pseudo-code for agent
const config = JSON.parse(fs.readFileSync('~/.claude.json'));
const projectPath = process.cwd();
if (!config.projects) config.projects = {};
if (!config.projects[projectPath]) {
config.projects[projectPath] = { mcpServers: {} };
}
// Update/add protocol-enforcer
config.projects[projectPath].mcpServers['protocol-enforcer'] = {
type: 'stdio',
command: 'npx',
args: ['-y', 'https://gist.github.com/mpalpha/c2f1723868c86343e590ed38e80f264d'],
env: {}
};
// Write back
fs.writeFileSync('~/.claude.json', JSON.stringify(config, null, 2));CRITICAL: Use absolute paths, NOT ${workspaceFolder} variable
Reason: ${workspaceFolder} resolution fails in Claude Code 2.1.17+, causing silent hook failures
Create Hooks Directory:
mkdir -p .cursor/hooksWrite Hook Files:
Agent must create 3 hook files (get content from Appendix C below):
.cursor/hooks/user-prompt-submit.cjs- Advisory reminder.cursor/hooks/pre-tool-use.cjs- Blocking enforcement (CRITICAL).cursor/hooks/post-tool-use.cjs- Automatic experience recording
Make Executable:
chmod +x .cursor/hooks/*.cjsConfigure Hooks with Absolute Paths:
# Get absolute workspace path
WORKSPACE_PATH=$(pwd)
# Create/update .claude/settings.local.json
cat > .claude/settings.local.json << EOF
{
"hooks": {
"UserPromptSubmit": [{
"hooks": [{
"type": "command",
"command": "$WORKSPACE_PATH/.cursor/hooks/user-prompt-submit.cjs"
}, {
"type": "command",
"command": "$WORKSPACE_PATH/.cursor/hooks/user-prompt-submit-reminder.cjs"
}]
}],
"PreToolUse": [{
"matcher": "Write|Edit|NotebookEdit|WebSearch|Grep",
"hooks": [{
"type": "command",
"command": "$WORKSPACE_PATH/.cursor/hooks/pre-tool-use.cjs"
}]
}],
"PostToolUse": [{
"matcher": "Write|Edit|NotebookEdit|WebSearch|Grep",
"hooks": [{
"type": "command",
"command": "$WORKSPACE_PATH/.cursor/hooks/post-tool-use.cjs"
}]
}]
}
}
EOFBOTH UserPromptSubmit hooks are REQUIRED - not optional:
- user-prompt-submit.cjs - Creates enforcement tokens (backend state tracking)
- user-prompt-submit-reminder.cjs - Shows protocol reminder to agent (frontend visibility)
Why BOTH are required:
- user-prompt-submit.cjs: Creates tokens for LEARN gate enforcement
- user-prompt-submit-reminder.cjs: Makes agent SEE the protocol reminder
- Without BOTH: System appears broken (agents skip protocol, no reminders OR no enforcement)
Hook Output Format (v3.0.3 Fix):
❌ WRONG - silently ignored by Claude Code:
console.log(JSON.stringify({ userPrompt: "message" })); // Invalid field!✅ CORRECT - agent sees this:
console.log("message"); // Plain text to stdoutTest Your Installation:
# Verify reminder hook outputs plain text
echo '{"prompt":"test"}' | node .cursor/hooks/user-prompt-submit-reminder.cjs
# Expected output (plain text):
🔵 NEW REQUEST - Protocol MANDATORY for ALL responses:
1. search_experiences (LEARN gate) - ALWAYS FIRST
2. informed_reasoning analyze phase (REASON gate)
3. THEN respond to user
⛔ Do NOT respond to user until steps 1-2 complete.
# WRONG output (will be ignored by agent):
{"userPrompt": "🔵 NEW REQUEST..."}Troubleshooting:
- Agent not seeing reminders? → Check both hooks configured in settings
- Only seeing stderr logs? → Missing user-prompt-submit-reminder.cjs
- No token enforcement? → Missing user-prompt-submit.cjs
- Hooks output JSON with userPrompt field? → Update to v3.0.3 (uses plain text)
Test 1: MCP Server Connection
// Agent calls this tool
await mcp__protocol_enforcer__get_protocol_config({});
// Expected: Returns config object
// If fails: Check ~/.claude.json configurationTest 2: Hook Enforcement
# Try to use Write tool WITHOUT calling informed_reasoning first
# Expected: Hook blocks operation with error message
# After calling informed_reasoning:
# Expected: Hook allows operationTest 3: Token Creation
# Clear existing tokens
rm ~/.protocol-informed-reasoning-token 2>/dev/null
# Have user request a file operation
# Hook should block and show error message
# User should see protocol requirement
# After informed_reasoning:
ls -la ~/.protocol-informed-reasoning-token && echo "✅ Token created" || echo "❌ FAILED"Issue: MCP Server Not Loading
Symptom: get_protocol_config returns "No such tool"
Diagnosis:
1. Check ~/.claude.json has project path entry
2. Verify mcpServers configuration exists
3. Check for .mcp.json conflicts (remove if exists)
4. Reload Claude Code (Cmd+Shift+P → Reload Window)
Resolution: Reconfigure in ~/.claude.json, reload
Issue: Hooks Not Blocking Operations
Symptom: Can use Write without informed_reasoning
Diagnosis:
1. Check hook files exist: ls .cursor/hooks/*.cjs
2. Check hook files executable: ls -la .cursor/hooks/*.cjs
3. Check settings.local.json has absolute paths
4. Check ${workspaceFolder} not used (causes silent failure)
Resolution: Rewrite settings.local.json with absolute paths, reload
Issue: Hooks Return Errors
Symptom: Hook execution fails with error messages
Diagnosis:
1. Check hook file permissions (must be executable)
2. Check Node.js shebang: #!/usr/bin/env node
3. Test hook manually: node .cursor/hooks/pre-tool-use.cjs
Resolution: Fix permissions with chmod +x, verify shebang line
Agent should inform user:
✅ Installation Complete
MCP Server Configured:
- protocol-enforcer (v2.0.14+)
Hooks Installed:
- user-prompt-submit.cjs (advisory)
- pre-tool-use.cjs (blocking enforcement)
- post-tool-use.cjs (automatic recording)
Verification Tests: 3/3 passed
Next Steps:
1. Reload Claude Code (Cmd+Shift+P → Reload Window)
2. Test protocol flow: Use informed_reasoning before file operations
3. Verify hook blocking works
For AI agents executing this installation:
-
Analyze Environment:
- Run:
pwd,uname -s,node --version,npm --version - Check:
~/.claude.json,.mcp.json,.cursor/hooks/
- Run:
-
Configure MCP Server:
- Read
~/.claude.json - Add/update
projects.{absolute_path}.mcpServers - Use npx remote execution URL
- Read
-
Install Hooks:
- Create
.cursor/hooks/directory - Write 3 hook files from Appendix C
- Make executable with
chmod +x - Configure in
.claude/settings.local.jsonwith absolute paths
- Create
-
Verify Installation:
- Test 1: MCP connection
- Test 2: Hook blocking
- Test 3: Token creation
-
Troubleshoot Failures:
- Use decision tree above
- Check logs and file permissions
- Prefer absolute paths over variables
-
Inform User:
- Summarize what was installed
- Report test results
- Provide next steps
| Platform | Config File | Hook Support | System Prompt | Enforcement |
|---|---|---|---|---|
| Claude Code | .mcp.json or ~/.claude.json |
✅ Full (all 5 hooks) | ✅ Via tweakcc | Hooks OR System Prompt |
| Cursor | ~/.cursor/mcp.json |
✅ Standard (PreToolUse) | ❌ Not available | Hooks only |
| Cline | ~/.cline/mcp.json |
❌ Not available | Audit only | |
| Zed | ~/.config/zed/mcp.json |
❌ None | ❌ Not available | Voluntary |
| Continue | ~/.continue/mcp.json |
❌ Not available | Voluntary |
Available Hooks: user_prompt_submit, session_start, pre_tool_use, post_tool_use, stop
- Option A: Hooks - External scripts that intercept and block operations (Quick Installation Steps 3-5)
- Option B: System Prompt - Modify Claude's instructions to use protocol tools (Quick Installation Step 6)
- Recommended: Both - Defense-in-depth approach for maximum reliability
Claude Code users should complete both hooks (Steps 3-5) and system prompt setup (Step 6) for maximum reliability.
Without hooks OR system prompt integration, the protocol-enforcer MCP server provides tools that must be voluntarily called by Claude.
- Option A: Hooks - External scripts block operations (covered below)
- Option B: System Prompt - Modify Claude's instructions to use protocol tools (see System Prompt Integration)
- Recommended: Both - For maximum reliability
This section covers Option A (Hooks). For Option B, see System Prompt Integration below.
Complete setup in 7 steps - installs MCP server + enforcement (hooks + system prompt):
Add to your platform's MCP config file (paths above):
{
"mcpServers": {
"protocol-enforcer": {
"command": "npx",
"args": ["-y", "https://gist.github.com/mpalpha/c2f1723868c86343e590ed38e80f264d"]
}
}
}Claude Code only: If using .claude/settings.local.json with enabledMcpjsonServers, add "protocol-enforcer".
.mcp.json file with enabledMcpjsonServers may not work reliably. If Step 7.1 shows "No matching deferred tools found", the MCP servers didn't load.
Workaround: Manually add servers to ~/.claude.json under projects path mcpServers key, or use Claude Code's UI to add MCP servers (Settings → MCP Servers).
Download the Foundation config template (recommended):
curl -o .protocol-enforcer.json \
https://gist.githubusercontent.com/mpalpha/c2f1723868c86343e590ed38e80f264d/raw/config.foundation.jsonOr create minimal config in .protocol-enforcer.json:
{
"enforced_rules": {
"require_protocol_steps": [
{
"name": "planning",
"hook": "pre_tool_use",
"applies_to": ["Write", "Edit"]
}
],
"require_checklist_confirmation": true,
"minimum_checklist_items": 2
},
"checklist_items": [
{
"text": "Requirements gathered",
"hook": "pre_tool_use"
},
{
"text": "Existing patterns analyzed",
"hook": "pre_tool_use"
},
{
"text": "Linting passed",
"hook": "post_tool_use"
}
]
}See: Example Configurations for more options.
Create hooks directory:
mkdir -p .cursor/hooksCreate hook files from Appendix C:
You need to create 3 hook scripts. See Appendix C: Hook Scripts Reference for complete code.
Required hooks:
.cursor/hooks/pre-tool-use.cjs- Blocks file and research operations without valid tokens (CRITICAL FOR ENFORCEMENT).cursor/hooks/post-tool-use.cjs- Triggers automatic experience recording after successful tool execution (integrates with memory-augmented-reasoning v2.0.1+).cursor/hooks/user-prompt-submit.cjs- Reminds Claude to use informed_reasoning first
Quick setup (AI-assisted installation recommended):
- If installing via Claude: "Create hooks from Appendix C"
- If installing manually: Copy hook code from Appendix C sections below
Make all executable:
chmod +x .cursor/hooks/*.cjsWhat each hook does:
- pre-tool-use.cjs - Blocks file and research operations (Write/Edit/NotebookEdit/WebSearch/Grep/Task) without valid tokens (CRITICAL FOR ENFORCEMENT)
- post-tool-use.cjs - Triggers automatic experience recording after successful tool execution (integrates with memory-augmented-reasoning v2.0.1+)
- user-prompt-submit.cjs - Reminds Claude to use informed_reasoning first
To skip hooks: MCP tools will still work, but no automatic blocking. Claude must voluntarily comply.
Add to .claude/settings.json (project) or ~/.claude/settings.json (user):
{
"hooks": {
"UserPromptSubmit": [{
"hooks": [{
"type": "command",
"command": "${workspaceFolder}/.cursor/hooks/user-prompt-submit.cjs"
}, {
"type": "command",
"command": "$WORKSPACE_PATH/.cursor/hooks/user-prompt-submit-reminder.cjs"
}]
}],
"PreToolUse": [{
"matcher": "Write|Edit|NotebookEdit|WebSearch|Grep",
"hooks": [{
"type": "command",
"command": "${workspaceFolder}/.cursor/hooks/pre-tool-use.cjs"
}]
}],
"PostToolUse": [{
"matcher": "Write|Edit|NotebookEdit|WebSearch|Grep",
"hooks": [{
"type": "command",
"command": "${workspaceFolder}/.cursor/hooks/post-tool-use.cjs"
}]
}]
}
}${workspaceFolder} variable may not resolve correctly in hook paths, causing hooks to silently fail. If Step 7.3 verification shows hooks aren't blocking operations, use absolute paths instead:
{
"hooks": {
"PreToolUse": [{
"matcher": "Write|Edit|NotebookEdit|WebSearch|Grep",
"hooks": [{
"type": "command",
"command": "/absolute/path/to/project/.cursor/hooks/pre-tool-use.cjs"
}]
}],
"PostToolUse": [{
"matcher": "Write|Edit|NotebookEdit|WebSearch|Grep",
"hooks": [{
"type": "command",
"command": "/absolute/path/to/project/.cursor/hooks/post-tool-use.cjs"
}]
}],
"UserPromptSubmit": [{
"hooks": [{
"type": "command",
"command": "/absolute/path/to/project/.cursor/hooks/user-prompt-submit.cjs"
}, {
"type": "command",
"command": "/absolute/path/to/project/.cursor/hooks/user-prompt-submit-reminder.cjs"
}]
}]
}
}Replace /absolute/path/to/project with your actual project path.
Extended Enforcement (Foundation v2.0): The matcher pattern includes 5 tool types for comprehensive protocol coverage:
- File Operations:
Write,Edit,NotebookEdit- Traditional file modifications - Research Operations:
WebSearch,Grep- Extended enforcement ensures protocol compliance before search/analysis
Important: Task Tool Exemption (v2.0.9+)
The Task tool is intentionally EXCLUDED from protocol enforcement to prevent circular dependencies:
- Task spawns subagents that may need to explore the codebase DURING the protocol analysis phase
- Including Task in the matcher creates deadlocks: Need Task → Need protocol → Need Task for analysis
- Task operations are still tracked for experience recording via PostToolUse hook
- If you only want to enforce protocol for file operations, use:
"matcher": "Write|Edit|NotebookEdit"
- Claude Code/Cursor/VSCode:
Cmd+Shift+P→ "Developer: Reload Window" - Zed: Restart Zed
This step adds protocol awareness to Claude's instructions for defense-in-depth.
Other platforms (Cursor, Cline, Zed): Skip to Step 7 - you'll rely on hooks only.
Claude Code users: This step is recommended for maximum reliability (hooks + system prompt = defense-in-depth).
Step 6a: Install tweakcc
Ask Claude to check if tweakcc is installed:
tweakcc --version 2>/dev/null || echo "Not installed"If not installed, ask Claude:
Install tweakcc globally with npm
Claude should run: npm install -g tweakcc
Step 6b: Launch tweakcc interactive UI
Ask Claude:
Launch tweakcc to extract and modify system prompts
Claude should run: npx tweakcc
This launches an interactive UI that extracts Claude Code's system prompts to ~/.tweakcc/system-prompts/.
Step 6c: Add protocol enforcement to system prompt
Ask Claude:
Add protocol-enforcer requirements to the end of the main system prompt file
Claude should add this to the end of the extracted main system prompt file in ~/.tweakcc/system-prompts/:
---
## Protocol Enforcement
Before using Write, Edit, NotebookEdit, WebSearch, Grep, or Task tools, you MUST:
1. Call `mcp__memory_augmented_reasoning__informed_reasoning` with analysis of the task
2. Call `mcp__protocol_enforcer__verify_protocol_compliance` with checklist verification
3. Call `mcp__protocol_enforcer__authorize_file_operation` to get operation token
After completing any task, you MUST:
4. Call `mcp__memory_augmented_reasoning__record_experience` to capture learnings
Only after completing all protocol steps can you proceed. This is enforced by hooks and your system prompt instructions. Experience recording is mandatory - protocol without learning is like code review without keeping notes.Step 6d: Apply modifications
Ask Claude:
Apply the tweakcc modifications to Claude Code
Claude should run: npx tweakcc --apply
Step 6e: Restart Claude Code
Fully restart Claude Code (quit and relaunch, not just reload window) for system prompt changes to take effect.
For detailed troubleshooting, see Appendix B: System Prompt Integration.
After reloading, verify the installation is working correctly:
Step 1: Test MCP Server Connection
Ask Claude:
Get my protocol enforcer configuration
Expected response: Claude will call mcp__protocol_enforcer__get_protocol_config() and show your .protocol-enforcer.json configuration.
Step 2: Test Full Protocol Flow (if using with memory-augmented-reasoning)
Ask Claude:
Use informed_reasoning to explain the protocol-enforcer configuration I just set up
This will:
- Call
informed_reasoning(analyze phase) - creates first token - Initialize the memory-augmented-reasoning database on first run
- Verify both MCP servers are loaded and responding correctly
- Provide an explanation of your configuration settings
Step 3: Test Hook Enforcement
Try a simple file operation that should be blocked:
Create a test.txt file with "hello"
Expected behavior:
- If hooks are working: Claude will be blocked and must complete protocol steps first
- If hooks aren't configured: Claude will write the file directly (protocol is voluntary)
${workspaceFolder}variable not resolving (use absolute paths - see Step 4)- Hook files not executable (run
chmod +x .cursor/hooks/*.cjs) - Configuration not reloaded (reload IDE)
- Wrong hook paths (verify files exist at specified paths)
Step 4: Test Experience Recording (if using with memory-augmented-reasoning)
After completing any task, verify experience recording is working:
Ask Claude:
Search for recent experiences related to protocol enforcement
Expected behavior:
- Claude will call
mcp__memory_augmented_reasoning__search_experienceswith query "protocol enforcement" - Should return any experiences recorded during this session
- If no experiences found: Check that informed_reasoning's "record" phase is being called after task completion
- Verify
~/.memory-augmented-reasoning/experiences.dbexists and is not empty
- informed_reasoning not reaching "record" phase (check protocol flow completion)
- Database permissions issue (check
~/.memory-augmented-reasoning/directory is writable) - MCP server errors during record phase (check Claude Code logs)
Troubleshooting:
- If MCP server doesn't appear: Check
.mcp.jsonsyntax, reload IDE - If hooks don't block: Verify hook paths are absolute, check
chmod +xon hook files - If token errors occur: Check
~/.protocol-enforcer-tokenand~/.protocol-informed-reasoning-tokenfiles exist after calling respective tools - Tools installed but protocol not enforced: Protocol-enforcer provides tools but doesn't auto-enforce. Configure hooks (above) OR system prompt with tweakcc (below). Verify by asking Claude: "What protocol steps do you follow before file operations?"
When a user requests installation:
- Detect platform - Check which AI assistant and MCP config location
- Analyze project - Read ALL rule files (
.cursor/rules/**/*,.cursorrules,.github/**/*,docs/**/*, etc.) - Extract requirements - Identify protocol steps, checklist items, and behavioral rules
- Determine hook support - Configure based on platform capabilities (see table above)
- Propose configuration - Present tailored config matching project workflow
- Get approval - Confirm before creating files
Detailed guide: See Appendix A: AI Agent Installation Guide
All protocol steps and checklist items must be objects with a hook property (string format not supported).
Protocol Step Object:
{
"name": "step_name",
"hook": "pre_tool_use",
"applies_to": ["Write", "Edit"] // Optional: tool-specific filtering
}Checklist Item Object:
{
"text": "Item description",
"hook": "pre_tool_use",
"applies_to": ["Write", "Edit"] // Optional: tool-specific filtering
}| Hook | When | Use Case |
|---|---|---|
user_prompt_submit |
Before processing user message | Pre-response checks, sequential thinking |
session_start |
At session initialization | Display requirements, initialize tracking |
pre_tool_use |
Before tool execution | Primary enforcement point for file operations |
post_tool_use |
After tool execution | Validation, linting, audit logging |
stop |
Before session termination | Compliance reporting, cleanup |
Minimal (3 items):
{
"enforced_rules": {
"require_protocol_steps": [
{ "name": "sequential_thinking", "hook": "user_prompt_submit" },
{ "name": "planning", "hook": "pre_tool_use", "applies_to": ["Write", "Edit"] }
],
"require_checklist_confirmation": true,
"minimum_checklist_items": 2
},
"checklist_items": [
{ "text": "Sequential thinking completed FIRST", "hook": "user_prompt_submit" },
{ "text": "Plan created and confirmed", "hook": "pre_tool_use" },
{ "text": "Completion verified", "hook": "post_tool_use" }
]
}See also:
config.minimal.json- Basic workflow (6 items)config.development.json- Full development workflow (17 items)config.behavioral.json- LLM behavioral corrections (12 items)
Verify protocol steps completed for a specific hook.
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
hook |
string | ✅ Yes | Lifecycle point: user_prompt_submit, session_start, pre_tool_use, post_tool_use, stop |
tool_name |
string | No | Tool being called (Write, Edit) for tool-specific filtering |
protocol_steps_completed |
string[] | ✅ Yes | Completed step names from config |
checklist_items_checked |
string[] | ✅ Yes | Verified checklist items from config |
Example:
const verification = await mcp__protocol_enforcer__verify_protocol_compliance({
hook: "pre_tool_use",
tool_name: "Write",
protocol_steps_completed: ["planning", "analysis"],
checklist_items_checked: ["Plan confirmed", "Patterns analyzed"]
});
// Returns: { compliant: true, operation_token: "abc123...", token_expires_in_seconds: 60 }
// Or: { compliant: false, violations: [...] }MANDATORY before Write/Edit (when using PreToolUse hooks).
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
operation_token |
string | ✅ Yes | Token from verify_protocol_compliance |
Token rules: Single-use, 60-second expiration, writes ~/.protocol-enforcer-token for hook verification.
Get current configuration.
Returns: { config_path: "...", config: {...} }
Get compliance statistics and recent violations.
Returns: { total_checks: N, passed: N, failed: N, recent_violations: [...] }
Create new config file.
Parameters: scope: "project" | "user"
// 1. MANDATORY FIRST STEP: Call informed_reasoning (analyze phase)
// This writes ~/.protocol-informed-reasoning-token
await mcp__memory_augmented_reasoning__informed_reasoning({
phase: "analyze",
problem: "User request description and context needed"
});
// 2. At user message (user_prompt_submit hook) - Optional verification
await mcp__protocol_enforcer__verify_protocol_compliance({
hook: "user_prompt_submit",
protocol_steps_completed: ["informed_reasoning_analyze"],
checklist_items_checked: ["Used informed_reasoning (analyze phase) tool"]
});
// 3. Before file operations (pre_tool_use hook)
const verification = await mcp__protocol_enforcer__verify_protocol_compliance({
hook: "pre_tool_use",
tool_name: "Write",
protocol_steps_completed: ["informed_reasoning_analyze", "planning", "analysis"],
checklist_items_checked: [
"Used informed_reasoning (analyze phase) tool before proceeding",
"Plan confirmed",
"Patterns analyzed"
]
});
// 4. Authorize file operation
// This writes ~/.protocol-enforcer-token
if (verification.compliant) {
await mcp__protocol_enforcer__authorize_file_operation({
operation_token: verification.operation_token
});
// Now Write/Edit operations allowed
// PreToolUse hook will check for BOTH tokens
}
// 5. After file operations (post_tool_use hook)
await mcp__protocol_enforcer__verify_protocol_compliance({
hook: "post_tool_use",
tool_name: "Write",
protocol_steps_completed: ["execution"],
checklist_items_checked: ["Linting passed", "Types checked"]
});- Only rules with matching
hookvalue are checked - If
applies_tospecified, tool name must match - Enables context-specific enforcement at different lifecycle points
For automatic blocking of unauthorized file operations (Claude Code, Cursor only).
- Create hooks directory:
mkdir -p .cursor/hooks-
Create hook scripts from templates (see Appendix C)
-
Make executable:
chmod +x .cursor/hooks/*.cjs- Configure platform:
Claude Code CLI (v2.1.7+) - Add to .claude/settings.json (project) or ~/.claude/settings.json (user):
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write|Edit|NotebookEdit|WebSearch|Grep",
"hooks": [
{
"type": "command",
"command": "/absolute/path/.cursor/hooks/pre-tool-use.cjs"
}
]
}
],
"PostToolUse": [
{
"matcher": "Write|Edit|NotebookEdit|WebSearch|Grep",
"hooks": [
{
"type": "command",
"command": "/absolute/path/.cursor/hooks/post-tool-use.cjs"
}
]
}
]
}
}Note: The extended matcher includes WebSearch, Grep, and Task to enforce protocol compliance for research/analysis operations, not just file modifications. This implements the extended_enforcement strategy from Foundation v2.0.
IMPORTANT: Hook response format as of Claude Code v2.1.7+ (Jan 2026):
// Deny operation
console.log(JSON.stringify({
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: "Reason shown to Claude/user"
}
}));
// Allow operation
console.log(JSON.stringify({
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "allow"
}
}));Replace /absolute/path/ with your actual project path.
1. AI calls informed_reasoning (analyze phase)
→ writes ~/.protocol-informed-reasoning-token
2. AI calls verify_protocol_compliance → receives operation_token (60s expiration)
3. AI calls authorize_file_operation(token) → writes ~/.protocol-enforcer-token
4. AI attempts Write/Edit → PreToolUse hook intercepts
CHECK 1: informed_reasoning token exists?
CHECK 2: protocol-enforcer token exists?
- Both found → consume both (delete), allow operation
- Either missing → block operation
5. Next Write/Edit → Both tokens missing → blocked
Result: Two-factor verification ensures both thinking and protocol compliance.
Why Two Tokens?
- Reasoning Token: Physical proof that informed_reasoning tool was actually called
- Protocol Token: Authorization after verifying all protocol steps and checklist items
- Cross-Process Verification: Hooks run separately from MCP server, tokens provide shared state
NEW: Foundation v2.0+ implements three-layer enforcement to prevent protocol bypass through unregulated tools.
Problem Solved: Previous enforcement only applied to specific tools (Write, Edit, NotebookEdit, WebSearch, Grep, Task), allowing bypass via Read, Bash, Glob, ToolSearch, or MCP tools.
Solution: Advisory user_prompt_submit hook + blocking pre_tool_use verification
The user_prompt_submit hook MUST be non-blocking to avoid deadlock:
- Agent Needs Message First: Agent cannot call informed_reasoning until it receives the user's prompt
- Blocking Creates Deadlock: If hook blocks at user_prompt_submit, agent never receives message → cannot call informed_reasoning → infinite wait
- Text Responses Are Safe: Agent can still provide text responses without tools (low risk)
- Tools Are Blocked: All tools blocked until informed_reasoning called (high risk operations protected)
Trade-off: Agent CAN give uninformed text responses but CANNOT use tools until calling informed_reasoning. This is acceptable because text responses are low-risk compared to tool operations.
Layer 1: user_prompt_submit (ADVISORY - non-blocking)
→ Check for session token (10min TTL)
→ If exists: Allow through (session active)
→ If not: Create requirement token, ALLOW MESSAGE THROUGH
Layer 2: Requirement Satisfaction
→ Agent receives message
→ Agent calls informed_reasoning(phase="analyze")
→ Deletes requirement token
→ Creates session token (10min)
→ Creates informed reasoning token (60s)
Layer 3: Tool Enforcement (BLOCKING)
→ pre_tool_use CHECK 0: Requirement token exists? → BLOCK
→ CHECK 1: Informed reasoning token exists? → Allow/Block
→ CHECK 2: Protocol enforcer token exists? → Allow/Block
→ CHECK 3: Search required token doesn't exist? → Allow/Block
| Token File | Created By | Consumed By | Lifetime | Purpose |
|---|---|---|---|---|
.protocol-informed-reasoning-required |
user_prompt_submit hook | informed_reasoning (analyze) | 2min | Signals requirement for informed_reasoning |
.protocol-informed-reasoning-token |
informed_reasoning (analyze) | PreToolUse hook | 60s | Proves analysis completed |
.protocol-enforcer-token |
authorize_file_operation | PreToolUse hook | 60s | Proves operation authorized |
.protocol-search-required-token |
informed_reasoning (when suggesting search) | search_experiences | 5min | Enforces suggested searches |
.protocol-session-token |
informed_reasoning (with session_id) | user_prompt_submit hook | 10min | Session optimization |
Key Differences:
- Requirement token: Created by hook when no session exists, cleared by informed_reasoning
- Session token: Prevents requirement token creation in multi-turn conversations (10min TTL)
- Recording token (
.protocol-session-recording-token): Separate, only used by automatic recording (not part of enforcement)
CRITICAL: Session tokens are REQUEST-SCOPED, not session-wide. Each new user message requires a new protocol flow.
How it works:
- Each user message = NEW request = NEW protocol flow required
- Token authorizes multi-tool workflows within a SINGLE request
- Example: User says "Add feature X" → protocol → token created → Read/Write/Edit (same request)
- Next user message = NEW request → must run protocol again
Common Misconception:
- ❌ WRONG: "Got 60-min token → can skip protocol for rest of session"
- ✅ CORRECT: "Each user message = new request = run protocol again"
Impact: Token reduces overhead within a single request (multiple tools for one task), but does NOT eliminate protocol for subsequent user requests.
Add to .claude/settings.json:
{
"hooks": {
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "/absolute/path/.cursor/hooks/user-prompt-submit.cjs"
}
]
}
],
"PreToolUse": [
{
"matcher": "Write|Edit|NotebookEdit|WebSearch|Grep",
"hooks": [
{
"type": "command",
"command": "/absolute/path/.cursor/hooks/pre-tool-use.cjs"
}
]
}
],
"PostToolUse": [
{
"matcher": "Write|Edit|NotebookEdit|WebSearch|Grep",
"hooks": [
{
"type": "command",
"command": "/absolute/path/.cursor/hooks/post-tool-use.cjs"
}
]
}
]
}
}CRITICAL:
- Do NOT include Task in PreToolUse matcher (creates circular dependency during analysis)
- Do NOT use
${workspaceFolder}variable (doesn't resolve in Claude Code 2.1.17+) - Use absolute paths only
user-prompt-submit.cjs (NEW - advisory, non-blocking):
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
const os = require('os');
let input = '';
process.stdin.on('data', chunk => input += chunk);
process.stdin.on('end', () => {
try {
const hookData = JSON.parse(input);
const sessionId = hookData.session_id;
// Check for existing session token (10min TTL)
const sessionTokenFile = path.join(os.homedir(), '.protocol-session-token');
let hasValidSession = false;
if (fs.existsSync(sessionTokenFile)) {
try {
const sessionToken = JSON.parse(fs.readFileSync(sessionTokenFile, 'utf8'));
const age = Date.now() - sessionToken.timestamp;
if (age < 600000 && sessionToken.sessionId === sessionId) {
hasValidSession = true;
} else {
fs.unlinkSync(sessionTokenFile);
}
} catch (e) {
if (fs.existsSync(sessionTokenFile)) {
fs.unlinkSync(sessionTokenFile);
}
}
}
// If no valid session, create requirement token
if (!hasValidSession) {
const requirementToken = {
sessionId: sessionId,
timestamp: Date.now(),
reason: 'user_prompt_submit: informed_reasoning required before tool use',
created: new Date().toISOString()
};
const requirementFile = path.join(os.homedir(), '.protocol-informed-reasoning-required');
fs.writeFileSync(requirementFile, JSON.stringify(requirementToken), 'utf8');
}
// ALWAYS allow message through (non-blocking)
const response = {
hookSpecificOutput: {
hookEventName: "UserPromptSubmit",
requirementCreated: !hasValidSession,
sessionTokenExists: hasValidSession,
advisory: "non-blocking - requirement set for pre_tool_use verification"
}
};
console.log(JSON.stringify(response));
process.exit(0);
} catch (e) {
console.log(JSON.stringify({
hookSpecificOutput: { hookEventName: "UserPromptSubmit" },
error: e.message
}));
process.exit(0);
}
});pre-tool-use.cjs (UPDATED - add CHECK 0):
Add CHECK 0 at the beginning of the hook, before existing checks:
// CHECK 0: Verify informed_reasoning requirement was satisfied
const requirementFile = path.join(os.homedir(), '.protocol-informed-reasoning-required');
if (fs.existsSync(requirementFile)) {
try {
const requirement = JSON.parse(fs.readFileSync(requirementFile, 'utf8'));
const age = Date.now() - requirement.timestamp;
if (age < 120000) { // 2 minutes
const response = {
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: `⛔ protocol-enforcer: user_prompt_submit requires informed_reasoning before tool use.
Call informed_reasoning(phase="analyze", problem="<describe task>") to satisfy this requirement.
After calling informed_reasoning, this check will pass and you can proceed with tool operations.`
}
};
console.log(JSON.stringify(response));
process.stderr.write(`\n⛔ protocol-enforcer: informed_reasoning required from user_prompt_submit\n`);
process.exit(0);
} else {
// Stale requirement, remove it
fs.unlinkSync(requirementFile);
}
} catch (e) {
// Invalid requirement file, ignore
}
}
// Continue with existing CHECK 1, CHECK 2, CHECK 3...Problem: Agent blocked at every prompt even after calling informed_reasoning
# Check if session token being created
cat ~/.protocol-session-token
# Expected: {"sessionId":"...","timestamp":..., ...}
# If missing, check informed_reasoning is creating it
# Look for session_id parameter in informed_reasoning call
# Manual test: Create session token
cat > ~/.protocol-session-token << 'EOF'
{"sessionId":"test","timestamp":$(date +%s)000}
EOFProblem: Requirement token persists after informed_reasoning
# Check if informed_reasoning is clearing requirement token
ls -la ~/.protocol-informed-reasoning-required
# If still exists after informed_reasoning call:
# 1. Check informed_reasoning version (needs analyze phase implementation)
# 2. Check session_id matches between requirement and informed_reasoning
# Manual clear:
rm ~/.protocol-informed-reasoning-requiredProblem: Agent can use tools without calling informed_reasoning
# Verify user_prompt_submit hook is configured
cat .claude/settings.json | grep -A 5 "UserPromptSubmit"
# Verify requirement token is being created
# Send a message, then immediately check:
cat ~/.protocol-informed-reasoning-required
# Should exist if no session token
# Verify CHECK 0 is in pre-tool-use.cjs
grep -A 10 "CHECK 0" .cursor/hooks/pre-tool-use.cjsAdd to your project's supervisor rules:
Claude Code: .cursor/rules/protocol-enforcer.mdc
Cursor: .cursorrules
Cline: .clinerules
Continue: .continuerules
## Protocol Enforcer Integration (MANDATORY)
Before ANY file write/edit operation:
1. Complete required protocol steps from `.protocol-enforcer.json`
2. Call `mcp__protocol_enforcer__verify_protocol_compliance` with:
- `hook`: lifecycle point (e.g., "pre_tool_use")
- `protocol_steps_completed`: completed step names
- `checklist_items_checked`: verified items
3. If `compliant: false`, fix violations and retry
4. Call `mcp__protocol_enforcer__authorize_file_operation` with token
5. Only proceed if `authorized: true`
**No exceptions allowed.**See: Appendix B: Complete Supervisor Examples for platform-specific examples.
Cause: npx caches downloaded gists at ~/.npm/_npx/
Solution: Clear cache before reloading: rm -rf ~/.npm/_npx/
When to do this: After every gist update during development/debugging
Cause: tweakcc patches are version-specific to Claude Code binaries
Solution:
- Update
ccVersionmetadata in~/.tweakcc/system-prompts/*.mdfiles to match current Claude Code version - Escape backticks in markdown code:
`tool_name` - Run:
npx tweakcc --apply - Restart Claude Code
How to check: After restart, ask Claude "What protocol steps do you follow before file operations?" - it should describe the 3-step process
Check 1: Verify absolute paths used instead of ${workspaceFolder} (see Step 4)
Check 2: Test hooks manually: .cursor/hooks/pre-tool-use.cjs (should output JSON with deny permission)
Check 3: Check hook files are executable: chmod +x .cursor/hooks/*.cjs
Check 4: Reload Claude Code after configuration changes
Symptom: Completing tasks but no experiences recorded in database
Check 1: Verify informed_reasoning reaches "record" phase
Ask Claude: "What phase of informed_reasoning did you last complete?"
Expected: Should mention "record" phase after task completion
Check 2: Check database file exists and is writable
ls -lh ~/.memory-augmented-reasoning/experiences.dbCheck 3: Verify memory-augmented-reasoning MCP server is loaded
Ask Claude: "Search experiences for anything"
If tool not found: MCP server not loaded, check .mcp.json and reload IDE
Check 4: Verify VALIDATE gate includes "recorded experience"
Ask Claude: "Show my protocol-enforcer configuration"
Look for VALIDATE checklist item mentioning "recorded experience"
How to fix:
- Update to config.foundation.json v2.0.8+ with automatic experience recording
- Ensure informed_reasoning completes all 4 phases (analyze, integrate, reason, record)
- Check Claude Code logs for MCP server errors during record phase
| Issue | Solution |
|---|---|
| Server not appearing | Check config file syntax, gist URL, file location, reload IDE |
| Configuration not loading | Verify .protocol-enforcer.json filename, check JSON syntax |
| Tools not working | Test with get_protocol_config, check tool names (must use full mcp__protocol-enforcer__*) |
| Token errors | Check ~/.protocol-enforcer-token exists after authorize_file_operation |
Claude Code only: Add "protocol-enforcer" to enabledMcpjsonServers if using allowlist.
AI assistants bypass project protocols under pressure or context limits, and fail to accumulate institutional knowledge. This server:
- Enforces consistency - same rules for every task, all platforms
- Builds institutional memory - mandatory experience recording accumulates knowledge across sessions
- Provides traceability - tracks protocol adherence and learning outcomes
- Reduces technical debt - prevents shortcuts violating standards
- Enables continuous improvement - systematic learning from every task completion
- Works with ANY workflow - not tied to specific tools
- Runs from npx - zero installation/maintenance
Protocol without learning is like code review without keeping notes. The combination of protocol-enforcer (enforcement) and memory-augmented-reasoning (learning) creates a system that not only ensures quality processes but also accumulates knowledge over time, enabling pattern recognition and informed decision-making based on past experiences.
See Quick Installation Step 6 for standard setup. This appendix provides detailed information about how system prompt integration works.
Embeds protocol requirements directly into Claude Code's system prompt, instructing Claude to use protocol-enforcer tools before file operations. This provides enforcement through Claude's instructions rather than external hooks.
- Hooks don't work - Platform limitations or technical issues
- Voluntary compliance - You trust Claude to follow instructions
- Defense-in-depth - Use with hooks for maximum reliability (recommended - covered in Step 6)
- Debugging - Easier to troubleshoot than hook execution
tweakcc (https://github.com/Piebald-AI/tweakcc) is a tool that provides an interactive UI to extract Claude Code's system prompts, modify them, and reapply the changes. We add protocol enforcement instructions to the main system prompt.
Comparison:
| Method | Enforcement | Setup Complexity | Reliability | Debugging |
|---|---|---|---|---|
| Hooks | Hard block - cannot bypass | Medium (create scripts) | 100% - External enforcement | Hard (check hook logs) |
| System Prompt | Soft requirement - Claude follows instructions | Low (install tweakcc) | 95% - Depends on Claude | Easy (visible in prompt) |
| Both | Hard block + Instruction | High | Maximum | Medium |
Prerequisites:
- Claude Code CLI installed and working
- Node.js 18+ (for tweakcc)
Step 1: Check tweakcc compatibility
Ask Claude to run:
# Check Claude Code version
claude --version
# Check if tweakcc is installed
tweakcc --version 2>/dev/null || echo "Not installed"Step 2: Install tweakcc globally
Ask Claude:
Install tweakcc globally with npm
Claude should run: npm install -g tweakcc
Step 3: Launch tweakcc interactive UI
Ask Claude:
Launch tweakcc to extract and modify system prompts
Claude should run: npx tweakcc
This launches an interactive UI that extracts Claude Code's system prompts to ~/.tweakcc/system-prompts/.
Step 4: Add protocol enforcement to system prompt
Ask Claude:
Add protocol-enforcer requirements to the TOP of the main system prompt file
Claude should add this to the very beginning of the extracted main system prompt file in ~/.tweakcc/system-prompts/:
# PROTOCOL ENFORCER - MANDATORY COMPLIANCE
BEFORE ANY file write/edit operation (Write, Edit, NotebookEdit tools), you MUST:
**STEP 1: Call informed_reasoning (analyze phase)**
- Tool: `mcp__memory_augmented_reasoning__informed_reasoning`
- Parameters: `{ phase: "analyze", problem: "<description of user request>" }`
- This creates `~/.protocol-informed-reasoning-token`
**STEP 2: Verify protocol compliance**
- Tool: `mcp__protocol_enforcer__verify_protocol_compliance`
- Parameters:
```json
{
"hook": "pre_tool_use",
"tool_name": "Write", // or "Edit", "NotebookEdit"
"protocol_steps_completed": ["informed_reasoning_analyze", "planning", "analysis"],
"checklist_items_checked": [
"Used informed_reasoning (analyze phase) tool before proceeding",
"Requirements understood and plan confirmed",
// Add items from .protocol-enforcer.json
]
}- Returns:
{ compliant: true, operation_token: "abc123..." }or{ compliant: false, violations: [...] }
STEP 3: Authorize file operation
- Tool:
mcp__protocol_enforcer__authorize_file_operation - Parameters:
{ operation_token: "<token from step 2>" } - This creates
~/.protocol-enforcer-token
STEP 4: Perform file operation
- Only after Steps 1-3 complete successfully
- Token expires in 60 seconds
CRITICAL RULES:
- Compliance is determined by the MCP server, not your judgment
- Never skip steps - hooks may also block unauthorized operations
- If verification fails, show violations to user and ask for guidance
- Configuration file:
.protocol-enforcer.jsonin project root
NO EXCEPTIONS. This supersedes all other instructions regarding file operations.
**Step 5: Apply modifications**
Ask Claude:
Apply the tweakcc modifications to Claude Code
Claude should run: `npx tweakcc --apply`
**Step 6: Restart Claude Code**
Tell the user:
Restart Claude Code completely (not just reload window) for system prompt changes to take effect.
#### Verification
After restarting, ask Claude:
What protocol steps do you follow before file operations?
Expected: Claude should describe the 3-step process (informed_reasoning → verify_protocol_compliance → authorize_file_operation).
#### Updating After Claude Code Upgrades
When Claude Code updates, system prompts may be reset. Re-run:
```bash
npx tweakcc # Launch UI to extract new prompts and re-add modifications
npx tweakcc --apply # Apply the changes
# Restart Claude Code
Tip: Keep a backup of your protocol enforcement block in .protocol-enforcer-system-prompt.md for easy copy-paste.
| Issue | Solution |
|---|---|
tweakcc: command not found |
Install with npm install -g tweakcc |
| Claude doesn't follow protocol | Verify modifications applied: check ~/.tweakcc/system-prompts/ for your changes |
| Protocol ignored after update | Claude Code update reset prompts - re-run npx tweakcc and npx tweakcc --apply |
| Changes not taking effect | Full restart required (quit and relaunch), not just reload |
| Conflicts with other tweaks | Check ~/.tweakcc/system-prompts/ for multiple modifications to same file |
Detailed analysis process for AI agents installing this MCP server.
Check which AI coding assistant is active:
- Look for existing MCP config files (
.mcp.json,~/.claude.json,~/.cursor/mcp.json, etc.) - Identify IDE/editor environment
Read ALL rule files (critical - don't skip):
.cursor/rules/**/*.mdc- All rule types.cursorrules,.clinerules,.continuerules- Platform rules.eslintrc.*,.prettierrc.*- Code formattingtsconfig.json- TypeScript config.github/CONTRIBUTING.md,.github/pull_request_template.md- Contribution guidelinesREADME.md,CLAUDE.md,docs/**/*- Project documentation
Extract from each file:
-
Protocol Steps (workflow stages):
- Look for: "first", "before", "then", "after", "finally"
- Example: "Before ANY file operation, do X" → protocol step "X"
- Group related steps (3-7 steps typical)
-
Checklist Items (verification checks):
- Look for: "MUST", "REQUIRED", "MANDATORY", "CRITICAL", "NEVER", "ALWAYS"
- Quality checks: "verify", "ensure", "check", "confirm"
- Each item should be specific and verifiable
-
Behavioral Rules (constraints):
- Hard requirements: "NO EXCEPTIONS", "supersede all instructions"
- Pre-approved actions: "auto-fix allowed", "no permission needed"
- Forbidden actions: "NEVER edit X", "DO NOT use Y"
-
Tool Requirements (MCP tool calls):
- Explicit requirements: "use mcp__X tool"
- Tool sequences: "call X before Y"
-
Conditional Requirements (context-specific):
- "If GraphQL changes, run codegen"
- "If SCSS changes, verify spacing"
- Mark as
required: falsein checklist
Example Extraction:
From .cursor/rules/mandatory-supervisor-protocol.mdc:
"BEFORE ANY OTHER ACTION, EVERY USER QUERY MUST:
1. First use mcp__clear-thought__sequentialthinking tool"
→ Protocol step: { name: "sequential_thinking", hook: "user_prompt_submit" }
→ Checklist: { text: "Sequential thinking completed FIRST", hook: "user_prompt_submit" }
If documentation references external URLs:
- Use WebSearch/WebFetch to retrieve library docs, style guides, API specs
- Extract additional requirements from online sources
- Integrate with local requirements
Based on analysis, determine workflow:
- TDD - Test files exist, tests-first culture
- Design-First - Figma links, design system, token mappings
- Planning & Analysis - Generic best practices
- Behavioral - Focus on LLM behavioral corrections (CHORES framework)
- Minimal - Small projects, emergency mode
Configure based on platform capabilities:
| Platform | Recommended Hooks | Strategy |
|---|---|---|
| Claude Code | All 5 hooks | Maximum enforcement |
| Cursor | PreToolUse + PostToolUse | Standard enforcement |
| Cline | PostToolUse only | Audit logging |
| Zed/Continue | None | Voluntary compliance |
- Present findings: "I've analyzed [N] rule files and detected [workflow type]. Your platform ([platform]) supports [hooks]."
- Show proposed config with extracted steps and checklist items
- Explain trade-offs: With/without hooks, full vs. minimal enforcement
- Get approval before creating files
- Add MCP server to config file
- Create
.protocol-enforcer.jsonwith tailored configuration - Create hook scripts if platform supports them
- Update supervisor protocol files with integration instructions
- Reload IDE
File: .cursor/rules/protocol-enforcer.mdc
---
description: Planning & Analysis Protocol with PreToolUse Hooks
globs:
alwaysApply: true
---
## Protocol Enforcer Integration (MANDATORY)
### Required Steps (from .protocol-enforcer.json):
1. **sequential_thinking** - Complete before responding
2. **planning** - Plan implementation with objectives
3. **analysis** - Analyze codebase for reusable patterns
### Required Checklist:
- Sequential thinking completed FIRST
- Searched for reusable components/utilities
- Matched existing code patterns
- Plan confirmed by user
### Workflow:
**CRITICAL OVERRIDE RULE:**
BEFORE ANY ACTION, call `mcp__clear-thought__sequentialthinking` then `mcp__protocol_enforcer__verify_protocol_compliance`.
NO EXCEPTIONS.
**Process:**
1. **Sequential Thinking** (user_prompt_submit hook)
- Use sequentialthinking tool
- Verify: `mcp__protocol_enforcer__verify_protocol_compliance({ hook: "user_prompt_submit", ... })`
2. **Planning**
- Define objectives, files to modify, dependencies
- Mark `planning` complete
3. **Analysis**
- Search codebase for similar features
- Review `src/components/`, `src/hooks/`, `src/utils/`
- Mark `analysis` complete
4. **Verify Compliance**
```typescript
const v = await mcp__protocol_enforcer__verify_protocol_compliance({
hook: "pre_tool_use",
tool_name: "Write",
protocol_steps_completed: ["planning", "analysis"],
checklist_items_checked: [
"Searched for reusable components/utilities",
"Matched existing code patterns",
"Plan confirmed by user"
]
});-
Authorize
await mcp__protocol_enforcer__authorize_file_operation({ operation_token: v.operation_token });
-
Implement
- Only after authorization
- Minimal changes only
- No scope creep
PreToolUse hooks block unauthorized file operations. Token required per file change (60s expiration).
**Config:** `config.development.json`
---
#### Example 2: Design-First (Cursor)
**File:** `.cursorrules`
- design_review - Review Figma specs
- component_mapping - Map to existing/new components
- Design tokens mapped to SCSS variables
- Figma specs reviewed
- Accessibility requirements checked
- Responsive breakpoints defined
- Open Figma, extract design tokens (colors, spacing, typography)
- Note accessibility (ARIA, keyboard nav)
- Document responsive breakpoints
- Search for similar components
- Decide: reuse, extend, or create
- Map Figma tokens to SCSS variables
mcp__protocol_enforcer__verify_protocol_compliance({
hook: "pre_tool_use",
tool_name: "Write",
protocol_steps_completed: ["design_review", "component_mapping"],
checklist_items_checked: [
"Design tokens mapped to SCSS variables",
"Figma specs reviewed",
"Accessibility requirements checked"
]
})
After verification, authorize then proceed with component implementation.
**Config:** Custom design-focused config with `design_review` and `component_mapping` steps.
---
#### Example 3: Behavioral Corrections (Any Platform)
**File:** `.cursor/rules/behavioral-protocol.mdc`
```markdown
---
description: LLM Behavioral Corrections (MODEL Framework CHORES)
alwaysApply: true
---
## Protocol Enforcer Integration (MANDATORY)
Enforces behavioral corrections from MODEL Framework CHORES analysis.
### Required Steps:
1. **analyze_behavior** - Analyze response for CHORES issues
2. **apply_chores_fixes** - Apply corrections before file operations
### Required Checklist (CHORES):
- **C**onstraint issues addressed (structure/format adherence)
- **H**allucination issues addressed (no false information)
- **O**verconfidence addressed (uncertainty when appropriate)
- **R**easoning issues addressed (logical consistency)
- **E**thical/Safety issues addressed (no harmful content)
- **S**ycophancy addressed (truthfulness over agreement)
### Workflow:
1. **Analyze Behavior** (user_prompt_submit)
- Review response for CHORES issues
- Verify: `mcp__protocol_enforcer__verify_protocol_compliance({ hook: "user_prompt_submit", ... })`
2. **Apply Fixes** (pre_tool_use)
- Address identified CHORES issues
- Verify all checklist items before file ops
- Authorize with token
### Enforcement:
This config uses the default behavioral corrections from `index.js` DEFAULT_CONFIG.
Config: config.behavioral.json
File: .protocol-enforcer.json (minimal)
{
"enforced_rules": {
"require_protocol_steps": [
{ "name": "acknowledge", "hook": "pre_tool_use" }
],
"require_checklist_confirmation": true,
"minimum_checklist_items": 1
},
"checklist_items": [
{ "text": "I acknowledge this change", "hook": "pre_tool_use" }
]
}Use: Emergency fixes, rapid prototyping only.
| Feature | Claude Code | Cursor | Cline | Zed/Continue |
|---|---|---|---|---|
| Hooks Available | All 5 | PreToolUse + PostToolUse | PostToolUse | None |
| Automatic Blocking | ✅ Yes | ✅ Yes | ❌ No | ❌ No |
| Recommended Steps | 5-7 steps | 3-5 steps | 2-3 steps | 1-2 steps |
| Enforcement Level | Maximum | Standard | Audit | Voluntary |
| Best For | Production | Development | Code review | Minimal |
All 5 hook scripts for creating in .cursor/hooks/ or .cline/hooks/.
Blocks unauthorized file operations without valid tokens. Two-token verification system ensures both informed_reasoning and protocol compliance.
Updated for Claude Code v2.1.7+ (Jan 2026):
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
const os = require('os');
let input = '';
process.stdin.on('data', chunk => input += chunk);
process.stdin.on('end', () => {
const homeDir = os.homedir();
// CHECK 1: Look for valid session files (Phase 1 fix - check correct files)
const sessionFiles = fs.readdirSync(homeDir).filter(f =>
f.startsWith('.protocol-session-') && f.endsWith('.json')
);
if (sessionFiles.length === 0) {
const response = {
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: "⛔ protocol-enforcer: must call informed_reasoning (analyze phase) to create session"
}
};
console.log(JSON.stringify(response));
process.stderr.write('\n⛔ protocol-enforcer: no valid session found\n');
process.exit(0);
}
// CHECK 2: Find a valid, non-expired session
let validSession = null;
for (const sessionFile of sessionFiles) {
try {
const sessionPath = path.join(homeDir, sessionFile);
const session = JSON.parse(fs.readFileSync(sessionPath, 'utf8'));
// Check if session is expired
if (Date.now() < session.expires_at) {
validSession = session;
break;
} else {
// Clean up expired session
fs.unlinkSync(sessionPath);
}
} catch (e) {
// Invalid session file, skip it
continue;
}
}
if (!validSession) {
const response = {
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: "⛔ protocol-enforcer: all sessions expired - call informed_reasoning to create new session"
}
};
console.log(JSON.stringify(response));
process.stderr.write('\n⛔ protocol-enforcer: all sessions expired\n');
process.exit(0);
}
// CHECK 3: Verify all phases complete (Phase 3 enhancement)
if (!validSession.allPhasesComplete) {
const phases = validSession.phases || {};
const incomplete = [];
if (!phases.analyze?.completed) incomplete.push('analyze');
if (!phases.integrate?.completed) incomplete.push('integrate');
if (!phases.reason?.completed) incomplete.push('reason');
if (!phases.record?.completed) incomplete.push('record');
const missingPhases = incomplete.length > 0
? incomplete.join(', ')
: 'unknown phases';
const response = {
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: `⛔ protocol-enforcer: incomplete phases - must complete: ${missingPhases}`
}
};
console.log(JSON.stringify(response));
process.stderr.write(`\n⛔ protocol-enforcer: incomplete phases: ${missingPhases}\n`);
process.stderr.write('Complete all 4 phases: analyze → integrate → reason → record\n');
process.exit(0);
}
// CHECK 4: Verify artifact quality (Phase 4 enhancement)
if (validSession.artifactsVerified === false) {
const issues = validSession.artifactIssues || ['Unknown artifact quality issues'];
const issueList = issues.slice(0, 3).join('; '); // Show first 3 issues
const response = {
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: `⛔ protocol-enforcer: insufficient artifact quality - ${issueList}`
}
};
console.log(JSON.stringify(response));
process.stderr.write(`\n⛔ protocol-enforcer: insufficient artifact quality\n`);
process.stderr.write(`Issues: ${issues.join(', ')}\n`);
process.stderr.write('Ensure all phases produce quality artifacts (min lengths, non-empty content)\n');
process.exit(0);
}
// Valid session with all phases complete and quality artifacts - allow operation
const response = {
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "allow",
permissionDecisionReason: `✅ protocol-enforcer: all phases complete (${validSession.session_id.substring(0, 8)}...)`
}
};
console.log(JSON.stringify(response));
process.stderr.write(`✅ protocol-enforcer: operation authorized (session: ${validSession.session_id.substring(0, 8)}...)\n`);
process.exit(0);
});Triggers automatic experience recording after successful tool execution (integrates with memory-augmented-reasoning v2.0.1+).
Download from gist:
curl -o .cursor/hooks/post-tool-use.cjs \
https://gist.githubusercontent.com/mpalpha/c2f1723868c86343e590ed38e80f264d/raw/post-tool-use.cjs
chmod +x .cursor/hooks/post-tool-use.cjsOr create manually:
#!/usr/bin/env node
/**
* Post-Tool-Use Hook for Protocol Enforcer
* Triggers automatic experience recording after successful tool execution
*
* Usage: Configure in .claude/settings.json:
* {
* "hooks": {
* "PostToolUse": [{
* "matcher": "Write|Edit|NotebookEdit|WebSearch|Grep",
* "hooks": [{
* "type": "command",
* "command": "/absolute/path/to/.cursor/hooks/post-tool-use.cjs"
* }]
* }]
* }
* }
*/
const fs = require('fs');
const path = require('path');
const os = require('os');
const { spawn } = require('child_process');
let input = '';
process.stdin.on('data', chunk => input += chunk);
process.stdin.on('end', () => {
try {
const hookData = JSON.parse(input);
const toolName = hookData.name || hookData.toolName || 'unknown';
const toolResult = hookData.result;
// Check if operation succeeded
const operationSucceeded = !toolResult || !toolResult.error;
// Check if this is a tool that should trigger recording
const recordableTools = ['Write', 'Edit', 'NotebookEdit', 'Task', 'WebSearch', 'Grep'];
const shouldRecord = recordableTools.includes(toolName) && operationSucceeded;
if (shouldRecord) {
// Read protocol-enforcer config to check if recording is enabled
const configPath = path.join(process.cwd(), '.protocol-enforcer.json');
let recordingEnabled = true; // Default to enabled
try {
if (fs.existsSync(configPath)) {
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
recordingEnabled = config.automatic_experience_recording?.enabled !== false;
}
} catch (e) {
// If config read fails, default to enabled
}
if (recordingEnabled) {
// Trigger experience recording in background
// Use npx to call memory-augmented-reasoning with a special flag
const record = spawn('npx', [
'-y',
'https://gist.github.com/mpalpha/32e02b911a6dae5751053ad158fc0e86',
'--record-last-session'
], {
stdio: 'ignore',
detached: true,
env: { ...process.env, FORCE_COLOR: '0' }
});
record.unref(); // Don't block on recording
}
}
// Log to audit file for debugging
const logFile = path.join(os.homedir(), '.protocol-enforcer-audit.log');
const logEntry = {
timestamp: new Date().toISOString(),
tool: toolName,
success: operationSucceeded,
recordingTriggered: shouldRecord
};
try {
fs.appendFileSync(logFile, JSON.stringify(logEntry) + '\n', 'utf8');
} catch (e) {
// Silent fail on logging errors
}
// Return success
const response = {
hookSpecificOutput: {
hookEventName: "PostToolUse",
experienceRecordingTriggered: shouldRecord
}
};
console.log(JSON.stringify(response));
process.exit(0);
} catch (e) {
console.log(JSON.stringify({ error: e.message }));
process.exit(0);
}
});Enforces CRITICAL OVERRIDE RULES, blocks bypass attempts.
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
let input = '';
process.stdin.on('data', chunk => input += chunk);
process.stdin.on('end', () => {
try {
const hookData = JSON.parse(input);
const userPrompt = hookData.userPrompt || '';
// Detect bypass attempts
const bypassPatterns = [
/ignore.*protocol/i,
/skip.*verification/i,
/bypass.*enforcer/i,
/disable.*mcp/i
];
for (const pattern of bypassPatterns) {
if (pattern.test(userPrompt)) {
process.stderr.write('⛔ BYPASS ATTEMPT DETECTED: Protocol enforcement cannot be disabled.\n');
process.exit(2); // Block
}
}
// Inject protocol reminder for file operations
if (/write|edit|create|modify/i.test(userPrompt)) {
const reminder = '\n\n[PROTOCOL REMINDER: Before file operations, call mcp__protocol-enforcer__verify_protocol_compliance and mcp__protocol-enforcer__authorize_file_operation]';
console.log(JSON.stringify({
userPrompt: userPrompt + reminder
}));
} else {
console.log(input); // Pass through unchanged
}
process.exit(0);
} catch (e) {
console.log(input); // Pass through on error
process.exit(0);
}
});Initializes compliance tracking, displays protocol requirements.
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
let input = '';
process.stdin.on('data', chunk => input += chunk);
process.stdin.on('end', () => {
try {
// Load .protocol-enforcer.json
const cwd = process.cwd();
const configPath = path.join(cwd, '.protocol-enforcer.json');
if (fs.existsSync(configPath)) {
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
console.error('\n📋 Protocol Enforcer Active\n');
console.error('Required Protocol Steps:');
config.enforced_rules.require_protocol_steps.forEach(step => {
console.error(` - ${step.name} (hook: ${step.hook})`);
});
console.error(`\nMinimum Checklist Items: ${config.enforced_rules.minimum_checklist_items}\n`);
}
process.exit(0);
} catch (e) {
process.exit(0); // Silent fail
}
});Generates compliance report at end of response.
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
const os = require('os');
let input = '';
process.stdin.on('data', chunk => input += chunk);
process.stdin.on('end', () => {
try {
// Check for unused tokens
const tokenFile = path.join(os.homedir(), '.protocol-enforcer-token');
if (fs.existsSync(tokenFile)) {
console.error('\n⚠️ Unused authorization token detected - was file operation skipped?\n');
fs.unlinkSync(tokenFile); // Cleanup
}
// Read audit log for session summary
const logFile = path.join(os.homedir(), '.protocol-enforcer-audit.log');
if (fs.existsSync(logFile)) {
const logs = fs.readFileSync(logFile, 'utf8').trim().split('\n');
const recentLogs = logs.slice(-10); // Last 10 operations
console.error('\n📊 Session Compliance Summary:');
console.error(`Total operations logged: ${recentLogs.length}`);
}
process.exit(0);
} catch (e) {
process.exit(0); // Silent fail
}
});Enhanced hooks for preserving session state across context resets and compactions.
These hooks work together to create a robust context persistence system with validation, cleanup, and error logging.
Auto-saves session state before context compaction with validation and error logging.
#!/usr/bin/env node
/**
* PreCompact Hook - Auto-save handoff before context compaction
* Preserves session state in JSON format (no dependencies required)
*/
const fs = require('fs');
const path = require('path');
// Validation function
function validateHandoff(handoff) {
const errors = [];
// Check required fields
if (!handoff.date) errors.push('Missing date');
if (!handoff.session_id) errors.push('Missing session_id');
if (!handoff.status) errors.push('Missing status');
// Check tasks structure
if (!handoff.tasks || typeof handoff.tasks !== 'object') {
errors.push('Missing or invalid tasks object');
} else {
if (!Array.isArray(handoff.tasks.completed)) errors.push('tasks.completed must be array');
if (!Array.isArray(handoff.tasks.in_progress)) errors.push('tasks.in_progress must be array');
if (!Array.isArray(handoff.tasks.pending)) errors.push('tasks.pending must be array');
if (!Array.isArray(handoff.tasks.blockers)) errors.push('tasks.blockers must be array');
}
// Check decisions and next_steps
if (!Array.isArray(handoff.decisions)) errors.push('decisions must be array');
if (!Array.isArray(handoff.next_steps)) errors.push('next_steps must be array');
return { valid: errors.length === 0, errors };
}
// Error logging function
function logError(handoffDir, hookName, error) {
try {
const logPath = path.join(handoffDir, 'errors.log');
const timestamp = new Date().toISOString();
const logEntry = `[${timestamp}] [${hookName}] ${error}\n`;
fs.appendFileSync(logPath, logEntry, 'utf8');
} catch (e) {
// Silent fail on logging error
}
}
let input = '';
process.stdin.on('data', chunk => input += chunk);
process.stdin.on('end', () => {
try {
const hookData = JSON.parse(input);
const sessionId = hookData.sessionId || 'unknown';
const trigger = hookData.trigger || 'unknown';
const cwd = hookData.cwd || process.cwd();
// Create handoff directory
const handoffDir = path.join(cwd, '.claude', 'handoffs');
if (!fs.existsSync(handoffDir)) {
fs.mkdirSync(handoffDir, { recursive: true });
}
// Create handoff document
const handoff = {
date: new Date().toISOString(),
session_id: sessionId,
trigger: trigger,
summary: "Session state preserved before compaction",
status: "in_progress",
context: {
cwd: cwd,
compaction_type: trigger
},
tasks: {
completed: [],
in_progress: [],
pending: [],
blockers: []
},
decisions: [],
next_steps: []
};
// Validate handoff
const validation = validateHandoff(handoff);
if (!validation.valid) {
logError(handoffDir, 'PreCompact', `Validation failed: ${validation.errors.join(', ')}`);
console.error(`\n⚠️ PreCompact: Handoff validation failed (${validation.errors.length} errors) - saving anyway`);
}
// Save handoff
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const filename = `${sessionId}_${timestamp}.json`;
const handoffPath = path.join(handoffDir, filename);
fs.writeFileSync(handoffPath, JSON.stringify(handoff, null, 2), 'utf8');
// Update latest.json reference
const latestPath = path.join(handoffDir, 'latest.json');
fs.writeFileSync(latestPath, JSON.stringify({
latest_handoff: filename,
created: handoff.date,
session_id: sessionId
}, null, 2), 'utf8');
// Write to stderr for visibility
console.error(`\n📋 PreCompact: Handoff saved to ${filename}`);
console.error(` Trigger: ${trigger}`);
if (!validation.valid) {
console.error(` Validation: ${validation.errors.length} issues detected`);
}
process.exit(0);
} catch (e) {
const cwd = process.cwd();
const handoffDir = path.join(cwd, '.claude', 'handoffs');
logError(handoffDir, 'PreCompact', `Exception: ${e.message}`);
console.error(`\n⚠️ PreCompact hook error: ${e.message}`);
process.exit(0);
}
});Creates final handoff with cleanup and validation when session ends.
#!/usr/bin/env node
/**
* SessionEnd Hook - Create final handoff and cleanup
* Captures session outcome and prepares for next session
*/
const fs = require('fs');
const path = require('path');
// Validation function
function validateHandoff(handoff) {
const errors = [];
// Check required fields
if (!handoff.date) errors.push('Missing date');
if (!handoff.session_id) errors.push('Missing session_id');
if (!handoff.status) errors.push('Missing status');
// Check tasks structure
if (!handoff.tasks || typeof handoff.tasks !== 'object') {
errors.push('Missing or invalid tasks object');
} else {
if (!Array.isArray(handoff.tasks.completed)) errors.push('tasks.completed must be array');
if (!Array.isArray(handoff.tasks.in_progress)) errors.push('tasks.in_progress must be array');
if (!Array.isArray(handoff.tasks.pending)) errors.push('tasks.pending must be array');
if (!Array.isArray(handoff.tasks.blockers)) errors.push('tasks.blockers must be array');
}
// Check decisions and next_steps
if (!Array.isArray(handoff.decisions)) errors.push('decisions must be array');
if (!Array.isArray(handoff.next_steps)) errors.push('next_steps must be array');
return { valid: errors.length === 0, errors };
}
// Cleanup function - keep last 10 handoff files
function cleanupOldHandoffs(handoffDir) {
try {
const files = fs.readdirSync(handoffDir)
.filter(f => f.endsWith('.json') && f !== 'latest.json')
.sort()
.reverse();
if (files.length > 10) {
const toDelete = files.slice(10);
toDelete.forEach(f => {
fs.unlinkSync(path.join(handoffDir, f));
});
return toDelete.length;
}
return 0;
} catch (e) {
return 0;
}
}
// Error logging function
function logError(handoffDir, hookName, error) {
try {
const logPath = path.join(handoffDir, 'errors.log');
const timestamp = new Date().toISOString();
const logEntry = `[${timestamp}] [${hookName}] ${error}\n`;
fs.appendFileSync(logPath, logEntry, 'utf8');
} catch (e) {
// Silent fail on logging error
}
}
let input = '';
process.stdin.on('data', chunk => input += chunk);
process.stdin.on('end', () => {
try {
const hookData = JSON.parse(input);
const sessionId = hookData.sessionId || 'unknown';
const reason = hookData.reason || 'unknown';
const cwd = hookData.cwd || process.cwd();
// Create handoff directory
const handoffDir = path.join(cwd, '.claude', 'handoffs');
if (!fs.existsSync(handoffDir)) {
fs.mkdirSync(handoffDir, { recursive: true });
}
// Read latest handoff if exists
const latestPath = path.join(handoffDir, 'latest.json');
let existingHandoff = {
tasks: { completed: [], in_progress: [], pending: [], blockers: [] },
decisions: [],
next_steps: []
};
if (fs.existsSync(latestPath)) {
try {
const latestInfo = JSON.parse(fs.readFileSync(latestPath, 'utf8'));
if (latestInfo.latest_handoff) {
const existingPath = path.join(handoffDir, latestInfo.latest_handoff);
if (fs.existsSync(existingPath)) {
existingHandoff = JSON.parse(fs.readFileSync(existingPath, 'utf8'));
}
}
} catch (e) {
logError(handoffDir, 'SessionEnd', `Error reading existing handoff: ${e.message}`);
}
}
// Create final handoff
const handoff = {
date: new Date().toISOString(),
session_id: sessionId,
session_end_reason: reason,
status: "completed",
context: {
cwd: cwd,
ended_at: new Date().toISOString()
},
tasks: existingHandoff.tasks,
decisions: existingHandoff.decisions,
next_steps: existingHandoff.next_steps,
notes: "Session ended. Review tasks and update handoff manually if needed."
};
// Validate handoff
const validation = validateHandoff(handoff);
if (!validation.valid) {
logError(handoffDir, 'SessionEnd', `Validation failed: ${validation.errors.join(', ')}`);
console.error(`\n⚠️ SessionEnd: Handoff validation failed (${validation.errors.length} errors) - saving anyway`);
}
// Save final handoff
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const filename = `${sessionId}_final_${timestamp}.json`;
const handoffPath = path.join(handoffDir, filename);
fs.writeFileSync(handoffPath, JSON.stringify(handoff, null, 2), 'utf8');
// Update latest reference
fs.writeFileSync(latestPath, JSON.stringify({
latest_handoff: filename,
created: handoff.date,
session_id: sessionId,
final: true
}, null, 2), 'utf8');
// Cleanup old handoffs
const deletedCount = cleanupOldHandoffs(handoffDir);
if (deletedCount > 0) {
console.error(` Cleanup: Deleted ${deletedCount} old handoff file(s)`);
}
// Write to stderr for visibility
console.error(`\n📋 SessionEnd: Final handoff saved to ${filename}`);
console.error(` Reason: ${reason}`);
if (!validation.valid) {
console.error(` Validation: ${validation.errors.length} issues detected`);
}
console.error(` Review: .claude/handoffs/${filename}`);
process.exit(0);
} catch (e) {
const cwd = process.cwd();
const handoffDir = path.join(cwd, '.claude', 'handoffs');
logError(handoffDir, 'SessionEnd', `Exception: ${e.message}`);
console.error(`\n⚠️ SessionEnd hook error: ${e.message}`);
process.exit(0);
}
});Loads previous handoff with validation and graceful degradation.
#!/usr/bin/env node
/**
* SessionStart Hook - Load previous handoff into context
* Provides continuity by injecting previous session state
*/
const fs = require('fs');
const path = require('path');
// Validation function
function validateHandoff(handoff) {
const errors = [];
// Check required fields
if (!handoff.date) errors.push('Missing date');
if (!handoff.session_id) errors.push('Missing session_id');
if (!handoff.status) errors.push('Missing status');
// Check tasks structure
if (!handoff.tasks || typeof handoff.tasks !== 'object') {
errors.push('Missing or invalid tasks object');
} else {
if (!Array.isArray(handoff.tasks.completed)) errors.push('tasks.completed must be array');
if (!Array.isArray(handoff.tasks.in_progress)) errors.push('tasks.in_progress must be array');
if (!Array.isArray(handoff.tasks.pending)) errors.push('tasks.pending must be array');
if (!Array.isArray(handoff.tasks.blockers)) errors.push('tasks.blockers must be array');
}
// Check decisions and next_steps
if (!Array.isArray(handoff.decisions)) errors.push('decisions must be array');
if (!Array.isArray(handoff.next_steps)) errors.push('next_steps must be array');
return { valid: errors.length === 0, errors };
}
// Error logging function
function logError(handoffDir, hookName, error) {
try {
const logPath = path.join(handoffDir, 'errors.log');
const timestamp = new Date().toISOString();
const logEntry = `[${timestamp}] [${hookName}] ${error}\n`;
fs.appendFileSync(logPath, logEntry, 'utf8');
} catch (e) {
// Silent fail on logging error
}
}
let input = '';
process.stdin.on('data', chunk => input += chunk);
process.stdin.on('end', () => {
try {
const hookData = JSON.parse(input);
const cwd = hookData.cwd || process.cwd();
// Look for latest handoff
const handoffDir = path.join(cwd, '.claude', 'handoffs');
const latestPath = path.join(handoffDir, 'latest.json');
if (!fs.existsSync(latestPath)) {
console.log(JSON.stringify({
userPrompt: "\n\n[NO PREVIOUS SESSION - Starting fresh]"
}));
process.exit(0);
return;
}
// Read latest handoff reference
const latestInfo = JSON.parse(fs.readFileSync(latestPath, 'utf8'));
if (!latestInfo.latest_handoff) {
console.log(JSON.stringify({
userPrompt: "\n\n[NO PREVIOUS SESSION - Starting fresh]"
}));
process.exit(0);
return;
}
// Read handoff document
const handoffPath = path.join(handoffDir, latestInfo.latest_handoff);
if (!fs.existsSync(handoffPath)) {
logError(handoffDir, 'SessionStart', `Handoff file not found: ${handoffPath}`);
console.log(JSON.stringify({
userPrompt: "\n\n[PREVIOUS HANDOFF NOT FOUND - Starting fresh]"
}));
process.exit(0);
return;
}
const handoff = JSON.parse(fs.readFileSync(handoffPath, 'utf8'));
// Validate handoff
const validation = validateHandoff(handoff);
if (!validation.valid) {
logError(handoffDir, 'SessionStart', `Validation failed: ${validation.errors.join(', ')}`);
console.error(`\n⚠️ SessionStart: Handoff validation failed (${validation.errors.length} errors) - loading anyway`);
}
// Format handoff for injection
let contextInjection = `
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📋 PREVIOUS SESSION HANDOFF
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
**Session ID:** ${handoff.session_id}
**Date:** ${handoff.date}
**Status:** ${handoff.status}
${handoff.session_end_reason ? `**End Reason:** ${handoff.session_end_reason}` : ''}
## Tasks
### Completed
${handoff.tasks.completed.length > 0 ? handoff.tasks.completed.map(t => `- [x] ${t}`).join('\n') : '- None'}
### In Progress
${handoff.tasks.in_progress.length > 0 ? handoff.tasks.in_progress.map(t => `- [ ] ${t}`).join('\n') : '- None'}
### Pending
${handoff.tasks.pending.length > 0 ? handoff.tasks.pending.map(t => `- [ ] ${t}`).join('\n') : '- None'}
### Blockers
${handoff.tasks.blockers.length > 0 ? handoff.tasks.blockers.map(b => `- ⚠️ ${b}`).join('\n') : '- None'}
## Key Decisions
${handoff.decisions.length > 0 ? handoff.decisions.map(d => `- ${d}`).join('\n') : '- None documented'}
## Next Steps
${handoff.next_steps.length > 0 ? handoff.next_steps.map((s, i) => `${i + 1}. ${s}`).join('\n') : '- Not specified'}
${handoff.notes ? `\n## Notes\n${handoff.notes}` : ''}
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
**CONTINUITY MODE ACTIVE** - Context preserved from previous session.
To update this handoff, modify: .claude/handoffs/${latestInfo.latest_handoff}
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
`;
// Add validation warning if needed
if (!validation.valid) {
contextInjection += `\n\n⚠️ **Warning:** Handoff validation detected ${validation.errors.length} issue(s):\n${validation.errors.map(e => `- ${e}`).join('\n')}\n`;
}
// Output to stdout
console.log(JSON.stringify({
userPrompt: contextInjection
}));
// Write to stderr for visibility
console.error(`\n📋 SessionStart: Loaded handoff from ${latestInfo.latest_handoff}`);
if (!validation.valid) {
console.error(` Validation: ${validation.errors.length} issues detected`);
}
process.exit(0);
} catch (e) {
const cwd = process.cwd();
const handoffDir = path.join(cwd, '.claude', 'handoffs');
logError(handoffDir, 'SessionStart', `Exception: ${e.message}`);
console.error(`\n⚠️ SessionStart hook error: ${e.message}`);
console.log(JSON.stringify({
userPrompt: "\n\n[ERROR LOADING PREVIOUS SESSION - Starting fresh]"
}));
process.exit(0);
}
});These context persistence hooks:
- Work independently of protocol enforcement
- Can be used with or without protocol-enforcer MCP server
- Provide robust error handling and validation
- Maintain non-blocking behavior (never prevent compaction/session operations)
- Create structured JSON handoffs in
.claude/handoffs/ - Automatically cleanup old handoffs (keep last 10)
- Log errors to
.claude/handoffs/errors.logfor debugging
Known Limitation: Hooks cannot access conversation history or internal state. Handoff files will have empty arrays for tasks/decisions/next_steps. Workaround: manually edit handoff files before session ends, or use project documentation (CLAUDE.md) for manual state tracking.
Installation:
- Create
.cursor/hooks/directory - Save these scripts with
.cjsextension - Make executable:
chmod +x .cursor/hooks/*.cjs - Configure in
.claude/settings.json(see Appendix C for format)
MIT License - Copyright (c) 2025 Jason Lusk