autom8/commands/
default.rs1use 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
16pub fn default_command(verbose: bool) -> Result<()> {
34 let project_name = crate::config::current_project_name()?;
36 if !crate::config::project_exists(&project_name)? {
37 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 ensure_project_dir()?;
58 }
59
60 let state_manager = StateManager::new()?;
61
62 if let Some(state) = state_manager.load_current()? {
64 handle_existing_state(state, verbose)
66 } else {
67 start_spec_creation(verbose)
69 }
70}
71
72fn handle_existing_state(state: RunState, verbose: bool) -> Result<()> {
84 print_header();
85
86 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 let choice = prompt::select(
97 "Resume or start fresh?",
98 &["Resume existing work", "Start fresh", "Exit"],
99 0, );
101
102 match choice {
103 0 => {
104 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 let state_manager = StateManager::new()?;
115
116 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 start_spec_creation(verbose)
129 }
130 _ => {
131 println!();
133 println!("Exiting.");
134 Ok(())
135 }
136 }
137}
138
139fn start_spec_creation(verbose: bool) -> Result<()> {
153 use std::process::Command;
154
155 print_header();
156
157 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 let snapshot = SpecSnapshot::capture()?;
176
177 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 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 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 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 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}