docs(docs): adopt keep-a-changelog with an Unreleased section - #337
Conversation
Every changelog line has been written by one person at release time, reconstructed from merge commits days later -- the anti-pattern the format warns about, done by hand -- because there was nowhere to accumulate an entry. An Unreleased section is the point of the change; the formatting is downstream of it. The header states the scope rather than claiming semver, which would be a false claim: major has meant a new capability, minor a feature group, and patch everything else including small features. The compatibility promise consumers actually read is carried by three constants, so those are named, with the rule that a release moving one says so in its Compatibility section. A header pointing at constants the entries do not track looks precise and is worse than no promise. How an entry is written is documented beside the entries: the subsection set plus Compatibility last, a bold subsystem prefix taken from the PR title's own scope token so the vocabulary is the one semantic-pr-title.yml already enforces, and four rules -- name the corpus, say whether it was reproduced, state the cost, and name the tree when the figure is marginal against a moving baseline. History is not converted. The 186 versioned entries and the undated lines below them move under Older releases verbatim, with a sentence saying they are ordered by version rather than date, since the two disagree legitimately and that makes a filing error indistinguishable from a patch released after a newer minor. One filing error is fixed, and it is not the one it looks like. v1.0.0 appears to sit above the v1.9.16-v1.9.11 block, but moving it below them leaves v1.0.1 -> v1.9.16 still ascending: the five v1.9 entries were appended after the end of the list instead of filed above the v1.9.9 that was already there. Moving the block is what makes the file ordered, and a test now checks it -- by eye the rule is unenforceable, which is how this survived 186 entries. The version test reads both shapes, so it keeps working across the release that introduces the first of the new form rather than failing on it.
|
Checked the three claims that carry this PR rather than reading the tables, and all three hold. The entry lines survive intact. 194 before, 194 after, and the two sets are identical as a multiset — no rewording, nothing dropped, nothing gained. The only positional change is the five Your reading of the filing error is the correct one. Running the ordering rule over both trees: master has exactly one violation, The version test reading both shapes is the part that mattered. On the header: naming the three constants with the rule that a release moving one says so in
Verified locally stacked over Accepted as-is. It lands behind #336 per the sequencing on #323, and then the three engine PRs each want their |
Rollout steps 1 and 2 from #323, built to the decisions you settled. They are one PR because the format and the document describing it cannot land apart.
What is in it
The header states the scope instead of claiming semver, with the three constants named and the rule that a release moving one says so in its
Compatibilitysection — your framing, that a header pointing at constants the entries do not track is worse than no promise because it looks precise.## How an entry is writtensits beside the entries, so the convention is where the next author already is: the subsection set plusCompatibilitylast, the bold subsystem prefix taken from the PR title's own scope token — the seventeen fromsemantic-pr-title.yml, verbatim, no prefix where a PR carries no scope — and the four content rules including your addition, a figure marginal against a moving baseline naming its tree.## [Unreleased]is empty, with the standard subsections in place for the first author to fill. #327, #329 and #335 are its content.History is not converted. All 194 entry lines move under
## Older releasesverbatim, with the sentence explaining version-over-date ordering so the two legitimate inversions do not read as errors.AGENTS.mddrops the old one-line format for the release steps — renameUnreleased, drop unused subsections, open a fresh one, update the compare links — plus tag aftermasteris green, with the reason: moving a tag nobody has fetched is cheap, moving one a PyPI publish has consumed is not.The filing error is not the one it looks like
The table in #323 reads
v1.0.0as misfiled above thev1.9.16–v1.9.11block. Moving it below them does not fix the file:v1.0.0below thev1.9blockv1.9block abovev1.9.9v1.0.0was already at the foot of its own descending run. The fivev1.9entries were appended past the end of the list rather than filed above thev1.9.9that was already at line 441, so movingv1.0.0down just leavesv1.0.1 → v1.9.16ascending instead. Moving the block is what makes the file ordered — 186 versioned entries, 0 violations after.Proved not to have cost anything: 194 entry lines before, 194 after, identical as a multiset. Nothing was reworded, dropped or gained; five lines changed position.
Two tests, because this rule cannot be enforced by eye
test_the_changelog_is_ordered_newest_version_firstis the point. Date order and version order disagree legitimately, so a misfiled entry looks exactly like a patch released after a newer minor — which is how this one survived 186 entries and a review that looked straight at it.test_the_changelog_documents_the_current_versionnow reads both shapes. Without that it passes today and fails the moment v4.7.0 is cut in the new format, since the newest release would no longer be a*line — a trap this PR would otherwise have set for the next release.Both verified by mutation:
v1.9block back where it was## [v4.7.0]heading, leave__version__at 4.6.0Verification
Full suite 2,111 passed, 1 skipped, 2,596 subtests (2,110 on master, plus the new ordering test);
ruff check .andruff format --check .clean;make typecheckexit 0 at master's own 263 diagnostics.Sequencing
#336 carries the CI check and you said it lands first; this PR does not depend on it, and neither ordering breaks the other. Once both are in, #327, #329 and #335 each need an
Unreleasedentry on their next push — I will write the three unless you would rather, and they are v4.7.0's content either way.Compare links: only
[Unreleased]is added, againstv4.6.0. Per-release links arrive as releases are cut, which is now a step in the checklist, rather than backfilling tags that do not exist.Closes #323.