Pre-Merge (マージ前レビュー)
GitHub PRを作成し、PRDとスライスイシューにリンクを張り、プロジェクトのアーキテクチャ原則に対して全差分をレビューします。アドバイザリ型の所見を生成します。マージをブロックしたり、コードを自動修正したり、イシューを作成したりすることはありません。
呼び出し位置
これは、実装が検証された後、メインブランチにマージする前、または他の誰かのPRをレビューするときに使用する主要なパイプラインスキルです。
ブランチがPR作成、アーキテクチャレビュー、および計画とコードの最終的な整合性確認の準備ができたときに /pre-merge を使用します。自分が作成していないPRをレビューするときは /pre-merge --pr <番号> を使用します。
実装検証、QA受付、またはリファクタリング計画の代わりとして使用しないでください。このスキルは、作業がすでに構築され、レビューの準備ができていることを前提としています。
モード
/pre-merge は3つのモードのいずれかで実行されます。3つのモードすべてが review-checklist.md で定義されたアーキテクチャレビューの次元を再利用します。消費するものと生成するものだけが異なります。
- Authorモード(デフォルト) —
--pr引数なしで自分のブランチで呼び出されます。このスキルはPRを作成し(フェーズ2)、ターミナルに所見をアドバイザリとして出力し(フェーズ4)、その後制御を戻します。自身で修正コミットを作成したり、2回目のパスを開始したりすることはありません。 これは/executeのステップ6でHITLパス上で自動的に呼び出されるモードです。何を読むかはブランチの状態によって決まり、フラグによって決まるわけではありません。 初回レビューはスライス全体を読みます。スライス全体は、スライスの作業項目が開始位置を記録していなければブランチ全体であり、記録されていればその開始位置からです。すでにレビュー通貨スタンプが付与され、その後HEADが移動したPRに対して再実行する場合、スタンプ後のデルタ(スタンプされたSHA以降のコミット)をレビューの対象とし、以前のレビューでカバーされた部分は再読み込みしません。フェーズ1のステップ4でこの判断を行い、出力します。デルタは対象であり、レビュアーが読む範囲を制限するものではありません。review-checklist.mdがレビュアーが読む範囲を決定し、それにはデルタ周辺のツリーも含まれます。 - Reviewerモード —
--pr <番号>を指定して、自分が作成していないPRに対して呼び出されます。このスキルはPR作成(PRはすでに存在する)をスキップし、レビューして投稿するためのドラフトコメントテキスト(フェーズ4)を生成します。構造はreferences/comment-craft.md(5Pゲート、トリプルR、コメントシグナル、MMG交換)に従います。 - Loopモード —
/pre-merge --loopとして呼び出されるか、AFK実行で/executeがハンドオフするときに自動的に開始されます。Authorモードと同様にフェーズ1〜4を実行します。ただし、フェーズ1のステップ4のスコープ決定は異なります。Loopモードは常にブランチ全体を読み取り、スタンプ後のデルタを対象とすることはありません。その後、フェーズ5に進みます。すべての所見をPR本文または作業項目の耐久性のある台帳に記録し([references/state-backend.md](references/state-backend.md)§ レビューの記録が保存される場所 を参照)、各所見を支持または反証する証拠を添付し、制御を運用者に戻します。このモードでは、所見はターミナル出力ではなくなり、オーナーを持つ耐久性のある行になります。Loopモードは何も修正せず、コミットも作成しません。 各所見の処理方法は運用者が決定します(フェーズ5 § ループが修正しない理由 を参照)。
Loopモードの終了条件は、すべての所見にオーナーがいることです。レビューがクリーンに戻ったことではありません。 「所見ゼロ」は禁止された終了シグナルです。これは、フェーズ4の既存の最低所見数ガードと一致しています。このガードは、重要な差分に対してほぼ空のレビューが返ってきた場合、コードに欠陥がないのではなく、レビューが早期に停止したことを意味するために追加されました。クリーンなレビューで終了するループは、確実にクリーンなレビューとそれ以外は何も生成しません(Meadows, Seeking the Wrong Goal; Leveson: 低いインシデント件数に報いるのではなく、報告自体に報いること)。
ユーザーの作業ブランチ以外のブランチで実行しており、--pr が指定されていない場合は、推測するのではなく、特定のPR番号に対するReviewerモードを意味するのかどうかを1回だけ尋ねてください。自動検出することでキーストロークを節約できますが、モードを誤って分類すると、ローカルアドバイザリであるべきものがドラフトコメントになったり、その逆になったりします。
使用時
- Authorモード: QA合格後、フィーチャーブランチをmainにマージする前。RalphがAFK実行を完了し、動作を確認した後。または、完全なパイプライン実行がなくても、マージ前にレビューしたいブランチがある場合。
- Reviewerモード: チームメイトまたは外部コントリビューターがPRを開き、その差分に
review-checklist.mdの完全なアーキテクチャレビューを適用し、建設的なコメントテキストを生成したい場合。 - Loopモード: 所見が、それらを生成したセッションを超えて存続する必要がある場合。つまり、
/executeからのAFKハンドオフ、または複数回にわたってレビューし、毎回再導出したくないブランチがある場合。支払っているコストが所見を見失うことであり、修正を行うことではない場合に使用します。
実行フロー
フェーズ1: コンテキスト収集
Authorモード:
-
PRDイシュー番号を尋ねます。 この変更が完全なパイプラインを経由していない場合は「なし」を受け入れます。AFK
/executeハンドオフから開始されたLoopモードでは、尋ねないでください。 ハンドオフからイシュー番号を取得し(/executeステップ6が渡します)、存在しない場合は「なし」として扱います。誰も答える人がいないため、無条件の質問は最初のステップで実行を停止させてしまいます。サイドカーバックエンドでは、答えはW-<n>ワークアイテムIDです(ステップ2)。そこで「なし」とすると、フェーズ2でPRを記録する場所がなくなり、レビューノートとLoopモードの台帳の保存場所もなくなります。[references/state-backend.md](references/state-backend.md)§ レビューの記録が保存される場所 を参照してください。 -
PRDが指定された場合:
gh issue view <番号> gh issue list --search "in:body #<prd-number>" --state all --json number,title,state,body --limit 100各スライスイシューの本文からバウンダリマップ(Produces/Consumesセクション)を解析します。
サイドカーバックエンド(
bash "$SC" backend slicesがsidecarを返す場合、[references/state-backend.md](references/state-backend.md)を参照。以下のブロックがそれを確認し、拒否された場合はgh issueにフォールバックせずに停止します)では、PRDとそのスライスはワークアイテムであり、ステップ1で尋ねられた番号はW-<n>IDです。これは、このブランチが実装するスライス(/executeが渡すもの)、またはそのPRDです。gh issueコマンドは実行されません。系統とバウンダリマップはワークアイテムから取得します。SC="<このスキルのディレクトリ>/references/sidecar.sh" W="W-<n>" case "$(bash "$SC" backend slices)" in sidecar) ;; github) echo "backend is github: read the lineage with the issue block above"; exit 0 ;; *) echo "lineage aborted: sidecar.sh backend slices failed; not guessing the github path" >&2; exit 1 ;; esac J="$(bash "$SC" work view "$W" --json)" || { echo "lineage aborted: cannot read $W" >&2; exit 1; } case "$(jq -r '.kind // empty' <<<"$J")" in slice) PRD="$(jq -r '.parent // empty' <<<"$J")" ;; prd) PRD="$W" ;; *) echo "lineage aborted: $W has no kind" >&2; exit 1 ;; esac SLICES="$W" if [ -n "$PRD" ]; then bash "$SC" work view "$PRD" --json | jq -r '"# \(.id) (prd, \(.state))\n\(.body)"' SLICES="$(bash "$SC" work list --kind slice --parent "$PRD" --state all)" \ || { echo "lineage aborted: cannot list the slices of $PRD" >&2; exit 1; } fi while IFS= read -r S; do [ -n "$S" ] || continue bash "$SC" work view "$S" --json | jq -r '"# \(.id) (slice, \(.state))\n\(.body)"' done <<<"$SLICES"イシュー本文とまったく同じように、これらの本文からバウンダリマップを解析します。
$Wを保持します。フェーズ2でPRを記録する際に使用します。 -
ベースブランチを検出し、差分を評価します:
SC="<このスキルのディレクトリ>/references/sidecar.sh" # $BASE_REF は範囲の終点です。$BASE_BRANCH は `--base` が受け取る名前です。 BASE_REF="$(bash "$SC" base-ref)" || { echo "base-ref failed: not guessing a base" >&2; exit 1; } BASE_BRANCH="$(bash "$SC" base-ref --name)" || { echo "base-ref --name failed: not guessing a base" >&2; exit 1; } git diff "$BASE_REF...HEAD" --stat git log --oneline "$BASE_REF..HEAD"base-refは、サイドカーが宣言したbase_refがある場合はそれを出力し、それ以外の場合はorigin/HEADのデフォルトを出力します。ゼロ以外の終了コードの場合は参照を出力しません。ベースを推測せずに、そのstderrを中継して停止します。スタックされたPRスライスの場合、ここで設定された変数は後のブロックには到達しません。各ブロック(バックエンドのフェーズ2ブロックとフェーズ4のスタンプブロック)のBASE_BRANCH=プレースホルダーに、兄弟スライスのブランチ名(PRがターゲットとするアップストリーム)を入れます。ベースからの差分がない場合は、レビューするものがないことをユーザーに伝えて停止します。残差:
$BASE_REFは最後のgit fetch以降の鮮度しかありません。origin/<base>が解決できない場合、base-refはローカルブランチに対して測定し、その旨をstderrに出力します。カウントがおかしいと思われる場合は、git fetchして再実行してください。 -
スコープを決定します — スライス全体、またはスタンプ後のデルタ。 この決定はAuthorモードのみです。Loopモードはこれをスキップし、
SCOPE_FROM="$BASE_REF"を無条件に設定し、毎回ブランチ全体を読み取ります(フェーズ3のLoopモードの箇条書きに従います)。初回レビューはスライス全体を読み取ります。sidecar.sh rangeはその左端を与えます。左端は、ステップ2で指定された作業項目にこのブランチ上にまだ記録された開始点がある場合はその開始点であり、それ以外の場合は$BASE_REFです。したがって、githubバックエンドでは、スライスはブランチ全体です。このスキルがすでにスタンプし、その後HEADが移動したPRに対する再実行(/fix-findingsラウンド、または意図的なロック削除後の手動修正)は、スタンプ後のデルタを対象とします。スタンプはbackend stampが示す場所にあります([references/state-backend.md](references/state-backend.md)参照)。PR本文またはサイドカーです。/closeoutステップ2と同じ式でPR本文を読み取ります。scripts/test-review-currency-marker.shは2つがバイト単位で同一であることを確認し、scripts/test-delta-review-scope.shは、両方のバックエンドで、名前が挙げられているすべてのブランチ状態に対してフィクスチャを使用してこのブロックを実行します。SC="<このスキルのディレクトリ>/references/sidecar.sh" case "$(bash "$SC" backend stamp)" in sidecar) REVIEWED_SHA=$(bash "$SC" stamp read "$(git branch --show-current)") ;; github) REVIEWED_SHA=$(gh pr list --head "$(git branch --show-current)" --json body -q '.[0].body // ""' \ | sed -n 's/.*<!-- reviewed-at: \([0-9a-f]\{40\}\) -->/\1/p' | tail -1) ;; *) echo "scope aborted: sidecar.sh backend stamp failed; not guessing the github path" >&2; exit 1 ;; esac W="<ステップ2で指定されたW-<n>。githubバックエンドでは空>" BASE_REF="$(bash "$SC" base-ref)" || { echo "scope aborted: sidecar.sh base-ref failed" >&2; exit 1; } RANGE="$(bash "$SC" range ${W:+"$W"})" || { echo "scope aborted: sidecar.sh range failed" >&2; exit 1; } SLICE_FROM="${RANGE%%..*}" SCOPE_FROM="$SLICE_FROM" if [ -n "$REVIEWED_SHA" ] \ && git merge-base --is-ancestor "$REVIEWED_SHA" HEAD 2>/dev/null \ && [ "$REVIEWED_SHA" != "$(git rev-parse HEAD)" ] \ && [ -z "$(git log --merges --format=%H "$REVIEWED_SHA..HEAD")" ]; then SCOPE_FROM="$REVIEWED_SHA" elif [ -n "$REVIEWED_SHA" ] \ && ! git merge-base --is-ancestor "$REVIEWED_SHA" HEAD 2>/dev/null \ && [ "$(git rev-parse -q --verify "$REVIEWED_SHA^{tree}")" = "$(git rev-parse 'HEAD^{tree}')" ]; then SCOPE_FROM="HEAD" fi if [ "$SCOPE_FROM" = HEAD ]; then echo "review scope: none, history-only rewrite — HEAD carries the tree reviewed at $REVIEWED_SHA" elif [ "$SCOPE_FROM" = "$BASE_REF" ]; then echo "review scope: whole branch, from $BASE_REF" elif [ "$SCOPE_FROM" = "$SLICE_FROM" ]; then echo "review scope: whole slice, from $SLICE_FROM ($W's recorded start)" else echo "review scope: post-stamp delta, from $SCOPE_FROM" fi git diff "$SCOPE_FROM...HEAD" --stat git log --oneline "$SCOPE_FROM..HEAD"4つの状態は、それぞれ意図的にスライス全体にフォールバックします。スタンプがない(初回レビュー、または手動で作成されたPR)。
HEADの祖先ではなく、HEADがツリーを保持していないスタンプ(ブランチが書き換えられ、コンテンツもそれに伴って変更されたか、レビューされたコミットが消失した —/closeoutはこれをより強い分岐シグナルと呼び、より小さなレビューではありません)。HEADと等しいスタンプ(スコープする新しいものはない)。スタンプ後のマージコミット(スタンプ後の差分には、このブランチで誰も書いていないベースブランチ自身の変更が含まれる)。スライス全体は、rangeが記録された開始点を見つけられない限りブランチ全体であり、foldやrebaseによって書き換えられた場合は、その旨をstderrに出力して$BASE_REFを返し、開始点を再設定することはありません。この決定は出力されるため、ターミナルの読み取りとPRは、読み取られた内容について同じことを言います。レビューされたツリーを保持した書き換えは分岐ではありません。 スタンプされたコミットが
HEADの祖先ではないが、ローカルにまだ存在し、HEADと同じツリーを持っている場合、履歴は変更されましたが、コンテンツは変更されていません。フェーズ4のハンドオフが提供するfold(git rebase -i --autosquash)はまさにこれを生成します。ブロックはreview scope: noneを出力し、SCOPE_FROM="HEAD"を設定します。フェーズ3とフェーズ4のスタンプ以外のすべて(HEADで再スタンプする)とハンドオフメニューをスキップします。スタンプはレビューされたコンテンツを証明し、ツリーハッシュはそのコンテンツを正確に示します。スタンプされたコミットがローカルで利用できない場合、ツリーを比較できず、書き換えは上記のブランチ全体フォールバックを取ります。デルタ再実行では、デルタが対象であり、ツリーはまだ読み取り可能です。 各フェーズ3のレビュアーに、スタンプ後のコミットがクローズすると主張する所見(このセッションでの以前の読み取り、Loopモードが台帳に書き込んだ場合はその台帳(
[references/state-backend.md](references/state-backend.md)§ レビューの記録が保存される場所 を参照)、またはコミットメッセージ自体)、/fix-findingsブレーカー判定(ある場合)、およびbash "$SC" profileの出力(そのまま)を、フェーズ3の契約がすべてのレビュアーに提供するものとして、簡単に説明します。コミットごとの最初の質問は、修正がその所見のすべてのサイトで所見をクローズするかどうかです。これは修正完全性の次元です。2つ目の質問は、...
[truncated for translation]