Release

Execute a deterministic release cycle: version the pending changesets, restructure the new changelog sections for a reader, add the release summary and commit, behind fail-closed gates. Touches only `CHANGELOG.md`, `package.json`, `pnpm-lock.yaml` and `.changeset/`, never rewrites released history, and never rewrites the wording that came from the changesets. Use when the user says "release", "cut a release", "bump version", or "publish".

How to use

Use this when you are ready to release a new version.

Prompt

Release

This skill does not write release notes. The wording was written once, in
cebreus-generate-missing-changesets, from the raw commit capture. Here it is
only moved and grouped. If a bullet reads badly, the fix belongs in the
changeset: STOP and go back a step. The only prose written here is the release
summary blockquote.

Binding rules (fail-closed)

  1. Any gate fails: STOP and report the exact reason. Never fix forward,
    reset, stage or commit without explicit user approval.
  2. Scope: never modify anything outside CHANGELOG.md, package.json,
    pnpm-lock.yaml and .changeset/. Reject the write before executing it.
  3. History: everything above and below the newest ## <version> section is
    immutable. Never delete, reorder or merge version sections.
  4. Content: never reword, merge, split or delete a bullet.
    regroup-changelog.mjs moves them; you do not.
  5. Never run changeset init — it overwrites the config and drops the
    custom changelog formatter.

1. Preflight

1.1 git status --porcelain MUST be empty. Otherwise STOP and list each
unclean path.

1.2 pnpm and node MUST exist. If not found, retry with the platform's
usual package-manager path prepended to PATH (on macOS, /opt/homebrew/bin).
Still missing: STOP.

1.3 At least one pending changeset MUST exist:

find .changeset -maxdepth 1 -name '*.md' ! -name README.md

None: STOP and run cebreus-generate-missing-changesets first. Never hand-write
a changeset here.

1.4 .changeset/config.json MUST exist — it is generated, never committed,
and step 2 deletes it. If it is missing, run
cebreus-generate-missing-changesets with --dry-run once to regenerate it.

1.5 Baseline (read-only, to .temp/). Record HEAD, every package version,
the line count of every existing CHANGELOG.md, and the full list of pending
changeset files. This is the reference for every post-flight gate.

2. Version

pnpm exec changeset version

If the CLI is missing, run pnpm install and retry. Never use
npx -y @changesets/cli: it fetches an unpinned version over the network at
release time, which can differ from the lockfile and from the config schema this
repository targets.

Non-zero exit, or no file changes: STOP.

Versioning was the last step that needed the config, so remove it — leaving it
behind puts an untracked file in the working tree of every repository that does
not gitignore it:

rm -f .changeset/config.json

3. Regroup

The changesets are split one per severity, so changeset version has already
produced correct ### Major Changes / ### Minor Changes / ### Patch Changes
subsections. Those stay. What the CLI cannot do is the finer split, because the
conventional type lives inside the bullet.

node .agents/skills/cebreus-release/regroup-changelog.mjs

It shares changelog-taxonomy.mjs with the capture script so the two cannot
disagree about which type belongs in which group. It finds that file beside
itself or in any sibling skill folder; --taxonomy <path> overrides.

With no arguments it takes every changelog git diff reports as modified,
rewrites only the newest ## <version> section of each, and leaves the rest
untouched. Beneath each severity heading it adds #### Breaking changes,
#### Features, #### Fixes, #### Improvements, #### Internal, in that
order, and strips the prefix that has become a heading. A severity block holding
a single group gets no inner heading. In a workspace repository the scope is
dropped (the changelog is already per package); in a single one it leads the
bullet as - **i18n:** …. Unknown or missing type goes to Internal. Nothing
is dropped, and a second run is a no-op.

Gate: the report MUST cover every file git diff --name-only -- '*CHANGELOG.md'
lists. Any skipped: no bullets in newest section means changeset version
produced an empty release section — STOP.

4. Release summary

Directly under each new ## <version> heading, insert a > blockquote of one
to three sentences: what this release changes and why it matters, consequence
first. Plain British English, no hashes, no paths, no claim the bullets below do
not support. Never touch an older version's blockquote.

This is the only text you write.

5. Post-flight gates

  1. Version. No package version may be lower than its baseline. Otherwise
    STOP and report the exact before and after strings.

  2. Changeset. Every baseline changeset file MUST be gone. Any survivor other
    than README.md: STOP and report the paths.

  3. Changelog coverage. changeset version bumps package.json and writes
    CHANGELOG.md in separate steps, and exits 0 even when the second fails — a
    broken prettier config in one package silently loses that package's whole
    release section. Prove every bump reached a changelog:

    for manifest in $(git diff --name-only -- '*package.json'); do
      dir=$(dirname "$manifest")
      version=$(grep -m1 '"version"' "$manifest" | cut -d'"' -f4)
      grep -q "^## ${version}\$" "$dir/CHANGELOG.md" 2>/dev/null \
        || echo "MISSING: $dir -> $version"
    done

    Any output: STOP and report. Never hand-write the missing section — fix the
    cause and re-run from a clean tree.

  4. History. Every pre-existing version heading MUST still be present and
    unmodified. Otherwise STOP and report it.

  5. Scope. git diff --name-only — any path outside rule 2: STOP and report
    each before doing anything else.

6. Commit

git add -u .changeset/
git add package.json pnpm-lock.yaml CHANGELOG.md
git add packages/*/package.json packages/*/CHANGELOG.md 2>/dev/null || true
git add sites/*/package.json sites/*/CHANGELOG.md 2>/dev/null || true
git add tools/*/package.json tools/*/CHANGELOG.md 2>/dev/null || true
git add apps/*/package.json apps/*/CHANGELOG.md 2>/dev/null || true

git add -u stages the changeset deletions without adding anything untracked
that happens to sit in .changeset/.

Verify with git diff --cached --name-only and unstage anything out of scope;
if it cannot be cleanly unstaged, STOP.

git commit -m $'chore(release): version packages' -m $'- package-a@1.0.1\n- package-b@2.0.0'

Subject is fixed: chore(release): version packages, verbatim, every time.
No version in the subject — the body carries it. Body: one line per updated
package, - <pkg>@<version>, sorted alphabetically. No trailers, no
attribution, no emoji.

Do NOT use --amend. Do NOT retry a failed commit without explicit approval.

A commit that fails is a STOP. Never pass --no-verify, never set
core.hooksPath, never touch .git/config, never edit or move a hook. A
release commit that skipped the repository's checks is worse than none. Report
the error verbatim and stop.

7. Report

Mode (workspace / single), commit hash, updated packages and versions,
modified changelogs, final git status --porcelain.


Why these two subjects are fixed, and how they sit against
.agents/skills/cebreus-commit/SKILL.md: docs and chore are allowed types;
both subjects are imperative, lower-case, ≤60 characters, no full stop. docs(changeset):
is what the changesets CLI itself uses; "Version Packages" is what the official
changesets/action names its commit. The one deviation: cebreus-commit says a
scope names a workspace directory or repo, while changeset and release
name pipeline stages. That is a closed vocabulary of two, not an invitation to
invent more.

Attachments