1use 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}