Skip to content
Back to skills

Stack Pr

ASecurity

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.

  • 6 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 9, 2026
ai-agentsgobashgitapi

Works with

  • api

Security analysis

A100/100

Pro scans all 2 files and shows the line behind each finding

Scanned October 9, 2026

npx -y skills add HigorAlves/orc --skill stack-pr --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Stack Pr?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Stack Pr
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/higoralves-stack-pr/badge)](https://www.skillsdirectory.com/skills/higoralves-stack-pr)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
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 that the user might lose to the rebase).

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 `AskUserQuestion`:

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.
```

`AskUserQuestion` (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, `AskUserQuestion`:

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.

Files in this skill

  • SKILL.md10.5 KB
  • references/STACKED-PRS.md4.7 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…