• 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 | 1234 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.yml — sentence_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.yml — sentence_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
  • v0.27.0 b36955e148

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

    bosun released this 2026-07-06 14:12:12 +02:00 | 1258 commits to main since this release

    v0.27.0 hardens release-toolkit for real Forgejo and Codeberg adoption ahead of v1.0: bounded, retryable API calls with pagination; per-project publish_mode overrides; Codeberg proven end-to-end; stale docstring pins cleaned up; and protection against cut cancellation.

    Added

    Forgejo API calls are now bounded and retryable: a 5s connect timeout and 30s total timeout on every call (env-tunable via FORGEJO_API_CONNECT_TIMEOUT_S / FORGEJO_API_TIMEOUT_S), with exponential backoff on 429 / 5xx / network errors. Tag and PR-list lookups now paginate, fixing missed results on repos with more than one page of tags or PRs. Retries are safety-gated: 429 is retried on any method, but 5xx / network errors are retried only for idempotent methods — a POST / PATCH is not retried by default (the server may have partially applied it) unless you set FORGEJO_API_RETRY_UNSAFE=1 (#334; v1.0.0 must-fix).

    You can now change publish_mode per project without editing your workflow: set vars.RT_PUBLISH_MODE in Forgejo → Settings → Actions → Variables, or add publish_mode: to release-toolkit.yml. Precedence, highest first: repo variable → config file → workflow with: input → the draft default (#332). Invalid values fail loud with the resolution chain shown (#408; v1.0.0 must-fix).

    Integration docs now collect the per-project override options in one table, with worked examples for config-file, repo-variable, and one-off dispatch overrides (#409).

    Changed

    release-toolkit is now demonstrated end-to-end on Codeberg's shared runners: the tic-tac-toe companion demo runs the full flow from fresh-repo bootstrap through cut to a draft release. The README and integration docs now describe Codeberg as proven rather than assumed, link the demo, and add a short "Running on Codeberg" note covering how to enable Actions and Releases and the no-token fallback (#412).

    Fixed

    Reusable workflow docstring examples now use @vX.Y.Z placeholders instead of concrete pins that had drifted stale; the copy-paste-ready refs in the docs and examples stay pinned and are kept fresh by the post-cut version-ref hook (#407).

    Cut-cancellation recovery. If another push lands while a cut is queued, the cut can be cancelled after the CHANGELOG section moved but before the release tag exists — leaving an orphaned ## [vX.Y.Z] section that the next run would otherwise re-emit as a duplicate entry.

    Recommended: add the new concurrency block from integration.md to your consumer workflow to prevent the cancellation. If you skip it, cuts stay safe — the toolkit fails loud on the state divergence — but an interrupted cut may need manual recovery.

    • Prevention (adopter-side): the consumer wiring template in docs/integration.md now includes a workflow-level concurrency: block (group: release-cut-${{ github.ref }}, cancel-in-progress: false) that queues later pushes behind the in-flight cut.
    • Detection (toolkit-side): release-decide.sh fails loud when CHANGELOG.md's top-most released section is ahead of manifest.last_released_version — the orphan signature — and lists two recovery options (finish the pending release and update the manifest, or move the section back under [Unreleased]). Auto-heal is out of scope for v1.0.0.

    Closes #417.

    Downloads
  • v0.26.0 512aa1a408

    Release v0.26.0
    Some checks failed
    changelog-body-check / changelog body Cold-Read linter (pull_request) Successful in 4s
    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 4s
    fragment-check / check (pull_request) Successful in 0s
    manifest-check / manifest-vs-tag consistency (pull_request) Successful in 5s
    manifest-check / check (pull_request) Successful in 0s
    register-check / register-drift check (pull_request) Successful in 4s
    register-check / check (pull_request) Successful in 0s
    tests / bats (pull_request) Successful in 1m33s
    tests / shellcheck (pull_request) Successful in 8s
    check-self-bootstrap / check (push) Failing after 3s
    release / decide + act (push) Successful in 6s
    release / release (push) Successful in 0s
    tests / bats (push) Successful in 1m34s
    tests / shellcheck (push) Successful in 12s
    release / mirror (push) Successful in 2s
    Stable

    release-bot released this 2026-07-05 16:46:34 +02:00 | 1278 commits to main since this release

    Added

    CI test suite as required merge gate — new .forgejo/workflows/tests.yml runs full bats tests/ + shellcheck --severity=warning on every PR + push:main. ADR-0001 named shellcheck as CI gate; this workflow implements it. One pre-existing SC1010 false-positive in scripts/lib/forgejo-api.sh (jq variable named do) fixed by rename to merge_do (#330).

    docs/SECURITY.md — new page documenting the release-toolkit trust boundary + the audit-scope gap between consumer-pinning and secondary-checkout script execution. Honest disclosure of the current state; architectural fix targets v1.1.0 (#336).

    docs/VERSIONING.md — new page memorializing the v1.0.0 SemVer stability contract: v1.x preserves backward compat on workflow inputs, config schema, published script CLI, and documented behavior contracts; breaking changes require v2.0.0; deprecation cycle spans a minor before removal; tag immutability commitment; post-v1.0.0 cadence shift to boring/settled infrastructure (#338).

    New docs/cold-read-adopter.md brings the full-project adopter-simulation cold-read prompt in-repo (previously BookStack-only) — the prompt used to prime outside-model reviews of the whole toolkit. docs/cold-read-prompt.md is renamed to docs/cold-read-changelog.md (distinguisher-leads naming, matching its # Changelog Cold-Read Prompt heading); inbound references updated (#366).

    Mechanized pre-merge register grep-gate (scripts/register-check.sh + reusable-register-check.yml). Case-insensitive sweep across scripts/ + .forgejo/ + tests/ + docs/ + changelog.d/ + README.md + CHANGELOG.md + AGENTS.md for chamber-name attribution

    • invented-jargon vocabulary. Fails PR merges on hits; extensible
      pattern list (bash array) + .register-allowlist for load-bearing exceptions. Closes #392.

    Empirical rationale: manual per-round sweeps don't stay closed under continued PR throughput. Round-4 alone saw n≥3 same-day re-seedings across concurrent-authorship PRs — careful authors crediting reviewers inline is the recurring failure mode. Manual sweep as a discipline is falsified at this cadence; CI grep-gate is the fix by construction.

    Adopter wiring: drop .forgejo/workflows/register-check.yml invoking frankenbit/release-toolkit/.forgejo/workflows/reusable-register-check.yml@v1.0.0 on your pull_request: branches: [main] trigger. Allow-list at .register-allowlist (glob patterns; directory-prefix dir/ for recursive allow) opts out load-bearing exceptions like compiled-changelog historical records or maintainer archaeology.

    Changed

    Adopter-visible header comments tightened — top-of-file comment blocks in reusable-mirror-to-codeberg.yml and toolkit-self's release.yml scrubbed of internal-review vocabulary ("substrate-of-record", "dogfoods its own new mechanic") + shortened. Consumer wiring pattern + design references preserved; canonical vocabulary in ADRs untouched. Set J adopter-hygiene sweep (#313).

    BREAKING (v1.0.0-blocking): publish_mode default flipped from immediate back to draft. Cold-read finding: README advertised "gates every release behind a human" but v0.10.0 immediate-default let the merge of the rolling PR publish directly, no Gate-3 click. The fix restores draft as the default so the advertised safety property holds by default. Adopters who value frictionless CI cadence over the per-cut Gate-3 opt in via publish_mode: 'immediate' at the workflow_call layer. Adopters currently relying on the immediate-default must explicitly opt in when bumping to this release. (#332)

    docs/integration.md — tighten the version-file-basename scope note: the two #252 refs (now closed as superseded) point at the broader-ecosystem tracker #337, which covers TOML (pyproject/Cargo), Maven, Helm, and other release-please-shaped ecosystems. Also names a workable escape hatch for unlisted ecosystems today (version_files: [] + consumer hook).

    docs/integration.md — new Token Decision Matrix (3 yes/no questions → adopter's token setup with minimum scope), Sample failure modes section naming the 4 concrete errors adopters actually hit (Repository not found, push_whitelist pre-flight FAIL, case-sensitive push-denied, mergeable=blocked on manifest PR), Bot user provisioning step-by-step for path α (Forgejo user + collaborator + PAT scope + whitelist + secret), and a concrete secrets: inherit wiring example. Retires the "token model is complicated" trust-dent surfaced by external cold-read round 2 (#356).

    README.md + docs/integration.md — new Runner requirements section naming explicitly what the toolkit assumes: Debian/Ubuntu-family runner with apt-get + sudo for install-on-demand of yq. Per-requirement table names root cause + what to do if your runner is non-Debian / rootless / ephemeral (pre-bake yq+jq+curl so the install step is a no-op). Configurable install commands + auto-skip logic named as v1.1.0+ target. Retires the "workflow installs deps with apt-get/sudo, not universally safe" trust-dent surfaced by external cold-read round 2 (#357).

    docs/integration.md — new Tag creation section documenting the mechanism explicitly: Forgejo release API creates the tag via tag_name + target_commitish (no git tag && git push --tags); tag points at the rolling-PR merge commit; idempotency behavior enumerated (same-tag-same-commit skips; same-tag-different-commit fails loud; same-tag-on-Forgejo-missing-local skips via pre-check); cross-variant status per Forgejo/Codeberg/Gitea. Retires the "delegated tag creation needs proof" trust-dent surfaced by external cold-read round 2 (#359).

    scripts/release-decide.sh + scripts/lib/conventional-commits.sh + docs/integration.md + README.md + examples/README.md + tests — BREAKING for the fresh-repo failure surface, no regression for repos with a real anchor: fresh consumer repo (no manifest, no stable tag) now bootstraps from a virtual v0.0.0 anchor and walks from the initial commit (#368). First cut produces v0.1.0 (any feat: since root), v0.0.1 (only fix:), or mode=noop (only chore:/docs:/refactor: — chore isn't release-relevant per conventional-commits; adopter adds a feat:/fix: or drops a bump/* label to force the cut). SUPERSEDES the #355 Option B fail-loud path shipped in v0.25.0: adopters no longer need to manually tag v0.0.0 before the first cut. cc_list_commits_since gains a --root sentinel so the virtual-anchor bump-walk stays root-inclusive even when a prerelease tag exists (prerelease v-tags matched git describe --match 'v*' and would silently drop the root feat: from the bump walk despite step-2 seeing it). A feat!: / BREAKING CHANGE at root bumps to v1.0.0 by default; set pre_v1_breaking_to_minor: true for the pre-1.0 opt-in that downgrades to v0.1.0. Advanced adopters can still hand-write .release-toolkit-manifest.json; that path is unchanged.

    docs/VERSIONING.md + docs/SECURITY.md — retire the "as of v1.0.0" / "target v1.1.0" future-tense-in-current-adopter-surface leaks external cold-read round 3 flagged. VERSIONING.md rewritten to describe current pre-1.0 state (Option A) + a "will commit at v1.0.0" preamble frames the effective-at-v1.0.0 sections without ambiguity. SECURITY.md's architectural-fix section labeled "Planned for v1.1.0" with an explicit callout that it describes not-yet-shipped work (Option B — preserves the fix-target framing). Example pin swapped from @v1.0.0 (aspirational) → @v0.25.0 (current). Tag-immutability section reframed as "already active" (the discipline was already in force pre-1.0, not a v1.0.0 addition). Adopters reading v0.25.0 docs no longer confuse future contract with current guarantee (#369).

    Adopter-facing docs read register-clean — internal review-process language (per-reviewer attributions, ratify ceremony) scrubbed from CHANGELOG.md + docs/adr/*.md, so the project no longer reads as internally process-driven. Technical content and issue/ADR anchors are preserved and the documented cold-read editorial discipline is kept + glossed; only the internal-review narration is removed. Third register-audit delta after #352 + #362 (#372).

    README.md + docs/integration.md — new Positioning section declaring release-toolkit Codeberg-primary (source, releases, and tags at codeberg.org/frankenbit/release-toolkit) and targeting Forgejo Actions as its CI runtime, with a runtime-compatibility list (Forgejo Actions tested; Gitea Actions untested; Codeberg where a runner is available). This is the definitive answer to the adopter positioning question external cold-read raised (rounds 2–4): where the project lives (Codeberg) and what CI runtime it targets (Forgejo Actions) are stated as separate facts so neither is ambiguous. The maintainers develop on a self-hosted Forgejo and mirror code + releases to Codeberg (#358, #397).

    Fixed

    examples/README.md + examples/{go,node}-project/.forgejo/workflows/*.yml — bumped stale @v0.16.0 pins to the current released tag @v0.25.0, closing the trust-dent surfaced by external cold-read round 2 (adopters landing on stale copy-paste refs). Extended scripts/hooks/update-doc-version-refs.sh to walk examples/**/*.yml + examples/README.md on every future cut so the drift can't recur; the hook silently no-ops when a consumer doesn't have an examples/ directory (#311).

    Mirror workflow: empty-TAG_NAME guard as Forgejo-agnostic first-check. Forgejo Actions doesn't reliably honor needs.<reusable-call-job>.outputs in a job-level if: gate (empirical: mirror fired on non-cut push:main events), causing spurious workflow failures on every non-cut push despite if: mode == 'cut'. Script-level guard exits cleanly on empty TAG_NAME + sidesteps the outputs-based gating question; wrong-commit class was already closed by the tag-guard (#322).

    SemVer parser now enforces the full SemVer 2.0.0 grammar — leading zeros in numeric identifiers, empty prerelease/build identifiers, and leading/trailing dots are rejected at parse time. Previous regex accepted these invalid values and then crashed on bump (bash-octal interpretation of 1.2.08 → 08: value too great for base). Base-10 arithmetic (10#$var) added to semver_bump as belt-and-suspenders. 20 new test cases cover every invalid-input class from the cold-read finding (#329).

    Squash-merge cut detection — PREP_SUBJECT_RE in release-decide.sh now accepts both chore(release): prepare vX.Y.Z (merge/rebase style) and chore(release): vX.Y.Z (Forgejo squash-merge style using PR title). Layer 2 branch-source-check remains the belt-and-suspenders gate. Adopters following standard Git squash-merge conventions were silently losing their release cut before this fix (#331).

    Token docs cleanup — removed all references to the unimplemented release_token (path β) that shipped in docs but never wired in the workflow. Rewrote the no-token path (γ) section: dedicated PAT (path α) is operationally required for repos with required-checks branch protection, not merely optional. Path (γ) fits only repos where main accepts unrestricted merges. Docs now match actual workflow behavior (#333).

    Config schema version now enforced against supported set — config_validate in scripts/lib/config.sh maintains SUPPORTED_SCHEMA_VERSIONS=(1) and rejects unsupported versions with a clear error naming what's supported. Previously any integer passed (version: 999 silently accepted). Forward-compat: when schema v2 arrives, add 2 to the array. +4 test cases (#335).

    scripts/repin.sh no longer defaults REPIN_REVIEWER to an internal reviewer username. The default is now empty and the PR review-request is skipped when unset, so an adopter running repin.sh no longer opens a re-pin PR requesting review from a user that doesn't exist in their repo. Set REPIN_REVIEWER=<user> to request a specific reviewer. (#340)

    README.md + docs/integration.md + docs/SECURITY.md + docs/events-logging.md + docs/VERSIONING.md + docs/migration/tmux-tell.md + ADRs 0001/0002/0003 — v1.0.0 pre-cut Cold-Read delta (#353): quick-start publish_mode example dropped so the reusable's draft default applies (was contradicting the "gates every release" claim, same class as #332); ADR-0003 now records the v1.0.0 supersession — the 2026-06-26 immediate-default amendment is retired, and the default-record matches the shipped code; adopter-facing docs no longer cite AGENTS.md (maintainer doc) — replaced with public references to the CI gate + inlined content; ADRs no longer link alcatraz-infra#65 (cross-repo private tracker) — replaced with plain-prose "initial architecture proposal"; docs/VERSIONING.md glosses paths α/γ + mode = noop|update|cut with links to ADR-0007 / ADR-0004; docs/migration/tmux-tell.md reframed as a pointer + tmux-tell-specific notes (drops "Status: stub" language).

    scripts/release-decide.sh + README.md + docs/integration.md + examples/README.md — BREAKING for docs-honesty, no behavior regression: fresh consumer repo (no .release-toolkit-manifest.json AND no stable vX.Y.Z tag) previously failed with a terse FATAL: no stable vX.Y.Z tag found; docs implied fresh repos "just worked" — external cold-read round 2 named this as the biggest trust-dent for first-user experience (#355). Fixed by (a) making the error actionable — names the exact git tag -a v0.0.0 && git push origin v0.0.0 command, points at the docs section, mentions the manifest hand-write escape hatch — and (b) documenting the anchor requirement upfront: new "Bootstrapping a new consumer repo" section in docs/integration.md naming both anchor options; README quick-start now has a "step 0" for the tag; examples/README.md post-copy checklist ends with the bootstrap command. Behavior unchanged: no code path that previously succeeded now fails; only the failure surface (message + docs coverage) improved.

    .forgejo/workflows/reusable-release.yml + scripts/lib/forgejo-api.sh + scripts/lib/config.sh + docs/integration.md — layered default-branch resolution replaces the hardcoded "main" at the manifest follow-up PR path. New resolve_default_branch helper in forgejo-api.sh walks 6 fallback layers: (1) RT_DEFAULT_BRANCH env var, (2) default_branch: field in release-toolkit.yml, (3) TOOLKIT_DEFAULT_BRANCH_HINT env var (workflow YAML passes ${{ github.event.repository.default_branch }}), (4) Forgejo API GET /repos/{owner}/{repo}, (5) git symbolic-ref refs/remotes/origin/HEAD, (6) fail-loud with actionable error naming all 5 preceding layers. New config_get_default_branch in config.sh exposes layer 2. Adopters on non-main default branches (trunk/master/develop/custom) previously hit a silent-corruption class: manifest PR opened against a nonexistent branch or one with unrelated content. Full docs in docs/integration.md § Default branch (#370). Regression tests cover all 6 layers (#370).

    scripts/lib/forgejo-api.sh + scripts/setup-bump-labels.sh — keep the FORGEJO_TOKEN out of curl's argv (visible via /proc/<pid>/cmdline + ps on some systems). All 6 previously-argv-exposing curl invocations refactored to use a 0600-mode auth config file (install -m600, -K "$auth_config"), matching the pattern already established in reusable-mirror-to-codeberg.yml:184-193. New shared helper _forgejo_auth_config_new in the lib consolidates the mktemp+install+write dance across the 3 lib callsites; setup-bump-labels.sh inlines the same shape at script start with a trap cleanup. Closes the argv-vs-config-file inconsistency between the two curl surfaces on the same substrate (#371).

    .forgejo/workflows/reusable-release.yml + scripts/release-prep.sh + scripts/repin.sh + scripts/manifest-precheck.sh — semantic-class sweep for hardcoded default-branch references missed by #370's literal-main sweep. Path-α direct-push (2 sites) + git_push_authed helper (1 site) + repin PR base + manifest-precheck remote-read: all now use resolve_default_branch (6-layer chain) or RT_DEFAULT_BRANCH env var. Adopters on non-main default branches (trunk/master/develop/custom) no longer hit silent-corruption at any push callsite. New tests/hardcoded-defaults-sweep.bats (7 grep-guards) locks the class-axis so future edits can't reintroduce the anti-patterns (#379).

    scripts/release-decide.sh + docs/integration.md — .release-toolkit-manifest.json anchor precedence rule for the last_released_sha vs last_released_tag disagreement class. When both fields are set AND the local tag exists AND its SHA disagrees with last_released_sha, release-decide.sh now fails loud with an actionable error naming both SHAs + three resolution options (tag authoritative / manifest authoritative / re-bootstrap by deleting manifest). Shallow clones and tags-pruned setups where the tag isn't local are treated as "cannot verify" — the check is skipped rather than erroring, so those adopters aren't blocked. Docs section explains the three divergence scenarios (force-push after cut / manual tag surgery / cross-repo mirror desync) + why v1.0.0 ships fail-loud rather than silent-manifest-wins (#380).

    .forgejo/workflows/reusable-release.yml + scripts/release-prep.sh — all git -c http.extraheader="Authorization: token $TOKEN" push argv-token exposures refactored to write the extraheader to a mktemp+chmod 600 config file scoped via GIT_CONFIG_GLOBAL. Same semantic class as #371 (curl argv-token → config file) and alcatraz-infra#89 (token in git remote URL): grep-sweep-missed-sibling because those sweeps targeted their specific literal patterns, not the semantic class of "token visible outside process env." Token now stays in the mode-0600 file + env (process-owned), never in argv — invisible to ps auxww + auditd audit logs. Regression test in tests/hardcoded-defaults-sweep.bats (grep-guard on git -c ... http.extraheader pattern) locks the class-axis (#381).

    scripts/draft-release.sh header comment (lines 1-8) refreshed to describe current v1.0.0 behavior: publish_mode default is draft (per #332) not immediate as the pre-#332 header claimed; the "cut path merges to main" phrasing now points at the consumer's resolved default branch (per #370) rather than hardcoding main (#383).

    docs/integration.md § Dry-run preview rewritten to accurately describe the three-script dry-run flow (release-decide.sh / release-prep.sh / draft-release.sh) — what mutates locally (release-prep.sh transitions CHANGELOG + bumps version_files in place), what emits summaries (draft-release.sh Forgejo API METHOD POST / URL / BODY), what skips (git remote writes + Forgejo API calls). Corrects the pre-fix "still needs an origin remote" note: origin is NOT needed for the git-log walk (release-decide.sh reads last_released_sha from the LOCAL manifest + walks LOCAL history); origin IS used for OWNER/REPO derivation (dry-run substitutes OWNER/REPO placeholder if missing) + Forgejo API calls (dry-run summarizes). Remote-less clones work for dry-run (#385).

    • security+workflow: semantic-class sweep for hardcoded-main + argv-token (#379 + #381) (#386)
    • workflow: move #370 env vars to correct step + wire config_get + docstrings
    • tests: preserve v0.10.0+ default marker in mutation-guard fixture

    Internal

    Docs reclassify — docs/cold-read/, docs/drift-audit-*, and docs/walkthrough-* moved to docs/internal/. Adopter-facing root docs/ now contains only canonical adopter documentation (integration, conventions, operations, architecture, adr, migration); maintainer audits + walkthrough artifacts live under docs/internal/ with a bucket-README explaining the split (#312).

    Workflow comments scrubbed of internal incident-history — the .forgejo/workflows/*.yml docblocks and inline comments no longer carry accumulated bug-archaeology (probe-iteration logs, dated cross-repo incident recoveries, version-specific cut-failure narration, internal review-vocabulary). Load-bearing invariants and issue/ADR anchors are kept; only the narration is removed, so the reusable workflows read as auditable machinery rather than internal history (#340).

    Script, workflow, and test comment blocks scrubbed of internal review-role attributions (per-reviewer names + short hex-id tags). Round-3 (#372) closed the prose surface (README + docs/ + ADRs + CHANGELOG); this closes the sibling code-comment surface — scripts/*.sh + .forgejo/workflows/*.yml + tests/*.bats — that an adopter reads when auditing the toolkit. Load-bearing technical invariants and issue/ADR anchors are kept; only the attribution narration is removed, with no behavior change (one test fixture genericized behavior-preservingly, mutation-verified green). (#384)

    AGENTS.md (contributor pre-flight doc) scrubbed of internal review-role attributions + invented-jargon — the last manual surface in the register-audit sweep (sibling to #384). Per-reviewer name+hex attributions removed from the disciplines (the guidance content is kept), the historical convergence-arc section trimmed of its internal narration (catch-count table + forward-going guidance kept), and a functional reviewer-identity default genericized. Round-3 held this doc as maintainer-surface; round-4 external reached it, so it's covered now (#391).

    docs/internal/ — moved the point-in-time maintainer archaeology (the 2026-06-27 consumer-walkthrough audit and documentation-drift audit) out of the source tree to the maintainers' internal wiki, and cleaned the remaining cold-read/ framework of internal-review-role names so a source-tree browser sees a neutral external-doc-review framework. Part of the Codeberg-primary shift: source-tree visibility is now the adopter-facing surface, so point-in-time internal-process artifacts are archived off-tree while the active cold-read/ maintainer tool stays in-repo, provenance preserved (#398).

    Downloads
  • v0.25.0 97bb807648

    Release v0.25.0
    All checks were successful
    check-self-bootstrap / check (push) Successful in 3s
    release / decide + act (push) Successful in 6s
    release / release (push) Successful in 0s
    release / mirror (push) Successful in 2s
    Stable

    release-bot released this 2026-07-03 12:44:58 +02:00 | 1342 commits to main since this release

    Added

    Codeberg-mirror reusable workflow — .forgejo/workflows/reusable-mirror-to-codeberg.yml publishes each cut's release to the Codeberg mirror. Toolkit-self wires it in release.yml as a needs: release job gated on mode == 'cut'. Idempotent (GET-before-POST) + warn-not-fail on Codeberg errors (#309).

    Historical mirror script for Codeberg (dev-tool). One-shot migration of existing releases to codeberg.org/FrankenBit/release-toolkit via scripts/dev-tools/migrate-releases-to-codeberg.sh. Idempotent + rate-limit-aware + order-preserving (#310).

    Downloads
  • v0.24.0 acb6db8521

    Release v0.24.0
    All checks were successful
    changelog-body-check / changelog body Cold-Read linter (pull_request) Successful in 4s
    check-self-bootstrap / check (pull_request) Has been skipped
    changelog-body-check / check (pull_request) Successful in 0s
    fragment-check / changelog fragment-kind (pull_request) Successful in 4s
    fragment-check / check (pull_request) Successful in 0s
    manifest-check / manifest-vs-tag consistency (pull_request) Successful in 5s
    manifest-check / check (pull_request) Successful in 0s
    check-self-bootstrap / check (push) Successful in 4s
    release / decide + act (push) Successful in 6s
    release / release (push) Successful in 0s
    Stable

    release-bot released this 2026-07-03 10:49:44 +02:00 | 1347 commits to main since this release

    Changed

    check-self-bootstrap now skips on release-prep/* rolling PRs. The rolling PR is the surface where "the mechanism that would fix the drift IS the drift" — the check fires genuine content-differences that aren't actionable AT that PR (the next cut resolves them via #283 auto-re-pin). Regular feature PRs and push:main keep firing the check unchanged; only release-bot's release-prep/ head branches are exempted. Per operator directive: zero-failing-runs on release PRs as a general goal. Closes #304.

    Fixed

    • ci: harden #304 rolling-PR exemption per Surveyor 41ce
    Downloads
  • v0.23.0 11c990bbd2

    Release v0.23.0
    Some checks failed
    changelog-body-check / changelog body Cold-Read linter (pull_request) Successful in 4s
    changelog-body-check / check (pull_request) Successful in 0s
    check-self-bootstrap / check (pull_request) Failing after 3s
    fragment-check / changelog fragment-kind (pull_request) Successful in 4s
    fragment-check / check (pull_request) Successful in 0s
    manifest-check / manifest-vs-tag consistency (pull_request) Successful in 5s
    manifest-check / check (pull_request) Successful in 0s
    check-self-bootstrap / check (push) Failing after 4s
    release / decide + act (push) Successful in 6s
    release / release (push) Successful in 0s
    Stable

    release-bot released this 2026-07-02 23:41:14 +02:00 | 1354 commits to main since this release

    Post-v0.22.0 correctness fix: retire the release-notes sidecar so edits to CHANGELOG.md actually reach the Forgejo release page.

    Removed

    BREAKING: .release-toolkit-release-notes.md sidecar retired — CHANGELOG.md is the single source of truth for the release-body content. The v0.5.0-introduced sidecar (fed by fragment summary: frontmatter) was preferred by draft-release.sh over the CHANGELOG section, so edits to CHANGELOG.md were silently ignored on the release page. draft-release.sh now always reads the CHANGELOG section. release-prep.sh no longer writes a sidecar and deletes any legacy committed sidecar on the first post-#301 cut so the file stops shipping stale content. summary: frontmatter on fragments becomes inert (safe to leave or strip). Closes #301.

    Upgrade

    If your repo has committed a .release-toolkit-release-notes.md file from a pre-v0.23.0 cut, delete it:

    git rm .release-toolkit-release-notes.md && git commit -m "chore: drop retired sidecar per release-toolkit#301"
    

    draft-release.sh ignores the file post-v0.23.0, so leaving it in place is a no-op (not an error). Nothing else needed to upgrade.

    Downloads
  • v0.22.0 9caa7644ec

    Release v0.22.0
    All checks were successful
    changelog-body-check / changelog body Cold-Read linter (pull_request) Successful in 4s
    changelog-body-check / check (pull_request) Successful in 0s
    check-self-bootstrap / check (pull_request) Successful in 3s
    fragment-check / changelog fragment-kind (pull_request) Successful in 4s
    fragment-check / check (pull_request) Successful in 0s
    manifest-check / manifest-vs-tag consistency (pull_request) Successful in 5s
    manifest-check / check (pull_request) Successful in 0s
    check-self-bootstrap / check (push) Successful in 3s
    release / decide + act (push) Successful in 6s
    release / release (push) Successful in 0s
    Stable

    release-bot released this 2026-07-02 21:31:16 +02:00 | 1359 commits to main since this release

    Post-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.yml invokes the toolkit's own reusable-changelog-body-check.yml on every PR that touches CHANGELOG.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 as fragment-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_wrappers and 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.yml in #278, changelog-body-check.yml in #290) required updating each list, and a missed update produced silent single-ref-alignment drift post-cut. Closes #295.

    changelog_dedup_cc_by_fragment_refs now catches scope-as-issue-ref bullets — commits with fix(#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
  • v0.21.0 0a814c85e0

    Release v0.21.0
    Some checks failed
    check-self-bootstrap / check (pull_request) Failing after 3s
    fragment-check / changelog fragment-kind (pull_request) Successful in 4s
    fragment-check / check (pull_request) Successful in 0s
    manifest-check / manifest-vs-tag consistency (pull_request) Successful in 6s
    manifest-check / check (pull_request) Successful in 0s
    check-self-bootstrap / check (push) Failing after 3s
    release / decide + act (push) Successful in 7s
    release / release (push) Successful in 0s
    Stable

    release-bot released this 2026-07-02 17:23:13 +02:00 | 1369 commits to main since this release

    Changelog 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.yml reusable workflow + scripts/changelog-body-check.sh. Runs 6 mechanical checks on the newest version-block in CHANGELOG.md at 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 their pull_request workflow same shape as fragment-check.yml. Toolkit-self dogfood wiring deferred to #290. Closes #282 Layer 1.

    Fragment template + brevity docs — new changelog.d/.template.md starter template + docs/conventions.md ### Fragment brevity section. 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.yml now auto-re-pins release.yml + manifest-check.yml + fragment-check.yml to 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 that check-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 — the cut_complete JSON event now carries ms_total (total cut wall-clock in integer milliseconds) and path (the alpha/gamma push-path). Off the workflow path (direct draft-release.sh invocation, or platform date lacking %N) both fields degrade to empty — the same graceful-degradation contract as api_call's ms. Completes the JSON-logging taxonomy from #159. (Part 2 of #204.)

    Changed

    check-self-bootstrap.yml scope 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.yml stays as a substrate-failure fail-safe.

    docs/integration.md post-#172 migration note — corrected to reflect empirically-observed Forgejo behavior: undeclared inputs to workflow_call reusables are silently ignored. The lazy-leave shape (bump @<ref> past #172, forget to drop toolkit_ref:) runs cleanly. Dropping the stale input is a hygiene fix, not a correctness fix. Closes #250.

    Fixed

    Fragment-check baked ref stuck at main for v0.20.0 consumers — v0.20.0 shipped reusable-changelog-fragment-check.yml with BUILD_BAKED_TOOLKIT_REF: 'main' because reusable-release.yml's three runtime bake-reset loops (added in #278) only listed the two pre-existing reusables. Impact for consumers pinning @v0.20.0 on the fragment-check wrapper: .release-toolkit scripts checked out at main (the baked value) rather than v0.20.0 — substrate drift class. Upgrade action: re-pin fragment-check to @v0.21.0 (the normal post-cut re-pin) to resolve. Extended tests/workflows.bats bake-marker guard now walks reusable-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.yml on 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 on if [[ -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 (@main breaks single-ref-alignment; @v0.21.0-rc.N doesn't yet contain the reusable). Deferred to #290 (post-v0.21.0-cut fold), same pattern as #281.

    Internal

    update-doc-version-refs.sh self-git add removed — 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.0 unless the Fragment-check baked-ref bug (see Fixed) applies — re-pin fragment-check.yml to @v0.21.0 in that case. The Cold-Read body linter and fragment template are opt-in.

    Downloads
  • v0.20.0 9fdc60db52

    Release v0.20.0
    Some checks failed
    check-self-bootstrap / check (pull_request) Failing after 3s
    manifest-check / manifest-vs-tag consistency (pull_request) Successful in 5s
    manifest-check / check (pull_request) Successful in 0s
    check-self-bootstrap / check (push) Failing after 3s
    release / decide + act (push) Successful in 6s
    release / release (push) Successful in 0s
    Stable

    release-bot released this 2026-07-02 12:10:58 +02:00 | 1385 commits to main since this release

    First-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.sh now probes the consumer's default-branch protection before doing any work. Fails fast + names the missing identity if release-bot isn't on push_whitelist_usernames, instead of the late mid-cut Not allowed to push to protected branch rejection that leaves the manifest desynced. Only fails on a definitive misconfig; warns-and-proceeds otherwise. docs/integration.md names it as a path-α precondition.

    reusable-changelog-fragment-check.yml + scripts/fragment-check.sh. Consumers wire it into their pull_request workflow 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.md gained an annual release-bot PAT rotation-cadence section. No action required on existing installs; consult before regenerating the RELEASE_TOOLKIT_TOKEN Actions 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 commit with git 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_head no longer mutates a random open PR. Forgejo's head= query filter is empirically ignored, so .[0].number was 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 exact head.label / head.ref match. No consumer-side change required. Closes #274.

    Rolling-PR prepare-commit push now authenticates with the path-α PAT. Previously it reused the actions/checkout system 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 injects FORGEJO_TOKEN (the RELEASE_TOOLKIT_TOKEN PAT) as an HTTP Authorization header, mirroring the manifest push. Necessary but not sufficient — the consumer must still set RELEASE_TOOLKIT_TOKEN to a real-user PAT (the documented path-α setup).

    release-decide detects the chore(release): prepare vX.Y.Z commit 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 to mode=update loudly. 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.sh now 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.sh PR body assembly now re-normalizes through changelog_merge_sections so any surviving duplicate ### Kind headings collapse to one section per kind. Belt-and-suspenders on top of the merge already done in changelog_transition. Closes #272.

    Removed / Deprecated

    None.

    Upgrade

    Pin your consumer wrapper's uses:@<ref> to v0.20.0. Fragment-check wiring is opt-in (add .forgejo/workflows/fragment-check.yml per docs/integration.md if you want the pre-merge gate). Everything else is transparent.

    Downloads
  • v0.19.0 139574e27b

    Release v0.19.0
    All checks were successful
    check-self-bootstrap / check (push) Successful in 3s
    release / decide + act (push) Successful in 6s
    release / release (push) Successful in 0s
    Stable

    release-bot released this 2026-06-28 23:43:04 +02:00 | 1404 commits to main since this release

    Changed

    • config_validate now rejects an unsupported version_files basename at config-load time. The version-file extraction layer (manifest-check read + release-prep write) keys on each entry's basename and handles only VERSION and package.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 using VERSION, package.json, or version_files: [] (tag-is-version mode) are unaffected.
    • release-prep.sh now auto-stages tracked files that post_bump_hooks modify, retiring the "hooks must git add their own changes" convention from #209. A hook just modifies tracked files; release-prep snapshots the content of every tracked file before the hook loop and git adds any whose content changed, so the edits are committed with the cut. Existing self-staging hooks keep working unchanged (a second git add of the same change is idempotent).
    Downloads