Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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) との整合性が崩れる可能性

---

175 changes: 175 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*?[/\\])?(?:cli-[\w-]+|hooks-[\w-]+|check-ci-[\w-]+)\.exe\s+(?:--help|-h|/\?)(\s|$)"#,
)
.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,140 @@ 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_allows_unrelated_exe() {
assert!(!is_blocked_with("foo.exe --help", &["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