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 read_last_output_from_prefix(log_prefix)
722}
723
724fn read_last_development_output(log_prefix: &Path) -> String {
730 read_last_output_from_prefix(log_prefix)
731}
732
733fn read_last_output_from_prefix(log_prefix: &Path) -> String {
738 let parent = log_prefix.parent().unwrap_or(Path::new("."));
739 let prefix_str = log_prefix
740 .file_name()
741 .and_then(|s| s.to_str())
742 .unwrap_or("");
743
744 let mut best_file: Option<(std::path::PathBuf, std::time::SystemTime)> = None;
746
747 if let Ok(entries) = fs::read_dir(parent) {
748 for entry in entries.flatten() {
749 let path = entry.path();
750 if let Some(filename) = path.file_name().and_then(|s| s.to_str()) {
751 if filename.starts_with(prefix_str)
753 && filename.len() > prefix_str.len()
754 && filename.ends_with(".log")
755 {
756 if let Ok(metadata) = fs::metadata(&path) {
758 if let Ok(modified) = metadata.modified() {
759 match &best_file {
760 None => best_file = Some((path.clone(), modified)),
761 Some((_, best_time)) if modified > *best_time => {
762 best_file = Some((path.clone(), modified));
763 }
764 _ => {}
765 }
766 }
767 }
768 }
769 }
770 }
771 }
772
773 if let Some((path, _)) = best_file {
775 if let Ok(content) = fs::read_to_string(&path) {
776 return content;
777 }
778 }
779
780 String::new()
781}
782
783fn format_xsd_error(error: &XsdValidationError) -> String {
785 format!(
786 "{} - expected: {}, found: {}",
787 error.element_path, error.expected, error.found
788 )
789}
790
791fn format_plan_as_markdown(elements: &PlanElements) -> String {
793 let mut result = String::new();
794
795 result.push_str("## Summary\n\n");
796 result.push_str(&elements.summary);
797 result.push_str("\n\n");
798
799 result.push_str("## Implementation Steps\n\n");
800 result.push_str(&elements.implementation_steps);
801 result.push_str("\n\n");
802
803 if let Some(ref critical_files) = elements.critical_files {
804 result.push_str("## Critical Files for Implementation\n\n");
805 result.push_str(critical_files);
806 result.push_str("\n\n");
807 }
808
809 if let Some(ref risks) = elements.risks_mitigations {
810 result.push_str("## Risks & Mitigations\n\n");
811 result.push_str(risks);
812 result.push_str("\n\n");
813 }
814
815 if let Some(ref verification) = elements.verification_strategy {
816 result.push_str("## Verification Strategy\n\n");
817 result.push_str(verification);
818 result.push_str("\n\n");
819 }
820
821 result
822}
823
824fn verify_plan_exists(
831 ctx: &mut PhaseContext<'_>,
832 iteration: u32,
833 resuming_into_development: bool,
834) -> anyhow::Result<bool> {
835 let plan_path = Path::new(".agent/PLAN.md");
836
837 let plan_ok = plan_path
838 .exists()
839 .then(|| fs::read_to_string(plan_path).ok())
840 .flatten()
841 .is_some_and(|s| !s.trim().is_empty());
842
843 if !plan_ok && resuming_into_development {
845 ctx.logger
846 .warn("Missing .agent/PLAN.md; rerunning plan generation to recover");
847 run_planning_step(ctx, iteration)?;
848
849 let plan_ok = plan_path
851 .exists()
852 .then(|| fs::read_to_string(plan_path).ok())
853 .flatten()
854 .is_some_and(|s| !s.trim().is_empty());
855
856 return Ok(plan_ok);
857 }
858
859 Ok(plan_ok)
860}
861
862fn run_fast_check(ctx: &PhaseContext<'_>, fast_cmd: &str, iteration: u32) -> anyhow::Result<()> {
864 let argv = crate::common::split_command(fast_cmd)
865 .map_err(|e| anyhow::anyhow!("FAST_CHECK_CMD parse error (iteration {iteration}): {e}"))?;
866 if argv.is_empty() {
867 ctx.logger
868 .warn("FAST_CHECK_CMD is empty; skipping fast check");
869 return Ok(());
870 }
871
872 let display_cmd = crate::common::format_argv_for_log(&argv);
873 ctx.logger.info(&format!(
874 "Running fast check: {}{}{}",
875 ctx.colors.dim(),
876 display_cmd,
877 ctx.colors.reset()
878 ));
879
880 let Some((program, cmd_args)) = argv.split_first() else {
881 ctx.logger
882 .warn("FAST_CHECK_CMD is empty after parsing; skipping fast check");
883 return Ok(());
884 };
885 let status = Command::new(program).args(cmd_args).status()?;
886
887 if status.success() {
888 ctx.logger.success("Fast check passed");
889 } else {
890 ctx.logger.warn("Fast check had issues (non-blocking)");
891 }
892
893 Ok(())
894}
895
896fn handle_commit_after_development(
902 ctx: &mut PhaseContext<'_>,
903 iteration: u32,
904) -> anyhow::Result<()> {
905 let start_time = Instant::now();
906 let commit_agent = get_primary_commit_agent(ctx);
908
909 if let Some(agent) = commit_agent {
910 ctx.logger.info(&format!(
911 "Creating commit with auto-generated message (agent: {agent})..."
912 ));
913
914 let diff = match crate::git_helpers::git_diff() {
916 Ok(d) => d,
917 Err(e) => {
918 ctx.logger
919 .error(&format!("Failed to get diff for commit: {e}"));
920 return Err(anyhow::anyhow!(e));
921 }
922 };
923
924 let git_name = ctx.config.git_user_name.as_deref();
926 let git_email = ctx.config.git_user_email.as_deref();
927
928 let result = commit_with_generated_message(&diff, &agent, git_name, git_email, ctx);
929
930 match result {
931 CommitResultFallback::Success(oid) => {
932 ctx.logger
933 .success(&format!("Commit created successfully: {oid}"));
934 ctx.stats.commits_created += 1;
935
936 {
937 let duration = start_time.elapsed().as_secs();
938 let step = ExecutionStep::new(
939 "Development",
940 iteration,
941 "commit",
942 StepOutcome::success(Some(oid.to_string()), vec![]),
943 )
944 .with_agent(&agent)
945 .with_duration(duration);
946 ctx.execution_history.add_step(step);
947 }
948 }
949 CommitResultFallback::NoChanges => {
950 ctx.logger.info("No commit created (no meaningful changes)");
952
953 {
954 let duration = start_time.elapsed().as_secs();
955 let step = ExecutionStep::new(
956 "Development",
957 iteration,
958 "commit",
959 StepOutcome::skipped("No meaningful changes to commit".to_string()),
960 )
961 .with_duration(duration);
962 ctx.execution_history.add_step(step);
963 }
964 }
965 CommitResultFallback::Failed(err) => {
966 ctx.logger.error(&format!(
968 "Failed to create commit (git operation failed): {err}"
969 ));
970
971 {
972 let duration = start_time.elapsed().as_secs();
973 let step = ExecutionStep::new(
974 "Development",
975 iteration,
976 "commit",
977 StepOutcome::failure(err.to_string(), false),
978 )
979 .with_duration(duration);
980 ctx.execution_history.add_step(step);
981 }
982
983 return Err(anyhow::anyhow!(err));
985 }
986 }
987 } else {
988 ctx.logger
989 .warn("Unable to get primary commit agent for commit");
990
991 {
992 let duration = start_time.elapsed().as_secs();
993 let step = ExecutionStep::new(
994 "Development",
995 iteration,
996 "commit",
997 StepOutcome::failure("No commit agent available".to_string(), true),
998 )
999 .with_duration(duration);
1000 ctx.execution_history.add_step(step);
1001 }
1002 }
1003
1004 Ok(())
1005}