From 28e6d473e2aa952160940b6ad4d008a12a674594 Mon Sep 17 00:00:00 2001 From: aloekun Date: Tue, 26 May 2026 22:29:43 +0900 Subject: [PATCH 1/3] =?UTF-8?q?docs(todo):=20PR=20#175=20post-merge-feedba?= =?UTF-8?q?ck=20=E6=8E=A1=E7=94=A8=201=20=E4=BB=B6=E3=82=92=20todo9.md=20/?= =?UTF-8?q?=20summary=20table=20=E3=81=AB=E7=99=BB=E9=8C=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR #175 (Bundle 2) post-merge-feedback で採用判定された Tier 1 #1 を docs/todo9.md 新規エントリとして追加し、docs/todo-summary.md table に行追加。 - 順位 159 (T1 #1): jj template 内で脆弱な `description.first_line()` パターン を lint で禁止する custom lint rule (rule ⑪) 追加 (PR #175 Minor finding 由来 の ad-hoc fix を構造化予防に格上げ、`empty` keyword 利用促進、Effort XS、 対象 ext = toml/yaml/md) --- docs/todo-summary.md | 1 + docs/todo9.md | 46 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/docs/todo-summary.md b/docs/todo-summary.md index af81126..b41f195 100644 --- a/docs/todo-summary.md +++ b/docs/todo-summary.md @@ -77,6 +77,7 @@ | 153 | 🔧 Tier 2 | **`review-harness-whole` facet 追加 — 観点 ① 独立 facet 化 (順位 8 follow-up、Phase B+1、2026-05-26 ユーザー合意) ★ 週次拡張** | todo9.md | S | 順位 8 Phase B land + 2-3 週 dogfood 後に着手判断 (extract 不要なら close)、順位 146-151 Bundle 既存ルール仕組み化の継続的発見源、architecture-whole から ① 観点を extract して context 圧迫回避 | | 154 | 🔧 Tier 2 | **`review-todo-whole` facet + aggregate 前 file size pre-step — 観点 ⑤ ⑦ 拡張 (順位 8 follow-up、Phase B+1、2026-05-26 ユーザー合意) ★ 週次拡張** | todo9.md | M | 順位 136 land + Phase B 2-3 週 dogfood 完了後着手、順位 95 / 147 と scope 整理必要 (CI 即時 vs 週次 batch)、ADR-031 3 層分離原則で file size は LLM 不要の Rust pre-step に分離 | | 155 | 🚀 Tier 1 | **cli-pr-monitor fix chain 末尾に空 commit 検査 + `jj abandon` step 追加 (PR #174 T1-#1 採用)** | todo9.md | S | なし (PR #174 で `kqvluqyv` 空 commit が PR diff 汚染した実証ベース、`master..@` 範囲を `jj log` で sweep して機械強制、既存 `CleanupEmptyFixCommit` action の補完層) | +| 159 | 🚀 Tier 1 | **jj template 内で脆弱な `description.first_line()` パターンを lint で禁止 (PR #175 T1-#1 採用)** | todo9.md | XS | なし (PR #175 Minor finding 由来の ad-hoc fix を構造化予防に格上げ、`empty` keyword 利用促進、対象 ext = toml/yaml/md で jj template DSL 固有 pattern → false positive 極小、testing.md § Custom Lint Rule Test Coverage 準拠で test_coverage meta field 必須) | | 157 | 🔧 Tier 2 | **Bundle 1 dogfood checklist 実行 — `__test.ps1` block + override env 確認 (PR #174 T2-#2 採用、ADR-039 bounded lifetime data point #1)** | todo9.md | XS | なし (PR #174 PR body の未消化 dogfood、Bundle 2 PR merge 前の前提条件として消化、結果は Bundle 2 PR body に記録) | **戦略**: 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/todo9.md b/docs/todo9.md index 565410e..3944e89 100644 --- a/docs/todo9.md +++ b/docs/todo9.md @@ -532,6 +532,52 @@ --- +### jj template 内で脆弱な `description.first_line()` パターンを lint で禁止 (PR #175 T1-#1 採用) + +> **動機**: PR #175 で 順位 155 entry の設計を書いた際、`jj log` template 内の `description.first_line()` と filter logic `description = ""` を組み合わせたが、両者は意味的に整合せず CR Minor finding で「全 description 空」vs「複数行 description で 1 行目だけ空」を区別できない曖昧性を指摘された。fix では `empty` keyword + `\u{1f}` Unit Separator 設計に変更したが、ad-hoc な修正に留まり今後別の場所で同型ミスを書く可能性が残る。`description.first_line()` パターン自体を lint で禁止し、`empty` keyword 利用を促進する構造化予防が必要。 +> +> **本タスクの位置づけ**: PR #175 post-merge-feedback Tier 1 #1 採用 (Severity Medium / Frequency Low / Effort XS / Adoption Risk None)。jj template 固有 pattern で false positive 極小、Effort XS で機械強制可能、rubric 充足。ad-hoc fix を構造的予防に格上げする「PR 自身の経験を直接 codify する自己強化パターン」(Bundle 1 順位 155 / Bundle 2 順位 156, 158 と同系統)。 +> +> **参照**: `.claude/feedback-reports/175.md` Tier 1 #1、PR #175 CR Minor finding (id 3303489656、`docs/todo9.md` line 465 周辺、現 fix 適用済)、PR #175 Minor fix commit (`b10a83ba` / `lnxmoxoz`)、`.claude/custom-lint-rules.toml` (rule ⑪ 新規追加先)、`~/.claude/rules/common/testing.md` § Custom Lint Rule Test Coverage +> +> **実行優先度**: 🚀 **Tier 1** — Effort XS。custom lint rule 1 件追加 (~20 行) + test 拡充。 + +#### 設計決定 (案) + +- **配置**: `.claude/custom-lint-rules.toml` に rule ⑪ (rule 番号は既存 lint rule 数に基づき確定) として追加 +- **検出 pattern**: `description\.first_line\(\)` (regex literal、jj template DSL 固有のため false positive 極小) +- **extensions**: `toml`, `yaml`, `md` (jj template が記述されうる主要 file 形式) +- **severity**: `error` (= block、ad-hoc を許容しない厳格運用) +- **block message**: 「jj template で `description.first_line()` は曖昧 (= 「全 description 空」と「複数行 description で 1 行目だけ空」を区別不可)。`empty` keyword (= commit が file change を含まないか) または `if(description, "DESCRIBED", "UNDESCRIBED")` を使用してください。詳細: PR #175 Minor finding 由来」 +- **test_coverage** (`~/.claude/rules/common/testing.md` § Custom Lint Rule Test Coverage 準拠): + - `main_ext_tests.toml`: 1+ positive test (`description.first_line()` を含む toml fixture が fire する) + - `main_ext_tests.yaml`: 1+ positive test + - `other_ext_tests`: 1+ positive test (md ext での fire 確認) + - 各 ext で `empty` keyword fixture が fire しない negative test も推奨 + +#### 作業計画 + +- [ ] `.claude/custom-lint-rules.toml` の既存 rule 番号を確認 (`grep -c '^\[\[rules\]\]' .claude/custom-lint-rules.toml` 等で連番判定) +- [ ] rule ⑪ を新規追加 (`id` / `pattern` / `extensions` / `severity` / `message` / `test_coverage` meta field) +- [ ] test fixture を 3 ext × (positive + negative) = 6 件追加 +- [ ] `cargo test rule_test_coverage_check` で meta field 整合性確認 (testing.md 機械強制レイヤ) +- [ ] dogfood: 既存の 順位 155 entry を読み込み、現在の clarified 記述が rule に fire しないことを確認 +- [ ] 本エントリ削除 + todo-summary.md 行削除 + +#### 完了基準 + +- `.claude/custom-lint-rules.toml` の rule ⑪ で `description.first_line()` 使用が機械検出される +- toml / yaml / md 各 ext で positive + negative test が pass +- `cargo test rule_test_coverage_check` で meta field 整合性確認 +- 既存 docs (順位 155 entry を含む) で false positive 0 件 +- PR #175 Minor finding 同型再発が構造的に予防される + +#### 詰まっている箇所 + +なし。Effort XS、既存 lint rule infrastructure (`.claude/custom-lint-rules.toml` + `hooks-post-tool-linter`) への純追加で副作用最小。jj template DSL 固有 pattern のため false positive リスクは低い。 + +--- + ## 既知課題 (記録のみ、本セッションで未対応) (現時点で本ファイルへの既知課題は無し。docs/todo8.md 末尾の post-merge-feedback workflow stale marker 問題を参照。) From 1f49138b44bd2e7afd32b40e1fc78aa9c78e1e58 Mon Sep 17 00:00:00 2001 From: aloekun Date: Tue, 26 May 2026 23:21:29 +0900 Subject: [PATCH 2/3] =?UTF-8?q?feat(cli-push-runner):=20Bundle=203=20?= =?UTF-8?q?=E2=80=94=20=E9=A0=86=E4=BD=8D=205=20=5Ftmp=5F*=20pattern=20?= =?UTF-8?q?=E8=BF=BD=E5=8A=A0=20+=20=E9=A0=86=E4=BD=8D=20159=20lint=20rule?= =?UTF-8?q?=E2=91=AA=20+=20=E9=A0=86=E4=BD=8D=20142=20ADR-041=20=E8=A3=9C?= =?UTF-8?q?=E5=BC=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 順位 5 (PR #88 T1-2) AI 生成一時スクリプト pattern の pre-push 検出を実装。 Bundle 1 で確定した補完アプローチ (config-driven patterns extension) を採用、 `_tmp_*` pattern を `scratch_file_warning` stage の patterns に追加することで PR scope 外 scratch file の混入を構造的に防止する範囲を拡張。 ## 実装 (順位 5) - push-runner-config.toml: patterns に `_tmp_*` 追加、ADR-007 との責務分担を コメントで明示 (本 stage = pre-push @ commit 検査 / ADR-007 = edit 時 text 検査) - src/cli-push-runner/src/stages/scratch_file_warning.rs: module doc に Bundle 3 完了状況と ADR-007 関係を追記、_tmp_* pattern の 3 件 test 追加 (detects_tmp_prefix_pattern / combined patterns / does_not_match_underscore_only) ## 実装 (順位 159) - .claude/custom-lint-rules.toml: rule⑪ (no-jj-template-first-line) 追加。 jj template の `description` の `first_line` メソッド使用を error severity で禁止、`empty` keyword 利用を促進。 - extensions: toml / yaml / md (jj template が記述されうる主要 file 形式) - Self-exclusion: message / why / example / コメント内で pattern 文字列を連続 記述しない設計で self-trigger を回避 (rule⑥ no-ephemeral-todo-reference と 同パターン) - src/hooks-post-tool-linter/src/main.rs: rule⑪ test 5 件追加 (toml / yaml positive+negative + md positive) - rule_test_coverage_check meta validation 通過 ## 実装 (順位 142) - docs/adr/adr-041-test-isolation-patterns.md: § 関連 pattern: State Preservation Invariant (write-once 不変式) を追加。Multi-Condition Guards (本体) との別 pattern class として codify、PR #168/169/170 連続観測の write-once field (state.fix_push_time 等) 設計 + 3 variant 直交 test pattern (既存値 preservation / 新値 provision / 書き換え不可) を明文化。参照実装 = poll.rs の finalize_*_preserves_existing_fix_push_time + monitor.rs の resume_returns_fix_push_time_from_state_when_set を cite。 ## Tests - cargo test --manifest-path src/cli-push-runner/Cargo.toml: 136 passed (+3) - cargo test --manifest-path src/hooks-post-tool-linter/Cargo.toml no_jj_template_first_line: 5 passed (新規) - cargo test --manifest-path src/hooks-post-tool-linter/Cargo.toml rule_test_coverage_check: 1 passed (meta validation) - pnpm build:cli-push-runner / pnpm build:hooks-post-tool-linter: release profile build success --- .claude/custom-lint-rules.toml | 57 +++++++++++++ docs/adr/adr-041-test-isolation-patterns.md | 33 +++++++ push-runner-config.toml | 12 ++- .../src/stages/scratch_file_warning.rs | 50 ++++++++++- src/hooks-post-tool-linter/src/main.rs | 85 +++++++++++++++++++ 5 files changed, 234 insertions(+), 3 deletions(-) diff --git a/.claude/custom-lint-rules.toml b/.claude/custom-lint-rules.toml index 18f1170..8e99b08 100644 --- a/.claude/custom-lint-rules.toml +++ b/.claude/custom-lint-rules.toml @@ -530,3 +530,60 @@ rs = [ "no_write_result_discard_skips_named_binding_starting_with_underscore", "no_write_result_discard_only_targets_rust_extension", ] + +# ─── ルール⑪: jj template の `first_line()` メソッド使用禁止 (曖昧性防止) ─── +# +# 由来: PR #175 で 順位 155 entry の design を書いた際、`jj log` template に +# `description` の `first_line()` メソッドと filter `description = ""` を組合せたが、 +# 両者は意味的に整合せず CR Minor finding で「全 description 空」vs「複数行 description で +# 1 行目だけ空」を区別できない曖昧性を指摘された。ad-hoc fix で `empty` keyword + +# `\u{1f}` Unit Separator 設計に変更したが、別の場所で同型ミスを書く可能性が残る。 +# 同パターン自体を lint で禁止し、`empty` keyword 利用を促進する構造化予防 +# (Bundle 3 順位 159 = PR #175 T1-#1 採用)。 +# +# Bundle Z #B-α と同じ「決定論的防止層」哲学 (ADR-007 の正規表現層)。 +# +# Pattern scope: jj template DSL 固有のため false positive 極小。Rust code 中の +# 同名メソッド呼出は理論上 hit するが、extensions = ["toml", "yaml", "md"] +# (= jj template 記述場所) に限定するため .rs file は対象外。 +# +# Self-exclusion (重要): 本 TOML は extensions = ["toml"] により対象内のため、 +# message / why / example / コメント内で pattern 文字列 (description ドット +# first_line 括弧) を**連続して**書かないこと。「description の first_line()」 +# のように間に文字を挟む形で記述する (placeholder 形式は使わない、Rust regex は +# 単純 literal match のため間に何かあれば trigger しない)。 + +[[rules]] +id = "no-jj-template-first-line" +pattern = 'description\.first_line\(\)' +severity = "error" +message = "jj template で `description` の `first_line` メソッドは曖昧性を生みます (= 「全 description 空」と「複数行 description で 1 行目だけ空」を区別不可)" +why = "PR #175 で order 155 entry 内の jj template / filter logic 不整合が CodeRabbit Minor 指摘で発覚。`empty` keyword (= commit が file change を含まないか) または `if(description, \"DESCRIBED\", \"UNDESCRIBED\")` で意味を明示してください" +extensions = ["toml", "yaml", "md"] + +[rules.fix] +strategy = "`empty` keyword または `if(description, ...)` への置換" +steps = [ + "commit が file change を含むかで判定したい場合: jj template の `empty` keyword を使う (例: `if(empty, \"EMPTY\", \"CONTENT\")`)", + "commit が description を持つかで判定したい場合: `if(description, \"DESCRIBED\", \"UNDESCRIBED\")` を使う", + "あえて 1 行目のみ抽出が必要な場合 (display 用途等): コメントで意図を明記し、filter logic 側で同じ抽象を使う", +] + +[rules.example] +bad = "jj log -T 'change_id ++ DESC_DOT_FIRSTLINE' # placeholder: 実コードでは description . first_line() = 曖昧性" +good = "jj log -T 'change_id ++ if(empty, \"EMPTY\", \"CONTENT\")' # commit 自体が file change を含むかで判定" + +[rules.test_coverage] +# rule⑪ は jj template が書かれうる主要 file 形式 (toml/yaml/md) を対象。 +# toml/yaml は主要拡張子のため main_ext_tests、md は非主要のため other_ext_tests。 +other_ext_tests = ["no_jj_template_first_line_detects_md_pattern"] + +[rules.test_coverage.main_ext_tests] +toml = [ + "no_jj_template_first_line_detects_toml_pattern", + "no_jj_template_first_line_toml_skips_empty_keyword", +] +yaml = [ + "no_jj_template_first_line_detects_yaml_pattern", + "no_jj_template_first_line_yaml_skips_empty_keyword", +] diff --git a/docs/adr/adr-041-test-isolation-patterns.md b/docs/adr/adr-041-test-isolation-patterns.md index 50397da..7697255 100644 --- a/docs/adr/adr-041-test-isolation-patterns.md +++ b/docs/adr/adr-041-test-isolation-patterns.md @@ -105,6 +105,38 @@ fn enrich_with_classifier_skips_when_findings_empty() { - **右 arm test**: `PrMonitorState::new()` の自然な初期値 (`findings` 空) を使うが、**precondition assert** で「左 arm が確実に不発になる」ことを test 内で文書化 + 機械検証 - 両 variant とも `classified_findings` を sentinel で pre-populate し、mutation 不発を survival assert で検出 +## 関連 pattern: State Preservation Invariant (write-once 不変式) + +ADR-041 本体の Multi-Condition Guards とは **別 pattern class** として、`cli-pr-monitor` の state preservation 設計でも類似の test isolation 問題が発生する。本 section は PR #168/169/170 の連続観測で抽出された pattern を codify し、派生プロジェクトへの transferability を確保する (順位 142 採用、本 PR で land)。 + +### Context (PR #168/169/170 連続観測) + +`state.fix_push_time` 等の **write-once field** (= 「一度 set されたら上書きしない」不変式) を持つ field は、`state.fix_push_time.or_else(|| Some(new))` 形式でパス内の任意の地点で代入されうる。test で「既存値が preservation される」「新値が provision される」両方を検証する必要があるが、両方を 1 test で混ぜると validation 軸が混在し、preservation 失敗時に「新 path 経由で書き換わったのか / preservation guard が失敗したのか」を切り分けられない。 + +Multi-Condition Guards (本体) との違い: + +| 観点 | Multi-Condition Guards | State Preservation Invariant | +|---|---|---| +| 検証対象 | OR/AND 早期 return guard の独立発火 | 一度 set した値の不変性 (write-once) | +| 検出 anti-pattern | guard を消した mutant が test を落とさない | preservation 失敗が新 path 由来か preservation guard 由来か判別不能 | +| 解決 idiom | sentinel pre-populate + 直交 precondition | 3 variant 直交 (既存値 / 値なし / 新値提供時の preservation) | + +### 原則: 3 variant 直交 test + +write-once field の test は以下 3 variant で直交化する: + +1. **既存値 preservation** (既存値あり、関数は値なし pass): state に既存値を pre-set した state で関数を呼び、関数が新値を提供しない場合に既存値が保たれることを assert +2. **新値 provision** (既存値なし、関数が新値 set): state に値なし (`None`) で関数を呼び、新値が set されることを assert +3. **書き換え不可 (preservation 強化)** (既存値あり、関数が新値提供): 既存値を pre-set 状態で「新値を提供しようとする」関数呼び出しでも既存値が変わらないことを assert (= write-once 不変式の本質) + +variant 3 が特に重要: variant 1 だけでは「関数が新値を提供しなかったから既存値が保たれた」と解釈の余地が残り、preservation guard が真に機能しているかが test できない。 + +### 参照実装 + +- `src/cli-pr-monitor/src/stages/poll.rs` の `finalize_*_preserves_existing_fix_push_time` (variant 1 + 3 を兼ねる) +- `src/cli-pr-monitor/src/monitor.rs` の `resume_returns_fix_push_time_from_state_when_set` (variant 1 の resume path 版) +- 実装側の不変式: `state.fix_push_time.or_else(|| Some(new))` (= `or_else` clause で値なし時のみ新値 set、= variant 2 の path) + ## 帰結 ### Pros @@ -135,3 +167,4 @@ fn enrich_with_classifier_skips_when_findings_empty() { ## 改訂履歴 - 2026-05-22: 初版 (順位 139 採用 PR で land、PR #120 W-001 + PR #168 の 2 PR 横断観測ベース) +- 2026-05-27: § 関連 pattern: State Preservation Invariant 追加 (順位 142 採用、PR #168/169/170 連続観測ベース、cli-pr-monitor の write-once field 設計 + 3 variant 直交 test pattern を codify) diff --git a/push-runner-config.toml b/push-runner-config.toml index d5090aa..1a0c36a 100644 --- a/push-runner-config.toml +++ b/push-runner-config.toml @@ -20,11 +20,19 @@ # コメントに反映する。 # # 順位 5 (AI 生成一時スクリプト pattern) で `_tmp_*` 等の追加 pattern を本 section -# の `patterns` に追加して拡張する (補完アプローチ、別 PR Bundle 3 で実施)。 +# の `patterns` に追加する (補完アプローチ)。Bundle 3 で `_tmp_*` を追加済。 +# +# ADR-007 (custom linter layer boundary) との関係: +# - 本 stage (`scratch_file_warning`) = pre-push 時点で `@` commit に含まれる +# ファイルを `jj file list -r @` で列挙し検査 (= push 直前の最終防衛層) +# - ADR-007 § custom_lint_rule = PostToolUse hook で AI が edit/write した瞬間に +# text 内容を regex で検査 (= 編集時の即時検出層) +# 両者は異なる timing で動作し、scratch file 検出は本 stage に集約する設計 +# (scratch file は通常 .gitignore 対象で text content 検査の対象外のため)。 # --------------------------------------------------------------------------- [scratch_file_warning] enabled = true -patterns = ["__*"] +patterns = ["__*", "_tmp_*"] [quality_gate] parallel = true diff --git a/src/cli-push-runner/src/stages/scratch_file_warning.rs b/src/cli-push-runner/src/stages/scratch_file_warning.rs index 2fd5bfa..eb721c9 100644 --- a/src/cli-push-runner/src/stages/scratch_file_warning.rs +++ b/src/cli-push-runner/src/stages/scratch_file_warning.rs @@ -20,7 +20,16 @@ //! //! Config-driven pattern: `[scratch_file_warning]` section で `patterns` を拡張可能。 //! 順位 5 (AI 生成一時スクリプト pattern の pre-push 検出) は本 stage の patterns -//! 拡張 (例: `_tmp_*`) + ADR-007 連携で補完的に実装する。 +//! 拡張で補完的に実装する設計 (Bundle 3 で `_tmp_*` 追加済)。 +//! +//! ADR-007 (custom linter layer boundary) との関係: +//! - 本 stage = pre-push 時点で `@` commit 内の file path を `jj file list -r @` で +//! 列挙して basename match で検査 (= push 直前の最終防衛層) +//! - ADR-007 § custom_lint_rule = PostToolUse hook で AI が edit/write した瞬間に +//! text 内容を regex で検査 (= 編集時の即時検出層) +//! 両者は異なる timing / 検査対象で動作し、scratch file 検出は本 stage に集約。 +//! scratch file は通常 .gitignore 対象で text content 検査の対象外のため、 +//! file existence 検査である本 stage に責務を分離している。 use std::process::Command; @@ -420,6 +429,45 @@ mod tests { assert!(find_violations(&files, &patterns).is_empty()); } + #[test] + fn find_violations_detects_tmp_prefix_pattern() { + let files = vec![ + "_tmp_dump.txt".to_string(), + "_tmp_log.ps1".to_string(), + "_tmp_script.py".to_string(), + "src/main.rs".to_string(), + ]; + let patterns = vec!["_tmp_*".to_string()]; + let violations = find_violations(&files, &patterns); + assert_eq!(violations.len(), 3); + assert!(violations.contains(&"_tmp_dump.txt".to_string())); + assert!(violations.contains(&"_tmp_log.ps1".to_string())); + assert!(violations.contains(&"_tmp_script.py".to_string())); + } + + #[test] + fn find_violations_with_dunder_and_tmp_patterns_combined() { + let files = vec![ + "__scratch.ps1".to_string(), + "_tmp_dump.txt".to_string(), + "src/main.rs".to_string(), + "Cargo.toml".to_string(), + ]; + let patterns = vec!["__*".to_string(), "_tmp_*".to_string()]; + let violations = find_violations(&files, &patterns); + assert_eq!(violations.len(), 2); + assert!(violations.contains(&"__scratch.ps1".to_string())); + assert!(violations.contains(&"_tmp_dump.txt".to_string())); + } + + #[test] + fn find_violations_tmp_pattern_does_not_match_underscore_only() { + let files = vec!["_underscore_var.txt".to_string(), "_tmp.txt".to_string()]; + let patterns = vec!["_tmp_*".to_string()]; + let violations = find_violations(&files, &patterns); + assert!(violations.is_empty()); + } + #[test] fn parse_override_env_truthy() { for v in [ diff --git a/src/hooks-post-tool-linter/src/main.rs b/src/hooks-post-tool-linter/src/main.rs index f2083ee..6b21bf4 100644 --- a/src/hooks-post-tool-linter/src/main.rs +++ b/src/hooks-post-tool-linter/src/main.rs @@ -2630,6 +2630,91 @@ extensions = ["ts", "js"] ); } + fn no_jj_template_first_line_rule() -> CustomRule { + make_test_rule( + "no-jj-template-first-line", + r"description\.first_line\(\)", + &["toml", "yaml", "md"], + ) + } + + fn build_first_line_fixture(label: &str) -> String { + let bad_method = format!("description{}{}", ".", "first_line()"); + format!("{} = \"jj log -T 'change_id ++ {}'\"\n", label, bad_method) + } + + fn build_empty_keyword_fixture(label: &str) -> String { + format!( + "{} = \"jj log -T 'change_id ++ if(empty, EMPTY, CONTENT)'\"\n", + label + ) + } + + #[test] + fn no_jj_template_first_line_detects_toml_pattern() { + let dir = tempfile::tempdir().unwrap(); + let file = write_file( + dir.path(), + "rule.toml", + &build_first_line_fixture("command"), + ); + let rules = compile_test_rules(vec![no_jj_template_first_line_rule()]); + let violations = run_custom_rules(file.to_str().unwrap(), &rules); + assert_eq!(violations.len(), 1); + } + + #[test] + fn no_jj_template_first_line_toml_skips_empty_keyword() { + let dir = tempfile::tempdir().unwrap(); + let file = write_file( + dir.path(), + "rule.toml", + &build_empty_keyword_fixture("command"), + ); + let rules = compile_test_rules(vec![no_jj_template_first_line_rule()]); + let violations = run_custom_rules(file.to_str().unwrap(), &rules); + assert!(violations.is_empty()); + } + + #[test] + fn no_jj_template_first_line_detects_yaml_pattern() { + let dir = tempfile::tempdir().unwrap(); + let file = write_file( + dir.path(), + "workflow.yaml", + &build_first_line_fixture("template"), + ); + let rules = compile_test_rules(vec![no_jj_template_first_line_rule()]); + let violations = run_custom_rules(file.to_str().unwrap(), &rules); + assert_eq!(violations.len(), 1); + } + + #[test] + fn no_jj_template_first_line_yaml_skips_empty_keyword() { + let dir = tempfile::tempdir().unwrap(); + let file = write_file( + dir.path(), + "workflow.yaml", + &build_empty_keyword_fixture("template"), + ); + let rules = compile_test_rules(vec![no_jj_template_first_line_rule()]); + let violations = run_custom_rules(file.to_str().unwrap(), &rules); + assert!(violations.is_empty()); + } + + #[test] + fn no_jj_template_first_line_detects_md_pattern() { + let dir = tempfile::tempdir().unwrap(); + let file = write_file( + dir.path(), + "doc.md", + &build_first_line_fixture("snippet"), + ); + let rules = compile_test_rules(vec![no_jj_template_first_line_rule()]); + let violations = run_custom_rules(file.to_str().unwrap(), &rules); + assert_eq!(violations.len(), 1); + } + fn collect_rust_files(root: &std::path::Path, out: &mut Vec) { let entries = match std::fs::read_dir(root) { Ok(e) => e, From d17c7eadb5e21466044aa2cd481cfd3802261770 Mon Sep 17 00:00:00 2001 From: aloekun Date: Wed, 27 May 2026 01:26:49 +0900 Subject: [PATCH 3/3] =?UTF-8?q?docs(todo):=20Bundle=203=20=E5=AE=8C?= =?UTF-8?q?=E4=BA=86=E3=81=AB=E4=BC=B4=E3=81=84=20=E9=A0=86=E4=BD=8D=205?= =?UTF-8?q?=20/=20159=20/=20142=20=E3=82=92=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/todo-summary.md | 3 --- docs/todo3.md | 44 ------------------------------------------ docs/todo8.md | 43 ----------------------------------------- docs/todo9.md | 46 -------------------------------------------- 4 files changed, 136 deletions(-) diff --git a/docs/todo-summary.md b/docs/todo-summary.md index b41f195..86d7a45 100644 --- a/docs/todo-summary.md +++ b/docs/todo-summary.md @@ -11,7 +11,6 @@ | 順位 | Tier | タスク | ファイル | 工数 | 依存 | |---|---|---|---|---|---| -| 5 | 🚀 Tier 1 | **AI 生成一時スクリプト pattern の pre-push 検出 (PR #88 T1-2)** | todo3.md | Small | 順位 1 と関連 (要擦り合わせ) | | 6 | 🚀 Tier 1 | ADR-032 PR-pre: GitHub Branch Protection 整備 | todo2.md | 設定のみ | なし (依存タスクは完了済) | | 8 | 🔧 Tier 2 | 週次レビュー (ADR-031) Phase B 実装 — 7 観点責務 mapping 確定 (① ハーネス遵守 + ⑥ テストロジック を MVP 優先、2026-05-26 ユーザー合意) | todo.md | 中-高 | 順位 20 compensating check 前提 + 順位 136 land 先行推奨 (観点 ⑤ 責務分離) | | 10 | 🔧 Tier 2 | ADR-032 PR-broken-link: broken-link-check + 内部アンカー検査 統合 | todo2.md | Small-中 | なし (clean baseline 確立済) | @@ -64,7 +63,6 @@ | 135 | 💎 Tier 3 | **todo entry の ADR 番号 hardcode 撤廃 — 「ADR-NNN (採番未確定、land 時に確定)」placeholder 採用 (順位 78 番号 conflict 2026-05-16 観測由来)** | todo8.md | XS | なし (順位 78 (旧 ADR-038 → ADR-041) で番号 conflict が顕在化、queue 滞留 entry の hardcode が後発 PR の採番と衝突する構造リスクを convention で予防、`~/.claude/rules/common/docs-governance.md` に 2-3 行追記。採番予約簿は管理コスト過剰のため見送り、land 時 PR で空き番号確定の軽量運用に統一) | | 136 | 🚀 Tier 1 | **working copy staleness 検出 hook 2 段構え + stale todo entry 既実装 grep 提示 (PR cleanup-stale-rank-39 由来 + PR #150 T3-#1 統合 2026-05-25)** | todo8.md | M-L | なし (本セッション実証 failure mode の structural enforcement + 旧 順位 122 機能統合。案 A SessionStart で jj fetch + lineage 報告、案 B PreToolUse で docs/todo*.md edit 時の stale block + 既実装 grep 自動実行で関連 commit を warning 提示、rule 追加 (= 順位 122 当初案) を仕組み化に切替で session 跨ぎ品質一定化、ADR-039 experimental pattern 適用) | | 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 確保) | -| 142 | 💎 Tier 3 | **ADR-041 補強 — "State Preservation Invariant" pattern section 追加 (PR #170 T3-#1 採用) ★ Bundle 171** | 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 確保) | | 143 | 🔧 Tier 2 | **複言語 fixture helper 標準化 (hooks-post-tool-linter-tests) (PR #171 T2-#4 採用) ★ Bundle 171** | todo8.md | S | なし (PR #151/#171 の 2 PR 横断で multi-byte fixture 手動組み立てコストが Frequency Medium で観測、Japanese / emoji / combining chars helper 3 関数を標準化して新規 string-processing 関数追加時の boundary test コスト削減 + silent regression early detection、順位 142 + 144 と同 PR で land 推奨) | | 145 | 🔧 Tier 2 | **preset matrix test 追加 — default fallback vs config-selectable の 2 軸 classification 検証 (PR #172 T2-#1 採用)** | todo8.md | M | なし (PR #172 Phase 3 で `jj-message-required` が opt-in preset であることを前提とせず test を書き rewrite が必要になった経緯、preset architecture の implicit assumption (always-enabled vs config-selectable) を classification 表として test レベルで codify、新 preset 追加時に matrix 更新を強制する mechanical enforcement で design misalignment を構造的検出、target は main.rs (feedback report の lib.rs 記載は誤り)) | | 146 | 🚀 Tier 1 | **Secret detection PreToolUse hook 追加 — AWS/OpenAI/GitHub token 等の hardcoded secret 検出 (PR #172 仕組み化方針切替由来、`security.md` § Secret Management 移管) ★ Bundle 既存ルール仕組み化** | todo9.md | M | なし (`~/.claude/rules/common/security.md` § Secret Management 記述のみで機械強制なし、AWS Access Key / OpenAI sk- / GitHub ghp_/gho_/ghs_ / Anthropic sk-ant- 等 6+ 種 pattern を `preset_secret_detection` で regex 検出 + 即 block、順位 144 hook 化 template 踏襲、security-critical かつ漏洩観測前の preventive 層として Tier 1、rule docs § Secret Management を hook block message に集約で縮小) | @@ -77,7 +75,6 @@ | 153 | 🔧 Tier 2 | **`review-harness-whole` facet 追加 — 観点 ① 独立 facet 化 (順位 8 follow-up、Phase B+1、2026-05-26 ユーザー合意) ★ 週次拡張** | todo9.md | S | 順位 8 Phase B land + 2-3 週 dogfood 後に着手判断 (extract 不要なら close)、順位 146-151 Bundle 既存ルール仕組み化の継続的発見源、architecture-whole から ① 観点を extract して context 圧迫回避 | | 154 | 🔧 Tier 2 | **`review-todo-whole` facet + aggregate 前 file size pre-step — 観点 ⑤ ⑦ 拡張 (順位 8 follow-up、Phase B+1、2026-05-26 ユーザー合意) ★ 週次拡張** | todo9.md | M | 順位 136 land + Phase B 2-3 週 dogfood 完了後着手、順位 95 / 147 と scope 整理必要 (CI 即時 vs 週次 batch)、ADR-031 3 層分離原則で file size は LLM 不要の Rust pre-step に分離 | | 155 | 🚀 Tier 1 | **cli-pr-monitor fix chain 末尾に空 commit 検査 + `jj abandon` step 追加 (PR #174 T1-#1 採用)** | todo9.md | S | なし (PR #174 で `kqvluqyv` 空 commit が PR diff 汚染した実証ベース、`master..@` 範囲を `jj log` で sweep して機械強制、既存 `CleanupEmptyFixCommit` action の補完層) | -| 159 | 🚀 Tier 1 | **jj template 内で脆弱な `description.first_line()` パターンを lint で禁止 (PR #175 T1-#1 採用)** | todo9.md | XS | なし (PR #175 Minor finding 由来の ad-hoc fix を構造化予防に格上げ、`empty` keyword 利用促進、対象 ext = toml/yaml/md で jj template DSL 固有 pattern → false positive 極小、testing.md § Custom Lint Rule Test Coverage 準拠で test_coverage meta field 必須) | | 157 | 🔧 Tier 2 | **Bundle 1 dogfood checklist 実行 — `__test.ps1` block + override env 確認 (PR #174 T2-#2 採用、ADR-039 bounded lifetime data point #1)** | todo9.md | XS | なし (PR #174 PR body の未消化 dogfood、Bundle 2 PR merge 前の前提条件として消化、結果は Bundle 2 PR body に記録) | **戦略**: 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/todo3.md b/docs/todo3.md index 747e217..afad7ae 100644 --- a/docs/todo3.md +++ b/docs/todo3.md @@ -10,50 +10,6 @@ ## 現在進行中 -### AI 生成一時スクリプト pattern の pre-push 検出 (PR #88 T1-2) - -> **動機**: PR #85 で Claude が transcript 確認用に作成した `__parse_transcripts.ps1` が `.gitignore` 漏れにより jj auto-snapshot 経由で commit に意図せず混入。CodeRabbit が発見し除去作業が必要となった。同パターン (`__*.ps1` / `_tmp_*.ps1` / `__*.py` / `_tmp_*.py` 等の AI 生成一時スクリプト) を pre-push で機械的に検出し再発を防止する。post-merge-feedback (PR #88) が同事象を transcript から再検出。 -> -> **本タスクの位置づけ**: **既存の push 前 untracked `__*` ファイル警告 hook task (PR #85 T1-4) と同一インシデントへの異なるアプローチによる補完**。前者 = working-tree の untracked file 検出 (hook 機構) / 本タスク = pre-push 時の lint ベース検出 (AI 命名 pattern 全体)。両機構を併用するか一方に統合するかは実装時に判断。 -> -> **参照**: `.claude/feedback-reports/88.md` の Tier 1 #2 finding -> -> **実行優先度**: 🚀 **Tier 1** — 工数 Small。daily efficiency への影響中 (再発リスクは低いが ADR-007 拡張で確実な再発防止)。**実装前に既存の push 前 untracked `__*` ファイル警告 hook task (PR #85 T1-4) と擦り合わせて重複か補完かを判定すること**。 - -#### 背景 - -- PR #85 で `__parse_transcripts.ps1` が混入 (Claude が transcript 解析用に作成、`.gitignore` 漏れ) -- `.gitignore` への `__*` 追加で当面の再発は防止済 -- ただし `_tmp_*` 等の他 prefix や、`.gitignore` の管理漏れ自体への保険として機械的検出が望ましい -- post-merge-feedback (PR #88) が PR #85 の transcript を解析し、本提案を独立に再生成 → 提案の妥当性が複数 source で corroborate された - -#### 設計決定 (案) - -- 候補機構 1: ADR-007 の custom_lint_rule (`.claude/custom-lint-rules.toml`) に AI 生成一時スクリプト pattern を追加 -- 候補機構 2: pre-push hook で `jj diff --name-only @` で staged file のうち `__*` / `_tmp_*` パターンに合致するものを検出 -- 候補機構 3: 既存の push 前 untracked `__*` ファイル警告 hook (PR #85 T1-4) を拡張し pattern を増やす -- 検出パターン (初稿): `__*.ps1`, `__*.py`, `_tmp_*.ps1`, `_tmp_*.py`, `__*.sh`, `__*.js`, `__*.ts` -- 警告メッセージ: 「AI 生成一時スクリプト pattern を検出: ``. `.gitignore` 漏れの可能性。意図的な commit なら override してください。」 - -#### 作業計画 - -- [ ] 既存の push 前 untracked `__*` ファイル警告 hook (PR #85 T1-4) の実装状況を確認 -- [ ] 重複なら本タスクは前者の hook 内へ統合 (pattern を拡張するだけ)、補完なら別実装 -- [ ] 機構決定後に `.claude/custom-lint-rules.toml` または既存 hook を拡張 -- [ ] dogfood: 試しに `__test.py` を作って commit 試行 → 警告が出ることを確認 -- [ ] 本 todo3.md エントリを削除 (push 前 untracked hook に統合した場合は description も更新) - -#### 完了基準 - -- AI 生成一時スクリプト pattern が pre-push で検出され警告が出る -- 既存の `__*` ファイル検出 hook と整合性が取れている (重複なし or 明示的補完) - -#### 詰まっている箇所 - -なし (Effort Small、ADR-007 既存パターンを拡張) - ---- - ### `vitest` を devDependencies に固定 (PR #88 T2-3) > **動機**: Stop hook の `pnpm test` → `npx vitest run` が `pnpm-lock.yaml` に vitest なしのため npx がネット DL を試みて偽陽性 FAIL する事象を観測。ネット環境・キャッシュ依存の不確実性を排除し、Stop gate を deterministic にする。 diff --git a/docs/todo8.md b/docs/todo8.md index 36c6715..1f73081 100644 --- a/docs/todo8.md +++ b/docs/todo8.md @@ -285,49 +285,6 @@ --- -### 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 本体への追記のみで完結する。 - ---- - ### 複言語 fixture helper 標準化 (hooks-post-tool-linter-tests) (PR #171 T2-#4 採用) ★ Bundle 171 > **動機**: PR #151 (`byte_offset_to_line` char-boundary panic 発見) + PR #171 (`build_violation_json` defensive test 追加) の 2 PR 横断で multi-byte content fixture を手動で組み立てるコストが顕在化。Japanese / emoji / combining chars の各 sample を helper として標準化することで、新規 string-processing 関数追加時の boundary test 実装コストを低減し silent regression を early detection できる。 diff --git a/docs/todo9.md b/docs/todo9.md index 3944e89..565410e 100644 --- a/docs/todo9.md +++ b/docs/todo9.md @@ -532,52 +532,6 @@ --- -### jj template 内で脆弱な `description.first_line()` パターンを lint で禁止 (PR #175 T1-#1 採用) - -> **動機**: PR #175 で 順位 155 entry の設計を書いた際、`jj log` template 内の `description.first_line()` と filter logic `description = ""` を組み合わせたが、両者は意味的に整合せず CR Minor finding で「全 description 空」vs「複数行 description で 1 行目だけ空」を区別できない曖昧性を指摘された。fix では `empty` keyword + `\u{1f}` Unit Separator 設計に変更したが、ad-hoc な修正に留まり今後別の場所で同型ミスを書く可能性が残る。`description.first_line()` パターン自体を lint で禁止し、`empty` keyword 利用を促進する構造化予防が必要。 -> -> **本タスクの位置づけ**: PR #175 post-merge-feedback Tier 1 #1 採用 (Severity Medium / Frequency Low / Effort XS / Adoption Risk None)。jj template 固有 pattern で false positive 極小、Effort XS で機械強制可能、rubric 充足。ad-hoc fix を構造的予防に格上げする「PR 自身の経験を直接 codify する自己強化パターン」(Bundle 1 順位 155 / Bundle 2 順位 156, 158 と同系統)。 -> -> **参照**: `.claude/feedback-reports/175.md` Tier 1 #1、PR #175 CR Minor finding (id 3303489656、`docs/todo9.md` line 465 周辺、現 fix 適用済)、PR #175 Minor fix commit (`b10a83ba` / `lnxmoxoz`)、`.claude/custom-lint-rules.toml` (rule ⑪ 新規追加先)、`~/.claude/rules/common/testing.md` § Custom Lint Rule Test Coverage -> -> **実行優先度**: 🚀 **Tier 1** — Effort XS。custom lint rule 1 件追加 (~20 行) + test 拡充。 - -#### 設計決定 (案) - -- **配置**: `.claude/custom-lint-rules.toml` に rule ⑪ (rule 番号は既存 lint rule 数に基づき確定) として追加 -- **検出 pattern**: `description\.first_line\(\)` (regex literal、jj template DSL 固有のため false positive 極小) -- **extensions**: `toml`, `yaml`, `md` (jj template が記述されうる主要 file 形式) -- **severity**: `error` (= block、ad-hoc を許容しない厳格運用) -- **block message**: 「jj template で `description.first_line()` は曖昧 (= 「全 description 空」と「複数行 description で 1 行目だけ空」を区別不可)。`empty` keyword (= commit が file change を含まないか) または `if(description, "DESCRIBED", "UNDESCRIBED")` を使用してください。詳細: PR #175 Minor finding 由来」 -- **test_coverage** (`~/.claude/rules/common/testing.md` § Custom Lint Rule Test Coverage 準拠): - - `main_ext_tests.toml`: 1+ positive test (`description.first_line()` を含む toml fixture が fire する) - - `main_ext_tests.yaml`: 1+ positive test - - `other_ext_tests`: 1+ positive test (md ext での fire 確認) - - 各 ext で `empty` keyword fixture が fire しない negative test も推奨 - -#### 作業計画 - -- [ ] `.claude/custom-lint-rules.toml` の既存 rule 番号を確認 (`grep -c '^\[\[rules\]\]' .claude/custom-lint-rules.toml` 等で連番判定) -- [ ] rule ⑪ を新規追加 (`id` / `pattern` / `extensions` / `severity` / `message` / `test_coverage` meta field) -- [ ] test fixture を 3 ext × (positive + negative) = 6 件追加 -- [ ] `cargo test rule_test_coverage_check` で meta field 整合性確認 (testing.md 機械強制レイヤ) -- [ ] dogfood: 既存の 順位 155 entry を読み込み、現在の clarified 記述が rule に fire しないことを確認 -- [ ] 本エントリ削除 + todo-summary.md 行削除 - -#### 完了基準 - -- `.claude/custom-lint-rules.toml` の rule ⑪ で `description.first_line()` 使用が機械検出される -- toml / yaml / md 各 ext で positive + negative test が pass -- `cargo test rule_test_coverage_check` で meta field 整合性確認 -- 既存 docs (順位 155 entry を含む) で false positive 0 件 -- PR #175 Minor finding 同型再発が構造的に予防される - -#### 詰まっている箇所 - -なし。Effort XS、既存 lint rule infrastructure (`.claude/custom-lint-rules.toml` + `hooks-post-tool-linter`) への純追加で副作用最小。jj template DSL 固有 pattern のため false positive リスクは低い。 - ---- - ## 既知課題 (記録のみ、本セッションで未対応) (現時点で本ファイルへの既知課題は無し。docs/todo8.md 末尾の post-merge-feedback workflow stale marker 問題を参照。)