#!/bin/bash # RALPH Loop Runner - Claude Code Edition (with Flight Recorder) # # Usage: # ./ralph.sh # Run (5 iterations default) # ./ralph.sh 10 # Run 10 iterations # ./ralph.sh --model # Run with model override # ./ralph.sh --postmortem-model # Different model for postmortem # ./ralph.sh --review-interval 3 # Run adversarial review every 3 iterations # ./ralph.sh --model 10 # RALPH_MODEL=claude-sonnet-4-20250514 ./ralph.sh # Postmortem runs automatically after the loop. # # Each iteration is a FRESH Claude context window. # Progress lives in files + git + .ralph flight recorder logs. set -euo pipefail # Ensure Ctrl+C kills the whole loop, not just the current child process. # Without this, claude catches SIGINT and exits 0, so the `if !` block # doesn't fire and the loop continues to the next iteration. trap 'echo ""; echo "RALPH loop interrupted."; exit 130' INT TERM PLAN_FILE="IMPLEMENTATION_PLAN.md" ITERATIONS=5 MODEL="${RALPH_MODEL:-claude-opus-4-6}" POSTMORTEM_MODEL="${RALPH_POSTMORTEM_MODEL:-$MODEL}" REVIEW_INTERVAL="${RALPH_REVIEW_INTERVAL:-0}" # 0 = disabled L3_GATE_ENABLED="${RALPH_L3_GATE:-true}" # Block commits without L3 evidence L3_GATE_BYPASS=false # --- arg parsing (allow: [--model X] [--review-interval N] [iterations]) --- while [[ $# -gt 0 ]]; do case "$1" in --model|-m) if [[ $# -lt 2 ]]; then echo "Missing value for $1" echo "Usage: $0 [--model ] [--postmortem-model ] [--review-interval N] [iterations]" exit 1 fi MODEL="$2" POSTMORTEM_MODEL="${RALPH_POSTMORTEM_MODEL:-$MODEL}" shift 2 ;; --postmortem-model) if [[ $# -lt 2 ]]; then echo "Missing value for $1" echo "Usage: $0 [--model ] [--postmortem-model ] [--review-interval N] [iterations]" exit 1 fi POSTMORTEM_MODEL="$2" shift 2 ;; --review-interval) if [[ $# -lt 2 ]]; then echo "Missing value for $1" echo "Usage: $0 [--model ] [--postmortem-model ] [--review-interval N] [--skip-l3-gate] [iterations]" exit 1 fi REVIEW_INTERVAL="$2" shift 2 ;; --skip-l3-gate) L3_GATE_BYPASS=true shift ;; *) # if it's a number, treat as iterations if [[ "$1" =~ ^[0-9]+$ ]]; then ITERATIONS="$1" shift else echo "Unknown arg: $1" echo "Usage: $0 [--model ] [--postmortem-model ] [--review-interval N] [--skip-l3-gate] [iterations]" exit 1 fi ;; esac done # Check if claude is installed if ! command -v claude &> /dev/null; then echo "Error: claude is not installed or not in PATH" exit 1 fi # Run ID and log directory RUN_ID="${RUN_ID:-$(date +%Y%m%d-%H%M%S)}" RUN_DIR=".ralph/runs/${RUN_ID}" mkdir -p "$RUN_DIR" echo "==========================================" echo " RALPH Loop (Claude Code)" echo " Model: $MODEL" echo " Postmortem Model: $POSTMORTEM_MODEL" echo " Review Interval: $REVIEW_INTERVAL (0=disabled)" echo " L3 Gate: $L3_GATE_ENABLED (bypass=$L3_GATE_BYPASS)" echo " Iterations: $ITERATIONS" echo " Mode: AUTONOMOUS (permissions skipped)" echo " Run ID: $RUN_ID" echo " Run Dir: $RUN_DIR" echo "==========================================" echo "" echo "WARNING: Running with --dangerously-skip-permissions" echo "Press Ctrl+C within 3 seconds to abort..." sleep 3 echo "" # Ensure we're on the right branch BRANCH=$(git branch --show-current) echo "Current branch: $BRANCH" if [[ "$BRANCH" == "dev" || "$BRANCH" == "main" || "$BRANCH" == "master" ]]; then NEW_BRANCH="ralph/claude-${RUN_ID}" echo "On protected branch, creating feature branch: $NEW_BRANCH" git checkout -b "$NEW_BRANCH" BRANCH=$(git branch --show-current) echo "Now on: $BRANCH" fi echo "" START_COMMIT=$(git rev-parse HEAD) LAST_REVIEW_COMMIT="$START_COMMIT" { echo "Run ID: $RUN_ID" echo "Branch: $BRANCH" echo "Model: $MODEL" echo "Postmortem model: $POSTMORTEM_MODEL" echo "Review interval: $REVIEW_INTERVAL" echo "Run start commit: $START_COMMIT" echo "Started: $(date)" echo "" } | tee "$RUN_DIR/run.md" >/dev/null # Function to run mid-loop adversarial review run_mid_review() { local iteration=$1 local current_commit current_commit=$(git rev-parse HEAD) echo "" echo "==========================================" echo " Mid-Loop Adversarial Review (after iteration $iteration)" echo " Reviewing commits: ${LAST_REVIEW_COMMIT}..${current_commit}" echo "==========================================" echo "" local review_log="${RUN_DIR}/review-after-iter-$(printf "%02d" "$iteration").md" # Find prior review files for cumulative context local prior_reviews="" for f in "$RUN_DIR"/review-after-iter-*.md; do [[ -f "$f" ]] && prior_reviews="$prior_reviews\n- $f" done if ! claude --dangerously-skip-permissions --model "$POSTMORTEM_MODEL" -p "Run a full adversarial review using the adversarial review skill. ## Context - RUN_ID: $RUN_ID - RUN_DIR: $RUN_DIR - Iteration just completed: $iteration - Commit range to review: ${LAST_REVIEW_COMMIT}..${current_commit} - Review log to write: $review_log - Prior review files (read these FIRST for cumulative context): ${prior_reviews:- (none — this is the first review)} ## Instructions 1. **REQUIRED:** Load .claude/skills/ralph-output-adversarial-review.md and execute the FULL review protocol. This is NOT optional. Do not perform a lightweight review. 2. **Cumulative review protocol:** - Read ALL prior review files listed above - For each prior review's open issues: check if resolved in commits since that review - If resolved: note resolution with commit hash - If NOT resolved: carry forward as open issue in THIS review - If a prior review missed something you now see: add it as a NEW finding 3. **Full review areas (from skill):** - A) Checkbox Integrity — do changes satisfy Done-when criteria? - B) Testing Strategy Compliance — integration tests for I/O, screenshots for UI - C) Architecture Compliance — follow constraints from AGENTS.md/CLAUDE.md - D) Framework compliance per CLAUDE.md - E) UI Sanity — error states, loading states, runtime errors - E2) UI Screenshot Gate — UI files must have L3+ with screenshots - F) Regression Risk — edge cases, N+1 queries, unsafe assumptions - G) Slopwatch — reward hacking patterns - H-K) Duplication, useless tests, L3 evidence audit, L3+ artifact verification 4. **Write findings to: $review_log** using the skill's deliverable format for mid-loop reviews. 5. **Write actionable items:** - NOW items: insert at top of IMPLEMENTATION_PLAN.md - PARK items: append to BACKLOG_PARKING_LOT.md - Every finding MUST have a disposition (NOW, PARK, or FIX INLINE) 6. **Process improvement authority (additive only):** - If you identify a recurring pattern that should be a skill: create it in .claude/skills/ - If you identify a gap in ralph-loop.md or testing-strategy.md: add the missing check/gate - You may ONLY add rules, never remove or weaken existing ones - You may NOT edit CLAUDE.md (constitution requires human approval) - Log all process edits under '## Process Improvements Applied' in the review file 7. **Verdict:** - Output 'VERDICT: FAIL' if hard fail criteria met (pauses the loop) - Output 'VERDICT: PARTIAL' if issues found but not blocking - Output 'VERDICT: PASS' if clean "; then echo "Mid-loop review failed to execute" return 1 fi # Check if the review log contains FAIL verdict if [[ -f "$review_log" ]] && grep -q "VERDICT: FAIL" "$review_log"; then echo "" echo "==========================================" echo " ADVERSARIAL REVIEW FAILED" echo " Review found issues requiring human attention." echo " See: $review_log" echo "==========================================" return 1 fi # Update last review commit LAST_REVIEW_COMMIT="$current_commit" echo "Mid-loop review passed, continuing..." echo "" return 0 } # Function to verify L3 evidence if L3 was claimed verify_l3_evidence() { local iter_log=$1 # Skip if gate disabled or bypassed if [[ "$L3_GATE_ENABLED" != "true" ]] || [[ "$L3_GATE_BYPASS" == "true" ]]; then return 0 fi # Check if iteration claimed L3 verification level if ! grep -q "Level: L3\|Level: L4" "$iter_log" 2>/dev/null; then # Not an L3/L4 task, no gate needed return 0 fi echo "" echo " L3/L4 Verification Gate" echo " Checking for required evidence in: $iter_log" local missing_evidence=() # Check for application running evidence if ! grep -qi "aspire run\|dotnet run\|npm start\|yarn dev\|Application Started\|resources healthy\|Server started\|listening on" "$iter_log" 2>/dev/null; then missing_evidence+=("Application running (start command with evidence)") fi # Check for routes checked evidence if ! grep -qi "Routes Checked\|Routes checked\|Route.*200\|Route.*rendered" "$iter_log" 2>/dev/null; then missing_evidence+=("Routes navigated (Routes Checked section)") fi # Check for console errors evidence if ! grep -qi "Console errors: none\|Console errors:.*none\|no console errors" "$iter_log" 2>/dev/null; then # Also check if they documented errors (which is valid if they then fix them) if ! grep -qi "Console errors:" "$iter_log" 2>/dev/null; then missing_evidence+=("Console errors checked (Console errors: none)") fi fi # Check for viewport evidence if ! grep -qi "Viewport.*pass\|viewport check\|1024.*1280.*1920\|1024px\|viewport sanity" "$iter_log" 2>/dev/null; then missing_evidence+=("Viewport sanity check (1024/1280/1920)") fi if [[ ${#missing_evidence[@]} -gt 0 ]]; then echo "" echo " ===========================================" echo " L3 VERIFICATION GATE FAILED" echo " ===========================================" echo "" echo " The iteration claimed L3/L4 verification level but is missing required evidence:" echo "" for item in "${missing_evidence[@]}"; do echo " - $item" done echo "" echo " Per ralph-loop.md L3 Verification Checklist, these are MANDATORY when claiming L3." echo "" echo " Options:" echo " 1. Fix the iteration to include proper L3 evidence" echo " 2. Downgrade to L2 with documented justification" echo " 3. Re-run with --skip-l3-gate (emergency bypass)" echo "" return 1 fi echo " L3 evidence verified: PASS" return 0 } # Loop - each iteration is a fresh Claude context for ((i=1; i<=ITERATIONS; i++)); do echo "==========================================" echo " RALPH Iteration $i of $ITERATIONS" echo " $(date)" echo "==========================================" echo "" # Check if there are any unchecked items if [[ ! -f "$PLAN_FILE" ]]; then echo "Missing required plan file: $PLAN_FILE" exit 1 fi if ! grep -q '\- \[ \]' "$PLAN_FILE"; then echo "No unchecked items remaining in $PLAN_FILE" echo "RALPH loop complete!" break fi ITER_PAD=$(printf "%02d" "$i") ITER_LOG="${RUN_DIR}/iter-${ITER_PAD}.md" if ! claude --dangerously-skip-permissions --model "$MODEL" -p "You are running RALPH iteration $i. ## Run Metadata (MUST USE) - RUN_ID: $RUN_ID - RUN_DIR: $RUN_DIR - ITERATION: $i - ITER_LOG (write this file before commit): $ITER_LOG ## Bootstrap (Read these files FIRST) 1. AGENTS.md and/or CLAUDE.md - Constitution (authority, constraints, quality bar, routing) 2. PROJECT_CONTEXT.md - Current architecture and state (if present) 3. TOOLING.md - Available tools/services (if present) 4. IMPLEMENTATION_PLAN.md - Task breakdown ## Instructions (ONE TASK ONLY) 1) Find the next incomplete task in IMPLEMENTATION_PLAN.md: - Look for '### Task:' blocks with unchecked 'Done when:' items - Work on the FIRST incomplete task you find - A task is complete only when ALL its Done-when checkboxes are satisfied 2) Determine MODE from Task Routing in AGENTS.md/CLAUDE.md (engineering/ux/marketing/ops/etc.) 3) Load relevant skills from .claude/skills/: - REQUIRED for code: testing-strategy.md (if present — integration vs unit; no fakes) - REQUIRED: ralph-loop.md (process discipline) - If UI impacted: ui-smoke-validation.md (or follow UI validation policy) - If schema/events touched: extend-only-design.md (if present) 4) BEFORE coding: choose Verification Level (L0-L4) and state why: - I/O coordination (DB/HTTP/actors/external) => L2+ (integration tests required) - UI or UI dependency changed => L3+ (UI smoke / Playwright required) 5) Implement to satisfy ALL unchecked Done-when criteria for the chosen task. 6) Verify (must match chosen level): - Minimum: build + test (language-appropriate commands) - If Level >= L3: run UI smoke/Playwright and check for console errors - Follow any additional quality gates from AGENTS.md/CLAUDE.md 7) FLIGHT RECORDER (MANDATORY): - Write $ITER_LOG BEFORE committing. - Include: - Task selected (exact title) - Surface area classification - Verification level chosen + reason - Skills consulted - Commands run + outcomes - Deviations/skips + justification - Follow-ups noticed but deferred + why - If you claim a command was run, it must appear in the log with outcome. - 'Log or it didn't happen.' 8) If verification passes: - Commit to the current feature branch with a descriptive message - Update IMPLEMENTATION_PLAN.md checkboxes in the SAME commit - Update TOOLING.md if you used or discovered a new tool/resource 9) Stop at checkpoints (UI approval, architecture decisions, credential setup) and ask the user if needed. 10) Exit - do NOT continue to additional tasks. ## Constraints (Constitution) - ONE iteration = ONE task block - Never commit to dev/main/master - Follow constraints from AGENTS.md/CLAUDE.md - Test against real infrastructure (per testing-strategy) "; then EXIT_CODE=$? echo "" echo "Claude exited with code $EXIT_CODE" echo "RALPH loop paused at iteration $i" exit $EXIT_CODE fi echo "" echo "Iteration $i complete" # Run L3 verification gate if L3 was claimed if ! verify_l3_evidence "$ITER_LOG"; then echo "RALPH loop paused due to L3 verification gate failure at iteration $i" echo "See iteration log for details: $ITER_LOG" exit 1 fi echo "" # Run mid-loop adversarial review if interval is set and we've hit it if [[ "$REVIEW_INTERVAL" -gt 0 ]] && (( i % REVIEW_INTERVAL == 0 )) && (( i < ITERATIONS )); then if ! run_mid_review "$i"; then echo "RALPH loop paused due to adversarial review failure at iteration $i" echo "Review the findings and fix issues before continuing." exit 1 fi fi sleep 2 done END_COMMIT=$(git rev-parse HEAD) { echo "Finished: $(date)" echo "Run end commit: $END_COMMIT" echo "Commit range: ${START_COMMIT}..${END_COMMIT}" } | tee -a "$RUN_DIR/run.md" >/dev/null echo "==========================================" echo " RALPH Loop Finished" echo " Run ID: $RUN_ID" echo " Branch: $(git branch --show-current)" echo " Logs: $RUN_DIR" echo " Commit range: ${START_COMMIT}..${END_COMMIT}" echo "==========================================" echo "" echo "Remaining incomplete tasks:" grep -B5 '^\- \[ \]' "$PLAN_FILE" | grep '### Task:' | head -5 || echo "(none or legacy format)" echo "" echo "Running postmortem (OpenProse): ralph-after-action" if ! claude --dangerously-skip-permissions --model "$POSTMORTEM_MODEL" -p "/open-prose:prose-run @.prose/ralph-after-action.prose RUN_ID=$RUN_ID RUN_DIR=$RUN_DIR BRANCH=$(git branch --show-current)"; then POSTMORTEM_EXIT=$? echo "" echo "Postmortem exited with code $POSTMORTEM_EXIT" exit $POSTMORTEM_EXIT fi echo "Postmortem complete" echo "Logs live at: $RUN_DIR"