use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use std::sync::atomic::{AtomicU32, Ordering};
static COUNTER: AtomicU32 = AtomicU32::new(0);
struct Sandbox {
base: PathBuf,
source: PathBuf,
mind_home: PathBuf,
claude_home: PathBuf,
}
struct Run {
stdout: String,
stderr: String,
success: bool,
}
impl Sandbox {
fn new() -> Sandbox {
let n = COUNTER.fetch_add(1, Ordering::SeqCst);
let base = std::env::temp_dir().join(format!("mind-cmd-{}-{n}", std::process::id()));
let _ = std::fs::remove_dir_all(&base);
let source = base.join("agents");
let sb = Sandbox {
base: base.clone(),
source: source.clone(),
mind_home: base.join("mind"),
claude_home: base.join("claude"),
};
write(
&source.join("commands/ship.md"),
"---\ndescription: Cut a release\nargument-hint: [version]\n---\n# ship\n",
);
write(
&source.join("skills/review/SKILL.md"),
"---\nname: review\ndescription: Review the diff\n---\n# review\n",
);
git(&source, &["-c", "init.defaultBranch=main", "init", "-q"]);
git(&source, &["config", "user.email", "t@t"]);
git(&source, &["config", "user.name", "t"]);
git(&source, &["add", "-A"]);
git(&source, &["commit", "-qm", "initial"]);
std::fs::create_dir_all(&sb.mind_home).unwrap();
sb
}
fn mind(&self, args: &[&str]) -> Run {
let out = Command::new(env!("CARGO_BIN_EXE_mind"))
.args(args)
.env("MIND_HOME", &self.mind_home)
.env("CLAUDE_HOME", &self.claude_home)
.env_remove("MIND_AGENT_HOMES")
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.stdin(Stdio::null())
.output()
.expect("run mind");
Run {
stdout: String::from_utf8_lossy(&out.stdout).into_owned(),
stderr: String::from_utf8_lossy(&out.stderr).into_owned(),
success: out.status.success(),
}
}
fn write_and_commit(&self, rel: &str, contents: &str) {
write(&self.source.join(rel), contents);
git(&self.source, &["add", "-A"]);
git(&self.source, &["commit", "-qm", "fixture"]);
}
fn source_spec(&self) -> String {
self.source.to_string_lossy().into_owned()
}
fn write_config(&self, body: &str) {
write(&self.mind_home.join("config.toml"), body);
}
fn link(&self, name: &str) -> PathBuf {
self.claude_home.join("commands").join(name)
}
}
impl Drop for Sandbox {
fn drop(&mut self) {
let _ = std::fs::remove_dir_all(&self.base);
}
}
fn write(path: &Path, contents: &str) {
std::fs::create_dir_all(path.parent().unwrap()).unwrap();
std::fs::write(path, contents).unwrap();
}
fn git(repo: &Path, args: &[&str]) {
std::fs::create_dir_all(repo).unwrap();
let status = Command::new("git")
.args(args)
.current_dir(repo)
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()
.expect("run git");
assert!(status.success(), "git {args:?} failed");
}
#[test]
fn command_installs_to_the_store_and_links_into_the_lobe() {
let sb = Sandbox::new();
let r = sb.mind(&["meld", &sb.source_spec()]);
assert!(r.success, "meld: {}", r.stderr);
let r = sb.mind(&["probe", "--no-tui", "--kind", "command"]);
assert!(r.success, "probe: {}\n{}", r.stdout, r.stderr);
assert!(
r.stdout.contains("command:ship"),
"--kind command must select the command: {}",
r.stdout
);
assert!(
r.stdout.contains("Cut a release"),
"the command's frontmatter description must be shown: {}",
r.stdout
);
assert!(
!r.stdout.contains("skill:review"),
"--kind command must exclude other kinds: {}",
r.stdout
);
let r = sb.mind(&["learn", "command:ship"]);
assert!(r.success, "learn: {}\n{}", r.stdout, r.stderr);
let link = sb.link("ship.md");
assert!(
link.symlink_metadata().is_ok(),
"the command must be linked at commands/ship.md"
);
let target = std::fs::read_link(&link).expect("the lobe entry is a symlink");
assert!(
target.ends_with("store/command/ship"),
"the link must point at store/command/ship, got {target:?}"
);
assert_eq!(
std::fs::read_to_string(&link).unwrap(),
std::fs::read_to_string(sb.source.join("commands/ship.md")).unwrap(),
"the installed command is the source file"
);
let r = sb.mind(&["recall", "--kind", "command"]);
assert!(r.success, "recall: {}\n{}", r.stdout, r.stderr);
assert!(
r.stdout.contains("command:ship"),
"recall must report the installed command: {}",
r.stdout
);
}
#[test]
fn a_prefixed_command_links_under_its_namespaced_name() {
let sb = Sandbox::new();
let r = sb.mind(&["meld", &sb.source_spec(), "--namespace", "jk"]);
assert!(r.success, "meld: {}", r.stderr);
let r = sb.mind(&["learn", "command:jk:ship"]);
assert!(r.success, "learn: {}\n{}", r.stdout, r.stderr);
assert!(
sb.link("jk:ship.md").symlink_metadata().is_ok(),
"a prefixed command links at commands/jk:ship.md"
);
assert!(
sb.link("ship.md").symlink_metadata().is_err(),
"the bare name must not also be linked"
);
assert!(
sb.mind_home.join("store/command/jk:ship").exists(),
"the store copy uses the effective name"
);
}
#[test]
fn command_upgrades_and_forgets_like_any_item() {
let sb = Sandbox::new();
assert!(sb.mind(&["meld", &sb.source_spec()]).success);
assert!(sb.mind(&["learn", "command:ship"]).success);
sb.write_and_commit(
"commands/ship.md",
"---\ndescription: Cut a release\n---\n# ship v2\n",
);
assert!(sb.mind(&["sync"]).success, "sync must succeed");
let r = sb.mind(&["upgrade", "--yes"]);
assert!(r.success, "upgrade: {}\n{}", r.stdout, r.stderr);
assert!(
r.stdout.contains("upgraded command:ship"),
"the command must upgrade: {}",
r.stdout
);
assert!(
std::fs::read_to_string(sb.link("ship.md"))
.unwrap()
.contains("# ship v2"),
"the linked file must show the new content"
);
let r = sb.mind(&["forget", "command:ship", "--yes"]);
assert!(r.success, "forget: {}\n{}", r.stdout, r.stderr);
assert!(
sb.link("ship.md").symlink_metadata().is_err(),
"forget removes the lobe link"
);
assert!(
!sb.mind_home.join("store/command/ship").exists(),
"forget removes the store copy"
);
}
#[test]
fn a_skills_only_lobe_admits_no_commands() {
let sb = Sandbox::new();
let other = sb.base.join("gemini");
std::fs::create_dir_all(&other).unwrap();
sb.write_config(&format!(
"[[lobes]]\npath = \"{}\"\n\n[[lobes]]\npath = \"{}\"\nkinds = [\"skill\"]\n",
sb.claude_home.display(),
other.display(),
));
assert!(sb.mind(&["meld", &sb.source_spec()]).success);
let r = sb.mind(&["learn", "command:ship"]);
assert!(r.success, "learn: {}\n{}", r.stdout, r.stderr);
assert!(
sb.link("ship.md").symlink_metadata().is_ok(),
"the all-kinds lobe gets the command"
);
assert!(
!other.join("commands/ship.md").exists(),
"a skills-only lobe must not receive a command link"
);
}
#[test]
fn a_hand_written_command_is_reported_as_unmanaged() {
let sb = Sandbox::new();
write(
&sb.claude_home.join("commands/mine.md"),
"---\ndescription: hand written\n---\n# mine\n",
);
assert!(sb.mind(&["meld", &sb.source_spec()]).success);
let r = sb.mind(&["recall"]);
assert!(r.success, "recall: {}\n{}", r.stdout, r.stderr);
assert!(
r.stdout.contains("unmanaged") && r.stdout.contains("command:mine"),
"a hand-written command must be listed as unmanaged: {}",
r.stdout
);
}
#[test]
fn a_nested_hand_written_command_is_not_detected_as_unmanaged() {
let sb = Sandbox::new();
write(
&sb.claude_home.join("commands/frontend/component.md"),
"---\ndescription: hand written, nested\n---\n# component\n",
);
assert!(sb.mind(&["meld", &sb.source_spec()]).success);
let r = sb.mind(&["recall"]);
assert!(r.success, "recall: {}\n{}", r.stdout, r.stderr);
assert!(
!r.stdout.contains("component"),
"a nested command must not be surfaced as unmanaged at all: {}",
r.stdout
);
let r = sb.mind(&["absorb", "frontend:component"]);
assert!(
!r.success,
"absorb must not find a nested command: stdout={} stderr={}",
r.stdout, r.stderr
);
assert!(
r.stderr.contains("not installed") || r.stderr.contains("NotInstalled"),
"error must indicate not installed: {}",
r.stderr
);
}