From aca01cf99076c94b6105c028d3def8004afee590 Mon Sep 17 00:00:00 2001 From: TzuHsuan <96853116+TzuH-Hsu@users.noreply.github.com> Date: Wed, 16 Sep 2026 16:19:23 +0800 Subject: [PATCH 1/4] docs: Release-As is a patch of the current version, an exception to rule 2; drop the refactor example Co-Authored-By: Claude Opus 5 (cherry picked from commit 5155034250c4d7fbad6df23c5f810d786c0a57f6) --- skills/branch-and-commit/SKILL.md | 11 +++++++---- skills/release-management/SKILL.md | 4 ++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/skills/branch-and-commit/SKILL.md b/skills/branch-and-commit/SKILL.md index 486a08e..74f7aae 100644 --- a/skills/branch-and-commit/SKILL.md +++ b/skills/branch-and-commit/SKILL.md @@ -32,10 +32,13 @@ code that closes it. Getting the format right keeps `git log`, the changelog, an | `test` | Test-only changes | No version bump | "No version bump" means release-please does not open a release PR for it at - all — it is a hidden type. A change users will notice that only fits a hidden - type (a new CI gate under `ci`, a behaviour-changing `refactor`) still has to - ship: put a `Release-As: X.Y.Z` footer in the PR body (the body becomes the - squash commit's message) and release-please cuts that version. + all — it is a hidden type, and it is also left out of the changelog. So first + ask whether the type is right: a change users will notice is usually a `feat` + or a `fix`, not a hidden type. When the type is genuinely right and a tag is + still needed (a new CI gate under `ci` that adopters must pick up), put + `Release-As: X.Y.Z` in the PR body — the body becomes the squash commit's + message — with X.Y.Z the current version plus one patch; anything larger means + the type was wrong (see `skills/release-management` rule 2). 4. Breaking changes append `!` after the type (`feat!:`) or add a `BREAKING CHANGE:` footer — either triggers a major bump. Use whichever is more visible for the diff --git a/skills/release-management/SKILL.md b/skills/release-management/SKILL.md index eb1fafe..7dff60d 100644 --- a/skills/release-management/SKILL.md +++ b/skills/release-management/SKILL.md @@ -17,7 +17,7 @@ A release must never ship from an unverified state, and version/changelog bookke 2. **A human merges the release PR — never auto-merge.** The branch ruleset requires the `ci` status check to be green before merge is even possible; this is the mitigation for the fact that both CI and release-please trigger on `push:main`, so an unverified commit could otherwise reach the release PR. 3. Merging the release PR cuts the git tag and GitHub Release automatically. 4. The maintainer then edits the published release notes to add a short, hand-written TLDR **above** the generated changelog. Release notes have non-technical readers — the generated bullet list alone is not the message. -2. **Version bump is derived, not chosen.** It comes from Conventional Commit types accumulated since the last release: `fix` → patch, `feat` → minor, any commit with `!` or a `BREAKING CHANGE:` footer → major. This is exactly why commit type discipline matters (see `CONTRIBUTING.md`). +2. **Version bump is derived, not chosen.** It comes from Conventional Commit types accumulated since the last release: `fix` → patch, `feat` → minor, any commit with `!` or a `BREAKING CHANGE:` footer → major. This is exactly why commit type discipline matters (see `CONTRIBUTING.md`). One exception: a change that adopters must pick up but that genuinely is a hidden type (a new CI gate under `ci`) produces no release on its own; then a `Release-As: X.Y.Z` footer in the PR body cuts one, and X.Y.Z is always the current version plus one patch — if you want more than a patch, the type was wrong, and a hidden type also keeps the change out of the changelog. 3. **Pre-1.0 semantics**: a minor bump may contain breaking changes. Don't assume `0.x` minor bumps are safe to blindly consume — read the changelog. 4. **Documented alternative — manual tag-first.** Use this instead of release-please when release cadence is near-zero or the team wants zero release automation: 1. Decide the version by hand. @@ -64,7 +64,7 @@ gh release edit v0.2.0 --notes "TLDR: ...\n\n$(gh release view v0.2.0 --json bod ## Pitfalls -- Shipping a release-worthy change under a hidden type (`ci`, `chore`, `docs`, `refactor`, `test`) and waiting for a release PR that never comes — release-please logs `No user facing commits found … skipping`. Either the type was wrong (`feat`/`fix`), or add `Release-As: X.Y.Z` to the PR body so the squash commit carries it (see `skills/branch-and-commit` rule 3). +- Shipping a release-worthy change under a hidden type (`ci`, `chore`, `docs`, `refactor`, `test`) and waiting for a release PR that never comes — release-please logs `No user facing commits found … skipping`. Usually the type was wrong (`feat`/`fix`); when it was right, the `Release-As` exception in rule 2 applies. - Publishing or delivering a repository whose `LICENSE` still names the upstream template author — `head -3 LICENSE` before anything leaves the building. For client work an inherited MIT grants the client, and everyone else, far more than the commission contract does, and it cannot be withdrawn (`docs/setup/licensing.md`). - Enabling auto-merge on the release-please PR "to save a click" — this defeats the entire point of the human gate described in ADR-0002; the `push:main` race is only closed because a human reviews before merge. From 9b0caf82d293fad6dafc17b9baeed6938597db35 Mon Sep 17 00:00:00 2001 From: TzuHsuan <96853116+TzuH-Hsu@users.noreply.github.com> Date: Mon, 21 Sep 2026 12:34:34 +0800 Subject: [PATCH 2/4] docs: the commit carrying Release-As is rendered in the changelog whatever its type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hidden-type commits are omitted from the generated changelog, but the one that carries the `Release-As` footer is not: conventional-changelog's conventionalcommits writer un-discards it ("Add an entry in the CHANGELOG if special Release-As footer is used", writer-opts.js). v0.5.2 shows the consequence — the changelog lists the docs follow-up that carried the footer and not the CI gate it released. Say so, and say the footer goes on the PR that is the change. Co-Authored-By: Claude Opus 5 --- skills/branch-and-commit/SKILL.md | 18 +++++++++++------- skills/release-management/SKILL.md | 2 +- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/skills/branch-and-commit/SKILL.md b/skills/branch-and-commit/SKILL.md index 74f7aae..1b9aed9 100644 --- a/skills/branch-and-commit/SKILL.md +++ b/skills/branch-and-commit/SKILL.md @@ -32,13 +32,17 @@ code that closes it. Getting the format right keeps `git log`, the changelog, an | `test` | Test-only changes | No version bump | "No version bump" means release-please does not open a release PR for it at - all — it is a hidden type, and it is also left out of the changelog. So first - ask whether the type is right: a change users will notice is usually a `feat` - or a `fix`, not a hidden type. When the type is genuinely right and a tag is - still needed (a new CI gate under `ci` that adopters must pick up), put - `Release-As: X.Y.Z` in the PR body — the body becomes the squash commit's - message — with X.Y.Z the current version plus one patch; anything larger means - the type was wrong (see `skills/release-management` rule 2). + all — it is a hidden type, and hidden-type commits are left out of the + changelog. So first ask whether the type is right: a change users will notice + is usually a `feat` or a `fix`, not a hidden type. When the type is genuinely + right and a tag is still needed (a new CI gate under `ci` that adopters must + pick up), put `Release-As: X.Y.Z` in the body of the PR that *is* the change — + the body becomes the squash commit's message — with X.Y.Z the current version + plus one patch; anything larger means the type was wrong (see + `skills/release-management` rule 2). The one commit carrying `Release-As` is + rendered in the changelog whatever its type, while the other hidden-type + commits in the range stay out — so a footer on a follow-up PR makes the + changelog name the follow-up instead of the change. 4. Breaking changes append `!` after the type (`feat!:`) or add a `BREAKING CHANGE:` footer — either triggers a major bump. Use whichever is more visible for the diff --git a/skills/release-management/SKILL.md b/skills/release-management/SKILL.md index 7dff60d..b33cbac 100644 --- a/skills/release-management/SKILL.md +++ b/skills/release-management/SKILL.md @@ -17,7 +17,7 @@ A release must never ship from an unverified state, and version/changelog bookke 2. **A human merges the release PR — never auto-merge.** The branch ruleset requires the `ci` status check to be green before merge is even possible; this is the mitigation for the fact that both CI and release-please trigger on `push:main`, so an unverified commit could otherwise reach the release PR. 3. Merging the release PR cuts the git tag and GitHub Release automatically. 4. The maintainer then edits the published release notes to add a short, hand-written TLDR **above** the generated changelog. Release notes have non-technical readers — the generated bullet list alone is not the message. -2. **Version bump is derived, not chosen.** It comes from Conventional Commit types accumulated since the last release: `fix` → patch, `feat` → minor, any commit with `!` or a `BREAKING CHANGE:` footer → major. This is exactly why commit type discipline matters (see `CONTRIBUTING.md`). One exception: a change that adopters must pick up but that genuinely is a hidden type (a new CI gate under `ci`) produces no release on its own; then a `Release-As: X.Y.Z` footer in the PR body cuts one, and X.Y.Z is always the current version plus one patch — if you want more than a patch, the type was wrong, and a hidden type also keeps the change out of the changelog. +2. **Version bump is derived, not chosen.** It comes from Conventional Commit types accumulated since the last release: `fix` → patch, `feat` → minor, any commit with `!` or a `BREAKING CHANGE:` footer → major. This is exactly why commit type discipline matters (see `CONTRIBUTING.md`). One exception: a change that adopters must pick up but that genuinely is a hidden type (a new CI gate under `ci`) produces no release on its own; then a `Release-As: X.Y.Z` footer in the PR body cuts one, and X.Y.Z is always the current version plus one patch — if you want more than a patch, the type was wrong. The footer goes on the PR that is the change, not on a follow-up: hidden-type commits are omitted from the generated changelog except the one that carries `Release-As`, which release-please always renders under its type's section, so a footer on a follow-up makes the changelog name the follow-up and omit the change. 3. **Pre-1.0 semantics**: a minor bump may contain breaking changes. Don't assume `0.x` minor bumps are safe to blindly consume — read the changelog. 4. **Documented alternative — manual tag-first.** Use this instead of release-please when release cadence is near-zero or the team wants zero release automation: 1. Decide the version by hand. From 0a058eafa941a60f5b347b4605472171d496d06a Mon Sep 17 00:00:00 2001 From: TzuHsuan <96853116+TzuH-Hsu@users.noreply.github.com> Date: Mon, 21 Sep 2026 12:38:11 +0800 Subject: [PATCH 3/4] docs: Release-As only when no release is pending; the footer overrides the computed version for the whole range Co-Authored-By: Claude Opus 5 --- skills/branch-and-commit/SKILL.md | 6 +++++- skills/release-management/SKILL.md | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/skills/branch-and-commit/SKILL.md b/skills/branch-and-commit/SKILL.md index 1b9aed9..c7159d3 100644 --- a/skills/branch-and-commit/SKILL.md +++ b/skills/branch-and-commit/SKILL.md @@ -39,7 +39,11 @@ code that closes it. Getting the format right keeps `git log`, the changelog, an pick up), put `Release-As: X.Y.Z` in the body of the PR that *is* the change — the body becomes the squash commit's message — with X.Y.Z the current version plus one patch; anything larger means the type was wrong (see - `skills/release-management` rule 2). The one commit carrying `Release-As` is + `skills/release-management` rule 2). Only do this when no release is already + pending: the footer overrides the computed version for every commit since the + last tag, so with a release PR open (an unreleased `feat` or `fix`) a patch + footer would under-version it — leave the footer out and let the change ride + along. The one commit carrying `Release-As` is rendered in the changelog whatever its type, while the other hidden-type commits in the range stay out — so a footer on a follow-up PR makes the changelog name the follow-up instead of the change. diff --git a/skills/release-management/SKILL.md b/skills/release-management/SKILL.md index b33cbac..2680c53 100644 --- a/skills/release-management/SKILL.md +++ b/skills/release-management/SKILL.md @@ -17,7 +17,7 @@ A release must never ship from an unverified state, and version/changelog bookke 2. **A human merges the release PR — never auto-merge.** The branch ruleset requires the `ci` status check to be green before merge is even possible; this is the mitigation for the fact that both CI and release-please trigger on `push:main`, so an unverified commit could otherwise reach the release PR. 3. Merging the release PR cuts the git tag and GitHub Release automatically. 4. The maintainer then edits the published release notes to add a short, hand-written TLDR **above** the generated changelog. Release notes have non-technical readers — the generated bullet list alone is not the message. -2. **Version bump is derived, not chosen.** It comes from Conventional Commit types accumulated since the last release: `fix` → patch, `feat` → minor, any commit with `!` or a `BREAKING CHANGE:` footer → major. This is exactly why commit type discipline matters (see `CONTRIBUTING.md`). One exception: a change that adopters must pick up but that genuinely is a hidden type (a new CI gate under `ci`) produces no release on its own; then a `Release-As: X.Y.Z` footer in the PR body cuts one, and X.Y.Z is always the current version plus one patch — if you want more than a patch, the type was wrong. The footer goes on the PR that is the change, not on a follow-up: hidden-type commits are omitted from the generated changelog except the one that carries `Release-As`, which release-please always renders under its type's section, so a footer on a follow-up makes the changelog name the follow-up and omit the change. +2. **Version bump is derived, not chosen.** It comes from Conventional Commit types accumulated since the last release: `fix` → patch, `feat` → minor, any commit with `!` or a `BREAKING CHANGE:` footer → major. This is exactly why commit type discipline matters (see `CONTRIBUTING.md`). One exception: a change that adopters must pick up but that genuinely is a hidden type (a new CI gate under `ci`) produces no release on its own; then a `Release-As: X.Y.Z` footer in the PR body cuts one, and X.Y.Z is always the current version plus one patch — if you want more than a patch, the type was wrong. Use it only when nothing is already waiting: the footer overrides release-please's computed version for the whole range since the last tag, so with a release PR already open (an unreleased `feat` or `fix`) a patch footer would under-version that release — merge it first, or leave the footer out and let the hidden-type change ride along. The footer goes on the PR that is the change, not on a follow-up: hidden-type commits are omitted from the generated changelog except the one that carries `Release-As`, which release-please always renders under its type's section, so a footer on a follow-up makes the changelog name the follow-up and omit the change. 3. **Pre-1.0 semantics**: a minor bump may contain breaking changes. Don't assume `0.x` minor bumps are safe to blindly consume — read the changelog. 4. **Documented alternative — manual tag-first.** Use this instead of release-please when release cadence is near-zero or the team wants zero release automation: 1. Decide the version by hand. From 21df1790e069ec7bb818ab46e208d72e83f0ce97 Mon Sep 17 00:00:00 2001 From: TzuHsuan <96853116+TzuH-Hsu@users.noreply.github.com> Date: Mon, 21 Sep 2026 12:44:25 +0800 Subject: [PATCH 4/4] docs: keep the Release-As rule in release-management only; add the first-release clause branch-and-commit rule 3 now points at release-management rule 2 instead of restating it (docs-hygiene rule 2, one home per fact). Rule 2 gains the first-release case: there is no current version before the first release, which `initial-version` numbers. Co-Authored-By: Claude Opus 5 --- skills/branch-and-commit/SKILL.md | 20 ++++++-------------- skills/release-management/SKILL.md | 2 +- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/skills/branch-and-commit/SKILL.md b/skills/branch-and-commit/SKILL.md index c7159d3..053e991 100644 --- a/skills/branch-and-commit/SKILL.md +++ b/skills/branch-and-commit/SKILL.md @@ -33,20 +33,12 @@ code that closes it. Getting the format right keeps `git log`, the changelog, an "No version bump" means release-please does not open a release PR for it at all — it is a hidden type, and hidden-type commits are left out of the - changelog. So first ask whether the type is right: a change users will notice - is usually a `feat` or a `fix`, not a hidden type. When the type is genuinely - right and a tag is still needed (a new CI gate under `ci` that adopters must - pick up), put `Release-As: X.Y.Z` in the body of the PR that *is* the change — - the body becomes the squash commit's message — with X.Y.Z the current version - plus one patch; anything larger means the type was wrong (see - `skills/release-management` rule 2). Only do this when no release is already - pending: the footer overrides the computed version for every commit since the - last tag, so with a release PR open (an unreleased `feat` or `fix`) a patch - footer would under-version it — leave the footer out and let the change ride - along. The one commit carrying `Release-As` is - rendered in the changelog whatever its type, while the other hidden-type - commits in the range stay out — so a footer on a follow-up PR makes the - changelog name the follow-up instead of the change. + changelog as well (one exception, below). So first ask whether the type is + right: a change users will notice is usually a `feat` or a `fix`, not a hidden + type. When the type is genuinely right and a tag is still needed anyway, a + `Release-As` footer in the PR body can cut one; the recipe, its limits and the + changelog exception live in `skills/release-management` rule 2 and are not + repeated here. 4. Breaking changes append `!` after the type (`feat!:`) or add a `BREAKING CHANGE:` footer — either triggers a major bump. Use whichever is more visible for the diff --git a/skills/release-management/SKILL.md b/skills/release-management/SKILL.md index 2680c53..679ca0e 100644 --- a/skills/release-management/SKILL.md +++ b/skills/release-management/SKILL.md @@ -17,7 +17,7 @@ A release must never ship from an unverified state, and version/changelog bookke 2. **A human merges the release PR — never auto-merge.** The branch ruleset requires the `ci` status check to be green before merge is even possible; this is the mitigation for the fact that both CI and release-please trigger on `push:main`, so an unverified commit could otherwise reach the release PR. 3. Merging the release PR cuts the git tag and GitHub Release automatically. 4. The maintainer then edits the published release notes to add a short, hand-written TLDR **above** the generated changelog. Release notes have non-technical readers — the generated bullet list alone is not the message. -2. **Version bump is derived, not chosen.** It comes from Conventional Commit types accumulated since the last release: `fix` → patch, `feat` → minor, any commit with `!` or a `BREAKING CHANGE:` footer → major. This is exactly why commit type discipline matters (see `CONTRIBUTING.md`). One exception: a change that adopters must pick up but that genuinely is a hidden type (a new CI gate under `ci`) produces no release on its own; then a `Release-As: X.Y.Z` footer in the PR body cuts one, and X.Y.Z is always the current version plus one patch — if you want more than a patch, the type was wrong. Use it only when nothing is already waiting: the footer overrides release-please's computed version for the whole range since the last tag, so with a release PR already open (an unreleased `feat` or `fix`) a patch footer would under-version that release — merge it first, or leave the footer out and let the hidden-type change ride along. The footer goes on the PR that is the change, not on a follow-up: hidden-type commits are omitted from the generated changelog except the one that carries `Release-As`, which release-please always renders under its type's section, so a footer on a follow-up makes the changelog name the follow-up and omit the change. +2. **Version bump is derived, not chosen.** It comes from Conventional Commit types accumulated since the last release: `fix` → patch, `feat` → minor, any commit with `!` or a `BREAKING CHANGE:` footer → major. This is exactly why commit type discipline matters (see `CONTRIBUTING.md`). One exception: a change that adopters must pick up but that genuinely is a hidden type (a new CI gate under `ci`) produces no release on its own; then a `Release-As: X.Y.Z` footer in the PR body cuts one, and X.Y.Z is always the current version plus one patch — if you want more than a patch, the type was wrong. Before the first release there is no current version: that release is numbered by `initial-version` in `release-please-config.json` (see `docs/setup/bootstrap.md`), so a footer there names that version. Use the footer only when nothing is already waiting: the footer overrides release-please's computed version for the whole range since the last tag, so with a release PR already open (an unreleased `feat` or `fix`) a patch footer would under-version that release — merge it first, or leave the footer out and let the hidden-type change ride along. The footer goes on the PR that is the change, not on a follow-up: hidden-type commits are omitted from the generated changelog except the one that carries `Release-As`, which release-please always renders under its type's section, so a footer on a follow-up makes the changelog name the follow-up and omit the change. 3. **Pre-1.0 semantics**: a minor bump may contain breaking changes. Don't assume `0.x` minor bumps are safe to blindly consume — read the changelog. 4. **Documented alternative — manual tag-first.** Use this instead of release-please when release cadence is near-zero or the team wants zero release automation: 1. Decide the version by hand.