1mod generators;
12mod templates;
13mod types;
14
15pub use types::{PromptArgument, PromptContent, PromptDefinition, PromptMessage};
16
17use serde_json::Value;
18use std::collections::HashMap;
19use types::user_prompt_to_definition;
20
21pub struct PromptRegistry {
23 prompts: HashMap<String, PromptDefinition>,
25}
26
27impl PromptRegistry {
28 #[must_use]
30 pub fn new() -> Self {
31 let mut prompts = HashMap::new();
32
33 for prompt in Self::all_prompts() {
35 prompts.insert(prompt.name.clone(), prompt);
36 }
37
38 Self { prompts }
39 }
40
41 fn all_prompts() -> Vec<PromptDefinition> {
43 vec![
44 Self::subcog_prompt(),
45 Self::tutorial_prompt(),
46 Self::generate_tutorial_definition(),
47 Self::generate_tutorial_alias_prompt(),
48 Self::capture_assistant_prompt(),
49 Self::capture_prompt_alias(),
50 Self::review_prompt(),
51 Self::document_decision_prompt(),
52 Self::generate_decision_alias_prompt(),
53 Self::search_help_prompt(),
54 Self::recall_prompt_alias(),
55 Self::browse_prompt(),
56 Self::list_prompt(),
57 Self::intent_search_prompt(),
59 Self::intent_search_alias_prompt(),
60 Self::query_suggest_prompt(),
61 Self::query_suggest_alias_prompt(),
62 Self::context_capture_prompt(),
63 Self::context_capture_alias_prompt(),
64 Self::discover_prompt(),
65 Self::discover_alias_prompt(),
66 Self::session_start_prompt(),
68 Self::session_start_alias_prompt(),
69 ]
70 }
71
72 fn session_start_prompt() -> PromptDefinition {
73 PromptDefinition {
74 name: "subcog_session_start".to_string(),
75 description: Some(
76 "Initialize a Subcog session with guidance, status, and optional context recall"
77 .to_string(),
78 ),
79 arguments: vec![
80 PromptArgument {
81 name: "include_recall".to_string(),
82 description: Some(
83 "Whether to recall project context (default: true)".to_string(),
84 ),
85 required: false,
86 },
87 PromptArgument {
88 name: "project".to_string(),
89 description: Some("Project name or context for recall".to_string()),
90 required: false,
91 },
92 ],
93 }
94 }
95
96 fn session_start_alias_prompt() -> PromptDefinition {
97 PromptDefinition {
98 name: "session_start".to_string(),
99 description: Some("Alias for subcog_session_start".to_string()),
100 arguments: vec![
101 PromptArgument {
102 name: "include_recall".to_string(),
103 description: Some(
104 "Whether to recall project context (default: true)".to_string(),
105 ),
106 required: false,
107 },
108 PromptArgument {
109 name: "project".to_string(),
110 description: Some("Project name or context for recall".to_string()),
111 required: false,
112 },
113 ],
114 }
115 }
116
117 fn subcog_prompt() -> PromptDefinition {
118 PromptDefinition {
119 name: "subcog".to_string(),
120 description: Some("Quickstart prompt (alias for subcog_tutorial)".to_string()),
121 arguments: vec![
122 PromptArgument {
123 name: "familiarity".to_string(),
124 description: Some("Your familiarity level with memory systems".to_string()),
125 required: false,
126 },
127 PromptArgument {
128 name: "focus".to_string(),
129 description: Some("Topic to focus on".to_string()),
130 required: false,
131 },
132 ],
133 }
134 }
135
136 fn tutorial_prompt() -> PromptDefinition {
137 PromptDefinition {
138 name: "subcog_tutorial".to_string(),
139 description: Some("Interactive tutorial for learning Subcog memory system".to_string()),
140 arguments: vec![
141 PromptArgument {
142 name: "familiarity".to_string(),
143 description: Some("Your familiarity level with memory systems".to_string()),
144 required: false,
145 },
146 PromptArgument {
147 name: "focus".to_string(),
148 description: Some("Topic to focus on".to_string()),
149 required: false,
150 },
151 ],
152 }
153 }
154
155 fn generate_tutorial_definition() -> PromptDefinition {
156 PromptDefinition {
157 name: "subcog_generate_tutorial".to_string(),
158 description: Some(
159 "Generate a tutorial on any topic using memories as source material".to_string(),
160 ),
161 arguments: vec![
162 PromptArgument {
163 name: "topic".to_string(),
164 description: Some("Topic to create tutorial for".to_string()),
165 required: true,
166 },
167 PromptArgument {
168 name: "level".to_string(),
169 description: Some(
170 "Tutorial level: beginner, intermediate, advanced".to_string(),
171 ),
172 required: false,
173 },
174 PromptArgument {
175 name: "format".to_string(),
176 description: Some("Output format: markdown, outline, steps".to_string()),
177 required: false,
178 },
179 ],
180 }
181 }
182
183 fn generate_tutorial_alias_prompt() -> PromptDefinition {
184 PromptDefinition {
185 name: "generate_tutorial".to_string(),
186 description: Some("Alias for subcog_generate_tutorial".to_string()),
187 arguments: vec![
188 PromptArgument {
189 name: "topic".to_string(),
190 description: Some("Topic to create tutorial for".to_string()),
191 required: true,
192 },
193 PromptArgument {
194 name: "level".to_string(),
195 description: Some(
196 "Tutorial level: beginner, intermediate, advanced".to_string(),
197 ),
198 required: false,
199 },
200 PromptArgument {
201 name: "format".to_string(),
202 description: Some("Output format: markdown, outline, steps".to_string()),
203 required: false,
204 },
205 ],
206 }
207 }
208
209 fn capture_assistant_prompt() -> PromptDefinition {
210 PromptDefinition {
211 name: "subcog_capture_assistant".to_string(),
212 description: Some("Help decide what to capture and which namespace to use".to_string()),
213 arguments: vec![PromptArgument {
214 name: "context".to_string(),
215 description: Some(
216 "The current context or conversation to analyze for memories".to_string(),
217 ),
218 required: true,
219 }],
220 }
221 }
222
223 fn capture_prompt_alias() -> PromptDefinition {
224 PromptDefinition {
225 name: "subcog_capture".to_string(),
226 description: Some("Alias for subcog_capture_assistant".to_string()),
227 arguments: vec![PromptArgument {
228 name: "content".to_string(),
229 description: Some("Memory content to capture".to_string()),
230 required: true,
231 }],
232 }
233 }
234
235 fn review_prompt() -> PromptDefinition {
236 PromptDefinition {
237 name: "subcog_review".to_string(),
238 description: Some("Review and analyze existing memories for a project".to_string()),
239 arguments: vec![
240 PromptArgument {
241 name: "namespace".to_string(),
242 description: Some("Optional namespace to focus on".to_string()),
243 required: false,
244 },
245 PromptArgument {
246 name: "action".to_string(),
247 description: Some(
248 "Action: summarize, consolidate, archive, or cleanup".to_string(),
249 ),
250 required: false,
251 },
252 ],
253 }
254 }
255
256 fn document_decision_prompt() -> PromptDefinition {
257 PromptDefinition {
258 name: "subcog_document_decision".to_string(),
259 description: Some(
260 "Help document an architectural or design decision properly".to_string(),
261 ),
262 arguments: vec![
263 PromptArgument {
264 name: "decision".to_string(),
265 description: Some("Brief description of the decision".to_string()),
266 required: true,
267 },
268 PromptArgument {
269 name: "alternatives".to_string(),
270 description: Some("Alternatives that were considered".to_string()),
271 required: false,
272 },
273 ],
274 }
275 }
276
277 fn generate_decision_alias_prompt() -> PromptDefinition {
278 PromptDefinition {
279 name: "generate_decision".to_string(),
280 description: Some("Alias for subcog_document_decision".to_string()),
281 arguments: vec![
282 PromptArgument {
283 name: "decision".to_string(),
284 description: Some("Brief description of the decision".to_string()),
285 required: true,
286 },
287 PromptArgument {
288 name: "context".to_string(),
289 description: Some("Background context for the decision".to_string()),
290 required: false,
291 },
292 PromptArgument {
293 name: "alternatives".to_string(),
294 description: Some("Alternatives that were considered".to_string()),
295 required: false,
296 },
297 ],
298 }
299 }
300
301 fn search_help_prompt() -> PromptDefinition {
302 PromptDefinition {
303 name: "subcog_search_help".to_string(),
304 description: Some("Help craft effective memory search queries".to_string()),
305 arguments: vec![PromptArgument {
306 name: "goal".to_string(),
307 description: Some("What you're trying to find or accomplish".to_string()),
308 required: true,
309 }],
310 }
311 }
312
313 fn recall_prompt_alias() -> PromptDefinition {
314 PromptDefinition {
315 name: "subcog_recall".to_string(),
316 description: Some("Alias for subcog_search_help".to_string()),
317 arguments: vec![PromptArgument {
318 name: "query".to_string(),
319 description: Some("Search query".to_string()),
320 required: true,
321 }],
322 }
323 }
324
325 fn browse_prompt() -> PromptDefinition {
326 PromptDefinition {
327 name: "subcog_browse".to_string(),
328 description: Some(
329 "Interactive memory browser with faceted discovery and filtering".to_string(),
330 ),
331 arguments: vec![
332 PromptArgument {
333 name: "filter".to_string(),
334 description: Some(
335 "Filter expression: ns:X, tag:X, tag:X,Y (OR), -tag:X (exclude), since:Nd, source:X, status:X".to_string(),
336 ),
337 required: false,
338 },
339 PromptArgument {
340 name: "view".to_string(),
341 description: Some(
342 "View mode: dashboard (default), tags, namespaces, memories".to_string(),
343 ),
344 required: false,
345 },
346 PromptArgument {
347 name: "top".to_string(),
348 description: Some("Number of items per facet (default: 10)".to_string()),
349 required: false,
350 },
351 ],
352 }
353 }
354
355 fn list_prompt() -> PromptDefinition {
356 PromptDefinition {
357 name: "subcog_list".to_string(),
358 description: Some(
359 "List memories in formatted table with namespace/tag summary".to_string(),
360 ),
361 arguments: vec![
362 PromptArgument {
363 name: "filter".to_string(),
364 description: Some(
365 "Filter expression: ns:X, tag:X, since:Nd (same syntax as subcog_browse)"
366 .to_string(),
367 ),
368 required: false,
369 },
370 PromptArgument {
371 name: "format".to_string(),
372 description: Some(
373 "Output format: table (default), compact, detailed".to_string(),
374 ),
375 required: false,
376 },
377 PromptArgument {
378 name: "limit".to_string(),
379 description: Some("Maximum memories to list (default: 50)".to_string()),
380 required: false,
381 },
382 ],
383 }
384 }
385
386 fn intent_search_prompt() -> PromptDefinition {
389 PromptDefinition {
390 name: "subcog_intent_search".to_string(),
391 description: Some(
392 "Search memories with automatic intent detection and query refinement".to_string(),
393 ),
394 arguments: vec![
395 PromptArgument {
396 name: "query".to_string(),
397 description: Some("Natural language query to search for".to_string()),
398 required: true,
399 },
400 PromptArgument {
401 name: "context".to_string(),
402 description: Some(
403 "Current working context (file, task) for relevance boosting".to_string(),
404 ),
405 required: false,
406 },
407 ],
408 }
409 }
410
411 fn intent_search_alias_prompt() -> PromptDefinition {
412 PromptDefinition {
413 name: "intent_search".to_string(),
414 description: Some("Alias for subcog_intent_search".to_string()),
415 arguments: vec![
416 PromptArgument {
417 name: "query".to_string(),
418 description: Some("Natural language query to search for".to_string()),
419 required: true,
420 },
421 PromptArgument {
422 name: "intent".to_string(),
423 description: Some(
424 "Intent hint: howto, location, explanation, etc.".to_string(),
425 ),
426 required: false,
427 },
428 PromptArgument {
429 name: "context".to_string(),
430 description: Some(
431 "Current working context (file, task) for relevance boosting".to_string(),
432 ),
433 required: false,
434 },
435 ],
436 }
437 }
438
439 fn query_suggest_prompt() -> PromptDefinition {
440 PromptDefinition {
441 name: "subcog_query_suggest".to_string(),
442 description: Some(
443 "Get query suggestions based on memory topics and current context".to_string(),
444 ),
445 arguments: vec![
446 PromptArgument {
447 name: "topic".to_string(),
448 description: Some("Topic area to explore".to_string()),
449 required: false,
450 },
451 PromptArgument {
452 name: "namespace".to_string(),
453 description: Some("Namespace to focus suggestions on".to_string()),
454 required: false,
455 },
456 ],
457 }
458 }
459
460 fn query_suggest_alias_prompt() -> PromptDefinition {
461 PromptDefinition {
462 name: "query_suggest".to_string(),
463 description: Some("Alias for subcog_query_suggest".to_string()),
464 arguments: vec![
465 PromptArgument {
466 name: "query".to_string(),
467 description: Some("Initial query".to_string()),
468 required: true,
469 },
470 PromptArgument {
471 name: "namespace".to_string(),
472 description: Some("Namespace to focus suggestions on".to_string()),
473 required: false,
474 },
475 ],
476 }
477 }
478
479 fn context_capture_prompt() -> PromptDefinition {
480 PromptDefinition {
481 name: "subcog_context_capture".to_string(),
482 description: Some(
483 "Analyze conversation context and suggest memories to capture".to_string(),
484 ),
485 arguments: vec![
486 PromptArgument {
487 name: "conversation".to_string(),
488 description: Some("Recent conversation or code changes to analyze".to_string()),
489 required: true,
490 },
491 PromptArgument {
492 name: "threshold".to_string(),
493 description: Some(
494 "Confidence threshold for suggestions (default: 0.7)".to_string(),
495 ),
496 required: false,
497 },
498 ],
499 }
500 }
501
502 fn context_capture_alias_prompt() -> PromptDefinition {
503 PromptDefinition {
504 name: "context_capture".to_string(),
505 description: Some("Alias for subcog_context_capture".to_string()),
506 arguments: vec![
507 PromptArgument {
508 name: "conversation".to_string(),
509 description: Some("Recent conversation or code changes to analyze".to_string()),
510 required: true,
511 },
512 PromptArgument {
513 name: "threshold".to_string(),
514 description: Some(
515 "Confidence threshold for suggestions (default: 0.7)".to_string(),
516 ),
517 required: false,
518 },
519 ],
520 }
521 }
522
523 fn discover_prompt() -> PromptDefinition {
524 PromptDefinition {
525 name: "subcog_discover".to_string(),
526 description: Some(
527 "Discover related memories and topics through exploratory navigation".to_string(),
528 ),
529 arguments: vec![
530 PromptArgument {
531 name: "start".to_string(),
532 description: Some("Starting point: memory ID, topic, or keyword".to_string()),
533 required: false,
534 },
535 PromptArgument {
536 name: "depth".to_string(),
537 description: Some("How many hops to explore (default: 2)".to_string()),
538 required: false,
539 },
540 ],
541 }
542 }
543
544 fn discover_alias_prompt() -> PromptDefinition {
545 PromptDefinition {
546 name: "discover".to_string(),
547 description: Some("Alias for subcog_discover".to_string()),
548 arguments: vec![
549 PromptArgument {
550 name: "topic".to_string(),
551 description: Some("Topic to explore".to_string()),
552 required: false,
553 },
554 PromptArgument {
555 name: "tag".to_string(),
556 description: Some("Tag to explore".to_string()),
557 required: false,
558 },
559 PromptArgument {
560 name: "depth".to_string(),
561 description: Some("How many hops to explore (default: 2)".to_string()),
562 required: false,
563 },
564 ],
565 }
566 }
567 #[must_use]
569 pub fn list_prompts(&self) -> Vec<&PromptDefinition> {
570 self.prompts.values().collect()
571 }
572
573 #[must_use]
578 pub fn list_all_prompts(
579 &self,
580 prompt_service: &mut crate::services::PromptService,
581 ) -> Vec<PromptDefinition> {
582 use crate::services::PromptFilter;
583
584 let mut result: Vec<PromptDefinition> = self.prompts.values().cloned().collect();
585
586 let user_prompts = prompt_service
588 .list(&PromptFilter::default())
589 .unwrap_or_default();
590 for template in user_prompts {
591 let definition = user_prompt_to_definition(&template);
592 if !self.prompts.contains_key(&definition.name) {
594 result.push(definition);
595 }
596 }
597
598 result
599 }
600
601 #[must_use]
605 pub fn get_prompt_with_user(
606 &self,
607 name: &str,
608 prompt_service: &mut crate::services::PromptService,
609 ) -> Option<PromptDefinition> {
610 if let Some(builtin) = self.prompts.get(name) {
612 return Some(builtin.clone());
613 }
614
615 let user_name = name.strip_prefix("user/").unwrap_or(name);
617 prompt_service
618 .get(user_name, None)
619 .ok()
620 .flatten()
621 .map(|t| user_prompt_to_definition(&t))
622 }
623
624 #[must_use]
626 pub fn get_prompt(&self, name: &str) -> Option<&PromptDefinition> {
627 self.prompts.get(name)
628 }
629
630 #[must_use]
632 pub fn get_prompt_messages(&self, name: &str, arguments: &Value) -> Option<Vec<PromptMessage>> {
633 match name {
634 "subcog_tutorial" | "subcog" => Some(generators::generate_tutorial_prompt(arguments)),
635 "subcog_generate_tutorial" | "generate_tutorial" => {
636 Some(generators::generate_generate_tutorial_messages(arguments))
637 },
638 "subcog_capture_assistant" | "subcog_capture" => {
639 Some(generators::generate_capture_assistant_prompt(arguments))
640 },
641 "subcog_review" => Some(generators::generate_review_prompt(arguments)),
642 "subcog_document_decision" | "generate_decision" => {
643 Some(generators::generate_decision_prompt(arguments))
644 },
645 "subcog_search_help" | "subcog_recall" => {
646 Some(generators::generate_search_help_prompt(arguments))
647 },
648 "subcog_browse" => Some(generators::generate_browse_prompt(arguments)),
649 "subcog_list" => Some(generators::generate_list_prompt(arguments)),
650 "subcog_intent_search" | "intent_search" => {
652 Some(generators::generate_intent_search_prompt(arguments))
653 },
654 "subcog_query_suggest" | "query_suggest" => {
655 Some(generators::generate_query_suggest_prompt(arguments))
656 },
657 "subcog_context_capture" | "context_capture" => {
658 Some(generators::generate_context_capture_prompt(arguments))
659 },
660 "subcog_discover" | "discover" => Some(generators::generate_discover_prompt(arguments)),
661 "subcog_session_start" | "session_start" => {
663 Some(generators::generate_session_start_prompt(arguments))
664 },
665 _ => None,
666 }
667 }
668}
669
670impl Default for PromptRegistry {
671 fn default() -> Self {
672 Self::new()
673 }
674}
675
676#[cfg(test)]
677mod tests {
678 use super::*;
679
680 #[test]
681 fn test_prompt_registry_creation() {
682 let registry = PromptRegistry::new();
683 let prompts = registry.list_prompts();
684
685 assert!(!prompts.is_empty());
686 assert!(registry.get_prompt("subcog_tutorial").is_some());
687 assert!(registry.get_prompt("subcog_capture_assistant").is_some());
688 assert!(registry.get_prompt("subcog").is_some());
689 assert!(registry.get_prompt("subcog_capture").is_some());
690 }
691
692 #[test]
693 fn test_prompt_definitions() {
694 let registry = PromptRegistry::new();
695
696 let tutorial = registry.get_prompt("subcog_tutorial").unwrap();
697 assert_eq!(tutorial.name, "subcog_tutorial");
698 assert!(tutorial.description.is_some());
699 assert!(!tutorial.arguments.is_empty());
700 }
701
702 #[test]
703 fn test_generate_tutorial_prompt() {
704 let registry = PromptRegistry::new();
705
706 let args = serde_json::json!({
707 "familiarity": "beginner",
708 "focus": "capture"
709 });
710
711 let messages = registry
712 .get_prompt_messages("subcog_tutorial", &args)
713 .unwrap();
714
715 assert_eq!(messages.len(), 2);
716 assert_eq!(messages[0].role, "user");
717 assert_eq!(messages[1].role, "assistant");
718
719 if let PromptContent::Text { text } = &messages[1].content {
720 assert!(text.contains("Capturing Memories"));
721 }
722 }
723
724 #[test]
725 fn test_prompt_alias_messages() {
726 let registry = PromptRegistry::new();
727
728 let messages = registry
729 .get_prompt_messages("subcog", &serde_json::json!({}))
730 .unwrap();
731 assert!(!messages.is_empty());
732
733 let messages = registry
734 .get_prompt_messages("generate_tutorial", &serde_json::json!({"topic": "MCP"}))
735 .unwrap();
736 assert!(!messages.is_empty());
737 }
738
739 #[test]
740 fn test_generate_decision_prompt() {
741 let registry = PromptRegistry::new();
742
743 let args = serde_json::json!({
744 "decision": "Use PostgreSQL",
745 "alternatives": "MySQL, SQLite"
746 });
747
748 let messages = registry
749 .get_prompt_messages("subcog_document_decision", &args)
750 .unwrap();
751
752 assert_eq!(messages.len(), 1);
753 if let PromptContent::Text { text } = &messages[0].content {
754 assert!(text.contains("PostgreSQL"));
755 assert!(text.contains("MySQL"));
756 }
757 }
758
759 #[test]
760 fn test_unknown_prompt() {
761 let registry = PromptRegistry::new();
762
763 let result = registry.get_prompt_messages("unknown_prompt", &serde_json::json!({}));
764 assert!(result.is_none());
765 }
766
767 #[test]
768 fn test_familiarity_levels() {
769 let registry = PromptRegistry::new();
770
771 for level in ["beginner", "intermediate", "advanced"] {
772 let args = serde_json::json!({ "familiarity": level });
773 let messages = registry
774 .get_prompt_messages("subcog_tutorial", &args)
775 .unwrap();
776
777 if let PromptContent::Text { text } = &messages[1].content {
778 assert!(!text.is_empty());
780 }
781 }
782 }
783
784 #[test]
787 fn test_intent_search_prompt() {
788 let registry = PromptRegistry::new();
789
790 let prompt = registry.get_prompt("subcog_intent_search").unwrap();
791 assert_eq!(prompt.name, "subcog_intent_search");
792 assert!(prompt.description.is_some());
793
794 let args = serde_json::json!({
795 "query": "authentication handling",
796 "context": "working on login flow"
797 });
798
799 let messages = registry
800 .get_prompt_messages("subcog_intent_search", &args)
801 .unwrap();
802
803 assert_eq!(messages.len(), 2);
804 if let PromptContent::Text { text } = &messages[0].content {
805 assert!(text.contains("authentication handling"));
806 assert!(text.contains("login flow"));
807 }
808 }
809
810 #[test]
811 fn test_query_suggest_prompt() {
812 let registry = PromptRegistry::new();
813
814 let prompt = registry.get_prompt("subcog_query_suggest").unwrap();
815 assert_eq!(prompt.name, "subcog_query_suggest");
816
817 let args = serde_json::json!({
818 "topic": "error handling",
819 "namespace": "patterns"
820 });
821
822 let messages = registry
823 .get_prompt_messages("subcog_query_suggest", &args)
824 .unwrap();
825
826 assert_eq!(messages.len(), 2);
827 if let PromptContent::Text { text } = &messages[0].content {
828 assert!(text.contains("error handling"));
829 assert!(text.contains("patterns"));
830 }
831 }
832
833 #[test]
834 fn test_context_capture_prompt() {
835 let registry = PromptRegistry::new();
836
837 let prompt = registry.get_prompt("subcog_context_capture").unwrap();
838 assert_eq!(prompt.name, "subcog_context_capture");
839
840 let args = serde_json::json!({
841 "conversation": "We decided to use PostgreSQL because it has better JSON support.",
842 "threshold": "0.8"
843 });
844
845 let messages = registry
846 .get_prompt_messages("subcog_context_capture", &args)
847 .unwrap();
848
849 assert_eq!(messages.len(), 2);
850 if let PromptContent::Text { text } = &messages[0].content {
851 assert!(text.contains("PostgreSQL"));
852 assert!(text.contains("0.8"));
853 }
854 }
855
856 #[test]
857 fn test_discover_prompt() {
858 let registry = PromptRegistry::new();
859
860 let prompt = registry.get_prompt("subcog_discover").unwrap();
861 assert_eq!(prompt.name, "subcog_discover");
862
863 let args = serde_json::json!({
864 "start": "authentication",
865 "depth": "3"
866 });
867
868 let messages = registry
869 .get_prompt_messages("subcog_discover", &args)
870 .unwrap();
871
872 assert_eq!(messages.len(), 2);
873 if let PromptContent::Text { text } = &messages[0].content {
874 assert!(text.contains("authentication"));
875 assert!(text.contains('3'));
876 }
877 }
878
879 #[test]
880 fn test_discover_prompt_no_start() {
881 let registry = PromptRegistry::new();
882
883 let args = serde_json::json!({});
884
885 let messages = registry
886 .get_prompt_messages("subcog_discover", &args)
887 .unwrap();
888
889 if let PromptContent::Text { text } = &messages[0].content {
890 assert!(text.contains("overview"));
891 }
892 }
893
894 #[test]
895 fn test_all_phase4_prompts_registered() {
896 let registry = PromptRegistry::new();
897
898 assert!(registry.get_prompt("subcog_intent_search").is_some());
900 assert!(registry.get_prompt("subcog_query_suggest").is_some());
901 assert!(registry.get_prompt("subcog_context_capture").is_some());
902 assert!(registry.get_prompt("subcog_discover").is_some());
903 }
904
905 #[test]
906 fn test_generate_tutorial_prompt_definition() {
907 let registry = PromptRegistry::new();
908
909 let prompt = registry.get_prompt("subcog_generate_tutorial").unwrap();
910 assert_eq!(prompt.name, "subcog_generate_tutorial");
911 assert!(prompt.description.is_some());
912 assert!(
913 prompt
914 .description
915 .as_ref()
916 .unwrap()
917 .contains("Generate a tutorial")
918 );
919
920 assert_eq!(prompt.arguments.len(), 3);
922
923 let topic_arg = prompt.arguments.iter().find(|a| a.name == "topic").unwrap();
924 assert!(topic_arg.required);
925
926 let level_arg = prompt.arguments.iter().find(|a| a.name == "level").unwrap();
927 assert!(!level_arg.required);
928
929 let format_arg = prompt
930 .arguments
931 .iter()
932 .find(|a| a.name == "format")
933 .unwrap();
934 assert!(!format_arg.required);
935 }
936
937 #[test]
938 fn test_generate_tutorial_prompt_messages() {
939 let registry = PromptRegistry::new();
940
941 let args = serde_json::json!({
942 "topic": "error handling",
943 "level": "intermediate",
944 "format": "markdown"
945 });
946
947 let messages = registry
948 .get_prompt_messages("subcog_generate_tutorial", &args)
949 .unwrap();
950
951 assert_eq!(messages.len(), 2);
952 assert_eq!(messages[0].role, "user");
953 assert_eq!(messages[1].role, "assistant");
954
955 if let PromptContent::Text { text } = &messages[0].content {
956 assert!(text.contains("error handling"));
957 assert!(text.contains("developer with some experience"));
958 assert!(text.contains("subcog_recall"));
959 }
960 }
961
962 #[test]
963 fn test_generate_tutorial_prompt_levels() {
964 let registry = PromptRegistry::new();
965
966 let beginner_args = serde_json::json!({ "topic": "testing", "level": "beginner" });
968 let beginner_messages = registry
969 .get_prompt_messages("subcog_generate_tutorial", &beginner_args)
970 .unwrap();
971 if let PromptContent::Text { text } = &beginner_messages[0].content {
972 assert!(text.contains("new to this topic"));
973 }
974
975 let advanced_args = serde_json::json!({ "topic": "testing", "level": "advanced" });
977 let advanced_messages = registry
978 .get_prompt_messages("subcog_generate_tutorial", &advanced_args)
979 .unwrap();
980 if let PromptContent::Text { text } = &advanced_messages[0].content {
981 assert!(text.contains("experienced developer"));
982 }
983 }
984
985 #[test]
986 fn test_generate_tutorial_prompt_formats() {
987 let registry = PromptRegistry::new();
988
989 let outline_args = serde_json::json!({ "topic": "api design", "format": "outline" });
991 let outline_messages = registry
992 .get_prompt_messages("subcog_generate_tutorial", &outline_args)
993 .unwrap();
994 if let PromptContent::Text { text } = &outline_messages[0].content {
995 assert!(text.contains("structured outline"));
996 }
997
998 let steps_args = serde_json::json!({ "topic": "api design", "format": "steps" });
1000 let steps_messages = registry
1001 .get_prompt_messages("subcog_generate_tutorial", &steps_args)
1002 .unwrap();
1003 if let PromptContent::Text { text } = &steps_messages[0].content {
1004 assert!(text.contains("step-by-step"));
1005 }
1006 }
1007
1008 #[test]
1009 fn test_generate_tutorial_prompt_defaults() {
1010 let registry = PromptRegistry::new();
1011
1012 let args = serde_json::json!({ "topic": "rust patterns" });
1014 let messages = registry
1015 .get_prompt_messages("subcog_generate_tutorial", &args)
1016 .unwrap();
1017
1018 if let PromptContent::Text { text } = &messages[0].content {
1019 assert!(text.contains("new to this topic"));
1021 assert!(text.contains("full markdown"));
1023 }
1024 }
1025}