Skip to main content

pr-flow

Take an issue through to a PR in this repo and close it out after merge. Use when asked to open, create or submit a PR; when naming a branch; when signing off commits or repairing a missing Signed-off-by; when recording client evidence for a PR; when requesting a Copilot review, answering its comments or deciding whether to run another round; or when closing out after a merge.

소스 정보

저장소
modelcontextprotocol/servers
최근 소스 활동
2026년 10월 5일 05:28
감지된 SKILL.md 언어
영어
스타
91,144
포크
11,778

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

파일 탐색기
2 개 파일

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
pr-flow
description
Take an issue through to a PR in this repo and close it out after merge. Use when asked to open, create or submit a PR; when naming a branch; when signing off commits or repairing a missing Signed-off-by; when recording client evidence for a PR; when requesting a Copilot review, answering its comments or deciding whether to run another round; or when closing out after a merge.
disable-model-invocation
false
# PR flow The rules this procedure carries out live in [`AGENTS.md`](../../../AGENTS.md) (**Contributing**, **Issue-driven work style**, **Responding to code reviews**, **Waiting on long-running work**). Board mechanics, including every board ID, are in `/board-ops`; creating a missing issue is `/issue-create`. This file names board columns and nothing else. **Pull requests against this repo are opened by the maintainers only.** Write access is not authorization to open one. Anyone else files a detailed issue (`/issue-create`) and a maintainer takes it from there. ## 1. Start from an issue **Every PR closes an issue.** A PR with no linked issue has no board card, so the work is invisible to the board. If there is no issue yet, create one first with `/issue-create`; never open the PR and backfill. **Read the issue in full, every comment included.** Scope changes, maintainer decisions and "not this, that" corrections are usually in the comments, not the body: ```sh gh issue view <N> --repo modelcontextprotocol/servers --comments ``` Then assign it to yourself and move its card to **In Progress** (the move-a-card recipe in `/board-ops`): ```sh gh issue edit <N> --repo modelcontextprotocol/servers --add-assignee @me ``` ## 2. Branch `v2/<type>/<N>-<slug>`, cut from **`origin/v2/main`**: ```sh git fetch origin v2/main git switch -c v2/fix/4810-filesystem-symlink-escape origin/v2/main ``` `<type>` follows the issue's type label: `bug` → `fix`, `enhancement` → `feat`, `documentation` → `docs`, `chore` → `chore`. A `question` has no branch type, because answering one ships nothing: if it turns out to need a change, relabel the issue with the type that change is, then branch. The `v2/` prefix matches the base branch and keeps the work legible in `git branch -a` next to `main`. **Never cut from `main`.** It is the release line and does not carry `v2/main`'s history, so the PR diff would show unrelated changes. **Stacking.** When one issue's work depends on another PR that has not merged yet, branch from that PR's branch and open the PR with `--base` set to it. Say in the body that it is stacked and on which PR. When the lower PR merges, retarget the stacked one to `v2/main` (`gh pr edit <PR> --base v2/main`) and rebase it if GitHub reports conflicts. ## 3. Sign off every commit (DCO) This repo adopted the [Developer Certificate of Origin](https://developercertificate.org/) (maintainer decision on #4861). Commit with **`git commit -s`**, every time. The rule is checked twice, by the same script (`scripts/verify-dco.mjs`): - **`npm run verify:dco`**, a stage of the pre-push gate (step 4), over `origin/v2/main..HEAD`: the commits you are about to push. Name another range with `-- --base <rev> --head <rev>` (a stacked branch's base, say). - **The `DCO signoff` job** (`.github/workflows/dco.yml`), on every pull request except into `main`, over the PR's `base.sha..head.sha`. What it checks: each commit carries a `Signed-off-by: Name <email>` trailer whose name **and** email match the commit's author or its committer. Merge commits and bot-authored commits are exempt. There is no partial credit: one unsigned commit out of six fails the whole check. The failure names each unsigned commit and prints the repair below. 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 commits already made** means rewriting them, onto the merge base the failure prints: ```sh git rebase --signoff <merge-base> git push --force-with-lease ``` `--signoff` signs each commit as you, the committer. Use `--force-with-lease` rather than `--force`, and rewrite only when you are the sole author and nobody has based work on the branch (a stacked PR above yours has). Nothing else clears the check: an empty "remediation" commit signs nothing that came before it, and the check reads every commit in the range. The signoff is a DCO assertion made in **your own name**. It does not claim you wrote the code, so signing off a cherry-pick is legitimate. Fabricating _someone else's_ certification never is. ## 4. Run the gate Format, then run the pre-push gate, and fix what fails before pushing: ```sh npm run format # TypeScript; in a Python server: uv run --frozen ruff format . npm run local:gate; echo "EXIT=$?" ``` `local:gate` runs every check CI runs, for both languages, whatever the change touched. Verify by the exit code, not by grepping the output. What each stage checks and how to fix a red one is `/pre-push-gate`; the stage list is [`docs/quality-gate.md`](../../../docs/quality-gate.md). One thing the gate deliberately leaves out: | Change touches | Also run | | --- | --- | | A skill's description, or a new skill | `npm run skills:eval` for the **whole** suite (spends model calls; not in the gate or CI). See `AGENTS.md` **Maintaining the skills** | ## 5. Client evidence A PR shows the change working, under the body's **How Has This Been Tested?** section (step 6). Here that means evidence from clients, not screenshots. **A change to server behavior** (a tool, resource, prompt, capability, transport or error it returns) records, for **both** the Inspector V2 and an LLM client, in **both** spec eras (a 2026-07-28 client and a 2025-11-25 client, per #4857): - what the client was asked to do (the Inspector action, or the prompt given to the LLM client), and - what it returned (the result, trimmed to what shows the change). A before/after pair is best when the change fixes a bug: the same request against `v2/main` and against the branch. **A change with no client-observable surface** (docs, skills, workflows, gate tooling, CI) carries a **targeted probe** instead: the one thing that proves it. For example, the guard made to fire on a planted defect and then pass, the workflow run that exercised the new job, the query that reads back the state a skill's recipe produced, or the `skills:eval` scores for a skill. Put the evidence in the PR body under **How Has This Been Tested?**. ## 6. Open the PR Base **`v2/main`** (or the lower branch, when stacked). **Never `main`.** Label it `v2`. The body's **first line is `Closes #<N>`**. The body carries these sections, in order. `.github/pull_request_template.md` is only the "issues, not PRs" banner that turns outside PRs away, so this list is where the structure lives: - **Description**: what changed and why. - **Server Details**: the server (or "none (repository-wide)") and what in it changed (tools, resources, prompts, docs, …). - **Motivation and Context**: the problem it solves; usually a pointer to the issue. - **How Has This Been Tested?**: the evidence from step 5. - **Breaking Changes**: whether users must change their client configuration. - **Types of changes**: tick each that applies: bug fix, new feature, breaking change, documentation update. - **Checklist**: answer every item. Tick each that holds, and tick and mark each that does not apply "(not applicable: <why>)"; leave none blank: - [ ] I have read the [MCP Protocol Documentation](https://modelcontextprotocol.io) - [ ] My changes follow MCP security best practices - [ ] I have updated the server's README accordingly - [ ] I have added a changeset (`npm run changeset`) if this changes what a TypeScript server publishes - [ ] I have tested this with an LLM client - [ ] My code follows the repository's style guidelines - [ ] New and existing tests pass locally - [ ] I have added appropriate error handling - [ ] I have documented all environment variables and configuration options - **Additional context** (optional): implementation notes or design decisions. Write the body to a file and pass it with `--body-file`. A body passed inline in double quotes goes through the shell, so every backtick in its Markdown runs as a command substitution and `$VAR` expands. Keep the file outside the worktree, where `git add -A` cannot pick it up. ```sh BODY=$(mktemp) cat > "$BODY" <<'EOF' Closes #<N> <the sections above, with the evidence> EOF gh pr create --repo modelcontextprotocol/servers \ --base v2/main --label v2 --title "<title>" --body-file "$BODY" ``` ⚠️ Closing keywords only link and auto-close for PRs that target the default branch (`main`). Against `v2/main`, `Closes #N` is only a cross-reference. Keep the line anyway, and **link the PR to the issue explicitly** right after opening it: ```sh PR=<PR_NUMBER>; N=<ISSUE_NUMBER> PR_ID=$(gh pr view "$PR" --repo modelcontextprotocol/servers --json id --jq .id) ISSUE_ID=$(gh issue view "$N" --repo modelcontextprotocol/servers --json id --jq .id) gh api graphql -f query=' mutation($issue:ID!,$pr:ID!) { addCloseIssueReferences(input:{issueId:$issue, pullRequestIds:[$pr]}) { issue { number } } }' -f issue="$ISSUE_ID" -f pr="$PR_ID" # Read the link back: the issue number must be listed. gh pr view "$PR" --repo modelcontextprotocol/servers \ --json closingIssuesReferences --jq '[.closingIssuesReferences[].number]' ``` The link does **not** make the issue close on merge into `v2/main`; that is still done by hand in step 9. Then move the card to **In Review** (`/board-ops`). The PR itself never goes on the board. ## 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 the request. ```sh PR_ID=$(gh pr view <PR> --repo modelcontextprotocol/servers --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' ``` A round usually lands in about five minutes. Wait for it with **one backgrounded loop that exits when the round lands, or when its deadline passes**, and wait for its notification rather than re-fetching once per turn. A review is remote state the harness cannot observe, which is the exception in `AGENTS.md` **Waiting on long-running work**, and this loop is where that poll belongs. ```sh PR=<PR> EXPECTED=1 # the review COUNT to reach; see below DEADLINE=$(( $(date +%s) + 20*60 )) while :; do # Capture first, so a gh failure stops the loop instead of being swallowed by # a pipeline. --slurp cannot be combined with --jq, hence the separate jq. raw=$(gh api --paginate --slurp \ repos/modelcontextprotocol/servers/pulls/$PR/reviews) || { echo "gh api failed ($?); not retrying blind" >&2; exit 1; } n=$(jq '[.[][] | select(.user.login | startswith("copilot-pull-request-reviewer"))] | length' <<<"$raw") || { echo "jq failed ($?) on an unexpected response shape" >&2; exit 1; } case $n in '' | *[!0-9]*) echo "not a count: '$n'" >&2; exit 1 ;; esac [ "$n" -ge "$EXPECTED" ] && { echo "round landed ($n reviews)"; break; } [ "$(date +%s)" -ge "$DEADLINE" ] && { echo "SILENT: no review by the deadline"; break; } sleep 30 done ``` `EXPECTED` is the review **count** to reach, so it is `1` only on the first round; on round two the first round's review is still there, and an existence check would return at once. Match the login with `startswith`: it carries a `[bot]` suffix. `sleep 30` is the remote-API floor `AGENTS.md` sets. **Every step that can fail exits the loop rather than retrying**: piping the count into `awk` would make an auth error read as `0`, and an empty count from a `jq` failure would make the comparison fail forever, so the job sleeps and retries without end. A background task that can never succeed is worse than none, because it looks like progress. Once the body lands, give the inline comments about another 60 seconds: they arrive after it (step 8). ## 8. Respond to the review Judge each finding against **the issue the PR closes**, as `AGENTS.md` **Responding to code reviews** says: fix defects in what the PR added; decline scope creep (pre-existing behavior, new capabilities, hardening the issue did not ask for) with a reason, and file an issue (`/issue-create`) for any that is worth doing on its own. Implementing a suggestion differently is fine. - **Reply to each review comment in its own thread**, saying what was done (with the commit) or why it was declined. That reply is the primary response, and it is not optional: each comment is a thread with its own resolve state, and a reply _in_ it is the only thing a reader of that thread sees. Replying does not resolve the thread; resolving is the reviewer's act. ```sh # The round's comments, by REVIEW id. The unpaginated /reviews listing hides # later rounds behind your own replies, and this endpoint returns 30 per page: # a round you only half fetch is a round you only half answer. gh api --paginate repos/modelcontextprotocol/servers/pulls/<PR>/reviews/<REVIEW_ID>/comments \ --jq '.[]|"\(.id) \(.path):\(.line)\n\(.body)\n"' # Reply into one thread, keyed by the comment id from above. gh api repos/modelcontextprotocol/servers/pulls/<PR>/comments/<COMMENT_ID>/replies \ -f body='Fixed in <sha>: …' ``` - ⚠️ **Then post a PR-level summary of the round, in addition, never instead.** Inline replies go hidden once the fix is pushed, because the threads become outdated, so the summary is what keeps the round readable afterwards. It does not replace the per-thread replies: a summary bullet cannot be connected back to the thread it answers. ```sh # Quoted heredoc, for the same reason as the PR body in step 6: Markdown # backticks inside a double-quoted --body run as commands. gh pr comment <PR> --repo modelcontextprotocol/servers --body-file - <<'EOF' Copilot round <k>: … EOF ``` Per-thread replies (`-f body=…` above) need the same care: single-quote the body, or read it from a file with `-F body=@<file>`. - ⚠️ **Read the "Suppressed comments" block in the review body.** Those findings have no comment id, so no thread to reply into. The PR-level summary is the only place to answer them. - ⚠️ **Inline comments lag the review body.** The body's "generated N comments" count lands first. Fetch by review id, and reconcile the count before answering. Push the fixes (signed off, step 3) before requesting the next round. ## 9. Another round, or stop Request another round (step 7) in one of two cases, and no other: - **You pushed a fix** in response to the last round. Raise `EXPECTED` by one. - **The last round was silent** (no review by the poll's deadline). Nothing was
GitHub에서 보기
이 SKILL.md는 매우 커서 SkillsMP가 여기에는 첫 섹션만 미리 보여줍니다. GitHub에서 보기