use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RunContext {
pub run_id: String,
pub parent_run_id: Option<String>,
pub resume_count: u32,
pub actual_developer_runs: u32,
pub actual_reviewer_runs: u32,
}
impl RunContext {
#[must_use]
pub fn new() -> Self {
Self {
run_id: uuid::Uuid::new_v4().to_string(),
parent_run_id: None,
resume_count: 0,
actual_developer_runs: 0,
actual_reviewer_runs: 0,
}
}
#[must_use]
pub fn from_checkpoint(checkpoint: &super::PipelineCheckpoint) -> Self {
Self {
run_id: uuid::Uuid::new_v4().to_string(), parent_run_id: Some(checkpoint.run_id.clone()),
resume_count: checkpoint.resume_count.saturating_add(1),
actual_developer_runs: checkpoint.actual_developer_runs,
actual_reviewer_runs: checkpoint.actual_reviewer_runs,
}
}
#[cfg(test)]
#[must_use]
pub const fn with_developer_runs(mut self, runs: u32) -> Self {
self.actual_developer_runs = runs;
self
}
#[cfg(test)]
#[must_use]
pub const fn with_reviewer_runs(mut self, runs: u32) -> Self {
self.actual_reviewer_runs = runs;
self
}
#[must_use]
pub const fn record_developer_iteration(mut self) -> Self {
self.actual_developer_runs = self.actual_developer_runs.saturating_add(1);
self
}
#[must_use]
pub const fn record_reviewer_pass(mut self) -> Self {
self.actual_reviewer_runs = self.actual_reviewer_runs.saturating_add(1);
self
}
}
impl Default for RunContext {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::checkpoint::state::{
AgentConfigSnapshot, CheckpointParams, CliArgsSnapshot, PipelineCheckpoint, PipelinePhase,
RebaseState,
};
#[test]
fn test_run_context_new() {
let ctx = RunContext::new();
assert!(!ctx.run_id.is_empty());
assert!(ctx.parent_run_id.is_none());
assert_eq!(ctx.resume_count, 0);
assert_eq!(ctx.actual_developer_runs, 0);
assert_eq!(ctx.actual_reviewer_runs, 0);
}
#[test]
fn test_run_context_from_checkpoint() {
let cli_args = CliArgsSnapshot::new(5, 2, None, true, 2, false, None);
let dev_config =
AgentConfigSnapshot::new("claude".into(), "cmd".into(), "-o".into(), None, true);
let rev_config =
AgentConfigSnapshot::new("codex".into(), "cmd".into(), "-o".into(), None, true);
let original_run_id = uuid::Uuid::new_v4().to_string();
let checkpoint = PipelineCheckpoint::from_params(CheckpointParams {
phase: PipelinePhase::Development,
iteration: 2,
total_iterations: 5,
reviewer_pass: 0,
total_reviewer_passes: 2,
developer_agent: "claude",
reviewer_agent: "codex",
cli_args,
developer_agent_config: dev_config,
reviewer_agent_config: rev_config,
rebase_state: RebaseState::default(),
git_user_name: None,
git_user_email: None,
run_id: &original_run_id,
parent_run_id: None,
resume_count: 1,
actual_developer_runs: 2,
actual_reviewer_runs: 0,
working_dir: "/test/repo".to_string(),
prompt_md_checksum: None,
config_path: None,
config_checksum: None,
});
let run_ctx = RunContext::from_checkpoint(&checkpoint);
assert_ne!(
run_ctx.run_id, original_run_id,
"new run_id should be generated"
);
assert_eq!(run_ctx.parent_run_id, Some(original_run_id));
assert_eq!(run_ctx.resume_count, 2, "resume_count should increment");
assert_eq!(run_ctx.actual_developer_runs, 2);
assert_eq!(run_ctx.actual_reviewer_runs, 0);
}
#[test]
fn test_run_context_with_developer_runs() {
let ctx = RunContext::new().with_developer_runs(5);
assert_eq!(ctx.actual_developer_runs, 5);
}
#[test]
fn test_run_context_with_reviewer_runs() {
let ctx = RunContext::new().with_reviewer_runs(3);
assert_eq!(ctx.actual_reviewer_runs, 3);
}
}