|
1 | 1 | --- |
2 | 2 | name: release |
3 | | -description: Create a new Perry release — commit all changes, push, bump version, tag, create GitHub release, build and install locally |
| 3 | +description: Create a new Perry release — tag the current HEAD (version already in Cargo.toml / CLAUDE.md from prior commits), push the tag, create a GitHub release, and watch the gated release-packages workflow |
4 | 4 | disable-model-invocation: true |
5 | | -argument-hint: [description of changes] |
| 5 | +argument-hint: [optional: "minor" to request a minor bump instead of just tagging HEAD, or free-text release highlights] |
6 | 6 | allowed-tools: Bash, Read, Edit, Write, Glob, Grep |
7 | 7 | --- |
8 | 8 |
|
9 | 9 | # New Perry Release |
10 | 10 |
|
11 | | -Create a new Perry release with all current uncommitted changes. |
| 11 | +## Model (important — read before acting) |
| 12 | + |
| 13 | +Perry's day-to-day workflow already bumps the patch version on **every** commit that lands on `main`. The CLAUDE.md workflow rule is explicit: each on-main commit touches `[workspace.package] version` in `Cargo.toml` and prepends a `Recent Changes` entry in `CLAUDE.md`. That means by the time `/release` runs, the current HEAD already knows what version it is. |
| 14 | + |
| 15 | +`/release` therefore does **not**: |
| 16 | +- bump the version (it's already bumped) |
| 17 | +- commit anything (the work is already committed, usually across many commits) |
| 18 | +- write a changelog entry (already in CLAUDE.md) |
| 19 | + |
| 20 | +What `/release` **does**: |
| 21 | +1. Reads the current version off HEAD |
| 22 | +2. Tags HEAD with `vX.Y.Z` and pushes the tag |
| 23 | +3. Creates a GitHub Release (notes assembled from the commits since the previous tag) |
| 24 | +4. Watches `release-packages.yml` — which now gates on `Tests` + `Simulator Tests (iOS)` — and reports when publish succeeds or fails |
| 25 | + |
| 26 | +There will typically be tens of commits between releases. That's normal; releases are a distribution event, not a commit event. |
12 | 27 |
|
13 | 28 | ## Steps |
14 | 29 |
|
15 | | -1. **Survey changes**: Run `git status` and `git diff --stat` to understand all modified and new files. Read the diffs to understand what changed. |
| 30 | +### 1. Sanity checks |
| 31 | + |
| 32 | +- `git status` — **must be clean**. If there are uncommitted changes, STOP and report. Those changes belong in regular commits (with version bumps + CLAUDE.md entries), not folded silently into a release. |
| 33 | +- `git rev-parse --abbrev-ref HEAD` — must be `main`. If not, STOP. |
| 34 | +- `git fetch origin && git log HEAD..origin/main --oneline` — must be empty. If origin is ahead, pull/resolve first. |
| 35 | + |
| 36 | +### 2. Read the current version |
| 37 | + |
| 38 | +Grep `Cargo.toml` for `^version = "` under `[workspace.package]`. This is the version to tag. Do **not** bump it here — the `$ARGUMENTS` "minor" handling is only for the edge case where the most recent on-main commit forgot to bump, which is rare. |
| 39 | + |
| 40 | +Verify: `perry --version` (if installed locally) should match, and the `**Current Version:**` line in CLAUDE.md should match. If the three sources disagree, STOP and report — something is off in the history. |
| 41 | + |
| 42 | +### 3. Verify the tag doesn't exist |
| 43 | + |
| 44 | +```bash |
| 45 | +git rev-parse "vX.Y.Z" 2>/dev/null && echo "tag exists — aborting" && exit 1 |
| 46 | +``` |
| 47 | + |
| 48 | +If the tag already exists locally or on origin, STOP. Either the release already shipped, or someone started it and didn't finish; either way, don't silently duplicate. |
| 49 | + |
| 50 | +### 4. Survey commits since the last tag |
| 51 | + |
| 52 | +```bash |
| 53 | +last_tag=$(git describe --tags --abbrev=0) |
| 54 | +git log "$last_tag"..HEAD --oneline |
| 55 | +``` |
| 56 | + |
| 57 | +There may be 20–60 commits. Read their subjects, mentally group by `fix:` / `feat:` / `docs:` / `chore:`. The GitHub Release body will summarize them. Don't paste the raw commit list — the release notes should be a *summary*. |
| 58 | + |
| 59 | +### 5. Tag + push |
| 60 | + |
| 61 | +```bash |
| 62 | +git tag "vX.Y.Z" |
| 63 | +git push origin "vX.Y.Z" |
| 64 | +``` |
| 65 | + |
| 66 | +This tag push fires three workflows in parallel (see `.github/workflows/`): |
| 67 | +- `test.yml` — runs the full PR test matrix on the tag SHA |
| 68 | +- `simctl-tests.yml` — tier-2 iOS simulator doc-example verification |
| 69 | +- `release-packages.yml` — waits for the two above via its `await-tests` gate job, then builds binaries and publishes brew / apt / npm / GitHub release assets |
| 70 | + |
| 71 | +### 6. Create the GitHub Release |
| 72 | + |
| 73 | +```bash |
| 74 | +gh release create "vX.Y.Z" \ |
| 75 | + --title "vX.Y.Z" \ |
| 76 | + --notes "$(cat <<'EOF' |
| 77 | +## Highlights |
| 78 | +- ... |
| 79 | +
|
| 80 | +## Fixes |
| 81 | +- ... |
| 82 | +
|
| 83 | +## Features |
| 84 | +- ... |
| 85 | +
|
| 86 | +## Infrastructure |
| 87 | +- ... |
| 88 | +EOF |
| 89 | +)" |
| 90 | +``` |
16 | 91 |
|
17 | | -2. **Determine version**: Read `CLAUDE.md` for the current version. Bump the patch version (e.g., 0.4.6 → 0.4.7). If `$ARGUMENTS` mentions "minor" bump the minor version instead. |
| 92 | +Group the commits since the last tag by type. Keep each bullet to one line. Link to issue numbers where the commit message mentions them. If `$ARGUMENTS` was provided, use it as the "Highlights" seed. |
18 | 93 |
|
19 | | -3. **Update version in Cargo.toml**: Edit `Cargo.toml` at the workspace level — the version is under `[workspace.package]`. |
| 94 | +**Note**: `release-packages.yml` also triggers on `release: published`. Since we already pushed the tag in step 5, both the tag-push and release-published paths converge — `concurrency` at the workflow level deduplicates. |
20 | 95 |
|
21 | | -4. **Update CLAUDE.md**: |
22 | | - - Update `**Current Version:**` to the new version |
23 | | - - Add a new `### vX.Y.Z` section under `## Recent Changes` (above existing entries) with 1-2 line summaries of each change |
| 96 | +### 7. Watch the gated publish |
24 | 97 |
|
25 | | -5. **Stage and commit**: Stage all changed and new files by name (never use `git add -A`). Commit with message format: |
26 | | - ``` |
27 | | - <type>: <concise summary> (vX.Y.Z) |
| 98 | +```bash |
| 99 | +gh run watch $(gh run list --workflow="Release Packages" --limit 1 --json databaseId --jq '.[0].databaseId') |
| 100 | +``` |
28 | 101 |
|
29 | | - - bullet points for each change |
30 | | - ``` |
31 | | - Where `<type>` is `fix`, `feat`, or `fix` + `feat` combined as appropriate. |
| 102 | +Expected timeline on the tag push: |
| 103 | +- `Tests` finishes in ~12 min |
| 104 | +- `Simulator Tests (iOS)` finishes in ~15 min |
| 105 | +- `release-packages` `await-tests` unblocks when both are green, then build+publish runs ~20 min |
32 | 106 |
|
33 | | -6. **Push**: `git push origin main` |
| 107 | +If `await-tests` shows a failed gate workflow in its logs, the release is blocked. Investigate the failed run, fix on main, cut the next patch. Do **not** re-tag vX.Y.Z — bump to vX.Y.(Z+1) and tag that. Retagging a published tag is a cardinal sin. |
34 | 108 |
|
35 | | -7. **Tag**: Create and push tag `vX.Y.Z`: |
36 | | - ``` |
37 | | - git tag vX.Y.Z && git push origin vX.Y.Z |
38 | | - ``` |
| 109 | +### 8. Verify locally (optional but recommended) |
39 | 110 |
|
40 | | -8. **GitHub release**: Create via `gh release create` with a body containing: |
41 | | - - `## Bug Fixes` section (if any fixes) |
42 | | - - `## Features` section (if any features) |
43 | | - - `## Tests` section (if new tests added) |
44 | | - Each with bullet points describing the changes. |
| 111 | +After the release is published: |
| 112 | +```bash |
| 113 | +cargo install --path crates/perry --force |
| 114 | +perry --version # should print vX.Y.Z |
| 115 | +``` |
45 | 116 |
|
46 | | -9. **Build all crates**: `cargo build --release` (timeout 10 minutes) |
| 117 | +Report back: |
| 118 | +- GitHub release URL |
| 119 | +- Whether `release-packages.yml` was green end-to-end |
| 120 | +- Local `perry --version` confirming the new build |
47 | 121 |
|
48 | | -10. **Install perry binary**: `cargo install --path crates/perry --force` (timeout 10 minutes) |
| 122 | +## Failure modes |
49 | 123 |
|
50 | | -11. **Verify**: Run `perry --version` and confirm it shows the new version. |
| 124 | +- **Dirty worktree**: STOP. Don't commit; hand control back to the user. |
| 125 | +- **Tag already exists**: STOP. Investigate before deciding whether to bump further or resume a partial release. |
| 126 | +- **Gate workflow failed (Tests or Simulator Tests)**: the tag is public but the release can't publish. Fix on main, push a new patch commit (with its own version bump + CLAUDE.md entry), then `/release` again. The stale vX.Y.Z tag is harmless — no assets, no GH release body; some git log noise. |
| 127 | +- **`release-packages.yml` build leg failed**: similar — investigate, patch forward, never retag. |
51 | 128 |
|
52 | | -## Important |
| 129 | +## What NOT to do |
53 | 130 |
|
54 | | -- Always read diffs before writing the commit message — understand what changed |
55 | | -- Never skip the build+install step — the release isn't done until perry is locally updated |
56 | | -- If the build fails, fix the issue before proceeding with the release |
57 | | -- Report the GitHub release URL and final `perry --version` output when done |
| 131 | +- Do not create commits during the release. If CLAUDE.md needs an entry, it needed it at commit time; it's too late now. |
| 132 | +- Do not rewrite or amend the version-bump commit. Just tag HEAD as-is. |
| 133 | +- Do not use `git add -A` anywhere in this skill — a dirty worktree is a STOP condition, not a thing to sweep into a release. |
| 134 | +- Do not force-push tags. |
0 commit comments