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.
58///
59/// Returns templates in the format used by 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        // Analysis Templates
123        // ============================================================================
124
125        m.insert(
126            "analysis_system_prompt",
127            EmbeddedTemplate {
128                name: "analysis_system_prompt",
129                content: include_str!("templates/analysis_system_prompt.txt"),
130                description: "Independent analysis agent system prompt (verifies PLAN vs DIFF and writes development_result.xml)",
131                deprecated: false,
132            },
133        );
134
135        // ============================================================================
136        // Developer Templates
137        // ============================================================================
138
139        m.insert(
140            "developer_iteration_xml",
141            EmbeddedTemplate {
142                name: "developer_iteration_xml",
143                content: include_str!("templates/developer_iteration_xml.txt"),
144                description: "Developer agent implementation mode prompt (no structured output; analysis verifies progress)",
145                deprecated: false,
146            },
147        );
148
149        m.insert(
150            "developer_iteration_xsd_retry",
151            EmbeddedTemplate {
152                name: "developer_iteration_xsd_retry",
153                content: include_str!("templates/developer_iteration_xsd_retry.txt"),
154                description: "XSD validation retry prompt for developer iteration",
155                deprecated: false,
156            },
157        );
158
159        m.insert(
160            "planning_xml",
161            EmbeddedTemplate {
162                name: "planning_xml",
163                content: include_str!("templates/planning_xml.txt"),
164                description: "Planning phase prompt with XML output format and XSD validation",
165                deprecated: false,
166            },
167        );
168
169        m.insert(
170            "planning_xsd_retry",
171            EmbeddedTemplate {
172                name: "planning_xsd_retry",
173                content: include_str!("templates/planning_xsd_retry.txt"),
174                description: "XSD validation retry prompt for planning phase",
175                deprecated: false,
176            },
177        );
178
179        m.insert(
180            "developer_iteration_continuation_xml",
181            EmbeddedTemplate {
182                name: "developer_iteration_continuation_xml",
183                content: include_str!("templates/developer_iteration_continuation_xml.txt"),
184                description: "Continuation prompt when previous attempt returned partial/failed",
185                deprecated: false,
186            },
187        );
188
189        // ============================================================================
190        // Review XML Templates
191        // ============================================================================
192
193        m.insert(
194            "review_xml",
195            EmbeddedTemplate {
196                name: "review_xml",
197                content: include_str!("templates/review_xml.txt"),
198                description: "Review mode prompt with XML output format and XSD validation",
199                deprecated: false,
200            },
201        );
202
203        m.insert(
204            "review_xsd_retry",
205            EmbeddedTemplate {
206                name: "review_xsd_retry",
207                content: include_str!("templates/review_xsd_retry.txt"),
208                description: "XSD validation retry prompt for review mode",
209                deprecated: false,
210            },
211        );
212
213        // ============================================================================
214        // Fix Mode Templates
215        // ============================================================================
216
217        m.insert(
218            "fix_mode_xml",
219            EmbeddedTemplate {
220                name: "fix_mode_xml",
221                content: include_str!("templates/fix_mode_xml.txt"),
222                description: "Fix mode prompt with XML output format and XSD validation",
223                deprecated: false,
224            },
225        );
226
227        m.insert(
228            "fix_mode_xsd_retry",
229            EmbeddedTemplate {
230                name: "fix_mode_xsd_retry",
231                content: include_str!("templates/fix_mode_xsd_retry.txt"),
232                description: "XSD validation retry prompt for fix mode",
233                deprecated: false,
234            },
235        );
236
237        // ============================================================================
238        // Rebase Templates
239        // ============================================================================
240
241        m.insert(
242            "conflict_resolution",
243            EmbeddedTemplate {
244                name: "conflict_resolution",
245                content: include_str!("templates/conflict_resolution.txt"),
246                description: "Merge conflict resolution prompt",
247                deprecated: false,
248            },
249        );
250
251        m.insert(
252            "conflict_resolution_fallback",
253            EmbeddedTemplate {
254                name: "conflict_resolution_fallback",
255                content: include_str!("templates/conflict_resolution_fallback.txt"),
256                description: "Fallback conflict resolution prompt",
257                deprecated: false,
258            },
259        );
260
261        // ============================================================================
262        // NOTE: Reviewer Templates Removed
263        // ============================================================================
264        //
265        // The following templates have been REMOVED as they were never used in production:
266        // - standard_review, comprehensive_review, security_review, universal_review
267        // - All *_minimal and *_normal variants
268        //
269        // The review phase uses `review_xml.txt` template via `prompt_review_xml_with_context()`
270        // in `src/prompts/review.rs`. The removed templates were registered here but never
271        // actually requested by any production code path.
272
273        m
274    });
275
276#[cfg(test)]
277mod tests {
278    use super::*;
279
280    #[test]
281    fn test_get_embedded_template_existing() {
282        let result = get_embedded_template("developer_iteration_xml");
283        assert!(result.is_some());
284        let content = result.unwrap();
285        assert!(!content.is_empty());
286        assert!(content.contains("IMPLEMENTATION MODE") || content.contains("Developer"));
287    }
288
289    #[test]
290    fn test_get_embedded_template_not_found() {
291        let result = get_embedded_template("nonexistent_template");
292        assert!(result.is_none());
293    }
294
295    #[test]
296    fn test_get_template_metadata() {
297        let metadata = get_template_metadata("commit_message_xml");
298        assert!(metadata.is_some());
299        let template = metadata.unwrap();
300        assert_eq!(template.name, "commit_message_xml");
301        assert!(!template.description.is_empty());
302    }
303
304    #[test]
305    fn test_list_all_templates() {
306        let templates = list_all_templates();
307        assert!(!templates.is_empty());
308        assert!(templates.len() >= 10); // At least 10 templates (reduced after removing unused reviewer templates)
309
310        // Verify sorted by name
311        for window in templates.windows(2) {
312            assert!(window[0].name <= window[1].name);
313        }
314    }
315
316    #[test]
317    fn test_get_templates_map() {
318        let map = get_templates_map();
319        assert!(!map.is_empty());
320        assert!(map.contains_key("developer_iteration_xml"));
321        assert!(map.contains_key("commit_message_xml"));
322
323        let (content, description) = map.get("developer_iteration_xml").unwrap();
324        assert!(!content.is_empty());
325        assert!(!description.is_empty());
326    }
327
328    #[test]
329    fn test_all_templates_have_content() {
330        let templates = list_all_templates();
331        for template in templates {
332            assert!(
333                !template.content.is_empty(),
334                "Template '{}' has empty content",
335                template.name
336            );
337        }
338    }
339
340    #[test]
341    fn test_all_templates_have_descriptions() {
342        let templates = list_all_templates();
343        for template in templates {
344            assert!(
345                !template.description.is_empty(),
346                "Template '{}' has empty description",
347                template.name
348            );
349        }
350    }
351
352    #[test]
353    fn test_fallback_templates_removed() {
354        // Verify legacy fallback templates have been removed
355        assert!(get_embedded_template("developer_iteration_fallback").is_none());
356        assert!(get_embedded_template("planning_fallback").is_none());
357        assert!(get_embedded_template("fix_mode_fallback").is_none());
358        // Note: Fallbacks are now embedded in code as inline strings, not separate .txt files
359    }
360
361    #[test]
362    fn test_legacy_non_xml_templates_removed() {
363        // Verify legacy non-XML templates have been removed
364        assert!(get_embedded_template("developer_iteration").is_none());
365        assert!(get_embedded_template("planning").is_none());
366        assert!(get_embedded_template("fix_mode").is_none());
367        // Note: Use *_xml variants instead
368    }
369
370    #[test]
371    fn test_unused_reviewer_templates_removed() {
372        // Verify the unused reviewer templates have been removed
373        // These templates were registered but never used in production code
374        assert!(get_embedded_template("standard_review").is_none());
375        assert!(get_embedded_template("comprehensive_review").is_none());
376        assert!(get_embedded_template("security_review").is_none());
377        assert!(get_embedded_template("universal_review").is_none());
378        assert!(get_embedded_template("standard_review_minimal").is_none());
379        assert!(get_embedded_template("standard_review_normal").is_none());
380        // Note: The review phase uses review_xml template via prompt_review_xml_with_context()
381    }
382
383    #[test]
384    fn test_review_xml_template_exists() {
385        // Verify the actually-used review template exists
386        assert!(get_embedded_template("review_xml").is_some());
387        let content = get_embedded_template("review_xml").unwrap();
388        assert!(
389            content.contains("REVIEW MODE"),
390            "review_xml should contain REVIEW MODE"
391        );
392    }
393
394    #[test]
395    fn test_commit_xsd_retry_is_read_only_except_for_xml_write() {
396        let content = get_embedded_template("commit_xsd_retry").expect("commit_xsd_retry exists");
397
398        assert!(
399            content.contains("XSD") && content.contains("FIX XML"),
400            "commit_xsd_retry should clearly be an XML-only retry prompt"
401        );
402
403        assert!(
404            content.contains("READ-ONLY")
405                && (content.contains("EXCEPT FOR writing")
406                    || content.contains("except for writing")
407                    || content.contains("Except for writing"))
408                && content.contains("{{COMMIT_MESSAGE_XML_PATH}}"),
409            "commit_xsd_retry should be read-only except for writing commit_message.xml"
410        );
411
412        assert!(
413            !content.contains("DO NOT print")
414                && !content.contains("Do NOT print")
415                && !content.contains("ONLY acceptable output")
416                && !content.contains("The ONLY acceptable output"),
417            "commit_xsd_retry should not include stdout suppression wording"
418        );
419    }
420}