Advise on release size, unpublished changes, and consumer migration guides. Use before a release or when asked about version bumps or migration needs; never tag, push, or publish.
Installs into .claude/skills of the current project.
Are you the author of Prepare Release?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/a-novel-kit-prepare-release)
---
name: prepare-release
description: >
Advise on release size, unpublished changes, and consumer migration guides. Use before a release
or when asked about version bumps or migration needs; never tag, push, or publish.
---
# Prepare a release — size it, and write its migration guide
A release has two questions this skill answers before anyone clicks "Run":
1. **How big is it?** — patch or minor, read from the commit history, not guessed. (A major is not sized
here; it is a planned `vX`-line initiative — see step 2.)
2. **What must a consumer do to adopt it?** — captured as a **migration guide** that ships with the
code and lives forever, so the answer is never lost in a PR description or a Slack thread.
You are advisory: read the diff, propose the version, draft the guide. Core or the repository's
`owners-<class>` review and cut the release from the Actions tab. You never tag, push, or publish —
releases are human-only (see `manage-versions`).
> This skill is the manual stand-in for a future agent that runs it automatically before every
> release. Until then, invoke it deliberately when a release is near.
---
## 1. Read the unpublished diff
Find the last release and everything since, on the branch you'll release from (the default branch):
```bash
git fetch --tags origin
last=$(git tag --list 'v*' --sort=-v:refname | head -1) # or <subdir>/v* for a sub-dir module
git log --no-merges --pretty='%s' "$last..origin/master"
```
Read the **subjects** (the conventional-commit type/scope/`!`) and, for anything that might break or
deprecate, the **body/footers** (`BREAKING CHANGE:`). A squash-merge repo has one commit per PR, so the
subjects are the PR titles — usually enough; open the PR/diff when a subject is ambiguous about consumer
impact.
With **no prior tag**, this is the first release (`v0.1.0` or `v1.0.0` per the team's call) — there is
nothing to migrate _from_, so a guide is rarely needed.
---
## 2. Propose the bump
Apply this mapping — a **local variant** of Conventional Commits; the **highest** wins across the range:
| Commit signal | Bump |
| ---------------------------------------------------------------------------------- | --------- |
| any `feat:`, or a `!` / `BREAKING CHANGE:` too small for its own major (see below) | **minor** |
| only `fix:` / `perf:` / `refactor:` / `chore:` | **patch** |
**A major is never derived from the commit history here.** This is the deliberate departure from stock
Conventional Commits: a `!` or `BREAKING CHANGE:` footer does **not** size a release to major. A major
is a **planned, initiative-led migration on its own `vX` release line** — a global sweep, a paradigm
shift, a change whose migration is large enough to track as its own initiative — and it is cut from that
line when it lands, not proposed by this pass. If the range you are sizing contains a `!` and you think
it warrants a major, that is a signal to **stop and raise an initiative**, not to bump the second digit.
So a **breaking change small enough to absorb in a normal release stays a minor**: removing a capability
(especially one that no longer works), dropping a dead action, tightening a validation. It ships as a
minor with a migration guide (below), because it still asks a consumer to act, but it does not touch the
major. Name it plainly in the proposal — _"minor, carries one small breaking change (#NNN removes the
dead `foo` action)"_ — so the operator sees the break without a version jump implying a bigger one.
The **highest across the range is therefore minor** whenever any `feat:` or an absorbable break is
present, else patch. Docs/CI-only ranges (`docs:`, `ci:`, `chore:`) are a **patch** (still a release if
you want the notes), or skip the release entirely.
(There is no pre-1.0 special case: majors are initiative-gated regardless of the current major digit.)
State the proposed version plainly — e.g. _"`v1.0.3` → `v1.1.0` (minor: three `feat:`, no breaking
footer)"_ — and name the one or two commits that drove it. That version is also the **release-type the
human picks in the release workflow** and the **filename of the guide**.
---
## 3. Decide whether a migration guide is needed
A guide documents **consumer action**, not a changelog. Use this gate:
| Release shape | Guide? |
| ------------------------------------------------------------------------------------------ | --------------- |
| A major (the planned `vX`-line migration) | **Required** |
| Minor carrying an absorbable breaking change (a removal, a tightened validation) | **Required** |
| Minor that **deprecates** a path, adds a **preferred** alternative, or recommends a change | **Recommended** |
| Minor that is purely additive (new optional thing, no consumer change) | No (notes only) |
| Patch (bug/perf/internal) | No |
The middle row is the subtle one: a backward-compatible release can still _ask_ consumers to move
(a new recommended input, a renamed-but-aliased field, a new pattern). Nothing forces the change, but
the migration is real and worth writing down once. When in doubt for a minor, ask: _"is there anything
a consumer should change to fully benefit, even though nothing breaks?"_ If yes, write the guide.
---
## 4. Write the guide
**One immutable file per version**, never a single growing `UPGRADING.md` — the convention every mature
migration system uses (Flyway `V1__…`, Rails / GitLab timestamped migrations). A single appended file is
a merge-conflict magnet across parallel release branches and blurs which change maps to which release;
per-version files are additive (no conflicts) and map 1:1 to a tag.
- **Path:** `docs/migrations/vX.Y.Z.md`, named by the version that introduces the change. (A sub-dir Go
module — e.g. `cli/` — keys the file by its version too; a repo releases one module, so the version
is unambiguous.)
- **Immutable once shipped.** Never edit a released guide; a correction goes in the _next_ version's
guide, so history stays truthful.
- **Indexed.** Keep `docs/migrations/README.md` as a table, newest-first, linking each guide.
- **Linked from the release.** The release notes for `vX.Y.Z` should point at its guide (add the link
to the generated notes / the GitHub Release body).
- **Committed _with_ the change**, before the release is cut — so the released tag already contains its
own guide.
### Template
```markdown
# Upgrading to vX.Y.Z
One-line summary of what changed and why it matters to a consumer.
## Do I need to act?
**Required / Recommended / No** — and the one-sentence reason. If "No", say why the release is safe to
take as-is and stop here.
## <Change 1 — imperative title, e.g. "Switch bot inputs to client_id">
What changed and **why**. Then the concrete migration, before → after:
\`\`\`yaml
# before
app_id: ${{ vars.AGENT_BOT_ID }}
# after
client_id: ${{ vars.AGENT_BOT_CLIENT_ID }}
\`\`\`
Any **prerequisite** (a new variable, a new permission), whether the old path **still works**
(deprecated vs removed), and how to **verify** the migration landed.
## <Change 2 …>
…
## Notes
Anything optional, related follow-ups, or pointers to a larger effort this is part of.
```
Lead with **"Do I need to act?"** — most readers want exactly that, and a clear "No" for a safe minor is
a feature. Keep each change section to _what changed → why → before/after → caveats_.
---
## 5. Present — advisory only
Hand the operator: the **proposed version**, the **commits that drove it**, whether a **guide** was
written (and where), and the **release-type to pick** in the workflow. Then stop. Core or the
repository's owners cut the release (Actions ▸ `release` ▸ pick the bump); `release-core` stamps
versions, tags, and publishes.
You do not run it.
If the change ships in stages or spans repos, that ordering is a `manage-versions` concern — note it
and defer.
---
## Principles
- **The guide is consumer-facing.** Release notes list _what changed_; the guide says _what to do about
it_. A change needing no consumer action belongs in the notes.
- **One file per version, immutable.** Additive, conflict-free, 1:1 with a tag.
- **Derive patch-vs-minor from the commit history.** If the history is wrong (a `feat` mislabeled
`fix`), fix the discipline (`git-conventions`) rather than silently overriding the math.
- **A major is never derived; it is planned.** No commit range proposes a major. A major is an
initiative with its own `vX` branch that becomes master when it lands. A `!` in an ordinary range is
an absorbable breaking change (a minor with a guide), or a prompt to raise that initiative — never a
second-digit bump on the current line.
- **Advisory, never the trigger.** You propose; core or the repository's owners release.
---
## How it composes
```
prepare-release ─ (reads the unpublished diff) ─> proposed bump + docs/migrations/vX.Y.Z.md
│
├─ git-conventions — the commit→bump mapping it applies
├─ manage-versions — versioning mechanics, staged/cross-repo rollouts the guide may reference
└─ release workflow — the human cuts the release with the proposed bump; the guide ships in the tag
```
A migration guide written here is the **instruction**; rolling it out across consumer repos is execution
(`implement-feature` / `manage-versions`), often tracked as its own follow-up issue.