expo-skill-eval について
expo-skill-evalは、Expoプラグインリポジトリ内のスキルを評価するためのスキルです。プロンプトと検証方法を選び、トリガー精度、生成コード、アプリの描画結果やスクリーンショットを確認する手順を案内します。
主な機能
- 01Expoスキルの評価
- 02組み込み・独自・画像プロンプトの選択
- 03スキルのトリガー精度確認
- 04デバイス不要のコードチェック
- 05実行時スクリーンショットの取得
活用シーン
- 01関連プロンプトでスキルが起動するか測定
- 02生成されたアプリコードが要件に合うか確認
- 03描画画面と目標スクリーンショットを比較
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ expo/ skills --skill expo-skill-evalインストール後、Claude Code で/expo-skill-evalを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
agents/visual-grader.mdreferences/design-rubric.mdruntime-matrix.mdscripts/check-static.shclean-fixture.shgenerate_viewer.pylatest-sdk.shmake-fixture.shmake-workspace.shsnapshot-android.shsnapshot-ios.shsnapshot-web.sh詳細
Expo スキル評価
plugins/expo/skills/ 内のスキルについて、トリガーの精度、生成されるコードの品質、および/または Expo Go での実行時のレンダリングを評価します。
必要なもの:Xcode(iOS シミュレーター)を搭載した macOS、少なくとも 1 つの AVD がある Android SDK、bun。その他のデバイスツールは想定していません。
ワークスペースのルート:/private/tmp/expo-skill-eval-<skill-name>/iteration-N/(例:/private/tmp/expo-skill-eval-expo-ui/iteration-4/)。
開始前 — 対象範囲を確認する
パイプラインの作業を始める前に、以下のすべてを必ず確認してください。省略しないでください(リクエストにすでに指定されている場合に限り、該当項目の確認を省略できます)。以下の順序で、1 回につき最大 4 問の AskUserQuestion 呼び出しにまとめてください。
- 評価するスキル(リクエストから明確でない場合)。
- プロンプト — 評価を進めるプロンプト。組み込みプロンプト(スキルの評価ケースから)はすべて事前選択されています。除外したり、カスタムのテキストプロンプトを追加したり、アップロードされたスクリーンショットから作成したりできます(スキルが再現する必要のある対象 UI)。詳しくは下記のプロンプトを参照してください。
- 検証する内容 — 次の 3 つの選択肢から 1 つを選ぶ複数選択:実行時+スクリーンショット/トリガーの精度/コードチェック(デバイスなし)。詳しくは下記の「検証する内容」を参照してください。
- Expo SDK — 最新版(デフォルト。自動検出)または固定バージョン。
- ランナー — Expo Go(デフォルト)または開発ビルド。
- プラットフォーム — iOS/Android/web(必ず 3 つすべてを提示してください)。
claude -pの権限フラグ — skip-permissions(デフォルト)または accept-edits。- ビューアーへの配信方法 — ローカルのみ(デフォルト)または共有可能な Artifact を公開。
- トリガーの精度を選択した場合 — 公開済みの
expoプラグインが無効になっていること(またはインストールされていないこと)を確認してください。
各項目の詳細は以下に記載しています。項目 4~6(SDK、ランナー、プラットフォーム)は、1 回の AskUserQuestion 呼び出しにまとめるのが自然です。
評価するスキルがリクエストから明確でない場合は、plugins/expo/skills/ 内で利用可能なスキルを一覧表示し、どれを評価するか尋ねてください。
テスト対象のスキルを読み込む方法 — フェーズごとに異なる 2 つの仕組み(全体でどちらか一方を選ばないでください):executor が実行するものはファイルパス(SKILL_PATH = plugins/expo/skills/<skill>/SKILL.md。明示的に読み込む)を参照し、トリガー評価ではスキルをプラグインとして読み込みます(--plugin-dir plugins/expo を指定することで、モデルが説明に基づいて自動選択できます)。どちらもリポジトリ内のローカル版を参照します。評価対象はこのローカル版です。ハーネスのセッション自体を起動するための特別なフラグは必要ありません(ハーネスはリポジトリのパスからスキルを見つけます)。これらの仕組みは、ハーネスが起動する claude -p サブプロセスに適用されます。フェーズごとに方法が異なる理由については、手順 1 と 3 を参照してください。実行前に必要な確認(トリガー評価を対象に含める場合は必須): 公開済みの expo プラグインがインストール済み/有効になっている場合は、ハーネスを起動する前に(/plugin から)無効にし、起動後に再度有効にしてください。無効化はグローバル設定の変更であり、このセッションと起動された claude -p サブプロセスの両方に反映されます。トリガー評価でこれが必要な理由:このフェーズではローカルスキルを --plugin-dir で読み込みますが、別の expo がインストールされていると競合します。モデルが公開済みの expo:expo-ui をトリガーする可能性があり、検出ではツール呼び出し名しか確認しないため、ローカルの変更内容ではなく公開済みの説明を誤って評価してしまいます(競合によってエラーが発生する場合もあります)。executor/実行時/静的フェーズには影響しません。これらのフェーズでは --plugin-dir を使わず、ローカルの SKILL_PATH からテスト対象のスキルを読み込むためです。そのため、トリガー評価を含まない実行では無効化を省略できます。expo を無効にしても expo-skill-eval は無効になりません(expo プラグインには含まれない独立したプロジェクトスキルです)。そのため、ハーネスは引き続き使用できます。
これを明示的な事前確認としてユーザーに提示してください。評価するスキルを確認するのと同じ方法です。トリガー評価が対象範囲に含まれる場合は、ステップ 1 を開始する前に、公開済みの expo プラグインが無効になっている(またはインストールされていない)ことをユーザーに確認してください。まだ有効になっている場合は、一時停止して /plugin から無効にしてもらいます。ユーザーが確認するまで、トリガー評価を実行しないでください。ハーネスはインストール済みのプラグインを自力で確実に検出できません(グローバルのプラグイン設定を読む場合や claude plugin list を実行する場合は、確認が求められます)。そのため、これは自動チェックではなく手動確認です。
プロンプトを選びます。組み込み、カスタム、または対象スクリーンショットから選択できます。 プロンプトは実行エンジンを動かす入力(スキルあり/スキルなし)であり、検証する対象とは別です。AskUserQuestion で確認してください(依頼ですでにプロンプトが指定されている場合は省略します)。
- 組み込みプロンプト — テスト対象のスキル(
SKILL.mdとreferences/)およびreferences/runtime-matrix.mdを読んで生成する、スキルの標準的なユースケースを網羅した代表的なプロンプトです。(スキルにevals/evals.jsonの評価ケースが含まれている場合は、そのpromptフィールドも組み込んでください。ただし、含まれているスキルはほとんどないため、通常は自分で作成します。)すべてを事前選択して、デフォルトの実行でスキルの標準ケースが試されるようにします。ユーザーが不要なものを選択解除できるようにしてください。 - カスタムテキストプロンプト — ユーザーが入力する単発のプロンプトです。専用の選択肢枠を使わないでください。
AskUserQuestionは「何かを入力」/その他の項目を自動で追加し、そこに入力された内容はカスタムテキストケースになります。 - アップロードしたスクリーンショットから作成 — ユーザーが対象スクリーンショットのパス(再現する UI)を指定します。実行エンジンにはその画像を開くよう指示します。
claude -pは Read ツールで PNG を読み取ります。画像に一致するアプリを構築し、ケースにはパスをreference_imageとして記録します。採点では、生成されたアプリと対象画像を比較します(ステップ 6)。これは UI スキルに対する最も強力な視覚テストです。「この UI を作ってください」という指示になります。
AskUserQuestion の質問ごとの選択肢上限 4 件を守り、次の優先順位に従ってください(避けるべき不具合:4 枠が埋まるとアップロードの選択肢がひっそりと削除されます)。
- 「アップロードしたスクリーンショットから作成」の枠を必ず確保してください。 これこそが視覚評価の目的なので、削除される選択肢にしてはいけません。
- 明示的な「カスタムテキストプロンプト」の選択肢は追加しないでください。自動追加される「何かを入力」/その他の項目で対応できます。
- 残りの最大 3 枠に、組み込み/代表的なプロンプトを事前選択して入れてください。3 件を超える場合は、すべてを事前選択した 1 つの**「すべての組み込みプロンプト(デフォルト)」**にまとめ、アップロードの選択肢を枠に収めるため、短い追加質問で一部を選べるようにします。
複数選択式で提示してください。「アップロードしたスクリーンショットから作成」が選ばれたら、追加質問で対象画像のパスを尋ねます。選択された各プロンプト(組み込み、入力、または画像)が 1 つの評価ケースとなり、スキルあり/スキルなしの両方で実行されます。
何を検証するかも必ず確認してください。依頼内容から明確に判断できる場合は除きます。次の選択肢を提示し、1 つ以上選んでもらってください(デフォルトは、スキルの references/runtime-matrix.md の項目に基づいて太字にします)。
| オプション | 内容 | デフォルトとして提案するタイミング |
|---|---|---|
| ランタイム+スクリーンショット | 全工程:フィクスチャ → エグゼキューター → 静的ゲート → iOS/Android でアプリを実行してスクリーンショットを撮影します。ランナー(Expo Go または開発ビルド)は別途確認するため、ここでは名前を挙げないでください。 | アプリ画面を描画するスキルではデフォルトです(references/runtime-matrix.md の expo-go/dev-build の行)。起動済みのシミュレーター/エミュレーターが必要です。 |
| トリガー精度 | claude -p で現実的なプロンプトを実行し、スキルが読み込まれるかを確認します。再現率を測定します(トリガーされるべきクエリのみ)。 | 単独のチェックとして常に有用です。 |
| コードチェック(デバイス不要) | tsc --noEmit +差分対応のリンター+expo export を実行します。さらに、指定したカスタム要件に照らして、生成されたコードをグレーダーがチェックします。デバイスは不要です。 | static-only と n/a のスキルではデフォルトです。また、アプリを実行せずにコードパターン(正しいインポートパス、Host ラッパーなど)を検証したい場合にも使用します。 |
| これらは「何を検証しますか?」という複数選択の質問として提示してください。 これらは採点の評価軸(構築されたものをどう評価するか)であり、プロンプトフェーズ(何を構築するか)とは異なります。ユーザーは任意の組み合わせを選択できます。プロンプトがアップロードされたスクリーンショット(プロンプトを参照)の場合は、「ランタイム+スクリーンショット」を含めて、ハーネスで生成されたアプリをキャプチャし、グレーダーが対象と照合して採点できるようにしてください。 |
提案する前に references/runtime-matrix.md を読んで、スキルのデフォルトモードを確認してください。リクエストですでにモードが指定されている場合(例:「トリガーされるかだけ確認」「デバイスで実行」)は、質問を省略してそのまま進めてください。
Expo SDK のバージョンを最初に一度だけ選びます。 bash /abs/path/expo-skill-eval/scripts/latest-sdk.sh で最新バージョンを検出します(メジャーバージョンを出力します。例:56。内部では bun を使って npm view expo dist-tags --json を実行し、JSON.parse/semver でメジャーバージョンを読み取ります。この処理は bash スクリプトのルールでカバーされるため、レジストリへの問い合わせを自分でインラインで実行しないでください。実行すると確認が求められます)。次に AskUserQuestion で確認します。デフォルトは最新 SDK とし、ユーザーがより古いバージョンを指定できるようにします(例:バージョン固有の問題を再現する場合)。フィクスチャのビルドでは、選択したバージョンをすべての箇所で使用してください。make-fixture.sh に <sdk> 引数として渡し、各評価ケースの runtime.sdk にも書き込みます。リクエストですでにバージョンが指定されている場合(「SDK 54 で評価」など)は、検出を省略してそのバージョンを使ってください。
デフォルトは最新バージョンにしてください。expo start がデバイスにインストールする Expo Go との互換性が保たれます。デバイスにインストール済みの Expo Go より古い SDK を指定すると、expo start が「推奨バージョンの Expo Go をインストールしますか?」と尋ねようとします。TTY がない場合(スナップショットスクリプトは標準入力を /dev/null から読み込みます)、Input is required, but 'npx expo' is in non-interactive mode というエラーで停止し、すべてのスナップショットが失敗します。そのため、より古い SDK を指定するのは、シミュレーター/エミュレーターに対応する Expo Go も事前にインストールする場合だけにしてください。それ以外は最新バージョンを使用してください。
ランナーを選びます。デフォルトは Expo Go、または開発ビルドです。 AskUserQuestion で尋ねてください(リクエストですでに指定されている場合は省略します)。
- Expo Go (デフォルト) — スナップショットスクリプトは、アプリをそのまま
expo start --ios/expo start --androidで実行します。高速で (ネイティブコンパイルなし)、Expo Go がバンドルするもの (SDK 56+ の@expo/uiを含む) はすべて実行できます。カスタムネイティブコード (expo-modules、config プラグイン、Expo Go に含まれないネイティブ依存関係) は実行できません。 - 開発ビルド — スナップショットスクリプトは代わりに
expo run:ios/expo run:androidを実行し、フィクスチャごとにネイティブの開発クライアントをコンパイルします。出力にカスタムネイティブコードが必要なスキル (そうでなければstatic-onlyになるケース) にはこちらを使用します。はるかに低速で —expo runはフィクスチャごとにプリビルドとネイティブコンパイルを行い (数分、特に初回)、完全な iOS/Android ビルドツールチェーンが必要です — そのため、スキルが実際にネイティブコードを必要とする場合にのみ選択してください。ディスクを大量に消費します: 各フィクスチャのネイティブビルドは数 GB になります。スナップショットフェーズでは各フィクスチャの後にclean-fixture.shを実行してピーク使用量を約 1 ビルド分に抑えますが、それでも開発ビルドの実行では評価ケースを少なくし、単一プラットフォームにすることを推奨し、数 GB の空き容量を確保してください。clean-fixture.shはフィクスチャごとのビルド 出力 (node_modules、ios、android、.expo、dist、およびそのフィクスチャの iOS DerivedData) を削除し、アプリのソース + git は保持します。開発ビルドのディスクを抑えるための手段は 評価ケースを減らす + 単一プラットフォーム です — これはフィクスチャごとのビルド出力のみを回収し、共有の依存関係キャッシュには一切触れないため、再ダウンロードは発生しません。
選択内容は EXPO_SKILL_EVAL_RUNNER 環境変数 (expo-go がデフォルト、または dev-build) を介してスナップショットスクリプトに渡し、各評価ケースの runtime.mode (expo-go または dev-build) に反映してください。ステップ 5 を参照してください。
プラットフォームを選択する — スキルに関係なく、常に確認してください。 AskUserQuestion で iOS / Android / web (複数選択) を提示してください。デフォルトは iOS + Android ですが、常に web を選択肢として提示してください — スキルによって事前に絞り込まないでください。Web はほとんどのスキルで有効な選択肢です: @expo/ui の ユニバーサル コンポーネント (Host、Row、Column、Button、List、…) は web でレンダリングされ、expo-dom、NativeWind/Tailwind、API ルート、および素の React Native も同様です。web で表示されない唯一のものは プラットフォーム固有 のネイティブツリー (@expo/ui/swift-ui または @expo/ui/jetpack-compose) で、これはそこで空白としてレンダリングされます — そしてその空白自体が有用なシグナルであるため、これもユーザーの判断となります。Web は ランナーに関係なく snapshot-web.sh (expo start --web + Playwright/Chromium) を介して実行され (expo run はネイティブ専用で、web の開発ビルドは存在しません)、最も実行頻度の低いパスです。選択したセットを各評価ケースの runtime.platforms に書き込み、run_snapshots.py でそれらをループさせてください。
claude -p サブプロセスの実行方法を確認する — 開始前に一度だけ。 AskUserQuestion で --dangerously-skip-permissions を付けて実行してよいか尋ね、その同じ回答をこの実行のすべてのサブプロセスに適用してください (実行中に再度プロンプトを出さないでください):
- 権限をスキップ (推奨) —
--dangerously-skip-permissionsを渡します。各サブプロセスは/private/tmp/expo-skill-eval-*配下の使い捨てフィクスチャ内で無人実行され、プロンプトなしでファイルの書き込みやセットアップコマンドの実行ができます。 - 編集のみ受け入れる — 代わりに
--permission-mode acceptEditsを渡します。Bash/インストールは自動的に拒否されるため (TTY なし)、一部の評価では部分的な出力になる場合があります。
どちらのフラグも付けない素の claude -p は、ファイルの書き込みがまったくできません。リクエストがすでに希望 (「権限をスキップ」、「危険なフラグを使わない」) を述べている場合は、質問を省略してください。
結果ビューアの配信方法を確認する — 事前に一度だけ。 claude.ai への公開は外部向けであるため、実行中に不意に行わないでください。同じ事前の AskUserQuestion で (権限フラグと並べて) 尋ねてください:
- ローカルのみ (デフォルト) —
generate_viewer.pyがviewer.htmlを書き出し、ローカルのブラウザで開きます。マシンから何も出ません。 - 共有可能な Artifact を公開 — さらに、最後にビューアを claude.ai Artifact (ユーザーがチームメイトと共有できるデフォルト非公開の Web ページ) としてレンダリングします。ユーザーがここでオプトインした場合にのみ行ってください。 リクエストに共有/公開するかどうかがすでに記載されている場合は、その質問を省略してください。公開の仕組みについては、Viewer セクションを参照してください。
Eval ケースのスキーマ
選択した各プロンプトにつき 1 件、実行の Eval ケースを生成し、<workspace>/iteration-N/evals.json に書き込みます(ビューアーはここから読み込みます)。各ケースは、標準の skill-creator Eval ケース形式に runtime ブロックと視覚的な期待値を追加したものです。
{
"id": 1,
"prompt": "Build me a settings screen with a dark mode toggle and a list of options",
"expected_output": "Working Expo Router screen",
"expectations": [
"Uses Expo Router file-based routing",
"TypeScript compiles with no errors"
],
"runtime": {
"mode": "expo-go",
"platforms": ["ios", "android"],
"sdk": "56"
},
"visual_expectations": [
"No red error screen or Expo Go error overlay on any platform",
"A settings screen with a visible toggle control is rendered"
]
}
-
runtime.mode:静的ゲートの後に Eval を実行する方法です。"expo-go":Expo Go で実行(expo start --<platform>)し、スクリーンショットを撮ります。高速で、JavaScript のみです。デフォルトです。"dev-build":ネイティブ開発クライアントをビルド(expo run:<platform>)し、スクリーンショットを撮ります。出力にカスタムネイティブコードを使うスキル向けです。かなり時間がかかります(フィクスチャごとにネイティブコンパイルが必要です)。"static-only":静的ゲートの後で停止します。UI を生成しないスキルや、デバイスをまったく実行したくない場合(CI)に使います。
どのリポジトリスキルがどのモードに対応しているかは、
references/runtime-matrix.mdを参照してください。(dev-buildを使うと、ネイティブコードが必要なため、以前はstatic-onlyにする必要があったスキルも実行できます。) -
runtime.platforms:ios、android、webのサブセットです。事前に選択します(常に選択肢として提示され、スキルによって制限されません。Before starting を参照してください)。デフォルトは["ios", "android"]です。 -
runtime.sdk:フィクスチャアプリに使用する Expo SDK のメジャーバージョンです。事前に選択したバージョンを設定します(Before starting — clarify scope を参照してください)。省略すると、最新のテンプレートが使われます。 -
reference_image(任意 — 画像プロンプト):スキルが再現する必要のあるターゲットのスクリーンショットの絶対パスです。設定すると、実行担当に(Read ツール経由で)画像を開いて、それに一致するアプリを構築するよう指示されます。また、通常の期待値に加えて、生成されたアプリがどれほど忠実に再現しているかを採点者が評価します(ステップ 6)。Prompts フェーズで「アップロードしたスクリーンショットから構築」を選択して設定します。
画像プロンプトのケースは、reference_image が設定された通常のケースです。ターゲットと比較できるよう、ハーネスが結果をキャプチャする「Runtime + screenshots」を有効にします。
{
"prompt": "Build an app whose UI matches the attached reference screenshot.",
"reference_image": "/abs/path/to/target.png",
"runtime": { "mode": "expo-go", "platforms": ["ios"], "sdk": "56" },
"visual_expectations": ["Matches the reference's layout, components, and color treatment"]
}
Eval ケースごとのパイプライン
オーケストレーションモデル — メインスレッドでは python3 <orchestrator> を実行し、それ以外はほとんど何もしません。 各フェーズは、ワークスペースに Write で作成し、python3 /private/tmp/expo-skill-eval-<skill>/<phase>.py で実行する小さな Python オーケストレーターによって駆動されます(python3 ルールが適用されます)。オーケストレーターは scripts/*.sh ファイルを呼び出す唯一の場所です。呼び出しは必ず subprocess.run(["bash", "<scripts>/<name>.sh", …]) 経由で行い、これは python3 の子プロセスとして実行されるため、独自のルールは必要ありません。また、並列処理、ログ記録、ディレクトリ作成を行う唯一の場所でもあります。そのため、メインスレッドで行うのは、オーケストレーターの作成、python3 による実行、Read/Glob/Grep ツールによる出力の確認、および採点者のサブエージェントの起動だけです。コマンドを連結、バックグラウンド実行、またはパイプ処理するシェル構文の中に入れてはいけません。また、その場限りの mkdir/ls/cat/tail/echo も実行しないでください。これらはプロンプトが処理します。(不安定なスナップショットを 1 件再実行するなど、単発の手動デバッグでは、単独の bash …/scripts/<name>.sh … を実行してもかまいません。ただし、パイプライン自体はオーケストレーター経由で実行します。)各オーケストレーターはフォアグラウンドで実行してください。ツールの呼び出しが完了するまで待ちます。オーケストレーターはフェーズ内ですでに並列化を行うため、フェーズを重ねて実行する必要はありません。… & echo "$!"/wait を使って、フェーズをシェルのバックグラウンドで実行してはいけません(&、echo、wait の各部分にはルールがなく、プロンプトが表示されます)。他の作業を続けながらフェーズを実行する必要が本当にある場合は、Bash ツールの run_in_background パラメーターを使い、python3 <orchestrator> 2>&1 | tee <ws>/…log の形式で実行してください。手作業でシェルの & を使ってはいけません。最初に、開始時点で権限確認が 1 回だけ表示されることを想定してください。 ワークスペースへの最初の Write が対象です。allowed-tools では Bash/Read の権限確認を抑制できますが、Write/Edit は抑制できません。最初の確認では**「このセッション中、このディレクトリ内のすべての編集を許可」**を選択してください。実行全体を通じて、すべてのオーケストレーター、evals.json、ビューアーファイルに適用されます。
0. ワークスペースのセットアップ
ワークスペーススクリプトを使って、実行用のディレクトリツリーを一度だけ作成します。その場しのぎの mkdir は決して使わないでください(生の mkdir はプロンプトを表示します。mkdir のルールが存在せず、"$WORKSPACE/…" 変数もパスのグロブには一致しません)。
bash /abs/path/expo-skill-eval/scripts/make-workspace.sh /private/tmp/expo-skill-eval-<skill> iteration-N <num-evals>
これにより、すべての評価用に trigger-evals/scratch と iteration-N/eval-<i>/{with_skill,without_skill}/outputs が作成されます。これは Bash(bash *expo-skill-eval/scripts/*) の対象であり、内部の mkdir はスクリプトの子プロセスとして実行されるため、個別のルールは必要ありません。この後のディレクトリはすべて、それを必要とするスクリプト/オーケストレーター(make-fixture.sh、executor オーケストレーターの os.makedirs、スナップショットスクリプト)によって作成されるか、Write ツールが親ディレクトリを自動作成します。そのため、別の mkdir を使う必要はありません。
1. トリガー評価(should-trigger のみ)
ワークスペースの trigger-evals/ ディレクトリに run_trigger_eval_real.py スクリプトを作成します。"should_trigger": true のクエリのみを使用してください。expo プラグインは相互補完的なスキル群であるため、同じプロンプトで複数のスキルがトリガーされても失敗ではありません。再現率のみを測定します。つまり、スキルを使うべき現実的なプロンプトを、トリガー率で評価します。
各クエリに対して claude -p <query> を実行し(--output-format=stream-json --verbose --include-partial-messages を指定し、環境から CLAUDECODE を除去し、権限フラグを 開始前 — スコープの確認 で事前に確認します)、ストリーム内の Skill または Read ツール呼び出しを監視して、対象のスキルがトリガーされたかを検出します。注意:--include-partial-messages には --output-format=stream-json と --verbose の両方が必要です。どちらかを省略すると、CLI エラーが即座に発生します。
テスト対象のスキルを読み込むには、すべてのトリガー用サブプロセスに --plugin-dir を渡します。 トリガー評価では、スキルの 説明 によってモデルがそのスキルを使おうとするかを測定するため、サブプロセスに ローカルの スキル(編集内容を反映したバージョン)を読み込む必要があります。claude -p サブプロセスは親セッションの --plugin-dir を引き継がないため、明示的に --plugin-dir <plugin-root> を追加してください。ここで <plugin-root> は、.claude-plugin/plugin.json を含む plugins/expo のような、スキルを所有するプラグインディレクトリの絶対パスです(例:--plugin-dir /Users/.../skills/plugins/expo)。絶対パスで指定する必要があります。サブプロセスは使い捨ての scratch/ をカレントワーキングディレクトリとして実行されるため、相対パスの plugins/expo は解決されません。また、プラグインディレクトリが見つからなくても何も読み込まれないまま処理が進み、トリガー率が 0% と誤って判定されます。その後、プラグイン修飾名(<plugin>:<skill>、例:expo:expo-ui)でスキルのトリガーを監視します。注意点が 2 つあります。(1)公開済みの expo プラグインもグローバルにインストールされている場合は、実行中は /plugin で無効にし、実行後に再び有効にしてください。そうしないと、サブプロセス内で expo の 2 つのコピーが競合し、モデルが公開済みの expo:expo-ui をトリガーして、ローカルの編集内容ではなく公開済みの説明を誤って評価する可能性があります(トリガー検出ではツール呼び出し名しか確認できないため、コピーを区別できません。通常、開発用チェックアウトにはインストールされていません)。(2)スキルの合成コピーは決して作成しないでください。実際に読み込まれたコピーが必ず優先されるため、合成ハーネスでは評価結果が 0% になります。(executor には、インストール済みのプラグインは影響しません。executor はローカルの SKILL_PATH を直接読み込み、--plugin-dir は渡しません。)
各クエリのサブプロセスは、リポジトリのルートではなく、空の使い捨てカレントワーキングディレクトリ(例:trigger-evals/scratch/)から実行します。「設定画面を作って」のような should-trigger プロンプトによって、サブプロセスがファイルを書き込むことがあります。--dangerously-skip-permissions を使うと、書き込み先はスキルリポジトリになってしまいます。トリガーの検出に必要なのは、ストリーム内にスキルの Skill/Read 呼び出しが現れることだけであり、フィクスチャは不要です。そのため、偶発的な書き込みがあっても使い捨てにできます。
クエリごとのサブプロセスのタイムアウトは、少なくとも 300 秒に設定してください。180 秒では短すぎます。クエリによっては、スキルをトリガーする前にモデルがコード生成を始め、合計実行時間が 3 分を超えることがあります。
トリガー評価は、コード評価のケースごとではなく、スキルごとに 1 回実行します。
2. フィクスチャ
各エグゼキューターの実行では、scripts/make-fixture.sh <app-path> <sdk> [clean|full] で新しい Expo アプリを作成します。
scripts/make-fixture.sh <workspace>/iteration-N/eval-X/<config>/app <sdk> # blank app (default)
scripts/make-fixture.sh <workspace>/iteration-N/eval-X/<config>/app <sdk> full # keep example tabs
スクリプトは、bunx create-expo-app -t default@sdk-<version>(バージョンが指定されていない場合は最新のテンプレート)を使って、SDK バージョンとバリアントの組み合わせごとに一度アプリを作成し、~/.cache/expo-skill-eval/fixtures/ にキャッシュしてから、APFS のコピーオンライトでキャッシュを複製します。そのため、各バリアントの初回実行ではインストールに時間がかかりますが、それ以降の実行はほぼ瞬時です。デフォルトの clean バリアントでは、テンプレートの reset-project スクリプトを実行するため、エグゼキューターは空のアプリから作業を始め、出力内のすべての画面がエグゼキューターによって作成されたものになります。これにより、評価の指標が格段に明確になります。評価プロンプトが既存のアプリを前提としている場合(例:「2 つのタブがあるアプリを持っています……」)に限り、full を使います。このスクリプトは複製したアプリ内の Git もリセットするため、アプリ内の git diff には、エグゼキューターが変更した内容だけが表示されます(評価者に有用な証拠になります)。
フィクスチャは順番に作成してから、エグゼキューターを並列実行してください。フィクスチャを並行して作成しないでください。 make-fixture.sh は、SDK とバリアントをキーとする ~/.cache/expo-skill-eval/fixtures/ 内のキャッシュを共有します。2 つの実行がどちらもキャッシュが空だと判断し、同時に bunx create-expo-app を呼び出すと、bun のリンク処理が競合し、片方が EEXIST / "could not determine executable to run for package create-expo-app" というエラーで失敗します。そのため、エグゼキューターのオーケストレーター(ステップ 3)では、まずフィクスチャをすべて 1 つずつ作成してください。具体的には、subprocess.run(["bash", "<scripts>/make-fixture.sh", app, sdk, variant]) を呼び出す単純な Python のループを使います(sdk は事前に選択したバージョンです)。その後で、ThreadPoolExecutor を使って claude -p エグゼキューターを並列実行します。順番に作成しても手間はかかりません。各 SDK とバリアントの組み合わせでインストールコストがかかるのは最初のフィクスチャだけで、残りは約 1 秒で完了する APFS クローンです。(また、make-fixture.sh A & make-fixture.sh B & wait のような場当たり的なシェルコマンドでフィクスチャを並列化しないでください。& / wait の部分で確認プロンプトが表示されます。順番に実行する Python ループを使えば、競合も確認プロンプトも回避できます。)
3. 実行(executor サブエージェント)
Python スクリプトから claude -p サブプロセスとして実行してください。Agent ツールは使わないでください。Agent ツールは独自の権限コンテキストを持つサブエージェントを起動するため、フィクスチャアプリ内のファイルを編集するとユーザーに確認が求められます。claude -p サブプロセスは権限システムの外部で動作する別プロセスです(トリガー評価ハーネスでも同じ方式を使います)。
Python スクリプトを /private/tmp/expo-skill-eval-<skill>/run_executors.py に作成してください。まず、すべての実行用フィクスチャを順次ループで作成します。subprocess.run(["bash", "<scripts>/make-fixture.sh", app, sdk, variant], …) を一度に 1 つずつ実行してください(同時に作成すると共有 bun キャッシュで競合が発生します。手順 2 を参照してください)。その後、ThreadPoolExecutor を使って、スキルありとスキルなしの claude -p 呼び出しを並行して実行します。どちらのフェーズも Python 内で実行してください(python3 ルールの対象です)。メインスレッドで場当たり的にシェルを実行してはいけません。各実行プロンプトには、次の内容を含めてください。
- スキルのパス(スキルありの実行のみ)と評価プロンプト。
- **画像プロンプトのケース(
reference_imageが設定されている場合):**対象スクリーンショットの絶対パスと、「Read ツールで<path>の参照スクリーンショットを開き、レイアウト、コンポーネント、間隔、色をできる限り一致させた UI のアプリを作成してください」のような指示。claude -pはこの方法で PNG を読み込んで描画するため、実行者が実際に参照画像を確認できます。 - フィクスチャアプリのパス:「変更は
<app-path>内で行ってください。プロジェクトはすでに存在し、依存関係もインストール済みです。すべてのファイル操作では絶対パスを使用してください。」 - 「ファイルを書き込む前に、プロジェクトのレイアウトを確認してください。正しいルートディレクトリを見つけるため、
lsを実行し、package.jsonとapp.jsonを読んでください。最近の SDK のデフォルトテンプレートでは Expo Router のルートはsrc/app/にあります。古いテンプレートではプロジェクトのルートにapp/が使われます。このフィクスチャでどちらが使われているか、確認してください。」 - 「開発サーバーを起動したり、シミュレーターを起動したり、スクリーンショットを撮ったりしないでください。完了後にハーネスが実行します。」
- 作成したものの簡単な概要の保存先。
claude -p サブプロセスのフラグ:
- 環境から
CLAUDECODEを除外してください(env = {k: v for k, v in os.environ.items() if k != "CLAUDECODE"})。除外しない場合、実行中の Claude Code セッション内でネストして起動したときにclaude -pが無言で停止します。 - ユーザーと事前に確認した権限フラグを指定してください(開始前 — 対象範囲の確認を参照)。
--dangerously-skip-permissionsまたは--permission-mode acceptEditsのいずれかを使います。選択したフラグを生成するスクリプトに組み込んでください。どちらのフラグも付けずにclaude -pを実行しても、ファイルは書き込めません。承認のための TTY がないため、コードがテキストとして出力されるだけです。 - 実行者には
--plugin-dirを渡さないでください(トリガー評価とは異なります)。スキルありの実行では、絶対パスのSKILL_PATHからスキルを読み込むため、ローカルの内容を直接テストできます。スキルなしの実行では、スキルを一切利用できないようにする必要があります。プラグインを読み込むとスキルが自動的にトリガーされ、ベースラインが汚染される可能性があります。実行者がパスで指定したスキルを使う方式にすることで、2 つの評価対象を明確に分けられます。実行者は 内容の品質(スキルを読んだ場合に役立つか)を測定し、トリガー評価は トリガーの動作(説明からスキルが適切に選ばれるか)を測定します。
採点の証拠として、各実行の stdout と stderr をフィクスチャの隣にあるログファイルに保存してください。実行者ごとのタイムアウトは 900 秒に設定してください。スキルありの実行では、コーディング前に複数の参照ファイルを読み込むため、通常 5~10 分かかります。
4. 静的ゲート
run_static.py を作成し、python3 で実行します。各 eval/config アプリについて、ThreadPoolExecutor を使って subprocess.run(["bash", "<scripts>/check-static.sh", app, "ios,android"], capture_output=True, …) を呼び出します(静的ゲートは互いに独立しているため、シェルの &/wait は使わず、必ず Python 内で並行実行してください)。各結果(終了コード+キャプチャした出力)は、採点側のために eval-<i>/<config>/static.json に書き込みます。
check-static.sh は、指定されたプラットフォームに対して tsc --noEmit、expo lint、expo export を実行します。エクスポートが成功すれば、デバイスに触れることなく、ほとんどのインポート/構文/モジュール不足のエラーを検出できます。エクスポートが失敗した場合は、ステップ 5 を明確な FAIL として早期終了し、結果を記録したうえで、スナップショットオーケストレーターにそのアプリをスキップさせます。
5. 実行+スクリーンショット(eval 間は逐次実行)
run_snapshots.py を作成し、python3 で実行します。シミュレーターとエミュレーターは共有リソースなので、このオーケストレーターは逐次実行します(スレッドプールは使いません)。静的ゲートを通過した各アプリと各プラットフォームについて、outputs/ ディレクトリを os.makedirs で作成し、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 を使用してください。expo run:ios/android --port N がサポートされており、ポートを分けておけば、将来並列実行する場合でも両プラットフォームのポートが競合しません。スクリーンショットは実行時の outputs/ ディレクトリに保存されるため、ビューアーでインライン表示されます。
各フィクスチャの処理後にディスク容量を確保してください。dev-build の実行では必須です。 アプリの選択した全プラットフォームのキャプチャが完了したら(次のフィクスチャのビルドを始める前に)、subprocess.run(["bash", "<scripts>/clean-fixture.sh", app]) を呼び出します。各 expo run:<platform> は、数 GB に及ぶネイティブビルド出力(iOS の Pods と DerivedData、Android の Gradle ビルド)を残します。これを行わないと、eval × config × iteration の生成物が蓄積して実行中にディスクがいっぱいになります(発生する不安定さの原因はディスク容量の枯渇です)。clean-fixture.sh は、再生成可能な大容量ディレクトリ(node_modules、ios、android、.expo、dist)とフィクスチャの iOS DerivedData を削除しますが、アプリのソースと git は残すため、採点側の git diff は引き続き機能します。スナップショットを逐次実行し、フィクスチャごとにクリーンアップすれば、ピーク時のディスク使用量を全フィクスチャ分ではなく、1 フィクスチャのビルド分程度に抑えられます(expo-go の実行でも問題ありません。回収できる容量が少ないだけです)。
runner は事前に選ぶ設定です(デフォルトは expo-go、または dev-build)。スナップショットスクリプトは EXPO_SKILL_EVAL_RUNNER を参照します。expo-go では expo start --<platform> で起動し(Expo Go のインストールとディープリンクの手順も実行します)、dev-build では expo run:<platform> --port <port> で起動します。こちらはネイティブの開発クライアントをコンパイルしてインストールし、Expo Go の手順を省略します。スクリプトでは dev-build のタイムアウトをすでにデフォルトで 900s に設定していますが、初回のネイティブコンパイルで必要な場合は EXPO_SKILL_EVAL_BUNDLE_TIMEOUT をさらに長くしてください。make-fixture.sh は各フィクスチャに expo-dev-client を事前インストールするため、expo run がアプリを開くためにディープリンクを試みる前に、開発クライアントの URL スキームが登録されます。
スナップショットスクリプトがキャプチャするのは、常に初期ルート / です。 ディープリンクでアプリを開き、スクリーンショットを 1 枚撮影します。タップや画面遷移はできません。テスト対象の機能がルート画面に表示されるよう、eval プロンプトを設計してください。実行側がメイン UI をナビゲーション操作の先(たとえば、index 画面にある「設定を開く」ボタン)に配置すると、スナップショットでその機能をまったく捉えられず、視覚的な期待事項がすべて失敗します。
各 snapshot-<platform>.sh は起動時に Metro のポートを解放し(前回の実行がクラッシュして残った古いプロセスを終了し)、終了時に Metro を停止するため、ポートを空けるために自分で lsof/kill/pkill を実行する必要はありません(確認が求められますし、すでに処理されています)。その後、Metro を起動し、Metro ログに「Bundled」の行が表示されるまで待ち、安定するまで待機してからスクリーンショットを撮影し、Metro を停止します。iOS では、起動中のシミュレーターがない場合、利用可能な最新の iPhone シミュレーターを起動します。Android では、デバイスが接続されていない場合、最初の AVD を起動します(時間のかかる処理です。一度起動したら、イテレーション全体で再利用してください)。Android ではまず、応答しなくなった/offline のエミュレーターを再起動します(adb emu kill で正常終了を試み、その後強制終了して adb をリセットします)。これにより、半端に停止したインスタンスが実行を妨げるのを防ぎます。また、ハードウェア GPU(-gpu host、Apple Silicon では Metal による高速化)で起動します。特定のマシンで host によりエミュレーターが自動終了する場合(gfxstream/Metal の深部で qemu の SIGABRT が発生。Apple Silicon では高負荷時に起こる可能性があります)、snapshot-android.sh の GPU_MODE をソフトウェアモードに変更してください(guest は確実に描画できますが、速度は遅いため、安定化までの待機時間を増やしてください。arm64 では起動時にハングする swiftshader_indirect は避けてください)。snapshot-web.sh は、platforms に web が含まれる場合にのみ実行されます。各スクリプトは、スクリーンショットと同じ場所に Metro ログ(<name>.metro.log)を書き込みます。採点者に渡す入力に含めてください。スクリプトが 0 以外の終了コードで終了した場合でも、可能な限りスクリーンショットの撮影を試みます(エラー画面も証拠になります)。dev-build の再起動: Metro の起動後、スクリプトは iOS では xcrun simctl launch、Android では adb shell am start -n <pkg>/.MainActivity を使ってアプリを再起動します。どちらの方法も、初回起動時に URL スキームのディープリンクで表示される「Open in X?」システムダイアログを回避します。
イテレーションのスクリーンショットをすべて撮影したら、必ずビューアーを生成してください。チェックイン済みのスクリプトにワークスペースのルートを渡します。
python3 /abs/path/expo-skill-eval/scripts/generate_viewer.py /private/tmp/expo-skill-eval-<skill>
ワークスペースのルート(iteration-N/ の 1 つ上の階層)に viewer.html を書き込み、ブラウザーで開きます(webbrowser.open 経由)。そのため、別途 open コマンドを実行する必要はなく、Bash(open:*) ルールも不要です。詳しくは、以下のビューアーのセクションを参照してください。
6. 採点
採点用サブエージェントをフォアグラウンドで起動します。そのプロンプトには、次の内容を含めてください。
- 評価ケースの eval プロンプト、期待事項のリスト、visual_expectations。
agents/visual-grader.mdの指示(スクリーンショットの採点、redbox の検出)。- 入力としてのスクリーンショットファイル、Metro ログ、ステップ 4 の
static.json。 - 画像プロンプトのケース(ケースに
reference_imageがある場合):ターゲットのスクリーンショット(reference_image)、references/design-rubric.md、フィクスチャのgit diffも含めてください。生成されたスクリーンショットをターゲットと比較し、以下のreference_matchおよびqualityブロックを出力するよう採点者に指示してください。
採点者は、以下の形式で出力の隣に grading.json を書き込みます。
{
"score": 8.5,
"max_score": 9,
"expectations": [
{"text": "...", "passed": true, "evidence": "..."}
],
"reference_match": {
"score": 7, "max": 10,
"evidence": "ios.png vs target.png: same two-section grouped list + toggle; accent color differs (blue vs target's green); row spacing tighter than target"
},
"quality": {
"dimensions": [
{"name": "Layout & hierarchy", "score": 2, "max": 3, "evidence": "ios.png: …"}
],
"subtotal": 17,
"max": 24,
"summary": "…"
},
"user_notes_summary": {"needs_review": false, "notes": ""}
}
視覚的な期待事項は同じ expectations 配列に含め、証拠にはスクリーンショットのファイル名を挙げ、何が見えるかを説明します。reference_match ブロック(生成されたアプリがターゲットのスクリーンショットをどれだけ再現しているか)と quality ブロック(references/design-rubric.md に基づくデザイン評価のスコア)は、画像プロンプトのケースの場合のみ、または品質評価が明示的に要求された場合に出力します。通常のテキストプロンプトの実行では、どちらも省略してください。
ロールアウトのフェーズ
次の順序でパイプラインを構築し、デバッグしてください。各フェーズは個別に役立ちます。
- 静的:ステップ 1~4 のみ(すべてに
runtime.mode: "static-only"を設定)。デバイスは不要で、CI に適しています。 - iOS:ループに
snapshot-ios.shを追加します。simctlは最もスクリプト化しやすいターゲットです。 - Android:
snapshot-android.shを追加します。エミュレーターの起動は最も時間がかかるため、セッション全体で 1 台のエミュレーターを起動したままにしてください。 - Web:Web を対象とするスキルには
snapshot-web.shを追加します(bunx経由で Playwright を使用します。初回実行時に Chromium がダウンロードされます)。
実用上の注意
- 一時ディレクトリ:すべての評価用ワークスペースは
/private/tmp/expo-skill-eval-<skill-name>/iteration-N/の下に配置します。この実行で使用するRead、Write、Edit、Bashはすべてallowed-toolsフロントマターでカバーされるため、正しく読み込まれたスキルであればプロンプトは表示されません。 - 権限ルールの形式(このスキルでプロンプトが表示されない理由):ルールの 構文 が重要で、2 種類のツールファミリーでは動作が異なります。
Bash(...)ルール — スキル独自のコードに対するパス指定(広範なインタープリターは許可しません)。Bash(python3 /private/tmp/expo-skill-eval-*)(および/tmpエイリアス)は、ワークスペースの下に生成する Python オーケストレーターを実行します。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/*)で実行できるのは、このスキルのscripts/*.shだけです。すべてのパスが固定されているため、抜け道は引き続き拒否されます。python3 -c …、bash -c …、tee /etc/…、その他の場所でのコード実行は一致しません(実機で検証済みです。スコープを限定したルールではbash <dir>/run.shは許可されますが、bash -c …とそれ以外のパスはブロックされます)。スクリプトが内部で呼び出すbunx、xcrun simctl、adb、git、mkdir、expoはスクリプトの子プロセスであり、Bash ツール呼び出しではないため、ルールは不要です。メインスレッドからアドホックにmkdir/ls/find/cat/grepを実行しないでください(これらにはルールがなく、プロンプトが表示されます。また、パスが展開されていない変数であるため、生のmkdir "$WORKSPACE/…"はパスの glob に一致しません)。ディレクトリツリーはmake-workspace.sh(ステップ 0)で作成し、オーケストレーターには独自のディレクトリを作成させ(os.makedirs)、結果の確認にはRead/Glob/Grepツールを使用してください(Bash ルールは不要です)。- Bash ルールの照合(テスト済み。直感に反する点があります): Bash ルールは、コマンド文字列に対する gitignore 形式の glob です。
*は/やスペースを含む任意の文字列に一致し、パターンの途中でも使えます。そのため、Bash(python3 /private/tmp/expo-skill-eval-*)はpython3 /private/tmp/expo-skill-eval-x/run.py 2>&1に一致し、Bash(bash *expo-skill-eval/scripts/*)はbash /any/abs/path/expo-skill-eval/scripts/foo.sh argsに一致します。以前の試行で問題になった注意点が 2 つあります。**は文字どおりに照合されるため、Bash ルールでは絶対に使わないでください。また、:*サフィックスが使えるのはコマンドトークンの直後だけ(Bash(python3:*))で、パスの一部の直後には使えません(Bash(python3 /path-:*)は一致しません)。複合コマンドは|、&&、||、;、&で分割され、それぞれのセグメントに個別の一致ルールが必要です。 Readルールはプロンプトを抑止しますが、Write/Editルールは抑止しません。 これは Claude Code の非対称な動作です(パターンの問題でも、再読み込みの問題でもありません。同じフロントマターのBash/Readルールが機能していることが明らかなセッションでも、Writeは引き続きプロンプトを表示します)。ファイルの作成や編集は、allowed-toolsに関係なく、常に Claude Code の編集承認フローを通ります。フロントマターでは、ドキュメントおよびガードレールとして、Read/Write/Editを…/expo-skill-eval-*/**(macOS がシンボリックリンクを自動解決しないため、/tmpと/private/tmpの両方の形式)にスコープ設定していますが、Write/Editの項目だけではプロンプトを抑止できません。実際には、実行の開始時にワークスペースへのWriteプロンプトが1 回表示されます。その際は **「Yes, allow all edits in this directory for the session」**を選択してください。以降、そのワークスペース内でオーケストレーター/evals.json/ビューアーが行う書き込みはすべて、プロンプトなしで実行されます。ファイル書き込みでプロンプトが表示されなくなるのはルールではなく、このディレクトリへの承認が 1 回だけ必要だからです。- フロントマターを編集したら、
/reload-skillsではなく完全に再起動してください。allowed-toolsはセッション開始時にスキルが読み込まれる際に 1 回だけ読み取られます。/reload-skillsはスキルの 本文 を再読み込みしますが、権限ルールは確実には更新されません。このファイルを編集した後は、Claude Code を完全に終了して新しいセッションを開始してからスキルを再実行してください。そうしないと、ディスク上のファイルが正しくても、古い(キャッシュ済みの)ルールセットによってプロンプトが表示され続けます。 - Grader サブエージェントは独自の権限コンテキストで実行されるため、ファイルアクセスのプロンプトは引き続き表示されます。これは想定どおりであり、メインスレッドのルールとは別のものです。
- 評価スクリプトの呼び出し — 個別のコマンドとして実行し、連結しないでください。 各スクリプトは絶対パスを指定し、独立した Bash 呼び出しとして実行します。例:
bash /abs/path/expo-skill-eval/scripts/snapshot-ios.sh arg1 arg2(Bash(bash *expo-skill-eval/scripts/*)で許可されます)。&、&&、||、;、wait、tail、head、echoと組み合わせないでください。複合コマンドはセグメントごとにチェックされ、追加されたセグメントにはルールがないため、bash …/scripts/…の部分が許可されていても全体でプロンプトが表示されます。許可されるパイプは… 2>&1 | tee <workspace>/…logだけです。スコープを限定したteeルールでカバーされるためです。並列処理や出力の切り詰めが必要ですか?ThreadPoolExecutorを使ってsubprocess経由でスクリプトを実行する Python オーケストレーター(python3 /…/expo-skill-eval-*で許可されます)に処理を記述してください。結果の確認にはcat/ls/grepではなく、Read/Glob/Grepツールを使います。**一般原則:**このスキルではスコープを厳密に限定しているため、エージェントが即興で実行するアドホックなシェルコマンドはすべてプロンプトを表示します。対処方法は、それをスクリプト/オーケストレーターに移す(またはスコープを限定したteeを使う)ことであり、ルールの範囲を広げることではありません。 - 出力(スクリーンショット、ログ、ファイル)の確認 — シェルではなくツールを使ってください。 ファイルを探すには Glob ツール(例:
/private/tmp/expo-skill-eval-<skill>/iteration-N/**/ios.png)を使い、表示するには Read ツールを使います。Read は PNG を視覚的に表示するため、スクリーンショットがレンダリングされたことを確認するのに最適です。ファイルの内容を検索するには Grep を使います。この用途でfind/ls/catを使わないでください。これらはプロンプトを表示します。また、find … -exec …は-execで何でも実行できる(例:-exec rm)ため、意図的に許可されていません。これらのツールはスコープが限定されており、プロンプトなしで使えるので、通常ならfind/ls/catを入力する場面では、必ずこれらのツールを使ってください。 - 生成する Python スクリプト:オーケストレーション/集計用のスクリプトはワークスペースの下に作成し(例:
/private/tmp/expo-skill-eval-<skill>/aggregate.py)、python3で実行します(Bash(python3 /private/tmp/expo-skill-eval-*)で許可されます)。例外はビューアーです。これはチェックイン済みのscripts/generate_viewer.pyで、Bash(python3 *expo-skill-eval/scripts/*)を使って実行します。Writeは親ディレクトリを自動作成しますが、初回はプロンプトを表示するため、ワークスペースのディレクトリを 1 回承認してください(上記のWrite/Editに関する注意を参照)。出力はスクリプト自身にログを書き込ませるか、python3 … 2>&1 | tee <workspace>/…logを使って保存します(スコープを限定したteeルールで許可されます)。ログはReadツールで読み取ってください。セットアップにpython3 -c …を使わないでください(スコープを限定したルールはワークスペース内のスクリプトパスにしか一致しないため、単独の-cはプロンプトを表示します)。 - インストール済みプラグインに対するトリガー評価:ストリーム内で実際にインストールされているスキル名(例:
expo:expo-ui)を検出します。実際のプラグインがインストールされている場合、モデルは合成コピーではなく本物のスキルを選ぶため、合成した重複スキルを使うハーネスでは常にスコアが0%になります。 - ベンチマークの集計:各実行の
grading.jsonとtiming.jsonをeval-<N>/<config>/run-1/に保存します。ワークスペース内にPythonの集計スクリプトを作成し、python3で実行します。 - Expo Goの制約:カスタムネイティブコードが必要なもの(expo-module、App Clips、brownfield)は、Expo Goでは実行できません。その場合は
static-onlyモードを使用してください。スキルの評価ケースを作成する前に、references/runtime-matrix.mdを確認してください(注:@expo/uiはSDK 56以降のExpo Goで実行できます)。 - APIルートのスキル:スクリーンショットの代わりに、Metroの起動中にルートに対して
curlを実行して検証し、採点用の出力ファイルとしてレスポンスを記録します。 - タイミングデータ:トークン数と所要時間を各エグゼキューターの実行直後に
timing.jsonへ記録します。後から復元することはできません。トークン数を記録するには、エグゼキューターのclaude -p呼び出しに--output-format=stream-json --verboseを追加し、ログからmessage_start/message_deltaイベントを解析します。これらのフラグがない場合、ログには文章しか含まれず、復元できる指標は経過秒数のみです。 - 初回起動時のダイアログ:新しいシミュレーターでは、Expo Goが一度だけ表示されるプロンプトをまれに出すことがあります。スクリーンショットにアプリではなくダイアログが写っている場合は、スナップショットスクリプトを再実行して(URLを再度開きます)、撮影し直してください。
ビューアー
スクリーンショットを撮影したら、ユーザーに確認を求めなくてもすぐに結果を見られるよう、必ずHTMLビューアーを生成して開きます。ビューアーには、チェックイン済みの scripts/generate_viewer.pyを使用します。ワークスペースのルートを引数として実行してください。
python3 /abs/path/expo-skill-eval/scripts/generate_viewer.py /private/tmp/expo-skill-eval-<skill>
自己完結型の/private/tmp/expo-skill-eval-<skill>/viewer.htmlを書き出し、ブラウザーで自動的に開きます(webbrowser.open)。表示内容は次のとおりです。
- イテレーションごとのタブ(ワークスペースのルートにある
iteration-*。最後に選択したタブはlocalStorageに記憶されます)。 - 各評価ケース(
<iteration>/evals.jsonから読み込みます)について、with_skill/without_skillの列を並べて表示します。各列には、static-gateのステータス、スコア、プラットフォームのスクリーンショット(クリックで拡大。ファイルを自己完結させるため、base64のdata:URIとして埋め込みます)、PASS/FAILバッジ付きの期待事項リスト、レビュー担当者のメモが表示されます。 - 画像プロンプトのケース(
reference_match/qualityを含むgrading.json)では、生成画像の横に目標スクリーンショットを表示します。また、reference_matchスコア(生成画像と目標画像の比較)、設定ごとのquality評価基準(各項目のスコア/最大値と小計を示すバー)、および正確性の差分と並べてサマリーバーに表示する品質の差分(with_skill − without_skillの小計)も表示します。 - with_skill %、without_skill %、差分を示すサマリーバー。
trigger-evals/trigger_results.jsonが存在する場合は、トリガー精度の表。- スコアを色分け(85%以上は緑、65%以上は琥珀色、それ未満は赤)したダーク背景。
ビューアーの公開(事前にオプトインした場合のみ)
ローカルの viewer.html は常に生成されます。事前確認でユーザーが「共有可能な Artifact を公開する」を選択した場合に限り、最後に追加で claude.ai Artifact としてレンダリングします。オプトインなしで公開してはいけません(外部に公開され、公開ページがキャッシュまたはインデックスに登録される可能性があります)。手順は次のとおりです。
Artifactツールはファイルを独自の<!doctype html>…<head></head><body>の骨組みで囲むため、渡すファイルはページコンテンツのみにしてください。インラインの<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: "📊")。 - ビューアーはすでに自己完結型(base64 のスクリーンショット、インラインの CSS/JS)なので、Artifact の CSP に準拠しています(外部ホストへのアクセスはありません)。
参考資料
references/runtime-matrix.md— スキルごとのランタイム適用可否(expo-go 対静的のみ、プラットフォームに関する注意事項)。agents/visual-grader.md— 採点サブエージェント向けのスクリーンショット採点手順。