/ scripts / ai-review.sh
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