前セツ
どうも、今年あんまりスズメバチみてないな~ な、え~すけさんですよ。
※毎年通勤時に執拗に追いかけられることがあったが、今年まだないので命がヒリヒリしない。。。
前回、自作フレームワーク hatake + AI で業務システムを1本まるごと作って、
21.0人日 → 3.75人日になった話を書きました。
で、たぶん一番知りたいのはここだと思うんですよ。
で、人は具体的に何をしたの?
定義 345行もアプリ 1,052行も AI が書いたんですが、AI に依頼を出したのは人です。
その人が何を用意して、何を決めて、何を投げたか。
これが残ってないと誰も真似できないので、全部リポジトリに残しました。
→ https://github.com/ASIL-E-Hatake/hatake-example
この記事はその解説です。
※ hatake の話というより「AI に業務システムを作らせるときの分担」の話なので、
別のツールを使ってる人にもだいたい当てはまるはずです。たぶん。
結論:人の仕事は3か所だけ
[人] 案件の説明を書く(普通の日本語でいい)
↓
[AI] 枠組みの外を仕分ける(認証・締め処理・採番…)
↓
[人] 前書き(hatake.project.yaml)を育てる ← 下書きは AI が起こす
↓
[AI] 定義を書く → 自分で読み返して直す
↓
[人] 返ってきた問いに答える ← ★ここが本番
↓
[AI] 設計資料を起こす(画面一覧・遷移図・設計書・API 一覧)
↓
[人] 何を確かめるかを決める → [AI] 回して記録を作る
人がやるのは ① 案件の説明を書く ② 前書きを育てる ③ 問いに答える の3つ。
あとは道具が答えます。
そして人にしか書けない紙が2枚あります。
案件の説明 と 案件の前書き(
hatake.project.yaml)。
ここを AI に埋めさせると、それっぽいけど誰も決めていないことが設計書として残ります。
ここが今日いちばん言いたいところです。
① 案件の説明を書く(人・57行)
普通の日本語で、知ってることを書き出すだけ。書式は要りません。
実際に書いたのはこんな感じです。
# どんなシステムか
人事部と購買部が使う、社内マスタの管理画面。いまは Excel で管理していて、
更新のたびに情シスに依頼が来ている。これを自分たちで直せるようにしたい。
利用者は社内だけ。同時に使うのは10人くらい。
# 管理したいマスタ
## 社員マスタ
社員番号・氏名・カナ・所属部署・役職・入社日・退職日・メール・内線・在籍区分。
- 社員番号は6桁の数字。あとから変えられない
- 退職者も残す(消さない)。一覧では既定で**在籍者だけ**を出す
- 入社日 ≤ 退職日
# 決まっていないこと
- 同時に同じレコードを直したときにどうするか
- 社員番号の採番を誰がやるか(人事が手で決める? 自動?)
- 退職者の扱い(消す? 残す?)── たぶん残す
- 与信限度額の端数
「できないこと・やらないこと」が一番大事です。
書かないと AI は書ける方に倒すので。
ログインは社内の Active Directory を使いたいが、今回は間に合わないので
**ID とパスワードをこのシステムで持つ**。将来 AD に差し替える。
あと「決まっていないこと」を決まっていないと書いていいのもポイントです。
「たぶん残す」でいい。後で AI に聞かれるので。
② 枠組みの外を仕分ける(AI に投げる)
投げた依頼文、そのまま:
docs/1-要件定義/案件の説明.md を hatake where --from で仕分けて、
**枠組みの外のもの**を教えて。
外と出たものは定義に書かずに、前書きの premises か logic に残したい。
この案件では 7件が「枠組みの外」 と出ました。いちばん大きいのが認証:
[枠組みの外(hatake は持たない)] 認証(ログイン・トークンの発行)
hatake は持たない。ログイン画面は定義で作れるが、資格を確かめるのはサーバか認証基盤。
hatake は業務ロジックも承認フローも認証も持たないと決めてるので、
ここで「外」と出たものを定義に書かせようとすると、
AI は画面の中に業務ロジックを書き始めます。
これ、動いちゃうから気づきにくくて、あとで剥がすのがいちばん高いんですよ。
先に仕分けるのはそのためです。
※ 道具が当たりすぎることもあります。 「与信限度額は金額(カンマ区切りで見せたい)」まで
「業務ロジック(与信)」に当たった。これは見せ方の話なので定義で書ける。
仕分けは下書きで、読むのは人です。全部鵜呑みにしない。
③ 前書きを育てる(人・149行)
hatake.project.yaml = 定義を書く前に AI に読ませる1枚です。
| 節 | 何を書くか | 機械が見るか |
|---|---|---|
system.what / users
|
何のシステムか・誰が使うか | 見ない(人と AI が読むだけ) |
system.premises |
できないこと | 見ない |
glossary |
業務の言葉 → 定義に書く名前 | 見る(定義と突き合わせる) |
logic |
業務ロジックの置き場(中身は書かない) | 見る |
naming |
名前の形(camelCase など) | 見る |
実物の一部:
system:
premises:
# **できないこと**を書く場所。書かないと、AI は書ける方に倒す。
- ログインは hatake の外。画面は定義で作るが、資格を確かめるのは API 側。
- 社員番号は採番しない。人事が決めた番号を手で入れる(既存の Excel と揃えるため)。
- 退職者は消さない。在籍区分を「退職」にするだけ。
- 監査(誰がいつ何を直したか)は API 側で残す。画面は残さない。
glossary:
# 「業務の言葉 → 定義に書く名前」。同じものが画面ごとに違う字になるのを止める。
- term: 在籍区分
field: employmentStatus
avoid: [ステータス]
note: 在籍 / 休職 / 退職。「退職フラグ」とは呼ばない(3値あるので)
logic:
# 業務ロジックの**置き場**だけを書く(規則の中身は書かない)。
- what: ログインして資格を確かめる
where: outside
why: hatake は認証を持たない。画面は作れるが、確かめるのは API と認証基盤
下書きは AI に起こさせていいです。ただし言い方が大事:
hatake project --draft --from docs/1-要件定義/案件の説明.md で下書きを起こして。
ただし**私が言っていないことは書かないで**。
返ってくる下書きは薄いです。 資料の見出しから拾えるところしか拾わないので、
ほとんど空で返ってくる。それでいいんですよ。
想像で埋まって返ってくるほうが困るので。育てるのは人。
ここで踏んだ地雷2つ
決めごとは「守れるものだけ」書く。
naming.suffix.number: Amount と書いたら、creditLimit を creditLimitAmount に
しろと言われ続けました。業務の言葉から離れるので、こっちが折れてやめた。
守れない決めごとを書くと助言が鳴りっぱなしになって、そのうち誰も読まなくなります。
これが一番マズい。
辞書の見出しは広く取りすぎない。
glossary の term と avoid は部分一致で効くので、「区分」を見出しにすると
「在籍区分」まで巻き込みます。実際やらかして、
「employmentStatus は supplier で始めろ」と言われ出しました。🤦
- term: 取引先区分
field: supplierType
avoid: [種別, タイプ]
note: >-
法人 / 個人。**見出しを「区分」にしない**(在籍区分まで巻き込んで、助言が
「employmentStatus は supplier で始めろ」と言い出す。実際に一度そうなった)
このコメント、実際に前書きに残ってます。踏んだ地雷はその場に書いておくのがいい。
④ 定義を書かせる(AI の仕事)
投げた依頼文:
definitions/hatake.project.yaml を読んでから、社員・部署・取引先の3マスタを
1枚の app 定義にして definitions/app.yaml に書いて。
要件は docs/1-要件定義/案件の説明.md のとおり。特に:
- 社員番号は6桁の数字。**採番しない**(前書きの決めごと)
- 退職者は消さない。在籍区分を変えるだけ
- 内線は admin と hr だけに見せる
- 取引先の検索は、範囲(与信限度額)と複数選択(締め日)を使いたい
- インボイス登録番号は法人のときだけ必須
書けたら hatake check --project にかけて、**警告ゼロ**になるまで直して。
**助言(好み)は当てる前に何を当てるか教えて**。案件の判断が要るものは私が決める。
必ず入れる3つ:
| なぜ | |
|---|---|
| 前書きを先に読ませる | 読ませないと用語が揺れる(「在籍区分」が「ステータス」になる) |
| 警告ゼロまで | 警告は事実。直すのに業務の判断が要らないので AI に任せていい |
| 助言は勝手に当てさせない | 助言は好み。案件によって答えが違う |
この「事実」と「好み」を分けてるのが地味に効きます。
hatake check は1回で4つの欄を、混ぜずに返すので:
| 欄 | 何を言うか | 誰が相手か |
|---|---|---|
| 事実(検証) | 書いたのに効かない | AI が直していい |
| 読み返し | 意図どおりか | 人が読む |
| 好み(助言) | 書いていないと不便かも | 人が選ぶ |
| 人が決めること | 定義に書けない | 人が答える(次の章) |
実際に道具に止められた話
「まとめて退職にする」の上限は 100 件ですが、この表は1ページ 50 件です。
選べるのは**画面に出ている行**だけなので、100 件は選べません=**この上限は一度も効きません**。
一括処理の上限を100件にしたら止められました。書けるし、画面も普通に出るし、
押してみても気づけないやつ。上限を 50件(hr は 20件)に直しました。
助言も1件出ました:
「まとめて退職にする」の失敗の言い方は件数だけです。**どの行が落ちたか**を言えないので、
押した人は全部やり直すか、当たりを付けて探すことになります。
これは業務として妥当だったので当てました(文言に {failedKeys} を足した)。
当てるかどうかは人が決める、が設計として効いた場面です。
⑤ 問いに答える(★ここが人の本番)
で、ここが一番言いたかったところです。
雑な依頼から画面を起こすと、書けることは全部書けてしまうんですよ。
だから出来上がった定義に空欄が無い。空欄が無いと、決まってないようには見えない。
そして動かした後にこう言われる。
- 「2人で同時に直したらどうなるの」
- 「消したやつって残ってるの」
- 「この端数、切り捨てでいいんだっけ」
全部、定義に書けないことなんです。だから検証も助言も言えない(あの2つは
定義に書けることしか見ない)。ここだけがスッポリ抜ける。
なので hatake ask という道具を作りました。投げた依頼文:
hatake ask definitions/app.yaml --project definitions/hatake.project.yaml
を回して、決めていないことを出して。
**答えは私が決めるので、勝手に決めて書かないで。**
返ってきた6件
| 問い(id) | 何を聞かれたか | 人が決めたこと |
|---|---|---|
concurrency |
同時に直したらどちらが残るか | 更新日時が変わっていたら弾いて読み直させる |
partial-failure |
一括が途中で失敗したら | 1件ずつ確定して、失敗した行だけ残す |
validation-server |
画面の検証をサーバでも見るか | 見る(同じ定義で回す) |
role-source |
役割はどこから来るか | ログイン時にこのシステムが返す(将来 AD) |
audit |
監査を残すか | 削除と一括は必ず残す |
notify |
一括のあと知らせるか | 知らせない |
どれも AI には答えられません。 業務の判断なので。
AI が答えると「それっぽいけど誰も決めていない設計」ができあがります。
そして半年後に必ず揉めるのがこの6件です。
聞かれ方はこんな感じ:
1. [concurrency] 同じ1件を2人が同時に直したら、どちらの入力を残しますか
(後から押した人を通す/弾いて読み直させる)。
聞く理由: 定義に書けるのは「入力できる」までで、**誰の入力が残るか**は保存する側の話。
決めないと: 先に保存した人の入力が黙って消える。しかも画面には両方とも「保存しました」と出る。
誰の担当か: サーバの担当
「決めないとどうなるか」が書いてあるのが大事で、
これが無いと人は判断できないんですよね。
答えたら前書きに戻す
答えは前書きの logic に1行足して answers: [<印>] を付けます。
次から同じ問いは出ません。
で、ここでもやらかしました。
questions.decided[0]: "soft-delete" という問いはありません
(npx hatake ask --kinds で引けます)
soft-delete とか auth-owner とか、それっぽいけど存在しない id を書いて落とされた。
正しくは erase(消したものを残すか)でした。
勝手な id が通らない=「答えたつもりで答えていない」が作れない、という設計です。
落とされるのが正しい。
区分を間違えても落とされました:
logic[2].answers: "numbering" は「サーバの担当」ですが、where に outside と
書いてあります(担当が違うなら、答えになっていないか区分が間違っています)。
Claude Code の設定(毎回同じ説明をしない)
ここは細かい運用の話。
hatake-example/
├─ CLAUDE.md リポジトリ全体の決めごと
└─ apps/<案件>-prj/
├─ CLAUDE.md その案件の決めごと(前書きから生成した節つき)
└─ .claude/
├─ settings.json 許す操作・許さない操作
└─ commands/ 定型の依頼(スラッシュコマンド)
CLAUDE.md の一部を前書きから生成してます。
案件の説明・用語・決めごとって、前書き(hatake.project.yaml)にもう書いてあるんですよ。
同じことを CLAUDE.md にも手で書くと、片方だけ直したときに必ず食い違う。
なので前書きを正にして、貼る側を生成します:
<!-- hatake:project:begin -->
<!-- hatake:project:end -->
npx hatake project definitions/hatake.project.yaml --agents --merge CLAUDE.md
印の中だけが入れ替わるので、外に書いたもの(ブランチ名・レビューの回し方)は消えません。
そして古くなってないかを CI で見る:
npx hatake project definitions/hatake.project.yaml --agents --check --merge CLAUDE.md
違ってたら終了コード 1。AI が古い決めごとを読むのを機械で止める、という話です。
※ ブランチ名やコミット規約は前書きに書きません。定義に現れないので機械が突き合わせ
られず、必ず腐るので。それは CLAUDE.md の印の外に手で書く。
よく使う依頼はスラッシュコマンドに
毎回同じ文を打たなくて済むように .claude/commands/ に入れてます。
| 打つもの | 何をするか |
|---|---|
/仕分け |
資料を hatake where --from にかけて、枠組みの外を出す |
/前書き |
前書きの下書きを起こす(言っていないことは書かせない) |
/画面 |
前書きを読ませてから定義を書かせ、check が通るまで直させる |
/検収 |
check と ask を回して、残っているものを出す |
やってはいけないこと(まとめ)
| なぜ | |
|---|---|
| 前書きを AI に想像で埋めさせる | 案件のことは人しか知らない。埋まった紙は嘘の設計書 |
| 問いに AI が答える | 業務の判断。答えは案件ごとに違う |
| 「外」と出たものを定義に書かせる | 画面の中に業務ロジックが入る。フレームワークが壊れる |
| 助言を全部当てさせる | 助言は好み。当てるほど良い定義になるわけではない |
| 定義を手で書く | 書けるけど、手で書くと前書きとの突き合わせが効かない |
最後のやつ、逆説的なんですが、人が定義を手で書くと機械のチェックが効かなくなるんですよ。
AI に書かせて道具にかけるほうが速いし正確、という話。
次
きれいな話ばかり書いたので、次は転んだ話です。
13件踏みました。 本体のバグ3件を含めて、見栄えのために減らさず全部出します。
あと「直ってないもの・向いてない案件・試してないこと」も。
導入を検討する人が見るべきなのは、たぶん成功談よりこっちなので。
→ 記事4: 転んだ話13件と、まだ出来ていないこと