1use crate::progress::{format_tokens, Breadcrumb};
6
7use super::colors::*;
8
9#[derive(Debug, Clone)]
11pub struct StoryResult {
12 pub id: String,
13 pub title: String,
14 pub passed: bool,
15 pub duration_secs: u64,
16}
17
18pub fn make_progress_bar(completed: usize, total: usize, width: usize) -> String {
20 if total == 0 {
21 return " ".repeat(width);
22 }
23 let filled = (completed * width) / total;
24 let empty = width - filled;
25 format!(
26 "{GREEN}{}{RESET}{GRAY}{}{RESET}",
27 "█".repeat(filled),
28 "░".repeat(empty)
29 )
30}
31
32pub fn print_story_complete(story_id: &str, duration_secs: u64) {
34 let mins = duration_secs / 60;
35 let secs = duration_secs % 60;
36 println!();
37 println!("{GRAY}{}{RESET}", "-".repeat(57));
38 println!(
39 "{GREEN}{BOLD}{} completed{RESET} in {}m {}s",
40 story_id, mins, secs
41 );
42 println!("{GRAY}{}{RESET}", "-".repeat(57));
43 println!();
44}
45
46pub fn print_all_complete() {
48 println!();
49 println!("{GREEN}{BOLD}All stories completed!{RESET}");
50 println!();
51}
52
53pub fn print_run_completed(duration_secs: u64, total_tokens: Option<u64>) {
60 let duration = format_run_duration(duration_secs);
61 let token_suffix = total_tokens
62 .map(|t| format!(" - {} total tokens", format_tokens(t)))
63 .unwrap_or_default();
64
65 println!();
66 println!(
67 "{GREEN}\u{2714} Run completed in {}{}{RESET}",
68 duration, token_suffix
69 );
70 println!();
71}
72
73fn format_run_duration(secs: u64) -> String {
75 if secs >= 3600 {
76 let hours = secs / 3600;
77 let mins = (secs % 3600) / 60;
78 let remaining_secs = secs % 60;
79 format!("{}h {}m {}s", hours, mins, remaining_secs)
80 } else if secs >= 60 {
81 let mins = secs / 60;
82 let remaining_secs = secs % 60;
83 format!("{}m {}s", mins, remaining_secs)
84 } else {
85 format!("{}s", secs)
86 }
87}
88
89pub fn print_reviewing(iteration: u32, max_iterations: u32) {
91 println!();
92 println!("{GRAY}{}{RESET}", "-".repeat(57));
93 println!(
94 "{YELLOW}Reviewing changes (review {}/{})...{RESET}",
95 iteration, max_iterations
96 );
97 println!("{GRAY}{}{RESET}", "-".repeat(57));
98 println!();
99}
100
101pub fn print_skip_review() {
103 println!();
104 println!("{GRAY}{}{RESET}", "-".repeat(57));
105 println!("{YELLOW}Skipping review (--skip-review flag set){RESET}");
106 println!("{GRAY}{}{RESET}", "-".repeat(57));
107 println!();
108}
109
110pub fn print_review_passed() {
112 println!();
113 println!("{GRAY}{}{RESET}", "-".repeat(57));
114 println!("{GREEN}{BOLD}Review passed! Proceeding to commit.{RESET}");
115 println!("{GRAY}{}{RESET}", "-".repeat(57));
116 println!();
117}
118
119pub fn print_issues_found(iteration: u32, max_iterations: u32) {
121 println!();
122 println!("{GRAY}{}{RESET}", "-".repeat(57));
123 println!(
124 "{YELLOW}Issues found. Running corrector (attempt {}/{})...{RESET}",
125 iteration, max_iterations
126 );
127 println!("{GRAY}{}{RESET}", "-".repeat(57));
128 println!();
129}
130
131pub fn print_max_review_iterations() {
133 println!();
134 println!("{GRAY}{}{RESET}", "-".repeat(57));
135 println!("{RED}{BOLD}Review failed after 3 attempts.{RESET}");
136 println!("{GRAY}{}{RESET}", "-".repeat(57));
137 println!();
138}
139
140pub fn print_tasks_progress(completed: usize, total: usize) {
142 let progress_bar = make_progress_bar(completed, total, 12);
143 println!(
144 "{BLUE}Tasks:{RESET} [{}] {}/{} complete",
145 progress_bar, completed, total
146 );
147}
148
149pub fn print_review_progress(current: u32, max: u32) {
151 let progress_bar = make_progress_bar(current as usize, max as usize, 8);
152 println!(
153 "{BLUE}Review:{RESET} [{}] {}/{}",
154 progress_bar, current, max
155 );
156}
157
158pub fn print_full_progress(
160 tasks_completed: usize,
161 tasks_total: usize,
162 review_current: u32,
163 review_max: u32,
164) {
165 print_tasks_progress(tasks_completed, tasks_total);
166 print_review_progress(review_current, review_max);
167}
168
169pub fn print_run_summary(
171 total_stories: usize,
172 completed_stories: usize,
173 total_iterations: u32,
174 total_duration_secs: u64,
175 story_results: &[StoryResult],
176) {
177 let hours = total_duration_secs / 3600;
178 let mins = (total_duration_secs % 3600) / 60;
179 let secs = total_duration_secs % 60;
180
181 println!();
182 println!("{CYAN}{BOLD}Run Summary{RESET}");
183 println!("{GRAY}{}{RESET}", "-".repeat(57));
184 println!(
185 "{BLUE}Stories:{RESET} {}/{} completed",
186 completed_stories, total_stories
187 );
188 println!("{BLUE}Tasks:{RESET} {}", total_iterations);
189 println!(
190 "{BLUE}Total time:{RESET} {:02}:{:02}:{:02}",
191 hours, mins, secs
192 );
193 println!();
194
195 if !story_results.is_empty() {
196 println!("{BOLD}Per-story breakdown:{RESET}");
197 for result in story_results {
198 let status = if result.passed {
199 format!("{GREEN}PASS{RESET}")
200 } else {
201 format!("{RED}FAIL{RESET}")
202 };
203 let story_mins = result.duration_secs / 60;
204 let story_secs = result.duration_secs % 60;
205 println!(
206 " [{}] {}: {} ({}m {}s)",
207 status, result.id, result.title, story_mins, story_secs
208 );
209 }
210 println!();
211 }
212 println!("{GRAY}{}{RESET}", "-".repeat(57));
213}
214
215pub fn print_breadcrumb_trail(breadcrumb: &Breadcrumb) {
217 breadcrumb.print();
218}
219
220#[cfg(test)]
221mod tests {
222 use super::*;
223
224 #[test]
229 fn test_format_run_duration_seconds() {
230 assert_eq!(format_run_duration(0), "0s");
231 assert_eq!(format_run_duration(1), "1s");
232 assert_eq!(format_run_duration(30), "30s");
233 assert_eq!(format_run_duration(59), "59s");
234 }
235
236 #[test]
237 fn test_format_run_duration_minutes() {
238 assert_eq!(format_run_duration(60), "1m 0s");
239 assert_eq!(format_run_duration(90), "1m 30s");
240 assert_eq!(format_run_duration(125), "2m 5s");
241 assert_eq!(format_run_duration(3599), "59m 59s");
242 }
243
244 #[test]
245 fn test_format_run_duration_hours() {
246 assert_eq!(format_run_duration(3600), "1h 0m 0s");
247 assert_eq!(format_run_duration(3661), "1h 1m 1s");
248 assert_eq!(format_run_duration(7326), "2h 2m 6s");
249 assert_eq!(format_run_duration(3723), "1h 2m 3s");
250 }
251
252 #[test]
253 fn test_format_run_duration_large() {
254 assert_eq!(format_run_duration(37845), "10h 30m 45s");
256 }
257}