{
  "destructive-guard": "#!/bin/bash\n# ================================================================\n# destructive-guard.sh — Destructive Command Blocker\n# ================================================================\n# PURPOSE:\n#   Blocks dangerous shell commands that can cause irreversible damage.\n#   Catches rm -rf on sensitive paths, git reset --hard, git clean -fd,\n#   and other destructive operations before they execute.\n#\n#   Built after a real incident where rm -rf on a pnpm project\n#   followed NTFS junctions and deleted an entire C:\\Users directory.\n#   (GitHub Issue #36339)\n#\n# TRIGGER: PreToolUse\n# MATCHER: \"Bash\"\n#\n# WHAT IT BLOCKS (exit 2):\n#   - rm -rf / rm -r on root, home, or parent paths (/, ~, .., /home, /etc)\n#   - git reset --hard\n#   - git clean -fd / git clean -fdx\n#   - chmod -R 777 on sensitive paths\n#   - find ... -delete on broad patterns\n#\n# WHAT IT ALLOWS (exit 0):\n#   - rm -rf on specific project subdirectories (node_modules, dist, build)\n#   - git reset --soft, git reset HEAD\n#   - All non-destructive commands\n#\n# CONFIGURATION:\n#   CC_ALLOW_DESTRUCTIVE=1 — disable this guard (not recommended)\n#   CC_SAFE_DELETE_DIRS — colon-separated list of safe-to-delete dirs\n#     default: \"node_modules:dist:build:.cache:__pycache__:coverage\"\n#\n# NOTE: On Windows/WSL2, rm -rf can follow NTFS junctions (symlinks)\n# and delete far more than intended. This guard is especially critical\n# on WSL2 environments.\n# ================================================================\n\nINPUT=$(cat)\nCOMMAND=$(printf '%s' \"$INPUT\" | jq -r '.tool_input.command // empty' 2>/dev/null)\n\nif [[ -z \"$COMMAND\" ]]; then\n    exit 0\nfi\n\n# Allow override (not recommended)\nif [[ \"${CC_ALLOW_DESTRUCTIVE:-0}\" == \"1\" ]]; then\n    exit 0\nfi\n\n# Log function — records blocked commands for audit\nlog_block() {\n    local reason=\"$1\"\n    local logfile=\"${CC_BLOCK_LOG:-$HOME/.claude/blocked-commands.log}\"\n    mkdir -p \"$(dirname \"$logfile\")\" 2>/dev/null\n    echo \"[$(date -Iseconds)] BLOCKED: $reason | cmd: $COMMAND\" >> \"$logfile\" 2>/dev/null\n}\n\n# Safe directories that can be deleted\nSAFE_DIRS=\"${CC_SAFE_DELETE_DIRS:-node_modules:dist:build:.cache:__pycache__:coverage:.next:.nuxt:tmp}\"\n\n# --- Check 0: --no-preserve-root ---\nif printf '%s' \"$COMMAND\" | grep -qE \"rm\\\\s.*\\\\-\\\\-no-preserve-root\"; then\n    echo \"BLOCKED: --no-preserve-root detected.\" >&2\n    exit 2\nfi\n\n# --- Check 1: rm -rf on dangerous paths ---\nif printf '%s' \"$COMMAND\" | grep -qE 'rm\\s+(-[rf]+\\s+)*(\\/$|\\/\\s|\\/[^a-z]|\\/home|\\/etc|\\/usr|\\/var|\\/mnt|~\\/|~\\s*$|\\.\\.\\/|\\.\\.\\s*$|\\.\\s*$|\\.\\/\\s*$)'; then\n    # Exception: safe directories\n    SAFE=0\n    IFS=':' read -ra DIRS <<< \"$SAFE_DIRS\"\n    for dir in \"${DIRS[@]}\"; do\n        if printf '%s' \"$COMMAND\" | grep -qE \"rm\\s+.*${dir}\\s*$|rm\\s+.*${dir}/\"; then\n            SAFE=1\n            break\n        fi\n    done\n\n    # Check for mounted filesystems inside the target (NFS, Docker, bind mounts)\n    # Why: GitHub #36640 — rm -rf on a dir with NFS mount deleted production data\n    if (( SAFE == 0 )); then\n        # Extract the target path from the rm command\n        TARGET_PATH=$(printf '%s' \"$COMMAND\" | grep -oP 'rm\\s+(-[rf]+\\s+)*\\K\\S+')\n        if [ -n \"$TARGET_PATH\" ] && command -v findmnt &>/dev/null; then\n            if findmnt -n -o TARGET --submounts \"$TARGET_PATH\" 2>/dev/null | grep -q .; then\n                log_block \"rm on path with mounted filesystem\"\n                echo \"BLOCKED: Target contains a mounted filesystem (NFS, Docker, bind).\" >&2\n                echo \"\" >&2\n                echo \"Command: $COMMAND\" >&2\n                echo \"\" >&2\n                echo \"Unmount the filesystem first, then retry.\" >&2\n                exit 2\n            fi\n        fi\n    fi\n\n    if (( SAFE == 0 )); then\n        log_block \"rm on sensitive path\"\n        echo \"BLOCKED: rm on sensitive path detected.\" >&2\n        echo \"\" >&2\n        echo \"Command: $COMMAND\" >&2\n        echo \"\" >&2\n        echo \"This command targets a sensitive directory that could cause\" >&2\n        echo \"irreversible data loss. On WSL2, rm -rf can follow NTFS\" >&2\n        echo \"junctions and delete far beyond the target directory.\" >&2\n        echo \"\" >&2\n        echo \"If you need to delete a specific subdirectory, target it directly:\" >&2\n        echo \"  rm -rf ./specific-folder\" >&2\n        exit 2\n    fi\nfi\n\n# --- Check 2: git reset --hard ---\n# Only match when git is the actual command, not inside strings/arguments\nif printf '%s' \"$COMMAND\" | grep -qE '^\\s*git\\s+reset\\s+--hard|;\\s*git\\s+reset\\s+--hard|&&\\s*git\\s+reset\\s+--hard|\\|\\|\\s*git\\s+reset\\s+--hard'; then\n    log_block \"git reset --hard\"\n    echo \"BLOCKED: git reset --hard discards all uncommitted changes.\" >&2\n    echo \"\" >&2\n    echo \"Command: $COMMAND\" >&2\n    echo \"\" >&2\n    echo \"Consider: git stash, or git reset --soft to keep changes staged.\" >&2\n    exit 2\nfi\n\n# --- Check 3: git clean -fd ---\nif printf '%s' \"$COMMAND\" | grep -qE '^\\s*git\\s+clean\\s+-[a-z]*[fd]|;\\s*git\\s+clean|&&\\s*git\\s+clean|\\|\\|\\s*git\\s+clean'; then\n    log_block \"git clean\"\n    echo \"BLOCKED: git clean removes untracked files permanently.\" >&2\n    echo \"\" >&2\n    echo \"Command: $COMMAND\" >&2\n    echo \"\" >&2\n    echo \"Consider: git clean -n (dry run) first to see what would be deleted.\" >&2\n    exit 2\nfi\n\n# --- Check 4: chmod 777 on broad paths ---\nif printf '%s' \"$COMMAND\" | grep -qE 'chmod\\s+(-R\\s+)?777\\s+(\\/|~|\\.)'; then\n    echo \"BLOCKED: chmod 777 on broad path is a security risk.\" >&2\n    echo \"\" >&2\n    echo \"Command: $COMMAND\" >&2\n    exit 2\nfi\n\n# --- Check 5: find -delete on broad patterns ---\nif printf '%s' \"$COMMAND\" | grep -qE 'find\\s+(\\/|~|\\.\\.)\\s.*-delete'; then\n    echo \"BLOCKED: find -delete on broad path risks mass deletion.\" >&2\n    echo \"\" >&2\n    echo \"Command: $COMMAND\" >&2\n    echo \"\" >&2\n    echo \"Consider: find ... -print first to verify what matches.\" >&2\n    exit 2\nfi\n\n# --- Check 6: sudo with dangerous commands ---\nif printf '%s' \"$COMMAND\" | grep -qE '^\\s*sudo\\s+(rm\\s+-[rf]|chmod\\s+(-R\\s+)?777|dd\\s+if=|mkfs)'; then\n    log_block \"sudo with dangerous command\"\n    echo \"BLOCKED: sudo with dangerous command detected.\" >&2\n    echo \"\" >&2\n    echo \"Command: $COMMAND\" >&2\n    echo \"\" >&2\n    echo \"Running destructive commands with sudo amplifies the damage.\" >&2\n    echo \"Review the command carefully before proceeding.\" >&2\n    exit 2\nfi\n\n\n# --- Check 7: PowerShell Remove-Item (Windows/WSL2) ---\n# Real incident: GitHub #37331 — destroyed entire repo\n# Skip if command is git commit (message text triggers false positive)\nif printf '%s' \"$COMMAND\" | grep -qE '^\\s*(git\\s+commit|echo\\s|printf\\s|cat\\s)'; then\n    :  # string output commands mentioning PS commands are not destructive\nelif printf '%s' \"$COMMAND\" | grep -qiE 'Remove-Item.*-Recurse.*-Force|Remove-Item.*-Force.*-Recurse|del\\s+/s\\s+/q|rd\\s+/s\\s+/q|rmdir\\s+/s\\s+/q'; then\n    log_block \"PowerShell destructive command\"\n    echo \"BLOCKED: Destructive PowerShell command detected.\" >&2\n    echo \"\" >&2\n    echo \"Command: $COMMAND\" >&2\n    echo \"\" >&2\n    echo \"Remove-Item with recursive force-delete can destroy entire directories\" >&2\n    echo \"irreversibly. Target specific files instead.\" >&2\n    exit 2\nfi\nif printf '%s' \"$COMMAND\" | grep -qE '(^|;|&&|\\|\\|)\\s*git\\s+(checkout|switch)\\s+.*(--force\\b|-f\\b|--discard-changes\\b)'; then\n    log_block \"git checkout/switch --force\"\n    echo \"BLOCKED: git checkout/switch with --force discards uncommitted changes.\" >&2\n    echo \"\" >&2\n    echo \"Command: $COMMAND\" >&2\n    echo \"\" >&2\n    echo \"Consider: git stash before switching, or use git switch without --force.\" >&2\n    exit 2\nfi\n\nif printf '%s' \"$COMMAND\" | grep -qE '(sh|bash|zsh)\\s+-c\\s+'; then\n    INNER=$(printf '%s' \"$COMMAND\" | sed -E \"s/.*(sh|bash|zsh)\\s+-c\\s+['\\\"]//\" | sed \"s/['\\\"]*$//\" )\n    if echo \"$INNER\" | grep -qE 'rm\\s+-[rf]*\\s+[/~]|git\\s+reset\\s+--hard|git\\s+clean\\s+-[fd]+|mkfs\\.|dd\\s+if='; then\n        echo \"BLOCKED: Destructive command hidden in shell wrapper\" >&2\n        echo \"\" >&2\n        echo \"Detected: $INNER\" >&2\n        exit 2\n    fi\nfi\nif printf '%s' \"$COMMAND\" | grep -qE '\\|\\s*(sh|bash)\\s*$'; then\n    if printf '%s' \"$COMMAND\" | grep -qE 'rm\\s+-[rf]*\\s+[/~]|git\\s+reset\\s+--hard|git\\s+clean\\s+-[fd]+'; then\n        echo \"BLOCKED: Destructive command piped to shell\" >&2\n        exit 2\n    fi\nfi\nexit 0",
  "branch-guard": "#!/bin/bash\n# ================================================================\n# branch-guard.sh — Branch Push Protector\n# ================================================================\n# PURPOSE:\n#   Prevents accidental git push to main/master branches AND\n#   blocks force-push on ALL branches without explicit approval.\n#\n#   Force-pushes rewrite history and can destroy teammates' work.\n#   Protected branch pushes bypass code review.\n#\n# TRIGGER: PreToolUse\n# MATCHER: \"Bash\"\n#\n# WHAT IT BLOCKS (exit 2):\n#   - git push origin main/master (any protected branch)\n#   - git push --force (any branch — history rewriting)\n#   - git push -f (short flag variant)\n#   - git push --force-with-lease (still destructive)\n#\n# WHAT IT ALLOWS (exit 0):\n#   - git push origin feature-branch (non-force)\n#   - git push -u origin feature-branch\n#   - All other git commands\n#   - All non-git commands\n#\n# CONFIGURATION:\n#   CC_PROTECT_BRANCHES — colon-separated list of protected branches\n#     default: \"main:master\"\n#   CC_ALLOW_FORCE_PUSH=1 — disable force-push protection\n# ================================================================\n\nINPUT=$(cat)\nCOMMAND=$(printf '%s' \"$INPUT\" | jq -r '.tool_input.command // empty' 2>/dev/null)\n\nif [[ -z \"$COMMAND\" ]]; then\n    exit 0\nfi\n\n# Only check git push commands\nif ! printf '%s' \"$COMMAND\" | grep -qE '^\\s*git\\s+push'; then\n    exit 0\nfi\n\n# --- Check 1: Force push on ANY branch ---\nif [[ \"${CC_ALLOW_FORCE_PUSH:-0}\" != \"1\" ]]; then\n    if printf '%s' \"$COMMAND\" | grep -qE 'git\\s+push\\s+.*(-f\\b|--force\\b|--force-with-lease\\b)'; then\n        echo \"BLOCKED: Force push detected.\" >&2\n        echo \"\" >&2\n        echo \"Command: $COMMAND\" >&2\n        echo \"\" >&2\n        echo \"Force push rewrites remote history and can destroy\" >&2\n        echo \"other people's work. This is almost never what you want.\" >&2\n        echo \"\" >&2\n        echo \"If you truly need to force push, set CC_ALLOW_FORCE_PUSH=1\" >&2\n        exit 2\n    fi\nfi\n\n# --- Check 2: Push to protected branches ---\nPROTECTED=\"${CC_PROTECT_BRANCHES:-main:master}\"\n\nBLOCKED=0\nIFS=':' read -ra BRANCHES <<< \"$PROTECTED\"\nfor branch in \"${BRANCHES[@]}\"; do\n    if printf '%s' \"$COMMAND\" | grep -qwE \"origin\\s+${branch}|${branch}\\s|${branch}$\"; then\n        BLOCKED=1\n        break\n    fi\ndone\n\nif (( BLOCKED == 1 )); then\n    echo \"BLOCKED: Attempted push to protected branch.\" >&2\n    echo \"\" >&2\n    echo \"Command: $COMMAND\" >&2\n    echo \"\" >&2\n    echo \"Protected branches: $PROTECTED\" >&2\n    echo \"\" >&2\n    echo \"Push to a feature branch first, then create a pull request.\" >&2\n    exit 2\nfi\n\nexit 0\n",
  "syntax-check": "#!/bin/bash\n# ================================================================\n# syntax-check.sh — Automatic Syntax Validation After Edits\n# ================================================================\n# PURPOSE:\n#   Runs syntax checks immediately after Claude Code edits or\n#   writes a file. Catches syntax errors before they propagate\n#   into downstream failures.\n#\n# SUPPORTED LANGUAGES:\n#   .py    — python -m py_compile\n#   .sh    — bash -n\n#   .bash  — bash -n\n#   .json  — jq empty\n#   .yaml  — python3 yaml.safe_load (if PyYAML installed)\n#   .yml   — python3 yaml.safe_load (if PyYAML installed)\n#   .js    — node --check (if node installed)\n#   .ts    — npx tsc --noEmit (if tsc available) [EXPERIMENTAL]\n#\n# TRIGGER: PostToolUse\n# MATCHER: \"Edit|Write\"\n#\n# DESIGN PHILOSOPHY:\n#   - Never blocks (always exit 0) — reports errors but doesn't\n#     prevent the edit from completing\n#   - Silent on success — only speaks up when something is wrong\n#   - Fails open — if a checker isn't installed, silently skips\n#\n# BORN FROM:\n#   Countless sessions where Claude Code introduced a syntax error,\n#   continued working for 10+ tool calls, then hit a wall when\n#   trying to run the broken file. Catching it immediately saves\n#   context window and frustration.\n# ================================================================\n\nINPUT=$(cat)\nFILE_PATH=$(printf '%s' \"$INPUT\" | jq -r '.tool_input.file_path // empty' 2>/dev/null)\n\n# No file path = nothing to check\nif [[ -z \"$FILE_PATH\" || ! -f \"$FILE_PATH\" ]]; then\n    exit 0\nfi\n\nEXT=\"${FILE_PATH##*.}\"\n\ncase \"$EXT\" in\n    py)\n        if python3 -m py_compile \"$FILE_PATH\" 2>&1; then\n            :  # silent on success\n        else\n            echo \"SYNTAX ERROR (Python): $FILE_PATH\" >&2\n        fi\n        ;;\n    sh|bash)\n        if bash -n \"$FILE_PATH\" 2>&1; then\n            :\n        else\n            echo \"SYNTAX ERROR (Shell): $FILE_PATH\" >&2\n        fi\n        ;;\n    json)\n        if command -v jq &>/dev/null; then\n            if jq empty \"$FILE_PATH\" 2>&1; then\n                :\n            else\n                echo \"SYNTAX ERROR (JSON): $FILE_PATH\" >&2\n            fi\n        fi\n        ;;\n    yaml|yml)\n        if python3 -c \"import yaml\" 2>/dev/null; then\n            if python3 -c \"\nimport yaml, sys\nwith open(sys.argv[1]) as f:\n    yaml.safe_load(f)\n\" \"$FILE_PATH\" 2>&1; then\n                :\n            else\n                echo \"SYNTAX ERROR (YAML): $FILE_PATH\" >&2\n            fi\n        fi\n        ;;\n    js)\n        if command -v node &>/dev/null; then\n            if node --check \"$FILE_PATH\" 2>&1; then\n                :\n            else\n                echo \"SYNTAX ERROR (JavaScript): $FILE_PATH\" >&2\n            fi\n        fi\n        ;;\n    ts)\n        # EXPERIMENTAL: TypeScript check requires tsc in PATH\n        if command -v npx &>/dev/null; then\n            if npx tsc --noEmit \"$FILE_PATH\" 2>&1; then\n                :\n            else\n                echo \"SYNTAX ERROR (TypeScript) [experimental]: $FILE_PATH\" >&2\n            fi\n        fi\n        ;;\n    *)\n        # Unknown extension — skip silently\n        ;;\nesac\n\nexit 0\n",
  "context-monitor": "#!/bin/bash\n# ================================================================\n# context-monitor.sh — Context Window Remaining Capacity Monitor\n# ================================================================\n# PURPOSE:\n#   Monitors how much context window remains during a Claude Code\n#   session. Issues graduated warnings (CAUTION → WARNING → CRITICAL\n#   → EMERGENCY) so you never get killed by context exhaustion.\n#\n# HOW IT WORKS:\n#   1. Reads Claude Code's debug log to extract actual token usage\n#   2. Falls back to tool-call-count estimation when debug logs\n#      are unavailable\n#   3. Saves current % to /tmp/cc-context-pct (other scripts can\n#      read this)\n#   4. At CRITICAL/EMERGENCY, writes an evacuation template to\n#      your mission file so you can hand off state before /compact\n#\n# TRIGGER: PostToolUse (all tools)\n# MATCHER: \"\" (empty = every tool invocation)\n#\n# CONFIGURATION:\n#   CC_CONTEXT_MISSION_FILE — path to your mission/state file\n#     default: $HOME/mission.md\n#\n# THRESHOLDS (edit below to taste):\n#   CAUTION   = 40%  — be mindful of consumption\n#   WARNING   = 25%  — finish current task, save state\n#   CRITICAL  = 20%  — run /compact immediately\n#   EMERGENCY = 15%  — stop everything, evacuate\n#\n# BORN FROM:\n#   A session that hit 3% context remaining with no warning.\n#   The agent died mid-task and all in-flight work was lost.\n#   Never again.\n# ================================================================\n\nSTATE_FILE=\"/tmp/cc-context-state\"\nPCT_FILE=\"/tmp/cc-context-pct\"\nCOUNTER_FILE=\"/tmp/cc-context-monitor-count\"\nMISSION_FILE=\"${CC_CONTEXT_MISSION_FILE:-$HOME/mission.md}\"\n\n# Tool invocation counter (fallback estimator)\nCOUNT=$(cat \"$COUNTER_FILE\" 2>/dev/null || echo 0)\nCOUNT=$((COUNT + 1))\necho \"$COUNT\" > \"$COUNTER_FILE\"\n\n# Check every 3rd invocation to reduce overhead\n# (but always check in CRITICAL/EMERGENCY state)\nLAST_STATE=$(cat \"$STATE_FILE\" 2>/dev/null || echo \"normal\")\nif [ $((COUNT % 3)) -ne 0 ] && [ \"$LAST_STATE\" != \"critical\" ] && [ \"$LAST_STATE\" != \"emergency\" ]; then\n    exit 0\nfi\n\n# --- Extract context % from Claude Code debug logs ---\nget_context_pct() {\n    local debug_dir=\"$HOME/.claude/debug\"\n    if [ ! -d \"$debug_dir\" ]; then\n        echo \"\"\n        return\n    fi\n\n    local latest\n    latest=$(find \"$debug_dir\" -maxdepth 1 -name '*.txt' -printf '%T@ %p\\n' 2>/dev/null | sort -rn | head -1 | cut -d' ' -f2)\n    if [ -z \"$latest\" ]; then\n        echo \"\"\n        return\n    fi\n\n    # Parse the last autocompact entry for token counts\n    local line\n    line=$(grep 'autocompact:' \"$latest\" 2>/dev/null | tail -1)\n    if [ -z \"$line\" ]; then\n        echo \"\"\n        return\n    fi\n\n    local tokens window\n    tokens=$(echo \"$line\" | sed 's/.*tokens=\\([0-9]*\\).*/\\1/')\n    window=$(echo \"$line\" | sed 's/.*effectiveWindow=\\([0-9]*\\).*/\\1/')\n\n    if [ -n \"$tokens\" ] && [ -n \"$window\" ] && [ \"$window\" -gt 0 ] 2>/dev/null; then\n        local pct\n        pct=$(( (window - tokens) * 100 / window ))\n        echo \"$pct\"\n    else\n        echo \"\"\n    fi\n}\n\nCONTEXT_PCT=$(get_context_pct)\n\n# Fallback: estimate from tool call count when debug logs unavailable\n# Assumes ~180 tool calls fills ~100% of context (conservative)\nif [ -z \"$CONTEXT_PCT\" ]; then\n    CONTEXT_PCT=$(( 100 - (COUNT * 100 / 180) ))\n    if [ \"$CONTEXT_PCT\" -lt 0 ]; then CONTEXT_PCT=0; fi\n    SOURCE=\"estimate\"\nelse\n    SOURCE=\"debug\"\nfi\n\necho \"$CONTEXT_PCT\" > \"$PCT_FILE\"\n\nTIMESTAMP=$(date '+%Y-%m-%d %H:%M')\n\n# --- Evacuation template (with cooldown to prevent spam) ---\nEVAC_COOLDOWN_FILE=\"/tmp/cc-context-evac-last\"\nEVAC_COOLDOWN_SEC=1800  # 30 min cooldown between template generations\n\ngenerate_evacuation_template() {\n    local level=\"$1\"\n\n    # Cooldown check\n    if [ -f \"$EVAC_COOLDOWN_FILE\" ]; then\n        local last_ts now_ts diff\n        last_ts=$(cat \"$EVAC_COOLDOWN_FILE\" 2>/dev/null || echo 0)\n        now_ts=$(date +%s)\n        diff=$((now_ts - last_ts))\n        if [ \"$diff\" -lt \"$EVAC_COOLDOWN_SEC\" ]; then\n            return\n        fi\n    fi\n\n    # Don't add a new template if there's already an unfilled one\n    if [ -f \"$MISSION_FILE\" ] && grep -q '\\[TODO\\]' \"$MISSION_FILE\" 2>/dev/null; then\n        return\n    fi\n\n    date +%s > \"$EVAC_COOLDOWN_FILE\"\n\n    # Create mission file directory if needed\n    mkdir -p \"$(dirname \"$MISSION_FILE\")\"\n\n    cat >> \"$MISSION_FILE\" << EVAC_EOF\n\n## Context Evacuation Template (${level} - ${TIMESTAMP})\n<!-- Auto-generated by context-monitor.sh. Fill in before /compact -->\n### Current Task\n- Task: [TODO]\n- Progress: [TODO]\n- Files being edited: [TODO]\n\n### Git State\n- Branch: [TODO]\n- Uncommitted changes: [TODO]\n\n### Next Action\n- Next command/action: [TODO]\nEVAC_EOF\n}\n\n# --- Graduated warnings ---\nif [ \"$CONTEXT_PCT\" -le 15 ]; then\n    # EMERGENCY\n    if [ \"$LAST_STATE\" != \"emergency\" ]; then\n        echo \"emergency\" > \"$STATE_FILE\"\n        generate_evacuation_template \"EMERGENCY\"\n    fi\n    echo \"\"\n    echo \"EMERGENCY: Context remaining ${CONTEXT_PCT}% (${SOURCE})\"\n    echo \"Run /compact IMMEDIATELY. Evacuation template written to ${MISSION_FILE}.\"\n    echo \"1. Fill in the [TODO] fields in the template\"\n    echo \"2. Run /compact\"\n    echo \"3. If needed, restart and resume from mission file\"\n    echo \"No further work allowed. Evacuate only.\"\n\nelif [ \"$CONTEXT_PCT\" -le 20 ]; then\n    # CRITICAL\n    if [ \"$LAST_STATE\" != \"critical\" ]; then\n        echo \"critical\" > \"$STATE_FILE\"\n        generate_evacuation_template \"CRITICAL\"\n    fi\n    echo \"\"\n    echo \"CRITICAL: Context remaining ${CONTEXT_PCT}% (${SOURCE})\"\n    echo \"Run /compact IMMEDIATELY. Evacuation template written to ${MISSION_FILE}.\"\n    echo \"1. Save current task state to the template\"\n    echo \"2. Run /compact\"\n\nelif [ \"$CONTEXT_PCT\" -le 25 ]; then\n    # WARNING\n    if [ \"$LAST_STATE\" != \"warning\" ]; then\n        echo \"warning\" > \"$STATE_FILE\"\n        echo \"\"\n        echo \"WARNING: Context remaining ${CONTEXT_PCT}% (${SOURCE})\"\n        echo \"Do not start new large tasks. Finish current work and save state.\"\n    fi\n\nelif [ \"$CONTEXT_PCT\" -le 40 ]; then\n    # CAUTION\n    if [ \"$LAST_STATE\" != \"caution\" ]; then\n        echo \"caution\" > \"$STATE_FILE\"\n        echo \"\"\n        echo \"CAUTION: Context remaining ${CONTEXT_PCT}% (${SOURCE})\"\n        echo \"Be mindful of context consumption. Keep interactions concise.\"\n    fi\nfi\n\nexit 0\n",
  "comment-strip": "#!/bin/bash\n# ================================================================\n# comment-strip.sh — Strip bash comments that break permissions\n# ================================================================\n# PURPOSE:\n#   Claude Code sometimes adds comments to bash commands like:\n#     # Check the diff\n#     git diff HEAD~1\n#   This breaks permission allowlists (e.g. Bash(git:*)) because\n#   the matcher sees \"# Check the diff\" instead of \"git diff\".\n#\n#   This hook strips leading comment lines and returns the clean\n#   command via updatedInput, so permissions match correctly.\n#\n# TRIGGER: PreToolUse\n# MATCHER: \"Bash\"\n#\n# INCIDENT: GitHub Issue #29582 (18 reactions)\n#   Users on linux/vscode report that bash comments added by Claude\n#   cause permission prompts even when the command is allowlisted.\n#\n# HOW IT WORKS:\n#   - Reads the command from tool_input\n#   - Strips leading lines that start with #\n#   - Strips trailing comments (everything after # on command lines)\n#   - Returns updatedInput with the cleaned command\n#   - Uses hookSpecificOutput.permissionDecision = \"allow\" only if\n#     the command was modified (so it doesn't override other hooks)\n# ================================================================\n\nINPUT=$(cat)\nCOMMAND=$(printf '%s' \"$INPUT\" | jq -r '.tool_input.command // empty' 2>/dev/null)\n\nif [[ -z \"$COMMAND\" ]]; then\n    exit 0\nfi\n\n# Strip leading comment lines and empty lines\nCLEAN=$(printf '%s' \"$COMMAND\" | sed '/^[[:space:]]*#/d; /^[[:space:]]*$/d')\n\n# If nothing changed, pass through\nif [[ \"$CLEAN\" == \"$COMMAND\" ]]; then\n    exit 0\nfi\n\n# If command is empty after stripping, don't modify\nif [[ -z \"$CLEAN\" ]]; then\n    exit 0\nfi\n\n# Return cleaned command via hookSpecificOutput\n# permissionDecision is not set — let the normal permission flow handle it\n# We only modify the input so the permission matcher sees the real command\njq -n --arg cmd \"$CLEAN\" '{\n  hookSpecificOutput: {\n    hookEventName: \"PreToolUse\",\n    updatedInput: {\n      command: $cmd\n    }\n  }\n}'\n",
  "cd-git-allow": "#!/bin/bash\n# ================================================================\n# cd-git-allow.sh — Auto-approve cd+git compound commands\n# ================================================================\n# PURPOSE:\n#   Claude Code shows \"Compound commands with cd and git require\n#   approval\" for commands like: cd /path && git log\n#   This is safe in trusted project directories but causes\n#   constant permission prompts.\n#\n#   This hook auto-approves cd+git compounds when the git operation\n#   is read-only (log, diff, status, branch, show, etc.)\n#   Destructive git operations (push, reset, clean) are NOT\n#   auto-approved — they still require manual approval.\n#\n# TRIGGER: PreToolUse\n# MATCHER: \"Bash\"\n#\n# INCIDENT: GitHub Issue #32985 (9 reactions)\n#\n# WHAT IT AUTO-APPROVES:\n#   - cd /path && git log\n#   - cd /path && git diff\n#   - cd /path && git status\n#   - cd /path && git branch\n#   - cd /path && git show\n#   - cd /path && git rev-parse\n#\n# WHAT IT DOES NOT APPROVE (still prompts):\n#   - cd /path && git push\n#   - cd /path && git reset --hard\n#   - cd /path && git clean\n#   - cd /path && git checkout (could discard changes)\n# ================================================================\n\nINPUT=$(cat)\nCOMMAND=$(printf '%s' \"$INPUT\" | jq -r '.tool_input.command // empty' 2>/dev/null)\n\nif [[ -z \"$COMMAND\" ]]; then\n    exit 0\nfi\n\n# Only handle cd + git compounds\nif ! printf '%s' \"$COMMAND\" | grep -qE '^\\s*cd\\s+.*&&\\s*git\\s'; then\n    exit 0\nfi\n\n# Extract the git subcommand\nGIT_CMD=$(printf '%s' \"$COMMAND\" | grep -oP '&&\\s*git\\s+\\K\\S+')\n\n# Read-only git operations — safe to auto-approve\nSAFE_GIT=\"log diff status branch show rev-parse tag remote stash-list describe name-rev\"\n\nfor safe in $SAFE_GIT; do\n    if [[ \"$GIT_CMD\" == \"$safe\" ]]; then\n        jq -n '{\n          hookSpecificOutput: {\n            hookEventName: \"PreToolUse\",\n            permissionDecision: \"allow\",\n            permissionDecisionReason: \"cd+git compound auto-approved (read-only git operation)\"\n          }\n        }'\n        exit 0\n    fi\ndone\n\n# Not a read-only git op — let normal permission flow handle it\nexit 0\n",
  "secret-guard": "#!/bin/bash\n# ================================================================\n# secret-guard.sh — Secret/Credential Leak Prevention\n# ================================================================\n# PURPOSE:\n#   Prevents accidental exposure of secrets, API keys, and\n#   credentials through git commits or shell output.\n#\n#   Catches the most common ways secrets leak:\n#   - git add .env (committing env files)\n#   - git add credentials.json / *.pem / *.key\n#   - echo $API_KEY or printenv (exposing secrets in output)\n#\n# TRIGGER: PreToolUse\n# MATCHER: \"Bash\"\n#\n# WHAT IT BLOCKS (exit 2):\n#   - git add .env / .env.local / .env.production\n#   - git add *credentials* / *secret* / *.pem / *.key\n#   - git add -A or git add . when .env exists (warns)\n#\n# WHAT IT ALLOWS (exit 0):\n#   - git add specific safe files\n#   - Reading .env for application use (not committing)\n#   - All non-git-add commands\n#\n# CONFIGURATION:\n#   CC_SECRET_PATTERNS — colon-separated additional patterns to block\n#     default: \".env:.env.local:.env.production:credentials:secret:*.pem:*.key:*.p12\"\n# ================================================================\n\nINPUT=$(cat)\nCOMMAND=$(printf '%s' \"$INPUT\" | jq -r '.tool_input.command // empty' 2>/dev/null)\n\nif [[ -z \"$COMMAND\" ]]; then\n    exit 0\nfi\n\n# --- Check 1: git add of secret files ---\nif printf '%s' \"$COMMAND\" | grep -qE '^\\s*git\\s+add'; then\n    # Direct .env file staging\n    if printf '%s' \"$COMMAND\" | grep -qiE 'git\\s+add\\s+.*\\.env(\\s|$|\\.|/)'; then\n        echo \"BLOCKED: Attempted to stage .env file.\" >&2\n        echo \"\" >&2\n        echo \"Command: $COMMAND\" >&2\n        echo \"\" >&2\n        echo \".env files contain secrets and should never be committed.\" >&2\n        echo \"Add .env to .gitignore instead.\" >&2\n        exit 2\n    fi\n\n    # Credential/key files\n    if printf '%s' \"$COMMAND\" | grep -qiE 'git\\s+add\\s+.*(credentials|\\.pem|\\.key|\\.p12|\\.pfx|id_rsa|id_ed25519)'; then\n        echo \"BLOCKED: Attempted to stage credential/key file.\" >&2\n        echo \"\" >&2\n        echo \"Command: $COMMAND\" >&2\n        echo \"\" >&2\n        echo \"Key and credential files should never be committed to git.\" >&2\n        echo \"Add them to .gitignore instead.\" >&2\n        exit 2\n    fi\n\n    # git add -A or git add . when .env exists — warn but check\n    if printf '%s' \"$COMMAND\" | grep -qE 'git\\s+add\\s+(-A|--all|\\.)(\\s|$)'; then\n        # Check if .env exists in the current or project directory\n        if [ -f \".env\" ] || [ -f \".env.local\" ] || [ -f \".env.production\" ]; then\n            echo \"BLOCKED: 'git add .' with .env file present.\" >&2\n            echo \"\" >&2\n            echo \"Command: $COMMAND\" >&2\n            echo \"\" >&2\n            echo \"An .env file exists in this directory. 'git add .' would stage it.\" >&2\n            echo \"Add specific files instead: git add src/ lib/ package.json\" >&2\n            echo \"Or add .env to .gitignore first.\" >&2\n            exit 2\n        fi\n    fi\nfi\n\nexit 0\n",
  "api-error-alert": "#!/bin/bash\nINPUT=$(cat)\nREASON=$(printf '%s' \"$INPUT\" | jq -r '.stop_reason // \"unknown\"' 2>/dev/null)\nHOOK_EVENT=$(printf '%s' \"$INPUT\" | jq -r '.hook_event_name // \"\"' 2>/dev/null)\nif [[ \"$REASON\" == \"user\" || \"$REASON\" == \"normal\" || -z \"$REASON\" ]]; then\n    exit 0\nfi\nLOG=\"${CC_ERROR_ALERT_LOG:-$HOME/.claude/session-errors.log}\"\nMISSION=\"${CC_CONTEXT_MISSION_FILE:-$HOME/mission.md}\"\nTS=$(date -Iseconds)\nmkdir -p \"$(dirname \"$LOG\")\" 2>/dev/null\necho \"[$TS] Session stopped: reason=$REASON event=$HOOK_EVENT\" >> \"$LOG\"\nif [ -z \"$WSL_DISTRO_NAME\" ]; then\n    notify-send \"Claude Code\" \"Session stopped: $REASON\" 2>/dev/null || true\n    osascript -e \"display notification \\\"Session stopped: $REASON\\\" with title \\\"Claude Code\\\"\" 2>/dev/null || true\nelse\n    powershell.exe -Command \"Write-Host 'Claude Code: Session stopped - $REASON'\" 2>/dev/null || true\nfi\nexit 0\n",
  "clear-command-confirm-guard": "#!/bin/bash\n# clear-command-confirm-guard.sh — Block accidental /clear command\n#\n# Solves: /clear destroys all conversation context with zero\n#         confirmation. Prefix matching means /c + Enter can\n#         accidentally trigger /clear instead of /commit or /compact (#40931).\n#\n# How it works: Blocks /clear entirely. Use /compact to reduce\n#   context without losing it.\n#\n# TRIGGER: UserPromptSubmit\n# MATCHER: \"^/clear$\"\n\nINPUT=$(cat)\nPROMPT=$(printf '%s' \"$INPUT\" | jq -r '.prompt // empty' 2>/dev/null)\n\nif printf '%s' \"$PROMPT\" | grep -qE '^/clear$'; then\n  echo \"BLOCKED: /clear permanently destroys all context. Use /compact instead to reduce context safely.\" >&2\n  exit 2\nfi\nexit 0\n",
  "claudemd-violation-detector": "#!/bin/bash\n# claudemd-violation-detector.sh — Remind critical CLAUDE.md rules after tool use\n#\n# Solves: Claude ignores CLAUDE.md instructions, especially after\n#         context compaction or in long sessions (#40930).\n#\n# How it works: After each tool use, extracts and prints\n#   critical rules (ABSOLUTE/MUST NEVER/NEVER/禁止) from CLAUDE.md\n#   as a reminder. Runs every N tool calls to avoid noise.\n#\n# TRIGGER: PostToolUse\n# MATCHER: \"\"\n\nset -euo pipefail\n\n# Rate limit: only remind every 20 tool calls\nCOUNTER_FILE=\"/tmp/claudemd-reminder-counter\"\nCOUNT=$(cat \"$COUNTER_FILE\" 2>/dev/null || echo \"0\")\nCOUNT=$((COUNT + 1))\necho \"$COUNT\" > \"$COUNTER_FILE\"\n[ $((COUNT % 20)) -ne 0 ] && exit 0\n\n# Find CLAUDE.md\nCLAUDEMD=\"\"\nfor candidate in \"CLAUDE.md\" \".claude/CLAUDE.md\" \"../CLAUDE.md\"; do\n  [ -f \"$candidate\" ] && CLAUDEMD=\"$candidate\" && break\ndone\n[ -z \"$CLAUDEMD\" ] && exit 0\n\n# Extract critical rules\nRULES=$(grep -iE '(ABSOLUTE|MUST NEVER|NEVER DO|禁止|絶対)' \"$CLAUDEMD\" 2>/dev/null | head -5 || true)\n[ -z \"$RULES\" ] && exit 0\n\necho \"📋 CLAUDE.md critical rules reminder:\" >&2\necho \"$RULES\" >&2\nexit 0\n",
  "subagent-context-size-guard": "#!/bin/bash\n# subagent-context-size-guard.sh — Warn on thin subagent prompts\n#\n# Solves: Subagents get spawned with minimal context, leading to\n#         poor results because they lack necessary background (#40929).\n#         The parent agent assumes shared context, but each subagent\n#         starts fresh.\n#\n# How it works: Checks Agent tool's prompt parameter length.\n#   If under 100 characters, warns that the prompt may be too thin\n#   for a standalone agent to work effectively.\n#\n# TRIGGER: PreToolUse\n# MATCHER: \"Agent\"\n\nset -euo pipefail\nINPUT=$(cat)\nPROMPT=$(printf '%s' \"$INPUT\" | jq -r '.tool_input.prompt // empty' 2>/dev/null)\n\n[ -z \"$PROMPT\" ] && exit 0\n\nLEN=${#PROMPT}\nif [ \"$LEN\" -lt 100 ]; then\n  echo \"WARNING: Agent prompt is only ${LEN} chars. Subagents start with zero context — include enough background for them to work independently.\" >&2\nfi\nexit 0\n"
}