release
Cut a tagged release of mind. The version in Cargo.toml is the single source
of truth: make release tags v<version> and pushes it, and that tag push
triggers .github/workflows/release.yml, which builds the per-platform binaries,
publishes the GitHub Release with the tarballs, and regenerates the Homebrew
formula.
Releases are SemVer. Pick the bump from what landed since the last tag: a new
verb/flag or other backward-compatible feature is a minor bump; a bug-fix-only
batch is a patch; a breaking change to the CLI, config, on-disk layout, or
mind.toml schema is a major bump.
Steps
1. Confirm a clean, green starting point
Work from main with everything merged and pushed.
git switch main && git pull
test -z "$(git status --porcelain)" || echo "tree is dirty; commit or stash first"
make ci # fmt-check + clippy (-D warnings) + test; must pass
2. Pick the version and survey the changes
git describe --tags --abbrev=0 # the previous release tag
git log "$(git describe --tags --abbrev=0)..HEAD" --format='%s'
Decide the new X.Y.Z from that list (see the SemVer note above).
3. Bump the version
Between releases main carries a pre-release version (X.Y.Z-dev) so a build
from main is distinguishable from the released binary. Drop the suffix here:
edit Cargo.toml [package].version to the new X.Y.Z, then sync the lockfile
so Cargo.lock's mind entry matches (the release build uses --locked, so a
stale lock fails CI):
cargo build # rewrites Cargo.lock's mind version
tests/changelog.rs only requires a matching ## [X.Y.Z] section once the
-dev suffix is gone, so this bump is what arms that check. Step 9 puts the
next -dev version back.
4. Update the changelog
Add a ## [X.Y.Z] - YYYY-MM-DD section at the top of CHANGELOG.md (Keep a
Changelog format), grouping the changes under Added / Changed / Fixed /
Removed. Describe user-facing behavior, not the commit workflow. Update the
link references at the bottom: point [Unreleased] at
.../compare/vX.Y.Z...HEAD and add [X.Y.Z]: .../compare/v<prev>...vX.Y.Z.
tests/changelog.rs enforces this (every version section needs a matching ref,
[Unreleased] must compare from the newest version, and each ref chains to the
next-older one), so make ci fails if a ref is missing or stale. Keep the voice
plain and factual (see ~/.local/share/agents/voice/voice-profile.md).
5. Commit the release prep
make ci # green on the bumped tree
git add Cargo.toml Cargo.lock CHANGELOG.md
git commit -m "release 0.0.0" # use the real version
git push origin main
The commit subject is release X.Y.Z (matches the repo's convention).
6. Tag and trigger the release workflow
make release requires a clean tree and an unused tag. It tags v<Cargo.toml version> and pushes the tag:
make release # tags v<version> from Cargo.toml and pushes it
# override only if needed: make release VERSION=1.2.3 (or TAG=v1.2.3)
7. Watch the GitHub workflow succeed
The tag push starts release.yml. It must finish green before the release is
real. Watch it:
gh run watch "$(gh run list --workflow=release.yml --limit=1 --json databaseId -q '.[0].databaseId')"
# or: gh run list --workflow=release.yml --limit=3
The workflow has five jobs:
ciandci-macos(parallel): re-runmake cion Linux and macOS at the tagged commit, gating everything downstream.build(matrix, needsci+ci-macos): builds--release --lockedforaarch64-apple-darwin,aarch64-unknown-linux-gnu,x86_64-unknown-linux-gnu, and the musl legsaarch64-unknown-linux-muslandx86_64-unknown-linux-musl(statically linked, glibc-version-independent; used byinstall.sh). Uploads eachmind-<version>-<target>.tar.gzas an artifact and attests build provenance for it. A--lockedfailure here meansCargo.lockis stale; fix it and re-tag.release(needsbuild): downloads the artifacts, generatesSHA256SUMS, and runsgh release createwith every tarball plusSHA256SUMS; a missing target's glob would fail the command, so the job fails closed on a partial matrix.publish(needsrelease): publishes to crates.io via trusted publishing (OIDC,rust-lang/crates-io-auth-action, no stored token), then regeneratesFormula/mind.rbfrom the tarball checksums viaresources/update-formula.shand commits it back tomainasgithub-actions[bot].
8. Verify the published release
gh release view "v<version>" # five .tar.gz assets attached
cargo info mind-cli # crates.io has the new version
git pull # pick up the bot's formula commit
grep -n 'version\|sha256' Formula/mind.rb # points at the new version
Optionally smoke-test the install path (resources/install.sh, or
brew upgrade mind from the tap).
9. Put main back on a -dev version
Bump Cargo.toml to the next patch version with a -dev suffix (after
0.22.0, use 0.22.1-dev), sync the lockfile, and add a fresh
## [Unreleased] heading to CHANGELOG.md:
cargo build # rewrites Cargo.lock
git add Cargo.toml Cargo.lock CHANGELOG.md
git commit -m "back to dev"
git push
Without this, a binary built from main reports the released version, so
mind --version in a bug report and evolve --check cannot tell a release from
unreleased work. The exact next number does not matter; step 3 sets the real one.
Notes
- The version lives only in
Cargo.toml;Makefileand the workflow derive the tag from it. Do not hand-write the tag except via themake releaseoverride. - If the workflow fails after the tag was pushed, delete the tag
(
git push origin :refs/tags/v<version>andgit tag -d v<version>), fix the cause onmain, and re-run from step 5. A partially-created GitHub Release may need to be deleted in the UI or withgh release delete. - The release is outward-facing and hard to reverse (it publishes binaries and
pushes a formula commit). Confirm steps 1-5 are correct before running
make release. - Known flake:
ci-macoscan fail the timing-based lock testtui::action::tests::execute_lock_is_exclusive_not_sharedunder a loaded runner (it did on the first v0.13.0 tag, run 28724441026; the re-tag passed). If that single test is the only failure onci-macos, treat it as a flake: delete the tag (see above), re-tag, and re-run. Investigate only if it fails deterministically across re-runs.