@splitfireai / agent / commits / 7ef4f8d

ci: publish crate to crates.io on v* tags

Add release-crates.yml, which runs `cargo publish --locked` when a v* tag is pushed, using the CARGO_REGISTRY_TOKEN secret. Also add the release skill documenting the version-bump checklist, the fmt/clippy/test gate, fast-forwarding main, tagging, and verifying the published GitHub release and crate. Co-Authored-By: SplitFire v1.1.1-acp <noreply@splitfire.ai>

Seto committed Oct 13, 2026 at 13:45 UTC 7ef4f8dc8594d31e23c2f2e7aa781a27c3de0503
2 files changed +148
.agents/skills/release/SKILL.md new
+130
@@ -0,0 +1,130 @@
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 (needs the `CARGO_REGISTRY_TOKEN` repo secret).
18 +
19 +Branch model: work lands on `development` (via PRs); `main` is the release branch;
20 +every tag points at a commit that is on `main`. Previous tags were plain annotated
21 +(not GPG-signed) tags authored by the maintainer, message `Release vX.Y.Z`.
22 +
23 +---
24 +
25 +## 1. Confirm the version is bumped
26 +
27 +The version bump often already landed with the feature PR — check before assuming
28 +you need a bump commit. These three must agree and must be the version you're
29 +releasing:
30 +
31 +- `Cargo.toml` (`version = "X.Y.Z"`)
32 +- `Cargo.lock` (the `splitfire-agent` package entry)
33 +- `registry/agent.json` (`"version": "X.Y.Z"`)
34 +
35 +```sh
36 +grep -m1 '^version' Cargo.toml
37 +grep -A2 '^name = "splitfire-agent"' Cargo.lock | grep version
38 +grep '"version"' registry/agent.json
39 +```
40 +
41 +If a bump is needed, edit all three, run `cargo build` to refresh `Cargo.lock`, and
42 +commit on `development` (or the release branch) before continuing. `registry/` is the
43 +ACP registry submission; keeping its `version` in sync with `Cargo.toml` is part of
44 +the release, per AGENTS.md.
45 +
46 +## 2. Run the gate
47 +
48 +CI runs exactly these three; run them locally and get them green before tagging. Use
49 +`cargo` directly — this is a Rust crate, the global `rv` rule is Ruby-only.
50 +
51 +```sh
52 +cargo fmt -- --check
53 +cargo clippy --locked --all-targets -- -D warnings
54 +cargo test --locked
55 +```
56 +
57 +## 3. Get the release commit onto main
58 +
59 +`main` must fast-forward to the commit you're releasing (no merge commit, matching
60 +history). Verify there is nothing on `main` that isn't on your source branch:
61 +
62 +```sh
63 +git log --oneline main..origin/main # expect empty (main not ahead)
64 +git log --oneline origin/main..development # the commits you're about to release
65 +```
66 +
67 +Fast-forward `main` to the release commit (works without checking it out), then push:
68 +
69 +```sh
70 +git branch -f main development
71 +git push origin main
72 +```
73 +
74 +## 4. Tag and push
75 +
76 +Confirm your git identity and gh account are the maintainer's before tagging — the
77 +`git-commit-signing` skill governs identity selection. Match the previous tag's
78 +style: plain annotated, message `Release vX.Y.Z`. Pushing the tag is what fires the
79 +release workflow, so push it last.
80 +
81 +```sh
82 +git tag -a vX.Y.Z -m "Release vX.Y.Z"
83 +git push origin vX.Y.Z
84 +```
85 +
86 +## 5. Verify the published release
87 +
88 +The workflow takes ~8-9 minutes (five-target matrix). Watch it, then confirm all
89 +five assets are attached:
90 +
91 +```sh
92 +gh run list --workflow=release.yml --limit 1
93 +gh run view <run-id> --json status,conclusion,jobs \
94 + --jq '{status,conclusion,jobs:[.jobs[]|{name,conclusion}]}'
95 +gh release view vX.Y.Z
96 +```
97 +
98 +A green release has five assets:
99 +`splitfire-agent-{darwin-arm64,darwin-x64,linux-arm64,linux-x64}.tar.gz` and
100 +`splitfire-agent-windows-x64.zip`.
101 +
102 +Also confirm the crate published:
103 +
104 +```sh
105 +gh run list --workflow=release-crates.yml --limit 1
106 +curl -s https://crates.io/api/v1/crates/splitfire-agent | python3 -Ic \
107 + 'import sys,json;print(json.load(sys.stdin)["crate"]["max_version"])'
108 +```
109 +
110 +---
111 +
112 +## Notes
113 +
114 +- A release is outward-facing and hard to reverse (public binaries, cached/indexed).
115 + The user asking to "release vX.Y.Z" authorizes it; still report faithfully if a
116 + gate step fails rather than tagging anyway.
117 +- The GitHub release is created by `github-actions[bot]`, not the tagger. That's
118 + expected — the workflow uses `GITHUB_TOKEN`.
119 +- Release notes are auto-generated from merged PRs since the previous tag
120 + (`--generate-notes`). There is no hand-written CHANGELOG file in this repo.
121 +- crates.io is immutable and append-only: a version can be published exactly once,
122 + then only *yanked* (hidden from new resolves, existing lockfiles keep working) —
123 + never replaced or deleted. So the tag's `Cargo.toml` version must not already be on
124 + crates.io; any re-release needs a fresh version bump. The `ed-*` dependencies are
125 + already on crates.io, so no dependency needs publishing first. To publish manually
126 + instead of via CI: `cargo login` once (token from crates.io/settings/tokens), then
127 + `cargo publish --locked` from the tagged commit.
128 +- If a platform build fails after the tag is pushed, fix the cause and re-run the
129 + failed jobs (`gh run rerun <run-id> --failed`); the release-create step tolerates
130 + an already-existing release and `--clobber`s assets, so re-runs are safe.
.github/workflows/release-crates.yml new
+18
@@ -0,0 +1,18 @@
1 +name: release-crates
2 +
3 +on:
4 + push:
5 + tags: ["v*"]
6 +
7 +jobs:
8 + publish:
9 + runs-on: ubuntu-latest
10 + steps:
11 + - uses: actions/checkout@v5
12 + - uses: dtolnay/rust-toolchain@stable
13 + # Publishes the version in Cargo.toml to crates.io. crates.io is immutable:
14 + # a version can only be published once (then yanked, never replaced), so the
15 + # tag's Cargo.toml version must not already be on crates.io.
16 + - run: cargo publish --locked
17 + env:
18 + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}