diff --git a/.claude/hooks-config.toml b/.claude/hooks-config.toml index 03ad669..9d0ea89 100644 --- a/.claude/hooks-config.toml +++ b/.claude/hooks-config.toml @@ -24,6 +24,19 @@ fetch_timeout_secs = 3 # jj git fetch 全体の timeout (network 異常 fetch_cache_secs = 300 # FETCH_HEAD mtime が N 秒以内なら fetch をスキップ (network cost 抑制) default_branch = "master" # trunk-based 前提、feature branch 運用では変更 +# [session_start.weekly_review_reminder] +# - ADR-031 Phase C: `/weekly-review` skill 起動の reminder (試験運用、ADR-039 experimental pattern 準拠)。 +# 2 経路で発火: +# 1. `.claude/weekly-review-last-run.json` の mtime が `reminder_threshold_days` 超過 +# 2. `.claude/weekly-reviews/*.md.failed` marker 1 件以上残存 (前回失敗 resume promote) +# 両方該当する場合は 1 nudge にまとめて出力。 +# 3-5 週の dogfood 後に default-ON 昇格 or 却下を判定 (bounded lifetime)。 +# Kill-switch: `enabled = false` で完全停止。 +[session_start.weekly_review_reminder] +enabled = false # ADR-039 experimental feature standard pattern: opt-in 契約 (default OFF、repo config で明示 enable する運用) +reminder_threshold_days = 7 # ADR-031 § トリガー方式: 「前回実行から 7 日経過で promote」と整合 +failed_marker_check_enabled = true # 前回失敗 marker 検出 → resume promote。false で staleness のみに限定可 + # ─── PreToolUse: コマンド検証 ─── [pre_tool_validate] diff --git a/.takt/facets/instructions/aggregate-weekly.md b/.takt/facets/instructions/aggregate-weekly.md index b5ac503..607d4c0 100644 --- a/.takt/facets/instructions/aggregate-weekly.md +++ b/.takt/facets/instructions/aggregate-weekly.md @@ -96,7 +96,9 @@ category が複数該当する場合は最も特徴的な 1 つを採用、補 例: `WR-2026-05-29-A03` = 2026-05-29 実行、architecture facet 由来、3 番目。 -JSON は ADR-031 § Findings スキーマ準拠で `findings.json` というファイル名で write する (workflow の output contract では `name: findings.json` + `format: findings-json` として宣言されている — `findings.json` がファイル名、`findings-json` が契約 (format) 名): +JSON は ADR-031 § Findings スキーマ準拠で `findings.json` というファイル名で write する (workflow の output contract では `name: findings.json` + `format: findings-json` として宣言されている — `findings.json` がファイル名、`findings-json` が契約 (format) 名)。 + +> **`report_path` の所有権**: 下記 JSON 例の `report_path` field は **Phase C skill `/weekly-review` が copy 後の canonical location** を指す (`.claude/weekly-reviews/.md`)。本 facet は `edit: false` のため自身で copy できないので、`report_path` field は将来 location を予告する形で記述する (= 「skill copy 後に存在する場所」を意味する forward-pointing 記述)。Phase C skill (`~/.claude/skills/weekly-review/SKILL.md`) Phase 2 が Report Directory から `.claude/weekly-reviews/.md` に copy する責務を持つ。Phase C 未実装時 (= Phase B のみ稼働) は `report_path` は dead pointer になるが、Phase C skill が land した後は資源が realize される (PR #182 pre-push reviewer P-1 finding の Phase C 対応): ```json { diff --git a/docs/handoff-rank-8-weekly-review-phase-b.md b/docs/handoff-rank-8-weekly-review-phase-b.md deleted file mode 100644 index 5e1b194..0000000 --- a/docs/handoff-rank-8-weekly-review-phase-b.md +++ /dev/null @@ -1,191 +0,0 @@ -# 順位 8 (週次レビュー Phase B) 着手用 引き継ぎ資料 - -> **本ファイルの位置付け**: 試験運用 ephemeral 計画書。順位 8 の Phase B 〜 Phase C land 完了で役割を終え、retire 時に永続価値 (= reusable rationale / pattern 等) は ADR-031 本体 / `docs/adr/` / `~/.claude/rules/common/` に移管する (グローバル `docs-governance.md` § Retirement Workflow 適用)。 -> -> **作成日**: 2026-05-27 (PR #177 land 直後、コンテキスト圧迫により別セッション引き継ぎのため作成)。 -> -> **対象 task**: docs/todo-summary.md 順位 8「週次レビュー (ADR-031) Phase B 実装」。詳細 entry は `docs/todo.md` § 「週次プロジェクト全体レビューパイプラインの導入 (ADR-031 起案 + 実装)」(line 219 周辺)。 - -## 1. ゴールと scope - -ADR-031 で設計済の **週次プロジェクト全体レビューパイプライン** の Phase B (takt workflow + 4 facets + persona) を実装する。Phase A (ADR-031 起案) は land 済 ([docs/adr/adr-031-weekly-review-pipeline.md](adr/adr-031-weekly-review-pipeline.md))。 - -**MVP 構成 (3 facets、本リポジトリで合意済 2026-05-26)**: - -- `review-simplicity-whole` (whole-tree、ADR-027 制約解除) -- `review-security-whole` (whole-tree) -- `review-architecture-whole` (新 persona、ADR 整合性 / モジュール境界 / ADR-012 命名 / 循環依存) -- `aggregate-weekly` (3 reports → findings JSON + markdown) - -並列構成: 3 review facets を `parallel:` block → `aggregate-weekly` で統合 ([post-merge-feedback.yaml](../.takt/workflows/post-merge-feedback.yaml) 構造流用、fix loop は不要)。 - -## 2. 7 観点責務 mapping (2026-05-26 AskUserQuestion 経由ユーザー合意) - -facet 数は増やさず prompt 重点配分で対応。MVP 優先観点は **① ハーネス遵守 + ⑥ テストロジック**。 - -| 観点 | 担当 facet | prompt 重点 | -|---|---|---| -| ① ハーネス遵守 (rule < pipeline < hook 重複) | architecture-whole | **MVP 最優先** — facet criteria の筆頭、rule/pipeline/hook 重複検出、順位 146-151 Bundle 既存ルール仕組み化の継続的発見源 | -| ② docs 内整合性 | architecture-whole の sub criterion | ADR 間 supersedes / cross-reference / todo routing、cli-docs-lint (preamble + cross-ref、push-runner lint group 統合済) と補完 | -| ③ docs-source 矛盾 | architecture-whole の sub criterion | 重要 ADR 限定リスト (ADR-007 / 012 / 021 / 022 等) で context 圧迫回避 | -| ④ セキュリティ | security-whole | ADR-031 設計通り、変更なし | -| ⑤ Todo 妥当性 | **MVP 対象外** (順位 136 land 済 hook へ委譲) | hook = 編集時 immediate guard / 週次 = batch 棚卸し で責務分離。Phase B+1 で順位 154 facet として再評価 | -| ⑥ テストロジック (振る舞い vs 実装詳細、境界) | simplicity-whole | **MVP 最優先** — facet criteria の筆頭、TDD anti-pattern + 境界欠落、順位 38 (cargo-mutants L3 weekly) と cross-validate | -| ⑦ ファイルサイズ (50KB) | aggregate 前の Rust 機械 pre-step (Phase B+1) | facet 不要、機械検査で十分。順位 154 で cli-docs-lint (preamble check) / 順位 147 と scope 整理 | - -## 3. 依存タスク現状 - -| 順位 | タスク | 状態 | 順位 8 着手への影響 | -|---|---|---|---| -| **136** | working copy staleness 検出 hook 2 段構え | ✅ **land 済 (PR #177)** | 観点 ⑤ 責務分離が完成、Phase B MVP の 6 観点 scope が clean | -| 20 | ADR-032 PR-β 実装 (compensating check) | ⚠️ 未着手 (ADR-032 自体が未起案) | 着手前提として entry が言及しているが hard blocker ではない。順位 8 完了後の Phase E (dogfood + 本採用判断) で順位 20 整合性を見直す程度で OK | -| 38 | cargo-mutants L3 weekly | ⚠️ 未着手 | bundle 化推奨だが必須ではない。Phase B land 後、Phase B+1 (順位 153 / 154 と並列) で着手判断 | -| ~~95~~ / ~~96~~ | preamble file count 自動照合 + Markdown cross-reference validator | ✅ **land 済 (cli-docs-lint binary、PR #178 直後の PR で land)** | 観点 ② docs 内整合性 が機械層で先行確保済、Phase B architecture-whole facet の context 圧迫低減 | - -**推奨実装順序**: - -1. ~~順位 95 + 96 を bundle 化して land~~ — **cli-docs-lint として land 済**。観点 ② docs 内整合性 が機械層で確保済 -2. 順位 8 Phase B 着手 (本資料の主目的) -3. Phase B dogfood 2-3 週運用 (試験運用 flag、ADR-039 bounded lifetime) -4. Phase B+1 (順位 38 / 153 / 154 のいずれか or bundle、dogfood 結果次第) - -### 2026-05-28 セッション update 履歴 (本セッションで発生した状況変化) - -handoff doc 作成後 (2026-05-27) からの差分: - -| land 順 | PR | 内容 | Phase B への影響 | -|---|---|---|---| -| 1 | **#178** | handoff doc 作成 + PR #177 followup todo 登録 | handoff 本体 land | -| 2 | **#179** | cli-docs-lint binary (順位 95+96 統合) + 5 outdated preambles + 13 broken cross-refs 同時 fix | 観点 ② docs 内整合性 を機械層で先行確保。 architecture-whole facet の context 圧迫低減 | -| 3 | **#180** | post-merge-feedback report の wording 強化 (`✅ 採用` → `✅ 採用候補`、AI agent 明示禁則 blockquote 追加) | Phase B land 直後の post-merge-feedback report もこの新 wording で出力される (上記 § 8 参照) | -| - | (local commit) | 順位 163 (Cross-ref edge case test) + 順位 164 (ADR-039 kill-switch 原則追記) を todo9.md / summary に登録 | Phase B 関連 follow-up タスクとして trackable に | - -memory rule 関連の変化 (§ 7 / § 8 に反映済): - -- `feedback_post_merge_feedback_adoption_requires_user_approval.md` 新規作成: post-merge-feedback report の「採用候補」を Claude が無断採用しない rule (PR #179 直後の process 違反を契機) -- `project_coderabbit_rate_limit_overlay.md` update: 「credit exhaustion」warning は rate-limit alias と明示 -- `feedback_coderabbit_no_actionable_merge_signal.md` clarify: 「ユーザー OK」は AskUserQuestion 回答 or テキスト承認のいずれかで OK と明示 - -**Phase B 着手時の注意**: 上記 PR #180 の wording 変更を踏まえ、Phase B land 直後の post-merge-feedback report が `✅ 採用候補` を提示しても、ユーザー明示承認なしに `docs/todo*.md` への自動登録をしない。AskUserQuestion 回答 or テキスト承認のいずれかで個別 finding 採否を確認してから登録する。 - -## 4. Phase B 実装計画 (ADR-031 + todo.md 順位 8 entry より) - -### Phase B 工程 (PR 2 として実装、PR 1 = Phase A は land 済) - -1. **`architecture-reviewer` persona 定義** - - allowed_tools: Read / Glob / Grep のみ (`edit: false`、ADR-022 原則 1 準拠) - - knowledge: architecture - - 既存 persona の場所を調査 (`.takt/personas/` または config 内) して同様に追加 -2. **`.takt/facets/instructions/review-simplicity-whole.md`** 新規作成 - - 既存 `review-simplicity.md` から派生コピー - - diff 局所制約を whole-tree 向けに改変 (主要 dir Glob 順読、累積複雑度視点) - - **観点 ⑥ テストロジック (TDD anti-pattern + 境界欠落) を criteria 筆頭に配置** (MVP 重点) -3. **`.takt/facets/instructions/review-security-whole.md`** 新規作成 - - 既存 `review-security.md` から派生、whole-tree 版 -4. **`.takt/facets/instructions/review-architecture-whole.md`** 新規作成 - - 観点: ADR 整合性 / モジュール境界 / ADR-012 命名規約 / 循環依存 / レイヤ侵犯 - - **観点 ① ハーネス遵守 (rule/pipeline/hook 重複検出) を criteria 筆頭に配置** (MVP 重点) - - 観点 ② docs 内整合性 / ③ docs-source 矛盾 を sub criterion に組込 (重要 ADR 限定リストで context 圧迫回避) -5. **`.takt/facets/instructions/aggregate-weekly.md`** 新規作成 - - 既存 `aggregate-feedback.md` を参考に、3 reports を統合し finding JSON + markdown を出力 -6. **`.takt/workflows/weekly-review.yaml`** 新規作成 - - `parallel: [simplicity-whole, security-whole, architecture-whole]` → `aggregate-weekly` の 2 step - - [post-merge-feedback.yaml](../.takt/workflows/post-merge-feedback.yaml) の構造をテンプレート流用 -7. **takt 単体 dry-run 検証** - - `takt run weekly-review.yaml` で 4 レポートが `.takt/runs/-weekly-review/reports/` に生成されることを確認 -8. **PR 作成・マージ** (本資料の対象範囲) - -### Phase C 工程 (PR 3 として実装、Phase B と別 PR 推奨) - -skill `/weekly-review` + SessionStart hook reminder の実装。詳細は [docs/todo.md 順位 8 entry](todo.md) § Phase C 参照。 - -### Phase D 〜 E 工程 - -e2e 検証 + dogfood (Phase B/C merge 後)。詳細は同 entry 参照。 - -## 5. 重要な設計判断 (順位 8 entry 内 「ユーザー判断記録 (本タスク策定時に合意済 — 2026-04-27)」より) - -| 質問 | 回答 | -|---|---| -| トリガー方式 | 手動 `/weekly-review` + SessionStart hook reminder (前回実行から 7 日経過で promote)、強制起動なし | -| レビュー対象スコープ | 毎回ソースツリー全体、サブツリー分割は MVP 不要 | -| 承認フロー | レポート提示 → 採否を一括選択 (pending JSON 経由) | -| Architecture facet 実装 | 新 `architecture-reviewer` persona 作成 | -| アーキテクチャ形態 | hybrid (takt workflow + skill)、ADR-030 の 3 層分離パターン継承 (4 例目) | -| PR 分割 | PR 1 (ADR、land 済) → **PR 2 (takt、本資料対象)** → PR 3 (skill + hook) → PR 4 (dogfood + 本採用判断) | -| 失敗ポリシー | best-effort (`.failed` marker + SessionStart hook reminder で再実行誘導、must-run ではない) | -| アンチパターン | whole-tree 用 facet を diff 用 facet と共通化しない (ADR-027 で diff 局所が本質要件のため separation 必須) | - -## 6. 参照リソース - -- **本体 ADR**: [docs/adr/adr-031-weekly-review-pipeline.md](adr/adr-031-weekly-review-pipeline.md) (Phase A、land 済) -- **todo entry**: [docs/todo.md](todo.md) § 「週次プロジェクト全体レビューパイプラインの導入」(line 219 周辺、7 観点責務 mapping table 含む) -- **summary table 行**: [docs/todo-summary.md](todo-summary.md) 順位 8 -- **関連 ADR**: - - [ADR-027](adr/adr-027-push-review-simplicity-focus.md) (push-time = simplicity 限定、本 ADR が補完する空白の根拠) - - [ADR-019](adr/adr-019-coderabbit-review-hybrid-policy.md) (post-pr-review 責務範囲) - - [ADR-020](adr/adr-020-takt-facets-sharing.md) (facets 共通化判断基準) - - [ADR-022](adr/adr-022-automation-responsibility-separation.md) (`edit: false` 方針、副作用範囲) - - [ADR-030](adr/adr-030-deterministic-post-merge-feedback.md) (3 層分離パターンの 3 例目、本 ADR は 4 例目) -- **参考実装 (構造流用元)**: - - [.takt/workflows/post-merge-feedback.yaml](../.takt/workflows/post-merge-feedback.yaml) (analyze 3 並列 → aggregate 構造、本 Phase B の workflow テンプレ) - - [.takt/facets/instructions/aggregate-feedback.md](../.takt/facets/instructions/aggregate-feedback.md) (aggregate facet 参考) - - [.takt/facets/instructions/review-simplicity.md](../.takt/facets/instructions/review-simplicity.md) (派生元、whole-tree 版とは **共通化不可** — 別物として派生コピー必須) - - [.takt/facets/instructions/review-security.md](../.takt/facets/instructions/review-security.md) (同上) -- **Phase B+1 follow-up entries**: - - [docs/todo9.md](todo9.md) 順位 153 (`review-harness-whole` facet、観点 ① 独立 facet 化) - - [docs/todo9.md](todo9.md) 順位 154 (`review-todo-whole` facet + aggregate 前 file size pre-step、観点 ⑤ ⑦ 拡張) -- **cli-docs-lint follow-up entries** (PR #179 land 後の改善候補、Phase B には直接依存しないが docs lint 関連): - - [docs/todo9.md](todo9.md) 順位 163 (Cross-ref edge case test coverage 追加 — percent-encode / GFM heading slug / relative path normalize) - - [docs/todo9.md](todo9.md) 順位 164 (ADR-039 kill-switch standard pattern に「診断メッセージは実装の受理値を網羅」原則追記) - -## 7. 適用すべき memory rule (運用上の重要 constraint) - -- **`feedback_test_dry_antipattern`**: テストで DRY を適用しない、各 variant 独立 fixture (Phase B での facet test 実装時に適用) -- **`feedback_review_severity_auto_fix`**: Critical/High/Major は無条件自動修正 (PR review で発見された場合) -- **`feedback_coderabbit_no_actionable_merge_signal`**: CR が "No actionable comments were generated in the recent review. 🎉" 表示でユーザーに最終確認、追加 wakeup 停止 -- **`feedback_pnpm_push_permission`**: pnpm push は foreground 実行 OK、`pnpm create-pr` / `pnpm merge-pr` は auto mode でも実行前にユーザー許可必須 -- **`feedback_global_config_backup`**: ~/.claude/* に触る前に snapshot 取得 (本 Phase B は repo 内のみで完結、適用不要見込み) -- **`feedback_no_unenforced_rules`**: mechanical 検知できないルール案は即却下 (Phase B では機械強制可能な layer に限定) -- **`feedback_pipeline_over_rules`**: パイプライン設計で機械的に解決を優先 (本 Phase B 自体がこの方針の体現) -- **`feedback_skill_flow_user_scope`**: skill のデフォルト flow よりユーザー指示の scope が優先 (Phase B では skill 起動なし、takt workflow のみ) -- **`feedback_no_empty_change_before_push`**: jj describe 後そのまま pnpm push する、空 @ を挟まない -- **`feedback_post_merge_feedback_adoption_requires_user_approval`** (2026-05-28 追加): post-merge-feedback report の「✅ 採用候補」マークは analyzer 推奨。ユーザー明示承認 (AskUserQuestion or テキスト) なしに `docs/todo*.md` への自動登録は禁止 (Phase B land 直後の post-merge-feedback で必ず適用) -- **`project_coderabbit_rate_limit_overlay`** (2026-05-28 update): CR walkthrough の「Your organization has run out of usage credits」warning は credit 枯渇ではなく rate-limit alias。待機 + `@coderabbitai review` でリトライ可能 (Phase B PR で CR rate-limit に遭遇したら活用) - -## 8. Auto mode + ユーザー preference pattern (Bundle 1-3 + 順位 136 で実証) - -- **CR Major/High auto-fix**: ヒアリングなしで即修正、AskUserQuestion options に「修正しない」を含めない -- **CR Minor**: AskUserQuestion で判断確認 (ユーザーは過去 4 回連続「修正する」を選択、auto-mode でも明示確認推奨) -- **CR Nitpick (💤 Low value)**: skip してマージが推奨パターン (PR #176 で実証) -- **CR rate-limit パターン** (2026-05-28 update): time-throttle (通常 30 分前後、walkthrough overlay に残り時間表示) のみなら待機 → `@coderabbitai review` で再 trigger。**「Your organization has run out of usage credits」warning が併記されても credit 枯渇ではなく rate-limit alias なので待機 + retry で対応** (memory `project_coderabbit_rate_limit_overlay` 参照)、即 merge 判断はしない -- **bookmark 自動命名**: 自動採番 OK -- **`pnpm merge-pr`** (2026-05-28 緩和): AskUserQuestion 回答 or 明示テキスト承認のいずれかで実行可。それ以前の「テキスト必須」要件は不要 (memory `feedback_coderabbit_no_actionable_merge_signal` clarification 参照) -- **`pnpm create-pr`**: hook が AskUserQuestion 回答を受け付けず block する場合あり (実際の hook 挙動)。明示テキスト承認 (「PR を作成してください」等) を待つのが安全 -- **post-merge-feedback report の表示変更** (PR #180 で land、2026-05-28): Recommendation 列値が「✅ 採用」→「✅ 採用候補」、「❌ 却下」→「❌ 却下推奨」に変更済。**Phase B land 直後の post-merge-feedback report もこの新 wording で出力される**。「採用候補」はユーザー承認後に採用確定する analyzer 推奨であり、Claude が自動採用してはならない (memory `feedback_post_merge_feedback_adoption_requires_user_approval` 参照) - -## 9. 推奨 first action (新セッションで) - -1. **本資料を読む** (この document) -2. **ADR-031 を読む** ([docs/adr/adr-031-weekly-review-pipeline.md](adr/adr-031-weekly-review-pipeline.md)) -3. **todo.md 順位 8 entry を読む** (line 219 周辺、特に「7 観点責務 mapping」表) -4. **既存 facet 構造を確認** (`.takt/facets/instructions/` の現状確認、特に `review-simplicity.md` / `review-security.md` / `aggregate-feedback.md`) -5. **persona 配置場所を調査** (`.takt/personas/` または config、grep で探す) -6. ~~順位 95 + 96 land 状況確認~~ — cli-docs-lint として land 済、順位 8 直接着手で OK -7. **Phase B 実装着手** (上記 § 4 工程順、PR diff target 250-800 行を意識して fit するか確認) -8. **着手前に AskUserQuestion で MVP 範囲の最終確認** (3 facets で start vs 5 facets + pre-step、ユーザーは Bundle 1-3 land で patterns 確立済のため 3 facets 推奨想定) - -## 10. PR diff 想定規模 - -ADR-031 entry の 「Effort: 中-高」+ 本資料 § 4 の 8 工程 = ~600-900 行想定 (facets 3 + workflow 1 + persona 1 + aggregate 1 = 6 files、test と config を加味)。PR diff target 250-800 行を超える場合は Phase B 自体を 2 PR に分割検討 (例: PR 2-A = persona + workflow + 2 facets / PR 2-B = 残り facets + aggregate)。 - -## 11. 本資料の retirement 条件 - -順位 8 (Phase B) が land し、Phase C / D / E のいずれも着手判断が ADR-031 + todo entry で十分 trackable な状態になったタイミングで本資料を retire: - -1. **永続価値の移管**: 7 観点責務 mapping は既に todo.md 順位 8 entry に codify 済 (重複)。本資料 § 5 ユーザー判断記録は ADR-031 に未記載なら追記 -2. **残タスクの確認**: Phase C / D / E 移行時に follow-up が残っていれば todo.md に登録 -3. **永続参照リンクの除去**: 本資料への永続参照が無いことを `grep -rn 'handoff-rank-8' docs/` 等で確認 -4. **削除**: 本ファイル (`docs/handoff-rank-8-weekly-review-phase-b.md`) を物理削除 - -retire 候補時期: Phase B land 直後 (Phase C / D は別 PR で独立追跡可能のため、Phase B 完了で本資料の役割は終わる)。 diff --git a/docs/handoff-weekly-review-phase-c-onwards.md b/docs/handoff-weekly-review-phase-c-onwards.md new file mode 100644 index 0000000..9f57b8e --- /dev/null +++ b/docs/handoff-weekly-review-phase-c-onwards.md @@ -0,0 +1,157 @@ +# 週次レビューパイプライン Phase C 以降 計画書 + +> **本ファイルの位置付け**: 試験運用 ephemeral 計画書 (`~/.claude/rules/common/docs-governance.md` § Document Lifecycle Classification)。 +> Phase C / D / E land 完了で役割を終え、retire 時に永続価値は ADR-031 / `docs/adr/` / `~/.claude/rules/common/` に移管する (本 doc § 9 retirement 条件)。 +> +> **作成日**: 2026-05-29 (Phase B = PR #182 land 後、旧 handoff doc (Phase B 用、PR #178 で作成 / #181 で update) retire により後継として作成) +> +> **対象 task**: `docs/todo-summary.md` 順位 8「週次レビュー (ADR-031) Phase B/C/D/E」のうち **Phase C 以降**。 +> Phase B は PR #182 で land 済 (workflow + 4 facets + persona、dry-run + post-merge-feedback で実体検証 6 採用候補抽出)。 +> +> **前任 doc**: 旧 Phase B handoff doc (PR #178 で作成、PR #181 で update) は本 doc 作成と同 commit で物理削除。本 doc は Phase B 詳細を **省略** し Phase C 以降にスコープを絞る。Phase B の land 経路詳細が必要な場合は `git log` で PR #182 を読む。 + +## 1. ゴールと scope + +Phase B (= takt workflow + 4 facets + persona) は land 済。本 doc は **Phase C (skill `/weekly-review` + SessionStart hook reminder)** および後続 (D = e2e 検証、E = 試験運用 dogfood) を carry-forward する。 + +**着手判断**: 前任 doc § 3 推奨実装順序では「Phase B dogfood 2-3 週運用 → Phase B+1 (順位 153/154) → Phase C」。本リポジトリのリズムでは 1 PR ≈ 1 セッション程度のため、3-5 PR の Phase B 経験を蓄積してから Phase C 着手判断するのが妥当。**本セッション (2026-05-29) では Phase C 着手判断は実施せず、carry-forward** (ユーザー判断、本 doc 作成と同セッション)。 + +## 2. Phase B 完了状況 (carry-forward 用 summary) + +| 工程 | 状態 | 関連 PR | +|---|---|---| +| `review-simplicity-whole.md` 作成 | ✅ | PR #182 | +| `review-security-whole.md` 作成 | ✅ | PR #182 | +| `review-architecture-whole.md` 作成 | ✅ | PR #182 | +| `aggregate-weekly.md` 作成 | ✅ | PR #182 | +| `weekly-review.yaml` 作成 | ✅ | PR #182 | +| 既存 `architecture-reviewer` persona 再利用 | ✅ | PR #182 (`persona_sessions.json` 既存登録分を再利用、新規 persona 定義は不要を実証) | +| dry-run dogfood (発見 finding 数) | ✅ | PR #182 で 5 findings 検出 (S01/A01 + 3 件、A01 は PR #183 で fix 済、S01 は順位 173 で trackable) | +| post-merge-feedback dogfood (採用候補数) | ✅ | PR #182/#183 で計 6 採用候補 (Bundle CR-RL = 順位 167-169 + Bundle DG-RULES = 順位 170-172) | +| pre-push reviewer 4 fix 認可済 | ✅ | PR #182 fix commit (M-1 cargo tree, M-2 claude provider, N-1 contract 名, N-2 grep tool, P-2 § citation) | + +## 3. 7 観点責務 mapping (前任 doc § 2 から carry-forward、Phase C/D/E でも適用) + +facet 数は MVP 3 (simplicity / security / architecture) で start し、Phase B+1 (順位 153/154) で観点 ⑤ ⑦ 拡張を判断する。 + +| 観点 | 担当 facet (MVP) | Phase B+1 候補 | +|---|---|---| +| ① ハーネス遵守 | architecture-whole の筆頭 | 順位 153 で独立 facet 化検討 | +| ② docs 内整合性 | architecture-whole sub | (cli-docs-lint と相補) | +| ③ docs-source 矛盾 | architecture-whole sub | (重要 ADR 限定リストで context 圧迫回避) | +| ④ セキュリティ | security-whole | 変更なし | +| ⑤ Todo 妥当性 | MVP 対象外 (順位 136 hook で部分対応) | 順位 154 で facet 化検討 | +| ⑥ テストロジック | simplicity-whole の筆頭 | 変更なし | +| ⑦ ファイルサイズ (50KB) | aggregate 前 Rust 機械 pre-step | 順位 154 で実装 | + +## 4. Phase C/D/E 実装計画 (前任 doc § 4 から carry-forward) + +### Phase C 工程 (PR として実装、Phase B = PR #182 と別 PR) + +skill `/weekly-review` + SessionStart hook reminder の実装: + +- skill が `findings.json` を Read → AskUserQuestion で採否一括選択 +- 採用分のみ `docs/todo*.md` に追記 (ADR-031 § 採否フロー仕様) +- SessionStart hook の 2 経路で promote (どちらも nudge を additionalContext に注入、強制起動なし、前任 doc § 5 ユーザー判断: event-driven のみ): + 1. **last-run staleness**: `.claude/weekly-review-last-run.json` の mtime が `reminder_threshold_days` (default 7 日) を超えていれば「`/weekly-review` 実行を検討」を nudge + 2. **failed marker 検出**: `.claude/weekly-reviews/*.md.failed` が 1 件以上存在すれば「前回 weekly-review が失敗、`/weekly-review` で resume」を nudge +- 詳細は ADR-031 § 採否フロー (pending JSON 経由) + § 失敗ポリシー 参照 + +### Phase D 工程 (PR として、Phase C 後) + +e2e 検証: 実際の `/weekly-review` 起動 → findings 採否 → todo 追記 までの flow を実 PR で dogfood。`findings.json` schema (ADR-031 § Findings スキーマ) が skill 採否 flow と整合することを実観測する。 + +### Phase E 工程 (PR として、Phase D 後) + +試験運用 dogfood (1-2 週運用 + ADR-031 ステータス更新「試験運用 → 採用」)。本 doc retirement の trigger でもある (§ 9 参照)。 + +## 5. 重要な設計判断 (前任 doc § 5 + 本セッション 2026-05-29 update) + +| 質問 | 回答 | +|---|---| +| トリガー方式 | 手動 `/weekly-review` + SessionStart hook reminder (前回実行から 7 日経過で promote)、強制起動なし | +| レビュー対象スコープ | 毎回ソースツリー全体、サブツリー分割は MVP 不要 | +| 承認フロー | レポート提示 → 採否を一括選択 (pending JSON 経由) | +| Architecture facet 実装 | 新 `architecture-reviewer` persona 作成 — **PR #182 で実証: `persona_sessions.json` 既存登録分を再利用すれば新規 persona 定義不要** | +| アーキテクチャ形態 | hybrid (takt workflow + skill)、ADR-030 の 3 層分離パターン継承 (4 例目) | +| PR 分割 | PR 1 (ADR、land 済) → PR 2 (takt = #182、land 済) → **PR 3 (skill + hook)** → PR 4 (dogfood + 本採用判断) | +| 失敗ポリシー | best-effort (`.failed` marker + SessionStart hook reminder で再実行誘導、must-run ではない) | +| アンチパターン | whole-tree 用 facet を diff 用 facet と共通化しない (ADR-031 § アンチパターン: `review-simplicity.md` を whole-tree 用と共有してはならない) | + +### 本セッション (PR #182/#183) で得られた追加知見 + +- **A01 = systemic adr-alignment drift** が weekly-review pipeline で検出可能と実証 (PR #183 で 8 ADR 修正、Cross-File Reference Lifecycle 違反の構造修正) +- **CR rate-limit marker drift** が Phase B PR #182 のセッション中に発覚 (順位 167-169 Bundle CR-RL で対応)。**Phase C 着手前に Bundle CR-RL land 推奨** (rate-limit 検出機構が回復していないと dogfood 中の polling コスト過剰になる) +- **operational reference vs pointer reference** の区別が docs governance の運用上重要 (順位 171 で codify 予定) + +## 6. 参照リソース + +- **本体 ADR**: [ADR-031](adr/adr-031-weekly-review-pipeline.md) +- **todo entry (推奨実行順序)**: [docs/todo-summary.md](todo-summary.md) 順位 8 +- **関連 ADR**: + - [ADR-027](adr/adr-027-push-review-simplicity-focus.md) (push-time = simplicity 限定) + - [ADR-019](adr/adr-019-coderabbit-review-hybrid-policy.md) (post-pr-review 責務範囲) + - [ADR-020](adr/adr-020-takt-facets-sharing.md) (facets 共通化判断基準) + - [ADR-022](adr/adr-022-automation-responsibility-separation.md) (`edit: false` 方針、副作用範囲) + - [ADR-030](adr/adr-030-deterministic-post-merge-feedback.md) (3 層分離パターンの 3 例目、本 ADR は 4 例目) +- **Phase B+1 follow-up entries** (Phase C 着手判断と相互参照): + - [docs/todo9.md](todo9.md) 順位 153 (`review-harness-whole` facet) + - [docs/todo9.md](todo9.md) 順位 154 (`review-todo-whole` facet + aggregate 前 file size pre-step) +- **Phase C 着手前提タスク** (Bundle CR-RL = rate-limit marker fix): + - [docs/todo9.md](todo9.md) 順位 167 (`RATE_LIMIT_MARKER` 新フォーマット対応) + - [docs/todo9.md](todo9.md) 順位 168 (CR rate-limit detection integration test) + - [docs/todo9.md](todo9.md) 順位 169 (ADR-018/034 codify CR format evolution) +- **Phase B 実装参照 (Phase C 着手時 reference として読む)**: + - [.takt/facets/instructions/review-simplicity-whole.md](../.takt/facets/instructions/review-simplicity-whole.md) + - [.takt/facets/instructions/review-security-whole.md](../.takt/facets/instructions/review-security-whole.md) + - [.takt/facets/instructions/review-architecture-whole.md](../.takt/facets/instructions/review-architecture-whole.md) + - [.takt/facets/instructions/aggregate-weekly.md](../.takt/facets/instructions/aggregate-weekly.md) + - [.takt/workflows/weekly-review.yaml](../.takt/workflows/weekly-review.yaml) + +## 7. 適用すべき memory rule (Phase C/D/E でも有効) + +前任 doc § 7 から carry-forward (operational rules、Phase C 着手時に再適用): + +- `feedback_test_dry_antipattern`: テスト独立性 (Phase C で skill test を書く場合に重要) +- `feedback_review_severity_auto_fix`: Critical/High/Major 無条件自動修正 +- `feedback_coderabbit_no_actionable_merge_signal`: CR No actionable で merge 判断 +- `feedback_pnpm_push_permission`: `pnpm push` / `pnpm create-pr` / `pnpm merge-pr` のユーザー許可 +- `feedback_global_config_backup`: `~/.claude/*` 編集前 snapshot 取得 (Phase C で SessionStart hook 拡張する場合に該当) +- `feedback_no_unenforced_rules`: 機械検知不可ルール却下 +- `feedback_pipeline_over_rules`: パイプライン > ルール +- `feedback_skill_flow_user_scope`: skill default flow より user scope 優先 (Phase C で skill 実装する際の自重 / Phase 4 で AskUserQuestion を必ず通す) +- `feedback_no_empty_change_before_push`: 空 @ for push 禁止 +- `feedback_post_merge_feedback_adoption_requires_user_approval`: post-merge-feedback 採用候補は user approval 必須 +- `project_coderabbit_rate_limit_overlay`: CR rate-limit 表現の認識 (Bundle CR-RL land 後に本 entry の鮮度確認) + +### Phase C 開発時に特に注意 (前任 doc § 8 から carry-forward + update) + +- **CR Nitpick (💤 Low value)**: skip + merge 推奨パターン (PR #176/#183 で実証、順位 172 で memory codify 予定) +- **`pnpm create-pr`**: `--body-file` 経由必須 (順位 165/166 / memory `feedback_pnpm_create_pr_body` 参照) +- **CR rate-limit**: 順位 167 (Bundle CR-RL) land まで rate-limit 検出が無効化されている (`RATE_LIMIT_MARKER` marker drift)。Phase C 着手前に Bundle CR-RL land 推奨 +- **CodeRabbit incremental review**: CR は既 review 済 commit を再 review しない (`@coderabbitai review` trigger も同 commit には冗長)。fix commit を push すれば自動的に再 review される (PR #182/#183 で実証) + +## 8. Phase C 着手時の推奨 first action + +新セッションで Phase C 着手する場合の推奨順序: + +1. **本 doc を読む** (この document) +2. **[ADR-031](adr/adr-031-weekly-review-pipeline.md) を読む** (§ 採否フロー pending JSON 経由、§ todo.md 反映ルール 中心) +3. **[todo-summary.md](todo-summary.md) 順位 8 entry を読む** (現状 Phase C 着手前) +4. **Bundle CR-RL の land 状況確認**: 順位 167-169 が land 済か `git log --oneline | grep 'RATE_LIMIT'` 等で確認、未 land なら Phase C 着手前に bundle CR-RL を先行 land +5. **既存 skill 構造を確認** (`~/.claude/skills/` または `.claude/skills/` の現状確認、特に `/post-merge-feedback` skill が良い reference) +6. **SessionStart hook 配置場所を調査** (`.claude/hooks-config.toml` または `~/.claude/settings.json` で hooks-session-start 系) +7. **Phase C 実装着手** (本 doc § 4 工程順、PR diff target 250-800 行を意識して fit するか確認) +8. **着手前に AskUserQuestion で Phase C scope の最終確認** (skill 単独 vs SessionStart hook 拡張も含めるか、ユーザーは Phase B land 経験で patterns 確立済のため統合実装推奨想定) + +## 9. retirement 条件 + +本 doc は以下を満たした時点で retire (`~/.claude/rules/common/docs-governance.md` § Retirement Workflow 4 step に整合): + +1. **Phase C/D/E がすべて land** +2. **永続価値の移管**: Phase C/D/E の dogfood 結果から得られた知見が ADR-031 / 関連 ADR / global rules に codify (主に skill / hook 設計の確定パターン) +3. **todo-summary.md 順位 8 entry が close** (Phase E 完了 + ADR-031 ステータス本採用化) +4. **永続参照リンクの除去** (`grep -rn 'handoff-weekly-review-phase-c-onwards' docs/` で本ファイル自身以外 0 件確認) +5. **本ファイル削除** + +retire 候補時期: **Phase E land 後** (= ADR-031 試験運用 → 本採用化 PR 内で同時 retire)。 diff --git a/docs/todo-summary.md b/docs/todo-summary.md index 7b253d9..ddc073e 100644 --- a/docs/todo-summary.md +++ b/docs/todo-summary.md @@ -83,6 +83,10 @@ | 167 | 🚀 Tier 1 | **`check-ci-coderabbit` の `RATE_LIMIT_MARKER` を新フォーマット対応に更新 (PR #182 T1-#1 採用) ★ Bundle CR-RL** | todo9.md | M | なし (`src/check-ci-coderabbit/src/main.rs:251` 旧 marker `Rate limit exceeded` 固定 → multi-variant array `["Rate limit exceeded", "rate limited by coderabbit.ai"]` に変更、現行 CR は HTML マーカー + `## Review limit reached` フォーマットを使用、PR #182 で 30+ 分 polling 観測の root cause、`RateLimitOutcome::Parked` 経路 (ADR-018) が silent regression で無効化されている critical bug、順位 168 + 169 と Bundle CR-RL で同 PR land 推奨) | | 168 | 🔧 Tier 2 | **CR rate-limit detection integration test — 新旧両フォーマット対応 fixture 追加 (PR #182 T2-#1 採用) ★ Bundle CR-RL** | todo9.md | S | 順位 167 の marker 配列化と pair、`#[cfg(test)]` に新 format fixture 2-3 variant 追加 (HTML マーカー + `## Review limit reached` の minimum reproduction)、既存 6 fixture (旧 format) は維持して backward compat、silent regression を test で検出可能化 | | 169 | 💎 Tier 3 | **ADR-018 / ADR-034 に CR rate-limit format evolution と検出ロジック同期戦略を codify (PR #182 T3-#1 採用) ★ Bundle CR-RL** | todo9.md | XS | 順位 167 + 168 と同 commit 推奨、ADR-034 に「既知 format 一覧 + 検出 logic 更新手順」を追記、ADR-018 lines 185-186 を multi-variant 参照に更新、CR の format 進化への構造的予防策、永続 layer (ADR) での format drift 防止 | +| 170 | 💎 Tier 3 | **`git-workflow.md § Multi-PR chaining` を「1 PR 内 multi-commit + intent 明記」パターンに拡張 (PR #183 T3-#1 採用)** | todo9.md | S | なし (PR #119/#120/#121 + #183 の 4 観測で Frequency High、commit 分割判断 + intent 記述ガイドを既存 section に追記、`~/.claude/rules/common/git-workflow.md` 編集、派生プロジェクトへ自動波及、`feedback_global_config_backup` 適用必須) | +| 171 | 💎 Tier 3 | **`docs-governance.md` に「Operational reference vs Pointer reference」区別 section を追加 (PR #183 T3-#2 採用) ★ Bundle DG-RULES** | todo9.md | S | 順位 172 と同 PR 推奨、PR #183 A01 修正で実適用した判定ロジック (operational = workflow 動作記述 = 保持可 / pointer = section 名・順位番号参照 = 置換必要) を `~/.claude/rules/common/docs-governance.md` § Cross-File Reference Lifecycle に新 sub-section として codify、ADR-031 lines 79-302 中 line 270 のみが真の pointer だった実例を inline cite、派生プロジェクトへ自動波及、`feedback_global_config_backup` 適用必須 | +| 172 | 💎 Tier 3 | **CR ephemeral artifact Nitpick の統一 skip 基準を memory に codify (PR #183 T3-#3 採用) ★ Bundle DG-RULES** | todo9.md | XS | 順位 171 と同 PR 推奨、CR が `docs/todo*.md` 系 ephemeral artifact 内の行番号参照を Nitpick 指摘した場合は skip 推奨という判断基準を新 memory `feedback_coderabbit_ephemeral_nitpick.md` に codify、既存 memory `feedback_coderabbit_no_actionable_merge_signal` の補完、本リポジトリ専用 (派生プロジェクトには波及しない)、`feedback_global_config_backup` 適用推奨 | +| 173 | 🔧 Tier 2 | **`combine_output` 5 crate 重複を `lib-runner-utils` (or 既存 lib-*) に extract (PR #182 dry-run S01 採用)** | todo9.md | S-M | なし (`src/cli-pr-monitor/src/runner.rs:80-89` の `combine_output` 8 行関数が `#[allow(dead_code)]` 付与で生産未使用、同関数が 4 他 crate (cli-push-runner, cli-push-pipeline, cli-merge-pipeline, hooks-post-tool-linter) にも複製 = 5 crate 横断 systemic duplication、ADR-026 Cargo workspace + ADR-012 lib-* naming で解決、Phase B dogfood の最初の実体ベース finding (A01 と並ぶ)、A01 は PR #183 で fix 済) | **戦略**: 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 73e76ca..e73b3c2 100644 --- a/docs/todo9.md +++ b/docs/todo9.md @@ -828,7 +828,7 @@ ADR-028 に以下を補足セクションとして追記: > > **本タスクの位置づけ**: PR #182 post-merge-feedback Tier 1 #1 採用 (Severity High / Frequency Medium / Effort M / Adoption Risk None、2026-05-29 ユーザー承認)。Tier 1 機械強制層の修正、Bundle CR-RL (本 entry + 順位 168 + 順位 169) で同一 PR land 推奨。 > -> **参照**: `.claude/feedback-reports/182.md` Tier 1 #1、`src/check-ci-coderabbit/src/main.rs:251` (現状コード)、`src/check-ci-coderabbit/src/main.rs:1298-1370` 周辺 (既存 fixture は旧フォーマットのみ)、`docs/adr/adr-018-pr-monitor-takt-migration.md` (rate-limit 経路の設計根拠、旧 marker 前提で記載)、`docs/adr/adr-034-coderabbit-auto-monitoring.md` line 64 (旧 marker regex 検出記述)、PR #182 セッションでの 30+ 分 polling 観測 (`docs/handoff-rank-8-weekly-review-phase-b.md` 関連 transcript) +> **参照**: `.claude/feedback-reports/182.md` Tier 1 #1、`src/check-ci-coderabbit/src/main.rs:251` (現状コード)、`src/check-ci-coderabbit/src/main.rs:1298-1370` 周辺 (既存 fixture は旧フォーマットのみ)、`docs/adr/adr-018-pr-monitor-takt-migration.md` (rate-limit 経路の設計根拠、旧 marker 前提で記載)、`docs/adr/adr-034-coderabbit-auto-monitoring.md` line 64 (旧 marker regex 検出記述)、PR #182 セッションでの 30+ 分 polling 観測 (PR #182 / #183 land 時の transcript) > > **実行優先度**: 🚀 **Tier 1** — Effort M。CR rate-limit 検出が常時無効化されている critical bug 修正。 @@ -948,6 +948,176 @@ ADR-018 lines 185-186 については、旧 marker 記述を「順位 167 で mu --- +### `git-workflow.md § Multi-PR chaining` を「1 PR 内 multi-commit + intent 明記」パターンに拡張 (PR #183 T3-#1 採用) + +> **動機**: PR #119/#120/#121 + 本 PR #183 で **4 回観測された** multi-commit single-PR bundling パターンを `~/.claude/rules/common/git-workflow.md` § Multi-PR chaining に codify する。現状の同 section は「複数 PR の分割」を扱うが、「1 PR 内で commit を分離する判断基準」「各 commit message での intent 明記の重要性」が未記載。reviewer (CodeRabbit / 人間) が PR diff を読む際、commit description 単位の intent が明確だと review 効率が向上する。Frequency High に到達したため Tier 3 codify 条件成立。 +> +> **本タスクの位置づけ**: PR #183 post-merge-feedback Tier 3 #1 採用 (Severity Low / Frequency High / Effort S / Adoption Risk None、2026-05-29 ユーザー承認)。`~/.claude/` global 配下のため派生プロジェクト (techbook-ledger / auto-review-fix-vc) へ自動波及。 +> +> **参照**: `.claude/feedback-reports/183.md` Tier 3 #1、`~/.claude/rules/common/git-workflow.md` § Multi-PR chaining ベストプラクティス (既存 section、拡張対象)、観測 PR: #119/#120/#121/#183 + +#### 設計決定 (案) + +`git-workflow.md § Multi-PR chaining ベストプラクティス` に以下を追記: + +- **「1 PR 内の multi-commit 分離」の判断基準**: 異なる論理単位 (例: docs update + feature impl) は **commit を分けて 1 PR で land** することで、reviewer が論理単位ごとに review focus を切り替えられる +- **commit message の intent 明記**: 各 commit description は単独で「何を / なぜ」を理解できる形で記述。`docs(todo): X 採用` / `feat(takt): Y 実装` 等の Conventional Commits + intent suffix のパターンを推奨 +- **典型例**: PR #181 (handoff doc + post-merge-feedback adoption の 2 commit)、PR #183 (Bundle CR-RL todo + A01 ADR fix の 2 commit) を実例として cite +- **single-commit vs multi-commit の境界**: 同一論理単位は 1 commit (例: 単一 facet の implementation + test)。**論理単位が異なる** ときに分離する (例: docs update commit + impl commit) + +#### 作業計画 + +- [ ] `~/.claude/rules/common/git-workflow.md` § Multi-PR chaining ベストプラクティス に新 sub-section 「1 PR 内 multi-commit の判断基準」を追加 (~10-15 行) +- [ ] PR #181 / #183 の commit 構成を実例として inline cite +- [ ] **`feedback_global_config_backup`** 適用: ~/.claude/* を触る前に snapshot 取得 (`cp -r ~/.claude ~/__claude-backup-YYYYMMDD`) +- [ ] markdownlint clean 確認 +- [ ] 派生プロジェクト (techbook-ledger / auto-review-fix-vc) への展開は別タスク +- [ ] 本エントリ削除 + todo-summary.md 行削除 + +#### 完了基準 + +- `~/.claude/rules/common/git-workflow.md` に「1 PR 内 multi-commit 分離」と「intent 明記」のガイドが codify される +- 将来の AI / 人間セッションで commit 分割判断と intent 記述が一貫した形で適用される +- markdownlint clean + +#### 詰まっている箇所 + +なし。Effort S、既存 section への追記のみで scope 明確。 + +--- + +### `docs-governance.md` に「Operational reference vs Pointer reference」区別 section を追加 (PR #183 T3-#2 採用) ★ Bundle DG-RULES + +> **動機**: PR #183 で A01 修正 (8 ADR の ephemeral todo 参照を permanent reference に置換) を実施する際、各 reference が以下のどちらに該当するか判定する作業が発生した: +> - **operational reference**: workflow / behavior が ephemeral artifact をどう扱うかを記述するもの (例: 「ADR-031 workflow が `docs/todo.md` に追記する」)。dead-pointer リスクなし、保持可能 +> - **pointer reference**: 特定の section 名 / 順位 N / Phase A-F 等を指すもの (例: 「Phase A-F section を参照」)。dead-pointer リスクあり、置換必要 +> +> 現状の `~/.claude/rules/common/docs-governance.md` § Cross-File Reference Lifecycle は「permanent → ephemeral 参照は dead-pointer 化する」を codify しているが、**「operational reference は除外」という重要な判定基準が未記載**。本 PR の修正で ADR-031 lines 79-302 の中で line 270 のみが真の pointer reference だった実例が示すように、operational reference を pointer と誤認すると過剰修正で workflow 記述自体を壊す可能性がある。 +> +> **本タスクの位置づけ**: PR #183 post-merge-feedback Tier 3 #2 採用 (Severity Low / Frequency Medium / Effort S / Adoption Risk None、2026-05-29 ユーザー承認)。`~/.claude/` global 配下のため派生プロジェクトへ自動波及。Bundle DG-RULES (本 entry + 順位 172) で同 PR land 推奨。 +> +> **参照**: `.claude/feedback-reports/183.md` Tier 3 #2、`~/.claude/rules/common/docs-governance.md` § Cross-File Reference Lifecycle (既存 section、拡張対象)、PR #183 の 8 ADR 修正 commit (実例として cite) + +#### 設計決定 (案) + +`docs-governance.md` § Cross-File Reference Lifecycle に新 sub-section「Operational vs Pointer Reference」を追加: + +- **Operational reference の定義**: workflow / 仕様 / behavior が ephemeral artifact (todo.md 等) を「どう扱うか」を記述するもの。**保持可能**。dead-pointer 化しない理由 = ephemeral artifact の特定 entry を指していないため。 + - 例: 「skill `/weekly-review` は採用 finding を `docs/todo.md` の新セクションに追記する」(動作記述、section 名は workflow が生成するため stale 化しない) + - 例: 「reviewer は `docs/todo.md` を作業計画ファイルとして扱う」(classification、特定 entry を指さない) +- **Pointer reference の定義**: 特定の section 名 / 順位 N / Phase A-F 等を指すもの。**dead-pointer 化リスクあり = 置換必要**。 + - 例: 「Phase B-F は `docs/todo.md` の section X を参照」(stale 化) + - 例: 「順位 42 を読む」(entry 削除で dead pointer) +- **判定基準**: reference が指す対象が「現在存在する specific entry / section」なら pointer、「workflow が描く general behavior」なら operational +- **実例**: PR #183 の ADR-031 line 270 (pointer、置換) vs lines 79-302 内の workflow 記述 (operational、保持)。ADR-034 の 順位 N + PR # pair (PR # 側が permanent reference として fallback、ephemeral 単独参照ではない) も example として cite + +#### 作業計画 + +- [ ] `~/.claude/rules/common/docs-governance.md` § Cross-File Reference Lifecycle に新 sub-section 「Operational vs Pointer Reference」を追加 (~15-20 行) +- [ ] PR #183 の修正例を inline cite (8 ADR の修正と「operational reference として保持」の判断根拠) +- [ ] **`feedback_global_config_backup`** 適用: ~/.claude/* を触る前に snapshot 取得 +- [ ] markdownlint clean 確認 +- [ ] 順位 172 (memory 追加) と同 PR で land 推奨 (Bundle DG-RULES、docs/rule + memory の 2 層) +- [ ] 本エントリ削除 + todo-summary.md 行削除 + +#### 完了基準 + +- `~/.claude/rules/common/docs-governance.md` に operational vs pointer の区別が codify される +- 将来の reviewer / AI が ADR 修正時に過剰修正 (operational reference の誤置換) を回避できる +- 派生プロジェクトへの自動波及で一貫した判定基準が確立 +- markdownlint clean + +#### 詰まっている箇所 + +なし。Effort S、既存 section への sub-section 追加で scope 明確。 + +--- + +### CR ephemeral artifact Nitpick の統一 skip 基準を memory に codify (PR #183 T3-#3 採用) ★ Bundle DG-RULES + +> **動機**: PR #183 で CodeRabbit が docs/todo9.md (= ephemeral artifact) 内の行番号参照 (`lines 1298-1370` 等) を Nitpick として指摘した。これは「行番号は将来 drift する」という general principle としては正しいが、**ephemeral artifact (todo entry) は完了時に削除される設計** のため、永続化を求めるルールを適用するのは over-engineering。本 PR では skip 判断したが、同パターンが構造的に recurring と予想される (CR は ephemeral artifact を permanent doc と同等に扱う傾向)。判断基準を memory entry に codify することで、将来のセッションで一貫した skip 判断が可能になる。 +> +> **本タスクの位置づけ**: PR #183 post-merge-feedback Tier 3 #3 採用 (Severity Low / Frequency Medium / Effort XS / Adoption Risk None、2026-05-29 ユーザー承認)。`~/.claude/projects/.../memory/` 配下のため**派生プロジェクトには波及しない** (本リポジトリ専用)。Bundle DG-RULES (順位 171 + 本 entry) で同 PR land 推奨。 +> +> **参照**: `.claude/feedback-reports/183.md` Tier 3 #3、既存 memory `feedback_coderabbit_no_actionable_merge_signal.md` (補完関係)、PR #183 の Nitpick 2 件 (CR-N1: 順位 168 line 1298-1370 / CR-N2: 順位 169 line 64/185-186) + +#### 設計決定 (案) + +新 memory ファイル `feedback_coderabbit_ephemeral_nitpick.md` を作成: + +- **rule 名**: `feedback_coderabbit_ephemeral_nitpick` +- **type**: feedback +- **description**: CR が ephemeral artifact (`docs/todo*.md` 等) 内の行番号参照を Nitpick (💤 Low value) として指摘した場合は skip 推奨 +- **content**: + - **why**: ephemeral artifact (todo entry) は完了時に削除される設計のため、永続化を求めるルール (line drift 防止 = symbol/section 参照推奨) の適用は over-engineering + - **how to apply**: CR Nitpick が `docs/todo*.md` 系 ephemeral artifact に対する line/symbol drift を指摘した場合、skip + merge 判断を維持。既存 memory `feedback_coderabbit_no_actionable_merge_signal` の「Nitpick 💤 Low value は skip 推奨」の補完。entry 実装着手時には自然に symbol 参照に置き換わる流れになるため、todo entry レベルで先取り fix する価値は低い + - **境界**: permanent artifact (ADR / coding-style.md 等) への同種指摘は通常通り対応する。判定基準 = 対象 file の lifecycle (ephemeral or permanent)。本 rule は ephemeral artifact 専用 + - **実例**: PR #183 の CR-N1 / CR-N2 (docs/todo9.md の行番号参照を skip した実例) + +#### 作業計画 + +- [ ] `~/.claude/projects/E--work-claude-code-hook-test/memory/feedback_coderabbit_ephemeral_nitpick.md` を新規作成 (~30-50 行、frontmatter 含む) +- [ ] `~/.claude/projects/E--work-claude-code-hook-test/memory/MEMORY.md` index に 1 行追加 (各 entry が「タイトル + 1 行 hook」の MEMORY.md 規約に従い、新 memory `feedback_coderabbit_ephemeral_nitpick.md` への 1 行 link を追加) +- [ ] **`feedback_global_config_backup`** 適用: 念のため memory ディレクトリの snapshot 取得 (`cp -r ~/.claude/projects/.../memory ~/__memory-backup-YYYYMMDD`) +- [ ] markdownlint clean 確認 (memory ファイル + MEMORY.md の両方) +- [ ] 順位 171 (docs-governance.md 拡張) と同 PR で land 推奨 (Bundle DG-RULES、docs/rule + memory の 2 層補強) +- [ ] 本エントリ削除 + todo-summary.md 行削除 + +#### 完了基準 + +- 新 memory ファイル `feedback_coderabbit_ephemeral_nitpick.md` が作成される +- MEMORY.md index に登録される +- 将来のセッションで CR が ephemeral artifact 内 Nitpick を出した場合、本 rule から逆引き可能になる +- markdownlint clean + +#### 詰まっている箇所 + +なし。Effort XS、新規 memory ファイル + index 1 行追加のみ。 + +--- + +### `combine_output` 5 crate 重複を `lib-runner-utils` (or 既存 lib-*) に extract (PR #182 dry-run S01 採用) + +> **動機**: PR #182 Phase B dry-run で検出された finding WR-2026-05-29-S01。`src/cli-pr-monitor/src/runner.rs:80-89` の `combine_output(stdout, stderr)` 8 行関数が `#[allow(dead_code)]` 付与され生産コードから呼ばれていない (cli-pr-monitor 内では test 4 件のみが参照)。同一 8 行関数が **4 他 crate にも複製** されている (`cli-push-runner`, `cli-push-pipeline`, `cli-merge-pipeline`, `hooks-post-tool-linter`)、それぞれが同じ test を持つ = **5 crate 横断の systemic duplication** で 5 倍の保守面。ADR-026 Cargo workspace + ADR-012 lib-* naming の既存パターンで解決コスト低。 +> +> **本タスクの位置づけ**: PR #182 post-merge-feedback の dry-run finding S01 採用 (Severity High / Frequency High / Effort S-M / Adoption Risk None、2026-05-29 ユーザー承認)。Phase B dogfood の最初の実体ベース finding (A01 と並ぶ)。A01 (Cross-File Reference Lifecycle 違反) は PR #183 で fix 済、本タスクは Phase B dogfood で発見された残 1 件の構造対策。 +> +> **参照**: PR #182 dry-run report (`.takt/runs/20260529-030546-weekly-review-dry-run-2026-05-29/reports/architecture-whole-review.md` および dry-run feedback report)、`src/cli-pr-monitor/src/runner.rs:80-89` (function) + `:282-298` (tests)、`src/cli-push-runner/`, `src/cli-push-pipeline/`, `src/cli-merge-pipeline/`, `src/hooks-post-tool-linter/` (他 4 crate の重複)、ADR-024 (shared jj-helpers library パターン)、ADR-026 (Cargo workspace) +> +> **実行優先度**: 🔧 **Tier 2** — Effort S-M。Cargo workspace 内の単純な lib extract、5 crate を順次差し替え。 + +#### 設計決定 (案) + +- **採用 strategy** (analyzer Option A 推奨): `lib-runner-utils` 新 crate (or `lib-process-helpers` 等の既存 lib-* crate を選定) に `combine_output(stdout, stderr) -> String` を移管。5 crate (`cli-*` 4 件 + `hooks-post-tool-linter`) から `pub use lib_runner_utils::combine_output;` で再 export +- **不採用 strategy** (analyzer Option B): cli-pr-monitor からのみ削除する最小修正案 → 他 4 crate に同じ未使用問題が残るため不採用、Option A の方が systemic 解決 +- **crate 名選定**: 既存 lib-* 一覧を `cargo metadata` で確認、`lib-runner-utils` / `lib-process-helpers` / `lib-subprocess` 等の候補から選定。新規作成より既存 lib-* (例: shared-jj-helpers) への追加が好ましい (ADR-024 の流れ) +- **test 移管**: 既存 4 test の集約版を新 crate の `#[cfg(test)]` に 1 set のみ配置、各 cli-* / hooks-* 側の test は削除 +- **memory `feedback_test_dry_antipattern`**: test は移管後も独立 variant を維持 (helper で共通化しない) + +#### 作業計画 + +- [ ] `cargo metadata --no-deps` で既存 lib-* crate を列挙、`combine_output` の論理 location として最も自然な crate を選定 +- [ ] 選定 crate (新規 or 既存) に `combine_output` を pub 関数として追加 + 集約 test を 1 set 配置 +- [ ] cli-pr-monitor / cli-push-runner / cli-push-pipeline / cli-merge-pipeline / hooks-post-tool-linter の 5 crate の各 `Cargo.toml` に新 dep を追加 (新規 lib の場合) +- [ ] 各 crate の `combine_output` impl + tests を削除、`use ::combine_output;` に置換 +- [ ] cargo test で 5 crate 全 pass 確認 +- [ ] cargo clippy で `#[allow(dead_code)]` が消えることを確認 (extract により生産 path に乗る、または未使用なら別 PR で削除判断) +- [ ] 本エントリ削除 + todo-summary.md 行削除 + +#### 完了基準 + +- `combine_output` 関数の単一 source of truth が新 crate (または選定 lib-*) に確立 +- 5 cli-*/hooks-* crate が再 export 経由で同 impl を共有 +- 既存 test が集約版 1 set + 各 crate での 4 set 削除で計 4 set 削減 +- `#[allow(dead_code)]` 付与が不要になる (extract 後の lib では pub function として正規 export 経路) +- cargo workspace 全体で cargo test + cargo clippy が pass + +#### 詰まっている箇所 + +extract 先の crate 選定: 既存 lib-* に追加するか新規 `lib-runner-utils` を作るかの判断。新規 crate は Cargo workspace に 1 line 追加で済むが、既存 lib-* (例: lib-pr-monitor-common 等の既存 shared crate) への追加の方が **Effort S** 寄り、新規作成だと **Effort M** に近づく。`cargo metadata` 結果次第。 + +--- + ## 既知課題 (記録のみ、本セッションで未対応) (現時点で本ファイルへの既知課題は無し。docs/todo8.md 末尾の post-merge-feedback workflow stale marker 問題を参照。) diff --git a/src/hooks-session-start/src/main.rs b/src/hooks-session-start/src/main.rs index 747ab56..d945cec 100644 --- a/src/hooks-session-start/src/main.rs +++ b/src/hooks-session-start/src/main.rs @@ -47,9 +47,29 @@ struct StalenessConfig { default_branch: Option, } +/// ADR-031 Phase C: `/weekly-review` skill 起動 promote 設定 (試験運用、ADR-039 experimental pattern)。 +/// +/// `[session_start.weekly_review_reminder]` section 不在 / `enabled` 未設定 / +/// `enabled = false` では完全 skip (default-OFF in source、repo config で明示 enable する)。 +/// +/// 2 種類の reminder を発火: +/// - last-run staleness: `.claude/weekly-review-last-run.json` の mtime が +/// `reminder_threshold_days` を超えていれば「`/weekly-review` の実行を検討」を nudge +/// - failed marker: `.claude/weekly-reviews/*.md.failed` が 1 件以上存在すれば +/// 「前回 weekly-review が失敗、`/weekly-review` で resume」を nudge +/// +/// fail-open: ファイル読込失敗時は warning なしで通過する (session 起動阻害しない)。 +#[derive(Deserialize)] +struct WeeklyReviewReminderConfig { + enabled: Option, + reminder_threshold_days: Option, + failed_marker_check_enabled: Option, +} + #[derive(Deserialize, Default)] struct SessionStartConfig { staleness: Option, + weekly_review_reminder: Option, } #[derive(Deserialize, Default)] @@ -60,6 +80,11 @@ struct HooksConfig { const STALENESS_DEFAULT_FETCH_TIMEOUT_SECS: u64 = 3; const STALENESS_DEFAULT_FETCH_CACHE_SECS: u64 = 300; const STALENESS_DEFAULT_BRANCH: &str = "master"; + +/// weekly review reminder の threshold (default 7 日、ADR-031 § トリガー方式 と整合)。 +const WEEKLY_REVIEW_DEFAULT_THRESHOLD_DAYS: u64 = 7; +const WEEKLY_REVIEW_LAST_RUN_PATH: &str = ".claude/weekly-review-last-run.json"; +const WEEKLY_REVIEW_REVIEWS_DIR: &str = ".claude/weekly-reviews"; const STALENESS_JJ_LOG_TIMEOUT_SECS: u64 = 5; /// catch-up nudge で案内する手動再開コマンド。 @@ -477,6 +502,144 @@ fn count_commits_in_revset(revset: &str) -> Option { Some(output.lines().filter(|l| !l.trim().is_empty()).count()) } +/// `.claude/weekly-review-last-run.json` の last-run 状態。 +/// +/// `Missing` (= 未実行 / 初回) と `Unreadable` (= 権限エラー等の読込失敗) を区別することで +/// fail-open 方針を正しく適用する: Missing は reminder 発火 (= 初回利用ナビ)、Unreadable は +/// reminder 抑制 (= ユーザーを誤通知で煩わせない)。 +enum WeeklyLastRunState { + Missing, + ElapsedDays(u64), + Unreadable, +} + +/// `.claude/weekly-review-last-run.json` の状態を判定する。 +fn weekly_review_last_run_state(repo_root: &Path) -> WeeklyLastRunState { + let path = repo_root.join(WEEKLY_REVIEW_LAST_RUN_PATH); + let metadata = match std::fs::metadata(&path) { + Ok(m) => m, + Err(e) if e.kind() == std::io::ErrorKind::NotFound => return WeeklyLastRunState::Missing, + Err(_) => return WeeklyLastRunState::Unreadable, + }; + let mtime = match metadata.modified() { + Ok(t) => t, + Err(_) => return WeeklyLastRunState::Unreadable, + }; + match mtime.elapsed() { + Ok(elapsed) => WeeklyLastRunState::ElapsedDays(elapsed.as_secs() / 86_400), + Err(_) => WeeklyLastRunState::Unreadable, + } +} + +/// `.claude/weekly-reviews/*.md.failed` を列挙する。 +/// ディレクトリ不在 / read_dir 失敗時は空 Vec (= failed reminder 非発火)。 +fn weekly_review_failed_markers(repo_root: &Path) -> Vec { + let dir = repo_root.join(WEEKLY_REVIEW_REVIEWS_DIR); + let entries = match std::fs::read_dir(&dir) { + Ok(e) => e, + Err(_) => return Vec::new(), + }; + let mut markers = Vec::new(); + for entry in entries.flatten() { + let name = entry.file_name(); + let name_str = match name.to_str() { + Some(s) => s, + None => continue, + }; + if name_str.ends_with(".md.failed") { + markers.push(name_str.to_string()); + } + } + markers.sort(); + markers +} + +fn weekly_review_staleness_label(state: &WeeklyLastRunState) -> &'static str { + match state { + WeeklyLastRunState::Missing => "未実行", + WeeklyLastRunState::ElapsedDays(_) => "", + WeeklyLastRunState::Unreadable => "読込失敗", + } +} + +fn weekly_review_staleness_hits(state: &WeeklyLastRunState, threshold_days: u64) -> bool { + match state { + WeeklyLastRunState::Missing => true, + WeeklyLastRunState::ElapsedDays(d) => *d >= threshold_days, + WeeklyLastRunState::Unreadable => false, + } +} + +fn build_weekly_review_staleness_lines( + state: &WeeklyLastRunState, + threshold_days: u64, +) -> Vec { + if !weekly_review_staleness_hits(state, threshold_days) { + return Vec::new(); + } + let elapsed_label = match state { + WeeklyLastRunState::ElapsedDays(d) => format!("{} 日経過", d), + _ => weekly_review_staleness_label(state).to_string(), + }; + vec![ + "[WEEKLY_REVIEW_REMINDER]".to_string(), + format!( + "週次プロジェクト全体レビュー (ADR-031) が threshold ({} 日) を超えました (前回からの経過: {})。\n\ + 推奨: `/weekly-review` skill を起動して whole-tree レビューを実施 (push-runner / post-PR / post-merge の 3 パイプラインが見ない累積複雑度・横断的 ADR 整合性・ハーネス遵守 観点を補完)", + threshold_days, elapsed_label, + ), + ] +} + +fn build_weekly_review_failed_marker_lines(markers: &[String]) -> Vec { + let mut lines = vec![format!( + "前回 weekly-review の `.failed` marker が {} 件残存しています (best-effort 失敗ポリシー、ADR-031 § 失敗ポリシー)。\n\ + 推奨: `/weekly-review` skill で resume を選択するか、不要なら手動で marker を削除:", + markers.len(), + )]; + for marker in markers { + lines.push(format!(" - `.claude/weekly-reviews/{}`", marker)); + } + lines +} + +/// ADR-031 Phase C: weekly review reminder の nudge を組み立てる。 +/// +/// 2 経路 (staleness + failed marker) は独立して評価し、両方該当する場合は 1 nudge にまとめる。 +/// 該当なし (= last-run が threshold 内 + failed marker なし) は None を返す。 +fn compute_weekly_review_reminder_nudge( + repo_root: &Path, + config: &WeeklyReviewReminderConfig, +) -> Option { + if !config.enabled.unwrap_or(false) { + return None; + } + let threshold_days = config + .reminder_threshold_days + .unwrap_or(WEEKLY_REVIEW_DEFAULT_THRESHOLD_DAYS); + let failed_check_enabled = config.failed_marker_check_enabled.unwrap_or(true); + let last_run_state = weekly_review_last_run_state(repo_root); + let staleness_lines = build_weekly_review_staleness_lines(&last_run_state, threshold_days); + let failed_markers = if failed_check_enabled { + weekly_review_failed_markers(repo_root) + } else { + Vec::new() + }; + if staleness_lines.is_empty() && failed_markers.is_empty() { + return None; + } + let mut lines = staleness_lines; + if !failed_markers.is_empty() { + if lines.is_empty() { + lines.push("[WEEKLY_REVIEW_REMINDER]".to_string()); + } else { + lines.push(String::new()); + } + lines.extend(build_weekly_review_failed_marker_lines(&failed_markers)); + } + Some(lines.join("\n")) +} + fn build_staleness_nudge_message(default_branch: &str, behind: usize) -> String { format!( "[working-copy-freshness]\n\ @@ -585,6 +748,16 @@ fn emit_session_start_output(session_id: &str) { context.push_str(&staleness_nudge); } } + if let Some(weekly_config) = hooks_config + .session_start + .as_ref() + .and_then(|s| s.weekly_review_reminder.as_ref()) + { + if let Some(weekly_nudge) = compute_weekly_review_reminder_nudge(&cwd, weekly_config) { + context.push_str("\n\n"); + context.push_str(&weekly_nudge); + } + } } let output = serde_json::json!({ "hookSpecificOutput": { @@ -1277,4 +1450,139 @@ default_branch = "main" assert_eq!(staleness.default_branch.as_deref(), Some("main")); let _ = std::fs::remove_dir_all(&root); } + + #[test] + fn compute_weekly_review_reminder_nudge_returns_none_when_disabled() { + let root = unique_temp_root("weekly-disabled"); + std::fs::create_dir_all(&root).unwrap(); + let config = WeeklyReviewReminderConfig { + enabled: Some(false), + reminder_threshold_days: Some(7), + failed_marker_check_enabled: Some(true), + }; + assert!(compute_weekly_review_reminder_nudge(&root, &config).is_none()); + let _ = std::fs::remove_dir_all(&root); + } + + #[test] + fn weekly_review_failed_markers_returns_empty_when_dir_missing() { + let root = unique_temp_root("weekly-no-dir"); + std::fs::create_dir_all(&root).unwrap(); + let markers = weekly_review_failed_markers(&root); + assert!(markers.is_empty()); + let _ = std::fs::remove_dir_all(&root); + } + + #[test] + fn weekly_review_failed_markers_lists_failed_md_files_only() { + let root = unique_temp_root("weekly-markers"); + let reviews_dir = root.join(".claude/weekly-reviews"); + std::fs::create_dir_all(&reviews_dir).unwrap(); + std::fs::write(reviews_dir.join("2026-05-22.md.failed"), "fail1").unwrap(); + std::fs::write(reviews_dir.join("2026-05-29.md.failed"), "fail2").unwrap(); + std::fs::write(reviews_dir.join("2026-05-29.md"), "report").unwrap(); + let markers = weekly_review_failed_markers(&root); + assert_eq!(markers.len(), 2); + assert!(markers.contains(&"2026-05-22.md.failed".to_string())); + assert!(markers.contains(&"2026-05-29.md.failed".to_string())); + assert!(!markers.contains(&"2026-05-29.md".to_string())); + let _ = std::fs::remove_dir_all(&root); + } + + #[test] + fn compute_weekly_review_reminder_nudge_emits_staleness_when_never_run() { + let root = unique_temp_root("weekly-staleness-never"); + std::fs::create_dir_all(&root).unwrap(); + let config = WeeklyReviewReminderConfig { + enabled: Some(true), + reminder_threshold_days: Some(7), + failed_marker_check_enabled: Some(false), + }; + let nudge = compute_weekly_review_reminder_nudge(&root, &config) + .expect("staleness nudge must be emitted when last-run file missing"); + assert!(nudge.contains("[WEEKLY_REVIEW_REMINDER]")); + assert!(nudge.contains("threshold (7 日)")); + assert!(nudge.contains("未実行")); + let _ = std::fs::remove_dir_all(&root); + } + + #[test] + fn compute_weekly_review_reminder_nudge_emits_failed_marker_when_present() { + use std::io::Write; + let root = unique_temp_root("weekly-failed-only"); + let reviews_dir = root.join(".claude/weekly-reviews"); + std::fs::create_dir_all(&reviews_dir).unwrap(); + std::fs::write(reviews_dir.join("2026-05-15.md.failed"), "fail").unwrap(); + let last_run_path = root.join(WEEKLY_REVIEW_LAST_RUN_PATH); + let mut last_run_file = std::fs::File::create(&last_run_path).unwrap(); + last_run_file.write_all(b"{}").unwrap(); + drop(last_run_file); + let config = WeeklyReviewReminderConfig { + enabled: Some(true), + reminder_threshold_days: Some(365), + failed_marker_check_enabled: Some(true), + }; + let nudge = compute_weekly_review_reminder_nudge(&root, &config) + .expect("failed marker nudge must be emitted"); + assert!(nudge.contains("[WEEKLY_REVIEW_REMINDER]")); + assert!(nudge.contains(".failed` marker が 1 件残存")); + assert!(nudge.contains("2026-05-15.md.failed")); + let _ = std::fs::remove_dir_all(&root); + } + + #[test] + fn weekly_review_staleness_hits_for_missing_state() { + assert!(weekly_review_staleness_hits(&WeeklyLastRunState::Missing, 7)); + } + + #[test] + fn weekly_review_staleness_hits_for_elapsed_above_threshold() { + assert!(weekly_review_staleness_hits( + &WeeklyLastRunState::ElapsedDays(10), + 7 + )); + } + + #[test] + fn weekly_review_staleness_skips_for_elapsed_below_threshold() { + assert!(!weekly_review_staleness_hits( + &WeeklyLastRunState::ElapsedDays(3), + 7 + )); + } + + #[test] + fn weekly_review_staleness_skips_for_unreadable_state() { + assert!(!weekly_review_staleness_hits( + &WeeklyLastRunState::Unreadable, + 7 + )); + } + + #[test] + fn hooks_config_parses_session_start_weekly_review_reminder_section() { + use std::io::Write; + let root = unique_temp_root("hooks-config-weekly"); + let claude_dir = root.join(".claude"); + std::fs::create_dir_all(&claude_dir).unwrap(); + let toml_str = r#" +[session_start.weekly_review_reminder] +enabled = true +reminder_threshold_days = 14 +failed_marker_check_enabled = false +"#; + let mut f = std::fs::File::create(claude_dir.join("hooks-config.toml")).unwrap(); + f.write_all(toml_str.as_bytes()).unwrap(); + drop(f); + let config = read_hooks_config(&root); + let weekly = config + .session_start + .as_ref() + .and_then(|s| s.weekly_review_reminder.as_ref()) + .expect("weekly_review_reminder section should parse"); + assert_eq!(weekly.enabled, Some(true)); + assert_eq!(weekly.reminder_threshold_days, Some(14)); + assert_eq!(weekly.failed_marker_check_enabled, Some(false)); + let _ = std::fs::remove_dir_all(&root); + } }