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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions docs/adr/adr-031-weekly-review-pipeline.md
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,7 @@ ADR-030 の `.failed` marker パターンを流用するが、L2 recovery (UserP
- **手動トリガー**: `/weekly-review` skill を明示呼出
- **reminder**: SessionStart hook が `.claude/weekly-review-last-run.json` の `last_run_at` を見て、7 日以上経過していれば `additionalContext` で促す (欠落/不正データは stale 扱いで発火、次回更新で移行。強制起動はしない)
- **staleness の情報源は `last_run_at` (内容 timestamp) のみとし、mtime は一切参照しない** (2026-07-01、[CR #233](https://github.com/aloekun/claude-code-hook-test/pull/233) Major で確定): mtime を情報源にすると、状態ファイルが jj checkout / `jj workspace add` ([ADR-045](adr-045-jj-workspace-parallel-sessions.md)) のたびに reset され、実際は数週間前の実行でも「fresh」に見えて reminder が永久に発火しない silent-fresh バグ (`past_time` / `reaper` と同クラス) を踏む。**欠落時に mtime へフォールバックすると legacy 経路で同バグを再導入する**ため、`last_run_at` が欠落/parse 不能/未来値の旧・破損データは `Stale` 扱い (= 発火) にし、次回 skill 実行で `last_run_at` を backfill する (self-healing、fail-open)。有効な過去値のみ `reaper::parse_iso8601_to_unix` + `past_time::PastTime` (未来値を型で reject) で経過日数を算出する。状態ファイルは `.gitignore` 済 (§ 出力)。
- **last-run 状態はメイン workspace を canonical とする** (2026-07-19、PR-N2 で確定): 上記 CR #233 は「mtime リセット」への対処だったが、`weekly-review-last-run.json` 自体が gitignore 済み untracked = **workspace ローカル**であるという盲点が残っていた ([ADR-045](adr-045-jj-workspace-parallel-sessions.md) 並列 workspace 運用)。前回実行 (2026-07-01) が secondary workspace (`claude-code-hook-test-improve`) 側で書かれ、メイン workspace には状態ファイルが存在せず、メイン側セッションは常に「未実行」判定で reminder を発火し続けた (約 4 週間、ユーザー気付かず。これが本 nudge 可視化改善の発端)。`weekly_review.rs` の doc「`last_run_at` は workspace 不変の値」は **値は checkout 不変だがファイル所在は workspace 依存**という点を取り違えていた。対処: SessionStart hook の last-run 読込を `lib_jj_helpers::resolve_main_workspace_root(cwd).unwrap_or(cwd)` でメイン workspace root に寄せ、secondary からもメイン側の実行記録を共有する (導出不能は現 root に fail-open)。skill (`weekly-review/SKILL.md` Step 5.3) の書込側も同じメイン root 解決を行い、読み書きの canonical を一致させる。**failed marker / pending JSON はレビュー成果物であり実行した workspace に属する**ため workspace ローカルのまま (状態の canonical 化は staleness timestamp のみに限定)。silent bug class としての一般化は [ADR-045 § gitignore 済み untracked 状態ファイルの workspace 分裂](adr-045-jj-workspace-parallel-sessions.md) を参照。
- **将来の自動化**: 機能安定後に schedule スキル (CronCreate-based) や `/loop 7d /weekly-review` を検討するが、MVP では実装しない (YAGNI、機能の安定性を観測してから判断)

### ADR-027 (push-time = simplicity 限定) との関係
Expand Down
22 changes: 21 additions & 1 deletion docs/adr/adr-045-jj-workspace-parallel-sessions.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## ステータス

試験運用 (2026-06-29) / 改訂 (2026-06-30: 初 PR 運用ケースで判明した secondary workspace の `.git` 不在 → gh ベースコマンドの `GIT_DIR` 必須、および merge-pipeline の bookmark 誤検出を「§ PR 運用時の追加設定」として追記) / 改訂 (2026-07-03: 恒久対策候補 1 = `GIT_DIR` 自動注入を実装。cli-* exe は手動 `GIT_DIR` 前置なしで動作するようになり、手動前置は直接 gh 呼び出し時の fallback に格下げ。PR #238 で `GH_REPO` による場当たり対処が部分故障を招いた実観測を受け `gh-repo-env-guard` preset も追加) / 改訂 (2026-07-13: **再評価 trigger #3 が発火** — 並列セッションの concurrent 操作と重なる時間帯に 2 コミット分の作業が消失する incident が発生 (PR #265 セッション、手動再構築で復旧)。「並行操作は jj が安全にマージする」という調整ポイント 4 の記述を jj 公式の並行モデルに即して是正し、「§ Known operational risks」「§ 並列運用の運用ルール」「§ Operation Verification Checklist」を新設)
試験運用 (2026-06-29) / 改訂 (2026-06-30: 初 PR 運用ケースで判明した secondary workspace の `.git` 不在 → gh ベースコマンドの `GIT_DIR` 必須、および merge-pipeline の bookmark 誤検出を「§ PR 運用時の追加設定」として追記) / 改訂 (2026-07-03: 恒久対策候補 1 = `GIT_DIR` 自動注入を実装。cli-* exe は手動 `GIT_DIR` 前置なしで動作するようになり、手動前置は直接 gh 呼び出し時の fallback に格下げ。PR #238 で `GH_REPO` による場当たり対処が部分故障を招いた実観測を受け `gh-repo-env-guard` preset も追加) / 改訂 (2026-07-13: **再評価 trigger #3 が発火** — 並列セッションの concurrent 操作と重なる時間帯に 2 コミット分の作業が消失する incident が発生 (PR #265 セッション、手動再構築で復旧)。「並行操作は jj が安全にマージする」という調整ポイント 4 の記述を jj 公式の並行モデルに即して是正し、「§ Known operational risks」「§ 並列運用の運用ルール」「§ Operation Verification Checklist」を新設) / 改訂 (2026-07-19: **gitignore 済み untracked 状態ファイルの workspace 分裂** を silent bug class として追記。weekly-review last-run ([ADR-031](adr-031-weekly-review-pipeline.md)) が secondary workspace 側にのみ存在しメイン側で永久「未実行」発火した実例で、mtime リセット (CR #233) と対になる。「§ gitignore 済み untracked 状態ファイルの workspace 分裂」を新設、PR-N2)

> ADR-039 (Experimental feature 標準パターン) に準拠: config opt-in なし (本 ADR は workflow 運用ポリシーであり実装機構ではないため該当しない) / kill-switch = 本 ADR を supersede する後続 ADR で停止可能、運用上は単一 workspace へ戻すだけで無効化 / bounded lifetime = 採用判定 3 ヶ月 (2026-09-29) を目安に dogfood 結果から本採用 / 修正 / 却下を判定。

Expand Down Expand Up @@ -39,6 +39,26 @@ git には worktree (同一リポジトリを汚染せず複数 working tree で
| `/target/` (Cargo workspace) | gitignore | workspace ごとに独立ビルド (disk コストのみ) |
| `.git` (colocated git ref) | **secondary workspace には存在しない** (`jj workspace add` は colocated 化しない、`.jj` のみ) | **gh ベースの `pnpm create-pr` / `pnpm merge-pr` / `cli-pr-monitor --monitor-only` は `GIT_DIR` 必須** (後述「§ PR 運用時の追加設定」)。`pnpm push` は `jj git push` backend のため不要 — この非対称が初回検証で見落とされた |

### gitignore 済み untracked 状態ファイルの workspace 分裂 (2026-07-19 追記、PR-N2)

上表の「state / lock / monitor は workspace ごとに独立」は、**ephemeral な per-session 状態** (`pr-monitor-state.json` / lock / `.session-id` / `feedback-reports/`) にとっては衝突回避の **利点**である。しかし **論理的にグローバルであるべき状態** を gitignore 済み untracked ファイルで持つと、同じ per-workspace 分離が **silent bug** に反転する。

**実観測 (2026-07-19)**: weekly-review の last-run timestamp (`.claude/weekly-review-last-run.json`、[ADR-031](adr-031-weekly-review-pipeline.md)) は前回実行 (2026-07-01) が secondary workspace (`claude-code-hook-test-improve`) 側で書かれ、メイン workspace には存在しなかった。SessionStart hook はメイン側で常に「未実行」判定となり、reminder が約 4 週間発火し続けた (ユーザーが気付かず、これが nudge 可視化改善 = PR-N1〜N3 の発端)。

これは [ADR-031](adr-031-weekly-review-pipeline.md) が対処した **mtime リセット** ([CR #233](https://github.com/aloekun/claude-code-hook-test/pull/233)) と **対になる silent bug class** である。両者とも「状態が fresh に見えて発火しない」形で表面化するが、原因が異なる:

| silent bug class | 原因 | 表面化 | 対処 |
|---|---|---|---|
| mtime リセット (CR #233) | 同一ファイルの mtime が checkout で変わる | 実際は数週間前でも fresh に見える | 内容 timestamp (`last_run_at`) のみを情報源にする |
| **workspace 分裂 (本追記)** | ファイル自体が workspace ごとに別物 (secondary に不在) | メイン側で永久「未実行」 | メイン workspace root に canonical 化 |

**判定基準**: gitignore 済み untracked 状態ファイルを追加するときは、その状態が **per-workspace であるべきか global であるべきか** を必ず区別する。

- **global** であるべき状態 → `lib_jj_helpers::resolve_main_workspace_root(cwd).unwrap_or(cwd)` でメイン workspace root に canonical 化して読み書きする (導出不能は現 root に fail-open)。PR-N2 で weekly-review last-run に適用。
- **per-workspace** であるべき状態 (レビュー成果物 = weekly-review report / `.failed` marker / pending JSON、monitor state、lock、session-id) → 「実行した workspace に属する」ため per-workspace のままが正しい。

`resolve_main_workspace_root` は `.jj/repo` の on-disk layout (secondary はメイン store への相対パスを格納したファイル、colocated main はディレクトリ) を辿ってメイン root を導出する。これは後述「§ PR 運用時の追加設定」で `GIT_DIR` を導出する `resolve_git_dir` と同じ layout 解釈を共有する姉妹関数 (最終 store ではなく workspace root を返す点が異なる)。

## 検討した選択肢

### 選択肢 A: 単一セッションで直列処理
Expand Down
18 changes: 18 additions & 0 deletions docs/weekly-review-notification-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,24 @@
- deploy 後、**ccht-improve workspace からセッション起動** → メイン側 last-run が読まれ、
systemMessage / additionalContext の経過日数が実日数になることを確認 (削除条件 3)。

### 作業記録 (2026-07-19 実装完了)

- **実装済み (main リポジトリ側)**。コミット粒度 (レビューしやすさ優先で 4 分割):
1. `feat(lib-jj-helpers)`: `resolve_main_workspace_root` 追加 (fixture + 実 jj E2E テストを `resolve_git_dir` パターンで流用)。この時点では caller なしで挙動不変。
2. `feat(session-start)`: hooks-session-start に lib-jj-helpers 依存追加 + `compute_weekly_review_reminder_nudge` の last-run 読込を `resolve_main_workspace_root(cwd).unwrap_or(cwd)` 基準に変更 (failed marker / pending JSON は workspace ローカル維持) + `weekly_review.rs` の「`last_run_at` は workspace 不変」誤記訂正 (値は checkout 不変だがファイル所在は workspace 依存) + `hooks_config.rs` の残存「mtime」記述訂正 (CR #233 drift) + secondary→main 読込の分割挙動テスト追加。
3. `docs(adr)`: ADR-031 § トリガー方式と reminder にメイン workspace canonical 化の決定を追記 + ADR-045 に「gitignore 済み untracked 状態ファイルの workspace 分裂」を silent bug class として新設 (mtime リセット CR #233 との対比表)。
4. `docs`: 本計画書に PR-N2 作業記録を反映 (本コミット)。
- **claude-code-skills リポジトリ (別 git repo)**: `weekly-review/SKILL.md` Step 5.3 を **メイン root 解決付き**に変更 (bash snippet で `.jj/repo` を辿って `$MAIN_ROOT` を導出、書込先を `$MAIN_ROOT/.claude/weekly-review-last-run.json` に) + frontmatter の SessionStart hook 経路説明を更新。**内容編集のみ実施し、git commit / `~/.claude/skills` への deploy は skills repo 側の PR/deploy ライフサイクルに委ねる** (本環境は git コマンドが hook で一律 block されるため、ユーザー確認済)。
- 検証結果:
- `cargo test --workspace`: 全 crate green (hooks-session-start **93 passed** = PR-N1 の 92 + 新規 secondary→main 分割テスト 1、lib-jj-helpers **37 passed + 実 jj E2E 2 passed**)。
- `cargo clippy --workspace --all-targets -- -D warnings`: クリーン。
- `pnpm build:all`: 成功 (更新 exe を `.claude/` に配布)。
- **デプロイ済み exe を secondary workspace レイアウトで駆動して end-to-end 確認済み**: `.jj/repo` がメイン store を指す fixture から exe を起動すると、メイン root の last-run (`2026-07-01`) を読んで additionalContext・systemMessage の経過日数が **「18 日経過」** になることを確認 (fix 前は secondary 自身の不在 last-run を見て「未実行」判定になっていた)。対照として `.jj/repo` 無し + last-run 不在のディレクトリからは現 root に fail-open して「未実行」/「実行記録なし」となり、導出不能時の安全動作も確認。
- **残タスク**:
- **運用ステップ (land 時に 1 回)**: `claude-code-hook-test-improve/.claude/weekly-review-last-run.json` (2026-07-01 実行記録) をメイン workspace の `.claude/` にコピーして実行履歴を救済する (未実施。land 前にメイン workspace で行うと reminder 表示が変わるため land 時に実施)。
- **削除条件 3**: land + deploy 後に **ccht-improve workspace から新セッションを起動**し、メイン側 last-run が読まれて経過日数が「未実行」ではなく実日数で表示されることを目視確認。
- claude-code-skills 側の SKILL.md 変更を skills repo で commit + `~/.claude/skills` へ deploy (skills repo ライフサイクル)。

---

## PR-N3: session-start nudge の telemetry 統合
Expand Down
1 change: 1 addition & 0 deletions src/hooks-session-start/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
toml = "0.8"
lib-subprocess = { path = "../lib-subprocess" }
lib-jj-helpers = { path = "../lib-jj-helpers" }

[dev-dependencies]
proptest = "1"
Expand Down
3 changes: 2 additions & 1 deletion src/hooks-session-start/src/hooks_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ pub(crate) struct StalenessConfig {
/// `enabled = false` では完全 skip (default-OFF in source、repo config で明示 enable する)。
///
/// 2 種類の reminder を発火:
/// - last-run staleness: `.claude/weekly-review-last-run.json` の mtime が
/// - last-run staleness: メイン workspace の `.claude/weekly-review-last-run.json` の
/// `last_run_at` (内容 timestamp。mtime ではない — CR #233 / ADR-045 PR-N2 で canonical 化) が
/// `reminder_threshold_days` を超えていれば「`/weekly-review` の実行を検討」を nudge
/// - failed marker: `.claude/weekly-reviews/*.md.failed` が 1 件以上存在すれば
/// 「前回 weekly-review が失敗、`/weekly-review` で resume」を nudge
Expand Down
Loading