1use anyhow::Result;
17use clap::{Args, ValueEnum};
18
19use crate::{agent, commands::scan as scan_cmd, config};
20
21#[derive(Clone, Copy, Debug, Default, ValueEnum, PartialEq, Eq)]
23pub enum ScanMode {
24 #[default]
27 General,
28 Maintenance,
31 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 let focus = if !args.prompt.is_empty() {
48 args.prompt.join(" ")
49 } else {
50 args.focus.clone()
51 };
52
53 let mode = match (args.mode, focus.trim().is_empty()) {
55 (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 (Some(mode), _) => mode,
67 (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 #[arg(value_name = "PROMPT")]
106 pub prompt: Vec<String>,
107
108 #[arg(long, default_value = "")]
110 pub focus: String,
111
112 #[arg(short = 'm', long, value_enum)]
116 pub mode: Option<ScanMode>,
117
118 #[arg(long, value_name = "NAME")]
121 pub profile: Option<String>,
122
123 #[arg(long)]
125 pub runner: Option<String>,
126
127 #[arg(long)]
129 pub model: Option<String>,
130
131 #[arg(short = 'e', long)]
134 pub effort: Option<String>,
135
136 #[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;