首页
/ Storybook open-pr 技能解析:AI Agent 按仓库约定自动发起 Pull Request 的完整工作流

Storybook open-pr 技能解析:AI Agent 按仓库约定自动发起 Pull Request 的完整工作流

2026-09-06 11:52:25作者:劳婵绚Shirley

本篇指南基于 Storybook 仓库中的 .claude/skills/open-pr/SKILL.md(该文件是指向 open-pr 技能 的引用指针)展开,完整拆解这一 AI Agent Skill 的六步工作流:从 Git 上下文收集、基础分支自动检测、标签交互询问,到基于 PR 模板 的正文填充、gh pr create 草稿 PR 创建,以及可选的 canary 发布。读完后你能理解该技能如何通过一个 Shell 脚本 支持 stacked PR 场景的基础分支推断,并能参照同样的约定为自己的仓库设计 PR 自动化技能。

技能定位与元数据

open-pr 是 Storybook 仓库内置的一套面向 AI Agent(如 Claude Code)的技能定义。技能文件位于 .agents/skills/open-pr/SKILL.md,而 .claude/skills/open-pr/SKILL.md 中仅有一行 @../../../.agents/skills/open-pr/SKILL.md——这是 Claude Code 的引用指针语法,让 .claude/skills/ 目录下的技能直接复用 .agents/skills/ 中的单一事实来源,避免两份内容各自维护。

技能的 YAML frontmatter 声明了它的身份与权限边界:

name: open-pr
description: Opens a pull request from the current branch using the PR template. Use when the user asks to open a PR, create a pull request, or invokes /open-pr.
allowed-tools: Bash, Read, AskQuestion

三个字段各承担一个职责:

  • name:技能注册名,用户可通过 /open-pr 直接唤起;
  • description:触发条件的自然语言描述,Agent 依据它判断用户请求("open a PR / create a pull request")是否命中该技能;
  • allowed-tools:工具白名单。本技能只允许 Bash(执行 git/gh 命令)、Read(读取 PR 模板)、AskQuestion(向用户询问标签),没有写文件的权限——这与 canary 技能 只允许 Bash 的设计一致:最小权限原则贯穿整套技能。

技能的整体目标是"从当前分支、遵循 Storybook 约定地开出一个草稿 PR"。它和 pr 技能 是配套关系:pr 技能定义 PR 标题格式、三类标签的完整语义与 PR 正文撰写规范,open-pr 则负责把这套约定落成一次可执行的六步流程,并在需要时跳转到 canary 技能。

工作流总览:六步流程

SKILL.md 将整个过程编排为六个阶段:

  1. Gather context——收集 Git 上下文;
  2. Detect base branch——检测基础分支;
  3. Ask for labels——交互式询问三类标签;
  4. Draft title and body——起草标题与正文;
  5. Create the PR——创建草稿 PR;
  6. Report and offer canary——汇报结果并询问是否发 canary。

以下按阶段展开,并深入关键脚本的源码实现。

第 1 步:并行收集 Git 上下文

技能要求并行运行以下命令以掌握当前分支状态:

git status
git diff
git log --oneline <base>...HEAD   # base 在第 2 步确定后补跑
git branch -vv

其中 git branch -vv 显示各分支的 upstream 跟踪关系,为后续 base 检测提供线索;git log --oneline <base>...HEAD 的三点语法列出"当前分支相对 base 分叉点"的全部提交,用于判断 PR 实际包含哪些改动。若当前分支尚未推送到远端,需要先执行:

git push -u origin HEAD

-u 参数会同时建立 upstream 跟踪——这正是第 2 步 base 检测第一优先级信号(tracked upstream)的来源。

第 2 步:基础分支检测(核心算法)

确定 PR 应合并到哪个分支是 Storybook 多分支协作模式的难点。仓库采用 main(当前版本线)与 next(开发线)双 trunk 结构,且 PR 模板 明确要求"除当前版本专属修复外,所有 PR 提交到 next 分支"。但仅默认 next 不足以覆盖 stacked PR(在一个已开 PR 的功能分支上再开一层 PR)的场景。技能给出的命令是:

git fetch origin
bash .agents/skills/open-pr/scripts/detect-base-branch.sh

detect-base-branch.sh 的完整检测策略可以从源码逐段验证,它按三级优先级依次尝试,并带有一组严格的合法性校验与平票裁决规则。

优先级 1:tracked upstream(第 99–102 行)

# 1. Tracked upstream (set when branching with -u or --track)
if upstream=$(git rev-parse --abbrev-ref '@{upstream}' 2>/dev/null); then
  try_explicit_base "${upstream}"
fi

git rev-parse --abbrev-ref '@{upstream}' 取当前分支显式跟踪的远端分支(第 1 步的 git push -u 或带 -u 建分支时设置),并归一化掉 origin/ 前缀后作为候选 base。

优先级 2:reflog 中的 checkout 来源(第 104–116 行)

当没有 upstream 时,脚本解析当前分支的 reflog,用正则匹配最近一次 checkout: moving from <X> to <当前分支>branch: Created from <X> 记录:

if [[ ${line} =~ checkout:\ moving\ from\ (.+)\ to\ ${current_branch} ]]; then
  try_explicit_base "${BASH_REMATCH[1]}"
  break
fi
if [[ ${line} =~ branch:\ Created\ from\ (.+) ]]; then
  try_explicit_base "${BASH_REMATCH[1]}"
  break
fi

这覆盖了"从 feature/foo 直接 git checkout -b feature/bar"这类未设置 upstream 的 stacked PR 场景。

优先级 3:扫描全部 origin/* 分支,取最近的严格祖先(第 118–131 行)

前两级都落空时,脚本用 git for-each-ref --format='%(refname:short)' refs/remotes/origin/ 枚举所有远端分支逐一评估,以 git rev-list --count "origin/$1..HEAD" 统计候选分支到 HEAD 的提交数,提交数最少的严格祖先胜出——这实现了"支持 stacked PR,而非只支持 main/next"的目标。

合法性校验 is_valid_base(第 30–41 行)

一个分支要成为有效 base 必须同时满足四个条件,任何一条不满足即被排除:

  1. 不能是当前分支自身(branch != current_branch);
  2. origin/<branch> 必须真实存在于远端;
  3. 候选分支的 tip 不得与当前 HEAD 同处一个提交("Skips branches at the same commit as HEAD");
  4. 必须通过 git merge-base --is-ancestor "${ref}" HEAD 证明它是 HEAD 的祖先。

平票裁决(第 76–90 行)

当两个候选分支到 HEAD 的提交数相同时,consider() 函数按两条规则裁决:

  • 功能分支优先于 trunk(telescoped 父分支优先于 main/next):is_trunk() 只认 mainnext,若现任 best 是功能分支而新候选是 trunk,则保留 best;
  • 同为 trunk 时,next 优先于 main(脚本第 87–90 行显式处理 branch == "next" 的覆盖逻辑)。

兜底(第 133–135 行)

若三级检测全部落空,脚本回退到 next

if [ -z "${best}" ]; then
  best="next"
fi

这与 PR 模板中"默认提交到 next"的仓库约定一致。脚本最终把检测到的分支名输出到 stdout,并要求 Agent 把结果告知用户。

第 3 步:交互式询问三类标签

技能要求通过 AskQuestion 工具向用户提出三个问题,选项取自 .github/PULL_REQUEST_TEMPLATE.md 中维护者检查单列出的可用标签:

问题 选项
CI 标签 ci:normalci:mergedci:daily
QA 标签 qa:neededqa:skip
类型标签 bugmaintenancedependenciesbuildcleanupdocumentationfeature requestBREAKING CHANGEother

这套标签体系在 pr 技能 中有完整的语义定义,三类标签均必选其一:

  • 类型标签决定改动性质与是否进入 release changelog:build/cleanup/documentation 不进 changelog,feature request 引入新功能,BREAKING CHANGE 表示破坏性变更,dependencies 专用于依赖升降级;
  • CI 标签控制 GitHub 检查单运行哪一组 sandbox。模板检查单明确指出各标签对应的 sandbox 集合定义在 code/lib/cli-storybook/src/sandbox-templates.ts
  • QA 标签告知 release 团队该 PR 在下一次 minor 发布前是否需要人工手动验证。pr 技能给出的启发式规则包括:触及路径/文件系统/可能影响 Windows 的行为 → qa:needed;跨多个模块必须协同生效的复杂改动 → qa:needed;简单直接的改动 → qa:skip;无法判断时直接问用户。

值得注意的是标签集合在两份文档间存在细微差异:pr 技能额外列出了 ci:docs(配合 documentation 类型使用,见 .agents/skills/pr/SKILL.md 第 42 行),而 open-pr 技能的提问表格以 PR 模板的检查单为准——这也解释了 SKILL.md 中"Verify the available labels with the PR template"这句话的用意:标签选项应以 PR 模板 为最终事实来源,避免技能文档与模板漂移。

第 4 步:起草标题与正文

标题采用 [Area]: [Description] 格式,具体规范(Area 首字母大写、不含空格、允许连字符)与示例(如 CSFFactories: Fix type exportNextjs-Vite: Add supportCLI: Fix automigrate issue)由 pr 技能 定义,open-pr 直接引用不重复。

正文的要求是:读取 .github/PULL_REQUEST_TEMPLATE.md逐字复制模板(包括所有 HTML 注释),然后填充指定字段。对照模板全文,可以明确各字段的处理规则:

  • Closes #:有关联 issue 时填入编号,多个 issue 用 closes #1000, closes #1001 形式拆分(模板第 3 行注释);

  • What I did:简述 PR 做了什么;

  • Testing 检查单:勾选 stories / unit / integration / end-to-end 中适用的自动化测试类型;

  • Manual testing:模板用 [!CAUTION] 标注此节"对所有贡献都是强制的",若确无手动测试必要,必须显式说明理由。模板注释还给出了书写范式——面向"另一位维护者"的复现步骤而非"我如何测过",例如:

    1. Run a sandbox for template, e.g. `yarn task --task sandbox --start-from auto --template react-vite/default-ts`
    2. Open Storybook in your browser
    3. Access X story
    

    pr 技能进一步要求每条步骤可复制粘贴、明确预期行为、UI 改动要链接到具体 story,并强调"提交 PR 前先自己跑一遍这些步骤";

  • Documentation 检查单:适用时勾选,含弃用/移除功能时须同步 MIGRATION.md(模板中该链接指向 storybookjs/storybook 仓库的 next 分支);

  • Checklist for Maintainers:三项维护者检查单(CI 标签、QA 声明、类型标签)保持未勾选——这些由维护者侧流程负责,贡献者不应代勾。

模板末尾还保留了若干机器可读的 HTML 注释锚点,填充时必须原样保留:<!-- CANARY_RELEASE_SECTION -->(canary 工作流回写发布版本的占位区)、<!-- BENCHMARK_SECTION -->(性能数据区)。pr 技能也提到,CI 完成后可以把已发布的 Chromatic 链接写进手动测试一节,格式为 https://<branch>--<project_id>.chromatic.com/?path=/story/<story_id>,其中 <branch> 需按 Chromatic 的 slug 规则转换(如 feature/foofeature-foo)。

第 5 步:创建草稿 PR

SKILL.md 给出的完整创建命令是:

gh pr create \
  --draft \
  --base "<detected-base>" \
  --title "<Area>: <Description>" \
  --body "$(cat <<'EOF'
<FILLED_TEMPLATE>
EOF
)" \
  --assignee @me \
  --label "<type>,<ci>,<qa>"

各参数要点:

  • --draft:技能 Notes 明确要求"Always draft"——PR 永远以草稿态创建,评审通过前不进入正式评审流程;
  • --base:填入第 2 步 detect-base-branch.sh 的输出;
  • --body 使用 heredoc(<<'EOF',带引号防止变量展开)承载填好的模板全文。相比 pr 技能中的单行 --body "<FILLED_TEMPLATE>" 写法,heredoc 能安全承载含反引号、!(模板中有 [!CAUTION])、多行的长正文,避免 shell 转义地狱;
  • --assignee @me:Notes 要求"always assign @me",把 PR 指派给操作者本人;
  • --label:三个标签以逗号拼接,对应第 3 步的三个回答。

pr 技能中还给出了该命令的最简形式,可作对照:

gh pr create --draft --title "<Area>: <Description>" --body "<FILLED_TEMPLATE>" --label "<category>,<ci>,<qa>"

第 6 步:汇报结果并询问 canary

创建成功后,技能要求先分享 PR URL,再通过 AskQuestion 追问"是否要为这个 PR 创建 canary release?":

  • 用户选 Yes:执行 /canary <PR_NUMBER>,并汇报 workflow 运行状态;
  • 用户选 No:流程结束。

canary 技能 定义了这一步的后续机制:通过 gh workflow run --repo storybookjs/storybook publish.yml --field pr=<PR_NUMBER> 触发 GitHub Actions,发布形如 0.0.0-pr-<PR_NUMBER>-sha-<SHORT_SHA> 的 npm 版本并打上 canary tag,随后 workflow 会把确切版本号回写到 PR 正文的 CANARY_RELEASE_SECTION 占位区——这正呼应了第 4 步"保留 HTML 注释"的要求。发布后可用 npx storybook@<VERSION> sandbox 或直接 npx storybook@<VERSION> upgrade 验证该版本。canary 发布的前提是拥有仓库 admin 权限、PR 处于 open 状态且 gh CLI 已认证。

关键约定小结(Notes 部分)

SKILL.md 结尾的 Notes 用两条短规则固化了硬性约定,也适合作为自定义同类技能时的检查清单:

  1. Always draft; always assign @me——草稿态 + 自指派是 Storybook PR 流程的不变式;
  2. canary 发布的细节交给 canary 技能——单一职责拆分:open-pr 只负责"问一句要不要",真正触发与监控逻辑在 canary 技能 中,二者通过 /canary <PR_NUMBER> 命令衔接。

可借鉴的设计要点

从这套技能文件的结构可以看到几个值得复用的工程实践:

  • 单一事实来源 + 指针复用.claude/skills/open-pr/SKILL.md 仅一行 @ 引用,.agents/skills/open-pr/SKILL.md 是唯一维护点;
  • 确定性逻辑下沉到脚本:base 分支检测这种有明确算法(upstream → reflog → 最近严格祖先 + 平票裁决 + 兜底 next)的逻辑被固化为 detect-base-branch.sh,而不是依赖 Agent 每次自由发挥,保证行为可重复、可 review;
  • 模板即契约:标签选项、正文结构、机器锚点(CANARY_RELEASE_SECTION)全部锚定在 .github/PULL_REQUEST_TEMPLATE.md 上,技能文档只负责"怎么填",模板负责"填什么",两者漂移时以模板为准;
  • 最小工具授权:frontmatter 的 allowed-tools 精确到 Bash, Read, AskQuestion,技能没有任何超出 PR 创建所需的能力面。

适用前提与限制

  • 该工作流针对 Storybook 仓库的多分支约定(main/next 双 trunk、PR 默认进 next、维护者 cherry-pick 回 main),移植到其他仓库时需要改写 is_trunk() 的 trunk 集合与兜底分支;
  • gh CLI 必须已认证且对目标仓库有开 PR 的权限;canary 环节额外要求 admin 权限;
  • 检测脚本依赖 git for-each-ref、reflog 等本地 Git 元数据,在浅克隆(shallow clone)或缺少 reflog 的检出环境中,优先级 2/3 的检测信号会不完整,最终依赖 next 兜底;
  • 标签集合以 PR 模板pr 技能 当前内容为准,其中 ci:docs 目前只出现在 pr 技能中,若模板后续补齐,open-pr 的提问表格需同步。

相关文件索引

文件 作用
.claude/skills/open-pr/SKILL.md 指向 .agents/skills/open-pr/SKILL.md 的引用指针
.agents/skills/open-pr/SKILL.md open-pr 技能主体:六步工作流
.agents/skills/open-pr/scripts/detect-base-branch.sh 基础分支检测脚本(upstream → reflog → 最近祖先 + 裁决)
.agents/skills/pr/SKILL.md PR 标题格式、三类标签完整语义、正文撰写规范
.agents/skills/canary/SKILL.md canary 发布触发、版本号规则、发布后验证
.github/PULL_REQUEST_TEMPLATE.md PR 正文模板(标签、检查单、CANARY_RELEASE_SECTION 锚点)
code/lib/cli-storybook/src/sandbox-templates.ts ci:normal/ci:merged/ci:daily 对应的 sandbox 集合定义
登录后查看全文
热门项目推荐
相关项目推荐