diff --git a/.claude/custom-lint-rules.toml b/.claude/custom-lint-rules.toml index f848701..735ad92 100644 --- a/.claude/custom-lint-rules.toml +++ b/.claude/custom-lint-rules.toml @@ -237,3 +237,45 @@ steps = [ [rules.example] bad = "comments.iter().filter(|c| c.created_at > push_time)" good = "comments.iter().filter(|c| c.created_at >= push_time)" + +# ─── ルール⑧: docs/ 内 Markdown の `../docs/` 相対パストラップ検出 ─── +# +# 由来: PR #133 (50KB 分割系列、Bundle j-1 = 順位 94 採用) で +# `[ADR-036](../docs/adr/adr-036-...)` 形式の壊れ link が docs/ 配下で混入し +# CodeRabbit Minor 指摘。docs/ 配下のファイルから `../docs/` を辿ると `docs/docs/` を +# 指すため directory nesting mismatch で必ず broken link。pre-existing bug が分割で +# 表面化した経緯。Custom lint rule で書いた瞬間に block すれば本 bug class が排除される。 +# +# Bundle Z #B-α と同じ「決定論的防止層」哲学 (ADR-007 の正規表現層)。 +# +# 自己限定設計 (extensions = ["md"] のみ、`paths` filter なし): +# pattern `](../docs/` は parent-directory 参照を伴う `docs/` への back-link で、 +# 文書 が docs/ 配下 (または同等位置) にある場合のみ意味を持つ。root-level README.md +# 等の通常 Markdown では `](docs/...)` 形式が自然で `](../docs/...)` は出現しない +# ため、pattern semantics で自己限定される。 +# +# Self-exclusion: 本 TOML の message / why / example で説明文として `](../docs/` +# 形式を直接記述しないこと (lint 対象は extensions = ["md"] のみで .toml は対象外 +# だが、説明用に書く場合も backtick code 内の同パターンが他文書に流れる事故を予防)。 + +[[rules]] +id = "no-docs-relative-back-to-docs" +# case-insensitive で safety margin (URL fragment / Windows path 大文字違い等) +pattern = '(?i)\]\(\.\./docs/' +severity = "error" +message = "`docs/` 配下から `../docs/` への相対参照は directory nesting mismatch で必ず broken link になります" +why = "PR #133 で同パターンの壊れ link が混入し CodeRabbit Minor 指摘 (docs/ 内 file から `../docs/` を辿ると `docs/docs/` を指すため必ず not found)。`(filename.md)` (同階層) または `adr/file.md` (sub-dir) で書く" +extensions = ["md"] + +[rules.fix] +strategy = "相対パスを正しい階層に置き換え" +steps = [ + "同階層の場合: `[text](filename.md)` (相対参照なし)", + "サブディレクトリの場合: `[text](adr/filename.md)` 等の forward path", + "親ディレクトリ (docs/ の外) を参照したい場合: `../<外側>/` (docs/ を再参照しない)", +] + +[rules.example] +# 説明文中で実際の trap pattern を直接書かないため、placeholder 形式で示す +bad = "[ADR-036](DOTDOT/docs/adr/adr-036-...) " +good = "[ADR-036](adr/adr-036-...)" diff --git a/docs/local-llm-offload-analysis.md b/docs/local-llm-offload-analysis.md index 36394d7..b17df48 100644 --- a/docs/local-llm-offload-analysis.md +++ b/docs/local-llm-offload-analysis.md @@ -204,12 +204,24 @@ cargo test -p cli-finding-classifier --test lint_screen_evals -- \ | Order | 構成 | Effort | Diff Profile | dogfood signal | |---|---|---|---|---| - | P-1 | Bundle h (順位 89+90) + Bundle g-2 (順位 87+88) | M | global rules markdown 4 file (うち project diff には ADR-039 + cross-link + todo cleanup のみ) | docs-only で `informational` 期待、false-positive 検証 baseline。**本 PR では config switch を commit に乗せない方針 (Phase d guide §1) のため pipeline 経由の lint_screen は未実行、cli-finding-classifier 直叩きでの classifier preview のみ取得**: latency 23s / findings 0 / fallback (JSON parse error: missing field `screen_decision`) — 順位 98 (`num_ctx` overflow detection) の必要性を再確認する signal。real pipeline 経由の P-1 metric は後続 dogfood で取得 (P-2 移行時に再検討) | + | P-1 | Bundle h (順位 89+90) + Bundle g-2 (順位 87+88) ✅ **完了 (PR #139、2026-05-10)** | M | global rules markdown 4 file (project diff は ADR-039 + cross-link + todo cleanup のみ) | classifier preview のみ取得 (real pipeline 未実行)。詳細は本 table 直後の **P-1 dogfood outcome** 参照 | | P-2 | Bundle j-1 (順位 94 — `../docs/` 相対パス detect lint rule) | S | TOML config + 軽い Rust regex | 小規模 mixed diff | | P-3 | Bundle g-1 (順位 85+86 — cli-pr-monitor verdict guard + transition test) | M | Rust impl + Rust test | 中規模 Rust、`auto_fix` 期待 | | P-4 | Bundle d (順位 68 — no-ephemeral-todo-reference self-exclusion test) | S | Rust test only | 狭 scope test diff | | P-5 | Bundle c-1 (順位 63+64+67 — cli-merge-pipeline Drop guard + reaper + ADR) | L | Rust impl ×2 + ADR | 大規模 Rust (PR #132 868 行 stress 再現候補) | + **P-1 dogfood outcome (PR #139、2026-05-10)**: + + - **classifier preview metrics** (cli-finding-classifier 直叩き、real pipeline 経由ではない): + - latency: 23s (eval baseline p95=8.4s の ~3x、337-line diff サイズ起因の推定) + - findings: 0 (空配列) + - screen_decision: `human_review` (fallback path activated) + - fallback_reason: `JSON parse error: missing field 'screen_decision'` — num_ctx=8192 でも 337-line diff で出力 truncate の可能性 + - **Phase d 学習**: 順位 98 (`num_ctx` overflow detection diagnostic warn log) の必要性を再確認 = mistral:7b 出力崩壊を runtime hint で即診断する優先度が確定 + - **post-merge-feedback (10 findings → 1 件採用)**: T3 #1 (development-workflow.md に 「同一ファイル複数編集の 1 task 統合」 + 「partial completion + 後続 PR 追補明記」 の 2 pattern 追補) を採用 → **順位 100** として登録済。様子見 3 件 / 却下 5 件 (詳細は `.claude/feedback-reports/139.md`) + - **観測 caveat**: post-merge-feedback agent が PR #139 で初観測した `baselinebaseline` (table cell 内連続単語重複) は session/prepush 間で観測が矛盾 (jj cache stale 疑い、未確定)。Frequency Low 単独で T1 #1 連続重複単語 lint / T1 #2 jj cache validation は様子見 / 却下。3 PR 観測閾値で再評価 + - **real pipeline 経由 P-1 metric**: P-2 (Bundle j-1) 移行時に再検討 = lint_screen を session-only opt-in で動かす機会を改めて作る (commit pollution 回避と integration test の trade-off は P-2 で再判断) + **設計判断のポイント**: - **Effort 分布 M→S→M→S→L**: 前半小規模 / 後半大規模で kill-switch (fallback > 50%) signal の質を切り分け可能 (小規模で発動 = 設計 issue / 大規模で発動 = num_ctx 再到達) - **Bundle h + g-2 を 1 PR に統合**: 共通テーマ「global rules consolidation (process/lifecycle codification)」、reviewer も「rule 追加 4 件まとめ」として認識しやすい diff --git a/docs/todo-summary.md b/docs/todo-summary.md index ac0c2b4..0d18d93 100644 --- a/docs/todo-summary.md +++ b/docs/todo-summary.md @@ -65,12 +65,12 @@ | 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 確保) | | 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、独立並列実施可) | -| 94 | 🚀 Tier 1 | **`docs/` 内 Markdown の `../docs/` 相対パストラップ検出 lint rule (PR #133 T1-1 採用) ★ Bundle j** | todo6.md | S | なし (PR #133 で `docs/todo7.md` L103 が `../docs/adr/...` で broken link 化した実例。`docs/` 配下から `../docs/` は常に誤りで FP 極小、`(?i)\]\(\.\./docs/` の regex 1 行で決定論的に防止) | | 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/ 整合性の多層検証 | | 97 | 🔧 Tier 2 | **`with_num_ctx(X)` override 値 serialization 検証テスト (PR #136 T2-#1 採用)** | todo6.md | S | なし (PR #136 で追加した builder method の wiring を mockito で seal、Phase d で num_ctx tweak する局面の silent degrade 防止、CodeRabbit が見逃した test gap を post-merge-feedback agent が独立発見) | | 98 | 🚀 Tier 1 | **`num_ctx` overflow detection — JSON parse error 検知時の context window 診断ログ (PR #137 T1-#1 採用)** | todo6.md | M | なし (PR #136 で誤診 pivot を発生させた blind spot を decisive に塞ぐ runtime hint、`lib-ollama-client` の response validation 層に warn log 追加、`prompt_eval_count` ≈ `num_ctx` cap で truncation を即診断) | | 99 | 💎 Tier 3 | **ADR-038 に PR #138 learning 2 件を追記 (cost-aware 実装層選択 + attention dilution pitfall) (PR #138 T3-#1+#2 採用)** | todo6.md | S | なし (lint_screen が takt facet → Rust stage に pivot した cost 根拠 + Phase b' v2 の diff header full 追加で agreement 75%→50% 33pt 低下した attention dilution 観測の 2 件を ADR に codify、次回 LLM 系 feature 開発時の prior assumption に) | +| 100 | 💎 Tier 3 | **`development-workflow.md` に 「同一ファイル複数編集の 1 task 統合」 + 「partial completion + 後続 PR 追補明記」 を追補 (PR #139 T3-#1 採用)** | todo6.md | XS | なし (PR #119/#120/#121 sub-PR 分割 + PR #139 partial completion で systemic に観測された 2 暗黙知を `~/.claude/rules/common/development-workflow.md` に codify、`feedback_no_unenforced_rules.md` 例外 = 既存実践の明文化のため非機械強制でも採用相当) | **戦略**: Tier 1 を 2〜3 セッションで片付け → Tier 2 で ADR-032 の前提 + rate-limit + convergence cost 削減を進める → Tier 3 で ADR-032 を land + ドキュメント整備。Tier 4-5 は cleanup / 外部展開で daily efficiency への直接効果は小さい。 @@ -110,4 +110,4 @@ **PR #114 (Bb-2 + 順位 75 = Bundle b PR-2 + T2-2) post-merge-feedback (2026-05-05)** ✅ 完了: 9 findings に対して **2 件採用 / 5 件様子見 / 3 件却下**。**Bb-3 (順位 55) で fold-in する採用提案**: T2-2 (Parity test coverage 拡張 = `finalize_park_siblings_have_symmetric_write_state_handling` テストに `finalize_initial_review_park` を追加、self-violation 解消、Effort S)。T2-1 (Legacy JSON deserialize test) は **PR #114 で既に実装済** (`state_legacy_json_without_new_fields_deserializes_with_defaults`、Bb-3 以降の新フィールド追加時に同 pattern を継続するための reference として保存)。**様子見 (5 件)**: T1-1 (finalize_* parity lint、Effort M + NLP 必要、簡易プロキシで再評価) / T1-2 (polling block lint、FP リスクで dogfood 後判断) / T2-3 (env override コメント強化、XS Low) / T2-4 (CI parallel race 確認、preventive only) / T3-1 (Wakeup Resume Invariant ADR) / T3-2 (DI 戦略 ADR、Bb-3 着手時に再検討)。**却下 (3 件)**: T1-3 (Serde schema lint、ROI 低、T2-1 で代替) / T3-3 (parity invariant の global rules 追加) / T3-4 (test-only env var prefix rule) — 後 2 件は **memory: feedback_no_unenforced_rules.md** を直接引用してアナライザが正しく即却下判定。Bb-2 land 時点で Bundle b の核 (CronCreate park モデル) 完成、残る Bb-3 は config 整理 + SessionStart catch-up + T2-2 follow-up を bundled。 -**Bundle j (PR #133 post-merge-feedback、docs/ 整合性多層検証、2026-05-09)**: PR #133 (todo.md / todo5.md 50KB 分割) merge 後の post-merge-feedback で 9 findings 中 **3 件採用** (Tier 1 #1 / Tier 2 #3, #4) で **3 層対策**: (1) **規約層** = 順位 94 (`(?i)\]\(\.\./docs/` regex 1 行で `docs/` 配下からの逆戻り参照を block) は CodeRabbit が PR #133 で実検出した broken link を決定論的に防止、(2) **CI 検証層** = 順位 95 (preamble file count 自動照合) は todo*.md 分割が今後反復する pattern (todo3 → 4 → 5 → 6 → 7) のため Frequency Medium で採用、(3) **包括的 link 検証層** = 順位 96 (Markdown cross-reference validator、directory-aware resolution) は順位 10 (ADR-032 PR-broken-link) と方向性近接で fold-in 検討余地あり。**Sub-PR 分割推奨**: j-1 (順位 94、`.claude/custom-lint-rules.toml` 規約追加、Effort S、独立) / j-2 (順位 95 + 96、`.github/workflows/lint.yml` 新設 = workflows 未存在 repo の最初の workflow 整備、Effort S+M、まとめて land が効率的)。**却下** (2 件): T1 #2 (prose 数詞 lint、NLP 必要 + FP 確実) / T3 #5/#6 (機械検知不可ルール追加、`feedback_no_unenforced_rules.md` 適用)。**様子見** (3 件): T3 #7 (ADR-035 not_applicable と GitHub thread state の乖離明文化、1 観測のみ) / T3 #8 (ADR-030 AFK wakeup 時 PR body intent ルール化、1 観測のみ) / T3 #9 (50KB 分割原則 CLAUDE.md 明文化、機械検知不可)。**本 PR 含意**: 順位 94 = 「決定論的防止層」哲学 (Bundle Z #B-α 系譜)、順位 95-96 = 「規約だけでは塞げない構造的検証は CI で」(ADR-031 週次レビューと相補)。`.github/workflows/` 未存在 repo に最初の workflow を追加する転換点でもあり、scope 慎重判断が必要。 +**Bundle j (PR #133 post-merge-feedback、docs/ 整合性多層検証、2026-05-09)**: PR #133 (todo.md / todo5.md 50KB 分割) merge 後の post-merge-feedback で 9 findings 中 **3 件採用** (Tier 1 #1 / Tier 2 #3, #4) で **3 層対策**: (1) **規約層** = 順位 94 (`(?i)\]\(\.\./docs/` regex 1 行で `docs/` 配下からの逆戻り参照を block) は CodeRabbit が PR #133 で実検出した broken link を決定論的に防止、(2) **CI 検証層** = 順位 95 (preamble file count 自動照合) は todo*.md 分割が今後反復する pattern (todo3 → 4 → 5 → 6 → 7) のため Frequency Medium で採用、(3) **包括的 link 検証層** = 順位 96 (Markdown cross-reference validator、directory-aware resolution) は順位 10 (ADR-032 PR-broken-link) と方向性近接で fold-in 検討余地あり。**Sub-PR 構成**: **j-1 (順位 94、`.claude/custom-lint-rules.toml` 規約追加) は land 済 (Phase d P-2)** / j-2 (順位 95 + 96、`.github/workflows/lint.yml` 新設 = workflows 未存在 repo の最初の workflow 整備、Effort S+M、まとめて land が効率的)。**却下** (2 件): T1 #2 (prose 数詞 lint、NLP 必要 + FP 確実) / T3 #5/#6 (機械検知不可ルール追加、`feedback_no_unenforced_rules.md` 適用)。**様子見** (3 件): T3 #7 (ADR-035 not_applicable と GitHub thread state の乖離明文化、1 観測のみ) / T3 #8 (ADR-030 AFK wakeup 時 PR body intent ルール化、1 観測のみ) / T3 #9 (50KB 分割原則 CLAUDE.md 明文化、機械検知不可)。**本 PR 含意**: 順位 94 = 「決定論的防止層」哲学 (Bundle Z #B-α 系譜)、順位 95-96 = 「規約だけでは塞げない構造的検証は CI で」(ADR-031 週次レビューと相補)。`.github/workflows/` 未存在 repo に最初の workflow を追加する転換点でもあり、scope 慎重判断が必要。 diff --git a/docs/todo6.md b/docs/todo6.md index 6bdb8b0..6af030c 100644 --- a/docs/todo6.md +++ b/docs/todo6.md @@ -122,46 +122,6 @@ config.rs + push-runner-config.toml + review-simplicity.md + ADR で family_tag --- -### `docs/` 内 Markdown の `../docs/` 相対パストラップ検出 lint rule (PR #133 T1-1 採用) ★ Bundle j - -> **動機**: PR #133 (todo5.md 分割) で `docs/todo7.md` L103 に `[ADR-036](../docs/adr/adr-036-...)` 形式の壊れ link が混入。`docs/` 配下のファイルから `../docs/` を辿ると `docs/docs/` を指すため directory nesting mismatch で必ず broken link になる。todo5.md 時代から存在した pre-existing bug が分割で表面化した経緯で、CodeRabbit Minor finding として検出。custom lint rule で書いた瞬間に block すれば bug class が排除される。 -> -> **本タスクの位置づけ**: PR #133 post-merge-feedback Tier 1 #1 採用 (Severity Medium / Frequency Low / Effort S / Adoption Risk None)。Bundle Z #B-α と同じ「決定論的防止層」哲学。AST 解析ではなく正規表現層 (ADR-007) で対応可能。 -> -> **参照**: `.claude/feedback-reports/133.md` Tier 1 #1、ADR-007 (custom lint rule の正規表現 / AST 層線引き)、CodeRabbit PR #133 review #3 -> -> **実行優先度**: 🚀 **Tier 1** — Effort S。`.claude/custom-lint-rules.toml` への regex rule 追加。 - -#### 設計決定 (案) - -- **配置先**: `.claude/custom-lint-rules.toml` に新規 rule entry -- **検出パターン (正規表現案)**: `(?i)\]\(\.\./docs/` - - case-insensitive flag は `.claude/rules/common/code-review.md` の Custom lint rule patterns 規約に従う (PowerShell 等向けだが安全側に倒す) -- **適用対象**: `docs/**/*.md` (rule の `paths` filter で限定。他 path 配下からは正当な `../docs/` 参照があり得るため) -- **rule 名 (案)**: `no-docs-relative-back-to-docs` -- **suppress マーカー**: 該当行末に `` 等 - -#### 作業計画 - -- [ ] 既存 `.claude/custom-lint-rules.toml` の rule 構造を確認 -- [ ] regex + path filter を新 rule として記述 -- [ ] PostToolUse hook の lint runner で synthetic test (PR #133 で混入した `../docs/adr/...` パターンを再現してマッチ確認) -- [ ] 既存 `docs/` 配下を grep して false positive 影響範囲を確認 (`grep -rn '\]\(\.\./docs/' docs/`) -- [ ] 派生プロジェクト (techbook-ledger / auto-review-fix-vc) への deploy 確認 -- [ ] 本 todo6.md エントリを削除 - -#### 完了基準 - -- `.claude/custom-lint-rules.toml` に新 rule が追加され `docs/**/*.md` 内の `\]\(\.\./docs/` を検出 -- 1〜2 PR で dogfood し false positive がないこと -- PR #133 と同型の broken link 混入が新 PR で構造的に防止される - -#### 詰まっている箇所 - -- 派生プロジェクト (techbook-ledger 等) で同 rule が適用された際、各 repo の `docs/` 構造が異なる可能性 — 着手時に各派生 repo の `docs/` レイアウトを確認 - ---- - ### `docs/todo*.md` preamble file count 自動照合スクリプト (PR #133 T2-#4 採用) ★ Bundle j > **動機**: PR #133 で `docs/todo6.md` L5 (「六つすべてを確認すること」) と `docs/todo7.md` L5 (「七つすべて」) が実 8 ファイル (todo.md / todo2-7.md / todo-summary.md) と乖離。CodeRabbit Minor finding として 2 件検出され、fix commit (`4889413`) で修正したが、`todo*.md` 分割が今後も繰り返される pattern (todo3 → 4 → 5 → 6 → 7) のため CI 層で自動検証する価値がある。Tier 1 #1 (custom lint) と相補で防御層を構築。 @@ -388,3 +348,35 @@ config.rs + push-runner-config.toml + review-simplicity.md + ADR で family_tag #### 参考: 不採用理由 (Tier 3 #4) `~/.claude/rules/common/coding-style.md` §Markdown に「重複表現 grep チェック手順」を追加する提案 (#3-4) は **ユーザー判断で見送り**。理由: 重複ワードのバリエーションが多すぎて grep pattern 列挙では網羅できないため、`feedback_no_unenforced_rules.md` 方針 (機械検知不可なルールは追加しない) と整合的に却下相当。週次レビュー (ADR-031) や reviewer の主観判断で対処する位置づけを維持。 + +--- + +### `development-workflow.md` に 「同一ファイル複数編集の 1 task 統合」 + 「partial completion + 後続 PR 追補明記」 を追補 (PR #139 T3-#1 採用) + +> **動機**: PR #139 (Bundle h+g-2 land) の post-merge-feedback で 2 つの暗黙知が systemic に観測された: +> +> 1. **同一ファイル複数編集の 1 task 統合**: PR #119/#120/#121 の sub-PR 分割では同一ファイル (`~/.claude/rules/common/*`) の複数編集を 1 task に統合した方が review 重複を回避できた。明文化されていないため次回類似 sub-PR で再発する余地 +> 2. **partial completion + 後続 PR 追補明記**: PR #139 で Bundle g-2 (順位 87+88) を land したが Bundle g-1 (順位 85+86) は未着手という partial completion を PR body / analysis.md で明記する pattern。Bundle h でも同様 (8 試験運用 ADR への back-link は本 PR 範囲外と明示)。明文化されていないと「全部やった」誤認や曖昧 review が生じる +> +> **本タスクの位置づけ**: PR #139 post-merge-feedback Tier 3 #1 採用 (Severity Low / Frequency Medium / Effort XS / Adoption Risk None)。`feedback_no_unenforced_rules.md` 方針との整合: 本提案は「既存実践の明文化」であり機械検知不可なルール追加ではない (review/PR body 記述で人間の意識付けに用いる目安) ため例外的に採用相当。 +> +> **参照**: `.claude/feedback-reports/139.md` Tier 3 #1、`~/.claude/rules/common/development-workflow.md`、PR #119/#120/#121 (sub-PR 分割実例)、PR #139 (partial completion 実例) + +#### 作業計画 + +- [ ] `~/.claude/rules/common/development-workflow.md` の Feature Implementation Workflow 直後 (現 § Edge case 観測頻度の前後 etc.) に新 section を追加 + - **(a) 同一ファイル複数編集の 1 task 統合**: 「sub-PR 分割時、同一ファイルへの複数 task 編集は 1 commit / 1 task に統合する。理由: review 重複回避 + diff の局所化」 + - **(b) partial completion + 後続 PR 追補明記**: 「bundle / scope を全消化できない場合、PR body の "Out of scope" や planning doc に未消化分を明示。理由: 「全部やった」誤認の防止 + 後続 PR の起点として trackable」 +- [ ] 既存 § Edge case 観測頻度との接続 (相互参照 or 配置順序検討) +- [ ] markdownlint clean 確認 +- [ ] 本 todo6.md エントリ削除 + todo-summary.md 行削除 + +#### 完了基準 + +- 上記 2 pattern が rule として codify される +- 次回 sub-PR 分割時 / partial completion 時に reviewer/Claude が rule から逆引き可能になる +- markdownlint clean + +#### 詰まっている箇所 + +なし。Effort XS、global rule への追記のみで副作用最小。配置先 (Feature Implementation Workflow 直後 vs 別 § で独立) は実装時の判断。 diff --git a/src/hooks-post-tool-linter/src/main.rs b/src/hooks-post-tool-linter/src/main.rs index ac92482..c4dfed8 100644 --- a/src/hooks-post-tool-linter/src/main.rs +++ b/src/hooks-post-tool-linter/src/main.rs @@ -1536,6 +1536,79 @@ extensions = ["ts", "js"] assert!(violations.is_empty()); } + fn md_no_docs_relative_back_to_docs_rule() -> CustomRule { + make_test_rule( + "no-docs-relative-back-to-docs", + r"(?i)\]\(\.\./docs/", + &["md"], + ) + } + + #[test] + fn md_no_docs_relative_detects_pr133_pattern() { + let dir = tempfile::tempdir().unwrap(); + let file = write_file( + dir.path(), + "todo7.md", + "See [ADR-036](../docs/adr/adr-036-bundle-z-three-layer-review.md) for details.\n", + ); + let rules = compile_test_rules(vec![md_no_docs_relative_back_to_docs_rule()]); + let violations = run_custom_rules(file.to_str().unwrap(), &rules); + assert_eq!(violations.len(), 1); + } + + #[test] + fn md_no_docs_relative_detects_uppercase_path() { + let dir = tempfile::tempdir().unwrap(); + let file = write_file( + dir.path(), + "note.md", + "Reference [Spec](../DOCS/feature.md).\n", + ); + let rules = compile_test_rules(vec![md_no_docs_relative_back_to_docs_rule()]); + let violations = run_custom_rules(file.to_str().unwrap(), &rules); + assert_eq!(violations.len(), 1); + } + + #[test] + fn md_no_docs_relative_skips_same_directory_link() { + let dir = tempfile::tempdir().unwrap(); + let file = write_file( + dir.path(), + "todo7.md", + "See [ADR-036](adr/adr-036-bundle-z-three-layer-review.md) for details.\n", + ); + let rules = compile_test_rules(vec![md_no_docs_relative_back_to_docs_rule()]); + let violations = run_custom_rules(file.to_str().unwrap(), &rules); + assert!(violations.is_empty()); + } + + #[test] + fn md_no_docs_relative_skips_parent_to_other_dir() { + let dir = tempfile::tempdir().unwrap(); + let file = write_file( + dir.path(), + "page.md", + "See [README](../README.md) and [src](../src/main.rs).\n", + ); + let rules = compile_test_rules(vec![md_no_docs_relative_back_to_docs_rule()]); + let violations = run_custom_rules(file.to_str().unwrap(), &rules); + assert!(violations.is_empty()); + } + + #[test] + fn md_no_docs_relative_only_targets_md() { + let dir = tempfile::tempdir().unwrap(); + let file = write_file( + dir.path(), + "config.toml", + "doc = \"](../docs/adr/foo.md)\"\n", + ); + let rules = compile_test_rules(vec![md_no_docs_relative_back_to_docs_rule()]); + let violations = run_custom_rules(file.to_str().unwrap(), &rules); + assert!(violations.is_empty()); + } + fn ps_rule_with_pattern(id: &str, pattern: &str) -> CustomRule { make_test_rule(id, pattern, &["ps1"]) }