1use crate::logger::Colors;
7use crate::logger::Loggable;
8
9pub struct PipelineSummary {
22 pub total_time: String,
24 pub dev_runs_completed: usize,
26 pub dev_runs_total: usize,
28 pub review_passes_completed: usize,
30 pub review_passes_total: usize,
32 pub review_runs: usize,
34 pub changes_detected: usize,
36 pub isolation_mode: bool,
38 pub verbose: bool,
40 pub review_summary: Option<ReviewSummary>,
42}
43
44pub struct ReviewSummary {
46 pub summary: String,
48 pub unresolved_count: usize,
50 pub blocking_count: usize,
52 pub detailed_breakdown: Option<String>,
54 pub samples: Vec<String>,
56}
57
58pub fn print_welcome_banner(colors: Colors, developer_agent: &str, reviewer_agent: &str) {
68 let _ = print_welcome_banner_to(colors, developer_agent, reviewer_agent, std::io::stdout());
69}
70
71pub fn print_welcome_banner_to<W: std::io::Write>(
72 colors: Colors,
73 developer_agent: &str,
74 reviewer_agent: &str,
75 output: W,
76) -> std::io::Result<()> {
77 let content = build_welcome_banner_content(colors, developer_agent, reviewer_agent);
78 write_banner_to(output, &content)
79}
80
81fn build_welcome_banner_content(
82 colors: Colors,
83 developer_agent: &str,
84 reviewer_agent: &str,
85) -> String {
86 let lines = [
87 "",
88 &format!(
89 "{}{}╭────────────────────────────────────────────────────────────╮{}",
90 colors.bold(),
91 colors.cyan(),
92 colors.reset()
93 ),
94 &format!(
95 "{}{}│{} {}{}🤖 Ralph{} {}─ PROMPT-driven agent orchestrator{} {}{}│{}",
96 colors.bold(),
97 colors.cyan(),
98 colors.reset(),
99 colors.bold(),
100 colors.white(),
101 colors.reset(),
102 colors.dim(),
103 colors.reset(),
104 colors.bold(),
105 colors.cyan(),
106 colors.reset()
107 ),
108 &format!(
109 "{}{}│{} {}{} × {} pipeline for autonomous development{} {}{}│{}",
110 colors.bold(),
111 colors.cyan(),
112 colors.reset(),
113 colors.dim(),
114 developer_agent,
115 reviewer_agent,
116 colors.reset(),
117 colors.bold(),
118 colors.cyan(),
119 colors.reset()
120 ),
121 &format!(
122 "{}{}╰────────────────────────────────────────────────────────────╯{}",
123 colors.bold(),
124 colors.cyan(),
125 colors.reset()
126 ),
127 "",
128 "",
129 ];
130 lines.join("\n")
131}
132
133pub fn print_final_summary<L: Loggable>(colors: Colors, summary: &PipelineSummary, logger: &L) {
144 let _ = print_final_summary_to(colors, summary, logger, std::io::stdout());
145}
146
147pub fn print_final_summary_to<L: Loggable, W: std::io::Write>(
148 colors: Colors,
149 summary: &PipelineSummary,
150 logger: &L,
151 output: W,
152) -> std::io::Result<()> {
153 logger.header("Pipeline Complete", crate::logger::Colors::green);
154
155 let content = build_final_summary_content(colors, summary);
156
157 write_banner_to(output, &content)?;
158
159 logger.success("Ralph pipeline completed successfully!");
161
162 if summary.review_runs > 0 {
164 logger.info(&format!("Completed {} review run(s)", summary.review_runs));
165 }
166 if summary.changes_detected > 0 {
167 logger.info(&format!("Detected {} change(s)", summary.changes_detected));
168 }
169 if summary.isolation_mode {
170 logger.info("Running in isolation mode");
171 }
172
173 if let Some(ref review) = summary.review_summary {
175 if review.unresolved_count > 0 {
176 logger.warn(&format!(
177 "{} unresolved issue(s) remaining",
178 review.unresolved_count
179 ));
180 }
181 if review.blocking_count > 0 {
182 logger.error(&format!(
183 "{} blocking issue(s) unresolved",
184 review.blocking_count
185 ));
186 }
187 }
188
189 Ok(())
190}
191
192fn write_banner_to<W: std::io::Write>(mut output: W, content: &str) -> std::io::Result<()> {
193 output.write_all(content.as_bytes())
194}
195
196fn build_final_summary_content(colors: Colors, summary: &PipelineSummary) -> String {
197 let base_lines: Vec<String> = vec![
198 "".to_string(),
199 format!(
200 "{}{}📊 Summary{}",
201 colors.bold(),
202 colors.white(),
203 colors.reset()
204 ),
205 format!(
206 "{}──────────────────────────────────{}",
207 colors.dim(),
208 colors.reset()
209 ),
210 format!(
211 " {}⏱{} Total time: {}{}{}",
212 colors.cyan(),
213 colors.reset(),
214 colors.bold(),
215 summary.total_time,
216 colors.reset()
217 ),
218 format!(
219 " {}🔄{} Dev runs: {}{}{}/{}",
220 colors.blue(),
221 colors.reset(),
222 colors.bold(),
223 summary.dev_runs_completed,
224 colors.reset(),
225 summary.dev_runs_total
226 ),
227 format!(
228 " {}🔍{} Review passes: {}{}{}/{}",
229 colors.magenta(),
230 colors.reset(),
231 colors.bold(),
232 summary.review_passes_completed,
233 colors.reset(),
234 summary.review_passes_total
235 ),
236 format!(
237 " {}📝{} Changes detected: {}{}{}",
238 colors.green(),
239 colors.reset(),
240 colors.bold(),
241 summary.changes_detected,
242 colors.reset()
243 ),
244 ];
245
246 let verbose_line: Vec<String> = if summary.verbose {
247 vec![format!(
248 " {} {} (Total runs: {}{}{}){}",
249 colors.dim(),
250 colors.magenta(),
251 colors.bold(),
252 summary.review_runs,
253 colors.reset(),
254 colors.reset()
255 )]
256 } else {
257 Vec::new()
258 };
259
260 let review_lines: Vec<String> = summary
261 .review_summary
262 .as_ref()
263 .map_or(Vec::new(), |review| {
264 build_review_summary_content(colors, summary.verbose, review)
265 .lines()
266 .map(String::from)
267 .collect()
268 });
269
270 let output_files_lines: Vec<String> =
271 build_output_files_content(colors, summary.isolation_mode)
272 .lines()
273 .map(String::from)
274 .collect();
275
276 base_lines
277 .into_iter()
278 .chain(verbose_line)
279 .chain(std::iter::once("".to_string()))
280 .chain(review_lines)
281 .chain(std::iter::once("".to_string()))
282 .chain(output_files_lines)
283 .collect::<Vec<_>>()
284 .join("\n")
285}
286
287fn build_review_summary_content(colors: Colors, verbose: bool, review: &ReviewSummary) -> String {
288 if review.unresolved_count == 0 && review.blocking_count == 0 {
289 return [format!(
290 " {}✓{} Review result: {}{}{}",
291 colors.green(),
292 colors.reset(),
293 colors.bold(),
294 review.summary,
295 colors.reset()
296 )]
297 .join("\n");
298 }
299
300 let base_line: Vec<String> = vec![format!(
301 " {}🔎{} Review summary: {}{}{}",
302 colors.yellow(),
303 colors.reset(),
304 colors.bold(),
305 review.summary,
306 colors.reset()
307 )];
308
309 let unresolved_line: Vec<String> = if review.unresolved_count > 0 {
310 vec![format!(
311 " {}⚠{} Unresolved: {}{}{} issues remaining",
312 colors.red(),
313 colors.reset(),
314 colors.bold(),
315 review.unresolved_count,
316 colors.reset()
317 )]
318 } else {
319 Vec::new()
320 };
321
322 let verbose_lines: Vec<String> = if verbose {
323 let breakdown_lines: Vec<String> =
324 review
325 .detailed_breakdown
326 .as_ref()
327 .map_or_else(Vec::<String>::new, |breakdown| {
328 let line_strs: Vec<&str> = breakdown.lines().collect();
329 let dimmed: Vec<String> = line_strs
330 .iter()
331 .map(|line| {
332 format!(" {}{}{}", colors.dim(), line.trim(), colors.reset())
333 })
334 .collect();
335 std::iter::once(format!(
336 " {}📊{} Breakdown:",
337 colors.dim(),
338 colors.reset()
339 ))
340 .chain(dimmed)
341 .collect()
342 });
343
344 let sample_lines: Vec<String> = if !review.samples.is_empty() {
345 std::iter::once(format!(
346 " {}🧾{} Unresolved samples:",
347 colors.dim(),
348 colors.reset()
349 ))
350 .chain(
351 review
352 .samples
353 .iter()
354 .map(|s| format!(" {}- {}{}", colors.dim(), s, colors.reset())),
355 )
356 .collect()
357 } else {
358 Vec::new()
359 };
360
361 breakdown_lines.into_iter().chain(sample_lines).collect()
362 } else {
363 Vec::new()
364 };
365
366 let blocking_line: Vec<String> = if review.blocking_count > 0 {
367 vec![format!(
368 " {}🚨{} BLOCKING: {}{}{} critical/high issues unresolved",
369 colors.red(),
370 colors.reset(),
371 colors.bold(),
372 review.blocking_count,
373 colors.reset()
374 )]
375 } else {
376 Vec::new()
377 };
378
379 base_line
380 .into_iter()
381 .chain(unresolved_line)
382 .chain(verbose_lines)
383 .chain(blocking_line)
384 .collect::<Vec<_>>()
385 .join("\n")
386}
387
388fn build_output_files_content(colors: Colors, isolation_mode: bool) -> String {
389 let base_lines: Vec<String> = vec![
390 format!(
391 "{}{}📁 Output Files{}",
392 colors.bold(),
393 colors.white(),
394 colors.reset()
395 ),
396 format!(
397 "{}──────────────────────────────────{}",
398 colors.dim(),
399 colors.reset()
400 ),
401 format!(
402 " → {}PROMPT.md{} Goal definition",
403 colors.cyan(),
404 colors.reset()
405 ),
406 format!(
407 " → {}.agent/STATUS.md{} Current status",
408 colors.cyan(),
409 colors.reset()
410 ),
411 ];
412
413 let isolation_lines: Vec<String> = if !isolation_mode {
414 vec![
415 format!(
416 " → {}.agent/ISSUES.md{} Review findings",
417 colors.cyan(),
418 colors.reset()
419 ),
420 format!(
421 " → {}.agent/NOTES.md{} Progress notes",
422 colors.cyan(),
423 colors.reset()
424 ),
425 ]
426 } else {
427 Vec::new()
428 };
429
430 base_lines
431 .into_iter()
432 .chain(isolation_lines)
433 .chain(std::iter::once(format!(
434 " → {}.agent/logs/{} Detailed logs",
435 colors.cyan(),
436 colors.reset()
437 )))
438 .collect::<Vec<_>>()
439 .join("\n")
440}