use crate::reviewer::{Issue, ReviewResult};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IterationContext {
pub iteration: usize,
pub existing_files: HashMap<String, FileInfo>,
pub last_review: Option<ReviewResult>,
pub pending_issues: Vec<Issue>,
pub progress_summary: String,
pub command_outputs: Vec<CommandOutput>,
pub failed_commands: Vec<FailedCommand>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FileInfo {
pub path: String,
pub language: String,
pub description: String,
pub has_issues: bool,
pub issues: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CommandOutput {
pub command: String,
pub step_id: String,
pub output: String,
pub success: bool,
pub exit_code: Option<i32>,
pub timestamp: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FailedCommand {
pub command: String,
pub step_id: String,
pub error: String,
pub failure_type: String,
pub retry_count: usize,
}
impl IterationContext {
pub fn new(iteration: usize) -> Self {
Self {
iteration,
existing_files: HashMap::new(),
last_review: None,
pending_issues: Vec::new(),
progress_summary: String::new(),
command_outputs: Vec::new(),
failed_commands: Vec::new(),
}
}
pub fn add_file(&mut self, filename: String, file_info: FileInfo) {
self.existing_files.insert(filename, file_info);
}
pub fn update_from_review(&mut self, review: ReviewResult) {
self.pending_issues = review.issues.clone();
for issue in &review.issues {
if let Some(file) = issue.location.as_ref() {
if let Some(file_info) = self.existing_files.get_mut(file) {
file_info.has_issues = true;
file_info.issues.push(issue.description.clone());
}
}
}
self.last_review = Some(review);
}
pub fn has_existing_files(&self) -> bool {
!self.existing_files.is_empty()
}
pub fn add_command_output(&mut self, output: CommandOutput) {
self.command_outputs.push(output);
}
pub fn add_failed_command(&mut self, failed: FailedCommand) {
if let Some(existing) = self.failed_commands.iter_mut()
.find(|f| f.command == failed.command && f.step_id == failed.step_id) {
existing.retry_count += 1;
existing.error = failed.error; } else {
self.failed_commands.push(failed);
}
}
pub fn has_failed_builds(&self) -> bool {
self.failed_commands.iter().any(|f| f.failure_type == "build")
}
pub fn has_failed_tests(&self) -> bool {
self.failed_commands.iter().any(|f| f.failure_type == "test")
}
pub fn get_recent_command_outputs(&self, limit: usize) -> Vec<&CommandOutput> {
let start = self.command_outputs.len().saturating_sub(limit);
self.command_outputs[start..].iter().collect()
}
}
impl fmt::Display for IterationContext {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut output = String::new();
output.push_str(&format!("Iteration #{}\n", self.iteration));
if !self.existing_files.is_empty() {
output.push_str("\nExisting files:\n");
for (name, info) in &self.existing_files {
output.push_str(&format!(" - {} ({})", name, info.language));
if info.has_issues {
output.push_str(" [HAS ISSUES]");
}
output.push('\n');
if !info.description.is_empty() {
output.push_str(&format!(" Description: {}\n", info.description));
}
for issue in &info.issues {
output.push_str(&format!(" Issue: {}\n", issue));
}
}
}
if !self.pending_issues.is_empty() {
output.push_str(&format!(
"\nPending issues ({}):\n",
self.pending_issues.len()
));
for issue in &self.pending_issues {
output.push_str(&format!(" - {}: {}\n", issue.severity, issue.description));
}
}
if let Some(review) = &self.last_review {
output.push_str(&format!("\nLast review: {}\n", review.summary));
}
if !self.failed_commands.is_empty() {
output.push_str(&format!("\nFailed commands ({}):\n", self.failed_commands.len()));
for failed in &self.failed_commands {
output.push_str(&format!(" - {} [{}] (step: {})\n",
failed.command, failed.failure_type, failed.step_id));
output.push_str(&format!(" Error: {}\n", failed.error));
if failed.retry_count > 0 {
output.push_str(&format!(" Retry count: {}\n", failed.retry_count));
}
}
}
let recent_outputs = self.get_recent_command_outputs(5);
if !recent_outputs.is_empty() {
output.push_str(&format!("\nRecent command outputs ({} total):\n",
self.command_outputs.len()));
for cmd_output in recent_outputs {
let status = if cmd_output.success { "✓" } else { "✗" };
output.push_str(&format!(" {} {} (step: {})\n",
status, cmd_output.command, cmd_output.step_id));
let truncated_output = if cmd_output.output.len() > 200 {
format!("{}...", &cmd_output.output[..200])
} else {
cmd_output.output.clone()
};
if !truncated_output.trim().is_empty() &&
!truncated_output.contains("Command:") {
output.push_str(&format!(" Output: {}\n",
truncated_output.lines().next().unwrap_or("").trim()));
}
}
}
if !self.progress_summary.is_empty() {
output.push_str(&format!("\nProgress: {}\n", self.progress_summary));
}
write!(f, "{}", output)
}
}