この記事は6080文字16で読めます

梅雨の季節ですね。じめじめしてますが、犬は元気です。でも家の庭にキノコが生えてきました…。最悪です。

さて、妻の友人から「犬の幼稚園のサイトを作ってほしい」と頼まれました。Figmaでデザインが作られているものからサイトを構築するというお仕事です。

育休から職場復帰して周りのエンジニアがもうコードなんて一ミリも書いていない現実を目の当たりにして絶望していた私は、この機会を使って徹底的にAIを使い倒そうと思いました。

そこでプロンプトは最小限にして、AIに差分検出から修正までのループを自律的に回させる構成を考えました。

いわゆるループエンジニアリングの実践です(実装当時はループエンジニアリングという言葉はなかったですが)。

#Table of Contents

#KNOT for dogsの紹介

まず、今回サイトを構築させていただいた犬の幼稚園、KNOT for dogsを紹介させてください。

KNOT for dogsトップページ

KNOT for dogsは犬の幼稚園です。

横浜市営地下鉄ブルーラインのセンター北駅から徒歩4分に、ワンちゃんたちが安心して社会性やマナーを楽しく学べる素敵な幼稚園がオープンしました。

我が家のむぎもプレオープンで訪問させていただきましたが、他の犬たちと一緒に過ごす様子は本当に楽しそうでした(この話はあとがきで改めて書きます)。

さて、そんな犬の幼稚園KNOT for dogsのWebサイトを作ることになったきっかけは妻でした。

妻が、KNOT for dogsのオーナーの北山さんと友人で、その伝手でサイトを作ってほしいというご依頼をいただき、制作を実施しました。

ただ、私はWebサイトのデザインはできません…。

そこでサイトの素敵なデザインを手がけてくださったのが@maiko_krhrさんです(こちらは妻の職場の同僚です、顔広すぎだろ)。

幼稚園のコンセプト、結び目を基調とした洗練されたデザインの中にユーザーの体験が機密に設計されたデザインをPC版・SP版の両方をセクション単位で丁寧にFigmaで作り込んでいただきました。

実装していてワクワクするデザインがあったからこそ今回のループエンジニアリングの実践が成り立っています。改めて感謝です。

FigmaデザインカンプのPC版・SP版

#エンジニアリングの進化とループエンジニアリング

AIを使った開発の手法論は、AI駆動開発が開発現場で一般的になりつつある昨今1〜2年で急速に体系化されてきました。

まずは今回の実践がどこに位置づけられるのか、超ざっくり振り返っておきます。

出発点はプロンプトエンジニアリングで、LLMへの指示文そのものを最適化する手法です(Prompt Engineering Guideより)。

そこからコンテキストエンジニアリングへ発展しました。

プロンプトだけでなく、システムプロンプト・ツール定義・会話履歴などLLMに渡すコンテキスト全体を設計する考え方です(Anthropic公式記事より)。

さらにハーネスエンジニアリングという概念が登場します。

Mitchell Hashimoto氏のブログ記事で語られた考え方で、エージェントが動作するインフラ全体、つまりツール群・実行環境・エラー処理・ガードレールといった足場を設計する手法です。

そして今回の主題、ループエンジニアリングです。

Addy Osmani氏が提唱した概念で、核心は人間が毎回プロンプトを入力する役割をやめて、エージェントを駆動するループそのものを設計するという思想です。

ハーネスがエージェントを取り巻く環境全体を設計するのに対して、ループはその中の繰り返し構造に焦点を当てています。

今回作ったループエンジニアリングのスキルは、検証→計画→修正というサイクルを自動で回す仕組みなので、まさにループエンジニアリングの実践になります。

#design-reviewスキルの設計

ここからが今回の実践の本題です。

作ったのはdesign-reviewというClaude Codeのスキルで、FigmaデザインとWordPress実装のビジュアル差分をゼロに近づけることを目指しています。

WordPressで実装しているのはクライアントの希望によるものですが、この記事ではWordPress自体の実装話は割愛します。

スキル設計のポイントは、Orchestrator + 4つのサブエージェントで構成したことです。

Claude Codeのサブエージェント機能を活用して、検証・計画・修正をそれぞれ独立したエージェントに委譲しています。

design-reviewスキルのフロー図

Orchestratorはサブエージェントの起動・コンテキスト中継・ループ管理のみを担当します。実装・検証・計画はすべてサブエージェントに委譲するのが原則です。

本来は/goalコマンドを使って、差分がゼロになるまで自動でループを回し続ける構成にしたかったのですが、実装当時のClaude Codeにはまだ/goalコマンドが存在しませんでした。

そのため、最大3サイクルという固定回数でループを打ち切る仕組みにしています。

#検証フェーズの役割分担

なぜ検証を2つのエージェントに分けているのか。それは、個々の要素の精度画面全体の配置では、得意な検証手段が異なるからです。

ボタンのサイズフォントの間隔といった個々の要素レベルのチェックは、Figmaのデザインコンテキストから値を取得して比較すると非常に精度が高くなります。

一方で、画面全体を俯瞰したときの要素の配置状況、たとえばセクション間のマージンや要素同士の位置関係は、個々のデザインコンテキストだけでは追いかけにくい問題です。要素単位のマージン値を見ればわかることもありますが、pixelmatchの差分画像から視覚的にざっくりズレている箇所に当たりをつけた方が、ずれている箇所を素早く特定できます。

この2つの視点を組み合わせることで、検出の網羅性を上げています。

CSS VerifierはFigmaの get_design_context でデザイン値を取得し、Playwright MCPでブラウザを開いて getComputedStyle() で実測値を取得します。

フォントサイズ・パディング・マージン・色など主要プロパティを一括比較して、差分を一致・軽微(1〜2px)・要修正の3段階で評価するエージェントです。

ここで単に要素にあたっているスタイルをチェックするのではなく、 getComputedStyle() を使っているのにも理由があります。実際のWebページでは、さまざまなCSSルールがカスケードして1つの要素に適用されます。

個々のCSSファイルだけを見ていると、別のルールが意図せず上書きしていることに気づけず、修正のたびに画面が少しずつ崩れていく事象が起きました。

個々のCSSルールではなく、最終的に要素に適用されている算出値を確認しながら実装を進める必要があります。これはフロントエンドエンジニアが開発者ツールで日常的にやっていることですが、AIにも同じアプローチを取らせると精度が上がることがわかりました。

Visual VerifierはFigmaのデザイン画像を取得し、ブラウザのフルページスクリーンショットとピクセル差分を計測します。数値だけでなく差分画像を視覚的に分析して、ヘッダー上部の余白差やカードの幅ずれのような具体的な差分エリアを特定するのがポイントです。

#計画・修正フェーズの役割分担

Plannerは両Verifierの結果を受け取って判定します。 diffPercent が1%以下かつCSS要修正項目がゼロなら完了判定です。そうでなければ、CSS差分と視覚差分が重なっている箇所を優先度高、CSSのみの差分を優先度中として修正計画を出力します。

GeneratorはPlannerの修正計画だけに基づいてCSSファイルを修正します。独自判断での追加修正はしないルールにしていて、これが意外と大事でした。勝手に直されると他のセクションが壊れたりするんですよね…。

#差分検出の仕組み

design-diff.jsというスキルから使えるscriptでは、pixelmatchを使って2枚の画像をピクセル単位で比較します。

Figmaのキャンバスとブラウザのスクリーンショットではサイズが異なることが多いので、Nearest Neighbor法でリサイズし、高さが足りない側を白パディングで合わせてから比較するのがこのスクリプトのポイントです。

const [normFigma, normActual] = normalizeImages(figmaImg, actualImg);

const diffImg = new PNG({ width, height });
const { default: pixelmatch } = await import('pixelmatch');

const diffPixels = pixelmatch(
  normFigma.data, normActual.data, diffImg.data,
  width, height,
  {
    threshold: parsed.threshold,
    includeAA: false,
    diffColor: [255, 0, 0],
    diffColorAlt: [0, 0, 255],
  }
);

const diffPercent = ((diffPixels / totalPixels) * 100).toFixed(2);

差分画像では赤色が実際の差分、青色がアンチエイリアス差分として可視化されます。

diffPercent が1%以下になれば完了、という閾値で判定しています。

pixelmatchによる差分検出の結果

ちなみに、ブラウザ側のスクリーンショット取得やCSS実測値はPlaywright MCP経由で自動化していますが、ビューポートサイズにちょっとした工夫があります。

Figmaのキャンバス幅が1280pxの場合、ブラウザのビューポートは1295pxに設定しています。+15pxはスクロールバー補正です。こういう地味な部分を調整しないといつまでもdiffPercentが減らないのです…。

#実際に回してみた結果と限界

さて、ここからが正直な振り返りです。

当時使っていたモデルはSonnet 4.6をメイン、Opus 4.6をアドバイザーとした構成でした。

本当はOpusでメインループを回したかったのですが、Opus 4.6で回すとかなり早い段階でレートリミットに到達してしまう問題がありました。

私のショボプランでは5時間制限にすぐ引っかかってしまうので、Sonnetをメインにして、Opusはアドバイザー役に限定する構成にしていました(ケチとか言わない)。

この構成で3サイクル回した結果、肌感ですが1ページを対象にだいたい90%のデザイン再現まではループで到達できました

大きなレイアウト・色・フォントサイズ・余白感は、初回〜2回目のサイクルでかなり近づきます。これはめちゃくちゃありがたいです。

しかし、残り10%が難しかったです

厄介だったのが、装飾要素のポジショニングです。

今回のデザインでは結び目のSVGのような装飾要素が多用されていて、これらはFigma上では親要素に対する絶対位置で配置されています。つまり position: absolute で実装すべきなのですが、Sonnetは position: relative で書こうとする癖がありました。

relativeで配置すると文書フローの中に要素が残るため、どう調整してもFigmaと同じレイアウトにはなりません。なのでいつまで経ってもdiffPercentが1%以下にならず、余計な修正をしまくってどんどん悪化していくという悪循環に陥りました。

結局「この装飾要素はabsoluteで配置してください」と明示的に指示を入れて修正をかける必要がありました。こういったCSS設計の判断は、まだ人間が介入すべきポイントです。

もう1つ厄介だったのがレスポンシブデザインです。

PCとSPで別々のFigmaノードが用意されていましたが、ブレイクポイント付近の挙動やSP専用の要素への切り替えが意図通りにならないことが多かったです。

特に、SP特有のSVGや要素の表示・非表示の切り替えは、SPの画面を修正するループでPC画面にデグレが発生する、という問題が頻発しました。

デグレが発生しないようにループの細かい調整をする際に、結局CSSやHTMLを読んで、「この要素はSP専用だからPCのループでは触らないようにする」といった判断を人間が入れる必要がありました。

コンテキスト量の問題もありました。Figmaのデザインは各ページごとにセクション単位で作り込まれていたため、それをそのままコンテキストとして渡すとかなりの量になりますので、各実装ターンごとに対象セクションを絞ってループを回す必要がありました。

最終的にはループエンジニアリングだけですべてが完結したわけではありません

とはいえ、何もない状態からFigmaデザインの90%を自動ループで再現できたのは、個人的にはかなりデカい成果です。スキルを起動して待つだけでデザイン実装が進んでいく体験はなかなか快適でした。

新しいモデルやFableであれば、この辺りさらに精度高くやれる予感はしていますが、まだ試せていないので今後の楽しみにしておきます。

#最後に

今回の実践を通じて、ループエンジニアリングの可能性と現在の限界を肌で感じました。

さて、話は変わりますが、飼い犬のむぎはKNOT for dogsのプレオープンにご訪問させていただきました。

他の犬たちと一緒に過ごす様子は本当に楽しそうで、普段とは違う表情を見せてくれました。

KNOT for dogsのプレオープンに訪問したむぎ

久しぶりに犬のお友だちと気兼ねなく走り回れたのが相当楽しかったらしく幼稚園から帰ってきたら、遊び疲れて速攻で爆睡していました…。

tubone24にラーメンを食べさせよう!

ぽちっとな↓

Buy me a ramen