- name
- issue-pr-conventions
- description
- The cross-project standard for issue and PR handling: conventional-commit prefixes on branches, issue titles, PR titles, and commit subjects; a label taxonomy capped at four axes (priority, area, status, size); and the claim protocol that pairs a `status/in-progress` label with a draft PR. Load before filing an issue, naming a branch, opening a PR, or applying labels in any repo.
# issue-pr-conventions
One standard for issue and PR handling, applied the same way in
every repo. This is the source of truth for what a name looks like,
which labels exist, and how an agent claims a piece of work.
Skills that reference this one implement the process (`triage` runs
the labeling pass, `draft-pr-first` runs the PR lifecycle); this
skill defines the vocabulary they use.
## When to apply
- Before filing an issue or naming a branch
- Before opening a PR
- Before applying any label
- When onboarding a repo that has no convention, or one that has
drifted from this one
## Naming: one prefix, four surfaces
The conventional-commit prefix goes on all four surfaces of a unit
of work. The prefix on each should agree with the others.
| surface | form | example |
|---|---|---|
| branch | `<type>/<short-slug>` | `fix/doctor-frontmatter-links` |
| issue title | `<type>: <description>` | `fix: doctor misses frontmatter links` |
| PR title | `<type>: <description> (closes #N)` | `fix: check frontmatter links (closes #355)` |
| commit subject | `<type>: <description>` | `fix: check frontmatter links` |
Types: `feat`, `fix`, `docs`, `chore`, `ci`, `refactor`, `test`,
`perf`, `build`. Append `!` for a breaking change
(`refactor!: drop --head`). An optional scope narrows the type
(`fix(ratelimiter): ...`); use it when a repo has stable
subsystems, skip it when it would be noise.
### The branch prefix matches the PR type
A `fix:` PR belongs on a `fix/` branch. Where they disagree, the
branch is what gets corrected, because the PR title is the one that
reaches the changelog. Rename before opening the PR:
```bash
git branch -m fix/<corrected-slug>
```
### Tool-owned names are exempt
Names generated by automation are not renamed to fit this scheme:
- `release-plz-<timestamp>`, `release-please--branches--main`
- `dependabot/<ecosystem>/<package>-<version>`
- Any branch created by a bot that also owns its own PR
Their PR titles usually already carry a prefix (`chore(deps): ...`).
Leave both alone.
## Labels: four axes, and no more
Four axes, and nothing outside them. The cap is the point: a queue
with six axes is not more searchable than one with four, it is just
slower to label and easier to leave half-applied.
| axis | labels | who applies it |
|---|---|---|
| priority | `p1`, `p2`, `p3` | triage |
| area | `area/<name>`, repo-defined, **cap 6** | triage |
| status | `status/in-progress`, `status/blocked`, `status/needs-review` | runner, dispatcher |
| size (PRs only) | `size/small`, `size/medium`, `size/large` | triage |
### There is no type axis
The conventional-commit prefix in the title already states the type.
A `feat` label on an issue titled `feat: ...` encodes the same fact
twice, and the two copies drift. So this taxonomy has no `feat`,
`fix`, `docs`, `chore`, `enhancement`, `bug`, or `documentation`
label. Filter by type with a title search instead:
```bash
gh issue list --search "fix: in:title"
```
This is the trade the prefix discipline buys: because every title
carries its type, no label has to.
### Priority
| priority | meaning | examples |
|---|---|---|
| `p1` | Blocking or high-impact | runtime failures, broken CI, missing behavior something depends on |
| `p2` | Standard queue | improvements, new capability, documentation gaps |
| `p3` | Nice-to-have | minor wording, future shapes, deferred ideas |
Most items are `p2`. If everything is `p1`, nothing is.
### Area
`area/*` names where the change lands, and each repo defines its
own set. **Six is the cap.** Past six, the axis stops being a
filter and becomes a second description of the title.
Examples of a set at the right grain:
- agent-tools: `area/skills`, `area/agents`, `area/ci`, `area/docs`
- a CLI tool: `area/cli`, `area/config`, `area/mcp`, `area/docs`, `area/ci`
When a repo already has per-subcommand or per-module labels, that
is a set to collapse, not a set to match. One `area/cli` beats nine
`cmd-*` labels; the specific command is in the title.
### Status
The only axis an agent writes during execution rather than during
triage.
- `status/in-progress` -- claimed, work is underway. See the claim
protocol below.
- `status/blocked` -- waiting on another issue or PR. Say which in
a comment.
- `status/needs-review` -- the runner will not auto-merge this;
a human decides.
### Size
PRs only, from the changed-file count: `size/small` (1-3),
`size/medium` (4-10), `size/large` (10+).
### The one exception
`field-feedback` marks an item an agent filed from a dispatch-time
observation. It is provenance, not type, and the title prefix
cannot express it. It stands outside the four axes and is the only
label that does.
### What does not get a label
- `good first issue` and `help wanted`. PR-farming accounts scrape
GitHub's global feed of newly labeled beginner issues and open
drive-by PRs within minutes. If a human applied one, leave it;
never add one.
- Anything restating the title prefix (see above).
- A second area when an item spans two. Pick the primary one and
note the other in a comment.
## Claiming work
Claiming is one step with two halves, and both halves are required.
The label says the work is taken; the draft PR says what is
planned. Neither alone tells the next agent enough to stay out of
the way.
```bash
# 1. Claim the issue
gh issue edit <N> --add-label status/in-progress
# 2. Branch and open the draft PR that states the plan
git checkout main && git pull --ff-only origin main
git checkout -b <type>/<short-slug>
git commit --allow-empty -m "chore: start work on #<N>"
git push -u origin <type>/<short-slug>
gh pr create --draft \
--title "<type>: <description> (closes #<N>)" \
--body "$(cat <<'EOF'
Closes #<N>.
## Plan
<What changes, which files, which gates verify it, and what is
explicitly out of scope. Shape per work-reports. Enough that another
agent reading only this knows whether to pick something else up.>
EOF
)"
```
### Check the claim before taking work
Before branching, check both halves. Either one means the work is
taken:
```bash
gh issue view <N> --json labels --jq '.labels[].name' | grep status/in-progress
gh pr list --search "<N>" --state open --json number,title,url
```
If an open PR already closes the issue, evaluate that PR against
the issue instead of opening a competing one.
### Releasing the claim
Merging the PR closes the issue, which retires the label with it.
No cleanup step. If work is abandoned without merging, remove
`status/in-progress` and say why in a comment, or the issue looks
taken forever.
## Bootstrapping a repo
`scripts/bootstrap-labels.sh` provisions the standard set into any
repo. It creates what is missing, leaves what already matches, and
reports what the repo carries beyond the standard. It never
deletes.
```bash
./scripts/bootstrap-labels.sh --repo <owner>/<name> # report only
./scripts/bootstrap-labels.sh --repo <owner>/<name> --apply # create labels
```
Area labels are repo-specific, so the script creates none. Add up
to six by hand after the bootstrap:
```bash
gh label create "area/cli" --repo <owner>/<name> --color 1d76db --description "Where the change lands"
```
## Adopting a repo that already has labels
Do not delete labels that are in use. Renaming preserves every
existing assignment; deleting loses it.
```bash
gh label edit "priority: high" --repo <owner>/<name> --name "p1"
gh label edit "cmd-new" --repo <owner>/<name> --name "area/cli"
```
Order of operations:
1. Rename the priority labels to `p1`/`p2`/`p3`.
2. Collapse the area candidates to six or fewer `area/*` labels.
3. Create the `status/*` labels; nothing to rename, they are new.
4. Leave the retired type labels in place on closed items. Stop
applying them to new ones. Delete only once the open queue is
clear of them.
The retired type labels are the slowest part, and they cost nothing
to leave sitting on history. Do not block adoption on them.
## Anti-patterns
- **A label that restates the title prefix.** `feat` on
`feat: add renumber command` is the same fact stored twice.
- **More than six `area/*` labels.** Per-subcommand or per-module
labels are the usual cause; collapse them.
- **A draft PR with no plan in the body.** The claim is half made:
another agent knows the work is taken but not what it covers, so
it cannot tell whether its own task overlaps.
- **`status/in-progress` without a draft PR, or a draft PR without
the label.** Both halves or neither.
- **Deleting labels during adoption.** Renaming keeps the
assignments; deleting drops them off every item that had one.
- **An unprefixed issue title.** The type axis was dropped on the
assumption the title carries it. An unprefixed title makes the
item invisible to type search.
- **Renaming a bot's branch** to fit the scheme. It will be
recreated on the next run.
## Related skills
- [`triage`](../triage/SKILL.md) -- the pass that applies these
labels to an open queue.
- [`github-authoring`](../github-authoring/SKILL.md) -- how the
issue and PR bodies these names sit on are written.
- [`work-reports`](../work-reports/SKILL.md) -- the shape of the plan
body that makes up half the claim, and of the report that closes
the unit.
- [`draft-pr-first`](../draft-pr-first/SKILL.md) -- the full PR
lifecycle the claim protocol opens.
- [`git-branch-pr-workflow`](../git-branch-pr-workflow/SKILL.md) --
the branch and merge mechanics.
- [`audit-remediate-handoff`](../audit-remediate-handoff/SKILL.md)
-- uses `status/in-progress` as the idempotency guard when firing
per-finding runners.
GitHub에서 보기