Skip to main content

ralph_workflow/prompts/
template_catalog.rs

1//! Embedded Template Catalog
2//!
3//! Central registry of all embedded templates with metadata.
4//!
5//! This module provides a single source of truth for all embedded templates,
6//! consolidating scattered `include_str!` calls across the codebase.
7
8use std::collections::HashMap;
9
10/// Metadata about an embedded template.
11#[derive(Debug, Clone)]
12pub struct EmbeddedTemplate {
13    /// Template name (used for lookup and user override files)
14    pub name: &'static str,
15    /// Template content
16    pub content: &'static str,
17    /// Human-readable description
18    pub description: &'static str,
19    /// Whether this template is deprecated
20    pub deprecated: bool,
21}
22
23/// Get an embedded template by name.
24///
25/// # Returns
26///
27/// * `Some(String)` - Template content if found
28/// * `None` - Template not found
29#[must_use]
30pub fn get_embedded_template(name: &str) -> Option<String> {
31    EMBEDDED_TEMPLATES.get(name).map(|t| t.content.to_string())
32}
33
34/// Get metadata about an embedded template.
35///
36/// # Returns
37///
38/// * `Some(&EmbeddedTemplate)` - Template metadata if found
39/// * `None` - Template not found
40#[must_use]
41pub fn get_template_metadata(name: &str) -> Option<&'static EmbeddedTemplate> {
42    EMBEDDED_TEMPLATES.get(name)
43}
44
45/// List all available embedded templates.
46///
47/// # Returns
48///
49/// A vector of all embedded templates with metadata, sorted by name.
50#[must_use]
51pub fn list_all_templates() -> Vec<&'static EmbeddedTemplate> {
52    let mut templates: Vec<&EmbeddedTemplate> = EMBEDDED_TEMPLATES.values().collect();
53    templates.sort_by_key(|t| t.name);
54    templates
55}
56
57/// Get all templates as a map for backwards compatibility.
58///
59/// This matches the format used by the CLI template management code.
60#[must_use]
61pub fn get_templates_map() -> HashMap<String, (String, String)> {
62    let mut map = HashMap::new();
63    for template in list_all_templates() {
64        map.insert(
65            template.name.to_string(),
66            (
67                template.content.to_string(),
68                template.description.to_string(),
69            ),
70        );
71    }
72    map
73}
74
75// ============================================================================
76// Embedded Template Definitions
77// ============================================================================
78
79/// Central registry of all embedded templates.
80///
81/// All templates are embedded at compile time using `include_str!`.
82/// User templates in `~/.config/ralph/templates/*.txt` override these.
83static EMBEDDED_TEMPLATES: std::sync::LazyLock<HashMap<&str, EmbeddedTemplate>> =
84    std::sync::LazyLock::new(|| {
85        let mut m = HashMap::new();
86
87        // ============================================================================
88        // Commit Templates
89        // ============================================================================
90
91        m.insert(
92            "commit_message_xml",
93            EmbeddedTemplate {
94                name: "commit_message_xml",
95                content: include_str!("templates/commit_message_xml.txt"),
96                description: "Generate Conventional Commits messages from git diffs (XML format)",
97                deprecated: false,
98            },
99        );
100
101        m.insert(
102            "commit_xsd_retry",
103            EmbeddedTemplate {
104                name: "commit_xsd_retry",
105                content: include_str!("templates/commit_xsd_retry.txt"),
106                description: "XSD validation retry prompt for commit messages",
107                deprecated: false,
108            },
109        );
110
111        m.insert(
112            "commit_simplified",
113            EmbeddedTemplate {
114                name: "commit_simplified",
115                content: include_str!("templates/commit_simplified.txt"),
116                description: "Simplified commit prompt with direct instructions",
117                deprecated: false,
118            },
119        );
120
121        // ============================================================================
122        // Developer Templates
123        // ============================================================================
124
125        m.insert(
126            "developer_iteration_xml",
127            EmbeddedTemplate {
128                name: "developer_iteration_xml",
129                content: include_str!("templates/developer_iteration_xml.txt"),
130                description: "Developer agent implementation mode prompt with XML output format and XSD validation",
131                deprecated: false,
132            },
133        );
134
135        m.insert(
136            "developer_iteration_xsd_retry",
137            EmbeddedTemplate {
138                name: "developer_iteration_xsd_retry",
139                content: include_str!("templates/developer_iteration_xsd_retry.txt"),
140                description: "XSD validation retry prompt for developer iteration",
141                deprecated: false,
142            },
143        );
144
145        m.insert(
146            "planning_xml",
147            EmbeddedTemplate {
148                name: "planning_xml",
149                content: include_str!("templates/planning_xml.txt"),
150                description: "Planning phase prompt with XML output format and XSD validation",
151                deprecated: false,
152            },
153        );
154
155        m.insert(
156            "planning_xsd_retry",
157            EmbeddedTemplate {
158                name: "planning_xsd_retry",
159                content: include_str!("templates/planning_xsd_retry.txt"),
160                description: "XSD validation retry prompt for planning phase",
161                deprecated: false,
162            },
163        );
164
165        m.insert(
166            "developer_iteration_continuation_xml",
167            EmbeddedTemplate {
168                name: "developer_iteration_continuation_xml",
169                content: include_str!("templates/developer_iteration_continuation_xml.txt"),
170                description: "Continuation prompt when previous attempt returned partial/failed",
171                deprecated: false,
172            },
173        );
174
175        // ============================================================================
176        // Review XML Templates
177        // ============================================================================
178
179        m.insert(
180            "review_xml",
181            EmbeddedTemplate {
182                name: "review_xml",
183                content: include_str!("templates/review_xml.txt"),
184                description: "Review mode prompt with XML output format and XSD validation",
185                deprecated: false,
186            },
187        );
188
189        m.insert(
190            "review_xsd_retry",
191            EmbeddedTemplate {
192                name: "review_xsd_retry",
193                content: include_str!("templates/review_xsd_retry.txt"),
194                description: "XSD validation retry prompt for review mode",
195                deprecated: false,
196            },
197        );
198
199        // ============================================================================
200        // Fix Mode Templates
201        // ============================================================================
202
203        m.insert(
204            "fix_mode_xml",
205            EmbeddedTemplate {
206                name: "fix_mode_xml",
207                content: include_str!("templates/fix_mode_xml.txt"),
208                description: "Fix mode prompt with XML output format and XSD validation",
209                deprecated: false,
210            },
211        );
212
213        m.insert(
214            "fix_mode_xsd_retry",
215            EmbeddedTemplate {
216                name: "fix_mode_xsd_retry",
217                content: include_str!("templates/fix_mode_xsd_retry.txt"),
218                description: "XSD validation retry prompt for fix mode",
219                deprecated: false,
220            },
221        );
222
223        // ============================================================================
224        // Rebase Templates
225        // ============================================================================
226
227        m.insert(
228            "conflict_resolution",
229            EmbeddedTemplate {
230                name: "conflict_resolution",
231                content: include_str!("templates/conflict_resolution.txt"),
232                description: "Merge conflict resolution prompt",
233                deprecated: false,
234            },
235        );
236
237        m.insert(
238            "conflict_resolution_fallback",
239            EmbeddedTemplate {
240                name: "conflict_resolution_fallback",
241                content: include_str!("templates/conflict_resolution_fallback.txt"),
242                description: "Fallback conflict resolution prompt",
243                deprecated: false,
244            },
245        );
246
247        // ============================================================================
248        // NOTE: Reviewer Templates Removed
249        // ============================================================================
250        //
251        // The following templates have been REMOVED as they were never used in production:
252        // - standard_review, comprehensive_review, security_review, universal_review
253        // - All *_minimal and *_normal variants
254        //
255        // The review phase uses `review_xml.txt` template via `prompt_review_xml_with_context()`
256        // in `src/prompts/review.rs`. The removed templates were registered here but never
257        // actually requested by any production code path.
258
259        m
260    });
261
262#[cfg(test)]
263mod tests {
264    use super::*;
265
266    #[test]
267    fn test_get_embedded_template_existing() {
268        let result = get_embedded_template("developer_iteration_xml");
269        assert!(result.is_some());
270        let content = result.unwrap();
271        assert!(!content.is_empty());
272        assert!(content.contains("IMPLEMENTATION MODE") || content.contains("Developer"));
273    }
274
275    #[test]
276    fn test_get_embedded_template_not_found() {
277        let result = get_embedded_template("nonexistent_template");
278        assert!(result.is_none());
279    }
280
281    #[test]
282    fn test_get_template_metadata() {
283        let metadata = get_template_metadata("commit_message_xml");
284        assert!(metadata.is_some());
285        let template = metadata.unwrap();
286        assert_eq!(template.name, "commit_message_xml");
287        assert!(!template.description.is_empty());
288    }
289
290    #[test]
291    fn test_list_all_templates() {
292        let templates = list_all_templates();
293        assert!(!templates.is_empty());
294        assert!(templates.len() >= 10); // At least 10 templates (reduced after removing unused reviewer templates)
295
296        // Verify sorted by name
297        for window in templates.windows(2) {
298            assert!(window[0].name <= window[1].name);
299        }
300    }
301
302    #[test]
303    fn test_get_templates_map() {
304        let map = get_templates_map();
305        assert!(!map.is_empty());
306        assert!(map.contains_key("developer_iteration_xml"));
307        assert!(map.contains_key("commit_message_xml"));
308
309        let (content, description) = map.get("developer_iteration_xml").unwrap();
310        assert!(!content.is_empty());
311        assert!(!description.is_empty());
312    }
313
314    #[test]
315    fn test_all_templates_have_content() {
316        let templates = list_all_templates();
317        for template in templates {
318            assert!(
319                !template.content.is_empty(),
320                "Template '{}' has empty content",
321                template.name
322            );
323        }
324    }
325
326    #[test]
327    fn test_all_templates_have_descriptions() {
328        let templates = list_all_templates();
329        for template in templates {
330            assert!(
331                !template.description.is_empty(),
332                "Template '{}' has empty description",
333                template.name
334            );
335        }
336    }
337
338    #[test]
339    fn test_fallback_templates_removed() {
340        // Verify legacy fallback templates have been removed
341        assert!(get_embedded_template("developer_iteration_fallback").is_none());
342        assert!(get_embedded_template("planning_fallback").is_none());
343        assert!(get_embedded_template("fix_mode_fallback").is_none());
344        // Note: Fallbacks are now embedded in code as inline strings, not separate .txt files
345    }
346
347    #[test]
348    fn test_legacy_non_xml_templates_removed() {
349        // Verify legacy non-XML templates have been removed
350        assert!(get_embedded_template("developer_iteration").is_none());
351        assert!(get_embedded_template("planning").is_none());
352        assert!(get_embedded_template("fix_mode").is_none());
353        // Note: Use *_xml variants instead
354    }
355
356    #[test]
357    fn test_unused_reviewer_templates_removed() {
358        // Verify the unused reviewer templates have been removed
359        // These templates were registered but never used in production code
360        assert!(get_embedded_template("standard_review").is_none());
361        assert!(get_embedded_template("comprehensive_review").is_none());
362        assert!(get_embedded_template("security_review").is_none());
363        assert!(get_embedded_template("universal_review").is_none());
364        assert!(get_embedded_template("standard_review_minimal").is_none());
365        assert!(get_embedded_template("standard_review_normal").is_none());
366        // Note: The review phase uses review_xml template via prompt_review_xml_with_context()
367    }
368
369    #[test]
370    fn test_review_xml_template_exists() {
371        // Verify the actually-used review template exists
372        assert!(get_embedded_template("review_xml").is_some());
373        let content = get_embedded_template("review_xml").unwrap();
374        assert!(
375            content.contains("REVIEW MODE"),
376            "review_xml should contain REVIEW MODE"
377        );
378    }
379}