Skip to main content

kaizen/retro/
types.rs

1// SPDX-License-Identifier: AGPL-3.0-or-later
2//! Pure data for the retro engine (`Report`, `Bet`, `Inputs`).
3
4use crate::core::event::{Event, SessionRecord};
5use crate::metrics::types::{FileFact, ToolSpanView};
6use serde::{Deserialize, Serialize};
7use std::collections::{HashMap, HashSet};
8
9/// Workspace-local facts assembled once at the IO boundary.
10#[derive(Debug, Clone)]
11pub struct Inputs {
12    pub window_start_ms: u64,
13    pub window_end_ms: u64,
14    /// Joined rows time-ordered.
15    pub events: Vec<(SessionRecord, Event)>,
16    pub files_touched: Vec<(String, String)>,
17    pub skills_used: Vec<(String, String)>,
18    pub tool_spans: Vec<ToolSpanView>,
19    /// Skills referenced in the last `usage_lookback_ms` window (for H1).
20    pub skills_used_recent_slugs: HashSet<String>,
21    pub usage_lookback_ms: u64,
22    pub skill_files_on_disk: Vec<SkillFileOnDisk>,
23    /// `.cursor/rules/*.mdc` stems (same shape as [`SkillFileOnDisk`]).
24    pub rule_files_on_disk: Vec<SkillFileOnDisk>,
25    pub rules_used_recent_slugs: HashSet<String>,
26    pub file_facts: HashMap<String, FileFact>,
27    pub aggregates: RetroAggregates,
28    /// LLM-as-Judge eval scores for sessions in the window: (session_id, score 0..1).
29    pub eval_scores: Vec<(String, f64)>,
30    /// Sessions with a recorded prompt fingerprint: (session_id, fingerprint).
31    pub prompt_fingerprints: Vec<(String, String)>,
32}
33
34#[derive(Debug, Clone)]
35pub struct SkillFileOnDisk {
36    pub slug: String,
37    /// Bytes of frontmatter + body (rough token proxy).
38    pub size_bytes: u64,
39    pub mtime_ms: u64,
40}
41
42#[derive(Debug, Clone, Default)]
43pub struct RetroAggregates {
44    pub unique_session_ids: HashSet<String>,
45    pub tool_event_counts: HashMap<String, u64>,
46    pub tool_cost_usd_e6: HashMap<String, i64>,
47    pub model_session_counts: HashMap<String, u64>,
48    pub total_cost_usd_e6: i64,
49}
50
51/// One ranked improvement bet.
52#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
53pub struct Bet {
54    /// Stable id for dedup (`H2:foo.rs|bar.rs`).
55    pub id: String,
56    pub heuristic_id: String,
57    pub title: String,
58    pub hypothesis: String,
59    pub expected_tokens_saved_per_week: f64,
60    pub effort_minutes: u32,
61    pub evidence: Vec<String>,
62    pub apply_step: String,
63    #[serde(default)]
64    pub evidence_recency_ms: u64,
65}
66
67impl Bet {
68    pub fn score(&self) -> f64 {
69        self.expected_tokens_saved_per_week / (self.effort_minutes as f64 + 1.0)
70    }
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
74pub struct RetroMeta {
75    pub week_label: String,
76    pub span_start_ms: u64,
77    pub span_end_ms: u64,
78    pub session_count: u64,
79    pub total_cost_usd_e6: i64,
80}
81
82#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
83pub struct RetroStats {
84    pub sessions: u64,
85    pub total_cost_usd_e6: i64,
86    pub top_model: Option<String>,
87    pub top_model_pct: Option<u64>,
88    pub top_tool: Option<String>,
89    pub top_tool_pct: Option<u64>,
90    pub median_session_minutes: Option<u64>,
91}
92
93/// JSON + markdown source of truth for CLI `--json` and reports.
94#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
95pub struct Report {
96    pub meta: RetroMeta,
97    pub top_bets: Vec<Bet>,
98    pub skipped_deduped: Vec<String>,
99    pub stats: RetroStats,
100}