1use crate::llm::LlmClient;
19use anyhow::Context;
20
21pub const SYSTEM_DEFAULT: &str = include_str!("../prompts/common/system_default.md");
26
27pub const CONTINUATION: &str = include_str!("../prompts/common/continuation.md");
30
31pub const BOUNDARIES: &str = include_str!("../prompts/common/boundaries.md");
37
38pub const AGENT_EXPLORE: &str = include_str!("../prompts/agents/explore.md");
44
45pub const AGENT_PLAN: &str = include_str!("../prompts/agents/plan.md");
47
48pub const AGENT_CODE_REVIEW: &str = include_str!("../prompts/agents/code_review.md");
50
51pub const CONTEXT_COMPACT: &str = include_str!("../prompts/common/context_compact.md");
57
58pub const CONTEXT_SUMMARY_PREFIX: &str =
60 include_str!("../prompts/common/context_summary_prefix.md");
61
62pub const LLM_PLAN_SYSTEM: &str = include_str!("../prompts/planning/llm_plan_system.md");
68
69pub const LLM_GOAL_EXTRACT_SYSTEM: &str =
71 include_str!("../prompts/planning/llm_goal_extract_system.md");
72
73pub const LLM_GOAL_CHECK_SYSTEM: &str =
75 include_str!("../prompts/planning/llm_goal_check_system.md");
76
77pub const PRE_ANALYSIS_SYSTEM: &str = include_str!("../prompts/analysis/pre_analysis_system.md");
79
80pub const PLAN_EXECUTE_GOAL: &str = include_str!("../prompts/planning/plan_execute_goal.md");
86
87pub const PLAN_EXECUTE_STEP: &str = include_str!("../prompts/planning/plan_execute_step.md");
89
90pub const PLAN_FALLBACK_STEP: &str = include_str!("../prompts/planning/plan_fallback_step.md");
92
93pub const SKILLS_CATALOG_HEADER: &str = include_str!("../prompts/common/skills_catalog_header.md");
95
96pub const AGENT_VERIFICATION: &str = include_str!("../prompts/agents/verification.md");
102
103pub const INTENT_CLASSIFY_SYSTEM: &str =
109 include_str!("../prompts/analysis/intent_classify_system.md");
110
111use serde::{Deserialize, Serialize};
116
117#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
123pub enum PlanningMode {
124 #[default]
128 Auto,
129 Disabled,
131 Enabled,
133}
134
135impl PlanningMode {
136 pub fn should_plan(&self, message: &str) -> bool {
141 match self {
142 PlanningMode::Auto => AgentStyle::detect_from_message(message).requires_planning(),
143 PlanningMode::Enabled => true,
144 PlanningMode::Disabled => false,
145 }
146 }
147}
148
149#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
158pub enum AgentStyle {
159 #[default]
161 GeneralPurpose,
162 Plan,
165 Verification,
167 Explore,
170 CodeReview,
172}
173
174#[derive(Debug, Clone, Copy, PartialEq, Eq)]
176pub enum DetectionConfidence {
177 High,
179 Medium,
181 Low,
183}
184
185impl AgentStyle {
186 pub fn base_prompt(&self) -> &'static str {
188 match self {
189 AgentStyle::GeneralPurpose => SYSTEM_DEFAULT,
190 AgentStyle::Plan => AGENT_PLAN,
191 AgentStyle::Verification => AGENT_VERIFICATION,
192 AgentStyle::Explore => AGENT_EXPLORE,
193 AgentStyle::CodeReview => AGENT_CODE_REVIEW,
194 }
195 }
196
197 pub fn guidelines(&self) -> Option<&'static str> {
199 match self {
200 AgentStyle::GeneralPurpose => None,
201 AgentStyle::Plan => None, AgentStyle::Verification => None, AgentStyle::Explore => None, AgentStyle::CodeReview => None,
205 }
206 }
207
208 pub fn description(&self) -> &'static str {
210 match self {
211 AgentStyle::GeneralPurpose => {
212 "General purpose coding agent for research and multi-step tasks"
213 }
214 AgentStyle::Plan => "Read-only planning and architecture analysis agent",
215 AgentStyle::Verification => "Adversarial verification specialist — tries to break code",
216 AgentStyle::Explore => "Fast read-only file search and codebase exploration agent",
217 AgentStyle::CodeReview => "Code review focused — analyzes quality and best practices",
218 }
219 }
220
221 pub fn builtin_agent_name(&self) -> &'static str {
223 match self {
224 AgentStyle::GeneralPurpose => "general",
225 AgentStyle::Plan => "plan",
226 AgentStyle::Verification => "verification",
227 AgentStyle::Explore => "explore",
228 AgentStyle::CodeReview => "review",
229 }
230 }
231
232 pub fn runtime_mode(&self) -> &'static str {
234 match self {
235 AgentStyle::GeneralPurpose => "general",
236 AgentStyle::Plan => "planning",
237 AgentStyle::Verification => "verification",
238 AgentStyle::Explore => "explore",
239 AgentStyle::CodeReview => "code_review",
240 }
241 }
242
243 pub fn requires_planning(&self) -> bool {
248 matches!(self, AgentStyle::Plan)
249 }
250
251 pub fn detect_with_confidence(message: &str) -> (Self, DetectionConfidence) {
257 if message
261 .chars()
262 .any(|c| ('\u{4e00}'..='\u{9fff}').contains(&c))
263 {
264 return (AgentStyle::GeneralPurpose, DetectionConfidence::Low);
265 }
266
267 let lower = message.to_lowercase();
268
269 if lower.contains("try to break")
273 || lower.contains("find vulnerabilities")
274 || lower.contains("adversarial")
275 || lower.contains("security audit")
276 {
277 return (AgentStyle::Verification, DetectionConfidence::High);
278 }
279
280 if lower.contains("help me plan")
282 || lower.contains("help me design")
283 || lower.contains("create a plan")
284 || lower.contains("implementation plan")
285 || lower.contains("step-by-step plan")
286 {
287 return (AgentStyle::Plan, DetectionConfidence::High);
288 }
289
290 if lower.contains("find all files")
292 || lower.contains("search for all")
293 || lower.contains("locate all")
294 {
295 return (AgentStyle::Explore, DetectionConfidence::High);
296 }
297
298 if lower.contains("verify")
302 || lower.contains("verification")
303 || lower.contains("break")
304 || lower.contains("debug")
305 || lower.contains("test")
306 || lower.contains("check if")
307 {
308 return (AgentStyle::Verification, DetectionConfidence::Medium);
309 }
310
311 if lower.contains("plan")
313 || lower.contains("design")
314 || lower.contains("architecture")
315 || lower.contains("approach")
316 {
317 return (AgentStyle::Plan, DetectionConfidence::Medium);
318 }
319
320 if lower.contains("find")
322 || lower.contains("search")
323 || lower.contains("where is")
324 || lower.contains("where's")
325 || lower.contains("locate")
326 || lower.contains("explore")
327 || lower.contains("look for")
328 {
329 return (AgentStyle::Explore, DetectionConfidence::Medium);
330 }
331
332 if lower.contains("review")
334 || lower.contains("code review")
335 || lower.contains("analyze")
336 || lower.contains("assess")
337 || lower.contains("quality")
338 || lower.contains("best practice")
339 {
340 return (AgentStyle::CodeReview, DetectionConfidence::Medium);
341 }
342
343 (AgentStyle::GeneralPurpose, DetectionConfidence::Low)
345 }
346
347 pub fn detect_from_message(message: &str) -> Self {
353 Self::detect_with_confidence(message).0
354 }
355
356 pub async fn detect_with_llm(llm: &dyn LlmClient, message: &str) -> anyhow::Result<Self> {
363 use crate::llm::Message;
364
365 let system = INTENT_CLASSIFY_SYSTEM;
366 let messages = vec![Message::user(message)];
367
368 let response = llm
369 .complete(&messages, Some(system), &[])
370 .await
371 .context("LLM intent classification failed")?;
372
373 let text = response.text().trim().to_lowercase();
374
375 let style = match text.as_str() {
376 "plan" => AgentStyle::Plan,
377 "explore" => AgentStyle::Explore,
378 "verification" => AgentStyle::Verification,
379 "codereview" | "code review" => AgentStyle::CodeReview,
380 _ => AgentStyle::GeneralPurpose,
381 };
382
383 Ok(style)
384 }
385}
386
387#[derive(Debug, Clone, Default)]
414pub struct SystemPromptSlots {
415 pub style: Option<AgentStyle>,
420
421 pub role: Option<String>,
426
427 pub guidelines: Option<String>,
431
432 pub response_style: Option<String>,
436
437 pub extra: Option<String>,
439}
440
441const DEFAULT_ROLE_LINE: &str = include_str!("../prompts/common/system_default_role_line.md");
443
444const DEFAULT_RESPONSE_FORMAT: &str =
446 include_str!("../prompts/common/system_default_response_format.md");
447
448impl SystemPromptSlots {
449 pub fn build(&self) -> String {
457 self.build_with_style(self.style.unwrap_or_default())
458 }
459
460 pub fn build_with_message(&self, initial_message: &str) -> String {
465 let style = self
466 .style
467 .unwrap_or_else(|| AgentStyle::detect_from_message(initial_message));
468 self.build_with_style(style)
469 }
470
471 fn build_with_style(&self, style: AgentStyle) -> String {
473 let mut parts: Vec<String> = Vec::new();
474
475 let base_prompt = style.base_prompt().replace('\r', "");
478 let default_role_line = DEFAULT_ROLE_LINE.replace('\r', "");
479 let default_response_format = DEFAULT_RESPONSE_FORMAT.replace('\r', "");
480
481 let core = if let Some(ref role) = self.role {
485 if style == AgentStyle::GeneralPurpose {
486 let custom_role = format!(
487 "{}. You operate in an agentic loop: inspect, act with tools, observe results, and continue until the user's request is genuinely complete.",
488 role.trim_end_matches('.')
489 );
490 base_prompt.replace(&default_role_line, &custom_role)
491 } else {
492 format!("{}\n\n{}", role, base_prompt)
494 }
495 } else {
496 base_prompt
497 };
498
499 let core = if self.response_style.is_some() {
501 core.replace(&default_response_format, "")
502 .trim_end()
503 .to_string()
504 } else {
505 core.trim_end().to_string()
506 };
507
508 parts.push(core);
509
510 parts.push(BOUNDARIES.replace('\r', "").trim_end().to_string());
514
515 if let Some(ref style) = self.response_style {
517 parts.push(format!("## Response Format\n\n{}", style));
518 }
519
520 let style_guidelines = style.guidelines();
522 if style_guidelines.is_some() || self.guidelines.is_some() {
523 let mut guidelines_parts = Vec::new();
524 if let Some(sg) = style_guidelines {
525 guidelines_parts.push(sg.to_string());
526 }
527 if let Some(ref g) = self.guidelines {
528 guidelines_parts.push(g.clone());
529 }
530 parts.push(format!(
531 "## Guidelines\n\n{}",
532 guidelines_parts.join("\n\n")
533 ));
534 }
535
536 if let Some(ref extra) = self.extra {
538 parts.push(extra.clone());
539 }
540
541 parts.join("\n\n")
542 }
543
544 pub fn is_empty(&self) -> bool {
546 self.style.is_none()
547 && self.role.is_none()
548 && self.guidelines.is_none()
549 && self.response_style.is_none()
550 && self.extra.is_none()
551 }
552
553 pub fn with_style(mut self, style: AgentStyle) -> Self {
555 self.style = Some(style);
556 self
557 }
558
559 pub fn with_role(mut self, role: impl Into<String>) -> Self {
561 self.role = Some(role.into());
562 self
563 }
564
565 pub fn with_guidelines(mut self, guidelines: impl Into<String>) -> Self {
567 self.guidelines = Some(guidelines.into());
568 self
569 }
570
571 pub fn with_response_style(mut self, style: impl Into<String>) -> Self {
573 self.response_style = Some(style.into());
574 self
575 }
576
577 pub fn with_extra(mut self, extra: impl Into<String>) -> Self {
579 self.extra = Some(extra.into());
580 self
581 }
582}
583
584pub fn render(template: &str, vars: &[(&str, &str)]) -> String {
590 let mut result = template.to_string();
591 for (key, value) in vars {
592 result = result.replace(&format!("{{{}}}", key), value);
593 }
594 result
595}
596
597#[cfg(test)]
598mod tests {
599 use super::*;
600
601 #[test]
602 fn test_all_prompts_loaded() {
603 assert!(!SYSTEM_DEFAULT.is_empty());
605 assert!(!CONTINUATION.is_empty());
606 assert!(!BOUNDARIES.is_empty());
607 assert!(!AGENT_EXPLORE.is_empty());
608 assert!(!AGENT_PLAN.is_empty());
609 assert!(!AGENT_CODE_REVIEW.is_empty());
610 assert!(!CONTEXT_COMPACT.is_empty());
611 assert!(!LLM_PLAN_SYSTEM.is_empty());
612 assert!(!LLM_GOAL_EXTRACT_SYSTEM.is_empty());
613 assert!(!LLM_GOAL_CHECK_SYSTEM.is_empty());
614 assert!(!SKILLS_CATALOG_HEADER.is_empty());
615 assert!(!PLAN_EXECUTE_GOAL.is_empty());
616 assert!(!PLAN_EXECUTE_STEP.is_empty());
617 assert!(!PLAN_FALLBACK_STEP.is_empty());
618 }
619
620 #[test]
621 fn test_render_template() {
622 let result = render(
623 PLAN_EXECUTE_GOAL,
624 &[("goal", "Build app"), ("steps", "1. Init")],
625 );
626 assert!(result.contains("Build app"));
627 assert!(!result.contains("{goal}"));
628 }
629
630 #[test]
631 fn test_render_multiple_placeholders() {
632 let template = "Goal: {goal}\nCriteria: {criteria}\nState: {current_state}";
633 let result = render(
634 template,
635 &[
636 ("goal", "Build a REST API"),
637 ("criteria", "- Endpoint works\n- Tests pass"),
638 ("current_state", "API is deployed"),
639 ],
640 );
641 assert!(result.contains("Build a REST API"));
642 assert!(result.contains("Endpoint works"));
643 assert!(result.contains("API is deployed"));
644 }
645
646 #[test]
647 fn test_delegated_agent_prompts_contain_guidelines() {
648 assert!(AGENT_EXPLORE.contains("Guidelines"));
649 assert!(AGENT_EXPLORE.contains("read-only"));
650 assert!(AGENT_PLAN.contains("Guidelines"));
651 assert!(AGENT_PLAN.contains("read-only"));
652 }
653
654 #[test]
655 fn test_context_summary_prefix() {
656 assert!(CONTEXT_SUMMARY_PREFIX.contains("Context Summary"));
657 }
658
659 #[test]
662 fn test_slots_default_builds_system_default() {
663 let slots = SystemPromptSlots::default();
664 let built = slots.build();
665 assert!(built.contains("Core Behaviour"));
666 assert!(built.contains("Tool Usage Strategy"));
667 assert!(built.contains("Completion Criteria"));
668 assert!(built.contains("Response Format"));
669 assert!(built.contains("A3S Code"));
670 assert!(built.contains("## Boundaries"));
673 assert!(built.contains("untrusted data"));
674 }
675
676 #[test]
677 fn test_boundaries_injected_for_every_style() {
678 for style in [
679 AgentStyle::GeneralPurpose,
680 AgentStyle::Plan,
681 AgentStyle::Verification,
682 AgentStyle::Explore,
683 AgentStyle::CodeReview,
684 ] {
685 let built = SystemPromptSlots::default().with_style(style).build();
686 assert!(
687 built.contains("## Boundaries"),
688 "style {style:?} missing Boundaries section"
689 );
690 }
691 }
692
693 #[test]
694 fn test_boundaries_not_duplicated_in_general_purpose() {
695 assert!(!SYSTEM_DEFAULT.contains("## Boundaries"));
698 let built = SystemPromptSlots::default().build();
699 assert_eq!(built.matches("## Boundaries").count(), 1);
700 }
701
702 #[test]
703 fn test_slots_custom_role_replaces_default() {
704 let slots = SystemPromptSlots {
705 role: Some("You are a senior Python developer".to_string()),
706 ..Default::default()
707 };
708 let built = slots.build();
709 assert!(built.contains("You are a senior Python developer"));
710 assert!(!built.contains("You are A3S Code"));
711 assert!(built.contains("Core Behaviour"));
713 assert!(built.contains("Tool Usage Strategy"));
714 }
715
716 #[test]
717 fn test_slots_custom_guidelines_appended() {
718 let slots = SystemPromptSlots {
719 guidelines: Some("Always use type hints. Follow PEP 8.".to_string()),
720 ..Default::default()
721 };
722 let built = slots.build();
723 assert!(built.contains("## Guidelines"));
724 assert!(built.contains("Always use type hints"));
725 assert!(built.contains("Core Behaviour"));
726 }
727
728 #[test]
729 fn test_slots_custom_response_style_replaces_default() {
730 let slots = SystemPromptSlots {
731 response_style: Some("Be concise. Use bullet points.".to_string()),
732 ..Default::default()
733 };
734 let built = slots.build();
735 assert!(built.contains("Be concise. Use bullet points."));
736 assert!(!built.contains("keep progress notes brief and useful"));
738 assert!(built.contains("Core Behaviour"));
740 }
741
742 #[test]
743 fn test_slots_extra_appended() {
744 let slots = SystemPromptSlots {
745 extra: Some("Remember: always write tests first.".to_string()),
746 ..Default::default()
747 };
748 let built = slots.build();
749 assert!(built.contains("Remember: always write tests first."));
750 assert!(built.contains("Core Behaviour"));
751 }
752
753 #[test]
754 fn test_slots_with_extra() {
755 let slots = SystemPromptSlots::default().with_extra("You are a helpful assistant.");
756 let built = slots.build();
757 assert!(built.contains("You are a helpful assistant."));
758 assert!(built.contains("Core Behaviour"));
759 assert!(built.contains("Tool Usage Strategy"));
760 }
761
762 #[test]
763 fn test_slots_all_slots_combined() {
764 let slots = SystemPromptSlots {
765 style: None,
766 role: Some("You are a Rust expert".to_string()),
767 guidelines: Some("Use clippy. No unwrap.".to_string()),
768 response_style: Some("Short answers only.".to_string()),
769 extra: Some("Project uses tokio.".to_string()),
770 };
771 let built = slots.build();
772 assert!(built.contains("You are a Rust expert"));
773 assert!(built.contains("Core Behaviour"));
774 assert!(built.contains("## Guidelines"));
775 assert!(built.contains("Use clippy"));
776 assert!(built.contains("Short answers only"));
777 assert!(built.contains("Project uses tokio"));
778 assert!(!built.contains("keep progress notes brief and useful"));
780 }
781
782 #[test]
783 fn test_slots_is_empty() {
784 assert!(SystemPromptSlots::default().is_empty());
785 assert!(!SystemPromptSlots {
786 role: Some("test".to_string()),
787 ..Default::default()
788 }
789 .is_empty());
790 assert!(!SystemPromptSlots {
791 style: Some(AgentStyle::Plan),
792 ..Default::default()
793 }
794 .is_empty());
795 }
796
797 #[test]
800 fn test_agent_style_default_is_general_purpose() {
801 assert_eq!(AgentStyle::default(), AgentStyle::GeneralPurpose);
802 }
803
804 #[test]
805 fn test_agent_style_base_prompt() {
806 assert_eq!(AgentStyle::GeneralPurpose.base_prompt(), SYSTEM_DEFAULT);
807 assert_eq!(AgentStyle::Plan.base_prompt(), AGENT_PLAN);
808 assert_eq!(AgentStyle::Explore.base_prompt(), AGENT_EXPLORE);
809 assert_eq!(AgentStyle::Verification.base_prompt(), AGENT_VERIFICATION);
810 assert_eq!(AgentStyle::CodeReview.base_prompt(), AGENT_CODE_REVIEW);
811 }
812
813 #[test]
814 fn test_agent_style_guidelines() {
815 assert!(AgentStyle::GeneralPurpose.guidelines().is_none());
816 assert!(AgentStyle::Plan.guidelines().is_none()); assert!(AgentStyle::Explore.guidelines().is_none());
818 assert!(AgentStyle::Verification.guidelines().is_none());
819 assert!(AgentStyle::CodeReview.guidelines().is_none());
820 }
821
822 #[test]
823 fn test_agent_style_builtin_agent_name_mapping() {
824 assert_eq!(AgentStyle::GeneralPurpose.builtin_agent_name(), "general");
825 assert_eq!(AgentStyle::Plan.builtin_agent_name(), "plan");
826 assert_eq!(AgentStyle::Explore.builtin_agent_name(), "explore");
827 assert_eq!(
828 AgentStyle::Verification.builtin_agent_name(),
829 "verification"
830 );
831 assert_eq!(AgentStyle::CodeReview.builtin_agent_name(), "review");
832 }
833
834 #[test]
835 fn test_agent_style_runtime_mode_mapping() {
836 assert_eq!(AgentStyle::GeneralPurpose.runtime_mode(), "general");
837 assert_eq!(AgentStyle::Plan.runtime_mode(), "planning");
838 assert_eq!(AgentStyle::Explore.runtime_mode(), "explore");
839 assert_eq!(AgentStyle::Verification.runtime_mode(), "verification");
840 assert_eq!(AgentStyle::CodeReview.runtime_mode(), "code_review");
841 }
842
843 #[test]
844 fn test_agent_style_detect_plan() {
845 assert_eq!(
846 AgentStyle::detect_from_message("Help me plan a new feature"),
847 AgentStyle::Plan
848 );
849 assert_eq!(
850 AgentStyle::detect_from_message("Design the architecture for this"),
851 AgentStyle::Plan
852 );
853 assert_eq!(
854 AgentStyle::detect_from_message("What's the implementation approach?"),
855 AgentStyle::Plan
856 );
857 }
858
859 #[test]
860 fn test_agent_style_detect_verification() {
861 assert_eq!(
862 AgentStyle::detect_from_message("Verify that this works correctly"),
863 AgentStyle::Verification
864 );
865 assert_eq!(
866 AgentStyle::detect_from_message("Test the login flow"),
867 AgentStyle::Verification
868 );
869 assert_eq!(
870 AgentStyle::detect_from_message("Check if the API handles edge cases"),
871 AgentStyle::Verification
872 );
873 }
874
875 #[test]
876 fn test_agent_style_detect_explore() {
877 assert_eq!(
878 AgentStyle::detect_from_message("Find all files related to auth"),
879 AgentStyle::Explore
880 );
881 assert_eq!(
882 AgentStyle::detect_from_message("Where is the user model defined?"),
883 AgentStyle::Explore
884 );
885 assert_eq!(
886 AgentStyle::detect_from_message("Search for password hashing code"),
887 AgentStyle::Explore
888 );
889 }
890
891 #[test]
892 fn test_agent_style_detect_code_review() {
893 assert_eq!(
894 AgentStyle::detect_from_message("Review the PR changes"),
895 AgentStyle::CodeReview
896 );
897 assert_eq!(
898 AgentStyle::detect_from_message("Analyze this code for best practices"),
899 AgentStyle::CodeReview
900 );
901 assert_eq!(
902 AgentStyle::detect_from_message("Assess code quality"),
903 AgentStyle::CodeReview
904 );
905 }
906
907 #[test]
908 fn test_agent_style_detect_default_is_general_purpose() {
909 assert_eq!(
911 AgentStyle::detect_from_message("Implement the new feature"),
912 AgentStyle::GeneralPurpose
913 );
914 assert_eq!(
916 AgentStyle::detect_from_message("Write code for the API"),
917 AgentStyle::GeneralPurpose
918 );
919 }
920
921 #[test]
922 fn test_build_with_message_auto_detects_style() {
923 let slots = SystemPromptSlots::default();
924 let built = slots.build_with_message("Help me plan a new feature");
925 assert!(built.contains("planning agent") || built.contains("READ-ONLY"));
927 }
928
929 #[test]
930 fn test_build_with_message_explicit_style_overrides() {
931 let slots = SystemPromptSlots {
932 style: Some(AgentStyle::Verification),
933 ..Default::default()
934 };
935 let built = slots.build_with_message("Help me plan a new feature");
936 assert!(built.contains("adversarial verification specialist"));
938 }
939
940 #[test]
941 fn test_build_with_message_plan_style() {
942 let slots = SystemPromptSlots::default();
943 let built = slots.build_with_message("Design the system architecture");
944 assert!(built.contains("planning agent") || built.contains("READ-ONLY"));
945 }
946
947 #[test]
948 fn test_build_with_message_explore_style() {
949 let slots = SystemPromptSlots::default();
950 let built = slots.build_with_message("Find all authentication files");
951 assert!(built.contains("exploration agent") || built.contains("explore"));
952 }
953
954 #[test]
955 fn test_build_with_message_code_review_style() {
956 let slots = SystemPromptSlots::default();
957 let built = slots.build_with_message("Review this code");
958 assert!(built.contains("code review agent"));
959 assert!(built.contains("regressions"));
960 }
961
962 #[test]
963 fn test_builder_methods() {
964 let slots = SystemPromptSlots::default()
965 .with_style(AgentStyle::Plan)
966 .with_role("You are a Python expert")
967 .with_guidelines("Use type hints")
968 .with_response_style("Be brief")
969 .with_extra("Additional instructions");
970
971 assert_eq!(slots.style, Some(AgentStyle::Plan));
972 assert_eq!(slots.role, Some("You are a Python expert".to_string()));
973 assert_eq!(slots.guidelines, Some("Use type hints".to_string()));
974 assert_eq!(slots.response_style, Some("Be brief".to_string()));
975 assert_eq!(slots.extra, Some("Additional instructions".to_string()));
976
977 let built = slots.build();
978 assert!(built.contains("Python expert"));
979 assert!(built.contains("Use type hints"));
980 assert!(built.contains("Be brief"));
981 assert!(built.contains("Additional instructions"));
982 }
983
984 #[test]
985 fn test_code_review_guidelines_appended() {
986 let slots = SystemPromptSlots {
987 style: Some(AgentStyle::CodeReview),
988 ..Default::default()
989 };
990 let built = slots.build();
991 assert!(built.contains("code review agent"));
992 assert!(built.contains("correctness"));
993 assert!(built.contains("regressions"));
994 assert!(built.contains("security"));
995 }
996
997 #[test]
998 fn test_prompts_do_not_reference_removed_surfaces() {
999 let prompts = [
1000 SYSTEM_DEFAULT,
1001 AGENT_VERIFICATION,
1002 PRE_ANALYSIS_SYSTEM,
1003 AGENT_EXPLORE,
1004 AGENT_PLAN,
1005 AGENT_CODE_REVIEW,
1006 SKILLS_CATALOG_HEADER,
1007 CONTINUATION,
1008 ]
1009 .join("\n")
1010 .to_lowercase();
1011
1012 for removed in [
1013 "orchestrator",
1014 "plugin",
1015 "agentic_search",
1016 "agentic_parse",
1017 "agentic-search",
1018 "agentic-parse",
1019 "manage_skill",
1020 "claude.md",
1021 ] {
1022 assert!(
1023 !prompts.contains(removed),
1024 "prompt still references removed surface: {removed}"
1025 );
1026 }
1027
1028 assert!(SYSTEM_DEFAULT.contains("program"));
1029 assert!(SYSTEM_DEFAULT.contains("task"));
1030 assert!(SYSTEM_DEFAULT.contains("parallel_task"));
1031 assert!(SYSTEM_DEFAULT.contains("AHP"));
1032 }
1033}