feat(hooks): 順位 136 — working copy staleness 検出 hook 2 段構え (案 A SessionStart + 案 B PreToolUse)#177
Conversation
…ble に登録 PR #176 (Bundle 3) post-merge-feedback で採用判定された Tier 3 #1 を docs/todo9.md 新規エントリとして追加し、docs/todo-summary.md table に行追加。 - 順位 160 (T3 #1): `docs-governance.md` に「ADR multi-variant pattern section 追加時の checklist」を codify (PR #175 Minor + PR #176 Nitpick の 2 連続観測 = Frequency Medium で採用条件成立、Effort XS、global file 編集のため本リポジトリ外 で実施)
…onStart + 案 B PreToolUse)
順位 136 (Tier 1) を実装。並列セッション運用で working copy が master より遅れた
状態の docs/todo*.md 編集事故 (PR cleanup-stale-rank-39 で実証) を構造的に予防し、
旧 順位 122 (新 todo 着手前の既実装確認) を hook 機能として統合する。
## 実装 (案 A: SessionStart fetch + lineage)
- src/hooks-session-start/src/main.rs:
- HooksConfig / SessionStartConfig / StalenessConfig 追加 (ADR-039 experimental
pattern 準拠、default-OFF in source、repo config で明示 enable)
- compute_staleness_nudge / build_staleness_nudge_message / fetch_head_is_recent /
run_jj_with_timeout / count_commits_in_revset 追加
- emit_session_start_output に staleness nudge を統合
- src/hooks-session-start/Cargo.toml: toml = "0.8" 追加
- .claude/hooks-config.toml: [session_start.staleness] section 追加
設計:
- fail-open: jj git fetch / jj log 失敗時は warning なしで pass-through
- fetch cache: .git/FETCH_HEAD mtime が N 秒以内なら fetch skip (network cost 抑制、default 5 分)
- 出力: master が @- より N commits ahead なら additionalContext で warning
## 実装 (案 B: PreToolUse hook で docs/todo*.md edit を block + 既実装 grep)
- src/hooks-pre-tool-validate/src/main.rs:
- ToolInput に old_string / new_string / content field 追加 (Edit/Write の text 取得用)
- TodoStalenessConfig 追加 (ADR-039 experimental pattern 準拠)
- is_docs_todo_path / extract_heading_keywords / parse_jj_log_records /
find_matching_commits / build_todo_staleness_message / check_todo_staleness /
collect_text_for_keywords / run_jj_with_timeout 追加
- main を read_hook_input / handle_bash_tool / handle_write_edit_tool に分割
(RUST_FUNCTION_TOO_LONG 50 行制限準拠の refactor)
- .claude/hooks-config.toml: [pre_tool_validate.todo_staleness] section 追加
設計:
- fail-closed: stale 検知時は exit 2 で block
- 動作 1 (stale 検知): @-..master の commit 数 > 0 → stderr + exit 2
- 動作 2 (既実装 grep): ### heading title から keyword 抽出 → jj log --limit 20 の
description に grep → 上位 3 件 commit を stderr + (stale なら exit 2、grep のみ
なら exit 0 = warn-only)
- Self-exclusion: rule⑥ no-ephemeral-todo-reference 対策で test fixture は
build_todo_path() helper 経由で format! 構築 (literal 回避)
## Tests
- cargo test --manifest-path src/hooks-session-start/Cargo.toml: 48 passed
(+8 staleness tests)
- cargo test --manifest-path src/hooks-pre-tool-validate/Cargo.toml: 178 passed
(+20 staleness tests)
## ADR-039 準拠
- Config opt-in: source default = false、repo config で明示 enable
- Kill-switch: enabled = false で完全停止
- Bounded lifetime: 3-5 PR dogfood 後に default-ON 昇格 or 却下判定
(hooks-config.toml のコメントで明示)
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
✅ Files skipped from review due to trivial changes (1)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughウォークスルーこのPRは、ワーキングコピーがデフォルトブランチから遅延している状態を2段階で検出・通知する機構を実装します。SessionStart時にフェッチ状況をnudgeし、todo*.md編集時にstalenessを検出してブロック。設定スキーマ、両hook実装、テスト、ドキュメント更新を含みます。 変更内容ワーキングコピーStaleness検出機構
シーケンスダイアグラムsequenceDiagram
participant SessionStart as SessionStart Hook
participant Config as hooks-config.toml
participant FetchHead as .git/FETCH_HEAD
participant JJ as jj log
participant Output as additionalContext
SessionStart->>Config: Read session_start.staleness
Config-->>SessionStart: enabled, timeout, cache_secs, default_branch
SessionStart->>FetchHead: Check modification time
alt Cache expired or not found
SessionStart->>JJ: Run jj git fetch (with timeout)
JJ-->>SessionStart: Fetch result
end
SessionStart->>JJ: Count commits in `@-`..{default_branch}
JJ-->>SessionStart: Ahead count
alt Count > 0
SessionStart->>Output: Append nudge message
end
SessionStart-->>Output: Emit with additionalContext
sequenceDiagram
participant Hook as PreToolValidate Hook
participant Config as hooks-config.toml
participant Input as ToolInput (old/new/content)
participant Keywords as extract_heading_keywords
participant JJ as jj log
participant Match as find_matching_commits
participant Output as stderr
Hook->>Config: Read pre_tool_validate.todo_staleness
Config-->>Hook: enabled, default_branch, grep_recent_limit
Hook->>Input: Check if docs/todo*.md edit
Input-->>Hook: File path, old_string, new_string, content
Hook->>Keywords: Extract rank and keywords
Keywords-->>Hook: Keyword list
Hook->>JJ: Fetch recent commit descriptions
JJ-->>Hook: Last N commit messages
Hook->>Match: Search for matching keywords
Match-->>Hook: Match results (found or not)
Hook->>Hook: Count commits branch ahead
alt Stale (ahead > 0) or matches found
Hook->>Output: Emit block message
alt Stale
Hook-->>Hook: Exit code 2
else Matches only (warning)
Hook-->>Hook: Exit code 0
end
end
予想される審査工数🎯 4 (複雑) | ⏱️ ~60 分 関連する可能性のあるPR
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/todo-summary.md`:
- Around line 77-78: The table rows are out of ascending order: swap the two
table entries so the row with ID "160" appears after the row with ID "157" to
restore ascending ranking; locate the two rows (IDs 160 and 157, both
referencing todo9.md) in docs/todo-summary.md and move the entire line/row for
"160 | 💎 Tier 3 | **`docs-governance.md` ..." so it follows the "157 | 🔧 Tier
2 | **Bundle 1 dogfood checklist..." row, preserving the original cell content
and pipe-delimited formatting.
In `@src/hooks-pre-tool-validate/src/main.rs`:
- Around line 825-845: count_commits_branch_ahead() returning None is currently
treated as behind = 0 (via unwrap_or(0)), letting stale=false and allowing edits
to pass; instead treat None as a hard failure (fail-closed) and return a
TodoStalenessResult that carries an error/failure message so the hook blocks the
change. Modify the code around count_commits_branch_ahead(branch) so that when
it returns None you do not call unwrap_or(0) but propagate a failure: construct
a descriptive message (including the branch name and failure reason) and return
it via build_todo_staleness_message (or return an Err / appropriate result) so
the hook rejects the change; keep successful path unchanged when
count_commits_branch_ahead returns Some.
In `@src/hooks-session-start/src/main.rs`:
- Around line 426-441: The run_jj_with_timeout helper currently spawns a thread
that runs Command::new("jj").output() but never kills the underlying jj child on
a recv_timeout, so you must rewrite run_jj_with_timeout to spawn the process
with Command::spawn (keeping the Child), move the Child into the thread or
return a handle via channel, and on timeout call child.kill() (and then
child.wait() / try_wait() as needed) to ensure the subprocess is terminated;
preserve the function signature return Option<String>, send stdout back on
success, and apply the exact same fix to the helper with the same name in
src/hooks-pre-tool-validate/src/main.rs.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 3692b7ad-ad87-420a-a0c6-3c4eee404b86
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (7)
.claude/hooks-config.tomldocs/todo-summary.mddocs/todo8.mddocs/todo9.mdsrc/hooks-pre-tool-validate/src/main.rssrc/hooks-session-start/Cargo.tomlsrc/hooks-session-start/src/main.rs
💤 Files with no reviewable changes (1)
- docs/todo8.md
Resolved findings: - [Minor] docs/todo-summary.md:78 順位テーブルの並び順が逆転しています(160 → 157)。 - [Major] src/hooks-pre-tool-validate/src/main.rs:845 lineage 判定失敗時に fail-closed できていません。 - [Major] src/hooks-session-start/src/main.rs:441 タイムアウトしても `jj` 子プロセスが停止されない点を修正してください
…on timeout + order swap)
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
* docs(handoff): 順位 8 (週次レビュー Phase B) 着手用引き継ぎ資料を作成 — 別セッション継続用、Phase B land で retire * docs(todo): PR #177 post-merge-feedback 採用 2 件を todo9.md / summary table に登録 PR #177 (順位 136) post-merge-feedback で採用判定された Tier 2 #1 / #2 を docs/todo9.md に新規エントリとして追加し、docs/todo-summary.md table に行追加。 - 順位 161 (T2 #1): Subprocess timeout+kill lifecycle 検証テスト追加 (PR #177 Major #2 fix の回帰テスト、Child::is_finished で 2 hook の run_jj_with_timeout lifecycle 検証、Effort M) - 順位 162 (T2 #2): fail-closed error path (Option::None) 個別テスト追加 (PR #177 Major #1 fix の回帰テスト、check_todo_staleness / build_todo_staleness_message の None ケース独立検証、Effort S) * fix(docs): address CR Minor findings on PR #178 — broken link + task description mismatch
Summary
順位 136 (Tier 1): working copy staleness 検出 hook を 2 段構え (案 A SessionStart + 案 B PreToolUse) で実装。並列セッション運用で working copy が master より遅れた状態の
docs/todo*.md編集事故 (PR cleanup-stale-rank-39 で実証) を構造的に予防し、旧 順位 122 の「新 todo 着手前の既実装確認」機能を hook 機能として統合。jj git fetch+@-..masterの lineage 確認 → 遅れていれば additionalContext で warning。fail-open (network 失敗時は pass-through)、.git/FETCH_HEADmtime cache で network cost 抑制。docs/todo*.mdへの Edit/Write 時に (1) staleness 検知で stale なら hard block、(2)### heading titleから keyword 抽出してjj log --limit 20の description に grep、関連既実装 commit を warning として stderr 出力。fail-closed (安全側)。ADR-039 (Experimental feature 標準パターン) 準拠の 3 点セット (config opt-in / kill-switch / bounded lifetime) を装備、3-5 PR の dogfood 後に default-ON 昇格 or 却下を判定。
Commit Chain
rywtyqovdocs(todo): PR feat(cli-push-runner): Bundle 3 — 順位 5 _tmp_* pattern 追加 + 順位 159 lint rule⑪ + 順位 142 ADR-041 補強 #176 post-merge-feedback 採用 1 件を todo9.md / summary table に登録 (前セッション分)ynnyqywqfeat(hooks): 順位 136 — working copy staleness 検出 hook 2 段構え (案 A + 案 B)mwusmpsodocs(todo): 順位 136 land 完了に伴い entry を削除Implementation Details (案 A: SessionStart)
src/hooks-session-start/src/main.rs:HooksConfig/SessionStartConfig/StalenessConfig追加 (ADR-039 準拠、default-OFF in source)compute_staleness_nudge/build_staleness_nudge_message/fetch_head_is_recent/run_jj_with_timeout/count_commits_in_revset追加emit_session_start_outputに staleness nudge を統合 (既存 PR_MONITOR_CATCHUP +POST_MERGE_FEEDBACK_REAPER と同列の
additionalContext補強)src/hooks-session-start/Cargo.toml:toml = "0.8"追加 (config parse 用).claude/hooks-config.toml:[session_start.staleness]section 追加 (本リポジトリで明示的にenabled = trueで dogfood 開始、ADR-039 bounded lifetime: 3-5 PR で評価)設計判断:
jj git fetch/jj logの失敗時は warning なしで pass-through (network 異常で session 起動を阻害しない).git/FETCH_HEADmtime が N 秒以内なら fetch skip (default 5 分、network cost 抑制)Command::output()の代替 (Rust 標準 timeout なし問題の workaround)Implementation Details (案 B: PreToolUse)
src/hooks-pre-tool-validate/src/main.rs:ToolInputにold_string/new_string/contentfield 追加 (Edit/Write text 抽出用)TodoStalenessConfig追加 (ADR-039 準拠)is_docs_todo_path/extract_heading_keywords/parse_jj_log_records/find_matching_commits/build_todo_staleness_message/check_todo_staleness/collect_text_for_keywords/run_jj_with_timeout追加mainをread_hook_input/handle_bash_tool/handle_write_edit_toolに分割(
RUST_FUNCTION_TOO_LONG50 行制限の touch-trigger ratchet を解消する refactor).claude/hooks-config.toml:[pre_tool_validate.todo_staleness]section 追加設計判断:
@-..masterの commit 数 > 0 → stderr に staleness + 関連 grep info 出力 + exit 2### heading titleから keyword 抽出 (順位 N prefix 除去、括弧/句読点除去) →jj log --limit 20の description に grep → 上位 3 件を表示build_todo_path("")helper 経由でformat!("docs/todo{}.md", n)構築 (literal のdocs/todo*.mdを source に書かない)docs/todo[\w-]*\.mdregex で windows backslash と forward slash の両方をサポート (replace('\\', '/')で正規化)Tests
cargo test --manifest-path src/hooks-session-start/Cargo.toml: 48 passed (+8 staleness tests)cargo test --manifest-path src/hooks-pre-tool-validate/Cargo.toml: 178 passed (+20 staleness tests)pnpm build:hooks-session-start/pnpm build:hooks-pre-tool-validate: release profile build success// --- 設定 ---および// ベースネーム指定...既存コメントを削除)docs/todo*.mdを編集し、案 A の SessionStart nudge + 案 B の PreToolUse block + grep 提示が機能することを確認観点 ⑤ 責務分離の完成
順位 136 land により、週次レビュー (ADR-031 / 順位 8) の 観点 ⑤ Todo 妥当性 が hook 層に完全委譲されます (2026-05-26 ユーザー合意の 7 観点責務 mapping 通り):
review-todo-wholefacet): hook が拾えない broad な観点 (経年劣化 entry / cross-file 重複 / preamble drift) の batch 棚卸しこれで順位 8 (週次レビュー Phase B) 着手時の MVP scope (3 facets で 6 観点) が clean に整理されます。
ADR-039 準拠
false、.claude/hooks-config.tomlで明示enabled = trueで dogfood 開始 (派生プロジェクトへの deploy 時は default OFF が継承)enabled = falseで完全停止 (network 異常 / feature branch 運用への退避経路).claude/hooks-config.tomlのコメントで明示)Out of Scope
feedback_verify_task_not_already_doneの closure: hook 化で機能吸収後の運用観察を経てから検討 (本 PR では memory 維持)post-merge-feedback で発見された別 finding
なし (takt pre-push-review pass、CR 結果は post-PR で観察)
Summary by CodeRabbit
新機能
設定変更
ドキュメント