1mod commit;
18mod developer;
19pub mod partials;
20mod rebase;
21pub mod reviewer;
22pub mod template_catalog;
23pub mod template_context;
24mod template_engine;
25mod template_macros;
26pub mod template_registry;
27mod template_validator;
28mod types;
29
30pub use commit::{
32 prompt_emergency_commit_with_context, prompt_emergency_no_diff_commit_with_context,
33 prompt_file_list_only_commit_with_context, prompt_file_list_summary_only_commit_with_context,
34 prompt_fix_with_context, prompt_generate_commit_message_with_diff_with_context,
35 prompt_strict_json_commit_v2_with_context, prompt_strict_json_commit_with_context,
36 prompt_ultra_minimal_commit_v2_with_context, prompt_ultra_minimal_commit_with_context,
37};
38pub use developer::{prompt_developer_iteration_with_context, prompt_plan_with_context};
39pub use rebase::{
40 build_conflict_resolution_prompt_with_context, collect_conflict_info, FileConflict,
41};
42pub use reviewer::{
43 prompt_comprehensive_review_with_diff_with_context,
44 prompt_detailed_review_without_guidelines_with_diff_with_context,
45 prompt_incremental_review_with_diff_with_context,
46 prompt_reviewer_review_with_guidelines_and_diff_with_context,
47 prompt_security_focused_review_with_diff_with_context,
48 prompt_universal_review_with_diff_with_context,
49};
50
51#[cfg(test)]
53pub use commit::{prompt_fix, prompt_generate_commit_message_with_diff};
54#[cfg(test)]
55pub use developer::{prompt_developer_iteration, prompt_plan};
56pub use template_context::TemplateContext;
57pub use template_engine::Template;
58pub use template_validator::{
59 extract_metadata, extract_partials, extract_variables, validate_template, ValidationError,
60 ValidationWarning,
61};
62pub use types::{Action, ContextLevel, Role};
63
64#[derive(Debug, Clone, Default, PartialEq, Eq)]
68#[must_use]
69pub struct PromptConfig {
70 pub iteration: Option<u32>,
72 pub total_iterations: Option<u32>,
74 pub prompt_md_content: Option<String>,
76 pub prompt_and_plan: Option<(String, String)>,
78 pub prompt_plan_and_issues: Option<(String, String, String)>,
80}
81
82impl PromptConfig {
83 #[must_use = "configuration is required for prompt generation"]
85 pub const fn new() -> Self {
86 Self {
87 iteration: None,
88 total_iterations: None,
89 prompt_md_content: None,
90 prompt_and_plan: None,
91 prompt_plan_and_issues: None,
92 }
93 }
94
95 #[must_use = "returns the updated configuration for chaining"]
97 pub const fn with_iterations(mut self, iteration: u32, total: u32) -> Self {
98 self.iteration = Some(iteration);
99 self.total_iterations = Some(total);
100 self
101 }
102
103 #[must_use = "returns the updated configuration for chaining"]
105 pub fn with_prompt_md(mut self, content: String) -> Self {
106 self.prompt_md_content = Some(content);
107 self
108 }
109
110 #[must_use = "returns the updated configuration for chaining"]
112 pub fn with_prompt_and_plan(mut self, prompt: String, plan: String) -> Self {
113 self.prompt_and_plan = Some((prompt, plan));
114 self
115 }
116
117 pub fn with_prompt_plan_and_issues(
119 mut self,
120 prompt: String,
121 plan: String,
122 issues: String,
123 ) -> Self {
124 self.prompt_plan_and_issues = Some((prompt, plan, issues));
125 self
126 }
127}
128
129pub fn prompt_for_agent(
147 role: Role,
148 action: Action,
149 context: ContextLevel,
150 template_context: &TemplateContext,
151 config: PromptConfig,
152) -> String {
153 match (role, action) {
154 (_, Action::Plan) => {
155 prompt_plan_with_context(template_context, config.prompt_md_content.as_deref())
156 }
157 (Role::Developer | Role::Reviewer, Action::Iterate) => {
158 let (prompt_content, plan_content) = config
159 .prompt_and_plan
160 .unwrap_or((String::new(), String::new()));
161 prompt_developer_iteration_with_context(
162 template_context,
163 config.iteration.unwrap_or(1),
164 config.total_iterations.unwrap_or(1),
165 context,
166 &prompt_content,
167 &plan_content,
168 )
169 }
170 (_, Action::Fix) => {
171 let (prompt_content, plan_content, issues_content) = config
172 .prompt_plan_and_issues
173 .unwrap_or((String::new(), String::new(), String::new()));
174 prompt_fix_with_context(
175 template_context,
176 &prompt_content,
177 &plan_content,
178 &issues_content,
179 )
180 }
181 }
182}
183
184#[cfg(test)]
185mod tests {
186 use super::*;
187 use crate::prompts::template_context::TemplateContext;
188
189 use crate::prompts::reviewer::prompt_detailed_review_without_guidelines_with_diff;
191
192 #[test]
193 fn test_prompt_for_agent_developer() {
194 let template_context = TemplateContext::default();
195 let result = prompt_for_agent(
196 Role::Developer,
197 Action::Iterate,
198 ContextLevel::Normal,
199 &template_context,
200 PromptConfig::new()
201 .with_iterations(3, 10)
202 .with_prompt_and_plan("test prompt".to_string(), "test plan".to_string()),
203 );
204 assert!(!result.contains("PROMPT.md"));
206 assert!(result.contains("test prompt"));
207 assert!(result.contains("test plan"));
208 }
209
210 #[test]
211 fn test_prompt_for_agent_reviewer() {
212 let result = prompt_detailed_review_without_guidelines_with_diff(
213 ContextLevel::Minimal,
214 "sample diff",
215 "",
216 "",
217 );
218 assert!(result.contains("fresh eyes"));
219 assert!(result.contains("DETAILED REVIEW MODE"));
220 }
221
222 #[test]
223 fn test_prompt_for_agent_plan() {
224 let template_context = TemplateContext::default();
225 let result = prompt_for_agent(
226 Role::Developer,
227 Action::Plan,
228 ContextLevel::Normal,
229 &template_context,
230 PromptConfig::new().with_prompt_md("test requirements".to_string()),
231 );
232 assert!(result.contains("PLANNING MODE"));
234 assert!(result.contains("Implementation Steps"));
235 }
236
237 #[test]
238 fn test_prompts_are_agent_agnostic() {
239 let agent_specific_terms = [
242 "claude", "codex", "opencode", "gemini", "aider", "goose", "cline", "continue",
243 "amazon-q", "gpt", "copilot",
244 ];
245
246 let prompts_to_check: Vec<String> = vec![
247 prompt_developer_iteration(1, 5, ContextLevel::Normal, "", ""),
248 prompt_developer_iteration(1, 5, ContextLevel::Minimal, "", ""),
249 prompt_detailed_review_without_guidelines_with_diff(
250 ContextLevel::Normal,
251 "sample diff",
252 "",
253 "",
254 ),
255 prompt_detailed_review_without_guidelines_with_diff(
256 ContextLevel::Minimal,
257 "sample diff",
258 "",
259 "",
260 ),
261 prompt_fix("", "", ""),
262 prompt_plan(None),
263 prompt_generate_commit_message_with_diff("diff --git a/a b/b"),
264 ];
265
266 for prompt in prompts_to_check {
267 let prompt_lower = prompt.to_lowercase();
268 for term in agent_specific_terms {
269 assert!(
270 !prompt_lower.contains(term),
271 "Prompt contains agent-specific term '{}': {}",
272 term,
273 &prompt[..prompt.len().min(100)]
274 );
275 }
276 }
277 }
278
279 #[test]
280 fn test_prompt_for_agent_fix() {
281 let template_context = TemplateContext::default();
282 let result = prompt_for_agent(
283 Role::Developer,
284 Action::Fix,
285 ContextLevel::Normal,
286 &template_context,
287 PromptConfig::new().with_prompt_plan_and_issues(
288 "test prompt".to_string(),
289 "test plan".to_string(),
290 "test issues".to_string(),
291 ),
292 );
293 assert!(result.contains("FIX MODE"));
294 assert!(result.contains("test issues"));
295 assert!(result.contains("test prompt"));
297 assert!(result.contains("test plan"));
298 }
299
300 #[test]
301 fn test_prompt_for_agent_fix_with_empty_context() {
302 let template_context = TemplateContext::default();
303 let result = prompt_for_agent(
304 Role::Developer,
305 Action::Fix,
306 ContextLevel::Normal,
307 &template_context,
308 PromptConfig::new(),
309 );
310 assert!(result.contains("FIX MODE"));
311 assert!(!result.is_empty());
313 }
314
315 #[test]
316 fn test_reviewer_can_use_iterate_action() {
317 let template_context = TemplateContext::default();
319 let result = prompt_for_agent(
320 Role::Reviewer,
321 Action::Iterate,
322 ContextLevel::Normal,
323 &template_context,
324 PromptConfig::new()
325 .with_iterations(1, 3)
326 .with_prompt_and_plan(String::new(), String::new()),
327 );
328 assert!(result.contains("IMPLEMENTATION MODE"));
330 }
331
332 #[test]
333 fn test_prompts_do_not_have_detailed_tracking_language() {
334 let detailed_tracking_terms = [
337 "iteration number",
338 "phase completed",
339 "previous iteration",
340 "history of",
341 "detailed log",
342 ];
343
344 let prompts_to_check = vec![
345 prompt_developer_iteration(1, 5, ContextLevel::Normal, "", ""),
346 prompt_fix("", "", ""),
347 ];
348
349 for prompt in prompts_to_check {
350 let prompt_lower = prompt.to_lowercase();
351 for term in detailed_tracking_terms {
352 assert!(
353 !prompt_lower.contains(term),
354 "Prompt contains detailed tracking language '{}': {}",
355 term,
356 &prompt[..prompt.len().min(100)]
357 );
358 }
359 }
360 }
361
362 #[test]
363 fn test_developer_notes_md_not_referenced() {
364 let developer_prompt = prompt_developer_iteration(1, 5, ContextLevel::Normal, "", "");
366 assert!(
367 !developer_prompt.contains("NOTES.md"),
368 "Developer prompt should not reference NOTES.md in isolation mode"
369 );
370 }
371
372 #[test]
373 fn test_all_prompts_isolate_agents_from_git() {
374 let git_command_patterns = [
378 "git diff HEAD",
379 "git status",
380 "git commit",
381 "git add",
382 "git log",
383 "git show",
384 "git reset",
385 "git checkout",
386 "git branch",
387 "Run `git",
388 "execute git",
389 ];
390
391 let prompts_to_check: Vec<String> = vec![
392 prompt_developer_iteration(1, 5, ContextLevel::Normal, "", ""),
393 prompt_developer_iteration(1, 5, ContextLevel::Minimal, "", ""),
394 prompt_detailed_review_without_guidelines_with_diff(
395 ContextLevel::Normal,
396 "sample diff",
397 "",
398 "",
399 ),
400 prompt_detailed_review_without_guidelines_with_diff(
401 ContextLevel::Minimal,
402 "sample diff",
403 "",
404 "",
405 ),
406 prompt_fix("", "", ""),
407 prompt_plan(None),
408 prompt_generate_commit_message_with_diff("diff --git a/a b/b\n"),
409 ];
410
411 for prompt in prompts_to_check {
412 for pattern in git_command_patterns {
413 assert!(
414 !prompt.contains(pattern),
415 "Prompt contains git command pattern '{}': {}",
416 pattern,
417 &prompt[..prompt.len().min(100)]
418 );
419 }
420 }
421
422 let orchestrator_prompt = prompt_generate_commit_message_with_diff("some diff");
426 assert!(
427 orchestrator_prompt.contains("DIFF:") || orchestrator_prompt.contains("diff"),
428 "Orchestrator prompt should contain the diff content for commit message generation"
429 );
430 for pattern in git_command_patterns {
432 assert!(
433 !orchestrator_prompt.contains(pattern),
434 "Orchestrator prompt contains git command pattern '{pattern}'"
435 );
436 }
437 }
438}