Skip to content

feat(hooks): 順位 136 — working copy staleness 検出 hook 2 段構え (案 A SessionStart + 案 B PreToolUse)#177

Merged
aloekun merged 5 commits into
masterfrom
rank-136-staleness-hooks
May 27, 2026
Merged

feat(hooks): 順位 136 — working copy staleness 検出 hook 2 段構え (案 A SessionStart + 案 B PreToolUse)#177
aloekun merged 5 commits into
masterfrom
rank-136-staleness-hooks

Conversation

@aloekun
Copy link
Copy Markdown
Owner

@aloekun aloekun commented May 27, 2026

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 機能として統合。

  • 案 A (SessionStart hook): session 起動時に jj git fetch + @-..master の lineage 確認 → 遅れていれば additionalContext で warning。fail-open (network 失敗時は pass-through)、.git/FETCH_HEAD mtime cache で network cost 抑制。
  • 案 B (PreToolUse hook): 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

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 で評価)

設計判断:

  • fail-open: jj git fetch / jj log の失敗時は warning なしで pass-through (network 異常で session 起動を阻害しない)
  • fetch cache: .git/FETCH_HEAD mtime が N 秒以内なら fetch skip (default 5 分、network cost 抑制)
  • mpsc + thread spawn: timeout 付き Command::output() の代替 (Rust 標準 timeout なし問題の workaround)

Implementation Details (案 B: PreToolUse)

  • src/hooks-pre-tool-validate/src/main.rs:
    • ToolInputold_string / new_string / content field 追加 (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 追加
    • mainread_hook_input / handle_bash_tool / handle_write_edit_tool に分割
      (RUST_FUNCTION_TOO_LONG 50 行制限の touch-trigger ratchet を解消する refactor)
  • .claude/hooks-config.toml: [pre_tool_validate.todo_staleness] section 追加

設計判断:

  • fail-closed: stale 検知時は exit 2 で block
  • 動作 1 (stale 検知): @-..master の commit 数 > 0 → stderr に staleness + 関連 grep info 出力 + exit 2
  • 動作 2 (既実装 grep): ### heading title から keyword 抽出 (順位 N prefix 除去、括弧/句読点除去) → jj log --limit 20 の description に grep → 上位 3 件を表示
  • Self-exclusion (rule⑥ no-ephemeral-todo-reference 対策): test fixture では build_todo_path("") helper 経由で format!("docs/todo{}.md", n) 構築 (literal の docs/todo*.md を source に書かない)
  • Path detection: docs/todo[\w-]*\.md regex で 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
  • markdownlint: 0 errors
  • comment-lint (Bundle Z #B-α): 0 errors (touch-trigger ratchet で発火した // --- 設定 --- および // ベースネーム指定... 既存コメントを削除)
  • custom-lint rule⑥ (no-ephemeral-todo-reference): 0 errors (test fixture self-exclusion で対応)
  • takt pre-push-review: APPROVE
  • dogfood (post-merge): 本 hook 動作確認 — 別 working copy で master より遅れた状態を作って docs/todo*.md を編集し、案 A の SessionStart nudge + 案 B の PreToolUse block + grep 提示が機能することを確認

観点 ⑤ 責務分離の完成

順位 136 land により、週次レビュー (ADR-031 / 順位 8) の 観点 ⑤ Todo 妥当性 が hook 層に完全委譲されます (2026-05-26 ユーザー合意の 7 観点責務 mapping 通り):

  • 本 hook (案 B): docs/todo*.md 編集時の immediate guard (stale block + grep 提示)
  • 週次レビュー (Phase B+1、順位 154 review-todo-whole facet): hook が拾えない broad な観点 (経年劣化 entry / cross-file 重複 / preamble drift) の batch 棚卸し

これで順位 8 (週次レビュー Phase B) 着手時の MVP scope (3 facets で 6 観点) が clean に整理されます。

ADR-039 準拠

  • Config opt-in: source default = false.claude/hooks-config.toml で明示 enabled = true で dogfood 開始 (派生プロジェクトへの deploy 時は default OFF が継承)
  • Kill-switch: enabled = false で完全停止 (network 異常 / feature branch 運用への退避経路)
  • Bounded lifetime: 3-5 PR の dogfood 後に default-ON 昇格 or 却下判定 (.claude/hooks-config.toml のコメントで明示)

Out of Scope

  • 派生プロジェクト (techbook-ledger / auto-review-fix-vc) deploy: post-merge で別途実施
  • memory feedback_verify_task_not_already_done の closure: hook 化で機能吸収後の運用観察を経てから検討 (本 PR では memory 維持)
  • 順位 152 (todo entry 削除時の事前 land 確認): 順位 136 と同系統だが scope 違い、別 PR で実施

post-merge-feedback で発見された別 finding

なし (takt pre-push-review pass、CR 結果は post-PR で観察)

Summary by CodeRabbit

  • 新機能

    • セッション開始時にワーキングコピーの遅れ(staleness)を検出して通知するナッジを追加
    • ドキュメント編集時に過去の実装や直近コミットと照合して「stale」や既実装候補を検出する検証を追加
  • 設定変更

    • セッション開始およびツール実行前検証で staleness 動作を制御する設定を追加
  • ドキュメント

    • TODO一覧のエントリ整理と、ADR関連チェックリストの追記を実施

Review Change Stack

aloekun added 3 commits May 27, 2026 13:39
…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 のコメントで明示)
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 27, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: aa438708-a78c-4194-901a-c9937119ae64

📥 Commits

Reviewing files that changed from the base of the PR and between 5b8b340 and db63cc3.

📒 Files selected for processing (3)
  • docs/todo-summary.md
  • src/hooks-pre-tool-validate/src/main.rs
  • src/hooks-session-start/src/main.rs
✅ Files skipped from review due to trivial changes (1)
  • docs/todo-summary.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/hooks-session-start/src/main.rs
  • src/hooks-pre-tool-validate/src/main.rs

📝 Walkthrough

ウォークスルー

このPRは、ワーキングコピーがデフォルトブランチから遅延している状態を2段階で検出・通知する機構を実装します。SessionStart時にフェッチ状況をnudgeし、todo*.md編集時にstalenessを検出してブロック。設定スキーマ、両hook実装、テスト、ドキュメント更新を含みます。

変更内容

ワーキングコピーStaleness検出機構

層 / ファイル 概要
Staleness設定スキーマ
.claude/hooks-config.toml
[session_start.staleness] セクションをSessionStart hook用に追加(enabled, fetch_timeout_secs, fetch_cache_secs, default_branch)。[pre_tool_validate.todo_staleness] セクションをPreToolValidate用に追加(enabled, default_branch, grep_recent_limit)。
SessionStartワーキングコピーnudge実装
src/hooks-session-start/Cargo.toml, src/hooks-session-start/src/main.rs
Cargo.toml に toml = "0.8" 依存を追加。SessionStart hook で .git/FETCH_HEAD の鮮度をチェックし、古い場合は jj git fetch を実行。その後 jj log @-..{default_branch} でコミット遅延数を計測し、遅延があればフェッチ/リベース/新規ブランチ作成のナッジメッセージを additionalContext に追記。設定の読込、タイムアウト実行、メッセージ生成、テストを含む。
PreToolValidate todo staleness実装
src/hooks-pre-tool-validate/src/main.rs
ToolInput に編集差分・本文フィールド(old_string, new_string, content)を追加。PreToolValidateConfigtodo_staleness 設定を導入。docs/todo*.md 編集を検出し、見出しからキーワード抽出→最近のコミット説明と照合→stale判定(ブランチ ahead)または実装済み候補検出。staleの場合は終了コード2で拒否、候補のみの場合は警告出力。入力読取、各toolハンドラ、staleness判定ロジック、包括的なテストを実装。
TODO追跡ドキュメント更新
docs/todo-summary.md, docs/todo8.md, docs/todo9.md
todo8.md から完成した staleness 検出機構タスク(順位136)を削除。代わりに ADR 番号戦略永続化タスクを追加。todo-summary.md で対応行を削除。todo9.md に「docs-governance.md への ADR multi-variant pattern checklist 追記」タスク(順位160)を追加。

シーケンスダイアグラム

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
Loading
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
Loading

予想される審査工数

🎯 4 (複雑) | ⏱️ ~60 分

関連する可能性のあるPR

  • aloekun/claude-code-hook-test#115: 同じく src/hooks-session-start/src/main.rsemit_session_start_output/additionalContext を拡張する変更を含むため出力組立の重複箇所がある。
  • aloekun/claude-code-hook-test#158: ADR番号付与・順位135運用の調整が本PRのdocs更新と重複する可能性がある。
  • aloekun/claude-code-hook-test#162: 本PRが実装した「2段階working copy staleness hook」をTODOに追加していたPRで、仕様上の重複がある。
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 56.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PRタイトルは順位136のworking copy staleness検出hook 2段構えの実装という、変更セットの主要な内容を正確に反映している。
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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

❤️ Share

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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

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

📥 Commits

Reviewing files that changed from the base of the PR and between 283eafc and 5b8b340.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (7)
  • .claude/hooks-config.toml
  • docs/todo-summary.md
  • docs/todo8.md
  • docs/todo9.md
  • src/hooks-pre-tool-validate/src/main.rs
  • src/hooks-session-start/Cargo.toml
  • src/hooks-session-start/src/main.rs
💤 Files with no reviewable changes (1)
  • docs/todo8.md

Comment thread docs/todo-summary.md Outdated
Comment thread src/hooks-pre-tool-validate/src/main.rs
Comment thread src/hooks-session-start/src/main.rs
aloekun added 2 commits May 27, 2026 16:24
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` 子プロセスが停止されない点を修正してください
@aloekun
Copy link
Copy Markdown
Owner Author

aloekun commented May 27, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 27, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@aloekun aloekun merged commit d8ff101 into master May 27, 2026
1 check passed
@aloekun aloekun deleted the rank-136-staleness-hooks branch May 27, 2026 09:13
aloekun added a commit that referenced this pull request May 28, 2026
* 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant