Skip to main content

autom8/commands/
default.rs

1//! Default command handler.
2//!
3//! Handles the default behavior when autom8 is run with no arguments.
4//! Checks for existing state and either resumes work or starts spec creation.
5
6use crate::error::{Autom8Error, Result};
7use crate::output::{print_error, print_header, BOLD, CYAN, GRAY, GREEN, RESET, YELLOW};
8use crate::prompt;
9use crate::prompts;
10use crate::state::{RunState, StateManager};
11use crate::Runner;
12use crate::SpecSnapshot;
13
14use super::ensure_project_dir;
15
16/// Default command when running `autom8` with no arguments.
17///
18/// # Workflow
19///
20/// 1. Check if the project is initialized. If not, prompt to initialize.
21/// 2. Check for existing state indicating work in progress.
22/// 3. If state exists, prompt user to resume or start fresh.
23/// 4. If no state, proceed to spec creation flow.
24///
25/// # Arguments
26///
27/// * `verbose` - If true, show full Claude output during spec creation
28///
29/// # Returns
30///
31/// * `Ok(())` on success
32/// * `Err(Autom8Error)` if any step fails
33pub fn default_command(verbose: bool) -> Result<()> {
34    // Check if this directory is already tracked by autom8
35    let project_name = crate::config::current_project_name()?;
36    if !crate::config::project_exists(&project_name)? {
37        // Project not initialized - ask user if they want to initialize
38        print_header();
39        println!(
40            "This directory ({CYAN}{}{RESET}) is not currently tracked by autom8.",
41            project_name
42        );
43        println!();
44
45        if prompt::confirm("Would you like to initialize it?", true) {
46            ensure_project_dir()?;
47            println!();
48            println!("{GREEN}Initialized.{RESET}");
49            println!();
50        } else {
51            println!();
52            println!("Exiting.");
53            return Ok(());
54        }
55    } else {
56        // Project exists, ensure directories are set up
57        ensure_project_dir()?;
58    }
59
60    let state_manager = StateManager::new()?;
61
62    // Check for existing state file
63    if let Some(state) = state_manager.load_current()? {
64        // State exists - proceed to prompt user
65        handle_existing_state(state, verbose)
66    } else {
67        // No state - proceed to spec creation
68        start_spec_creation(verbose)
69    }
70}
71
72/// Handle existing state file - prompt user to resume or start fresh.
73///
74/// # Arguments
75///
76/// * `state` - The existing run state
77/// * `verbose` - If true, show full Claude output
78///
79/// # Returns
80///
81/// * `Ok(())` on success
82/// * `Err(Autom8Error)` if any step fails
83fn handle_existing_state(state: RunState, verbose: bool) -> Result<()> {
84    print_header();
85
86    // Display clear prompt with context
87    println!("{YELLOW}Work in progress detected.{RESET}");
88    println!();
89    println!("  Branch: {CYAN}{}{RESET}", state.branch);
90    if let Some(story) = &state.current_story {
91        println!("  Current story: {CYAN}{}{RESET}", story);
92    }
93    println!();
94
95    // Present options to the user
96    let choice = prompt::select(
97        "Resume or start fresh?",
98        &["Resume existing work", "Start fresh", "Exit"],
99        0, // Default to Resume
100    );
101
102    match choice {
103        0 => {
104            // Option 1: Resume - continue the existing run
105            println!();
106            prompt::print_action("Resuming existing work...");
107            println!();
108
109            let runner = Runner::new()?.with_verbose(verbose);
110            runner.resume()
111        }
112        1 => {
113            // Option 2: Start fresh - archive state and proceed to spec creation
114            let state_manager = StateManager::new()?;
115
116            // Archive before deleting
117            let archive_path = state_manager.archive(&state)?;
118            state_manager.clear_current()?;
119
120            println!();
121            println!(
122                "{GREEN}Previous state archived:{RESET} {}",
123                archive_path.display()
124            );
125            println!();
126
127            // Proceed to spec creation
128            start_spec_creation(verbose)
129        }
130        _ => {
131            // Option 3: Exit - do nothing, exit cleanly
132            println!();
133            println!("Exiting.");
134            Ok(())
135        }
136    }
137}
138
139/// Start a new spec creation session.
140///
141/// Spawns an interactive Claude session to help create a spec file.
142/// After the session ends, detects new spec files and proceeds to implementation.
143///
144/// # Arguments
145///
146/// * `verbose` - If true, show full Claude output
147///
148/// # Returns
149///
150/// * `Ok(())` on success
151/// * `Err(Autom8Error)` if spec creation or implementation fails
152fn start_spec_creation(verbose: bool) -> Result<()> {
153    use std::process::Command;
154
155    print_header();
156
157    // Print explanation of what will happen
158    println!("{BOLD}Starting Spec Creation Session{RESET}");
159    println!();
160    println!("This will spawn an interactive Claude session to help you create a spec.");
161    println!("Claude will guide you through defining your feature with questions about:");
162    println!("  - Project context and tech stack");
163    println!("  - Feature requirements and user stories");
164    println!("  - Acceptance criteria for each story");
165    println!();
166    println!(
167        "When you're done, save the spec as {CYAN}spec-<feature>.md{RESET} and exit the session."
168    );
169    println!("autom8 will automatically proceed to implementation.");
170    println!();
171    println!("{GRAY}Starting Claude...{RESET}");
172    println!();
173
174    // Take a snapshot of existing spec files before spawning Claude
175    let snapshot = SpecSnapshot::capture()?;
176
177    // Spawn interactive Claude session with the spec skill prompt
178    let status = Command::new("claude")
179        .arg(prompts::SPEC_SKILL_PROMPT)
180        .stdin(std::process::Stdio::inherit())
181        .stdout(std::process::Stdio::inherit())
182        .stderr(std::process::Stdio::inherit())
183        .status();
184
185    match status {
186        Ok(exit_status) => {
187            if exit_status.success() {
188                println!();
189                println!("{GREEN}Claude session ended.{RESET}");
190                println!();
191
192                // Detect new spec files created during the session
193                let new_files = snapshot.detect_new_files()?;
194
195                match new_files.len() {
196                    0 => {
197                        print_error("No new spec files detected.");
198                        println!();
199                        println!("{BOLD}Possible causes:{RESET}");
200                        println!("  - Claude session ended before the spec was saved");
201                        println!("  - Spec was saved to an unexpected location");
202                        println!("  - Claude didn't follow the spec skill instructions");
203                        println!();
204                        println!("{BOLD}Suggestions:{RESET}");
205                        println!("  - Run {CYAN}autom8{RESET} again to start a fresh session");
206                        println!("  - Or use the manual workflow:");
207                        println!("      1. Run {CYAN}autom8 skill spec{RESET} to get the prompt");
208                        println!("      2. Start a Claude session and paste the prompt");
209                        println!("      3. Save the spec as {CYAN}spec-<feature>.md{RESET}");
210                        println!("      4. Run {CYAN}autom8{RESET} to implement");
211                        std::process::exit(1);
212                    }
213                    1 => {
214                        let spec_path = &new_files[0];
215                        println!("{GREEN}Detected new spec:{RESET} {}", spec_path.display());
216                        println!();
217                        println!("{BOLD}Proceeding to implementation...{RESET}");
218                        println!();
219
220                        // Create a new runner and run from the spec
221                        let runner = Runner::new()?.with_verbose(verbose);
222                        runner.run_from_spec(spec_path)
223                    }
224                    n => {
225                        println!("{YELLOW}Detected {} new spec files:{RESET}", n);
226                        println!();
227
228                        // Build options list with file paths
229                        let options: Vec<String> = new_files
230                            .iter()
231                            .enumerate()
232                            .map(|(i, file)| {
233                                let filename = file
234                                    .file_name()
235                                    .and_then(|n| n.to_str())
236                                    .unwrap_or("spec.md");
237                                format!("{}. {}", i + 1, filename)
238                            })
239                            .collect();
240                        let option_refs: Vec<&str> = options.iter().map(|s| s.as_str()).collect();
241
242                        let choice = prompt::select(
243                            "Which spec would you like to implement?",
244                            &option_refs,
245                            0,
246                        );
247
248                        let selected_spec = &new_files[choice];
249                        println!();
250                        println!("{GREEN}Selected:{RESET} {}", selected_spec.display());
251                        println!();
252                        println!("{BOLD}Proceeding to implementation...{RESET}");
253                        println!();
254
255                        // Create a new runner and run from the spec
256                        let runner = Runner::new()?.with_verbose(verbose);
257                        runner.run_from_spec(selected_spec)
258                    }
259                }
260            } else {
261                Err(Autom8Error::ClaudeError(format!(
262                    "Claude exited with status: {}",
263                    exit_status
264                )))
265            }
266        }
267        Err(e) => {
268            if e.kind() == std::io::ErrorKind::NotFound {
269                Err(Autom8Error::ClaudeError(
270                    "Claude CLI not found. Please install it from https://github.com/anthropics/claude-code".to_string()
271                ))
272            } else {
273                Err(Autom8Error::ClaudeError(format!(
274                    "Failed to spawn Claude: {}",
275                    e
276                )))
277            }
278        }
279    }
280}