| name | release |
| description | Cut a new AperiSolve release — bump the version, commit "chore(release): X.Y.Z", tag it, push, and publish a GitHub Release whose notes are computed from the commits since the last tag. Use when the user says "release", "cut a release", "ship a release", "publish X.Y.Z", or "/release". Pushing the tag triggers the production deploy workflow, so this always confirms first. |
release — bump, commit, tag, push, publish GitHub Release
Cut a release of AperiSolve. The steps are: bump the version, commit the bump as
chore(release): X.Y.Z, push main, create and push an annotated tag X.Y.Z,
then publish a GitHub Release whose body is computed from the commits since the
last tag.
The user's version request (if any) is: $ARGUMENTS — an explicit version
(3.3.3), a bump level (patch / minor / major), or empty (infer + confirm).
⚠️ Pushing the X.Y.Z tag triggers .github/workflows/release.yml, which
builds the Docker image AND deploys to production. Treat this as a
hard-to-reverse, outward-facing action: you MUST show the computed plan and get
an explicit go-ahead (step 4) before executing step 5 onward.
How this repo versions (facts, don't re-derive)
- Version lives in
pyproject.toml ([project] version = "X.Y.Z") and in
uv.lock (under [[package]] name = "aperisolve").
- ⚠️ Never blind-
sed uv.lock. Unrelated packages can share the version
string (e.g. greenlet has sat at the same 3.3.x), so a global replace
corrupts them. Bump the lockfile with uv lock, or, if offline, edit only
the version = "…" line that sits directly under name = "aperisolve".
- Tags are bare
X.Y.Z (no v prefix), annotated, message
Release X.Y.Z — <one-line summary>.
- The release commit contains only the version bump (2 files). Feature code is
expected to already be merged to
main (via /commit).
- Repo slug for compare/release URLs:
Zeecka/AperiSolve.
Workflow
Run in order. Stop and report on any problem instead of forcing anything.
Combine independent Bash calls where you can.
1. Preflight
git rev-parse --is-inside-work-tree; confirm origin exists (git remote).
gh auth status — must be logged in (needed for the GitHub Release).
- Branch:
git branch --show-current. Releases deploy from main, so require
main. If you're not on main, stop and tell the user to land the code on
main first (e.g. /commit) then re-run — do not tag a feature branch.
- Sync:
git fetch --tags origin, then ensure local main matches
origin/main (git pull --ff-only origin main). If it can't fast-forward,
stop and report.
- Clean tree:
git status --porcelain must be empty (the only diff will be the
bump this skill creates). If dirty, stop — tell the user to commit/stash first.
2. Compute the version and the release text
- Previous version =
pyproject.toml [project] version (call it $PREV).
Sanity-check a tag $PREV exists: git rev-parse -q --verify "refs/tags/$PREV".
- Commits to release =
git log "$PREV"..HEAD --no-merges --format='%h %s'
(also skim bodies for BREAKING CHANGE). If empty, stop: "nothing to
release since $PREV".
- Next version
$NEXT:
- If
$ARGUMENTS is an explicit X.Y.Z, use it.
- If it's
patch/minor/major, apply that bump to $PREV.
- If empty, infer and propose (confirm in step 4): any
feat → minor;
only fix/perf/refactor/chore/docs/test → patch; any ! or
BREAKING CHANGE → major. This is a suggestion, not a rule — the
maintainer decides (past feat releases have shipped as patches).
$NEXT must be strictly greater than $PREV and not already a tag.
- Compose two texts from the commits (this is the "computed content"):
$SUMMARY — one line (≤ ~120 chars), plain, what changed. Used in the
tag message and as the commit-body lead.
$NOTES — the GitHub Release body, markdown, in this repo's house style:
a themed ##/### heading, grouped bullets that describe user-visible
changes (not raw commit subjects), and end with the compare link:
**Full changelog:** https://github.com/Zeecka/AperiSolve/compare/$PREV...$NEXT
Keep it faithful to the actual commits — summarize, don't invent.
3. Bump the version
- Edit
pyproject.toml: [project] version = "$PREV" → "$NEXT" (the version
line under [project], not target-version).
- Update the lockfile: run
uv lock (regenerates uv.lock, touching only the
aperisolve version). If uv is unavailable/offline, edit only the
version line directly beneath name = "aperisolve" in uv.lock.
- Verify the diff is exactly those two files and just the version:
git diff --stat (expect pyproject.toml, uv.lock) and eyeball
git diff -- pyproject.toml uv.lock. If anything else changed, stop and report.
4. Confirm (mandatory gate — this deploys to prod)
Show the user, and get an explicit go-ahead before proceeding:
$PREV → $NEXT and how it was chosen,
- the commit subject
chore(release): $NEXT + $SUMMARY body,
- the tag message
Release $NEXT — $SUMMARY,
- the full
$NOTES release body,
- a reminder that pushing the tag builds the image and deploys production.
If the user asked to proceed non-interactively in this turn, that go-ahead counts.
5. Commit the bump
Use a heredoc so the body + trailer stay intact:
git commit -aF - <<'EOF'
chore(release): $NEXT
$SUMMARY
<session-required trailer lines>
EOF
- Append the trailer this harness session requires — the
Co-Authored-By:
and Claude-Session: lines from your current environment's git/commit
instructions. Read them from the live session; never hardcode them (the
session URL changes each session). If the session specifies none, omit them.
6. Push main
git push origin main.
- If rejected (non-fast-forward): stop, suggest
git pull --rebase origin main and re-run. Never force-push.
7. Tag and push the tag (⇒ triggers build + deploy)
git tag -a "$NEXT" -m "Release $NEXT — $SUMMARY".
git push origin "$NEXT".
8. Publish the GitHub Release
9. Report
- New commit sha + subject, the pushed tag, and the release URL
(
gh release view "$NEXT" --json url -q .url).
- Note that the tag push kicked off the Docker build + production deploy, and
point at Actions to watch it:
gh run list --workflow release.yml --limit 3.
Safety rules (non-negotiable)
- Confirm before step 5 — the tag push deploys production. No silent releases.
- Never blind-replace in
uv.lock — uv lock or the anchored aperisolve
line only.
- Release only from
main, with a clean, up-to-date tree.
$NEXT must be > $PREV and not an existing tag; tags are bare X.Y.Z.
- Never force-push; never delete or move an existing tag/branch/release.
- If any step fails, stop and report — don't improvise around a failure.
Example usage
/release # infer the bump from commits since the last tag, then confirm
/release patch # 3.3.2 -> 3.3.3
/release minor # 3.3.2 -> 3.4.0
/release 3.4.0 # explicit version