Storybook open-pr 技能解析:AI Agent 按仓库约定自动发起 Pull Request 的完整工作流
本篇指南基于 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 将整个过程编排为六个阶段:
- Gather context——收集 Git 上下文;
- Detect base branch——检测基础分支;
- Ask for labels——交互式询问三类标签;
- Draft title and body——起草标题与正文;
- Create the PR——创建草稿 PR;
- 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 必须同时满足四个条件,任何一条不满足即被排除:
- 不能是当前分支自身(
branch != current_branch); origin/<branch>必须真实存在于远端;- 候选分支的 tip 不得与当前 HEAD 同处一个提交("Skips branches at the same commit as HEAD");
- 必须通过
git merge-base --is-ancestor "${ref}" HEAD证明它是 HEAD 的祖先。
平票裁决(第 76–90 行)
当两个候选分支到 HEAD 的提交数相同时,consider() 函数按两条规则裁决:
- 功能分支优先于 trunk(telescoped 父分支优先于
main/next):is_trunk()只认main和next,若现任 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:normal、ci:merged、ci:daily |
| QA 标签 | qa:needed、qa:skip |
| 类型标签 | bug、maintenance、dependencies、build、cleanup、documentation、feature request、BREAKING CHANGE、other |
这套标签体系在 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 export、Nextjs-Vite: Add support、CLI: 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 storypr 技能进一步要求每条步骤可复制粘贴、明确预期行为、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/foo → feature-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 用两条短规则固化了硬性约定,也适合作为自定义同类技能时的检查清单:
- Always draft; always assign
@me——草稿态 + 自指派是 Storybook PR 流程的不变式; - 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 集合与兜底分支; ghCLI 必须已认证且对目标仓库有开 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 集合定义 |
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00