diff --git a/.github/PROJECT_FIELDS.md b/.github/PROJECT_FIELDS.md index 628ac2b..837322d 100644 --- a/.github/PROJECT_FIELDS.md +++ b/.github/PROJECT_FIELDS.md @@ -11,7 +11,7 @@ Every issue/PR attribute lives in **exactly one place**. Never dual-write the sa | Priority | `priority:*` **labels** | `p0` critical / `p1` milestone-blocking / `p2` important / `p3` polish; Project views filter by label (`label:"priority:p0"`), no Priority field | | Area | `area:*` **labels** | This library's own domains — see `.github/labels.yml` | | Workflow status | **Project `Status` field** | `Backlog` / `Ready` / `In Progress` / `In Review` / `Blocked` / `Done`. Status is fully automatic: `Backlog`, `Ready` and `Blocked` are synced before 00:00 and 12:00 Taipei time every day from `Start` and blocked-by by the project-status-sync workflow in the shared board's home repository; `In Progress`, `In Review` and `Done` are set by PR and issue events. People only maintain the issues' blocked-by relations and `Start` on the board. | -| Target version | **Milestone** | `vX.Y.Z` releases, `gov-*` process phases; **no milestone = backlog** | +| Delivery checkpoint | **Milestone** | `M1`–`M4`, grouping work by delivery checkpoint; **no milestone = backlog**; release versions are git tags cut by release-please, not issue metadata | | Estimate | **Project `Estimate` field** (number) | Rough estimate in person-days. Replaces Effort (S/M/L); the Effort field is retired. An issue with a large estimate is split by its implementer into sub-issues before work starts; the parent stays as the progress roll-up (Sub-issues progress) Once an issue is split into sub-issues, its Estimate is cleared; the numbers stay on the sub-issues and the parent shows Sub-issues progress. | | Schedule | **Project `Start` / `Target` fields** (date) | Scheduled start and finish. Target is the committed date; a milestone's due date is a different layer | | Checkpoint | **Project `Checkpoint` field** (iteration) | Internal checkpoint, one level finer than a milestone; milestones are unchanged | @@ -43,4 +43,4 @@ The issue form you pick still matters (it decides the body template); GitHub sim - Labels: `.github/labels.yml` (declarative source of truth; sync = re-run `scripts/bootstrap.sh`) - Project: the user-level board for the five implementation repositories, this one included. Issues are added by the board's built-in Auto-add workflow; `Status` is computed by a status-sync workflow outside this repository. Fields `Status`, `Estimate`, `Start`, `Target`, `Checkpoint` and the views are set up on the board — `docs/setup/project-views.md` -- Native issue types: set automatically by the issue forms in `.github/ISSUE_TEMPLATE/` +- Coarse type: the `type:bug` / `type:feature` labels (Type rows above); the issue forms in `.github/ISSUE_TEMPLATE/` cannot set a native issue type on a personal account diff --git a/AGENTS.md b/AGENTS.md index 3f914c5..37335a4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -8,7 +8,7 @@ Agent-specific entry files (`CLAUDE.md`, `GEMINI.md`, `.github/copilot-instructi 1. Work from an issue. If no issue exists for what you are about to do, create one first (see `skills/issue-writing/` — non-interactively, write the body in the form's shape so the labeler applies the labels). 2. Never commit directly to `main`. Branch as `/-` (e.g. `feat/42-label-sync`), open a PR. -3. Follow the metadata single-home contract in `.github/PROJECT_FIELDS.md` — every attribute (type, priority, area, status, version) lives in exactly one place. Never dual-write. +3. Follow the metadata single-home contract in `.github/PROJECT_FIELDS.md` — every attribute (type, priority, area, status, milestone) lives in exactly one place. Never dual-write. 4. Run `make verify` before opening or updating a PR. 5. Declare skipped validation levels in the PR body: `RISK: not run — `. Never skip silently. 6. Never commit `*.local.md` files, secrets, or `.env*` files (only `.env.example` is allowed). @@ -46,7 +46,7 @@ If this repository deliberately differs from the template it was created from, r ## Workflow -1. **Issue** — created via issue forms; native type (Bug/Feature/Task) is set by the form; labels for priority/area follow `.github/PROJECT_FIELDS.md`. +1. **Issue** — created via issue forms; the coarse type is carried by the `type:bug` / `type:feature` labels (neither means Task); labels for priority/area follow `.github/PROJECT_FIELDS.md`. 2. **Branch** — `/-`; the type is one of the Conventional Commit types this repository uses: `feat`, `fix`, `docs`, `chore`, `refactor`, `ci`, `test`, `perf` (this line is the one home for that list — `scripts/pr-lint.js` enforces it on the branch and its test asserts equality). 3. **Commits** — Conventional Commits, English, imperative (`feat: add label sync phase to bootstrap`). 4. **PR** — English title in Conventional Commit format; body follows the PR template: summary, linked issue (`Closes #N`), validation ladder checkboxes, `RISK:` lines, rollback notes. CI fails the PR when the branch name is malformed, or when GitHub reports that the PR does not close exactly one open issue in this repository — the branch's (`scripts/pr-lint.js` asks GitHub which issues the body closes, so a `Closes` line inside code does not count, and neither does an issue linked only in the sidebar — changing that link re-runs no check). @@ -54,7 +54,7 @@ If this repository deliberately differs from the template it was created from, r ## AI agent conventions -- **Starting work**: an agent starts on an issue only when a human assigns it or names it in the conversation. There is no self-service queue; Project status `Ready` means "scoped", not "claimable by agents". +- **Starting work**: an agent starts on an issue only when a human assigns it or names it in the conversation. There is no self-service queue. Project status `Ready` is computed by the status sync from `Start` and blocked-by; it does not mean an issue can be picked up. `In Progress` is set automatically when a pull request is linked to the issue. - **Provenance**: commit trailers (`Co-Authored-By`) and the PR author account are the audit trail. No provenance labels, no attribution line in the PR body. - **Capability boundaries**: agents may manage issues, labels, milestones, and Project items via `gh`; agents must NOT perform destructive operations (deleting Project fields, force-pushing, rewriting history, changing repo settings) without explicit human approval in the conversation. - **Session handoff**: long-running work may keep exactly one gitignored `HANDOVER.local.md` (hard cap ~150 lines, rewrite — don't append — at session end). Durable knowledge gets promoted to issues, ADRs, or skills, then deleted from the handoff file. See `skills/context-handoff/`. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0103377..330f61a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -39,11 +39,12 @@ skipped level. ## Issues -- Always use the issue forms — never open a blank issue. The form you pick - sets the native GitHub issue type (Bug, Feature, Task). +- Always use the issue forms — never open a blank issue. The coarse type is + carried by the `type:bug` / `type:feature` labels; neither means Task. After + opening a Bug or Feature issue, add the matching label by hand. - Priority and area labels are applied automatically by the labeler workflow; you don't set them by hand. -- Every piece of issue/PR metadata (type, priority, area, status, version) +- Every piece of issue/PR metadata (type, priority, area, status, milestone) lives in exactly one place. See `.github/PROJECT_FIELDS.md` for the single-home contract — never dual-write the same attribute in two places. @@ -80,10 +81,9 @@ Details: `skills/release-management/` (coming) and ## Milestones -- `vX.Y.Z` — a release commitment; issues attached to it are meant to ship in - that release. -- `gov-*` — process and governance work, not tied to a release. -- No milestone — backlog; not yet committed to a release. +- `M1`–`M4` — milestones group work by delivery checkpoint + (`.github/PROJECT_FIELDS.md`). +- No milestone — backlog. ## What not to commit diff --git a/docs/adr/ADR-0007-retire-agent-labels.md b/docs/adr/ADR-0007-retire-agent-labels.md index ffa8682..f084dab 100644 --- a/docs/adr/ADR-0007-retire-agent-labels.md +++ b/docs/adr/ADR-0007-retire-agent-labels.md @@ -1,6 +1,6 @@ # ADR-0007: Retire the `agent-ok` / `by-agent` label mechanism -- **Status**: Accepted +- **Status**: Accepted. Partially superseded 2026-10-08: `Ready` is computed by the status sync from `Start` and blocked-by, and `In Progress` is set when a pull request is linked (`docs/setup/project-views.md`) - **Date**: 2026-09-07 ## Context diff --git a/docs/adr/README.md b/docs/adr/README.md index 36afb5e..66c5da6 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -16,7 +16,7 @@ Routine choices (a library patch bump, a wording tweak) do not get ADRs. When in 1. Copy `template.md` to `ADR-NNNN-short-slug.md` (next free number). 2. Write it in the PR that implements (or proposes) the decision. -3. Status lifecycle: `Proposed` → `Accepted` → (later) `Superseded by ADR-XXXX` or `Deprecated`. Never delete an ADR — supersede it. +3. Status lifecycle: `Proposed` → `Accepted` → (later) `Superseded by ADR-XXXX` or `Deprecated`. When a later change replaces only part of an accepted decision, the status reads `Accepted. Partially superseded : `, and the body is left as it was. Never delete an ADR — supersede it. ## Index @@ -24,6 +24,6 @@ Routine choices (a library patch bump, a wording tweak) do not get ADRs. When in | --- | --- | --- | | [ADR-0001](ADR-0001-adopt-adr.md) | Adopt Architecture Decision Records | Accepted | | [ADR-0002](ADR-0002-release-flow.md) | Release flow: release-please with human-gated release PRs | Accepted | -| [ADR-0003](ADR-0003-metadata-single-home.md) | Metadata single-home policy | Accepted | -| [ADR-0007](ADR-0007-retire-agent-labels.md) | Retire the `agent-ok` / `by-agent` label mechanism | Accepted | +| [ADR-0003](ADR-0003-metadata-single-home.md) | Metadata single-home policy | Accepted. Partially superseded 2026-10-08 | +| [ADR-0007](ADR-0007-retire-agent-labels.md) | Retire the `agent-ok` / `by-agent` label mechanism | Accepted. Partially superseded 2026-10-08 | | [ADR-0008](ADR-0008-event-workflow-logic-in-scripts.md) | Event-driven workflow logic lives in `scripts/`, behind a thin `github-script` caller | Accepted | diff --git a/docs/adr/template.md b/docs/adr/template.md index 9408d81..924ed03 100644 --- a/docs/adr/template.md +++ b/docs/adr/template.md @@ -1,6 +1,6 @@ # ADR-NNNN: Title -- **Status**: Proposed | Accepted | Superseded by ADR-XXXX | Deprecated +- **Status**: Proposed | Accepted | Accepted. Partially superseded YYYY-MM-DD: … | Superseded by ADR-XXXX | Deprecated - **Date**: YYYY-MM-DD - **Issue**: #NNN (if any) diff --git a/docs/setup/bootstrap.md b/docs/setup/bootstrap.md index 1304fd6..bef0dad 100644 --- a/docs/setup/bootstrap.md +++ b/docs/setup/bootstrap.md @@ -56,6 +56,12 @@ On personal-account repos, see the "Personal accounts" note in ### 3. Milestone +> This repository's `scripts/bootstrap.sh` is not changed: phase 3 below still +> offers to create a `v0.1.0` milestone. This repository's milestones are +> `M1`–`M4`, so run bootstrap without `--yes` and answer no at the prompt: +> with `--yes` the phase creates `v0.1.0` without asking, and every such run +> creates it again. Close or delete a `v0.1.0` milestone the script created. + Creates a `v0.1.0` milestone ("First release") if one doesn't already exist (checked across both open and closed milestones, so a closed v0.1.0 is not recreated). diff --git a/docs/template-drift.md b/docs/template-drift.md index e534da6..13df38b 100644 --- a/docs/template-drift.md +++ b/docs/template-drift.md @@ -70,6 +70,9 @@ Files in the template that this repo does not have. | .github/PROJECT_FIELDS.md | Coarse Type row points at `type:bug`/`type:feature` labels, not native issue type; adds Owner/assignee row and rule 5 | This repo is a personal-account repo, so native issue types are unavailable | #18 (personal-account rewrite), bootstrap (Owner/assignee row and rule 5) | | docs/adr/ADR-0003-metadata-single-home.md (status line) | Marked partially superseded: the Project field set is defined in `.github/PROJECT_FIELDS.md` | The board's fields changed after the ADR was accepted | #67 | | .github/PROJECT_FIELDS.md (authority map, rule 1, "Where things are defined"), docs/setup/project-views.md | Effort row replaced by Estimate (number); Schedule (`Start`/`Target`) and Checkpoint rows added; Priority row notes that views filter by label; rule 1 keeps the planning fields on the Project board only. project-views.md lists `Status`, `Estimate`, `Start`, `Target`, `Checkpoint` and the Now / Roadmap / Checkpoint / Milestone / By area / Blocked views. Both say `Status` is set automatically (a status-sync workflow outside this repository, and PR and issue events) and that issues reach the board through its built-in Auto-add workflow (`is:issue is:open`), so this repository holds no board workflow or secret. PROJECT_FIELDS.md also says a parent issue's `Estimate` is cleared once it is split into sub-issues, and that the status sync runs before 00:00 and 12:00 Taipei time every day; project-views.md says pull requests are not on the board and that the board's Pull request linked to issue workflow moves an issue to `In Progress` | The Project board's planning fields changed; Effort is retired; the board's Status and enrollment are automated on the board | #65 | +| CONTRIBUTING.md, .github/PROJECT_FIELDS.md ("Where things are defined", Delivery checkpoint row), docs/adr/README.md (lifecycle, index) | The coarse type is described as the `type:bug` / `type:feature` labels; milestones are `M1`–`M4`; the ADR lifecycle allows `Accepted. Partially superseded : …`, and the index shows each ADR's status line | Native issue types cannot be applied on a personal account; the milestones group work by delivery checkpoint; part of an accepted decision can change without a new ADR | #69 | +| docs/setup/bootstrap.md (phase 3) | A note at the top of phase 3: the unchanged script still offers a `v0.1.0` milestone, so decline it | `scripts/bootstrap.sh` is not changed, while the milestones are `M1`–`M4` | #69 | +| AGENTS.md (Workflow, AI agent conventions), skills/agent-workflow/SKILL.md, skills/issue-writing/SKILL.md, skills/milestone-planning/SKILL.md, skills/release-management/SKILL.md (rule 5), skills/adr-writing/SKILL.md (rule 5), docs/adr/template.md (status line), docs/adr/ADR-0007-retire-agent-labels.md (status line) | The coarse type is carried by labels, added by hand for Bug and Feature; milestones are `M1`–`M4`, and release readiness is checked against open `priority:p0` / `priority:p1` issues instead of a milestone; `Ready` is computed by the status sync and does not mean an issue can be picked up; `In Progress` is set when a pull request is linked, not by hand; ADR-0007 is marked partially superseded; the ADR skill and template allow `Accepted. Partially superseded : …` | The board's `Status` is automated | #69 | | docs/setup/bootstrap.md (phase 4) | A note at the top of phase 4: the unchanged script still creates a per-repository board with `Effort`, so skip the phase and check the shared board instead; and the script does not create Project views (instead of saying the API cannot) | `scripts/bootstrap.sh` is not changed for the shared board, and its phase 4 still creates a per-repository board, while the board is a shared user-level board | #67 | | .github/labels.yml | `type:bug`/`type:feature` active (not commented out); `area:*` set is `update`/`health`/`docs`/`ci` instead of `docs`/`skills`/`ci`/`governance` | Personal-account Type fallback is in use; areas renamed to this library's own domains | #18 | | .github/workflows/ci.yml | `runs-on` reads `vars.CI_RUNNER_LABELS`; header comments trimmed to the shorter public-repo runner-cost rationale; PR-lint step comment moved next to the step | Split the single `RUNNER_LABELS` into one variable per workflow so jobs can run on different runners | #20 | @@ -84,7 +87,7 @@ Files in the template that this repo does not have. | docs/setup/bootstrap.md | Reduced to the phases this repo's `scripts/bootstrap.sh` actually has | Only the phase-5 merge-settings block was ever synced from upstream after bootstrap; the rest are this repo's original, older phases (no phase 6/9, phase 2 misreports on personal accounts) | #13, bootstrap | | docs/setup/licensing.md | Adds an "Adopter note" pointing out this repo predates upstream's phase 9 and has carried MIT attribution in `NOTICE` since bootstrap; "See also" links ADR-0004 by upstream URL | Explains why the phase-9 prompt described on the page never ran here | #18 | | docs/setup/runners.md | Documents four variables (`CI_RUNNER_LABELS`, `AUTOMATION_RUNNER_LABELS`, `MAINTENANCE_RUNNER_LABELS`, `RELEASE_RUNNER_LABELS`) with a table, instead of the template's single `RUNNER_LABELS`; "measured" failure example is attributed to upstream, not this repo; ADR-0005 referenced by upstream URL | Per-workflow runner-variable split | #18 | -| scripts/bootstrap.sh | ~700 lines shorter than the template; only the phase-5 merge-settings block matches upstream | Same as docs/setup/bootstrap.md — this repo's copy predates most later upstream phases | #18, #13, #3, bootstrap | +| scripts/bootstrap.sh | Older than the template; only the phase-5 merge-settings block matches upstream | Same as docs/setup/bootstrap.md — this repo's copy predates most later upstream phases | #18, #13, #3, bootstrap | | scripts/install-ci-tools.sh | `RUNNER_LABELS` comment updated to `*_RUNNER_LABELS`; markdownlint-cli2 pin kept current with upstream | Per-workflow runner-variable split | #20 | | skills/anti-patterns/SKILL.md | Inherited-licence-leak row says this repo chose Apache-2.0 and has carried template attribution in `NOTICE` since bootstrap, instead of describing the generic bootstrap-phase-9 mechanism | Documents this repo's actual licence outcome | #18 | | skills/github-actions-hygiene/SKILL.md | Rule 1 lists the four per-workflow runner variables instead of one `RUNNER_LABELS` | Per-workflow runner-variable split | #18 | diff --git a/skills/adr-writing/SKILL.md b/skills/adr-writing/SKILL.md index c043231..f1909d3 100644 --- a/skills/adr-writing/SKILL.md +++ b/skills/adr-writing/SKILL.md @@ -20,7 +20,7 @@ Decisions made in a PR thread or a chat session evaporate — the context window - **Decision** is written as a fact ("We use X for Y"), not a proposal or a debate summary. - **Consequences** names what becomes easier *and* what becomes harder — an ADR that only lists benefits is marketing, not a decision record. Include accepted costs explicitly. - **Alternatives considered** names each real alternative and *why not*, not just "we could have also...". -5. **Status lifecycle is one-directional**: `Proposed` → `Accepted` → (eventually) `Superseded by ADR-XXXX` or `Deprecated`. **Never edit an old ADR's Decision/Consequences to reflect a new choice, and never delete one.** Write a new ADR that supersedes it and update the old one's status line to point at the new number. History has to stay legible. +5. **Status lifecycle is one-directional**: `Proposed` → `Accepted` → (eventually) `Superseded by ADR-XXXX` or `Deprecated`. **Never edit an old ADR's Decision/Consequences to reflect a new choice, and never delete one.** When a new choice replaces the whole decision, write a new ADR that supersedes it and update the old one's status line to point at the new number. When a later change replaces only part of an accepted decision and that change is recorded elsewhere (a contract file or another ADR), set the status line to `Accepted. Partially superseded : `, leave the body as it was, and update the index (`docs/adr/README.md`). History has to stay legible. 6. **Keep it under a page.** If the Context section needs subheadings, the decision is probably two decisions — split it. 7. **Update `docs/adr/README.md`'s index table in the same PR.** An ADR not in the index is functionally undiscoverable. 8. **Agents: read the relevant ADRs before changing behavior adjacent to one.** An ADR is a "do not helpfully fix this" fence — if a decision looks locally suboptimal but an ADR explains why it's that way on purpose, that's a signal to stop and ask, not to improve it silently. @@ -48,6 +48,12 @@ Supersede an existing ADR instead of editing it: - **Status**: Superseded by ADR-0007 ``` +Mark a partial supersession instead when only part of the decision changed: + +```markdown +- **Status**: Accepted. Partially superseded YYYY-MM-DD: +``` + Add the new ADR to the index in the same PR: ```markdown diff --git a/skills/agent-workflow/SKILL.md b/skills/agent-workflow/SKILL.md index a2281bf..08785ac 100644 --- a/skills/agent-workflow/SKILL.md +++ b/skills/agent-workflow/SKILL.md @@ -17,11 +17,13 @@ blanket across the repository. 1. **Work only what a human handed you.** An issue is yours when a human assigns it or names it in the conversation. There is no self-service queue: - Project `Status` `Ready` means "scoped", not "claimable by agents". Anything - you were not given needs an explicit human request naming the work. -2. **Claim before working.** Claiming = set Status `In Progress` and comment on - the issue that you are taking it. No silent claims; two agents must never - discover mid-flight they grabbed the same issue. + Project `Status` `Ready` is computed by the status sync from `Start` and + blocked-by and does not mean an issue can be picked up. Anything you were + not given needs an explicit human request naming the work. +2. **Claim before working.** Claiming = comment on the issue that you are + taking it. `Status` moves to `In Progress` on its own when a pull request is + linked to the issue; do not set it by hand. No silent claims; two agents + must never discover mid-flight they grabbed the same issue. 3. **Refuse work that is not safe to hand off.** Ambiguous scope, untestable acceptance criteria, security-sensitive or destructive changes, or a judgment call the human has not yet made — push those back to the issue @@ -49,7 +51,7 @@ Start on an issue you were handed: ```bash gh issue view 42 --json title,body,labels,assignees # confirm scope and acceptance criteria gh issue comment 42 --body "Claiming — starting work on a feat/42 branch." -# set Project Status -> In Progress, branch feat/42-, open the PR +# branch feat/42-, open the PR with "Closes #42" (Status then moves to In Progress) ``` The honesty ladder in a PR body — claim only the rung you actually reached: @@ -72,7 +74,7 @@ then rebase and land PR B. Two agents force-pushing one branch is chaos. - Starting on an issue nobody handed you because it "looks easy" — the human's assignment is the gate, not your read of difficulty. -- Setting Status `In Progress` but never commenting, so a human cannot tell a +- Starting work without commenting on the issue, so a human cannot tell a live claim from a stale one. - Burying a skipped validation level instead of writing the `RISK:` line — a reviewer who trusts silent success merges a hole. diff --git a/skills/issue-writing/SKILL.md b/skills/issue-writing/SKILL.md index a493e45..2411a21 100644 --- a/skills/issue-writing/SKILL.md +++ b/skills/issue-writing/SKILL.md @@ -15,7 +15,10 @@ safe to hand to an AI agent without a live conversation. ## Rules 1. Always use an issue form (Bug / Feature / Task) — never open a blank issue. The - form you pick sets the native GitHub issue type; that type is authoritative. + form you pick sets the template; on this personal account the coarse type is + carried by the `type:bug` / `type:feature` labels (`.github/PROJECT_FIELDS.md`). + After opening an issue from the Bug or Feature form, add the matching label by + hand (`gh issue edit N --add-label type:bug`); a Task carries neither. Non-interactively (an agent, a script — anything that cannot answer the form's prompts), write the body in the shape the form would have rendered (see "Non-interactive: no form" below). The labeler reads that shape, not the form. @@ -78,17 +81,18 @@ The native issue type is separate. On an **organization** repository add account leave it out: `gh` **creates the issue, then fails** to set the type and exits 1 with `type "Task" not found; available types:` and no URL (GitHub reports no types there — see `.github/PROJECT_FIELDS.md`, "When native issue types are -unavailable"), so a script that retries on failure opens duplicates. There the default is no coarse Type at all; only if the -adopter has uncommented the `type:bug` / `type:feature` block in `labels.yml` and -re-run bootstrap do you apply one of those by hand — the one label family the -labeler leaves alone. +unavailable"), so a script that retries on failure opens duplicates. There the coarse type is the `type:bug` / `type:feature` label: add +`--label type:bug` or `--label type:feature` when the form you imitate is Bug or +Feature, and neither for a Task. The labeler leaves this label family alone, so the +label stays as set. Good acceptance criteria read like a test plan: ```markdown - [ ] `make verify` passes on a fresh clone -- [ ] `gh issue create` via the Feature form produces a native `Feature` type issue -- [ ] No `type:*`, `priority:*`, or `area:*` label is missing after form submission +- [ ] An issue opened from the Feature form carries the `type:feature` label +- [ ] No `priority:*` or `area:*` label is missing after form submission, and a Bug + or Feature issue carries `type:bug` / `type:feature` - [ ] An issue opened with `--body` in the form's shape carries `priority:*` and `area:*` after the labeler run ``` @@ -106,7 +110,7 @@ reviewer nothing to check against — rewrite before handing the issue to anyone right approach" — that is a human-judgment task; resolve it in the issue first. - Using an `epic:*` label or a "blocked by #12" sentence instead of native sub-issues and blocked-by links — invisible to automation and easy to let go stale. -- Opening a blank issue to "save time" — it skips native type assignment and the +- Opening a blank issue to "save time" — it skips the form's template and the priority/area fields entirely, pushing the cleanup onto triage later. - An agent opening issues with `--body` prose and no `### Priority` / `### Area` sections — every such issue is label-less unless `--label` supplied some, which diff --git a/skills/milestone-planning/SKILL.md b/skills/milestone-planning/SKILL.md index e3cbd6b..637486e 100644 --- a/skills/milestone-planning/SKILL.md +++ b/skills/milestone-planning/SKILL.md @@ -14,12 +14,13 @@ scope creep that turns a release date into a moving target. ## Rules -1. Milestone taxonomy is exactly two shapes: `vX.Y.Z` (a release commitment) and - `gov-*` (a process or governance phase not tied to a release). No milestone means - backlog — not yet committed to anything. +1. Milestones are exactly `M1`–`M4`, the delivery checkpoints, created by hand with + their due dates (`.github/PROJECT_FIELDS.md`). No milestone means backlog — not + yet committed to anything. Do not create other milestones: release versions + belong to release-please, not to milestones. 2. Assigning a milestone is a commitment, not a bucket for someday-maybe work. Only - attach an issue once the team actually intends to ship it in that version or - phase. Pull backlog items in when capacity opens up; don't pre-load a milestone + attach an issue once the team actually intends to finish it by that + checkpoint. Pull backlog items in when capacity opens up; don't pre-load a milestone with wishes. 3. When a milestone's date is fixed, the date is the constant and scope is the variable. If a milestone is at risk, cut issues out of it (back to backlog or a @@ -27,32 +28,28 @@ scope creep that turns a release date into a moving target. scope. 4. Refactors get their own milestone, later. Never cram a refactor into a release train that is already under deadline pressure — mixing "ship a deadline" with - "restructure the code" is how both go badly. Give the refactor a dedicated - `vX.Y.Z+1` milestone or a `gov-*` phase instead. + "restructure the code" is how both go badly. Give the refactor its own issue + in a later checkpoint, or keep it in backlog. 5. Define exit criteria before opening a milestone, not while closing it. Write down - what "done" means for the release stage — which issues, which validation levels, - what the release notes need to say — so scoping decisions during the milestone - have a fixed target to check against. -6. Keep at most two active milestones at a time (typically the current release and - the next). More than that fragments attention and makes "what ships next" an - open question instead of an answer. + what "done" means for the checkpoint — which issues, which validation levels — + so scoping decisions during the milestone have a fixed target to check against. +6. Plan against the current checkpoint and the next one. Later checkpoints hold + only issues already committed to them; anything else stays in backlog. ## How ```bash -# Create a milestone with an explicit due date and exit criteria in the description -gh api repos///milestones -f title="v1.2.0" -f state="open" \ - -f description="Exit: L0-L2 green, CHANGELOG reviewed, no open p0/p1" \ - -f due_on="2026-08-15T00:00:00Z" +# List the checkpoints and their due dates +gh api "repos///milestones?state=all" --jq '.[] | "\(.title) \(.due_on)"' # See what's committed to a milestone right now -gh issue list --repo / --milestone "v1.2.0" +gh issue list --repo / --milestone "M2" # Cut scope: move an at-risk issue back to backlog (clear the milestone) gh issue edit 87 --repo / --milestone "" # Move it to a later milestone instead of dropping it -gh issue edit 87 --repo / --milestone "v1.3.0" +gh issue edit 87 --repo / --milestone "M3" ``` ## Pitfalls @@ -66,8 +63,8 @@ gh issue edit 87 --repo / --milestone "v1.3.0" pressure — this is the specific failure this skill exists to prevent; split it out. - Opening a milestone with no written exit criteria, then arguing about "done" at the end — define it up front. -- Running three or more active milestones simultaneously — work spreads thin and - nothing has a clear "next" target. +- Creating a milestone outside `M1`–`M4` (a `vX.Y.Z` or `gov-*` one) — release + versions belong to release-please, and a stray milestone splits the plan. ## Related diff --git a/skills/release-management/SKILL.md b/skills/release-management/SKILL.md index 83e4d78..4bcaa22 100644 --- a/skills/release-management/SKILL.md +++ b/skills/release-management/SKILL.md @@ -25,16 +25,21 @@ A release must never ship from an unverified state, and version/changelog bookke 3. `git tag vX.Y.Z` on the merged commit. 4. `gh release create --generate-notes`, then add the hand-written TLDR. Trade-off: fewer moving parts and no bot to maintain, but CHANGELOG/version consistency now depends entirely on human discipline — this is the exact failure mode release-please exists to remove. -5. **Define release exit criteria per milestone before opening it**, not while triaging what's left. A typical criterion: zero open `priority:p0`/`priority:p1` issues attached to the milestone. Write the criteria into the milestone description. +5. **Release readiness is not a milestone query.** Releases are cut by release-please from `main` and are independent of the `M1`–`M4` milestones, which are delivery checkpoints. Before merging the release PR, read every open `priority:p0` / `priority:p1` issue: any that describes a defect still present in what `origin/main` would release blocks the release, whether the defect came in since the last release tag or was already in an earlier release. To judge what the release changes, read every commit since the last release tag (every commit, before the first release), not only the changelog, which leaves out hidden types (rule 2). 6. **Never cut a release with red or skipped CI.** If a validation level was skipped with a `RISK:` line during development, resolve or explicitly accept that risk before the release — don't let it ride silently into a tagged version. ## How -Check whether the milestone is release-ready: +Check for open blockers before merging the release PR: ```bash -gh issue list --milestone "v0.2.0" --label "priority:p0,priority:p1" --state open -# empty output = exit criterion met +gh issue list --label "priority:p0" --state open --limit 1000 +gh issue list --label "priority:p1" --state open --limit 1000 +git fetch origin main --tags +# before the first release there is no tag: then every commit up to origin/main is in range +last_tag="$(git describe --tags --abbrev=0 origin/main 2>/dev/null || true)" +git log --oneline "${last_tag:+$last_tag..}origin/main" +# a defect still present in origin/main blocks the release, whether or not it came in with these commits ``` Merge the release-please PR yourself, with squash — the only strategy this repo enables (bootstrap phase 5 sets `allow_merge_commit=false` and `allow_rebase_merge=false`). release-please does not merge its own PR; its next run on `push: main` detects the merged release PR and cuts the tag and GitHub Release: @@ -71,7 +76,7 @@ gh release edit v0.2.0 --notes "TLDR: ...\n\n$(gh release view v0.2.0 --json bod - Shipping release notes with only the generated Conventional Commit list and no TLDR — accurate for engineers, meaningless for the actual audience of a release announcement. - Running both release-please and the manual tag-first flow at once — pick one per repo; running both produces duplicate or conflicting tags. - Treating a `0.x` minor bump as automatically non-breaking because "it's not a major" — pre-1.0, minor can break; read the changelog before upgrading dependents. -- Opening a milestone with no stated exit criteria, then improvising "is this done?" at cut time — define it up front so the decision is a lookup, not a debate. +- Treating an empty milestone query as release readiness — milestones are delivery checkpoints, not releases, so a query on a version-named milestone finds nothing and proves nothing. - The release PR shows no checks and cannot merge. The `ci` run exists but is parked with conclusion `action_required` — GitHub holds workflow runs on PRs opened with the default `GITHUB_TOKEN` (its recursion guard) until someone approves them. Approve the parked run; closing and reopening the PR does not release it: ```bash