use std::path::Path;
pub fn warn_if_not_loadable(
name: &str,
git: &str,
reference: &str,
subpath: Option<&str>,
content: &Path,
) {
if is_regular_file(&content.join("SKILL.md")) {
return;
}
let subskills = child_skills(content);
if subskills.is_empty() {
eprintln!("warning: skill `{name}` has no SKILL.md at its root — agents may ignore it");
return;
}
let selector = selector_flag(reference);
let plural = if subskills.len() == 1 { "" } else { "s" };
eprintln!(
"warning: skill `{name}` has no SKILL.md at its root, but its directory contains {} skill{plural}.",
subskills.len()
);
eprintln!(
" `--path {}` points at a collection of skills, not a single skill. Did you mean one of:",
subpath.unwrap_or(".")
);
for sub in &subskills {
let path = join_subpath(subpath, sub);
eprintln!(" spm add {git} {selector} --path {path} --name {sub}");
}
}
fn selector_flag(reference: &str) -> String {
match reference.split_once(':') {
Some((kind @ ("tag" | "branch" | "commit"), value)) => format!("--{kind} {value}"),
_ => reference.to_string(),
}
}
fn child_skills(dir: &Path) -> Vec<String> {
let mut names: Vec<String> = match std::fs::read_dir(dir) {
Ok(entries) => entries
.flatten()
.filter(|e| is_regular_file(&e.path().join("SKILL.md")))
.filter_map(|e| e.file_name().into_string().ok())
.collect(),
Err(_) => Vec::new(),
};
names.sort();
names
}
fn is_regular_file(path: &Path) -> bool {
std::fs::symlink_metadata(path)
.map(|m| m.file_type().is_file())
.unwrap_or(false)
}
fn join_subpath(parent: Option<&str>, child: &str) -> String {
match parent {
Some(p) if !p.is_empty() => {
let p = p.replace('\\', "/");
format!("{}/{child}", p.trim_end_matches('/'))
}
_ => child.to_string(),
}
}
#[cfg(test)]
mod tests {
use super::{join_subpath, selector_flag};
#[test]
fn join_subpath_combines_with_forward_slash() {
assert_eq!(
join_subpath(Some("skills"), "camunda-ds"),
"skills/camunda-ds"
);
assert_eq!(join_subpath(Some("skills/"), "migrate"), "skills/migrate");
assert_eq!(join_subpath(Some("a\\b\\"), "c"), "a/b/c");
assert_eq!(join_subpath(None, "greet"), "greet");
assert_eq!(join_subpath(Some(""), "greet"), "greet");
}
#[test]
fn selector_flag_renders_runnable_flags() {
assert_eq!(selector_flag("tag:v1.2.0"), "--tag v1.2.0");
assert_eq!(selector_flag("branch:main"), "--branch main");
assert_eq!(
selector_flag("commit:0123456789abcdef0123456789abcdef01234567"),
"--commit 0123456789abcdef0123456789abcdef01234567"
);
assert_eq!(selector_flag("weird"), "weird");
}
}