設計書から Thymeleaf を生成する:IR・生成器・ループとグラフ
ウォーターフォールで画面設計書を正とし、Spring Boot + Thymeleaf のマルチページアプリケーション(MPA)を自動生成する、という前提で考えた構成をまとめます。狙いは「人が画面の HTML を直さない」「生成AIに毎回『次はこれをやって』と指示し続けない」ことです。
用語の説明から入るので、ループエンジニアリングやグラフエンジニアリングに詳しくない人向けです。先に結論だけ言うと、ループは1つの係の繰り返し、グラフはその係の配置と戻り先の配線です。後者は前者を捨てません。中にループを残したまま、外側を線でつなぎます。
この記事は生成AIで書かれています。
ほぼレビューをしておらず、自分用のメモとして載せているだけなのでわかりにくい個所もありますがご容赦ください。
この記事で言うこと
- フロントは画面ごとに HTML を書かない。閉じた UI カタログを、画面 IR から合成する
- 生成器はチャットボットではなくコンパイラである
- 人が触るのは仕様 IR・UI カタログ・生成器の3つだけ
- ループは「AIに任せる」ことではなく、ゴール・ゲート・停止条件を先に書くこと
- グラフはループの卒業ではなく包含である。直し先が複数に分かれたとき、誰が次か・どこへ戻るかを線にする
- 失敗したとき、生成物をヤスリで直してテストを緑にしない。Critic は原因の種類だけを返し、それがグラフの次の線になる
対象は次のような人です。
- 画面一覧・画面遷移図・項目定義書が成果物として残る開発をしている
- Spring Boot と Thymeleaf のサーバサイドレンダリングを使う(または使う予定がある)
- 生成AIを「速い下書き係」以上にしたいが、チャット往復に疲れた
なぜ Thymeleaf の MPA が、このプロセスに合うか
ウォーターフォールの画面成果物は、だいたい次の形をしています。
- 画面一覧(画面ID、URL、概要)
- 画面遷移図(どのボタンで次の画面へ行くか)
- 項目定義(名前、型、必須、桁、権限)
- 入出力とメッセージ
MPA の「URL・POST・次画面」は、この形と同じです。フォームを送信し、サーバが次の HTML を返す。確認画面・完了画面もある。日本の基幹系でよく見る流れそのものです。
SPA のクライアント状態は、設計書に現れにくいです。生成AIが「それっぽい画面」は作れますが、項目定義との一致を機械的に検査しづらい。Thymeleaf なら、Form オブジェクトと HTML の対応をテストで数えられます。
もう一つの理由は、部品集合を閉じられることです。使えるレイアウトとウィジェットをカタログとして固定すれば、生成器は未知の HTML を発明しません。AI の創造性は、画面を自由に書くことではなく、設計書の正規化と生成器の改良に使います。
核心:生成された画面は「投影」である
人が極力手を出さない、と言ったとき、やりがちな失敗はこれです。
生成AIに画面を書かせ、気になるところを人が Thymeleaf で整える
次に設計書から再生成すると、人の手入れは消えます。差分レビューは仕様ではなく「手癖」になります。生成器のバグは直らないので、次の画面でも同じ欠陥が出ます。
だから境界を先に決めます。
| 層 | 例 | 誰が触るか |
|---|---|---|
| 仕様 IR | 画面・項目・遷移・権限・メッセージの YAML | 人、および設計書パーサ |
| UI カタログ | list / form / confirm / complete と基本ウィジェット | 人(稀。見た目の正が変わるのでレビュー必須) |
| 生成器 | JSON Schema、型紙、依存グラフ | 人、および「型紙が悪い」と分かったときの AI |
| 画面コード | Controller、Form、templates/screens/
|
生成のみ。人手も AI も直接編集しない |
| 業務ロジック | domain / application | 画面生成グラフの外。別仕様・別ループ |
ディレクトリで分けると、エージェントが迷いません。
specs/ # 正規化した仕様(IR)
ui-catalog/ # 使ってよい部品の契約
codegen/ # 生成器(型紙と Emitter)
src/main/resources/templates/
_catalog/ # 実行時の Thymeleaf fragment(カタログ実装)
screens/ # 生成物。触らない
src/main/java/.../web/ # 生成された Controller / Form
確認画面は日本の遷移の定石なので、最初から第一級のレイアウトにします。後付けの JavaScript 確認ダイアログに逃がさない方が、遷移の辺に乗ります。
Thymeleaf 側の決め事は短くて足ります。
- Layout Dialect の魔法より、
th:replace/th:insertで部品の参照関係を明示する - 入力は必ず
th:fieldで Form に束縛する - 画面固有の CSS / JS は出さない。動的制御は
data-*を1つの汎用ランタイムが解釈する - HTMX を使うなら、IR の部分更新ノードとしてだけ使う。JSON API とフロント枠は並行しない
- Controller に業務分岐を書かない。次画面は遷移の表引きにする
IR とは何か
IR は Intermediate Representation(中間表現)です。設計書とソースコードのあいだに置く、機械が読める正規化データです。
Word や Excel、画面イメージは人向けです。レイアウトも項目名の揺れも大きい。一方、Spring の Controller や Thymeleaf はファイル名・型・検証ルールが厳密です。この二つを直結すると、生成器が毎回「解釈」してしまい、同じ設計書なのに出力が揺れます。
そこで、一度 YAML(や JSON)に落としてから生成します。
screenId: customer-edit
path: /customers/{id}/edit
layoutId: form
fields:
- name: name
widgetId: text
required: true
maxLength: 80
- name: email
widgetId: text
required: true
actions:
- id: confirm
next: customer-confirm
設計書パーサの仕事は、Excel をこの形にすることまでです。以降の係は Excel を見ません。画面 IR に最低限持たせる情報は次です。
-
screenId/path/method… URL と Controller マッピング -
layoutId… カタログ上のレイアウト -
fields[]… name、widgetId、必須、型、権限、表示条件 -
actions[]/next[]… ボタンと、成功・失敗・権限不足ごとの次画面 -
messages[]… メッセージキー(本文はメッセージ定義へ) -
fixtures[]… スナップショット用の代表データ
widgetId がカタログに無い画面 IR は、生成せずに落とします。新しい見た目が必要なら、画面を増やす前にカタログへ部品を足します。
生成器のイメージ:コンパイラである
生成器は「AI が毎回画面を書く仕組み」ではありません。javac が .java から .class を出すように、画面 IR から Form / Controller / Thymeleaf を出します。同じ YAML を入れれば、同じファイルが出ます。ここが揺れると、テストの赤が「仕様のずれ」なのか「生成のゆらぎ」なのか切れません。
中を流れるものは単純です。
画面IR → Schema検証 → 依存解決
├ FormEmitter
├ ControllerEmitter
├ TemplateEmitter
├ MessageEmitter
└ TestEmitter
↓
生成ファイル
Emitter は互いにファイルを直接読まず、解決済みの依存だけを見ます。1画面分の対応は次のとおりです。
| 入力(IR) | 生成器がすること | 出力 |
|---|---|---|
| 項目名、widget、必須、桁 | Bean Validation 付き Form を書く | CustomerEditForm.java |
| URL、ボタン、次画面 | マッピングと PRG 遷移を書く | CustomerEditController.java |
| layoutId と項目一覧 | カタログ部品の th:replace を並べる |
templates/screens/customer-edit.html |
| メッセージキー | properties に落とす |
messages.properties の差分 |
| 代表データ | HTML を固定するテストを書く | 適合テスト |
ここで混同しやすいテンプレートが2種類あります。
生成時の型紙(codegen/templates/)
Handlebars や JavaPoet など、「Java / HTML を書き出す型紙」。アプリ実行には使いません。ここを直すと全画面に効きます。
実行時の部品(templates/_catalog/)
本物の Thymeleaf fragment。テキストボックス、検索フォーム、確認レイアウトなど。生成器は新しい HTML を発明せず、このカタログの部品 ID を並べるだけです。
生成される HTML のイメージは、完成した画面ではなく参照リストです。
<th:block th:replace="~{_catalog/layouts/form :: layout}">
<div th:replace="~{_catalog/widgets/text :: field(field='name')}"></div>
<div th:replace="~{_catalog/widgets/text :: field(field='email')}"></div>
</th:block>
見た目(余白、エラー表示、ラベル位置)はカタログ側。項目の増減は IR 側。配線のバグだけ生成器側、と切れます。
最初の実装は「YAML を読んで型紙を埋めるスクリプト + 数個の Emitter」で足ります。LLM を生成パスの中に入れない方がよいです。
AI が入るのは外側です。
- 設計書を IR に正規化する
- ゲート失敗が生成器起因なら、
codegen/templates/を直す - 未知の widget ならカタログに部品を足す(レビュー必須)
ここまでが「何を作るか」です。ここから「どう回すか」に入ります。回し方には、ループとグラフの2層があります。
ループとグラフは何が違うか
いま多くの現場がやっているのは、こういう往復です。
- 「この画面を作って」
- 結果を見る
- 「ここが違う、直して」
- また見る
これはすでにループです。ただし 回しているのは人 です。AI は1回ずつ動くだけです。疲れると「だいたい良さそう」で終わります。
ループエンジニアリング:1つの係の繰り返しを書く
ループエンジニアリングは、この往復そのものを仕組みにします。人は途中の指示をやめ、最初にルールを書きます。エアコンで言えば、寒くなるたびにリモコンを押すのではなく、目標温度と温度計を置いてサーモスタットに任せることです。
1周の形はいつも同じです。
ゴールを渡す → 作業する → ゲートで測る
↓不合格
直し方を決める → また作業する
↓合格、または上限
止まる
人が先に決めるのは3つだけです。
ゴール
何ができたら成功か。「いい感じの画面」はゴールになりません。「正しいパスワードでログインでき、誤りでは入れない」ならゴールです。
ゲート
誰が、何で合格と判定するか。いちばん弱いのは、作った AI に「できた?」と聞くことです。答案を自分で採点するのと同じで、だいたい満点になります。テストが赤なら不合格、のように、できるだけ機械が測ります。
停止
「うまくいくまで」は止まりません。合格、同じ失敗が2回、5周したら人へ、のように出口を先に書きます。止まらないループは、お金と時間だけが増えます。
「完成するまで自分で直して」と頼むだけでは、ループエンジニアリングではありません。測り方が自己申告のままだと、見た目だけ整えて止まります。設計しているのはサイクルであり、丸投げではありません。
作る役と採点役は分けます(いわゆる Maker / Checker)。測れないものは回せません。「もっと綺麗に」はゲートにならないので、そういう判断は最後に人が見ます。
グラフエンジニアリング:係を配置し、線でつなぐ
1画面を「テストが通るまで直す」だけなら、ループで足ります。戻り先がいつも「もう一度同じ作業」だからです。
この画面生成がループだけでは足りないのは、不合格のあとの直し先が1つではないからです。
- 仕様が悪いのか
- 生成の型紙が悪いのか
- カタログに部品が無いのか
- そもそも画面生成の仕事ではないのか
直し先が分かれる、スクリプトと AI と人が混在する、失敗時に戻る場所が複数ある。このとき書くのはループではなく、配線図 です。それがグラフエンジニアリングです。
ループを捨てる話ではありません。作業員に道具と手順書を渡すのがループ、作業員を配置して業務フローを引くのがグラフ、という関係です。Parse の「Excel を読み直す」や、型紙修正の「直してサンプル再生成」は、それぞれゴール・ゲート・停止を持った小さいループのままです。グラフは、そのループをどの順番で走らせるかを書く層です。
GraphRAG のように知識をグラフ DB に入れる話とは別です。こちらは 実行の順番と分岐 のグラフです。
グラフの部品は3つです。
Node(係)
仕事の単位。AI でも、mvn test でも、人の承認でもよい。1つの係に「作る・採点する・直す」を詰め込むと、またチャット往復に戻る。
Edge(線)
次に進む条件。「validate が成功なら Generate」「Critic が generator_defect なら型紙へ」のように、線に条件を書く。条件の無い図は、実行可能な設計ではない。
State(作業机)
全員が見る共有メモ。画面ID、今の係、失敗分類、書いてよいパス。会話の記憶に置かない。ループのときに「状態はファイルに出せ」と言ったものが、グラフでは State になる。
関心が一段ずつ外側に広がっただけ
乗り換えではなく、扱う範囲が広がった、と捉えると分かりやすいです。
| 層 | 何を良くするか | たとえ | この記事での例 |
|---|---|---|---|
| プロンプト | 1回の指示文 | 注文の言い方 | 「この Excel を IR にして」 |
| コンテキスト | 判断材料の揃え方 | 厨房に置く材料 | スキーマ、カタログ、失敗分類の文書 |
| ハーネス | 道具・権限・実行環境 | 厨房そのもの | 生成 CLI、テスト、書き込み禁止 |
| ループ | 1つの係の繰り返し | 1工程の品質検査 | 型紙を直してサンプル再生成する |
| グラフ | 複数の係の配置と戻り先 | 工場のライン図 | Parse → Critic → Patch の配線 |
いきなりグラフから始めなくてよいです。測れる1ループ(IR を1項目足すとテストが赤になり、再生成で緑に戻る)が先です。直し先が4つに分かれた時点で、グラフに昇格させます。
この画面生成の制御グラフ
幸福な一本道は短いです。ここまではほぼループと同じ一直線です。
Parse(設計書→IR)
→ Validate(スキーマ・カタログ・仕様の抜け)
→ Generate(型紙でファイルを出す)
→ Gates(コンパイル・項目適合・遷移・テスト)
→ Done
グラフになるのは、Gates が赤になったあとです。戻り先が1つではないからです。
Gates が赤
→ Critic(4値だけ返す。ファイルは触らない)
├ spec_defect → Patch IR → Validate からやり直し
├ generator_defect → Patch 生成器 → Generate からやり直し
├ catalog_gap → Human(部品追加) → 承認後、Validate へ
├ domain_out_of_scope → Human → このグラフは終了
└ 分類できない → Human → 止める
Critic の4値は、感想ではなく 次の線を選ぶスイッチ です。これがループとグラフの境目です。ループなら「不合格ならもう一周」で足ります。グラフでは「不合格なら、どの係へ戻るか」まで先に書きます。
意図して引かない線もあります。
Gates が赤 → 生成HTMLを直す → 再テスト
この線を引くと、Critic も上流パッチも迂回されます。グラフを書く意味は、許す経路を先に固定することでもあります。
エージェントは「画面を実装する」のではなく、この線の上を進みます。ゲートの中身は次のとおりです。
- 設計書パース → IR
- JSON Schema 検証
- カタログ参照(未知の
layoutId/widgetIdは生成せず落とす) - 仕様の抜け(孤立画面、行き止まり、未使用項目、権限欠落)
- 生成 + コンパイル
- 仕様適合(全項目が Form と HTML の両方にあるか)
- 遷移適合(全辺がマッピングとボタンを持つか)
- 実行時(MockMvc、HTML スナップショット、a11y)
会話の中に進行状況を置かない方がよいです。コンテキストが圧縮されると「生成物を触るな」が消え、エージェントは Thymeleaf を直接直して緑にします。進行は .loop/state.yaml のような State ファイルが正です。
停止もグラフの出口として先に固定します。全ゲート緑、同一失敗が2回、回数上限、catalog_gap、意味が取れない spec。止まらずに生成物を直すのは、存在しない線を走っている状態です。
並列してよいのは、カタログも生成器も触らない画面同士です。generator_defect と catalog_gap は共有部品の話なので、他画面の生成は止めます。型紙を直している最中に別画面を出すと、同じ欠陥が複製されます。これも「いつ並列で、いつ直列に戻すか」というグラフの線です。
Critic:直す人ではなく、次の線を選ぶ人
テストが落ちると、人でも AI でもやりたくなるのはこれです。
生成された HTML に足りない項目を1行足して、テストを緑にする
その画面は動きます。工場で不良品をヤスリで直して出荷しているのと同じです。型(生成の仕組み)は直っていないので、次の画面でも同じ不良が出ます。再生成すれば、さっき足した1行も消えます。
欲しいのは「この画面が動いた」ではなく、次からも同じ失敗をしないこと です。だから Critic(失敗を見る係)にはヤスリを持たせません。診断だけさせ、グラフの次の線を選ばせます。
ループの中で「直して再実行」まで同一エージェントにやらせると、いちばん楽な直し(生成 HTML への1行)を選びます。グラフでは、採点役と修理役を別ノードにし、採点役が返してよい答えを4つに縛ります。
spec_defect(仕様が悪い)
設計書や IR が足りない、矛盾している。画面イメージには電話番号があるのに項目定義に無い、次画面が書いていない、など。コードを足しても「正しさ」が無いので、またずれます。次の線は Patch IR、そのあと Validate からやり直しです。
generator_defect(生成の型紙が悪い)
仕様には書いてあるのに、生成器が落としている。任意項目を HTML に出さない、確認画面の「戻る」を付けない、など。1画面を直しても、同じ型紙を使う全画面が壊れたままです。次の線は Patch 生成器、そのあと Generate からやり直しです。型紙修正そのものは、小さいループ(直す → サンプル再生成 → 適合)で回せます。
catalog_gap(部品が無い)
和暦の日付のように、使いたい部品がカタログに無い。無いものをその画面だけ手書きすると、以降の生成が自由創作になります。次の線は Human です。見た目の正が変わるので、人が承認してから Validate へ戻します。
domain_out_of_scope(このグラフの仕事ではない)
割引計算や与信判定など、画面の話ではなく業務ルールの話。画面生成が抱え込むと、フロントの中にドメインが埋まります。次の線は Human で、このグラフは終了 します。別の担当・別のループへ渡します。ループの停止条件と違い、「成功せずに出口へ出る」辺です。
この4つは「誰が・何を直すか」が違います。混ぜると、線を引き間違えます。
顧客編集画面のテストが「email が HTML に無い」で落ちたときの切り分けは、次のように機械的です。
| 調べて分かること | 分類 | 次の線 |
|---|---|---|
| IR にも設計書にも email が無い | spec_defect | Patch IR → Validate |
| IR には email がある。生成 HTML にだけ無い。別画面も同じ | generator_defect | Patch 生成器 → Generate |
| email ではなく、和暦ウィジェットが必要でカタログに無い | catalog_gap | Human → 承認後 Validate |
| 本当の失敗は「メアド重複時の業務エラー」で、画面項目の話ではない | domain_out_of_scope | Human → このグラフ終了 |
Critic はファイルを直さず、ラベルと根拠(どのログの何行か、IR のどのキーか)だけ返します。出力は文章ではなく JSON にします。「たぶん生成器のせいです」では次のノードを選べないからです。
{
"failureClass": "generator_defect",
"gate": "fit",
"evidence": [
"logs/fit.log:42",
"specs/screens/customer-edit.yaml#fields.email"
],
"nextNode": "patch-generator",
"doNotTouch": ["templates/screens/customer-edit.html"]
}
パースできない応答は、分類失敗として人へ上げます。線を推測で引かない、がグラフ側の約束です。
曖昧なら、無理に4つのどれかに入れない
分かる例: IR に email がある。HTML に無い。型紙が optional を飛ばしている。→ generator_defect
分からない例: 電話番号が HTML に無い。Excel が2枚あり、片方にしか項目が無い。どちらの設計書が正かは人しか決められない。
ここで AI が「たぶん spec_defect」と決めて仕様を書き換えると、本当は型紙のバグなのに設計書が増えていきます。分からないときは止めて、人に「分かっていること / 分かっていないこと / 決めてほしい選択肢」を渡します。
正しい緑は、仕様か型紙かカタログを直したあとに もう一度生成して、何も手を入れていない生成物でテストが通ることです。生成された Java や Thymeleaf をその場で直して緑にするのは、答案の誤字だけ直して「満点だった」と記録するようなものです。ループとしては「通った」、グラフとしては存在しない線を走った、ことになります。
制御グラフが読む、もう3つのグラフ
配線(誰が動くか)とは別に、成果物そのものもグラフとして持ちます。Validate と Generate がこれを読みます。混同しやすいので、名前を分けます。
| 種類 | 何のグラフか | この記事での中身 |
|---|---|---|
| 制御グラフ | グラフエンジニアリングの本丸。誰が動き、どこへ戻るか | Parse → Critic → Patch の配線 |
| 仕様グラフ | 画面・項目・遷移のデータ | 孤立画面、行き止まり、未使用項目の検査 |
| 部品グラフ | layout → fragment → widget | 未知部品、循環 include の検査 |
| 成果物グラフ | どの IR がどのファイルを生むか | 再生成とレビューの範囲 |
前半で「依存グラフを持つ」と言っていたのは、主に下の3つ(データのグラフ)です。グラフエンジニアリングの本丸は、その上に乗っている制御グラフです。両方必要で、役割が違います。
生成AIにこれをさせる仕組み:Document / Skill / Agent
「AIにループして」と頼むだけでは回りません。正しさの定義、ノードの手順、実行者、逸脱の防止を分けます。ループは各ノードの中、グラフはノード間の線、です。
| 層 | 置く場所の例 | 仕事 |
|---|---|---|
| Document |
specs/ docs/ ui-catalog/
|
正しさの定義(契約) |
| Skill | .cursor/skills/*/SKILL.md |
ノードの手順。何を実行し、何を書き、いつ止まるか |
| Agent | 親 + サブエージェント | 判断。書き込み範囲が違う仕事は分ける |
| Rule | .cursor/rules/*.mdc |
常時の言葉。「生成ディレクトリを編集しない」 |
| Hook / Script | hooks、codegen の CLI |
常時の機械。生成物編集の拒否、mvn、スキーマ検証 |
Document に手順を長々と書かない。Skill にスキーマをコピーしない。契約は文書、手順は Skill、禁止は Hook、です。Skill は無視されうるので、生成ディレクトリへの書き込みは Hook で deny します。存在しない線(生成 HTML への直接編集)は、言葉だけでなく機械で止めます。
Agent は1体にしない
1体に「この画面を完成させて」と頼むと、生成 HTML を直してループが壊れます。グラフのノードは、書き込んでよいパスで分けます。
- orchestrator … State を読み、線を表引きする。ソースは触らない。ここがグラフの実行係
-
parser …
specs/だけ書く。不明はunknown。推測で埋めない。中は小さいループでもよい - critic … 実装を触らず、分類 JSON だけ出す。線を選ぶ係であり、修理係ではない
-
patch-generator …
codegen/だけ直す。中は「直す→サンプル再生成→適合」のループ - patch-catalog … カタログ追加。必ず人の承認で止める
遷移はエージェントに「次は?」と聞きません。validate が 0 なら generate、非0 なら critic。critic が generator_defect なら patch-generator、その後 generate に戻る、と エッジとして Skill に書きます。これがループ用の「もう一周」との違いです。
Cursor を使うなら、親チャットを orchestrator、ノードをサブエージェントにする、が現実的です。定期起こし用の機能は、この状態機械の代わりにはしません。
Skill はノード1つにつき1つ
SKILL.md には「トリガー・読む文書・実行コマンド・書いてよいパス・出力形式・次ノード」だけを書きます。長い解説は文書へリンクします。オーケストレータ用 Skill だけがグラフ全体を知っていればよく、各ノードは自分の入口と出口だけを知ります。
| Skill | 層 | すること |
|---|---|---|
orchestrate-graph |
グラフ | State を読み、線を引き、停止判定 |
parse-design-doc |
ループ可 | 設計書 → YAML。スキーマ外を作らない |
run-gates |
スクリプト | validate / generate / compile / fit / test を叩き、終了コードを返す |
critic-classify |
ノード(単発) | ログと分類文書から JSON を出す |
patch-generator |
ループ | 型紙を直し、生成画面は触らない |
patch-ir |
ノード | スキーマ上明らかな欠落だけ直す |
escalate-human |
グラフの出口 | 人が決める選択肢を1枚にまとめて止まる |
run-gates は AI に「テストを直して」と頼みません。script を実行して、赤なら critic に渡すだけです。ゲートはループの測り方であり、同時にグラフの条件エッジの入力です。
Document の最小セット
常時読む憲法と、必要なときだけ開く契約に分けます。
常時(AGENTS.md など)
- 最初に読むファイルの順
- 生成物は編集禁止(グラフに無い線)
- State ファイルが進行の正
- ノード一覧と、Critic の4値から引く次ノード
- 停止条件(グラフの出口)
オンデマンド
-
specs/schema/*.json… IR の形。parser と validate の正本 -
docs/failure-taxonomy.md… 4分類の定義と典型ログ。critic 専用。エッジの仕様書 -
ui-catalog/catalog.yaml… 存在してよい layoutId / widgetId -
codegen/README.md… 生成コマンドと、直してよい型紙 -
docs/definition-of-done.md… どのゲートが緑なら画面完了か
設計書そのもの(Excel など)は Document の入力であって、生成器の入力ではありません。
1本の実行の流れ
- parser が Excel から IR を書く
- validate が未知の
widgetId: date-jpで落ちる - critic が
catalog_gap→ Human へ。ここで生成しない(グラフに Generate への線が無い) - 人がカタログに date を足す
- Validate から再開。Generate は成功、compile は通るが、fit が「email が HTML に無い」
- critic が
generator_defect(optional 項目を Emitter が飛ばしていた) - patch-generator が型紙を直す(ここは小さいループ)
- Generate からやり直し、fit と snapshot が緑。Done
このあいだ、生成 HTML は人も AI も開いていません。直ったのはカタログと生成器です。次の画面からは同じ欠陥は出ません。
ループだけ見ると「3周して緑になった」です。グラフを見ると「Human に1回出たあと、生成器ノードに1回戻り、Generate から再入場した」です。後者の方が、次に同じ失敗が起きたとき再現できます。
導入順
パーサを先に大きくすると、ゲートが無い状態で IR だけが増えます。グラフを先に大きくすると、測れない線が増えます。先に「測れる1ループ」を置き、直し先が分かれたらグラフにします。
- カタログを 4 レイアウト(list / form / confirm / complete)と基本ウィジェットで凍結する。画面ゼロでも fragment の見た目テストが通る
- 画面 IR の JSON Schema と、1画面分のサンプル YAML を置く
- Form / Controller / テンプレートを出す生成器を通し、サンプル画面が手動実装なしで起動する
- IR を1項目足すとテストが赤になり、再生成で緑に戻ることを確認する(ここまでがループ)
-
AGENTS.mdと生成物禁止の Hook(存在しない線を機械で止める) - 失敗分類文書と critic(4値がエッジになる)
- orchestrator と State ファイル(ここからグラフ)
- その後で設計書パーサ
やってはいけないこと
| やりがち | 何が壊れるか | 壊れる層 |
|---|---|---|
| 設計書から HTML を直接生成する | レイアウト揺れが画面ごとに固定され、カタログも適合検査も効かない | 生成器 |
| 生成後に人がテンプレートを整える | 次の再生成で消え、差分が手癖になる | ループのゲート |
| 画面ごとに JS と CSS を足す | 制御にもデータにも載らない挙動が増え、目視 QA に戻る | 両方 |
| Thymeleaf を残しつつ JSON API とフロント枠を並行する | 遷移の正が二重になる | 仕様 |
| 失敗時に生成コードへ AI がホットフィックスする | 生成器のバグが隠蔽され、全画面へ同じ欠陥が残る | グラフに無い線 |
4値のどれか分からないのに、いちばん楽な spec_defect にする |
仕様が汚染され、型紙のバグなのに設計書が増える | エッジの誤配線 |
| 「完成するまで自分で直して」と1体に任せる | 採点と修理が同一人物になり、HTML を直して緑になる | ループとグラフの混線 |
まとめ
設計書駆動で Thymeleaf の MPA を生成するとき、フロントのベストプラクティスは「画面を上手く書くこと」ではありません。
- 設計書は IR に正規化してから生成する
- 生成器は決定的なコンパイラにし、LLM は外側に置く
- 使える見た目はカタログに閉じ、生成 HTML は部品への参照リストにする
- 人は仕様・カタログ・生成器だけを触る
- ループは、1つの係についてゴール・ゲート・停止を先に書く。作る役と採点役を分ける
- グラフはループを捨てない。係を配置し、不合格のときの戻り先を線にする。直し先が4つに分かれた時点で必要になる
- Critic はパッチを書かず、
spec_defect/generator_defect/catalog_gap/domain_out_of_scopeだけを返す。それが次の線である - 曖昧なら人へ上げる。生成物を直して緑にする線は、グラフに引かない
- 制御グラフ(誰が動くか)と、仕様・部品・成果物のグラフ(何を検査し、何を再生成するか)は別物である
画面の正しさを「仕様との一致」に還元できたとき、ループは測れ、グラフは配線できます。ドメインサービスは画面 IR に埋め込まず、別仕様・別グラフにします。
最初の一歩はグラフでなくてよいです。閉じたカタログ、1画面分の IR、それを食う生成器、項目を1つ足すと赤になるテスト。その1ループが回れば、Critic の4値をエッジにしてグラフに昇格できます。