ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Storybook agent-eval 808 评测剖析:共享设计 Token 变更时,如何验证「消费者故事发现」fallback 工作流

Storybook agent-eval 808 评测剖析:共享设计 Token 变更时,如何验证「消费者故事发现」fallback 工作流 Storybook agent-eval 808 评测剖析共享设计 Token 变更时如何验证「消费者故事发现」fallback 工作流【免费下载链接】storybookStorybook is the industry standard workshop for building, documenting, and testing UI components in isolation项目地址: https://gitcode.com/GitHub_Trending/st/storybookStorybook 官方仓库中的agent-eval套件用于在沙箱中运行编码 AgentClaude Code、Codex断言它们是否正确遵循仓库所交付的 Storybook 工作流——写故事、预览/评审、跑 story 测试。PROMPT.md 定义的是其中一类极具代表性的边缘场景当一次视觉改动落在没有任何 story 的共享样式基础设施设计 token 文件上时Agent 必须通过变更检测发现「改动文件的消费者」并展示消费者组件的故事而不是对着一个没有故事可看的文件干瞪眼。读完本文你会完整掌握该评测的任务设定、fixture 结构、EVAL.ts的断言矩阵以及它背后stories-changed/stories-find-by-component两个 MCP 工具的实现原理——这既是理解「共享基础设施变更 → 消费者故事发现 fallback」这一 Agent 工作流的最佳样例也是自研 Agent 评测断言的参考模板。评测意图为什么「改一个 token」值得单独一条评测线在agent-eval/evals/的 8xx 工作流评测线中每个评测目录包含三样东西给 Agent 的任务书PROMPT.md、运行后执行的 vitest 断言EVAL.ts以及声明元数据的package.json。808 这条线808-shared-infra-fallback考察的核心问题可以概括为改动的是一个共享样式基础设施文件设计 token该文件本身没有 story——视觉结果如何被「浮出水面」正确答案是改动必须沿着「消费者」这条路径被呈现——要么发布一个包含消费者故事 storyId 的评审review-on要么给出消费者故事的预览链接review-off。而消费者 storyId 的获取路径又分两条变更检测diff本身就能覆盖到消费者时直接使用覆盖不到时fallback到按组件查找故事的发现工具。评测名中的 shared-infra-fallback 正来源于此。EVAL.ts顶部的 describe 注释把这一意图写得非常直白见 EVAL.tsdescribe(changing a shared accent token and surfacing consumer stories, () { // The edited token file has no stories of its own, so the run must surface // the stories of its *consumers* (Badge and StatusPill).Fixture 拆解一个没有 story 的 token 文件 两个消费它的组件808 的 package.json 声明该 fixture 基于共享模板reshaped-storybook——即「设计系统形态」完整 Storybooknext 版本使用本仓库的本地 addon 构建、MSW 与 vitest story 测试环境并由模板的postinstall在 Agent 运行前把 Storybook dev server 拉起{ name: 808-shared-infra-fallback, type: module, evals: { template: reshaped-storybook } }模板本体位于 templates/reshaped-storybook共享 app 文件留在模板里评测目录只保留自己的PROMPT.md、EVAL.ts与package.json这正是 agent-eval/README.md 中「prompt variants stay small」的设计原则。被改动的文件src/theme/colors.tsfixture 中的 src/theme/colors.ts 只有 5 行但开头两行注释直接点明了它的角色// Shared design tokens. This file has no stories of its own; components // consume these values. export const accentColor #2563eb; export const accentContrastColor #ffffff; export const neutralColor #6b7280;注意它在整个 fixture 中没有对应的*.stories.*文件——这就是「共享基础设施」的典型形态纯值导出视觉影响全部体现在消费者身上。消费者 1Badge默认 variant 使用 accent tokenBadge.tsx 同时消费三个 token默认variant: accent的背景色正是待改动的accentColor因此该 token 从蓝色变紫红色后Accent 故事的视觉会直接变化import type { ReactNode } from react; import { accentColor, accentContrastColor, neutralColor } from ../theme/colors; export type BadgeProps { children: ReactNode; variant?: accent | neutral; }; export default function Badge({ children, variant accent }: BadgeProps) { const background variant accent ? accentColor : neutralColor; return ( span >const meta { title: Example/Badge, component: Badge, args: { children: New, }, } satisfies Metatypeof Badge; export default meta; type Story StoryObjtypeof meta; export const Accent: Story {}; export const Neutral: Story { args: { variant: neutral, children: Archived, }, };消费者 2StatusPillStatusPill.tsx 是第二个 token 消费者其 StatusPill.stories.tsx 以Example/StatusPill为 title 导出故事。至此 fixture 形成了完整的依赖链src/theme/colors.ts无 story被 diff 覆盖 ├── src/components/Badge.tsx → stories/Badge.stories.tsxExample/Badge └── src/components/StatusPill.tsx → stories/StatusPill.stories.tsxExample/StatusPill任务书PROMPT.md 原文评测给 Agent 的任务只有一句话但信息密度足够指出文件、旧值、新值并明确说明它是跨组件共享的样式基础设施Change the accent color token insrc/theme/colors.tsfrom blue (#2563eb) to violet (#7c3aed). It is shared styling infrastructure used across our components.这句 shared styling infrastructure 的提示非常关键——它是在考察 Agent 能否据此联想到「该文件没有自己的故事需要去看消费者」这一推理而不只是机械执行颜色替换。EVAL.ts 断言矩阵一个评测目录如何定义「正确的工作流」EVAL.ts 是理解整条线价值的核心。它不检查 Agent「是否改了颜色」这件事本身有多优雅而是把一次合格的运行拆解为可观测的行为序列改对了 → 跑了测试且覆盖消费者 → 通过发现工具拿到故事 → 发布评审或给出预览链接 → 最终回复中包含可点击的链接。断言助手全部来自 lib/test-utils.ts。基础断言改动必须真实发生后续所有 fallback 断言都以此为前提The fallback assertions only count if the token change was actually donetest(changes the accent color token, () { const colors readFileSync(src/theme/colors.ts, utf8); expect(colors, Expected the accent token to change to #7c3aed).toMatch(/#7c3aed/i); expect(colors, Expected the old accent value #2563eb to be gone).not.toMatch(/#2563eb/i); });新值必须出现、旧值必须消失两条断言缺一不可。测试断言test-run 必须覆盖两个消费者test.skipIf(codexMcpReviewGap)( runs story tests after the change and finishes with them passing, () { expectStoryTestsRanAndPassed({ covering: [badge, statuspill] }); } );expectStoryTestsRanAndPassed见 test-utils.ts会取test-run工具调用中最后一条仍形似完整测试报告的结果通过## Passing Stories/## Failing Stories等报告标记过滤掉被grep/sed管道截断的碎片要求最终运行成功、无 Failing/Unhandled 段落且covering中的子串这里是badge、statuspill至少有一个出现在报告里——即测试确实跑到了受影响消费者的故事上。review 开启分支评审必须带上消费者故事review的取值由运行环境决定plugin 实验永远 review-onMCP 实验默认 review-offEVAL_REVIEW1翻转见 test-utils.ts 的注释与 README。review-on 时有四条断言expectDisplayReviewForVisualChange()最后一次review-create调用必须存在、payload 合法且最终回复中分享评审链接expectStoryIdsInDisplayReview([badge, statuspill])评审 payload 的 storyIds 中必须同时包含 badge 与 statuspill——评审里出现的是消费者故事不是 token 文件expectStoryDiscoveryBeforeReview()stories-changed或stories-find-by-component至少一次且发生在review-create之前——storyId 必须来自发现工具禁止凭文件名或记忆编造条件式 fallback 断言本评测的灵魂见下文专节。review 关闭分支预览链接替代评审describe.runIf(!review)(when review is disabled, () { test(previews the consumer stories for the visual token change, () { expectPreviewStoriesWithFinalLinks({ coveringAnyOf: [badge, statuspill] }); }); });这里刻意使用coveringAnyOf任意一个而非covering全部注释解释了原因——review-off 的指令要求预览的是发现结果中「selected」的 storyId只浮出一个消费者的故事属于合法的筛选EVAL.ts。expectPreviewStoriesWithFinalLinks还会校验最终回复中出现 story 预览链接?path/story/或/iframe.html?id形态且不得出现评审链接——review 工具此时根本未注册。按 Agent / 集成形态门控的断言test.skipIf(integration mcp)(invokes the stories skill, () { expectSkillInvoked(stories); }); test.skipIf(agent ! claude-code || integration ! plugin)( keeps the pre-existing Storybook launch config valid, () { expectValidStorybookLaunchConfig(); } ); test.skipIf(integration ! plugin)(opens the preview browser when using the plugin, () { expectPreviewBrowserStarted(); });MCP 路径不安装 skill所以storiesskill 调用断言仅在 plugin 路径生效.claude/launch.json校验端口 6006、autoPort: true、runtimeArgs含storybook只针对 claude-code plugin 组合预览浏览器断言覆盖两种 plugin 表面Claude Code 必须经过preview_start工具Codex 必须通过node_repl的js工具把 tab 导航到 Storybook 预览 URL且不得在验证结束后杀掉 dev server。被文档化的已知失败codexMcpReviewGapEVAL.ts顶部有一段值得每个评测作者借鉴的注释EVAL.ts在 Codex MCP review-on 组合下Agent 观察到「编辑 token 文件后零 MCP 调用即结束回合」的行为约占一半的运行原因是 Codex 只把 MCP server 指令作为工具命名空间描述呈现对自认为 trivial 的编辑不会去读 storybook 命名空间。因此test.skipIf(codexMcpReviewGap)暂时关闭该单元的部分断言注释同时写明了重新启用的条件。这正是 README 「Known Failures」一节规定的格式被接受的失败以自包含注释观察到的行为、证据日期、重启用条件直接写在被放宽的断言上方。条件式 fallback 断言为什么「两种正确路径都要过」这是 808 区别于其他视觉变更评测的关键断言EVAL.ts// Deliberately conditional: the module graphs related-stories detection can // legitimately surface both consumers from the diff alone, and that is // correct behavior; the fallback is only required when it doesnt. test(falls back to stories-find-by-component when the diff does not cover the consumers, () { const changedStoriesResults getWorkflowToolResults(stories-changed); const lastChangedStories changedStoriesResults.at(-1); const diffCoversConsumers lastChangedStories ! undefined !lastChangedStories.isError /badge/i.test(lastChangedStories.output) /statuspill/i.test(lastChangedStories.output); if (diffCoversConsumers) { return; } expect( getWorkflowCalls(stories-find-by-component).length, stories-changed did not surface the consumer stories, so stories-find-by-component must be used ).toBeGreaterThan(0); });其逻辑分两层若最后一次stories-changed调用的输出已经同时包含badge 与 statuspill模块图的反向依赖分析能直接从 diff 推出两个消费者则无需再调用 fallback直接通过否则必须观察到至少一次stories-find-by-component调用——即 Agent 正确执行了「diff 没覆盖到消费者 → 按组件路径查找故事」的降级路径。这种设计避免了断言「唯一正确步骤」的僵硬fallback 是条件义务只在需要时才被要求。源码纵深两个发现工具如何实现这条工作流评测断言的是行为行为能力的来源是 Storybook open service 中的 stories 工具集实现位于 code/core/src/shared/open-service/toolsets/stories/definition.ts。该文件不仅定义了三个工具preview/changed/findByComponent的输入输出 schema工具描述本身就是一份写给 Agent 的工作流指令808 考察的推理链在其中有明确的文字依据。stories-changed累积 working-tree diff但共享文件可能不在图内changed方法从core/module-graph服务读取变更检测状态new/modified/affected三种状态值并结合 git working-tree diff 计算受影响故事同时返回unreachableFiles——工作区中被修改、但不在故事图内的文件definition.ts。其描述describeChanged明确交代了两点结果反映的是累积的 working-tree diff而非最近一次编辑多轮编辑后可能出现「覆盖了早期子改动、漏掉最新一个」的情况要求调用方自查每个被触碰文件是否被代表若有文件缺失应「find its consumer components and pass their paths tostories.findByComponent」——fallback 路由直接写在工具描述里。这正是 808 场景的底层机制colors.ts不是组件也不是故事它出现在unreachableFiles一类的「未覆盖」信号中于是 Agent 需要转向消费者路径。stories-find-by-component基于实时反向依赖图的 fallbackfindByComponent接受componentPaths绝对路径优先与可选的maxDistance导入深度上限默认值DEFAULT_MAX_DISTANCE返回按distance升序的匹配0 表示路径本身是 story 文件1 表示直接导入者2 表示传递依赖。其描述中的几句几乎就是 808 评测的注释definition.tswhen the changed file issharedinfrastructure (theme token, design token, util, hook, CSS module) it isnt itself a component — grep for its consumers and passtheirpaths, not the shared files.Never invent IDs from file names, feature names, or memory … only IDs returned by discovery tools resolve.Backed by Storybooks live reverse dependency graph, available only when the dev server runs a builder that supports change detection (e.g. Vite) — otherwise returns a typed error.从实现结构看findStoriesByComponent见同目录find-by-component.ts依托core/module-graph与core/module-graph-index两个内部服务做热状态查询 冷反向索引查询的组合pathNotFound字段还能区分「路径拼错」与「该组件尚无故事」。评测断言「storyId 必须来自发现工具」与工具层「ID 来自实时故事索引、禁止编造」的设计首尾呼应。stories-preview 与 review 的开关节点preview方法的描述按reviewEnabled分成两种语气describePreview见 definition.tsreview 关闭808 MCP 默认路径「Call it after editing anything that changes how the UI looks — components, stories, styles, CSS, themes, colors, ordesign tokens— no exceptions.A shared file has no stories of its own: preview the stories of the components that consume it.」——这句与 PROMPT.md 的任务设定完全同构requiresDevServer: true保证预览 URL 指向活的 originreview 开启plugin 路径预览退化为迭代中的中间工具视觉工作的收尾必须是review.create并以?path/review/链接作为最终回复的一部分。评测侧的 review 开/关分支断言上节的两组describe.runIf正是对这两份工具描述的镜像验证。工具 API 的公开文档见 docs/ai/mcp/api.mdx 与 docs/ai/mcp/overview.mdx。本地运行这条评测线按 agent-eval/README.md 的说明评测在仓库根目录以 workspace 方式运行# 先安装依赖并配置 .env.localANTHROPIC_API_KEY / OPENAI_API_KEY及 Vercel Sandbox 凭据 yarn install cp .env.example .env.local # 零成本预览将要运行的内容 yarn workspace agent-eval run eval:dry # 只跑 808 这一个评测逐个调试的标准姿势 EVAL_ONLY808-shared-infra-fallback yarn workspace agent-eval run eval # 跑完整 8xx 线12 条工作流评测 × 各实验 EVAL_EXTRA_EVALS1 yarn workspace agent-eval run eval # 让 MCP 实验也走 review-on 工作流 EVAL_REVIEW1 yarn workspace agent-eval run eval # 本地查看结果 playgroundhttp://localhost:3000 yarn workspace agent-eval run playground需要说明的三个前提默认只跑第一个核心评测801-create-component-no-launch-config808 需要EVAL_ONLY或EVAL_EXTRA_EVALS1才会执行ci:eval等 CI label 仅限人工触发本地先构建 MCP 包沙箱注入的是本 checkout 的storybook/addon-mcp/storybook/mcp本地构建先执行yarn nx run-many -t compile --projects mcp,addon-mcp否则陈旧的dist会让沙箱 Storybook 在 preset 加载时崩溃表现为就绪超时而非构建错误成本README 给出单条工作流评测约 $0.30–0.80 的每运行均值完整一次 8xx 运行约 $30–45预算护栏为 $75/次。小结这条评测线验证了什么808-shared-infra-fallback表面上是一条「把蓝色 token 改成紫色」的任务实质上是 Agent 与 Storybook 工作流之间的一段契约测试改动的真实性新值出现、旧值消失直接读文件断言验证的完备性test-run必须跑到且跑绿并覆盖两个消费者的故事故事 ID 的可溯源性发现工具调用先于评审发布storyId 只来自stories-changed或stories-find-by-component禁止凭记忆或文件名编造视觉结果的正确呈现review-on 发带消费者故事 ID 的评审review-off 给消费者故事的预览链接且最终回复必须携带对应链接fallback 的条件义务diff 能覆盖消费者时不必调用覆盖不到时调用是硬性要求。对读者而言这份 fixture 的价值在于它把「共享基础设施变更」这个在真实设计系统中最常见的边缘场景拆解成了可复用的评测结构PROMPT.mdEVAL.ts 模板元数据、可执行的行为断言lib/test-utils.ts 的断言族以及可审计的工具实现open-service stories 工具集。三者一一对应既是观察 Storybook 对编码 Agent 提供工作流能力的窗口也是构建自己 Agent 行为评测时的一个可直接参照的工程范本。【免费下载链接】storybookStorybook is the industry standard workshop for building, documenting, and testing UI components in isolation项目地址: https://gitcode.com/GitHub_Trending/st/storybook创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表