| name | release |
| description | Cut a new release of the Product CLI by bumping the workspace version in lockstep, committing, tagging `vX.Y.Z`, and pushing the tag to trigger the cargo-dist release workflow. Use when the user asks to "cut a release", "release version X", "ship 0.X.Y", "tag a release", "publish a release", or "bump and tag for release". |
Release the Product CLI
The release pipeline (.github/workflows/release.yml, autogenerated by dist generate — don't hand-edit it) is driven by cargo-dist. It fires when a git
tag matching **[0-9]+.[0-9]+.[0-9]+* is pushed and builds the GitHub Release
artifacts from the tagged commit. This is a Cargo workspace, and cargo-dist
runs in lockstep mode: pushing vX.Y.Z releases every dist-able package in the
workspace at version X.Y.Z (it errors if a dist-able package's version doesn't
match the tag). The binary is product, in the product-cli package.
The version sources — keep them in lockstep at X.Y.Z
There is no root version — root Cargo.toml is [workspace]-only. The
version is spread across these files, and they must all agree:
| File | Field | Why |
|---|
product-core/Cargo.toml | line 3 version | published lib version |
product-mcp/Cargo.toml | line 3 version | published lib version |
product-cli/Cargo.toml | line 3 version | the dist-able binary package — cargo-dist matches the tag against this |
.product/config.toml | line 2 version | Product config (mirrors the release) |
product-cli/Cargo.toml | lines 20–21 internal version on the product-core/product-mcp path deps | lockstep target — see below |
product-mcp/Cargo.toml | line 20 internal version on the product-core path dep | lockstep target — see below |
xtask/Cargo.toml stays at 0.1.0 — it's an internal build-helper, not
dist-able, not released. Do not bump it.
The internal path-dep versions matter. Since v0.3.0, the intra-workspace
path deps carry an explicit version (e.g.
product-core = { path = "../product-core", version = "0.3.0" }). This is what
lets cargo deny check's wildcards = "deny" ban pass on the publishable
crates. If you bump the package versions but forget these, Product CI's
cargo-deny step goes red (and a real cargo publish would mismatch). They are
the easiest thing to miss — there's no test that pins them, only the CI gate.
server.json / TC-776 are gone. Older versions of this skill bumped a
server.json MCP-registry manifest enforced by a tc_776_* test. Both were
removed. The .github/workflows/publish-mcp.yml workflow still exists but is
currently inert — it expects the deleted server.json. Ignore it for a
normal release (cargo-dist ships the binaries regardless). If the user wants
MCP-registry publishing back, that's a separate task (restore server.json +
its schema check), not part of cutting a release.
When to use
- The user asks to release a specific version (e.g. "release 0.3.1")
- The user asks to "tag the release" after a version-bump commit has already landed
- A release pipeline didn't run because the tag was forgotten
What to do
1. Determine the target version
Ask the user if not specified. Otherwise infer from the current version:
- Patch (default) —
0.3.0 → 0.3.1 for bugfixes / small additions
- Minor —
0.3.x → 0.4.0 for new features / non-breaking surface changes
- Major — reserve for breaking changes; confirm with the user first
Read the current version:
grep -E '^version = ' product-cli/Cargo.toml | head -1
2. Verify preconditions
git status -sb
git fetch origin && git status -sb
git tag -l "v<TARGET>"
If any check fails, surface it rather than papering over it. A dirty tree needs
a decision; an existing tag means the release was already attempted — find out
why before re-running.
3. Decide: bump-and-tag, or tag-only?
git log -1 --format='%h %s'
grep -E '^version = ' product-cli/Cargo.toml
- If the versions already equal the target and there's an untagged "Bump
version" commit at HEAD — skip to step 5 (tag-only).
- Otherwise — do step 4.
4. Bump and commit
Set NEW and update every lockstep source at once:
NEW=X.Y.Z
sed -i "s/^version = .*/version = \"$NEW\"/" \
product-core/Cargo.toml product-mcp/Cargo.toml product-cli/Cargo.toml .product/config.toml
sed -i -E "s#(path = \"\.\./product-(core|mcp)\", version = \")[0-9.]+#\1$NEW#" \
product-cli/Cargo.toml product-mcp/Cargo.toml
Verify all sources agree, the build is clean, and the supply-chain gate passes
(this is the check that catches a missed path-dep version):
grep -nE 'version = "' \
product-core/Cargo.toml product-mcp/Cargo.toml product-cli/Cargo.toml .product/config.toml
cargo build --workspace
cargo deny check
cargo t
Commit (push the commit before the tag — Actions checks out the tagged ref
from the remote, so an unpushed commit isn't fetchable):
git add product-core/Cargo.toml product-mcp/Cargo.toml product-cli/Cargo.toml .product/config.toml Cargo.lock
git commit -m "$(cat <<'EOF'
Bump version to X.Y.Z
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
EOF
)"
git push origin main
5. Tag and push
git tag vX.Y.Z <commit-sha>
git push origin vX.Y.Z
The tag must be v-prefixed to match the workflow trigger and the existing tag
convention (v0.1.0 … v0.3.0). git push does not push tags — push it
explicitly (avoid --tags, which can shove stale local tags).
6. Verify the pipeline started
gh run list --workflow=release.yml --limit 1
gh run watch
Confirm the GitHub Release object was created:
gh release view vX.Y.Z --json tagName,isDraft,assets \
-q '.tagName + " draft=" + (.isDraft|tostring) + " assets=" + (.assets|length|tostring)'
If release.yml fails immediately in dist plan with a version mismatch, a
dist-able package's Cargo.toml on the tagged commit doesn't equal the tag.
Fix: bump it, commit, delete the tag (git tag -d vX.Y.Z && git push origin :refs/tags/vX.Y.Z), and re-tag on the new commit — but only if the workflow
hasn't already published the Release (once it has, deleting the tag won't delete
the Release; use gh release delete).
Gotchas
- Dist-able
Cargo.toml version must equal the tag. cargo-dist reads it
from the tagged commit and refuses to plan otherwise — the most common failure
mode (the 0.1.1 redo, commit 9c9f828).
- Six-place lockstep. Three package
versions + .product/config.toml +
the three internal path-dep versions. Nothing tests the path-dep versions
except Product CI's cargo-deny step — bump them or CI goes red.
- Don't bump
xtask. It stays 0.1.0; it's not released.
- Push the commit before the tag. An unpushed commit isn't fetchable by the
tag checkout.
v0.1.0+<hash> tags are nightlies, not releases — don't reuse that format
for an intentional release. Plain vX.Y.Z only.
- No CHANGELOG.md. cargo-dist generates the release body from commit
messages; there's no manual changelog step.
publish-mcp.yml is currently inert (the server.json it needs was
removed). It won't block a release; don't chase its failures as part of
cutting one.
Skill output
After the tag is pushed, report:
- The version released (
vX.Y.Z)
- The commit SHA the tag points at
- The status/URL of the running workflow (
gh run list --workflow=release.yml --limit 1)
- Anything unusual (version mismatch, dirty tree, a red gate) — don't hide
problems in the success message