ハンズオンガイド Day2

AIカスタム研修 / 2026年10月6日(火)

Day2 のハンズオンの手順です。午前は GitLab で1本の Issue をマージまで手で回し、午後は同じ流れを、Issue から AI が実装するサイクルとして回します。左のメニュー(狭い画面では上の[目次])から、いま進めている節に直接飛べます。

今日の地図

全体

人が手を動かす3か所

今日の研修で人が手を動かすのは3か所だけです。その間の実装、テスト、構文の検査、レビューの前さばきは、AI とパイプラインが持ちます。任せられる理由は、手元のフックと GitLab のゲートが、この3か所を飛ばして先へ進まないように止めているからです。

人 1
Issue に受け入れ条件まで書く

何ができたら終わりかを、読んだ人で結果が変わらない形で書きます。AI も同じ文を読みます。

人 2
AI に渡してよいと承認する

Issue にラベル ai::approved を付けた時点が承認です。付いていない Issue を AI は実装しません。

人 3
MR を読んでマージする

差分、パイプライン、AI レビューを見て、マージするかを人が決めます。main に直接入れる道はありません。

午前は、この流れを GitLab で1本、手で回します。午後は、同じ流れを Issue から AI が実装するサイクルとして回します。

時間割

このページで進める節

節時間成果物
準備[10min]3点のチェック
1周を手で回す(レベル1)[50min]自分の MR がマージされ、Issue が閉じている
任せて大丈夫な理由(手元)[25min]止まった画面と、止まらなかった画面を1つずつ
サイクルを回す[85min]AI が作った自分の MR
任せて大丈夫な理由(GitLab)[30min]パイプラインで止まった自分のブランチ
運用しながら直す[40min]自社のハーネスを直す順番の表
夜に回す[25min]今夜流れる自分の Issue
クイズ[15min]自分の点数
Tips:座学の時間は、このページの節に入っていません。全体の時間割は Day2 トップにあります。
区別

コミットと 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]

Step 1

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 で使います)
Tips:Issue の画面から MR を作ると、ブランチ名に Issue 番号が入り、MR の説明に Closes #番号 が入ります。マージすると Issue が自動で閉じます。MR を先に作っておくと、最初の push から MR のパイプライン1本だけが走ります。
Step 2

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 の変更が混ざらない、きれいな状態から始めるためです。

Step 3

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行ずつ読んで選びます。

プチ演習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 はこの形です。

プチ演習2

@ で取り込むファイルと、名前で指すだけのファイル [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 はこの形です。

Step 4

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 ごと、リポジトリごとなので、クローンし直したらもう一度要ります。

Step 5

テストを書かせて、コミットする [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 タブには、まだ差分が出ていない
ここが今日いちばん大事な区別です。 コミットは手元の履歴に区切りを残しただけで、GitLab には届いていません。MR に差分が出ないのはそのためです。GitLab に届けるのは次の Step の push です。
Step 6

push して、パイプラインを見る [10min]

手順
  • 1次の文を送ります。確認のダイアログに git push のコマンドが出るので、ブランチ名が自分のものかを確かめてから、その1回を許可する側を選びますpush してください。
  • 2GitLab の MR の Pipelines タブを開きます。パイプラインが1本走っています
  • 3ジョブが全部緑になるまで待ちます。3〜5分かかります。待っている間に、下の表でジョブの役割を読みます
  • 4Changes タブに、自分のテストのファイルが出ていることを確かめます。MR の Overview に、AI レビューのコメントが1本付きます
ジョブ見ていること赤のときに疑う所
lint-phpdevPHP 8.3 で構文が通るか書き間違い
lint-phpver対象の PHP 7.4 で構文が通るか、7.4 に無い関数を呼んでいないかmatch や str_contains など 8.x のもの
check-rulesCLAUDE.md の行数、1行の長さ、@ の参照先プチ演習1 を戻し忘れた
test既存の簡易テストアプリの側を変えてしまった
test-phpunitPHPUnit のテスト(自分のテストを含む)テストの期待値、PHPUnit 7.5 に無い assert(assertMatchesRegularExpression など)
ai_reviewAI が差分を読み、指摘を MR にコメントする。コードは変えないAPI キーの設定(講師が見ます)
ai_gate重い指摘があるかを判定する。今は赤でもマージは止めない設定指摘の中身をコメントで読む
終わった状態
  • パイプラインの7つのジョブが緑(ai_gate は黄色の注意の印でも可)
  • Changes タブに自分のテストのファイルがある
赤になったとき

赤のジョブをクリックすると、ログが開きます。下のほうに、何行目の何が悪いかが出ています。直し方は Claude に頼みます。ログの最後の数行をコピーして「このエラーを直して、コミットしてください」と送り、直ったら「push してください」と送ります。コミットと push が別の手順なのは、ここでも同じです。

パイプラインがなかなか始まらず「pending」のままのときは、Runner の順番待ちです。18人がほぼ同時に push しているので、数分待ちます。

Step 7

Draft を外し、マージを頼む [4min]

手順
  • 1MR の Mark as ready を押し、Draft を外します
  • 2Issue のラベルを Doing から Review に替えます。ボードで Review の列に移ります
  • 3講師がマージします。マージされると、MR の説明の Closes #番号 によって Issue が自動で閉じ、ボードの Closed の列に移ります
終わった状態
  • MR が Merged になり、Issue が閉じている
Tips:Draft の MR はマージできません。作業の途中は Draft のままにしておき、見てほしい所まで来たら Draft を外します。マージできるのは Maintainer(講師)だけです。Community Edition には「承認が何人要る」という設定が無いので、マージできる人を絞ることで、人の判断の場所を作っています。
ふりかえり

1周で起きたこと

操作起きたこと見た画面
Issue から MR を作るブランチ 番号-… と Draft の MR ができたGitLab の Issue と MR
コミット手元の履歴に残った。GitLab には何も起きていないClaude Desktop(git log)
pushGitLab に届き、MR のパイプラインが1本走ったMR の Pipelines タブ
パイプラインが緑構文、ルール、テスト、AI レビューが通ったMR の Pipelines と Overview
Draft を外すマージできる状態になったMR
マージ(講師)main に入り、Issue が閉じたボードの Closed

任せて大丈夫な理由(手元) [25min]

AI に書かせても大丈夫なのは、書く前と書いた後に、フックが見ているからです。3つ起こします。自分のレベル1のブランチのままで構いません。変えたものは、最後に必ず元に戻します。

プチ演習3

書く前に止まる [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 はこの形です。

プチ演習4

シェルの抜け道 [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 で作れます。

プチ演習5

書いた後の検査 [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 だけを進めるワークツリーは切らないフォルダが増えるだけで得が無い
Tips:Claude Desktop では、新しいセッションを始めるときにワークツリーを選べます。作られる場所は .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 ドル / 終わり方 successAI が使ったターン数と費用
DENIED:AI が許可の外の操作を試み、実行の前に止められた記録
MR を出しました:できた MR の URL
AI-CYCLE: MR を出さずに止まります。止まった理由。Issue のコメントにも同じ内容が残る
パイプラインは自分で起動しません。 スケジュールが10分おきに拾います。ラベルを付けたら、Issue のラベルが ai::running に変わるのを待ちます。
レベル1

型に沿って Issue を書き、AI に渡す [目安 40min]

目的

受け入れ条件を自分で書き、AI が MR を出してマージされるまでを見ます。午前と同じ大きさの Issue なので、全員が最後まで届きます。

題材

午前に自分が取った Issue の題(「テスト:」の後ろ)と同じ行の題材を使います。ほかの人と重ならないようにするためです。

午前の Issue の題呼び出し結果テストのファイル名
h() が < と > をエスケープするh('a&b')'a&amp;b'tests/phpunit/HAmpInTextTest.php
h() が二重引用符をエスケープするh('<script>')'&lt;script&gt;'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&amp;b' を返すことを確かめている
- 変えたファイルは、足したテストの1本だけ
- パイプラインの test-phpunit が緑
レベル2

要望メモから受け入れ条件を書く [目安 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営業部取下げの見積を一覧から外す
自分で考える [5min]

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件)が変わること
レベル3

Community Edition に無いものをパイプラインで作る [目安 60min]

目的

CE に無い機能を、パイプラインのジョブとフックで作ります。わざと違反したものが止まり、直すと通るところまで確かめます。

題材

GitLab にラベル level::3 の Issue が5本あります。1本選び、assign yourself を押します。

Issue作るもの先方のハーネスや他社で当たるもの
ブランチ名の検査ci/jobs/branch-name.ymlCE に無い push rules の代わり
MR の説明に Closes が無ければ落とすci/jobs/mr-closes.ymlIssue とマージのつながりを機械で保つ
AI ゲートに非常口を付けるci/jobs/review.yml の ai_gateCloudflare の break glass
承認の後で本文が変わった Issue を止めるci/ai_implement.shsmart3pm の承認レシート(sha256)
PowerShell の書き込みも止めるフックを作る.claude/hooks/guard_shell_write.ps1smart3pm の 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 の説明にある
Tips:ハーネスやパイプラインを変える作業は、AI のサイクルに任せきりにしません。止め金そのものを AI が書き換えると、何が止まるのかを人が説明できなくなるからです。Claude Desktop で手伝わせても、差分は人が読んで決めます。

任せて大丈夫な理由(GitLab) [30min]

プチ演習6

読むだけの 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 はこの形です。

プチ演習7

手元の検査を飛ばしても、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 側に検査が無い
プチ演習8

フックの本体が無いとき [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 はこの形です。

プチ演習9

承認の指紋 [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行書きます。

起きたこと分類足す場所確かめ方
Tips:分類に迷ったら、「人に言えば直るか(指示)」「設定で止められるか(権限)」「仕組みで止めるか(ゲート)」「置き場所や版の問題か(環境)」の順に当てはめます。

夜に回す [25min]

昼と夜

夜に回すときに変わる所 [8min]

昼(研修の午後)夜
起動10分おきのスケジュール22時のスケジュール
承認ラベル ai::approved同じ
上限1本30ターン、30分。1回の起動で20本まで同じ
費用1本 0.26〜0.55 ドル(9/30 の実測)同じ。本数ぶん増える
確認その場で MR を読む翌朝、MR と Issue のコメントを読む
止まったときその場で直して流し直す朝に理由を読んで直し、次の夜に回す

昼と夜で変わるのは起動のしかただけで、止め金は同じです。人がいない夜は、確認のダイアログ(ask)を出しても誰も答えないので、夜のジョブは許可の外を全部拒否する設定(dontAsk)で動かします。

プチ演習10

止める(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 にも届きます
Tips:夜に流すなら、受け入れ条件はとくに機械で確かめられる形にします。朝まで誰も質問に答えられないので、曖昧な Issue は質問を返して止まるだけになります。

持ち帰り

持ち帰り

自社に写すときの順番

  • 1main を保護し、「パイプラインが通らないとマージできない」を入れる
  • 2workflow:rules で、ブランチ用と MR 用のパイプラインを1本にまとめる
  • 3構文、ルール、テストのジョブを置く。ジョブは ci/jobs/ に種類ごとのファイルで置く
  • 4AI レビュー(読むだけ)とゲートを分けて置く。ゲートは最初は赤でも止めない設定から始める
  • 5手元のフックを置く。書く前に止める、書いた後に検査する、本体が無ければ止める
  • 6Issue の型と、承認のラベルを決める
  • 7AI の実装サイクルを、昼に手で起動して試す。ログの DENIED と止まった理由を読んで直す
  • 8夜のスケジュールに載せる
Tips:研修リポジトリの .gitlab-ci.yml、ci/、.claude/、.gitlab/issue_templates/ がそのまま雛形になります。
ページの先頭へ