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