docs(agents): document the release workflow and link .claude/skills

Update the ascapi-release runbook for release-crates.yml: pushing the vX.Y.Z tag verifies the workspace, publishes the eight crates through crates.io trusted publishing, and dispatches the GitHub and MCP Registry releases. The manual publish steps become a fallback. Point .claude/skills at .agents/skills so Claude Code and the other agents read the same skills. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

Seto Elkahfi committed Oct 12, 2026 at 00:59 UTC 3bc688b9fb33393d58e2d984ae377050c7bbd910
2 files changed +88 -25
.agents/skills/ascapi-release/SKILL.md
+87 -25
@@ -3,23 +3,26 @@ name: ascapi-release
3 description: >
4 Cut a new release of the smbcloud-ascapi Rust workspace: merge development into
5 main, bump the single workspace version with cargo-workspaces, create the
6 - Release commit and matching tags, keep server.json in sync, publish the crates
7 - to crates.io, and run the MCP Registry workflow. Use whenever the user asks to
6 + Release commit and matching tags, keep server.json in sync, then push the tag
7 + so release-crates.yml publishes the crates to crates.io and dispatches the
8 + GitHub and MCP Registry releases. Use whenever the user asks to
9 release, cut a version, tag, bump the version, publish to crates.io, or publish
10 the MCP server for this repo. Covers the forge/remote quirks (GitHub, remote
11 named "setoelkahfi"), the signing identity, and the registry workflow's
12 hard invariants that will fail a release if violated.
13 metadata:
14 author: smbcloudXYZ
14 - version: "1.0"
15 + version: "1.1"
16 ---
17
18 # smbcloud-ascapi release runbook
19
20 One Rust workspace, one version. The root `Cargo.toml` `[workspace.package]`
21 `version` (around line 15) is the single source of truth — every crate inherits
21 -it with `version.workspace = true`. A release bumps that one number, tags it,
22 -publishes the crates, and republishes the MCP server.
22 +it with `version.workspace = true`. A release bumps that one number and tags
23 +it. Pushing the `vX.Y.Z` tag starts `.github/workflows/release-crates.yml`,
24 +which publishes the crates and then dispatches the GitHub release and the MCP
25 +Registry release.
26
27 ## Facts that are easy to get wrong
28
@@ -125,32 +128,66 @@ grep -n '"version"' server.json # both -> X.Y.Z
128 sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml | head -1 # -> X.Y.Z
129 git status --short # clean
130 cargo check --workspace --locked
131 +```
132 +
133 +Push the branch first, then the tags. The tag push is what starts the
134 +publish, and the workflow checks out the tag, so `main` has to be on the
135 +remote before it runs:
136 +
137 +```sh
138 git push setoelkahfi main
139 git push setoelkahfi --tags --force # --force only if you moved tags in step 3
140 ```
141
132 -## Step 5 — publish crates to crates.io
133 -
134 -Publish in dependency order (lower crates first), because the MCP Registry
135 -workflow verifies the **published** CLI crate, and the CLI depends on the rest:
142 +## Step 5 — `release-crates.yml` publishes the crates
143 +
144 +Pushing a `vX.Y.Z` tag starts `.github/workflows/release-crates.yml`. The
145 +per-crate `smbcloud-ascapi-*@X.Y.Z` tags don't match its `v*.*.*` filter, so
146 +they don't start extra runs. Three jobs run in order:
147 +
148 +1. **`verify`** fails the run if the tag differs from the workspace version in
149 + `Cargo.toml`, then runs `cargo fmt --check`, clippy with `-D warnings`,
150 + `cargo test --workspace --locked`, and `cargo check` on `core`, `aso`,
151 + `pricing`, `signing` and `reports` on their own.
152 +2. **`publish`** (environment `crates-io`) publishes `core`, `aso`, `pricing`,
153 + `signing`, `reports`, `frontend`, `mcp`, `cli`, in that order, with
154 + `cargo publish -p <crate> --locked`. Authentication is crates.io trusted
155 + publishing: the job exchanges its GitHub OIDC token through
156 + `rust-lang/crates-io-auth-action`, so there is no registry token secret.
157 + A crate version that is already on crates.io is skipped, so re-running the
158 + workflow after a partial failure resumes where it stopped.
159 +3. **`dispatch-distributions`** dispatches `release-github.yml` (six binaries
160 + attached to a GitHub release) and `release-mcp-registry.yml`, both with the
161 + same tag. It only runs when `publish` succeeded.
162 +
163 +Watch it with:
164
165 +```sh
166 +gh run list --workflow release-crates.yml --limit 3
167 +gh run watch <run-id>
168 ```
138 -core → aso / pricing / signing / reports → frontend → mcp → cli
139 -```
169 +
170 +To run it by hand for an existing tag (for example after fixing a trusted
171 +publisher config), dispatch it against that tag:
172
173 ```sh
142 -cargo workspaces publish --from-git --allow-branch main --yes
174 +gh workflow run release-crates.yml --ref vX.Y.Z -f tag=vX.Y.Z
175 ```
176
145 -`--from-git` publishes exactly what the tags describe and skips versions already
146 -on crates.io (safe to re-run). If you publish manually with `cargo publish -p`,
147 -respect the order above and wait for each to index before the next.
177 +### One-time setup it depends on
178
149 -## Step 6 — publish the MCP server
179 +- Each of the eight crates has a trusted publisher on crates.io for repository
180 + `smbcloudXYZ/smbcloud-ascapi` and workflow `release-crates.yml`. If a crate
181 + is missing one, `cargo publish` fails for that crate and the later crates are
182 + not published. Add the publisher and re-run.
183 +- If a trusted publisher names an environment, it must be `crates-io`, which
184 + is the environment the `publish` job runs in.
185
151 -Dispatch `.github/workflows/release-mcp-registry.yml` with the tag (e.g.
152 -`vX.Y.Z`). It authenticates to the MCP Registry via GitHub OIDC (no secret) and
153 -enforces these invariants — a release fails if any is violated:
186 +## Step 6 — MCP Registry release
187 +
188 +`release-mcp-registry.yml` is dispatched by step 5, so there is normally
189 +nothing to do here. It authenticates to the MCP Registry via GitHub OIDC (no
190 +secret) and enforces these invariants. A release fails if any is violated:
191
192 1. **Version match:** root `Cargo.toml` workspace version == tag (minus the `v`).
193 Step 2 guarantees this.
@@ -160,22 +197,47 @@ enforces these invariants — a release fails if any is violated:
197 3. **Ownership marker in the published crate:** the CLI crate downloaded from
198 crates.io for this exact version must contain the same marker in its README.
199 crates.io versions are immutable, so **the marker must be in the README
163 - BEFORE you publish the crate in step 5** — if a published version lacks it,
164 - you cannot fix that version; you must cut a new release. This is why step 5
165 - precedes step 6.
200 + BEFORE you tag**. If a published version lacks it, you cannot fix that
201 + version; you must cut a new release.
202
203 The workflow rewrites `server.json`'s version from the tag, publishes with
204 `mcp-publisher` (treating "duplicate version" as success), and verifies the
169 -server is listed in the registry.
205 +server is listed in the registry. It waits up to ten minutes for crates.io to
206 +index the CLI crate, so it is safe that it starts while step 5 is still
207 +publishing.
208 +
209 +To publish the server again for a tag, dispatch it yourself:
210 +
211 +```sh
212 +gh workflow run release-mcp-registry.yml --ref vX.Y.Z -f tag=vX.Y.Z
213 +```
214 +
215 +## Manual fallback
216 +
217 +Only if the workflow can't run (Actions outage, trusted publishing not set up
218 +yet). Publish in dependency order, because the CLI depends on the rest:
219 +
220 +```sh
221 +cargo workspaces publish --from-git --allow-branch main --yes
222 +```
223 +
224 +`--from-git` publishes exactly what the tags describe and skips versions
225 +already on crates.io. Then dispatch `release-github.yml` and
226 +`release-mcp-registry.yml` yourself with the tag.
227
228 ## Release-order summary
229
230 merge → bump+tag → sync server.json into Release commit → verify sig & versions
174 -→ push main+tags → **publish crates (step 5)** → **then** run MCP Registry
175 -workflow (step 6). Steps 5 and 6 are order-dependent; don't swap them.
231 +→ push `main`, then tags → `release-crates.yml` verifies and publishes → it
232 +dispatches the GitHub release and the MCP Registry release. Check the run in
233 +Actions; nothing after the tag push is manual.
234
235 ## References
236
237 +- `../../../.github/workflows/release-crates.yml` — verify, publish and
238 + dispatch, in full.
239 +- `../../../.github/workflows/release-github.yml` — the binary build and GitHub
240 + release.
241 - `../../../.github/workflows/release-mcp-registry.yml` — the registry workflow
242 and its invariant checks, in full.
243 - Past release design notes live in `.agents/plans/` (upload / signing-identity
.claude/skills new
+1
@@ -0,0 +1 @@
1 +../.agents/skills
\ No newline at end of file