ralph_workflow/templates/
mod.rs

1//! Prompt template management module.
2//!
3//! This module provides a collection of PROMPT.md templates for different
4//! task types (feature specifications, bug fixes, refactoring, etc.).
5//!
6//! Templates are embedded at compile time using `include_str!` and can be
7//! accessed via the `get_template_content()` function.
8
9use std::fmt;
10
11/// Available prompt template types.
12///
13/// Each variant represents a different template for a specific use case.
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum PromptTemplate {
16    /// Comprehensive product specification template
17    FeatureSpec,
18    /// Concise bug fix template
19    BugFix,
20    /// Code refactoring template
21    Refactor,
22    /// Test writing template
23    Test,
24    /// Documentation update template
25    Docs,
26    /// Quick/small change template
27    Quick,
28}
29
30impl PromptTemplate {
31    /// Returns the name/key for this template (used for CLI arguments).
32    pub const fn name(self) -> &'static str {
33        match self {
34            Self::FeatureSpec => "feature-spec",
35            Self::BugFix => "bug-fix",
36            Self::Refactor => "refactor",
37            Self::Test => "test",
38            Self::Docs => "docs",
39            Self::Quick => "quick",
40        }
41    }
42
43    /// Returns a short description of this template.
44    pub const fn description(self) -> &'static str {
45        match self {
46            Self::FeatureSpec => "Comprehensive product specification with questions to consider and code quality standards",
47            Self::BugFix => "Bug fix template with investigation guidance and testing requirements",
48            Self::Refactor => "Code refactoring template with behavior preservation emphasis",
49            Self::Test => "Test writing template with edge case considerations",
50            Self::Docs => "Documentation update template with completeness checklist",
51            Self::Quick => "Quick/small change template (minimal)",
52        }
53    }
54
55    /// Returns the embedded template content.
56    pub const fn content(self) -> &'static str {
57        match self {
58            Self::FeatureSpec => {
59                include_str!("../../templates/prompts/feature-spec.md")
60            }
61            Self::BugFix => {
62                include_str!("../../templates/prompts/bug-fix.md")
63            }
64            Self::Refactor => {
65                include_str!("../../templates/prompts/refactor.md")
66            }
67            Self::Test => {
68                include_str!("../../templates/prompts/test.md")
69            }
70            Self::Docs => {
71                include_str!("../../templates/prompts/docs.md")
72            }
73            Self::Quick => {
74                include_str!("../../templates/prompts/quick.md")
75            }
76        }
77    }
78}
79
80impl fmt::Display for PromptTemplate {
81    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82        write!(f, "{}", self.name())
83    }
84}
85
86/// All available prompt templates.
87pub const ALL_TEMPLATES: [PromptTemplate; 6] = [
88    PromptTemplate::FeatureSpec,
89    PromptTemplate::BugFix,
90    PromptTemplate::Refactor,
91    PromptTemplate::Test,
92    PromptTemplate::Docs,
93    PromptTemplate::Quick,
94];
95
96/// Get a template by name.
97///
98/// # Arguments
99///
100/// * `name` - The template name (e.g., "feature-spec", "bug-fix")
101///
102/// # Returns
103///
104/// * `Some(PromptTemplate)` - The template if found
105/// * `None` - If no template matches the name
106pub fn get_template(name: &str) -> Option<PromptTemplate> {
107    ALL_TEMPLATES.iter().find(|t| t.name() == name).copied()
108}
109
110/// List all available templates with their descriptions.
111///
112/// Returns a vector of (name, description) tuples.
113pub fn list_templates() -> Vec<(&'static str, &'static str)> {
114    ALL_TEMPLATES
115        .iter()
116        .map(|t| (t.name(), t.description()))
117        .collect()
118}
119
120#[cfg(test)]
121mod tests {
122    use super::*;
123
124    #[test]
125    fn test_template_names() {
126        assert_eq!(PromptTemplate::FeatureSpec.name(), "feature-spec");
127        assert_eq!(PromptTemplate::BugFix.name(), "bug-fix");
128        assert_eq!(PromptTemplate::Refactor.name(), "refactor");
129        assert_eq!(PromptTemplate::Test.name(), "test");
130        assert_eq!(PromptTemplate::Docs.name(), "docs");
131        assert_eq!(PromptTemplate::Quick.name(), "quick");
132    }
133
134    #[test]
135    fn test_template_descriptions() {
136        assert!(!PromptTemplate::FeatureSpec.description().is_empty());
137        assert!(!PromptTemplate::BugFix.description().is_empty());
138        assert!(!PromptTemplate::Refactor.description().is_empty());
139        assert!(!PromptTemplate::Test.description().is_empty());
140        assert!(!PromptTemplate::Docs.description().is_empty());
141        assert!(!PromptTemplate::Quick.description().is_empty());
142    }
143
144    #[test]
145    fn test_get_template() {
146        assert_eq!(
147            get_template("feature-spec"),
148            Some(PromptTemplate::FeatureSpec)
149        );
150        assert_eq!(get_template("bug-fix"), Some(PromptTemplate::BugFix));
151        assert_eq!(get_template("nonexistent"), None);
152    }
153
154    #[test]
155    fn test_list_templates() {
156        let templates = list_templates();
157        assert_eq!(templates.len(), 6);
158        assert!(templates.iter().any(|(name, _)| name == &"feature-spec"));
159        assert!(templates.iter().any(|(name, _)| name == &"bug-fix"));
160    }
161
162    #[test]
163    fn test_template_content_has_goal() {
164        for template in ALL_TEMPLATES {
165            let content = template.content();
166            assert!(
167                content.contains("## Goal"),
168                "Template {} missing Goal section",
169                template.name()
170            );
171        }
172    }
173
174    #[test]
175    fn test_template_content_has_acceptance() {
176        for template in ALL_TEMPLATES {
177            let content = template.content();
178            assert!(
179                content.contains("## Acceptance") || content.contains("## Acceptance Checks"),
180                "Template {} missing Acceptance section",
181                template.name()
182            );
183        }
184    }
185}