Communitygithub.com

agent-rig/rig

Copy-in kit of config-driven agent skills, agents, and CI templates — native to Claude Code and adaptable to any AGENTS.md-reading agent.

What is rig?

rig is a Claude Code agent skill that copy-in kit of config-driven agent skills, agents, and CI templates — native to Claude Code and adaptable to any AGENTS.md-reading agent.

Works withClaude Code~Codex CLI~Cursor
npx skills add agent-rig/rig

Ask in your favorite AI

Open a new chat with this agent skill pre-loaded.

Documentation

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.providerlinear | 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.
  • 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.

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" }
  ]
}

This is the single source of truth next/run/review/finish/prune read. Add .rig/epics/ to .gitignore (it's transient coordination state). Parent inference (when <PARENT> is omitted): 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

  1. 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.
  2. Sanity-check it's an epic (per the interleave test above). If not → send to /rig-sprint plan.
  3. 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.
    • tracker: none → the parent is a slug + title recorded in the state file only.
  4. 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 mode: set the tracker's native parent/blocked-by relations and mirror them into the state file. none: state file only.
  5. Show a summary table (ID · Title · Depends On).
  6. Chain into start <PARENT>plan is plan-and-start; don't stop to ask.
  7. 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).

  1. Integration branch name: <parent-slug>-<title-slug> (kebab, e.g. abc-42-agent-as-definition).
  2. 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).
  3. 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.
  4. 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). Children get their own In Progress from their /rig-task Step 1.
  5. 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.
  6. Report the branch, the children, and the next step.

next (single child)

  1. 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).
  2. 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.
  3. 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.)
  4. 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.
  5. 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). 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:

  1. Fan out agents.architect + agents.qa over every child's spec, read against the integration branch; each returns its blockers.
  2. 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.
  3. 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).

  1. Resolve the integration branch; git fetch origin.
  2. 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).
  3. 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.
  4. Consolidate — dedupe, produce one P0/P1/P2 list grouped by lens, print counts.
  5. 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.
  6. 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.

  1. Run review (gate) inline; branch on its outcome (clean/applied → proceed; paused → stop and print pending findings).
  2. 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>).
  3. 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.
  4. Merge behavior:
    • default → stop, print the PR URL (squash-to-trunk is the human gate).
    • --mergegh 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).
  5. 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). 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).
  6. Delete the epic state file .rig/epics/<integration-branch>.json (the work is on the trunk now).
  7. 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.

  1. 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).
  2. 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.
  3. 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.)

Related Skills