| name | prod-upd |
| description | Push the current main branch to production. Reads the latest version from CHANGELOG.md, creates an annotated vX.Y.Z git tag on main's latest commit, and pushes the tag to trigger the automatic production deploy. Use when the user wants to release/deploy/ship to prod (e.g. "/prod-upd", "push to prod", "cut the release"). |
/prod-upd — push to production
Cuts a production release by tagging main's latest commit. Pushing a
vX.Y.Z tag is what triggers the automatic prod deploy (GitHub Actions). This
skill only pushes a tag — it never pushes commits, force-pushes, or moves an
existing tag.
Work through the steps in order. If any check fails, stop and report instead
of pushing — a bad tag is a prod deploy.
Steps
-
Cut the release notes (on the release PR, before the commit reaches
main). Three files, three audiences — see
Writing the release notes below for the
bar each one has to clear:
CHANGELOG.md — what a user gets. Short. This is the one that goes
wrong; read that section before writing a line of it.
dev-changelog.md — the engineering history. Everything cut from
CHANGELOG.md lands here, at whatever length it needs.
src/features/whats-new/whats-new-content.tsx — only on a new
major.minor line, which is the only thing the panel announces.
-
Sync main.
git fetch origin --tags --prune
git checkout main
git pull --ff-only origin main
- Confirm the working tree is clean:
git status --porcelain must be empty.
If it isn't, stop and report (don't tag a dirty tree).
- Confirm local
main == origin/main (the release commit must already be on
main — merge the release PR first).
-
Read the version from the newest CHANGELOG.md entry.
- The newest entry is the first
## [X.Y.Z] - YYYY-MM-DD heading in the
file. Extract X.Y.Z; the tag is vX.Y.Z.
- Sanity check:
X.Y.Z should equal the "version" in package.json. If
they differ, stop and ask which is correct — do not guess.
- Read the entry as a user would, in the app:
pnpm test covers the
changelog modal, and scripts/changelog-stats.mjs prints the numbers.
A newest entry averaging much over ~180 characters a bullet has
engineering detail in it that belongs in dev-changelog.md — fix it on
the release PR and merge that first. A tag is not the place to discover
this.
-
Guard against re-tagging.
git tag -l vX.Y.Z and git ls-remote --tags origin vX.Y.Z.
- If the tag already exists (locally or remotely), stop and report: the
release is already cut. Bump the version + changelog first.
-
Walk the guided tours (release gate — per CLAUDE.md this full walk
runs at release time, not per PR):
- Run the
/tour-check skill (pnpm run build, serve dist on :3005,
then pnpm run test:tours and MOBILE=1 pnpm run test:tours).
- Compare misses against the known pre-existing list; any NEW
MISS
is a release blocker — stop and report instead of tagging.
-
Create the annotated tag on main's latest commit:
git tag -a vX.Y.Z -m "Release vX.Y.Z"
(Use the changelog entry's one-line theme in the message if there's a tidy
one, e.g. -m "Release vX.Y.Z — guided tours & mic insights".)
-
Push the tag (this triggers the prod deploy):
-
Report.
- State the tag, the commit SHA it points at, and the deploy run that started
(
gh run list -L 3 or the Actions URL) so the user can watch it.
- If a DB migration is pending for this release (check project memory, e.g.
the prod-db-migration note), surface it now — a tag deploy ships code, not
schema.
Writing the release notes
Three files. Getting this wrong is not cosmetic: CHANGELOG.md is rendered
verbatim in the app's Changelog modal, so whatever is written there is what a
user reads.
CHANGELOG.md — the user-facing one
The bar: every bullet is one to three short sentences, and a stranger can
tell what changed for them without knowing how the app is built.
0.9.0 is what going wrong looks like — 110 bullets averaging 347 characters,
full of internal module names and rationale nobody outside the repo needs.
0.8.0 is the model: 61 bullets, ~142 characters each. Aim there.
Write it like this:
- Open with a bold sentence that is the whole point. "The rest of the
band plays." Then at most two sentences on what it means for the reader.
Many entries need nothing after the bold sentence at all.
- Say what a person gets, not what was built. "Every part you choose,
stacked a few bars to a row" — not "a pure-geometry layout pass shared by
two painters".
- Cut every trace of the work itself: PR numbers, issue numbers, file paths,
function and module names, store names, migration numbers, plan documents,
"refactored", "extracted", "now uses". None of it belongs here.
- Concrete nouns over adjectives. No marketing, no superlatives, no
"seamless" or "powerful". Never claim AI.
- Fold the small ones together. A closing "Smaller things." bullet
holding five one-clause fixes beats five bullets nobody reads.
- Only the markdown the modal renders:
**bold**, _italic_, `code`,
[label](url), and one level of indented sub-bullets. Hard-wrap at 78
columns — continuation lines are indented two spaces and carry no marker.
- Do not drop anything. Everything cut for length moves to
dev-changelog.md in the same commit; the detail is worth keeping, it is
just not worth a user's evening.
If a change genuinely needs a paragraph of explanation, that is a sign it
wants a What's New entry — not a longer changelog bullet.
dev-changelog.md — the engineering one
No length limit and no audience problem: PR numbers, plan documents, the
reasoning, the traps found on the way. Same version heading and date as
CHANGELOG.md, sectioned however the release is easiest to follow.
src/features/whats-new/whats-new-content.tsx — the release page
Only touched when the major.minor line changes — the panel announces once
per release line, so a patch never gets one. Six to eight highlights, chosen
from the changelog's own entries: what it is, and the shortest real path to
trying it. Every tryIt must be followable by looking at the app.
Notes
- Author/commit identity and "never force-push" rules from project memory still
apply. Annotated tag only; no
-f, no re-tagging.
main must contain the release commit before running this. If main is
behind or the version/changelog weren't bumped, do that first (or via the
release PR), then run /prod-upd.