Release Way¶
Source: hooks/ways/softwaredev/delivery/release/release.md
Frontmatter
| Field | Value |
|---|---|
description |
software releases, the changelog, version bump, semantic versioning, tagging, publishing the same immutable artifact that passed CI, and a rehearsed rollback |
vocabulary |
release releases changelog version bump semver semantic versioning git tag tagging release notes release candidate publish package registry artifact digest immutable build promote promotion rollback rehearsal restore point github release cargo publish npm publish |
pattern |
release|changelog|semver|git.?tag|release.?(notes|candidate)|npm.?publish|cargo.?publish |
refire |
0.15 |
scope |
agent, subagent |
pattern_keep |
release |
First: Check for make release¶
Before writing ad-hoc release commands, check if the project has a Makefile with a release or dist target:
make help 2>/dev/null | grep -iE 'release|dist|publish|deploy'
# or just: grep -E '^(release|dist|publish)' Makefile 2>/dev/null
If it exists, use it. The Makefile is the canonical release interface — it knows the project's packaging, signing, and publishing steps.
When There's No make release¶
Generate Changelog¶
Format using Keep a Changelog:
Infer Version Bump¶
From commit messages since last tag:
- Any feat!: or BREAKING CHANGE → major
- Any feat: → minor
- Only fix:, docs:, chore: → patch
Update Version¶
Detect the version file (package.json, Cargo.toml, pyproject.toml, version.txt) and update it.
Reconcile the Issue Tracker¶
A release is the moment "fixed in X" becomes a public claim, so the tracker is reconciled before the tag, not after. This is the release-time sibling of the ADR status flip in delivery/merge — same failure, different ledger: nothing breaks while it drifts, and the correction arrives later as a bulk audit.
Find what the release claims:
git log $(git describe --tags --abbrev=0)..HEAD --format='%s%n%b' \
| grep -oiE '(clos|fix|resolv)(e[sd])? +#?[A-Z]+-?[0-9]+'
The pattern is deliberately tracker-agnostic — it catches #123 and PROJ-456 equally. Three things to settle with the hits:
- Items the commits closed get the released version recorded, where the tracker has a fix-version field.
- Items referenced without a closing keyword get checked against what the release actually does.
- An item the changelog names as fixed while the tracker shows it open means one of the two is wrong.
Act through whatever CLI the project already uses — gh issue, glab, jira, an MCP tool, a checklist in a file. Detect it from the repo rather than assuming. A project with no tracker is a valid outcome: say so and move on.
Publishing Artifacts¶
| Destination | How |
|---|---|
| GitHub Releases | gh release create vX.Y.Z --notes-file CHANGELOG.md <binaries> |
| npm | npm publish (in make release) |
| PyPI | python -m build && twine upload dist/* |
| Cargo | cargo publish |
| AUR | Update PKGBUILD, makepkg --printsrcinfo > .SRCINFO, push to AUR |
| Container registry | docker build -t repo:vX.Y.Z . && docker push |
For multi-platform binaries, build per-platform and attach all of them to a single GitHub Release with a checksums.txt.
Two-Step Release Under Branch Protection¶
A protected main splits the release in two, and this is common enough to plan for. The bump — version file, lockfile, changelog — goes through a PR. A bump limited to the version and lockfile merges on green CI without a review, unless the repository enforces one (delivery/merge). Only after it merges does the tag land on main.
Tagging is then the single outward step, and CI usually takes it from there: a tag-triggered workflow builds each platform and creates the release. Check for that workflow before hand-building artifacts.
Signed tags stop an agent cold. If the project signs (tag.gpgsign, or a -s in the release script), the tag command needs a passphrase from a terminal the agent doesn't own. Do everything up to that point, then hand the exact command to the operator rather than retrying into a timeout.
Promote What Passed¶
The artifact released is the byte-identical one that passed the gates, identified by its digest alongside the tag. A floating tag can point at a later build. Rebuilding at release time invalidates every gate that ran before it. If the pipeline rebuilds on tag, the gates run again on the rebuilt artifact before it is promoted. Only configuration changes between environments, and the previous configuration stays recreatable so reversal needs no rebuild. A commit id proves authorship. A claim that a fix ships carries an ancestry check against the released reference.
Rollback and the Irreversible Step¶
A rollback exists once it has been run. An assumed backup counts for nothing until someone has restored from it, and a written procedure nobody has executed is a hope. Before the release, capture the restore point and verify it after capture, record the previous build identifiers, and record whether the migration has a reverse path. Where deployments auto-apply migrations, reverting the artifact leaves the schema in place. Say so in the runbook.
Name the irreversible step as such: a destructive migration, a key rotation, a column drop. It lands last, after every reversible step has landed and its evidence is green. A missing or unrehearsed rollback is recorded as absent with a date, never implied.
Do Not¶
- Explain what semantic versioning is — just apply it
- List human process steps (deploy, announce) — produce artifacts Claude can generate
- Write publishing commands without checking
make releasefirst
See Also¶
- delivery/commits(softwaredev) — changelog generated from commits
- delivery/groundwork(softwaredev) — the four conditions a release assumes