mcpField
M
merge-change

merge-change

github.com/jan-jan開発140 回インストール4 日前に更新
リポジトリ

merge-change について

完了した変更をベースブランチへ統合する手順を示すスキルです。事前チェック、独立レビュー、検証記録、テストゲートを経て、署名付き squash merge を引き継ぎます。

主な機能

  1. 01ベースブランチの同期と競合処理
  2. 02レビュー前後の事前チェック
  3. 03独立レビューと指摘への対応
  4. 04検証記録の作成とテストゲート
  5. 05署名付き squash merge の引き継ぎ

活用シーン

  1. 01レビュー済み変更の統合
  2. 02ワークツリーでのマージ競合の解消
  3. 03マージ前のテスト証跡の記録

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/jan-jan/guardrails --skill merge-change

インストール後、Claude Code で/merge-changeを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
cleanup-rejections.md
multi-unit.md
rationale.md
review-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 から再実行します。

タグは割り当てごとに一意でなければなりません。

  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 で変更全体を再度レビューします。それ以外の場合は、ゲートの再実行で確認します。
  2. レビューの前に事前チェックを実行します:

    sh .guardrails/scripts/merge-preflight.sh --before-review <change-branch>
    

    ステップ 1 でリモートを対象外にした場合は、ここおよび 6c で --local-base を渡します。レビュー担当者を割り当てないラウンドでは、このステップを省略します。6c で実行するすべてのチェックが実行されます。

    チェックが成功した場合でも、出力される警告を読みます。最初に失敗したチェックの出力と fix <CHECK>: の行をもって実行を中止します。終了コード 1 はチェックの失敗、終了コード 2 は事前チェックまたはそれが実行するツールの使用法もしくは環境のエラーを示します。ステップ 3 に別の規則がある場合を除き、fix の行に従います。

  3. 事前チェックで判断できない事項を判定します。

    • MALFORMED-ID:new-id.sh で項目に ID を付けます。トークンを受け入れるためにパターンを広げてはいけません。
    • この変更で追加していないパスに対する DRAFT-FILE は、別の変更で名前を変更する、他の変更が残したドラフト名です。
  4. 独立レビュー — 新しいサブエージェント(またはチームのポリシーで必要な場合は人間のレビュアー)に、差分、計画、およびこの変更が申告または修正する各 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 review
    
    • remove はコミットのあるレビューブランチを拒否します。それぞれを 指摘として記録してから、task-worktree.sh discard review を実行します。 決してマージしないでください。
    • すべてのラウンドでワークツリーが作成されるわけではありません。人間の レビュアーは自分のチェックアウトを使います。開始していないタグに対して remove を実行すると失敗するため、その場合はスキップしてください。
    • remove は変更済みファイルだけでなく、追跡対象外ファイルも拒否します。 その中に gitignore 対象のファイルはありません。残す価値のあるものは 記録にコピーしてから、スクラッチを読み、削除し、再度 remove を実行します。

    修正はそれぞれ .worktrees/<change-branch>-<tag> に割り当てます。 クラス A はこのステップを省略できます。クラス B ではレビュアーが1人必要で、 クラス C では徹底的なレビューが必要です。クラス C では、重要な項目について 独立したレビュアーを2人割り当てることを検討してください。

  5. 検証記録 — .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 を読みます。

コメント