Skip to main content

kaizen/guidance/
types.rs

1// SPDX-License-Identifier: AGPL-3.0-or-later
2use serde::{Deserialize, Serialize};
3use std::fmt;
4use std::path::PathBuf;
5
6#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
7#[serde(rename_all = "snake_case")]
8pub enum ArtifactKind {
9    Skill,
10    Rule,
11}
12
13#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
14#[serde(rename_all = "snake_case")]
15pub enum ArtifactState {
16    Current,
17    Stale,
18    InsufficientEvidence,
19}
20
21#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
22pub struct ArtifactRef {
23    pub kind: ArtifactKind,
24    pub slug: String,
25}
26
27#[derive(Clone, Debug, Serialize, Deserialize)]
28pub struct Artifact {
29    pub kind: ArtifactKind,
30    pub slug: String,
31    pub path: PathBuf,
32    pub content_hash: String,
33    pub bytes: u64,
34    pub mtime_ms: u64,
35}
36
37#[derive(Clone, Debug, Serialize, Deserialize)]
38pub struct GuidanceScoreRow {
39    pub artifact: ArtifactRef,
40    pub path: String,
41    pub state: ArtifactState,
42    pub score: f64,
43    pub sessions: u64,
44    pub avg_cost_usd: Option<f64>,
45    pub mean_eval_score: Option<f64>,
46    pub bad_feedback: u64,
47    pub failed_outcomes: u64,
48    pub tool_loops: u64,
49    pub train: GuidanceScoreSlice,
50    pub validation: GuidanceScoreSlice,
51    pub generalization_gap: Option<f64>,
52    pub validation_gate: GuidanceValidationGate,
53    pub evidence: Vec<String>,
54}
55
56#[derive(Clone, Debug, Default, Serialize, Deserialize)]
57pub struct GuidanceScoreSlice {
58    pub score: f64,
59    pub sessions: u64,
60    pub avg_cost_usd: Option<f64>,
61    pub mean_eval_score: Option<f64>,
62    pub bad_feedback: u64,
63    pub failed_outcomes: u64,
64    pub tool_loops: u64,
65}
66
67#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
68#[serde(rename_all = "snake_case")]
69pub enum GuidanceValidationGate {
70    #[default]
71    NoData,
72    NeedsMoreValidation,
73    Stable,
74    Regression,
75}
76
77#[derive(Clone, Debug, Serialize, Deserialize)]
78pub struct GuidanceScoreReport {
79    pub workspace: String,
80    pub window_start_ms: u64,
81    pub window_end_ms: u64,
82    pub validation_start_ms: u64,
83    pub min_sessions: u64,
84    pub rows: Vec<GuidanceScoreRow>,
85}
86
87#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
88#[serde(rename_all = "snake_case")]
89pub enum CandidateStatus {
90    Proposed,
91    Applied,
92    Validated,
93    Rejected,
94    Archived,
95}
96
97#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
98#[serde(rename_all = "snake_case", tag = "op")]
99pub enum CandidateAction {
100    Delete,
101    Replace { content: String },
102    ReviewOnly,
103}
104
105#[derive(Clone, Debug, Serialize, Deserialize)]
106pub struct GuidanceCandidate {
107    pub id: String,
108    pub artifact: ArtifactRef,
109    pub action: CandidateAction,
110    pub status: CandidateStatus,
111    pub rationale: String,
112    pub evidence: Vec<String>,
113    pub created_at_ms: u64,
114    pub applied_at_ms: Option<u64>,
115    pub treatment_fingerprint: Option<String>,
116    pub experiment_id: Option<String>,
117    pub backup_path: Option<String>,
118}
119
120impl ArtifactRef {
121    pub fn parse(raw: &str) -> Option<Self> {
122        let (kind, slug) = raw.split_once(':')?;
123        Some(Self {
124            kind: ArtifactKind::parse(kind)?,
125            slug: slug.to_string(),
126        })
127    }
128}
129
130impl ArtifactKind {
131    pub fn parse(raw: &str) -> Option<Self> {
132        match raw {
133            "skill" => Some(Self::Skill),
134            "rule" => Some(Self::Rule),
135            _ => None,
136        }
137    }
138
139    pub fn as_str(self) -> &'static str {
140        match self {
141            Self::Skill => "skill",
142            Self::Rule => "rule",
143        }
144    }
145}
146
147impl CandidateStatus {
148    pub fn parse(raw: &str) -> Option<Self> {
149        Some(match raw {
150            "proposed" => Self::Proposed,
151            "applied" => Self::Applied,
152            "validated" => Self::Validated,
153            "rejected" => Self::Rejected,
154            "archived" => Self::Archived,
155            _ => return None,
156        })
157    }
158
159    pub fn as_str(self) -> &'static str {
160        match self {
161            Self::Proposed => "proposed",
162            Self::Applied => "applied",
163            Self::Validated => "validated",
164            Self::Rejected => "rejected",
165            Self::Archived => "archived",
166        }
167    }
168}
169
170impl fmt::Display for ArtifactRef {
171    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
172        write!(f, "{}:{}", self.kind.as_str(), self.slug)
173    }
174}