Merge pull request #2299 from modelcontextprotocol/v2/docs/2298-release-skill-two-pr-flow
docs(release): make the two-PR flow, the production smoke, the ledger and the UI tag explicit
This commit is contained in:
commit
770fe44a5b
1 changed files with 110 additions and 15 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
name: release
|
name: release
|
||||||
description: Cut an Inspector v2 release — run npm audit and 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.
|
description: "Cut an Inspector v2 release — two PRs and then a GitHub Release. PR 1 puts the npm audit, any fixes it forces, and the version bump on v2/main; PR 2 merges v2/main into main and is smoke-tested from the production build with a ledger artifact for the maintainers; the maintainer then tags and publishes through the GitHub UI. Also covers the v1 line and what the publish jobs gate on."
|
||||||
disable-model-invocation: true
|
disable-model-invocation: true
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -24,14 +24,35 @@ job or the coverage gate red:
|
||||||
--access public --provenance`.
|
--access public --provenance`.
|
||||||
- **`publish-github-container-registry`** — the GHCR image.
|
- **`publish-github-container-registry`** — the GHCR image.
|
||||||
|
|
||||||
|
## The shape: two PRs, then the Release
|
||||||
|
|
||||||
There is **one version number** (only the root `package.json` has one — the
|
There is **one version number** (only the root `package.json` has one — the
|
||||||
clients carry none), so the flow is three steps.
|
clients carry none), and the release moves through **two pull requests** in
|
||||||
|
order. They are not interchangeable and neither one's content belongs on the
|
||||||
|
other.
|
||||||
|
|
||||||
## 1. `npm audit`, then bump, on `v2/main` — before the milestone merge
|
| | PR 1 — prep | PR 2 — the milestone merge |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| Branch | `v2/chore/<ISSUE>-bump-<X-Y-Z>`, cut from `origin/v2/main` | the milestone-merge branch, cut from `origin/main` |
|
||||||
|
| Base | **`v2/main`** | **`main`** |
|
||||||
|
| Carries | the `npm audit` report, **any fixes the audit forces**, and the **version bump** — all three, one PR | the milestone's work, arriving whole from `v2/main`. **No commits of its own.** |
|
||||||
|
| Verified by | `npm run local:gate` | `npm run local:gate` **plus** a hand-driven smoke of every contribution in the milestone, from the **production build**, written up as a **ledger artifact** |
|
||||||
|
| Merged when | reviewed and green | the ledger is reviewed by the maintainers and clean |
|
||||||
|
|
||||||
Both are part of the milestone's work, so both belong on the develop branch and
|
Then, and only then, a maintainer tags and publishes the **GitHub Release**
|
||||||
flow into `main` together, in the same PR — audit first, so the bump sits on top
|
(step 3), which is what triggers the publish jobs.
|
||||||
of a tree you have just checked.
|
|
||||||
|
⚠️ **Do not fold the two together.** The bump must exist on `v2/main` before the
|
||||||
|
merge (see [Why the bump goes on `v2/main` first](#why-the-bump-goes-on-v2main-first-2010)),
|
||||||
|
and PR 2 must stay a pure merge — a commit authored on the merge branch is a
|
||||||
|
change that exists downstream of `v2/main` and nothing carries it back.
|
||||||
|
|
||||||
|
## 1. PR 1 — audit, audit fixes and the bump, on `v2/main`
|
||||||
|
|
||||||
|
All three are part of the milestone's work, so all three belong on the develop
|
||||||
|
branch and flow into `main` together, **in the same PR** — audit first, so the
|
||||||
|
bump sits on top of a tree you have just checked, and so a reviewer sees the
|
||||||
|
report and the fixes it forced as one change.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# Branch from the REMOTE ref, and read the version only once you are on it.
|
# Branch from the REMOTE ref, and read the version only once you are on it.
|
||||||
|
|
@ -92,7 +113,11 @@ and the tag would land on a `v2/main` commit — but the release must be cut fro
|
||||||
`main`, so the tag has to point at the merge commit there (step 3). Tagging here
|
`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.
|
creates a tag on a commit that is never released.
|
||||||
|
|
||||||
## 2. Merge `v2/main` → `main`
|
**PR 1 merges before PR 2 is opened.** The merge branch is cut from `main` and
|
||||||
|
takes `v2/main` whole, so opening it early means merging a `v2/main` that does
|
||||||
|
not yet carry the bump.
|
||||||
|
|
||||||
|
## 2. PR 2 — merge `v2/main` → `main`, smoke-test it, and write the ledger
|
||||||
|
|
||||||
Through the usual milestone-merge branch. It now carries the bump, so the
|
Through the usual milestone-merge branch. It now carries the bump, so the
|
||||||
release lands on `main` with the version already correct.
|
release lands on `main` with the version already correct.
|
||||||
|
|
@ -104,10 +129,78 @@ 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
|
`main`. If you see `v2/main` ahead of `main`, a release is in flight; if you see
|
||||||
it behind, something went wrong.
|
it behind, something went wrong.
|
||||||
|
|
||||||
## 3. Tag `origin/main` and draft the Release
|
### 2a. Smoke-test the release candidate from the production build
|
||||||
|
|
||||||
Derive the tag from the version that just landed, rather than typing one — a
|
The merge branch's tree **is** the release candidate. Check that rather than
|
||||||
hard-coded tag is either already taken (so `git tag` aborts) or, worse, wrong:
|
assume it — the merge commit's tree and `origin/v2/main`'s must be identical:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git rev-parse origin/v2/main^{tree}
|
||||||
|
git rev-parse <merge-commit>^{tree} # must print the same hash
|
||||||
|
```
|
||||||
|
|
||||||
|
Then drive it. Work from a **dedicated worktree** with its own full
|
||||||
|
`npm install` (a symlinked `node_modules` passes lint and tests and then fails
|
||||||
|
every story file), run `npm run local:gate` there, and exercise the app from the
|
||||||
|
**production build** — the packaged bin and the built bundles, not `vite dev`.
|
||||||
|
The `local-dev`, `test-servers` and `pre-push-gate` skills cover the mechanics;
|
||||||
|
`pack:verify` inside the gate is what proves the tarball a consumer installs
|
||||||
|
actually resolves.
|
||||||
|
|
||||||
|
**Every contribution closed in the milestone gets driven, not read.** The bar is
|
||||||
|
observed behavior from the running app — a rendered panel, a status attribute, a
|
||||||
|
server's own stderr — against a real test server, through whichever clients the
|
||||||
|
change touches (web, CLI, TUI). "Its tests pass" is not evidence for this step;
|
||||||
|
the gate already said that. For a change with no observable surface, the
|
||||||
|
evidence is the thing that holds it — a probe that makes the guard fire, a
|
||||||
|
counted before/after, a resolved binary path.
|
||||||
|
|
||||||
|
### 2b. The ledger artifact
|
||||||
|
|
||||||
|
Write the results up as a **published artifact** for the maintainers to review,
|
||||||
|
and link it from PR 2. Shape it like the
|
||||||
|
[v2.5.0 ledger](https://claude.ai/code/artifact/6f25d292-3623-419f-af7f-26aba57247ef):
|
||||||
|
|
||||||
|
- **Masthead** — repo, PR number and merge commit, version, date; and a
|
||||||
|
standfirst saying what tree was tested and that its hash matches
|
||||||
|
`origin/v2/main`, plus whether the milestone payload is complete (the only
|
||||||
|
issue left open should be the merge itself).
|
||||||
|
- **Verdict band** — `local:gate` result, milestone issues verified as `N / N`,
|
||||||
|
distinct test count, regressions found.
|
||||||
|
- **The automated gate** — one cell per stage with its number (file counts,
|
||||||
|
test counts, smoke count, `pack:verify` size), and a note on what is new this
|
||||||
|
milestone.
|
||||||
|
- **One section per theme**, each a table of *Issue · What was driven ·
|
||||||
|
Observed · Status*. One row per closed issue, issue-linked, with the actual
|
||||||
|
output in the Observed cell.
|
||||||
|
- **Notes / findings** — anything that is a caveat rather than a pass, called
|
||||||
|
out rather than folded into a row.
|
||||||
|
|
||||||
|
A row that says "verified" without saying what was run is not a ledger entry.
|
||||||
|
|
||||||
|
### 2c. When the smoke finds something
|
||||||
|
|
||||||
|
**The fix goes on `v2/main`, never on the merge branch.** File the issue, fix it
|
||||||
|
through an ordinary PR against `v2/main`, then merge `v2/main` into the merge
|
||||||
|
branch again so the fix arrives the same way everything else did. That keeps the
|
||||||
|
merge tree byte-identical to `origin/v2/main` — which is both the invariant
|
||||||
|
checked in 2a and the reason a finding here does not create a commit that only
|
||||||
|
exists downstream (#2000 → #2092; #2215 → #2216–2224).
|
||||||
|
|
||||||
|
Re-run the affected part of the smoke afterwards and update the ledger; it is
|
||||||
|
the artifact the maintainers approve the merge on.
|
||||||
|
|
||||||
|
## 3. Tag and publish the Release
|
||||||
|
|
||||||
|
**Normally this is done by a maintainer through the GitHub UI**, after PR 2 has
|
||||||
|
merged: *Releases → Draft a new release → Choose a tag → type the bare `x.y.z`
|
||||||
|
→ Create new tag on publish*, with **Target: `main`**, then generate the notes
|
||||||
|
and publish. Publishing the Release is what fires the `publish` and
|
||||||
|
`publish-github-container-registry` jobs.
|
||||||
|
|
||||||
|
The equivalent by hand, for when the UI is not an option — derive the tag from
|
||||||
|
the version that just landed rather than typing one, since a hard-coded tag is
|
||||||
|
either already taken (so `git tag` aborts) or, worse, wrong:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
git fetch origin main
|
git fetch origin main
|
||||||
|
|
@ -122,13 +215,15 @@ resolves through whatever merge-or-rebase strategy you have configured, so a
|
||||||
divergent local `main` can quietly produce or replay local commits. Tagging
|
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
|
`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.
|
<tag>` pushes only the tag — leaving a release whose commit was never published.
|
||||||
|
The UI path avoids this by construction: the target is `main` itself.
|
||||||
|
|
||||||
⚠️ **No `v` prefix.** This repo's release tags are bare `x.y.z` — which is why
|
⚠️ **No `v` prefix.** This repo's release tags are bare `x.y.z` — which is why
|
||||||
the command above tags `$VERSION` and not `v$VERSION`. npm's own `tag-version-prefix` defaults to `v` and the repo
|
the command above tags `$VERSION` and not `v$VERSION`, and why the tag typed
|
||||||
sets no `.npmrc`, so a bare `npm version` would have produced a mismatched tag;
|
into the UI carries no prefix either. npm's own `tag-version-prefix` defaults to
|
||||||
tagging by hand is what keeps it right. (The workflow's assert step strips a
|
`v` and the repo sets no `.npmrc`, so a bare `npm version` would have produced a
|
||||||
leading `v` before comparing, so a `v`-prefixed tag would still publish — it
|
mismatched tag; tagging by hand is what keeps it right. (The workflow's assert
|
||||||
would just be inconsistent with every previous release.)
|
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
|
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.
|
when a release is cut from a commit carrying the v2 workflow.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue