GitHub Issue Standards¶
Purpose¶
Define a consistent, enforced workflow for GitHub issues so all changes are tracked, reviewable, and auditable.
Scope¶
Applies to all repositories that use GitHub issues and pull requests.
Definitions¶
- Issue: The unit of tracked work in GitHub.
- Primary issue: The single issue a pull request is intended to close.
- Sub-issue: A scoped unit of work that contributes to a parent issue but does not complete it.
- Operational task: A not-PR-workable task whose acceptance is proven by running something and recording the result as a comment — not by merging a pull request. Two kinds: validation (verify) and deployment (make merged work usable). See Operational tasks.
- Intake item: An uncurated capture —
triage,idea, orresearch— that is not yet a task, held in a queue until it is groomed into the epic/task model. See Intake queues.
Core rules¶
- Every pull request must have a primary GitHub issue. No exceptions.
- Work must not begin until the issue exists.
- One primary issue per pull request. If a PR must close multiple issues, document why in the PR description.
Acceptance criteria¶
Every issue must specify acceptance criteria if they are not intuitively obvious. If acceptance criteria are ambiguous, get explicit human confirmation before proceeding.
Examples:
- Docs-only issues: satisfied when documentation changes are merged.
- Bug reports: may require reporter confirmation or verified reproduction and resolution steps.
Issue templates¶
Repositories must use GitHub Issue Forms and disable blank issues so all issues capture the required structure.
Minimum required fields:
- Summary
- Problem or goal
- Acceptance criteria
- include an explicit "criteria are obvious" option
- require explicit criteria when not obvious
- Validation or evidence
Required configuration:
.github/ISSUE_TEMPLATE/issue.yml(or equivalent form name).github/ISSUE_TEMPLATE/config.ymlwithblank_issues_enabled: false
Issue creation and linking¶
- If a human already specified an issue, use it as the primary issue.
- If no issue exists, create one before creating a branch or changing files.
- If no issue exists, create it immediately using best-effort assumptions and explicitly note those assumptions in the issue body. Do not delay work by asking for an issue number unless acceptance criteria are materially ambiguous.
- The PR description must link to the primary issue.
- If an issue has no special acceptance criteria, include a closing keyword in the PR description so the issue auto-closes on merge.
- If acceptance criteria are specified, use a non-closing reference in the PR description and close the issue only when the criteria are satisfied.
Sub-issues¶
Create a sub-issue when:
- the parent issue is too large for a single PR
- the PR will not fully resolve the parent issue
- the work can be reviewed and merged independently
Sub-issue rules:
- Link each sub-issue to its parent with the sanctioned tooling (see below).
- The PR should close the sub-issue, not the parent, unless the PR completes the parent's full scope.
Linking a sub-issue¶
Raw gh api is blocked for agents, so sub-issue links are created through the
sanctioned tools. Both establish the same native GitHub sub-issue relationship
(with a portable Parent: body reflink as the cross-forge fallback) that the
rollup reads — never hand-roll the link.
- A new task under its parent — create it already linked:
- An existing task — link it (or backfill a reflink-only child):
Epics¶
An epic is an umbrella issue — carrying the epic label — over the task
issues that deliver it. Tasks may live in other repos in the same org; the link
is a native GitHub sub-issue, with a portable Parent: body reflink as the
cross-forge fallback.
Finite vs perpetual epics¶
- Finite epic — a bounded initiative with a definite end. It rolls up:
once every child task is closed,
vrg-finalize-prcloses the epic. A managed task (an issue with anepic-labelled parent) links its PR withCloses, so the task auto-closes on merge and the final close rolls its epic up. - Perpetual (ad-hoc) epic — a standing umbrella for unplanned work in one
repo. Titled
Epic (ad hoc): <repo>, labelledepic+ad-hoc, one per repo, and it never auto-closes. Target it withvrg-issue-create --epic adhoc. (The olderstandingalias is retired — onlyad-hocremains.)vrg-adhoc-epic ensure --repo <owner>/<repo>creates a repo's ad-hoc epic on demand (idempotent). Its closed children are continuously drained into per-quarter archives so the live umbrella shows only in-flight work — see Ad-hoc epic archiving.
Ad-hoc epic archiving¶
A live ad-hoc epic accumulates closed children forever, so its "what's in flight" signal drowns in finished work. To keep the umbrella honest, closed children are continuously drained into per-quarter archives — a live ad-hoc epic ends up holding only its still-open children.
- Archives are titled
Archive (ad hoc): <repo> — <YYYY>-Q<n>(the separator is a space, an em-dash—, and a space). They live in the same resolved home as the live epic and carry thearchive+ad-hoclabels. Thearchivelabel replaced theepiclabel these buckets used to carry (ad-hocis kept), so terminal per-quarter buckets no longer clutterlabel:epicviews; only the liveEpic (ad hoc): <repo>epic keepsepic+ad-hoc. - Bucketing is by close quarter. Each closed child is re-parented into the
archive for the quarter of its
closedAttimestamp (UTC), so theYYYY-Qnarchive holds the ad-hoc work that finished in that quarter — not the work filed in it. - The live epic is never renamed, recreated, or closed. It only loses its closed children to the archives; it stays the one perpetual umbrella per repo.
- Only canonical per-repo ad-hoc epics are archived. Both the batch drain and
the on-close event path key on the repo named in the
Epic (ad hoc): <repo>title, so a closed child always lands in its own repo's quarter archive — even for a public repo whose ad-hoc epic is homed centrally in.github. A special-purposead-hoc-labelled epic whose title bare-name is a description rather than a real repo (e.g. a standing reliability collection) is left untouched — it is not treated as a drainable ad-hoc epic. - Past-quarter archives are closed; the current-quarter archive stays open. A closed archive is a stable historical record; the current one keeps receiving this quarter's closures. A late straggler whose close-quarter archive was already closed is re-parented into it regardless.
- Idempotent and dry-run by default. Every step is check-before-act (archives are create-if-missing by exact title, an already-parented child is skipped, an already-closed past archive is skipped), so a run that dies partway is repaired by the next one. Empty quarters get no archive. A single drain creates at most one archive per quarter however many same-quarter children it moves, and if a duplicate archive ever exists the oldest is reused rather than the run failing on the ambiguity — so the drain is safe to re-run and self-heals.
Running the drain¶
# Dry-run: report which closed children move to which quarter archive,
# which archives would be created, and which past archives would close.
vrg-adhoc-epic archive --repo <owner>/<repo>
# Apply the moves and closes.
vrg-adhoc-epic archive --repo <owner>/<repo> --apply
# Visibility-aware sweep across every repo in an org.
vrg-adhoc-epic archive --all-in <ORG> --apply
--repo targets a single repo; --all-in <ORG> sweeps every repo in the org,
resolving each repo's ad-hoc home by visibility. Without --apply the command
mutates nothing.
Normalizing legacy archives¶
Archives created before the archive-label rename carry the old
Epic (ad hoc): <repo> — <YYYY>-Qn title and the epic label. vrg-adhoc-epic
normalize converts them in place to the current form — retitling to
Archive (ad hoc): …, adding archive, and removing epic (keeping ad-hoc):
# Dry-run: list every legacy-form archive that would be converted.
vrg-adhoc-epic normalize --all-in <ORG>
# Apply the conversions.
vrg-adhoc-epic normalize --all-in <ORG> --apply
It sweeps every repo's resolved home, so it covers both the centrally-homed archives of public repos and the self-homed archives of private ones, and it converts both open and closed archives. It is idempotent — an already-converted archive is new-form and is skipped — so it is safe to re-run and doubles as a straggler net. The creation path is also self-healing: the next time the drain touches a quarter whose archive is still in legacy form, it heals that archive in place rather than minting a duplicate, so there is no ordering hazard between the code landing and this sweep running.
Automation (no new schedule)¶
Two existing call sites keep archives current with no dedicated cron:
- Steady state — on close. When a child of a live ad-hoc epic closes, the
issues.closedrollup path (vrg-epic-rollup) archives just that child into its close-quarter archive. - Backstop + backfill. The daily
vrg-epic-audit --closesweep runs the org-wide drain: it distributes any backlog the steady-state path missed and closes past-quarter archives. This is the same trusted sweep that closes other provably-complete drift, so it is allowed to mutate under the scheduled automation signal.
Creating an epic (the epic-create workflow)¶
Non-trivial work starts with the epic-create workflow — the default entry
point, since a solution worth designing is worth tracking rather than
brainstorming and walking away. epic-create is the outer orchestrator: it
runs the whole design pipeline and seeds the epic's bookend tasks (below) at
defined handoffs:
superpowers:brainstorming— explore intent, one question at a time (interactive).- Initialize the epic in its home and seed the bookend tasks — the docs-first opening task, the documentation-review sweep, and the terminal retrospective (see the bookend convention below).
- Write
spec.md, thenpaad:pushbackon it (interactive). - Human review.
superpowers:writing-plans→plan.md(automated — no gating).paad:alignment— reconcile the plan against the spec (interactive).- One docs PR (spec + plan) that closes the docs-first task.
- File the implementation tasks and link them under the epic.
The four-stage interaction doctrine governs the pipeline: brainstorming,
pushback, and alignment are interactive while writing-plans is automated,
and every interactive stage gates only on judgment calls that materially
affect the outcome — minor, obvious corrections are batched into a single
end-of-stage "correct me if I'm wrong" review rather than gating each one. The
goal is to front-load analysis so implementation runs near-automated. If the
design collapses to a single-PR change, it drops onto the target repo's ad-hoc
epic instead of minting a finite epic. The full doctrine lives in the
epic-create skill (vergil-claude-plugin).
The bookend convention¶
An epic is never closed until you have decided what comes next AND confirmed
the docs reflect what changed. Almost no real problem is fully resolved by one
epic — you deliver a completed subset and acknowledge the follow-on. Epic work
runs through a three-skill lifecycle, each with a distinct cardinality:
epic-create (once — open the epic) → epic-implement (N times — drive
the tasks, pausing and resuming as blockers allow) → epic-retrospective
(once — the terminal finishing gate). So every epic carries fixed bookends:
- Opening bookend — documentation. A docs-first task carrying the epic's spec and plan, born from planning; it lands before the implementation tasks.
- Closing bookends — ordered:
- Documentation-review sweep (runs first) — verify the shipped work is fully reflected in the human-facing docs across every repo the epic touched; it spawns per-repo doc tasks where other repos' docs need work.
- Retrospective (terminal — the final gate) — a mandatory, single,
backward-looking record that partners the spec and plan
(
epics/<N>-<slug>/retrospective.md), so a later reader follows spec → plan → retrospective. Authored withepic-retrospective, whose preflight refuses to run until every other child of the epic is closed; its docs PR closes the epic. - Follow-on brainstorm — semi-optional. Reviewing what shipped and brainstorming the follow-on epic(s) is a forward-looking concern that no longer defines the end of an epic: seed it at creation only when a known enabling chain already exists, otherwise accrue it during implementation or skip it. Its outcomes are recorded in the retrospective.
The bookends need no new closing mechanism — they ride the existing rollup. A
finite epic rolls up only when every child is closed, and the retrospective is
the last to close, so it naturally gates closure as the final bookend. The
convention is mechanized as prose in the epic-create / epic-retrospective
skills (vergil-claude-plugin), not in rigid tooling, because the retrospective
and any follow-on are inherently agentic judgment.
Epic home (the <org>/.github rule)¶
An epic's home repo is derived from repository visibility:
- A public repo homes its epics centrally in the org's
.github. - A private repo (with a public
.github) homes its epics in itself. - A private
.githubmeans the whole org is private, so everything homes in.github.
Ad-hoc epics follow the same rule — Epic (ad hoc): <repo> lives in the repo's
resolved home. See
Epic home visibility flips for
relocating epics when a repo's visibility changes.
Compliance invariants¶
vrg-epic-audit reports (read-only) any drift from the model:
- Epics live in
.github. An openepic-labelled issue outside.githubin a public repo is a violation (a private repo self-homes legitimately). - No stray
.githubissues. The epic home holds only epics, intake (triage/idea/research), thearchive-labelled per-quarter buckets, and tasks linked under an epic; any other open issue there is a stray. - An epic is never closed while a child is open. A closed finite epic with an
open child is a violation (perpetual
ad-hocepics are exempt — they never roll up; their closed children are drained into per-quarter archives instead, see Ad-hoc epic archiving).
Intake queues¶
Not every capture is ready to be a task. Three intake queues hold uncurated
work until it is groomed into the epic/task model. Each is a label, and all
three route to the org's .github by default, so the entire org-wide intake
backlog is one filtered view beside the epic roster:
| Kind | Captures | Graduates into |
|---|---|---|
triage |
A problem or bug not yet understood — needs diagnosis | an epic (or a task) |
idea |
A spark — "what if we did this" | a feature or epic |
research |
An investigation that yields a reproducible result | an epic with tooling PRs and a report |
A result worth having is worth reproducing, so research is not ad-hoc work — it graduates into a proper finite epic with automated tooling, never a hand-run one-off.
Create an intake item with the sanctioned tool. --kind selects the shape and
stamps its label (default triage); the target defaults to the org's .github:
Intake lives in .github, never in a member repo — an intake item is not yet a
single-PR task, so the "member repos hold only single-PR tasks" invariant
requires it to sit with the epic roster instead. (This .github default
supersedes the earlier current-repo default for vrg-triage-create.) Grooming
an intake item into an epic or a task is a separate, periodic review step.
Closing behavior¶
- Default: auto-close issues via PR closing keywords.
- If acceptance criteria are specified, do not auto-close. The agent finalizing the PR is responsible for determining closure once the criteria are met.
- If auto-closing is disabled or the PR targets a non-default branch, close the issue manually after merge only when acceptance criteria are satisfied.
- A closed issue must reflect completed work. If work is deferred, keep the issue open or create a follow-up issue and link it explicitly.
Operational tasks¶
Some work is proven not by merging a PR but by running something after merge and recording the result as a comment. These are operational tasks — a family of not-PR-workable task types. Two kinds:
- Validation — verify prior work is correct (a cold rebuild, a
live-environment check, a deploy smoke test). Run with the
issue-validateskill. - Deployment — make merged work usable: install/sync/deploy it into the
environment so the next step can run against it. Run with the
issue-deployskill.
They share one mechanism; each kind supplies its own label, scaffold, and run skill.
Merged vs deployed. An implementation task closes when its PR merges. But the
next step sometimes needs the change not just merged but deployed and usable.
A deployment task makes that explicit, and its closure is the "deployed"
signal — so the common shape of an epic's tail is implement → deploy →
validate, each Blocked-by the last.
Shared rules (both kinds):
- Acceptance is a recorded result, not a merge. The task is proven by running
its procedure and posting
Outcome: SUCCESS(orOutcome: FAILURE) as a comment. - Not PR-workable. It has no code PR and never auto-closes; the PR tooling
(
vrg-submit-pr,vrg-pr-workflow report-ready) refuses it. - Closes only on SUCCESS. On failure it stays open — like a pull request that cannot merge — and the parent epic stays open too. (Validation files a fix issue; deployment retries first, then files a fix issue only for a genuine defect.)
- Gates epic closure by staying open — an open operational child holds the epic until it succeeds, so rollup is honest.
- Records dependencies as
Blocked-by:reflinks.vrg-epic-auditreads them to report each as runnable (dependencies closed) or blocked, tagged by kind. The sameBlocked-by:reflink works on a plain task too — pass--blocked-bytovrg-issue-create --kind taskand it is appended to the task body — so an epic driver's plain-task frontier (which open tasks have every dependency closed) is machine-derivable, not inferred from plan prose.
Create one with the sanctioned path — never hand-roll the body:
vrg-issue-create --epic <org>/.github#N --repo <org>/<repo> \
--kind {validation|deployment} --title "<what>" --blocked-by <org>/<repo>#<TASK>
This stamps the kind's label and an executable scaffold: an author-defined
precondition self-check (a machine probe or a human-attested statement — no
mechanism is prescribed; if a precondition is unmet, record blocked and stop,
never fabricating), the procedure, the acceptance criteria, and a
SUCCESS/FAILURE results template.
Add a validation task when acceptance needs a check the pipeline's own tests cannot do (a cold rebuild, a live check, a deploy smoke test); provisioning and infrastructure work carry a cold-rebuild validation by default. Add a deployment task when the next step needs the change deployed and usable, not merely merged.
Deployment autonomy boundary. A deployment task owns only the agent-safe deploy steps (install/sync/restart). Where deploying needs a release (bump/tag/publish), that release is a human-gated precondition — attested, never performed by the agent — the same policy that keeps PR submission and merge in human hands.