Autonomously cut a Rove (`@sma1lboy/rove`) release end-to-end — detect the semver bump from pending changesets (flagging an upstream `minor` you didn't intend), run the release gates, bump/tag/push via `scripts/release.sh`, then poll the GitHub Actions Release workflow with `gh` until npm publish completes, diagnosing CI failures (npm token, registry 404, lint, branch mismatch) instead of leaving them silent. Use when the user says "cut a release", "ship a version", "release Rove", "release kobe", "发版", "release.sh", or "bump the version". Never force-pushes; always verifies the release landed on `main`.
Install
npx skillscat add sma1lboy/rove/release Install via the SkillsCat registry.
Release Rove
Autonomous release driver for @sma1lboy/rove. This is the supervised loop the
manual flow in `docs/RELEASING.md` describes — read
that doc once if anything here is ambiguous; it is the source of truth and this
skill must never contradict it.
Releases are automatic (
.github/workflows/changesets.yml, since
2026-08-12): any push tomaincarrying pending changesets triggers the
full chain in Actions — CI-green wait → version+commit → tag → publish.
When the user asks to release, FIRST check whether that workflow already
has it:gh run list --workflow=changesets.yml --limit 3. If a run is
mid-flight or completed for the relevant push, jump to Step 4 (watch the
publish pipeline / verify npm). Run thescripts/release.shflow below
only when the automatic path is unavailable (Actions down) or the user
explicitly asks for the local flow — and never while a changesets.yml
run is mid-flight on the same version (they'd race to tag it).
The job is: detect the bump → gate → bump/tag/push → watch CI → confirm
published, or stop with a precise report. Do the whole chain without
hand-holding, but stop and surface (never guess) at the two human-judgment gates
marked ⚠ ASK below.
Hard rules (non-negotiable)
- Never force-push. No
git push -f, no--force-with-lease, nogit reset --hardon a shared branch, no retag-over-existing. If a tag or push
conflicts, stop and report — recovery is the user's call. - Bump default is
patch. Per AGENTS.md: pre-1.0 Rove ships features as
patches. Aminor/majoronly happens when the user explicitly said so this
turn, OR a pending changeset already carries that bump — and the second case is
exactly the trap to flag (see Step 1). - Release lands on
mainonly. Verify branch before and after. A release on
a stray feature branch is the #1 historical failure — catch it early. - No
--no-verify, no skipping hooks. If a gate fails, fix the cause or stop. - The release commit is
chore: release — X.Y.Z. No AI/Claude attribution
anywhere (commit, tag, GitHub release body).
Step 0 — Preflight
Confirm the working tree is sane and you're where you think you are:
git rev-parse --abbrev-ref HEAD # MUST be main (see Step 3)
git status --porcelain # working tree must be clean
git fetch origin && git log --oneline origin/main..HEAD # any unpushed commits?
git log --oneline HEAD..origin/main # are we behind? if so, surface — don't auto-merge
gh auth status # gh must be authed for CI pollingscripts/release.sh itself refuses a dirty tree (except the files it rewrites),
but do this first so you fail fast with a clear message instead of mid-script.
If origin/main is ahead of HEAD, stop and surface — Rove main moves fast
(often several releases/day); releasing from a stale base is how versions
collide. Let the user decide whether to pull/rebase.
Step 1 — Detect the bump (and flag the surprise minor) ⚠ ASK
The bump is not chosen by you — it's the max of the pending .changeset/*.md
bump types, computed by changeset version. Inspect before consuming:
bun run changeset:status # shows pending changesets + resulting bump
ls .changeset/*.md | grep -v README.md # raw list
# read each one — the first line frontmatter is the bump type:
# ---
# "@sma1lboy/rove": minor ← THIS is the bump that file forces
# ---Then decide:
- No pending changesets → nothing to release.
release.shwill abort. Tell
the user and offer to draft one (thechangelog-generatorskill does this). - All pending are
patch→ proceed silently; this is the normal case. - Any pending is
minorormajor→ ⚠ STOP AND ASK. This is the
documented annoyance: an upstream/peer changeset silently promotes the release
to a minor the user didn't intend. Quote the offending file + its bump line and
confirm: ".changeset/foo.mdcarries aminor— the release will be X.(Y+1).0,
not a patch. Intended?" Only continue on an explicit yes. Do not edit
someone's changeset bump without permission.
Record the predicted next version (current packages/kobe/package.json version
applied with the detected bump) so you can verify it later.
Step 2 — Run the gates locally (abort on failure)
scripts/release.sh now enforces lint && typecheck && (cd packages/kobe && bun run test) itself before touching version/CHANGELOG, and the push-triggeredrelease.yml re-runs lint + typecheck + test + build + the behavior suite beforenpm publish. Running the same set here first just fails fast, before burning achangeset version cycle:
bun run lint
bun run typecheck
bun run test # fast Vitest + unix-socket daemon/bridge suite
bun run build
cd packages/kobe && bun run perf:golden # golden perf doctor (~90s incl. binary compile smoke; docs/HARNESS.md §Performance contracts)perf:golden ceilings are 2-3× the reference numbers, so a FAIL means a real
structural regression (startup, PTY spawn/wake, per-tab memory, park reclaim)
— treat it like a red test, not jitter; rerun once to confirm before digging.perf:golden is not part of the enforced release.sh/release.yml gate (opt-in,
local/pre-release only per docs/HARNESS.md), so run it manually here.
bun run test:behavior exercises the built CLI against an isolated daemon and
standalone PTY Host with a fake claude shim; cases that drive the outer terminal
also need native node-pty support. release.yml runs the same black-box suite
before npm publish. Running it locally first is optional but catches a failure
before the tag push.
If a gate fails: report the exact failing command + output, fix it if it's an
obvious in-scope issue (and re-run the full set), or stop. Never proceed to tag a
red tree.
Step 3 — Verify branch, then bump/tag/push
git rev-parse --abbrev-ref HEAD # MUST print: mainIf not on main, stop — do not checkout/merge to "fix" it autonomously
(concurrent sessions + branch juggling is the documented git-tangle failure).
Surface the actual branch and ask.
On main with gates green, run the release script. It is the single source of the
bump→version→CHANGELOG→commit→tag→push sequence — don't reimplement those steps by
hand:
scripts/release.shWhat it does (don't fight it): gate (lint → typecheck → test → build →behavior) → changeset version → bun install + --frozen-lockfile →lint:fix on the regenerated JSON → commits chore: release — X.Y.Z (no tag
yet) → prompts, pushes the release commit to main, waits for that
commit's ci.yml run to go green (the Linux/macOS gates the local macOS run
can't prove — v0.8.66 died exactly there), and only then tags vX.Y.Z and
pushes the tag.
- Confirm the printed
CURRENT → NEW (vX.Y.Z)matches your Step 1 prediction. A
mismatch means a changeset changed under you — stop and re-inspect. - The script asks
Push now? [y/N]. Answeryonly after the version line checks
out. If the user wanted a dry run / review-before-push, answerN— re-running
the script later resumes (push → wait CI → tag). - If the CI wait comes back RED, no tag exists and the version is NOT burned:
land the fix onmain(no new changeset) and re-runscripts/release.sh—
with zero pending changesets and an untagged committed version it enters
resume mode and tags the same version at the fixed HEAD.
The push of tag vX.Y.Z is what triggers .github/workflows/release.yml.
Step 4 — Poll CI until publish completes
The tag push starts the Release workflow (publish job: gates → npm publish →
GitHub release). npm is the sole distribution channel — standalone binaries were
dropped 2026-08-02, so an empty release-assets list is normal. Watch the run to
terminal state — don't declare success on push alone:
gh run list --workflow=release.yml --limit 5 # find the run for this tag
gh run watch <run-id> --exit-status # blocks until done; nonzero on failure
# or poll: gh run view <run-id> --json status,conclusion,jobsOn success, verify the canonical package and its compatibility alias actually landed (don't trust the green check alone):
npm view @sma1lboy/rove@<new-version> version # the published package; must echo the new version
# @sma1lboy/kobe is NOT published anymore (frozen at 0.9.64) — do not check it,
# and do not "fix" its absence from a release.
# Every Rove release checks the SDK's current version and publishes either
# missing package name, even without a new SDK changeset. Always verify both
# names at the version recorded in packages/kobe-plugin-sdk/package.json:
npm view @sma1lboy/rove-plugin-sdk@<sdk-version> version
npm view @sma1lboy/kobe-plugin-sdk@<sdk-version> version
gh release view v<new-version> --json name -q .name # GitHub release existsConfirm: @sma1lboy/rove and both SDK names report their expected versions,
the Rove version matches the tag and packages/kobe/package.json,
and the release landed on main (git log --oneline -1 origin/main is the chore: release commit).
Then report done with the version, the npm dist-tag it went to (latest for
plain semver), and the release URL.
Step 5 — Diagnose CI failure (auto-fix or stop precisely)
If the run fails, identify the job + step before doing anything:
gh run view <run-id> --log-failedMap the failure to a cause and act. Never retry blindly or force-push.
| Symptom in the log | Likely cause | Action |
|---|---|---|
npm publish → 401/403, ENEEDAUTH, EOTP |
NPM_TOKEN secret missing/expired/wrong scope |
Code is fine and the tag is published-or-not — stop and report. Token rotation is the user's job (Settings → secrets → NPM_TOKEN, automation token with @sma1lboy publish rights). After they fix it, a re-publish needs a new version (npm won't overwrite) — never retag the same version. |
npm publish → 404 on registry / scope |
registry URL or scope access wrong | Report; check .npmrc auth line + access: public. Don't mutate published state. |
Verify tag matches package.json step fails |
tag ≠ package.json version (retag drift) |
Means the tag and the committed version disagree — surface it; do not force-retag. The fix is to bump+commit then tag fresh, which is the user's call. |
| Typecheck / test / build red | real regression that local gates somehow missed | Reproduce locally (`bun run typecheck |
npm publish → E409/cannot publish over |
version already on npm | The version is already out — likely a double-run. Stop; the next release is a new version. |
A sibling job (behavior/render-track/visual-ground-truth) fails but publish succeeded |
flake in a non-blocking rerun | npm already has the package; report it. gh run rerun <run-id> --failed is safe for those jobs; re-running publish is NOT — it'll hit E409. |
The principle: anything that changes published artifacts or rewrites history
(retag, force-push, republish) is stop-and-report, not auto-fix. Anything
local and idempotent (re-run a flaky binary matrix, fix a lint/type error for the
next release) you may do.
Prerelease note
For vX.Y.Z-<id>.N tags (e.g. v0.7.0-experimental.0), the workflow publishes to
the npm dist-tag named after the identifier (experimental), so latest stays
stable. These come from Changesets prerelease mode (changeset pre enter <id> …changeset pre exit), not release.sh. If the user asks for a prerelease, follow
RELEASING.md's prerelease section rather than this default flow.