merge-orchestrator について
ExarchosのワークフローでローカルGitブランチを安全にマージするスキルです。事前チェックと復旧ポイントの記録、失敗時の復旧を行い、共有統合ブランチには直列化されたリース経由でマージします。
主な機能
- 01明示した戦略でローカルブランチをマージ
- 02ワークツリーとブランチ状態の事前確認
- 03マージ前の復旧ポイント記録
- 04ハードリセットを使わない失敗時の復旧
- 05構造化イベントとフェーズ再開
活用シーン
- 01サブエージェントのワークツリーブランチを統合
- 02共有統合ブランチへのマージを直列化
- 03マージ失敗後にターゲットブランチを復旧
MCP 対応
Exarchos MCPのオーケストレーションアクションでローカルマージを実行・追跡します。共有統合ブランチでは serialize_merge が単一書き込みリースを取得します。
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ lvlup-sw/ exarchos --skill merge-orchestratorインストール後、Claude Code で/merge-orchestratorを入力して読み込まれたか確認します。
同梱ファイルreferences/local-git-semantics.mdreferences/recovery-runbook.md
詳細
マージオーケストレーターのスキル
リモート VCS ではなくローカル Git
このスキルは、ソース(サブエージェント)のワークツリーブランチをターゲット(統合)ブランチにローカルで git merge し、INV-14 の復旧手順(git merge --abort → git reset --keep。--hard は決して使用しません)で失敗時にマージを巻き戻せるよう、復旧ポイントの SHA を記録します。VCS プロバイダー(GitHub / GitLab / Azure DevOps)は呼び出さず、PR ID も必要ありません。リモート PR のマージについては、merge_pr アクションをラップする関連スキルを参照してください。
これらが 2 つの別個の関心事である理由と、その考え方については、references/local-git-semantics.md に記載されています。
単一書き込みエントリーポイント:serialize_merge
共有統合ブランチ、つまり、兄弟ワークツリーのマージと競合する可能性がある delegate → merge-pending → delegate ループ中にサブエージェントのワークツリーブランチを統合 ref に取り込む場合、serialize_merge が統合マージの唯一の経路です。 楽観的な integrationRef ごとのリース(統合 ref ごとに同時進行できるマージは最大 1 件)を取得し、そのリースのもとでこのアクションを変更せずに実行します。統合マージには serialize_merge を使用してください。統合マージに生の merge_orchestrate をディスパッチしないでください。
ターゲットに対する同時マージが競合しない場合に限り、merge_orchestrate を直接呼び出してください。
- 統合以外のマージ — ターゲットがプライベート/スクラッチ/単独作業用のブランチで、他のエージェントが触れない場合。
- クラッシュから再開する呼び出し元 — すでにリースを取得したマージを新しい pid で完了させる場合。元の claim の
leaseOperationIdを再提示します。 - 合成された実行呼び出し —
serialize_merge自身が行う呼び出し。自身のリースのoperationIdを引き継ぐため、ガードを通過します。
DR-2 リースガード。 有効な他者のリース(保持者の operationId ≠ 提示された leaseOperationId、保持者の生存状態 ∈ {alive, unknown})が設定された統合 ref に対して、生の merge_orchestrate を実行すると、構造化された MERGE_LEASE_HELD エラーでフェイルクローズし、Git による副作用は発生しません。エラーメッセージには、経路として serialize_merge が示されます。リースがないターゲット、または保持者が確実に停止しているターゲットでは、これまでと同様に処理が続行されます(後方互換性あり。停止した保持者はブロッカーになりません)。このため、上記のルーティングは単なる慣例ではなく、ハンドラーの要所で強制されます。serialize_merge を使用してください。
概要
オペレーター(または自動化された next_actions ディスパッチャー)が Exarchos MCP に対して merge_orchestrate アクションを呼び出すと、このスキルが有効になります。ソースブランチがワークツリーで準備された後、このスキルは次の処理を行います。
- ワークツリーの利用可能性を事前確認します(ターゲットブランチが兄弟ワークツリーでチェックアウトされていないこと)。
- 追加の事前確認ガードを組み合わせます(祖先関係、現在のブランチの保護、メインワークツリーの検証、作業ツリーのドリフト)。
- マージ前のターゲットブランチの
HEADを復旧ポイントとして記録します(参照の変更前に永続化される、先行書き込みログのマーカー)。 - ソースブランチをターゲットブランチにローカルで
git mergeします。 - 失敗時には必ず、INV-14 の復旧手順(
git merge --abort→git reset --keep <recoveryPointSha>。--hardは決して使用しません)を実行し、分類された失敗理由を提示します。 - 専用のイベントタイプを出力し、イベントログだけからマージのタイムラインを再構築できるようにします。
再開可能です。終端フェーズ(completed/rolled-back/aborted)に再度入った場合、イベントを再出力せずに処理を終了します。冪等です。同じ taskId で再度ディスパッチすると、next_actions の冪等性キーによって重複がまとめられます。
トリガー
次の場合にこのスキルを有効にします。
- オペレーターが
exarchos merge-orchestrate ...(CLI)を実行した場合。 exarchos:exarchos_orchestrate({ action: "merge_orchestrate", ... })が直接呼び出された場合。- 自動化された
next_actionsエンベロープに、merge-pendingの迂回処理用のマージ動詞(冪等性キー<streamId>:merge_orchestrate:<taskId>)が現れた場合。
共有インテグレーションのマージは
serialize_mergeを経由してください。 トリガーが共有インテグレーションブランチの場合(一般的なのはmerge-pendingの迂回処理です)、上記の単一ライターのエントリーポイントに従い、serialize_mergeを介して反映してください。serialize_mergeはリースのもとでこのアクションを組み合わせます。直接の未加工のmerge_orchestrateディスパッチは、非インテグレーションのマージ、またはすでにリースを取得済みのマージに限ります。リース中のインテグレーション参照に対して実行すると、安全側に倒して失敗します(MERGE_LEASE_HELD)。
次の場合は、このスキルを有効にしないでください。
- リモート PR をマージする場合 — それは
merge_prです。2つは互いに独立したアクションです。以下の「識別」を参照してください。 - オーケストレーターに記録された
mergeOrchestrator.phaseがすでに終了状態の場合 — 再開時のショートサーキットは実行されますが、新たなディスパッチは不要です。
プロセス
スキーマ:
exarchos:exarchos_orchestrate({ action: "describe", actions: ["merge_orchestrate"] })を使って、アクションの引数スキーマを確認してください。Strategy は必須です(スキーマレベルのデフォルト値はありません)。squash/merge/rebaseから慎重に選択してください。
ステップ 1:マージ戦略を選択する
| 戦略 | ローカルの git 操作 | 選択する場合 |
|---|---|---|
merge | git merge --no-ff --no-edit <source> — 明示的なマージコミット | サブエージェントのコミット履歴を維持し、明確なマージ境界を残します。 |
squash | git merge --squash <source> の後に git commit — ターゲット上に単一のスカッシュコミットを作成 | サブエージェントのコミット履歴がノイズになる場合や、1つの論理的な変更を1つのコミットとして反映する場合に選択します。 |
rebase | ソースの一時コピーをターゲット上にリベースしてから、ターゲットを fast-forward マージ — 直線的な履歴 | マージコミットを作成せず、インテグレーションブランチの履歴を直線的に保ちます。元のソース参照は維持されます(リベースは後で削除される一時ブランチ上で実行されるため)。そのため、エグゼキューターの復旧時に必要なのは、ターゲットを復旧ポイントまで巻き戻すことだけです。 |
戦略はスキーマ層で必須です(競合チェックとユーザーに表示される内容の整合性のため)。暗黙のデフォルトはありません。オペレーターの意図は常にイベントログで明示されます。
ステップ 2:呼び出す
インテグレーションのマージですか? 以下の直接呼び出しは、未加工のエグゼキューター形式です。非インテグレーションのマージ、またはすでにリースを取得済みのマージに使用してください。共有インテグレーションブランチの場合は、代わりに
serialize_mergeを呼び出してください(単一ライターのリースのもとで、この呼び出しと同一の処理を組み合わせます)。上記の単一ライターのエントリーポイントを参照してください。
MCP 経由(例示です。正式な引数名は describe から取得してください):
exarchos:exarchos_orchestrate({
action: "merge_orchestrate",
// workflow-correlation identifier — name per the action's schema
sourceBranch: "<subagent-branch>",
targetBranch: "<integration-branch>",
taskId: "<task-id>", // present when auto-dispatched from next_actions
strategy: "squash", // required
dryRun: false, // optional — preflight only, no executor invocation
resume: false, // optional — short-circuit on terminal phases
})
CLI 経由:
exarchos merge-orchestrate \
--source-branch <subagent-branch> \
--target-branch <integration-branch> \
--task-id <task-id> \
--strategy squash
# plus the workflow-correlation id flag — see `--help`
# add --dry-run for preflight-only, --resume for terminal-phase short-circuit
CLI の終了コード:0 = 成功、1 = 無効な入力、2 = マージ失敗(事前チェックでブロックされたか、復旧処理が実行された)、3 = 捕捉されなかった例外。
ステップ 3:結果を解釈する
ハンドラーは、結果を判別する data.phase を持つ ToolResult を返します。
phase | 意味 | オペレーターの対応 |
|---|---|---|
completed | ローカルでのマージが完了し、mergeSha はターゲットの新しい HEAD です。 | なし — ワークフローはオーケストレーターのプレイブックに従って続行します。 |
aborted | 事前チェックに失敗し、マージは試みられていません。data.preflight には構造化されたガードのサブ結果(ボディの事前チェックによって生成された場合)が含まれます。または、data.reason によって早期中断の原因が判別されます。 | 以下の中断理由の表を参照してください。根本的な条件を解消して、再度ディスパッチしてください。 |
rolled-back | マージが試みられましたが、失敗しました(reason: 'merge-failed' / 'verification-failed' / 'timeout')。その後、INV-14 の復旧手順(git merge --abort → git reset --keep <recoveryPointSha>)が実行されました。ターゲットブランチは復旧ポイントまで巻き戻されています。(フェーズ値は rolled-back のままです。これは復旧時の再解釈によって変更されない、重要な状態トークンです。) | data.reason を確認してください。data.recoveryError / data.recoveryErrorDetail も存在する場合は、リセット自体が失敗しています — 作業ツリーが取り残されており、オペレーターの介入が必要です。 |
中断理由のペイロード形式
phase === 'aborted' の場合、データペイロードによって原因が判別されます。
data.reason | ペイロードのフィールド | 原因 | オペレーターによる解決方法 |
|---|---|---|---|
target-checked-out-elsewhere | siblingWorktreePath: string(絶対パス) | ターゲットブランチは、同じリポジトリの別のワークツリーですでにチェックアウトされています。イベントの出力、エグゼキューターの呼び出し、状態の永続化が行われる前に、ワークツリー可用性の事前チェックで検出されます。 | 該当するワークツリーを解消してください。削除する(git worktree remove <path>)、チェックアウト先を別のブランチに切り替える、または別のターゲットに対してマージを再実行する方法があります。その後、共有の統合参照に対しては serialize_merge を通じて再度ディスパッチしてください。 |
| (ボディの事前チェックの失敗) | data.preflight: { ancestry, worktree, currentBranchProtection, drift } | ボディの事前チェックガードに失敗しました(祖先関係の不一致、ワークツリーの不整合、現在のブランチが保護されている場合など)。 | preflight.* のサブ結果を調べ、どのガードに失敗したかを特定してください。根本的な条件(コミット/スタッシュの不整合、保護されたブランチからの切り替えなど)を解消して、再度ディスパッチしてください。 |
target-checked-out-elsewhere による中断経路は特殊です。merge.requested と merge.preflight の両イベントの出力を抑止し、状態の永続化も完全にスキップします。これにより、正しい復旧ポイントを取得できなかった可能性のある試みでイベントログが汚染されることはありません(エグゼキューターが誤ったワークツリーから HEAD を読み取っていたはずです)。
結果ごとの復旧フロー全体については、references/recovery-runbook.md を参照してください。
ステップ 4:イベントの出力を確認する
イベントはオーケストレーターのイベントストリーム(ストリーム ID は streamId に渡される値)に直接出力されます。gate.executed にはラップされません。
| イベントタイプ | タイミング | 内容 |
|---|---|---|
merge.preflight | 常に(プリフライトの実行後、マージの試行前)。ただし、早期中断となる target-checked-out-elsewhere の経路では何も出力されません | 構造化されたガードのサブ結果全体と、passed: false の場合は failureReasons |
merge.requested | プリフライトの通過後、エグゼキューターの実行前(2 イベント分割におけるフェーズ A の意図記録)。早期中断となる target-checked-out-elsewhere の経路では出力されません | sourceBranch、targetBranch、strategy、taskId |
merge.executed | ローカルマージの成功時 | mergeSha、rollbackSha、taskId、ソースブランチ/ターゲットブランチ |
merge.recovered | マージ後の失敗に続いてリカバリーが行われたとき。正規のリカバリーイベントであり、DR-2 以降、出力される唯一のイベントです | recoveryPointSha、reason、recoveryError?、recoveryErrorDetail?、taskId、ソースブランチ/ターゲットブランチ |
merge.rollback | 廃止済み(DR-2)。レガシーなワイヤ形式で、読み取り可能ですが出力されません。現在は何も書き込みません。DR-2 より前のイベントログにのみ残っており、merge.recovered と同じように処理されます | rollbackSha、reason、taskId、ソースブランチ/ターゲットブランチ |
リカバリーの用語。 正規の枠組みはサガではなく、データベーストランザクションです。つまり、リカバリーポイント(記録された HEAD SHA。マージ前に先行書き込みログのマーカーとして永続化されます)と、リカバリーイベント(
merge.recovered)です。merge.recoveredはmerge.rollbackの後継であり、DR-2 以降、エグゼキューターが出力する唯一のリカバリーイベントです。従来のmerge.rollback書き込み経路は廃止済みです(読み取り可能ですが出力されず、DR-2 より前のログを同じように再生できるようにするためだけに保持されています)。merge.recoveredは、解決済みのrecoveryPointSha/recoveryErrorDetailという名前を、変更されていない INV-14 のrecoveryError識別子とともに保持します。保持されるmerge.executedイベントには、引き続きrollbackShaのワイヤフィールドが含まれます。フェーズ値の'rolled-back'は意図的に保持されています。
これらのイベントはハンドラーによって自動的に出力されます。通常の操作中に exarchos:exarchos_event を使って手動で追加しないでください。手動出力が認められるのは、マージがオフバンドで完了した場合(例:競合解決)に、イベントログを同期させる必要があるときの、recovery-runbook.md に記載された手動リカバリーフローのみです。そのランブックに記載されたイベント優先の順序に従ってください。
exarchos:exarchos_event({ action: "describe", eventTypes: ["merge.preflight", "merge.requested", "merge.executed", "merge.rollback", "merge.recovered"] })でイベントペイロードのスキーマを確認できます。(merge.rollbackはレガシーで読み取り可能です。DR-2 より前のログを再生できるように保持されていますが、現在は出力されません。)
区別:merge_orchestrate と merge_pr
関連する 2 つのアクションですが、用途はそれぞれ異なります。
| 項目 | merge_orchestrate(このスキル) | merge_pr(連携するスキル) |
|---|---|---|
| レイヤー | ローカル SDLC の引き継ぎ | リモート PR の基本操作 |
| マージ対象 | サブエージェントのワークツリーブランチからターゲットブランチへのマージ | VCS プロバイダー API を介した、ユーザー向け PR |
| 必要な識別子 | sourceBranch + targetBranch | prId |
| 基盤となる操作 | git merge(ローカル) | provider.mergePr()(リモート API) |
| リカバリー | INV-14 のリカバリー手順:git merge --abort → git reset --keep <recoveryPointSha>(実際にマージを巻き戻します。--hard は使用しません) | なし。VCS プロバイダーがマージ状態を管理します |
| イベント | merge.preflight/merge.requested/merge.executed/merge.recovered(レガシーな merge.rollback は廃止済み。読み取り可能ですが出力されません) | pr.merged |
「PR をマージしたい」と考えて merge_orchestrate を使おうとしている場合は、代わりに merge_pr を使用してください。
再開時のセマンティクス
resume: true を指定して呼び出すと、ハンドラーは既存の mergeOrchestrator 状態を読み取ります。終端フェーズ(completed / rolled-back / aborted、EXCLUDED_MERGE_PHASES のメンバー)の場合は、短絡して記録済みの結果を返し、新しいイベントの発行もエグゼキューターの呼び出しも行いません。非終端フェーズ(pending / executing)の場合は、改めてプリフライトとエグゼキューターの実行に進みます。基盤となる Git 操作は、すでにマージ済みのブランチに対して冪等であるため、安全に実行できます。
resume を指定せずに呼び出した場合、以前の状態は意図的に無視されます。これは新規ディスパッチのセマンティクスです。
注:target-checked-out-elsewhere の早期中止パスは、再開状態の読み取りより前に実行されるため、resume の値にかかわらず短絡し、状態を永続化しません。
ドライラン
dryRun: true を指定すると、本文のプリフライトを実行して merge.preflight を発行した後、エグゼキューターの実行と状態の永続化を行う前に短絡します。{ dryRun: true, preflight, phase: 'pending' | 'aborted' } を返します。マージウィンドウが開く前にマージ可能かどうかを確認する CI 統合に便利です。ドライランでもワークツリーの利用可否に基づく早期中止は適用されます。兄弟ワークツリーとの競合がある場合は、dryRun の指定中でも reason: 'target-checked-out-elsewhere' で中止します。
アンチパターン
| 避けること | 代わりに行うこと |
|---|---|
生の merge_orchestrate をディスパッチして、共有統合ブランチにマージする | serialize_merge(シングルライターリース)経由でルーティングします。生の merge_orchestrate は、非統合マージまたはリース保持中のマージ用です。有効な他者のリースがあると、フェイルクローズで失敗します(MERGE_LEASE_HELD) |
| このスキルを使ってリモート PR をマージする | merge_pr スキルを使用します |
通常のフローで merge.preflight / merge.requested / merge.executed / merge.recovered を手動で発行する | ハンドラーによる自動発行に任せます。手動で発行すると重複が発生します(例外は、recovery-runbook.md に記載された手動リカバリーフローです) |
マージイベントを gate.executed でラップする | 専用のイベントタイプでストリームに直接追加します。これらは状態遷移であり、ゲートの実行ではありません |
理由を確認せず、rolled-back の結果後に再ディスパッチする | data.reason と data.recoveryErrorDetail(識別子 data.recoveryError を含む)を読み取り、まず根本原因に対処します |
兄弟ワークツリーを先に解放せず、reason: 'target-checked-out-elsewhere' の後に再ディスパッチする | data.siblingWorktreePath で参照されている兄弟ワークツリーを削除するか、チェックアウトし直してから再ディスパッチします |
デフォルト値を期待して --strategy / strategy: フィールドを省略する | ストラテジーの指定は必須です。squash / merge / rebase のいずれかを明示的に指定します |
| サブエージェントのワークツリーから呼び出す | プリフライトで拒否されます(メインワークツリーのアサーション)。メインワークツリーから呼び出してください |
診断
merge_orchestrate を呼び出す前に、環境で EXARCHOS_PREFLIGHT_DEBUG=1 を設定すると、merge.preflight イベントに構造化デバッグペイロードが付加されます。このペイロードが有効になるには、次の2つの条件を両方満たす必要があります。
- オーケストレーターのプロセス環境に
EXARCHOS_PREFLIGHT_DEBUG=1が設定されている。 - プリフライトの祖先ガードが失敗している(つまり、
ancestry.passed === false)。
環境変数が設定されていても、プリフライトが成功した場合はデバッグペイロードは含まれません。この失敗時のみ有効にする制御は意図的なものです(イベントストアの肥大化を懸念)。将来、EXARCHOS_PREFLIGHT_DEBUG=2 チャネルで詳細な診断や成功したプリフライトの診断を追加する可能性がありますが、フェーズ 1 の対象外です。
デバッグブロックには9つのフィールドがあります。
| フィールド | ソース | 目的 |
|---|---|---|
gitVersion | git --version | git のリリース間での動作の違いを特定します。 |
repoRoot | git rev-parse --show-toplevel | シンボリックリンク経由または正規化済みのリポジトリパスと、生のリポジトリパスを区別します。 |
worktreeList | git worktree list --porcelain | 参照解決に影響する可能性がある、兄弟ワークツリーのトポロジーを明らかにします。 |
refsHeadsSource | git for-each-ref refs/heads/<source> | ソースブランチ参照の SHA とパック状態を示します。 |
refsHeadsTarget | git for-each-ref refs/heads/<target> | ターゲットブランチ参照の SHA とパック状態を示します。 |
mergeBaseCommand | 構築済み | ヘルパーが再実行する正確な argv。'git' プレフィックスも含み、オペレーターがコピーして実行できます。 |
mergeBaseExitCode | git merge-base --is-ancestor <target> <source> の再実行 | 祖先チェックの失敗を引き起こした終了コードです。 |
mergeBaseStdout | 同じ呼び出し | 取得した標準出力です。 |
mergeBaseStderr | 同じ呼び出し | 取得した標準エラー出力です(デフォルトの git アダプターでは標準出力にまとめられます)。 |
フェイルクローズ方式です。デバッグヘルパー内の個々の git 呼び出しが失敗した場合、例外をスローするのではなく、そのフィールドは空文字列またはデフォルト値になります。デバッグ情報の添付によって、オペレーターが調査しようとしているプリフライトの根本的な失敗が覆い隠されることがあってはなりません。
報告ワークフロー。 デバッグ情報を含むイベントを取得したら、data.debug ブロック全体を、関連するスコープラベル(例:windows、merge-orchestrator、preflight)を付けた新しい GitHub issue に添付してください。フェーズ 2 の根本原因分析には、このペイロードを含む実ホストのイベントが少なくとも1件必要です。
スキーマの検出
引数スキーマを確認するには、exarchos:exarchos_orchestrate({ action: "describe", actions: ["merge_orchestrate"] }) を呼び出してください。イベントペイロードの形式は、exarchos:exarchos_event({ action: "describe", eventTypes: ["merge.preflight", "merge.requested", "merge.executed", "merge.rollback", "merge.recovered"] }) で確認できます(merge.rollback はレガシーで、読み取り時の互換性のために保持されています。DR-2 より前のログを再生するために残されており、現在は出力されません)。
ワークフロー状態の mergeOrchestrator.* フィールドはこのスキルによって書き込まれ、mergeOrchestrator.phase はゲートによって読み取られます。基盤となる phase ワークフローフィールドは変更不可であり、update ではなく transition を使って変更する必要があります。不変キーの一覧と型付き RESERVED_FIELD エラーエンベロープの詳細については、checkpoint スキルの予約済みフィールドセクションを参照してください。
完了条件
- プリフライト結果が
passed: trueである(または、文書化されたプリフライト上の未解決点があっても続行するとオペレーターが判断している) - オーケストレーター状態で
mergeOrchestrator.phase === 'completed'である - 記録された
mergeShaとrollbackSha(v2.11.x の非推奨化期間中に保持される、復旧ポイント用のレガシーな wire フィールド)を含むmerge.executedイベントがストリームに存在する - ターゲットブランチの HEAD が、記録された
mergeShaと一致する
いずれかの条件を満たさない場合は、再ディスパッチする前に references/recovery-runbook.md を参照してください。