Skip to content
Development
Skill

/epic-merge

Sequential squash-merge of stacked PR chains into an epic branch. Handles dependency-ordered rebase, collision-safe backup tags, CI monitoring (delegates to /watch-ci), and post-merge verification. Use when: merging a chain of stacked PRs into an epic branch, collapsing a linear

From plugin
sd0x-dev-flow
18899 skills16 agents5 hooks
Install
$ npx -y skills add sd0xdev/sd0x-dev-flow --skill epic-merge --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/epic-merge

Context preview

The summary Claude sees to decide when to auto-load this skill.

Sequential squash-merge of stacked PR chains into an epic branch. Handles dependency-ordered rebase, collision-safe backup tags, CI monitoring (delegates to /watch-ci), and post-merge verification. Use when: merging a chain of stacked PRs into an epic branch, collapsing a linear

SKILL.md

epic-merge.SKILL.md
name: epic-merge
description: "Sequential squash-merge of stacked PR chains into an epic branch. Handles dependency-ordered rebase, collision-safe backup tags, CI monitoring (delegates to /watch-ci), and post-merge verification. Use when: merging a chain of stacked PRs into an epic branch, collapsing a linear PR stack into per-PR squash commits, preparing an epic branch for final review. Triggers on: 'merge PRs into epic', 'squash chain', 'collapse PR stack', 'epic merge', or when user has a linear PR dependency chain (PR A -> B -> C) targeting an epic branch. Not for: single PR merge (use /create-pr + GitHub UI), simple rebase (use /smart-rebase), pre-merge analysis (use /merge-prep). Output: chain analysis table + backup tag manifest + per-iteration AskUserQuestion gate + verification log."
disable-model-invocation: true
allowed-tools: Bash(git:*), Bash(gh:*), Bash(bash:*), Read, Grep, Glob, AskUserQuestion, Skill

Epic Merge — Stacked PR Chain Squash-Merge

Sequentially squash-merge a chain of stacked PRs into an epic branch, producing one squash commit per PR for clean per-PR review on the epic. Every destructive iteration is gated by `AskUserQuestion` to keep the operator in control.

When NOT to Use

  • Single PR merge — use `/create-pr` + GitHub UI
  • Simple rebase without stacked dependencies — use `/smart-rebase`
  • Pre-merge conflict / impact analysis only — use `/merge-prep`
  • Diamond / parallel merge chains — this skill handles **linear chains only**
  • Repos that use merge-commit or rebase-merge — this skill assumes squash-merge only

Permissions

This skill is one of the explicit exceptions in `@rules/git-workflow.md` allowed to execute `git rebase --onto`, `git push --force-with-lease`, and `gh pr merge --squash`. Every destructive step is gated by `AskUserQuestion`.

| Phase | Operation | Mutates | Approval | |-------|-----------|---------|----------| | Phase 0 step 0 refresh | bounded `git fetch` (§ Phase 0 step 0) | `refs/remotes/origin/*` + `.git/FETCH_HEAD` | No (local, bounded, recoverable) | | Phase 1 backup | `git tag -f` | local refs only | No (no remote / non-recoverable mutation) | | Phase 2 iteration | rebase + force-push + `gh pr merge` | local + remote | **Yes** — single bundled gate per iteration (or per-step with `--per-step`) | | Phase 3 verify | `git log` | none | No (read-only) |

**`--dry-run` outputs the plan and performs exactly one bounded local *operation*** — enumerated rather than implied, because every earlier phrasing was a promise the flag did not keep. "Skips all destructive steps" let Phase 1 run unconditionally, so a dry run force-updated every `backup/pr-*` tag and left a manifest per PR in the working tree. The repair overcorrected: it said the run "writes nothing" while still running `git fetch origin`, which is not a read and is not confined to `origin/*` — see § Phase 0 step 0. And "one bounded **write**" was still too strong: a refspec bounds which *refs* a fetch may update, not what else fetching does.

| Under `--dry-run` | Behaviour | |-------------------|-----------| | Phase 0 step 0 refresh | **Runs.** The one mutating operation a dry run keeps: the bounded fetch of § Phase 0 step 0, whose **ref updates** are bounded to `refs/remotes/origin/*` plus `.git/FETCH_HEAD` — its other writes are not, see § `--dry-run` residue below. Skipping it would print a plan derived from stale refs, and a wrong plan is worse than a refreshed `origin/*` | | Phase 0 analysis | Runs — read-only, over the refs step 0 has just refreshed | | Phase 1 `git tag -f "backup/pr-*"` | **Skipped.** Printed as a command, not executed | | Phase 1 manifest files | **Skipped.** The `git log` runs, but its output goes to the report instead of `$(git rev-parse --git-path epic-merge)/expected-pr-*.manifest`, so no file is created and none of a previous run's are overwritten | | Phase 2 iteration | **Skipped entirely** — no gate is asked, no rebase, no push, no merge. The commands are printed | | Phase 3 verification | Skipped — there is nothing to verify |

The residue, stated because a dry run that leaves a trace should say where. **Refs**: `refs/remotes/origin/*` and `.git/FETCH_HEAD` — that set is what the explicit refspec bounds. **Objects and metadata**: a fetch that finds new commits downloads them into the object database, and depending on configuration git may run auto-maintenance or rewrite the commit-graph on its way out; in a shallow clone it may update the shallow metadata too. None of that is destructive and none of it is bounded by a refspec, which is why the promise above is "one bounded operation" and not "one bounded write". Pass `--no-auto-maintenance --no-write-commit-graph` to suppress the maintenance half; the downloaded objects are inherent to fetching and remain. **Nothing changes on the remote.**

Core Concept

After squash-merging PR N, the original commits are replaced by a single squash commit on epic. PR N+1 still contains N's original commits as its base — these must be cut via `git rebase --onto` before merging N+1. The cut point is the original tip of PR N's branch, captured in Phase 1 as a backup tag.

epic:    E ─── S_N (squash of PR N)
PR N+1:  E ─ A1 ─ A2 ─ ... ─ B1 ─ B2
              └── drop (in S_N) ──┘ └─ keep ─┘

After: git rebase --onto origin/epic backup/pr-<N> PR_N+1
epic:    E ─── S_N ─── B1' ─ B2'

Names in commands

**A PR head branch name is not display text.** It arrives from GitHub, and `git check-ref-format` accepts far more than the names people type: measured, `refs/heads/feat/x$(printf${IFS}PWNED>&2)` and `refs/heads/--all` both pass, `git update-ref` creates both, and `git clone` carries both to every copy of the repository. `git switch -C` refuses to *create* such a name, which is why they look impossible — creation is not how they arrive.

Two separate readers, and each needs its own answer:

| Reader | What a hostile name does | The answer | |--------|--------------------------|----------

Read more
Ships withsd0x-dev-flow

Language: English | 繁體中文 | 简体中文 | 日本語 | 한국어 | Español The harness layer for Claude Code. Let the model choose the path. Keep "done" verifiable. Full control plane on Claude Code. Skills-only distribution for Codex CLI and other compatible agents.

Get the whole plugin

Other skills on sd0x-dev-flow.