diff --git a/.cursor-plugin/marketplace.json b/.cursor-plugin/marketplace.json index 4b1c895..193fb08 100644 --- a/.cursor-plugin/marketplace.json +++ b/.cursor-plugin/marketplace.json @@ -72,6 +72,11 @@ "name": "figma", "source": "figma", "description": "Figma MCP integration for design and collaboration." + }, + { + "name": "ralph-loop", + "source": "ralph-loop", + "description": "Iterative self-referential AI loops using the Ralph Wiggum technique." } ] } diff --git a/README.md b/README.md index b3ab0f3..6edfaed 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ Official Cursor plugins for popular developer tools, frameworks, and SaaS produc | [Learning](learning/) | Utilities | Skill maps, practice plans, and feedback loops | | [Cursor Dev Kit](cursor-dev-kit/) | Developer Tools | Internal-style workflows for CI, code review, shipping, and testing | | [Create Plugin](create-plugin/) | Developer Tools | Meta workflows for creating Cursor plugins with scaffolding and submission checks | +| [Ralph Loop](ralph-loop/) | Developer Tools | Iterative self-referential AI loops using the Ralph Wiggum technique | ## Repository structure diff --git a/ralph-loop/.cursor-plugin/plugin.json b/ralph-loop/.cursor-plugin/plugin.json new file mode 100644 index 0000000..4d5ed2a --- /dev/null +++ b/ralph-loop/.cursor-plugin/plugin.json @@ -0,0 +1,29 @@ +{ + "name": "ralph-loop", + "displayName": "Ralph Loop", + "version": "1.0.0", + "description": "Continuous self-referential AI loops for iterative development, implementing the Ralph Wiggum technique. Run the agent in a while-true loop with the same prompt until task completion.", + "author": { + "name": "Cursor", + "email": "plugins@cursor.com" + }, + "homepage": "https://github.com/cursor/plugins", + "repository": "https://github.com/cursor/plugins", + "license": "MIT", + "keywords": [ + "ralph", + "loop", + "iteration", + "self-referential", + "automation", + "iterative-development" + ], + "category": "developer-tools", + "tags": [ + "automation", + "iteration", + "agent-loop" + ], + "skills": "./skills/", + "hooks": "./hooks/hooks.json" +} diff --git a/ralph-loop/LICENSE b/ralph-loop/LICENSE new file mode 100644 index 0000000..ca2bba7 --- /dev/null +++ b/ralph-loop/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Cursor + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/ralph-loop/README.md b/ralph-loop/README.md new file mode 100644 index 0000000..67a3f75 --- /dev/null +++ b/ralph-loop/README.md @@ -0,0 +1,61 @@ +# Ralph Loop + +Ralph Loop runs Cursor in a self-referential loop, feeding the same prompt back after every turn until the task is complete. It implements the [Ralph Wiggum technique](https://ghuntley.com/ralph/) pioneered by Geoffrey Huntley. + +## How it works + +Two hooks drive the loop. An `afterAgentResponse` hook watches each response for a `` tag matching the completion phrase. A `stop` hook fires when Cursor finishes a turn. If the promise hasn't been detected and the iteration limit hasn't been reached, the stop hook sends the original prompt back as a `followup_message`, starting the next iteration. Cursor sees its own previous edits in the working tree and git history, iterates on them, and repeats. The prompt never changes. The code does. + +## Installation + +``` +agent install ralph-loop +``` + +## Quick start + +> Start a ralph loop: "Build a REST API for todos. CRUD operations, input validation, tests. Output COMPLETE when done." --completion-promise "COMPLETE" --max-iterations 50 + +Cursor will implement the API, run tests, see failures, fix them, and repeat until all requirements are met. + +## Skills + +**ralph-loop** starts the loop. Provide a prompt and options: + +> Start a ralph loop: "Refactor the cache layer" --max-iterations 20 --completion-promise "DONE" + +- `--max-iterations ` stops after N iterations (default: unlimited) +- `--completion-promise ` sets the phrase that signals completion + +**cancel-ralph** removes the state file and stops the loop. + +**ralph-loop-help** explains the technique and usage in detail. + +## Writing good prompts + +Define explicit completion criteria. Vague goals like "make it good" give Cursor nothing to verify against. + +```markdown +Build a REST API for todos. + +When complete: +- All CRUD endpoints working +- Input validation in place +- Tests passing (coverage > 80%) +- Output: COMPLETE +``` + +Break large tasks into phases. Encourage self-correction by including test/fix cycles in the prompt. Always pass `--max-iterations` to prevent runaway loops. + +## When to use Ralph Loop + +Works well for tasks with clear, verifiable success criteria: getting tests to pass, completing a migration, building a feature from a spec. Not a good fit for tasks that need human judgment or have ambiguous goals. + +## Learn more + +- [Original technique by Geoffrey Huntley](https://ghuntley.com/ralph/) +- [Ralph Orchestrator](https://github.com/mikeyobrien/ralph-orchestrator) + +## License + +MIT diff --git a/ralph-loop/hooks/capture-response.sh b/ralph-loop/hooks/capture-response.sh new file mode 100755 index 0000000..d8da50a --- /dev/null +++ b/ralph-loop/hooks/capture-response.sh @@ -0,0 +1,46 @@ +#!/bin/bash + +# afterAgentResponse hook for Ralph Loop. +# Checks if the agent's response contains a matching completion promise. +# If found, writes a done flag so the stop hook knows to end the loop. +# +# Input: { "text": "" } +# Output: none (fire-and-forget) + +set -euo pipefail + +HOOK_INPUT=$(cat) + +PROJECT_DIR="${CURSOR_PROJECT_DIR:-.}" +STATE_FILE="$PROJECT_DIR/.cursor/ralph/scratchpad.md" +DONE_FLAG="$PROJECT_DIR/.cursor/ralph/done" + +# No active loop, nothing to do +if [[ ! -f "$STATE_FILE" ]]; then + exit 0 +fi + +# Extract completion promise from state file frontmatter +FRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' "$STATE_FILE") +COMPLETION_PROMISE=$(echo "$FRONTMATTER" | grep '^completion_promise:' | sed 's/completion_promise: *//' | sed 's/^"\(.*\)"$/\1/') + +# No promise configured, nothing to check +if [[ "$COMPLETION_PROMISE" = "null" ]] || [[ -z "$COMPLETION_PROMISE" ]]; then + exit 0 +fi + +# Extract response text from hook input +RESPONSE_TEXT=$(echo "$HOOK_INPUT" | jq -r '.text // empty') + +if [[ -z "$RESPONSE_TEXT" ]]; then + exit 0 +fi + +# Check for TEXT in the response +PROMISE_TEXT=$(echo "$RESPONSE_TEXT" | perl -0777 -pe 's/.*?(.*?)<\/promise>.*/$1/s; s/^\s+|\s+$//g; s/\s+/ /g' 2>/dev/null || echo "") + +if [[ -n "$PROMISE_TEXT" ]] && [[ "$PROMISE_TEXT" = "$COMPLETION_PROMISE" ]]; then + touch "$DONE_FLAG" +fi + +exit 0 diff --git a/ralph-loop/hooks/hooks.json b/ralph-loop/hooks/hooks.json new file mode 100644 index 0000000..04a9966 --- /dev/null +++ b/ralph-loop/hooks/hooks.json @@ -0,0 +1,16 @@ +{ + "version": 1, + "hooks": { + "afterAgentResponse": [ + { + "command": "./hooks/capture-response.sh" + } + ], + "stop": [ + { + "command": "./hooks/stop-hook.sh", + "loop_limit": null + } + ] + } +} diff --git a/ralph-loop/hooks/stop-hook.sh b/ralph-loop/hooks/stop-hook.sh new file mode 100755 index 0000000..ba0ba34 --- /dev/null +++ b/ralph-loop/hooks/stop-hook.sh @@ -0,0 +1,86 @@ +#!/bin/bash + +# Ralph Loop stop hook. +# When the agent finishes a turn, this hook decides whether to feed the +# same prompt back for another iteration or let the session end. +# +# Cursor stop hook API: +# Input: { "status": "completed"|"aborted"|"error", "loop_count": N, ...common } +# Output: { "followup_message": "" } to continue, or exit 0 with no output to stop + +set -euo pipefail + +HOOK_INPUT=$(cat) + +PROJECT_DIR="${CURSOR_PROJECT_DIR:-.}" +STATE_FILE="$PROJECT_DIR/.cursor/ralph/scratchpad.md" +DONE_FLAG="$PROJECT_DIR/.cursor/ralph/done" + +# No active loop. Let the session end. +if [[ ! -f "$STATE_FILE" ]]; then + exit 0 +fi + +# Parse state file frontmatter +FRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' "$STATE_FILE") +ITERATION=$(echo "$FRONTMATTER" | grep '^iteration:' | sed 's/iteration: *//') +MAX_ITERATIONS=$(echo "$FRONTMATTER" | grep '^max_iterations:' | sed 's/max_iterations: *//') +COMPLETION_PROMISE=$(echo "$FRONTMATTER" | grep '^completion_promise:' | sed 's/completion_promise: *//' | sed 's/^"\(.*\)"$/\1/') + +# Validate iteration is numeric +if [[ ! "$ITERATION" =~ ^[0-9]+$ ]]; then + echo "Ralph loop: state file corrupted (iteration: '$ITERATION'). Stopping." >&2 + rm -f "$STATE_FILE" "$DONE_FLAG" + exit 0 +fi + +if [[ ! "$MAX_ITERATIONS" =~ ^[0-9]+$ ]]; then + echo "Ralph loop: state file corrupted (max_iterations: '$MAX_ITERATIONS'). Stopping." >&2 + rm -f "$STATE_FILE" "$DONE_FLAG" + exit 0 +fi + +# Check if completion promise was detected by the afterAgentResponse hook +if [[ -f "$DONE_FLAG" ]]; then + echo "Ralph loop: completion promise fulfilled at iteration $ITERATION." >&2 + rm -f "$STATE_FILE" "$DONE_FLAG" + exit 0 +fi + +# Check max iterations +if [[ $MAX_ITERATIONS -gt 0 ]] && [[ $ITERATION -ge $MAX_ITERATIONS ]]; then + echo "Ralph loop: max iterations ($MAX_ITERATIONS) reached." >&2 + rm -f "$STATE_FILE" "$DONE_FLAG" + exit 0 +fi + +# Extract prompt text (everything after the closing --- in frontmatter) +PROMPT_TEXT=$(awk '/^---$/{i++; next} i>=2' "$STATE_FILE") + +if [[ -z "$PROMPT_TEXT" ]]; then + echo "Ralph loop: no prompt text found in state file. Stopping." >&2 + rm -f "$STATE_FILE" "$DONE_FLAG" + exit 0 +fi + +# Increment iteration +NEXT_ITERATION=$((ITERATION + 1)) +TEMP_FILE="${STATE_FILE}.tmp.$$" +sed "s/^iteration: .*/iteration: $NEXT_ITERATION/" "$STATE_FILE" > "$TEMP_FILE" +mv "$TEMP_FILE" "$STATE_FILE" + +# Build the followup message: iteration context + original prompt +if [[ "$COMPLETION_PROMISE" != "null" ]] && [[ -n "$COMPLETION_PROMISE" ]]; then + HEADER="[Ralph loop iteration $NEXT_ITERATION. To complete: output $COMPLETION_PROMISE ONLY when genuinely true.]" +else + HEADER="[Ralph loop iteration $NEXT_ITERATION.]" +fi + +FOLLOWUP="$HEADER + +$PROMPT_TEXT" + +# Output followup_message to continue the loop +jq -n --arg msg "$FOLLOWUP" '{"followup_message": $msg}' + +exit 0 diff --git a/ralph-loop/skills/cancel-ralph/SKILL.md b/ralph-loop/skills/cancel-ralph/SKILL.md new file mode 100644 index 0000000..1e75c5b --- /dev/null +++ b/ralph-loop/skills/cancel-ralph/SKILL.md @@ -0,0 +1,28 @@ +--- +name: cancel-ralph +description: Cancel an active Ralph Loop. Use when the user wants to stop, cancel, or abort a running ralph loop. +--- + +# Cancel Ralph + +## Trigger + +The user wants to cancel or stop an active Ralph loop. + +## Workflow + +1. Check if `.cursor/ralph/scratchpad.md` exists. + +2. **If it does not exist**: Tell the user "No active Ralph loop found." + +3. **If it exists**: + - Read `.cursor/ralph/scratchpad.md` to get the current iteration from the `iteration:` field. + - Remove the state file and any done flag: + ```bash + rm -rf .cursor/ralph + ``` + - Report: "Cancelled Ralph loop (was at iteration N)." + +## Output + +A short confirmation with the iteration count, or a message that no loop was active. diff --git a/ralph-loop/skills/ralph-loop-help/SKILL.md b/ralph-loop/skills/ralph-loop-help/SKILL.md new file mode 100644 index 0000000..8e6a76a --- /dev/null +++ b/ralph-loop/skills/ralph-loop-help/SKILL.md @@ -0,0 +1,81 @@ +--- +name: ralph-loop-help +description: Explain the Ralph Loop plugin, how it works, and available skills. Use when the user asks for help with ralph loop, wants to understand the technique, or needs usage examples. +--- + +# Ralph Loop Help + +## Trigger + +The user asks what Ralph Loop is, how it works, or needs usage guidance. + +## What to Explain + +### What is Ralph Loop? + +Ralph Loop implements the Ralph Wiggum technique — an iterative development methodology based on continuous AI loops, pioneered by Geoffrey Huntley. + +Core concept: the same prompt is fed to the agent repeatedly. The "self-referential" aspect comes from the agent seeing its own previous work in the files and git history, not from feeding output back as input. + +Each iteration: +1. The agent 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. The agent sees its previous work in the files +6. Iteratively improves until completion + +### Starting a Ralph Loop + +Tell the agent your task along with options: + +``` +Start a ralph loop: "Build a REST API for todos" --max-iterations 20 --completion-promise "COMPLETE" +``` + +Options: +- `--max-iterations N` — max iterations before auto-stop +- `--completion-promise "TEXT"` — phrase to signal completion + +How it works: +1. Creates `.cursor/ralph/scratchpad.md` state file +2. Agent works on the task +3. Stop hook intercepts exit and feeds the same prompt back +4. Agent sees its previous work and iterates +5. Continues until promise detected or max iterations reached + +### Cancelling a Ralph Loop + +Ask the agent to cancel the ralph loop. It will remove the state file and report the iteration count. + +### Completion Promises + +To signal completion, the agent outputs a `` tag: + +``` +TASK COMPLETE +``` + +The stop hook looks for this specific tag. Without it (or `--max-iterations`), Ralph runs indefinitely. + +### 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 + +### Learn More + +- Original technique: https://ghuntley.com/ralph/ +- Ralph Orchestrator: https://github.com/mikeyobrien/ralph-orchestrator + +## Output + +Present the above information clearly to the user, tailored to their specific question. diff --git a/ralph-loop/skills/ralph-loop/SKILL.md b/ralph-loop/skills/ralph-loop/SKILL.md new file mode 100644 index 0000000..5c94318 --- /dev/null +++ b/ralph-loop/skills/ralph-loop/SKILL.md @@ -0,0 +1,54 @@ +--- +name: ralph-loop +description: Start a Ralph Loop for iterative self-referential development. Use when the user asks to run a ralph loop, start an iterative loop, or wants repeated autonomous iteration on a task until completion. +--- + +# Ralph Loop + +## Trigger + +The user wants to start a Ralph loop. An iterative development loop where the same prompt is fed back after every turn, and the agent sees its own previous work each iteration. + +## Workflow + +1. Gather the user's task prompt and optional parameters: + - `max_iterations` (number, default 0 for unlimited) + - `completion_promise` (text, or "null" if not set) + +2. Create the directory `.cursor/ralph/` if it doesn't exist, then write the state file at `.cursor/ralph/scratchpad.md` with this exact format: + + ```markdown + --- + iteration: 1 + max_iterations: + completion_promise: "" or null + --- + + + ``` + + Example: + ```markdown + --- + iteration: 1 + max_iterations: 20 + completion_promise: "COMPLETE" + --- + + Build a REST API for todos with CRUD operations, input validation, and tests. + ``` + +3. Confirm to the user that the Ralph loop is active, then begin working on the task. + +4. The stop hook automatically intercepts each turn end and feeds the same prompt back as a followup message. You will see it prefixed with `[Ralph loop iteration N.]`. + +## Guardrails + +- If a completion promise is set, you may ONLY output `TEXT` when the statement is completely and genuinely true. +- Do not output false promises to escape the loop. +- Always recommend setting `max_iterations` as a safety net. +- Quote the `completion_promise` value in the YAML frontmatter if it contains special characters. + +## Output + +Confirm the loop is active (prompt, iteration limit, promise if set), then start working on the task immediately.