Use when breaking a too-big branch into a stack of smaller chained PRs — commit-based default, --smart agent-reshape, gh-stack detection, cross-links. Invoked by $orc-stack-pr and $orc-ship's size gate.
Installs into .claude/skills of the current project.
Sponsors
SSkills DirectorySkills Directory
Know which skills are safe — weekly.
SSkills DirectorySkills Directory
Is that skill safe to install?
SSkills Directory ProSkills Directory
Get any skill into Claude in one click.
Are you the author of Stack Pr?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/higoralves-stack-pr-orc)
---
name: stack-pr
description: "Use when breaking a too-big branch into a stack of smaller chained PRs — commit-based default, --smart agent-reshape, gh-stack detection, cross-links. Invoked by $orc-stack-pr and $orc-ship's size gate."
---
# Stack PR
## Overview
A stack is N chained PRs where each branch's base is the previous branch (not `main`). Reviewers approve incrementally, merging bottom-up. orc supports two construction strategies and one tool integration:
- **Strategy A — commit-based (default)** — every commit on the branch becomes one PR. Requires clean Conventional Commits.
- **Strategy B — smart (`--smart`)** — an analyzer agent reads the diff, proposes logical slices, and emits a non-interactive rebase plan. Higher risk, requires backup branch.
- **Tooling** — `github/gh-stack` `gh` extension if installed; otherwise a plain-`gh` fallback that does the same chaining manually.
**Announce at start:** "I'm using the stack-pr skill to construct a stacked PR group."
## Preconditions (both strategies)
1. **Tests pass** — invoke `orc-verification-before-completion` first.
2. **Clean working tree** — `git status --porcelain` must be empty.
3. **No merge commits** on `base..HEAD` — `git rev-list --merges base..HEAD` must be empty. Stacks fall apart through merges.
4. **At least 2 commits** on `base..HEAD`. A single commit isn't a stack — use `$orc-ship`.
5. **Branch is pushed-clean** — local matches `origin` (no unpushed work the rebase could lose).
If any precondition fails, surface a **⚠️ Caution** callout naming the failed precondition and **stop**. Don't try to "fix it for them" — these are user decisions.
## Strategy A — commit-based (default)
### Validate Conventional Commits
Each commit subject MUST match:
```
^(feat|fix|refactor|chore|test|docs|perf|build|ci|style)(\([^)]+\))?!?: .+
```
If any commit fails the regex, surface the offenders and `the available user-question tool (or ask in chat)`:
1. **Reword via reword-only rebase** — non-interactive (`GIT_SEQUENCE_EDITOR` writes a todo of `reword` lines; user is prompted for each subject one by one outside the rebase).
2. **Switch to `--smart`** — the analyzer agent doesn't care about commit subjects; it works from the diff.
3. **Cancel.**
### Build the stack
For each commit `Cᵢ` on `base..HEAD` in chronological order:
```bash
slug_i=$(echo "$subject" | sed 's/^[a-z]*([^)]*): *//' | tr ' ' '-' | tr -cd '[:alnum:]-' | tr A-Z a-z | cut -c1-40)
branch_i="${original_branch}/$(printf '%02d' $i)-${slug_i}"
```
Branch naming: zero-padded position keeps `git branch` output sortable. Suffix is the slugified subject — readable at a glance.
Reshape:
```bash
# Backup first (cheap, life-saving)
git branch "${original_branch}-pre-stack-backup"
# Reset to base, then replay commits onto sequentially-stacked branches
git checkout "$base"
git checkout -b "$branch_1"
git cherry-pick "$C1"
# C1 is now on branch_1 atop base
git checkout -b "$branch_2" # branches off branch_1 (current HEAD)
git cherry-pick "$C2"
# ... and so on
```
The original branch is preserved as the backup. Caller may delete or keep it.
### Push & open PRs
If `gh-stack` is installed (see "Tooling detection"):
```bash
gs push # pushes every layer
gs submit --base "$original_base" # creates the chained PRs
```
Otherwise, plain-`gh` fallback (per-branch loop):
```bash
for i in 1..N; do
prev_branch=$([ $i -eq 1 ] && echo "$original_base" || echo "$branch_$((i-1))")
git push -u origin "$branch_$i"
pr_url=$(gh pr create \
--base "$prev_branch" \
--head "$branch_$i" \
--title "[$i/$N] $subject_i" \
--body "$body_i_placeholder")
echo "$pr_url" >> /tmp/orc-stack-urls.$$
done
```
### Two-pass body decoration
PR #1 cannot reference PR #2's URL at creation time (#2 doesn't exist yet). So all bodies are written with a placeholder, then rewritten in pass 2 once every URL is known.
Default (terse) block:
```
## Stack ({i}/{N}, id: {stackId})
1 api#311 · 2 api#312 (this) · 3 ui#447
order: bottom-up
```
`--verbose` variant (only when the caller shipped with `--verbose` bodies):
```markdown
## Stacked PR ({i}/{N})
Stack: `{stackId}`
Merge bottom-up — each merge retargets the next PR's base. See [references/STACKED-PRS.md](references/STACKED-PRS.md).
| # | Subject | PR | Status |
|---|---------|----|--------|
| 1 | feat(api): add row stream | org/api#311 | parent |
| 2 | feat(api): wire export job | org/api#312 | **this** |
| 3 | feat(ui): download button | org/ui#447 | child |
```
Update each PR via `gh pr edit <url> --body "<rewritten>"`.
### Metadata writeback
Append entries to the active session's `linkedPRs[]` in `${ORC_STATE_DIR}/orc.json`:
```json
{
"repo": "api",
"url": "https://github.com/acme/api/pull/311",
"stackId": "<sessionId>-api",
"stackPosition": 1,
"stackedOn": null
}
```
`stackedOn` for position 1 is `null` (it's stacked on the base branch, not on another PR). For positions 2..N, it's the URL of position N-1.
`stackId = <sessionId>-<repo>` in workspace mode; `<sessionId>` alone in single-repo. Stable across reshape — re-stacking the same branch reuses the same `stackId`.
## Strategy B — `--smart`
Use when commits are messy (multiple WIP commits, fixups, no clean Conventional Commits) but the diff still has natural seams (refactor → schema → API → UI).
### Backup is non-negotiable
```bash
git branch "${original_branch}-pre-stack-backup"
```
The skill MUST refuse to proceed without this branch existing. On any error, surface the recovery command in a **🛑** danger callout (see "Recovery" below).
### Dispatch the analyzer
```
Agent: orc-stack-analyzer
Inputs:
branch: <original_branch>
base: <base>
diff: `git diff base...HEAD`
log: `git log --reverse --format='%h %s%n%b' base..HEAD`
numstat: `git diff --numstat base...HEAD`
```
Expected JSON output (analyzer never runs anything destructive):
```json
{
"slices": [
{
"position": 1,
"name": "Refactor: extract row-stream interface",
"rationale": "Pure refactor with no behavior change. Lands first to keep API slice small.",
"files": ["api/src/stream/RowStream.ts", "api/src/stream/index.ts"],
"est_loc": 80,
"commits_to_include": ["a1b2c3d", "e4f5g6h"]
},
{
"position": 2,
"name": "feat: stream export job through new interface",
"files": ["api/src/jobs/ExportJob.ts", "api/test/ExportJob.test.ts"],
"est_loc": 140,
"commits_to_include": ["i7j8k9l"]
}
],
"rebase_plan": [
{ "branch": "feat/x/01-refactor-extract-row-stream", "base_ref": "<base>", "cherry_pick": ["a1b2c3d", "e4f5g6h"] },
{ "branch": "feat/x/02-feat-stream-export-job", "base_ref": "feat/x/01-refactor-extract-row-stream", "cherry_pick": ["i7j8k9l"] }
],
"warnings": [
"Commit `e4f5g6h` touches files in slice 2 — moving the slice-2 lines via interactive rebase is recommended."
]
}
```
### Preview & confirm
Render the preview headline and any analyzer warnings as a separate **⚠️ Caution** callout, then ask with the slice table + rebase plan as the **Apply** option's `preview` (`orc-gates` §3; never inside the callout):
```markdown
> **📋 Preview — stack plan**
>
> <N> slices from <M> commits; est. <total> LOC across the stack.
```
`the available user-question tool (or ask in chat)` (header `Stack plan`):
1. **Apply (Recommended)** — execute the rebase plan exactly as proposed. `preview`: the slice table + rebase plan.
2. **Edit** — open the JSON in `$EDITOR` for manual tweaks; re-validate.
3. **Cancel.**
### Execute the rebase plan
For each entry in `rebase_plan`:
```bash
git checkout "$base_ref"
git checkout -b "$branch"
for sha in "${cherry_pick[@]}"; do
git cherry-pick --no-edit "$sha" || exit 1
done
```
On cherry-pick failure, surface the danger callout with the abort commands fenced below (same shape as `$orc-stack-pr`'s Strategy-A failure path):
```markdown
> **🛑 Cherry-pick of <sha> failed**
>
> Your original branch is safe in `${original_branch}-pre-stack-backup`.
```
```
git cherry-pick --abort && git reset --hard ${original_branch}-pre-stack-backup
```
**Never** use `git rebase -i` — only the non-interactive composition above. The analyzer is responsible for ordering commits to minimize conflicts; on any conflict, abort and surface the recovery callout.
After the reshape, push + open PRs identically to Strategy A (Pass 1, then Pass 2 body rewrite).
## Tooling detection
```bash
if command -v gh-stack >/dev/null 2>&1; then
USE_GHSTACK=1
elif gh stack --help >/dev/null 2>&1; then
# Installed as a gh extension; invoke via `gh stack` (or the `gs` alias if configured).
USE_GHSTACK=1
GHSTACK_CMD=(gh stack)
else
USE_GHSTACK=0
fi
```
If absent, `the available user-question tool (or ask in chat)`:
1. **Install** — `gh extension install github/gh-stack`. Run, then continue.
2. **Use plain-gh fallback** — chain via `gh pr create --base <prev-branch>`. No extension needed; works everywhere `gh` works.
3. **Cancel.**
**Never auto-install.** Tools that mutate the user's environment require explicit consent every time.
## Recovery (always print on error)
If any step fails after the backup branch exists, print the danger callout, then the recovery commands in a fence:
```markdown
> **🛑 Stack construction failed at <step>**
>
> Your original branch is safe in `${original_branch}-pre-stack-backup`. Recover with the commands below.
```
```
git checkout "${original_branch}-pre-stack-backup"
git branch -D "${original_branch}"
git branch -m "${original_branch}-pre-stack-backup" "${original_branch}"
# then delete any partial stack branches:
git branch | grep "^ ${original_branch}/" | xargs -r git branch -D
```
Echo the partial state too: which branches were created, which PRs were opened. The user shouldn't have to dig through `git reflog` to figure out what happened.
## Workspace mode
Per-repo. Each repo's stack is independent. `stackId = <sessionId>-<repo>` keeps stacks segregated in `linkedPRs[]`. `$orc-cleanup` groups by `stackId` and enforces bottom-up ordering within each group.
## When NOT to stack
- **Single commit on the branch** — there's nothing to stack. Use `$orc-ship`.
- **Tightly-coupled changes** — if every commit references symbols defined in every other commit, splitting buys nothing because reviewers must read the whole stack to understand any one PR. Open big-with-reason instead.
- **Hot-fix in production-incident scope** — stacking adds review cycles. Open big-with-reason `Hot-fix incident #N` and rotate later.
- **Generated/vendored bulk** — auto-generated migrations, lockfile bumps, vendor updates. Excluded from LOC by default; gate won't fire.