ralph_workflow/prompts/template_macros.rs
1//! Template enforcement macros for ensuring template usage conventions.
2//!
3//! This module provides compile-time and runtime tools to enforce that all
4//! AI communication prompts come from template files, not inline strings.
5
6#![deny(unsafe_code)]
7
8/// Macro to verify that a string comes from a template file.
9///
10/// This macro provides compile-time assurance by using `include_str!` which
11/// only works with files at compile time. This prevents inline prompt strings
12/// from being accidentally used.
13///
14/// # Example
15///
16/// ```ignore
17/// use crate::prompts::template_macros::include_template;
18///
19/// // This works - loads from template file
20/// let template = include_template!("templates/my_prompt.txt");
21///
22/// // This would NOT work with include_template! macro - prevents inline templates
23/// // let inline = "Hello {{NAME}}"; // Cannot be passed to include_template!
24/// ```
25///
26/// # Enforcement
27///
28/// - The macro uses `concat!` with `include_str!` to ensure the template
29/// path is known at compile time
30/// - Returns a `&'static str` which makes it clear this is compiled content
31#[macro_export]
32macro_rules! include_template {
33 ($path:expr) => {
34 include_str!(concat!("../prompts/templates/", $path))
35 };
36}
37
38/// Macro to verify a template file exists and contains expected content.
39///
40/// This is primarily used in tests to verify template structure.
41///
42/// # Example
43///
44/// ```ignore
45/// assert_template_exists!("templates/my_prompt.txt");
46/// assert_template_has_variable!("templates/my_prompt.txt", "CONTEXT");
47/// ```
48#[macro_export]
49macro_rules! assert_template_exists {
50 ($path:expr) => {
51 let content = include_str!(concat!("../prompts/templates/", $path));
52 assert!(!content.is_empty(), "Template file {} is empty", $path);
53 };
54}
55
56#[macro_export]
57macro_rules! assert_template_has_variable {
58 ($path:expr, $var:expr) => {
59 let content = include_str!(concat!("../prompts/templates/", $path));
60 let var_pattern = concat!("{{", $var, "}}");
61 assert!(
62 content.contains(var_pattern) || content.contains(concat!("{{ ", $var, " }}")),
63 "Template {} does not contain variable {{{}}}",
64 $path,
65 $var
66 );
67 };
68}
69
70#[cfg(test)]
71mod tests {
72 #[test]
73 fn test_include_template_macro() {
74 // Test that we can include a template using the macro
75 let _ = include_template!("conflict_resolution.txt");
76 }
77
78 #[test]
79 fn test_assert_template_exists() {
80 assert_template_exists!("conflict_resolution.txt");
81 }
82
83 #[test]
84 fn test_assert_template_has_variable() {
85 assert_template_has_variable!("conflict_resolution.txt", "CONTEXT");
86 assert_template_has_variable!("conflict_resolution.txt", "CONFLICTS");
87 }
88
89 #[test]
90 fn test_inline_template_detection() {
91 // Test that we can detect potential inline templates in strings
92 // These patterns suggest inline prompt content that should be in templates
93
94 let suspicious_patterns = [
95 // Multi-line raw string literals with prompt-like content
96 r"You are a",
97 r"Please review",
98 r"Generate a",
99 // Long format strings that look like prompts
100 "## Instructions",
101 "### Task",
102 "# PROMPT",
103 // JSON/structured prompt patterns
104 r#"{"role": "developer""#,
105 ];
106
107 // This test documents what patterns to look for
108 // In a real scenario, you'd use a build script or clippy lint to detect these
109 for pattern in suspicious_patterns {
110 assert!(!pattern.is_empty(), "Pattern should not be empty");
111 }
112 }
113}