Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions docs/adr/adr-019-coderabbit-review-hybrid-policy.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,13 @@ WP-03 は 2026-04-19 で却下した「rate-limit 耐性 (超過後の auto-retr
- **手動 fix push は手動トリガーが必要**: 明示トリガーは監視の auto-push 経路 (`auto_push_severity` = critical/major) のみ。ユーザー手動 push (severity=none / minor) 後は `@coderabbitai review` を手動投稿する (fail-open のログが誘導する)。
- **設定の二重管理**: `.coderabbit.yaml` の `auto_incremental_review` と `pr-monitor-config.toml` の `trigger_review_after_push` は必ず揃える (前者 false ⇔ 後者 true)。揃わないと再レビュー欠落 (両 off) or 二重レビュー (両誤設定) になる。派生プロジェクトの template では default false + コメント例で明示。

#### 再トリガー抑止ガード (2026-07-05 追記、WP-05 follow-up)

CodeRabbit は「**既にレビュー済みのコミットは再レビューしない**」incremental 仕様のため、同一 HEAD への `@coderabbitai review` 再投稿は**レート枠を消費するだけの無駄**になる。加えて OSS/public リポのレビュー枠は有償 Pro (5 レビュー/時) より低くプロジェクトの人気度依存のため、無駄消費を構造的に避ける必要がある。

- **決定論層 (機構)**: 監視の明示トリガー (`src/cli-pr-monitor/src/stages/review_trigger.rs`) は投稿前に `head_already_reviewed(pr, repo)` で「現 HEAD がいずれかの CodeRabbit review の `commit_id` と一致するか」を gh 照会し、一致 (= レビュー済み) なら `@coderabbitai review` を skip する。判定不能 (gh 照会失敗 / repo 未確定) は fail-open で投稿し、再レビュー欠落を招かない (確証がある `Some(true)` のときだけ skip)。
- **運用層 (規律)**: 手動で `@coderabbitai review` を投げる場合も**同一 HEAD に再投稿しない**。2026-07-05 セッションで、fix の手動 push 後に同一 HEAD へ複数回 `@coderabbitai review` を投稿し (CodeRabbit は毎回「already reviewed, nothing to do」を返すだけ)、レート枠を無駄消費した実例に由来する。新規コミット (別 SHA) への 1 回の明示トリガーは意図した消費であり抑止対象ではない (新しい修正はレビューされるべき)。

#### 受け入れ基準 (dogfood)

rate 解除待ちの発生が 1 回/日未満になること。導入後の実績で確認する。未達なら `auto_pause` 値 / トリガー条件を調整、または `enabled = false` (フル手動トリガー) への切替を再検討する。
Expand Down
73 changes: 72 additions & 1 deletion src/cli-pr-monitor/src/stages/review_trigger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,29 @@ pub(crate) fn should_trigger_review_after_push(push_ok: bool, flag: bool) -> boo
pub(crate) fn trigger_coderabbit_review() {
use crate::state::{read_state_from, state_file_path};

let Some(pr) = read_state_from(&state_file_path()).and_then(|s| s.pr) else {
let Some(state) = read_state_from(&state_file_path()) else {
log_info(
"[review_trigger] state を読み込めず @coderabbitai review をスキップ (必要なら手動投稿してください)",
);
return;
};
let Some(pr) = state.pr else {
log_info(
"[review_trigger] PR 番号が state から解決できず @coderabbitai review をスキップ (必要なら手動投稿してください)",
);
return;
};

if let Some(repo) = state.repo.as_deref() {
if head_already_reviewed(pr, repo) == Some(true) {
log_info(&format!(
"[review_trigger] PR #{} の現 HEAD は既に CodeRabbit レビュー済みのため @coderabbitai review をスキップ (再トリガー抑止、レート消費回避)",
pr
));
return;
}
}

let pr_str = pr.to_string();
if crate::runner::run_gh_quiet(&["pr", "comment", &pr_str, "--body", "@coderabbitai review"])
.is_none()
Expand All @@ -47,6 +64,39 @@ pub(crate) fn trigger_coderabbit_review() {
));
}

/// 現 HEAD が既に CodeRabbit にレビュー済みか判定する (WP-05 follow-up、再トリガー抑止)。
///
/// `Some(true)` = レビュー済み / `Some(false)` = 未レビュー / `None` = 判定不能。
/// CodeRabbit の PR review は submit された commit を `commit_id` に持つため、現 HEAD が
/// いずれかの CodeRabbit review の `commit_id` と一致すれば「その HEAD はレビュー済み」。
/// gh 照会失敗時は `None` を返し、呼び出し側は fail-open (投稿) にする。確証がある
/// `Some(true)` のときだけ skip し、再レビュー欠落を招かない設計。
fn head_already_reviewed(pr: u64, repo: &str) -> Option<bool> {
let pr_str = pr.to_string();
let head = crate::runner::run_gh_quiet(&[
"pr", "view", &pr_str, "--json", "headRefOid", "--jq", ".headRefOid",
])?;
let head = head.trim();
if head.is_empty() {
return None;
}
let reviewed = crate::runner::run_gh_quiet(&[
"api",
"--paginate",
&format!("repos/{}/pulls/{}/reviews", repo, pr),
"--jq",
r#".[] | select(.user.login=="coderabbitai[bot]") | .commit_id"#,
])?;
Some(is_head_in_reviewed(head, &reviewed))
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

/// CodeRabbit がレビューした commit SHA 一覧 (改行区切り) に現 HEAD が含まれるかの純粋判定。
fn is_head_in_reviewed(head: &str, reviewed_commit_ids: &str) -> bool {
reviewed_commit_ids
.lines()
.any(|line| line.trim() == head)
}

#[cfg(test)]
mod tests {
use super::*;
Expand All @@ -64,4 +114,25 @@ mod tests {
);
assert!(!should_trigger_review_after_push(false, false));
}

#[test]
fn is_head_in_reviewed_detects_already_reviewed_head() {
let reviewed = "abc1230000\ndef4560000\n789aaa0000";
assert!(
is_head_in_reviewed("def4560000", reviewed),
"現 HEAD が CodeRabbit review 済み SHA 集合に含まれる → 再トリガー抑止"
);
assert!(
is_head_in_reviewed("789aaa0000", " 789aaa0000 \nabc1230000"),
"前後空白を trim して一致判定する"
);
assert!(
!is_head_in_reviewed("999zzz0000", reviewed),
"未レビューの新 HEAD は含まれない → 投稿する"
);
assert!(
!is_head_in_reviewed("abc1230000", ""),
"CodeRabbit review が無ければ false (投稿する)"
);
}
}