docs(contributing): add PR description template and convention #123

Merged
rootiest merged 4 commits from docs/pr-template into main 2026-09-01 02:42:38 +00:00
Owner

Summary

An agent working in this repo recently went looking for a PR template
(.github/pull_request_template.md, .github/PULL_REQUEST_TEMPLATE.md,
PULL_REQUEST_TEMPLATE.md, docs/PULL_REQUEST_TEMPLATE.md) and found
nothing. The format was real but implicit — every contributor and agent had
to reverse-engineer it from prior PRs, which is exactly how the drift
described below crept in. This codifies it.

  • New .github/PULL_REQUEST_TEMPLATE.md## Summary, optional ##
    sections, then ## Verification last. The guidance lives in HTML
    comments, so it vanishes on render and leaves no boilerplate residue in
    the posted body. Gitea falls back to .github/ when .gitea/ is absent
    and the GitHub mirror reads the same path, so one file covers both
    remotes — and it's a path the agent above was already checking.
  • CONTRIBUTING.md — new ### Pull request descriptions subsection
    under Branching & Pull Requests documenting the same structure, plus two
    new merge rules: the verification gate and the WIP: prefix.

Documentation only. No fish, Python, or workflow files are touched.

How the convention was derived

Pulled all 122 prior PRs from the Gitea API
(/repos/rootiest/fish-config/pulls, state=all) and tallied the ##
headings across every body rather than sampling recent ones:

Section Count Era
## Summary 86 PRs 8-119, dominant throughout
## Manual Verification 63 PRs 10-89, the canonical form
## Manual Verification Checklist 12 PRs 90-113
## Test plan 11 PRs 8-9, then 114-119
## Verification 3 PRs 121-122

Checkbox items across all merged bodies run 585 - [x] to 12 - [ ],
confirming that verification is genuinely performed rather than aspirational.

Optional middle sections were taken from what actually recurs: Root cause, Why, How it works, Behavior, Changes, Docs, Notes,
Scope note, Opinionated guard (C1-C6), and ## ⚠️ Breaking Change
(paired with the ! title marker, as in #89). The Why not <alternative>?
heading pattern is called out too, since it shows up whenever a reviewer
would reasonably ask why the obvious approach was rejected.

Why the recent variants are mostly drift

The ## Summary + verification-section shape is stable across the whole
history; what moved around was the second heading's name, in three
different directions — which is what marks most of it as drift rather than
deliberate evolution:

  • PRs 90-113 renamed it to ## Manual Verification Checklist.
  • PRs 114-119 reverted to ## Test plan, the PR 8-9 form that had already
    been abandoned once.
  • PRs 121-122 shortened it to ## Verificationand that one was
    right
    , for the reason below. Those same PRs also dropped ## Summary
    entirely in favor of freeform openers (## Why the plural failed); that
    part is not carried forward.

Why ## Verification, not ## Manual Verification

"Manual" understated what the list actually holds. In practice most entries
are programmatic — fish tests/run-tests.fish, fish_indent,
docs/verify-manual.py, CI — and the old name left no clean home for them.
Dropping the word lets one list carry both kinds and gives the checkbox
state real meaning:

  • Checked — verified, whether it ran programmatically or the author
    performed it by hand.
  • Unchecked — an outstanding manual check the reviewer still has to
    perform. Left visible rather than quietly dropped.
  • The list is the merge gate. A PR isn't merged until every box is
    checked.

A direct consequence, also documented: only resolvable checks belong in
this section, since one nobody can run would block the PR indefinitely.
Genuinely unverifiable caveats go in ## Notes — which is how this PR's
own body is organized.

The WIP: prefix, and how it differs from the merge gate

A branch that still has code coming opens as WIP: type(scope): description.
Gitea recognizes the prefix, flags the PR as a draft, and refuses to merge
until it's removed.

The distinction from the verification gate is the whole point of
documenting it, so both files state it outright: WIP: means more
changes are coming — not "done but unverified."
A finished branch waiting
on manual checks is an ordinary PR whose ## Verification boxes aren't all
ticked yet, and that case is already covered by the merge gate. The two
signals are independent and can occur in any combination:

All boxes ticked Boxes outstanding
No prefix Ready to merge Normal PR, awaiting manual checks
WIP: Code still landing Code still landing, checks pending

Verified against this instance rather than assumed from Gitea's defaults —
see the checklist below.

Issue linking (Closes #N)

Not derived from precedent — a grep across all 122 prior PRs found zero
Closes / Fixes / Resolves references, since the repo hasn't used issue
tracking yet. Established deliberately so the first PR that does close an
issue has a rule to follow rather than inventing one.

  • Placement: a trailing line at the end of ## Summary, not the bottom
    of the body, because ## Verification is always last.
  • Fixes #N and Resolves #N are equivalent; Refs #N references a
    related issue without closing it.
  • The keyword must be repeated per issue — Closes #42, closes #43 closes
    both, while Closes #42, #43 closes only the first and leaves the second
    as a plain link.
  • Lives in the template's HTML comments rather than as a visible
    placeholder, so the common case (no associated issue) leaves no stray
    Closes # in the rendered body.

Notes

Three things that cannot be verified from this branch and so are
deliberately not in the checklist below, per the rule this PR adds:

  • Template pre-loading can only be confirmed after merge. Gitea
    resolves the PR template from the base branch, so the description box
    on this branch's compare page is still empty — confirmed by parsing the
    page, where both <textarea> elements render with no content. Once this
    lands on main, the next PR opened will pre-fill.
  • GitHub mirror behavior is untested. The mirror is push-only and PRs
    are opened on Gitea, so it's cosmetic. An earlier draft of
    CONTRIBUTING.md claimed the mirror pre-loads the template; that claim
    was removed rather than left unverified.
  • Closes #N auto-closing is assumed, not observed. Standard Gitea
    behavior, but there are no issues in this repo to test it against. Worth
    confirming the first time it's used for real.

Verification

  • fish tests/run-tests.fish passes — 220/220 syntax lint, 10/10
    functional checks
  • python3 docs/verify-manual.py passes — 74/74
  • git status clean; no generated docs artifacts disturbed
  • WIP: behavior confirmed empirically on this instance, not
    inferred from Gitea's defaults: retitling this PR to WIP: docs(...)
    flipped the API's draft to true and mergeable to false;
    removing the prefix restored draft: false / mergeable: true
  • Cited facts checked against the source: CONTRIBUTING.md §Testing
    gives the entry point as fish tests/run-tests.fish, and
    §Opinionated Components defines the tiers as C1-C6. Both were
    wrong in the first draft of the template
  • No line added by this PR exceeds 80 columns in either file; the six
    over-length lines remaining in CONTRIBUTING.md are pre-existing
    URLs and code blocks
  • Every Manual Verification reference renamed — grep -n 'Manual Verification' returns no hits in either file
  • This PR's own body follows the convention it establishes, including
    moving unverifiable items out of this section and into ## Notes
  • Read through the rendered template on the Files Changed tab and
    confirm the HTML-comment guidance reads clearly and matches how you
    actually want PRs written
  • Confirm the merge-gate rule is one you want binding on yourself as
    the sole merger — it means no PR merges with an open box
  • Confirm the WIP: / unchecked-box split matches your intent: the
    prefix for incoming code, unticked boxes for pending verification,
    never one standing in for the other
## Summary An agent working in this repo recently went looking for a PR template (`.github/pull_request_template.md`, `.github/PULL_REQUEST_TEMPLATE.md`, `PULL_REQUEST_TEMPLATE.md`, `docs/PULL_REQUEST_TEMPLATE.md`) and found nothing. The format was real but implicit — every contributor and agent had to reverse-engineer it from prior PRs, which is exactly how the drift described below crept in. This codifies it. - **New `.github/PULL_REQUEST_TEMPLATE.md`** — `## Summary`, optional `##` sections, then `## Verification` last. The guidance lives in HTML comments, so it vanishes on render and leaves no boilerplate residue in the posted body. Gitea falls back to `.github/` when `.gitea/` is absent and the GitHub mirror reads the same path, so one file covers both remotes — and it's a path the agent above was already checking. - **`CONTRIBUTING.md`** — new `### Pull request descriptions` subsection under Branching & Pull Requests documenting the same structure, plus two new merge rules: the verification gate and the `WIP:` prefix. Documentation only. No fish, Python, or workflow files are touched. ## How the convention was derived Pulled all 122 prior PRs from the Gitea API (`/repos/rootiest/fish-config/pulls`, `state=all`) and tallied the `##` headings across every body rather than sampling recent ones: | Section | Count | Era | | --- | --- | --- | | `## Summary` | 86 | PRs 8-119, dominant throughout | | `## Manual Verification` | 63 | PRs 10-89, the canonical form | | `## Manual Verification Checklist` | 12 | PRs 90-113 | | `## Test plan` | 11 | PRs 8-9, then 114-119 | | `## Verification` | 3 | PRs 121-122 | Checkbox items across all merged bodies run **585 `- [x]` to 12 `- [ ]`**, confirming that verification is genuinely performed rather than aspirational. Optional middle sections were taken from what actually recurs: `Root cause`, `Why`, `How it works`, `Behavior`, `Changes`, `Docs`, `Notes`, `Scope note`, `Opinionated guard (C1-C6)`, and `## ⚠️ Breaking Change` (paired with the `!` title marker, as in #89). The `Why not <alternative>?` heading pattern is called out too, since it shows up whenever a reviewer would reasonably ask why the obvious approach was rejected. ## Why the recent variants are mostly drift The `## Summary` + verification-section shape is stable across the whole history; what moved around was the second heading's name, in three different directions — which is what marks most of it as drift rather than deliberate evolution: - PRs 90-113 renamed it to `## Manual Verification Checklist`. - PRs 114-119 reverted to `## Test plan`, the PR 8-9 form that had already been abandoned once. - PRs 121-122 shortened it to `## Verification` — **and that one was right**, for the reason below. Those same PRs also dropped `## Summary` entirely in favor of freeform openers (`## Why the plural failed`); that part is not carried forward. ## Why `## Verification`, not `## Manual Verification` "Manual" understated what the list actually holds. In practice most entries are programmatic — `fish tests/run-tests.fish`, `fish_indent`, `docs/verify-manual.py`, CI — and the old name left no clean home for them. Dropping the word lets one list carry both kinds and gives the checkbox state real meaning: - **Checked** — verified, whether it ran programmatically or the author performed it by hand. - **Unchecked** — an outstanding manual check the reviewer still has to perform. Left visible rather than quietly dropped. - **The list is the merge gate.** A PR isn't merged until every box is checked. A direct consequence, also documented: only *resolvable* checks belong in this section, since one nobody can run would block the PR indefinitely. Genuinely unverifiable caveats go in `## Notes` — which is how this PR's own body is organized. ## The `WIP:` prefix, and how it differs from the merge gate A branch that still has code coming opens as `WIP: type(scope): description`. Gitea recognizes the prefix, flags the PR as a draft, and refuses to merge until it's removed. The distinction from the verification gate is the whole point of documenting it, so both files state it outright: **`WIP:` means more changes are coming — not "done but unverified."** A finished branch waiting on manual checks is an ordinary PR whose `## Verification` boxes aren't all ticked yet, and that case is already covered by the merge gate. The two signals are independent and can occur in any combination: | | All boxes ticked | Boxes outstanding | | --- | --- | --- | | **No prefix** | Ready to merge | Normal PR, awaiting manual checks | | **`WIP:`** | Code still landing | Code still landing, checks pending | Verified against this instance rather than assumed from Gitea's defaults — see the checklist below. ## Issue linking (`Closes #N`) Not derived from precedent — a grep across all 122 prior PRs found zero `Closes` / `Fixes` / `Resolves` references, since the repo hasn't used issue tracking yet. Established deliberately so the first PR that does close an issue has a rule to follow rather than inventing one. - **Placement: a trailing line at the end of `## Summary`**, not the bottom of the body, because `## Verification` is always last. - `Fixes #N` and `Resolves #N` are equivalent; `Refs #N` references a related issue without closing it. - The keyword must be repeated per issue — `Closes #42, closes #43` closes both, while `Closes #42, #43` closes only the first and leaves the second as a plain link. - Lives in the template's HTML comments rather than as a visible placeholder, so the common case (no associated issue) leaves no stray `Closes #` in the rendered body. ## Notes Three things that cannot be verified from this branch and so are deliberately *not* in the checklist below, per the rule this PR adds: - **Template pre-loading can only be confirmed after merge.** Gitea resolves the PR template from the *base* branch, so the description box on this branch's compare page is still empty — confirmed by parsing the page, where both `<textarea>` elements render with no content. Once this lands on `main`, the next PR opened will pre-fill. - **GitHub mirror behavior is untested.** The mirror is push-only and PRs are opened on Gitea, so it's cosmetic. An earlier draft of `CONTRIBUTING.md` claimed the mirror pre-loads the template; that claim was removed rather than left unverified. - **`Closes #N` auto-closing is assumed, not observed.** Standard Gitea behavior, but there are no issues in this repo to test it against. Worth confirming the first time it's used for real. ## Verification - [x] `fish tests/run-tests.fish` passes — 220/220 syntax lint, 10/10 functional checks - [x] `python3 docs/verify-manual.py` passes — 74/74 - [x] `git status` clean; no generated docs artifacts disturbed - [x] **`WIP:` behavior confirmed empirically on this instance**, not inferred from Gitea's defaults: retitling this PR to `WIP: docs(...)` flipped the API's `draft` to `true` and `mergeable` to `false`; removing the prefix restored `draft: false` / `mergeable: true` - [x] Cited facts checked against the source: `CONTRIBUTING.md` §Testing gives the entry point as `fish tests/run-tests.fish`, and §Opinionated Components defines the tiers as **C1-C6**. Both were wrong in the first draft of the template - [x] No line added by this PR exceeds 80 columns in either file; the six over-length lines remaining in `CONTRIBUTING.md` are pre-existing URLs and code blocks - [x] Every `Manual Verification` reference renamed — `grep -n 'Manual Verification'` returns no hits in either file - [x] This PR's own body follows the convention it establishes, including moving unverifiable items out of this section and into `## Notes` - [x] Read through the rendered template on the Files Changed tab and confirm the HTML-comment guidance reads clearly and matches how you actually want PRs written - [x] Confirm the merge-gate rule is one you want binding on yourself as the sole merger — it means no PR merges with an open box - [x] Confirm the `WIP:` / unchecked-box split matches your intent: the prefix for incoming code, unticked boxes for pending verification, never one standing in for the other
rootiest added 1 commit 2026-09-01 02:28:54 +00:00
Codifies the PR body format the repo has converged on across its first
122 pull requests, which until now lived only as an implicit pattern
agents and contributors had to reverse-engineer from prior PRs.

- **`.github/PULL_REQUEST_TEMPLATE.md`** — `## Summary`, optional `##`
  sections, then `## Manual Verification` as a checkbox list. Gitea falls
  back to `.github/` when `.gitea/` is absent, and the GitHub mirror reads
  the same path, so one file covers both.
- **`CONTRIBUTING.md`** — new "Pull request descriptions" subsection under
  Branching & Pull Requests, documenting the same three-part structure.

Section names were chosen by frequency across all 122 PRs: `## Summary`
(86) and `## Manual Verification` (63) are the clear majority. The later
`## Manual Verification Checklist` (12, PRs 90-113), `## Test plan` (11)
and `## Verification` (3, PRs 121-122) variants are drift away from that
baseline, not a newer standard, so the template restores the dominant
form. Checkboxes ship unchecked but are meant to be checked before the
PR opens, matching the 585-to-12 ratio of `[x]` to `[ ]` in merged PRs.
rootiest added 1 commit 2026-09-01 02:32:55 +00:00
The repo has no issue-tracking history yet — a grep across all 122 prior
PRs found zero `Closes #N` references — so this is forward-looking rather
than derived from precedent. Establishing it now means the first PR that
does close an issue has a rule to follow instead of inventing one.

- Placement is a trailing line at the end of `## Summary`, not the bottom
  of the body, because `## Manual Verification` is always last.
- `Fixes`/`Resolves` noted as equivalent; `Refs #N` for a related issue
  that should stay open.
- Guidance calls out that the keyword must be repeated per issue, since a
  bare `#43` after a comma links without closing.
- Lives in the template's HTML comments, so a PR with no associated issue
  leaves no stray `Closes #` behind.

Also drops an unverified claim that the GitHub mirror pre-loads the
template; PRs are opened on Gitea, and the mirror is push-only.
rootiest added 1 commit 2026-09-01 02:35:32 +00:00
The old name implied the list was only for by-hand steps, which sat badly
with the fact that most entries in practice are programmatic — the test
suite, `fish_indent`, `docs/verify-manual.py`, CI. Dropping "Manual" lets
one list carry both kinds, and gives the checkbox state real meaning:

- **Checked** — verified, whether it ran programmatically or the author
  performed it by hand.
- **Unchecked** — an outstanding manual check the reviewer still has to
  perform. Left visible rather than dropped.
- **The list is the merge gate.** A PR isn't merged until every box is
  checked; added as an explicit rule under Branching & Pull Requests
  alongside the other merge rules.

Because unchecked boxes now block the merge, the guidance also states that
only resolvable checks belong here — one nobody can run would block the PR
indefinitely. Genuinely unverifiable caveats and assumptions go in
`## Notes`, which already existed in the optional-sections list.

Incidentally realigns with PRs 121-122, which had already shortened the
heading to `## Verification`; that part of the recent drift was right.
rootiest marked the pull request as work in progress 2026-09-01 02:40:28 +00:00
rootiest marked the pull request as ready for review 2026-09-01 02:40:34 +00:00
rootiest added 1 commit 2026-09-01 02:41:15 +00:00
Gitea treats a `WIP:` title prefix as a draft marker and refuses to merge
while it's present. Verified empirically against this instance by
temporarily retitling PR #123: the API flipped `draft` to true and
`mergeable` to false, and both reverted when the prefix was removed.

The distinction from the verification merge gate is the point, so it's
stated explicitly in both files: `WIP:` means more code changes are
coming, NOT "finished but unverified". A complete branch with unticked
`## Verification` boxes is an ordinary PR — that case is already covered
by the merge gate and does not want the prefix. The two signals are
independent and can occur in any combination.

Documented as a merge rule under Branching & Pull Requests, next to the
verification gate, and in the template's title guidance where an author
picking a title will actually encounter it.
rootiest merged commit fcb9e0c468 into main 2026-09-01 02:42:38 +00:00
rootiest deleted branch docs/pr-template 2026-09-01 02:42:38 +00:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: rootiest/fish-config#123