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,
mind_home: PathBuf,
claude_home: PathBuf,
}
struct Run {
stdout: String,
stderr: String,
success: bool,
}
impl Sandbox {
fn new(tag: &str) -> Sandbox {
let n = COUNTER.fetch_add(1, Ordering::SeqCst);
let base = std::env::temp_dir().join(format!("mind-doc-{tag}-{}-{n}", std::process::id()));
let _ = std::fs::remove_dir_all(&base);
std::fs::create_dir_all(&base).unwrap();
Sandbox {
mind_home: base.join("mind"),
claude_home: base.join("claude"),
base,
}
}
fn mind_in(&self, cwd: &Path, path_prefix: Option<&Path>, args: &[&str]) -> Run {
let mut cmd = Command::new(env!("CARGO_BIN_EXE_mind"));
cmd.args(args)
.current_dir(cwd)
.env("MIND_HOME", &self.mind_home)
.env("CLAUDE_HOME", &self.claude_home)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.stdin(Stdio::null());
if let Some(prefix) = path_prefix {
let orig = std::env::var("PATH").unwrap_or_default();
cmd.env("PATH", format!("{}:{orig}", prefix.display()));
}
let out = cmd.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 mind(&self, args: &[&str]) -> Run {
self.mind_in(&self.base, None, args)
}
}
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 write_exec(path: &Path, contents: &str) {
write(path, contents);
use std::os::unix::fs::PermissionsExt;
let mut perms = std::fs::metadata(path).unwrap().permissions();
perms.set_mode(0o755);
std::fs::set_permissions(path, perms).unwrap();
}
fn git(dir: &Path, args: &[&str]) {
let status = Command::new("git")
.args(args)
.current_dir(dir)
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()
.expect("run git");
assert!(status.success(), "git {args:?} failed in {dir:?}");
}
fn init_repo(dir: &Path) {
write(&dir.join("README.md"), "# fixture\n");
git(dir, &["-c", "init.defaultBranch=main", "init", "-q"]);
git(dir, &["config", "user.email", "t@t"]);
git(dir, &["config", "user.name", "t"]);
git(dir, &["add", "-A"]);
git(dir, &["commit", "-qm", "initial"]);
}
fn stub_git(bin_dir: &Path, log: &Path) -> PathBuf {
let script = format!(
"#!/bin/sh\nprintf '%s\\n' \"$*\" >> {log:?}\nexit 1\n",
log = log
);
write_exec(&bin_dir.join("git"), &script);
bin_dir.to_path_buf()
}
#[test]
fn review_reads_a_two_segment_relative_path_as_a_local_dir_without_cloning() {
let sb = Sandbox::new("rel-review");
let work = sb.base.join("work");
let bin = sb.base.join("bin");
std::fs::create_dir_all(&bin).unwrap();
let git_log = sb.base.join("git.log");
let path_prefix = stub_git(&bin, &git_log);
write(
&work.join("skills/greet/mind.toml"),
"this is not = valid = toml\n",
);
let r = sb.mind_in(&work, Some(&path_prefix), &["review", "skills/greet"]);
assert!(
!git_log.exists(),
"review must not invoke git at all for a local directory target; \
recorded invocations: {:?}",
std::fs::read_to_string(&git_log).unwrap_or_default()
);
assert!(
!r.success,
"the local directory's malformed mind.toml is a hard finding, so this \
must exit non-zero -- proving the local bytes were read: {}\n{}",
r.stdout, r.stderr
);
let combined = format!("{}{}", r.stdout, r.stderr);
assert!(
combined.contains("mind.toml") || combined.to_lowercase().contains("toml"),
"the failure must be the local mind.toml parse, not a clone failure: {combined}"
);
assert!(
r.stderr.contains("skills/greet") && r.stderr.contains("mind review 'github:skills/greet'"),
"the shadow note must name the target and the remote-forcing escape: {}",
r.stderr
);
}
#[test]
fn review_relative_path_to_a_clean_local_source_exits_zero_without_cloning() {
let sb = Sandbox::new("rel-clean");
let work = sb.base.join("work");
let bin = sb.base.join("bin");
std::fs::create_dir_all(&bin).unwrap();
let git_log = sb.base.join("git.log");
let path_prefix = stub_git(&bin, &git_log);
write(
&work.join("skills/greet/skills/hello/SKILL.md"),
"---\ndescription: say hello\n---\n# hello\n",
);
let r = sb.mind_in(&work, Some(&path_prefix), &["review", "skills/greet"]);
assert!(
r.success,
"a clean local source at a two-segment relative path must review \
cleanly: {}\n{}",
r.stdout, r.stderr
);
assert!(
!git_log.exists(),
"no clone may be attempted: {:?}",
std::fs::read_to_string(&git_log).unwrap_or_default()
);
}
#[test]
fn review_registry_selector_wins_over_an_identically_named_local_directory() {
let sb = Sandbox::new("registry-wins");
let melded = sb.base.join("greet");
write(
&melded.join("skills/hello/SKILL.md"),
"---\ndescription: say hello\n---\n# hello\n",
);
init_repo(&melded);
let r = sb.mind(&["meld", melded.to_string_lossy().as_ref(), "--register-only"]);
assert!(r.success, "meld: {}\n{}", r.stdout, r.stderr);
let work = sb.base.join("work");
write(
&work.join("greet/mind.toml"),
"this is not = valid = toml\n",
);
let r = sb.mind_in(&work, None, &["review", "greet"]);
assert!(
r.success,
"the registry match wins, so the CLEAN melded source is what gets \
reviewed -- if the local ./greet had won, its malformed mind.toml \
would be a hard finding and this would exit non-zero: {}\n{}",
r.stdout, r.stderr
);
assert!(
r.stdout.contains("no issues found"),
"the clean melded source is what was reviewed: {}",
r.stdout
);
}
#[test]
fn review_local_directory_wins_when_no_source_is_registered() {
let sb = Sandbox::new("local-wins");
let work = sb.base.join("work");
write(
&work.join("greet/mind.toml"),
"this is not = valid = toml\n",
);
let r = sb.mind_in(&work, None, &["review", "greet"]);
assert!(
!r.success,
"with nothing registered, ./greet is what is reviewed, and its \
malformed mind.toml is a hard finding: {}\n{}",
r.stdout, r.stderr
);
}
#[test]
fn dead_local_source_recovery_recipe_from_the_docs_works_end_to_end() {
let sb = Sandbox::new("dead-source");
let source = sb.base.join("greet");
write(
&source.join("skills/hello/SKILL.md"),
"---\ndescription: say hello\n---\n# hello\n",
);
init_repo(&source);
let r = sb.mind(&["meld", source.to_string_lossy().as_ref(), "--register-only"]);
assert!(r.success, "meld: {}\n{}", r.stdout, r.stderr);
let identity = format!(
"local/{}/greet",
sb.base.file_name().unwrap().to_string_lossy()
);
std::fs::remove_dir_all(&source).unwrap();
let r = sb.mind(&["recall", "--sources"]);
assert!(
r.success,
"recall --sources must still run with the directory gone: {}\n{}",
r.stdout, r.stderr
);
assert!(
r.stdout.contains(&identity),
"the recipe says to read the source's name here, so the full identity \
`unmeld` accepts must be printed: {}",
r.stdout
);
let r = sb.mind(&["unmeld", &identity]);
assert!(
r.success,
"unmeld must drop a source whose linked working tree is gone -- this \
is the documented remedy, and it is the only way out: {}\n{}",
r.stdout, r.stderr
);
let r = sb.mind(&["recall", "--sources"]);
assert!(r.success, "recall after unmeld: {}\n{}", r.stdout, r.stderr);
assert!(
!r.stdout.contains(&identity),
"the dropped source must no longer be listed: {}",
r.stdout
);
let r = sb.mind(&["recall"]);
assert!(
r.success && !r.stderr.contains("is gone"),
"the vanished-source warning must be gone once it is unmelded: {}\n{}",
r.stdout,
r.stderr
);
}
#[test]
fn unmeld_of_a_vanished_source_does_not_require_the_directory_to_exist() {
let sb = Sandbox::new("unmeld-gone");
let source = sb.base.join("ghost");
write(
&source.join("skills/hello/SKILL.md"),
"---\ndescription: say hello\n---\n# hello\n",
);
init_repo(&source);
assert!(
sb.mind(&["meld", source.to_string_lossy().as_ref(), "--register-only"])
.success
);
std::fs::remove_dir_all(&source).unwrap();
let r = sb.mind(&["unmeld", "ghost"]);
assert!(
r.success,
"unmeld by the short selector must work on a vanished source: {}\n{}",
r.stdout, r.stderr
);
}
#[test]
fn config_lobes_add_backfills_only_the_kinds_the_lobe_admits() {
let sb = Sandbox::new("kinds-backfill");
let source = sb.base.join("agents");
write(
&source.join("skills/hello/SKILL.md"),
"---\ndescription: say hello\n---\n# hello\n",
);
write(
&source.join("rules/style-rule.md"),
"---\ndescription: ASCII only\n---\n# style\n",
);
init_repo(&source);
assert!(
sb.mind(&["meld", source.to_string_lossy().as_ref(), "--register-only"])
.success
);
assert!(sb.mind(&["learn", "hello"]).success, "learn skill");
assert!(sb.mind(&["learn", "style-rule"]).success, "learn rule");
let project = sb.base.join("project");
std::fs::create_dir_all(&project).unwrap();
let r = sb.mind(&[
"config",
"lobes",
"add",
project.to_string_lossy().as_ref(),
"--preset",
"windsurf",
]);
assert!(r.success, "config lobes add: {}\n{}", r.stdout, r.stderr);
let windsurf = project.join(".windsurf");
assert!(
std::fs::symlink_metadata(windsurf.join("skills/hello")).is_ok(),
"the skill is a kind the windsurf lobe admits, so it must be backfilled: {}",
r.stdout
);
assert!(
std::fs::symlink_metadata(windsurf.join("rules/style-rule.md")).is_err(),
"the windsurf preset is skill-only, so the already-installed rule must \
NOT be backfilled into it: {}",
r.stdout
);
}
#[test]
fn config_lobes_add_creates_target_dir_only_on_the_managed_path() {
let sb = Sandbox::new("mkdir-managed");
let managed = sb.base.join("managed-target");
assert!(!managed.exists(), "precondition: target must not pre-exist");
let r = sb.mind(&["config", "lobes", "add", managed.to_string_lossy().as_ref()]);
assert!(r.success, "config lobes add: {}\n{}", r.stdout, r.stderr);
assert!(
std::fs::symlink_metadata(&managed).is_ok(),
"a managed lobe add must create its target directory immediately: {}",
r.stdout
);
let snap = sb.base.join("snapshot-target");
assert!(!snap.exists(), "precondition: target must not pre-exist");
let r = sb.mind(&[
"config",
"lobes",
"add",
snap.to_string_lossy().as_ref(),
"--snapshot",
]);
assert!(r.success, "snapshot add: {}\n{}", r.stdout, r.stderr);
assert!(
std::fs::symlink_metadata(&snap).is_err(),
"--snapshot must NOT create the target directory (HARN-15 is confined \
to the managed path): {}",
r.stdout
);
}