| name | releasing |
| description | Cut a new ntucool release — bump versions across the workspace + plugin manifest, push, tag, let cargo-dist build prebuilt binaries on GitHub. Use whenever the user says "ship", "release", "publish", "tag a version", "cut a release", or asks "should we release v0.0.X?". Covers the dual-channel ship (binary via cargo-dist tag, plugin via plugin.json version) and the version-file matrix that's easy to miss. |
Releasing ntucool
ntucool ships two artifacts on every release that have independent version sources in the repo:
| Artifact | Version source | Distribution channel | Trigger |
|---|
cool binary | [workspace.package] version in root Cargo.toml (+ 3 internal path-dep pins) | GitHub Release via cargo-dist | git push <tag> matching v* |
| Claude Code plugin | version in plugins/ntucool/.claude-plugin/plugin.json | Claude Code's plugin update reads marketplace.json from origin/main and diffs plugin.json versions | git push origin main |
Both must bump for a release to flow end-to-end. Skipping the plugin bump is a silent failure — Claude Code's plugin update won't notice anything changed (it keys on plugin.json version, not git tags).
Pre-flight (run before bumping)
cargo test --workspace --quiet
cargo check -p ntucool --quiet
Quick MCP smoke (kept in scripts/):
cargo build --release -p ntucool --quiet
(printf '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"smoke","version":"0"}}}\n{"jsonrpc":"2.0","method":"notifications/initialized"}\n{"jsonrpc":"2.0","id":2,"method":"tools/list"}\n'; sleep 0.5) \
| timeout 5 ./target/release/cool mcp 2>/dev/null \
| grep -o '"name":"[^"]*"' | wc -l
Tag the current expected tool count somewhere and treat a deviation as a regression — past releases settled at 24 - 25.
The bump matrix (5 lines, in 4 files)
Same OLD → NEW substitution everywhere:
Cargo.toml: [workspace.package] version
cool-cli/Cargo.toml (2 lines): path-dep pins to cool-api, cool-tools
cool-tools/Cargo.toml: path-dep pin to cool-api
cool-tui/Cargo.toml: path-dep pin to cool-api
plugins/ntucool/.claude-plugin/plugin.json: version
One-liner that bumps all five at once (verify the match list first):
OLD=0.0.2 NEW=0.0.3
sed -i "s/version = \"$OLD\"/version = \"$NEW\"/g" \
Cargo.toml cool-cli/Cargo.toml cool-tools/Cargo.toml cool-tui/Cargo.toml
sed -i "s/\"version\": \"$OLD\"/\"version\": \"$NEW\"/" plugins/ntucool/.claude-plugin/plugin.json
cargo check -p ntucool --quiet
git diff --stat
Confirm the diff hits exactly those 4 files + Cargo.lock. Anything else means the sed picked up a co-incidental "0.0.X" somewhere.
Decision: which version number?
The project follows 0.0.x "everything is alpha" semantics. Breaking changes (renaming an MCP tool, changing the .mcp.json command users copy, removing a CLI subcommand) are fine inside 0.0.x — no minor bump needed.
If the contract feels stable enough to advertise, bump to 0.1.0. Until then, default to 0.0.<next>.
Ship sequence
git add Cargo.toml Cargo.lock cool-*/Cargo.toml plugins/ntucool/.claude-plugin/plugin.json
git commit -m "chore(release): bump workspace + plugin to v0.0.X
What changed since v0.0.<prev>:
- <bullet 1>
- <bullet 2>
..."
git push origin main
git tag v0.0.X
git push origin v0.0.X
gh run watch the resulting Release workflow. Expected jobs:
plan → build-local-artifacts (3 targets) → build-global-artifacts → host → announce
15 - 20 min wall clock. On success, gh release view v0.0.X --json assets should list:
ntucool-installer.sh / .ps1
ntucool-x86_64-{pc-windows-msvc.zip, unknown-linux-{gnu,musl}.tar.xz}
each artifact's .sha256
dist-manifest.json + source.tar.gz
Known caveats — surface these in the release notes
- macOS prebuilt is disabled.
dist-workspace.toml has it commented out (GitHub free-tier macOS runner queue stalled v0.0.1 for 12h+). macOS users currently can't cargo install ntucool either — the crate has never been pushed to crates.io. State this honestly in the release announcement.
cool update available since v0.0.3. The embedded axoupdater reads an install-receipt that cargo-dist's shell/powershell installer drops at $XDG_CONFIG_HOME/ntucool/ntucool-receipt.json. Users on v0.0.2 or older need one last manual curl ...installer.sh | sh to reach a binary that has the subcommand; from then on cool update self-sustains. cargo install users never have a receipt — error message in commands/update.rs tells them to cargo install ntucool --force.
- Plugin / binary version skew is possible. A user can have plugin v0.0.3 with binary v0.0.1 (or reversed).
setup.md doesn't (yet) check version drift.
Pitfalls (post-mortems of past mistakes)
install-updater = true is NOT what makes the receipt get written. v0.0.3 set this flag thinking it was needed for cool update to find a receipt. Wrong — cargo-dist 0.9+ writes the receipt regardless. The flag actually ships a standalone ntucool-update binary alongside cool, undoing the single-binary principle. v0.0.4 reverted to false. Keep it false unless you specifically want a separate updater binary on PATH. Verify by checking gh release view v0.0.X --json assets — there should be NO ntucool-x86_64-*-update entries.
- Don't push the tag before pushing main. If origin/main has commits you don't have locally (e.g. the user pushed a README edit while you were preparing the bump), pushing the tag first succeeds but pushing the branch fails with non-fast-forward. Recovery sequence:
gh run cancel <id> for any triggered CI, git push origin :v0.0.X to delete the orphan tag, git pull --rebase (or git fetch && git rebase origin/main), git tag v0.0.X at the rebased HEAD, then git push origin HEAD:main v0.0.X together.
- Main worktree may have uncommitted WIP that blocks a local fast-forward. If
git -C <main-worktree> merge --ff-only reports "Your local changes would be overwritten", do NOT stash the user's WIP without asking. Push the worktree branch directly to origin/main with git push origin HEAD:main from the release worktree — this updates origin without touching the main worktree's working tree.
What this skill does NOT cover
cargo publish to crates.io — never done, namespace deferred indefinitely
- macOS-target restoration in cargo-dist
- Generating release notes from git log automatically (do it by hand for now)