Skip to main content

oven_cli/agents/
planner.rs

1use anyhow::{Context, Result};
2use askama::Template;
3
4use crate::{agents::InFlightIssue, issues::PipelineIssue};
5
6#[derive(Template)]
7#[template(path = "planner.txt")]
8struct PlannerPrompt<'a> {
9    issues: &'a [PipelineIssue],
10    in_flight: &'a [InFlightIssue],
11}
12
13pub fn build_prompt(issues: &[PipelineIssue], in_flight: &[InFlightIssue]) -> Result<String> {
14    let tmpl = PlannerPrompt { issues, in_flight };
15    tmpl.render().context("rendering planner template")
16}
17
18#[cfg(test)]
19mod tests {
20    use super::*;
21    use crate::{agents::Complexity, issues::IssueOrigin};
22
23    fn sample_issues() -> Vec<PipelineIssue> {
24        vec![
25            PipelineIssue {
26                number: 1,
27                title: "Add login".to_string(),
28                body: "implement login flow".to_string(),
29                source: IssueOrigin::Github,
30                target_repo: None,
31            },
32            PipelineIssue {
33                number: 2,
34                title: "Fix bug".to_string(),
35                body: "crash on startup".to_string(),
36                source: IssueOrigin::Github,
37                target_repo: None,
38            },
39        ]
40    }
41
42    #[test]
43    fn prompt_includes_issue_details() {
44        let prompt = build_prompt(&sample_issues(), &[]).unwrap();
45        assert!(prompt.contains("#1: Add login"));
46        assert!(prompt.contains("#2: Fix bug"));
47        assert!(prompt.contains("<issue_body>implement login flow</issue_body>"));
48        assert!(prompt.contains("<issue_body>crash on startup</issue_body>"));
49    }
50
51    #[test]
52    fn prompt_includes_complexity_classification() {
53        let prompt = build_prompt(&sample_issues(), &[]).unwrap();
54        assert!(prompt.contains("**simple**"));
55        assert!(prompt.contains("**full**"));
56        assert!(prompt.contains("Complexity Classification"));
57    }
58
59    #[test]
60    fn prompt_includes_conflict_detection() {
61        let prompt = build_prompt(&sample_issues(), &[]).unwrap();
62        assert!(prompt.contains("Conflict Detection"));
63        assert!(prompt.contains("CANNOT parallelize"));
64        assert!(prompt.contains("CAN parallelize"));
65    }
66
67    #[test]
68    fn prompt_structured_json_output_is_valid() {
69        let prompt = build_prompt(&sample_issues(), &[]).unwrap();
70        assert!(prompt.contains("\"complexity\": \"simple\""));
71        assert!(prompt.contains("\"has_migration\""));
72        assert!(prompt.contains("\"predicted_files\""));
73        assert!(prompt.contains("\"area\""));
74        assert!(prompt.contains("\"total_issues\""));
75        assert!(prompt.contains("\"parallel_capacity\""));
76    }
77
78    #[test]
79    fn prompt_omits_in_flight_when_empty() {
80        let prompt = build_prompt(&sample_issues(), &[]).unwrap();
81        assert!(!prompt.contains("<in_flight>"));
82    }
83
84    #[test]
85    fn prompt_includes_in_flight_context() {
86        let in_flight = vec![
87            InFlightIssue {
88                number: 10,
89                title: "Refactor auth".to_string(),
90                area: "auth".to_string(),
91                predicted_files: vec!["src/auth.rs".to_string(), "src/middleware.rs".to_string()],
92                has_migration: false,
93                complexity: Complexity::Full,
94            },
95            InFlightIssue {
96                number: 11,
97                title: "Add migration".to_string(),
98                area: "db".to_string(),
99                predicted_files: vec!["src/db/mod.rs".to_string()],
100                has_migration: true,
101                complexity: Complexity::Simple,
102            },
103        ];
104
105        let prompt = build_prompt(&sample_issues(), &in_flight).unwrap();
106        assert!(prompt.contains("<in_flight>"));
107        assert!(prompt.contains("#10: Refactor auth"));
108        assert!(prompt.contains("area: auth"));
109        assert!(prompt.contains("src/auth.rs"));
110        assert!(prompt.contains("src/middleware.rs"));
111        assert!(prompt.contains("has_migration: false"));
112        assert!(prompt.contains("#11: Add migration"));
113        assert!(prompt.contains("has_migration: true"));
114        assert!(prompt.contains("in-flight work"));
115    }
116}