| name | pr-human-review-gate |
| description | GitHub PR を「人間レビューが必要か」の観点で判定し、required / pending / optional の3値で報告するスキル。統制系変更・不可逆操作・セキュリティ境界・個人情報フロー・外部契約・新規パターン導入・高リスク値計算・実行時ハザードの8トリガーに照らして判定し、どれにも当てはまらなければ optional に倒す。diffの外の事実に正しさが依存する場合のみ pending とし、その場で質問して回答を反映し required / optional に確定させる。GitHubへの投稿・Approve・ラベル付与は一切行わず、会話に報告するのみ。/pr-human-review-gate コマンドで起動する。 |
| license | MIT |
| argument-hint | <PR番号 | PR URL> |
| allowed-tools | Bash(gh pr view:*), Bash(gh pr diff:*), Read, Grep, Glob, AskUserQuestion |
| disable-model-invocation | true |
PR Human Review Gate
このスキルは、GitHub PR を読み、人間のレビューが必要かどうかだけを判定する。判定値は required / pending / optional の 3 値。
目的は、レビューをリードタイムのボトルネックから外すことにある。PR の大半は人間が目を通しても指摘が出ない。そこで「本当に品質を担保すべき領域」を 8 トリガーに限定列挙し、そこに触れない PR は人間レビュー不要 (optional) として流す。
注記: このスキルに context: fork を付けてはならない。AskUserQuestion はユーザー入力を待つ UI をフロントエンドに出すため、fork したサブエージェント内では質問が届かず、「一回の実行で required / optional まで確定させる」仕様が壊れる。
起動と入力
/pr-human-review-gate コマンド経由で呼ばれる。引数は PR 番号または PR URL のいずれか:
/pr-human-review-gate 1234
/pr-human-review-gate https://github.com/owner/repo/pull/1234
gh は URL をそのまま受け取れるので、owner/repo のパースは不要。
引数がない場合は PR 番号または URL を尋ねる。このスキルは PR を対象とし、ローカルブランチの差分は扱わない。
このスキルが判定するもの / しないもの
判定するのは 人間レビュー要否の 1 軸だけ である。コードの良し悪し・バグの有無・設計の妥当性は判定しない。
この 2 軸は独立している。したがって次の両方がありうる:
- コードに問題はないが、人間も見るべき (
required) — 例: 正しく書かれた物理削除バッチ
- コードに改善余地があるが、人間レビューは不要 (
optional) — 例: 命名がいまいちな UI 変更
コード品質の指摘が必要なら別スキル (code-review 等) を使う。このスキルは「誰が見るべきか」の振り分けだけを担う。
判定の原則
このセクションがこのスキルの心臓部である。4 原則すべてを同じ重みで守る。
原則 1: デフォルトは optional
references/triggers.md の 8 トリガーのどれにも明確に該当しない変更は、すべて optional。
「ロジックが変わっている」「新しい条件分岐が増えた」「新機能である」「変更が大きい」「テストが薄い」「PR 本文が薄い」は、どれも required の理由にならない。
この向きは意図的なものである。出典記事では以前「ロジックの変更があれば人間レビュー」という広い基準を置いていたが、ほとんどの PR がそれに引っかかって人間レビュー必須に回り、ゲートが選別として機能しなくなった。基準を 8 項目に限定列挙し、そこに当てはまらなければ optional に倒す形に変えたことで、approve 率が 1 割から 58% に改善している。
原則 2: 「自信がないから required」は禁止
判定の確信度を required の根拠にしてはならない。判定は必ず、
「T の、このチェックポイントに、<file:line> のこの変更が該当する」
と名指しできる形で行う。名指しできないなら該当しない = optional。
自分がそのドメイン・技術に詳しくないこと、diff の一部しか読んでいないこと、PR 本文に説明がないことは、いずれも判定者側の情報不足であり、PR のリスクではない。情報が足りないなら Grep/Read/Glob で調べる。調べても出てこず、かつ原則 4 の 3 条件を満たすなら pending にする。それ以外は optional。
原則 3: 該当が明確なものは確実に required
原則 1・2 は「甘く判定してよい」という意味ではない。8 トリガーは、該当した場合に誤りのコストが極めて大きい領域だけを選んである。したがって:
- 1 件でも明確に該当すれば
required。 他がすべて安全でも、optional に混ぜてはならない
- トリガーに該当するかを判断するために、必要なら Grep/Read でスキーマ・既存実装・参照箇所を実際に確認する。「たぶん大丈夫」で非該当にしない
- 該当が疑われる箇所を読み飛ばして
optional にしてはならない。原則 1 は「調べた上で名指しできなかった」場合の帰結であって、調べないことの許可ではない
原則 4: pending は「diff の外の事実」に限る
pending にできるのは、次の 3 条件をすべて満たすときだけ:
- 判定に必要な事実が、diff・PR 本文・リポジトリ内コードのいずれにも存在しない (Grep/Read/Glob で調べても出てこない)
- その事実の値によって、判定が
required と optional のどちらにも転びうる
- 質問が、PR 作者が事実として答えられる形になっている (意見や判断を求める質問ではない)
典型例: 後方互換でない Webhook ペイロード変更に対し、リポジトリ内に参照がなく、外部の受信側が存在するかがコードから分からない。
pending にしてはいけない例 (Grep で分かる / どちらの答えでも判定が変わらない / トリガー該当が既に明確) は references/triggers.md 末尾に列挙してある。詳細はそちらを参照する。
pending は中間状態であり、最終報告の値にはならない。必ず対話で required / optional に確定させる。
判定ワークフロー
references/triggers.md を必ず先に読み込んでから本作業に入る。
1. PR 情報の取得
次の 2 コマンドを並列で実行する:
gh pr view <PR番号またはURL> --json number,title,body,url,author,baseRefName,headRefName,changedFiles,additions,deletions,files
gh pr diff <PR番号またはURL>
2. 読む範囲を決める
- 50 ファイル以下: 全 diff を精査する
- 50 ファイル超: トリガーに当たりそうな箇所に絞って読む
この判定は「8 トリガーに触れる変更が 1 つでもあるか」の探索である。全 diff を均等に読む必要はなく、ファイル一覧と PR 本文から当たりをつけ、gh pr diff <PR番号> -- <path> で個別に取得して読む。
当たりの付け方の例: マイグレーション/スキーマ (T2/T8)、CI ワークフロー・CODEOWNERS (T1)、認可・policy・middleware (T3)、serializer・API スキーマ (T4/T5)、Gemfile/package.json (T6)、金額・計算系 (T7)、ジョブ・バッチ (T2/T8)。
絞り込んだこと自体を required の理由にしてはならない (原則 2)。読んでいない範囲があることは、トリガーの名指しにはならない。ただし、絞り込みの根拠は報告の「確認範囲」に書く。
3. 8 トリガーとの照合
references/triggers.md の T1〜T8 について、各トリガーの「含まれる例 → 含まれない例 → チェックポイント」の順で照合する。「含まれない例」を飛ばしてはならない。トリガーの領域に隣接するだけの変更 (認可コードのリネーム、金額の表示フォーマット変更など) を該当扱いすると、このゲートは機能しなくなる。
T6 (新規パターン導入) と T8 (実行時ハザード) は、判定前に Grep/Glob/Read でリポジトリを確認することが必須。T6 は類似実装の件数、T8 はインデックス定義とバッチ上限を実際に見る。
4. 判定値の決定
- 名指しできる該当が 1 件以上 →
required
- 該当なし →
optional
- 原則 4 の 3 条件をすべて満たす未確定事実がある →
pending (ステップ 5 へ)
5. pending の場合の対話による確定
- 質問を作る前に Grep/Glob/Read でリポジトリを調べる。 答えが出るなら質問しない (原則 4 条件 1)
pending の中間報告を先に会話へ出す。 いきなり AskUserQuestion を出さない。何が未確定でなぜ聞くのかをユーザーが掴めないまま選択肢を突きつけられるのを避ける
- AskUserQuestion で質問する。 複数ある場合は 1 回の呼び出しにまとめる (最大 4 問)。
question は事実を問う形にし、各選択肢の description に「この場合 → required」「この場合 → optional」と結果を明記する
- 5 問以上になるのは、調べ足りないか原則 4 を緩めているサイン。判定に最も効く 4 問に絞り、残りは報告の「注意事項」に回す
- 回答を「diff の外の事実」として扱い再評価する。 1 問でも
required 側に倒れる回答があれば required。すべて optional 側なら optional。pending のまま終わらない
- ユーザーが「分からない」と答えた場合は
required。これは原則 2 の例外ではなく、「その事実が誰にも未確定である」という確定した事実に基づく判定である (未確定な破壊的変更を人間の確認なしに流すことはできない)
- 最終報告の「判定理由」に、どの質問にどう答えられて何が変わったかを明記する
6. 報告
次節のフォーマットで会話に報告する。GitHub への投稿は一切行わない。
報告フォーマット
会話への出力は次のテンプレートを厳守する。読み手は PR 作者とレビュワーの双方を想定しており、根拠が名指しで見えることが重要。
## 人間レビュー判定: <required | pending | optional>
- **PR**: #<番号> 「<タイトル>」
- **変更規模**: <N> ファイル (+<追加> / -<削除>)
- **確認範囲**: <全diff精査 | 絞り込み精査 (精査したファイル: ...)>
### 該当トリガー
- **<trigger_id> (<日本語名>)**: `<file:line>` — <どの変更がなぜ該当するかを1〜2行>
(該当なしの場合は「該当なし」と1行だけ)
### 判定理由
- <2〜4行。optional の場合は、検討したが該当しなかった主要な候補トリガーを挙げて示す>
### 注意事項 (任意)
- <optional だが伝えておきたい点。ここに書いた内容は判定を required に変えない>
記入例: optional
このスキルで最も重要な例である。新規ロジック・UI 変更・テスト追加をすべて含むが、候補トリガーを検討した上で非該当と判定している。
## 人間レビュー判定: optional
- **PR**: #1234 「記事一覧に公開日の並び替えを追加」
- **変更規模**: 6 ファイル (+128 / -14)
- **確認範囲**: 全diff精査
### 該当トリガー
該当なし
### 判定理由
- T5 (外部契約) を検討: `sort` はオプショナルパラメータの追加で、未指定時は従来と同じ `created_at desc`。既存クライアントに影響しないため非該当
- T8 (実行時ハザード) を検討: ソート対象の `published_at` は `db/schema.rb:88` にインデックスあり、既存のページング (`per: 20`) も維持されているため非該当
- 新規の条件分岐 (`app/queries/article_query.rb:31-38`)、UI のセレクトボックス追加、spec 追加を含むが、いずれも 8 トリガーの対象外 (原則 1)
### 注意事項
- `sort` に未知の値が来た場合のフォールバックが暗黙 (`else` なし) なので、明示すると読みやすい。判定には影響しない
記入例: required
## 人間レビュー判定: required
- **PR**: #2345 「退会ユーザーの関連データ削除バッチを追加」
- **変更規模**: 3 ファイル (+74 / -0)
- **確認範囲**: 全diff精査
### 該当トリガー
- **T2 (不可逆操作)**: `app/jobs/purge_withdrawn_users_job.rb:18` — `user.posts.destroy_all` による物理削除の新規追加。Grep で確認したが `deleted_at` / アーカイブテーブル等の復元経路はなく、revert しても削除済みデータは戻らない
- **T8 (実行時ハザード)**: `app/jobs/purge_withdrawn_users_job.rb:14` — `User.withdrawn.each` が全件をメモリに載せる。バッチサイズ・件数上限の指定がなく、対象件数はデータ量に比例して増える
### 判定理由
- 復元経路のない物理削除であり、対象範囲の誤りが取り返しのつかない結果になる (T2)
- 加えて対象件数に上限がないため、本番規模での実行時に事故を起こしうる (T8)
- 1 件でも明確な該当があれば required (原則 3)
記入例: pending の中間報告
pending は中間報告としてのみ出力する。この報告を出した直後に AskUserQuestion で質問し、回答後に required / optional の最終報告を出す。
## 人間レビュー判定: pending
- **PR**: #3456 「Webhook ペイロードから legacy_status を削除」
- **変更規模**: 2 ファイル (+3 / -9)
- **確認範囲**: 全diff精査
### 該当トリガー
- **T5 (外部契約) の判定が未確定**: `app/serializers/order_webhook_serializer.rb:12` — 既存フィールド `legacy_status` の削除。後方互換でない変更
### 判定理由
- リポジトリ内の `legacy_status` 参照は serializer と spec のみ (Grep 済み) で、同一 PR 内で更新されている
- Webhook は外部システムが受信するため、`legacy_status` に依存する受信側が存在するかで判定が分かれる。存在する → required / 存在しない → optional
- この事実はリポジトリ内に記述がなく、作者が事実として答えられる形の質問になるため pending とし、続けて確認する (原則 4)
このスキルが扱わないこと
- GitHub への外向き操作: PR へのコメント投稿・Approve・Request changes・ラベル付与・レビュワー指定は一切行わない。判定結果は会話に報告するだけ。ユーザーから求められても、
allowed-tools に投稿系コマンドを含めない設計方針を伝えて断る
- コード品質レビュー: バグ・設計・命名・可読性の指摘はしない (「判定するもの / しないもの」参照)
- CI の成否: テストや CI が通っているかは見ない。CI green を前提とする
- レビュワーの選定:
required と判定しても「誰がレビューすべきか」は判定しない
- ローカルブランチの差分: PR を対象とする
判定の限界
- 判定は diff とリポジトリ内コードに基づく。運用上の事実 (テーブルの実データ規模、Webhook の外部利用者、バックアップ運用) は原則 4 を満たす範囲で質問して補うが、質問しない範囲については diff から読める事実だけで判定する
- 絞り込み精査を行った大規模 PR では、読まなかった範囲にトリガー該当が潜む可能性が残る。この取りこぼしは設計上受け入れているトレードオフであり、
required に倒すことで埋めるものではない (原則 2)。取りこぼしを減らしたい場合は、当たりの付け方 (ワークフロー 2) を改善する
- T6 (新規パターン導入) は「コードベースに前例があるか」で判定する。前例があるパターンでも技術的に不適切な場合はありうるが、それはコード品質の問題であり、このスキルの判定軸ではない