|
|
@@ -0,0 +1,293 @@
|
|
|
+# Pull Request Operations Reference
|
|
|
+
|
|
|
+Per-operation playbooks for `gh pr` workflows. Every write op with author voice (`--body` / `--title` / merge `--subject` / `--body`) is **preview-gated** per hard rule 8 — quote the draft verbatim in chat, name the send command, wait for explicit user approval, then send.
|
|
|
+
|
|
|
+## Reads (no preview)
|
|
|
+
|
|
|
+```bash
|
|
|
+# Single PR with metadata
|
|
|
+gh pr view <n> --repo <o>/<r>
|
|
|
+
|
|
|
+# PR + comment thread (the discussion is half the story)
|
|
|
+gh pr view <n> --repo <o>/<r> --comments
|
|
|
+
|
|
|
+# Diff (use this — not local git diff — to see exactly what the PR proposes)
|
|
|
+gh pr diff <n> --repo <o>/<r>
|
|
|
+
|
|
|
+# CI checks (snapshot)
|
|
|
+gh pr checks <n> --repo <o>/<r>
|
|
|
+
|
|
|
+# CI checks (block until all complete — for use during merge gate)
|
|
|
+gh pr checks <n> --repo <o>/<r> --watch --interval 10
|
|
|
+
|
|
|
+# List with filters
|
|
|
+gh pr list --repo <o>/<r> --state open --base main --limit 20
|
|
|
+
|
|
|
+# Inline review comments on specific lines (default --comments shows top-level only)
|
|
|
+gh api repos/<o>/<r>/pulls/<n>/comments --jq '.[] | {path,line,user:.user.login,body}'
|
|
|
+
|
|
|
+# Detailed merge-readiness fields
|
|
|
+gh pr view <n> --repo <o>/<r> --json mergeable,mergeStateStatus,reviewDecision,statusCheckRollup
|
|
|
+```
|
|
|
+
|
|
|
+## Create (preview required — title AND body)
|
|
|
+
|
|
|
+PR title shows in lists, notifications, and (for squash merges) becomes the commit message subject on `main`. Body should explain *why* + how to verify. Always preview both.
|
|
|
+
|
|
|
+### Chat preview
|
|
|
+
|
|
|
+> Drafted PR for `<o>/<r>`:
|
|
|
+>
|
|
|
+> **Title:** `<title>`
|
|
|
+>
|
|
|
+> **Base:** main ← **Head:** `<branch>`
|
|
|
+>
|
|
|
+> **Body:**
|
|
|
+> ```markdown
|
|
|
+> <body>
|
|
|
+> ```
|
|
|
+>
|
|
|
+> Command: `gh pr create --base main --head <branch> --title "..." --body "..."`
|
|
|
+>
|
|
|
+> Send?
|
|
|
+
|
|
|
+### Send
|
|
|
+
|
|
|
+```bash
|
|
|
+gh pr create --repo <o>/<r> \
|
|
|
+ --base main \
|
|
|
+ --head <branch> \
|
|
|
+ --title "<approved title>" \
|
|
|
+ --body "$(cat <<'EOF'
|
|
|
+<approved body>
|
|
|
+EOF
|
|
|
+)"
|
|
|
+```
|
|
|
+
|
|
|
+### Body shape (claude-mods convention)
|
|
|
+
|
|
|
+```markdown
|
|
|
+## Summary
|
|
|
+
|
|
|
+<1-3 bullets — what changed and why>
|
|
|
+
|
|
|
+## Test plan
|
|
|
+
|
|
|
+- [x] <thing that was tested>
|
|
|
+- [ ] <thing the reviewer should test>
|
|
|
+
|
|
|
+Closes #<n> <!-- if this PR resolves an issue, link it so merge auto-closes -->
|
|
|
+```
|
|
|
+
|
|
|
+The "Closes #N" / "Fixes #N" footer is load-bearing — it triggers GitHub's auto-close on merge. Without it the issue stays open after the PR lands and someone has to clean up.
|
|
|
+
|
|
|
+### Draft vs ready
|
|
|
+
|
|
|
+For work-in-progress or pre-review polish, create as draft:
|
|
|
+
|
|
|
+```bash
|
|
|
+gh pr create … --draft
|
|
|
+```
|
|
|
+
|
|
|
+Promote later (mechanical, no preview): `gh pr ready <n>`.
|
|
|
+
|
|
|
+## Comment (preview required)
|
|
|
+
|
|
|
+Same discipline as issue comments. Top-level PR comments are different from inline review comments (next section).
|
|
|
+
|
|
|
+### Send
|
|
|
+
|
|
|
+```bash
|
|
|
+gh pr comment <n> --repo <o>/<r> --body "$(cat <<'EOF'
|
|
|
+<approved body>
|
|
|
+EOF
|
|
|
+)"
|
|
|
+```
|
|
|
+
|
|
|
+## Review (preview required for body)
|
|
|
+
|
|
|
+Three flavours: `--approve`, `--request-changes`, `--comment` (review-level, not inline). All three may include a body; if they do, **preview the body**.
|
|
|
+
|
|
|
+```bash
|
|
|
+# Approve with no message — preview not required (the action itself is the signal)
|
|
|
+gh pr review <n> --repo <o>/<r> --approve
|
|
|
+
|
|
|
+# Approve with a parting comment — preview the body
|
|
|
+gh pr review <n> --repo <o>/<r> --approve --body "<approved body>"
|
|
|
+
|
|
|
+# Request changes — body is required and definitely preview-gated
|
|
|
+gh pr review <n> --repo <o>/<r> --request-changes --body "<approved body>"
|
|
|
+
|
|
|
+# Comment-only review (no approve/block) — preview the body
|
|
|
+gh pr review <n> --repo <o>/<r> --comment --body "<approved body>"
|
|
|
+```
|
|
|
+
|
|
|
+Inline-line comments (`gh api repos/<o>/<r>/pulls/<n>/comments`) are heavier — currently out of scope; use the GitHub UI or extend this reference when first needed.
|
|
|
+
|
|
|
+## Edit title / body (preview required)
|
|
|
+
|
|
|
+```bash
|
|
|
+gh pr edit <n> --repo <o>/<r> \
|
|
|
+ --title "<new title>" \
|
|
|
+ --body "$(cat <<'EOF'
|
|
|
+<new body>
|
|
|
+EOF
|
|
|
+)"
|
|
|
+```
|
|
|
+
|
|
|
+Mechanical edits (labels, reviewers, milestone, base branch) don't need preview:
|
|
|
+
|
|
|
+```bash
|
|
|
+gh pr edit <n> --repo <o>/<r> \
|
|
|
+ --add-label "ready-for-review" \
|
|
|
+ --remove-label "wip" \
|
|
|
+ --add-reviewer <user> \
|
|
|
+ --milestone "v2.11.0"
|
|
|
+```
|
|
|
+
|
|
|
+## Pre-merge gate
|
|
|
+
|
|
|
+**Never invoke `gh pr merge` without running this gate first.** This is the discipline distilled from PR #11.
|
|
|
+
|
|
|
+```bash
|
|
|
+# 1. Merge-readiness JSON
|
|
|
+gh pr view <n> --repo <o>/<r> --json mergeable,mergeStateStatus,reviewDecision \
|
|
|
+ --jq '{mergeable,mergeStateStatus,reviewDecision}'
|
|
|
+# Need: mergeable=MERGEABLE, mergeStateStatus=CLEAN (or UNSTABLE if non-required checks fail)
|
|
|
+
|
|
|
+# 2. All checks pass (use --watch to block during a CI run)
|
|
|
+gh pr checks <n> --repo <o>/<r>
|
|
|
+
|
|
|
+# 3. Review the actual diff one more time
|
|
|
+gh pr diff <n> --repo <o>/<r> | less
|
|
|
+
|
|
|
+# 4. Confirm the PR body still accurately describes the diff
|
|
|
+# (PRs that grew during review often have stale descriptions — edit first if so)
|
|
|
+gh pr view <n> --repo <o>/<r> --json title,body --jq '.body' | head -50
|
|
|
+```
|
|
|
+
|
|
|
+Surface the result in chat as a green/red checklist before proposing the merge command. If anything is red, **stop and report** — don't merge through a failing gate.
|
|
|
+
|
|
|
+## Merge strategy decision tree
|
|
|
+
|
|
|
+| Situation | Strategy | Why |
|
|
|
+|---|---|---|
|
|
|
+| Fix or feature branch with multiple WIP commits all serving one logical change | **Squash** (`--squash`) | Clean single-line history on main; the PR is the unit, individual commits were drafts |
|
|
|
+| Each commit on the branch is independently meaningful and worth preserving in `git log` | **Merge commit** (`--merge`) | Preserves authorship and intermediate steps; useful for collaborative branches |
|
|
|
+| Repo enforces linear history (`gh api repos/<o>/<r> --jq .allow_rebase_merge`) and commits are individually clean | **Rebase** (`--rebase`) | Each commit lands on main as a separate commit, no merge bubble |
|
|
|
+| Mixed / unclear | Ask the user | Don't guess; the project's history style is theirs to decide |
|
|
|
+
|
|
|
+**Default for one-off PRs in this project family (claude-mods, etc.):** `--squash`.
|
|
|
+
|
|
|
+Check repo allowance once via `gh api repos/<o>/<r> --jq '{allow_squash_merge,allow_merge_commit,allow_rebase_merge}'`. If only some are enabled, that constrains the choice.
|
|
|
+
|
|
|
+### Squash with custom subject/body
|
|
|
+
|
|
|
+When using `--squash`, the merge commit's subject and body land on `main` as a public commit. If passing `--subject` / `--body`, **preview both** (hard rule 8):
|
|
|
+
|
|
|
+```bash
|
|
|
+gh pr merge <n> --repo <o>/<r> --squash \
|
|
|
+ --subject "<approved subject — typically: PR title (#<n>)>" \
|
|
|
+ --body "$(cat <<'EOF'
|
|
|
+<approved body — typically: one-paragraph summary of the change>
|
|
|
+EOF
|
|
|
+)"
|
|
|
+```
|
|
|
+
|
|
|
+Without `--subject` / `--body`, `gh` uses the PR title and the concatenated commit messages — no preview required, but state in chat which you're using.
|
|
|
+
|
|
|
+## Merge — the final call
|
|
|
+
|
|
|
+After the pre-merge gate is green and the strategy is chosen:
|
|
|
+
|
|
|
+> Pre-merge gate green:
|
|
|
+> - mergeable: MERGEABLE / mergeStateStatus: CLEAN
|
|
|
+> - All 3 checks pass (validate 18s, Socket x2)
|
|
|
+> - Diff reviewed, PR body matches
|
|
|
+>
|
|
|
+> Proposing: `gh pr merge <n> --repo <o>/<r> --squash --subject "<…>" --body "<…>"`
|
|
|
+>
|
|
|
+> Branch deletion: **not bundled** — separate step after merge if you want it.
|
|
|
+>
|
|
|
+> Merge?
|
|
|
+
|
|
|
+Then on approval:
|
|
|
+
|
|
|
+```bash
|
|
|
+gh pr merge <n> --repo <o>/<r> --squash --subject "..." --body "..."
|
|
|
+```
|
|
|
+
|
|
|
+## Branch deletion (separate explicit step)
|
|
|
+
|
|
|
+`gh pr merge --delete-branch` exists but couples merge + delete. **Keep them separate.** Delete branches as a discrete operation after merge, with its own confirmation — both because branch deletion is destructive and because a currently-checked-out branch can't be deleted at all (relevant if the operating worktree is on the PR branch).
|
|
|
+
|
|
|
+```bash
|
|
|
+# After merge + user OK:
|
|
|
+git -C <local-repo> fetch origin --prune
|
|
|
+git -C <local-repo> checkout --detach origin/main # leave the branch if it's checked out
|
|
|
+git -C <local-repo> branch -D <branch> # local
|
|
|
+git push origin --delete <branch> # remote
|
|
|
+```
|
|
|
+
|
|
|
+## Close (without merging)
|
|
|
+
|
|
|
+```bash
|
|
|
+# Plain close — no preview
|
|
|
+gh pr close <n> --repo <o>/<r>
|
|
|
+
|
|
|
+# Close with a parting comment — preview the comment first
|
|
|
+gh pr comment <n> --repo <o>/<r> --body "..." # preview-gated
|
|
|
+gh pr close <n> --repo <o>/<r>
|
|
|
+```
|
|
|
+
|
|
|
+### Closing-comment templates
|
|
|
+
|
|
|
+**Superseded:**
|
|
|
+> Closing in favour of #<m>, which takes a different approach: <one sentence>. Thanks for the work here — pieces of the idea live on in the new PR.
|
|
|
+
|
|
|
+**Won't merge:**
|
|
|
+> Closing — after discussion, decided not to land this because <reason>. Detailed why in the thread above. Not a rejection of the code quality, just the direction.
|
|
|
+
|
|
|
+## Common workflows
|
|
|
+
|
|
|
+### Standard fix flow
|
|
|
+
|
|
|
+```
|
|
|
+1. Branch off origin/main
|
|
|
+2. Commit; push branch
|
|
|
+3. Draft PR body in chat, preview with user, gh pr create (preview-gated)
|
|
|
+4. CI runs; gh pr checks --watch
|
|
|
+5. Address review feedback if any (responses preview-gated)
|
|
|
+6. Pre-merge gate
|
|
|
+7. Merge (squash by default; explicit user OK)
|
|
|
+8. Branch cleanup (separate step, explicit OK)
|
|
|
+```
|
|
|
+
|
|
|
+### Reviewing a PR
|
|
|
+
|
|
|
+```
|
|
|
+1. gh pr view <n> --comments # full context including prior discussion
|
|
|
+2. gh pr diff <n> # what actually changed
|
|
|
+3. gh pr checks <n> # CI status
|
|
|
+4. Read the linked issue(s) for original intent
|
|
|
+5. Draft review body in chat → preview → gh pr review (preview-gated)
|
|
|
+```
|
|
|
+
|
|
|
+### Hotfix with auto-close
|
|
|
+
|
|
|
+```
|
|
|
+1. Branch off main, fix, commit, push
|
|
|
+2. PR body includes "Fixes #<bug-issue>" (auto-closes on merge)
|
|
|
+3. Standard merge flow
|
|
|
+4. After merge: post a closing-credit comment on #<bug-issue> with version + PR link
|
|
|
+```
|
|
|
+
|
|
|
+## Anti-patterns
|
|
|
+
|
|
|
+- ❌ Merging without running the pre-merge gate (`mergeStateStatus` / checks / diff).
|
|
|
+- ❌ Bundling `--delete-branch` into the merge command — couples two destructive decisions into one.
|
|
|
+- ❌ Defaulting to `--merge` (merge-commit) when squash gives a cleaner history; only use it when individual commits matter.
|
|
|
+- ❌ Sending a review body, PR description, or comment without showing the draft to the user first (hard rule 8).
|
|
|
+- ❌ Promising follow-up work in a PR description and not capturing it as an issue.
|
|
|
+- ❌ Skipping the "Closes #N" footer in PR body — leaves issues unclosed after merge.
|
|
|
+- ❌ Treating `mergeable: UNKNOWN` as green — wait for GitHub to compute (re-poll).
|