Skip to content

feat(hooks-post-tool-linter): WP-08 incident→eval 回帰スイート — 由来 incident 再現 fixture + 実 exe E2E + fixture 必須 fail-closed gate (ADR-049) - #254

Merged
aloekun merged 1 commit into
masterfrom
wp-08-incident-eval
Jul 6, 2026
Merged

feat(hooks-post-tool-linter): WP-08 incident→eval 回帰スイート — 由来 incident 再現 fixture + 実 exe E2E + fixture 必須 fail-closed gate (ADR-049)#254
aloekun merged 1 commit into
masterfrom
wp-08-incident-eval

Conversation

@aloekun

@aloekun aloekun commented Jul 6, 2026

Copy link
Copy Markdown
Owner

概要

ハーネス改善実行計画書 (docs/harness-improvement-plan.md) の WP-08: incident→eval 回帰スイート を実装する。目的は「ハーネス自体の退行」の機械検出。カスタムリントルールの多くは実 incident (過去 PR の事故) 由来だが、ルールがその incident を今も検出できるかを機械検証する仕組みが無かった。設計は ADR-049 に集約。

標準手法 (回帰テスト + linter fixture corpus) の応用に provenance ポリシー (各ルールが由来 incident と再現 fixture を機械可読に持つ) を重ねた構成。

調査で判明した前提の修正

  • カスタムルールは 12 本中 11 本が実 incident 由来(rule① no-console-log は汎用サンプルで incident 由来でない。ADR-042 の「11 本」と整合)。計画の「12 本全て由来」は要調整だった。
  • provenance は従来 toml コメントの散文のみ → 機械可読化が必要。
  • カスタムリンターは非致命(違反は stdout の severity 付き JSON、exit 0)。block/warn の assert は「stdout JSON を parse して severity を検査」。

変更内容

provenance の構造化

  • .claude/custom-lint-rules.toml の incident 由来 11 ルールに [rules.incident] (pr / bad_fixture / good_fixture / adr) を追加。CustomRuleIncident struct として parse。
  • 追跡鎖 incident (PR) → rule → fixture (bad/good) → regression test → ADR を機械可読化。

fixtures (tests/fixtures/incidents/{bad,good}/, 22 個)

  • 1 fixture = 1 failure mode(各 bad は該当ルールの incident パターンのみ。LLVM/rustc UI test 流儀)。
  • good (negative) fixture 必須(bad は fire、good は fire しない = false-positive 退行ガード)。
  • synthetic test data であることと由来 PR を冒頭コメントで明示。

Hook E2E test (tests/incident_eval.rs)

  • 実 exe を CARGO_BIN_EXE で spawnPostToolUse JSON を stdin→stdout parse(内部関数呼びでなく arg/stdin パース〜feedback〜exit の全経路を通す)。
  • assert は type / severity / line のみ(feedback 全文は固定せず、文言修正で壊れない)。
  • paths filter 付きルール (rule⑨) は temp-CWD 配下の .takt/workflows/ に stage し path filter も検証。
  • 本 repo 初の exe-spawn integration test パターン(WP-16 CI smoke test で流用可能)。

coverage gate (incident_fixture_coverage_check)

  • incident 由来ルールに [rules.incident] + bad/good fixture 実在を fail-closed で強制(ADR-043)。rule① は NON_INCIDENT_RULES allowlist で免除。

fixture の隔離(ハーネス運用を壊さない)

  • src/** の外(deployed_tests.rs clean-baseline に触れない)・markdownlint ignore・.rs//! doc ヘッダ(comment-lint 回避)。カスタムリンター自体が非致命のため運用は不変。

検証

  • cargo test -p hooks-post-tool-linter: 146 unit + E2E(1) + coverage gate、全 ok
    • incident_eval_all_incident_rules ... ok(bad 11 全発火・good 11 全 clean を実 exe で確認)
    • incident_fixture_coverage_check ... ok(fail-closed ゲート)
  • cargo clippy --all-targets -- -D warnings: 0 warnings
  • lint:docs OK / lint:md 0 errors
  • pre-push AI レビュー: simplicity / security とも approved(fixtures を意図的 test data と認識)

関連

  • ADR-049(本 PR で新規起票)/WP-08 状態を「実装済」に更新
  • ADR-007(対象 11 ルールの居所)・ADR-036(comment-lint、fixture ヘッダ制約源)・ADR-042(「11 ルール」由来カウント)・ADR-043(fail-closed)

Summary by CodeRabbit

  • New Features

    • ルールの由来情報と再現用フィクスチャをひも付ける仕組みを追加し、回帰確認をより追跡しやすくしました。
    • 実行時の回帰テストで、対象ケースの「発火する/しない」を自動検証するようになりました。
  • Bug Fixes

    • テスト用の合成データが通常の品質チェックに誤検知されないよう、対象ディレクトリを除外しました。
    • 既存ルールのテスト定義を整理し、追加メタ情報が欠けにくくなりました。

…t 再現 fixture + 実 exe E2E + fixture 必須 fail-closed gate (ADR-049)
@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

ADR-049に基づき、.claude/custom-lint-rules.tomlの11ルールへ[rules.incident]メタを追加し、CustomRule型にincidentフィールドとCustomRuleIncident構造体を導入した。対応するbad/goodフィクスチャ、coverage gate、E2Eテスト(incident_eval.rs)を新規追加し、ADR文書・改善計画・markdownlint設定を更新した。

Changes

Incident→Eval 回帰スイート

Layer / File(s) Summary
CustomRuleIncident 型とルール型の拡張
src/hooks-post-tool-linter/src/custom_rules/types.rs, src/hooks-post-tool-linter/src/custom_rules/deployed_tests.rs, .../engine_tests.rs, .../rule_tests.rs, .../rule_tests_extras.rs
CustomRuleincident: Option<CustomRuleIncident>を追加し、CustomRuleIncident(pr/bad_fixture/good_fixture/adr)を新規定義。既存テストのmake_test_ruleincident: Noneを明示。
ルール定義への incident メタ付与
.claude/custom-lint-rules.toml
11ルールに[rules.incident]を追加し、参照PR番号、bad/goodフィクスチャ名、ADR参照を定義。
Incident再現用 bad/good フィクスチャ
tests/fixtures/incidents/bad/*, tests/fixtures/incidents/good/*, .markdownlint-cli2.jsonc
各ルール向けの合成テストデータ(発火用bad、非発火用good)を追加し、markdownlintのignoresへ対象ディレクトリを追加。
Incident fixture coverage gate
src/hooks-post-tool-linter/src/custom_rules/coverage.rs
NON_INCIDENT_RULESincident_fixtures_dircollect_incident_gapsincident_fixture_coverage_checkを追加し、incident由来ルールのfixture実在をfail-closedで検証。
incident_eval.rs E2E 回帰テスト
src/hooks-post-tool-linter/tests/incident_eval.rs, src/hooks-post-tool-linter/Cargo.toml
linterバイナリを実起動しbad/goodフィクスチャで発火/非発火を検証するE2Eテストを追加、serde_jsonをdev-dependenciesに追記。
ADR-049と改善計画の更新
docs/adr/adr-049-incident-eval-regression-suite.md, CLAUDE.md, docs/harness-improvement-plan.md
ADR-049を新規追加し、CLAUDE.mdのADR一覧とWP-08のステータスを「実装済」に更新。

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Test as incident_eval.rs
  participant Linter as hooks-post-tool-linter
  participant Fixture as incident fixture

  Test->>Test: ensure_rules_toml_beside_exe
  Test->>Fixture: stage(workflow_rel)
  Test->>Linter: run_linter(PostToolUse JSON stdin)
  Linter-->>Test: stdout JSON
  Test->>Test: parse_custom_lint_violations
  Test->>Test: assert_bad_fixture_fires / assert_good_fixture_clean
Loading

Possibly related PRs

  • aloekun/claude-code-hook-test#76: no-personal-pathsルールに対応するincident-eval用フィクスチャとcoverage/E2E検証を追加している点で関連。
  • aloekun/claude-code-hook-test#126: no-time-field-strict-greaterルールに紐づくincident回帰用bad/goodフィクスチャとcoverage/E2E検証を追加している点で関連。
  • aloekun/claude-code-hook-test#217: src/hooks-post-tool-linter/src/custom_rules/coverage.rsへの同種のカバレッジ検証ロジック追加がコードレベルで関連。
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 主要な変更点である WP-08 incident→eval 回帰スイート、fixture、実 exe E2E、fail-closed gate を的確に要約しています。
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch wp-08-incident-eval

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/hooks-post-tool-linter/tests/incident_eval.rs (1)

111-131: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

子プロセス待機にタイムアウトがない

child.wait_with_output() にタイムアウトが設定されておらず、被テストバイナリが何らかの理由でハングした場合、このテストひいてはCIジョブが無期限にブロックされる可能性があります。

♻️ ウォッチドッグスレッドによるタイムアウト案(概念例)
+use std::sync::mpsc;
+use std::time::Duration;
+
 fn run_linter(cwd: &Path, invoke_path: &str) -> Vec<serde_json::Value> {
     let payload = serde_json::json!({ "tool_input": { "file_path": invoke_path } }).to_string();
     let mut child = Command::new(exe_path())
         .current_dir(cwd)
         .stdin(Stdio::piped())
         .stdout(Stdio::piped())
         .stderr(Stdio::piped())
         .spawn()
         .expect("spawn hooks-post-tool-linter");
     child
         .stdin
         .take()
         .expect("child stdin")
         .write_all(payload.as_bytes())
         .expect("write stdin payload");
-    let out = child.wait_with_output().expect("wait for linter exe");
+    let (tx, rx) = mpsc::channel();
+    std::thread::spawn(move || {
+        let _ = tx.send(child.wait_with_output());
+    });
+    let out = rx
+        .recv_timeout(Duration::from_secs(30))
+        .expect("linter exe timed out")
+        .expect("wait for linter exe");
     let stdout = String::from_utf8_lossy(&out.stdout);
     parse_custom_lint_violations(&stdout)
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hooks-post-tool-linter/tests/incident_eval.rs` around lines 111 - 131,
The test helper run_linter currently waits on the spawned hooks-post-tool-linter
process with child.wait_with_output() without any timeout, so add bounded
waiting around the child execution to prevent hangs; use the existing run_linter
and child setup in incident_eval.rs to locate it, and implement a timeout-based
wait strategy (for example via a watchdog thread or equivalent) that kills or
aborts the child if it exceeds a reasonable limit before parsing stdout.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/hooks-post-tool-linter/tests/incident_eval.rs`:
- Around line 111-131: The test helper run_linter currently waits on the spawned
hooks-post-tool-linter process with child.wait_with_output() without any
timeout, so add bounded waiting around the child execution to prevent hangs; use
the existing run_linter and child setup in incident_eval.rs to locate it, and
implement a timeout-based wait strategy (for example via a watchdog thread or
equivalent) that kills or aborts the child if it exceeds a reasonable limit
before parsing stdout.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: abb2867f-8700-411a-b50c-7adaadc8bbf6

📥 Commits

Reviewing files that changed from the base of the PR and between f19a5bb and bbde02d.

📒 Files selected for processing (35)
  • .claude/custom-lint-rules.toml
  • .markdownlint-cli2.jsonc
  • CLAUDE.md
  • docs/adr/adr-049-incident-eval-regression-suite.md
  • docs/harness-improvement-plan.md
  • src/hooks-post-tool-linter/Cargo.toml
  • src/hooks-post-tool-linter/src/custom_rules/coverage.rs
  • src/hooks-post-tool-linter/src/custom_rules/deployed_tests.rs
  • src/hooks-post-tool-linter/src/custom_rules/engine_tests.rs
  • src/hooks-post-tool-linter/src/custom_rules/rule_tests.rs
  • src/hooks-post-tool-linter/src/custom_rules/rule_tests_extras.rs
  • src/hooks-post-tool-linter/src/custom_rules/types.rs
  • src/hooks-post-tool-linter/tests/incident_eval.rs
  • tests/fixtures/incidents/bad/no-docs-relative-back-to-docs.md
  • tests/fixtures/incidents/bad/no-empty-powershell-catch.ps1
  • tests/fixtures/incidents/bad/no-ephemeral-todo-reference.rs
  • tests/fixtures/incidents/bad/no-hardcoded-jj-revset-range.rs
  • tests/fixtures/incidents/bad/no-jj-template-first-line.toml
  • tests/fixtures/incidents/bad/no-mutable-anchor.md
  • tests/fixtures/incidents/bad/no-personal-paths.md
  • tests/fixtures/incidents/bad/no-silent-error-action.ps1
  • tests/fixtures/incidents/bad/no-time-field-strict-greater.rs
  • tests/fixtures/incidents/bad/no-write-result-discard.rs
  • tests/fixtures/incidents/bad/takt-workflow-persona-without-model.yaml
  • tests/fixtures/incidents/good/no-docs-relative-back-to-docs.md
  • tests/fixtures/incidents/good/no-empty-powershell-catch.ps1
  • tests/fixtures/incidents/good/no-ephemeral-todo-reference.rs
  • tests/fixtures/incidents/good/no-hardcoded-jj-revset-range.rs
  • tests/fixtures/incidents/good/no-jj-template-first-line.toml
  • tests/fixtures/incidents/good/no-mutable-anchor.md
  • tests/fixtures/incidents/good/no-personal-paths.md
  • tests/fixtures/incidents/good/no-silent-error-action.ps1
  • tests/fixtures/incidents/good/no-time-field-strict-greater.rs
  • tests/fixtures/incidents/good/no-write-result-discard.rs
  • tests/fixtures/incidents/good/takt-workflow-persona-without-model.yaml

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant