Skip to content

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

git log --oneline $(git describe --tags --abbrev=0 2>/dev/null || echo "HEAD~20")..HEAD

Format using Keep a Changelog:

## [X.Y.Z] - YYYY-MM-DD
### Added
### Changed
### Fixed
### Removed

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 release first

See Also

  • delivery/commits(softwaredev) — changelog generated from commits
  • delivery/groundwork(softwaredev) — the four conditions a release assumes