AI に書かせる
AI はブラウザを開かない。だからこのページは「AI が読むページ」ではなく、あなたの AI を hatake 対応にするための手順書である。読者は人間。
繋いだあと「どう頼むか」は AI への頼み方(埋めるだけのテンプレとケース別の型)。
hatake 側の仕組みは3つの入口しか持たない。使っているクライアントで選ぶ。
| 使っているもの | 選ぶ経路 |
|---|---|
| Claude Code / Claude Desktop など MCP 対応クライアント | 経路1: MCP — 一番速い |
| ChatGPT・ブラウザの AI など、MCP が使えないもの | 経路2: URL を渡す |
| 自分で書いた定義を機械的に確かめたい | 経路3: CLI |
経路1: MCP
繋ぐと、このサイトも仕様書も読ませずに、必要なときだけ仕様を引きに来る。渡すコンテキストが小さくて済むのでこれが一番速い。
claude mcp add hatake -- node <repo>/typescript/dist/mcp.jsリポジトリを clone してある場合は、同梱の .mcp.json がそのまま使える(事前に cd typescript && npm install && npm run build)。
{
"mcpServers": {
"hatake": {
"command": "node",
"args": ["typescript/dist/mcp.js"]
}
}
}エージェントに渡る道具は23個。どれにもそのまま渡せる引数の例が1組付いていて(説明の最後に出る)、その例は CI が実際に呼んで通ることを見ている=古い例が残らない(例を写して最初の1回で転ぶ、が起きない)。
知らない引数は断る(受け取る名前と例を返す。黙って捨てると、渡したつもりの条件が効かないまま答えが返る)。定義を受け取る道具は、中身(source)の代わりにファイルの道(file_path: definitions/app.yaml)でも受け取る=ファイルに書いた定義を貼り直さなくてよい。
| 道具 | 何をするか |
|---|---|
hatake_doctor | 既存の案件を触り始める最初の1回。固定した版と入っている版のずれ・定義が読めるか・MCP の設定を見る |
hatake_where | 定義で書けないことを頼まれたとき。どこの担当かを引く(定義 / 登録 / サーバ / 枠組みの外)。外と出たものはコードを書き始めず、そう言う → 先に決めること |
hatake_ask | 定義を書けたあと。定義に書けないのに決まっていないこと(排他・採番・論理削除・端数・サーバ側の検証・止めるのは誰か)を問いにして返す。返った問いは人に投げる=当てて書くと嘘の設計になる → AI への頼み方 |
hatake_project | いちばん最初。案件の前書き(何のシステムか・何ができないか・業務の言葉と項目名の対応・名前の決めごと)を読む。書いてある言葉と名前で定義を書く → 先に決めること |
hatake_intent | 人からの指示文を1枚にする(言われたまま・1行1件・全部 source: ai-draft)。定義も渡すと covers が当たり、言われていないのに在るものが出る。定義を書く前と書いたあとに呼ぶ → 言ったことを残す |
hatake_examples | やりたいことに近い例を取る(まずこれ。写して直すのが最速) |
hatake_new_page | 新規なら雛形を出す |
hatake_reference | キーの型・既定値・書ける場所を引く(仕様書を読まなくていい)。名前を省くと目次(全体は all: true)。maxLength のような値でも引ける。無い名前なら意味の近いキーを書き方つきで添える(immutable → readOnlyWhen: { mode: edit }) |
hatake_check | 書けたらまずこれ。検証・読み返し・助言・人が決めることを1回で、欄を分けたまま返す |
hatake_validate | 書いた定義を検証する。必ず通す |
hatake_run | 定義を動かして答えを見る(検証エラー・計算した値・隠れている項目・いま必須の項目・押せるボタン)。draft: true で下書きのシナリオを作り、cover: true で「まだ試していない分岐」も出る。→ 試す |
hatake_advise | 検証を通したあと、書いていないから不便かもしれない所を挙げる(並べ替えできない一覧・誰でも消せる画面・確認の無い一括)。好みなので直すかは業務の判断。書く値の下書きも付く |
hatake_apply_advice | 当てると決めた助言を、その場に書き込む(書く値は渡す側が決め、書く場所は機械が決める) |
hatake_pitfalls | よくある間違いと直し方 |
hatake_rules | 言われた規則(警告・助言)が何か、どう直すかを引く(規則名を省くと目次) |
hatake_api_shape | バックエンドが返すべき JSON の形(format: fixtures でサーバ側の試験データも。通る形と弾く形を定義の制約から作る → 試す) |
hatake_print_preview | 帳票を刷ったらどう見えるか(列の並び・小計の位置・切れた文字)を文字で返す |
hatake_diff | 既存の定義を直したとき、契約を壊していないか・確かめてほしい変化(消えた列・ボタン・選択肢、権限の変化)はないか |
hatake_explain | 書いた定義が何をする画面か、日本語で読み返す(意図どおりかは警告では分からない)。before を渡せば変更の言い直し、brief で1行 |
hatake_fix | 指摘のうち直し方が一意なものを直す(綴り違い・入れる値が決まっている指定)。自分で書き直すと別の場所を壊しがち |
hatake_minimize | 冗長になった定義を短くする(既定値と同じ指定を落とす。意味は変えない) |
hatake_refs | その定義をアプリに組み込むのに、何を登録すればいいか |
hatake_wire | そのまま繋ぐコードの下書き(Flutter の HatakeScope) |
Docker で動かす手順は MCP ガイド にある。
経路2: URL を渡す
MCP が使えないクライアントには、/llms.txt を読ませる。ここから先の素材へは llms.txt がリンクしているので、渡すのは1本でいい。
前置きとして、これをコピーして貼る。
hatake という宣言型フレームワークで業務画面を作る。
仕様は https://asil-e-hatake.github.io/hatake/llms.txt にある。まずこれを読むこと。
守ること:
- Flutter / Dart のコードは書かない。書くのは定義(YAML)だけ。
- 定義に無い機能は、実装で回避せずプラグインでの拡張を提案する。
- 近い例があれば例を写して直す。ゼロから書かない。
- 書いたら npx -p @hatake-fw/api hatake validate にかけ、警告が出たら直す(警告は「落ちないが意図どおり動かない」書き方)。
- 知らないキーを想像で書かない。迷ったら reference.json を引く。「Flutter を書かない」と明示するのが効く。指示が無いと、AI は慣れている Widget コードを書きに行く。
素材の一覧
| 素材 | 用途 | 大きさ |
|---|---|---|
/llms.txt | AI 向けの入口。まずこれ1枚 | 29 KB |
/llms-en.txt | 同じものの英語版 | 23 KB |
/api-cheatsheet.ja.md | 書ける名前の一覧+最小例。実装を読まずに書ける | 50 KB |
/api-cheatsheet.md | チートシートの英語版 | 37 KB |
/reference.json | 全キーの索引(型・既定値・書ける場所)。機械可読 | 102 KB |
/pitfalls.json | よくある間違い→正しい書き方。機械可読 | 39 KB |
/examples-index.json | 「やりたいこと→例」の索引。機械可読 | 8 KB |
/hatake-page.schema.json | JSON Schema。エディタ補完と機械検証用 | 66 KB |
/dsl-spec.ja.md | 全仕様(長い。普段は引かなくてよい) | 161 KB |
/dsl-spec.md | 仕様の英語版 | 129 KB |
大きさを載せているのは、コンテキストに何をどれだけ積むかを選べるようにするため。全部渡す必要はない。普通は llms.txt か api-cheatsheet.ja.md の1枚で足りる。
このサイトのページは .md でも取れる
どのページも、同じ URL に .md を付けると Markdown の素で返る。HTML を取ってタグを剥がす必要はない。
https://asil-e-hatake.github.io/hatake/dsl/table-columns ← 人間向け
https://asil-e-hatake.github.io/hatake/dsl/table-columns.md ← AI 向け(中身は同じ)AI が書いた定義を人が見るときは、VS Code 拡張が使える(左のツリーに定義を業務の言葉で並べ、選ぶと画面・項目・操作・権限・確認のタブで開く。YAML を読まなくてよい)。中身は MCP と同じ道具の束なので、人と AI が同じ答えを見る → VS Code 拡張の手引き。
人間向けと AI 向けで文書を書き分けてはいない。同じ1つの中身を、2つの形で出しているだけである。書き分けると必ず片方が古くなるので、そうしない。
経路3: CLI
AI に書かせるにしても、最後は機械で確かめる。エージェントが validate を通す前提で作られているので、CI でも同じものを使う。
npx -p @hatake-fw/api hatake check page.yaml # 1往復で1本(事実・読み返し・好み・人が決めること)
npx -p @hatake-fw/api hatake validate page.yaml # 検証。--json で機械可読、--warn-as-error で CI を落とせる
npx -p @hatake-fw/api hatake new crud --id customer --title 顧客マスタ
npx -p @hatake-fw/api hatake reference rowsPerPage # そのキーの型・既定値・書ける場所
npx -p @hatake-fw/api hatake reference --placeholders # 文言に書ける差し込みと、いつ埋まるか
npx -p @hatake-fw/api hatake examples 帳票 # 近い例
npx -p @hatake-fw/api hatake pitfalls groupBy # 間違い → 正しい書き方
npx -p @hatake-fw/api hatake failures unknown-repository # 実際に転んだ実例(なぜそう書くか付き)
npx -p @hatake-fw/api hatake explain page.yaml # この定義、結局どういう画面?
npx -p @hatake-fw/api hatake explain page.yaml --brief # 1行の要約(app なら画面一覧の表)
npx -p @hatake-fw/api hatake explain --diff before.yaml page.yaml # 何を変えたのか、画面の言葉で
npx -p @hatake-fw/api hatake harvest definitions/ # 繰り返し転んでいる所を実例カタログの候補に
npx -p @hatake-fw/api hatake index definitions/ --find "顧客 検索" # どこに何の画面があるか
npx -p @hatake-fw/api hatake diagram app.yaml --out app.svg # 画面とメニューと遷移の図
npx -p @hatake-fw/api hatake diagram app.yaml --format mermaid # PR に貼れる形(--format dot も)
npx -p @hatake-fw/api hatake diagram page.yaml --computed # 計算の依存(箱の中に計算の中身・順番が逆の線は赤)
npx -p @hatake-fw/api hatake diagram app.yaml --computed --all # 画面ぜんぶを1枚に(画面ごとに囲む)
npx -p @hatake-fw/api hatake minimize page.yaml # 既定値と同じ指定を落として短く(意味は変えない)
npx -p @hatake-fw/api hatake diff before.yaml after.yaml # 変更の影響(契約・画面・権限・アプリ構成)
npx -p @hatake-fw/api hatake refs page.yaml --needs-registration # アプリ側に何を登録すればいいか
npx hatake registry lib/main.dart # アプリが登録しているものを実装から読む
npx -p @hatake-fw/api hatake refs page.yaml --filled --source lib/ # その登録が本当に埋まったか(TODO のまま・中身が無いを数える)
npx -p @hatake-fw/api hatake gaps app.yaml # 繋がっていない所を1枚で(押す所ごとに1行+画面ごとの残り)
npx -p @hatake-fw/api hatake advise --effect before.yaml after.yaml # 助言の効き目(どの規則が実際に直されたか)
npx -p @hatake-fw/api hatake attack app.yaml --all-roles --accounts accounts.json --base … # 役割ぜんぶ+誰でもない人で権限の穴を突く
npx -p @hatake-fw/api hatake attack app.yaml --all-roles --login login.json --since last.json --fail-on new
# 毎晩回す形(資格は毎回取る・変わった所だけ出す)validate は構文エラーだけでなく、解析は通るのに意図どおり動かない書き方も警告する(宣言していない行アクション、存在しないページへの遷移、sort の無い groupBy など)。画面を見ても気づけない類なので、警告が出たら直す。
4本を1往復で
定義を書いたあとにやることは4つあって、それぞれ相手が違う ── 事実(書いたのに効かない)、読み返し(そう書いてある)、好み(書いていないと不便かも)、人が決めること(定義に書けない)。AI にやらせると、この4つを別々に呼ぶので往復も文脈も4回ぶん食う。しかも順番は説明が渡しているだけなので、validate だけかけて「通りました」と言うエージェントが実際に居る。
npx -p @hatake-fw/api hatake check page.yamlpage.yaml(crud)を1回で見ました。
## 事実(書いたのに効かない)
登録済みの一覧を見ました(渡された一覧)。繋がっていない所を1枚で見るなら hatake gaps。
警告はありません。
## 読み返し(そう書いてある)
顧客マスタ(customer_master)— 検索して一覧に出し、その場で登録・修正・削除までできる画面
…
## 好み(書いていないと不便かも)
書き足すと良さそうな所が 2 件:
page.table.columns [key-not-in-table]
…
## 人が決めること(定義に書けない)
決めてください(5件)。
…
## 次の1手
・好みが 2 件(書いていないと不便かも)。当てるなら hatake advise --apply、…
・人に聞くことが 5 件。**AI が決めて書いてはいけません**(…)肝は欄を混ぜていないこと。混ぜた瞬間に「助言を直さないと CI が赤い」になり、そうなると事実の側まで読まれなくなる。だから終了コードを動かすのは事実の欄だけ(--warn-as-error のとき)で、好みと問いは何件あっても 0 で終わる。
--no-explain / --no-advise / --no-ask で欄は落とせるが、事実の欄は落とせない。旗で消せると「警告ゼロ」と「見ていない」が同じ顔になる。落とした欄は消えずに「入れなかった欄」に理由つきで残る(節ごと消すと、読んだ人は「無い」と読む)。
新しいことは何も言わない ── 4本を別々に呼んだのと同じ結果にするのがこの紙の値打ちで、別の数え方をした瞬間に「道具ごとに違うことを言う紙」になる。CI がそこを見ている(check の中身と、4本を別々に呼んだ結果を突き合わせて、違ったら落ちる)。
人に渡す1枚が要るなら design のほう。あちらは意図の1枚と突き合わせる紙で、合否は言わない。
書けたものを、日本語で読み返す
validate が見るのは綴りと構造だけ。条件の向きを間違えた・意図と違う項目を必須にしたは全部通る。AI に書かせるなら、最後に人の言葉で読み返すのが要る。
npx -p @hatake-fw/api hatake explain page.yaml顧客入力(customer_form)— 1件を入力する画面(新規と編集の両方)
## 基本情報
・コード … 必須、新規のときだけ触れる、20 文字以内
・登録番号 … 区分 が 法人 のときだけ必須
## 請求先(区分 が 法人 のときだけ出る枠)
・請求先コード … 必須キーの名前は出さないので、DSL を知らない人がレビューできる。条件は項目と選択肢のラベルで言う({ field: kind, value: corp } ではなく「区分 が 法人 のとき」)。AI 自身に読み返させてもよく、MCP なら hatake_explain。
1行だけ欲しいときは --brief。画面一覧や PR 本文に貼る形で、app を渡すと表になる。
顧客入力(customer_form)… 1件の入力。4 枠に項目 11(必須 5)、条件で出し分け 4 項目、customerRepository からAI に直させたとき、何が変わったのかを読む
diff は機械の言葉で言う(ui / column-format-changed / …columns.amount.format)。壊れるかを CI で見るにはそれが正しいが、人がレビューするときに読みたいものではない。
npx -p @hatake-fw/api hatake explain --diff before.yaml after.yaml顧客入力(customer_form)— 変わったところ
## 基本情報
・「コード」が変わりました
前: コード … 必須、20 文字以内
後: コード … 必須、30 文字以内
## 請求先
・枠「請求先」は、区分 が 法人 のときだけ出るようになりました
※ ここは見え方の話です。呼び出し側が壊れるか(後方互換)は hatake diff で見てください。やっているのは「説明どうしを比べる」こと。差分の規則から文を組み立てているのではないので、既定値の変化や「できないこと」の増減のような、規則を書いていない変化も自動で入ってくる。app ならメニューの移動(開く先が同じなら「消えて増えた」ではなく「移った」)と、両方にあるページを1枚ずつ。
判定(壊すか)は diff、言い直し(何が変わったか)は explain --diff。後者は終了コードを変えない(読むための道具なので)。
AI が実際に転んだ実例
対照表(pitfalls)は人が考えた間違いの集合で、AI が転ぶ所とはズレる。実例は別に溜めてある。
npx -p @hatake-fw/api hatake failures # 全件(こう書いた → こう言われた → こう直した)各件は本当に道具にかけ直して、記録した診断と一致することを CI で確認している。機械では拾えない件も載っている(載せないと「道具が万全」という嘘になる)ので、そこには「レビューでどこを見るか」が書いてある。
実例は手で書くと増えない。増えないカタログは、道具が良くなったのか拾っていないだけなのか見分けが付かないので、定義の山から候補を拾う。
npx -p @hatake-fw/api hatake harvest definitions/ # 繰り返し出ている診断を候補として出す(既定は2回以上)候補は人が書く欄を空のまま出る。「なぜそう書いてしまうか」は機械には書けないし、そこがこのカタログの価値なので、自動で追加はしない。定義そのものも持ち出さない(ラベルや列名に客先の語彙が入るので、出すのはファイル名・場所・回数だけ)。
--repro を付けると、最小の再現(その診断が出続ける形まで削った下書き)も作る。守るのは意味ではなく診断で、削り終わってからラベルを記号に置き換える。手で作るといちばん手間な所なので、下書きまでは機械がやる。
一意な直しは機械にやらせる
AI は指摘されると別の場所を直して壊すことがある。「witdh は知らないキーです」と言われて列ごと書き換える、というような直し方をする。綴り違いのような一意な直しは、機械のほうが速くて安全。
npx -p @hatake-fw/api hatake fix page.yaml # 既定は出すだけ。--write で上書き2 件を直しました:
page.table.columns[0].witdh のキー名を width に直しました
page.table.rowActions[1] を "aprove" から "approve" に直しました
直さなかったもの(意図が要るので人の仕事です):
page.repository [unknown-repositories] 登録済みの名前に近いものがありません("zzz")。名前を決めるのは人の仕事です。直すのは綴り違い(キー名・Repository / プラグイン / 型 / ページ id / アクション id)と、入れる値が決まっている指定(小計のある帳票に report.sort を足す)だけ。近い名前が2つあれば直さない(人が選ぶ)。1件ずつ当てて「問題が減る・新しい問題が出ない」ことを確かめ、崩れたら何もしない。
同じ項目の重複・field の無い集計・条件で使えない演算子は意図が要るので触らず、理由つきで「直さなかった」と言う。
書き足したほうがいい所
短くする(minimize)の逆。業務システムで多いのは書きすぎより書き足りない(並べ替えできない一覧・絞り込みの無い一覧・誰でも消せる画面)。書いていないものは警告にもスキーマにも出ないので、別の物差しで見る。
npx -p @hatake-fw/api hatake advise page.yaml# page.actions[0].roles [open-dangerous-action]
こうなる: 「削除」は誰でも押せます(消したものは戻りません)。
書き足す: `roles` で見える人を決める(権限はアプリ側の判定と合わせて二重にかける)。これは助言で、警告ではない。 警告は「書いたのに効かない」=事実なので CI で落としてよいが、助言は「書いていないから不便かもしれない」=好みなので終了コードを変えない。混ぜると警告の信頼が落ちる。画面の種別も見る(照会に「必須が無い」とは言わない)。
助言は好みなので、物差しは案件ごとに差し替えられる。
npx -p @hatake-fw/api hatake advise page.yaml --rules team.jsonteam.json に書けるのは3つだけ。合わない規則を止める(off)、組み込みの規則の目盛りを変える(options)、案件の決めごとを「この場所には必ずこのキーを書く」の形で足す(require)。条件式は書けない — 書けるようにすると設定ファイルが小さなプログラムになるので。知らないキーや知らない規則名はエラーにする(設定が黙って効かないのが一番まずい)。
1画面だけ黙らせる
物差しは案件ぜんたいの決めごとなので、1枚のための例外が書けない。1画面のために規則を切ると他の画面まで緩むので、例外はその画面の中に書く。
page:
type: search
key: id
# advise-off: key-not-in-table # 現場は受注番号で話すので id は出さない画面の外(app: の直下など)に書けば定義ぜんたいに効く。# の後ろが理由で、出力に一緒に出る。
黙らせた件数は必ず出る。
※ 定義の中の印(advise-off)で 1 件を黙らせました(印 1 件: key-not-in-table/order_list)。消えた助言が見えないと、助言ゼロが「きれいな定義」に見えてしまう。1件も黙らせていない印は「何も黙らせていません」と言う(消し忘れか、書く場所が違う)。知らない規則名はエラーにする — 止めたつもりで止まっていないのが一番困るので。止められるのは助言だけで、警告(書いたのに効かない=事実)は止められない。
物差しは、もう定義に書いてある
案件の決めごとは、たいていもう定義に書いてある(誰も明文化していないだけ)。手で物差しを書くのは最初の一歩が重いので、既にある定義から起こす。
npx -p @hatake-fw/api hatake harvest definitions/ --rules起こせた決めごと 1 件:
・画面は**全部**(22 箇所 / 12 本)に repository を書いています [page-needs-repository]起こすのは全部に書いてあるものだけ。8割の所は「決めごとではなく揺れ」として別に並べる(規則にすると、2割を直す宿題が黙って生まれるので)。スキーマで必須のキーは起こさない(書いてあって当たり前のものを決めごとにすると、物差しが当たり前で埋まる)。
出したものはそのまま --rules に渡せる形で、しかも起こした物差しを同じ定義の山に当てて確かめてある — 1件でも鳴ったら候補から落とす。「起こした規則が、当てたら鳴る」は道具として一番みっともない形なので、そこは機械で潰す。
書き込みはしない(貼るのは人)。says(何が不便か)と絞り(pages / when)は数からは出ないので、人が足す欄として空けてある。
その助言、直されているのか
助言は 22 個まで増えた。増やす根拠は毎回あったが、減らす根拠はどこにも無い。鳴るだけで誰も直さない規則が混ざっていると、読む側は全部を流し読みするようになり、AI は「全部直す」をやって余計な差分を作る。騒音は規則の数ではなく直されない規則の数で決まるので、そこを数える。
npx -p @hatake-fw/api hatake advise --effect --git main...HEAD definitions/*.yaml助言の効き目(12 組を比べました。見立ては 3 回鳴ってから):
money-without-format … 切る候補
鳴った 7 / 直った 0 / 残った 4 / 黙らせた 3
7 回鳴って**1回も直されていません**(うち 3 回は黙らされました)。物差しで切る候補です。
bulk-without-confirm … 効いている
鳴った 4 / 直った 3 / 残った 1
切るならこう書きます(助言の物差し):
{ "off": ["money-without-format"] }数える単位は画面×規則。1件ごとにしないのは、場所の道(…columns[2])が列を1本足すと動くので、同じ1件かどうかが言えないから。言えないものを数えると、直していないのに「直った」が並ぶ。
「直った」は「後の版で鳴らなくなった」という意味しかない ── 直したのか、書き方を変えたら鳴らなくなったのかは見ていない。区別できるものだけは分けてある: 印(# advise-off:)で消したものは「黙らせた」、その画面ごと消えたものは「画面ごと消えた」。黙らせたのを「直った」に入れないのが一番大事な所で、黙らせた回数は「その規則は騒音だ」という強い証拠なので、切る候補の根拠として残す。
--min(既定 3)に届かない規則は「まだ言えない」に置く。1組の PR で規則の値打ちは決まらない。
「じゃあ何を書くのか」も下書きする
助言は「何を足すか」までは言うが、値(絞り込みに何を出すか・確認の文・1回に何件まで)は業務の決めごとなので言わない。そこが最後の往復だったので、定義から作れる値は下書きする。
# page.actions[0].confirm [bulk-without-confirm]
こうなる: 「一括承認」は選んだ行にまとめて実行しますが、確認を出しません。
書き足す: `confirm: { message: … }`(何件に何をするのかを書く)。
値の下書き: { message: '{count} 件を「一括承認」します。よろしいですか?' }
(ボタンの名前から(文は業務の言葉に直してください)。**下書きです**=決めるのは業務の側)何から作ったかを必ず書く。 根拠の無い下書きは、読む側が正解として読んでしまう。列の名前から・ボタンの名前から・1ページの件数から・定義に出てくる役割から、のどれかで、外れることがある前提のもの。文(確認・失敗の言い方)は業務の言葉に直す。
下書きはそのまま当てられる形であることを試験で縛っている(当てられない下書きは出さない)。なので読んで納得したら、次の1往復で終わる。
当てると決めたものは、そのまま書き込める
助言を読んで YAML を手で書き足すのは、AI がいちばん転ぶ所である(字下げを間違える・隣のキーを巻き込む・配列の何番目かを取り違える)。場所は機械のほうが正確なので、そこだけ機械に渡す。
npx -p @hatake-fw/api hatake advise page.yaml --apply picks.json # 既定は出すだけ。--write で上書き[
{ "rule": "bulk-without-confirm",
"value": { "message": "{count} 件を承認します。よろしいですか?" } },
{ "rule": "money-without-format" }
]分担がはっきり分かれている。当てるかどうかと書く値は渡す側(確認の文・1回に何件まで・誰に見せるかは業務の決めごと)。どこに書くかと書けたかは機械。
value を省けるのは定義から決まるものだけ(金額の見せ方 format: currency、確認の OK を赤くする danger: true、1件を指すキーの列=業務名が定義の中にあるとき)。それ以外は当てずに「何を渡せばいいか」を返すので、次の1往復で終わる。
当てなかったもの:
page.actions[0].onError [bulk-without-error-message] 失敗したときの言い方は業務の決めごとなので、
value に渡してください(例 value: { message: '{count} 件のうち {failed} 件が承認できませんでした' })。当てたあとに1件ずつ読み直して、読める・別の問題が出ない・その助言が消えるの3つを確かめている。3つ目が肝で、書いた場所が違えば助言は消えないので、そこで止まる(当てた気にならない)。全部当てる口は無い — 助言は好みなので、機械が全部当てた時点で警告との区別が消える。
当てた所は画面の言葉でも言い直す。定義の道(page.actions[0].confirm)で言われても、人はレビューできない。
受注照会(order_search)— 変わったところ
## できる操作
・「一括承認」が変わりました
前: 一括承認 … アプリ側の処理を呼ぶ(approveOrders)。選んだ行に対して実行する
後: 一括承認 … アプリ側の処理を呼ぶ(approveOrders)。選んだ行に対して実行する。押すと確認を出す役割の名前は、定義から数える
roles は5か所に書けるので、役割名は定義のあちこちに散る。綴りを間違えても画面は出る(そのボタンが誰にも見えないだけ)ので、書く前に一覧を引く。
npx -p @hatake-fw/api hatake explain app.yaml --roles販売管理(sales) — 出てくる役割 3
manager … 2 か所
・メニュー「原価」 … app.menu[1].roles
・ボタン「CSV 出力」(order_search) + admin … app.pages[0].actions[1].roles
manaher … 1 か所
・列「原価」(cost_search) … app.pages[1].table.columns[1].roles
※ 1か所しか出てこない役割は**綴り違いの疑い**があります(並びは出てくる回数の多い順)。出てくる回数の多い順なので、1か所しか出てこない役割が下に落ちてくる。上の manaher がそれ。役割ごとに「その役割だから開ける画面」も出る(空なら、その役割は列や項目にしか効いていない)。MCP なら hatake_explain に roles: true。
書き方の見本は spec/examples/roles_app.yaml(roles を書ける5か所ぜんぶと、役割ごとの一括上限)。npx -p @hatake-fw/api hatake examples 権限 で引ける。
出るのは定義に書いてある名前だけで、アプリ側の権限判定と合っているかは見られない(それは Framework の外の話)。「誰がどの画面を開けるか」は入口を辿った別の答えで、explain の「開ける人」に出る。
レビュー用に1枚で出す
説明の「この画面でできないこと」と助言の「書き足したほうがいい所」は隣の話。レビューする人が見る紙は1枚がいいので、まとめて出せる。
npx -p @hatake-fw/api hatake explain page.yaml --review
npx -p @hatake-fw/api hatake explain app.yaml --review --page order_search # 助言もその画面だけに絞る助言は最後の節にまとめ、警告ではないと毎回書く。1枚にしても終了コードは変わらない。
レビューに出す紙としてもう一段まとめたものが design(言ったこと・読み返し・決まっていないこと・助言・終わりの判定を1枚に)。手で書く欄が無いので刷り直せば古くならない — 言ったことを残す。
生成した定義が長くなったら短くする
AI に書かせた定義は冗長になる(type: text、required: false、validators: [])。冗長な定義はレビューが重くなり、次に AI が読むときのコンテキストも太る。
npx -p @hatake-fw/api hatake minimize page.yaml > short.yaml # 落としたものは標準エラーに出る落とすのは「既定値と同じ指定」と「空の指定」だけ。1つ落とすたびに解析後のモデルが1バイトも変わらないことを確かめ、変わったら戻すので、意味は変わらない。出力は落とす所だけを切るので、コメントも書き方も改行コードもそのまま(差分が「消えた行」だけになる)。書き間違いのある定義は最小化しない — 未知キーを黙って消す道具になってはいけないので。
定義が増えたら、索引で探す
画面が10枚を超えると「どこに何があるか」が分からなくなる。grep では「その画面が何をするか」が出てこないので、1行の要約を集めた索引を作る。
npx -p @hatake-fw/api hatake index definitions/ --find "顧客 検索"
npx -p @hatake-fw/api hatake index definitions/ --by size # 規模の大きい画面から
npx -p @hatake-fw/api hatake index definitions/ --json # AI に渡す(近い画面を探させる入口)探せるのは現場の言葉と実装の言葉の両方(ラベルの「得意先」でも、項目名の customer でも当たる)。--find は語の AND なので、文ではなく語を並べる。
索引はアプリの中からも引ける(Dart 版・Java 版に同じものがある)。定義の山を持っているのはアプリ側なので、CLI だけに在ると「自分の画面を探す」ができない。
final index = ScreenIndex.ofApp(app); // hatake_core
for (final screen in index.search('顧客 検索')) print(screen.brief);種別の見出し語は spec/vocabulary.json が正で、3つのエディションはそれを転記している(一致は各エディションの試験で見る)。同じ定義の山なら、どのエディションでも同じ枚数になる。
画面と遷移を図にする
npx -p @hatake-fw/api hatake diagram app.yaml --out app.svg段は「メニューから開ける画面 → そこから遷移で開く画面 → …」。この並べ方にするとどこからも開けない画面(メニューにも遷移先にも無い)が自然に落ちてくる。画面が増えると一覧では気づけないやつ。
段のあいだの遷移は1本ずつ線になる(まとめて1本の矢印にすると「AとBのどちらから開くのか」が読めない)。線を引けなかった遷移(同じ段の中・戻り)は文で全部挙げるので、図に出ていない遷移が黙って消えることはない。できあがりは図解のページに置いてある。
箱の中には誰が開けるかも出る。ページに roles は書けないので、メニューとボタンの roles から辿って数えている。赤枠=誰でも開けて消す/持ち出せる画面、点線=誰も開けない画面(入口の権限が食い違っている)。--role admin を付けると、その役割で通れる道だけの図になる。
1枚の画面の中身は図にしない(explain のほうが読める)。図は「画面が増えたときの遷移」のためのもの。図解のページに載せている3枚も同じコマンドで描いている。
書き方が違うだけか
AI に直させると差分は爆発する(キーの並びが変わる・既定値が明示される・囲みの書き方が変わる)。けれど意味は変わっていないことが多い。いまの律速は AI の速さではなく人のレビューなので、ここはAI を使うほど効く。
npx -p @hatake-fw/api hatake same before.yaml after.yaml見ているのは解析後のモデル。だから並べ替えと既定値の明示は差にならないし、逆に意味が変わっていれば1件でも必ず言う ── 違うときは、どこが違うかを道つきで出す(同じか違うかだけでは、レビューには使えない)。
違う画面です(違う所 1 件):
form.sections[0].fields[1].label
before.yaml: 金額
after.yaml: 金額(税込)同じなら終了コード 0、違えば 1。等価な条件の書き換えは見ない(all: [x] と x はモデルが違うので「違う」と言う)── 条件の代数を入れると、道具が「同じ」と言った所を人が確かめられなくなるので、そこは踏み込まない。同じ=正しい、ではない(両方とも同じように間違っていることはある)。
この判定が嘘をつかない仕掛けは、minimize(既定値と同じ指定を落とす)の出力が必ず「同じ」と判定されることを試験が見ているところ ── 意味を変えないと言っている道具の結果を、別の道具で言い直せる。
画面の試験も、最初の1本は起こせる
シナリオ(値の話)は下書きから「まだ試していない所」まで閉じた。残っていたのは画面の試験 ── 押せるか・出ているか・保存に行ったかは、画面を出さないと分からない。道具(hatake_test)は配ったが、最初の1本を書くのは今も人だった。
npx -p @hatake-fw/api hatake run page.yaml --widget-draft --assets assets/page.yaml --out page_test.dart// table.rowActions(edit)+ form から。
testWidgets('行から編集を開いて保存できる', (tester) async {
final page = await pumpPage(tester, definition, rows: [{'id': 1, 'code': 'テスト'}]);
await tester.tap(HatakeFind.edit(1));
await tester.pumpAndSettle();
await tester.enterText(HatakeFind.field('code'), 'テスト');
await tester.tap(HatakeFind.formSave);
await tester.pumpAndSettle();
// 保存に行ったこと(何をどう保存するかは Repository の担当)。
expect(page.repository.calls, contains('update(1)'));
});キーの字は1つも書いていない。 押す相手は公開された規約(HatakeFind)で書くので、規約を変えたら下書きも一緒に動く。
期待に書くのは枠組みが必ずそうする所だけ ── 問い合わせに行った・保存に行かなかった・登録した処理が呼ばれた。業務として正しいか(その値でいいのか・保存後にどこへ行くのか)は人が足す。見ていないもの(押した先の画面・プラグインの中身)は毎回言う。
この下書きが嘘をつかない仕掛けは、デモアプリの test/ に生成したものをそのまま置いてあるところ。CI が再生成して差分を見て、flutter test がそれを本当に走らせる ── 通らない試験を書き出す道具は、無いほうがまだいい。
その変更は、どの要求から来たか
diff は「何が変わったか」しか言えない。なぜ変わったかは人が PR 本文に書くしかなく、書き忘れると半年後に誰も理由を思い出せない。意図の1枚と定義の差を組めば、そこは機械が言える。
npx -p @hatake-fw/api hatake trace --diff before.yaml after.yaml --intent order_search.intent.yaml足されたもの(2 件):
・column:stock「在庫数」… **どの要求からも来ていません**
・action:reject「却下」… R7 から
消えたもの(1 件):
・action:approve「承認」… R2 が指していました(その要求は空を指すようになります)言えるのは足された/消えた相手まで。 書き換え(ラベル・必須・見せ方)は指せる相手が動かないので由来を言えず、件数だけ出して「言えない」と書く ── そこを推し量ると、由来の表そのものが信用されなくなる。
由来が無い=間違い、ではない(先に書いて、あとで意図に足すこともある)ので既定では落とさない。CI で止めるなら --require-intent。
次に書く1件まで下ろす
--cover は「まだ試していない分岐」を挙げる所まで来たが、そこから書くのは人だった。判断を投げられた AI は止まるか、勝手に決める(後者のほうが悪い)。
npx -p @hatake-fw/api hatake run page.yaml --scenario s.json --cover --draft --out more.jsonまだ試していない分岐から 2 件起こしました:
・lines[].qty の「落ちた」側 ← 明細「明細」の1行目で必須の「数量」を空にした
・lines[].price の「落ちた」側 ← 明細「明細」の1行目で必須の「単価」を空にした起こした形は実際に回して、その分岐が本当に埋まることを確かめてから配る。当たっていない下書きを配ると --cover が永久にゼロにならず、AI は同じ所を何度も試すことになる。
起こさないものも決まっている ── 組み合わせの条件(all / any / not)と明細の行に対する条件は、どれを動かすかが意図なので人が書く(理由つきで一覧に出る)。
「その規則は何か」を引く
validate に groupby-without-sort と言われたとき、それが何かを引く先が無かった。仕様書を全文読ませるか、その警告を出す定義をもう一度作って転ばせるしかない。だから規則そのものを引ける形にした(reference と同じ立ち位置)。
npx -p @hatake-fw/api hatake rules groupby-without-sort# groupby-without-sort 並びの決まっていないグループ
こうなる: グループはコントロールブレイクなので、行がその順で届かないとグループが分裂し、小計が何度も出ます。
直し方 : report.sort に印刷したい並びを書いてください(並べ替えは Repository の責務)。
対照表 : hatake pitfalls groupby-without-sort定義は要らない。 書く前に「何を言われるか」を眺めてもいい。名前を省けば全部(警告 93・助言 21)出る。MCP なら hatake_rules。
警告と助言は混ぜない。 警告は「書いたのに効かない」=事実なので CI で落としてよく、助言は「書いていないから不便かも」=好みなので終了コードを変えない。出力でも毎回そう書く。助言にはつまみ(--rules の options で変えられる目盛り)も付く。
この1枚が古くならない仕掛けは、実装が出しうる規則名を機械で数えて、表と完全に一致することを試験が見ているところ。新しい警告を足したら、表に書くまで CI が通らない(消した規則が表に残っていても落ちる)。1件ごとの fix も表から出るので、同じ字が2か所に無い。
引けないものも決まっている。綴り違いの候補・件数・紙の実寸はその場でしか出せないので1件ごとの警告に残るし、人が決めること(排他・採番・端数…)はそもそもここには出てこない ── それは ask の担当。
押すまで・刷るまで気づけないもの
validate が見るのは綴りと構造だけ、ではない。通るのに黙って効かない書き方も言う。
| 書いたもの | 何が起きるか |
|---|---|
params: { id: $row.ordreNo } | 遷移は起きて、渡る値だけが空になる(開いた先が「該当なし」) |
enabledWhen: { all: [status = 受付, status = 承認済] } | ボタンは出て、永久に押せない |
paper: { size: B4 } | 刷る側が知らない紙は A4 として刷られる(列が溢れるのは刷ったあと) |
どれも綴り違いに見えるときだけ言う。行やレコードは Repository から来るので、定義に書いていない項目が実際には在る(一覧に出していない値を渡すのは普通の書き方)── ここを厳しくすると、正しい定義に毎回警告が出て読まれなくなる。
動いているアプリに聞く
静的な走査(hatake registry <ソース>)は、その場に書いてある文字列しか読めない。変数や関数から組み立てている登録は原理的に読めないので、そこは「読めなかった」として残る。動いていれば全部分かるので、申告する口がある。
File('hatake-registry.json').writeAsStringSync(registrySnapshotJson(scope));npx hatake registry --from-app hatake-registry.json --out hatake-registry.json書いた紙には印($source)が入っていて、印の無い紙は受け取らない。手で書いた一覧は「登録していない」と「書き忘れた」の区別が付かないので、申告として扱うと道具が無い種類について断定するようになる。
印のある一覧を --registry に渡すと、そこまで言える。
警告 page.actions[0].type: exportSink を使うボタンがありますが、動いているアプリの申告に
出す口が1つもありません。**ボタンは出るのに何も起きません**。印の中身が本当かは見ていない(書けば名乗れる)。言えるのは「名乗っていない紙は受けない」まで ── そう出力にも書いてある。
定義の外との食い違いも見られる
定義は自分だけでは動かない。repository: orderRepository と書いても、アプリ側がその名前で登録していなければ画面は出るがデータが来ない。名前の食い違いは画面を見ても気づけないので、機械に言わせる。
npx -p @hatake-fw/api hatake refs page.yaml --needs-registration # 定義が要求しているもの
npx hatake registry lib/main.dart --out hatake-registry.json # アプリが登録しているもの
npx hatake validate page.yaml --registry hatake-registry.json # 突き合わせるrefs が「何を登録すればいいか」、registry が「実際に何を登録しているか」を出し、validate が突き合わせる。定義の隣に hatake-registry.json を置いておけば --registry は省ける。AI に組み込みまでやらせるなら、hatake_refs を引かせてから登録コードを書かせるのが早い。
registry は言語のパーサを持たない。その場に書いてある文字列しか読めないので、変数や関数から組み立てている登録は「読めなかった」と報告して終了コード 1 になる。黙って落とすと「登録してあるのに未登録」という嘘の警告になるため、不完全なら不完全だと言って止まる。
読めなかったぶんは、動いているアプリに聞く(Flutter 側の registrySnapshot)。出す形は同じなので、ソースを読む道と実行時に聞く道のどちらで作った一覧でも validate --registry に渡せる。
File('hatake-registry.json').writeAsStringSync(registrySnapshotJson(scope));繋がっていない所を1枚で
押す前に言えることは増えた(validate --registry の警告、Renderer が繋がっていないボタンを灰色にして理由を出す所)。それでも言われるのはその画面を開いたときか、その1件ごとで、「全部で何か所残っているか」はアプリを巡らないと分からない。配線の抜けは「あと3か所」と分かっていれば終わるのに、分からないと終わらない。
npx hatake gaps app.yaml --registry hatake-registry.json繋がっていない所が 2 か所(足りない登録は 1 件)。定義 1 枚を、渡された登録済みの一覧と突き合わせました。
プラグインが 1 件 [unknown-plugin]
こうなる: そのボタンは押しても何も起きません(画面には出ます)。
order_search / approve … approveOrders
app.pages[4].actions[1].plugin
order_detail / approve … approveOrders
app.pages[6].actions[0].plugin
画面ごとの残り:
order_detail: 1 か所
order_search: 1 か所押す所ごとに1行なのがこの紙の形。警告は同じ名前を1件にまとめる(直す所は登録の側なので、それが正しい)が、この紙は「どのボタンが空振りするか」を見る紙なので、参照している所を全部出す。だから数が2つ出る ── 直す側の数(足りない登録)と、空振りする所の数。
新しい判断はしていない。種類の呼び方も「埋めないと何が起きるか」も、警告の規則表から採っている(同じ物差しを2つ持つと、警告には出るのに一覧に出てこない、が起きる)。見出しに規則名が付いているのは、そのまま hatake rules で引けるように。
言えないことも決めてある。見たのは名前が登録されているかまで ── 中身が業務として正しいか・wire が置いた TODO のままかは数えない(そこは refs --filled --source)。そして渡されていない種類について「登録していない」とは言わない。手で書いた一覧では「登録していない」と「一覧に書き忘れた」の区別が付かないので、理由つきで「見なかった種類」に並べる。動いているアプリの申告(registry --from-app)なら、申告できる種類について「1つも登録していない」まで言える。
数を良くするために、中身を消せない
配線が「本当に埋まったか」は数えられる(refs --filled --source)。この数は出荷前の 判断に使うので、数え方そのものが嘘をつくと紙が無いより悪い。穴が2つあった。
1つめ。目印(UnimplementedError)を消して空実装に置き換えると、数え方が「TODO の まま」から「埋まっている」に変わる=数を良くするために中身を消せる。
npx -p @hatake-fw/api hatake refs page.yaml --filled --source lib/定義が要求している登録: 2 件(実装 1 ファイルと突き合わせました)
TODO のまま 0
中身が無い 1
埋まっている 1
中身が無い(本体が空です=**落ちないので気づけません**(押しても何も起きない)。
何もしないのが正しいなら、そう分かる中身を書いてください):
plugins/approveOrders lib/wiring.ts:4見るのは空の本体だけ({} / => null / { return null; })。式が1つでも書いて あれば見ない ── 中身が業務として正しいかは、そもそもこの道具に言えない。検証 (validators)の null は「OK」という答えなので、そこは空実装と言わない(契約の表は 道具の側に1つだけ持っている)。
間違いとは言わない。 何もしないのが正しい登録もあるので、「TODO のまま」とは欄を 分けて理由を書く。ただし --pending-as-error では落とす ── 落とさないと、目印を消して CI を緑にできてしまう。
2つめ。目印は Dart の形しか知らなかった。TypeScript の定番(throw new Error("TODO: …")) を知らないと、TS の配線を渡したときに黙って「全部埋まっている」と出る。いまは 投げていて、かつ TODO と言っていることの両方を見る(TODO の字だけで拾うと、業務の メッセージまで未実装と言い出す)。
手引きに載せたコマンドは、本当に走っている
手引きに載せたものは走らせないと必ず腐る(読んだ人の所で初めて落ちる)。定義は validate にかけ、断片はキー名を語彙と突き合わせてきたが、コマンドは --help に載っているかまでしか見ていなかった。旗の名前が合っていても、引く先が 消えていれば手引きは嘘になる(hatake rules <消えた規則名> は何も返さない)。
いまは引く道具だけ実際に走らせる。
引く道具を 8 回**実際に走らせました**(終了コードだけを見ています)。
走らせなかった: 60 回 … 引く道具ではない(定義や紙が要る・通信する・書き込む)
走らせなかった: 5 回 … 定義が要る形(--kinds なら走らせる)
走らせなかった: 3 回 … 置き換える所(<…>)が書いてある選び方には理由がある。reference / examples / pitfalls / rules / failures / where は定義も紙も要らず、通信せず、1バイトも書かない。しかもAI が引く知識 そのものなので、ここが腐ると一番効く。判定は終了コードだけで、新しい物差しは 作っていない(この道具はどれも「引けなかったら 1」を既に返すので)。
走らせなかったものは理由ごとに数えて出す。黙って飛ばすと、見ていないことが見えなく なる ── 数が出ていれば「思ったより走っていない」に気づける。
直した影響を見る
npx -p @hatake-fw/api hatake diff before.yaml after.yaml3段で返る。✗ 破壊的 は呼び出し側が壊れる(必須項目を足した・返す形から消した・型を変えた)。△ 要確認 は壊れないが人に確かめてほしい(列やボタンや選択肢が消えた・権限が狭まった/広がった・ページやメニューが消えた)。・安全 は増えただけ。
「要確認」を「破壊的」と混ぜていないのが要点で、列を消すのは普通にやることだから止める話ではなく気づかせる話。CI で止めたいなら --caution-as-error。