Skip to content

chore(release): assemble CHANGELOG.md from changelog.d fragments at version time - #2918

Closed
thymikee wants to merge 8 commits into
mainfrom
chore/2877-changelog-fragments
Closed

thymikee wants to merge 8 commits into
mainfrom
chore/2877-changelog-fragments

Conversation

@thymikee

@thymikee thymikee commented Sep 24, 2026 •

Copy link
Copy Markdown
Member

Summary

Closes #2877 (migration half; the guard workflow is the follow-up PR the issue orders second).

Adds a changelog.d/<issue>-<slug>.md fragment format so PRs stop editing CHANGELOG.md directly.
scripts/changelog-release.ts exposes a pure parseFragment/assembleChangelog core plus a thin
CLI, wired into package.json's version/release:prepare lifecycle: npm version folds pending
fragments into a new version section (kind-rank order, independent of file order) and deletes the
consumed fragments. Dotfiles in changelog.d/ are ignored. Six CI workflows gain changelog.d/**
to paths-ignore. Docs (docs/agents/pull-requests.md, CONTRIBUTING.md, changelog.d/README.md,
website/docs/docs/migrating-gestures.md) now describe the fragment workflow. One-time migration
folds ## Unreleased into ## 0.15.1 – 0.21.13, rebased onto current origin/main and re-derived
so the migrated section matches the tagged Unreleased block byte-for-byte (enforced by a new
self-test). Every bullet main added after v0.21.13 is a fragment (#2491, #2788, #2796, #2860, #2864,
#2915).

The CI guard refusing a direct CHANGELOG.md edit is intentionally not in this branch — a
same-PR guard would fail this PR's own migration commit. It ships as the follow-up PR.

Touched files: 22.

Validation

Tested at 9ec54d3b5a731747b54d3856e295fd1010151023 (rebased onto origin/main efce352, the tip
at push time).

Remaining risk: until the Guard PR merges, nothing yet blocks a direct CHANGELOG.md edit, so every
main CHANGELOG edit before merge needs one more rebase into a fragment. The byte-identity test
catches a missed one.

@github-actions

github-actions Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
PR Preview Action v1.8.1

QR code for preview link

🚀 View preview at
https://callstack.github.io/agent-device/pr-preview/pr-2918/

Built to branch gh-pages at 2026-09-24 16:27 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

@github-actions

github-actions Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

Size Report

Metric Base Current Diff
Installed (including dependencies) 4.81 MB 4.81 MB +161 B
Package (unpacked) 4.80 MB 4.80 MB +161 B
Package (download) 1.44 MB 1.44 MB +39 B

Startup median (7 runs, lower is better):

Scenario Base Current Diff
CLI --version 27.3 ms 27.3 ms +0.0 ms
CLI --help 80.9 ms 79.3 ms -1.6 ms

@thymikee

Copy link
Copy Markdown
Member Author

Reviewed at 20495c4. The fragment parser, the assembly order, and the version/release:prepare wiring look sound. I did not run the tests or a scratch npm version.

The branch conflicts with main, which is the next blocker. When you rebase, please move the CHANGELOG.md bullet that #2865 added into its own changelog.d fragment, not into the migrated 0.15.1 to 0.21.13 section; the byte-identity test enforces this.

One design question: I did not find a smaller design for the script itself, since changesets or towncrier would add a dependency to replace about 200 lines of pure string transforms. Is the tag-pinned byte-identity test the one part that should not stay, since it only guards a one-time migration? If it stays, the vitest.config.ts:173 comment should not say the test needs no subprocess, because it runs git show v0.21.13:CHANGELOG.md.

Two smaller points. readFragments should skip dotfiles, so a stray .DS_Store in changelog.d/ does not abort npm version. And changelog.d/2864-post-gesture-outcome.md restates the #2864 fix already in the migrated v0.21.13 section, so the next release notes would repeat that bullet.

The failing Android Smoke Tests job (the alert result row did not appear after five scrolls) looks unrelated: the diff touches no src/, packages/, or Android test code. Coverage and Repo Guards were still running at review time.

…lease

PRs no longer share one insertion point at the top of `## Unreleased`, which
was the repo's most frequent merge-conflict file. A PR instead adds
changelog.d/<slug>.md, and the npm version lifecycle script now folds every
fragment present into a new `## <version>` section (deterministic kind, then
fragment name, then position order) and deletes the consumed fragments.
release:prepare runs the assembler in --check mode so a release cannot ship
with unconsumed fragments.
Pins determinism, kind ordering, all four refusal cases, the no-fragments
pass-through, and a scratch-directory run of both CLI modes. Registers the
suite in the unit-core project and updates the release-script assertions in
npm-package-scripts.test.ts for the new version/release:prepare strings.
A PR that only adds a changelog fragment should skip CI/size/device lanes
the same way a root-doc-only PR does. Extends the existing pin test to
assert changelog.d/2799-example.md is ignored by all six workflows too.
docs/agents/pull-requests.md and CONTRIBUTING.md now say a user-visible
change adds a changelog.d/<slug>.md fragment and that PRs never edit
CHANGELOG.md directly; the gesture-deprecation policy page updates its one
reference to recording an entry under Unreleased.
…agment-name enumeration gap

CHANGELOG.md still carried "## Unreleased" while the version script now
refuses to assemble fragments into a changelog that has one, which would
block the first release after a fragment lands. No bullet was added after
the v0.21.13 tag, so the whole block becomes one historical section, and a
repo-guard test now pins the absence of the heading.

readFragments filtered to *.md while the repo-guard test and parseFragment
did not, so a fragment named without a .md extension slipped past --check
and the version script and stayed in changelog.d forever. readFragments now
enumerates every non-README entry and parseFragment rejects a missing .md
extension, so the same list drives assembly, --check and the test.
…ched-on-refusal CLI writes

Adds pinning tests for the leading-indented-line and unknown-kind
parseFragment branches, a CLI-level double-run refusal against an
already-released version heading, and a check that a refusal leaves
CHANGELOG.md and the pending fragment byte-identical on disk. Also
covers a fragment missing the .md extension being rejected loudly by
both --check and the default run, since readFragments and the CLI
share one enumeration. Trims the module header comment down to the
non-narrative essentials.
The rebase onto origin/main brought three CHANGELOG.md edits that landed
after the v0.21.13 tag this branch's migration used as its base: two new
bullets and a rewrite of an existing one. Replaying the Unreleased-heading
rename over that history folded all three under the released heading,
crediting changes to versions that never shipped them.

Move each post-tag change into its own changelog.d fragment instead, and
restore the rewritten bullet to its v0.21.13 wording under the released
heading. Add a repository-guard test that reads the tag's own Unreleased
block and asserts the migrated section is byte-identical to it, so a
future stale-base migration fails the suite instead of merging clean.
…ag change

readFragments ignores dotfiles, so a stray .DS_Store no longer aborts
npm version; every other entry still has to be a valid fragment.

The #2864 fragment restated the unsettled-surface fix that v0.21.13
already shipped. It now names only what changed after the tag: the
unsettledGesture field renamed to postGestureOutcome, the no-effect
kind, and disclosure on interactions.

The vitest lane comment now says the migration guard runs one git show.
@thymikee
thymikee force-pushed the chore/2877-changelog-fragments branch from 20495c4 to 9ec54d3 Compare September 24, 2026 16:26
@thymikee

Copy link
Copy Markdown
Member Author

Addressed the review at 20495c4. New head: 9ec54d3.

Rebase. Rebased onto origin/main (efce352, the tip at push time). CHANGELOG.md follows this PR's model: the migrated 0.15.1 – 0.21.13 section is still the tag's Unreleased block (the byte-identity test passes). Every bullet that main added after v0.21.13 is now a fragment:

readFragments and dotfiles. Done. Dotfiles are skipped, and every other entry must still be a valid fragment. There is a new CLI test (a .DS_Store beside a fragment gives assemble exit 0 and --check exit 0, and the dotfile stays). changelog.d/README.md says this too.

2864-post-gesture-outcome.md repeats a released bullet. Partly. #2864 (d852b79) is in no tag (git tag --contains is empty). v0.21.13 shipped the field as unsettledGesture (it is in packages/kernel/src/snapshot.ts at the tag), and #2864 renamed that public field after the release. So the next release needs a note, but the old fragment restated the whole fix. The fragment now has one Changed (mobile) bullet with the post-tag delta only: the unsettledGesture → postGestureOutcome rename, the no-effect kind, the re-capture inheritance, disclosure on interactions, and the hint on a failed interaction. If you want the rename in the Breaking group, change the kind.

Byte-identity test. Its value stops when this PR merges. After that, the section is history, and the follow-up guard PR makes direct CHANGELOG.md edits fail. Until then, it is the check that catches rebase drift like #2865's and #2915's. My suggestion: keep it through merge, and delete it in the guard PR. The vitest.config.ts comment now says the test runs one git show v0.21.13:CHANGELOG.md subprocess.

Android Smoke Tests. The failure (id="automation-alert-result" did not become visible after five scrolls) was on 20495c4. The diff has no src/, packages/, or Android test change, so the failure is not from this PR. The push starts a fresh run.

Validation at 9ec54d3. pnpm check:affected --run passed with exit 0. Focused tests pass. Before the #2915 rebase, two gate runs on the previous head stopped at integration-node: test/integration/cli-exit-flush.test.ts hit its 10 s timeout while the host load average was 220–540 from other sessions. That file passed alone, and the full gate on this head passed at load average 44. The PR does not touch that file or any file it imports.

@thymikee

Copy link
Copy Markdown
Member Author

Closing: main removed CHANGELOG.md in favor of generated release notes (#2922), so changelog fragments are no longer needed. #2877 is superseded by #2922.

@thymikee thymikee closed this Sep 24, 2026
@thymikee

Copy link
Copy Markdown
Member Author

The code at 9ec54d3 reads correctly: the conflict from the earlier pass is resolved, and the fragment assembly works at version time as written. I read the script, the package.json wiring, and the script-string pin test. I did not run the changelog-release tests or a scratch npm version.

Since this review, #2922 has merged and removed CHANGELOG.md in favor of generated release notes. That takes the other design, and this PR now conflicts with main. Should this PR be closed, or is there a part of the fragment flow you want to keep on top of #2922?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Release tooling: changelog.d fragments assembled by the npm version commit, plus a CI guard so no PR edits CHANGELOG.md

1 participant