Commit Graph

2 Commits

Author SHA1 Message Date
Diego Rodrigues de Sa e Souza
86b1cb84fe feat(release): reconcile-changelog tool + version-anchored fragment aggregation (#12987)
* feat(release): reconcile-changelog tool + version-anchored fragment aggregation

`npm run release:reconcile` (scripts/release/reconcile-changelog.mjs) turns the
v3.8.51 reconciliation pass (#12971) into a repeatable Phase 0a step:

- folds `changelog.d/` fragments under `## [<version>]` — never under the first
  matching heading — and credits each one with the PR of the commit that ADDED
  it (`git log --diff-filter=A`), because the filename prefix is not reliable
  (issue numbers, closed/recreated PRs, literal `#PR_NUMBER`); `--carrier N`
  marks a PR that only back-filled fragments for other people's PRs
- drops fragments whose text already ships in another version section
  (phantom fragments re-added by branches cut before the previous aggregation)
- covers a commit only when its OWN PR is a primary ref of a bullet (a
  `/pull/N` link, the trailing `(#N)`, or an explicit `(#N …)` group) so an
  incidental mention cannot hide a PR's own bullet; generates
  `**type(scope):** subject (#PR) — thanks @author` for the rest, rolls
  Dependabot bumps into one line, documents direct pushes by hash
- `--credit N=handle` carries the closed-PR / co-author / deleted-PR audit
- keeps pre-existing section bullets verbatim (changelog-integrity compares
  bullet lines), never touches `[Unreleased]` or older sections
- opens the section with "📊 Release by the numbers" + "🏆 Top 25" (mailmap +
  merged-PR login), the v3.8.50 format

`aggregate-changelog.mjs` gains the same anchoring: `insertBullets(text,
bullets, version)` searches the heading inside `## [version]` only (with
`[Unreleased]` still carrying `###  New Features`, every feature fragment was
landing there); `aggregate()` reads the version from package.json.

Tests: tests/unit/reconcile-changelog.test.ts (helpers + an end-to-end
reconcile fixture) and two new cases in tests/unit/changelog-fragments.test.ts.

* fix(release): escape every regex metacharacter before building the mention regex

CodeQL js/incomplete-sanitization on reconcile-changelog.mjs: the handle was only
escaping '-' before being interpolated into a RegExp. Use a full escapeRegExp
helper instead; handles are [A-Za-z0-9_-] in practice, so behaviour is unchanged
for real input and the addCredit tests still pass.
2026-09-08 09:11:13 -03:00
Diego Rodrigues de Sa e Souza
1ddb102a90 feat(release): changelog.d/ fragments — eliminate the CHANGELOG merge-storm cascade (#6783)
* feat(release): changelog.d/ fragments — kill the CHANGELOG-eat merge-storm cascade

Every PR used to edit the same top lines of CHANGELOG.md (its bullet), so in a
merge-storm each merge conflicted every sibling (CHANGELOG-eat / DIRTY cascade),
forcing a re-sync push + full CI re-run per PR per merge — O(N^2) CI runs.

A PR now adds ONE new file under changelog.d/{features|fixes|maintenance}/ with its
bullet; two PRs never touch the same file. scripts/release/aggregate-changelog.mjs
(npm run changelog:aggregate) folds fragments into the living section and deletes
them at release reconciliation. check:changelog-integrity (already wired in the
merge-integrity CI job — zero workflow change) now also validates fragment
well-formedness. This PR dogfoods the convention: its own entry is a fragment.

* chore(changelog): fragment filename matches PR number (#6783)
2026-07-10 07:22:49 -03:00