triage-visual-changes について
triage-visual-changesは、UI VerifyのMCPサーバーを通じてビルドを確認するエージェントスキルです。ビルドやストーリーの調査、画像比較、コメント確認、視覚的変更の判断記録を案内します。
主な機能
- 01ビルドゲートとストーリー変更の確認
- 02ベースライン画像と候補画像の比較
- 03AI判定と人間のコメントの確認
- 04判断の記録とコメント管理
- 05PR全体の視覚的変更の要約
活用シーン
- 01ビルド内の変更ストーリーをトリアージ
- 02PRレビューで視覚的なリグレッションを特定
- 03変更を受け入れる前にレビューコメントを確認
- 04PR全体の変更をベースと比較
MCP 対応
MCPクライアントを`https://uiverify.ai/mcp`のリモートStreamable-HTTPサーバーに接続します。サーバーはビルド、ストーリー、画像、コメント、レビュー判断のツールを提供します。
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ uiverify/ uiverify --skill triage-visual-changesインストール後、Claude Code で/triage-visual-changesを入力して読み込まれたか確認します。
詳細
エージェントから UI Verify ビルドをトリアージする
ビルドの結果が変更ありになると、ダッシュボードでは各ストーリーについて、ベースライン/候補/差分の3画面が表示されます。このスキルでは、UI Verify MCP を通じてエージェントからレビューを行います。変更内容を読み、数値だけでは判断しにくい場合はピクセルを確認し、その後、要約または承認します。
MCP に接続する(一度だけ)
サーバーは https://uiverify.ai/mcp にあるリモート Streamable-HTTP MCP です。ブラウザーで UI Verify アカウントに一度ログイン(OAuth)すると、その接続で所属するすべてのチームのすべてのプロジェクトにアクセスできるため、プロジェクトごとの設定も、設定ファイル内の API キーも必要ありません。エンドポイントに対して curl を手作業で実行せず、エージェントに通常の MCP サーバーとして追加してください。 curl で直接アクセスすると、画像ツールの結果はシェルでは表示できない MCP 画像ブロックとして返されるため、スクリプトではピクセルを利用できません。ネイティブクライアントなら、モデルに直接画像を表示できます。
UI Verify プラグインがインストール済みの Claude Code:プラグインはすでにこのサーバーに接続しています。/mcp を実行して UI Verify サーバーを選び、ログインしてください。以下のコマンドは実行不要です。
Claude Code — プロジェクトのルートで実行します:
claude mcp add --scope project --transport http uiverify https://uiverify.ai/mcp
その後、Claude Code を再起動し、/mcp を実行して uiverify を選び、ログインしてください。--scope project を指定すると、サーバー設定がコミット対象の .mcp.json に書き込まれます。ここには秘密情報が含まれないため、チーム全体で共有でき、各メンバーは自分のアカウントでログインできます。
Cursor — .cursor/mcp.json に追加して(共有する場合はコミットします)、Cursor の MCP 設定からログインします:
{
"mcpServers": {
"uiverify": { "url": "https://uiverify.ai/mcp" }
}
}
その他の MCP クライアント(VS Code、Codex、Copilot、Gemini)では、同じ Streamable-HTTP URL を指定すると、ログインを求められます。サーバーを追加した後、ツールを読み込むためにエージェントのセッションを再起動してください。クライアントから手動で HTTP 経由でしかアクセスできない場合は、render_diff_image(インライン画像)よりも get_diff ツール(ダウンロード可能な画像のURLを返します)を優先してください。以下のツール一覧を参照してください。
公開されるツール:
| ツール | 内容 | 主な引数 |
|---|---|---|
list_projects | アクセス可能なすべてのプロジェクトと、その projectId、チーム、名前、種類、リポジトリ、最終ビルド時刻を返します。複数のプロジェクトにアクセスできる場合にのみ必要です。commitSha / prNumber を使った検索と list_builds では、その後 projectId を指定します(プロジェクトが1つの場合は自動的に使用され、特定できない呼び出しではプロジェクトの一覧を含むエラーが返されます) | なし |
list_builds | 直近のビルドとゲートのステータスを返し、確認対象のビルドを探します | projectId?、branch?、status?、limit? |
get_build | 1つのビルドを簡潔にトリアージします。ゲート、counts {total, changed, failed, unchanged}、ビルド全体のコメント数 comments {total, unresolved}、AI の集計、および変更/失敗したストーリーの**最初のページ(25件)**を返します。各ストーリーには diffResultId、差分率、AI レビューが有効な場合は判定者の aiVerdict、aiConfidence、回帰の場合は aiFlagReason(「意図しないように見える箇所」を1行で示すもの)が含まれます。次のページを示す nextCursor も返します。未変更の一覧は出力しません。counts.unchanged が目印となり、残りは list_build_stories でページ取得します。comments.unresolved > 0 は、人またはエージェントが未解決のコメントを残したことを意味します。承認する前に list_comments で確認してください | commitSha / prNumber / buildId のいずれか |
list_build_stories | ステータスごとに1つのビルドのストーリーをページ取得します。get_build の最初のページを超えて、変更済み/失敗/未変更の全件を確認する唯一の方法です。get_build の counts.unchanged はページ取得される正確な件数です。各変更済みストーリーの行には、ここでも get_build でも、そのストーリー固有の comments {total, unresolved} が含まれます | セレクター、status: changed | unchanged | failed、cursor?、limit?(デフォルト25、最大100) |
get_diff | ストーリーごとの差分メトリクスと、ファイルにダウンロードしたり PR に直接リンクしたりできる署名付き画像 URL(ベースライン/候補/差分)を返します。AI レビューが有効な場合は、判定者の完全な判定内容 aiVerdict、aiConfidence、aiSummary(変更点)、aiReasoning(理由)、aiFlagReason も返します。さらに、ストーリーごとの comments {total, unresolved} も返します(get_build と同じ目印ですが、対象はそのストーリーのみです)。storyId を指定すると、変更がなかったストーリーも取得できます。現在のベースライン URL が得られるため、ビルド合格時に「ベースラインと同一」と表示できます | 同じセレクター、任意の storyId |
render_diff_image | 画像1枚の実際のピクセルを、ビジョンモデルが確認できるようインラインで返します(URL ではありません)。baseline / candidate / diff、または変更箇所を拡大した前後比較の切り抜きである before_after を指定します。ストーリーが大きく離れた複数箇所(ヘッダーとフッターなど)で移動している場合、領域ごとに切り抜き、縦に並べて返します。ストーリーに未解決のレビューコメントがある場合、それを示すテキストの注記が画像より先に表示されます | diffResultId(または変更がなかったストーリーの storyId)、which |
get_pr_changeset | 累積的な「この PR とベースとの差分セット」を返します。つまり、プルリクエスト全体がマージ先ブランチに対して UI に加えた変更です。new / changed / removed のストーリーと、それぞれのレビュー状況(AI レビューが有効な場合は判定者の評価)、さらに counts {new, changed, removed, unchanged} と各一覧の最初のページ(25件)を返します。get_build とは異なり、こちらは1コミットのゲートではありません。PR 内での承認後も、マージ後も結果が残り、追加後に承認されたストーリーや、どのビルドの差分にも現れない削除済みストーリーを検出します | commitSha / prNumber / buildId のいずれか |
list_pr_stories | get_pr_changeset の最初のページ以降にある、PR とベースとの差分セットのバケットをページ取得します | セレクター、kind: new | changed | removed、cursor?、limit? |
review_diff | 1つのストーリーに対する判定を記録します。ストーリーに未解決のレビューコメントがある間は、accept と ignore は拒否されます。deny は常に実行できます | diffResultId、decision: accept | deny | ignore |
accept_build | ビルド内の変更済みストーリーをすべてまとめて承認します。変更済みストーリーのいずれかに未解決のレビューコメントがある間は、拒否され、何も承認されません。エラーには該当するストーリーが示されます | 同じセレクター |
list_comments | デザイナー/QA(または別のエージェント)がビルドの差分に残したレビューコメントを読み込みます。ビルド全体の未解決コメントをすべて返します。各コメントには diffResultId、authorType、body、side、parentId(返信のスレッドルート)、anchor(画像上の点/領域)、resolved が含まれます。これは get_build / get_diff の comments 件数が参照している内容です | commitSha / prNumber / buildId のいずれか |
post_comment | ストーリー1つの差分にコメント(またはスレッドへの返信)を残します。たとえば、レビュー担当者の質問に返信したり、回帰を知らせたりできます。エージェントとして投稿されます。ブランチに新しいビルドがある場合、新しいルートコメントは拒否されます(最新のビルドにコメントしてください)。返信は常に許可されます | セレクター + diffResultId、body、side?、anchor?、parentId?(ルートコメントの ID への返信) |
resolve_comment | コメントの指摘に対応した後、コメントスレッドを解決(または再オープン)します | commentId(スレッドルート)、resolved |
update_comment / delete_comment | コメントを編集または削除します。エージェントが操作できるのはエージェント自身が作成したコメントのみです | commentId(更新時は body も指定) |
レシピ 1 — 「このビルドをトリアージする」
変更を分類し、人間に60行もの一覧ではなく重要な情報が伝わるようにします。PR に対して get_build を呼び出してください。最初のページの各ストーリーについて、AI 判定の結論(aiVerdict、回帰の場合は aiFlagReason)が含まれています。counts.changed がそのページの件数より大きい場合は、nextCursor を使って list_build_stories { status: "changed" } を繰り返し呼び出し、結果がなくなるまでページを取得してください。ページの境界で回帰を見落とさないためです。自分で判断する前に判定を読み、実際にコンテンツが変わったものについては、単独で評価せず、変更前の画像と照らし合わせて判断してください。
-
差分だけでなく、両方の画像をレンダリングします。
render_diff_imageのwhich: "baseline"とwhich: "candidate"を呼び出して比較します。緑色の差分マスクが示すのはピクセルが「どこで」動いたかであり、結果が良いかどうかではありません。暗い背景に暗いコードブロック、コントラストが失われたテキスト、クリップされたコントロールは、どれもマスク上では「ピクセルが変わっただけ」に見えますが、変更前と変更後を比べると回帰だとわかります。候補だけを見て判断すると、「色が付いたから改善」として、読めないコードブロックを見逃してしまいます。判定でフラグが立ったものについては、get_diffでaiReasoning/aiFlagReasonを取得してください。 -
判断する前に、人間のコメントを読みます。
get_buildのcomments.unresolved > 0の場合(またはget_diffのストーリーにcommentsが表示される場合)、デザイナー、QA、または別のエージェントがこのビルドにコメントを残しています。list_commentsでコメントを読み取れます。各コメントにはdiffResultIdが紐付いているので、分類したストーリーと照合できます。コメントにはピクセルだけではわからない背景(「この余白は意図的なので元に戻さないでください」/「ここではロゴが違います」など)が記されているため、ストーリーを分類する前に読んでください。いずれにしても、未解決のコメントがあるストーリーは承認できません。accept_buildとreview_diffの accept/ignore は、未解決のコメントがあるストーリーを拒否します。手順は、コメントを読む → 指示に対応する(多くの場合、コードを変更して再度プッシュする)→resolve_comment→ 承認、です。承認を進めるためだけにコメントを解決済みにしてはいけません。対応したからこそ解決済みにしてください。
実際のストーリー名と変化率を使い、次の4つに分類して報告します。
- 回帰 — 実際に変更され、問題があるもの — コンテンツが変わり、変更後の表示が壊れている、または悪化しているものです。例として、読めない/コントラストの低いテキスト、クリッピング、重なり、コンテンツの欠落、何のコントロールか判別できなくなったものがあります。現在の PR が引き起こした回帰も、依然として回帰です。「自分の差分で説明がつく」からといって、それが正しいとは限りません。回帰は決して承認しないでください。トリアージが必要なのは、まさにこのためです。
- 意図した変更 — コンテンツが変わり、変更後が正しく、PR に記された意図に合致しているものです(意図的なスタイル変更、新しい文言、実際に新しく追加されたストーリーなど)。人間による確認は必要ですが、承認候補です。
- 見た目上の再配置 — 差分ピクセルはあるものの、差分画像を見ると同じコンテンツが数ピクセル移動しただけのものです(行が約5px下に移動した、テキストが薄く下に残って見える、など)。コンテンツの変更ではありません。
- レンダリングノイズ — 安全に承認可能 — グラフの縁やアンチエイリアス処理された線に生じた、0.4%未満の揺らぎです。純粋なレンダリングの揺らぎです。
判定の regression は、より詳しく調べるべき理由であり、無視する理由ではありません。 判定でフラグが立ったストーリーを「意図した変更」として扱おうとするなら、その aiFlagReason を覆す具体的な根拠を変更前後の画像から示す必要があります。「色の変更だから、おそらく問題ない」では不十分です。覆す根拠がない場合は、その判定を信頼してください。
出力例の形式:
67 件の変更されたストーリー → 分類。 ⚠️ リグレッション(2 件) — 誤っているように見える実際の変更です。承認しないでください:
SetupWizard/Playwright(8.3%、 コードブロックが暗い背景に暗い文字で表示され、判読不能 — 判定結果はregression)、Card/Compact(5.1%、タイトル が切り詰められている)… 1. 意図された変更(11 件) — コンテンツが変わり、正しく見えるため、承認候補です:Hero/Default(14%、 見出しの文言を更新)… 2. 表示上のリフロー(9 件) — 実際のピクセル変更で、垂直方向への移動のみです:EventStatusCard/All Variants(4.1%、 行が約 5px 移動)、TestingInit/Playground(3.7%)… 3. グラフのアンチエイリアスノイズ(6 件) — 0.4%未満のエッジの揺らぎで、安心して承認できます:FunnelChart/Smaller Screen(3.2%)、UpliftChart(1px のマークが 1 つ)… 新しいストーリー(12 件) — 初回のベースラインで、比較対象はありません:Button/AllVariants、…
トリアージの段階で承認しないでください。分類を提示して人に判断を委ねるか、レシピ 3 で明示的に実行してください。
レシピ 2 — 「ビジュアルリグレッションを要約する」
同じ内容を確認し、人の対応が必要な変更を出力します。まず ⚠️ リグレッション、次に意図されたコンテンツの変更を、PR コメント形式で 1 行ずつ、リフローやノイズなしで示します。PR に投稿するコメントです:
🔎 ビジュアルレビュー — リグレッション 1 件、意図された変更 2 件(フラグ付き 67 件中。リフロー/ノイズ 64 件は折りたたみの下に記載)
- ⚠️
SetupWizard/Playwright— コードブロックが暗い背景に暗い文字で表示され、判読不能(8.3%、判定:regression)Pricing/Card— CTA ボタンの色が青から緑に変更(12.4%)Nav/Header— ロゴが 8px 拡大(3.1%)
レシピ 3 — 「新しいベースラインをすべて承認する」
変更がすべて意図されたもの(意図的なスタイル変更)か、すべてノイズであり、リストにリグレッションが隠れていないことを確認済み(⚠️ の分類が空)なら、1 回の呼び出しですべての変更されたストーリーのベースラインを更新します:
accept_build { prNumber: 123 }
「見出しの変更は意図されたもの」という判断をビルド全体に波及させないでください。グローバル CSS やテーマの変更は、PR で変更するつもりのなかったストーリーにも及びます。「PR が原因で発生した」ことは「PR で意図した」ことを意味しません。変更された各ストーリーを、変更前後の比較に基づいて個別に分類してください。
一部は実際の変更で一部はそうでない場合、対象を絞って承認するには、diffResultId ごとに accept/ignore/deny を指定して review_diff を繰り返し実行します。意図された変更は承認し、ノイズは無視し、実際のリグレッションは人に確認してもらいます。
レシピ 4 — 「変更前後を PR に載せる」
人にダッシュボードへ移動してもらうのではなく、PR 内で確認できるようにします。トリアージ後、変更されたストーリーをインラインで投稿します:
- 最速の単一画像:
render_diff_imageのwhich: "before_after"を使うと、変更領域に切り抜いた変更前後を並べた PNG が 1 枚得られます(ストーリーの離れた複数箇所が変わった場合は、領域ごとの切り抜きが縦に並びます)。目視確認の手間を最小限にできます。保存して添付してください。 - ダウンロード可能なファイル/永続的なリンク:
get_diffは署名付き URL のbaselineUrl/candidateUrl/diffUrlを返します。ダウンロードして(curl -L "$url" -o before.png)PR に添付するか、ビルドリンクと URL をコメントに貼り付けてください。レビュー担当者が 3 枚の画像を並べた全体表示を開けるよう、ビルドリンク(https://uiverify.ai/builds/<id>)を必ず含めてください。
適切な PR コメントは、レシピ 2 の「リグレッション 1 件につき 1 行」の要約、リグレッションの変更前後の画像、ビルドリンクを含むものです。投稿して、探しに行かせないでください。
レシピ 5 —「この PR で UI 全体に何が起きましたか?」
get_build が答えるのは 1 つのコミットについて、つまり このビルドに未レビューのものがあるか です。PR 全体が意図どおりの変更を行ったことを証明するものではありません。また、仕組み上、次の 2 つは検出できません。PR の早い段階で追加して承認したストーリー(最新ビルドでは正常にレンダリングされるため、変更として表示されません)と、削除されたストーリー(ベースラインが削除または名前変更されているため、どのビルドにもレンダリングされず、ビルドごとの差分にもフラグが立ちません)です。両方を検出できるのは get_pr_changeset だけなので、PR を完了と判断する前に実行してください。
PR の変更セットを取得し、HEAD と base の全体像を確認します。
get_pr_changeset { prNumber: 123 } → new / changed / removed vs the base branch
list_pr_stories { prNumber: 123, kind: "removed" } → page any one bucket
すべての項目を次の基準で確認してください。この PR の差分で実際に行ったことに原因を帰属できますか?
- はい、かつ正しいです → PR による意図された視覚的な影響です。
- 自分の差分に原因を帰属できない変更 → そこで止めてください。承認せず、フレークとして片付けず、「再実行して様子を見る」こともしないでください。その旨を伝え、人間による確認を依頼してください。原因として考えられるのは、本来変更されるべきではないベースラインが変更されたことです。この問題は仕組み上、表面化しません。
- 意図していない
removedストーリー → 誤って削除された可能性があります(意図しない名前変更も、削除と追加として表示されます)。明確にフラグを立て、緑のチェックだけでそのままリリースされないようにしてください。
変更セットは GA です。有効化するフラグは不要で、公開プロジェクトなら、ログインしていなくても誰でもビルドの変更セットを読めます。
レシピ 6 —「レビューコメントに回答する、または解決する」
レビュー担当者のメモが回答できる質問である場合や、指摘された箇所を修正した場合は、レビュー担当者に追いかけてもらうのではなく、エージェントからやり取りを完了させます。list_comments でスレッドを読み、次の操作を行います。
- スレッドに返信する —
post_comment { diffResultId, body, parentId }を使います。parentIdにはルートコメントのidを指定します(返信はルートのサイドを引き継ぐため、独自のアンカーは不要です)。変更した差分を示して「なぜこれを移動したのですか?」に答えたり、「次のプッシュで修正済みです」と伝えたりするために使います。 - 回帰を指摘する — 新しいルートコメントとして
post_comment { diffResultId, body, anchor? }を使い、レビュー担当者に確認してもらうため、残しておくストーリーを指摘します(レシピ 1 の ⚠️ バケットと併用してください)。 - 解決する — メモに対応したら(依頼された変更を行った、または質問に回答したら)、
resolve_comment { commentId, resolved: true }を使います。承認する前に解決してください。未解決のスレッドがあると、accept_build/review_diffはそのストーリーを拒否します。スレッドが本当に完了した場合にのみ解決してください。未解決のコメントは、早まった承認を防ぐシグナルです。
このキーが作成するコメントはエージェントとしてのものであり、自分が作成したコメントのみ編集・削除できます。人間のコメントには決して手を加えないでください。
ビルドに合格しましたか?それでもピクセルを確認できます
変更のないビルドでは 3 枚組画像は表示されませんが、コンポーネントはレンダリングされています。「何も変わっていない」という結論を信頼できるのは、実際に確認できた場合だけです。list_build_stories { status: "unchanged" } で変更のない項目を列挙し(返された nextCursor を使ってページを進めます)、その後、ストーリーの ID を指定して現在の画像を取得します。ベースライン URL には get_diff { buildId, storyId }、画像には render_diff_image { storyId, which: "baseline" } を使います。これにより、緑のビルドでコンポーネントの表示が正しいことを確認したり、ダッシュボードを開かずにレビュー担当者へ「こちらがベースラインと同一の画像です」と提示したりできます。
ガードレール
- 書き込む前に読みます。
review_diff/accept_buildの前に、必ずget_build(曖昧な点がある場合はrender_diff_imageも)を実行します。確認していないビルドを承認しないでください。 - 変更後だけでなく、変更前と変更後を比較します。 内容が変更されたストーリーでは、
which: "baseline"とwhich: "candidate"の両方をレンダリングして確認します。差分マスクは変更箇所を示し、両方を並べることで悪化していないか(コントラスト/読みやすさ、クリッピング、レイアウト)を確認できます。「色が付いた/動いた」ことは「改善した」ことを意味しません。 - AI 判定の
regressionは、原則として信頼すべきシグナルです。 これを覆すには、変更前と変更後を見て、そのaiFlagReasonを覆す具体的な根拠が必要です。単にこの PR が原因だというだけでは、無視する理由になりません。 - 一括承認はベースラインの変更です。
accept_buildを実行すると、変更されたすべてのストーリーで候補が新たな正となります。リストに実際のリグレッションが隠れていないことを確認した場合に限り、実行してください。 - 未解決のコメントがある場合は承認できません。
get_buildのcomments.unresolved > 0は、まだ読んでいない人間のメモがあることを意味します。review_diff/accept_buildの前にlist_commentsで確認し、その内容を尊重してください。「これを承認しないでください」というレビューコメントがあるのに、エージェントが一括承認する事態を防ぐために、この件数があります。 - 「新しいストーリー」と「変更されたストーリー」を区別します。 初回ベースラインのストーリーには比較対象がありません。これはリグレッションではなく、ベースラインが必要なだけです。別に分類してください。