epic-merge
Sequential squash-merge of stacked PR chains into an epic branch.
Skill by sd0xdev
Chalk Labs
Need help deploying epic-merge?
Chalk Labs sets up agents like this inside real teams: connected to your tools, tested on your data, with a human in the loop where it matters.
Install
Works with Claude Code. Read the repository before you give any tool access to your data.
npx agentmarket install skill/sd0xdev-epic-merge
Asks which of your tools to add it to (Claude Code, Codex, Cursor, VS Code, Windsurf, Gemini CLI, Claude Desktop) and shows the change first. See https://agentmart.dev/cli.
Get the weekly diff
The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.
About
Use cases: design, devops
Reviews
Write a reviewNo reviews yet. Installed it? Reviews come from verified installers only. How reviews work
Permissions
What this skill can do and what it asks for, from its SKILL.md. Checked Oct 25, 2026.
- Tools
- No tools found
- Secrets
- None asked for
- Install scripts
- None found
Network hosts it talks to 1
codeload.github.com· found in install command
Files it touches
~/.claude/skills/epic-merge
Tools it pre-approves 8
From allowed-tools in SKILL.md: the agent may use these without asking you each time.
Bash(git:*),Bash(gh:*),Bash(bash:*),Read,Grep,Glob,AskUserQuestion,Skill
What's inside
- Skill 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.
- License
- MIT
- Allowed tools
- Bash(git:*),, Bash(gh:*),, Bash(bash:*),, Read,, Grep,, Glob,, AskUserQuestion,, Skill
- Instructions
- 2,610 lines in SKILL.md · view on GitHub
Before installing
- Pre-approves some shell commands
- Instructions read files in your home folder (~/.ssh, ~/.aws…)
Agent Skills spec issues
- body: 2610 lines (spec recommends under 500)
- files: referenced but missing: scripts/protected-branches.sh, scripts/pre-push-gate.sh
Files (1)
- SKILL.md
SKILL.md (first lines)
# Epic Merge — Stacked PR Chain Squash-Merge
**Read first**: `@skills/push-ci/references/authorization-contract.md` before any push or merge — § Push safety for the credential, the unshared attestation and the force-with-lease rules, § Efficacy Boundary for what the per-iteration approval authorizes. If that Read fails, stop and report it; push and merge nothing.
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 |
|--------|--------------------------|------------|
| The **shell** | A name pasted into a command as literal text is *evaluated*: `case "feat/x$(printf${IFS}PWNED>&2)" in` runs `printf` before the guard decides anything, and the guard then passes the branch as unprotected | **Bind once to a variable** (`head=<quoted head>`), then use `"$head"`. Expanding a variable does not re-scan for `$( )` — measured both ways |
| git's **option parser** | Quotes are consumed by the shell, so git still sees `--all` as a flag. Measured: pushing such a branch without a separator answers `Everything up-to-date` — git took the flag and pushed **every** branch, none of them the one the operator approved | The **separator**, before the ref operand |
Every `<…>` slot bound above is written `<quoted …>`: substitute a **shell-quoted** value,
single-quoted with each `'` rendered as `'\''`.
**And the separator ends option parsing, not refspec parsing — a third reader with its own
answer.** After `--`, git reads the operand as a *refspec*, where a leading `+` means "force" and a
`:` splits source from destination. `git check-ref-format refs/heads/+main` exits 0, so `+main` is a
legal branch name that the protected-head guards below compare against `main` and pass as
unprotected. Measured, with a local `main` rewound behind the remote:
```
$ git push origin -- "+main" # no force flag anywhere on this line
+ affcbe7...ad7e970 main -> main (forced update) # exit 0 — a protected branch, force-updated
```
It is not only a bypass, it is the wrong branch: with a real `+main` branch present, that form
pushed `main` and never created `refs/heads/+main` on the remote, while
`refs/heads/+main:refs/heads/+main` created it correctly and left `main` untouched. So both pushes
below name a **full `src:dst` refspec**, whose first character cannot be read as `+`. Write
`${head}` in braces — `$head:refs` is a modifier expansion in zsh and silently eats the `:refs`.
**Bind at the first use, not at the first destructive one.** Phase 0 reads the names and already
puts them on a command line, so a binding that started at Phase 2 would leave the whole analysis
step evaluating them. Every fenced block below that names a ref binds it at the top of that block —
each fence is its own shell, so nothing carries over between them.
**Which separator a command takes is measured, not assumed — and for one command neither works.**
Measured on git 2.55.0:
Subcommand names below are written **without** the `git ` prefix, so that naming a command in this
table is not mistaken for issuing it:
| Subcommand | Separator | Measured |
|------------|-----------|----------|
| `push`, `fetch`, `merge-base` | `--` and `--end-of-options` | **Both accepted, equivalent.** `--` here is a convention, not a correctness requirement |
| `branch -D` | `--` | `git branch -D -- --all` deletes the branch actually named `--all`; without the separator git answers `fatal: branch name required` |
| **`rev-parse`** | **neither** | `git rev-parse -- main` prints `--` and `main` back verbatim; `git rev-parse --end-of-options main` prints `--end-of-options` **and then** the SHA — two lines. Either way the captured value is not a SHA, and Step 7 would hand it to `/watch-ci` |
`rev-parse` is therefore solved by **`--verify --quiet` with a fully-qualified ref**, never by aFrom sd0xdev/sd0x-harness, MIT.
More from sd0xdev/sd0x-harness
- 0.713score
debug
sd0xdev
Interactive debugging workflow with hypothesis-driven probe loop.
SkillAFreshness: A★ 194Claude Code - 0.713score
contract-decode
sd0xdev
EVM contract error and calldata decoder.
SkillAFreshness: A★ 194Claude Code - 0.713score
codex-brainstorm
sd0xdev
Adversarial brainstorming via Claude+Codex debate.
SkillAFreshness: A★ 194Claude Code +9 - 0.713score
best-practices
sd0xdev
Industry best practices conformance audit with mandatory adversarial debate.
SkillAFreshness: A★ 194Claude Code - 0.713score
codex-cli-review
sd0xdev
Code review via Codex CLI with full disk access.
SkillAFreshness: A★ 194Claude Code +9 - 0.713score
codex-explain
sd0xdev
Explain complex code via Codex exec.
SkillAFreshness: A★ 194Claude Code +9 - 0.713score
ask
sd0xdev
Context-aware Q&A with auto context gathering.
SkillAFreshness: A★ 194Claude Code +9 - 0.713score
claude-health
sd0xdev
Claude Code config health check + plugin sync.
SkillAFreshness: A★ 194Claude Code +9 - 0.713score
code-explore
sd0xdev
Pure Claude code investigation.
SkillAFreshness: A★ 194Claude Code +9 - 0.713score
code-investigate
sd0xdev
Dual-perspective code investigation.
SkillAFreshness: A★ 194Claude Code +9 - 0.713score
codex-architect
sd0xdev
Codex architecture consulting.
SkillAFreshness: A★ 194Claude Code +9 - 0.713score
deep-explore
sd0xdev
Multi-wave parallel code exploration orchestrator.
SkillAFreshness: A★ 194Claude Code +9
Signals
- Stars
- 194
- Forks
- 26
- Open issues
- 5
- Last commit (this folder)
- Oct 3, 2026 (29 days ago)
- Last release
- Oct 6, 2026 (26 days ago)
From GitHub (fetched Oct 24, 2026).
Details
- License
- MIT
- Repository
- github.com/sd0xdev/sd0x-harness
- First seen
- Oct 24, 2026
- Last checked
- Oct 24, 2026
- Source
- GitHubgithub-graphql
More from sd0xdev
- 0.713score
code-investigate
sd0xdev
Dual-perspective code investigation.
SkillAFreshness: A★ 194Claude Code +9 - 0.713score
code-explore
sd0xdev
Pure Claude code investigation.
SkillAFreshness: A★ 194Claude Code +9 - 0.713score
ask
sd0xdev
Context-aware Q&A with auto context gathering.
SkillAFreshness: A★ 194Claude Code +9 - 0.713score
best-practices
sd0xdev
Industry best practices conformance audit with mandatory adversarial debate.
SkillAFreshness: A★ 194Claude Code - 0.713score
claude-health
sd0xdev
Claude Code config health check + plugin sync.
SkillAFreshness: A★ 194Claude Code +9 - 0.713score
codex-architect
sd0xdev
Codex architecture consulting.
SkillAFreshness: A★ 194Claude Code +9
Done for you
Rather not set it up yourself?
Chalk Labs scopes, builds and runs agents for your team: the task audit, the eval harness, the integrations and the weekly ops report. Fixed scope, priced before we start.
See how we build AI agents →Read the case studies
Chalk Labs is a two-founder AI and growth studio. We build AI agents, CRMs and MVPs for Web3 and AI companies, and have shipped 50+ product and token launches.
- launches shipped
- 50+
- launches shipped
- media placements
- 500+
- media placements
- founders on every account
- 2
- founders on every account
Press we've placed: Forbes · CoinDesk · Decrypt · Cointelegraph · The Block
Rahil Jain, Co-founder, Chalk Labs. Ex-Goldman Sachs engineer. You talk to the person who builds it.
Get epic-merge running in your business
Tell us what you want to automate. A founder replies within one working day with a plan, not a pitch deck.
Cite this page
epic-merge skill. Agent Mart. https://agentmart.dev/skills/sd0xdev-epic-merge (last updated 2026-10-24)