Skip to content

ci: Check CHANGELOG.md against the Keep a Changelog format - #8

Merged
tablackburn merged 2 commits into
mainfrom
feature/changelog-format-check
Aug 28, 2026
Merged

ci: Check CHANGELOG.md against the Keep a Changelog format#8
tablackburn merged 2 commits into
mainfrom
feature/changelog-format-check

Conversation

@tablackburn

@tablackburn tablackburn commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Adds Keep a Changelog validation to the shared module workflow, so every psake module adopting it gets the check from one place.

Uses mindsers/changelog-reader-action@v2 with validation_level: error — the same action and the same level PowerShellOrg already runs in its powershell-ci workflow. The two orgs now check changelogs the same way.

What it validates

Its four rules: has-correct-sections (the six Keep a Changelog types), is-valid-version, has-sections, and has-chronological-order.

Two deviations from PowerShellOrg's config, both deliberate

validation_depth: 100, up from the default of 10. At the default only the ten most recent entries are checked. psake/PowerShellBuild has sixteen, and the two format defects that prompted this work were in 0.3.0 (a Breaking changes heading) and 0.7.1 (a Fixes heading) — both outside a ten-entry window. Format drift accumulates in old entries precisely because nobody looks at them.

Skipped when there is no CHANGELOG.md. This is a shared workflow; psake-contrib and docs have no changelog, and failing a consumer for not having a file it never claimed to have would be wrong. if: hashFiles('CHANGELOG.md') != '' handles it — note PowerShellOrg's if: always() does not, since that guards against a previous step failing rather than a missing file.

Why not the hand-written script this branch started with

The first draft was a self-contained pwsh step, written after searching for an off-the-shelf action and not finding one. That search was wrong: it looked for what the thing does ("changelog lint", "validate keep-a-changelog") and this action is published as a reader, with validation as a secondary flag. Checking what a peer org actually uses would have found it immediately.

The action is strictly better anyway — it covers everything the script did, adds chronological ordering and pep440 support, and being a reader means the same action can extract a release's notes when cutting a release, which a bespoke validator would never have grown into.

Not pinned to a SHA

Every other action here uses a moving tag (actions/checkout@v4, cspell-action@v6, action-suggester@v1.21.0). Pinning one entry alone is the partial pinning that psake/PowerShellBuild#145 rejected as buying nothing. Worth revisiting org-wide when that issue is settled.

Sequencing for whoever merges

psake/PowerShellBuild is currently the only consumer of this workflow, and its changelog fails validation today — a Fixes heading, a Breaking changes heading, eleven release headings missing the date separator, and a [0.5.0] (beta1) version identifier.

psake/PowerShellBuild#208 fixes all of it and must merge first, or PowerShellBuild CI goes red the moment this lands. psake/psake passes unchanged — it is the reference implementation here.

Noticed while working here, not fixed

Invoke-ScriptAnalyzer . -Fix in the lint job has no -Recurse, so it analyses only files directly in the repository root. On psake/psake that means build.ps1 alone: 11 diagnostics scoped, versus 224 when recursed. Combined with reviewdog/action-suggester's default fail_level: none, the step currently cannot fail a job and, for a repo whose root holds only a build script, inspects almost nothing. Left alone because adding -Recurse would surface hundreds of findings across every consumer at once and deserves its own change.

🤖 Generated with Claude Code

https://claude.ai/code/session_01U1Jhu7fgTRJq7LK5MuKteE

Adds a changelog job to the shared module workflow, so every psake module
that adopts it gets the check from one place.

The check only applies when the file says the format is the one it
follows. A CHANGELOG.md that never mentions Keep a Changelog is skipped,
and so is a repository with no changelog at all, so adding this to a
shared workflow cannot ambush a consumer that never made the claim.

What it verifies, for a file that does make the claim:

  - Section headings are among the six types Keep a Changelog defines --
    Added, Changed, Deprecated, Removed, Fixed, Security. An invented
    seventh is how a changelog quietly stops being machine-readable.
  - Release headings read '## [version] - YYYY-MM-DD'.
  - An '## Unreleased' section exists.

Every violation is reported, not just the first, because a changelog that
has drifted has usually drifted in several places and one-at-a-time is a
poor way to find that out.

Written as a self-contained pwsh step rather than a third-party action.
The search for an existing one turned up nothing suitable: the mature
option, dangoslen/changelog-enforcer, checks that a pull request modified
the changelog rather than that it is well-formed, and would misfire on
every change that correctly needs no entry. The parsers that do validate
format are Node packages with negligible adoption. A pwsh step also
avoids adding a moving third-party tag to pin.

Verified against real changelogs before landing: psake/psake passes
unchanged, psake/PowerShellBuild fails on exactly the drift it has today
(a 'Fixes' heading, a 'Breaking changes' heading, and eleven release
headings missing the date separator), a changelog that never declares the
format is skipped, and a repository with no changelog is skipped. The
script was then extracted back out of the YAML and re-run to confirm the
embedding did not mangle it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U1Jhu7fgTRJq7LK5MuKteE
Copilot AI lite review requested due to automatic review settings August 28, 2026 18:37

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

tablackburn added a commit to psake/PowerShellBuild that referenced this pull request Aug 28, 2026
## [0.5.0] (beta1) - 2020-11-15  ->  ## [0.5.0-beta1] - 2020-11-15

This was left out of the first pass as a content decision rather than a
formatting one, on the reasoning that rewriting a released version's
identifier asserts something about how it was published, and that no
v0.5.0-beta1 tag corroborates it. That reasoning was sound but the
conclusion does not hold: "0.5.0 (beta1)" and "0.5.0-beta1" say the same
thing, and the second is the spelling both Semantic Versioning and Keep a
Changelog use for a prerelease. Nothing is being asserted that the
parenthetical did not already assert.

Two things improve for free.

The file no longer carries two headings labelled [0.5.0]. A duplicate
identifier makes the version ambiguous to a reader and unlinkable to a
tool, and it is part of why the linkability question raised in this pull
request has no clean answer -- one label cannot serve two releases.

And it is the last heading in the file that the Keep a Changelog format
check rejects. That check is proposed for the shared module workflow in
psake/.github#8, and psake/PowerShellBuild is its only consumer today, so
this heading was the one thing standing between that workflow and a green
run here. Verified: the check now passes over this branch, 496 lines, no
findings.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U1Jhu7fgTRJq7LK5MuKteE
Uses mindsers/changelog-reader-action, the same action and the same
validation_level PowerShellOrg already runs in its powershell-ci workflow,
so the two orgs check changelogs the same way.

This replaces a hand-written pwsh script in the first draft of this
branch. The script worked, but the action is strictly better: it validates
section types and version identifiers as the script did, and additionally
checks chronological ordering and understands both semver and pep440
version schemes. It is also the thing it is named for -- a reader -- so
the same action can extract a release's notes when cutting a release,
which a bespoke validator would never have grown.

Two deviations from PowerShellOrg's configuration, both deliberate:

  validation_depth is raised from its default of 10 to 100. At the default
  only the ten most recent entries are checked. psake/PowerShellBuild has
  sixteen, and the two format defects that prompted this work were in
  0.3.0 and 0.7.1 -- both outside a ten-entry window. Format drift
  accumulates in old entries precisely because nobody looks at them.

  The step is skipped when the repository has no CHANGELOG.md. This is a
  shared workflow, and psake-contrib and docs have no changelog; failing a
  consumer for not having a file it never claimed to have would be wrong.

Not pinned to a commit SHA. Every other action here uses a moving tag,
and pinning one entry alone is the partial pinning that
psake/PowerShellBuild#145 rejected as buying nothing. Worth revisiting
org-wide when that issue is settled.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U1Jhu7fgTRJq7LK5MuKteE
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.

2 participants