Skip to main content

chant/
prompts.rs

1//! # Bundled Prompt Management
2//!
3//! This module manages the standard prompts bundled into the Chant binary.
4//! All prompts are embedded at compile time using `include_str!` and can be
5//! written to the `.chant/prompts/` directory during project initialization.
6
7/// Standard execution prompt - default prompt for spec execution
8pub const STANDARD: &str = include_str!("../prompts/standard.md");
9
10/// Split prompt - for splitting driver specs into member specs
11pub const SPLIT: &str = include_str!("../prompts/split.md");
12
13/// Verify prompt - for verifying acceptance criteria are met
14pub const VERIFY: &str = include_str!("../prompts/verify.md");
15
16/// Merge conflict prompt - for resolving git merge conflicts
17pub const MERGE_CONFLICT: &str = include_str!("../prompts/merge-conflict.md");
18
19/// Ollama prompt - optimized prompt for local LLM execution
20pub const OLLAMA: &str = include_str!("../prompts/ollama.md");
21
22// Dev-only prompts (not included in distribution)
23#[cfg(debug_assertions)]
24mod dev {
25    /// Bootstrap prompt - minimal prompt that defers to prep command
26    pub const BOOTSTRAP: &str = include_str!("../prompts-dev/bootstrap.md");
27
28    /// Documentation prompt - for generating documentation from source code
29    pub const DOCUMENTATION: &str = include_str!("../prompts-dev/documentation.md");
30
31    /// Documentation audit prompt - for auditing Rust code against mdbook documentation
32    pub const DOC_AUDIT: &str = include_str!("../prompts-dev/doc-audit.md");
33
34    /// Research analysis prompt - for chant-specific research analysis
35    pub const RESEARCH_ANALYSIS: &str = include_str!("../prompts-dev/research-analysis.md");
36
37    /// Research synthesis prompt - for chant-specific research synthesis
38    pub const RESEARCH_SYNTHESIS: &str = include_str!("../prompts-dev/research-synthesis.md");
39}
40
41/// Metadata about a bundled prompt
42#[derive(Debug, Clone)]
43pub struct PromptMetadata {
44    /// The name of the prompt (used as filename without .md extension)
45    pub name: &'static str,
46    /// The purpose/description of the prompt
47    pub purpose: &'static str,
48    /// The content of the prompt
49    pub content: &'static str,
50}
51
52/// Returns all bundled prompts with their metadata
53pub fn all_bundled_prompts() -> Vec<PromptMetadata> {
54    let prompts = vec![
55        PromptMetadata {
56            name: "standard",
57            purpose: "Default execution prompt",
58            content: STANDARD,
59        },
60        PromptMetadata {
61            name: "split",
62            purpose: "Split a driver spec into members with detailed acceptance criteria",
63            content: SPLIT,
64        },
65        PromptMetadata {
66            name: "verify",
67            purpose: "Verify that acceptance criteria are met",
68            content: VERIFY,
69        },
70        PromptMetadata {
71            name: "merge-conflict",
72            purpose: "Resolve git merge conflicts during rebase operations",
73            content: MERGE_CONFLICT,
74        },
75        PromptMetadata {
76            name: "ollama",
77            purpose: "Optimized prompt for local LLM execution",
78            content: OLLAMA,
79        },
80    ];
81
82    // Include dev-only prompts when running in debug mode
83    #[cfg(debug_assertions)]
84    let prompts = {
85        let mut prompts = prompts;
86        prompts.extend(vec![
87            PromptMetadata {
88                name: "bootstrap",
89                purpose: "Minimal bootstrap prompt that defers to prep command",
90                content: dev::BOOTSTRAP,
91            },
92            PromptMetadata {
93                name: "documentation",
94                purpose: "Generate documentation from tracked source files",
95                content: dev::DOCUMENTATION,
96            },
97            PromptMetadata {
98                name: "doc-audit",
99                purpose: "Audit Rust code against mdbook documentation",
100                content: dev::DOC_AUDIT,
101            },
102            PromptMetadata {
103                name: "research-analysis",
104                purpose: "Chant-specific research analysis",
105                content: dev::RESEARCH_ANALYSIS,
106            },
107            PromptMetadata {
108                name: "research-synthesis",
109                purpose: "Chant-specific research synthesis",
110                content: dev::RESEARCH_SYNTHESIS,
111            },
112        ]);
113        prompts
114    };
115
116    prompts
117}
118
119/// Get a prompt by name
120pub fn get_prompt(name: &str) -> Option<PromptMetadata> {
121    all_bundled_prompts().into_iter().find(|p| p.name == name)
122}
123
124#[cfg(test)]
125mod tests {
126    use super::*;
127
128    #[test]
129    fn test_all_bundled_prompts_not_empty() {
130        let prompts = all_bundled_prompts();
131        assert!(!prompts.is_empty());
132    }
133
134    #[test]
135    fn test_all_prompts_have_content() {
136        let prompts = all_bundled_prompts();
137        for prompt in prompts {
138            assert!(
139                !prompt.content.is_empty(),
140                "Prompt {} has no content",
141                prompt.name
142            );
143        }
144    }
145
146    #[test]
147    #[cfg(debug_assertions)]
148    fn test_get_prompt_bootstrap() {
149        let prompt = get_prompt("bootstrap");
150        assert!(prompt.is_some());
151        let p = prompt.unwrap();
152        assert_eq!(p.name, "bootstrap");
153        assert!(p.content.contains("chant prep"));
154    }
155
156    #[test]
157    fn test_get_prompt_nonexistent() {
158        let prompt = get_prompt("nonexistent");
159        assert!(prompt.is_none());
160    }
161}