#!/bin/bash set -e # Ralph - AI Coding Loop Runner # Based on Matt Pocock's Ralph Wiggum pattern # https://github.com/rathi/ralph-cli VERSION="1.0.0" RALPH_DIR=".ralph" PROGRESS_FILE="ralph-progress.txt" # Colors (optional) RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' # No Color usage() { cat << EOF Ralph v$VERSION - AI Coding Loop Runner USAGE: ralph [options] COMMANDS: run [prd-file] Run Ralph loop with PRD file init Initialize Ralph in current directory generate Generate PRD from codebase analysis status Show current progress reset Clear progress file OPTIONS: -p, --prd PRD file to use (default: .ralph/prd.md) -m, --module Run specific module only --docker Run in Docker sandbox (safer for AFK) --dry-run Show what would happen without executing -h, --help Show this help EXAMPLES: ralph init # Set up Ralph in current repo ralph run 10 # Run 10 iterations with default PRD ralph run 5 -p my-prd.md # Run 5 iterations with custom PRD ralph generate tests # Generate test PRD from codebase ralph status # Check progress EOF } # Initialize Ralph in a repo init_ralph() { echo "Initializing Ralph in $(pwd)..." mkdir -p "$RALPH_DIR" touch "$PROGRESS_FILE" # Create template PRD cat > "$RALPH_DIR/prd.md" << 'EOF' # Project PRD ## Overview Describe what you want Ralph to accomplish. ## Tasks (Priority Order) - [ ] CRITICAL: First high-priority task Description and acceptance criteria. - [ ] HIGH: Second task Description. - [ ] MEDIUM: Third task Description. - [ ] LOW: Lower priority task Description. ## Completion Criteria All tasks checked off and verified. ## Notes - Add any context Claude needs - Reference specific files if helpful EOF # Create .gitignore entry if [ -f .gitignore ]; then if ! grep -q "ralph-progress.txt" .gitignore; then echo "" >> .gitignore echo "# Ralph" >> .gitignore echo "ralph-progress.txt" >> .gitignore fi fi echo "" echo "Ralph initialized!" echo "" echo "Next steps:" echo " 1. Edit $RALPH_DIR/prd.md with your tasks" echo " 2. Run: ralph run 10" echo "" } # Generate PRD from codebase generate_prd() { local type=${1:-"general"} local output="$RALPH_DIR/prd-$type.md" mkdir -p "$RALPH_DIR" echo "Generating $type PRD..." # Use Claude to analyze codebase and generate PRD claude --dangerously-skip-permissions -p \ "Analyze this codebase and generate a PRD for: $type Create a markdown PRD file with: 1. Overview of what needs to be done 2. Tasks in priority order (CRITICAL, HIGH, MEDIUM, LOW) 3. Each task should have: - [ ] checkbox - Priority label - Clear description - File paths if relevant 4. Completion criteria Output ONLY the PRD content, no explanations. Format it exactly like this example: # [Type] PRD ## Overview Brief description. ## Tasks (Priority Order) - [ ] CRITICAL: Task name Description. Source: path/to/file.ts - [ ] HIGH: Another task Description. ## Completion Criteria What done looks like. " > "$output" echo "Generated: $output" echo "" cat "$output" } # Show status show_status() { echo "=== Ralph Status ===" echo "" if [ -f "$PROGRESS_FILE" ]; then echo "Progress file: $PROGRESS_FILE" echo "---" cat "$PROGRESS_FILE" else echo "No progress file found. Run 'ralph init' first." fi echo "" echo "PRD files in $RALPH_DIR/:" ls -la "$RALPH_DIR"/*.md 2>/dev/null || echo " (none)" } # Reset progress reset_progress() { if [ -f "$PROGRESS_FILE" ]; then rm "$PROGRESS_FILE" touch "$PROGRESS_FILE" echo "Progress reset." else echo "No progress file to reset." fi } # Main Ralph loop run_ralph() { local iterations=$1 local prd_file=${2:-"$RALPH_DIR/prd.md"} local use_docker=${3:-false} if [ ! -f "$prd_file" ]; then echo "Error: PRD file not found: $prd_file" echo "Run 'ralph init' first or specify a PRD file." exit 1 fi touch "$PROGRESS_FILE" echo "=== Ralph Loop Starting ===" echo "PRD: $prd_file" echo "Progress: $PROGRESS_FILE" echo "Iterations: $iterations" echo "" for ((i=1; i<=$iterations; i++)); do echo "" echo "==============================================" echo "=== RALPH ITERATION $i of $iterations ===" echo "==============================================" echo "" local claude_cmd="claude --dangerously-skip-permissions" if [ "$use_docker" = true ]; then claude_cmd="docker sandbox run claude --permission-mode acceptEdits" fi result=$($claude_cmd -p \ "@$prd_file @$PROGRESS_FILE You are an AI coding assistant running in a loop (Ralph pattern). PROCESS: 1. Read the PRD to see what needs to be done. 2. Read ralph-progress.txt to see what's already completed. 3. Choose the HIGHEST PRIORITY incomplete task. Priority order: CRITICAL > HIGH > MEDIUM > LOW 4. Implement the task thoroughly. 5. Run any relevant checks (tests, types, linting). 6. Append your progress to ralph-progress.txt: - Task completed - Files created/modified - Any issues or notes 7. Make a git commit with a clear message. RULES: - ONLY WORK ON ONE TASK PER ITERATION. - Be thorough - quality over speed. - If a task is blocked, note it and move to the next. If ALL tasks in the PRD are complete, output: COMPLETE ") echo "$result" # Check for completion if [[ "$result" == *"COMPLETE"* ]]; then echo "" echo "==============================================" echo -e "${GREEN}ALL TASKS COMPLETE after $i iteration(s)${NC}" echo "==============================================" return 0 fi if [ $i -lt $iterations ]; then echo "" echo "--- Iteration $i done, continuing in 2s ---" sleep 2 fi done echo "" echo "==============================================" echo -e "${YELLOW}Reached max iterations ($iterations)${NC}" echo "Run 'ralph status' to see progress." echo "==============================================" } # Parse arguments COMMAND="" ITERATIONS="" PRD_FILE="" USE_DOCKER=false DRY_RUN=false while [[ $# -gt 0 ]]; do case $1 in init) COMMAND="init" shift ;; run) COMMAND="run" shift ITERATIONS=${1:-10} shift ;; generate) COMMAND="generate" shift GEN_TYPE=${1:-"general"} shift ;; status) COMMAND="status" shift ;; reset) COMMAND="reset" shift ;; -p|--prd) PRD_FILE="$2" shift 2 ;; --docker) USE_DOCKER=true shift ;; --dry-run) DRY_RUN=true shift ;; -h|--help) usage exit 0 ;; -v|--version) echo "Ralph v$VERSION" exit 0 ;; *) # Check if it's a number (iterations) or file path if [[ $1 =~ ^[0-9]+$ ]]; then ITERATIONS=$1 elif [[ -f $1 ]]; then PRD_FILE=$1 fi shift ;; esac done # Execute command case $COMMAND in init) init_ralph ;; run) if [ -z "$ITERATIONS" ]; then echo "Error: Specify number of iterations" echo "Usage: ralph run [prd-file]" exit 1 fi run_ralph "$ITERATIONS" "$PRD_FILE" "$USE_DOCKER" ;; generate) generate_prd "$GEN_TYPE" ;; status) show_status ;; reset) reset_progress ;; *) usage exit 1 ;; esac