Skip to main content

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}