inspector/.claude/skills/release/SKILL.md
cliffhall 73447c72d1 chore: extract AGENTS.md recipes into agent skills
Closes #2163

`AGENTS.md` was one 1,223-line contract loaded in full on every turn, whether
the task was a one-line CSS fix or a release cut. This splits it: rules stay
resident, procedures move to `.claude/skills/`.

- Ten skills under `.claude/skills/`, seven `disable-model-invocation: true`
  (invoked by name only, so they cost no listing context and have no trigger
  question) and three model-invoked with committed eval cases.
- `AGENTS.md` 1,223 → 380 lines: rules, a skills index, and a project tree
  reduced to one line per entry. The removed recipes are not duplicated.
- `README.md` 811 → 105 lines, split into `docs/{architecture,quality-gate,
  test-servers,publishing,docker}.md`, and dropped from `CLAUDE.md`'s auto-load.
- `.github/copilot-instructions.md` and its mirroring rule removed — Copilot
  reads `AGENTS.md`.
- `npm run verify:skills` (in `validate`, so in `local:gate` and CI) parses each
  SKILL.md the way Claude Code does, requires an explicit invocation mode and
  eval cases for model-invoked skills, and budgets the listing.
- `npm run skills:eval` runs the committed trigger cases headless.

Signed-off-by: cliffhall <cliff@futurescale.com>
2026-08-29 00:36:37 -04:00

4.8 KiB

name description disable-model-invocation
release Cut an Inspector v2 release — bump the version on v2/main first, merge the milestone into main, tag origin/main with a bare x.y.z, and publish via the GitHub Release. Also covers the v1 line and what the publish jobs gate on. true

Cutting a release

Background on what ships and why (the files allowlist, the bundling rules, the container image) is in docs/publishing.md. This skill is the procedure.

A v2 release is cut from main, after the milestone's work has been merged there from v2/main — not from v2/main itself. The v1 line releases independently from v1/main to the v1-latest tag and never touches main.

Publishing is automated by two release-gated jobs in .github/workflows/main.yml (github.event_name == 'release'), both needs: [build, coverage] — so a release cannot publish with either the build job or the coverage gate red:

  • publish — runs npm run pack:verify as the pre-publish gate, asserts the release tag matches the root package.json version, then npm publish --access public --provenance.
  • publish-github-container-registry — the GHCR image.

There is one version number (only the root package.json has one — the clients carry none), so the flow is three steps.

1. Bump on v2/main, before the milestone merge

The bump is part of the milestone's work, so it belongs on the develop branch and flows into main with everything else.

git checkout -b v2/chore/<ISSUE>-bump-2-3-0 v2/main
npm version minor --no-git-tag-version   # or major / patch; bump only, no tag
# PR → v2/main

⚠️ --no-git-tag-version is load-bearing. A bare npm version also tags, and the tag would land on a v2/main commit — but the release must be cut from main, so the tag has to point at the merge commit there (step 3). Tagging here creates a tag on a commit that is never released.

2. Merge v2/main → main

Through the usual milestone-merge branch. It now carries the bump, so the release lands on main with the version already correct.

Between steps 1 and 2 the two branches do differ, and that is expected, not drift: v2/main reads the version being built while main still reads the one currently released. What this ordering removes is post-release drift — once the milestone merge lands they agree again, and v2/main is never left behind main. If you see v2/main ahead of main, a release is in flight; if you see it behind, something went wrong.

3. Tag origin/main and draft the Release

git fetch origin main
git tag 2.3.0 origin/main && git push origin 2.3.0
# then draft & publish a GitHub Release for that tag → triggers `publish`

⚠️ Tag origin/main, not your local HEAD. git checkout main && git pull resolves through whatever merge-or-rebase strategy you have configured, so a divergent local main can quietly produce or replay local commits. Tagging HEAD there tags a commit that is not on origin/main, and git push origin <tag> pushes only the tag — leaving a release whose commit was never published.

⚠️ No v prefix. This repo's release tags are bare x.y.z — 2.2.0, 2.1.0, 2.0.0. npm's own tag-version-prefix defaults to v and the repo sets no .npmrc, so a bare npm version would have produced a mismatched tag; tagging by hand is what keeps it right. (The workflow's assert step strips a leading v before comparing, so a v-prefixed tag would still publish — it would just be inconsistent with every previous release.)

The release's target commit selects which workflow runs, so this only publishes when a release is cut from a commit carrying the v2 workflow.

Why the bump goes on v2/main first (#2010)

It used to happen on the milestone-merge branch, which is cut from main — so the bump existed only downstream of v2/main and nothing carried it back. v2/main sat at 2.0.0 through both the 2.1.0 and 2.2.0 releases. That is not cosmetic: a branch cut from a milestone-merge branch silently carries the bump into an unrelated PR (this happened on #2009, where a container bugfix arrived with a 2.0.0 → 2.2.0 diff), and anything reading the version in development reported a version two releases old.

⚠️ Never close a drift by merging main into v2/main. main carries the entire pre-v2 v1 history (~230 commits v2/main does not have), so a back-merge grafts all of it into the develop branch's log permanently in order to deliver a two-file change. Bumping first means there is nothing to back-merge.

The v1 line

Flat: feature branch → v1/main → npm v1-latest, with no merge into main at any point. The two lines publish independently under separate dist-tags, so a v1 fix does not need forward-porting to reach users on npx @modelcontextprotocol/inspector@v1-latest.