Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 08:37:22 +08:00
commit adc9537c2c
10 changed files with 1018 additions and 0 deletions

View File

@@ -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"
]
}

3
README.md Normal file
View File

@@ -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.

33
commands/cancel-ralph.md Normal file
View File

@@ -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)"

127
commands/help.md Normal file
View File

@@ -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 <PROMPT> [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 <n>` - Max iterations before auto-stop
- `--completion-promise <text>` - 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 `<promise>` tag:
```
<promise>TASK COMPLETE</promise>
```
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 <promise>FIXED</promise> 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

61
commands/ralph-loop.md Normal file
View File

@@ -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>$PROMISE</promise>"
echo ""
echo "STRICT REQUIREMENTS (DO NOT VIOLATE):"
echo " ✓ Use <promise> 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.

35
hooks/hooks.json Normal file
View File

@@ -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"
}
]
}
]
}
}

87
hooks/session-start.sh Executable file
View File

@@ -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-system>
**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 \`<promise>TEXT</promise>\` found or max iterations
**Example:**
\`\`\`
/ralph-wiggum:ralph-loop \"Build REST API. Output <promise>DONE</promise> when tests pass.\" --completion-promise \"DONE\" --max-iterations 20
\`\`\`
For details: \`/ralph-wiggum:help\`
</ralph-wiggum-system>"
# 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 <<EOF
{
"hookSpecificOutput": {
"hookEventName": "SessionStart",
"additionalContext": "${context_escaped}"
}
}
EOF
else
# Fallback to static output
cat <<'EOF'
{
"hookSpecificOutput": {
"hookEventName": "SessionStart",
"additionalContext": "<ralph-wiggum-system>\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 `<promise>TEXT</promise>` found or max iterations\n\n**Example:**\n```\n/ralph-wiggum:ralph-loop \"Build REST API. Output <promise>DONE</promise> when tests pass.\" --completion-promise \"DONE\" --max-iterations 20\n```\n\nFor details: `/ralph-wiggum:help`\n</ralph-wiggum-system>"
}
}
EOF
fi
else
# Fallback if Python not available
cat <<'EOF'
{
"hookSpecificOutput": {
"hookEventName": "SessionStart",
"additionalContext": "<ralph-wiggum-system>\n**Ralph Wiggum - Iterative AI Development Loops** (3 commands)\n\nFor full list: `/ralph-wiggum:help`\n</ralph-wiggum-system>"
}
}
EOF
fi

288
hooks/stop-hook.sh Executable file
View File

@@ -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 <promise> 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>(.*?)<\/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 '(?<=<promise>)[^<]+(?=</promise>)' | 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>\([^<]*\)<\/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 <promise>$COMPLETION_PROMISE</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 <promise>$COMPLETION_PROMISE</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

69
plugin.lock.json Normal file
View File

@@ -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": []
}
}

View File

@@ -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 <promise>DONE</promise> 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 <promise>COMPLETE</promise> 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 <promise>COMPLETE</promise> 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: <promise>DONE</promise>
```
### 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 `<promise>TEXT</promise>` 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