autom8/commands/run.rs
1//! Run command handler.
2//!
3//! Executes the autom8 agent loop to implement spec stories.
4
5use std::path::Path;
6
7use crate::config::spec_dir;
8use crate::error::Result;
9use crate::output::{print_header, GREEN, RESET};
10use crate::self_test::{
11 cleanup_self_test, create_self_test_spec, print_cleanup_results, print_failure_details,
12 SELF_TEST_SPEC_FILENAME,
13};
14use crate::Runner;
15
16use super::{detect_input_type, ensure_project_dir, InputType};
17
18/// Run implementation from a spec file.
19///
20/// Parses the spec file and runs the agent loop to implement
21/// each user story sequentially.
22///
23/// # Arguments
24///
25/// * `verbose` - If true, show full Claude output
26/// * `spec` - Path to the spec file (JSON or Markdown)
27/// * `skip_review` - If true, skip the review loop
28/// * `worktree` - If true, enable worktree mode (overrides config)
29/// * `no_worktree` - If true, disable worktree mode (overrides config)
30/// * `self_test` - If true, run a self-test with hardcoded spec (ignores spec argument)
31///
32/// # Returns
33///
34/// * `Ok(())` on successful completion
35/// * `Err(Autom8Error)` if implementation fails
36pub fn run_command(
37 verbose: bool,
38 spec: &Path,
39 skip_review: bool,
40 worktree: bool,
41 no_worktree: bool,
42 self_test: bool,
43) -> Result<()> {
44 ensure_project_dir()?;
45
46 // Handle self-test mode
47 if self_test {
48 return run_self_test(verbose, skip_review, worktree, no_worktree);
49 }
50
51 let mut runner = Runner::new()?
52 .with_verbose(verbose)
53 .with_skip_review(skip_review);
54
55 // Apply worktree CLI flag override (CLI flags take precedence over config)
56 if worktree {
57 runner = runner.with_worktree(true);
58 } else if no_worktree {
59 runner = runner.with_worktree(false);
60 }
61
62 print_header();
63
64 match detect_input_type(spec) {
65 InputType::Json => runner.run(spec),
66 InputType::Markdown => runner.run_from_spec(spec),
67 }
68}
69
70/// Run a self-test with the hardcoded spec.
71///
72/// Creates the self-test spec, saves it to the config directory, and runs
73/// the normal implementation flow with commit and PR disabled. Cleans up
74/// all test artifacts afterward (on both success and failure).
75fn run_self_test(
76 verbose: bool,
77 skip_review: bool,
78 worktree: bool,
79 no_worktree: bool,
80) -> Result<()> {
81 // Create and save the self-test spec to the config directory
82 let spec = create_self_test_spec();
83 let spec_path = spec_dir()?.join(SELF_TEST_SPEC_FILENAME);
84 spec.save(&spec_path)?;
85
86 // Configure runner with commit and PR disabled (self-test shouldn't create commits or PRs)
87 let mut runner = Runner::new()?
88 .with_verbose(verbose)
89 .with_skip_review(skip_review)
90 .with_commit(false)
91 .with_pull_request(false);
92
93 // Apply worktree CLI flag override
94 if worktree {
95 runner = runner.with_worktree(true);
96 } else if no_worktree {
97 runner = runner.with_worktree(false);
98 }
99
100 print_header();
101
102 // Run the normal implementation flow with the self-test spec
103 let run_result = runner.run(&spec_path);
104
105 // On failure, print detailed error information before cleanup
106 if let Err(ref e) = run_result {
107 print_failure_details(e);
108 }
109
110 // Always clean up, regardless of success or failure
111 let cleanup_result = cleanup_self_test();
112 print_cleanup_results(&cleanup_result);
113
114 // Return the original run result (cleanup failures are just reported, not propagated)
115 run_result
116}
117
118/// Run implementation from a file argument.
119///
120/// Handles the positional file argument, moving the spec file to the
121/// config directory if necessary before running.
122///
123/// # Arguments
124///
125/// * `runner` - The configured Runner instance
126/// * `file` - Path to the spec file
127///
128/// # Returns
129///
130/// * `Ok(())` on successful completion
131/// * `Err(Autom8Error)` if implementation fails
132pub fn run_with_file(runner: &Runner, file: &Path) -> Result<()> {
133 ensure_project_dir()?;
134
135 // Move file to config directory if not already there
136 let move_result = crate::config::move_to_config_dir(file)?;
137
138 print_header();
139
140 // Notify user if file was moved
141 if move_result.was_moved {
142 println!(
143 "{GREEN}Moved{RESET} {} → {}",
144 file.display(),
145 move_result.dest_path.display()
146 );
147 println!();
148 }
149
150 // Use the destination path for processing
151 match detect_input_type(&move_result.dest_path) {
152 InputType::Json => runner.run(&move_result.dest_path),
153 InputType::Markdown => runner.run_from_spec(&move_result.dest_path),
154 }
155}