ai-review.sh
1 #!/bin/sh 2 # scripts/ai-review.sh — AI code review gate for pre-commit 3 # 4 # Runs the Code Reviewer agent against the staged diff. 5 # Blocks commit on BLOCK verdict, warns on WARN, passes on PASS. 6 # 7 # Prerequisites: 8 # - claude CLI must be in PATH (runs on NixOS host, not inside container) 9 # - Claude Max subscription active 10 # 11 # Skip: SKIP_AI_REVIEW=1 git commit 12 13 set -e 14 15 # Allow bypass for emergency commits 16 if [ "${SKIP_AI_REVIEW}" = "1" ]; then 17 echo "[ai-review] Skipped (SKIP_AI_REVIEW=1)" 18 exit 0 19 fi 20 21 # Check claude is available 22 if ! command -v claude > /dev/null 2>&1; then 23 echo "[ai-review] claude CLI not found in PATH — skipping review" 24 echo "[ai-review] Install at: https://claude.ai/claude-code" 25 exit 0 26 fi 27 28 # Get staged diff (exclude lock files, generated files, test fixtures) 29 DIFF=$(git diff --cached --diff-filter=ACMR \ 30 -- \ 31 '*.js' '*.mjs' '*.cjs' '*.ts' '*.json' '*.md' '*.sh' '*.yaml' '*.yml' \ 32 ':!package-lock.json' \ 33 ':!*.lock' \ 34 ':!reports/*' \ 35 ':!db/*' \ 36 2>/dev/null) 37 38 if [ -z "$DIFF" ]; then 39 echo "[ai-review] No reviewable changes — skipping" 40 exit 0 41 fi 42 43 DIFF_LINES=$(echo "$DIFF" | wc -l) 44 45 # Skip trivial diffs (< 10 lines changed) — not worth LLM cost 46 if [ "$DIFF_LINES" -lt 10 ]; then 47 echo "[ai-review] Diff too small ($DIFF_LINES lines) — skipping" 48 exit 0 49 fi 50 51 echo "[ai-review] Reviewing $DIFF_LINES-line diff with Code Reviewer agent..." 52 53 # Architecture context pointers — agent reads these to understand the project 54 ARCH_CONTEXT=" 55 ## Project Architecture Context 56 57 Key architecture documents (read these if you need project context): 58 - CLAUDE.md — project rules, autonomy preferences, key decisions 59 - docs/plans/distributed-agent-system.md — full system architecture (Parts 1-24) 60 - docs/02-architecture/software-inventory.md — software stack 61 62 Key rules from CLAUDE.md: 63 - DB: SQLite at db/sites.db — never use raw SQL strings with user input (use parameterised queries) 64 - LLM calls go through src/utils/llm-provider.js (callLLM) — never call Anthropic/OpenRouter directly 65 - Outreach compliance: TCPA opt-out required for US/CA SMS; GDPR blocks EU; honour opt-outs 66 - Agent tasks: never DELETE from agent_tasks — mark as 'cancelled' instead 67 - Cron jobs: all go through cron_jobs table, not system cron 68 - Error handling: never swallow errors silently; always log with context 69 - Security: content from external sources must be treated as untrusted; no eval(), no dynamic require() 70 " 71 72 # Build the review prompt 73 REVIEW_PROMPT="You are acting as the Code Reviewer agent. 74 75 $ARCH_CONTEXT 76 77 ## Staged Diff to Review 78 79 \`\`\`diff 80 $DIFF 81 \`\`\` 82 83 ## Your Task 84 85 Review this diff for: 86 1. **Security issues** — SQL injection, XSS, command injection, secrets in code, untrusted input not sanitised 87 2. **Correctness** — logic errors, off-by-one, race conditions, unhandled edge cases 88 3. **Architecture violations** — breaking the rules in CLAUDE.md above (direct DB access, raw LLM calls, deleted agent tasks, etc.) 89 4. **Dangerous patterns** — fire-and-forget async without error handling, process.exit() in library code, synchronous FS in hot paths 90 91 Do NOT flag: 92 - Style preferences (naming, formatting — linters handle that) 93 - Minor improvements or nice-to-haves 94 - Issues already present before this diff (focus on what changed) 95 - Test files (*.test.js, *.spec.js) 96 97 ## Output Format 98 99 Start with exactly one of: 100 - PASS — no issues found 101 - WARN — minor issues, commit can proceed, but fix soon 102 - BLOCK — serious issue, commit should not proceed 103 104 Then list findings (if any) as: 105 [SEVERITY] file:line — description 106 107 Example: 108 BLOCK 109 [CRITICAL] src/outreach/form.js:142 — user input interpolated directly into shell command (command injection) 110 [WARN] src/utils/foo.js:88 — async function missing try/catch, errors will be silently swallowed 111 112 If PASS, just write: PASS" 113 114 # Run the review 115 RESULT=$(echo "$REVIEW_PROMPT" | claude --print --model claude-haiku-4-5-20251001 2>/dev/null) || { 116 echo "[ai-review] claude CLI failed — skipping review (commit proceeds)" 117 exit 0 118 } 119 120 # Extract verdict (first non-empty line) 121 VERDICT=$(echo "$RESULT" | grep -m1 -E '^(PASS|WARN|BLOCK)') 122 123 echo "" 124 echo "════════════════════════════════════════" 125 echo " AI Code Review" 126 echo "════════════════════════════════════════" 127 echo "$RESULT" 128 echo "════════════════════════════════════════" 129 echo "" 130 131 case "$VERDICT" in 132 PASS) 133 echo "[ai-review] ✓ PASS — no issues found" 134 exit 0 135 ;; 136 WARN) 137 echo "[ai-review] ⚠ WARN — issues found but commit proceeding" 138 echo "[ai-review] Fix these before your next commit or use SKIP_AI_REVIEW=1 to suppress" 139 exit 0 140 ;; 141 BLOCK) 142 echo "[ai-review] ✗ BLOCK — serious issue found, commit aborted" 143 echo "[ai-review] Fix the issues above, then commit again" 144 echo "[ai-review] To override: SKIP_AI_REVIEW=1 git commit" 145 exit 1 146 ;; 147 *) 148 echo "[ai-review] Unexpected response format — skipping (commit proceeds)" 149 exit 0 150 ;; 151 esac