Skip to content

release

Cut a release the only sanctioned way — SemVer bump from the commit log, a CHANGELOG section, an annotated tag. The only path to a version tag.

This is the only sanctioned way a version tag gets created. It never invents a version number — it walks every commit since the last tag, classifies each by its Conventional Commits type, and applies whichever bump that history actually earns (a breaking change beats a feature, a feature beats a fix). The changelog section is assembled the same mechanical way, pulled from the commit footers rather than freehand summary, and a commit that should have carried one but didn’t is a hard stop rather than a gap silently papered over. Composing the tag locally and publishing it are two separate moments: nothing pushes to the remote or shows up as a GitHub Release until you explicitly authorize that second step.

/ca:release ["<version>"] | --auto | --dry-run

An explicit version still gets checked against the derived classification; --auto derives it outright (the default when no version is given); --dry-run runs every check and reports readiness without composing anything.

> /ca:release --dry-run
Working tree clean. Branch: release/prep (not the default branch — OK).
Last tag: v2.4.1. Scanning 14 commits since...
Classification: 3 feat, 6 fix, 5 chore -> minor bump.
Derived version: v2.5.0. Matches plugin manifest? no — manifest still reads 2.4.1, would need updating.
Changelog: all 9 feat/fix commits carry a CHANGELOG: footer.
Dry run complete — no tag composed. Update the manifest version, then re-run without --dry-run.

Reach for /ca:release once the target branch is clean and its suite is green and you’re ready to cut a version. Work still in progress lands through /ca:feature or /ca:fix first — a release aggregates what already passed, it doesn’t chase down anything new.

GateWhenEffect
version derivationbefore a tag is composedthe SemVer bump is computed mechanically from Conventional Commits since the last tag — an explicit version that disagrees with that classification blocks rather than being silently adjusted
changelog completenesssame phase as version derivationevery feat/fix/perf commit in the release window must carry its own changelog note; a missing one blocks rather than being auto-filled
publish authorizationafter the tag is composed locallythe tag and the GitHub Release publish together, and only once you explicitly say to
Source — plugins/ca/commands/release.md (v2.9.1)
---
description: Cut a release the only sanctioned way — SemVer bump from the commit log, a CHANGELOG section, an annotated tag. The only path to a version tag.
argument-hint: ["<version>"] | --auto | --dry-run
---
# /ca:release — tagged release
The only permitted path to a version tag. A release is a deployment-readiness assertion: the codebase at this SHA satisfies the bar for shipping. `/ca:release` aggregates existing compliance — it does not duplicate it.
## Flow
Routes to the `release` skill. `/ca:release` targets the **`ca`** plugin only (ADR-0007): `LAST_TAG`
is the newest `ca` SemVer tag (`v*`, never the sibling `ca-sandbox-v*`), and the release window is the
`plugins/ca/`-scoped commit set — a `ca-sandbox` tag or commit never bumps `ca` or lands in its
changelog.
1. **Pre-flight** — working tree clean, on the configured release branch, suite green, no blocking
`[CONFIRM-NN]` open, HEAD not produced via `/ca:override`. Identify `LAST_TAG` and the release window.
2. **Version bump (SemVer)** — classify every commit in `LAST_TAG..HEAD` by Conventional Commits type
and apply the highest-precedence bump (major beats minor beats patch). An explicit version that
disagrees with the classification BLOCKS — the bump is never silently up- or downgraded.
3. **Changelog** — roll up the `CHANGELOG:` footers from `feat`, `fix`, and `perf` commits into a new
section. BLOCK if any `feat`/`fix` commit lacks the footer; never auto-fill it.
4. **Tag** — compose the annotated tag. Never push it to a remote without explicit user
authorization — publication is a separate decision.
`--dry-run` runs every gate and surfaces the readiness report, then STOPs before composing the tag.
## Arguments
- **`<version>`** (e.g. `"1.2.3"`) — explicit version; Phase 2 still classifies the window and BLOCKs
on disagreement.
- **`--auto`** — derive the version from the commit log (default when no version is given).
- **`--dry-run`** — run all gates, compose nothing. Combines with `--auto` or an explicit version.
An explicit version never combines with `--auto` — they are mutually exclusive.
## Routes to
`release` (`${CLAUDE_PLUGIN_ROOT}/skills/release/SKILL.md`).
## When NOT to use
- Tagging an in-progress branch → land work first via `/ca:feature` / `/ca:fix`.
- Pushing an already-composed tag → that is a separate user-authorized step.
- A changelog only → it is a phase output, not a standalone deliverable.
## Hard gate
MUST NOT compose a tag on a red suite. MUST NOT silently up- or downgrade the SemVer classification —
an explicit version that disagrees with the commit log BLOCKS. MUST NOT auto-fill a missing
`CHANGELOG:` footer. MUST NOT write to the default branch or force-push. MUST NOT push the tag without
explicit user authorization. Any BLOCK may be bypassed only via `/ca:override`.

View in repo