use std::path::Path;
use serde::Serialize;
use crate::error::MarsError;
use crate::lock::ItemKind;
use super::output;
#[derive(Debug, clap::Args)]
pub struct WhyArgs {
pub name: String,
}
#[derive(Debug, Serialize)]
struct WhyResult {
name: String,
kind: String,
source: String,
version: String,
dest_path: String,
required_by: Vec<String>,
}
pub fn run(args: &WhyArgs, ctx: &super::MarsContext, json: bool) -> Result<i32, MarsError> {
let lock = crate::lock::load(&ctx.project_root)?;
let mut found = None;
for (dest_path, item) in &lock.items {
let name_matches = match item.kind {
ItemKind::Agent => {
let last = dest_path.as_str().rsplit('/').next().unwrap_or("");
let stem = last.strip_suffix(".md").unwrap_or(last);
stem == args.name || dest_path.to_string() == args.name
}
ItemKind::Skill => {
let dir_name = dest_path.as_str().rsplit('/').next().unwrap_or("");
dir_name == args.name || dest_path.to_string() == args.name
}
};
if name_matches {
found = Some((dest_path.clone(), item.clone()));
break;
}
}
let (dest_path, item) = match found {
Some(f) => f,
None => {
return Err(MarsError::Source {
source_name: "why".to_string(),
message: format!("item `{}` not found in lock file", args.name),
});
}
};
let mars_dir = ctx.project_root.join(".mars");
let required_by = if item.kind == ItemKind::Skill {
find_referencing_agents(&mars_dir, &lock, &args.name)
} else {
Vec::new()
};
let result = WhyResult {
name: args.name.clone(),
kind: item.kind.to_string(),
source: item.source.to_string(),
version: item.version.clone().unwrap_or_else(|| "-".to_string()),
dest_path: dest_path.to_string(),
required_by: required_by.clone(),
};
if json {
output::print_json(&result);
} else {
println!("{} ({})", args.name, item.kind);
println!(
" provided by: {}@{}",
item.source,
item.version.as_deref().unwrap_or("-")
);
println!(" installed at: {dest_path}");
if required_by.is_empty() {
println!(" required by: (no dependents)");
} else {
println!(" required by:");
for agent in &required_by {
println!(" {agent}");
}
}
}
Ok(0)
}
fn find_referencing_agents(
root: &Path,
lock: &crate::lock::LockFile,
skill_name: &str,
) -> Vec<String> {
let mut refs = Vec::new();
for (dest_path, item) in &lock.items {
if item.kind != ItemKind::Agent {
continue;
}
let agent_path = dest_path.resolve(root);
if let Ok(skills) = crate::validate::parse_agent_skills(&agent_path)
&& skills.iter().any(|s| s == skill_name)
{
refs.push(dest_path.to_string());
}
}
refs.sort();
refs
}