cargo-governor 2.0.3

Machine-First, LLM-Ready, CI/CD-Native release automation tool for Rust crates
Documentation
//! CLI module for cargo-governor

use crate::error::{CommandExitCode, Result};
use clap::{Parser, Subcommand};

pub mod agent;
pub mod mcp;
pub mod owners;
pub mod release;

pub use agent::{AgentSubCommands, ContextOpts, ExportDocsOpts};
pub use mcp::{McpServeOpts, McpSubCommands};
pub use owners::OwnersSubCommands;
pub use release::{
    AnalyzeOpts, BumpOpts, CheckOpts, PlanOpts, PublishOpts, ReleaseSubCommands, ResumeOpts,
    SimulateOpts, StatusOpts,
};

/// Output format
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OutputFormat {
    Json,
    Yaml,
    Human,
}

impl OutputFormat {
    pub fn from_str(s: &str) -> Option<Self> {
        match s.to_lowercase().as_str() {
            "json" => Some(Self::Json),
            "yaml" => Some(Self::Yaml),
            "human" => Some(Self::Human),
            _ => None,
        }
    }
}

/// cargo-governor: Machine-First, LLM-Ready, CI/CD-Native release automation tool
#[derive(Parser, Debug)]
#[command(name = "cargo-governor")]
#[command(bin_name = "cargo-governor")]
#[command(about, long_about = None)]
#[command(version)]
#[command(propagate_version = true)]
pub struct Cli {
    /// When invoked as 'cargo governor', cargo passes 'governor' as first arg
    #[arg(hide = true)]
    pub governor: Option<String>,

    /// Path to workspace root (default: current directory)
    #[arg(short = 'w', long, global = true)]
    pub workspace: Option<String>,

    /// Config file path (default: .cargo-governor.toml)
    #[arg(short = 'c', long, global = true)]
    pub config: Option<String>,

    /// Output format: json (default), yaml, human
    #[arg(short = 'f', long, global = true)]
    pub format: Option<String>,

    /// Write output to file
    #[arg(short = 'o', long, global = true)]
    pub output: Option<String>,

    /// Verbose logging
    #[arg(short = 'v', long, global = true)]
    pub verbose: bool,

    /// Suppress all output except result
    #[arg(short = 'q', long, global = true)]
    pub quiet: bool,

    /// Simulate without making changes
    #[arg(long, global = true)]
    pub dry_run: bool,

    #[command(subcommand)]
    pub command: Commands,
}

#[derive(Subcommand, Debug)]
pub enum Commands {
    /// Manage crate owners on crates.io
    #[command(name = "owners")]
    Owners {
        #[command(subcommand)]
        subcmd: OwnersSubCommands,
    },

    /// Release automation - analyze, plan, bump, publish
    #[command(name = "release")]
    Release {
        #[command(subcommand)]
        subcmd: ReleaseSubCommands,
    },

    /// Coding-agent context and discovery.
    #[command(name = "agent")]
    Agent {
        #[command(subcommand)]
        subcmd: AgentSubCommands,
    },

    /// Model Context Protocol server.
    #[command(name = "mcp")]
    Mcp {
        #[command(subcommand)]
        subcmd: McpSubCommands,
    },
}

/// Main entry point for the CLI
pub async fn run() -> Result<CommandExitCode> {
    use crate::cli::{Cli, Commands, OutputFormat};

    let args: Vec<String> = std::env::args().collect();

    // Check if we're being called as 'cargo governor'
    let cli = if args.len() > 1 && args[1] == "governor" {
        let args_without_governor: Vec<String> = std::iter::once(args[0].clone())
            .chain(args.into_iter().skip(2))
            .collect();
        Cli::parse_from(args_without_governor)
    } else {
        Cli::parse()
    };

    // Get output format (default: JSON for machine-first)
    let format = cli
        .format
        .and_then(|f| OutputFormat::from_str(&f))
        .unwrap_or(OutputFormat::Json);

    let workspace_path = cli.workspace.unwrap_or_else(|| ".".to_string());

    match cli.command {
        Commands::Owners { subcmd } => handle_owners(subcmd, &workspace_path, format).await,
        Commands::Release { subcmd } => {
            handle_release(subcmd, &workspace_path, format, cli.dry_run).await
        }
        Commands::Agent { subcmd } => handle_agent(subcmd),
        Commands::Mcp { subcmd } => handle_mcp(subcmd, &workspace_path).await,
    }
}

/// Handle owners subcommands
async fn handle_owners(
    subcmd: OwnersSubCommands,
    workspace_path: &str,
    format: OutputFormat,
) -> Result<CommandExitCode> {
    match subcmd {
        OwnersSubCommands::Show { .. } => {
            crate::commands::owners::show_cmd(workspace_path, &subcmd, format).await
        }
        OwnersSubCommands::Check { .. } => {
            crate::commands::owners::check_cmd(workspace_path, &subcmd, format).await
        }
        OwnersSubCommands::Sync { .. } => {
            crate::commands::owners::sync_cmd(workspace_path, &subcmd, format).await
        }
    }
}

/// Handle release subcommands
async fn handle_release(
    subcmd: ReleaseSubCommands,
    workspace_path: &str,
    format: OutputFormat,
    dry_run: bool,
) -> Result<CommandExitCode> {
    match subcmd {
        ReleaseSubCommands::Analyze(opts) => {
            crate::commands::release::analyze::execute(workspace_path, opts, format).await
        }
        ReleaseSubCommands::Plan(opts) => {
            crate::commands::release::plan::execute(workspace_path, opts, format).await
        }
        ReleaseSubCommands::Bump(opts) => {
            crate::commands::release::bump::execute(workspace_path, opts, format, dry_run).await
        }
        ReleaseSubCommands::Publish(opts) => {
            crate::commands::release::publish::execute(workspace_path, opts, format, dry_run).await
        }
        ReleaseSubCommands::Simulate(opts) => {
            crate::commands::release::simulate::execute(workspace_path, opts, format).await
        }
        ReleaseSubCommands::Check(opts) => {
            crate::commands::release::check::execute(workspace_path, opts, format).await
        }
        ReleaseSubCommands::Status(opts) => {
            crate::commands::release::status::execute(workspace_path, opts, format).await
        }
        ReleaseSubCommands::Resume(opts) => {
            crate::commands::release::resume::execute(workspace_path, opts, format).await
        }
        ReleaseSubCommands::Full(opts) => {
            crate::commands::release::full::execute(workspace_path, opts, format, dry_run).await
        }
    }
}

fn handle_agent(subcmd: AgentSubCommands) -> Result<CommandExitCode> {
    match subcmd {
        AgentSubCommands::Context(opts) => crate::commands::agent::context(&opts),
        AgentSubCommands::ExportDocs(opts) => crate::commands::agent::export_docs(&opts),
    }
}

async fn handle_mcp(subcmd: McpSubCommands, workspace_path: &str) -> Result<CommandExitCode> {
    match subcmd {
        McpSubCommands::Serve(opts) => crate::commands::mcp::serve(workspace_path, opts).await,
    }
}