#!/bin/bash # Prowler UI - Pre-Commit Hook # Optionally validates ONLY staged files against AGENTS.md standards using Claude Code # Controlled by CODE_REVIEW_ENABLED in .env set -e # Colors RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color echo "" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "🚀 Prowler UI - Pre-Commit Hook" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" # Load .env file (look in git root directory) GIT_ROOT=$(git rev-parse --show-toplevel) if [ -f "$GIT_ROOT/ui/.env" ]; then CODE_REVIEW_ENABLED=$(grep "^CODE_REVIEW_ENABLED" "$GIT_ROOT/ui/.env" | cut -d'=' -f2 | tr -d ' ') elif [ -f "$GIT_ROOT/.env" ]; then CODE_REVIEW_ENABLED=$(grep "^CODE_REVIEW_ENABLED" "$GIT_ROOT/.env" | cut -d'=' -f2 | tr -d ' ') elif [ -f ".env" ]; then CODE_REVIEW_ENABLED=$(grep "^CODE_REVIEW_ENABLED" .env | cut -d'=' -f2 | tr -d ' ') else CODE_REVIEW_ENABLED="false" fi # Normalize the value to lowercase CODE_REVIEW_ENABLED=$(echo "$CODE_REVIEW_ENABLED" | tr '[:upper:]' '[:lower:]') echo -e "${BLUE}ℹ️ Code Review Status: ${CODE_REVIEW_ENABLED}${NC}" echo "" # Get staged files (what will be committed) STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(tsx?|jsx?)$' || true) if [ "$CODE_REVIEW_ENABLED" = "true" ]; then if [ -z "$STAGED_FILES" ]; then echo -e "${YELLOW}⚠️ No TypeScript/JavaScript files staged to validate${NC}" echo "" else echo -e "${YELLOW}🔍 Running Claude Code standards validation...${NC}" echo "" echo -e "${BLUE}📋 Files to validate:${NC}" echo "$STAGED_FILES" | while IFS= read -r file; do echo " - $file"; done echo "" echo -e "${BLUE}📤 Sending to Claude Code for validation...${NC}" echo "" # Build prompt with full file contents VALIDATION_PROMPT=$( cat <<'PROMPT_EOF' You are a code reviewer for the Prowler UI project. Analyze the full file contents of changed files below and validate they comply with AGENTS.md standards. **RULES TO CHECK:** 1. React Imports: NO `import * as React` or `import React, {` → Use `import { useState }` 2. TypeScript: NO union types like `type X = "a" | "b"` → Use const-based: `const X = {...} as const` 3. Tailwind: NO `var()` or hex colors in className → Use Tailwind utilities and semantic color classes. Exception: `var()` is allowed when passing colors to chart/graph components that require CSS color strings (not Tailwind classes) for their APIs. 4. cn(): Use for merging multiple classes or for conditionals (handles Tailwind conflicts with twMerge) → `cn(BUTTON_STYLES.base, BUTTON_STYLES.active, isLoading && "opacity-50")` 5. React 19: NO `useMemo`/`useCallback` without reason 6. Zod v4: Use `.min(1)` not `.nonempty()`, `z.email()` not `z.string().email()`. All inputs must be validated with Zod. 7. File Org: 1 feature = local, 2+ features = shared 8. Directives: Server Actions need "use server", clients need "use client" 9. Implement DRY, KISS principles. (example: reusable components, avoid repetition) 10. Layout must work for all the responsive breakpoints (mobile, tablet, desktop) 11. ANY types cannot be used - CRITICAL: Check for `: any` in all visible lines 12. Use the components inside components/shadcn if possible 13. Check Accessibility best practices (like alt tags in images, semantic HTML, Aria labels, etc.) === FILES TO REVIEW === PROMPT_EOF ) # Add full file contents for each staged file for file in $STAGED_FILES; do VALIDATION_PROMPT="$VALIDATION_PROMPT === FILE: $file === $(cat "$file" 2>/dev/null || echo "Error reading file")" done VALIDATION_PROMPT="$VALIDATION_PROMPT === END FILES === **IMPORTANT: Your response MUST start with exactly one of these lines:** STATUS: PASSED STATUS: FAILED **If FAILED:** List each violation with File, Line Number, Rule Number, and Issue. **If PASSED:** Confirm all files comply with AGENTS.md standards. **Start your response now with STATUS:**" # Send to Claude Code if VALIDATION_OUTPUT=$(echo "$VALIDATION_PROMPT" | claude 2>&1); then echo "$VALIDATION_OUTPUT" echo "" # Check result - STRICT MODE: fail if status unclear if echo "$VALIDATION_OUTPUT" | grep -q "^STATUS: PASSED"; then echo "" echo -e "${GREEN}✅ VALIDATION PASSED${NC}" echo "" elif echo "$VALIDATION_OUTPUT" | grep -q "^STATUS: FAILED"; then echo "" echo -e "${RED}❌ VALIDATION FAILED${NC}" echo -e "${RED}Fix violations before committing${NC}" echo "" exit 1 else echo "" echo -e "${RED}❌ VALIDATION ERROR${NC}" echo -e "${RED}Could not determine validation status from Claude Code response${NC}" echo -e "${YELLOW}Response must start with 'STATUS: PASSED' or 'STATUS: FAILED'${NC}" echo "" echo -e "${YELLOW}To bypass validation temporarily, set CODE_REVIEW_ENABLED=false in .env${NC}" echo "" exit 1 fi else echo -e "${YELLOW}⚠️ Claude Code not available${NC}" fi echo "" fi else echo -e "${YELLOW}⏭️ Code review disabled (CODE_REVIEW_ENABLED=false)${NC}" echo "" fi # Run healthcheck (typecheck and lint check) echo -e "${BLUE}🏥 Running healthcheck...${NC}" echo "" cd ui || cd . if pnpm run healthcheck; then echo "" echo -e "${GREEN}✅ Healthcheck passed${NC}" echo "" else echo "" echo -e "${RED}❌ Healthcheck failed${NC}" echo -e "${RED}Fix type errors and linting issues before committing${NC}" echo "" exit 1 fi # Run build echo -e "${BLUE}🔨 Running build...${NC}" echo "" if pnpm run build; then echo "" echo -e "${GREEN}✅ Build passed${NC}" echo "" else echo "" echo -e "${RED}❌ Build failed${NC}" echo -e "${RED}Fix build errors before committing${NC}" echo "" exit 1 fi