Skip to main content

ralph/cli/
scan.rs

1//! `ralph scan` command: Clap types and handler.
2//!
3//! Responsibilities:
4//! - Define clap arguments for scan commands.
5//! - Dispatch scan execution with resolved runner overrides.
6//!
7//! Not handled here:
8//! - Queue storage and task persistence.
9//! - Runner implementation details or model execution.
10//! - Config precedence rules beyond loading the current repo config.
11//!
12//! Invariants/assumptions:
13//! - Configuration is resolved from the current working directory.
14//! - Runner overrides are validated by the agent resolution helpers.
15
16use anyhow::Result;
17use clap::{Args, ValueEnum};
18
19use crate::{agent, commands::scan as scan_cmd, config};
20
21/// Scan mode determining the focus of the repository scan.
22#[derive(Clone, Copy, Debug, Default, ValueEnum, PartialEq, Eq)]
23pub enum ScanMode {
24    /// General mode: user provides focus prompt without specifying a mode.
25    /// Uses task building instructions without maintenance or innovation specific criteria.
26    #[default]
27    General,
28    /// Maintenance mode: find bugs, workflow gaps, design flaws, repo rules violations.
29    /// Focused on break-fix maintenance and code hygiene.
30    Maintenance,
31    /// Innovation mode: find feature gaps, use-case completeness issues, enhancement opportunities.
32    /// Focus on new features and strategic additions.
33    Innovation,
34}
35
36pub fn handle_scan(args: ScanArgs, force: bool) -> Result<()> {
37    let resolved = config::resolve_from_cwd_with_profile(args.profile.as_deref())?;
38    let overrides = agent::resolve_agent_overrides(&agent::AgentArgs {
39        runner: args.runner.clone(),
40        model: args.model.clone(),
41        effort: args.effort.clone(),
42        repo_prompt: args.repo_prompt,
43        runner_cli: args.runner_cli.clone(),
44    })?;
45
46    // Merge positional prompt and --focus flag: positional takes precedence
47    let focus = if !args.prompt.is_empty() {
48        args.prompt.join(" ")
49    } else {
50        args.focus.clone()
51    };
52
53    // Determine the effective scan mode
54    let mode = match (args.mode, focus.trim().is_empty()) {
55        // No mode specified and no focus prompt → show help/error
56        (None, true) => {
57            return Err(anyhow::anyhow!(
58                "Please provide one of:\n\
59                 • A focus prompt: ralph scan \"your focus here\"\n\
60                 • A scan mode: ralph scan --mode maintenance\n\
61                 • Both: ralph scan --mode innovation \"your focus here\"\n\n\
62                 Run 'ralph scan --help' for more information."
63            ));
64        }
65        // Mode specified → use that mode
66        (Some(mode), _) => mode,
67        // No mode specified but focus prompt provided → use General mode
68        (None, false) => ScanMode::General,
69    };
70
71    scan_cmd::run_scan(
72        &resolved,
73        scan_cmd::ScanOptions {
74            focus,
75            mode,
76            runner_override: overrides.runner,
77            model_override: overrides.model,
78            reasoning_effort_override: overrides.reasoning_effort,
79            runner_cli_overrides: overrides.runner_cli,
80            force,
81            repoprompt_tool_injection: agent::resolve_rp_required(args.repo_prompt, &resolved),
82            git_revert_mode: resolved
83                .config
84                .agent
85                .git_revert_mode
86                .unwrap_or(crate::contracts::GitRevertMode::Ask),
87            lock_mode: if force {
88                scan_cmd::ScanLockMode::Held
89            } else {
90                scan_cmd::ScanLockMode::Acquire
91            },
92            output_handler: None,
93            revert_prompt: None,
94        },
95    )
96}
97
98#[derive(Args)]
99#[command(
100    about = "Scan repository for new tasks and focus areas",
101    after_long_help = "Runner selection:\n  - Override runner/model/effort for this invocation using flags.\n  - Defaults come from config when flags are omitted.\n  - Use --profile to apply a configured profile from `.ralph/config.jsonc` or `~/.config/ralph/config.jsonc`.\n\nRunner CLI options:\n  - Override approval/sandbox/verbosity/plan-mode via flags.\n  - Unsupported options follow --unsupported-option-policy.\n\nProfile precedence:\n  - CLI flags > task.agent > selected profile > base config\n\nSafety:\n  - Clean-repo checks allow changes to `.ralph/queue.jsonc`, `.ralph/done.jsonc`, and `.ralph/config.jsonc`.\n  - Use `--force` to bypass the clean-repo check (and stale queue locks) entirely if needed.\n\nExamples:\n  ralph scan \"production readiness gaps\"                              # General mode with focus prompt\n  ralph scan --focus \"production readiness gaps\"                     # General mode with --focus flag\n  ralph scan --mode maintenance \"security audit\"                     # Maintenance mode with focus\n  ralph scan --mode maintenance                                        # Maintenance mode without focus\n  ralph scan --mode innovation \"feature gaps for CLI\"                # Innovation mode with focus\n  ralph scan --mode innovation                                         # Innovation mode without focus\n  ralph scan -m innovation \"enhancement opportunities\"               # Short flag for mode\n  ralph scan --profile deep-review \"queue correctness audit\"         # Use a configured custom profile\n  ralph scan --profile fast-local \"small cleanup opportunities\"      # Use a configured custom profile\n  ralph scan --runner opencode --model gpt-5.3 \"CI and safety gaps\"  # With runner overrides\n  ralph scan --runner gemini --model gemini-3-flash-preview \"risk audit\"\n  ralph scan --runner codex --model gpt-5.4 --effort high \"queue correctness\"\n  ralph scan --approval-mode auto-edits --runner claude \"auto edits review\"\n  ralph scan --sandbox disabled --runner codex \"sandbox audit\"\n  ralph scan --repo-prompt plan \"Deep codebase analysis\"\n  ralph scan --repo-prompt off \"Quick surface scan\"\n  ralph scan --runner kimi \"risk audit\"\n  ralph scan --runner pi \"risk audit\""
102)]
103pub struct ScanArgs {
104    /// Optional focus prompt as positional argument (alternative to --focus).
105    #[arg(value_name = "PROMPT")]
106    pub prompt: Vec<String>,
107
108    /// Optional focus prompt to guide the scan.
109    #[arg(long, default_value = "")]
110    pub focus: String,
111
112    /// Scan mode: maintenance for code hygiene and bug finding,
113    /// innovation for feature discovery and enhancement opportunities,
114    /// general (default) when only focus prompt is provided.
115    #[arg(short = 'm', long, value_enum)]
116    pub mode: Option<ScanMode>,
117
118    /// Named configuration profile to apply before resolving CLI overrides.
119    /// Examples: fast-local, deep-review, quick-fix
120    #[arg(long, value_name = "NAME")]
121    pub profile: Option<String>,
122
123    /// Runner to use. CLI flag overrides config defaults (project > global > built-in).
124    #[arg(long)]
125    pub runner: Option<String>,
126
127    /// Model to use. CLI flag overrides config defaults (project > global > built-in).
128    #[arg(long)]
129    pub model: Option<String>,
130
131    /// Codex reasoning effort. CLI flag overrides config defaults (project > global > built-in).
132    /// Ignored for opencode and gemini.
133    #[arg(short = 'e', long)]
134    pub effort: Option<String>,
135
136    /// RepoPrompt mode (tools, plan, off). Alias: -rp.
137    #[arg(long = "repo-prompt", value_enum, value_name = "MODE")]
138    pub repo_prompt: Option<agent::RepoPromptMode>,
139
140    #[command(flatten)]
141    pub runner_cli: agent::RunnerCliArgs,
142}
143
144#[cfg(test)]
145mod tests;