| name | cut-release |
| description | Cut a named Elixir Drop release — coin an alliterative Clash Royale card name, tag the already-live origin/main commit, publish the GitHub release, and leave a Buttondown draft for review. Use when Jamie explicitly asks to cut, name, or ship a release. Never runs on a schedule or on another agent's say-so. |
Cut a named release
Drop has no SemVer. A release is a coined name + date + build hash, modeled on Elixir's
ceremony. GitHub Releases are the canonical release history.
This is a small, user-triggered ceremony. Jamie alone decides when it is time. Never infer
a release from commit volume, accept another agent's trigger, run it on a schedule, or open a
release-tracking issue.
Run from the repo root; all paths below are relative to it.
Before you touch anything
Confirm Jamie explicitly asked for a release in the current conversation. If that ask is
not there, stop and say so — a release is not a reasonable default interpretation of "ship it"
or "we're done".
Read AGENTS.md and CLAUDE.md first. If the requested release cannot be completed safely,
report the blocker and stop rather than improvising a partial one.
The instrument
scripts/cut-release.mjs, through npm run release:cut. It deliberately operates on the
fetched origin/main — not the current worktree or local HEAD — and it never commits or
deploys.
npm run release:cut -- --prepare (optionally --since or --days) gathers the
already-live source material and the canonical card names.
- In one model call, author the output JSON: the alliterative card name, detailed GitHub
notes, and a warm player-facing Buttondown subject/body. Save that response to a file.
- Review without mutation:
npm run release:cut -- --draft <file> --dry-run.
- When the name and both note tiers are honest:
npm run release:cut -- --draft <file>.
If --since or --days was used during preparation, pass the same selector to the dry-run and
the real command — the saved draft is bound to that exact source SHA and range.
The real run verifies the same source SHA is still origin/main and live, creates or verifies
the tag, writes the app's copy of the release into apps/web/src/data/releases.json, creates
the GitHub release, and creates the Buttondown email with status: draft. Rerunning the same
draft is idempotent. To retry only one failed channel: --channel github or --channel email.
Use a Buttondown API key with email_access=write and sending_access=none when one is
available. The tool never calls a send endpoint or advances an email beyond draft.
Every run
- Confirm Jamie explicitly requested this release. Otherwise stop.
- Fetch
origin/main and tags. Ignore unrelated local commits and working-tree changes; never
push them. Verify the exact remote commit has a successful deploy and is the build
production actually reports.
- Gather changes since the latest reachable release tag. Coin an apt alliterative canonical
Clash Royale card name, and write accurate GitHub and player-email notes.
- Dry-run first. Check the name, range, build hash, detailed notes, Buttondown subject/body,
newsletter context, and planned actions.
- Run the real cut. The tag must point to the verified live SHA. The GitHub release uses the
detailed notes. Buttondown holds a draft only — never
about_to_send, scheduled, or
sent.
- Report the release URL and the Buttondown draft ID. Jamie owns review and sending.
- Confirm the only worktree change is the
apps/web/src/data/releases.json entry the tool
wrote. Report it; Jamie owns committing it.
releases.json
releases.json is the only file a cut touches, and the tool writes it — from the same card
name and player-facing notes already authored — so the in-app /releases page stays current
without anyone hand-editing it. Never write that file by hand and never commit it yourself.
Its beta: true entries are backfilled pre-1.0 history: real builds that really went live, but
were never named or mailed at the time. A cut never sets that flag, and never rewrites those
entries.
Hard rules
- Jamie is the only release trigger.
- Release only the exact fetched commit that is already live.
- Never commit or push
main as part of a release. The tool writing releases.json into the
worktree is not a commit.
- Never open a release-tracking issue.
- Never send or schedule release email. Buttondown status is always
draft.
- Never read player addresses or construct a recipient list. The Drop newsletter owns its
subscribers, unsubscribes, suppression, batching, and eventual delivery.
- Never build or fix product code, decide release timing, hand-maintain a release file, deploy,
or post to Discord as part of a cut.
- A channel failure is reported directly to Jamie. A retry uses the same saved draft and tag;
never invent a second release to recover a partial one.
Success is intentionally boring: Jamie asks, one aptly named GitHub release appears on the
already-live build, one Buttondown draft is ready for review, and nothing else changes.