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>
7.5 KiB
| name | description | disable-model-invocation |
|---|---|---|
| pr-flow | 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). | true |
PR flow
Rules this procedure enforces live in 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 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 truedoes nothing here. Despite the name it only defaults the-sflag forgit format-patch;git commitnever reads it, and there is nocommit.signoffequivalent. - ⚠️ A
prepare-commit-msghook 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_IDENTreturns 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:
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 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"):
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>.
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.
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.