use std::collections::VecDeque;
fn bound_execution_history_steps(
steps: VecDeque<ExecutionStep>,
limit: usize,
) -> VecDeque<ExecutionStep> {
if limit == 0 {
return VecDeque::new();
}
let len = steps.len();
if len <= limit {
return steps;
}
let keep_from = len.saturating_sub(limit);
steps.into_iter().skip(keep_from).collect()
}
impl PipelineState {
pub(crate) fn from_checkpoint_with_execution_history_limit(
mut checkpoint: PipelineCheckpoint,
execution_history_limit: usize,
) -> Self {
let rebase_state = map_checkpoint_rebase_state(&checkpoint.rebase_state);
let agent_chain = AgentChainState::initial();
let last_substitution_log = checkpoint.last_substitution_log.clone();
let (template_validation_failed, template_validation_unsubstituted) = last_substitution_log
.as_ref()
.map_or((false, Vec::new()), |log| {
(!log.is_complete(), log.unsubstituted.clone())
});
let execution_history_steps = checkpoint
.execution_history
.take()
.map(|h| h.steps)
.unwrap_or_default();
let cloud_state = checkpoint.cloud_state.take();
let (
cloud,
pending_push_commit,
git_auth_configured,
pr_created,
pr_url,
pr_number,
push_count,
push_retry_count,
last_push_error,
unpushed_commits,
last_pushed_commit,
) = cloud_state.as_ref().map_or_else(
|| {
(
crate::config::CloudStateConfig::disabled(),
None,
false,
false,
None,
None,
0,
0,
None,
Vec::new(),
None,
)
},
|cs| {
let git_auth_configured = match &cs.cloud.git_remote.auth_method {
crate::config::GitAuthStateMethod::SshKey { key_path }
if key_path.is_some() =>
{
false
}
_ => cs.git_auth_configured,
};
(
cs.cloud.clone(),
cs.pending_push_commit.clone(),
git_auth_configured,
cs.pr_created,
cs.pr_url.clone(),
cs.pr_number,
cs.push_count,
cs.push_retry_count,
cs.last_push_error.clone(),
cs.unpushed_commits.clone(),
cs.last_pushed_commit.clone(),
)
},
);
let bounded_steps =
bound_execution_history_steps(execution_history_steps, execution_history_limit);
let execution_history = if bounded_steps.is_empty() {
BoundedExecutionHistory::new()
} else {
BoundedExecutionHistory::new().with_replaced(
bounded_steps,
execution_history_limit,
)
};
Self {
phase: map_checkpoint_phase(checkpoint.phase),
previous_phase: None,
iteration: checkpoint.iteration,
total_iterations: checkpoint.total_iterations,
reviewer_pass: checkpoint.reviewer_pass,
total_reviewer_passes: checkpoint.total_reviewer_passes,
review_issues_found: false,
planning_prompt_prepared_iteration: None,
planning_required_files_cleaned_iteration: None,
planning_agent_invoked_iteration: None,
planning_xml_extracted_iteration: None,
planning_validated_outcome: None,
planning_markdown_written_iteration: None,
planning_xml_archived_iteration: None,
development_context_prepared_iteration: None,
development_prompt_prepared_iteration: None,
development_required_files_cleaned_iteration: None,
development_agent_invoked_iteration: None,
analysis_agent_invoked_iteration: None,
development_xml_extracted_iteration: None,
development_validated_outcome: None,
development_xml_archived_iteration: None,
review_context_prepared_pass: None,
review_prompt_prepared_pass: None,
review_required_files_cleaned_pass: None,
review_agent_invoked_pass: None,
review_issues_xml_extracted_pass: None,
review_validated_outcome: None,
review_issues_markdown_written_pass: None,
review_issue_snippets_extracted_pass: None,
review_issues_xml_archived_pass: None,
fix_prompt_prepared_pass: None,
fix_required_files_cleaned_pass: None,
fix_agent_invoked_pass: None,
fix_analysis_agent_invoked_pass: None,
fix_result_xml_extracted_pass: None,
fix_validated_outcome: None,
fix_result_xml_archived_pass: None,
commit_prompt_prepared: false,
commit_diff_prepared: false,
commit_diff_empty: false,
commit_diff_content_id_sha256: None,
commit_agent_invoked: false,
commit_required_files_cleaned: false,
commit_xml_extracted: false,
commit_validated_outcome: None,
commit_xml_archived: false,
commit_selected_files: checkpoint.commit_selected_files.clone(),
commit_excluded_files: checkpoint.commit_excluded_files.clone(),
commit_residual_retry_pass: if checkpoint.commit_residual_retry_pass > 0 {
checkpoint.commit_residual_retry_pass
} else if checkpoint.commit_is_second_pass {
2
} else {
0
},
max_commit_residual_retries: 10,
commit_residual_files: checkpoint.commit_residual_files.clone(),
context_cleaned: false,
agent_chain,
rebase: rebase_state,
commit: CommitState::NotStarted,
execution_history,
checkpoint_saved_count: 0,
continuation: ContinuationState::new(),
connectivity: ConnectivityState::default(),
dev_fix_triggered: false,
dev_fix_attempt_count: checkpoint.dev_fix_attempt_count,
recovery_epoch: checkpoint.recovery_epoch,
recovery_escalation_level: checkpoint.recovery_escalation_level,
failed_phase_for_recovery: checkpoint.failed_phase_for_recovery,
completion_marker_pending: false,
completion_marker_is_failure: false,
completion_marker_reason: None,
gitignore_entries_ensured: false,
prompt_inputs: checkpoint.prompt_inputs.unwrap_or_default(),
prompt_permissions: checkpoint.prompt_permissions,
last_substitution_log,
template_validation_failed,
template_validation_unsubstituted,
metrics: {
let continuation = ContinuationState::new();
RunMetrics {
dev_iterations_completed: checkpoint.actual_developer_runs,
review_passes_completed: checkpoint.actual_reviewer_runs,
max_dev_iterations: checkpoint.total_iterations,
max_review_passes: checkpoint.total_reviewer_passes,
max_xsd_retry_count: continuation.max_xsd_retry_count,
max_dev_continuation_count: continuation.max_continue_count,
max_fix_continuation_count: continuation.max_fix_continue_count,
max_same_agent_retry_count: continuation.max_same_agent_retry_count,
..RunMetrics::default()
}
},
interrupted_by_user: checkpoint.interrupted_by_user,
termination_resume_phase: None,
pre_termination_commit_checked: false,
cloud,
pending_push_commit,
git_auth_configured,
pr_created,
pr_url,
push_count,
push_retry_count,
last_push_error,
unpushed_commits,
last_pushed_commit,
pr_number,
prompt_history: checkpoint
.prompt_history
.unwrap_or_default()
.into_iter()
.collect(),
}
}
}
impl From<PipelineCheckpoint> for PipelineState {
fn from(checkpoint: PipelineCheckpoint) -> Self {
let limit = crate::config::Config::default().execution_history_limit;
Self::from_checkpoint_with_execution_history_limit(checkpoint, limit)
}
}
const fn map_checkpoint_phase(phase: CheckpointPhase) -> PipelinePhase {
match phase {
CheckpointPhase::Rebase | CheckpointPhase::Planning | CheckpointPhase::PreRebase => {
PipelinePhase::Planning
}
CheckpointPhase::Development => PipelinePhase::Development,
CheckpointPhase::Review => PipelinePhase::Review,
CheckpointPhase::CommitMessage
| CheckpointPhase::PostRebase
| CheckpointPhase::PostRebaseConflict => PipelinePhase::CommitMessage,
CheckpointPhase::FinalValidation => PipelinePhase::FinalValidation,
CheckpointPhase::Complete => PipelinePhase::Complete,
CheckpointPhase::PreRebaseConflict => PipelinePhase::Planning,
CheckpointPhase::AwaitingDevFix => PipelinePhase::AwaitingDevFix,
CheckpointPhase::Interrupted => PipelinePhase::Interrupted,
}
}
fn map_checkpoint_rebase_state(rebase_state: &CheckpointRebaseState) -> RebaseState {
match rebase_state {
CheckpointRebaseState::NotStarted => RebaseState::NotStarted,
CheckpointRebaseState::PreRebaseInProgress { upstream_branch }
| CheckpointRebaseState::PostRebaseInProgress { upstream_branch } => {
RebaseState::InProgress {
original_head: "HEAD".to_string(),
target_branch: upstream_branch.clone(),
}
}
CheckpointRebaseState::PreRebaseCompleted { commit_oid }
| CheckpointRebaseState::PostRebaseCompleted { commit_oid } => RebaseState::Completed {
new_head: commit_oid.clone(),
},
CheckpointRebaseState::HasConflicts { files } => RebaseState::Conflicted {
original_head: "HEAD".to_string(),
target_branch: "main".to_string(),
files: files.iter().map(PathBuf::from).collect(),
resolution_attempts: 0,
},
CheckpointRebaseState::Failed { .. } => RebaseState::Skipped,
}
}
include!("checkpoint_conversion_tests.rs");