inspector/.claude/skills/pr-flow/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

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 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:

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.