screenmap(aleqsio/screenmap)は、Expo / React Native アプリのすべての画面を地図として持ち、プルリクエストの差分が届く画面だけをCIで撮影してレビューに貼るMITライセンスのOSSだ。Webの PR にはプレビューURLがあるが、モバイルの PR は QR コードとビルドを渡されて「自分で画面を探してね」になりがちで、視覚的な変化がレビューで素通りする。screenmap はその隙間を埋める。本記事はリポジトリを clone し、静的ルート解析・Action CLI・Map Viewer のビルドを Linux で実測し、同梱のデモ地図(Bluesky クライアント・70画面)を Playwright で開いて実画面を撮った。GitHubスターは執筆時点で 189、公開は 2026年8月8日で、まだ若いプロジェクトである。
demo.scrmap)を読み込んだ実画面。ヘッダに「70 screens・50 captured・126 flows」、左に AGENT FLOWS 54 本。線は実線がコードから宣言されたリンク、破線が AI が観測した遷移。- ・正体:Expo / React Native 向けの GitHub Action+Claude Code プラグイン+Map Viewer。ルータを静的解析して画面グラフを作り、PR の差分が到達しうる画面(suspects)だけをシミュレータで撮って before/after と注記を PR コメントに貼る。
- ・設計の芯:コミット済みの flow(argent 形式 YAML)が真実で、
mainの地図はキャッシュ、AI は flow が無い画面だけを予算内で探索する。合否ゲートは持たず「報告だけ」。 - ・実測:静的解析は fixture 2 件が 0.19 秒で期待グラフと一致。Action CLI は 26 パッケージ・3 秒、Viewer は 454 パッケージ・ビルド 1.2 秒。シミュレータが要る撮影と CI 実行は Linux コンテナでは未検証。
- ・注意:★189・貢献者 1 人・公開 5 週間。AI レーンは Claude Code を
--dangerously-skip-permissionsで起動する。iOS は macOS ランナー(Linux の 10 倍課金)。
screenmap は「AIでレビューを自動化する」ツール群の中では、判定を人に残して証拠だけを機械が集める側に位置する。周辺ツールの全体像はAI自動化ツール|ノーコードからコードまで2026年版の比較と選び方にまとめている。
screenmapとは——PRが届く画面だけを撮る3つの入口
README 冒頭の定義は次の一文だ(原文)。
screenmap shows you every screen in an Expo / React Native app, and shows your reviewers exactly which screens a pull request changed, without anyone writing a test.
入口は3つある。
| 使い方 | 何を使うか | 何が得られるか |
|---|---|---|
| PR の画面変化をレビューする | GitHub Action aleqsio/screenmap@v1 |
before/after・変更領域のボックス・1画面1行の注記・Viewer へのリンクが付いた sticky コメント |
| 手元のアプリを地図にする | Claude Code プラグイン /screenmap |
.screenmap/out/ に graph.json・画面PNG・flow YAML・map.html |
| 地図を眺める・共有する | Map Viewer(app.screenmap.dev またはローカル) |
ブラウザ内でパース(アップロード無し)。?map=<url>&changes=<url> で PR の差分を重ねて表示 |
読者の3問に答える。
・何ができる:ルータの静的解析で画面とリンクのグラフを作り、ディープリンクと flow 再生で各画面を撮り、.scrmap(zip・manifest+map.json+screens/*.png)に固める。PR では差分から suspects を出し、head 側だけ撮り直して .diff.scrmap にする
・何を解決する:モバイル PR で「どの画面がどう変わったか」をレビュアーが見られない問題。テストを書かずに視覚的な証拠を PR に残す
・何を代替する:QR コード+ビルド配布の手動確認、そして「画面の証拠のためだけに書く」E2E テストの一部。E2E テストランナー(Maestro・Detox)やビジュアルリグレッションのゲート(Chromatic 型)の代替ではない
なお同梱デモの manifest.json には "generator": "expo-map/2.0" とあり、プロジェクトは expo-map から改名されたと読める。同名の別 OSS(地図ライブラリなど)と混同しやすい名前なので、検索するときは aleqsio/screenmap で引くのがよい。
screenmapの仕組み:静的解析・suspects・決定論レーンとAIレーン
README「Three ideas the design rests on」は次の3点だ。
- コミット済み flow が真実:
.screenmap/flows/*.yamlと.meta.jsonはアプリのリポジトリに入り、コードと同じくレビューされる。flow のある画面は決定論的に再生される mainの地図はキャッシュ:完全な.scrmapは一度作り、push のたびに差分更新する。PR では suspects の head 側だけを撮り、base 側は地図から流用する- AI は隙間を埋めて退場する:flow が無い画面だけをヘッドレスのコーディングエージェントが
.screenmap/SKILL.mdの指示で探索し、effortの予算で打ち切られる
screenmaps ブランチの .scrmap"] B --> P["parse-routes(head)
+ diff-map suspects"] P --> S{"suspect ごとに
flow がある?"} S -->|"yes"| F["argent flow run
OCR で着地確認(決定論)"] S -->|"no・deep link 可"| D["simctl openurl / adb am start
+ screenshot(決定論)"] S -->|"no・flow なし"| A["agent explores
予算 6 / 8 / 24 画面"] F --> K["diff-map pack
.diff.scrmap"] D --> K A --> K K --> O["publish: screenmaps ブランチ+artifact"] O --> C["sticky PR comment
?map=…&changes=…"]
パイプラインは README「How it works under the hood」で4段に分かれる。
・Static parse(parse-routes.mjs):expo-router のファイル規約か react-navigation のルートマップを読み、ルート一覧・エッジ・状態ヒントを graph.json に出す。プロバイダ方式で、docs/route-providers.md に従えば独自ルータも足せる
・Exploration:iOS シミュレータまたは Android エミュレータでディープリンクを総なめし、撮影して分類、タップ操作は argent(Software Mansion 製・@swmansion/[email protected])の flow YAML として記録
・Pack(pack-map.mjs):producer に依存しない .scrmap zip に固める
・Visualise:React 19+Vite 8+Tailwind 4+shadcn/ui+React Flow+elkjs の SPA。差分は pixelmatch と opencv-js で領域を検出する
flow 再生には「着地確認」がある。argent はタップがどこに落ちても成功を返すので、再生後の画面を OCR(macOS は Apple Vision、Linux は tesseract)にかけ、flow のサイドカーに記録した 2〜5 語のランドマークと突き合わせる。ずれていればその画面は「再生失敗」として扱われ、無言で古い画面が貼られることを防ぐ。
実測:静的解析・Action CLI・Map Viewer を Linux で動かす
シミュレータが無い Linux コンテナで動かせる範囲を、README の記載どおりに実行した。
git clone https://github.com/aleqsio/screenmap.git && cd screenmap
node plugins/screenmap/skills/screenmap/scripts/parse-routes.mjs fixtures/demo-app # 静的解析
node fixtures/run-tests.mjs # 同梱 fixture の期待グラフと照合
cd action/cli && npm install && node screenmap-ci.mjs --help # Action の CLI
| 工程 | 結果 | 備考 |
|---|---|---|
parse-routes.mjs fixtures/demo-app |
成功(0.06 秒) | mode: expo-router・routes 7・layouts 2・edges 10・unresolvedEdges 0。プロバイダ自動判定は「app/ に _layout がある」で expo-router(score 0.60) |
fixtures/run-tests.mjs |
2 件合格(0.19 秒) | demo-app(expo-router・7 routes・10 edges)と rn-demo-app(react-navigation・5 routes・4 edges)が expected-graph.json と一致 |
action/cli の npm install |
26 パッケージ・3 秒 | 依存は puppeteer-core 25.9 と yaml 2.9 のみ |
screenmap-ci --help |
成功 | サブコマンドは baseline / pr / comment / status / publish / flows-pr / flows-adopt / resolve-app / merge / shot の 10 個 |
apps/visualiser の npm install → npm run build |
454 パッケージ・11 秒 → ビルド 1.2 秒 | 出力 JS は 2.16 MB(gzip 674 KB)。500 KB 超のチャンク警告あり |
Viewer に ?template=bluesky でデモを読込 |
成功 | 70 画面・50 captured・126 flows。Changes タブで PR #11123(Bluesky 本家の PR 番号)の差分「+1 added・±1 changed・−0 removed・1 edge」を表示 |
検証環境:Linux x86_64/Node.js v22.22.2/2026-09-16。確認したのは静的解析・fixture テスト・Action CLI の起動・Viewer のビルドとデモ地図の表示まで。iOS シミュレータ/Android エミュレータでの撮影、EAS ビルド、GitHub Actions 上での PR 実行、AI レーンの探索は未検証で、これらの挙動は README と action.yml の記述に基づく。
Map Viewer は README のとおり apps/visualiser で動かした。開発サーバー(npm run dev)ではなく本番ビルドを vite preview で配信し、Playwright で ?template=bluesky を開いて撮ったのが本記事のスクリーンショットだ。
cd apps/visualiser && npm install && npm run build # 454 パッケージ・ビルド 1.2 秒
npx vite preview --port 4173 --host 127.0.0.1 # http://127.0.0.1:4173/?template=bluesky でデモ地図
デモ地図の中身も数えた。demo.scrmap は 5.17 MB の zip で 341 エントリ、screens/ に PNG 85 枚、flows/ に YAML 126 本、map.json は nodes 70・edges 56。Viewer で開くと、ディープリンクで開けなかった画面には「CRASHES ON DEEP LINK」「NEEDS NAVIGATION」「EMPTY-STATE」のバッジと、AI が書いた到達手順の注記(例:「集約された通知をタップして到達する」)が付いている。README が「AI は隙間を埋める」と言う中身はこれだ。
screenmapをGitHub Actionsへ導入する:テンプレートとActionの入力
README「Before you start」が求める前提は4つで、expo-router か react-navigation を使う Expo / RN アプリ(expo-dev-client 入り・ディープリンク可能)、シミュレータビルド(iOS)か APK(Android。.aab はエミュレータに入らない)を出す EAS プロファイルか自前ビルド、secrets を置ける GitHub リポジトリ、そしてランナー時間だ。導入は action/templates/ の2つのワークフローを .github/workflows/ にコピーするところから始まる。PR 側のテンプレートは次の内容で、本記事はこのファイルを clone から読んだ(CI での実行はしていない)。
# .github/workflows/screenmap-pr.yml(action/templates より・コメント一部省略)
name: screenmap · PR changes
on:
pull_request:
types: [opened, synchronize, reopened]
permissions:
contents: write # screenmaps ブランチへ push(publish: false なら不要)
pull-requests: write # sticky コメント
actions: write # 地図が無いとき baseline ワークフローを起動
jobs:
changes:
runs-on: macos-26
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
- uses: actions/setup-node@v4
with: { node-version: 22 }
- uses: aleqsio/screenmap@v1
with:
mode: pr
agent_api_key: $ # 省略すると決定論レーンのみ
expo_token: $ # EAS が dev client を用意(app_path でも可)
もう1本の screenmap-baseline.yml は main への push と平日 cron で地図を更新し、AI が記録した flow を PR として main に対して開く(feature ブランチには触らない)。主な入力を action.yml から抜くと次のようになる。
| 入力 | 既定値 | 意味 |
|---|---|---|
mode |
必須 | pr または baseline |
platform |
ios |
ios/android。1 ジョブ 1 プラットフォーム |
agent_provider |
claude |
claude/codex/gemini/opencode |
agent_api_key |
空 | 空なら AI レーン無し |
effort |
balanced |
fast/balanced/thorough。AI が探索する画面数の上限 6/8/24 と suspect の探索深さ 1/2/3 ホップ |
publish |
"true" |
screenmaps ブランチへ発行。private リポジトリでは raw URL が匿名で読めないため "false" にして artifact リンクを使う |
simulator |
iPhone 17 Pro |
起動する iOS デバイス |
app_path |
空 | ビルド済み .app/.apk。指定すると EAS を飛ばす |
expo_token |
空 | app_path が無いとき必須 |
screenmaps_branch |
screenmaps |
地図と PR ごとの差分バンドルを置く orphan ブランチ |
ファイルの読み書きは明快で、読むのは .screenmap/SKILL.md(AI へのアプリ固有の指示)・.screenmap/config.json(scheme・デバイス・待ち時間・suspect の深さ・AI 予算)・.screenmap/flows/・eas.json、書くのは screenmaps ブランチの main/<sha7>.scrmap・main/latest.scrmap・pr-<n>/<headsha>.diff.scrmap と、90 日保持の workflow artifact だ。Action 自身はビルドを一切持たない(EAS か app_path が dev client を渡す)。ステータスバーは simctl status_bar override で 9:41 に固定し、simctl privacy grant all でシステムダイアログを先に潰しておく、といった細部も README「Decisions baked in」に明記されている。
AIレーンの中身と権限:どのCLIをどう起動するか
AI レーンはプロバイダ非依存で、契約はファイルベースだ。エージェントは「どの画面を担当し、撮影・flow・notes.json・summary.json をどこに書くか」を指示され、CLI が後から検証する。README の表をそのまま引く。
agent_provider |
起動される CLI | キーの環境変数 |
|---|---|---|
claude(既定) |
claude -p … --dangerously-skip-permissions |
ANTHROPIC_API_KEY |
codex |
codex exec --dangerously-bypass-approvals-and-sandbox |
OPENAI_API_KEY |
gemini |
gemini --yolo -p … |
GEMINI_API_KEY |
opencode |
opencode run … |
設定したプロバイダ次第 |
つまり CI ランナー上で Claude Code を権限確認なしで走らせる。用途がシミュレータ操作とファイル書き出しに限られ、書いたファイルを CLI が検証するとはいえ、contents: write と pull-requests: write を持つジョブの中で確認スキップのエージェントが動くことは把握しておきたい。Action は選んだ CLI をその場でインストールし、.screenmap/config.json の "agent": { "command": "myagent --prompt-file {promptFile}", "keyEnv": "..." } で任意の CLI に差し替えられる。予算は effort で 6/8/24 画面に上限が付き、超えた分は「skipped」としてコメントに出る。README はコスト(トークン量や金額)を示していないので、fast から始めて様子を見るのが妥当だ。
手元での地図作りは Claude Code プラグインで行う。claude plugin marketplace add aleqsio/screenmap と claude plugin install screenmap@screenmap を実行し、Expo プロジェクト内で /screenmap(静的解析+シミュレータ探索+パック)、/screenmap --static(デバイス不要・解析と描画のみ)、/screenmap replay <flow名>、/screenmap pr <番号> を使う(本記事の環境では Claude Code のプラグイン導入とデバイス操作を実行していない)。プラグインの SKILL.md は 333 行あり、iOS/Android の対応コマンド表(xcrun simctl openurl と adb shell am start、simctl io screenshot と screencap)や、Android で Metro に届かせるための adb reverse tcp:8081 tcp:8081 の注意まで書き込まれている。
制約とメンテナンス状況:採用前に見るべき点
README「Known limits」は隠さず書かれている。
・1 ジョブ 1 プラットフォーム:iOS は macOS、Android は Linux。両方は別ジョブで撮って screenmap-ci merge で結合
・Android の dev メニュー抑止はベストエフォート:run-as が要るので debuggable ビルド限定
・Linux の OCR は tesseract:着地確認の誤検出が Apple Vision より増える
・flow はプラットフォームごと:座標は正規化するが、レイアウトやシステム UI の差は吸収しない
・ルータ必須:expo-router のファイル規約か react-navigation のルートマップ以外は認識しない。URL の無い画面は AI 探索頼み
・エッジ抽出は正規表現:動的 href はルートパターンに丸める
・報告のみ・ゲート無し:合否は出さない。設計上の意思
メンテナンス状況の実測値は次の通り。
| 項目 | 実測値(2026-09-16) |
|---|---|
| GitHub star / fork / open issues | 189 / 14 / 2 |
| コミット | 72(初コミット 2026-08-08・最終 2026-09-09) |
| 貢献者 | 1 人(aleqsio・非マージ 69 コミット) |
| タグ | v1(2026-08-28)。main はそこから 15 コミット先で、直近は Android 対応(PR #8) |
| ランナー | テンプレートは macos-26・Node 22。iOS 26 シミュレータ前提 |
| LICENSE 実体 | MIT License(Copyright (c) 2026 Aleksander Mikucki) |
v1 タグは動くタグで、Action の参照 aleqsio/screenmap@v1 は main の進みに追随する。Android 対応は 9 月 8〜9 日に入ったばかりなので、Android で使うなら main の直近コミット(「The Android PR lane works」など)を読んでから判断したい。
類似ツールとの比較
| 観点 | screenmap | Maestro | Playwright(Web E2E) | Chromatic 型のビジュアルリグレッション |
|---|---|---|---|---|
| 対象 | Expo / React Native の画面 | iOS/Android/Web の UI テスト | Web | Storybook コンポーネント/Web |
| 何を書くか | 何も書かない(flow は AI が記録し YAML でコミット) | YAML の flow を自分で書く | テストコード | ストーリー |
| 出力 | PR コメントの before/after と注記・地図 | テストの合否 | テストの合否・トレース | ピクセル差分の承認フロー |
| ゲート | 無し(報告のみ) | 有り | 有り | 有り(承認) |
| AI の役割 | flow が無い画面の探索と注記(任意) | 無し | 記事参照 | 無し |
| 実行場所 | 自分の GitHub Actions・画面は自分のリポジトリの orphan ブランチ | 任意 | 任意 | SaaS |
Maestro は flow を人が書いて合否を出すテストランナーで、screenmap は flow を機械が記録して証拠を出す観察ツール、という違いになる。両者は排他ではなく、screenmap の flow 形式である argent も Maestro と同じ YAML 志向の操作記述だ。Web 側で「壊れたら直す」ループを組む発想はPlaywright E2Eで自己改善ループを作る実践ガイド|壊れたら自分で直るテストへに近く、コードレビュー全体を AI に委ねるツールとの役割分担はAIコードレビューツール比較2026を参照してほしい。
まとめ
agent_api_key 無しの決定論レーンだけで 1 本 PR を流し、flow を育ててから AI を足す順番が現実的だ。
参照ソース
・aleqsio/screenmap(公式リポジトリ) — README・LICENSE・action/templates/・plugins/screenmap/skills/screenmap/SKILL.md(2026-09-16 時点の main)
・action.yml — 入力 20 項目と出力 3 項目の定義
・docs/scrmap-format.md・docs/diff-scrmap-format.md — 地図と差分バンドルの仕様
・app.screenmap.dev — ホスト版 Map Viewer(?template=bluesky でデモ)