use serde::{Deserialize, Serialize};
use std::path::Path;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct PersonaDefinition {
pub agent_name: String,
pub role_name: String,
pub name_origin: String,
pub vibe: String,
pub symbol: String,
#[serde(default)]
pub core_characteristics: Vec<CharacteristicDef>,
pub speech_style: String,
pub terraphim_nature: String,
pub sfia_title: String,
pub primary_level: u8,
pub guiding_phrase: String,
pub level_essence: String,
#[serde(default)]
pub sfia_skills: Vec<SfiaSkillDef>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct CharacteristicDef {
pub name: String,
pub description: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct SfiaSkillDef {
pub code: String,
pub name: String,
pub level: u8,
pub description: String,
}
impl PersonaDefinition {
pub fn from_toml(toml_str: &str) -> Result<Self, toml::de::Error> {
toml::from_str(toml_str)
}
pub fn from_file(path: impl AsRef<Path>) -> Result<Self, PersonaLoadError> {
let content = std::fs::read_to_string(path.as_ref()).map_err(PersonaLoadError::Io)?;
Self::from_toml(&content).map_err(|e| PersonaLoadError::Parse(e.to_string()))
}
pub fn to_toml(&self) -> Result<String, toml::ser::Error> {
toml::to_string_pretty(self)
}
}
#[derive(Debug, thiserror::Error)]
pub enum PersonaLoadError {
#[error("IO error reading persona file: {0}")]
Io(#[from] std::io::Error),
#[error("TOML parse error: {0}")]
Parse(String),
#[error("Persona not found: {0}")]
NotFound(String),
}
#[cfg(test)]
mod tests {
use super::*;
use std::env::temp_dir;
use std::fs;
#[test]
fn test_persona_from_toml_minimal() {
let toml = r#"
agent_name = "Test Agent"
role_name = "Tester"
name_origin = "Test"
vibe = "Helpful"
symbol = "T"
speech_style = "Clear"
terraphim_nature = "Test nature"
sfia_title = "Test Engineer"
primary_level = 3
guiding_phrase = "Test everything"
level_essence = "Ensures quality"
"#;
let persona = PersonaDefinition::from_toml(toml).unwrap();
assert_eq!(persona.agent_name, "Test Agent");
assert_eq!(persona.role_name, "Tester");
assert_eq!(persona.primary_level, 3);
assert!(persona.core_characteristics.is_empty());
assert!(persona.sfia_skills.is_empty());
}
#[test]
fn test_persona_from_toml_full() {
let toml = r#"
agent_name = "Terraphim Architect"
role_name = "Systems Architect"
name_origin = "Greek: Terra (Earth) + phainein (to show)"
vibe = "Thoughtful, grounded, precise, architectural"
symbol = "⚡"
speech_style = "Technical yet accessible"
terraphim_nature = "Earth spirit of knowledge architecture"
sfia_title = "Solution Architect"
primary_level = 5
guiding_phrase = "Structure precedes function"
level_essence = "Enables and ensures"
[[core_characteristics]]
name = "Systems Thinking"
description = "Views problems holistically"
[[core_characteristics]]
name = "Pattern Recognition"
description = "Identifies recurring structures"
[[sfia_skills]]
code = "ARCH"
name = "Solution Architecture"
level = 5
description = "Designs and communicates solution architectures"
[[sfia_skills]]
code = "DESN"
name = "Systems Design"
level = 5
description = "Specifies and designs large-scale systems"
"#;
let persona = PersonaDefinition::from_toml(toml).unwrap();
assert_eq!(persona.agent_name, "Terraphim Architect");
assert_eq!(persona.symbol, "⚡");
assert_eq!(persona.primary_level, 5);
assert_eq!(persona.core_characteristics.len(), 2);
assert_eq!(persona.core_characteristics[0].name, "Systems Thinking");
assert_eq!(persona.sfia_skills.len(), 2);
assert_eq!(persona.sfia_skills[0].code, "ARCH");
assert_eq!(persona.sfia_skills[1].name, "Systems Design");
}
#[test]
fn test_persona_roundtrip() {
let toml = r#"
agent_name = "Test Agent"
role_name = "Tester"
name_origin = "Test"
vibe = "Helpful"
symbol = "T"
speech_style = "Clear"
terraphim_nature = "Test nature"
sfia_title = "Test Engineer"
primary_level = 3
guiding_phrase = "Test everything"
level_essence = "Ensures quality"
[[core_characteristics]]
name = "Test Char"
description = "A test characteristic"
[[sfia_skills]]
code = "TEST"
name = "Testing"
level = 3
description = "Tests things"
"#;
let persona = PersonaDefinition::from_toml(toml).unwrap();
let output = persona.to_toml().unwrap();
let reparsed = PersonaDefinition::from_toml(&output).unwrap();
assert_eq!(persona, reparsed);
}
#[test]
fn test_persona_missing_required_field() {
let toml = r#"
role_name = "Tester"
name_origin = "Test"
vibe = "Helpful"
symbol = "T"
speech_style = "Clear"
terraphim_nature = "Test nature"
sfia_title = "Test Engineer"
primary_level = 3
guiding_phrase = "Test everything"
level_essence = "Ensures quality"
"#;
let result = PersonaDefinition::from_toml(toml);
assert!(result.is_err());
}
#[test]
fn test_persona_characteristic_parsing() {
let toml = r#"
agent_name = "Test"
role_name = "Tester"
name_origin = "Test"
vibe = "Helpful"
symbol = "T"
speech_style = "Clear"
terraphim_nature = "Test"
sfia_title = "Tester"
primary_level = 3
guiding_phrase = "Test"
level_essence = "Test"
[[core_characteristics]]
name = "First"
description = "First characteristic"
[[core_characteristics]]
name = "Second"
description = "Second characteristic"
[[core_characteristics]]
name = "Third"
description = "Third characteristic"
"#;
let persona = PersonaDefinition::from_toml(toml).unwrap();
assert_eq!(persona.core_characteristics.len(), 3);
assert_eq!(persona.core_characteristics[1].name, "Second");
assert_eq!(
persona.core_characteristics[1].description,
"Second characteristic"
);
}
#[test]
fn test_persona_sfia_skill_parsing() {
let toml = r#"
agent_name = "Test"
role_name = "Tester"
name_origin = "Test"
vibe = "Helpful"
symbol = "T"
speech_style = "Clear"
terraphim_nature = "Test"
sfia_title = "Tester"
primary_level = 3
guiding_phrase = "Test"
level_essence = "Test"
[[sfia_skills]]
code = "CODE1"
name = "Skill One"
level = 2
description = "First skill"
[[sfia_skills]]
code = "CODE2"
name = "Skill Two"
level = 4
description = "Second skill"
"#;
let persona = PersonaDefinition::from_toml(toml).unwrap();
assert_eq!(persona.sfia_skills.len(), 2);
assert_eq!(persona.sfia_skills[0].code, "CODE1");
assert_eq!(persona.sfia_skills[0].level, 2);
assert_eq!(persona.sfia_skills[1].name, "Skill Two");
assert_eq!(persona.sfia_skills[1].level, 4);
}
#[test]
fn test_persona_sfia_level_bounds() {
let toml = r#"
agent_name = "Test"
role_name = "Tester"
name_origin = "Test"
vibe = "Helpful"
symbol = "T"
speech_style = "Clear"
terraphim_nature = "Test"
sfia_title = "Tester"
primary_level = 0
guiding_phrase = "Test"
level_essence = "Test"
[[sfia_skills]]
code = "ZERO"
name = "Zero Level"
level = 0
description = "Level zero"
[[sfia_skills]]
code = "EIGHT"
name = "Eight Level"
level = 8
description = "Level eight"
"#;
let persona = PersonaDefinition::from_toml(toml).unwrap();
assert_eq!(persona.primary_level, 0);
assert_eq!(persona.sfia_skills[0].level, 0);
assert_eq!(persona.sfia_skills[1].level, 8);
}
#[test]
fn test_persona_from_file_not_found() {
let path = temp_dir().join("nonexistent_persona_12345.toml");
let result = PersonaDefinition::from_file(&path);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.to_string().contains("IO error"));
}
#[test]
fn test_persona_from_file_invalid_toml() {
let temp_file = temp_dir().join("invalid_persona_test.toml");
fs::write(&temp_file, "this is not valid toml = [").unwrap();
let result = PersonaDefinition::from_file(&temp_file);
fs::remove_file(&temp_file).unwrap();
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.to_string().contains("TOML parse error"));
}
#[test]
fn test_persona_definition_clone_eq() {
let toml = r#"
agent_name = "Test Agent"
role_name = "Tester"
name_origin = "Test"
vibe = "Helpful"
symbol = "T"
speech_style = "Clear"
terraphim_nature = "Test nature"
sfia_title = "Test Engineer"
primary_level = 3
guiding_phrase = "Test everything"
level_essence = "Ensures quality"
"#;
let persona = PersonaDefinition::from_toml(toml).unwrap();
let cloned = persona.clone();
assert_eq!(persona, cloned);
assert!(persona.agent_name == cloned.agent_name);
}
}