<!-- swain-model-hint: sonnet, effort: medium -->
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:
bash
git tag --sort=-v:refname | head -20
From existing tags, infer:
- Tag format — , , , 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:
bash
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:
bash
# 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
files,
in SKILL.md frontmatter,
fields in
,
,
, 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 |
|---|
| minor bump |
| patch bump |
| , , , , | patch bump |
| 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
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.
Thematic sections over commit-type buckets
Group by
what changed for the user, not by
/
/
prefix. A section like "### Docker Sandbox (swain-box)" that tells the story of that feature area is far more useful than scattering its commits across "New Features", "Bug Fixes", and "Documentation".
Identify the major themes by scanning commits for shared scopes, artifact IDs, and feature areas. Each theme that has 3+ commits gets its own
section with a descriptive heading.
Prose for major work, bullets for small work
If a theme represents significant new capability, write a paragraph (or a few) with bold lead-ins explaining what it is and why it matters. The reader should understand the feature without reading the commits.
markdown
### Trove Redesign (BREAKING)
"Evidence pool" is now "trove" — a better name for what swain-search produces,
which ranges from research evidence to reference libraries to repo mirrors.
`evidencewatch` becomes `trovewatch`.
**Hierarchical sources** — sources are no longer flattened to `001-slug.md`. Each
source gets its own directory (`sources/<source-id>/`), and repository or
documentation-site sources mirror their original tree structure.
For smaller themes or isolated changes, a flat bullet list is fine.
Supporting Changes catchall
Minor fixes, chore commits, refactors, and small improvements that don't fit a theme go in a
section at the end — not interspersed with the major stories.
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 (
,
) 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
vs
.
Where to put the changelog:
- If a 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 field
- pyproject.toml / Cargo.toml: update
- SKILL.md frontmatter: update 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.
6. Commit and tag
Stage the changed files (changelog + version bumps) and commit:
bash
git add <changed-files>
git commit -m "release: v1.5.0"
Then create an annotated tag:
bash
git tag -a <tag> -m "Release <tag>"
Use the tag format detected in step 1 (or what the user specified).
7. Offer to push
Ask the user if they want to push the commit and tag:
bash
git push && git push origin <tag>
Push only the specific tag —
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.,
,
), 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:
. 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
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}"