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>
47 lines
1.4 KiB
Bash
Executable File
47 lines
1.4 KiB
Bash
Executable File
#!/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": "<assistant response 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 <promise>TEXT</promise> in the response
|
|
PROMISE_TEXT=$(echo "$RESPONSE_TEXT" | perl -0777 -pe 's/.*?<promise>(.*?)<\/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
|