use std::path::PathBuf;
fn root() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
}
#[test]
fn gap_doc_004_product_line_040_e_file_only() {
let readme = std::fs::read_to_string(root().join("README.md")).expect("README");
assert!(
readme.contains("0.5.0") || readme.contains("0.4.0") || readme.contains("0.4.1"),
"README must mention product line 0.5.x / 0.4.x history"
);
let lower = readme.to_lowercase();
assert!(
lower.contains("regular file")
|| lower.contains("file-only")
|| lower.contains("files only")
|| lower.contains("not directories"),
"README must document file-only SCP limit"
);
assert!(
readme.contains("scp-transfer") || readme.contains("tunnel_listening"),
"README must surface scp-transfer and/or tunnel_listening for agents"
);
assert!(
readme.contains(".ssh-cli.partial") || lower.contains("partial"),
"README must document partial download path"
);
}
#[test]
fn gap_doc_004_root_security_integrations_honest() {
let sec = std::fs::read_to_string(root().join("SECURITY.md")).expect("SECURITY");
assert!(
(sec.contains("0.5.x") || sec.contains("0.5.0") || sec.contains("0.4.x"))
&& (sec.contains("current line") || sec.contains("current") || sec.contains("atual")),
"SECURITY Supported Versions must brand current product line"
);
assert!(
!sec.contains("| 0.3.x | Supported | Yes, current line |"),
"SECURITY must not claim 0.3.x is the current product line"
);
let integ = std::fs::read_to_string(root().join("INTEGRATIONS.md")).expect("INTEGRATIONS");
assert!(
(integ.contains("0.5.0") || integ.contains("0.4.0") || integ.contains("0.4.1"))
&& (integ.contains("scp-transfer") || integ.contains("tunnel_listening")),
"INTEGRATIONS 0.4.x must document real SCP/tunnel surface"
);
assert!(
integ.contains("0.3.9"),
"INTEGRATIONS must keep 0.3.9 residual facts under their own version bullet"
);
let llms_full = std::fs::read_to_string(root().join("llms-full.txt")).expect("llms-full");
assert!(
llms_full.contains("scp-transfer.schema.json"),
"llms-full must index scp-transfer schema"
);
assert!(
llms_full.contains("tunnel-listening.schema.json"),
"llms-full must index tunnel-listening schema"
);
for doc in ["CONTRIBUTING.md", "CONTRIBUTING.pt-BR.md"] {
let contrib = std::fs::read_to_string(root().join(doc)).expect("CONTRIBUTING");
assert!(
contrib.contains("gaps_v040"),
"{doc} must mention gaps_v040 regression suite"
);
assert!(
contrib.contains("E10") || contrib.contains("E01–E14") || contrib.contains("E01-E14"),
"{doc} must mention official e2e SCP matrix E10+"
);
}
}
#[test]
fn gap_doc_004c_docs_folder_scp_tunnel_honest() {
let agents = std::fs::read_to_string(root().join("docs/AGENTS.md")).expect("AGENTS");
assert!(
agents.contains("scp-transfer") && agents.contains("tunnel_listening"),
"docs/AGENTS.md must document scp-transfer and tunnel_listening contracts"
);
assert!(
agents.to_lowercase().contains("regular files only")
|| agents.contains("file-only")
|| agents.contains("no directories"),
"docs/AGENTS.md must document SCP file-only"
);
let howto = std::fs::read_to_string(root().join("docs/HOW_TO_USE.md")).expect("HOW_TO_USE");
assert!(
howto.contains("0.3.9") && howto.contains(".ssh-cli.partial"),
"docs/HOW_TO_USE.md must warn 0.3.9 and document partial downloads"
);
let cook = std::fs::read_to_string(root().join("docs/COOKBOOK.md")).expect("COOKBOOK");
assert!(
cook.contains("tunnel_listening") && cook.contains("scp-transfer"),
"docs/COOKBOOK.md must include tunnel_listening and scp-transfer recipes"
);
let mig = std::fs::read_to_string(root().join("docs/MIGRATION.md")).expect("MIGRATION");
assert!(
mig.contains("tunnel_listening")
&& mig.contains(".ssh-cli.partial")
&& mig.contains("32 KiB"),
"docs/MIGRATION.md 0.4.0 section must cover tunnel JSON, partial, stream"
);
for doc in ["docs/TESTING.md", "docs/TESTING.pt-BR.md"] {
let testing = std::fs::read_to_string(root().join(doc)).expect("TESTING");
assert!(
testing.contains("gaps_v040")
&& (testing.contains("E10")
|| testing.contains("E01–E14")
|| testing.contains("E01-E14")),
"{doc} must list gaps_v040 and e2e E10+"
);
}
for doc in [
"docs/RELEASE_CHECKLIST.md",
"docs/RELEASE_CHECKLIST.pt-BR.md",
] {
let release = std::fs::read_to_string(root().join(doc)).expect("RELEASE");
assert!(
release.contains("gaps_v040") && release.contains("DOC-004"),
"{doc} must gate gaps_v040 and DOC-004"
);
}
let cross = std::fs::read_to_string(root().join("docs/CROSS_PLATFORM.md")).expect("CROSS");
let cross_l = cross.to_lowercase();
assert!(
cross.contains(".ssh-cli.partial")
&& (cross_l.contains("regular files only")
|| cross.contains("file-only")
|| cross_l.contains("regular files")),
"docs/CROSS_PLATFORM.md must document SCP portability"
);
let schema_idx =
std::fs::read_to_string(root().join("docs/schemas/README.md")).expect("schemas README");
assert!(
schema_idx.contains("scp-transfer.schema.json")
&& schema_idx.contains("tunnel-listening.schema.json"),
"docs/schemas/README.md must index scp-transfer and tunnel-listening"
);
assert!(
root()
.join("docs/schemas/tunnel-listening.schema.json")
.is_file(),
"missing tunnel-listening.schema.json"
);
}
#[test]
fn gap_doc_004d_skills_scp_tunnel_honest() {
for rel in ["skills/ssh-cli-en/SKILL.md", "skills/ssh-cli-pt/SKILL.md"] {
let body = std::fs::read_to_string(root().join(rel)).expect(rel);
let lower = body.to_ascii_lowercase();
assert!(
body.contains("tunnel_listening"),
"{rel} must document tunnel_listening ready event"
);
assert!(
body.contains(".ssh-cli.partial"),
"{rel} must document partial download path"
);
assert!(
body.contains("32 KiB") || body.contains("32KiB"),
"{rel} must document 32 KiB upload stream"
);
assert!(
lower.contains("files-only")
|| lower.contains("file-only")
|| lower.contains("regular-file")
|| lower.contains("regular file")
|| body.contains("somente-arquivo")
|| body.contains("só-arquivo")
|| body.contains("arquivo regular"),
"{rel} must document scp regular-files-only"
);
assert!(
body.contains("ok")
&& body.contains("direction")
&& body.contains("bytes")
&& body.contains("duration_ms"),
"{rel} must document scp-transfer success fields"
);
assert!(
body.contains("local_port")
&& body.contains("remote_host")
&& body.contains("remote_port")
&& body.contains("timeout_ms"),
"{rel} must document tunnel_listening fields"
);
assert!(
body.contains("scp upload")
&& body.contains("--json")
&& body.contains("tunnel")
&& body.contains("--timeout-ms"),
"{rel} must include scp --json and tunnel --timeout-ms formulas"
);
assert!(
!body.contains("0.4.0 did")
&& !body.contains("0.3.9 did")
&& !body.contains("in version 0.3.9")
&& !body.contains("versão 0.3.9")
&& !body.contains("na versão 0.3.9"),
"{rel} must stay consolidated without version-story prose"
);
let fm = body
.strip_prefix("---\n")
.and_then(|s| s.split_once("\n---"))
.map(|(a, _)| a)
.expect("frontmatter");
let desc = fm
.lines()
.find(|l| l.starts_with("description:"))
.expect("description")
.trim_start_matches("description:")
.trim();
assert!(
desc.chars().count() < 1024,
"{rel} description must be < 1024 chars (got {})",
desc.chars().count()
);
assert_eq!(
desc.matches(':').count(),
0,
"{rel} description must not contain ':' in content"
);
assert!(
desc.contains("tunnel_listening")
&& (desc.contains("files-only")
|| desc.contains("só-arquivo")
|| desc.contains("file-only")
|| desc.contains("regular")),
"{rel} description must surface scp file-only + tunnel_listening for auto-activation"
);
}
for rel in [
"skills/ssh-cli-en/evals/queries.json",
"skills/ssh-cli-pt/evals/queries.json",
] {
let q = std::fs::read_to_string(root().join(rel)).expect(rel);
assert!(
q.contains("tunnel_listening")
&& (q.contains(".ssh-cli.partial") || q.contains("ssh-cli.partial"))
&& (q.contains("files only")
|| q.contains("regular files")
|| q.contains("arquivos regulares")
|| q.contains("somente arquivo")
|| q.contains("directory")
|| q.contains("diretorio")),
"{rel} evals must cover tunnel_listening + partial + file-only surface"
);
}
}
#[test]
fn gap_rel_004_changelog_039_scp_broken_e_040() {
let ch = std::fs::read_to_string(root().join("CHANGELOG.md")).expect("CHANGELOG");
assert!(ch.contains("0.4.0"), "CHANGELOG must have 0.4.0 section");
let lower = ch.to_lowercase();
assert!(
lower.contains("0.3.9")
&& (lower.contains("broken") || lower.contains("inoperant") || lower.contains("wire")),
"CHANGELOG must honestly mention 0.3.9 SCP wire issue"
);
}
#[test]
fn skills_en_and_pt_document_the_same_agent_surface() {
let en = std::fs::read_to_string(root().join("skills/ssh-cli-en/SKILL.md")).expect("EN skill");
let pt =
std::fs::read_to_string(root().join("skills/ssh-cli-pt/SKILL.md")).expect("pt-BR skill");
let tokens = [
"--select",
"--filter",
"--limit",
"--sort",
"--dedupe-by",
"--count-only",
"--truncate-content",
"--max-output-bytes",
"--no-input",
"--dry-run",
"--i-accept-network-exposure",
"--socks5",
"--remote-socket",
"--reverse",
"--timeout-ms",
"tunnel_listening",
"tunnel_closed",
"dry-run",
"socks5",
"streamlocal",
"reverse",
"local_port",
"capacity_waits",
"forwards_served",
"executed",
"replaces_existing",
"hosts_to_reencrypt",
"`ok`/`direction`",
"mtime_preserved",
"durable",
"vps remove",
"vps import",
"sftp rm",
"sftp rmdir",
"secrets init",
"secrets reencrypt",
];
let mut missing_en = Vec::new();
let mut missing_pt = Vec::new();
for token in tokens {
if !en.contains(token) {
missing_en.push(token);
}
if !pt.contains(token) {
missing_pt.push(token);
}
}
assert!(
missing_en.is_empty(),
"EN skill is missing agent-facing tokens: {missing_en:?}"
);
assert!(
missing_pt.is_empty(),
"pt-BR skill is missing agent-facing tokens: {missing_pt:?}"
);
}
#[test]
fn changelogs_describe_the_same_release_surface() {
let en = std::fs::read_to_string(root().join("CHANGELOG.md")).expect("CHANGELOG");
let pt = std::fs::read_to_string(root().join("CHANGELOG.pt-BR.md")).expect("CHANGELOG pt-BR");
for token in [
"0.5.4",
"--dry-run",
"--socks5",
"--remote-socket",
"--reverse",
"G-TUN-R01",
"G-TUN-R02",
"G-TUN-R03",
"B2",
"BREAKING",
"direct-streamlocal@openssh.com",
"RFC 1928",
] {
assert!(en.contains(token), "CHANGELOG.md must mention {token}");
assert!(
pt.contains(token),
"CHANGELOG.pt-BR.md must mention {token}"
);
}
let breaking_en = en.matches("BREAKING").count();
let breaking_pt = pt.matches("BREAKING").count();
assert_eq!(
breaking_en, breaking_pt,
"BREAKING count diverges: EN {breaking_en}, pt-BR {breaking_pt}"
);
}
const CHANGELOG_SECTIONS: &[(&str, &str)] = &[
("Security", "Segurança"),
("Added", "Adicionado"),
("Changed", "Alterado"),
("Deprecated", "Depreciado"),
("Removed", "Removido"),
("Fixed", "Corrigido"),
("Internal", "Interno"),
];
fn is_shipped_release_heading(line: &str) -> bool {
line.starts_with("## [") && !line.starts_with("## [Unreleased]")
}
fn top_release_sections(text: &str) -> Vec<String> {
text.lines()
.skip_while(|l| !is_shipped_release_heading(l))
.skip(1)
.take_while(|l| !l.starts_with("## ["))
.filter_map(|l| l.strip_prefix("### "))
.map(str::to_string)
.collect()
}
#[test]
fn the_release_parser_skips_the_unreleased_section() {
let with_unreleased = "\
## [Unreleased]
- Nothing yet.
## [9.9.9] - 2026-01-01
### Added
- thing
## [9.9.8] - 2025-01-01
### Fixed
- older thing
";
assert_eq!(
top_release_sections(with_unreleased),
vec!["Added".to_string()],
"an empty `[Unreleased]` must not be mistaken for the newest release"
);
assert!(!is_shipped_release_heading("## [Unreleased]"));
assert!(is_shipped_release_heading("## [0.5.5] - 2026-08-10"));
assert!(!is_shipped_release_heading("### Added"));
}
#[test]
fn the_newest_release_has_one_section_per_name_in_both_languages() {
let en = std::fs::read_to_string(root().join("CHANGELOG.md")).expect("CHANGELOG");
let pt = std::fs::read_to_string(root().join("CHANGELOG.pt-BR.md")).expect("CHANGELOG pt-BR");
let en_sections = top_release_sections(&en);
let pt_sections = top_release_sections(&pt);
assert!(
!en_sections.is_empty() && !pt_sections.is_empty(),
"no `### ` headings found under the newest `## [` heading; the parser is \
broken, not the changelog"
);
for (lang, sections, known) in [
("CHANGELOG.md", &en_sections, 0usize),
("CHANGELOG.pt-BR.md", &pt_sections, 1usize),
] {
let allowed: Vec<&str> = CHANGELOG_SECTIONS
.iter()
.map(|pair| if known == 0 { pair.0 } else { pair.1 })
.collect();
let mut seen: Vec<&String> = Vec::new();
for s in sections {
assert!(
allowed.contains(&s.as_str()),
"{lang}: `### {s}` is not a Keep a Changelog section for this \
project. Allowed: {allowed:?}"
);
assert!(
!seen.contains(&s),
"{lang}: `### {s}` appears twice in the newest release. Merge them: \
a reader scanning for one heading stops at the first, and entries \
under the second are invisible."
);
seen.push(s);
}
let order: Vec<usize> = sections
.iter()
.filter_map(|s| allowed.iter().position(|a| a == s))
.collect();
let mut sorted = order.clone();
sorted.sort_unstable();
assert_eq!(
order, sorted,
"{lang}: sections are out of canonical order {allowed:?}"
);
}
let expected_pt: Vec<String> = en_sections
.iter()
.map(|s| {
CHANGELOG_SECTIONS
.iter()
.find(|pair| pair.0 == s)
.map(|pair| pair.1.to_string())
.unwrap_or_else(|| unreachable!("validated above"))
})
.collect();
assert_eq!(
expected_pt, pt_sections,
"the two changelogs split the newest release differently. EN {en_sections:?} \
maps to {expected_pt:?}, but pt-BR has {pt_sections:?}. An entry filed under \
a different heading in one language is unfindable to that language's reader."
);
}
const SURFACE_054: &[(&str, &[&str])] = &[
(
"README.md",
&[
"--reverse",
"--socks5",
"--remote-socket",
"--i-accept-network-exposure",
"tunnel_closed",
"--select",
"--count-only",
],
),
(
"README.pt-BR.md",
&[
"--reverse",
"--socks5",
"--remote-socket",
"--i-accept-network-exposure",
"tunnel_closed",
"--select",
"--count-only",
],
),
(
"llms.txt",
&[
"0.5.4",
"--reverse",
"--socks5",
"--remote-socket",
"--i-accept-network-exposure",
"tunnel_closed",
],
),
(
"llms.pt-BR.txt",
&[
"0.5.4",
"--reverse",
"--socks5",
"--remote-socket",
"--i-accept-network-exposure",
"tunnel_closed",
],
),
(
"llms-full.txt",
&[
"--reverse",
"--socks5",
"--remote-socket",
"--i-accept-network-exposure",
"tunnel_closed",
"--select",
"--dry-run",
"--no-input",
"streamlocal",
],
),
(
"docs/AGENTS.md",
&[
"--select",
"--filter",
"--limit",
"--sort",
"--dedupe-by",
"--count-only",
"--truncate-content",
"--max-output-bytes",
"--dry-run",
"--no-input",
"--reverse",
"--socks5",
"--remote-socket",
"tunnel_closed",
],
),
(
"docs/AGENTS.pt-BR.md",
&[
"--select",
"--filter",
"--limit",
"--sort",
"--dedupe-by",
"--count-only",
"--truncate-content",
"--max-output-bytes",
"--dry-run",
"--no-input",
"--reverse",
"--socks5",
"--remote-socket",
"tunnel_closed",
],
),
(
"docs/HOW_TO_USE.md",
&[
"--reverse",
"--socks5",
"--remote-socket",
"--i-accept-network-exposure",
"--select",
"--dry-run",
"tunnel_closed",
],
),
(
"docs/HOW_TO_USE.pt-BR.md",
&[
"--reverse",
"--socks5",
"--remote-socket",
"--i-accept-network-exposure",
"--select",
"--dry-run",
"tunnel_closed",
],
),
(
"docs/COOKBOOK.md",
&[
"--reverse",
"--socks5",
"--remote-socket",
"--i-accept-network-exposure",
"--select",
"--count-only",
],
),
(
"docs/COOKBOOK.pt-BR.md",
&[
"--reverse",
"--socks5",
"--remote-socket",
"--i-accept-network-exposure",
"--select",
"--count-only",
],
),
(
"INTEGRATIONS.md",
&["tunnel_closed", "--reverse", "--socks5", "--remote-socket"],
),
(
"INTEGRATIONS.pt-BR.md",
&["tunnel_closed", "--reverse", "--socks5", "--remote-socket"],
),
(
"docs/MIGRATION.md",
&[
"0.5.4",
"--i-accept-network-exposure",
"--reverse",
"--socks5",
"--remote-socket",
],
),
(
"docs/MIGRATION.pt-BR.md",
&[
"0.5.4",
"--i-accept-network-exposure",
"--reverse",
"--socks5",
"--remote-socket",
],
),
("SECURITY.md", &["--i-accept-network-exposure", "0.5.4"]),
(
"SECURITY.pt-BR.md",
&["--i-accept-network-exposure", "0.5.4"],
),
(
"docs/CROSS_PLATFORM.md",
&["--remote-socket", "streamlocal"],
),
(
"docs/CROSS_PLATFORM.pt-BR.md",
&["--remote-socket", "streamlocal"],
),
(
"docs/RELEASE_CHECKLIST.md",
&[
"--reverse",
"--socks5",
"--remote-socket",
"SFTP_PERM_MASK_UNTRUSTED",
],
),
(
"docs/RELEASE_CHECKLIST.pt-BR.md",
&[
"--reverse",
"--socks5",
"--remote-socket",
"SFTP_PERM_MASK_UNTRUSTED",
],
),
(
"docs/TESTING.md",
&["--reverse", "--socks5", "--remote-socket"],
),
(
"docs/TESTING.pt-BR.md",
&["--reverse", "--socks5", "--remote-socket"],
),
];
const SURFACE_055: &[(&str, &[&str])] = &[
(
"README.md",
&["--use-active", "host_resolved", "active_vps"],
),
(
"README.pt-BR.md",
&["--use-active", "host_resolved", "active_vps"],
),
(
"docs/AGENTS.md",
&[
"--use-active",
"host_resolved",
"host_source",
"active_fallback",
],
),
(
"docs/AGENTS.pt-BR.md",
&[
"--use-active",
"host_resolved",
"host_source",
"active_fallback",
],
),
(
"docs/HOW_TO_USE.md",
&["--use-active", "host_source", "active_vps"],
),
(
"docs/HOW_TO_USE.pt-BR.md",
&["--use-active", "host_source", "active_vps"],
),
(
"docs/COOKBOOK.md",
&["--use-active", "host_resolved", "host_source"],
),
(
"docs/COOKBOOK.pt-BR.md",
&["--use-active", "host_resolved", "host_source"],
),
(
"docs/MIGRATION.md",
&["--use-active", "host_resolved", "host_source"],
),
(
"docs/MIGRATION.pt-BR.md",
&["--use-active", "host_resolved", "host_source"],
),
(
"docs/schemas/README.md",
&["host_resolved", "host_source", "active_fallback"],
),
(
"skills/ssh-cli-en/SKILL.md",
&[
"--use-active",
"host_resolved",
"host_source",
"active_fallback",
],
),
(
"skills/ssh-cli-pt/SKILL.md",
&[
"--use-active",
"host_resolved",
"host_source",
"active_fallback",
],
),
];
const FLEET_SELECTOR_DOCS: &[&str] = &[
"docs/AGENTS.md",
"docs/AGENTS.pt-BR.md",
"docs/HOW_TO_USE.md",
"docs/HOW_TO_USE.pt-BR.md",
"docs/COOKBOOK.md",
"docs/COOKBOOK.pt-BR.md",
];
#[test]
fn fleet_documents_name_every_selector() {
let mut missing = Vec::new();
for doc in FLEET_SELECTOR_DOCS {
let text = std::fs::read_to_string(root().join(doc))
.unwrap_or_else(|e| panic!("cannot read {doc}: {e}"));
for selector in ["--all", "--hosts", "--tags", "--use-active"] {
if !text.contains(selector) {
missing.push(format!("{doc}: {selector}"));
}
}
}
assert!(
missing.is_empty(),
"these documents describe fleet execution without naming every selector:\n {}\n\
`--tags` is declared on exec/sudo-exec/su-exec in src/cli/commands.rs. A \
selector absent from the docs is a selector the agent never uses.",
missing.join("\n ")
);
}
#[test]
fn permission_mask_claims_are_directional() {
fn collect(dir: &std::path::Path, out: &mut Vec<std::path::PathBuf>) {
let Ok(entries) = std::fs::read_dir(dir) else {
return;
};
for path in entries.filter_map(|e| e.ok().map(|e| e.path())) {
if path.is_dir() {
if path
.file_name()
.is_some_and(|n| n == "target" || n == ".git")
{
continue;
}
collect(&path, out);
} else if path.extension().is_some_and(|x| x == "md" || x == "txt") {
out.push(path);
}
}
}
let mut offenders = Vec::new();
let mut docs: Vec<std::path::PathBuf> = Vec::new();
collect(&root(), &mut docs);
docs.sort();
for path in &docs {
if path.file_name().is_some_and(|n| n == "CLAUDE.md") {
continue;
}
let Ok(text) = std::fs::read_to_string(path) else {
continue;
};
if text.contains("0o7777") && !text.contains("SFTP_PERM_MASK_UNTRUSTED") {
let name = path
.strip_prefix(root())
.unwrap_or(path)
.to_string_lossy()
.into_owned();
offenders.push(name);
}
}
assert!(
offenders.is_empty(),
"these documents name the outbound mask `0o7777` without the inbound mask:\n {}\n\
Always qualify the direction and cite both constants: `SFTP_PERM_MASK` \
(0o7777, upload) and `SFTP_PERM_MASK_UNTRUSTED` (0o0777, download).",
offenders.join("\n ")
);
}
#[test]
fn the_054_surface_reaches_every_user_facing_document() {
let mut missing = Vec::new();
for (doc, tokens) in SURFACE_054.iter().chain(SURFACE_055) {
let text = std::fs::read_to_string(root().join(doc))
.unwrap_or_else(|e| panic!("cannot read {doc}: {e}"));
for token in *tokens {
if !text.contains(token) {
missing.push(format!("{doc}: {token}"));
}
}
}
assert!(
missing.is_empty(),
"these documents do not mention 0.5.4 surface they are responsible for:\n {}\n\
Document the feature where the reader will look for it. A release note in \
the banner is an announcement, not a contract.",
missing.join("\n ")
);
}
const LEAF_COMMANDS: &[&str] = &[
"vps add",
"vps list",
"vps remove",
"vps edit",
"vps show",
"vps path",
"vps doctor",
"vps export",
"vps import",
"connect",
"exec",
"sudo-exec",
"su-exec",
"scp upload",
"scp download",
"sftp upload",
"sftp download",
"sftp ls",
"sftp mkdir",
"sftp rmdir",
"sftp rm",
"sftp stat",
"sftp rename",
"tunnel",
"health-check",
"secrets status",
"secrets init",
"secrets reencrypt",
"completions",
"commands",
"schema",
"doctor",
"locale show",
"locale set",
"locale clear",
"tls provider",
"tls paths",
"tls mtls list",
"tls mtls import",
"tls mtls show",
"tls mtls remove",
"tls acme account create",
"tls acme account show",
"tls acme issue",
"tls acme complete",
"tls acme status",
"tls acme list",
];
const FULL_INVENTORY_DOCS: &[&str] = &[
"llms-full.txt",
"README.md",
"README.pt-BR.md",
"docs/AGENTS.md",
"docs/AGENTS.pt-BR.md",
"docs/HOW_TO_USE.md",
"docs/HOW_TO_USE.pt-BR.md",
"docs/CROSS_PLATFORM.md",
"docs/CROSS_PLATFORM.pt-BR.md",
];
const SKILLS: &[&str] = &["skills/ssh-cli-en/SKILL.md", "skills/ssh-cli-pt/SKILL.md"];
const EXPORT_CLAIM_DOCS: &[&str] = &[
"README.md",
"README.pt-BR.md",
"llms.txt",
"llms.pt-BR.txt",
"INTEGRATIONS.md",
"INTEGRATIONS.pt-BR.md",
"docs/COOKBOOK.md",
"docs/COOKBOOK.pt-BR.md",
"docs/MIGRATION.md",
"docs/MIGRATION.pt-BR.md",
"docs/TESTING.md",
"docs/TESTING.pt-BR.md",
"docs/RELEASE_CHECKLIST.md",
"docs/RELEASE_CHECKLIST.pt-BR.md",
"docs/schemas/README.md",
];
#[test]
fn every_command_appears_in_every_full_inventory_document() {
assert_eq!(
LEAF_COMMANDS.len(),
47,
"LEAF_COMMANDS drifted from the shipped tree; run `ssh-cli commands` and \
reconcile, then document the new command in every file in FULL_INVENTORY_DOCS"
);
let mut missing = Vec::new();
for doc in FULL_INVENTORY_DOCS {
let text = std::fs::read_to_string(root().join(doc))
.unwrap_or_else(|e| panic!("cannot read {doc}: {e}"));
for cmd in LEAF_COMMANDS {
if !text.contains(cmd) {
missing.push(format!("{doc}: {cmd}"));
}
}
}
assert!(
missing.is_empty(),
"these documents claim a complete command inventory but omit commands:\n {}\n\
Write the full path (`tls acme account create`), not brace notation \
(`tls acme {{account create,…}}`): a retriever matches literal strings.",
missing.join("\n ")
);
}
#[test]
fn every_schema_is_indexed_in_the_full_llm_map() {
let dir = root().join("docs/schemas");
let map = std::fs::read_to_string(root().join("llms-full.txt")).expect("llms-full");
let mut schemas: Vec<String> = std::fs::read_dir(&dir)
.expect("read schemas dir")
.filter_map(Result::ok)
.filter_map(|e| e.file_name().into_string().ok())
.filter(|n| n.ends_with(".schema.json"))
.collect();
schemas.sort_unstable();
assert!(!schemas.is_empty(), "no schemas found to index");
let missing: Vec<&String> = schemas
.iter()
.filter(|n| !map.contains(n.as_str()))
.collect();
assert!(
missing.is_empty(),
"llms-full.txt indexes {} of {} schemas; it omits: {missing:?}\n\
It advertises itself as the complete discovery map, so every contract on disk \
must be listed there.",
schemas.len() - missing.len(),
schemas.len()
);
}
#[test]
fn every_schema_is_indexed_in_both_languages() {
let dir = root().join("docs/schemas");
let readme = std::fs::read_to_string(dir.join("README.md")).expect("schemas README");
let mut schemas: Vec<String> = std::fs::read_dir(&dir)
.expect("read schemas dir")
.filter_map(Result::ok)
.filter_map(|e| e.file_name().into_string().ok())
.filter(|n| n.ends_with(".schema.json"))
.collect();
schemas.sort();
assert!(!schemas.is_empty(), "no schemas found to index");
for name in &schemas {
let mentions = readme.matches(name.as_str()).count();
assert!(
mentions >= 2,
"{name} is mentioned {mentions} time(s) in docs/schemas/README.md; \
both the English and the pt-BR section must index it"
);
}
}
const SKILL_WORD_BUDGET: usize = 4000;
#[test]
fn skills_stay_within_the_word_budget() {
for rel in ["skills/ssh-cli-en/SKILL.md", "skills/ssh-cli-pt/SKILL.md"] {
let body = std::fs::read_to_string(root().join(rel)).expect(rel);
let words = body.split_whitespace().count();
assert!(
words <= SKILL_WORD_BUDGET,
"{rel} has {words} words, over the {SKILL_WORD_BUDGET}-word budget by {}. \
Cut duplicated prose before cutting formulas: the formulas are the part an \
agent copies, while a prohibition that merely mirrors a REQUIRED bullet in \
the same section teaches nothing.",
words - SKILL_WORD_BUDGET
);
}
}
#[test]
fn skills_name_every_command() {
let mut missing = Vec::new();
for rel in ["skills/ssh-cli-en/SKILL.md", "skills/ssh-cli-pt/SKILL.md"] {
let body = std::fs::read_to_string(root().join(rel)).expect(rel);
for cmd in LEAF_COMMANDS {
if !body.contains(cmd) {
missing.push(format!("{rel}: {cmd}"));
}
}
}
assert!(
missing.is_empty(),
"these skills claim the whole command surface but omit commands:\n {}\n\
Write the full path (`tls acme account create`): an agent greps literal strings \
and concludes an unmatched command does not exist.",
missing.join("\n ")
);
}
#[test]
fn no_document_claims_export_defaults_to_toml() {
const BANNED: &[&str] = &[
"TOML by default",
"stays TOML",
"export TOML default",
"body is TOML",
"default body is TOML",
"TOML por padrão",
"permanece TOML",
"export TOML padrão",
"corpo padrão é TOML",
"padrão de `vps export` é TOML",
];
let mut hits = Vec::new();
for doc in FULL_INVENTORY_DOCS
.iter()
.chain(SKILLS.iter())
.chain(EXPORT_CLAIM_DOCS.iter())
{
let path = root().join(doc);
let Ok(body) = std::fs::read_to_string(&path) else {
continue;
};
for (idx, line) in body.lines().enumerate() {
for phrase in BANNED {
if line.contains(phrase) {
hits.push(format!("{doc}:{}: {phrase}", idx + 1));
}
}
}
}
assert!(
hits.is_empty(),
"these lines claim a TOML default that the binary does not have:\n {}\n\
The body follows the resolved output format: JSON on any non-TTY stdout, even into \
a `.toml` filename, and TOML only with `--output-format text`. Say that instead.",
hits.join("\n ")
);
}