use super::super::workspace::MAX_WORKSPACE_MEMBERS;
use super::*;
use std::fs;
use tempfile::TempDir;
fn fake_catalog() -> BTreeSet<String> {
[
"rust-engineer",
"js-engineer",
"react-engineer",
"vercel-ops",
"qa",
"ops",
]
.iter()
.map(|s| (*s).to_string())
.collect()
}
fn fake_manifest() -> String {
r#"
version = 1
[agent_categories]
universal = ["qa"]
deprecated = ["ops"]
[[agent_categories.language]]
stem = "rust-engineer"
markers = ["Cargo.toml"]
[[agent_categories.language]]
stem = "js-engineer"
markers = ["package.json"]
[[agent_categories.framework]]
stem = "react-engineer"
markers = ["package.json::\"react\""]
[[agent_categories.platform]]
stem = "vercel-ops"
markers = ["vercel.json"]
[skill_categories]
universal = ["tm"]
"#
.to_string()
}
fn fake_skill_catalog() -> BTreeSet<String> {
["tm"].iter().map(|s| (*s).to_string()).collect()
}
fn parsed_fake() -> AgentCategories {
parse_framework_manifest(&fake_manifest(), &fake_catalog()).expect("fixture is valid")
}
fn touch(dir: &Path, name: &str) {
let path = dir.join(name);
if let Some(parent) = path.parent() {
fs::create_dir_all(parent).unwrap();
}
fs::write(path, "").unwrap();
}
#[test]
fn bundled_framework_manifest_is_valid() {
let categories = framework_agent_categories().expect("bundled manifest must validate");
assert!(!categories.universal.is_empty());
assert_eq!(
categories
.platform
.iter()
.map(|entry| entry.stem.as_str())
.collect::<Vec<_>>(),
vec!["gcp-ops", "vercel-ops"],
"the two platform-ops agents are platform-gated"
);
let entry = |list: &[GatedAgent], stem: &str| {
list.iter()
.find(|e| e.stem == stem)
.unwrap_or_else(|| panic!("{stem} must be declared"))
.markers
.clone()
};
assert_eq!(
entry(&categories.language, "elixir-engineer"),
vec!["mix.exs".to_string()],
"elixir-engineer is language-gated on mix.exs"
);
assert_eq!(
entry(&categories.framework, "phoenix-engineer"),
vec!["mix.exs::{:phoenix,".to_string()],
"phoenix-engineer is framework-gated on the phoenix dependency"
);
assert!(categories.deprecated.is_empty());
}
#[test]
fn bundled_agent_stems_excludes_foundations() {
let stems = bundled_agent_stems(&crate::core::content_source::test_support::repo_roster());
assert!(
stems.contains("engineer"),
"dispatchable agents are included"
);
assert!(
stems.contains("elixir-engineer"),
"#4760 added elixir-engineer to the bundle"
);
assert!(
!stems.contains("ops"),
"#4760 deleted the deprecated `ops` agent from the bundle"
);
for base in [
"BASE-AGENT",
"BASE-ENGINEER",
"BASE-OPS",
"BASE-QA",
"BASE-RESEARCH",
] {
assert!(
!stems.contains(base),
"{base} is an inheritance fragment, not a dispatchable agent"
);
}
}
#[test]
fn bundled_manifest_partitions_the_whole_catalog() {
let roster = crate::core::content_source::test_support::repo_roster();
let categories = framework_agent_categories_for(&roster).expect("valid");
let declared: Vec<String> = labelled_stems(&categories)
.into_iter()
.map(|(_, stem)| stem.clone())
.collect();
let mut sorted = declared.clone();
sorted.sort();
sorted.dedup();
assert_eq!(sorted.len(), declared.len(), "no stem declared twice");
assert_eq!(
sorted,
bundled_agent_stems(&roster).into_iter().collect::<Vec<_>>(),
"the five categories must exactly cover the bundled catalog"
);
}
fn deploys(scope: &AgentSet, stem: &str) -> bool {
super::super::schema::selection_matches(stem, &scope.include, &scope.exclude)
}
#[test]
fn framework_agent_scope_selects_universal_agents() {
let tmp = TempDir::new().unwrap();
let scope = framework_agent_scope(tmp.path());
assert!(deploys(&scope, "engineer"));
assert!(deploys(&scope, "qa"));
assert_eq!(scope.source, ContentSource::Bundled);
}
#[test]
fn undeclared_source_agents_still_deploy() {
let tmp = TempDir::new().unwrap();
let scope = framework_agent_scope(tmp.path());
assert!(deploys(&scope, "base-engineer"));
assert!(deploys(&scope, "some-catalog-only-agent"));
}
#[test]
fn malformed_manifest_is_an_error() {
let err = parse_framework_manifest("this is not toml {{{", &fake_catalog())
.expect_err("malformed input must not resolve to a selection");
assert!(matches!(err, FrameworkManifestError::Malformed(_)));
}
#[test]
fn missing_section_is_an_error() {
let err = parse_framework_manifest("version = 1\n", &fake_catalog())
.expect_err("a manifest with no categories declares nothing");
assert_eq!(err, FrameworkManifestError::MissingCategories);
}
#[test]
fn empty_universal_is_an_error() {
let raw = "[agent_categories]\nuniversal = []\n";
let err = parse_framework_manifest(raw, &fake_catalog()).expect_err("empty universal");
assert_eq!(err, FrameworkManifestError::EmptyUniversal);
}
#[test]
fn unknown_stem_is_an_error() {
let raw = "[agent_categories]\nuniversal = [\"qa\", \"no-such-agent\"]\n";
let err = parse_framework_manifest(raw, &fake_catalog()).expect_err("unknown stem");
assert_eq!(
err,
FrameworkManifestError::UnknownAgent {
category: "universal",
stem: "no-such-agent".to_string(),
}
);
}
#[test]
fn undeclared_bundled_agent_is_an_error() {
let raw = "[agent_categories]\nuniversal = [\"qa\"]\n";
let err = parse_framework_manifest(raw, &fake_catalog()).expect_err("undeclared agents");
match err {
FrameworkManifestError::UndeclaredAgents(missing) => {
assert_eq!(
missing,
vec![
"js-engineer".to_string(),
"ops".to_string(),
"react-engineer".to_string(),
"rust-engineer".to_string(),
"vercel-ops".to_string()
]
);
}
other => panic!("expected UndeclaredAgents, got {other:?}"),
}
}
#[test]
fn duplicate_declaration_is_an_error() {
let raw = "[agent_categories]\nuniversal = [\"qa\"]\ndeprecated = [\"qa\"]\n";
let err = parse_framework_manifest(raw, &fake_catalog()).expect_err("duplicate");
assert_eq!(
err,
FrameworkManifestError::DuplicateDeclaration("qa".to_string())
);
}
#[test]
fn gated_entry_without_markers_is_an_error() {
let raw = r#"
[agent_categories]
universal = ["ops"]
[[agent_categories.language]]
stem = "qa"
markers = []
[[agent_categories.language]]
stem = "rust-engineer"
markers = ["Cargo.toml"]
[[agent_categories.language]]
stem = "js-engineer"
markers = ["package.json"]
[[agent_categories.framework]]
stem = "react-engineer"
markers = ["package.json"]
[[agent_categories.platform]]
stem = "vercel-ops"
markers = ["vercel.json"]
"#;
let err = parse_framework_manifest(raw, &fake_catalog()).expect_err("ungated entry");
assert_eq!(
err,
FrameworkManifestError::UngatedEntry {
category: "language",
stem: "qa".to_string(),
}
);
}
#[test]
fn detected_stack_engineers_matches_the_manifest() {
let tmp = TempDir::new().unwrap();
touch(tmp.path(), "Cargo.toml");
let detected = detected_stack_engineers(tmp.path()).engineers;
assert!(
detected.contains("rust-engineer"),
"a Cargo project's stack profile names rust-engineer: {detected:?}"
);
assert!(
!detected.contains("python-engineer"),
"...and not a language the project shows no marker for: {detected:?}"
);
let categories = framework_agent_categories().expect("bundled manifest must validate");
let declared: BTreeSet<&str> = categories
.language
.iter()
.chain(categories.framework.iter())
.map(|entry| entry.stem.as_str())
.collect();
for stem in &detected {
assert!(
declared.contains(stem.as_str()),
"`{stem}` is reported to the prompt but is not a declared stack agent"
);
}
}
#[test]
fn mixed_stack_repo_detects_nested_web_engineers() {
let tmp = TempDir::new().unwrap();
fs::write(
tmp.path().join("Cargo.toml"),
"[workspace]\nmembers = [\"crates/*\"]\n",
)
.unwrap();
touch(tmp.path(), "crates/app/Cargo.toml");
fs::create_dir_all(tmp.path().join("crates/app/ui")).unwrap();
fs::write(
tmp.path().join("crates/app/ui/package.json"),
r#"{"devDependencies":{"svelte":"^5.0.0"}}"#,
)
.unwrap();
touch(tmp.path(), "crates/app/ui/tsconfig.json");
let detected = detected_stack_engineers(tmp.path()).engineers;
for expected in [
"rust-engineer",
"javascript-engineer",
"typescript-engineer",
"svelte-engineer",
] {
assert!(
detected.contains(expected),
"a Cargo workspace with a nested Svelte UI must detect `{expected}`: {detected:?}"
);
}
assert!(
!detected.contains("python-engineer"),
"the walk must not invent a stack the repo has no marker for: {detected:?}"
);
}
#[test]
fn rust_only_repo_detects_only_rust_engineer() {
let tmp = TempDir::new().unwrap();
touch(tmp.path(), "Cargo.toml");
let detected = detected_stack_engineers(tmp.path()).engineers;
assert_eq!(
detected,
BTreeSet::from(["rust-engineer".to_string()]),
"a single-crate Rust repo detects exactly one engineer"
);
}
#[test]
fn truncated_detection_is_reported_to_the_caller() {
let deep = TempDir::new().unwrap();
touch(deep.path(), "Cargo.toml");
fs::create_dir_all(deep.path().join("a/b/c/d/e")).unwrap();
let detected = detected_stack_engineers(deep.path());
assert!(
detected.depth_limited,
"a tree with directories past the depth bound must report the depth limit"
);
assert!(
!detected.truncated,
"#7781 round-3: the depth bound is scope, not a resource cap — the \
fail-closed flag must stay clear"
);
assert!(
detected.engineers.contains("rust-engineer"),
"a bound reports a PARTIAL answer; it must not suppress what was found"
);
let wide = TempDir::new().unwrap();
fs::write(wide.path().join("package.json"), r#"{"workspaces":["m*"]}"#).unwrap();
for i in 0..=MAX_WORKSPACE_MEMBERS {
touch(wide.path(), &format!("m{i:04}/package.json"));
}
let detected = detected_stack_engineers(wide.path());
assert!(
detected.truncated,
"exhausting the shared member cap is truncation the caller must see"
);
assert!(
!detected.depth_limited,
"a one-level-deep workspace never reaches the depth bound"
);
let shallow = TempDir::new().unwrap();
touch(shallow.path(), "Cargo.toml");
let detected = detected_stack_engineers(shallow.path());
assert!(
!detected.truncated && !detected.depth_limited,
"a tree the walk read in full must claim neither bound"
);
}
#[test]
fn declared_member_cap_is_reported_to_the_caller() {
let tmp = TempDir::new().unwrap();
fs::write(
tmp.path().join("package.json"),
r#"{"workspaces":["m*","vendor/*"]}"#,
)
.unwrap();
for i in 0..MAX_WORKSPACE_MEMBERS {
touch(tmp.path(), &format!("m{i:04}/package.json"));
}
touch(tmp.path(), "vendor/dropped/package.json");
let detected = detected_stack_engineers(tmp.path());
assert!(
detected.truncated,
"dropping a declared member is a resource cap, and the caller must see \
it whether or not the nested walk can reach that member"
);
assert!(
!detected.depth_limited,
"a one-level-deep workspace never reaches the depth bound"
);
}
#[test]
fn bundled_skill_roster_is_valid() {
let content = crate::core::content_source::test_support::repo_content();
let skills =
framework_skill_categories_for(&content).expect("bundled skill roster must validate");
assert_eq!(
skills.universal.len(),
bundled_skill_stems(&content).len(),
"every bundled skill is declared exactly once"
);
assert!(skills.universal.contains(&"tm".to_string()));
assert_eq!(framework_skill_categories().expect("runtime read"), skills);
}
#[test]
fn bundled_skill_stems_excludes_reference_files() {
let stems = bundled_skill_stems(&crate::core::content_source::test_support::repo_content());
assert!(stems.contains("systematic-debugging"));
assert!(
!stems.iter().any(|s| s.contains('/')),
"a skill's own references/*.md are not catalog entries"
);
}
#[test]
fn missing_skill_section_is_an_error() {
let err = parse_framework_skills("version = 1\n", &fake_skill_catalog())
.expect_err("a manifest with no skill section declares nothing");
assert_eq!(err, FrameworkManifestError::MissingSkillCategories);
}
#[test]
fn unknown_skill_is_an_error() {
let raw = "[skill_categories]\nuniversal = [\"tm\", \"no-such-skill\"]\n";
let err = parse_framework_skills(raw, &fake_skill_catalog()).expect_err("unknown skill");
assert_eq!(
err,
FrameworkManifestError::UnknownSkill("no-such-skill".to_string())
);
}
#[test]
fn undeclared_bundled_skill_is_an_error() {
let raw = "[skill_categories]\nuniversal = []\n";
let err = parse_framework_skills(raw, &fake_skill_catalog()).expect_err("undeclared skill");
assert_eq!(
err,
FrameworkManifestError::UndeclaredSkills(vec!["tm".to_string()])
);
}
#[test]
fn duplicate_skill_is_an_error() {
let raw = "[skill_categories]\nuniversal = [\"tm\", \"tm\"]\n";
let err = parse_framework_skills(raw, &fake_skill_catalog()).expect_err("duplicate skill");
assert_eq!(
err,
FrameworkManifestError::DuplicateSkill("tm".to_string())
);
}
#[test]
fn universal_agents_deploy_with_no_markers() {
let tmp = TempDir::new().unwrap();
touch(tmp.path(), "README.md");
let scope = agent_scope_from(&parsed_fake(), tmp.path());
assert!(
deploys(&scope, "qa"),
"universal deploys with zero detection"
);
}
#[test]
fn language_engineers_still_gate_on_detection() {
let rust = TempDir::new().unwrap();
touch(rust.path(), "Cargo.toml");
let scope = agent_scope_from(&parsed_fake(), rust.path());
assert!(deploys(&scope, "rust-engineer"));
assert!(
deploys(&scope, "qa"),
"universal is unaffected by stack gating"
);
let js = TempDir::new().unwrap();
touch(js.path(), "package.json");
let scope = agent_scope_from(&parsed_fake(), js.path());
assert!(
deploys(&scope, "js-engineer"),
"the JS project detects its own engineer"
);
assert!(
!deploys(&scope, "rust-engineer"),
"a JS project must not get the Rust engineer"
);
assert!(
deploys(&scope, "qa"),
"...and the exclusion must be stack-specific, not a blanket drop"
);
}
#[test]
fn framework_engineers_gate_on_detection() {
let react = TempDir::new().unwrap();
std::fs::write(
react.path().join("package.json"),
"{\"dependencies\": {\"react\": \"^18.0.0\"}}",
)
.unwrap();
assert!(deploys(
&agent_scope_from(&parsed_fake(), react.path()),
"react-engineer"
));
let bare_js = TempDir::new().unwrap();
std::fs::write(
bare_js.path().join("package.json"),
"{\"dependencies\": {\"lodash\": \"^4.0.0\"}}",
)
.unwrap();
assert!(
!deploys(
&agent_scope_from(&parsed_fake(), bare_js.path()),
"react-engineer"
),
"a framework-free JS project must not receive react-engineer"
);
let rust = TempDir::new().unwrap();
touch(rust.path(), "Cargo.toml");
let scope = agent_scope_from(&parsed_fake(), rust.path());
assert!(!deploys(&scope, "react-engineer"));
assert!(
deploys(&scope, "rust-engineer"),
"the same call still selects the detected stack engineer"
);
}
#[test]
fn deprecated_agent_never_deploys() {
for markers in [
vec!["README.md"],
vec!["Cargo.toml"],
vec!["package.json", "vercel.json"],
] {
let tmp = TempDir::new().unwrap();
for m in &markers {
touch(tmp.path(), m);
}
let scope = agent_scope_from(&parsed_fake(), tmp.path());
assert!(
!deploys(&scope, "ops"),
"deprecated `ops` must not deploy for markers {markers:?}"
);
assert!(
deploys(&scope, "qa"),
"...while the universal agent still does, for markers {markers:?}"
);
}
}
#[test]
fn platform_agent_absent_without_marker() {
let tmp = TempDir::new().unwrap();
touch(tmp.path(), "Cargo.toml");
let scope = agent_scope_from(&parsed_fake(), tmp.path());
assert!(
!deploys(&scope, "vercel-ops"),
"no Vercel marker -> no vercel-ops"
);
assert!(
deploys(&scope, "rust-engineer") && deploys(&scope, "qa"),
"the same call still deploys the stack and universal agents — the empty \
platform result is explicit, not a collapsed selection"
);
}
#[test]
fn platform_agent_deploys_with_marker() {
let tmp = TempDir::new().unwrap();
touch(tmp.path(), "Cargo.toml");
touch(tmp.path(), "vercel.json");
assert!(deploys(
&agent_scope_from(&parsed_fake(), tmp.path()),
"vercel-ops"
));
}
#[test]
fn no_platform_detected_is_not_a_manifest_error() {
let tmp = TempDir::new().unwrap();
touch(tmp.path(), "Cargo.toml");
assert!(parse_framework_manifest(&fake_manifest(), &fake_catalog()).is_ok());
assert!(!deploys(
&agent_scope_from(&parsed_fake(), tmp.path()),
"vercel-ops"
));
assert!(parse_framework_manifest("broken {{{", &fake_catalog()).is_err());
}
#[test]
fn unknown_project_keeps_every_stack_engineer() {
let tmp = TempDir::new().unwrap();
touch(tmp.path(), "README.md");
let scope = agent_scope_from(&parsed_fake(), tmp.path());
assert!(deploys(&scope, "rust-engineer"));
assert!(deploys(&scope, "react-engineer"));
assert!(
!deploys(&scope, "vercel-ops"),
"platform has no unknown-project fallback"
);
assert!(!deploys(&scope, "ops"));
}
#[test]
fn stack_detection_is_memoized_per_project() {
let tmp = TempDir::new().unwrap();
touch(tmp.path(), "Cargo.toml");
let dir = tmp.path().to_path_buf();
let first = detected_stack_engineers(&dir);
assert!(first.engineers.contains("rust-engineer"), "{first:?}");
assert_eq!(super::super::detect_memo::computes_for(&dir), 1);
drop(tmp);
let second = detected_stack_engineers(&dir);
assert_eq!(
second.engineers, first.engineers,
"the memo answers a vanished tree, so nothing was walked or read"
);
let scope = framework_agent_scope(&dir);
assert!(deploys(&scope, "rust-engineer"));
assert_eq!(
super::super::detect_memo::computes_for(&dir),
1,
"both entry points share ONE walk"
);
}
#[test]
fn invalidating_picks_up_a_changed_tree() {
let tmp = TempDir::new().unwrap();
touch(tmp.path(), "Cargo.toml");
let before = detected_stack_engineers(tmp.path());
assert!(
!before.engineers.contains("javascript-engineer"),
"{before:?}"
);
fs::write(tmp.path().join("package.json"), "{}").unwrap();
assert_eq!(
detected_stack_engineers(tmp.path()).engineers,
before.engineers,
"the memo holds until the operation boundary"
);
super::super::detect_memo::invalidate_stack_detection(tmp.path());
let after = detected_stack_engineers(tmp.path());
assert!(after.engineers.contains("javascript-engineer"), "{after:?}");
assert_eq!(super::super::detect_memo::computes_for(tmp.path()), 2);
}
#[test]
fn memo_is_keyed_by_project_dir() {
let rust = TempDir::new().unwrap();
touch(rust.path(), "Cargo.toml");
let js = TempDir::new().unwrap();
touch(js.path(), "package.json");
let rust_engineers = detected_stack_engineers(rust.path()).engineers;
let js_engineers = detected_stack_engineers(js.path()).engineers;
assert!(rust_engineers.contains("rust-engineer"));
assert!(!rust_engineers.contains("javascript-engineer"));
assert!(js_engineers.contains("javascript-engineer"));
assert!(!js_engineers.contains("rust-engineer"));
assert_eq!(super::super::detect_memo::computes_for(rust.path()), 1);
assert_eq!(super::super::detect_memo::computes_for(js.path()), 1);
}