mur_core/workflow/
autofix.rs1use crate::model::router::ModelRouter;
6use crate::types::{StepResult, StepType};
7use anyhow::Result;
8use std::sync::Arc;
9
10pub struct AutoFixEngine {
12 router: Arc<ModelRouter>,
13}
14
15#[derive(Debug, Clone)]
17pub struct FixResult {
18 pub success: bool,
20 pub analysis: String,
22 pub fix_command: String,
24 pub attempts: u32,
26 pub final_result: Option<StepResult>,
28}
29
30impl AutoFixEngine {
31 pub fn new(router: Arc<ModelRouter>) -> Self {
32 Self { router }
33 }
34
35 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 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 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 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 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}