1use crate::message::Message;
12use serde::{Deserialize, Serialize};
13
14#[derive(Debug, Clone, Default, Serialize, Deserialize)]
30pub struct SelfModel {
31 #[serde(default)]
33 pub self_context: Vec<Message>,
34
35 #[serde(default)]
37 pub user_context: Vec<Message>,
38
39 #[serde(default)]
41 pub collective_context: Vec<Message>,
42}
43
44impl SelfModel {
45 pub fn new() -> Self {
47 Self::default()
48 }
49
50 #[must_use]
52 pub fn with_self_context(mut self, messages: Vec<Message>) -> Self {
53 self.self_context = messages;
54 self
55 }
56
57 #[must_use]
59 pub fn with_user_context(mut self, messages: Vec<Message>) -> Self {
60 self.user_context = messages;
61 self
62 }
63
64 #[must_use]
66 pub fn with_collective_context(mut self, messages: Vec<Message>) -> Self {
67 self.collective_context = messages;
68 self
69 }
70}
71
72#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
91pub struct NegativeKnowledge {
92 pub category: String,
94
95 pub constraint: String,
97
98 pub severity: Severity,
100
101 pub source: String,
103
104 #[serde(default)]
106 pub context: String,
107
108 #[serde(default)]
110 pub created_at: String,
111}
112
113impl NegativeKnowledge {
114 pub fn new(
116 category: impl Into<String>,
117 constraint: impl Into<String>,
118 severity: Severity,
119 ) -> Self {
120 Self {
121 category: category.into(),
122 constraint: constraint.into(),
123 severity,
124 source: String::new(),
125 context: String::new(),
126 created_at: String::new(),
127 }
128 }
129
130 #[must_use]
132 pub fn with_source(mut self, source: impl Into<String>) -> Self {
133 self.source = source.into();
134 self
135 }
136}
137
138#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
149#[non_exhaustive]
150pub enum Severity {
151 Critical,
153 High,
155 Medium,
157 Low,
159}
160
161impl Severity {
162 pub fn is_at_least(&self, minimum: &Severity) -> bool {
171 self <= minimum
172 }
173}
174
175#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
191pub struct FailureRecord {
192 pub task_type: String,
194
195 pub approach_tried: String,
197
198 #[serde(default)]
200 pub error_kind: String,
201
202 #[serde(default)]
204 pub root_cause: Option<String>,
205
206 #[serde(default)]
208 pub resolution: Option<String>,
209
210 #[serde(default)]
212 pub timestamp: String,
213
214 #[serde(default)]
216 pub scope_id: String,
217}
218
219impl FailureRecord {
220 pub fn new(task_type: impl Into<String>, approach_tried: impl Into<String>) -> Self {
222 Self {
223 task_type: task_type.into(),
224 approach_tried: approach_tried.into(),
225 error_kind: String::new(),
226 root_cause: None,
227 resolution: None,
228 timestamp: String::new(),
229 scope_id: String::new(),
230 }
231 }
232
233 #[must_use]
235 pub fn with_error_kind(mut self, kind: impl Into<String>) -> Self {
236 self.error_kind = kind.into();
237 self
238 }
239
240 #[must_use]
242 pub fn with_root_cause(mut self, cause: impl Into<String>) -> Self {
243 self.root_cause = Some(cause.into());
244 self
245 }
246
247 #[must_use]
249 pub fn with_resolution(mut self, resolution: impl Into<String>) -> Self {
250 self.resolution = Some(resolution.into());
251 self
252 }
253}
254
255#[cfg(test)]
256mod tests {
257 use super::*;
258
259 #[test]
260 fn test_self_model_creation() {
261 let model = SelfModel::new()
262 .with_self_context(vec![Message::system("I analyze code.")])
263 .with_user_context(vec![Message::system("User is a senior dev.")]);
264 assert_eq!(model.self_context.len(), 1);
265 assert_eq!(model.user_context.len(), 1);
266 assert!(model.collective_context.is_empty());
267 }
268
269 #[test]
270 fn test_negative_knowledge() {
271 let nk = NegativeKnowledge::new("api", "max 100 items per batch", Severity::High)
272 .with_source("production incident");
273 assert_eq!(nk.category, "api");
274 assert_eq!(nk.severity, Severity::High);
275 assert_eq!(nk.source, "production incident");
276 }
277
278 #[test]
279 fn test_severity_ordering() {
280 assert!(Severity::Critical < Severity::High);
281 assert!(Severity::High < Severity::Medium);
282 assert!(Severity::Medium < Severity::Low);
283 }
284
285 #[test]
286 fn test_failure_record() {
287 let record = FailureRecord::new("db_migration", "ALTER TABLE")
288 .with_error_kind("timeout")
289 .with_root_cause("table too large")
290 .with_resolution("use pt-online-schema-change");
291 assert_eq!(record.task_type, "db_migration");
292 assert_eq!(record.error_kind, "timeout");
293 assert_eq!(record.root_cause.as_deref(), Some("table too large"));
294 assert_eq!(
295 record.resolution.as_deref(),
296 Some("use pt-online-schema-change")
297 );
298 }
299
300 #[test]
301 fn test_negative_knowledge_serialization() {
302 let nk = NegativeKnowledge::new("parsing", "don't use regex for HTML", Severity::Critical);
303 let json = serde_json::to_string(&nk).unwrap();
304 let back: NegativeKnowledge = serde_json::from_str(&json).unwrap();
305 assert_eq!(back, nk);
306 }
307
308 #[test]
309 fn test_failure_record_serialization() {
310 let record = FailureRecord::new("auth", "JWT validation").with_error_kind("expired_token");
311 let json = serde_json::to_string(&record).unwrap();
312 let back: FailureRecord = serde_json::from_str(&json).unwrap();
313 assert_eq!(back, record);
314 }
315}