oven_cli/agents/
merger.rs1use anyhow::{Context, Result};
2use askama::Template;
3
4use super::AgentContext;
5
6#[derive(Template)]
7#[template(path = "merger.txt")]
8struct MergerPrompt<'a> {
9 ctx: &'a AgentContext,
10 auto_merge: bool,
11 pr_number: u32,
12}
13
14pub fn build_prompt(ctx: &AgentContext, auto_merge: bool) -> Result<String> {
15 let pr_number = ctx.pr_number.context("merger requires a PR number")?;
16 let tmpl = MergerPrompt { ctx, auto_merge, pr_number };
17 tmpl.render().context("rendering merger template")
18}
19
20#[cfg(test)]
21mod tests {
22 use super::*;
23
24 fn sample_context() -> AgentContext {
25 AgentContext {
26 issue_number: 42,
27 issue_title: "Fix auth bug".to_string(),
28 issue_body: "details".to_string(),
29 branch: "oven/issue-42-abc".to_string(),
30 pr_number: Some(99),
31 test_command: None,
32 lint_command: None,
33 review_findings: None,
34 cycle: 1,
35 target_repo: None,
36 issue_source: "github".to_string(),
37 base_branch: "main".to_string(),
38 }
39 }
40
41 #[test]
42 fn prompt_references_pr_number() {
43 let prompt = build_prompt(&sample_context(), false).unwrap();
44 assert!(prompt.contains("PR #99"));
45 assert!(prompt.contains("#42"));
46 }
47
48 #[test]
49 fn prompt_without_merge() {
50 let prompt = build_prompt(&sample_context(), false).unwrap();
51 assert!(!prompt.contains("gh pr merge"));
52 }
53
54 #[test]
55 fn prompt_with_merge() {
56 let prompt = build_prompt(&sample_context(), true).unwrap();
57 assert!(prompt.contains("gh pr merge 99"));
58 assert!(prompt.contains("--squash"));
59 assert!(prompt.contains("--delete-branch"));
60 }
61
62 #[test]
63 fn prompt_includes_issue_close_when_auto_merge() {
64 let prompt = build_prompt(&sample_context(), true).unwrap();
65 assert!(prompt.contains("gh issue close 42"));
66 }
67
68 #[test]
69 fn prompt_no_pr_edit() {
70 let prompt = build_prompt(&sample_context(), true).unwrap();
71 assert!(!prompt.contains("gh pr edit"));
72 assert!(!prompt.contains("gh pr ready"));
73 }
74
75 #[test]
76 fn prompt_includes_merge_summary_output() {
77 let prompt = build_prompt(&sample_context(), false).unwrap();
78 assert!(prompt.contains("Merge Summary"));
79 }
80
81 #[test]
82 fn prompt_skips_issue_close_in_multi_repo() {
83 let mut ctx = sample_context();
84 ctx.target_repo = Some("backend-api".to_string());
85 let prompt = build_prompt(&ctx, true).unwrap();
86 assert!(prompt.contains("gh pr merge 99"));
87 assert!(!prompt.contains("gh issue close"));
88 }
89
90 #[test]
91 fn prompt_includes_issue_close_in_single_repo() {
92 let prompt = build_prompt(&sample_context(), true).unwrap();
93 assert!(prompt.contains("gh issue close 42"));
94 }
95
96 #[test]
97 fn prompt_skips_issue_close_for_local_source() {
98 let mut ctx = sample_context();
99 ctx.issue_source = "local".to_string();
100 let prompt = build_prompt(&ctx, true).unwrap();
101 assert!(prompt.contains("gh pr merge 99"));
102 assert!(!prompt.contains("gh issue close"));
103 }
104
105 #[test]
106 fn prompt_fails_without_pr_number() {
107 let mut ctx = sample_context();
108 ctx.pr_number = None;
109 let result = build_prompt(&ctx, false);
110 assert!(result.is_err());
111 assert!(result.unwrap_err().to_string().contains("PR number"));
112 }
113}