• v0.28.0 f759909bcb

    Release v0.28.0
    Some checks failed
    changelog-body-check / changelog body Cold-Read linter (pull_request) Successful in 7s
    changelog-body-check / check (pull_request) Successful in 0s
    check-self-bootstrap / check (pull_request) Has been skipped
    fragment-check / changelog fragment-kind (pull_request) Successful in 6s
    fragment-check / check (pull_request) Successful in 0s
    manifest-check / manifest-vs-tag consistency (pull_request) Successful in 8s
    manifest-check / check (pull_request) Successful in 0s
    register-check / register-drift check (pull_request) Successful in 6s
    register-check / check (pull_request) Successful in 0s
    tests / bats (pull_request) Successful in 1m51s
    tests / shellcheck (pull_request) Successful in 7s
    check-self-bootstrap / check (push) Failing after 4s
    release / decide + act (push) Successful in 6s
    release / release (push) Successful in 0s
    tests / bats (push) Successful in 1m51s
    tests / shellcheck (push) Successful in 12s
    mirror-on-cut / mirror-1 (release) Successful in 1s
    mirror-on-cut / mirror (release) Successful in 0s
    Stable

    release-bot released this 2026-07-06 21:12:56 +02:00 | 1086 commits to main since this release

    v0.28.0 tightens release-note hygiene and release-cut reliability. Register-check now covers commit subjects and composed text. CHANGELOG output is normalized and readability-gated. Orphan-check handles breaking-change footers and legitimate prep-PR merges. Codeberg mirroring is decoupled from the cut workflow.

    Added

    Per-line exemption for register-check. Files scanned by register-check sometimes legitimately use pattern words in adopter-relevant contexts — a role title in a doc, a program name in a script. A trailing # register-check: allow marker suppresses that one line's match (<!-- register-check: allow --> in Markdown and HTML). No action required. Scrubbing the pattern remains first-resort; .register-allowlist at file scope is second-resort; the line marker is the last resort. The --stdin scan called from release-prep.sh ignores markers, so composed content stays gated. Closes #396.

    Register-check now scans commit subjects and composed CHANGELOG content. Chamber-name attributions in commit subjects used to ride into CHANGELOG.md past the file-scan gate. The compose step read subjects the file scan never touched. Two new modes close the gap. --git-log RANGE scans commit subjects at PR review time. --stdin scans arbitrary text and runs from release-prep.sh on the composed section before it lands in CHANGELOG.md, catching drift from any source. Recommended for adopters using commit-subject → CHANGELOG composition (cc_list_commits_since / cc_categorize_commits_since): set check_commit_subjects: 'true' on the reusable-register-check.yml input. Fragments-only adopters can leave the default ('false'). Closes #403.

    Fragment authors can hard-wrap paragraphs without breaking the rendered CHANGELOG. release-prep.sh now normalizes compiled sections before they transition into CHANGELOG.md. Blank lines stay paragraph boundaries. Structural markdown — lists, headers, blockquotes, fenced code — passes through verbatim. Continuation lines within a paragraph join with a single space. A hyphen-word rejoin heuristic catches breaks inside hyphenated words. If a residual [a-z]- [a-z] pattern survives, the step fails loud on the offending source so the fragment can be fixed before release-prep ships. No action required. Hard-wrapping fragments at ~72 columns continues to work. Closes #420.

    CHANGELOG bodies get a readability check. changelog-body-check now flags long sentences, deep parenthetical nesting, and long paragraphs on newly-composed sections before the release-prep PR reaches operator eyes. Defaults: sentence fails at 30 words (warns at 25), paragraph fails at 100 words (warns at 75), parenthetical nesting fails above depth 2. Historical sections stay out of scope — the check targets the newest version block only. Recommended for adopters wanting different thresholds: override via new inputs on reusable-changelog-body-check.ymlsentence_max_words, sentence_warn_words, paren_depth_max, paragraph_max_words, paragraph_warn_words. Author-side guidance lives in docs/conventions.md "Writing adopter-facing prose". Closes #421.

    Changed

    Orphan-check now catches BREAKING CHANGE: body-footer commits. A refactor: or chore: commit whose body carries a BREAKING CHANGE: footer is bump-worthy per conventional-commits. The pre-fix range-walk in release-decide.sh keyed only on the subject and false-skipped them. release-decide.sh now delegates release-relevance to cc_bump_level_from_subject in scripts/lib/conventional-commits.sh. The helper handles subject-level feat! / fix! markers AND body-level BREAKING CHANGE: footers, so orphan-check uses the same rules as the release-cut bump logic. Any future addition to the bump-worthy type set (for example, perf:) propagates automatically. No action required. Closes #424.

    check-self-bootstrap docstrings clarify their scope. The script and workflow headers now open with "Pin-drift detection, NOT content validation." A red on a PR that touches a compose-script means the mechanism is firing correctly. The pinned ref needs to advance to catch main up at the next cut. It does not mean something is wrong with the code. Content-quality gates (shellcheck, bats, register-check) are named explicitly so a reader triaging a failure knows where content-questions live. No action required. Closes #432.

    Fixed

    Orphan-check no longer false-fires on a legitimate cut — v1.0.0 must-fix follow-up to #417. Release-toolkit's own v0.27.0 cut on PR#406 tripped release-decide.sh with the exact fail-loud the detector was designed to emit. The detector's signature — semver_compare(CHANGELOG_TOP, manifest.last_released) == 1 — matches both an orphan-post-cancellation AND a legitimate cut-in-progress. Both have CHANGELOG advanced, manifest not yet updated, and no tag yet. The distinguisher is action-context, not signature.

    release-decide.sh now reads HEAD_SUBJECT early and matches it against PREP_SUBJECT_RE. When HEAD IS a prep-PR merge commit AND the version it cuts equals CHANGELOG_TOP, the check skips — that is the legitimate cut-about-to-fire path. Any other HEAD shape (a bystander push after a cancelled cut) still fires the fail-loud. Both prep-merge subject styles are handled: chore(release): prepare vX.Y.Z (merge / rebase) and chore(release): vX.Y.Z (squash-merge using the PR title). Three new regression tests lock the behavior. No action required. Closes #417.

    Codeberg mirror no longer runs inside the cut workflow. A new .forgejo/workflows/mirror-on-cut.yml triggers on the release: published event. It fires either on immediate-mode publication, or when the operator clicks Publish on a draft.

    The in-graph mirror: job is removed from release.yml. It combined continue-on-error: true with a uses: job, which Forgejo Actions correctly rejects per the GitHub Actions schema. Mirror-outages, PAT-rotations, and Codeberg-side timeouts no longer cascade into cut-failure. Mirror-failure never touches the cut workflow's job graph.

    Adopter action required only if you fork the toolkit's workflow files and mirror to Codeberg from your own copy. Move the mirror job into a separate release: published-triggered workflow rather than a mirror: job inside release.yml. The reusable-mirror-to-codeberg.yml docstring shows the new pattern. Closes #427.

    Removed

    None.

    Deprecated

    None.

    Upgrade

    • Optional: set check_commit_subjects: 'true' on reusable-register-check.yml if you compose CHANGELOG entries from commit subjects. Default remains 'false' for fragments-only adopters. See #403.
    • Optional: override readability thresholds via new inputs on reusable-changelog-body-check.ymlsentence_max_words, sentence_warn_words, paren_depth_max, paragraph_max_words, paragraph_warn_words. Defaults follow the "Writing adopter-facing prose" convention. See #421.
    • Required if you fork the toolkit's workflow files and mirror releases to Codeberg from your own copy: move the mirror job into a separate release: published-triggered workflow rather than an inline mirror: job in release.yml. Toolkit-self's own mirror-on-cut.yml is a working example. See #427.
    Downloads