원클릭으로
cross-review
PR を codex / gemini 両方にレビューさせ、両方 APPROVE まで /ndf:review → /ndf:fix を自動ループ。サブエージェント分離・PR ローテーション・nit 集約でメイン context 消費を最小化
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
PR を codex / gemini 両方にレビューさせ、両方 APPROVE まで /ndf:review → /ndf:fix を自動ループ。サブエージェント分離・PR ローテーション・nit 集約でメイン context 消費を最小化
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
修正を複数ブランチに適用する際のブランチ戦略。featureブランチへの先行commitとcherry-pickによる短命branchへの適用手順。環境別branch(qa/staging/epsilon等)への修正適用時に参照する。
DeepWiki (Devin MCP) のドキュメント内容を対象リポジトリの Markdown ファイルとして転載する。セクション構成維持・番号付きファイル分割・GFM 準拠補正・日本語翻訳 (オプション) まで自動処理。
PRのレビューコメントを確認し、優先度に応じてコード修正を実行する。サブエージェント (general-purpose) 起動にも対応。--defer-nit / --severity-min で対応範囲を制御。
実装プランファイル作成・更新の手順。実装開始時およびPR作成時にissues/配下の実装プランの有無を確認し、なければ会話履歴・git log・git diffから生成する。複数ファイル変更・新規機能追加・DBマイグレーション伴う変更が対象。
調査レポート作成のルール。否定的結論のエビデンス要件、残課題の記載フォーマット、ハルシネーション防止のための裏取り原則を扱う。DB調査に限らずコードベース調査・仕様調査一般に適用。
ログを追加・修正する際のガイドライン。ログレベルの選択基準、ループ内ログの制御、エラー蓄積パターン、機密情報の扱いを扱う。言語/FW非依存の原則。
| name | cross-review |
| description | PR を codex / gemini 両方にレビューさせ、両方 APPROVE まで /ndf:review → /ndf:fix を自動ループ。サブエージェント分離・PR ローテーション・nit 集約でメイン context 消費を最小化 |
| argument-hint | [PR番号] [--max-rounds N] [--rotate-after K] [--only codex|gemini] |
| disable-model-invocation | true |
| allowed-tools | ["Bash","Read","Edit","Write","Glob","Grep"] |
PR を codex / gemini 両方 にレビューさせ、両者が APPROVE を返すまで
/ndf:review と /ndf:fix を自動で回す。
詳細手順は docs/ 配下に、主要コマンドは scripts/ 配下に分割している:
monitor.py の薄ラッパ(互換用)メインセッションからは $SCRIPTS/state.py <subcommand> 形式で呼ぶだけで、
state.json の読み書きや AI launcher 起動・完了待ちは全て委譲される。
長丁場が予想されるため メインセッションの context 消費を最小化 する:
| 観点 | 方針 |
|---|---|
| レビュー投稿 | AI 自身が gh api で PR に直接投稿。メインはペイロードを保持しない |
| 修正 | 必ずサブエージェント (general-purpose) で実行。メイン context に diff は載せない |
| ユーザ問い合わせ | 自動判断を最大化(critical/major/minor は自動修正、nit は最後にまとめて 1 回だけ問い合わせ) |
| 状態の永続化 | /tmp/cross-review-pr<番号>-state.json に集約。中断・再開可能 |
| 長尺PR対策 | --rotate-after ラウンドで PR をローテーション(squash + 新ブランチ + 新 PR) |
| 振動検知 | 同じ指摘が 2 round で 50%以上重複したら中断 |
| 引数 | 意味 | 既定 |
|---|---|---|
[PR番号] | 対象 PR(省略時は直前 PR / 現在ブランチ) | — |
--max-rounds N | 全体最大ラウンド数(PR ローテーションを含む通算) | 6 |
--rotate-after K | この round 数で未収束なら PR ローテーション | 5 |
--only codex / --only gemini | 片方だけで回す(デバッグ用) | 両方 |
例:
/ndf:cross-review 123
/ndf:cross-review 123 --max-rounds 4 --rotate-after 2
/ndf:cross-review 123 --only codex
/ndf:review が AI 直接投稿(外部 AI 自身が gh api で投稿)に対応/ndf:fix が サブエージェント起動 + 重要度ベース自動修正 + Resolve Conversation に対応codex / gemini CLI が動作し、gh CLI が認証済みAgent(subagent_type="general-purpose", ...) でサブエージェントを起動可能state.py init が自動実施)ループ開始前に 4 つのプリチェック が必要だが、すべて scripts/state.py init
が内部で実施する。メインは結果を KEY=VALUE 形式で受け取るだけで良い。
| # | 対策 | スクリプト側で何をするか |
|---|---|---|
| 1 | 自分の PR 判定(422 回避) | gh api user と gh pr view --json author を比較し is_own_pr / event_downgrade を state.json に書く |
| 2 | worktree 分離 | git worktree add /work/worktrees/pr<PR> <head> を冪等実行 |
| 3 | gemini trusted directory | launch-gemini.sh が GEMINI_CLI_TRUST_WORKSPACE=true + --skip-trust を必ず併用。さらに tmp dir は ~/.gemini/tmp/<workspace>/ を採用し、gemini の workspace 制約 (workspace 外の read_file / write_file がブロックされる) を回避 |
| 4 | 既存コメント差分 | gh api .../comments --paginate を $TMP_DIR/cross-review-pr<PR>-existing-comments.txt に保存し、gemini プロンプトには 内容をインライン埋め込み、codex プロンプトには path を渡す |
GitHub は 自分の PR には REQUEST_CHANGES でレビューを投稿できない
(HTTP 422)。state.json には 両方 を保持する:
"codex": {
"intent": "REQUEST_CHANGES", // AI の本来判定。ループ収束判定に使う
"posted_as": "COMMENT", // 422 回避でダウングレードした結果
"comments": 5, "review_url": "..."
}
state.py judge は intent を見るので、ダウングレード投稿してもループは続行する。
flowchart TD
Start([事前確認 / loop 開始前に 1 回だけ]):::phase --> Init["worktree 作成 + state.json 初期化<br/>・自分の PR 判定 → event downgrade 設定<br/>・/work/worktrees/pr<PR> を用意<br/>・既存コメントスナップショット保存"]
Init --> Round["Round N start<br/>current_pr = PR#"]:::phase
Round -.並列バックグラウンド.-> Codex["/ndf:review <PR> codex<br/>(AI が gh api で直接投稿)<br/>body 先頭: cross-review / round N / codex / intent<br/>→ result.json (intent + posted_as)"]
Round -.並列バックグラウンド.-> Gemini["/ndf:review <PR> gemini<br/>--skip-trust 必須<br/>body 先頭: cross-review / round N / gemini / intent<br/>→ result.json (intent + posted_as)"]
Codex --> Decide{"判定 (intent ベース)"}
Gemini --> Decide
Decide -->|両方 APPROVE / SKIP| Approved([final = approved]):::ok
Decide -->|一方でも REQUEST_CHANGES| Fix["Agent (general-purpose)<br/>/ndf:fix <PR> --defer-nit を worktree 内で実行<br/>・critical/major/minor 修正 + push<br/>・reply + resolveReviewThread<br/>・deferred/rejected は reply のみ<br/>→ /tmp/fix-pr<#>-result.json"]
Fix --> Check{収束チェック}
Check -->|max-rounds 到達| MaxR([final = max_rounds]):::stop
Check -->|振動検知 50% 重複| Osc([final = oscillation]):::stop
Check -->|CI failure code-related| Err([final = error]):::stop
Check -->|"CI failure meta-only (Assignees 等)"| Round
Check -->|round_in_pr >= rotate-after| Rotate["PR rotation<br/>squash + 新ブランチ + 新 PR"]
Check -->|それ以外| Round
Rotate --> Round
Approved --> Nit[最後に 1 回<br/>deferred nit 一覧をユーザに問い合わせ]
MaxR --> Nit
Osc --> Nit
Err --> Nit
classDef phase fill:#eef,stroke:#557
classDef ok fill:#dfd,stroke:#383
classDef stop fill:#fdd,stroke:#933
各ステップの詳細は docs/ 参照。メインは以下のテンプレートで scripts/ を呼ぶだけ:
SCRIPTS="$CLAUDE_PLUGIN_ROOT/skills/cross-review/scripts"
# STATE_PR は state.json のキー (= 最初に init した PR 番号)。
# rotation 後も state.json のパスは変わらないため、scripts/ への引数には常に
# STATE_PR を渡す。「現在レビュー中の PR」は state.json の current_pr を内部参照する。
STATE_PR=$INITIAL_PR
# Step 0: state 初期化 / 再開
eval "$("$SCRIPTS/state.py" init "$STATE_PR" \
--max-rounds "$MAX_ROUNDS" --rotate-after "$ROTATE_AFTER" \
${ONLY:+--only "$ONLY"})"
# eval で TMP_DIR がセットされる。後続スクリプトに env として伝播させる。
export CROSS_REVIEW_TMP_DIR="$TMP_DIR"
cd "$WORKTREE"
while :; do
# Step 1: round 開始判定 (max_rounds 到達で exit 1)
eval "$("$SCRIPTS/state.py" start-round "$STATE_PR")"
# Step 2: 並列レビュー
[ "$ONLY" != "gemini" ] && "$SCRIPTS/launch-codex.sh" "$STATE_PR" "$ROUND"
[ "$ONLY" != "codex" ] && "$SCRIPTS/launch-gemini.sh" "$STATE_PR" "$ROUND"
# 監視: 既定 timeout=7 分 / stall=3 分。失敗時は対象プロセスを kill して返す。
"$SCRIPTS/monitor.py" "$STATE_PR" "${ONLY:-both}" || handle_review_failure $?
[ "$ONLY" != "gemini" ] && "$SCRIPTS/state.py" read-result "$STATE_PR" codex
[ "$ONLY" != "codex" ] && "$SCRIPTS/state.py" read-result "$STATE_PR" gemini
# Step 3: 判定 (0=approved/2=continue)
if "$SCRIPTS/state.py" judge "$STATE_PR"; then break; fi
# Step 4: 振動検知 (4=oscillation)
"$SCRIPTS/state.py" check-oscillation "$STATE_PR" || [ $? -eq 2 ] || exit 4
# Step 5: 修正サブエージェント起動 (Agent tool) → /tmp/fix-pr<STATE_PR>-result.json
# - メインで Agent(subagent_type=general-purpose, ...) を呼ぶ。docs/02 参照
# - tmp パスは launcher / monitor.py と同じく **STATE_PR ベース** で統一
# Step 5 後段: fix 戻り値マージ + CI 分類 (3=code-fail で中断)
"$SCRIPTS/state.py" merge-fix "$STATE_PR"
# Step 6: PR ローテーション判定 (0=rotate/2=keep)。state.json の current_pr を内部更新。
if "$SCRIPTS/state.py" should-rotate "$STATE_PR"; then
eval "$("$SCRIPTS/rotate-pr.sh" "$STATE_PR")" # NEW_PR を eval で取り込む
"$SCRIPTS/state.py" set-current-pr "$STATE_PR" "$NEW_PR"
# NOTE: STATE_PR は変えない。次ループの scripts も $STATE_PR を渡す。
fi
done
# Step 8: 終了処理 (deferred nit + ラウンドサマリ)
"$SCRIPTS/state.py" report "$STATE_PR"
各ステップの内容と契約(state.json / result.json スキーマ等)の詳細は:
目的: PR 上に Resolve 義務を伴うインラインコメントを増やさない。 修正アクションを伴わない記述は一切出さない ことを両 launcher プロンプトで強制する。
人間アカウントから AI が投稿するため、GitHub UI 上では誰のレビューか分からない。 body 先頭に必ず以下を入れる:
## 🤖 cross-review | round 1 | codex | REQUEST_CHANGES
書式: ## 🤖 cross-review | round <N> | <agent> | <event>
<agent>: codex / gemini のいずれか<event>: AI の本来の判定(REQUEST_CHANGES / APPROVE / COMMENT)
posted_as ではなく intent を書くインラインコメントは GitHub 上で Resolve 操作が必須 になるため、本当に直すものだけ作る:
| 重要度 | インライン化 | 説明 |
|---|---|---|
critical / major | ✅ する | 修正必須 |
minor | ✅ する | 明らかな改善のみ。判断が割れるなら出さない |
nit | ❌ 出さない | 好み・スタイルはコメント化禁止。気になっても無視する |
1 インラインコメント = 1 修正アクション を厳守。
コメント本文は [重要度 / カテゴリ] 修正提案 の 1 文で完結させ、
コード引用ブロック(...)や現状説明だけのコメントは作らない。
body に書くのは 設計レベル・PR 横断の修正提案 のみ。
書くことが無ければ body は ## 🤖 cross-review ... の prefix 行 + 1 行サマリのみで良い。
APPROVE — 修正必須の指摘なし(minor 以下しか無い場合も APPROVE で良い)REQUEST_CHANGES — critical / major の指摘ありCOMMENT — 基本使わない。雑感だけの投稿は禁止「CI 失敗 → 即 final=error」は乱暴。scripts/state.py merge-fix が
fix 戻り値ファイル (/tmp/fix-pr<PR>-result.json) を受け取った際に
ci_failed_checks を以下で分類する:
| 分類 | パターン | 振る舞い |
|---|---|---|
| code-fail | pint / larastan / phpstan / test / lint / type / build / ruff / eslint / tsc / mypy | final=error で中断 (exit 3) |
| meta-only | check_pr_requirements / assignees / reviewers / labels / meta | ci_note に記録して継続 |
| 不明 | 上記以外 | 保守的に code-fail 扱い |
PR メタデータ系の check(Assignees / Reviewers / Labels)は 継続、 pint / larastan / test / build などは 中断 を原則とする。
max-rounds なしで回す — 無限ループの温床REQUEST_CHANGES で投稿 — 必ず 422。事前判定 + COMMENT ダウングレードgemini --yolo だけで起動 — trusted directory で YOLO 無効化。--skip-trust 併用pgrep -fa <prompt> で完了判定 — gemini は long prompt が引数に乗り検知失敗。pidfile 必須monitor.py の多軸判定 (pidfile / sentinel / 早期エラー / stall / hard timeout / result.json) を使うことmonitor.py の hard timeout (30 分既定) + stall timeout (10 分既定) を必ず効かせる/tmp/ に置き、メインは state.json と result.json だけ読むループ終了後、メインからユーザへの報告:
最終ステータス: approved / max_rounds / oscillation / error
総ラウンド数 / PR 数: 例: 5 rounds / 2 PRs (rotated 1 回)
PR 履歴: 各 PR 番号 + closed/open 状態 + round 数
各ラウンドのサマリ表:
| round | PR | codex | gemini | fix | CI |
|---|---|---|---|---|---|
| 1 | #123 | REQ (5) | REQ (3) | abc123 (5 fixed, 2 deferred) | ✅ |
| 2 | #123 | REQ (2) | APP | def456 (2 fixed) | ✅ |
| 3 | #145 | APP | APP | — | — |
残 deferred nit リスト(ユーザ判断要)
rejected 件数(bot 誤指摘で却下したもの)
最終 PR URL
詳細は PR 上のインラインコメントと state.json に残っているため、本報告では 繰り返さない。
/ndf:review — 単発レビュー(AI 直接投稿対応)/ndf:fix — 修正対応(サブエージェント起動対応)/ndf:codex — codex CLI 呼び出し手順/ndf:gemini — gemini CLI 呼び出し手順/ndf:resolve-pr-comments — Resolve Conversation の詳細general-purpose エージェント — fix 実行用サブエージェント