Created
January 11, 2026 16:48
-
-
Save fongandrew/32222869f25202c8532f705f2fb9abd1 to your computer and use it in GitHub Desktop.
Review the guidelines stop hook for Claude Code
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| # Review modified files against relevant guides on Stop | |
| # | |
| # This hook fires when Claude finishes work. It checks if any files matching | |
| # configured patterns were modified and, if so, blocks the stop and asks | |
| # Claude to review them against the appropriate guide. | |
| # | |
| # To add new patterns, add entries to the PATTERNS and GUIDES arrays below. | |
| set -e | |
| # ============================================================================= | |
| # CONFIGURATION: Map file patterns to guide documents | |
| # ============================================================================= | |
| # Add patterns and their corresponding guides at matching indices. | |
| # The regex patterns are matched against the full file path using grep -E. | |
| PATTERNS=( | |
| '\.test\.(ts|tsx)$' | |
| '\.spec\.(ts|tsx)$' | |
| '\.css$' | |
| ) | |
| GUIDES=( | |
| "docs/unit-testing-guide.md" | |
| "docs/playwright-testing-guide.md" | |
| "docs/css-guide.md" | |
| ) | |
| # ============================================================================= | |
| # HOOK LOGIC (no need to modify below unless changing behavior) | |
| # ============================================================================= | |
| # Read hook input from stdin | |
| INPUT=$(cat) | |
| # Prevent infinite loops: if a stop hook already triggered, allow stop | |
| STOP_HOOK_ACTIVE=$(echo "$INPUT" | jq -r '.stop_hook_active // false') | |
| if [ "$STOP_HOOK_ACTIVE" = "true" ]; then | |
| echo '{"decision": "approve"}' | |
| exit 0 | |
| fi | |
| TRANSCRIPT_PATH=$(echo "$INPUT" | jq -r '.transcript_path // empty') | |
| if [ -z "$TRANSCRIPT_PATH" ] || [ ! -f "$TRANSCRIPT_PATH" ]; then | |
| echo '{"decision": "approve"}' | |
| exit 0 | |
| fi | |
| # Find the line number of the last Stop hook event (if any) | |
| # We only want to review files modified AFTER the last stop hook ran | |
| # Stop hook events are recorded as type:"system" subtype:"stop_hook_summary" | |
| LAST_STOP_LINE=$(grep -n -E '"subtype"[[:space:]]*:[[:space:]]*"stop_hook_summary"' "$TRANSCRIPT_PATH" 2>/dev/null | tail -1 | cut -d: -f1 || echo "0") | |
| LAST_STOP_LINE=${LAST_STOP_LINE:-0} | |
| # Extract file paths from Edit/Write tool calls, but only after the last Stop hook | |
| # The transcript is JSONL format - each line is a separate JSON object | |
| # Tool uses are nested inside message.content[] for assistant messages | |
| ALL_MODIFIED_FILES=$(tail -n +$((LAST_STOP_LINE + 1)) "$TRANSCRIPT_PATH" | while read -r line; do | |
| echo "$line" | jq -r ' | |
| .message.content[]? | |
| | select(.type == "tool_use") | |
| | select(.name == "Edit" or .name == "Write") | |
| | .input.file_path // empty | |
| ' 2>/dev/null | |
| done | grep -v '^$' | sort -u || true) | |
| if [ -z "$ALL_MODIFIED_FILES" ]; then | |
| echo '{"decision": "approve"}' | |
| exit 0 | |
| fi | |
| # Build review instructions for each pattern that has matching files | |
| REVIEW_SECTIONS="" | |
| for i in "${!PATTERNS[@]}"; do | |
| pattern="${PATTERNS[$i]}" | |
| guide="${GUIDES[$i]}" | |
| # Find files matching this pattern | |
| MATCHING_FILES=$(echo "$ALL_MODIFIED_FILES" | grep -E "$pattern" || true) | |
| if [ -n "$MATCHING_FILES" ]; then | |
| FILE_LIST=$(echo "$MATCHING_FILES" | sed 's/^/ - /') | |
| REVIEW_SECTIONS="${REVIEW_SECTIONS} | |
| ## Files matching pattern \`${pattern}\`: | |
| ${FILE_LIST} | |
| Review against: ${guide} | |
| " | |
| fi | |
| done | |
| # If any patterns matched, block stop and request review | |
| if [ -n "$REVIEW_SECTIONS" ]; then | |
| # Properly escape the reason for JSON | |
| REASON="Files were modified that require review against project guidelines.${REVIEW_SECTIONS} | |
| Please review the listed files against their respective guides and fix any violations before stopping." | |
| # Use jq to properly escape the string for JSON | |
| JSON_REASON=$(echo "$REASON" | jq -Rs .) | |
| echo "{\"decision\": \"block\", \"reason\": ${JSON_REASON}}" | |
| else | |
| echo '{"decision": "approve"}' | |
| fi |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment