agentsclimarketplace

Release caret

Skill macintacos/caret/.claude/skills/release-caret

Cut a caret release. Computes the next version with the deterministic release script, confirms it once, composes the GitHub Release notes under a themed release name, then drives the flow end-to-end — phase 1 opens a PR with the version bump, merges it, then phase 2 tags trunk, publishes the GitHub Release, and publishes the plugin to npm. Triggers on "/release-caret", "release caret", "cut a caret release", "ship a caret version".From its SKILL.md

Install
npx -y skills add macintacos/caret --skill release-caret

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 2 stars2 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

15.1 KB, as published. Nobody here has run it

Release caret

Cut a caret release by orchestrating the release subcommand group of the caret tasks CLI (bun scripts/tasks/cli.ts release <subcommand>). The script owns every judgment-free step — version math, version-file edits, the commit range, all git/gh operations — and is the sole source of the version number. Your only jobs are: (1) confirm the version the script computes — the single gate — (2) compose the themed release name and the release-notes body, and (3) orchestrate the full flow end-to-end: open the release PR, merge it, then finalize (tag, GitHub Release, npm). Once the version is confirmed, everything after it runs without further prompts. Never invent, compute, or alter the version yourself — always take it from the script's JSON.

The script is invoked directly so its stdout is pure JSON:

bun scripts/tasks/cli.ts release <subcommand> [args]

Every invocation prints exactly one JSON object on stdout. Parse it. A { "ok": false, "errorCode": "...", "message": "..." } object (or a non-zero exit) means abort: surface message to the user and stop. Never proceed past a failed script call.

Arguments

/release-caret [patch|minor|major] [dry run]

  • The bump level (patch/minor/major) is required for a phase-1 release; it is ignored in phase 2 (the script derives the finalized version from trunk).
  • If the user says "dry run" (or passes --dry-run), pass --dry-run through to every mutating call and never pass --yes. A dry run prints what would happen and changes nothing.

Which phase am I in?

Releases run in two phases, both started from a clean, up-to-date trunk. In the normal flow a single invocation runs both phases end-to-end — Phase 1 opens the PR, the skill merges it (Phase 1 step 5), then Phase 2 tags and publishes — so no second invocation is needed. Phase detection still matters for resuming an interrupted run: compute reads local state, so pull trunk before releasing. Detect the phase by running the version oracle once:

bun scripts/tasks/cli.ts release compute <bump>
  • ok: false with errorCode: "NO_BASELINE" → no release tags exist yet → run Phase 1, starting at step 1 (offer to lay down the baseline), then re-run compute.
  • ok: false with any other errorCode (DIRTY_TREE, WRONG_BRANCH, DETACHED_HEAD, MANIFEST_DRIFT, NO_GH, NOT_A_REPO) → surface message and stop; fix the precondition (usually clean or pull trunk) first.
  • ok: true and currentVersion === previousVersion → the manifests still match the latest release tag, so no prepared bump is merged → run Phase 1.
  • ok: true and currentVersion !== previousVersion → the manifests are ahead of the latest tag, i.e. a prepared bump is already merged on trunk awaiting its tag → run Phase 2.

compute is the phase oracle, but it needs a clean, up-to-date trunk (it guards the branch and reads local state). When you're resuming Phase 2 — e.g. still on the release/<tag> branch Phase 1 left you on — finalize --dry-run is a valid alternative probe: it tags origin/trunk regardless of your local branch, so it reports NOT_MERGED while the bump isn't merged yet and ok: true (with the concrete tag/taggedSha) once it is. Use it to detect Phase-2 readiness without switching back to trunk first.


Running under plan mode

/release-caret mutates state behind a single confirmation gate — the version — so its flow maps cleanly onto plan mode's single ExitPlanMode approval. Outside plan mode this section does not apply — the version gate fires as the normal AskUserQuestion written below. When you are invoked under plan mode, remap as follows:

  • Version confirmation (Phase 1 step 2) folds into ExitPlanMode. Don't raise a separate AskUserQuestion for the version — present the script-computed version (verbatim from compute's JSON) inside the plan, and let plan approval stand in for that gate.
  • The release notes go in the plan, not on disk yet. Plan mode can't write files, so put the full proposed notes (summary paragraph + category sections) in the plan for review. Write them for real after exiting plan mode, to a temp file outside the repo, before prepare.
  • Plan approval IS authorization for the whole release. There is no separate remote-mutation gate. Once you've exited plan mode and written the notes to their temp file, proceed straight through prepare → merge the PR → finalize, handing the scripts the themed title and that file as flags and passing --yes to the mutating calls — no further prompt. (Stop only on a script or gh error.)

Phase 1 — open the release PR

1. Baseline (first release only)

If phase detection returned NO_BASELINE, there are no release tags yet — the first release needs v0.0.1 on the repository's initial commit so future releases have a range to bump from. This first-release bootstrap is the one exception to the single-gate rule (there is no computed version to confirm yet), so it asks its own one-time question:

No baseline tag exists yet. The first release tags the repository's initial commit as v0.0.1 so future releases have a range to bump from.

  • Tag the initial commit as v0.0.1 — runs baseline, pushing the tag.
  • Cancel

On confirmation, run bun scripts/tasks/cli.ts release baseline --yes (or --dry-run for a dry run), then re-run compute <bump>.

Otherwise you already have the successful compute result from phase detection. From it, keep: currentVersion, version, tag, commits[], manifests. (commits[] is the raw material for the release notes; manifests lists the version-bearing files the script mutates, so you never have to grep steps.ts for them.)

2. Confirm the version — the single gate

This is the one confirmation a real release asks for. Accepting the version authorizes the entire remainder of the flow — prepare --yes, merging the PR, and finalize --yes — with no further prompts. Surface the script-computed version for explicit confirmation. Show the concrete numbers — never paraphrase them:

Release <version>? Bumping <currentVersion> → <version> (<bump>), covering <N> commits since <previousTag>. Accepting runs the whole release end-to-end: opens the PR, merges it, tags trunk, and publishes the GitHub Release + npm.

  • Release <version> (Recommended)
  • Cancel the release

Use the version verbatim from the JSON. If the user cancels, stop. After this point the skill does not prompt again unless a step errors.

3. Compose the release notes

Pick an evocative theme for the release by reading commits[] — the descriptor that captures what this release is really about (e.g. "Foundations", "Plan Review", "Polish"). The descriptor can be any number of words and is allowed to be clever, but it must be Title Case and always sandwiched between "The" and "Release" (The <Descriptor> Release). That exact string is what you hand prepare and finalize as --title.

The notes body leads with a one- or two-sentence release summary: a plain-language digest of what this release delivers, not a re-listing of the category sections under it.

Write it tight and direct, the way the category entries under it read. Lead with caret <version> and a plain verb for what the release does — adds, ships, fixes — then name the headline changes concretely, by what they are and what they now do. If a second batch of changes is worth calling out, attach it as a second sentence stated just as plainly (It also …: a, b, c); where a change's point isn't self-evident, pin it with an em-dash or a so clause. Keep the register even and professional — the summary reports what shipped, so it needs no launch framing to sell it. The release theme belongs in the title; keep it — and any metaphor or filler clause written only to sound like an announcement — out of the summary prose.

Given what shipped in v0.5.0, this reads in the right register:

caret 0.5.0 adds first-run onboarding, a notification bell that flags a waiting plan and clears once you open it, and a confirmation step before you approve. It also polishes the review surface: steadier hover previews, a refined inline comment card, warmer light-theme greys, and room to scroll past the end of a plan.

The wordier alternative — opening on a metaphor ("rolls out the welcome mat") and closing on a frame that carries no information ("a round of polish … rounds it out") — is what to avoid.

Under the summary, group the work into the standard keep-a-changelog categories (Added, Changed, Deprecated, Removed, Fixed, Security) as ### sections — write human-readable entries derived from commits[], not raw commit subjects.

Write the whole body — summary paragraph on top, then the category sections — to one markdown file outside the repo, e.g. /tmp/caret-release-notes-<version>.md. finalize guards on a clean working tree with no allowlist, so an untracked notes file inside the repo would trip DIRTY_TREE and abort the release. Phase 2 step 2 hands that path to finalize through --notes-file.

4. Run prepare

The version gate (step 2) already authorized this — no separate confirmation. Pass the themed name from step 3 as --title; without it prepare aborts with TITLE_MISSING. The script composes v<version> - The <Theme> Release from it and uses that for the commit message, the PR title, the tag message, and the GitHub Release title.

bun scripts/tasks/cli.ts release prepare <bump> --title "The <Theme> Release" --yes      # real
bun scripts/tasks/cli.ts release prepare <bump> --title "The <Theme> Release" --dry-run  # dry run (no --yes)

Parse the result and keep prNumber and prUrl. Report the prUrl, then continue to step 5 to merge it. (On a dry run, prepare opens no real PR — prNumber is null — so skip step 5 and stop here.)

5. Merge the release PR

The skill merges its own release PR; there is no human-merge handoff. caret has no CI to wait on, release does not gate on mise run preflight (verify locally before releasing), and the repo merges via squash, so merge immediately:

gh pr merge <prNumber> --squash --delete-branch

If GitHub reports the PR's mergeability is still computing, wait briefly and retry once. On a real merge failure (merge conflict, branch protection, not mergeable, auth), abort: surface the gh error and work with the operator to resolve — do not force or --admin around it. The squash lands the bump on trunk; Phase 2 picks it up from origin/trunk. Skip this step entirely on a dry run.


Phase 2 — tag, publish, and ship to npm

finalize tags origin/trunk's merged HEAD after an unconditional fetch, so it runs from any branch — including the release/<tag> branch Phase 1 leaves you on. You don't need to switch back to trunk first. The publish-safety gates are a clean working tree and the NOT_MERGED check that the bump is actually on trunk, not the working branch.

The GitHub Release body is the notes file you composed in Phase 1 step 3, passed through --notes-file (step 2) and reflowed with rumdl to single-line paragraphs (the source is hard-wrapped, which renders as awkward mid-sentence breaks on GitHub), under the themed title passed through --title. A re-run without --notes-file leaves an existing Release's notes untouched; a re-run with the same file regenerates a byte-identical body, so nothing ever doubles.

After tagging and creating the GitHub Release, finalize builds the run-from-source bundle and publishes the plugin to npm (@macintacos/caret), because the marketplace's plugin source is an npm source — that publish is what makes /plugin marketplace add macintacos/caret resolve the new version (EXC-643). The npm step is resume-aware: it is skipped when the version is already on the registry, so a re-run after a partial failure completes cleanly. It needs the operator's existing npm auth (e.g. ~/.npmrc with publish rights to the @macintacos scope); if publish fails on auth, set that up and re-run finalize — it reuses the existing tag/release and retries only the publish.

1. Preview the finalize

bun scripts/tasks/cli.ts release finalize --dry-run --title "The <Theme> Release" --notes-file <path>

This fetches origin/trunk and returns the concrete version, tag, title, and taggedSha (trunk's merged HEAD), and previews the npm publish, without mutating anything. Pass the same flags you will pass for real: --title so the previewed title is the themed one rather than the bare v<version> fallback, and --notes-file so a mistyped path fails here as NOTES_MISSING instead of on the real run. It confirms the squash-merge from Phase 1 step 5 actually landed: ok: true means proceed. If it returns ok: false with NOT_MERGED, the merge didn't reach origin/trunk (the gh pr merge failed or is still settling) — surface that and work with the operator before continuing; do not run finalize --yes.

2. Run finalize

The version gate (Phase 1 step 2) already authorized this — no separate confirmation. Provided the dry-run probe returned ok: true, run it with the same --title you gave prepare and the notes file from Phase 1 step 3:

bun scripts/tasks/cli.ts release finalize --yes --title "The <Theme> Release" --notes-file <path>      # real
bun scripts/tasks/cli.ts release finalize --dry-run --title "The <Theme> Release" --notes-file <path>  # dry run

Parse the result and report the releaseUrl and whether npmPublished is true. The release is live. After a real release (--yes), return the checkout to a clean, updated trunk:

git switch trunk && git pull --ff-only

Guardrails

  • The script computes and owns the version; you only confirm it. If a script call fails, stop and surface its message — do not retry with a hand-edited version or work around the guard.
  • Pass the same --title string (The <Theme> Release) to both prepare and finalize, so the commit, PR, tag, and GitHub Release all carry one title.
  • One confirmation gates a real release — the version (Phase 1 step 2). Accepting it authorizes the entire remainder: prepare --yes, merging the PR (gh pr merge --squash), and finalize --yes, with no further prompts. A dry run skips --yes entirely and merges nothing. Stop only on a script or gh error.
  • The script is safe to re-run after a partial failure — it detects an existing branch, PR, tag, or release and resumes or no-ops. If a run is interrupted, just invoke /release-caret again.

Keep looking

Skills are one crate of 628,971. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.