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        // TODO: Add commit_validation_retry template when implementing retry logic
122
123        // ============================================================================
124        // Developer Templates
125        // ============================================================================
126
127        m.insert(
128            "developer_iteration_xml",
129            EmbeddedTemplate {
130                name: "developer_iteration_xml",
131                content: include_str!("templates/developer_iteration_xml.txt"),
132                description: "Developer agent implementation mode prompt with XML output format and XSD validation",
133                deprecated: false,
134            },
135        );
136
137        m.insert(
138            "developer_iteration_xsd_retry",
139            EmbeddedTemplate {
140                name: "developer_iteration_xsd_retry",
141                content: include_str!("templates/developer_iteration_xsd_retry.txt"),
142                description: "XSD validation retry prompt for developer iteration",
143                deprecated: false,
144            },
145        );
146
147        m.insert(
148            "planning_xml",
149            EmbeddedTemplate {
150                name: "planning_xml",
151                content: include_str!("templates/planning_xml.txt"),
152                description: "Planning phase prompt with XML output format and XSD validation",
153                deprecated: false,
154            },
155        );
156
157        m.insert(
158            "planning_xsd_retry",
159            EmbeddedTemplate {
160                name: "planning_xsd_retry",
161                content: include_str!("templates/planning_xsd_retry.txt"),
162                description: "XSD validation retry prompt for planning phase",
163                deprecated: false,
164            },
165        );
166
167        // ============================================================================
168        // Review XML Templates
169        // ============================================================================
170
171        m.insert(
172            "review_xml",
173            EmbeddedTemplate {
174                name: "review_xml",
175                content: include_str!("templates/review_xml.txt"),
176                description: "Review mode prompt with XML output format and XSD validation",
177                deprecated: false,
178            },
179        );
180
181        m.insert(
182            "review_xsd_retry",
183            EmbeddedTemplate {
184                name: "review_xsd_retry",
185                content: include_str!("templates/review_xsd_retry.txt"),
186                description: "XSD validation retry prompt for review mode",
187                deprecated: false,
188            },
189        );
190
191        // ============================================================================
192        // Fix Mode Templates
193        // ============================================================================
194
195        m.insert(
196            "fix_mode_xml",
197            EmbeddedTemplate {
198                name: "fix_mode_xml",
199                content: include_str!("templates/fix_mode_xml.txt"),
200                description: "Fix mode prompt with XML output format and XSD validation",
201                deprecated: false,
202            },
203        );
204
205        m.insert(
206            "fix_mode_xsd_retry",
207            EmbeddedTemplate {
208                name: "fix_mode_xsd_retry",
209                content: include_str!("templates/fix_mode_xsd_retry.txt"),
210                description: "XSD validation retry prompt for fix mode",
211                deprecated: false,
212            },
213        );
214
215        // ============================================================================
216        // Rebase Templates
217        // ============================================================================
218
219        m.insert(
220            "conflict_resolution",
221            EmbeddedTemplate {
222                name: "conflict_resolution",
223                content: include_str!("templates/conflict_resolution.txt"),
224                description: "Merge conflict resolution prompt",
225                deprecated: false,
226            },
227        );
228
229        m.insert(
230            "conflict_resolution_fallback",
231            EmbeddedTemplate {
232                name: "conflict_resolution_fallback",
233                content: include_str!("templates/conflict_resolution_fallback.txt"),
234                description: "Fallback conflict resolution prompt",
235                deprecated: false,
236            },
237        );
238
239        // ============================================================================
240        // Reviewer Templates (Consolidated - 4 primary templates)
241        // ============================================================================
242        //
243        // CONSOLIDATION: Templates have been consolidated from 12 (6 types × 2 context levels)
244        // to 4 primary templates. The "minimal context" concept has been deprecated as it
245        // provided no real value - reviewers should read changed files for context.
246        //
247        // Primary templates:
248        // - standard_review: Default balanced review (most common)
249        // - comprehensive_review: Priority-ordered thorough review
250        // - security_review: OWASP Top 10 focused review
251        // - universal_review: Simplified prompt for problematic agents (GLM, ZhipuAI)
252
253        // Primary consolidated templates
254        m.insert(
255            "standard_review",
256            EmbeddedTemplate {
257                name: "standard_review",
258                content: include_str!("reviewer/templates/standard_review.txt"),
259                description: "Standard balanced review with comprehensive checklist (DEFAULT)",
260                deprecated: false,
261            },
262        );
263
264        m.insert(
265            "comprehensive_review",
266            EmbeddedTemplate {
267                name: "comprehensive_review",
268                content: include_str!("reviewer/templates/comprehensive_review.txt"),
269                description: "Comprehensive priority-ordered review (12 categories)",
270                deprecated: false,
271            },
272        );
273
274        m.insert(
275            "security_review",
276            EmbeddedTemplate {
277                name: "security_review",
278                content: include_str!("reviewer/templates/security_review.txt"),
279                description: "Security-focused review (OWASP Top 10)",
280                deprecated: false,
281            },
282        );
283
284        m.insert(
285            "universal_review",
286            EmbeddedTemplate {
287                name: "universal_review",
288                content: include_str!("reviewer/templates/universal_review.txt"),
289                description: "Simplified review for maximum agent compatibility",
290                deprecated: false,
291            },
292        );
293
294        // Legacy aliases - deprecated templates point to consolidated versions
295        // These exist for backward compatibility with user template overrides
296        m.insert(
297            "detailed_review_minimal",
298            EmbeddedTemplate {
299                name: "detailed_review_minimal",
300                content: include_str!("reviewer/templates/standard_review.txt"),
301                description: "[DEPRECATED] Use standard_review instead",
302                deprecated: true,
303            },
304        );
305
306        m.insert(
307            "detailed_review_normal",
308            EmbeddedTemplate {
309                name: "detailed_review_normal",
310                content: include_str!("reviewer/templates/standard_review.txt"),
311                description: "[DEPRECATED] Use standard_review instead",
312                deprecated: true,
313            },
314        );
315
316        m.insert(
317            "incremental_review_minimal",
318            EmbeddedTemplate {
319                name: "incremental_review_minimal",
320                content: include_str!("reviewer/templates/standard_review.txt"),
321                description: "[DEPRECATED] Use standard_review instead",
322                deprecated: true,
323            },
324        );
325
326        m.insert(
327            "incremental_review_normal",
328            EmbeddedTemplate {
329                name: "incremental_review_normal",
330                content: include_str!("reviewer/templates/standard_review.txt"),
331                description: "[DEPRECATED] Use standard_review instead",
332                deprecated: true,
333            },
334        );
335
336        m.insert(
337            "universal_review_minimal",
338            EmbeddedTemplate {
339                name: "universal_review_minimal",
340                content: include_str!("reviewer/templates/universal_review.txt"),
341                description: "[DEPRECATED] Use universal_review instead",
342                deprecated: true,
343            },
344        );
345
346        m.insert(
347            "universal_review_normal",
348            EmbeddedTemplate {
349                name: "universal_review_normal",
350                content: include_str!("reviewer/templates/universal_review.txt"),
351                description: "[DEPRECATED] Use universal_review instead",
352                deprecated: true,
353            },
354        );
355
356        m.insert(
357            "standard_review_minimal",
358            EmbeddedTemplate {
359                name: "standard_review_minimal",
360                content: include_str!("reviewer/templates/standard_review.txt"),
361                description: "[DEPRECATED] Use standard_review instead",
362                deprecated: true,
363            },
364        );
365
366        m.insert(
367            "standard_review_normal",
368            EmbeddedTemplate {
369                name: "standard_review_normal",
370                content: include_str!("reviewer/templates/standard_review.txt"),
371                description: "[DEPRECATED] Use standard_review instead",
372                deprecated: true,
373            },
374        );
375
376        m.insert(
377            "comprehensive_review_minimal",
378            EmbeddedTemplate {
379                name: "comprehensive_review_minimal",
380                content: include_str!("reviewer/templates/comprehensive_review.txt"),
381                description: "[DEPRECATED] Use comprehensive_review instead",
382                deprecated: true,
383            },
384        );
385
386        m.insert(
387            "comprehensive_review_normal",
388            EmbeddedTemplate {
389                name: "comprehensive_review_normal",
390                content: include_str!("reviewer/templates/comprehensive_review.txt"),
391                description: "[DEPRECATED] Use comprehensive_review instead",
392                deprecated: true,
393            },
394        );
395
396        m.insert(
397            "security_review_minimal",
398            EmbeddedTemplate {
399                name: "security_review_minimal",
400                content: include_str!("reviewer/templates/security_review.txt"),
401                description: "[DEPRECATED] Use security_review instead",
402                deprecated: true,
403            },
404        );
405
406        m.insert(
407            "security_review_normal",
408            EmbeddedTemplate {
409                name: "security_review_normal",
410                content: include_str!("reviewer/templates/security_review.txt"),
411                description: "[DEPRECATED] Use security_review instead",
412                deprecated: true,
413            },
414        );
415
416        m
417    });
418
419#[cfg(test)]
420mod tests {
421    use super::*;
422
423    #[test]
424    fn test_get_embedded_template_existing() {
425        let result = get_embedded_template("developer_iteration_xml");
426        assert!(result.is_some());
427        let content = result.unwrap();
428        assert!(!content.is_empty());
429        assert!(content.contains("IMPLEMENTATION MODE") || content.contains("Developer"));
430    }
431
432    #[test]
433    fn test_get_embedded_template_not_found() {
434        let result = get_embedded_template("nonexistent_template");
435        assert!(result.is_none());
436    }
437
438    #[test]
439    fn test_get_template_metadata() {
440        let metadata = get_template_metadata("commit_message_xml");
441        assert!(metadata.is_some());
442        let template = metadata.unwrap();
443        assert_eq!(template.name, "commit_message_xml");
444        assert!(!template.description.is_empty());
445    }
446
447    #[test]
448    fn test_list_all_templates() {
449        let templates = list_all_templates();
450        assert!(!templates.is_empty());
451        assert!(templates.len() >= 20); // At least 20 templates
452
453        // Verify sorted by name
454        for window in templates.windows(2) {
455            assert!(window[0].name <= window[1].name);
456        }
457    }
458
459    #[test]
460    fn test_get_templates_map() {
461        let map = get_templates_map();
462        assert!(!map.is_empty());
463        assert!(map.contains_key("developer_iteration_xml"));
464        assert!(map.contains_key("commit_message_xml"));
465
466        let (content, description) = map.get("developer_iteration_xml").unwrap();
467        assert!(!content.is_empty());
468        assert!(!description.is_empty());
469    }
470
471    #[test]
472    fn test_all_templates_have_content() {
473        let templates = list_all_templates();
474        for template in templates {
475            assert!(
476                !template.content.is_empty(),
477                "Template '{}' has empty content",
478                template.name
479            );
480        }
481    }
482
483    #[test]
484    fn test_all_templates_have_descriptions() {
485        let templates = list_all_templates();
486        for template in templates {
487            assert!(
488                !template.description.is_empty(),
489                "Template '{}' has empty description",
490                template.name
491            );
492        }
493    }
494
495    #[test]
496    fn test_fallback_templates_removed() {
497        // Verify legacy fallback templates have been removed
498        assert!(get_embedded_template("developer_iteration_fallback").is_none());
499        assert!(get_embedded_template("planning_fallback").is_none());
500        assert!(get_embedded_template("fix_mode_fallback").is_none());
501        // Note: Fallbacks are now embedded in code as inline strings, not separate .txt files
502    }
503
504    #[test]
505    fn test_legacy_non_xml_templates_removed() {
506        // Verify legacy non-XML templates have been removed
507        assert!(get_embedded_template("developer_iteration").is_none());
508        assert!(get_embedded_template("planning").is_none());
509        assert!(get_embedded_template("fix_mode").is_none());
510        // Note: Use *_xml variants instead
511    }
512
513    #[test]
514    fn test_consolidated_reviewer_templates_exist() {
515        // Verify the 4 consolidated reviewer templates exist
516        assert!(get_embedded_template("standard_review").is_some());
517        assert!(get_embedded_template("comprehensive_review").is_some());
518        assert!(get_embedded_template("security_review").is_some());
519        assert!(get_embedded_template("universal_review").is_some());
520    }
521
522    #[test]
523    fn test_consolidated_reviewer_templates_have_review_checklist() {
524        // Standard review should have the review coverage checklist
525        let standard = get_embedded_template("standard_review").unwrap();
526        assert!(
527            standard.contains("REVIEW COVERAGE CHECKLIST"),
528            "Standard review template should have review checklist"
529        );
530
531        // Comprehensive review should have review categories
532        let comprehensive = get_embedded_template("comprehensive_review").unwrap();
533        assert!(
534            comprehensive.contains("REVIEW CATEGORIES"),
535            "Comprehensive review template should have review categories"
536        );
537
538        // Security review should have OWASP categories
539        let security = get_embedded_template("security_review").unwrap();
540        assert!(
541            security.contains("OWASP TOP 10"),
542            "Security review template should have OWASP Top 10"
543        );
544    }
545
546    #[test]
547    fn test_deprecated_templates_point_to_consolidated() {
548        // Legacy templates should have same content as consolidated versions
549        let standard = get_embedded_template("standard_review").unwrap();
550        let standard_minimal = get_embedded_template("standard_review_minimal").unwrap();
551        let standard_normal = get_embedded_template("standard_review_normal").unwrap();
552
553        assert_eq!(
554            standard, standard_minimal,
555            "standard_review_minimal should point to standard_review"
556        );
557        assert_eq!(
558            standard, standard_normal,
559            "standard_review_normal should point to standard_review"
560        );
561    }
562}