diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index eca232fc..75fa3c19 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -31,6 +31,6 @@ If docs-site/ changed, also: bun run docs:test && bun run docs:build ## Checklist -- [ ] Added a changelog fragment, or this PR is labeled `skip-changelog` (see [`changes/README.md`](../changes/README.md)) +- [ ] Reconciled current-outcome changelog fragment(s), or this PR is labeled `skip-changelog` (see [`changes/README.md`](../changes/README.md)) - [ ] Docs updated in the same PR if behavior, defaults, flags, shortcuts, ports, or APIs changed - [ ] Relevant checks pass locally (typecheck, tests, build) diff --git a/AGENTS.md b/AGENTS.md index c9212a52..306d9e6f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -68,7 +68,7 @@ Start here, then leave this file. ## Release / PR Notes -- User-visible PRs need one fragment in `changes/*.md` — format and rules in [`changes/README.md`](./changes/README.md) (`type` + `area` keys required; apply the `skip-changelog` label to opt out) +- User-visible PRs need reconciled current-outcome fragment(s) in `changes/*.md` — format and rules in [`changes/README.md`](./changes/README.md) (`type` + `area` keys required; inspect existing same-PR fragments, then update/remove stale bullets or add only genuinely separate outcomes; apply the `skip-changelog` label to opt out) - User-visible docs changes get a `type: docs` fragment - CI enforces `bun run changelog:lint` and `bun run changelog:pr-check` - PR review helpers: diff --git a/changes/README.md b/changes/README.md index c22364a6..667c6845 100644 --- a/changes/README.md +++ b/changes/README.md @@ -31,6 +31,13 @@ Rules: - `README.md` is ignored by the generator - if a PR should not produce release notes, apply the `skip-changelog` label instead of adding a fragment +PR branch workflow: + +- Before adding a fragment or bullet, inspect the `changes/*.md` files already changed in the PR +- If the new work fixes, modifies, renames, or supersedes behavior introduced or referenced by that fragment, edit or remove the stale bullet instead of adding follow-up churn +- Add a new bullet only when it describes a truly separate user-visible outcome +- Multiple fragment files are allowed when one PR has genuinely separate release-note outcomes, but keep them minimized and current + How fragments turn into a release: - At release time, `bun run changelog:build` (and `bun run changelog:prerelease-notes`) pipes every pending fragment through `claude -p` to merge related items, drop noise, and rewrite into a clean user-facing release body. Write fragments as raw, informative notes — don't worry about polished prose, deduping across PRs, or line-by-line phrasing. The polish step handles all of that. diff --git a/changes/changelog-pr-fragment-consolidation.md b/changes/changelog-pr-fragment-consolidation.md new file mode 100644 index 00000000..be8dc1fc --- /dev/null +++ b/changes/changelog-pr-fragment-consolidation.md @@ -0,0 +1,4 @@ +type: changed +area: release + +- Changed PR changelog guidance to preserve multiple fragments for genuinely separate outcomes while directing contributors to update, remove, or merge same-PR fragment notes before adding follow-up churn. diff --git a/scripts/build-changelog.test.ts b/scripts/build-changelog.test.ts index cdc0dcc3..995ed157 100644 --- a/scripts/build-changelog.test.ts +++ b/scripts/build-changelog.test.ts @@ -488,7 +488,7 @@ test('verifyPullRequestChangelog requires fragments for user-facing changes and changedEntries: [{ path: 'src/main-entry.ts', status: 'M' }], changedLabels: [], }), - /requires a changelog fragment/, + /requires a reconciled changelog fragment/, ); assert.doesNotThrow(() => @@ -514,7 +514,7 @@ test('verifyPullRequestChangelog requires fragments for user-facing changes and ], changedLabels: [], }), - /requires a changelog fragment/, + /requires a reconciled changelog fragment/, ); assert.doesNotThrow(() => @@ -526,6 +526,27 @@ test('verifyPullRequestChangelog requires fragments for user-facing changes and changedLabels: [], }), ); + + assert.doesNotThrow(() => + verifyPullRequestChangelog({ + changedEntries: [ + { path: 'src/main-entry.ts', status: 'M' }, + { path: 'changes/001.md', status: 'M' }, + ], + changedLabels: [], + }), + ); + + assert.doesNotThrow(() => + verifyPullRequestChangelog({ + changedEntries: [ + { path: 'src/main-entry.ts', status: 'M' }, + { path: 'changes/001.md', status: 'A' }, + { path: 'changes/002.md', status: 'A' }, + ], + changedLabels: [], + }), + ); }); test('writePrereleaseNotesForVersion writes cumulative beta notes without mutating stable changelog artifacts', async () => { diff --git a/scripts/build-changelog.ts b/scripts/build-changelog.ts index f30935ea..97b5a246 100644 --- a/scripts/build-changelog.ts +++ b/scripts/build-changelog.ts @@ -661,14 +661,15 @@ export function verifyPullRequestChangelog(options: PullRequestChangelogOptions) return; } - const hasFragment = normalizedEntries.some( + const fragmentEntries = normalizedEntries.filter( (entry) => entry.status !== 'D' && isFragmentPath(entry.path), ); + const hasFragment = fragmentEntries.length > 0; const requiresFragment = normalizedEntries.some((entry) => !isIgnoredPullRequestPath(entry.path)); if (requiresFragment && !hasFragment) { throw new Error( - `This pull request changes release-relevant files and requires a changelog fragment under changes/ or the ${SKIP_CHANGELOG_LABEL} label.`, + `This pull request changes release-relevant files and requires a reconciled changelog fragment under changes/ or the ${SKIP_CHANGELOG_LABEL} label. Before adding a new fragment, update the existing PR fragment when the new work modifies, fixes, or supersedes behavior already described there.`, ); } }