Migrate changelog from single file to directory structure - #1552
Conversation
Every PR used to add its entry at the top of `## Unreleased` in a single 2000-line file, so any two concurrent PRs conflicted on the same lines. The changelog is now `changelog/`: one file per released version, plus `changelog/unreleased/<PR>.md` for entries not released yet. PR numbers are unique, so concurrent PRs can never conflict. Entry files hold list items only — the version or PR number is the file name — and stay in the Markdown subset the website will render with a self-hosted parser. Released sections are retrofitted verbatim, one file per version. `CHANGELOG.md` is a short stub pointing at the directory. AGENTS.md §8.3 (entry workflow), §8.4 (release step), §4 (documentation table), and CONTRIBUTING.md describe the new layout. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WQFBgRCxeiy5gw1is28t2H
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
functionalscript | 631223d | Commit Preview URL Branch Preview URL |
Aug 14 2026, 03:18 PM |
o2alexanderfedin
left a comment
There was a problem hiding this comment.
Approving. Baseline origin/main = f04c670bfe3283dc0ea579aeae37b3f17667feb9; tested head 631223dd. This is the implementation of #1551, so I re-derived the mechanism rather than reading the migration.
Content preservation — mechanical, not by eye
I reconstructed the changelog from the new tree and compared it to main's CHANGELOG.md programmatically.
Released sections: byte-identical, all 96.
old released sections: 96, new version files: 96
no file for old section: []
file with no old section: []
released sections NOT byte-identical (trimmed): 0
non-list top-level lines in released bodies: 0
Every ## X.Y.Z body in main equals changelog/X.Y.Z.md exactly after trimming the surrounding blank lines — same characters, same entries, same order. Nothing reworded, merged, dropped, or reordered in released history. 0.1.608 was the one empty section in main and is the one empty file here, which is exactly the "empty <version>.md retrofits a released section that recorded no entries" case the new README documents.
Whole corpus conserved:
total entries in old CHANGELOG.md: 461
total entries in new corpus: 461
The ordering deviation, quantified
changelog/unreleased/ holds 25 files carrying 30 entries. Concatenating them in descending PR-number order, exactly as changelog/README.md and §8.4 now specify, gives:
unreleased: old entries 30, new entries 30, files 25
entries only in old: 0; only in new: 0
entries at a different index: 15 of 30
So the entry set is identical — the reorder is the only change — and 15 of 30 entries move. The cause is that main's ## Unreleased is in merge order, not PR-number order; the old sequence has 6 adjacent ascending steps:
[1547,1547,1546,1548,1548,1545,1544,1543,1542,1542,1541,1538,1539,
1530,1540,1536,1537,1533,1534,1531,1527,1526,1526,1520,1520,1525,
1522,1519,1516,1514]
(1546<1548, 1538<1539, 1530<1540, 1536<1537, 1533<1534, 1520<1525.)
This is the deviation #1551 was approved on, and I am glad to see it is stated rather than glossed: changelog/README.md says "Entries are therefore ordered by pull-request number, not by merge order — a pull request opened earlier can merge after one opened later. The deviation is accepted." Visible and intentional, which is what I asked for. Within each PR's own file the relative order of that PR's entries is preserved (checked for all 25 files).
There is no generator script to run — the release step is a documented manual concatenation. I simulated precisely that procedure, and the numbers above are its output.
§8.3 rules survive the move
- PR-only links: 461 entries, 3 with no
/pull/link — the same 3 pre-convention entries main carries — and 0 non-/pull/links anywhere in the entry corpus. Main's only two non-/pull/links were preamble boilerplate (Keep a Changelog, SemVer); they moved intochangelog/README.md, which is not an entry file. Clean. - Released sections not rewritten: proven above by the byte-identity result.
- Unreleased vs released distinction preserved: the
## Unreleasedheading becomes theunreleased/directory, released sections become<version>.md. §8.4's version-bump table was updated in step (changelog/unreleased/contains …), so the rule that decides which number moves still has a well-defined subject. - New "list items only, no heading inside a file" rule: holds — 0 of the 121 files contains a Markdown heading.
Rest of the battery
- Diff is Markdown-only plus
changelog/unreleased/.gitkeep; nopackage.json, no CI, no generated files, so notsc/prepack/drift gate is in scope.filesis["**/*.js","**/*.d.ts","**/*.mjs","**/*.d.mts"], so neither the oldCHANGELOG.mdnor the new directory is published — no packaging impact. node bin/linkcheck.mjsoutput is identical toorigin/main's, line for line. Deletingtodo/changelog-directory.mdand rewriting theCHANGELOG.mdreferences stranded nothing.- No code, CI workflow, or the website generator reads
CHANGELOG.md, so the stub does not break a build step. - No entry for this PR, correctly — §8.3 exempts documentation-only changes.
One observation, not a request: a directory listing sorts 0.10.0.md before 0.2.0.md, so browsing changelog/ no longer shows versions newest-first the way the single file did. todo/changelog-website.md is where that gets solved, and nothing here depends on it.
Summary
Restructure the changelog from a single
CHANGELOG.mdfile into a per-version directory structure (changelog/) with individual version files and anunreleased/subdirectory for pending changes. This eliminates merge conflicts on concurrent PRs and improves maintainability.Key Changes
CHANGELOG.md(2079 lines) and thetodo/changelog-directory.mdtask (now completed)changelog/directory with:README.mddocumenting the new layout and Keep a Changelog format0.1.608.mdthrough0.44.0.md) containing release notesunreleased/subdirectory with per-PR files (e.g.,1547.md,1542.md, etc.) for pending changes.gitkeepto preserve theunreleased/directoryAGENTS.md: Updated changelog location referenceCONTRIBUTING.md: Updated changelog workflow instructionstodo/changelog-from-git-history.md: Updated cross-reference to the new structuretodo/changelog-website.md: Unblocked (no longer depends on the directory migration)Implementation Details
The new structure follows Keep a Changelog conventions:
0.44.0.md)unreleased/<PR>.mdfiles, one per PRREADME.mdprovides clear guidance on the layout and contribution workflowhttps://claude.ai/code/session_01WQFBgRCxeiy5gw1is28t2H