use std::process::Command;
const GUIDES: &[(&str, &str)] = &[
(
"01-quickstart",
include_str!("../../../docs/ai-guide/01-quickstart.md"),
),
("05-cli", include_str!("../../../docs/ai-guide/05-cli.md")),
(
"08-cookbook",
include_str!("../../../docs/ai-guide/08-cookbook.md"),
),
("11-mcp", include_str!("../../../docs/ai-guide/11-mcp.md")),
];
fn help_for(path: &[&str]) -> Option<String> {
let exe = env!("CARGO_BIN_EXE_smix");
let out = Command::new(exe).args(path).arg("--help").output().ok()?;
if !out.status.success() {
return None;
}
Some(String::from_utf8_lossy(&out.stdout).into_owned())
}
fn help_declares_flag(help: &str, flag: &str) -> bool {
help.lines().any(|l| {
let l = l.trim_start();
l.starts_with(&format!("--{flag}"))
|| l.contains(&format!(", --{flag} "))
|| l.contains(&format!(", --{flag}<"))
|| l.trim_end() == format!("--{flag}")
})
}
#[allow(clippy::type_complexity)]
fn commands_in(doc: &str) -> Vec<(Vec<String>, Vec<String>, Vec<String>)> {
let mut out = Vec::new();
for line in doc.lines() {
let line = line.trim().trim_start_matches("$ ");
let Some(rest) = line.strip_prefix("smix ") else {
continue;
};
let rest = rest
.split(" | ")
.next()
.unwrap_or(rest)
.split(" # ")
.next()
.unwrap_or(rest)
.split(" && ")
.next()
.unwrap_or(rest)
.split(" || ")
.next()
.unwrap_or(rest)
.split(" > ")
.next()
.unwrap_or(rest);
let mut path: Vec<String> = Vec::new();
let mut flags: Vec<String> = Vec::new();
let mut extras: Vec<String> = Vec::new();
let mut prev_was_flag = false;
for tok in rest.split_whitespace() {
if let Some(f) = tok.strip_prefix("--") {
let f = f.split('=').next().unwrap_or(f);
let f = f.trim_end_matches(|c: char| !c.is_alphanumeric() && c != '-');
if !f.is_empty() {
flags.push(f.to_string());
}
prev_was_flag = true;
continue;
} else if flags.is_empty()
&& extras.is_empty()
&& tok.chars().all(|c| c.is_ascii_lowercase() || c == '-')
&& !tok.is_empty()
{
path.push(tok.to_string());
} else if prev_was_flag {
} else if !tok.starts_with('\\') && !tok.starts_with('#') && !tok.starts_with('|') {
extras.push(tok.to_string());
}
prev_was_flag = false;
}
if !path.is_empty() && (!flags.is_empty() || !extras.is_empty()) {
out.push((path, flags, extras));
}
}
out
}
fn positional_arity(help: &str) -> usize {
help.lines()
.find(|l| l.trim_start().starts_with("Usage:"))
.map(|usage| {
usage
.split_whitespace()
.filter(|t| {
(t.starts_with('<') || t.starts_with('['))
&& !t.contains("OPTIONS")
&& !t.contains("COMMAND")
})
.count()
})
.unwrap_or(0)
}
#[test]
fn every_command_the_guides_print_matches_the_cli_surface() {
let mut checked = 0usize;
let mut bogus: Vec<String> = Vec::new();
for (name, doc) in GUIDES {
for (path, flags, extras) in commands_in(doc) {
let refs: Vec<&str> = path.iter().map(String::as_str).collect();
let Some(help) = help_for(&refs) else {
continue;
};
for flag in &flags {
if help_declares_flag(&help, flag) {
checked += 1;
} else {
bogus.push(format!("{name}: `smix {} --{flag}`", refs.join(" ")));
}
}
let arity = positional_arity(&help);
if arity == 0 && !extras.is_empty() {
bogus.push(format!(
"{name}: `smix {} {}` — that subcommand takes no positional argument",
refs.join(" "),
extras.join(" ")
));
}
}
}
assert!(
checked >= 10,
"extracted only {checked} flag usages from the guides — the \
extraction stopped matching and this check would pass by \
knowing nothing"
);
assert!(
bogus.is_empty(),
"the guides tell users to type flags the CLI does not have:\n {}",
bogus.join("\n ")
);
}
#[test]
fn every_repo_path_the_guides_name_exists() {
let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.and_then(|p| p.parent())
.expect("workspace root");
const ROOTS: &[&str] = &[
"examples/",
"flows/",
"docs/",
"crates/",
"scripts/",
"swift-bridge/",
"android-runner/",
"npm/",
"web/",
"dashboard/",
];
const PATH_DOCS: &[(&str, &str)] = &[
("README", include_str!("../../../README.md")),
(
"03-selectors",
include_str!("../../../docs/ai-guide/03-selectors.md"),
),
(
"07-errors",
include_str!("../../../docs/ai-guide/07-errors.md"),
),
(
"09-sessions",
include_str!("../../../docs/ai-guide/09-sessions.md"),
),
];
let mut checked = 0usize;
let mut missing: Vec<String> = Vec::new();
for (name, doc) in GUIDES.iter().chain(PATH_DOCS) {
for raw in doc.split(|c: char| c.is_whitespace() || c == '`' || c == '(' || c == ')') {
let tok = raw.trim_matches(|c: char| matches!(c, ',' | '.' | ':' | ';' | '"' | '\''));
if tok.contains('<') || tok.contains('*') || tok.contains('$') {
continue; }
if !ROOTS.iter().any(|r| tok.starts_with(r)) {
continue;
}
if !tok.rsplit('/').next().is_some_and(|f| f.contains('.')) {
continue;
}
if tok
.split('/')
.any(|seg| matches!(seg, "build" | "target" | "dist" | "outputs"))
{
continue;
}
checked += 1;
if !root.join(tok).exists() {
missing.push(format!("{name}: {tok}"));
}
}
}
assert!(
checked >= 3,
"found only {checked} repo paths in the guides — the extraction \
stopped matching and this check would pass by knowing nothing. \
(The guides cite few in-repo files; 3 is the floor observed \
when this was written, not a target.)"
);
assert!(
missing.is_empty(),
"the guides name repo files that do not exist:\n {}",
missing.join("\n ")
);
}
#[test]
fn every_command_the_cli_offers_is_in_the_reference() {
const CLI_GUIDE: &str = include_str!("../../../docs/ai-guide/05-cli.md");
let exe = env!("CARGO_BIN_EXE_smix");
let out = Command::new(exe)
.arg("--help")
.output()
.expect("smix --help runs");
let help = String::from_utf8_lossy(&out.stdout);
let mut in_commands = false;
let mut commands: Vec<String> = Vec::new();
for line in help.lines() {
if line.starts_with("Commands:") {
in_commands = true;
continue;
}
if in_commands {
if line.trim().is_empty() || line.starts_with(|c: char| !c.is_whitespace()) {
break;
}
if let Some(name) = line.split_whitespace().next()
&& name.chars().all(|c| c.is_ascii_lowercase() || c == '-')
{
commands.push(name.to_string());
}
}
}
assert!(
commands.len() >= 10,
"found only {commands:?} in --help — the parse stopped matching and \
this check would pass by knowing nothing"
);
let undocumented: Vec<&String> = commands
.iter()
.filter(|c| *c != "help")
.filter(|c| !CLI_GUIDE.contains(&format!("smix {c}")))
.collect();
assert!(
undocumented.is_empty(),
"the CLI offers commands the reference never mentions: {undocumented:?}\n\
A command a user cannot find is one that does not exist to them."
);
}