diff --git a/docs/claude-code-web-tasks.md b/docs/claude-code-web-tasks.md index a936113d..4fa7885c 100644 --- a/docs/claude-code-web-tasks.md +++ b/docs/claude-code-web-tasks.md @@ -97,7 +97,6 @@ | 240 | T2 | ✅ | `takt.rs` の spawn/try_wait `Err(_)` → `Err(e)` + `eprintln!`(原因握り潰し解消、`.failed` marker debug 改善) | `src/cli-merge-pipeline/src/feedback/takt.rs`(60・68 行) | XS | pnpm/takt の実実行は成功条件外。compile + clippy 通過で足りる | fix(merge-pipeline): takt spawn/try_wait のエラー握り潰しを解消する | | 180 | T2 | — | `escape_markdown_pipe(&str)` を pub 追加 + `format_table` の user field に適用 + 5 variant test(markdown table 破壊の防止 / prompt injection の緩和 = defense-in-depth の一層) | `src/lib-report-formatter/src/lib.rs` | XS-S | 外部依存ゼロの純 lib。既存 private `truncate()` と escape ロジック重複、DRY 整理(共通化 or 役割分担)を検討 | | | 228 | T2 | ✅ | `evaluate_rate_limit_shortcut` の cr_clean 判定(`new_comments` / `actionable_comments` / `unresolved_threads` 3 field × None/Some 境界)の回帰テスト | `src/cli-pr-monitor/src/stages/poll/rate_limit_signal.rs`(末尾 tests) | S | pure 関数、silent-clean 誤認保護。同 crate の `#[ignore]` 統合テストは無関係 | test(check-ci): rate-limit shortcut の cr_clean 判定をテストで固定する | -| 339 | T2 | ✅ | CR rate-limit 3 世代 format × 4 parse path(old/new/next/fallback)× 主要 CR state の複合マトリックステスト | `src/check-ci-coderabbit/src/decide.rs`(既存 `mod tests`) | S | 既存 helper(`pr309_incident_*` 等)と世代別書式を組み合わせるだけ。純 parse + decide | test(check-ci): CR rate-limit パースの複合マトリックステストを追加する | | 178 | T2 | — | `state.rs` の behavioral invariant test を ADR-041 pattern(sentinel 事前投入 + mutation 不在 assert)で 3-5 件追加 | `src/cli-pr-monitor/src/state.rs` | S | **todo 提案の invariant #1/#2 は実挙動と不一致**。`update_state_from_check_result` の実挙動を読んで実在する invariant を再選定する | | | 239 | T2 | ✅ | `filter_transcripts` の `read_dir` 非決定順を timestamp ソートで決定論化 + 回帰テスト | `src/cli-merge-pipeline/src/feedback/transcript.rs`(`filter_transcripts` + tests) | M | temp-dir に複数 jsonl 生成 → 順序 assert で完結。実 hook 発火不要 | fix(merge-pipeline): transcript の読み取り順を timestamp ソートで決定論化する | diff --git a/docs/todo-summary2.md b/docs/todo-summary2.md index cd8a0aee..2ddb1e3c 100644 --- a/docs/todo-summary2.md +++ b/docs/todo-summary2.md @@ -94,7 +94,6 @@ | 336 | 🚀 Tier 1 | **post-merge-feedback の分析ソース選定を対象 PR の commit/bookmark 照合ベースに修正 — 時刻範囲のみ選定を廃止 (#311/#312 post-merge feedback 採用)** | todo14.md | M | なし (時刻範囲のみの pre-push run / transcript 選定が並行 push (#311/#312/#313) で他 PR 知見を誤帰属、#311/#312 feedback で実地確認。post-merge-feedback 分析範囲欠陥として過去 3 回 recurrence した先行 todo の同型・より深刻版。#311 feedback=✅ / #312 feedback=🤔 と判定割れだが両者実害確認済、ADR-042 で mechanizable=Yes) | | 337 | 🔧 Tier 2 | **並行テストで thread::spawn 結果を Vec::collect 後に判定する pattern を custom lint 強制 (#312 post-merge feedback 採用)** | todo14.md | M | なし (#312 で遅延イテレータが実行中 thread を drop し「2 Acquired」偽陽性、collect で回避した実績。thread::spawn は 8 ファイルで使用され再発余地。対象を concurrent test 近傍限定で FP 軽減。analyzer Tier1 = mechanical enforcement → memory `feedback_tier_classification` per project Tier 2 に再分類) | | 338 | 🔧 Tier 2 | **CodeRabbit rate-limit format の fixture ライブラリ化 + 新世代検出の定期 CI 検証 (#311 post-merge feedback 採用)** | todo14.md | M | なし (CR は 2026-01→05→07 で 3 回 format 変更。ADR-049 fixture 化を CI 定期検証まで拡張し silent drift を land 前に捕捉。ADR-065 の CI matrix を土台にできる。順位 343 (ADR-034 SOP) と相補) | -| 339 | 🔧 Tier 2 | **3 世代 CR format × 4 parse path × CR state の複合マトリックステスト (#311 post-merge feedback 採用)** | todo14.md | S | なし (verdict_*_takes_precedence は #311 の 7 テストで解消済だが format 世代軸 (old/new/next/fallback) の網羅は未実施。新世代追加時の回帰防止、Frequency Medium) | | 340 | 🔧 Tier 2 | **decide.rs/main.rs の境界値・parameter threading テスト拡充 (#311 post-merge feedback 採用)** | todo14.md | S | なし (前回 incident の根本原因 = parameter threading 欠落と同クラスのリグレッション防止。positive evidence 複合 + main.rs の rate_limit 構成検証。インシデントドメイン直下で Severity Medium) | | 341 | 💎 Tier 3 | **Silent Fallback 排除原則を CLAUDE.md 開発 convention に明文化 (#311 post-merge feedback 採用)** | todo14.md | XS | なし (#311/#309 の rate-limit marker 検知 + wait 解析失敗で None 誤認 = fail-open の再発防止。自動 lint は意味論解析要で FP 過多につき却下、人間向けガイドラインで担保。Severity High) | | 342 | 💎 Tier 3 | **Positive Evidence Requirement を CLAUDE.md/ADR に明文化 (#311 post-merge feedback 採用)** | todo14.md | M | なし (#311 の「commit status pass を review 実行と同一視」fail-open の再発防止。外部監視は成功定義を単一ソースで充足させず陽性証拠を要求。has_review_evidence が参考実装、ADR-009/018/034 系列に適用可) | diff --git a/docs/todo14.md b/docs/todo14.md index 2d06b0a3..0b211784 100644 --- a/docs/todo14.md +++ b/docs/todo14.md @@ -234,28 +234,6 @@ --- -### 3 世代 CR format × 4 parse path × CR state の複合マトリックステスト - -> **動機**: CodeRabbit nitpick (`verdict_*_takes_precedence` 不足) は #311 の 7 テストで解消済みだが、**format 世代軸** (old / new / next / fallback の 4 parse path) × CR state の組合せ網羅は未実施で、新世代 format 追加時の回帰防止に不足がある。 -> -> **対処案**: `check-ci-coderabbit/src/decide.rs` の `#[cfg(test)]` に parametrized matrix test を追加し、3 世代 CR format × 4 parse path × 主要 CR state の組合せを網羅する。 -> -> **参照**: `.claude/feedback-reports/311.md` Tier2 #1、`src/check-ci-coderabbit/src/decide.rs`。 -> -> **実行優先度**: 🔧 Tier 2 — Severity Medium / Frequency Medium (format 世代は今後も増える見込み) / Effort S / Adoption Risk None。 - -#### 作業計画 - -- [ ] format 世代 × parse path × CR state の parametrized matrix test を追加 -- [ ] 新世代 format 追加時に fixture を足す運用と紐付け -- [ ] 本エントリ削除 + todo-summary2.md 行削除 - -#### 完了基準 - -- 全 CR format 世代 × parse path × state の組合せがテストで固定され、新世代追加時のリグレッションを機械検知できること。 - ---- - ### decide.rs/main.rs の境界値・parameter threading テスト拡充 > **動機**: 前回 incident の根本原因は parameter threading の欠落 (`parse_rate_limit()` はするが `decide()` に渡さない) だった。同クラスのリグレッションを防ぐテストが、インシデント発生ドメイン (rate-limit 判定) 直下で不足している。positive evidence の複合シナリオ、呼び出し側 (`main.rs`) が `decide()` に `rate_limit` を正しく構成することの検証が未固定。 diff --git a/src/check-ci-coderabbit/src/decide.rs b/src/check-ci-coderabbit/src/decide.rs index 4ab3759c..f19f99ba 100644 --- a/src/check-ci-coderabbit/src/decide.rs +++ b/src/check-ci-coderabbit/src/decide.rs @@ -639,4 +639,106 @@ mod tests { assert!(summary.contains("新規指摘3件")); assert!(summary.contains("未解決スレッド1件")); } + + /// 3 世代 CR rate-limit format (old/new/next) + 未知書式 fallback の計 4 parse path + /// それぞれが `crate::rate_limit::parse_rate_limit` で実際に検出され、かつ `decide()` + /// の主要な CR state 分岐で世代非依存に同じ結果を返すことを固定するマトリックステスト。 + /// + /// `decide()` は `rate_limit.is_some()` だけを見て `wait_minutes` 等の中身を見ないため、 + /// 世代が増えても分岐結果は変わらないはずだが、それを固定していないと新世代追加時に + /// この暗黙の前提が崩れても気づけない (#311 post-merge feedback Tier2 #1 採用)。 + #[test] + fn decide_matrix_across_rate_limit_format_generations_and_cr_states() { + struct FormatCase { + label: &'static str, + body: &'static str, + } + // old/new/next は rate_limit.rs の既存 fixture と同じ実書式を再利用。 + // fallback は marker のみ一致し既知 3 世代のどれにも一致しない未知書式。 + let format_cases = [ + FormatCase { + label: "old", + body: "Rate limit exceeded\n\nPlease wait **5 minutes and 13 seconds** before requesting another review.", + }, + FormatCase { + label: "new", + body: "\n\n> [!WARNING]\n> ## Review limit reached\n>\n> More reviews will be available in 36 minutes and 52 seconds.", + }, + FormatCase { + label: "next", + body: "\n\n> [!WARNING]\n> ## Review limit reached\n>\n> **Next review available in:** **57 minutes**", + }, + FormatCase { + label: "fallback", + body: "Rate limit exceeded but the wording changed in a way none of the known parsers recognize.", + }, + ]; + + struct StateCase { + label: &'static str, + cr: fn() -> CodeRabbitStatus, + expected: (&'static str, &'static str), + } + let state_cases = [ + StateCase { + label: "no_evidence_with_stale_unresolved_threads", + cr: pr309_incident_cr_status, + expected: ("pending", "continue_monitoring"), + }, + StateCase { + label: "walkthrough_clean_no_unresolved", + cr: || CodeRabbitStatus { + review_state: "not_found".to_string(), + new_comments: 0, + actionable_comments: None, + unresolved_threads: Some(0), + walkthrough_clean: true, + }, + expected: ("complete", "stop_monitoring_success"), + }, + StateCase { + label: "actionable_evidence_present", + cr: || CodeRabbitStatus { + review_state: "success".to_string(), + new_comments: 0, + actionable_comments: Some(3), + unresolved_threads: Some(0), + walkthrough_clean: false, + }, + expected: ("action_required", "action_required"), + }, + ]; + + for format_case in &format_cases { + let json = serde_json::json!([{ + "user": {"login": "coderabbitai[bot]"}, + "body": format_case.body, + "created_at": "2026-07-20T12:10:47Z" + }]) + .to_string(); + let rl = crate::rate_limit::parse_rate_limit(&json, "2026-07-20T12:00:00Z") + .unwrap_or_else(|| { + panic!( + "{} format must be detected as rate-limited by parse_rate_limit", + format_case.label + ) + }); + + for state_case in &state_cases { + let ci = ci_no_runs("success"); + let cr = (state_case.cr)(); + let (status, action) = decide(&ci, &cr, Some(&rl)); + assert_eq!( + status, state_case.expected.0, + "format={} state={}: status mismatch", + format_case.label, state_case.label + ); + assert_eq!( + action, state_case.expected.1, + "format={} state={}: action mismatch", + format_case.label, state_case.label + ); + } + } + } }