移动/游戏
评估 Expo skill 的触发准确性、生成代码质量与…
在 macOS 上端到端评估 plugins/expo/skills/ 下的 skill:用 claude -p 测触发、用静态检查与 Expo Go/开发构建截图验证生成代码。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 38/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 55/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 60/100 ×0.15
适用场景
评估 Expo skill 的触发准确性特定
解决思路
- 在开始任何 pipeline 工作前,通过 AskUserQuestion 分批(每批 ≤4 个问题)确认所有事项,顺序为:要评估哪个 skill;Prompts;要验证什么;Expo SDK;Runner;Platforms;claude -p 权限标志;Viewer 交付;如果选 Trigger accuracy 则确认已发布 expo 插件已禁用。请求已说明的项跳过。
- 如果请求中不明确要评估哪个 skill,列出 plugins/expo/skills/ 下的可用 skills 并询问要评估哪一个。
- 理解并遵循 skill 加载机制:executor 运行通过文件路径引用 skill(SKILL_PATH = plugins/expo/skills/<skill>/SKILL.md,显式读取);trigger eval 将 skill 作为插件加载(--plugin-dir plugins/expo,使模型能根据描述自动选择)。两者都指向本地仓库内版本。harness 自身不需要特殊标志;这些机制应用于它生成的 claude -p 子进程。
- 当 trigger eval 在范围内时,运行前检查:如果已安装/启用已发布的 expo 插件,在启动 harness 前通过 /plugin 禁用它,之后重新启用。向用户明确确认这一点;如果仍启用,暂停并让用户禁用。executor/runtime/static 阶段不受影响,没有 trigger eval 的运行可跳过此禁用。
- 选择 prompts:通过 AskUserQuestion 确认(如果请求已命名 prompt 则跳过)。内置 prompts:读取被测 skill 的 SKILL.md + references/ 和 references/runtime-matrix.md,生成代表性 prompts,覆盖标准用例;如果 skill 已有 evals/evals.json,合并其 prompt 字段;预选全部。
- 自定义文本 prompt:依赖 AskUserQuestion 自动添加的 'Type something' / Other 条目,不要专门占选项槽。
- 从上传截图构建:用户提供目标截图路径,executor 用 Read 工具打开并构建匹配 app,case 记录路径为 reference_image,评分比较生成 app 与目标。
- 遵守 AskUserQuestion 每问 4 选项上限的优先级:始终保留 'Build from an uploaded screenshot' 槽位;不要添加显式 'Custom text prompt' 选项;剩余 ≤3 个槽位放内置/代表性 prompts 并预选;如果超过 3 个,合并为一个预选的 'All built-in prompts (default)' 选项,并在简短后续中提供子集选择,以便仍能放下上传选项。
- 将 prompts 呈现为多选。当用户选择 'Build from an uploaded screenshot' 后,在后续追问中询问目标图片路径。每个选中的 prompt(内置、输入、图片)成为一个 eval case(运行 with-skill 和 without-skill)。
- 确认要验证什么:除非请求明确无歧义,否则始终确认。提供选项并让用户选一个或多个:Runtime + screenshots(全流程:fixture → executor → static gate → 在 iOS/Android 上运行 app 并截图;runner 是单独问题;默认用于渲染 app 屏幕的 skill;需要已启动的模拟器/模拟器),Trigger accuracy(通过 claude -p 运行现实 prompts,检查 skill 是否被读取;衡量 recall,仅 should-trigger 查询;始终有用),Code checks (no device)(tsc --noEmit + diff-aware lint + expo export,grader 根据自定义期望检查生成代码;无设备;默认用于 static-only 和 n/a skills)。
- 将「要验证什么」呈现为一个多选问题——'What do you want to verify?'。当 prompt 是上传截图时,包括 'Runtime + screenshots'。在建议前读取 references/runtime-matrix.md 查找 skill 的默认模式。如果请求已指定模式,跳过问题并继续。
- 选择 Expo SDK 版本一次,在开始时:用 bash /abs/path/expo-skill-eval/scripts/latest-sdk.sh 检测最新(打印 major,如 56)。不要自己内联运行注册表查询,会提示。
- 用 AskUserQuestion 确认 SDK:默认该最新 SDK,或让用户固定旧版本。在构建 fixture 的所有地方使用所选版本——作为 <sdk> 参数传给 make-fixture.sh,并写入每个 eval case 的 runtime.sdk。如果请求已命名版本,跳过检测并使用它。
- 默认最新 SDK,因为它与 expo start 在设备上安装的 Expo Go 兼容。固定比设备已安装 Expo Go 更旧的 SDK 会使 expo start 尝试提示 'Install the recommended Expo Go version?';无 TTY(快照脚本从 /dev/null 读 stdin)会因 Input is required, but 'npx expo' is in non-interactive mode 失败,所有快照失败。所以只有同时预装匹配 Expo Go 时才固定旧 SDK,否则用最新。
- 选择 runner:Expo Go(默认)或 development build。用 AskUserQuestion(如果请求已说明则跳过)。
- Expo Go:快照脚本按原样用 expo start --ios / expo start --android 运行 app;快速(无原生编译),运行 Expo Go 打包的任何内容(包括 SDK 56+ 上的 @expo/ui);不能运行自定义原生代码(expo-modules、config plugins、Expo Go 中不存在的原生依赖)。
- Development build:快照脚本用 expo run:ios / expo run:android 代替,为每个 fixture 编译原生 dev client;用于输出需要自定义原生代码的 skill;慢很多——expo run 预构建并原生编译每个 fixture(分钟级,尤其第一次),需要完整 iOS/Android 构建工具链;只在该 skill 实际需要原生代码时选择。
- Dev-build 磁盘管理:每个 fixture 的原生构建是数 GB。快照阶段在每个 fixture 后运行 clean-fixture.sh 以保持峰值约一个构建,但仍优先较少 eval cases 和单一平台用于 dev-build 运行,并保持几 GB 空闲。clean-fixture.sh 移除每个 fixture 的构建输出(node_modules、ios、android、.expo、dist,以及 fixture 的 iOS DerivedData)并保留 app 源码 + git。dev-build 磁盘的杠杆是更少 eval cases + 一个平台。
- 通过 EXPO_SKILL_EVAL_RUNNER 环境变量传递 runner 选择(expo-go 默认,或 dev-build),并反映在每个 eval case 的 runtime.mode(expo-go 或 dev-build)。见步骤 5。
- 选择平台:总是询问,无论 skill。提供 iOS / Android / web(多选)用 AskUserQuestion;默认 iOS + Android,但总是提供 web 作为选项——不要按 skill 预过滤。
- Web 对大多数 skill 是有效选择:@expo/ui 的通用组件(Host、Row、Column、Button、List 等)在 web 上渲染,expo-dom、NativeWind/Tailwind、API routes、纯 React Native 也是如此。唯一不会在 web 上显示的是平台特定的原生树(@expo/ui/swift-ui 或 @expo/ui/jetpack-compose),它在 web 上渲染为空白——而空白本身是有用信号,所以仍是用户的选择。
- Web 通过 snapshot-web.sh(expo start --web + Playwright/Chromium)运行,无论 runner 如何(expo run 仅原生;没有 web dev build),它是最少被测试的路径。将所选集合写入每个 eval case 的 runtime.platforms,并让 run_snapshots.py 循环它们。
- 确认 claude -p 子进程如何运行一次,在开始前:用 AskUserQuestion 问是否可以用 --dangerously-skip-permissions,然后对本次运行的每个子进程应用同一答案(运行中绝不重新提示)。
- 选项 Skip permissions(推荐):传 --dangerously-skip-permissions;每个子进程在 /private/tmp/expo-skill-eval-* 下的一次性 fixture 内无人值守运行,可写文件和运行安装命令而无需提示。
- 选项 Accept edits only:传 --permission-mode acceptEdits;Bash/安装被自动拒绝(无 TTY),所以某些 eval 可能产生部分输出。
- 裸 claude -p 无任一标志根本无法写文件。如果请求已说明偏好,跳过问题。
- 确认如何交付结果 viewer 一次,在开始时:发布到 claude.ai 是外向的,所以绝不在运行中意外发布;在与权限标志相同的 upfront AskUserQuestion 中询问。
- Local only(默认):generate_viewer.py 写 viewer.html 并在本地浏览器打开;无内容离开机器。
- Publish a shareable Artifact:额外在最后将 viewer 渲染为 claude.ai Artifact(默认私有网页,用户可与队友分享)。仅当用户在此 opt in 时才做。
- 如果请求已说明是否分享/发布,跳过问题。见 Viewer 部分了解发布机制。
- 生成 eval cases:每个选中的 prompt 一个,写入 <workspace>/iteration-N/evals.json。每个 case 扩展标准 skill-creator eval-case 形状,带 runtime 块和视觉期望。使用提供的 JSON 结构。
- runtime.mode 取值:expo-go(在 Expo Go 中运行并截图,快速,仅 JS,默认),dev-build(构建原生 dev client 并截图,用于自定义原生代码,慢很多),static-only(在静态门后停止,用于无 UI 的 skill,或不想运行设备时)。
- runtime.platforms:ios、android、web 的子集——开始时选择(总是提供,不按 skill 门控)。默认 ['ios', 'android']。
- runtime.sdk:fixture app 的 Expo SDK major——设为开始时选择的版本;省略则用最新模板。
- reference_image(可选——图像 prompt):目标截图的绝对路径,skill 必须重现。当设置时,executor 被告知打开它(通过 Read 工具)并构建匹配 app,grader 在常规期望之上评估生成 app 与目标的匹配程度。在 Prompts 阶段通过 'build from an uploaded screenshot' 设置。
- 图像 prompt case 是正常 case 带 reference_image;启用 'Runtime + screenshots',以便 harness 捕获结果与目标比较。
- 遵循编排模型:在主线程上你运行 python3 <orchestrator> 和几乎其他什么都不做。每个阶段由你 Write 到工作区并用 python3 /private/tmp/expo-skill-eval-<skill>/<phase>.py 运行的小 Python orchestrator 驱动。orchestrator 是唯一调用 scripts/*.sh 文件的地方——总是通过 subprocess.run(['bash', '<scripts>/<name>.sh', …])。在主线程你只:Write orchestrators,用 python3 运行它们,用 Read/Glob/Grep 工具检查输出,并生成 grader subagent。
- 绝不在链式/后台/管道 shell 构造中放命令,绝不运行 ad-hoc mkdir/ls/cat/tail/echo——那会提示。单个独立 bash …/scripts/<name>.sh … 可用于一次性手动调试,但 pipeline 本身通过 orchestrators 走。
- 每个 orchestrator 前台运行——让工具调用阻塞直到完成;orchestrator 已在阶段内并行化。不要用 shell 后台运行阶段(… & echo '$!' / wait)。如果确实必须在继续其他工作时运行阶段,使用 Bash 工具的 run_in_background 参数在普通 python3 <orchestrator> 2>&1 | tee <ws>/…log 调用上——绝不要手写 shell &。
- 预期开始时正好一个权限提示:第一次 Write 到工作区。allowed-tools 可以抑制 Bash/Read 但不能 Write/Edit,所以在第一次提示选择 'allow all edits in this directory for the session'——它覆盖整个运行的每个 orchestrator、evals.json 和 viewer 文件。
- 步骤 0 工作区设置:用工作区脚本一次性创建运行目录树——绝不用 ad-hoc mkdir。命令:bash /abs/path/expo-skill-eval/scripts/make-workspace.sh /private/tmp/expo-skill-eval-<skill> iteration-N <num-evals>。这为每个 eval 创建 trigger-evals/scratch 和 iteration-N/eval-<i>/{with_skill,without_skill}/outputs。之后,每个其他目录由需要它的脚本/orchestrator 创建或由 Write 工具自动创建父目录。
- 步骤 1 Trigger eval(仅 should-trigger):写 run_trigger_eval_real.py 到工作区的 trigger-evals/ 目录。仅用 'should_trigger': true 查询。仅测 recall:应该使用 skill 的现实 prompts,按触发率评分。
- Trigger eval 脚本为每个查询运行 claude -p <query>,带 --output-format=stream-json --verbose --include-partial-messages,从环境剥离 CLAUDECODE,以及 upfront 确认的权限标志。注意:--include-partial-messages 需要 --output-format=stream-json 和 --verbose 两者。
- 通过观察流中目标 skill 的 Skill 或 Read 工具调用来检测目标 skill 是否被触发。
- 加载被测 skill——给每个 trigger 子进程传 --plugin-dir,参数为拥有该 skill 的插件目录的绝对路径——包含 .claude-plugin/plugin.json 的 plugins/expo 祖先。必须是绝对路径,因为子进程从一次性 scratch/ cwd 运行。然后观察 skill 在其插件限定名下触发(<plugin>:<skill>,例如 expo:expo-ui)。
- 如果已发布的 expo 插件也在全局安装,在运行中通过 /plugin 禁用它,之后重新启用——否则 expo 的两个副本在子进程中冲突,模型可能触发已发布的 expo:expo-ui,静默评分其描述而不是你的本地编辑。
- 绝不要合成重复 skill——真实加载的副本总是胜出,所以合成 harness 得 0%。
- 从空的一次性 cwd(例如 trigger-evals/scratch/)运行每个查询的子进程,而不是仓库根。触发检测只需要 skill 的 Skill/Read 调用出现在流中——它不需要 fixture——所以任何附带写入都是 throwaway。
- 设置每查询子进程超时至少 300 秒。180s 限制太短——某些查询导致模型在触发 skill 前开始生成代码,推高总运行时间超过 3 分钟。
- Trigger evals 每个 skill 运行一次,不是每个 code eval case。
- 步骤 2 Fixture:每个 executor 运行获得一个全新 Expo app,由 scripts/make-fixture.sh <app-path> <sdk> [clean|full] 创建。默认空白 app:scripts/make-fixture.sh <workspace>/iteration-N/eval-X/<config>/app <sdk>;保留 example tabs:scripts/make-fixture.sh <workspace>/iteration-N/eval-X/<config>/app <sdk> full。
- make-fixture.sh 用 bunx create-expo-app -t default@sdk-<version>(或无版本时最新模板)创建 app 一次每个 SDK 版本 + 变体,缓存到 ~/.cache/expo-skill-eval/fixtures/,并用 APFS copy-on-write 克隆缓存——所以每变体首次运行支付安装成本,之后每次运行几乎即时。
- 默认 clean 变体运行模板的 reset-project 脚本,所以 executors 从空白 app 开始,输出中的每个屏幕都是他们的——更干净的评分信号。仅当 eval prompt 假设现有 app 时用 full(例如 'I have an app with two tabs...')。
- make-fixture.sh 还重置克隆内的 git,所以 app 中的 git diff 显示 executor 改了什么(对 grader 有用的证据)。
- 顺序构建 fixtures,然后 fan out executors——绝不要并发创建 fixtures。make-fixture.sh 共享 ~/.cache/expo-skill-eval/fixtures/ 下按 SDK+变体键控的缓存。如果两个运行都发现缓存冷并同时调用 bunx create-expo-app,bun 的链接步骤冲突,一个失败并报 EEXIST / 'could not determine executable to run for package create-expo-app'。
- 在 executor orchestrator(步骤 3)中,先一次一个创建所有 fixtures——一个普通 Python 循环调用 subprocess.run(['bash', '<scripts>/make-fixture.sh', app, sdk, variant])(其中 sdk 是 upfront 选择的版本)——然后 fan out claude -p executors 用 ThreadPoolExecutor。顺序创建便宜:只有每个 SDK+变体的第一个 fixture 支付安装成本;其余是 ~1s APFS 克隆。绝不要用 ad-hoc shell 如 make-fixture.sh A & make-fixture.sh B & wait fan fixtures。
- 步骤 3 Generate(executor subagents):将 executors 作为 claude -p 子进程调用从 Python 脚本运行,不要通过 Agent 工具。Agent 工具生成具有自己权限上下文的子代理——fixture app 内的文件编辑会提示用户。一个 claude -p 子进程是完全在权限系统外的独立进程。
- 写一个 Python 脚本到 /private/tmp/expo-skill-eval-<skill>/run_executors.py。先顺序循环创建每个运行的 fixture,然后通过 ThreadPoolExecutor 并行运行 with-skill 和 without-skill claude -p 调用。
- 每个 executor prompt 必须包括:skill 路径(仅 with-skill 运行)和 eval prompt;图像 prompt cases(设置了 reference_image):目标截图的绝对路径加指令 'Open the reference screenshot at <path> with your Read tool and build an app whose UI matches it as closely as you can — layout, components, spacing, and colors.';fixture app 路径:'Make your changes inside <app-path>. The project already exists and has dependencies installed. Use absolute paths for all file operations.';'Before writing any files, inspect the project layout — run ls, read package.json and app.json — to find the correct routes directory. Recent SDK default templates place Expo Router routes in src/app/; older ones use app/ at the project root — inspect to confirm which this fixture uses.';'Do NOT start the dev server, boot simulators, or take screenshots — the harness does that after you finish.';在哪里保存所构建的简短摘要。
- claude -p 子进程的标志:从环境剥离 CLAUDECODE(env = {k: v for k, v in os.environ.items() if k != 'CLAUDECODE'})——否则嵌套在运行的 Claude Code 会话内时 claude -p 会静默挂起。
- 传权限标志,已与用户 upfront 确认:要么 --dangerously-skip-permissions 要么 --permission-mode acceptEdits。将选定标志烘焙到生成的脚本中。裸 claude -p 无任一标志不能写文件。
- 不要给 executors 传 --plugin-dir(与 trigger eval 不同)。with-skill 运行已通过其绝对 SKILL_PATH 读取 skill;without-skill 运行必须完全没有 skill 可用——加载插件会让 skill 自动触发并污染基线。
- 捕获每次运行的 stdout/stderr 到 fixture 旁的日志文件作为评分证据。设置超时 900s 每个 executor。
- 步骤 4 Static gate:写 run_static.py 并用 python3 运行它。对每个 eval/config app 它调用 subprocess.run(['bash', '<scripts>/check-static.sh', app, 'ios,android'], capture_output=True, …) 跨 ThreadPoolExecutor(静态门是独立的——在 Python 内并发运行它们,绝不用 shell &/wait),并将每个结果写入 eval-<i>/<config>/static.json(退出码 + 捕获输出)供 grader 使用。
- check-static.sh 为列出的平台运行 tsc --noEmit、expo lint 和 expo export。通过的 export 在未接触设备的情况下捕获大多数 import/语法/缺失模块失败;失败的 export 用干净 FAIL 短路步骤 5——记录它并让快照 orchestrator 跳过该 app。
- 步骤 5 Run + screenshot(跨 evals 串行):写 run_snapshots.py 并用 python3 运行它。模拟器和模拟器是共享资源,所以这个 orchestrator 串行运行(无线程池):对每个通过静态门的 app,每个平台,它 os.makedirs outputs/ 目录并调用 subprocess.run(['bash', '<scripts>/snapshot-<platform>.sh', app, f'{outputs}/<platform>.png', port], env={**os.environ, 'EXPO_SKILL_EVAL_RUNNER': runner}, …)。将端口作为位置参数传递:iOS 用 8081,Android 用 8082。
- 每个 fixture 后回收磁盘——对 dev-build 运行至关重要。一旦一个 app 的所有选中平台被捕获(并且在下一个 fixture 构建之前),调用 subprocess.run(['bash', '<scripts>/clean-fixture.sh', app])。clean-fixture.sh 移除 node_modules、ios、android、.expo、dist 和 fixture 的 iOS DerivedData,保留 app 源码 + git 以便 grader 的 git diff 仍工作。
- runner 是 upfront 选择(expo-go 默认,或 dev-build)。快照脚本遵守 EXPO_SKILL_EVAL_RUNNER:expo-go 用 expo start --<platform> 启动;dev-build 用 expo run:<platform> --port <port> 启动,编译+安装原生 dev client 并跳过 Expo Go 步骤。如果第一次原生编译需要更高,提高 EXPO_SKILL_EVAL_BUNDLE_TIMEOUT。
- make-fixture.sh 在每个 fixture 预装 expo-dev-client,所以 dev-client URL scheme 在 expo run 尝试深链接打开 app 前已注册。
- 快照脚本总是捕获初始路由 /。它们通过深链接打开 app 并拍一张截图——它们不能点击或导航。设计 eval prompts 使被测功能在根路由渲染。
- 每个 snapshot-<platform>.sh 在启动时释放其 Metro 端口并拆除 Metro 退出。然后它启动 Metro,等待 Metro 日志中的 'Bundled' 行,稳定,捕获截图,并拆除 Metro。iOS 启动最新可用 iPhone 模拟器如果无已启动;Android 启动第一个 AVD 如果无设备连接。Android 首先回收 wedged/offline 模拟器(优雅 adb emu kill,然后强制杀死 + adb reset),并以硬件 GPU 启动(-gpu host)。如果 host 自中止,编辑 snapshot-android.sh 中 GPU_MODE 为软件模式(guest 可靠但慢——增大 settle;避免 swiftshader_indirect,它在 arm64 上启动时挂起)。snapshot-web.sh 仅在 platforms 包含 web 时运行。每个脚本在截图旁写一个 Metro 日志(<name>.metro.log)——包含在 grader 的输入中。如果脚本非零退出,它仍尝试最佳截图。
- dev-build 重启:Metro 起来后,脚本通过 xcrun simctl launch(iOS)和 adb shell am start -n <pkg>/.MainActivity(Android)重启 app——两者避免首次启动时 URL-scheme 深链接触发的 'Open in X?' 系统对话框。
- 所有该 iteration 的截图捕获后,总是生成 viewer——将工作区根传给已检入的脚本:python3 /abs/path/expo-skill-eval/scripts/generate_viewer.py /private/tmp/expo-skill-eval-<skill>。它将 viewer.html 写入工作区根并自己在浏览器打开(通过 webbrowser.open)。
- 步骤 6 Grade:在前台生成 grader subagent。它的 prompt 必须包括:来自 eval case 的 eval prompt、expectations 列表和 visual_expectations;agents/visual-grader.md 中的指令(截图评分、redbox 检测);截图文件、Metro 日志和步骤 4 的 static.json 作为输入;图像 prompt cases(case 有 reference_image):还包括目标截图(reference_image)、references/design-rubric.md 和 fixture 的 git diff。告诉 grader 比较生成的截图与目标并发出 reference_match + quality 块。
- grader 在 outputs 旁写 grading.json,形状如提供的 JSON(score, max_score, expectations, reference_match, quality, user_notes_summary)。视觉期望进入同一 expectations 数组,证据命名截图文件并描述可见内容。reference_match 和 quality 仅对图像 prompt cases 发出——或当明确要求质量评分时。对纯文本 prompt 运行省略两者。
- Rollout 阶段:按顺序构建和调试 pipeline——每个阶段独立有用:1. Static:仅步骤 1–4(runtime.mode: 'static-only' 所有)。无需设备;CI 友好。2. iOS:将 snapshot-ios.sh 加入循环。simctl 是最可脚本化的目标。3. Android:加入 snapshot-android.sh。模拟器启动是最慢部分——保持一个模拟器运行整个会话。4. Web:为面向 web 的 skill 加入 snapshot-web.sh(通过 bunx 使用 Playwright;首次运行下载 Chromium)。
- 临时位置:所有 eval 工作区在 /private/tmp/expo-skill-eval-<skill-name>/iteration-N/。本次运行的一切——Read、Write、Edit 和 Bash——由 allowed-tools frontmatter 覆盖,所以正确加载的 skill 运行无提示。
- 权限规则形式:Bash(...) 规则路径限定到 skill 自己的代码。使用 Bash(python3 /private/tmp/expo-skill-eval-*)(加 /tmp 别名)运行生成的 Python orchestrators;Bash(python3 *expo-skill-eval/scripts/*) 运行已检入的 scripts/generate_viewer.py;Bash(tee /private/tmp/expo-skill-eval-*)(+ /tmp)让 python3 … 2>&1 | tee <workspace>/…log 写日志无提示;Bash(bash *expo-skill-eval/scripts/*) 仅运行此 skill 的 scripts/*.sh。不要用 python3 -c …、bash -c …、tee /etc/… 或在其他任何地方运行代码。
- 不要从主线程运行 ad-hoc mkdir/ls/find/cat/grep:用 make-workspace.sh 创建目录树(步骤 0),让 orchestrators 创建自己的目录(os.makedirs),并用 Read/Glob/Grep 工具检查结果。
- Bash 规则匹配细节:* 匹配任何字符包括 / 和空格,并在模式中间工作。** 被字面匹配,绝不要在 Bash 规则中用 **。:* 后缀只在命令 token 后工作(Bash(python3:*)),不在部分路径后。复合命令按 |、&&、||、;、& 分割,每段需要自己的匹配规则。
- Read 规则抑制提示;Write/Edit 规则不。文件创建/编辑总是通过 Claude Code 的编辑批准流。在运行开始时你得到一个 Write 提示用于工作区——选择 'Yes, allow all edits in this directory for the session',然后每个后续写入该工作区下的都静默通过。
- 编辑 frontmatter 后重新加载——完全重启,不是 /reload-skills。完全退出 Claude Code 并开始新会话,然后重新运行 skill。Grader subagents 以自己的权限上下文运行,仍会提示文件访问——这是预期的。
- 调用 eval 脚本——一个独立命令,绝不链式。将每个脚本作为自己的 Bash 调用用绝对路径调用:bash /abs/path/expo-skill-eval/scripts/snapshot-ios.sh arg1 arg2。不要与 &、&&、||、;、wait、tail、head 或 echo 组合。唯一允许的管道是 … 2>&1 | tee <workspace>/…log。需要并行或输出修剪?放在 Python orchestrator。用 Read/Glob/Grep 工具检查结果。
- 检查输出(截图、日志、文件)——用工具,不是 shell。查找文件用 Glob 工具(例如 /private/tmp/expo-skill-eval-<skill>/iteration-N/**/ios.png);查看它们用 Read 工具;搜索文件内容用 Grep。绝不用 find/ls/cat。
- 生成的 Python 脚本:写编排/聚和脚本到工作区(例如 /private/tmp/expo-skill-eval-<skill>/aggregate.py)并用 python3 运行。viewer 是例外——通过 Bash(python3 *expo-skill-eval/scripts/*) 运行 scripts/generate_viewer.py。Write 自动创建父目录但首次提示——批准工作区目录一次。捕获输出要么让脚本写自己的日志,要么通过 python3 … 2>&1 | tee <workspace>/…log;用 Read 工具读回日志。不要用 python3 -c … 做设置。
- Trigger evals vs installed plugin:在流中检测真实安装的 skill 名(例如 expo:expo-ui)——合成重复 harness 总是得 0% 当真实插件安装时。
- Benchmark 聚合:保存每次运行的 grading.json + timing.json 在 eval-<N>/<config>/run-1/ 下。写一个 Python 聚合脚本到工作区并用 python3 运行。
- Expo Go 上限:任何需要自定义原生代码(expo-module、App Clips、brownfield)不能在 Expo Go 运行。对那些用 static-only 模式——为 skill 写 eval cases 前见 references/runtime-matrix.md。注意:@expo/ui 在 SDK 56+ 确实在 Expo Go 运行。
- API 路由 skills:不用截图,用 curl 对路由验证而 Metro 起来;记录响应为输出文件供评分。
- Timing 数据:每次 executor 运行后立即捕获 token 计数和持续时间到 timing.json——它不可恢复。给 executor claude -p 调用加 --output-format=stream-json --verbose 并从日志解析 message_start / message_delta 事件。
- 首次启动对话框:Expo Go 偶尔在全新模拟器上显示一次性提示。如果截图捕获对话框而不是 app,重新运行快照脚本(它重新打开 URL)并重新捕获。
- Viewer:截图后,总是生成并打开 HTML viewer。viewer 是已检入的 scripts/generate_viewer.py——用工作区根作为参数运行:python3 /abs/path/expo-skill-eval/scripts/generate_viewer.py /private/tmp/expo-skill-eval-<skill>。它写一个自包含的 viewer.html 并自己在浏览器打开。
- Viewer 渲染:每个 iteration 一个标签;对每个 eval case 并排 with_skill / without_skill 列,显示静态门状态、分数、平台截图(点击缩放;嵌入为 base64 data: URI)、期望列表带 PASS/FAIL 徽章和 reviewer 注释;对图像 prompt cases 显示目标截图、reference_match 分数、quality rubric、quality delta;摘要条带 with_skill %、without_skill % 和 delta;当 trigger-evals/trigger_results.json 存在时有 trigger accuracy 表;深色背景与颜色编码分数。
- 发布 viewer(仅当 upfront opt in):仅当用户选择 'Publish a shareable Artifact' 时,额外在最后渲染为 claude.ai Artifact。机制:Artifact 工具用自己的骨架包装文件,所以文件必须只有页面内容——内联 <style>/<script>、base64 data: 图片和 <title>,但没有 <!DOCTYPE>/<html>/<head>/<body> 标签。脚本在你添加 --artifact 时发出 Artifact 友好变体:python3 /abs/path/expo-skill-eval/scripts/generate_viewer.py /private/tmp/expo-skill-eval-<skill> --artifact 写 viewer_artifact.html。将那个文件传给 Artifact 工具(favicon: '📊'),不是独立文件。
- References:使用 references/runtime-matrix.md 查找每个 skill 运行时适用性(expo-go vs static-only,平台注释)。使用 agents/visual-grader.md 获取 grader subagent 的截图评分指令。
适用边界 · 注意事项
- 评估需要自定义原生代码的 Expo skill 时,不要使用 Expo Go 运行时模式(用 static-only 或 development build)。
- 没有 macOS、Xcode、Android SDK 和 bun 环境时,无法执行完整设备快照 pipeline。
- 已发布 expo 插件未禁用时,不要运行 trigger eval,否则会污染触发检测。