Epics & tasks¶
The model has exactly two artifacts — the task (the unit of work) and the epic (the unit of delivery) — plus one special-purpose variant of the epic for the work that refuses to fit a plan.
Tasks¶
A task is an issue that represents a single, self-contained change:
- It is implemented by exactly one pull request, in the repository where that PR lands (task↔PR locality).
- It is born linked to an epic. On GitHub the link is a native
sub-issue; on forges without sub-issues it degrades to a portable
Parent: <owner>/<repo>#<n>line in the task body. Either way, the tooling speaks one vocabulary and never the underlying mechanism. - It closes when its PR merges. A task with an epic parent links its PR with
the
Closeskeyword, so the merge closes the task automatically; the one close keyword isCloses(Fixes/Resolvesare banned to keep it unambiguous).
Because a task is exactly one PR, "done" is unambiguous: once the change is in the trunk, the task is finished. Any later change is a new task, never a reopening.
Finite epics¶
A finite epic is a cross-cutting initiative with an end — a real project made
of related tasks, often spanning several repositories. It carries the epic
label and lives in the org's .github. Its spec and plan live beside it, at
epics/<n>-<slug>/ in .github.
When every child task of a finite epic is closed, the epic rolls up — it
auto-closes. Roll-up is event-driven: an on: issues.closed Action fires the
check on every issue close, so an epic wraps itself up the moment its last task
lands, with no human bookkeeping.
Ad-hoc epics¶
Not all work belongs to an initiative. The small, unplanned, standalone
things — fix a typo in a man page, bump a pin — would clutter the model if each
demanded its own epic. So every repository has exactly one ad-hoc epic:
Epic (ad hoc): <repo>, labelled epic + ad-hoc, and (like all epics) located
in .github. Its tasks live in the target repository, linked across.
ad-hoc is the single marker for this perpetual variant. An earlier
standing alias has been retired — removed from the code and the label
registry — so ad-hoc is the only form the model recognises.
An ad-hoc epic is perpetual: created once, never auto-closed, kept open even when empty. That perpetual-ness is the only thing distinguishing it from a finite epic. The name does deliberate work — "ad hoc" is the smell a serious engineer drives toward zero. The long-term goal is for ad-hoc work to become the rare exception as real work is increasingly shaped into finite, tidy, self-closing epics.
The bookend convention¶
An epic is never truly "done" the instant its implementation lands, because almost no real problem is 100% closed by a single initiative. So every epic carries bookend tasks:
- It opens with a documentation task — the spec and plan, born from planning.
- It closes with two kinds of task: a follow-on brainstorm (review what shipped, decide what comes next, and spawn the follow-on epic if there is one) and a documentation review (confirm the change is reflected in the docs — especially this site).
This rides the roll-up: because the epic cannot close until all its tasks close, the closing bookends gate completion. You cannot finish an epic without answering "now what?" and confirming the story is documented. Nothing is ever done without asking what comes next.
Compliance invariants¶
These guarantees are enforced, not just documented. vrg-epic-audit is a
read-only sweep (with a human-gated --close) that flags drift and invariant
violations:
- Epics live in
.github. A public repository's epics belong in the org's.github; an epic that has leaked out is flagged. (A private repository may legitimately home its own epics.) - No stray
.githubissues. The epic home holds only epics, intake, and tasks linked under an epic. An unlinked, non-epic, non-intake issue there is a stray. - An epic is never closed while a child is open. Roll-up closes an epic only once all its children are closed; a closed epic with a still-open child is a violation, remediated by reopening it.
When a finite epic is found outside its home, relocate it: move the issue
into .github with vrg-gh issue transfer, then re-establish its task links
with vrg-epic-link.