use super::prompt_fork_point::{Fixture, fixture, git, system_slot};
use std::fs;
fn declare_planner(fx: &Fixture) {
let yaml = "\
roles:
worker:
provider: test
model: claude-sonnet-5
tools: [bash, read_file]
compactor:
provider: test
model: claude-haiku-4-5
planner:
provider: test
model: claude-sonnet-5
tools: [load_skill, read_file, search_history]
";
crate::template::authoring::author(
&fx.ws,
&fx.ws.join(".no-pools"),
"default",
crate::template::authoring::Origin::Advance,
|dir| fs::write(dir.join("providers.yaml"), yaml),
&crate::template::RealGit::new(),
)
.unwrap();
}
fn declared_tools(fx: &Fixture, agent: &str) -> Vec<String> {
let path = fx.ws.join("steps").join(agent).join("001/request.json");
let request: serde_json::Value = serde_json::from_slice(&fs::read(path).unwrap()).unwrap();
let mut names: Vec<String> = request["tools"]
.as_array()
.expect("a request declares its tools")
.iter()
.map(|t| t["name"].as_str().expect("a tool has a name").to_string())
.collect();
names.sort();
names
}
fn dispatch_subject(fx: &Fixture, agent: &str) -> String {
let bare = fx.bare();
let pattern = crate::prompt::role::founding_pattern(agent);
git(
&bare,
&[
"log",
"-n",
"1",
"--format=%s",
"-E",
"--grep",
&pattern,
&format!("agents/{agent}"),
],
)
}
#[test]
fn a_root_born_on_planner_carries_the_planner_grant_and_soul() {
let fx = fixture();
declare_planner(&fx);
let planner = fx.start("how would you add X to Y?", &["--role", "planner"]);
let soul = git(&fx.bare(), &["show", &format!("agents/{planner}:soul.md")]);
assert!(soul.starts_with("# Planner"), "got {soul:?}");
assert!(
system_slot(&fx, &planner).ends_with(&soul),
"the soul closes the system slot"
);
assert_eq!(
declared_tools(&fx, &planner),
["load_skill", "read_file", "search_history"]
);
assert_eq!(
dispatch_subject(&fx, &planner),
format!("dispatch: planner [{planner}]")
);
}
#[test]
fn a_start_naming_no_role_is_the_same_path_with_the_worker_default() {
let fx = fixture();
let worker = fx.start("do it", &[]);
let soul = git(&fx.bare(), &["show", &format!("agents/{worker}:soul.md")]);
assert!(soul.starts_with("# Worker"), "got {soul:?}");
let tools = declared_tools(&fx, &worker);
assert!(tools.contains(&"bash".to_string()), "got {tools:?}");
assert_eq!(
dispatch_subject(&fx, &worker),
format!("dispatch: worker [{worker}]")
);
}
#[test]
fn a_role_the_governing_config_does_not_declare_is_refused_before_any_branch() {
let fx = fixture();
let bare = fx.bare();
let before = git(&bare, &["for-each-ref", "--format=%(refname)"]);
let out = fx.prompt("hi", &["--role", "ghost"]);
assert!(!out.status.success());
let err = String::from_utf8_lossy(&out.stderr).to_string();
assert!(err.contains("ghost"), "{err}");
assert_eq!(before, git(&bare, &["for-each-ref", "--format=%(refname)"]));
assert!(!fx.ws.join("agents").exists());
}