use crate::cli::{resolve_db_path, Cli, HotpathsArgs, OutputFormat};
use crate::output;
use anyhow::Result;
pub fn hotpaths(args: &HotpathsArgs, cli: &Cli) -> Result<()> {
use crate::cfg::{
detect_natural_loops, enumerate_paths, find_entry,
hotpaths::{compute_hot_paths, HotpathsOptions},
PathLimits,
};
use crate::storage::MirageDb;
let db_path = resolve_db_path(cli.db.clone())?;
let db = match MirageDb::open(&db_path) {
Ok(db) => db,
Err(_e) => {
if matches!(cli.output, OutputFormat::Json | OutputFormat::Pretty) {
let error = output::JsonError::database_not_found(&db_path);
let wrapper = output::JsonResponse::new(error);
println!("{}", wrapper.to_json());
std::process::exit(output::EXIT_DATABASE);
} else {
output::error(&format!("Failed to open database: {}", db_path));
output::info("Hint: Run 'magellan watch' to create the database");
std::process::exit(output::EXIT_DATABASE);
}
}
};
let function_id = match crate::storage::resolve_function_or_semantic(
&db,
&args.function,
args.semantic_query.as_deref(),
None,
) {
Ok(id) => id,
Err(e) => {
if matches!(cli.output, OutputFormat::Json | OutputFormat::Pretty) {
let error = output::JsonError::new(
"FunctionNotFound",
&format!("{}", e),
output::E_CFG_ERROR,
);
let wrapper = output::JsonResponse::new(error);
println!("{}", wrapper.to_json());
std::process::exit(output::EXIT_DATABASE);
} else {
output::error(&format!("{}", e));
output::info(&format!("Hint: {}", output::R_HINT_LIST_FUNCTIONS));
std::process::exit(output::EXIT_DATABASE);
}
}
};
let cfg = match db.load_cfg(function_id) {
Ok(cfg) => cfg,
Err(_e) => {
if matches!(cli.output, OutputFormat::Json | OutputFormat::Pretty) {
let error = output::JsonError::new(
"CfgLoadError",
&format!("Failed to load CFG for function '{}'", args.function),
output::E_CFG_ERROR,
);
let wrapper = output::JsonResponse::new(error);
println!("{}", wrapper.to_json());
std::process::exit(output::EXIT_DATABASE);
} else {
output::error(&format!(
"Failed to load CFG for function '{}'",
args.function
));
output::info("The function may be corrupted. Try re-running 'magellan watch'");
std::process::exit(output::EXIT_DATABASE);
}
}
};
let entry = match find_entry(&cfg) {
Some(entry) => entry,
None => {
output::error(&format!(
"No entry block found for function '{}'",
args.function
));
std::process::exit(output::EXIT_DATABASE);
}
};
let natural_loops = detect_natural_loops(&cfg);
let limits = PathLimits::default();
let paths = enumerate_paths(&cfg, &limits);
if paths.is_empty() {
output::info(&format!("No paths found for function '{}'", args.function));
return Ok(());
}
let options = HotpathsOptions {
top_n: args.top,
include_rationale: args.rationale,
};
let mut hot_paths = match compute_hot_paths(&cfg, &paths, entry, &natural_loops, options) {
Ok(hp) => hp,
Err(e) => {
output::error(&format!("Failed to compute hot paths: {}", e));
std::process::exit(output::EXIT_DATABASE);
}
};
if let Some(min_score) = args.min_score {
hot_paths.retain(|hp| hp.hotness_score >= min_score);
}
match cli.output {
OutputFormat::Human => {
print_hotpaths_human(&hot_paths, args.rationale, args.tokens);
}
OutputFormat::Json => {
let json_str = serde_json::to_string(&hot_paths)?;
let processed = output::apply_token_budget(json_str, args.tokens);
println!("{}", processed);
}
OutputFormat::Pretty => {
let json_str = serde_json::to_string_pretty(&hot_paths)?;
let processed = output::apply_token_budget(json_str, args.tokens);
println!("{}", processed);
}
}
Ok(())
}
fn print_hotpaths_human(
hot_paths: &[crate::cfg::hotpaths::HotPath],
show_rationale: bool,
tokens: Option<usize>,
) {
use crate::output;
let mut output = String::new();
output.push_str(&format!("Hot Paths (top {})\n", hot_paths.len()));
output.push('\n');
if hot_paths.is_empty() {
output.push_str("No hot paths found\n");
let processed = output::apply_token_budget(output, tokens);
print!("{}", processed);
return;
}
for (i, hp) in hot_paths.iter().enumerate() {
output.push_str(&format!(
"\n{}. Path {} - Score: {:.2}\n",
i + 1,
hp.path_id,
hp.hotness_score
));
if show_rationale && !hp.rationale.is_empty() {
output.push_str(" Rationale:\n");
for r in &hp.rationale {
output.push_str(&format!(" - {}\n", r));
}
}
output.push_str(&format!(" Blocks: {} blocks\n", hp.blocks.len()));
for (j, block) in hp.blocks.iter().enumerate() {
if j < 5 || j == hp.blocks.len() - 1 {
output.push_str(&format!(" {}\n", block));
if j == 4 && hp.blocks.len() > 6 {
output.push_str(&format!(" ... (+{} more)\n", hp.blocks.len() - 6));
break;
}
}
}
}
let processed = output::apply_token_budget(output, tokens);
print!("{}", processed);
}