並行開発における自動マージリレー × AIの導入事例
こちらは Applibot Advent Calendar 2025 11 日目の記事になります。
前日の記事は@sminofさんのAIで楽しくメタメタメタプログラミングでした。
はじめに
現在、私が所属しているゲーム開発プロジェクトでは、複数の新規バージョンを並行して開発しています。
この並行開発フローでは、バージョン間の変更を取りこぼさないようにするための作業が必要になりますが、これが意外と手間がかかるものです。
本記事では、私たちのプロジェクトで構築した「マージリレー」という自動化の仕組みと、運用中に直面した課題、そしてその改善策としてClaude Code(AIコーディングアシスタント)を導入した事例を紹介します。
マージリレー自動化を作った経緯
複数バージョンの並行開発
私たちのプロジェクトでは、複数のバージョンを並行して開発しています。
現行バージョン -> 次回バージョン -> 次々回バージョン -> ...
例えば、現行バージョンでバグ修正が行われた場合、その変更は次回バージョン、次々回バージョン...と順番にマージしていく必要があります。この作業を私たちのプロジェクトでは「マージリレー」と名付けています。
これを怠ると、過去バージョンで実装した機能や修正したバグが新バージョンに反映されない事態が発生してしまうため、必要不可欠な作業です。
手動運用の限界
当初は手動でマージ作業を行っていましたが、以下の問題がありました:
- 作業漏れのリスク: 忙しいと忘れがち
- タイミングのばらつき: 担当者の都合に依存。これまで必要になった時に行っていたので数週間空くこともしばしば
- 競合発生のリスク: 間が空いてしまうとそれだけブランチ間の乖離が広がるため競合のリスクが高まる
- 手順ミス: 複数リポジトリを正しい順序でマージする必要がある
そこで、これらの作業を自動化する仕組みを構築しました。
マージリレーの仕組み
毎朝の自動実行
マージリレーはJenkinsパイプラインスクリプトで自動化されており、平日の早朝など誰もリポジトリを触らないような時間帯に自動実行されます。
triggers {
// 例えばこの場合は平日AM06:00に自動実行
cron('H 6 * * 1-5')
}
出社前に自動でマージが完了しているため、プロジェクトメンバーは最新のコードで作業を開始できます。
処理の流れ
マージリレーの処理は以下の順序で実行されます:
1. サーバーコードリポジトリ PR作成
|
v
2. クライアントコードリポジトリ PR作成
|
v
[競合発生時] パターンベース解決
| - 自動生成ファイル: 事前に定義したルールで自動解決
|
+---> [解決成功] そのまま次のステップへ
|
+---> [未解決の競合あり] 処理停止、手動解決を依頼
|
v
3. サーバーコードリポジトリ PRマージ
|
v
4. クライアントコードリポジトリ PRマージ
|
v
次のバージョンへ繰り返し...
これにより、複数リポジトリ x 複数バージョンのマージが自動で行われます。
競合発生時の挙動
私たちのプロジェクトでは、DB定義系の構造体を含むいくつかのファイルは自動生成される仕組みになっています。
競合が発生した場合、競合されたファイルが対象であれば事前に定義したルールに基づいて自動生成し直しなどの処理を実行することで自動解決を試みます。
ただし、対象外のファイルなどが競合していて自動解決できない場合、
処理は停止して担当者による手動解決を依頼します。この時点ではマージは行われません。
その後、再度マージリレー開始ボタンを押下することでマージリレーが再開されます。
導入後に直面した課題
マージリレーの自動化により、手動作業は大幅に削減されました。しかし、運用を続ける中で新たな課題が浮上しました。
競合の頻発
以前に比べて競合の頻度は下がったものの、依然として大きな開発が複数のブランチで並行して進んでいるタイミングでは、マージリレー時に競合が高頻度で発生します。
特に以下のようなケースで競合が起きやすくなります:
- 同じファイルを複数のバージョンで修正
- 新機能の追加とリファクタリングが重なる
- 自動生成コードの更新タイミングのずれ
手動解決の負荷
競合が発生すると、マージリレーは中断され、担当者による手動解決が必要になります。
繁忙期などの特定タイミングでは、担当者が競合解決をほぼ毎日対応しており、場合によっては数時間を費やすこともありました。
競合解決には以下の作業が必要です:
- 競合ファイルの内容を理解
- 各ブランチの変更意図を調査(コミット履歴の確認)
- 適切な解決方法を判断
- 解決後のコンパイルエラー確認
これらの作業は単純なものではなく、
コードの文脈や実装の経緯を理解した上での判断が必要になります。
せっかく自動化したマージリレーが、競合解決のために結局手動作業が発生するという状況でした。
改善策: Claude Codeによる競合解決
この課題を解決するために、Claude Codeを導入しました。
競合解決という「文脈を理解した判断」が必要な作業を、AIに任せるアプローチです。
Claude Codeとは
Claude Codeは、Anthropic社が提供するAIコーディングアシスタントです。ターミナル上で動作し、コードの読み書き、コマンド実行、Git操作などを自然言語の指示で行うことができます。
主な特徴
- コードベースの理解: プロジェクト全体のコードを読み込み、文脈を理解した上で作業を行う
- ファイル編集: 指示に基づいてコードの追加・修正・削除を実行
- コマンド実行: シェルコマンドやGit操作を自動で実行
- カスタムコマンド: プロジェクト固有の処理をカスタムコマンドとして定義可能
CI/CDでの利用
Claude Codeはターミナル上で動作するため、
対話的な利用だけでなく、CI/CDパイプラインに組み込んで自動実行することも可能です。
今回のマージリレーにClaude Codeを採用した理由の8割はコレです。
# 非対話モードでプロンプトを実行
claude -p "XXXXXをしてください" --permission-mode acceptEdits
今回は、この機能を活用してマージリレーの競合解決を自動化しました。
実際のアプローチ
さて、実際にどのようにClaude Codeによる競合解決のアプローチを取ったかを説明します。
競合解決には2段階のアプローチを採用しています:
1. パターンベース解決(従来手法)
特定のファイルパターンに対して、機械的に解決できるものは自動で処理します。
// 自身の変更を優先
static final def conflictOursPatterns = autoGeneratedFiles
// 相手の変更を優先
static final def conflictTheirsPatterns = configFiles
- 自動生成ファイル: 外部ツールやスクリプトから生成されるファイルは、ベースブランチを優先(競合解決後に自動生成し直すのでどちらでも良い)
- 設定ファイル: 特定の設定ファイルは、マージ元を優先
2. Claude Code解決(新規導入)
パターンベースで解決できない競合に対して、Claude Codeのカスタムコマンドを使用して解決を試みます。
def claudeResult = resolveConflictsWithClaude()
claudeResolvedFiles = claudeResult.claudeResolvedFiles
finalUnresolvedFiles = claudeResult.stillUnresolvedFiles
Claude Codeの呼び出し
private Map resolveConflictsWithClaude() {
// カスタムコマンドを実行(非対話モード・自動承認)
script.sh '''
claude -p "/analyze-merge-conflict --auto" \
--permission-mode acceptEdits \
--allowedTools 'Bash(git add:*)' \
--allowedTools 'Bash(git log:*)' \
--allowedTools 'Bash(git diff:*)' \
--verbose \
--output-format stream-json
'''
// 省略(git diffコマンドで競合解決後の差分を見て結果を返す)
}
ポイント:
-
-p: カスタムコマンドを直接指定 -
--permission-mode acceptEdits: ファイル編集を自動承認 -
--allowedTools: 指定したコマンド操作を許可する -
--dangerously-skip-permissionsは使用しない: 全ての操作を許可できて便利ではあるものの、意図しない操作を防ぐために今回は使用を避けて--allowedToolsで指定しました。
カスタムコマンドの設計
カスタムコマンドとして実装した経緯
Claude Codeでは、-p オプションでプロンプトを直接指定することもできます。
# プロンプト直接指定の例
claude -p "競合を解決してください。ただし.csファイルのみ対象として..."
しかし、今回は以下の理由からカスタムコマンドとして実装しました。
カスタムコマンドを採用した理由:
-
プロンプトの可読性: 競合解決には複雑なルールや手順が必要で、プロンプトが長大になります。Markdownファイルとして管理することで、見出しやリストを使った構造化が可能になり、可読性が向上します。
-
バージョン管理: カスタムコマンドはリポジトリにコミットできるため、プロンプトの変更履歴を追跡できます。「なぜこのルールを追加したのか」をコミットメッセージで残せます。
-
再利用性: 同じコマンドをローカル開発でも使用できます。開発者が手動で競合解決する際にも、同じルールに従った解決が可能です。
-
メンテナンス性: ルールの追加・変更がMarkdownファイルの編集だけで完結します。Jenkinsパイプラインを直接変更する必要がありません。Jenkinsパイプラインの事前知識がなくても保守可能です。
絶対ルール
カスタムコマンド(/analyze-merge-conflict)には、以下の絶対ルールを設定しています:
### 実行停止レベル(違反時は即座に処理を中断)
1. **`.cs` ファイル以外は絶対に自動解決しない**
- prefab, asset, meta等のファイルは編集禁止
2. **`git add` は各ファイル編集直後に必ず実行する**
- 編集 -> 即座に `git add` -> 次のファイルへ
3. **競合マーカーを残したまま `git add` しない**
- `<<<<<<<`, `=======`, `>>>>>>>` が残っていないことを確認
1.のprefab, asset, meta等のファイルは編集禁止というルールについては悩みどころでしたが、AIが変更した内容を人間が差分を見て正しいかどうか判断するのが厳しいと感じたので、自動解決できないようにしています。
処理フロー
[PLANモード] フェーズ1: 情報収集
|
v
[PLANモード] フェーズ2: レポート生成
|
v
[Auto Acceptモード] フェーズ3: 競合解決 + git add
|
v
[Auto Acceptモード] フェーズ4: コンパイルエラー確認・修正
|
v
[検証] 完了チェックリスト確認
解決方針の決定基準
| 状況 | 解決方法 | 判断基準 |
|---|---|---|
| 片方が機能追加、もう片方が同じ箇所の修正 | 両方統合 | 両方の変更が独立している |
| 同じ機能の異なる実装 | MERGE_HEAD側採用 | マージ元が最新の意図を反映 |
| 片方がリファクタリング、もう片方が機能変更 | 機能変更側を優先 | 機能が優先 |
競合解決レポート
Claude Codeは競合解決時に詳細なレポートを自動生成します。
レポートの内容
# Git マージ競合レポート
## マージ情報
| 項目 | 値 |
|------|-----|
| 現在のブランチ | `feature/merge-relay/current-to-next` |
| マージ元ブランチ | `origin/version/current` |
| MERGE_HEAD | `abc1234def` ... |
| マージベース | `def5678abc` ... |
## 競合ファイル一覧と解決方法
### 1. SampleExtensions.cs
**競合元コミット(HEAD側)**:
| コミット | コミット者 | 説明 |
|----------|-----------|------|
| `1a2b3c4d5e` | user-a | 機能A実装 |
**競合元コミット(MERGE_HEAD側)**:
| コミット | コミット者 | 説明 |
|----------|-----------|------|
| `5e4d3c2b1a` | user-b | 機能B対応 |
**解決方法**: **両方統合**
**解決理由**:
- HEAD側は次回バージョンの新機能「機能A」を追加
- MERGE_HEAD側は現行バージョンの新機能「機能B」を追加
- 両方の機能は独立しており、両方とも含める必要がある
**解決後のコード**:
csharp
public static bool CheckSampleCondition(SampleData data, StageType type)
{
// 機能A/機能B
return data.IsCondition() ||
type == StageType.FeatureA ||
type == StageType.FeatureB;
}
Slack通知
レポートはSlackに自動アップロードされ、チーム全体で確認できます。
def uploadClaudeReportToSlack() {
if (fileExists("merge-conflict-report.md")) {
slackUploadFile filePath: "merge-conflict-report.md", channel: slackChannel, initialComment: "競合解決レポート"
}
}
レビュープロセス
人間によるレビューの必須化
Claude Codeによる自動解決が含まれる場合、PRは自動マージされません。
// Claude Code解決が含まれる場合はレビュー必須のためジョブを停止
if (env.CLAUDE_RESOLVED_FILES_LIST?.trim()) {
env.JOB_MESSAGE = "PRにClaude Codeによる自動解決が含まれています\nレビュー後に手動でマージしてください"
error(env.JOB_MESSAGE)
}
レビューの流れ
- Claude Codeが競合を解決してPRを作成
- CIワークフロー(コンパイルチェック等)が通過
- 各機能担当者がClaude Codeによって修正されたファイルのコードレビュー
- 問題なければ手動でマージ
これにより、AIの判断を人間が検証する体制を維持しています。
導入効果
人的コストの削減
- Before: 繁忙期には担当者がほぼ毎日、数時間かけて競合解決
- After: Claude Codeが自動解決し、担当者はレビューのみ
解決品質
Claude Codeを導入したことで品質の高いアウトプットを実現できています。
- コミット履歴の分析: 各ブランチの変更意図を理解
- コンパイルエラーの検出・修正: 解決後に自動でコンパイル確認
- 詳細なレポート: 解決理由が明確で、レビューしやすい
実装した当初は、実行時に精度がバラつくことがあり懸念していたのですが、
作成したカスタムコマンドのプロンプトをAIに繰り返しレビューして、
AIが理解しやすい文章(曖昧な表現を避ける、禁則事項などが明確)になるようブラッシュアップを続けました。
結果的に現在は数回実行しても結果がバラつくことなく、AIが作成した競合解決レポートが信頼できるものになったと思います。
まとめ
本記事では、マージリレーの構築からClaude Code導入までの取り組みを紹介しました。
マージリレー自動化
複数バージョンの並行開発において、変更の取りこぼしを防ぐために「マージリレー」を構築しました:
- 定期実行: 毎朝自動でマージ処理を実行
- 複数リポジトリ対応: クライアント・サーバー等の複数リポジトリを順番に処理
- パターンベース解決: 自動生成ファイル等の競合は自動生成し直すことで自動解決
これにより、手動でのマージ作業漏れやタイミングのばらつきを解消しました。
Claude Code導入
マージリレー運用中に発生した「競合解決の負荷」という課題に対して、Claude Codeを導入しました:
- 自動化: 競合解決作業の大部分を自動化
- 品質維持: レポート生成とレビュー必須化で品質を担保
- コスト削減: 担当者の工数を大幅に削減
AIを「完全自動化」ではなく「人間のサポート」として位置づけることで、安全性と効率性を両立させています。
今後の展望
導入してまだ日が浅いため、これからも継続的にブラッシュアップしていく予定です。
特に競合解決後のコードレビューについても軽度な修正内容であればAIに代替してもらうことも検討しています。
株式会社アプリボット(applibot.co.jp/)のテックブログです! 採用情報はこちら > applibot.co.jp/recruit/ 過去の技術ブログはこちら > blog.applibot.co.jp/
Discussion