Cut a release — detect versioning context, generate a changelog from conventional commits, bump versions, create a git tag, and optionally squash-merge to a release branch. Use when the user says "release", "cut a release", "tag a release", "bump the version", "create a changelog", "ship a version", "publish", or any variation of shipping/publishing a version. This skill is intentionally generic and works across any repo — it infers context from git history and project structure rather than assuming a specific setup. Supports the trunk+release branch model (ADR-013) when a `release` branch exists.
Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.
Quelldateien prüfen
Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.
Mit Codex oder Claude installieren Kopieren Sie diesen Prompt, fügen Sie ihn in Codex, Claude oder einen anderen Assistant ein und lassen Sie die Skill-Seite prüfen und installieren.
Ein direkter Befehl überspringt den Prüf-Prompt. Prüfen Sie die Quelle, bevor Sie ihn ausführen.
Cut a release — detect versioning context, generate a changelog from conventional commits, bump versions, create a git tag, and optionally squash-merge to a release branch. Use when the user says "release", "cut a release", "tag a release", "bump the version", "create a changelog", "ship a version", "publish", or any variation of shipping/publishing a version. This skill is intentionally generic and works across any repo — it infers context from git history and project structure rather than assuming a specific setup. Supports the trunk+release branch model (ADR-013) when a `release` branch exists.
{"short-description":"Version bump, changelog, and git tag","version":"1.5.0","author":"cristos","source":"swain"}
Release
Cut a release by detecting the project's versioning context, generating a changelog, bumping versions, and tagging. Works across any repo by reading context from git history and project structure rather than hardcoding assumptions.
Override file
Before starting, read .agents/release.override.skill.md if it exists. This is a freeform markdown file authored by the project owner whose instructions layer on top of this skill — its contents take precedence where they conflict. It can narrow defaults, specify version file locations, set tag formats, add pre/post-release steps, or anything else.
If no override exists, proceed with context detection alone.
Workflow
1. Gather context
Infer the project's release conventions from what already exists. Do all of these checks up front before proposing anything to the user.
Tag history:
git tag --sort=-v:refname | head -20
From existing tags, infer:
Tag format — v1.2.3, 1.2.3, name-v1.2.3, or something else
Versioning scheme — semver, calver, or custom
Current version — the most recent tag that matches the detected pattern
If there are no tags at all, note that this is the first release and ask the user what format they want.
Commits since last release:
git log <last-tag>..HEAD --oneline --no-decorate
If no tags exist, use all commits (or a reasonable window — ask the user if there are hundreds).
Version files — scan for files that commonly hold version numbers:
# Look for common version carriers
grep -rl 'version' --include='*.json' --include='*.toml' --include='*.yaml' --include='*.yml' -l . 2>/dev/null | head -20
Also check for VERSION files, version: in SKILL.md frontmatter, version fields in package.json, pyproject.toml, Cargo.toml, etc. Don't modify anything yet — just catalog what exists.
2. Determine the bump
Parse commits since the last tag using conventional-commit prefixes to suggest a bump level:
Commit prefix
Suggests
feat
minor bump
fix
patch bump
docs, chore, refactor, test, ci
patch bump
BREAKING CHANGE in body, or ! after type
major bump
The highest-level signal wins (any breaking change = major, any feat = at least minor, otherwise patch).
If commits don't follow conventional-commit format, fall back to listing them and asking the user what bump level feels right.
3. Propose the release
Present the user with a release plan before executing anything. Include:
Current version (from latest tag, or "first release")
Proposed version (with the detected bump applied)
Changelog preview (thematic narrative — see step 4)
Files to update (version files found in step 1, if any)
Tag to create (using the detected format)
Wait for the user to confirm, adjust the version, or abort. If the user wants a different version than what was suggested, use theirs — the suggestion is a starting point, not a mandate.
4. Generate the changelog
Synthesize, don't transcribe. The changelog is for humans reading release notes, not for git log --oneline with extra steps. Dozens of commits should collapse into a few coherent narratives.
Before writing, read the existing CHANGELOG.md (if any) to match the voice, density, and structure the project already uses. The changelog should read like the same person wrote every entry.
Template-driven changelog
The changelog is rendered from a Jinja2 template (templates/changelog.md.j2) fed by a JSON data file. This separates the bucketing decision (which section does a change belong in?) from the rendering (how does the markdown look?).
Step 4a — Classify commits into four buckets. Each commit goes into exactly one:
Bucket
What belongs here
What does NOT belong here
features
Shipped capability: new skills, CLI flags, scripts, bug fixes that change behavior, refactors that change UX
Planning artifacts that describe future work
roadmap
Forward-looking previews of planned work — what's coming and why it matters to the user. Write as "X is being planned/designed because Y" not "SPEC-NNN created". Omit artifact IDs unless the reader would search for them. Skip items that are pure internal housekeeping.
Artifact state transitions ("EPIC activated", "SPEC created"), anything that shipped (that's features)
research
Trove collections, spike completions, research artifacts, evidence gathered
Specs that resulted from research (those are roadmap)
supporting
Chores, dependency bumps, cross-ref enrichment, minor refactors, CI changes
Anything that changes user-visible behavior (that's features)
The key distinction agents get wrong: creating a SPEC or EPIC is a roadmap change, not a feature. A feature is something the operator can use today because it shipped in this release. A SPEC is a plan for something that will ship later.
Roadmap anti-pattern: "EPIC-029 activated with 3 child SPECs (SPEC-118, SPEC-119, SPEC-120)" is noise — it describes artifact state transitions that only matter to the project maintainer. Instead write: "Trunk detection is being generalized so swain works on any branch name without configuration." The reader should understand what's coming and why they'd care, not which internal tracking artifacts changed state.
Step 4b — Build the JSON data file. Write a temporary JSON file with this structure:
{"version":"0.10.0-alpha","date":"2026-03-21","features":[{"heading":"CLI Roadmap Renderer","body":"chart.sh roadmap --cli produces deterministic, terminal-friendly\noutput grouped by Eisenhower quadrant with all first-degree children\nnested under their parent initiative. New swain-roadmap skill wraps\nit as the user-facing entry point: regenerate, open, display."},{"text":"Dependency graph rendering switched to flowchart TD for clearer layout"}],"roadmap":["Session facilitation rebuild — rethinking how swain helps the operator maintain focus, make decisions, and recover context across sessions"],"research":["Google Stitch SDK trove — 7 sources collected"],"supporting":["Cross-reference enrichment across ~100 doc files"]}
Feature and roadmap items use {"heading": "Title", "body": "Narrative..."} for major work (renders as #### Title with a narrative paragraph) or {"text": "..."} for smaller bullets. Roadmap items can also be plain strings for one-liners. Use headings when a topic has enough substance for a paragraph; use bullets for one-liners. Research and supporting sections are flat string arrays. Empty arrays are fine — the template omits empty sections.
This prints the rendered markdown to stdout. Review it, then prepend to CHANGELOG.md.
If jinja2 is unavailable, fall back to writing the markdown directly using the same four-section structure — the template encodes the format, not the only way to produce it.
What to omit
Merge commits, lifecycle hash stamps, index refreshes, bookmark advances, and other mechanical commits should be omitted entirely — they add noise without information. Commit-type prefixes (feat:, fix:) should be stripped from any text that makes it into the changelog.
Use commit prefixes for bump detection, not changelog structure
Conventional-commit types determine whether it's a major/minor/patch bump (step 2). After that, forget them — the changelog reader doesn't care that something was a feat vs docs.
Where to put the changelog:
If a CHANGELOG.md exists, prepend the new section at the top (below any header)
If no changelog exists, ask the user whether they want one created, and where
If the user doesn't want a file, just output it to the conversation
5. Bump versions
Update version strings in the files identified in step 1. Be surgical — only change the version value, not surrounding content. For each file type:
package.json / composer.json: update the "version" field
pyproject.toml / Cargo.toml: update version = "..."
SKILL.md frontmatter: update version: in YAML header
VERSION file: replace contents
If a file has multiple version-like strings and it's ambiguous which one to update, ask the user rather than guessing.
5.5. Security gate
Before tagging, run the security scanner to catch secrets, dependency vulnerabilities, and static analysis issues. Invoke the swain-security-check skill (or run the scanner script directly if the skill isn't available):
Use the tag format detected in step 1 (or what the user specified).
6.5. Squash-merge to release branch
If a release branch exists (check with git rev-parse --verify release 2>/dev/null), squash-merge the current branch (trunk) into it:
# Detect trunk branch dynamically (EPIC-029)
REPO_ROOT=$(git rev-parse --show-toplevel)
TRUNK=$(bash "$REPO_ROOT/scripts/swain-trunk.sh")
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
# Ensure we're on the trunk (development) branchif [ "$CURRENT_BRANCH" != "$TRUNK" ]; thenecho"Warning: not on $TRUNK ($CURRENT_BRANCH). Skipping release branch update."else# Tag trunk first (lifecycle hashes must be reachable from trunk per ADR-012)# Tag was already created in step 6# Squash-merge trunk into release
git checkout release
git merge --squash "$TRUNK"
git commit -m "release: <tag>"# Return to trunk
git checkout "$TRUNK"fi
If no release branch exists, skip this step silently — the project hasn't adopted the trunk+release model yet.
7. Offer to push
Ask the user if they want to push. If a release branch was updated in step 6.5:
Push only the specific tag — git push --tags tries to push every local tag and produces noisy rejections for tags that already exist on the remote.
Don't push without asking — the user may want to review first, or they may have a CI pipeline that triggers on tags.
Edge cases
Monorepo with multiple version streams: If the tag history suggests per-package tags (e.g., frontend-v1.2.0, api-v3.1.0), ask the user which package they're releasing rather than assuming.
Pre-release versions: If the user asks for a pre-release (alpha, beta, rc), append the pre-release suffix to the version: 1.5.0-alpha.1. Follow the existing convention if prior pre-release tags exist.
No conventional commits: If the commit history doesn't use conventional prefixes, don't force the grouping. Present a flat list and let the changelog be a simple bullet list of changes.
Dirty working tree: If there are uncommitted changes when /swain-release is invoked, warn the user and ask whether to proceed (changes won't be included in the release) or abort so they can commit first.
Session bookmark
After a successful release, update the bookmark: bash "$(find . .claude .agents -path '*/swain-session/scripts/swain-bookmark.sh' -print -quit 2>/dev/null)" "Released v{version}"