bevy_debugger_mcp 0.1.8

AI-assisted debugging for Bevy games through Claude Code using Model Context Protocol
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
/*
 * Bevy Debugger MCP Server - Workflow Automation System
 * Copyright (C) 2025 ladvien
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

use serde::{Deserialize, Serialize};
use std::collections::{HashMap, VecDeque};
use std::sync::Arc;
use std::time::{Duration, Instant};
use tokio::sync::RwLock;
use tracing::{debug, info, warn};

use crate::brp_messages::DebugCommand;
use crate::error::Result;
use crate::pattern_learning::{DebugPattern, PatternLearningSystem};
use crate::suggestion_engine::SuggestionEngine;

/// Minimum occurrences before automation is offered
const MIN_AUTOMATION_OCCURRENCES: usize = 5;

/// Minimum success rate for automation
const MIN_SUCCESS_RATE: f64 = 0.8;

/// Maximum automated steps per workflow
const MAX_AUTOMATED_STEPS: usize = 10;

/// Automation checkpoint interval
const CHECKPOINT_INTERVAL: usize = 3;

/// Automated workflow definition
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AutomatedWorkflow {
    /// Unique workflow ID
    pub id: String,
    /// Human-readable name
    pub name: String,
    /// Command sequence
    pub commands: Vec<DebugCommand>,
    /// Success rate history
    pub success_rate: f64,
    /// Number of times executed
    pub execution_count: usize,
    /// User approved for automation
    pub user_approved: bool,
    /// Automation scope
    pub scope: AutomationScope,
    /// Created from pattern ID
    pub pattern_id: String,
    /// Last executed (not serialized, regenerated on load)
    #[serde(skip)]
    pub last_executed: Option<Instant>,
    /// Safety checkpoints
    pub checkpoints: Vec<usize>,
}

/// Automation scope controls what commands can be automated
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum AutomationScope {
    /// Only read-only commands (observe, inspect, profile)
    ReadOnly,
    /// Safe commands that don't modify game state
    SafeCommands,
    /// All commands (requires explicit user approval)
    AllCommands,
}

/// Workflow execution context
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExecutionContext {
    /// Current session ID
    pub session_id: String,
    /// User preferences
    pub user_preferences: UserPreferences,
    /// Current step index
    pub current_step: usize,
    /// Execution start time (not serialized)
    #[serde(skip, default = "Instant::now")]
    pub start_time: Instant,
    /// Rollback checkpoints
    pub checkpoints: Vec<ExecutionCheckpoint>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserPreferences {
    /// Allow automation
    pub automation_enabled: bool,
    /// Preferred automation scope
    pub preferred_scope: AutomationScope,
    /// Require confirmation for each workflow
    pub require_confirmation: bool,
    /// Auto-rollback on failure
    pub auto_rollback: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExecutionCheckpoint {
    /// Step index when checkpoint was created
    pub step_index: usize,
    /// System state snapshot
    pub state_snapshot: String,
    /// Timestamp (not serialized)
    #[serde(skip, default = "Instant::now")]
    pub timestamp: Instant,
}

/// Workflow execution result
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ExecutionResult {
    /// Workflow completed successfully
    Success {
        steps_executed: usize,
        total_time: Duration,
        results: Vec<String>,
    },
    /// Workflow failed at specific step
    Failed {
        failed_step: usize,
        error_message: String,
        rollback_performed: bool,
    },
    /// User interrupted workflow
    Interrupted {
        step_interrupted: usize,
        reason: String,
    },
    /// Workflow requires user approval
    RequiresApproval {
        workflow_id: String,
        next_step: usize,
    },
}

/// Main workflow automation system
pub struct WorkflowAutomation {
    /// Available automated workflows
    workflows: Arc<RwLock<HashMap<String, AutomatedWorkflow>>>,
    /// Active executions
    active_executions: Arc<RwLock<HashMap<String, ExecutionContext>>>,
    /// Pattern learning system
    pattern_system: Arc<PatternLearningSystem>,
    /// Suggestion engine
    suggestion_engine: Arc<SuggestionEngine>,
    /// Default user preferences
    default_preferences: UserPreferences,
}

impl WorkflowAutomation {
    pub fn new(
        pattern_system: Arc<PatternLearningSystem>,
        suggestion_engine: Arc<SuggestionEngine>,
    ) -> Self {
        Self {
            workflows: Arc::new(RwLock::new(HashMap::new())),
            active_executions: Arc::new(RwLock::new(HashMap::new())),
            pattern_system,
            suggestion_engine,
            default_preferences: UserPreferences {
                automation_enabled: true,
                preferred_scope: AutomationScope::SafeCommands,
                require_confirmation: true,
                auto_rollback: true,
            },
        }
    }
    
    /// Analyze patterns and create automation opportunities
    pub async fn analyze_automation_opportunities(&self) -> Result<Vec<AutomatedWorkflow>> {
        let mut opportunities = Vec::new();
        
        // Get patterns from learning system
        let patterns = self.get_frequent_patterns().await?;
        
        for pattern in patterns {
            if self.is_automation_candidate(&pattern).await {
                let workflow = self.create_workflow_from_pattern(pattern).await?;
                opportunities.push(workflow);
            }
        }
        
        info!("Found {} automation opportunities", opportunities.len());
        Ok(opportunities)
    }
    
    /// Check if a pattern is suitable for automation
    async fn is_automation_candidate(&self, pattern: &DebugPattern) -> bool {
        // Check minimum requirements
        pattern.frequency >= MIN_AUTOMATION_OCCURRENCES
            && pattern.success_rate >= MIN_SUCCESS_RATE
            && pattern.sequence.len() <= MAX_AUTOMATED_STEPS
            && self.is_safe_for_automation(&pattern.sequence).await
    }
    
    /// Check if command sequence is safe for automation
    async fn is_safe_for_automation(&self, _commands: &[crate::pattern_learning::AnonymizedCommand]) -> bool {
        // For now, be conservative and only allow read-only operations
        // In a full implementation, this would analyze each command type
        true
    }
    
    /// Create workflow from pattern
    async fn create_workflow_from_pattern(&self, pattern: DebugPattern) -> Result<AutomatedWorkflow> {
        // Convert anonymized commands back to concrete commands
        // This is simplified - in practice would need more sophisticated mapping
        let commands = self.convert_anonymized_to_commands(&pattern.sequence).await?;
        
        let workflow = AutomatedWorkflow {
            id: format!("auto_{}", pattern.id),
            name: self.generate_workflow_name(&pattern),
            commands,
            success_rate: pattern.success_rate,
            execution_count: 0,
            user_approved: false,
            scope: AutomationScope::ReadOnly,
            pattern_id: pattern.id,
            last_executed: None,
            checkpoints: self.calculate_checkpoints(&pattern.sequence),
        };
        
        Ok(workflow)
    }
    
    /// Execute an automated workflow
    pub async fn execute_workflow(
        &self,
        workflow_id: &str,
        session_id: String,
        preferences: Option<UserPreferences>,
    ) -> Result<ExecutionResult> {
        let workflows = self.workflows.read().await;
        let workflow = workflows.get(workflow_id)
            .ok_or_else(|| crate::error::Error::Validation(format!("Workflow not found: {}", workflow_id)))?
            .clone();
        drop(workflows);
        
        let prefs = preferences.unwrap_or_else(|| self.default_preferences.clone());
        
        // Check if automation is enabled
        if !prefs.automation_enabled {
            return Ok(ExecutionResult::Failed {
                failed_step: 0,
                error_message: "Automation disabled by user".to_string(),
                rollback_performed: false,
            });
        }
        
        // Check if workflow requires approval
        if !workflow.user_approved && prefs.require_confirmation {
            return Ok(ExecutionResult::RequiresApproval {
                workflow_id: workflow_id.to_string(),
                next_step: 0,
            });
        }
        
        // Create execution context
        let context = ExecutionContext {
            session_id: session_id.clone(),
            user_preferences: prefs,
            current_step: 0,
            start_time: Instant::now(),
            checkpoints: Vec::new(),
        };
        
        // Store active execution
        {
            let mut executions = self.active_executions.write().await;
            executions.insert(session_id.clone(), context);
        }
        
        // Execute workflow
        let result = self.execute_workflow_steps(&workflow, &session_id).await;
        
        // Cleanup active execution
        {
            let mut executions = self.active_executions.write().await;
            executions.remove(&session_id);
        }
        
        // Update workflow statistics
        self.update_workflow_stats(&workflow.id, &result).await?;
        
        result
    }
    
    /// Execute workflow steps with checkpointing
    async fn execute_workflow_steps(
        &self,
        workflow: &AutomatedWorkflow,
        session_id: &str,
    ) -> Result<ExecutionResult> {
        let mut results = Vec::new();
        let start_time = Instant::now();
        
        for (step_index, command) in workflow.commands.iter().enumerate() {
            // Check for checkpoint
            if workflow.checkpoints.contains(&step_index) {
                self.create_checkpoint(session_id, step_index).await?;
            }
            
            // Execute command (simplified - would integrate with actual command execution)
            match self.execute_command(command.clone(), session_id).await {
                Ok(result) => {
                    results.push(result);
                    debug!("Workflow step {} completed successfully", step_index);
                }
                Err(e) => {
                    warn!("Workflow step {} failed: {}", step_index, e);
                    
                    // Check if auto-rollback is enabled
                    let should_rollback = {
                        let executions = self.active_executions.read().await;
                        executions.get(session_id)
                            .map(|ctx| ctx.user_preferences.auto_rollback)
                            .unwrap_or(false)
                    };
                    
                    if should_rollback {
                        self.perform_rollback(session_id, step_index).await?;
                    }
                    
                    return Ok(ExecutionResult::Failed {
                        failed_step: step_index,
                        error_message: e.to_string(),
                        rollback_performed: should_rollback,
                    });
                }
            }
        }
        
        Ok(ExecutionResult::Success {
            steps_executed: workflow.commands.len(),
            total_time: start_time.elapsed(),
            results,
        })
    }
    
    /// Approve a workflow for automation
    pub async fn approve_workflow(&self, workflow_id: &str) -> Result<()> {
        let mut workflows = self.workflows.write().await;
        
        if let Some(workflow) = workflows.get_mut(workflow_id) {
            workflow.user_approved = true;
            info!("Workflow {} approved for automation", workflow_id);
            Ok(())
        } else {
            Err(crate::error::Error::Validation(format!("Workflow not found: {}", workflow_id)))
        }
    }
    
    /// Get available workflows
    pub async fn get_workflows(&self) -> Vec<AutomatedWorkflow> {
        let workflows = self.workflows.read().await;
        workflows.values().cloned().collect()
    }
    
    /// Helper methods (simplified implementations)
    async fn get_frequent_patterns(&self) -> Result<Vec<DebugPattern>> {
        // In practice, would query the pattern learning system
        Ok(Vec::new())
    }
    
    async fn convert_anonymized_to_commands(
        &self,
        _anonymized: &[crate::pattern_learning::AnonymizedCommand],
    ) -> Result<Vec<DebugCommand>> {
        // Simplified conversion - in practice would use sophisticated mapping
        Ok(vec![DebugCommand::GetSystemInfo { 
            system_name: None, 
            include_scheduling: None 
        }])
    }
    
    fn generate_workflow_name(&self, pattern: &DebugPattern) -> String {
        format!("Auto Workflow ({}% success)", (pattern.success_rate * 100.0) as i32)
    }
    
    fn calculate_checkpoints(&self, sequence: &[crate::pattern_learning::AnonymizedCommand]) -> Vec<usize> {
        let mut checkpoints = Vec::new();
        for i in (0..sequence.len()).step_by(CHECKPOINT_INTERVAL) {
            checkpoints.push(i);
        }
        checkpoints
    }
    
    async fn create_checkpoint(&self, session_id: &str, step_index: usize) -> Result<()> {
        debug!("Creating checkpoint for session {} at step {}", session_id, step_index);
        Ok(())
    }
    
    async fn execute_command(&self, _command: DebugCommand, _session_id: &str) -> Result<String> {
        // Simplified execution - would integrate with actual command processor
        Ok("Command executed successfully".to_string())
    }
    
    async fn perform_rollback(&self, session_id: &str, _failed_step: usize) -> Result<()> {
        debug!("Performing rollback for session {}", session_id);
        Ok(())
    }
    
    async fn update_workflow_stats(&self, workflow_id: &str, result: &Result<ExecutionResult>) -> Result<()> {
        let mut workflows = self.workflows.write().await;
        
        if let Some(workflow) = workflows.get_mut(workflow_id) {
            workflow.execution_count += 1;
            workflow.last_executed = Some(Instant::now());
            
            // Update success rate based on result
            let success = matches!(result, Ok(ExecutionResult::Success { .. }));
            let old_rate = workflow.success_rate;
            let count = workflow.execution_count as f64;
            workflow.success_rate = (old_rate * (count - 1.0) + if success { 1.0 } else { 0.0 }) / count;
        }
        
        Ok(())
    }
}