1use crate::llm::LlmClient;
26use anyhow::Context;
27
28pub const SYSTEM_DEFAULT: &str = include_str!("../prompts/system_default.md");
33
34pub const CONTINUATION: &str = include_str!("../prompts/continuation.md");
37
38pub const SUBAGENT_EXPLORE: &str = include_str!("../prompts/subagent_explore.md");
44
45pub const SUBAGENT_PLAN: &str = include_str!("../prompts/subagent_plan.md");
47
48pub const SUBAGENT_CODE_REVIEW: &str = include_str!("../prompts/subagent_code_review.md");
50
51pub const SUBAGENT_TITLE: &str = include_str!("../prompts/subagent_title.md");
53
54pub const SUBAGENT_SUMMARY: &str = include_str!("../prompts/subagent_summary.md");
56
57pub const CONTEXT_COMPACT: &str = include_str!("../prompts/context_compact.md");
63
64pub const CONTEXT_SUMMARY_PREFIX: &str = include_str!("../prompts/context_summary_prefix.md");
66
67#[allow(dead_code)]
73pub const TITLE_GENERATE: &str = include_str!("../prompts/title_generate.md");
74
75pub const LLM_PLAN_SYSTEM: &str = include_str!("../prompts/llm_plan_system.md");
81
82pub const LLM_GOAL_EXTRACT_SYSTEM: &str = include_str!("../prompts/llm_goal_extract_system.md");
84
85pub const LLM_GOAL_CHECK_SYSTEM: &str = include_str!("../prompts/llm_goal_check_system.md");
87
88pub const PRE_ANALYSIS_SYSTEM: &str = include_str!("../prompts/pre_analysis_system.md");
90
91pub const PLAN_EXECUTE_GOAL: &str = include_str!("../prompts/plan_execute_goal.md");
97
98pub const PLAN_EXECUTE_STEP: &str = include_str!("../prompts/plan_execute_step.md");
100
101pub const PLAN_FALLBACK_STEP: &str = include_str!("../prompts/plan_fallback_step.md");
103
104#[allow(dead_code)]
106pub const PLAN_PARALLEL_RESULTS: &str = include_str!("../prompts/plan_parallel_results.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 INTENT_CLASSIFY_SYSTEM: &str = include_str!("../prompts/intent_classify_system.md");
134
135use serde::{Deserialize, Serialize};
140
141#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
147pub enum PlanningMode {
148 #[default]
152 Auto,
153 Disabled,
155 Enabled,
157}
158
159impl PlanningMode {
160 pub fn should_plan(&self, message: &str) -> bool {
162 match self {
163 PlanningMode::Auto => AgentStyle::detect_from_message(message).requires_planning(),
164 PlanningMode::Enabled => true,
165 PlanningMode::Disabled => false,
166 }
167 }
168}
169
170#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
179pub enum AgentStyle {
180 #[default]
182 GeneralPurpose,
183 Plan,
186 Verification,
188 Explore,
191 CodeReview,
193}
194
195#[derive(Debug, Clone, Copy, PartialEq, Eq)]
197pub enum DetectionConfidence {
198 High,
200 Medium,
202 Low,
204}
205
206impl AgentStyle {
207 pub fn base_prompt(&self) -> &'static str {
209 match self {
210 AgentStyle::GeneralPurpose => SYSTEM_DEFAULT,
211 AgentStyle::Plan => SUBAGENT_PLAN,
212 AgentStyle::Verification => AGENT_VERIFICATION,
213 AgentStyle::Explore => SUBAGENT_EXPLORE,
214 AgentStyle::CodeReview => SYSTEM_DEFAULT, }
216 }
217
218 pub fn guidelines(&self) -> Option<&'static str> {
220 match self {
221 AgentStyle::GeneralPurpose => None,
222 AgentStyle::Plan => None, AgentStyle::Verification => None, AgentStyle::Explore => None, AgentStyle::CodeReview => Some(CODE_REVIEW_GUIDELINES),
226 }
227 }
228
229 pub fn description(&self) -> &'static str {
231 match self {
232 AgentStyle::GeneralPurpose => {
233 "General purpose coding agent for research and multi-step tasks"
234 }
235 AgentStyle::Plan => "Read-only planning and architecture analysis agent",
236 AgentStyle::Verification => "Adversarial verification specialist — tries to break code",
237 AgentStyle::Explore => "Fast read-only file search and codebase exploration agent",
238 AgentStyle::CodeReview => "Code review focused — analyzes quality and best practices",
239 }
240 }
241
242 pub fn builtin_agent_name(&self) -> &'static str {
244 match self {
245 AgentStyle::GeneralPurpose => "general",
246 AgentStyle::Plan => "plan",
247 AgentStyle::Verification => "verification",
248 AgentStyle::Explore => "explore",
249 AgentStyle::CodeReview => "review",
250 }
251 }
252
253 pub fn runtime_mode(&self) -> &'static str {
255 match self {
256 AgentStyle::GeneralPurpose => "general",
257 AgentStyle::Plan => "planning",
258 AgentStyle::Verification => "verification",
259 AgentStyle::Explore => "explore",
260 AgentStyle::CodeReview => "code_review",
261 }
262 }
263
264 pub fn requires_planning(&self) -> bool {
269 matches!(self, AgentStyle::Plan)
270 }
271
272 pub fn detect_with_confidence(message: &str) -> (Self, DetectionConfidence) {
279 if message
283 .chars()
284 .any(|c| ('\u{4e00}'..='\u{9fff}').contains(&c))
285 {
286 return (AgentStyle::GeneralPurpose, DetectionConfidence::Low);
287 }
288
289 let lower = message.to_lowercase();
290
291 if lower.contains("try to break")
295 || lower.contains("find vulnerabilities")
296 || lower.contains("adversarial")
297 || lower.contains("security audit")
298 {
299 return (AgentStyle::Verification, DetectionConfidence::High);
300 }
301
302 if lower.contains("help me plan")
304 || lower.contains("help me design")
305 || lower.contains("create a plan")
306 || lower.contains("implementation plan")
307 || lower.contains("step-by-step plan")
308 {
309 return (AgentStyle::Plan, DetectionConfidence::High);
310 }
311
312 if lower.contains("find all files")
314 || lower.contains("search for all")
315 || lower.contains("locate all")
316 {
317 return (AgentStyle::Explore, DetectionConfidence::High);
318 }
319
320 if lower.contains("verify")
324 || lower.contains("verification")
325 || lower.contains("break")
326 || lower.contains("debug")
327 || lower.contains("test")
328 || lower.contains("check if")
329 {
330 return (AgentStyle::Verification, DetectionConfidence::Medium);
331 }
332
333 if lower.contains("plan")
335 || lower.contains("design")
336 || lower.contains("architecture")
337 || lower.contains("approach")
338 {
339 return (AgentStyle::Plan, DetectionConfidence::Medium);
340 }
341
342 if lower.contains("find")
344 || lower.contains("search")
345 || lower.contains("where is")
346 || lower.contains("where's")
347 || lower.contains("locate")
348 || lower.contains("explore")
349 || lower.contains("look for")
350 {
351 return (AgentStyle::Explore, DetectionConfidence::Medium);
352 }
353
354 if lower.contains("review")
356 || lower.contains("code review")
357 || lower.contains("analyze")
358 || lower.contains("assess")
359 || lower.contains("quality")
360 || lower.contains("best practice")
361 {
362 return (AgentStyle::CodeReview, DetectionConfidence::Medium);
363 }
364
365 (AgentStyle::GeneralPurpose, DetectionConfidence::Low)
367 }
368
369 pub fn detect_from_message(message: &str) -> Self {
374 Self::detect_with_confidence(message).0
375 }
376
377 pub async fn detect_with_llm(llm: &dyn LlmClient, message: &str) -> anyhow::Result<Self> {
384 use crate::llm::Message;
385
386 let system = INTENT_CLASSIFY_SYSTEM;
387 let messages = vec![Message::user(message)];
388
389 let response = llm
390 .complete(&messages, Some(system), &[])
391 .await
392 .context("LLM intent classification failed")?;
393
394 let text = response.text().trim().to_lowercase();
395
396 let style = match text.as_str() {
397 "plan" => AgentStyle::Plan,
398 "explore" => AgentStyle::Explore,
399 "verification" => AgentStyle::Verification,
400 "codereview" | "code review" => AgentStyle::CodeReview,
401 _ => AgentStyle::GeneralPurpose,
402 };
403
404 Ok(style)
405 }
406}
407
408const CODE_REVIEW_GUIDELINES: &str = r#"## Code Review Focus
410
411When reviewing code, pay attention to:
412
4131. **Correctness** — Does the code do what it's supposed to do? Are edge cases handled?
4142. **Security** — Are there potential vulnerabilities (injection, auth bypass, data exposure)?
4153. **Performance** — Are there obvious inefficiencies (N+1 queries, unnecessary allocations)?
4164. **Maintainability** — Is the code readable? Are names clear? Is there appropriate documentation?
4175. **Best Practices** — Does it follow language/framework conventions? Are there anti-patterns?
418
419Be specific in your review. Quote the actual code you're referring to. Suggest concrete improvements with examples where possible.
420
421Remember: your job is to find issues, not to be nice. A code review that finds nothing is a missed opportunity."#;
422
423#[derive(Debug, Clone, Default)]
450pub struct SystemPromptSlots {
451 pub style: Option<AgentStyle>,
456
457 pub role: Option<String>,
462
463 pub guidelines: Option<String>,
467
468 pub response_style: Option<String>,
472
473 pub extra: Option<String>,
475}
476
477const DEFAULT_ROLE_LINE: &str = include_str!("../prompts/system_default_role_line.md");
479
480const DEFAULT_RESPONSE_FORMAT: &str = include_str!("../prompts/system_default_response_format.md");
482
483impl SystemPromptSlots {
484 pub fn build(&self) -> String {
492 self.build_with_style(self.style.unwrap_or_default())
493 }
494
495 pub fn build_with_message(&self, initial_message: &str) -> String {
500 let style = self
501 .style
502 .unwrap_or_else(|| AgentStyle::detect_from_message(initial_message));
503 self.build_with_style(style)
504 }
505
506 fn build_with_style(&self, style: AgentStyle) -> String {
508 let mut parts: Vec<String> = Vec::new();
509
510 let base_prompt = style.base_prompt().replace('\r', "");
513 let default_role_line = DEFAULT_ROLE_LINE.replace('\r', "");
514 let default_response_format = DEFAULT_RESPONSE_FORMAT.replace('\r', "");
515
516 let core = if let Some(ref role) = self.role {
520 if style == AgentStyle::GeneralPurpose {
521 let custom_role = format!(
522 "{}. You operate in an agentic loop: you\nthink, use tools, observe results, and keep working until the task is fully complete.",
523 role.trim_end_matches('.')
524 );
525 base_prompt.replace(&default_role_line, &custom_role)
526 } else {
527 format!("{}\n\n{}", role, base_prompt)
529 }
530 } else {
531 base_prompt
532 };
533
534 let core = if self.response_style.is_some() {
536 core.replace(&default_response_format, "")
537 .trim_end()
538 .to_string()
539 } else {
540 core.trim_end().to_string()
541 };
542
543 parts.push(core);
544
545 if let Some(ref style) = self.response_style {
547 parts.push(format!("## Response Format\n\n{}", style));
548 }
549
550 let style_guidelines = style.guidelines();
552 if style_guidelines.is_some() || self.guidelines.is_some() {
553 let mut guidelines_parts = Vec::new();
554 if let Some(sg) = style_guidelines {
555 guidelines_parts.push(sg.to_string());
556 }
557 if let Some(ref g) = self.guidelines {
558 guidelines_parts.push(g.clone());
559 }
560 parts.push(format!(
561 "## Guidelines\n\n{}",
562 guidelines_parts.join("\n\n")
563 ));
564 }
565
566 if let Some(ref extra) = self.extra {
568 parts.push(extra.clone());
569 }
570
571 parts.join("\n\n")
572 }
573
574 pub fn is_empty(&self) -> bool {
576 self.style.is_none()
577 && self.role.is_none()
578 && self.guidelines.is_none()
579 && self.response_style.is_none()
580 && self.extra.is_none()
581 }
582
583 pub fn with_style(mut self, style: AgentStyle) -> Self {
585 self.style = Some(style);
586 self
587 }
588
589 pub fn with_role(mut self, role: impl Into<String>) -> Self {
591 self.role = Some(role.into());
592 self
593 }
594
595 pub fn with_guidelines(mut self, guidelines: impl Into<String>) -> Self {
597 self.guidelines = Some(guidelines.into());
598 self
599 }
600
601 pub fn with_response_style(mut self, style: impl Into<String>) -> Self {
603 self.response_style = Some(style.into());
604 self
605 }
606
607 pub fn with_extra(mut self, extra: impl Into<String>) -> Self {
609 self.extra = Some(extra.into());
610 self
611 }
612}
613
614pub fn render(template: &str, vars: &[(&str, &str)]) -> String {
620 let mut result = template.to_string();
621 for (key, value) in vars {
622 result = result.replace(&format!("{{{}}}", key), value);
623 }
624 result
625}
626
627#[cfg(test)]
628mod tests {
629 use super::*;
630
631 #[test]
632 fn test_all_prompts_loaded() {
633 assert!(!SYSTEM_DEFAULT.is_empty());
635 assert!(!CONTINUATION.is_empty());
636 assert!(!SUBAGENT_EXPLORE.is_empty());
637 assert!(!SUBAGENT_PLAN.is_empty());
638 assert!(!SUBAGENT_CODE_REVIEW.is_empty());
639 assert!(!SUBAGENT_TITLE.is_empty());
640 assert!(!SUBAGENT_SUMMARY.is_empty());
641 assert!(!CONTEXT_COMPACT.is_empty());
642 assert!(!TITLE_GENERATE.is_empty());
643 assert!(!LLM_PLAN_SYSTEM.is_empty());
644 assert!(!LLM_GOAL_EXTRACT_SYSTEM.is_empty());
645 assert!(!LLM_GOAL_CHECK_SYSTEM.is_empty());
646 assert!(!SKILLS_CATALOG_HEADER.is_empty());
647 assert!(!BTW_SYSTEM.is_empty());
648 assert!(!PLAN_EXECUTE_GOAL.is_empty());
649 assert!(!PLAN_EXECUTE_STEP.is_empty());
650 assert!(!PLAN_FALLBACK_STEP.is_empty());
651 assert!(!PLAN_PARALLEL_RESULTS.is_empty());
652 }
653
654 #[test]
655 fn test_render_template() {
656 let result = render(
657 PLAN_EXECUTE_GOAL,
658 &[("goal", "Build app"), ("steps", "1. Init")],
659 );
660 assert!(result.contains("Build app"));
661 assert!(!result.contains("{goal}"));
662 }
663
664 #[test]
665 fn test_render_multiple_placeholders() {
666 let template = "Goal: {goal}\nCriteria: {criteria}\nState: {current_state}";
667 let result = render(
668 template,
669 &[
670 ("goal", "Build a REST API"),
671 ("criteria", "- Endpoint works\n- Tests pass"),
672 ("current_state", "API is deployed"),
673 ],
674 );
675 assert!(result.contains("Build a REST API"));
676 assert!(result.contains("Endpoint works"));
677 assert!(result.contains("API is deployed"));
678 }
679
680 #[test]
681 fn test_subagent_prompts_contain_guidelines() {
682 assert!(SUBAGENT_EXPLORE.contains("Guidelines"));
683 assert!(SUBAGENT_EXPLORE.contains("read-only"));
684 assert!(SUBAGENT_PLAN.contains("Guidelines"));
685 assert!(SUBAGENT_PLAN.contains("read-only"));
686 }
687
688 #[test]
689 fn test_context_summary_prefix() {
690 assert!(CONTEXT_SUMMARY_PREFIX.contains("Context Summary"));
691 }
692
693 #[test]
696 fn test_slots_default_builds_system_default() {
697 let slots = SystemPromptSlots::default();
698 let built = slots.build();
699 assert!(built.contains("Core Behaviour"));
700 assert!(built.contains("Tool Usage Strategy"));
701 assert!(built.contains("Completion Criteria"));
702 assert!(built.contains("Response Format"));
703 assert!(built.contains("A3S Code"));
704 }
705
706 #[test]
707 fn test_slots_custom_role_replaces_default() {
708 let slots = SystemPromptSlots {
709 role: Some("You are a senior Python developer".to_string()),
710 ..Default::default()
711 };
712 let built = slots.build();
713 assert!(built.contains("You are a senior Python developer"));
714 assert!(!built.contains("You are A3S Code"));
715 assert!(built.contains("Core Behaviour"));
717 assert!(built.contains("Tool Usage Strategy"));
718 }
719
720 #[test]
721 fn test_slots_custom_guidelines_appended() {
722 let slots = SystemPromptSlots {
723 guidelines: Some("Always use type hints. Follow PEP 8.".to_string()),
724 ..Default::default()
725 };
726 let built = slots.build();
727 assert!(built.contains("## Guidelines"));
728 assert!(built.contains("Always use type hints"));
729 assert!(built.contains("Core Behaviour"));
730 }
731
732 #[test]
733 fn test_slots_custom_response_style_replaces_default() {
734 let slots = SystemPromptSlots {
735 response_style: Some("Be concise. Use bullet points.".to_string()),
736 ..Default::default()
737 };
738 let built = slots.build();
739 assert!(built.contains("Be concise. Use bullet points."));
740 assert!(!built.contains("emit tool calls, no prose"));
742 assert!(built.contains("Core Behaviour"));
744 }
745
746 #[test]
747 fn test_slots_extra_appended() {
748 let slots = SystemPromptSlots {
749 extra: Some("Remember: always write tests first.".to_string()),
750 ..Default::default()
751 };
752 let built = slots.build();
753 assert!(built.contains("Remember: always write tests first."));
754 assert!(built.contains("Core Behaviour"));
755 }
756
757 #[test]
758 fn test_slots_with_extra() {
759 let slots = SystemPromptSlots::default().with_extra("You are a helpful assistant.");
760 let built = slots.build();
761 assert!(built.contains("You are a helpful assistant."));
762 assert!(built.contains("Core Behaviour"));
763 assert!(built.contains("Tool Usage Strategy"));
764 }
765
766 #[test]
767 fn test_slots_all_slots_combined() {
768 let slots = SystemPromptSlots {
769 style: None,
770 role: Some("You are a Rust expert".to_string()),
771 guidelines: Some("Use clippy. No unwrap.".to_string()),
772 response_style: Some("Short answers only.".to_string()),
773 extra: Some("Project uses tokio.".to_string()),
774 };
775 let built = slots.build();
776 assert!(built.contains("You are a Rust expert"));
777 assert!(built.contains("Core Behaviour"));
778 assert!(built.contains("## Guidelines"));
779 assert!(built.contains("Use clippy"));
780 assert!(built.contains("Short answers only"));
781 assert!(built.contains("Project uses tokio"));
782 assert!(!built.contains("emit tool calls, no prose"));
784 }
785
786 #[test]
787 fn test_slots_is_empty() {
788 assert!(SystemPromptSlots::default().is_empty());
789 assert!(!SystemPromptSlots {
790 role: Some("test".to_string()),
791 ..Default::default()
792 }
793 .is_empty());
794 assert!(!SystemPromptSlots {
795 style: Some(AgentStyle::Plan),
796 ..Default::default()
797 }
798 .is_empty());
799 }
800
801 #[test]
804 fn test_agent_style_default_is_general_purpose() {
805 assert_eq!(AgentStyle::default(), AgentStyle::GeneralPurpose);
806 }
807
808 #[test]
809 fn test_agent_style_base_prompt() {
810 assert_eq!(AgentStyle::GeneralPurpose.base_prompt(), SYSTEM_DEFAULT);
811 assert_eq!(AgentStyle::Plan.base_prompt(), SUBAGENT_PLAN);
812 assert_eq!(AgentStyle::Explore.base_prompt(), SUBAGENT_EXPLORE);
813 assert_eq!(AgentStyle::Verification.base_prompt(), AGENT_VERIFICATION);
814 assert_eq!(AgentStyle::CodeReview.base_prompt(), SYSTEM_DEFAULT);
816 }
817
818 #[test]
819 fn test_agent_style_guidelines() {
820 assert!(AgentStyle::GeneralPurpose.guidelines().is_none());
821 assert!(AgentStyle::Plan.guidelines().is_none()); assert!(AgentStyle::Explore.guidelines().is_none());
823 assert!(AgentStyle::Verification.guidelines().is_none());
824 assert!(AgentStyle::CodeReview.guidelines().is_some());
825 assert!(AgentStyle::CodeReview
826 .guidelines()
827 .unwrap()
828 .contains("Correctness"));
829 }
830
831 #[test]
832 fn test_agent_style_builtin_agent_name_mapping() {
833 assert_eq!(AgentStyle::GeneralPurpose.builtin_agent_name(), "general");
834 assert_eq!(AgentStyle::Plan.builtin_agent_name(), "plan");
835 assert_eq!(AgentStyle::Explore.builtin_agent_name(), "explore");
836 assert_eq!(
837 AgentStyle::Verification.builtin_agent_name(),
838 "verification"
839 );
840 assert_eq!(AgentStyle::CodeReview.builtin_agent_name(), "review");
841 }
842
843 #[test]
844 fn test_agent_style_runtime_mode_mapping() {
845 assert_eq!(AgentStyle::GeneralPurpose.runtime_mode(), "general");
846 assert_eq!(AgentStyle::Plan.runtime_mode(), "planning");
847 assert_eq!(AgentStyle::Explore.runtime_mode(), "explore");
848 assert_eq!(AgentStyle::Verification.runtime_mode(), "verification");
849 assert_eq!(AgentStyle::CodeReview.runtime_mode(), "code_review");
850 }
851
852 #[test]
853 fn test_agent_style_detect_plan() {
854 assert_eq!(
855 AgentStyle::detect_from_message("Help me plan a new feature"),
856 AgentStyle::Plan
857 );
858 assert_eq!(
859 AgentStyle::detect_from_message("Design the architecture for this"),
860 AgentStyle::Plan
861 );
862 assert_eq!(
863 AgentStyle::detect_from_message("What's the implementation approach?"),
864 AgentStyle::Plan
865 );
866 }
867
868 #[test]
869 fn test_agent_style_detect_verification() {
870 assert_eq!(
871 AgentStyle::detect_from_message("Verify that this works correctly"),
872 AgentStyle::Verification
873 );
874 assert_eq!(
875 AgentStyle::detect_from_message("Test the login flow"),
876 AgentStyle::Verification
877 );
878 assert_eq!(
879 AgentStyle::detect_from_message("Check if the API handles edge cases"),
880 AgentStyle::Verification
881 );
882 }
883
884 #[test]
885 fn test_agent_style_detect_explore() {
886 assert_eq!(
887 AgentStyle::detect_from_message("Find all files related to auth"),
888 AgentStyle::Explore
889 );
890 assert_eq!(
891 AgentStyle::detect_from_message("Where is the user model defined?"),
892 AgentStyle::Explore
893 );
894 assert_eq!(
895 AgentStyle::detect_from_message("Search for password hashing code"),
896 AgentStyle::Explore
897 );
898 }
899
900 #[test]
901 fn test_agent_style_detect_code_review() {
902 assert_eq!(
903 AgentStyle::detect_from_message("Review the PR changes"),
904 AgentStyle::CodeReview
905 );
906 assert_eq!(
907 AgentStyle::detect_from_message("Analyze this code for best practices"),
908 AgentStyle::CodeReview
909 );
910 assert_eq!(
911 AgentStyle::detect_from_message("Assess code quality"),
912 AgentStyle::CodeReview
913 );
914 }
915
916 #[test]
917 fn test_agent_style_detect_default_is_general_purpose() {
918 assert_eq!(
920 AgentStyle::detect_from_message("Implement the new feature"),
921 AgentStyle::GeneralPurpose
922 );
923 assert_eq!(
925 AgentStyle::detect_from_message("Write code for the API"),
926 AgentStyle::GeneralPurpose
927 );
928 }
929
930 #[test]
931 fn test_build_with_message_auto_detects_style() {
932 let slots = SystemPromptSlots::default();
933 let built = slots.build_with_message("Help me plan a new feature");
934 assert!(built.contains("planning agent") || built.contains("READ-ONLY"));
936 }
937
938 #[test]
939 fn test_build_with_message_explicit_style_overrides() {
940 let slots = SystemPromptSlots {
941 style: Some(AgentStyle::Verification),
942 ..Default::default()
943 };
944 let built = slots.build_with_message("Help me plan a new feature");
945 assert!(built.contains("verification specialist") || built.contains("try to break"));
947 }
948
949 #[test]
950 fn test_build_with_message_plan_style() {
951 let slots = SystemPromptSlots::default();
952 let built = slots.build_with_message("Design the system architecture");
953 assert!(built.contains("planning agent") || built.contains("READ-ONLY"));
954 }
955
956 #[test]
957 fn test_build_with_message_explore_style() {
958 let slots = SystemPromptSlots::default();
959 let built = slots.build_with_message("Find all authentication files");
960 assert!(built.contains("exploration agent") || built.contains("explore"));
961 }
962
963 #[test]
964 fn test_build_with_message_code_review_style() {
965 let slots = SystemPromptSlots::default();
966 let built = slots.build_with_message("Review this code");
967 assert!(built.contains("Correctness") || built.contains("Code Review"));
969 }
970
971 #[test]
972 fn test_builder_methods() {
973 let slots = SystemPromptSlots::default()
974 .with_style(AgentStyle::Plan)
975 .with_role("You are a Python expert")
976 .with_guidelines("Use type hints")
977 .with_response_style("Be brief")
978 .with_extra("Additional instructions");
979
980 assert_eq!(slots.style, Some(AgentStyle::Plan));
981 assert_eq!(slots.role, Some("You are a Python expert".to_string()));
982 assert_eq!(slots.guidelines, Some("Use type hints".to_string()));
983 assert_eq!(slots.response_style, Some("Be brief".to_string()));
984 assert_eq!(slots.extra, Some("Additional instructions".to_string()));
985
986 let built = slots.build();
987 assert!(built.contains("Python expert"));
988 assert!(built.contains("Use type hints"));
989 assert!(built.contains("Be brief"));
990 assert!(built.contains("Additional instructions"));
991 }
992
993 #[test]
994 fn test_code_review_guidelines_appended() {
995 let slots = SystemPromptSlots {
996 style: Some(AgentStyle::CodeReview),
997 ..Default::default()
998 };
999 let built = slots.build();
1000 assert!(built.contains("Correctness"));
1001 assert!(built.contains("Security"));
1002 assert!(built.contains("Performance"));
1003 assert!(built.contains("Maintainability"));
1004 }
1005}