ralph_workflow/app/
finalization.rs1use crate::banner::{print_final_summary, PipelineSummary};
11use crate::checkpoint::clear_checkpoint_with_workspace;
12use crate::config::Config;
13use crate::files::protection::monitoring::PromptMonitor;
14use crate::logger::Colors;
15use crate::logger::Logger;
16use crate::pipeline::AgentPhaseGuard;
17use crate::pipeline::Timer;
18use crate::reducer::state::PipelineState;
19use crate::workspace::Workspace;
20
21pub struct FinalizeContext<'a> {
23 pub logger: &'a Logger,
24 pub colors: Colors,
25 pub config: &'a Config,
26 pub timer: &'a Timer,
27 pub workspace: &'a dyn Workspace,
28}
29
30pub fn finalize_pipeline(
40 agent_phase_guard: &mut AgentPhaseGuard,
41 ctx: FinalizeContext<'_>,
42 final_state: &PipelineState,
43 prompt_monitor: Option<PromptMonitor>,
44) {
45 if let Some(monitor) = prompt_monitor {
47 monitor.stop();
48 }
49
50 crate::git_helpers::end_agent_phase();
52 crate::git_helpers::disable_git_wrapper(agent_phase_guard.git_helpers);
53 if let Err(err) = crate::git_helpers::uninstall_hooks(ctx.logger) {
54 ctx.logger
55 .warn(&format!("Failed to uninstall Ralph hooks: {err}"));
56 }
57
58 let summary = PipelineSummary {
63 total_time: ctx.timer.elapsed_formatted(),
64 dev_runs_completed: final_state.metrics.dev_iterations_completed as usize,
65 dev_runs_total: final_state.metrics.max_dev_iterations as usize,
66 review_passes_completed: final_state.metrics.review_passes_completed as usize,
67 review_passes_total: final_state.metrics.max_review_passes as usize,
68 review_runs: final_state.metrics.review_runs_total as usize,
69 changes_detected: final_state.metrics.commits_created_total as usize,
70 isolation_mode: ctx.config.isolation_mode,
71 verbose: ctx.config.verbosity.is_verbose(),
72 review_summary: None,
73 };
74 print_final_summary(ctx.colors, &summary, ctx.logger);
75
76 if ctx.config.features.checkpoint_enabled {
77 if let Err(err) = clear_checkpoint_with_workspace(ctx.workspace) {
78 ctx.logger
79 .warn(&format!("Failed to clear checkpoint: {err}"));
80 }
81 }
82
83 agent_phase_guard.disarm();
88}
89
90#[cfg(test)]
91mod tests {
92 use crate::reducer::state::PipelineState;
93
94 #[test]
95 fn test_summary_derives_from_reducer_metrics() {
96 let mut state = PipelineState::initial(5, 2);
97 state.metrics.dev_iterations_completed = 3;
98 state.metrics.review_runs_total = 4;
99 state.metrics.commits_created_total = 3;
100
101 let dev_runs_completed = state.metrics.dev_iterations_completed as usize;
103 let dev_runs_total = state.metrics.max_dev_iterations as usize;
104 let review_runs = state.metrics.review_runs_total as usize;
105 let changes_detected = state.metrics.commits_created_total as usize;
106
107 assert_eq!(dev_runs_completed, 3);
108 assert_eq!(dev_runs_total, 5);
109 assert_eq!(review_runs, 4);
110 assert_eq!(changes_detected, 3);
111 }
112
113 #[test]
114 fn test_metrics_reflect_actual_progress_not_config() {
115 let mut state = PipelineState::initial(10, 5);
116
117 state.metrics.dev_iterations_completed = 2;
119 state.metrics.review_runs_total = 0;
120
121 assert_eq!(state.metrics.dev_iterations_completed, 2);
123 assert_eq!(state.metrics.max_dev_iterations, 10);
124 }
125
126 #[test]
127 fn test_summary_no_drift_from_runtime_counters() {
128 let mut state = PipelineState::initial(10, 5);
129
130 state.metrics.dev_iterations_completed = 7;
132 state.metrics.review_runs_total = 3;
133 state.metrics.commits_created_total = 8;
134
135 let runtime_dev_completed = 5; let runtime_review_runs = 2; let dev_runs = state.metrics.dev_iterations_completed as usize;
141 let review_runs = state.metrics.review_runs_total as usize;
142 let commits = state.metrics.commits_created_total as usize;
143
144 assert_eq!(dev_runs, 7); assert_eq!(review_runs, 3); assert_eq!(commits, 8); assert_ne!(dev_runs, runtime_dev_completed);
150 assert_ne!(review_runs, runtime_review_runs);
151 }
152
153 #[test]
154 fn test_summary_uses_all_reducer_metrics() {
155 let mut state = PipelineState::initial(5, 3);
156
157 state.metrics.dev_iterations_started = 5;
159 state.metrics.dev_iterations_completed = 5;
160 state.metrics.dev_attempts_total = 7; state.metrics.analysis_attempts_total = 5;
162 state.metrics.review_passes_started = 3;
163 state.metrics.review_passes_completed = 3;
164 state.metrics.review_runs_total = 3;
165 state.metrics.fix_runs_total = 2;
166 state.metrics.commits_created_total = 6; state.metrics.xsd_retry_attempts_total = 2;
168 state.metrics.same_agent_retry_attempts_total = 1;
169
170 let dev_runs_completed = state.metrics.dev_iterations_completed as usize;
172 let dev_runs_total = state.metrics.max_dev_iterations as usize;
173 let review_passes_completed = state.metrics.review_passes_completed as usize;
174 let review_passes_total = state.metrics.max_review_passes as usize;
175 let review_runs_total = state.metrics.review_runs_total as usize;
176 let changes_detected = state.metrics.commits_created_total as usize;
177
178 assert_eq!(dev_runs_completed, 5);
180 assert_eq!(dev_runs_total, 5);
181 assert_eq!(review_passes_completed, 3);
182 assert_eq!(review_passes_total, 3);
183 assert_eq!(review_runs_total, 3);
184 assert_eq!(changes_detected, 6);
185
186 }
189
190 #[test]
191 fn test_partial_run_shows_actual_not_configured() {
192 let mut state = PipelineState::initial(10, 5);
193
194 state.metrics.dev_iterations_completed = 3;
196 state.metrics.review_passes_completed = 1;
197 state.metrics.commits_created_total = 3;
198
199 assert_eq!(state.metrics.dev_iterations_completed, 3);
200 assert_eq!(state.metrics.max_dev_iterations, 10);
201 assert_eq!(state.metrics.review_passes_completed, 1);
202 assert_eq!(state.metrics.max_review_passes, 5);
203 }
204}