Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .claude/hooks-config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@
# "gh-pr-merge-guard" — gh pr merge ブロック (pnpm merge-pr に誘導)
# "polling-anti-pattern" — until/while ! ... sleep の polling ループをブロック
# (rate-limit 浪費防止、PR #86 T1-1)
# "exe-help-block" — 本リポジトリの Rust 製 exe (.claude/*.exe) + 単独 --help/-h/?
# をブロックして src/<exe-name>/src/main.rs Read に誘導
# (PR #109 SIGPIPE 事故の直接 trigger を構造的に防止、順位 65 / Bundle c)
# "electron" — Electron GUI 実行ブロック
# プリセット名以外の文字列は正規表現としてカスタムパターン扱い
blocked_patterns = [
Expand All @@ -38,6 +41,7 @@ blocked_patterns = [
"gh-pr-create-guard",
"gh-pr-merge-guard",
"polling-anti-pattern",
"exe-help-block",
"electron",
]

Expand Down
2 changes: 0 additions & 2 deletions docs/todo-summary.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,6 @@
| 57 | 🔧 Tier 2 | **Aggregation cap integration test (PR #105 T2-1 採用)** | todo7.md | S | なし (`collect_all_violations` の MAX_VIOLATIONS contract を test 化、将来の lint 追加時に `truncate(MAX)` 削除 regression を防止する explicit 安全網) |
| 60 | 💎 Tier 3 | **analyze-session の transcript filter 絞り込み (旧 #A-3)** | todo7.md | M | なし (旧 docs/pipeline-token-efficiency.md #A-3、ADR-036/037 化に伴い計画書削除、本 task のみ todo に移管。analyze-session の input range を PR 作成 commit〜merge に限定して input token 30-50% 削減見込み、dogfood で実測必要) |
| 61 | 🔧 Tier 2 | **post-PR 検証フローに CR review.body 手動スキャン step 追加 (PR #108 T2-1 採用)** | todo7.md | XS | なし (PR #108 で analyze-coderabbit が review body の outside diff range comment を検出漏れし line 371/378 の修正が後追い、blind spot の暫定緩和策として手動 checklist を整備) |
| 65 | 🚀 Tier 1 | **exe + `--help` を PreToolUse でブロックして src/ Read に誘導 (PR #109 T1-3 採用) ★ Bundle c** | todo7.md | S | なし (PR #109 SIGPIPE の直接トリガ = AI が `cli-merge-pipeline.exe --help` 実行 → exe は --help 未対応で merge 本体実行を構造的に防止、今後追加 exe にも自動適用) |
| 66 | 💎 Tier 3 | **長時間 subprocess の pipe truncate 禁止ルールをグローバル明文化 (PR #109 T3-1 採用) ★ Bundle c** | todo7.md | XS | なし (順位 65 = 決定論層、本ルール = 判断ガイド層、`~/.claude/rules/common/development-workflow.md` 等に追加) |
| 78 | 💎 Tier 3 | **ADR-041 (Rust timestamp arithmetic safety) + CLAUDE.md security 拡充 (PR #115 T3-1 採用) ★ Bb-3 follow-up** | todo5.md | S | なし (config が user-editable system boundary のとき `sanitize()` 値域検証を必須化し dependent arithmetic に `// SAFETY: <sanitize-fn> により上限保証` コメントを要求するパターンを ADR + CLAUDE.md に codify、Rust 固有の checked_add + MAX_SAFE capping + time-dependent test の 3 層を明文化。2026-05-16 に entry 登録時の旧予約番号 ADR-038 が Local LLM finding classification で占有済と判明し ADR-041 に振り直し) |
| 79 | 💎 Tier 3 | **`docs-governance.md` § Retirement Workflow に「残タスクの lifecycle 整合」要件明記 (PR #117 T3-1 採用)** | todo5.md | XS | なし (PR #117 で順位 15 を Bb-3 で吸収済として削除した際、現 Step 2「残タスクを priority table に登録」が priority table から除外するケース = 完了/deprioritize/defer を未定義だった実証。除外時の commit/PR で 3 値のいずれかを明示する要件を追加して将来の同型 ambiguity を構造的に防ぐ) |
| 81 | 🚀 Tier 1 | **cli-pr-monitor: CR 投稿エラー (`Failed to post review comments`) auto-retry 拡張 (PR #120 T1-2 採用) ★ Bundle f (defer)** | todo5.md | M | 1 観測のみで systemic 性未確認 (§A-2 P-5 PR で defer 判断、ADR-018 §追記 2026-05-08 で re-trigger 条件 = 2 件以上の同型観測を規定) |
Expand Down
96 changes: 0 additions & 96 deletions docs/todo7.md
Original file line number Diff line number Diff line change
Expand Up @@ -263,99 +263,3 @@

---

### exe + `--help` を PreToolUse でブロックして `src/<exe-name>/` Read に誘導する hook (PR #109 T1-3 採用) ★ Bundle c

> **動機**: PR #109 SIGPIPE 事故の **直接トリガ** が「AI が `cli-merge-pipeline.exe --help` を実行 → 当該 exe は `--help` 未対応のため merge 本体を実行 → 出力 truncate で SIGPIPE」だった。ユーザー提案: exe ごとに `--help` を実装する案は exe 数増加で漏れが出るが、`exe + --help` をセットで PreToolUse block すればソース閲覧フローに自動誘導でき、想定外実行を構造的に排除。今後追加される exe にも自動適用される一般解。
>
> **本タスクの位置づけ**: Bundle c (PR #109 post-merge-feedback 堅牢化) の trigger pattern 防止層。順位 63 / 64 が「中断されても recovery する」事後対策、本 task は「中断パターンを発生させない」事前対策。
>
> **参照**: `.claude/feedback-reports/109.md` Tier 1 #3、`.claude/hooks-config.toml` (PreToolUse block_pattern)、`src/hooks-pre-tool-validate*/`
>
> **実行優先度**: 🚀 **Tier 1 High** — Effort S。ユーザー提案の事前防衛策。

#### 設計決定 (案)

- **検出パターン** (regex):
- `(?:\.\\.claude\\|\\./|^|\s)(?:[\w\-]+\.exe|cli-[\w\-]+\.exe)\s+(?:--help|-h|/\?)\b`
- exe 名は `cli-*.exe` / `hooks-*.exe` / `check-*.exe` 等を含む全 Rust exe 想定
- 引数が `--help` / `-h` / `/?` の **単独実行** に限定 (`exe foo --help` のような subcommand help は対象外)
- **ブロック時の代替誘導メッセージ**:
```text
exe の --help は本リポジトリで未実装の可能性があります。
詳細を見るには次を試してください:
- ソースを Read: src/<exe-name>/src/main.rs (引数定義は clap struct を確認)
- 既存 docs を検索: grep -r "<exe-name>" docs/
```
- **配置先**: `src/hooks-pre-tool-validate*/` の Bash command validation ロジック (既存 git block と同居)
- **適用範囲**: Bash tool での実行のみ。Read tool 等での参照は対象外

#### 作業計画

- [ ] 既存 `hooks-pre-tool-validate*` の構造を確認 (Rust exe か Python か)
- [ ] regex パターン設計 + テストケース作成 (TP / FP の境界明確化)
- TP: `cli-merge-pipeline.exe --help`、`./.claude/foo.exe -h`
- FP: `cargo run --help`、`gh pr view --help`、`exe foo --help` (subcommand)
- [ ] hook に block ロジック追加 + 代替誘導メッセージ実装
- [ ] integration test: 上記 TP / FP ケースで block / pass を assert
- [ ] dogfood: 本 hook 有効状態で `cli-merge-pipeline.exe --help` を実行し block されることを確認
- [ ] 派生プロジェクトに deploy
- [ ] 本 todo7.md エントリを削除

#### 完了基準

- `exe + --help` 系コマンドが Bash tool 経由で block される
- block メッセージで「ソースを Read」フローに誘導される
- 既存の正規 `--help` (cargo / gh / pnpm 等) は誤検出しない
- 今後追加される exe にも自動適用される (regex で exe 名を限定列挙しないため)

#### 詰まっている箇所

- regex の精度: `cli-merge-pipeline.exe` は対象だが `cargo --help` は対象外、という線引きを `.exe` suffix の有無で判定するか exe 名 prefix で判定するかで挙動が変わる。本リポジトリの全 exe を grep して命名規則を確認してから決定
- AI 側の挙動学習: block されたとき AI が代替フロー (ソース Read) に正しく遷移するかの dogfood 観察。失敗するなら block message を強化

---

### 長時間 subprocess の pipe truncate 禁止ルールをグローバル明文化 (PR #109 T3-1 採用) ★ Bundle c

> **動機**: PR #109 SIGPIPE 事故は「AI が長時間 subprocess (cli-merge-pipeline) の出力を `\| head -40` で truncate」したのが直接トリガ。順位 65 (PreToolUse block) が決定論層、本ルールは AI/人間の判断ガイド層。二層防御で hole を減らす。
>
> **本タスクの位置づけ**: Bundle c (PR #109 post-merge-feedback 堅牢化) の知識層。決定論的 block では捕捉しきれないパターン (例: `pnpm push \| tail`、`gh pr view --json reviews \| jq`) も含めて AI に教育的に指示。
>
> **参照**: `.claude/feedback-reports/109.md` Tier 3 #5、`~/.claude/rules/common/development-workflow.md`、`~/.claude/rules/common/git-workflow.md`
>
> **実行優先度**: 💎 **Tier 3** — Effort XS。グローバルルール 1 セクション追加。

#### 設計決定 (案)

- **配置先候補** (着手時に決定):
- **案 A**: `~/.claude/rules/common/development-workflow.md` の "Bash 実行ガイド" として新セクション追加
- **案 B**: `~/.claude/rules/common/git-workflow.md` の "gh CLI 使用規則" の隣に "長時間 subprocess の出力扱い" 節を追加
- 推奨: **案 A** (development-workflow が development pipeline 全般を扱うため整合性高い)
- **記述内容** (案):
- 長時間 subprocess (`pnpm push` / `pnpm merge-pr` / `cli-*.exe` / takt workflow) を **`\| head` / `\| tail` / `\| tee` で truncate しない**
- 理由: parent process の SIGPIPE で workflow が abrupt 中断され、`.failed` marker や成果物が silent loss する (PR #109 で実証)
- 代替策: 出力をファイルに redirect (`> out.log 2>&1`) または `run_in_background` で実行 (Bash tool のオプション) し、後から `tail out.log` 等で確認
- 例外: 短命な subprocess (`ls`, `cat` 等) や exit code のみが必要な場合は OK
- **既存ルールとの関係**: gh CLI 使用規則 (token 効率) と相補。token 効率は --jq / -q による絞り込み、本ルールは長時間 process の中断回避

#### 作業計画

- [ ] 案 A / B のどちらを採用するか決定 (着手時に grep で類似 rule の配置を確認)
- [ ] 配置先に「長時間 subprocess の出力扱い」セクションを追加 (規則 + 理由 + 代替策 + 例外を 1 ページに集約)
- [ ] PR #109 SIGPIPE 事故を実例として inline 引用 (`docs/adr/adr-030-...md` 参照)
- [ ] 派生プロジェクトで global rule 反映を確認 (rule は global、自動適用)
- [ ] 本 todo7.md エントリを削除

#### 完了基準

- グローバルルールに「長時間 subprocess の pipe truncate 禁止」が codify される
- 次回 AI が `pnpm push \| head` 系を打とうとした時、ルール参照で自己修正できる
- 順位 65 (block_pattern) と整合 (二層防御の上層 = ガイド、下層 = block)

#### 詰まっている箇所

- 「長時間」の定義 ambiguity: `gh pr view` は通常短命だが rate-limit 中は長時間化する。閾値を秒数で明文化するか、特定 exe を列挙するかの判断
- 例外列挙の網羅性: AI が「これは例外だろう」と自己判断する余地を残すと block_pattern (順位 65) との整合性が崩れる可能性

---

170 changes: 170 additions & 0 deletions src/hooks-pre-tool-validate/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,46 @@ fn preset_polling_anti_pattern() -> Vec<BlockedPattern> {
]
}

/// プリセット: exe-help-block (本リポジトリの Rust 製 exe + 単独 --help/-h/? をブロック)
///
/// 動機: PR #109 SIGPIPE 事故の直接トリガは `cli-merge-pipeline.exe --help` を AI が
/// 打ったこと。本リポジトリの Rust 製 exe (`.claude/*.exe`) は `--help` を未実装のため、
/// 実行すると help を表示せず実体 (例: cli-merge-pipeline は merge 本体) が即座に起動する。
/// `| head -40` 等の出力 truncate と相互作用して SIGPIPE で abrupt 終了 → Drop guard 不発 →
/// `.failed` marker 未生成 → ADR-030 仕様違反、という連鎖の起点。
///
/// 設計:
/// - `<path-prefix>?<name>.exe` + 単独 `--help|-h|/?` (subcommand 形式 `exe foo --help` は対象外)
/// - 引数 `--version` 等は block 対象外 (本 preset の責務は --help 系の trigger のみ)
/// - 順位 65 (PR #109 post-merge-feedback 採用、Bundle c)
fn preset_exe_help_block() -> Vec<BlockedPattern> {
let msg = r#"**exe + --help がブロックされました**

本リポジトリの Rust 製 exe (`.claude/*.exe`) は `--help` を未実装のため、
実行すると help を表示せず実体が起動します (PR #109 SIGPIPE 事故の直接トリガ)。

**代替経路 — exe の使い方を確認するには:**
- 引数定義の Read: `src/<exe-name>/src/main.rs` (clap struct または手動パースを確認)
- 既存 docs を検索: `grep -r "<exe-name>" docs/`

**例:**
```
# NG: cli-merge-pipeline.exe --help
# NG: .claude/cli-merge-pipeline.exe -h
# OK: Read src/cli-merge-pipeline/src/main.rs
# OK: grep -r cli-merge-pipeline docs/
```

詳細: ADR-030 (SIGPIPE 事故の根因と Drop guard / reaper による recovery 機構)。"#;
vec![BlockedPattern {
pattern: Regex::new(
r#"(?im)(^|&&|;|\|\||\||&|\n)\s*(?:[A-Za-z_][A-Za-z0-9_]*=\S+\s+|command\s+|env\s+)*(?:\S*?[/\\])?[\w-]+\.exe\s+(?:--help|-h|/\?)(\s|$)"#,
)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
.unwrap(),
message: msg,
}]
}

/// プリセット: gh-pr-merge-guard (gh pr merge を禁止し pnpm merge-pr に誘導)
fn preset_gh_pr_merge_guard() -> Vec<BlockedPattern> {
let msg = r#"**gh pr merge がブロックされました**
Expand Down Expand Up @@ -387,6 +427,7 @@ fn build_blocked_patterns(config: &Config) -> Vec<BlockedPattern> {
"gh-pr-create-guard" => patterns.extend(preset_gh_pr_create_guard()),
"gh-pr-merge-guard" => patterns.extend(preset_gh_pr_merge_guard()),
"polling-anti-pattern" => patterns.extend(preset_polling_anti_pattern()),
"exe-help-block" => patterns.extend(preset_exe_help_block()),
"electron" => patterns.extend(preset_electron()),
custom => {
// プリセット名以外はカスタム正規表現として扱う
Expand Down Expand Up @@ -1021,6 +1062,135 @@ mod tests {
assert!(validate_command("until grep -q done; do sleep 5; done", &patterns).is_none());
}

#[test]
fn exe_help_block_blocks_cli_merge_pipeline_help() {
assert!(is_blocked_with(
"cli-merge-pipeline.exe --help",
&["exe-help-block"]
));
}

#[test]
fn exe_help_block_blocks_cli_merge_pipeline_short_help() {
assert!(is_blocked_with(
"cli-merge-pipeline.exe -h",
&["exe-help-block"]
));
}

#[test]
fn exe_help_block_blocks_cli_merge_pipeline_windows_help() {
assert!(is_blocked_with(
"cli-merge-pipeline.exe /?",
&["exe-help-block"]
));
}

#[test]
fn exe_help_block_blocks_dot_slash_claude_prefix() {
assert!(is_blocked_with(
"./.claude/cli-merge-pipeline.exe --help",
&["exe-help-block"]
));
}

#[test]
fn exe_help_block_blocks_claude_prefix() {
assert!(is_blocked_with(
".claude/check-ci-coderabbit.exe --help",
&["exe-help-block"]
));
}

#[test]
fn exe_help_block_blocks_hooks_exe() {
assert!(is_blocked_with(
"hooks-pre-tool-validate.exe --help",
&["exe-help-block"]
));
}

#[test]
fn exe_help_block_blocks_after_chain() {
assert!(is_blocked_with(
"cd /tmp && cli-merge-pipeline.exe --help",
&["exe-help-block"]
));
}

#[test]
fn exe_help_block_blocks_with_env_prefix() {
assert!(is_blocked_with(
"RUST_LOG=debug cli-merge-pipeline.exe --help",
&["exe-help-block"]
));
}

#[test]
fn exe_help_block_blocks_windows_path() {
assert!(is_blocked_with(
r"e:\work\.claude\cli-merge-pipeline.exe --help",
&["exe-help-block"]
));
}

#[test]
fn exe_help_block_blocks_after_pipe() {
assert!(is_blocked_with(
"echo x | cli-merge-pipeline.exe --help",
&["exe-help-block"]
));
}

#[test]
fn exe_help_block_allows_subcommand_help() {
assert!(!is_blocked_with(
"cli-merge-pipeline.exe foo --help",
&["exe-help-block"]
));
}

#[test]
fn exe_help_block_allows_cargo_run_help() {
assert!(!is_blocked_with("cargo run --help", &["exe-help-block"]));
}

#[test]
fn exe_help_block_allows_gh_pr_view_help() {
assert!(!is_blocked_with("gh pr view --help", &["exe-help-block"]));
}

#[test]
fn exe_help_block_allows_pnpm_build_help() {
assert!(!is_blocked_with("pnpm build --help", &["exe-help-block"]));
}

#[test]
fn exe_help_block_allows_exe_without_help_arg() {
assert!(!is_blocked_with(
"cli-merge-pipeline.exe",
&["exe-help-block"]
));
}

#[test]
fn exe_help_block_allows_exe_with_version() {
assert!(!is_blocked_with(
"cli-merge-pipeline.exe --version",
&["exe-help-block"]
));
}

#[test]
fn exe_help_block_default_config_does_not_enable() {
let config = Config::default();
let patterns = build_blocked_patterns(&config);
assert!(
validate_command("cli-merge-pipeline.exe --help", &patterns).is_none(),
"exe-help-block should be opt-in via hooks-config.toml"
);
}

#[test]
fn allows_pnpm_lint() {
assert!(!is_blocked("pnpm lint"));
Expand Down