use std::collections::BTreeSet;
use std::process::Command;
fn skill_md() -> String {
let dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../../plugin/skills/cliban");
let mut md = std::fs::read_to_string(dir.join("SKILL.md"))
.unwrap_or_else(|e| panic!("read {}: {e}", dir.join("SKILL.md").display()));
if let Ok(refs) = std::fs::read_dir(dir.join("references")) {
for entry in refs.flatten() {
let path = entry.path();
if path.extension().is_some_and(|e| e == "md") {
md.push('\n');
md.push_str(
&std::fs::read_to_string(&path)
.unwrap_or_else(|e| panic!("read {}: {e}", path.display())),
);
}
}
}
md
}
fn documented_paths(md: &str) -> BTreeSet<Vec<String>> {
let mut out = BTreeSet::new();
let mut in_code = false;
for line in md.lines() {
if line.trim_start().starts_with("```") {
in_code = !in_code;
continue;
}
let line = line.trim();
if in_code {
let Some(rest) = line.strip_prefix("cliban ") else {
continue;
};
let path: Vec<String> = rest
.split_whitespace()
.take_while(|w| !w.starts_with(['-', '#', '<', '>', '|', '$']))
.take(2)
.map(str::to_string)
.collect();
if !path.is_empty() {
out.insert(path);
}
continue;
}
if !line.starts_with('|') {
continue;
}
let Some(span) = line.trim_start_matches('|').trim().strip_prefix('`') else {
continue;
};
let Some((span, _)) = span.split_once('`') else {
continue;
};
let cleaned = span.replace("\\|", "|");
let mut words = cleaned.split_whitespace();
let Some(noun) = words.next() else { continue };
if noun.is_empty() || !noun.chars().all(|c| c.is_ascii_lowercase()) {
continue;
}
match words.next() {
None => {
out.insert(vec![noun.to_string()]);
}
Some(verbs) => {
for v in verbs.split('|').filter(|v| !v.is_empty()) {
out.insert(vec![noun.to_string(), v.to_string()]);
}
}
}
}
out
}
fn feature_gated(path: &[String]) -> bool {
cfg!(not(feature = "linear")) && path[0] == "linear"
}
#[test]
fn every_command_the_skill_names_exists() {
let md = skill_md();
let paths = documented_paths(&md);
assert!(
paths.len() > 20,
"expected the skill to document a real command surface, found {}",
paths.len()
);
let mut missing = Vec::new();
for path in paths.iter().filter(|p| !feature_gated(p)) {
let out = Command::new(env!("CARGO_BIN_EXE_cliban"))
.args(path)
.arg("--help")
.output()
.expect("run cliban --help");
if !out.status.success() {
missing.push(path.join(" "));
}
}
assert!(
missing.is_empty(),
"SKILL.md documents commands that no longer exist: {missing:?}"
);
}
#[test]
fn the_skill_names_every_top_level_command() {
let md = skill_md();
let out = Command::new(env!("CARGO_BIN_EXE_cliban"))
.arg("--help")
.output()
.unwrap();
let help = String::from_utf8_lossy(&out.stdout);
let commands: Vec<&str> = help
.lines()
.skip_while(|l| !l.starts_with("Commands:"))
.skip(1)
.take_while(|l| l.starts_with(" ") && !l.trim().is_empty())
.filter_map(|l| l.split_whitespace().next())
.filter(|c| !matches!(*c, "help"))
.collect();
assert!(!commands.is_empty(), "could not parse `cliban --help`");
let undocumented: Vec<&str> = commands
.iter()
.copied()
.filter(|c| !md.contains(&format!("`{c}")) && !md.contains(&format!("cliban {c}")))
.collect();
assert!(
undocumented.is_empty(),
"these commands exist but SKILL.md never mentions them: {undocumented:?}"
);
}