| 1 | --- |
| 2 | name: release |
| 3 | description: Cut a tagged release of the splitfire-agent crate. Covers the version-bump checklist, the gate (fmt/clippy/test), fast-forwarding main, the annotated tag that fires the cross-platform release and crates.io workflows, and verifying the published GitHub release and crate. Use whenever the user asks to release, cut, tag, or publish a new splitfire-agent version. |
| 4 | allowed-tools: Read, Write, Edit, Glob, Grep, Bash |
| 5 | user-invocable: true |
| 6 | --- |
| 7 | |
| 8 | # Release |
| 9 | |
| 10 | How to cut a `splitfire-agent` release. Releases are driven by git tags. Pushing a |
| 11 | `v*` tag fires two workflows: |
| 12 | |
| 13 | - `.github/workflows/release.yml` — builds the binary on five targets |
| 14 | (darwin-arm64/x64, linux-x64/arm64, windows-x64) and publishes a GitHub release |
| 15 | with auto-generated notes and the archives attached. |
| 16 | - `.github/workflows/release-crates.yml` — runs `cargo publish --locked` to push |
| 17 | the crate to crates.io, authenticated by **trusted publishing**: a GitHub OIDC |
| 18 | token is exchanged for a short-lived crates.io token. There is no |
| 19 | `CARGO_REGISTRY_TOKEN` secret to store or rotate. |
| 20 | |
| 21 | Branch model: work lands on `development` (via PRs); `main` is the release branch; |
| 22 | every tag points at a commit that is on `main`. Previous tags were plain annotated |
| 23 | (not GPG-signed) tags authored by the maintainer, message `Release vX.Y.Z`. |
| 24 | |
| 25 | --- |
| 26 | |
| 27 | ## 0. One-time setup: the trusted publisher |
| 28 | |
| 29 | Trusted publishing needs a publisher registered on crates.io **before** the first |
| 30 | tagged run. The crate must already be on crates.io (a first-ever publish still |
| 31 | needs a manual `cargo login` + `cargo publish`), and you must be a crate owner. |
| 32 | Then: crate page → **Settings → Trusted Publishing → Add → GitHub**: |
| 33 | |
| 34 | - Repository owner: `SplitFireAI` |
| 35 | - Repository name: `splitfire-agent` |
| 36 | - Workflow filename: `release-crates.yml` (the bare filename, not a path) |
| 37 | - Environment: leave blank unless the job also declares `environment:` |
| 38 | |
| 39 | The workflow filename is part of the trust claim, so renaming the workflow breaks |
| 40 | publishing until the crates.io config is updated to match — keep the two in sync. |
| 41 | |
| 42 | --- |
| 43 | |
| 44 | ## 1. Confirm the version is bumped |
| 45 | |
| 46 | The version bump often already landed with the feature PR — check before assuming |
| 47 | you need a bump commit. These three must agree and must be the version you're |
| 48 | releasing: |
| 49 | |
| 50 | - `Cargo.toml` (`version = "X.Y.Z"`) |
| 51 | - `Cargo.lock` (the `splitfire-agent` package entry) |
| 52 | - `registry/agent.json` (`"version": "X.Y.Z"`) |
| 53 | |
| 54 | ```sh |
| 55 | grep -m1 '^version' Cargo.toml |
| 56 | grep -A2 '^name = "splitfire-agent"' Cargo.lock | grep version |
| 57 | grep '"version"' registry/agent.json |
| 58 | ``` |
| 59 | |
| 60 | If a bump is needed, edit all three, run `cargo build` to refresh `Cargo.lock`, and |
| 61 | commit on `development` (or the release branch) before continuing. `registry/` is the |
| 62 | ACP registry submission; keeping its `version` in sync with `Cargo.toml` is part of |
| 63 | the release, per AGENTS.md. |
| 64 | |
| 65 | ## 2. Run the gate |
| 66 | |
| 67 | CI runs exactly these three; run them locally and get them green before tagging. Use |
| 68 | `cargo` directly — this is a Rust crate, the global `rv` rule is Ruby-only. |
| 69 | |
| 70 | ```sh |
| 71 | cargo fmt -- --check |
| 72 | cargo clippy --locked --all-targets -- -D warnings |
| 73 | cargo test --locked |
| 74 | ``` |
| 75 | |
| 76 | ## 3. Get the release commit onto main |
| 77 | |
| 78 | `main` must fast-forward to the commit you're releasing (no merge commit, matching |
| 79 | history). Verify there is nothing on `main` that isn't on your source branch: |
| 80 | |
| 81 | ```sh |
| 82 | git log --oneline main..origin/main # expect empty (main not ahead) |
| 83 | git log --oneline origin/main..development # the commits you're about to release |
| 84 | ``` |
| 85 | |
| 86 | Fast-forward `main` to the release commit (works without checking it out), then push: |
| 87 | |
| 88 | ```sh |
| 89 | git branch -f main development |
| 90 | git push origin main |
| 91 | ``` |
| 92 | |
| 93 | ## 4. Tag and push |
| 94 | |
| 95 | Confirm your git identity and gh account are the maintainer's before tagging — the |
| 96 | `git-commit-signing` skill governs identity selection. Match the previous tag's |
| 97 | style: plain annotated, message `Release vX.Y.Z`. Pushing the tag is what fires the |
| 98 | release workflow, so push it last. |
| 99 | |
| 100 | ```sh |
| 101 | git tag -a vX.Y.Z -m "Release vX.Y.Z" |
| 102 | git push origin vX.Y.Z |
| 103 | ``` |
| 104 | |
| 105 | ## 5. Verify the published release |
| 106 | |
| 107 | The workflow takes ~8-9 minutes (five-target matrix). Watch it, then confirm all |
| 108 | five assets are attached: |
| 109 | |
| 110 | ```sh |
| 111 | gh run list --workflow=release.yml --limit 1 |
| 112 | gh run view <run-id> --json status,conclusion,jobs \ |
| 113 | --jq '{status,conclusion,jobs:[.jobs[]|{name,conclusion}]}' |
| 114 | gh release view vX.Y.Z |
| 115 | ``` |
| 116 | |
| 117 | A green release has five assets: |
| 118 | `splitfire-agent-{darwin-arm64,darwin-x64,linux-arm64,linux-x64}.tar.gz` and |
| 119 | `splitfire-agent-windows-x64.zip`. |
| 120 | |
| 121 | Also confirm the crate published: |
| 122 | |
| 123 | ```sh |
| 124 | gh run list --workflow=release-crates.yml --limit 1 |
| 125 | curl -s https://crates.io/api/v1/crates/splitfire-agent | python3 -Ic \ |
| 126 | 'import sys,json;print(json.load(sys.stdin)["crate"]["max_version"])' |
| 127 | ``` |
| 128 | |
| 129 | --- |
| 130 | |
| 131 | ## Notes |
| 132 | |
| 133 | - A release is outward-facing and hard to reverse (public binaries, cached/indexed). |
| 134 | The user asking to "release vX.Y.Z" authorizes it; still report faithfully if a |
| 135 | gate step fails rather than tagging anyway. |
| 136 | - The GitHub release is created by `github-actions[bot]`, not the tagger. That's |
| 137 | expected — the workflow uses `GITHUB_TOKEN`. |
| 138 | - Release notes are auto-generated from merged PRs since the previous tag |
| 139 | (`--generate-notes`). There is no hand-written CHANGELOG file in this repo. |
| 140 | - crates.io is immutable and append-only: a version can be published exactly once, |
| 141 | then only *yanked* (hidden from new resolves, existing lockfiles keep working) — |
| 142 | never replaced or deleted. So the tag's `Cargo.toml` version must not already be on |
| 143 | crates.io; any re-release needs a fresh version bump. The `ed-*` dependencies are |
| 144 | already on crates.io, so no dependency needs publishing first. To publish manually |
| 145 | instead of via CI: `cargo login` once (token from crates.io/settings/tokens), then |
| 146 | `cargo publish --locked` from the tagged commit. |
| 147 | - `crates-io-auth-action` is pinned to `v1.0.5` deliberately: the action repo has |
| 148 | no moving `v1` tag (only `v1.0.x`), so the `@v1` shown in the crates.io docs |
| 149 | example fails to resolve. Bump the pin when a newer release lands. |
| 150 | - If a platform build fails after the tag is pushed, fix the cause and re-run the |
| 151 | failed jobs (`gh run rerun <run-id> --failed`); the release-create step tolerates |
| 152 | an already-existing release and `--clobber`s assets, so re-runs are safe. |