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_fix_with_context, prompt_generate_commit_message_with_diff_with_context,
33 prompt_simplified_commit_with_context, prompt_xsd_retry_with_context,
34};
35pub use developer::{prompt_developer_iteration_with_context, prompt_plan_with_context};
36pub use rebase::{
37 build_conflict_resolution_prompt_with_context, build_enhanced_conflict_resolution_prompt,
38 collect_branch_info, collect_conflict_info, BranchInfo, FileConflict,
39};
40pub use reviewer::{
41 prompt_comprehensive_review_with_diff_with_context,
42 prompt_detailed_review_without_guidelines_with_diff_with_context,
43 prompt_incremental_review_with_diff_with_context,
44 prompt_reviewer_review_with_guidelines_and_diff_with_context,
45 prompt_security_focused_review_with_diff_with_context,
46 prompt_universal_review_with_diff_with_context,
47};
48
49#[cfg(test)]
51pub use commit::{prompt_fix, prompt_generate_commit_message_with_diff};
52#[cfg(test)]
53pub use developer::{prompt_developer_iteration, prompt_plan};
54pub use template_context::TemplateContext;
55pub use template_engine::Template;
56pub use template_validator::{
57 extract_metadata, extract_partials, extract_variables, validate_template, ValidationError,
58 ValidationWarning,
59};
60pub use types::{Action, ContextLevel, Role};
61
62#[derive(Debug, Clone, Default, PartialEq, Eq)]
66#[must_use]
67pub struct PromptConfig {
68 pub iteration: Option<u32>,
70 pub total_iterations: Option<u32>,
72 pub prompt_md_content: Option<String>,
74 pub prompt_and_plan: Option<(String, String)>,
76 pub prompt_plan_and_issues: Option<(String, String, String)>,
78}
79
80impl PromptConfig {
81 #[must_use = "configuration is required for prompt generation"]
83 pub const fn new() -> Self {
84 Self {
85 iteration: None,
86 total_iterations: None,
87 prompt_md_content: None,
88 prompt_and_plan: None,
89 prompt_plan_and_issues: None,
90 }
91 }
92
93 #[must_use = "returns the updated configuration for chaining"]
95 pub const fn with_iterations(mut self, iteration: u32, total: u32) -> Self {
96 self.iteration = Some(iteration);
97 self.total_iterations = Some(total);
98 self
99 }
100
101 #[must_use = "returns the updated configuration for chaining"]
103 pub fn with_prompt_md(mut self, content: String) -> Self {
104 self.prompt_md_content = Some(content);
105 self
106 }
107
108 #[must_use = "returns the updated configuration for chaining"]
110 pub fn with_prompt_and_plan(mut self, prompt: String, plan: String) -> Self {
111 self.prompt_and_plan = Some((prompt, plan));
112 self
113 }
114
115 pub fn with_prompt_plan_and_issues(
117 mut self,
118 prompt: String,
119 plan: String,
120 issues: String,
121 ) -> Self {
122 self.prompt_plan_and_issues = Some((prompt, plan, issues));
123 self
124 }
125}
126
127pub fn prompt_for_agent(
145 role: Role,
146 action: Action,
147 context: ContextLevel,
148 template_context: &TemplateContext,
149 config: PromptConfig,
150) -> String {
151 match (role, action) {
152 (_, Action::Plan) => {
153 prompt_plan_with_context(template_context, config.prompt_md_content.as_deref())
154 }
155 (Role::Developer | Role::Reviewer, Action::Iterate) => {
156 let (prompt_content, plan_content) = config
157 .prompt_and_plan
158 .unwrap_or((String::new(), String::new()));
159 prompt_developer_iteration_with_context(
160 template_context,
161 config.iteration.unwrap_or(1),
162 config.total_iterations.unwrap_or(1),
163 context,
164 &prompt_content,
165 &plan_content,
166 )
167 }
168 (_, Action::Fix) => {
169 let (prompt_content, plan_content, issues_content) = config
170 .prompt_plan_and_issues
171 .unwrap_or((String::new(), String::new(), String::new()));
172 prompt_fix_with_context(
173 template_context,
174 &prompt_content,
175 &plan_content,
176 &issues_content,
177 )
178 }
179 }
180}
181
182#[cfg(test)]
183mod tests {
184 use super::*;
185 use crate::prompts::template_context::TemplateContext;
186
187 use crate::prompts::reviewer::prompt_detailed_review_without_guidelines_with_diff;
189
190 #[test]
191 fn test_prompt_for_agent_developer() {
192 let template_context = TemplateContext::default();
193 let result = prompt_for_agent(
194 Role::Developer,
195 Action::Iterate,
196 ContextLevel::Normal,
197 &template_context,
198 PromptConfig::new()
199 .with_iterations(3, 10)
200 .with_prompt_and_plan("test prompt".to_string(), "test plan".to_string()),
201 );
202 assert!(!result.contains("PROMPT.md"));
204 assert!(result.contains("test prompt"));
205 assert!(result.contains("test plan"));
206 }
207
208 #[test]
209 fn test_prompt_for_agent_reviewer() {
210 let result = prompt_detailed_review_without_guidelines_with_diff(
211 ContextLevel::Minimal,
212 "sample diff",
213 "",
214 "",
215 );
216 assert!(result.contains("REVIEW MODE"));
219 assert!(result.contains("CRITICAL CONSTRAINTS"));
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 instructive_git_patterns = [
381 "Run `git",
382 "run git",
383 "execute git",
384 "Try: git",
385 "you can git",
386 "should run git",
387 "please run git",
388 "\ngit ", ];
390
391 let forbid_contexts = [
394 "MUST NOT run",
395 "DO NOT run",
396 "must not run",
397 "do not run",
398 "NOT run commands",
399 "commands (",
400 "commands:",
401 "including:",
402 "such as",
403 ];
404
405 let prompts_to_check: Vec<String> = vec![
410 prompt_developer_iteration(1, 5, ContextLevel::Normal, "", ""),
411 prompt_developer_iteration(1, 5, ContextLevel::Minimal, "", ""),
412 prompt_detailed_review_without_guidelines_with_diff(
413 ContextLevel::Normal,
414 "sample diff",
415 "",
416 "",
417 ),
418 prompt_detailed_review_without_guidelines_with_diff(
419 ContextLevel::Minimal,
420 "sample diff",
421 "",
422 "",
423 ),
424 prompt_fix("", "", ""),
428 prompt_plan(None),
429 prompt_generate_commit_message_with_diff("diff --git a/a b/b\n"),
430 ];
431
432 for prompt in prompts_to_check {
433 for pattern in instructive_git_patterns {
434 if prompt.contains(pattern) {
435 let is_forbidden = forbid_contexts.iter().any(|ctx| {
437 if let Some(pos) = prompt.find(ctx) {
438 if let Some(pattern_pos) = prompt[pos..].find(pattern) {
440 pattern_pos < 200
442 } else {
443 false
444 }
445 } else {
446 false
447 }
448 });
449
450 if !is_forbidden {
451 panic!(
452 "Prompt contains instructive git command pattern '{}': {}",
453 pattern,
454 &prompt[..prompt.len().min(150)]
455 );
456 }
457 }
458 }
459 }
460
461 let orchestrator_prompt = prompt_generate_commit_message_with_diff("some diff");
465 assert!(
466 orchestrator_prompt.contains("DIFF:") || orchestrator_prompt.contains("diff"),
467 "Orchestrator prompt should contain the diff content for commit message generation"
468 );
469 for pattern in instructive_git_patterns {
471 if orchestrator_prompt.contains(pattern) {
472 let is_forbidden = forbid_contexts.iter().any(|ctx| {
474 if let Some(pos) = orchestrator_prompt.find(ctx) {
475 if let Some(pattern_pos) = orchestrator_prompt[pos..].find(pattern) {
476 pattern_pos < 200
477 } else {
478 false
479 }
480 } else {
481 false
482 }
483 });
484
485 assert!(
486 is_forbidden,
487 "Orchestrator prompt contains instructive git command pattern '{pattern}'"
488 );
489 }
490 }
491 }
492}