Skip to content

feat(telemetry): cli-telemetry-report で月次ハーネス ROI レビュー集計を実装 (WP-12 step 2/3) - #330

Merged
aloekun merged 2 commits into
masterfrom
feat/cli-telemetry-report
Jul 29, 2026
Merged

feat(telemetry): cli-telemetry-report で月次ハーネス ROI レビュー集計を実装 (WP-12 step 2/3)#330
aloekun merged 2 commits into
masterfrom
feat/cli-telemetry-report

Conversation

@aloekun

@aloekun aloekun commented Jul 29, 2026

Copy link
Copy Markdown
Owner

概要

月次ハーネス ROI レビュー (WP-12 step 2/3、ADR-062) の決定論集計 exe cli-telemetry-report を実装。
各 workspace root の .claude/telemetry/firings-*.jsonl を横断集計し、月次 rollup を main workspace
側に確定、発火 0 の機構の非アクティブ化判定候補を提示するレポート (markdown + 機械可読 JSON) を
出力する L2 決定論層。自動で config を変更せず、採否は L3 skill (/monthly-review) の AskUserQuestion
を経る前提 (ADR-022/028)。docs/monthly-harness-roi-review-plan.mdPhase 1 (PR-2) に対応。

実装

新規 crate src/cli-telemetry-report/:

モジュール 責務
discover root 発見 (jj workspace list 動的列挙 + extra_roots) と degraded 判定
aggregate firing JSONL の月次集計・rollup 確定 (確定月不変 / 当月再計算)・retention 削除
snapshot config enabled + exe 配備の月次スナップショット
verdict 連続 N か月発火 0 + snapshot enabled ゲートによる判定候補、degraded で promote 抑止
incident custom-lint-rules.toml[rules.incident] から維持推奨ルールを導出
report (a) 月別×id別+前月比 (b) 発火0リスト (c) snapshot (d) incident マーク (e) 判定候補
timekit / model / config / main 時刻変換 / serde 型 / 設定 / orchestration

配線: workspace Cargo.toml members、package.json (build:cli-telemetry-report + build:all +
telemetry-report script)、.gitignore (monthly-reviews/ + monthly-review-last-run.json)、
.claude/hooks-config.toml[telemetry_report] で dogfood (retention_days=90 / zero_streak_months=2 /
leak 機構マッピング 1 件)。

設計上の決定 (プラン未指定箇所、Phase 4 で ADR-062 へ反映)

  • incident 由来ルールの真実源 = custom-lint-rules.toml[rules.incident] (id を exe 側に複製しない)。
  • degraded 追加条件: 「現 workspace 以外で root 未解決の workspace 数 > 到達可能な extra_roots 数」でも
    degraded とする。現 workspace 自身の self.root() 解決失敗は現 root (exe 隣接 .claude の親) で補うため
    degraded にしない。この環境の ccht-improve は jj 格納パス不整合で self.root() 解決不能なため、
    main から実行すると degraded → promote 抑止となる (leak 偏在の誤 promote 防止として正しい挙動)。
  • root 発見テンプレートは --ignore-working-copy で read-only 化し current_working_copy() で現 workspace 判定。
  • retention は ADR-039 opt-in (retention_days 未設定で削除無効)。

検証

  • cli-telemetry-report: 48 unit テスト pass (月跨ぎ集計 / 前月比 / 連続0判定 / rollup 確定月不変 /
    retention 境界 / 壊れ行 skip / snapshot を網羅)。
  • quality gate 全 PASS: pnpm lint / pnpm test / pnpm build / cargo clippy --workspace --all-targets -- -D warnings / cargo test (通常 + --ignored)。
  • 実データ実測: improve から実行 → main+improve 2 root 横断、degraded なし。hooks-stop-tool-call-leak
    = 13 block、.../prompt-recovery = 2 warn の内訳分離を確認。main から見ると improve が未解決 → degraded
    抑止も裏取り済み。
  • takt pre-push-review: security-review / simplicity-review 両 APPROVE

非ブロッキング follow-up (simplicity-review 指摘、本 PR では未対応)

  1. discover.rs — degraded 判定が workspace 名でなく件数比較 (unresolved_non_current > reachable_extra.len())
    のため、degraded メッセージが解決失敗 workspace 名を示さない + 件数一致で誤って degraded 抑止し得る。
    設計上の既知の割り切り (最終ゲートは必ずユーザー承認、ADR-022/028)。Phase 3 の skill 実装時に identity
    マッチングへ改善を検討。
  2. timekit.rs — Howard Hinnant civil-date アルゴリズムの 3 例目の複製 (lib-telemetry / cli-takt-timings と)。
    ADR-044 の「3 crate 目で中立 crate へ抽出」トリガに到達。別 DRY PR での抽出候補。

補足

  • PR diff 2523 行 (> block_threshold 1500) は新規 crate 1 本 + 網羅テストによる想定内サイズで、crate は
    モジュール相互依存のため分割すると中間状態がビルド不能。PR_SIZE_CHECK_OVERRIDE=1 で意図的に単一 PR とした
    (プランが Phase 1 を単一 PR-2 としてスコープ)。閾値の恒久変更はしていない。
  • 本 PR はコード + 配線 + プラン進捗更新のみ。ADR-062 / ADR-055・053・061 追記・reminder・skill は
    Phase 2〜4 の後続 PR。

🤖 Generated with Claude Code

Summary by CodeRabbit

  • 新機能

    • 複数ワークスペースのテレメトリを月次集計し、MarkdownおよびJSON形式のレビュー reports を生成できるようになりました。
    • 発火状況、設定・配備状態、異常情報をもとに、機構の非アクティブ化候補を提示します。
    • 集計期間、保持期間、判定閾値などを設定できるようになりました。
    • degraded 状態では候補の提示を自動的に抑制します。
  • ドキュメント

    • 月次ハーネス ROI レビュー計画の進捗と判定仕様を更新しました。

…ep 2/3)

各 workspace root の firings-*.jsonl を横断集計し月次 rollup を main workspace 側に確定、
発火 0 の機構の非アクティブ化判定候補を提示するレポート (md + JSON) を出力する決定論 L2 exe。
root 発見が不完全 (jj workspace list 失敗 / 現 workspace 以外の未解決 / extra_roots 到達不能) な
実行では判定候補の promote を抑止する (発見漏れ + 発火 0 の誤 promote 防止)。retention は
ADR-039 opt-in (retention_days 未設定で削除無効)。自動で config を変更せず、採否は L3 skill の
AskUserQuestion を経る前提 (ADR-022/028)。

- 新規 crate src/cli-telemetry-report (discover/aggregate/snapshot/verdict/incident/report/
  timekit/model/config/main)
- workspace members / package.json (build:cli-telemetry-report + build:all + telemetry-report
  script) / .gitignore (monthly-reviews/ + monthly-review-last-run.json) 配線
- hooks-config.toml [telemetry_report] で dogfood (retention_days=90 / zero_streak_months=2 /
  leak 機構マッピング 1 件)
- 48 unit テスト + 実データ実測 (main+improve 横断、leak block/warn 内訳分離、degraded 抑止)
- docs/monthly-harness-roi-review-plan.md Phase 1 を完了記録に更新

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 80d44a9e-feb5-453b-b399-3020ac1edac4

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

月次テレメトリ設定、複数 workspace の firing 集計、rollup 保存、retention、機構状態判定、Promote 候補、Markdown/JSON レポートを生成する Rust CLI と関連するビルド・文書・ignore 設定が追加されました。

Changes

月次テレメトリ ROI レポート

Layer / File(s) Summary
設定とデータ契約
.claude/hooks-config.toml, src/cli-telemetry-report/src/{Cargo.toml,model.rs,config.rs,timekit.rs,incident.rs}
telemetry_report の設定、月次 rollup/集計モデル、時刻変換、incident ルール ID 抽出を追加しました。
root 発見と月次 rollup
src/cli-telemetry-report/src/{discover.rs,aggregate.rs}
workspace root を発見し、各 root の firing JSONL を集計、rollup を確定・保存し、設定時のみ古い firing を削除します。
機構状態と判定候補
src/cli-telemetry-report/src/{snapshot.rs,verdict.rs}
設定キーと executable の状態を snapshot 化し、連続ゼロ発火月数から PromoteNotMetSuppressed を判定します。
CLI とレポート出力
src/cli-telemetry-report/src/{main.rs,report.rs}, Cargo.toml, package.json, .gitignore, docs/monthly-harness-roi-review-plan.md
集計から判定、Markdown/JSON 出力までの CLI フローを追加し、workspace ビルド、実行スクリプト、成果物 ignore、実装計画を更新しました。

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CLI
  participant WorkspaceDiscovery
  participant TelemetryAggregator
  participant VerdictEngine
  participant ReportWriter
  CLI->>WorkspaceDiscovery: root を発見する
  CLI->>TelemetryAggregator: firing JSONL を集計し rollup を更新する
  TelemetryAggregator-->>CLI: 月次 rollup を返す
  CLI->>VerdictEngine: snapshot と rollup から verdict を計算する
  VerdictEngine-->>CLI: Promote/NotMet/Suppressed を返す
  CLI->>ReportWriter: Markdown/JSON レポートを書き出す
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 新規クレート cli-telemetry-report による月次ハーネス ROI レビュー集計の実装を正しく要約しており、変更内容と一致しています。
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/cli-telemetry-report

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown
Contributor

🤖 PR Monitor 分析 (GitHub Actions バックストップ)

  • トリガー: issue_comment (created) / 実行 run
  • CI: CodeRabbit レビューが in progress (pending)。他に報告されている required/status check なし
  • レビュー状況: CodeRabbit はレビュー実施前の「処理中」プレースホルダーコメントのみ投稿済み (指摘なし)。人間レビューは未提出 (reviews 0 件、reviewDecision 未設定)。インラインコメントなし
  • Verdict: user_decision (レビュー未着のため判定保留。指摘が出揃うまで待機)

Applicable Findings (Critical / High / Major)

該当なし (レビュー指摘がまだ存在しない)

Applicable Findings (Medium 以下)

該当なし

Filtered (not applicable)

該当なし

次のアクション

  • CodeRabbit のレビュー完了を待ち、指摘が投稿され次第あらためて分析する (本セッションでは待機・ポーリングしない)。
  • 軽量サマリー: 変更 17 ファイル (+2520/-3行)。新規 crate src/cli-telemetry-report/ (aggregate/config/discover/incident/main/model/report/snapshot/timekit/verdict の10ファイル) を追加し、Cargo.toml / package.json / .gitignore / .claude/hooks-config.toml に配線変更、docs/monthly-harness-roi-review-plan.md に実施結果を追記。ADR-062 (試験運用) に基づく月次ハーネス ROI レビュー集計機能の実装 (WP-12 step 2/3)。
  • mergeStateStatus: UNSTABLE の要因 (承認未取得等) を人間側で確認するのが望ましい。

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.claude/hooks-config.toml:
- Around line 371-380: 設定内の telemetry_report テーブル再定義を削除し、[telemetry_report]
ヘッダーを1回だけ残してください。あわせて、空の [[telemetry_report.mechanisms]] 宣言を削除し、必須フィールドを持つ
mechanism エントリだけを残して parse_config が retention_days や mechanism
判定を読み込める状態にしてください。

In `@src/cli-telemetry-report/src/aggregate.rs`:
- Around line 166-194: Update resolve_month so the past-month branch for empty
raw_counts and existing non-empty ids does not return None when the existing
rollup is not finalized; instead, return a preserved copy of that MonthRollup
with finalized set to true. Keep the existing rollup data and metadata
unchanged, while retaining the current behavior for already-finalized months and
recalculated months.

In `@src/cli-telemetry-report/src/discover.rs`:
- Around line 241-255: Update
combine_roots_current_workspace_error_is_not_degraded to exercise
discover_roots, including an unresolved current-workspace root, rather than
passing unresolved_non_current as zero directly to combine_roots. Assert that
discover_roots filters the current-workspace failure out while preserving the
expected non-degraded result, thereby testing the behavior implemented by the
discover_roots filter.

In `@src/cli-telemetry-report/src/timekit.rs`:
- Around line 62-70: Update date_str_to_day to validate the exact YYYY-MM-DD
format, including fixed length, separator positions, and zero-padded numeric
components, then reject days exceeding the selected month’s maximum while
accounting for leap years before calling days_from_civil. Preserve returning
None for every invalid date and the existing conversion for valid dates.

In `@src/cli-telemetry-report/src/verdict.rs`:
- Around line 78-97: Update trailing_zero_streak to verify that each rollup
month is the immediately preceding calendar month before incrementing streak,
rather than relying only on reverse array order. Use the existing
proleptic-Gregorian day conversion utilities from timekit.rs to calculate and
compare adjacent month keys, and stop the streak at any gap while preserving the
current qualification and partial-month handling.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: aee9af92-5ed7-4e14-b943-00fa6320f7a0

📥 Commits

Reviewing files that changed from the base of the PR and between d81f431 and 5b33f22.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (16)
  • .claude/hooks-config.toml
  • .gitignore
  • Cargo.toml
  • docs/monthly-harness-roi-review-plan.md
  • package.json
  • src/cli-telemetry-report/Cargo.toml
  • src/cli-telemetry-report/src/aggregate.rs
  • src/cli-telemetry-report/src/config.rs
  • src/cli-telemetry-report/src/discover.rs
  • src/cli-telemetry-report/src/incident.rs
  • src/cli-telemetry-report/src/main.rs
  • src/cli-telemetry-report/src/model.rs
  • src/cli-telemetry-report/src/report.rs
  • src/cli-telemetry-report/src/snapshot.rs
  • src/cli-telemetry-report/src/timekit.rs
  • src/cli-telemetry-report/src/verdict.rs

Comment thread .claude/hooks-config.toml
Comment on lines +371 to +380
[telemetry_report]
retention_days = 90 # 90 日超過の raw firing partition を削除 (rollup 確定後のため判定に影響なし)
zero_streak_months = 2 # 連続 2 か月発火 0 で非アクティブ化候補として promote (ユーザー決定事項 1)
trend_months = 6 # レポートが遡る月数 (月別表 / 発火 0 リストの窓)
# extra_roots = ["C:\\path\\to\\other-workspace"] # 環境ごとに必要なら有効化

# 初期マッピング 1 件: ADR-053/061 の tool call leak 検知。監視 2 id が連続発火 0 かつ各月
# enabled+配備済みなら非アクティブ化候補として提示する (crate 撤去 revert PR は ADR-053/061
# bounded lifetime 手順に従う)。全試験運用 ADR の網羅登録は将来拡張。
[[telemetry_report.mechanisms]]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

重複した TOML テーブル宣言を削除してください。

[telemetry_report] の再定義で TOML パースが失敗します。これを一つにしても、重複した [[telemetry_report.mechanisms]] は必須フィールドのない空要素を作るため、parse_config がデフォルトへフォールバックします。結果として retention と mechanism 判定が無効になります。各ヘッダーを1回だけ残してください。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.claude/hooks-config.toml around lines 371 - 380, 設定内の telemetry_report
テーブル再定義を削除し、[telemetry_report] ヘッダーを1回だけ残してください。あわせて、空の
[[telemetry_report.mechanisms]] 宣言を削除し、必須フィールドを持つ mechanism エントリだけを残して
parse_config が retention_days や mechanism 判定を読み込める状態にしてください。

Comment on lines +166 to +194
/// 1 月の rollup をどう扱うか決める。上書きが必要なら新 [`MonthRollup`]、据え置きなら `None`。
///
/// 確定月 (当月でない `finalized`) は据え置き。raw が空でも既存が非空の過去月は据え置き
/// (retention 削除後の維持)。それ以外は raw で確定/再計算する。
fn resolve_month(
by_month: &BTreeMap<String, MonthRollup>,
raw_counts: &BTreeMap<String, MonthCounts>,
month: &str,
current_month: &str,
snapshot: &Snapshot,
now_iso: &str,
) -> Option<MonthRollup> {
let is_current = month == current_month;
let prev = by_month.get(month);
if prev.is_some_and(|r| r.finalized) && !is_current {
return None;
}
let ids = raw_counts.get(month).cloned().unwrap_or_default();
if ids.is_empty() && !is_current && prev.is_some_and(|r| !r.ids.is_empty()) {
return None;
}
Some(MonthRollup {
month: month.to_string(),
finalized: !is_current,
ids,
snapshot: snapshot.clone(),
generated_at: now_iso.to_string(),
})
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

resolve_month が過去月の finalized を永久に false のまま固定してしまう可能性。

184-186 の分岐は「raw が空でも既存の非空データがある過去月は据え置き」を意図していますが、「据え置き」は既存 MonthRollupそのまま返す (None) ため、その月がまだ finalized: false だった場合 (直前の実行では current_month だったが、今回は過去月に遷移し、かつ raw がその間に消えた場合)、finalized は決して true にならず不変条件「既存の確定月は不変」を満たせなくなります。データ自体 (ids) は保持されますが、メタデータとしての確定状態が壊れたままになり、これに依存する将来のレポート/検証ロジックを誤解させる可能性があります。

🛠️ 修正例: 据え置きでも `finalized: true` に補正する
     let ids = raw_counts.get(month).cloned().unwrap_or_default();
     if ids.is_empty() && !is_current && prev.is_some_and(|r| !r.ids.is_empty()) {
-        return None;
+        let existing = prev.expect("checked above via is_some_and");
+        return Some(MonthRollup {
+            month: month.to_string(),
+            finalized: true,
+            ids: existing.ids.clone(),
+            snapshot: existing.snapshot.clone(),
+            generated_at: existing.generated_at.clone(),
+        });
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/// 1 月の rollup をどう扱うか決める。上書きが必要なら新 [`MonthRollup`]、据え置きなら `None`。
///
/// 確定月 (当月でない `finalized`) は据え置き。raw が空でも既存が非空の過去月は据え置き
/// (retention 削除後の維持)。それ以外は raw で確定/再計算する。
fn resolve_month(
by_month: &BTreeMap<String, MonthRollup>,
raw_counts: &BTreeMap<String, MonthCounts>,
month: &str,
current_month: &str,
snapshot: &Snapshot,
now_iso: &str,
) -> Option<MonthRollup> {
let is_current = month == current_month;
let prev = by_month.get(month);
if prev.is_some_and(|r| r.finalized) && !is_current {
return None;
}
let ids = raw_counts.get(month).cloned().unwrap_or_default();
if ids.is_empty() && !is_current && prev.is_some_and(|r| !r.ids.is_empty()) {
return None;
}
Some(MonthRollup {
month: month.to_string(),
finalized: !is_current,
ids,
snapshot: snapshot.clone(),
generated_at: now_iso.to_string(),
})
}
/// 1 月の rollup をどう扱うか決める。上書きが必要なら新 [`MonthRollup`]、据え置きなら `None`。
///
/// 確定月 (当月でない `finalized`) は据え置き。raw が空でも既存が非空の過去月は据え置き
/// (retention 削除後の維持)。それ以外は raw で確定/再計算する。
fn resolve_month(
by_month: &BTreeMap<String, MonthRollup>,
raw_counts: &BTreeMap<String, MonthCounts>,
month: &str,
current_month: &str,
snapshot: &Snapshot,
now_iso: &str,
) -> Option<MonthRollup> {
let is_current = month == current_month;
let prev = by_month.get(month);
if prev.is_some_and(|r| r.finalized) && !is_current {
return None;
}
let ids = raw_counts.get(month).cloned().unwrap_or_default();
if ids.is_empty() && !is_current && prev.is_some_and(|r| !r.ids.is_empty()) {
let existing = prev.expect("checked above via is_some_and");
return Some(MonthRollup {
month: month.to_string(),
finalized: true,
ids: existing.ids.clone(),
snapshot: existing.snapshot.clone(),
generated_at: existing.generated_at.clone(),
});
}
Some(MonthRollup {
month: month.to_string(),
finalized: !is_current,
ids,
snapshot: snapshot.clone(),
generated_at: now_iso.to_string(),
})
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/cli-telemetry-report/src/aggregate.rs` around lines 166 - 194, Update
resolve_month so the past-month branch for empty raw_counts and existing
non-empty ids does not return None when the existing rollup is not finalized;
instead, return a preserved copy of that MonthRollup with finalized set to true.
Keep the existing rollup data and metadata unchanged, while retaining the
current behavior for already-finalized months and recalculated months.

Comment on lines +241 to +255
#[test]
fn combine_roots_current_workspace_error_is_not_degraded() {
let (_, degraded) = combine_roots(
PathBuf::from("/cur"),
vec![PathBuf::from("/main")],
0,
true,
Vec::new(),
&[],
);
assert!(
degraded.is_empty(),
"現 workspace の root 解決失敗は unresolved_non_current に数えないため degraded にならない"
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

テストが実際の対象ロジックを検証していない。

combine_roots_current_workspace_error_is_not_degradedcombine_roots_complete_when_all_resolved (214-226) と全く同じ引数 (unresolved_non_current: 0 を直接渡す) で呼び出しており、「現 workspace の root 未解決は unresolved_non_current に数えない」という不変条件は discover_roots (123-126) 側のフィルタで実現されているにも関わらず、このテストはその関数をまったく経由していません。名前が示す振る舞いは未検証のままです。

✅ 修正例: `discover_roots` のフィルタ自体を検証する
-    #[test]
-    fn combine_roots_current_workspace_error_is_not_degraded() {
-        let (_, degraded) = combine_roots(
-            PathBuf::from("/cur"),
-            vec![PathBuf::from("/main")],
-            0,
-            true,
-            Vec::new(),
-            &[],
-        );
-        assert!(
-            degraded.is_empty(),
-            "現 workspace の root 解決失敗は unresolved_non_current に数えないため degraded にならない"
-        );
-    }
+    #[test]
+    fn parse_workspace_list_current_error_excluded_from_unresolved() {
+        let parsed = parse_workspace_list("cur\ttrue\t<Error: x>\nother\tfalse\t/other\n");
+        let unresolved_non_current = parsed
+            .iter()
+            .filter(|w| w.root.is_none() && !w.is_current)
+            .count();
+        assert_eq!(unresolved_non_current, 0, "現 workspace の未解決は数えない");
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#[test]
fn combine_roots_current_workspace_error_is_not_degraded() {
let (_, degraded) = combine_roots(
PathBuf::from("/cur"),
vec![PathBuf::from("/main")],
0,
true,
Vec::new(),
&[],
);
assert!(
degraded.is_empty(),
"現 workspace の root 解決失敗は unresolved_non_current に数えないため degraded にならない"
);
}
#[test]
fn parse_workspace_list_current_error_excluded_from_unresolved() {
let parsed = parse_workspace_list("cur\ttrue\t<Error: x>\nother\tfalse\t/other\n");
let unresolved_non_current = parsed
.iter()
.filter(|w| w.root.is_none() && !w.is_current)
.count();
assert_eq!(unresolved_non_current, 0, "現 workspace の未解決は数えない");
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/cli-telemetry-report/src/discover.rs` around lines 241 - 255, Update
combine_roots_current_workspace_error_is_not_degraded to exercise
discover_roots, including an unresolved current-workspace root, rather than
passing unresolved_non_current as zero directly to combine_roots. Assert that
discover_roots filters the current-workspace failure out while preserving the
expected non-degraded result, thereby testing the behavior implemented by the
discover_roots filter.

Comment thread src/cli-telemetry-report/src/timekit.rs Outdated
Comment on lines +62 to +70
pub fn date_str_to_day(date: &str) -> Option<i64> {
let mut it = date.split('-');
let y: i64 = it.next()?.parse().ok()?;
let m: i64 = it.next()?.parse().ok()?;
let d: i64 = it.next()?.parse().ok()?;
if it.next().is_some() || !(1..=12).contains(&m) || !(1..=31).contains(&d) {
return None;
}
Some(days_from_civil(y, m, d))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

暦日として不正な日付を拒否してください。

2026-02-31 と非ゼロ埋めの 2026-1-1 が受理されます。壊れた partition 名がここを通ると retention の削除日が誤計算されます。長さ・区切り位置・月ごとの最大日数(うるう年を含む)を検証してください。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/cli-telemetry-report/src/timekit.rs` around lines 62 - 70, Update
date_str_to_day to validate the exact YYYY-MM-DD format, including fixed length,
separator positions, and zero-padded numeric components, then reject days
exceeding the selected month’s maximum while accounting for leap years before
calling days_from_civil. Preserve returning None for every invalid date and the
existing conversion for valid dates.

Comment on lines +78 to +97
/// 最新月から遡り「全監視 id が発火 0 かつ snapshot が enabled+配備済み」の連続月数を数える。
/// 返り値は (連続月数, 連続列先頭が未確定当月か)。
fn trailing_zero_streak(
rollups: &[MonthRollup],
mechanism: &MechanismConfig,
current_month: &str,
) -> (u64, bool) {
let mut streak = 0u64;
let mut partial = false;
for (i, rollup) in rollups.iter().rev().enumerate() {
if !month_qualifies(rollup, mechanism) {
break;
}
if i == 0 && rollup.month == current_month && !rollup.finalized {
partial = true;
}
streak += 1;
}
(streak, partial)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
ast-grep run --pattern 'fn $NAME($$$) -> $RET { $$$ }' --lang rust src/cli-telemetry-report/src/timekit.rs
rg -n 'fn ' src/cli-telemetry-report/src/timekit.rs

Repository: aloekun/claude-code-hook-test

Length of output: 2634


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '--- files ---\n'
git ls-files src/cli-telemetry-report/src | sort

printf '\n--- verdict outline ---\n'
ast-grep outline src/cli-telemetry-report/src/verdict.rs --view expanded || true

printf '\n--- verdict relevant lines ---\n'
cat -n src/cli-telemetry-report/src/verdict.rs | sed -n '1,140p'

printf '\n--- timekit relevant lines ---\n'
cat -n src/cli-telemetry-report/src/timekit.rs | sed -n '1,120p'

Repository: aloekun/claude-code-hook-test

Length of output: 258


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- files ---'
git ls-files src/cli-telemetry-report/src | sort

echo
echo '--- verdict outline ---'
ast-grep outline src/cli-telemetry-report/src/verdict.rs --view expanded || true

echo
echo '--- verdict relevant lines ---'
cat -n src/cli-telemetry-report/src/verdict.rs | sed -n '1,140p'

echo
echo '--- timekit relevant lines ---'
cat -n src/cli-telemetry-report/src/timekit.rs | sed -n '1,120p'

Repository: aloekun/claude-code-hook-test

Length of output: 11963


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- aggregate relevant lines ---'
cat -n src/cli-telemetry-report/src/aggregate.rs | sed -n '120,180p'

echo
echo '--- model MonthRollup / DecisionCounts ---'
cat -n src/cli-telemetry-report/src/model.rs | sed -n '1,220p'

echo
echo '--- tests relevant lines ---'
cat -n src/cli-telemetry-report/src/verdict.rs | sed -n '140,270p'

Repository: aloekun/claude-code-hook-test

Length of output: 10521


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- aggregate earlier/merge ---'
cat -n src/cli-telemetry-report/src/aggregate.rs | sed -n '1,130p'

Repository: aloekun/claude-code-hook-test

Length of output: 5672


🌐 Web query:

Howard Hinnant civil_from_days days_from_civil date algorithms

💡 Result:

Howard Hinnant's date algorithms, specifically days_from_civil and civil_from_days, are highly efficient, low-level C++ functions designed for converting between a Gregorian calendar date (year, month, day) and a serial day count (often representing days since January 1, 1970) [1][2]. These algorithms are notable for their ability to handle dates over a vast range, their performance, and their suitability for compile-time evaluation (constexpr) [3][4]. days_from_civil converts a {year, month, day} triple into a serial count of days [1]. It uses a mathematical approach involving the calculation of an "era" (a 400-year cycle in the Gregorian calendar) and "day of era" (doe) to transform the date into a continuous count, offset by 719468 to align with the Unix epoch [1][2][4]. civil_from_days is the inverse operation, converting a serial day count back into a {year, month, day} triple using a similar mathematical derivation based on Gregorian calendar rules [1][2]. These algorithms serve as the technical foundation for Hinnant's widely used "date" C++ library [5][6] and were eventually incorporated into the C++20 standard library's header [4]. They are intended for use where high performance and type safety are required, often replacing traditional C-style date manipulation techniques [3][6]. The full source code for these algorithms is maintained in Hinnant's public technical documentation and his date library repository [1][6]. They include static assertions to ensure the integer types used are sufficiently large (e.g., at least 18 bits for unsigned and 20 bits for signed integers) to prevent overflow or porting issues [1][2].

Citations:


trailing_zero_streak で実際月の連続性を検証して streak を数えてください。

aggregate.rs::finalize_rollupsexisting 内の確定 rollup をそのまま保持するため、特定の機構 id 以外が全く発火しなかった月など「その機構の raw に落ちない月」がある場合、rollups 上で隣接する月が実際には暦月で連続していないことがあります。trailing_zero_streak は配列順だけでカウントするため、ギャップを飛び越しながら zero_streak を積み上げ、実際には継続して条件を満たさないのに promote 基準を満たす判定が出かねません。timekit.rs には proleptic Gregorian の日数変換器があるため、直前キーの算出・比較などで streak += 1 前に連続性の断絶を切ってください。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/cli-telemetry-report/src/verdict.rs` around lines 78 - 97, Update
trailing_zero_streak to verify that each rollup month is the immediately
preceding calendar month before incrementing streak, rather than relying only on
reverse array order. Use the existing proleptic-Gregorian day conversion
utilities from timekit.rs to calculate and compare adjacent month keys, and stop
the streak at any gap while preserving the current qualification and
partial-month handling.

@github-actions

Copy link
Copy Markdown
Contributor

🤖 PR Monitor 分析 (GitHub Actions バックストップ)

  • トリガー: pull_request_review (submitted) / 実行 run
  • CI: analyze(本ワークフロー自身) が in-progress なのみ。他の required/status check は無し。CodeRabbit チェックは pass(Review completed)
  • レビュー状況: CodeRabbit が COMMENTED レビューを提出済み (2026-07-29T12:23:37Z、actionable comments 5件)。人間レビューは未提出 (reviewDecision 未設定)。前回の本ワークフロー分析コメント (2026-07-29T12:10:03Z) はこのレビュー提出の暫定サマリーだったため、今回新たに出揃った指摘を分析
  • Verdict: needs_fix (Major の applicable 指摘が2件存在)

Applicable Findings (Critical / High / Major)

# File (Line) Reviewer Issue Recommended Action
1 src/cli-telemetry-report/src/aggregate.rs:194 CodeRabbit resolve_month で「raw が空 && 過去月 && 既存 ids 非空」の据え置き分岐が既存 rollup をそのまま None 扱いにするため、その月がまだ finalized: false だった場合に永久に確定しない (不変条件「確定月は不変」が壊れる) 据え置き時も既存データを保持したまま finalized: true にした MonthRollup を返すよう修正 (CodeRabbit の diff 提案どおり)
2 src/cli-telemetry-report/src/verdict.rs:97 CodeRabbit trailing_zero_streakrollups 配列の逆順走査のみで streak を数えており、月キーが暦月として連続しているかを検証していない。ある月に全機構で発火が0件だと union_months にその月キー自体が現れずギャップが生じ得るため、streak が実際の連続性を無視して積み上がり誤 promote の恐れ timekit.rs の日数変換 (date_str_to_day/days_from_civil) を使い隣接月キーの連続性を確認してから streak += 1 する

Applicable Findings (Medium 以下)

# File (Line) Reviewer Issue Recommended Action
3 src/cli-telemetry-report/src/timekit.rs:70 CodeRabbit date_str_to_day2026-02-31 や非ゼロ埋めの 2026-1-1 を受理する (長さ・区切り位置・月ごとの最大日数/うるう年を未検証)。壊れた partition ファイル名が通ると retention の削除日計算を誤る フォーマット厳格化 (固定長・区切り位置・ゼロ埋め) と月ごとの最大日数チェックを追加
4 src/cli-telemetry-report/src/discover.rs:255 CodeRabbit combine_roots_current_workspace_error_is_not_degraded テストが combine_roots_complete_when_all_resolved と全く同じ引数 (unresolved_non_current: 0 を直接渡す) で呼んでおり、「現 workspace の root 未解決は数えない」というフィルタは discover_roots 側の実装なのに未経由。テスト名が示す挙動が実質未検証 discover_roots/parse_workspace_list を実際に経由するテストに置き換え (CodeRabbit の diff 提案どおり)

Filtered (not applicable)

# File (Line) Issue Filter Reason
5 .claude/hooks-config.toml:371-380 [telemetry_report] テーブルと [[telemetry_report.mechanisms]] が重複宣言され TOML パースが失敗する」との指摘 False positive — 実際の diff (gh pr diff、PR head commit 5b33f22 と一致) を確認したところ [telemetry_report] ヘッダーは1回、[[telemetry_report.mechanisms]] も1回のみで重複は存在しない。CodeRabbit の誤読と判断

次のアクション

  1. Major 2件 (aggregate.rs の finalized 据え置きバグ、verdict.rs の暦月連続性検証漏れ) は次の fix イテレーションで対応。いずれも src/ 配下でセンシティブファイル保護の対象外のため自動修正可能
  2. Medium/Minor 2件 (timekit.rs の日付バリデーション、discover.rs のテスト実効性) も併せて修正を推奨
  3. hooks-config.toml の重複指摘は false positive と判断したため対応不要。ただし人間レビュアーが mergeStateStatus: UNSTABLE の要因 (承認未取得等) を別途確認するのが望ましい

PR #330 の CodeRabbit 指摘に対応。Major 2 件は post-pr-review 自動修正、Minor 2 件は追加対応。

- verdict.rs (Major): `trailing_zero_streak` が暦月連続性を検証せず、rollup に月の欠落
  (例 [2026-05, 2026-06, 2026-08]) があると非連続月を 1 streak と誤カウントし false promote
  を招く。直前に数えた月のちょうど 1 か月前であることを要求し欠落で streak を打ち切る
  (`streak_continues` / `is_month_before` / `parse_year_month`)。回帰テスト `gap_month_breaks_streak`。
- aggregate.rs (Major): `resolve_month` が、当月を過ぎた月の raw が retention で削除済みの場合に
  early-return で据え置き、`finalized` が永久に false のまま固定される。過去月は必ず確定させ、
  raw があれば raw、無ければ既存 rollup の ids を維持する (`ids_without_raw`)。回帰テスト
  `finalize_rollups_finalizes_stale_unfinalized_month_when_raw_is_gone`。
- timekit.rs (Minor): `date_str_to_day` が暦日として不正な日付 (2026-02-30 / 非うるう年の
  2026-02-29 等) を受理していた。`civil_from_days` で round-trip し正準でない日付を拒否
  (月ごとの日数・うるう年をハードコードしない)。テスト `date_str_to_day_rejects_non_canonical_dates`。
- discover.rs (Minor): 現 workspace 除外ロジックを純粋関数 `count_unresolved_non_current` に抽出し、
  現 workspace の root 解決失敗が unresolved_non_current に数えられないことを直接検証するテスト
  `count_unresolved_non_current_excludes_current_workspace_error` を追加。

hooks-config.toml:380 (Major) は false positive として不採用: `[telemetry_report]` +
`[[telemetry_report.mechanisms]]` は既存の `[stop_quality]` + `[[stop_quality.steps]]` と同じ
正当な TOML array-of-tables であり重複ではない (exe も正常パース)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@aloekun

aloekun commented Jul 29, 2026

Copy link
Copy Markdown
Owner Author

作業完了報告

CodeRabbit の指摘 5 件に対応し、fix コミット (4692565e) を push 済みです。

指摘への対応内訳

# File (Line) Severity 対応
1 src/cli-telemetry-report/src/verdict.rs:97 Major ✅ 修正 — trailing_zero_streak に暦月連続性チェックを追加(月の欠落で streak を打ち切り、非連続月を連続と誤カウントする false promote を防止)。回帰テスト gap_month_breaks_streak 追加
2 src/cli-telemetry-report/src/aggregate.rs:194 Major ✅ 修正 — resolve_month が retention 削除後に過去月の finalized を永久 false で固定するバグを解消(過去月は必ず確定、raw 消失時は既存 rollup の ids を維持)。回帰テスト finalize_rollups_finalizes_stale_unfinalized_month_when_raw_is_gone 追加
3 src/cli-telemetry-report/src/timekit.rs:70 Minor ✅ 修正 — date_str_to_daycivil_from_days の round-trip で正準でない暦日(2026-02-30 / 非うるう年の 2026-02-29 等)を拒否。テスト date_str_to_day_rejects_non_canonical_dates 追加
4 src/cli-telemetry-report/src/discover.rs:255 Minor ✅ 修正 — 現 workspace 除外ロジックを純粋関数 count_unresolved_non_current に抽出し、現 workspace の root 解決失敗が unresolved_non_current に数えられないことを直接検証するテストを追加
5 .claude/hooks-config.toml:380 Major ❌ 不採用(false positive)— [telemetry_report] + [[telemetry_report.mechanisms]] は同ファイルの [stop_quality] + [[stop_quality.steps]] と同じ正当な TOML array-of-tables であり、重複したテーブル宣言ではありません(exe も正常にパース)。

検証

  • cargo test -p cli-telemetry-report52 passed / 0 failed(回帰テスト 2 件込み)
  • cargo clippy --workspace --all-targets -- -D warnings — クリーン
  • pre-push レビュー(takt)— security-review / simplicity-review 両 APPROVE

Windows-only プロジェクトの決定論ツール(WP-12 step 2/3、ADR-062)で、対象は src/cli-telemetry-report/ に限定されています。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant