ralph_workflow/
banner.rs

1//! Banner and UI output utilities.
2//!
3//! This module contains presentation logic for the pipeline's visual output,
4//! including the welcome banner and the final summary display.
5
6use crate::logger::Colors;
7use crate::logger::Logger;
8
9/// Summary data for pipeline completion display.
10///
11/// Decouples the banner presentation logic from the actual pipeline types.
12pub struct PipelineSummary {
13    /// Total elapsed time formatted as "Xm YYs"
14    pub total_time: String,
15    /// Number of developer runs completed
16    pub dev_runs_completed: usize,
17    /// Total configured developer iterations
18    pub dev_runs_total: usize,
19    /// Number of reviewer runs completed
20    pub review_runs: usize,
21    /// Number of changes detected during pipeline
22    pub changes_detected: usize,
23    /// Whether isolation mode is enabled
24    pub isolation_mode: bool,
25    /// Whether to show verbose output
26    pub verbose: bool,
27    /// Optional review metrics summary
28    pub review_summary: Option<ReviewSummary>,
29}
30
31/// Review metrics summary for display.
32pub struct ReviewSummary {
33    /// One-line summary of review results
34    pub summary: String,
35    /// Number of unresolved issues
36    pub unresolved_count: usize,
37    /// Number of unresolved blocking issues
38    pub blocking_count: usize,
39    /// Optional detailed breakdown (for verbose mode)
40    pub detailed_breakdown: Option<String>,
41    /// Optional sample unresolved issues (for verbose mode)
42    pub samples: Vec<String>,
43}
44
45/// Print the welcome banner for the Ralph pipeline.
46///
47/// Displays a styled ASCII box with the pipeline name and agent information.
48///
49/// # Arguments
50///
51/// * `colors` - Color configuration for terminal output
52/// * `developer_agent` - Name of the developer agent
53/// * `reviewer_agent` - Name of the reviewer agent
54pub fn print_welcome_banner(colors: Colors, developer_agent: &str, reviewer_agent: &str) {
55    println!();
56    println!(
57        "{}{}╭────────────────────────────────────────────────────────────╮{}",
58        colors.bold(),
59        colors.cyan(),
60        colors.reset()
61    );
62    println!(
63        "{}{}│{}  {}{}🤖 Ralph{} {}─ PROMPT-driven agent orchestrator{}              {}{}│{}",
64        colors.bold(),
65        colors.cyan(),
66        colors.reset(),
67        colors.bold(),
68        colors.white(),
69        colors.reset(),
70        colors.dim(),
71        colors.reset(),
72        colors.bold(),
73        colors.cyan(),
74        colors.reset()
75    );
76    println!(
77        "{}{}│{}  {}{} × {} pipeline for autonomous development{}                 {}{}│{}",
78        colors.bold(),
79        colors.cyan(),
80        colors.reset(),
81        colors.dim(),
82        developer_agent,
83        reviewer_agent,
84        colors.reset(),
85        colors.bold(),
86        colors.cyan(),
87        colors.reset()
88    );
89    println!(
90        "{}{}╰────────────────────────────────────────────────────────────╯{}",
91        colors.bold(),
92        colors.cyan(),
93        colors.reset()
94    );
95    println!();
96}
97
98/// Print the final summary after pipeline completion.
99///
100/// Displays statistics about the pipeline run including timing, run counts,
101/// and review metrics if available.
102///
103/// # Arguments
104///
105/// * `colors` - Color configuration for terminal output
106/// * `summary` - Pipeline summary data
107/// * `logger` - Logger for final success message
108pub fn print_final_summary(colors: Colors, summary: &PipelineSummary, logger: &Logger) {
109    logger.header("Pipeline Complete", crate::logger::Colors::green);
110
111    println!();
112    println!(
113        "{}{}📊 Summary{}",
114        colors.bold(),
115        colors.white(),
116        colors.reset()
117    );
118    println!(
119        "{}──────────────────────────────────{}",
120        colors.dim(),
121        colors.reset()
122    );
123    println!(
124        "  {}⏱{}  Total time:      {}{}{}",
125        colors.cyan(),
126        colors.reset(),
127        colors.bold(),
128        summary.total_time,
129        colors.reset()
130    );
131    println!(
132        "  {}🔄{}  Dev runs:        {}{}{}/{}",
133        colors.blue(),
134        colors.reset(),
135        colors.bold(),
136        summary.dev_runs_completed,
137        colors.reset(),
138        summary.dev_runs_total
139    );
140    println!(
141        "  {}🔍{}  Review runs:     {}{}{}",
142        colors.magenta(),
143        colors.reset(),
144        colors.bold(),
145        summary.review_runs,
146        colors.reset()
147    );
148    println!(
149        "  {}📝{}  Changes detected: {}{}{}",
150        colors.green(),
151        colors.reset(),
152        colors.bold(),
153        summary.changes_detected,
154        colors.reset()
155    );
156
157    // Review metrics
158    if let Some(ref review) = summary.review_summary {
159        print_review_summary(colors, summary.verbose, review);
160    }
161    println!();
162
163    print_output_files(colors, summary.isolation_mode);
164
165    logger.success("Ralph pipeline completed successfully!");
166}
167
168/// Print review metrics summary.
169fn print_review_summary(colors: Colors, verbose: bool, review: &ReviewSummary) {
170    // No issues case
171    if review.unresolved_count == 0 && review.blocking_count == 0 {
172        println!(
173            "  {}✓{}   Review result:   {}{}{}",
174            colors.green(),
175            colors.reset(),
176            colors.bold(),
177            review.summary,
178            colors.reset()
179        );
180        return;
181    }
182
183    // Issues present
184    println!(
185        "  {}🔎{}  Review summary:  {}{}{}",
186        colors.yellow(),
187        colors.reset(),
188        colors.bold(),
189        review.summary,
190        colors.reset()
191    );
192
193    // Show unresolved count
194    if review.unresolved_count > 0 {
195        println!(
196            "  {}⚠{}   Unresolved:      {}{}{} issues remaining",
197            colors.red(),
198            colors.reset(),
199            colors.bold(),
200            review.unresolved_count,
201            colors.reset()
202        );
203    }
204
205    // Show detailed breakdown in verbose mode
206    if verbose {
207        if let Some(ref breakdown) = review.detailed_breakdown {
208            println!("  {}📊{}  Breakdown:", colors.dim(), colors.reset());
209            for line in breakdown.lines() {
210                println!("      {}{}{}", colors.dim(), line.trim(), colors.reset());
211            }
212        }
213        // Show sample unresolved issues
214        if !review.samples.is_empty() {
215            println!(
216                "  {}🧾{}  Unresolved samples:",
217                colors.dim(),
218                colors.reset()
219            );
220            for s in &review.samples {
221                println!("      {}- {}{}", colors.dim(), s, colors.reset());
222            }
223        }
224    }
225
226    // Highlight blocking issues
227    if review.blocking_count > 0 {
228        println!(
229            "  {}🚨{}  BLOCKING:        {}{}{} critical/high issues unresolved",
230            colors.red(),
231            colors.reset(),
232            colors.bold(),
233            review.blocking_count,
234            colors.reset()
235        );
236    }
237}
238
239/// Print the output files list.
240fn print_output_files(colors: Colors, isolation_mode: bool) {
241    println!(
242        "{}{}📁 Output Files{}",
243        colors.bold(),
244        colors.white(),
245        colors.reset()
246    );
247    println!(
248        "{}──────────────────────────────────{}",
249        colors.dim(),
250        colors.reset()
251    );
252    println!(
253        "  → {}PROMPT.md{}           Goal definition",
254        colors.cyan(),
255        colors.reset()
256    );
257    println!(
258        "  → {}.agent/STATUS.md{}    Current status",
259        colors.cyan(),
260        colors.reset()
261    );
262    // Only show ISSUES.md and NOTES.md when NOT in isolation mode
263    if !isolation_mode {
264        println!(
265            "  → {}.agent/ISSUES.md{}    Review findings",
266            colors.cyan(),
267            colors.reset()
268        );
269        println!(
270            "  → {}.agent/NOTES.md{}     Progress notes",
271            colors.cyan(),
272            colors.reset()
273        );
274    }
275    println!(
276        "  → {}.agent/logs/{}        Detailed logs",
277        colors.cyan(),
278        colors.reset()
279    );
280    println!();
281}