use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use std::sync::atomic::{AtomicU32, Ordering};
use serde_json::Value;
static COUNTER: AtomicU32 = AtomicU32::new(0);
const PROFILES: &str = "[profiles]\n\
default = [\"basic\"]\n\
basic = [\"skill:review\", \"rule:style\"]\n\
full = [\"basic\", \"agent:*\", \"skill:deploy\"]\n";
struct Sandbox {
base: PathBuf,
source: PathBuf,
mind_home: PathBuf,
claude_home: PathBuf,
}
struct Run {
stdout: String,
stderr: String,
success: bool,
}
impl Sandbox {
fn with_toml(name: &str, mind_toml: &str) -> Sandbox {
let n = COUNTER.fetch_add(1, Ordering::SeqCst);
let base = std::env::temp_dir().join(format!("mind-profiles-{}-{n}", std::process::id()));
let _ = std::fs::remove_dir_all(&base);
let source = base.join(name);
let sb = Sandbox {
base: base.clone(),
source: source.clone(),
mind_home: base.join("mind"),
claude_home: base.join("claude"),
};
for skill in ["review", "test", "helper"] {
write_file(
&source.join(format!("skills/{skill}/SKILL.md")),
&format!("---\nname: {skill}\ndescription: {skill} skill\n---\n# {skill}\n"),
);
}
write_file(
&source.join("skills/deploy/SKILL.md"),
"---\nname: deploy\ndescription: deploy skill\n---\n# deploy\nUses {{ns:helper}}.\n",
);
write_file(
&source.join("agents/planner.md"),
"---\nname: planner\ndescription: Planner agent\n---\n# planner\n",
);
write_file(
&source.join("rules/style.md"),
"---\ndescription: Style rule\n---\n# style\n",
);
if !mind_toml.is_empty() {
write_file(&source.join("mind.toml"), mind_toml);
}
git_init(&source);
sb
}
fn new(name: &str) -> Sandbox {
Sandbox::with_toml(name, PROFILES)
}
fn spec(&self) -> String {
self.source.to_string_lossy().into_owned()
}
fn identity(&self) -> String {
format!(
"local/{}/{}",
self.base.file_name().unwrap().to_string_lossy(),
self.source.file_name().unwrap().to_string_lossy(),
)
}
fn mind(&self, args: &[&str]) -> Run {
self.mind_env(args, &[], None)
}
fn mind_env(&self, args: &[&str], envs: &[(&str, &str)], stdin: Option<&str>) -> Run {
use std::io::Write;
let mut cmd = Command::new(env!("CARGO_BIN_EXE_mind"));
cmd.args(args)
.env("MIND_HOME", &self.mind_home)
.env("CLAUDE_HOME", &self.claude_home)
.env_remove("MIND_MAX_METADATA_SIZE")
.env_remove("MIND_TTY")
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.stdin(match stdin {
Some(_) => Stdio::piped(),
None => Stdio::null(),
});
for (k, v) in envs {
cmd.env(k, v);
}
let mut child = cmd.spawn().expect("run mind");
if let Some(text) = stdin {
child
.stdin
.as_mut()
.expect("piped stdin")
.write_all(text.as_bytes())
.expect("write stdin");
}
let out = child.wait_with_output().expect("wait for 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 source_entry(&self, identity: &str) -> Option<Value> {
let raw = std::fs::read_to_string(self.mind_home.join("sources.json")).ok()?;
let v: Value = serde_json::from_str(&raw).expect("sources.json is JSON");
let list = v
.get("sources")
.and_then(Value::as_array)
.cloned()
.or_else(|| v.as_array().cloned())
.unwrap_or_default();
list.into_iter()
.find(|s| s["name"].as_str() == Some(identity))
}
fn recorded_profiles(&self) -> Option<Vec<String>> {
strings(&self.source_entry(&self.identity())?["profiles"])
}
fn recorded_snapshot(&self) -> Option<Vec<String>> {
strings(&self.source_entry(&self.identity())?["profile_snapshot"])
}
fn manifest_item(&self, key: &str) -> Option<Value> {
let raw = std::fs::read_to_string(self.mind_home.join("manifest.json")).ok()?;
let v: Value = serde_json::from_str(&raw).expect("manifest.json is JSON");
v["items"].get(key).cloned()
}
fn installed(&self) -> Vec<String> {
let Ok(raw) = std::fs::read_to_string(self.mind_home.join("manifest.json")) else {
return Vec::new();
};
let v: Value = serde_json::from_str(&raw).expect("manifest.json is JSON");
let mut keys: Vec<String> = v["items"]
.as_object()
.map(|o| o.keys().cloned().collect())
.unwrap_or_default();
keys.sort();
keys
}
fn reasons(&self, key: &str) -> Vec<String> {
self.manifest_item(key)
.and_then(|i| strings(&i["profiles"]))
.unwrap_or_default()
}
}
impl Drop for Sandbox {
fn drop(&mut self) {
let _ = std::fs::remove_dir_all(&self.base);
}
}
fn strings(v: &Value) -> Option<Vec<String>> {
v.as_array().map(|a| {
a.iter()
.map(|s| s.as_str().unwrap_or_default().to_string())
.collect()
})
}
fn svec(items: &[&str]) -> Vec<String> {
items.iter().map(|s| s.to_string()).collect()
}
fn write_file(path: &Path, contents: &str) {
std::fs::create_dir_all(path.parent().unwrap()).unwrap();
std::fs::write(path, contents).unwrap();
}
fn git_init(dir: &Path) {
for args in [
vec!["-c", "init.defaultBranch=main", "init", "-q"],
vec!["config", "user.email", "t@t"],
vec!["config", "user.name", "t"],
vec!["add", "-A"],
vec!["commit", "-qm", "initial"],
] {
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:?} in {dir:?}");
}
}
fn backticked(text: &str) -> String {
let start = text
.find('`')
.unwrap_or_else(|| panic!("expected a backticked command in: {text}"));
let rest = &text[start + 1..];
let end = rest.find('`').expect("a closing backtick");
rest[..end].to_string()
}
fn shell_split(s: &str) -> Vec<String> {
let mut tokens = Vec::new();
let mut cur = String::new();
let mut in_token = false;
let mut quoted = false;
for c in s.chars() {
if quoted {
if c == '\'' {
quoted = false;
} else {
cur.push(c);
}
continue;
}
if c.is_whitespace() {
if in_token {
tokens.push(std::mem::take(&mut cur));
in_token = false;
}
continue;
}
in_token = true;
if c == '\'' {
quoted = true;
} else {
cur.push(c);
}
}
if in_token {
tokens.push(cur);
}
tokens
}
fn run_printed(sb: &Sandbox, command: &str, extra: &[&str]) -> Run {
let mut last = None;
for step in command.split("&&") {
let argv = shell_split(step.trim());
assert_eq!(argv.first().map(String::as_str), Some("mind"), "{command}");
let mut args: Vec<&str> = argv[1..].iter().map(String::as_str).collect();
args.extend_from_slice(extra);
let run = sb.mind(&args);
if !run.success {
return run;
}
last = Some(run);
}
last.expect("at least one step")
}
fn json_doc(run: &Run) -> Value {
serde_json::from_str(run.stdout.trim())
.unwrap_or_else(|e| panic!("stdout is one JSON object ({e}): {}", run.stdout))
}
#[test]
fn source_without_profiles_keeps_install_all() {
let sb = Sandbox::with_toml("plain", "");
let r = sb.mind(&["meld", &sb.spec(), "--yes"]);
assert!(r.success, "{} {}", r.stdout, r.stderr);
assert_eq!(
sb.installed(),
svec(&[
"agent:planner",
"rule:style",
"skill:deploy",
"skill:helper",
"skill:review",
"skill:test"
])
);
assert_eq!(sb.recorded_profiles(), None);
assert_eq!(sb.recorded_snapshot(), None);
assert!(sb.reasons("skill:review").is_empty());
}
#[test]
fn profiles_without_default_keep_install_all_and_stay_convention() {
let sb = Sandbox::with_toml("nodefault", "[profiles]\nbasic = [\"skill:review\"]\n");
let r = sb.mind(&["meld", &sb.spec(), "--yes"]);
assert!(r.success, "{} {}", r.stdout, r.stderr);
assert_eq!(sb.installed().len(), 6, "{:?}", sb.installed());
assert_eq!(sb.recorded_profiles(), None);
assert!(sb.reasons("skill:review").is_empty());
}
#[test]
fn plain_meld_offers_the_default_expansion_and_records_it() {
let sb = Sandbox::new("src");
let r = sb.mind(&["meld", &sb.spec(), "--yes"]);
assert!(r.success, "{} {}", r.stdout, r.stderr);
assert_eq!(sb.installed(), svec(&["rule:style", "skill:review"]));
assert!(r.stdout.contains("enabled default"), "{}", r.stdout);
assert!(
r.stdout.contains("not enabled: basic, full"),
"{}",
r.stdout
);
assert!(r.stdout.contains("--profile"), "{}", r.stdout);
assert!(r.stdout.contains("--all-profiles"), "{}", r.stdout);
assert_eq!(sb.recorded_profiles(), Some(svec(&["default"])));
assert_eq!(
sb.recorded_snapshot(),
Some(svec(&["rule:style", "skill:review"]))
);
assert_eq!(sb.reasons("skill:review"), svec(&["default"]));
let item = sb.manifest_item("skill:review").unwrap();
assert!(item.get("explicit").is_none(), "{item}");
}
#[test]
fn profile_flag_adds_to_default_and_tags_closure_items() {
let sb = Sandbox::new("src");
let r = sb.mind(&["meld", &sb.spec(), "--profile", "full,basic", "--yes"]);
assert!(r.success, "{} {}", r.stdout, r.stderr);
assert_eq!(
sb.installed(),
svec(&[
"agent:planner",
"rule:style",
"skill:deploy",
"skill:helper",
"skill:review"
])
);
assert_eq!(
sb.recorded_profiles(),
Some(svec(&["basic", "default", "full"]))
);
assert_eq!(sb.reasons("skill:helper"), svec(&["full"]));
assert_eq!(
sb.reasons("skill:review"),
svec(&["basic", "default", "full"])
);
let snap = sb.recorded_snapshot().unwrap();
assert!(
!snap.contains(&"skill:helper".to_string()),
"a closure item is not a member: {snap:?}"
);
assert!(!sb.installed().contains(&"skill:test".to_string()));
}
#[test]
fn no_default_profile_alone_installs_nothing_and_names_the_remedy() {
let sb = Sandbox::new("src");
let r = sb.mind(&["meld", &sb.spec(), "--no-default-profile", "--yes"]);
assert!(r.success, "{} {}", r.stdout, r.stderr);
assert!(sb.installed().is_empty(), "{:?}", sb.installed());
assert!(
sb.source_entry(&sb.identity()).is_some(),
"still registered"
);
assert_eq!(sb.recorded_profiles(), None);
assert!(r.stdout.contains("mind learn"), "{}", r.stdout);
assert!(r.stdout.contains("--profile <name>"), "{}", r.stdout);
}
#[test]
fn no_default_profile_with_profile_enables_only_the_named() {
let sb = Sandbox::new("src");
let r = sb.mind(&[
"meld",
&sb.spec(),
"--no-default-profile",
"--profile",
"full",
"--yes",
]);
assert!(r.success, "{} {}", r.stdout, r.stderr);
assert_eq!(sb.recorded_profiles(), Some(svec(&["full"])));
assert_eq!(sb.reasons("skill:review"), svec(&["full"]));
}
#[test]
fn all_profiles_enables_every_declared_profile_but_not_unlisted_items() {
let sb = Sandbox::new("src");
let r = sb.mind(&[
"meld",
&sb.spec(),
"--all-profiles",
"--profile",
"basic",
"--yes",
]);
assert!(r.success, "{} {}", r.stdout, r.stderr);
assert_eq!(
sb.recorded_profiles(),
Some(svec(&["basic", "default", "full"]))
);
assert!(
!sb.installed().contains(&"skill:test".to_string()),
"an item in no profile is not installed: {:?}",
sb.installed()
);
}
#[test]
fn malformed_profile_name_is_refused_before_the_clone() {
let sb = Sandbox::new("src");
let r = sb.mind(&["meld", &sb.spec(), "--profile", "Bad", "--yes"]);
assert!(!r.success);
assert!(
r.stderr.contains("invalid profile name 'Bad'"),
"{}",
r.stderr
);
assert!(sb.source_entry(&sb.identity()).is_none());
}
#[test]
fn undeclared_profile_is_profile_not_found_and_source_stays_melded() {
let sb = Sandbox::new("src");
let r = sb.mind(&["meld", &sb.spec(), "--profile", "nope", "--yes"]);
assert!(!r.success);
assert!(
r.stderr.contains("declares no profile 'nope'"),
"{}",
r.stderr
);
assert!(r.stderr.contains("basic, default, full"), "{}", r.stderr);
assert!(sb.source_entry(&sb.identity()).is_some());
assert!(sb.installed().is_empty());
assert_eq!(sb.recorded_profiles(), None);
let plain = Sandbox::with_toml("plain", "");
let r = plain.mind(&["meld", &plain.spec(), "--profile", "full", "--yes"]);
assert!(!r.success);
assert!(r.stderr.contains("declares no profiles"), "{}", r.stderr);
}
#[test]
fn exact_member_naming_no_item_is_bad_reference_at_meld() {
let sb = Sandbox::with_toml(
"src",
"[profiles]\ndefault = [\"skill:missing\", \"command:*\"]\n",
);
let r = sb.mind(&["meld", &sb.spec(), "--yes"]);
assert!(!r.success, "{}", r.stdout);
assert!(r.stderr.contains("skill:missing"), "{}", r.stderr);
assert!(sb.installed().is_empty());
let ok = Sandbox::with_toml(
"src",
"[profiles]\ndefault = [\"skill:review\", \"command:*\"]\n",
);
let r = ok.mind(&["meld", &ok.spec(), "--yes"]);
assert!(r.success, "{} {}", r.stdout, r.stderr);
assert_eq!(ok.installed(), svec(&["skill:review"]));
}
#[test]
fn profile_composes_with_learn_and_conflicts_with_register_only() {
let sb = Sandbox::new("src");
let r = sb.mind(&["meld", &sb.spec(), "--learn", "test", "--yes"]);
assert!(r.success, "{} {}", r.stdout, r.stderr);
assert_eq!(
sb.installed(),
svec(&["rule:style", "skill:review", "skill:test"])
);
assert!(sb.reasons("skill:test").is_empty());
assert_eq!(
sb.recorded_snapshot(),
Some(svec(&["rule:style", "skill:review"])),
"a --learn match is not a member"
);
for flag in [&["--profile", "full"][..], &["--all-profiles"][..]] {
let other = Sandbox::new("src");
let spec = other.spec();
let mut args = vec!["meld", spec.as_str(), "--register-only"];
args.extend_from_slice(flag);
let r = other.mind(&args);
assert!(!r.success, "{flag:?} must conflict with --register-only");
assert!(r.stderr.contains("cannot be used with"), "{}", r.stderr);
assert!(other.source_entry(&other.identity()).is_none());
}
}
#[test]
fn non_tty_meld_without_yes_records_nothing_and_its_remedy_works() {
let sb = Sandbox::new("src");
let r = sb.mind(&["meld", &sb.spec(), "--profile", "full"]);
assert!(r.success, "{} {}", r.stdout, r.stderr);
assert!(sb.installed().is_empty());
assert_eq!(sb.recorded_profiles(), None);
let line = r
.stdout
.lines()
.find(|l| l.contains("not a TTY"))
.unwrap_or_else(|| panic!("{}", r.stdout));
let cmd = backticked(line);
assert!(cmd.contains("--profile default,full"), "{cmd}");
let run = run_printed(&sb, &cmd, &["--yes"]);
assert!(run.success, "{} {}", run.stdout, run.stderr);
assert_eq!(sb.recorded_profiles(), Some(svec(&["default", "full"])));
assert!(sb.installed().contains(&"skill:deploy".to_string()));
}
#[test]
fn tty_meld_prompts_once_and_records_only_on_yes() {
let sb = Sandbox::new("src");
let r = sb.mind_env(
&["meld", &sb.spec(), "--profile", "basic"],
&[("MIND_TTY", "1")],
Some("n\n"),
);
assert!(r.success, "{} {}", r.stdout, r.stderr);
assert!(r.stdout.contains("would learn 2 item(s)"), "{}", r.stdout);
assert!(
r.stdout.contains("skipped; run `mind learn"),
"{}",
r.stdout
);
assert!(sb.installed().is_empty());
assert_eq!(sb.recorded_profiles(), None);
let sb = Sandbox::new("src");
let r = sb.mind_env(
&["meld", &sb.spec(), "--profile", "basic"],
&[("MIND_TTY", "1")],
Some("y\n"),
);
assert!(r.success, "{} {}", r.stdout, r.stderr);
assert_eq!(sb.installed(), svec(&["rule:style", "skill:review"]));
assert_eq!(sb.recorded_profiles(), Some(svec(&["basic", "default"])));
}
#[test]
fn interactive_meld_asks_which_profiles_with_default_preselected() {
let sb = Sandbox::new("src");
let r = sb.mind_env(&["meld", &sb.spec()], &[("MIND_TTY", "1")], Some("\ny\n"));
assert!(r.success, "{} {}", r.stdout, r.stderr);
assert!(r.stdout.contains("enable which profiles?"), "{}", r.stdout);
assert_eq!(sb.recorded_profiles(), Some(svec(&["default"])));
let sb = Sandbox::new("src");
let r = sb.mind_env(
&["meld", &sb.spec()],
&[("MIND_TTY", "1")],
Some("full\ny\n"),
);
assert!(r.success, "{} {}", r.stdout, r.stderr);
assert_eq!(sb.recorded_profiles(), Some(svec(&["full"])));
assert!(sb.installed().contains(&"agent:planner".to_string()));
let sb = Sandbox::new("src");
let r = sb.mind_env(
&["meld", &sb.spec(), "--profile", "basic"],
&[("MIND_TTY", "1")],
Some("y\n"),
);
assert!(!r.stdout.contains("enable which profiles?"), "{}", r.stdout);
}
#[test]
fn remeld_adds_to_the_recorded_set_and_never_disables() {
let sb = Sandbox::new("src");
assert!(sb.mind(&["meld", &sb.spec(), "--yes"]).success);
assert_eq!(sb.recorded_profiles(), Some(svec(&["default"])));
let r = sb.mind(&["meld", &sb.spec(), "--profile", "full", "--yes"]);
assert!(r.success, "{} {}", r.stdout, r.stderr);
assert_eq!(sb.recorded_profiles(), Some(svec(&["default", "full"])));
assert!(sb.installed().contains(&"skill:deploy".to_string()));
let r = sb.mind(&["meld", &sb.spec(), "--no-default-profile", "--yes"]);
assert!(r.success, "{} {}", r.stdout, r.stderr);
assert_eq!(
sb.recorded_profiles(),
Some(svec(&["default", "full"])),
"a re-meld never disables a profile"
);
let r = sb.mind(&["meld", &sb.spec(), "--yes"]);
assert!(r.success, "{} {}", r.stdout, r.stderr);
assert!(!sb.installed().contains(&"skill:test".to_string()));
}
#[test]
fn learn_source_profile_uses_learns_gate_and_records() {
let sb = Sandbox::new("src");
assert!(sb.mind(&["meld", &sb.spec(), "--register-only"]).success);
let r = sb.mind(&["learn", "src", "--profile", "full", "--dry-run"]);
assert!(r.success, "{} {}", r.stdout, r.stderr);
assert!(r.stdout.contains("would learn 5 item(s)"), "{}", r.stdout);
assert!(sb.installed().is_empty());
assert_eq!(sb.recorded_profiles(), None);
let r = sb.mind(&["learn", "src", "--profile", "full"]);
assert!(r.success, "{} {}", r.stdout, r.stderr);
assert!(
r.stdout.contains("cancelled; nothing installed"),
"{}",
r.stdout
);
assert!(sb.installed().is_empty());
let r = sb.mind(&["learn", "src", "--profile", "full", "--yes"]);
assert!(r.success, "{} {}", r.stdout, r.stderr);
assert_eq!(sb.recorded_profiles(), Some(svec(&["full"])));
assert_eq!(sb.installed().len(), 5, "{:?}", sb.installed());
let r = sb.mind(&["learn", "src", "--all-profiles", "--yes"]);
assert!(r.success, "{} {}", r.stdout, r.stderr);
assert_eq!(
sb.recorded_profiles(),
Some(svec(&["basic", "default", "full"]))
);
assert!(!sb.installed().contains(&"skill:test".to_string()));
}
#[test]
fn learn_source_profile_without_deps_installs_directly() {
let sb = Sandbox::new("src");
assert!(sb.mind(&["meld", &sb.spec(), "--register-only"]).success);
let r = sb.mind(&["learn", &sb.identity(), "--profile", "basic"]);
assert!(r.success, "{} {}", r.stdout, r.stderr);
assert_eq!(sb.installed(), svec(&["rule:style", "skill:review"]));
assert_eq!(sb.recorded_profiles(), Some(svec(&["basic"])));
let r = sb.mind(&["learn", "src", "--profile", "nope"]);
assert!(!r.success);
assert!(
r.stderr.contains("declares no profile 'nope'"),
"{}",
r.stderr
);
}
#[test]
fn json_meld_reports_profiles_and_union_pending() {
let sb = Sandbox::new("src");
let r = sb.mind(&["--json", "meld", &sb.spec(), "--profile", "full"]);
assert!(r.success, "{} {}", r.stdout, r.stderr);
let v = json_doc(&r);
assert_eq!(v["pending_items"], 5, "{v}");
assert_eq!(strings(&v["profiles"]), Some(svec(&["default", "full"])));
assert!(sb.installed().is_empty());
assert_eq!(sb.recorded_profiles(), None);
let sb = Sandbox::new("src");
let r = sb.mind(&["--json", "meld", &sb.spec(), "--profile", "full", "--yes"]);
assert!(r.success, "{} {}", r.stdout, r.stderr);
let v = json_doc(&r);
assert_eq!(v["installed"].as_array().map(Vec::len), Some(5), "{v}");
assert_eq!(strings(&v["profiles"]), Some(svec(&["default", "full"])));
assert_eq!(sb.recorded_profiles(), Some(svec(&["default", "full"])));
}
#[test]
fn json_learn_reports_profiles() {
let sb = Sandbox::new("src");
assert!(sb.mind(&["meld", &sb.spec(), "--register-only"]).success);
let r = sb.mind(&["--json", "learn", "src", "--profile", "basic"]);
assert!(r.success, "{} {}", r.stdout, r.stderr);
let v = json_doc(&r);
assert_eq!(v["action"], "learn");
assert_eq!(v["outcome"], "installed");
assert_eq!(strings(&v["profiles"]), Some(svec(&["basic"])));
assert_eq!(v["installed"].as_array().map(Vec::len), Some(2), "{v}");
}
#[test]
fn profile_flags_on_an_item_link_are_a_usage_error() {
let sb = Sandbox::new("src");
let link = format!("file://{}/tree/main/skills/review", sb.spec());
let r = sb.mind(&["meld", &link, "--profile", "basic"]);
assert!(!r.success);
assert!(
r.stderr
.contains("--profile cannot be used with an item link"),
"{}",
r.stderr
);
let r = sb.mind(&["learn", &link, "--all-profiles"]);
assert!(!r.success);
assert!(
r.stderr
.contains("--all-profiles cannot be used with an item link"),
"{}",
r.stderr
);
let r = sb.mind(&["meld", &link, "--no-default-profile"]);
assert!(!r.success);
assert!(sb.installed().is_empty());
}
#[test]
fn by_name_learn_marks_a_profile_item_explicit() {
let sb = Sandbox::new("src");
assert!(sb.mind(&["meld", &sb.spec(), "--yes"]).success);
assert!(
sb.manifest_item("skill:review")
.unwrap()
.get("explicit")
.is_none()
);
let r = sb.mind(&["learn", "review"]);
assert!(r.success, "{} {}", r.stdout, r.stderr);
let item = sb.manifest_item("skill:review").unwrap();
assert_eq!(item["explicit"], true, "{item}");
assert_eq!(sb.reasons("skill:review"), svec(&["default"]));
}
#[test]
fn enabling_a_profile_over_a_hand_installed_item_keeps_it_explicit() {
let sb = Sandbox::new("src");
assert!(sb.mind(&["meld", &sb.spec(), "--register-only"]).success);
assert!(sb.mind(&["learn", "review"]).success);
let r = sb.mind(&["learn", "src", "--profile", "basic", "--yes"]);
assert!(r.success, "{} {}", r.stdout, r.stderr);
let item = sb.manifest_item("skill:review").unwrap();
assert_eq!(item["explicit"], true, "{item}");
assert_eq!(sb.reasons("skill:review"), svec(&["basic"]));
assert!(item.get("explicit").is_some());
let style = sb.manifest_item("rule:style").unwrap();
assert!(style.get("explicit").is_none(), "{style}");
}
fn curator(nested: &Sandbox, entry: &str) -> Sandbox {
let toml = format!(
"[discover]\nsources = [{{ source = {:?}{entry} }}]\n",
nested.spec()
);
let n = COUNTER.fetch_add(1, Ordering::SeqCst);
let base = std::env::temp_dir().join(format!("mind-profiles-cur-{}-{n}", std::process::id()));
let _ = std::fs::remove_dir_all(&base);
let source = base.join("registry");
write_file(&source.join("README.md"), "# registry\n");
write_file(&source.join("mind.toml"), &toml);
git_init(&source);
Sandbox {
base: base.clone(),
source,
mind_home: base.join("mind"),
claude_home: base.join("claude"),
}
}
impl Sandbox {
fn nested_profiles(&self, nested: &Sandbox) -> Option<Vec<String>> {
strings(&self.source_entry(&nested.identity())?["profiles"])
}
}
#[test]
fn curator_entry_profiles_apply_like_the_flags() {
let nested = Sandbox::new("nested");
let reg = curator(&nested, ", profiles = [\"full\"]");
let r = reg.mind(&["meld", ®.spec(), "--yes"]);
assert!(r.success, "{} {}", r.stdout, r.stderr);
assert_eq!(
reg.nested_profiles(&nested),
Some(svec(&["default", "full"]))
);
assert!(reg.installed().contains(&"skill:deploy".to_string()));
}
#[test]
fn curator_default_profile_false_acts_as_no_default_profile() {
let nested = Sandbox::new("nested");
let reg = curator(&nested, ", profiles = [\"full\"], default-profile = false");
let r = reg.mind(&["meld", ®.spec(), "--yes"]);
assert!(r.success, "{} {}", r.stdout, r.stderr);
assert_eq!(reg.nested_profiles(&nested), Some(svec(&["full"])));
}
#[test]
fn curator_profiles_with_install_items_install_the_union() {
let nested = Sandbox::new("nested");
let reg = curator(
&nested,
", profiles = [\"basic\"], install-items = [\"skill:test\"]",
);
let r = reg.mind(&["meld", ®.spec(), "--yes"]);
assert!(r.success, "{} {}", r.stdout, r.stderr);
assert_eq!(
reg.installed(),
svec(&["rule:style", "skill:review", "skill:test"])
);
}
#[test]
fn curator_install_true_offers_the_nested_default() {
let nested = Sandbox::new("nested");
let reg = curator(&nested, ", install = true");
let r = reg.mind(&["meld", ®.spec(), "--yes"]);
assert!(r.success, "{} {}", r.stdout, r.stderr);
assert_eq!(reg.installed(), svec(&["rule:style", "skill:review"]));
assert_eq!(reg.nested_profiles(&nested), Some(svec(&["default"])));
let plain = Sandbox::with_toml("plain", "");
let reg = curator(&plain, ", install = true");
let r = reg.mind(&["meld", ®.spec(), "--yes"]);
assert!(r.success, "{} {}", r.stdout, r.stderr);
assert_eq!(reg.installed().len(), 6, "{:?}", reg.installed());
}
#[test]
fn curator_undeclared_profile_is_bad_reference_at_meld() {
let nested = Sandbox::new("nested");
let reg = curator(&nested, ", profiles = [\"nope\"]");
let r = reg.mind(&["meld", ®.spec(), "--yes"]);
assert!(!r.success, "{}", r.stdout);
assert!(r.stderr.contains("profile 'nope'"), "{}", r.stderr);
assert!(reg.installed().is_empty());
}
fn add_skill(sb: &Sandbox, name: &str, body: &str) {
write_file(
&sb.source.join(format!("skills/{name}/SKILL.md")),
&format!("---\nname: {name}\ndescription: {name} skill\n---\n{body}\n"),
);
for args in [vec!["add", "-A"], vec!["commit", "-qm", "more"]] {
let status = Command::new("git")
.args(&args)
.current_dir(&sb.source)
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()
.expect("run git");
assert!(status.success(), "git {args:?}");
}
}
#[test]
fn closure_item_shared_by_two_members_carries_the_union_of_reasons() {
let sb = Sandbox::with_toml(
"src",
"[profiles]\np1 = [\"skill:deploy\"]\np2 = [\"skill:deploy2\"]\n",
);
add_skill(&sb, "deploy2", "# deploy2\nUses {{ns:helper}}.");
let r = sb.mind(&["meld", &sb.spec(), "--profile", "p1,p2", "--yes"]);
assert!(r.success, "{} {}", r.stdout, r.stderr);
assert_eq!(
sb.installed(),
svec(&["skill:deploy", "skill:deploy2", "skill:helper"])
);
assert_eq!(sb.reasons("skill:helper"), svec(&["p1", "p2"]));
assert_eq!(sb.reasons("skill:deploy"), svec(&["p1"]));
assert_eq!(sb.reasons("skill:deploy2"), svec(&["p2"]));
assert_eq!(
sb.recorded_snapshot(),
Some(svec(&["skill:deploy", "skill:deploy2"]))
);
}
#[test]
fn closure_pending_items_count_each_item_once_in_json() {
let sb = Sandbox::with_toml(
"src",
"[profiles]\np1 = [\"skill:deploy\"]\np2 = [\"skill:deploy2\"]\n",
);
add_skill(&sb, "deploy2", "# deploy2\nUses {{ns:helper}}.");
let r = sb.mind(&["--json", "meld", &sb.spec(), "--profile", "p1,p2"]);
assert!(r.success, "{} {}", r.stdout, r.stderr);
assert_eq!(json_doc(&r)["pending_items"], 3, "{}", r.stdout);
assert!(sb.installed().is_empty());
}
#[test]
fn policy_skipped_member_stays_out_of_the_snapshot() {
let sb = Sandbox::new("src");
assert!(sb.mind(&["meld", &sb.spec(), "--register-only"]).success);
let policy_path = sb.base.join("policy.toml");
write_file(
&policy_path,
"[sources]\nlock = true\nallow = [\"example.invalid/*\"]\n",
);
let policy = policy_path.to_string_lossy().into_owned();
let r = sb.mind_env(
&["learn", "src", "--profile", "basic", "--yes"],
&[("MIND_POLICY_FILE", policy.as_str())],
None,
);
assert!(r.success, "{} {}", r.stdout, r.stderr);
assert!(
r.stdout.contains("not permitted by the managed policy"),
"{} {}",
r.stdout,
r.stderr
);
assert!(sb.installed().is_empty(), "{} {}", r.stdout, r.stderr);
let snap = sb.recorded_snapshot().unwrap_or_default();
assert!(snap.is_empty(), "skipped members snapshotted: {snap:?}");
}
#[test]
fn partial_install_failure_still_records_what_installed() {
let sb = Sandbox::with_toml(
"src",
"[profiles]\ndefault = [\"skill:review\", \"skill:zbroken\"]\n",
);
add_skill(&sb, "zbroken", "# zbroken\nUses {{ns:nonexistent}}.");
let r = sb.mind(&["meld", &sb.spec(), "--yes"]);
assert!(!r.success, "{} {}", r.stdout, r.stderr);
assert!(!sb.installed().contains(&"skill:zbroken".to_string()));
assert_eq!(sb.installed(), svec(&["skill:review"]), "{}", r.stderr);
assert_eq!(sb.reasons("skill:review"), svec(&["default"]));
assert_eq!(sb.recorded_snapshot(), Some(svec(&["skill:review"])));
assert_eq!(sb.recorded_profiles(), Some(svec(&["default"])));
assert!(sb.source_entry(&sb.identity()).is_some());
}
#[test]
fn remeld_json_with_profile_flags_installs_and_reports() {
let sb = Sandbox::new("src");
assert!(sb.mind(&["meld", &sb.spec(), "--yes"]).success);
let r = sb.mind(&["--json", "meld", &sb.spec(), "--profile", "full"]);
assert!(r.success, "{} {}", r.stdout, r.stderr);
let v = json_doc(&r);
assert_eq!(v["outcome"], "already-melded", "{v}");
assert_eq!(strings(&v["profiles"]), Some(svec(&["default", "full"])));
assert_eq!(v["pending_items"], 3, "{v}");
assert_eq!(sb.recorded_profiles(), Some(svec(&["default"])));
let r = sb.mind(&["--json", "meld", &sb.spec(), "--profile", "full", "--yes"]);
assert!(r.success, "{} {}", r.stdout, r.stderr);
let v = json_doc(&r);
assert_eq!(strings(&v["profiles"]), Some(svec(&["default", "full"])));
assert_eq!(v["installed"].as_array().map(Vec::len), Some(3), "{v}");
assert_eq!(sb.recorded_profiles(), Some(svec(&["default", "full"])));
assert!(sb.installed().contains(&"skill:helper".to_string()));
}
#[test]
fn curator_json_install_profiles_apply_like_the_flags() {
let nested = Sandbox::new("nested");
let reg = curator(&nested, ", profiles = [\"full\"], default-profile = false");
let r = reg.mind(&["--json", "meld", ®.spec(), "--yes"]);
assert!(r.success, "{} {}", r.stdout, r.stderr);
assert_eq!(reg.nested_profiles(&nested), Some(svec(&["full"])));
assert!(reg.installed().contains(&"skill:deploy".to_string()));
assert!(reg.installed().contains(&"agent:planner".to_string()));
let nested = Sandbox::new("nested");
let reg = curator(&nested, ", install = true");
let r = reg.mind(&["--json", "meld", ®.spec(), "--yes"]);
assert!(r.success, "{} {}", r.stdout, r.stderr);
assert_eq!(reg.installed(), svec(&["rule:style", "skill:review"]));
assert_eq!(reg.nested_profiles(&nested), Some(svec(&["default"])));
let nested = Sandbox::new("nested");
let reg = curator(&nested, ", profiles = [\"nope\"]");
let r = reg.mind(&["--json", "meld", ®.spec(), "--yes"]);
assert!(!r.success, "{}", r.stdout);
assert!(reg.installed().is_empty());
}
#[test]
fn enabled_profile_matching_nothing_is_recorded_and_installs_nothing() {
let sb = Sandbox::with_toml("src", "[profiles]\ndefault = [\"command:*\"]\n");
let r = sb.mind(&["meld", &sb.spec(), "--yes"]);
assert!(r.success, "{} {}", r.stdout, r.stderr);
assert!(sb.installed().is_empty(), "{:?}", sb.installed());
assert_eq!(sb.recorded_profiles(), Some(svec(&["default"])));
assert_eq!(
sb.recorded_snapshot().unwrap_or_default(),
Vec::<String>::new()
);
let sb = Sandbox::with_toml("src", "[profiles]\ndefault = [\"command:*\"]\n");
let r = sb.mind(&["--json", "meld", &sb.spec(), "--yes"]);
assert!(r.success, "{} {}", r.stdout, r.stderr);
assert_eq!(strings(&json_doc(&r)["profiles"]), Some(svec(&["default"])));
assert!(sb.installed().is_empty());
}
#[test]
fn learn_source_profile_with_unknown_ambiguous_or_hash_selector_fails_cleanly() {
let sb = Sandbox::new("src");
assert!(sb.mind(&["meld", &sb.spec(), "--register-only"]).success);
let r = sb.mind(&["learn", "nosuch", "--profile", "basic", "--yes"]);
assert!(!r.success, "{}", r.stdout);
assert!(r.stderr.contains("nosuch"), "{}", r.stderr);
let r = sb.mind(&["learn", "src#review", "--profile", "basic", "--yes"]);
assert!(!r.success, "{}", r.stdout);
assert!(sb.installed().is_empty());
assert_eq!(sb.recorded_profiles(), None);
let other = Sandbox::new("src");
assert!(sb.mind(&["meld", &other.spec(), "--register-only"]).success);
let r = sb.mind(&["learn", "src", "--profile", "basic", "--yes"]);
assert!(!r.success, "{}", r.stdout);
assert!(sb.installed().is_empty());
assert_eq!(sb.recorded_profiles(), None);
let r = sb.mind(&["learn", &sb.identity(), "--profile", "basic", "--yes"]);
assert!(r.success, "{} {}", r.stdout, r.stderr);
assert_eq!(sb.recorded_profiles(), Some(svec(&["basic"])));
}
#[test]
fn learn_json_with_closure_needs_confirmation_and_installs_nothing() {
let sb = Sandbox::new("src");
assert!(sb.mind(&["meld", &sb.spec(), "--register-only"]).success);
let r = sb.mind(&["--json", "learn", "src", "--profile", "full"]);
assert!(!r.success, "{} {}", r.stdout, r.stderr);
assert!(
r.stderr.contains("confirmation") || r.stdout.contains("confirmation"),
"{} {}",
r.stdout,
r.stderr
);
assert!(sb.installed().is_empty());
assert_eq!(sb.recorded_profiles(), None);
let r = sb.mind(&["--json", "learn", "src", "--profile", "full", "--yes"]);
assert!(r.success, "{} {}", r.stdout, r.stderr);
assert_eq!(json_doc(&r)["outcome"], "installed");
assert_eq!(sb.installed().len(), 5);
}
#[test]
fn no_default_profile_composes_with_register_only() {
let sb = Sandbox::new("src");
let r = sb.mind(&[
"meld",
&sb.spec(),
"--register-only",
"--no-default-profile",
]);
assert!(r.success, "{} {}", r.stdout, r.stderr);
assert!(sb.installed().is_empty());
assert!(sb.source_entry(&sb.identity()).is_some());
assert_eq!(sb.recorded_profiles(), None);
}
#[test]
fn help_text_mentions_the_profile_flags() {
let sb = Sandbox::new("src");
let r = sb.mind(&["meld", "--help"]);
assert!(r.success);
for flag in ["--profile", "--no-default-profile", "--all-profiles"] {
assert!(r.stdout.contains(flag), "meld --help lacks {flag}");
}
let r = sb.mind(&["learn", "--help"]);
assert!(r.success);
for flag in ["--profile", "--all-profiles"] {
assert!(r.stdout.contains(flag), "learn --help lacks {flag}");
}
}
#[test]
fn learn_all_conflicts_with_profile_flags() {
let sb = Sandbox::new("src");
assert!(sb.mind(&["meld", &sb.spec(), "--register-only"]).success);
for flag in ["--profile=basic", "--all-profiles"] {
let r = sb.mind(&["learn", "src", "--all", flag, "--yes"]);
assert!(!r.success, "{flag}");
assert!(r.stderr.contains("cannot be used with"), "{}", r.stderr);
}
assert!(sb.installed().is_empty());
}