Skip to main content

subcog/mcp/prompts/
mod.rs

1//! MCP pre-defined prompts.
2//!
3//! Provides prompt templates for the Model Context Protocol.
4//!
5//! # Module Structure
6//!
7//! - [`types`]: Core data structures (`PromptDefinition`, `PromptMessage`, etc.)
8//! - [`templates`]: Static content strings for prompts
9//! - [`generators`]: Prompt message generation logic
10
11mod 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
21/// Registry of pre-defined prompts.
22pub struct PromptRegistry {
23    /// Available prompts.
24    prompts: HashMap<String, PromptDefinition>,
25}
26
27impl PromptRegistry {
28    /// Creates a new prompt registry.
29    #[must_use]
30    pub fn new() -> Self {
31        let mut prompts = HashMap::new();
32
33        // Register all prompts
34        for prompt in Self::all_prompts() {
35            prompts.insert(prompt.name.clone(), prompt);
36        }
37
38        Self { prompts }
39    }
40
41    /// Returns all prompt definitions.
42    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            // Phase 4: Intent-aware prompts
58            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            // Session initialization prompts
67            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    // Phase 4: Intent-aware prompts
387
388    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    /// Returns all prompt definitions (built-in only).
568    #[must_use]
569    pub fn list_prompts(&self) -> Vec<&PromptDefinition> {
570        self.prompts.values().collect()
571    }
572
573    /// Returns all prompt definitions including user-defined prompts.
574    ///
575    /// User prompts are fetched from the `PromptService` and combined with built-in prompts.
576    /// Built-in prompts take precedence if there are name conflicts.
577    #[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        // Add user prompts from all domains
587        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            // Skip if we already have a built-in prompt with this name
593            if !self.prompts.contains_key(&definition.name) {
594                result.push(definition);
595            }
596        }
597
598        result
599    }
600
601    /// Gets a prompt definition by name, including user prompts.
602    ///
603    /// User prompts are prefixed with "user/" (e.g., "user/code-review").
604    #[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        // Check built-in prompts first
611        if let Some(builtin) = self.prompts.get(name) {
612            return Some(builtin.clone());
613        }
614
615        // Check user prompts (with or without "user/" prefix)
616        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    /// Gets a prompt definition by name.
625    #[must_use]
626    pub fn get_prompt(&self, name: &str) -> Option<&PromptDefinition> {
627        self.prompts.get(name)
628    }
629
630    /// Generates prompt messages for a given prompt and arguments.
631    #[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            // Phase 4: Intent-aware prompts
651            "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            // Session initialization
662            "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                // Each level should have different intro text
779                assert!(!text.is_empty());
780            }
781        }
782    }
783
784    // Phase 4: Intent-aware prompt tests
785
786    #[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        // Verify all Phase 4 prompts are registered
899        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        // Verify arguments
921        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        // Test beginner level
967        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        // Test advanced level
976        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        // Test outline format
990        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        // Test steps format
999        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        // Only required topic, others use defaults
1013        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            // Default level is beginner
1020            assert!(text.contains("new to this topic"));
1021            // Default format is markdown
1022            assert!(text.contains("full markdown"));
1023        }
1024    }
1025}