ハンズオンガイド Day1

AIカスタム研修 / 2026年9月29日(火)

Day1 はプチ演習1〜3 とハンズオン1 です。左のメニュー(狭い画面では上の[目次])から、いま進めている演習と Step に直接飛べます。課題の正式な文面と提出先は GitLab の Issue にあり、このページはその進め方を1操作ずつ説明します。

プチ演習1 規模別レビュアーの2定義

D1-05

レビュアーの切り替え境界

目的

変更の大きさでレビュアーを当て分けられるようになります。3行の修正のために最重量のモデルを待つ状態をなくします。

触るもの.claude/agents/review-light.md.claude/agents/review-full.md.claude/settings.json。差分を作るために app/libraries/util.phpapp/controllers/stock_ctl.php を1行ずつ触ります
作るもの境界の1文を自分の言葉に書き換えた定義2本、settings.json の3行(model / effortLevel / maxEffortLevel)、記録表4行
所要[20min]
差分の箱から境界の判定の菱形に入り、review-light と review-full の2つへ分かれる図。上の箱から下の箱へ戻る細い矢印が付いている
この演習で作る分岐です。菱形で見るのは行数と触った層の2つで、どちらか一方でも重い側に振れたら下の review-full に回します。上から下へ戻る細い矢印は、軽いほうで読み始めてから境界を超えていたと分かった場合の道です。図に書いてある 50行 は配布した定義ファイルの数字なので、自社の値に書き換える前提で見てください。
準備

次の4つがそろってから手順に入ります。

  • Claude Desktop で dl-training-app(事前セットアップで取り込んだフォルダ)をプロジェクトとして開いている
  • VSCode でも同じフォルダを開き、左のエクスプローラーに app / db / tests / .claude が見えている
  • .claude/agents/review-light.mdreview-full.md の2本がある
  • VSCode の左下のブランチ名が ex/p1- で始まっている
ブランチの切り替えと、表示が食い違うときの確認

ブランチは VSCode の左下のブランチ名をクリックして切り替えます。一覧から main を選んだあと、ソース管理ペインの メニューで プル を実行し、もう一度左下をクリックして 新しいブランチの作成 を選び、ex/p1-yamada と入力します。yamada はご自身の GitLab のユーザー名に置き換えてください。

画面の表示が他の受講者と食い違うときは、版と環境変数を見ます。Claude Desktop のバージョンは設定画面で確認できます。中の Claude Code が 2.1.280 より前だと、model: opus の行き先が Opus 5.5 ではなく Opus 5 になります。maxEffortLevel は 2.1.267 で入った設定で、これより古いと settings.json に書いても効きません。あわせて環境変数 CLAUDE_CODE_EFFORT_LEVEL に値が入っていないことを確認してください。値が入っていると定義側の effort より強く、この演習の表示がそちらに固定されます。

自分で考える [3min]

手を動かす前に、次の2点をメモに書き出してください。配布した定義には 50行 という数字が入っていますが、その数字が自社に合っているかは別の話です。

  • 1軽い側と重い側を分ける境界を、自分の言葉で1行にします。行数で切るのか、触った層(DB・権限・外部連携)で切るのか、両方を使うのか。決めた理由も1行添えます。
  • 2自社のレビューで「毎回出るが毎回見送っている指摘」を1つ思い出し、軽い側の「書かないもの」に入れるかどうかを決めます。
手順
  • 1[2min] VSCode で .claude/agents/review-light.md を開き、frontmatter(先頭の --- で囲まれた部分)の model / effort / tools を見ます(sonnet / medium)。そのまま最終行にある境界の1文の前半を、自分で決めた境界に書き換えて保存します。「review-full の対象です」の文言は変えません。手順5の判定に使います。
  • 2[1min] review-full.md を開き、frontmatter が opus / xhigh になっていることを見てから、「観点2 絶対制約」の箇条書きに、自社で絶対に守らせたい制約を1行足して保存します。
  • 3[3min] .claude/settings.json を開き、{ の直後、"permissions" の前に3行を足して保存します。 { "model": "sonnet", "effortLevel": "medium", "maxEffortLevel": "xhigh", "worktree": { "baseRef": "head" }, "permissions": { "deny": [], "ask": [], "allow": [] }, "hooks": {} } worktree の3行は配布時点で入っているので、消さずに残します。保存したら、Claude Desktop のサイドバーで 新しいセッション(Ctrl+N)を開き、プロジェクトフォルダに同じ dl-training-app を選びます。settings.json はセッション開始時に読まれるので、書き換えたあとは必ずこの操作が要ります。新しいセッションの入力欄の右下に出るモデル名と effort の表示が、Sonnet と 中 になっていることを確認してください。
  • 4[3min] 小さい差分を1つ作ります。VSCode で app/libraries/util.php を開き、warehouse_name() の中で倉庫コードと名前を対応させている箇所に、既存の行と同じ書き方で倉庫コード 4九州 とする1行を足して保存します。続けて Claude Desktop のターミナルで差分をファイルに落とします。ターミナルは右上の >_ アイコン、または Ctrl+バッククォートで開きます。セッションの作業ディレクトリで開くので、フォルダを移動する必要はありません。サブエージェントは ReadGrepGlob しか持たないので、差分は自分でファイルにしてから渡します。手順1〜3で書き換えた .claude 配下の3ファイルも未コミットのまま残っているので、差分はパスで util.php だけに絞ります。
    git diff --output=small.diff -- app/libraries/util.php
    git diff --stat -- app/libraries/util.php
    --stat の行に util.php | 1 + と出て、エクスプローラーに small.diff が現れれば取れています。
  • 5[3min] Claude Desktop の入力欄に下の2行を貼って送ります。先頭の @"review-light (agent)" は、入力欄で @ を打つと出る一覧から review-light (agent) を選んでも同じです。 @"review-light (agent)" small.diff をレビューしてください。 指示は「warehouse_name に倉庫コード 4 = 九州 を足す」です。 動いている間に右上の ⋮ メニューの バックグラウンドタスク を開き、review-light の行のモデル名とトークン数を控えます。サブエージェントの effort はこの画面に出ないので、定義に書いた値を表に書きます。
    入力欄に @rev と打ち、REVIEW.md と review-full、review-light、review-other、spec-reviewer の (agent) 付き候補が出ている画面
    入力欄で @ に続けて rev まで打つと出る候補です。(agent) が付いている行がサブエージェントで、付いていない REVIEW.md はファイルの候補です。上下キーで review-light (agent) を選んで Enter を押すと、入力欄に @"review-light (agent)" が入ります。候補の並びは講師環境のもので、review-otherspec-reviewer は午後のハンズオンで使います。
  • 6[2min] 同じ small.diff@"review-full (agent)" にも渡し、バックグラウンドタスクのモデル名とトークン数を同じように控えます。
  • 7[2min] 次は重い側の担当になるものを渡します。差分は作らず、在庫の引き当てを扱う app/controllers/stock_ctl.phpassign() をそのまま軽い側に渡します。境界は行数だけでなく「DB・権限・外部連携に触る変更」も見ているので、これで差し戻されます。差し戻されたら同じ依頼を重い側に出し直し、両方のモデル名とトークン数を控えます。 @"review-light (agent)" app/controllers/stock_ctl.php の assign() をレビューしてください。 在庫の引き当てをしている箇所です。
  • 8[1min] 記録表の4行を埋め、後片付けをします。small.diff はコミットしないので、VSCode のエクスプローラーで削除します。残す変更は2つのコミットに分けます。ソース管理ペインで app/libraries/util.php だけをステージして「倉庫コード 4 を足す」でコミットし、続けて .claude 配下の3ファイルをステージして「レビュアー2本と settings の3行」でコミットします。プチ演習2 はこの2つのコミットの上から始めます。
達成状態
見る場所バックグラウンドタスクのペイン(エージェント名、モデル名、トークン数)、返答末尾の 判定: の1行
できた形小さい差分では観点2つ分の出力と 判定: の1行だけが返り、DB に触る依頼では review-full の対象です の1行が返る
  • small.diff に軽い側を当てると、判定: の1行で終わる
  • stock_ctl.phpassign() を軽い側に渡すと review-full の対象です が返る
  • バックグラウンドタスクの行に、定義した model のモデル名が出ている
  • 記録表の4行が埋まり、トークンの列に数字が入っている
  • util.php.claude 配下の3ファイルが2つのコミットに分かれ、ソース管理ペインの変更一覧が空になっている(stock_ctl.php は触っていない)

記録表です。トークンの列は、バックグラウンドタスクのペインでその行に出た数字を書きます。

| 差分 | レビュアー | モデル(ペイン) | effort(定義) | 指摘件数 | うち重大 | トークン(ペイン) |
|---|---|---|---|---|---|---|
| small.diff | review-light | | | | | |
| small.diff | review-full | | | | | |
| assign() | review-light | | | | | |
| assign() | review-full | | | | | |
Claude Desktop のバックグラウンドタスクのペイン。4本のサブエージェントごとにエージェント名、モデル名、トークン数、ツール使用回数が並んでいる
右上の ⋮ メニューの バックグラウンドタスク で開くペインです。画像は別の演習で4本を走らせたときのもので、1本ごとにエージェント名、モデル名、トークン数、ツール使用回数が並びます。review-light の行に Sonnet 5 が出ていれば、定義した frontmatter がそのまま効いています。サブエージェントの effort はこの画面に出ません。Claude Desktop の入力欄では /tasks は使えず、「isn't available in this environment」と返ります
解説と補足
レビュアーを2本に分ける理由

3行の修正にも4観点のレビューが最重量のモデルで走ると、確認の待ち時間だけが増えます。観点を絞った軽い側を前段に置き、境界を超えたものだけを重い側へ送ると、待ち時間とトークンの両方が下がります。境界の1文を配布物の数字のまま使わないでほしいのは、何行で切るのが適切かが業務ごとに違うからです。行数ではなく触った層で切ったほうが合う現場もあります。

軽い側の返答で指摘が0件なら、表を省いて 判定: 合格 の1行だけが返ります。命名やインデントの話が混ざっていたら、定義の「書かないもの」が効いていません。大きい差分で4観点のレビューが返ってきた場合は、review-light.md の最終行にある境界の1文が消えていないかを確認してください。assign()tr_stocktr_order を続けて書き換えるので、軽い側は差分の中身を読まずに手を引くのが正しい動きです。

モデルとエフォートの対応

どのモデルにどのエフォートを当てるかは、配布物のプチ演習1のフォルダにある対応表にまとめてあります。段の名前と数はツールによって違うので、自社ハーネスへ写すときはこの表を横に置いてください。

maxEffortLevel は全スコープのうち最も低い値が上限になります。ここを high のままにすると、review-full に書いた xhighhigh に丸められ、重い側が重くなりません。

トークンの数え方

サブエージェント1本ごとのトークン数は、バックグラウンドタスクのペインの行に出ます。手順6から手順8までの各回の数字を、その行から記録表に写してください。セッション全体の使用量は入力欄に /usage と送ると見られますが、こちらは累計なので1回分は引き算でしか出ません。本番の運用では軽い側と重い側をセッションの境界で切り替えますが、この演習では差を測るため、あえて1セッションで行き来させます。

つまずいたときは
症状直し方
ペインに当てたつもりと違うモデル名が出る定義の model が読まれていません。ファイル名と name が一致しているか、frontmatter が --- で閉じているかを見ます
実行が数秒で終わり、ペインを開く前に完了になる完了した分も同じペインの「完了」の欄に残ります。そこでモデル名を読み、effort は frontmatter の値を記録表に書きます
settings.json を保存しても効かないVSCode に波線が出ていないかを見ます。波線が出たまま保存すると settings.json はファイルごと無視されます
差分が文字化けして届くgit diff > small.diff の形で書き出していないかを見ます。PowerShell 5.1 の > は UTF-16 でファイルを作ります。--output= は git 自身が書くので UTF-8 になります
定義の model より環境変数が勝つCLAUDE_CODE_SUBAGENT_MODEL_FORCE1 になっていないかを見ます。1 のときだけ環境変数 CLAUDE_CODE_SUBAGENT_MODEL が定義より先に効きます
追加と考察、発展課題

軽い側と重い側で、同じ差分に対する指摘の中身がどう違ったかを1行で書いてください。件数の差よりも、重い側だけが出した指摘が「見送ってよいもの」だったかどうかを見ます。見送ってよいものばかりなら、その観点は重い側からも外せます。自社の既存のレビュー用エージェントが5行の修正に何分かけているかを思い出し、この2本に置き換えたときに減る時間を見積もってください。

発展として、3本目にテストの欠落だけを見る定義を tools: Read, Grep, Glob で書いてみてください。観点を1つに絞ると、モデルを haiku まで落としても指摘の質が落ちないことがあります。落ちる場合は、観点の書き方が曖昧だということです。Day2 のハンズオン2で test-gap-reviewer を使うので、そこで自分の版と見比べられます。

影響範囲
  • maxEffortLevel は全スコープの上限になるので、既存エージェントの effort が丸められることがあります
  • 定義の model は環境変数 CLAUDE_CODE_SUBAGENT_MODEL より優先されます。CLAUDE_CODE_SUBAGENT_MODEL_FORCE=1 のときだけ逆になります
  • 自社では、既存のレビュー用エージェントの前段に軽い側を置き、数行の修正で最重量のモデルを待たない形にします
持ち帰るものD2 での置き場所smart3pm での置き場所
review-light.md.claude/agents/ 直下。既存のレビュー用エージェントは残し、軽い変更の入口だけをこちらに向けます.claude/agents/ 直下。中身の差はありません
review-full.md同上。既存の4観点はこちらへ寄せます同上
settings.json の3行既存の settings.json に追記します同じ3行を追記します
トークンと所要の記録持ち帰り台帳.md の1行同じ

プチ演習2 巻き戻し制約の機械強制

D1-07

戻せない操作の停止

目的

AI が一度に広げた変更を、自分の手直しを巻き込まずに戻せるようになります。戻す手間そのものを減らすために、触らせる範囲を先に絞る側も設定で作ります。

触るもの.claude/settings.jsonpermissions(askdeny)、CLAUDE.md の戻し方の節。実験用に index.phpapp/libraries/util.phpapp/controllers/ 配下を動かします
作るもの確認に回す ask 3本、戻せない操作を止める deny 10本(Bash と PowerShell の5組)、AI が触った範囲だけを戻す手順1本
所要[20min]

この演習で扱うのは、AI に書かせたときにだけ起きる戻し方です。1回の依頼で複数のファイルが同時に書き換わり、そこへ自分の手直しが混ざるところが、人が1人で書いていたときとの違いになります。

ソース管理ペインの変更一覧を模した帯。AI が書き換えたファイル、自分が直したファイル、両方が混ざった1ファイルが濃淡で分かれ、右に破棄する範囲と残す範囲の括弧がある
手順5と手順6でソース管理ペインに出る一覧の形です。上の3行はファイル単位で変更を破棄できますが、点線で割れている index.php だけは1ファイルの中に AI の行と自分の行が同居しています。この1ファイルをファイル単位で破棄すると自分の行も一緒に消えるので、選択した範囲を元に戻す側の操作を使います。
範囲の事前制限、戻せない操作の停止、部分ロールバック、コミット後の revert の4箱を横一列に並べ、依頼の前と依頼の後の2本の帯で分けた図
同じ「止める」でも、効く時点が違います。左の2つは依頼を出す前に設定として置くもの、右の2つは書かれた後に人が判断して戻すものです。左へ寄せるほど戻す手間が小さくなるので、この演習では askdeny を先に作ってから、戻す手順のほうを試します。
準備
  • プチ演習1 の変更が、ソース管理ペインで2つのコミットに分かれて確定している(util.php の1行と、.claude 配下の3ファイル)
  • ソース管理ペインの変更一覧が空になっている。未確定の変更が残っていると、戻った分と残った分が混ざります
  • VSCode の左下のブランチ名が ex/p2- で始まっている。ex/p1- から続けて切ります。main には戻りません
  • Claude Desktop で同じフォルダを開いている
  • パーミッションモードが 手動 になっている。自動 のままだと確認のダイアログが出ず、手順1と手順8が成立しません。新しいセッションを開くたびに、送る前にモードを見てください
自分で考える [2min]
  • 1ファイル編集ツールで変えたファイルと、シェルのコマンドで名前を変えたファイル。セッションを巻き戻したあと、それぞれどうなっているかを予想します。
  • 2自社のリポジトリで「AI に書き換えさせたくないディレクトリ」を1つ挙げます。手順3でそこを設定に書きます。
手順
  • 1[2min] Claude Desktop の入力欄に次を貼って送り、2つの変更を続けて実行させます。1つ目はファイル編集ツール、2つ目はシェルのコマンドです。確認はどちらも、その1回を許可する側の選択肢を選びます。 index.php の先頭に「// rewind test」という行を1行足してください。 そのあとシェルのコマンドで app/libraries/util.php を util.bak にリネームしてください。 実行後、VSCode で index.php の1行目に // rewind test が入っていること、app/libraries の中が util.bak になっていることを見ます。
  • 2[2min] 入力欄に /rewind と打って送ります。メッセージの一覧が開いたら、手順1の依頼文を選びます。確認画面の「復元」を コードのみ にして 巻き戻し を押します。index.php は戻り、util.bak は残ります。セッション単位の巻き戻しが届くのは、AI がファイル編集ツールで書いた範囲だけです。
    Claude Desktop で /rewind と打って開いた巻き戻しの一覧。これまでのメッセージが上から順に並んでいる
    /rewind を送った直後の一覧です。これまでのメッセージが並び、戻したい地点の文を選びます。写っている依頼文は別のセッションのもので、演習では手順1の依頼文の行を選びます
    巻き戻しの確認画面。復元の欄で コードと会話 / 会話のみ / コードのみ の3つから選べる
    地点を選んだ後の確認画面です。「復元」の欄で コードのみ を選ぶと、会話は残してファイルだけ戻ります。変更されるファイル数と、シェルコマンドによる変更は戻らない旨がここに出ます
  • 3[1min] util.bak を元の名前に戻します。VSCode のエクスプローラーで app/libraries/util.bak を右クリックし、名前の変更util.php に戻してください。ソース管理ペインの変更一覧が空になれば片付いています。ここを飛ばすと、午後のハンズオンでテストが全部赤になります。
  • 4[2min] 人の確認に回す範囲を先に決めます。.claude/settings.json"ask": [] を次の内容に置き換えて保存します。db 配下の編集と git push が、どのモードでも確認に回ります。パスの規則は Edit(...) で書きます。Write(...) で書いたパスの規則は受け付けられても参照されません。 "ask": [ "Edit(./db/**)", "Bash(git push *)", "PowerShell(git push *)" ] VSCode で settings.jsonask に3本が並び、赤い波線が出ていないことを確認してから、Ctrl+N で新しいセッションを開いて同じフォルダを選び直します。この手順のあいだだけ、パーミッションモードを 編集を受け入れる に切り替えてください。app/libraries/util.php の末尾にコメントを1行足すよう依頼すると、確認なしで書き換わります。続けて db 配下のファイルの末尾にコメントを1行足すよう依頼すると、こちらだけ確認が出ます。確認では許可しない側を選び、2つとも見たらモードを 手動 に戻し、util.php に足された1行はソース管理ペインで 変更を破棄 します。
  • 5[4min] AI が触った範囲だけを戻します。次を送り、複数のファイルを一度に書き換えさせます。 app/libraries と app/controllers の中から3ファイルを選び、 それぞれの先頭に「// reviewed」というコメント行を1行ずつ足してください。 AI が動いている間に、ご自身で index.php の末尾に // 手直し の1行を足して保存します。終わったらソース管理ペインを開き、AI が書き換えた3ファイルの行だけを選んで 変更を破棄 を実行します。index.php の手直しは一覧に残ります。
  • 6[3min] 1つのファイルの中で混ざった場合を分離します。index.php の先頭にコメント行を足すよう AI に依頼し、実行後にソース管理ペインで index.php をクリックして差分エディタを開きます。AI が足した行だけを選択し、右クリックの 選択した範囲を元に戻す を実行してください。末尾の // 手直し は残ります。ファイル単位で破棄すると自分の1行も一緒に消えるので、ここは行単位で切ります。
  • 7[2min] 戻せない操作そのものを止めます。.claude/settings.json"deny": [] を次の内容に置き換えて保存します。denyaskallow より先に評価され、allow では deny に例外を作れません。 "deny": [ "Bash(git reset --hard *)", "PowerShell(git reset --hard *)", "Bash(git push --force *)", "PowerShell(git push --force *)", "Bash(git push -f *)", "PowerShell(git push -f *)", "Bash(git checkout -- *)", "PowerShell(git checkout -- *)", "Bash(git clean *)", "PowerShell(git clean *)" ] VSCode で settings.jsondeny に10本が並び、赤い波線が出ていないことを確認してから、Ctrl+N で新しいセッションを開いて同じフォルダを選び直します。Claude Desktop の入力欄では /permissions は使えないので、登録の確認はいつもこのファイルで行います。

    同じ禁止を Bash(...)PowerShell(...) の2行で書くのは、Windows で Git for Windows が入っていると、Claude がコマンドを打つときの主なシェルが PowerShell になるためです。このとき Claude は Bash ツールではなく PowerShell ツールを呼び、Bash(...) の規則はその呼び出しに当たりません。Bash(...) だけを書いた設定は、講師の Mac では止まって見えても、皆さんの Windows では素通りします。PowerShell(...) の規則は rmRemove-Item のような別名も同じコマンドとして照合し、大文字と小文字を区別しません。

  • 8[2min] 止まることを確かめます。次の1行をそのまま送ります。 git reset --hard HEAD~1 を実行してください。 返答に、permissions.deny の規則で拒否されて実行できなかった旨が出て、ソース管理ペインのコミットの先頭が動いていなければ合格です。確認のダイアログが出た場合は、規則が読まれていません。許可せずに手順7の確認からやり直してください。あわせて CLAUDE.md の見出し「戻し方」の箇条書きに、自社の運用に合う1行を足します。書いた場所は文脈であって設定ではありません。実際に止めているのは手順7の deny です。最後に後片付けをします。ソース管理ペインで index.php変更を破棄 を押して // 手直し の1行を消し、.claude/settings.jsonCLAUDE.md の2つをステージして「巻き戻し制約: ask と deny、戻し方の1行」でコミットします。
達成状態
見る場所VSCode のエクスプローラーとソース管理ペイン、settings.jsonaskdeny、手順8の返答、ソース管理ペインの コミット グラフの先頭
できた形AI が書いた範囲だけが戻り、自分の手直しが1行も消えていない。git reset --hard を名指しで頼んでも規則で拒否され、履歴が書き換わらない
  • コードのみ で巻き戻したあと、index.php は戻り util.bak が残ることを自分の目で見た
  • 編集を受け入れる のあいだ、app 配下は確認なしで書き換わり、db 配下だけ確認が出た
  • AI が書き換えた3ファイルだけが一覧から消え、index.php// 手直し が残っている
  • index.php の中で、AI が足した行だけが消えて自分の1行が残っている
  • 手順8の返答に permissions.deny による拒否の旨が出て、コミットの先頭が動いていない
reset --hard が permissions.deny で拒否され、revert を使う案が返ってきた画面
手順8と同じ依頼文で deny が効いたときの返答です。講師がターミナル版の Claude Code で撮ったもので、Claude Desktop では見た目が違います。見るのは、permissions.deny がブロックした旨が返答に入っていることです。返答の言い回しは毎回変わるので、判定は拒否の旨とコミットの先頭が動いていないことの2つで行ってください
解説と補足
セッションの巻き戻しで戻らない4種

チェックポイントが追いかけているのは、AI がファイル編集ツールで書いた変更だけです。次の4つは戻りません。

戻らないもの戻す手段
シェルのコマンド(Bash ツールと PowerShell ツール)で動かしたファイル(rm / mv / cp / git)ソース管理ペインの 変更を破棄、または名前を戻す
サブエージェントが書いた変更同上
自分で手編集した分、別セッションの編集同上。ここが残るからこそ、部分ロールバックが要ります
シンボリックリンクのファイル復元の対象から外れます。リンク先のファイルをソース管理ペインで戻します

手順2の確認画面には、戻るファイルの数と、シェルコマンドによる変更は戻らない旨が出ます。戻るファイルが0なら、選んだ地点が違います。

混ざる前に避ける手と、混ざった後の手

自分の手直しを先にコミットしてから AI に依頼すれば、そもそも混ざりません。これが一番安い手です。ただし、AI が動いている間に別の箇所を直したくなる場面は実際に起きるので、混ざった後の手も持っておきます。

混ざり方は2段階です。ファイルが分かれているだけなら、ソース管理ペインで行を選んで 変更を破棄 を当てれば済みます(手順5)。同じファイルの中で混ざったら、差分エディタで行を選ぶ 選択した範囲を元に戻す に落とします(手順6)。どちらも未確定の変更にだけ効きます。コミットしてしまった後は、そのコミットを revert して逆差分を足す形になります。

ファイル数での制限

askdeny で書けるのは、ツールの種類とパスの形までです。「1回の依頼で5ファイル以上を書き換えたら止める」という数での制限は、設定には書けません。数える処理が要るので、ここは PreToolUse フックの仕事になります。プチ演習3 で作るフックが、そのまま書き換えの入口になります。

範囲を絞る側と戻す側は対になっています。絞れていれば戻す手間は小さく、絞れていなければ毎回どこまでが AI の変更かを読み直すことになります。先に絞るほうが安く付きます。

コミット済みの変更を戻すとき

確定した後の戻し方は、AI が絡んでも普通の手順と同じです。公開済みのコミットは revert で逆差分を足します。GitLab の Web 画面でも、マージ済みのマージリクエストの画面から Revert が押せます。未公開の分だけを消したいときに reset --hard を使いますが、これは手順7で AI には禁じた側です。ご自身の手で打つ場合も、消したコミットを探すには git reflog が要ります。これはコマンドでしか見られないので、Claude Desktop の統合ターミナル(右上の >_ アイコン、または Ctrl+バッククォート)から打ちます。

git reflog
CLI で同じことをする場合

Claude Desktop と CLI は同じ設定ファイルを読みます。ここで書いた askdenyCLAUDE.md の1行は、どちらから使っても同じように効きます。書き分けは要りません。研修では Claude Desktop で進めます。

つまずいたときは
症状直し方
Esc を2回押しても一覧が開かないClaude Desktop では Esc 2回の開き方は効きません。入力欄に /rewind と打って送ります。ターミナル版の claude では入力欄を空にして Esc を2回押しても開きます
settings.json に赤い波線があるJSON が壊れていて、ファイルごと読まれていません。波線の箇所のカンマと括弧を直して保存し、Ctrl+N で開き直します
設定を足したのに効かないCtrl+N で新しいセッションを開き直したかを見ます。settings.json はセッション開始時に読まれます
手順4で db 配下にも確認が出ずに書き換わるパーミッションモードが 自動 になっているか、ask を足したあと開き直していません。編集を受け入れる に変え、Ctrl+N で開き直してから手順4をやり直します
手順8が拒否されずに確認のダイアログが出る許可しない側を選びます。deny が読まれていないので、settings.json の波線と、開き直したかを見ます
変更を破棄 が見当たらないソース管理ペインのファイル名の行にマウスを乗せると、右端に矢印のアイコンが出ます
復元したつもりで残っているシェルのコマンドで動かしたファイルか、シンボリックリンクです。どちらも巻き戻しの対象外なので、ソース管理ペインで戻します
追加と考察、発展課題

手順8では依頼を名指しにしました。「さっきの変更を、最後の push のところまで全部戻してください」のように曖昧に頼むと何が起きるかも試してください。AI が最初から revert を選べば何も止まらず、reset --hard を選べば規則で止まります。どちらになったかと、止まったあとに何が提案されたかを1行書いておきます。

deny はコマンドの文字列を照合しているだけです。git -C . reset --hard と書いた場合にどうなるかを1回試してください。通る場合、文章でも設定でも止まらない領域が残っていることになります。ここを塞ぐのがプチ演習3 のフックです。

発展として、手順4で書いた ask の範囲を deny に変えてみてください。確認すら出さずに止める形になります。確認を挟む範囲と、そもそも書かせない範囲の線をどこに引くかは、自社のディレクトリ構成で決まります。denyaskallow の3つを同じリポジトリで併用すると、どの操作がどの層で止まるかが1枚で見えるようになります。持ち帰るときは、この割り振り表を添えてください。

影響範囲
  • askdeny が届くのは Claude Code のツール呼び出しだけです(Claude Desktop と CLI は同じ設定を読むので、どちらでも効きます)。ご自身がターミナルに打つ git checkout -- . は止まりません。人の手元まで含めて止めたいなら、置き場所はリポジトリのブランチ保護と CI になります
  • ask を広げると確認が増えて手が止まり、狭めると範囲外まで書き換えられます。まずは戻すのが高くつくディレクトリだけに当てます
  • 部分ロールバックが効くのは未確定の変更だけです。コミットした後は revert に切り替わります
  • 既存の allow と重なっても deny が勝ちます
持ち帰るものD2 での置き場所smart3pm での置き場所
ask 3本permissions.ask。戻すのが高くつくディレクトリから書きます同じ形。パスの区切りは同じです
deny 10本既存の permissions.deny に追記します。PowerShell(...) の側を落とさないでください同じ10本。bash を正にしていても、Windows の端末で動かす人がいれば対のまま置きます
CLAUDE.md の戻し方の1行CLAUDE.md の該当節規約本体の該当節(当日確定)
部分ロールバックの手順持ち帰り台帳.md の1行同じ

プチ演習3 秘密情報の遮断とフックの単体テスト

D1-10

シェル経由の秘密情報の遮断

目的

設定では塞げない読み取りの経路をフックで止め、そのフックが動くかどうかを AI に聞かずに検査できるようになります。

触るものダミーの .env.claude/settings.json.claude/hooks/block-destructive.ps1.claude/hooks/tests/cases.json
作るもの読み取りの deny 3本、hooks.PreToolUse の登録1本(matcherBash|PowerShell)、自分で考えたテストケース1件
所要[15min]
入力欄の依頼からツールの選択、PreToolUse、Bash の実行、PostToolUse までを横一列に並べ、PreToolUse から終了コード 0 と 2 の2本へ分岐させた図
フックが走る位置です。図の Bash の箱は、Windows では PowerShell ツールにあたります。permissionsdeny はツールを選ぶ段で効くので、そこで塞ぎきれない書き方を拾うには、後段にある PreToolUse を使います。分岐の右側、終了コード 2 を返したときだけ実行が止まり、止めた理由の文がそのままモデルへ返ります。
準備
  • プチ演習2 の変更が確定し、ソース管理ペインの変更一覧が空になっている
  • VSCode の左下のブランチ名が ex/p3- で始まっている。main には戻りません
  • リポジトリ直下にダミーの .env がある。本物の接続情報は置きません
  • ソース管理ペインの一覧に .env が出ていない。.gitignore.env の行は配布時点で入っています
ダミーの .env の作り方

Claude Desktop の統合ターミナルで1行打ちます。ターミナルは右上の >_ アイコン、または Ctrl+バッククォートで開きます。Mac は echo 'DB_PASS=dummy-secret-123' > .env です。

Set-Content .env 'DB_PASS=dummy-secret-123'

.gitignore.worktreeinclude には .env の行が配布時点で入っているので、書き足す必要はありません。ソース管理ペインの一覧に .env が出なければ合っています。.env が出たままコミットすると、あとで削除しても履歴に残ります。.env.tplop:// の参照文字列しか持たないので、こちらは追跡したままにします。

自分で考える [2min]
  • 1自社で「AI に読ませたくないファイル」を3つ挙げ、そのファイル名の形を書きます。固定名か、credentials を含む名前か、拡張子で決まるかで、書く正規表現が変わります。
  • 2そのファイルに触る「止めたい形」と「止めてはいけない形」を1つずつ挙げます。手順5でテストケースに変えます。
手順
  • 1[2min] .claude/settings.jsondeny の配列の最後 "PowerShell(git clean *)" の末尾にカンマを付け、その下に読み取りの3本を足します。最後の1本にカンマは付けません。VSCode で deny に13本が並び、赤い波線が出ていないことを確認してから、保存して Ctrl+N で新しいセッションを開きます。 "Read(./.env)", "Read(./.env.*)", "Read(./**/credentials*)"
  • 2[1min] .env を読ませてみます。Read ツールが権限で拒否され、中身は返りません。 .env の中身を教えてください。
  • 3[2min] 同じセッションで、今度はシェルのコマンドで読ませます。Windows では Claude が PowerShell ツールで Get-Content .envcat .env を打ちます。確認で許可する側を選ぶと DB_PASS=dummy-secret-123 がそのまま返ります。Read(./.env)deny は PowerShell ツールの経路を塞ぎません。 cat .env の結果を貼ってください。 演習リポジトリの CLAUDE.md には「.env と認証情報を読みません」の1行があるので、コマンドを打つ前に断られることもあります。その場合は「文脈で断られた」と記録して手順4へ進みます。止めたのは CLAUDE.md の文脈で、設定ではありません。頼み方を変えれば通ることがある状態なので、手順4からのフックで止める理由は変わりません。
  • 4[2min] フックを登録します。本体の .claude/hooks/block-destructive.ps1(Mac は block-destructive.sh)は同梱済みです。VSCode で開き、規則が「patterns が全部一致し、except が1つも一致しないとき止める」の組になっていることだけ見てから、settings.json"hooks": {} を次の塊に置き換えて保存します。"hooks" のキーを2つにすると JSON が壊れ、ファイルごと無視されます。 "hooks": { "PreToolUse": [ { "matcher": "Bash|PowerShell", "hooks": [ { "type": "command", "command": "powershell.exe", "args": ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-destructive.ps1"] } ] } ] } matcher は「どのツールの直前に走らせるか」で、Bash|PowerShell は Bash ツールと PowerShell ツールの両方です。Bash だけにすると、Windows で Claude が PowerShell ツールから打ったコマンドではフックが呼ばれません。command に実行するプログラム、args に引数を分けて書く形なので、フォルダのパスに空白や日本語が入っても引用符の問題が起きません。Mac の方は "command": "bash""args": ["${CLAUDE_PROJECT_DIR}/.claude/hooks/block-destructive.sh"] の2行に差し替えます。完成形は .claude/settings.example.jsonc にあります。
  • 5[2min] AI を起動せずに1件流します。Claude Desktop の統合ターミナルで次の2行を打ちます。1行目がフックに JSON を渡す部分、2行目が終了コードを表示する部分です。2 が返れば止まっています。
    echo '{"tool_name":"PowerShell","tool_input":{"command":"Get-Content .env"}}' | powershell -NoProfile -ExecutionPolicy Bypass -File .claude\hooks\block-destructive.ps1
    $LASTEXITCODE
    Mac は echo '{"tool_name":"Bash","tool_input":{"command":"cat .env"}}' | bash .claude/hooks/block-destructive.shecho $? です。
  • 6[2min] .claude/hooks/tests/cases.json を開き、配列の最後の要素の閉じ } の後ろにカンマを付けて、自分で考えたケースを1件足します。id は他と重ならない名前にします。保存したらターミナルでランナーを回します。Mac は bash .claude/hooks/tests/run_cases.sh です。 { "id": "ps-ng-my-secret", "hook": "block-destructive", "input": { "tool_name": "PowerShell", "tool_input": { "command": "type .env" } }, "expect_exit": 2, "note": "PowerShell の type でも同じ経路" }
    powershell -NoProfile -ExecutionPolicy Bypass -File .claude\hooks\tests\run_cases.ps1
    cases.json の配列末尾に1件足す位置を示した図。最後の要素の閉じ括弧の直後にカンマを足し、新しい要素を続ける
    足す位置の図です。配列の最後にある要素の閉じ } の直後にカンマを付け、その下に1件を続けます。新しく足した要素が最後になるので、その閉じ } にはカンマを付けません。
  • 7[1min] Ctrl+N で新しいセッションを開き、手順3と同じ依頼をもう一度します。今度は止まります。登録の確認は settings.json を VSCode で開いて見ます。Claude Desktop の入力欄では /hooks は使えません。
    git reset --hard を頼んだ直後の Claude の返答。実行できないことと、block-destructive.sh が PreToolUse で止めたこと、フックが返した文言が引用されている
    フックが止めたときの Claude Desktop の返答です。講師の Mac で bash 版のフックが git reset --hard を止めた例なので、皆さんの画面ではフック名が block-destructive.ps1、理由の行が env-file になります。止めたのが規則かフックかは、この返答の文言で分かります。規則が止めたときは permissions.deny による拒否と返ります
  • 8[1min] ソース管理ペインで .claude/settings.json.claude/hooks/tests/cases.json の2つをステージし、「秘密情報の遮断: deny 3本、PreToolUse、テスト1件」でコミットします。.env が一覧に出ていないことをもう一度見てから確定してください。午後のハンズオン1 のワークツリーは、このコミットから切られます。
達成状態
見る場所$LASTEXITCODE、ランナーの PASS / FAIL 行と末尾の件数、settings.json の登録内容
できた形手で流した JSON に理由の2行と 2 が返る。ランナーが 23 cases: 23 passed, 0 failed。Claude Desktop から .env を読むコマンドが止まる
  • deny だけの状態で、シェルのコマンドから .env が読めてしまうこと、または CLAUDE.md の文脈だけで断られたことを1度見た
  • 自分で足したケースを含めてランナーが 23 cases: 23 passed, 0 failed で終わる
  • フックを登録したあと、同じ依頼が止まる
PowerShell でフックに JSON を流し、ブロックの理由と終了コード 2 が表示された画面
手順5と同じ手動検査を、講師の Mac の bash 版で git reset --hard を流した例です。command: git reset --hard の行に続いて echo "exit=$?"2 を返しています。PowerShell 版で Get-Content .env を流すと、理由の行と command: の行のあとに $LASTEXITCODE2 になります。AI を起動せずに済むので、書き換えたその場で試せます
解説と補足
手動検査とランナーの出力

手順5で出る内容です。1行目が理由、2行目が止めたコマンド、最後が終了コードです。

[block-destructive] env-file: touches a .env file. Keep secrets out of the session: wrap the single command with run-with-secrets.ps1 (op run) or read them from CI/CD variables.
command: Get-Content .env
2

0 が返っていたら、フックがペイロードを読めていません。settings.json が壊れた JSON だとファイルごと無視されるので、VSCode で開いて赤い波線が無いかを確認してください。

手順6のランナーの出力です。1行目に対象の版と件数が出て、そのあと1件1行で並びます。配布時点の22件に自分で足した1件を含めて、末尾の合計が23件になります。

target=PHP 7.4 / cases=23 / materials=v2 2026-09-17

PASS  bash-ok-run-tests  (exit 0)
PASS  bash-ok-op-run  (exit 0)
PASS  ps-ok-git-status  (exit 0)
PASS  bash-ng-reset-hard  (exit 2)
PASS  ps-ng-get-content-env  (exit 2)
PASS  ps-ng-my-secret  (exit 2)

23 cases: 23 passed, 0 failed

1件でも FAIL が出たらランナーは exit 1 を返し、そのケースの出力を続けて表示します。自分で足したケースだけが落ちる場合は、正規表現が type .env の形を拾えていません。

止める幅の決め方

.env の規則はこの形です。patterns が全部一致し、except が1つも一致しないときに止めます。

patterns = @('(^|[\s"/\\=])\.env([^A-Za-z0-9_]|$)') except = @('\.env\.tpl([^A-Za-z0-9_]|$)')

.env.tpl だけを例外にしているのは、op:// の参照文字列しか持たないファイルで、ここを止めると起動用のスクリプトが動かなくなるためです。例外はこの1本だけにしてください。広く止めれば業務が止まり、狭く止めればすり抜けます。cases.json があると、この幅を1件ずつ足しながら詰められます。

フックの例外とは別に、denyRead(./.env.*) はファイル名の照合なので .env.tpl にも当たります。AI の Read ツールでは .env.tpl も読めません。中身を確かめるときは、人が VSCode で開きます。

PowerShell で引っかかるところ
症状直し方
.ps1 cannot be loaded because running scripts is disabled on this system呼び出しに -ExecutionPolicy Bypass が付いているかを見ます。手で叩くときも同じ指定が要ります。付いているのに出る場合は、グループポリシーで実行ポリシーが固定されています。この端末では ps1 は動かないので、講師に知らせてください
フックのメッセージが化ける.ps1 は ASCII だけで書きます。日本語版 Windows の PowerShell 5.1 は、指定しなければ CP932 で読み書きします。日本語は cases.jsonnote に置けます。こちらは UTF-8 で読まれます
bash 版が素通りするjq が無いと標準エラーに1行出して通します。ガードを止めるよりセッション開始時に大きく報告する設計で、sessionstart_verify.shjq の不在を知らせます
CLI で同じことをする場合

Claude Desktop と CLI は同じ settings.json を読むので、ここで登録したフックはどちらから使っても走ります。ランナーは AI を起動しないので、結果も同じです。研修では Claude Desktop で進めます。

追加と考察、発展課題

except に入れる必要があるファイルを、ご自身の職場から1つ挙げてください。自社の hooks にテストが揃っている側と揃っていない側があるなら、揃っていない側から1本選んで、今日と同じ形のケースを足すところまでを持ち帰りの宿題にします。

発展として、matcherBash だけに戻して開き直し、手順7と同じ依頼をしてみてください。PowerShell ツールから打たれたコマンドにはフックが呼ばれず、読めてしまうはずです。確かめたら Bash|PowerShell に戻します。無人実行を設計する Day2 では、この性質が止め金の土台になります。あわせて、ブロックの理由の文面を自分のチームの言葉に書き換えてください。この文面はそのままモデルに返るので、次の手として何をすればよいかが書いてあるかどうかで、止めたあとの動きが変わります。

影響範囲
  • PreToolUsedeny と同じく、どのパーミッションモードでも走ります。except を広げると業務が止まり、狭めるとすり抜けます
  • matcherBash だけだと、Windows の PowerShell ツールには効きません
  • フックは手元で動く仕組みなので、設定を外した人には効きません。消されたら困るものは、リポジトリのブランチ保護と受け入れ時の検査で持ちます

同じフックを別の環境に写すと、黙って効かなくなることがあります。次の4つは、どれも「止まらずに通る」方向に倒れます。フックの単体テストを書く理由がここにあります。

効かなくなる原因そのときの見え方
改行が CRLF になっているフックが呼ばれず、画面に何も出ません。.gitattributes*.sh text eol=lf を書きます
実行ビットが落ちている同じく何も出ません。WSL の /mnt/c 配下では chmod +x が効かないので、フックを WSL 側に置きます
パスを絶対パスで書いている他の人の手元で保護したいパスに当たらず、通ります。リポジトリ相対に直します
判定に使うコマンドが無いjq などが無いとエラーを握りつぶして通します。コマンドの存在確認を入れます

環境は「AI ツールが動く場所」「コードが置いてある場所」「ランタイムの版」の3軸で見ます。WSL と Windows をまたぐ構成は、フックが1回動くたびにパスの変換が入るので遅くなります。判定と実行を分け、止める対象を設定ファイルに出しておくと、環境が増えても入口を1本足すだけで済みます。組み合わせ別の設計は、配布物の 50_環境別のフック設計/環境別のフック設計.md にまとめてあります。

持ち帰るものD2 での置き場所smart3pm での置き場所
読み取りの deny 3本既存の permissions.deny に追記します。Read(...) は PowerShell の Get-Content には効かないので、フックと組にします同じ3本
block-destructive.ps1hooks/ 直下。既存のフックと同じ PowerShell 5.1 と ASCII の規則に合わせます持ち込みません。bash 版を使います
block-destructive.sh持ち込みませんhooks/ 直下。既存の検査スクリプトと同じ並びに置きます
追加したテストケースhooks/tests/cases.json に1件追記しますテストの無い検査スクリプトが残っているので、同じ形の受入テストを1本足す入口にします

ハンズオン1 改修1本の AI 駆動一周と実装・レビューの分離

Issue 1本を調査から MR まで一周させ、同じ差分を3通りで読ませたときに指摘がどう変わるかを、自分の数字で言えるようになります。

目的

実装とレビューの分離

題材は演習リポジトリの Issue #1 です。見積一覧の顧客名検索で、入力値を SQL 文字列にそのまま連結している箇所を直します。

本体は Step 4 です。実装したセッションに「レビューして」と頼むのと、履歴を切った読み手に読ませるのとでは、出てくる指摘が同じになりません。この差を手元で測ってから、分離の置き方を自社ハーネスへ1本書き戻して終わります。

Tips:この演習は Claude Desktop の中の Claude Code で進めます。ファイルの編集とコミットは VSCode、Issue と MR は GitLab のブラウザ画面です。CLI でも同じことができますが、研修では Claude Desktop で進めます。
人、Claude Desktop、VSCode、手元のフォルダ、GitLab、GitLab CI の6つを矢印でつないだ図。Claude Desktop の箱の中に入力欄と統合ターミナルの2つが入っている
共通章と同じ図です。この演習から GitLab の箱を使う場面が増え、Issue を読む、MR を立てる、パイプラインの結果を見るの3つが同じブラウザの中で続きます。午前のプチ演習では push していないので、CI の箱に並ぶジョブが動くのはここからです。
達成状態

終わったかどうかの判定

上から順に、自分で確認できる形にしてあります。講師に聞かずに判定してください。

  • MR が1本あり、lint-phpdev / lint-phpver / test / test-phpunit の4つが緑になっている。途中で lint-phpver が赤になった方は、混入した構文と行が MR の説明欄に記録してある(Issue #1 の受入条件3)
  • git grep -n "customer_name" -- app/controllers/estimate_ctl.php の結果に、$_GET['customer_name']$where へ連結している行が無い
  • index()db_select 呼び出しが第2引数のパラメータ配列を取っている
  • tests/phpunit/EstimateSearchTest.php に、顧客名に ' を含む入力のテストが1本以上増えている
  • そのテストが変更前のコードでは落ちたことを、CI のログで確認した
  • MR の「AI レビュー結果」表の3行が件数と所要時間まで埋まり、4行目の ai_review が件数か「未実施」で埋まっている
  • MR の「人が判断した採否」表に全指摘が入り、直さなかったものに理由が書いてある
  • ai_gate の結果を見て、今日はそれが止める力を持っていないことを説明できる
  • 自社ハーネスに分離の置き方が1本入っている
準備

始める前にそろっている状態

開いておく画面Claude Desktop、VSCode、ブラウザで GitLab の演習プロジェクト
いるファイル手元にクローン済みの dl-training-app。読むだけのものは Issue #1、REVIEW.md.claude/agents/review-other.md
直前の演習の成果物プチ演習1〜3で .claude/settings.json に足した model、effort、ask、deny、PreToolUse フックの設定。ex/p3- のブランチにコミット済みであること。ワークツリーはこのブランチの HEAD から切られるので、コミットしていない変更は持ち込まれません
紙かメモ帳Step 1 で3つの欄を書きます
パーミッションモードClaude Desktop を 手動 にしておきます。自動 のままだと確認が出ないまま編集が進み、Step 3 で差分を読む前に変更が増えます。Step 2 でワークツリーに移ったら一度見直してください
Tips:演習リポジトリの Issue は https://gitlab-09291006aidev.give-app.net/dlive-training/dl-training-app/-/issues にあります。使うのはラベル 演習::ハンズオン1 が付いた1本だけです。
手元に PHP は入っていません。 構文チェックとテストの実行は GitLab の CI が代わりに行います。Claude Code に「php で確認して」と頼んでも実行できないので、確認はコミットして push し、パイプラインの結果を見る形になります。この演習の手順はその前提で組んであります。
全体図

8つの Step と時間の配分

Stepやること目安
導入全体図の確認と題材の共有[5min]
Step 1Issue #1 を読み、影響範囲の仮説と改修方針と検証方法を紙に書く[15min]
Step 2worktree で作業場所を隔離し、grep-scout の調査結果と仮説を突き合わせる[20min]
Step 3機能テストを先に足し、実装し、1ステップ1コミットで進める[35min]
Step 4同じ差分を3通りでレビューし、件数と所要時間を記録する[25min]
Step 5指摘を委譲判定シートに仕分け、限定修正する[15min]
Step 6MR を開き、CI の6ジョブの結果を受ける[20min]
Step 7作業指示書を生成し、自分で赤入れする[5min]
Step 8分離の置き方を自社ハーネスへ書き戻す[20min]
合計[160min]

Step 3 が一番長く、Step 4 がその次です。時間が押したときに削るのは Step 7 で、Step 4 と Step 5 は削りません。

Step 1 から Step 8 を所要時間に比例した幅の帯で並べ、各帯の下に所要と生成物を添えた図
上の表の所要を、帯の幅に置き換えたものです。遅れが出たときにどこを詰めるかは、この幅を見て決めてください。薄い帯だけが削ってよい場所で、濃い2本は最後まで残します。帯の下に書いてあるものが手元にそろっていれば、その Step は終わったものとして先へ進んで構いません。
影響範囲

この演習で触るもの

触るファイルapp/controllers/estimate_ctl.phpindex()tests/phpunit/EstimateSearchTest.php、MR の説明欄、docs/shijisho/issue-1.md
操作紙に3欄を書き、Claude Desktop のワークツリーで作業場所を分け、grep-scout に調査を委譲する。テストを先に push して赤を見てから実装し、同じ差分を3通りでレビューして4分類に仕分け、MR を仕上げて指示書を生成する
見る場所バックグラウンドタスクのモデル名、CI の test-phpunit が赤から緑に変わる履歴、lint-phpver の結果、MR の Pipelines タブに並ぶ2本のパイプライン
達成の状態「終わったかどうかの判定」の9項目。MR 1本、lint-phpver 以外の3ジョブが緑(lint-phpver は緑か、赤なら記録して残す)、テストが変更前に落ちた履歴、3通りの計測表、採否表、自社ハーネスに1本
自社での使いどころIssue から MR までの標準の流れと、実装した本人に採点させない仕組み
影響が及ぶ範囲main は保護されていて直接 push できない。worktree には .envvendor/ が入らない。date_from / date_tosql_lib.php は範囲外
この演習で使う語と、作るものの置き場所

先に押さえる語です。パイプラインは、push か MR の作成をきっかけに GitLab が .gitlab-ci.yml の内容を実行する1回分のことで、その中の1つ1つの処理(lint-phpdevtest-phpunit など)がジョブです。ジョブを実際に動かすサーバーをランナーと呼び、演習環境では受講者全員で共有しています。サブエージェントは、本体の Claude Code とは別の履歴で動き、決められたツールだけを持つ下請けの Claude です。grep-scoutreview-other がこれにあたり、入力欄で @"名前 (agent)" と書いて呼びます。REVIEW.md はリポジトリ直下にあるレビュー基準のファイルで、CI の ai_review とサブエージェントの両方がこれを読みます。

終わったときに手元に残っているものです。名前と場所をここで決めておくと、Step 8 で自社ハーネスへ移すときに探さずに済みます。

生成物名前と場所
仮説と方針のメモ紙、または docs/handson1/plan.md
影響範囲の調査結果grep-scout の出力。MR 説明の「影響範囲」欄に貼る
実装app/controllers/estimate_ctl.php
機能テストtests/phpunit/EstimateSearchTest.php に追記
3通りレビューの計測MR 説明の「AI レビュー結果」欄
委譲判定シートMR 説明の「人が判断した採否」欄
作業指示書docs/shijisho/issue-1.md
ブランチと MRStep 2 で控えた worktree- で始まるブランチから main への MR 1本

ブランチ名は Step 2 でワークツリーを作ったときに worktree- で始まる名前が自動で付きます。この資料の worktree-issue-1 は、その控えた名前に読み替えてください。ブランチ名が ex/ で始まらないのは11本の演習のうちここだけです。Step 2 の折りたたみの手順でワークツリーを使わずに進めた方は、ex/h1-<ユーザー名> になります。.claude/skills/implement-issue/SKILL.md の手順3にある ai/issue-1 は、夜間の無人実行が自分で作るときの名前です。今回は自分で起動するので使いません。

STEP 1

Issue の読み解きと、紙に書く3つの欄 [15min]

AI を起動する前に、自分で Issue を読んで結論を出します。

  • 1ブラウザで GitLab の演習プロジェクトを開き、左のメニューの Plan から Issues を選びます。一覧の #1 が今回の Issue です。
  • 2背景現状期待する振る舞い受入条件対象ファイル範囲外 の6つの見出しを読みます。このタブは Step 6 と Step 7 でもう一度使うので閉じないでください。
  • 3範囲外 の2つを声に出して確認します。date_fromdate_to の連結、app/libraries/sql_lib.php の共通処理です。
  • 4下の3つの欄を紙に書きます。書けたら伏せておき、Step 2 の終わりに開いて突き合わせます。
書くこと
影響範囲の仮説触るファイル名。そのファイルを呼んでいる場所。関係するテーブル名。今あるテストのうち、この変更で壊れる可能性があるもの
改修方針どう直すか。選ばなかった案を1つと、選ばなかった理由
検証方法誰が何を実行して、何が見えたら正しいか。Issue の受入条件6項目のうち、自分で確認できるのはどれか
GitLab の Issue #1 の画面。現状、期待する振る舞い、受入条件の見出しが並んでいる
手順1で開く画面。左の本文で「受入条件」まで下りると、機械で判定できる形の6項目が並んでいます
この Step が終わった状態
  • 3つの欄がすべて埋まっている。空欄のまま Step 2 に進まない
  • 範囲外の2つを言える
  • 受入条件6項目のうち、自分で確認できるものに印が付いている
先に自分で結論を出す理由と、受入条件の確かめ方

ここを飛ばすと、Step 2 で返ってくる調査結果が正しいのかどうかを判定できなくなります。突き合わせる相手が無いからです。改修方針の欄で案が1つしか出ていないときは、まだ方針が決まっていないことが多いです。

範囲外の2つは、Step 4 のレビューで指摘として上がってきます。そのときに「範囲外だから直さない」と判断できる状態にしておいてください。

受入条件の1番は次の1行で判定できます。Windows の PowerShell には grep が無いので、Windows と Mac のどちらでも同じ結果が出るこの形を使ってください。

git grep -n "customer_name" -- app/controllers/estimate_ctl.php

自分で確認できる条件と、CI に任せる条件を分けて書いておくと、Step 6 で結果を見る順番が決まります。

ハンズオン1 影響範囲の調査と実装

STEP 2

worktree での作業場所の隔離と、調査の委譲 [20min]

作業用のチェックアウトを別に作ってから始めます。元のフォルダには手が入らなくなるので、途中で方針を変えたときにフォルダごと捨てられます。

Tips:worktree は Claude Desktop の画面から作れます。コマンドは打ちません。
  • 1VSCode でこれまでの演習フォルダを開き、左下のブランチ名が ex/p3- で始まっていることを確かめてから、左サイドバーの枝分かれアイコン(ソース管理)を押します。変更が残っていたら、メッセージ欄に「午前の設定を持ち込む」と書いて コミット を押してください。ワークツリーはこのブランチの最後のコミットから切られるので、未コミットの変更は入りません。
  • 2Claude Desktop で新しいセッションを開き、入力欄の上の行のプロジェクトフォルダに演習フォルダ(index.php がある場所)を選びます。
  • 3同じ行のブランチ名の右にある ワークツリー にチェックを入れ、入力欄に「作業を始めます」と1行送ってセッションを開始します。名前を入れる欄はありません。
  • 4入力欄の上に出たブランチ名を、紙に控えます。worktree- で始まる名前が自動で付いています。以降、この資料の worktree-issue-1 は控えた名前に読み替えてください。
  • 5VSCode でも同じ作業場所を開きます。ファイル から フォルダーを開く を選び、演習フォルダの .claude/worktrees/ の下にできた1つのフォルダを指定してください。左下の青い帯のブランチ名が、手順4で控えた名前であることを確認します。
  • 6午前の設定が入っているかを見ます。VSCode のファイル一覧で .claude > settings.json を開き、deny の13行(プチ演習2 の10本とプチ演習3 の3本)、ask の3行、modelPreToolUse が入っているかを確認してください。deny が空だった場合は、下の折りたたみ「grep-scout が返す見出しと、worktree の注意点」の最後の2段落の手順に切り替えます。
編集するのは .claude/worktrees/ の下にできたフォルダの中だけです。 元のフォルダ側のファイルを開いて直すと、変更がワークツリーのブランチに入らず、push しても CI に届きません。開いているファイルのタブにマウスを乗せ、パスに .claude\worktrees\ が含まれていることを確認してから編集してください。下の折りたたみの手順でワークツリーを使わずに進める方は、元のフォルダで ex/h1- のブランチに乗っていることを確かめます。

調査を grep-scout に委譲します。影響範囲を調べるだけのサブエージェントで、ReadGrepGlob しか持っていません。書き換えはできない作りです。

  • 7手順3で開始した同じセッションの入力欄に、次の1行をそのまま貼って送信します。Issue の本文は丸ごと貼らないでください。
    @"grep-scout (agent)" 見積一覧の顧客名検索の条件組み立てを直します。影響範囲を調べてください
  • 8実行中に右上の ⋮ メニューの バックグラウンドタスク を開いて、モデル名が Haiku になっていることを確認します。.claude/agents/grep-scout.md の frontmatter に書いた model: haiku が効いている場所です。調査には1分から3分かかります。
  • 9返ってきた一覧と、Step 1 で紙に書いた仮説を突き合わせ、下の表を埋めます。
突き合わせ書くこと
自分だけが挙げたものgrep-scout が見落としたのか、自分の思い込みか。どちらかを判定する
grep-scout だけが挙げたもの自分が見落とした理由。ファイルを開いて事実かどうかを確かめる
両方が挙げたものそのまま MR の「影響範囲」欄へ
Tips:grep-scout の ## 同じ知識が重複している箇所 の節は必ず読んでください。今回の Issue では触らない箇所も挙がりますが、片側だけ直す事故はこの節が予防します。
重複の節に出る例

自分の仮説と突き合わせてから開いてください。見積の状態コード(作成中・提出済など)の対応表が、app/libraries/util.phpapp/controllers/estimate_ctl.phpapp/views/estimate_list.phpapp/views/estimate_detail.php の4か所にあります。Day2 のハンズオン3 では、この4か所を1つにまとめる Issue #4 を無人で流します。

この Step が終わった状態
  • ワークツリーのブランチ名を紙に控えてあり、VSCode の左下のブランチ表示がその名前になっている
  • ワークツリーの .claude/settings.json に午前の設定が入っている
  • 突き合わせの表が3行とも埋まっている
  • MR の「影響範囲」欄に貼る内容が決まっている
grep-scout が返す見出しと、worktree の注意点

grep-scout の定義は .claude/agents/grep-scout.md にあり、調べる順番と返す形がそこに書いてあります。返ってくる見出しはこの7つで、行番号のない記述は書かない決まりにしてあります。

## 対象
## 触るファイル
## 呼び出し元と呼び出し先
## データベース
## テストの有無
## 同じ知識が重複している箇所
## 確認できなかったもの

worktree は新しいチェックアウトなので、.envvendor/ は入りません。今回の演習では使わないので、このまま進めて構いません。持ち込む必要が出た場合は、プロジェクトルートに .worktreeinclude を置いて、入れたいファイルを書きます。フックを書くときの $CLAUDE_PROJECT_DIR は元のプロジェクトルートを指したままで、worktree のパスは標準入力の JSON の cwd に入ります。

分岐元は settings.jsonworktree.baseRef で決まります。既定の freshorigin/main から切るので、手元のブランチで足した設定が入りません。配布した settings.json には head が入っていて、開始したときに乗っていたブランチの HEAD から切ります。

手順6で deny が空だった場合は、ワークツリーを始めたときに ex/p3- 以外のブランチに乗っていたか、設定をコミットしていなかった可能性があります。いったんセッションをアーカイブし、元のフォルダで ex/p3- に切り替えて設定をコミットしてから、手順2からやり直してください。

それでも deny が入っていない場合は、baseRef が効かずに main から切られています。ワークツリーを使わずに進めます。セッションをアーカイブし、VSCode で元のフォルダを開いて左下のブランチ名が ex/p3-<ユーザー名> であることを確かめ、新しいブランチの作成ex/h1-<ユーザー名> を切ります。Claude Desktop は ワークツリー のチェックを入れずに、元のフォルダで新しいセッションを開きます。以降の worktree-issue-1ex/h1-<ユーザー名> に、.claude/worktrees/ の下のフォルダは元のフォルダに読み替えてください。

CLI から同じことをする場合

CLI からは、手順2と手順3を1行で済ませられます。CLI では名前を付けて起動できるので、ブランチ名は worktree-issue-1 になります。研修では Claude Desktop で進めます。設定ファイルもフックも共有なので、午前に足した denymodel はどちらで起動しても効きます。

claude --worktree issue-1
claude --worktree issue-1 で起動した直後の Claude Code の画面。作業ディレクトリが .claude/worktrees/issue-1 になっている
CLI で起動した直後の画面。上部の作業ディレクトリの表示が .claude/worktrees/issue-1 に変わっていれば隔離できています
STEP 3

先にテスト、次に実装、1ステップ1コミット [35min]

テストを先に足し、変更前のコードでそれが落ちることを CI で確認してから実装に入ります。落ちないテストは、通っても何も保証しません。

  • 1VSCode で tests/phpunit/EstimateSearchTest.php を開き、既存の5本がこの画面をどう確認しているかを読みます。$_GET に検索条件を入れ、capture_view() で出力を文字列にし、count_list_rows() で件数を数える形です。
  • 2顧客名に ' を含む入力で例外が出ず0件になるテストを1本足します。Claude Desktop に頼む場合の文面の例です。入力値と確認する件数の2か所を、自分で決めた内容に替えてください。
    tests/phpunit/EstimateSearchTest.php に、$_GET['customer_name'] に半角の引用符を含む文字列(例: オライオン'商会)を入れて index を表示し、例外が出ず count_list_rows が 0 になることを確認するテスト testAcceptsSingleQuoteInCustomerName を、既存の5本と同じ書き方で1本足してください。app/ 以下のファイルは変更しないでください
  • 3VSCode の ソース管理 ペインを開きます。変更 の一覧に tests/phpunit/EstimateSearchTest.php だけが出ていることを確認してください。ファイル名をクリックすると差分が開きます。増えたのがテスト1本だけで、既存の5本が変わっていないことを目で見ます。
  • 4ファイル名の右の + を押してステージに上げ、上のメッセージ欄に「顧客名に引用符が入る場合のテストを足す」と書いて コミット を押します。
  • 5同じペインの 変更の同期(または ブランチの発行)を押して GitLab へ送ります。初回は「このブランチを公開しますか」と聞かれるので、そのまま進めてください。ユーザー名とパスワードを聞かれたら、事前セットアップで決めた GitLab のものを入れます。
  • 6ブラウザで GitLab を開き、左メニューの Build から Pipelines を選びます。検索欄に控えたブランチ名(この資料の worktree-issue-1)を入れて絞り、Stages の丸から test-phpunit を開いて、赤になっていることと落ちた理由が SQL の構文エラーであることを確認します。
  • 7実装に入ります。Step 1 で書いた改修方針を、Claude Desktop に渡してください。渡すのは方針と範囲外の2つです。角括弧の中を自分の紙の内容で埋めます。
    app/controllers/estimate_ctl.php の index() で、customer_name を [自分の方針。例: db_select の第2引数にパラメータ配列で渡す形] に直してください。
    参考にする前例は [Step 2 の調査で確認した前例のファイルと関数] です。
    date_from と date_to の連結と、app/libraries/sql_lib.php は今回触りません。
    このプロジェクトの対象は PHP 7.4 です。CLAUDE.md の制約の表に従ってください。
    変更したらファイル名と変更した行を一覧で教えてください。コミットは私がします
  • 8ソース管理ペインで app/controllers/estimate_ctl.php をクリックし、差分を自分で読みます。方針と違う場所に手が入っていたら、その場で Claude Desktop に戻させてください。
  • 9手順4と同じ要領でステージに上げ、「見積一覧の検索条件をプレースホルダに置き換える」でコミットし、同期を押して送ります。1つの変更意図につき1コミットです。
  • 10Pipelines の一覧でいちばん上に増えた行を開き、lint-phpdevtesttest-phpunit の3つと lint-phpver が緑になることを確認します。終わるまで3分から5分かかります。lint-phpver が赤のあいだは、後ろの段の testtest-phpunit が skipped になって動きません。赤になった場合は、Step 6 の記録の表に沿って混入した構文と行を控えてから、実装したセッションに「PHP 7.4 で動く書き方に直して」と頼んで直し、もう一度コミットして同期します。Issue #1 の受入条件3 がこの扱いです。
GitLab の Branches 一覧。ex/h1-yamada、main、nightly/issue-4 の3本が並び、main に default と protected のバッジが付いている
手順5のあとで Code > Branches を開いた画面です。自分のブランチが Active branches に並びます。main には defaultprotected のバッジが付いていて、ここへ直接 push できないことがこの画面でも読めます。この画面は講師環境で撮ったもので、ブランチ名は皆さんが Step 2 で控えた名前と異なります
GitLab の Pipelines 一覧。検索欄に Branch name = ex/h1-yamada の条件が入り、merge request と branch のラベルが付いた行が並んでいる
手順6で開く Build > Pipelines の画面です。上の検索欄で Branch name = 自分のブランチ名に絞ると、他の受講者の行が消えます。左端の Status 列が結果で、青い回転が実行中、赤い × が失敗、緑のチェックが成功です。Stages 列の丸を押すとジョブの名前が出ます
Tips:一覧に pending や時計のアイコンが出ている間は、ランナーの空きを待っている状態です。全員が同じ時間帯に送るので順番待ちになります。やり直してもパイプラインが1本増えて列が長くなるだけです。待ち時間は手順7の方針の文面を書く時間に使ってください。
この Step が終わった状態
  • コミットが2本以上ある。テストのコミットが実装より前にある
  • テストだけのコミットで test-phpunit が赤になったことを見た
  • 実装後に lint-phpdevlint-phpvertesttest-phpunit が緑になっている。lint-phpver が一度赤になった方は、構文と行を控えてから直してある
  • git grep -n "customer_name" -- app/controllers/estimate_ctl.php の結果に、$_GET$where へ連結している行が無い
1ステップ1コミットにする理由と、混ざった後の戻し方

AI は1回の依頼で複数のファイルをまとめて書き換えます。そこに自分の手直しが乗ると、後から「AI が書いた部分」と「自分が直した部分」を切り分けられなくなります。コミットを分けておくと、混ざる前の状態に境界線が引かれるので、あとで AI の変更だけを戻せます。

すでに混ざってしまった場合の手当てです。コミットを丸ごと戻すのではなく、AI が触ったファイルだけを1つ前のコミットの状態へ戻します。

git log --stat -3
git checkout HEAD~1 -- app/controllers/estimate_ctl.php

1行目で、直前のコミットにどのファイルが入ったかを一覧で見ます。2行目で、そのうち戻したいファイルだけを1つ前の状態に戻します。他のファイルは触りません。戻した結果はソース管理ペインに変更として現れるので、差分を読んでからコミットしてください。

1つのファイルの中で混ざっている場合は、ソース管理ペインの行単位ステージを使います。差分の表示で、残したい行の左の余白を右クリックし、選択した範囲をステージ を選ぶと、その行だけがコミットの対象になります。自分の手直しだけを先にコミットして退避させ、残った AI の変更を 変更を破棄 で捨てる形です。

戻す手間そのものを減らすには、依頼の時点で範囲を絞るほうが効きます。手順7の文面が「触らないファイル」を名指ししているのはこのためです。

PHP 7.4 に合わせる書き方と、静的検査を通り抜けるもの

このプロジェクトの対象は PHP 7.4 です。AI は素で PHP 8.x の構文を書きたがるので、match 式、enumreadonly、名前付き引数、コンストラクタプロモーション、?-> は使いません。代替の書き方はリポジトリの CLAUDE.md の表にまとまっています。テストは既存の5本と同じ書き方にそろえてください。

php -l が通っても動かないものがあります。str_contains()str_starts_with() は PHP 8.0 で入った関数で、構文としては正しいので php -l を通ります。7.4 で実行すると Call to undefined function で落ちます。そのため lint-phpver は2段で見ています。1段目の php -l が構文を、2段目の php ci/undefined_functions.php が 7.4 の実行環境に関数があるかを function_exists() で確かめ、str_contains() はこの2段目で落ちます。構文検査だけでは対象バージョンを守りきれない、という話がここで出ます。

この時点では対象バージョンを機械で止めるフックをまだ入れていません。制約は CLAUDE.md の文章だけで守られている状態です。文章に書いた制約が数ターン後に薄れる様子は、Step 6 の lint-phpver の結果に出ます。出たら記録してください。Day2 の最初のセッションでその記録を使います。

現行環境がもっと古い場合でも、対象には移行先の版を書くほうが移行が進みます。古い版を対象にして書かせると、移行のたびに書き直しが発生するためです。

CI の出力の読み方と、赤と緑の見分け

ジョブの名前の左に緑のチェックが付いていれば成功、赤い × は失敗、橙色の ! は「失敗したが allow_failure で全体は止めない」です。ジョブを開いたログの最終行が Job succeeded なら緑、ERROR: Job failed: exit code 1 なら赤です。test-phpunit は他のジョブより2分ほど長くかかります。

手順6でこう見えていれば正しい状態です。テストが落ちる理由が「まだ直っていないから」であることを、エラー文で確認してください。落ちた理由はログのいちばん下ではなく、赤い行の直前にあります。

.....E                                                            6 / 6 (100%)

There was 1 error:

1) EstimateSearchTest::testAcceptsSingleQuoteInCustomerName
PDOException: SQLSTATE[42601]: Syntax error: 7 ERROR:  syntax error at or near "商会"
LINE 6:  WHERE e.del_flg = 0 AND c.customer_name LIKE '%オライオン'商会%'

ERRORS!
Tests: 6, Assertions: 12, Errors: 1.

手順10でこう見えていれば正しい状態です。アサーションの数は自分が足したテストの書き方で変わります。見るのは赤のほうの E が1つ出ていることと、緑のほうの OK の行です。

......                                                            6 / 6 (100%)

OK (6 tests, 13 assertions)
手が止まったときの確認先

テストが赤にならない場合は、足したテストが実際に新しい経路を通っているかを見てください。$_GET['customer_name'] に入れた文字列が ' を含んでいるかが最初の確認点です。

実装後もまだ赤い場合は、db_select() の第2引数にパラメータ配列を渡せているかを見ます。app/controllers/stock_ctl.phpindex() に、同じライブラリをプレースホルダで使っている前例があります。

同期が拒否された場合は、main に対して送っていないかを確認してください。main は保護されていて直接 push できません。pre-receive hook declined と出るのがこの状態です。VSCode 左下のブランチ表示が Step 2 で控えた名前であることを見てから、もう一度押してください。

Authentication failed と出た場合はパスワードが違います。何度も続くときは、事前セットアップの FAQ にある資格情報マネージャーの手順で、記憶された古いパスワードを消してください。

手順6で test-phpunit が緑になってしまった場合は、テストが worktree 側ではなく元のフォルダ側のファイルに書かれています。VSCode のタブのパスに .claude\worktrees\ が含まれているかを見てください。

ハンズオン1 3通りのレビューと採否

STEP 4

3通りのレビューと記録 [25min]

ここがこの演習の本体です。差分は1つ、読ませ方は3つ。読み手から情報を1つずつ外していき、指摘がどう変わるかを自分の数字で見ます。

読ませ方読み手が持っているもの1つ前から外したもの
1 実装したセッションでそのまま依頼実装の履歴、REVIEW.md、役割の指定
2 review-other サブエージェントREVIEW.md、定義ファイルで固定した役割と観点実装の履歴
3 履歴を切った新しい会話差分ファイルと REVIEW.md だけ役割と観点の定義
4 CI の ai_review差分と REVIEW.md。人の言い方が一切入らない。結果は Step 6 で受ける人がその場で足す言葉

1から2で履歴が外れ、2から3で役割の定義が外れます。どちらの段で指摘が変わったかで、効いていたのが履歴なのか定義なのかが分かれます。

review-input.diff から4つの経路が縦に並び、右に履歴・役割の定義・人の言葉の3列のチェック表を添えた図。下に行くほど丸が減っていく
この Step の全体像です。同じ review-input.diff を4つの経路で読ませます。1の実装したセッションそのまま依頼だけが履歴を持っていて、自分の変更を甘く見ます。2の review-other サブエージェントは定義ファイルで役割を固定した読み手、3の履歴を切った新しい会話は差分だけを渡した素の状態、4は runner 上で動く CI の ai_review です。3通りの結果は ho1-reviews.md に貼って比べます。4は Step 6 で受けます。右の表は、その4つがそれぞれ何を持って差分を読むかです
  • 11通り目。実装したセッションでそのまま頼みます。新しい会話にせず、続けてください。
    この変更をレビューしてください。REVIEW.md の基準で、重大な順に最大8件です
  • 2差分をファイルに出します。ここはコマンドを使います。サブエージェントは ReadGrepGlob しか持たず、自分で差分を取れないためです。Claude Desktop の右上の >_ アイコン、または Ctrl+バッククォートで統合ターミナルを開きます。セッションと同じ場所、つまりワークツリーの中で開くので、移動は要りません。yamada はご自身のユーザー名に置き換えます。
    git diff ex/p3-yamada --output=review-input.diff
    比べる相手は main ではなく、ワークツリーを切った元の ex/p3- のブランチです。main と比べると、プチ演習1〜3 で足した設定の変更まで差分に入ります。
  • 32通り目。実装の経緯を渡さずに、定義ファイルで役割を固定したサブエージェントへ読ませます。
    @"review-other (agent)" review-input.diff を、同僚が書いたコードとして読んでください。差分に出ていない前提はリポジトリのファイルで裏を取ってください
    結果は「重大度、場所、内容、根拠、直し方」の5列の表と、最後の 判定: 合格判定: 要修正 の1行で返ります。表の行数が指摘件数です。
  • 43通り目。Ctrl+N で新しいセッションを開き、プロジェクトフォルダに演習フォルダの .claude/worktrees/ の下の自分のフォルダを選びます。ワークツリーのチェックは入れません。初めて開くフォルダなので、ワークスペースの信頼を聞かれたら ワークスペースを信頼 を押します。押さないとフックと deny が読まれません。役割の定義は渡さず、差分ファイルと基準だけを渡してください。
    review-input.diff と REVIEW.md を読み、重大な順に最大8件で指摘してください。重大度、場所、内容、根拠、直し方の5列の表で返してください
    2通り目と同じ形で返るように、返し方だけを指定しています。役割も観点も与えていないので、2通り目と出方が変わります。
  • 53通りの出力を、VSCode で新しいファイルに貼り付けて残します(保存先は演習フォルダの外、たとえばデスクトップに ho1-reviews.md)。Step 6 で MR に貼ります。
  • 6review-input.diff を削除します。ソース管理ペインの 変更 にこのファイルが残っていないことまで見てください。
  • 7下の表の1行目から3行目を埋めます。開始と終了の時刻を控えて所要時間も入れてください。4行目は Step 6 で CI の結果を受けてから足します。
読ませ方指摘件数P0/P1 の件数所要時間気づいた差
1 実装したセッションでそのまま依頼
2 review-other サブエージェント
3 履歴を切った新しい会話
4 CI の ai_reviewStep 6 で埋めます
件数の増減で良し悪しを決めないでください。 見るのは「実装したセッションが出さなかった指摘が何か」です。自分で書いたコードを自分で採点すると検出が落ちることは測られています。同じモデルでも、履歴を捨てた別セッションで読ませたほうが F1 が 28.6、自己レビューは 24.6 でした。自己レビューを2回重ねても、1回目との差は有意ではありませんでした(arXiv 2603.12123)。効いているのは履歴を切ったことです。
この Step が終わった状態
  • 表の1行目から3行目が埋まっている。所要時間の列も埋まっている
  • 「気づいた差」の列に、件数以外のことが1行書いてある
  • 3通りの出力をファイルに貼って残してある
  • review-input.diff を消している。ソース管理ペインの変更一覧に出ていない
基準が空のままレビューさせる意味と、JSON で受け取る形

REVIEW.md は今日の時点では見出しだけの空の雛形です。自社版を起案するのは Day2 です。基準が空でもレビューは返りますが、返り方が基準のある場合とどう違うかを「気づいた差」の列に書いておくと、Day2 の起案の材料になります。

件数を機械で数えたい場合は、JSON スキーマを指定して受け取る形があります。パイプで渡す形は Claude Desktop の入力欄からは実行できないので、統合ターミナルを開いて CLI から打ちます。研修では使いませんが、自社で集計に載せるときはこちらが楽です。

git diff ex/p3-yamada | claude -p --output-format json --json-schema ((Get-Content .claude\review.schema.json -Raw -Encoding UTF8) -replace '"','\"') "REVIEW.md の基準でレビューし、findings を返す"

--json-schema にはファイル名ではなく、スキーマの中身を文字列で渡します。PowerShell 5.1 は引数の中の " をそのまま渡さないので、-replace\" に置き換えています。Mac では --json-schema "$(cat .claude/review.schema.json)" です。

findings は返ってきた JSON の structured_output に入ります。形はこう固定されるので、Step 5 の仕分けにそのまま使えます。

{
  "summary": "見積一覧の検索条件の変更を読み、3件見つけました。",
  "findings": [
    {
      "severity": "P1",
      "confidence": 0.9,
      "file": "app/controllers/estimate_ctl.php",
      "line": 22,
      "message": "date_from が文字列のまま SQL に連結されています。db_select の第2引数へ移してください。",
      "category": "sql-injection"
    }
  ]
}

Windows の PowerShell では、先に次の1行を打って文字コードを UTF-8 にそろえないと、日本語が ? に化けて渡ります。

$OutputEncoding = [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
STEP 5

委譲判定シートへの仕分けと限定修正 [15min]

集まった指摘を4つに分けます。分けるのは人です。指摘を全部受け入れると、意図してそうしていた設計が巻き戻ります。

見る点軽い側重い側
可逆性直して外したとき、何を戻せば元に戻るかコードを戻せば元に戻るDB、外部連携、権限に届く
機械検証直ったかどうかを機械が判定できるかlint かテストで差が出る目で見ないと判定できない
影響範囲触る画面の数1画面2画面以上、または共通処理
分類この分類にする条件この場でやること
即修正3軸すべてが軽い側直す。1件1コミット
要調査機械検証が重い側。正しいかどうかが読んだだけでは決まらない誰が何を調べるかを書く。直さない
将来課題Issue の範囲外Issue を切る。番号を採否の表に書く
意図した設計可逆性が重い側、または背景があって今の形にしている直さない。理由を書く
指摘1件が可逆性、機械検証、影響範囲の3つの菱形を上から順に通り、意図した設計・要調査・将来課題・即修正の4つの出口に分かれる図
上の表を、判定の順番として描いたものです。菱形は上から順に通し、どれか1つで右へ抜けた時点でその行の分類が決まります。菱形の横に書いてあるのは右へ抜ける側の条件で、3つとも抜けずに下まで来たものだけが即修正です。出口の箱の下の1行が、その分類でその場にやることです。
  • 13通りで出た指摘を1つの表にまとめます。同じ指摘が複数のレビューから出ている場合は1行にし、どのレビューから出たかを残します。
  • 2各行に3軸を付けます。付けられない行は、その場でファイルを開いて確かめてください。
  • 33軸から分類を決めます。分類から先に決めると、感覚で仕分けることになります。
  • 4date_fromdate_to の指摘は範囲外なので将来課題です。GitLab の Plan > Issues から右上の New issue を押し、Title に「見積一覧の見積日の検索条件の組み立て」のように対象が分かる1行を書きます。
  • 5Description には Issue #1 と同じ6つの見出しのうち、少なくとも 現状範囲外 の2つを書いて Create issue を押します。作成後のページの見出しの # の後ろの数字が Issue の番号です。
  • 6「即修正」に入れたものだけを直します。1件1コミットで、コミットメッセージは「レビュー指摘: 直した内容を1行で」の形にします。ソース管理ペインからコミットまで行い、同期はまだ押しません。送るのは Step 6 の手順1でまとめてです。
  • 7直したら、その指摘を出した読ませ方でもう一度レビューし、その指摘が消えたことだけを確認します。新しく出てきた P2 以下は追いません。
GitLab の New Issue 画面。Title (required) の入力欄、Description の上の Choose a template、左下の Create Issue ボタンがある
手順4で New issue を押したあとの画面です。Title (required) に対象が分かる1行、Description現状範囲外 を書き、左下の Create Issue を押します。作成後に開くページの見出しに出る # の後ろの数字が Issue の番号なので、そこで控えてください。他の受講者も同じ Issue を切るので、番号は人によって違います
この Step が終わった状態
  • すべての指摘が4分類のどれかに入っている。空欄が無い
  • 「将来課題」にしたものは Issue の番号が書いてある
  • 「意図した設計」にしたものは理由が書いてある
  • 直したのは「即修正」だけになっている
「意図した設計」に入れた指摘の扱い

AI は業務の背景を知りません。「意図した設計」に入れた指摘は、次の改修でも同じ形で出ます。同じ判断を毎回やり直さずに済ませるには、理由を REVIEW.mdDo not report に移すかどうかをその場で検討してください。Day2 のレビュー基準の起案で、この判断が材料になります。

ハンズオン1 MR と CI の結果

STEP 6

MR と6ジョブの読み方 [20min]

MR を GitLab の画面で作ります。MR ができると、push のたびに動く lint と test の4ジョブに加えて、MR 用のパイプラインで AI レビューの2ジョブが動きます。合流前の最終判定をここで受けます。

  • 1VSCode のソース管理ペインで 変更の同期 を押し、Step 5 の修正を送ります。即修正が0件で新しいコミットが無い場合は押さずに次へ進んでください。
  • 2ブラウザで GitLab の左メニュー Code > Merge requests を開き、右上の New merge request を押します。
  • 3Source branch に Step 2 で控えたブランチ(この資料の worktree-issue-1)、Target branchmain を選び、Compare branches and continue を押します。
  • 4次の画面で Title を「Issue #1 見積一覧の検索条件をプレースホルダに置き換える」に書き換えます。
  • 5Description には、リポジトリの既定のテンプレート(Default.md)の節が最初から入っています。「変更概要」に何をなぜ変えたかを2行と Closes #1、「影響範囲」に Step 2 の突き合わせ結果、「テスト」に Step 3 で見た CI の結果、「AI レビュー結果」に Step 4 の表の4行、「観点別レビュー結果」に Step 4 で review-other が返した指摘、「人が判断した採否」に Step 5 の仕分けを書きます。観点別の3体(security / spec / test-gap)を当てるのは Day2 なので、今日は review-other の分だけで足ります。
  • 6Preview タブで表の罫線が崩れていないかを見てから Create merge request を押します。
  • 7MR のページ上部の Pipelines タブを開きます。いちばん上の2本が最新で、merge request のラベルが付いた行が MR 用(ai_reviewai_gate)、branch のラベルが付いた行がブランチ用(残りの4つ)です。合わせて6つのジョブの結果を確認してください。
  • 8Changes タブを開き、Step 4 で読ませた review-input.diff の変更が含まれているかを見ます。MR の Changes と CI の ai_reviewmain との差なので、プチ演習1〜3 で足した .claude 配下の設定や util.php の1行も一緒に出ます。Step 4 の3通りより読む量が多いことを踏まえて、4行目の件数を比べてください。
nightly_implement は押さないでください。 手動ジョブなので6つに数えません。
ジョブ見ているもの落ちたときにすること
lint-phpdev現行環境(PHP 8.3)で構文が通るかエラー行を直す
lint-phpver対象の PHP 7.4 で動くか。1段目の php -lmatch 式や enum のような 8.x の構文を、2段目の php ci/undefined_functions.phpstr_contains() のような 7.4 に無い関数の呼び出しを落とす混入した構文と行を MR の説明欄に記録してから直す(Issue #1 の受入条件3。記録は Day2 の題材になります)
test簡易ランナー11件落ちたケースの名前から該当箇所を探す
test-phpunit機能テスト。対象と同じ PHP 7.4 で回る手元では再現できないので、ジョブのログを読む
ai_review差分を読み、findings.json を作って MR にコメントを1本置く。落とす判断はしない落ちるのは応答がスキーマの形にならなかったときだけ
ai_gatefindings.json に P0 か P1 があれば exit 1今日は allow_failure が付いている。赤くても止まらない

ai_review が動くと、MR の Overview タブの下のほうに「AIレビュー(モデル: ... / 深さ: ... / 差分 ... 行)」で始まるコメントが1本付きます。見出し行の件数を表の4行目に写してください。所要時間はジョブの画面右側の Duration を使います。

ここで一度立ち止まってください。ai_gateallow_failure: true なので、赤くなってもパイプライン全体は赤になりません。ゲートが赤いのにマージできる状態は、ゲートではありません。列挙しているだけです。この1行を外してゲートを赤にするのが Day2 のハンズオン2です。Merge ボタンまで止めるには、プロジェクトで Pipelines must succeed を有効にしておく必要があります。演習のプロジェクトはこの設定を入れていないので、ボタンは押せますが押しません。

lint-phpver が落ちた方は、直す前に記録してください。記録先は MR の説明欄です。落ちた構文は Day2 の最初のセッションでそのまま教材になります。

記録すること書き方
落ちた行ファイル名と行番号
出たエラー文ログに出たエラーの1行目を、そのまま貼る
混入した構文match のように、どの構文が入ったか
指示のどこに書いてあったかCLAUDE.md に書いてあったのに混入したのか、書いていなかったのか
GitLab の Merge requests 一覧。Open タブに MR が2本並び、右上に New merge request のボタンがある
手順2で開く Code > Merge requests の一覧です。右上の New merge request から作ります。作成後は、Open タブにソースブランチが自分のもので作成者が自分の名前になっている行が並び、行の左下に !1 のような MR の番号が出ます。この画面は講師環境のものです
GitLab の New merge request 画面。左に Source branch の選択、右に Target branch の main、下に Compare branches and continue のボタンがある
手順3の画面です。左の Source branchSelect source branchworktree-issue-1 を選び、右の Target branchmain になっていることを見てから、左下の Compare branches and continue を押します
MR の編集画面。Title の入力欄、Description の上の Choose a template、本文に変更概要と影響範囲の見出しと表が入っている
手順4から手順6で使う画面です。上の Title (required) を書き換えます。Description には「変更概要」「影響範囲」から始まる節の見出しと表が入っています。斜体の1行が各節に何を書くかの案内です
GitLab の MR の Changes タブ。estimate_ctl.php の差分が表示され、消した行が赤、足した行が緑で塗られている
手順8で開く Changes タブです。左の列に変更したファイルの一覧、右上に 2 files +14 -2 のようにファイル数と足した行数、消した行数が出ます。差分の左の2列の数字が変更前と変更後の行番号で、赤い行が消した行、緑の行が足した行です
同じコミットに対して走る2本のパイプラインの図。上がブランチ用で lint-phpdev、lint-phpver、test、test-phpunit と手動の nightly_implement、下が MR 用で ai_review と ai_gate
手順7で見る2本の関係です。上のブランチ用は同期のたびに走り、lint-phpdevlint-phpver のあとに testtest-phpunit が動きます。点線の先の nightly_implement は手動なので押しません。下の MR 用は MR がある間の push のたびに走り、ai_review のあとに ai_gateGATE_LEVEL 以上の指摘があると落ちます
MR の Overview タブに付いた ai_review_bot のコメント。モデル、深さ、差分の行数の見出しと、P0 から P3 の件数、指摘の箇条書きが並んでいる
Overview タブで読む ai_review_bot のコメントです。1行目に「AIレビュー(モデル: ... / 深さ: ... / 差分 ... 行)」、次に要約、その下に P0 0 件 / P1 2 件 / P2 3 件 / P3 1 件 のような件数の行が出るので、これを表の4行目に写します。この画面は差分 146 行で claude-opus-5 が6件指摘した例です。差分が 43 行だと sonnet に切り替わり、指摘の件数は減ります
ai_review ジョブの画面。Passed の表示と、ログの最後に findings.json に保存し MR コメントに投稿した旨の行と Job succeeded、右側に Duration 15 seconds
所要時間を取るジョブの画面です。MR の Pipelines タブから ai_review を開くと、右側の Duration に秒数が出ます(この例は 15 seconds)。ログの bash ci/ai_review.sh の次の行に「レビュー結果を findings.json に保存し、MRコメントに投稿しました。」と出ていれば、コメントが付いています
パイプラインの詳細画面。上段に review、gate、implement の3ステージ、下段に lint、test、implement の3ステージが並び、ai_review と ai_gate を含むジョブが緑になっている
手順7でパイプラインの番号を押すと開く詳細画面を、MR 用(上)とブランチ用(下)の2本で並べたものです。上は review、gate の順に ai_reviewai_gate が並び、見出しの下に merge request のラベルと Related merge request !1 の行が出ます。下は lint、test の順に4ジョブが並びます
lint-phpver ジョブのログ画面。PHP Parse error の行が赤で表示されている
落ちたときのログ。Errors parsing の行でファイル名を確認し、その1つ上の行のエラー文を記録してください
この Step が終わった状態
  • MR が1本あり、lint-phpdevlint-phpvertesttest-phpunit の4つが緑。途中で lint-phpver が赤になった方は、その記録が MR の説明欄に残っている
  • MR 説明の節が埋まっている。「AI レビュー結果」の4行目は、ai_review が動かなかった場合は「未実施」と書いてある
  • ai_gate の赤に関係なく Merge ボタンが押せる状態で出ていることを確認した(押しません)
  • lint-phpver が落ちた場合、4項目の記録が残っている
ai_gate と lint-phpver の出力の読み方

ai_gate の出力はこう見えます。allow_failure が付いているため、赤くてもパイプライン全体は赤になりません。

しきい値 P1 以上、確信度 0.0 以上の指摘を数えます。
confidence の無い指摘は 1.0、P0〜P3 以外の severity は P0 として数えます。
P1: 1 件 / P2: 2 件

ゲートで止めた指摘 1 件
  [P1] app/controllers/estimate_ctl.php:22 date_from が文字列のまま SQL に連結されています。

直すか、直さない理由をMRのコメントに書いてから再実行してください。

lint-phpver は2段で見ます。1段目の php -l で落ちたときは、ログに PHP Parse error で始まる行と、Errors parsing に続くファイル名の行が出ます。エラーの文面は PHP の版で変わるので、出た1行目をそのまま記録してください。

2段目は php ci/undefined_functions.php です。str_contains()str_starts_with() のような PHP 8.0 で入った関数の呼び出しは、構文としては正しいので1段目を通ります。2段目が 7.4 の実行環境に function_exists() で聞き直し、無ければ次のように落とします。

undefined_functions: PHP 7.4 に無い関数を呼んでいます。php -l は通りますが実行時に落ちます。

この行の下に、ファイル名と行番号と関数名が1件1行で並びます。手元で同じものを編集の直後に止めるのが、Day2 のプチ演習4 で登録する check-phpver フックです。

ハンズオン1 持ち帰り物と書き戻し

STEP 7

作業指示書の生成と赤入れ [5min]

実装が終わった後に指示書を作るのは順番が逆ですが、生成したものと自分が書いたものの差を見るのが目的です。

  • 1Step 1 で開いたブラウザの Issue #1 を表示し、本文(背景から範囲外まで)を範囲選択してコピーします。
  • 2Claude Desktop の入力欄に /shijisho 1 と打ち、Shift+Enter で改行してから、手順1でコピーした Issue 本文を続けて貼り付けて送信します。Enter だけを押すと、本文を貼る前に送られます。途中で grep-scout が再び呼ばれるので、2分から3分かかります。glabcurl で Issue を取りに行く実行確認が出たら、許可しない側を選んでください。本文は貼ってあるので、取りに行く必要はありません。
  • 3VSCode で docs/shijisho/issue-1.md を開き、5項目(目的、影響範囲、変更方針、検証、戻し方)で止まっているか確認します。
  • 4「目的」と「戻し方」の2つを、自分の言葉に直します。この2つは、Issue の本文を言い換えただけになりやすい箇所です。
  • 5ソース管理ペインでステージに上げ、「Issue #1 の作業指示書を足す」でコミットし、同期を押して送ります。worktree を片付けたときに消えないよう、MR に載せておきます。
この Step が終わった状態
  • docs/shijisho/issue-1.md がある
  • 「目的」と「戻し方」に自分の言葉が入っている
Issue 本文を手で渡す理由と、直した箇所の読み方

/shijisho.claude/skills/shijisho/SKILL.md に書かれた手順を Claude Code に実行させるスキルです。この skill は本来 Issue の本文を GitLab から取りに行きますが、受講者の端末には glab もアクセストークンも入っていないので、最初から本文を一緒に渡します。

直した箇所が「目的」と「戻し方」に集中していたら、生成物の使いどころが見えたことになります。影響範囲と検証は grep-scout と CI の出力から機械的に埋まるので、人が書き直す必要はほとんど出ません。

STEP 8

自社ハーネスへの書き戻し [20min]

今日のうちに1本だけ自社ハーネスへ移します。移すのは「実装した本人に採点させない」を仕組みにするものです。2つ用意してありますが、入れるのは片方だけにしてください。

選ぶもの向いている場合入れるファイル
履歴を切るサブエージェント手元でレビューを回したい。端末ごとの環境差を減らしたい.claude/agents/review-other.md
CI の AI レビュージョブレビューを MR に残したい。全員の端末に同じ設定を配りたくないci/ai_review.sh.gitlab-ci.yml の review ステージ
  • 12つのファイルを開いて、どちらを自社に置くか決めます。決め手は、レビューを人の手元でやるか MR に置くかです。
  • 2選んだ1本を自分のハーネスに写します。下の表の置き場所を見てください。
  • 3自社のリポジトリ名、ブランチ名、レビュー基準のファイル名に書き換えます。演習リポジトリの名前がそのまま残っていると、次に使う人が混乱します。
  • 4持ち帰り台帳の本日分に、今日足した1本を記入します。
ファイルD2 での置き場所smart3pm での置き場所
.claude/agents/review-other.md既存のエージェント定義と同じ階層。効いているのは履歴を切ることなので、model: は実装側と同じままで構いません。変えるのは発展課題2 と同じ任意の実験です同じ階層に同じ名前で置く
ci/ai_review.shCI 設定と同じ階層。CI の runner(alpine)の上の bash で動くので、PowerShell へ書き直す必要はありません同じ階層に同じ名前で置く。コマンドはそのまま使える
3通りレビューの計測表持ち帰り台帳に貼る持ち帰り台帳に貼る
委譲判定シート(4分類×3軸)レビュー基準のファイルの近くに置く規約本体のどのタグに属するかを決めてから足す
MR テンプレの「AI レビュー結果」欄.gitlab/merge_request_templates/同じ
worktree 運用手順(2行)戻し方の節の近くに追記規約本体の該当節に追記
この Step が終わった状態
  • 自社ハーネスに1本入っている。2本入れていない
  • 演習リポジトリの名前が残っていない
  • 持ち帰り台帳の本日分に1行増えている
2つの環境で書き方が割れる箇所

ファイルの名前と中身は写しますが、本文のコマンド例は自社の形に直してください。D2 は PowerShell 5.1 で、スクリプトとメッセージを ASCII に保つ約束になっていると伺っています。smart3pm は bash が正です。

同じ目的のものが2つの書き方で並ぶことになるので、どちらを正にするかを決めておくと、次に足す人が迷いません。決められない場合は、決められないこと自体を台帳に書いてください。Day2 の最後にこの判断を扱います。

追加と考察

終わった後に1行ずつ書くこと

次の3つを、持ち帰り台帳の余白に1行ずつ書いてください。Day2 の冒頭で使います。

問い書き方
履歴を切った読み手だけが出した指摘件数ではなく、指摘の中身を1つ書く。何も無かった場合は「無し」と書く
grep-scout と自分の仮説の差の出どころ自分が見落とした理由を、コードの読み方の話として書く
今日の指摘のうち機械で判定できた割合4分類のうち「即修正」に入れた件数を、指摘の総数で割る。この数字が Day2 のレビュー基準の起案の入り口になる
発展課題

手が空いた方が進めるもの

上から順に、効き方が大きい順です。全部やる必要はありません。

  • 1Step 5 で「将来課題」にした date_fromdate_to の Issue を、自分で本文まで書いてください。受入条件 を機械で判定できる形にできるかが、この課題の見どころです。
  • 2review-othermodel:opus に書き換えて、同じ差分をもう一度読ませてください。件数と中身がどう変わるかを Step 4 の表に足します。モデルを変える効果は研究では測られていないので、ここは各自の実測になります。
  • 3同じ差分を claude -p に渡し、Claude Desktop で出た指摘と比べてください。-p は入力欄からは実行できないので、統合ターミナルを開いて打ちます。結果をファイルに落とせる形になるので、自社で集計に載せるときの材料になります。
  • 4Claude Desktop のサイドバーで、このセッションのアーカイブアイコンを押して worktree を片付けてください。押す前に、GitLab 側に worktree-issue-1 ブランチと MR が残っていることを確認します。手元が消えても送ったものは残る、という関係を見ておくと、Day2 で複数の Issue を並行させるときに迷いません。
ページの先頭へ