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>
180 lines
7.5 KiB
Markdown
180 lines
7.5 KiB
Markdown
---
|
|
name: pr-flow
|
|
description: Take an issue through to a merged PR — branch naming, the DCO signoff, screenshots, opening the PR against the right base, requesting a Copilot review, responding to review rounds, and closing out (manual close plus Done).
|
|
disable-model-invocation: true
|
|
---
|
|
|
|
# PR flow
|
|
|
|
Rules this procedure enforces live in [`AGENTS.md`](../../../AGENTS.md)
|
|
(Issue-driven Work Style, Contributing). Board mechanics are in `/board-ops`;
|
|
the gate itself is `/pre-push-gate`.
|
|
|
|
**Pull requests against this repo are opened by the repo maintainers only.**
|
|
Having write access is not authorization to open one — anyone else files a
|
|
detailed issue and a maintainer takes it from there.
|
|
|
|
## 1. Start from an issue
|
|
|
|
**Every PR references an issue. No exceptions, regardless of who opens it.** A
|
|
PR with no linked issue has no board card, so the work is invisible to the
|
|
project board and untracked. If there's no issue yet, create one first with
|
|
`/issue-create` — don't open the PR and backfill.
|
|
|
|
Move the issue's card to **In Progress** (`/board-ops`).
|
|
|
|
## 2. Branch
|
|
|
|
**Branch names start with the target version segment** — the first path segment
|
|
is the version whose base branch the PR targets, then the type, then a slug:
|
|
|
|
```
|
|
v2/fix/2071-oauth-resource-metadata
|
|
v2/chore/2146-rename-local-gate
|
|
v1/fix/proxy-ssrf-pin
|
|
```
|
|
|
|
Not `fix/oauth-resource-metadata`. This keeps the two lines legible in
|
|
`git branch -a` and in the PR list once v1 and v2 branches coexist on the same
|
|
remote, and it matches the base branches themselves (`v2/main`, `v1/main`).
|
|
|
|
**Cut the branch from `v2/main`**, never from a milestone-merge branch — the
|
|
latter carries release-only commits that will show up in your PR's diff.
|
|
|
|
Working in a git worktree is fine and often preferable. ⚠️ **A worktree needs a
|
|
real `npm install`, not a symlinked `node_modules`** — a symlink passes
|
|
lint/test/coverage and then fails all Storybook story files on Vite's
|
|
`fs.allow`.
|
|
|
|
For order-dependent PRs on one issue, **stack** them: each branch is based on the
|
|
previous one, not all cut from `v2/main`.
|
|
|
|
## 3. Sign off every commit
|
|
|
|
**The DCO check is a hard merge gate.** The [probot DCO
|
|
app](https://probot.github.io/apps/dco/) fails the PR unless each commit carries
|
|
a `Signed-off-by: Name <email>` trailer whose name **and** email match either the
|
|
commit's author or its committer. Its only exemptions are merge commits and
|
|
bot-authored commits; there is no partial credit — one unsigned commit out of six
|
|
fails the whole check.
|
|
|
|
**Prevent it with `git commit -s`.** Two things that look like automation and are
|
|
not:
|
|
|
|
- ⚠️ **`git config format.signOff true` does nothing here.** Despite the name it
|
|
only defaults the `-s` flag for `git format-patch`; `git commit` never reads it,
|
|
and there is no `commit.signoff` equivalent.
|
|
- ⚠️ **A `prepare-commit-msg` hook works, but think before installing one.** The
|
|
trailer is a certification, and a hook makes it on your behalf for *every*
|
|
commit, including work you merely cherry-picked. Inside that hook,
|
|
`git var GIT_AUTHOR_IDENT` returns your config identity rather than the
|
|
preserved author, so it cannot even tell it is signing for someone else.
|
|
|
|
**Repairing already-pushed commits** means rewriting them:
|
|
|
|
```sh
|
|
git rebase HEAD~<n> --signoff
|
|
git push --force-with-lease
|
|
```
|
|
|
|
Use `--force-with-lease` rather than `--force`, and only rewrite when you are the
|
|
sole author and nobody else has based work on the branch. The two apparent
|
|
alternatives are not alternatives: the app's empty "remediation commit" flow
|
|
requires `allowRemediationCommits.individual` and this repo ships no
|
|
`.github/dco.yml`, so it runs disabled; and the override button anyone with write
|
|
access sees only silences the check without anyone certifying anything.
|
|
|
|
The signoff is a [Developer Certificate of
|
|
Origin](https://developercertificate.org/) assertion made in **your own name**. It
|
|
does not claim you wrote the code, so signing off a cherry-pick is legitimate.
|
|
What is never acceptable is fabricating *someone else's* certification.
|
|
|
|
## 4. Run the gate
|
|
|
|
`npm run format`, then `npm run local:gate`. See `/pre-push-gate` — `npm run
|
|
validate` is **not** a substitute.
|
|
|
|
## 5. Screenshots, for any UI change
|
|
|
|
Any change to the web UI or the TUI must show its result: capture before/after
|
|
screenshots (or a short GIF for an interaction) into a **`pr-screenshots/`
|
|
folder off the repo root**, creating it if it doesn't exist. That folder is
|
|
**gitignored** — the images are working artifacts staged for upload, never
|
|
committed — so attach them to the PR body from there rather than referencing an
|
|
in-repo path. Name them for what they show (`tools-tab-before.png`), not
|
|
`Screenshot 2026-07-31 at 14.02.11.png`.
|
|
|
|
To host them, upload to GitHub's attachment endpoint with your `gh` token; the
|
|
parameters go in the **query string** with the raw bytes as the body (a JSON
|
|
body fails with a misleading "Invalid name for request"):
|
|
|
|
```sh
|
|
curl -sX POST -H "Authorization: token $(gh auth token)" \
|
|
--data-binary @pr-screenshots/tools-tab-after.png \
|
|
"https://uploads.github.com/user-attachments/assets?repository_id=<REPO_ID>&name=tools-tab-after.png&content_type=image/png"
|
|
```
|
|
|
|
## 6. Open the PR
|
|
|
|
Target the base branch matching the work: **`v2/main`** for v2, **`v1/main`**
|
|
for v1. Never `main`.
|
|
|
|
The body's **first line is `Closes #<ISSUE_NUMBER>`**.
|
|
|
|
```sh
|
|
gh pr create --repo modelcontextprotocol/inspector \
|
|
--base v2/main --label v2 \
|
|
--title "<title>" --body "Closes #<N>
|
|
|
|
<what changed and why>"
|
|
```
|
|
|
|
⚠️ Closing keywords only auto-link and auto-close for PRs targeting the repo's
|
|
**default branch** (`main`). Because v2 PRs target `v2/main`, `Closes #N` there
|
|
is only a cross-reference — it will **not** create a hard link or close the issue
|
|
on merge. Keep it anyway, so the issues close if/when `v2/main` reaches `main`.
|
|
There is no `gh` flag for manual linking; closing keywords are the only
|
|
mechanism GitHub exposes.
|
|
|
|
Move the card to **In Review**.
|
|
|
|
## 7. Request a Copilot review
|
|
|
|
Only the GraphQL `requestReviews` mutation with the Copilot **bot id** works —
|
|
REST, `gh pr edit --add-reviewer`, `userIds`, and `copilot-swe-agent` all fail or
|
|
silently drop.
|
|
|
|
```sh
|
|
PR_ID=$(gh pr view <N> --repo modelcontextprotocol/inspector --json id --jq .id)
|
|
gh api graphql -f query='
|
|
mutation($pr:ID!,$bot:[ID!]!) {
|
|
requestReviews(input:{pullRequestId:$pr, botIds:$bot, union:true}) {
|
|
pullRequest { id }
|
|
}
|
|
}' -f pr="$PR_ID" -f bot='BOT_kgDOCnlnWA'
|
|
```
|
|
|
|
Poll for the review with a `startswith` match — the review login carries a
|
|
`[bot]` suffix.
|
|
|
|
## 8. Respond to the review
|
|
|
|
- It is **not** necessary to implement every suggestion. Implementing one a
|
|
different way, or declining it with a reason, is fine.
|
|
- After making the changes, **respond to each comment** with what was done, or
|
|
why it was ignored.
|
|
- ⚠️ **Inline replies go hidden once the fix is pushed** (the threads become
|
|
outdated), so **mirror each round at PR level** as a summary comment, and always
|
|
read the "Suppressed comments" block.
|
|
- ⚠️ **Copilot's inline comments lag its review body.** The body's "generated N
|
|
comments" count lands first; fetch by recency and reconcile. Repeated
|
|
re-review silence means the session ended.
|
|
|
|
## 9. Merge and close out
|
|
|
|
**On merge of a v2 PR, manually close its issue and move the board item to
|
|
Done** (`259d6aab`), since auto-close won't fire on `v2/main`.
|
|
|
|
`Done` is only for work that **shipped** — a merged PR, or a parent whose last
|
|
sub-issue closed. Anything else (duplicate, won't fix, not planned, obsolete)
|
|
means nothing shipped, so **delete the card** instead; see `/board-ops`.
|