# Prepare Release

> Run BEFORE cutting a release: it reads the commits since the last tag, proposes the version bump (`fix`/`chore`→patch, `feat` or an absorbable breaking change→minor; a major is never derived from commits — it is a planned `vX`-line initiative), and drafts the per-version migration guide (`docs/migrations/vX.Y.Z.md`) when consumers must act. Use it when asked "is this patch / minor / major?", "what changed since the last release?", or "does this need a migration guide?". ADVISORY — the human cuts the release; this never tags, pushes, or publishes.

- Skill: `a-novel-kit/prepare-release` (Agent Skill)
- Install (CLI): `npx skillmds@latest add a-novel-kit/prepare-release`
- Raw SKILL.md: https://api.skillmd.com/api/skills/a-novel-kit/prepare-release/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: a-novel-kit (https://skillmd.com/u/a-novel-kit)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/a-novel-kit/prepare-release

---


# 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. The human reviews and cuts 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. The human cuts 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; the human releases.

---

## 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.

