rig-epic — integration-branch workflow
For multi-item arcs where landing each item directly on the trunk would
temporarily break the runtime contract, stack child PRs on a shared
integration branch and squash that branch into the trunk as one PR at the
end. Each child PR is reviewed as the step it actually is; the trunk-bound
delivery is one squashed rebase.
Configuration
Reads .rig/config.json (defaults in parentheses):
tracker.provider — linear | github | none (none). How parent/child
items are stored. none → items live only in the epic state file (below);
no tracker calls.
tracker.team / tracker.project / tracker.ticketPrefix / tracker.githubIntegration.
tracker.shapeLabels.epic (epic) — GitHub-only label put on the parent at
creation so a Project board / dispatcher can distinguish the epic from child
feature issues. Omit shapeLabels to skip.
tracker.board — GitHub Projects v2 identity. When set, the parent's board
transitions and each child's Done move go through the rig-tracker adapter
(gh issue edit can't touch a ProjectV2 Status field). Resolver used below:
<TRACKER> = .rig/rig-tracker if executable else <RIG_DIR>/scripts/rig-tracker.sh
(see docs/tracker-adapter.md). Absent → skip
the board moves; the linear/none paths are unchanged.
vcs.baseRef (origin/main), vcs.defaultBranch (main),
vcs.protectedBranchMergeQueue (false).
sourceScope[0] — where to explore during plan.
agents.architect / agents.reviewer (default rig-<role>) — for the
fresh-context review panel.
style.guideFile (.claude/STYLE.md) — how parent/child ticket bodies, the
finish PR body, and every status report are written.
Delegates to /rig-task (per child), /rig-worktree (checkouts), /rig-review
/rig-review fix (the combined-diff review), /rig-tidy (optional).
Epic state file (replaces any external memory)
Each active epic is tracked in a repo-local JSON file at
.rig/epics/<integration-branch>.json:
{
"parent": "ABC-42 or a slug in tracker=none",
"parentTitle": "…",
"integrationBranch": "abc-42-<slug>",
"whyEpic": "which children interleave and why",
"children": [
{ "id": "ABC-43", "title": "…", "blockedBy": [], "branch": null, "status": "todo|merged" }
]
}
Where the truth lives. On tracker: github, the source of truth is the
parent issue and its native sub-issues on the tracker — not this file. The
JSON is a local cache rebuilt from the tracker (gh api repos/<repo>/issues/<parent>/sub_issues), and it is gitignored — never
committed (.rig/epics/ is in .gitignore). On linear the parent/children
live in Linear; on none the file is the only record. Either way it's throwaway
coordination state, rebuildable from the tracker at any time — the board is the
shared brain, not the repo.
next/run/review/finish/prune read the reconstructed set. Parent
inference (when <PARENT> is omitted): on github, the open issue labelled
shapeLabels.epic that has sub-issues; else if exactly one .rig/epics/*.json
exists, use it; if zero or many, ask.
Intent banner: every invocation MUST print one line first — mode, what
branch PRs target, what it won't do:
rig-epic: <subcommand> — <action>. PRs target <branch>. Will not <thing>.
status (default, report-only)
Read the epic state file(s). Report: integration branch + commits ahead of
vcs.baseRef; open child PRs (gh pr list --base <integration-branch>) and
their state; each child's tracker status (if a tracker); leftover merged/deleted
worktrees (suggest /rig-epic prune); and a one-line recommendation (open child PR X, ready for /rig-epic next, or ready for /rig-epic finish). Modify
nothing; spawn nothing.
plan <FEATURE>
Decompose a feature into a parent + 3–8 children, write the state file, then
chain into start.
Use this only when the work is genuinely epic-shaped (children interleave —
at least one child's runtime contract depends on another being partially
complete). If the items are independent and each can land on the trunk alone,
stop and tell the user to run /rig-sprint plan instead — same decomposition,
no integration branch.
Procedure
- Read context. Product/spec docs if the project has them (ask if unsure);
explore the codebase (
sourceScope[0]) to see what exists. If a tracker is
configured, search it for near-duplicate items first.
- Sanity-check it's an epic (per the interleave test above). If not → send
to
/rig-sprint plan.
- Create the parent.
tracker: linear/github → create the parent item (team/project from
config); description covers Overview, why-it's-an-epic, and the planned
children.
- GitHub only — mark the shape. After creating the parent, apply the epic
shape label so a Project board / dispatcher can tell the parent apart from
the child
feature issues: gh issue edit <parent#> --add-label "<L>"
where <L> = tracker.shapeLabels.epic (default epic). Ensure the label
exists first (gh label create "<L>" --force). Skip entirely if
tracker.shapeLabels is absent. (Linear needs no label — native parent
grouping already distinguishes the epic.)
tracker: none → the parent is a slug + title recorded in the state file only.
- Create each child with its dependency edges.
- Small enough for one agent session (1–3 files); concrete, testable
acceptance criteria; foundational work first.
- Record
blockedBy for every real dependency — this is what next
reads to pick the next unblocked child. Without it the pick falls back to
declaration order and gets interleaved epics wrong.
tracker: github: create each child as its own issue, then link it as
a native sub-issue of the parent —
gh api repos/<repo>/issues/<parent#>/sub_issues -F sub_issue_id=$(gh api repos/<repo>/issues/<child#> -q .id) —
and write "Blocked by #" in the child body (GitHub has no native
blockedBy). The sub-issues are the epic on the board; the .rig/epics
cache is derived from them, never the other way around.
tracker: linear: set the native parent + blocked-by relations.
none: the state file is the only record.
- Show a summary table (ID · Title · Depends On).
- Chain into
start <PARENT> — plan is plan-and-start; don't stop to ask.
- Stop after
start. Report the integration branch, the children, and the
next step (/rig-epic next for one, /rig-epic run for the loop). Never
auto-start run — execution is an explicit opt-in.
start <PARENT>
Pre-flight: git fetch origin; confirm the parent and at least one child exist
(in the tracker, or as arguments for none).
Rebuild the cache from the tracker (github). If <PARENT> is an existing
GitHub epic (e.g. /rig-epic 42 or /rig-epic run), reconstruct the children
from its sub-issues rather than trusting any local file:
gh api repos/<repo>/issues/<parent#>/sub_issues → each becomes a child (id =
issue number, blockedBy parsed from "Blocked by #n" in the body,
status from open/closed + board column). Write that to .rig/epics/<branch>.json
(gitignored). This is what makes an epic you were handed on the board runnable —
no committed state required.
- Integration branch name:
<parent-slug>-<title-slug> (kebab, e.g.
abc-42-agent-as-definition).
- Cut the integration branch from
vcs.baseRef without a local checkout:git fetch origin
git push origin <vcs.baseRef>:refs/heads/<integration-branch>
Non-destructive. If the branch already exists, leave it (never overwrite —
could destroy in-flight work).
- Write
.rig/epics/<integration-branch>.json (schema above) with the
parent, why-epic, and every child + blockedBy. This is what makes each
/rig-task <child> target the integration branch instead of the trunk.
- If a tracker is configured, add an "Integration branch: target
<integration-branch>, not the trunk" note to each child so the next agent
doesn't re-read this skill, and ensure the parent is In Progress
(adaptive: move it only if not already started; defer if a live integration
did). On a tracker: github board, do the parent move through the adapter:
<TRACKER> set-status <parent#> "<tracker.board.statusOptions.inProgress>".
Children get their own In Progress from their /rig-task Step 1.
- Name the session
"EPIC: <parent title> (<parent>)" via
scripts/set-session-name.sh (Claude-Code-only; no-ops elsewhere) so the
background-job list reads as the epic. Children set their own FEAT:/CHORE:
names with --skip-if-prefix "EPIC:", so this epic label stays on top.
- Report the branch, the children, and the next step.
next (single child)
- Resolve the active integration branch (one expected; ask if many). If the
session isn't already
EPIC:-named (i.e. next invoked directly, not via
start), name it "EPIC: <parent title> (<parent>)" (Claude-Code-only).
- Pick the next unblocked child: the first not-done child whose
blockedBy
are all merged into the integration branch. State the pick + reasoning in the
banner.
- Ensure the previous child has landed. With
--auto-merge (Step 4) each
child's PR merges into the integration branch on its own once CI passes, so the
integration tip advances automatically — just git fetch origin and confirm.
(Fallback for a child run without --auto-merge: fast-forward the branch over
its tip — git push origin origin/<previous-child-branch>:refs/heads/<integration-branch>,
which auto-closes that PR as MERGED. Safe to re-run.)
- Run
/rig-task <CHILD> --base <integration-branch> --auto-merge (add
--spec-cleared when run's front-loaded spec review already cleared the
specs) — --base makes the child's worktree branch from, and its PR target, the
integration branch; --auto-merge makes the child enable gh pr merge --squash --auto on
its own PR once its review is clean, so CI lands it into the integration
branch (always squash — don't rebase-merge; many repos disallow it. If the
integration branch has no required checks, the child merges directly). Run
one-shot (start→finish); it returns a single outcome string for the merge gate.
- Merge gate — only
clean is merge-green:
clean → the child enabled auto-merge, so its PR lands when CI passes.
Wait for it: poll gh pr view <N> --json state (~60s, up to ~30min)
until MERGED, then git fetch origin and confirm the tip advanced. Record
the child merged + its branch in the state file, and ensure the child
ticket is Done (adaptive — move only if not already Done; defer if a live
integration closed it). On a tracker: github board, move it via the
adapter: <TRACKER> set-status <child#> "<tracker.board.statusOptions.done>".
(The child'"'"'s PR merged into the integration branch, not the default
branch, so its Closes #<n> has not fired — the adapter move is what
marks it Done now; the issue itself closes when the epic squashes to trunk.)
If CI fails / it never merges → treat as not-clean.
- anything else (
actionable/timeout, or a tracker-parked state) → the child
did not enable auto-merge; stop. Surface the outcome, the PR URL, and any
reviewer summary. Wait for the user; don't retry a parked review.
next does exactly one child. Use run for the loop.
run [--advisor]
Front-loaded spec review — once, before the loop. Review all children's
specs together, up front, instead of discovering blockers one child at a time
mid-run. A child that pauses on a spec question stalls the whole epic (and under
parallel or durable execution, a paused child can fail the batch outright), so
resolve the specs before any child starts coding:
- Fan out
agents.architect + agents.qa over every child's spec, read
against the integration branch; each returns its blockers.
- Consolidate into one blocker list and resolve it once — clarify in the
tracker, or decide with the user. This is the epic's single spec decision point.
- Then run each child with its own spec gate pre-cleared:
/rig-task <CHILD> --base <integration-branch> --auto-merge --spec-cleared, so
no child re-pauses on a question already answered.
This is the pre-coding spec pass; the combined-diff review (below) is the
post-merge code pass — different gates.
--advisor (unattended). Decide that front-loaded gate with an advisor
pass instead of a human: a delegated agents.architect review reads the
architect + QA specs and either proceeds with synthesized per-child direction
(handed verbatim to each child's coder), or halts with a blocked report naming
what a human must decide. No human waits, so the epic runs hands-off to child PRs;
a genuine blocker still stops it rather than fanning out on a bad spec. (The paired
Smithers durable workflow implements this with a cheap model — the "Fable
advisor" — so kicked-off epics never park at the gate.)
Then loop next until no unblocked child remains:
while an unblocked child exists:
run /rig-epic next # children run --spec-cleared (spec was front-loaded)
if it stopped without merging (outcome ≠ clean) → stop, hand back
else → re-evaluate unblocked children
When the queue empties, report "epic ready for /rig-epic finish."
review [<PARENT>]
Run after the last child merged into the integration branch, before finish.
Per-PR review (/rig-review inside each /rig-task) sees each child in
isolation; this sees the combined shape and the simplification only visible
once everything is in. Fresh-context sub-agents (input = the squashed diff +
child PR titles) can't inherit the implementer's remembered rationalizations.
Run before prune — the review sub-agents need a working checkout; reuse a
child worktree if one still exists (avoids a fresh install + env re-symlink).
- Resolve the integration branch;
git fetch origin.
- Ensure an integration-branch worktree (
$WT). Reuse a merged-child
worktree fast-forwarded to the integration tip, else create one with
/rig-worktree (--base <integration-branch> --reuse).
- Fan out three sub-agents in parallel (one message, three
Agent calls),
each cd-ing into $WT with the integration tip checked out:
- Lens 1 — Simplification (
agents.architect): diff
git diff <vcs.baseRef>...HEAD; child PR list via gh pr list --base <BR> --state merged. Mandate: find abstractions to collapse, helpers one
PR added that another PR's final shape made redundant, config knobs nobody
sets, code paths the combined diff made dead, one-caller types. Concrete
deletions/merges, file:line, highest-impact first. Skip correctness.
- Lens 2 — Cross-PR correctness (
agents.reviewer): walk the project's
review-pattern catalog (review.patternsFile) against the combined diff.
Per-PR review already ran; catch interactions only visible at the merged
shape (PR-A's helper vs PR-D's stale caller; PR-B removed a knob PR-F still
reads). P0/P1/P2 with file:line + category.
- Lens 3 — Dead code & stale refs (
agents.reviewer): for every symbol
the diff added, is it called elsewhere? For every symbol removed, grep
the tree (workflows, manifests, IaC, scripts, docs) for residual refs.
Report with file:line.
- Consolidate — dedupe, produce one P0/P1/P2 list grouped by lens, print
counts.
- Triage:
- 0 items →
clean — ready for /rig-epic finish, stop.
- findings → ask via
AskUserQuestion: apply now / skip and finish anyway (downgrade to follow-ups) / pause. On apply now, run
/rig-review fix --source local in $WT (spawns agents.coder,
re-reviews to clean), commit + git push origin <integration-branch>,
re-run review once to confirm convergence.
- Outcome string (for
finish): clean — … / applied — … / paused — ….
finish [<PARENT>] [--merge]
Squashes the whole integration branch into a single PR to the trunk. Default:
open the PR and stop — this is the one PR to eyeball.
review is a hard gate. finish always runs review first; the squash PR
opens only on clean or applied. On paused, stop and wait.
- Run
review (gate) inline; branch on its outcome (clean/applied →
proceed; paused → stop and print pending findings).
- Refresh & rebase if needed.
git fetch origin; list
git log <vcs.baseRef>..origin/<integration-branch> --oneline. If the trunk
moved, rebase the integration branch onto it first
(git rebase <vcs.baseRef> on a local copy, then
git push --force-with-lease origin <local>:<integration-branch>).
- Open the final PR to
vcs.defaultBranch with a title referencing the
parent and a body summarizing all children. In a tracker with a closes-verb
(Fixes <PARENT> / Closes #<n>), include it so the parent auto-closes.
On tracker: github, also add a Closes #<child> line for every child
issue — children merged into the integration branch (not the default), so
their close verbs never fired; this squash to the trunk is where they finally
close. (Their board items were already moved to Done in next step 5.)
- Merge behavior:
- default → stop, print the PR URL (squash-to-trunk is the human gate).
--merge → gh pr merge <N> --squash --delete-branch, adding --auto if
CI gates it. If vcs.protectedBranchMergeQueue is true, use
gh pr merge <N> --auto with no method flag (the queue's method wins).
- Parent state — adaptive (don't trust
tracker.githubIntegration; check
reality): with --merge, once the squash PR is MERGED ensure the parent is
Done (move only if not already Done — the closes-verb also closes it if a
live integration is connected; don't clobber). On a tracker: github
board, do the move via the adapter:
<TRACKER> set-status <parent#> "<tracker.board.statusOptions.done>". Without
--merge, leave the parent In Progress — it goes Done when a human merges the
squash PR (a later finish/reconcile run, or a live integration's closes-verb,
sets it).
- Delete the epic state file
.rig/epics/<integration-branch>.json (the
work is on the trunk now).
- Offer local verification. Ask whether to verify the epic on a local build
before moving on. If the project ships a local-run/QA skill, invoke it from
the integration-branch worktree (
cd there first — don't accidentally serve
the trunk). Otherwise point the user at the project's run instructions.
prune
Remove integration-branch worktrees whose branches are merged (auto-closed PRs)
or gone on origin. Epic-specific policy here; the teardown delegates to
/rig-worktree rm so the skip-if-dirty safety lives in one place.
- List candidates:
git worktree list --porcelain; for each child-branch
worktree, check whether its PR is MERGED/CLOSED or its remote tracking
branch is gone (/rig-worktree list prints this table — reuse it).
- Keep-one-for-review rule: until
finish lands the squash PR, keep one
usable worktree (most recent merged child, or an explicit integration-branch
worktree) so review doesn't pay a fresh install + env re-symlink. After
finish, all are fair game.
- Remove the rest via
/rig-worktree rm (dirty worktrees are skipped, not
removed). Report removed / kept (+why) / skipped (+reason).
Gotchas
- Long-lived integration branches drift. If the trunk moves during the epic,
rebase the integration branch periodically — catching drift mid-epic beats a
painful conflict at
finish.
- Don't force-push the integration branch except when rebasing onto the
trunk. In-flight child PRs are based on its tip; a rewrite breaks them. The
FF-between-children pattern advances the branch without rewriting history.
- Auto-closed PRs aren't reviewed. FF'ing over a child's tip closes its PR as
MERGED without a gate — the real review is
finish's combined squash PR. If a
child truly needs its own review, hold off FF'ing and let the user merge it.
- State file carries the why. The integration branch carries the code; the
.rig/epics/*.json note carries why-it's-an-epic, the dependency chain, and
what got descoped. Future sessions shouldn't re-derive the plan.
- Drive tracker state adaptively —
githubIntegration is a hint, not a gate.
The flag can claim true while nothing is actually connected (no PR ever links
to an issue), which silently strands tickets. So ensure each transition
idempotently: check the item's current state, move it only if it isn't already
there, and defer if a live integration already moved it (never clobber a
further-along state). rig sets parent In Progress (start), child Done (next,
once its PR is MERGED), and parent Done (finish --merge); a live branch +
closes-verb integration, if present, just gets there first. (Each child's
start-of-work "In Progress" is set by its own /rig-task, Step 1.)
1---2name: rig-epic3description: Plan and run a multi-ticket epic: decompose a feature into parent + child items and stack PRs on a shared integration branch instead of landing each on main. Use when children interleave (one item's runtime contract depends on another's incomplete state) — stacking keeps each child PR reviewable without temporarily breaking main, then squashes to main once. Triggers on: 'epic', 'plan epic', 'plan this as an epic', 'break this into an epic', 'start epic', 'integration branch', 'stack PRs', 'finish epic'.4---56# rig-epic — integration-branch workflow78For multi-item arcs where landing each item directly on the trunk would9temporarily break the runtime contract, stack child PRs on a shared10**integration branch** and squash that branch into the trunk as one PR at the11end. Each child PR is reviewed *as the step it actually is*; the trunk-bound12delivery is one squashed rebase.1314## Configuration1516Reads `.rig/config.json` (defaults in parentheses):1718- `tracker.provider` — `linear` | `github` | `none` (`none`). How parent/child19 items are stored. `none` → items live only in the epic **state file** (below);20 no tracker calls.21- `tracker.team` / `tracker.project` / `tracker.ticketPrefix` / `tracker.githubIntegration`.22- `tracker.shapeLabels.epic` (`epic`) — GitHub-only label put on the parent at23 creation so a Project board / dispatcher can distinguish the epic from child24 `feature` issues. Omit `shapeLabels` to skip.25- `tracker.board` — GitHub Projects v2 identity. When set, the **parent's** board26 transitions and each child's **Done** move go through the `rig-tracker` adapter27 (`gh issue edit` can't touch a ProjectV2 Status field). Resolver used below:28 `<TRACKER>` = `.rig/rig-tracker` if executable else `<RIG_DIR>/scripts/rig-tracker.sh`29 (see [`docs/tracker-adapter.md`](../../docs/tracker-adapter.md)). Absent → skip30 the board moves; the `linear`/`none` paths are unchanged.31- `vcs.baseRef` (`origin/main`), `vcs.defaultBranch` (`main`),32 `vcs.protectedBranchMergeQueue` (`false`).33- `sourceScope[0]` — where to explore during `plan`.34- `agents.architect` / `agents.reviewer` (default `rig-<role>`) — for the35 fresh-context `review` panel.36- `style.guideFile` (`.claude/STYLE.md`) — how parent/child ticket bodies, the37 `finish` PR body, and every status report are written.3839Delegates to `/rig-task` (per child), `/rig-worktree` (checkouts), `/rig-review`40+ `/rig-review fix` (the combined-diff review), `/rig-tidy` (optional).4142### Epic state file (replaces any external memory)4344Each active epic is tracked in a repo-local JSON file at45**`.rig/epics/<integration-branch>.json`**:4647```json48{49 "parent": "ABC-42 or a slug in tracker=none",50 "parentTitle": "…",51 "integrationBranch": "abc-42-<slug>",52 "whyEpic": "which children interleave and why",53 "children": [54 { "id": "ABC-43", "title": "…", "blockedBy": [], "branch": null, "status": "todo|merged" }55 ]56}57```5859**Where the truth lives.** On `tracker: github`, the source of truth is the60**parent issue and its native sub-issues on the tracker** — not this file. The61JSON is a *local cache* rebuilt from the tracker (`gh api62repos/<repo>/issues/<parent>/sub_issues`), and it is **gitignored — never63committed** (`.rig/epics/` is in `.gitignore`). On `linear` the parent/children64live in Linear; on `none` the file is the only record. Either way it's throwaway65coordination state, rebuildable from the tracker at any time — the board is the66shared brain, not the repo.6768`next`/`run`/`review`/`finish`/`prune` read the reconstructed set. Parent69inference (when `<PARENT>` is omitted): on `github`, the open issue labelled70`shapeLabels.epic` that has sub-issues; else if exactly one `.rig/epics/*.json`71exists, use it; if zero or many, ask.7273**Intent banner:** every invocation MUST print one line first — mode, what74branch PRs target, what it won't do:7576```77rig-epic: <subcommand> — <action>. PRs target <branch>. Will not <thing>.78```7980## `status` (default, report-only)8182Read the epic state file(s). Report: integration branch + commits ahead of83`vcs.baseRef`; open child PRs (`gh pr list --base <integration-branch>`) and84their state; each child's tracker status (if a tracker); leftover merged/deleted85worktrees (suggest `/rig-epic prune`); and a one-line recommendation (`open child86PR X`, `ready for /rig-epic next`, or `ready for /rig-epic finish`). Modify87nothing; spawn nothing.8889## `plan <FEATURE>`9091Decompose a feature into a parent + 3–8 children, write the state file, then92chain into `start`.9394Use this only when the work is genuinely epic-shaped (children interleave —95at least one child's runtime contract depends on another being partially96complete). If the items are independent and each can land on the trunk alone,97stop and tell the user to run `/rig-sprint plan` instead — same decomposition,98no integration branch.99100### Procedure1011. **Read context.** Product/spec docs if the project has them (ask if unsure);102 explore the codebase (`sourceScope[0]`) to see what exists. If a tracker is103 configured, search it for near-duplicate items first.1042. **Sanity-check it's an epic** (per the interleave test above). If not → send105 to `/rig-sprint plan`.1063. **Create the parent.**107 - `tracker: linear`/`github` → create the parent item (team/project from108 config); description covers Overview, why-it's-an-epic, and the planned109 children.110 - **GitHub only — mark the shape.** After creating the parent, apply the epic111 shape label so a Project board / dispatcher can tell the parent apart from112 the child `feature` issues: `gh issue edit <parent#> --add-label "<L>"`113 where `<L>` = `tracker.shapeLabels.epic` (default `epic`). Ensure the label114 exists first (`gh label create "<L>" --force`). Skip entirely if115 `tracker.shapeLabels` is absent. (Linear needs no label — native parent116 grouping already distinguishes the epic.)117 - `tracker: none` → the parent is a slug + title recorded in the state file only.1184. **Create each child** with its dependency edges.119 - Small enough for one agent session (1–3 files); concrete, testable120 acceptance criteria; foundational work first.121 - **Record `blockedBy` for every real dependency** — this is what `next`122 reads to pick the next unblocked child. Without it the pick falls back to123 declaration order and gets interleaved epics wrong.124 - **`tracker: github`**: create each child as its own issue, then **link it as125 a native sub-issue of the parent** —126 `gh api repos/<repo>/issues/<parent#>/sub_issues -F sub_issue_id=$(gh api repos/<repo>/issues/<child#> -q .id)` —127 and write "Blocked by #<n>" in the child body (GitHub has no native128 blockedBy). The sub-issues **are** the epic on the board; the `.rig/epics`129 cache is derived from them, never the other way around.130 - **`tracker: linear`**: set the native parent + blocked-by relations.131 - **`none`**: the state file is the only record.1325. **Show a summary table** (ID · Title · Depends On).1336. **Chain into `start <PARENT>`** — `plan` is plan-and-start; don't stop to ask.1347. **Stop after `start`.** Report the integration branch, the children, and the135 next step (`/rig-epic next` for one, `/rig-epic run` for the loop). Never136 auto-start `run` — execution is an explicit opt-in.137138## `start <PARENT>`139140Pre-flight: `git fetch origin`; confirm the parent and at least one child exist141(in the tracker, or as arguments for `none`).142143**Rebuild the cache from the tracker (github).** If `<PARENT>` is an existing144GitHub epic (e.g. `/rig-epic 42` or `/rig-epic run`), reconstruct the children145from its **sub-issues** rather than trusting any local file:146`gh api repos/<repo>/issues/<parent#>/sub_issues` → each becomes a child (id =147issue number, `blockedBy` parsed from "Blocked by #n" in the body,148`status` from open/closed + board column). Write that to `.rig/epics/<branch>.json`149(gitignored). This is what makes an epic you were handed on the board runnable —150no committed state required.1511521. **Integration branch name:** `<parent-slug>-<title-slug>` (kebab, e.g.153 `abc-42-agent-as-definition`).1542. **Cut the integration branch from `vcs.baseRef`** without a local checkout:155 ```bash156 git fetch origin157 git push origin <vcs.baseRef>:refs/heads/<integration-branch>158 ```159 Non-destructive. If the branch already exists, leave it (never overwrite —160 could destroy in-flight work).1613. **Write `.rig/epics/<integration-branch>.json`** (schema above) with the162 parent, why-epic, and every child + `blockedBy`. This is what makes each163 `/rig-task <child>` target the integration branch instead of the trunk.1644. **If a tracker is configured**, add an "Integration branch: target165 `<integration-branch>`, not the trunk" note to each child so the next agent166 doesn't re-read this skill, and ensure the **parent** is In Progress167 (adaptive: move it only if not already started; defer if a live integration168 did). On a **`tracker: github` board**, do the parent move through the adapter:169 `<TRACKER> set-status <parent#> "<tracker.board.statusOptions.inProgress>"`.170 Children get their own In Progress from their `/rig-task` Step 1.1715. **Name the session** `"EPIC: <parent title> (<parent>)"` via172 `scripts/set-session-name.sh` (Claude-Code-only; no-ops elsewhere) so the173 background-job list reads as the epic. Children set their own `FEAT:`/`CHORE:`174 names with `--skip-if-prefix "EPIC:"`, so this epic label stays on top.1756. **Report** the branch, the children, and the next step.176177## `next` (single child)1781791. Resolve the active integration branch (one expected; ask if many). If the180 session isn't already `EPIC:`-named (i.e. `next` invoked directly, not via181 `start`), name it `"EPIC: <parent title> (<parent>)"` (Claude-Code-only).1822. **Pick the next unblocked child:** the first not-done child whose `blockedBy`183 are all merged into the integration branch. State the pick + reasoning in the184 banner.1853. **Ensure the previous child has landed.** With `--auto-merge` (Step 4) each186 child's PR merges into the integration branch on its own once CI passes, so the187 integration tip advances automatically — just `git fetch origin` and confirm.188 (Fallback for a child run *without* `--auto-merge`: fast-forward the branch over189 its tip — `git push origin origin/<previous-child-branch>:refs/heads/<integration-branch>`,190 which auto-closes that PR as MERGED. Safe to re-run.)1914. **Run `/rig-task <CHILD> --base <integration-branch> --auto-merge`** (add192 `--spec-cleared` when `run`'s front-loaded spec review already cleared the193 specs) — `--base` makes the child's worktree branch from, and its PR target, the194 integration branch; `--auto-merge` makes the child enable `gh pr merge --squash --auto` on195 its own PR once its review is clean, so **CI lands it into the integration196 branch** (always **squash** — don't rebase-merge; many repos disallow it. If the197 integration branch has no required checks, the child merges directly). Run198 one-shot (start→finish); it returns a single outcome string for the merge gate.1995. **Merge gate — only `clean` is merge-green:**200 - `clean` → the child enabled auto-merge, so its PR lands when CI passes.201 **Wait** for it: poll `gh pr view <N> --json state` (~60s, up to ~30min)202 until `MERGED`, then `git fetch origin` and confirm the tip advanced. Record203 the child `merged` + its branch in the state file, and **ensure the child204 ticket is Done** (adaptive — move only if not already Done; defer if a live205 integration closed it). On a **`tracker: github` board**, move it via the206 adapter: `<TRACKER> set-status <child#> "<tracker.board.statusOptions.done>"`.207 (The child'"'"'s PR merged into the **integration branch**, not the default208 branch, so its `Closes #<n>` has **not** fired — the adapter move is what209 marks it Done now; the issue itself closes when the epic squashes to trunk.)210 If CI fails / it never merges → treat as not-clean.211 - anything else (`actionable`/`timeout`, or a tracker-parked state) → the child212 did **not** enable auto-merge; stop. Surface the outcome, the PR URL, and any213 reviewer summary. Wait for the user; don't retry a parked review.214215`next` does exactly one child. Use `run` for the loop.216217## `run [--advisor]`218219**Front-loaded spec review — once, before the loop.** Review **all** children's220specs together, up front, instead of discovering blockers one child at a time221mid-run. A child that pauses on a spec question stalls the whole epic (and under222parallel or durable execution, a paused child can fail the batch outright), so223resolve the specs before any child starts coding:2241. Fan out `agents.architect` + `agents.qa` over **every** child's spec, read225 against the integration branch; each returns its blockers.2262. Consolidate into one blocker list and resolve it **once** — clarify in the227 tracker, or decide with the user. This is the epic's single spec decision point.2283. Then run each child with its own spec gate **pre-cleared**:229 `/rig-task <CHILD> --base <integration-branch> --auto-merge --spec-cleared`, so230 no child re-pauses on a question already answered.231232This is the *pre-coding* spec pass; the combined-diff `review` (below) is the233*post-merge* code pass — different gates.234235**`--advisor` (unattended).** Decide that front-loaded gate with an **advisor236pass** instead of a human: a delegated `agents.architect` review reads the237architect + QA specs and either **proceeds** with synthesized per-child direction238(handed verbatim to each child's coder), or **halts** with a blocked report naming239what a human must decide. No human waits, so the epic runs hands-off to child PRs;240a genuine blocker still stops it rather than fanning out on a bad spec. (The paired241Smithers durable workflow implements this with a cheap model — the "Fable242advisor" — so kicked-off epics never park at the gate.)243244Then loop `next` until no unblocked child remains:245```246while an unblocked child exists:247 run /rig-epic next # children run --spec-cleared (spec was front-loaded)248 if it stopped without merging (outcome ≠ clean) → stop, hand back249 else → re-evaluate unblocked children250```251When the queue empties, report "epic ready for `/rig-epic finish`."252253## `review [<PARENT>]`254255Run after the last child merged into the integration branch, before `finish`.256Per-PR review (`/rig-review` inside each `/rig-task`) sees each child in257isolation; this sees the *combined* shape and the *simplification* only visible258once everything is in. Fresh-context sub-agents (input = the squashed diff +259child PR titles) can't inherit the implementer's remembered rationalizations.260261**Run before `prune`** — the review sub-agents need a working checkout; reuse a262child worktree if one still exists (avoids a fresh install + env re-symlink).2632641. Resolve the integration branch; `git fetch origin`.2652. **Ensure an integration-branch worktree** (`$WT`). Reuse a merged-child266 worktree fast-forwarded to the integration tip, else create one with267 `/rig-worktree` (`--base <integration-branch> --reuse`).2683. **Fan out three sub-agents in parallel** (one message, three `Agent` calls),269 each `cd`-ing into `$WT` with the integration tip checked out:270 - **Lens 1 — Simplification (`agents.architect`):** diff271 `git diff <vcs.baseRef>...HEAD`; child PR list via `gh pr list --base272 <BR> --state merged`. Mandate: find abstractions to collapse, helpers one273 PR added that another PR's final shape made redundant, config knobs nobody274 sets, code paths the combined diff made dead, one-caller types. Concrete275 deletions/merges, file:line, highest-impact first. Skip correctness.276 - **Lens 2 — Cross-PR correctness (`agents.reviewer`):** walk the project's277 review-pattern catalog (`review.patternsFile`) against the *combined* diff.278 Per-PR review already ran; catch interactions only visible at the merged279 shape (PR-A's helper vs PR-D's stale caller; PR-B removed a knob PR-F still280 reads). P0/P1/P2 with file:line + category.281 - **Lens 3 — Dead code & stale refs (`agents.reviewer`):** for every symbol282 the diff *added*, is it called elsewhere? For every symbol *removed*, grep283 the tree (workflows, manifests, IaC, scripts, docs) for residual refs.284 Report with file:line.2854. **Consolidate** — dedupe, produce one P0/P1/P2 list grouped by lens, print286 counts.2875. **Triage:**288 - 0 items → `clean — ready for /rig-epic finish`, stop.289 - findings → ask via `AskUserQuestion`: `apply now` / `skip and finish290 anyway` (downgrade to follow-ups) / `pause`. On `apply now`, run291 `/rig-review fix --source local` in `$WT` (spawns `agents.coder`,292 re-reviews to clean), commit + `git push origin <integration-branch>`,293 re-run `review` once to confirm convergence.2946. **Outcome string** (for `finish`): `clean — …` / `applied — …` / `paused — …`.295296## `finish [<PARENT>] [--merge]`297298Squashes the whole integration branch into a single PR to the trunk. **Default:299open the PR and stop** — this is the one PR to eyeball.300301**`review` is a hard gate.** `finish` always runs `review` first; the squash PR302opens only on `clean` or `applied`. On `paused`, stop and wait.3033041. **Run `review` (gate)** inline; branch on its outcome (`clean`/`applied` →305 proceed; `paused` → stop and print pending findings).3062. **Refresh & rebase if needed.** `git fetch origin`; list307 `git log <vcs.baseRef>..origin/<integration-branch> --oneline`. If the trunk308 moved, rebase the integration branch onto it first309 (`git rebase <vcs.baseRef>` on a local copy, then310 `git push --force-with-lease origin <local>:<integration-branch>`).3113. **Open the final PR** to `vcs.defaultBranch` with a title referencing the312 parent and a body summarizing all children. In a tracker with a closes-verb313 (`Fixes <PARENT>` / `Closes #<n>`), include it so the parent auto-closes.314 **On `tracker: github`, also add a `Closes #<child>` line for every child315 issue** — children merged into the integration branch (not the default), so316 their close verbs never fired; this squash to the trunk is where they finally317 close. (Their board items were already moved to Done in `next` step 5.)3184. **Merge behavior:**319 - default → stop, print the PR URL (squash-to-trunk is the human gate).320 - `--merge` → `gh pr merge <N> --squash --delete-branch`, adding `--auto` if321 CI gates it. **If `vcs.protectedBranchMergeQueue` is true**, use322 `gh pr merge <N> --auto` with **no** method flag (the queue's method wins).3235. **Parent state — adaptive** (don't trust `tracker.githubIntegration`; check324 reality): with `--merge`, once the squash PR is MERGED ensure the parent is325 **Done** (move only if not already Done — the closes-verb also closes it if a326 live integration is connected; don't clobber). On a **`tracker: github`327 board**, do the move via the adapter:328 `<TRACKER> set-status <parent#> "<tracker.board.statusOptions.done>"`. Without329 `--merge`, leave the parent In Progress — it goes Done when a human merges the330 squash PR (a later `finish`/reconcile run, or a live integration's closes-verb,331 sets it).3326. **Delete the epic state file** `.rig/epics/<integration-branch>.json` (the333 work is on the trunk now).3347. **Offer local verification.** Ask whether to verify the epic on a local build335 before moving on. If the project ships a local-run/QA skill, invoke it **from336 the integration-branch worktree** (`cd` there first — don't accidentally serve337 the trunk). Otherwise point the user at the project's run instructions.338339## `prune`340341Remove integration-branch worktrees whose branches are merged (auto-closed PRs)342or gone on origin. Epic-specific *policy* here; the teardown delegates to343`/rig-worktree rm` so the skip-if-dirty safety lives in one place.3443451. **List candidates:** `git worktree list --porcelain`; for each child-branch346 worktree, check whether its PR is `MERGED`/`CLOSED` or its remote tracking347 branch is gone (`/rig-worktree list` prints this table — reuse it).3482. **Keep-one-for-review rule:** until `finish` lands the squash PR, keep one349 usable worktree (most recent merged child, or an explicit integration-branch350 worktree) so `review` doesn't pay a fresh install + env re-symlink. After351 `finish`, all are fair game.3523. **Remove the rest** via `/rig-worktree rm` (dirty worktrees are skipped, not353 removed). Report removed / kept (+why) / skipped (+reason).354355## Gotchas356357- **Long-lived integration branches drift.** If the trunk moves during the epic,358 rebase the integration branch periodically — catching drift mid-epic beats a359 painful conflict at `finish`.360- **Don't force-push the integration branch except when rebasing onto the361 trunk.** In-flight child PRs are based on its tip; a rewrite breaks them. The362 FF-between-children pattern advances the branch without rewriting history.363- **Auto-closed PRs aren't reviewed.** FF'ing over a child's tip closes its PR as364 MERGED without a gate — the real review is `finish`'s combined squash PR. If a365 child truly needs its own review, hold off FF'ing and let the user merge it.366- **State file carries the *why*.** The integration branch carries the code; the367 `.rig/epics/*.json` note carries why-it's-an-epic, the dependency chain, and368 what got descoped. Future sessions shouldn't re-derive the plan.369- **Drive tracker state adaptively — `githubIntegration` is a hint, not a gate.**370 The flag can claim `true` while nothing is actually connected (no PR ever links371 to an issue), which silently strands tickets. So *ensure* each transition372 idempotently: check the item's current state, move it only if it isn't already373 there, and defer if a live integration already moved it (never clobber a374 further-along state). rig sets parent In Progress (`start`), child Done (`next`,375 once its PR is MERGED), and parent Done (`finish --merge`); a live branch +376 closes-verb integration, if present, just gets there first. (Each child's377 start-of-work "In Progress" is set by its own `/rig-task`, Step 1.)