commit f3174cebc9526d20080fb77f5bd4a8cdfe548c42 Author: tegwick Date: Sat Mar 14 19:16:57 2026 +0100 feat: initial ralph-workplan skill Standalone Claude Code skill that ties a ralph loop to a workplan file. Retires automatically when all tasks are done — no external dependencies. - plugin/ralph-workplan.md skill entrypoint - plugin/scripts/check-done.sh pre-start guard (reads workplan status) - plugin/scripts/setup.sh writes ralph state file with workplan-aware prompt - install.sh copies plugin files to ~/.claude/plugins/ - workplan-spec.md workplan file format reference - README.md Co-Authored-By: Claude Sonnet 4.6 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4c5f206 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.claude/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..b72f403 --- /dev/null +++ b/README.md @@ -0,0 +1,104 @@ +# ralph-workplan + +A Claude Code skill that starts a [Ralph Loop](https://github.com/anthropics/claude-code) +tied to a workplan file. The loop retires automatically when all tasks in the +workplan are done — no external services required. + +## What it does + +``` +/ralph-workplan workplans/WP-0001-my-feature.md +/ralph-workplan workplans/WP-0001-my-feature.md --max-iterations 15 +``` + +On each iteration, Claude: +1. Re-reads the workplan file and checks task statuses +2. If all tasks are `done` and workplan `status: done` → outputs `HEUREKA` and the loop stops +3. Otherwise → continues implementing, marking tasks done as it goes + +Before starting, the skill guards against running on an already-completed workplan. + +## Requirements + +- [Claude Code](https://claude.ai/code) with the `ralph-loop` plugin installed +- Bash (macOS or Linux) + +No other dependencies. No external services. + +## Install + +```bash +git clone ~/ralph-workplan +cd ~/ralph-workplan +./install.sh +# restart Claude Code +``` + +To uninstall: +```bash +./install.sh --uninstall +``` + +## Workplan format + +A workplan is a Markdown file with YAML frontmatter and task blocks: + +```markdown +--- +id: WP-0001 +title: "Build a REST API" +status: active +--- + +Build a simple REST API with CRUD endpoints for a todo list. + +## Task: Set up project structure + +​```task +id: T-01 +status: todo +priority: high +​``` + +## Task: Implement endpoints + +​```task +id: T-02 +status: todo +priority: high +​``` + +## Task: Write tests + +​```task +id: T-03 +status: todo +priority: medium +​``` +``` + +See [workplan-spec.md](workplan-spec.md) for the full format reference. + +## How completion works + +The loop is entirely file-driven. As Claude completes tasks it edits the +workplan file: + +``` +status: todo → status: in_progress → status: done +``` + +When every task is `done`, Claude also updates the workplan frontmatter to +`status: done`. The ralph loop detects this on the next iteration and stops. + +No state hub, no HTTP calls, no external coordination needed. + +## Why not just use `/ralph-loop` directly? + +`/ralph-loop` with a static prompt has no awareness of completion state — it +loops forever (or until `--max-iterations`) even if the work is already done. +`/ralph-workplan` ties the loop lifecycle to the workplan file, so it: + +- Refuses to start if the workplan is already done +- Self-retires the moment all tasks are complete +- Always sets `--completion-promise HEUREKA` and a bounded iteration count diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..067f44a --- /dev/null +++ b/install.sh @@ -0,0 +1,39 @@ +#!/bin/bash +# install.sh — install the ralph-workplan skill into ~/.claude/plugins/ +# +# Usage: +# ./install.sh # install +# ./install.sh --uninstall # remove + +set -euo pipefail + +PLUGIN_NAME="ralph-workplan" +PLUGIN_SRC="$(cd "$(dirname "$0")/plugin" && pwd)" +PLUGIN_DEST="${HOME}/.claude/plugins/${PLUGIN_NAME}" + +if [[ "${1:-}" == "--uninstall" ]]; then + if [[ -d "$PLUGIN_DEST" ]]; then + rm -rf "$PLUGIN_DEST" + echo "✅ Uninstalled: $PLUGIN_DEST" + else + echo "ℹ️ Not installed (nothing to remove)" + fi + exit 0 +fi + +# Install +mkdir -p "${PLUGIN_DEST}/scripts" + +cp "${PLUGIN_SRC}/ralph-workplan.md" "${PLUGIN_DEST}/ralph-workplan.md" +cp "${PLUGIN_SRC}/scripts/check-done.sh" "${PLUGIN_DEST}/scripts/check-done.sh" +cp "${PLUGIN_SRC}/scripts/setup.sh" "${PLUGIN_DEST}/scripts/setup.sh" + +chmod +x "${PLUGIN_DEST}/scripts/check-done.sh" +chmod +x "${PLUGIN_DEST}/scripts/setup.sh" + +echo "✅ Installed: $PLUGIN_DEST" +echo "" +echo " Skill: /ralph-workplan [--max-iterations N]" +echo " Spec: $(cd "$(dirname "$0")" && pwd)/workplan-spec.md" +echo "" +echo " Restart Claude Code for the skill to appear." diff --git a/plugin/ralph-workplan.md b/plugin/ralph-workplan.md new file mode 100644 index 0000000..dc44ac3 --- /dev/null +++ b/plugin/ralph-workplan.md @@ -0,0 +1,76 @@ +--- +description: "Start a Ralph loop tied to a workplan file — retires automatically when all tasks are done" +argument-hint: " [--max-iterations N]" +allowed-tools: ["Read", "Bash(${CLAUDE_PLUGIN_ROOT}/scripts/check-done.sh:*)", "Bash(${CLAUDE_PLUGIN_ROOT}/scripts/setup.sh:*)"] +hide-from-slash-command-tool: "true" +--- + +# Ralph Workplan + +Start a ralph loop driven by a workplan file. The loop retires automatically +when all tasks in the workplan are marked done. + +## Usage + +``` +/ralph-workplan workplans/WP-0001-my-task.md +/ralph-workplan workplans/WP-0001-my-task.md --max-iterations 15 +``` + +The workplan file must follow the format described in `workplan-spec.md` +(YAML frontmatter with `id`, `title`, `status` fields; tasks as fenced +`task` code blocks with `id` and `status`). + +## Steps + +Parse `$ARGUMENTS` to extract: +- `WORKPLAN_FILE` — the positional argument (required) +- `MAX_ITERATIONS` — value after `--max-iterations` (default: 20) + +**Step 1 — Validate the workplan file exists** + +If the file does not exist: +> "❌ Workplan file not found: $WORKPLAN_FILE" + +Stop. + +**Step 2 — Read the workplan frontmatter** + +Read `$WORKPLAN_FILE`. Extract from the YAML frontmatter: +- `id` — workplan identifier +- `title` — workplan title +- `status` — current status + +**Step 3 — Guard: check if already done** + +Run: +```bash +"${CLAUDE_PLUGIN_ROOT}/scripts/check-done.sh" "$WORKPLAN_FILE" +``` + +Exit code 0 means the workplan is already done. If so: +> "✅ Workplan $ID ('$TITLE') is already done — all tasks complete. +> Nothing to do. No ralph loop started." + +Stop. + +**Step 4 — Show current task summary** + +Count the task blocks in the file (fenced blocks with language tag `task`). +Report: +> "📋 $ID — $TITLE +> Tasks: X done, Y in progress, Z todo (N total) +> Starting ralph loop (max $MAX_ITERATIONS iterations)..." + +**Step 5 — Start the loop** + +Run: +```bash +"${CLAUDE_PLUGIN_ROOT}/scripts/setup.sh" \ + "$WORKPLAN_FILE" "$ID" "$TITLE" "$MAX_ITERATIONS" +``` + +Then report: +> "🔄 Ralph loop active. The loop will feed the workplan prompt back on every +> iteration. It retires when all tasks are done and the workplan status is +> updated to 'done' in the file." diff --git a/plugin/scripts/check-done.sh b/plugin/scripts/check-done.sh new file mode 100755 index 0000000..7856ac5 --- /dev/null +++ b/plugin/scripts/check-done.sh @@ -0,0 +1,33 @@ +#!/bin/bash +# check-done.sh +# Exit 0 if the workplan is done, 1 if not. +# Used as the pre-start guard in the ralph-workplan skill. +# +# Usage: check-done.sh + +set -euo pipefail + +WORKPLAN_FILE="${1:?workplan_file required}" + +if [[ ! -f "$WORKPLAN_FILE" ]]; then + echo "❌ Workplan file not found: $WORKPLAN_FILE" >&2 + exit 2 +fi + +# Extract status from YAML frontmatter (first occurrence between --- markers) +STATUS=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' "$WORKPLAN_FILE" \ + | grep '^status:' \ + | head -1 \ + | sed 's/status:[[:space:]]*//' \ + | tr -d '"'"'" ) + +if [[ -z "$STATUS" ]]; then + echo "⚠️ No status field found in frontmatter of: $WORKPLAN_FILE" >&2 + exit 1 +fi + +if [[ "$STATUS" == "done" ]]; then + exit 0 +else + exit 1 +fi diff --git a/plugin/scripts/setup.sh b/plugin/scripts/setup.sh new file mode 100755 index 0000000..5009d43 --- /dev/null +++ b/plugin/scripts/setup.sh @@ -0,0 +1,102 @@ +#!/bin/bash +# setup.sh +# Writes the .claude/ralph-loop.local.md state file with a workplan-aware prompt. +# Called by the /ralph-workplan skill after the pre-start guard passes. +# +# Usage: setup.sh <max_iterations> + +set -euo pipefail + +WORKPLAN_FILE="${1:?workplan_file required}" +WORKPLAN_ID="${2:?workplan_id required}" +TITLE="${3:?title required}" +MAX_ITERATIONS="${4:-20}" +SESSION_ID="${CLAUDE_CODE_SESSION_ID:-}" + +STATE_FILE=".claude/ralph-loop.local.md" + +mkdir -p .claude + +# Extract workplan body — everything after the second --- +WORKPLAN_BODY=$(awk '/^---$/{i++; next} i>=2' "$WORKPLAN_FILE") + +cat > "$STATE_FILE" <<EOF +--- +active: true +iteration: 1 +session_id: ${SESSION_ID} +max_iterations: ${MAX_ITERATIONS} +completion_promise: "HEUREKA" +workplan_id: ${WORKPLAN_ID} +workplan_file: ${WORKPLAN_FILE} +started_at: "$(date -u +%Y-%m-%dT%H:%M:%SZ)" +--- + +## Workplan Status Check — Do This First, Every Iteration + +Read the workplan file at: \`${WORKPLAN_FILE}\` + +Count the task blocks (fenced code blocks with language tag \`task\`): +- How many tasks exist in total? +- How many have \`status: done\`? + +If **every task** has \`status: done\` AND the frontmatter \`status\` is \`done\`: + The workplan is complete. Output exactly: <promise>HEUREKA</promise> + Do nothing else. Stop here. + +Otherwise: continue with the implementation below. + +--- + +## Workplan: ${WORKPLAN_ID} — ${TITLE} +**File:** \`${WORKPLAN_FILE}\` + +${WORKPLAN_BODY} + +--- + +## How to Work + +- Stay strictly within the scope of the workplan above +- Work through tasks in priority order (high → medium → low) +- Use TDD where applicable: write a failing test, make it pass, then refactor +- Use whatever test runner, linter, and build tools this repository already uses +- Consult existing documentation (README, docs/, wiki/, specs/) for context +- Document significant architecture decisions as ADRs if the project uses them + +## Updating Task Status + +As you complete each task, edit the workplan file to update its status: + +\`\`\` +status: todo → status: in_progress (when you start it) +status: in_progress → status: done (when it is verified complete) +\`\`\` + +When **every task** is \`done\`, also update the workplan frontmatter: + +\`\`\` +status: active → status: done +\`\`\` + +## Success Criteria + +Before marking the workplan done and outputting \`<promise>HEUREKA</promise>\`, +verify all of the following are true: + +1. Every task block in \`${WORKPLAN_FILE}\` has \`status: done\` +2. The workplan frontmatter \`status\` is \`done\` +3. The full test suite passes with no failures +4. The codebase passes the project's standard code-quality checks + (linting, type checking, formatting — whatever applies to this project) +5. Documentation reflects the implemented behaviour + +Output \`<promise>HEUREKA</promise>\` only when all five are genuinely true. + +EOF + +echo "✅ Ralph state file written: $STATE_FILE" +echo " Workplan : $WORKPLAN_ID — $TITLE" +echo " File : $WORKPLAN_FILE" +echo " Max iter : $MAX_ITERATIONS" +echo " Promise : HEUREKA (output only when workplan is genuinely done)" diff --git a/workplan-spec.md b/workplan-spec.md new file mode 100644 index 0000000..7093573 --- /dev/null +++ b/workplan-spec.md @@ -0,0 +1,71 @@ +# Workplan File Specification + +A workplan is a Markdown file with YAML frontmatter that describes a unit of +work. It is the only input the `/ralph-workplan` skill needs. + +## File Format + +```markdown +--- +id: WP-0001 +title: "Build a thing" +status: active +--- + +Optional free-text description of the workplan scope and goals. + +## Task: Do the first thing + +```task +id: T-01 +status: todo +priority: high +``` + +## Task: Do the second thing + +```task +id: T-02 +status: todo +priority: medium +``` +``` + +## Frontmatter Fields + +| Field | Required | Values | Description | +|-------|----------|--------|-------------| +| `id` | yes | string | Unique workplan identifier | +| `title` | yes | string | Human-readable name | +| `status` | yes | `active` \| `done` \| `paused` | Workplan lifecycle state | + +## Task Blocks + +Each task is a fenced code block with language tag `task`, embedded anywhere in +the document body. Fields: + +| Field | Required | Values | Description | +|-------|----------|--------|-------------| +| `id` | yes | string | Unique task identifier within the workplan | +| `status` | yes | `todo` \| `in_progress` \| `done` | Task state | +| `priority` | no | `high` \| `medium` \| `low` | Execution priority | + +## Completion Rule + +A workplan is **done** when: +1. Every task block has `status: done` +2. The frontmatter `status` field is `done` + +The skill updates both as work progresses — tasks individually, then the +workplan when all tasks are complete. + +## Naming Convention + +The skill works with any filename. A common convention used in custodian-family +projects is: + +``` +workplans/<PREFIX>-WP-NNNN-<slug>.md +``` + +but this is not required.