Add ascapi-release skill documenting the release runbook

Co-Authored-By: siGit Code v1.6.7-acp <noreply@sigit.si>

Seto Elkahfi committed Oct 11, 2026 at 20:01 UTC de82e1e16d5ffb9a31146893f841528bcba00a52
1 file changed +182
.agents/skills/ascapi-release/SKILL.md new
+182
@@ -0,0 +1,182 @@
1 +---
2 +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
8 + release, cut a version, tag, bump the version, publish to crates.io, or publish
9 + the MCP server for this repo. Covers the forge/remote quirks (GitHub, remote
10 + named "setoelkahfi"), the signing identity, and the registry workflow's
11 + hard invariants that will fail a release if violated.
12 +metadata:
13 + author: smbcloudXYZ
14 + version: "1.0"
15 +---
16 +
17 +# smbcloud-ascapi release runbook
18 +
19 +One Rust workspace, one version. The root `Cargo.toml` `[workspace.package]`
20 +`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.
23 +
24 +## Facts that are easy to get wrong
25 +
26 +- **Forge is GitHub** (`github.com/smbcloudXYZ`). The `mcp__sigit__*` tools do
27 + NOT apply here. Use `git` / `gh` via the shell for any forge work.
28 +- **The remote is named `setoelkahfi`, not `origin`.** Push with
29 + `git push setoelkahfi main` and `git push setoelkahfi --tags`. Confirm with
30 + `git remote -v` before pushing.
31 +- **Signing identity** (confirmed, and already the repo's local git config):
32 + - `user.email = 1797197+setoelkahfi@users.noreply.github.com`
33 + - `user.signingkey = 2B76DF0F04014E36`
34 + - A signed Release commit shows `sig=G key=2B76DF0F04014E36` under
35 + `git log -1 --format='sig=%G? key=%GK'`. If it doesn't, stop and fix the
36 + identity before tagging — do not release unsigned or under the wrong key.
37 + (The `seto@smbcloud.xyz` / `B5690EEE…` key is a different, non-release
38 + identity — don't use it here.)
39 +- **Release tool is `cargo-workspaces`** (binary `cargo workspaces`). Install
40 + with `cargo install cargo-workspaces` if missing.
41 +
42 +## Step 1 — merge `development` into `main`
43 +
44 +```sh
45 +git checkout main
46 +git pull setoelkahfi main
47 +git merge development
48 +```
49 +
50 +The only conflict you're likely to hit is `Cargo.lock`, and it's almost always
51 +just package-entry **ordering** (new crates shift lines around), not a real
52 +dependency divergence. Resolve by taking development's lockfile, then prove it:
53 +
54 +```sh
55 +git checkout development -- Cargo.lock # if the conflict is only ordering
56 +git add Cargo.lock
57 +git commit # finish the merge
58 +cargo check --workspace --locked # MUST pass — this validates Cargo.lock
59 +```
60 +
61 +If `--locked` fails, the lock really did diverge; regenerate with
62 +`cargo check --workspace` and commit the result instead.
63 +
64 +## Step 2 — bump the version and tag
65 +
66 +Pick `patch` / `minor` / `major`. This repo keeps the Release commit body as the
67 +plain cargo-workspaces default (`Release X.Y.Z` subject, per-crate lines, and a
68 +`Generated by cargo-workspaces` footer) — don't hand-edit it.
69 +
70 +```sh
71 +cargo workspaces version minor \
72 + --yes --allow-branch main --no-git-push --force '*'
73 +```
74 +
75 +- `--allow-branch main` because releases go out on `main`.
76 +- `--no-git-push` so you control the push (to the `setoelkahfi` remote).
77 +- `--force '*'` bumps every crate to the shared version even if cargo-workspaces
78 + thinks some are unchanged (single-version workspace).
79 +
80 +This creates the `Release X.Y.Z` commit, the `vX.Y.Z` tag, and **8 per-crate
81 +tags** `smbcloud-ascapi-{aso,cli,core,frontend,mcp,pricing,reports,signing}@X.Y.Z`,
82 +all pointing at the Release commit.
83 +
84 +## Step 3 — sync `server.json`
85 +
86 +`server.json` is NOT touched by cargo-workspaces. The MCP Registry workflow
87 +rewrites it from the tag at publish time, but keep the repo honest by bumping it
88 +in the Release commit anyway. Two version fields:
89 +
90 +- top-level `.version`
91 +- `.packages[0].version`
92 +
93 +Edit both to the new version, then fold them into the Release commit and move
94 +every tag to the new commit:
95 +
96 +```sh
97 +git add server.json
98 +git commit --amend --no-edit
99 +NEW=$(git rev-parse HEAD)
100 +for t in \
101 + smbcloud-ascapi-aso@X.Y.Z smbcloud-ascapi-cli@X.Y.Z \
102 + smbcloud-ascapi-core@X.Y.Z smbcloud-ascapi-frontend@X.Y.Z \
103 + smbcloud-ascapi-mcp@X.Y.Z smbcloud-ascapi-pricing@X.Y.Z \
104 + smbcloud-ascapi-reports@X.Y.Z smbcloud-ascapi-signing@X.Y.Z \
105 + vX.Y.Z; do
106 + git tag -f "$t" "$NEW"
107 +done
108 +```
109 +
110 +Beware: any tooling that **amends a co-author trailer** after your commit
111 +changes the hash again. After all amends settle, re-point the tags to the final
112 +HEAD and verify they all land on one commit:
113 +
114 +```sh
115 +git tag --points-at HEAD # must list all 9 tags
116 +git log -1 --format='sig=%G? key=%GK' # must be sig=G key=2B76DF0F04014E36
117 +```
118 +
119 +## Step 4 — final consistency check, then push
120 +
121 +All three must read the same version, and the locked build must pass:
122 +
123 +```sh
124 +grep -n '"version"' server.json # both -> X.Y.Z
125 +sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml | head -1 # -> X.Y.Z
126 +git status --short # clean
127 +cargo check --workspace --locked
128 +git push setoelkahfi main
129 +git push setoelkahfi --tags --force # --force only if you moved tags in step 3
130 +```
131 +
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:
136 +
137 +```
138 +core → aso / pricing / signing / reports → frontend → mcp → cli
139 +```
140 +
141 +```sh
142 +cargo workspaces publish --from-git --allow-branch main --yes
143 +```
144 +
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.
148 +
149 +## Step 6 — publish the MCP server
150 +
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:
154 +
155 +1. **Version match:** root `Cargo.toml` workspace version == tag (minus the `v`).
156 + Step 2 guarantees this.
157 +2. **Ownership marker in source:** `mcp-name: io.github.smbcloudXYZ/ascapi` must
158 + be present in BOTH `README.md` and `crates/cli/README.md`. (The server name
159 + comes from `server.json` `.name`.)
160 +3. **Ownership marker in the published crate:** the CLI crate downloaded from
161 + crates.io for this exact version must contain the same marker in its README.
162 + 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.
166 +
167 +The workflow rewrites `server.json`'s version from the tag, publishes with
168 +`mcp-publisher` (treating "duplicate version" as success), and verifies the
169 +server is listed in the registry.
170 +
171 +## Release-order summary
172 +
173 +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.
176 +
177 +## References
178 +
179 +- `../../../.github/workflows/release-mcp-registry.yml` — the registry workflow
180 + and its invariant checks, in full.
181 +- Past release design notes live in `.agents/plans/` (upload / signing-identity
182 + behaviour) — check there before reworking release mechanics.