1use crate::llm::LlmClient;
28use anyhow::Context;
29
30pub const SYSTEM_DEFAULT: &str = include_str!("../prompts/system_default.md");
35
36pub const CONTINUATION: &str = include_str!("../prompts/continuation.md");
39
40pub const SUBAGENT_EXPLORE: &str = include_str!("../prompts/subagent_explore.md");
46
47pub const SUBAGENT_PLAN: &str = include_str!("../prompts/subagent_plan.md");
49
50pub const SUBAGENT_CODE_REVIEW: &str = include_str!("../prompts/subagent_code_review.md");
52
53pub const SUBAGENT_TITLE: &str = include_str!("../prompts/subagent_title.md");
55
56pub const SUBAGENT_SUMMARY: &str = include_str!("../prompts/subagent_summary.md");
58
59pub const CONTEXT_COMPACT: &str = include_str!("../prompts/context_compact.md");
65
66pub const CONTEXT_SUMMARY_PREFIX: &str = include_str!("../prompts/context_summary_prefix.md");
68
69#[allow(dead_code)]
75pub const TITLE_GENERATE: &str = include_str!("../prompts/title_generate.md");
76
77pub const LLM_PLAN_SYSTEM: &str = include_str!("../prompts/llm_plan_system.md");
83
84pub const LLM_GOAL_EXTRACT_SYSTEM: &str = include_str!("../prompts/llm_goal_extract_system.md");
86
87pub const LLM_GOAL_CHECK_SYSTEM: &str = include_str!("../prompts/llm_goal_check_system.md");
89
90pub const PRE_ANALYSIS_SYSTEM: &str = include_str!("../prompts/pre_analysis_system.md");
92
93pub const PLAN_EXECUTE_GOAL: &str = include_str!("../prompts/plan_execute_goal.md");
99
100pub const PLAN_EXECUTE_STEP: &str = include_str!("../prompts/plan_execute_step.md");
102
103pub const PLAN_FALLBACK_STEP: &str = include_str!("../prompts/plan_fallback_step.md");
105
106#[allow(dead_code)]
108pub const PLAN_PARALLEL_RESULTS: &str = include_str!("../prompts/plan_parallel_results.md");
109
110pub const TEAM_LEAD: &str = include_str!("../prompts/team_lead.md");
112
113pub const TEAM_REVIEWER: &str = include_str!("../prompts/team_reviewer.md");
115
116pub const SKILLS_CATALOG_HEADER: &str = include_str!("../prompts/skills_catalog_header.md");
118
119pub const BTW_SYSTEM: &str = include_str!("../prompts/btw_system.md");
128
129pub const AGENT_VERIFICATION: &str = include_str!("../prompts/agent_verification.md");
135
136pub const AGENT_VERIFICATION_RESTRICTIONS: &str =
138 include_str!("../prompts/agent_verification_restrictions.md");
139
140pub const INTENT_CLASSIFY_SYSTEM: &str = include_str!("../prompts/intent_classify_system.md");
146
147pub const SESSION_MEMORY_TEMPLATE: &str = include_str!("../prompts/system_session_memory.md");
153
154pub const PROMPT_SUGGESTION: &str = include_str!("../prompts/service_prompt_suggestion.md");
160
161pub const UNDERCOVER_INSTRUCTIONS: &str = include_str!("../prompts/undercover_instructions.md");
167
168use serde::{Deserialize, Serialize};
173
174#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
180pub enum PlanningMode {
181 #[default]
185 Auto,
186 Disabled,
188 Enabled,
190}
191
192impl PlanningMode {
193 pub fn should_plan(&self, message: &str) -> bool {
195 match self {
196 PlanningMode::Auto => AgentStyle::detect_from_message(message).requires_planning(),
197 PlanningMode::Enabled => true,
198 PlanningMode::Disabled => false,
199 }
200 }
201}
202
203#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
212pub enum AgentStyle {
213 #[default]
215 GeneralPurpose,
216 Plan,
219 Verification,
221 Explore,
224 CodeReview,
226}
227
228#[derive(Debug, Clone, Copy, PartialEq, Eq)]
230pub enum DetectionConfidence {
231 High,
233 Medium,
235 Low,
237}
238
239impl AgentStyle {
240 pub fn base_prompt(&self) -> &'static str {
242 match self {
243 AgentStyle::GeneralPurpose => SYSTEM_DEFAULT,
244 AgentStyle::Plan => SUBAGENT_PLAN,
245 AgentStyle::Verification => AGENT_VERIFICATION,
246 AgentStyle::Explore => SUBAGENT_EXPLORE,
247 AgentStyle::CodeReview => SYSTEM_DEFAULT, }
249 }
250
251 pub fn guidelines(&self) -> Option<&'static str> {
253 match self {
254 AgentStyle::GeneralPurpose => None,
255 AgentStyle::Plan => None, AgentStyle::Verification => None, AgentStyle::Explore => None, AgentStyle::CodeReview => Some(CODE_REVIEW_GUIDELINES),
259 }
260 }
261
262 pub fn description(&self) -> &'static str {
264 match self {
265 AgentStyle::GeneralPurpose => {
266 "General purpose coding agent for research and multi-step tasks"
267 }
268 AgentStyle::Plan => "Read-only planning and architecture analysis agent",
269 AgentStyle::Verification => "Adversarial verification specialist — tries to break code",
270 AgentStyle::Explore => "Fast read-only file search and codebase exploration agent",
271 AgentStyle::CodeReview => "Code review focused — analyzes quality and best practices",
272 }
273 }
274
275 pub fn builtin_agent_name(&self) -> &'static str {
277 match self {
278 AgentStyle::GeneralPurpose => "general",
279 AgentStyle::Plan => "plan",
280 AgentStyle::Verification => "verification",
281 AgentStyle::Explore => "explore",
282 AgentStyle::CodeReview => "review",
283 }
284 }
285
286 pub fn runtime_mode(&self) -> &'static str {
288 match self {
289 AgentStyle::GeneralPurpose => "general",
290 AgentStyle::Plan => "planning",
291 AgentStyle::Verification => "verification",
292 AgentStyle::Explore => "explore",
293 AgentStyle::CodeReview => "code_review",
294 }
295 }
296
297 pub fn requires_planning(&self) -> bool {
302 matches!(self, AgentStyle::Plan | AgentStyle::GeneralPurpose)
303 }
304
305 pub fn detect_with_confidence(message: &str) -> (Self, DetectionConfidence) {
312 if message
316 .chars()
317 .any(|c| ('\u{4e00}'..='\u{9fff}').contains(&c))
318 {
319 return (AgentStyle::GeneralPurpose, DetectionConfidence::Low);
320 }
321
322 let lower = message.to_lowercase();
323
324 if lower.contains("try to break")
328 || lower.contains("find vulnerabilities")
329 || lower.contains("adversarial")
330 || lower.contains("security audit")
331 {
332 return (AgentStyle::Verification, DetectionConfidence::High);
333 }
334
335 if lower.contains("help me plan")
337 || lower.contains("help me design")
338 || lower.contains("create a plan")
339 || lower.contains("implementation plan")
340 || lower.contains("step-by-step plan")
341 {
342 return (AgentStyle::Plan, DetectionConfidence::High);
343 }
344
345 if lower.contains("find all files")
347 || lower.contains("search for all")
348 || lower.contains("locate all")
349 {
350 return (AgentStyle::Explore, DetectionConfidence::High);
351 }
352
353 if lower.contains("verify")
357 || lower.contains("verification")
358 || lower.contains("break")
359 || lower.contains("debug")
360 || lower.contains("test")
361 || lower.contains("check if")
362 {
363 return (AgentStyle::Verification, DetectionConfidence::Medium);
364 }
365
366 if lower.contains("plan")
368 || lower.contains("design")
369 || lower.contains("architecture")
370 || lower.contains("approach")
371 {
372 return (AgentStyle::Plan, DetectionConfidence::Medium);
373 }
374
375 if lower.contains("find")
377 || lower.contains("search")
378 || lower.contains("where is")
379 || lower.contains("where's")
380 || lower.contains("locate")
381 || lower.contains("explore")
382 || lower.contains("look for")
383 {
384 return (AgentStyle::Explore, DetectionConfidence::Medium);
385 }
386
387 if lower.contains("review")
389 || lower.contains("code review")
390 || lower.contains("analyze")
391 || lower.contains("assess")
392 || lower.contains("quality")
393 || lower.contains("best practice")
394 {
395 return (AgentStyle::CodeReview, DetectionConfidence::Medium);
396 }
397
398 (AgentStyle::GeneralPurpose, DetectionConfidence::Low)
400 }
401
402 pub fn detect_from_message(message: &str) -> Self {
407 Self::detect_with_confidence(message).0
408 }
409
410 pub async fn detect_with_llm(llm: &dyn LlmClient, message: &str) -> anyhow::Result<Self> {
417 use crate::llm::Message;
418
419 let system = INTENT_CLASSIFY_SYSTEM;
420 let messages = vec![Message::user(message)];
421
422 let response = llm
423 .complete(&messages, Some(system), &[])
424 .await
425 .context("LLM intent classification failed")?;
426
427 let text = response.text().trim().to_lowercase();
428
429 let style = match text.as_str() {
430 "plan" => AgentStyle::Plan,
431 "explore" => AgentStyle::Explore,
432 "verification" => AgentStyle::Verification,
433 "codereview" | "code review" => AgentStyle::CodeReview,
434 _ => AgentStyle::GeneralPurpose,
435 };
436
437 Ok(style)
438 }
439}
440
441const CODE_REVIEW_GUIDELINES: &str = r#"## Code Review Focus
443
444When reviewing code, pay attention to:
445
4461. **Correctness** — Does the code do what it's supposed to do? Are edge cases handled?
4472. **Security** — Are there potential vulnerabilities (injection, auth bypass, data exposure)?
4483. **Performance** — Are there obvious inefficiencies (N+1 queries, unnecessary allocations)?
4494. **Maintainability** — Is the code readable? Are names clear? Is there appropriate documentation?
4505. **Best Practices** — Does it follow language/framework conventions? Are there anti-patterns?
451
452Be specific in your review. Quote the actual code you're referring to. Suggest concrete improvements with examples where possible.
453
454Remember: your job is to find issues, not to be nice. A code review that finds nothing is a missed opportunity."#;
455
456#[derive(Debug, Clone, Default)]
483pub struct SystemPromptSlots {
484 pub style: Option<AgentStyle>,
489
490 pub role: Option<String>,
495
496 pub guidelines: Option<String>,
500
501 pub response_style: Option<String>,
505
506 pub extra: Option<String>,
511}
512
513const DEFAULT_ROLE_LINE: &str = include_str!("../prompts/system_default_role_line.md");
515
516const DEFAULT_RESPONSE_FORMAT: &str = include_str!("../prompts/system_default_response_format.md");
518
519impl SystemPromptSlots {
520 pub fn build(&self) -> String {
528 self.build_with_style(self.style.unwrap_or_default())
529 }
530
531 pub fn build_with_message(&self, initial_message: &str) -> String {
536 let style = self
537 .style
538 .unwrap_or_else(|| AgentStyle::detect_from_message(initial_message));
539 self.build_with_style(style)
540 }
541
542 fn build_with_style(&self, style: AgentStyle) -> String {
544 let mut parts: Vec<String> = Vec::new();
545
546 let base_prompt = style.base_prompt().replace('\r', "");
549 let default_role_line = DEFAULT_ROLE_LINE.replace('\r', "");
550 let default_response_format = DEFAULT_RESPONSE_FORMAT.replace('\r', "");
551
552 let core = if let Some(ref role) = self.role {
556 if style == AgentStyle::GeneralPurpose {
557 let custom_role = format!(
558 "{}. You operate in an agentic loop: you\nthink, use tools, observe results, and keep working until the task is fully complete.",
559 role.trim_end_matches('.')
560 );
561 base_prompt.replace(&default_role_line, &custom_role)
562 } else {
563 format!("{}\n\n{}", role, base_prompt)
565 }
566 } else {
567 base_prompt
568 };
569
570 let core = if self.response_style.is_some() {
572 core.replace(&default_response_format, "")
573 .trim_end()
574 .to_string()
575 } else {
576 core.trim_end().to_string()
577 };
578
579 parts.push(core);
580
581 if let Some(ref style) = self.response_style {
583 parts.push(format!("## Response Format\n\n{}", style));
584 }
585
586 let style_guidelines = style.guidelines();
588 if style_guidelines.is_some() || self.guidelines.is_some() {
589 let mut guidelines_parts = Vec::new();
590 if let Some(sg) = style_guidelines {
591 guidelines_parts.push(sg.to_string());
592 }
593 if let Some(ref g) = self.guidelines {
594 guidelines_parts.push(g.clone());
595 }
596 parts.push(format!(
597 "## Guidelines\n\n{}",
598 guidelines_parts.join("\n\n")
599 ));
600 }
601
602 if let Some(ref extra) = self.extra {
604 parts.push(extra.clone());
605 }
606
607 parts.join("\n\n")
608 }
609
610 pub fn from_legacy(prompt: String) -> Self {
615 Self {
616 extra: Some(prompt),
617 ..Default::default()
618 }
619 }
620
621 pub fn is_empty(&self) -> bool {
623 self.style.is_none()
624 && self.role.is_none()
625 && self.guidelines.is_none()
626 && self.response_style.is_none()
627 && self.extra.is_none()
628 }
629
630 pub fn with_style(mut self, style: AgentStyle) -> Self {
632 self.style = Some(style);
633 self
634 }
635
636 pub fn with_role(mut self, role: impl Into<String>) -> Self {
638 self.role = Some(role.into());
639 self
640 }
641
642 pub fn with_guidelines(mut self, guidelines: impl Into<String>) -> Self {
644 self.guidelines = Some(guidelines.into());
645 self
646 }
647
648 pub fn with_response_style(mut self, style: impl Into<String>) -> Self {
650 self.response_style = Some(style.into());
651 self
652 }
653
654 pub fn with_extra(mut self, extra: impl Into<String>) -> Self {
656 self.extra = Some(extra.into());
657 self
658 }
659}
660
661pub fn render(template: &str, vars: &[(&str, &str)]) -> String {
667 let mut result = template.to_string();
668 for (key, value) in vars {
669 result = result.replace(&format!("{{{}}}", key), value);
670 }
671 result
672}
673
674#[cfg(test)]
675mod tests {
676 use super::*;
677
678 #[test]
679 fn test_all_prompts_loaded() {
680 assert!(!SYSTEM_DEFAULT.is_empty());
682 assert!(!CONTINUATION.is_empty());
683 assert!(!SUBAGENT_EXPLORE.is_empty());
684 assert!(!SUBAGENT_PLAN.is_empty());
685 assert!(!SUBAGENT_CODE_REVIEW.is_empty());
686 assert!(!SUBAGENT_TITLE.is_empty());
687 assert!(!SUBAGENT_SUMMARY.is_empty());
688 assert!(!CONTEXT_COMPACT.is_empty());
689 assert!(!TITLE_GENERATE.is_empty());
690 assert!(!LLM_PLAN_SYSTEM.is_empty());
691 assert!(!LLM_GOAL_EXTRACT_SYSTEM.is_empty());
692 assert!(!LLM_GOAL_CHECK_SYSTEM.is_empty());
693 assert!(!TEAM_LEAD.is_empty());
694 assert!(!TEAM_REVIEWER.is_empty());
695 assert!(!SKILLS_CATALOG_HEADER.is_empty());
696 assert!(!BTW_SYSTEM.is_empty());
697 assert!(!PLAN_EXECUTE_GOAL.is_empty());
698 assert!(!PLAN_EXECUTE_STEP.is_empty());
699 assert!(!PLAN_FALLBACK_STEP.is_empty());
700 assert!(!PLAN_PARALLEL_RESULTS.is_empty());
701 }
702
703 #[test]
704 fn test_render_template() {
705 let result = render(
706 PLAN_EXECUTE_GOAL,
707 &[("goal", "Build app"), ("steps", "1. Init")],
708 );
709 assert!(result.contains("Build app"));
710 assert!(!result.contains("{goal}"));
711 }
712
713 #[test]
714 fn test_render_multiple_placeholders() {
715 let template = "Goal: {goal}\nCriteria: {criteria}\nState: {current_state}";
716 let result = render(
717 template,
718 &[
719 ("goal", "Build a REST API"),
720 ("criteria", "- Endpoint works\n- Tests pass"),
721 ("current_state", "API is deployed"),
722 ],
723 );
724 assert!(result.contains("Build a REST API"));
725 assert!(result.contains("Endpoint works"));
726 assert!(result.contains("API is deployed"));
727 }
728
729 #[test]
730 fn test_subagent_prompts_contain_guidelines() {
731 assert!(SUBAGENT_EXPLORE.contains("Guidelines"));
732 assert!(SUBAGENT_EXPLORE.contains("read-only"));
733 assert!(SUBAGENT_PLAN.contains("Guidelines"));
734 assert!(SUBAGENT_PLAN.contains("read-only"));
735 }
736
737 #[test]
738 fn test_context_summary_prefix() {
739 assert!(CONTEXT_SUMMARY_PREFIX.contains("Context Summary"));
740 }
741
742 #[test]
745 fn test_slots_default_builds_system_default() {
746 let slots = SystemPromptSlots::default();
747 let built = slots.build();
748 assert!(built.contains("Core Behaviour"));
749 assert!(built.contains("Tool Usage Strategy"));
750 assert!(built.contains("Completion Criteria"));
751 assert!(built.contains("Response Format"));
752 assert!(built.contains("A3S Code"));
753 }
754
755 #[test]
756 fn test_slots_custom_role_replaces_default() {
757 let slots = SystemPromptSlots {
758 role: Some("You are a senior Python developer".to_string()),
759 ..Default::default()
760 };
761 let built = slots.build();
762 assert!(built.contains("You are a senior Python developer"));
763 assert!(!built.contains("You are A3S Code"));
764 assert!(built.contains("Core Behaviour"));
766 assert!(built.contains("Tool Usage Strategy"));
767 }
768
769 #[test]
770 fn test_slots_custom_guidelines_appended() {
771 let slots = SystemPromptSlots {
772 guidelines: Some("Always use type hints. Follow PEP 8.".to_string()),
773 ..Default::default()
774 };
775 let built = slots.build();
776 assert!(built.contains("## Guidelines"));
777 assert!(built.contains("Always use type hints"));
778 assert!(built.contains("Core Behaviour"));
779 }
780
781 #[test]
782 fn test_slots_custom_response_style_replaces_default() {
783 let slots = SystemPromptSlots {
784 response_style: Some("Be concise. Use bullet points.".to_string()),
785 ..Default::default()
786 };
787 let built = slots.build();
788 assert!(built.contains("Be concise. Use bullet points."));
789 assert!(!built.contains("emit tool calls, no prose"));
791 assert!(built.contains("Core Behaviour"));
793 }
794
795 #[test]
796 fn test_slots_extra_appended() {
797 let slots = SystemPromptSlots {
798 extra: Some("Remember: always write tests first.".to_string()),
799 ..Default::default()
800 };
801 let built = slots.build();
802 assert!(built.contains("Remember: always write tests first."));
803 assert!(built.contains("Core Behaviour"));
804 }
805
806 #[test]
807 fn test_slots_from_legacy() {
808 let slots = SystemPromptSlots::from_legacy("You are a helpful assistant.".to_string());
809 let built = slots.build();
810 assert!(built.contains("You are a helpful assistant."));
812 assert!(built.contains("Core Behaviour"));
813 assert!(built.contains("Tool Usage Strategy"));
814 }
815
816 #[test]
817 fn test_slots_all_slots_combined() {
818 let slots = SystemPromptSlots {
819 style: None,
820 role: Some("You are a Rust expert".to_string()),
821 guidelines: Some("Use clippy. No unwrap.".to_string()),
822 response_style: Some("Short answers only.".to_string()),
823 extra: Some("Project uses tokio.".to_string()),
824 };
825 let built = slots.build();
826 assert!(built.contains("You are a Rust expert"));
827 assert!(built.contains("Core Behaviour"));
828 assert!(built.contains("## Guidelines"));
829 assert!(built.contains("Use clippy"));
830 assert!(built.contains("Short answers only"));
831 assert!(built.contains("Project uses tokio"));
832 assert!(!built.contains("emit tool calls, no prose"));
834 }
835
836 #[test]
837 fn test_slots_is_empty() {
838 assert!(SystemPromptSlots::default().is_empty());
839 assert!(!SystemPromptSlots {
840 role: Some("test".to_string()),
841 ..Default::default()
842 }
843 .is_empty());
844 assert!(!SystemPromptSlots {
845 style: Some(AgentStyle::Plan),
846 ..Default::default()
847 }
848 .is_empty());
849 }
850
851 #[test]
854 fn test_agent_style_default_is_general_purpose() {
855 assert_eq!(AgentStyle::default(), AgentStyle::GeneralPurpose);
856 }
857
858 #[test]
859 fn test_agent_style_base_prompt() {
860 assert_eq!(AgentStyle::GeneralPurpose.base_prompt(), SYSTEM_DEFAULT);
861 assert_eq!(AgentStyle::Plan.base_prompt(), SUBAGENT_PLAN);
862 assert_eq!(AgentStyle::Explore.base_prompt(), SUBAGENT_EXPLORE);
863 assert_eq!(AgentStyle::Verification.base_prompt(), AGENT_VERIFICATION);
864 assert_eq!(AgentStyle::CodeReview.base_prompt(), SYSTEM_DEFAULT);
866 }
867
868 #[test]
869 fn test_agent_style_guidelines() {
870 assert!(AgentStyle::GeneralPurpose.guidelines().is_none());
871 assert!(AgentStyle::Plan.guidelines().is_none()); assert!(AgentStyle::Explore.guidelines().is_none());
873 assert!(AgentStyle::Verification.guidelines().is_none());
874 assert!(AgentStyle::CodeReview.guidelines().is_some());
875 assert!(AgentStyle::CodeReview
876 .guidelines()
877 .unwrap()
878 .contains("Correctness"));
879 }
880
881 #[test]
882 fn test_agent_style_builtin_agent_name_mapping() {
883 assert_eq!(AgentStyle::GeneralPurpose.builtin_agent_name(), "general");
884 assert_eq!(AgentStyle::Plan.builtin_agent_name(), "plan");
885 assert_eq!(AgentStyle::Explore.builtin_agent_name(), "explore");
886 assert_eq!(
887 AgentStyle::Verification.builtin_agent_name(),
888 "verification"
889 );
890 assert_eq!(AgentStyle::CodeReview.builtin_agent_name(), "review");
891 }
892
893 #[test]
894 fn test_agent_style_runtime_mode_mapping() {
895 assert_eq!(AgentStyle::GeneralPurpose.runtime_mode(), "general");
896 assert_eq!(AgentStyle::Plan.runtime_mode(), "planning");
897 assert_eq!(AgentStyle::Explore.runtime_mode(), "explore");
898 assert_eq!(AgentStyle::Verification.runtime_mode(), "verification");
899 assert_eq!(AgentStyle::CodeReview.runtime_mode(), "code_review");
900 }
901
902 #[test]
903 fn test_agent_style_detect_plan() {
904 assert_eq!(
905 AgentStyle::detect_from_message("Help me plan a new feature"),
906 AgentStyle::Plan
907 );
908 assert_eq!(
909 AgentStyle::detect_from_message("Design the architecture for this"),
910 AgentStyle::Plan
911 );
912 assert_eq!(
913 AgentStyle::detect_from_message("What's the implementation approach?"),
914 AgentStyle::Plan
915 );
916 }
917
918 #[test]
919 fn test_agent_style_detect_verification() {
920 assert_eq!(
921 AgentStyle::detect_from_message("Verify that this works correctly"),
922 AgentStyle::Verification
923 );
924 assert_eq!(
925 AgentStyle::detect_from_message("Test the login flow"),
926 AgentStyle::Verification
927 );
928 assert_eq!(
929 AgentStyle::detect_from_message("Check if the API handles edge cases"),
930 AgentStyle::Verification
931 );
932 }
933
934 #[test]
935 fn test_agent_style_detect_explore() {
936 assert_eq!(
937 AgentStyle::detect_from_message("Find all files related to auth"),
938 AgentStyle::Explore
939 );
940 assert_eq!(
941 AgentStyle::detect_from_message("Where is the user model defined?"),
942 AgentStyle::Explore
943 );
944 assert_eq!(
945 AgentStyle::detect_from_message("Search for password hashing code"),
946 AgentStyle::Explore
947 );
948 }
949
950 #[test]
951 fn test_agent_style_detect_code_review() {
952 assert_eq!(
953 AgentStyle::detect_from_message("Review the PR changes"),
954 AgentStyle::CodeReview
955 );
956 assert_eq!(
957 AgentStyle::detect_from_message("Analyze this code for best practices"),
958 AgentStyle::CodeReview
959 );
960 assert_eq!(
961 AgentStyle::detect_from_message("Assess code quality"),
962 AgentStyle::CodeReview
963 );
964 }
965
966 #[test]
967 fn test_agent_style_detect_default_is_general_purpose() {
968 assert_eq!(
970 AgentStyle::detect_from_message("Implement the new feature"),
971 AgentStyle::GeneralPurpose
972 );
973 assert_eq!(
975 AgentStyle::detect_from_message("Write code for the API"),
976 AgentStyle::GeneralPurpose
977 );
978 }
979
980 #[test]
981 fn test_build_with_message_auto_detects_style() {
982 let slots = SystemPromptSlots::default();
983 let built = slots.build_with_message("Help me plan a new feature");
984 assert!(built.contains("planning agent") || built.contains("READ-ONLY"));
986 }
987
988 #[test]
989 fn test_build_with_message_explicit_style_overrides() {
990 let slots = SystemPromptSlots {
991 style: Some(AgentStyle::Verification),
992 ..Default::default()
993 };
994 let built = slots.build_with_message("Help me plan a new feature");
995 assert!(built.contains("verification specialist") || built.contains("try to break"));
997 }
998
999 #[test]
1000 fn test_build_with_message_plan_style() {
1001 let slots = SystemPromptSlots::default();
1002 let built = slots.build_with_message("Design the system architecture");
1003 assert!(built.contains("planning agent") || built.contains("READ-ONLY"));
1004 }
1005
1006 #[test]
1007 fn test_build_with_message_explore_style() {
1008 let slots = SystemPromptSlots::default();
1009 let built = slots.build_with_message("Find all authentication files");
1010 assert!(built.contains("exploration agent") || built.contains("explore"));
1011 }
1012
1013 #[test]
1014 fn test_build_with_message_code_review_style() {
1015 let slots = SystemPromptSlots::default();
1016 let built = slots.build_with_message("Review this code");
1017 assert!(built.contains("Correctness") || built.contains("Code Review"));
1019 }
1020
1021 #[test]
1022 fn test_builder_methods() {
1023 let slots = SystemPromptSlots::default()
1024 .with_style(AgentStyle::Plan)
1025 .with_role("You are a Python expert")
1026 .with_guidelines("Use type hints")
1027 .with_response_style("Be brief")
1028 .with_extra("Additional instructions");
1029
1030 assert_eq!(slots.style, Some(AgentStyle::Plan));
1031 assert_eq!(slots.role, Some("You are a Python expert".to_string()));
1032 assert_eq!(slots.guidelines, Some("Use type hints".to_string()));
1033 assert_eq!(slots.response_style, Some("Be brief".to_string()));
1034 assert_eq!(slots.extra, Some("Additional instructions".to_string()));
1035
1036 let built = slots.build();
1037 assert!(built.contains("Python expert"));
1038 assert!(built.contains("Use type hints"));
1039 assert!(built.contains("Be brief"));
1040 assert!(built.contains("Additional instructions"));
1041 }
1042
1043 #[test]
1044 fn test_code_review_guidelines_appended() {
1045 let slots = SystemPromptSlots {
1046 style: Some(AgentStyle::CodeReview),
1047 ..Default::default()
1048 };
1049 let built = slots.build();
1050 assert!(built.contains("Correctness"));
1051 assert!(built.contains("Security"));
1052 assert!(built.contains("Performance"));
1053 assert!(built.contains("Maintainability"));
1054 }
1055}