1use crate::agents::AgentRole;
11use crate::checkpoint::restore::ResumeContext;
12use crate::checkpoint::{save_checkpoint, CheckpointBuilder, PipelinePhase};
13use crate::files::llm_output_extraction::xsd_validation::XsdValidationError;
14use crate::files::llm_output_extraction::{
15 extract_development_result_xml, extract_plan_xml, format_xml_for_display,
16 validate_development_result_xml, validate_plan_xml, PlanElements,
17};
18use crate::files::{delete_plan_file, update_status};
19use crate::git_helpers::{git_snapshot, CommitResultFallback};
20use crate::logger::print_progress;
21use crate::phases::commit::commit_with_generated_message;
22use crate::phases::get_primary_commit_agent;
23use crate::phases::integrity::ensure_prompt_integrity;
24use crate::pipeline::{run_with_fallback, PipelineRuntime};
25use crate::prompts::{
26 get_stored_or_generate_prompt, prompt_developer_iteration_xml_with_context,
27 prompt_developer_iteration_xsd_retry_with_context, prompt_planning_xml_with_context,
28 prompt_planning_xsd_retry_with_context, ContextLevel,
29};
30use std::fs;
31use std::path::Path;
32use std::process::Command;
33
34use super::context::PhaseContext;
35
36use crate::checkpoint::execution_history::{ExecutionStep, StepOutcome};
37
38use std::time::Instant;
39
40pub struct DevelopmentResult {
42 pub had_errors: bool,
44}
45
46pub fn run_development_phase(
63 ctx: &mut PhaseContext<'_>,
64 start_iter: u32,
65 resume_context: Option<&ResumeContext>,
66) -> anyhow::Result<DevelopmentResult> {
67 let mut had_errors = false;
68 let mut prev_snap = git_snapshot()?;
69 let developer_context = ContextLevel::from(ctx.config.developer_context);
70
71 for i in start_iter..=ctx.config.developer_iters {
72 ctx.logger.subheader(&format!(
73 "Iteration {} of {}",
74 i, ctx.config.developer_iters
75 ));
76 print_progress(i, ctx.config.developer_iters, "Overall");
77
78 let resuming_into_development = resume_context.is_some() && i == start_iter;
79
80 if resuming_into_development {
82 ctx.logger
83 .info("Resuming at development step; skipping plan generation");
84 } else {
85 run_planning_step(ctx, i)?;
86 }
87
88 let plan_ok = verify_plan_exists(ctx, i, resuming_into_development)?;
90 if !plan_ok {
91 anyhow::bail!("Planning phase did not create a non-empty .agent/PLAN.md");
92 }
93 ctx.logger.success("PLAN.md created");
94
95 if ctx.config.features.checkpoint_enabled {
97 let builder = CheckpointBuilder::new()
98 .phase(PipelinePhase::Development, i, ctx.config.developer_iters)
99 .reviewer_pass(0, ctx.config.reviewer_reviews)
100 .capture_from_context(
101 ctx.config,
102 ctx.registry,
103 ctx.developer_agent,
104 ctx.reviewer_agent,
105 ctx.logger,
106 &ctx.run_context,
107 )
108 .with_execution_history(ctx.execution_history.clone())
109 .with_prompt_history(ctx.clone_prompt_history());
110
111 if let Some(checkpoint) = builder.build() {
112 let _ = save_checkpoint(&checkpoint);
113 }
114 }
115
116 ctx.record_developer_iteration();
118
119 ctx.logger.info("Executing plan...");
121 update_status("Starting development iteration", ctx.config.isolation_mode)?;
122
123 let dev_result = run_development_iteration_with_xml_retry(
125 ctx,
126 i,
127 developer_context,
128 resuming_into_development,
129 resume_context,
130 )?;
131
132 if dev_result.had_error {
133 ctx.logger.error(&format!(
134 "Iteration {i} encountered an error but continuing"
135 ));
136 had_errors = true;
137 }
138
139 ctx.stats.developer_runs_completed += 1;
141
142 {
144 let dev_start_time = Instant::now(); let duration = dev_start_time.elapsed().as_secs();
146 let outcome = if dev_result.had_error {
147 StepOutcome::failure("Agent exited with non-zero code".to_string(), true)
148 } else {
149 StepOutcome::success(
150 dev_result.summary.clone(),
151 dev_result.files_changed.clone().unwrap_or_default(),
152 )
153 };
154 let step = ExecutionStep::new("Development", i, "dev_run", outcome)
155 .with_agent(ctx.developer_agent)
156 .with_duration(duration);
157 ctx.execution_history.add_step(step);
158 }
159 update_status("Completed progress step", ctx.config.isolation_mode)?;
160
161 if let Some(ref summary) = dev_result.summary {
163 ctx.logger
164 .info(&format!("Development summary: {}", summary));
165 }
166
167 let snap = git_snapshot()?;
168 if snap == prev_snap {
169 if snap.is_empty() {
170 ctx.logger
171 .warn("No git-status change detected (repository is clean)");
172 } else {
173 ctx.logger.warn(&format!(
174 "No git-status change detected (existing changes: {})",
175 snap.lines().count()
176 ));
177 }
178 } else {
179 ctx.logger.success(&format!(
180 "Repository modified ({} file(s) changed)",
181 snap.lines().count()
182 ));
183 ctx.stats.changes_detected += 1;
184 handle_commit_after_development(ctx, i)?;
185 }
186 prev_snap = snap;
187
188 if let Some(ref fast_cmd) = ctx.config.fast_check_cmd {
190 run_fast_check(ctx, fast_cmd, i)?;
191 }
192
193 ensure_prompt_integrity(ctx.logger, "development", i);
196
197 ctx.logger.info("Deleting PLAN.md...");
199 if let Err(err) = delete_plan_file() {
200 ctx.logger.warn(&format!("Failed to delete PLAN.md: {err}"));
201 }
202 ctx.logger.success("PLAN.md deleted");
203
204 if ctx.config.features.checkpoint_enabled {
207 let next_iteration = i + 1;
208 let builder = CheckpointBuilder::new()
209 .phase(
210 PipelinePhase::Development,
211 next_iteration,
212 ctx.config.developer_iters,
213 )
214 .reviewer_pass(0, ctx.config.reviewer_reviews)
215 .capture_from_context(
216 ctx.config,
217 ctx.registry,
218 ctx.developer_agent,
219 ctx.reviewer_agent,
220 ctx.logger,
221 &ctx.run_context,
222 )
223 .with_execution_history(ctx.execution_history.clone())
224 .with_prompt_history(ctx.clone_prompt_history());
225
226 if let Some(checkpoint) = builder.build() {
227 let _ = save_checkpoint(&checkpoint);
228 }
229 }
230 }
231
232 Ok(DevelopmentResult { had_errors })
233}
234
235struct DevIterationResult {
237 had_error: bool,
239 summary: Option<String>,
241 files_changed: Option<Vec<String>>,
243}
244
245fn run_development_iteration_with_xml_retry(
260 ctx: &mut PhaseContext<'_>,
261 iteration: u32,
262 _developer_context: ContextLevel,
263 _resuming_into_development: bool,
264 _resume_context: Option<&ResumeContext>,
265) -> anyhow::Result<DevIterationResult> {
266 let prompt_md = fs::read_to_string("PROMPT.md").unwrap_or_default();
267 let plan_md = fs::read_to_string(".agent/PLAN.md").unwrap_or_default();
268 let log_dir = format!(".agent/logs/developer_{iteration}");
269
270 let max_xsd_retries = 10;
271 let max_continuations = 100; let mut final_summary: Option<String> = None;
273 let mut final_files_changed: Option<Vec<String>> = None;
274 let mut had_any_error = false;
275
276 'continuation: for continuation_num in 0..max_continuations {
278 let is_continuation = continuation_num > 0;
279 if is_continuation {
280 ctx.logger.info(&format!(
281 "Continuation {} of {} (status was not 'completed')",
282 continuation_num, max_continuations
283 ));
284 }
285
286 let mut xsd_error: Option<String> = None;
287
288 for retry_num in 0..max_xsd_retries {
290 let is_retry = retry_num > 0;
291 let total_attempts = continuation_num * max_xsd_retries + retry_num + 1;
292
293 let dev_prompt = if !is_retry && !is_continuation {
296 let prompt_key = format!("development_{}", iteration);
298 let (prompt, was_replayed) =
299 get_stored_or_generate_prompt(&prompt_key, &ctx.prompt_history, || {
300 prompt_developer_iteration_xml_with_context(
301 ctx.template_context,
302 &prompt_md,
303 &plan_md,
304 )
305 });
306
307 if !was_replayed {
308 ctx.capture_prompt(&prompt_key, &prompt);
309 } else {
310 ctx.logger.info(&format!(
311 "Using stored prompt from checkpoint for determinism: {}",
312 prompt_key
313 ));
314 }
315
316 prompt
317 } else if !is_continuation {
318 ctx.logger.info(&format!(
320 " In-session retry {}/{} for XSD validation (total attempt: {})",
321 retry_num,
322 max_xsd_retries - 1,
323 total_attempts
324 ));
325 if let Some(ref error) = xsd_error {
326 ctx.logger.info(&format!(" XSD error: {}", error));
327 }
328
329 let last_output = read_last_development_output(Path::new(&log_dir));
330
331 prompt_developer_iteration_xsd_retry_with_context(
332 ctx.template_context,
333 &prompt_md,
334 &plan_md,
335 xsd_error.as_deref().unwrap_or("Unknown error"),
336 &last_output,
337 )
338 } else if !is_retry {
339 ctx.logger.info(&format!(
341 " Continuation attempt {} (XSD validation attempt {}/{})",
342 total_attempts, 1, max_xsd_retries
343 ));
344
345 prompt_developer_iteration_xml_with_context(
346 ctx.template_context,
347 &prompt_md,
348 &plan_md,
349 )
350 } else {
351 ctx.logger.info(&format!(
353 " Continuation retry {}/{} for XSD validation (total attempt: {})",
354 retry_num,
355 max_xsd_retries - 1,
356 total_attempts
357 ));
358 if let Some(ref error) = xsd_error {
359 ctx.logger.info(&format!(" XSD error: {}", error));
360 }
361
362 let last_output = read_last_development_output(Path::new(&log_dir));
363
364 prompt_developer_iteration_xsd_retry_with_context(
365 ctx.template_context,
366 &prompt_md,
367 &plan_md,
368 xsd_error.as_deref().unwrap_or("Unknown error"),
369 &last_output,
370 )
371 };
372
373 let exit_code = {
375 let mut runtime = PipelineRuntime {
376 timer: ctx.timer,
377 logger: ctx.logger,
378 colors: ctx.colors,
379 config: ctx.config,
380 #[cfg(any(test, feature = "test-utils"))]
381 agent_executor: None,
382 };
383 run_with_fallback(
384 AgentRole::Developer,
385 &format!(
386 "run #{}{}",
387 iteration,
388 if is_continuation {
389 format!(" (continuation {})", continuation_num)
390 } else {
391 String::new()
392 }
393 ),
394 &dev_prompt,
395 &log_dir,
396 &mut runtime,
397 ctx.registry,
398 ctx.developer_agent,
399 )?
400 };
401
402 if exit_code != 0 {
404 had_any_error = true;
405 }
406
407 let log_dir_path = Path::new(&log_dir);
409 let dev_content = read_last_development_output(log_dir_path);
410
411 let xml_to_validate =
414 if let Some(xml_content) = extract_development_result_xml(&dev_content) {
415 xml_content
416 } else {
417 dev_content.clone()
420 };
421
422 match validate_development_result_xml(&xml_to_validate) {
424 Ok(result_elements) => {
425 let formatted_xml = format_xml_for_display(&xml_to_validate);
427
428 if is_retry {
429 ctx.logger
430 .success(&format!("Status validated after {} retries", retry_num));
431 } else {
432 ctx.logger.success("Status extracted and validated (XML)");
433 }
434
435 ctx.logger.info(&format!("\n{}", formatted_xml));
437
438 final_summary = Some(result_elements.summary.clone());
440 final_files_changed = result_elements
441 .files_changed
442 .as_ref()
443 .map(|f| f.lines().map(|s| s.to_string()).collect());
444
445 if result_elements.is_completed() {
447 return Ok(DevIterationResult {
449 had_error: had_any_error,
450 summary: final_summary,
451 files_changed: final_files_changed,
452 });
453 } else if result_elements.is_partial() {
454 ctx.logger
456 .info("Status is 'partial' - continuing with same iteration");
457 continue 'continuation;
458 } else if result_elements.is_failed() {
459 ctx.logger
461 .warn("Status is 'failed' - continuing with same iteration");
462 continue 'continuation;
463 }
464 }
465 Err(xsd_err) => {
466 let error_msg = format_xsd_error(&xsd_err);
468 ctx.logger
469 .warn(&format!(" XSD validation failed: {}", error_msg));
470
471 if retry_num < max_xsd_retries - 1 {
472 xsd_error = Some(error_msg);
474 continue;
476 } else {
477 ctx.logger
478 .warn(" No more in-session XSD retries remaining");
479 break 'continuation;
481 }
482 }
483 }
484 }
485
486 ctx.logger
488 .warn("XSD retry loop exhausted - stopping continuation");
489 break;
490 }
491
492 Ok(DevIterationResult {
494 had_error: had_any_error,
495 summary: final_summary.or_else(|| {
496 Some(format!(
497 "Continuation stopped after {} attempts",
498 max_continuations * max_xsd_retries
499 ))
500 }),
501 files_changed: final_files_changed,
502 })
503}
504
505fn run_planning_step(ctx: &mut PhaseContext<'_>, iteration: u32) -> anyhow::Result<()> {
510 let start_time = Instant::now();
511 if ctx.config.features.checkpoint_enabled {
513 let builder = CheckpointBuilder::new()
514 .phase(
515 PipelinePhase::Planning,
516 iteration,
517 ctx.config.developer_iters,
518 )
519 .reviewer_pass(0, ctx.config.reviewer_reviews)
520 .capture_from_context(
521 ctx.config,
522 ctx.registry,
523 ctx.developer_agent,
524 ctx.reviewer_agent,
525 ctx.logger,
526 &ctx.run_context,
527 )
528 .with_execution_history(ctx.execution_history.clone())
529 .with_prompt_history(ctx.clone_prompt_history());
530
531 if let Some(checkpoint) = builder.build() {
532 let _ = save_checkpoint(&checkpoint);
533 }
534 }
535
536 ctx.logger.info("Creating plan from PROMPT.md...");
537 update_status("Starting planning phase", ctx.config.isolation_mode)?;
538
539 let prompt_md_content = std::fs::read_to_string("PROMPT.md").ok();
543
544 let prompt_key = format!("planning_{}", iteration);
547 let prompt_md_str = prompt_md_content.as_deref().unwrap_or("");
548
549 let (plan_prompt, was_replayed) =
551 get_stored_or_generate_prompt(&prompt_key, &ctx.prompt_history, || {
552 prompt_planning_xml_with_context(ctx.template_context, Some(prompt_md_str))
553 });
554
555 if !was_replayed {
557 ctx.capture_prompt(&prompt_key, &plan_prompt);
558 } else {
559 ctx.logger.info(&format!(
560 "Using stored prompt from checkpoint for determinism: {}",
561 prompt_key
562 ));
563 }
564
565 let log_dir = format!(".agent/logs/planning_{iteration}");
566 let plan_path = Path::new(".agent/PLAN.md");
567
568 if let Some(parent) = plan_path.parent() {
570 fs::create_dir_all(parent)?;
571 }
572
573 let max_retries = 10;
575 let mut xsd_error: Option<String> = None;
576
577 for retry_num in 0..max_retries {
578 let plan_prompt = if retry_num == 0 {
581 plan_prompt.clone()
582 } else {
583 ctx.logger.info(&format!(
584 " In-session retry {}/{} for XSD validation",
585 retry_num,
586 max_retries - 1
587 ));
588 if let Some(ref error) = xsd_error {
589 ctx.logger.info(&format!(" XSD error: {}", error));
590 }
591
592 let last_output = read_last_planning_output(Path::new(&log_dir));
594
595 prompt_planning_xsd_retry_with_context(
596 ctx.template_context,
597 prompt_md_str,
598 xsd_error.as_deref().unwrap_or("Unknown error"),
599 &last_output,
600 )
601 };
602
603 let mut runtime = PipelineRuntime {
604 timer: ctx.timer,
605 logger: ctx.logger,
606 colors: ctx.colors,
607 config: ctx.config,
608 #[cfg(any(test, feature = "test-utils"))]
609 agent_executor: None,
610 };
611
612 let _exit_code = run_with_fallback(
613 AgentRole::Developer,
614 &format!("planning #{}", iteration),
615 &plan_prompt,
616 &log_dir,
617 &mut runtime,
618 ctx.registry,
619 ctx.developer_agent,
620 )?;
621
622 let log_dir_path = Path::new(&log_dir);
624 let plan_content = read_last_planning_output(log_dir_path);
625
626 let xml_to_validate = if let Some(xml_content) = extract_plan_xml(&plan_content) {
629 xml_content
630 } else {
631 plan_content.clone()
634 };
635
636 match validate_plan_xml(&xml_to_validate) {
638 Ok(plan_elements) => {
639 let formatted_xml = format_xml_for_display(&xml_to_validate);
641
642 let markdown = format_plan_as_markdown(&plan_elements);
644 fs::write(plan_path, &markdown)?;
645
646 if retry_num > 0 {
647 ctx.logger
648 .success(&format!("Plan validated after {} retries", retry_num));
649 } else {
650 ctx.logger.success("Plan extracted and validated (XML)");
651 }
652
653 ctx.logger.info(&format!("\n{}", formatted_xml));
655
656 {
658 let duration = start_time.elapsed().as_secs();
659 let step = ExecutionStep::new(
660 "Planning",
661 iteration,
662 "plan_generation",
663 StepOutcome::success(None, vec![".agent/PLAN.md".to_string()]),
664 )
665 .with_agent(ctx.developer_agent)
666 .with_duration(duration);
667 ctx.execution_history.add_step(step);
668 }
669
670 return Ok(());
671 }
672 Err(xsd_err) => {
673 let error_msg = format_xsd_error(&xsd_err);
675 ctx.logger
676 .warn(&format!(" XSD validation failed: {}", error_msg));
677
678 if retry_num < max_retries - 1 {
679 xsd_error = Some(error_msg);
681 continue;
683 } else {
684 ctx.logger
685 .error(" No more in-session XSD retries remaining");
686 let placeholder = "# Plan\n\nAgent produced no valid XML output. Only XML format is accepted.\n";
688 fs::write(plan_path, placeholder)?;
689 anyhow::bail!(
690 "Planning agent did not produce valid XML output after {} attempts",
691 max_retries
692 );
693 }
694 }
695 }
696 }
697
698 {
700 let duration = start_time.elapsed().as_secs();
701 let step = ExecutionStep::new(
702 "Planning",
703 iteration,
704 "plan_generation",
705 StepOutcome::failure("No valid XML output produced".to_string(), false),
706 )
707 .with_agent(ctx.developer_agent)
708 .with_duration(duration);
709 ctx.execution_history.add_step(step);
710 }
711
712 anyhow::bail!("Planning failed after {} XSD retry attempts", max_retries)
713}
714
715fn read_last_planning_output(log_prefix: &Path) -> String {
721 let parent = log_prefix.parent().unwrap_or(Path::new("."));
726 let prefix_str = log_prefix
727 .file_name()
728 .and_then(|s| s.to_str())
729 .unwrap_or("");
730
731 let mut best_file: Option<(std::path::PathBuf, std::time::SystemTime)> = None;
733
734 if let Ok(entries) = fs::read_dir(parent) {
735 for entry in entries.flatten() {
736 let path = entry.path();
737 if let Some(filename) = path.file_name().and_then(|s| s.to_str()) {
738 if filename.starts_with(prefix_str)
740 && filename.len() > prefix_str.len()
741 && filename.ends_with(".log")
742 {
743 if let Ok(metadata) = fs::metadata(&path) {
745 if let Ok(modified) = metadata.modified() {
746 match &best_file {
747 None => best_file = Some((path.clone(), modified)),
748 Some((_, best_time)) if modified > *best_time => {
749 best_file = Some((path.clone(), modified));
750 }
751 _ => {}
752 }
753 }
754 }
755 }
756 }
757 }
758 }
759
760 if let Some((path, _)) = best_file {
762 if let Ok(content) = fs::read_to_string(&path) {
763 return content;
764 }
765 }
766
767 String::new()
768}
769
770fn read_last_development_output(log_prefix: &Path) -> String {
776 let parent = log_prefix.parent().unwrap_or(Path::new("."));
781 let prefix_str = log_prefix
782 .file_name()
783 .and_then(|s| s.to_str())
784 .unwrap_or("");
785
786 let mut best_file: Option<(std::path::PathBuf, std::time::SystemTime)> = None;
788
789 if let Ok(entries) = fs::read_dir(parent) {
790 for entry in entries.flatten() {
791 let path = entry.path();
792 if let Some(filename) = path.file_name().and_then(|s| s.to_str()) {
793 if filename.starts_with(prefix_str)
795 && filename.len() > prefix_str.len()
796 && filename.ends_with(".log")
797 {
798 if let Ok(metadata) = fs::metadata(&path) {
800 if let Ok(modified) = metadata.modified() {
801 match &best_file {
802 None => best_file = Some((path.clone(), modified)),
803 Some((_, best_time)) if modified > *best_time => {
804 best_file = Some((path.clone(), modified));
805 }
806 _ => {}
807 }
808 }
809 }
810 }
811 }
812 }
813 }
814
815 if let Some((path, _)) = best_file {
817 if let Ok(content) = fs::read_to_string(&path) {
818 return content;
819 }
820 }
821
822 String::new()
823}
824
825fn format_xsd_error(error: &XsdValidationError) -> String {
827 format!(
828 "{} - expected: {}, found: {}",
829 error.element_path, error.expected, error.found
830 )
831}
832
833fn format_plan_as_markdown(elements: &PlanElements) -> String {
835 let mut result = String::new();
836
837 result.push_str("## Summary\n\n");
838 result.push_str(&elements.summary);
839 result.push_str("\n\n");
840
841 result.push_str("## Implementation Steps\n\n");
842 result.push_str(&elements.implementation_steps);
843 result.push_str("\n\n");
844
845 if let Some(ref critical_files) = elements.critical_files {
846 result.push_str("## Critical Files for Implementation\n\n");
847 result.push_str(critical_files);
848 result.push_str("\n\n");
849 }
850
851 if let Some(ref risks) = elements.risks_mitigations {
852 result.push_str("## Risks & Mitigations\n\n");
853 result.push_str(risks);
854 result.push_str("\n\n");
855 }
856
857 if let Some(ref verification) = elements.verification_strategy {
858 result.push_str("## Verification Strategy\n\n");
859 result.push_str(verification);
860 result.push_str("\n\n");
861 }
862
863 result
864}
865
866fn verify_plan_exists(
873 ctx: &mut PhaseContext<'_>,
874 iteration: u32,
875 resuming_into_development: bool,
876) -> anyhow::Result<bool> {
877 let plan_path = Path::new(".agent/PLAN.md");
878
879 let plan_ok = plan_path
880 .exists()
881 .then(|| fs::read_to_string(plan_path).ok())
882 .flatten()
883 .is_some_and(|s| !s.trim().is_empty());
884
885 if !plan_ok && resuming_into_development {
887 ctx.logger
888 .warn("Missing .agent/PLAN.md; rerunning plan generation to recover");
889 run_planning_step(ctx, iteration)?;
890
891 let plan_ok = plan_path
893 .exists()
894 .then(|| fs::read_to_string(plan_path).ok())
895 .flatten()
896 .is_some_and(|s| !s.trim().is_empty());
897
898 return Ok(plan_ok);
899 }
900
901 Ok(plan_ok)
902}
903
904fn run_fast_check(ctx: &PhaseContext<'_>, fast_cmd: &str, iteration: u32) -> anyhow::Result<()> {
906 let argv = crate::common::split_command(fast_cmd)
907 .map_err(|e| anyhow::anyhow!("FAST_CHECK_CMD parse error (iteration {iteration}): {e}"))?;
908 if argv.is_empty() {
909 ctx.logger
910 .warn("FAST_CHECK_CMD is empty; skipping fast check");
911 return Ok(());
912 }
913
914 let display_cmd = crate::common::format_argv_for_log(&argv);
915 ctx.logger.info(&format!(
916 "Running fast check: {}{}{}",
917 ctx.colors.dim(),
918 display_cmd,
919 ctx.colors.reset()
920 ));
921
922 let Some((program, cmd_args)) = argv.split_first() else {
923 ctx.logger
924 .warn("FAST_CHECK_CMD is empty after parsing; skipping fast check");
925 return Ok(());
926 };
927 let status = Command::new(program).args(cmd_args).status()?;
928
929 if status.success() {
930 ctx.logger.success("Fast check passed");
931 } else {
932 ctx.logger.warn("Fast check had issues (non-blocking)");
933 }
934
935 Ok(())
936}
937
938fn handle_commit_after_development(
944 ctx: &mut PhaseContext<'_>,
945 iteration: u32,
946) -> anyhow::Result<()> {
947 let start_time = Instant::now();
948 let commit_agent = get_primary_commit_agent(ctx);
950
951 if let Some(agent) = commit_agent {
952 ctx.logger.info(&format!(
953 "Creating commit with auto-generated message (agent: {agent})..."
954 ));
955
956 let diff = match crate::git_helpers::git_diff() {
958 Ok(d) => d,
959 Err(e) => {
960 ctx.logger
961 .error(&format!("Failed to get diff for commit: {e}"));
962 return Err(anyhow::anyhow!(e));
963 }
964 };
965
966 let git_name = ctx.config.git_user_name.as_deref();
968 let git_email = ctx.config.git_user_email.as_deref();
969
970 let result = commit_with_generated_message(&diff, &agent, git_name, git_email, ctx);
971
972 match result {
973 CommitResultFallback::Success(oid) => {
974 ctx.logger
975 .success(&format!("Commit created successfully: {oid}"));
976 ctx.stats.commits_created += 1;
977
978 {
979 let duration = start_time.elapsed().as_secs();
980 let step = ExecutionStep::new(
981 "Development",
982 iteration,
983 "commit",
984 StepOutcome::success(Some(oid.to_string()), vec![]),
985 )
986 .with_agent(&agent)
987 .with_duration(duration);
988 ctx.execution_history.add_step(step);
989 }
990 }
991 CommitResultFallback::NoChanges => {
992 ctx.logger.info("No commit created (no meaningful changes)");
994
995 {
996 let duration = start_time.elapsed().as_secs();
997 let step = ExecutionStep::new(
998 "Development",
999 iteration,
1000 "commit",
1001 StepOutcome::skipped("No meaningful changes to commit".to_string()),
1002 )
1003 .with_duration(duration);
1004 ctx.execution_history.add_step(step);
1005 }
1006 }
1007 CommitResultFallback::Failed(err) => {
1008 ctx.logger.error(&format!(
1010 "Failed to create commit (git operation failed): {err}"
1011 ));
1012
1013 {
1014 let duration = start_time.elapsed().as_secs();
1015 let step = ExecutionStep::new(
1016 "Development",
1017 iteration,
1018 "commit",
1019 StepOutcome::failure(err.to_string(), false),
1020 )
1021 .with_duration(duration);
1022 ctx.execution_history.add_step(step);
1023 }
1024
1025 return Err(anyhow::anyhow!(err));
1027 }
1028 }
1029 } else {
1030 ctx.logger
1031 .warn("Unable to get primary commit agent for commit");
1032
1033 {
1034 let duration = start_time.elapsed().as_secs();
1035 let step = ExecutionStep::new(
1036 "Development",
1037 iteration,
1038 "commit",
1039 StepOutcome::failure("No commit agent available".to_string(), true),
1040 )
1041 .with_duration(duration);
1042 ctx.execution_history.add_step(step);
1043 }
1044 }
1045
1046 Ok(())
1047}