mcpField
S
stack-mode

stack-mode

github.com/athola开发34374 次安装1 天前更新
代码仓库

关于 stack-mode

供处理依赖型拉取请求堆栈的命令共用的技能契约。它解析堆栈成员,按从基线到顶端的顺序运行各 PR 工作流,并在根 PR 发布汇总。

主要功能

  1. 01通过三种策略解析堆栈成员
  2. 02按基线到顶端排列拉取请求
  3. 03逐个运行拉取请求工作流
  4. 04遇到失败的拉取请求时停止
  5. 05在根 PR 发布一条汇总

适用场景

  1. 01审查堆栈中的每个拉取请求
  2. 02对依赖型拉取请求逐个运行修复流程
  3. 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)

约定

调用命令必须:

  1. 接受 --stack 布尔标志和 --base <branch> 覆盖参数(默认值:master)。
  2. 在运行主要工作流之前,调用下方的步骤 1 来解析栈成员关系。
  3. 按解析出的顺序,针对每个 PR 运行主要工作流。
  4. 按原样输出每个 PR 的结果(线程回复、问题链接等)。
  5. 使用步骤 4 中的格式,在根 PR 上发布一条栈级汇总评论。
  6. 遵守每个 PR 的 Gate 规则(每个 PR 仍需单独处理线程和问题跟踪)。

调用命令可以:

  • 自动检测栈成员关系,并提示用户,而不是要求用户显式指定 --stack。
  • 当某个 PR 失败导致下游 PR 的上下文过时时,提前终止迭代。

所需进度跟踪

调用方创建以下进度项:

  1. stack-mode:membership-resolved
  2. stack-mode:iteration-complete
  3. stack-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>

评论

支持 Markdown