ralph-agent-loop 0.4.0

A Rust CLI for managing AI agent loops with a structured JSON task queue
Documentation
//! Top-level Clap argument definitions for `ralph`.
//!
//! Responsibilities:
//! - Define the root `Cli` parser and the top-level command enum.
//! - Keep top-level command documentation and long-help text together.
//! - Re-export the small `cli-spec` format args used by the machine/app tooling.
//!
//! Not handled here:
//! - Command execution logic.
//! - Shared queue/list helper functions.
//! - Parse-regression tests.
//!
//! Invariants/assumptions:
//! - Subcommands validate their own inputs and config dependencies.
//! - CLI parsing happens after argument normalization in `main`.

use clap::{Args, Parser, Subcommand, ValueEnum};

use super::{
    app, cleanup, color::ColorArg, completions, config, context, daemon, doctor, init, machine,
    migrate, plugin, prd, productivity, prompt, queue, run, runner, scan, task, tutorial, undo,
    version, watch, webhook,
};

#[derive(Parser)]
#[command(name = "ralph")]
#[command(about = "Ralph")]
#[command(version)]
#[command(after_long_help = r#"Runner selection:
  - CLI flags override project config, which overrides global config, which overrides built-in defaults.
  - Default runner/model come from config files: project config (.ralph/config.jsonc) > global config (~/.config/ralph/config.jsonc) > built-in.
  - `task` and `scan` accept --runner/--model/--effort as one-off overrides.
  - `run one` and `run loop` accept --runner/--model/--effort as one-off overrides; otherwise they use task.agent overrides when present; otherwise config agent defaults.

Config example (.ralph/config.jsonc):
  {
    "version": 2,
    "agent": {
      "runner": "codex",
      "model": "gpt-5.4",
      "codex_bin": "codex",
      "gemini_bin": "gemini",
      "claude_bin": "claude"
    }
  }

Notes:
  - Allowed runners: codex, opencode, gemini, claude, cursor, kimi, pi
  - Allowed models: gpt-5.4, gpt-5.3-codex, gpt-5.3-codex-spark, gpt-5.3, zai-coding-plan/glm-4.7, gemini-3-pro-preview, gemini-3-flash-preview, sonnet, opus, kimi-for-coding (codex supports only gpt-5.4 + gpt-5.3-codex + gpt-5.3-codex-spark + gpt-5.3; opencode/gemini/claude/cursor/kimi/pi accept arbitrary model ids))
  - On macOS: use `ralph app open` to launch the GUI (requires an installed Ralph.app)
  - App-launched runs are noninteractive: they stream output, but interactive approvals remain terminal-only.

Examples:
  ralph app open
  ralph queue list
  ralph queue show RQ-0008
  ralph queue next --with-title
  ralph scan --runner opencode --model gpt-5.3 --focus "CI gaps"
  ralph task --runner codex --model gpt-5.4 --effort high "Fix the flaky test"
  ralph scan --runner gemini --model gemini-3-flash-preview --focus "risk audit"
  ralph scan --runner claude --model sonnet --focus "risk audit"
  ralph task --runner claude --model opus "Add tests for X"
  ralph scan --runner cursor --model claude-opus-4-5-20251101 --focus "risk audit"
  ralph task --runner cursor --model claude-opus-4-5-20251101 "Add tests for X"
  ralph scan --runner kimi --focus "risk audit"
  ralph task --runner kimi --model kimi-for-coding "Add tests for X"
  ralph run one
  ralph run loop --max-tasks 1
  ralph run loop

More help:
  - Default help shows core commands only.
  - Run `ralph help-all` to see advanced and experimental commands."#)]
pub struct Cli {
    #[command(subcommand)]
    pub command: Command,

    /// Force operations (e.g., bypass stale queue locks; bypass clean-repo safety checks for commands that enforce them, e.g. `run one`, `run loop`, and `scan`).
    #[arg(long, global = true)]
    pub force: bool,

    /// Increase output verbosity (sets log level to info).
    #[arg(short, long, global = true)]
    pub verbose: bool,

    /// Color output control.
    #[arg(long, value_enum, default_value = "auto", global = true)]
    pub color: ColorArg,

    /// Disable colored output (alias for `--color never`).
    /// Also respects the NO_COLOR environment variable.
    #[arg(long, global = true)]
    pub no_color: bool,

    /// Automatically approve all migrations and fixes without prompting.
    /// Useful for CI/scripting environments.
    #[arg(long, global = true, conflicts_with = "no_sanity_checks")]
    pub auto_fix: bool,

    /// Skip startup sanity checks (migrations and unknown-key prompts).
    #[arg(long, global = true, conflicts_with = "auto_fix")]
    pub no_sanity_checks: bool,
}

#[derive(Subcommand)]
pub enum Command {
    Queue(queue::QueueArgs),
    Config(config::ConfigArgs),
    Run(Box<run::RunArgs>),
    Task(Box<task::TaskArgs>),
    Scan(scan::ScanArgs),
    Init(init::InitArgs),
    /// macOS app integration commands.
    App(app::AppArgs),
    /// Show core, advanced, and experimental command groups.
    HelpAll,
    /// Versioned machine-facing JSON API for the macOS app.
    #[command(hide = true)]
    Machine(Box<machine::MachineArgs>),
    /// Render and print the final compiled prompts used by Ralph (for debugging/auditing).
    #[command(
        hide = true,
        after_long_help = "Examples:\n  ralph prompt worker --phase 1 --repo-prompt plan\n  ralph prompt worker --phase 2 --task-id RQ-0001 --plan-file .ralph/cache/plans/RQ-0001.md\n  ralph prompt scan --focus \"CI gaps\" --repo-prompt off\n  ralph prompt task-builder --request \"Add tests\" --tags rust,tests --scope crates/ralph --repo-prompt tools\n"
    )]
    Prompt(prompt::PromptArgs),
    /// Verify environment readiness and configuration.
    #[command(
        hide = true,
        after_long_help = "Examples:\n  ralph doctor\n  ralph doctor --auto-fix\n  ralph doctor --no-sanity-checks\n  ralph doctor --format json\n  ralph doctor --format json --auto-fix"
    )]
    Doctor(doctor::DoctorArgs),
    /// Manage project context (AGENTS.md) for AI agents.
    #[command(
        hide = true,
        after_long_help = "Examples:\n  ralph context init\n  ralph context init --project-type rust\n  ralph context update --section troubleshooting\n  ralph context validate\n  ralph context update --dry-run"
    )]
    Context(context::ContextArgs),
    /// Manage Ralph daemon (background service).
    #[command(
        hide = true,
        after_long_help = "Examples:\n  ralph daemon start\n  ralph daemon start --empty-poll-ms 5000\n  ralph daemon stop\n  ralph daemon status"
    )]
    Daemon(daemon::DaemonArgs),
    /// Convert PRD (Product Requirements Document) markdown to tasks.
    #[command(
        hide = true,
        after_long_help = "Examples:\n  ralph prd create docs/prd/new-feature.md\n  ralph prd create docs/prd/new-feature.md --multi\n  ralph prd create docs/prd/new-feature.md --dry-run\n  ralph prd create docs/prd/new-feature.md --priority high --tag feature\n  ralph prd create docs/prd/new-feature.md --draft"
    )]
    Prd(prd::PrdArgs),
    /// Generate shell completion scripts.
    #[command(
        hide = true,
        after_long_help = "Examples:\n  ralph completions bash\n  ralph completions bash > ~/.local/share/bash-completion/completions/ralph\n  ralph completions zsh > ~/.zfunc/_ralph\n  ralph completions fish > ~/.config/fish/completions/ralph.fish\n  ralph completions powershell\n\nInstallation locations by shell:\n  Bash:   ~/.local/share/bash-completion/completions/ralph\n  Zsh:    ~/.zfunc/_ralph (and add 'fpath+=~/.zfunc' to ~/.zshrc)\n  Fish:   ~/.config/fish/completions/ralph.fish\n  PowerShell: Add to $PROFILE (see: $PROFILE | Get-Member -Type NoteProperty)"
    )]
    Completions(completions::CompletionsArgs),
    /// Check and apply migrations for config and project files.
    #[command(
        hide = true,
        after_long_help = "Examples:\n  ralph migrate              # Check for pending migrations\n  ralph migrate --check      # Exit with error code if migrations pending (CI)\n  ralph migrate --apply      # Apply all pending migrations\n  ralph migrate --list       # List all migrations and their status\n  ralph migrate status       # Show detailed migration status"
    )]
    Migrate(migrate::MigrateArgs),
    /// Clean up temporary files created by Ralph.
    #[command(
        hide = true,
        after_long_help = "Examples:\n  ralph cleanup              # Clean temp files older than 7 days\n  ralph cleanup --force      # Clean all ralph temp files\n  ralph cleanup --dry-run    # Show what would be deleted without deleting"
    )]
    Cleanup(cleanup::CleanupArgs),
    /// Display version information.
    #[command(after_long_help = "Examples:\n  ralph version\n  ralph version --verbose")]
    Version(version::VersionArgs),
    /// Watch files for changes and auto-detect tasks from TODO/FIXME/HACK/XXX comments.
    #[command(
        hide = true,
        after_long_help = "Examples:\n  ralph watch\n  ralph watch src/\n  ralph watch --patterns \"*.rs,*.toml\"\n  ralph watch --auto-queue\n  ralph watch --notify\n  ralph watch --comments todo,fixme\n  ralph watch --debounce-ms 1000\n  ralph watch --ignore-patterns \"vendor/,target/,node_modules/\""
    )]
    Watch(watch::WatchArgs),
    /// Webhook management commands.
    #[command(
        hide = true,
        after_long_help = "Examples:\n  ralph webhook test\n  ralph webhook test --event task_completed\n  ralph webhook status --format json\n  ralph webhook replay --dry-run --id wf-1700000000-1"
    )]
    Webhook(webhook::WebhookArgs),

    /// Productivity analytics (streaks, velocity, milestones).
    #[command(
        hide = true,
        after_long_help = "Examples:\n  ralph productivity summary\n  ralph productivity velocity\n  ralph productivity streak"
    )]
    Productivity(productivity::ProductivityArgs),

    /// Plugin management commands.
    #[command(
        hide = true,
        after_long_help = "Examples:\n  ralph plugin init my.plugin\n  ralph plugin init my.plugin --scope global\n  ralph plugin list\n  ralph plugin validate\n  ralph plugin install ./my-plugin --scope project\n  ralph plugin uninstall my.plugin --scope project"
    )]
    Plugin(plugin::PluginArgs),

    /// Runner management commands (capabilities, list).
    #[command(
        hide = true,
        after_long_help = "Examples:\n  ralph runner capabilities codex\n  ralph runner capabilities claude --format json\n  ralph runner list\n  ralph runner list --format json"
    )]
    Runner(runner::RunnerArgs),

    /// Run interactive tutorial for Ralph onboarding.
    #[command(
        hide = true,
        after_long_help = "Examples:\n  ralph tutorial\n  ralph tutorial --keep-sandbox\n  ralph tutorial --non-interactive"
    )]
    Tutorial(tutorial::TutorialArgs),

    /// Restore or preview an earlier continuation checkpoint.
    #[command(
        after_long_help = "Continuation workflow:\n  - `ralph undo --list` shows the checkpoints Ralph created before queue-changing operations.\n  - `ralph undo --dry-run` previews the restore path without modifying queue files.\n  - `ralph undo` restores the most recent checkpoint; `--id` restores a specific one.\n  - After restoring, run `ralph queue validate` and then continue normal work.\n\nExamples:\n  ralph undo\n  ralph undo --list\n  ralph undo --dry-run\n  ralph undo --id undo-20260215073000000000\n\nCheckpoints are created automatically before queue mutations such as:\n  - ralph task mutate / task decompose --write\n  - ralph task done/reject/start/ready/schedule\n  - ralph task edit/field/clone/split\n  - ralph task relate/blocks/mark-duplicate\n  - ralph queue archive/prune/sort/import/repair\n  - ralph queue issue publish/publish-many\n  - ralph task batch operations"
    )]
    Undo(undo::UndoArgs),

    /// Emit a machine-readable CLI specification (JSON) for tooling and legacy clients.
    #[command(name = "cli-spec", alias = "__cli-spec", hide = true)]
    CliSpec(CliSpecArgs),
}

#[derive(Args, Debug, Clone)]
pub struct CliSpecArgs {
    /// Output format.
    #[arg(long, value_enum, default_value_t = CliSpecFormatArg::Json)]
    pub format: CliSpecFormatArg,
}

#[derive(ValueEnum, Debug, Copy, Clone, PartialEq, Eq)]
pub enum CliSpecFormatArg {
    Json,
}