diff --git a/README.md b/README.md index d83aa28..6b13fc1 100644 --- a/README.md +++ b/README.md @@ -50,10 +50,21 @@ Modes: ## Workflow ``` -/grill-me → /to-prd → /to-issues → /to-project → agentic-toolkit run --project N --mode + ┌─→ agentic-toolkit run --project N --mode (Docker + ANTHROPIC_API_KEY) +/grill-me → /to-prd → /to-issues → /to-project ─┤ + └─→ /ralph-loop project=N mode= (Claude Code subscription, no Docker) ``` -`to-project` lives in `skills/engineering/to-project/SKILL.md` and is installed by `setup.sh`. +`to-project` and `ralph-loop` skills live under `skills/engineering/` and are installed by `setup.sh`. + +### Pick a runner + +| Path | Auth | Sandbox | Cost | Parallelism | +|------|------|---------|------|-------------| +| `agentic-toolkit run` (sandcastle) | `ANTHROPIC_API_KEY` | Docker container | API metered | Per-container | +| `/ralph-loop` (skill) | Claude Code subscription | None — host repo | Subscription flat | Serial (one ticket at a time per Claude session) | + +`/ralph-loop` is the zero-infra path: no Docker, no API key. It dispatches each ticket to a fresh Claude Code subagent (clean context per tick) using the same project schema and phase logic as the CLI runner. Trade-off: no sandbox isolation — run on a clean checkout. See `skills/engineering/ralph-loop/SKILL.md`. ## Architecture (modules in `src/modules/`) diff --git a/skills/engineering/ralph-loop/SKILL.md b/skills/engineering/ralph-loop/SKILL.md new file mode 100644 index 0000000..9769661 --- /dev/null +++ b/skills/engineering/ralph-loop/SKILL.md @@ -0,0 +1,210 @@ +--- +name: ralph-loop +description: Run a GitHub Project (v2) of issues against the current repo using Claude Code subagents — one fresh subagent per ticket. Subscription-only alternative to `agentic-toolkit run` (which uses sandcastle + Docker + Anthropic API). Use after `/to-project` once the project is ready. +--- + +# Ralph Loop + +Implement a GitHub Project's issues by dispatching each `Ready` issue to a fresh Claude Code subagent (`subagent_type=general-purpose`). Each subagent gets a clean context window — the "ralph loop" property that keeps long-running multi-ticket work coherent. + +This skill is the subscription-based counterpart to `agentic-toolkit run`. Pick the path that fits your setup: + +| Path | Auth | Sandbox | Cost | Parallelism | +|------|------|---------|------|-------------| +| `agentic-toolkit run` (sandcastle) | `ANTHROPIC_API_KEY` | Docker container | API metered | Per-container | +| `/ralph-loop` (this skill) | Claude Code subscription | None — host repo | Subscription flat | Serial (one per Claude session) | + +**Trade-offs you accept by using this skill:** + +- No sandbox isolation. The subagent has full tool access on your host repo. **Run on a clean checkout** with no uncommitted personal work. +- Serial only — one ticket at a time per Claude Code session. Cannot parallelise across machines. +- Visible: every tick happens in the foreground Claude Code UI. You can interrupt anytime. + +## Inputs + +Confirm the following before starting. If anything is missing, ask the user. + +- **Project number** (e.g. `2`). +- **Mode** — `per-ticket` or `single-pr`. Same semantics as the runner CLI. +- **Owner** — defaults to current `gh` repo owner (`gh repo view --json owner --jq .owner.login`). +- **Repo** — defaults to current `gh` repo (`gh repo view --json name --jq .name`). +- **Target repo path** — the local checkout the subagent will work in. Defaults to the current working directory. +- **Base branch** — defaults to current HEAD of the target repo (`git -C rev-parse --abbrev-ref HEAD`). + +Pre-flight: +- `gh auth status` shows scopes `project`, `read:project`, `repo`. If `project` missing, run `gh auth refresh -s project,read:project --hostname github.com`. +- The target repo working tree is clean (`git -C status --porcelain` is empty). If not, halt and tell the user. + +## Algorithm + +The loop runs in the current Claude Code conversation. The orchestrator (you) coordinates state via `gh` and dispatches one subagent per ticket via the Agent tool. + +### 1. Read project state + +Run a GraphQL query equivalent to the runner's `PROJECT_QUERY`: + +```graphql +query($owner: String!, $repo: String!, $number: Int!) { + repository(owner: $owner, name: $repo) { + projectV2(number: $number) { + id + field(name: "Status") { ... on ProjectV2SingleSelectField { id options { id name } } } + items(first: 100) { + nodes { + id + fieldValues(first: 20) { + nodes { ... on ProjectV2ItemFieldSingleSelectValue { name field { ... on ProjectV2SingleSelectField { name } } } } + } + content { + ... on Issue { id number title body labels(first: 20) { nodes { name } } assignees(first: 5) { nodes { login } } } + } + } + } + } + } +} +``` + +Parse each item to: `{ number, nodeId, itemId, title, body, labels, status, blockedBy, kind }` where: +- `kind = "HITL"` if labels include `hitl` or `ready-for-human`, else `"AFK"`. +- `blockedBy` = numbers extracted from the `## Blocked by` section of the body. + +If the Status field is missing any of the six required options (Backlog, Ready, In progress, In review, Needs human, Done), halt and tell the user to run `/to-project`. + +### 2. Autopromote + +For each issue with `kind === "AFK"` AND `status === "Backlog"` AND every `blockedBy` is either `Done` in this project OR not present in this project: set its Status to `Ready`. + +### 3. Compute current phase + pick next + +Topologically partition issues by `Blocked by` (issues with all blockers in earlier phases form phase N). The current phase = lowest-indexed phase with any non-Done issue. + +Within the current phase, `pickNextReady` = the lowest-numbered issue with `status === "Ready"` AND `kind === "AFK"`. + +If no such issue: +- `single-pr`: jump to step 9 (finalise). +- `per-ticket`: exit with "Phase N complete. Merge open PRs, then re-invoke." + +### 4. Claim + +- Set the issue's Status to `In progress`. +- Assign to the current viewer: `gh api user --jq .login` -> `gh issue edit --add-assignee ` (best-effort; ignore failure). + +### 5. Prepare branch + +Project slug = `-p` (matches CLI's `projectSlugFrom`). + +Branch name: +- `per-ticket`: `claude//-` (slug = lowercased, non-alphanumeric -> `-`, trimmed, max 50 chars). +- `single-pr`: `claude/` (reused across tickets). + +In the target repo: +```sh +git fetch origin +git checkout +git pull +if branch exists: git checkout +else: git checkout -b +``` + +### 6. Dispatch subagent + +Use the Agent tool. `subagent_type=general-purpose`. The prompt MUST be fully self-contained — the subagent has no memory of this conversation. + +Prompt template: + +```` +You are implementing GitHub issue # in /. + +# Issue title + + +# Issue body +<BODY> + +# Working environment +- Target repo path: <TARGET-REPO-PATH> +- You are on branch: <BRANCH-NAME> +- Branch base: <BASE-BRANCH> +- Do NOT change branches. Do NOT push. Do NOT open PRs. The orchestrator handles those. + +# Your task +1. Implement the issue's acceptance criteria fully. +2. If the repo has a test suite, run it and resolve failures before reporting complete. +3. Make commits as you go using the repo's commit style. Don't squash; the orchestrator may amend. +4. If you encounter ambiguity that blocks progress (e.g. unclear acceptance criteria, missing context, conflicting requirements), commit any progress made and report blocked rather than guessing. + +# Reporting back +Your FINAL message must be exactly one of: + +- `TICKET_COMPLETE: <one-line summary of what was implemented>` +- `TICKET_BLOCKED: <one-line reason and what's needed to unblock>` + +Do not include any text after that line. The orchestrator parses it. +```` + +### 7. Validate result + +After the subagent returns: + +- Read the last line of the subagent's final message. +- Count commits: `git -C <target> rev-list --count <base-branch>..HEAD`. +- Success = subagent reported `TICKET_COMPLETE` AND commit count > 0. +- Otherwise: failure (treat `TICKET_BLOCKED`, missing tag, or zero commits all as failure). + +### 8. After-success actions + +`per-ticket`: +- `git -C <target> push -u origin <branch>`. +- `gh pr create --base <base> --head <branch> --title "<title> (#<N>)" --body "Closes #<N>\n\nImplemented by /ralph-loop."`. +- Set Status = `In review`. +- Loop back to step 1 (state may have changed; re-read). + +`single-pr`: +- Leave branch local — no push, no PR yet. +- Loop back to step 1. + +### 9. Finalise (single-pr only) + +When `pickNextReady` returns nothing: +- `commits = git -C <target> rev-list --count <base>..<branch>`. +- If `commits === 0`: halt with "No commits to PR. Nothing was implemented." +- Else: `git push -u origin <branch>`; open ONE PR `Implement project #<projectNumber>` with body listing `Closes #<number>` for every non-Done issue. Set those issues' Status to `In review`. +- Print PR URL. + +## Failure handling (v1) + +Halt on first failure. No retry. Specifically, when step 7 detects failure: + +1. Set the failing issue's Status to `Needs human`. +2. Post a comment on the issue: + ``` + ### Automated run failed (/ralph-loop) + + <subagent's TICKET_BLOCKED reason or "Subagent returned without completing"> + + <details><summary>Last subagent message</summary> + + <last 6000 chars of subagent's final message> + + </details> + ``` +3. Stop the loop. Print: `Halted on issue #<N>: <reason>`. Tell the user to fix the underlying issue and re-invoke. + +Future versions can add retry logic mirroring `failure-handler.ts` in the toolkit's `src/modules/`. + +## Idempotency + +Re-invoking the skill is safe and resumes from current state: +- Issues already `Done` are excluded from phase computation. +- Issues `In review` are skipped (their PR is awaiting human merge). +- The loop picks up at the next `Ready` issue. + +In `single-pr` mode, the shared branch is reused across invocations — do not delete it between runs unless you're abandoning the project. + +## Notes + +- The skill needs `gh` CLI authed with `project + read:project + repo` scopes. The runner's CLI shares the same requirement; if you've already used `agentic-toolkit run` on the same machine, you're set. +- This skill must run with `Bash` and `Agent` tools enabled. The subagent uses standard `general-purpose` permissions (file edit, bash, etc.). +- For privacy/security, the subagent prompt includes the full issue body — do not put secrets in issue descriptions. +- The orchestrator (you, the calling Claude session) keeps full conversation context across the entire loop. Only the subagents reset. If the orchestrator's context fills up, the user can `/clear` and re-invoke; idempotency handles resumption.