diff --git a/CLAUDE.md b/CLAUDE.md index a5aa9bc..d5fafda 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -30,6 +30,7 @@ - [ADR-026: Cargo workspace による Rust パッケージ統合](docs/adr/adr-026-cargo-workspace.md) - [ADR-027: Push-time review を simplicity に限定し architectural review は post-PR に委ねる](docs/adr/adr-027-push-review-simplicity-focus.md) - [ADR-028: 外部可視成果物の生成コマンド (PR 作成/マージ) の実行ゲート](docs/adr/adr-028-pnpm-create-pr-gate.md) +- [ADR-029: Post-Merge Feedback の自動起動 — pending file + 現セッション起動](docs/adr/adr-029-post-merge-feedback-auto-trigger.md) *(試験運用)* ## Build diff --git a/docs/adr/adr-013-merge-pipeline.md b/docs/adr/adr-013-merge-pipeline.md index f3204ee..ccc178c 100644 --- a/docs/adr/adr-013-merge-pipeline.md +++ b/docs/adr/adr-013-merge-pipeline.md @@ -2,7 +2,7 @@ ## ステータス -承認済み (2026-04-06) +承認済み (2026-04-06) / 改訂 (2026-04-23: `ai` ステップの実装方式を ADR-029 に分離して参照) ## コンテキスト @@ -96,7 +96,14 @@ step_timeout = 120 - 新しい exe のビルドが `build:all` に追加される(ビルド時間の微増) +### 将来の展望 (2026-04-23 追加) + +- **`ai` ステップの実装方式 (ADR-029)**: `[[merge_pipeline.post_steps]]` の `type = "ai"` スロット (現状 [src/cli-merge-pipeline/src/main.rs:313-322](../../src/cli-merge-pipeline/src/main.rs#L313-L322) で SKIP 実装) は、[ADR-029: Post-Merge Feedback の自動起動](adr-029-post-merge-feedback-auto-trigger.md) に従って「`.claude/post-merge-feedback-pending.json` への atomic 書き込み」として実装する。新規 Stop hook が pending file を検出して `additionalContext` 経由で Claude に skill 起動を指示する構成のため、exe 自体は AI を spawn しない。ADR-022 原則 1 (新規 artifact への自己記述) の枠内で完結する +- **pre_steps 拡張**: CI 必須チェック、コンフリクト事前検出、secret scan 等を `type = "command"` で追加可能 + ## References - [ADR-008: Push Pipeline ハーネスの実装](adr-008-push-pipeline-harness.md) — 同じ「ガード + CLI」パターンの先行例 - [ADR-012: src/ ディレクトリの命名規約](adr-012-src-naming-convention.md) — `cli-` プレフィックスの命名根拠 +- [ADR-014: Post-Merge Feedback](adr-014-post-merge-feedback.md) — `ai` ステップで呼び出す skill のフロー定義 +- [ADR-029: Post-Merge Feedback の自動起動](adr-029-post-merge-feedback-auto-trigger.md) — `ai` ステップの具体実装仕様 (2026-04-23 追加) diff --git a/docs/adr/adr-014-post-merge-feedback.md b/docs/adr/adr-014-post-merge-feedback.md index 53c6868..02e9732 100644 --- a/docs/adr/adr-014-post-merge-feedback.md +++ b/docs/adr/adr-014-post-merge-feedback.md @@ -2,7 +2,7 @@ ## ステータス -試験運用 (2026-04-09) +試験運用 (2026-04-09) / 改訂 (2026-04-23: 自動起動の具体仕様を ADR-029 に分離して参照) ## コンテキスト @@ -142,10 +142,12 @@ pnpm merge-pr (既存: ADR-013) - 試験結果が良好であれば、スキルが `pnpm merge-pr` を内包する統合形態に移行 - フィードバック提案の自動実装(Tier 1 の自動適用)も検討可能 +- **自動起動 (2026-04-23 追記)**: [ADR-029: Post-Merge Feedback の自動起動](adr-029-post-merge-feedback-auto-trigger.md) で「pending file + 現セッション起動」方式を採用。skill の呼び忘れ問題を解消する。ADR-029 は選択肢 3 の原則 (skill はメイン会話内で実行) を維持するための設計であり、選択肢 1 (exe からの AI spawn) を復活させるものではない — pending file は単なる state の受け渡し媒体で、新規 Claude Code session を spawn しないためセッション知見は維持される ## References - [ADR-013: Merge Pipeline](adr-013-merge-pipeline.md) — マージパイプラインの基盤。「Skill が exe を呼び出す形で統合可能」と言及 +- [ADR-029: Post-Merge Feedback の自動起動](adr-029-post-merge-feedback-auto-trigger.md) — 本 ADR の skill を自動発火する仕組み (2026-04-23 追加) - [ADR-006: hooks の設定駆動型アーキテクチャ](adr-006-config-driven-hooks.md) — hooks-config.toml による設定管理 - [ADR-007: カスタムリンターの正規表現層/AST層の線引き](adr-007-custom-linter-layer-boundary.md) — custom-lint-rules.toml の設計 - Plankton パターン — PostToolUse でリンター群を実行し決定論的に品質を保証するアプローチ diff --git a/docs/adr/adr-029-post-merge-feedback-auto-trigger.md b/docs/adr/adr-029-post-merge-feedback-auto-trigger.md new file mode 100644 index 0000000..087018f --- /dev/null +++ b/docs/adr/adr-029-post-merge-feedback-auto-trigger.md @@ -0,0 +1,256 @@ +# ADR-029: Post-Merge Feedback の自動起動 — pending file + 現セッション起動 + +## ステータス + +試験運用 (2026-04-23) + +## コンテキスト + +### 問題 + +ADR-014 で導入した `/post-merge-feedback` skill は、ユーザーが明示的に呼び出す前提である。実運用では呼び忘れが発生し、得られるはずのフィードバックが失われていた。ADR-013 の merge pipeline は `[[merge_pipeline.post_steps]]` に `type = "ai"` のスロットを持つが、現状は [src/cli-merge-pipeline/src/main.rs:313-322](../../src/cli-merge-pipeline/src/main.rs#L313-L322) で SKIP 実装のまま残っている。 + +`pnpm merge-pr` 後に自動でフィードバックループを起動する仕組みを追加したい。ただし以下の設計制約を破ってはならない: + +1. **セッション知見の維持 (ADR-014)**: skill はメイン会話内で実行される必要がある。新規 Claude Code session を spawn すると、ADR-014 が選択肢 3 で回避した「会話履歴にアクセスできない」問題 (選択肢 1 の欠点) が再発する +2. **意図表現の不可侵 (ADR-022 原則 1)**: automated actor は commit description / bookmark 名 / PR title/body 等の既存 artifact を書き換えない +3. **外部可視成果物の生成ゲート (ADR-028)**: PR 作成/マージ等は既存の二層ゲートで管理済み。本 ADR で新たな外部成果物を生成しない + +### 検討した選択肢 + +#### 1. exe からの AI spawn (`claude -p` など) + +`cli-merge-pipeline` が直接 `claude -p "/post-merge-feedback"` を起動する案。 + +- 新規セッションになりセッション知見 (現在のメイン会話履歴) が失われる +- ADR-014 選択肢 1 と同じ欠点が再発する +- **却下** + +#### 2. Stop hook が無条件で `/post-merge-feedback` を呼ぶ + +Stop のたびに skill を呼ぶ案。 + +- マージ直後以外でもノイズとして発火する +- 「マージ直後だけ呼ぶ」ための状態受け渡しがどのみち必要 → 案 3 のベースとなる +- 単独では **却下** + +#### 3. state file + 現セッション起動 (採用) + +`cli-merge-pipeline` が `.claude/post-merge-feedback-pending.json` を書き込み、新規 Stop hook (`hooks-stop-feedback-dispatch`) が検出して `additionalContext` で Claude に skill 起動を指示する。 + +- 新規 session を spawn しないのでセッション知見が維持される (ADR-014 選択肢 3 の強みを維持) +- pending file が決定論的 artifact として 3 コンポーネント (CLI / Stop hook / skill) を疎結合に協調させる +- state は `cat` で確認でき可観測性が高い + +## 決定 + +**選択肢 3 を採用する。** + +### アーキテクチャ + +```text +pnpm merge-pr (cli-merge-pipeline, ADR-013) + ├─ ... (マージ本体 + ローカル同期) + ├─ post_steps: type="ai" 分岐が pending file を atomic 書き込み + └─ exit 0 + │ + ▼ +Claude Code が Stop に向かう + │ + ▼ +Stop hooks (ADR-004 + 本 ADR, 責務分離は ADR-022 に準拠) + 1. hooks-stop-quality (既存: lint / test / build) + 2. hooks-stop-feedback-dispatch (新規) + ├─ stop_hook_active == true → silent exit (無限ループ防止) + ├─ pending 不在 / 破損 / stale → 削除 + silent exit + ├─ status == "dispatched" → silent exit (二重通知しない) + ├─ status == "consumed" → 削除 + silent exit (後片付け) + └─ status == "pending" → additionalContext 出力 + atomic で status="dispatched" に更新 + │ + ▼ +Claude がメイン会話内で /post-merge-feedback を起動 + ├─ Phase 0: pending file 先読み (本 ADR 追加) + ├─ Phase 1-5: ADR-014 のフローを踏襲 + └─ skill 完了時に status="consumed" → ファイル削除 +``` + +### Pending file JSON スキーマ (v1) + +**配置パス**: `.claude/post-merge-feedback-pending.json` (本プロジェクト・派生プロジェクトで統一) + +```json +{ + "schema_version": 1, + "pr_number": 123, + "owner_repo": "aloekun/claude-code-hook-test", + "prompt": "post-merge-feedback", + "status": "pending", + "created_at": "2026-04-23T10:00:00Z", + "dispatched_at": null, + "consumed_at": null +} +``` + +**フィールド定義**: + +| キー | 型 | 必須 | 説明 | +|---|---|---|---| +| `schema_version` | u32 | yes | スキーマ互換性管理。非一致で削除して silent exit | +| `pr_number` | u64 | yes | 対象 PR 番号 | +| `owner_repo` | string | yes | `{owner}/{repo}` 形式 | +| `prompt` | string | yes | skill 名または prompt key (今は `"post-merge-feedback"` 固定) | +| `status` | enum | yes | `"pending" \| "dispatched" \| "consumed"` | +| `created_at` | ISO 8601 UTC string | yes | cli-merge-pipeline が書き込んだ時刻 | +| `dispatched_at` | ISO 8601 UTC string | nullable | hooks-stop-feedback-dispatch が additionalContext を出した時刻 | +| `consumed_at` | ISO 8601 UTC string | nullable | skill が完了処理を行った時刻 | + +### 状態遷移 + +```text + ┌──────────────────────────────┐ + │ stale TTL (24h) → 強制削除 │ + └───────────┬──────────────────┘ + │ +(書き込み) → pending ──→ dispatched ──→ consumed ──→ (削除) + │ │ + └─ (破損) ──┤ + └─ 削除 + silent exit +``` + +- `pending`: cli-merge-pipeline が書き込んだ直後 +- `dispatched`: hooks-stop-feedback-dispatch が additionalContext を出した (二重発火防止のマーカー) +- `consumed`: skill が処理完了した直後 (削除する前の最終状態。論理的には極短時間で遷移) +- ファイル不在: 初期状態 or consumed 後の正常終端 + +**stale TTL**: `now - created_at > 24h` の pending は hooks-stop-feedback-dispatch が削除して silent exit。skill 呼び忘れに対する自動回復機構として働く。TTL 24h はマージ直後の dogfood セッションが途中終了しても翌日には復帰できる幅を狙った初期値。 + +### 競合ポリシー + +cli-merge-pipeline が pending file を書き込もうとしたときの既存ファイル別の挙動: + +| 既存 status | 挙動 | +|---|---| +| 不在 | 新規書き込み (通常経路) | +| `consumed` (削除忘れ) | 上書き | +| `pending` / `dispatched` | **書き込み skip + WARN** (ステップ自体は PASS 扱いで merge-pr を中断しない) | +| 破損 (size 0 / JSON parse 失敗 / schema_version 不一致) | 削除してから書き込み | + +同一セッション内で短時間に複数 PR をマージした場合 (現実には稀)、最初の pending が consume されるまで後続は取りこぼす。取りこぼしは WARN ログで可観測性を残すことで後追い対応可能とする。 + +**将来拡張**: 取りこぼしが問題化したらディレクトリベースのキュー (`.claude/post-merge-feedback/.json`) への移行を検討。現段階では YAGNI で単一ファイルを採用する。 + +### 破損耐性 + +hooks-stop-feedback-dispatch が pending file を読み取る際の分岐表: + +| 状態 | 挙動 | +|---|---| +| `stop_hook_active == true` | silent exit (無限ループ防止。pending は読まない) | +| ファイル不在 | silent exit (通常経路) | +| size 0 | ファイル削除 + silent exit | +| JSON parse 失敗 | ファイル削除 + silent exit | +| `schema_version` 不一致 | ファイル削除 + silent exit | +| stale (`created_at + 24h < now`) | ファイル削除 + silent exit | +| `status == "pending"` | additionalContext 出力 + `status="dispatched"` に atomic 更新 | +| `status == "dispatched"` | silent exit (二重通知しない) | +| `status == "consumed"` | ファイル削除 + silent exit | + +**書き込み方式**: 「一時ファイルに write → `fs::rename` で atomic rename」の 2 段階を常に使う。ロックファイルは不要。 + +ただし atomic 保証はプラットフォームとファイルシステムに依存する: + +| 環境 | `std::fs::rename` の atomicity | +|---|---| +| POSIX (Linux / macOS 等) | `rename(2)` により atomic overwrite (同一ファイルシステム内) | +| Windows 10 1607+ / NTFS or ReFS | `FileRenameInfoEx` + `FILE_RENAME_FLAG_POSIX_SEMANTICS` 経路が成功すれば atomic overwrite。本プロジェクトのターゲット (Windows 11 + NTFS) はこの範囲 | +| 旧 Windows / 非対応 FS | `FileRenameInfo` に fallback し **atomic 保証なし**。他プロセスが中間状態を観測する可能性がある | + +Rust 側の実装順序は rust-lang/rust の [#131072](https://github.com/rust-lang/rust/pull/131072) / [#138133](https://github.com/rust-lang/rust/pull/138133) で 2024-2025 に変更されており、信頼性のため non-atomic を先に試行、失敗時のみ POSIX semantics 版へ fallback する挙動になっている点にも留意。 + +本 ADR が「atomic rename で十分、ロック不要」と結論する前提は「Windows 10 1607+ / NTFS」が成り立つ本プロジェクト環境に限る。派生プロジェクトへバックポートする際は環境を再確認すること。実装 (task 1-B) 時の action item は docs/todo.md 1-B を参照。 + +### additionalContext 構造化フォーマット + +hooks-stop-feedback-dispatch が stdout に出力する JSON: + +```json +{ + "hookSpecificOutput": { + "hookEventName": "Stop", + "additionalContext": "[POST_MERGE_FEEDBACK_TRIGGER]\nschema_version: 1\npr_number: 123\nowner_repo: aloekun/claude-code-hook-test\naction: invoke_skill\ncommand: /post-merge-feedback 123\nreason: cli-merge-pipeline wrote pending artifact" + } +} +``` + +`additionalContext` の内部は行区切りの `key: value` 形式。先頭行の `[POST_MERGE_FEEDBACK_TRIGGER]` タグが他の additionalContext (例: `hooks-session-start` の `CLAUDE_CODE_SESSION_ID=...`) との識別子になる。 + +**固定キー順序** (パース容易性と unit test の比較を単純化するため): + +1. `schema_version` +2. `pr_number` +3. `owner_repo` +4. `action` (現状 `invoke_skill` 固定) +5. `command` (Claude が実行すべき slash command 文字列) +6. `reason` (観察用のコンテキスト) + +### ADR-022 原則 1 との整合性 + +本 ADR が導入する全副作用は原則 1 の許可側に収まる: + +| 副作用 | 分類 | 整合性 | +|---|---|---| +| `.claude/post-merge-feedback-pending.json` の新規書き込み | **新規 artifact への自己記述** | 許可 (緩和条項の適用も不要) | +| `status` の `pending → dispatched → consumed` 更新 | **自身が作成した artifact の自己更新** (意図表現ではない内部状態) | 許可 | +| 破損/stale pending の削除 | **自身が管理する artifact の破棄** | 許可 | +| `additionalContext` 出力 | **現セッション内 Claude への指示** (ファイル成果物を生成しない) | 副作用なし (草案生成に類する) | + +commit description / bookmark 名 / PR title/body への介入は一切発生しない。skill 側が pending を `consumed` に更新してから削除するのも同じ枠内。 + +### ADR-013 / ADR-014 / ADR-016 / ADR-022 / ADR-028 との関係 + +- **ADR-013**: `[[merge_pipeline.post_steps]]` の `type = "ai"` ステップを本 ADR の仕様で実装する。ADR-013 の「将来実装」プレースホルダを具体化する +- **ADR-014**: 選択肢 3 (skill はメイン会話内で実行) を維持。本 ADR は「明示呼び出し → 自動発火」の橋渡しのみを担い、skill 本体フローは ADR-014 を踏襲 +- **ADR-016**: `pnpm merge-pr` は 10-30 秒で完了するため長時間コマンド戦略の対象外。pending file 書き込みは追加のブロッキングを生まない +- **ADR-022**: 原則 1 (新規 artifact への自己記述) と原則 3 (amend ≠ describe、意図表現不変) の枠内で完結する。既存 commit / bookmark / PR には触れない +- **ADR-028**: 本 ADR は外部可視成果物 (PR / tag 等) を生成しない。したがって ADR-028 の `permissions.ask` ゲートの対象外 + +## 実装タスク + +詳細な実装手順は `docs/todo.md` の「マージ後フィードバックの定常化」セクションを参照。本 ADR は仕様のみを規定する。 + +- **1-B**: cli-merge-pipeline の `"ai"` 分岐を pending file 書き込みに置き換え +- **1-C**: `hooks-stop-feedback-dispatch` 新規 exe の追加 + Stop hook 登録 +- **1-D**: `.claude/hooks-config.toml` の post_steps 有効化 + dogfood 開始 +- **1-E**: post-merge-feedback skill の Phase 0 「pending file 先読み」追加 + +## 影響 + +### Positive + +- マージ直後にフィードバックループが自動で起動し、skill 呼び忘れによるロスがなくなる +- 新規 session を spawn しないためセッション知見が維持される (ADR-014 選択肢 3 の強みを継承) +- pending file が単一の決定論的 artifact なので、CLI / Stop hook / skill の 3 者が疎結合に協調できる +- state の可観測性が高い (ファイル内容を `cat` するだけで現状把握) +- 責務分離 (ADR-022) を維持したまま自動化を実現 + +### Negative + +- Stop hook が 2 つになる (hooks-stop-quality + hooks-stop-feedback-dispatch)。実行順序は 1 → 2 固定で、品質ゲート失敗時は pending dispatch に進まない +- pending file の schema 変更時は `schema_version` を bump して互換性管理が必要 +- 同一セッションで短時間に複数 PR をマージした場合、最初の 1 件しか自動発火しない (後続は WARN で可観測。将来拡張でキュー化可能) + +### 将来の展望 + +- 取りこぼしが問題化したらディレクトリベースのキュー (`.claude/post-merge-feedback/.json`) へ移行 (schema_version bump を伴う) +- dogfood で問題なければ ADR-014 の試験運用ステータスを本採用化 (docs/todo.md の 1-F タスク) +- 派生プロジェクト (takt-test-vc 等) へバックポート + +## References + +- [ADR-004: Stop フックによる品質ゲート](adr-004-stop-hook-quality-gate.md) — `stop_hook_active` 無限ループ防止パターンの先行例 +- [ADR-013: Merge Pipeline](adr-013-merge-pipeline.md) — `post_steps` の `type = "ai"` スロットの提供元 +- [ADR-014: Post-Merge Feedback](adr-014-post-merge-feedback.md) — skill 本体のフロー定義 (選択肢 3 採用の根拠) +- [ADR-016: 長時間コマンド実行戦略](adr-016-long-running-command-strategy.md) — `pnpm merge-pr` の実行時間特性 +- [ADR-022: 自動化コンポーネントの責務分離原則](adr-022-automation-responsibility-separation.md) — 原則 1 (新規 artifact への自己記述) の適用根拠 +- [ADR-026: Cargo workspace](adr-026-cargo-workspace.md) — `hooks-stop-feedback-dispatch` 新規 crate の追加手順 +- [ADR-028: pnpm create-pr ゲート](adr-028-pnpm-create-pr-gate.md) — 外部可視成果物ゲートとの軸別境界 (本 ADR の射程外) diff --git a/docs/todo.md b/docs/todo.md index cdd1dc7..01b2c11 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -6,53 +6,121 @@ ## 現在進行中 -### 1. マージ後フィードバックの定常化 (cli-merge-pipeline の post_steps 統合) - -- **やろうとしたこと**: `pnpm merge-pr` 後の「ADR 記録すべきもの」「仕組みに反映すべきもの」の手動依頼を自動化。ADR-014 で提唱された `post-merge-feedback` スキルを cli-merge-pipeline から自動起動する -- **現在地**: 設計段階。未着手 - - [ ] `src/cli-merge-pipeline/src/main.rs` の `run_steps` の `"ai"` 分岐を現在の `SKIP` から実装に置き換える (takt 経由で skill を起動、または claude -p で起動) - - [ ] `.claude/hooks-config.toml` の `[[merge_pipeline.post_steps]]` に `type = "ai"`, `prompt = "post-merge-feedback"` を設定 - - [ ] `post-merge-feedback` スキルが PR 番号とブランチ名を受け取れるよう、cli-merge-pipeline から環境変数または引数で渡す設計 - - [ ] マージ済みセッションの会話ログを参照する手段 (Claude Code Session ID 等) の検討 -- **詰まっている箇所**: - - **主要ブロッカー**: 「マージ時点のセッション会話」を post_steps 用の新セッションに引き継ぐ手段が決まっていない。会話ログがないと「何を議論した末のマージか」が失われ、フィードバック品質が下がる - - **Why**: post-merge-feedback は ADR-014 で「セッション知見 + PR 知見の統合」を前提にしているが、merge-pipeline は別プロセスで起動されるため会話がない状態から始まる - - **How to apply / 再開手順**: SessionStart hook (master の `src/hooks-session-start/`) で伝播した session ID を jsonl transcript に紐付けて読み取る方式が候補。ADR を書いてから実装 - - **制約**: ADR-016 (長時間コマンド) のため、post_steps の AI 起動も `run_in_background: true` + `timeout: 600000` 前提で設計する必要あり -- **依存関係**: - - SessionStart hook は master に実装済み (`src/hooks-session-start/`)。セッション引継ぎ設計は session ID → jsonl transcript 紐付けの ADR が必要 - - takt-test-vc での試験運用を先に行い、本プロジェクトに反映 - -### 2. post-pr review フローの並行通知化 (E2E 検証待ち) - -- **やろうとしたこと**: `pnpm create-pr` 実行中に CodeRabbit 指摘検出 → takt 自動修正 → re-push が BG で進行する間、Claude Code が中間状態を受け取れず「未対応レビューをリストアップして」の重複依頼が発生していた。observer パスで早期通知して解消する -- **現在地**: 実装完了 (ADR-018 追記セクションに仕組み反映済み)。残るは実 PR での E2E 観察のみ - - [x] `src/cli-pr-monitor/src/stages/observe.rs` 新設 (Rust exe サブコマンド、PowerShell 廃案) - - [x] `cli-pr-monitor --observe` ハンドラを `main.rs` に配線 + observe stage の unit test 7 件 - - [x] `poll.rs`: iteration を跨いで `notified` flag を preserve (`PrMonitorState::new` が毎回 reset する挙動を修正) - - [x] `start_monitoring` 冒頭で state を明示初期化 (新セッション開始時の reset) - - [x] `package.json` に `observe-pr` / `mark-notified` スクリプト追加 - - [x] `~/.claude/skills/post-pr-create-review-check/skill.md` を並行 BG 構成に更新 (stale な daemon/CronCreate 記述を除去) - - [ ] 実 PR での E2E 検証: CodeRabbit Major ありの PR で、Claude Code が `pnpm create-pr` と `pnpm observe-pr` を並行 BG 起動し、observer の早期通知で Minor ヒアリングが走ることを確認 -- **参照**: - - ADR-018 追記 (2026-04-22) — observer モードと責務分離原則 - - ADR-022 — 「主フローは 100% 機械的 / 通知は read-only side effect」の境界 - -### 3. cli-pr-monitor の auto re-push に bookmark 自動前進を移植 - -- **やろうとしたこと**: takt 自動修正後の auto re-push で「修正コミットができても bookmark が動かず remote に届かない」問題を解消。cli-push-runner には PR #50 で `push_jj_bookmark.rs` の advance ロジックが入っているが、cli-pr-monitor の `run_push` は `jj new` → `jj git push` だけで bookmark を進めない -- **現在地**: port 完了 + 統合テストで機能等価を確認。あとは実 PR でのロールアウトのみ - - [x] `src/cli-pr-monitor/src/stages/push_jj_bookmark.rs` 新設 (cli-push-runner から port、log prefix は `[action]`/`[state]` に調整、`lib_jj_helpers::is_trunk_bookmark` 再利用) - - [x] `src/cli-pr-monitor/src/stages/push.rs:run_push` の `jj new` 後・push 前に `advance_jj_bookmarks` を挿入 (`push_command` が `jj ` で始まる場合のみ、失敗時はログして push 続行) - - [x] unit テスト (dedup / parse_bookmarks_from_template / parse_bookmark_list_output / dispatch_bookmark_advance) - - [x] 統合テスト `integration_advance_moves_bookmark_to_parent_after_jj_new` で実 jj を使い PR #53 症状の退行防止を確認 (push-runner-config の rust-test グループで自動実行される `#[ignore]` テスト、`--test-threads=1` 必須) - - [ ] 実 PR での E2E 検証: 次回 CodeRabbit Major 指摘が出た PR で、takt 修正 → auto re-push で bookmark が remote 反映まで自動到達することを目視確認 (本 PR マージ後にリリース) -- **詰まっている箇所**: - - **共通化方針**: まず port で機能等価を確認。将来 `lib-jj-helpers` へ集約する候補として `push_jj_bookmark.rs` 先頭に TODO コメントを残した (ADR-024) -- **参照 ADR / PR**: - - PR #50 (cli-push-runner の bookmark fallback) - - PR #63 (takt fix のコミット分離、完了済) - - ADR-024 (共通 jj helper、試験運用) +### マージ後フィードバックの定常化 (post-merge-feedback 自動起動) + +> **全体ゴール**: `pnpm merge-pr` 後、Stop 時に `/post-merge-feedback` skill の起動を Claude に指示する自動化を本プロジェクトで dogfood 開始できる状態にする。 +> +> **設計の核 (state file + 現セッション起動)**: cli-merge-pipeline が `.claude/post-merge-feedback-pending.json` を書き込み、新規 Stop hook が検出 → `additionalContext` で Claude に skill 起動を指示。新セッションを spawn しないので ADR-014 選択肢 3「skill はメイン会話内で実行」の原則を維持し、セッション知見の引き継ぎ問題を構造的に回避する。 +> +> **依存関係・順序**: ADR-029 (PR #69) マージ後に `1-B (CLI)` と `1-C (hook)` を並行可。最後に `1-D (有効化 + 試験運用開始)`。`1-E (skill 更新)` は独立タスクとして切り出し済み (依存: ADR-029 マージ)。 +> +> **全タスク共通の参照先**: 設計の詳細は `docs/adr/adr-029-post-merge-feedback-auto-trigger.md` (PR #69 で新規作成)。以降のタスクはこの ADR の仕様に従う。 +> +> **採否済みのフィードバック論点** (ADR-029 に反映済み): +> 1. 多重実行耐性 → pending file に `status: "pending" | "dispatched" | "consumed"` を持たせる +> 2. atomic write / 破損耐性 → atomic rename、読み取り時は size 0 / parse 失敗で削除、ロック不要 +> 3. 既存 pending との競合 → 既存 `status != "consumed"` なら新規書き込み skip + WARN (将来キュー化への拡張余地を Note に明記) +> 4. `additionalContext` は構造化タグ形式 (`[POST_MERGE_FEEDBACK_TRIGGER]` 等) にする +> 5. `run_steps` は `Option<&PipelineContext>` で後方互換を保つ + +#### 1-B. cli-merge-pipeline の `ai` 分岐実装 (コード + テスト、1 PR) + +- **やろうとしたこと**: 現状 SKIP 実装の `run_steps` の `"ai"` 分岐 ([src/cli-merge-pipeline/src/main.rs:313-322](../src/cli-merge-pipeline/src/main.rs#L313-L322)) を、ADR-029 仕様に沿った pending file 書き込みに置き換える +- **現在地**: 未着手 + - [ ] `PipelineContext` struct を新設 (`pr_number: u64`, `owner_repo: Option`) + - [ ] `run_steps` シグネチャを `Option<&PipelineContext>` で拡張 (後方互換、pre_steps は `None` を渡す) + - [ ] `"ai"` 分岐の実装: + - ctx が `None` → SKIP + log + - 既存 pending 読み取り: `status != "consumed"` なら WARN + skip (ステップ自体は PASS 扱い)、破損ファイル (size 0 / parse 失敗 / schema_version 不一致) は削除して続行 + - 新規 pending 書き込み (`status = "pending"`): tmp file → `fs::rename` で atomic + - pending file パス: `config_path().parent() / "post-merge-feedback-pending.json"` + - [ ] unit test 追加: + - 正常書き込み (ctx ありで新規作成) + - ctx なしで SKIP + - 既存 consumed 上書き成功 + - 既存 pending/dispatched で skip + WARN + - 破損 pending (parse 失敗) で削除後書き込み + - tmp → rename の atomicity (partial file が残らない) + - [ ] **atomic rename の環境確認と fallback 戦略** (CodeRabbit PR #69 指摘): + - 実装時に `std::fs::rename` のターゲット環境挙動を確認 (本プロジェクトは Windows 11 + NTFS 想定で atomic 経路に入るが、派生プロジェクトでは要再確認) + - `fs::rename` の Err を戻り値として伝播させ、呼び出し側でログ出力する (silent fail させない) + - 旧 Windows / 非対応 FS で non-atomic fallback が走ったケースの対処方針を docstring に明記: (a) 許容する (POST_MERGE_FEEDBACK_TRIGGER が 1 回発火失敗しても次マージで復帰可能)、(b) 必要なら `ReplaceFile` / `FileRenameInfoEx` の直接呼び出しを検討 + - `owner_repo` の入力検証 (newline injection 防御、正規表現 `^[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+$` 程度) を pending file 書き込み前に実施 (push 時の security-review で指摘済) + - [ ] `"ai"` 分岐のエラーハンドリング方針明文化: pending 書き込み失敗時もステップを FAIL にせず WARN + PASS とする (merge 自体は完了しているので pipeline を止めない) +- **完了基準**: `cargo test` 通過 + ローカルで `pnpm merge-pr` 手動実行 → 正しい pending file が生成される + atomic rename の挙動確認メモが実装コードの doc コメントに残っていること +- **詰まっている箇所**: なし +- **依存関係**: ADR-029 (PR #69) マージ後に着手 + +#### 1-C. hooks-stop-feedback-dispatch 新規 exe (コード + 配布統合、1 PR) + +- **やろうとしたこと**: Stop 時に pending file を検出し、`additionalContext` で Claude に skill 起動を指示する単一責務 hook を追加。既存 `hooks-stop-quality` とは責務分離 (ADR-022 原則) +- **現在地**: 未着手 + - [ ] `src/hooks-stop-feedback-dispatch/` 新規 crate + - `Cargo.toml` を workspace member に登録 (ADR-026) + - `src/main.rs` を実装: + - stdin JSON 読み取り (`stop_hook_active` 等) + - `stop_hook_active == true` → silent exit (無限ループ防止、hooks-stop-quality と同じパターン) + - pending 不在 → silent exit + - 破損 (size 0 / parse 失敗 / schema_version 不一致) → 削除して silent exit + - stale (created_at + 24h < now) → 削除して silent exit + - `status == "pending"` → 構造化 `additionalContext` を stdout に出力 + pending file の `status` を `"dispatched"` に atomic 更新 (`dispatched_at` も設定) + - `status == "dispatched"` → silent exit (二重通知しない) + - `status == "consumed"` → 削除して silent exit (後片付け) + - [ ] `Cargo.toml` (workspace root) の `members` に追加 + - [ ] `package.json` に `build:hooks-stop-feedback-dispatch` 追加、`deploy:hooks` に統合 + - [ ] `.claude/settings.json` の Stop hook エントリに 2 つ目の exe を追加 (hooks-stop-quality の**後**の順序) + - [ ] `templates/settings.json` にも同様の設定を反映 (派生プロジェクト配布用) + - [ ] unit test 追加: + - pending 不在で正常 exit + - `stop_hook_active = true` で silent exit (pending を読まない) + - 破損 pending の削除 + silent exit + - stale pending の削除 + silent exit + - status=pending → additionalContext 生成 + status=dispatched へ更新 + - status=dispatched → silent exit + - status=consumed → 削除 + silent exit + - additionalContext 文字列フォーマット検証 (構造化タグの key 順序等) +- **完了基準**: `cargo test` 通過 + `pnpm build:hooks-stop-feedback-dispatch` / `pnpm deploy:hooks` 成功 + hooks-stop-quality と並行動作確認 +- **詰まっている箇所**: なし +- **依存関係**: ADR-029 (PR #69) マージ後に着手 + +#### 1-D. post_steps 有効化 + 試験運用開始 (設定 + todo 更新、1 PR) + +- **やろうとしたこと**: 設定を有効化し、本プロジェクトで dogfood を開始する +- **現在地**: 未着手 + - [ ] `.claude/hooks-config.toml` の `[[merge_pipeline.post_steps]]` を有効化: + ```toml + [[merge_pipeline.post_steps]] + name = "post_merge_feedback" + type = "ai" + prompt = "post-merge-feedback" + ``` + - [ ] `templates/hooks-config.toml` にも反映 (派生プロジェクト用、デフォルト opt-in/opt-out 方針は PR 内で判断) + - [ ] `docs/todo.md` から本タスク群 (1-B〜1-D、および section ヘッダーと前文) を削除 (運用ルール: 完了タスクは ADR/仕組みに反映後に削除。1-A は PR #69 時点で削除済) +- **完了基準**: 実マージ (別 PR) の `pnpm merge-pr` で pending file が生成され、Stop 時に Claude が構造化 `additionalContext` を受け取って skill 起動を試みるフローが走ること (skill 未対応なら手動起動で検証) +- **詰まっている箇所**: なし +- **依存関係**: 1-B (CLI) + 1-C (hook) 両方の完了 + +#### 1-E. post-merge-feedback skill の pending file 対応 (別タスク、skill リポジトリ側で実施) + +- **やろうとしたこと**: skill Phase 1 の前段に「pending file 先読み (Phase 0)」を追加し、status が `"dispatched"` の場合は引数指定と同等の最優先度で採用。skill 完了時に `status = "consumed"` に更新してからファイル削除 +- **現在地**: 未着手。ADR-029 (PR #69) マージ後に仕様参照可能 + - [ ] skill リポジトリの管理場所を特定 (`$CLAUDE_SKILLS_REPO` 経由 or `~/.claude/skills/` 直接) → `/skill-sync-check` で確認 + - [ ] `SKILL.md` に Phase 0 「pending file 先読み」を追加: + - pending file を読み取り、`status == "dispatched"` ならその `pr_number` / `owner_repo` を採用 (引数・セッションコンテキスト・fallback より優先) + - `status == "pending"` (hook 未経由の fallback 経路) も受け入れる + - `status == "consumed"` なら無視してファイル削除 + - [ ] skill 完了時の consume 処理: `status = "consumed"` + `consumed_at` 設定 (atomic 更新) → その後ファイル削除 + - [ ] (任意) skill eval の追加: pending file ありのケース / 破損ケース / status 別の挙動 +- **完了基準**: skill が pending file を正しく consume し、本プロジェクトの dogfood で Claude が自動起動した skill から Feedback Report が出力される +- **詰まっている箇所**: skill の管理場所 (本プロジェクト外) の扱いは `/skill-sync-check` の結果次第 +- **依存関係**: ADR-029 (PR #69) マージ。1-B/1-C/1-D とは並行可能だが、dogfood の完結には 1-E も必要 + +#### 1-F. (追って) ADR-014 試験運用フラグ解除 + takt-test-vc 反映 + +- **やろうとしたこと**: dogfood 1-2 週間で問題なければ ADR-014 を本採用化し、takt-test-vc へバックポート +- **現在地**: 未着手。1-D 以降 + 運用観察が前提 +- **詰まっている箇所**: dogfood 結果に依存するため着手タイミングは未定 +- **依存関係**: 1-D 完了 + 本プロジェクトで実マージ数回の観察 ---