ready-to-merge — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited ready-to-merge (Agent Skill) and scored it 96/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 1 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 1 flagged
The text {match} tells the agent to skip the normal "ask the user first" gate. Used adversarially it removes the human-in-the-loop check before destructive or sensitive actions, turning a normally-gated agent into a fire-and-forget executor.
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Verify a PR is safe to merge, present a terse readout, and — only after explicit user approval — squash-merge it.
This skill is a synthesis layer: it calls into /pr-status's scripts, optionally /contract-check, and reuses /review-pr's view/diff scripts. It does NOT reimplement those skills.
status (summary only)./contract-check flags stale or uncommitted pacts./ready-to-merge # PR for current branch
/ready-to-merge 5753 # explicit PR number
/ready-to-merge --no-merge # report only, never offer to merge# If no number given:
~/.claude/skills/review-pr/scripts/gh-pr-current-number.sh
# Fallback: gh pr view --json number --jq '.number'Resolve owner/repo from git remote get-url origin.
Run these in parallel — they're independent fetches:
~/.claude/skills/pr-status/scripts/gh-pr-details.sh {owner} {repo} {pr} ~/.claude/skills/review-pr/scripts/gh-pr-view.sh {pr} ~/.claude/skills/review-pr/scripts/gh-pr-diff.sh {pr} gh pr list --base {head-branch} --state open --json number,title,headRefName,url git status --porcelain
~/.claude/skills/ready-to-merge/scripts/git-unpushed.sh # unpushed commitsUncommitted/unpushed work is a hard blocker. Use the wrapper for the unpushed-commits check — never inline git log @{u}..HEAD. The sandbox flags the bare @{u}..HEAD as brace expansion, so it can never be auto-approved.
Extract the Jira key from branch name first, then PR title (/[A-Z]+-\d+/).
If found:
mcp__jira__jira_get
path: /rest/api/3/issue/{key}
jq: "{key: key, summary: fields.summary, status: fields.status.name, issuetype: fields.issuetype.name}"Flag if Jira status is not one of: Code Review, In Progress, Test/Review, Ready for Test. Specifically:
Done → ⚠️ already closed before merge — unusual, confirm with user.Backlog / Ready to Work → ⚠️ ticket never moved to In Progress — flow drift.Blocked → ❌ confirm before merging.Beads: if bd is available, look for a bead referencing this Jira key:
bd list --status=in_progress
bd list --status=openMatch by Jira key in title or description. Note id + status. If a bead is in_progress for this work it will need closing after merge.
Trello: only if ./scripts/trello-api exists in the repo. Look for a card matching the branch or ticket — best effort, skip silently if none found.
Walk the changed files list (don't re-read the whole diff). Flag only what's present — quiet success, loud risk:
| Risk | Detection (heuristic) |
|---|---|
| Migration / schema change | path matches migrations/, migrate/, *.sql, schema.prisma, alembic/ |
| Dependency change | package.json, package-lock.json, pnpm-lock.yaml, yarn.lock, requirements.txt, Cargo.lock, go.mod, Gemfile.lock, build.sbt |
| Secrets / sensitive | .env*, *.pem, *.key, files matching credential / secret |
| CI / infra changes | .github/workflows/*, Dockerfile*, terraform/, helm/ |
| Public-API delete | grep diff for removed export , pub fn, public def |
| Feature flag absent | new behaviour added but no flag toggle — flag only if PR description claims to be gated |
| Leftover debug | console.log, dbg!, println!, pp , binding.pry, debugger added in diff |
| Lingering TODOs | new TODO/FIXME/XXX lines added |
| Test/code ratio | non-trivial code change (≥50 LOC added in non-test files) with zero new/modified test files → flag low coverage |
Skip rows that have nothing to report — only show risks that fired.
Compute three buckets:
isDraft: truereviewDecision != APPROVEDSUCCESSmergeState is DIRTY (conflict), BLOCKED, or BEHINDmain/master (PR is stacked — should be merged via parent, not directly)BlockedmergeState: UNKNOWN — GitHub hasn't computed mergeability yet; usually transient. Suggest a retry before merging.Backlog, Done, missing)in_progress (will need closing post-merge)/review-pr for depthOnly if pacts are present in the repo (./scripts/contract-check exists, or **/pacts/ directory present, or *Consumer*.scala/*.consumer.test.* files exist):
Skill /contract-check statusAdd a row to the gate table for Contracts. If WARN or FAIL, move to Warnings (don't auto-block — contract drift rarely blocks a single PR merge but should be visible).
Do NOT auto-run /contract-test — it's the runner (generate → sync → verify) and is redundant with CI, which already ran on push. If /contract-check surfaces stale, uncommitted, or sync-gap pacts, recommend /contract-test (or /contract-test sync / full) as the remediation under Risks, and let the user run it before re-invoking /ready-to-merge.
The user squash-merges by default. Pre-draft the squash commit so they can approve or tweak:
Jira: {KEY} line if a ticket is linked.Keep the body under ~10 lines. The user can edit if they want more.
Render compactly. Skip empty sections.
## PR #{number} — Ready-to-Merge Check
**{title}**
[#{number}]({url}) · `{head}` → `{base}` · +{additions} / -{deletions} across {changedFiles} files
| Gate | Status |
|--------------|-------------------------------------|
| CI | ✅ / ❌ / ⏳ |
| Approvals | ✅✅ alice, bob (or 🔔 awaiting) |
| Threads | ✅ 0 / 💬 N unresolved |
| Sync w/ base | ✅ clean / ❌ behind / 💥 conflict |
| Mergeable | ✅ / ❌ {reason} |
| Contracts | ✅ / ⚠️ / — |
| Tests | ✅ N test files touched / ⚠️ none |
**Linked work**
- Jira: [{KEY}]({url}) — {summary} _(status: {status})_
- Bead: `{bead-id}` — {status}
- Trello: {card-name} (or `—`)
**Stacked children** _(omit section if none)_
- ⚠️ #{n} {title} — will need `/rebase-merged-parent` after this merges.
**Diff summary**
- 3–5 bullets, each one short, derived from the diff. No file lists.
**Risks** _(omit section if empty — do not pad with "no secrets" etc.)_
- ⚠️ Migration added `db/migrate/20260511_xxx.sql` — verify backwards compatible.
- ⚠️ 87 LOC added without new tests in `src/foo.ts`.
**Drafted squash commit**{subject}
Jira: {KEY}
### Verdict
❌ Blocked — {reason}
or
⚠️ Mergeable with warnings — review risks above.
or
🚀 Ready to squash-merge.If verdict is ❌ blocked, stop here. Print the blockers and suggest next steps (often /rebase-main, /review-comments, /clean-code).
If --no-merge was passed, stop here regardless.
Otherwise ask the user via AskUserQuestion:
Question: Squash-merge PR #{n} now? Options: - Yes — merge with drafted commit (recommended when verdict is 🚀) - Edit commit message first — user supplies new subject/body, then re-prompt - Wait — print no further action, end the skillOnly on explicit "Yes". Use the GitHub CLI's squash mode with the drafted (or edited) commit.
Do NOT pass `--delete-branch`. It makes gh run local git operations (switch to the default branch, pull, delete the local branch) as a side effect of the merge. In a worktree checkout — where main/master is checked out in a different worktree — that local step fails with fatal: '{branch}' is already used by worktree at ... even though the remote merge succeeded. The merge is done; only the local cleanup errored, which reads as a scary failure for a no-op. Branch deletion is handled separately below, against the remote only.
gh pr merge {number} --squash \
--subject "{subject}" \
--body "$(cat <<'EOF'
{body}
EOF
)"Idempotency: if the command prints Pull request ... was already merged (e.g. a prior attempt merged remotely before erroring on local cleanup), treat that as success — do not retry or alarm. Confirm with gh pr view {number} --json state --jq .state (expect MERGED) if unsure.
After the merge succeeds, delete the remote branch separately — this is a pure remote ref delete with no local git involvement, so it's worktree-safe. But check it still exists first — many repos auto-delete the head branch on merge, and skipping the DELETE avoids both a wasted call and a permission prompt:
gh api "repos/{owner}/{repo}/branches/{head-branch}" --jq '.name'
# 404 → auto-deleted on merge; report "Remote branch auto-deleted." and skip the DELETE
gh api -X DELETE "repos/{owner}/{repo}/git/refs/heads/{head-branch}"The DELETE is best-effort: it may be denied by branch permissions/SSO. On any error, don't retry — just note that the remote branch wasn't deleted and add it to the follow-ups. A local permission denial (the harness/user declining the Bash call) is the user saying no — treat exactly like any other failure: never re-run the same command, note it, move on. Never delete the local branch or switch worktrees yourself.
After merge succeeds, print:
✅ Merged #{number}.{branch-note}where {branch-note} is Remote branch deleted. on success, Remote branch auto-deleted. when the pre-check 404'd, or (remote branch not deleted — {reason}; delete manually if wanted.) otherwise.
Then list post-merge follow-ups as a checklist (do NOT execute them):
bd close {bead-id} if a bead is still in_progress/rebase-merged-parent on stacked child PR(s): #{n1}, #{n2}Don't auto-perform these — they're explicit user follow-ups, often touching other branches/repos. In particular, do not switch the local checkout to the default branch or run git worktree/git branch -d yourself.
AskUserQuestion answer must be "Yes" — anything else (silence, "Wait", a clarifying comment) means do not merge.gh calls./create-pr first._Jira MCP not configured._ and continue.? rather than aborting. Surface the failed fetch under Risks so the user knows the check was incomplete.--delete-branch local-cleanup failure (Phase 10) — the remote merge already succeeded. Don't pass --delete-branch; verify with gh pr view {number} --json state (expect MERGED) and delete the remote branch via the API call in Phase 10. Never resolve it by switching worktrees or deleting local branches.~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.