use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Fact {
pub id: String,
pub section: String,
pub content: String,
#[serde(default)]
pub tags: Vec<String>,
#[serde(default = "default_confidence")]
pub confidence: f32,
#[serde(default)]
pub reinforced: u32,
pub created_at: String,
pub last_accessed: String,
}
fn default_confidence() -> f32 {
0.9
}
impl Fact {
pub fn new(
id: String,
section: String,
content: String,
tags: Vec<String>,
confidence: f32,
) -> Self {
let now = chrono::Utc::now().to_rfc3339();
Self {
id,
section,
content,
tags,
confidence: confidence.clamp(0.0, 1.0),
reinforced: 0,
created_at: now.clone(),
last_accessed: now,
}
}
pub fn reinforce(&mut self) {
self.reinforced = self.reinforced.saturating_add(1);
self.last_accessed = chrono::Utc::now().to_rfc3339();
}
pub fn touch(&mut self) {
self.last_accessed = chrono::Utc::now().to_rfc3339();
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Episode {
pub id: String,
pub title: String,
#[serde(default)]
pub description: String,
pub created_at: String,
#[serde(default)]
pub facts: Vec<String>,
#[serde(default)]
pub context: HashMap<String, String>,
}
impl Episode {
pub fn new(id: String, title: String) -> Self {
Self {
id,
title,
description: String::new(),
created_at: chrono::Utc::now().to_rfc3339(),
facts: vec![],
context: HashMap::new(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetMindResponse {
pub facts: Vec<Fact>,
#[serde(default)]
pub episodes: Vec<Episode>,
pub total_facts: usize,
pub matched: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LoadMindResponse {
pub loaded: bool,
pub facts_loaded: usize,
#[serde(default)]
pub episodes_loaded: usize,
#[serde(default)]
pub checkpoint_path: String,
#[serde(default)]
pub last_checkpoint: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StoreMindResponse {
pub stored: bool,
pub facts_count: usize,
#[serde(default)]
pub bytes_written: u64,
#[serde(default)]
pub checkpoint_path: String,
#[serde(default)]
pub timestamp: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AddFactResponse {
pub id: String,
pub stored: bool,
pub total_facts: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FactOpResponse {
pub id: String,
pub success: bool,
pub total_facts: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MindMetadata {
pub extension: String,
pub extension_version: String,
pub sdk_version: String,
#[serde(default = "default_mind_version")]
pub mind_version: u16,
pub created_at: String,
pub last_updated: String,
pub total_facts: usize,
#[serde(default)]
pub total_episodes: usize,
#[serde(default)]
pub bytes_on_disk: u64,
#[serde(default)]
pub last_checkpoint: String,
}
fn default_mind_version() -> u16 {
1
}
impl MindMetadata {
pub fn new(extension: String, extension_version: String, sdk_version: String) -> Self {
let now = chrono::Utc::now().to_rfc3339();
Self {
extension,
extension_version,
sdk_version,
mind_version: 1,
created_at: now.clone(),
last_updated: now.clone(),
total_facts: 0,
total_episodes: 0,
bytes_on_disk: 0,
last_checkpoint: now,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_fact_creation() {
let fact = Fact::new(
"test-001".to_string(),
"Architecture".to_string(),
"Team uses async/await".to_string(),
vec!["patterns".to_string()],
0.95,
);
assert_eq!(fact.id, "test-001");
assert_eq!(fact.section, "Architecture");
assert_eq!(fact.reinforced, 0);
assert_eq!(fact.confidence, 0.95);
}
#[test]
fn test_fact_reinforce() {
let mut fact = Fact::new(
"test-001".to_string(),
"Architecture".to_string(),
"Team uses async/await".to_string(),
vec![],
0.95,
);
fact.reinforce();
assert_eq!(fact.reinforced, 1);
fact.reinforce();
assert_eq!(fact.reinforced, 2);
}
#[test]
fn test_confidence_clamped() {
let fact = Fact::new(
"test-001".to_string(),
"Test".to_string(),
"Test".to_string(),
vec![],
1.5, );
assert_eq!(fact.confidence, 1.0);
}
#[test]
fn test_episode_creation() {
let episode = Episode::new("ep-001".to_string(), "Architecture Review".to_string());
assert_eq!(episode.id, "ep-001");
assert_eq!(episode.title, "Architecture Review");
assert!(episode.facts.is_empty());
}
}