Skip to content
open tomato/ rafa
GitHub
rafa pages
  1. rafa
  2. Epics and boards

Epics and boards

Group specs into epics, give each team its own board, and let a loop follow a blocker into another epic and come home, one step at a time.

Everything on this page is optional. A project with no epics and one board works exactly as Specs and plans describes, and each step below is added only when the one before stops being enough.

NOTE

These commands read and write GitHub issues and need gh signed in, so their output is not shown here.

Alone: one roadmap, no epics

The roadmap stays a list of specs, and rafa roadmap prints it as a table. Nothing on this page changes that.

One team: epics

An epic is a bigger issue that groups specs into a feature. It holds acceptance criteria, an estimate and an ordered checklist of its specs. A spec joins an epic by carrying the epic's label, epic:<slug>, and the checklist says in what order the epic's specs are built. So membership and order are two separate things: the label says which epic, the checklist says when.

rafa epic new "Export reports" --slug=export   # the issue, its line on the board, its label
rafa epic show                                 # the epic's issues as the roadmap table

Once the roadmap lists epics, rafa roadmap shows one row per epic with its done/total, and rafa plan create --next walks into the first open epic's specs. rafa never plans across two epics.

Priorities change. rafa epic promote and rafa epic defer move an epic between now and later, and each move leaves a comment on the issue, so the board's history answers "why is this later now?". rafa epic move moves an issue filed in the wrong epic, and rafa epic close closes an epic only through a gate: every member closed, then its criteria checked against main by Claude Code sessions, because every issue closed is not the same as the feature working. The gate spends usage; nothing runs while a member is still open. Closing an epic walks its steps.

TIP

Why labels for membership: every tracker graded for this work (Linear, Jira, Notion and GitHub's own sub-issues) records membership as one parent field. One label per issue does the same, so a project can move trackers later without re-deciding its epics.

One team: blockers

A board records which issue blocks which in one of two modes, set by board.relationships:

  • labels, the default: a spec:blocked label and a Blocked by: line in the issue body. Reading them costs nothing extra.
  • native: GitHub's own parent and blocked-by links, which show in GitHub's interface. A read costs about 8 of the 5,000 GitHub API points an hour.

rafa init --board moves a board from one mode to the other: it lists every change, asks once, and offers to remove the old marks afterwards.

Following a blocker: one hop, then home

When the work in your epic is blocked by an issue in another epic, rafa next --roadmap reaches for that blocker, works it, and comes back.

Picture climbing with two hands. One hand holds home, the other reaches for the next handle. Reaching a third handle would mean letting go of home, and that is where rafa halts. In the design's worked example, issue H in epic E is blocked by C in epic F: rafa next --roadmap works C and comes home. If C were itself blocked by B, it would halt and show the chain H ← C ← B.

What this means at each step:

  • Alone: a dry hop. When one epic runs out, --roadmap goes on to the next.
  • One team: hops between the team's own epics.
  • Several teams: a hop into another team's board opens a pull request that waits for that team's review, and rafa status says it is waiting on the owner.

Several teams: one board each

A repository shared by several teams can hold one board per team. Each board names its owner in an Owner: line and the folders it owns in an Owns: line, tied to the repository's CODEOWNERS file. A person moves to their team's board once, and from then on rafa next works there.

rafa board list     # every board, with its owner and which one is current
rafa switch 252     # move this checkout to board or epic #252
rafa switch -       # back to where you were
rafa status         # where you stand now

rafa switch works like cd - and git checkout -: two slots, where you are and where you were, plus a mark for home. A switch made by hand re-homes, so the new place becomes home as well. --no-rehome keeps home where it is, for a workflow that moves for one task and goes back. The position lives in .rafa/position.json. A longer trail you can walk back, like pushd and popd, is not part of it.

A package inside a monorepo can be its own rafa project, with its own .rafa/config.yaml and its own default board. [verify: a nested project's own default board, Q-R3 in records/docs/rafa/questions.md]

Coming: borders

NOTE

Coming, in an open spec (#248): rules for unattended work that crosses epics and teams. The design's picture is borders, transit and a visa the owner issues for one crossing; the structure underneath is a firewall's access-control list, with an action, a direction and a source. The rules are a guardrail for loops that run unattended. A person can still switch branches and work anywhere: what the rules control is whether a loop lands changes outside its home without an owner's say.

Decisions not taken

Each was considered and dropped, with the reason the design gave:

  • An integration branch per epic: it drifts from main.
  • Planning a whole epic as one plan: it breaks the rule of one spec, one plan, one pull request.
  • Pinning a running loop to the epic it started in: it would work on stale priorities.
  • Claiming an epic with a marker on the board: the marker would publish machine names.
  • Letting an idle epic fade back to the backlog by itself.
  • Capping hops with a count: the risk is permission, not distance.
  • Border rules set on issues, and true/false switches in any of these settings: nobody could say whether boundaries: true meant "borders exist" or "borders are loose".