#!/bin/bash # RALPH Loop Runner - OpenCode Edition (with Flight Recorder) # # Usage: # ./ralph-opencode.sh # Run (5 iterations default) # ./ralph-opencode.sh 10 # Run 10 iterations # ./ralph-opencode.sh --model # Run with model override # ./ralph-opencode.sh --postmortem-model # ./ralph-opencode.sh --variant high # Run with model variant override # ./ralph-opencode.sh --review-interval 3 # Run adversarial review every 3 iterations # ./ralph-opencode.sh --model 10 # RALPH_MODEL=openai/gpt-5.5 RALPH_VARIANT=xhigh ./ralph-opencode.sh # ./ralph-opencode.sh --model openai/gpt-5.5 --variant xhigh --postmortem-model github-copilot/claude-opus-4.5 --postmortem-variant max 9 # Postmortem runs automatically after the loop. # # Each iteration is a FRESH OpenCode 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, the child CLI 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:-openai/gpt-5.5}" VARIANT="${RALPH_VARIANT:-xhigh}" POSTMORTEM_MODEL="${RALPH_POSTMORTEM_MODEL:-$MODEL}" POSTMORTEM_VARIANT="${RALPH_POSTMORTEM_VARIANT:-$VARIANT}" 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] [--variant 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 ] [--variant ] [--postmortem-model ] [--postmortem-variant ] [--review-interval N] [--skip-l3-gate] [iterations]" exit 1 fi MODEL="$2" POSTMORTEM_MODEL="${RALPH_POSTMORTEM_MODEL:-$MODEL}" shift 2 ;; --variant) if [[ $# -lt 2 ]]; then echo "Missing value for $1" echo "Usage: $0 [--model ] [--variant ] [--postmortem-model ] [--postmortem-variant ] [--review-interval N] [--skip-l3-gate] [iterations]" exit 1 fi VARIANT="$2" POSTMORTEM_VARIANT="${RALPH_POSTMORTEM_VARIANT:-$VARIANT}" shift 2 ;; --postmortem-model) if [[ $# -lt 2 ]]; then echo "Missing value for $1" echo "Usage: $0 [--model ] [--variant ] [--postmortem-model ] [--postmortem-variant ] [--review-interval N] [--skip-l3-gate] [iterations]" exit 1 fi POSTMORTEM_MODEL="$2" shift 2 ;; --postmortem-variant) if [[ $# -lt 2 ]]; then echo "Missing value for $1" echo "Usage: $0 [--model ] [--variant ] [--postmortem-model ] [--postmortem-variant ] [--review-interval N] [--skip-l3-gate] [iterations]" exit 1 fi POSTMORTEM_VARIANT="$2" shift 2 ;; --review-interval) if [[ $# -lt 2 ]]; then echo "Missing value for $1" echo "Usage: $0 [--model ] [--variant ] [--postmortem-model ] [--postmortem-variant ] [--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 ] [--variant ] [--postmortem-model ] [--postmortem-variant ] [--review-interval N] [--skip-l3-gate] [iterations]" exit 1 fi ;; esac done # Check if opencode is installed if ! command -v opencode &> /dev/null; then echo "Error: opencode 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 (OpenCode)" echo " Model: $MODEL" echo " Variant: $VARIANT" echo " Postmortem Model: $POSTMORTEM_MODEL" echo " Postmortem Variant: $POSTMORTEM_VARIANT" echo " Review Interval: $REVIEW_INTERVAL (0=disabled)" echo " L3 Gate: $L3_GATE_ENABLED (bypass=$L3_GATE_BYPASS)" echo " Iterations: $ITERATIONS" echo " Run ID: $RUN_ID" echo " Run Dir: $RUN_DIR" echo "==========================================" 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/opencode-${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 "Variant: $VARIANT" echo "Postmortem model: $POSTMORTEM_MODEL" echo "Postmortem variant: $POSTMORTEM_VARIANT" 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 ! opencode run --model "$POSTMORTEM_MODEL" --variant "$POSTMORTEM_VARIANT" "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 } verify_signed_iteration_commit() { local before_commit=$1 local after_commit after_commit=$(git rev-parse HEAD) if [[ "$after_commit" == "$before_commit" ]]; then echo "Iteration did not create a commit; skipping signature check." return 0 fi local signature_status signature_status=$(git log -1 --format=%G? "$after_commit") case "$signature_status" in G|U) echo "Signed commit verified: $after_commit (status=$signature_status)" ;; *) echo "" echo "==========================================" echo " SIGNED COMMIT GATE FAILED" echo "==========================================" echo "" echo "Latest commit is not GPG-signed or has an invalid signature." echo "Commit: $after_commit" echo "Signature status: $signature_status" echo "Do not bypass signing. Fix GPG signing and rerun." return 1 ;; esac } # 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=() # Netclaw L3 means a native smoke/interactive CLI proof, not just web-route checks. if ! grep -qi "run-smoke.sh\|native smoke\|VHS\|tape:" "$iter_log" 2>/dev/null; then missing_evidence+=("Native smoke evidence (run-smoke.sh, VHS, or named tape)") fi if ! grep -qi "All smoke checks passed\|assertions passed\|: OK\|smoke.*passed" "$iter_log" 2>/dev/null; then missing_evidence+=("Smoke result and assertion outcome") fi if ! grep -qi "semantic assertion\|assertion script\|canonical\|persisted.*value\|config.*assert" "$iter_log" 2>/dev/null; then missing_evidence+=("Semantic assertion evidence, not only screen text") fi if grep -q "Level: L4" "$iter_log" 2>/dev/null && \ ! grep -qi "Aspire\|Docker\|container.*healthy\|resources healthy\|live provider\|demo smoke" "$iter_log" 2>/dev/null; then missing_evidence+=("L4 runtime evidence (Aspire, Docker, live provider, or demo smoke)") 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 IMPLEMENTATION_PLAN.md Automation-First QA Floor, these are MANDATORY when claiming L3/L4." 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 OpenCode 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" ITER_START_COMMIT=$(git rev-parse HEAD) if ! opencode run --model "$MODEL" --variant "$VARIANT" "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 N.N:' 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) Apply IMPLEMENTATION_PLAN.md quality gates: - Identify the downstream consumer for any config/event/message/tool/schema/persistence output - Prove canonical representation at the producer/consumer boundary - Add negative-path coverage for invalid, unresolved, denied, malformed, or missing inputs - For TUI input: add headless typed-key coverage and native smoke for critical flows - For dynamic validation: add fake-failure tests proving save is blocked before persistence - For old config migration/porting: add load/round-trip tests from old shape to runtime-consumed shape 4) Load relevant skills from .claude/skills/: - REQUIRED for code: testing-strategy.md (if present) - REQUIRED: ralph-loop.md (process discipline, if present) - If UI impacted: ui-smoke-validation.md (or follow IMPLEMENTATION_PLAN.md TUI gate) - If schema/events touched: extend-only-design.md (if present) 5) BEFORE coding: choose Verification Level (L0-L4) and state why: - I/O coordination (DB/HTTP/actors/external) => L2+ (integration tests required) - TUI/config/init changes => L3+ (native smoke required) - Live provider/demo/container proof => L4 6) Implement to satisfy ALL unchecked Done-when criteria for the chosen task. 7) Verify (must match chosen level): - Minimum: build + test (language-appropriate commands) - If Level >= L3: run native smoke and semantic assertion scripts - Follow any additional quality gates from AGENTS.md/CLAUDE.md 8) FLIGHT RECORDER (MANDATORY): - Write $ITER_LOG BEFORE committing. - Include: - Task selected (exact title) - Surface area classification - Verification level chosen + reason - Producer/consumer contract identified (or why none applies) - Skills consulted - Commands run + outcomes - Positive behavior verified - Negative behavior verified - Runtime/smoke/eval evidence or explicit blocker - 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.' 9) If verification passes: - Commit to the current feature branch with a descriptive message using git commit -S - Update IMPLEMENTATION_PLAN.md checkboxes in the SAME commit - Update TOOLING.md if you used or discovered a new tool/resource - Never use --no-gpg-sign 10) Stop at checkpoints (UI approval, architecture decisions, credential setup) and ask the user if needed. 11) Exit - do NOT continue to additional tasks. ## Constraints (Constitution) - ONE iteration = ONE task block - Never commit to dev/main/master - Never create unsigned commits - Follow constraints from AGENTS.md/CLAUDE.md - Automate all proof that can reasonably be automated; manual testing is last-mile only "; then EXIT_CODE=$? echo "" echo "OpenCode exited with code $EXIT_CODE" echo "RALPH loop paused at iteration $i" exit $EXIT_CODE fi echo "" echo "Iteration $i complete" if ! verify_signed_iteration_commit "$ITER_START_COMMIT"; then echo "RALPH loop paused due to signed commit gate failure at iteration $i" exit 1 fi # 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 -E '#### Task [0-9]+\.[0-9]+:' | head -5 || echo "(none or legacy format)" echo "" echo "Running postmortem (skill): ralph-after-action" # Note: OpenCode doesn't have OpenProse plugin, so we invoke the skill directly. # This runs sequentially. For parallel execution, use ralph.sh with Claude Code. if ! opencode run --model "$POSTMORTEM_MODEL" --variant "$POSTMORTEM_VARIANT" "/ralph-after-action 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"