1use crate::llm::LlmClient;
27use anyhow::Context;
28
29pub const SYSTEM_DEFAULT: &str = include_str!("../prompts/system_default.md");
34
35pub const CONTINUATION: &str = include_str!("../prompts/continuation.md");
38
39pub const SUBAGENT_EXPLORE: &str = include_str!("../prompts/subagent_explore.md");
45
46pub const SUBAGENT_PLAN: &str = include_str!("../prompts/subagent_plan.md");
48
49pub const SUBAGENT_TITLE: &str = include_str!("../prompts/subagent_title.md");
51
52pub const SUBAGENT_SUMMARY: &str = include_str!("../prompts/subagent_summary.md");
54
55pub const CONTEXT_COMPACT: &str = include_str!("../prompts/context_compact.md");
61
62pub const CONTEXT_SUMMARY_PREFIX: &str = include_str!("../prompts/context_summary_prefix.md");
64
65#[allow(dead_code)]
71pub const TITLE_GENERATE: &str = include_str!("../prompts/title_generate.md");
72
73pub const LLM_PLAN_SYSTEM: &str = include_str!("../prompts/llm_plan_system.md");
79
80pub const LLM_GOAL_EXTRACT_SYSTEM: &str = include_str!("../prompts/llm_goal_extract_system.md");
82
83pub const LLM_GOAL_CHECK_SYSTEM: &str = include_str!("../prompts/llm_goal_check_system.md");
85
86pub const PLAN_EXECUTE_GOAL: &str = include_str!("../prompts/plan_execute_goal.md");
92
93pub const PLAN_EXECUTE_STEP: &str = include_str!("../prompts/plan_execute_step.md");
95
96pub const PLAN_FALLBACK_STEP: &str = include_str!("../prompts/plan_fallback_step.md");
98
99pub const PLAN_PARALLEL_RESULTS: &str = include_str!("../prompts/plan_parallel_results.md");
101
102pub const TEAM_LEAD: &str = include_str!("../prompts/team_lead.md");
104
105pub const TEAM_REVIEWER: &str = include_str!("../prompts/team_reviewer.md");
107
108pub const SKILLS_CATALOG_HEADER: &str = include_str!("../prompts/skills_catalog_header.md");
110
111pub const BTW_SYSTEM: &str = include_str!("../prompts/btw_system.md");
120
121pub const AGENT_VERIFICATION: &str = include_str!("../prompts/agent_verification.md");
127
128pub const AGENT_VERIFICATION_RESTRICTIONS: &str =
130 include_str!("../prompts/agent_verification_restrictions.md");
131
132pub const INTENT_CLASSIFY_SYSTEM: &str = include_str!("../prompts/intent_classify_system.md");
138
139pub const SESSION_MEMORY_TEMPLATE: &str = include_str!("../prompts/system_session_memory.md");
145
146pub const PROMPT_SUGGESTION: &str = include_str!("../prompts/service_prompt_suggestion.md");
152
153pub const UNDERCOVER_INSTRUCTIONS: &str = include_str!("../prompts/undercover_instructions.md");
159
160use serde::{Deserialize, Serialize};
165
166#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
172pub enum PlanningMode {
173 #[default]
177 Auto,
178 Disabled,
180 Enabled,
182}
183
184impl PlanningMode {
185 pub fn should_plan(&self, message: &str) -> bool {
187 match self {
188 PlanningMode::Auto => AgentStyle::detect_from_message(message).requires_planning(),
189 PlanningMode::Enabled => true,
190 PlanningMode::Disabled => false,
191 }
192 }
193}
194
195#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
204pub enum AgentStyle {
205 #[default]
207 GeneralPurpose,
208 Plan,
211 Verification,
213 Explore,
216 CodeReview,
218}
219
220#[derive(Debug, Clone, Copy, PartialEq, Eq)]
222pub enum DetectionConfidence {
223 High,
225 Medium,
227 Low,
229}
230
231impl AgentStyle {
232 pub fn base_prompt(&self) -> &'static str {
234 match self {
235 AgentStyle::GeneralPurpose => SYSTEM_DEFAULT,
236 AgentStyle::Plan => SUBAGENT_PLAN,
237 AgentStyle::Verification => AGENT_VERIFICATION,
238 AgentStyle::Explore => SUBAGENT_EXPLORE,
239 AgentStyle::CodeReview => SYSTEM_DEFAULT, }
241 }
242
243 pub fn guidelines(&self) -> Option<&'static str> {
245 match self {
246 AgentStyle::GeneralPurpose => None,
247 AgentStyle::Plan => None, AgentStyle::Verification => None, AgentStyle::Explore => None, AgentStyle::CodeReview => Some(CODE_REVIEW_GUIDELINES),
251 }
252 }
253
254 pub fn description(&self) -> &'static str {
256 match self {
257 AgentStyle::GeneralPurpose => {
258 "General purpose coding agent for research and multi-step tasks"
259 }
260 AgentStyle::Plan => "Read-only planning and architecture analysis agent",
261 AgentStyle::Verification => "Adversarial verification specialist — tries to break code",
262 AgentStyle::Explore => "Fast read-only file search and codebase exploration agent",
263 AgentStyle::CodeReview => "Code review focused — analyzes quality and best practices",
264 }
265 }
266
267 pub fn requires_planning(&self) -> bool {
272 matches!(self, AgentStyle::Plan | AgentStyle::GeneralPurpose)
273 }
274
275 pub fn detect_with_confidence(message: &str) -> (Self, DetectionConfidence) {
282 let lower = message.to_lowercase();
283
284 if lower.contains("try to break")
288 || lower.contains("find vulnerabilities")
289 || lower.contains("adversarial")
290 || lower.contains("security audit")
291 {
292 return (AgentStyle::Verification, DetectionConfidence::High);
293 }
294
295 if lower.contains("help me plan")
297 || lower.contains("help me design")
298 || lower.contains("create a plan")
299 || lower.contains("implementation plan")
300 || lower.contains("step-by-step plan")
301 {
302 return (AgentStyle::Plan, DetectionConfidence::High);
303 }
304
305 if lower.contains("find all files")
307 || lower.contains("search for all")
308 || lower.contains("locate all")
309 {
310 return (AgentStyle::Explore, DetectionConfidence::High);
311 }
312
313 if lower.contains("verify")
317 || lower.contains("verification")
318 || lower.contains("break")
319 || lower.contains("debug")
320 || lower.contains("test")
321 || lower.contains("check if")
322 {
323 return (AgentStyle::Verification, DetectionConfidence::Medium);
324 }
325
326 if lower.contains("plan")
328 || lower.contains("design")
329 || lower.contains("architecture")
330 || lower.contains("approach")
331 {
332 return (AgentStyle::Plan, DetectionConfidence::Medium);
333 }
334
335 if lower.contains("find")
337 || lower.contains("search")
338 || lower.contains("where is")
339 || lower.contains("where's")
340 || lower.contains("locate")
341 || lower.contains("explore")
342 || lower.contains("look for")
343 {
344 return (AgentStyle::Explore, DetectionConfidence::Medium);
345 }
346
347 if lower.contains("review")
349 || lower.contains("code review")
350 || lower.contains("analyze")
351 || lower.contains("assess")
352 || lower.contains("quality")
353 || lower.contains("best practice")
354 {
355 return (AgentStyle::CodeReview, DetectionConfidence::Medium);
356 }
357
358 (AgentStyle::GeneralPurpose, DetectionConfidence::Low)
360 }
361
362 pub fn detect_from_message(message: &str) -> Self {
367 Self::detect_with_confidence(message).0
368 }
369
370 pub async fn detect_with_llm(llm: &dyn LlmClient, message: &str) -> anyhow::Result<Self> {
377 use crate::llm::Message;
378
379 let system = INTENT_CLASSIFY_SYSTEM;
380 let messages = vec![Message::user(message)];
381
382 let response = llm
383 .complete(&messages, Some(system), &[])
384 .await
385 .context("LLM intent classification failed")?;
386
387 let text = response.text().trim().to_lowercase();
388
389 let style = match text.as_str() {
390 "plan" => AgentStyle::Plan,
391 "explore" => AgentStyle::Explore,
392 "verification" => AgentStyle::Verification,
393 "codereview" | "code review" => AgentStyle::CodeReview,
394 _ => AgentStyle::GeneralPurpose,
395 };
396
397 Ok(style)
398 }
399}
400
401const CODE_REVIEW_GUIDELINES: &str = r#"## Code Review Focus
403
404When reviewing code, pay attention to:
405
4061. **Correctness** — Does the code do what it's supposed to do? Are edge cases handled?
4072. **Security** — Are there potential vulnerabilities (injection, auth bypass, data exposure)?
4083. **Performance** — Are there obvious inefficiencies (N+1 queries, unnecessary allocations)?
4094. **Maintainability** — Is the code readable? Are names clear? Is there appropriate documentation?
4105. **Best Practices** — Does it follow language/framework conventions? Are there anti-patterns?
411
412Be specific in your review. Quote the actual code you're referring to. Suggest concrete improvements with examples where possible.
413
414Remember: your job is to find issues, not to be nice. A code review that finds nothing is a missed opportunity."#;
415
416#[derive(Debug, Clone, Default)]
443pub struct SystemPromptSlots {
444 pub style: Option<AgentStyle>,
449
450 pub role: Option<String>,
455
456 pub guidelines: Option<String>,
460
461 pub response_style: Option<String>,
465
466 pub extra: Option<String>,
471}
472
473const DEFAULT_ROLE_LINE: &str = include_str!("../prompts/system_default_role_line.md");
475
476const DEFAULT_RESPONSE_FORMAT: &str = include_str!("../prompts/system_default_response_format.md");
478
479impl SystemPromptSlots {
480 pub fn build(&self) -> String {
488 self.build_with_style(self.style.unwrap_or_default())
489 }
490
491 pub fn build_with_message(&self, initial_message: &str) -> String {
496 let style = self
497 .style
498 .unwrap_or_else(|| AgentStyle::detect_from_message(initial_message));
499 self.build_with_style(style)
500 }
501
502 fn build_with_style(&self, style: AgentStyle) -> String {
504 let mut parts: Vec<String> = Vec::new();
505
506 let base_prompt = style.base_prompt().replace('\r', "");
509 let default_role_line = DEFAULT_ROLE_LINE.replace('\r', "");
510 let default_response_format = DEFAULT_RESPONSE_FORMAT.replace('\r', "");
511
512 let core = if let Some(ref role) = self.role {
516 if style == AgentStyle::GeneralPurpose {
517 let custom_role = format!(
518 "{}. You operate in an agentic loop: you\nthink, use tools, observe results, and keep working until the task is fully complete.",
519 role.trim_end_matches('.')
520 );
521 base_prompt.replace(&default_role_line, &custom_role)
522 } else {
523 format!("{}\n\n{}", role, base_prompt)
525 }
526 } else {
527 base_prompt
528 };
529
530 let core = if self.response_style.is_some() {
532 core.replace(&default_response_format, "")
533 .trim_end()
534 .to_string()
535 } else {
536 core.trim_end().to_string()
537 };
538
539 parts.push(core);
540
541 if let Some(ref style) = self.response_style {
543 parts.push(format!("## Response Format\n\n{}", style));
544 }
545
546 let style_guidelines = style.guidelines();
548 if style_guidelines.is_some() || self.guidelines.is_some() {
549 let mut guidelines_parts = Vec::new();
550 if let Some(sg) = style_guidelines {
551 guidelines_parts.push(sg.to_string());
552 }
553 if let Some(ref g) = self.guidelines {
554 guidelines_parts.push(g.clone());
555 }
556 parts.push(format!(
557 "## Guidelines\n\n{}",
558 guidelines_parts.join("\n\n")
559 ));
560 }
561
562 if let Some(ref extra) = self.extra {
564 parts.push(extra.clone());
565 }
566
567 parts.join("\n\n")
568 }
569
570 pub fn from_legacy(prompt: String) -> Self {
575 Self {
576 extra: Some(prompt),
577 ..Default::default()
578 }
579 }
580
581 pub fn is_empty(&self) -> bool {
583 self.style.is_none()
584 && self.role.is_none()
585 && self.guidelines.is_none()
586 && self.response_style.is_none()
587 && self.extra.is_none()
588 }
589
590 pub fn with_style(mut self, style: AgentStyle) -> Self {
592 self.style = Some(style);
593 self
594 }
595
596 pub fn with_role(mut self, role: impl Into<String>) -> Self {
598 self.role = Some(role.into());
599 self
600 }
601
602 pub fn with_guidelines(mut self, guidelines: impl Into<String>) -> Self {
604 self.guidelines = Some(guidelines.into());
605 self
606 }
607
608 pub fn with_response_style(mut self, style: impl Into<String>) -> Self {
610 self.response_style = Some(style.into());
611 self
612 }
613
614 pub fn with_extra(mut self, extra: impl Into<String>) -> Self {
616 self.extra = Some(extra.into());
617 self
618 }
619}
620
621pub fn render(template: &str, vars: &[(&str, &str)]) -> String {
627 let mut result = template.to_string();
628 for (key, value) in vars {
629 result = result.replace(&format!("{{{}}}", key), value);
630 }
631 result
632}
633
634#[cfg(test)]
635mod tests {
636 use super::*;
637
638 #[test]
639 fn test_all_prompts_loaded() {
640 assert!(!SYSTEM_DEFAULT.is_empty());
642 assert!(!CONTINUATION.is_empty());
643 assert!(!SUBAGENT_EXPLORE.is_empty());
644 assert!(!SUBAGENT_PLAN.is_empty());
645 assert!(!SUBAGENT_TITLE.is_empty());
646 assert!(!SUBAGENT_SUMMARY.is_empty());
647 assert!(!CONTEXT_COMPACT.is_empty());
648 assert!(!TITLE_GENERATE.is_empty());
649 assert!(!LLM_PLAN_SYSTEM.is_empty());
650 assert!(!LLM_GOAL_EXTRACT_SYSTEM.is_empty());
651 assert!(!LLM_GOAL_CHECK_SYSTEM.is_empty());
652 assert!(!TEAM_LEAD.is_empty());
653 assert!(!TEAM_REVIEWER.is_empty());
654 assert!(!SKILLS_CATALOG_HEADER.is_empty());
655 assert!(!BTW_SYSTEM.is_empty());
656 assert!(!PLAN_EXECUTE_GOAL.is_empty());
657 assert!(!PLAN_EXECUTE_STEP.is_empty());
658 assert!(!PLAN_FALLBACK_STEP.is_empty());
659 assert!(!PLAN_PARALLEL_RESULTS.is_empty());
660 }
661
662 #[test]
663 fn test_render_template() {
664 let result = render(
665 PLAN_EXECUTE_GOAL,
666 &[("goal", "Build app"), ("steps", "1. Init")],
667 );
668 assert!(result.contains("Build app"));
669 assert!(!result.contains("{goal}"));
670 }
671
672 #[test]
673 fn test_render_multiple_placeholders() {
674 let template = "Goal: {goal}\nCriteria: {criteria}\nState: {current_state}";
675 let result = render(
676 template,
677 &[
678 ("goal", "Build a REST API"),
679 ("criteria", "- Endpoint works\n- Tests pass"),
680 ("current_state", "API is deployed"),
681 ],
682 );
683 assert!(result.contains("Build a REST API"));
684 assert!(result.contains("Endpoint works"));
685 assert!(result.contains("API is deployed"));
686 }
687
688 #[test]
689 fn test_subagent_prompts_contain_guidelines() {
690 assert!(SUBAGENT_EXPLORE.contains("Guidelines"));
691 assert!(SUBAGENT_EXPLORE.contains("read-only"));
692 assert!(SUBAGENT_PLAN.contains("Guidelines"));
693 assert!(SUBAGENT_PLAN.contains("read-only"));
694 }
695
696 #[test]
697 fn test_context_summary_prefix() {
698 assert!(CONTEXT_SUMMARY_PREFIX.contains("Context Summary"));
699 }
700
701 #[test]
704 fn test_slots_default_builds_system_default() {
705 let slots = SystemPromptSlots::default();
706 let built = slots.build();
707 assert!(built.contains("Core Behaviour"));
708 assert!(built.contains("Tool Usage Strategy"));
709 assert!(built.contains("Completion Criteria"));
710 assert!(built.contains("Response Format"));
711 assert!(built.contains("A3S Code"));
712 }
713
714 #[test]
715 fn test_slots_custom_role_replaces_default() {
716 let slots = SystemPromptSlots {
717 role: Some("You are a senior Python developer".to_string()),
718 ..Default::default()
719 };
720 let built = slots.build();
721 assert!(built.contains("You are a senior Python developer"));
722 assert!(!built.contains("You are A3S Code"));
723 assert!(built.contains("Core Behaviour"));
725 assert!(built.contains("Tool Usage Strategy"));
726 }
727
728 #[test]
729 fn test_slots_custom_guidelines_appended() {
730 let slots = SystemPromptSlots {
731 guidelines: Some("Always use type hints. Follow PEP 8.".to_string()),
732 ..Default::default()
733 };
734 let built = slots.build();
735 assert!(built.contains("## Guidelines"));
736 assert!(built.contains("Always use type hints"));
737 assert!(built.contains("Core Behaviour"));
738 }
739
740 #[test]
741 fn test_slots_custom_response_style_replaces_default() {
742 let slots = SystemPromptSlots {
743 response_style: Some("Be concise. Use bullet points.".to_string()),
744 ..Default::default()
745 };
746 let built = slots.build();
747 assert!(built.contains("Be concise. Use bullet points."));
748 assert!(!built.contains("emit tool calls, no prose"));
750 assert!(built.contains("Core Behaviour"));
752 }
753
754 #[test]
755 fn test_slots_extra_appended() {
756 let slots = SystemPromptSlots {
757 extra: Some("Remember: always write tests first.".to_string()),
758 ..Default::default()
759 };
760 let built = slots.build();
761 assert!(built.contains("Remember: always write tests first."));
762 assert!(built.contains("Core Behaviour"));
763 }
764
765 #[test]
766 fn test_slots_from_legacy() {
767 let slots = SystemPromptSlots::from_legacy("You are a helpful assistant.".to_string());
768 let built = slots.build();
769 assert!(built.contains("You are a helpful assistant."));
771 assert!(built.contains("Core Behaviour"));
772 assert!(built.contains("Tool Usage Strategy"));
773 }
774
775 #[test]
776 fn test_slots_all_slots_combined() {
777 let slots = SystemPromptSlots {
778 style: None,
779 role: Some("You are a Rust expert".to_string()),
780 guidelines: Some("Use clippy. No unwrap.".to_string()),
781 response_style: Some("Short answers only.".to_string()),
782 extra: Some("Project uses tokio.".to_string()),
783 };
784 let built = slots.build();
785 assert!(built.contains("You are a Rust expert"));
786 assert!(built.contains("Core Behaviour"));
787 assert!(built.contains("## Guidelines"));
788 assert!(built.contains("Use clippy"));
789 assert!(built.contains("Short answers only"));
790 assert!(built.contains("Project uses tokio"));
791 assert!(!built.contains("emit tool calls, no prose"));
793 }
794
795 #[test]
796 fn test_slots_is_empty() {
797 assert!(SystemPromptSlots::default().is_empty());
798 assert!(!SystemPromptSlots {
799 role: Some("test".to_string()),
800 ..Default::default()
801 }
802 .is_empty());
803 assert!(!SystemPromptSlots {
804 style: Some(AgentStyle::Plan),
805 ..Default::default()
806 }
807 .is_empty());
808 }
809
810 #[test]
813 fn test_agent_style_default_is_general_purpose() {
814 assert_eq!(AgentStyle::default(), AgentStyle::GeneralPurpose);
815 }
816
817 #[test]
818 fn test_agent_style_base_prompt() {
819 assert_eq!(AgentStyle::GeneralPurpose.base_prompt(), SYSTEM_DEFAULT);
820 assert_eq!(AgentStyle::Plan.base_prompt(), SUBAGENT_PLAN);
821 assert_eq!(AgentStyle::Explore.base_prompt(), SUBAGENT_EXPLORE);
822 assert_eq!(AgentStyle::Verification.base_prompt(), AGENT_VERIFICATION);
823 assert_eq!(AgentStyle::CodeReview.base_prompt(), SYSTEM_DEFAULT);
825 }
826
827 #[test]
828 fn test_agent_style_guidelines() {
829 assert!(AgentStyle::GeneralPurpose.guidelines().is_none());
830 assert!(AgentStyle::Plan.guidelines().is_none()); assert!(AgentStyle::Explore.guidelines().is_none());
832 assert!(AgentStyle::Verification.guidelines().is_none());
833 assert!(AgentStyle::CodeReview.guidelines().is_some());
834 assert!(AgentStyle::CodeReview
835 .guidelines()
836 .unwrap()
837 .contains("Correctness"));
838 }
839
840 #[test]
841 fn test_agent_style_detect_plan() {
842 assert_eq!(
843 AgentStyle::detect_from_message("Help me plan a new feature"),
844 AgentStyle::Plan
845 );
846 assert_eq!(
847 AgentStyle::detect_from_message("Design the architecture for this"),
848 AgentStyle::Plan
849 );
850 assert_eq!(
851 AgentStyle::detect_from_message("What's the implementation approach?"),
852 AgentStyle::Plan
853 );
854 }
855
856 #[test]
857 fn test_agent_style_detect_verification() {
858 assert_eq!(
859 AgentStyle::detect_from_message("Verify that this works correctly"),
860 AgentStyle::Verification
861 );
862 assert_eq!(
863 AgentStyle::detect_from_message("Test the login flow"),
864 AgentStyle::Verification
865 );
866 assert_eq!(
867 AgentStyle::detect_from_message("Check if the API handles edge cases"),
868 AgentStyle::Verification
869 );
870 }
871
872 #[test]
873 fn test_agent_style_detect_explore() {
874 assert_eq!(
875 AgentStyle::detect_from_message("Find all files related to auth"),
876 AgentStyle::Explore
877 );
878 assert_eq!(
879 AgentStyle::detect_from_message("Where is the user model defined?"),
880 AgentStyle::Explore
881 );
882 assert_eq!(
883 AgentStyle::detect_from_message("Search for password hashing code"),
884 AgentStyle::Explore
885 );
886 }
887
888 #[test]
889 fn test_agent_style_detect_code_review() {
890 assert_eq!(
891 AgentStyle::detect_from_message("Review the PR changes"),
892 AgentStyle::CodeReview
893 );
894 assert_eq!(
895 AgentStyle::detect_from_message("Analyze this code for best practices"),
896 AgentStyle::CodeReview
897 );
898 assert_eq!(
899 AgentStyle::detect_from_message("Assess code quality"),
900 AgentStyle::CodeReview
901 );
902 }
903
904 #[test]
905 fn test_agent_style_detect_default_is_general_purpose() {
906 assert_eq!(
908 AgentStyle::detect_from_message("Implement the new feature"),
909 AgentStyle::GeneralPurpose
910 );
911 assert_eq!(
913 AgentStyle::detect_from_message("Write code for the API"),
914 AgentStyle::GeneralPurpose
915 );
916 }
917
918 #[test]
919 fn test_build_with_message_auto_detects_style() {
920 let slots = SystemPromptSlots::default();
921 let built = slots.build_with_message("Help me plan a new feature");
922 assert!(built.contains("planning agent") || built.contains("READ-ONLY"));
924 }
925
926 #[test]
927 fn test_build_with_message_explicit_style_overrides() {
928 let slots = SystemPromptSlots {
929 style: Some(AgentStyle::Verification),
930 ..Default::default()
931 };
932 let built = slots.build_with_message("Help me plan a new feature");
933 assert!(built.contains("verification specialist") || built.contains("try to break"));
935 }
936
937 #[test]
938 fn test_build_with_message_plan_style() {
939 let slots = SystemPromptSlots::default();
940 let built = slots.build_with_message("Design the system architecture");
941 assert!(built.contains("planning agent") || built.contains("READ-ONLY"));
942 }
943
944 #[test]
945 fn test_build_with_message_explore_style() {
946 let slots = SystemPromptSlots::default();
947 let built = slots.build_with_message("Find all authentication files");
948 assert!(built.contains("exploration agent") || built.contains("explore"));
949 }
950
951 #[test]
952 fn test_build_with_message_code_review_style() {
953 let slots = SystemPromptSlots::default();
954 let built = slots.build_with_message("Review this code");
955 assert!(built.contains("Correctness") || built.contains("Code Review"));
957 }
958
959 #[test]
960 fn test_builder_methods() {
961 let slots = SystemPromptSlots::default()
962 .with_style(AgentStyle::Plan)
963 .with_role("You are a Python expert")
964 .with_guidelines("Use type hints")
965 .with_response_style("Be brief")
966 .with_extra("Additional instructions");
967
968 assert_eq!(slots.style, Some(AgentStyle::Plan));
969 assert_eq!(slots.role, Some("You are a Python expert".to_string()));
970 assert_eq!(slots.guidelines, Some("Use type hints".to_string()));
971 assert_eq!(slots.response_style, Some("Be brief".to_string()));
972 assert_eq!(slots.extra, Some("Additional instructions".to_string()));
973
974 let built = slots.build();
975 assert!(built.contains("Python expert"));
976 assert!(built.contains("Use type hints"));
977 assert!(built.contains("Be brief"));
978 assert!(built.contains("Additional instructions"));
979 }
980
981 #[test]
982 fn test_code_review_guidelines_appended() {
983 let slots = SystemPromptSlots {
984 style: Some(AgentStyle::CodeReview),
985 ..Default::default()
986 };
987 let built = slots.build();
988 assert!(built.contains("Correctness"));
989 assert!(built.contains("Security"));
990 assert!(built.contains("Performance"));
991 assert!(built.contains("Maintainability"));
992 }
993}