Skip to content

docs(docs): adopt keep-a-changelog with an Unreleased section - #337

Merged
danielplohmann merged 1 commit into
danielplohmann:masterfrom
r0ny123:docs/adopt-keep-a-changelog
Sep 11, 2026
Merged

docs(docs): adopt keep-a-changelog with an Unreleased section#337
danielplohmann merged 1 commit into
danielplohmann:masterfrom
r0ny123:docs/adopt-keep-a-changelog

Conversation

@r0ny123

@r0ny123 r0ny123 commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

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 Compatibility section — 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 written sits beside the entries, so the convention is where the next author already is: the subsection set plus Compatibility last, the bold subsystem prefix taken from the PR title's own scope token — the seventeen from semantic-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 releases verbatim, with the sentence explaining version-over-date ordering so the two legitimate inversions do not read as errors.

AGENTS.md drops the old one-line format for the release steps — rename Unreleased, drop unused subsections, open a fresh one, update the compare links — plus tag after master is 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.0 as misfiled above the v1.9.16v1.9.11 block. Moving it below them does not fix the file:

fix ordering violations
as it stood 1
move v1.0.0 below the v1.9 block 1
move the v1.9 block above v1.9.9 0

v1.0.0 was already at the foot of its own descending run. The five v1.9 entries were appended past the end of the list rather than filed above the v1.9.9 that was already at line 441, so moving v1.0.0 down just leaves v1.0.1 → v1.9.16 ascending 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_first is 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_version now 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:

mutation result
put the v1.9 block back where it was ordering test fails, and only it
add a ## [v4.7.0] heading, leave __version__ at 4.6.0 version test fails — it is reading the new heading
add that heading and bump both version strings all 10 pass, which is the real release path

Verification

Full suite 2,111 passed, 1 skipped, 2,596 subtests (2,110 on master, plus the new ordering test); ruff check . and ruff format --check . clean; make typecheck exit 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 Unreleased entry 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, against v4.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.

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.
@danielplohmann

Copy link
Copy Markdown
Owner

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 v1.9.16v1.9.11 lines moving from the foot of the file to just above v1.9.9.

Your reading of the filing error is the correct one. Running the ordering rule over both trees: master has exactly one violation, 1.0.0 → 1.9.16, and your tree has none across all 186 versioned entries. Moving v1.0.0 below the block would indeed just relocate the inversion to v1.0.1 → v1.9.16, so the block was the thing that was misfiled. The ## Older releases sentence naming v4.4.0/v4.3.11 and v3.1.0/v3.0.2 as legitimate is what keeps the next reader from "fixing" those two.

The version test reading both shapes is the part that mattered. _documentedVersions() returning headings first and legacy after is what makes documented[0] still answer correctly on the release that introduces the first ## [vX.Y.Z] heading, and the ordering test then spans the join between the two formats rather than stopping at it. Without that this PR sets a trap that springs on whoever cuts v4.7.0.

On the header: naming the three constants with the rule that a release moving one says so in Compatibility is the version of this I wanted. It promises something the entries can actually be held to, which the semver badge could not.

## How an entry is written taking the prefix vocabulary from semantic-pr-title.yml rather than restating it is the right call — one list, enforced in one place, and a scope added there is available here the same day. Rule 4 reads as agreed.

Verified locally stacked over a043145 together with #336, #327, #329 and #335: 2,142 passed, 2 skipped, 2,596 subtests, ruff check and ruff format --check clean, ty exits 0 at +4 diagnostics over master, all four in master's own pre-existing None | Unknown family on the new lines rather than a new kind.

Accepted as-is. It lands behind #336 per the sequencing on #323, and then the three engine PRs each want their Unreleased entry — please do write them, since each of the three has a caveat only you can phrase, and they are v4.7.0's content either way.

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.

Adopt keep-a-changelog: agreeing the fine tuning before the rewrite

2 participants