mirror of
https://github.com/cursor/plugins.git
synced 2026-09-14 20:00:00 +08:00
e87eaecc7b
* Add Ralph Loop plugin Port of the Ralph Wiggum technique from Claude Code to Cursor. Runs Cursor in a self-referential loop, feeding the same prompt back after every turn until a completion condition is met. Components: - Stop hook that intercepts session exit and re-injects the prompt - Setup script for initializing loop state - Skills for starting, cancelling, and explaining the loop Co-authored-by: Cursor <cursoragent@cursor.com> * Comply with Cursor hooks API - Rewrite hooks.json to Cursor format (version 1, lowercase event names, flat structure, loop_limit: null) - Replace Claude Code stop hook output (decision/block/reason) with Cursor's followup_message API - Add afterAgentResponse hook to detect completion promise without depending on transcript format - Use CURSOR_PROJECT_DIR env var for reliable state file paths - Remove setup script; ralph-loop skill now instructs the agent to create the state file directly - Update cancel-ralph skill to clean up done flag file Co-authored-by: Cursor <cursoragent@cursor.com> * Move state files to .cursor/ralph/ subdirectory State file: .cursor/ralph/scratchpad.md Done flag: .cursor/ralph/done Cancel cleans up with rm -rf .cursor/ralph Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
87 lines
2.8 KiB
Bash
Executable File
87 lines
2.8 KiB
Bash
Executable File
#!/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": "<text>" } 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 <promise>$COMPLETION_PROMISE</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
|