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}
20
21/// Get an embedded template by name.
22///
23/// # Returns
24///
25/// * `Some(String)` - Template content if found
26/// * `None` - Template not found
27#[must_use]
28pub fn get_embedded_template(name: &str) -> Option<String> {
29    EMBEDDED_TEMPLATES.get(name).map(|t| t.content.to_string())
30}
31
32/// Get metadata about an embedded template.
33///
34/// # Returns
35///
36/// * `Some(&EmbeddedTemplate)` - Template metadata if found
37/// * `None` - Template not found
38#[must_use]
39#[cfg(test)]
40pub fn get_template_metadata(name: &str) -> Option<&'static EmbeddedTemplate> {
41    EMBEDDED_TEMPLATES.get(name)
42}
43
44/// List all available embedded templates.
45///
46/// # Returns
47///
48/// A vector of all embedded templates with metadata, sorted by name.
49#[must_use]
50pub fn list_all_templates() -> Vec<&'static EmbeddedTemplate> {
51    let mut templates: Vec<&EmbeddedTemplate> = EMBEDDED_TEMPLATES.values().collect();
52    templates.sort_by_key(|t| t.name);
53    templates
54}
55
56/// Get all templates as a map for backwards compatibility.
57///
58/// This matches the format used by the CLI template management code.
59#[must_use]
60pub fn get_templates_map() -> HashMap<String, (String, String)> {
61    let mut map = HashMap::new();
62    for template in list_all_templates() {
63        map.insert(
64            template.name.to_string(),
65            (
66                template.content.to_string(),
67                template.description.to_string(),
68            ),
69        );
70    }
71    map
72}
73
74// ============================================================================
75// Embedded Template Definitions
76// ============================================================================
77
78/// Central registry of all embedded templates.
79///
80/// All templates are embedded at compile time using `include_str!`.
81/// User templates in `~/.config/ralph/templates/*.txt` override these.
82static EMBEDDED_TEMPLATES: std::sync::LazyLock<HashMap<&str, EmbeddedTemplate>> =
83    std::sync::LazyLock::new(|| {
84        let mut m = HashMap::new();
85
86        // ============================================================================
87        // Commit Templates
88        // ============================================================================
89
90        m.insert(
91            "commit_message_xml",
92            EmbeddedTemplate {
93                name: "commit_message_xml",
94                content: include_str!("templates/commit_message_xml.txt"),
95                description: "Generate Conventional Commits messages from git diffs (XML format)",
96            },
97        );
98
99        m.insert(
100            "commit_strict_json",
101            EmbeddedTemplate {
102                name: "commit_strict_json",
103                content: include_str!("templates/commit_strict_json.txt"),
104                description: "Strict JSON commit message format (retry attempt 1)",
105            },
106        );
107
108        m.insert(
109            "commit_strict_json_v2",
110            EmbeddedTemplate {
111                name: "commit_strict_json_v2",
112                content: include_str!("templates/commit_strict_json_v2.txt"),
113                description: "Strict JSON commit message format with examples (retry attempt 2)",
114            },
115        );
116
117        m.insert(
118            "commit_ultra_minimal",
119            EmbeddedTemplate {
120                name: "commit_ultra_minimal",
121                content: include_str!("templates/commit_ultra_minimal.txt"),
122                description: "Ultra-minimal commit message prompt (retry attempt 3)",
123            },
124        );
125
126        m.insert(
127            "commit_ultra_minimal_v2",
128            EmbeddedTemplate {
129                name: "commit_ultra_minimal_v2",
130                content: include_str!("templates/commit_ultra_minimal_v2.txt"),
131                description: "Ultra-minimal commit message prompt v2 (retry attempt 4)",
132            },
133        );
134
135        m.insert(
136            "commit_file_list_only",
137            EmbeddedTemplate {
138                name: "commit_file_list_only",
139                content: include_str!("templates/commit_file_list_only.txt"),
140                description: "Commit message from file list only (fallback 1)",
141            },
142        );
143
144        m.insert(
145            "commit_file_list_summary",
146            EmbeddedTemplate {
147                name: "commit_file_list_summary",
148                content: include_str!("templates/commit_file_list_summary.txt"),
149                description: "Commit message from file summary (fallback 2)",
150            },
151        );
152
153        m.insert(
154            "commit_emergency",
155            EmbeddedTemplate {
156                name: "commit_emergency",
157                content: include_str!("templates/commit_emergency.txt"),
158                description: "Emergency commit message with diff (fallback 3)",
159            },
160        );
161
162        m.insert(
163            "commit_emergency_no_diff",
164            EmbeddedTemplate {
165                name: "commit_emergency_no_diff",
166                content: include_str!("templates/commit_emergency_no_diff.txt"),
167                description: "Emergency commit message without diff (last resort)",
168            },
169        );
170
171        m.insert(
172            "commit_message_fallback",
173            EmbeddedTemplate {
174                name: "commit_message_fallback",
175                content: include_str!("templates/commit_message_fallback.txt"),
176                description: "Fallback commit message template",
177            },
178        );
179
180        // ============================================================================
181        // Developer Templates
182        // ============================================================================
183
184        m.insert(
185            "developer_iteration",
186            EmbeddedTemplate {
187                name: "developer_iteration",
188                content: include_str!("templates/developer_iteration.txt"),
189                description: "Developer agent implementation mode prompt",
190            },
191        );
192
193        m.insert(
194            "planning",
195            EmbeddedTemplate {
196                name: "planning",
197                content: include_str!("templates/planning.txt"),
198                description: "Planning phase prompt for implementation plans",
199            },
200        );
201
202        m.insert(
203            "developer_iteration_fallback",
204            EmbeddedTemplate {
205                name: "developer_iteration_fallback",
206                content: include_str!("templates/developer_iteration_fallback.txt"),
207                description: "Fallback developer iteration prompt",
208            },
209        );
210
211        m.insert(
212            "planning_fallback",
213            EmbeddedTemplate {
214                name: "planning_fallback",
215                content: include_str!("templates/planning_fallback.txt"),
216                description: "Fallback planning prompt",
217            },
218        );
219
220        // ============================================================================
221        // Fix Mode Templates
222        // ============================================================================
223
224        m.insert(
225            "fix_mode",
226            EmbeddedTemplate {
227                name: "fix_mode",
228                content: include_str!("templates/fix_mode.txt"),
229                description: "Fix mode prompt for addressing review issues",
230            },
231        );
232
233        m.insert(
234            "fix_mode_fallback",
235            EmbeddedTemplate {
236                name: "fix_mode_fallback",
237                content: include_str!("templates/fix_mode_fallback.txt"),
238                description: "Fallback fix mode prompt",
239            },
240        );
241
242        // ============================================================================
243        // Rebase Templates
244        // ============================================================================
245
246        m.insert(
247            "conflict_resolution",
248            EmbeddedTemplate {
249                name: "conflict_resolution",
250                content: include_str!("templates/conflict_resolution.txt"),
251                description: "Merge conflict resolution prompt",
252            },
253        );
254
255        m.insert(
256            "conflict_resolution_fallback",
257            EmbeddedTemplate {
258                name: "conflict_resolution_fallback",
259                content: include_str!("templates/conflict_resolution_fallback.txt"),
260                description: "Fallback conflict resolution prompt",
261            },
262        );
263
264        // ============================================================================
265        // Reviewer Templates
266        // ============================================================================
267
268        m.insert(
269            "detailed_review_minimal",
270            EmbeddedTemplate {
271                name: "detailed_review_minimal",
272                content: include_str!("reviewer/templates/detailed_review_minimal.txt"),
273                description: "Detailed review mode (minimal context)",
274            },
275        );
276
277        m.insert(
278            "detailed_review_normal",
279            EmbeddedTemplate {
280                name: "detailed_review_normal",
281                content: include_str!("reviewer/templates/detailed_review_normal.txt"),
282                description: "Detailed review mode (normal context)",
283            },
284        );
285
286        m.insert(
287            "incremental_review_minimal",
288            EmbeddedTemplate {
289                name: "incremental_review_minimal",
290                content: include_str!("reviewer/templates/incremental_review_minimal.txt"),
291                description: "Incremental review (changed files only, minimal context)",
292            },
293        );
294
295        m.insert(
296            "incremental_review_normal",
297            EmbeddedTemplate {
298                name: "incremental_review_normal",
299                content: include_str!("reviewer/templates/incremental_review_normal.txt"),
300                description: "Incremental review (changed files only, normal context)",
301            },
302        );
303
304        m.insert(
305            "universal_review_minimal",
306            EmbeddedTemplate {
307                name: "universal_review_minimal",
308                content: include_str!("reviewer/templates/universal_review_minimal.txt"),
309                description: "Universal review (all file types, minimal context)",
310            },
311        );
312
313        m.insert(
314            "universal_review_normal",
315            EmbeddedTemplate {
316                name: "universal_review_normal",
317                content: include_str!("reviewer/templates/universal_review_normal.txt"),
318                description: "Universal review (all file types, normal context)",
319            },
320        );
321
322        m.insert(
323            "standard_review_minimal",
324            EmbeddedTemplate {
325                name: "standard_review_minimal",
326                content: include_str!("reviewer/templates/standard_review_minimal.txt"),
327                description: "Standard review (balanced, minimal context)",
328            },
329        );
330
331        m.insert(
332            "standard_review_normal",
333            EmbeddedTemplate {
334                name: "standard_review_normal",
335                content: include_str!("reviewer/templates/standard_review_normal.txt"),
336                description: "Standard review (balanced, normal context)",
337            },
338        );
339
340        m.insert(
341            "comprehensive_review_minimal",
342            EmbeddedTemplate {
343                name: "comprehensive_review_minimal",
344                content: include_str!("reviewer/templates/comprehensive_review_minimal.txt"),
345                description: "Comprehensive review (thorough, minimal context)",
346            },
347        );
348
349        m.insert(
350            "comprehensive_review_normal",
351            EmbeddedTemplate {
352                name: "comprehensive_review_normal",
353                content: include_str!("reviewer/templates/comprehensive_review_normal.txt"),
354                description: "Comprehensive review (thorough, normal context)",
355            },
356        );
357
358        m.insert(
359            "security_review_minimal",
360            EmbeddedTemplate {
361                name: "security_review_minimal",
362                content: include_str!("reviewer/templates/security_review_minimal.txt"),
363                description: "Security-focused review (OWASP, minimal context)",
364            },
365        );
366
367        m.insert(
368            "security_review_normal",
369            EmbeddedTemplate {
370                name: "security_review_normal",
371                content: include_str!("reviewer/templates/security_review_normal.txt"),
372                description: "Security-focused review (OWASP, normal context)",
373            },
374        );
375
376        m
377    });
378
379#[cfg(test)]
380mod tests {
381    use super::*;
382
383    #[test]
384    fn test_get_embedded_template_existing() {
385        let result = get_embedded_template("developer_iteration");
386        assert!(result.is_some());
387        let content = result.unwrap();
388        assert!(!content.is_empty());
389        assert!(content.contains("IMPLEMENTATION MODE") || content.contains("Developer"));
390    }
391
392    #[test]
393    fn test_get_embedded_template_not_found() {
394        let result = get_embedded_template("nonexistent_template");
395        assert!(result.is_none());
396    }
397
398    #[test]
399    fn test_get_template_metadata() {
400        let metadata = get_template_metadata("commit_message_xml");
401        assert!(metadata.is_some());
402        let template = metadata.unwrap();
403        assert_eq!(template.name, "commit_message_xml");
404        assert!(!template.description.is_empty());
405    }
406
407    #[test]
408    fn test_list_all_templates() {
409        let templates = list_all_templates();
410        assert!(!templates.is_empty());
411        assert!(templates.len() >= 20); // At least 20 templates
412
413        // Verify sorted by name
414        for window in templates.windows(2) {
415            assert!(window[0].name <= window[1].name);
416        }
417    }
418
419    #[test]
420    fn test_get_templates_map() {
421        let map = get_templates_map();
422        assert!(!map.is_empty());
423        assert!(map.contains_key("developer_iteration"));
424        assert!(map.contains_key("commit_message_xml"));
425
426        let (content, description) = map.get("developer_iteration").unwrap();
427        assert!(!content.is_empty());
428        assert!(!description.is_empty());
429    }
430
431    #[test]
432    fn test_all_templates_have_content() {
433        let templates = list_all_templates();
434        for template in templates {
435            assert!(
436                !template.content.is_empty(),
437                "Template '{}' has empty content",
438                template.name
439            );
440        }
441    }
442
443    #[test]
444    fn test_all_templates_have_descriptions() {
445        let templates = list_all_templates();
446        for template in templates {
447            assert!(
448                !template.description.is_empty(),
449                "Template '{}' has empty description",
450                template.name
451            );
452        }
453    }
454
455    #[test]
456    fn test_fallback_templates_exist() {
457        // Verify all fallback templates exist
458        assert!(get_embedded_template("developer_iteration_fallback").is_some());
459        assert!(get_embedded_template("planning_fallback").is_some());
460        assert!(get_embedded_template("fix_mode_fallback").is_some());
461        assert!(get_embedded_template("conflict_resolution_fallback").is_some());
462        assert!(get_embedded_template("commit_message_fallback").is_some());
463    }
464}