merge-change について
完了した変更をベースブランチへ統合する手順を示すスキルです。事前チェック、独立レビュー、検証記録、テストゲートを経て、署名付き squash merge を引き継ぎます。
主な機能
- 01ベースブランチの同期と競合処理
- 02レビュー前後の事前チェック
- 03独立レビューと指摘への対応
- 04検証記録の作成とテストゲート
- 05署名付き squash merge の引き継ぎ
活用シーン
- 01レビュー済み変更の統合
- 02ワークツリーでのマージ競合の解消
- 03マージ前のテスト証跡の記録
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ jan-jan/ guardrails --skill merge-changeインストール後、Claude Code で/merge-changeを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/cleanup-rejections.mdmulti-unit.mdrationale.mdreview-checklist.md詳細
変更のマージ
最初に次のようにアナウンスします:「merge-change スキルを使用して、この変更を統合します。」
前提条件
-
develop-changeが完了しており、そのテストスイートが成功しています。 -
ステップ 7 より前のすべてのステップは、変更用ワークツリー内で実行します。
-
ベースブランチはプライマリチェックアウトのブランチです。一度だけ検出します:
BASE=$(. .guardrails/scripts/lib.sh && gr_base_branch) && [ -n "$BASE" ]
手順
失敗した場合は中止します。修正作業は、プロンプトで指定され、sh .guardrails/scripts/task-worktree.sh start <tag> で作成された、ネストされたタスク用ワークツリー .worktrees/<change-branch>-<tag> に割り当てます。タスクのレポートが成功したら、sh .guardrails/scripts/task-worktree.sh merge <tag> を実行してタスクブランチをマージし、ワークツリーを削除してタスクブランチを削除します。その後、ステップ 1 から再実行します。
タグは割り当てごとに一意でなければなりません。
-
ベースブランチの最新の変更をワークツリーブランチにマージします:
primary=$(git worktree list --porcelain | sed -n '1s/^worktree //p') [ -n "$primary" ] || { echo "no primary checkout" >&2; exit 1; } git -C "$primary" diff --cached --quiet || case $? in (1) echo "the index of $primary is not empty" >&2; exit 1 ;; (*) echo "cannot read the index of $primary" >&2; exit 1 ;; esac if [ -n "$(git remote)" ]; then git fetch origin || { echo "fetch failed — fix it before merging" >&2; exit 1; } base_ref="origin/$BASE" else base_ref=$BASE fi reviewed=$(git rev-list -1 --grep='^review dispatched$' "$base_ref..HEAD") [ -z "$reviewed" ] || git diff --name-only --no-renames "$(git merge-base "$reviewed" "$base_ref")" "$base_ref" | grep -Fx "$(git diff --name-only --no-renames "$base_ref...HEAD")" | sed 's/^/shared: /' git merge "$base_ref"- プライマリチェックアウトにステージ済みの squash がある場合は停止します。ユーザーに伝え、ユーザーが署名するか解消したら、このステップを再実行します。
- fetch に失敗した場合は停止します。fetch 可能なセッションから取得するか、ローカルの参照をマージし、ステップ 7 の
Base:トレーラーにその名前を記載します。 - プロジェクトでリモートを対象外にできるのは、その理由が AGENTS.md と
Base:トレーラーの両方に記載されている場合だけです。類推して対象外にしてはいけません。 - コンフリクトはここで解決し、ベースブランチ上では決して解決しません。その後、再実行します。
- 台帳ファイルで add/add コンフリクトが発生した場合は、ベース側のファイルを残し、この変更のバージョンを新しい slug として保存した後、
git checkout --theirs -- <path>を実行し、git show :2:<path> > <new path>を実行します。その後、両方をgit addし、この変更で参照しているすべての箇所に新しい名前を記載します。 shared:の行は、この変更が編集したファイルのうち、最後にレビューしたコミット以降にベース側で編集されたファイルを示します。その場合、最後のレビューラウンドの後であっても、ステップ 4 で変更全体を再度レビューします。それ以外の場合は、ゲートの再実行で確認します。
-
レビューの前に事前チェックを実行します:
sh .guardrails/scripts/merge-preflight.sh --before-review <change-branch>ステップ 1 でリモートを対象外にした場合は、ここおよび 6c で
--local-baseを渡します。レビュー担当者を割り当てないラウンドでは、このステップを省略します。6c で実行するすべてのチェックが実行されます。チェックが成功した場合でも、出力される警告を読みます。最初に失敗したチェックの出力と
fix <CHECK>:の行をもって実行を中止します。終了コード 1 はチェックの失敗、終了コード 2 は事前チェックまたはそれが実行するツールの使用法もしくは環境のエラーを示します。ステップ 3 に別の規則がある場合を除き、fixの行に従います。 -
事前チェックで判断できない事項を判定します。
MALFORMED-ID:new-id.shで項目に ID を付けます。トークンを受け入れるためにパターンを広げてはいけません。- この変更で追加していないパスに対する
DRAFT-FILEは、別の変更で名前を変更する、他の変更が残したドラフト名です。
-
独立レビュー — 新しいサブエージェント(またはチームのポリシーで必要な場合は人間のレビュアー)に、差分、計画、およびこの変更が申告または修正する各 ID の
.guardrails/scripts/find-items.sh showの出力を渡してレビューを依頼します。実装の経緯やチャット履歴は渡しません。プロンプトでワークツリー.worktrees/<change-branch>-reviewを指定し、task-worktree.sh start reviewで作成します。まず、レビュー対象のコミットを指定します:git -c commit.gpgsign=false commit --allow-empty -m 'review dispatched'レビュアーは次の点に回答します:コードは申告された各 REQ/LLR を満たしているか、
verifies:が示す動作が壊れた場合に各テストは失敗するか、申告された各 ID について異常入力ケース(クラス B/C)があるか、要求されていない動作(マークされていない派生作業)があるか。毎回のレビューで、レビュアーに
references/review-checklist.mdを渡します。レビュアーが実行するのは、自身が判断したテストのみであり、その件数を報告します。対象は、申告された各 ID の
verifies:テストと、差分で追加または編集された各テストファイルです。テストランナーで選択できない場合は、テストスイート全体を実行します。6a のゲートではテストスイート全体を実行します。ドキュメントのみの差分では、レビュアーはテストを実行せず、その旨を記録します。 各指摘はタグで始めます —code、requirement、recordのいずれかです。 実装に誤りがある場合はcode、項目、スキル、テンプレートがツリーの動作を 誤って記述または省略している場合はrequirement、記録、計画、台帳が 正しい作業を誤って記述している場合はrecordです。codeまたはrequirementタグの後には、重要度high、medium、lowを続けます。 レビュアーは1行の判定を返します。指摘は一字一句そのままコピーしてください。タグにかかわらず、各指摘が出たらシーケンスをステップ1に戻します。 タグによって決まるのは、別のレビュアーを割り当てるかどうかだけです。
recordの指摘をその場で処理することは意図的に行いません。codeとrequirementの指摘がある場合、それらがすべてlowのラウンドが 最終レビューラウンドです。機械的に修正できるlowの指摘はそれぞれ修正し、 それ以外は未解決の問題項目として記録します。それ以降、レビュアーは 割り当てません。 後続ラウンドのレビュアーは、それ以前のラウンドで記録に残された内容を読みます。 ステップ5で計画が削除された後は、次のコマンドで計画を読みます。<commit>にはgit log -1 --format=%H --grep='^chore: drop plans$'の結果を指定します。6a のゲート失敗を修正した場合は、記録だけに触れる修正を除き、 レビュアーを割り当てます。
レポートが届いたら、作成したすべてのラウンドのレビュー用ワークツリーを 削除します。
sh .guardrails/scripts/task-worktree.sh remove reviewremoveはコミットのあるレビューブランチを拒否します。それぞれを 指摘として記録してから、task-worktree.sh discard reviewを実行します。 決してマージしないでください。- すべてのラウンドでワークツリーが作成されるわけではありません。人間の
レビュアーは自分のチェックアウトを使います。開始していないタグに対して
removeを実行すると失敗するため、その場合はスキップしてください。 removeは変更済みファイルだけでなく、追跡対象外ファイルも拒否します。 その中に gitignore 対象のファイルはありません。残す価値のあるものは 記録にコピーしてから、スクラッチを読み、削除し、再度removeを実行します。
修正はそれぞれ
.worktrees/<change-branch>-<tag>に割り当てます。 クラス A はこのステップを省略できます。クラス B ではレビュアーが1人必要で、 クラス C では徹底的なレビューが必要です。クラス C では、重要な項目について 独立したレビュアーを2人割り当てることを検討してください。 -
検証記録 —
.guardrails/templates/verification.mdをdocs/verification/<date>-<branch>.mdにコピーするか、既存のファイルを更新して、 記録を作成します。不足している内容だけを記入してください (<…>プレースホルダーも不足とみなします)。計画の**Implements:**の値(計画がない場合は ID)、 この変更が解決、受け入れ、または未解決とする ID、 計画にあるred -> green:とinherited:の各行、 ガイドラインレビュー(## Guideline reviews)、レビュアーの判定と、 各指摘およびその処置を記入します。再実行時は、ステップ4の検索方法で 計画を読みます。その後、次を実行します。sh .guardrails/scripts/drop-plans.sh --base "$BASE"この変更が追加または編集した各計画を、ステップ7でスカッシュする ローカルのベースブランチに対して、削除するか元に戻します。
6c では、値の入った4つのフィールド(
branch:、reviewer:、verdict:、reproduced:)がすべて含まれていない記録を拒否します。INCOMPLETE-RECORD。
6a. 検証スイートを割り当てます。verify-before-merge の説明に従い、
返されたゲートサマリーの成功数と失敗数から判定を読み取ります。
サマリーが示すツリーを、サマリーの横に記録します。
git rev-parse HEAD^{tree} # clean worktree, at dispatch
6b. ステップ6aのサマリーがこのツリーを示していることを確認します。 サマリーが届いたら、次を実行します。
git status --porcelain # must be empty
git rev-parse HEAD^{tree} # the step 6a hash
ハッシュが同じで、ワークツリーがクリーンであれば、ステップ6aの サマリーを有効とします。それ以外の場合は、スイートを再度割り当てます。
6c. レビュー確認を含めて、プリフライトを再実行します。
sh .guardrails/scripts/merge-preflight.sh <change-branch>
ステップ2と同じ方法で読み取ります。REVIEW の失敗に対処するために、
指摘を削除してはいけません。
6d. プリフライトで指定されたワークツリーを削除します。 NESTED-WORKTREE
の失敗に一覧表示された各ワークツリーを、その修正行に記載された方法で
廃棄します。すなわち、task-worktree.sh remove <tag> を実行するか、
remove が拒否したコミットを記録した後に discard <tag> を実行するか、
task-worktree.sh が作成していないワークツリーには通常の git コマンドを
使います。remove がワークツリーの変更を理由に拒否した場合は、
ステップ4に記載された対処法を適用します。
ほかの場所に登録されたワークツリーは、この手順の対象外です。
7. ベースブランチにスカッシュしてから、署名を引き渡します。 ベースブランチをチェックアウトした状態で、プライマリチェックアウト内で次を実行します。
git diff --cached --quiet && git merge --squash <branch>
git diff --cached --quiet <branch>
2 行目は、インデックスにブランチのツリーと完全に一致する内容がある場合に終了コード 0 を返します。この場合はメッセージに進みます。一致しない場合は、次のように対応します。
- 最初の行にファイルが「would be overwritten」と表示された場合、プライマリチェックアウトがダーティです。停止してユーザーに伝えます。
- それ以外の内容が表示された場合、手順 1 の後にベースが移動しています。
git reset --mergeを実行してから、ベース移動ラウンドとして手順 1 から再実行します。shared:行の場合のみ手順 2~4 を実行し、再実行で手順 5 に進むレビューラウンドとして、その後 6a~7 を実行します。 - 何も表示されない場合、スカッシュはすでにステージされています。別の変更のものか、この変更の古いものです。停止してユーザーに伝えます。ユーザーが署名するか、ステージを解除した後、手順 1 から再実行します。
コミットメッセージをリポジトリの外部にあるファイルに書き込みます。
<type>: <summary>
Implements: <final REQ/RC/SDD/LLR IDs>
Resolves: <PR IDs this change closes>
Opens: <PR IDs this change raises>
Verified: docs/verification/<record file>
Base: <base ref> at <its commit>
Gate-tree: <step 6a tree>
Gate: <verify_commands entry> <passed>/<total>
Gate: <coverage_command> <figure>
空の Resolves: または Opens: 行は省略します。verify_commands のエントリごとに Gate: 行を 1 行ずつ記述し、設定されている場合は coverage_command についても 1 行記述します。
<its commit> は git rev-parse <base ref> です。
Co-Authored-By: を含むすべてのトレーラーを、この最後の段落に記述します。
引き渡す前に、インデックスと手順 6a のツリーを比較します。
[ "$(git write-tree)" = <step 6a tree> ]
不一致の場合は git reset --merge を実行してから、手順 1 から再実行します。
次のコマンドを完成させてユーザーに渡し、停止します。
git commit -S -F /tmp/merge-<branch>.msg && sh .guardrails/scripts/finish-merge.sh <branch>
署名にはハードウェアキーへのタッチが必要になる場合があることを伝えます。成功すると、最後に finish-merge: の行が 3 行表示されます。それ以外の場合は、ユーザーに出力を貼り付けてもらいます。
finish-merge.sh は、何かを削除する前に 5 つの項目を検証します。パスではなく、ブランチ名を渡します。ハーネスがワークツリーを作成した場合は、ハーネスのツールを使ってそのワークツリーを残すか削除します。
そのブランチに登録されたワークツリーがない場合でも、スクリプトはブランチを削除します。
キーが設定されていないため署名に失敗した場合は、停止してラチェットのセットアップチェックリストを案内します。署名なしでの代替手段はありません。
8. 報告します。 ユーザーが成功を伝えたら、メッセージファイルからブランチ、ID、レコードを含む締めくくりの行を 1 行記述します。
Git コマンドは実行しません。署名は finish-merge.sh によって検証済みです。
次の変更の前にコンパクションすることを勧めます。ハーネスにコンパクション手順がある場合は、ユーザーが実行できるよう、その手順名を伝えます。
ユーザーから問題が報告された場合、または finish-merge.sh が 0 以外で終了した場合は、references/cleanup-rejections.md を読みます。
原因を修正してから、複合コマンド全体ではなく、スクリプトのみを再実行します。
sh .guardrails/scripts/finish-merge.sh <branch>
注意すべき考え方
| 考え方 | 実際のところ |
|---|---|
「git commit -S を自分で実行しよう。コマンドは 1 つだけだ」 | コミットするのはユーザーです。複合コマンドを渡して停止します(手順 7)。 |
| 「スクリプトでクリーンアップを拒否された。手動でワークツリーとブランチを削除しよう」 | --force と -D は、ガードが検出したものを破壊します。原因を修正してスクリプトを再実行します(手順 8)。 |
完了条件
- ベースブランチの HEAD が、この変更に対するスカッシュコミット 1 つになっており、そのメッセージにレコードとゲート対象のツリーが記載されていること。これは
finish-merge.shによって検証されます。 - 変更用のワークツリーとブランチが、
finish-merge.shによって削除されていること。 - マージを報告し、コンパクションを勧めていること。
参考資料
.guardrails/units.yamlが存在する場合は、手順 1 の前にreferences/multi-unit.mdを読みます。- 手順 4 でレビュアーを割り当てる場合は、
references/review-checklist.mdを読みます。 - 手順 7 または 8 で
finish-merge.shが 0 以外で終了した場合は、references/cleanup-rejections.mdを読みます。 - ここに記載されたルールが誤っているように思える場合、または変更を提案する前に、
references/rationale.mdを読みます。