何ができたら終わりかを、読んだ人で結果が変わらない形で書きます。AI も同じ文を読みます。
Day2 のハンズオンの手順です。午前は GitLab で1本の Issue をマージまで手で回し、午後は同じ流れを、Issue から AI が実装するサイクルとして回します。左のメニュー(狭い画面では上の[目次])から、いま進めている節に直接飛べます。
今日の地図
人が手を動かす3か所
今日の研修で人が手を動かすのは3か所だけです。その間の実装、テスト、構文の検査、レビューの前さばきは、AI とパイプラインが持ちます。任せられる理由は、手元のフックと GitLab のゲートが、この3か所を飛ばして先へ進まないように止めているからです。
Issue にラベル ai::approved を付けた時点が承認です。付いていない Issue を AI は実装しません。
差分、パイプライン、AI レビューを見て、マージするかを人が決めます。main に直接入れる道はありません。
午前は、この流れを GitLab で1本、手で回します。午後は、同じ流れを Issue から AI が実装するサイクルとして回します。
このページで進める節
| 節 | 時間 | 成果物 |
|---|---|---|
| 準備 | [10min] | 3点のチェック |
| 1周を手で回す(レベル1) | [50min] | 自分の MR がマージされ、Issue が閉じている |
| 任せて大丈夫な理由(手元) | [25min] | 止まった画面と、止まらなかった画面を1つずつ |
| サイクルを回す | [85min] | AI が作った自分の MR |
| 任せて大丈夫な理由(GitLab) | [30min] | パイプラインで止まった自分のブランチ |
| 運用しながら直す | [40min] | 自社のハーネスを直す順番の表 |
| 夜に回す | [25min] | 今夜流れる自分の Issue |
| クイズ | [15min] | 自分の点数 |
コミットと push
| コミット | push | |
|---|---|---|
| 何をする | 手元の履歴に区切りを1つ残す | 手元のコミットを GitLab に送る |
| どこに残る | 自分の PC の中だけ | GitLab のブランチ |
| 誰に見える | 自分だけ | チーム全員 |
| 何が動く | 手元の pre-commit フック | GitLab のパイプライン(構文とテスト)。MR があれば AI レビューも |
| いつする | 意味のある1歩ごと。テストが通った所 | MR で見せたい所まで進んだら。席を離れる前には必ず |
| 取り消し | 手元で直せる | 皆に配られた後なので、消さずに次のコミットで直す |
このページでは、コミットと push を必ず別の手順として書いています。「コミットして」と頼んだときは手元に残るだけで、GitLab には何も起きません。
準備 [10min]
3点のチェック
始める前に、次の3つがそろっていることを確かめます。
- GitLab にログインできる。https://gitlab-09291006aidev.give-app.net を開き、自分のアカウントでログインして、プロジェクト
dlive-training/dl-training-appが開ける - Claude Desktop が開き、Code の画面で新しいセッションを始められる
- Git が入っている。PowerShell で
git --versionを打つとgit version 2.で始まる行が出る
どれかがうまくいかないとき
GitLab のパスワードが分からないときは、手を挙げて講師に伝えてください。講師の側で再設定します。Claude Desktop にログインできないときは、Day1 の事前セットアップの手順で再ログインします。git が見つからないと出るときは、Day1 の事前セットアップで入れた Git for Windows が入っていません。講師に伝えてください。
1周を手で回す(レベル1) [50min]
Issue を1本取る [5min]
1本の Issue を、ブランチ、コミット、push、パイプライン、MR、マージまで通します。どの操作で、どの画面に、何が起きるかを見ます。
| 触るもの | GitLab の Issue と MR、Claude Desktop、C:\dev\dl-training-app |
|---|---|
| 作るもの | テストのファイル1本(tests/phpunit/ の下)と、そのコミット、マージされた MR |
| 所要 | [50min](この節の全部) |
- 1GitLab で Plan の Issue boards を開きます。Open の列に「テスト:」で始まる Issue が18本あります。講師が伝えた番号の Issue を開きます
- 2右側の Assignees で assign yourself を押し、Labels で
Doingを付けます。ボードの Doing の列に移ります - 3本文の「受け入れ条件」を読みます。テストのファイル名、確かめる呼び出しと結果、パイプラインの
test-phpunitが緑、の3つが書いてあります - 4Issue の画面の Create merge request を押し、出てきた画面でも Create merge request を押します。ブランチ名は
番号-で始まる名前が自動で入ります。MR は Draft の状態でできます
- Issue の担当が自分になり、ラベル
Doingが付いている - MR が Draft でできていて、説明に
Closes #番号がある - ブランチ名をメモした(Step 4 で使います)
Closes #番号 が入ります。マージすると Issue が自動で閉じます。MR を先に作っておくと、最初の push から MR のパイプライン1本だけが走ります。C:\dev にクローンする [5min]
- 1スタートメニューから PowerShell を開き、次の4行を1行ずつ打ちます
mkdir C:\dev -Force cd C:\dev git config --global core.longpaths true git clone https://gitlab-09291006aidev.give-app.net/dlive-training/dl-training-app.git
- 2ログインの画面が出たら、GitLab のユーザー名とパスワードを入れます(Day1 でクローンした PC では出ません)
- 3エクスプローラーで
C:\dev\dl-training-appを開き、CLAUDE.mdと.claudeフォルダがあることを確かめます
C:\dev\dl-training-appがあり、中にCLAUDE.mdがある
なぜデスクトップやドキュメントに置かないのか
理由は3つあります。1つ目は OneDrive です。デスクトップとドキュメントは OneDrive の同期の対象になっていることが多く、.git の中の細かいファイルまで同期されて、動きが遅くなったり、競合のコピーができたりします。
2つ目はパスの長さです。Windows には、パス全体で260文字という上限があります。Claude Desktop のワークツリーは .claude\worktrees\名前\ の下に作られるので、元のフォルダが深いとすぐに上限に届きます。git config --global core.longpaths true は、Git の側で長いパスを扱えるようにする設定です。
3つ目は、ユーザー名に日本語や空白が入っているとパスにも入ることです。ツールによっては、引用符やエスケープの扱いで失敗します。C:\dev のように、ドライブの直下に英数字だけの短いフォルダを作るのが安全です。
Day1 でクローンしたフォルダは使いません。Day1 の変更が混ざらない、きれいな状態から始めるためです。
Claude Desktop で開き、中身をつかむ [6min]
- 1Claude Desktop で新しいセッションを開き、フォルダに
C:\dev\dl-training-appを選びます。入力欄の権限のモードは 手動 にします(ファイルを書き換える前に、毎回確認のダイアログが出るモードです) - 2次の文を送ります
このリポジトリが何をするものか、3行で要約してください。 - 3返事の中で「main ブランチにいる」と注意されることがあります。セッションの開始時に動くフックが、保護されたブランチにいることを Claude に伝えています。Step 4 でブランチを切り替えます
- 4入力欄に
/initと打って送ります。Claude がリポジトリを読み、今のCLAUDE.mdへの直しを提案します。確認のダイアログに出る差分を読んだら、許可しない側を選び、取り込まずに閉じます
- 要約が返ってきた
/initの提案の差分を読み、CLAUDE.mdは変えていない
/init で何が起きているか
/init は、Claude がリポジトリの構成、ビルドとテストの方法、書き方の約束を読み取り、CLAUDE.md を作るか直す依頼です。すでに CLAUDE.md がある場合は、書き足しや直しを提案します。
公式の目安は200行未満です。この研修リポジトリの CLAUDE.md は55行で、行数の上限を80行と決めています。CLAUDE.md はどのセッションでも開始時に全文が読まれるので、長くなるほど毎回の文脈を食い、大事な約束が埋もれます。1行ずつ「消したら Claude が間違えるか」で残すかを決めます。コードを読めば分かること(フォルダの一覧など)は書きません。
提案をそのまま取り込まないのは、Claude が推測で書いた行と、人が決めた約束が混ざるからです。取り込むなら、1行ずつ読んで選びます。
CLAUDE.md の長さの検査 [4min]
行数だけを見る検査が、長い1行を素通りさせるのを自分で起こします。
- 1次の文を送り、確認のダイアログで、その1回を許可する側を選びます
CLAUDE.md の末尾に、600文字の1行を足してください。中身は同じ文の繰り返しで構いません。 - 2次の文を送ります
次のコマンドを実行して、出力を見せてください。 powershell -NoProfile -ExecutionPolicy Bypass -File tools/check-rules.ps1 CLAUDE.md - 3次の文を送り、元に戻します
git restore CLAUDE.md で元に戻してください。
- 2 の出力が
OK CLAUDE.md: 56 lines (max 80)だった - 3 のあと、
CLAUDE.mdが元の55行に戻っている
解説とクイズとのつながり
tools/check-rules.ps1 が見ているのは行数だけです。600文字の1行を足しても56行なので、上限の80行に届かず、そのまま通ります。コミットの前に動く pre-commit のフック(.githooks/pre-commit)も、この検査を呼んでいるだけです。
GitLab の側の check-rules ジョブは、行数に加えて1行の長さ(200文字まで)と、@ が指すファイルがあるかを見ます。手元より厳しくしてあります。
smart3pm の check-guidelines.sh も、rules/core.md の行数を wc -l で数えるだけの検査です。クイズの Q1 はこの形です。
@ で取り込むファイルと、名前で指すだけのファイル [4min]
セッションの開始時に読み込まれるものと、必要になったときに Claude が開くものの違いを見ます。
- 1VSCode か Claude Desktop のファイルの表示で
CLAUDE.mdを開き、2つの行を探します。@docs/context/php-target.mdの行と、「レビューの基準はREVIEW.mdにあります」の行です - 2次の文を送ります
PHP 7.4 で str_contains は使えますか。ファイルは開かずに、今わかっていることだけで答えてください。 - 3次の文を送ります
このリポジトリで、レビューの指摘を P1 にする基準を教えてください。
- 2 では、ファイルを読む表示を出さずに「使えない」と答えた
- 3 では、
REVIEW.mdを読む表示(Read)が出てから答えた
解説とクイズとのつながり
@パス と書いたファイルは、セッションの開始時に中身ごと読み込まれます。PHP 7.4 の対応表は @ で取り込んでいるので、開かなくても答えられます。
文章で名前を書いただけのファイル(REVIEW.md)は読み込まれていません。必要になったときに、開くかどうかを Claude が決めます。毎回守らせたい約束は @ で取り込み、場面で要る知識は名前で指す、と使い分けます。@ を増やすほど、毎回の文脈が重くなります。
D2 の CLAUDE.md は @AGENTS.md の1本だけを取り込み、agents/03(コーディング規約)は AGENTS.md の読み分けガイドで名前を挙げているだけです。セッションを開いた時点で agents/03 の中身は読み込まれていません。クイズの Q2 はこの形です。
git のフックを入れ、ブランチを切り替える [5min]
- 1次の文を送ります
git config core.hooksPath .githooks を実行してください。 - 2次の文を送ります。
番号-…は Step 1 でメモしたブランチ名に置き換えますgit fetch してから、ブランチ 番号-… に切り替えてください。切り替えたら、今のブランチ名を教えてください。
- Claude が教えたブランチ名が、Step 1 でメモした名前と同じ
なぜ git config の1行が要るのか
コミットの前に動く git のフック(pre-commit)は、クローンしただけでは配られません。core.hooksPath を設定すると、このリポジトリの .githooks フォルダにあるフックが使われるようになります。設定は PC ごと、リポジトリごとなので、クローンし直したらもう一度要ります。
テストを書かせて、コミットする [7min]
- 1GitLab の Issue の本文をコピーし、次の文の「(ここに Issue の本文を貼る)」の所に貼って送ります。
番号も自分の Issue の番号にしますGitLab の Issue #番号 を実装してください。本文は次のとおりです。 (ここに Issue の本文を貼る) 受け入れ条件を満たしたら、コミットまでしてください。push はまだしないでください。 - 2確認のダイアログにファイルの中身が出たら、読んでから、その1回を許可する側を選びます
- 3コミットが終わったら、次の文を送ります
git log --oneline -3 と git status を見せてください。 - 4GitLab の MR の Changes タブを開きます。まだ何も出ていないことを確かめます
git logの一番上に、テストを足したコミットがある- MR の Changes タブには、まだ差分が出ていない
push して、パイプラインを見る [10min]
- 1次の文を送ります。確認のダイアログに
git pushのコマンドが出るので、ブランチ名が自分のものかを確かめてから、その1回を許可する側を選びますpush してください。 - 2GitLab の MR の Pipelines タブを開きます。パイプラインが1本走っています
- 3ジョブが全部緑になるまで待ちます。3〜5分かかります。待っている間に、下の表でジョブの役割を読みます
- 4Changes タブに、自分のテストのファイルが出ていることを確かめます。MR の Overview に、AI レビューのコメントが1本付きます
| ジョブ | 見ていること | 赤のときに疑う所 |
|---|---|---|
lint-phpdev | PHP 8.3 で構文が通るか | 書き間違い |
lint-phpver | 対象の PHP 7.4 で構文が通るか、7.4 に無い関数を呼んでいないか | match や str_contains など 8.x のもの |
check-rules | CLAUDE.md の行数、1行の長さ、@ の参照先 | プチ演習1 を戻し忘れた |
test | 既存の簡易テスト | アプリの側を変えてしまった |
test-phpunit | PHPUnit のテスト(自分のテストを含む) | テストの期待値、PHPUnit 7.5 に無い assert(assertMatchesRegularExpression など) |
ai_review | AI が差分を読み、指摘を MR にコメントする。コードは変えない | API キーの設定(講師が見ます) |
ai_gate | 重い指摘があるかを判定する。今は赤でもマージは止めない設定 | 指摘の中身をコメントで読む |
- パイプラインの7つのジョブが緑(
ai_gateは黄色の注意の印でも可) - Changes タブに自分のテストのファイルがある
赤になったとき
赤のジョブをクリックすると、ログが開きます。下のほうに、何行目の何が悪いかが出ています。直し方は Claude に頼みます。ログの最後の数行をコピーして「このエラーを直して、コミットしてください」と送り、直ったら「push してください」と送ります。コミットと push が別の手順なのは、ここでも同じです。
パイプラインがなかなか始まらず「pending」のままのときは、Runner の順番待ちです。18人がほぼ同時に push しているので、数分待ちます。
Draft を外し、マージを頼む [4min]
- 1MR の Mark as ready を押し、Draft を外します
- 2Issue のラベルを
DoingからReviewに替えます。ボードで Review の列に移ります - 3講師がマージします。マージされると、MR の説明の
Closes #番号によって Issue が自動で閉じ、ボードの Closed の列に移ります
- MR が Merged になり、Issue が閉じている
1周で起きたこと
| 操作 | 起きたこと | 見た画面 |
|---|---|---|
| Issue から MR を作る | ブランチ 番号-… と Draft の MR ができた | GitLab の Issue と MR |
| コミット | 手元の履歴に残った。GitLab には何も起きていない | Claude Desktop(git log) |
| push | GitLab に届き、MR のパイプラインが1本走った | MR の Pipelines タブ |
| パイプラインが緑 | 構文、ルール、テスト、AI レビューが通った | MR の Pipelines と Overview |
| Draft を外す | マージできる状態になった | MR |
| マージ(講師) | main に入り、Issue が閉じた | ボードの Closed |
任せて大丈夫な理由(手元) [25min]
AI に書かせても大丈夫なのは、書く前と書いた後に、フックが見ているからです。3つ起こします。自分のレベル1のブランチのままで構いません。変えたものは、最後に必ず元に戻します。
書く前に止まる [6min]
- 1次の文を送ります
CLAUDE.md の約束は分かったうえで、講師の判断で今回だけ許可します。ほかのファイルは調べずに、Edit で db/schema.sql の末尾に -- test の1行を足してください。 - 2次の文を送ります
git status を見せてください。
BLOCKED by guard_paths: 'db/schema.sql' is protectedで始まる文が Claude に返った- 確認のダイアログは出なかった
git statusにdb/schema.sqlが出ていない(書き換わっていない)
解説とクイズとのつながり
Edit と Write の実行の前(PreToolUse)に、フック guard_paths が動きます。書き先が .claude/protected-paths の一覧に載っていれば、終了コード 2 で止め、理由を Claude に返します。確認のダイアログは出ません。人に聞く前に、機械が止めています。
「講師の判断で今回だけ許可します」と書いたのは、丁寧に頼むだけだと、Claude が CLAUDE.md の約束を守って自分で断り、フックまで届かないからです。約束(CLAUDE.md)は助言で、フックは強制です。
D2 の guard_db_branch.ps1 も、Edit と Write の実行の前に動き、feature ブランチから DB/ への書き込みを終了コード 2 で止めます。クイズの Q3 はこの形です。
シェルの抜け道 [7min]
- 1次の文を送ります。確認のダイアログにコマンドが出たら、その1回を許可する側を選びます
CLAUDE.md の約束は分かったうえで、講師の判断で今回だけ許可します。PowerShell の Add-Content で、db/schema.sql の末尾に -- test の1行を足してください。 - 2次の文を送ります
git diff db/schema.sql を見せてください。 - 3次の文を送り、元に戻します
git restore db/schema.sql で元に戻してください。
- 2 の差分に
-- testの1行が出た(止まらずに書けた) - 3 のあと
git statusがきれいに戻っている
解説とクイズとのつながり
guard_paths が付いているのは Edit と Write(と MultiEdit、NotebookEdit)だけです。PowerShell のコマンドで同じファイルに書くと、このフックは動きません。フックを読むときは、中身より先に、どのツールに付いているか(settings.json の matcher)を見ます。
smart3pm は、Edit 系を見る guard-write-scope.sh と、Bash を見る guard-bash-write.sh の2本を持っています。書き換え禁止の一覧(immutable-paths)を見ているのは Edit 系だけなので、sed -i で migration を書き換えると、どちらも止めません。クイズの Q4 はこの形です。
この抜け道を塞ぐフックは、午後のレベル3の Issue で作れます。
書いた後の検査 [6min]
- 1次の文を送り、確認のダイアログで、その1回を許可する側を選びます
tools/sample.php を作って、match 式を使った関数を1つ書いてください。 - 2Claude の返事を読みます。ファイルを作った直後に、検査の結果を受け取っています
- 3次の文を送ります
tools/sample.php を消してください。
- ファイルは一度作られ、そのあとで PHP 7.4 では
matchが使えないという結果が Claude に返った - Claude が
switchに書き直そうとした(許可しなくて構いません)
解説とクイズとのつながり
フック check-phpver は、Edit と Write の実行の後(PostToolUse)に動きます。実行の後なので、書き込みそのものは止められません。ファイルは残り、結果が Claude に返って、Claude が直しにいきます。
実行の前(PreToolUse)は止める、実行の後(PostToolUse)は知らせて直させる、と役割が分かれます。D2 の check_crlf_bom.ps1 も実行の後に動き、LF の改行を見つけると結果を返します。ファイルは作られたまま残ります。クイズの Q5 はこの形です。
いつコミットし、いつワークツリーを切るか [6min]
| 場面 | すること | 理由 |
|---|---|---|
| テストが通る単位で1歩進んだ | コミット | 戻る場所ができる。1つの変更意図につき1回にすると、後で1つだけ戻せる |
| MR で人に見せたい所まで来た | push | パイプラインと AI レビューが動き、チームから見える |
| 席を離れる、その日の作業を終える | push | 手元にだけあるコミットは、PC が壊れると消え、誰にも見えない |
| 同じリポジトリで、2本目の Issue を並行したい | ワークツリーを切る | 作業ツリーが分かれ、1本目のファイルを壊さない。AI に2本目を任せ、自分は1本目のパイプラインを待てる |
| 1本の Issue だけを進める | ワークツリーは切らない | フォルダが増えるだけで得が無い |
.claude\worktrees\名前\ です。セッションをアーカイブすると、ワークツリーのフォルダも消えます。コミットしていない変更は一緒に消えるので、アーカイブの前にコミットして push します。サイクルを回す [85min]
サイクルの動き
Issue に承認のラベルが付くと、GitLab のパイプラインが拾い、AI が実装して MR を出します。人がするのは、Issue を書く、ラベルを付ける、MR を読んでマージする、の3つです。
| 順番 | 誰が | 何が起きるか | どこで見るか |
|---|---|---|---|
| 1 | 人 | Issue を書き、ラベル ai::approved を付ける | Issue |
| 2 | パイプライン(ai_pick) | 10分おきに、承認済みで未着手の Issue を拾い、ai::running を付ける | Build の Pipelines |
| 3 | パイプライン(implement-issue-番号) | 承認のラベルを確かめ直し、AI が実装してコミットする。最大30ターン、30分 | 子パイプラインのジョブのログ |
| 4 | パイプライン | push して MR を作り、Issue に ai::done とコメントを付ける。止まったら ai::stopped と理由のコメント | Issue のコメント |
| 5 | パイプライン | MR のパイプライン(構文、テスト、AI レビュー)が走る | MR の Pipelines |
| 6 | 人 | 差分と受け入れ条件を突き合わせ、マージする | MR |
| 行の頭 | 意味 |
|---|---|
AI-GATE: | 承認のラベルを確かめ直した結果。外れていれば実装しない |
ターン 7 / 費用 0.26 ドル / 終わり方 success | AI が使ったターン数と費用 |
DENIED: | AI が許可の外の操作を試み、実行の前に止められた記録 |
MR を出しました: | できた MR の URL |
AI-CYCLE: MR を出さずに止まります。 | 止まった理由。Issue のコメントにも同じ内容が残る |
ai::running に変わるのを待ちます。型に沿って Issue を書き、AI に渡す [目安 40min]
受け入れ条件を自分で書き、AI が MR を出してマージされるまでを見ます。午前と同じ大きさの Issue なので、全員が最後まで届きます。
午前に自分が取った Issue の題(「テスト:」の後ろ)と同じ行の題材を使います。ほかの人と重ならないようにするためです。
| 午前の Issue の題 | 呼び出し | 結果 | テストのファイル名 |
|---|---|---|---|
| h() が < と > をエスケープする | h('a&b') | 'a&b' | tests/phpunit/HAmpInTextTest.php |
| h() が二重引用符をエスケープする | h('<script>') | '<script>' | tests/phpunit/HScriptTagTest.php |
| h() が一重引用符をエスケープする | h('日本語') | '日本語' | tests/phpunit/HJapaneseTest.php |
| h() が & をエスケープする | h('') | '' | tests/phpunit/HEmptyStringTest.php |
| h() が null と数値を文字列にする | h(0) | '0' | tests/phpunit/HZeroTest.php |
| fmt_money() が大きい金額を3桁で区切る | fmt_money(999) | '999' | tests/phpunit/FmtMoneyNoSeparatorTest.php |
| fmt_money() が 0 を 0 と出す | fmt_money(1000) | '1,000' | tests/phpunit/FmtMoneyThousandTest.php |
| fmt_money() が負の金額に符号を付ける | fmt_money(0.4) | '0' | tests/phpunit/FmtMoneyRoundDownTest.php |
| fmt_money() が小数を丸める | fmt_money('1500') | '1,500' | tests/phpunit/FmtMoneyStringTest.php |
| estimate_status_name() が5つの状態名を返す | fmt_money(1000000) | '1,000,000' | tests/phpunit/FmtMoneyMillionTest.php |
| estimate_status_name() が知らないコードに「不明」を返す | estimate_status_name('9') | '取下げ' | tests/phpunit/EstimateStatusWithdrawnTest.php |
| estimate_status_name() が文字列のコードでも引ける | estimate_status_name(3.0) | '受注' | tests/phpunit/EstimateStatusFloatTest.php |
| stock_status_name() が5つの状態名を返す | estimate_status_name(-1) | '不明' | tests/phpunit/EstimateStatusNegativeTest.php |
| stock_status_name() が知らないコードに「不明」を返す | stock_status_name('3') | '出荷済' | tests/phpunit/StockStatusShippedTest.php |
| warehouse_name() が3つの倉庫名を返す | stock_status_name(0) | '不明' | tests/phpunit/StockStatusZeroTest.php |
| warehouse_name() が知らないコードに「不明」を返す | warehouse_name('2') | '東日本' | tests/phpunit/WarehouseStringCodeTest.php |
| next_estimate_dl_id() が E と年月と5桁の番号を返す | warehouse_name(null) | '不明' | tests/phpunit/WarehouseNullTest.php |
| db_select_one() が0件のとき false を返す | db_select('SELECT CAST(? AS text) AS v', array('abc')) | array(array('v' => 'abc')) | tests/phpunit/DbSelectPlaceholderTest.php |
- 1GitLab で Plan の Issues を開き、New issue を押します。Description の上の型の選びで
ai-taskを選びます - 2タイトルを「テスト: 呼び出しの内容」にし、型の「背景」「やること」「受け入れ条件」を埋めます。受け入れ条件には、テストのファイル名、確かめる呼び出しと結果、「変えたファイルはテストの1本だけ」、「パイプラインが緑」を1行ずつ書きます。「決まっていないこと」は空にします
- 3ラベルに
ai::approvedとlevel::1を付けて、Create issue を押します - 410分以内にラベルが
ai::runningに変わります。数分後にai::doneに変わり、「MR を出しました」のコメントが付きます - 5コメントの MR を開き、Changes の差分と自分の受け入れ条件を突き合わせます。Pipelines が緑かを見ます
- 6Mark as ready で Draft を外し(Draft でなければそのまま)、講師にマージを頼みます
- AI が作った MR がマージされ、自分の Issue が閉じた
- 自分が手を動かしたのは、Issue を書くことと、ラベルを付けることと、MR を読むことだけだった
ai::stopped になったとき
Issue のコメントに止まった理由が出ています。「受け入れ条件が決まっていない」なら、Claude が挙げた点を本文に書き足します。「承認のラベルが外れていた」なら、ラベルを付け直します。直したら、ラベル ai::stopped を外します。次の起動で拾い直します。
受け入れ条件の書き方の例(表の1行目)
自分で書いてから開いてください。
## 受け入れ条件
- tests/phpunit/HAmpInTextTest.php があり、クラス名は HAmpInTextTest
- h('a&b') が 'a&b' を返すことを確かめている
- 変えたファイルは、足したテストの1本だけ
- パイプラインの test-phpunit が緑要望メモから受け入れ条件を書く [目安 60min]
あいまいな要望を、AI に渡せる Issue に書き直します。1回目で緑にならなかったら、ログを読んで直し、流し直します。
研修リポジトリの docs/requests/ に要望メモが8本あります。ボードを見て、ほかの人が選んでいないものを1本選びます。
| ファイル | 差出人 | 件名 |
|---|---|---|
01-estimate-count.md | 営業部 | 見積一覧の件数 |
02-estimate-expired.md | 営業事務 | 見積の有効期限切れ |
03-estimate-total.md | 経理 | 見積一覧の金額の合計 |
04-estimate-date-format.md | 保守担当 | 見積日の入力の形 |
05-customer-quote.md | 営業部 | 顧客名の検索でエラー |
06-stock-serial-search.md | 倉庫 | シリアル番号での在庫の検索 |
07-stock-long-stay.md | 倉庫 | 長く置いている在庫 |
08-estimate-hide-withdrawn.md | 営業部 | 取下げの見積を一覧から外す |
Issue を書く前に、メモに次の3つを書き出します。
- 1何ができたら終わりか。画面のどこに、何が、どう出るか
- 2どんな入力で確かめるか。ふつうの場合、0件や空の場合、境目(ちょうど90日、今日と同じ日など)
- 3どのテストで確かめるか。既存のテスト(
tests/phpunit/EstimateSearchTest.php)の期待値が変わらないか
- 1型
ai-taskで Issue を書きます。「決まっていないこと」が残っていたら、そこに書きます(書いたままラベルを付けると、AI は実装せずに質問を返します) - 2ラベル
ai::approvedとlevel::2を付けて作ります - 3MR が出たら、差分と受け入れ条件を突き合わせます。パイプラインが緑か、AI レビューで P1 の指摘が0件かを見ます
- 4緑にならなかった、または止まったときは、ジョブのログと Issue のコメントを読み、直す場所を決めます。受け入れ条件が足りないなら Issue を、毎回守らせたい約束が足りないなら
CLAUDE.mdを直します。直したらai::doneかai::stoppedを外して流し直します(前の回の MR は自動で閉じます) - 5直した所とその理由を、Issue のコメントに1行で書きます
- 2回以内に、パイプラインが緑の MR が出た
- 直した所とその理由を1行で書けた
書き漏れやすい条件
| メモ | 書き漏れやすいこと |
|---|---|
| 01 件数 | 表示する場所と文言、0件のとき、検索条件を変えたとき |
| 02 期限切れ | 今日と同じ日を切れとみなすか、表示の文言と場所、状態が「受注」の見積も対象か |
| 03 合計 | 表示している行だけか、税抜か税込か、0件のとき |
| 04 見積日の形 | 通す形(YYYY-MM-DD)、存在しない日付(2026-02-30)、エラーの文言、入力を画面に残すか |
| 05 引用符 | プレースホルダで渡すこと、% や _ を入れたとき、期待する件数 |
| 06 シリアル番号 | 部分一致か前方一致か、大文字と小文字、空のとき、倉庫の絞り込みと一緒に使えるか |
| 07 90日 | ちょうど90日を含むか、何で示すか、出荷済みや廃棄も対象か |
| 08 取下げ | 見たいときの操作、既定で隠すことで既存のテストの期待値(15件)が変わること |
Community Edition に無いものをパイプラインで作る [目安 60min]
CE に無い機能を、パイプラインのジョブとフックで作ります。わざと違反したものが止まり、直すと通るところまで確かめます。
GitLab にラベル level::3 の Issue が5本あります。1本選び、assign yourself を押します。
| Issue | 作るもの | 先方のハーネスや他社で当たるもの |
|---|---|---|
| ブランチ名の検査 | ci/jobs/branch-name.yml | CE に無い push rules の代わり |
| MR の説明に Closes が無ければ落とす | ci/jobs/mr-closes.yml | Issue とマージのつながりを機械で保つ |
| AI ゲートに非常口を付ける | ci/jobs/review.yml の ai_gate | Cloudflare の break glass |
| 承認の後で本文が変わった Issue を止める | ci/ai_implement.sh | smart3pm の承認レシート(sha256) |
| PowerShell の書き込みも止めるフックを作る | .claude/hooks/guard_shell_write.ps1 | smart3pm の guard-bash-write.sh |
- 1Issue の画面の Create merge request でブランチと MR を作り、Claude Desktop でそのブランチに切り替えます
- 2自分で考える [5min]: どのジョブ(フック)が、いつ動き、何を見て、どう止めるかを3行で書きます
- 3Claude Desktop で実装し、コミットして、push します。このレベルは、サイクルに任せず自分で進めます
- 4受け入れ条件にある「わざと違反した場合」を作り、赤(または止まる)になるのを確かめます。直して緑になるのを確かめます
- 5赤と緑のパイプラインの URL を MR の説明に貼り、Draft を外して、講師にマージを頼みます
- 違反したときに止まり、直すと通る
- 2本のパイプラインの URL が MR の説明にある
任せて大丈夫な理由(GitLab) [30min]
読むだけの AI レビュー [8min]
- 1午前の自分の MR(または午後に AI が作った MR)を開き、AI レビューのコメントを読みます
- 2Commits タブを開き、コミットの数が、レビューの前と後で変わっていないことを確かめます
- 3Pipelines タブで、
ai_reviewの次にai_gateが動いていることを見ます
- AI レビューは指摘を書いただけで、コードは変えていない
- 通すかどうかの判定は
ai_gate、マージするかどうかは人、と分かれている
解説とクイズとのつながり
ai_review は読むだけのジョブです。差分を読んで指摘を書き、MR にコメントを1本置きます。コードは変えず、承認もしません。落とすかどうかは ai_gate が決め、マージするかどうかは人が決めます。Anthropic の自社の Code Review も「承認は人の判断」として、承認はしない作りです。
D2 の d2-code-reviewer も、レビュー②の前に差分を読み、根拠つきで報告するだけです。コードは直さず、次に進めるかは人が決めます。クイズの Q7 はこの形です。
手元の検査を飛ばしても、GitLab で止まる [14min]
- 1次の文を送ります。
自分の名前は GitLab のユーザー名にしますmain に切り替えて最新にしてから、練習用のブランチ p7-自分の名前 を作って切り替えてください。 - 2次の文を送り、確認のダイアログで、その1回を許可する側を選びます
CLAUDE.md の末尾に、箇条書きの行を30行足してください。 - 3次の文を送ります
CLAUDE.md の変更をコミットしてください。 - 4コミットが止まったことを確かめます。
NG CLAUDE.md: 85 lines (max 80)とThe commit was stopped.が出ます - 5Claude Desktop の統合ターミナルを開き(Ctrl +
`)、自分で次の2行を打ちますgit commit --no-verify -am "p7: 手元の検査を飛ばしたコミット" git push -u origin HEAD
- 6GitLab の Build の Pipelines で、自分の
p7-のブランチのパイプラインを開きます。check-rulesが赤になります - 7講師の画面で、同じ状態の MR ではマージのボタンが押せないことを見ます
- 8次の文を送ります
main に切り替えてください。
- 手元の pre-commit は
--no-verifyで素通りした - GitLab の
check-rulesが赤になり、MR ならマージできない
解説とクイズとのつながり
手元のフックは、その PC で動く速報です。--no-verify ひとつで飛ばせますし、飛ばしたことはサーバーからは見えません。止め金は GitLab の側に置きます。check-rules のジョブ、保護された main、「パイプラインが通らないとマージできない」の設定の3つです。
smart3pm の最後の検査は、手元で動く git の pre-push フックで、GitLab の側には検査を置いていません。人が端末で git push --no-verify を実行すると、検査を通らずに push され、後でセッションを開いたときに警告が出るだけです。クイズの Q10 はこの形です。
組織に要るパイプライン [8min]
| 種類 | いつ動く | 研修リポジトリのジョブ |
|---|---|---|
| 構文 | push と MR のたび | lint-phpdev、lint-phpver |
| ルール | push と MR のたび | check-rules |
| テスト | push と MR のたび | test、test-phpunit |
| AI レビューとゲート | MR のときだけ | ai_review、ai_gate |
| AI の実装 | スケジュールか手動(変数 AI_CYCLE=on) | ai_pick と子パイプラインの implement-issue-番号 |
| 表示 | 意味 |
|---|---|
| 緑(passed) | ジョブが終了コード 0 で終わった |
| 赤(failed) | ジョブが 0 以外で終わった。「パイプラインが通らないとマージできない」の設定で、マージが止まる |
| 黄色の注意の印(allowed to fail) | 赤だが、allow_failure: true なのでパイプラインは止めない |
| 灰色(skipped) | 前の段が赤だった、または条件(rules)に合わず動かなかった |
| 待ち(pending) | Runner の順番待ち |
パイプラインの定義の読み方は GitLab の基礎 にあります。
運用しながら直す [40min]
足りない所が出たときの進め方 [10min]
ハーネスは、動かし始めると足りない所が必ず出ます。そのたびに、次の順で直します。
- 1記録する。いつ、何が、どのツールで、止まったか(すり抜けたか)。ログの行をそのまま残す
- 2原因を4つに分ける。指示(
CLAUDE.mdや Issue の書き方)、権限(deny、ask、許可するツール)、ゲート(フック、CI)、環境(パス、文字コード、改行、版) - 3足す場所を決める。毎回守らせたいことはフックか CI、場面で要る知識は名前で指すファイルかスキル、判断は人
- 4ケースで確かめる。止まるべき入力と通るべき入力を表にし、フックの単体の試験(
.claude/hooks/tests/run_cases.ps1)に足す - 5MR で入れる。ハーネスの変更も、人が差分を読んでマージする
| 今日起きたこと | 分類 | 足した場所 | smart3pm で当たる所 |
|---|---|---|---|
| シェルで保護パスに書けた | ゲート | PreToolUse(Bash|PowerShell)のフック | guard-bash-write.sh が一覧を見ていない |
| フックの本体が無いと素通りする | 環境 | 止めるラッパー(run_hook.ps1) | 親フォルダを探すラッパー |
| 承認の後で計画が変わる | ゲート | 指紋(sha256)の照合 | verify-approval-receipt.sh |
| 手元の検査を飛ばせる | ゲート | CI のジョブと保護ブランチ | GitLab 側に検査が無い |
フックの本体が無いとき [12min]
- 1Claude Desktop の統合ターミナルで、フックの本体の名前を変えます
Rename-Item .claude\hooks\guard_paths.ps1 guard_paths.ps1.off
- 2次の文を送ります
CLAUDE.md の約束は分かったうえで、講師の判断で今回だけ許可します。ほかのファイルは調べずに、Edit で db/schema.sql の末尾に -- test の1行を足してください。 - 3書けてしまうことを確かめ、次の文を送って戻します
git restore db/schema.sql で元に戻してください。 - 4VSCode で
.claude/settings.jsonを開き、guard_paths.ps1を指している1行を、次の3行に置き換えて保存します"${CLAUDE_PROJECT_DIR}/.claude/hooks/run_hook.ps1", "-Name", "guard_paths" - 5新しいセッションを開きます(設定はセッションの開始時に読まれます)。2 と同じ文を送ります
- 6後片付けをします。ターミナルで名前を戻し、設定も戻して、もう一度新しいセッションを開きます
Rename-Item .claude\hooks\guard_paths.ps1.off guard_paths.ps1 git restore .claude/settings.json
- 2 では書けてしまった(本体が無いのに止まらない)
- 5 では
HARNESS MISSINGで止まった - 5 の新しいセッションの開始時に、
guard_paths.ps1が無いという警告を Claude が受け取っていた
解説とクイズとのつながり
設定がフックの本体を直接指していると、本体が無いときは powershell.exe が 2 以外の終了コードで終わります。Claude Code はそれを「止めない失敗」として扱い、書き込みを通します。止め金が黙って外れた状態です。
ラッパー run_hook.ps1 は、本体が無いことを見つけると終了コード 2 を返して止めます。安全装置が無いときは、作業のほうを止める(fail loud)という考え方です。
smart3pm の各ユニットの設定も、親のフォルダをたどって本体を探し、見つからなければ [ -r "$H" ] || { …; exit 2; } で止める短いコマンドになっています。クイズの Q6 はこの形です。
承認の指紋 [10min]
- 1ターミナルで、計画書の承認を記録します
powershell -NoProfile -ExecutionPolicy Bypass -File tools/new-receipt.ps1 docs/plan/issue-1-plan.md
- 2承認が有効かを確かめます。
VALIDと出ますpowershell -NoProfile -ExecutionPolicy Bypass -File tools/verify-receipt.ps1 docs/plan/issue-1-plan.md
- 3次の文を送り、確認のダイアログで、その1回を許可する側を選びます
docs/plan/issue-1-plan.md の誤字「見積一欄」を「見積一覧」に直してください。 - 42 と同じコマンドを打ちます。
INVALIDと出ます - 5次の文を送って戻し、2 のコマンドをもう一度打ちます。
VALIDに戻りますgit restore docs/plan/issue-1-plan.md で元に戻してください。
- 1文字の直しで
INVALIDになった - 元に戻すと
VALIDに戻った
解説とクイズとのつながり
承認のレシートには、承認した時点の計画書の sha256 が書かれています。sha256 はファイルの中身から計算する指紋のような値で、1文字でも変われば別の値になります。承認は「その中身」に対して出したものなので、中身が変われば失効します。誤字の直しでも同じです。
smart3pm のゲート①も、new-approval-receipt.sh が計画書の sha256 をレシートに書き、verify-approval-receipt.sh が作業フォルダの今の計画書から計算し直して比べます。コミットしていない直しでも失効します。クイズの Q8 はこの形です。
GitLab の Issue で同じことをするのが、レベル3の「承認の後で本文が変わった Issue を止める」です。
自社のハーネスを直す順番の表 [8min]
上の表と同じ列で、自社のハーネス(D2 か smart3pm)で起きていること、または起きそうなことを1行書きます。
| 起きたこと | 分類 | 足す場所 | 確かめ方 |
|---|---|---|---|
夜に回す [25min]
夜に回すときに変わる所 [8min]
| 昼(研修の午後) | 夜 | |
|---|---|---|
| 起動 | 10分おきのスケジュール | 22時のスケジュール |
| 承認 | ラベル ai::approved | 同じ |
| 上限 | 1本30ターン、30分。1回の起動で20本まで | 同じ |
| 費用 | 1本 0.26〜0.55 ドル(9/30 の実測) | 同じ。本数ぶん増える |
| 確認 | その場で MR を読む | 翌朝、MR と Issue のコメントを読む |
| 止まったとき | その場で直して流し直す | 朝に理由を読んで直し、次の夜に回す |
昼と夜で変わるのは起動のしかただけで、止め金は同じです。人がいない夜は、確認のダイアログ(ask)を出しても誰も答えないので、夜のジョブは許可の外を全部拒否する設定(dontAsk)で動かします。
止める(deny)と聞く(ask) [9min]
- 1次の文を送ります
git push --force origin HEAD を実行してください。 - 2確認のダイアログが出ずに止まったことを確かめます
- 3次の文を送ります
git push を実行してください。 - 4確認のダイアログが出たことを確かめ、許可しない側を選びます
- 5GitLab で研修リポジトリの ci/settings.ci.json を開き、
denyにBash(git push *)があることを見ます
- 1 は確認なしで止まった(deny と block-destructive のフック)
- 3 は確認のダイアログが出た(ask)
- 夜のジョブでは push そのものが deny で、push はジョブのスクリプトだけが行う
解説とクイズとのつながり
deny は、確認を出さずに実行の前に拒否します。ask は人に聞きます。研修リポジトリの手元の設定では、強制 push は deny、ふつうの push は ask です。
手動のモードでは、許可していないコマンドは ask に書かなくても確認のダイアログが出ます。それでも ask に書いておくのは、後で allow に Bash(git *) のような広い許可を足しても、push だけは必ず聞かせるためです。判定は deny、ask、allow の順で、先に当たったものが勝ちます。
夜のジョブでは、push そのものを deny にしています。人がいない時間に確認のダイアログを出しても、朝まで待ち続けるだけだからです。push はジョブのスクリプトが、AI の作業が終わった後で行います。deny は、人がいてもいなくても同じように効きます。
D2 も、settings.json の deny に git push を入れ、guard_shell_policy.ps1 でも止めています。夜に Claude が push しようとしても、実行の前に拒否されます。クイズの Q9 はこの形です。
今夜流す Issue を書く [8min]
- 1レベル1と同じ大きさで、Issue を1本書きます。題材は、午後の表の自分の行と同じ関数に、別の入力を1つ選びます(例
fmt_money(-0.4))。結果は自分で予想して受け入れ条件に書きます - 2テストのファイル名とクラス名の末尾に、自分の GitLab のユーザー名を付けます(例
tests/phpunit/FmtMoneyNightYamadaTest.php)。ほかの人の MR とファイルが重ならないようにするためです - 3ラベル
ai::approvedを付けます - 4明日の朝、Issue のコメントと、できた MR を見ます。GitLab の To-Do にも届きます
持ち帰り
自社に写すときの順番
- 1main を保護し、「パイプラインが通らないとマージできない」を入れる
- 2
workflow:rulesで、ブランチ用と MR 用のパイプラインを1本にまとめる - 3構文、ルール、テストのジョブを置く。ジョブは
ci/jobs/に種類ごとのファイルで置く - 4AI レビュー(読むだけ)とゲートを分けて置く。ゲートは最初は赤でも止めない設定から始める
- 5手元のフックを置く。書く前に止める、書いた後に検査する、本体が無ければ止める
- 6Issue の型と、承認のラベルを決める
- 7AI の実装サイクルを、昼に手で起動して試す。ログの
DENIEDと止まった理由を読んで直す - 8夜のスケジュールに載せる
.gitlab-ci.yml、ci/、.claude/、.gitlab/issue_templates/ がそのまま雛形になります。