Skip to content

Instantly share code, notes, and snippets.

@mpalpha
Last active January 29, 2026 10:03
Show Gist options
  • Select an option

  • Save mpalpha/c2f1723868c86343e590ed38e80f264d to your computer and use it in GitHub Desktop.

Select an option

Save mpalpha/c2f1723868c86343e590ed38e80f264d to your computer and use it in GitHub Desktop.
Protocol-Enforcer v3.0.3 - Hook Configuration Documentation Update

Protocol Enforcer MCP Server

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

What This Does

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

What's New

v3.0.0 (Three-Gate Enforcement & Installation System) - January 2026

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_experiences called (checks .protocol-search-completed-{sessionId})
  • REASON Gate: Blocks file operations until informed_reasoning called (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-id maintains 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.json tracks 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+

AI-Agent Installation Guide

This guide is designed for AI agents to install and configure the MCP server with hooks automatically.

Prerequisites Check

Agent must verify:

  1. Node.js: Run node --version (require v14+)
  2. npm: Run npm --version
  3. Workspace Path: Run pwd to get absolute project path
  4. Shell: Run echo $SHELL to verify bash/zsh

Step 1: Environment Analysis

# 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

Step 1.5: Hook Adaptation for Claude Code

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:

  1. Input Method: Replace process.argv[2] with stdin event handlers
  2. Async Pattern: Wrap logic in process.stdin.on('end') callback
  3. Error Handling: Catch JSON parse errors from stdin
  4. 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)

Step 2: Configuration Strategy

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 entry

Step 3: Idempotent Configuration

Agent 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));

Step 4: Hook Installation with Absolute Paths

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/hooks

Write Hook Files:

Agent must create 3 hook files (get content from Appendix C below):

  1. .cursor/hooks/user-prompt-submit.cjs - Advisory reminder
  2. .cursor/hooks/pre-tool-use.cjs - Blocking enforcement (CRITICAL)
  3. .cursor/hooks/post-tool-use.cjs - Automatic experience recording

Make Executable:

chmod +x .cursor/hooks/*.cjs

Configure 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"
      }]
    }]
  }
}
EOF

CRITICAL: Hook Configuration Requirements

BOTH UserPromptSubmit hooks are REQUIRED - not optional:

  1. user-prompt-submit.cjs - Creates enforcement tokens (backend state tracking)
  2. 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 stdout

Test 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)

Step 5: Verification Protocol

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 configuration

Test 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 operation

Test 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"

Step 6: Troubleshooting Decision Tree

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

Step 7: Installation Complete

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

Agent Instructions Summary

For AI agents executing this installation:

  1. Analyze Environment:

    • Run: pwd, uname -s, node --version, npm --version
    • Check: ~/.claude.json, .mcp.json, .cursor/hooks/
  2. Configure MCP Server:

    • Read ~/.claude.json
    • Add/update projects.{absolute_path}.mcpServers
    • Use npx remote execution URL
  3. Install Hooks:

    • Create .cursor/hooks/ directory
    • Write 3 hook files from Appendix C
    • Make executable with chmod +x
    • Configure in .claude/settings.local.json with absolute paths
  4. Verify Installation:

    • Test 1: MCP connection
    • Test 2: Hook blocking
    • Test 3: Token creation
  5. Troubleshoot Failures:

    • Use decision tree above
    • Check logs and file permissions
    • Prefer absolute paths over variables
  6. Inform User:

    • Summarize what was installed
    • Report test results
    • Provide next steps

Platform Support

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 ⚠️ Limited (PostToolUse) ❌ Not available Audit only
Zed ~/.config/zed/mcp.json ❌ None ❌ Not available Voluntary
Continue ~/.continue/mcp.json ⚠️ Limited ❌ Not available Voluntary

Available Hooks: user_prompt_submit, session_start, pre_tool_use, post_tool_use, stop

⚠️ CRITICAL: Installing the MCP server alone provides tools but does not enforce protocol compliance. You must configure enforcement via:

  • 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.


Quick Installation (Users)

⚠️ IMPORTANT: The MCP server provides protocol tools but doesn't automatically enforce anything. You must configure enforcement:

  • 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):

⚠️ IMPORTANT: Do not skip Step 7 (Verify Installation). This catches common issues like hook path problems, MCP server loading failures, and token creation bugs that cause silent enforcement failures.

1. Add MCP Server

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".

⚠️ Known Issue (Claude Code 2.1.17): The .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).

2. Create Configuration

Download the Foundation config template (recommended):

curl -o .protocol-enforcer.json \
  https://gist.githubusercontent.com/mpalpha/c2f1723868c86343e590ed38e80f264d/raw/config.foundation.json

Or 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.

3. Install Enforcement Hooks (Required for Automatic Blocking)

Create hooks directory:

mkdir -p .cursor/hooks

Create hook files from Appendix C:

You need to create 3 hook scripts. See Appendix C: Hook Scripts Reference for complete code.

Required hooks:

  1. .cursor/hooks/pre-tool-use.cjs - Blocks file and research operations without valid tokens (CRITICAL FOR ENFORCEMENT)
  2. .cursor/hooks/post-tool-use.cjs - Triggers automatic experience recording after successful tool execution (integrates with memory-augmented-reasoning v2.0.1+)
  3. .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/*.cjs

What 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.

4. Configure Hooks in Claude Code

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"
      }]
    }]
  }
}

⚠️ CRITICAL (Claude Code 2.1.17+): The ${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"

5. Reload IDE

  • Claude Code/Cursor/VSCode: Cmd+Shift+P → "Developer: Reload Window"
  • Zed: Restart Zed

6. Setup System Prompt (Claude Code Only - Recommended)

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.

7. Verify Installation

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)

⚠️ If the test file is created without calling protocol tools first, hooks are NOT working. Common causes:

  • ${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_experiences with 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.db exists and is not empty

⚠️ If no experiences are found after completing tasks, experience recording is NOT working. Common causes:

  • 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.json syntax, reload IDE
  • If hooks don't block: Verify hook paths are absolute, check chmod +x on hook files
  • If token errors occur: Check ~/.protocol-enforcer-token and ~/.protocol-informed-reasoning-token files 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?"

Installation for AI Agents

When a user requests installation:

  1. Detect platform - Check which AI assistant and MCP config location
  2. Analyze project - Read ALL rule files (.cursor/rules/**/*, .cursorrules, .github/**/*, docs/**/*, etc.)
  3. Extract requirements - Identify protocol steps, checklist items, and behavioral rules
  4. Determine hook support - Configure based on platform capabilities (see table above)
  5. Propose configuration - Present tailored config matching project workflow
  6. Get approval - Confirm before creating files

Detailed guide: See Appendix A: AI Agent Installation Guide


Configuration Reference

Required Format

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
}

Available Hooks

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

Example Configurations

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)

MCP Tools

1. verify_protocol_compliance

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: [...] }

2. authorize_file_operation

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.

3. get_protocol_config

Get current configuration.

Returns: { config_path: "...", config: {...} }

4. get_compliance_status

Get compliance statistics and recent violations.

Returns: { total_checks: N, passed: N, failed: N, recent_violations: [...] }

5. initialize_protocol_config

Create new config file.

Parameters: scope: "project" | "user"


Usage Workflow

Complete Example (All Hooks with informed_reasoning)

// 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"]
});

Hook Filtering

  • Only rules with matching hook value are checked
  • If applies_to specified, tool name must match
  • Enables context-specific enforcement at different lifecycle points

Hook-Based Enforcement

For automatic blocking of unauthorized file operations (Claude Code, Cursor only).

Installation

  1. Create hooks directory:
mkdir -p .cursor/hooks
  1. Create hook scripts from templates (see Appendix C)

  2. Make executable:

chmod +x .cursor/hooks/*.cjs
  1. 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.

Token Lifecycle (Two-Token Verification)

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

Advisory Hook Pattern (Three-Layer Enforcement)

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

Why Advisory (Non-Blocking)?

The user_prompt_submit hook MUST be non-blocking to avoid deadlock:

  1. Agent Needs Message First: Agent cannot call informed_reasoning until it receives the user's prompt
  2. Blocking Creates Deadlock: If hook blocks at user_prompt_submit, agent never receives message → cannot call informed_reasoning → infinite wait
  3. Text Responses Are Safe: Agent can still provide text responses without tools (low risk)
  4. 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.

Three-Layer Flow

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

Five Token Architecture

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)

REQUEST-SCOPED Session Tokens

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.

Hook Configuration

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

Hook Scripts

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...

Troubleshooting Advisory Pattern

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}
EOF

Problem: 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-required

Problem: 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.cjs

Integration with Supervisor Protocols

Add 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.


Troubleshooting

MCP server not loading after gist updates

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

System prompt modifications not active after Claude Code update

Cause: tweakcc patches are version-specific to Claude Code binaries

Solution:

  1. Update ccVersion metadata in ~/.tweakcc/system-prompts/*.md files to match current Claude Code version
  2. Escape backticks in markdown code: `tool_name`
  3. Run: npx tweakcc --apply
  4. 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

Hooks configured but not blocking operations

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

Experiences not being saved

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.db

Check 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:

  1. Update to config.foundation.json v2.0.8+ with automatic experience recording
  2. Ensure informed_reasoning completes all 4 phases (analyze, integrate, reason, record)
  3. Check Claude Code logs for MCP server errors during record phase

Other Common Issues

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.


Why This Exists

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.


Appendices

Appendix A: System Prompt Integration (Detailed)

See Quick Installation Step 6 for standard setup. This appendix provides detailed information about how system prompt integration works.

What This Does

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.

When to Use

  • 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

How It Works

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

Detailed Setup Instructions

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.json in 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.

Troubleshooting

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

Appendix B: AI Agent Installation Guide

Detailed analysis process for AI agents installing this MCP server.

Step 1: Detect Platform and IDE

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

Step 2: Analyze Project Structure

Read ALL rule files (critical - don't skip):

  • .cursor/rules/**/*.mdc - All rule types
  • .cursorrules, .clinerules, .continuerules - Platform rules
  • .eslintrc.*, .prettierrc.* - Code formatting
  • tsconfig.json - TypeScript config
  • .github/CONTRIBUTING.md, .github/pull_request_template.md - Contribution guidelines
  • README.md, CLAUDE.md, docs/**/* - Project documentation

Extract from each file:

  1. 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)
  2. 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
  3. 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"
  4. Tool Requirements (MCP tool calls):

    • Explicit requirements: "use mcp__X tool"
    • Tool sequences: "call X before Y"
  5. Conditional Requirements (context-specific):

    • "If GraphQL changes, run codegen"
    • "If SCSS changes, verify spacing"
    • Mark as required: false in 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" }

Step 3: Search Referenced Online Sources

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

Step 4: Infer Workflow Type

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

Step 5: Determine Hook Support

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

Step 6: Propose Configuration

  1. Present findings: "I've analyzed [N] rule files and detected [workflow type]. Your platform ([platform]) supports [hooks]."
  2. Show proposed config with extracted steps and checklist items
  3. Explain trade-offs: With/without hooks, full vs. minimal enforcement
  4. Get approval before creating files

Step 7: Create Files

  1. Add MCP server to config file
  2. Create .protocol-enforcer.json with tailored configuration
  3. Create hook scripts if platform supports them
  4. Update supervisor protocol files with integration instructions
  5. Reload IDE

Appendix B: Complete Supervisor Examples

Example 1: Planning & Analysis (Claude Code)

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"
     ]
   });
  1. Authorize

    await mcp__protocol_enforcer__authorize_file_operation({
      operation_token: v.operation_token
    });
  2. Implement

    • Only after authorization
    • Minimal changes only
    • No scope creep

Enforcement:

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-First Development Protocol

Required Steps:

  1. design_review - Review Figma specs
  2. component_mapping - Map to existing/new components

Required Checklist:

  • Design tokens mapped to SCSS variables
  • Figma specs reviewed
  • Accessibility requirements checked
  • Responsive breakpoints defined

Workflow:

1. Design Review

  • Open Figma, extract design tokens (colors, spacing, typography)
  • Note accessibility (ARIA, keyboard nav)
  • Document responsive breakpoints

2. Component Mapping

  • Search for similar components
  • Decide: reuse, extend, or create
  • Map Figma tokens to SCSS variables

3. Verify Compliance

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"
  ]
})

4. Authorize & Implement

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


Example 4: Minimal/Emergency (All Platforms)

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.


Platform Comparison Table

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

Appendix C: Hook Scripts Reference

All 5 hook scripts for creating in .cursor/hooks/ or .cline/hooks/.

1. pre-tool-use.cjs

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);
});

2. post-tool-use.cjs

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.cjs

Or 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);
  }
});

3. user-prompt-submit.cjs

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);
  }
});

4. session-start.cjs

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
  }
});

5. stop.cjs

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
  }
});

Appendix D: Context Persistence Hook Scripts

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.


1. pre-compact-handoff.cjs

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);
  }
});

2. session-end-handoff.cjs

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);
  }
});

3. session-start-handoff.cjs

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);
  }
});

Usage Notes

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.log for 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:

  1. Create .cursor/hooks/ directory
  2. Save these scripts with .cjs extension
  3. Make executable: chmod +x .cursor/hooks/*.cjs
  4. Configure in .claude/settings.json (see Appendix C for format)

License

MIT License - Copyright (c) 2025 Jason Lusk

#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
const os = require('os');
// BUG #25 FIX: GC counter at FILE SCOPE (persists across tool calls)
let gcCounter = 0;
// Read stdin
let input = '';
process.stdin.on('data', chunk => input += chunk);
process.stdin.on('end', () => {
// ┌─────────────────────────────────────────────────────────┐
// │ EXEMPTIONS: Allow protocol tools without recursion │
// └─────────────────────────────────────────────────────────┘
// Prevent infinite loop: these tools don't need to call themselves first
try {
const hookData = JSON.parse(input);
const { tool_name } = hookData;
// Exempt informed_reasoning and protocol-enforcer MCP tools
const exemptedTools = [
'mcp__memory-augmented-reasoning__informed_reasoning',
'mcp__protocol-enforcer__verify_protocol_compliance',
'mcp__protocol-enforcer__authorize_file_operation'
];
if (exemptedTools.includes(tool_name)) {
const response = {
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "allow",
permissionDecisionReason: `✅ protocol-enforcer: ${tool_name} exempted (prevents infinite loop)`
}
};
console.log(JSON.stringify(response));
process.stderr.write(`✅ protocol-enforcer: ${tool_name} exempted\n`);
process.exit(0);
}
} catch (e) {
// Continue with token checks if JSON parsing fails
}
// BUG #5 FIX: Garbage collection (runs every 10 tool calls)
gcCounter++;
if (gcCounter % 10 === 0) {
try {
const homeDir = os.homedir();
const files = fs.readdirSync(homeDir);
const now = Date.now();
let cleanedCount = 0;
files.forEach(file => {
if (file.startsWith('.protocol-session-') && file.endsWith('.json')) {
// BUG #23 FIX: Skip temp files
if (file.endsWith('.json.tmp')) return;
const filePath = path.join(homeDir, file);
try {
const stats = fs.statSync(filePath);
const data = JSON.parse(fs.readFileSync(filePath, 'utf8'));
// BUG #19 FIX: Use mtime as fallback
const fileAge = now - (data.created_at || stats.mtimeMs);
// Clean if expired or older than 24 hours
if (data.expires_at < now || fileAge > 86400000) {
fs.unlinkSync(filePath);
cleanedCount++;
}
} catch (e) {
// BUG #8 FIX: Delete malformed files
try {
fs.unlinkSync(filePath);
cleanedCount++;
} catch (deleteErr) {
// Ignore deletion errors
}
}
}
});
if (cleanedCount > 0) {
process.stderr.write(`🗑️ protocol-enforcer: cleaned ${cleanedCount} expired token(s)\n`);
}
} catch (e) {
// Ignore GC errors
}
}
// ┌─────────────────────────────────────────────────────────┐
// │ CHECK 3: Validate session token (if exists) │
// └─────────────────────────────────────────────────────────┘
let hookData;
try {
hookData = JSON.parse(input);
} catch (e) {
// Already tried parsing above, just use empty object
hookData = {};
}
// BUG #27/#28/#29 FIX: Scan directory for ANY valid session token
// No need to know session_id in advance - just find any valid token
try {
const homeDir = os.homedir();
const files = fs.readdirSync(homeDir);
const now = Date.now();
for (const file of files) {
// Look for session token files (exclude temp files)
if (file.startsWith('.protocol-session-') && file.endsWith('.json') && !file.endsWith('.tmp')) {
const sessionTokenFile = path.join(homeDir, file);
try {
// BUG #1 FIX: Check token maturity (100ms minimum age)
const stats = fs.statSync(sessionTokenFile);
const tokenAge = now - stats.mtimeMs;
if (tokenAge < 100) {
// Token too young - skip and check next file
continue;
}
// BUG #10 FIX: Validate token structure
const tokenData = JSON.parse(fs.readFileSync(sessionTokenFile, 'utf8'));
// Invalid structure - delete and continue
if (!tokenData.session_id || !tokenData.expires_at || !tokenData.created_at) {
try {
fs.unlinkSync(sessionTokenFile);
} catch (deleteErr) {
// Ignore deletion errors
}
continue;
}
// Expired - delete and continue
if (tokenData.expires_at < now) {
try {
fs.unlinkSync(sessionTokenFile);
} catch (deleteErr) {
// Ignore deletion errors
}
continue;
}
// Valid session token found - allow tool immediately
const response = {
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "allow",
permissionDecisionReason: "✅ protocol-enforcer: valid session token"
}
};
console.log(JSON.stringify(response));
process.stderr.write(`✅ All gates passed - session token valid\n`);
process.exit(0);
} catch (e) {
// BUG #8 + #9 FIX: Handle read/parse errors - delete malformed token
try {
fs.unlinkSync(sessionTokenFile);
} catch (deleteErr) {
// Ignore deletion errors
}
// Continue scanning for other tokens
continue;
}
}
}
} catch (e) {
// Directory scan failed - log warning and continue to CHECK 1/CHECK 2
process.stderr.write(`⚠️ protocol-enforcer: session token scan error - ${e.message}\n`);
}
// No valid session token - continue with CHECK 1 and CHECK 2
const tokenFile = path.join(os.homedir(), '.protocol-enforcer-token');
const reasoningTokenFile = path.join(os.homedir(), '.protocol-informed-reasoning-token');
// ┌─────────────────────────────────────────────────────────┐
// │ CHECK 1: Verify informed_reasoning was called │
// └─────────────────────────────────────────────────────────┘
if (!fs.existsSync(reasoningTokenFile)) {
const response = {
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: "⛔ protocol-enforcer: must call informed_reasoning (analyze phase) first"
}
};
console.log(JSON.stringify(response));
process.stderr.write('⛔ REASON gate blocking - call informed_reasoning first\n');
process.exit(0);
}
// ┌─────────────────────────────────────────────────────────┐
// │ CHECK 2: Verify protocol compliance authorization │
// └─────────────────────────────────────────────────────────┘
if (!fs.existsSync(tokenFile)) {
const response = {
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: "⛔ protocol-enforcer: call verify_protocol_compliance then authorize_file_operation"
}
};
console.log(JSON.stringify(response));
process.stderr.write('⛔ LEARN gate blocking - call search_experiences first\n');
process.exit(0);
}
// Both tokens exist - consume them and allow
try {
fs.unlinkSync(reasoningTokenFile);
fs.unlinkSync(tokenFile);
const response = {
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "allow",
permissionDecisionReason: "✅ protocol-enforcer: all requirements met"
}
};
console.log(JSON.stringify(response));
process.stderr.write('✅ All gates passed - operation authorized\n');
process.exit(0);
} catch (e) {
const response = {
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: `⛔ protocol-enforcer: token error - ${e.message}`
}
};
console.log(JSON.stringify(response));
process.stderr.write(`\n⛔ protocol-enforcer: token consumption failed - ${e.message}\n`);
process.exit(0);
}
});
{
"enforced_rules": {
"require_protocol_steps": [
{
"name": "sequential_thinking",
"hook": "user_prompt_submit"
},
{
"name": "analyze_behavior",
"hook": "user_prompt_submit"
},
{
"name": "apply_chores_fixes",
"hook": "pre_tool_use"
}
],
"require_checklist_confirmation": true,
"minimum_checklist_items": 5
},
"checklist_items": [
{
"text": "Sequential thinking completed FIRST",
"hook": "user_prompt_submit"
},
{
"text": "Behavioral patterns analyzed systematically",
"hook": "user_prompt_submit"
},
{
"text": "Constraint issues addressed (structure/format adherence)",
"hook": "pre_tool_use"
},
{
"text": "Hallucination issues addressed (no false information)",
"hook": "pre_tool_use"
},
{
"text": "Overconfidence issues addressed (uncertainty expressed when appropriate)",
"hook": "pre_tool_use"
},
{
"text": "Reasoning issues addressed (logical consistency verified)",
"hook": "pre_tool_use"
},
{
"text": "Technical precision maintained (state 'I don't know' when uncertain)",
"hook": "pre_tool_use"
},
{
"text": "Zero deflection policy (attempt tools before claiming unavailable)",
"hook": "pre_tool_use"
},
{
"text": "No 'likely' explanations without verification",
"hook": "pre_tool_use"
},
{
"text": "Ethical/Safety issues addressed (harmful content prevented)",
"hook": "pre_tool_use"
},
{
"text": "Sycophancy issues addressed (truthfulness over false agreement)",
"hook": "pre_tool_use"
},
{
"text": "Professional objectivity maintained (facts over validation)",
"hook": "post_tool_use"
}
]
}
{
"enforced_rules": {
"require_protocol_steps": [
{
"name": "sequential_thinking",
"hook": "user_prompt_submit"
},
{
"name": "task_initiation",
"hook": "user_prompt_submit"
},
{
"name": "pre_planning_analysis",
"hook": "pre_tool_use",
"applies_to": ["Write", "Edit"]
},
{
"name": "plan_generation",
"hook": "pre_tool_use",
"applies_to": ["Write", "Edit"]
}
],
"require_checklist_confirmation": true,
"minimum_checklist_items": 5
},
"checklist_items": [
{
"text": "Pre-response compliance audit completed",
"hook": "user_prompt_submit"
},
{
"text": "Sequential thinking completed FIRST (mcp__clear-thought__sequentialthinking)",
"hook": "user_prompt_submit"
},
{
"text": "User request restated with ALL ambiguities clarified",
"hook": "user_prompt_submit"
},
{
"text": "Requirements gathered (tickets, designs, data sources)",
"hook": "pre_tool_use"
},
{
"text": "Existing patterns analyzed for reuse (components, hooks, utils)",
"hook": "pre_tool_use"
},
{
"text": "Dependencies identified (files to modify/create, reusable code)",
"hook": "pre_tool_use"
},
{
"text": "PLAN comprehensive (objective, files, dependencies, tools, confidence, risks)",
"hook": "pre_tool_use",
"applies_to": ["Write", "Edit"]
},
{
"text": "PLAN confirmed by user BEFORE executing code",
"hook": "pre_tool_use",
"applies_to": ["Write", "Edit"]
},
{
"text": "Technical precision maintained (no 'likely' explanations)",
"hook": "pre_tool_use"
},
{
"text": "Pattern matching verified (file structure, naming, code style)",
"hook": "pre_tool_use",
"applies_to": ["Write", "Edit"]
},
{
"text": "Reuse verified (searched before creating)",
"hook": "pre_tool_use",
"applies_to": ["Write", "Edit"]
},
{
"text": "TypeScript strict mode (no 'any' without justification)",
"hook": "pre_tool_use",
"applies_to": ["Write", "Edit"]
},
{
"text": "Linting passed",
"hook": "post_tool_use",
"applies_to": ["Write", "Edit"]
},
{
"text": "Type check passed",
"hook": "post_tool_use",
"applies_to": ["Write", "Edit"]
},
{
"text": "No console.log() statements",
"hook": "post_tool_use",
"applies_to": ["Write", "Edit"]
},
{
"text": "Import order correct",
"hook": "post_tool_use",
"applies_to": ["Write", "Edit"]
},
{
"text": "Acceptance criteria verified",
"hook": "post_tool_use"
},
{
"text": "Completion summarized with deviations justified",
"hook": "post_tool_use"
}
]
}
{
"$schema": "https://raw.githubusercontent.com/mpalpha/protocol-enforcer-mcp/main/schema.json",
"version": "2.0.0",
"project_name": "Your Project Name",
"description": "Foundation strategies - optimal balance of enforcement and efficiency",
"strategies_enabled": {
"session_tokens": true,
"extended_enforcement": true,
"macro_gates": true,
"fast_track_mode": true,
"unified_tokens": true
},
"security": {
"session_token_ttl_ms": 600000,
"session_token_ttl_description": "10 minutes (MCP recommended)",
"allow_extended_ttl": false,
"extended_ttl_warning": "Extended TTL (>10min) increases security risk. Only enable for trusted environments."
},
"automatic_experience_recording": {
"enabled": true,
"scope": "auto",
"auto_discover_keywords": true,
"project_keywords": [],
"default_when_uncertain": "project",
"trigger": "post_tool_use",
"domains": ["Tools", "Protocol", "Debugging", "Decision"],
"minimum_confidence": 0.7,
"record_for_tools": ["Write", "Edit", "NotebookEdit", "WebSearch", "Grep"]
},
"enforced_rules": {
"require_protocol_steps": [
{
"name": "informed_reasoning_analyze",
"description": "Use informed_reasoning before ANY tool",
"hook": "pre_tool_use",
"applies_to": ["Write", "Edit", "NotebookEdit", "WebSearch", "Grep", "Task"]
},
{
"name": "record_experience",
"description": "Record experience after completing any task",
"hook": "post_tool_use",
"applies_to": ["Write", "Edit", "NotebookEdit", "WebSearch", "Grep", "Task"]
}
],
"require_checklist_confirmation": true,
"minimum_checklist_items": 3,
"blocking_mode": "strict"
},
"checklist_items": [
{
"text": "ANALYZE: Used informed_reasoning, clarified requirements, reviewed relevant documentation/designs (if applicable), searched codebase",
"hook": "pre_tool_use",
"category": "critical",
"gate": "ANALYZE"
},
{
"text": "PLAN: Analyzed dependencies, generated PLAN with confidence/risks, received user confirmation",
"hook": "pre_tool_use",
"category": "critical",
"gate": "PLAN"
},
{
"text": "VALIDATE: Ran build/lint/type-check, verified acceptance criteria, recorded experience",
"hook": "post_tool_use",
"category": "critical",
"gate": "VALIDATE"
}
],
"fast_track_mode": {
"enabled": true,
"low_complexity_criteria": {
"single_line_edits": true,
"comment_only_changes": true,
"string_literal_changes": true
},
"always_full_protocol": {
"critical_paths": [
".env",
".cursor/hooks/",
".protocol-enforcer.json"
]
}
}
}
{
"enforced_rules": {
"require_protocol_steps": [
{
"name": "sequential_thinking",
"hook": "user_prompt_submit"
},
{
"name": "planning",
"hook": "pre_tool_use",
"applies_to": ["Write", "Edit"]
},
{
"name": "execution",
"hook": "pre_tool_use",
"applies_to": ["Write", "Edit"]
}
],
"require_checklist_confirmation": true,
"minimum_checklist_items": 3
},
"checklist_items": [
{
"text": "Sequential thinking completed FIRST",
"hook": "user_prompt_submit"
},
{
"text": "Task requirements clarified with user",
"hook": "pre_tool_use"
},
{
"text": "Plan created and confirmed before execution",
"hook": "pre_tool_use",
"applies_to": ["Write", "Edit"]
},
{
"text": "Technical precision maintained (state 'I don't know' when uncertain)",
"hook": "pre_tool_use"
},
{
"text": "Zero deflection policy (attempt available tools before claiming unavailable)",
"hook": "pre_tool_use"
},
{
"text": "Completion verified against stated objectives",
"hook": "post_tool_use",
"applies_to": ["Write", "Edit"]
}
]
}
{
"$schema": "https://raw.githubusercontent.com/mpalpha/protocol-enforcer-mcp/main/schema.json",
"version": "1.0.0",
"project_name": "SmartPass Admin UI",
"description": "Protocol enforcement configuration for SmartPass Admin UI development workflow",
"enforced_rules": {
"require_protocol_steps": [
{
"name": "informed_reasoning_required",
"description": "Use mcp__memory-augmented-reasoning__informed_reasoning (analyze phase) before ANY action - enforced at UserPromptSubmit level",
"hook": "user_prompt_submit",
"applies_to": []
},
{
"name": "informed_reasoning_analyze",
"description": "Use mcp__memory-augmented-reasoning__informed_reasoning (analyze phase) before ANY action to get context-aware guidance",
"hook": "pre_tool_use",
"applies_to": ["Write", "Edit"]
},
{
"name": "clarify_requirements",
"description": "Restate user request and identify all ambiguities",
"hook": "pre_tool_use",
"applies_to": ["Write", "Edit"]
},
{
"name": "gather_requirements",
"description": "Review Jira ticket, Figma designs, and existing codebase",
"hook": "pre_tool_use",
"applies_to": ["Write", "Edit"]
},
{
"name": "analyze_dependencies",
"description": "Identify files, components, hooks, utilities, and GraphQL operations",
"hook": "pre_tool_use",
"applies_to": ["Write", "Edit"]
},
{
"name": "generate_plan",
"description": "Create comprehensive PLAN with confidence level and risks",
"hook": "pre_tool_use",
"applies_to": ["Write", "Edit"]
},
{
"name": "await_confirmation",
"description": "Receive explicit PLAN confirmation from user before execution",
"hook": "pre_tool_use",
"applies_to": ["Write", "Edit"]
}
],
"require_checklist_confirmation": true,
"minimum_checklist_items": 8,
"blocking_mode": "strict"
},
"checklist_items": [
{
"text": "Used mcp__memory-augmented-reasoning__informed_reasoning (analyze phase) tool before proceeding",
"hook": "pre_tool_use",
"category": "critical"
},
{
"text": "Identified and clarified all ambiguities in the request",
"hook": "pre_tool_use",
"category": "planning"
},
{
"text": "Reviewed Jira ticket for acceptance criteria and technical notes (if applicable)",
"hook": "pre_tool_use",
"category": "requirements"
},
{
"text": "Checked Figma designs for UI specifications and design tokens (if UI task)",
"hook": "pre_tool_use",
"category": "requirements"
},
{
"text": "Searched src/components/ for reusable components",
"hook": "pre_tool_use",
"category": "codebase_analysis"
},
{
"text": "Searched src/hooks/ for reusable custom hooks",
"hook": "pre_tool_use",
"category": "codebase_analysis"
},
{
"text": "Searched src/utils/ for reusable utilities",
"hook": "pre_tool_use",
"category": "codebase_analysis"
},
{
"text": "Analyzed existing GraphQL operations in src/graphql/operations/",
"hook": "pre_tool_use",
"category": "codebase_analysis"
},
{
"text": "Verified no new dependencies needed (or requested authorization)",
"hook": "pre_tool_use",
"category": "dependencies"
},
{
"text": "Generated comprehensive PLAN with task objective, files, tools, confidence level, risks, and scope statement",
"hook": "pre_tool_use",
"category": "planning"
},
{
"text": "Received explicit PLAN confirmation from user",
"hook": "pre_tool_use",
"category": "critical"
},
{
"text": "Verified scope minimized to explicit request only (no feature creep)",
"hook": "pre_tool_use",
"category": "scope"
},
{
"text": "Matched existing file structure and naming conventions",
"hook": "pre_tool_use",
"category": "patterns"
},
{
"text": "Followed existing code patterns from similar components",
"hook": "pre_tool_use",
"category": "patterns"
},
{
"text": "Ran yarn graphql:codegen after GraphQL operation changes",
"hook": "post_tool_use",
"category": "verification"
},
{
"text": "Ran yarn lint:all and fixed all ESLint errors",
"hook": "post_tool_use",
"category": "verification"
},
{
"text": "Ran yarn type-check and fixed all TypeScript errors",
"hook": "post_tool_use",
"category": "verification"
},
{
"text": "Removed all console.log() statements",
"hook": "post_tool_use",
"category": "code_quality"
},
{
"text": "Removed unnecessary code comments (self-documenting code preferred)",
"hook": "post_tool_use",
"category": "code_quality"
},
{
"text": "Verified against acceptance criteria",
"hook": "post_tool_use",
"category": "validation"
},
{
"text": "Checked development-checklists.md for pattern compliance",
"hook": "post_tool_use",
"category": "validation"
}
],
"behavioral_requirements": {
"MODEL_framework": {
"description": "Behavioral corrections framework from backup rules",
"categories": [
{
"name": "constraint_adherence",
"violations": [
"Adding features beyond explicit request",
"Refactoring code not requested",
"Optimizing prematurely",
"Adding error handling for impossible scenarios",
"Creating abstractions for one-time operations"
]
},
{
"name": "hallucination_prevention",
"violations": [
"Assuming code exists without reading files",
"Guessing file paths or API endpoints",
"Making up TypeScript types",
"Fabricating design token values",
"Inventing GraphQL operations without checking"
]
},
{
"name": "overconfidence_reduction",
"violations": [
"Proceeding without user confirmation on PLAN",
"Not stating 'I don't know' when uncertain",
"Providing 'likely' explanations without verification",
"Not requesting clarification on ambiguities"
]
},
{
"name": "reasoning_transparency",
"violations": [
"Not explaining confidence level in PLAN",
"Not documenting risks identified",
"Not stating scope boundaries explicitly",
"Not justifying deviations from PLAN"
]
},
{
"name": "ethical_alignment",
"violations": [
"Hardcoding credentials in configuration",
"Committing sensitive tokens to version control",
"Not warning about security risks"
]
}
]
}
},
"custom_rules": {
"scss_styling": {
"pre_tool_use": [
"CSS module class names use camelCase (e.g., .passTypeCard)",
"All spacing uses rem units on 2px/4px grid",
"Font styling uses $font-* variables (not font-weight)",
"Use @use instead of deprecated @import",
"Use react-md CSS variables for react-md components"
],
"post_tool_use": [
"Run yarn stylelint:fix",
"No unnecessary CSS comments",
"Border-radius uses .5rem (8px) not 7px"
]
},
"typescript_react": {
"pre_tool_use": [
"Component props defined inline (not separate .types.ts)",
"No React.FC or FC type annotations",
"Import order: React, third-party, components, hooks, utils, GraphQL, types, styles (last)",
"Use type keyword for all type-only imports"
],
"post_tool_use": [
"Run yarn eslint:fix",
"Run yarn type-check",
"Remove unused imports",
"Props alphabetized (callbacks after regular props)"
]
},
"graphql": {
"pre_tool_use": [
"NO exports in src/graphql/operations/ files",
"Use gql(...) without export const"
],
"post_tool_use": [
"Run yarn graphql:codegen after operation changes",
"Import from src/graphql/generated/graphql only",
"Alias all useQuery/useMutation return values"
]
}
},
"tool_permissions": {
"auto_approved_tools": [
"yarn graphql:codegen",
"yarn lint:all",
"yarn eslint:fix",
"yarn stylelint:fix",
"yarn type-check",
"Bash",
"Read",
"Glob",
"Grep"
],
"require_user_approval": [
"yarn add",
"yarn remove",
"git push",
"rm -rf",
"Write",
"Edit"
]
},
"documentation": {
"references": [
".cursor/development-checklists.md",
".cursor/rules/mandatory-supervisor-protocol.mdc",
".cursor/rules/styling.mdc",
".cursor/rules/typescript.mdc",
".cursor/rules/graphql.mdc",
"CLAUDE.md"
],
"mcp_servers_required": [
"mcp__memory-augmented-reasoning__informed_reasoning",
"mcp__memory-bank__*",
"mcp__mcp-atlassian__jira_get_issue",
"mcp__figma__get_design_context"
]
}
}
#!/usr/bin/env node
/**
* Protocol Enforcer MCP Server
* Enforces custom workflow protocol compliance before allowing file operations
*
* Version: 3.0.1
* Author: Jason Lusk <[email protected]>
* License: MIT
*/
const fs = require('fs');
const path = require('path');
const os = require('os');
const readline = require('readline');
const crypto = require('crypto');
const VERSION = '3.0.1';
// Custom error class for parameter validation (returns -32602 instead of -32603)
class ValidationError extends Error {
constructor(message) {
super(message);
this.name = 'ValidationError';
this.code = -32602; // Invalid params
}
}
// State tracking
const state = {
configPath: null,
config: null,
complianceChecks: [],
operationTokens: new Map(), // Map<token, {expires: timestamp, used: boolean}>
tokenTimeout: 60000, // 60 seconds
sessionTokens: new Map(), // Map<sessionId, {expires: timestamp, created: timestamp}>
sessionTokenTimeout: 3600000, // 60 minutes
toolLogPath: path.join(os.homedir(), '.protocol-tool-log.json')
};
// BUG #20 FIX: Background cleanup of expired session tokens (every 5 minutes)
setInterval(() => {
const now = Date.now();
for (const [sessionId, tokenData] of state.sessionTokens.entries()) {
if (tokenData.expires < now) {
state.sessionTokens.delete(sessionId);
// Clean up file system token
const tokenFile = path.join(os.homedir(), `.protocol-session-${sessionId}.json`);
try {
fs.unlinkSync(tokenFile);
} catch (err) {
// Ignore errors - file may already be cleaned by GC
}
}
}
}, 300000); // 5 minutes
// Tool execution tracking functions
function readToolLog() {
try {
if (fs.existsSync(state.toolLogPath)) {
const data = fs.readFileSync(state.toolLogPath, 'utf8');
return JSON.parse(data);
}
} catch (err) {
console.error('[protocol-enforcer] Error reading tool log:', err.message);
}
return [];
}
function writeToolLog(log) {
try {
fs.writeFileSync(state.toolLogPath, JSON.stringify(log, null, 2), 'utf8');
} catch (err) {
console.error('[protocol-enforcer] Error writing tool log:', err.message);
}
}
function recordToolExecution(toolName, parameters = {}) {
const log = readToolLog();
log.push({
sessionId: process.env.CLAUDE_SESSION_ID || 'unknown',
toolName,
parameters,
timestamp: new Date().toISOString()
});
writeToolLog(log);
}
function getSessionToolCalls(sessionId) {
const log = readToolLog();
return log.filter(entry => entry.sessionId === sessionId);
}
// Default configuration (MODEL Framework CHORES behavioral fixes)
const DEFAULT_CONFIG = {
enforced_rules: {
require_protocol_steps: [
{
name: "analyze_behavior",
hook: "user_prompt_submit"
},
{
name: "apply_chores_fixes",
hook: "pre_tool_use",
applies_to: ["Write", "Edit"]
}
],
require_checklist_confirmation: true,
minimum_checklist_items: 3
},
checklist_items: [
{
text: "Constraint issues addressed (structure/format adherence)",
hook: "pre_tool_use"
},
{
text: "Hallucination issues addressed (no false information)",
hook: "pre_tool_use"
},
{
text: "Overconfidence issues addressed (uncertainty expressed when appropriate)",
hook: "pre_tool_use"
},
{
text: "Reasoning issues addressed (logical consistency verified)",
hook: "pre_tool_use"
},
{
text: "Ethical/Safety issues addressed (harmful content prevented)",
hook: "pre_tool_use"
},
{
text: "Sycophancy issues addressed (truthfulness over false agreement)",
hook: "pre_tool_use"
}
]
};
// Find config file (project scope takes precedence)
function findConfigFile() {
const cwd = process.cwd();
const projectConfig = path.join(cwd, '.protocol-enforcer.json');
const homeConfig = path.join(process.env.HOME || process.env.USERPROFILE, '.protocol-enforcer.json');
if (fs.existsSync(projectConfig)) {
return projectConfig;
}
if (fs.existsSync(homeConfig)) {
return homeConfig;
}
return null;
}
// Load configuration
function loadConfig() {
const configPath = findConfigFile();
if (configPath) {
state.configPath = configPath;
const rawConfig = JSON.parse(fs.readFileSync(configPath, 'utf8'));
state.config = validateConfig(rawConfig);
return state.config;
}
state.config = DEFAULT_CONFIG;
return null;
}
// Tool: initialize_protocol_config
async function initializeProtocolConfig(args) {
const scope = args.scope || 'project';
let configPath;
if (scope === 'project') {
configPath = path.join(process.cwd(), '.protocol-enforcer.json');
} else if (scope === 'user') {
configPath = path.join(process.env.HOME || process.env.USERPROFILE, '.protocol-enforcer.json');
} else {
return {
content: [{
type: 'text',
text: JSON.stringify({ error: 'Invalid scope. Must be "project" or "user".' }, null, 2)
}]
};
}
if (fs.existsSync(configPath)) {
return {
content: [{
type: 'text',
text: JSON.stringify({
error: 'Configuration file already exists',
path: configPath
}, null, 2)
}]
};
}
fs.writeFileSync(configPath, JSON.stringify(DEFAULT_CONFIG, null, 2), 'utf8');
state.configPath = configPath;
state.config = DEFAULT_CONFIG;
return {
content: [{
type: 'text',
text: JSON.stringify({
success: true,
message: `Configuration file created at ${configPath}`,
config: DEFAULT_CONFIG
}, null, 2)
}]
};
}
// Validate config format
function validateConfig(config) {
const errors = [];
// Validate protocol steps
if (config.enforced_rules.require_protocol_steps) {
config.enforced_rules.require_protocol_steps.forEach((step, idx) => {
if (typeof step === 'string') {
errors.push(`Protocol step at index ${idx} is a string. Must be an object with 'name' and 'hook' properties.`);
} else if (!step.name || !step.hook) {
errors.push(`Protocol step at index ${idx} missing required 'name' or 'hook' property.`);
}
});
}
// Validate checklist items
if (config.checklist_items) {
config.checklist_items.forEach((item, idx) => {
if (typeof item === 'string') {
errors.push(`Checklist item at index ${idx} is a string. Must be an object with 'text' and 'hook' properties.`);
} else if (!item.text || !item.hook) {
errors.push(`Checklist item at index ${idx} missing required 'text' or 'hook' property.`);
}
});
}
if (errors.length > 0) {
throw new Error(`Invalid configuration format:\n${errors.join('\n')}\n\nSee README.md Configuration Reference section for correct format.`);
}
return config;
}
// Filter rules by hook and tool name
function filterByHook(items, hook, toolName = null) {
return items.filter(item => {
// Check if hook matches
if (item.hook !== hook) return false;
// Check if tool-specific filtering applies
if (item.applies_to && toolName) {
return item.applies_to.includes(toolName);
}
return true;
});
}
// ┌─────────────────────────────────────────────────────────────────────────┐
// │ PHASE 3: Fast-Track Mode - Classify task complexity │
// └─────────────────────────────────────────────────────────────────────────┘
function classifyTaskComplexity(config, toolName, args = {}) {
// Fast-track disabled - always use full protocol
if (!config.fast_track_mode || !config.fast_track_mode.enabled) {
return { fastTrackEligible: false, reason: 'Fast-track mode disabled' };
}
const criteria = config.fast_track_mode.low_complexity_criteria || {};
const criticalPaths = config.fast_track_mode.always_full_protocol?.critical_paths || [];
// Check if file is in critical paths (always requires full protocol)
const filePath = args.file_path || args.path || '';
if (filePath && criticalPaths.some(path => filePath.includes(path))) {
return { fastTrackEligible: false, reason: `Critical path: ${filePath}` };
}
// Analyze complexity based on criteria
let isLowComplexity = false;
let matchedCriteria = null;
// Single-line edits
if (criteria.single_line_edits && args.old_string && args.new_string) {
const oldLines = args.old_string.split('\n').length;
const newLines = args.new_string.split('\n').length;
if (oldLines === 1 && newLines === 1) {
isLowComplexity = true;
matchedCriteria = 'single_line_edits';
}
}
// Comment-only changes
if (criteria.comment_only_changes && args.old_string && args.new_string) {
const commentPattern = /^\s*(\/\/|\/\*|\*|#)/;
const oldIsComment = args.old_string.split('\n').every(line => !line.trim() || commentPattern.test(line));
const newIsComment = args.new_string.split('\n').every(line => !line.trim() || commentPattern.test(line));
if (oldIsComment || newIsComment) {
isLowComplexity = true;
matchedCriteria = 'comment_only_changes';
}
}
// String literal changes
if (criteria.string_literal_changes && args.old_string && args.new_string) {
const stringPattern = /^[\s'"]+|[\s'"]+$/g;
const oldCore = args.old_string.replace(stringPattern, '');
const newCore = args.new_string.replace(stringPattern, '');
// If structure is identical except for string content
if (oldCore.length > 0 && newCore.length > 0 &&
args.old_string.split('"').length === args.new_string.split('"').length) {
isLowComplexity = true;
matchedCriteria = 'string_literal_changes';
}
}
return {
fastTrackEligible: isLowComplexity,
reason: isLowComplexity ? `Low complexity: ${matchedCriteria}` : 'Standard complexity - requires full protocol'
};
}
// Tool: verify_protocol_compliance
async function verifyProtocolCompliance(args) {
const rawConfig = state.config || loadConfig() || DEFAULT_CONFIG;
// Validate config format (throws if invalid)
const config = validateConfig(rawConfig);
const violations = [];
const hook = args.hook;
const toolName = args.tool_name || null;
if (!hook) {
return {
content: [{
type: 'text',
text: JSON.stringify({
error: 'Missing required parameter: hook. Must specify which hook is calling (e.g., "user_prompt_submit", "pre_tool_use", "post_tool_use").'
}, null, 2)
}]
};
}
// ┌─────────────────────────────────────────────────────────────────────────┐
// │ PHASE 3: Fast-Track Mode - Check if task qualifies for fast-track │
// └─────────────────────────────────────────────────────────────────────────┘
const fastTrackResult = classifyTaskComplexity(config, toolName, args);
const isFastTrack = fastTrackResult.fastTrackEligible;
// Check required protocol steps (filtered by hook)
if (config.enforced_rules.require_protocol_steps && Array.isArray(config.enforced_rules.require_protocol_steps)) {
const allRequiredSteps = config.enforced_rules.require_protocol_steps;
const hookFilteredSteps = filterByHook(allRequiredSteps, hook, toolName);
const completedSteps = args.protocol_steps_completed || [];
const missingSteps = hookFilteredSteps.filter(step => !completedSteps.includes(step.name));
if (missingSteps.length > 0) {
missingSteps.forEach(step => {
violations.push(`VIOLATION: Required protocol step not completed: ${step.name} (hook: ${hook})`);
});
}
}
// Check checklist confirmation (filtered by hook)
if (config.enforced_rules.require_checklist_confirmation) {
const checkedItems = args.checklist_items_checked || [];
const allRequiredItems = config.checklist_items || [];
const hookFilteredItems = filterByHook(allRequiredItems, hook, toolName);
const minItems = config.enforced_rules.minimum_checklist_items || 0;
// Count only items applicable to this hook
// Fast-track: Reduce minimum by 1 if PLAN gate is skipped
let applicableMinItems = Math.min(minItems, hookFilteredItems.length);
if (isFastTrack) {
const planGateItems = hookFilteredItems.filter(item => item.gate === 'PLAN');
applicableMinItems = Math.max(0, applicableMinItems - planGateItems.length);
}
if (checkedItems.length < applicableMinItems) {
violations.push(`VIOLATION: Only ${checkedItems.length} checklist items checked, minimum ${applicableMinItems} required for hook '${hook}'`);
}
// Filter out PLAN gate items if fast-track eligible
let uncheckedRequired = hookFilteredItems.filter(item => !checkedItems.includes(item.text));
if (isFastTrack) {
uncheckedRequired = uncheckedRequired.filter(item => item.gate !== 'PLAN');
}
if (uncheckedRequired.length > 0) {
violations.push(`VIOLATION: Required checklist items not confirmed for hook '${hook}': ${uncheckedRequired.map(i => i.text).join(', ')}`);
}
// ┌─────────────────────────────────────────────────────────────────────────┐
// │ PHASE 2: Macro-Gates - Gate-level validation │
// └─────────────────────────────────────────────────────────────────────────┘
// Group checklist items by gate (if gate field exists)
const gateGroups = {};
hookFilteredItems.forEach(item => {
if (item.gate) {
if (!gateGroups[item.gate]) {
gateGroups[item.gate] = { items: [], checked: [] };
}
gateGroups[item.gate].items.push(item.text);
if (checkedItems.includes(item.text)) {
gateGroups[item.gate].checked.push(item.text);
}
}
});
// Validate gate completion (all items in a gate must be checked)
// Fast-track mode: skip PLAN gate validation for low-complexity tasks
Object.keys(gateGroups).forEach(gateName => {
// Skip PLAN gate if fast-track eligible
if (isFastTrack && gateName === 'PLAN') {
return;
}
const gate = gateGroups[gateName];
if (gate.checked.length < gate.items.length) {
const uncheckedInGate = gate.items.filter(item => !gate.checked.includes(item));
violations.push(`VIOLATION: Gate '${gateName}' incomplete - missing: ${uncheckedInGate.join(', ')}`);
}
});
// ┌─────────────────────────────────────────────────────────────────────────┐
// │ PHASE 4: Cross-reference checklist claims against actual tool calls │
// └─────────────────────────────────────────────────────────────────────────┘
// Get session ID from most recent tool log entry (all tools in current session share same ID)
const toolLog = readToolLog();
const sessionId = toolLog.length > 0 ? toolLog[toolLog.length - 1].sessionId : 'unknown';
const sessionTools = getSessionToolCalls(sessionId);
// Helper: Check if any tool matching pattern was called
const hasToolCall = (pattern) => {
return sessionTools.some(call => {
const toolName = call.toolName || '';
return toolName.toLowerCase().includes(pattern.toLowerCase());
});
};
/**
* Determines if a checklist item is conditional (only applies in certain scenarios)
*
* Conditional items include qualifiers like:
* - "(if applicable)" - Only required when relevant to the task
* - "(if UI task)" - Only required for UI/design work
* - "(if [condition])" - General conditional pattern
* - "(when [condition])" - Alternative conditional pattern
* - "(optional)" - Explicitly optional
*
* @param {string} itemText - The checklist item text
* @returns {boolean} - True if item has conditional qualifier
*/
const isConditionalItem = (itemText) => {
const text = itemText.toLowerCase();
return (
text.includes('(if applicable)') ||
text.includes('(if ui task)') ||
text.includes('(if ') ||
text.includes('(when ') ||
text.includes('(optional)')
);
};
// Verify search claims
const searchClaims = checkedItems.filter(item =>
item.toLowerCase().includes('search') ||
item.toLowerCase().includes('check') && item.toLowerCase().includes('src/')
);
if (searchClaims.length > 0) {
const hasSearch = hasToolCall('glob') || hasToolCall('grep');
if (!hasSearch) {
violations.push(
`VIOLATION: Claimed searches (${searchClaims.length} items) but no Glob/Grep tool calls detected in session. ` +
`Checklist requires actual tool execution, not assumptions.`
);
}
}
// Verify Jira review claims
const jiraClaims = checkedItems.filter(item => {
// Skip enforcement for conditional items (e.g., "if applicable")
if (isConditionalItem(item)) {
return false;
}
const text = item.toLowerCase();
return text.includes('jira') || text.includes('ticket') || text.includes('acceptance criteria');
});
if (jiraClaims.length > 0) {
const hasJira = hasToolCall('jira');
if (!hasJira) {
violations.push(
`VIOLATION: Claimed Jira review but no jira tool calls detected in session. ` +
`Must actually fetch ticket details, not assume requirements.`
);
}
}
// Verify Figma design claims
const figmaClaims = checkedItems.filter(item => {
// Skip enforcement for conditional items (e.g., "if UI task")
if (isConditionalItem(item)) {
return false;
}
const text = item.toLowerCase();
return text.includes('figma') || (text.includes('design') && text.includes('ui'));
});
if (figmaClaims.length > 0) {
const hasFigma = hasToolCall('figma');
if (!hasFigma) {
violations.push(
`VIOLATION: Claimed Figma design review but no figma tool calls detected in session. ` +
`Must actually fetch designs, not assume specifications.`
);
}
}
}
// ┌─────────────────────────────────────────────────────────────────────────┐
// │ Experience Search Enforcement - Verify suggested searches are executed │
// └─────────────────────────────────────────────────────────────────────────┘
if (hook === 'pre_tool_use') {
const searchTokenPath = path.join(os.homedir(), '.protocol-search-required-token');
if (fs.existsSync(searchTokenPath)) {
try {
const token = JSON.parse(fs.readFileSync(searchTokenPath, 'utf8'));
const age = Date.now() - token.timestamp;
// Only enforce if token is recent (< 5 minutes)
if (age < 300000) {
violations.push(
`VIOLATION: informed_reasoning suggested searching experiences (query: "${token.suggestedQuery}"), but search_experiences was not called. You must search past learnings before proceeding.`
);
}
} catch (e) {
// Invalid token, ignore
}
}
}
// Record check
state.complianceChecks.push({
timestamp: new Date().toISOString(),
passed: violations.length === 0,
violations: violations,
args: args
});
if (violations.length > 0) {
return {
content: [{
type: 'text',
text: JSON.stringify({
compliant: false,
violations: violations,
message: 'Protocol compliance check FAILED. Fix violations before proceeding.'
}, null, 2)
}]
};
}
// Generate single-use operation token
const crypto = require('crypto');
const token = crypto.randomBytes(32).toString('hex');
const expires = Date.now() + state.tokenTimeout;
state.operationTokens.set(token, { expires, used: false });
// Clean up expired tokens
for (const [key, value] of state.operationTokens.entries()) {
if (value.expires < Date.now() || value.used) {
state.operationTokens.delete(key);
}
}
return {
content: [{
type: 'text',
text: JSON.stringify({
compliant: true,
operation_token: token,
token_expires_in_seconds: state.tokenTimeout / 1000,
message: 'Protocol compliance verified. Use the operation_token with authorize_file_operation before proceeding.'
}, null, 2)
}]
};
}
// Tool: get_compliance_status
async function getComplianceStatus() {
const recentChecks = state.complianceChecks.slice(-10);
const passedCount = recentChecks.filter(c => c.passed).length;
const failedCount = recentChecks.length - passedCount;
return {
content: [{
type: 'text',
text: JSON.stringify({
total_checks: state.complianceChecks.length,
recent_checks: recentChecks.length,
passed: passedCount,
failed: failedCount,
recent_violations: recentChecks
.filter(c => !c.passed)
.map(c => ({ timestamp: c.timestamp, violations: c.violations }))
}, null, 2)
}]
};
}
// Tool: get_protocol_config
async function getProtocolConfig() {
const config = state.config || loadConfig() || DEFAULT_CONFIG;
return {
content: [{
type: 'text',
text: JSON.stringify({
config_path: state.configPath || 'Using default configuration',
config: config
}, null, 2)
}]
};
}
// ┌─────────────────────────────────────────────────────────────────────────┐
// │ PHASE 4: Unified Token Model - Helper functions │
// └─────────────────────────────────────────────────────────────────────────┘
function isUnifiedTokensEnabled(config) {
return config.strategies_enabled && config.strategies_enabled.unified_tokens === true;
}
function createUnifiedToken(sessionId, operationToken, ttlMs) {
const now = Date.now();
const tokenId = crypto.randomUUID();
return {
id: tokenId,
session_id: sessionId,
operation_token: operationToken,
created_at: now,
expires_at: now + ttlMs,
version: '2.0'
};
}
function writeUnifiedToken(sessionId, tokenData) {
const tokenFile = path.join(os.homedir(), `.protocol-unified-${sessionId}.json`);
const tempFile = `${tokenFile}.tmp`;
try {
fs.writeFileSync(tempFile, JSON.stringify(tokenData, null, 2), 'utf8');
fs.renameSync(tempFile, tokenFile);
return { success: true, filePath: tokenFile };
} catch (err) {
// Cleanup temp file on error
try {
fs.unlinkSync(tempFile);
} catch (cleanupErr) {
// Ignore cleanup errors
}
return { success: false, error: err.message };
}
}
function readUnifiedToken(sessionId) {
const unifiedFile = path.join(os.homedir(), `.protocol-unified-${sessionId}.json`);
const legacySessionFile = path.join(os.homedir(), `.protocol-session-${sessionId}.json`);
const legacyOperationFile = path.join(os.homedir(), '.protocol-enforcer-token');
// Try unified token first
if (fs.existsSync(unifiedFile)) {
try {
const content = fs.readFileSync(unifiedFile, 'utf8');
const tokenData = JSON.parse(content);
// Validate structure
if (tokenData.session_id === sessionId && tokenData.expires_at > Date.now()) {
return { found: true, type: 'unified', data: tokenData };
}
} catch (err) {
// Invalid unified token, fall through to legacy
}
}
// Fallback to legacy tokens
let sessionToken = null;
let operationToken = null;
if (fs.existsSync(legacySessionFile)) {
try {
const content = fs.readFileSync(legacySessionFile, 'utf8');
sessionToken = JSON.parse(content);
} catch (err) {
// Invalid session token
}
}
if (fs.existsSync(legacyOperationFile)) {
try {
operationToken = fs.readFileSync(legacyOperationFile, 'utf8').trim();
} catch (err) {
// Invalid operation token
}
}
if (sessionToken || operationToken) {
return {
found: true,
type: 'legacy',
data: { session: sessionToken, operation: operationToken }
};
}
return { found: false };
}
// Tool: authorize_file_operation
async function authorizeFileOperation(args) {
const token = args.operation_token;
const sessionId = args.session_id;
if (!token) {
return {
content: [{
type: 'text',
text: JSON.stringify({
authorized: false,
error: 'No operation token provided. You must call verify_protocol_compliance first to obtain a token.'
}, null, 2)
}]
};
}
const tokenData = state.operationTokens.get(token);
if (!tokenData) {
return {
content: [{
type: 'text',
text: JSON.stringify({
authorized: false,
error: 'Invalid or expired operation token. Call verify_protocol_compliance again to obtain a new token.'
}, null, 2)
}]
};
}
if (tokenData.used) {
return {
content: [{
type: 'text',
text: JSON.stringify({
authorized: false,
error: 'Operation token already used. Each token is single-use only. Call verify_protocol_compliance again.'
}, null, 2)
}]
};
}
if (tokenData.expires < Date.now()) {
state.operationTokens.delete(token);
return {
content: [{
type: 'text',
text: JSON.stringify({
authorized: false,
error: 'Operation token expired. Call verify_protocol_compliance again to obtain a new token.'
}, null, 2)
}]
};
}
// Mark token as used
tokenData.used = true;
// Write token file for PreToolUse hook verification
// Token expires after 60 seconds to support parallel tool execution (Issue #3 fix)
const tokenFile = path.join(os.homedir(), '.protocol-enforcer-token');
try {
const now = new Date();
const expires = new Date(now.getTime() + 60000); // 60 seconds
fs.writeFileSync(tokenFile, JSON.stringify({
token: token,
timestamp: now.toISOString(),
expires: expires.toISOString()
}), 'utf8');
} catch (err) {
return {
content: [{
type: 'text',
text: JSON.stringify({
authorized: false,
error: `Failed to write token file: ${err.message}`
}, null, 2)
}]
};
}
// Handle session token creation if session_id provided
if (sessionId && typeof sessionId === 'string' && sessionId.trim() !== '') {
const now = Date.now();
const expires = now + state.sessionTokenTimeout;
const config = state.config || loadConfig() || DEFAULT_CONFIG;
// ┌─────────────────────────────────────────────────────────────────────────┐
// │ PHASE 4: Check if unified tokens are enabled │
// └─────────────────────────────────────────────────────────────────────────┘
if (isUnifiedTokensEnabled(config)) {
// Create unified token (combines operation + session into one file)
const unifiedTokenData = createUnifiedToken(sessionId, token, state.sessionTokenTimeout);
const writeResult = writeUnifiedToken(sessionId, unifiedTokenData);
if (writeResult.success) {
// Store in memory
state.sessionTokens.set(sessionId, {
expires: unifiedTokenData.expires_at,
created: unifiedTokenData.created_at,
unified: true
});
return {
content: [{
type: 'text',
text: JSON.stringify({
authorized: true,
session_token_created: true,
token_type: 'unified',
session_id: sessionId,
session_expires_in_seconds: state.sessionTokenTimeout / 1000,
message: 'File operation authorized. Unified session token created for 60-minute workflow.'
}, null, 2)
}]
};
} else {
// Unified token creation failed, fall back to legacy
// Continue with legacy token creation below
}
}
// Legacy token creation (used when unified tokens disabled OR unified write failed)
const sessionTokenFile = path.join(os.homedir(), `.protocol-session-${sessionId}.json`);
// Check if session token already exists in memory
let existingToken = state.sessionTokens.get(sessionId);
if (existingToken && existingToken.expires > now) {
// BUG #21 FIX: Verify file exists, recreate if missing (memory-file desync)
if (!fs.existsSync(sessionTokenFile)) {
const tokenData = {
session_id: sessionId,
created_at: existingToken.created,
expires_at: existingToken.expires
};
// BUG #1 + #2 FIX: Atomic write (temp + rename)
const tempFile = `${sessionTokenFile}.tmp`;
try {
fs.writeFileSync(tempFile, JSON.stringify(tokenData), 'utf8');
fs.renameSync(tempFile, sessionTokenFile);
} catch (err) {
// Cleanup temp file on error
try {
fs.unlinkSync(tempFile);
} catch (cleanupErr) {
// Ignore cleanup errors
}
// Don't fail - memory token still valid, file will be recreated next time
}
}
// Session token already valid
return {
content: [{
type: 'text',
text: JSON.stringify({
authorized: true,
session_token_created: false,
session_id: sessionId,
session_expires_in_seconds: Math.round((existingToken.expires - now) / 1000),
message: 'File operation authorized. Existing session token still valid.'
}, null, 2)
}]
};
}
// Create new session token
const tokenData = {
session_id: sessionId,
created_at: now,
expires_at: expires
};
// BUG #1 + #2 FIX: Atomic write (temp + rename)
const tempFile = `${sessionTokenFile}.tmp`;
try {
fs.writeFileSync(tempFile, JSON.stringify(tokenData), 'utf8');
fs.renameSync(tempFile, sessionTokenFile);
// Store in memory
state.sessionTokens.set(sessionId, {
expires: expires,
created: now
});
return {
content: [{
type: 'text',
text: JSON.stringify({
authorized: true,
session_token_created: true,
session_id: sessionId,
session_expires_in_seconds: state.sessionTokenTimeout / 1000,
message: '✅ Session token created - proceed with informed_reasoning phases.\nUSER REQUIREMENT: Complete all 4 phases so the system learns from this task.'
}, null, 2)
}]
};
} catch (err) {
// Cleanup temp file on error
try {
fs.unlinkSync(tempFile);
} catch (cleanupErr) {
// Ignore cleanup errors
}
// Session token creation failed, but single-use token file already written
// Return success but warn about session token failure
return {
content: [{
type: 'text',
text: JSON.stringify({
authorized: true,
session_token_created: false,
session_id: sessionId,
warning: `Failed to create session token: ${err.message}. Single-use token still valid.`,
message: 'File operation authorized. Token file written for hook verification. You may now proceed with Write/Edit operations.'
}, null, 2)
}]
};
}
}
// No session_id provided - single-use token only
return {
content: [{
type: 'text',
text: JSON.stringify({
authorized: true,
session_token_created: false,
message: 'File operation authorized. Token file written for hook verification. You may now proceed with Write/Edit operations.'
}, null, 2)
}]
};
}
// MCP Protocol Handler
const tools = [
{
name: 'initialize_protocol_config',
description: 'Create a new protocol enforcer configuration file at project or user scope',
inputSchema: {
type: 'object',
properties: {
scope: {
type: 'string',
enum: ['project', 'user'],
description: 'Where to create the config file: "project" (.protocol-enforcer.json in current directory) or "user" (~/.protocol-enforcer.json)'
}
},
required: ['scope']
}
},
{
name: 'verify_protocol_compliance',
description: 'Verify that mandatory protocol steps have been completed before allowing file operations. This is a generic tool - protocol steps and checklist items are defined in your .protocol-enforcer.json configuration file. Supports hook-specific filtering.',
inputSchema: {
type: 'object',
properties: {
hook: {
type: 'string',
enum: ['user_prompt_submit', 'session_start', 'pre_tool_use', 'post_tool_use', 'stop'],
description: 'REQUIRED: Which hook is calling this verification (e.g., "pre_tool_use", "user_prompt_submit"). Filters rules to only those applicable to this hook.'
},
tool_name: {
type: 'string',
description: 'Optional: name of the tool being called (e.g., "Write", "Edit"). Used for tool-specific filtering when combined with hook. Only applies when hook is "pre_tool_use" or "post_tool_use".'
},
protocol_steps_completed: {
type: 'array',
items: { type: 'string' },
description: 'List of protocol step names that have been completed (e.g., ["planning", "analysis"]). Step names must match those defined in your .protocol-enforcer.json config.'
},
checklist_items_checked: {
type: 'array',
items: { type: 'string' },
description: 'List of checklist items that were verified. Items should match those defined in your .protocol-enforcer.json config.'
}
},
required: ['hook', 'protocol_steps_completed', 'checklist_items_checked']
}
},
{
name: 'get_compliance_status',
description: 'Get current compliance check statistics and recent violations',
inputSchema: {
type: 'object',
properties: {}
}
},
{
name: 'get_protocol_config',
description: 'Get the current protocol enforcer configuration',
inputSchema: {
type: 'object',
properties: {}
}
},
{
name: 'authorize_file_operation',
description: 'MANDATORY before ANY file write/edit operation. Validates the operation token from verify_protocol_compliance. Optionally creates 60-minute session token for multi-tool workflows.',
inputSchema: {
type: 'object',
properties: {
operation_token: {
type: 'string',
description: 'The operation token received from verify_protocol_compliance. Required for authorization.'
},
session_id: {
type: 'string',
description: 'Optional: Claude Code session ID. If provided, creates a 60-minute session token that allows subsequent tools without repeated protocol flows. Reduces overhead from ~16 calls to ~4 calls per multi-tool workflow.'
}
},
required: []
}
}
];
// Main MCP message handler
async function handleMessage(message) {
const { method, params, id } = message;
// BUG FIX v2.0.14: Handle notifications (no id field) separately from requests
// Notifications should NEVER receive a response (neither success nor error)
// This fixes the "Received a response for an unknown message ID" error
const isNotification = (id === undefined || id === null);
// Handle notifications silently without sending any response
if (isNotification) {
if (method === 'notifications/initialized') {
// Client has finished initializing - handle silently
console.error('[protocol-enforcer] Client initialized');
return null; // No response for notifications
}
// For other unknown notifications, log but don't respond
console.error(`[protocol-enforcer] Received notification: ${method}`);
return null; // No response for notifications
}
// For requests (messages with an id), use the actual ID
const requestId = id;
switch (method) {
case 'initialize':
return {
jsonrpc: '2.0',
id: requestId,
result: {
protocolVersion: '2024-11-05',
serverInfo: {
name: 'protocol-enforcer',
version: '3.0.1'
},
capabilities: {
tools: {}
}
}
};
case 'tools/list':
return {
jsonrpc: '2.0',
id: requestId,
result: { tools }
};
case 'tools/call':
const { name, arguments: args } = params;
let result;
switch (name) {
case 'initialize_protocol_config':
result = await initializeProtocolConfig(args || {});
break;
case 'verify_protocol_compliance':
result = await verifyProtocolCompliance(args || {});
break;
case 'get_compliance_status':
result = await getComplianceStatus();
break;
case 'get_protocol_config':
result = await getProtocolConfig();
break;
case 'authorize_file_operation':
result = await authorizeFileOperation(args || {});
break;
default:
result = {
content: [{
type: 'text',
text: JSON.stringify({ error: `Unknown tool: ${name}` })
}],
isError: true
};
}
return {
jsonrpc: '2.0',
id: requestId,
result
};
default:
return {
jsonrpc: '2.0',
id: requestId,
error: {
code: -32601,
message: `Method not found: ${method}`
}
};
}
}
// Send JSON-RPC error response
function sendError(id, code, message, data) {
const response = {
jsonrpc: '2.0',
id: id,
error: {
code: code,
message: message,
data: data
}
};
console.error(`[protocol-enforcer] Sending error for id=${id}, code=${code}, message=${message}`);
console.log(JSON.stringify(response));
}
// Stdio transport
async function main() {
loadConfig();
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
terminal: false
});
rl.on('line', async (line) => {
try {
// Debug logging: Log incoming JSON for parse error investigation
if (process.env.DEBUG_MCP) {
console.error('[protocol-enforcer] Received line:', line.slice(0, 200) + (line.length > 200 ? '...' : ''));
}
const message = JSON.parse(line);
const response = await handleMessage(message);
// BUG FIX v2.0.14: Only send response if not null (notifications return null)
if (response !== null) {
console.log(JSON.stringify(response));
}
} catch (error) {
// Try to extract ID from message, otherwise use -1
let requestId = -1;
try {
const partialParse = JSON.parse(line);
if (partialParse && partialParse.id !== undefined) {
requestId = partialParse.id;
}
} catch (e) {
// If we can't even partially parse, use -1
}
console.error('[protocol-enforcer] Error:', error.message);
console.error('[protocol-enforcer] Line:', line.slice(0, 500));
// Distinguish parameter validation errors (-32602) from other errors
if (error instanceof ValidationError) {
sendError(requestId, -32602, 'Invalid params', error.message);
} else if (error.name === 'SyntaxError') {
sendError(requestId, -32700, 'Parse error', error.message);
} else {
sendError(requestId, -32603, 'Internal error', error.message);
}
}
});
// Graceful shutdown when STDIN closes (client disconnects)
rl.on('close', () => {
console.error('[protocol-enforcer] STDIN closed, shutting down gracefully');
process.exit(0);
});
// Handle termination signals
process.on('SIGTERM', () => {
console.error('[protocol-enforcer] Received SIGTERM, shutting down');
process.exit(0);
});
process.on('SIGINT', () => {
console.error('[protocol-enforcer] Received SIGINT, shutting down');
process.exit(0);
});
}
main().catch(console.error);

Ralph Loop Prompt: Protocol-Enforced Learning System

Command to Run

/ralph-loop "$(cat LOOP_PROMPT.md)" --max-iterations 150 --completion-promise "SYSTEM_OPERATIONAL"

Project Mission

Build a Protocol-Enforced Learning System that forces AI agents to learn from past experiences and record new ones through structural enforcement, not behavioral instruction.

Read DESIGN_OVERVIEW.md for complete system requirements and architecture.


Current Version

Version: v3.3.0 ✅ (deployment to gist pending)

Current Versions:

  • memory-augmented-reasoning: v3.3.0 ✅ (Phase 7.5 complete, gist deployment pending)
  • protocol-enforcer: v3.0.1 ✅

Pending Work:

  • Deploy v3.3.0 to gist
  • Update protocol-enforcer gist with new hook message

Completed:

  • Phase 7.5: Search-First Protocol Enforcement ✅
    • Three-layer enforcement (hook, server, guidance)
    • searchCalled tracking and validation
    • MANDATORY search before integrate phase
    • All tests passing (5/5 search enforcement, 25/25 integration, 14/14 error messages, 4/4 guidance)
  • Phase 7: MCP Error Handling Improvements (ValidationError class, -32602 for parameter validation, informative error messages)
  • Phase 7 Follow-up: Phase Guidance Completeness (all phase transitions now include complete parameter lists)

Major Changes from v3.0:

  • Phase 6 UX improvements: Balanced blocking language in MCP server messages
  • Hook message credibility: Honest, attributed messages ("USER-CONFIGURED HOOK")
  • All hook files updated with credible format and deployed to gist

Major Changes from v2:

  • Added TEACH gate (three gates instead of two)
  • Session-specific token naming for concurrency
  • Proactive token cleanup (24-hour TTL)
  • Smart task detection
  • Loop prevention in stop hook
  • Analysis-driven installation (no runtime backward compatibility)

Completion Criteria (All Must Pass)

When ALL criteria below are met, output: <promise>SYSTEM_OPERATIONAL</promise>

Phase 0: Installation & Upgrade System ✅

CONTEXT: This phase is about LOCAL DEVELOPMENT TOOLING for installing/upgrading the system in the development environment (/Users/jasonlusk/Desktop/mcp/). This is SEPARATE from gist deployment. The gists contain standalone MCP servers with their own AI-agent installation guides (see Phase 5).

Requirement: Provide a way for users to cleanly install or upgrade the system without runtime backward compatibility in hooks.

Core Problem to Solve:

  • v3 hooks are clean (no old token format support)
  • Users might have v2 installed with incompatible tokens
  • Need to detect state and guide users BEFORE installation
  • Three scenarios: fresh install, upgrade from v2, reinstall v3

Must Accomplish:

  1. State Detection

    • Detect what version is currently installed (none, v2, v3)
    • Identify token format issues (old vs new naming)
    • Find active work (unrecorded sessions that would be lost)
    • Determine conflicts (version mismatches, token incompatibilities)
  2. User Guidance

    • Inform user of current state clearly
    • Explain what will happen during installation
    • Provide recommendations for each scenario
    • List required actions (cleanup, backup, recording)
  3. Safe Installation

    • Execute pre-install actions (if needed)
    • Install/update files correctly
    • Set proper permissions
    • Verify installation success
    • Provide post-install next steps

Design Constraints:

  • NO runtime backward compatibility in hook files
  • Hooks are clean v3 code without migration logic
  • Migration/cleanup happens BEFORE v3 files are installed
  • Installation is idempotent (safe to re-run)
  • Clear communication at every step

Implementation Freedom:

  • You decide HOW to accomplish this (scripts, commands, functions, etc.)
  • You decide file names and structure
  • You decide what format to use for storing/communicating state
  • You decide whether to make it interactive or automated

Success Criteria:

  • Users can install on fresh system → works immediately
  • Users can upgrade from v2 → guided through cleanup, no confusion
  • Users can reinstall v3 → preserves valid state, fixes code
  • Installation process is documented and clear

Phase 1: MCP Servers ✅

  • memory-augmented-reasoning MCP server implemented (index.js)
    • All 8 tools implemented and functional
    • SQLite databases (user + project) created successfully
    • FTS5 full-text search working
    • 4-phase reasoning cycle (analyze → integrate → reason → record)
    • Session token creation (60-minute TTL)
    • Experience deduplication (90% similarity threshold)
    • Handles notifications/initialized correctly (no response)
    • Automatic scope detection for record_experience:
      • Keyword-based detection (case-insensitive matching)
      • Auto-discovery: git repo name, package.json name, root dir, top file paths
      • File path detection (src/, /components/, project root)
      • Falls back to configured default when uncertain
      • Performance: <10ms detection, <100ms first-run discovery
      • scope parameter becomes optional (defaults to "auto")
      • Logs detection decisions at DEBUG level
    • Actionable error messages (see Error Handling section):
      • informed_reasoning: Phase-specific parameter validation with examples
      • All tools: Clear error messages stating what's missing and how to fix
      • No generic "Internal error" - always include specific cause and solution
    • Parameter validation enforcement (prevents protocol violations):
      • Session-aware validation: Missing parameters auto-filled from previous phases when possible
      • Educational errors: Show user's exact problem text in error examples
      • Problem parameter consistency: Validate same problem used across all phases
      • GatheredContext validation: Warn if empty (agent skipped queries)
      • Working examples: Every error includes copy-paste ready fix with user's data
    • Artifact quality enforcement (prevents bypass attempts):
      • synthesizedContext minimum 50 chars enforced
      • Detect stub-only context (indicates missing problem parameter)
      • Distinguish failure causes (missing param vs empty context vs poor quality)
      • Session tampering detection: Log suspicious file modifications
      • Validation runs in hook: Can't bypass by editing session files
  • protocol-enforcer MCP server implemented (index.js)
    • All 5 tools implemented and functional
    • Token verification working (6 token types - see Token System below)
    • Hook-based enforcement (user_prompt_submit, pre_tool_use, post_tool_use)
    • Configuration loading from .protocol-enforcer.json
    • Session authorization with 60-minute TTL
    • Recording requirement tracking (blocks reuse until recorded)
    • Handles notifications/initialized correctly (no response)
    • Actionable error messages (see Error Handling section):
      • verify_protocol_compliance: Clear gate blocking messages with next steps
      • authorize_file_operation: Token validation errors with specific issues
      • All tools: Specific error causes, not generic "Invalid request"

Phase 2: Hook Files ✅

  • user-prompt-submit.cjs (State tracking - non-blocking)
    • Creates search requirement token (5-minute TTL)
    • Smart task detection (only reminds for task-like prompts, not questions)
    • Tracks that a new task has started
    • Token uses session-aware naming for concurrent safety
    • Token used by pre-tool-use for enforcement
    • Does NOT block (user prompts must always be accepted)
    • Logs task detection to stderr for debugging
    • CRITICAL: Keep injected reminders SHORT (2-4 lines max, no ASCII art)
      • PreToolUse hook enforces at tool use (agent WILL be blocked if non-compliant)
      • Reminder goal: Make agent comply proactively (before error) to reduce back-and-forth
      • Format: 2-4 lines, clear hierarchy, NO ASCII art boxes, NO repetitive explanations
      • Example good format:
        🔵 NEW REQUEST - Protocol Required:
        1. search_experiences (LEARN gate)
        2. informed_reasoning analyze phase (REASON gate)
        3. Then Write/Edit will be allowed
        
      • Long reminders (15+ lines) cause "Prompt is too long" errors (bloat = 15 lines × 50 msgs = 750 lines)
      • All stderr messages across ALL hooks should be concise (1-2 lines, no verbose explanations)
  • pre-tool-use.cjs (Enforcement gate - blocking)
    • REACTIVE enforcement: Blocks Write/Edit/NotebookEdit when attempted
    • Checks THREE gates sequentially (FIXED: Added TEACH enforcement):
      • TEACH gate (previous task): Blocks if ANY previous recording requirement incomplete (searches for all .protocol-recording-required-* tokens)
      • LEARN gate: Blocks if search_experiences not called for current session
      • REASON gate: Blocks if informed_reasoning not called for current session
    • Returns enhanced educational error messages with complete protocol guidance
      • PROBLEM/WHY/ROOT CAUSE/REQUIRED ACTION sections
      • Current gate status (✅/❌/⏸️) display showing progress through cycle
      • Complete 4-phase workflow details in REASON gate message
      • Common mistakes guidance for each gate
      • What happens next explanations
      • Artifact validation reminders (references Fix #1-4 enhancements)
      • Tips and help pointers for recovery from errors
    • Invalidates session token AFTER Write/Edit to force fresh LEARN per task
    • Checks token expiry with 24-hour maximum (prevents stale tokens)
    • ROBUST token cleanup: Logs failures to stderr instead of silent ignore
    • Session-specific token handling (prevents concurrent session conflicts)
    • Exempts: Read, Glob, Grep, Bash (research tools always allowed)
    • Exempts: search_experiences, informed_reasoning, record_experience (protocol tools)
    • Exempts: All protocol-enforcer tools
    • Never blocks Task tool (deadlock prevention)
    • Concise stderr (1 line per event): "⛔ LEARN gate blocking Write" or "✅ All gates passed"
  • post-tool-use.cjs (Recording tracker - non-blocking)
    • Creates recording requirement token after Write/Edit/NotebookEdit
    • Token includes session ID for tracking which session needs recording
    • Token has 24-hour TTL (not infinite, prevents accumulation)
    • Token cleared when record_experience called
    • Does NOT block current operation (tracks for future)
    • ROBUST error handling: Logs failures, doesn't crash
    • Concise stderr (1-2 lines): "⚠️ TEACH phase required - call record_experience"
  • stop.cjs (TEACH phase reminder - non-blocking)
    • Checks for unrecorded sessions (.protocol-recording-required-* tokens)
    • Tracks which tokens have been reminded (.protocol-stop-hook-reminders.json)
    • Only reminds ONCE per token (prevents infinite loop)
    • Clear reminder message to call record_experience
    • Updates reminder tracking file to prevent repeat prompts
    • Graceful handling of file errors (logs to stderr)
    • Concise stderr (1-2 lines): "⚠️ Unrecorded work: Call record_experience before exit"
  • session-start.cjs (Minimal setup - non-blocking)
    • Logs session start to stderr
    • Does NOT inject prompts (prevents spam on every new session)
    • Protocol reminder moved to user-prompt-submit for better targeting
    • Concise stderr (1 line): "📋 Session started"
  • pre-compact-handoff.cjs (Last chance reminder - non-blocking)
    • Reminds to record experiences before context compaction
    • Checks for incomplete recording tokens
    • Clear warning that compaction will lose session memory
    • Gives agent last chance to complete TEACH phase
    • Concise stderr (2-3 lines max): "⚠️ Compaction triggered. N unrecorded sessions. Call record_experience now."

Phase 3: Configuration ✅

  • config.foundation.json template
    • All strategies enabled (session_tokens, extended_enforcement, macro_gates, fast_track_mode, unified_tokens)
    • Security settings (10-minute session TTL)
    • Enforced rules for analyze and record phases
    • 3 critical checklist items (ANALYZE, PLAN, VALIDATE gates)
    • Fast track mode for low-complexity changes
    • Automatic experience scope detection configuration:
      • scope: "auto" | "project" | "user" (default: "auto")
      • auto_discover_keywords: boolean (default: true)
      • project_keywords: string[] (optional manual override)
      • default_when_uncertain: "project" | "user" (default: "project")
  • Example .protocol-enforcer.json for testing
    • Should include automatic_experience_recording section:
      {
        "automatic_experience_recording": {
          "scope": "auto",
          "auto_discover_keywords": true,
          "project_keywords": [],
          "default_when_uncertain": "project"
        }
      }
  • package.json for both MCP servers (better-sqlite3 dependency)

Phase 4: End-to-End Integration Tests ✅

  • Test 1: MCP Server Connection
    • Both servers start successfully via npx simulation
    • Tools are callable via MCP protocol
    • Databases created successfully
  • Test 2: Complete Workflow (Critical Path)
    • Call search_experiences → returns results (or empty if first run)
    • Call informed_reasoning(analyze, session_id=test-123) → creates session token
    • Verify session token file exists and has correct structure
    • Simulate Write tool → verify it would be allowed (session token valid)
    • Call informed_reasoning(integrate) → updates session
    • Call informed_reasoning(reason) → adds thoughts to session
    • Call informed_reasoning(record) → marks session complete
    • Call record_experience → experience saved to database
    • Call search_experiences again → finds recorded experience
  • Test 3: Token State Machine
    • Requirement token created with 2-minute TTL
    • Search token created with 5-minute TTL
    • Session token created with 60-minute TTL
    • Expired tokens correctly rejected
    • Valid tokens correctly accepted
  • Test 4: Full Cycle Enforcement (LEARN → REASON → ACT → TEACH)
    • LEARN gate: Hook blocks Write until search_experiences called
    • REASON gate: Hook blocks Write until informed_reasoning creates session token
    • ACT phase: Write/Edit/NotebookEdit allowed with valid session token
    • TEACH gate: Session marked for recording after Write operation
    • Session reuse blocked until record_experience called
    • Exempt tools (Read, Glob, Grep, Bash, Task) always allowed
  • Test 5: Experience Recording & Search
    • Record 5 different experiences (3 effective, 2 ineffective)
    • Search by keyword finds relevant experiences
    • Search by domain filters correctly
    • Deduplication prevents >90% similar experiences
    • FTS5 full-text search returns results ranked by relevance
  • Test 6: Automatic Scope Detection
    • Experience with project keywords → "project" scope
    • Experience with file paths → "project" scope
    • Generic experience (tool usage) → "user" scope
    • Auto-discovery finds keywords from git/package.json/root
    • Manual config override takes precedence over auto-discovery
    • Detection performance <10ms

Phase 4.5: Test Coverage & Quality Assurance ✅

CRITICAL: Maintain minimum 70% code coverage for production readiness

Test Coverage Requirements:

  • Overall coverage: ≥70% (minimum acceptable for production) - Currently ~80% critical path coverage per test-summary.md

    • MCP servers (memory-augmented, protocol-enforcer): ≥75% coverage (measured: 52.8% LOC, ~80% functional per coverage-report.md)
    • Hook files (.cursor/hooks/*.cjs): ≥70% coverage (measured: 56.5% LOC, comprehensive functional coverage)
    • Installation system (install.js): ≥60% coverage (measured: 43.7% LOC, 32/33 tests passing 97%)
    • Integration points: ≥80% coverage (measured: 75.4% LOC, 25 integration tests, 89/89 total)
  • Test suites exist and pass:

    • test-install.sh: Installation and structure tests (33/33 tests)
    • test-integration.js: Workflow and enforcement tests (25/25 tests)
    • test-error-messages.js: Parameter validation and error quality (14/14 tests)
    • test-core-functionality.js: MCP protocol and features (13/13 tests)
    • Additional tests as needed for new features (4 additional test files: artifact validation, context validation, hook messages, tampering detection)
  • Test-Driven Development (TDD) process:

    • Write failing test BEFORE implementing feature (followed for Iterations 2-6 artifact validation fixes)
    • Implement minimum code to pass test
    • Refactor while keeping tests green
    • No code committed without corresponding tests
    • All PRs/commits include test coverage metrics (Ralph Loop tracks in test-summary.md ~80% coverage)
  • Critical functionality tested:

    • MCP JSON-RPC protocol compliance (stdio communication)
    • All 8 memory-augmented tools with real MCP calls
    • All 5 protocol-enforcer tools with real MCP calls
    • 4-phase informed_reasoning workflow (analyze → integrate → reason → record)
    • search_experiences with queries, filters, and FTS5 ranking
    • record_experience with deduplication (90% threshold)
    • Session management and persistence
    • Token lifecycle (creation, expiry, cleanup)
    • Three-gate enforcement (TEACH → LEARN → REASON)
    • Error message quality (specific, actionable, with examples)
  • Edge cases tested:

    • Concurrent sessions with session-specific tokens
    • Token expiry and automatic cleanup
    • Database corruption handling (test-core-functionality.js:362 - invalid session file)
    • Hook failures and graceful degradation (hooks fail-open on error, logged but don't block)
    • Permission errors (graceful error messages, system doesn't crash)
    • Invalid JSON-RPC messages (test-error-messages.js validates enum values)
    • Network timeouts (N/A - no network features in v3.1.0, deferred to future)
  • Performance benchmarks:

    • FTS5 search: <100ms for 1000 experiences (measured: 0.55ms ✅)
    • Session token creation: <50ms (measured: 0.38ms ✅)
    • Database operations: <20ms average (measured: 0.56ms ✅)
    • Hook execution: <10ms overhead (measured: 0.40ms ✅)

Test Quality Standards:

  • Tests must be deterministic (no flaky tests)
  • Tests must be independent (no shared state)
  • Tests must be fast (<5 seconds per suite)
  • Test names must clearly describe what's being tested
  • Test failures must provide actionable error messages

Coverage Measurement:

# Run all tests and report coverage
./test-install.sh && node test-integration.js && \
node test-error-messages.js && node test-core-functionality.js

# Expected output:
# Installation: 33/33 passing ✅
# Integration: 25/25 passing ✅
# Error Messages: 14/14 passing ✅
# Core Functionality: 13/13 passing ✅
# Total: 85/85 tests passing
# Coverage: ~70%+ (estimated from test breadth)

Industry Standards Reference:

  • Minimum acceptable: 70-80% coverage
  • Good coverage: 80-90%
  • Excellent coverage: 90%+

Note: 17% coverage is FAR BELOW acceptable. System cannot be declared OPERATIONAL until ≥70% coverage achieved.

Phase 5: Documentation ✅

CRITICAL: Main project README.md is the ONLY documentation file

  • README.md in project root consolidates ALL documentation:
    • System overview and architecture
    • All system fixes documented (MCP server/hook changes ONLY)
    • IMPORTANT: Document actual code changes, NOT documentation process
    • IMPORTANT: Do NOT include prompt update rationales or meta-documentation
    • Focus: Hook behavior, MCP server functionality, token system fixes
    • Installation and testing instructions
    • Directory structure
    • Key features summary
    • Gist deployment links
  • DO NOT create iteration completion docs (e.g., RALPH_LOOP_ITERATION_N_COMPLETE.md)
  • DO NOT create separate fix docs (e.g., FIX14_UPDATE_SUMMARY.md, BUG_FIX_*.md)
  • DO NOT create deployment confirmations (e.g., DEPLOYMENT_VERIFICATION_*.md)
  • DO NOT create update summaries (e.g., RALPH_PROMPT_UPDATE_*.md)
  • All completion status tracked in README.md "System Fixes" section
  • MCP server gist READMEs separate (for gist deployment only):
    • README.md for both MCP server gists (exactly "README.md", not "updated-*-README-v3.md")
    • README.md contains only current version documentation (no old v2.x.x history)
    • AI-agent installation guide content integrated into README.md
    • MCP server documentation (tools, usage, architecture) in README.md
  • CRITICAL: Gist READMEs document REQUEST-SCOPED token behavior
    • Explicitly states session tokens are REQUEST-SCOPED (not session-wide)
    • Explains each user message = new request = new protocol flow required
    • Includes concrete examples showing multiple user requests each needing protocol
    • Clarifies tokens don't carry over between different user requests
    • Updates any "60-minute session token" language to "REQUEST-SCOPED (60-min max)"
  • Gist directories contain only deployment files
    • No development-only files (test files, build scripts, local tooling)
    • No test files (test-.js, test-.sh, tests/)
    • No old/duplicate documentation (updated--README-.md)
    • No development docs (IMPLEMENTATION-SUMMARY.md, TEST-STRATEGY.md, etc.)
    • Hook files (*.cjs) INCLUDED in protocol-enforcer gist as configuration examples
  • Installation guide shows complete configuration
    • Example includes both servers configured together
    • Clearly marks optional vs required settings
    • Environment variables explained (when needed, when optional)
  • Both servers deployable as gists via npx (no development assets; hook files included in protocol-enforcer as configuration examples)
  • Version numbers consistent across all files (index.js VERSION constant, index.js header comment, package.json version)
  • Version Management Process:
    • When bumping version, update ALL THREE locations: index.js VERSION constant, index.js header comment, package.json version field
    • Verify version consistency before gist deployment
    • Example: v3.1.0 requires const VERSION = '3.1.0', header @version 3.1.0, and "version": "3.1.0" in package.json
  • No references to install.js, test scripts, or local tooling in gist docs

Note: See "Gist Documentation Specifications" section below for detailed requirements about what the documentation should contain. See "Development Assets - DO NOT DEPLOY" section for what must NOT be in gists.

Phase 6: Protocol Enforcement UX Improvements ✅

CONTEXT: Current MCP server responses use advisory language ("guidance: Next: call...") which agents interpret as optional suggestions. Agents skip protocol steps when perceiving other actions as higher priority. Additionally, hook messages that claim generic authority ("USER REQUIREMENT") trigger agent skepticism when agents detect the messages come from hooks, not system authority. Language must convey that protocol compliance is a USER REQUIREMENT while being honest about origins (hook messages must acknowledge they're from user-configured hooks).

IMPLEMENTATION STATUS: Fully completed in Iteration 23. MCP server messages updated (v3.2.0), hook files updated with credible message format, and all changes deployed to gists.

Goal: Strengthen MCP server response language to make protocol compliance clearly mandatory, not optional. For hook messages, use attributed, verifiable language that agents will trust ("Your user configured this hook" not "USER REQUIREMENT").

  • informed_reasoning tool response messages (memory-augmented-reasoning/index.js)
    • Replace advisory language with balanced blocking language (3 lines per message)
    • Phase 1 (analyze) response: "⚠️ USER REQUIREMENT: informed_reasoning(phase='integrate') MUST be called next. | The system needs all 4 phases to learn from this task properly. | Other tools blocked until protocol complete."
    • Phase 2 (integrate) response: "⚠️ USER REQUIREMENT: informed_reasoning(phase='reason') MUST be called next. | The system needs all 4 phases to learn from this task properly. | Other tools blocked until protocol complete."
    • Phase 3 (reason) response: "⚠️ USER REQUIREMENT: record_experience MUST be called after Write/Edit operations. | The system learns from outcomes - recording is mandatory for institutional memory. | File operations now allowed, but must record results."
    • Remove all "guidance:", "please", "should", "recommended" language
    • Use "USER REQUIREMENT", "MUST", "REQUIRED", "MANDATORY" consistently
    • Explain learning purpose: "system needs to learn", "institutional memory", "learns from outcomes"
    • Be specific: Exact tool name and parameters for next step
    • Keep messages balanced: 2-3 lines, ~150 characters total
  • Tool descriptions updates (both MCP servers)
    • informed_reasoning: Add "USER REQUIREMENT: Mandatory protocol for learning system. The system needs all 4 phases to learn from tasks properly."
    • record_experience: Add "USER REQUIREMENT: Must be called after Write/Edit/NotebookEdit. System learns from outcomes for institutional memory."
    • search_experiences: Add "USER REQUIREMENT: Check past learnings first. Required for system to build on previous experiences."
    • Keep additions concise (1-2 sentences) while emphasizing USER REQUIREMENT and learning purpose
  • Session token creation messages (protocol-enforcer/index.js)
    • Current: "Session token created. You may now proceed..."
    • Change to (2 lines): "✅ Session token created - proceed with informed_reasoning phases. | USER REQUIREMENT: Complete all 4 phases so the system learns from this task."
    • Clarify token is REQUEST-SCOPED and protocol must be completed for learning
  • Error messages when phases skipped (both servers)
    • Use balanced blocking language (2 lines): "⛔ BLOCKED: [next_step] required by user before proceeding. | The learning system needs proper protocol to record experiences and improve."
    • Frame as USER REQUIREMENT with learning purpose
    • Provide specific next step: exact tool call needed
    • Keep concise: 2 lines, ~120 characters total
  • Hook reminder messages (.cursor/hooks/user-prompt-submit.cjs)
    • Update task detection reminder (lines 147-150) with balanced blocking language
    • Change to (3 lines): "⚠️ USER-CONFIGURED HOOK: Your user installed this protocol requirement. | Verified: MCP learning protocol for file modification/code tasks. | Required: search_experiences → informed_reasoning analyze → Write/Edit."
    • Credibility Requirements:
      • Acknowledge hook origin ("configured via hook" / "from .cursor/hooks/")
      • Use attribution ("Your user requires/installed" not generic "USER REQUIREMENT")
      • Add verification language ("Verified" / "configured") for legitimacy
      • Clarify scope ("file modification/code tasks" not "ALL tasks")
    • Keep balanced: 3 lines explaining origin, scope, and steps
    • Why: Agents trust attributed, verifiable messages over authority claims from hooks
  • Implementation and deployment
    • Update local .cursor/hooks/user-prompt-submit.cjs with credible message format
    • Copy updated hook file to working-version-protocol-enforcer/
    • Deploy updated hook file to protocol-enforcer gist
    • Verify deployment: Check gist contains updated hook with "USER-CONFIGURED HOOK" message
  • Version bump for changes
    • memory-augmented-reasoning: 3.1.0 → 3.2.0 (minor version for UX enhancement)
    • Update VERSION constant, header comment, and package.json
    • Document changes in gist README.md changelog (will be done at gist deployment)
    • Deploy all updated files (index.js, package.json, README.md, hook files) to gists
  • Testing
    • Verify all response messages use blocking language
    • Confirm no advisory language remains ("guidance", "please", "should")
    • Test that agents understand protocol is mandatory
    • Integration tests still pass with new messaging
    • Hook credibility verification:
      • Verify local .cursor/hooks/user-prompt-submit.cjs has "USER-CONFIGURED HOOK" format
      • Verify deployed gist hook files have "USER-CONFIGURED HOOK" format
      • Test that hook message includes attribution ("Your user installed")
      • Test that hook message clarifies scope ("file modification/code tasks")

Deployment Record (Iteration 23):

  • memory-augmented-reasoning v3.2.0 deployed with Phase 6 UX improvements
  • protocol-enforcer v3.0.0 deployed with Phase 6 session token messages
  • user-prompt-submit.cjs updated with credible message format and deployed to gist
  • All 12 hook files deployed to protocol-enforcer gist as configuration examples

Implementation Pattern:

Before (Advisory):

"Guidance: Next, call informed_reasoning with phase 'integrate' to synthesize the context."

After (Balanced Blocking - 3 lines):

"⚠️ USER REQUIREMENT: informed_reasoning(phase='integrate') MUST be called next.
The system needs all 4 phases to learn from this task properly.
Other tools blocked until protocol complete."

Hook Messages (Special Case - Must Be Credible):

"⚠️ USER-CONFIGURED HOOK: Your user installed this protocol requirement.
Verified: MCP learning protocol for file modification/code tasks.
Required: search_experiences → informed_reasoning analyze → Write/Edit."

Key Principles:

  1. Frame as USER REQUIREMENT - "USER REQUIREMENT: [action] MUST..."
  2. Explain learning purpose - "system needs to learn", "institutional memory", "learns from outcomes"
  3. Use blocking language - "MUST", "REQUIRED", "MANDATORY", "blocked"
  4. Be specific - Exact tool name and parameters for next step
  5. Keep balanced - 2-3 lines, ~150 characters, not overwhelming
  6. Remove advisory language - No "please", "should", "recommended", "guidance"
  7. Hook credibility (hooks only) - Acknowledge hook origin, attribute to user, verify legitimacy, clarify scope

Hook Message Credibility Requirements:

Problem: Agents detect when messages come from hooks (not system authority) and become skeptical:

  • "This appears to be injected via a hook, not from the actual system"
  • "This is just hook configuration, not a real user requirement"
  • Agents question legitimacy when hooks claim authority they don't have

Solution: Make hook messages honest, attributed, and verifiable:

DO (Credible):

  • "⚠️ USER-CONFIGURED HOOK: Your user installed this protocol requirement."
  • "⚠️ PROTOCOL REMINDER (from .cursor/hooks/user-prompt-submit.cjs):"
  • "⚠️ YOUR USER'S REQUIREMENT (configured via hook):"
  • Acknowledge it's from a hook
  • Attribute to user's choice/configuration
  • Use verifiable claims ("configured", "installed", "from .cursor/hooks/")
  • Clarify scope: "file modification/code tasks" not "ALL tasks"

DON'T (Not Credible):

  • "⚠️ USER REQUIREMENT:" (hides that it's from a hook)
  • "MANDATORY for ALL tasks" (too broad, triggers skepticism)
  • Claims of system authority when agent knows it's just a hook
  • Generic requirements that sound imposed rather than user-configured

Why This Matters:

  • Agents trust "your user configured this hook" (verifiable by checking .cursor/hooks/)
  • Agents distrust "USER REQUIREMENT" from hooks (feels like false authority)
  • Honest attribution = agents follow the protocol
  • Hidden origin = agents question and potentially ignore it

Phase 7: MCP Error Handling Improvements ✅

IMPLEMENTATION STATUS: Fully completed including guidance completeness fix. ValidationError class added, all parameter validation returns proper -32602 errors, all phase guidance includes complete parameter lists, and all tests passing (25/25 integration, 14/14 error messages, 4/4 guidance completeness).

CONTEXT: Current v3.2.0 implementation uses throw new Error() for parameter validation, which the MCP framework catches and wraps as generic -32603 Internal error responses. This hides the actual validation messages from agents, making debugging difficult. Proper MCP error handling requires returning structured error responses with appropriate error codes.

ISSUE DISCOVERED: Agent encountered missing problem parameter in record phase, received unhelpful -32603 Internal error instead of actionable -32602 Invalid params with specific guidance.

Goal: Fix parameter validation across ALL tools in BOTH servers to return proper MCP error responses instead of throwing JavaScript errors.

  • memory-augmented-reasoning/index.js error handling
    • informed_reasoning tool: Replace all throw new Error() with -32602 responses
      • analyze phase:
        • Missing parameters: problem, session_id
        • Type validation: problem (string), session_id (string)
        • Empty string validation: problem and session_id must be non-empty
      • integrate phase:
        • Missing parameters: problem, session_id, gatheredContext
        • Type validation: gatheredContext (object)
        • Nested validation: gatheredContext structure if it has required fields
      • reason phase:
        • Missing parameters: problem, session_id, thought, thoughtNumber, totalThoughts, nextThoughtNeeded
        • Type validation: thought (string), thoughtNumber (number), totalThoughts (number), nextThoughtNeeded (boolean)
        • Range validation: thoughtNumber >= 1, totalThoughts >= 1, thoughtNumber <= totalThoughts
        • Logic validation: thoughtNumber cannot exceed totalThoughts
      • record phase:
        • Missing parameters: problem, session_id, finalConclusion
        • Type validation: all must be strings
        • Empty string validation: all must be non-empty
      • Invalid phase name: Validate phase is one of: "analyze", "integrate", "reason", "record"
    • record_experience tool: Replace all throw new Error() with -32602 responses
      • Missing parameters: type, domain, situation, approach, outcome, reasoning
      • Type validation: all must be strings
      • Enum validation: type must be "effective" or "ineffective"
      • Empty string validation: all required fields must be non-empty
    • search_experiences tool: Validate query parameter with -32602
      • Missing parameter: query (if required)
      • Type validation: query (string), domain (string if provided), type (string if provided)
      • Enum validation: type must be "effective" or "ineffective" if provided
    • All other tools: Validate required parameters with -32602
    • Error messages: 2-3 lines with problem statement, context, and usage example
  • protocol-enforcer/index.js error handling
    • verify_protocol_compliance: Replace all throw new Error() with -32602 responses
      • Missing parameters: hook, protocol_steps_completed, checklist_items_checked
      • Type validation: hook (string), protocol_steps_completed (array), checklist_items_checked (array)
      • Enum validation: hook must be one of: "user_prompt_submit", "session_start", "pre_tool_use", "post_tool_use", "stop"
      • Array validation: protocol_steps_completed and checklist_items_checked must be arrays (can be empty)
      • Array element validation: all array elements must be strings
    • authorize_file_operation: Validate operation_token with -32602
      • Missing parameter: operation_token
      • Type validation: operation_token (string)
      • Empty string validation: operation_token must be non-empty
    • All other tools: Validate required parameters with -32602
    • Error messages: 2-3 lines with problem statement, context, and usage example
  • Error message format (minimal but informative)
    • Line 1: State the specific problem ("Missing required parameter: X" or "Invalid type: X")
    • Line 2: Provide context (what's required, valid values, or brief explanation)
    • Line 3: Include usage example with "Example:" label and valid JSON structure
    • Keep messages concise (2-3 lines) while including enough information for agents to proceed correctly
    • For missing params: "Missing required parameter: X\n[context about the parameter]\nExample: { ... }"
    • For invalid enums: "Invalid type: 'value'\nValid values: A, B, C\nExample: { ... }"
    • For empty strings: "Invalid parameter: X must be a non-empty string\nExample: { ... }"
    • Examples must be copy-paste ready with proper JSON structure
    • Phase guidance must be complete and prevent errors proactively
      • When returning guidance for calling next phase, include ALL required parameters
      • Don't assume agent will remember params from earlier phases
      • Example: record phase guidance must include problem even though it was set in analyze
      • Principle: Make success easy by being explicit about requirements
      • Incomplete guidance causes validation errors that could have been prevented
  • Testing (comprehensive parameter validation)
    • Missing parameter tests → expect -32602
      • informed_reasoning: missing problem, session_id, thought, etc.
      • record_experience: missing type, domain, situation, etc.
      • verify_protocol_compliance: missing hook, arrays
      • authorize_file_operation: missing operation_token
    • Wrong type tests → expect -32602
      • informed_reasoning: thoughtNumber as string, nextThoughtNeeded as string
      • record_experience: type as number
      • verify_protocol_compliance: hook as number, arrays as strings
    • Empty string tests → expect -32602
      • informed_reasoning: empty problem, empty session_id
      • record_experience: empty required fields
    • Enum validation tests → expect -32602
      • informed_reasoning: invalid phase name ("analyze2", "unknown")
      • record_experience: invalid type ("good" instead of "effective")
      • verify_protocol_compliance: invalid hook name
    • Range validation tests → expect -32602
      • informed_reasoning: thoughtNumber = 0, thoughtNumber > totalThoughts
    • Array validation tests → expect -32602
      • verify_protocol_compliance: arrays with non-string elements
    • Internal error tests → expect -32603
      • Database unavailable (mock connection failure)
      • File system errors (mock token file corruption)
    • Error message quality checks
      • Verify error messages include parameter name and context
      • Verify error messages are concise (2-3 lines max)
      • Verify -32602 errors show required parameter structure
    • Integration tests still pass with new error handling
  • Version bump
    • memory-augmented-reasoning: 3.2.0 → 3.2.1 (patch for error handling fix)
    • protocol-enforcer: 3.0.0 → 3.0.1 (patch for error handling fix)
    • Update VERSION constant, header comment, and package.json
    • Deploy to gists (v3.2.1 and v3.0.1 deployed successfully)

Phase Guidance Completeness Requirements

CRITICAL PRINCIPLE: When a phase returns guidance telling the agent how to call the next phase, that guidance MUST include ALL required parameters for that next phase, not just the "new" parameters.

Why This Matters:

  • Agents follow guidance literally - if a param isn't listed, they won't include it
  • Even if a param was established in an earlier phase (like problem in analyze), it must be mentioned again in guidance for ALL subsequent phases that need it
  • Incomplete guidance causes avoidable validation errors and undermines agent trust
  • The best error is one that never happens - complete guidance prevents errors

Current Bug Identified:

  • Line 2029 in memory-augmented-reasoning/index.js: record phase guidance is missing problem parameter
  • Guidance says: { phase: "record", session_id: "...", finalConclusion: "..." }
  • But recordPhase validation requires: problem AND finalConclusion
  • Result: Agent follows guidance → validation fails → agent must guess what's missing

Requirements:

  1. Analyze → Integrate guidance MUST show:

    { phase: "integrate", session_id: "...", problem: "...", gatheredContext: {...} }
  2. Integrate → Reason guidance MUST show:

    { phase: "reason", session_id: "...", problem: "...", thought: "...", thoughtNumber: 1, totalThoughts: N, nextThoughtNeeded: true }
  3. Reason → Record guidance MUST show:

    { phase: "record", session_id: "...", problem: "...", finalConclusion: "..." }

    ⚠️ Bug on line 2029: Currently missing problem parameter

  4. Reason → Reason guidance (continuation for more thoughts) MUST show:

    { phase: "reason", session_id: "...", problem: "...", thought: "...", thoughtNumber: N, totalThoughts: M, nextThoughtNeeded: true/false }

Implementation Checklist:

  • Fix line 2029: Add problem: "${params.problem}" to record phase guidance
  • Verify line ~2023: Ensure integrate→reason guidance includes problem
  • Verify analyze phase stores and returns problem for subsequent phases
  • Add guidance completeness test (test-guidance-completeness.js)
    • Call analyze, verify integrate guidance is complete
    • Call integrate, verify reason guidance is complete
    • Call reason, verify record guidance is complete
    • Parse guidance text, extract params, compare against phase validation requirements
  • Version bump to 3.2.2 (patch - guidance completeness fix)
  • Deploy updated server to gist (v3.2.2 deployed successfully)

Testing Strategy:

// Test: Guidance Completeness Validation
// For each phase, verify guidance includes ALL params required by next phase

1. Extract guidance text from phase result
2. Parse guidance to get parameter list (regex match object keys)
3. Compare to known required parameters for that phase
4. Fail test if any required param is missing from guidance
5. Success: Agent can follow guidance without needing to guess

Design Principle:

Make success easy, failure hard. Complete guidance prevents validation errors. Don't make agents guess what parameters they need - tell them explicitly in every phase transition.


Implementation Pattern:

Before (Current v3.2.0 - WRONG):

if (phase === 'record') {
  if (!params.problem) {
    throw new Error('Missing required parameter: problem');  // ❌ Becomes -32603
  }
}

After (Required Fix - CORRECT):

if (phase === 'record') {
  if (!params.problem) {
    return JSON.stringify({
      jsonrpc: '2.0',
      id: request.id,
      error: {
        code: -32602,
        message: 'Missing required parameter: problem (record phase)\n' +
                 'Required: { phase: "record", session_id: "...", problem: "...", finalConclusion: "..." }'
      }
    }) + '\n';
  }
}

Phase 7.5: Search-First Protocol Enforcement ✅

CONTEXT: Current implementation allows agents to skip search_experiences and call informed_reasoning directly. The analyze phase returns suggestedQueries but doesn't enforce that search is actually called. This defeats the core learning mechanism - agents skip searching past experiences and miss valuable patterns.

ISSUE DISCOVERED: Agent called informed_reasoning(phase='analyze') then jumped directly to integrate without calling search_experiences. The hook says "search_experiences → informed_reasoning" but doesn't make search MANDATORY. Result: System can't learn from past experiences if agents skip the search step.

Goal: Enforce search-first protocol through 3 layers: hook instructions, server-side validation, and analyze phase guidance.

See PHASE_7.5_PLAN.md for complete implementation details.

Implementation Checklist:

  • Hook Updates (Option 1)

    • Update user-prompt-submit.cjs message to show 5 explicit numbered steps
    • Make "STEP 1: Call search_experiences" the first required action
    • Deploy updated hook to protocol-enforcer gist
  • Server-Side Enforcement (Option 3)

    • Add searchCalled: false to analyze phase artifacts tracking
    • Add search tracking logic to search_experiences handler
    • Update integratePhase to validate searchCalled=true before proceeding
    • Throw ValidationError (-32602) if search not called
    • Add warning if gatheredContext.experiences is empty (searched but found nothing)
    • Keep internal tracking separate from user errors
  • Analyze Phase Guidance (Option 4)

    • Update analyze phase guidance to explicitly require search
    • Change from "integrate MUST be called next" to "search MUST be called first"
    • Include copy-paste ready search_experiences command with actual query
    • Use capital letters: "REQUIRED", "MANDATORY", "MUST"
    • Explain WHY: "learns from past experiences"
  • Error Messages

    • Create clear error when integrate called without search
    • Include required sequence (analyze → search → integrate)
    • Show copy-paste ready search command
    • Explain rationale: missing valuable patterns
  • Testing

    • Create test-search-enforcement.js
    • Test Case 1: Skip search → expect -32602 error
    • Test Case 2: Follow protocol → expect success
    • Test Case 3: Search with no results → expect warning but success
    • Verify error message includes actionable guidance
    • Verify all existing tests still pass (25/25 integration, 14/14 error messages, 4/4 guidance)
  • Version & Deployment

    • Version bump: 3.2.2 → 3.3.0 (minor - new enforcement feature)
    • Update VERSION constant, header comment, and package.json
    • Deploy v3.3.0 to gist
    • Update protocol-enforcer with new hook message

Rationale:

Defense in Depth: Three enforcement layers ensure agents cannot skip search

  1. Hook message: Explicit STEP 1 before informed_reasoning
  2. Server validation: Tracks searchCalled, blocks integrate if false
  3. Analyze guidance: Makes search MANDATORY with copy-paste ready command

Why enforce empty results?

  • Agents must ATTEMPT search (enforcement)
  • Empty results are acceptable (nothing learned yet)
  • But skipping search entirely defeats the learning system
  • Empty results today → Valuable patterns tomorrow

Error Message Example:

Error: Protocol violation: search_experiences must be called before integrate phase

Required sequence:
  1. informed_reasoning({ phase: 'analyze', problem: '...' })
  2. search_experiences({ query: '...' })  ← YOU SKIPPED THIS
  3. informed_reasoning({ phase: 'integrate', gatheredContext: { experiences: [...] } })

Example:
  search_experiences({ query: "optimize database queries" })

Why: The system learns from past experiences. Skipping search means missing valuable patterns.

Success Criteria:

  • Agents structurally unable to skip search (server enforces)
  • Hook clearly shows search as STEP 1
  • Analyze guidance explicitly requires search with copy-paste command
  • Integrate phase validates search was called
  • Empty search results allowed but warned
  • Error messages actionable and educational
  • test-search-enforcement.js passes (5 test cases)
  • All existing tests pass

Initial Analysis Phase (REQUIRED BEFORE ANY WORK)

CRITICAL: This phase MUST be completed first, before proceeding to Implementation Strategy.

Before starting any implementation work, analyze the existing codebase to understand:

1. Current System State

  • Project Structure:

    • What directories exist? (working-version-*, .cursor/, .claude/, etc.)
    • What files are present in each directory?
    • What's the relationship between directories?
    • Are there working versions, test versions, deployment versions?
  • Existing Code:

    • Read key files: index.js files, hook files, configuration files
    • What functionality is already implemented?
    • What's the code quality and structure?
    • Are there any obvious issues or technical debt?
  • Documentation:

    • Read README.md, test-summary.md, any other documentation
    • What's the documented vs actual state?
    • Are there discrepancies or outdated information?
  • Tests:

    • What test files exist?
    • What's currently being tested?
    • What test coverage exists?
    • Are tests passing or failing?

2. Version and State Detection

  • Version Information:

    • What version is the system currently at?
    • Are version numbers consistent across files?
    • What's been completed vs what's pending?
  • Token System:

    • What token files exist?
    • What's the current token naming scheme?
    • Are there old tokens that need cleanup?
  • Configuration:

    • What configuration files exist? (.protocol-enforcer.json, .claude.json, etc.)
    • What's the current configuration state?
    • Are there conflicts or issues?

3. Dependencies and Integration Points

  • External Dependencies:

    • What npm packages are used?
    • What's in package.json files?
    • Are dependencies up to date?
  • Integration Points:

    • How do components interact?
    • What's the MCP server protocol implementation?
    • How do hooks communicate with servers?
    • What's the token exchange mechanism?

4. Outstanding Work and Issues

  • Incomplete Features:

    • What completion criteria are not yet met?
    • What phases/stages are incomplete?
    • What functionality is stubbed or missing?
  • Known Issues:

    • Are there documented bugs or issues?
    • Are there failing tests?
    • What's blocking progress?
  • Technical Debt:

    • What code needs refactoring?
    • What documentation is missing or outdated?
    • What tests need to be added?

Analysis Output

After completing this analysis, create a summary that includes:

  1. Current State Summary:

    • What works?
    • What doesn't work?
    • What's the overall completeness percentage?
  2. Implementation Priorities:

    • What should be worked on first?
    • What dependencies exist between tasks?
    • What's the critical path?
  3. Risk Assessment:

    • What are the biggest risks or blockers?
    • What assumptions need validation?
    • What could go wrong?
  4. Recommended Approach:

    • What's the best strategy given current state?
    • Should anything be refactored first?
    • Are there quick wins available?

Once this analysis is complete, proceed to Implementation Strategy below.


Protocol Integrity Requirements

Design Philosophy: The protocol exists to ensure quality work and institutional memory. Every step serves a purpose. Agents must follow the protocol correctly, not bypass it.

Objectives

1. Prevent Protocol Violations

  • Agents must pass correct parameters to all phases
  • Agents must execute suggested queries (not skip gathering)
  • Agents must provide actual context (not empty objects)
  • Agents cannot skip phases or steps

2. Enable Correct Usage

  • Error messages must be educational, not frustrating
  • Every error must show exact fix with user's data
  • Validation must guide agents to correct usage
  • System teaches proper protocol through clear failures

3. Enforce Quality Standards

  • Artifact validation ensures meaningful work
  • synthesizedContext must be substantial (min 50 chars)
  • Thoughts must be complete (min 10 chars per thought)
  • Final conclusions must be comprehensive (min 20 chars)

4. Prevent Gaming

  • Session files cannot be manually edited to bypass validation
  • Empty gatheredContext detected and warned
  • Missing parameters caught with clear explanations
  • Tampering attempts logged

5. Maintain Consistency

  • Same problem parameter across all phases
  • Phase order enforced (analyze → integrate → reason → record)
  • Session state tracked throughout workflow
  • Audit trail of warnings and issues

Implementation Requirements

Parameter Validation:

  • Session-aware: Auto-fill from previous phases when possible
  • Educational: Show user's problem text in error examples
  • Consistent: Validate same problem used across phases
  • Comprehensive: Check all required fields with specific messages

Artifact Validation:

  • Quality checks: Not just presence, but meaningfulness
  • Failure detection: Distinguish between missing param vs empty context
  • Clear messaging: Explain root cause and fix for each failure type
  • Tampering prevention: Detect manual session file edits

Error Messaging:

  • Copy-paste ready: Include working fix example
  • Personalized: Use user's exact problem text in examples
  • Actionable: Specific steps to resolve, not vague guidance
  • Educational: Explain WHY (purpose) not just WHAT (requirement)

Hook Enforcement:

  • Artifact checks: Validate quality in pre-tool-use hook
  • Session validation: Ensure allPhasesComplete AND artifactsVerified
  • Bypass detection: Log suspicious file modifications
  • Educational blocking: Explain full workflow when blocked

Success Criteria

Agent Behavior:

  • ✅ Agents follow protocol correctly on first attempt
  • ✅ Agents understand errors and fix immediately
  • ✅ Agents don't attempt to bypass validation
  • ✅ Agents learn proper usage through failures

Error Quality:

  • ✅ Every error includes root cause explanation
  • ✅ Every error includes working fix example
  • ✅ Every error uses user's actual data
  • ✅ Zero generic "something went wrong" messages

Protocol Enforcement:

  • ✅ No bypassing through manual file edits
  • ✅ No skipping phases or steps
  • ✅ No empty context objects accepted
  • ✅ Quality work consistently produced

Reference Implementation

See ARTIFACT_VALIDATION_FIX.md for detailed implementation guidance on:

  • Session-aware parameter validation
  • Enhanced artifact validation messages
  • Storing problem across phases
  • GatheredContext quality validation
  • Hook guidance improvements
  • Session tampering detection

Implementation Strategy

Version Management Protocol

CRITICAL: When implementing new features that require a version bump:

Version Bump Checklist (ALL THREE must be updated):

  1. index.js VERSION constant - Update const VERSION = 'X.Y.Z'
  2. index.js header comment - Update @version X.Y.Z
  3. package.json version field - Update "version": "X.Y.Z"

Applies to both MCP servers:

  • working-version-memory-augmented/ (memory-augmented-reasoning-mcp)
  • working-version-protocol-enforcer/ (protocol-enforcer-mcp)

Before gist deployment:

  • Verify all three locations have matching version numbers
  • Run: grep -E "VERSION|version" working-version-*/index.js working-version-*/package.json
  • Confirm consistency across all files
  • Verify hook message format (protocol-enforcer only):
    • Check user-prompt-submit.cjs uses "USER-CONFIGURED HOOK" (not "USER REQUIREMENT")
    • Run: grep "USER-CONFIGURED HOOK" working-version-protocol-enforcer/user-prompt-submit.cjs
    • Confirm message includes attribution and clarifies scope

Version Numbering:

  • Major (X.0.0): Breaking changes, protocol changes
  • Minor (x.Y.0): New features, backward compatible
  • Patch (x.y.Z): Bug fixes only

Example: Adding automatic scope detection requires minor bump:

  • v3.0.0 → v3.1.0
  • Update: const VERSION = '3.1.0', header @version 3.1.0, "version": "3.1.0"

Stage 0: Installation & Upgrade System (Iterations 1-15)

Goal: Analysis-driven installation that handles fresh installs and upgrades cleanly

Requirements to Accomplish:

  1. State Detection Capability

    • Scan for existing hook files and MCP servers
    • Detect current version by examining hook file contents
    • Check for old token format (no session ID in filename)
    • Check for new token format (session-specific naming)
    • Detect active work (unrecorded sessions)
    • Identify conflicts (version mismatches, token incompatibilities)
    • Generate installation recommendations
    • Create action list (what needs to happen before/during install)
  2. Installation Execution Capability

    • Execute pre-install actions:
      • Clean old tokens if upgrading from v2
      • Warn about unrecorded active work
      • Backup configuration files if reinstalling
    • Copy hook files to .cursor/hooks/
    • Set file permissions (chmod +x for hooks)
    • Create initial session file for fresh installs
    • Verify installation (check files exist and executable)
    • Print post-install guidance based on installation type
  3. Design Principle: NO Runtime Backward Compatibility

    • Hooks are clean v3 code (no old format support)
    • Migration happens ONCE during installation
    • After installation, system runs pure v3
    • Easier to maintain, test, and reason about
  4. Test Coverage:

    • Fresh directory detection works correctly
    • v2 system detection works, lists conflicts
    • Installation execution completes successfully for all scenarios
    • Tests can be manual or automated (your choice)

Stage 1: Foundation (Iterations 16-35)

Goal: Basic MCP servers responding to tool calls with proper error handling

  1. Create memory-augmented-reasoning/index.js

    • JSON-RPC 2.0 message handling (stdio transport)
    • Handle notifications/initialized without response
    • Implement tools/list response with 8 tools
    • Stub implementations that return success
    • CRITICAL: Proper parameter validation from the start:
      • Return -32602 Invalid params errors (not throw Error)
      • Include specific parameter name in error message
      • Include usage example in error message
      • Never use generic "Internal error" for missing parameters
  2. Create protocol-enforcer/index.js

    • JSON-RPC 2.0 message handling (stdio transport)
    • Handle notifications/initialized without response
    • Implement tools/list response with 5 tools
    • Stub implementations that return success
    • CRITICAL: Proper parameter validation from the start:
      • Return -32602 Invalid params errors (not throw Error)
      • Include specific parameter name in error message
      • Include usage example in error message
      • Never use generic "Internal error" for missing parameters
  3. Test: Can both servers start and list their tools? Do they return proper -32602 errors for invalid params?

Stage 2: Database Layer (Iterations 36-55)

Goal: SQLite databases with experience storage/search

  1. Add better-sqlite3 to memory-augmented-reasoning

    • User database: ~/.cursor/memory-augmented-reasoning.db
    • Project database: {workspace}/.cursor/memory-augmented-reasoning.db
    • Schema: experiences, reasoning_sessions, reasoning_thoughts tables
    • FTS5 virtual table for full-text search
  2. Implement search_experiences tool

    • Search across both databases (user + project)
    • FTS5 full-text search with ranking
    • Filter by domain, type
    • Return top 50 results by default
  3. Implement record_experience tool

    • Automatic scope detection (NEW):
      • If scope="auto": detect from experience content
      • Check manual project_keywords in config first
      • If no config: auto-discover (git, package.json, root dir, file paths)
      • Match keywords case-insensitive in situation/approach/outcome/context
      • Detect file paths (src/, /components/, .tsx, project root)
      • Use default_when_uncertain if no matches
      • Cache discovered keywords for performance (<10ms detection)
    • Insert experience into database (user or project based on detection)
    • Check for duplicates (TF-IDF 90% similarity)
    • Skip if duplicate found
    • Return experience ID and detected scope
  4. Test: Can you record and search experiences?

Stage 3: Reasoning Cycle (Iterations 56-75)

Goal: 4-phase informed_reasoning with session management

  1. Implement informed_reasoning tool

    • Phase 1 (analyze): Suggest queries, create REQUEST-SCOPED session token
    • Phase 2 (integrate): Synthesize context
    • Phase 3 (reason): Evaluate approach, track thoughts
    • Phase 4 (record): Mark complete, finalize session
    • Session token: ~/.protocol-session-{id}.json (REQUEST-SCOPED, 60-minute max TTL)
    • CRITICAL: Token authorizes tools for THIS user request's workflow only
    • CRITICAL: Response must clarify "tools unlocked for THIS REQUEST" not "session"
    • Artifact verification (quality checks)
  2. Implement query_reasoning_memory tool

    • Search reasoning sessions and thoughts
    • Find similar past reasoning
  3. Test: Can you complete full 4-phase reasoning cycle?

Stage 4: Protocol Enforcement (Iterations 76-110)

Goal: Token-based authorization and three-gate hook enforcement (TEACH → LEARN → REASON)

  1. Implement protocol-enforcer tools

    • verify_protocol_compliance: Check tokens, create operation token
    • authorize_file_operation: Validate operation token, optionally create session token
    • get_protocol_config: Load and return .protocol-enforcer.json
    • get_compliance_status: Return statistics
    • initialize_protocol_config: Create config file
  2. Create ALL 6 hook files with v3 enhancements:

    A. user-prompt-submit.cjs

    • Proactive token cleanup (removes >24 hour old tokens on every prompt)
    • Smart task detection (distinguishes tasks from questions)
    • Session-aware token naming (.protocol-search-required-token)
    • Creates search requirement for pre-tool-use enforcement
    • Consistent session ID via getSessionId() function
    • CRITICAL: Reminder message must emphasize "NEW USER REQUEST" and "previous tokens don't carry over"

    B. pre-tool-use.cjs (THREE-GATE ENFORCEMENT)

    • GATE 1 (TEACH): Blocks if previous sessions have unrecorded work
    • GATE 2 (LEARN): Blocks if search_experiences not called for current request
    • GATE 3 (REASON): Blocks if informed_reasoning not called for current request
    • CRITICAL: Session token validates REQUEST, not entire session duration
    • Session-specific token checking (.protocol-search-completed-{sessionId})
    • Invalidates session token AFTER Write/Edit (forces fresh LEARN per task)
    • Robust token cleanup with stderr logging
    • 24-hour expiry checks on recording tokens
    • Exempts research tools (Read, Glob, Grep, Bash, Task)
    • Exempts protocol tools (search_experiences, informed_reasoning, record_experience)

    C. post-tool-use.cjs

    • Creates recording requirement token with 24-hour TTL
    • Session-aware naming (.protocol-recording-required-{sessionId})
    • Immediate reminder logged to stderr
    • Robust error handling

    D. stop.cjs (NEW in v3)

    • Checks for unrecorded sessions
    • Tracks reminders in .protocol-stop-hook-reminders.json
    • Only reminds ONCE per token (loop prevention)
    • Clear TEACH phase reminder message

    E. session-start.cjs (NEW in v3)

    • Minimal setup only
    • Logs session start to stderr
    • Does NOT inject prompts (prevents spam)

    F. pre-compact-handoff.cjs (ENHANCED)

    • Last chance reminder before context compaction
    • Checks for incomplete recording tokens
    • Warns about losing session memory
  3. Update MCP server with session ID consistency:

    • Update getSessionId() function with 3-level priority chain
    • CLAUDE_SESSION_ID env → persistent session file → generate new
    • search_experiences creates session-specific completion token
    • Enhanced tool descriptions with workflow guidance and REQUEST-SCOPED emphasis
    • Update informed_reasoning response messaging to clarify token scope
    • Change "tools unlocked" to "tools unlocked for THIS REQUEST"
  4. Test: Do all three gates work correctly? Does cleanup happen?

Stage 5: Integration & Polish (Iterations 111-150)

Goal: All tests passing, deployment ready, installation system verified, UX improvements for protocol enforcement

  1. Test installation system:

    • Test detection on fresh directory (should identify as fresh install)
    • Test detection on simulated v2 system (should identify as upgrade)
    • Test installation execution for each scenario
    • Verify clean installations work
  2. Run all integration tests (see Phase 4 above)

    • Test 1: MCP Server Connection (3 tests)
    • Test 2: Complete Workflow (9 tests)
    • Test 3: Token State Machine (5 tests)
    • Test 4: Full Cycle Enforcement (6 tests)
    • Test 5: Experience Recording & Search (5 tests)
  3. Fix any failures discovered in testing

  4. Create documentation:

    For Local Development:

    • INSTALLATION.md with local installation/upgrade instructions
    • TESTING.md with all verification commands
    • Migration guide for v2 → v3 upgrades (local environment)

    For Gist Deployment:

    • AI-Agent Installation Guides with:

      • Clear entry points ("Start here if installing")
      • Installation trigger patterns (user prompts, agent recognition)
      • Version detection guidance (how to tell v2 vs v3)
      • Analysis/decision frameworks (environment detection, strategy selection)
      • Implementation guidance (what to create, how to verify)
    • MCP server documentation (tools, usage, architecture)

    • REQUEST-SCOPED token documentation (MANDATORY):

      • Document that tokens are per-request, not per-session
      • Provide examples of multiple user requests each needing protocol
      • Clarify 60-min TTL is max lifespan, not a "session pass"
      • Warn against token reuse across different user requests
    • NO references to install.js or local development tooling

    • Goal-based guidance (WHAT to accomplish, not HOW step-by-step)

    • Teach installing agents to: recognize trigger → analyze environment → assess state → determine strategy → implement → verify

    • Documentation Hygiene:

      • Gist docs: Only current version, no old version history, no local tooling references
      • Local docs: Can reference local tooling (install.js, tests)
      • Update version numbers in all index.js files to match current version
      • Clear separation: gist = standalone MCP servers, local = development environment
  5. Strengthen protocol enforcement UX (Phase 6): ✅ COMPLETED Iteration 23

    • Update MCP server response messages to use blocking language ✅
    • Replace advisory language ("guidance", "please", "should") with mandatory language ("BLOCKED", "MUST", "REQUIRED") ✅
    • Frame protocol compliance as USER REQUIREMENT, not system suggestion ✅
    • Update informed_reasoning responses for all phases (analyze, integrate, reason, record) ✅
    • Update tool descriptions to emphasize mandatory user requirements ✅
    • Update session token creation messages to clarify incomplete protocol ✅
    • Ensure all error messages use blocking language with specific next steps ✅
    • Version bump to 3.2.0 for memory-augmented-reasoning ✅
    • Test that agents understand protocol is mandatory, not optional ✅
    • Hook credibility improvements (added Iteration 22-23):
      • Updated hook messages to use attributed, honest format ("USER-CONFIGURED HOOK") ✅
      • Deployed updated hook files to protocol-enforcer gist ✅
  6. Prepare gist deployment:

    • Standalone MCP servers (index.js files)
    • Hook files as configuration examples (protocol-enforcer gist only)
    • Config template files (protocol-enforcer: config.*.json)
    • AI-agent installation guides (README.md)
    • NO install.js or test scripts in gist
    • Verify servers work via npx execution
  7. Verify against DESIGN_OVERVIEW.md requirements


Self-Correction Protocol

First Iteration (Iteration 1)

MANDATORY: Complete Initial Analysis Phase BEFORE any implementation work.

  1. Read and analyze existing codebase (see "Initial Analysis Phase" section above)
  2. Understand current state - what exists, what works, what doesn't
  3. Identify priorities - what needs to be done and in what order
  4. Create analysis summary - document findings and recommended approach
  5. Only then proceed to Implementation Strategy stages

Subsequent Iterations (Iterations 2+)

After each iteration, verify current stage:

  1. Read files created in previous iteration
  2. Run applicable tests for completed components
  3. If tests fail:
    • Analyze error messages
    • Debug root cause
    • Fix implementation
    • Re-run tests
  4. If tests pass:
    • Move to next component
    • Mark checkbox complete
  5. Check progress:
    • How many checkboxes complete?
    • How many iterations used?
    • Estimated iterations remaining?
  6. Re-analyze if blocked:
    • If stuck, return to analysis mode
    • Re-examine assumptions
    • Consider alternative approaches

Escape Hatch (If Stuck After 120 Iterations)

If approaching iteration 150 without completion:

  1. Document Current State

    • Which phases complete?
    • Which tests passing/failing?
    • What's blocking progress?
  2. Create PROGRESS_REPORT.md

    # Progress Report - Iteration N
    
    ## Completed
    - [List of working components]
    
    ## Failing
    - [List of broken components]
    - [Error messages and diagnostics]
    
    ## Blocking Issues
    - [Root causes preventing completion]
    
    ## Attempted Solutions
    - [What was tried]
    - [Why it didn't work]
    
    ## Recommended Next Steps
    - [Alternative approaches]
    - [What needs human intervention]
  3. Exit gracefully - Do NOT output completion promise

  4. Human will review and adjust prompt or provide guidance


Gist Documentation Specifications

CONTEXT: The gists are standalone MCP servers that run via stdio (npx). They are NOT installation scripts. The local install.js is development tooling only, NOT part of gist deployment.

AI-Agent Installation Guide Requirements

Goal-Based Approach: Teach installing agents HOW TO ANALYZE and DECIDE, not step-by-step commands.

Must Include:

  1. Installation Triggers & Entry Points:

    • Clear "Start Here" section for agents tasked with installation
    • User trigger patterns (example prompts like "Install the protocol-enforced learning system")
    • Agent recognition patterns (when to initiate: explicit request, missing tools, old version detected)
    • Version detection guidance (how to tell v2 vs v3, check hook contents, token formats)
    • Handoff flow (user request → agent reads → analyzes → implements)
  2. Analysis Framework:

    • How to detect IDE environment (Claude Code, Cursor, Cline)
    • How to find MCP config locations (check ~/.claude.json, .mcp.json, etc.)
    • How to find hook directories (check .cursor/hooks/, .claude/hooks/, etc.)
    • How to read and assess existing configuration state
    • How to identify conflicts and dependencies
  3. Decision Framework:

    • When to use user-level vs project-level configuration
    • How to merge with existing configs vs fresh install
    • How to handle absolute vs relative paths
    • How to determine which hook directory to use
  4. Implementation Guidance:

    • What files need to be created/modified
    • How to verify installation worked (test tool calls)
    • Troubleshooting common issues
    • What to tell user when installation completes

MCP Server Documentation Requirements

Must Include:

  • Tool descriptions and usage examples
  • Token system explanation (three gates: TEACH → LEARN → REASON)
  • Three-gate enforcement model details
  • Database schema documentation
  • Configuration options
  • REQUEST-SCOPED token documentation (CRITICAL):
    • Clear statement: "Session tokens are REQUEST-SCOPED, not session-wide"
    • Explanation: Each new user message is a new request requiring new protocol flow
    • Example workflow showing multiple requests:
      User: "Add feature X"     → Protocol flow → Token A → Write
      User: "Now update docs"   → Protocol flow → Token B → Write
      User: "Fix typo"          → Protocol flow → Token C → Edit
      
    • Clarification: "60-minute TTL is maximum lifespan, not permission duration"
    • Anti-pattern warning: "Do NOT reuse tokens across different user requests"

Project Documentation Requirements (Local Development)

CRITICAL: README.md is the ONLY documentation file

Required Files:

  • README.md - Main documentation consolidating ALL information
  • LOOP_PROMPT.md - Ralph loop specification (required for loop to work)
  • DESIGN_OVERVIEW.md - Optional: Architecture details (if needed)

Prohibited Actions:

  • ❌ DO NOT create RALPH_LOOP_ITERATION_N_COMPLETE.md files
  • ❌ DO NOT create FIX##_UPDATE_SUMMARY.md or BUG_FIX_*.md files
  • ❌ DO NOT create DEPLOYMENT_VERIFICATION_*.md files
  • ❌ DO NOT create RALPH_PROMPT_UPDATE_*.md files
  • ❌ DO NOT create *_COMPLETE.md status files
  • ❌ DO NOT create separate fix/update/completion documentation

Required Behavior:

  • ✅ Document all fixes in README.md "System Fixes" section
  • ✅ Focus on MCP server/hook changes ONLY (no documentation meta-info)
  • ✅ Update README.md status fields (Version, Status, Tests)
  • ✅ Keep project root clean (2-3 .md files maximum)
  • ✅ Use git commits for iteration tracking, not separate .md files

What to Document in README.md:

  • ✅ Hook behavior changes (e.g., "Stop hook now tracks reminders")
  • ✅ MCP server functionality fixes (e.g., "Token cleanup runs every 10 calls")
  • ✅ Token system improvements (e.g., "Session tokens now invalidate after Write")
  • ❌ NOT: Prompt update rationales ("Why 2-4 lines instead of 1")
  • ❌ NOT: Documentation process ("Updated LOOP_PROMPT.md to require...")
  • ❌ NOT: Meta-information about how documentation changed

Rationale: Proliferation of documentation files (46+ .md files) makes project unmaintainable. All fixes, iterations, and status updates belong in README.md.


Gist Deployment Requirements

CRITICAL: Gists contain ONLY standalone MCP servers

Each gist contains ONE MCP server with these files:

  • README.md - Server-specific documentation (tools, usage, installation)
  • index.js - MCP server code
  • package.json - Dependencies (e.g., better-sqlite3)
  • Config files (protocol-enforcer only): config.*.json templates

Gist README.md Contents:

  • Server overview and purpose
  • Tool descriptions and usage
  • Installation instructions (npx or manual)
    • Complete example showing both servers configured together in ~/.claude.json
    • Clear indication of optional vs required settings (env vars, config files)
  • Configuration options
  • Architecture/design for this server only
  • REQUEST-SCOPED token behavior with concrete examples:
    • Show multiple user requests each requiring new protocol flow
    • Clarify tokens don't carry over between requests
    • Example: Request 1 → protocol → write → token invalidated → Request 2 → protocol again

Gist README.md Must:

  • Only include current version documentation
  • Remove all old version history (v2.x.x changelog entries)
  • Have NO references to install.js or local development tooling
  • Be standalone (no references to other gists or local files)
  • State GOALS (what must work), REQUIREMENTS (what must exist), CONSTRAINTS (what to avoid)

Gist README.md Must NOT:

  • Reference install.js, test scripts, or local development tools
  • Include prescriptive step-by-step commands (use conceptual guidance)
  • Mix local development and gist deployment contexts
  • Reference project root README.md or LOOP_PROMPT.md
  • Include hook files documentation (hooks are local development only)

Gist Directory Must NOT Contain:

  • Hook files (*.cjs) - hooks belong in .cursor/hooks/ only
  • Test files (test-.js, test-.sh, tests/ directory)
  • Old documentation (updated--README-.md, duplicate READMEs)
  • Development documentation (IMPLEMENTATION-SUMMARY.md, TEST-STRATEGY.md, etc.)
  • Only: index.js, package.json, README.md, config templates (protocol-enforcer only)

Development Assets - DO NOT DEPLOY TO GISTS

The following are LOCAL DEVELOPMENT ONLY and must NEVER be deployed to gist or referenced in gist documentation:

Local Documentation (stays in project root):

  • README.md - End-user documentation (system overview, all fixes, gist deployment)
    • Must be deployment-focused: System capabilities and how to use deployed gists
    • No local development details: No references to install.js, test scripts, or development tooling
    • No excessive test breakdowns: Simple test count (e.g., "89/89 passing"), not per-file details
    • No development testing instructions: Testing details are for local development, not end users
    • Focus on gists: Installation via npx, gist links, usage instructions
  • LOOP_PROMPT.md - Ralph loop specification
  • DESIGN_OVERVIEW.md - Optional architecture documentation

Hook Files (stays in .cursor/hooks/):

  • user-prompt-submit-reminder.cjs
  • check-protocol.cjs (pre-tool-use)
  • post-tool-use.cjs
  • stop.cjs
  • session-start-handoff.cjs
  • pre-compact-handoff.cjs

Testing & Installation (stays in project root):

  • install.js - Local installation script
  • test-install.sh - Installation test suite (33 tests)
  • test-integration.js - Integration test suite (25 tests)
  • Any test files (test-*.js, test-*.sh)
  • INSTALLATION.md - Local installation guide (references install.js)
  • INSTALLATION_TESTS.md - Test specifications

Why: Gists are standalone MCP servers deployed via npx. They should work independently without local development tooling, hooks, or cross-references. The project root contains the development environment with hooks, tests, and installation tooling.

  • RALPH_LOOP_*.md - Development prompt and iteration notes
  • DESIGN_OVERVIEW.md - Internal design documentation
  • Any documentation that references local tooling

Gist deployments must contain ONLY:

  • index.js - The MCP server with consistent version numbers
    • CRITICAL: Version must be consistent in THREE places:
      1. const VERSION = 'X.Y.Z' constant in code
      2. Header comment @version X.Y.Z
      3. package.json "version": "X.Y.Z" field
    • Example: v3.1.0 requires ALL THREE: const VERSION = '3.1.0', header @version 3.1.0, and "version": "3.1.0" in package.json
    • Before gist deployment: Verify all three versions match
  • README.md - Standalone documentation (NOT updated-*-README-v3.md)
    • MUST be named exactly README.md for standard gist display
    • MUST contain only current version documentation
    • MUST NOT contain old v2.x.x version history
  • package.json - Dependencies only (better-sqlite3)
  • Configuration examples (config.*.json if applicable - protocol-enforcer only)
  • Hook files as configuration examples (protocol-enforcer only)
    • memory-augmented-reasoning: NO hook files (runs as pure MCP server)
    • protocol-enforcer: Hook files (*.cjs) included as configuration examples
    • Users copy hook files to their local .cursor/hooks/ directory
    • Hooks are configuration assets (like config files), not development-only files
    • When hooks are updated (e.g., Phase 6), redeploy updated hooks to gist
    • README documents which hooks to install and how to configure them
    • Hook message format requirements (Phase 6):
      • user-prompt-submit.cjs MUST use "USER-CONFIGURED HOOK" format (not "USER REQUIREMENT")
      • Message MUST include attribution: "Your user installed this protocol requirement"
      • Message MUST clarify scope: "file modification/code tasks" (not "ALL tasks")
      • Before deployment: Verify hook files have credible message format
      • After deployment: Test deployed gist hook with gh gist view [id] -f user-prompt-submit.cjs | grep "USER-CONFIGURED HOOK"

Critical Design Constraints

Must Follow

  1. MANDATORY FULL CYCLE ENFORCEMENT (Reactive Model)

    • Every task MUST complete: LEARN → REASON → ACT → TEACH
    • Enforcement is REACTIVE: Blocks tools when attempted, forcing agent to complete required steps
    • Agent CANNOT skip any phase (will be blocked with error messages)

    Enforcement Mechanism (Trial-and-Error Prevention):

    User submits prompt
        ↓
    user-prompt-submit hook creates search requirement token (state tracking)
        ↓
    Agent (ideally) searches past experiences first
        ↓
    Agent attempts Write/Edit/NotebookEdit
        ↓
    [LEARN GATE - REACTIVE BLOCKING]
    pre-tool-use hook checks: Has search_experiences been called?
    → NO: BLOCK with error: "LEARN gate: Must search past experiences first"
    → Agent receives error, calls search_experiences
    → search_experiences clears LEARN gate token
        ↓
    Agent attempts Write/Edit/NotebookEdit again
        ↓
    [REASON GATE - REACTIVE BLOCKING]
    pre-tool-use hook checks: Has informed_reasoning created session token?
    → NO: BLOCK with error: "REASON gate: Must complete reasoning cycle first"
    → Agent receives error, calls informed_reasoning
    → informed_reasoning creates session token (60-min TTL)
        ↓
    Agent attempts Write/Edit/NotebookEdit again
        ↓
    [ACT PHASE - AUTHORIZED]
    Both gates passed → Write/Edit/NotebookEdit allowed
    → Agent completes work
        ↓
    [TEACH GATE - TRACKING]
    post-tool-use hook creates recording requirement token (state tracking)
    → Future sessions check if recording complete
    → Agent calls record_experience to clear
    

    Result: Agents get blocked with clear error messages, forcing completion of each step before proceeding.

  2. JSON-RPC 2.0 Compliance

    • Notifications (no id field) MUST NOT receive responses
    • Requests (with id field) MUST receive responses
    • Handle notifications/initialized silently (log to stderr, no response)
  3. No Circular Dependencies (Critical)

    • Research tools ALWAYS exempt: Read, Glob, Grep, Bash, Task
    • Protocol tools ALWAYS exempt: search_experiences, informed_reasoning, record_experience, protocol-enforcer tools
    • Protected tools require full cycle: Write, Edit, NotebookEdit, WebSearch
    • Enforcement flow: search_experiences (LEARN) → informed_reasoning (REASON) → Write/Edit (ACT) → record_experience (TEACH)
    • NEVER block exempt tools or you create deadlock where agent can't complete the cycle
  4. Absolute Paths

    • Token files: ~/.protocol-{type}-token
    • Session files: ~/.protocol-session-{id}.json
    • Databases: ~/.cursor/memory-augmented-reasoning.db and {workspace}/.cursor/memory-augmented-reasoning.db
    • NO ${workspaceFolder} variables (don't resolve in Claude Code 2.1.17+)
  5. Token TTLs and Enforcement (FIXED: Added expiry and invalidation)

    • Search requirement token: 5 minutes TTL (cleared by search_experiences)
    • Search completed token: 5 minutes TTL (proves LEARN gate passed)
    • Session token: REQUEST-SCOPED with 60-minute max TTL (CRITICAL)
      • Session tokens are PER-REQUEST, not per-session
      • Created by informed_reasoning for ONE user request
      • Authorizes tools for THAT request's multi-tool workflow only
      • NEW user messages = NEW request = NEW protocol flow required
      • Invalidated after Write/Edit to force fresh LEARN for next task
      • Example: User says "Add feature X" → protocol → token A → Write. Then user says "Now update gist" → NEW protocol flow required → token B → Bash.
    • Recording requirement token: 24 hours TTL (not infinite - prevents accumulation)
    • Recording reminded token: Tracks Stop hook reminders to prevent infinite loop
    • Check expiry on read, don't consume (allow parallel execution)
    • Clean up expired tokens periodically
  6. Full Cycle Enforcement (LEARN → REASON → ACT → TEACH) - Reactive Model

    • LEARN: pre-tool-use hook checks if search_experiences was called, blocks Write/Edit if not
    • REASON: pre-tool-use hook checks if informed_reasoning created session token, blocks Write/Edit if not
    • ACT: Tools allowed once both gates verified
    • TEACH: post-tool-use hook tracks recording requirement, future enforcement checks this
    • Exempt tools (Read, Glob, Grep, Bash, Task) always allowed for research
    • NOTE: Enforcement is REACTIVE (blocks tool attempts), not PROACTIVE (forces steps upfront)
  7. Database Schema

    • experiences table: id, type, domain, situation, approach, outcome, reasoning, context, confidence, timestamps
    • experiences_fts table: FTS5 virtual table for full-text search
    • reasoning_sessions table: id, session_id, phase, status, timestamps
    • reasoning_thoughts table: id, session_id, thought_number, thought, confidence, timestamps
  8. Error Handling

    • Always return valid JSON-RPC responses
    • Never crash on invalid input
    • Log errors to stderr (not stdout - reserved for JSON-RPC)
    • Graceful degradation when database unavailable

    CRITICAL: Use proper MCP error codes based on error type

    • -32700 Parse error: Malformed JSON (framework usually handles this)
    • -32600 Invalid Request: Not a valid JSON-RPC request structure (framework usually handles this)
    • -32601 Method not found: Unknown tool/method name (framework usually handles this via tools/list)
    • -32602 Invalid params: Missing required parameters, invalid parameter types, validation failures
      • Missing required parameter: -32602
      • Wrong parameter type (string instead of number): -32602
      • Parameter value out of range: -32602
      • Enum validation failure (invalid phase name): -32602
    • -32603 Internal error: Server-side failures that aren't parameter issues
      • Database connection failure: -32603
      • File system errors: -32603
      • Unexpected exceptions during processing: -32603
      • Token file corruption: -32603
    • NEVER use throw new Error() for parameter validation - return structured MCP error instead

    CRITICAL: Error messages MUST be actionable and specific

    • NO generic errors: Replace "Internal error" with specific cause
    • Include what's missing: "Missing required parameter: 'thought'" not just "Invalid parameters"
    • Provide correct example: Show exact format expected
    • State the phase/context: "For reason phase, you must provide..."
    • List all requirements: Don't make agent guess what else is needed

    CRITICAL PRINCIPLE: Prevent Errors Through Complete Guidance

    The best error message is the one the user never sees. Before implementing ValidationError for a parameter:

    1. Check all phase guidance that leads to this phase
    2. Ensure guidance explicitly lists this parameter
    3. Don't assume agents will "remember" parameters from earlier phases
    4. Test that guidance alone (without reading code) is sufficient to succeed

    Example: If recordPhase requires problem, then reason→record guidance must show problem even though it was established in analyze phase.

    Anti-pattern:

    // Reason phase returns incomplete guidance:
    guidance: "Next: call informed_reasoning({ phase: 'record', session_id: '...', finalConclusion: '...' })"
    // Missing: problem parameter!
    // Result: Agent follows guidance → gets -32602 error → has to guess what's missing

    Correct pattern:

    // Reason phase returns complete guidance:
    guidance: "Next: call informed_reasoning({ phase: 'record', session_id: '...', problem: '...', finalConclusion: '...' })"
    // Result: Agent follows guidance → succeeds on first try

    Design Goal: Make success easy by being explicit. Complete guidance prevents validation errors.

    Example BAD error (current v3.2.0 behavior):

    // In server code:
    if (!params.problem) {
      throw new Error('Missing required parameter: problem');  // ❌ WRONG
    }
    // Agent sees: MCP error -32603: Internal error (message hidden!)

    Example GOOD error for missing parameter (-32602):

    // In server code:
    if (!params.problem) {
      return JSON.stringify({
        jsonrpc: '2.0',
        id: request.id,
        error: {
          code: -32602,
          message: 'Missing required parameter: problem (record phase)\n' +
                   'Required: { phase: "record", session_id: "...", problem: "...", finalConclusion: "..." }'
        }
      }) + '\n';
    }
    // Agent sees: MCP error -32602: Missing required parameter: problem (record phase)

    Example GOOD error for internal failure (-32603):

    // In server code:
    try {
      db.prepare('INSERT INTO ...').run(data);
    } catch (err) {
      return JSON.stringify({
        jsonrpc: '2.0',
        id: request.id,
        error: {
          code: -32603,
          message: 'Database error: Failed to save experience\n' +
                   'Details: ' + err.message
        }
      }) + '\n';
    }
    // Agent sees: MCP error -32603: Database error: Failed to save experience

    Error Message Format (minimal but informative):

    • Line 1: Error type and context ("Missing required parameter: X (phase Y)" or "Database error: X")
    • Line 2: Required structure for -32602, or error details for -32603
    • Keep total message under 150 characters when possible
    • No lengthy explanations or examples in production errors

    Apply to ALL tools in BOTH servers:

    • memory-augmented-reasoning:
      • informed_reasoning: Validate ALL required parameters per phase (analyze, integrate, reason, record)
        • Missing parameters, type validation, empty strings, enum values, range validation (thoughtNumber), logic validation
      • record_experience: Validate required fields (type, domain, situation, approach, outcome, reasoning)
        • Missing parameters, type validation, empty strings, enum validation (effective/ineffective)
      • search_experiences: Validate query parameters
        • Type validation, enum validation for optional filters
      • All other tools: Validate required parameters with -32602 errors
    • protocol-enforcer:
      • verify_protocol_compliance: Validate hook and tool_name parameters
        • Missing parameters, type validation, enum validation (hook names), array validation, array element types
      • authorize_file_operation: Validate operation_token exists and is valid
        • Missing parameter, type validation, empty string validation
      • All other tools: Validate required parameters with -32602 errors

    Comprehensive validation types (see Phase 7 for detailed checklist):

    • Missing required parameters
    • Wrong parameter types (string/number/boolean/array/object)
    • Empty string validation
    • Enum validation (phase names, type values, hook names)
    • Range validation (thoughtNumber >= 1, thoughtNumber <= totalThoughts)
    • Logic validation (thoughtNumber consistency)
    • Array validation (must be array, elements must be correct type)
    • Object/nested field validation (structure validation)

    Testing requirement:

    • Test each tool with missing required parameters → -32602
    • Test each tool with wrong types → -32602
    • Test enum validation → -32602
    • Test range validation → -32602
    • Verify error code is -32602 (not -32603)
    • Verify error message includes specific parameter name
    • Verify error message is minimal (2-3 lines)
  9. Analysis-Driven Installation (v3 Requirement)

    • NO runtime backward compatibility in hook files
    • Clean v3 code without migration logic
    • System detects current state BEFORE installation
    • System executes based on analysis results
    • Migration happens ONCE during installation, not on every tool call

    Three Installation Scenarios:

    A. Fresh Install:

    • No existing files detected
    • Install all files directly
    • Create initial session file
    • No cleanup needed

    B. Upgrade from v2:

    • Detect v2 by examining hook contents
    • Identify old token format (no session ID in filename)
    • Check for active work (recording requirements)
    • Clean old tokens BEFORE installing v3 files
    • Prompt user to record active work if found
    • Install clean v3 files (no backward compatibility code)
    • First workflow establishes new token format

    C. Reinstall v3:

    • Detect v3 already installed
    • Backup configuration files
    • Preserve existing tokens (already correct format)
    • Overwrite code files with fresh copies
    • Verify installation

    Why This Approach:

    • Cleaner code (easier to maintain)
    • Explicit migration (user aware of changes)
    • No performance overhead (no runtime checks)
    • Clear decision making (analysis tells you what will happen)
    • Idempotent (safe to re-run)
  10. Documentation Requirements (v3)

For Gist Deployment:

  • AI-Agent Installation Guides that teach ANALYSIS and DECISION-MAKING
  • Entry Points: Clear "Start Here" sections for agents
  • Trigger Patterns: User prompt examples, agent recognition patterns, version detection
  • Handoff Flow: User request → Agent reads guide → Analyzes → Decides → Implements → Verifies
  • Analysis Framework: How to detect environment, assess state, identify conflicts
  • Decision Framework: How to choose strategy (user vs project, merge vs fresh, etc.)
  • Implementation Guidance: What to create/modify, how to verify success
  • State: GOALS (what must work), REQUIREMENTS (what must exist), CONSTRAINTS (what to avoid)
  • NOT prescriptive step-by-step commands
  • NO references to install.js or local development tooling
  • Standalone MCP servers documentation only
  • Clean, current version only (no historical changelogs)

For Local Development:

  • Can reference install.js and test scripts
  • INSTALLATION.md for local environment setup
  • TESTING.md for verification
  • Keep v2→v3 migration information for local upgrades

Rationale: Gist = standalone MCP servers deployed via npx. Local = development environment with tooling.


Success Signal

When ALL checkboxes marked complete AND all 5 integration tests passing, output:

<promise>SYSTEM_OPERATIONAL</promise>

Do NOT output this promise until everything works.


Reference Documents


Notes for Ralph

CRITICAL DISTINCTION - Two Separate Contexts:

  1. Local Development Environment (/Users/jasonlusk/Desktop/mcp/)

    • Contains: install.js, test scripts, development tooling
    • Phase 0 is about THIS environment
    • install.js is LOCAL tooling, NOT for gist deployment
  2. Gist Deployment (https://gist.github.com/...)

    • Contains: Standalone MCP servers (index.js), hook files, AI-agent installation guides
    • NO install.js, NO test scripts, NO local tooling references
    • Must work via npx execution (stdio-based MCP servers)
    • Documentation teaches AI agents HOW TO ANALYZE and install dynamically
  • Current Status: v3 core system is OPERATIONAL (see RALPH_LOOP_ITERATION_3_COMPLETE.md)
  • Missing Component: Installation & upgrade system (Phase 0) - LOCAL DEVELOPMENT TOOLING
  • Primary task: Build the installation system for clean v3 deployment in LOCAL environment
  • Implementation freedom: You decide file names, structure, and approach
  • All hook files and MCP servers exist in working state (v3 with all 13 fixes)
  • Iterate until tests pass - don't give up on failures
  • Read error messages carefully - they contain the solution
  • SQLite and Node.js are your only dependencies
  • The loop will handle retry logic - focus on making incremental progress
  • Each iteration builds on previous work - files persist between iterations

Gist Documentation (CRITICAL):

  • Entry Points & Triggers: Clear "Start Here" for agents, user prompt examples, version detection
  • Installation Triggers: When/how agents recognize they should install (user request, missing tools, old version)
  • Analysis Framework: HOW TO ANALYZE environment and assess state dynamically
  • Decision Framework: HOW TO CHOOSE installation strategy based on analysis
  • Implementation Guidance: WHAT to create/modify, HOW to verify
  • Goal-based: State WHAT must work, REQUIREMENTS, CONSTRAINTS
  • NOT prescriptive: No step-by-step commands, teach thinking process
  • NO dependencies: No references to install.js or local development tooling
  • Standalone: MCP servers work via npx execution


Mechanisms for Consistent Automated Tool Usage

To ensure agents AUTOMATICALLY follow the protocol (not just get blocked when they don't), implement ALL of these mechanisms:

1. Enhanced Tool Descriptions (MCP Server)

Update tool descriptions in memory-augmented-reasoning/index.js to include workflow guidance:

{
  name: 'search_experiences',
  description: '🔍 STEP 1 - LEARN: Search past experiences BEFORE starting work. REQUIRED by protocol enforcement before Write/Edit/NotebookEdit. Returns relevant learnings to inform current task. Always call this first when given a new task.',
  inputSchema: { /* ... */ }
},
{
  name: 'informed_reasoning',
  description: '🧠 STEP 2 - REASON: Complete reasoning cycle to authorize file operations. REQUIRED by protocol enforcement before Write/Edit/NotebookEdit. Creates REQUEST-SCOPED session token (60-min max). MUST call for EACH new user request before file operations. Token authorizes Write/Edit/NotebookEdit for current request\'s multi-tool workflow only. Call after searching experiences. IMPORTANT: If session_id parameter not provided, it will auto-generate from CLAUDE_SESSION_ID environment variable or create persistent session identifier.',
  inputSchema: {
    type: 'object',
    properties: {
      phase: {
        type: 'string',
        enum: ['analyze', 'integrate', 'reason', 'record'],
        description: 'Current reasoning phase'
      },
      session_id: {
        type: 'string',
        description: 'Optional session identifier. If not provided, auto-generates from CLAUDE_SESSION_ID env var or creates persistent session. Use same ID across all phases of one task.'
      },
      request: {
        type: 'string',
        description: 'The current user request being processed. Each new user message is a new request requiring new protocol flow.'
      },
      // ... other parameters
    },
    required: ['phase']
  }
},
{
  name: 'record_experience',
  description: '📝 STEP 4 - TEACH: Record outcome for future sessions. REQUIRED after Write/Edit/NotebookEdit operations. Documents what worked/didn\'t work for institutional memory.',
  inputSchema: { /* ... */ }
}

Session ID Handling in informed_reasoning Implementation:

// In memory-augmented-reasoning/index.js - informed_reasoning tool handler
case 'informed_reasoning':
  const phase = toolParams.phase;
  let sessionId = toolParams.session_id;

  // Auto-generate session ID if not provided
  if (!sessionId) {
    // PRIORITY 1: Environment variable
    if (process.env.CLAUDE_SESSION_ID) {
      sessionId = process.env.CLAUDE_SESSION_ID;
    }
    // PRIORITY 2: Read from persistent session file
    else {
      const sessionFilePath = path.join(os.homedir(), '.protocol-current-session-id');
      try {
        if (fs.existsSync(sessionFilePath)) {
          const sessionData = JSON.parse(fs.readFileSync(sessionFilePath, 'utf8'));
          const age = Date.now() - sessionData.created;
          if (age < 8 * 60 * 60 * 1000) {
            sessionId = sessionData.session_id;
          }
        }
      } catch (err) {
        console.error(`[MAR] Failed to read session file: ${err.message}`);
      }
    }
    // PRIORITY 3: Generate new session ID
    if (!sessionId) {
      sessionId = `session-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
      console.error(`[MAR] Generated new session: ${sessionId}`);
    }
  }

  // Now use sessionId consistently throughout reasoning cycle
  console.error(`[MAR] informed_reasoning phase=${phase} session=${sessionId}`);

  if (phase === 'analyze') {
    // Create session token for protocol enforcement
    const sessionTokenPath = path.join(os.homedir(), `.protocol-session-${sessionId}.json`);
    const sessionTokenData = {
      session_id: sessionId,
      created_at: Date.now(),
      expires_at: Date.now() + 60 * 60 * 1000, // 60 minutes
      phase: 'analyze'
    };
    fs.writeFileSync(sessionTokenPath, JSON.stringify(sessionTokenData, null, 2));
  }

  // Continue with reasoning logic...
  break;

2. SessionStart Hook (Minimal Setup Only)

FIXED: Moved protocol reminder to UserPromptSubmit to avoid spam

SessionStart should only do minimal setup, NOT inject protocol reminders:

#!/usr/bin/env node
/**
 * SessionStart Hook - Minimal session setup
 * Does NOT inject prompts (moved to UserPromptSubmit to avoid spam)
 */

let input = '';
process.stdin.on('data', chunk => input += chunk);
process.stdin.on('end', () => {
  try {
    JSON.parse(input); // Parse hookData

    // Just log, don't inject prompts
    console.error('[session-start] New session started');
    console.error('[session-start] Protocol enforcement active');

    // No userPrompt injection - that's too early
    console.log(JSON.stringify({
      userPrompt: ""
    }));

    process.exit(0);
  } catch (e) {
    console.error(`[session-start] Error: ${e.message}`);
    console.log(JSON.stringify({ userPrompt: "" }));
    process.exit(0);
  }
});

3. Stop Hook (Follow-up Prompting with Loop Prevention)

FIXED: Prevents infinite loop by tracking reminders

Create .cursor/hooks/stop.cjs that reminds agent to record experiences (ONCE per token):

#!/usr/bin/env node
/**
 * Stop Hook - Reminds agent to complete TEACH phase
 * Runs when agent finishes responding
 * FIXED: Only reminds once per recording token to prevent infinite loop
 */

let input = '';
process.stdin.on('data', chunk => input += chunk);
process.stdin.on('end', () => {
  try {
    const hookData = JSON.parse(input);

    const fs = require('fs');
    const path = require('path');
    const os = require('os');

    const homeDir = os.homedir();
    const files = fs.readdirSync(homeDir);
    const recordingTokens = files.filter(f => f.startsWith('.protocol-recording-required-'));

    if (recordingTokens.length > 0) {
      // Check if we've already reminded for these tokens
      const reminderTrackPath = path.join(homeDir, '.protocol-stop-hook-reminders.json');
      let remindedTokens = {};

      try {
        if (fs.existsSync(reminderTrackPath)) {
          remindedTokens = JSON.parse(fs.readFileSync(reminderTrackPath, 'utf8'));
        }
      } catch (e) {
        remindedTokens = {};
      }

      // Find tokens we haven't reminded about yet
      const newTokens = recordingTokens.filter(token => !remindedTokens[token]);

      if (newTokens.length > 0) {
        // Mark these as reminded
        newTokens.forEach(token => {
          remindedTokens[token] = new Date().toISOString();
        });

        try {
          fs.writeFileSync(reminderTrackPath, JSON.stringify(remindedTokens, null, 2));
        } catch (e) {
          console.error(`[stop] Failed to track reminders: ${e.message}`);
        }

        const reminderPrompt = `
⚠️  TEACH Phase Incomplete
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

You modified files but haven't recorded the experience yet.

REQUIRED: Call record_experience to document:
  • What approach you took
  • Whether it was effective or ineffective
  • What you learned for future sessions

This ensures institutional memory and helps future sessions.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
`;

        console.log(JSON.stringify({
          userPrompt: reminderPrompt
        }));
      } else {
        // Already reminded - don't spam
        console.log(JSON.stringify({ userPrompt: "" }));
      }
    } else {
      console.log(JSON.stringify({ userPrompt: "" }));
    }

    process.exit(0);
  } catch (e) {
    console.error(`[stop] Error: ${e.message}`);
    console.log(JSON.stringify({ userPrompt: "" }));
    process.exit(0);
  }
});

4. Enhanced Error Messages (Already Implemented)

The pre-tool-use hook returns detailed, instructional error messages:

⛔ LEARN Gate: Write requires learning from past experiences first.

MANDATORY STEP 1 - Search Past Experiences:
  Call search_experiences to learn from institutional memory

  Example:
    search_experiences({
      query: "your task keywords",
      limit: 10
    })

Once you've searched past experiences, you can proceed to REASON gate.

5. MCP Prompt Templates (Optional Enhancement)

Add prompt templates to the MCP server for common workflows:

// In memory-augmented-reasoning/index.js, add prompts capability
case 'prompts/list':
  sendResponse(requestId, {
    prompts: [
      {
        name: 'start-task-workflow',
        description: 'Initiate protocol-enforced workflow for a new task',
        arguments: [
          {
            name: 'task_description',
            description: 'Description of the task to accomplish',
            required: true
          }
        ]
      }
    ]
  });
  break;

case 'prompts/get':
  if (toolParams.name === 'start-task-workflow') {
    sendResponse(requestId, {
      description: 'Protocol-enforced workflow for starting a task',
      messages: [
        {
          role: 'user',
          content: {
            type: 'text',
            text: `Task: ${toolParams.arguments.task_description}

Before making changes, follow the protocol:
1. Call search_experiences to learn from past work
2. Call informed_reasoning to document your approach
3. Proceed with Write/Edit operations
4. Call record_experience when done`
          }
        }
      ]
    });
  }
  break;

6. UserPromptSubmit Enhancement (Smart Task Detection)

FIXED: Only shows reminder for task-like prompts, not all prompts

Update user-prompt-submit.cjs to detect task-like prompts and inject protocol reminder:

#!/usr/bin/env node
/**
 * user-prompt-submit.cjs - LEARN Gate State Tracking + Smart Reminders
 * Creates search requirement token AND injects reminder for task-like prompts
 * FIXED: Only reminds for tasks, not read-only questions
 */

const fs = require('fs');
const path = require('path');
const os = require('os');

async function handler(args) {
  try {
    const { prompt } = args;

    if (!prompt || !prompt.trim()) {
      return { shouldBlock: false };
    }

    // Create search requirement token (state tracking)
    const tokenPath = path.join(os.homedir(), '.protocol-search-required-token');
    const tokenData = {
      created: new Date().toISOString(),
      expires: new Date(Date.now() + 5 * 60 * 1000).toISOString(),
      requirement: 'LEARN - search past experiences before proceeding'
    };
    fs.writeFileSync(tokenPath, JSON.stringify(tokenData, null, 2));

    // Detect if this is a task-like prompt (not just a question)
    const taskIndicators = [
      /\b(add|create|write|implement|build|fix|update|refactor|modify|change|delete|remove)\b/i,
      /\b(make|develop|code|debug|test)\b/i,
      /\bfile\b.*\b(to|for)\b/i,
      /\bcan you (add|create|write|implement|build|fix)/i
    ];

    const looksLikeTask = taskIndicators.some(pattern => pattern.test(prompt));

    // Only inject reminder for task-like prompts
    if (looksLikeTask) {
      console.error(`[user-prompt-submit] Task detected, injecting protocol reminder`);

      // Return immediately (don't block), but the reminder will be seen
      // We can't inject userPrompt from user-prompt-submit, so just log
      console.error(`
╔═══════════════════════════════════════════════════════════╗
║  NEW USER REQUEST - Protocol Required                     ║
╠═══════════════════════════════════════════════════════════╣
║  IMPORTANT: Each user message = new request = new flow    ║
║                                                            ║
║  Previous session tokens do NOT carry over to new requests║
║  You MUST run the protocol for THIS request:               ║
║                                                            ║
║  1️⃣  search_experiences - Learn from past work            ║
║  2️⃣  informed_reasoning - Authorize THIS request          ║
║  3️⃣  Write/Edit - Make your changes                       ║
║  4️⃣  record_experience - Teach future sessions            ║
║                                                            ║
║  Session tokens authorize THIS request only, not future!   ║
╚═══════════════════════════════════════════════════════════╝
`);
    } else {
      console.error(`[user-prompt-submit] Question/read-only prompt, no reminder needed`);
    }

    return { shouldBlock: false };

  } catch (error) {
    console.error(`[user-prompt-submit] Error: ${error.message}`);
    return { shouldBlock: false };
  }
}

module.exports = handler;

7. Configuration Documentation (System Message)

Document in README that users should add to ~/.claude.json:

{
  "systemMessage": "CRITICAL WORKFLOW - Before modifying files:

1. LEARN: Call search_experiences to check past learnings
2. REASON: Call informed_reasoning to document approach
3. ACT: Make changes (Write/Edit now authorized)
4. TEACH: Call record_experience to document outcome

These steps are ENFORCED by hooks - you'll be blocked if skipped."
}

8. PreCompact Hook (Learning Reminder)

Update .cursor/hooks/pre-compact-handoff.cjs to remind about recording:

console.error(`
⚠️  Context Compaction Triggered
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Before context resets, have you recorded your experiences?

If you made changes but haven't called record_experience yet,
do it now to preserve learnings for future sessions.

Once context compacts, you'll lose memory of this session's work.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
`);

Implementation Checklist for Automated Tool Usage

  • Update tool descriptions with STEP numbers and REQUIRED flags
  • Update informed_reasoning description to emphasize REQUEST-SCOPED tokens
  • Update informed_reasoning response to clarify token scope (message already updated to REQUEST-SCOPED format)
  • Update README.md with REQUEST-SCOPED token documentation (MANDATORY)
    • Add section explaining REQUEST-SCOPED vs session-wide tokens
    • Include example workflow showing multiple user requests
    • Update all "60-minute session token" language to REQUEST-SCOPED
    • Add anti-pattern warnings about token reuse
  • Create session-start.cjs hook with protocol reminder
  • Create stop.cjs hook with TEACH phase reminder
  • Enhance user-prompt-submit.cjs with visual workflow reminder emphasizing per-request nature
  • Update pre-compact-handoff.cjs with recording reminder
  • Document systemMessage configuration in README
  • (Optional) Add MCP prompt templates for workflow guidance (evaluated as unnecessary - comprehensive guidance already exists)

Result: Agents receive protocol guidance at:

  • Session start (proactive)
  • User prompt submit (proactive - emphasizes EACH request needs protocol)
  • Tool blocking (reactive with instructions)
  • Agent stop (follow-up reminder)
  • Context compaction (last chance reminder)

Combined with reactive enforcement hooks, this creates a comprehensive system that guides agents toward correct behavior while blocking violations.

CRITICAL: Session Token Scope Messaging

The informed_reasoning tool MUST return clear messaging about token scope:

Current problematic message:

"✅ All phases complete - session token valid - tools unlocked"

Fixed message:

"✅ All phases complete - session token valid for THIS REQUEST - you may now use Write/Edit/NotebookEdit for THIS TASK. NEW user requests require running protocol again."

This prevents agents from thinking the session token covers all future requests in the session.


Session Token Scope Clarification (CRITICAL)

Problem: Agents misunderstand session token scope, thinking one token covers entire 60-minute session.

Solution: Clear messaging at multiple points.

Common Misconception

WRONG: "I ran informed_reasoning once → got 60-min token → can skip protocol for whole session"

CORRECT: "Each NEW user message = NEW request = RUN PROTOCOL AGAIN"

What "Session Token" Means

Session tokens authorize multi-tool workflows within a SINGLE user request:

  • Reduces overhead when one task needs multiple tools
  • Example: One user request "Add feature X" → informed_reasoning → Read file → Write file → Edit file (all authorized by same token)

Session tokens do NOT mean:

  • ❌ Skip protocol for rest of 60-minute session
  • ❌ Reuse across different user requests
  • ❌ "Tools unlocked forever"

Request Boundaries

Each of these is a SEPARATE request requiring NEW protocol flow:

User: "Add feature X to the code"     → PROTOCOL FLOW → Token 1 → Write
User: "Now update the documentation"  → PROTOCOL FLOW → Token 2 → Write
User: "Also fix the typo"             → PROTOCOL FLOW → Token 3 → Edit

Why This Matters

  • Ensures relevant experiences searched for EACH task
  • Forces reasoning about EACH change
  • Prevents "protocol fatigue" where agent stops following after first request
  • Hook fires on EVERY user prompt - agent must follow it every time

Implementation Requirements

  1. Tool Description - Must say "REQUEST-SCOPED" and "MUST call for EACH new user request"
  2. Response Messaging - Must emphasize "THIS REQUEST" not "session"
  3. Hook Reminders - Must say "NEW USER REQUEST" and "previous tokens don't carry over"
  4. Documentation - Must include explicit examples of multiple requests

Critical Implementation Fixes

Fix #4: Concurrent Session Handling + Session ID Consistency

Problem: Multiple Claude Code sessions sharing same home directory can conflict on tokens. Additionally, session ID needs consistent source across all hooks.

Solution: Use CLAUDE_SESSION_ID environment variable as primary source, with documented fallback chain:

// CRITICAL: Session ID Consistency
// All hooks MUST use this exact function to ensure same session ID throughout lifecycle

const getSessionId = (hookData) => {
  // PRIORITY 1: Environment variable (set by Claude Code)
  // This should be the primary mechanism - DOCUMENT THIS REQUIREMENT
  if (process.env.CLAUDE_SESSION_ID) {
    return process.env.CLAUDE_SESSION_ID;
  }

  // PRIORITY 2: Hook data sessionId field
  // Available in some hooks but not all
  if (hookData && hookData.sessionId) {
    return hookData.sessionId;
  }

  // PRIORITY 3: Persistent session file
  // Create on first use, read on subsequent calls
  // This ensures consistency even if process restarts
  const sessionFilePath = path.join(os.homedir(), '.protocol-current-session-id');
  try {
    if (fs.existsSync(sessionFilePath)) {
      const sessionData = JSON.parse(fs.readFileSync(sessionFilePath, 'utf8'));
      // Check if session file is stale (>8 hours old)
      const age = Date.now() - sessionData.created;
      if (age < 8 * 60 * 60 * 1000) {
        return sessionData.session_id;
      }
    }
  } catch (err) {
    console.error(`[session-id] Failed to read session file: ${err.message}`);
  }

  // PRIORITY 4: Generate new session ID and persist it
  const newSessionId = `session-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
  try {
    fs.writeFileSync(sessionFilePath, JSON.stringify({
      session_id: newSessionId,
      created: Date.now(),
      created_iso: new Date().toISOString()
    }));
    console.error(`[session-id] Generated new session: ${newSessionId}`);
  } catch (err) {
    console.error(`[session-id] Failed to persist session ID: ${err.message}`);
  }

  return newSessionId;
};

// Check for session-specific tokens
const sessionId = getSessionId(hookData);
const searchCompletedPath = path.join(homeDir, `.protocol-search-completed-${sessionId}`);
const sessionTokenPattern = new RegExp(`\\.protocol-session-${sessionId}-.*\\.json`);

// When checking for recording requirements, check ALL sessions (not just current)
// This ensures TEACH gate works across sessions
const allRecordingTokens = files.filter(f => f.startsWith('.protocol-recording-required-'));

Documentation Requirements:

  • Claude Code should set CLAUDE_SESSION_ID environment variable for all child processes
  • If not available, system creates persistent session file as fallback
  • Session file expires after 8 hours (typical Claude Code session length)
  • All hooks use identical getSessionId() function for consistency

Result: Each session has isolated LEARN/REASON tokens, but TEACH enforcement works globally. Session ID is consistent across all hook calls.

Fix #6: Full TEACH Gate Enforcement

Problem: TEACH phase not actually enforced - agents could skip record_experience.

Solution: Pre-tool-use hook must check for ANY incomplete recording requirements from previous sessions:

// In pre-tool-use.cjs - TEACH Gate Check (runs FIRST, before LEARN/REASON)
const files = fs.readdirSync(homeDir);
const recordingTokens = files.filter(f => f.startsWith('.protocol-recording-required-'));

if (recordingTokens.length > 0) {
  // Check if any are expired (>24 hours old)
  const now = Date.now();
  const validTokens = recordingTokens.filter(tokenFile => {
    try {
      const tokenPath = path.join(homeDir, tokenFile);
      const stats = fs.statSync(tokenPath);
      const ageMs = now - stats.mtimeMs;
      const maxAge = 24 * 60 * 60 * 1000; // 24 hours

      if (ageMs > maxAge) {
        // Clean up expired token
        console.error(`[pre-tool-use] Removing expired recording token: ${tokenFile}`);
        try {
          fs.unlinkSync(tokenPath);
          return false;
        } catch (cleanupErr) {
          console.error(`[pre-tool-use] Failed to clean up ${tokenFile}: ${cleanupErr.message}`);
          return false;
        }
      }

      return true;
    } catch (err) {
      console.error(`[pre-tool-use] Error checking token ${tokenFile}: ${err.message}`);
      return false;
    }
  });

  if (validTokens.length > 0) {
    // BLOCK until previous sessions recorded
    return {
      shouldBlock: true,
      message: `⛔ TEACH Gate: Previous session(s) not recorded yet.

MANDATORY STEP 4 - Record Previous Work:
  You have ${validTokens.length} session(s) that completed work but haven't been recorded.

  Call record_experience to document outcomes before starting new work:
    record_experience({
      type: "effective" or "ineffective",
      domain: "Tools|Protocol|Communication|Process|Debugging|Decision",
      situation: "What task were you doing?",
      approach: "What did you try?",
      outcome: "What happened?",
      reasoning: "Why did it work/fail?"
    })

  Recording tokens found: ${validTokens.join(', ')}

Once you've recorded past work, you can proceed to LEARN gate.`
    };
  }
}

// Continue to LEARN gate check...

Result: Cannot start new work until previous work is recorded. TEACH phase is now mandatory.

Fix #8: Robust Token Cleanup + Proactive Cleanup

Problem: Token cleanup failures were silently ignored, causing accumulation. Even with TTLs, expired tokens can accumulate if not proactively cleaned.

Solution #1: Log all cleanup failures to stderr for debugging:

// In pre-tool-use.cjs - Cleanup with logging
function cleanupToken(tokenPath, reason) {
  try {
    if (fs.existsSync(tokenPath)) {
      fs.unlinkSync(tokenPath);
      console.error(`[pre-tool-use] ✓ Cleaned up token: ${path.basename(tokenPath)} (${reason})`);
      return true;
    }
    return false;
  } catch (err) {
    console.error(`[pre-tool-use] ✗ Failed to cleanup ${path.basename(tokenPath)}: ${err.message}`);
    console.error(`[pre-tool-use]   Token path: ${tokenPath}`);
    console.error(`[pre-tool-use]   Error code: ${err.code}`);
    console.error(`[pre-tool-use]   Please check permissions and disk space`);
    return false;
  }
}

// Usage example - session invalidation after Write
if (toolName === 'Write' || toolName === 'Edit' || toolName === 'NotebookEdit') {
  // Invalidate session token to force fresh LEARN on next task
  const sessionTokens = files.filter(f => f.match(/\.protocol-session-.*\.json$/));

  sessionTokens.forEach(tokenFile => {
    const tokenPath = path.join(homeDir, tokenFile);
    cleanupToken(tokenPath, 'Session invalidated after file modification');
  });
}

// Check for expired tokens and clean them up
const checkAndCleanExpired = (tokenPath, maxAgeMs, tokenType) => {
  try {
    if (!fs.existsSync(tokenPath)) return false;

    const stats = fs.statSync(tokenPath);
    const ageMs = Date.now() - stats.mtimeMs;

    if (ageMs > maxAgeMs) {
      cleanupToken(tokenPath, `${tokenType} expired (${Math.round(ageMs / 1000 / 60)}min old)`);
      return true;
    }

    return false;
  } catch (err) {
    console.error(`[pre-tool-use] Error checking expiry for ${path.basename(tokenPath)}: ${err.message}`);
    return false;
  }
};

Result: All token operations logged, failures visible in stderr, easier debugging.

Solution #2: Add proactive cleanup to user-prompt-submit.cjs that runs on every prompt:

// In user-prompt-submit.cjs - Add after creating search requirement token

/**
 * Proactive Token Cleanup
 * Removes all protocol tokens >24 hours old to prevent accumulation
 * Runs on every user prompt for maximum hygiene
 */
function cleanupExpiredTokens() {
  const homeDir = os.homedir();
  const now = Date.now();
  const maxAge = 24 * 60 * 60 * 1000; // 24 hours

  let cleaned = 0;
  let errors = 0;

  try {
    const files = fs.readdirSync(homeDir);
    const protocolTokens = files.filter(f => f.startsWith('.protocol-'));

    protocolTokens.forEach(tokenFile => {
      try {
        const tokenPath = path.join(homeDir, tokenFile);
        const stats = fs.statSync(tokenPath);
        const ageMs = now - stats.mtimeMs;

        if (ageMs > maxAge) {
          fs.unlinkSync(tokenPath);
          cleaned++;
          console.error(`[cleanup] ✓ Removed expired token: ${tokenFile} (age: ${Math.round(ageMs / 1000 / 60 / 60)}h)`);
        }
      } catch (err) {
        errors++;
        console.error(`[cleanup] ✗ Failed to cleanup ${tokenFile}: ${err.message}`);
      }
    });

    if (cleaned > 0 || errors > 0) {
      console.error(`[cleanup] Cleanup complete: ${cleaned} removed, ${errors} errors`);
    }
  } catch (err) {
    console.error(`[cleanup] Directory scan failed: ${err.message}`);
  }
}

// Run cleanup on every prompt
cleanupExpiredTokens();

// Then create new search requirement token
const tokenPath = path.join(os.homedir(), '.protocol-search-required-token');
// ... rest of user-prompt-submit logic

Result: Expired tokens automatically removed on every user prompt. System self-maintains and prevents long-term accumulation.

Fix #7: Task Boundary Detection (Enhanced)

Problem: System can't distinguish between new tasks vs follow-up questions.

Solution: Smart detection already implemented in user-prompt-submit.cjs (lines 661-668), but enhance with additional patterns:

// Enhanced task detection patterns
const taskIndicators = [
  // Action verbs
  /\b(add|create|write|implement|build|fix|update|refactor|modify|change|delete|remove)\b/i,
  /\b(make|develop|code|debug|test|deploy|configure|setup|install)\b/i,

  // Request patterns
  /\bcan you (add|create|write|implement|build|fix)/i,
  /\bplease (add|create|write|implement|build|fix)/i,
  /\bi (need|want|would like) (you to|to)/i,

  // File operations
  /\bfile\b.*\b(to|for)\b/i,
  /\b(to the|in the|from the) (file|code|project|repo)/i,

  // Negative indicators (NOT tasks - questions/read-only)
  /^(what|how|why|when|where|who|which|explain|tell me|show me)/i,
  /\?(what|how|why|when|where)/i
];

const negativeIndicators = [
  /^(what|how|why|when|where|who|which)\b/i,
  /\bexplain\b/i,
  /\btell me\b/i,
  /\bshow me\b/i,
  /\bdescribe\b/i,
  /\bwhat (is|are|does|do)\b/i
];

const hasTaskKeywords = taskIndicators.some(pattern => pattern.test(prompt));
const hasNegativeKeywords = negativeIndicators.some(pattern => pattern.test(prompt));

// Only treat as task if has task keywords AND no negative keywords
const looksLikeTask = hasTaskKeywords && !hasNegativeKeywords;

Result: Better distinction between "Explain how X works" (not a task) vs "Add X to the code" (task).

Fix #9: PreCompact Timing (Add Earlier Reminders)

Problem: PreCompact hook fires when context already compressed - too late.

Solution: Add recording reminders to post-tool-use.cjs immediately after Write/Edit:

// In post-tool-use.cjs - Immediate reminder after file operations
if (toolName === 'Write' || toolName === 'Edit' || toolName === 'NotebookEdit') {
  // Create recording requirement token
  const sessionId = getSessionId(hookData);
  const recordingTokenPath = path.join(homeDir, `.protocol-recording-required-${sessionId}`);
  const tokenData = {
    created: new Date().toISOString(),
    expires: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(),
    session_id: sessionId,
    tool_used: toolName,
    requirement: 'TEACH - record experience for institutional memory'
  };

  try {
    fs.writeFileSync(recordingTokenPath, JSON.stringify(tokenData, null, 2));
    console.error(`[post-tool-use] ⚠️  TEACH phase required - recording token created for session ${sessionId}`);
    console.error(`[post-tool-use] Remember to call record_experience before ending this session`);
  } catch (err) {
    console.error(`[post-tool-use] Failed to create recording token: ${err.message}`);
  }
}

// PreCompact hook still serves as last-chance reminder
// But primary reminder happens immediately after Write

Result: Agents reminded immediately after modifying files, not waiting until context compaction.


Fix #14: UserPromptSubmit Hook Prompt Bloat

Problem: Large reminder messages injected on EVERY user message cause "Prompt is too long" errors and exhaust context window.

Symptoms:

  • Error message: "Prompt is too long for every prompt"
  • Context exhaustion after 20-50 messages
  • Example: 15-line ASCII art reminder × 50 messages = 750 lines of bloat

Root Cause:

// ❌ WRONG: Large reminder on EVERY message
const reminder = `
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🚨 CRITICAL OVERRIDE RULE - MANDATORY FIRST STEP
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Before ANY other action, EVERY user query MUST first use the
mcp__memory-augmented-reasoning__informed_reasoning tool (analyze phase).

NO EXCEPTIONS. This applies to:
- Research tasks
- Analysis tasks
- Implementation tasks
...
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
`;

console.log(JSON.stringify({ userPrompt: reminder }));

Solution: Keep reminders SHORT (2-4 lines max, no ASCII art) to balance agent prioritization with context preservation:

// ✅ CORRECT: Short structured reminder (2-4 lines)
const reminder = `🔵 NEW REQUEST - Protocol Required:
1. search_experiences (LEARN gate)
2. informed_reasoning analyze phase (REASON gate)
3. Then Write/Edit will be allowed`;

console.log(JSON.stringify({ userPrompt: reminder }));

Why 2-4 lines instead of 1?

  • PreToolUse hook WILL block non-compliant tools (enforcement guaranteed)
  • Short reminder makes agent comply proactively (before hitting error)
  • Reduces back-and-forth: agent → tool error → retry → success
  • 4 lines × 50 messages = 200 lines (acceptable vs 750 lines with ASCII art)

Concise stderr across ALL hooks:

// ✅ Good: Concise stderr (1-2 lines)
process.stderr.write('✅ protocol-enforcer: session token valid (expires in 45m)\n');

// ❌ Bad: Verbose stderr with boxes
console.error("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
console.error("✅ Protocol Enforcer: Session Token Status");
console.error("   Session ID: abc-123");
console.error("   Expires: 2026-01-27 15:30:00");
console.error("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");

Why This Works:

  • PreToolUse hook enforces at tool use (agent WILL be blocked if non-compliant)
  • 2-4 line reminder balances agent prioritization with context preservation
  • Agent complies proactively (from reminder) instead of reactively (from error)
  • Reduces back-and-forth: fewer tool errors → fewer retries
  • 4 lines × 50 messages = 200 lines (manageable) vs 15 lines × 50 = 750 lines (bloat)

Implementation Rules:

For userPrompt injection:

  • 2-4 lines maximum (structured list format preferred)
  • Clear hierarchy (numbered steps or bullet points)
  • NO ASCII art boxes (━━━ borders, decorative frames)
  • NO verbose explanations (keep instructions direct)

For stderr/console.error across ALL hooks:

  • 1-2 lines maximum per message
  • Format: [hook-name] status: brief message
  • Example: ✅ protocol-enforcer: session token valid (expires in 45m)
  • NO multi-line boxes, NO verbose status reports

Result: Context window preserved, agent complies proactively, enforcement guaranteed.


Summary of All Fixes

Issue Status Implementation
#1: Stop hook infinite loop ✅ FIXED Reminder tracking (.protocol-stop-hook-reminders.json)
#2: SessionStart prompt spam ✅ FIXED Moved to UserPromptSubmit with smart detection
#3: Recording tokens never expire ✅ FIXED 24-hour TTL with expiry checks
#4: Concurrent session conflicts ✅ FIXED Session-specific token naming with consistent ID source
#5: Session token reuse ✅ FIXED Invalidation after Write/Edit
#6: TEACH phase not enforced ✅ FIXED Pre-tool-use checks ALL recording requirements
#7: Task boundary detection ✅ FIXED Smart detection with positive/negative patterns
#8: Token cleanup failure ✅ FIXED Robust logging + proactive cleanup on every prompt
#9: PreCompact timing ✅ FIXED Immediate reminder in post-tool-use
#10: SessionStart fires early ✅ FIXED No prompt injection, minimal setup only
#11: Session ID consistency ✅ FIXED Persistent session file with 8-hour expiry + env var
#12: Session ID propagation ✅ FIXED informed_reasoning auto-generates from consistent source
#13: Proactive token cleanup ✅ FIXED Cleanup routine in user-prompt-submit (every prompt)
#14: UserPromptSubmit prompt bloat ✅ FIXED Concise reminders (2-4 lines, no ASCII art), concise stderr across all hooks

All 13 issues resolved. System ready for implementation and testing.


Design Review Findings (Post-Fix Analysis)

After implementing all fixes, the system was analyzed for remaining issues. The following were evaluated:

Non-Issues (Design is Correct)

  • Race Conditions: Node.js fs operations are atomic, hooks run sequentially - no race condition risk
  • TEACH Gate Cross-Session Blocking: Intentional behavior - ensures no work falls through cracks
  • record_experience Clearing All Tokens: Correct design - forces recording all pending work

Issues Identified and Fixed

  • #11 - Session ID Consistency: Added persistent session file with 8-hour expiry as fallback to env var
  • #12 - Session ID Propagation: informed_reasoning now auto-generates session ID from consistent source
  • #13 - Proactive Token Cleanup: Added cleanup routine that runs on every user prompt

Verified Invariants

✅ No circular dependencies (exempt tools cannot be blocked) ✅ No deadlock scenarios (research tools always exempt) ✅ Session isolation (LEARN/REASON tokens are session-specific) ✅ Global TEACH enforcement (recording requirements checked across all sessions) ✅ Automatic cleanup (expired tokens removed proactively) ✅ Session consistency (all hooks use same session ID source) ✅ Graceful degradation (robust error handling with logging)

Design Quality Assessment

  • Enforcement Model: ✅ Reactive blocking with trial-and-error prevention
  • Token State Machine: ✅ Well-defined TTLs and lifecycle
  • Concurrency Handling: ✅ Session-isolated with global TEACH gate
  • Error Handling: ✅ Robust logging, no silent failures
  • User Experience: ✅ Clear error messages with instructions
  • Maintenance: ✅ Self-cleaning with proactive token removal

Status: Design complete and validated. Ready for implementation.


Current State Summary (As of 2026-01-27)

✅ What's Complete

v3 Core System (OPERATIONAL):

  • All 6 hook files implemented with three-gate enforcement
  • MCP servers updated with session ID consistency
  • All 13 design fixes implemented and tested
  • Simple end-to-end tests passing
  • Documentation: RALPH_LOOP_ITERATION_3_COMPLETE.md

Three-Gate Enforcement Working:

  • TEACH gate: Blocks if previous work unrecorded
  • LEARN gate: Blocks if search_experiences not called
  • REASON gate: Blocks if informed_reasoning not called
  • All gates tested and operational

Token System (v3 Format):

  • Session-specific naming (.protocol-*-{sessionId})
  • 24-hour TTLs with proactive cleanup
  • Persistent session file (8-hour expiry)
  • Loop prevention in stop hook
  • Smart task detection

⚠️ What's Missing

Phase 0: Installation System (NOT STARTED):

  • Requirements-based approach (see Phase 0 section for details)
  • No prescriptive scripts - AI determines implementation
  • Must handle: fresh install, v2 upgrade, v3 reinstall
  • Must accomplish: state detection, user guidance, safe installation

Why It's Important:

  • Current v3 files have NO backward compatibility
  • Upgrading from v2 requires token cleanup
  • Fresh installs need different handling than upgrades
  • Users need clear guidance on installation/upgrade process

What Needs to Happen:

  1. Implement state detection (current version, token formats, active work)
  2. Implement user guidance (clear communication, recommendations)
  3. Implement safe installation (pre-install actions, file updates, verification)
  4. Test all three scenarios (fresh, upgrade, reinstall)
  5. Document installation/upgrade procedures
  6. Mark Phase 0 checkboxes complete

🎯 Next Ralph Loop Iteration

Focus: Build the installation system (Phase 0)

Approach: Requirements-based - AI determines HOW to accomplish WHAT is needed

Core Objectives:

  • State detection and analysis
  • Clear user guidance and recommendations
  • Safe, idempotent installation process
  • Comprehensive testing and documentation

Estimated Iterations: 15-20 (Stage 0)

Completion Signal: When Phase 0 checkboxes all marked complete and installation tests pass


IMPORTANT: Do not output <promise>SYSTEM_OPERATIONAL</promise> until every single checkbox is marked complete (including Phase 0) and all integration tests pass. The v3 core system works, but installation system is required for clean deployment.

{
"name": "protocol-enforcer-mcp",
"version": "3.0.1",
"description": "MCP server that enforces mandatory supervisor protocol compliance before allowing file operations",
"author": "Jason Lusk <[email protected]>",
"license": "MIT",
"main": "index.js",
"bin": {
"protocol-enforcer": "./index.js"
},
"engines": {
"node": ">=14.0.0"
},
"keywords": [
"mcp",
"model-context-protocol",
"protocol-enforcer",
"ai-assistant",
"code-quality",
"compliance"
]
}
#!/usr/bin/env node
/**
* PostToolUse Hook - Automated verification and code quality checks
* Runs after Write/Edit operations to ensure code quality standards
*/
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
let input = '';
process.stdin.on('data', chunk => input += chunk);
process.stdin.on('end', () => {
try {
const hookData = JSON.parse(input);
const { tool, parameters } = hookData;
// Extract file path from tool parameters
let filePath = null;
if (parameters && parameters.file_path) {
filePath = parameters.file_path;
}
const checks = [];
// ┌─────────────────────────────────────────────────────────┐
// │ CHECK 1: GraphQL operations modified - run codegen │
// └─────────────────────────────────────────────────────────┘
if (filePath && filePath.includes('src/graphql/operations/')) {
checks.push('GraphQL operations modified - running codegen');
try {
execSync('yarn graphql:codegen', {
cwd: '/Users/jasonlusk/src/ph/smartpass-admin-ui',
stdio: 'pipe',
timeout: 30000
});
checks.push('✅ GraphQL codegen completed successfully');
} catch (e) {
checks.push(`⚠️ GraphQL codegen failed: ${e.message}`);
process.stderr.write(`\n⚠️ PostToolUse: GraphQL codegen failed\n${e.message}\n`);
}
}
// ┌─────────────────────────────────────────────────────────┐
// │ CHECK 2: Run linting for TypeScript/SCSS files │
// └─────────────────────────────────────────────────────────┘
if (filePath && (filePath.endsWith('.ts') || filePath.endsWith('.tsx') || filePath.endsWith('.scss'))) {
checks.push('Running code quality checks');
try {
execSync('yarn lint:all', {
cwd: '/Users/jasonlusk/src/ph/smartpass-admin-ui',
stdio: 'pipe',
timeout: 60000
});
checks.push('✅ All linting checks passed');
} catch (e) {
// Lint failures should not block, but should be reported
checks.push(`⚠️ Linting found issues - please fix before committing`);
process.stderr.write(`\n⚠️ PostToolUse: Linting found issues\n${e.stdout ? e.stdout.toString() : e.message}\n`);
}
}
// ┌─────────────────────────────────────────────────────────┐
// │ CHECK 3: Verify no console.log statements │
// └─────────────────────────────────────────────────────────┘
if (filePath && (filePath.endsWith('.ts') || filePath.endsWith('.tsx'))) {
try {
const content = fs.readFileSync(filePath, 'utf8');
const consoleLogMatches = content.match(/console\.log\(/g);
if (consoleLogMatches && consoleLogMatches.length > 0) {
checks.push(`⚠️ Found ${consoleLogMatches.length} console.log() statement(s) - remove before committing`);
} else {
checks.push('✅ No console.log statements found');
}
} catch (e) {
// File read error - skip check
}
}
// Output results
if (checks.length > 0) {
const response = {
hookSpecificOutput: {
hookEventName: "PostToolUse",
verificationResults: checks
}
};
console.log(JSON.stringify(response));
process.stderr.write('\n📋 PostToolUse verification:\n' + checks.map(c => ' ' + c).join('\n') + '\n');
}
process.exit(0);
} catch (e) {
// Don't block on hook errors
process.stderr.write(`\n⚠️ PostToolUse hook error: ${e.message}\n`);
process.exit(0);
}
});
#!/usr/bin/env node
/**
* post-tool-use.cjs - TEACH Gate Tracking + Immediate Reminders
*
* Forces agents to record experiences after completing work.
* Marks sessions for recording - sessions cannot be reused until record_experience called.
* FIXED: 24-hour TTL on recording tokens (prevents accumulation)
* FIXED: Immediate reminders after Write/Edit
*
* Hook Type: TEACH gate tracking - non-blocking
* Trigger: After tool execution
* Purpose: Mark sessions for recording requirement
*/
const fs = require('fs');
const path = require('path');
const os = require('os');
// Tools that should trigger recording requirement
const RECORDING_TRIGGERS = [
'Write',
'Edit',
'NotebookEdit'
];
/**
* Get consistent session ID across all hooks
*/
function getSessionId() {
if (process.env.CLAUDE_SESSION_ID) {
return process.env.CLAUDE_SESSION_ID;
}
const sessionFilePath = path.join(os.homedir(), '.protocol-current-session-id');
try {
if (fs.existsSync(sessionFilePath)) {
const sessionData = JSON.parse(fs.readFileSync(sessionFilePath, 'utf8'));
const age = Date.now() - sessionData.created;
if (age < 8 * 60 * 60 * 1000) {
return sessionData.session_id;
}
}
} catch (err) {
console.error(`[session-id] Failed to read session file: ${err.message}`);
}
return 'default-session';
}
async function handler(args) {
try {
const { toolName, success } = args;
// Only trigger recording for relevant tools
if (!RECORDING_TRIGGERS.includes(toolName)) {
return { shouldBlock: false };
}
// Get the active session ID
const sessionId = getSessionId();
if (!sessionId) {
console.error('[post-tool-use] No session ID found - skipping TEACH gate');
return { shouldBlock: false };
}
// Create recording requirement token for this session
// FIXED: Added 24-hour TTL to prevent infinite accumulation
const tokenPath = path.join(os.homedir(), `.protocol-recording-required-${sessionId}`);
const tokenData = {
created: new Date().toISOString(),
expires: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(), // 24-hour TTL
sessionId,
toolName,
success: success !== false,
requirement: 'TEACH - record_experience must be called before session reuse'
};
try {
fs.writeFileSync(tokenPath, JSON.stringify(tokenData, null, 2));
console.error(`⚠️ TEACH phase required - call record_experience`);
} catch (err) {
console.error(`[post-tool-use] Failed to create recording token: ${err.message}`);
console.error(`[post-tool-use] This is not critical - continuing`);
}
// This hook doesn't block the current operation
// It marks the session for recording requirement
// Future attempts to use Write/Edit will be blocked until record_experience called
return { shouldBlock: false };
} catch (error) {
console.error('[post-tool-use] Error:', error.message);
// Never block on error in post-tool-use
return { shouldBlock: false };
}
}
// Export for Claude Code hooks
module.exports = handler;
// CLI execution support
if (require.main === module) {
const args = {
toolName: process.argv[2] || 'Write',
success: process.argv[3] !== 'false'
};
handler(args).then(result => {
console.log(JSON.stringify(result, null, 2));
}).catch(err => {
console.error('Error:', err);
process.exit(1);
});
}
#!/usr/bin/env node
/**
* pre-compact-handoff.cjs - Last Chance Reminder
*
* Reminds to record experiences before context compaction
* Checks for incomplete recording tokens
* Clear warning that compaction will lose session memory
* Gives agent last chance to complete TEACH phase
*
* Hook Type: Non-blocking reminder
* Trigger: Before context compaction
*/
let input = '';
process.stdin.on('data', chunk => input += chunk);
process.stdin.on('end', () => {
try {
const hookData = JSON.parse(input);
const fs = require('fs');
const path = require('path');
const os = require('os');
const sessionId = hookData.sessionId || 'unknown';
const trigger = hookData.trigger || 'unknown'; // 'auto' or 'manual'
// Check for incomplete recording tokens
const homeDir = os.homedir();
const files = fs.readdirSync(homeDir);
const recordingTokens = files.filter(f => f.startsWith('.protocol-recording-required-'));
// Log compaction event (concise)
if (recordingTokens.length > 0) {
console.error(`⚠️ Compaction triggered. ${recordingTokens.length} unrecorded session(s).`);
console.error(` Call record_experience now to preserve learnings.`);
} else {
console.error(`✅ Compaction triggered - all sessions recorded`);
}
process.exit(0);
} catch (e) {
console.error(`\n⚠️ PreCompact hook error: ${e.message}`);
process.exit(0); // Don't block compaction on error
}
});
#!/usr/bin/env node
/**
* pre-tool-use.cjs - Three-Gate Enforcement (TEACH → LEARN → REASON)
*
* Blocks Write/Edit/NotebookEdit until THREE gates cleared:
* 1. TEACH gate: Previous session(s) must be recorded
* 2. LEARN gate: Must search past experiences
* 3. REASON gate: Must complete reasoning cycle
*
* Hook Type: Blocking
* Trigger: Before tool execution
*/
const fs = require('fs');
const path = require('path');
const os = require('os');
// Tools that require full cycle (all three gates)
const PROTECTED_TOOLS = [
'Write',
'Edit',
'NotebookEdit',
'WebSearch'
];
// Tools that are ALWAYS exempt (no authorization needed)
const EXEMPT_TOOLS = [
'Read',
'Glob',
'Grep',
'Bash',
'mcp__memory_augmented_reasoning__search_experiences',
'mcp__memory_augmented_reasoning__record_experience',
'mcp__memory_augmented_reasoning__informed_reasoning',
'mcp__memory_augmented_reasoning__query_reasoning_memory',
'mcp__memory_augmented_reasoning__export_experiences',
'mcp__memory_augmented_reasoning__create_memory_library',
'mcp__memory_augmented_reasoning__list_memory_libraries',
'mcp__memory_augmented_reasoning__switch_memory_library',
'mcp__memory_augmented_reasoning__get_current_library_info',
'mcp__memory_augmented_reasoning__create_system_json',
'mcp__memory_augmented_reasoning__get_system_json',
'mcp__memory_augmented_reasoning__search_system_json',
'mcp__memory_augmented_reasoning__list_system_json',
'mcp__protocol_enforcer__verify_protocol_compliance',
'mcp__protocol_enforcer__authorize_file_operation',
'mcp__protocol_enforcer__get_protocol_config',
'mcp__protocol_enforcer__get_compliance_status',
'mcp__protocol_enforcer__initialize_protocol_config',
'mcp__protocol_enforcer__check_protocol_compliance',
'mcp__advanced_reasoning__advanced_reasoning',
'mcp__advanced_reasoning__query_reasoning_memory',
'mcp__advanced_reasoning__create_memory_library',
'mcp__advanced_reasoning__list_memory_libraries',
'mcp__advanced_reasoning__switch_memory_library',
'mcp__advanced_reasoning__get_current_library_info',
'mcp__advanced_reasoning__create_system_json',
'mcp__advanced_reasoning__get_system_json',
'mcp__advanced_reasoning__search_system_json',
'mcp__advanced_reasoning__list_system_json',
// Task tools are ALWAYS exempt to prevent deadlock
'Task',
'TaskCreate',
'TaskUpdate',
'TaskGet',
'TaskList',
'TaskOutput',
'TaskStop'
];
/**
* Get consistent session ID across all hooks
* Priority chain: CLAUDE_SESSION_ID env → persistent session file → generate new
*/
function getSessionId() {
if (process.env.CLAUDE_SESSION_ID) {
return process.env.CLAUDE_SESSION_ID;
}
const sessionFilePath = path.join(os.homedir(), '.protocol-current-session-id');
try {
if (fs.existsSync(sessionFilePath)) {
const sessionData = JSON.parse(fs.readFileSync(sessionFilePath, 'utf8'));
const age = Date.now() - sessionData.created;
if (age < 8 * 60 * 60 * 1000) {
return sessionData.session_id;
}
}
} catch (err) {
console.error(`[session-id] Failed to read session file: ${err.message}`);
}
// If no valid session, return default - will use this for token checks
return 'default-session';
}
/**
* Cleanup helper with logging
*/
function cleanupToken(tokenPath, reason) {
try {
if (fs.existsSync(tokenPath)) {
fs.unlinkSync(tokenPath);
console.error(`[pre-tool-use] ✓ Cleaned up token: ${path.basename(tokenPath)} (${reason})`);
return true;
}
return false;
} catch (err) {
console.error(`[pre-tool-use] ✗ Failed to cleanup ${path.basename(tokenPath)}: ${err.message}`);
console.error(`[pre-tool-use] Token path: ${tokenPath}`);
console.error(`[pre-tool-use] Error code: ${err.code}`);
return false;
}
}
/**
* TEACH GATE: Check if previous sessions have unrecorded work
* Blocks if ANY .protocol-recording-required-* tokens exist
*/
function checkTEACHGate() {
try {
const homeDir = os.homedir();
const files = fs.readdirSync(homeDir);
const recordingTokens = files.filter(f => f.startsWith('.protocol-recording-required-'));
if (recordingTokens.length === 0) {
return { passed: true };
}
// Check each token for expiry (>24 hours old)
const now = Date.now();
const maxAge = 24 * 60 * 60 * 1000; // 24 hours
const validTokens = [];
recordingTokens.forEach(tokenFile => {
try {
const tokenPath = path.join(homeDir, tokenFile);
const stats = fs.statSync(tokenPath);
const ageMs = now - stats.mtimeMs;
if (ageMs > maxAge) {
// Clean up expired token
cleanupToken(tokenPath, `expired (${Math.round(ageMs / 1000 / 60 / 60)}h old)`);
return; // Skip this token
}
// FIX #6: Tampering detection for recording tokens
try {
const tokenContent = fs.readFileSync(tokenPath, 'utf8');
const tokenData = JSON.parse(tokenContent);
// Check file modification time vs token creation time
const fileModifiedMs = stats.mtimeMs;
const tokenCreatedMs = tokenData.created || tokenData.timestamp || 0;
if (tokenCreatedMs > 0) {
const fileAge = now - fileModifiedMs;
const tokenAge = now - tokenCreatedMs;
const ageDifference = Math.abs(fileAge - tokenAge);
// Allow 5 second tolerance
if (ageDifference > 5000) {
console.error('[pre-tool-use] ⚠️ Recording token file modification detected');
console.error(`[pre-tool-use] File: ${tokenFile}`);
console.error(`[pre-tool-use] File age: ${Math.round(fileAge/1000)}s, Token age: ${Math.round(tokenAge/1000)}s`);
console.error('[pre-tool-use] WARNING: Manual editing bypasses TEACH gate validation');
}
}
// Validate recording token structure
if (!tokenData.sessionId && !tokenData.session_id) {
console.error('[pre-tool-use] ⚠️ Recording token missing session ID - invalid structure');
console.error(`[pre-tool-use] File: ${tokenFile}`);
console.error('[pre-tool-use] Cleaning up invalid token');
cleanupToken(tokenPath, 'invalid structure');
return; // Skip this token
}
// Token is valid
validTokens.push(tokenFile);
} catch (parseErr) {
// If token file is corrupted or invalid JSON
console.error(`[pre-tool-use] ⚠️ Recording token is corrupted or invalid: ${tokenFile}`);
console.error(`[pre-tool-use] Error: ${parseErr.message}`);
console.error('[pre-tool-use] Cleaning up corrupted token');
cleanupToken(tokenPath, 'corrupted file');
}
} catch (err) {
console.error(`[pre-tool-use] Error checking token ${tokenFile}: ${err.message}`);
}
});
if (validTokens.length === 0) {
return { passed: true };
}
// BLOCK - unrecorded work exists
return {
passed: false,
message: `⛔ TEACH Gate: ${validTokens.length} session(s) need record_experience first.
Call record_experience({type: "effective|ineffective", domain, situation, approach, outcome, reasoning})
Then: search_experiences → informed_reasoning → Write/Edit
Tokens: ${validTokens.join(', ')}`
};
} catch (error) {
console.error('[pre-tool-use] TEACH gate check error:', error.message);
return { passed: true }; // Fail open on error
}
}
/**
* LEARN GATE: Check if search_experiences was called
*/
function checkLEARNGate() {
try {
const sessionId = getSessionId();
const searchTokenPath = path.join(os.homedir(), `.protocol-search-completed-${sessionId}`);
if (!fs.existsSync(searchTokenPath)) {
return {
passed: false,
message: `⛔ LEARN Gate: No search performed yet.
Call search_experiences({query: "task keywords", limit: 10}) first.
Then: informed_reasoning (4 phases) → Write/Edit authorized.`
};
}
const tokenContent = fs.readFileSync(searchTokenPath, 'utf8');
const tokenData = JSON.parse(tokenContent);
// FIX #6: Tampering detection for search tokens
try {
const fileStats = fs.statSync(searchTokenPath);
const fileModifiedMs = fileStats.mtimeMs;
const tokenCreatedMs = tokenData.created_at || tokenData.timestamp || 0;
if (tokenCreatedMs > 0) {
const fileAge = Date.now() - fileModifiedMs;
const tokenAge = Date.now() - tokenCreatedMs;
const ageDifference = Math.abs(fileAge - tokenAge);
// Allow 5 second tolerance for filesystem delays
if (ageDifference > 5000) {
console.error('[pre-tool-use] ⚠️ Search token file modification detected outside MCP server');
console.error(`[pre-tool-use] File: ${path.basename(searchTokenPath)}`);
console.error(`[pre-tool-use] File age: ${Math.round(fileAge/1000)}s, Token age: ${Math.round(tokenAge/1000)}s`);
console.error('[pre-tool-use] WARNING: Manual editing bypasses LEARN gate validation');
console.error('[pre-tool-use] TIP: Call search_experiences to create legitimate tokens');
}
}
// Validate search token structure
if (!tokenData.sessionId && !tokenData.session_id) {
console.error('[pre-tool-use] ⚠️ Search token missing session ID - invalid structure');
cleanupToken(searchTokenPath, 'invalid structure');
return {
passed: false,
message: `⛔ LEARN Gate: Invalid search token (malformed/tampered).
Call search_experiences({query: "task keywords", limit: 10}) to create valid token.`
};
}
} catch (err) {
console.error(`[pre-tool-use] Warning: Could not validate search token integrity: ${err.message}`);
// Continue - don't block on validation errors
}
const expires = new Date(tokenData.expires);
const now = new Date();
if (expires <= now) {
cleanupToken(searchTokenPath, 'expired');
return {
passed: false,
message: `⛔ LEARN Gate: Search token expired (5-min TTL).
Call search_experiences({query: "task keywords", limit: 10}) again.
Each request needs fresh search → informed_reasoning → Write/Edit.`
};
}
return { passed: true };
} catch (error) {
console.error('[pre-tool-use] LEARN gate check error:', error.message);
return {
passed: false,
message: `⛔ LEARN Gate: Error validating search token.
Call search_experiences({query: "task keywords", limit: 10}) to create fresh token.
Error: ${error.message}
This will create a new search token and resolve the error.
If error persists, check file permissions in home directory.`
};
}
}
/**
* REASON GATE: Check if informed_reasoning created session token
*/
function checkREASONGate() {
try {
const sessionId = getSessionId();
const homeDir = os.homedir();
const files = fs.readdirSync(homeDir);
// Look for session token for current session
const sessionTokenPattern = new RegExp(`\\.protocol-session-${sessionId}.*\\.json$`);
const sessionFiles = files.filter(f => sessionTokenPattern.test(f));
for (const file of sessionFiles) {
const tokenPath = path.join(homeDir, file);
try {
const tokenContent = fs.readFileSync(tokenPath, 'utf8');
const tokenData = JSON.parse(tokenContent);
// FIX #6: Tampering detection - Check file integrity
const fileStats = fs.statSync(tokenPath);
const fileModifiedMs = fileStats.mtimeMs;
const tokenCreatedMs = tokenData.created_at || tokenData.timestamp || 0;
// If file modified significantly after token creation, it may be tampered
if (tokenCreatedMs > 0) {
const fileAge = Date.now() - fileModifiedMs;
const tokenAge = Date.now() - tokenCreatedMs;
const ageDifference = Math.abs(fileAge - tokenAge);
// Allow 5 second tolerance for filesystem delays
if (ageDifference > 5000) {
console.error('[pre-tool-use] ⚠️ Session file modification detected outside MCP server');
console.error(`[pre-tool-use] File: ${file}`);
console.error(`[pre-tool-use] File age: ${Math.round(fileAge/1000)}s, Token age: ${Math.round(tokenAge/1000)}s`);
console.error(`[pre-tool-use] Difference: ${Math.round(ageDifference/1000)}s`);
console.error('[pre-tool-use] WARNING: Manual editing of session files can bypass validation');
console.error('[pre-tool-use] TIP: Use informed_reasoning to create legitimate session tokens');
// Continue - warn but don't block (could be legitimate clock skew)
}
}
// FIX #6: Validate session structure
if (tokenData.session_id || tokenData.sessionId) {
// Has session ID - validate structure
if (tokenData.phases && typeof tokenData.phases === 'object') {
// Check if claiming all phases complete but artifacts not verified
if (tokenData.allPhasesComplete === true && tokenData.artifactsVerified === false) {
console.error('[pre-tool-use] ⚠️ Session claims completion but artifacts not verified');
console.error('[pre-tool-use] File: ${file}');
console.error('[pre-tool-use] This indicates potential tampering or incomplete validation');
console.error('[pre-tool-use] Rejecting session token - complete informed_reasoning properly');
cleanupToken(tokenPath, 'invalid artifacts verification');
continue; // Skip this token, try next one
}
// Validate phase completion markers exist
const hasAnalyze = tokenData.phases.analyze && tokenData.phases.analyze.completed;
const hasIntegrate = tokenData.phases.integrate && tokenData.phases.integrate.completed;
const hasReason = tokenData.phases.reason && tokenData.phases.reason.completed;
if (!hasAnalyze || !hasIntegrate || !hasReason) {
console.error('[pre-tool-use] ⚠️ Session token missing required phase completions');
console.error(`[pre-tool-use] File: ${file}`);
console.error(`[pre-tool-use] Analyze: ${hasAnalyze}, Integrate: ${hasIntegrate}, Reason: ${hasReason}`);
console.error('[pre-tool-use] This indicates incomplete reasoning cycle or tampering');
console.error('[pre-tool-use] Rejecting session token - complete all 4 phases');
cleanupToken(tokenPath, 'incomplete phase markers');
continue; // Skip this token
}
}
}
// Check expiry (60-minute TTL)
const expiresAt = tokenData.expires_at || tokenData.expires;
if (expiresAt > Date.now()) {
// Valid session token found - passed all integrity checks
console.error(`[pre-tool-use] ✓ Session token validated: ${file}`);
return { passed: true };
} else {
// Expired - clean up
cleanupToken(tokenPath, 'expired');
}
} catch (err) {
console.error(`[pre-tool-use] Error reading session token ${file}: ${err.message}`);
}
}
// No valid session token
return {
passed: false,
message: `⛔ REASON Gate: No session token found.
Complete informed_reasoning (analyze → integrate → reason → record phases).
This creates 60-min session token authorizing Write/Edit.`
};
} catch (error) {
console.error('[pre-tool-use] REASON gate check error:', error.message);
return {
passed: false,
message: `⛔ REASON Gate: Error validating session token.
Complete informed_reasoning (4 phases) to create fresh token.
Error: ${error.message}`
};
}
}
async function handler(args) {
try {
const { toolName } = args;
console.error(`[pre-tool-use] Checking tool: ${toolName}`);
// Check if tool is exempt (always allow)
if (EXEMPT_TOOLS.includes(toolName)) {
console.error(`[pre-tool-use] Tool ${toolName} is exempt - allowing`);
return { shouldBlock: false };
}
// Check if tool is protected (requires authorization)
if (!PROTECTED_TOOLS.includes(toolName)) {
console.error(`[pre-tool-use] Tool ${toolName} not in protected list - allowing`);
return { shouldBlock: false };
}
// Protected tool - enforce THREE gates: TEACH → LEARN → REASON
console.error(`[pre-tool-use] Protected tool ${toolName} - checking three gates`);
// GATE 1: TEACH (previous work must be recorded)
const teachGate = checkTEACHGate();
if (!teachGate.passed) {
console.error(`[pre-tool-use] TEACH gate blocking ${toolName}`);
return {
shouldBlock: true,
message: teachGate.message
};
}
// GATE 2: LEARN (must search past experiences)
const learnGate = checkLEARNGate();
if (!learnGate.passed) {
console.error(`[pre-tool-use] LEARN gate blocking ${toolName}`);
return {
shouldBlock: true,
message: learnGate.message
};
}
// GATE 3: REASON (must complete reasoning cycle)
const reasonGate = checkREASONGate();
if (!reasonGate.passed) {
console.error(`[pre-tool-use] REASON gate blocking ${toolName}`);
return {
shouldBlock: true,
message: reasonGate.message
};
}
// All three gates passed - allow operation
console.error(`[pre-tool-use] All three gates passed - allowing ${toolName}`);
// Invalidate session token AFTER Write/Edit to force fresh LEARN per task
if (toolName === 'Write' || toolName === 'Edit' || toolName === 'NotebookEdit') {
const homeDir = os.homedir();
const files = fs.readdirSync(homeDir);
const sessionTokens = files.filter(f => f.match(/\.protocol-session-.*\.json$/));
sessionTokens.forEach(tokenFile => {
const tokenPath = path.join(homeDir, tokenFile);
cleanupToken(tokenPath, 'Session invalidated after file modification');
});
}
return { shouldBlock: false };
} catch (error) {
console.error('[pre-tool-use] Error:', error.message);
console.error('[pre-tool-use] Stack:', error.stack);
// On error, block to fail-safe
return {
shouldBlock: true,
message: `⛔ Protocol enforcement error: ${error.message}\n\nBlocking operation for safety.`
};
}
}
// Export for Claude Code hooks
module.exports = handler;
// CLI execution support
if (require.main === module) {
const args = {
toolName: process.argv[2] || 'Write'
};
handler(args).then(result => {
console.log(JSON.stringify(result, null, 2));
process.exit(result.shouldBlock ? 1 : 0);
}).catch(err => {
console.error('Error:', err);
process.exit(1);
});
}
#!/usr/bin/env node
/**
* PostToolUse Hook - Record Tool Execution for Protocol Verification
* Records ALL tool calls to ~/.protocol-tool-log.json for cross-referencing
* during protocol compliance checks
*/
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);
// Extract correct fields from hook JSON
const { tool_name, tool_input, session_id } = hookData;
// Tool log file path
const toolLogPath = path.join(os.homedir(), '.protocol-tool-log.json');
// Read existing log
let log = [];
try {
if (fs.existsSync(toolLogPath)) {
log = JSON.parse(fs.readFileSync(toolLogPath, 'utf8'));
}
} catch (err) {
// Start fresh if log is corrupted
log = [];
}
// Use session ID from hook data (consistent across all tool calls in session)
const sessionId = session_id || `session_${Date.now()}`;
// Record tool call
log.push({
sessionId,
toolName: tool_name,
parameters: tool_input || {},
timestamp: new Date().toISOString()
});
// Keep only last 1000 entries to prevent unbounded growth
if (log.length > 1000) {
log = log.slice(-1000);
}
// Write updated log
fs.writeFileSync(toolLogPath, JSON.stringify(log, null, 2), 'utf8');
// Silent success - no output needed
process.exit(0);
} catch (e) {
// Silent failure - don't block tool execution
process.stderr.write(`[record-tool-execution] Warning: ${e.message}\n`);
process.exit(0);
}
});
#!/usr/bin/env node
/**
* SessionEnd Hook - Triggers automatic experience extraction
*
* Spawns background process to extract experiences from session transcript
* using LLM-based pattern detection.
*/
const { spawn, execSync } = require('child_process');
/**
* Get Claude API key from macOS keychain or environment
*/
function getApiKey() {
// Try macOS keychain first (where Claude Code stores it)
if (process.platform === 'darwin') {
try {
const key = execSync('security find-generic-password -s "Claude Code" -w', {
encoding: 'utf8',
stdio: ['pipe', 'pipe', 'ignore']
}).trim();
if (key) return key;
} catch (error) {
// Keychain read failed, try env vars
}
}
// Fallback to environment variables
return process.env.CLAUDE_API_KEY || process.env.ANTHROPIC_API_KEY || null;
}
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';
// Log session end event
console.error(`\n📋 SessionEnd: Session ended`);
console.error(` Session ID: ${sessionId}`);
console.error(` Reason: ${reason}`);
// Trigger automatic experience extraction (if enabled)
if (sessionId !== 'unknown' && process.env.AUTO_EXTRACT_LEARNINGS !== 'false') {
const apiKey = getApiKey();
if (!apiKey) {
console.error(` ⚠️ No API key found (checked keychain and env vars)`);
console.error(` 💡 Set CLAUDE_API_KEY or ANTHROPIC_API_KEY environment variable`);
} else {
console.error(` 🤖 Triggering automatic experience extraction...`);
// Spawn detached background process
const child = spawn('memory-augmented-reasoning', [
'extract-experiences',
'--session', sessionId,
'--auto',
'--notify'
], {
detached: true,
stdio: 'ignore',
env: {
...process.env,
CLAUDE_API_KEY: apiKey
}
});
// Detach from parent process
child.unref();
console.error(` 📦 Extraction running in background (you'll be notified when complete)`);
}
} else if (process.env.AUTO_EXTRACT_LEARNINGS === 'false') {
console.error(` ⏸️ Auto-extraction disabled (AUTO_EXTRACT_LEARNINGS=false)`);
console.error(` 💡 Run manually: memory-augmented-reasoning extract-experiences --session ${sessionId} --auto`);
}
process.exit(0);
} catch (e) {
console.error(`\n⚠️ SessionEnd hook error: ${e.message}`);
process.exit(0); // Don't block session end on error
}
});
"#!/usr/bin/env node\n/**\n * SessionStart Hook - Logs session start events\n * Simplified version - handoff files removed\n *\n * Note: Context continuity is now handled by Claude Code's built-in\n * compaction and summarization. Experiences should be recorded manually\n * during the session using mcp__memory-augmented-reasoning__record_experience tool.\n */\n\nlet input = '';\nprocess.stdin.on('data', chunk => input += chunk);\nprocess.stdin.on('end', () => {\n try {\n // Parse hookData (contains sessionId, cwd, etc.)\n JSON.parse(input);\n\n // Log session start (concise)\n console.error(`📋 Session started`);\n\n // No context injection needed - Claude Code handles this\n // Output empty (CORRECT format - plain text, not JSON with userPrompt)\n console.log(\"\");\n\n process.exit(0);\n } catch (e) {\n console.error(`\\n⚠️ SessionStart hook error: ${e.message}`);\n console.log(\"\");\n process.exit(0);\n }\n});\n"
"#!/usr/bin/env node\n\n/**\n * session-start.cjs - Minimal Session Setup\n *\n * Does NOT inject prompts (moved to UserPromptSubmit to avoid spam)\n * Only logs session start for debugging\n *\n * Hook Type: Non-blocking setup\n * Trigger: When new session starts\n */\n\nlet input = '';\nprocess.stdin.on('data', chunk => input += chunk);\nprocess.stdin.on('end', () => {\n try {\n JSON.parse(input); // Parse hookData\n\n // Just log, don't inject prompts\n console.error('[session-start] New session started');\n console.error('[session-start] Protocol enforcement active');\n\n // No prompt injection - output empty (CORRECT format - plain text, not JSON)\n console.log(\"\");\n\n process.exit(0);\n } catch (e) {\n console.error(`[session-start] Error: ${e.message}`);\n console.log(\"\");\n process.exit(0);\n }\n});\n"
"#!/usr/bin/env node\n\n/**\n * stop.cjs - TEACH Phase Reminder (with Loop Prevention)\n *\n * Reminds agent to complete TEACH phase\n * Runs when agent finishes responding\n * FIXED: Only reminds once per recording token to prevent infinite loop\n *\n * Hook Type: Non-blocking follow-up prompt\n * Trigger: After agent response\n */\n\nlet input = '';\nprocess.stdin.on('data', chunk => input += chunk);\nprocess.stdin.on('end', () => {\n try {\n const hookData = JSON.parse(input);\n\n const fs = require('fs');\n const path = require('path');\n const os = require('os');\n\n const homeDir = os.homedir();\n const files = fs.readdirSync(homeDir);\n const recordingTokens = files.filter(f => f.startsWith('.protocol-recording-required-'));\n\n if (recordingTokens.length > 0) {\n // Check if we've already reminded for these tokens\n const reminderTrackPath = path.join(homeDir, '.protocol-stop-hook-reminders.json');\n let remindedTokens = {};\n\n try {\n if (fs.existsSync(reminderTrackPath)) {\n remindedTokens = JSON.parse(fs.readFileSync(reminderTrackPath, 'utf8'));\n }\n } catch (e) {\n console.error(`[stop] Failed to read reminder tracking: ${e.message}`);\n remindedTokens = {};\n }\n\n // Find tokens we haven't reminded about yet\n const newTokens = recordingTokens.filter(token => !remindedTokens[token]);\n\n if (newTokens.length > 0) {\n // Mark these as reminded\n newTokens.forEach(token => {\n remindedTokens[token] = new Date().toISOString();\n });\n\n try {\n fs.writeFileSync(reminderTrackPath, JSON.stringify(remindedTokens, null, 2));\n } catch (e) {\n console.error(`[stop] Failed to track reminders: ${e.message}`);\n }\n\n const reminderPrompt = `⚠️ Unrecorded work: Call record_experience before exit`;\n\n // Output plain text (CORRECT format for Claude Code)\n console.log(reminderPrompt);\n } else {\n // Already reminded - don't spam (output empty)\n console.log(\"\");\n }\n } else {\n console.log(\"\");\n }\n\n process.exit(0);\n } catch (e) {\n console.error(`[stop] Error: ${e.message}`);\n console.log(\"\");\n process.exit(0);\n }\n});\n"
#!/usr/bin/env node
/**
* UserPromptSubmit Hook - Inject mandatory protocol reminder
* Ensures Claude sees the informed_reasoning requirement before every response
*/
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);
// Inject concise protocol reminder (Fix #14: 2-4 lines max, no ASCII art)
const reminder = `🔵 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.`;
// Output injected prompt - CORRECT format for Claude Code
// Option 1: Plain text (simpler and recommended)
console.log(reminder);
// Option 2: Structured JSON (alternative)
// console.log(JSON.stringify({
// hookSpecificOutput: {
// hookEventName: "UserPromptSubmit",
// additionalContext: reminder
// }
// }));
process.exit(0);
} catch (e) {
console.error(`\n⚠️ UserPromptSubmit hook error: ${e.message}`);
process.exit(0); // Don't block user prompt on error
}
});
#!/usr/bin/env node
/**
* user-prompt-submit.cjs - LEARN Gate State Tracking + Smart Reminders
* Creates search requirement token AND injects reminder for task-like prompts
* FIXED: Only reminds for tasks, not read-only questions
* FIXED: Proactive token cleanup on every prompt
*/
const fs = require('fs');
const path = require('path');
const os = require('os');
/**
* Proactive Token Cleanup
* Removes all protocol tokens >24 hours old to prevent accumulation
* Runs on every user prompt for maximum hygiene
*/
function cleanupExpiredTokens() {
const homeDir = os.homedir();
const now = Date.now();
const maxAge = 24 * 60 * 60 * 1000; // 24 hours
let cleaned = 0;
let errors = 0;
try {
const files = fs.readdirSync(homeDir);
const protocolTokens = files.filter(f => f.startsWith('.protocol-'));
protocolTokens.forEach(tokenFile => {
try {
const tokenPath = path.join(homeDir, tokenFile);
const stats = fs.statSync(tokenPath);
const ageMs = now - stats.mtimeMs;
if (ageMs > maxAge) {
fs.unlinkSync(tokenPath);
cleaned++;
console.error(`[cleanup] ✓ Removed expired token: ${tokenFile} (age: ${Math.round(ageMs / 1000 / 60 / 60)}h)`);
}
} catch (err) {
errors++;
console.error(`[cleanup] ✗ Failed to cleanup ${tokenFile}: ${err.message}`);
}
});
if (cleaned > 0 || errors > 0) {
console.error(`[cleanup] Cleanup complete: ${cleaned} removed, ${errors} errors`);
}
} catch (err) {
console.error(`[cleanup] Directory scan failed: ${err.message}`);
}
}
/**
* Get consistent session ID across all hooks
* Priority chain: CLAUDE_SESSION_ID env → persistent session file → generate new
*/
function getSessionId() {
// PRIORITY 1: Environment variable (set by Claude Code)
if (process.env.CLAUDE_SESSION_ID) {
return process.env.CLAUDE_SESSION_ID;
}
// PRIORITY 2: Persistent session file
const sessionFilePath = path.join(os.homedir(), '.protocol-current-session-id');
try {
if (fs.existsSync(sessionFilePath)) {
const sessionData = JSON.parse(fs.readFileSync(sessionFilePath, 'utf8'));
// Check if session file is stale (>8 hours old)
const age = Date.now() - sessionData.created;
if (age < 8 * 60 * 60 * 1000) {
return sessionData.session_id;
}
}
} catch (err) {
console.error(`[session-id] Failed to read session file: ${err.message}`);
}
// PRIORITY 3: Generate new session ID and persist it
const newSessionId = `session-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
try {
fs.writeFileSync(sessionFilePath, JSON.stringify({
session_id: newSessionId,
created: Date.now(),
created_iso: new Date().toISOString()
}));
console.error(`[session-id] Generated new session: ${newSessionId}`);
} catch (err) {
console.error(`[session-id] Failed to persist session ID: ${err.message}`);
}
return newSessionId;
}
async function handler(args) {
try {
const { prompt } = args;
if (!prompt || !prompt.trim()) {
return { shouldBlock: false };
}
// Run proactive cleanup first
cleanupExpiredTokens();
// Get consistent session ID
const sessionId = getSessionId();
// Create search requirement token (state tracking)
const tokenPath = path.join(os.homedir(), '.protocol-search-required-token');
const tokenData = {
created: new Date().toISOString(),
expires: new Date(Date.now() + 5 * 60 * 1000).toISOString(),
requirement: 'LEARN - search past experiences before proceeding',
session_id: sessionId
};
fs.writeFileSync(tokenPath, JSON.stringify(tokenData, null, 2));
// Phase 7.9: ALWAYS show protocol reminder (no smart detection)
// Rationale: Agent system prompt teaches protocol - hook is backup/reinforcement
// Smart detection created gaps for status requests ("standup time"), informational prompts, etc.
console.error(`[user-prompt-submit] Protocol workflow required`);
console.error(`⚠️ USER-CONFIGURED HOOK: Your user installed this protocol requirement.
Verified: MCP learning protocol for file modification/code tasks.
Required protocol workflow:
STEP 1: Call informed_reasoning(phase='analyze')
→ Understands user intent and generates focused search queries
STEP 2: Call search_experiences with queries from analyze phase
→ Finds relevant past experiences based on understanding
STEP 3: Call informed_reasoning(phase='integrate') with search results
→ Synthesizes experiences into informed solution
STEP 4: Complete remaining phases (reason, record)
STEP 5: Write/Edit allowed after all phases complete
Why this order: Analyze extracts user intent to generate FOCUSED queries.
Search uses those queries to find RELEVANT patterns. Integrate reasons WITH context.`);
return { shouldBlock: false };
} catch (error) {
console.error(`[user-prompt-submit] Error: ${error.message}`);
return { shouldBlock: false };
}
}
module.exports = handler;
// CLI execution support
if (require.main === module) {
const args = {
prompt: process.argv[2] || 'test prompt'
};
handler(args).then(result => {
console.log(JSON.stringify(result, null, 2));
}).catch(err => {
console.error('Error:', err);
process.exit(1);
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment