use anyhow::Result;
use clap::{Parser, ValueEnum};
use std::path::PathBuf;
use candle_graph::cli::{self, AnalyzeRequest, ModelRun, OutputFormat, ReportMode};
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
enum Format {
Tree,
Json,
#[cfg(feature = "visualizer")]
Html,
}
#[derive(Parser, Debug)]
#[command(
name = "candle-graph",
about = "Static structure, dtype, and gradient analysis for candle-rs models",
long_about = "Crate-wide model IR, bounded agent queries, and optional HTML visualization. \
Prefer `cargo candle-graph check` for Clippy-like package analysis."
)]
struct Args {
#[arg(value_name = "DIR")]
dir: PathBuf,
#[arg(long, value_name = "NAME")]
root: Option<String>,
#[arg(long, value_enum, default_value_t = Format::Tree)]
format: Format,
#[arg(long, value_name = "KIND")]
query: Option<String>,
#[arg(long, value_name = "SELECTOR", requires = "query")]
select: Option<String>,
#[arg(long, value_name = "SELECTOR", requires = "query")]
to: Option<String>,
#[arg(long, default_value_t = 100, requires = "query")]
limit: usize,
#[arg(long, default_value_t = 0, requires = "query")]
offset: usize,
#[cfg(feature = "runtime")]
#[arg(long, value_name = "FILE")]
runtime_trace: Option<PathBuf>,
#[arg(long, value_delimiter = ',', value_name = "FEATURE")]
features: Vec<String>,
#[arg(long, conflicts_with = "no_default_features")]
all_features: bool,
#[arg(long)]
no_default_features: bool,
#[arg(long, value_name = "TRIPLE")]
target: Option<String>,
#[arg(long, value_name = "NAME")]
cargo_target: Option<String>,
#[arg(long)]
heuristic_architecture: bool,
#[arg(long, value_name = "FILE")]
output: Option<PathBuf>,
#[arg(long)]
diagnostics: bool,
#[arg(long)]
strict: bool,
#[arg(long, value_name = "FILE", conflicts_with = "update_baseline")]
check: Option<PathBuf>,
#[arg(long, value_name = "FILE", conflicts_with = "check")]
update_baseline: Option<PathBuf>,
#[arg(long, value_name = "DIR", conflicts_with_all = ["output", "query"])]
audit_dir: Option<PathBuf>,
#[arg(long, value_name = "RULES", value_delimiter = ',')]
deny: Vec<String>,
#[arg(long)]
cache: bool,
#[arg(long, value_name = "FILE", requires = "audit_dir")]
audit_checkpoint: Option<PathBuf>,
#[arg(long, value_name = "NAME", requires = "audit_dir")]
audit_verify_root: Option<String>,
#[arg(long, value_name = "FILE", conflicts_with = "audit_dir")]
checkpoint: Option<PathBuf>,
#[arg(long, value_name = "NAME", requires = "checkpoint")]
verify_root: Option<String>,
}
fn main() -> Result<()> {
let args = Args::parse();
let format = match args.format {
Format::Json => OutputFormat::Json,
Format::Tree => OutputFormat::Tree,
#[cfg(feature = "visualizer")]
Format::Html => OutputFormat::Html,
};
let report = if let Some(kind) = args.query.as_deref() {
ReportMode::Query {
kind: kind.to_string(),
selector: args.select.clone(),
to: args.to.clone(),
limit: args.limit,
offset: args.offset,
format,
}
} else {
ReportMode::FullIr { format }
};
cli::run_model(&ModelRun {
analyze: AnalyzeRequest {
path: args.dir,
cargo: candle_graph::cargo_context::CargoOptions {
features: args.features,
all_features: args.all_features,
no_default_features: args.no_default_features,
target: args.target,
package_target: args.cargo_target,
},
#[cfg(feature = "runtime")]
runtime_trace: args.runtime_trace,
component_root: args.root,
dataflow: true,
heuristic_architecture: args.heuristic_architecture,
use_cache: args.cache,
},
report,
output: args.output,
diagnostics: args
.diagnostics
.then_some(candle_graph::diagnostics::MessageFormat::Human),
fail_on_warning_error: args.strict,
deny_rules: args.deny.clone(),
check_baseline: args.check,
update_baseline: args.update_baseline,
audit: args.audit_dir.map(|output_dir| cli::AuditBundle {
output_dir,
checkpoint: args.audit_checkpoint,
verify_root: args.audit_verify_root,
deny_rules: args.deny.clone(),
strict: args.strict,
}),
checkpoint: args.checkpoint,
verify_root: args.verify_root,
})
}