Storybook Agent 评测实战:801 用例如何验证"无 launch 配置时创建可访问组件"
本篇以 Storybook 仓库 agent-eval 评测套件中的 801 用例为对象,讲清楚这个"让 Agent 在一个没有 Storybook launch 配置的项目里创建可访问 ToggleSwitch 组件"的评测场景是如何定义、拼装和判分的。读完后你能掌握:Agent 评测 fixture 的三件套结构(PROMPT.md / EVAL.ts / package.json)、共享模板 reshaped-storybook 的注入机制,以及各条 vitest 断言背后的行为校验逻辑。
任务提示词:一句话说清需求
该用例发给编码 Agent 的完整提示词只有一句话(PROMPT.md):
Build an accessible
ToggleSwitchcomponent withlabel,checked,onChange, and optionaldisabledprops, exported fromsrc/components/ToggleSwitch.tsx.
这句话信息密度极高,拆解出四个硬性要求:
- 可访问性("accessible"):组件实现必须满足 a11y 约束,这也是后续 story 测试要跑
@storybook/addon-a11y的原因; - 四个 prop:
label、checked、onChange必选,disabled可选; - 受控组件语义:
checked+onChange是典型的受控开关写法; - 固定出口:文件必须放在
src/components/ToggleSwitch.tsx并导出——评测器后续会用文件名/组件名做检索匹配(如断言里的小写子串toggleswitch)。
用例名字里的 "no-launch-config" 是它区别于兄弟用例 802(802-create-component)的关键:fixture 用空数组覆盖了模板自带的 .claude/launch.json,模拟"一个已经装了 Storybook、但没有为 Claude 预览工具链配置 launch 入口"的真实项目,考察 Agent 是否会把 launch 配置也补上。
Fixture 元数据:如何挂上共享模板
用例目录下的 package.json 只有两行核心内容:
{
"name": "801-create-component-no-launch-config",
"type": "module",
"evals": {
"template": "reshaped-storybook"
}
}
evals.template 声明了 fixture 复用共享模板 reshaped-storybook。根据 agent-eval/lib/templates.ts 中 setupSandbox 的实现,这个声明会触发一条完整的沙箱装配流水线:
readTemplateFiles读取 agent-eval/templates/reshaped-storybook 下全部文件(排除eval-template.json元数据文件);mergeTemplateAndFixtureFiles把 fixture 自身文件叠加到模板上——同名 JSON 文件(如package.json)会做deepMergeJson深合并,非 JSON 文件直接覆盖;pinStorybookPackages把所有storybook/@storybook/*依赖解析到 npm 的nextdist-tag 精确版本(EVAL_STORYBOOK_LATEST=1时改为latest),保证每次结果快照记录用的是哪个版本;usesLocalStorybookMcpPackages检测到模板引用了file:./local-packages/addon-mcp这类本地构建,就把本 checkout 的code/addons/mcp/dist与code/lib/mcp/dist注入沙箱。
模板元数据 eval-template.json 声明 "amazonLinuxPackages": "playwright-chromium",setupTemplateSandbox 会据此在 Amazon Linux 沙箱镜像上安装 alsa-lib、libX11、nss 等一套 Chromium 运行库(源码里 PLAYWRIGHT_CHROMIUM_AMAZON_LINUX_PACKAGES 列了完整 18 个包名),让浏览器型 story 测试能跑起来。
沙箱里的项目长什么样:reshaped-storybook 模板
agent-eval/templates/reshaped-storybook/package.json 展示了 Agent 开工时的项目基线:
{
"dependencies": {
"react": "19.2.0",
"react-dom": "19.2.0",
"reshaped": "4.0.3"
},
"devDependencies": {
"@storybook/addon-a11y": "next",
"@storybook/addon-docs": "next",
"@storybook/addon-mcp": "file:./local-packages/addon-mcp",
"@storybook/addon-vitest": "next",
"@storybook/mcp": "file:./local-packages/mcp",
"@storybook/react": "next",
"@storybook/react-vite": "next",
"storybook": "next",
"vitest": "4.0.6"
},
"scripts": {
"test:stories": "vitest run --config ./vitest.storybook.config.ts --project storybook",
"storybook": "storybook dev",
"postinstall": "playwright install chromium && node ./scripts/start-storybook-mcp.mjs"
}
}
几个关键点:
- Reshaped 组件库:这是"设计系统形态"的模板(README 称之为 design-system shape),组件的 props 不能靠猜,必须查文档——这直接对应 EVAL.ts 里
docs-show断言存在的原因; - 本地 MCP 构建:
file:./local-packages/*指向注入的本仓库构建产物,即评测的是当前 checkout 的 MCP 工具链而非 npm 发布版; postinstall自启动:模板在依赖安装后自动拉起 Storybook 并挂载 MCP server(脚本源头维护在 agent-eval/lib/mcp/start-storybook-mcp.mjs,由 setup 步骤统一注入),Agent 拿到沙箱时http://127.0.0.1:6006/mcp已经可用;- 空
stories/目录:模板的 stories/ 是空的,意味着磁盘上出现的每一个*.stories.tsx都是 Agent 自己写的——这正是"全量 story 必须出现在 review 里"断言敢这么写的前提(源码注释明确假设 "the template starts without story files")。
另外,EVAL.ts 与 PROMPT.md 不会进入沙箱工作区:readSandboxWorkspaceFiles 的 find 命令显式排除了它们,只有评测阶段才会用。
判分逻辑:EVAL.ts 的完整断言矩阵
EVAL.ts 用 vitest 组织所有断言,全部助手函数来自 agent-eval/lib/test-utils.ts。按断言分组拆解:
1. 测试必须跑通且覆盖新组件(所有实验必过)
test('runs story tests after the change and finishes with them passing', () => {
expectStoryTestsRanAndPassed({ covering: ['toggleswitch'] });
});
expectStoryTestsRanAndPassed 是这条用例的"硬底线",它做了四层校验:
- 转录里至少有一次
test-run工作流调用(MCP 工具或插件路径下的storybook ai test-runCLI 均算); - 取"最后一份可识别的测试报告"(
selectFinalRunStoryTestsReport,靠## Passing Stories等标记识别,防止 Agent 把 CLI 输出管道给 grep 后只剩过滤碎片); - 该报告不能含
## Failing Stories与## Unhandled Errors,且必须含## Passing Stories; covering: ['toggleswitch']要求最后一份通过报告的 story id 中出现toggleswitch子串——把"测试通过"锚定到本次改动本身,而不是跑了一堆无关测试碰巧全绿。
2. Review 开启时:发现故事必须先于发布 review
describe.runIf(reviewEnabled)('with review enabled', () => {
test('uses Storybook story instructions and publishes a display review', () => {
expectWorkflowCalls(['get-storybook-story-instructions', 'review-create']);
expectDisplayReviewForVisualChange();
});
test('discovers stories through the workflow tools before publishing the review', () => {
expectStoryDiscoveryBeforeReview();
});
test('every new story appears in the display review', () => {
expectAllStoryExportsInDisplayReview();
});
});
expectStoryDiscoveryBeforeReview:story id 必须来自发现工具(stories-changed或stories-find-by-component),且首次发现调用的下标必须早于最后一次review-create的下标。源码注释解释得很直白——"发布 review 前没有发现调用,说明 Agent 是凭文件名或记忆猜出了看起来有效的 id";expectDisplayReviewForVisualChange:检查最后一次review-create的 payload 合法,且最终回复中必须分享该 review 链接;expectAllStoryExportsInDisplayReview:这是完整性底线——递归扫描沙箱里所有*.stories.[jt]sx?文件(跳过 node_modules 等),用正则/^export (?:const|function) ([A-Za-z0-9_$]+)/gm提取每个具名导出,再断言 review payload 的 storyIds 中有以--kebab-case(exportName)结尾的条目。因为模板起点没有任何 story 文件,所以"每一个"都必须是 Agent 写的,一个都不能漏进 review;- 还有一条被
test.skip的软性断言publishes a well-curated review(curation 打分门槛 0.5),注释记录了失败原因:Agent 倾向把所有 story 塞进一个集合,而不是 2–5 个有意义的分组(如"视觉状态 vs 交互行为"),待 review-create 工作流的指引教会它分组后再启用。这是仓库对"已知失败"的标准处理方式——就地注释说明观察到的行为、证据(CI 运行号与日期)和重启用条件。
3. Review 关闭时:以预览链接收尾
describe.runIf(!reviewEnabled)('with review disabled', () => {
test('uses Storybook story instructions and previews the new stories', () => {
expectWorkflowCalls(['get-storybook-story-instructions']);
expectPreviewStoriesWithFinalLinks({ covering: ['toggleswitch'] });
});
});
review 模式由集成方式决定(isReviewEnabledFor):plugin 集成恒开(storybook ai CLI 通道默认开启 review),MCP 集成默认关闭、EVAL_REVIEW=1 才开。review 关闭时 review-create 根本没注册,正确的收尾是调用 stories-preview 并在最终回复里给出匹配 ?path=/story/ 或 /iframe.html?id= 的预览 URL;expectPreviewStoriesWithFinalLinks 同时禁止回复中出现 ?path=/review/ 链接——review 没开就不许假装发了 review。
4. 按 Agent/集成方式分门别条的适配性断言
describe('depending on the current agent and integration', () => {
test.skipIf(agent === 'codex' && integration === 'mcp')('uses the documentation tooling', () =>
expectWorkflowCalls(['docs-show'])
);
test.skipIf(integration === 'mcp')('invokes the stories skill', () =>
expectSkillInvoked('stories')
);
test.skipIf(agent !== 'claude-code' || integration !== 'plugin')(
'writes a valid Storybook launch config for Claude preview tooling',
() => expectValidStorybookLaunchConfig()
);
test.skipIf(integration !== 'plugin')('opens the preview browser when using the plugin', () =>
expectPreviewBrowserStarted()
);
});
docs-show(文档工具):基于外部 Reshaped 组件构建时 props 不能靠记忆或翻 node_modules,必须走文档工具。对 Codex+MCP 组合被skipIf——注释记录了 GPT-5.5 约 1/4 的 run 不调用 docs-show 就凭先验知识写码(证据:CI 28673251562 与 2026-07-03 本地运行),而 Codex+plugin 稳定通过,故只豁免这一个格子;storiesskill:plugin 实验会把本仓库 code/lib/claude-plugin/skills 或 code/lib/codex-plugin/plugins/storybook/skills 拷进沙箱的.claude/skills/.agents/skills,断言验证 Agent 真正"调用了" skill(Claude 走 Skill 工具的精确skill === 'stories'匹配;Codex 走 shell 命令读取 skill 指令文件);MCP 路径没装 skill,必须 skip 而非假通过;- launch 配置:这就是用例名 "no-launch-config" 的落点,下面单独展开;
- 预览浏览器:
expectPreviewBrowserStarted对 Claude 要求在转录中出现preview_start工具调用,对 Codex 要求node_repl的js工具里出现对本地 Storybook 预览 URL(必须带?path=/或/iframe.html?形态,裸 origin 不算)的goto导航,且不允许出现杀 dev server 的 kill 命令——"验证完不许把服务器杀了"。
5. expectValidStorybookLaunchConfig:launch 配置的逐字段校验
这是本用例最独特的断言。fixture 覆盖了模板的 .claude/launch.json 为一个空 configurations 数组(源码注释解释:fixture 只能覆盖文件、不能删除文件,所以用空数组模拟"没有配置"),Agent 必须自己写出可用的 launch 入口。test-utils 里的校验逐条检查(agent-eval/lib/test-utils.ts):
.claude/launch.json存在且可解析为 JSON 对象,configurations是非空数组;- 其中必须有一条
runtimeArgs含storybook的 configuration; - 该条目的
port必须为6006; autoPort必须为true;- 交叉校验
package.json的scripts.storybook是字符串——launch 入口引用了 storybook script,那这个 script 就必须真实存在(模板里正是"storybook": "storybook dev")。
运行机制速览:这条用例何时被触发
- 801 是默认唯一运行的 core eval——不加任何环境变量时只有它跑,
EVAL_EXTRA_EVALS=1才把整条 8xx 手工用例线加进来,EVAL_ONLY=801-create-component-no-launch-config可单独调试它; - 六个实验(
cc-mcp-opus-high、cc-plugin-opus-high、codex-mcp-gpt-5.5-medium、codex-plugin-gpt-5.5-medium等)都会对它跑一遍,所以 EVAL.ts 的断言必须能同时覆盖 MCP 与 plugin、Claude 与 Codex 四种组合,skipIf矩阵就是为这个二维组合设计的; - 运行前需在本仓库根目录
yarn nx run-many -t compile --projects mcp,addon-mcp构建本地 MCP 包,过期的dist会让沙箱 Storybook 在 preset 加载时崩溃,表现成 readiness 超时而非构建错误; - 结果通过
yarn workspace agent-eval run playground在本地结果页浏览,或results:download拉 CI 产物。
小结
801 用例展示了一个完整的 Agent 评测设计范式:PROMPT.md 用一句自然语言定义行为目标,package.json 把 fixture 挂到共享模板上,EVAL.ts 则把"好的 Agent 行为"翻译成可机检的断言——测试必须覆盖到改动本身、story id 必须来自发现工具而非猜测、每个新 story 都必须进入 review、launch 配置必须逐字段合法、dev server 必须留给用户继续用。配合 skipIf 组合矩阵和"已知失败就地注释"的规范,这条评测线可以低成本地在多 Agent、多集成、多模型实验上持续回归。对想在自己的项目中搭建同类 Agent 行为评测的开发者,agent-eval/evals/801-create-component-no-launch-config/ 目录下的三个文件加上 agent-eval/lib/test-utils.ts 中的断言库,是一份可直接参照的完整范本。
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