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 + 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 pub const fn record_developer_iteration(&mut self) {
74 self.actual_developer_runs += 1;
75 }
76
77 pub const fn record_reviewer_pass(&mut self) {
79 self.actual_reviewer_runs += 1;
80 }
81}
82
83impl Default for RunContext {
84 fn default() -> Self {
85 Self::new()
86 }
87}
88
89#[cfg(test)]
90mod tests {
91 use super::*;
92 use crate::checkpoint::state::{
93 AgentConfigSnapshot, CheckpointParams, CliArgsSnapshot, PipelineCheckpoint, PipelinePhase,
94 RebaseState,
95 };
96
97 #[test]
98 fn test_run_context_new() {
99 let ctx = RunContext::new();
100 assert!(!ctx.run_id.is_empty());
101 assert!(ctx.parent_run_id.is_none());
102 assert_eq!(ctx.resume_count, 0);
103 assert_eq!(ctx.actual_developer_runs, 0);
104 assert_eq!(ctx.actual_reviewer_runs, 0);
105 }
106
107 #[test]
108 fn test_run_context_from_checkpoint() {
109 let cli_args = CliArgsSnapshot::new(5, 2, None, true, 2, false, None);
111 let dev_config =
112 AgentConfigSnapshot::new("claude".into(), "cmd".into(), "-o".into(), None, true);
113 let rev_config =
114 AgentConfigSnapshot::new("codex".into(), "cmd".into(), "-o".into(), None, true);
115
116 let original_run_id = uuid::Uuid::new_v4().to_string();
117 let checkpoint = PipelineCheckpoint::from_params(CheckpointParams {
118 phase: PipelinePhase::Development,
119 iteration: 2,
120 total_iterations: 5,
121 reviewer_pass: 0,
122 total_reviewer_passes: 2,
123 developer_agent: "claude",
124 reviewer_agent: "codex",
125 cli_args,
126 developer_agent_config: dev_config,
127 reviewer_agent_config: rev_config,
128 rebase_state: RebaseState::default(),
129 git_user_name: None,
130 git_user_email: None,
131 run_id: &original_run_id,
132 parent_run_id: None,
133 resume_count: 1,
134 actual_developer_runs: 2,
135 actual_reviewer_runs: 0,
136 working_dir: "/test/repo".to_string(),
137 prompt_md_checksum: None,
138 config_path: None,
139 config_checksum: None,
140 });
141
142 let run_ctx = RunContext::from_checkpoint(&checkpoint);
143
144 assert_ne!(
145 run_ctx.run_id, original_run_id,
146 "new run_id should be generated"
147 );
148 assert_eq!(run_ctx.parent_run_id, Some(original_run_id));
149 assert_eq!(run_ctx.resume_count, 2, "resume_count should increment");
150 assert_eq!(run_ctx.actual_developer_runs, 2);
151 assert_eq!(run_ctx.actual_reviewer_runs, 0);
152 }
153
154 #[test]
155 fn test_run_context_with_developer_runs() {
156 let ctx = RunContext::new().with_developer_runs(5);
157 assert_eq!(ctx.actual_developer_runs, 5);
158 }
159
160 #[test]
161 fn test_run_context_with_reviewer_runs() {
162 let ctx = RunContext::new().with_reviewer_runs(3);
163 assert_eq!(ctx.actual_reviewer_runs, 3);
164 }
165}