ci: Check CHANGELOG.md against the Keep a Changelog format - #8
Merged
Conversation
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
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
This was referenced Aug 28, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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@v2withvalidation_level: error— the same action and the same level PowerShellOrg already runs in itspowershell-ciworkflow. 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, andhas-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/PowerShellBuildhas sixteen, and the two format defects that prompted this work were in 0.3.0 (aBreaking changesheading) and 0.7.1 (aFixesheading) — 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-contribanddocshave 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'sif: 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/PowerShellBuildis currently the only consumer of this workflow, and its changelog fails validation today — aFixesheading, aBreaking changesheading, 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/psakepasses unchanged — it is the reference implementation here.Noticed while working here, not fixed
Invoke-ScriptAnalyzer . -Fixin thelintjob has no-Recurse, so it analyses only files directly in the repository root. Onpsake/psakethat meansbuild.ps1alone: 11 diagnostics scoped, versus 224 when recursed. Combined withreviewdog/action-suggester's defaultfail_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-Recursewould surface hundreds of findings across every consumer at once and deserves its own change.🤖 Generated with Claude Code
https://claude.ai/code/session_01U1Jhu7fgTRJq7LK5MuKteE