关于 stack-mode
供处理依赖型拉取请求堆栈的命令共用的技能契约。它解析堆栈成员,按从基线到顶端的顺序运行各 PR 工作流,并在根 PR 发布汇总。
主要功能
- 01通过三种策略解析堆栈成员
- 02按基线到顶端排列拉取请求
- 03逐个运行拉取请求工作流
- 04遇到失败的拉取请求时停止
- 05在根 PR 发布一条汇总
适用场景
- 01审查堆栈中的每个拉取请求
- 02对依赖型拉取请求逐个运行修复流程
- 03汇总覆盖整个堆栈的命令结果
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ athola/ claude-night-market --skill stack-mode安装后在 Claude Code 中输入/stack-mode确认已加载。
详细介绍
Stack 模式
适用于需要在一次调用中跨越整个依赖 PR 栈执行的命令的共享约定。
由 /pr-review --stack 和 /fix-pr --stack 使用。
目标很简单:当某个 PR 属于以同一基础分支为目标的 PR 栈时,按从基础到顶端的顺序,对栈中的每个 PR 执行命令的常规工作流,并在栈根 PR 上输出一条汇总摘要。
何时使用
当命令接受 --stack 标志(或会自动检测栈成员关系),并且需要在多个 PR 上迭代执行常规工作流时,加载此技能。
单个 PR 的工作流不要加载此技能。每个 PR 的工作流保持不变;栈模式会对其进行封装。
何时不使用
- 创建栈(使用
sanctum:stack-create) - 逐级执行 rebase(使用
sanctum:stack-rebase)
约定
调用命令必须:
- 接受
--stack布尔标志和--base <branch>覆盖参数(默认值:master)。 - 在运行主要工作流之前,调用下方的步骤 1 来解析栈成员关系。
- 按解析出的顺序,针对每个 PR 运行主要工作流。
- 按原样输出每个 PR 的结果(线程回复、问题链接等)。
- 使用步骤 4 中的格式,在根 PR 上发布一条栈级汇总评论。
- 遵守每个 PR 的 Gate 规则(每个 PR 仍需单独处理线程和问题跟踪)。
调用命令可以:
- 自动检测栈成员关系,并提示用户,而不是要求用户显式指定
--stack。 - 当某个 PR 失败导致下游 PR 的上下文过时时,提前终止迭代。
所需进度跟踪
调用方创建以下进度项:
stack-mode:membership-resolvedstack-mode:iteration-completestack-mode:root-summary-posted
步骤 1:解析栈成员关系(membership-resolved)
给定起始 PR 编号 $PR_NUM 和可选的基础分支 $BASE(默认值:master),按顺序尝试以下三种策略,并在首次得到大小 >= 2 的栈时停止。
策略 A:分支名称约定(成本最低)
START_HEAD=$(gh pr view "$PR_NUM" \
--json headRefName -q .headRefName)
# stack/<feature-name>/<slice-name>
if [[ "$START_HEAD" =~ ^stack/([^/]+)/.+$ ]]; then
FEATURE="${BASH_REMATCH[1]}"
PREFIX="stack/${FEATURE}/"
# All local branches with the same prefix
STACK_BRANCHES=$(git branch --list "${PREFIX}*" \
| sed 's/^[* ]*//' | sort)
# Map branches -> PR numbers (skip branches with no PR)
STACK_PRS=()
for branch in $STACK_BRANCHES; do
pr=$(gh pr list --head "$branch" \
--json number --jq '.[0].number')
[ -n "$pr" ] && STACK_PRS+=("$pr")
done
echo "strategy=A prs=${STACK_PRS[*]}"
fi
策略 B:栈摘要评论(最可靠)
stack-push 会在根 PR 上发布一个包含 # | Branch | PR 列的 ## Stack Markdown 表格。解析该表格:
# Find the root PR of the stack containing $PR_NUM
# The current PR may be the root, or may link to it via
# a "Part of stack `stack/<feature>`" phrase in its body
BODY=$(gh pr view "$PR_NUM" --json body -q .body)
# Look for a "## Stack" table in THIS PR's comments
TABLE=$(gh pr view "$PR_NUM" --json comments \
--jq '.comments[] | select(.body | contains("## Stack"))
| .body' | head -1)
# If not on this PR, look for the root PR reference
if [ -z "$TABLE" ]; then
ROOT_REF=$(echo "$BODY" \
| grep -oE 'stack/[^ `]+' | head -1)
# ... resolve ROOT_REF -> root PR number and re-fetch
fi
# Parse the table: extract all `#<num>` from the PR column
STACK_PRS=($(echo "$TABLE" \
| grep -oE '#[0-9]+' | tr -d '#' | sort -un))
echo "strategy=B prs=${STACK_PRS[*]}"
策略 C:基础分支链遍历(后备方案)
从 $PR_NUM 开始向两个方向遍历:
- 向上:反复解析
baseRefName,直到其等于$BASE。每个访问过的 head 都是栈成员。 - 向下:查找
baseRefName等于任一已访问 head 的开放 PR。递归执行。
# Ascend from $PR_NUM to the root
CUR=$PR_NUM
CHAIN=($CUR)
while true; do
BASE_REF=$(gh pr view "$CUR" \
--json baseRefName -q .baseRefName)
[ "$BASE_REF" = "$BASE" ] && break
PARENT=$(gh pr list --head "$BASE_REF" \
--state open --json number --jq '.[0].number')
[ -z "$PARENT" ] && break
CHAIN=("$PARENT" "${CHAIN[@]}")
CUR=$PARENT
done
# Descend from the root collecting children
ROOT=${CHAIN[0]}
FRONTIER=("$ROOT")
while [ ${#FRONTIER[@]} -gt 0 ]; do
PARENT=${FRONTIER[0]}
FRONTIER=("${FRONTIER[@]:1}")
PARENT_HEAD=$(gh pr view "$PARENT" \
--json headRefName -q .headRefName)
CHILDREN=($(gh pr list --base "$PARENT_HEAD" \
--state open --json number --jq '.[].number'))
for c in "${CHILDREN[@]}"; do
CHAIN+=("$c")
FRONTIER+=("$c")
done
done
STACK_PRS=("${CHAIN[@]}")
echo "strategy=C prs=${STACK_PRS[*]}"
排序
无论使用哪种策略,都要将最终列表按从基础到顶端的顺序排列(根 PR 在首位,叶 PR 在末位)。根 PR 是 baseRefName 等于 $BASE 的 PR。
ROOT_PR=""
for p in "${STACK_PRS[@]}"; do
base=$(gh pr view "$p" --json baseRefName -q .baseRefName)
if [ "$base" = "$BASE" ]; then
ROOT_PR=$p
break
fi
done
如果栈大小为 1,命令必须退回到单 PR 模式,并输出警告:stack-mode: only one PR found; --stack has no effect, proceeding as single-PR。
GitLab / Bitbucket
有关 gh 调用对应的 glab / Bitbucket 用法,请参阅 Skill(leyline:git-platform)。这三种策略可直接对应使用:GitLab MR 依赖关系替代基础分支链遍历,且 glab mr view --output json 的结构与 gh pr view 相符。
步骤 2:与用户确认(若未显式指定)
如果调用方未传入 --stack,但步骤 1 解析出的栈大小 >= 2,则打印该栈并询问:
Detected stack of N PRs rooted at #<root>:
1. #<root> <title>
2. #<p2> <title>
...
Run the workflow on the full stack? [y/N]
默认为“N”(单 PR 模式)。如果显式指定了 --stack,则跳过此提示。
步骤 3:迭代(iteration-complete)
按从基础到顶端的顺序,对每个 PR 执行一次调用方的主要工作流:
FAILED_PRS=()
for p in "${STACK_PRS[@]}"; do
echo "=== stack-mode: PR #$p ($(($idx+1))/${#STACK_PRS[@]}) ==="
# Caller hook: run the single-PR workflow on $p
# This includes Gate 1 (thread resolution) and
# Gate 2 (issue tracking) per the caller's contract.
if ! run_single_pr_workflow "$p"; then
FAILED_PRS+=("$p")
# Stop iteration: downstream PRs may now be stale
break
fi
done
如果任一 PR 失败,命令必须停止,并报告 stack-mode: halted at #<pr>; downstream PRs untouched。下游 PR 会有意保持不变,因为其上下文可能已发生变化。
步骤 4:在根 PR 上发布栈摘要(root-summary-posted)
成功完成迭代后,在根 PR 上发布一条汇总评论。此评论不会取代每个 PR 的摘要,而是将它们关联起来。
## Stack <Command Name> Summary
**Stack**: N PRs rooted at #<root>, base `<base-branch>`
**Command**: <command> (e.g., /fix-pr, /pr-review)
**Run**: <timestamp>
| # | PR | Branch | Status | Per-PR Summary |
|---|----|--------|--------|----------------|
| 1 | #<root> | `stack/<feat>/<root-slice>` | <done/skipped/failed> | [link to comment] |
| 2 | #<p2> | `stack/<feat>/<slice-2>` | <done/skipped/failed> | [link to comment] |
| ... | ... | ... | ... | ... |
### Cross-PR Notes
- <Any observations that spanned multiple PRs>
- <Issues created that reference multiple stack PRs>
- <Base-PR-merged cascade reminders, if any>
### Next Steps
- If root PR merges, run `Skill(sanctum:stack-rebase)` to
cascade the base update through descendants.
- Reviewers: rebase-triggered stale-review flags on
descendants are expected after any force-push.
通过 gh pr comment "$ROOT_PR" --body-file <file> 发布。
失败模式与恢复
| 失败 | 检测方式 | 恢复方式 |
|---|---|---|
| 堆栈大小为 1 | 步骤 1 返回一个 PR | 发出警告,回退到单 PR 模式 |
| 基础分支链出现循环 | 上溯时使用已访问集合 | 停止,报告 stack-mode: cycle detected |
| 堆栈中途的 PR 未通过其 Gate | 调用方引发错误 | 停止迭代,不触碰下游 PR |
| 策略 B 表格格式错误 | 解析失败 | 转而使用策略 C |
| 授权范围不足 | gh 返回 403 | 停止并给出明确错误;调用方将错误显示给用户 |
注意事项
- 堆栈模式是增量式的:单 PR 行为保持不变。 只有迭代和根 PR 摘要是新增内容。
- 每个 PR 的 Gate 1(线程处理)和 Gate 2(问题 跟踪)保持不变:每个 PR 仍会独立通过各自的 Gate。
- 切勿合并整个堆栈的 Gate 1。在一个 PR 上已解决的线程, 不代表另一个 PR 上的线程也已解决。
- 根 PR 摘要具有幂等性:重新运行堆栈模式时,
应更新(而非重复创建)摘要评论。
以
## Stack <Command Name> Summary作为匹配键。 stack-mode是一种读取与编排技能,不会推送、变基或编辑分支。 这些操作由stack-create、stack-push和stack-rebase负责。
退出标准
- 在开始迭代前,使用三种策略中的至少一种解析堆栈成员关系(A:分支名称、B:堆栈评论、C:沿基础分支链遍历),并按从基础到顶端的顺序排列 PR
- 创建 3 个必需的进度项(
stack-mode:membership-resolved、stack-mode:iteration-complete、stack-mode:root-summary-posted),并依次标记为已完成 - 处理完所有 PR 后,根 PR 恰好收到一条合并后的
## Stack <Command> Summary评论 - 如果堆栈大小为 1,则发出警告
stack-mode: only one PR found; --stack has no effect,并使用单 PR 模式 - 如果某个 PR 未通过其 Gate,则停止迭代,不触碰下游 PR,并报告
stack-mode: halted at #<pr>