仕込んだ設定と役割

GitLabに仕込んだ34の設定

研修のGitLabと、受講者のPCに入る設定を、置き場所ごとに並べました。カードの下の段に、その設定があるとき、何をすると何が起きるかを書いています。

設定の置き場所

受講者のPC

  • Claude Desktopのフックと許可の設定
  • コミットの前のpre-commit
  • リポジトリの約束の文書(CLAUDE.mdなど)

速報の役です。--no-verifyや設定の外し方ひとつで飛ばせます。

GitLabのプロジェクト

  • mainの保護とマージの条件
  • Issueの型、ラベル、ボード
  • 鍵の変数、トークン、スケジュール
  • パイプラインの定義(.gitlab-ci.ymlとci/)

合流の前の最終判定です。受講者の手元から外せません。

RunnerとEC2

  • ジョブを動かすRunner(同時8本)
  • 無人のClaudeに渡す許可と禁止
  • 実装ジョブの上限と確かめ

夜も動く場所です。pushの権限はジョブのスクリプトだけが持ちます。

34の設定

リポジトリ

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の両方が止めます。