ralph_workflow/checkpoint/
run_context.rs1use serde::{Deserialize, Serialize};
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct RunContext {
19 pub run_id: String,
21 pub parent_run_id: Option<String>,
23 pub resume_count: u32,
25 pub actual_developer_runs: u32,
27 pub actual_reviewer_runs: u32,
29}
30
31impl RunContext {
32 #[must_use]
34 pub fn new() -> Self {
35 Self {
36 run_id: uuid::Uuid::new_v4().to_string(),
37 parent_run_id: None,
38 resume_count: 0,
39 actual_developer_runs: 0,
40 actual_reviewer_runs: 0,
41 }
42 }
43
44 #[must_use]
46 pub fn from_checkpoint(checkpoint: &super::PipelineCheckpoint) -> Self {
47 Self {
48 run_id: uuid::Uuid::new_v4().to_string(), parent_run_id: Some(checkpoint.run_id.clone()),
50 resume_count: checkpoint.resume_count.saturating_add(1),
51 actual_developer_runs: checkpoint.actual_developer_runs,
52 actual_reviewer_runs: checkpoint.actual_reviewer_runs,
53 }
54 }
55
56 #[cfg(test)]
58 #[must_use]
59 pub const fn with_developer_runs(mut self, runs: u32) -> Self {
60 self.actual_developer_runs = runs;
61 self
62 }
63
64 #[cfg(test)]
66 #[must_use]
67 pub const fn with_reviewer_runs(mut self, runs: u32) -> Self {
68 self.actual_reviewer_runs = runs;
69 self
70 }
71
72 #[must_use]
74 pub const fn record_developer_iteration(mut self) -> Self {
75 self.actual_developer_runs = self.actual_developer_runs.saturating_add(1);
76 self
77 }
78
79 #[must_use]
81 pub const fn record_reviewer_pass(mut self) -> Self {
82 self.actual_reviewer_runs = self.actual_reviewer_runs.saturating_add(1);
83 self
84 }
85}
86
87impl Default for RunContext {
88 fn default() -> Self {
89 Self::new()
90 }
91}
92
93#[cfg(test)]
94mod tests {
95 use super::*;
96 use crate::checkpoint::state::{
97 AgentConfigSnapshot, CheckpointParams, CliArgsSnapshot, PipelineCheckpoint, PipelinePhase,
98 RebaseState,
99 };
100
101 #[test]
102 fn test_run_context_new() {
103 let ctx = RunContext::new();
104 assert!(!ctx.run_id.is_empty());
105 assert!(ctx.parent_run_id.is_none());
106 assert_eq!(ctx.resume_count, 0);
107 assert_eq!(ctx.actual_developer_runs, 0);
108 assert_eq!(ctx.actual_reviewer_runs, 0);
109 }
110
111 #[test]
112 fn test_run_context_from_checkpoint() {
113 let cli_args = CliArgsSnapshot::new(5, 2, None, true, 2, false, None);
115 let dev_config =
116 AgentConfigSnapshot::new("claude".into(), "cmd".into(), "-o".into(), None, true);
117 let rev_config =
118 AgentConfigSnapshot::new("codex".into(), "cmd".into(), "-o".into(), None, true);
119
120 let original_run_id = uuid::Uuid::new_v4().to_string();
121 let checkpoint = PipelineCheckpoint::from_params(CheckpointParams {
122 phase: PipelinePhase::Development,
123 iteration: 2,
124 total_iterations: 5,
125 reviewer_pass: 0,
126 total_reviewer_passes: 2,
127 developer_agent: "claude",
128 reviewer_agent: "codex",
129 cli_args,
130 developer_agent_config: dev_config,
131 reviewer_agent_config: rev_config,
132 rebase_state: RebaseState::default(),
133 git_user_name: None,
134 git_user_email: None,
135 run_id: &original_run_id,
136 parent_run_id: None,
137 resume_count: 1,
138 actual_developer_runs: 2,
139 actual_reviewer_runs: 0,
140 working_dir: "/test/repo".to_string(),
141 prompt_md_checksum: None,
142 config_path: None,
143 config_checksum: None,
144 });
145
146 let run_ctx = RunContext::from_checkpoint(&checkpoint);
147
148 assert_ne!(
149 run_ctx.run_id, original_run_id,
150 "new run_id should be generated"
151 );
152 assert_eq!(run_ctx.parent_run_id, Some(original_run_id));
153 assert_eq!(run_ctx.resume_count, 2, "resume_count should increment");
154 assert_eq!(run_ctx.actual_developer_runs, 2);
155 assert_eq!(run_ctx.actual_reviewer_runs, 0);
156 }
157
158 #[test]
159 fn test_run_context_with_developer_runs() {
160 let ctx = RunContext::new().with_developer_runs(5);
161 assert_eq!(ctx.actual_developer_runs, 5);
162 }
163
164 #[test]
165 fn test_run_context_with_reviewer_runs() {
166 let ctx = RunContext::new().with_reviewer_runs(3);
167 assert_eq!(ctx.actual_reviewer_runs, 3);
168 }
169}