diff --git a/docs/todo-summary.md b/docs/todo-summary.md index 86f92bba..f1a8906c 100644 --- a/docs/todo-summary.md +++ b/docs/todo-summary.md @@ -48,8 +48,7 @@ | 78 | 💎 Tier 3 | **ADR-NNN (採番未確定、land 時に確定): Rust timestamp arithmetic safety + CLAUDE.md security 拡充 (PR #115 T3-1 採用) ★ Bb-3 follow-up** | todo5.md | S | なし (config が user-editable system boundary のとき `sanitize()` 値域検証を必須化し dependent arithmetic に `// SAFETY: により上限保証` コメントを要求するパターンを ADR + CLAUDE.md に codify、Rust 固有の checked_add + MAX_SAFE capping + time-dependent test の 3 層を明文化。2026-05-16 entry 登録時の旧予約 ADR-038 → ADR-041 振り直し → 順位 139 (PR #168 follow-up) ADR-041 取得に伴い 2026-05-22 再 placeholder 化、land 時 PR で空き番号確定 — 順位 135 codified placeholder policy の実例運用) | | 79 | 💎 Tier 3 | **`docs-governance.md` § Retirement Workflow に「残タスクの lifecycle 整合」要件明記 (PR #117 T3-1 採用)** | todo5.md | XS | なし (PR #117 で順位 15 を Bb-3 で吸収済として削除した際、現 Step 2「残タスクを priority table に登録」が priority table から除外するケース = 完了/deprioritize/defer を未定義だった実証。除外時の commit/PR で 3 値のいずれかを明示する要件を追加して将来の同型 ambiguity を構造的に防ぐ) | | 81 | 🚀 Tier 1 | **cli-pr-monitor: CR 投稿エラー (`Failed to post review comments`) auto-retry 拡張 (PR #120 T1-2 採用) ★ Bundle f (defer)** | todo5.md | M | 1 観測のみで systemic 性未確認 (§A-2 P-5 PR で defer 判断、ADR-018 §追記 2026-05-08 で re-trigger 条件 = 2 件以上の同型観測を規定) | -| 91 | 🔧 Tier 2 | **`[lint_screen]` config parse テスト (PR #132 T2-#4 採用) ★ Bundle i** | todo6.md | S | なし (PR #132 で追加した push-runner-config.toml の `[lint_screen]` section に対する toml::from_str テスト、CodeRabbit nitpick 起点、silent field rename 防止) | -| 92 | 🔧 Tier 2 | **scale-aware eval fixtures (200+ 行) — Phase d 投入前の必須 infrastructure (PR #132 T2-#5 採用) ★ Bundle i** | todo6.md | M | 順位 91 と同 PR 推奨 (Bundle i コア、PR #132 smoke で観測した mistral:7b 大規模 diff JSON 不完全 (`missing field 'screen_decision'`) を fixture 化、Phase d 着手前の改善 ループ reference point 確保) | +| 92 | 🔧 Tier 2 | **scale-aware eval fixtures (200+ 行) — Phase d 投入前の必須 infrastructure (PR #132 T2-#5 採用)** | todo6.md | M | なし (PR #132 smoke で観測した mistral:7b 大規模 diff JSON 不完全 (`missing field 'screen_decision'`) を fixture 化、Phase d 着手前の改善 ループ reference point 確保。順位 91 = Bundle i ペアタスクは既存 test カバー判明で 2026-05-24 削除済) | | 93 | 💎 Tier 3 | **`coding-style.md` Cross-File Reference Lifecycle に partial fix 例を追記 (PR #132 T3-#8 採用)** | todo6.md | XS | なし (PR #94 / #111 / #132 で反復した「変更差分外ファイルへの partial fix 再発」パターンを anti-pattern 例として codify、独立並列実施可) | | 95 | 🔧 Tier 2 | **`docs/todo*.md` preamble file count 自動照合スクリプト (PR #133 T2-#4 採用) ★ Bundle j** | todo6.md | S | なし (PR #133 で todo6.md「六つ」/ todo7.md「七つ」が実 8 ファイルと乖離した実例。todo*.md 分割が今後も繰り返す pattern (todo3 → 4 → 5 → 6 → 7) のため CI 層で自動検証) | | 96 | 🔧 Tier 2 | **Markdown cross-reference validator CI step (PR #133 T2-#3 採用) ★ Bundle j** | todo6.md | M | 順位 10 (ADR-032 PR-broken-link) と方向性が近接、fold-in 検討の余地あり。順位 94 (regex 規約) + 順位 95 (count 照合) と組み合わせて docs/ 整合性の多層検証 | @@ -73,6 +72,7 @@ | 139 | 💎 Tier 3 | **ADR-041: Test Isolation Patterns for Multi-Condition Guards (PR #168 T3-#2 採用) — 本 PR で land** | todo8.md | M | なし (PR #120 W-001 初発見 + PR #168 sentinel pattern 実装の 2 PR 横断で Frequency Medium、`feedback_no_unenforced_rules.md` 例外 = 既存実践の明文化 + project-specific 実装例 (poll.rs) + PR #120 W-001 history codify、順位 84 = code-review.md global checklist の補完 layer、順位 135 codified placeholder 番号 policy 適用 — 当初 ADR-NNN placeholder で entry 登録 → land 時 PR で ADR-041 確定取得、順位 78 を ADR-NNN に再 placeholder 化) | | 140 | 💎 Tier 3 | **順位 135「codified placeholder policy」を正式 ADR に昇格 (PR #169 T3-#2 採用)** | todo8.md | S | なし (順位 135 entry を retire し、ADR-NNN (採番未確定、land 時に確定): ADR Numbering Strategy として永続化。PR #111/#132/#169 の 3+ PR で適用実証済 — PR #169 で「ADR-038 → 041 → NNN」3 段振り直し dogfood が land、ephemeral todo entry 限りでは派生プロジェクトへの transferability 不足、`feedback_no_unenforced_rules.md` 例外 = 既存実践 (3 PR で実証) の明文化 + 後続 entry が同 policy を参照する際の永続 reference 確保) | | 141 | 🚀 Tier 1 | **CR rate-limit detection bug 修正 — fix_push_time 固定 + 早期 merge 判断 signal (PR #169 観測由来)** | todo8.md | S | なし (PR #169 セッションで systemic 観測 = wakeup ごとの push_time 更新で CR walkthrough overlay の updated_at が「過去扱い」になり parse_rate_limit の event_time >= push_time filter で除外される構造バグ、`feedback_pipeline_over_rules` 適用 = パイプライン側機械的修正で Claude 判断介入を排除、wall clock 短縮 = rate-limit 検出時に mergeable CLEAN なら 5-10 分で人間判断 (38 分 reset 待ちを bypass)、既存 auto-retry path は維持 (ユーザーが「待つ」選択時は通常 flow)、Bundle a Sub-PR 2 / Bundle f scope 外の独立 layer) | +| 142 | 💎 Tier 3 | **ADR-041 補強 — "State Preservation Invariant" pattern section 追加 (PR #170 T3-#1 採用)** | todo8.md | S | なし (PR #168/169/170 で連続観測の write-once 不変式 (once-set-never-overwritten) パターンを ADR-041 に追記、`state.fix_push_time.or_else(...)` 形式の 3 点セット test pattern (既存値あり / 新値提供 / preservation 確認) を明文化、参照実装 = poll.rs `finalize_*_preserves_existing_fix_push_time` + monitor.rs `resume_returns_fix_push_time_from_state_when_set`、ADR-041 既存 section (Multi-Condition Guards) とは別 pattern class、`feedback_no_unenforced_rules.md` 例外 = 既存実践 (3 PR で実証) の明文化 + 派生プロジェクト transferability 確保) | **戦略**: Tier 1 を 2〜3 セッションで片付け → Tier 2 で ADR-032 の前提 + rate-limit + convergence cost 削減を進める → Tier 3 で ADR-032 を land + ドキュメント整備。Tier 4-5 は cleanup / 外部展開で daily efficiency への直接効果は小さい。 diff --git a/docs/todo6.md b/docs/todo6.md index ae9b8e79..38ee5027 100644 --- a/docs/todo6.md +++ b/docs/todo6.md @@ -10,34 +10,6 @@ ## 現在進行中 -### `[lint_screen]` config parse テスト (PR #132 T2-#4 採用) ★ Bundle i - -> **動機**: PR #132 (Phase c MVP) で `push-runner-config.toml` に新 section `[lint_screen]` を追加したが、`config.rs` の test module には parse テストが不在。CodeRabbit nitpick で指摘 (`config_parses_with_diff` 相当が `[lint_screen]` には未存在)。serde TOML は field name の完全一致を要求するため、parse テストがないと将来の field rename / 追加で silent `None` fallback が発生し、機能が無音で停止するリスクがある。 -> -> **本タスクの位置づけ**: PR #132 post-merge-feedback Tier 2 #4 採用 (Frequency Medium / Effort S / Adoption Risk None)。 -> -> **参照**: `.claude/feedback-reports/132.md` Tier 2 #4、`src/cli-push-runner/src/config.rs` (テスト module の `config_parses_with_diff` を template に踏襲)、PR #132 commit `73903d72` (lint_screen config 追加) -> -> **実行優先度**: 🔧 **Tier 2** — Effort S。順位 92 と同 PR (Bundle i) 推奨。 - -#### 作業計画 - -- [ ] `src/cli-push-runner/src/config.rs` の `#[cfg(test)] mod tests` に `config_parses_with_lint_screen_section` を追加 -- [ ] 全 7 field (`enabled`, `exe_path`, `model`, `endpoint`, `timeout_secs`, `max_diff_lines`, `output_path`) の deserialize 検証 -- [ ] `enabled = false` でも `Option` が `Some(...)` で構築されることを assert (= section があれば parse される、なくなれば None) -- [ ] 一部 field 省略時に default (`None`) になることを assert (`config.lint_screen.unwrap().exe_path.is_none()` 等) - -#### 完了基準 - -- 上記テストが pass -- 将来 `LintScreenConfig` に field 追加 / rename した時に test 側で気付ける構造になる - -#### 詰まっている箇所 - -なし - ---- - ### scale-aware eval fixtures (200+ 行) — Phase d 投入前の必須 infrastructure (PR #132 T2-#5 採用) ★ Bundle i > **動機**: PR #132 smoke dogfood で 868 行の現実 PR diff を mistral:7b に流したところ、JSON 出力が不完全 (`missing field 'screen_decision'`) になり fallback path が作動した。Phase b' eval fixtures (10-30 行/件) では出ない failure mode で、Phase d 本番 PR 投入時に頻発するリスクが顕在化していた。fixture 化することで再現可能化し、 §8.D prompt v3 / v4 改善ループの reference point として固定する。 diff --git a/docs/todo8.md b/docs/todo8.md index f88569de..2ff23d1e 100644 --- a/docs/todo8.md +++ b/docs/todo8.md @@ -519,6 +519,49 @@ CR 仕様変更時は graceful degradation: 検出が壊れたら shortcut signa --- +### ADR-041 補強 — "State Preservation Invariant" pattern section 追加 (PR #170 T3-#1 採用) + +> **動機**: PR #170 post-merge-feedback analyzer が **PR #168/169/170 で write-once 不変式 (once-set-never-overwritten) のテストカバレッジ漏れが連続観測** されたことを Frequency Medium で識別。ADR-041 (Test Isolation Patterns for Multi-Condition Guards) の既存 section は early-return guard (sentinel pattern + 直交 precondition) のみで、`state.fix_push_time.or_else(...)` のような **write-once 不変式は別 pattern class** として未収録。順位 141 で takt-fix が自動追加した 3 件の preservation test (poll.rs `finalize_*_preserves_existing_fix_push_time` / monitor.rs `resume_returns_fix_push_time_from_state_when_set`) が、ADR-041 の延長として補強される自然な pattern であることが post-merge analyzer により独立識別された。 +> +> **本タスクの位置づけ**: PR #170 post-merge-feedback Tier 3 #1 採用。`feedback_no_unenforced_rules.md` の例外 = 既存実践 (3 PR で実証) + project-specific 参照実装の明文化 + 派生プロジェクト transferability 確保。Severity Low / **Frequency Medium (PR #168/169/170 の 3 PR 横断)** / Effort S / Adoption Risk None。 +> +> **参照**: `.claude/feedback-reports/170.md` Tier 3 #1、`docs/adr/adr-041-test-isolation-patterns.md` (本セッション順位 139 で land 済、本 task で補強)、`src/cli-pr-monitor/src/stages/poll.rs` (preservation test 2 件)、`src/cli-pr-monitor/src/stages/monitor.rs` (preservation test 1 件)、PR #168/169/170 history +> +> **実行優先度**: 💎 **Tier 3** — Effort S。既存 ADR への追記のみ (新規 ADR / コード変更なし)。 + +#### 設計決定 (案) + +analyzer report の `[ADR-041 追加 section 案]` をベースに、`docs/adr/adr-041-test-isolation-patterns.md` の「## 適用範囲」セクションの前に新 section `## 補足: State Preservation Invariant パターン (once-set-never-overwritten)` を挿入する。内容構成: + +- **パターン定義**: `state.fix_push_time = state.fix_push_time.or_else(|| ctx.fix_push_time.map(String::from));` 形式の write-once 不変式コード例 +- **3 点セット test**: + 1. `state.fix_push_time = Some("old_time")` — 既存値あり (preservation される側) + 2. `ctx.fix_push_time = Some("new_time")` — 新値を提供 (上書きを試みる側) + 3. `assert_eq!(state.fix_push_time, Some("old_time"))` — old value が retain されたことを確認 +- **Anti-pattern**: 全テスト fixture を `fix_push_time: None` で統一すると "don't overwrite" branch (preservation path) が実行されず coverage = 0 +- **適用タイミング**: 新 field を追加し、その field が `or_else` / `if existing.is_none() { ... }` 等の write-once 意味論を持つ場合、**field 追加と同一 PR で** 上記 3 点セット test を追加する +- **参照実装**: PR #170 で land された 3 件 (`finalize_initial_review_park_preserves_existing_fix_push_time` / `finalize_review_recheck_park_preserves_existing_fix_push_time` / `resume_returns_fix_push_time_from_state_when_set`) +- **由来**: PR #170 simplicity-review F-2 + post-merge analyzer session で観測 + +#### 作業計画 + +- [ ] `docs/adr/adr-041-test-isolation-patterns.md` に新 section `## 補足: State Preservation Invariant パターン (once-set-never-overwritten)` を挿入 (上記 6 項目) +- [ ] `## 適用範囲` セクション内の対象記述に「write-once 不変式を持つ pure function 系 state 更新」を追記 (既存 = 2+ 条件の OR/AND 早期 return を持つ pure function 系 test、追加 = write-once 不変式パターン) +- [ ] `## 改訂履歴` に「2026-05-23: PR #170 T3-#1 採用、State Preservation Invariant section 追加」を追記 +- [ ] 本 todo8.md entry を削除 (本 ADR 補強で内容が ADR に migrate されるため、`feedback_todo_no_history` 適用) + +#### 完了基準 + +- ADR-041 に State Preservation Invariant section が追加され、3 点セット test pattern + 参照実装 + Anti-pattern + 適用タイミングが記述される +- 次回 write-once 不変式 field を追加する PR で、本 ADR section を直接 cite して 3 点セット test を実装できる +- 順位 142 entry が todo8.md から削除される + +#### 詰まっている箇所 + +なし。記述のみで実装変更不要。順位 141 と異なり ADR 本体への追記のみで完結する。 + +--- + ## 既知課題 (記録のみ、本セッションで未対応) ### post-merge-feedback workflow が長時間 stale marker を残す問題 (PR #119 marker observed 2026-05-15) diff --git a/src/hooks-post-tool-comment-lint-rust/src/main.rs b/src/hooks-post-tool-comment-lint-rust/src/main.rs index c305abe2..e833a079 100644 --- a/src/hooks-post-tool-comment-lint-rust/src/main.rs +++ b/src/hooks-post-tool-comment-lint-rust/src/main.rs @@ -763,6 +763,38 @@ mod tests { assert_eq!(violations.len(), MAX_VIOLATIONS); } + /// 順位 57 (PR #105 T2-1 採用): `collect_all_violations` の `truncate(MAX_VIOLATIONS)` + /// contract test。`find_violations` (comment-forbidden 系) と + /// `find_function_length_violations` (function-too-long 系) の **両 source 混在** で + /// 合計が MAX_VIOLATIONS を超えるとき、最終 vec が MAX_VIOLATIONS に cap されることを + /// 機械強制する。 + /// + /// 既存 cap test (`max_violations_capped` / `function_length_violations_capped`) は + /// 各 source 単体の cap のみを検証しており、`collect_all_violations` で + /// `truncate(MAX_VIOLATIONS)` を削除した場合 (= 両 source 合算が cap を超える regression) + /// は検知できない gap がある。本 test は将来の lint 追加時にもこの contract を seal する。 + #[test] + fn collect_all_violations_truncates_combined_total_to_max() { + let mut source = String::new(); + source.push_str("fn long_function() {\n"); + for i in 0..(MAX_FUNCTION_LINES + 5) { + source.push_str(&format!(" let _x{} = {};\n", i, i)); + } + source.push_str("}\n"); + for i in 0..(MAX_VIOLATIONS + 5) { + source.push_str(&format!("// trailing comment {}\n", i)); + } + + let violations = collect_all_violations("test.rs", &source, None); + + assert_eq!( + violations.len(), + MAX_VIOLATIONS, + "combined violations from both sources must be truncated to MAX_VIOLATIONS (= {})", + MAX_VIOLATIONS + ); + } + #[test] fn is_rust_file_accepts_rs() { assert!(is_rust_file("main.rs")); diff --git a/src/hooks-post-tool-linter/src/main.rs b/src/hooks-post-tool-linter/src/main.rs index 2967e6b7..f2083ee8 100644 --- a/src/hooks-post-tool-linter/src/main.rs +++ b/src/hooks-post-tool-linter/src/main.rs @@ -1156,6 +1156,59 @@ mod tests { assert_eq!(violations.len(), MAX_CUSTOM_VIOLATIONS); } + /// 順位 125 (PR #151 T2-#1 横展開) defensive seal: `build_violation_json` の + /// `content[..m.start()]` は `regex::Match::start()` が char boundary を保証するため + /// panic 安全だが、multi-byte content でも line 算出 + /// (`.bytes().filter(|b| *b == b'\n').count() + 1`) が正しく動作することを + /// empirical に seal する。 + /// + /// fixture lines (1-indexed): + /// - L1: Japanese text (3 bytes/char) — non-match + /// - L2: emoji (4 bytes) — non-match + /// - L3: ASCII match (expect line=3) + /// - L4: combining character (e + U+0301) — non-match + /// - L5: ASCII match (expect line=5) + /// + /// 将来 `content[..m.start()]` を `char_indices()` 等に書き換えた際の line off-by-one + /// regression を catch する。 + #[test] + fn run_custom_rules_line_number_correct_with_multibyte_content() { + use std::io::Write; + let dir = tempfile::tempdir().unwrap(); + let file = dir.path().join("multibyte_fixture.ts"); + { + let mut f = std::fs::File::create(&file).unwrap(); + writeln!(f, "// 日本語コメント").unwrap(); + writeln!(f, "// 🦀 rust").unwrap(); + writeln!(f, "console.log('after multibyte');").unwrap(); + writeln!(f, "// caf\u{00e9}").unwrap(); + writeln!(f, "console.log('second');").unwrap(); + } + + let rules = compile_test_rules(vec![make_test_rule( + "no-console-log", + r"console\.log\(", + &["ts"], + )]); + let violations = run_custom_rules(file.to_str().unwrap(), &rules); + + assert_eq!( + violations.len(), + 2, + "two console.log violations expected after multi-byte content" + ); + let v1: serde_json::Value = serde_json::from_str(&violations[0]).unwrap(); + let v2: serde_json::Value = serde_json::from_str(&violations[1]).unwrap(); + assert_eq!( + v1["location"]["line"], 3, + "first violation should be on line 3 (after multi-byte L1 + L2)" + ); + assert_eq!( + v2["location"]["line"], 5, + "second violation should be on line 5 (after combining char L4)" + ); + } + #[test] fn run_custom_rules_outer_break_skips_subsequent_rules() { use std::io::Write;