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 AGENT_EXPLORE: &str = include_str!("../prompts/agents/explore.md");
37
38pub const AGENT_PLAN: &str = include_str!("../prompts/agents/plan.md");
40
41pub const AGENT_CODE_REVIEW: &str = include_str!("../prompts/agents/code_review.md");
43
44pub const CONTEXT_COMPACT: &str = include_str!("../prompts/common/context_compact.md");
50
51pub const CONTEXT_SUMMARY_PREFIX: &str =
53 include_str!("../prompts/common/context_summary_prefix.md");
54
55pub const LLM_PLAN_SYSTEM: &str = include_str!("../prompts/planning/llm_plan_system.md");
61
62pub const LLM_GOAL_EXTRACT_SYSTEM: &str =
64 include_str!("../prompts/planning/llm_goal_extract_system.md");
65
66pub const LLM_GOAL_CHECK_SYSTEM: &str =
68 include_str!("../prompts/planning/llm_goal_check_system.md");
69
70pub const PRE_ANALYSIS_SYSTEM: &str = include_str!("../prompts/analysis/pre_analysis_system.md");
72
73pub const PLAN_EXECUTE_GOAL: &str = include_str!("../prompts/planning/plan_execute_goal.md");
79
80pub const PLAN_EXECUTE_STEP: &str = include_str!("../prompts/planning/plan_execute_step.md");
82
83pub const PLAN_FALLBACK_STEP: &str = include_str!("../prompts/planning/plan_fallback_step.md");
85
86pub const SKILLS_CATALOG_HEADER: &str = include_str!("../prompts/common/skills_catalog_header.md");
88
89pub const AGENT_VERIFICATION: &str = include_str!("../prompts/agents/verification.md");
95
96pub const INTENT_CLASSIFY_SYSTEM: &str =
102 include_str!("../prompts/analysis/intent_classify_system.md");
103
104use serde::{Deserialize, Serialize};
109
110#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
116pub enum PlanningMode {
117 #[default]
121 Auto,
122 Disabled,
124 Enabled,
126}
127
128impl PlanningMode {
129 pub fn should_plan(&self, message: &str) -> bool {
134 match self {
135 PlanningMode::Auto => AgentStyle::detect_from_message(message).requires_planning(),
136 PlanningMode::Enabled => true,
137 PlanningMode::Disabled => false,
138 }
139 }
140}
141
142#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
151pub enum AgentStyle {
152 #[default]
154 GeneralPurpose,
155 Plan,
158 Verification,
160 Explore,
163 CodeReview,
165}
166
167#[derive(Debug, Clone, Copy, PartialEq, Eq)]
169pub enum DetectionConfidence {
170 High,
172 Medium,
174 Low,
176}
177
178impl AgentStyle {
179 pub fn base_prompt(&self) -> &'static str {
181 match self {
182 AgentStyle::GeneralPurpose => SYSTEM_DEFAULT,
183 AgentStyle::Plan => AGENT_PLAN,
184 AgentStyle::Verification => AGENT_VERIFICATION,
185 AgentStyle::Explore => AGENT_EXPLORE,
186 AgentStyle::CodeReview => AGENT_CODE_REVIEW,
187 }
188 }
189
190 pub fn guidelines(&self) -> Option<&'static str> {
192 match self {
193 AgentStyle::GeneralPurpose => None,
194 AgentStyle::Plan => None, AgentStyle::Verification => None, AgentStyle::Explore => None, AgentStyle::CodeReview => None,
198 }
199 }
200
201 pub fn description(&self) -> &'static str {
203 match self {
204 AgentStyle::GeneralPurpose => {
205 "General purpose coding agent for research and multi-step tasks"
206 }
207 AgentStyle::Plan => "Read-only planning and architecture analysis agent",
208 AgentStyle::Verification => "Adversarial verification specialist — tries to break code",
209 AgentStyle::Explore => "Fast read-only file search and codebase exploration agent",
210 AgentStyle::CodeReview => "Code review focused — analyzes quality and best practices",
211 }
212 }
213
214 pub fn builtin_agent_name(&self) -> &'static str {
216 match self {
217 AgentStyle::GeneralPurpose => "general",
218 AgentStyle::Plan => "plan",
219 AgentStyle::Verification => "verification",
220 AgentStyle::Explore => "explore",
221 AgentStyle::CodeReview => "review",
222 }
223 }
224
225 pub fn runtime_mode(&self) -> &'static str {
227 match self {
228 AgentStyle::GeneralPurpose => "general",
229 AgentStyle::Plan => "planning",
230 AgentStyle::Verification => "verification",
231 AgentStyle::Explore => "explore",
232 AgentStyle::CodeReview => "code_review",
233 }
234 }
235
236 pub fn requires_planning(&self) -> bool {
241 matches!(self, AgentStyle::Plan)
242 }
243
244 pub fn detect_with_confidence(message: &str) -> (Self, DetectionConfidence) {
250 if message
254 .chars()
255 .any(|c| ('\u{4e00}'..='\u{9fff}').contains(&c))
256 {
257 return (AgentStyle::GeneralPurpose, DetectionConfidence::Low);
258 }
259
260 let lower = message.to_lowercase();
261
262 if lower.contains("try to break")
266 || lower.contains("find vulnerabilities")
267 || lower.contains("adversarial")
268 || lower.contains("security audit")
269 {
270 return (AgentStyle::Verification, DetectionConfidence::High);
271 }
272
273 if lower.contains("help me plan")
275 || lower.contains("help me design")
276 || lower.contains("create a plan")
277 || lower.contains("implementation plan")
278 || lower.contains("step-by-step plan")
279 {
280 return (AgentStyle::Plan, DetectionConfidence::High);
281 }
282
283 if lower.contains("find all files")
285 || lower.contains("search for all")
286 || lower.contains("locate all")
287 {
288 return (AgentStyle::Explore, DetectionConfidence::High);
289 }
290
291 if lower.contains("verify")
295 || lower.contains("verification")
296 || lower.contains("break")
297 || lower.contains("debug")
298 || lower.contains("test")
299 || lower.contains("check if")
300 {
301 return (AgentStyle::Verification, DetectionConfidence::Medium);
302 }
303
304 if lower.contains("plan")
306 || lower.contains("design")
307 || lower.contains("architecture")
308 || lower.contains("approach")
309 {
310 return (AgentStyle::Plan, DetectionConfidence::Medium);
311 }
312
313 if lower.contains("find")
315 || lower.contains("search")
316 || lower.contains("where is")
317 || lower.contains("where's")
318 || lower.contains("locate")
319 || lower.contains("explore")
320 || lower.contains("look for")
321 {
322 return (AgentStyle::Explore, DetectionConfidence::Medium);
323 }
324
325 if lower.contains("review")
327 || lower.contains("code review")
328 || lower.contains("analyze")
329 || lower.contains("assess")
330 || lower.contains("quality")
331 || lower.contains("best practice")
332 {
333 return (AgentStyle::CodeReview, DetectionConfidence::Medium);
334 }
335
336 (AgentStyle::GeneralPurpose, DetectionConfidence::Low)
338 }
339
340 pub fn detect_from_message(message: &str) -> Self {
346 Self::detect_with_confidence(message).0
347 }
348
349 pub async fn detect_with_llm(llm: &dyn LlmClient, message: &str) -> anyhow::Result<Self> {
356 use crate::llm::Message;
357
358 let system = INTENT_CLASSIFY_SYSTEM;
359 let messages = vec![Message::user(message)];
360
361 let response = llm
362 .complete(&messages, Some(system), &[])
363 .await
364 .context("LLM intent classification failed")?;
365
366 let text = response.text().trim().to_lowercase();
367
368 let style = match text.as_str() {
369 "plan" => AgentStyle::Plan,
370 "explore" => AgentStyle::Explore,
371 "verification" => AgentStyle::Verification,
372 "codereview" | "code review" => AgentStyle::CodeReview,
373 _ => AgentStyle::GeneralPurpose,
374 };
375
376 Ok(style)
377 }
378}
379
380#[derive(Debug, Clone, Default)]
407pub struct SystemPromptSlots {
408 pub style: Option<AgentStyle>,
413
414 pub role: Option<String>,
419
420 pub guidelines: Option<String>,
424
425 pub response_style: Option<String>,
429
430 pub extra: Option<String>,
432}
433
434const DEFAULT_ROLE_LINE: &str = include_str!("../prompts/common/system_default_role_line.md");
436
437const DEFAULT_RESPONSE_FORMAT: &str =
439 include_str!("../prompts/common/system_default_response_format.md");
440
441impl SystemPromptSlots {
442 pub fn build(&self) -> String {
450 self.build_with_style(self.style.unwrap_or_default())
451 }
452
453 pub fn build_with_message(&self, initial_message: &str) -> String {
458 let style = self
459 .style
460 .unwrap_or_else(|| AgentStyle::detect_from_message(initial_message));
461 self.build_with_style(style)
462 }
463
464 fn build_with_style(&self, style: AgentStyle) -> String {
466 let mut parts: Vec<String> = Vec::new();
467
468 let base_prompt = style.base_prompt().replace('\r', "");
471 let default_role_line = DEFAULT_ROLE_LINE.replace('\r', "");
472 let default_response_format = DEFAULT_RESPONSE_FORMAT.replace('\r', "");
473
474 let core = if let Some(ref role) = self.role {
478 if style == AgentStyle::GeneralPurpose {
479 let custom_role = format!(
480 "{}. You operate in an agentic loop: inspect, act with tools, observe results, and continue until the user's request is genuinely complete.",
481 role.trim_end_matches('.')
482 );
483 base_prompt.replace(&default_role_line, &custom_role)
484 } else {
485 format!("{}\n\n{}", role, base_prompt)
487 }
488 } else {
489 base_prompt
490 };
491
492 let core = if self.response_style.is_some() {
494 core.replace(&default_response_format, "")
495 .trim_end()
496 .to_string()
497 } else {
498 core.trim_end().to_string()
499 };
500
501 parts.push(core);
502
503 if let Some(ref style) = self.response_style {
505 parts.push(format!("## Response Format\n\n{}", style));
506 }
507
508 let style_guidelines = style.guidelines();
510 if style_guidelines.is_some() || self.guidelines.is_some() {
511 let mut guidelines_parts = Vec::new();
512 if let Some(sg) = style_guidelines {
513 guidelines_parts.push(sg.to_string());
514 }
515 if let Some(ref g) = self.guidelines {
516 guidelines_parts.push(g.clone());
517 }
518 parts.push(format!(
519 "## Guidelines\n\n{}",
520 guidelines_parts.join("\n\n")
521 ));
522 }
523
524 if let Some(ref extra) = self.extra {
526 parts.push(extra.clone());
527 }
528
529 parts.join("\n\n")
530 }
531
532 pub fn is_empty(&self) -> bool {
534 self.style.is_none()
535 && self.role.is_none()
536 && self.guidelines.is_none()
537 && self.response_style.is_none()
538 && self.extra.is_none()
539 }
540
541 pub fn with_style(mut self, style: AgentStyle) -> Self {
543 self.style = Some(style);
544 self
545 }
546
547 pub fn with_role(mut self, role: impl Into<String>) -> Self {
549 self.role = Some(role.into());
550 self
551 }
552
553 pub fn with_guidelines(mut self, guidelines: impl Into<String>) -> Self {
555 self.guidelines = Some(guidelines.into());
556 self
557 }
558
559 pub fn with_response_style(mut self, style: impl Into<String>) -> Self {
561 self.response_style = Some(style.into());
562 self
563 }
564
565 pub fn with_extra(mut self, extra: impl Into<String>) -> Self {
567 self.extra = Some(extra.into());
568 self
569 }
570}
571
572pub fn render(template: &str, vars: &[(&str, &str)]) -> String {
578 let mut result = template.to_string();
579 for (key, value) in vars {
580 result = result.replace(&format!("{{{}}}", key), value);
581 }
582 result
583}
584
585#[cfg(test)]
586mod tests {
587 use super::*;
588
589 #[test]
590 fn test_all_prompts_loaded() {
591 assert!(!SYSTEM_DEFAULT.is_empty());
593 assert!(!CONTINUATION.is_empty());
594 assert!(!AGENT_EXPLORE.is_empty());
595 assert!(!AGENT_PLAN.is_empty());
596 assert!(!AGENT_CODE_REVIEW.is_empty());
597 assert!(!CONTEXT_COMPACT.is_empty());
598 assert!(!LLM_PLAN_SYSTEM.is_empty());
599 assert!(!LLM_GOAL_EXTRACT_SYSTEM.is_empty());
600 assert!(!LLM_GOAL_CHECK_SYSTEM.is_empty());
601 assert!(!SKILLS_CATALOG_HEADER.is_empty());
602 assert!(!PLAN_EXECUTE_GOAL.is_empty());
603 assert!(!PLAN_EXECUTE_STEP.is_empty());
604 assert!(!PLAN_FALLBACK_STEP.is_empty());
605 }
606
607 #[test]
608 fn test_render_template() {
609 let result = render(
610 PLAN_EXECUTE_GOAL,
611 &[("goal", "Build app"), ("steps", "1. Init")],
612 );
613 assert!(result.contains("Build app"));
614 assert!(!result.contains("{goal}"));
615 }
616
617 #[test]
618 fn test_render_multiple_placeholders() {
619 let template = "Goal: {goal}\nCriteria: {criteria}\nState: {current_state}";
620 let result = render(
621 template,
622 &[
623 ("goal", "Build a REST API"),
624 ("criteria", "- Endpoint works\n- Tests pass"),
625 ("current_state", "API is deployed"),
626 ],
627 );
628 assert!(result.contains("Build a REST API"));
629 assert!(result.contains("Endpoint works"));
630 assert!(result.contains("API is deployed"));
631 }
632
633 #[test]
634 fn test_delegated_agent_prompts_contain_guidelines() {
635 assert!(AGENT_EXPLORE.contains("Guidelines"));
636 assert!(AGENT_EXPLORE.contains("read-only"));
637 assert!(AGENT_PLAN.contains("Guidelines"));
638 assert!(AGENT_PLAN.contains("read-only"));
639 }
640
641 #[test]
642 fn test_context_summary_prefix() {
643 assert!(CONTEXT_SUMMARY_PREFIX.contains("Context Summary"));
644 }
645
646 #[test]
649 fn test_slots_default_builds_system_default() {
650 let slots = SystemPromptSlots::default();
651 let built = slots.build();
652 assert!(built.contains("Core Behaviour"));
653 assert!(built.contains("Tool Usage Strategy"));
654 assert!(built.contains("Completion Criteria"));
655 assert!(built.contains("Response Format"));
656 assert!(built.contains("A3S Code"));
657 }
658
659 #[test]
660 fn test_slots_custom_role_replaces_default() {
661 let slots = SystemPromptSlots {
662 role: Some("You are a senior Python developer".to_string()),
663 ..Default::default()
664 };
665 let built = slots.build();
666 assert!(built.contains("You are a senior Python developer"));
667 assert!(!built.contains("You are A3S Code"));
668 assert!(built.contains("Core Behaviour"));
670 assert!(built.contains("Tool Usage Strategy"));
671 }
672
673 #[test]
674 fn test_slots_custom_guidelines_appended() {
675 let slots = SystemPromptSlots {
676 guidelines: Some("Always use type hints. Follow PEP 8.".to_string()),
677 ..Default::default()
678 };
679 let built = slots.build();
680 assert!(built.contains("## Guidelines"));
681 assert!(built.contains("Always use type hints"));
682 assert!(built.contains("Core Behaviour"));
683 }
684
685 #[test]
686 fn test_slots_custom_response_style_replaces_default() {
687 let slots = SystemPromptSlots {
688 response_style: Some("Be concise. Use bullet points.".to_string()),
689 ..Default::default()
690 };
691 let built = slots.build();
692 assert!(built.contains("Be concise. Use bullet points."));
693 assert!(!built.contains("keep progress notes brief and useful"));
695 assert!(built.contains("Core Behaviour"));
697 }
698
699 #[test]
700 fn test_slots_extra_appended() {
701 let slots = SystemPromptSlots {
702 extra: Some("Remember: always write tests first.".to_string()),
703 ..Default::default()
704 };
705 let built = slots.build();
706 assert!(built.contains("Remember: always write tests first."));
707 assert!(built.contains("Core Behaviour"));
708 }
709
710 #[test]
711 fn test_slots_with_extra() {
712 let slots = SystemPromptSlots::default().with_extra("You are a helpful assistant.");
713 let built = slots.build();
714 assert!(built.contains("You are a helpful assistant."));
715 assert!(built.contains("Core Behaviour"));
716 assert!(built.contains("Tool Usage Strategy"));
717 }
718
719 #[test]
720 fn test_slots_all_slots_combined() {
721 let slots = SystemPromptSlots {
722 style: None,
723 role: Some("You are a Rust expert".to_string()),
724 guidelines: Some("Use clippy. No unwrap.".to_string()),
725 response_style: Some("Short answers only.".to_string()),
726 extra: Some("Project uses tokio.".to_string()),
727 };
728 let built = slots.build();
729 assert!(built.contains("You are a Rust expert"));
730 assert!(built.contains("Core Behaviour"));
731 assert!(built.contains("## Guidelines"));
732 assert!(built.contains("Use clippy"));
733 assert!(built.contains("Short answers only"));
734 assert!(built.contains("Project uses tokio"));
735 assert!(!built.contains("keep progress notes brief and useful"));
737 }
738
739 #[test]
740 fn test_slots_is_empty() {
741 assert!(SystemPromptSlots::default().is_empty());
742 assert!(!SystemPromptSlots {
743 role: Some("test".to_string()),
744 ..Default::default()
745 }
746 .is_empty());
747 assert!(!SystemPromptSlots {
748 style: Some(AgentStyle::Plan),
749 ..Default::default()
750 }
751 .is_empty());
752 }
753
754 #[test]
757 fn test_agent_style_default_is_general_purpose() {
758 assert_eq!(AgentStyle::default(), AgentStyle::GeneralPurpose);
759 }
760
761 #[test]
762 fn test_agent_style_base_prompt() {
763 assert_eq!(AgentStyle::GeneralPurpose.base_prompt(), SYSTEM_DEFAULT);
764 assert_eq!(AgentStyle::Plan.base_prompt(), AGENT_PLAN);
765 assert_eq!(AgentStyle::Explore.base_prompt(), AGENT_EXPLORE);
766 assert_eq!(AgentStyle::Verification.base_prompt(), AGENT_VERIFICATION);
767 assert_eq!(AgentStyle::CodeReview.base_prompt(), AGENT_CODE_REVIEW);
768 }
769
770 #[test]
771 fn test_agent_style_guidelines() {
772 assert!(AgentStyle::GeneralPurpose.guidelines().is_none());
773 assert!(AgentStyle::Plan.guidelines().is_none()); assert!(AgentStyle::Explore.guidelines().is_none());
775 assert!(AgentStyle::Verification.guidelines().is_none());
776 assert!(AgentStyle::CodeReview.guidelines().is_none());
777 }
778
779 #[test]
780 fn test_agent_style_builtin_agent_name_mapping() {
781 assert_eq!(AgentStyle::GeneralPurpose.builtin_agent_name(), "general");
782 assert_eq!(AgentStyle::Plan.builtin_agent_name(), "plan");
783 assert_eq!(AgentStyle::Explore.builtin_agent_name(), "explore");
784 assert_eq!(
785 AgentStyle::Verification.builtin_agent_name(),
786 "verification"
787 );
788 assert_eq!(AgentStyle::CodeReview.builtin_agent_name(), "review");
789 }
790
791 #[test]
792 fn test_agent_style_runtime_mode_mapping() {
793 assert_eq!(AgentStyle::GeneralPurpose.runtime_mode(), "general");
794 assert_eq!(AgentStyle::Plan.runtime_mode(), "planning");
795 assert_eq!(AgentStyle::Explore.runtime_mode(), "explore");
796 assert_eq!(AgentStyle::Verification.runtime_mode(), "verification");
797 assert_eq!(AgentStyle::CodeReview.runtime_mode(), "code_review");
798 }
799
800 #[test]
801 fn test_agent_style_detect_plan() {
802 assert_eq!(
803 AgentStyle::detect_from_message("Help me plan a new feature"),
804 AgentStyle::Plan
805 );
806 assert_eq!(
807 AgentStyle::detect_from_message("Design the architecture for this"),
808 AgentStyle::Plan
809 );
810 assert_eq!(
811 AgentStyle::detect_from_message("What's the implementation approach?"),
812 AgentStyle::Plan
813 );
814 }
815
816 #[test]
817 fn test_agent_style_detect_verification() {
818 assert_eq!(
819 AgentStyle::detect_from_message("Verify that this works correctly"),
820 AgentStyle::Verification
821 );
822 assert_eq!(
823 AgentStyle::detect_from_message("Test the login flow"),
824 AgentStyle::Verification
825 );
826 assert_eq!(
827 AgentStyle::detect_from_message("Check if the API handles edge cases"),
828 AgentStyle::Verification
829 );
830 }
831
832 #[test]
833 fn test_agent_style_detect_explore() {
834 assert_eq!(
835 AgentStyle::detect_from_message("Find all files related to auth"),
836 AgentStyle::Explore
837 );
838 assert_eq!(
839 AgentStyle::detect_from_message("Where is the user model defined?"),
840 AgentStyle::Explore
841 );
842 assert_eq!(
843 AgentStyle::detect_from_message("Search for password hashing code"),
844 AgentStyle::Explore
845 );
846 }
847
848 #[test]
849 fn test_agent_style_detect_code_review() {
850 assert_eq!(
851 AgentStyle::detect_from_message("Review the PR changes"),
852 AgentStyle::CodeReview
853 );
854 assert_eq!(
855 AgentStyle::detect_from_message("Analyze this code for best practices"),
856 AgentStyle::CodeReview
857 );
858 assert_eq!(
859 AgentStyle::detect_from_message("Assess code quality"),
860 AgentStyle::CodeReview
861 );
862 }
863
864 #[test]
865 fn test_agent_style_detect_default_is_general_purpose() {
866 assert_eq!(
868 AgentStyle::detect_from_message("Implement the new feature"),
869 AgentStyle::GeneralPurpose
870 );
871 assert_eq!(
873 AgentStyle::detect_from_message("Write code for the API"),
874 AgentStyle::GeneralPurpose
875 );
876 }
877
878 #[test]
879 fn test_build_with_message_auto_detects_style() {
880 let slots = SystemPromptSlots::default();
881 let built = slots.build_with_message("Help me plan a new feature");
882 assert!(built.contains("planning agent") || built.contains("READ-ONLY"));
884 }
885
886 #[test]
887 fn test_build_with_message_explicit_style_overrides() {
888 let slots = SystemPromptSlots {
889 style: Some(AgentStyle::Verification),
890 ..Default::default()
891 };
892 let built = slots.build_with_message("Help me plan a new feature");
893 assert!(built.contains("adversarial verification specialist"));
895 }
896
897 #[test]
898 fn test_build_with_message_plan_style() {
899 let slots = SystemPromptSlots::default();
900 let built = slots.build_with_message("Design the system architecture");
901 assert!(built.contains("planning agent") || built.contains("READ-ONLY"));
902 }
903
904 #[test]
905 fn test_build_with_message_explore_style() {
906 let slots = SystemPromptSlots::default();
907 let built = slots.build_with_message("Find all authentication files");
908 assert!(built.contains("exploration agent") || built.contains("explore"));
909 }
910
911 #[test]
912 fn test_build_with_message_code_review_style() {
913 let slots = SystemPromptSlots::default();
914 let built = slots.build_with_message("Review this code");
915 assert!(built.contains("code review agent"));
916 assert!(built.contains("regressions"));
917 }
918
919 #[test]
920 fn test_builder_methods() {
921 let slots = SystemPromptSlots::default()
922 .with_style(AgentStyle::Plan)
923 .with_role("You are a Python expert")
924 .with_guidelines("Use type hints")
925 .with_response_style("Be brief")
926 .with_extra("Additional instructions");
927
928 assert_eq!(slots.style, Some(AgentStyle::Plan));
929 assert_eq!(slots.role, Some("You are a Python expert".to_string()));
930 assert_eq!(slots.guidelines, Some("Use type hints".to_string()));
931 assert_eq!(slots.response_style, Some("Be brief".to_string()));
932 assert_eq!(slots.extra, Some("Additional instructions".to_string()));
933
934 let built = slots.build();
935 assert!(built.contains("Python expert"));
936 assert!(built.contains("Use type hints"));
937 assert!(built.contains("Be brief"));
938 assert!(built.contains("Additional instructions"));
939 }
940
941 #[test]
942 fn test_code_review_guidelines_appended() {
943 let slots = SystemPromptSlots {
944 style: Some(AgentStyle::CodeReview),
945 ..Default::default()
946 };
947 let built = slots.build();
948 assert!(built.contains("code review agent"));
949 assert!(built.contains("correctness"));
950 assert!(built.contains("regressions"));
951 assert!(built.contains("security"));
952 }
953
954 #[test]
955 fn test_prompts_do_not_reference_removed_surfaces() {
956 let prompts = [
957 SYSTEM_DEFAULT,
958 AGENT_VERIFICATION,
959 PRE_ANALYSIS_SYSTEM,
960 AGENT_EXPLORE,
961 AGENT_PLAN,
962 AGENT_CODE_REVIEW,
963 SKILLS_CATALOG_HEADER,
964 CONTINUATION,
965 ]
966 .join("\n")
967 .to_lowercase();
968
969 for removed in [
970 "orchestrator",
971 "plugin",
972 "agentic_search",
973 "agentic_parse",
974 "agentic-search",
975 "agentic-parse",
976 "manage_skill",
977 "claude.md",
978 ] {
979 assert!(
980 !prompts.contains(removed),
981 "prompt still references removed surface: {removed}"
982 );
983 }
984
985 assert!(SYSTEM_DEFAULT.contains("program"));
986 assert!(SYSTEM_DEFAULT.contains("task"));
987 assert!(SYSTEM_DEFAULT.contains("parallel_task"));
988 assert!(SYSTEM_DEFAULT.contains("AHP"));
989 }
990}