Skip to content

feat(hooks): merge 前 mechanical gate 強化 (clippy + 空 commit sweep) - #194

Merged
aloekun merged 3 commits into
masterfrom
feat/bundle-mechanical-gate
Jun 3, 2026
Merged

feat(hooks): merge 前 mechanical gate 強化 (clippy + 空 commit sweep)#194
aloekun merged 3 commits into
masterfrom
feat/bundle-mechanical-gate

Conversation

@aloekun

@aloekun aloekun commented Jun 3, 2026

Copy link
Copy Markdown
Owner

Summary

Why

Changes

Phase 1 — 既存 clippy errors 修正 (順位 175 の前提)

  • src/hooks-pre-tool-validate/src/main.rs: if behind.is_none() { return None; }behind?;
  • src/cli-push-runner/src/stages/scratch_file_warning.rs: doc list 段落区切り (doc_lazy_continuation ×3)
  • src/cli-push-runner/src/stages/scratch_file_warning.rs: name.strip_prefix(prefix) (manual_strip)

Phase 2 — 順位 175: cargo clippy mechanical gate

  • .claude/hooks-config.toml: [stop_quality]lint:rust step 追加
  • push-runner-config.toml: [quality_gate.groups]rust-testrust-lint-test rename し clippy を先頭追加 (cargo target dir のロック衝突回避のため同 group 統合)

Phase 3 — 順位 155: 範囲 sweep

  • src/cli-pr-monitor/src/fix_commit.rs: sweep_empty_commits_in_pr_range() 新規 + parse_empty_change_ids() pure helper
  • src/cli-pr-monitor/src/stages/repush.rs: execute_repush_flow 末尾で sweep 呼び出し
  • 新規テスト: parse pure 4 件 + integration 2 件 + 3 test helper (setup / enter_repo / assert_descriptions_absent_in_pr_range)

docs cleanup (2 commit 構成)

  • docs/todo10.md / docs/todo9.md / docs/todo-summary.md: 順位 175 + 155 完了 entry 削除 (memory feedback_todo_no_history 適用)

設計判断 (sweep 配置)

execute_repush_flow 内で sweep を呼ぶ位置として 3 案を検討:

  1. match action: 現 iteration の push 前に sweep → AutoPush の場合も remote が clean
  2. match action (CleanupEmptyFixCommit と統合): tracked と untracked を統一処理
  3. match action後 (採用): 既存 behavior を完全保護、追加的 sweep として動作

案 3 採用理由: 既存 try_abandon_empty_fix_commitreparent_at_to_pr_tip を含む丁寧な cleanup を行うため、sweep を先に走らせて @ を動かすと既存ロジックが [warn] でスキップされ reparent が機能しなくなる。1 iteration 遅れ で次回 push 前に清掃されるトレードオフを受け入れ、現 iteration の既存 flow を保護する。

Test plan

Bundle 内 dogfood 観測

本 PR の Edit 中に hooks-post-tool-comment-lint-rust が 3 回発火 (コメント禁止 7 件 + 関数長 50 行超過 3 件) し、設計修正 (コメント削除 + helper 抽出) を Claude 自身が即座に対応。順位 175 の clippy gate と同じ Bundle Z #B-α 決定論層 family の mechanical enforcement が land 前のセッション内で機能した実例。

Summary by CodeRabbit

  • 新機能

    • Rust 向けの静的チェック(clippy)を品質ゲート/パイプラインに追加
    • PR 内の空コミットを検出して後処理(sweep)する自動化を追加(設定で有効化)
  • 改善

    • push パイプラインの Rust チェック範囲と実行順序を拡張
    • TODO一覧・進行中ドキュメントの整理
  • 変更

    • 未判定のブランチ差分を「古い」と扱う挙動に変更(通知が生成されます)
  • テスト

    • 空コミット処理のユニット/統合テストを追加

Review Change Stack

aloekun added 2 commits June 3, 2026 16:21
- 順位 175: stop_quality / quality_gate に `cargo clippy --workspace -- -D warnings` step 追加
  (PR #185 Tier 1 #1、Rust lint structural gap 補填)
- 順位 155: cli-pr-monitor fix chain 末尾に `master..@` 範囲の空 commit sweep 追加
  (PR #174 Tier 1 #1、kqvluqyv 事例の構造的予防)
- 前提として既存 clippy errors 5 件を併せて修正
  (question_mark / doc_lazy_continuation x3 / manual_strip)

新規テスト: parse_empty_change_ids 4 unit + sweep_empty_commits_in_pr_range 統合 2 件
@coderabbitai

coderabbitai Bot commented Jun 3, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Rust ワークスペース向けに cargo clippy を stop_quality/push-runner 品質ゲートへ追加し、cli-pr-monitor に PR 範囲の空 fix commit を列挙して jj abandon で削除する sweep 機能を実装・repush フローへ統合、関連 todo を削除しました。

変更

Rust Clippy 品質ゲート追加

Layer / File(s) Summary
stop_quality ゲートへの clippy ステップ追加
.claude/hooks-config.toml
[stop_quality]lint:rust ステップを追加し、cargo clippy --workspace -- -D warnings と順位・所要時間・cold cache リスクに関するコメントを追記。
push-runner 品質ゲートグループの再編
push-runner-config.toml
quality_gate.groupsrust-lint-test に再編し、clippy を workspace 対象で fail-fast として追加。既存の cargo test/--ignored テストコマンドは保持。

空 commit 自動クリーンアップ実装

Layer / File(s) Summary
sweep 関数とテスト実装
src/cli-pr-monitor/src/fix_commit.rs
sweep_empty_commits_in_pr_rangeparse_empty_change_ids を実装し、jj log で PR 範囲(default_branch..@)の empty fix commit の change_id を抽出して jj abandon で順次削除する処理、ユニット/統合テストと補助セットアップ関数を追加。
repush フローへの統合
src/cli-pr-monitor/src/stages/repush.rs
execute_repush_flow の action 実行を整理し、fix_config.sweep.enabled のときに sweep_empty_commits_in_pr_range(&fix_config.sweep.default_branch) を呼び出すよう統合。
完了タスク記録の削除
docs/todo-summary.md, docs/todo10.md, docs/todo9.md
「cli-pr-monitor fix chain に空 commit 検査+jj abandon 追加」および「cargo clippy を stop_quality に追加」の完了タスク記述を削除。

設定拡張とテスト

Layer / File(s) Summary
FixConfig に sweep を追加
src/cli-pr-monitor/src/config.rs
SweepConfig 型(enabled: bool, default_branch: String)を追加し、FixConfig#[serde(default)] pub(crate) sweep: SweepConfig を追加。デフォルト値と TOML 読み込みのテストを追加。

コード品質向上(小変更)

Layer / File(s) Summary
文字列処理の簡素化
src/cli-push-runner/src/stages/scratch_file_warning.rs
consume_prefixstarts_with + 手動スライスから str::strip_prefix に置き換え。
staleness 判定の扱い変更
src/hooks-pre-tool-validate/src/main.rs
behind == None の場合も stale 扱いとし、メッセージに「lineage 判定不能のため fail-closed で block」を追加。早期 return を廃止して処理を継続。

Sequence Diagram(s)

sequenceDiagram
  participant RepushFlow as execute_repush_flow
  participant RepushAction as execute_repush_action
  participant Sweep as sweep_empty_commits_in_pr_range
  participant JJLog as "jj log"
  participant JJAbandon as "jj abandon"

  RepushFlow->>RepushAction: perform RepushAction
  RepushAction-->>RepushFlow: action result
  RepushFlow->>Sweep: if fix_config.sweep.enabled
  Sweep->>JJLog: run revset query (default_branch..@) for empty fix(review) commits
  JJLog-->>Sweep: change_id list
  Sweep->>JJAbandon: jj abandon <change_id> (for each)
  JJAbandon-->>Sweep: abandon results
  Sweep-->>RepushFlow: sweep complete
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

関連する可能性のあるプルリクエスト

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed タイトルは、PR の主要な変更内容(mechanical gate 強化、clippy追加、空 commit sweep)を簡潔に正確に反映している。
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/cli-pr-monitor/src/stages/repush.rs (1)

145-169: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

fail-safe 分岐のあとで sweep を無条件実行しないでください。

上の分岐は IdCaptureFailedtry_abandon_empty_fix_commit() の安全側スキップでローカル履歴を触らない設計ですが、Line 169 で直後に再び jj abandon を走らせています。これだと「不確実なので何もしない」と判断したケースでも sweep が同じ空 commit を消し得るので、少なくとも安全に状態を信頼できる action に限定して呼ぶ必要があります。

🤖 Prompt for 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.

In `@src/cli-pr-monitor/src/stages/repush.rs` around lines 145 - 169, The
unconditional call to
crate::fix_commit::sweep_empty_commits_in_pr_range("master") must be removed and
only invoked for actions that are allowed to mutate local history; update the
match over RepushAction to call sweep_empty_commits_in_pr_range("master") inside
the safe arms (e.g., RepushAction::AutoPush and
RepushAction::CleanupEmptyFixCommit { .. }) instead of after the match, leaving
the fail-safe arms (RepushAction::UserConfirmWithSeparatedFix,
RepushAction::UserConfirmNoSeparation, RepushAction::SkipNoChange,
RepushAction::FailSafeCaptureFailed and any IdCaptureFailed paths) without any
sweep; keep the existing try_abandon_empty_fix_commit("fix_state=Created:",
Some(&commit_id)) usage in CleanupEmptyFixCommit and add the sweep call there
(and in run_auto_push result path) so only trusted flows run
sweep_empty_commits_in_pr_range.
🤖 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 `@push-runner-config.toml`:
- Line 64: The comment "# PostToolUse / Stop hook では実行せず、イテレーション速度を保護する。" in
push-runner-config.toml is outdated because a Stop hook (see
[[stop_quality.steps]] name = "lint:rust" in .claude/hooks-config.toml) now
runs; update that comment to accurately reflect current behavior (e.g., state
that the hook runs on Stop and mention impact on iteration speed or that
lint:rust is executed on Stop) so operators aren't misled.

In `@src/cli-pr-monitor/src/fix_commit.rs`:
- Around line 217-238: run_cmd_direct returns combined stdout/stderr so the
current code passes merged output into parse_empty_change_ids which may pick up
jj warnings from stderr; change this by making the jj log invocation use only
stdout (call/run the underlying function to return stdout separately) or by
filtering the returned out before calling parse_empty_change_ids: feed
parse_empty_change_ids only lines that match a valid change_id pattern (e.g.
regex for expected ID format) or explicitly split and discard stderr-like lines;
update the code around run_cmd_direct, the variable out, and the call site
parse_empty_change_ids to ensure only valid stdout-derived change IDs are
processed before using change_ids (and keep JJ_CMD_TIMEOUT_SECS/ok handling
unchanged).
- Around line 203-248: The revset used in sweep_empty_commits_in_pr_range is too
broad (empty() & (default_branch..@)) and abandons any empty commit; narrow it
to only target fix-related commits by adding a provenance/summary filter (e.g.
require commit summary to match the fix pattern). Change the revset construction
in sweep_empty_commits_in_pr_range (the revset variable) to include a summary
regex like summary ~ "^fix\\(" (or another project-specific marker for fix
commits) so parse_empty_change_ids and the subsequent run_cmd_direct("jj",
&["abandon", cid], ...) only operate on intended fix commits; update the log
message accordingly and keep existing fail-open behavior.

In `@src/cli-pr-monitor/src/stages/repush.rs`:
- Line 169: Replace the hardcoded "master" call in repush.rs to respect the
experimental feature pattern: read an opt-in flag and kill-switch from config
(e.g., config.experimental.sweep_empty_commits or
config.kill_switchs.sweep_empty_commits), fetch the repository default branch
name (do not assume "master") and pass that variable into
sweep_empty_commits_in_pr_range, and add a bounded lifetime parameter (e.g.,
max_age_days from config) so the sweep only runs for a limited window; ensure
the sweep is skipped unless the opt-in is set and not globally disabled by the
kill-switch.

In `@src/hooks-pre-tool-validate/src/main.rs`:
- Around line 845-847: The current use of behind? after calling
count_commits_branch_ahead causes check_todo_staleness to return None when
lineage cannot be determined, bypassing the fail-closed behavior; change the
handling so None is treated as "stale" instead of propagating None: in
check_todo_staleness replace the behind? call with explicit matching or map_or
(e.g., treat None as >0) so stale is true when count_commits_branch_ahead
returns None, and update build_todo_staleness_message to likewise interpret a
None behind result as stale (return the same stale message path as for non-zero
behind) so both functions consistently treat None => stale; refer to
count_commits_branch_ahead, check_todo_staleness, build_todo_staleness_message
and the behind variable.

---

Outside diff comments:
In `@src/cli-pr-monitor/src/stages/repush.rs`:
- Around line 145-169: The unconditional call to
crate::fix_commit::sweep_empty_commits_in_pr_range("master") must be removed and
only invoked for actions that are allowed to mutate local history; update the
match over RepushAction to call sweep_empty_commits_in_pr_range("master") inside
the safe arms (e.g., RepushAction::AutoPush and
RepushAction::CleanupEmptyFixCommit { .. }) instead of after the match, leaving
the fail-safe arms (RepushAction::UserConfirmWithSeparatedFix,
RepushAction::UserConfirmNoSeparation, RepushAction::SkipNoChange,
RepushAction::FailSafeCaptureFailed and any IdCaptureFailed paths) without any
sweep; keep the existing try_abandon_empty_fix_commit("fix_state=Created:",
Some(&commit_id)) usage in CleanupEmptyFixCommit and add the sweep call there
(and in run_auto_push result path) so only trusted flows run
sweep_empty_commits_in_pr_range.
🪄 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: 16081d24-e75b-484d-ae11-7968a14a91ba

📥 Commits

Reviewing files that changed from the base of the PR and between f59a2f7 and ec62dff.

📒 Files selected for processing (9)
  • .claude/hooks-config.toml
  • docs/todo-summary.md
  • docs/todo10.md
  • docs/todo9.md
  • push-runner-config.toml
  • src/cli-pr-monitor/src/fix_commit.rs
  • src/cli-pr-monitor/src/stages/repush.rs
  • src/cli-push-runner/src/stages/scratch_file_warning.rs
  • src/hooks-pre-tool-validate/src/main.rs
💤 Files with no reviewable changes (3)
  • docs/todo10.md
  • docs/todo9.md
  • docs/todo-summary.md

Comment thread push-runner-config.toml
Comment thread src/cli-pr-monitor/src/fix_commit.rs
Comment on lines +217 to +238
let (ok, out) = run_cmd_direct(
"jj",
&[
"log",
"-r",
&revset,
"--no-graph",
"-T",
"change_id ++ \"\\n\"",
],
&[],
JJ_CMD_TIMEOUT_SECS,
);
if !ok {
log_info(&format!(
"[warn] sweep_empty_commits: jj log 失敗 (sweep skip): {}",
out.trim()
));
return;
}

let change_ids = parse_empty_change_ids(&out);

@coderabbitai coderabbitai Bot Jun 3, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

jj log の結合出力をそのまま change_id 一覧として parse しないでください。

src/cli-pr-monitor/src/runner.rs:24-78run_cmd_direct() は成功時でも stdout/stderr を結合して返します。ここで jj log が warning を stderr に出すと、その行まで parse_empty_change_ids() に入り、存在しない ID を jj abandon に渡す形になります。ID 抽出は stdout のみに限定するか、少なくとも change_id として妥当な行だけを通してください。

🤖 Prompt for 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.

In `@src/cli-pr-monitor/src/fix_commit.rs` around lines 217 - 238, run_cmd_direct
returns combined stdout/stderr so the current code passes merged output into
parse_empty_change_ids which may pick up jj warnings from stderr; change this by
making the jj log invocation use only stdout (call/run the underlying function
to return stdout separately) or by filtering the returned out before calling
parse_empty_change_ids: feed parse_empty_change_ids only lines that match a
valid change_id pattern (e.g. regex for expected ID format) or explicitly split
and discard stderr-like lines; update the code around run_cmd_direct, the
variable out, and the call site parse_empty_change_ids to ensure only valid
stdout-derived change IDs are processed before using change_ids (and keep
JJ_CMD_TIMEOUT_SECS/ok handling unchanged).

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

resolved: run_cmd_direct 非成功時は早期 return で parse 対象外。jj log 成功時は stderr emit せず structured な change_id 出力のみを parse する経路。dogfood で stderr leakage 観測時に別途追加防御を検討。

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@aloekun、説明ありがとうございます。整理できました。

  • 失敗パス: !ok で即 return → parse_empty_change_ids は呼ばれない ✓
  • 成功パス: jj log --no-graph -T "change_id ++ \"\\n\"" は structured template 出力のみ。成功時に jj が stderr へ warning を出すケースは通常ない ✓
  • 追加の安全層: 実装では revset に description(substring:"fix(review):") フィルタが入っており、diff 時点のドキュメントより scope がさらに絞られている ✓

dogfood で stderr leakage が観測された場合の段階的防御方針も妥当です。現状の実装で問題ないと判断します。


🧠 Learnings used
Learnt from: aloekun
Repo: aloekun/claude-code-hook-test PR: 119
File: src/cli-finding-classifier/src/lib.rs:86-96
Timestamp: 2026-05-06T14:17:51.353Z
Learning: In `src/cli-finding-classifier/src/lib.rs`, the `normalized_issue` field from LLM output is intentionally validated only with trim + non-empty filter (no newline/length checks). Strict validation (single-line, max 80 chars enforcement + fallback) is deferred to Phase 5 (cli-pr-monitor integration) or when an actual problem occurs. The current prompt template (`classify.txt`) includes a `max 80 chars` instruction that is considered sufficient guard in the meantime. Do not flag this as missing validation in code reviews.

Learnt from: aloekun
Repo: aloekun/claude-code-hook-test PR: 71
File: src/hooks-stop-feedback-dispatch/src/main.rs:127-135
Timestamp: 2026-04-23T17:49:44.755Z
Learning: In `aloekun/claude-code-hook-test`, `src/hooks-stop-feedback-dispatch/src/main.rs`, the stale-GC branch in `handle_pending()` unconditionally deletes `{pending}.lock` before acquiring the lock. This is an accepted tradeoff: the race window is ~0.001% (24h TTL vs 10–100ms dispatch), and acquiring the lock before stale cleanup was rejected because it would eliminate the only recovery path for leaked locks (from kill -9 etc.), causing permanent stuck state. The design relies on the 24h TTL as a backstop for leaked locks. Re-raise this finding only if: (1) double dispatch is observed in production, (2) a shorter stale TTL (e.g., 1h) is needed, or (3) the code is ported to a hostile multi-process environment.

Learnt from: aloekun
Repo: aloekun/claude-code-hook-test PR: 53
File: docs/todo.md:106-106
Timestamp: 2026-04-18T08:42:52.662Z
Learning: In `aloekun/claude-code-hook-test`, within `src/cli-pr-monitor`, `run_push()` is defined in `src/cli-pr-monitor/src/stages/push.rs` (line 15) as `pub(crate) fn run_push(config: &FixConfig) -> bool`. It is imported and called from `src/cli-pr-monitor/src/stages/repush.rs` via `use crate::stages::push::run_push;`. Do NOT flag references to `push.rs` for `run_push` as incorrect — `push.rs` is the canonical definition site.

Learnt from: aloekun
Repo: aloekun/claude-code-hook-test PR: 99
File: docs/pipeline-token-efficiency.md:222-242
Timestamp: 2026-05-01T17:18:31.023Z
Learning: In `aloekun/claude-code-hook-test`, `docs/pipeline-token-efficiency.md` is a phased improvement planning document. Specifications described within a "Phase N" section (e.g., `#B-β` Phase 2, `#B-γ` Phase 3) represent future planned implementations and should NOT be flagged as missing implementations in PRs that only cover an earlier phase. Always check the PR scope and the progress table at the bottom of the document to determine which phase is being implemented before raising findings about unimplemented specs.

Learnt from: aloekun
Repo: aloekun/claude-code-hook-test PR: 44
File: src/cli-pr-monitor/src/stages/repush.rs:40-54
Timestamp: 2026-04-16T15:41:17.548Z
Learning: In `aloekun/claude-code-hook-test`, `auto_push_severity` in `src/cli-pr-monitor` is a configuration preset (not a severity threshold comparator). Valid values are "none" / "critical" / "major", where both "critical" and "major" mean "always auto push" per ADR-019 L44-46. The function `should_auto_push(setting: &str)` in `src/cli-pr-monitor/src/stages/repush.rs` correctly returns `true` for both "critical" and "major" — do NOT flag this as missing threshold comparison logic. ADR-019 L52 prohibits double-judging AI evaluations in Rust, and ADR-019 L78 prohibits raw-findings-based auto push judgment.

Learnt from: aloekun
Repo: aloekun/claude-code-hook-test PR: 64
File: docs/adr/adr-022-automation-responsibility-separation.md:128-128
Timestamp: 2026-04-21T09:09:02.341Z
Learning: In aloekun/claude-code-hook-test, markdownlint-cli2 (0.22.0) can false-positive MD038 (no-space-in-code) on content inside fenced code blocks (``` ... ```). MD038 only applies to inline backtick code spans, not fenced code block content. Do not flag MD038 warnings for lines inside fenced code blocks in this repository.

Learnt from: CR
Repo: aloekun/claude-code-hook-test PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-05-25T14:21:38.565Z
Learning: Applies to hooks/**/*.rs : Implement Stop hooks as quality gates (ADR-004) to enforce code quality standards before proceeding

Learnt from: aloekun
Repo: aloekun/claude-code-hook-test PR: 38
File: src/cli-pr-monitor/src/runner.rs:109-115
Timestamp: 2026-04-15T05:44:09.231Z
Learning: In this Windows-only `cli-pr-monitor` tool, hardcoded Windows `.exe` expectations are intentional. Do not require cross-platform handling (e.g., avoid changing `.exe` suffix usage) when reviewing Rust code under `src/cli-pr-monitor/src/`—including logic such as `checker_exe_path()` and related `.exe` references in error messages.

Comment thread src/cli-pr-monitor/src/stages/repush.rs Outdated
Comment thread src/hooks-pre-tool-validate/src/main.rs Outdated
Resolved findings:
- [Minor] push-runner-config.toml:64 コメント説明が最新の設定と矛盾しています。
- [Major] src/cli-pr-monitor/src/fix_commit.rs:248 削除対象の revset が広すぎます。
- [Minor] src/cli-pr-monitor/src/fix_commit.rs:238 `jj log` の結合出力をそのまま change_id 一覧として parse しないでください。
- [Major] src/cli-pr-monitor/src/stages/repush.rs:169 新しい sweep を `master` 固定・常時有効で配線しないでください。
- [Major] src/hooks-pre-tool-validate/src/main.rs:847 `behind` 判定不能時に gate をバイパスしており fail-closed 契約に反しています

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
src/cli-pr-monitor/src/config.rs (1)

97-118: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

ADR-039 の実験機能パターン要件が未充足です。

Line 97-118 の SweepConfig は opt-in(enabled)のみで、kill-switch と bounded lifetime がありません。設定スキーマに両方を追加し、実行側で必ず評価してください。

🔧 最小追加イメージ
 pub(crate) struct SweepConfig {
     #[serde(default)]
     pub(crate) enabled: bool,
+    #[serde(default)]
+    pub(crate) kill_switch: bool,
+    #[serde(default = "default_sweep_ttl_days")]
+    pub(crate) ttl_days: u16,
     #[serde(default = "default_sweep_branch")]
     pub(crate) default_branch: String,
 }
+
+fn default_sweep_ttl_days() -> u16 {
+    30
+}
// repush 側イメージ(別箇所)
if fix_config.sweep.enabled
    && !fix_config.sweep.kill_switch
    && !fix_config.sweep.is_expired()
{
    sweep_empty_commits_in_pr_range(&fix_config.sweep.default_branch);
}

As per coding guidelines "Implement Experimental feature standard pattern with config opt-in, kill-switch, and bounded lifetime (ADR-039)".

🤖 Prompt for 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.

In `@src/cli-pr-monitor/src/config.rs` around lines 97 - 118, SweepConfig
currently only provides opt-in via enabled; add the ADR-039 required kill-switch
and bounded lifetime by adding a pub(crate) kill_switch: bool with serde default
false and a pub(crate) expires_at: Option<DateTime<Utc>> (or equivalent
timestamp string) plus an is_expired(&self) -> bool method that returns true if
expires_at is Some and <= now; update Default for SweepConfig to set kill_switch
= false and expires_at = None (or a sane default), and update the execution site
that calls sweep_empty_commits_in_pr_range to guard with the full check:
fix_config.sweep.enabled && !fix_config.sweep.kill_switch &&
!fix_config.sweep.is_expired() so the sweep runs only when enabled, not killed,
and not expired.
🧹 Nitpick comments (1)
src/hooks-pre-tool-validate/src/main.rs (1)

2187-2231: ⚡ Quick win

build_todo_staleness_messagebehind = None ケースのテストがありません。

既存テストは behindSome(0), Some(2), Some(3) のケースをカバーしていますが、今回追加された fail-closed 動作 (behind = None) のテストがありません。

🧪 テスト追加案
     #[test]
     fn build_todo_staleness_message_neither_returns_none() {
         let path = build_todo_path("");
         let msg = build_todo_staleness_message(&path, Some(0), &[], "master");
         assert!(msg.is_none());
     }
+
+    #[test]
+    fn build_todo_staleness_message_fail_closed_when_behind_none() {
+        let path = build_todo_path("");
+        let msg = build_todo_staleness_message(&path, None, &[], "master");
+        let msg = msg.expect("None behind should produce fail-closed message");
+        assert!(msg.contains("lineage 判定不能"));
+        assert!(msg.contains("fail-closed"));
+    }
🤖 Prompt for 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.

In `@src/hooks-pre-tool-validate/src/main.rs` around lines 2187 - 2231, Add a unit
test covering the new fail-closed behavior when build_todo_staleness_message is
called with behind = None: create a test (e.g.,
build_todo_staleness_message_behind_none_returns_stale) that calls
build_todo_staleness_message(&path, None, &[], "main") and asserts the result is
Some(...) and that the message contains the path, the branch name ("main"), and
the stale indicator (e.g., "stale parent detected"); also add a second small
case with a non-empty matches vector to assert it still includes the
related-implementation text (e.g., "関連既実装の可能性") when matches are present. Ensure
the tests refer to build_todo_staleness_message and use the existing
build_todo_path helper for the path.
🤖 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.

Duplicate comments:
In `@src/cli-pr-monitor/src/config.rs`:
- Around line 97-118: SweepConfig currently only provides opt-in via enabled;
add the ADR-039 required kill-switch and bounded lifetime by adding a pub(crate)
kill_switch: bool with serde default false and a pub(crate) expires_at:
Option<DateTime<Utc>> (or equivalent timestamp string) plus an is_expired(&self)
-> bool method that returns true if expires_at is Some and <= now; update
Default for SweepConfig to set kill_switch = false and expires_at = None (or a
sane default), and update the execution site that calls
sweep_empty_commits_in_pr_range to guard with the full check:
fix_config.sweep.enabled && !fix_config.sweep.kill_switch &&
!fix_config.sweep.is_expired() so the sweep runs only when enabled, not killed,
and not expired.

---

Nitpick comments:
In `@src/hooks-pre-tool-validate/src/main.rs`:
- Around line 2187-2231: Add a unit test covering the new fail-closed behavior
when build_todo_staleness_message is called with behind = None: create a test
(e.g., build_todo_staleness_message_behind_none_returns_stale) that calls
build_todo_staleness_message(&path, None, &[], "main") and asserts the result is
Some(...) and that the message contains the path, the branch name ("main"), and
the stale indicator (e.g., "stale parent detected"); also add a second small
case with a non-empty matches vector to assert it still includes the
related-implementation text (e.g., "関連既実装の可能性") when matches are present. Ensure
the tests refer to build_todo_staleness_message and use the existing
build_todo_path helper for the path.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 092c1813-d53b-4d53-bc72-9aa75c297988

📥 Commits

Reviewing files that changed from the base of the PR and between ec62dff and dfad56f.

📒 Files selected for processing (4)
  • src/cli-pr-monitor/src/config.rs
  • src/cli-pr-monitor/src/fix_commit.rs
  • src/cli-pr-monitor/src/stages/repush.rs
  • src/hooks-pre-tool-validate/src/main.rs

@aloekun
aloekun merged commit 94f4f36 into master Jun 3, 2026
1 check passed
@aloekun
aloekun deleted the feat/bundle-mechanical-gate branch June 3, 2026 16:23
aloekun added a commit that referenced this pull request Jun 4, 2026
…t patterns + experimental feature checklist (#195)

* docs(todo): PR #194 follow-up 6 件登録 (順位 183-188)

* test(hooks): behind=None fail-closed ケースの回帰 test 追加 (PR #194 T2-#1)

* test(pr-monitor): sweep revset scope + alternative branch variant tests 追加 (PR #194 T2-#2)

* docs(rules): jj integration test の不変式パターンを testing.md に codify (PR #194 T2-#3)

* docs(adr): ADR-043 Security/Quality Gate Fail-Closed 原則 新設 (PR #194 T3-#2)

* docs(adr): ADR-021 に § Revset Composability section 追記 (PR #194 T3-#3)

* docs(adr): ADR-039 § 設計チェックリスト 追加 + patterns.md experimental feature section (PR #194 T3-#1)

* docs(todo): PR #194 follow-up 6 件 land に伴い todo-summary/todo10 entry 削除

* fix(review): assert_descriptions_absent_in_pr_range を default_branch 引数化 (CR Major PR #195)
aloekun added a commit that referenced this pull request Jun 6, 2026
本 PR は以下 4 つの作業を 1 コミットに統合:

## A. PR #196 post-merge-feedback 採用 2 件 (順位 193, 194 登録)

PR #196 (Bundle 195-FB) post-merge-feedback 8 件のうち 2 件採用、6 件却下/様子見。

採用 (todo10.md に entry 追加、todo-summary.md table に行追加):
- 順位 193 (T2): Companion helper group 署名整合 compile-time validation test
- 順位 194 (T3): development-workflow.md \"1. Plan First\" に Codification
  重複確認 step 追記

却下/様子見の詳細は .claude/feedback-reports/196.md 参照。

## B. queue 棚卸し (順位 ≤ 100 の 32 件を audit)

8 件削除 + 7 件改訂で queue の signal/noise 改善:

削除 8 件 (既存 land 確認、または動機失効):
- 順位 41 (Bundle Y2 効果定量計測): 動機の主軸失効 (Bundle Z 完成 + Z2 不採用)
- 順位 42, 43, 46 (rate-limit auto-retry 系): PR #97/#113/#129/#185 段階 land 完了
- 順位 45 (--list-findings Rust モード): PR #101 で land 済
- 順位 57 (Aggregation cap integration test): PR #171 で land 済
- 順位 93 (coding-style.md partial fix 例追加): ~/.claude/rules/common/
  coding-style.md に既に section 存在
- 順位 97 (with_num_ctx serialization test): lib.rs:494 で mockito test 実体存在

改訂 7 件 (Status update 2026-06-06 を front-matter に追加、現状反映):
- 順位 11: ADR-018 park / ADR-030 短命プロセス移行後の再現確認が前段
- 順位 19: ADR-037/043/PR #194 land 後の残余 case baseline 観測が前段
- 順位 27: Phase D-7 = PR #154 land 済を反映
- 順位 38: ADR-031 採用昇格済 (PR #192) → Bundle W/X land のみ残依存
- 順位 40: PR #175 push-runner bookmark_check 実装済 → skill 側は二重防御に縮小
- 順位 51: 採用案 C (fix.md instruction 追加) は land 済、残作業 = dogfood 観測のみ
- 順位 92: ADR-038 採用昇格済 (PR #156) で Phase d 運用入り、動機書き換え

cross-reference 修復:
- 順位 49: 旧依存 Bundle a Sub-PR 2 (順位 42/43/46) 消滅を反映
- 順位 61: 旧依存 順位 45 land 済を反映

## C. todo9.md → todo11.md 分割

todo9.md が 75KB / 890 行に到達し読み取り安定性閾値 (50KB) 超過のため分割:

- todo9.md (37KB / 454 行): 既存ルール仕組み化バンドル (順位 146-151) + 週次
  レビュー拡張 (順位 152-154) を保持
- todo11.md (41KB / 453 行、新規): PR-specific follow-up entries 10 件
  (順位 157, 160, 161, 162, 163, 165, 170, 171, 172, 173)

theme-based split で意味的分離 + 両ファイルとも 50KB 閾値以下に収まる。
todo-summary.md table の file 参照を Python script で一括更新 (10 件)、
todo-summary.md 冒頭の \"追加先ファイル\" 説明を todo10.md に更新。

## D. 順位 177 優先度引上げ

PostToolUse hook ファイルサイズ検出 task が 4 回目の同型観測に到達 (PR #133 +
#172 + #186 + 本セッション = Very High frequency)。CLAUDE.md code-review.md
\"同型 finding の閾値判定\" (3 観測 = Tier 1 昇格) を超え systemic risk 閾値到達。

3 箇所同期更新:
- todo10.md entry 本体に Status update 2026-06-06 blockquote 追加、優先度を
  \"Tier 1\" → \"Tier 1 (優先実装)\" に格上げ
- todo-summary.md table 行で Tier 列を太字 + 注記、dependency 列に urgency note
- todo-summary.md 末尾の戦略 note に \"直近優先 (2026-06-06 ユーザー指示)\"
  段落を新設、Bundle 195-FB-Followup (順位 193 + 194) の次の PR で消化推奨と
  specific call-out

## 統計

- 10 ファイル変更 (1 新規)
- ~670 lines insertions, ~910 lines deletions (net ~240 行削減)
- 全 todo*.md が 50KB 閾値以下に収まる (todo9: 37KB, todo10: 36KB, todo11: 41KB)

## 参照

- .claude/feedback-reports/196.md (採否確定 commit、PR #196 由来)
- memory feedback_post_merge_feedback_adoption_requires_user_approval per
  ユーザー承認済
- memory feedback_todo_no_history (削除は痕跡なし、コメントマーカー不使用)
- ADR-035 (docs-only PR 評価ポリシー)
- ADR-033 (採番管理簡素化、順位 renumber は避けて semantic markers で表現)
aloekun added a commit that referenced this pull request Jun 8, 2026
…除) (#199)

* feat(cli-pr-monitor): PastTime newtype + proptest for parse_iso8601/from_parts (Bundle W)

Bundle W (順位 34 PBT + 順位 35 PastTime newtype) を Tier 1 として独立 land。
PR #96 で実証された Finding D class (saturating_sub silent semantic mismatch) を
型層で再発不能化し、proptest で regression net を確立。

## 順位 35: PastTime newtype

src/cli-pr-monitor/src/lock.rs に PastTime { epoch_secs, captured_now } を導入。
`from_iso8601_now` / `from_parts` で「parse 成功 + then <= now」の 2 ステップを
construction に閉じ込め、`age_secs()` が常に非負となる invariant を構造的に保証。

旧 `parse_age_secs(iso8601) -> Option<i64>` は削除し、`read_fresh_lock` を
PastTime ベースに refactor。public API への影響なし (lock module 内 closure)。

## 順位 34: proptest properties

src/cli-pr-monitor/Cargo.toml の [dev-dependencies] に proptest = "1" を追加。
lock.rs の #[cfg(test)] mod proptests に 5 properties を記述:

- P1 past_time_age_is_correct_when_in_past: then <= now で age_secs == now - then
- P2 past_time_rejects_future: then > now で必ず None (Finding D 直接対応)
- P3 parse_iso8601_never_panics: 任意 string で panic しない
- P4 parse_iso8601_rejects_pre_epoch_year: year < 1970 を必ず reject
- P5 parse_iso8601_accepts_well_formed: 有効範囲内の正規 ISO 8601 を必ず accept

PastTime 単体 unit test 4 件 (accepts_past / accepts_equal / rejects_future /
accepts_unix_epoch_origin / rejects_far_future_year_9999) も追加。

実行時間: cargo test -p cli-pr-monitor lock:: で 0.04s (todo4.md 完了基準
「pre-push pipeline +1 秒以内」を充足)。

## 依存解除

Bundle W は元来「順位 19 (REJECT-ESCALATE) land 後着手」とユーザー指示があったが、
順位 19 自身が Status update 2026-06-06 で「5-10 PR baseline 観測フェーズ」に移行
した結果、Tier 1 → Tier 2 の priority inversion + 待機解除タイミング未定でデッド
ロックに近い状態だった。

依存解除の根拠 (advisor 検証済):
- ADR-037 (fix-trust shortcut, PR #106 land): `convergence_verdict: fully_resolved`
  で COMPLETE 直行 → iteration が verdict ベースで有限化、無限 loop 経路は解消済
- ADR-043 (fail-closed 原則, PR #194 land): security/quality gate の semantic を構造化
- PR #194 (mechanical gate 強化): merge 前の検証層を拡充

これにより PBT 由来の test 失敗を fix loop が処理しきれず暴走する旧懸念は解消済。
docs/todo-summary.md 順位 34 row および docs/todo4.md 順位 34 実行優先度行に
依存解除の根拠を明記。

## scope 外

- Finding E (concurrency race): proptest は data generation には強いが thread
  interleaving 網羅は苦手 (todo4.md 詰まっている箇所に既記載)。Bundle X
  (順位 36 cargo-mutants + 順位 37 stress runner) に委譲
- 派生プロジェクト deploy 計画: Bundle W land 後の別 task として todo 登録予定

## 完了確認

- cargo build -p cli-pr-monitor: 成功
- cargo test -p cli-pr-monitor lock::: 23 passed (proptest 5 + PastTime 4 + 既存 14)
- cargo clippy -p cli-pr-monitor --all-targets -- -D warnings: clean

Refs: 順位 34, 35 (todo-summary.md), todo4.md Bundle W entries

* docs(todo): Bundle W (順位 34 + 35) land 完了に伴い削除
aloekun added a commit that referenced this pull request Jun 8, 2026
…nd で代替経路実現済、PR #194-後 8 runs で escalation 候補 0 件

PR #199 (Bundle W) session 内合意 (ユーザー指示 2026-06-07): 「A の PR マージまで進ん
だら、B. 順位 19 自体の Tier 再評価 のタスク化を進める」を実行し、実測値に基づき
(C) retire を決定。

## 決定根拠 (empirical data)

PR #194 (mechanical gate 強化) land 後の takt run logs を集計:

| Run | iteration 上限到達 | supervise step 到達 | escalation 候補 |
|---|---|---|---|
| 8 runs (pre-push-review 7 + post-pr-review 1) | 0/8 | 0/8 | 0/8 |

REJECT-ESCALATE が解決するはずの「iteration 上限到達 + supervise/fix_supervisor の
無限ループ」が **PR #194 land 以降 1 件も観測されていない**。代替経路で十分実用に
耐えていることが定量的に確認できた:

- ADR-037 (fix-trust shortcut、PR #106 land): `convergence_verdict: fully_resolved` で
  COMPLETE 直行 → iteration が verdict ベースで有限化
- ADR-043 (fail-closed 原則、PR #194 land): security/quality gate semantics 構造化
- PR #194 (mechanical gate 強化): merge 前 clippy + 空 commit sweep の決定論層追加

## 変更内容

- docs/todo3.md: 順位 19 entry block を削除 (-46 行)
- docs/todo-summary.md: table から 順位 19 row を削除 (-1 行)

順位 19 を gate にしていた Tier 1 (旧 順位 34/35) は既に PR #199 で priority
inversion 解除済のため、本 retire で残るブロッキングはない。

## 関連 PR / ADR

- PR #199 (Bundle W、priority inversion 解除)
- ADR-037 fix-trust shortcut
- ADR-043 Security/Quality Gate Fail-Closed 原則
- PR #194 mechanical gate 強化 (clippy + 空 commit sweep)
aloekun added a commit that referenced this pull request Jun 9, 2026
…-1/T3-2 採用 (#200)

* feat(cli-docs-lint): priority_inversion validator — Tier 1→Tier 2 依存を機械検知

PR #199 (Bundle W) で実観測した「Tier N が Tier N+k に依存 + 待ち先 Tier N+k 自身が
観測フェーズ」パターンを cli-docs-lint に validator 化。決定論的検出層で Claude 判断
介入なし (`feedback_pipeline_over_rules.md` 適用)。

## 実装

- `src/cli-docs-lint/src/priority_inversion.rs` (新規): todo-summary.md table を parse し
  - 順位/Tier 抽出 (`parse_row`, `parse_tier`)
  - 依存記述から 順位 NN 参照を抽出 (`extract_referenced_ranks`、複合 `順位 NN/MM` 対応)
  - 「なし」プレフィックスで context note を排除 (`has_no_dependency_prefix`)
  - 依存先 Tier > 自分の Tier かつ resolved-marker なしを violation 化
- `src/cli-docs-lint/src/lib.rs`: `priority_inversion` module を export
- `src/cli-docs-lint/src/main.rs`: `--check priority-inversion` mode + `all` 経路統合 +
  help / describe_mode に追記

## resolved-marker 設計

依存記述内で「順位 NN ... land 済 / 完了 / retired / 採用昇格済」が 80 char window
以内に現れる場合 resolved 扱い (= inversion check skip)。「land 済 (2026-06-07)」のような
日付付きマーカーや「Bundle X land 済」のような bundle-level マーカーをカバー。

## 性能 (F-2 対応、pre-push reviewer non-blocking finding)

`parse_tier` / `extract_referenced_ranks` の regex は `std::sync::LazyLock<Regex>` で
module 初期化時に 1 度だけ compile (per-row 再 compile を回避)。既存 codebase の
OnceLock 利用 (cli-pr-monitor) と整合する std::sync::* family。

## テスト fixture 設計 (F-3 対応、pre-push reviewer non-blocking finding)

`check_content_skips_when_referenced_rank_missing_from_table` テストの fixture を
`"順位 19 land 後推奨"` (resolved-marker 非含有) に変更。missing-rank 経路を厳密に
exercise する形に修正し、将来 fixture に rank 19 行を追加した際の test 経路 silent
shift (missing-rank → resolved-marker) を防止。

## prefix-match 防御 (pre-push takt-fix で auto 補正)

`has_resolved_marker_after` に `c.is_ascii_digit()` ガードを追加し、`"順位 19"` が
`"順位 195"` 等の prefix match で誤検出するバグを防止。専用 unit test 追加。

## テスト & dogfood

- 38 unit tests pass (priority_inversion 18 + preamble 9 + cross_ref 11)
- 現 docs/todo-summary.md に対し 0 violations (false positive なし)
- pnpm lint:docs OK (preamble + cross-ref + priority-inversion)

## 統合

- `pnpm lint:docs` 経由で `--check all` 実行時に自動実行
- `.claude/cli-docs-lint.exe` rebuild + deploy 済 (release profile)
- ADR-039 試験運用パターン継承 (kill-switch `CLI_DOCS_LINT_DISABLE=1`、3-5 PR dogfood
  後に default-ON 昇格 / 却下を判定)

* docs(todo): 順位 19 (REJECT-ESCALATE) retire — ADR-037/043 + PR #194 land で代替経路実現済、PR #194-後 8 runs で escalation 候補 0 件

PR #199 (Bundle W) session 内合意 (ユーザー指示 2026-06-07): 「A の PR マージまで進ん
だら、B. 順位 19 自体の Tier 再評価 のタスク化を進める」を実行し、実測値に基づき
(C) retire を決定。

## 決定根拠 (empirical data)

PR #194 (mechanical gate 強化) land 後の takt run logs を集計:

| Run | iteration 上限到達 | supervise step 到達 | escalation 候補 |
|---|---|---|---|
| 8 runs (pre-push-review 7 + post-pr-review 1) | 0/8 | 0/8 | 0/8 |

REJECT-ESCALATE が解決するはずの「iteration 上限到達 + supervise/fix_supervisor の
無限ループ」が **PR #194 land 以降 1 件も観測されていない**。代替経路で十分実用に
耐えていることが定量的に確認できた:

- ADR-037 (fix-trust shortcut、PR #106 land): `convergence_verdict: fully_resolved` で
  COMPLETE 直行 → iteration が verdict ベースで有限化
- ADR-043 (fail-closed 原則、PR #194 land): security/quality gate semantics 構造化
- PR #194 (mechanical gate 強化): merge 前 clippy + 空 commit sweep の決定論層追加

## 変更内容

- docs/todo3.md: 順位 19 entry block を削除 (-46 行)
- docs/todo-summary.md: table から 順位 19 row を削除 (-1 行)

順位 19 を gate にしていた Tier 1 (旧 順位 34/35) は既に PR #199 で priority
inversion 解除済のため、本 retire で残るブロッキングはない。

## 関連 PR / ADR

- PR #199 (Bundle W、priority inversion 解除)
- ADR-037 fix-trust shortcut
- ADR-043 Security/Quality Gate Fail-Closed 原則
- PR #194 mechanical gate 強化 (clippy + 空 commit sweep)

* docs(todo): post-merge-feedback T2-1/T3-2 採用 — 順位 197/198 追加

PR #199 (Bundle W) の post-merge-feedback report (.claude/feedback-reports/199.md)
で analyzer が ✅ 採用候補とした 2 件を、ユーザー承認 (2026-06-08) を経て
docs/todo*.md 系列に登録する。

## 順位 197: hooks-session-start orphan age 計算に proptest 追加 (T2-1)

Bundle W (PR #199) で `cli-pr-monitor::lock` に PastTime newtype + proptest properties を
導入し、`saturating_sub` silent semantic mismatch (Finding D) を構造的に排除した。
同じ bug class が `src/hooks-session-start/src/main.rs:236` の orphan age 計算
(`now_unix.saturating_sub(start_unix)`) にも存在し、clock rewind / future timestamp で
age=0 → orphan reaper が「young」判定でスキップ → `.failed` marker 未生成 → ADR-030
L2 recovery 停止という具体的 failure chain が確認済。Bundle W の pattern を
hooks-session-start に展開する。

- Tier 2 / Effort M / Severity High / Frequency Medium / Adoption Risk None
- proptest 1.x は既存 dev-dependencies、新規依存追加は hooks-session-start のみ

## 順位 198: ADR-NNN Timestamp invariant safety (T3-2)

PR #96 Finding D + PR #199 Bundle W で同型 bug class が 2 件観測 (Frequency Medium)。
「時刻計算における silent failure class と型レベル防御」を永続化し、派生プロジェクト
(techbook-ledger / auto-review-fix-vc) への transferability を確保する ADR を新設。

- Tier 3 / Effort M / Severity Medium / Frequency Medium / Adoption Risk None
- 順位 135 codified placeholder policy 適用 (ADR 番号は land 時 PR で確定)
- CLAUDE.md ADR list 追記

## 変更ファイル

- docs/todo10.md: 順位 197/198 詳細 entry を 2 セクション追加
- docs/todo-summary.md: table に順位 197/198 行を追加

* fix(cli-docs-lint): priority_inversion の resolved-marker window を char-based 化 (CR Major #1)

PR #200 CodeRabbit review で指摘された Major finding を修正。

## Bug

`has_resolved_marker_after` の window 計算が以下の 2 点で spec とズレていた:

1. **byte vs char**: `(abs_pos + RESOLUTION_WINDOW_CHARS)` は **byte** 演算。日本語 1 文字 = 3 bytes
   なので「80 文字 window」のつもりが実質 ~27 文字 window に縮退
2. **window 開始位置**: `&haystack[abs_pos..]` で needle (順位 NN) 自体を window に含めていた。
   spec は「順位参照の **直後** から N 文字以内」

具体 failure 例 (CR review 提示):
- `"順位 19" + "あ"*40 + " land 済"` で、marker "land 済" は char-distance 41 (window 80 内)
- 旧 byte-based: 80 bytes window は ~26 chars のみカバー → marker 圏外 → resolved 扱いされず
  → **false negative (= inversion 誤検出 → reviewer に noise)**

## Fix

```rust
let window_end = haystack[after..]
    .char_indices()
    .nth(RESOLUTION_WINDOW_CHARS)
    .map(|(i, _)| after + i)
    .unwrap_or(haystack.len());
let window = &haystack[after..window_end];
```

- 起点を `after` (needle 直後) に変更
- `char_indices().nth(N)` で N 文字目の byte offset を取得 → char-based window

不要になった `next_char_boundary` ヘルパー関数を削除。

## Test

regression test 追加 (`is_resolved_detects_marker_across_multibyte_gap`):
- `"順位 19" + "あ".repeat(40) + " land 済"` で resolved 判定が true になることを assert
- 旧実装ではこの test は fail する (byte window で marker 圏外)

## Doc update

`RESOLUTION_WINDOW_CHARS` の doc に「**文字数** (バイト数ではない)」を明記。
multi-byte でも spec 通り動作することを comment で保証。

## 検証

- cargo test -p cli-docs-lint: 19 priority_inversion tests pass (新規 regression 1 含む)
- cargo clippy -p cli-docs-lint --all-targets -- -D warnings: clean
- pnpm lint:docs: OK (preamble + cross-ref + priority-inversion)
- .claude/cli-docs-lint.exe rebuild + deploy 済

Refs: CR comment on PR #200 (src/cli-docs-lint/src/priority_inversion.rs:182)
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