-
v1.6.0
Stablereleased this
2026-10-31 18:57:37 +00:00 | 1 commits to main since this releaseAdded
appendaction,mikluko/action-changelog/append@v1, which adds a note
under a section of the[Unreleased]entry.releaseaction,mikluko/action-changelog/release@v1, which turns the
[Unreleased]entry into a dated or open versioned entry, with the version
given or computed from its notes, and reportsversion,notesand
prerelease.appendandreleasesubcommands of the command.- The Go packages
sarak.sh/actions/changelog/pkg/changelog,
sarak.sh/actions/changelog/pkg/semverand
sarak.sh/actions/changelog/pkg/git.
Changed
- The command's module path is
sarak.sh/actions/changelog, run as
go run sarak.sh/actions/changelog/cmd/changelog@latest.
github.com/mikluko/action-changeloggets no further versions.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
-
v1.5.0
Stablereleased this
2026-10-22 10:11:28 +00:00 | 3 commits to main since this releaseChanged
- A composite action that downloads a prebuilt static binary from the
release's assets, verifies it against the release'schecksums.txtand runs
it, rather than a Docker container action. It runs on Linux and macOS runners,
on X64 and ARM64, and needs no Docker. Inputs, outputs and behavior are
unchanged. No image is published for this release or later ones; the tags
before it keep theirs. - Developed at
https://forge.sarak.sh/actions/changelog, which Forgejo
workflows reference ashttps://forge.sarak.sh/actions/changelog@v1.
mikluko/action-changelogon GitHub is its mirror and carries every release,
so a GitHub workflow referencingmikluko/action-changelog@v1resolves it as
before.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
- A composite action that downloads a prebuilt static binary from the
-
v1.4.0 Stable
released this
2026-10-04 14:51:58 +00:00 | 6 commits to main since this releaseAdded
dueanddue-reasonoutputs.dueistruewhen the newest entry
names a version, no tag names it, and it is not a pre-release;due-reasonis
one sentence for thefalsecase, also written to the step summary.due-prereleaseinput, defaultfalse, which lets a pre-release count as
due.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
-
v1.3.1 Stable
released this
2026-09-10 20:43:28 +00:00 | 11 commits to main since this releaseFixed
-
duplicate-sectionsays nothing about an entry the reference tag has
frozen. Shipped aterrorin 1.3.0, it fired on released entries whose
repairrelease-entry-modifiedforbids, and between them the two checks
described a document that cannot exist: removing the duplicate rewrites notes
a tag published, and leaving it keeps the run red. A repository carrying one
anywhere in its history had no move that turned the run green.The set spared is the one
immutablealready refuses to let anyone edit, so
the two agree by construction rather than by a rule stated twice and kept in
step by hand. An entry the tag never carried is checked as before:
backfilling history nobody recorded is a repair, and a repair may be edited.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
-
-
v1.3.0 Stable
released this
2026-09-10 17:42:24 +00:00 | 14 commits to main since this releaseAdded
duplicate-section, for a level-3 heading repeating a name another section
under the same entry already carries, aterror.unknown-sectionholds
each heading to the vocabulary but never compares one to another, so two
### Addedheadings under one entry parsed clean, each with its own list
below it. That is the shape a rebase leaves where it replays a bullet into an
entry already carrying the section it belongs under. Nineteen checks.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
-
v1.2.0 Stable
released this
2026-09-07 07:28:16 +00:00 | 19 commits to main since this releaseAdded
-
unreadable-version, a check of its own for an entry heading naming no
version that can be read, aterror, which leavesheading-formthe shape
question its name is about. One check was doing both jobs and only one of them
is a policy:heading-formis about the form of a heading, which a repository
may reasonably differ on and switch off, and switching it off also switched
off the only thing standing between a heading nothing can read and a release
described from the wrong entry. It is the splitundated-entryand
undated-releasemade, for the same reason. Sixteen checks. -
oci-incompatible-version, for a version that is valid Semantic Versioning
and cannot be an OCI tag, aterrorand switchable off. The specification
permits1.2.3+build.1; the OCI distribution specification's tag grammar is
[a-zA-Z0-9_][a-zA-Z0-9._-]{0,127}, which has no+. Git ref syntax does
permit it, so such a version cuts a tag and is then not a name a registry
takes. The finding names the character at fault, and the length bound is
checked too: a pre-release run has no bound in SemVer and a tag stops at 128.This is the first check whose subject is outside the document. The other
seventeen are about the changelog or about the repository's tags; this one is
about what a version can become somewhere else, and the register says so
rather than pretending otherwise. A repository that publishes no image or
chart switches it off inoff:and it never fires again. Eighteen checks. -
A SemVer compliance section in the README, stating what this action
accepts as a version and what four named tools do with one:x/mod/semver's
two documented deviations,Masterminds/semver/v3admitting a pre-release
only where the constraint's own literal carries one, the difference between
matching a range and selecting from it, and the OCI tag grammar having no+.
Each is documented where its own tool lives and nowhere near the
specification, so a reader holding a valid version had no way to meet them
except by running into one. Every entry names the version it was read against. -
date-mismatch, for a released entry whose date is not the day its tag was
cut, aterror. Nothing compared the two:date-formatreads the shape,
date-orderanddate-futureread the document alone, and the immutability
pair compares text. An entry could claim2026-09-01whilev1.2.3was cut
on the 11th and every check passed. An entry no tag names is silent, because a
release pending is not a date that disagrees. Seventeen checks.Two things it has to get right, and both are settled where the tag is read
rather than where the dates are compared. A repository cuts one kind of tag
or the other, and they carry their date in different places: an annotated
tag has a tagger date of its own, a lightweight tag is a bare reference whose
only date is on the commit it names. And the day is the tag's own, not
UTC — a changelog date means the day the human cut the release, so
normalising a tag cut at 23:30-07:00 turns a correct entry into a finding.The open entry is what makes this check exact. Under
release-branchthe
date is written by the merge that cuts the tag, so it is a record. Under
release-trunkit is written in the pull request before the tag exists, so it
is a prediction, and a pull request open across midnight ships an entry that
disagrees with its own tag legitimately. That is the invocation that switches
this check off, the way a branch accumulating a release switches off
undated-entry.
Changed
-
What counts as a version is Semantic Versioning 2.0.0 exactly, read by a
parser written here rather than bygolang.org/x/mod/semver, whose own package
doc declares two deviations from the specification: it requires a leadingv,
and it recognisesvMAJORandvMAJOR.MINORas alternatives to the
three-component form. Working around those cost a canonicalisation comparison
that carried a second policy nobody chose. Two headings change verdict:
[v1.2.3]named a version and now names none, because the leadingvis
x/mod's requirement and a heading is not a tag; and[1.2.3+build.1]named
none and now names one, because build metadata is valid Semantic Versioning.
Whether a version this project accepts is one it wants is a separate
question, asked under a name of its own. One departure is this project's and
not the specification's: a major, minor or patch component past 2^64-1 is
rejected, where the specification sets no bound. -
golang.org/x/mod/semveris gone, and ordering is this project's too.
Sections 10 and 11 are where the specification stops being obvious: build
metadata is ignored for precedence, so1.0.0+aand1.0.0+bare one version
and two strings; a numeric pre-release identifier ranks below every identifier
that is not one; and numeric identifiers compare numerically with no upper
bound, which1.0.0-99999999999999999999needs and no fixed-width integer
gives. They carry no leading zero, so the longer of two is the larger and the
value is never read as a number. -
A tag is read by
ParseTag, which is deliberately laxer than the
specification and says so. A tag namespace carries a leadingv, and the
GitHub Actions convention keeps a movingv1beside thev1.0.0it points
at; both name one version, and the fuller spelling still wins the tie for the
reference tag. That behaviour used to rest onx/modacceptingvMAJOR
shorthand as a side effect. It is now a named, tested contract of this
project's own, and read strictly it would have gone silently. -
A tag now matches an entry by precedence rather than by string equality, so an
entry naming build metadata matches the tag naming its version. It could not
before, because canonicalisation dropped the metadata from one side only.
Fixed
-
undated-releaseno longer fires where the tag naming the newest entry is a
candidate. The check split on whether a tag already named the entry's
version, read as the release shipped. That reading is sound for a final and
false for a pre-release: a candidate ships its tag and stays undated on
purpose, which is what a stabilization branch is for. It fires on a final
tag now, and a candidate falls through toundated-entry, which is the check
such a branch already switches off. -
The defect was unreachable until the
release-branchexample stopped
composing its tag. While the branch cutvX.Y.Z-pre.<run>and the entry named
X.Y.Z, no tag a branch cut ever named its own entry's version. Once the
entry names what is cut, the tag names the entry's version from the first
candidate on, and every push after it was a finding on the one branch whose
purpose is to cut them. -
The
release-branchexample teaches what the strategy's own adopter
settled on: the changelog names the release it is heading for and never an
attempt at one. The entry says1.3.0from the moment the branch opens; each
pull request into that branch cuts a numbered candidate,v1.3.0-rc.1and up;
merging to the trunk dates the entry and cutsv1.3.0. A heading therefore
never carries an identifier, andprerelease-entryis raised on every
invocation rather than on the trunk alone. -
A release branch has no push trigger. A candidate comes from a pull request
into the branch, which is what lets the suite stand in front of it: the cut is
a job withneeds: [changelog, suite], andneeds:is job-level, so a step
had no boundary a gate could attach to. The two cutting workflows also carry
paths: [CHANGELOG.md], so a change proposing no release starts no run at all. -
The workflows are named for what each run yields, not for what triggers it:
ci,release-candidate,release,publish, and thesuitethey call.
Read that list and it is the strategy. It replacespull-request.yaml,
release-branch.yamlandmain.yaml, whose names described their triggers and,
in one case, described the wrong thing. -
Where the newest entry's heading names no version that can be read,
version
andnotesreport nothing rather than describing the entry below it. That
entry was being read as the newest, so a workflow reading those cut a tag for
the wrong release and published the previous release's notes under it. Nothing
under such a heading is the newest entry, and reporting nothing is what a
document naming no version already does, so a workflow guarding on an empty
versionlands on the path it already has.already-taggedandprerelease
answerfalsebeside it, as they do for a document naming no version. -
A rejected heading names the rule it broke rather than restating the
grammar. One message covered every rejection, so the author of
## [1.3.0+build.1]was told that a version states all three of major, minor
and patch, which it does. A leading zero, a component that is not a number, an
empty identifier, a character outside[0-9A-Za-z-]and a leadingvnow each
say so, and point at the fragment at fault.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
-
-
v1.1.0 Stable
released this
2026-09-05 14:37:49 +00:00 | 56 commits to main since this releaseAdded
undated-entryandundated-release, a pair the repository's tags decide
between where the newest entry names a version and carries no date. No tag
names that version yet, and the entry is open: a release still accumulating on
a branch of its own, which names its version before its date is known.
undated-entryreports it, defaulting toerrorbecause an entry with no
date is illegal under Keep a Changelog, and a branch accumulating a release
is the one invocation that switches it off inoff:, the way a trunk
invocation switchesprerelease-entryon. A tag already names that version,
and the release shipped and nobody dated it:undated-releasereports that,
also aterror, and switching the other check off does not silence it.
Fifteen checks.- The two are separate because one setting cannot serve both. A stabilization
branch has to admit its open entry on every run, and a release that reached a
tag with no date is a defect on exactly that workflow, so a single check would
be switched off for the whole of the window in which the defect occurs. The
same tag question answers both, and it is the onealready-taggedreports, so
the check and the output cannot disagree about which case a run is in. - Where the tag history cannot be read, the answer is unknowable and the entry
is reported as open.no-git-tagsalready names that cause, and accusing a
repository of shipping an undated release on the strength of tags nobody could
read is the more expensive of the two mistakes.
Changed
heading-formaccepts## [1.2.3]with no date on the newest versioned entry
only, which is the entry the two checks above then answer for. Every entry
below it has shipped and carries a date, so one missing there still fires
heading-form.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
-
v1.0.2 Stable
released this
2026-09-04 14:04:40 +00:00 | 62 commits to main since this releaseFixed
- Where two tags name one version, the reference tag is the one that spells it
most fully. A repository following the GitHub Actions convention carriesv1
besidev1.0.0, both read asv1.0.0, and which of them won was whatever
order the refs happened to arrive in.latest-tagcould therefore report the
moving major tag, which is documented as the ref a workflow can check out and
is the one that moves to the next release under whoever checked it out.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
- Where two tags name one version, the reference tag is the one that spells it
-
v1.0.1 Stable
released this
2026-09-04 13:58:04 +00:00 | 64 commits to main since this releaseFixed
no-git-tagsfires where the repository is present and cannot be read. A
.gitfile naming a git directory that does not resolve here (a linked
worktree away from its parent, a submodule without the superproject, a
container that mounted the working tree and not the repository) read as a
repository holding no tags, so every tag-dependent check passed on a history
nothing had read. The finding's message now carries the remedy for both
causes and names the one this run met.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
-
v1.0.0 Stable
released this
2026-09-04 13:33:05 +00:00 | 67 commits to main since this releaseFirst release. The candidates that preceded it were staging posts and are not
recorded separately: nothing consumed them but this repository.Added
- A GitHub Action, and a command, that reads a Keep a Changelog document and
reports where it departs from the format. Each finding lands as a workflow
annotation on the offending line of the diff. Run with no argument it
validatesCHANGELOG.md. - Thirteen checks, each with a name that appears in the finding, in the
annotation's title, and in the flags that reconfigure it.-list-checks
prints the register, and the README's table is generated from that same
register with CI failing on a stale copy, so the documentation cannot
disagree with the binary. - Severity is the practitioner's, not the tool's.
-error,-warnand
-offmove a named check, and-fail-on error|warning|neverdecides what
turns the step red. A check that catches a defect is an error by default; a
check that encodes a policy is off by default, because a policy check's
absence is the majority case being correct. - Six outputs, written to
$GITHUB_OUTPUTand printed on stdout for a local
run:valid,version,notes,already-tagged,prereleaseand
latest-tag. Every one is something the action read, never a convention it
chose: nothing proposes how to spell a tag, because that belongs to whatever
cuts them.notesis the entry body verbatim, under a delimiter drawn at
random per value so that a changelog cannot declare outputs of its own. - One reference tag behind every tag-dependent check and behind
latest-tag: the newest version tag reachable from HEAD, which is what
git describe --tagsnames. Reachability takes no setting, because a tag on a
branch the checkout is not on is never the right baseline, and it is what lets
a maintained support line compare against its own last release rather than
another line's.reference-tags: final|alldecides whether a pre-release may
serve as one, defaulting tofinal. A repository that tags no pre-release
reads the same under either setting. - Checks that read the repository as well as the document:
no-git-tagsfor a
checkout whose tag history cannot be read,version-behind-tagfor a newest
entry behind the reference tag, andrelease-entry-modifiedand
prerelease-entry-modifiedfor a released entry that has changed or gone
since it. A released entry is immutable; an entry added for a version already
tagged is not, because recording history nobody wrote down is a repair rather
than a rewrite. - A Docker container action on a
scratchbase, published forlinux/amd64and
linux/arm64. It reads git through go-git rather than by shelling out, so the
image carries the binary and nothing else. examples/carries one worked policy: a changelog written to it,
the two workflow invocations that enforce it, and a deliberately broken copy.
A test resolves the inputs out of those workflow files and runs the linter
under them, so the example is executed rather than described.- This repository releases itself with its own action. A push to
mainruns
the action against this changelog and, where it names a version no tag
carries, publishes the image, cuts the tag and creates the release from
notes. Release candidates are cut from the pull request instead, so a
candidate can be pulled and tested before it merges, and one reachingmain
is refused rather than published.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
- A GitHub Action, and a command, that reads a Keep a Changelog document and