feat(telemetry): cli-telemetry-report で月次ハーネス ROI レビュー集計を実装 (WP-12 step 2/3) - #330
Conversation
…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>
|
Important Review skippedAuto incremental reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthrough月次テレメトリ設定、複数 workspace の firing 集計、rollup 保存、retention、機構状態判定、Promote 候補、Markdown/JSON レポートを生成する Rust CLI と関連するビルド・文書・ignore 設定が追加されました。 Changes月次テレメトリ ROI レポート
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 レポートを書き出す
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
🤖 PR Monitor 分析 (GitHub Actions バックストップ)
Applicable Findings (Critical / High / Major)該当なし (レビュー指摘がまだ存在しない) Applicable Findings (Medium 以下)該当なし Filtered (not applicable)該当なし 次のアクション
|
There was a problem hiding this comment.
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
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (16)
.claude/hooks-config.toml.gitignoreCargo.tomldocs/monthly-harness-roi-review-plan.mdpackage.jsonsrc/cli-telemetry-report/Cargo.tomlsrc/cli-telemetry-report/src/aggregate.rssrc/cli-telemetry-report/src/config.rssrc/cli-telemetry-report/src/discover.rssrc/cli-telemetry-report/src/incident.rssrc/cli-telemetry-report/src/main.rssrc/cli-telemetry-report/src/model.rssrc/cli-telemetry-report/src/report.rssrc/cli-telemetry-report/src/snapshot.rssrc/cli-telemetry-report/src/timekit.rssrc/cli-telemetry-report/src/verdict.rs
| [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]] |
There was a problem hiding this comment.
🎯 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 判定を読み込める状態にしてください。
| /// 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(), | ||
| }) | ||
| } |
There was a problem hiding this comment.
🗄️ 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.
| /// 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.
| #[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 にならない" | ||
| ); | ||
| } |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
テストが実際の対象ロジックを検証していない。
combine_roots_current_workspace_error_is_not_degraded は combine_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.
| #[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.
| 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)) |
There was a problem hiding this comment.
🎯 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.
| /// 最新月から遡り「全監視 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) | ||
| } |
There was a problem hiding this comment.
🎯 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.rsRepository: 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:
- 1: https://howardhinnant.github.io/date_algorithms.html
- 2: https://howardhinnant.github.io/date_algorithms
- 3: https://static.sched.com/hosted_files/cppcon2015/43/hinnant_dates.pdf
- 4: https://stackoverflow.com/questions/33712685/c-days-between-given-date-and-today%c2%b4s-date
- 5: https://howardhinnant.github.io/date/date.html
- 6: https://www.github.com/HowardHinnant/date
trailing_zero_streak で実際月の連続性を検証して streak を数えてください。
aggregate.rs::finalize_rollups は existing 内の確定 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.
🤖 PR Monitor 分析 (GitHub Actions バックストップ)
Applicable Findings (Critical / High / Major)
Applicable Findings (Medium 以下)
Filtered (not applicable)
次のアクション
|
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>
作業完了報告CodeRabbit の指摘 5 件に対応し、fix コミット ( 指摘への対応内訳
検証
Windows-only プロジェクトの決定論ツール(WP-12 step 2/3、ADR-062)で、対象は |
概要
月次ハーネス 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.mdの Phase 1 (PR-2) に対応。実装
新規 crate
src/cli-telemetry-report/:discoverjj workspace list動的列挙 +extra_roots) と degraded 判定aggregatesnapshotverdictincidentcustom-lint-rules.tomlの[rules.incident]から維持推奨ルールを導出reporttimekit/model/config/main配線: workspace
Cargo.tomlmembers、package.json(build:cli-telemetry-report+build:all+telemetry-reportscript)、.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 へ反映)
custom-lint-rules.tomlの[rules.incident](id を exe 側に複製しない)。extra_roots数」でもdegraded とする。現 workspace 自身の
self.root()解決失敗は現 root (exe 隣接.claudeの親) で補うためdegraded にしない。この環境の
ccht-improveは jj 格納パス不整合でself.root()解決不能なため、main から実行すると degraded → promote 抑止となる (leak 偏在の誤 promote 防止として正しい挙動)。
--ignore-working-copyで read-only 化しcurrent_working_copy()で現 workspace 判定。retention_days未設定で削除無効)。検証
cli-telemetry-report: 48 unit テスト pass (月跨ぎ集計 / 前月比 / 連続0判定 / rollup 確定月不変 /retention 境界 / 壊れ行 skip / snapshot を網羅)。
pnpm lint/pnpm test/pnpm build/cargo clippy --workspace --all-targets -- -D warnings/cargo test(通常 +--ignored)。hooks-stop-tool-call-leak= 13 block、
.../prompt-recovery= 2 warn の内訳分離を確認。main から見ると improve が未解決 → degraded抑止も裏取り済み。
非ブロッキング follow-up (simplicity-review 指摘、本 PR では未対応)
discover.rs— degraded 判定が workspace 名でなく件数比較 (unresolved_non_current > reachable_extra.len())のため、degraded メッセージが解決失敗 workspace 名を示さない + 件数一致で誤って degraded 抑止し得る。
設計上の既知の割り切り (最終ゲートは必ずユーザー承認、ADR-022/028)。Phase 3 の skill 実装時に identity
マッチングへ改善を検討。
timekit.rs— Howard Hinnant civil-date アルゴリズムの 3 例目の複製 (lib-telemetry / cli-takt-timings と)。ADR-044 の「3 crate 目で中立 crate へ抽出」トリガに到達。別 DRY PR での抽出候補。
補足
モジュール相互依存のため分割すると中間状態がビルド不能。
PR_SIZE_CHECK_OVERRIDE=1で意図的に単一 PR とした(プランが Phase 1 を単一 PR-2 としてスコープ)。閾値の恒久変更はしていない。
Phase 2〜4 の後続 PR。
🤖 Generated with Claude Code
Summary by CodeRabbit
新機能
ドキュメント