-
Release v0.22.0
StableAll checks were successfulchangelog-body-check / changelog body Cold-Read linter (pull_request) Successful in 4schangelog-body-check / check (pull_request) Successful in 0scheck-self-bootstrap / check (pull_request) Successful in 3sfragment-check / changelog fragment-kind (pull_request) Successful in 4sfragment-check / check (pull_request) Successful in 0smanifest-check / manifest-vs-tag consistency (pull_request) Successful in 5smanifest-check / check (pull_request) Successful in 0scheck-self-bootstrap / check (push) Successful in 3srelease / decide + act (push) Successful in 6srelease / release (push) Successful in 0sreleased this
2026-07-02 21:31:16 +02:00 | 1211 commits to main since this releasePost-v0.21.0 stabilization: consumer-wrapper discovery unified, CC-bullet dedup extended to scope-as-issue-ref shape, and the toolkit-self body-linter dogfood wrapper lands.
Added
Toolkit-self dogfood wrapper for the Cold-Read body linter — new
.forgejo/workflows/changelog-body-check.ymlinvokes the toolkit's ownreusable-changelog-body-check.ymlon every PR that touchesCHANGELOG.md. Structural + formatting issues on the toolkit's own CHANGELOG now fail PR CI before merge, exactly like consumers do once they wire the reusable in. Same shape asfragment-check.yml's dogfood wrapper (#281). Closes #290.Fixed
Consumer-wrapper discovery unified into a single source of truth — extracted to
scripts/lib/wrappers.sh::discover_consumer_wrappersand consumed by all three surfaces (check-self-bootstrap.sh,scripts/repin.sh,reusable-release.yml's runtime auto-re-pin loops). Before, each surface carried its own hardcoded wrapper list; a new wrapper (e.g.,fragment-check.ymlin #278,changelog-body-check.ymlin #290) required updating each list, and a missed update produced silent single-ref-alignment drift post-cut. Closes #295.changelog_dedup_cc_by_fragment_refsnow catches scope-as-issue-ref bullets — commits withfix(#NNN): ...scope-format subjects (which produce- **#NNN**: ...bullets) are now suppressed when a matching fragment exists, alongside the existing(#NNN)trailer format. Closes #293.Removed / Deprecated
None.
Upgrade
No action required to upgrade.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
-
Source code (ZIP)
-
Release v0.21.0
StableSome checks failedcheck-self-bootstrap / check (pull_request) Failing after 3sfragment-check / changelog fragment-kind (pull_request) Successful in 4sfragment-check / check (pull_request) Successful in 0smanifest-check / manifest-vs-tag consistency (pull_request) Successful in 6smanifest-check / check (pull_request) Successful in 0scheck-self-bootstrap / check (push) Failing after 3srelease / decide + act (push) Successful in 7srelease / release (push) Successful in 0sreleased this
2026-07-02 17:23:13 +02:00 | 1221 commits to main since this releaseChangelog tooling + release automation: mechanical Cold-Read body linter, fragment template + brevity docs, auto re-pin of toolkit-self wrappers post-cut, and observability fields for cut-timing.
Added
Cold-Read body linter — new
reusable-changelog-body-check.ymlreusable workflow +scripts/changelog-body-check.sh. Runs 6 mechanical checks on the newest version-block inCHANGELOG.mdat PR time: version header + date, standard sections present, forward-reference detection, body length ceiling (5000 chars), bullet/prose consistency, duplicate section headers. Consumers wire it into theirpull_requestworkflow same shape asfragment-check.yml. Toolkit-self dogfood wiring deferred to #290. Closes #282 Layer 1.Fragment template + brevity docs — new
changelog.d/.template.mdstarter template +docs/conventions.md### Fragment brevitysection. Codifies "outcome + required action in 1-3 sentences" as the human-side density guidance; complements the automated linter. Closes #282 Layer 2.Auto re-pin of toolkit-self consumer wrappers —
reusable-release.ymlnow auto-re-pinsrelease.yml+manifest-check.yml+fragment-check.ymlto the just-cut tag as part of post-cut bookkeeping. Bundled with the bake-reset commit so both land atomically. Obsoletes the manual re-pin PR thatcheck-self-bootstrap.yml(#124) previously fired as a reminder for. External consumers are unaffected — the auto-re-pin targets toolkit-self only;if [[ -f ... ]]guard skips non-existent files. Closes #283.cut_complete.ms_total+path— thecut_completeJSON event now carriesms_total(total cut wall-clock in integer milliseconds) andpath(thealpha/gammapush-path). Off the workflow path (directdraft-release.shinvocation, or platformdatelacking%N) both fields degrade to empty — the same graceful-degradation contract asapi_call'sms. Completes the JSON-logging taxonomy from #159. (Part 2 of #204.)Changed
check-self-bootstrap.ymlscope narrowed — now fires only when the auto re-pin (#283) fails, not as a "human forgot" reminder. Consumers who relied on this check as a manual-re-pin nudge should transition to the auto re-pin flow;check-self-bootstrap.ymlstays as a substrate-failure fail-safe.docs/integration.mdpost-#172 migration note — corrected to reflect empirically-observed Forgejo behavior: undeclared inputs toworkflow_callreusables are silently ignored. The lazy-leave shape (bump@<ref>past #172, forget to droptoolkit_ref:) runs cleanly. Dropping the stale input is a hygiene fix, not a correctness fix. Closes #250.Fixed
Fragment-check baked ref stuck at
mainfor v0.20.0 consumers — v0.20.0 shippedreusable-changelog-fragment-check.ymlwithBUILD_BAKED_TOOLKIT_REF: 'main'becausereusable-release.yml's three runtime bake-reset loops (added in #278) only listed the two pre-existing reusables. Impact for consumers pinning@v0.20.0on the fragment-check wrapper:.release-toolkitscripts checked out atmain(the baked value) rather thanv0.20.0— substrate drift class. Upgrade action: re-pin fragment-check to@v0.21.0(the normal post-cut re-pin) to resolve. Extendedtests/workflows.batsbake-marker guard now walksreusable-release.yml's runtime loops so the class can't reintroduce. (Fold-in from #283 work.)Auto re-pin loops corrupted external consumer wrappers — without a toolkit-self gate, the auto re-pin (new in #283) would rewrite
release.yml/manifest-check.yml/fragment-check.ymlon any external consumer's checkout, breaking their pin state and their next CI run's reusable resolution. Both path-α and path-γ auto re-pin loops are now gated onif [[ -f .forgejo/workflows/reusable-release.yml ]](the toolkit-self detection). Caught pre-merge; no external consumer was affected in the wild.Cold-Read linter dogfood wrapper mis-scoped pre-cut — the toolkit-self dogfood wrapper for the Cold-Read body linter was dropped from #289 because the pre-v0.21.0 window has no consistent pin target (
@mainbreaks single-ref-alignment;@v0.21.0-rc.Ndoesn't yet contain the reusable). Deferred to #290 (post-v0.21.0-cut fold), same pattern as #281.Internal
update-doc-version-refs.shself-git addremoved —release-prep.sh's #236 auto-stage sweeps hook-modified tracked files, so the belt-and-suspenders self-stage from #251 is redundant. Closes #253.Removed / Deprecated
None.
Upgrade
No action required for consumers on
@v0.20.0unless the Fragment-check baked-ref bug (see Fixed) applies — re-pinfragment-check.ymlto@v0.21.0in that case. The Cold-Read body linter and fragment template are opt-in.Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
-
Source code (ZIP)
-
Release v0.20.0
StableSome checks failedcheck-self-bootstrap / check (pull_request) Failing after 3smanifest-check / manifest-vs-tag consistency (pull_request) Successful in 5smanifest-check / check (pull_request) Successful in 0scheck-self-bootstrap / check (push) Failing after 3srelease / decide + act (push) Successful in 6srelease / release (push) Successful in 0sreleased this
2026-07-02 12:10:58 +02:00 | 1237 commits to main since this releaseFirst-cut reliability hardening. Every issue that surfaced on the first external toolkit-driven cut (tmux-tell) is now fixed at the substrate. Upgrade is a pin bump — fragment-check wiring is opt-in, everything else is transparent.
Added
Push-whitelist pre-flight on path-α cuts.
draft-release.shnow probes the consumer's default-branch protection before doing any work. Fails fast + names the missing identity ifrelease-botisn't onpush_whitelist_usernames, instead of the late mid-cutNot allowed to push to protected branchrejection that leaves the manifest desynced. Only fails on a definitive misconfig; warns-and-proceeds otherwise.docs/integration.mdnames it as a path-α precondition.reusable-changelog-fragment-check.yml+scripts/fragment-check.sh. Consumers wire it into theirpull_requestworkflow to fail-loud on unknown-kind fragments (e.g..documentation.md) before merge, instead of at the next cut. See docs/integration.md § fragment-check.yml for the consumer stanza. Closes #271.Changed
docs/operations.mdgained an annualrelease-botPAT rotation-cadence section. No action required on existing installs; consult before regenerating theRELEASE_TOOLKIT_TOKENActions secret. Closes #273.Fixed
External cuts no longer halt on the post-cut bake-reset step. The reusable's bake-reset blocks now guard
git commitwithgit diff --cached --quiet, so the empty-staging case (every external consumer — the bake-reset is toolkit-self-only by design) cleanly skips the commit + push instead of failing "nothing to commit" and cascading into the manifest never landing. This unblocks every external toolkit-driven cut. Toolkit-self behavior unchanged.forgejo_find_pr_by_headno longer mutates a random open PR. Forgejo'shead=query filter is empirically ignored, so.[0].numberwas picking whichever PR the API sorted first — releasing PATCHes onto a random open feature PR instead of the actual rolling PR. Now filters response client-side by exacthead.label/head.refmatch. No consumer-side change required. Closes #274.Rolling-PR prepare-commit push now authenticates with the path-α PAT. Previously it reused the
actions/checkoutsystem token, which Forgejo Actions' anti-recursion safeguard suppresses — so no CI ran on the rolling PR and its required checks could never satisfy. The push now injectsFORGEJO_TOKEN(theRELEASE_TOOLKIT_TOKENPAT) as an HTTPAuthorizationheader, mirroring the manifest push. Necessary but not sufficient — the consumer must still setRELEASE_TOOLKIT_TOKENto a real-user PAT (the documented path-α setup).release-decidedetects thechore(release): prepare vX.Y.Zcommit anywhere in the merged range, not only at HEAD. Previously, any commit landing on top of the prepare commit (a rebase nudge, a CHANGELOG dedupe) shifted HEAD off the prepare subject and the cut was silently skipped — the manifest stayed at the old version, no tag/release, stale rolling PR against the old baseline. The range scan is a full git-log walk. Existing Layer 2/3 safeguards still gate — a stale prepare commit in range falls through tomode=updateloudly. Squash-merge of the rolling PR is not yet covered; tracked as #259.Fragment vs conventional-commit duplicate suppressed at generation layer. When a fragment and a CC subject reference the same issue number,
release-prep.shnow suppresses the CC-subject bullet in favour of the fragment prose (richer wins). A[changelog-dedup]line is logged to stderr for auditability. Closes #258.release-prep.shPR body assembly now re-normalizes throughchangelog_merge_sectionsso any surviving duplicate### Kindheadings collapse to one section per kind. Belt-and-suspenders on top of the merge already done inchangelog_transition. Closes #272.Removed / Deprecated
None.
Upgrade
Pin your consumer wrapper's
uses:@<ref>tov0.20.0. Fragment-check wiring is opt-in (add.forgejo/workflows/fragment-check.ymlper docs/integration.md if you want the pre-merge gate). Everything else is transparent.Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
-
Source code (ZIP)
-
Release v0.19.0
Stablereleased this
2026-06-28 23:43:04 +02:00 | 1256 commits to main since this releaseChanged
config_validatenow rejects an unsupportedversion_filesbasename at config-load time. The version-file extraction layer (manifest-check read + release-prep write) keys on each entry's basename and handles onlyVERSIONandpackage.json; previously a config listing an unextractable basename (e.g.pyproject.toml,Cargo.toml) passed validation and then failed late at manifest-check or release-prep. The gate moves that failure to the earliest layer with a clear message naming the unsupported basename, the supported set, and a pointer to the TOML-support tracker (#252). Configs usingVERSION,package.json, orversion_files: [](tag-is-version mode) are unaffected.release-prep.shnow auto-stages tracked files thatpost_bump_hooksmodify, retiring the "hooks mustgit addtheir own changes" convention from #209. A hook just modifies tracked files; release-prep snapshots the content of every tracked file before the hook loop andgit adds any whose content changed, so the edits are committed with the cut. Existing self-staging hooks keep working unchanged (a secondgit addof the same change is idempotent).
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
-
Release v0.18.1
StableAll checks were successfulcheck-self-bootstrap / check (pull_request) Successful in 3smanifest-check / manifest-vs-tag consistency (pull_request) Successful in 5smanifest-check / check (pull_request) Successful in 0scheck-self-bootstrap / check (push) Successful in 3srelease / decide + act (push) Successful in 6srelease / release (push) Successful in 0sreleased this
2026-06-28 21:54:28 +02:00 | 1262 commits to main since this releaseFixed
scripts/repin.shnow tags every[repin]output line with its mode --DRY-RUN:in dry-run,LIVE:in a real run (e.g.[repin] LIVE: detached bake commit: ...,[repin] DRY-RUN: PLAN). A dispatcher copy-pasting a bake-commit SHA from the output now sees the mode inline, so the dry-run bake (thrown away) can't be mistaken for the live one the rc tag actually points at. Closes a same-day n=2 mis-citation class that the per-re-pin verify discipline had been catching by hand.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
-
Release v0.18.0
StableAll checks were successfulcheck-self-bootstrap / check (pull_request) Successful in 3smanifest-check / manifest-vs-tag consistency (pull_request) Successful in 5smanifest-check / check (pull_request) Successful in 0scheck-self-bootstrap / check (push) Successful in 3srelease / decide + act (push) Successful in 6srelease / release (push) Successful in 0sreleased this
2026-06-28 20:44:09 +02:00 | 1266 commits to main since this releaseChanged
- The two reusable workflows are renamed to drop the
_prefix in favour of an explicitreusable-prefix:.forgejo/workflows/_release.yml->reusable-release.ymland_manifest-check.yml->reusable-manifest-check.yml. The_prefix was an informal "internal" community signal;reusable-states theworkflow_callshape outright and reads cleaner at the adoption surface (the consumer types the full reusable path in theiruses:line). The consumer-wrapper names (release.yml,manifest-check.yml) are unchanged, as is all workflow runtime behavior -- this is a pure rename.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
- The two reusable workflows are renamed to drop the
-
Release v0.17.1
Stablereleased this
2026-06-28 18:57:25 +02:00 | 1271 commits to main since this releaseFixed
- The Layer 2 / Layer 3 cut-safeguard now finds the merged cut PR via Forgejo's direct commit->PR lookup (
GET /repos/{owner}/{repo}/commits/{sha}/pull) instead of apulls?state=closed&sort=updated&limit=5list-and-filter. The old query paired an arbitrary 5-PR window with Forgejo'sissue_id-descendingsort=updatedordering, so a rolling cut PR created early but merged late could fall outside the window once more than a few PRs closed between cuts -- the safeguard then returned empty and the cut fell through tomode=update. (Anchor: the v0.17.0 cut, where rolling PR #227 sat at list position 11 of the limit=5 query and had to be recovered manually.) The direct lookup keys on the merged commit server-side and has no window to outgrow; a 404 (no PR for the commit, including the brief post-merge indexing-lag window) collapses to the existing empty-result contract that the caller's retry + protective fall-through already handle.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
- The Layer 2 / Layer 3 cut-safeguard now finds the merged cut PR via Forgejo's direct commit->PR lookup (
-
Release v0.17.0
StableAll checks were successfulcheck-self-bootstrap / check (pull_request) Successful in 3smanifest-check / manifest-vs-tag consistency (pull_request) Successful in 5smanifest-check / check (pull_request) Successful in 0scheck-self-bootstrap / check (push) Successful in 3srelease / decide + act (push) Successful in 6srelease / release (push) Successful in 0sreleased this
2026-06-28 18:30:33 +02:00 | 1276 commits to main since this releaseAdded
- hooks: dogfood post_bump_hooks for own doc ref-pins (#209)
- forgejo-api: emit api_call structured event (#204 part 1)
Fixed
- release-prep: relax origin requirement in dry-run (#216)
- cut path: add [skip ci] to bake-reset commit to suppress CI noise (#202)
Added
Added
examples/go-projectandexamples/node-project— complete drop-in consumer bootstraps (config + the two wrapper workflows + a CHANGELOG skeleton + version file) you cancp -rinto a new repo to adopt the toolkit, plus anexamples/README.mdindex. Post-Phase-2 shape (notoolkit_ref:input;@v0.16.0pin).api_callstructured event:forgejo_api_callnow emits one JSON event per real API call (endpoint,method,status,ms) to stderr, giving Loki-ingestable observability over the toolkit's Forgejo interactions including latency and non-2xx failures. Emitted at the wrapper on every real call (not in dry-run);msis the curl wall-clock in integer milliseconds. The emit is guarded soforgejo-api.shkeeps no hard dependency on the events substrate -- callers that sourcedevents.shget the event, others degrade cleanly to no-emit. (Part 1 of #204; the workflow-levelcut_complete.ms_totalremains deferred to the next compose-script cut.)The toolkit now dogfoods its own
post_bump_hookson its own docs: a new hook (scripts/hooks/update-doc-version-refs.sh), wired inrelease-toolkit.yml, re-pins the adopter-facing example refs inREADME.md+docs/integration.mdto the freshly-cut tag on every cut -- eliminating the manual doc-ref-pin drift class (#196 was the recurring manual fix; Shipwright #157 found the dogfooding gap). The hook preserves historical "feature-since" references (e.g.v0.12.0+) and pins only the@vX.Y.Z/Latest:/Replaceexample shapes. A newRELEASE_TOOLKIT_RELEASE_DATEenv var is exported to all post_bump_hooks (the cut date, matching the CHANGELOG heading) so hooks can refresh dated examples. The post_bump_hooks docs now also document the staging convention (hooks mustgit addtheir own changes; release-prep commits only staged files).- hooks: dogfood post_bump_hooks for own doc ref-pins (#209)
- forgejo-api: emit api_call structured event (#204 part 1)
Changed
docs: sweep adopter-facing ref pins (README + integration.md) to
@v0.16.0(#196).Fixed
Corrected adopter-facing
integration.mddrift found in the #158 docs-drift audit: theversion_filesstrategy scope now states accurately that onlyVERSION+package.jsonare handled (replacing the invented, mutually-contradictory "v0.3 scope"/"v0.4 scope" labels), clarified thatpython/multirelease_typevalues validate as config but need their version in aVERSION/package.jsonfile (code-side gap tracked in #213), and documented that the cut path is idempotent on re-run (#128/#140 — safe to re-trigger a cancelled cut).release-prep.sh --dry-runno longer requires a configuredoriginremote when none is present. Dry-run skips every remote write, and the sole use oforiginis owner/repo inference that (in dry-run) feeds only dry-run-noop API calls plus the echoedowner=/repo=outputs -- so a fresh scratch repo with no remote can now preview a cut. The real git-history read (LAST_TAGviagit describe) uses local tags, not the remote. A present-but-unparseable remote stays fatal even in dry-run (a genuine misconfiguration dry-run should surface), and production cuts still hard-requireorigin. Also fixes a latent silent-abort in the same path: an unparseable remote previously trippedset -eat the owner/repo assignment before the "could not derive owner/repo" diagnostic could print.Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
-
Release v0.16.0
StableSome checks failedcheck-self-bootstrap / check (pull_request) Successful in 3smanifest-check / manifest-vs-tag consistency (pull_request) Successful in 5smanifest-check / check (pull_request) Successful in 0scheck-self-bootstrap / check (push) Successful in 3srelease / release (push) Has been cancelledrelease / decide + act (push) Has been cancelledreleased this
2026-06-28 13:06:07 +02:00 | 1293 commits to main since this releaseRemoved
- The
toolkit_refworkflow_call input is removed from the reusables (_release.yml+_manifest-check.yml). The toolkit ref is now baked into the reusable at every pinnable point — cut tags at cut-prep, rc tags at re-pin via the newscripts/repin.sh(a detached bake commit the rc tag points at) — souses:@<ref>is the single source of truth and theresolve-refstep reads the baked value unconditionally. This removes the second ref surface that could drift (toolkit_ref:vsuses:@) rather than merely enforcing their alignment.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
- The
-
Release v0.15.0
Stablereleased this
2026-06-27 16:06:59 +02:00 | 1303 commits to main since this releaseAdded
- Structured JSON event logging substrate (
scripts/lib/events.sh):event_emit KIND key=value...emits one JSON object per event to stderr for Loki ingest. The cut path is instrumented withcut_decided,manifest_skip/manifest_proceed,cut_complete,repin_check, anderrorevents (release-toolkit#159).
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
- Structured JSON event logging substrate (