A commy release is driveable end-to-end except one step: the PR merge, which is the maintainer's. A worker prepares a release-bump PR; the maintainer merges it; CI does everything outward-facing (tag, npm publish, GitHub Release) from there. No agent ever pushes a tag or publishes anything.
This is path B2: the merge is the single human trigger. The post-merge work
is fully automated in .github/workflows/release.yml,
so there is no maintainer-local "release-plugin" skill to depend on.
WORKER (this runbook) MAINTAINER CI (release.yml)
───────────────────── ────── ────────────────
1. decide the bump (semver)
2. edit the 8 lockstep version sites
3. write RELEASE-NOTES/<version>.md
4. open PR, drive `bun run check` green ──► merge PR ──► detect release
verify 8-site parity
push commy-v<version> tag
publish npm (OIDC)
cut GitHub Release
Your deliverable is the PR. The merge fires the rest.
Sweep merged PRs since the last release tag:
git fetch origin --tags
last=$(git describe --tags --match 'commy-v*' --exclude '*-rc.*' --abbrev=0)
git log --oneline "${last}..origin/main"The --exclude skips release-candidate tags, so a full release sweeps
everything since the last full release rather than since its own rc.
Pick MAJOR.MINOR.PATCH per semver against what landed (behaviour change →
minor; fix only → patch; breaking → major). The previous version is ${last#commy-v}.
To soak a build before its full release, cut it as MAJOR.MINOR.PATCH-rc.N,
numbered for the full release it leads to: 0.24.0-rc.1 comes before 0.24.0.
It goes through every step below unchanged. CI publishes it to npm under
next, not latest, and marks its GitHub Release as a prerelease. Pin a
marketplace to its commy-v<version> tag to run it.
rc.N is the only prerelease shape the release path accepts. pyproject.toml
and uv.lock both record it in PEP 440 form, as 0.24.0rc1, and the lockstep
test compares them that way.
All eight must read the new version. Six are hand-edited; the other two
(clients/hermes/pyproject.toml and clients/hermes/uv.lock) are set together
by uv version, never hand-edited.
-
clients/claude-code/.claude-plugin/plugin.json— canonical; the tag, the npm artifact, and the detection logic all derive from this one. -
clients/claude-code/package.json -
packages/mcp/package.json -
packages/mcp/mcp-server.ts— thePLUGIN_VERSIONexport -
clients/hermes/commy/plugin.yaml -
clients/claude-code/.mcp.json— the@codeforbreakfast/commy-mcp@<version>launcher pin, so the plugin and the server it starts move as one artefact -
clients/hermes/pyproject.toml -
clients/hermes/uv.lock— set together withpyproject.tomlby runningcd clients/hermes && uv version <version>
passing the PEP 440 form for a release candidate, e.g.
uv version 0.24.0rc1. This writespyproject.tomland re-locksuv.lockin one step. Never hand-edit either — the hermes gate runsuv syncand a stale lock fails CI.uvlives in the flake dev shell (nix develop).
All eight are asserted in lockstep by
clients/claude-code/manifests.test.ts.
It checks the pyproject.toml version and the uv.lock self-entry against the
PEP 440 form of the canonical version, so a forgotten or hand-edited bump fails
at the unit-test bar. A partial bump cannot land green.
Then run bun install to carry the two workspace versions into bun.lock, and
commit the lockfile with the bump. Nothing fails if you forget, so nothing will
remind you.
Create RELEASE-NOTES/<version>.md — see
RELEASE-NOTES/README.md for the convention and
template. This file is required: its presence is how CI decides the merge is
a release, and it is the body of the GitHub Release. Write it user-facing and
impact-classified, matching PRs #90
and #93. Write for the
people running the plugin and server, not for commy's own developers: one
plain sentence per thing that now works or behaves differently for them, and
anything they must do. Leave out a dependency bump unless it changes
something for a user, such as a security fix. No internals, file names, or
PR-by-PR detail.
git checkout -b release-<version>
git add -A
git commit -m "commy plugin: release <version>"Drive the gate green inside the dev shell (the hermes gate needs uv):
nix develop --command bun run checkOpen the PR against main. A green bun run check is the merge gate.
That's the whole worker deliverable. Stop here — do not push a tag or publish anything. The merge is the maintainer's, and it triggers everything else.
When the bump commit lands on main, release.yml:
- detect — reads the canonical version, confirms
RELEASE-NOTES/<version>.mdexists andcommy-v<version>is not already tagged. Logic is inscripts/release-detection.ts, unit-tested inscripts/release-detection.test.ts. An ordinary main push (no notes file, or already tagged) is a no-op. - verify parity — re-runs the eight-site lockstep test on the merged commit.
- tag — creates and pushes
commy-v<version>as the record. - publish — builds and publishes
@codeforbreakfast/commy-mcpto npm via OIDC trusted publishing (no token). - release — cuts the GitHub Release from
RELEASE-NOTES/<version>.md.
release.yml also has a workflow_dispatch trigger (Actions → Release → Run
workflow). It runs the same detection. Use it if the auto-trigger on merge does
not fire, or — with the force input — to recover a release that tagged but
failed before publishing. The notes file must still be present.
- The npm publish step stays in
release.yml. npm's Trusted Publisher (OIDC) is pinned to the workflow filename and to thenpm-publishGitHub environment thereleasejob declares. Renaming or moving the workflow, or renaming the environment, makes npm reject the publish — that needs a maintainer-side npm-config change. If a rename ever seems unavoidable, stop and surface it. The environment's deployment rule admits onlymain. It is repo configuration, not a file here, and it refuses a manual dispatch from any other branch. - CI triggers on the merge-to-
main, not on the tag it pushes. A tag pushed by the defaultGITHUB_TOKENdoes not trigger anotheron: push: tagsworkflow, so the release must be driven directly off the release commit. Do not refactor this into a two-workflow "auto-tag fires the release" design — it silently will not fire.