use crate::helpers::*;
use predicates::str::contains;
use std::fs;
#[test]
fn claude_dispatch_guidance_uses_claude_p() {
let tmp = tempfile::TempDir::new().unwrap();
let (skill_dir, cwd) = setup(tmp.path(), DEFAULT_EVALS);
let assert = skill_eval()
.current_dir(&cwd)
.args(["run", "--skill-dir"])
.arg(&skill_dir)
.args([
"--skill",
"mr-review",
"--mode",
"new-skill",
"--harness",
"claude-code",
])
.assert()
.success();
let stdout = String::from_utf8(assert.get_output().stdout.clone()).unwrap();
assert!(stdout.contains("claude -p --output-format stream-json"));
assert!(stdout.contains("--verbose"));
assert!(stdout.contains("cd <eval-root>"));
assert!(stdout.contains("claude-events.jsonl"));
assert!(!stdout.contains("--output-last-message"));
let manifest = read_str(&iteration_dir(&cwd).join("dispatch-manifest.md"));
assert!(manifest.contains("claude -p --output-format stream-json"));
assert!(manifest.contains("claude-events.jsonl"));
assert!(manifest.contains("xargs -0 -P"));
let conditions = read_json(&iteration_dir(&cwd).join("conditions.json"));
assert_eq!(conditions["harness"], "claude-code");
}
#[test]
fn claude_dispatch_guidance_includes_agent_model_when_provided() {
let tmp = tempfile::TempDir::new().unwrap();
let (skill_dir, cwd) = setup(tmp.path(), DEFAULT_EVALS);
let assert = skill_eval()
.current_dir(&cwd)
.args(["run", "--skill-dir"])
.arg(&skill_dir)
.args([
"--skill",
"mr-review",
"--harness",
"claude-code",
"--agent-model",
"opus",
])
.assert()
.success();
let stdout = String::from_utf8(assert.get_output().stdout.clone()).unwrap();
assert!(stdout.contains("claude -p --output-format stream-json"));
assert!(stdout.contains("--model opus"));
}
#[test]
fn claude_run_writes_human_followed_runbook() {
let tmp = tempfile::TempDir::new().unwrap();
let (skill_dir, cwd) = setup(tmp.path(), DEFAULT_EVALS);
skill_eval()
.current_dir(&cwd)
.args(["run", "--skill-dir"])
.arg(&skill_dir)
.args([
"--skill",
"mr-review",
"--harness",
"claude-code",
"--dry-run",
])
.assert()
.success();
let manifest = read_str(&iteration_dir(&cwd).join("dispatch-manifest.md"));
assert!(manifest.contains("claude -p --output-format stream-json"));
let runbook = read_str(&iteration_dir(&cwd).join("RUNBOOK.md"));
assert!(
runbook.contains("human driving"),
"uses the human-followed template: {runbook}"
);
assert!(
runbook.contains("claude -p"),
"carries the claude -p dispatch recipe: {runbook}"
);
assert!(
runbook.contains("--harness claude-code"),
"pipeline commands carry --harness claude-code: {runbook}"
);
assert!(
!runbook.contains("{{"),
"no unsubstituted tokens: {runbook}"
);
}
#[test]
fn claude_record_runs_does_not_require_a_session_id() {
let tmp = tempfile::TempDir::new().unwrap();
let (skill_dir, cwd) = setup(tmp.path(), DEFAULT_EVALS);
skill_eval()
.current_dir(&cwd)
.args(["run", "--skill-dir"])
.arg(&skill_dir)
.args(["--skill", "mr-review", "--harness", "claude-code"])
.assert()
.success();
skill_eval()
.current_dir(&cwd)
.env_remove("CLAUDE_CODE_SESSION_ID")
.args(["record-runs", "--skill-dir"])
.arg(&skill_dir)
.args(["--skill", "mr-review", "--workspace-dir"])
.arg(cwd.join(".eval-magic"))
.args(["--harness", "claude-code"])
.assert()
.success()
.stdout(contains("Recorded:"));
}
#[test]
fn claude_cli_guard_installs_project_hook() {
let tmp = tempfile::TempDir::new().unwrap();
let (skill_dir, cwd) = setup(tmp.path(), DEFAULT_EVALS);
skill_eval()
.current_dir(&cwd)
.args(["run", "--skill-dir"])
.arg(&skill_dir)
.args([
"--skill",
"mr-review",
"--harness",
"claude-code",
"--guard",
])
.assert()
.success();
let with_env = cli_env_dir(&cwd, "g1", "with_skill");
let settings_path = with_env.join(".claude/settings.local.json");
assert!(settings_path.exists());
let settings = read_json(&settings_path);
let hook = &settings["hooks"]["PreToolUse"][0];
let command = hook["hooks"][0]["command"].as_str().unwrap();
assert!(
command.contains("guard") && !command.contains("guard-codex"),
"hook invokes the claude guard entry point: {settings}"
);
assert!(
hook["matcher"].as_str().unwrap().contains("Write"),
"hook matches write tools: {settings}"
);
assert!(
with_env
.join(".claude/skills/.slow-powers-eval-guard.json")
.exists()
);
let without_env = cli_env_dir(&cwd, "g1", "without_skill");
assert!(
without_env.join(".claude/settings.local.json").exists(),
"the without_skill env is guarded too"
);
assert!(
!without_env
.join(".claude/skills/slow-powers-eval-1-with_skill__mr-review")
.exists(),
"the control arm's env contains no staged skill slug"
);
}
#[test]
fn cli_plugin_shadow_preflight_reads_per_env_project_settings() {
let tmp = tempfile::TempDir::new().unwrap();
let evals = r#"{ "skill_name": "mr-review", "evals": [ { "id": "e1", "prompt": "p", "expected_output": "o", "files": [".claude/settings.json"] } ] }"#;
let (skill_dir, cwd) = setup(tmp.path(), evals);
let config = tmp.path().join("config");
let install = config.join("plugins/cache/shadowplug__test");
fs::create_dir_all(install.join("skills/mr-review")).unwrap();
fs::write(
install.join("skills/mr-review/SKILL.md"),
"---\nname: mr-review\ndescription: x\n---\n",
)
.unwrap();
fs::create_dir_all(config.join("plugins")).unwrap();
fs::write(
config.join("plugins/installed_plugins.json"),
format!(
"{{\"version\":2,\"plugins\":{{\"shadowplug@test\":[{{\"installPath\":{:?}}}]}}}}",
install.to_string_lossy()
),
)
.unwrap();
fs::create_dir_all(skill_dir.join("mr-review/evals/.claude")).unwrap();
fs::write(
skill_dir.join("mr-review/evals/.claude/settings.json"),
"{\"enabledPlugins\":{\"shadowplug@test\":true}}",
)
.unwrap();
skill_eval()
.current_dir(&cwd)
.env("CLAUDE_CONFIG_DIR", &config)
.args(["run", "--skill-dir"])
.arg(&skill_dir)
.args(["--skill", "mr-review", "--harness", "claude-code"])
.assert()
.success();
assert!(
iteration_dir(&cwd).join("plugin-shadow.json").exists(),
"preflight detected the project-enabled plugin shadow by scanning the staged env"
);
}
#[test]
fn run_omits_run_mode_from_every_artifact_and_command() {
let tmp = tempfile::TempDir::new().unwrap();
let (skill_dir, cwd) = setup(tmp.path(), DEFAULT_EVALS);
let assert = skill_eval()
.current_dir(&cwd)
.args(["run", "--skill-dir"])
.arg(&skill_dir)
.args([
"--skill",
"mr-review",
"--harness",
"claude-code",
"--dry-run",
])
.assert()
.success();
let stdout = String::from_utf8(assert.get_output().stdout.clone()).unwrap();
assert!(
!stdout.contains("--run-mode"),
"printed next-step commands carry no --run-mode: {stdout}"
);
let conditions = read_json(&iteration_dir(&cwd).join("conditions.json"));
assert!(
conditions.get("run_mode").is_none(),
"conditions.json carries no run_mode: {conditions}"
);
let dispatch = read_json(&iteration_dir(&cwd).join("dispatch.json"));
assert!(
dispatch.get("run_mode").is_none(),
"dispatch.json carries no run_mode: {dispatch}"
);
let runbook = read_str(&iteration_dir(&cwd).join("RUNBOOK.md"));
assert!(
!runbook.contains("--run-mode"),
"runbook pipeline commands carry no --run-mode: {runbook}"
);
let manifest = read_str(&iteration_dir(&cwd).join("dispatch-manifest.md"));
assert!(
!manifest.contains("--run-mode"),
"dispatch manifest carries no --run-mode: {manifest}"
);
}
#[test]
fn run_mode_flag_is_rejected() {
let tmp = tempfile::TempDir::new().unwrap();
let (skill_dir, cwd) = setup(tmp.path(), DEFAULT_EVALS);
skill_eval()
.current_dir(&cwd)
.args(["run", "--skill-dir"])
.arg(&skill_dir)
.args([
"--skill",
"mr-review",
"--harness",
"claude-code",
"--run-mode",
"hybrid",
"--dry-run",
])
.assert()
.failure();
}