Skip to main content

mur_core/workflow/
autofix.rs

1//! Auto-fix engine — uses AI to analyze failures and generate fixes.
2//!
3//! Flow: Step fails → Thinking model analyzes error → Coding model generates fix → Re-execute.
4
5use crate::model::router::ModelRouter;
6use crate::types::{StepResult, StepType};
7use anyhow::Result;
8use std::sync::Arc;
9
10/// Auto-fix engine that uses AI models to analyze and repair failed steps.
11pub struct AutoFixEngine {
12    router: Arc<ModelRouter>,
13}
14
15/// Result of an auto-fix attempt.
16#[derive(Debug, Clone)]
17pub struct FixResult {
18    /// Whether the fix succeeded.
19    pub success: bool,
20    /// The analysis from the Thinking model.
21    pub analysis: String,
22    /// The generated fix command/code.
23    pub fix_command: String,
24    /// Number of fix attempts made.
25    pub attempts: u32,
26    /// The final step result after fix.
27    pub final_result: Option<StepResult>,
28}
29
30impl AutoFixEngine {
31    pub fn new(router: Arc<ModelRouter>) -> Self {
32        Self { router }
33    }
34
35    /// Attempt to auto-fix a failed step.
36    ///
37    /// 1. Send error to Thinking model for analysis
38    /// 2. Send analysis to Coding model for fix generation
39    /// 3. Return the fix command for re-execution
40    pub async fn analyze_and_fix(
41        &self,
42        step_name: &str,
43        action: &str,
44        error_output: &str,
45        max_attempts: u32,
46    ) -> Result<FixResult> {
47        let mut last_analysis = String::new();
48        let mut last_fix = String::new();
49
50        for attempt in 1..=max_attempts {
51            tracing::info!(
52                "Auto-fix attempt {}/{} for step '{}'",
53                attempt,
54                max_attempts,
55                step_name
56            );
57
58            // Step 1: Analyze with Thinking model
59            let analysis_prompt = format!(
60                "A workflow step failed. Analyze the error and suggest a fix.\n\n\
61                 Step: {}\n\
62                 Command: {}\n\
63                 Error output:\n```\n{}\n```\n\n\
64                 {}\
65                 Provide a brief analysis and the exact command to fix the issue.",
66                step_name,
67                action,
68                error_output,
69                if attempt > 1 {
70                    format!(
71                        "Previous fix attempt failed. Previous analysis: {}\nPrevious fix: {}\n\n",
72                        last_analysis, last_fix
73                    )
74                } else {
75                    String::new()
76                }
77            );
78
79            let analysis_response = self
80                .router
81                .complete_for_step(&StepType::Analyze, &analysis_prompt)
82                .await?;
83
84            last_analysis = analysis_response.content.clone();
85
86            // Step 2: Generate fix with Coding model
87            let fix_prompt = format!(
88                "Based on this analysis, generate a single shell command to fix the issue.\n\n\
89                 Analysis: {}\n\n\
90                 Original command: {}\n\n\
91                 Respond with ONLY the fix command, no explanation.",
92                analysis_response.content, action
93            );
94
95            let fix_response = self
96                .router
97                .complete_for_step(&StepType::Fix, &fix_prompt)
98                .await?;
99
100            last_fix = fix_response.content.trim().to_string();
101
102            if !last_fix.is_empty() {
103                // Got a fix command — return it for the runner to try
104                return Ok(FixResult {
105                    success: true,
106                    analysis: last_analysis,
107                    fix_command: last_fix,
108                    attempts: attempt,
109                    final_result: None,
110                });
111            }
112        }
113
114        Ok(FixResult {
115            success: false,
116            analysis: last_analysis,
117            fix_command: last_fix,
118            attempts: max_attempts,
119            final_result: None,
120        })
121    }
122
123    /// Generate a MUR pattern from a successful fix for knowledge retention.
124    pub async fn generate_pattern(
125        &self,
126        step_name: &str,
127        original_error: &str,
128        fix_command: &str,
129    ) -> Result<String> {
130        let prompt = format!(
131            "Create a brief MUR pattern (YAML) documenting this fix for future reference.\n\n\
132             Problem: Step '{}' failed\n\
133             Error: {}\n\
134             Fix: {}\n\n\
135             Format:\n\
136             ```yaml\n\
137             id: fix-<descriptive-name>\n\
138             name: <short name>\n\
139             description: <what was fixed>\n\
140             context: <when this applies>\n\
141             solution: <the fix>\n\
142             ```",
143            step_name, original_error, fix_command
144        );
145
146        let response = self
147            .router
148            .complete_for_step(&StepType::Summarize, &prompt)
149            .await?;
150
151        Ok(response.content)
152    }
153}
154
155#[cfg(test)]
156mod tests {
157    use super::*;
158
159    #[test]
160    fn test_fix_result_default() {
161        let result = FixResult {
162            success: false,
163            analysis: "test".into(),
164            fix_command: "echo fix".into(),
165            attempts: 1,
166            final_result: None,
167        };
168        assert!(!result.success);
169        assert_eq!(result.attempts, 1);
170    }
171}