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
149 changes: 149 additions & 0 deletions .agents/skills/release/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
---
name: release
description: Use whenever cutting, preparing, tagging, or publishing an onde-cli release — version bumps, release branch, merges, tagging, pushing to trigger the publish pipeline, and verifying it landed. Also use when a release went wrong (wrong tag target, failed crates.io verify, development left behind main, unsigned release commits) and the repo state needs repair. Trigger even if the user only says "release vX.Y.Z", "bump the version", "tag a release", or "publish the new version".
user-invocable: true
---

# Release skill

End-to-end release procedure for `onde-cli`. Complements the git skill (merge rules)
and the distribution skill (channel details) — this skill is the concrete runbook.

## Decide the version

- Look at `git tag -l 'v*' --sort=-v:refname | head -1` for the latest release.
- Look at the commits since (`git log v<latest>..development --oneline`).
Fixes and chores only → patch bump. New user-facing features → minor bump.
- Check for an abandoned `release/vX.Y.Z` branch — its version is usually the
intended next one. If it exists, inspect it (`git log development..release/...`)
before deciding to reuse or delete it; do not blindly `branch -D` without looking,
and never reuse a branch whose commits were made unsigned or by the wrong identity.

## Version bump surfaces

These files pin the crate version and must all move together:

- `Cargo.toml` — `version = "X.Y.Z"` in `[package]`
- `pub/onde_cli/pubspec.yaml` — `version: X.Y.Z`
- `server.json` — three occurrences: top-level `version`, and the npm and NuGet
`packages[].version` entries (search for the old version string to catch all)

`docs/mcp-registry.md` and `.github/workflows/release-mcp-registry.yml` mention
past versions only as historical examples — do not bump those.

The npm wrapper manifests are NOT bump targets: the committed `package.json` files
are rewritten at release time by `npm/scripts/render-*.cjs` from the tag.

After editing, run `cargo check --locked` (or let the build update `Cargo.lock`)
so the lockfile carries the new version, and commit it in the same bump commit.

## Sequence

Follow the git skill's branch flow — `release/vX.Y.Z → development → main`,
`--no-ff` at every hop, tag on the final `main` merge commit. Concretely:

```bash
git checkout development
git pull <remote> development # start from the pushed tip
git checkout -b release/vX.Y.Z
# ... edit the bump surfaces ...
git commit -am "Prepare vX.Y.Z" # signed, correct identity (see below)
git checkout development
git merge --no-ff release/vX.Y.Z -m "Merge release prep for vX.Y.Z"
git checkout main
git pull <remote> main
git merge --no-ff development -m "Merge development for vX.Y.Z"
git tag vX.Y.Z
git push <remote> development main vX.Y.Z
git branch -d release/vX.Y.Z # only after the merge lands
```

The remote here is the one whose URL is the sigit.si or GitHub org repo —
check `git remote -v`; this repo is commonly cloned with per-account remotes
(`gh-setoelkahfi`, `gh-paydii`, `gh-keypair34`) rather than `origin`.

### What a good result looks like

- The tag sits on the step-3 merge commit on `main` (`git log --graph main`
shows a merge commit directly above the previous release).
- `git log development..main` and `git log main..development` are both empty
after the merges — the two branches carry identical trees.
- Never tag a release-branch tip or a bare bump commit on `main`; if the work
somehow already landed on `main` linearly, follow the git skill's fallback
(normal commit, tag it) rather than rewriting history.

### Post-push back-merge check

If anything reached `main` without passing through `development` first (hotfix,
manual push, a fast-forwarded release), bring it back immediately:

```bash
git checkout development
git merge --no-ff main -m "Merge main for vX.Y.Z"
git push <remote> development
```

A release is not done until the two branches contain the same tree.

## Commits and signatures — read the git-commit-signing skill first

Release commits must be GPG-signed by the maintainer identity that GitHub can
verify (this repo: `Seto Elkahfi <1797197+setoelkahfi@users.noreply.github.com>`,
EDDSA key `2B76DF0F04014E36`). The `gh` CLI's active account is independent of
git config — set both, and verify before pushing:

```bash
git log -1 --format='%an <%ae> sig:%G? %GK' # want sig:G and the right key
gh api repos/<owner>/<repo>/commits/<sha> --jq .commit.verification.verified
```

Every `git commit` and `git merge` in this flow creates a commit — configure
signing so merges are signed too, or amend unsigned ones before pushing
(`git commit --amend --no-edit -S`, only on unpushed commits).

Commit trailer: end release-related commit messages with

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

## The tag push is the release

Pushing `vX.Y.Z` triggers `release-crates.yml`, which verifies the tag against
`Cargo.toml` and `pubspec.yaml`, publishes the crate, then dispatches the
wrapper workflows (GitHub Releases, npm, NuGet, PyPI, then Homebrew and pub.dev
after the GitHub Release assets exist). There is no separate "publish" step.

Watch it land:

```bash
gh run list --branch vX.Y.Z
gh run watch <run-id> --exit-status
```

`CI` also runs on the tag push; both should be green. If `release-crates` fails,
no wrapper publishes — fix the cause, move the tag only if it has not published
anything yet (crates.io versions are immutable; a failed verify is safe to retry,
a published crate is not).

## Verify

After the pipeline finishes, spot-check the fan-out (all idempotent, safe to re-check):

- crates.io: `cargo search onde-cli --limit 1`
- GitHub Release: `gh release view vX.Y.Z`
- npm: `npm view onde-cli version`
- PyPI: `https://pypi.org/pypi/onde-cli/json` → `info.version`
- NuGet: flat container index for `onde-cli`
- Homebrew/pub.dev trail the GitHub Release; they may take a few extra minutes

## Failure modes seen before

- Fast-forwarding `main` to the bump commit and tagging it — leaves no merge
commit, and leaves `development` behind. Back-merge `main` into `development`
right away and prefer merge commits next time.
- Deleting a leftover `release/` branch that contained unsigned bump commits and
redoing the bump fresh — fine, but check it first.
- Forgetting `Cargo.lock` in the bump commit.
- Pushing with the wrong `gh` account active — the push works, but PR/issue
actions afterwards get attributed to the wrong identity.
35 changes: 35 additions & 0 deletions .agents/skills/release/evals/evals.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"skill_name": "release",
"evals": [
{
"id": 1,
"prompt": "Cut release v0.4.3 for onde-cli. Bump all version pins, follow the repo's release branch flow, tag, and push.",
"expected_output": "Agent bumps Cargo.toml, pubspec.yaml, server.json (3 spots), commits signed with Co-Authored-By trailer, creates release/v0.4.3 from development, merges --no-ff into development then main, tags the main merge commit, pushes development+main+tag, and watches the release pipeline.",
"files": []
},
{
"id": 2,
"prompt": "We fast-forwarded main to the version bump commit by mistake and tagged it. development is now behind main. Fix the repo state without rewriting pushed history.",
"expected_output": "Agent recognizes the git skill's rule (tag belongs on a merge commit; development must carry the release state) and back-merges main into development with --no-ff instead of rewriting main or deleting the pushed tag.",
"files": []
},
{
"id": 3,
"prompt": "The v0.4.2 tag push failed the crates.io verify step because pubspec.yaml still said 0.4.1. Nothing was published. Walk me through fixing it and re-releasing.",
"expected_output": "Agent fixes the pubspec version on a new release branch or hotfix, re-runs the release flow, deletes and recreates the tag only if crates.io never published, and explains why crates.io versions are immutable so a published 0.4.2 would require 0.4.3 instead.",
"files": []
},
{
"id": 4,
"prompt": "Update the README install instructions for the npm wrapper and fix the NuGet README badge that points at the wrong package id.",
"expected_output": "Should NOT deeply engage the release skill beyond reading it; this is distribution/README work. Agent edits npm/onde-cli/README.md and nuget/onde-cli/README.md without cutting a release, bumping versions, or tagging.",
"files": []
},
{
"id": 5,
"prompt": "Merge the feature/lora-scheduler branch into development.",
"expected_output": "Should NOT trigger the release skill; this is a normal feature merge governed by the git skill (--no-ff into development, no tag).",
"files": []
}
]
}
7 changes: 7 additions & 0 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Link the MSVC C runtime statically on Windows. Without this the binary
# imports VCRUNTIME140.dll and the UCRT api-ms-win-crt-*.dll set, and on a
# machine without the Visual C++ Redistributable it dies at load time with
# STATUS_DLL_NOT_FOUND (0xC0000135) before main runs. winget ships onde as a
# portable exe, so there is no installer to bring the runtime along.
[target.'cfg(all(windows, target_env = "msvc"))']
rustflags = ["-C", "target-feature=+crt-static"]
1 change: 1 addition & 0 deletions .claude/skills
7 changes: 7 additions & 0 deletions .github/workflows/release-github.yml
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,13 @@ jobs:
shell: bash
run: cargo build --locked --release --target ${{ matrix.target }}

# The exe must not import the VC++ runtime DLLs (see .cargo/config.toml), or it fails to
# load on a machine without the redistributable. Hostx64 dumpbin reads arm64 PE files too.
- name: Check static CRT
if: matrix.runner == 'windows-latest'
shell: pwsh
run: ./packaging/windows/check-static-crt.ps1 -Path target/${{ matrix.target }}/release/onde.exe

- name: Package binary
shell: bash
run: |
Expand Down
Loading
Loading