impl PipelineState {
const fn initial_phase_for_run_configuration(&self) -> PipelinePhase {
if self.total_iterations == 0 {
if self.total_reviewer_passes == 0 {
PipelinePhase::CommitMessage
} else {
PipelinePhase::Review
}
} else {
PipelinePhase::Planning
}
}
#[must_use]
pub const fn is_complete(&self) -> bool {
matches!(self.phase, PipelinePhase::Complete)
|| (matches!(self.phase, PipelinePhase::Interrupted)
&& (self.checkpoint_saved_count > 0
|| matches!(self.previous_phase, Some(PipelinePhase::AwaitingDevFix))))
}
#[must_use]
pub fn current_head(&self) -> String {
self.rebase
.current_head()
.unwrap_or_else(|| "HEAD".to_string())
}
#[must_use]
pub fn fix_drain_active(&self) -> bool {
self.agent_chain.current_drain == crate::agents::AgentDrain::Fix
|| self.review_issues_found
|| self.fix_prompt_prepared_pass.is_some()
|| self.fix_required_files_cleaned_pass.is_some()
|| self.fix_agent_invoked_pass.is_some()
|| self.fix_analysis_agent_invoked_pass.is_some()
|| self.fix_result_xml_extracted_pass.is_some()
|| self.fix_validated_outcome.is_some()
|| self.fix_result_xml_archived_pass.is_some()
|| self.continuation.fix_continue_pending
|| self.continuation.fix_continuation_attempt > 0
|| self.continuation.fix_status.is_some()
|| self.continuation.fix_previous_summary.is_some()
|| self.continuation.last_fix_xsd_error.is_some()
}
#[must_use]
pub fn runtime_drain(&self) -> crate::agents::AgentDrain {
match self.phase {
PipelinePhase::Planning => crate::agents::AgentDrain::Planning,
PipelinePhase::Development => {
if self.agent_chain.current_drain == crate::agents::AgentDrain::Analysis {
crate::agents::AgentDrain::Analysis
} else {
crate::agents::AgentDrain::Development
}
}
PipelinePhase::Review => {
if self.agent_chain.current_drain == crate::agents::AgentDrain::Analysis {
crate::agents::AgentDrain::Analysis
} else if self.fix_drain_active() {
crate::agents::AgentDrain::Fix
} else {
crate::agents::AgentDrain::Review
}
}
PipelinePhase::CommitMessage => crate::agents::AgentDrain::Commit,
_ => self.agent_chain.current_drain,
}
}
pub(crate) fn clear_phase_flags(&self, phase: PipelinePhase) -> Self {
match phase {
PipelinePhase::Planning => self.clear_planning_flags(),
PipelinePhase::Development => self.clear_development_flags(),
PipelinePhase::Review => self.clear_review_flags(),
PipelinePhase::CommitMessage => self.clear_commit_flags(),
_ => self.clone(),
}
}
fn clear_planning_flags(&self) -> Self {
Self {
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,
..self.clone()
}
}
fn clear_development_flags(&self) -> Self {
Self {
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,
..self.clone()
}
}
fn clear_review_flags(&self) -> Self {
Self {
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,
review_issues_found: false,
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,
..self.clone()
}
}
fn clear_commit_flags(&self) -> Self {
Self {
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: CommitState::NotStarted,
..self.clone()
}
}
pub(crate) fn reset_iteration(&self) -> Self {
let new_iteration = if self.total_iterations == 0 {
0
} else {
self.iteration.saturating_sub(1)
};
let initial_phase = self.initial_phase_for_run_configuration();
let prompt_inputs = self
.prompt_inputs
.clone()
.with_planning_cleared()
.with_development_cleared()
.with_commit_cleared()
.with_review_cleared()
.with_xsd_retry_cleared();
Self {
iteration: new_iteration,
phase: initial_phase,
context_cleaned: false,
gitignore_entries_ensured: false,
prompt_inputs,
continuation: self.continuation.clone().reset(),
..self.clone()
}
.clear_planning_flags()
.clear_development_flags()
.clear_commit_flags()
.clear_review_flags()
}
pub(crate) fn reset_to_iteration_zero(&self) -> Self {
let initial_phase = self.initial_phase_for_run_configuration();
let prompt_inputs = self
.prompt_inputs
.clone()
.with_planning_cleared()
.with_development_cleared()
.with_commit_cleared()
.with_review_cleared()
.with_xsd_retry_cleared();
Self {
iteration: 0,
phase: initial_phase,
context_cleaned: false,
gitignore_entries_ensured: false,
prompt_inputs,
continuation: self.continuation.clone().reset(),
..self.clone()
}
.clear_planning_flags()
.clear_development_flags()
.clear_commit_flags()
.clear_review_flags()
}
}
#[cfg(test)]
mod helper_tests {
use super::*;
use crate::reducer::state::{
CommitState, ContinuationState, MaterializedCommitInputs, MaterializedDevelopmentInputs,
MaterializedPlanningInputs, MaterializedPromptInput, PromptInputKind,
PromptInputRepresentation, PromptInputsState, PromptMaterializationReason,
};
fn mp(kind: PromptInputKind) -> MaterializedPromptInput {
MaterializedPromptInput {
kind,
content_id_sha256: "id".to_string(),
consumer_signature_sha256: "sig".to_string(),
original_bytes: 1,
final_bytes: 1,
model_budget_bytes: None,
inline_budget_bytes: None,
representation: PromptInputRepresentation::Inline,
reason: PromptMaterializationReason::WithinBudgets,
}
}
#[test]
fn reset_iteration_resets_global_phase_start_prereqs() {
let state = PipelineState::initial(2, 0);
let state = PipelineState {
iteration: 1,
context_cleaned: true,
gitignore_entries_ensured: true,
continuation: ContinuationState {
same_agent_retry_pending: true,
same_agent_retry_count: 2,
..Default::default()
},
prompt_inputs: PromptInputsState {
planning: Some(MaterializedPlanningInputs {
iteration: 1,
prompt: mp(PromptInputKind::Prompt),
}),
development: Some(MaterializedDevelopmentInputs {
iteration: 1,
prompt: mp(PromptInputKind::Prompt),
plan: mp(PromptInputKind::Plan),
}),
commit: Some(MaterializedCommitInputs {
attempt: 1,
diff: mp(PromptInputKind::Diff),
}),
review: None,
xsd_retry_last_output: None,
},
..state
};
let reset = state.reset_iteration();
assert!(!reset.context_cleaned);
assert!(!reset.gitignore_entries_ensured);
assert!(!reset.continuation.same_agent_retry_pending);
assert_eq!(reset.continuation.same_agent_retry_count, 0);
assert!(reset.prompt_inputs.planning.is_none());
assert!(reset.prompt_inputs.development.is_none());
assert!(reset.prompt_inputs.commit.is_none());
}
#[test]
fn reset_to_iteration_zero_resets_global_phase_start_prereqs() {
let state = PipelineState::initial(2, 0);
let state = PipelineState {
iteration: 2,
context_cleaned: true,
gitignore_entries_ensured: true,
prompt_inputs: PromptInputsState {
planning: Some(MaterializedPlanningInputs {
iteration: 2,
prompt: mp(PromptInputKind::Prompt),
}),
..Default::default()
},
..state
};
let reset = state.reset_to_iteration_zero();
assert_eq!(reset.iteration, 0);
assert!(!reset.context_cleaned);
assert!(!reset.gitignore_entries_ensured);
assert!(reset.prompt_inputs.planning.is_none());
}
#[test]
fn clear_commit_flags_resets_commit_state_machine() {
let state = PipelineState::initial(1, 0);
let state = PipelineState {
commit: CommitState::Generated {
message: "stale".to_string(),
},
commit_prompt_prepared: true,
commit_diff_prepared: true,
..state
};
let reset = state.clear_phase_flags(PipelinePhase::CommitMessage);
assert!(matches!(reset.commit, CommitState::NotStarted));
assert!(!reset.commit_prompt_prepared);
assert!(!reset.commit_diff_prepared);
}
#[test]
fn reset_iteration_clears_review_and_fix_flags() {
let state = PipelineState::initial(2, 0);
let state = PipelineState {
iteration: 1,
review_issues_found: true,
review_context_prepared_pass: Some(1),
fix_prompt_prepared_pass: Some(1),
fix_agent_invoked_pass: Some(1),
..state
};
let reset = state.reset_iteration();
assert!(!reset.review_issues_found);
assert!(reset.review_context_prepared_pass.is_none());
assert!(reset.fix_prompt_prepared_pass.is_none());
assert!(reset.fix_agent_invoked_pass.is_none());
}
#[test]
fn reset_to_iteration_zero_clears_review_and_fix_flags() {
let state = PipelineState::initial(2, 0);
let state = PipelineState {
iteration: 2,
review_issues_found: true,
review_agent_invoked_pass: Some(2),
fix_result_xml_extracted_pass: Some(2),
..state
};
let reset = state.reset_to_iteration_zero();
assert_eq!(reset.iteration, 0);
assert!(!reset.review_issues_found);
assert!(reset.review_agent_invoked_pass.is_none());
assert!(reset.fix_result_xml_extracted_pass.is_none());
}
#[test]
fn iteration_resets_restart_at_initial_phase_for_run_configuration() {
let state = PipelineState::initial(0, 2);
let reset = state.reset_to_iteration_zero();
assert_eq!(reset.phase, PipelinePhase::Review);
let state = PipelineState::initial(0, 0);
let reset = state.reset_to_iteration_zero();
assert_eq!(reset.phase, PipelinePhase::CommitMessage);
let state = PipelineState::initial(0, 2);
let reset = state.reset_iteration();
assert_eq!(reset.phase, PipelinePhase::Review);
let state = PipelineState::initial(0, 0);
let reset = state.reset_iteration();
assert_eq!(reset.phase, PipelinePhase::CommitMessage);
}
}