首页
/ Storybook Agent 评测实战:801 用例如何验证"无 launch 配置时创建可访问组件"

Storybook Agent 评测实战:801 用例如何验证"无 launch 配置时创建可访问组件"

2026-09-06 18:23:56作者:彭桢灵Jeremy

本篇以 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 ToggleSwitch component with label, checked, onChange, and optional disabled props, exported from src/components/ToggleSwitch.tsx.

这句话信息密度极高,拆解出四个硬性要求:

  • 可访问性("accessible"):组件实现必须满足 a11y 约束,这也是后续 story 测试要跑 @storybook/addon-a11y 的原因;
  • 四个 proplabelcheckedonChange 必选,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.tssetupSandbox 的实现,这个声明会触发一条完整的沙箱装配流水线:

  1. readTemplateFiles 读取 agent-eval/templates/reshaped-storybook 下全部文件(排除 eval-template.json 元数据文件);
  2. mergeTemplateAndFixtureFiles 把 fixture 自身文件叠加到模板上——同名 JSON 文件(如 package.json)会做 deepMergeJson 深合并,非 JSON 文件直接覆盖;
  3. pinStorybookPackages 把所有 storybook / @storybook/* 依赖解析到 npm 的 next dist-tag 精确版本(EVAL_STORYBOOK_LATEST=1 时改为 latest),保证每次结果快照记录用的是哪个版本;
  4. usesLocalStorybookMcpPackages 检测到模板引用了 file:./local-packages/addon-mcp 这类本地构建,就把本 checkout 的 code/addons/mcp/distcode/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.tsPROMPT.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-run CLI 均算);
  • 取"最后一份可识别的测试报告"(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-changedstories-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 稳定通过,故只豁免这一个格子;
  • stories skill:plugin 实验会把本仓库 code/lib/claude-plugin/skillscode/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_repljs 工具里出现对本地 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):

  1. .claude/launch.json 存在且可解析为 JSON 对象,configurations 是非空数组;
  2. 其中必须有一条 runtimeArgsstorybook 的 configuration;
  3. 该条目的 port 必须为 6006
  4. autoPort 必须为 true
  5. 交叉校验 package.jsonscripts.storybook 是字符串——launch 入口引用了 storybook script,那这个 script 就必须真实存在(模板里正是 "storybook": "storybook dev")。

运行机制速览:这条用例何时被触发

agent-eval/README.md

  • 801 是默认唯一运行的 core eval——不加任何环境变量时只有它跑,EVAL_EXTRA_EVALS=1 才把整条 8xx 手工用例线加进来,EVAL_ONLY=801-create-component-no-launch-config 可单独调试它;
  • 六个实验(cc-mcp-opus-highcc-plugin-opus-highcodex-mcp-gpt-5.5-mediumcodex-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 中的断言库,是一份可直接参照的完整范本。

登录后查看全文
热门项目推荐
相关项目推荐