Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions .claude/skills/release/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
---
name: release
description: Cut an oxide-code release tag. Use whenever the user asks to release, tag, ship, or cut version X.Y.Z of oxide-code (e.g., "release v0.1.0", "let's tag 0.2.0-rc.1", "ship the release"). Wraps the canonical procedure in `RELEASING.md` with reminders about the common pitfalls (cliff `--prepend` vs `--output`, the manual compare-link footer). Use even if the user just says "release X.Y.Z" without further context.
description: Cut an oxide-code release tag. Use whenever the user asks to release, tag, ship, or cut version X.Y.Z of oxide-code (e.g., "release v0.1.0", "let's tag 0.2.0-rc.1", "ship the release"). Wraps the canonical procedure in `RELEASING.md` with reminders about the common pitfalls (local checks before pushing, cliff `--prepend` vs `--output`, the manual compare-link footer). Use even if the user just says "release X.Y.Z" without further context.
---

# Cut an oxide-code release

Read `RELEASING.md` at the repo root and execute the procedure step by step. It is the source of truth. Do not improvise around it.

The release lands through a PR, never a direct push to `main`: bump and changelog on a branch, CI green, review, merge, then tag the merged commit. Tagging a reviewed CI-green commit avoids amend-or-re-cut recovery when something turns out wrong.

## Reminders

These are the steps where mistakes are most likely:
These are the steps where mistakes are most likely, in procedure order:

- **Run the local checks before pushing.** `cargo fmt --all --check`, `cargo clippy --all-targets -- -D warnings`, `cargo test`, `pnpm lint`, and `pnpm spellcheck` are the same gates CI runs. Run them locally first so the PR does not bounce on something avoidable.
- **Spell-check catches changelog crate names.** `cspell` scans `CHANGELOG.md`, and the new section is derived verbatim from commit subjects. A dependency bump like `Bump idna ...` drops a crate name the dictionary does not know. Add the bare word to `.cspell/words.txt` in its case-insensitive alphabetical slot. Never reword the changelog to dodge it, since cliff must be able to regenerate the body.
- **Cliff invocation.** Use `git cliff --unreleased --tag vX.Y.Z --prepend CHANGELOG.md`. Never `--output`, since that resurfaces pre-release tags as phantom sections.
- **Compare-link footer.** `--prepend` does not regenerate the footer block. After cliff runs, add the new line by hand above the previous-version line. The very first tag has no compare base, so use the `releases/tag/<tag>` form instead.
- **Diff sanity check.** Only `Cargo.toml`, `Cargo.lock`, and `CHANGELOG.md` should change. Anything else means an unrelated edit slipped in.
- **Tag confirmation.** Pushing the tag triggers the GitHub release workflow and is hard to undo cleanly. Confirm with the user before `git push origin vX.Y.Z`, even if they already approved the version bump.
- **Compare-link footer.** `--prepend` does not regenerate the footer block. Add the new line by hand above the previous-version line. The very first tag has no compare base, so use the `releases/tag/<tag>` form instead.
- **PR diff sanity check.** Only `Cargo.toml`, `Cargo.lock`, `CHANGELOG.md`, and possibly `.cspell/words.txt` should change. Anything else means an unrelated edit slipped in.
- **Tag only after merge.** Wait for the PR to merge with CI green, then tag the merge commit. Pushing the tag triggers the release workflow and is hard to undo, so confirm with the user before `git push origin vX.Y.Z`.
- **Homebrew formula.** After the release workflow finishes uploading assets, run `./scripts/update-homebrew-formula.sh vX.Y.Z` on its own branch and open a second PR. The script fetches the `.sha256` sidecars, so it 404s if run before uploads complete.

After the tag is pushed, watch the workflow with `gh run watch` and report the resulting release URL.
60 changes: 39 additions & 21 deletions RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,15 @@ Any prose that should land in the changelog must come from a commit message: use

## Standard release

1. Bump version in `Cargo.toml` (`workspace.package.version`).
The release lands through a PR, never a direct push to `main`. The order is: open a PR with the version bump and changelog, let CI pass, review, merge, then tag the merge commit. Tagging a reviewed and CI-green commit removes the need to amend or re-cut when something turns out wrong.

2. Run `cargo build` to refresh `Cargo.lock`.
1. Branch off `main`: `git switch -c chore/release-vX.Y.Z`.

3. Prepend the new changelog section:
2. Bump version in `Cargo.toml` (`workspace.package.version`).

3. Run `cargo build` to refresh `Cargo.lock`.

4. Prepend the new changelog section:

```bash
git cliff --unreleased --tag vX.Y.Z --prepend CHANGELOG.md
Expand All @@ -22,36 +26,50 @@ Any prose that should land in the changelog must come from a commit message: use

Inspect the diff to confirm the new section reads well. If it does not, fix the underlying commits (rebase, amend, reword the squash commit subject) and regenerate. Never edit the body sections of `CHANGELOG.md` directly.

4. Add the compare-link footer line manually. `--prepend` does not touch the footer block, so insert this line above the previous-version line: `[X.Y.Z]: https://github.com/hakula139/oxide-code/compare/<prev-tag>..vX.Y.Z`. For the very first tag, use `https://github.com/hakula139/oxide-code/releases/tag/vX.Y.Z` instead (no compare base).
5. Add the compare-link footer line manually. `--prepend` does not touch the footer block, so insert this line above the previous-version line: `[X.Y.Z]: https://github.com/hakula139/oxide-code/compare/<prev-tag>..vX.Y.Z`. For the very first tag, use `https://github.com/hakula139/oxide-code/releases/tag/vX.Y.Z` instead (no compare base).

6. Run the full local check suite before pushing, so CI does not surface anything avoidable. This is the same set CI gates:

```bash
cargo fmt --all --check
cargo clippy --all-targets -- -D warnings
cargo test
pnpm lint
pnpm spellcheck
```

`cspell` scans `CHANGELOG.md`, and the new section is derived verbatim from commit subjects. A dependency bump such as `Bump idna ...` drops a crate name the dictionary does not know, which fails `pnpm spellcheck`. Add the bare word to `.cspell/words.txt` in its case-insensitive alphabetical slot. Never reword the changelog to dodge it: cliff must be able to regenerate the body.

7. Commit `chore(release): vX.Y.Z`, push the branch, and open a PR. Only `Cargo.toml`, `Cargo.lock`, `CHANGELOG.md`, and possibly `.cspell/words.txt` should be in the diff.

5. Commit: `chore(release): vX.Y.Z`.
8. Wait for CI to pass, fix anything it flags, then have the PR reviewed and merged. Do not tag until the PR is merged and green.

6. Tag and push:
9. Tag the merge commit and push the tag:

```bash
git switch main && git pull
git tag vX.Y.Z
git push origin main
git push origin vX.Y.Z
```

7. The workflow creates the GitHub Release, extracting the matching `[X.Y.Z]` section from `CHANGELOG.md` as release notes, and uploads:
10. The workflow creates the GitHub Release, extracting the matching `[X.Y.Z]` section from `CHANGELOG.md` as release notes, and uploads:

- `oxide-code-x86_64-unknown-linux-gnu.tar.gz` (+ `.sha256`)
- `oxide-code-aarch64-apple-darwin.tar.gz` (+ `.sha256`)
- `oxide-code-x86_64-apple-darwin.tar.gz` (+ `.sha256`)
- `oxide-code-x86_64-pc-windows-msvc.zip` (+ `.sha256`)
- `oxide-code-x86_64-unknown-linux-gnu.tar.gz` (+ `.sha256`)
- `oxide-code-aarch64-apple-darwin.tar.gz` (+ `.sha256`)
- `oxide-code-x86_64-apple-darwin.tar.gz` (+ `.sha256`)
- `oxide-code-x86_64-pc-windows-msvc.zip` (+ `.sha256`)

Each archive contains the `ox` binary.
Each archive contains the `ox` binary.

8. Refresh the Homebrew formula against the published artifacts and commit:
11. Refresh the Homebrew formula against the published artifacts. Run this after the workflow finishes uploading assets, otherwise the sidecar URLs return 404. The script regenerates `Formula/oxide-code.rb` by fetching each `.sha256` sidecar from the release, so it goes through its own PR:

```bash
./scripts/update-homebrew-formula.sh vX.Y.Z
git commit -am "chore(release): refresh Homebrew formula for vX.Y.Z"
git push
```
```bash
git switch -c chore/homebrew-vX.Y.Z
./scripts/update-homebrew-formula.sh vX.Y.Z
git commit -am "chore(release): refresh Homebrew formula for vX.Y.Z"
```

The script regenerates `Formula/oxide-code.rb` by fetching each `.sha256` sidecar from the release. Run it after the workflow finishes uploading assets, otherwise the sidecar URLs return 404.
Push and open a PR as in steps 7 and 8.

## Installing `git-cliff`

Expand All @@ -62,7 +80,7 @@ cargo install git-cliff # any platform with cargo

## Re-cutting an existing tag

If a release needs to be redone (e.g., bad assets):
The PR-based flow tags only reviewed, CI-green commits, so re-cutting should be rare. If a release still needs to be redone (e.g., bad assets), let any in-flight release run finish first so deletion does not race its uploads, then:

```bash
gh release delete vX.Y.Z --cleanup-tag --yes
Expand Down