1use std::path::Path;
4
5use serde::Serialize;
6
7use crate::error::MarsError;
8use crate::lock::ItemKind;
9
10use super::output;
11
12#[derive(Debug, clap::Args)]
14pub struct WhyArgs {
15 pub name: String,
17}
18
19#[derive(Debug, Serialize)]
20struct WhyResult {
21 name: String,
22 kind: String,
23 source: String,
24 version: String,
25 dest_path: String,
26 required_by: Vec<String>,
27}
28
29pub fn run(args: &WhyArgs, ctx: &super::MarsContext, json: bool) -> Result<i32, MarsError> {
31 let lock = crate::lock::load(&ctx.project_root)?;
32
33 let mut found = None;
35 for (dest_path, item) in lock.flat_items() {
36 let name_matches =
37 dest_path.item_name(item.kind) == args.name || dest_path.as_str() == args.name;
38
39 if name_matches {
40 found = Some((dest_path, item));
41 break;
42 }
43 }
44
45 let (dest_path, item) = match found {
46 Some(f) => f,
47 None => {
48 return Err(MarsError::Source {
49 source_name: "why".to_string(),
50 message: format!("item `{}` not found in lock file", args.name),
51 });
52 }
53 };
54
55 let mars_dir = ctx.project_root.join(".mars");
57 let required_by = if item.kind == ItemKind::Skill {
58 let skill_name = dest_path.item_name(ItemKind::Skill);
59 find_referencing_agents(&mars_dir, &lock, &skill_name)
60 } else {
61 Vec::new()
62 };
63
64 let result = WhyResult {
65 name: args.name.clone(),
66 kind: item.kind.to_string(),
67 source: item.source.to_string(),
68 version: item.version.clone().unwrap_or_else(|| "-".to_string()),
69 dest_path: dest_path.to_string(),
70 required_by: required_by.clone(),
71 };
72
73 if json {
74 output::print_json(&result);
75 } else {
76 println!("{} ({})", args.name, item.kind);
77 println!(
78 " provided by: {}@{}",
79 item.source,
80 item.version.as_deref().unwrap_or("-")
81 );
82 println!(" installed at: {dest_path}");
83 if required_by.is_empty() {
84 println!(" required by: (no dependents)");
85 } else {
86 println!(" required by:");
87 for agent in &required_by {
88 println!(" {agent}");
89 }
90 }
91 }
92
93 Ok(0)
94}
95
96fn find_referencing_agents(
98 root: &Path,
99 lock: &crate::lock::LockFile,
100 skill_name: &str,
101) -> Vec<String> {
102 let mut refs = Vec::new();
103
104 for (dest_path, item) in lock.flat_items() {
105 if item.kind != ItemKind::Agent {
106 continue;
107 }
108
109 let agent_path = dest_path.resolve(root);
110 if let Ok(skills) = crate::validate::parse_agent_skills(&agent_path)
111 && skills.iter().any(|s| s == skill_name)
112 {
113 refs.push(dest_path.to_string());
114 }
115 }
116
117 refs.sort();
118 refs
119}