use std::{
fs,
path::PathBuf,
time::{SystemTime, UNIX_EPOCH},
};
use shepherd_cli::{
content_compiler::{embedded_compile_input, load_compile_input},
shepherd::compiler::{BudgetClass, BudgetLimits, HarnessProfile, compile, measure_text},
};
const CANONICAL_ROLE_STARTUP_SKILLS: [(&str, &str); 9] = [
("auditor", "reviewing"),
("coder", "implementing"),
("conductor", "lane-execution"),
("critic", "reviewing"),
("discovery", "researching"),
("engineer", "planning"),
("planter", "planting"),
("shepherd", "shepherd"),
("worker", "artifact-work"),
];
#[test]
fn embedded_content_is_the_exact_canonical_authored_corpus() {
let filesystem = load_compile_input(&content_dir()).expect("load live content");
let embedded = embedded_compile_input().expect("load embedded content");
assert_eq!(embedded, filesystem);
}
#[test]
fn canonical_roles_expose_exact_startup_bundles_on_every_target() {
let input = load_compile_input(&content_dir()).expect("load live content");
for (role_name, expected_skill) in CANONICAL_ROLE_STARTUP_SKILLS {
let role = input
.roles
.iter()
.find(|role| role.role == role_name)
.unwrap_or_else(|| panic!("canonical role {role_name}"));
assert_eq!(
role.startup_skill, expected_skill,
"authored startup skill for {role_name}"
);
}
for profile in HarnessProfile::canonical() {
let tree = compile(&input, &profile).expect("compile canonical target");
assert_eq!(
tree.roles.len(),
CANONICAL_ROLE_STARTUP_SKILLS.len(),
"{} canonical role contracts",
profile.target.as_str()
);
for (role_name, expected_skill) in CANONICAL_ROLE_STARTUP_SKILLS {
let role = tree
.roles
.iter()
.find(|role| role.role == role_name)
.unwrap_or_else(|| panic!("emitted role {role_name}"));
assert_eq!(
role.startup_skill.as_deref(),
Some(expected_skill),
"{} startup skill for {role_name}",
profile.target.as_str()
);
assert_eq!(
role.startup_skill_sha256.as_deref().map(str::len),
Some(64),
"{} startup bundle digest for {role_name}",
profile.target.as_str()
);
let skill_prefix = if profile.target.as_str() == "codex" {
".agents/skills"
} else {
"skills"
};
assert!(
tree.files.iter().any(|file| {
file.path == format!("{skill_prefix}/{expected_skill}/SKILL.md")
}),
"{} packaged startup bundle for {role_name}",
profile.target.as_str()
);
let carrier = tree
.files
.iter()
.find(|file| file.path == role.carrier_path)
.unwrap_or_else(|| {
panic!(
"{} carrier {} for {role_name}",
profile.target.as_str(),
role.carrier_path
)
});
match profile.target.as_str() {
"claude" => assert!(
carrier
.content
.contains(&format!("\nskills: [{expected_skill}]\n")),
"Claude native startup attachment for {role_name}"
),
"pi" => assert!(
carrier
.content
.contains(&format!("\nskills: {expected_skill}\n")),
"Pi native startup attachment for {role_name}"
),
"codex" if role.dispatchable => assert!(
carrier.content.contains(&format!("`${expected_skill}`")),
"Codex explicit startup invocation for {role_name}"
),
"codex" => assert_eq!(role.carrier_path, "shepherd.codex.toml"),
target => panic!("unexpected canonical target {target}"),
}
let stale_digest = role
.startup_skill_sha256
.as_deref()
.expect("complete startup bundle digest");
let mut changed_input = input.clone();
changed_input
.skills
.iter_mut()
.find(|skill| skill.name == expected_skill)
.expect("canonical startup bundle")
.body
.push_str("\n!");
let changed_tree = compile(&changed_input, &profile).unwrap_or_else(|error| {
panic!("changed {} bundle: {error}", profile.target.as_str())
});
let changed_digest = changed_tree
.roles
.iter()
.find(|role| role.role == role_name)
.and_then(|role| role.startup_skill_sha256.as_deref())
.expect("changed startup bundle digest");
assert_ne!(
stale_digest,
changed_digest,
"{} must invalidate a stale {role_name} startup bundle digest",
profile.target.as_str()
);
}
}
}
fn content_dir() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../content")
}
fn fixture(label: &str) -> PathBuf {
let nonce = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("clock")
.as_nanos();
let root = std::env::temp_dir().join(format!("shepherd-content-{label}-{nonce:x}"));
fs::create_dir_all(root.join("content/roles")).expect("roles");
fs::create_dir_all(root.join("content/skills/example")).expect("skills");
root
}
#[test]
fn filesystem_fixture_emits_target_native_startup_skill_attachments() {
let root = fixture("startup-skill");
let content = root.join("content");
fs::write(
content.join("roles/example.md"),
"---\nrole: example\ndescription: \"Example role\"\nsource: agents/example.md\nmodel_hint: standard\nwrite_eligible: true\ndispatchable: true\ncapabilities: [read]\nskill: example\nwrite_scope: \"assigned scope\"\n---\n\n# example\n\nDo the work.\n",
)
.expect("role");
fs::write(
content.join("skills/example/SKILL.md"),
"---\nname: example\ndescription: \"Example startup skill\"\nsource: skills/example/SKILL.md\nportability: cross-harness\n---\n\n# example\n\nFollow the contract.\n",
)
.expect("skill");
let input = load_compile_input(&content).expect("typed fixture");
assert_eq!(input.roles[0].startup_skill, "example");
let claude = compile(&input, &HarnessProfile::claude()).expect("Claude fixture");
assert!(claude.files[0].content.contains("\nskills: [example]\n"));
let codex = compile(&input, &HarnessProfile::codex()).expect("Codex fixture");
assert!(
codex
.files
.iter()
.any(|file| file.path == ".codex/agents/example.toml")
);
let pi = compile(&input, &HarnessProfile::pi()).expect("Pi fixture");
assert!(
pi.files
.iter()
.find(|file| file.path == "prompts/example.md")
.expect("Pi prompt")
.content
.contains("\nskills: example\n")
);
fs::remove_dir_all(root).expect("cleanup");
}
#[test]
fn live_content_matches_the_frozen_target_final_oracle() {
let input = load_compile_input(&content_dir()).expect("load live content");
assert_eq!(input.roles.len(), 9);
for lead in ["engineer", "conductor"] {
let role = input
.roles
.iter()
.find(|role| role.role == lead)
.expect("canonical reasoning lead exists");
assert_eq!(role.model_hint, "reasoning-high", "{lead}");
}
let conductor = input
.roles
.iter()
.find(|role| role.role == "conductor")
.expect("canonical conductor exists");
assert!(conductor.body.contains("independent Auditor review"));
assert!(conductor.body.contains("Require each result artifact"));
assert!(conductor.body.contains("is not acceptance;"));
assert!(conductor.body.contains("digest-bound handoff"));
assert!(
!conductor
.body
.contains("never dispatches a plan-authoring or gating role directly")
);
assert_eq!(input.skills.len(), 19);
for name in ["plant", "planting"] {
assert!(
input.skills.iter().any(|skill| skill.name == name),
"{name}"
);
}
let oracle: serde_json::Value = serde_json::from_str(include_str!(
"../../../conformance/content-target-final.json"
))
.expect("target-final oracle is valid JSON");
assert_eq!(oracle["schema"], "shepherd.content-target-final/1");
for profile in HarnessProfile::canonical() {
let tree = compile(&input, &profile).expect("compile live content");
let target = profile.target.as_str();
let expected = &oracle["targets"][target];
let expected_files = expected["files"].as_object().expect("oracle files");
assert_eq!(tree.roles.len(), 9);
assert_eq!(tree.files.len(), expected_files.len());
assert_eq!(
tree.digest,
expected["tree_digest"].as_str().unwrap(),
"{target}"
);
for file in &tree.files {
let frozen = expected_files
.get(&file.path)
.unwrap_or_else(|| panic!("{target}: missing frozen path {}", file.path))
.as_array()
.expect("[bytes, sha256]");
assert_eq!(
file.content.len(),
usize::try_from(frozen[0].as_u64().expect("byte count"))
.expect("byte count fits usize"),
"{target}: {} byte count",
file.path
);
assert_eq!(
file.content_sha256,
frozen[1].as_str().expect("content digest"),
"{target}: {} digest",
file.path
);
}
for (role_name, expected_role) in oracle["roles"].as_object().expect("oracle roles") {
let role = tree
.roles
.iter()
.find(|role| &role.role == role_name)
.unwrap_or_else(|| panic!("{target}: missing role {role_name}"));
assert_eq!(
role.model_hint,
expected_role["model_hint"]
.as_str()
.expect("oracle model_hint"),
"portable model hint for {role_name}"
);
match target {
"claude" => assert_eq!(
role.model.as_deref(),
expected_role["claude_model"].as_str(),
"Claude model for {role_name}"
),
"codex" => {
assert_eq!(
role.model.as_deref(),
expected_role["codex_model"].as_str(),
"Codex model for {role_name}"
);
assert_eq!(
role.profile.as_deref(),
expected_role["codex_profile"].as_str(),
"Codex profile for {role_name}"
);
assert_eq!(
role.reasoning_effort.as_deref(),
expected_role["codex_effort"].as_str(),
"Codex effort for {role_name}"
);
}
"pi" => {
assert_eq!(
role.model.as_deref(),
expected_role["pi_model"].as_str(),
"Pi model for {role_name}"
);
assert_eq!(
serde_json::to_value(&role.tools).expect("Pi tools JSON"),
expected_role["pi_tools"],
"Pi tools for {role_name}"
);
assert_eq!(
serde_json::to_value(&role.unsupported_capabilities)
.expect("Pi unsupported JSON"),
expected_role["pi_unsupported"],
"Pi unsupported capabilities for {role_name}"
);
}
_ => unreachable!("canonical target"),
}
}
}
}
#[test]
fn codex_target_final_config_bytes_are_frozen() {
const EXPECTED: &str = "# Generated by the canonical Rust shepherd compiler. Source: content/roles/*.md.\n\
# Do not hand-edit; regenerate via `shepherd compile --target codex --out <directory>`.\n\n\
max_concurrent_children = 3\n\n\
[agent_types]\n\
auditor = \"explorer\"\n\
coder = \"worker\"\n\
conductor = \"worker\"\n\
critic = \"explorer\"\n\
discovery = \"explorer\"\n\
engineer = \"worker\"\n\
worker = \"worker\"\n\n\
[models]\n\
auditor = \"standard\"\n\
coder = \"standard\"\n\
conductor = \"reasoning-high\"\n\
critic = \"standard\"\n\
discovery = \"economy\"\n\
engineer = \"reasoning-high\"\n\
planter = \"reasoning-high\"\n\
worker = \"standard\"\n\n\
[profiles.\"economy\"]\n\
reasoning_effort = \"low\"\n\n\
[profiles.\"reasoning-high\"]\n\
reasoning_effort = \"high\"\n\n\
[profiles.\"standard\"]\n\
reasoning_effort = \"medium\"\n";
let input = load_compile_input(&content_dir()).expect("load live content");
let tree = compile(&input, &HarnessProfile::codex()).expect("compile Codex");
let config = tree
.files
.iter()
.find(|file| file.path == "shepherd.codex.toml")
.expect("Codex config");
assert_eq!(config.content, EXPECTED);
}
#[test]
fn live_authored_entrypoints_respect_every_file_budget() {
let input = load_compile_input(&content_dir()).expect("load live content");
let mut violations = Vec::new();
for (name, class, source) in input
.roles
.iter()
.map(|role| {
(
role.source_path.as_str(),
BudgetClass::Role,
role.source_content.as_str(),
)
})
.chain(input.skills.iter().map(|skill| {
(
skill.source_path.as_str(),
BudgetClass::Skill,
skill.source_content.as_str(),
)
}))
{
let measured = measure_text(source);
let (lines, words, bytes) = BudgetLimits::for_class(class);
eprintln!(
"{name}: {} lines, {} words, {} bytes, {} prompt tokens",
measured.lines, measured.words, measured.utf8_bytes, measured.prompt_tokens
);
if measured.lines > lines || measured.words > words || measured.utf8_bytes > bytes {
violations.push(format!(
"{name}: {}/{lines} lines, {}/{words} words, {}/{bytes} bytes",
measured.lines, measured.words, measured.utf8_bytes
));
}
}
assert!(violations.is_empty(), "{}", violations.join("\n"));
}
#[test]
fn malformed_metadata_and_symlinks_fail_closed_without_echoing_content() {
let root = fixture("invalid");
let content = root.join("content");
fs::write(
content.join("roles/coder.md"),
"---\nrole: coder\ndescription: [secret]\nsource: agents/coder.md\nmodel_hint: standard\nwrite_eligible: true\ndispatchable: true\ncapabilities: [read]\nwrite_scope: scope\n---\nbody\n",
)
.expect("role");
fs::write(
content.join("skills/example/SKILL.md"),
"---\nname: example\ndescription: ok\nsource: x\nportability: cross-harness\n---\nbody\n",
)
.expect("skill");
let error = load_compile_input(&content).expect_err("typed description");
let message = error.message_text().expect("message");
assert!(message.contains("invalid role frontmatter"));
assert!(!message.contains("secret"));
fs::remove_file(content.join("roles/coder.md")).expect("remove invalid role");
let target = content.join("skills/example/SKILL.md");
let link = content.join("roles/coder.md");
#[cfg(unix)]
let linked = std::os::unix::fs::symlink(&target, &link).is_ok();
#[cfg(windows)]
let linked = std::os::windows::fs::symlink_file(&target, &link).is_ok();
#[cfg(not(any(unix, windows)))]
let linked = false;
if linked {
let error = load_compile_input(&content).expect_err("symlink");
assert!(error.message_text().expect("message").contains("symlink"));
} else {
eprintln!("skipped the symlink refusal: this environment cannot create a symlink");
}
fs::remove_dir_all(root).expect("cleanup");
}