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,
#[arg(long, short = 'y', conflicts_with = "no_skills")]
yes: bool,
#[arg(long)]
no_skills: bool,
},
Hook {
#[command(subcommand)]
event: HookEvent,
},
Status {
#[arg(long)]
json: bool,
#[arg(long, value_enum)]
metric: Option<StatusMetric>,
},
Logs(LogFilters),
Snapshots(LogFilters),
Checks(ChecksFilters),
Check(CheckArgs),
Fix {
#[command(subcommand)]
action: FixAction,
},
Skills {
#[command(subcommand)]
action: SkillsAction,
},
Calibrate {
#[arg(long)]
force: bool,
},
Compact {
#[arg(long)]
verbose: bool,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)]
pub enum StatusMetric {
Loc,
Complexity,
Churn,
ChangeCoupling,
Duplication,
Hotspot,
Lcom,
}
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",
Self::Lcom => "lcom",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)]
pub enum CheckMetric {
Ccn,
Cognitive,
Complexity,
Duplication,
Coupling,
Hotspot,
Lcom,
}
impl CheckMetric {
#[must_use]
pub fn matches(self, metric: &str) -> bool {
match self {
Self::Ccn => metric == "ccn",
Self::Cognitive => metric == "cognitive",
Self::Complexity => matches!(metric, "ccn" | "cognitive"),
Self::Duplication => metric == "duplication",
Self::Coupling => matches!(metric, "change_coupling" | "change_coupling.symmetric"),
Self::Hotspot => metric == "hotspot",
Self::Lcom => metric == "lcom",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)]
pub enum SeverityFilter {
Critical,
High,
Medium,
Ok,
}
impl SeverityFilter {
#[must_use]
pub fn into_severity(self) -> crate::core::severity::Severity {
use crate::core::severity::Severity;
match self {
Self::Critical => Severity::Critical,
Self::High => Severity::High,
Self::Medium => Severity::Medium,
Self::Ok => Severity::Ok,
}
}
}
#[derive(Debug, Clone, Copy, Subcommand)]
pub enum HookEvent {
Commit,
Edit,
Stop,
}
impl HookEvent {
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
Self::Commit => "commit",
Self::Edit => "edit",
Self::Stop => "stop",
}
}
}
#[derive(Debug, clap::Args)]
#[allow(clippy::struct_excessive_bools)] pub struct CheckArgs {
#[arg(long, value_enum)]
pub metric: Option<CheckMetric>,
#[arg(long)]
pub feature: Option<String>,
#[arg(long, value_enum)]
pub severity: Option<SeverityFilter>,
#[arg(long)]
pub all: bool,
#[arg(long)]
pub json: bool,
#[arg(long)]
pub refresh: bool,
#[arg(long, value_name = "N")]
pub top: Option<usize>,
}
#[derive(Debug, clap::Args)]
pub struct LogFilters {
#[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, clap::Args)]
pub struct ChecksFilters {
#[arg(long)]
pub since: Option<String>,
#[arg(long)]
pub limit: Option<usize>,
#[arg(long)]
pub json: bool,
}
#[derive(Debug, Subcommand)]
pub enum FixAction {
Show {
check_id: String,
#[arg(long)]
json: bool,
},
Diff {
#[arg(value_name = "FROM")]
from: Option<String>,
#[arg(value_name = "TO")]
to: Option<String>,
#[arg(long)]
all: bool,
#[arg(long)]
json: bool,
},
Mark {
#[arg(long, value_name = "ID")]
finding_id: String,
#[arg(long, value_name = "SHA")]
commit_sha: String,
},
}
#[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,
yes,
no_skills,
} => commands::init::run(&project, force, yes, no_skills),
Command::Hook { event } => commands::hook::run(&project, event),
Command::Status { json, metric } => commands::status::run(&project, json, metric),
Command::Logs(args) => commands::logs::run_logs(&project, &args),
Command::Snapshots(args) => commands::logs::run_snapshots(&project, &args),
Command::Checks(args) => commands::logs::run_checks(&project, &args),
Command::Check(args) => commands::check::run(&project, &args),
Command::Fix { action } => commands::fix::run(&project, action),
Command::Skills { action } => commands::skills::run(&project, action),
Command::Calibrate { force } => commands::calibrate::run(&project, force),
Command::Compact { verbose } => commands::compact::run(&project, verbose),
}
}
}