acp/commands/
attempt.rs

1//! @acp:module "Attempt Command"
2//! @acp:summary "Manage troubleshooting attempts"
3//! @acp:domain cli
4//! @acp:layer handler
5
6use anyhow::Result;
7use console::style;
8
9use crate::constraints::AttemptStatus;
10use crate::AttemptTracker;
11
12/// Subcommand types for the attempt command
13#[derive(Debug, Clone)]
14pub enum AttemptSubcommand {
15    Start {
16        id: String,
17        for_issue: Option<String>,
18        description: Option<String>,
19    },
20    List {
21        active: bool,
22        failed: bool,
23        history: bool,
24    },
25    Fail {
26        id: String,
27        reason: Option<String>,
28    },
29    Verify {
30        id: String,
31    },
32    Revert {
33        id: String,
34    },
35    Cleanup,
36    Checkpoint {
37        name: String,
38        files: Vec<String>,
39        description: Option<String>,
40    },
41    Checkpoints,
42    Restore {
43        name: String,
44    },
45}
46
47/// Execute the attempt command
48pub fn execute_attempt(subcommand: AttemptSubcommand) -> Result<()> {
49    let mut tracker = AttemptTracker::load_or_create();
50
51    match subcommand {
52        AttemptSubcommand::Start {
53            id,
54            for_issue,
55            description,
56        } => {
57            tracker.start_attempt(&id, for_issue.as_deref(), description.as_deref());
58            tracker.save()?;
59            println!("{} Started attempt: {}", style("✓").green(), id);
60        }
61
62        AttemptSubcommand::List {
63            active,
64            failed,
65            history,
66        } => {
67            if history {
68                println!("{}", style("Attempt History:").bold());
69                for entry in &tracker.history {
70                    let status_color = match entry.status {
71                        AttemptStatus::Verified => style("✓").green(),
72                        AttemptStatus::Failed => style("✗").red(),
73                        AttemptStatus::Reverted => style("↩").yellow(),
74                        _ => style("?").dim(),
75                    };
76                    println!(
77                        "  {} {} - {:?} ({} files)",
78                        status_color, entry.id, entry.status, entry.files_modified
79                    );
80                }
81            } else {
82                println!("{}", style("Active Attempts:").bold());
83                for attempt in tracker.attempts.values() {
84                    if active && attempt.status != AttemptStatus::Active {
85                        continue;
86                    }
87                    if failed && attempt.status != AttemptStatus::Failed {
88                        continue;
89                    }
90
91                    let status_color = match attempt.status {
92                        AttemptStatus::Active => style("●").cyan(),
93                        AttemptStatus::Testing => style("◐").yellow(),
94                        AttemptStatus::Failed => style("✗").red(),
95                        _ => style("?").dim(),
96                    };
97
98                    println!("  {} {} - {:?}", status_color, attempt.id, attempt.status);
99                    if let Some(issue) = &attempt.for_issue {
100                        println!("    For: {}", issue);
101                    }
102                    println!("    Files: {}", attempt.files.len());
103                }
104            }
105        }
106
107        AttemptSubcommand::Fail { id, reason } => {
108            tracker.fail_attempt(&id, reason.as_deref())?;
109            tracker.save()?;
110            println!("{} Marked attempt as failed: {}", style("✗").red(), id);
111        }
112
113        AttemptSubcommand::Verify { id } => {
114            tracker.verify_attempt(&id)?;
115            tracker.save()?;
116            println!("{} Verified attempt: {}", style("✓").green(), id);
117        }
118
119        AttemptSubcommand::Revert { id } => {
120            let actions = tracker.revert_attempt(&id)?;
121            println!("{} Reverted attempt: {}", style("↩").yellow(), id);
122            for action in &actions {
123                println!("  {} {}", style(&action.action).dim(), action.file);
124            }
125        }
126
127        AttemptSubcommand::Cleanup => {
128            let actions = tracker.cleanup_failed()?;
129            println!(
130                "{} Cleaned up {} files from failed attempts",
131                style("✓").green(),
132                actions.len()
133            );
134        }
135
136        AttemptSubcommand::Checkpoint {
137            name,
138            files,
139            description,
140        } => {
141            let file_refs: Vec<&str> = files.iter().map(|s| s.as_str()).collect();
142            tracker.create_checkpoint(&name, &file_refs, description.as_deref())?;
143            println!(
144                "{} Created checkpoint: {} ({} files)",
145                style("✓").green(),
146                name,
147                files.len()
148            );
149        }
150
151        AttemptSubcommand::Checkpoints => {
152            println!("{}", style("Checkpoints:").bold());
153            for (name, cp) in &tracker.checkpoints {
154                println!(
155                    "  {} - {} files, created {}",
156                    style(name).cyan(),
157                    cp.files.len(),
158                    cp.created_at.format("%Y-%m-%d %H:%M")
159                );
160            }
161        }
162
163        AttemptSubcommand::Restore { name } => {
164            let actions = tracker.restore_checkpoint(&name)?;
165            println!("{} Restored checkpoint: {}", style("↩").yellow(), name);
166            for action in &actions {
167                println!("  {} {}", style(&action.action).dim(), action.file);
168            }
169        }
170    }
171
172    Ok(())
173}