ralph_workflow/checkpoint/
restore.rs1use crate::checkpoint::execution_history::ExecutionHistory;
7use crate::checkpoint::state::{PipelineCheckpoint, PipelinePhase, RebaseState};
8use crate::config::Config;
9
10#[derive(Debug, Clone, PartialEq, Eq)]
16pub struct ResumeContext {
17 pub phase: PipelinePhase,
19 pub iteration: u32,
21 pub total_iterations: u32,
23 pub reviewer_pass: u32,
25 pub total_reviewer_passes: u32,
27 pub resume_count: u32,
29 pub rebase_state: RebaseState,
31 pub run_id: String,
33 pub prompt_history:
35 Option<std::collections::HashMap<String, crate::prompts::PromptHistoryEntry>>,
36 pub execution_history: Option<ExecutionHistory>,
38}
39
40impl ResumeContext {
41 #[must_use]
43 pub fn phase_name(&self) -> String {
44 match self.phase {
45 PipelinePhase::Rebase => "Rebase".to_string(),
46 PipelinePhase::Planning => "Planning".to_string(),
47 PipelinePhase::Development => format!(
48 "Development iteration {}/{}",
49 self.iteration, self.total_iterations
50 ),
51 PipelinePhase::Review => format!(
52 "Review (pass {}/{})",
53 self.reviewer_pass, self.total_reviewer_passes
54 ),
55 PipelinePhase::CommitMessage => "Commit Message Generation".to_string(),
56 PipelinePhase::FinalValidation => "Final Validation".to_string(),
57 PipelinePhase::Complete => "Complete".to_string(),
58 PipelinePhase::PreRebase => "Pre-Rebase".to_string(),
59 PipelinePhase::PreRebaseConflict => "Pre-Rebase Conflict".to_string(),
60 PipelinePhase::PostRebase => "Post-Rebase".to_string(),
61 PipelinePhase::PostRebaseConflict => "Post-Rebase Conflict".to_string(),
62 PipelinePhase::AwaitingDevFix => "Awaiting Dev Fix".to_string(),
63 PipelinePhase::Interrupted => "Interrupted".to_string(),
64 }
65 }
66}
67
68impl PipelineCheckpoint {
69 #[must_use]
74 pub fn resume_context(&self) -> ResumeContext {
75 ResumeContext {
76 phase: self.phase,
77 iteration: self.iteration,
78 total_iterations: self.total_iterations,
79 reviewer_pass: self.reviewer_pass,
80 total_reviewer_passes: self.total_reviewer_passes,
81 resume_count: self.resume_count,
82 rebase_state: self.rebase_state.clone(),
83 run_id: self.run_id.clone(),
84 prompt_history: self.prompt_history.clone(),
85 execution_history: self.execution_history.clone(),
86 }
87 }
88}
89
90pub fn apply_checkpoint_to_config(config: &mut Config, checkpoint: &PipelineCheckpoint) {
92 let cli_args = &checkpoint.cli_args;
93
94 config.developer_iters = cli_args.developer_iters;
97 config.reviewer_reviews = cli_args.reviewer_reviews;
98
99 if let Some(ref model) = checkpoint.developer_agent_config.model_override {
105 config.developer_model = Some(model.clone());
106 }
107 if let Some(ref model) = checkpoint.reviewer_agent_config.model_override {
108 config.reviewer_model = Some(model.clone());
109 }
110
111 if let Some(ref provider) = checkpoint.developer_agent_config.provider_override {
113 config.developer_provider = Some(provider.clone());
114 }
115 if let Some(ref provider) = checkpoint.reviewer_agent_config.provider_override {
116 config.reviewer_provider = Some(provider.clone());
117 }
118
119 config.developer_context = checkpoint.developer_agent_config.context_level;
121 config.reviewer_context = checkpoint.reviewer_agent_config.context_level;
122
123 if let Some(ref name) = checkpoint.git_user_name {
125 config.git_user_name = Some(name.clone());
126 }
127 if let Some(ref email) = checkpoint.git_user_email {
128 config.git_user_email = Some(email.clone());
129 }
130
131 config.isolation_mode = cli_args.isolation_mode;
133
134 config.verbosity = crate::config::types::Verbosity::from(cli_args.verbosity);
136
137 config.show_streaming_metrics = cli_args.show_streaming_metrics;
139
140 if let Some(ref parser) = cli_args.reviewer_json_parser {
142 config.reviewer_json_parser = Some(parser.clone());
143 }
144}
145
146#[must_use]
150pub fn restore_environment_from_checkpoint(checkpoint: &PipelineCheckpoint) -> usize {
151 restore_environment_impl(checkpoint, |key, value| {
152 std::env::set_var(key, value);
153 })
154}
155
156pub(crate) fn restore_environment_impl(
162 checkpoint: &PipelineCheckpoint,
163 mut set_var: impl FnMut(&str, &str),
164) -> usize {
165 let Some(ref env_snap) = checkpoint.env_snapshot else {
166 return 0;
167 };
168
169 let mut restored = 0;
170
171 for (key, value) in &env_snap.ralph_vars {
173 if crate::checkpoint::state::is_sensitive_env_key(key) {
174 continue;
175 }
176 set_var(key, value);
177 restored += 1;
178 }
179
180 restored
181}
182
183#[must_use]
185pub fn calculate_start_iteration(checkpoint: &PipelineCheckpoint, max_iterations: u32) -> u32 {
186 match checkpoint.phase {
187 PipelinePhase::Planning | PipelinePhase::Development => {
188 checkpoint.iteration.clamp(1, max_iterations)
189 }
190 _ => max_iterations,
192 }
193}
194
195#[must_use]
209pub fn calculate_start_reviewer_pass(checkpoint: &PipelineCheckpoint, max_passes: u32) -> u32 {
210 match checkpoint.phase {
211 PipelinePhase::Review => checkpoint.reviewer_pass.clamp(1, max_passes.max(1)),
212 PipelinePhase::Planning
214 | PipelinePhase::Development
215 | PipelinePhase::PreRebase
216 | PipelinePhase::PreRebaseConflict => 1,
217 _ => max_passes,
219 }
220}
221
222#[must_use]
226pub const fn should_skip_phase(phase: PipelinePhase, checkpoint: &PipelineCheckpoint) -> bool {
227 phase_rank(phase) < phase_rank(checkpoint.phase)
228}
229
230const fn phase_rank(phase: PipelinePhase) -> u32 {
234 match phase {
235 PipelinePhase::Planning => 0,
236 PipelinePhase::Development => 1,
237 PipelinePhase::CommitMessage => 3,
238 PipelinePhase::FinalValidation => 4,
239 PipelinePhase::Complete => 5,
240 PipelinePhase::AwaitingDevFix => 6,
241 PipelinePhase::Interrupted => 7,
242 PipelinePhase::Review
244 | PipelinePhase::PreRebase
245 | PipelinePhase::PreRebaseConflict
246 | PipelinePhase::Rebase
247 | PipelinePhase::PostRebase
248 | PipelinePhase::PostRebaseConflict => 2,
249 }
250}
251
252#[cfg(test)]
253#[derive(Debug, Clone)]
254pub struct RestoredContext {
255 pub phase: PipelinePhase,
256 pub resume_iteration: u32,
257 pub total_iterations: u32,
258 pub resume_reviewer_pass: u32,
259 pub total_reviewer_passes: u32,
260 pub developer_agent: String,
261 pub reviewer_agent: String,
262 pub cli_args: Option<crate::checkpoint::state::CliArgsSnapshot>,
263}
264
265#[cfg(test)]
266impl RestoredContext {
267 #[must_use]
269 pub fn from_checkpoint(checkpoint: &PipelineCheckpoint) -> Self {
270 let cli_args = if checkpoint.cli_args.developer_iters > 0
272 || checkpoint.cli_args.reviewer_reviews > 0
273 {
274 Some(checkpoint.cli_args.clone())
275 } else {
276 None
277 };
278
279 Self {
280 phase: checkpoint.phase,
281 resume_iteration: checkpoint.iteration,
282 total_iterations: checkpoint.total_iterations,
283 resume_reviewer_pass: checkpoint.reviewer_pass,
284 total_reviewer_passes: checkpoint.total_reviewer_passes,
285 developer_agent: checkpoint.developer_agent.clone(),
286 reviewer_agent: checkpoint.reviewer_agent.clone(),
287 cli_args,
288 }
289 }
290
291 #[must_use]
296 pub fn should_use_checkpoint_iterations(&self) -> bool {
297 self.cli_args
298 .as_ref()
299 .is_some_and(|args| args.developer_iters > 0)
300 }
301
302 #[must_use]
304 pub fn should_use_checkpoint_reviewer_passes(&self) -> bool {
305 self.cli_args
306 .as_ref()
307 .is_some_and(|args| args.reviewer_reviews > 0)
308 }
309}
310
311#[cfg(test)]
312mod tests;