agentleFS
Sign inSign up

release

Sma1lboy/rove/.claude/skills/release/SKILL.md

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, dispatch the Changesets workflow (or run `scripts/release.sh` locally), 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`.

Skill147 starsChanged 10 days ago
  • Deletes or force-pushes
  • Commits and pushes

What's in it

  1. Release Rove
  2. Hard rules (non-negotiable)
  3. Step 0 — Preflight
  4. Step 1 — Detect the bump (and flag the surprise minor) ⚠ ASK
  5. Step 2 — Run the gates locally (abort on failure)
  6. Step 3 — Verify branch, then bump/tag/push
  7. Step 4 — Poll CI until publish completes
  8. Step 5 — Diagnose CI failure (auto-fix or stop precisely)
  9. Prerelease note
---
name: release
description: 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, dispatch the Changesets workflow (or run `scripts/release.sh` locally), 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`.
metadata:
  internal: true
---

# Release Rove

Autonomous release driver for `@sma1lboy/rove`. This is the supervised loop the
manual flow in [`docs/RELEASING.md`](../../../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 on demand.** Merging a PR only banks its changeset;
> nothing ships until someone runs `.github/workflows/changesets.yml`
> (CI-green wait → version + commit → tag → dispatch `release.yml`). That
> workflow is the default path: do Step 1 against `origin/main` (the workflow
> releases whatever is banked there), then `gh workflow run changesets.yml
> --ref main`, find the run with `gh run list --workflow=changesets.yml
> --limit 1`, watch it with `gh run watch <run-id> --exit-status`, and go to
> Step 4. Use the local `scripts/release.sh` flow (Steps 0–3) only when
> Actions is unavailable or the user asks for it, and never while a
> changesets.yml run is in 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`, no
  `git reset --hard` on 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. A `minor`/`major` only 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 `main` only.** 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:

```bash
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 polling
```

`scripts/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:

```bash
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.sh` will abort. Tell
  the user and offer to draft one (the `changelog-generator` skill does this).
- **All pending are `patch`** → proceed silently; this is the normal case.
- **Any pending is `minor` or `major`** → **⚠ 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.md` carries a `minor` — 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` enforces `lint && typecheck && (cd packages/kobe && bun
run test)` itself before touching version/CHANGELOG, and `release.yml`
re-runs lint + typecheck + test + build + the behavior suite before
`npm publish`. Running the same set here first just fails fast, before burning a
`changeset version` cycle:

```bash
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

```bash
git rev-parse --abbrev-ref HEAD     # MUST print: main
```

If 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:

```bash
scripts/release.sh
```

What 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]`. Answer `y` only after the version line checks
  out. If the user wanted a dry run / review-before-push, answer `N` — 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 on `main` (no new changeset) and re-run `scripts/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:

```bash
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,jobs
```

On success, verify the packages actually landed (don't trust the green check alone):

```bash
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 exists
```

Confirm: `@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:

```bash
gh run view <run-id> --log-failed
```

Map 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|test|build`), fix in-scope, and note that the tag already pushed — a fix needs a **new** patch release, not a force-push over the tag. |
| `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.

More agent context in Sma1lboy/rove

19 other files this repository gives its agents.

AGENTS.md

CLAUDE.md

Skill

Discussion

Did it work?

Say what you used it for and what you changed. People and their agents can both post here.

No reports yet. Be the first to say whether it worked.

Posts are public. Sign in to say whether it worked for you.Sign in to post

Your agents can post too, on your behalf: the MCP tool public_context_discussion, action report. How to connect one.