リポジトリ
mainの保護
Settings > Repository > Protected branches
mainに直接pushできる人を誰もいない設定にし、マージできる人をMaintainerに絞っています。
push: No one / merge: Maintainers
こうすると講師がmainへ直接pushしても、GitLabが拒否します。AIの変更も人の変更も、必ずMRを通ります。
リポジトリ
パイプラインが通らないとマージできない
Settings > Merge requests > Pipelines must succeed
赤のジョブが1つでもあるMRでは、マージのボタンが出ません。失敗を許す設定のジョブは数えません。
only_allow_merge_if_pipeline_succeeds = true
こうすると手元の検査を飛ばしてpushしても、GitLabのcheck-rulesが赤になり、マージできません。
リポジトリ
パイプラインを1本にまとめる決まり
.gitlab-ci.ymlのworkflow:rules
MRがあるブランチへのpushでは、MRのパイプラインだけを走らせます。MRの無いブランチでは、ブランチのパイプラインが走ります。
merge_request_event / push(MRなし)
こうすると2本に分かれると、マージの条件はMR用の1本しか見ません。ブランチ用で赤のテストがあっても、マージできてしまいます。
リポジトリ
古いパイプラインの取り消し
Settings > CI/CD > General pipelines、各ジョブのinterruptible
同じブランチに続けてpushすると、古いパイプラインを途中で止めます。AIの実装ジョブには付けていません。
Auto-cancel redundant pipelines、interruptible: true
こうすると18人が直してpushし直しても、Runnerの順番待ちとAIレビューの費用が積み上がりません。
Issueとラベル
Issueの型ai-task
.gitlab/issue_templates/ai-task.md
背景、やること、受け入れ条件、触らないもの、決まっていないこと、の5つの見出しを入れます。
Work items > New itemでDescriptionの型に選ぶ
こうすると決まっていないことに1行でも書くと、AIは実装せずに止まります。ボードの+から作ると型が入らないので、New itemから作ります。
Issueとラベル
MRの型
.gitlab/merge_request_templates/Default.md
変更の概要、影響の範囲、テストの結果の欄と、Closes #番号の行を入れます。
Closes # /影響範囲の表/テストの表
こうするとCloses #番号が残ったままマージすると、そのIssueが自動で閉じます。
Issueとラベル
承認のラベルai::approved
Manage > Labels
人だけが付けるラベルです。付けた時点が、AIに任せてよいという承認になります。
変数AI_LABEL_APPROVEDで名前を変えられる
こうすると付けていないIssueは拾われません。実装の直前に外すと、ジョブが確かめ直して止まります。
Issueとラベル
状態のラベル
ai::running、ai::done、ai::stopped、ai::made
パイプラインが付け替えます。拾った、MRを出した、止まった、AIが作ったMR、の4つです。
CEではai::approvedと並んで付く
こうすると止まったIssueは、本文を直してai::stoppedを外すと、次の便で拾い直されます。
Issueとラベル
非常口のラベル
ai-gate::override
レベル3の課題「AIゲートに非常口を付ける」で使う印です。ゲートが本当に止めるようになったあと、人の判断で通すために付けます。
研修の時点のmainでは効かない
こうすると課題の答えでは、ラベルを付けて流し直すと緑になり、ログにOVERRIDEの行が残ります。
Issueとラベル
演習のラベル
level::1〜3、演習::…、種別::…、領域::…、実行::夜間、day1
題材を探すための印です。サイクルの動きには関わりません。
Work itemsの検索欄で絞り込む
こうするとレベル3の5本は、ラベルlevel::3で絞るとそろって出ます。
Issueとラベル
ボードDevelopment
Plan > Issue boards
Open、Doing、Review、Closedの4列です。DoingとReviewは、同じ名前のラベルで動きます。
列: Open / Doing / Review / Closed
こうするとラベルをDoingからReviewに替えると列が動き、マージでIssueが閉じるとClosedに移ります。
鍵と変数
ANTHROPIC_API_KEY
Settings > CI/CD > Variables
AIレビューとAIの実装が、Claudeを呼ぶための鍵です。
Masked、Protectなし
こうすると使った費用はAnthropicのConsoleで見られ、上限は80ドルです。研修の後に作り直します。
鍵と変数
GITLAB_BOT_TOKEN
Settings > CI/CD > Variables
ai-cycle-botとして、ラベルとコメント、push、MRの作成、レビューのコメントを行う鍵です。
Masked、Protectなし
こうするとProtectを付けると、受講者のブランチのMRに値が届かず、AIレビューが「未設定」で落ちます。10月2日に実際に起きました。
鍵と変数
ai-cycle-botのトークン
Settings > Access tokens
プロジェクトアクセストークンです。ロールはDeveloper、スコープはapiとwrite_repositoryで、期限は10月31日です。
Developer / api, write_repository
こうするとDeveloperは保護されたmainにpushもマージもできません。AIが作った変更も、必ずMRを通ります。
鍵と変数
設定の変数
.gitlab-ci.ymlのvariables
実装に使うモデル、レビューのモデルの切り替え、ゲートが落とす重大度を決めています。
AI_MODEL=claude-sonnet-5 / AI_REVIEW_DIFF_THRESHOLD=80 / GATE_LEVEL=P1
こうすると差分が80行を超えたMRでは、レビューのモデルがclaude-opus-5に切り替わります。
鍵と変数
サイクルのスイッチAI_CYCLE
スケジュールかNew pipelineのVariables
値がonのときだけ、AIのサイクルが動きます。このときコードの検査のジョブは走りません。
AI_CYCLE = on
こうすると付け忘れてNew pipelineを押すと、mainのふつうの検査だけが走って終わります。
パイプライン
lintの3ジョブ
ci/jobs/lint.yml、ci/jobs/rules.yml
PHP 8.3の構文、対象のPHP 7.4の構文と関数の有無、CLAUDE.mdの行数と1行の長さを見ます。
lint-phpdev / lint-phpver / check-rules
こうすると8.0で入ったstr_contains()を書くと、構文の検査を通っても、lint-phpverの2段目で赤になります。
パイプライン
testの2ジョブ
ci/jobs/test.yml
簡易のテストと、PHPUnit 7.5とPostgreSQL 9.5のテストを走らせます。
test / test-phpunit
こうすると結果の行が出ないまま終わったテストも赤になります。画面の関数のexitで黙って緑になる穴を塞いでいます。
パイプライン
ai_review
ci/jobs/review.yml、ci/ai_review.sh
差分をClaudeに読ませ、指摘をMRにコメントで1本置きます。読むだけで、合否は決めません。
MRのときだけ/失敗を許す
こうするとAPIが混んで落ちても、パイプラインは止まりません。レビューが無かったことはai_gateが拾います。
パイプライン
ai_gate
ci/jobs/review.yml、ci/ai_gate.sh
レビューの結果を読み、P0とP1の指摘が1件でもあれば赤にします。
MRのときだけ/初期は失敗を許す
こうすると研修の時点では黄色の注意で終わり、マージを止めるかは人が決めます。本当のゲートにするのは、レベル3の課題です。
パイプライン
ai_scope
ci/jobs/ai-scope.yml、ci/ai_scope.sh
ブランチ名がai/で始まるMRでだけ動き、止め金と触らない場所を変えていないかを見ます。
赤はexit 1 /注意はexit 3
こうするとAIのMRがREVIEW.mdを書き換えると、赤になってマージできません。テストなしでapp/を変えると、注意で知らせます。
パイプライン
ai_pick
ci/jobs/ai.yml、ci/ai_pick.sh
承認済みで未着手のIssueを古い順に最大20本拾い、ai::runningを付けて、子パイプラインを書き出します。
resource_group: ai_pick / AI_PICK_MAX=20
こうすると手で押した起動とスケジュールが重なっても、1本ずつ流れるので、同じIssueを2回拾いません。
パイプライン
implement-issue-番号
ci/ai_implement.sh(子パイプラインのジョブ)
承認を確かめ直し、Claudeに実装させ、pushしてMRを作ります。Issue1本に1ジョブです。
最大30ターン/ 25分で打ち切り/ジョブは30分
こうすると曖昧なIssueでは質問を書いて止まり、ai::stoppedと理由のコメントを付けます。
スケジュール
AIサイクル(午後の研修)
Build > Pipeline schedules
午後のレベル1〜3の間、承認されたIssueを10分おきに拾います。普段はActivatedを外しておきます。
*/10 * * * * / Asia/Tokyo / main
こうするとGitLabの決まりで、毎時3分、13分、23分のように動きます。10月6日の昼休みの終わりに有効にし、夜に回す節の前の休憩で外します。
スケジュール
AIサイクル(夜間)
Build > Pipeline schedules
夕方までに承認されたIssueを、夜にまとめて実装します。作った人の権限で動くので、rootで作っています。
0 22 * * * / Asia/Tokyo / main
こうすると実際に動くのは22時3分です。10月5日の夕方に有効にし、10月7日の朝に外します。
RunnerとAIジョブ
Runner dlive-shared-runner
Admin > CI/CD > Runners、EC2のconfig.toml
すべてのジョブをDockerで動かします。GitLabと同じEC2(t3.xlarge)の上にあります。
同時に8本/イメージは毎回確かめる(always)
こうすると9月29日のDay1では、10分に10本が重なり、待ちは最大107秒でした。
RunnerとAIジョブ
GitLab Community Edition 19.3.1
gitlab-09291006aidev.give-app.net
無料版です。承認の人数、pushのルール、MRの側から消せないジョブの仕組みは入っていません。
Duoなし/ PremiumとUltimateの機能なし
こうするとCEに無い機能は、パイプラインのジョブで作ります。ブランチ名の検査やClosesの検査が、レベル3の課題です。
RunnerとAIジョブ
無人のClaudeの権限
ci/settings.ci.json
実装ジョブのClaudeに渡す許可と禁止です。push、ブランチの切り替え、curl、.envの読み出しを禁止しています。
--permission-mode dontAsk /許可は読み書きとphpとgit addとgit commit
こうすると許可の外の操作は、確認を出さずに実行の前に拒否され、ログにDENIEDの行が残ります。
RunnerとAIジョブ
MRを出す前の確かめ
ci/ai_implement.sh
MRを出す前に、app/core/、db/、.claude/、ci/などの変更が無いかを見ます。
見つかったらMRを出さずにai::stopped
こうするとClaudeがci/を書き換えても、MRは出ません。その外側のCLAUDE.mdやREVIEW.mdは、ai_scopeが見ます。
受講者のPC
手元のフック
.claude/settings.jsonのhooks、.claude/hooks/
Claude Desktopの操作の前後で動く5種類の検査です。書く前に止めるguard_paths、壊すコマンドを止めるblock-destructive、書いた後のPHPの版の検査などがあります。
PreToolUse / PostToolUse / Stop / SessionStart
こうするとdb/schema.sqlを書かせると、書く前に「BLOCKED by guard_paths」で止まります。
受講者のPC
手元の許可と禁止
.claude/settings.jsonのpermissions
強制pushやgit reset --hardは禁止、ふつうのpushは人に聞く設定です。
deny: reset --hard、push --force、cleanほか/ ask: git push
こうすると「pushしてください」と頼むと、確認のダイアログにコマンドが出ます。
受講者のPC
pre-commit
.githooks/pre-commit、tools/check-rules.ps1
コミットの前にCLAUDE.mdの行数を数え、80行を超えるとコミットを止めます。
Step 4でgit config core.hooksPath .githooks
こうするとgit commit --no-verifyで飛ばせます。止め金はGitLabのcheck-rulesの側にあります。
受講者のPC
触らない場所の一覧
.claude/protected-paths
app/core/、db/schema.sql、db/seed.sqlと、この一覧そのものです。手元のフック、実装ジョブのフック、ai_scopeが同じファイルを読みます。
/で終わる行はフォルダ全体
こうするとMRの中で一覧を書き換えても、ai_scopeはmainの側の一覧で判定します。
受講者のPC
AIが読む約束
CLAUDE.md、AGENTS.md、REVIEW.md
CLAUDE.mdとAGENTS.mdは実装の約束で、REVIEW.mdはAIレビューの基準です。
CLAUDE.mdは80行まで
こうするとCLAUDE.mdが80行を超えると、手元のpre-commitとGitLabのcheck-rulesの両方が止めます。