| name | release-guard |
| description | Canonical release workflow for this repo. One path every time: main green → release-manager ship → tag vX.Y.Z → release.yml. Use when the user says release, tag, publish, deploy, version, or cut a GitHub release. |
Release Guard — Canonical Release Skill
One workflow. Same for humans and agents. No alternate paths.
Golden path (memorize this)
1. Version + CHANGELOG already on main (via PR)
2. origin/main CI fully green
3. ./scripts/release-manager.sh ship --execute
4. release.yml builds artifacts + creates GitHub Release
5. Optional: ./scripts/release-manager.sh wait-release
| Step | Who | Tool |
|---|
Bump Cargo.toml version + CHANGELOG | Human/agent via PR | PR to main |
Docs: Released Version = workspace version | Same PR | ROADMAP + STATUS |
| Wait for main CI | Agent | gh run list --branch main --commit $(git rev-parse origin/main) |
| Tag + push only | Agent/human | ./scripts/release-manager.sh ship --execute |
| Build + GitHub Release | GitHub Actions | .github/workflows/release.yml (on tag push) |
| crates.io (if needed) | GitHub Actions | publish-crates.yml (separate) |
NEVER
| Forbidden | Why |
|---|
gh release create by hand | Bypasses cargo-dist / preflight / artifacts |
Tag from non-main | Policy + dist target |
Tag when Cargo.toml ≠ tag (v0.1.35 ↔ 0.1.35) | release.yml preflight fails |
--admin / force merge | Branch protection exists for a reason |
| Ship while main CI pending/failed | Broken release |
| Multiple competing “release procedures” | This skill + release-manager.sh only |
Agent checklist (every release)
./scripts/release-manager.sh status
git checkout main && git pull --ff-only origin main
test -z "$(git status --porcelain)"
./scripts/verify-release-state.sh --check-unreleased
./scripts/release-manager.sh ci-check
./scripts/release-manager.sh ship --execute
./scripts/release-manager.sh wait-release
Dry-run first if unsure:
./scripts/release-manager.sh ship
Version rules
- Workspace
Cargo.toml version = "X.Y.Z" is the source of truth.
- Git tag is always
vX.Y.Z (leading v).
- release.yml rejects tag/version mismatch.
- ROADMAP_ACTIVE and STATUS/CURRENT first
Released Version line must equal X.Y.Z before ship (verify-release-state).
- CHANGELOG must have exactly one
## [X.Y.Z] section (unique version headers).
Semver for this 0.x line: prefer patch for fixes, minor for features (team convention).
CHANGELOG / GitHub Release notes (mandatory)
release.yml (cargo-dist) uses parse-changelog to fill the GitHub Release body from CHANGELOG.md. Format must stay consistent with published releases (e.g. v0.1.34 / v0.1.35):
- Unique
## [X.Y.Z] - YYYY-MM-DD headings — duplicate historical versions make parse-changelog fail and produce an empty release body.
- Ship notes under standard Keep a Changelog sections (
### Added, ### Fixed, …).
- After
wait-release, confirm: gh release view vX.Y.Z shows ## Release Notes content (not blank).
- If notes were empty: fix CHANGELOG uniqueness →
gh release edit vX.Y.Z --notes-file … (or re-run notes step); land verify gate (PR #858 pattern) so it cannot regress.
parse-changelog CHANGELOG.md "$VERSION" >/dev/null
./scripts/verify-release-state.sh --check-unreleased
What ship does
verify-release-state.sh --check-unreleased
- Local: fmt, clippy, build check, nextest, doctest, quality-gates
(skip with --skip-local-tests only in documented emergencies)
ci-check on origin/main HEAD (all runs completed success/skipped)
git tag -a vX.Y.Z -m "Release vX.Y.Z" on that HEAD
git push origin refs/tags/vX.Y.Z only (does not push commits)
- Prints how to monitor
release.yml
After ship
- Confirm:
gh release view vX.Y.Z
- Drift issue (#849-style) should close when tag matches workspace version
- Bump workspace to next patch for development in a follow-up PR (optional)
Failure playbook
| Symptom | Fix |
|---|
| verify-release-state fails docs | PR: set Released Version: vX.Y.Z in ROADMAP + STATUS |
| ci-check pending | Wait; re-run ci-check |
| ci-check failed | Fix main CI first |
| Tag already on remote | Do not retag; inspect gh release view |
| release.yml failed preflight | Version/tag mismatch — delete bad tag only after review |
| GitHub Release body empty / no notes | Duplicate ## [X.Y.Z] in CHANGELOG (parse-changelog fails). Make versions unique; gh release edit to restore notes; keep gate in verify-release-state |
| Want crates.io | Use publish workflow / team process after GitHub Release |
Relationship to other skills
| Skill | Use for |
|---|
| release-guard (this) | All release/tag/publish/deploy requests |
| github-release-best-practices | Background only; defers to this skill |
| pr-readiness | Merging the version-bump PR before ship |
| release-drift workflow | Alerts when main outruns tags — does not ship |
Commands reference
./scripts/release-manager.sh status
./scripts/release-manager.sh validate
./scripts/release-manager.sh ci-check
./scripts/release-manager.sh ship
./scripts/release-manager.sh ship --execute
./scripts/release-manager.sh wait-release
./scripts/release-manager.sh rollback --tag vX.Y.Z --execute
Progressive disclosure
- CI wait patterns: ci-reference.md
- Workflow definition:
.github/workflows/release.yml
- Version consistency:
./scripts/verify-release-state.sh