AGENT SKILL

stack-pr

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.

License 许可
MIT
所属仓库
HigorAlves/orc
最近更新
2026-11-07
Stars
★ 7
Forks
0

01 前言 Front Matter

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.

02 资源文件 1

03 SKILL.md 正文

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

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:

# 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"):

gs push                                          # pushes every layer
gs submit --base "$original_base"                # creates the chained PRs

Otherwise, plain-gh fallback (per-branch loop):

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):

## 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:

{
  "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

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):

{
  "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):

> **📋 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:

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):

> **🛑 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

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:

> **🛑 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.

04 同仓库更多 Skills