Prompt file imported from kaiohenricunha/dotbabel (
.github/prompts/merge-pr.prompt.md). Fill in{{arguments}}before use. Copyright stays with the author.
Merge a pull request only after full local verification and a passing quality gate.
Trigger: when the user asks to merge a PR. Also triggered directly via /merge-pr <N>.
Arguments: {{arguments}} — the PR number (e.g. 125). If missing, ask the user which PR.
Steps
-
Fetch PR metadata.
gh pr view <N> --json number,title,author,headRefName,baseRefName,body,labels,files,mergeable,mergeStateStatus,statusCheckRollupRecord: branch name, changed files, CI status, mergeable status.
-
Verify PR body has required sections.
- Must contain
## Summary - Must contain
## Test plan - If the repo uses spec IDs (a
specs/ordocs/specs/dir), or the PR changes a protected path fromdocs/repo-facts.json, must contain a## Spec IDsection or a## No-spec rationalesection.dotbabel pr-stack gate --gate mergeaccepts either one. A Spec ID must name an approved, implementing, or done spec, and it does not cover a changed protected path that is missing from that spec'slinked_paths. In both of those cases, use the rationale. If any are missing, STOP and ask the user whether to auto-append them viagh pr edit <N> --body-file.
- Must contain
-
Checkout the branch in an isolated worktree.
git fetch origin git worktree add /tmp/merge-pr-<N> origin/<headRefName> cd /tmp/merge-pr-<N>Never mutate the user's active working directory.
-
Run the full project test suite. Detect runner:
Makefilewithtest→make testpackage.json→npm test(orpnpm/yarnbased on lockfile)go.mod→go test ./...pyproject.toml→pytestoruv run pytest
Paste the tail of output (last ~40 lines) regardless of pass/fail.
-
Quality gate. Run the PR quality profile against the base branch:
dotbabel quality check --profile pr --base origin/<baseRefName>Exit code
1means a checked rule failed. Exit code2means required evidence, trust, or tooling is unavailable. STOP for either exit code and surface the result — do not merge past it. Any other non-zero exit also means the gate did not run —64is invalid usage, and127isdotbabelnot installed, which is the normal state on the bootstrap-only install path (see the top ofCLAUDE.md). STOP there too, say which exit you got, and never report an unrun gate as a pass. Exit0means no error verdict; continue to step 6.Expect exit
2on the first run: step 3 put you in a throwaway/tmp/merge-pr-<N>worktree, and project-command trust matches the repository path exactly, so a fresh worktree is never trusted. Resolve it deliberately, per PR — read the branch's own diff to.dotbabel.jsonand to anypackage.jsonscripts the tools invoke, then either trust that one path or run this step from your own checkout of the branch. Do not reach for a blanket--allow-project-commandshere: in this flow it would execute commands defined by the pull request under review. -
Interpret failures honestly. If the test suite fails:
git stash <test-command> git stash popReport whether the failure is pre-existing on
origin/<baseRefName>or introduced by this PR. Do not assert "pre-existing" without running this proof. -
Verify CI is green.
gh pr checks <N>If any check is
failingorpending, STOP and wait or ask the user. -
Request merge confirmation from the user. Show:
- Summary of local test result
- Quality gate result
- CI status
- The exact merge command you will run Wait for the user to say "merge" (or equivalent).
-
Merge — with an explicit, marker-free squash subject AND body. The default squash body concatenates the branch's commit messages, and — the sneakier half — on a single-commit PR the default squash subject is that commit's subject line, not the PR title. Every intermediate commit carries
[skip ci](the conductor requires it), GitHub honors the marker anywhere in the final message including line 1, and release-please is a push-triggered workflow — so a default merge silently suppresses the release for its own commits. Pass both halves explicitly, built from the PR title and description with any CI-suppression markers stripped:STRIP='s/\[skip ci\]//g; s/\[ci skip\]//g; s/skip-checks: *true//g' SUBJ="$(gh pr view <N> --json title -q .title | sed "$STRIP") (#<N>)" gh pr view <N> --json body -q .body | sed "$STRIP" > /tmp/merge-pr-<N>-body.md gh pr merge <N> --squash --delete-branch \ --subject "$SUBJ" --body-file /tmp/merge-pr-<N>-body.mdAfterwards, verify the merge commit is marker-free — this exact flow has been beaten three ways (body, then subject) and the check is one line:
git log --format=%B -1 <merge-sha> | grep -nE '\[skip ci\]|\[ci skip\]|skip-checks' \ && echo "SUPPRESSED — run: gh workflow run release-please.yml --ref main" \ || echo "clean"Then clean up the worktree:
cd - git worktree remove /tmp/merge-pr-<N>
Rules
- Never skip the full test suite, even if CI is green — CI config drift is real.
- Never let a squash merge carry a
[skip ci]/[ci skip]/skip-checks:marker into main's history — it suppresses push-triggered workflows (release-please included) for the merge itself. Step 9's explicit--subject/--body-fileflow exists for exactly this; the single-commit-PR default subject is the trap that bites after the body is fixed. - Never claim a failure is "pre-existing" without the
git stashproof. - Never merge without explicit user confirmation. CI green alone is not authorization.
- Never force-push; never merge into
main/masterwith failing local tests. - Clean up temporary worktrees after merge, even on failure paths.