mcpField
D
diff

diff

github.com/adobeテスト・品質318301 回インストール1 日前に更新
リポジトリ

diff について

stardust:diff は、Playwright でレンダリング済みのプロトタイプと構築ページを検査するスキルです。計算スタイルと DOM 構造を調べる相補的な2種類のプローブを、プロファイルでフレームワーク非依存にしています。

主な機能

  1. 01視覚・構造を比較する2種類のプローブ
  2. 02計算スタイルと DOM の分析
  3. 03EDS・汎用プロファイル
  4. 04メインコンテンツとヘッダー・フッターの検査
  5. 05公開ページのクリッピングと内容有無の検査
  6. 06ヘッダー操作のパリティ確認

活用シーン

  1. 01EDS 変換結果をプロトタイプと照合
  2. 02Figma の書き出しと構築ページを比較
  3. 03内容欠落やレイアウト差を検出
  4. 04再構築したヘッダーの動作を元ページと照合

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/adobe/skills --skill diff

インストール後、Claude Code で/diffを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

scripts/
chrome-compare.mjs
chrome-explore.mjs
clip-probe.mjs
content-diff.mjs
content-inventory.mjs
content-presence.mjs
diff-profiles.mjs
image-signature.mjs
live-session.mjs
measure-live.mjs
unit-geometry.mjs
visual-diff.mjs
scripts/test/
chrome-compare.test.mjs
chrome-explore.test.mjs
chrome-fixtures.mjs
clip-probe.test.mjs
content-diff.test.mjs
content-presence.test.mjs
image-signature.test.mjs
live-session.test.mjs
unit-geometry.test.mjs
visual-diff.test.mjs

詳細

stardust:diff — プロトタイプ ↔ ビルドの照合

ソースのプロトタイプとビルド済みのページを比較する2つのプローブです。互いに異なる種類の問題を検出するため、両方を実行してください。どちらか一方だけでは、「問題なさそう」という誤った判断につながります。

どちらもフレームワークに依存しない Playwright プローブで、レンダリングされた2つの URL を計算済みスタイル + DOM(ピクセルではありません)で比較します。スタック固有の言葉はすべてプロファイル(--profile eds|generic)に集約されており、比較ロジックは汎用です。

使用する場面

  • プロトタイプを EDS に変換した後(stardust deploy スキルの Step 10)— --profile eds を使用します。
  • レンダリングされた2つの URL の間で「ビルドはデザインに一致しているか」を確認する場合(Figma エクスポートと React ビルド、レガシーページと再構築したページなど)— --profile generic を使用します。
  • 変換/QA ワークフロー内の検証ゲートとして使用する場合(ワークフローでの使用を参照)。

対象外:JS による装飾のない単一の静的ファイル(コンポーネントを装飾するため、ビルド/ハーネスの URL を使用してください。生の .plain.html には分類対象となるロールがありません)。

2つのプローブ

プローブスクリプト検出するもの検出できないもの
ピクセル/レイアウトskills/diff/scripts/visual-diff.mjs(ソースと比べて)引き伸ばされた画像、最大幅の折り返しの欠落、空白のレンダリング、サーフェス/グラウンドの色の反転、見出しサイズ/書体の差、画像数の不一致「テキストは正しいが、配置先が違う」ケース、CTA の欠落(全ピクセルが揃い、色も妥当なためフラグが立たない)
構造コンテンツ + タイポグラフィskills/diff/scripts/content-diff.mjs見出し・アイブロウ・CTA の欠落/ロールの入れ替わり、本文の創作/欠落、レンダリングされた書体の分岐(幅のプローブ)、placeholder/aria-label/title の値の欠落、アイコンの欠落/誤り/移動 — デフォルトではメインルートと header/footer のクロームルートが対象ですジオメトリ/レイアウトのリグレッション

content-diff は各ルートから、ロールで分類された順序付きのインベントリ(heading/eyebrow/cta+href/body)を抽出します。対象は --main ルートと、デフォルトで header/footer のクロームルートです。計算済みスタイル + タグで分類するため、プロトタイプの DOM とビルド済み DOM を対称的に比較でき、その後、ルートごとに差分を比較します。各ルートには、placeholder/aria-label/title の値とアイコンを保持する2つ目のインベントリもあります。

実行方法

# Prereq 0: playwright importable from the project root (node -e "import('playwright')"), else
# npm i -D playwright pixelmatch pngjs cheerio --legacy-peer-deps
# (never --no-save; extract SKILL.md § Setup). Run the copied scripts from the project
# root: ESM resolves from the script.
# Copy the WHOLE skills/diff/scripts/ dir: content-diff imports its local diff-profiles.mjs
# AND content-inventory.mjs (deploy keeps its own synced copies for #93/#94).
# Prereq: a RENDERABLE source. Static → serve from its own dir (python3 -m http.server).
# The build URL must be the DECORATED page (live/preview or a local harness), not raw markup.
# ONE server, ONE port — probe before starting one (curl is always present, lsof is not):
curl -sI localhost:8791/ | head -1                   # 200/404 = something serves the port; no line = free
curl -sI localhost:8791/<prototype>.html | head -1   # 200 = it serves YOUR dir: reuse it
command -v lsof >/dev/null && lsof -nP -iTCP:8791 -sTCP:LISTEN   # optional: names the pid
# Nothing answered → start yours. A foreign server: never kill it; use a per-project port
# (the traps: ../deploy/SKILL.md § Step 10).
PROTO="http://localhost:8791/<prototype>.html"
BUILD="https://<branch>--<repo>--<owner>.aem.page/<path>"   # or http://localhost:3000/<harness>

# 1. PIXEL/layout
node skills/diff/scripts/visual-diff.mjs   "$PROTO" "$BUILD" --profile eds --sections ".hero"

# 2. STRUCTURAL content + type
node skills/diff/scripts/content-diff.mjs  "$PROTO" "$BUILD" --profile eds   # --json dumps both inventories

両方のツールで使えるフラグ:--profile eds|generic(デフォルトは eds)、--width <px>(デフォルトは 1280)、 --main <selector[,selector…]>(コンテンツのルート。content-diff のデフォルトはプロファイルから取得し、カンマ区切りのリストを指定します。各ルートのインベントリ作成と差分比較は個別に行われ、各指摘行にはそのルートが [root] として示されます。visual-diff ではセレクターを 1 つ指定し、デフォルトは main です)。content-diff のみ: --chrome / --no-chrome(デフォルトはオン。header と footer のルートを、main の各ルート外にある最初の <header>/[role=banner] と <footer>/[role=contentinfo] として解決し、main の横で比較します。一方に存在しないルートはそこで空として測定され、ルートの行にその旨が示されます)。さらに、ライブターゲットの設定があります(共有エンジン:scripts/live-session.mjs — すべてのコンテキストで実際の Chrome の UA と標準の Chrome リクエストヘッダーを送信します。UA だけでは Akamai クラスのボット管理で引き続き 403 になります)。

  • --ua <string> — ユーザーエージェントの上書き(デフォルトは実際の Chrome デスクトップ UA)。

  • --wait-until <state> — goto の待機条件を上書きします。デフォルトのルール(scripts/live-session.mjs にある共有の defaultWaitUntil)は、URL のサイドごとに次の 3 段階で決定されます。

    • localhost/127.0.0.1 → networkidle(ローカルのプロトタイプ/ハーネス。変更なし)。
    • EDS のビルド/プレビューのオリジン — ホスト名が .aem.page、.aem.live、.hlx.page、.hlx.live で終わるもの → networkidle(これらは非同期で装飾され、安定して networkidle に到達します。domcontentloaded で測定すると装飾前の DOM が読み込まれ、デプロイの Step 10 で不安定な誤検出の赤判定/FONT FORK が発生します)。
    • その他すべてのライブ http(s) → domcontentloaded(アナリティクスのビーコンがあるライブサイトは networkidle に到達しません)。

    --wait-until は 3 段階すべてを上書きします。

  • --dismiss [sel,...] — 両方のサイドでオーバーレイを閉じます。Cookie の同意(削除ではなくクリック)と、時間経過で表示されるマーケティング/ニュースレターのモーダルに加え、サイト固有のセレクターも追加できます。その後、マウスは待機位置に置かれます。

  • --headed — ボット管理されたサイトに対するエスカレーション:ヘッドありのステルス実 Chrome。

  • --locale <tag> — Accept-Language とコンテキストのロケールを固定します(固定しない場合、地域リダイレクトを行うサイトでは実行ごとに異なるロケールが取得されます)。

visual-diff ではさらに、--out <dir>、--sections a,b(セクションごとのスクリーンショット)も使えます。

どちらかのナビゲーションでボット管理のチャレンジ/ブロック中間ページが表示された場合、明確なエラーで失敗し、終了コード 3 になります。ソースとして測定されることはありません。--headed でエスカレーションしてください。それでもブロックされる場合、そのサイトには crawl.mjs クラスのキャプチャが必要であり、ヘッドレスではチェックを実行できません。

いずれかのサイドで通常の(チャレンジではない)HTTP エラーが発生した場合 — たとえば aem.page ではプレビュー反映前に通常発生するビルド側の 404 — 致命的エラーにはなりません。プローブは警告を明確に記録し、エラーページを測定します。フラグ(BLANK RENDER/コンテンツの非対称性)で問題を示し、終了コード 0 を返します。これがプローブの助言的な契約です。0 = 実行済み(フラグは助言)、1 = プローブエラー、3 = ボットチャレンジ。

公開オリジンのプローブ (#125)

content-diff はプロトタイプとビルドを照合しますが、稼働中のコマースオリジンを対象にすると、ノードごとの検出結果は誤りで、ピクセルゲートはすべてのカードが切り取られているページを合格にしていました(../replica/reference/source-fidelity-gate.md § 公開オリジンの全ページゲート)。確認可能な問いを尋ねるプローブは3つあります。いずれもウィンドウを使わない実際の Chrome の同じ階層で両側を読み込み、同じ方法で安定状態になるのを待ちます(scripts/measure-live.mjs)。

ORIGIN="https://www.example.com/<path>"; SERVED="https://main--repo--owner.aem.live/<path>"
node stardust/scripts/diff/clip-probe.mjs "$SERVED" [--json clip.json]                       # D1: exit 2 on cut / hidden text or controls
node stardust/scripts/diff/content-presence.mjs "$ORIGIN" "$SERVED" [--variable "<selO>=<selE>"]  # D2: exit 2 on MISSING/HIDDEN link or heading
node stardust/scripts/diff/unit-geometry.mjs "$ORIGIN" "$SERVED" --unit "<selO>=<selE>" --n 2      # D3: exit 2 on an element off / hidden / missing

読み方:🔴 のクリップグループは、クリップしている要素(固定高のボックスで、overflow: hidden が指定されています)を示します。そのブロックの CSS を修正し、コンテンツは決して変更しないでください。HIDDEN LINK は DOM 内にあるもののクリップされているリンク(CSS)、MISSING LINK は配信されていないリンク(エンコーダー)、CONTROL STATE は同じコントロールの別の値、COUNT … はセッションごとに変化する領域なので目視で確認してください。オリジン側で HTTP ≥ 400 が発生した場合はエラー終了(exit 4)し、ボットチャレンジが発生した場合もエラー終了します(exit 3)。注意点:無限スクロールするオリジンは安定状態を待っている間も読み込み続けるため、その領域に --variable を指定してください。稼働中のオリジンに <main> がない場合はページ全体同士を比較します(スコープの行にその旨が表示されます)。--chrome を指定しない限り、ヘッダーとフッターはクロームのクロップゲートに任せます。カウント表現と「続きを読む」のヒューリスティックは英語の単語リストです(--count-words、--more-words)。

ヘッダーの契約 (#132)

上記のすべてのプローブは、ヘッダーが静止している状態(ポインターを置き、動きを停止)で確認するため、すべてのメニューが間違っていても合格してしまいます。実際に納品された実装では、ホバー式のメガメニューに定型的なクリック式ドロップダウンを使い、検索とカートを単なるリンクにし、ドリルダウン式ドロワーを静的なリストにしていました。ヘッダーの動作をソースから一度記録し、ビルドをそれと比較します。

node stardust/scripts/diff/chrome-explore.mjs "$ORIGIN" stardust/chrome/header-contract.json   # 1440 + 390
node stardust/scripts/diff/chrome-explore.mjs "$SERVED" stardust/chrome/header-build.json
node stardust/scripts/diff/chrome-compare.mjs stardust/chrome/header-contract.json stardust/chrome/header-build.json \
  --profile replica --json stardust/chrome/header-parity.json    # functional under redesign and reskin

エクスプローラーは(デスクトップで)ホバーし、すべてのヘッダーコントロールをクリックしてキー操作し、開いた状態をそれぞれ記録します(リンク、見出し、クロップ、aria-expanded、フォーカス、スクロールロック、動き、閉じる操作)。--max-states 400 --max-depth 4 の範囲内でネストされた階層、検索の入力候補と送信、スクロール後のヘッダーも記録し、サマリー行で作業量を示します。比較ツールは、コントロールや状態の欠落(ホバーをクリックとして再構築したケースも含む)、パネル内のリンク、キーボード操作、閉じる操作、スクロールロックに問題があると exit 2 で終了します。replica は、開いた状態のクロップ(2 %)、動き(max(80 ms, 25 %))、スクロール状態も追加します。検出結果をクリアできるのはサイトの所有者だけです:stardust/chrome/header-decisions.json({ decisions: [{ finding, decision, reason, by, at }] })に記録してください。エージェントが署名することはできません。抽出時の --prep(契約)、基盤シェル、デプロイのステップ10、引き渡し前のライブホスト(done-check header_parity)、および qa で実行されます。クロップは stardust/.work/chrome/ に保存されます。

content-diff の読み方

  • 🔴 CTA/見出し/アイブロウの欠落 — 実際にコンテンツが欠落しています。修正してください。アイブロウの欠落は、多くの場合、見出しの前にあるアイブロウがセグメンテーション時に脱落したことを示します。CTA の欠落は、コンポーネントでリンクがレンダリングされなかったことを意味します。これらはまさにピクセルプローブでは検出できない問題です。
  • 🔴 ロールの入れ替わり — 同じテキストが別のロールで表示されています(本文がアイブロウとして描画されている、アイブロウがティーザーに組み込まれているなど)。コンポーネントのノードセグメンテーションを修正してください。同じ見た目で、本文/アイブロウ → 見出しとなっている場合は、許容されるロールの昇格です(ルート行と --json は accepted となり、指摘にはなりません)。見出し → 本文/アイブロウは 🟡 ロールの降格です。視覚的に非表示のテキスト(sr-only)が失われた場合は 🟡 sr-only テキストの欠落です。sr-only のリンクが失われた場合は 🔴 CTA の欠落です。
  • 🔴 プレースホルダーの欠落、アイコンの欠落、インタラクティブ要素上のアイコンの差異(入力欄、ボタン、リンク、またはその内部)— ローカライズされた検索プレースホルダーの欠落、アイコンのない共有リンク、ロケールリンクの誤った旗、空のアイコン枠などです。修正してください。インタラクティブ要素の外側で同じ指摘がある場合は 🟡 です。aria-label の欠落は、どこにあっても何も失われていない 🟡 です(ゲートを通過できません)。
  • アイコンは、同一性または同じピクセルでペアになります。再ホストされたファイル(SAME IMAGE)や <img> として描画された svg(SAME ICON)は許容されます。レイアウト上のサイズが 0×0 のアイコンは該当しません。
  • 🟡 タイトルの欠落 — 常に 🟡 です。タイトルのツールチップは多くの支援技術では読み上げられず、設計上よく省略されるためです。🟡 アイコンの移動 — 同じアイコンが別のアンカーにあり、アンカーの照合後にペアになった場合(通常は、テキストのない <button> が button#n としてアンカーになるケース)は、配置を確認してください。🟡 アイコンの種類(非表示アイコンなど、ピクセルを比較していないもの)と、すべての余分な要素。
  • すべての指摘行にはルートが記載されます — 🔴 MISSING PLACEHOLDER [header]: … — レポートにはルートごとに 1 つのブロックが出力されます(root "main"、root "header" など。片方にしかないルートは、もう片方では空として測定され、その行にどちら側かが記載されます)。
  • 🟡 本文の欠落/余分な要素 — 本文の文章が失われた場合、またはソースにないビルド側の文言です。通常はプレースホルダーから実際の文言への書き換えです。意図したものか確認し、むやみに「修正」しないでください。
  • 🟠 フォントの分岐 — 描画された FACE が異なる一致行です(幅プローブを使用し、document.fonts.check は使用しません)。source X→sys は、プロトタイプでフォント X が指定されていたものの読み込まれず、システムフォントにフォールバックしたことを意味します。その場合、ビルドが意図されたフォールバックをセルフホストしているのは正しく、バグではありません。分岐した行はすべて 1 つの参考情報にまとめられます。
  • 既知の制限 — ノード単位の JOIN/SPLIT は 🔴 として報告されます(#87)。 ソースでは 1 つのテキストランが N 個の兄弟ノードとして描画され、ビルドでは同じテキストが 1 つのノードとして描画される場合(またはその逆。例:3 つのファクトチップと、1 つにまとめられたチップの span)、現在の差分では実際には問題がないケースでも、欠落 + ロールの入れ替わり + 余分な要素として報告されます。連結照合が実装されるまでは(同じ領域内で、ソースのノードがビルド側ノードの部分文字列である場合 → 🟡 JOIN/SPLIT の参考情報)、🔴 のテキストを連結したものが EXTRA の指摘のテキストと一致するか確認してから、コンテンツの欠落と判断してください。妥当と確認されたものは合格です。

合格基準: 視覚的な赤い指摘がない、または妥当と確認済み かつ content-diff が 🔴 0 件、何も失われていない 🟡 0 件(../replica/reference/source-fidelity-gate.md § 合格基準の項目 1)。修正のたびに両方を再実行してください。

プロファイル

skills/diff/scripts/diff-profiles.mjs に定義されています。プロファイルには、ソース/ターゲットのラベル、フラグごとの修正のヒント、フォント差分のしきい値、デフォルトのコンテンツルートセレクター、およびアイブロウの分類しきい値が指定されています。エンジンにスタック固有の文字列は含まれません。

  • eds(デフォルト)— Edge Delivery/DA の修正用語と、stardust の deploy スキルの指摘番号を使用します。
  • generic — どのスタックにも使える中立的なソース/ビルド用語です。

diff-profiles.mjs の generic をコピーし、hints を編集してプロファイルを追加してください。

共通エンジンとループ内の兄弟スクリプト

構造プローブの分類器と差分ツールは skills/diff/scripts/content-inventory.mjs にあります (deploy では同期済みのコピーをインポートします)。これらは、stardust deploy スキルの2つのゲートと同じ計測器で検証します。 section-schema.mjs(コード作成前の ENCODE/DECODE 契約、deploy #93)と block-roundtrip.mjs (ループ内のブロック単位のゲート、deploy #94 — 同じインベントリ差分を、作成時にローカルの decorate() ハーネスに対してブロックごとに実行し、DA は不要、終了コードで判定)。変換中はループ内ゲートを実行し、最終的なデプロイ後の証明として、このスキルの2つのプローブを実行してください。ここで初めて見つかった欠陥をループ内ゲートが見逃していた場合、配信パイプラインが転送中にコンテンツの形状を変えています。作成時の内容ではなく、ブロックのフラット化された形状へのフォールバックを修正してください。

ワークフローでの使用

検証フェーズで両方のスクリプトを呼び出し、デプロイの Validate フェーズと同様に、配信済みの装飾済みビルド(ローカルハーネスまたはブランチプレビュー)上で出力をゲートとして使ってください。visual-diff の STRETCHED/FLUSH-LEFT/SURFACE-GROUND/GAP/HEADING FACE フラグ(正当な理由がある場合を除く — フォント置換は HEADING FACE ×N の1行です)と、content-diff の 🔴 をすべて修正し、🟡/🟠 を確認して、両方がクリーンになるまで繰り返してください。

コメント