use std::path::PathBuf;
use anyhow::Result;
use clap::{Parser, Subcommand};
use crate::commands;
#[derive(Debug, Parser)]
#[command(name = "heal", version, about = "Code health hook-driven harness", long_about = None)]
pub struct Cli {
#[arg(long, global = true)]
pub project: Option<PathBuf>,
#[command(subcommand)]
pub command: Command,
}
#[derive(Debug, Subcommand)]
pub enum Command {
Init {
#[arg(long)]
force: bool,
},
Hook {
#[command(subcommand)]
event: HookEvent,
},
Status {
#[arg(long)]
json: bool,
#[arg(long, value_enum)]
metric: Option<StatusMetric>,
},
Logs(LogsArgs),
Check(CheckArgs),
Skills {
#[command(subcommand)]
action: SkillsAction,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)]
pub enum StatusMetric {
Loc,
Complexity,
Churn,
ChangeCoupling,
Duplication,
Hotspot,
}
impl StatusMetric {
#[must_use]
pub fn json_key(self) -> &'static str {
match self {
Self::Loc => "loc",
Self::Complexity => "complexity",
Self::Churn => "churn",
Self::ChangeCoupling => "change_coupling",
Self::Duplication => "duplication",
Self::Hotspot => "hotspot",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)]
pub enum OutputFormat {
Auto,
Plain,
Markdown,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)]
pub enum CheckSkill {
Overview,
Hotspots,
Complexity,
Duplication,
Coupling,
}
impl CheckSkill {
#[must_use]
pub fn skill_name(self) -> &'static str {
match self {
Self::Overview => "check-overview",
Self::Hotspots => "check-hotspots",
Self::Complexity => "check-complexity",
Self::Duplication => "check-duplication",
Self::Coupling => "check-coupling",
}
}
#[must_use]
pub fn short_name(self) -> &'static str {
match self {
Self::Overview => "overview",
Self::Hotspots => "hotspots",
Self::Complexity => "complexity",
Self::Duplication => "duplication",
Self::Coupling => "coupling",
}
}
#[must_use]
pub fn for_rule(rule_id: &str) -> Option<Self> {
match rule_id.split('.').next()? {
"hotspot" => Some(Self::Hotspots),
"complexity" => Some(Self::Complexity),
"duplication" => Some(Self::Duplication),
"change_coupling" => Some(Self::Coupling),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, Subcommand)]
pub enum HookEvent {
Commit,
Edit,
Stop,
SessionStart,
}
impl HookEvent {
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
Self::Commit => "commit",
Self::Edit => "edit",
Self::Stop => "stop",
Self::SessionStart => "session-start",
}
}
}
#[derive(Debug, clap::Args)]
pub struct CheckArgs {
#[arg(value_enum, default_value_t = CheckSkill::Overview)]
pub skill: CheckSkill,
#[arg(long, value_enum, default_value_t = OutputFormat::Auto)]
pub format: OutputFormat,
#[arg(long, conflicts_with = "raw")]
pub quiet: bool,
#[arg(long, conflicts_with = "quiet")]
pub raw: bool,
#[arg(last = true, allow_hyphen_values = true)]
pub claude_args: Vec<String>,
}
#[derive(Debug, clap::Args)]
pub struct LogsArgs {
#[arg(long)]
pub since: Option<String>,
#[arg(long)]
pub filter: Option<String>,
#[arg(long)]
pub limit: Option<usize>,
#[arg(long)]
pub json: bool,
}
#[derive(Debug, Clone, Copy, Subcommand)]
pub enum SkillsAction {
Install {
#[arg(long)]
force: bool,
},
Update {
#[arg(long)]
force: bool,
},
Status,
Uninstall,
}
impl Cli {
pub fn run(self) -> Result<()> {
let project = self
.project
.unwrap_or_else(|| std::env::current_dir().expect("cwd"));
match self.command {
Command::Init { force } => commands::init::run(&project, force),
Command::Hook { event } => commands::hook::run(&project, event),
Command::Status { json, metric } => commands::status::run(&project, json, metric),
Command::Logs(args) => commands::logs::run(&project, &args),
Command::Check(args) => commands::check::run(&project, &args),
Command::Skills { action } => commands::skills::run(&project, action),
}
}
}