Skip to content
Back to skills

Release

ASecurity

Cut a {{project.name}} release — pick the semver bump from the changes since the last tag (or take an explicit version), open a `chore/bump-X.Y.Z` PR, and label it so the tag is pushed on merge. Invokable by users and agents.

  • 2 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added September 19, 2026
ai-agentsbashnoderailsgit

Security analysis

A100/100

Scanned September 19, 2026

npx -y skills add dustinkeeton/wafflestack --skill release --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Release?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Release
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/dustinkeeton-release-wafflestack/badge)](https://www.skillsdirectory.com/skills/dustinkeeton-release-wafflestack)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: release
description: Cut a {{project.name}} release — pick the semver bump from the changes since the last tag (or take an explicit version), open a `chore/bump-X.Y.Z` PR, and label it so the tag is pushed on merge. Invokable by users and agents.
user-invocable: true
argument-hint: "<explicit X.Y.Z> | <major|minor|patch> | (omit to infer the level from changes since the last tag)"
---

# Cut a Release

Releases are **lightweight tags `vX.Y.Z` on the bump commit** (see the `git-workflow`
skill's *Releases* section for the spec). This skill automates the **bump-PR half**: it
prepares the version bump and opens a labeled PR. It deliberately does **not** push the tag —
the `waffle-release-hook` workflow does that on merge, so the tag always lands on the actual
bump commit on `main`. Never `git tag` / `git push --tags` from this skill.

## 1. Determine the new version

Resolve `$ARGUMENTS`:

| `$ARGUMENTS` | What to do |
|--------------|------------|
| an explicit `X.Y.Z` (e.g. `0.9.0`) | Use it verbatim as the new version. |
| `major` / `minor` / `patch` | Bump the current `package.json` version by that level. |
| empty / omitted | **Infer the level** from the changes since the last tag (below), then bump. |

Read the current version and the last release tag:

```bash
CURRENT=$(node -p "require('./package.json').version")
LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
```

To infer the level, review what changed since `$LAST_TAG` and classify by semver — read from
the perspective of a **consuming** project:

```bash
git log --oneline "${LAST_TAG:+$LAST_TAG..}HEAD"
git diff --stat "${LAST_TAG:+$LAST_TAG..}HEAD"
```

- **patch** — content-only fixes; a consumer just re-renders.
- **minor** — new stacks/items or additive (optional) config; existing config/extensions untouched.
- **major** — a renamed/removed item, a new **required** config key, or a changed file layout (ships a migration).

Prefer the project's `CHANGELOG.md` `[Unreleased]` section as the source of truth for what
landed; if it lists a breaking change, that forces the level. State the level and the
resulting `X.Y.Z` before proceeding.

## 2. Branch

Never work on `main`. Create the bump branch (per `git-workflow`):

```bash
git checkout main
git pull
git checkout -b chore/bump-X.Y.Z
```

## 3. Apply the bump

1. **package.json + lockfile** — the one command that touches both, no tag:

   ```bash
   npm version X.Y.Z --no-git-tag-version
   ```

2. **Other documented version locations** — update the version string in each file listed in
   `release.versionFiles` (find the old `CURRENT` version, replace with the new one). For this
   project that list is: {{release.versionFiles}}. If the list is empty, there are no extra
   files to touch beyond package.json/lockfile — skip this step.

3. **CHANGELOG.md** — stamp the release. Rename the `## [Unreleased]` heading to
   `## [X.Y.Z] - YYYY-MM-DD` (today's date), then add a fresh empty `## [Unreleased]` section
   above it. Keep every entry — `wafflestack upgrade` reads these dated sections to tell a
   consumer what changed between versions, so an unstamped release is invisible to it. Confirm
   the entry carries a **Consumer impact** line.

4. **Re-render if generated output tracks the version** — if this project generates files from
   versioned sources (e.g. a toolkit that renders stacks), regenerate them now so the bump and
   its render land in the same commit. Never hand-edit generated output.

## 4. Pre-flight

Run the full checklist from `git-workflow`; do not open the PR if any fail:

1. `{{project.lintCmd}}`
2. `{{project.typecheckCmd}}`
3. `{{project.testCmd}}`
4. `{{project.buildCmd}}`

## 5. Commit, push, open the labeled PR

```bash
{{git.cmd}} commit -m "$(cat <<'EOF'
chore: bump to X.Y.Z

{{git.coAuthorTrailer}}
EOF
)" <explicit paths — package.json, package-lock.json, CHANGELOG.md, and each versionFiles path>
git push -u origin chore/bump-X.Y.Z
```

Open the PR against `main`. The body must carry the **consumer-impact notes** for this
version — lift them from the `[X.Y.Z]` CHANGELOG entry you just stamped, so a reviewer sees at
a glance whether the upgrade is a plain re-render or needs a migration:

```bash
gh pr create --base main --title "chore: bump to X.Y.Z" --body "$(cat <<'EOF'
## Summary
- Bump to X.Y.Z (<level>).

## Consumer impact
<the Consumer impact line(s) from the CHANGELOG [X.Y.Z] entry>

## Release
Labeled `{{labelHook.releaseLabel}}` — merging this PR triggers `waffle-release-hook`, which
pushes the lightweight tag on the merge commit. No tag is pushed from this PR.

{{git.coAuthorTrailer}}
EOF
)"
```

## 6. Apply the release label (arms the tag-on-merge hook)

The `waffle-release-hook` workflow fires only when a **merged** PR carries the label in
`labelHook.releaseLabel` (default `waffle:release`). Apply it to the bump PR:

```bash
gh pr edit <PR#> --add-label "{{labelHook.releaseLabel}}"
```

The label must already exist in the repo — `gh pr edit --add-label` fails otherwise. Check
with `gh label list`; if it's missing, ask the user before creating it, e.g.:

```bash
gh label create "{{labelHook.releaseLabel}}" --color 0E8A16 --description "Merging pushes the release tag (waffle-release-hook)"
```

If the repo has **not** installed `waffle-release-hook.yml` (it is opt-in syrup), applying
the label does nothing on merge — say so, and the maintainer must push the tag manually:
`git tag vX.Y.Z <merge-commit-sha>` (lightweight), then `git push origin vX.Y.Z` — two
separate commands.

**Caveat — tag-triggered downstream pipelines.** If the repo has `on: push: tags` workflows
(a build, provenance, or GitHub-Release-asset pipeline the tag is meant to start), the identity
that pushes the tag decides whether they fire. The installed hook pushes with the default
`GITHUB_TOKEN` unless a `WAFFLE_RELEASE_TOKEN` PAT/GitHub App token secret is set — and a tag
pushed by `GITHUB_TOKEN` does **not** trigger other workflows (GitHub's anti-recursion rule).
The tag lands and looks done, but nothing downstream ships. Tell the maintainer to set
`WAFFLE_RELEASE_TOKEN` (or have the tag workflow expose a `workflow_call` entry point invoked by
a content-gated tagging workflow); see the github-workflow stack's release-hook setup note. A
**manual** `git push origin vX.Y.Z` runs under the maintainer's own credentials, so it does
trigger those workflows — the limitation is specific to the hook's `GITHUB_TOKEN` push.

## 7. Report

Output: the new version and inferred level, the PR URL, that the release label was applied
(or created / skipped, with reason), and the reminder that **the tag is pushed by
`waffle-release-hook` when the PR merges — do not push it by hand.**

## Guardrails

- Never push to `main`; the bump lands via the PR the maintainer merges.
- Never `git tag` or `git push --tags` from this skill — tagging is the on-merge hook's job so
  the tag lands on the real bump commit.
- Stage explicit paths only; never `git add -A`.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…