Skip to content

Latest commit

 

History

History
164 lines (128 loc) · 7.39 KB

releasing.md

File metadata and controls

164 lines (128 loc) · 7.39 KB

Releasing commy

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.

The two halves

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.

Worker steps

1. Decide the bump

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}.

A release candidate

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.

2. Bump the 8 lockstep version sites

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.

  1. clients/claude-code/.claude-plugin/plugin.json — canonical; the tag, the npm artifact, and the detection logic all derive from this one.

  2. clients/claude-code/package.json

  3. packages/mcp/package.json

  4. packages/mcp/mcp-server.ts — the PLUGIN_VERSION export

  5. clients/hermes/commy/plugin.yaml

  6. clients/claude-code/.mcp.json — the @codeforbreakfast/commy-mcp@<version> launcher pin, so the plugin and the server it starts move as one artefact

  7. clients/hermes/pyproject.toml

  8. clients/hermes/uv.lock — set together with pyproject.toml by running

    cd clients/hermes && uv version <version>

    passing the PEP 440 form for a release candidate, e.g. uv version 0.24.0rc1. This writes pyproject.toml and re-locks uv.lock in one step. Never hand-edit either — the hermes gate runs uv sync and a stale lock fails CI. uv lives 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.

3. Write the release notes

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.

4. Open the PR and go green

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 check

Open 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.

What CI does on merge (no action needed)

When the bump commit lands on main, release.yml:

  1. detect — reads the canonical version, confirms RELEASE-NOTES/<version>.md exists and commy-v<version> is not already tagged. Logic is in scripts/release-detection.ts, unit-tested in scripts/release-detection.test.ts. An ordinary main push (no notes file, or already tagged) is a no-op.
  2. verify parity — re-runs the eight-site lockstep test on the merged commit.
  3. tag — creates and pushes commy-v<version> as the record.
  4. publish — builds and publishes @codeforbreakfast/commy-mcp to npm via OIDC trusted publishing (no token).
  5. release — cuts the GitHub Release from RELEASE-NOTES/<version>.md.

Manual fallback

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.

Two constraints that must not be tripped

  • The npm publish step stays in release.yml. npm's Trusted Publisher (OIDC) is pinned to the workflow filename and to the npm-publish GitHub environment the release job 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 only main. 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 default GITHUB_TOKEN does not trigger another on: push: tags workflow, 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.