commit adc9537c2cd6231574c2d5c5fb52907a53049a45 Author: Zhongwei Li Date: Sun Nov 30 08:37:22 2025 +0800 Initial commit diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json new file mode 100644 index 0000000..2644ef5 --- /dev/null +++ b/.claude-plugin/plugin.json @@ -0,0 +1,18 @@ +{ + "name": "ralph-wiggum", + "description": "Ralph Wiggum iterative AI development loops - autonomous task refinement using Stop hooks. Enables hands-off iteration where Claude continuously works on a task until completion, with progress tracked via state files and completion signaled through XML promise tags.", + "version": "0.4.4", + "author": { + "name": "Fred Amaral", + "email": "fred@fredamaral.com.br" + }, + "skills": [ + "./skills" + ], + "commands": [ + "./commands" + ], + "hooks": [ + "./hooks" + ] +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..befd041 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# ralph-wiggum + +Ralph Wiggum iterative AI development loops - autonomous task refinement using Stop hooks. Enables hands-off iteration where Claude continuously works on a task until completion, with progress tracked via state files and completion signaled through XML promise tags. diff --git a/commands/cancel-ralph.md b/commands/cancel-ralph.md new file mode 100644 index 0000000..f8084ba --- /dev/null +++ b/commands/cancel-ralph.md @@ -0,0 +1,33 @@ +--- +name: cancel-ralph +description: "Cancel active Ralph Wiggum loop" +allowed-tools: ["Bash"] +hide-from-slash-command-tool: "true" +--- + +# Cancel Ralph + +```bash +bash -c ' +STATE_FILE=$(find .claude -maxdepth 1 -name "ralph-loop-*.local.md" -type f 2>/dev/null | head -1) +if [[ -n "$STATE_FILE" ]] && [[ -f "$STATE_FILE" ]]; then + ITERATION=$(grep "^iteration:" "$STATE_FILE" | sed "s/iteration: *//") + SESSION_ID=$(grep "^session_id:" "$STATE_FILE" | sed "s/session_id: *//" | tr -d "\"") + echo "FOUND_LOOP=true" + echo "ITERATION=$ITERATION" + echo "SESSION_ID=$SESSION_ID" + echo "STATE_FILE=$STATE_FILE" +else + echo "FOUND_LOOP=false" +fi +' +``` + +Check the output above: + +1. **If FOUND_LOOP=false**: + - Say "No active Ralph loop found." + +2. **If FOUND_LOOP=true**: + - Use Bash to remove the state file shown in STATE_FILE + - Report: "Cancelled Ralph loop (session: SESSION_ID, was at iteration ITERATION)" diff --git a/commands/help.md b/commands/help.md new file mode 100644 index 0000000..56f4f92 --- /dev/null +++ b/commands/help.md @@ -0,0 +1,127 @@ +--- +name: help +description: "Explain Ralph Wiggum technique and available commands" +--- + +# Ralph Wiggum Plugin Help + +Please explain the following to the user: + +## What is the Ralph Wiggum Technique? + +The Ralph Wiggum technique is an iterative development methodology based on continuous AI loops, pioneered by Geoffrey Huntley. + +**Core concept:** +```bash +while :; do + cat PROMPT.md | claude-code --continue +done +``` + +The same prompt is fed to Claude repeatedly. The "self-referential" aspect comes from Claude seeing its own previous work in the files and git history, not from feeding output back as input. + +**Each iteration:** +1. Claude receives the SAME prompt +2. Works on the task, modifying files +3. Tries to exit +4. Stop hook intercepts and feeds the same prompt again +5. Claude sees its previous work in the files +6. Iteratively improves until completion + +The technique is described as "deterministically bad in an undeterministic world" - failures are predictable, enabling systematic improvement through prompt tuning. + +## Available Commands + +### /ralph-wiggum:ralph-loop [OPTIONS] + +Start a Ralph loop in your current session. + +**Usage:** +``` +/ralph-wiggum:ralph-loop "Refactor the cache layer" --max-iterations 20 +/ralph-wiggum:ralph-loop "Add tests" --completion-promise "TESTS COMPLETE" +``` + +**Options:** +- `--max-iterations ` - Max iterations before auto-stop +- `--completion-promise ` - Promise phrase to signal completion + +**How it works:** +1. Creates `.claude/ralph-loop-{session-id}.local.md` state file (unique per session) +2. You work on the task +3. When you try to exit, stop hook intercepts +4. Same prompt fed back +5. You see your previous work +6. Continues until promise detected or max iterations + +--- + +### /ralph-wiggum:cancel-ralph + +Cancel an active Ralph loop (removes the loop state file). + +**Usage:** +``` +/ralph-wiggum:cancel-ralph +``` + +**How it works:** +- Checks for active loop state file (`.claude/ralph-loop-{session-id}.local.md`) +- Removes the state file +- Reports cancellation with session ID and iteration count + +--- + +## Key Concepts + +### Completion Promises + +To signal completion, Claude must output a `` tag: + +``` +TASK COMPLETE +``` + +The stop hook looks for this specific tag. Without it (or `--max-iterations`), Ralph runs infinitely. + +### Self-Reference Mechanism + +The "loop" doesn't mean Claude talks to itself. It means: +- Same prompt repeated +- Claude's work persists in files +- Each iteration sees previous attempts +- Builds incrementally toward goal + +## Example + +### Interactive Bug Fix + +``` +/ralph-wiggum:ralph-loop "Fix the token refresh logic in auth.ts. Output FIXED when all tests pass." --completion-promise "FIXED" --max-iterations 10 +``` + +You'll see Ralph: +- Attempt fixes +- Run tests +- See failures +- Iterate on solution +- In your current session + +## When to Use Ralph + +**Good for:** +- Well-defined tasks with clear success criteria +- Tasks requiring iteration and refinement +- Iterative development with self-correction +- Greenfield projects + +**Not good for:** +- Tasks requiring human judgment or design decisions +- One-shot operations +- Tasks with unclear success criteria +- Debugging production issues (use targeted debugging instead) + +## Learn More + +- Original technique: https://ghuntley.com/ralph/ +- Ralph Orchestrator: https://github.com/mikeyobrien/ralph-orchestrator diff --git a/commands/ralph-loop.md b/commands/ralph-loop.md new file mode 100644 index 0000000..382804e --- /dev/null +++ b/commands/ralph-loop.md @@ -0,0 +1,61 @@ +--- +name: ralph-loop +description: "Start Ralph Wiggum loop in current session" +argument-hint: "PROMPT [--max-iterations N] [--completion-promise TEXT]" +allowed-tools: ["Bash(${CLAUDE_PLUGIN_ROOT}/scripts/setup-ralph-loop.sh)"] +hide-from-slash-command-tool: "true" +--- + +# Ralph Loop Command + +Execute the setup script to initialize the Ralph loop: + +```bash +bash -c ' +# Disable glob expansion to prevent command injection from user-provided prompts +set -f +"'"${CLAUDE_PLUGIN_ROOT}"'/scripts/setup-ralph-loop.sh" "'"$ARGUMENTS"'" +SETUP_EXIT_CODE=$? +set +f + +# If setup failed, do not continue with promise display +if [ $SETUP_EXIT_CODE -ne 0 ]; then + exit $SETUP_EXIT_CODE +fi + +# Extract and display completion promise if set (find session-based state file) +STATE_FILE=$(find .claude -maxdepth 1 -name "ralph-loop-*.local.md" -type f 2>/dev/null | head -1) +if [ -n "$STATE_FILE" ] && [ -f "$STATE_FILE" ]; then + PROMISE=$(grep "^completion_promise:" "$STATE_FILE" | sed "s/completion_promise: *//" | sed "s/^\"\(.*\)\"$/\1/") + if [ -n "$PROMISE" ] && [ "$PROMISE" != "null" ]; then + echo "" + echo "═══════════════════════════════════════════════════════════" + echo "CRITICAL - Ralph Loop Completion Promise" + echo "═══════════════════════════════════════════════════════════" + echo "" + echo "To complete this loop, output this EXACT text:" + echo " $PROMISE" + echo "" + echo "STRICT REQUIREMENTS (DO NOT VIOLATE):" + echo " ✓ Use XML tags EXACTLY as shown above" + echo " ✓ The statement MUST be completely and unequivocally TRUE" + echo " ✓ Do NOT output false statements to exit the loop" + echo " ✓ Do NOT lie even if you think you should exit" + echo "" + echo "IMPORTANT - Do not circumvent the loop:" + echo " Even if you believe you are stuck, the task is impossible," + echo " or you have been running too long - you MUST NOT output a" + echo " false promise statement. The loop is designed to continue" + echo " until the promise is GENUINELY TRUE. Trust the process." + echo "" + echo " If the loop should stop, the promise statement will become" + echo " true naturally. Do not force it by lying." + echo "═══════════════════════════════════════════════════════════" + fi +fi +' +``` + +Please work on the task. When you try to exit, the Ralph loop will feed the SAME PROMPT back to you for the next iteration. You'll see your previous work in files and git history, allowing you to iterate and improve. + +CRITICAL RULE: If a completion promise is set, you may ONLY output it when the statement is completely and unequivocally TRUE. Do not output false promises to escape the loop, even if you think you're stuck or should exit for other reasons. The loop is designed to continue until genuine completion. diff --git a/hooks/hooks.json b/hooks/hooks.json new file mode 100644 index 0000000..87a7460 --- /dev/null +++ b/hooks/hooks.json @@ -0,0 +1,35 @@ +{ + "description": "Ralph Wiggum plugin hooks for iterative development loops", + "hooks": { + "SessionStart": [ + { + "matcher": "startup|resume", + "hooks": [ + { + "type": "command", + "command": "${CLAUDE_PLUGIN_ROOT}/hooks/session-start.sh" + } + ] + }, + { + "matcher": "clear|compact", + "hooks": [ + { + "type": "command", + "command": "${CLAUDE_PLUGIN_ROOT}/hooks/session-start.sh" + } + ] + } + ], + "Stop": [ + { + "hooks": [ + { + "type": "command", + "command": "${CLAUDE_PLUGIN_ROOT}/hooks/stop-hook.sh" + } + ] + } + ] + } +} diff --git a/hooks/session-start.sh b/hooks/session-start.sh new file mode 100755 index 0000000..01e5246 --- /dev/null +++ b/hooks/session-start.sh @@ -0,0 +1,87 @@ +#!/usr/bin/env bash +set -euo pipefail +# Session start hook for ralph-wiggum plugin +# Dynamically generates quick reference for Ralph Wiggum commands + +# Find the monorepo root (where shared/ directory exists) +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)" +PLUGIN_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +MONOREPO_ROOT="$(cd "$PLUGIN_ROOT/.." && pwd)" + +# Path to shared utility +SHARED_UTIL="$MONOREPO_ROOT/shared/lib/generate-reference.py" + +# Generate commands reference +if [ -f "$SHARED_UTIL" ] && command -v python3 &>/dev/null; then + # Use || true to prevent set -e from exiting on non-zero return + commands_table=$(python3 "$SHARED_UTIL" commands "$PLUGIN_ROOT/commands" 2>/dev/null) || true + + if [ -n "$commands_table" ]; then + # Build the context message + context=" +**Ralph Wiggum - Iterative AI Development Loops** + +Autonomous task refinement using Stop hooks. Claude works on a task until completion. + +**Commands:** +${commands_table} + +**How it works:** +1. You provide a prompt with clear completion criteria +2. Claude works on the task +3. Stop hook intercepts exit, re-feeds prompt +4. Loop continues until \`TEXT\` found or max iterations + +**Example:** +\`\`\` +/ralph-wiggum:ralph-loop \"Build REST API. Output DONE when tests pass.\" --completion-promise \"DONE\" --max-iterations 20 +\`\`\` + +For details: \`/ralph-wiggum:help\` +" + + # Escape for JSON using jq + if command -v jq &>/dev/null; then + context_escaped=$(echo "$context" | jq -Rs . | sed 's/^"//;s/"$//') + else + # Fallback: more complete escaping (handles tabs, carriage returns, form feeds) + # Note: Still not RFC 8259 compliant for all control chars - jq is strongly recommended + context_escaped=$(printf '%s' "$context" | \ + sed 's/\\/\\\\/g' | \ + sed 's/"/\\"/g' | \ + sed 's/ /\\t/g' | \ + sed $'s/\r/\\\\r/g' | \ + sed 's/\f/\\f/g' | \ + awk '{printf "%s\\n", $0}') + fi + + cat <\n**Ralph Wiggum - Iterative AI Development Loops**\n\nAutonomous task refinement using Stop hooks. Claude works on a task until completion.\n\n**Commands:**\n| Command | Purpose |\n|---------|----------|\n| `/ralph-wiggum:ralph-loop PROMPT [--max-iterations N] [--completion-promise TEXT]` | Start iterative loop |\n| `/ralph-wiggum:cancel-ralph` | Cancel active loop |\n| `/ralph-wiggum:help` | Show Ralph technique guide |\n\n**How it works:**\n1. You provide a prompt with clear completion criteria\n2. Claude works on the task\n3. Stop hook intercepts exit, re-feeds prompt\n4. Loop continues until `TEXT` found or max iterations\n\n**Example:**\n```\n/ralph-wiggum:ralph-loop \"Build REST API. Output DONE when tests pass.\" --completion-promise \"DONE\" --max-iterations 20\n```\n\nFor details: `/ralph-wiggum:help`\n" + } +} +EOF + fi +else + # Fallback if Python not available + cat <<'EOF' +{ + "hookSpecificOutput": { + "hookEventName": "SessionStart", + "additionalContext": "\n**Ralph Wiggum - Iterative AI Development Loops** (3 commands)\n\nFor full list: `/ralph-wiggum:help`\n" + } +} +EOF +fi diff --git a/hooks/stop-hook.sh b/hooks/stop-hook.sh new file mode 100755 index 0000000..628b463 --- /dev/null +++ b/hooks/stop-hook.sh @@ -0,0 +1,288 @@ +#!/usr/bin/env bash + +# Ralph Wiggum Stop Hook +# Prevents session exit when a ralph-loop is active +# Feeds Claude's output back as input to continue the loop + +set -euo pipefail + +# Cleanup function for temp files +cleanup() { + [[ -n "${TEMP_FILE:-}" ]] && [[ -f "$TEMP_FILE" ]] && rm -f "$TEMP_FILE" + [[ -n "${LOCK_FD:-}" ]] && exec {LOCK_FD}>&- 2>/dev/null || true +} +trap cleanup EXIT + +# Check for required dependency: jq +if ! command -v jq &>/dev/null; then + echo "⚠️ Ralph loop: Missing required dependency 'jq'" >&2 + echo " Install with: brew install jq (macOS) or apt install jq (Linux)" >&2 + echo " Ralph loop cannot function without jq. Allowing exit." >&2 + exit 0 +fi + +# Check for optional dependencies and set flags +HAS_PERL=true +HAS_FLOCK=true + +if ! command -v perl &>/dev/null; then + HAS_PERL=false + # Only warn once per session by checking if we've warned before + if [[ ! -f ".claude/.ralph-perl-warned" ]]; then + echo "⚠️ Ralph loop: 'perl' not found - using basic promise detection." >&2 + echo " Install perl for better multiline promise support." >&2 + touch ".claude/.ralph-perl-warned" 2>/dev/null || true + fi +fi + +if ! command -v flock &>/dev/null; then + HAS_FLOCK=false + # Only warn once per session + if [[ ! -f ".claude/.ralph-flock-warned" ]]; then + echo "⚠️ Ralph loop: 'flock' not found - file locking disabled." >&2 + echo " Install with: brew install flock (macOS) or apt install util-linux (Linux)" >&2 + echo " Without flock, concurrent operations may cause issues." >&2 + touch ".claude/.ralph-flock-warned" 2>/dev/null || true + fi +fi + +# Read hook input from stdin (advanced stop hook API) +HOOK_INPUT=$(cat) + +# Check if ralph-loop is active (find any ralph-loop-*.local.md file) +# Note: Use || true to prevent set -e from exiting if .claude directory doesn't exist +RALPH_STATE_FILE=$(find .claude -maxdepth 1 -name 'ralph-loop-*.local.md' -type f 2>/dev/null | head -1 || true) + +if [[ -z "$RALPH_STATE_FILE" ]] || [[ ! -f "$RALPH_STATE_FILE" ]]; then + # No active loop - allow exit + exit 0 +fi + +# Get transcript path from hook input EARLY to verify session ownership +TRANSCRIPT_PATH_CHECK=$(echo "$HOOK_INPUT" | jq -r '.transcript_path') + +# Validate transcript path is a reasonable location (defense-in-depth, fail-secure) +if [[ -n "$TRANSCRIPT_PATH_CHECK" ]] && [[ "$TRANSCRIPT_PATH_CHECK" != "null" ]]; then + # Resolve to absolute path and verify it's under expected directories + RESOLVED_PATH=$(realpath -q "$TRANSCRIPT_PATH_CHECK" 2>/dev/null || echo "") + # Fail-secure: if realpath fails or path is outside expected directories, allow exit + if [[ -z "$RESOLVED_PATH" ]]; then + echo "⚠️ Ralph loop: Could not resolve transcript path. Allowing exit." >&2 + exit 0 + fi + if [[ ! "$RESOLVED_PATH" =~ ^(/Users/|/home/|/tmp/|/var/|/private/) ]]; then + echo "⚠️ Ralph loop: Unexpected transcript path location. Allowing exit." >&2 + exit 0 + fi +fi + +# CRITICAL: Verify this loop was started in THIS session, not a stale file from another session +# Extract session_id from state file (handles various YAML quote formats) +# SYNC-POINT: Format must match setup-ralph-loop.sh output "Session ID: <8-char-alphanumeric>" +STATE_SESSION_ID=$(grep '^session_id:' "$RALPH_STATE_FILE" | sed 's/^session_id: *//' | sed 's/^["'"'"']//; s/["'"'"']$//' | tr -cd 'a-z0-9') + +if [[ -n "$TRANSCRIPT_PATH_CHECK" ]] && [[ -f "$TRANSCRIPT_PATH_CHECK" ]] && [[ -n "$STATE_SESSION_ID" ]]; then + # Check if this session's transcript contains evidence of starting this specific loop + # SYNC-POINT: grep pattern must match setup-ralph-loop.sh line ~229 output format + if ! grep -qF "Session ID: $STATE_SESSION_ID" "$TRANSCRIPT_PATH_CHECK" 2>/dev/null; then + # This state file was created by a DIFFERENT session - it's stale + echo "⚠️ Found stale Ralph state file from another session (ID: $STATE_SESSION_ID)" >&2 + echo " This session did not start the loop. Cleaning up stale file..." >&2 + rm "$RALPH_STATE_FILE" + exit 0 + fi +fi + +# Parse markdown frontmatter (YAML between ---) and extract values +FRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' "$RALPH_STATE_FILE") +ITERATION=$(echo "$FRONTMATTER" | grep '^iteration:' | sed 's/iteration: *//') +MAX_ITERATIONS=$(echo "$FRONTMATTER" | grep '^max_iterations:' | sed 's/max_iterations: *//') +# Extract completion_promise and strip surrounding quotes if present +COMPLETION_PROMISE=$(echo "$FRONTMATTER" | grep '^completion_promise:' | sed 's/completion_promise: *//' | sed 's/^"\(.*\)"$/\1/') + +# Validate numeric fields before arithmetic operations +if [[ ! "$ITERATION" =~ ^[0-9]+$ ]]; then + echo "⚠️ Ralph loop: State file corrupted" >&2 + echo " File: $RALPH_STATE_FILE" >&2 + echo " Problem: 'iteration' field is not a valid number (got: '$ITERATION')" >&2 + echo "" >&2 + echo " This usually means the state file was manually edited or corrupted." >&2 + echo " Ralph loop is stopping. Run /ralph-loop again to start fresh." >&2 + rm "$RALPH_STATE_FILE" + exit 0 +fi + +if [[ ! "$MAX_ITERATIONS" =~ ^[0-9]+$ ]]; then + echo "⚠️ Ralph loop: State file corrupted" >&2 + echo " File: $RALPH_STATE_FILE" >&2 + echo " Problem: 'max_iterations' field is not a valid number (got: '$MAX_ITERATIONS')" >&2 + echo "" >&2 + echo " This usually means the state file was manually edited or corrupted." >&2 + echo " Ralph loop is stopping. Run /ralph-loop again to start fresh." >&2 + rm "$RALPH_STATE_FILE" + exit 0 +fi + +# Check if max iterations reached +if [[ $MAX_ITERATIONS -gt 0 ]] && [[ $ITERATION -ge $MAX_ITERATIONS ]]; then + echo "🛑 Ralph loop: Max iterations ($MAX_ITERATIONS) reached." + rm "$RALPH_STATE_FILE" + exit 0 +fi + +# Get transcript path from hook input +TRANSCRIPT_PATH=$(echo "$HOOK_INPUT" | jq -r '.transcript_path') + +if [[ ! -f "$TRANSCRIPT_PATH" ]]; then + echo "⚠️ Ralph loop: Transcript file not found" >&2 + echo " Expected: $TRANSCRIPT_PATH" >&2 + echo " This is unusual and may indicate a Claude Code internal issue." >&2 + echo " Ralph loop is stopping." >&2 + rm "$RALPH_STATE_FILE" + exit 0 +fi + +# Read last assistant message from transcript (JSONL format - one JSON per line) +# First check if there are any assistant messages +if ! grep -q '"role":"assistant"' "$TRANSCRIPT_PATH"; then + echo "⚠️ Ralph loop: No assistant messages found in transcript" >&2 + echo " Transcript: $TRANSCRIPT_PATH" >&2 + echo " This is unusual and may indicate a transcript format issue" >&2 + echo " Ralph loop is stopping." >&2 + rm "$RALPH_STATE_FILE" + exit 0 +fi + +# Extract last assistant message with explicit error handling +LAST_LINE=$(grep '"role":"assistant"' "$TRANSCRIPT_PATH" | tail -1) +if [[ -z "$LAST_LINE" ]]; then + echo "⚠️ Ralph loop: Failed to extract last assistant message" >&2 + echo " Ralph loop is stopping." >&2 + rm "$RALPH_STATE_FILE" + exit 0 +fi + +# Parse JSON with proper error handling +# Note: $? after assignment always returns 0, so we use if ! pattern +JQ_ERROR="" +if ! LAST_OUTPUT=$(echo "$LAST_LINE" | jq -r ' + .message.content | + map(select(.type == "text")) | + map(.text) | + join("\n") +' 2>&1); then + JQ_ERROR="$LAST_OUTPUT" + echo "⚠️ Ralph loop: Failed to parse assistant message JSON" >&2 + echo " Error: $JQ_ERROR" >&2 + echo " This may indicate a transcript format issue." >&2 + echo " Ralph loop is stopping." >&2 + rm "$RALPH_STATE_FILE" + exit 0 +fi + +# Limit output size to prevent OOM (max 1MB) +if [[ ${#LAST_OUTPUT} -gt 1048576 ]]; then + LAST_OUTPUT="${LAST_OUTPUT:0:1048576}" +fi + +if [[ -z "$LAST_OUTPUT" ]]; then + echo "⚠️ Ralph loop: Assistant message contained no text content." >&2 + echo " Ralph loop is stopping." >&2 + rm "$RALPH_STATE_FILE" + exit 0 +fi + +# Check for completion promise (only if set) +if [[ "$COMPLETION_PROMISE" != "null" ]] && [[ -n "$COMPLETION_PROMISE" ]]; then + # Extract text from tags + PROMISE_TEXT="" + + if [[ "$HAS_PERL" = true ]]; then + # Perl method: supports multiline, non-greedy matching + # -0777 slurps entire input, s flag makes . match newlines + # .*? is non-greedy (takes FIRST tag), whitespace normalized + PROMISE_TEXT=$(echo "$LAST_OUTPUT" | perl -0777 -pe 's/.*?(.*?)<\/promise>.*/$1/s; s/^\s+|\s+$//g; s/\s+/ /g' 2>/dev/null || echo "") + else + # Fallback: grep-based extraction (single-line only, but works without perl) + # Uses grep -oP for Perl-compatible regex if available, else sed + if echo "" | grep -oP '' &>/dev/null 2>&1; then + PROMISE_TEXT=$(echo "$LAST_OUTPUT" | grep -oP '(?<=)[^<]+(?=)' | head -1 | sed 's/^[[:space:]]*//; s/[[:space:]]*$//; s/[[:space:]]\+/ /g') + else + # Ultimate fallback: sed-based (limited but portable) + PROMISE_TEXT=$(echo "$LAST_OUTPUT" | sed -n 's/.*\([^<]*\)<\/promise>.*/\1/p' | head -1 | sed 's/^[[:space:]]*//; s/[[:space:]]*$//; s/[[:space:]]\+/ /g') + fi + fi + + # Normalize stored promise whitespace for comparison (handles "TASK COMPLETE" vs "TASK COMPLETE") + NORMALIZED_PROMISE=$(echo "$COMPLETION_PROMISE" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//; s/[[:space:]]\+/ /g') + + # Use = for literal string comparison (not pattern matching) + # == in [[ ]] does glob pattern matching which breaks with *, ?, [ characters + if [[ -n "$PROMISE_TEXT" ]] && [[ "$PROMISE_TEXT" = "$NORMALIZED_PROMISE" ]]; then + echo "✅ Ralph loop: Detected $COMPLETION_PROMISE" + rm "$RALPH_STATE_FILE" + exit 0 + fi +fi + +# Not complete - continue loop with SAME PROMPT +NEXT_ITERATION=$((ITERATION + 1)) + +# Extract prompt (everything after the closing ---) +# Skip first --- line, skip until second --- line, then print everything after +# Use i>=2 instead of i==2 to handle --- in prompt content +PROMPT_TEXT=$(awk '/^---$/{i++; next} i>=2' "$RALPH_STATE_FILE") + +if [[ -z "$PROMPT_TEXT" ]]; then + echo "⚠️ Ralph loop: State file corrupted or incomplete." >&2 + echo " File: $RALPH_STATE_FILE" >&2 + echo " Problem: No prompt text found." >&2 + echo " This usually means the state file was manually edited or corrupted." >&2 + echo " Ralph loop is stopping. Run /ralph-wiggum:ralph-loop again to start fresh." >&2 + rm "$RALPH_STATE_FILE" + exit 0 +fi + +# Update iteration in frontmatter with file locking (if available) and secure temp file +LOCK_FILE="${RALPH_STATE_FILE}.lock" + +if [[ "$HAS_FLOCK" = true ]]; then + # Use flock for atomic read-modify-write to prevent race conditions + exec {LOCK_FD}>"$LOCK_FILE" + if ! flock -n "$LOCK_FD"; then + echo "⚠️ Ralph loop: Another operation in progress. Retrying..." >&2 + flock "$LOCK_FD" # Wait for lock + fi +fi + +# Use mktemp for secure temp file creation (prevents symlink attacks) +TEMP_FILE=$(mktemp "${RALPH_STATE_FILE}.XXXXXX") +sed "s/^iteration: .*/iteration: $NEXT_ITERATION/" "$RALPH_STATE_FILE" > "$TEMP_FILE" +mv "$TEMP_FILE" "$RALPH_STATE_FILE" + +# Release lock if we acquired one +if [[ "$HAS_FLOCK" = true ]]; then + exec {LOCK_FD}>&- + rm -f "$LOCK_FILE" +fi + +# Build system message with iteration count and completion promise info +if [[ "$COMPLETION_PROMISE" != "null" ]] && [[ -n "$COMPLETION_PROMISE" ]]; then + SYSTEM_MSG="🔄 Ralph iteration $NEXT_ITERATION | To stop: output $COMPLETION_PROMISE (ONLY when statement is TRUE - do not lie to exit!)" +else + SYSTEM_MSG="🔄 Ralph iteration $NEXT_ITERATION | No completion promise set - loop runs infinitely" +fi + +# Output JSON to block the stop and feed prompt back +# The "reason" field contains the prompt that will be sent back to Claude +jq -n \ + --arg prompt "$PROMPT_TEXT" \ + --arg msg "$SYSTEM_MSG" \ + '{ + "decision": "block", + "reason": $prompt, + "systemMessage": $msg + }' + +# Exit 0 for successful hook execution +exit 0 diff --git a/plugin.lock.json b/plugin.lock.json new file mode 100644 index 0000000..2d88802 --- /dev/null +++ b/plugin.lock.json @@ -0,0 +1,69 @@ +{ + "$schema": "internal://schemas/plugin.lock.v1.json", + "pluginId": "gh:LerianStudio/ring:ralph-wiggum", + "normalized": { + "repo": null, + "ref": "refs/tags/v20251128.0", + "commit": "8b8ab6786b1483f5767a1390eae060e22b5eba2c", + "treeHash": "ed97d725d08ae4f889afa294ca2ea27e99e960973ab015f681ddd2b5fefe062d", + "generatedAt": "2025-11-28T10:12:02.047780Z", + "toolVersion": "publish_plugins.py@0.2.0" + }, + "origin": { + "remote": "git@github.com:zhongweili/42plugin-data.git", + "branch": "master", + "commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390", + "repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data" + }, + "manifest": { + "name": "ralph-wiggum", + "description": "Ralph Wiggum iterative AI development loops - autonomous task refinement using Stop hooks. Enables hands-off iteration where Claude continuously works on a task until completion, with progress tracked via state files and completion signaled through XML promise tags.", + "version": "0.4.4" + }, + "content": { + "files": [ + { + "path": "README.md", + "sha256": "28c3a33847d1075eaf0bbc03bf8ac56f43263bd037ee214d55e86fa47cc6a82e" + }, + { + "path": "hooks/stop-hook.sh", + "sha256": "62cca2d27c47f824681d2491dacbb64099b55201c94ea7aa58ea310932e031a8" + }, + { + "path": "hooks/session-start.sh", + "sha256": "b4d6f2c5eb4465883bb21ee87f0288576c187b934387ca05bda56e3574bdb37b" + }, + { + "path": "hooks/hooks.json", + "sha256": "d62dbdf2212a7a9a5fe31942faeb70fd890cb78d817969356d05118c32dd7ff4" + }, + { + "path": ".claude-plugin/plugin.json", + "sha256": "3c1a68bd004ddaeaf757c8035c60982d877d90232c833205ee15d52b933789d8" + }, + { + "path": "commands/help.md", + "sha256": "74ee601ea3faebb40874eca8fda9ef01839f767832cc48896f5061eaff862e1a" + }, + { + "path": "commands/cancel-ralph.md", + "sha256": "fa67302336a12ee8709d46cd25d9c9b2f4735cba57400c1fa30650d6e01a008b" + }, + { + "path": "commands/ralph-loop.md", + "sha256": "6ee5dc4ccf285f0ff37642fbd736645d24a3392d964510c1b16c83b07c499186" + }, + { + "path": "skills/using-ralph-wiggum/SKILL.md", + "sha256": "0b34b5bb3b2d3486238f525275d9381496d1a08d222c23ba7c6f03e79b61c9d7" + } + ], + "dirSha256": "ed97d725d08ae4f889afa294ca2ea27e99e960973ab015f681ddd2b5fefe062d" + }, + "security": { + "scannedAt": null, + "scannerVersion": null, + "flags": [] + } +} \ No newline at end of file diff --git a/skills/using-ralph-wiggum/SKILL.md b/skills/using-ralph-wiggum/SKILL.md new file mode 100644 index 0000000..01418ce --- /dev/null +++ b/skills/using-ralph-wiggum/SKILL.md @@ -0,0 +1,297 @@ +--- +name: using-ralph-wiggum +description: | + Autonomous iterative loops using Stop hooks - Claude continuously works on + a task until completion promise is emitted, with same prompt re-fed each iteration. + +trigger: | + - Well-defined task with clear success criteria + - Task has objective verification (tests pass, requirements met) + - Progress is visible in file changes + - Want autonomous iteration without human intervention + +skip_when: | + - Design judgment required → use brainstorming + - Subjective quality assessment → human review needed + - Strategic pivoting may be needed → interactive approach + - Success criteria unclear → clarify first +--- + +# Using Ralph Wiggum Loops + +The ralph-wiggum plugin enables **autonomous iterative development** using Stop hooks. Instead of manually re-running prompts, Ralph creates a self-referential loop where Claude continuously works on a task until completion. + +--- + +## Dependencies + +Ralph requires certain tools to function. Check your dependencies before starting: + +### Required + +| Tool | Purpose | Check | Install | +|------|---------|-------|---------| +| **jq** | JSON parsing (transcripts) | `jq --version` | `brew install jq` (macOS) / `apt install jq` (Linux) | + +### Recommended + +| Tool | Purpose | Check | Install | Without It | +|------|---------|-------|---------|------------| +| **flock** | File locking (prevents race conditions) | `flock --version` | `brew install flock` (macOS) / `apt install util-linux` (Linux) | Ralph works but concurrent access may cause issues | +| **perl** | Multiline promise detection | `perl --version` | Usually pre-installed | Falls back to basic regex (single-line promises only) | + +### Quick Dependency Check + +Run this to verify your setup: + +```bash +echo "=== Ralph Wiggum Dependency Check ===" && \ +echo -n "jq: " && (command -v jq &>/dev/null && echo "✅ installed" || echo "❌ MISSING (required)") && \ +echo -n "flock: " && (command -v flock &>/dev/null && echo "✅ installed" || echo "⚠️ missing (recommended)") && \ +echo -n "perl: " && (command -v perl &>/dev/null && echo "✅ installed" || echo "⚠️ missing (recommended)") +``` + +**Note:** Ralph will warn you at runtime if optional dependencies are missing, but only once per session. + +--- + +## How Ralph Works + +``` +User runs: /ralph-wiggum:ralph-loop "Task description" --completion-promise "DONE" + ↓ +Plugin creates: .claude/ralph-loop-{session-id}.local.md (session-isolated state file) + ↓ +Claude works on the task... + ↓ +Session exit attempted + ↓ +Stop hook intercepts → Checks for DONE in output + ↓ + ├── Promise found → Allow exit (task complete!) + ├── Max iterations → Allow exit (safety limit) + └── Neither → Block exit, re-feed original prompt → Loop continues +``` + +The key insight: **the prompt never changes** - Claude improves by reading its own previous work in modified files and git history. + +--- + +## Available Commands + +| Command | Purpose | +|---------|---------| +| `/ralph-wiggum:ralph-loop` | Start an iterative development loop | +| `/ralph-wiggum:cancel-ralph` | Cancel the active loop | +| `/ralph-wiggum:help` | Detailed technique guide and examples | + +--- + +## Starting a Ralph Loop + +```bash +/ralph-wiggum:ralph-loop "PROMPT" --max-iterations N --completion-promise "TEXT" +``` + +**Parameters:** +- `PROMPT` - Your task description (required) +- `--max-iterations N` - Safety limit (recommended, default: unlimited) +- `--completion-promise TEXT` - Phrase that signals completion + +**Example:** +```bash +/ralph-wiggum:ralph-loop "Build a REST API for todos with CRUD operations and tests. Output COMPLETE when all tests pass." --completion-promise "COMPLETE" --max-iterations 30 +``` + +--- + +## Writing Effective Prompts + +### 1. Clear Completion Criteria + +❌ **Bad:** +``` +Build a todo API and make it good. +``` + +✅ **Good:** +```markdown +Build a REST API for todos. + +Requirements: +- CRUD endpoints (GET, POST, PUT, DELETE) +- Input validation +- Test coverage > 80% +- README with API documentation + +Output COMPLETE when ALL requirements are met. +``` + +### 2. Include Self-Correction + +❌ **Bad:** +``` +Write code for feature X. +``` + +✅ **Good:** +```markdown +Implement feature X following TDD: +1. Write failing tests +2. Implement feature +3. Run tests +4. If any fail, debug and fix +5. Repeat until all green +6. Output: DONE +``` + +### 3. Always Set Safety Limits + +```bash +# RECOMMENDED: Always use --max-iterations +/ralph-wiggum:ralph-loop "Try feature X" --max-iterations 20 --completion-promise "DONE" + +# Include stuck-handling in prompt: +"After 15 iterations if not complete: + - Document blocking issues + - List attempted approaches + - Suggest alternatives" +``` + +--- + +## When to Use Ralph + +### ✅ Good Fit (Ralph Excels) + +| Task Type | Why It Works | +|-----------|--------------| +| "Make all tests pass" | Clear, verifiable success criteria | +| "Implement features from spec" | Additive work visible in files | +| "Fix CI pipeline errors" | Objective pass/fail feedback | +| Greenfield with clear requirements | Progress is self-evident | +| "Build X with tests" | Tests provide automatic verification | + +**Key pattern:** Tasks where success is **objectively verifiable** and progress is **visible in files**. + +### ❌ Poor Fit (Ralph Struggles) + +| Task Type | Why It Struggles | +|-----------|------------------| +| "Design a good API" | Requires judgment, no objective criteria | +| "Refactor for maintainability" | Success is subjective | +| "Debug intermittent failure" | May not reproduce consistently | +| Exploratory/architectural work | Needs human course-correction | +| "Make it better" | No clear completion criteria | + +**Key pattern:** Tasks requiring **design judgment**, **strategic pivoting**, or **subjective quality assessment**. + +### Understanding Ralph's Limitations + +Ralph's power comes from **prompt invariance** - the same prompt feeds every iteration. This is both strength and weakness: + +**Strength:** Simple, deterministic, no prompt drift +**Weakness:** If the original prompt is flawed or ambiguous, Ralph iterates on a flawed foundation + +Claude must infer "what to do next" entirely from file changes and git history. This works when: +- Changes are clearly visible in files +- The task is additive (build more features) +- Success criteria are objective + +It struggles when: +- The issue is architectural (not visible in individual files) +- Previous attempts need explicit "don't do this again" memory +- The task requires strategic pivoting based on discoveries + +--- + +## State Management + +Ralph tracks state in `.claude/ralph-loop-{session-id}.local.md`: + +```yaml +--- +active: true +session_id: "a1b2c3d4" +iteration: 5 +max_iterations: 30 +completion_promise: "COMPLETE" +started_at: 2025-01-26T10:30:00Z +--- + +Original prompt here... +``` + +**Session Isolation:** Each Ralph loop gets a unique session ID, so you can run multiple Claude sessions in different directories without conflicts. + +**Commands interact with this file:** +- `/ralph-wiggum:ralph-loop` creates it (with unique session ID) +- `/ralph-wiggum:cancel-ralph` finds and removes it +- Stop hook finds and updates it + +--- + +## Safety Features + +1. **Max Iterations** - Prevents infinite loops on impossible tasks +2. **Session-Isolated State** - Each loop has unique ID (`ralph-loop-{id}.local.md`) +3. **Active Loop Detection** - Prevents accidental overwrites of running loops +4. **Cancel Command** - Immediately stop any running loop +5. **Completion Promise** - Explicit signal that work is done + +--- + +## Philosophy + +Ralph embodies key principles: + +1. **Iteration > Perfection** - Don't aim for perfect first try; let the loop refine +2. **Failures Are Data** - Use failures to improve the prompt +3. **Persistence Wins** - Keep trying until success +4. **Operator Skill Matters** - Success depends on writing good prompts + +--- + +## Integration with Ring + +Ralph complements other Ring workflows: + +- Use **brainstorming** to design the task before starting a Ralph loop +- Include **TDD patterns** in your Ralph prompt for self-verification +- After Ralph completes, run **/ring-default:codereview** for code quality check + +**Example workflow:** +``` +1. /ring-default:brainstorm "TODO API design" +2. /ralph-wiggum:ralph-loop "Implement TODO API per design..." --max-iterations 30 +3. /ring-default:codereview src/ +4. /ring-default:commit "feat: add TODO API" +``` + +--- + +## Troubleshooting + +**Loop not starting?** +- Check if `.claude/ralph-loop-*.local.md` was created +- Verify the prompt is properly quoted +- Check for "already active" error (cancel existing loop first) + +**Loop not stopping?** +- Ensure `TEXT` exactly matches `--completion-promise` +- Check iteration count hasn't hit max + +**Want to cancel?** +- Run `/ralph-wiggum:cancel-ralph` +- Or manually: `rm .claude/ralph-loop-*.local.md` + +**Multiple sessions?** +- Each session gets its own state file with unique ID +- Only one loop per directory at a time (safety feature) + +--- + +## Learn More + +- Original technique: https://ghuntley.com/ralph/ +- Ralph Orchestrator: https://github.com/mikeyobrien/ralph-orchestrator