Implement one unit of work end-to-end — from a tracker issue OR an ad-hoc description: spec review, TDD (RED→GREEN→REFACTOR), pre-PR self-review, open a PR, then drive the review-bot loop to clean. Runs start→finish in one shot by default; `start`/`finish` are optional phases for pause/resume. Sibling to /rig-epic (one unit vs many). Never auto-merges unless `--auto-merge` is passed (then CI is the merge gate). Triggers on: 'implement', 'implement this', 'work on', 'pick up', 'start task', 's...
Installs into .claude/skills of the current project.
Are you the author of Rig Task?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/agent-rig-rig-task)
---
name: rig-task
description: "Implement one unit of work end-to-end — from a tracker issue OR an ad-hoc description: spec review, TDD (RED→GREEN→REFACTOR), pre-PR self-review, open a PR, then drive the review-bot loop to clean. Runs start→finish in one shot by default; `start`/`finish` are optional phases for pause/resume. Sibling to /rig-epic (one unit vs many). Never auto-merges unless `--auto-merge` is passed (then CI is the merge gate). Triggers on: 'implement', 'implement this', 'work on', 'pick up', 'start task', 'start <ISSUE>', 'finish task'."
argument-hint: "[start|finish] [<issue-id> | \"<description>\"] [--local] [--base <ref>] [--auto-merge] — no subcommand runs start→finish in one shot. e.g. 'start ABC-18', 'start \"add dark mode\"', 'finish'."
---
# rig-task — implement one unit of work end-to-end
Take a single unit of work — a tracker issue **or** an ad-hoc "implement this"
with no issue filed — code it test-first, open a PR, drive it to a clean review,
and hand back. **Never auto-merges unless `--auto-merge` is passed** (then CI
gates the merge — see Step 5). This is the orchestrator over the kit's
building blocks: it delegates isolation to `/rig-worktree`, review to
`/rig-review`, and the fix loop to `/rig-review fix`, so each concern lives in
one place.
It's the single-unit sibling of `/rig-epic` (one unit vs many interleaved), and
mirrors its lifecycle: **`start`** gets you to an open PR, **`finish`** drives
the review to clean. By default a bare invocation runs both in one continuous
session — the phases exist for when you want to open the PR, let CI / the review
bot run, and `finish` later. `/rig-sprint` calls this once per item.
## Configuration
Reads `.rig/config.json` (defaults in parentheses):
- `tracker.provider` — `linear` | `github` | `none` (`none`). Selects how the
spec is loaded and the PR is linked. `none` → the item is an ad-hoc task
description; no ticket calls.
- `tracker.ticketPrefix` — e.g. `ABC-`; used to infer a ticket from the branch.
- `tracker.githubIntegration` — when `true`, GitHub drives PR/merge transitions
(In Review, Done); rig still sets the start-of-work "In Progress" (Step 1),
which GitHub can't observe before a PR exists. When `false`/absent, you may
move states freely.
- `tracker.labelMapFile` (`.claude/label-mapping.md`) — label source of truth.
- `tracker.board` — GitHub Projects v2 identity (owner, projectNumber,
statusOptions, closingKeyword). Used at PR-open (Step 4) to link the PR and
move the board via the `rig-tracker` adapter. Absent → no board steps.
- `vcs.baseRef` (`origin/main`), `vcs.defaultBranch` (`main`),
`vcs.branchConvention` (`{user}/{ticket}-{slug}`),
`vcs.protectedBranchMergeQueue` (`false`).
- `runtime.packageManager` / `runtime.installCommand` — passed to `/rig-worktree`.
- `test.command` (`npm test`), `test.integrationCommand`, `test.e2eCommand`.
- `review.patternsFile` (`.claude/REVIEWER.md`), `review.bot`
(`none`), `review.botRetrigger`, `review.maxRounds` (`5`).
- `style.guideFile` (`.claude/STYLE.md`) — the house style for the commit
message, the PR body, and the hand-back line.
- `agents.architect` / `agents.qa` / `agents.coder` / `agents.reviewer`
(default `rig-<role>`) — the registered agent names for each role.
**Unconfigured fallback:** with no profile, this runs with tracker `none` (ad-hoc
task), `npm test`, base `origin/main`, and a local-only review loop.
## Subcommands (phases)
`$ARGUMENTS` begins with an optional phase, then a target:
- **(no phase)** — run `start` then `finish` in one continuous session. The
common case: "implement this."
- **`start [<issue-id> | "<description>"]`** — Steps 1–5: load/decide the spec,
set up a worktree, TDD to green, pre-PR self-review, and **open the PR**. Then
stop. Use to kick a unit off and let CI / the review bot run while you move on.
- **`finish [<issue-id | PR#>]`** — Steps 6–7: resolve the open PR (from the arg,
the current branch, or the single open PR for this unit), drive the review-bot
loop to a terminal state, and hand back. Resumes a unit `start` (or a teammate)
opened.
**Target resolution:**
- An `<issue-id>` matching `tracker.ticketPrefix` (and tracker ≠ none) → load the
spec from the tracker.
- A quoted `"<description>"` (or tracker = none) → **ad-hoc**: the description is
the spec; no tracker record required. Optionally offer to file an issue at the
end.
- Omitted → infer from the current branch's `{tracker.ticketPrefix}<N>` token;
else ask.
`--local` — in the `finish` phase, force the local `/rig-review fix` loop even if
a cloud auto-fix workflow is enabled (see Step 6).
`--base <ref>` — branch the worktree from, and target the PR at, `<ref>` instead
of `vcs.baseRef`. A caller like `/rig-epic` passes the integration branch here so
the child stacks on it rather than the trunk.
`--auto-merge` — once the pre-PR self-review (Step 4.5) is clean, **squash-merge**
the PR: `gh pr merge <N> --squash --delete-branch --auto` so it lands **when CI /
required checks pass — CI is the merge gate, not a human**. **Always squash** —
into a stacked integration branch *or* the trunk (don't rebase-merge; many repos
disallow it). If there are **no** required checks (`--auto` can't arm / the PR is
already mergeable), merge directly (drop `--auto`). If `vcs.protectedBranchMergeQueue`
is true, pass `--auto` with **no** method flag (the queue decides). Without this
flag the skill never merges — it opens the PR and hands back. `/rig-epic` passes
`--auto-merge` per child so each lands on the integration branch on its own.
`--spec-cleared` — skip Step 2's blocking pause: run the spec review for its notes
but do **not** stop for a human to resolve blockers. Use when the caller already
front-loaded and cleared the spec (e.g. `/rig-epic run`'s front-loaded spec review
resolved every child's blockers up front), so this child doesn't re-pause on a
question already answered. Spec review still informs RED/GREEN; it just never gates.
Print the resolved unit + phase as the first output line, e.g.
`rig-task start: ABC-369 (from branch alice/abc-369-...)` or
`rig-task start: ad-hoc "add dark mode"`.
## Run state
This skill runs long enough to outlive its own transcript. A compaction between
Step 3 and Step 6 drops the acceptance criteria, the failing assertion, and
which review findings you already fixed. Keep them on disk instead, in a
validated run-state document. See [`docs/state.md`](../../docs/state.md).
Resolve the helper once: `<STATE>` = `bun <SCRIPTS>/rig-state.ts`, with
`<SCRIPTS>` resolved from the repo root — `.claude/scripts/` then
`.rig/scripts/`, first hit wins, as in [`/rig-worktree`](../rig-worktree/SKILL.md). If the script is absent, skip every state call below and run as
before — the state is an accelerator, not a dependency.
**Run id:** the ticket, lowercased (`abc-18`), or a kebab slug of the task in
ad-hoc mode. `start` and `finish` share it. That reuse is what lets `finish`
read what `start` recorded instead of reconstructing it.
Each step below ends with the patch it owes. Two rules govern what goes in:
- **Persist what a later step reads**, not what happened. Record the branch
name, the failing assertion, and the reason you rejected an approach. Don't
record a narration of Step 3.
- **A rejected patch means fix the patch**, not the state. The tool prints the
reason and leaves the state untouched; correct it and retry. A rejection on
`phase: pr-open` means a gate genuinely isn't met.
---
---
The steps below are grouped into the two phases. **Steps 1–5 are `start`;
Steps 6–7 are `finish`.** A bare `rig-task` runs all of them in order.
## Step 1 — Load the spec and set up a worktree
0. **Base guard — before any worktree or agent work.** If this item is an epic
**child** — its tracker parent is an epic, or an integration branch
`<parent-slug>-*` for its parent already exists on origin (`git ls-remote
--heads origin`) — **and no stacked `--base` was given**, stop now: report that
it must stack on `<integration-branch>` (or be driven by `/rig-epic`), and do
**not** set up a worktree or spend spec-review/TDD. This catches an epic child
mistakenly launched against the trunk before any agent runs. (When `--base` is
supplied, a child is expected — proceed.)
1. **Load the spec.**
- `tracker: linear` → fetch the issue via the Linear MCP `get_issue`; the
description + acceptance criteria are the spec. **Set the issue to "In
Progress"** now (Linear: `save_issue` with `state: "In Progress"`) — work
is starting. Do this **even when `tracker.githubIntegration` is true**: a
GitHub integration can't observe local work until a PR exists, so this is
the one transition it can't drive. It's idempotent (only move if not already
started). rig then **ensures In Review / Done idempotently** later (Steps
5/7) — deferring to a live integration if it already moved them, driving them
itself if nothing did (see the adaptive note in Step 5).
- `tracker: github` → fetch the issue via `gh issue view`.
- `tracker: none` → the argument/task description is the spec; if it's a
one-liner, ask the user to expand the acceptance criteria.
Restate the acceptance criteria to yourself before coding.
2. **Set up an isolated checkout via `/rig-worktree`** (don't inline `git worktree
add` — the skill owns fetch-before-branch, env symlinks, and per-worktree
install). Branch name:
- `tracker: linear` and the payload carries a suggested per-user branch name
(`gitBranchName`) → use it **verbatim** (it's what a tracker↔GitHub
integration matches on for auto-state).
- Otherwise build from `vcs.branchConvention`: `{user}` = git user, `{ticket}`
= the ID (or a kebab slug of the task in ad-hoc mode), `{slug}` = short
kebab of the title.
Base from `vcs.baseRef`. `cd` into the printed worktree path (`$WT`); use it
as `{worktree-path}` in the agent prompts below.
3. **Open the run state** with the spec from Step 1.1:
```bash
<STATE> init rig-task <run-id> --json '{"spec":{"id":"…","title":"…","source":"linear|github|adhoc","acceptanceCriteria":["…"]},"base":"<vcs.baseRef or --base>","nextAction":"spec review"}'
```
Then patch the checkout: `<STATE> patch <run-id> --step step-1 --json
'{"branch":"…","worktree":"$WT"}'`. If the run already exists, you're
resuming — `<STATE> show <run-id>` and continue from its phase.
**Name the session** so a background-job list reads as the work, not the
prompt (Claude-Code-only; no-ops elsewhere). Pass `--name` through to
`/rig-worktree` — `"FEAT: <title> (<ticket>)"` for new behavior/bug fixes
(conventional-commit `feat:`/`fix:`), `"CHORE: <title> (<ticket>)"` for
maintenance (`chore:`/`refactor:`/`test:`/`docs:`). Add `--skip-if-prefix
"EPIC:"` so an epic label set by `/rig-epic` wins when this runs as a child.
## Step 2 — Spec review
Launch the **architect** and **qa** agents in parallel (names via `agents.*`):
- **architect**: "Review this spec for implementability. Identify ambiguities,
missing acceptance criteria, files that need to change, and a suggested
implementation order. Spec:\n{title}\n\n{description}"
- **qa**: "Review this spec from a testing perspective. What test cases are
needed? Are the acceptance criteria testable? What edge cases matter?
Spec:\n{title}\n\n{description}"
**Patch:** `{"phase":"spec-review","decisions":["<what you clarified, one line each>"],"blockers":["<what a human must answer>"],"nextAction":"write failing tests"}`.
Blockers are what a resumed session needs to know it's parked on — clear them
(`"blockers":null`) once they're answered.
If either flags something that should be fixed before coding, clarify it — and,
in tracker mode, update the item's description with the clarification and tell
the user what changed. **With `--spec-cleared`** (a caller like `/rig-epic run`
already front-loaded and resolved the specs), skip this pause: keep the architect
+ qa notes to inform RED/GREEN, but do **not** stop for blockers — proceed
straight to Step 3.
## Step 3 — RED: tests first
Launch **qa** to write tests against the spec, *before* any implementation:
- **qa**: "Write tests for this item. Cover every acceptance criterion + the
edge cases the architect flagged. Don't stub or comment out — the tests must
compile and fail for the right reason (missing implementation), not a syntax
error. Worktree: {worktree-path}. Match the project's test framework and
colocation conventions.\n\nSpec:\n{title}\n\n{description}\n\nArchitect
notes:\n{architect_output}"
Run the suite from the worktree: `cd "$WT" && <test.command>`.
**Patch:** `{"phase":"red","tests":{"command":"<test.command>","status":"red","fail":<n>,"failures":["<the assertion, not the stack trace>"]},"nextAction":"minimum implementation"}`.
The failing assertions are the spec Step 4 codes against, so they're worth the
bytes; the stack traces aren't.
**Verify red.** The new tests must fail with a message that reflects the missing
behavior (e.g. "expected X, got undefined" / "not-yet-implemented symbol"). A
new test that passes immediately was pinning existing behavior — send it back to
qa. A syntax error is a qa bug; fix it before moving on.
## Step 4 — GREEN: minimum implementation
Launch **coder** with the failing tests as the spec:
- **coder**: "Implement {item} to make the failing tests pass. Worktree:
{worktree-path}. Explore the affected files first and prefer *extending or
reusing* existing code over a parallel implementation. Make the minimum change
— no features not required by a test.\n\nSpec:\n{title}\n{description}\n\n
Architect notes:\n{architect_output}\n\nFailing tests:\n{test_output}"
**Patch once green:** `{"phase":"green","tests":{"status":"green","pass":<n>,"fail":0,"failures":null},"nextAction":"pre-PR self-review"}`.
Re-run the suite. If still red, send coder a fix iteration with the current
failures. **Max 3 iterations**; if still red after 3, stop and surface to the
user — the spec is likely wrong or there's an unstated constraint. If
implementing exposes a missing edge case, add that test first (loop back to RED
for it) rather than piling untested behavior into GREEN.
## Step 4.25 — REFACTOR (optional, only while green)
If the GREEN code has obvious duplication, awkward naming, or a helper that
wants extracting, spawn **coder** once more — "no new behavior, tests stay
green" — and re-run the suite. Skip if the code is already clean.
## Step 4.5 — Pre-PR self-review (gate before push)
The point: pre-empt the PR review bot — every finding caught here is one you
don't pay a round-trip on. Delegate so the gate lives in one place:
1. **Find.** Run `/rig-review {vcs.baseRef}` for this item (in
`{worktree-path}`). It walks `review.patternsFile` against
`git diff {vcs.baseRef}...HEAD` and returns a triaged P0–P3 list.
2. **Classify and fix:**
- **No P0/P1** → proceed to Step 5 (P2/P3 are fine to ship; report them).
- **Any P0/P1** → run `/rig-review fix --source local --base {vcs.baseRef}` in
`{worktree-path}`. It spawns **coder** per finding, re-runs `/rig-review`,
and loops to clean (max `review.maxRounds`). If still P0/P1 after the last
round it returns `unresolved` — stop, surface to the user, don't push.
3. Report the final state before pushing, e.g. `tests: green, review: 0 P0/P1 +
2 P2 (deferred)`.
4. **Patch:** `{"phase":"self-review","review":{"source":"local","round":<n>,"p0":0,"p1":0,"p2":<n>,"open":["<deferred P2, one line each>"]},"nextAction":"open the PR"}`.
The `pr-open` phase is guarded on `tests.status: green` and zero P0/P1. A
rejection here means the gate isn't met — go fix the code, not the patch.
## Step 5 — Push and open the PR
1. Commit all changes with a message referencing the item.
2. Push the branch.
3. Open a PR (`gh pr create`) targeting `vcs.defaultBranch` (or the base from
Step 1 if a stacked base was used):
- Title carries the item ID where a tracker is used (e.g. `feat(ABC-18):
…`), so it's visible in the PR list, not just the body.
- Body includes: a summary; the tracker link (`Fixes ABC-18` for Linear /
`Closes #<n>` for GitHub — the keyword that auto-links and, with
`tracker.githubIntegration`, auto-transitions state); a test plan; and an
**`## Architecture`** section stating any new abstraction/package/
dependency/migration or core-domain touch and **why existing code wasn't
reused** (write "No architectural change." otherwise).
- Write both to `style.guideFile` (default `.claude/STYLE.md`): commit subject
in the imperative under ~72 characters, PR summary that answers *what
changed and why* in its first sentence, a test plan another person could
run. Don't narrate the diff — the diff is right there.
- **Proof the draft body before creating the PR** — `/rig-proof find` on it
(pipe the draft in on stdin) and apply what comes back. Every reviewer reads
this body; rewriting it after it's posted notifies all of them again.
4. **Link the PR + move to In Review — adaptively** (works with *or* without a
live tracker↔GitHub integration; treat `tracker.githubIntegration` as a hint,
not a gate — it may claim `true` while nothing is actually connected):
- **`tracker: linear`:**
- **Link:** `get_issue`; if no attachment already references this PR URL (a
live integration may have added one), `create_attachment` with the PR URL.
- **In Review:** `get_issue`; if the item is **not** already In Review or
beyond, move it there (`save_issue`). If the integration already advanced
it, leave it — never clobber a further-along state.
- **`tracker: github`: go through the tracker adapter** (resolver: `<TRACKER>`
= `.rig/rig-tracker` if executable else `<RIG_DIR>/scripts/rig-tracker.sh`;
see [`docs/tracker-adapter.md`](../../docs/tracker-adapter.md)):
- **Link:** `<TRACKER> link-pr <issue#> <pr#>` — idempotently ensures the PR
closes/links the issue (Step 3 may already have written the `Closes #<n>`;
`link-pr` is a no-op then). This is what moves the board to **Done** on
merge via the Project's built-in automation.
- **In Review:** if `tracker.board` is configured,
`<TRACKER> set-status <issue#> "<tracker.board.statusOptions.inReview>"` —
no GitHub *event* moves a card to In Review, so the adapter does it. Skip
if no board is configured.
Ensure the item's labels match `tracker.labelMapFile` if that file exists.
5. Capture the PR number for Step 6, and **patch:**
`{"phase":"pr-open","pr":{"number":<n>,"url":"…","state":"open"},"nextAction":"drive the review loop"}`.
This is what a later `finish` resolves the PR from — no re-derivation from
the branch.
**Merge behavior.** Without `--auto-merge`, **do NOT `gh pr merge`** — open the PR
and hand back (the human, `/rig-sprint`, or `/rig-epic` decides). **With
`--auto-merge`**, the self-review is clean, so enable `gh pr merge <N> --squash --auto`
now (always squash — see the `--auto-merge` flag doc above); **CI/required checks are
the merge gate** — the PR lands on its own when they pass. Either way, don't hand-add PR
labels if the project ships a PR-labeler workflow — it applies them.
---
## ── `finish` phase (Steps 6–7) ──
When resuming via `rig-task finish` (rather than a one-shot run), first
**re-establish context from the run state** — `<STATE> show <run-id>` (or
`<STATE> list --skill rig-task` if you're not sure of the id). It carries the
spec, the branch, the worktree, the PR, and what the self-review deferred, so a
compacted or brand-new session picks up where `start` left off. `cd` into
`worktree` and continue.
**If there's no state** — the run predates it, or `start` ran without the
script — fall back to the old path. Resolve the open PR and its worktree from
the target arg, the current branch, or the single open PR for this unit, then
`cd` into that worktree.
## Step 6 — Review-bot loop: watch (default) or drive (`--local`)
- **`review.bot: none`** → nothing to do; the Step 4.5 local gate was the whole
review. Go to Step 7.
- **A cloud auto-fix workflow is enabled** (the project installed
`ci/workflows/auto-review-fix.yml` and its enable marker) and `--local` was
**not** passed → **watch**: do not fix or push. Poll the PR (~60s intervals,
up to ~30 min) until the bot's review reaches a terminal state and report it.
Running a local driver alongside a cloud one collides (both push, both
re-trigger), so only watch here.
- **Otherwise (drive)** → delegate to **`/rig-review fix <PR>`**. It owns the bot
protocol for `review.bot` (poll + classify the bot's review, fix via
**coder**, commit, push, re-trigger via `review.botRetrigger`), up to
`review.maxRounds`. Take its returned outcome as this step's result:
- `clean` — reviewed with no actionable issues (merge gates take over).
- `actionable` — still has feedback after the last round; left for a human.
- `timeout` — bot didn't respond in time; left for a human.
**Patch:** `{"phase":"review-loop","review":{"source":"bot","round":<n>,"outcome":"clean|actionable|timeout"},"nextAction":"hand back"}`.
Only `clean` is merge-green; everything else stops for a human.
## Step 7 — Hand back
Print the final outcome line and the PR URL. The outcome line carries real
state, not reassurance — `tests: 412 pass, 0 fail · review: 0 P0/P1, 2 P2
deferred · PR #418 open`, never "everything looks good".
**Tracker state — adaptive** (don't
trust `tracker.githubIntegration`; check reality): if the PR has already **merged**
by hand-back (e.g. `--auto-merge` landed it), ensure the item is **Done**
(`get_issue`; move only if not already Done — defer if a live integration closed
it). If the PR is still open, leave it **In Review** — the merge event (a later
`finish` run, `/rig-epic`'s merge gate, or a live integration) moves it to Done.
Never clobber a further-along state.
**Close the run.** Patch `{"phase":"done","nextAction":null}` once the PR is
open and the review reached a terminal state (`done` is guarded on a
`pr.number`). Keep the state file — `/rig-doctor` reports stale runs, and the
journal (`<STATE> journal <run-id>`) is how a human reconstructs a run that went
wrong. Delete it with `<STATE> rm <run-id>` when the PR merges.
If invoked by `/rig-sprint`, **return the outcome string** so the caller can decide
whether to merge, and skip any local-QA offer (the caller makes it once).
## Error handling
- Item can't be loaded/fetched → tell the user immediately.
- Branch already exists → ask whether to continue on it or start fresh.
- Test command times out or crashes (not just failures) → report and stop
before opening a PR.
- `gh pr create` fails because a PR already exists → capture that PR's number
and skip to Step 6.
## Env files in worktrees
A worktree is a fresh checkout — gitignored env files aren't carried over, and a
missing secret often reads as a flaky/timeout test failure. Step 1's `/rig-worktree`
setup symlinks gitignored env files from the main repo into the worktree before
any command runs. To re-link by hand, re-run the worktree setup with `--reuse`.