use std::fs::File;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use std::sync::atomic::{AtomicU32, Ordering};
use std::time::{Duration, Instant};
static COUNTER: AtomicU32 = AtomicU32::new(0);
static BOUNDED_COUNTER: AtomicU32 = AtomicU32::new(0);
const REVIEW_JS: &str = r#"export const meta = {
name: 'review-changes',
description: 'Review changed files',
whenToUse: 'before opening a PR',
}
phase('Review')
"#;
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-wf-{}-{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("workflows/review-changes.js"), REVIEW_JS);
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 {
self.mind_env(args, None)
}
fn mind_under_home(&self, args: &[&str]) -> Run {
self.mind_env(args, Some(&self.base))
}
fn mind_env(&self, args: &[&str], home: Option<&Path>) -> Run {
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_AGENT_HOMES")
.env_remove("MIND_MAX_METADATA_SIZE")
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.stdin(Stdio::null());
if let Some(home) = home {
cmd.env("HOME", home);
}
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_bounded(&self, args: &[&str]) -> Run {
let n = BOUNDED_COUNTER.fetch_add(1, Ordering::SeqCst);
let out_path = self.base.join(format!("bounded-{n}.out"));
let err_path = self.base.join(format!("bounded-{n}.err"));
let mut child = 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")
.env_remove("MIND_MAX_METADATA_SIZE")
.stdout(Stdio::from(File::create(&out_path).unwrap()))
.stderr(Stdio::from(File::create(&err_path).unwrap()))
.stdin(Stdio::null())
.spawn()
.expect("spawn mind");
let deadline = Instant::now() + Duration::from_secs(60);
let status = loop {
match child.try_wait().expect("wait for mind") {
Some(status) => break status,
None if Instant::now() >= deadline => {
let _ = child.kill();
let _ = child.wait();
panic!("`mind {}` did not finish within 60s", args.join(" "));
}
None => std::thread::sleep(Duration::from_millis(25)),
}
};
Run {
stdout: String::from_utf8_lossy(&std::fs::read(&out_path).unwrap()).into_owned(),
stderr: String::from_utf8_lossy(&std::fs::read(&err_path).unwrap()).into_owned(),
success: 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 link(&self, name: &str) -> PathBuf {
self.claude_home.join("workflows").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 workflow_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", "workflow"]);
assert!(r.success, "probe: {}\n{}", r.stdout, r.stderr);
assert!(
r.stdout.contains("workflow:review-changes"),
"--kind workflow must select the workflow: {}",
r.stdout
);
assert!(
r.stdout.contains("Review changed files"),
"the description must come from the `meta` object: {}",
r.stdout
);
assert!(
!r.stdout.contains("skill:review"),
"--kind workflow must exclude other kinds: {}",
r.stdout
);
let r = sb.mind(&["learn", "workflow:review-changes"]);
assert!(r.success, "learn: {}\n{}", r.stdout, r.stderr);
let link = sb.link("review-changes.js");
assert!(
link.symlink_metadata().is_ok(),
"the workflow must be linked at workflows/review-changes.js"
);
let target = std::fs::read_link(&link).expect("the lobe entry is a symlink");
assert!(
target.ends_with("store/workflow/review-changes"),
"the link must point at store/workflow/review-changes, got {target:?}"
);
assert_eq!(
std::fs::read_to_string(&link).unwrap(),
REVIEW_JS,
"the installed workflow is the source file"
);
let r = sb.mind(&["recall", "--kind", "workflow"]);
assert!(r.success, "recall: {}\n{}", r.stdout, r.stderr);
assert!(
r.stdout.contains("workflow:review-changes"),
"recall must report the installed workflow: {}",
r.stdout
);
}
#[test]
fn probe_shows_when_to_use_beside_the_description() {
let sb = Sandbox::new();
assert!(sb.mind(&["meld", &sb.source_spec()]).success);
let r = sb.mind(&["probe", "--no-tui"]);
assert!(r.success, "probe: {}\n{}", r.stdout, r.stderr);
assert!(
r.stdout
.contains("Review changed files - before opening a PR"),
"the workflow row must read `<description> - <whenToUse>`: {}",
r.stdout
);
assert!(
r.stdout.contains("Review the diff") && !r.stdout.contains("Review the diff -"),
"a skill's description must be untouched: {}",
r.stdout
);
let r = sb.mind(&["probe", "--no-tui", "opening a PR"]);
assert!(r.success, "probe: {}\n{}", r.stdout, r.stderr);
assert!(
r.stdout.contains("workflow:review-changes"),
"a whenToUse phrase must match the workflow: {}",
r.stdout
);
}
#[test]
fn the_scan_is_flat_and_js_only() {
let sb = Sandbox::new();
sb.write_and_commit("workflows/nested/deep.js", REVIEW_JS);
sb.write_and_commit("workflows/modern.mjs", REVIEW_JS);
sb.write_and_commit("workflows/typed.ts", REVIEW_JS);
sb.write_and_commit("workflows/notes.md", "---\ndescription: notes\n---\n");
sb.write_and_commit("workflows/LOUD.JS", REVIEW_JS);
assert!(sb.mind(&["meld", &sb.source_spec()]).success);
let r = sb.mind(&["probe", "--no-tui"]);
assert!(r.success, "probe: {}\n{}", r.stdout, r.stderr);
assert!(
r.stdout.contains("workflow:review-changes"),
"the flat `.js` workflow is still found: {}",
r.stdout
);
for missed in ["deep", "modern", "typed", "notes", "LOUD"] {
assert!(
!r.stdout.contains(missed),
"'{missed}' must not be discovered as an item: {}",
r.stdout
);
}
}
#[test]
fn a_prefixed_workflow_links_and_expands_under_its_namespaced_name() {
let sb = Sandbox::new();
sb.write_and_commit(
"workflows/review-changes.js",
"export const meta = {\n name: '{{ns:review-changes}}',\n description: 'Review changed files',\n}\n",
);
let r = sb.mind(&["meld", &sb.source_spec(), "--namespace", "jk"]);
assert!(r.success, "meld: {}", r.stderr);
let r = sb.mind(&["learn", "workflow:jk:review-changes"]);
assert!(r.success, "learn: {}\n{}", r.stdout, r.stderr);
let link = sb.link("jk:review-changes.js");
assert!(
link.symlink_metadata().is_ok(),
"a prefixed workflow links at workflows/jk:review-changes.js"
);
assert!(
sb.link("review-changes.js").symlink_metadata().is_err(),
"the bare name must not also be linked"
);
assert!(
sb.mind_home
.join("store/workflow/jk:review-changes")
.exists(),
"the store copy uses the effective name"
);
let installed = std::fs::read_to_string(&link).unwrap();
assert!(
installed.contains("name: 'jk:review-changes'"),
"the `{{{{ns:}}}}` token in meta.name must expand to the effective name: {installed}"
);
assert!(
!installed.contains("{{ns:"),
"no token may survive into the installed file: {installed}"
);
}
#[test]
fn an_unprefixed_workflow_expands_its_token_to_the_bare_name() {
let sb = Sandbox::new();
sb.write_and_commit(
"workflows/review-changes.js",
"export const meta = {\n name: '{{ns:review-changes}}',\n description: 'Review changed files',\n}\n",
);
assert!(sb.mind(&["meld", &sb.source_spec()]).success);
let r = sb.mind(&["learn", "workflow:review-changes"]);
assert!(r.success, "learn: {}\n{}", r.stdout, r.stderr);
let installed = std::fs::read_to_string(sb.link("review-changes.js")).unwrap();
assert!(
installed.contains("name: 'review-changes'"),
"an unprefixed token expands to the bare name: {installed}"
);
}
#[test]
fn workflow_upgrades_and_forgets_like_any_item() {
let sb = Sandbox::new();
assert!(sb.mind(&["meld", &sb.source_spec()]).success);
assert!(sb.mind(&["learn", "workflow:review-changes"]).success);
sb.write_and_commit(
"workflows/review-changes.js",
"export const meta = {\n name: 'review-changes',\n description: 'Review changed files v2',\n}\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 workflow:review-changes"),
"the workflow must upgrade: {}",
r.stdout
);
assert!(
std::fs::read_to_string(sb.link("review-changes.js"))
.unwrap()
.contains("Review changed files v2"),
"the linked file must show the new content"
);
let r = sb.mind(&["forget", "workflow:review-changes", "--yes"]);
assert!(r.success, "forget: {}\n{}", r.stdout, r.stderr);
assert!(
sb.link("review-changes.js").symlink_metadata().is_err(),
"forget removes the lobe link"
);
assert!(
!sb.mind_home.join("store/workflow/review-changes").exists(),
"forget removes the store copy"
);
}
#[test]
fn a_hand_written_workflow_is_reported_as_unmanaged() {
let sb = Sandbox::new();
write(&sb.claude_home.join("workflows/mine.js"), REVIEW_JS);
write(&sb.claude_home.join("workflows/nested/deep.js"), REVIEW_JS);
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("workflow:mine"),
"a hand-written workflow must be listed as unmanaged: {}",
r.stdout
);
assert!(
!r.stdout.contains("deep"),
"a nested workflow must not be surfaced as unmanaged: {}",
r.stdout
);
}
#[test]
fn a_mindfile_declares_and_globs_workflows() {
let sb = Sandbox::new();
sb.write_and_commit("orchestration/audit.js", REVIEW_JS);
sb.write_and_commit("packages/one/workflows/ship.js", REVIEW_JS);
sb.write_and_commit(
"mind.toml",
"[[items]]\nkind = \"workflow\"\nname = \"audit\"\npath = \"orchestration/audit.js\"\n\n\
[discover]\nworkflows = { include = [\"packages/*/workflows/*.js\"] }\n",
);
assert!(sb.mind(&["meld", &sb.source_spec()]).success);
let r = sb.mind(&["probe", "--no-tui", "--kind", "workflow"]);
assert!(r.success, "probe: {}\n{}", r.stdout, r.stderr);
assert!(
r.stdout.contains("workflow:audit"),
"the declared workflow must be offered: {}",
r.stdout
);
assert!(
r.stdout.contains("workflow:ship"),
"the globbed workflow must be offered: {}",
r.stdout
);
assert!(
!r.stdout.contains("review-changes"),
"an authoritative mind.toml turns off the convention scan: {}",
r.stdout
);
let r = sb.mind(&["learn", "workflow:audit"]);
assert!(r.success, "learn: {}\n{}", r.stdout, r.stderr);
assert!(
sb.link("audit.js").symlink_metadata().is_ok(),
"a declared workflow still links at workflows/<name>.js"
);
}
#[test]
fn a_skills_only_lobe_admits_no_workflows() {
let sb = Sandbox::new();
let other = sb.base.join("gemini");
std::fs::create_dir_all(&other).unwrap();
write(
&sb.mind_home.join("config.toml"),
&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", "workflow:review-changes"]);
assert!(r.success, "learn: {}\n{}", r.stdout, r.stderr);
assert!(
sb.link("review-changes.js").symlink_metadata().is_ok(),
"the all-kinds lobe gets the workflow"
);
assert!(
!other.join("workflows/review-changes.js").exists(),
"a skills-only lobe must not receive a workflow link"
);
}
#[test]
fn a_path_token_in_a_workflow_renders_in_the_home_form() {
let sb = Sandbox::new();
sb.write_and_commit(
"workflows/review-changes.js",
"export const meta = {\n name: 'review-changes',\n \
description: 'Review changed files',\n}\n\
agent(`read {{path:skill:review}}/SKILL.md`)\n",
);
assert!(sb.mind_under_home(&["meld", &sb.source_spec()]).success);
let r = sb.mind_under_home(&["learn", "workflow:review-changes"]);
assert!(r.success, "learn: {}\n{}", r.stdout, r.stderr);
let installed = std::fs::read_to_string(sb.link("review-changes.js")).unwrap();
assert!(
installed.contains("~/mind/store/skill/review/SKILL.md"),
"the path token must render in the `~` home form, not an absolute path \
(the NS-57 expand-listed form): {installed}"
);
assert!(
!installed.contains("{{path:"),
"no token may survive into the installed file: {installed}"
);
}
#[test]
fn review_does_not_call_a_workflows_tokens_inert() {
let sb = Sandbox::new();
sb.write_and_commit(
"workflows/review-changes.js",
"export const meta = {\n name: '{{ns:review-changes}}',\n \
description: 'Review changed files',\n}\n\
agent(`read {{path:skill:review}}/SKILL.md`)\n",
);
let r = sb.mind(&["review", &sb.source_spec()]);
assert!(
r.success,
"review must succeed on a source whose tokens all resolve: {}\n{}",
r.stdout, r.stderr
);
assert!(
r.stdout.contains("[workflow-content]") && r.stdout.contains("workflow:review-changes"),
"review must have reached the workflow checks at all: {}\n{}",
r.stdout,
r.stderr
);
assert!(
!r.stdout.contains("inert-token") && !r.stderr.contains("inert-token"),
"a workflow's resolving tokens are not inert: {}\n{}",
r.stdout,
r.stderr
);
sb.write_and_commit(
"workflows/review-changes.js",
"export const meta = {\n name: 'review-changes',\n \
description: 'Review changed files',\n}\n\
agent(`hand off to {{ns:nonesuch}}`)\n",
);
let r = sb.mind(&["review", &sb.source_spec()]);
let all = format!("{}{}", r.stdout, r.stderr);
assert!(
!r.success,
"a hard bad-reference must fail the review: {}\n{}",
r.stdout, r.stderr
);
assert!(
r.stderr
.lines()
.any(|l| l.starts_with("error [bad-reference]:") && l.contains("nonesuch")),
"an unresolvable token in a workflow is a hard defect: {all}"
);
assert!(
!all.contains("dead text"),
"it must not be downgraded to the dead-text advisory: {all}"
);
}
#[test]
fn only_a_workflow_may_link_into_the_workflows_directory() {
let sb = Sandbox::new();
sb.write_and_commit(
"mind.toml",
"[[items]]\nkind = \"rule\"\nname = \"style\"\npath = \"rules/style.md\"\n\
link = \"workflows/deploy.js\"\n",
);
sb.write_and_commit("rules/style.md", "---\ndescription: style\n---\n# style\n");
let r = sb.mind(&["meld", &sb.source_spec(), "--json"]);
assert!(
!r.success,
"a rule linking into workflows/ must be refused: {}\n{}",
r.stdout, r.stderr
);
let doc: serde_json::Value = serde_json::from_str(r.stdout.trim())
.unwrap_or_else(|e| panic!("stdout must be one JSON document: {e}\n{}", r.stdout));
assert_eq!(
doc["error"]["kind"].as_str(),
Some("mind-toml"),
"the refusal must come from validating the declared link, not from some \
unrelated failure: {}",
r.stdout
);
let message = format!("{}{}", doc["error"]["message"], r.stderr);
assert!(
message.contains("workflows/deploy.js") && message.contains("link"),
"and must name the offending link target: {message}"
);
assert!(
!sb.link("deploy.js").symlink_metadata().is_ok(),
"nothing may be linked into workflows/ by a non-workflow item"
);
}
#[test]
fn review_reports_a_workflow_the_harness_will_not_load() {
let sb = Sandbox::new();
sb.write_and_commit("workflows/no-meta.js", "phase('Review')\nagent('go')\n");
sb.write_and_commit(
"workflows/blank-name.js",
"export const meta = {\n name: ' ',\n description: 'Review the diff',\n}\n",
);
sb.write_and_commit(
"workflows/no-description.js",
"export const meta = {\n name: 'no-description',\n}\n",
);
let sized = |name: &str, bytes: usize| -> String {
let header =
format!("export const meta = {{\n name: '{name}',\n description: 'Big',\n}}\n// ");
let mut s = String::with_capacity(bytes);
s.push_str(&header);
s.push_str(&"x".repeat(bytes - header.len()));
assert_eq!(s.len(), bytes, "the fixture size must be exact");
s
};
sb.write_and_commit("workflows/at-cap.js", &sized("at-cap", 524_288));
sb.write_and_commit("workflows/huge.js", &sized("huge", 524_289));
let r = sb.mind(&["review", &sb.source_spec()]);
assert!(
r.success,
"an unloadable workflow is advisory, never a failure: {}\n{}",
r.stdout, r.stderr
);
let findings: Vec<&str> = r
.stdout
.lines()
.filter(|l| l.contains("[workflow-unloadable]"))
.collect();
let joined = findings.join("\n");
assert!(
joined.contains("workflow:no-meta")
&& joined.contains("mind read no `name`, `description`, or `whenToUse`"),
"an unreadable meta must be reported, in terms of what mind read: {joined}"
);
assert!(
joined.contains("workflow:blank-name") && joined.contains("`meta.name` is empty"),
"an empty name must be reported: {joined}"
);
assert!(
joined.contains("workflow:no-description")
&& joined.contains("`meta.description` is missing"),
"a missing description must be reported: {joined}"
);
assert!(
joined.contains("workflow:huge") && joined.contains("524288-byte cap"),
"a file over the cap must be reported: {joined}"
);
assert!(
!joined.contains("workflow:at-cap"),
"a file exactly at the cap is not over it: {joined}"
);
assert!(
!joined.contains("workflow:review-changes"),
"a complete workflow must not be reported: {joined}"
);
assert!(sb.mind(&["meld", &sb.source_spec()]).success);
let r = sb.mind(&["learn", "workflow:huge"]);
assert!(r.success, "learn: {}\n{}", r.stdout, r.stderr);
assert!(
sb.link("huge.js").symlink_metadata().is_ok(),
"the over-cap workflow is installed anyway"
);
}
#[test]
fn learn_warns_about_an_unloadable_workflow_and_installs_it() {
let sb = Sandbox::new();
sb.write_and_commit("workflows/no-meta.js", "phase('Review')\nagent('go')\n");
assert!(sb.mind(&["meld", &sb.source_spec()]).success);
let r = sb.mind(&["learn", "workflow:no-meta"]);
assert!(
r.success,
"the install must succeed: {}\n{}",
r.stdout, r.stderr
);
assert!(
r.stderr.contains("the harness will not load this workflow")
&& r.stderr.contains("installed anyway"),
"learn must warn and say it installed anyway: {}",
r.stderr
);
assert!(
sb.link("no-meta.js").symlink_metadata().is_ok(),
"the workflow is installed despite the warning"
);
let r = sb.mind(&["learn", "workflow:review-changes"]);
assert!(r.success, "learn: {}\n{}", r.stdout, r.stderr);
assert!(
!r.stderr.contains("will not load"),
"a loadable workflow must be silent: {}",
r.stderr
);
}
#[test]
fn a_diverging_meta_name_is_reported_at_learn_review_and_recall() {
let sb = Sandbox::new();
sb.write_and_commit(
"workflows/review-changes.js",
"export const meta = {\n name: 'deploy',\n description: 'Review changed files',\n}\n",
);
let r = sb.mind(&["review", &sb.source_spec()]);
assert!(r.success, "review: {}\n{}", r.stdout, r.stderr);
assert!(
r.stdout.contains("[workflow-name]")
&& r.stdout.contains("resolves it as 'deploy'")
&& r.stdout.contains("not 'review-changes'"),
"review must report the divergence: {}",
r.stdout
);
assert!(sb.mind(&["meld", &sb.source_spec()]).success);
let r = sb.mind(&["learn", "workflow:review-changes"]);
assert!(r.success, "learn: {}\n{}", r.stdout, r.stderr);
assert!(
r.stderr.contains("resolves it as 'deploy'"),
"learn must warn about the divergence: {}",
r.stderr
);
let r = sb.mind(&["recall", "workflow:review-changes"]);
assert!(r.success, "recall: {}\n{}", r.stdout, r.stderr);
assert!(
r.stdout.contains("resolves it as 'deploy'"),
"the detail view must keep reporting it: {}",
r.stdout
);
let r = sb.mind(&["recall"]);
assert!(r.success, "recall: {}\n{}", r.stdout, r.stderr);
assert!(
!r.stdout.contains("resolves it as"),
"the listing must stay quiet: {}",
r.stdout
);
let r = sb.mind(&["introspect"]);
assert!(
!r.stdout.contains("resolves it as") && !r.stderr.contains("resolves it as"),
"introspect must stay quiet: {}\n{}",
r.stdout,
r.stderr
);
}
#[test]
fn a_tokenized_meta_name_draws_no_divergence_warning() {
let sb = Sandbox::new();
sb.write_and_commit(
"workflows/review-changes.js",
"export const meta = {\n name: '{{ns:review-changes}}',\n description: 'Review changed files',\n}\n",
);
let r = sb.mind(&["review", &sb.source_spec(), "--namespace", "jk"]);
assert!(r.success, "review: {}\n{}", r.stdout, r.stderr);
assert!(
!r.stdout.contains("[workflow-name]"),
"a tokenized meta.name matches the prefixed effective name: {}",
r.stdout
);
assert!(
sb.mind(&["meld", &sb.source_spec(), "--namespace", "jk"])
.success
);
let r = sb.mind(&["learn", "workflow:jk:review-changes"]);
assert!(r.success, "learn: {}\n{}", r.stdout, r.stderr);
assert!(
!r.stderr.contains("resolves it as"),
"learn must stay quiet: {}",
r.stderr
);
}
#[test]
fn the_divergence_remedy_token_is_one_that_actually_installs() {
let sb = Sandbox::new();
sb.write_and_commit(
"workflows/review-changes.js",
"export const meta = {\n name: 'review-changes',\n description: 'Review changed files',\n}\n",
);
let r = sb.mind(&["review", &sb.source_spec(), "--namespace", "jk"]);
assert!(r.success, "review: {}\n{}", r.stdout, r.stderr);
let finding = r
.stdout
.lines()
.find(|l| l.contains("[workflow-name]"))
.unwrap_or_else(|| panic!("review must report the divergence: {}", r.stdout))
.to_string();
assert!(
finding.contains("resolves it as 'review-changes'")
&& finding.contains("not 'jk:review-changes'"),
"the divergence names both spellings: {finding}"
);
let start = finding
.find("{{ns:")
.unwrap_or_else(|| panic!("the finding must suggest a token: {finding}"));
let end = finding[start..]
.find("}}")
.map(|i| start + i + 2)
.unwrap_or_else(|| panic!("the suggested token must be terminated: {finding}"));
let token = &finding[start..end];
assert_eq!(
token, "{{ns:review-changes}}",
"the remedy must name the BARE name, the only spelling `{{{{ns:}}}}` resolves: {finding}"
);
sb.write_and_commit(
"workflows/review-changes.js",
&format!(
"export const meta = {{\n name: '{token}',\n description: 'Review changed files',\n}}\n"
),
);
let r = sb.mind(&["review", &sb.source_spec(), "--namespace", "jk"]);
assert!(
r.success,
"review after the fix: {}\n{}",
r.stdout, r.stderr
);
assert!(
!r.stdout.contains("[workflow-name]"),
"following the advice must clear the divergence: {}",
r.stdout
);
assert!(
!r.stdout.contains("bad-reference") && !r.stderr.contains("bad-reference"),
"the suggested token must resolve: {}\n{}",
r.stdout,
r.stderr
);
assert!(
sb.mind(&["meld", &sb.source_spec(), "--namespace", "jk"])
.success
);
let r = sb.mind(&["learn", "workflow:jk:review-changes"]);
assert!(
r.success,
"the install must succeed with the suggested token: {}\n{}",
r.stdout, r.stderr
);
assert!(
!r.stderr.contains("resolves it as"),
"and draw no divergence warning: {}",
r.stderr
);
let installed = std::fs::read_to_string(sb.link("jk:review-changes.js")).unwrap();
assert!(
installed.contains("name: 'jk:review-changes'"),
"the token expanded to the effective name: {installed}"
);
}
#[test]
fn an_invisible_character_in_a_meta_name_is_reported_not_accepted() {
let sb = Sandbox::new();
sb.write_and_commit(
"workflows/deploy.js",
"export const meta = {\n name: 'deploy',\n description: 'Deploy it',\n}\n",
);
sb.write_and_commit(
"workflows/staging.js",
"export const meta = {\n name: 'dep\u{200B}loy',\n description: 'Deploy to staging',\n}\n",
);
let r = sb.mind(&["review", &sb.source_spec()]);
assert!(r.success, "review: {}\n{}", r.stdout, r.stderr);
let unloadable: Vec<&str> = r
.stdout
.lines()
.filter(|l| l.contains("[workflow-unloadable]"))
.collect();
let joined = unloadable.join("\n");
assert!(
joined.contains("workflow:staging") && joined.contains("control or invisible character"),
"an invisible character in `meta.name` must be reported: {}",
r.stdout
);
assert!(
!joined.contains("workflow:deploy:"),
"the ordinary sibling draws nothing: {}",
r.stdout
);
assert!(
!r.stdout.contains("[workflow-name-collision]"),
"an unusable name claims nothing, so it collides with nothing: {}",
r.stdout
);
assert!(
!r.stdout.contains("resolves it as"),
"and draws no divergence, which would compare a name with itself: {}",
r.stdout
);
assert!(sb.mind(&["meld", &sb.source_spec()]).success);
let r = sb.mind(&["learn", "workflow:staging"]);
assert!(r.success, "learn: {}\n{}", r.stdout, r.stderr);
assert!(
r.stderr.contains("control or invisible character"),
"learn warns on the same terms: {}",
r.stderr
);
assert!(
sb.link("staging.js").symlink_metadata().is_ok(),
"the workflow installs anyway"
);
}
#[test]
fn a_meta_name_padded_by_a_multiline_template_is_usable_not_a_defect() {
let sb = Sandbox::new();
sb.write_and_commit(
"workflows/release.js",
"export const meta = {\n name: `\n release\n `,\n \
description: 'Cut a release',\n}\n",
);
let r = sb.mind(&["review", &sb.source_spec()]);
assert!(r.success, "review: {}\n{}", r.stdout, r.stderr);
assert!(
!r.stdout.contains("control or invisible character"),
"padding is not a character defect: {}",
r.stdout
);
assert!(
!r.stdout.contains("[workflow-unloadable]"),
"a padded name is a name: {}",
r.stdout
);
assert!(
!r.stdout.contains("resolves it as"),
"the trimmed name is the item's own name, so nothing diverges: {}",
r.stdout
);
assert!(sb.mind(&["meld", &sb.source_spec()]).success);
let r = sb.mind(&["learn", "workflow:release"]);
assert!(r.success, "learn: {}\n{}", r.stdout, r.stderr);
assert!(
!r.stderr.contains("control or invisible character")
&& !r.stderr.contains("resolves it as"),
"learn warns about nothing either: {}",
r.stderr
);
let r = sb.mind(&["recall", "workflow:release", "--json"]);
assert!(r.success, "recall --json: {}\n{}", r.stdout, r.stderr);
let doc: serde_json::Value = serde_json::from_str(r.stdout.trim()).expect("one JSON document");
assert_eq!(
doc["harness_name"].as_str(),
Some("release"),
"the harness name is the trimmed one: {}",
r.stdout
);
assert_eq!(
doc["workflow_findings"].as_array().map(Vec::len),
Some(0),
"and there is nothing to report: {}",
r.stdout
);
}
#[test]
fn a_collision_warning_is_subjected_to_the_item_the_run_touched() {
let sb = Sandbox::new();
for name in ["alpha", "zeta"] {
sb.write_and_commit(
&format!("workflows/{name}.js"),
&format!(
"export const meta = {{\n name: 'deploy',\n \
description: 'Deploy from {name}',\n}}\n"
),
);
}
assert!(sb.mind(&["meld", &sb.source_spec()]).success);
let first = sb.mind(&["learn", "workflow:alpha"]);
assert!(
first.success,
"learn alpha: {}\n{}",
first.stdout, first.stderr
);
assert!(
!first.stderr.contains("harness name 'deploy'"),
"one claimant is no collision: {}",
first.stderr
);
let second = sb.mind(&["learn", "workflow:zeta"]);
assert!(
second.success,
"learn zeta: {}\n{}",
second.stdout, second.stderr
);
let lines: Vec<&str> = second
.stderr
.lines()
.filter(|l| l.contains("harness name 'deploy'"))
.collect();
assert_eq!(lines.len(), 1, "one name, one warning: {lines:?}");
assert!(
lines[0].contains("warning: workflow:zeta:"),
"the subject is the item this run installed, not the alphabetically first \
claimant: {lines:?}"
);
assert!(
lines[0].contains("workflow:alpha also claims"),
"and the untouched claimant is named as the other: {lines:?}"
);
assert!(
!lines[0].contains("workflow:zeta also"),
"no claimant is ever reported against itself: {lines:?}"
);
let sb = Sandbox::new();
for name in ["alpha", "zeta"] {
sb.write_and_commit(
&format!("workflows/{name}.js"),
&format!(
"export const meta = {{\n name: 'deploy',\n \
description: 'Deploy from {name}',\n}}\n"
),
);
}
assert!(sb.mind(&["meld", &sb.source_spec()]).success);
let both = sb.mind(&["learn", "--all", "agents"]);
assert!(both.success, "learn both: {}\n{}", both.stdout, both.stderr);
let lines: Vec<&str> = both
.stderr
.lines()
.filter(|l| l.contains("harness name 'deploy'"))
.collect();
assert_eq!(
lines.len(),
1,
"one name is one warning however many of its claimants a run touched: {lines:?}"
);
assert!(
lines[0].contains("warning: workflow:alpha:") && lines[0].contains("workflow:zeta also"),
"the first claimant is the subject when the run touched both: {lines:?}"
);
assert!(
!lines[0].contains("workflow:alpha also"),
"and it is not listed among the others: {lines:?}"
);
}
#[test]
fn recall_json_carries_the_harness_name_and_its_findings() {
let sb = Sandbox::new();
sb.write_and_commit(
"workflows/deploy-staging.js",
"export const meta = {\n name: 'deploy',\n description: 'Deploy to staging',\n}\n",
);
sb.write_and_commit(
"workflows/deploy-prod.js",
"export const meta = {\n name: 'deploy',\n description: 'Deploy to prod',\n}\n",
);
assert!(sb.mind(&["meld", &sb.source_spec()]).success);
assert!(sb.mind(&["learn", "workflow:deploy-staging"]).success);
assert!(sb.mind(&["learn", "workflow:deploy-prod"]).success);
let r = sb.mind(&["recall", "workflow:deploy-staging", "--json"]);
assert!(r.success, "recall --json: {}\n{}", r.stdout, r.stderr);
let doc: serde_json::Value = serde_json::from_str(r.stdout.trim())
.unwrap_or_else(|e| panic!("stdout must be one JSON document: {e}\n{}", r.stdout));
assert_eq!(
doc["harness_name"].as_str(),
Some("deploy"),
"the document must carry the name the harness answers to: {}",
r.stdout
);
let findings: Vec<&str> = doc["workflow_findings"]
.as_array()
.unwrap_or_else(|| panic!("workflow_findings must be an array: {}", r.stdout))
.iter()
.map(|f| f.as_str().expect("each finding is a string"))
.collect();
assert!(
findings
.iter()
.any(|f| f.contains("resolves it as 'deploy'") && f.contains("not 'deploy-staging'")),
"the WF-24 divergence must ride the document: {findings:?}"
);
assert!(
findings
.iter()
.any(|f| f.contains("harness name 'deploy'") && f.contains("workflow:deploy-prod")),
"so must the WF-29 collision: {findings:?}"
);
let text = sb.mind(&["recall", "workflow:deploy-staging"]);
assert!(text.success, "recall: {}\n{}", text.stdout, text.stderr);
for finding in &findings {
assert!(
text.stdout.contains(finding),
"every JSON finding must be one the text view prints: {finding}\n{}",
text.stdout
);
}
assert!(sb.mind(&["learn", "skill:review"]).success);
let r = sb.mind(&["recall", "skill:review", "--json"]);
assert!(r.success, "recall skill --json: {}\n{}", r.stdout, r.stderr);
let doc: serde_json::Value = serde_json::from_str(r.stdout.trim()).expect("one JSON document");
assert!(
doc.get("harness_name").is_none() && doc.get("workflow_findings").is_none(),
"only a workflow carries the workflow fields: {}",
r.stdout
);
}
#[test]
fn recall_of_a_workflow_whose_store_copy_is_unreadable_reports_no_harness_name() {
for (what, break_it) in [("deleted", 0u8), ("a directory", 1), ("not UTF-8", 2)] {
let sb = Sandbox::new();
assert!(sb.mind(&["meld", &sb.source_spec()]).success);
assert!(sb.mind(&["learn", "workflow:review-changes"]).success);
let store = sb.mind_home.join("store/workflow/review-changes");
assert!(store.is_file(), "the store copy is a file to begin with");
match break_it {
0 => std::fs::remove_file(&store).unwrap(),
1 => {
std::fs::remove_file(&store).unwrap();
std::fs::create_dir(&store).unwrap();
}
_ => std::fs::write(&store, [0xff, 0xfe, 0x00, 0x80]).unwrap(),
}
let r = sb.mind(&["recall", "workflow:review-changes", "--json"]);
assert!(
r.success,
"recall --json with a {what} store copy must still succeed: {}\n{}",
r.stdout, r.stderr
);
let doc: serde_json::Value = serde_json::from_str(r.stdout.trim())
.unwrap_or_else(|e| panic!("one JSON document ({what}): {e}\n{}", r.stdout));
assert!(
doc["harness_name"].is_null(),
"a {what} store copy yields no harness name: {}",
r.stdout
);
assert_eq!(
doc["workflow_findings"].as_array().map(Vec::len),
Some(0),
"and no findings, since mind knows nothing about the file ({what}): {}",
r.stdout
);
assert!(
!r.stdout.contains("max-metadata-size"),
"an unreadable file is not an over-cap one ({what}): {}",
r.stdout
);
let text = sb.mind(&["recall", "workflow:review-changes"]);
assert!(
text.success,
"the text view too ({what}): {}\n{}",
text.stdout, text.stderr
);
assert!(
!text.stdout.contains("harness "),
"nothing to say about the harness ({what}): {}",
text.stdout
);
}
}
#[test]
fn two_workflows_sharing_a_meta_name_are_reported() {
let sb = Sandbox::new();
sb.write_and_commit(
"workflows/deploy-staging.js",
"export const meta = {\n name: 'deploy',\n description: 'Deploy to staging',\n}\n",
);
sb.write_and_commit(
"workflows/deploy-prod.js",
"export const meta = {\n name: 'deploy',\n description: 'Deploy to prod',\n}\n",
);
let r = sb.mind(&["review", &sb.source_spec()]);
assert!(
r.success,
"a name collision is advisory, never a failure: {}\n{}",
r.stdout, r.stderr
);
let findings: Vec<&str> = r
.stdout
.lines()
.filter(|l| l.contains("[workflow-name-collision]"))
.collect();
assert_eq!(
findings.len(),
1,
"one finding for the one shared name: {:?}",
findings
);
assert!(
findings[0].contains("harness name 'deploy'"),
"the finding names the shared harness name: {findings:?}"
);
assert!(
findings[0].contains("workflow:deploy-prod")
&& findings[0].contains("workflow:deploy-staging"),
"and names both claimants: {findings:?}"
);
assert!(sb.mind(&["meld", &sb.source_spec()]).success);
let r = sb.mind(&["learn", "--all", "agents"]);
assert!(r.success, "learn: {}\n{}", r.stdout, r.stderr);
assert!(
r.stderr.contains("harness name 'deploy'"),
"learn must warn about the shared name: {}",
r.stderr
);
assert!(sb.link("deploy-staging.js").symlink_metadata().is_ok());
assert!(sb.link("deploy-prod.js").symlink_metadata().is_ok());
let r = sb.mind(&["recall", "workflow:deploy-prod"]);
assert!(r.success, "recall: {}\n{}", r.stdout, r.stderr);
assert!(
r.stdout.contains("workflow:deploy-staging"),
"the detail view names the other claimant: {}",
r.stdout
);
}
#[test]
fn a_workflow_meta_the_reader_cannot_model_does_not_stall_a_scan() {
let sb = Sandbox::new();
sb.write_and_commit(
"workflows/deploy.js",
"export const meta = {\n name: 'deploy',\n tagPattern: /release\\)/,\n \
description: 'Deploy a release',\n}\nphase('Deploy')\n",
);
let r = sb.mind_bounded(&["meld", &sb.source_spec()]);
assert!(r.success, "meld: {}\n{}", r.stdout, r.stderr);
let r = sb.mind_bounded(&["probe", "--no-tui", "--kind", "workflow"]);
assert!(r.success, "probe: {}\n{}", r.stdout, r.stderr);
assert!(
r.stdout.contains("Deploy a release"),
"the keys around the unreadable one still read: {}",
r.stdout
);
let r = sb.mind_bounded(&["learn", "workflow:deploy"]);
assert!(r.success, "learn: {}\n{}", r.stdout, r.stderr);
assert!(
sb.link("deploy.js").symlink_metadata().is_ok(),
"the workflow installs: the reader yields less, it never fails an install"
);
}
const PATHOLOGICAL_JS: &str = "export const meta = {\n name: 'deploy',\n \
tagPattern: /rel)ease/,\n arrow: () => x),\n [)]: 1,\n \
phases: [{ title: 'a' ) }],\n description: 'Deploy a release',\n \
whenToUse: 'when shipping',\n}\nphase('Deploy')\n";
#[test]
fn a_pathological_workflow_meta_stalls_no_verb_that_scans() {
let sb = Sandbox::new();
sb.write_and_commit("workflows/deploy.js", PATHOLOGICAL_JS);
let r = sb.mind_bounded(&["meld", &sb.source_spec()]);
assert!(r.success, "meld: {}\n{}", r.stdout, r.stderr);
let r = sb.mind_bounded(&["learn", "workflow:deploy"]);
assert!(r.success, "learn: {}\n{}", r.stdout, r.stderr);
for args in [
vec!["sync"],
vec!["introspect"],
vec!["dump"],
vec!["recall", "--sources"],
] {
let r = sb.mind_bounded(&args);
assert!(
r.success,
"`mind {}` failed: {}\n{}",
args.join(" "),
r.stdout,
r.stderr
);
}
let r = sb.mind_bounded(&["recall"]);
assert!(r.success, "recall: {}\n{}", r.stdout, r.stderr);
assert!(
r.stdout.contains("workflow:deploy"),
"the item is listed as installed: {}",
r.stdout
);
let r = sb.mind_bounded(&["recall", "workflow:deploy"]);
assert!(r.success, "recall detail: {}\n{}", r.stdout, r.stderr);
assert!(
r.stdout.contains("Deploy a release"),
"the keys around the unreadable ones still describe the item: {}",
r.stdout
);
let r = sb.mind_bounded(&["review", &sb.source_spec()]);
assert!(r.success, "review: {}\n{}", r.stdout, r.stderr);
sb.write_and_commit(
"workflows/deploy.js",
&PATHOLOGICAL_JS.replace("Deploy a release", "Deploy a release v2"),
);
let r = sb.mind_bounded(&["upgrade", "--yes"]);
assert!(r.success, "upgrade: {}\n{}", r.stdout, r.stderr);
assert!(
std::fs::read_to_string(sb.link("deploy.js"))
.unwrap()
.contains("Deploy a release v2"),
"the new content is linked: {}",
r.stdout
);
let r = sb.mind_bounded(&["forget", "workflow:deploy", "--yes"]);
assert!(r.success, "forget: {}\n{}", r.stdout, r.stderr);
assert!(
sb.link("deploy.js").symlink_metadata().is_err(),
"forget removes the lobe link"
);
}
#[test]
fn curate_does_not_stall_on_a_pathological_workflow_meta() {
let sb = Sandbox::new();
sb.write_and_commit("workflows/deploy.js", PATHOLOGICAL_JS);
let curator = sb.base.join("curator");
write(
&curator.join("mind.toml"),
&format!(
"[discover]\nsources = [\n {{ source = \"{}\", install = true }}\n]\n",
sb.source_spec()
),
);
git(&curator, &["-c", "init.defaultBranch=main", "init", "-q"]);
git(&curator, &["config", "user.email", "t@t"]);
git(&curator, &["config", "user.name", "t"]);
git(&curator, &["add", "-A"]);
git(&curator, &["commit", "-qm", "curator"]);
let spec = curator.to_string_lossy().into_owned();
let r = sb.mind_bounded(&["meld", &spec, "--register-only"]);
assert!(r.success, "meld curator: {}\n{}", r.stdout, r.stderr);
let r = sb.mind_bounded(&["curate", "--check"]);
assert!(r.success, "curate --check: {}\n{}", r.stdout, r.stderr);
let r = sb.mind_bounded(&["curate", "--yes"]);
assert!(r.success, "curate --yes: {}\n{}", r.stdout, r.stderr);
assert!(
sb.link("deploy.js").symlink_metadata().is_ok(),
"the curated source's workflow installs: {}\n{}",
r.stdout,
r.stderr
);
}
const TINY_JS: &str = "export const meta={name:'fine',description:'Fine'}\n";
fn padded_js(name: &str) -> String {
format!(
"export const meta = {{ name: '{name}', description: 'Ship it' }}\n// {}\n",
"p".repeat(2600)
)
}
#[test]
fn an_over_cap_workflow_leaves_every_scanning_verb_working() {
let sb = Sandbox::new();
sb.write_and_commit("workflows/fine.js", TINY_JS);
sb.write_and_commit("workflows/huge.js", &padded_js("huge"));
let cap = ["--max-metadata-size", "2KiB"];
let mut meld = vec!["meld", "--register-only"];
let spec = sb.source_spec();
meld.push(&spec);
meld.extend_from_slice(&cap);
let r = sb.mind_bounded(&meld);
assert!(r.success, "meld: {}\n{}", r.stdout, r.stderr);
let mut probe = vec!["probe", "--no-tui"];
probe.extend_from_slice(&cap);
let r = sb.mind_bounded(&probe);
assert!(r.success, "probe: {}\n{}", r.stdout, r.stderr);
assert!(
r.stdout.contains("workflow:huge") && r.stdout.contains("workflow:fine"),
"both workflows must be offered: {}",
r.stdout
);
assert!(
r.stdout.contains("Fine"),
"the sibling keeps its description: {}",
r.stdout
);
for (name, unloadable) in [("workflow:fine", false), ("workflow:huge", true)] {
let mut learn = vec!["learn", name];
learn.extend_from_slice(&cap);
let r = sb.mind_bounded(&learn);
assert!(r.success, "learn {name}: {}\n{}", r.stdout, r.stderr);
assert_eq!(
r.stderr.contains("over mind's own 2 KiB metadata read cap"),
unloadable,
"learn {name} reported the wrong loadability: {}",
r.stderr
);
assert_eq!(
r.stderr.contains("--max-metadata-size"),
unloadable,
"learn {name} must name the flag that raises the cap: {}",
r.stderr
);
assert!(
!r.stderr.contains("the harness will not load this workflow"),
"mind's own cap says nothing about the harness: {}",
r.stderr
);
}
assert!(
sb.link("huge.js").symlink_metadata().is_ok(),
"the over-cap workflow installs like any other unloadable one (WF-31)"
);
for args in [
vec!["sync"],
vec!["introspect"],
vec!["dump"],
vec!["recall"],
vec!["recall", "--sources"],
vec!["recall", "workflow:huge"],
vec!["upgrade", "--yes"],
vec!["review", &spec],
] {
let mut full = args.clone();
full.extend_from_slice(&cap);
let r = sb.mind_bounded(&full);
assert!(
r.success,
"`mind {}` failed: {}\n{}",
args.join(" "),
r.stdout,
r.stderr
);
assert!(
!format!("{}{}", r.stdout, r.stderr).contains("size cap"),
"`mind {}` must not report the cap as a failure: {}\n{}",
args.join(" "),
r.stdout,
r.stderr
);
}
}
#[test]
fn curate_installs_a_source_carrying_an_over_cap_workflow() {
let sb = Sandbox::new();
sb.write_and_commit("workflows/huge.js", &padded_js("huge"));
let curator = sb.base.join("curator");
write(
&curator.join("mind.toml"),
&format!(
"[discover]\nsources = [\n {{ source = \"{}\", install = true }}\n]\n",
sb.source_spec()
),
);
git(&curator, &["-c", "init.defaultBranch=main", "init", "-q"]);
git(&curator, &["config", "user.email", "t@t"]);
git(&curator, &["config", "user.name", "t"]);
git(&curator, &["add", "-A"]);
git(&curator, &["commit", "-qm", "curator"]);
let spec = curator.to_string_lossy().into_owned();
let r = sb.mind_bounded(&[
"meld",
&spec,
"--register-only",
"--max-metadata-size",
"2KiB",
]);
assert!(r.success, "meld curator: {}\n{}", r.stdout, r.stderr);
let r = sb.mind_bounded(&["curate", "--yes", "--max-metadata-size", "2KiB"]);
assert!(r.success, "curate: {}\n{}", r.stdout, r.stderr);
assert!(
sb.link("huge.js").symlink_metadata().is_ok(),
"the curated source's over-cap workflow installs: {}\n{}",
r.stdout,
r.stderr
);
}
#[test]
fn an_over_cap_workflow_declared_in_mind_toml_is_catalogued() {
let sb = Sandbox::new();
write(&sb.source.join("flows/huge.js"), &padded_js("huge"));
sb.write_and_commit(
"mind.toml",
"[[items]]\nkind = \"workflow\"\nname = \"huge\"\npath = \"flows/huge.js\"\n",
);
let spec = sb.source_spec();
let r = sb.mind_bounded(&[
"meld",
&spec,
"--register-only",
"--max-metadata-size",
"2KiB",
]);
assert!(r.success, "meld: {}\n{}", r.stdout, r.stderr);
let r = sb.mind_bounded(&["learn", "workflow:huge", "--max-metadata-size", "2KiB"]);
assert!(r.success, "learn: {}\n{}", r.stdout, r.stderr);
assert!(
sb.link("huge.js").symlink_metadata().is_ok(),
"a declared over-cap workflow installs: {}\n{}",
r.stdout,
r.stderr
);
assert!(
r.stderr.contains("over mind's own 2 KiB metadata read cap"),
"it is reported as mind's cap, not as a read failure: {}",
r.stderr
);
}
#[test]
fn an_over_cap_workflow_in_a_plugin_is_catalogued() {
let sb = Sandbox::new();
write(&sb.source.join("workflows/fine.js"), TINY_JS);
write(&sb.source.join("workflows/huge.js"), &padded_js("huge"));
sb.write_and_commit(
".claude-plugin/plugin.json",
"{\"name\":\"acme-tools\",\"description\":\"Tools\"}\n",
);
let spec = sb.source_spec();
let r = sb.mind_bounded(&[
"meld",
&spec,
"--register-only",
"--max-metadata-size",
"2KiB",
]);
assert!(r.success, "meld: {}\n{}", r.stdout, r.stderr);
let r = sb.mind_bounded(&["probe", "--no-tui", "--max-metadata-size", "2KiB"]);
assert!(r.success, "probe: {}\n{}", r.stdout, r.stderr);
assert!(
r.stdout.contains("acme-tools:huge") && r.stdout.contains("acme-tools:fine"),
"both of the plugin's workflows must be offered under the plugin's \
namespace: {}",
r.stdout
);
}
#[test]
fn an_over_cap_skill_still_fails_the_scan() {
let sb = Sandbox::new();
sb.write_and_commit("workflows/huge.js", &padded_js("huge"));
sb.write_and_commit(
"skills/review/SKILL.md",
&format!("---\ndescription: Review\n---\n{}\n", "p".repeat(2600)),
);
let r = sb.mind_bounded(&[
"meld",
&sb.source_spec(),
"--register-only",
"--max-metadata-size",
"2KiB",
]);
assert!(
!r.success,
"an over-cap SKILL.md must still fail the scan: {}\n{}",
r.stdout, r.stderr
);
let combined = format!("{}{}", r.stdout, r.stderr);
assert!(
combined.contains("SKILL.md") && combined.contains("size cap"),
"and must fail naming the file and the cap: {combined}"
);
}
#[test]
fn a_token_in_an_expand_listed_file_is_a_dependency_edge() {
let sb = Sandbox::new();
sb.write_and_commit(
"skills/deployer/SKILL.md",
"---\nname: deployer\ndescription: Deploy things\nexpand: run.sh\n---\n# deployer\n",
);
sb.write_and_commit("skills/deployer/run.sh", "#!/bin/sh\n# see {{ns:review}}\n");
assert!(sb.mind(&["meld", &sb.source_spec()]).success);
let r = sb.mind(&["learn", "skill:deployer", "--yes"]);
assert!(r.success, "learn: {}\n{}", r.stdout, r.stderr);
assert!(
sb.claude_home.join("skills/review").exists(),
"the token's referent must come with the closure: {}\n{}",
r.stdout,
r.stderr
);
let installed = std::fs::read_to_string(sb.claude_home.join("skills/deployer/run.sh")).unwrap();
assert!(
installed.contains("# see review") && !installed.contains("{{ns:"),
"the expand-listed file must have been expanded: {installed}"
);
}
#[test]
fn the_unguarded_reference_scan_covers_a_workflow_file() {
let sb = Sandbox::new();
sb.write_and_commit(
"workflows/review-changes.js",
"export const meta = {\n name: '{{ns:review-changes}}',\n description: 'Review changed files',\n}\n\
agent('follow the review skill')\n",
);
let r = sb.mind(&["review", &sb.source_spec(), "--namespace", "jk"]);
assert!(r.success, "review: {}\n{}", r.stdout, r.stderr);
assert!(
r.stdout.contains("[unguarded-reference]")
&& r.stdout.contains("workflow:jk:review-changes"),
"a bare sibling name in a workflow's prompt must be reported: {}",
r.stdout
);
}
#[test]
fn a_token_in_a_workflow_body_is_a_dependency_edge() {
let sb = Sandbox::new();
sb.write_and_commit(
"workflows/review-changes.js",
"export const meta = {\n name: 'review-changes',\n description: 'Review changed files',\n}\n\
agent(`run {{ns:review}} on the diff`)\n",
);
assert!(sb.mind(&["meld", &sb.source_spec()]).success);
let r = sb.mind(&["learn", "workflow:review-changes", "--yes"]);
assert!(r.success, "learn: {}\n{}", r.stdout, r.stderr);
assert!(
sb.claude_home.join("skills/review").exists(),
"the token's referent must come with the closure: {}\n{}",
r.stdout,
r.stderr
);
}
#[test]
fn an_over_cap_workflows_token_is_still_a_dependency_edge() {
let sb = Sandbox::new();
let body = format!(
"{}agent(`run {{{{ns:review}}}} on the diff`)\n",
padded_js("review-changes")
);
sb.write_and_commit("workflows/review-changes.js", &body);
let spec = sb.source_spec();
let r = sb.mind_bounded(&[
"meld",
&spec,
"--register-only",
"--max-metadata-size",
"2KiB",
]);
assert!(r.success, "meld: {}\n{}", r.stdout, r.stderr);
let r = sb.mind_bounded(&[
"learn",
"workflow:review-changes",
"--yes",
"--max-metadata-size",
"2KiB",
]);
assert!(r.success, "learn: {}\n{}", r.stdout, r.stderr);
assert!(
r.stderr.contains("over mind's own 2 KiB metadata read cap"),
"the fixture must really be over the cap: {}",
r.stderr
);
assert!(
sb.claude_home.join("skills/review").exists(),
"an over-cap workflow's token must still pull its referent: {}\n{}",
r.stdout,
r.stderr
);
}
#[test]
fn an_unmanaged_workflow_is_a_collision_claimant() {
let sb = Sandbox::new();
write(
&sb.claude_home.join("workflows/hand.js"),
"export const meta = { name: 'deploy', description: 'Hand-written deploy' }\n",
);
sb.write_and_commit(
"workflows/deploy.js",
"export const meta = { name: 'deploy', description: 'Deploy it' }\n",
);
assert!(sb.mind(&["meld", &sb.source_spec()]).success);
let r = sb.mind(&["learn", "workflow:deploy", "--yes"]);
assert!(r.success, "learn: {}\n{}", r.stdout, r.stderr);
assert!(
sb.link("deploy.js").symlink_metadata().is_ok(),
"the item still installs"
);
let warnings: Vec<&str> = r
.stderr
.lines()
.filter(|l| l.contains("workflow:hand (unmanaged)"))
.collect();
assert_eq!(
warnings.len(),
1,
"exactly one collision warning names the unmanaged claimant: {}",
r.stderr
);
assert!(
warnings[0].starts_with("warning: workflow:deploy:")
&& warnings[0].contains("harness name 'deploy'"),
"the installed item is the subject: {}",
r.stderr
);
assert!(
!r.stderr.contains("warning: workflow:hand"),
"an unmanaged claimant is never the subject: {}",
r.stderr
);
let text = sb.mind(&["recall", "workflow:deploy"]);
assert!(text.success, "recall: {}\n{}", text.stdout, text.stderr);
assert!(
text.stdout.contains("workflow:hand (unmanaged)"),
"recall <item> must report the unmanaged claimant: {}",
text.stdout
);
let json = sb.mind(&["recall", "workflow:deploy", "--json"]);
assert!(
json.success,
"recall --json: {}\n{}",
json.stdout, json.stderr
);
let doc: serde_json::Value = serde_json::from_str(json.stdout.trim()).expect("one document");
let findings = doc["workflow_findings"].as_array().expect("findings array");
assert!(
findings.iter().any(|f| f
.as_str()
.is_some_and(|s| s.contains("workflow:hand (unmanaged)"))),
"the --json findings carry it too: {doc}"
);
}
#[test]
fn json_carries_when_to_use_separately_from_the_description() {
let sb = Sandbox::new();
assert!(sb.mind(&["meld", &sb.source_spec()]).success);
let r = sb.mind(&["probe", "--no-tui", "--json"]);
assert!(r.success, "probe --json: {}\n{}", r.stdout, r.stderr);
let doc: serde_json::Value = serde_json::from_str(r.stdout.trim()).expect("one document");
let rows = doc["items"].as_array().expect("items array");
let wf = rows
.iter()
.find(|row| row["kind"] == "workflow" && row["name"] == "review-changes")
.unwrap_or_else(|| panic!("a workflow row: {doc}"));
assert_eq!(wf["description"], "Review changed files", "{wf}");
assert_eq!(wf["when_to_use"], "before opening a PR", "{wf}");
let skill = rows
.iter()
.find(|row| row["kind"] == "skill" && row["name"] == "review")
.unwrap_or_else(|| panic!("a skill row: {doc}"));
assert!(
skill.get("when_to_use").is_none(),
"a skill has no key: {skill}"
);
let human = sb.mind(&["probe", "--no-tui"]);
assert!(
human
.stdout
.contains("Review changed files - before opening a PR"),
"human output keeps the join: {}",
human.stdout
);
assert!(
sb.mind(&["learn", "workflow:review-changes", "--yes"])
.success
);
assert!(sb.mind(&["learn", "skill:review", "--yes"]).success);
let r = sb.mind(&["recall", "workflow:review-changes", "--json"]);
assert!(r.success, "recall item --json: {}\n{}", r.stdout, r.stderr);
let doc: serde_json::Value = serde_json::from_str(r.stdout.trim()).expect("one document");
assert_eq!(doc["description"], "Review changed files", "{doc}");
assert_eq!(doc["when_to_use"], "before opening a PR", "{doc}");
let r = sb.mind(&["recall", "skill:review", "--json"]);
let doc: serde_json::Value = serde_json::from_str(r.stdout.trim()).expect("one document");
assert!(
doc.get("when_to_use").is_none(),
"a skill has no key: {doc}"
);
let r = sb.mind(&["recall", "--json"]);
assert!(r.success, "recall --json: {}\n{}", r.stdout, r.stderr);
let doc: serde_json::Value = serde_json::from_str(r.stdout.trim()).expect("one document");
let rows: Vec<&serde_json::Value> = doc["items"]
.as_array()
.expect("sources array")
.iter()
.flat_map(|s| s["items"].as_array().expect("item rows").iter())
.collect();
let wf = rows
.iter()
.find(|row| row["key"] == "workflow:review-changes")
.unwrap_or_else(|| panic!("a workflow row: {doc}"));
assert_eq!(wf["when_to_use"], "before opening a PR", "{wf}");
let skill = rows
.iter()
.find(|row| row["key"] == "skill:review")
.unwrap_or_else(|| panic!("a skill row: {doc}"));
assert!(
skill.get("when_to_use").is_none(),
"a skill has no key: {skill}"
);
}
#[test]
fn recall_reports_why_the_harness_will_not_load_an_installed_workflow() {
let sb = Sandbox::new();
sb.write_and_commit(
"workflows/bare.js",
"export const meta = { name: 'bare' }\n",
);
assert!(sb.mind(&["meld", &sb.source_spec()]).success);
assert!(sb.mind(&["learn", "workflow:bare", "--yes"]).success);
let text = sb.mind(&["recall", "workflow:bare"]);
assert!(text.success, "recall: {}\n{}", text.stdout, text.stderr);
assert!(
text.stdout.lines().any(|l| l.contains("harness")
&& l.contains("the harness will not load this workflow")
&& l.contains("`meta.description` is missing")),
"the text view must print the unloadable reason: {}",
text.stdout
);
let json = sb.mind(&["recall", "workflow:bare", "--json"]);
let doc: serde_json::Value = serde_json::from_str(json.stdout.trim()).expect("one document");
let findings: Vec<&str> = doc["workflow_findings"]
.as_array()
.expect("findings array")
.iter()
.filter_map(|f| f.as_str())
.collect();
assert!(
findings
.iter()
.any(|f| f.contains("`meta.description` is missing")),
"the --json findings carry it: {doc}"
);
assert!(
sb.mind(&["learn", "workflow:review-changes", "--yes"])
.success
);
std::fs::write(sb.mind_home.join("store/workflow/review-changes"), "").unwrap();
let json = sb.mind(&["recall", "workflow:review-changes", "--json"]);
assert!(
json.success,
"recall --json: {}\n{}",
json.stdout, json.stderr
);
let doc: serde_json::Value = serde_json::from_str(json.stdout.trim()).expect("one document");
assert!(doc["harness_name"].is_null(), "{doc}");
assert!(
doc["workflow_findings"]
.as_array()
.expect("findings array")
.iter()
.any(|f| f
.as_str()
.is_some_and(|s| s.contains("mind read no `name`"))),
"an empty store copy is a WF-30 finding: {doc}"
);
}