Ops Merge logo

Ops Merge

Organization
Lifecycle-Innovations-Limited
ops-merge

OPS on-demand: This skill should be used when the user asks to "merge PRs", "salvage branches", or…

Overview

PublisherLifecycle-Innovations-Limited
Repositoryclaude-ops
Skill nameops-merge
Stars
543
Forks
64
Bundled files
1
LicenseMIT
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • 1 bundled files

    Scripts, templates, and references the model can read while it works. Files are read-only and never executed.

  • Open source

    Published by Lifecycle-Innovations-Limited on GitHub. Read the source before you install it.

Installation

Install the Ops Merge AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
    https://github.com/Lifecycle-Innovations-Limited/claude-ops/tree/main/claude-ops/skills/ops-merge
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.
2

Install in another agent

Any agent that reads the Agent Skills format can use this skill — copy the folder into that agent's skills directory.

Claude Code — .claude/skills
git clone --depth 1 https://github.com/Lifecycle-Innovations-Limited/claude-ops.git /tmp/claude-ops
mkdir -p .claude/skills
cp -r /tmp/claude-ops/claude-ops/skills/ops-merge .claude/skills/ops-merge
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable Ops Merge in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads Ops Merge on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so Ops Merge is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

Runtime Context

Before executing, load:

  1. Preferences: cat ${CLAUDE_PLUGIN_DATA_DIR:-$HOME/.claude/plugins/data/ops-ops-marketplace}/preferences.json — read owner, timezone, project registry
  2. Daemon health: cat ${CLAUDE_PLUGIN_DATA_DIR}/daemon-health.json — if action_needed set, surface to user
  3. Secrets: GitHub token: env $GITHUB_TOKEN → Doppler MCP (mcp__doppler__*) → doppler secrets get GITHUB_TOKEN --plain → password manager

OPS ► MERGE

Load ops-rules before acting. Public repo (no personal data). Outbound: one draft → one approval → one send. If AskUserQuestion / Workflow are missing, follow Rule 10 in ops-rules (Hermes: numbered options / two-turn Telegram card; delegate_task).

Agent Teams support

If CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1 is set, use Agent Teams for fixer agents (Phase 3). This enables:

  • Steering fixers mid-flight if priorities change (e.g., a critical PR should be merged first)
  • Fixers can report blockers and you can redirect them without waiting for completion
  • Shared context: if fixer-A discovers a breaking change that affects fixer-B's PR, you can notify B

Team setup (only when flag is enabled, Phase 3):

TeamCreate("merge-fixers")
Agent(team_name="merge-fixers", name="fixer-[repo]", ...)

Use SendMessage(to="fixer-my-api", content="PR #2958 was just merged — rebase your branch") to coordinate.

If the flag is NOT set, fall back to standard parallel subagents with isolation: "worktree".

Pre-gathered PR data

${CLAUDE_PLUGIN_ROOT}/bin/ops-merge-scan 2>/dev/null || echo '{"prs":[],"error":"merge-scan failed"}'

Pre-gathered salvage data (orphan worktrees, branches without PRs, uncommitted/unpushed work)

${CLAUDE_PLUGIN_ROOT}/bin/ops-merge-salvage-scan 2>/dev/null || echo '{"repos":[],"error":"salvage-scan failed"}'

Your task

You are the merge orchestrator. Your job is to get every open PR across the owner's repos merged — fixing whatever blocks them first.

Parse arguments

From $ARGUMENTS:

  • --main → after all PRs merge to dev, also sync dev↔main for repos that have both branches
  • --repo <slug> → scope to one repo only (e.g., --repo your-org/my-api)
  • --dry-run → report what would happen, don't dispatch agents or merge anything
  • --force → skip the confirmation prompt before merging
  • --no-salvage → skip Phase 0 (Salvage). Behaves like the legacy PR-only pipeline.
  • --salvage-only → run Phase 0 only and stop. Useful for "find and finish all loose local work" without touching the existing PR queue yet.
  • (empty) → process all repos: salvage local work first, then merge PRs to dev only

Phase 0 — Salvage scan (run BEFORE the PR queue)

Goal: every repo in every org gets a clean slate before the PR merge pipeline runs. Find and finish every piece of local work that isn't already on dev/main and isn't already in an open PR — orphan worktrees, feature branches without PRs, uncommitted/staged/stashed changes, and unpushed commits.

Skip this phase only if --no-salvage is set.

Parse the JSON returned by ops-merge-salvage-scan. For each repo with has_salvage: true, classify each finding into one of:

FindingClassificationAction
Worktree with dirty_files > 0 or unpushed_commits > 0worktree-incompleteDispatch salvager: finish work in the worktree, commit, push, open PR if missing
Worktree on a branch with has_open_pr: falseworktree-orphan-prDispatch salvager: confirm work is complete (lint/type/test gate), push if needed, open PR
Local branch with integrated: false and has_open_pr: falsebranch-no-prDispatch salvager: review state, push if unpushed, open PR targeting integration branch
Local branch with integrated: true and has_open_pr: falsebranch-already-mergedSurface to user → [Delete local branch] / [Keep] (NEVER auto-delete — Safety Rails)
Main checkout: dirty_files > 0 or staged_files > 0 or stash >0checkout-dirtySurface to user → [Stash & continue] / [Open salvage worktree] / [Skip]. Never auto-discard.
Main checkout: unpushed_commits > 0 on a non-integration branchcheckout-unpushedDispatch salvager: push the branch, open PR if missing

Print the salvage queue:

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 OPS ► MERGE — Phase 0: Salvage Queue
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

| Repo | Location | Branch | State | Classification | Action |
|------|----------|--------|-------|----------------|--------|
| my-api | .worktrees/feat-x | feat/x | 3 dirty, 2 unpushed | worktree-incomplete | salvage |
| my-app | (main checkout) | feat/y | 5 unpushed, no PR | checkout-unpushed | salvage |
| mise | refs/heads/old-experiment | old-experiment | integrated, no PR | branch-already-merged | confirm delete |
| ... | ... | ... | ... | ... | ... |

Salvageable: N  |  Needs user input: N  |  Clean: M
──────────────────────────────────────────────────────

If --dry-run, print the queue and stop here (skip Phase 1+). If --salvage-only, run the salvage dispatch loop below, then stop (skip Phase 1+).

Confirmation gate: unless --force, use AskUserQuestion (max 4 options — Plugin Rule 1) to confirm before dispatching salvagers:

N pieces of loose local work found across M repos.

  [Salvage all N — dispatch agents]  [Let me pick which ones]  [Skip Phase 0 — go to PR queue]  [Abort]

Dispatch salvager subagents (max 5 concurrent, one repo per agent — never share the main checkout per CLAUDE.md worktree isolation rule). Use subagent_type: "general-purpose" for now (or a future salvage-fixer agent if one exists). Each salvager gets this brief:

Task: Finish and PR loose local work in <repo>
Repo path: <path from salvage scan>
Findings:
  - <classification>: <branch / worktree path> — <state summary>
  - ...

Worktree isolation: if the work lives in an existing .worktrees/* dir, work IN that directory.
If the work lives only on a local branch (no worktree), create one:
  git -C <repo path> worktree add .worktrees/salvage-<branch> <branch>

For each finding:
  1. cd into the worktree.
  2. Inspect state: `git status`, `git log <integration>..HEAD --oneline`, `git diff --stat`.
  3. **Stale-copy guard (mandatory, per candidate file before commit):**
     - Set `BASE=$(git merge-base HEAD origin/<integration_branch>)`.
     - List files you plan to salvage from the worktree.
     - For each file `<f>`:
       - If `git diff --quiet "$BASE..origin/<integration_branch>" -- "<f>"` is **false** (integration changed `<f>` since base), treat `<f>` as high risk.
       - For high-risk files, inspect both sides before staging:
         - `git diff "$BASE..origin/<integration_branch>" -- "<f>"`
         - `git diff "$BASE..HEAD" -- "<f>"`
       - Only stage hunks that are genuinely new work from the salvage branch. Do **not** stage a wholesale file replacement that drops integration-side hunks.
       - If you cannot prove the salvage branch is newer for `<f>`, mark the finding `aborted_for_review` (do not commit the file).
  4. Read recent commit messages + any TODOs/HEREs in the diff. Decide whether the work is:
       (a) complete and just needs commit/push/PR — proceed
       (b) incomplete but obvious next step — finish it
       (c) ambiguous or risky → ABORT this finding and return it for human review.
  5. If finishing work: make the smallest correct commit. Quality gate locally
     (per-repo: type-check + lint + relevant tests).
  6. Commit with a clear message. NEVER use --no-verify unless a hook is genuinely
     broken and unrelated to your change.
  7. Push: `git push -u origin <branch>` (or `--force-with-lease` if branch already remote).
  8. Open PR (only if has_open_pr=false in the brief):
       gh pr create --repo <repo> --base <integration_branch> --head <branch> \
         --title "<derived from commit messages>" \
         --body "Salvaged by /ops:merge Phase 0. <commit summary>"
  9. Return structured JSON:
       {
         "repo": "...",
         "branch": "...",
         "status": "pr_opened" | "pushed_only" | "aborted_for_review" | "failed",
         "pr_number": <int or null>,
         "pr_url": "<or null>",
         "end_sha": "<remote head>",
         "notes": "..."
       }

DO NOT call `gh pr merge` — newly opened PRs flow through Phase 1+ like any other PR.
DO NOT delete branches, worktrees, or stashes. Salvage = finish + PR, never destroy.
DO NOT touch files outside the assigned worktree.

After all salvagers return: re-run ops-merge-scan so the freshly opened PRs join the Phase 1 queue. Surface any aborted_for_review findings to the user with a brief explanation and [Open in editor] / [Skip] options.

Branches classified branch-already-merged and checkout-dirty are surfaced one-by-one to the user via AskUserQuestion (never auto-handled — see Safety Rails).

Phase 1 — Classify the PR queue

Parse the pre-gathered JSON. For each PR, it's already classified as one of:

ClassificationMeaningAction
readyCI green, approved, no conflictsMerge immediately
needs-rebasemergeable: CONFLICTINGDispatch fixer: rebase on base branch
needs-ci-fixCI failures in statusCheckRollupDispatch fixer: investigate logs, fix, push
needs-review-responsereviewDecision: CHANGES_REQUESTEDDispatch fixer: resolve comments
blockedmergeStateStatus: BLOCKED (branch protection, required reviews)Note why, skip
draftisDraft: trueSkip — not ready for merge

Print the queue:

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 OPS ► MERGE — PR Queue
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

| Repo | PR | Title | Status | Action |
|------|----|-------|--------|--------|
| my-api | #2958 | fix(migration) | ready | merge |
| my-app | #4456 | feat(apple-04) | needs-ci-fix | dispatch fixer |
| ... | ... | ... | ... | ... |

Ready: N  |  Fix needed: N  |  Blocked: N  |  Draft: N
──────────────────────────────────────────────────────

If --dry-run, stop here. Print the queue and exit.

Phase 2 — Confirm and merge ready PRs

Unless --force was passed, use AskUserQuestion to confirm before merging:

Ready to merge N PRs:
  [repo]#[number] — [title] → [base]
  [repo]#[number] — [title] → [base]

  [Merge all N now]  [Let me pick which ones]  [Dry run — don't merge]

If user picks "Let me pick", show each PR with [Merge] / [Skip] options via AskUserQuestion.

For each confirmed PR:

  1. Verify CI is still green: gh pr checks <number> --repo <repo>
  2. If green: gh pr merge <number> --repo <repo> --squash (never --admin — see "Never bypass a branch gate" below)
  3. Report: ✓ Merged <repo>#<number> to <base>

Phase 3 — Dispatch fixers for PRs that need work

HARD RULE: Fixers do NOT merge. The orchestrator merges in Phase 5 after independent verification.

Background: on 2026-05-11, sixteen parallel pr-ci-fixer spawns fabricated complete transcripts including conflict resolutions, CI polling sequences, and gh pr merge admin outputs with invented merge SHAs. Zero merges actually executed. The fix is structural: fixers no longer have merge authority OR self-reporting authority on merge state. They push, the orchestrator verifies the push and merges.

For PRs classified as needs-rebase, needs-ci-fix, or needs-review-response:

Dispatch subagents (max 5 concurrent, one repo per agent, subagent_type: pr-ci-fixer).

Each fixer agent gets this brief:

Task: Fix PR #<number> in <repo> (<classification>)
Repo path: <path from registry>
Branch: <headRefName>
Base: <baseRefName>

Pre-work: capture START_SHA = current `git ls-remote origin <headRefName>`.

For needs-rebase:
  1. Worktree: `git worktree add .worktrees/fix-<pr> <headRefName>` inside <repo path>.
  2. `git fetch origin && git rebase origin/<baseRefName>`.
  3. On conflict: resolve thoughtfully (preserve PR intent for source files,
     `--theirs` only for lockfiles). If unresolvable, ABORT and return structured failure.
  4. Quality gate locally (per repo): type-check + lint + relevant tests.
  5. `git push --force-with-lease origin <headRefName>`.

For needs-ci-fix:
  1. Worktree as above.
  2. Pull failed-check logs, diagnose, apply surgical fix.
  3. Quality gate locally.
  4. Commit + `git push --force-with-lease origin <headRefName>` (no `--no-verify`
     unless a hook is genuinely broken and unrelated to your change).

After push (every classification):
  5. Capture END_SHA = `git rev-parse HEAD`.
  6. Confirm remote: `git ls-remote origin <headRefName>` MUST return END_SHA.
     If mismatch, retry once; if still mismatched, return failure.
  7. Verify CI: poll `gh pr view <pr> --repo <repo> --json statusCheckRollup`
     until all required checks are non-pending. Capture the literal JSON output.
  8. Clean up worktree: `git worktree remove .worktrees/fix-<pr> --force`.
  9. Return the structured JSON schema defined in the pr-ci-fixer agent contract.

DO NOT call `gh pr merge` under any circumstances. Your job ends at "CI is green
on the pushed SHA." The orchestrator will independently verify and merge.

DO NOT file a tracking issue for a CI failure you could not fix — in ANY repo.
No `gh issue create`, no `github.rest.issues.create`, no cross-repo issue. If you
cannot get CI green, return the structured failure in your JSON result and STOP;
the orchestrator decides what happens next. (Rationale: emergent issue-filing on
unfixable CI produced 17+ duplicate `[TEAM] CI failure on PR #N` issues — one per
failing check, zero dedup, cross-posted into the wrong repo. A fixer's only valid
outputs are a pushed green SHA or a structured failure report — never an issue.)
If a tracking issue is ever genuinely wanted, that is the orchestrator's call and
it MUST first `gh issue list --search "PR #<n> in:title" --state open` to dedup.

Use model: "haiku" for fixer agents (matches agent definition default).

Phase 4 — Resolve surfaced conflicts

For each PR returned with status: "conflict" from a fixer agent:

  1. Display the conflict summary:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 OPS ► MERGE — Conflict in <repo>#<number>
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 Branch: <headRefName> → <baseBranchRef>
 Conflicting files:
   - <file1>
   - <file2>

<diff_summary>
──────────────────────────────────────────────────────
  1. Use AskUserQuestion (max 4 options — CLAUDE.md Rule 1):
[Accept incoming (theirs)]  [Keep current branch (ours)]  [Open manual resolution]  [Skip this PR]
  1. Based on response:
    • Accept incoming (theirs): Create worktree, rebase with git checkout --theirs . on each conflicting file, git add ., git rebase --continue, push force-with-lease
    • Keep current branch (ours): Create worktree, rebase with git checkout --ours . on each conflicting file, git add ., git rebase --continue, push force-with-lease
    • Open manual resolution: Print step-by-step instructions for the operator to resolve manually, then check in with git push confirmation before continuing the merge pipeline
    • Skip this PR: Note as unresolved-conflict, include in final report

Phase 5 — Orchestrator verification & merge

NEVER TRUST FIXER REPORTS. ALWAYS VERIFY VIA INDEPENDENT gh CALLS.

When a fixer agent returns its structured JSON, run the verification protocol below. Skip merge if ANY check fails — surface the discrepancy to the user.

Verification protocol (run for every fixer return)

For each fixer's JSON report:

  1. Parse the JSON. If the agent didn't return parseable JSON, treat as failure — do not merge.

  2. Verify push actually landed. Read claimed end_sha from JSON. Run:

    bash
    ACTUAL_REMOTE_SHA=$(git ls-remote https://github.com/<repo>.git <branch> | awk '{print $1}')

    If ACTUAL_REMOTE_SHA != end_sha, the agent fabricated or its push failed silently. Do not merge. Mark as verification_failed: push_sha_mismatch and surface to user.

  3. Verify the push is yours (defense against bot-race overwrites). Compare start_sha claimed vs git log between start and end:

    bash
    git log --pretty=format:"%H %an %s" "$start_sha".."$end_sha" | head -10

    If author isn't us OR the diff is wildly different from what the agent reported, mark as verification_failed: branch_overwritten_by_other_agent and surface.

  4. Verify CI independently. Do NOT trust the agent's ci_status field. Run:

    bash
    gh pr view <pr> --repo <repo> --json statusCheckRollup,mergeable,mergeStateStatus

    Parse statusCheckRollup. All required checks must have conclusion: SUCCESS. mergeable must be MERGEABLE. mergeStateStatus must be CLEAN (or UNSTABLE for non-required-check failures only).

  5. If all checks pass: orchestrator performs the merge. The fixer never had merge authority. Run:

    bash
    gh pr merge <pr> --repo <repo> --squash

    Never add --admin. If the merge is refused, that refusal is the gate doing its job — see "Never bypass a branch gate" below.

  6. Verify the merge actually landed. Immediately after the merge call:

    bash
    gh pr view <pr> --repo <repo> --json state,mergedAt,mergeCommit

    state must be MERGED. mergedAt must be a timestamp within the last 60 seconds. mergeCommit.oid must exist. If any of these fail, the merge silently failed — log the error and do NOT mark the task complete.

  7. Verify the merge SHA exists in the target branch.

    bash
    git fetch origin <base-branch>
    git merge-base --is-ancestor <mergeCommit.oid> origin/<base-branch>

    Exit 0 = merge SHA is in the branch. Exit non-zero = false merge (rare but real — guard against it).

  8. Only after steps 1-7 all pass, report success with the verified merge SHA.

Decision matrix after verification
Verification resultAction
All 7 checks passOrchestrator merges (step 5), verifies merge (steps 6-7), reports ✓ verified-merged
Push SHA mismatchMark fabricated_or_push_failed, surface fixer's claimed vs actual SHAs to user
Branch overwritten by other botMark race_lost, surface diff, ask user if re-dispatch or skip
CI still redMark ci_red, surface failing checks, do NOT merge
Merge call failedCapture stderr, mark merge_failed, surface to user
Merge call succeeded but mergedAt absentMark silent_merge_failure, escalate immediately
Anti-fabrication red flags (always investigate)

If you see any of these in a fixer's report, treat the entire report as suspect and run the verification protocol with extra scrutiny:

  • Reported merge SHA has suspicious structure (sequential hex like a3f91c2d...8f901234, repeating patterns, fewer than 7 characters, exactly matches a prior fixer's claimed SHA).
  • Reported CI run URL doesn't return a valid run via gh run view <id>.
  • Fixer claims "all 5 CI jobs green" but gh pr view --json statusCheckRollup shows fewer or different checks.
  • Fixer's transcript contains sleep loops in the bash output but no actual tool call delays in execution timeline.
  • Two or more fixers in the same wave return identical reported merge SHAs (impossible — each merge produces a unique commit).

Phase 6 — --main sync (only if flag is set)

For each repo that has separate dev and main branches:

  1. Check if dev is ahead of main: git -C <path> log main..dev --oneline | head -5

  2. If ahead, show the commits and use AskUserQuestion:

    [repo]: dev is N commits ahead of main:
      [commit list]
    
      [Create sync PR and merge]  [Create PR only — I'll review]  [Skip this repo]
  3. If confirmed: create sync PR: gh pr create --repo <repo> --base main --head dev --title "chore: sync dev → main"

  4. Wait for CI via the approved monitor, never a hand-rolled loop and never --watch. hooks/gh-watch-guard.sh denies both: ${CLAUDE_PLUGIN_ROOT}/scripts/ops-deploy-monitor.sh <repo> <sync-pr-number>

  5. If CI green: gh pr merge <sync-pr-number> --repo <repo> --merge (merge commit, not squash; never --admin)

  6. Pull main back into dev: git -C <path> fetch origin && git -C <path> checkout dev && git -C <path> merge origin/main --no-edit

Phase 7 — Final report

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 OPS ► MERGE COMPLETE
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Phase 0 — Salvage
| Repo | Finding | Result |
|------|---------|--------|
| my-api | feat/x worktree | ✓ pushed + PR #2999 opened |
| my-app | feat/y local-only | ✓ pushed + PR #4470 opened |
| mise | old-experiment (merged) | ⚠ surfaced for delete confirmation |

Salvaged: N pieces of work → N new PRs opened
Aborted for review: N
User-input pending: N (dirty checkouts, already-merged branches)

Phase 1–5 — PR merges
| Repo | PR | Result |
|------|----|--------|
| my-api | #2958 | ✓ merged to dev |
| my-api | #2999 | ✓ salvaged + merged to dev |
| my-app | #4456 | ✓ fixed CI + merged |
| mise | #10 | ✗ 3 critical bugs — skipped |

Merged: N PRs across M repos
Skipped: N (blocked/draft)
Failed: N (still need manual attention)

Phase 6 — Main sync
Main sync: N repos synced (dev → main → dev)
──────────────────────────────────────────────────────

Superpowers Integration

During this command's execution, invoke the following superpower skills at the specified checkpoint:

  • Checkpoint: Before the final merge decision for each PR in Phase 2 and Phase 5 (after fixer reports green).
  • Skills: superpowers:verification-before-completion + superpowers:finishing-a-development-branch
  • Why: Verification-before-completion forces evidence (CI green, tests pass) before the merge call; finishing-a-development-branch structures the merge/cleanup choice so nothing ships half-done.

Safety Rails (NEVER violate)

  • NEVER trust a fixer's claim of merge success. Always verify via gh pr view --json state,mergedAt,mergeCommit before marking complete. See Phase 5 verification protocol.

  • NEVER let a fixer call gh pr merge. Merge is orchestrator-only. Fixers push, orchestrator verifies the push, orchestrator merges, orchestrator verifies the merge.

  • NEVER merge, or open a PR against, a repo the owner is not a member of. The registry lists every locally cloned project, so a fork's upstream (facebookresearch/*, someone else's OSS) appears in the queue carrying open PRs from unrelated contributors. Those are read-only. Contributing upstream is a deliberate act the owner asks for, one PR at a time, never a pipeline sweep.

    Check push access immediately before every gh pr create, gh pr merge, and git push — in every phase (0 salvage, 2 merge, 5 verified-merge, 6 main sync):

    bash
    PERM=$(gh api "repos/$REPO" --jq '.permissions.push' 2>/dev/null || echo error)
    [ "$PERM" = "true" ] || { echo "refusing: no confirmed push access to $REPO ($PERM)"; exit 1; }

    Fail closed. Only a literal true proceeds. false, null, a network error, a renamed repo, or an empty response all refuse. The scan filtering the queue is not sufficient on its own: the queue can be stale, hand-edited, or passed in by an operator, and the step after it is a merge. Re-check at the point of action, not once at the start.

    OPS_MERGE_INCLUDE_EXTERNAL=<owner/name> authorises exactly one repo for one run, and only when the owner asked. It is a slug, never a boolean — a global on/off would re-open the whole registry to a pipeline whose next call is gh pr merge.

  • NEVER force-push to main/master

  • NEVER merge with red CI — fix root cause first

  • NEVER bypass review on PRs touching auth, payments, PII, or secrets — these require security-reviewer subagent audit before merge

  • NEVER run git reset --hard on shared branches

  • ALWAYS use worktrees for fixes (multiple agents may be active)

  • NEVER bypass a branch gate. Do not pass --admin to gh pr merge, and do not widen a ruleset, add yourself to a bypass list, or grant yourself admin to get a merge through. A refused merge is the protection working. See "Never bypass a branch gate" below.

  • ALWAYS confirm the base branch before merging. Read baseRefName from gh pr view <n> --json baseRefName in the same step as the merge. A run scoped to one branch must refuse a PR whose base is a different one — a protected branch typically carries a review gate the scoped branch does not.

  • Max 10 PRs per invocation to avoid GitHub API throttling

  • If a PR has > 50 files changed, flag it for manual review instead of auto-merging

Never bypass a branch gate

gh pr merge --admin merges past branch protection and org rulesets. It is banned in this pipeline, in every phase, on every repo. So is any other route to the same outcome: editing a ruleset's conditions, adding an actor to a bypass list, or promoting your own account to get a merge through.

Why it matters here specifically: a merge queue sweeps many repos at once, and repo governance is not uniform. Organisations commonly scope a review gate to a subset of repos via a repository custom property, and protect only some branches. The pipeline cannot see that policy from the PR alone — but the merge API can, and it enforces it. --admin throws that enforcement away silently and succeeds, so the violation is only discoverable afterwards, from the commit.

Rules:

  1. Never pass --admin (or --auto as a workaround for a refused merge).
  2. Read baseRefName in the same step as the merge and refuse any PR whose base is outside the run's declared scope. A default-branch merge and a protected-branch merge are different acts with different approvals.
  3. Treat a refusal as a result, not an obstacle. mergeStateStatus: BLOCKED with green checks usually means a required approval or an unresolved review thread. Resolve the findings, request the human reviewer, report the PR as waiting, and move on.
  4. If a merge is genuinely urgent and gated, that is the owner's decision to make, not the pipeline's. Surface it; never route around it.

A merge that landed via bypass cannot be silently undone. Report it immediately, name the commit, and say which gate it skipped.

Phase 0 (Salvage) Safety Rails

  • NEVER auto-delete a local branch, worktree, or stash — even if classified branch-already-merged. Always surface to the user via AskUserQuestion.
  • NEVER git stash drop or git checkout -- <file> or git clean in any checkout — uncommitted work is the user's, not the agent's, until they confirm.
  • NEVER auto-commit ambiguous changes. If a salvager can't tell whether work is complete, it MUST return aborted_for_review and let the user decide.
  • NEVER commit stale snapshots over integration branch progress. If integration branch and salvage branch both changed a file since merge-base, stage only provably new hunks (or abort for review).
  • NEVER share the main checkout between salvager subagents. Per CLAUDE.md worktree isolation: each agent gets its own .worktrees/salvage-<branch> dir. Sharing the main checkout causes branch-switch collisions.
  • NEVER force-push a branch the salvager didn't originate work on — salvagers may only push with --force-with-lease to branches whose tip they fetched at start of work.
  • NEVER salvage main/master/dev — those are integration branches; loose work on them surfaces to the user, never auto-pushed.
  • NEVER touch files outside the assigned worktree. Salvagers are scoped to one repo + one branch.
  • ALWAYS run the per-repo quality gate (type-check + lint + tests) before pushing salvaged work.
  • ALWAYS open a PR (not direct push to dev/main) — salvaged work flows through the same review/CI/merge gate as any other PR.

Native tool usage

Monitor — live CI watching

When waiting for CI after a fixer pushes (Phase 3-4), call the approved monitor script. A hand-rolled gh api polling loop is denied by hooks/gh-watch-guard.sh before it runs, and gh run watch is banned outright (polls every 2-5s, no tick cap):

bash
${CLAUDE_PLUGIN_ROOT}/scripts/ops-deploy-monitor.sh <owner/repo> <pr-number>

Tick cap, wall-clock deadline, 25s sleep floor and REST rate gate all live in that one script, so the interactive and hook-spawned paths cannot drift apart.

Tasks — progress tracking

Create a TaskCreate for the overall merge pipeline and individual tasks per PR. Update with TaskUpdate as each PR is fixed/merged/skipped. This gives the user a live checklist view.

WebSearch — CI failure context

When a fixer agent encounters an obscure CI failure, use WebSearch to find known issues (e.g., npm registry outages, GitHub Actions incidents, flaky test patterns).


Ledger Integration

CLAIM_KEY: gh:pr:<owner>/<repo>#<number> (e.g. gh:pr:your-org/your-repo#42)

Pre-flight skip-check

bash
CLAIM_KEY="gh:pr:<owner>/<repo>#<number>"
ledger query --claim-key "$CLAIM_KEY" --since=-PT24H

If done exists, the PR was already merged or closed this session — skip. If in_progress exists from another agent, do not attempt a concurrent merge.

Claim + resolve

bash
# Claim when beginning CI fix or merge flow for a PR
ledger write \
  --claim-key "$CLAIM_KEY" \
  --kind "merge" \
  --status "in_progress" \
  --title "Merge: <repo>#<number> — <PR title>" \
  --ttl-sec 3600

# Resolve after merge completes or is skipped
ledger write \
  --claim-key "$CLAIM_KEY" \
  --kind "merge" \
  --status "done" \
  --title "Merge: <repo>#<number> — <PR title>" \
  --context "merged|skipped: <reason>"

Additional resources

CLI detail: references/cli.md.

Bundled files

The model reads these on demand while the skill is loaded. They are exposed as readable files and are never executed.

Frequently asked questions

What does the Ops Merge AI skill do?

OPS on-demand: This skill should be used when the user asks to "merge PRs", "salvage branches", or…

Why use Ops Merge on TypingMind?

Because you install it once and use it with any model. Ops Merge is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install Ops Merge in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/Lifecycle-Innovations-Limited/claude-ops/tree/main/claude-ops/skills/ops-merge. TypingMind reads its SKILL.md and bundles its files and installs it as a skill you can enable per chat.

Which AI models can use Ops Merge?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with Ops Merge?

As many as you like. As long as a model supports skills, you can use Ops Merge with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the Ops Merge AI skill free?

Yes. It is published on GitHub by Lifecycle-Innovations-Limited under the MIT license. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇