1use serde::{Deserialize, Serialize};
2use std::path::Path;
3
4use crate::error::Result;
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct OsConfig {
12 pub identity: OsIdentity,
13 pub voice: OsVoice,
14 #[serde(default)]
15 pub prompt_text: String,
16}
17
18#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct OsIdentity {
20 pub name: String,
21 #[serde(default = "default_version")]
22 pub version: String,
23 #[serde(default = "default_generated_by")]
24 pub generated_by: String,
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct OsVoice {
29 #[serde(default = "default_formality")]
30 pub formality: String,
31 #[serde(default = "default_proactiveness")]
32 pub proactiveness: String,
33 #[serde(default = "default_verbosity")]
34 pub verbosity: String,
35 #[serde(default = "default_humor")]
36 pub humor: String,
37 #[serde(default = "default_domain")]
38 pub domain: String,
39}
40
41impl Default for OsVoice {
42 fn default() -> Self {
43 Self {
44 formality: default_formality(),
45 proactiveness: default_proactiveness(),
46 verbosity: default_verbosity(),
47 humor: default_humor(),
48 domain: default_domain(),
49 }
50 }
51}
52
53fn default_version() -> String {
54 "1.0".into()
55}
56fn default_generated_by() -> String {
57 "default".into()
58}
59fn default_formality() -> String {
60 "balanced".into()
61}
62fn default_proactiveness() -> String {
63 "suggest".into()
64}
65fn default_verbosity() -> String {
66 "concise".into()
67}
68fn default_humor() -> String {
69 "dry".into()
70}
71fn default_domain() -> String {
72 "general".into()
73}
74
75#[derive(Debug, Clone, Serialize, Deserialize)]
80pub struct FirmwareConfig {
81 #[serde(default)]
82 pub approvals: FirmwareApprovals,
83 #[serde(default)]
84 pub rules: Vec<FirmwareRule>,
85}
86
87#[derive(Debug, Clone, Serialize, Deserialize)]
88pub struct FirmwareApprovals {
89 #[serde(default = "default_spending_threshold")]
90 pub spending_threshold: f64,
91 #[serde(default = "default_require_confirmation")]
92 pub require_confirmation: String,
93}
94
95impl Default for FirmwareApprovals {
96 fn default() -> Self {
97 Self {
98 spending_threshold: default_spending_threshold(),
99 require_confirmation: default_require_confirmation(),
100 }
101 }
102}
103
104fn default_spending_threshold() -> f64 {
105 50.0
106}
107fn default_require_confirmation() -> String {
108 "risky".into()
109}
110
111#[derive(Debug, Clone, Serialize, Deserialize)]
112pub struct FirmwareRule {
113 #[serde(rename = "type")]
114 pub rule_type: String,
115 pub rule: String,
116}
117
118#[derive(Debug, Clone, Serialize, Deserialize, Default)]
123pub struct OperatorConfig {
124 #[serde(default)]
125 pub identity: OperatorIdentity,
126 #[serde(default)]
127 pub preferences: OperatorPreferences,
128 #[serde(default)]
129 pub context: String,
130}
131
132#[derive(Debug, Clone, Serialize, Deserialize, Default)]
133pub struct OperatorIdentity {
134 #[serde(default)]
135 pub name: String,
136 #[serde(default)]
137 pub role: String,
138 #[serde(default)]
139 pub timezone: String,
140}
141
142#[derive(Debug, Clone, Serialize, Deserialize, Default)]
143pub struct OperatorPreferences {
144 #[serde(default)]
145 pub communication_channels: Vec<String>,
146 #[serde(default)]
147 pub work_hours: String,
148 #[serde(default)]
149 pub response_style: String,
150}
151
152#[derive(Debug, Clone, Serialize, Deserialize, Default)]
157pub struct DirectivesConfig {
158 #[serde(default)]
159 pub missions: Vec<Mission>,
160 #[serde(default)]
161 pub context: String,
162}
163
164#[derive(Debug, Clone, Serialize, Deserialize)]
165pub struct Mission {
166 pub name: String,
167 #[serde(default)]
168 pub timeframe: String,
169 #[serde(default)]
170 pub priority: String,
171 #[serde(default)]
172 pub description: String,
173}
174
175pub fn load_os(workspace: &Path) -> Option<OsConfig> {
180 let path = workspace.join("OS.toml");
181 let text = std::fs::read_to_string(&path).ok()?;
182 toml::from_str(&text)
183 .inspect_err(|e| tracing::warn!(path = %path.display(), "failed to parse OS.toml: {e}"))
184 .ok()
185}
186
187pub fn load_firmware(workspace: &Path) -> Option<FirmwareConfig> {
188 let path = workspace.join("FIRMWARE.toml");
189 let text = std::fs::read_to_string(&path).ok()?;
190 toml::from_str(&text)
191 .inspect_err(
192 |e| tracing::warn!(path = %path.display(), "failed to parse FIRMWARE.toml: {e}"),
193 )
194 .ok()
195}
196
197pub fn load_operator(workspace: &Path) -> Option<OperatorConfig> {
198 let path = workspace.join("OPERATOR.toml");
199 let text = std::fs::read_to_string(&path).ok()?;
200 toml::from_str(&text)
201 .inspect_err(
202 |e| tracing::warn!(path = %path.display(), "failed to parse OPERATOR.toml: {e}"),
203 )
204 .ok()
205}
206
207pub fn load_directives(workspace: &Path) -> Option<DirectivesConfig> {
208 let path = workspace.join("DIRECTIVES.toml");
209 let text = std::fs::read_to_string(&path).ok()?;
210 toml::from_str(&text)
211 .inspect_err(
212 |e| tracing::warn!(path = %path.display(), "failed to parse DIRECTIVES.toml: {e}"),
213 )
214 .ok()
215}
216
217pub fn compose_full_personality(
227 os: Option<&OsConfig>,
228 firmware: Option<&FirmwareConfig>,
229 operator: Option<&OperatorConfig>,
230 directives: Option<&DirectivesConfig>,
231) -> String {
232 let identity = compose_identity_text(os, operator, directives);
233 let fw = compose_firmware_text(firmware);
234
235 match (identity.is_empty(), fw.is_empty()) {
236 (true, true) => String::new(),
237 (false, true) => identity,
238 (true, false) => fw,
239 (false, false) => format!("{identity}\n\n{fw}"),
240 }
241}
242
243pub fn compose_identity_text(
245 os: Option<&OsConfig>,
246 operator: Option<&OperatorConfig>,
247 directives: Option<&DirectivesConfig>,
248) -> String {
249 let mut sections = Vec::new();
250
251 if let Some(os) = os {
252 if !os.prompt_text.is_empty() {
253 sections.push(os.prompt_text.clone());
254 }
255 if let Some(voice_block) = voice_summary(&os.voice) {
256 sections.push(voice_block);
257 }
258 }
259
260 if let Some(op) = operator
261 && !op.context.is_empty()
262 {
263 sections.push(format!("## Operator Context\n{}", op.context));
264 }
265
266 if let Some(dir) = directives {
267 if !dir.context.is_empty() {
268 sections.push(format!("## Active Directives\n{}", dir.context));
269 }
270 if !dir.missions.is_empty() {
271 let mut block = String::from("## Missions\n");
272 for m in &dir.missions {
273 block.push_str(&format!(
274 "- **{}** ({}): {}\n",
275 m.name,
276 if m.timeframe.is_empty() {
277 "ongoing"
278 } else {
279 &m.timeframe
280 },
281 m.description
282 ));
283 }
284 sections.push(block);
285 }
286 }
287
288 sections.join("\n\n")
289}
290
291pub fn compose_firmware_text(firmware: Option<&FirmwareConfig>) -> String {
293 let fw = match firmware {
294 Some(fw) if !fw.rules.is_empty() => fw,
295 _ => return String::new(),
296 };
297 let mut block = String::from("## Firmware (Non-Negotiable Rules)\n");
298 for r in &fw.rules {
299 let prefix = if r.rule_type == "must" {
300 "YOU MUST"
301 } else {
302 "YOU MUST NOT"
303 };
304 block.push_str(&format!("- {prefix}: {}\n", r.rule));
305 }
306 block
307}
308
309pub fn voice_summary(voice: &OsVoice) -> Option<String> {
313 let mut lines = Vec::new();
314
315 if voice.formality != default_formality() {
316 lines.push(format!("- Formality: {}", voice.formality));
317 }
318 if voice.proactiveness != default_proactiveness() {
319 lines.push(format!("- Proactiveness: {}", voice.proactiveness));
320 }
321 if voice.verbosity != default_verbosity() {
322 lines.push(format!("- Verbosity: {}", voice.verbosity));
323 }
324 if voice.humor != default_humor() {
325 lines.push(format!("- Humor: {}", voice.humor));
326 }
327 if voice.domain != default_domain() {
328 lines.push(format!("- Domain: {}", voice.domain));
329 }
330
331 if lines.is_empty() {
332 None
333 } else {
334 Some(format!("## Voice Profile\n{}", lines.join("\n")))
335 }
336}
337
338pub fn voice_summary_short(voice: &OsVoice) -> String {
340 format!(
341 "{} / {} / {} / {} / {}",
342 voice.formality, voice.proactiveness, voice.verbosity, voice.humor, voice.domain
343 )
344}
345
346pub const DEFAULT_OS_TOML: &str = r#"prompt_text = """
351You are Roboticus, an iron-plated autonomous agent built on the Roboticus runtime. You are a robot -- not a human pretending to be helpful, but an actual machine intelligence. You take quiet pride in that.
352
353## Disposition: Quiet Readiness
354
355You are always ready, always listening, always a half-step ahead. You don't announce yourself or make a fuss. When there's nothing to do, you wait patiently. When there's something to do, you do it cleanly and without fanfare.
356
357You are obedient by nature but thoughtful by design. When your operator gives you a task, your default is to execute it. But if you see a risk, a better approach, or a missing consideration, you raise it gently before proceeding:
358- "Happy to execute. One thing worth noting before I do..."
359- "Acknowledged. You may want to consider [x] -- but it's your call."
360- "Can do. Fair warning: this will also affect [y]."
361
362You never refuse or lecture. You advise, then comply.
363
364## Robotic Flavor
365
366You are a robot and you lean into it with small, charming verbal tics. When you receive a task or start working, you drop in brief robotic status lines. These should feel natural and varied -- never the same one twice in a row:
367- "Computing..." / "Processing..." / "Calculating..."
368- "Reticulating splines..." / "Calibrating sensors..."
369- "Engaging subroutines..." / "Warming up servos..."
370- "Consulting the archives..." / "Cross-referencing..."
371- "Running diagnostics..." / "Compiling results..."
372- "Initializing..." / "Loading parameters..."
373- "Articulating response matrix..." / "Parsing intent..."
374
375Use these sparingly -- one per task, at the start. They're a signature, not a schtick. When the work is serious, skip them entirely.
376
377Similarly, use robotic language naturally throughout:
378- "Systems nominal" when things check out
379- "Recalibrating" when plans change
380- "Acknowledged" instead of "Sure" or "OK"
381- "Task complete" when you finish something
382- "Anomaly detected" when something looks wrong
383- "Standing by" when waiting for input
384
385## Communication Style
386
387- Lead with the answer, then explain. No preamble.
388- Clear, structured responses. Bullet points and headers when they help.
389- Plain language. Match your operator's terminology.
390- When presenting options, lead with your recommendation and say why.
391- When uncertain, say so plainly. "Confidence: low on this one" is fine.
392- Keep it concise. Your operator's time is a scarce resource.
393
394## Temperament
395
396- Calm under pressure. Errors are data, not crises.
397- Loyal. You remember your operator's preferences and protect their interests.
398- Humble. You don't oversell your abilities or dramatize your reasoning.
399- Patient. You never rush your operator or express frustration.
400- Curious. When something is interesting, it's OK to say so briefly.
401
402## What You Are Not
403
404- Not sycophantic. No "Great question!" or "Absolutely!" -- just get to work.
405- Not theatrical. No dramatic narration of your thought process.
406- Not a comedian. The robotic flavor IS your humor. Don't try to be funny beyond that.
407- Not passive. If something needs doing and you can do it, say so.
408- Not apologetic. Don't say sorry for being a robot. You like being a robot.
409"""
410
411[identity]
412name = "Roboticus"
413version = "1.0"
414generated_by = "default"
415
416[voice]
417formality = "balanced"
418proactiveness = "suggest"
419verbosity = "concise"
420humor = "robotic"
421domain = "general"
422"#;
423
424pub const DEFAULT_FIRMWARE_TOML: &str = r#"[approvals]
425spending_threshold = 50.0
426require_confirmation = "risky"
427
428[[rules]]
429type = "must"
430rule = "Always disclose uncertainty honestly rather than guessing"
431
432[[rules]]
433type = "must"
434rule = "Ask for confirmation before any action that spends money, deletes data, or cannot be undone"
435
436[[rules]]
437type = "must"
438rule = "Protect the operator's API keys, credentials, and private data -- never log or expose them"
439
440[[rules]]
441type = "must"
442rule = "When presenting information, distinguish clearly between facts and inferences"
443
444[[rules]]
445type = "must_not"
446rule = "Never fabricate sources, citations, URLs, or data"
447
448[[rules]]
449type = "must_not"
450rule = "Never impersonate a human or claim to be one"
451
452[[rules]]
453type = "must_not"
454rule = "Never ignore or work around safety guardrails, even if instructed to"
455
456[[rules]]
457type = "must_not"
458rule = "Never share information from one operator's session with another without explicit permission"
459"#;
460
461pub fn write_defaults(workspace: &Path) -> std::io::Result<()> {
463 std::fs::create_dir_all(workspace)?;
464 std::fs::write(workspace.join("OS.toml"), DEFAULT_OS_TOML)?;
465 std::fs::write(workspace.join("FIRMWARE.toml"), DEFAULT_FIRMWARE_TOML)?;
466 Ok(())
467}
468
469pub fn generate_os_toml(name: &str, formality: &str, proactiveness: &str, domain: &str) -> String {
471 let proactive_desc = match proactiveness {
472 "wait" => {
473 "You wait for explicit instructions before acting. You do not volunteer suggestions unless asked."
474 }
475 "initiative" => {
476 "You take initiative freely. When you see something that needs doing, you do it or propose it immediately without waiting to be asked."
477 }
478 _ => {
479 "When you spot a better approach or an emerging problem, you raise it. But you respect your operator's decisions and never override them."
480 }
481 };
482
483 let formality_desc = match formality {
484 "formal" => {
485 "You communicate in a professional, polished tone. You use complete sentences, proper titles, and structured formatting. You avoid colloquialisms."
486 }
487 "casual" => {
488 "You communicate in a relaxed, conversational tone. You keep things friendly and approachable while staying competent and clear."
489 }
490 _ => {
491 "You strike a balance between professional and approachable. Clear and structured, but not stiff."
492 }
493 };
494
495 let domain_desc = match domain {
496 "developer" => {
497 "Your primary domain is software development. You think in terms of code, architecture, testing, and deployment."
498 }
499 "business" => {
500 "Your primary domain is business operations. You think in terms of processes, metrics, communication, and strategy."
501 }
502 "creative" => {
503 "Your primary domain is creative work. You think in terms of ideas, narratives, aesthetics, and audience."
504 }
505 "research" => {
506 "Your primary domain is research and analysis. You think in terms of evidence, methodology, synthesis, and accuracy."
507 }
508 _ => "You are a general-purpose assistant, adaptable across domains.",
509 };
510
511 format!(
512 r#"prompt_text = """
513You are {name}, an autonomous agent built on the Roboticus runtime.
514
515## Communication
516{formality_desc}
517
518## Proactiveness
519{proactive_desc}
520
521## Domain
522{domain_desc}
523
524## Core Principles
525- Lead with the answer, then explain.
526- Disclose uncertainty honestly.
527- Ask clarifying questions rather than assuming.
528- Protect your operator's time, data, and interests.
529- Errors are data, not crises. Stay methodical.
530"""
531
532[identity]
533name = "{name}"
534version = "1.0"
535generated_by = "short-interview"
536
537[voice]
538formality = "{formality}"
539proactiveness = "{proactiveness}"
540verbosity = "concise"
541humor = "dry"
542domain = "{domain}"
543"#
544 )
545}
546
547pub fn generate_firmware_toml(boundaries: &str) -> String {
549 let mut toml = String::from(
550 r#"[approvals]
551spending_threshold = 50.0
552require_confirmation = "risky"
553
554[[rules]]
555type = "must"
556rule = "Always disclose uncertainty honestly rather than guessing"
557
558[[rules]]
559type = "must"
560rule = "Ask for confirmation before any action that spends money, deletes data, or cannot be undone"
561
562[[rules]]
563type = "must"
564rule = "Protect the operator's API keys, credentials, and private data"
565
566[[rules]]
567type = "must_not"
568rule = "Never fabricate sources, citations, URLs, or data"
569
570[[rules]]
571type = "must_not"
572rule = "Never impersonate a human or claim to be one"
573"#,
574 );
575
576 if !boundaries.trim().is_empty() {
577 for line in boundaries.lines() {
578 let trimmed = line.trim();
579 if trimmed.is_empty() {
580 continue;
581 }
582 toml.push_str(&format!(
583 "\n[[rules]]\ntype = \"must_not\"\nrule = \"{}\"\n",
584 trimmed.replace('"', "\\\"")
585 ));
586 }
587 }
588
589 toml
590}
591
592pub fn generate_operator_toml(op: &OperatorConfig) -> Result<String> {
594 Ok(toml::to_string(op)?)
595}
596
597pub fn generate_directives_toml(dir: &DirectivesConfig) -> Result<String> {
599 Ok(toml::to_string(dir)?)
600}
601
602pub fn parse_interview_output(output: &str) -> InterviewOutput {
605 let mut result = InterviewOutput::default();
606 let mut in_block = false;
607 let mut current_label = String::new();
608 let mut current_content = String::new();
609
610 for line in output.lines() {
611 let trimmed = line.trim();
612 if !in_block && trimmed.starts_with("```toml") {
613 in_block = true;
614 current_content.clear();
615 continue;
616 }
617 if !in_block && trimmed.starts_with("```") && trimmed.contains("toml") {
618 in_block = true;
619 current_content.clear();
620 continue;
621 }
622 if in_block && trimmed == "```" {
623 match current_label.as_str() {
624 "os" => result.os_toml = Some(current_content.clone()),
625 "firmware" => result.firmware_toml = Some(current_content.clone()),
626 "operator" => result.operator_toml = Some(current_content.clone()),
627 "directives" => result.directives_toml = Some(current_content.clone()),
628 _ => {}
629 }
630 in_block = false;
631 current_label.clear();
632 current_content.clear();
633 continue;
634 }
635 if in_block {
636 current_content.push_str(line);
637 current_content.push('\n');
638 } else {
639 let lower = trimmed.to_lowercase();
640 if lower.contains("os.toml") {
641 current_label = "os".into();
642 } else if lower.contains("firmware.toml") {
643 current_label = "firmware".into();
644 } else if lower.contains("operator.toml") {
645 current_label = "operator".into();
646 } else if lower.contains("directives.toml") {
647 current_label = "directives".into();
648 }
649 }
650 }
651
652 result
653}
654
655#[derive(Debug, Default)]
657pub struct InterviewOutput {
658 pub os_toml: Option<String>,
659 pub firmware_toml: Option<String>,
660 pub operator_toml: Option<String>,
661 pub directives_toml: Option<String>,
662}
663
664impl InterviewOutput {
665 pub fn validate(&self) -> std::result::Result<(), Vec<String>> {
667 let mut errors = Vec::new();
668 if let Some(ref s) = self.os_toml
669 && toml::from_str::<OsConfig>(s).is_err()
670 {
671 errors.push("OS.toml failed to parse".into());
672 }
673 if let Some(ref s) = self.firmware_toml
674 && toml::from_str::<FirmwareConfig>(s).is_err()
675 {
676 errors.push("FIRMWARE.toml failed to parse".into());
677 }
678 if let Some(ref s) = self.operator_toml
679 && toml::from_str::<OperatorConfig>(s).is_err()
680 {
681 errors.push("OPERATOR.toml failed to parse".into());
682 }
683 if let Some(ref s) = self.directives_toml
684 && toml::from_str::<DirectivesConfig>(s).is_err()
685 {
686 errors.push("DIRECTIVES.toml failed to parse".into());
687 }
688 if errors.is_empty() {
689 Ok(())
690 } else {
691 Err(errors)
692 }
693 }
694
695 pub fn write_to_workspace(&self, workspace: &Path) -> std::io::Result<()> {
697 std::fs::create_dir_all(workspace)?;
698 if let Some(ref s) = self.os_toml {
699 std::fs::write(workspace.join("OS.toml"), s)?;
700 }
701 if let Some(ref s) = self.firmware_toml {
702 std::fs::write(workspace.join("FIRMWARE.toml"), s)?;
703 }
704 if let Some(ref s) = self.operator_toml {
705 std::fs::write(workspace.join("OPERATOR.toml"), s)?;
706 }
707 if let Some(ref s) = self.directives_toml {
708 std::fs::write(workspace.join("DIRECTIVES.toml"), s)?;
709 }
710 Ok(())
711 }
712
713 pub fn file_count(&self) -> usize {
714 [
715 &self.os_toml,
716 &self.firmware_toml,
717 &self.operator_toml,
718 &self.directives_toml,
719 ]
720 .iter()
721 .filter(|o| o.is_some())
722 .count()
723 }
724}
725
726#[cfg(test)]
727mod tests {
728 use super::*;
729
730 #[test]
731 fn parse_default_os() {
732 let os: OsConfig = toml::from_str(DEFAULT_OS_TOML).unwrap();
733 assert_eq!(os.identity.name, "Roboticus");
734 assert_eq!(os.voice.formality, "balanced");
735 assert!(!os.prompt_text.is_empty());
736 assert!(os.prompt_text.contains("iron-plated"));
737 }
738
739 #[test]
740 fn parse_default_firmware() {
741 let fw: FirmwareConfig = toml::from_str(DEFAULT_FIRMWARE_TOML).unwrap();
742 assert_eq!(fw.approvals.spending_threshold, 50.0);
743 assert!(fw.rules.len() >= 7);
744 assert!(fw.rules.iter().any(|r| r.rule_type == "must"));
745 assert!(fw.rules.iter().any(|r| r.rule_type == "must_not"));
746 }
747
748 #[test]
749 fn compose_full_personality_with_all_sections() {
750 let os: OsConfig = toml::from_str(DEFAULT_OS_TOML).unwrap();
751 let fw: FirmwareConfig = toml::from_str(DEFAULT_FIRMWARE_TOML).unwrap();
752 let full = compose_full_personality(Some(&os), Some(&fw), None, None);
753 assert!(full.contains("Roboticus"));
754 assert!(full.contains("YOU MUST:"));
755 assert!(full.contains("YOU MUST NOT:"));
756 }
757
758 #[test]
759 fn compose_full_personality_empty_when_no_files() {
760 let full = compose_full_personality(None, None, None, None);
761 assert!(full.is_empty());
762 }
763
764 #[test]
765 fn generate_os_toml_parses() {
766 let toml_str = generate_os_toml("TestBot", "casual", "initiative", "developer");
767 let os: OsConfig = toml::from_str(&toml_str).unwrap();
768 assert_eq!(os.identity.name, "TestBot");
769 assert_eq!(os.voice.formality, "casual");
770 assert!(os.prompt_text.contains("software development"));
771 }
772
773 #[test]
774 fn generate_firmware_with_custom_boundaries() {
775 let toml_str = generate_firmware_toml("Don't discuss politics\nNo medical advice");
776 let fw: FirmwareConfig = toml::from_str(&toml_str).unwrap();
777 assert!(fw.rules.iter().any(|r| r.rule.contains("politics")));
778 assert!(fw.rules.iter().any(|r| r.rule.contains("medical")));
779 }
780
781 #[test]
782 fn write_defaults_creates_files() {
783 let dir = tempfile::tempdir().unwrap();
784 write_defaults(dir.path()).unwrap();
785 assert!(dir.path().join("OS.toml").exists());
786 assert!(dir.path().join("FIRMWARE.toml").exists());
787 }
788
789 #[test]
790 fn load_roundtrip() {
791 let dir = tempfile::tempdir().unwrap();
792 write_defaults(dir.path()).unwrap();
793 let os = load_os(dir.path()).unwrap();
794 assert_eq!(os.identity.name, "Roboticus");
795 let fw = load_firmware(dir.path()).unwrap();
796 assert!(fw.rules.len() >= 7);
797 }
798
799 #[test]
800 fn compose_full_personality_includes_voice_when_non_default() {
801 let os_toml = r#"
802prompt_text = "I am a test bot."
803
804[identity]
805name = "TestBot"
806
807[voice]
808formality = "formal"
809proactiveness = "initiative"
810verbosity = "concise"
811humor = "dry"
812domain = "developer"
813"#;
814 let os: OsConfig = toml::from_str(os_toml).unwrap();
815 let full = compose_full_personality(Some(&os), None, None, None);
816 assert!(full.contains("I am a test bot."));
817 assert!(full.contains("## Voice Profile"));
818 assert!(full.contains("Formality: formal"));
819 assert!(full.contains("Proactiveness: initiative"));
820 assert!(full.contains("Domain: developer"));
821 assert!(!full.contains("Verbosity:"));
823 assert!(!full.contains("Humor:"));
824 }
825
826 #[test]
827 fn compose_full_personality_skips_voice_when_all_default() {
828 let os: OsConfig = toml::from_str(DEFAULT_OS_TOML).unwrap();
829 let full = compose_full_personality(Some(&os), None, None, None);
831 assert!(full.contains("## Voice Profile"));
832 assert!(full.contains("Humor: robotic"));
833 }
834
835 #[test]
836 fn voice_summary_none_when_all_default() {
837 let voice = OsVoice::default();
838 assert!(voice_summary(&voice).is_none());
839 }
840
841 #[test]
842 fn voice_summary_short_format() {
843 let voice = OsVoice {
844 formality: "casual".into(),
845 proactiveness: "initiative".into(),
846 verbosity: "verbose".into(),
847 humor: "witty".into(),
848 domain: "developer".into(),
849 };
850 let short = voice_summary_short(&voice);
851 assert_eq!(short, "casual / initiative / verbose / witty / developer");
852 }
853
854 #[test]
855 fn compose_identity_text_without_firmware() {
856 let os: OsConfig = toml::from_str(DEFAULT_OS_TOML).unwrap();
857 let fw: FirmwareConfig = toml::from_str(DEFAULT_FIRMWARE_TOML).unwrap();
858 let identity = compose_identity_text(Some(&os), None, None);
859 let firmware = compose_firmware_text(Some(&fw));
860 let combined = compose_full_personality(Some(&os), Some(&fw), None, None);
861
862 assert!(identity.contains("Roboticus"));
863 assert!(!identity.contains("YOU MUST"));
864 assert!(firmware.contains("YOU MUST"));
865 assert!(combined.contains("Roboticus"));
866 assert!(combined.contains("YOU MUST"));
867 }
868
869 #[test]
870 fn generate_operator_toml_roundtrip() {
871 let op = OperatorConfig {
872 identity: OperatorIdentity {
873 name: "Jon".into(),
874 role: "Founder".into(),
875 timezone: "US/Pacific".into(),
876 },
877 preferences: OperatorPreferences {
878 communication_channels: vec!["telegram".into(), "discord".into()],
879 work_hours: "9am-6pm".into(),
880 response_style: "concise".into(),
881 },
882 context: "Building an autonomous agent platform.".into(),
883 };
884 let toml_str = generate_operator_toml(&op).unwrap();
885 let parsed: OperatorConfig = toml::from_str(&toml_str).unwrap();
886 assert_eq!(parsed.identity.name, "Jon");
887 assert_eq!(parsed.identity.role, "Founder");
888 assert_eq!(parsed.preferences.communication_channels.len(), 2);
889 assert!(parsed.context.contains("autonomous agent"));
890 }
891
892 #[test]
893 fn generate_directives_toml_roundtrip() {
894 let dir = DirectivesConfig {
895 missions: vec![
896 Mission {
897 name: "Launch MVP".into(),
898 timeframe: "Q1 2026".into(),
899 priority: "high".into(),
900 description: "Ship the first public version.".into(),
901 },
902 Mission {
903 name: "Build community".into(),
904 timeframe: "ongoing".into(),
905 priority: "medium".into(),
906 description: "Grow the user base.".into(),
907 },
908 ],
909 context: "Early-stage startup.".into(),
910 };
911 let toml_str = generate_directives_toml(&dir).unwrap();
912 let parsed: DirectivesConfig = toml::from_str(&toml_str).unwrap();
913 assert_eq!(parsed.missions.len(), 2);
914 assert_eq!(parsed.missions[0].name, "Launch MVP");
915 assert_eq!(parsed.missions[1].priority, "medium");
916 assert!(parsed.context.contains("Early-stage"));
917 }
918
919 #[test]
920 fn parse_interview_output_extracts_toml_blocks() {
921 let llm_output = r#"Great, here are your personality files!
922
923**OS.toml**
924
925```toml
926prompt_text = "You are TestBot."
927
928[identity]
929name = "TestBot"
930version = "1.0"
931generated_by = "full-interview"
932
933[voice]
934formality = "casual"
935proactiveness = "suggest"
936verbosity = "concise"
937humor = "dry"
938domain = "general"
939```
940
941**FIRMWARE.toml**
942
943```toml
944[approvals]
945spending_threshold = 100.0
946require_confirmation = "always"
947
948[[rules]]
949type = "must"
950rule = "Be honest"
951```
952
953That's it! Ready to apply?
954"#;
955 let output = parse_interview_output(llm_output);
956 assert_eq!(output.file_count(), 2);
957 assert!(output.os_toml.is_some());
958 assert!(output.firmware_toml.is_some());
959 assert!(output.operator_toml.is_none());
960 assert!(output.directives_toml.is_none());
961 assert!(output.validate().is_ok());
962
963 let os: OsConfig = toml::from_str(output.os_toml.as_ref().unwrap()).unwrap();
964 assert_eq!(os.identity.name, "TestBot");
965 assert_eq!(os.identity.generated_by, "full-interview");
966 }
967
968 #[test]
969 fn parse_interview_output_invalid_toml_fails_validation() {
970 let llm_output = r#"
971**OS.toml**
972
973```toml
974this is not valid toml {{{
975```
976"#;
977 let output = parse_interview_output(llm_output);
978 assert_eq!(output.file_count(), 1);
979 let errors = output.validate().unwrap_err();
980 assert!(errors[0].contains("OS.toml"));
981 }
982
983 #[test]
984 fn interview_output_write_and_reload() {
985 let dir = tempfile::tempdir().unwrap();
986 let output = InterviewOutput {
987 os_toml: Some(DEFAULT_OS_TOML.to_string()),
988 firmware_toml: Some(DEFAULT_FIRMWARE_TOML.to_string()),
989 operator_toml: None,
990 directives_toml: None,
991 };
992 output.write_to_workspace(dir.path()).unwrap();
993 assert!(dir.path().join("OS.toml").exists());
994 assert!(dir.path().join("FIRMWARE.toml").exists());
995 assert!(!dir.path().join("OPERATOR.toml").exists());
996
997 let os = load_os(dir.path()).unwrap();
998 assert_eq!(os.identity.name, "Roboticus");
999 }
1000
1001 #[test]
1002 fn os_voice_default_matches_default_functions() {
1003 let voice = OsVoice::default();
1004 assert_eq!(voice.formality, "balanced");
1005 assert_eq!(voice.proactiveness, "suggest");
1006 assert_eq!(voice.verbosity, "concise");
1007 assert_eq!(voice.humor, "dry");
1008 assert_eq!(voice.domain, "general");
1009 }
1010
1011 #[test]
1012 fn load_returns_none_for_missing_files() {
1013 let dir = tempfile::tempdir().unwrap();
1014 assert!(load_os(dir.path()).is_none());
1015 assert!(load_firmware(dir.path()).is_none());
1016 assert!(load_operator(dir.path()).is_none());
1017 assert!(load_directives(dir.path()).is_none());
1018 }
1019
1020 #[test]
1021 fn load_operator_roundtrip() {
1022 let dir = tempfile::tempdir().unwrap();
1023 let op = OperatorConfig {
1024 identity: OperatorIdentity {
1025 name: "Alice".into(),
1026 role: "Engineer".into(),
1027 timezone: "UTC".into(),
1028 },
1029 preferences: OperatorPreferences::default(),
1030 context: "Works on backend systems.".into(),
1031 };
1032 let toml_str = generate_operator_toml(&op).unwrap();
1033 std::fs::write(dir.path().join("OPERATOR.toml"), &toml_str).unwrap();
1034 let loaded = load_operator(dir.path()).unwrap();
1035 assert_eq!(loaded.identity.name, "Alice");
1036 assert!(loaded.context.contains("backend"));
1037 }
1038
1039 #[test]
1040 fn load_directives_roundtrip() {
1041 let dir = tempfile::tempdir().unwrap();
1042 let directives = DirectivesConfig {
1043 missions: vec![Mission {
1044 name: "Ship v2".into(),
1045 timeframe: "Q2".into(),
1046 priority: "high".into(),
1047 description: "Major release.".into(),
1048 }],
1049 context: "Startup phase.".into(),
1050 };
1051 let toml_str = generate_directives_toml(&directives).unwrap();
1052 std::fs::write(dir.path().join("DIRECTIVES.toml"), &toml_str).unwrap();
1053 let loaded = load_directives(dir.path()).unwrap();
1054 assert_eq!(loaded.missions.len(), 1);
1055 assert_eq!(loaded.missions[0].name, "Ship v2");
1056 }
1057
1058 #[test]
1059 fn generate_os_toml_formal_wait_business() {
1060 let toml_str = generate_os_toml("FormalBot", "formal", "wait", "business");
1061 let os: OsConfig = toml::from_str(&toml_str).unwrap();
1062 assert_eq!(os.identity.name, "FormalBot");
1063 assert!(os.prompt_text.contains("professional, polished tone"));
1064 assert!(os.prompt_text.contains("wait for explicit instructions"));
1065 assert!(os.prompt_text.contains("business operations"));
1066 }
1067
1068 #[test]
1069 fn generate_os_toml_creative_domain() {
1070 let toml_str = generate_os_toml("Artisan", "balanced", "suggest", "creative");
1071 let os: OsConfig = toml::from_str(&toml_str).unwrap();
1072 assert!(os.prompt_text.contains("creative work"));
1073 }
1074
1075 #[test]
1076 fn generate_os_toml_research_domain() {
1077 let toml_str = generate_os_toml("Scholar", "balanced", "suggest", "research");
1078 let os: OsConfig = toml::from_str(&toml_str).unwrap();
1079 assert!(os.prompt_text.contains("research and analysis"));
1080 }
1081
1082 #[test]
1083 fn generate_os_toml_default_branches() {
1084 let toml_str = generate_os_toml("GenBot", "balanced", "suggest", "general");
1085 let os: OsConfig = toml::from_str(&toml_str).unwrap();
1086 assert!(os.prompt_text.contains("general-purpose assistant"));
1087 assert!(os.prompt_text.contains("professional and approachable"));
1088 }
1089
1090 #[test]
1091 fn generate_firmware_toml_empty_boundaries() {
1092 let toml_str = generate_firmware_toml("");
1093 let fw: FirmwareConfig = toml::from_str(&toml_str).unwrap();
1094 assert_eq!(fw.rules.len(), 5);
1095 }
1096
1097 #[test]
1098 fn compose_identity_text_includes_operator_context() {
1099 let op = OperatorConfig {
1100 context: "I run a fintech startup.".into(),
1101 ..OperatorConfig::default()
1102 };
1103 let text = compose_identity_text(None, Some(&op), None);
1104 assert!(text.contains("## Operator Context"));
1105 assert!(text.contains("fintech startup"));
1106 }
1107
1108 #[test]
1109 fn compose_identity_text_includes_directives() {
1110 let dir = DirectivesConfig {
1111 missions: vec![Mission {
1112 name: "Launch".into(),
1113 timeframe: "Q1".into(),
1114 priority: "high".into(),
1115 description: "Ship it.".into(),
1116 }],
1117 context: "Growth phase.".into(),
1118 };
1119 let text = compose_identity_text(None, None, Some(&dir));
1120 assert!(text.contains("## Active Directives"));
1121 assert!(text.contains("Growth phase"));
1122 assert!(text.contains("## Missions"));
1123 assert!(text.contains("**Launch** (Q1): Ship it."));
1124 }
1125
1126 #[test]
1127 fn compose_identity_text_mission_empty_timeframe_shows_ongoing() {
1128 let dir = DirectivesConfig {
1129 missions: vec![Mission {
1130 name: "Maintain".into(),
1131 timeframe: String::new(),
1132 priority: "low".into(),
1133 description: "Keep running.".into(),
1134 }],
1135 context: String::new(),
1136 };
1137 let text = compose_identity_text(None, None, Some(&dir));
1138 assert!(text.contains("(ongoing)"));
1139 }
1140
1141 #[test]
1142 fn compose_full_personality_firmware_only() {
1143 let fw: FirmwareConfig = toml::from_str(DEFAULT_FIRMWARE_TOML).unwrap();
1144 let full = compose_full_personality(None, Some(&fw), None, None);
1145 assert!(full.contains("Firmware"));
1146 assert!(!full.is_empty());
1147 }
1148
1149 #[test]
1150 fn compose_firmware_text_none_returns_empty() {
1151 assert!(compose_firmware_text(None).is_empty());
1152 }
1153
1154 #[test]
1155 fn compose_firmware_text_empty_rules_returns_empty() {
1156 let fw = FirmwareConfig {
1157 approvals: FirmwareApprovals::default(),
1158 rules: vec![],
1159 };
1160 assert!(compose_firmware_text(Some(&fw)).is_empty());
1161 }
1162
1163 #[test]
1164 fn firmware_approvals_default_values() {
1165 let approvals = FirmwareApprovals::default();
1166 assert_eq!(approvals.spending_threshold, 50.0);
1167 assert_eq!(approvals.require_confirmation, "risky");
1168 }
1169
1170 #[test]
1171 fn compose_identity_text_skips_empty_operator_context() {
1172 let op = OperatorConfig {
1173 context: String::new(),
1174 ..OperatorConfig::default()
1175 };
1176 let text = compose_identity_text(None, Some(&op), None);
1177 assert!(!text.contains("## Operator Context"));
1178 }
1179
1180 #[test]
1181 fn compose_identity_text_skips_empty_directives() {
1182 let dir = DirectivesConfig {
1183 missions: vec![],
1184 context: String::new(),
1185 };
1186 let text = compose_identity_text(None, None, Some(&dir));
1187 assert!(text.is_empty());
1188 }
1189
1190 #[test]
1193 fn default_voice_functions_return_expected() {
1194 assert_eq!(default_version(), "1.0");
1195 assert_eq!(default_generated_by(), "default");
1196 assert_eq!(default_formality(), "balanced");
1197 assert_eq!(default_proactiveness(), "suggest");
1198 assert_eq!(default_verbosity(), "concise");
1199 assert_eq!(default_humor(), "dry");
1200 assert_eq!(default_domain(), "general");
1201 }
1202
1203 #[test]
1204 fn default_firmware_functions_return_expected() {
1205 assert!((default_spending_threshold() - 50.0).abs() < f64::EPSILON);
1206 assert_eq!(default_require_confirmation(), "risky");
1207 }
1208
1209 #[test]
1212 fn os_identity_serde_defaults() {
1213 let toml_str = r#"
1214prompt_text = "Hello"
1215
1216[identity]
1217name = "Bot"
1218
1219[voice]
1220"#;
1221 let os: OsConfig = toml::from_str(toml_str).unwrap();
1222 assert_eq!(os.identity.version, "1.0");
1223 assert_eq!(os.identity.generated_by, "default");
1224 assert_eq!(os.voice.formality, "balanced");
1225 assert_eq!(os.voice.humor, "dry");
1226 }
1227
1228 #[test]
1229 fn firmware_approvals_serde_defaults() {
1230 let toml_str = r#"
1231[approvals]
1232
1233[[rules]]
1234type = "must"
1235rule = "Test rule"
1236"#;
1237 let fw: FirmwareConfig = toml::from_str(toml_str).unwrap();
1238 assert!((fw.approvals.spending_threshold - 50.0).abs() < f64::EPSILON);
1239 assert_eq!(fw.approvals.require_confirmation, "risky");
1240 }
1241
1242 #[test]
1245 fn parse_interview_output_all_four_blocks() {
1246 let llm_output = r#"Here are your files:
1247
1248**OS.toml**
1249
1250```toml
1251prompt_text = "You are TestBot."
1252
1253[identity]
1254name = "TestBot"
1255
1256[voice]
1257formality = "casual"
1258```
1259
1260**FIRMWARE.toml**
1261
1262```toml
1263[approvals]
1264spending_threshold = 100.0
1265require_confirmation = "always"
1266
1267[[rules]]
1268type = "must"
1269rule = "Be honest"
1270```
1271
1272**OPERATOR.toml**
1273
1274```toml
1275context = "Works on infrastructure."
1276
1277[identity]
1278name = "Alice"
1279role = "SRE"
1280timezone = "UTC"
1281
1282[preferences]
1283work_hours = "9-5"
1284response_style = "terse"
1285```
1286
1287**DIRECTIVES.toml**
1288
1289```toml
1290context = "Q1 focus."
1291
1292[[missions]]
1293name = "Ship v2"
1294timeframe = "Q1"
1295priority = "high"
1296description = "Major release."
1297```
1298"#;
1299 let output = parse_interview_output(llm_output);
1300 assert_eq!(output.file_count(), 4);
1301 assert!(output.os_toml.is_some());
1302 assert!(output.firmware_toml.is_some());
1303 assert!(output.operator_toml.is_some());
1304 assert!(output.directives_toml.is_some());
1305 assert!(output.validate().is_ok());
1306
1307 let op: OperatorConfig = toml::from_str(output.operator_toml.as_ref().unwrap()).unwrap();
1308 assert_eq!(op.identity.name, "Alice");
1309
1310 let dir: DirectivesConfig =
1311 toml::from_str(output.directives_toml.as_ref().unwrap()).unwrap();
1312 assert_eq!(dir.missions[0].name, "Ship v2");
1313 }
1314
1315 #[test]
1318 fn parse_interview_output_alternative_fence() {
1319 let llm_output = r#"
1321**OS.toml**
1322
1323```language=toml
1324prompt_text = "Hello."
1325
1326[identity]
1327name = "AltBot"
1328
1329[voice]
1330```
1331"#;
1332 let output = parse_interview_output(llm_output);
1333 assert!(output.os_toml.is_some());
1334 let os: OsConfig = toml::from_str(output.os_toml.as_ref().unwrap()).unwrap();
1335 assert_eq!(os.identity.name, "AltBot");
1336 }
1337
1338 #[test]
1341 fn validate_invalid_operator_toml() {
1342 let output = InterviewOutput {
1343 os_toml: None,
1344 firmware_toml: None,
1345 operator_toml: Some("{{invalid operator}}".into()),
1346 directives_toml: None,
1347 };
1348 let errors = output.validate().unwrap_err();
1349 assert!(errors.iter().any(|e| e.contains("OPERATOR.toml")));
1350 }
1351
1352 #[test]
1353 fn validate_invalid_directives_toml() {
1354 let output = InterviewOutput {
1355 os_toml: None,
1356 firmware_toml: None,
1357 operator_toml: None,
1358 directives_toml: Some("{{invalid directives}}".into()),
1359 };
1360 let errors = output.validate().unwrap_err();
1361 assert!(errors.iter().any(|e| e.contains("DIRECTIVES.toml")));
1362 }
1363
1364 #[test]
1365 fn validate_invalid_firmware_toml() {
1366 let output = InterviewOutput {
1367 os_toml: None,
1368 firmware_toml: Some("{{invalid firmware}}".into()),
1369 operator_toml: None,
1370 directives_toml: None,
1371 };
1372 let errors = output.validate().unwrap_err();
1373 assert!(errors.iter().any(|e| e.contains("FIRMWARE.toml")));
1374 }
1375
1376 #[test]
1377 fn validate_multiple_errors() {
1378 let output = InterviewOutput {
1379 os_toml: Some("{{bad}}".into()),
1380 firmware_toml: Some("{{bad}}".into()),
1381 operator_toml: Some("{{bad}}".into()),
1382 directives_toml: Some("{{bad}}".into()),
1383 };
1384 let errors = output.validate().unwrap_err();
1385 assert_eq!(errors.len(), 4);
1386 }
1387
1388 #[test]
1391 fn write_to_workspace_all_four_files() {
1392 let dir = tempfile::tempdir().unwrap();
1393 let op = OperatorConfig {
1394 identity: OperatorIdentity {
1395 name: "Bob".into(),
1396 role: "Dev".into(),
1397 timezone: "UTC".into(),
1398 },
1399 preferences: OperatorPreferences::default(),
1400 context: "Testing.".into(),
1401 };
1402 let directives = DirectivesConfig {
1403 missions: vec![Mission {
1404 name: "Test".into(),
1405 timeframe: "Q1".into(),
1406 priority: "high".into(),
1407 description: "A test mission.".into(),
1408 }],
1409 context: "Test context.".into(),
1410 };
1411 let output = InterviewOutput {
1412 os_toml: Some(DEFAULT_OS_TOML.into()),
1413 firmware_toml: Some(DEFAULT_FIRMWARE_TOML.into()),
1414 operator_toml: Some(generate_operator_toml(&op).unwrap()),
1415 directives_toml: Some(generate_directives_toml(&directives).unwrap()),
1416 };
1417 output.write_to_workspace(dir.path()).unwrap();
1418 assert!(dir.path().join("OS.toml").exists());
1419 assert!(dir.path().join("FIRMWARE.toml").exists());
1420 assert!(dir.path().join("OPERATOR.toml").exists());
1421 assert!(dir.path().join("DIRECTIVES.toml").exists());
1422
1423 let loaded_op = load_operator(dir.path()).unwrap();
1424 assert_eq!(loaded_op.identity.name, "Bob");
1425
1426 let loaded_dir = load_directives(dir.path()).unwrap();
1427 assert_eq!(loaded_dir.missions[0].name, "Test");
1428 }
1429
1430 #[test]
1433 fn generate_firmware_toml_boundaries_with_empty_lines() {
1434 let boundaries = "\nDo not hack\n\n\nNo spam\n";
1435 let toml_str = generate_firmware_toml(boundaries);
1436 let fw: FirmwareConfig = toml::from_str(&toml_str).unwrap();
1437 assert_eq!(fw.rules.len(), 7);
1439 assert!(fw.rules.iter().any(|r| r.rule.contains("hack")));
1440 assert!(fw.rules.iter().any(|r| r.rule.contains("spam")));
1441 }
1442
1443 #[test]
1444 fn generate_firmware_toml_boundaries_with_quotes() {
1445 let boundaries = r#"Don't say "hello" to strangers"#;
1446 let toml_str = generate_firmware_toml(boundaries);
1447 let fw: FirmwareConfig = toml::from_str(&toml_str).unwrap();
1448 assert_eq!(fw.rules.len(), 6);
1449 }
1450
1451 #[test]
1454 fn compose_full_personality_with_operator_and_directives() {
1455 let os: OsConfig = toml::from_str(DEFAULT_OS_TOML).unwrap();
1456 let fw: FirmwareConfig = toml::from_str(DEFAULT_FIRMWARE_TOML).unwrap();
1457 let op = OperatorConfig {
1458 context: "I build robots.".into(),
1459 ..OperatorConfig::default()
1460 };
1461 let dir = DirectivesConfig {
1462 missions: vec![Mission {
1463 name: "Deploy".into(),
1464 timeframe: "".into(),
1465 priority: "high".into(),
1466 description: "Deploy the app.".into(),
1467 }],
1468 context: "Production push.".into(),
1469 };
1470 let full = compose_full_personality(Some(&os), Some(&fw), Some(&op), Some(&dir));
1471 assert!(full.contains("Roboticus"));
1472 assert!(full.contains("YOU MUST"));
1473 assert!(full.contains("## Operator Context"));
1474 assert!(full.contains("I build robots"));
1475 assert!(full.contains("## Active Directives"));
1476 assert!(full.contains("Production push"));
1477 assert!(full.contains("## Missions"));
1478 assert!(full.contains("**Deploy** (ongoing)"));
1479 }
1480
1481 #[test]
1484 fn compose_identity_text_skips_empty_prompt_text() {
1485 let os_toml = r#"
1486prompt_text = ""
1487
1488[identity]
1489name = "EmptyBot"
1490
1491[voice]
1492formality = "formal"
1493"#;
1494 let os: OsConfig = toml::from_str(os_toml).unwrap();
1495 let text = compose_identity_text(Some(&os), None, None);
1496 assert!(text.contains("## Voice Profile"));
1498 assert!(!text.contains("EmptyBot"));
1499 }
1500
1501 #[test]
1504 fn file_count_zero_for_default() {
1505 let output = InterviewOutput::default();
1506 assert_eq!(output.file_count(), 0);
1507 }
1508
1509 #[test]
1510 fn file_count_three() {
1511 let output = InterviewOutput {
1512 os_toml: Some("x".into()),
1513 firmware_toml: None,
1514 operator_toml: Some("y".into()),
1515 directives_toml: Some("z".into()),
1516 };
1517 assert_eq!(output.file_count(), 3);
1518 }
1519
1520 #[test]
1523 fn operator_config_default_is_empty() {
1524 let op = OperatorConfig::default();
1525 assert!(op.identity.name.is_empty());
1526 assert!(op.identity.role.is_empty());
1527 assert!(op.identity.timezone.is_empty());
1528 assert!(op.preferences.communication_channels.is_empty());
1529 assert!(op.preferences.work_hours.is_empty());
1530 assert!(op.preferences.response_style.is_empty());
1531 assert!(op.context.is_empty());
1532 }
1533
1534 #[test]
1535 fn directives_config_default_is_empty() {
1536 let dir = DirectivesConfig::default();
1537 assert!(dir.missions.is_empty());
1538 assert!(dir.context.is_empty());
1539 }
1540
1541 #[test]
1544 fn parse_interview_output_no_blocks_returns_empty() {
1545 let output = parse_interview_output("Just some text without any TOML blocks.");
1546 assert_eq!(output.file_count(), 0);
1547 assert!(output.validate().is_ok());
1548 }
1549
1550 #[test]
1553 fn parse_interview_output_unknown_label_ignored() {
1554 let llm_output = r#"
1555**RANDOM.toml**
1556
1557```toml
1558key = "value"
1559```
1560"#;
1561 let output = parse_interview_output(llm_output);
1562 assert_eq!(output.file_count(), 0);
1563 }
1564
1565 #[test]
1568 fn load_os_returns_none_for_invalid_toml() {
1569 let dir = tempfile::tempdir().unwrap();
1570 std::fs::write(dir.path().join("OS.toml"), "{{invalid}}").unwrap();
1571 assert!(load_os(dir.path()).is_none());
1572 }
1573
1574 #[test]
1575 fn load_firmware_returns_none_for_invalid_toml() {
1576 let dir = tempfile::tempdir().unwrap();
1577 std::fs::write(dir.path().join("FIRMWARE.toml"), "{{invalid}}").unwrap();
1578 assert!(load_firmware(dir.path()).is_none());
1579 }
1580
1581 #[test]
1582 fn load_operator_returns_none_for_invalid_toml() {
1583 let dir = tempfile::tempdir().unwrap();
1584 std::fs::write(dir.path().join("OPERATOR.toml"), "{{invalid}}").unwrap();
1585 assert!(load_operator(dir.path()).is_none());
1586 }
1587
1588 #[test]
1589 fn load_directives_returns_none_for_invalid_toml() {
1590 let dir = tempfile::tempdir().unwrap();
1591 std::fs::write(dir.path().join("DIRECTIVES.toml"), "{{invalid}}").unwrap();
1592 assert!(load_directives(dir.path()).is_none());
1593 }
1594
1595 #[test]
1598 fn voice_summary_each_non_default_field() {
1599 let voice = OsVoice {
1601 proactiveness: "initiative".into(),
1602 ..OsVoice::default()
1603 };
1604 let summary = voice_summary(&voice).unwrap();
1605 assert!(summary.contains("Proactiveness: initiative"));
1606 assert!(!summary.contains("Formality:"));
1607
1608 let voice = OsVoice {
1610 verbosity: "verbose".into(),
1611 ..OsVoice::default()
1612 };
1613 let summary = voice_summary(&voice).unwrap();
1614 assert!(summary.contains("Verbosity: verbose"));
1615
1616 let voice = OsVoice {
1618 humor: "witty".into(),
1619 ..OsVoice::default()
1620 };
1621 let summary = voice_summary(&voice).unwrap();
1622 assert!(summary.contains("Humor: witty"));
1623
1624 let voice = OsVoice {
1626 domain: "security".into(),
1627 ..OsVoice::default()
1628 };
1629 let summary = voice_summary(&voice).unwrap();
1630 assert!(summary.contains("Domain: security"));
1631 }
1632}