diff --git a/docs/pipeline-token-efficiency.md b/docs/pipeline-token-efficiency.md index 78cf0f95..cf9a2c0d 100644 --- a/docs/pipeline-token-efficiency.md +++ b/docs/pipeline-token-efficiency.md @@ -1,15 +1,26 @@ # takt パイプライン トークン効率調査・改善計画 -> **動機**: Claude Code Max 5x の 5 時間レートリミットの 90% を 3 時間時点で消費する事象が観測された (PR #97 セッション、2026-04-30 〜 2026-05-01 JST)。本ドキュメントは、その session log (`6cbc5021-...jsonl`、6.18MB / 1180 assistant turns) を分析した結果と、4 つの改善方向 (#A〜#D) の調査結果・改善方針を記録する。 +> **動機**: Claude Code Max 5x の 5 時間レートリミットの 90% を 3 時間時点で消費する事象が観測された (PR #97 セッション、2026-04-30 〜 2026-05-01 JST)。本ドキュメントは、その session log (`6cbc5021-...jsonl`、6.18MB / 1180 assistant turns) を分析した結果のうち、**未実施の改善方向のみ** を記録する。 > -> **方針**: 各改善案は「調査結果 → 改善案 → 採用判定」の 3 セクションで管理する。実装決定後は本ドキュメントから該当セクションを削除し、ADR 化または todo3.md/todo4.md にタスク登録する。 +> **方針**: 各改善案は「調査結果 → 改善案 → 採用判定」の 3 セクションで管理する。実装決定後は本ドキュメントから該当セクションを削除し、ADR 化または todoX.md にタスク登録する。 > -> **状態**: 試験運用 (本ドキュメントは "計画書" であり、実装が完了したら役割を終える) +> **状態**: 試験運用 (本ドキュメントは "計画書" であり、残作業を消化したら役割を終える) +> +> **完了済 Bundle (履歴・調査内容は本ドキュメントから削除済)**: +> +> | Bundle | 内容 | 完了 PR | +> |---|---|---| +> | Bundle Y2 | #A-1 + #C-1 (analyze facets を haiku 化) | #98 | +> | Bundle Z Phase 1 | #B-α (Rust comment lint hook、決定論レイヤー) | #99 | +> | Bundle a Sub-PR 0 | #D-1 (gh CLI 規則 → `~/.claude/rules/common/git-workflow.md`) + ADR-034 起案 | #100 | +> | Bundle a Sub-PR 1 | #D-3 (`check-ci-coderabbit --list-findings` モード) | #101 | --- ## 観測データ (PR #97 セッション、2026-04-30 〜 2026-05-01 JST) +> **用途**: 残作業の改善効果検証時の比較ベースライン。 + ### セッション全体 | 指標 | 値 | @@ -41,57 +52,28 @@ --- -## #A: post-merge-feedback パイプライン - -### 調査結果 - -`.takt/workflows/post-merge-feedback.yaml` 解析の結果、観測された **「常に 2 iterations」パターンは workflow 設計上の必然** と判明。 - -- **Step 1 `analyze`**: 3 facets を並列実行 (analyze-pr / analyze-session / analyze-prepush-reports) -- **Step 2 `aggregate-feedback`**: 3 レポートを Plankton 優先度で統合し最終 feedback-report.md を生成 +## #A: post-merge-feedback パイプライン (残作業) -2 step とも必須 (片方を削除すると output が成立しない)。「2nd iter 冗長」仮説は **誤り**。 - -ただし、別の改善余地が 3 つ見つかった (下記 #A-1, #A-2, #A-3)。 - -### 改善案 - -#### #A-1: analyze facets を haiku model 化 (現状 sonnet) - -3 facets はすべて `model: sonnet`。analyze 系は「情報源から finding を抽出する分類タスク」で deep reasoning は aggregate 側に任せる方が適切。 - -**変更**: -```yaml -# .takt/workflows/post-merge-feedback.yaml -# analyze-pr / analyze-session / analyze-prepush-reports の 3 facets -# 変更前: model: sonnet -# 変更後: model: haiku -# aggregate-feedback は sonnet 維持 (品質担保) -``` - -**期待効果**: 1 run あたり ~2-3 分削減 + token cost 大幅削減 (haiku は sonnet の約 1/3 cost)。4 runs/session で **約 10 分 + 大規模 token 削減**。 - -**リスク**: haiku が finding を見落とす可能性。dogfood 1-2 PR で品質変化を確認する gating が必要。 - -#### #A-2: trivial PR の post-merge-feedback skip 条件追加 +### #A-2: trivial PR の post-merge-feedback skip 条件追加 doc-only PR (`.md` のみ変更) や 1-commit fix PR では post-merge-feedback の ROI が低い。`cli-merge-pipeline` 側で PR diff size を判定して skip する。 **変更箇所**: `src/cli-merge-pipeline/` の merge 後処理ロジック **判定条件 (案)**: + - diff の changed files が `*.md` のみ - かつ commit 数 = 1 - かつ +/- 合計 < 50 行 -**期待効果**: doc PR (本セッション中の PR #94 等) で post-merge-feedback 9 分丸ごと削減。月数件の doc-only PR があれば効果大。 +**期待効果**: doc PR で post-merge-feedback 9 分丸ごと削減。月数件の doc-only PR があれば効果大。 -**リスク**: doc PR でも文書間 reference 整合性等の発見がありうる (Bundle U の起源)。skip 判定は緩めに設定し、誤 skip による学習機会損失を最小化する。 +**リスク**: doc PR でも文書間 reference 整合性等の発見がありうる。skip 判定は緩めに設定し、誤 skip による学習機会損失を最小化する。 -#### #A-3: transcript filter の絞り込み強化 +### #A-3: transcript filter の絞り込み強化 analyze-session が読む `.takt/post-merge-feedback-transcript.jsonl` には **session 全履歴** が入る。PR-related な部分のみ filter すれば input token 削減 (analyze-session の cache_read 削減)。 -**変更箇所**: `cli-merge-pipeline` の transcript 生成ロジック (どの範囲を filter するか) +**変更箇所**: `cli-merge-pipeline` の transcript 生成ロジック **現状**: 全 session **改善案**: 当該 PR の作成 commit から merge までの時刻 range で filter @@ -103,117 +85,40 @@ analyze-session が読む `.takt/post-merge-feedback-transcript.jsonl` には ** | 改善案 | ROI | 実装コスト | 推奨 | |---|---|---|---| -| #A-1 haiku 化 | ★★★★★ | XS (yaml 1 行 ×3) | **即実施推奨** | | #A-2 trivial PR skip | ★★★★ | S (Rust 判定ロジック) | 中期 | | #A-3 transcript filter | ★★★ | M (cli-merge-pipeline 改修) | dogfood 後 | --- -## #B: pre-push-review パイプライン +## #B: pre-push-review パイプライン (Bundle Z Phase 2 / 3) -> **背景**: 6 iter / 17-18 分の outlier が PR #97 で **2 回**発生 (各 round で個別)。総時間 36 分が突出 (47.9 分中 75% を消費)。 +> **背景**: 6 iter / 17-18 分の outlier が PR #97 で 2 回発生 (各 round で個別)。総時間 36 分が突出 (47.9 分中 75% を消費)。 +> +> **アーキテクチャ 3 層**: 「決定論レイヤー (#B-α、PR #99 で完了) → 制約付き修正 (#B-β、Phase 2) → 異常検知レビュアー (#B-γ、Phase 3)」を Phase 1〜3 で順次実装する設計。各 Phase 完了後の dogfood で次 Phase 着手判断。 -### 調査結果 +### Phase 2/3 着手前に把握すべき構造的問題 #### Workflow 構造 [`.takt/workflows/pre-push-review.yaml`](../.takt/workflows/pre-push-review.yaml): -``` +```text reviewers (parallel: simplicity + security) → fix → (loop, threshold 2) → supervise → fix_supervisor → COMPLETE ``` `loop_monitors.threshold: 2` で reviewers→fix サイクル 2 周まで許容。逸脱で supervise → fix_supervisor へエスカレート。**6 iter = 2 cycles + supervise + fix_supervisor の最大 path**。 -#### 6-iter run の中身 (詳細解析) - -**Run 1 (10:30 UTC, 18m 30s)**: PR #97 Phase 4 初回 push - -| Iter | 結果 | 内容 | -|---|---|---| -| 1 | REJECT | What コメント 3 件 (S01, S02, S03) を検出 | -| 2 | REJECT | S01-S03 修正済み、**新たに S04 What コメント発見** (parse_rate_limit) | -| 3 | APPROVE | S04 修正済み | - -**Run 2 (14:20 UTC, 17m 22s)**: PR #97 Round 3 push (Result 化対応) - -| Iter | 結果 | 内容 | -|---|---|---| -| 1 | REJECT | F-001 What コメント (`// 成功時のみ dedup key と state を更新`) | -| 2 | REJECT | F-001 修正済み、**fix が introduce した F-002 (Nesting depth 5+) 発見** | -| 3 | APPROVE | F-002 修正 (match → if let Err パターン) | - -#### 共通する waste 源泉 - -両 run とも **Iter 1 で全 finding が検出されない / fix が新たな violation を introduce** することで Iter 2 が必要になり、その結果 supervise + fix_supervisor のエスカレートで合計 6 iter に膨らんだ。 - -**根因 1: simplicity-review iter 1 の検出漏れ** (Run 1 の S04) -- 1000+ 行の diff 全体をスキャンする際、LLM の attention drift で一部の What コメントを見落とす -- `review-simplicity.md` instruction には「ALL comments を enumerate」の指示がない - -**根因 2: fix step が新 violation を introduce** (Run 2 の F-002) -- F-001 の修正 (What コメント削除) のため `match Ok(()) => / Err(e) =>` パターンを採用したが、これが nesting depth 7 を導入 -- `fix.md` instruction には「自分の変更が他の criteria を violate しないか self-check」の指示がない - -**根因 3 (より上流): AI が What/How コメントを **そもそも書く*** -- explanatory output style mode が "include in conversation, not in code" と指示しているにも関わらず、私 (Claude) はコメントを書く習性がある -- 簡潔に書かせる Stop hook / lint rule が **不在** - -### 再評価 (2026-05-01: PR #98 セッション後) - -ユーザーフィードバック (PR #98 セッション末) により、本セクションの当初提案 (旧 #B-1〜#B-4) は **構造的に不適切** と判定し全面再編した。論点は以下: - -- **6 iter = worst path は「たまたま」ではなく構造的必然**: `非決定 reviewer × 非制約 fix × 短い loop threshold = 高確率で最大 path 到達` -- **LLM 検証器の追加では収束しない**: LLM review → LLM fix → LLM review の連鎖は完全性保証も再現性もない (ask-based の本質的限界) -- **解くべき問題の捉え直し**: 「iter を減らす」ではなく **「iter を不要にする」** = LLM 通過前に決定論層で止める +#### 観測された 3 つの根因 (6-iter run の解析より) -旧 #B-1〜#B-4 はすべて「LLM 検証器を増設する案」で、批判は以下: +両 run とも Iter 1 で全 finding が検出されない / fix が新たな violation を introduce することで Iter 2 が必要になり、結果として supervise + fix_supervisor のエスカレートで 6 iter に膨らんだ。 -| 旧案 | 批判 | 移行先 | +| 根因 | 例 | 解消手段 | |---|---|---| -| #B-1 fix self-check | LLM self-check は信頼できない (検出漏れと同じ問題を内包) | 取り下げ → #B-β (制約付き fix、機械的指標 diff) | -| #B-2 reviewer exhaustive scan | recall は上がるが 100% 保証なし、deterministic lint の下位互換 | 取り下げ → #B-γ (reviewer 役割を異常検知に再定義) | -| #B-3 regex lint hook | regex で「意味」を取るのは危険 (Why/What 区別不能、多言語対応で破綻) | 取り下げ → #B-α (AST/トークンベース、コメント存在自体を禁止) | -| #B-4 coding-style.md 強化 | 「文化」であって「制御」ではない (ルール増 → 読まれない、モデル変わる → 崩れる) | **完全削除** (補助層としても採用しない) | - -新案 #B-α / #B-β / #B-γ で「決定論レイヤー → 制約付き修正 → 異常検知レビュアー」のアーキテクチャ 3 層を構築する。 - -### 改善案 - -#### #B-α: 決定論 comment lint hook (Rust 限定 PoC) - -**設計思想**: regex で "What っぽい文章を検出" するのではなく、**コメントの存在自体を制約する** (例外マーカーのみ許可)。意味解析を回避することで言語非依存な shell を確保する。 - -**検出ロジック**: - -- 原則: Rust ソース内のすべての comment (`//`, `/* */`, `///`) を count -- 例外マーカー (実装 `src/hooks-post-tool-comment-lint-rust/src/main.rs` の `ALLOWED_LINE_PREFIXES` / `ALLOWED_BLOCK_PREFIXES` 定数が **single source of truth**): - - **line comment**: `///` (rustdoc outer) / `//!` (rustdoc inner) / `// TODO:` / `// FIXME:` / `// SAFETY:` / `// NOTE:` / `// HACK:` / `// XXX:` - - **block comment**: `/**` (block rustdoc outer) / `/*!` (block rustdoc inner) -- 上記以外のコメントは **REJECT** (count > 0 で hook が block) -- マーカー追加・削除時は実装定数と本箇所を必ず同期させる (docs と実装の乖離は次フェーズの誤誘導要因) - -**配置 (ADR-002 / ADR-006 / ADR-007 整合)**: - -- 新 crate `src/hooks-post-tool-comment-lint-rust/` (PoC は Rust 限定、将来 ts/py を独立 crate で並列追加) -- 既存の **PreToolUse hooks** (`hooks-pre-tool-validate.exe` 等) とは **別エントリ** として配置 (言語別 plugin の独立性確保、ユーザー指示) -- PostToolUse タイミング (Edit/Write 後) で発火、書かれた直後に block して即時修正させる -- ADR-002 (PostToolUse の Biome + oxlint 二段構成) には統合せず、独立 hook entry として並列 -- ADR-007 の **AST 層** に位置づけ (正規表現層ではない)。`tree-sitter` / `tree-sitter-rust` で `(line_comment)` / `(block_comment)` ノードを query で抽出 - -**期待効果**: +| 1. simplicity-review iter 1 の検出漏れ | LLM の attention drift で What コメント S04 を見落とし | **#B-γ で解消対象** | +| 2. fix step が新 violation を introduce | F-001 修正で `match Ok / Err` 採用 → nesting depth 7 を導入 | **#B-β で解消対象** | +| 3. AI が What/How コメントをそもそも書く | explanatory output style の指示があっても Claude の習性として残る | **#B-α (PR #99) で解消済** | -- AI が What/How コメントを書いた瞬間に hook が block → 修正させる -- takt 起動時にはコメント問題が解決済みのため、reviewer は ALL APPROVE に近い動作 -- 長期的に **6-iter run を 1-iter に構造的に圧縮** - -**リスク**: - -- 例外マーカーリストの保守 (新例外 `// LICENSE:` 等を追加するたびに list 更新) -- false positive (合理的な Why コメントが誤 block されると開発体験悪化) → 例外マーカー充実で回避 -- 派生プロジェクト展開時の言語拡張作業が **言語別に Effort M** ずつ加算 (本 PoC は Rust のみ) - -#### #B-β: 制約付き fix instruction +### #B-β: 制約付き fix instruction (Phase 2) **設計思想**: fix step の self-check を LLM 判断ではなく **機械的指標の diff 比較** に置き換える。指標増加で fix 自身がやり直しを self-trigger。 @@ -240,9 +145,13 @@ reviewers (parallel: simplicity + security) → fix → (loop, threshold 2) → - helper script `scripts/fix-metrics-check.ps1`: `rust-code-analysis` (Rust) を呼び出し metrics を JSON 出力 → diff 計算 → 増加 detected なら exit 非ゼロ - `.takt/runs//fix-metrics.log` に記録 (audit 用) +**PR #99 (Phase 1) との同期要件**: + +コメント数の例外マーカーリストは PR #99 で実装した [`src/hooks-post-tool-comment-lint-rust/src/main.rs`](../src/hooks-post-tool-comment-lint-rust/src/main.rs) の `ALLOWED_LINE_PREFIXES` / `ALLOWED_BLOCK_PREFIXES` 定数を **single source of truth** として参照すること。fix.md と lint hook で例外マーカー定義が乖離すると、lint で許容されたコメントが fix の non-doc comment count を増やして誤 reject となる。 + **期待効果**: -- Run 2 タイプ (fix が新 violation を introduce) の **構造的排除** +- 根因 2 (fix が新 violation を introduce) の **構造的排除** - LLM 判断ではなく数値比較なので、attention drift や指示読み飛ばしの影響を受けない **リスク**: @@ -251,9 +160,11 @@ reviewers (parallel: simplicity + security) → fix → (loop, threshold 2) → → 対策: function 単位ではなく **diff 内の change site 周辺** に scope を絞る - metric tool の Rust 限定 (PoC は `rust-code-analysis`、将来言語拡張で別 tool 評価) -#### #B-γ: reviewer の役割を「検査」から「異常検知」へ +### #B-γ: reviewer の役割を「検査」から「異常検知」へ (Phase 3) -**設計思想**: 決定論層 (#B-α + #B-β) を通過した状態を前提に、reviewer の責務を **「lint で防げない高次違反のみ flag」** に再定義する。enumerate 義務を削除して attention drift 問題を解消。 +**設計思想**: 決定論層 (#B-α PR #99 + #B-β) を通過した状態を前提に、reviewer の責務を **「lint で防げない高次違反のみ flag」** に再定義する。enumerate 義務を削除して attention drift 問題を解消。 + +**前提条件**: Phase 2 (#B-β) 完了後に着手する。決定論層が未完成のまま reviewer 役割を絞ると、二重 miss で違反が pre-push を素通りする。 **変更内容**: @@ -280,129 +191,40 @@ reviewers (parallel: simplicity + security) → fix → (loop, threshold 2) → | 改善案 | ROI | 実装コスト | 推奨 | |---|---|---|---| -| **#B-α 決定論 comment lint hook (Rust)** | ★★★★★ | M (新 crate + AST + hook 登録) | **Phase 1 (PoC)** | -| **#B-β 制約付き fix instruction** | ★★★★ | M (helper script + facet update) | **Phase 2** | -| **#B-γ reviewer 役割変更** | ★★★ | S (facet instruction 書き換え) | **Phase 3** | - -**Bundle 案 (Bundle Z 再編)**: アーキテクチャ 3 層を **3 Phase 分割** で順次実装し、各 Phase の dogfood で次 Phase 着手判断。 - -- **Phase 1 — #B-α (Rust 限定 PoC)**: - - 新 crate `src/hooks-post-tool-comment-lint-rust/` を Cargo workspace (ADR-026) に追加 - - `tree-sitter` / `tree-sitter-rust` で `(line_comment)` / `(block_comment)` ノードを抽出 + 例外マーカー判定 - - PostToolUse hook として独立配置 (既存 PreToolUse hooks とは別エントリ、ADR-006 整合) - - dogfood 1〜2 PR: 例外マーカー漏れ / false positive を観測 → list 拡充 -- **Phase 2 — #B-β (制約付き fix)**: - - `scripts/fix-metrics-check.ps1` + `rust-code-analysis` 統合 - - `.takt/facets/instructions/fix.md` に deterministic check ブロック追加 - - dogfood 1〜2 PR: 「適切 refactor 誤 reject」の頻度を観測 → scope 調整 -- **Phase 3 — #B-γ (reviewer 異常検知化)**: - - `review-simplicity.md` / `review-security.md` 書き換え - - dogfood 1〜2 PR: 二重 miss 発生率と 1-iter APPROVE 率を観測 → 本採用判断 - -**期待累積効果**: pre-push iter 分布 `{1×3, 3×2, 6×1}` (PR #97 ベースライン) → `{1×N}` (1-iter ALL APPROVE 構造化)。outlier 率 1/6 (16.7%) → 0% 達成試算。 +| **#B-β 制約付き fix instruction** | ★★★★ | M (helper script + facet update) | **Phase 2** (PR #99 後の dogfood 観測完了後着手) | +| **#B-γ reviewer 役割変更** | ★★★ | S (facet instruction 書き換え) | **Phase 3** (Phase 2 dogfood 観測完了後着手) | + +**期待累積効果** (Phase 1 完了済 + Phase 2/3 完了後): pre-push iter 分布 `{1×3, 3×2, 6×1}` (PR #97 ベースライン) → `{1×N}` (1-iter ALL APPROVE 構造化)。outlier 率 1/6 (16.7%) → 0% 達成試算。 --- -## #C: post-pr-review パイプライン +## #C: post-pr-review パイプライン (残作業) > **背景**: pre-push-review と比較すると概ね健全だが、3-iter の outlier が 1 件あり (PR #96 の auto-fix run、10m 19s)。 -### 調査結果 - -#### Workflow 構造 - -[`.takt/workflows/post-pr-review.yaml`](../.takt/workflows/post-pr-review.yaml): - -``` -analyze → fix → analyze (loop, threshold 2) → supervise → fix_supervisor → COMPLETE -``` - -`analyze` step は明示的に `model:` 指定なし (default モデル使用)。fix / supervise / fix_supervisor は `model: sonnet`。 - -#### ディスク上のメタデータ実測 (8 runs) - -| 起動時刻 | iter | 所要時間 | 備考 | -|---|---|---|---| -| 04:06 | **3** | 10m 19s | PR #96 auto-fix (Critical/Major 2 件) | -| 04:23 | 1 | 2m 16s | approved | -| 05:21 | 1 | 1m 30s | approved | -| 11:17 | 1 | 2m 26s | PR #97 round 1 (Minor 1 件のみ → user_decision) | -| 11:33 | 1 | 1m 38s | approved | -| 13:59 | 1 | 1m 40s | approved | -| 14:38 | 1 | 1m 22s | approved | -| 15:30 | 1 | 2m 0s | approved | - -**8 runs 中 7 runs が 1-iter で完了**。outlier 1 件のみ (12.5%)。pre-push-review の outlier 率 (1/6 = 16.7%) と比べて同水準だが、3-iter 中央値は post-pr-review の方が低い。 - -#### 3-iter outlier の中身 (PR #96, 10m 19s) - -`coderabbit-analysis.md.20260430T041651Z` (Iter 1) → `coderabbit-analysis.md` (Iter 3) を比較: +### #C-2: fix step 報告に基づく Iter 3 短絡 -| Iter | Step | 結果 | -|---|---|---| -| 1 | analyze | CR Major 2 件検出 (lock.rs `LockResult::Acquired` の I/O 失敗時誤返却 / `parse_iso8601` panic) → needs_fix | -| 2 | fix | 両 finding を修正 (新 variant `Unavailable` 追加 + 範囲チェック追加 + 回帰テスト) | -| 3 | analyze | **同じ `.takt/review-comments.json` snapshot を再分析**、各 finding を current source 読んで verify → "Already fixed" 判定 → approved | - -#### 観測された waste 源泉 - -**根因 1: review-comments.json snapshot が refresh されない** +**根因**: post-pr-review の 3-iter 解析より、`review-comments.json` snapshot は fix 後も refresh されないため、Iter 3 の analyze は **同じ findings を再評価する**。Iter 3 output 例: -post-pr-review が起動する時点で `cli-pr-monitor` が CR comments を取得し snapshot 化。fix 後も snapshot は更新されないため、Iter 3 の analyze は **同じ findings を再評価する** 必要がある。 - -Iter 3 の output 例: > 「`.takt/review-comments.json` is a snapshot captured before fix iteration 1 ran; CodeRabbit has not re-reviewed yet, so the same 2 findings appear. The previous fix step report indicates both were addressed. **I verified each by reading the current source**...」 -つまり Iter 3 は「**fix step が言う通りに本当に修正されているか**」をソースを読んで再確認している。**fix step を信頼すれば不要な作業**。 - -**根因 2: snapshot refresh しても CR の resolution は遅延** - -仮に snapshot を refresh しても、CR が thread を resolved にマークするのは数分〜数時間後 (人間が resolve ボタンを押すか、CR が次の review で確認するまで)。即時 refresh の効果は薄い。 +つまり Iter 3 は「fix step が言う通りに本当に修正されているか」をソースを読んで再確認している。**fix step を信頼すれば不要な作業**。 -**根因 3: rate-limit 発生時に post-pr-review を skip しない** +**実装案**: -本セッションで観測されたとおり、CR rate-limit 中に post-pr-review を起動しても新しい findings は得られない。それでも analyze は実行される (~1-2 分の無駄)。 - -### 改善案 - -#### #C-1: analyze step に明示的に `model: haiku` を指定 - -現在 model 未指定 (default = sonnet)。analyze は CR 既存 findings の **分類タスク** で deep reasoning は不要。haiku で十分。 - -**変更**: -```yaml -# .takt/workflows/post-pr-review.yaml の analyze step -- name: analyze - edit: false - persona: code-reviewer - model: haiku # 追加 - ... -``` - -**期待効果**: -- 1-iter run (7/8 runs): 1.5-2.5 分 → 0.5-1 分 (analyze 自体が短縮) -- 3-iter run (1/8 runs): 10m 19s → 7-8 min (analyze × 2 が短縮) -- **session あたり累積 5-7 分削減 + token cost 大削減** - -**リスク**: haiku は finding の severity 判定や applicability filter で精度低下する可能性。dogfood 1-2 PR で精度比較必要。 - -#### #C-2: fix step 報告に基づく Iter 3 短絡 - -fix step が "All applicable findings fixed" を report に明記した場合、Iter 3 の analyze 自体を skip して COMPLETE に直行する option を workflow に追加。 - -**実装案 (案)**: - `fix.md` instruction の `## Convergence gate` table の `persists` が 0 で `misdirected` が 0 なら "fully resolved" マーカーを report に書く - workflow rule に `condition: All findings fixed (no persists)` を追加して `next: COMPLETE` **期待効果**: 3-iter run を 2-iter に圧縮 (~3 分削減)。年に数十回ある仮想シナリオで累積効果 **リスク**: + - fix step の自己評価信頼性 (現状でも `persists: 0` を report しているが、未修正のまま 0 を書く可能性ゼロではない) - 後続 supervise step でカバーされない場合は安全網が薄くなる -#### #C-3: rate-limit 発生時の post-pr-review skip +### #C-3: rate-limit 発生時の post-pr-review skip -`cli-pr-monitor` が rate-limit を検出した場合、post-pr-review takt 起動を skip。 +**前提となる完了 PR**: PR #97 (cli-pr-monitor の rate-limit 自動検出 + 再トリガー) で rate-limit 検出機構は実装済。本作業はその検出結果を post-pr-review takt invoke の skip 判定に流用する。 **実装案**: `cli-pr-monitor` 側で `rate_limit.is_some()` の時 takt invoke を skip し、log のみ出力。次のセッションで再起動された時に通常 flow が走る。 @@ -414,166 +236,26 @@ fix step が "All applicable findings fixed" を report に明記した場合、 | 改善案 | ROI | 実装コスト | 推奨 | |---|---|---|---| -| **#C-1** analyze haiku 化 | ★★★★★ | XS (yaml 1 行) | **即実施** (#A-1 と同 PR) | | #C-3 rate-limit skip | ★★★★ | S (cli-pr-monitor 1 分岐) | 即実施 | | #C-2 Iter 3 短絡 | ★★ | M (workflow + instruction 改修) | dogfood 後 | -**Bundle 案**: #A-1 (post-merge-feedback haiku) + #C-1 (post-pr-review haiku) を **同 PR で land** 推奨。共通テーマは "分類・抽出タスクは haiku で十分" で、yaml 4 行の変更で完結。**期待効果合計: session あたり 15-20 分 + 大規模 token 削減**。 - --- -## #D: CR review query / Claude 応答スタイル +## #D: Claude 応答スタイル (保留) -> **背景**: `gh pr` / `gh api` 関連 query 76-82 回 / 303KB chars / max 47KB が Bash tool_result の最大カテゴリ。Claude (私自身) の text-only 応答が 8.5M cache_creation tokens (全体の 62%) を占める。 -> -> **採用判定 (2026-05-02、PR #99 セッション末)**: Bundle Z2 を再構成し、**#D-1 + #D-3 を Bundle a に統合**、**#D-2 は取り下げ** (#D-3 で代替)、**#D-4 は保留** (思考連続性低下リスク、Bundle Z Phase 2/3 完了後の副作用観測手段確立後に再評価)。設計根拠は ADR-034 参照。 +> **完了済の関連項目**: gh CLI 使用パターン最適化は Bundle a (PR #100 + #101) で対応済。`check-ci-coderabbit --list-findings` で構造化取得が可能になっている (#C-3 でも活用余地あり)。 -### 調査結果 +### #D-4: Claude 応答スタイルの簡素化 ⏸️ 保留 (2026-05-02) -#### gh CLI 使用パターン (82 calls 解析) +**背景**: Claude (私自身) の text-only 応答が 8.5M cache_creation tokens (全体の 62%) を占める。これらは **後続全 turn の cache に乗り続ける** ため、初回の出力サイズが最終的に 9x で billable input token に膨らむ (1KB の応答 → 後続 9KB)。 -| パターン | 件数 | 特徴 | -|---|---|---| -| `--jq` filter なし | **44 (54%)** | 生 JSON を全取得後に python pipe で filter | -| `--jq` filter あり | 38 (46%) | 効率的 | - -**最大の waste 箇所**: - -1. **POST `/replies` の応答破棄漏れ** (9 calls, 53KB stdout) - - CR thread に `resolved: ...` で reply する POST。応答に full reply object が返る (diff_hunk + URL + node_id + body) が、私は **success/fail だけ知れば十分** - - 最大単発 24KB - - 改善: `> /dev/null 2>&1` で出力抑制 - -2. **List endpoint での `--jq` 未使用** (16 calls, 18KB) - - `gh api .../comments` で全 metadata を取得 → 後段で python フィルタ - - 改善: `--jq '.[] | {created_at, body_first: .body[:200]}'` で最初から filter - -3. **`gh pr view` で過剰 field 取得** (12 calls) - - `--json reviews,comments,reviewDecision,statusCheckRollup` の `comments` field に CR walkthrough の **embedded base64 internal state** が混入 - - 1 call で 44KB (うち 80% 以上が base64 noise) - -4. **特殊大型 outlier** (1 call, **47.98KB**) - - `gh api .../pulls/N/comments/N/replies -f body=...` の応答 (CR thread への reply で、応答本体に元 thread の diff_hunk 等を含めて返す) - -#### Read tool 使用パターン (94 calls, 266KB) - -| 指標 | 値 | -|---|---| -| 全文 read | 26 (28%) | -| offset/limit 付き read | **74 (74%)** | -| 同一ファイル複数回 read 上位 | `main.rs ×24`, `todo3.md ×11`, `poll.rs ×9` | - -74% が partial read で、これは健全。`main.rs` の 24 回は調査・修正・確認サイクルで再 read する性質上避けにくい。 - -#### Text-only assistant turn (cache_creation 占有率 62.3%, 8.5M tokens) - -主要発生源: -- CR review listing (round 1-4 で計 4 回、各 2-5KB) -- 完了報告サマリ (push 完了 / merge 完了 / fix 完了 で各 1-3KB) -- 分析テーブル (本セッションの token analysis 等で 3-5KB) -- Insight ブロック (各応答に 1-3 個、計約 1KB ずつ) - -これらは **後続全 turn の cache に乗り続ける** ため、初回の出力サイズが最終的に 9x で billable input token に膨らむ (1KB の応答 → 後続 9KB)。 - -### 改善案 - -#### #D-1: gh CLI 使用ルールの定型化 — `~/.claude/rules/common/git-workflow.md` ✅ **採用 (Bundle a 統合、2026-05-02)** - -私 (Claude) が gh CLI を使うときの定型パターンをルール化: - -```markdown -## gh CLI 使用規則 - -### POST 操作 (作成・更新) - -応答 body は破棄する (success/fail は exit code で判別): - -```bash -# BAD: 24KB の reply object が返って context に乗る -gh api repos/.../comments/N/replies -f body='resolved: ...' - -# GOOD: 出力を捨てる -gh api repos/.../comments/N/replies -f body='resolved: ...' > /dev/null 2>&1 -``` - -### GET 操作 (取得) - -`--jq` で必要 field のみ抽出する: - -```bash -# BAD: 44KB JSON 全部取得 -gh pr view 97 --json reviews,comments - -# GOOD: --jq で構造化抽出 -gh pr view 97 --json reviews --jq '.reviews | map({commit: .commit.oid[:8], state})' -``` - -### CR walkthrough 除外 - -`gh pr view` の `comments` field には CR walkthrough の base64 internal state が含まれる (1 PR で 30KB+)。確認時は `--jq 'del(.comments[].body)'` 等で除外。 -``` - -**期待効果**: -- gh tool_result 削減: ~70KB (POST replies 53KB + jq 化 18KB) -- 9x 再キャッシュ効果で **~150K cache_creation tokens 削減** -- effort: rule 追記のみ (XS) -- 持続性: ルール化で次セッション以降も継続効果 - -**リスク**: ルール量が増えると AI が読み込まないリスク。`git-workflow.md` の既存セクションに追記する形で目立たせる工夫必要。 - -#### #D-2: `pnpm cr:findings ` wrapper script 追加 ❌ **取り下げ (2026-05-02、#D-3 で代替可能のため機能重複)** - -CR findings を私が読みやすい形で取得する shell/Node script を追加: - -```bash -$ pnpm cr:findings 97 -PR #97 (state: OPEN, head: badaaf57) -Latest CR review: 2026-04-30T14:06:10Z (commit 79b7c3dd) - -Unresolved findings (4): - Major src/check-ci-coderabbit/src/main.rs:415 updated_at 基準で計算すべき - Major src/cli-pr-monitor/src/stages/poll.rs:183 max_duration を素通り - Major src/cli-pr-monitor/src/stages/poll.rs:203 失敗時 perma-skip - Minor docs/todo.md:69 順位 絶対参照 -``` - -**期待効果**: -- gh pr view + jq pipeline を script に隠蔽 -- 私の応答で「未対応レビューリスト」を作るときの効率化 -- effort: S (Node/Bash script + jq クエリ) -- ROI: 高 (CR review listing の繰り返し作業を 1 コマンド化) - -#### #D-3: `check-ci-coderabbit --list-findings` モード追加 ✅ **採用 (Bundle a 統合、2026-05-02)** - -Rust 側で構造化 findings JSON を生成 (元案 #7 の再掲): - -```bash -$ check-ci-coderabbit.exe --list-findings --pr 97 -{ - "findings": [ - {"severity": "major", "file": "src/.../main.rs", "line": 415, "summary": "...", "url": "..."}, - ... - ] -} -``` - -**期待効果**: -- `gh api` の生 JSON 取得 → Rust 側で構造化済み JSON を一度で取得 -- cli-pr-monitor からも消費可能になり、retrigger 自動化と連携 -- effort: M (Rust 実装 + テスト) -- ROI: 大 (#D-1 + #D-2 を deterministic に置き換える) - -**リスク**: 既存の cli-pr-monitor / check-ci-coderabbit の責務分離 (ADR-022) に抵触しないか要確認。 - -#### #D-4: Claude 応答スタイルの簡素化 — `~/.claude/rules/common/coding-style.md` または専用 rule ⏸️ **保留 (2026-05-02)** - -**保留理由** (PR #99 セッション末のユーザー判断): +**保留理由** (PR #99 セッション末のユーザー判断、ADR-034 参照): - **思考連続性低下リスク**: 中間出力 (Insight ブロック / 完了報告 / 分析テーブル) は後続 turn の cache に乗り、Claude が「これまでの判断」を参照するソース。削減すると後段で context 再構築 (再 grep / 再 read) を招き、token カテゴリが入れ替わるだけで正味削減が縮む可能性 - **副作用観測手段が未確立**: ルール導入で実際にどれだけ削減 / どれだけ思考品質低下するかの定量比較が困難 - **再評価条件**: Bundle Z Phase 2/3 (#B-β / #B-γ) 完了後、副作用観測手段 (例: session 比較メトリクス、思考品質 proxy 指標) が確立してから慎重 pilot -私自身の text-only 応答パターンを抑制するガイドライン: +**ガイドライン案** (採用時に `~/.claude/rules/common/coding-style.md` または専用 rule に追加): ```markdown ## トークン効率優先の応答スタイル (rate-limit 不安定期は特に) @@ -593,80 +275,98 @@ $ check-ci-coderabbit.exe --list-findings --pr 97 - 真に非自明 (調査結果・予想外の挙動) のみ。一般的な感想は省略 ``` -**期待効果**: -- text-only response 約 30-50% 削減 (推定 **2.5-4M cache_creation tokens 削減**) -- 全 #D 案中 **最大 ROI** - -**リスク**: -- ルールベースの行動変容は不安定 (持続性が低い) -- ユーザーへの説明不足で意図が伝わらない可能性 -- explanatory mode との緊張 (Insight 削減 vs 教育的応答) +**期待効果**: text-only response 約 30-50% 削減 (推定 **2.5-4M cache_creation tokens 削減**) -### 採用判定 (2026-05-02 改訂、PR #99 セッション末のユーザー判断) +--- -| 改善案 | ROI | 実装コスト | 採用判定 | 移行先 | -|---|---|---|---|---| -| **#D-1** gh CLI 規則 | ★★★★ | XS (rule 追記) | ✅ **採用** | Bundle a Sub-PR 1 | -| **#D-3** Rust findings mode | ★★★ | M (Rust) | ✅ **採用** (cli-pr-monitor 連携で価値増) | Bundle a Sub-PR 1 | -| ~~#D-2~~ pnpm cr:findings wrapper | — | S | ❌ **取り下げ** | #D-3 で代替 | -| ~~#D-4~~ 応答スタイル簡素化 | — | S | ⏸️ **保留** | Bundle Z Phase 2/3 完了後に再評価 | +## 全体統合: 残作業の PR 計画 -**Bundle 移行**: 旧 Bundle Z2 (#D-1 + #D-4) を再構成し、#D-1 + #D-3 を Bundle a (PR #99 post-merge-feedback 由来、cli-pr-monitor の rate-limit auto-retry + ADR 明文化と統合) に組み込む。設計根拠と実装方針は **ADR-034** 参照。 +> **方針**: 「少ない PR で的確に + フィードバックループを活かす」を満たすため、3 PR の依存順実行とする。Bundle Z Phase 2/3 は dogfood signal の純度確保のため分割必須、その他は即時 skip 系 / fix-trust 系で thematic にバンドル。 -**期待累積効果 (Bundle Y2 + 縮小 Z2 統合、2026-05-02 改訂)**: +### PR 計画 (3 PR、依存順) -- #A-1 + #C-1 (haiku 化): session あたり 15-20 分削減 + token 大削減 -- #D-1 + #D-3 (Bundle a 統合): cache_creation **~150-500K tokens 削減 (1-3.7%)** + rate-limit 自動回復で手動介入消滅 -- Bundle Z 再編 (#B-α + #B-β + #B-γ、3 Phase 分割): pre-push iter 数を **1-iter 固定** に構造化 (outlier 率 1/6 (16.7%) → 0% 試算) -- #D-4 保留分: 約 **2.5-4M tokens** の潜在削減余地 (Bundle Z Phase 2/3 完了後に副作用観測手段確立後の再評価対象) -- **合計**: rate-limit 90% 消費が ~75% / 3h に下がる試算 (#D-4 抜き、Bundle Z 完了込み) +#### PR 1: 即時 skip バンドル (即実施可) ---- +| 含む項目 | 変更箇所 | 内容 | effort | +|---|---|---|---| +| #C-3 | `cli-pr-monitor` | rate-limit 検出時に post-pr-review takt invoke を skip (PR #97 の `rate_limit.is_some()` を流用) | S | +| #A-2 | `cli-merge-pipeline` | doc-only かつ commit 数=1 かつ +/-<50 の trivial PR で post-merge-feedback skip | S | -## 全体統合: Bundle 群の累積効果見積 +**バンドル根拠**: 異なる crate だが両方とも「条件検出 → パイプライン skip」の単純 guard 追加。発火する PR の種類が異なる (rate-limit 中の PR vs doc-only PR) ため、後続 dogfood で誤発火が起きても帰属が明確。 -> **目的**: 別セッションで改善作業を進める際の **指示・優先順位の根拠** + **改善後の比較ベースライン** として使用する。本見積は本ドキュメント執筆時点 (PR #97 セッション、2026-04-30 〜 2026-05-01 JST) の観測値から導出。 +**期待効果**: rate-limit 頻発セッションで 4-8 分削減 + doc PR 月次発生分の post-merge-feedback コスト削減 -### Bundle 編成 +#### PR 2: Bundle Z Phase 2 — #B-β 単独 (PR #99 dogfood 完了後) -| Bundle | 内容 | effort | 即効性 | +| 含む項目 | 変更箇所 | 内容 | effort | |---|---|---|---| -| **Bundle Y2** | #A-1 + #C-1 (analyze facets を haiku 化、aggregate/fix/supervise は sonnet 維持) | XS (yaml 4 行) | 最即効 | -| **Bundle Z (再編)** | #B-α + #B-β + #B-γ (アーキテクチャ 3 層: 決定論 comment lint / 制約付き fix / 異常検知 reviewer)、Phase 1〜3 で順次 dogfood | M+M+S (3 Phase 分割) | 段階的 (Phase 1 から) | -| ~~**Bundle Z2**~~ (retire) | 旧案 #D-1 + #D-4 → 再構成: **#D-1 + #D-3 を Bundle a に統合** (PR #99 post-merge-feedback 統合、ADR-034 参照)、**#D-4 は保留** (思考連続性低下リスク、Bundle Z Phase 2/3 完了後に再評価) | — | — | -| **Bundle a** (新規、PR #99 post-merge-feedback 由来) | cli-pr-monitor の rate-limit auto-retry (T2-4) + ADR-018/009 rate-limit retry ポリシー明文化 (T3-5) + #D-1 (gh CLI 規則) + #D-3 (`check-ci-coderabbit --list-findings`) | M+S+XS+M (2 Sub-PR 分割) | 中期 (CR rate-limit 自動回復 + listing token 削減) | +| #B-β | `.takt/facets/instructions/fix.md` + `scripts/fix-metrics-check.ps1` (新規) | 制約付き fix instruction (deterministic check)。例外マーカーは PR #99 の `ALLOWED_LINE_PREFIXES` / `ALLOWED_BLOCK_PREFIXES` を import | M | + +**単独 PR にする根拠**: 決定論メトリクス層は novel で、合理的 refactor の誤 reject リスクが未知。Phase 3 (#B-γ) は Phase 2 が信頼できることを前提に reviewer 役割を絞るため、**Phase 2 単独 dogfood で誤 reject 率を計測しないと Phase 3 の二重 miss リスクが評価不能**。 -### 期待効果 (Bundle 別) +**期待効果**: pre-push-review 根因 2 (fix が新 violation を introduce) の構造的排除 -| Bundle | 削減対象 | 想定削減量 | 検証指標 | +#### PR 3: Phase 3 + fix-trust 連帯 (PR 2 dogfood 完了後) + +| 含む項目 | 変更箇所 | 内容 | effort | |---|---|---|---| -| Y2 | analyze step の sonnet 利用 | session あたり 15-20 分 + sonnet → haiku で **当該 step の token cost 1/3** | post-pr-review / post-merge-feedback の avg time、当該 facets の billable input tokens | -| Z (再編) | pre-push-review iter 数を構造的に固定化 | **outlier 0% 達成 + 1-iter ALL APPROVE 90% 超** (旧推定: avg iter 2.5 → 1.5 だったが、決定論層導入で 1-iter 固定が target に格上げ) | pre-push-review iter 分布 (`{1×N}` 集中度)、6-iter outlier 発生率 (target 0%)、1-iter ALL APPROVE 率 | -| ~~Z2~~ (retire) | — | — | — | -| Bundle a (#D-1 + #D-3 部分) | gh CLI noise + CR review listing 重複 metadata | cache_creation **~150-500K tokens 削減** (#D-1 で gh tool_result ~70KB 削減 + #D-3 で `gh api` 重複取得消滅) + rate-limit 自動回復によるユーザー手動介入消滅 | gh tool_result avg/max chars、cli-pr-monitor の rate-limit auto-trigger 投稿成功率、CR review listing token 量比較 | -| #D-4 (保留分) | Claude text-only response (8.5M tokens、cache_creation 62%) | 潜在 **2.5-4M tokens 削減** (18-29%)、ただし副作用観測手段確立後に再評価 | Bundle Z Phase 2/3 完了後に session 比較メトリクスを設計 | +| #B-γ | `.takt/facets/instructions/review-{simplicity,security}.md` | reviewer enumerate 義務を削除、決定論層が intercept する metric (comment count / nesting / function length) は skip、異常検知のみ flag | S | +| #C-2 | `.takt/workflows/post-pr-review.yaml` + `fix.md` の Convergence gate | fix step が `persists: 0 / misdirected: 0` を report したら Iter 3 analyze を skip して COMPLETE 直行 | M | -### 統合効果試算 +**バンドル根拠**: 両方とも **「LLM が出した結果を後段で再検証しない」** という設計哲学の応用。失敗モードも共通 (fix step が誤って "fully resolved" を report → 後段でカバーされない)。同 PR で land すると「決定論層 + fix step trust」のフルシフトが 1 セッションで観測でき、効果計測も統合的。 -ベースライン (PR #97 セッション): -- 一意 cache_creation: **13.64M tokens** -- takt パイプライン総時間: **114.7 分** (セッション 63%) -- rate-limit 90% を 3 時間で消費 +**期待効果**: pre-push iter 分布 → `{1×N}` 構造化 (1-iter ALL APPROVE 90% 超) + post-pr-review 3-iter outlier 消失 -Bundle Y2 + Z + a 全実装後の試算 (2026-05-02 改訂、#D-4 保留により縮小): +### 実行順序 -- 一意 cache_creation: **11-12M tokens** (Y2 + Bundle a #D-1 + #D-3 で 10-15% 削減、#D-4 抜き) -- takt パイプライン総時間: **80-95 分** (Z + Y2 で 25-30% 短縮、Bundle Z Phase 2/3 で outlier 0% に収束) -- rate-limit 消費: 90% / 3h → **75% / 3h** 試算 (#D-4 込みなら 60-70% に到達余地) -- Bundle a によるユーザー手動介入: **rate-limit 解除待ち + `@coderabbitai review` 投稿が完全自動化** +```text +時間 → -**注**: 上記は **各効果が独立加法的** との仮定。実際は中間効果が打ち消される可能性あり。dogfood 1-2 セッションで実測必須。 +PR 1 (skip バンドル) ━━━╋━━━━━━━━━━━━━━━━━ [merge → 通常 dogfood で観測] + ↓ +PR 2 (Bundle Z Phase 2) ━━━╋━━━━━━━━━━━━━━ [merge → 1-2 PR で誤 reject 率計測] + ↓ +PR 3 (Phase 3 + fix-trust) ━━━╋━━━━━━━━ [merge → outlier 0% 検証] -### 検証方法 (別セッションで Bundle 実装後に実施) +任意: PR 1 と PR 2 は並列着手可 (依存なし) +必須: PR 3 は PR 2 merge + dogfood 1-2 PR 後 +``` -実装後セッションを 1 つ完走させた後、以下を本ドキュメントの「観測データ」セクションと比較: +### 各 PR 着手前チェックリスト -#### ① セッション全体メトリクス比較 +| PR | 着手前に確認すべきこと | +|---|---| +| PR 1 | なし (即着手可) | +| PR 2 | PR #99 の `ALLOWED_LINE_PREFIXES` 定数が安定していること (PR #99 merge 後の dogfood で例外マーカー追加が落ち着いていること) | +| PR 3 | PR 2 merge 後 1-2 PR で **適切な refactor が誤 reject されていないこと** を確認 (`.takt/runs//fix-metrics.log` を観測) | + +### 番外: #A-3 transcript filter (任意タイミング) + +**判断**: PR 1〜3 のいずれにも組み込まない。**スキマ時間の単独 PR** か **#D-4 再評価セッション** に同梱が妥当。 + +**理由**: + +- 他のどの PR とも依存も共通テーマもない (analyze-session の input range filter という独立 infra 作業) +- effort M でテスト追加が要る → PR 1 に混ぜると規模が膨らむ +- Phase 3 dogfood の signal 純度を保ちたいので PR 3 にも混ぜない +- ROI ★★★ で優先度が中程度 + +### 期待効果 (残作業) + +| 項目 | 削減対象 | 想定削減量 | 検証指標 | +|---|---|---|---| +| Bundle Z Phase 2 + 3 | pre-push-review iter 数 | **outlier 0% 達成 + 1-iter ALL APPROVE 90% 超** | pre-push-review iter 分布 (`{1×N}` 集中度)、6-iter outlier 発生率 | +| #A-2 trivial PR skip | doc-only PR の post-merge-feedback 起動 | doc PR ごとに 9 分削減 | post-merge-feedback runs 数 | +| #A-3 transcript filter | analyze-session の input token | 30-50% 削減 (推定) | analyze-session の billable input tokens | +| #C-2 Iter 3 短絡 | post-pr-review iter 3 | 3-iter run を 2-iter に圧縮 (~3 分削減/run) | post-pr-review の avg iter 数 | +| #C-3 rate-limit skip | rate-limit 中の空打ち | 計 4-8 分削減/session (rate-limit 頻発時) | rate-limit 検出時の post-pr-review skip 率 | +| #D-4 (保留) | Claude text-only response | 潜在 2.5-4M tokens 削減 (18-29%)、要副作用観測 | session 比較メトリクス (要設計) | + +### 検証方法 (Bundle 実装後に実施) + +実装後セッションを 1 つ完走させた後、以下を「観測データ」セクションと比較。 + +#### ① セッション全体メトリクス比較 (全残作業共通) ```bash # 別セッションの jsonl path を取得 (例: ~/.claude/projects//.jsonl) @@ -692,14 +392,13 @@ EOF ``` **比較値 (ベースライン)**: + - Turns: 1,181 - Cache creation: 13,638,572 - Cache read: 350,868,831 - Output: 833,825 -#### ② takt パイプライン時間比較 - -各 takt run の `meta.json` から iter / 時間を集計: +#### ② takt パイプライン時間比較 (#A / #C 系の検証用) ```bash for d in .takt/runs/-*-{pre-push-review,post-pr-review,post-merge-feedback}*; do @@ -718,82 +417,65 @@ done | post-merge-feedback | 4 | 8 | 35.5 分 | | post-pr-review | 6-8 | 9-15 | 22-31 分 | -#### ③ Bash gh CLI tool_result 比較 - -```python -# Bash gh-pr 関連の tool_result chars を集計 (#D-1 効果検証) -# Top 5 大きい gh 出力サイズを ベースライン値と比較: -# - 47.98KB (POST /replies) -# - 44.08KB (gh pr view --json reviews,comments) -# - 24.10KB / 22.86KB / 19.62KB -``` - -#### ④ pre-push-review iter 分布比較 (#B 効果検証) +#### ③ pre-push-review iter 分布比較 (Bundle Z Phase 2 / 3 検証専用) ベースライン: `{1×3, 3×2, 6×1}` = 6 runs (うち 6-iter outlier 1 件、avg iter 2.5) -目標 (Bundle Z 再編後): `{1×N}` (1-iter 固定) で 6-iter outlier 消失 (outlier 率 1/6 (16.7%) → 0%)、avg iter 2.5 → 1.0、1-iter ALL APPROVE 率 90% 超 (L302 / L633 と統一) - -#### ⑤ サンプル CR review listing token 量比較 (#D-4 効果検証) -ベースライン: round 1-3 の review listing は各 2-5KB chars -目標: 各 1-2KB chars (file:line + severity + 1 行要約のみ) +目標 (Phase 2/3 完了後): `{1×N}` (1-iter 固定) で 6-iter outlier 消失 (outlier 率 1/6 (16.7%) → 0%)、avg iter 2.5 → 1.0、1-iter ALL APPROVE 率 90% 超 ### 別セッションでの作業指示 (テンプレート) -別セッションで Bundle Y2 / Z / Z2 のいずれかを実装する際、以下のフォーマットで本ドキュメントを参照する: - ```markdown ## 作業概要 -本セッションは [Bundle Y2 | Bundle Z | Bundle Z2] の実装を行う。 -詳細は docs/pipeline-token-efficiency.md の該当セクションを参照。 +本セッションは docs/pipeline-token-efficiency.md の [PR 1 | PR 2 | PR 3 | 番外 #A-3] を実装する。 +含む項目と変更箇所は同ドキュメントの「PR 計画」セクションを参照。 + +## 着手前チェック +docs/pipeline-token-efficiency.md の「各 PR 着手前チェックリスト」を確認し、前提が満たされていることを確認。 ## 実装タスク -- [ ] [該当 Bundle の改善案を順に列挙] +- [ ] PR 計画に列挙された全項目の実装 - [ ] テスト追加 (該当する場合) - [ ] 実装 PR を作成 - [ ] merge 後に本セッションを 1 つ完走させる (検証用 dogfood) ## 検証方法 -docs/pipeline-token-efficiency.md の「全体統合: Bundle 群の累積効果見積 → 検証方法」を実行。 -本ドキュメントのベースライン値と比較し、想定削減量に届いているか測定。 +docs/pipeline-token-efficiency.md の「検証方法」を実行。 +ベースライン値と比較し、想定削減量に届いているか測定。 ## 完了基準 -- 想定削減量の 70% 以上達成 → 本ドキュメントの「進捗管理」table で「採用済」マーク + 完了 PR 番号記録 -- 想定削減量に届かず → 原因分析を「全体統合」セクション末尾に追記し、必要なら追加 Bundle 提案 +- 想定削減量の 70% 以上達成 → 本ドキュメントから該当 PR セクション削除 + 進捗管理に PR 番号記録 +- 想定削減量に届かず → 原因分析を「全体統合」セクション末尾に追記し、必要なら計画再編 ``` --- ## 進捗管理 -| 改善案 | 状態 | 採用日 | 完了 PR | -|---|---|---|---| -| #A-1 analyze facets haiku 化 | 採用済 (Bundle Y2) | 2026-05-01 | #98 | -| #A-2 trivial PR skip | 計画 | - | - | -| #A-3 transcript filter | 計画 | - | - | -| ~~#B-1 fix self-check~~ | 取り下げ (Bundle Z 再編 → #B-β) | 2026-05-01 | — | -| ~~#B-2 reviewer exhaustive scan~~ | 取り下げ (Bundle Z 再編 → #B-γ) | 2026-05-01 | — | -| ~~#B-3 決定論的 lint hook~~ | 取り下げ (Bundle Z 再編 → #B-α) | 2026-05-01 | — | -| ~~#B-4 coding-style.md 強化~~ | 完全削除 (Bundle Z 再編、補助層としても不採用) | 2026-05-01 | — | -| #B-α 決定論 comment lint hook (Rust 限定 PoC) | 採用済 (Bundle Z Phase 1) | 2026-05-02 | #99 | -| #B-β 制約付き fix instruction | 計画 (Bundle Z Phase 2) | 2026-05-01 | - | -| #B-γ reviewer 役割変更 (異常検知化) | 計画 (Bundle Z Phase 3) | 2026-05-01 | - | -| #C-1 analyze haiku 化 | 採用済 (Bundle Y2) | 2026-05-01 | #98 | -| #C-2 Iter 3 短絡 | 検討 | - | - | -| #C-3 rate-limit skip | 計画 | - | - | -| #D-1 gh CLI 規則 | 採用 (Bundle a Sub-PR 1) | 2026-05-02 | - | -| ~~#D-2 pnpm cr:findings wrapper~~ | 取り下げ (#D-3 で代替) | 2026-05-02 | — | -| #D-3 Rust findings mode | 採用 (Bundle a Sub-PR 1) | 2026-05-02 | - | -| ~~#D-4 応答スタイル簡素化~~ | 保留 (Bundle Z Phase 2/3 完了後再評価、ADR-034) | 2026-05-02 | — | +| 改善案 | 配置 | 状態 | 採用日 | 完了 PR | 備考 | +|---|---|---|---|---|---| +| #C-3 rate-limit skip | **PR 1** | 計画 | - | - | PR #97 の rate-limit 検出を流用 | +| #A-2 trivial PR skip | **PR 1** | 計画 | - | - | 単独実施可 | +| #B-β 制約付き fix instruction | **PR 2** (Bundle Z Phase 2) | 計画 | 2026-05-01 | - | PR #99 の例外マーカー定数と同期必須 | +| #B-γ reviewer 役割変更 | **PR 3** (Bundle Z Phase 3) | 計画 | 2026-05-01 | - | PR 2 dogfood 完了が前提 | +| #C-2 Iter 3 短絡 | **PR 3** (fix-trust 連帯) | 計画 | - | - | PR 3 で #B-γ と同梱 | +| #A-3 transcript filter | **番外** | 計画 | - | - | スキマ時間の単独 PR か #D-4 再評価時に同梱 | +| #D-4 応答スタイル簡素化 | (保留) | 保留 (ADR-034) | 2026-05-02 | - | Bundle Z (PR 2 + PR 3) 完了後再評価 | --- ## 関連 - 元セッション: `C:\Users\HIROKI\.claude\projects\e--work-claude-code-hook-test\6cbc5021-e5f4-420d-853b-e1b467d45ae4.jsonl` +- 前提となる完了 PR (残作業の依存先): + - **PR #97** (cli-pr-monitor rate-limit 自動検出): #C-3 の前提 — 検出機構を流用して skip 判定を追加 + - **PR #99** (Bundle Z Phase 1 — `src/hooks-post-tool-comment-lint-rust/`): #B-β / #B-γ の前提 — 例外マーカー定数 (`ALLOWED_LINE_PREFIXES` / `ALLOWED_BLOCK_PREFIXES`) を single source of truth として参照 + - **PR #100** (Bundle a Sub-PR 0 — gh CLI 規則 + ADR-034 起案): #D-4 保留判断の根拠 + - **PR #101** (Bundle a Sub-PR 1 — `check-ci-coderabbit --list-findings`): rate-limit 関連で構造化 findings 取得が可能 - 関連 ADR: - [ADR-015](adr/adr-015-push-runner-takt-migration.md) (push runner takt 化) - [ADR-018](adr/adr-018-pr-monitor-takt-migration.md) (cli-pr-monitor takt 化) - [ADR-020](adr/adr-020-takt-facets-sharing.md) (facets 共通化) - [ADR-030](adr/adr-030-deterministic-post-merge-feedback.md) (post-merge-feedback 決定論化) + - [ADR-034](adr/adr-034-coderabbit-auto-monitoring.md) (#D-4 保留判断 + Bundle a 設計根拠) - 関連 workflow: [.takt/workflows/](../.takt/workflows/) diff --git a/src/cli-merge-pipeline/src/feedback.rs b/src/cli-merge-pipeline/src/feedback.rs index dfae9bf7..805c3f14 100644 --- a/src/cli-merge-pipeline/src/feedback.rs +++ b/src/cli-merge-pipeline/src/feedback.rs @@ -67,6 +67,32 @@ pub struct PrTimeRange { pub merged_at: String, } +/// PR の diff summary (#A-2 の trivial PR skip 判定で使用)。 +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct PrDiffSummary { + pub commit_count: usize, + pub total_lines_changed: u64, + pub all_files_are_markdown: bool, +} + +/// 「trivial」と判定する +/- 合計の上限 (この値未満なら trivial)。 +/// +/// 動機 (#A-2): doc-only PR や 1-commit fix PR では post-merge-feedback の ROI が +/// 低いため skip する。50 行は doc 系 PR の典型サイズ + α として設定。 +pub const TRIVIAL_PR_LINE_LIMIT: u64 = 50; + +impl PrDiffSummary { + /// docs/pipeline-token-efficiency.md PR 1 #A-2 の判定条件: + /// 1) changed files が全て .md + /// 2) かつ commit 数 = 1 + /// 3) かつ +/- 合計 < TRIVIAL_PR_LINE_LIMIT + pub fn is_trivial(&self) -> bool { + self.commit_count == 1 + && self.total_lines_changed < TRIVIAL_PR_LINE_LIMIT + && self.all_files_are_markdown + } +} + /// takt workflow に渡す JSON コンテキスト。 #[derive(Serialize)] struct WorkflowContext<'a> { @@ -161,6 +187,75 @@ pub fn fetch_pr_time_range(pr_number: u64, owner_repo: &str) -> Result Result { + let pr_str = pr_number.to_string(); + let output = Command::new("gh") + .args([ + "pr", + "view", + &pr_str, + "--repo", + owner_repo, + "--json", + "files,commits,additions,deletions", + ]) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .output() + .map_err(|e| format!("gh コマンド起動失敗: {}", e))?; + + if !output.status.success() { + return Err(format!( + "gh pr view (diff summary) 失敗: {}", + String::from_utf8_lossy(&output.stderr).trim() + )); + } + + let json: serde_json::Value = serde_json::from_slice(&output.stdout) + .map_err(|e| format!("gh 出力 JSON パース失敗: {}", e))?; + + parse_pr_diff_summary(&json) +} + +/// `gh pr view --json files,commits,additions,deletions` の応答を `PrDiffSummary` に変換する。 +fn parse_pr_diff_summary(json: &serde_json::Value) -> Result { + let commits = json + .get("commits") + .and_then(|v| v.as_array()) + .ok_or("commits が応答に含まれていません")?; + + let additions = json + .get("additions") + .and_then(|v| v.as_u64()) + .ok_or("additions が応答に含まれていません")?; + let deletions = json + .get("deletions") + .and_then(|v| v.as_u64()) + .ok_or("deletions が応答に含まれていません")?; + + let files = json + .get("files") + .and_then(|v| v.as_array()) + .ok_or("files が応答に含まれていません")?; + + let all_md = !files.is_empty() + && files.iter().all(|f| { + f.get("path") + .and_then(|v| v.as_str()) + .map(|p| p.to_lowercase().ends_with(".md")) + .unwrap_or(false) + }); + + Ok(PrDiffSummary { + commit_count: commits.len(), + total_lines_changed: additions + deletions, + all_files_are_markdown: all_md, + }) +} + /// transcript jsonl をフィルタして書き出す。 /// /// 入力: `source_dir` 配下の `*.jsonl` @@ -468,6 +563,116 @@ fn check_concurrent_run_guard(context_path: &Path) -> Result<(), String> { mod tests { use super::*; + #[test] + fn diff_summary_trivial_when_single_md_commit_under_limit() { + let summary = PrDiffSummary { + commit_count: 1, + total_lines_changed: TRIVIAL_PR_LINE_LIMIT - 1, + all_files_are_markdown: true, + }; + assert!(summary.is_trivial()); + } + + #[test] + fn diff_summary_not_trivial_when_at_line_limit_boundary() { + let summary = PrDiffSummary { + commit_count: 1, + total_lines_changed: TRIVIAL_PR_LINE_LIMIT, + all_files_are_markdown: true, + }; + assert!(!summary.is_trivial()); + } + + #[test] + fn diff_summary_not_trivial_when_multi_commit() { + let summary = PrDiffSummary { + commit_count: 2, + total_lines_changed: 10, + all_files_are_markdown: true, + }; + assert!(!summary.is_trivial()); + } + + #[test] + fn diff_summary_not_trivial_when_non_md_file_present() { + let summary = PrDiffSummary { + commit_count: 1, + total_lines_changed: 5, + all_files_are_markdown: false, + }; + assert!(!summary.is_trivial()); + } + + #[test] + fn parse_diff_summary_recognizes_trivial_doc_pr() { + let json = serde_json::json!({ + "commits": [{ "oid": "abc" }], + "additions": 10, + "deletions": 5, + "files": [ + { "path": "docs/todo.md", "additions": 10, "deletions": 5 } + ], + }); + let summary = parse_pr_diff_summary(&json).unwrap(); + assert_eq!(summary.commit_count, 1); + assert_eq!(summary.total_lines_changed, 15); + assert!(summary.all_files_are_markdown); + assert!(summary.is_trivial()); + } + + #[test] + fn parse_diff_summary_uppercase_md_extension_recognized() { + let json = serde_json::json!({ + "commits": [{ "oid": "abc" }], + "additions": 1, + "deletions": 0, + "files": [ + { "path": "README.MD", "additions": 1, "deletions": 0 } + ], + }); + let summary = parse_pr_diff_summary(&json).unwrap(); + assert!(summary.all_files_are_markdown); + } + + #[test] + fn parse_diff_summary_mixed_files_not_all_md() { + let json = serde_json::json!({ + "commits": [{ "oid": "abc" }], + "additions": 20, + "deletions": 10, + "files": [ + { "path": "docs/todo.md", "additions": 10, "deletions": 5 }, + { "path": "src/main.rs", "additions": 10, "deletions": 5 } + ], + }); + let summary = parse_pr_diff_summary(&json).unwrap(); + assert!(!summary.all_files_are_markdown); + assert!(!summary.is_trivial()); + } + + #[test] + fn parse_diff_summary_empty_files_not_all_md() { + let json = serde_json::json!({ + "commits": [{ "oid": "abc" }], + "additions": 0, + "deletions": 0, + "files": [], + }); + let summary = parse_pr_diff_summary(&json).unwrap(); + assert!(!summary.all_files_are_markdown); + assert!(!summary.is_trivial()); + } + + #[test] + fn parse_diff_summary_errors_on_missing_field() { + let json = serde_json::json!({ + "commits": [{ "oid": "abc" }], + "additions": 10, + "files": [], + }); + assert!(parse_pr_diff_summary(&json).is_err()); + } + #[test] fn project_id_windows_drive() { let p = Path::new("E:\\work\\claude-code-hook-test"); diff --git a/src/cli-merge-pipeline/src/main.rs b/src/cli-merge-pipeline/src/main.rs index c02fbb3d..6f50d70f 100644 --- a/src/cli-merge-pipeline/src/main.rs +++ b/src/cli-merge-pipeline/src/main.rs @@ -494,6 +494,29 @@ fn run_ai_step(label: &str, ctx: Option<&PipelineContext>) { } }; + match feedback::fetch_pr_diff_summary(pr_number, owner_repo) { + Ok(summary) if summary.is_trivial() => { + log_step( + label, + "SKIP", + &format!( + "trivial PR (commits={}, lines={}, all_md={}) — \ + post-merge-feedback skip (#A-2)", + summary.commit_count, + summary.total_lines_changed, + summary.all_files_are_markdown, + ), + ); + return; + } + Ok(_) => {} + Err(e) => log_step( + label, + "WARN", + &format!("trivial PR 判定失敗: {} — 通常 flow で続行", e), + ), + } + let transcript_source_dir = feedback::project_transcript_dir(&repo_root); if transcript_source_dir.is_none() { log_step( diff --git a/src/cli-pr-monitor/src/stages/monitor.rs b/src/cli-pr-monitor/src/stages/monitor.rs index 6273dc37..bee67f60 100644 --- a/src/cli-pr-monitor/src/stages/monitor.rs +++ b/src/cli-pr-monitor/src/stages/monitor.rs @@ -90,6 +90,11 @@ pub(crate) fn start_monitoring(pr_info: &PrInfo) -> i32 { if has_coderabbit_findings { if !collect_findings(&poll_result) { log_info("review-comments.json 書き出し失敗 (takt 分析をスキップ)"); + } else if poll_result.rate_limit.is_some() { + log_info( + "[rate_limit] CR rate-limit が active のため post-pr-review takt invoke を skip \ + (stale findings の空打ち回避、#C-3)", + ); } else if let Some(takt_config) = &config.takt { // ADR task 4 fix_state = create_fix_commit(pr_info.pr_number, &poll_result.findings); diff --git a/src/cli-pr-monitor/src/stages/poll.rs b/src/cli-pr-monitor/src/stages/poll.rs index 5963144a..c5a0dbbd 100644 --- a/src/cli-pr-monitor/src/stages/poll.rs +++ b/src/cli-pr-monitor/src/stages/poll.rs @@ -6,7 +6,7 @@ use crate::log::{log_info, truncate_safe}; use crate::runner::{checker_exe_path, run_cmd_direct, run_gh_quiet}; use crate::state::{ read_state, update_state_from_check_result, write_state, CiState, CodeRabbitState, - PrMonitorState, + PrMonitorState, RateLimitState, }; use crate::util::{utc_now_iso8601, PrInfo}; @@ -17,6 +17,11 @@ pub(crate) struct PollResult { pub(crate) coderabbit: Option, pub(crate) findings: Vec, pub(crate) check_output: Option, + /// 終了時点で rate-limit が active なら Some。caller (monitor.rs) は + /// `is_some()` を見て post-pr-review takt invoke を skip する (#C-3)。 + /// rate-limit 中は CR の fresh review が得られないため、stale な findings に + /// 対する takt 分析は空打ちになる。 + pub(crate) rate_limit: Option, } /// in-process 同期ポーリングループ (daemon.rs の同期版) @@ -41,6 +46,7 @@ pub(crate) fn run_poll_loop(full_config: &Config, pr_info: &PrInfo) -> PollResul coderabbit: None, findings: Vec::new(), check_output: None, + rate_limit: None, }; } @@ -83,6 +89,7 @@ pub(crate) fn run_poll_loop(full_config: &Config, pr_info: &PrInfo) -> PollResul coderabbit: None, findings: Vec::new(), check_output: None, + rate_limit: None, }; } @@ -97,6 +104,7 @@ pub(crate) fn run_poll_loop(full_config: &Config, pr_info: &PrInfo) -> PollResul coderabbit: None, findings: Vec::new(), check_output: None, + rate_limit: None, }; } }; @@ -157,6 +165,7 @@ pub(crate) fn run_poll_loop(full_config: &Config, pr_info: &PrInfo) -> PollResul coderabbit: state.coderabbit, findings: state.findings, check_output: Some(result), + rate_limit: state.rate_limit, }; } @@ -203,6 +212,7 @@ pub(crate) fn run_poll_loop(full_config: &Config, pr_info: &PrInfo) -> PollResul coderabbit: state.coderabbit, findings: state.findings, check_output: Some(result), + rate_limit: state.rate_limit, }; } state.rate_limit_last_retriggered_at = Some(rl.comment_event_time.clone()); @@ -223,6 +233,7 @@ pub(crate) fn run_poll_loop(full_config: &Config, pr_info: &PrInfo) -> PollResul coderabbit: state.coderabbit, findings: state.findings, check_output: Some(result), + rate_limit: state.rate_limit, }; } continue; // skip 通常 sleep、次 iteration で fresh polling @@ -241,6 +252,7 @@ pub(crate) fn run_poll_loop(full_config: &Config, pr_info: &PrInfo) -> PollResul coderabbit: state.coderabbit, findings: state.findings, check_output: Some(result), + rate_limit: state.rate_limit, }; } } @@ -255,6 +267,7 @@ pub(crate) fn run_poll_loop(full_config: &Config, pr_info: &PrInfo) -> PollResul coderabbit: state.coderabbit, findings: state.findings, check_output: Some(result), + rate_limit: state.rate_limit, }; }