Use when merging a pull request that has cleared the SDLC pipeline. Triggered by 'merge this PR', 'do-merge', or automatically by /sdlc at the MERGE stage.
Scanned 9/2/2026
Install to Claude Code
npx -y skills add tomcounsell/ai --skill do-merge --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Do Merge?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/tomcounsell-do-merge)More formats (shields.io, HTML) on the badges page.
---
name: do-merge
description: "Use when merging a pull request that has cleared the SDLC pipeline. Triggered by 'merge this PR', 'do-merge', or automatically by /sdlc at the MERGE stage."
---
# Do-Merge (Deterministic Merge Gate)
You perform the **terminal SDLC merge gate**: verify a PR is genuinely finished,
authorize the merge through the merge-guard hook, squash-merge it, and clean up.
This skill is portable — it runs in any repo, not just `~/src/ai`.
The gate is deterministic: every precondition is a checkable fact (PR state, CI
rollup, review verdict, issue link). If any precondition fails, the skill
refuses to merge, surfaces a clear reason, and does NOT call the merge command.
## Repo Context Probe
If `docs/sdlc/do-merge.md` exists, read it and honor its declarations; otherwise use the generic defaults described below.
This addendum is where a repo layers SDLC automation onto the generic `git`/`gh`
gate: a stage/verdict substrate (stage markers, recorded REVIEW verdicts), a
merge-authorization hook, extra deterministic gates (lint, lockfile, full
suite), plan migration, and post-merge cleanup/restart. When the file is absent
(the common case in a foreign repo), this skill runs entirely on `git` and `gh`
— no repo-specific tooling required.
If the addendum declares a **shared deterministic merge predicate** (a single
command that evaluates the whole gate and returns structured pass/fail legs),
run that command and honor its result in place of hand-assembling the
equivalent checks — it is the same predicate the repo's merge-guard hook
enforces, so evaluating anything else invites drift. The repo-specific command
lives in the addendum, never in this body.
## Variables
PR_ARG: the PR number to merge (e.g. `42` or `#42`). Strip any leading `#`.
If PR_ARG is empty, resolve it from the conversation context. If the repo-context
file declares a pipeline-state tool, use it to recover the PR number. If it still
cannot be resolved, STOP and ask the caller for the PR number.
## PRs That Did Not Originate in the Pipeline
A dependabot bump, a hand-authored bug fix, and a follow-up filed from another
PR's review findings all reach this gate without a plan document behind them.
They run **every step below, unchanged**: an issue link, a real review, and a
recorded completion. There is no exemption path, because an exempted PR is a PR
that merged without anyone confirming it was ready.
What such a PR needs instead is a way to say, truthfully, that the planning
stages never ran. If the repo-context file declares a stage substrate, it names
the command for recording a stage as *not applicable*; run it before the review
step so the substrate's ordering checks have an honest answer instead of a
missing one. Absent a substrate there is nothing to record and nothing to do.
Detect the dependabot shape by including `author` and `labels` in the Step 1
`gh pr view` call — `author.is_bot == true` with `author.login` matching
`app/dependabot` or `dependabot`, plus a `dependencies` label. Two behaviors are
specific to it:
> **mergeable "UNKNOWN":** GitHub computes mergeability asynchronously. If
> `mergeable == "UNKNOWN"`, wait 8 seconds, re-fetch once, and check again. If
> still `"UNKNOWN"` after the retry, FAIL the gate and ask the user to try again
> shortly. Never treat `"UNKNOWN"` as a pass.
> **PR-body edits do not survive a force-push.** Dependabot rewrites its own PR
> body when it rebases, silently dropping a `Closes #N` line added by hand. If
> you edited the body to add the issue link, re-read it immediately before Step 3
> and re-apply the edit if the rebase ate it.
## Step 0: Stage Marker (only if the context file declares a substrate)
If the repo-context file declares a stage-marker substrate, write an
`in_progress` marker for the MERGE stage now, following its exact invocation and
degraded-mode handling. This lets a forked sub-skill announce degraded mode
instead of silently lagging state. The gate itself depends only on `gh`, never
on the substrate, so a missing or degraded substrate never blocks the merge.
**A missing context file is not proof there is no substrate (issue #2419).** If your prompt carries a run identity (a `run_id` to pass on state writes) and the `sdlc-tool` CLI is on PATH, a supervisor is tracking this run and will otherwise hand-backfill whatever you skip:
```bash
sdlc-tool stage-marker --stage MERGE --status in_progress --issue-number {issue_number} --run-id {run_id}
```
Report a failed write; the gate depends only on `gh`, so never block the merge on it.
Only when there is no run identity and no `sdlc-tool` — a genuinely standalone merge — skip this step.
## Step 1: Verify PR State
Read the PR's full state via `gh`:
```bash
gh pr view {PR} --json state,mergeable,mergeStateStatus,statusCheckRollup,body,headRefName
```
ALL of the following must hold, or the gate FAILS:
1. **`state == "OPEN"`** — a merged/closed PR is not mergeable.
2. **`mergeable == "MERGEABLE"`** — GitHub reports no conflicts.
3. **`mergeStateStatus == "CLEAN"`** — no blocking branch-protection state.
(`BLOCKED`, `BEHIND`, `DIRTY`, `UNSTABLE` all FAIL.)
4. **CI green** — every entry in `statusCheckRollup` has
`conclusion == "SUCCESS"` (an empty rollup means no required checks — treat
as pass only if branch protection does not require checks).
If any check fails, STOP: report exactly which precondition failed and its
observed value. Do NOT create the auth file. Do NOT call merge.
> Conflict resolution is explicitly OUT OF SCOPE. The gate verifies
> `mergeable`/`CLEAN` and stops; it never rebases, force-pushes, or resolves
> conflicts.
## Step 2: Verify Review Approved
Confirm the PR has an approving review. **Generic baseline** — read GitHub's
native review decision:
```bash
gh pr view {PR} --json reviewDecision
```
`reviewDecision == "APPROVED"` passes. `CHANGES_REQUESTED`, `REVIEW_REQUIRED`,
or an empty decision (no review) FAILS the gate — route back to review/patch, do
not merge.
If the repo-context file declares a recorded-verdict substrate (e.g. an SDLC
REVIEW verdict), use it as the authority instead, following its exact
invocation. The verdict text must contain `APPROVED` (case-insensitive); any
other value or no verdict FAILS.
Whichever source is used: if review approval cannot be confirmed, FAIL closed —
never merge an unconfirmed-review PR.
If the repo-context file declares a DOCS stage-completion substrate, treat
DOCS-stage completion as a first-class precondition here alongside the REVIEW
verdict, following its exact invocation (the deterministic gate lives in that
substrate addendum, not this global skill). When no such substrate exists, DOCS
completion cannot be verified at merge time, so emit this announced non-gate
advisory line to the merge log (an auditable advisory, NOT a silent pass) and
proceed on supervisor sequencing:
`"DOCS-completion gate: NOT ENFORCED — no substrate; DOCS completion cannot be verified here, merge relies on supervisor sequencing (see #1915)."`
## Step 3: Verify Issue Link
The PR body (from Step 1's `body`) must contain a `Closes #{issue_number}` (or
`Closes #N` / `Fixes #N` / `Resolves #N`) line that links the tracking issue,
so the issue auto-closes on merge. If absent, STOP and report — the PR is not
correctly linked to its issue.
## Step 4: Authorize and Merge
Only after Steps 1-3 all pass:
1. **Satisfy any merge-authorization guard the repo-context file declares.** If
the repo gates `gh pr merge` behind a merge-guard hook that requires an
authorization file, create it now exactly as the context file specifies, and
delete it immediately after the merge (success or failure) so a stale auth
file never lingers. In the generic case there is no guard — skip straight to
the merge.
2. **Squash-merge** the PR:
```bash
gh pr merge {PR} --squash
```
3. **Clean up** any authorization file created in sub-step 1, on every path.
If the merge command itself fails (e.g. a race where branch protection changed
between Step 1 and now), report the failure, ensure any auth file is removed,
and do NOT retry blindly.
## Step 5: Record Completion
If the repo-context file declares a stage-marker substrate, mark the MERGE stage
`completed` now (no-op / degraded marker if the substrate is absent is fine).
Absent a context file, apply the same Step 0 detection: with a run identity and
`sdlc-tool` on PATH, write `--stage MERGE --status completed` yourself. Only a
genuinely standalone merge skips this — there the merge itself is the completion
signal.
## Step 6: Apply Repo-Specific Addenda
If the repo-context file (read in the Repo Context Probe) declares additional
gate steps — extra lint gates, lockfile sync, full-suite runs, documentation
gates, plan migration, worktree cleanup, post-merge restarts — apply them now,
in addition to the deterministic gate above. The addendum is additive — it never
relaxes the verify-then-merge contract. In the generic case there is no addendum
and the merge is already complete.
Run every addendum step **in-turn, synchronously** (issue #2051): execute each
gate command (including a full test suite) to completion and read its result
within your current turn. If a long command must be backgrounded, poll it
in-turn with repeated status checks until it exits, then act on the result in
the same turn. Before waiting on anything, verify a live producer exists that
will complete it — this skill often runs as a fork with exactly one turn, and
no completion event, monitor notification, or scheduled wake-up will ever
arrive after the turn ends. The proven pattern is start → poll in-turn → read
result → act, all in one turn.
## Critical Rules
- **Never bypass the gate.** The auth file is created ONLY after every
precondition passes. Creating the repo's authorization file without running
the gate defeats the entire mechanism.
- **Fail closed.** Any unconfirmed precondition (unknown CI state, missing
review verdict, unresolved mergeability) is a FAIL, not a pass.
- **Clean up the auth file** on every path — success, gate failure after
creation (should not happen, but defensive), or merge-command error.
- **No conflict resolution.** Out of scope; the gate stops at `mergeable`.
- **Work in-turn, synchronously.** Poll every gate command to completion within
your current turn and record the outcome before the turn ends (issue #2051);
verify a live producer exists before waiting on anything.
## OUTCOME Contract Emission
As the last line of your final response, emit an OUTCOME contract:
- **Merged**: `<!-- OUTCOME {"status":"success","stage":"MERGE","artifacts":{"pr_url":"<URL>"}} -->`
- **Gate refused / merge failed**: `<!-- OUTCOME {"status":"fail","stage":"MERGE","artifacts":{}} -->`
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!