use super::settings::{
clean_global_trusty_memory_hooks, deploy_output_style, inject_trusty_memory_mcp,
inject_trusty_search_mcp, preseed_workspace_trust, write_output_style, write_project_hooks,
};
use super::*;
use tempfile::tempdir;
#[test]
fn build_system_prompt_includes_trusty_block() {
let prompt = build_system_prompt().expect("trusty block is always present");
assert!(prompt.contains("## Trusty Tool Priority (Non-Overridable)"));
assert!(prompt.contains("mcp__trusty-memory__memory_recall"));
assert!(prompt.contains("mcp__trusty-search__search_code"));
assert!(prompt.contains("# PM Agent -- Claude MPM"));
}
#[test]
fn build_system_prompt_for_applies_project_override() {
let tmp = tempdir().unwrap();
let project = tmp.path();
let override_dir = project.join(".trusty-mpm");
std::fs::create_dir_all(&override_dir).unwrap();
std::fs::write(
override_dir.join("INSTRUCTIONS.md"),
"PROJECT_OVERRIDE_MARKER\n",
)
.unwrap();
let prompt = build_system_prompt_for(project);
assert!(prompt.contains("PROJECT_OVERRIDE_MARKER"));
assert!(prompt.contains("# BASE_PM Framework Floor"));
assert!(prompt.contains("# PM Agent -- Claude MPM"));
}
#[test]
fn build_system_prompt_for_no_override_matches_bundled_sections() {
let tmp = tempdir().unwrap();
let prompt = build_system_prompt_for(tmp.path());
assert!(prompt.contains("# PM Agent -- Claude MPM"));
assert!(prompt.contains("# Agent Delegation Routing"));
let base = prompt.find("# BASE_PM Framework Floor").expect("base");
let deleg = prompt.find("# Agent Delegation Routing").expect("deleg");
assert!(base > deleg, "BASE_PM floor must be last");
}
#[test]
fn prepare_session_stash_reflects_override() {
let tmp_home = tempdir().unwrap();
let tmp = tempdir().unwrap();
let project = tmp.path();
let fw = crate::core::paths::FrameworkPaths::under(tmp_home.path());
let override_dir = project.join(".trusty-mpm");
std::fs::create_dir_all(&override_dir).unwrap();
std::fs::write(
override_dir.join("WORKFLOW.md"),
"# Custom Workflow\n\nSTASH_OVERRIDE_MARKER\n",
)
.unwrap();
let report = prepare_session(&fw, project).expect("prep succeeds");
let stash = std::fs::read_to_string(&report.stash).expect("stash readable");
assert!(
stash.contains("STASH_OVERRIDE_MARKER"),
"stash must reflect the WORKFLOW.md override"
);
assert!(
!stash.contains("# PM Workflow Configuration"),
"bundled workflow heading must be replaced in the stash"
);
assert!(
stash.contains("# BASE_PM Framework Floor"),
"stash must still carry the BASE_PM floor"
);
assert_eq!(stash, build_system_prompt_for(project));
}
#[test]
fn prepare_session_writes_claude_md_and_stash() {
let tmp_home = tempdir().unwrap();
let tmp = tempdir().unwrap();
let project = tmp.path();
let fw = crate::core::paths::FrameworkPaths::under(tmp_home.path());
let report = prepare_session(&fw, project).expect("prep succeeds");
assert!(
project.join("CLAUDE.md").exists(),
"CLAUDE.md must exist after prep"
);
assert!(
report.stash.exists(),
"merged instructions stash must be written"
);
assert_eq!(
report.stash,
project.join(".trusty-mpm").join("last-instructions.md")
);
}
#[test]
fn prepare_session_sets_output_style() {
let tmp_home = tempdir().unwrap();
let tmp = tempdir().unwrap();
let project = tmp.path();
let fw = crate::core::paths::FrameworkPaths::under(tmp_home.path());
prepare_session(&fw, project).expect("prep succeeds");
let settings_path = project.join(".claude").join("settings.json");
assert!(settings_path.exists(), ".claude/settings.json must exist");
let value: serde_json::Value =
serde_json::from_str(&std::fs::read_to_string(&settings_path).unwrap()).unwrap();
assert_eq!(value["outputStyle"], serde_json::json!("trusty-mpm"));
}
#[test]
fn write_output_style_preserves_existing_keys() {
let tmp = tempdir().unwrap();
let project = tmp.path();
let claude_dir = project.join(".claude");
std::fs::create_dir_all(&claude_dir).unwrap();
std::fs::write(
claude_dir.join("settings.json"),
r#"{"theme":"dark","outputStyle":"old"}"#,
)
.unwrap();
write_output_style(project).expect("write succeeds");
let value: serde_json::Value =
serde_json::from_str(&std::fs::read_to_string(claude_dir.join("settings.json")).unwrap())
.unwrap();
assert_eq!(value["outputStyle"], serde_json::json!("trusty-mpm"));
assert_eq!(value["theme"], serde_json::json!("dark"));
}
#[test]
fn write_output_style_sets_spinner_tips() {
let tmp = tempdir().unwrap();
let project = tmp.path();
write_output_style(project).expect("write succeeds");
let value: serde_json::Value = serde_json::from_str(
&std::fs::read_to_string(project.join(".claude").join("settings.json")).unwrap(),
)
.unwrap();
assert_eq!(value["spinnerTipsEnabled"], serde_json::json!(true));
let tips = value["spinnerTipsOverride"]["tips"]
.as_array()
.expect("spinnerTipsOverride.tips must be an array");
assert!(!tips.is_empty(), "spinner tips must be non-empty");
assert!(tips.iter().all(|tip| tip.is_string()));
}
#[test]
fn write_project_hooks_writes_all_event_types() {
let tmp = tempdir().unwrap();
let project = tmp.path();
write_project_hooks(project).expect("write succeeds");
let value: serde_json::Value = serde_json::from_str(
&std::fs::read_to_string(project.join(".claude").join("settings.json")).unwrap(),
)
.unwrap();
let hooks = value["hooks"].as_object().expect("hooks must be an object");
for event in ["UserPromptSubmit", "SessionStart"] {
let groups = hooks[event]
.as_array()
.unwrap_or_else(|| panic!("{event} must be an array"));
assert!(!groups.is_empty(), "{event} must have a handler group");
let cmd = groups[0]["hooks"][0]["command"]
.as_str()
.expect("command must be a string");
assert!(
cmd.starts_with("trusty-memory "),
"{event} command must invoke trusty-memory: {cmd}"
);
}
}
#[test]
fn write_project_hooks_uses_canonical_commands() {
let tmp = tempdir().unwrap();
let project = tmp.path();
write_project_hooks(project).expect("write succeeds");
let value: serde_json::Value = serde_json::from_str(
&std::fs::read_to_string(project.join(".claude").join("settings.json")).unwrap(),
)
.unwrap();
let raw = serde_json::to_string(&value).unwrap();
assert!(
!raw.contains("hooks fire"),
"the broken `hooks fire` command must never be written: {raw}"
);
assert_eq!(
value["hooks"]["UserPromptSubmit"][0]["hooks"][0]["command"],
serde_json::json!("trusty-memory prompt-context")
);
assert_eq!(
value["hooks"]["SessionStart"][0]["hooks"][0]["command"],
serde_json::json!("trusty-memory inbox-check")
);
}
#[test]
fn write_project_hooks_omits_post_tool_use_and_stop() {
let tmp = tempdir().unwrap();
let project = tmp.path();
write_project_hooks(project).expect("write succeeds");
let value: serde_json::Value = serde_json::from_str(
&std::fs::read_to_string(project.join(".claude").join("settings.json")).unwrap(),
)
.unwrap();
for absent in ["PreToolUse", "PostToolUse", "Stop"] {
assert!(
value["hooks"].get(absent).is_none(),
"{absent} hook must not be registered"
);
}
}
#[test]
fn write_project_hooks_replaces_existing() {
let tmp = tempdir().unwrap();
let project = tmp.path();
write_project_hooks(project).expect("first write succeeds");
write_project_hooks(project).expect("second write succeeds");
let value: serde_json::Value = serde_json::from_str(
&std::fs::read_to_string(project.join(".claude").join("settings.json")).unwrap(),
)
.unwrap();
let ss = value["hooks"]["SessionStart"]
.as_array()
.expect("SessionStart must be an array");
assert_eq!(
ss.len(),
1,
"re-running must replace, not append, handler groups"
);
write_project_hooks(project).expect("third write succeeds");
let value: serde_json::Value = serde_json::from_str(
&std::fs::read_to_string(project.join(".claude").join("settings.json")).unwrap(),
)
.unwrap();
assert_eq!(
value["hooks"]["UserPromptSubmit"].as_array().unwrap().len(),
1
);
}
#[test]
fn inject_trusty_memory_mcp_adds_server() {
let tmp = tempdir().unwrap();
let project = tmp.path();
inject_trusty_memory_mcp(project).expect("injection succeeds");
let mcp_path = project.join(".mcp.json");
assert!(mcp_path.exists(), ".mcp.json must be created");
let value: serde_json::Value =
serde_json::from_str(&std::fs::read_to_string(&mcp_path).unwrap()).unwrap();
let server = &value["mcpServers"]["trusty-memory"];
assert_eq!(server["type"], serde_json::json!("stdio"));
assert_eq!(server["command"], serde_json::json!("trusty-memory"));
assert_eq!(server["args"], serde_json::json!(["serve", "--stdio"]));
}
#[test]
fn inject_trusty_memory_mcp_uses_serve_stdio() {
let tmp = tempdir().unwrap();
let project = tmp.path();
inject_trusty_memory_mcp(project).expect("injection succeeds");
let value: serde_json::Value =
serde_json::from_str(&std::fs::read_to_string(project.join(".mcp.json")).unwrap()).unwrap();
let args = value["mcpServers"]["trusty-memory"]["args"]
.as_array()
.expect("args is an array");
assert_eq!(
args,
&vec![serde_json::json!("serve"), serde_json::json!("--stdio")]
);
assert!(
!args.contains(&serde_json::json!("mcp")),
"must not use the nonexistent `mcp` subcommand"
);
}
#[test]
fn inject_trusty_memory_mcp_preserves_existing() {
let tmp = tempdir().unwrap();
let project = tmp.path();
std::fs::write(
project.join(".mcp.json"),
r#"{"mcpServers":{"trusty-search":{"type":"stdio","command":"trusty-search","args":["serve"]}}}"#,
)
.unwrap();
inject_trusty_memory_mcp(project).expect("injection succeeds");
let value: serde_json::Value =
serde_json::from_str(&std::fs::read_to_string(project.join(".mcp.json")).unwrap()).unwrap();
let servers = value["mcpServers"]
.as_object()
.expect("mcpServers must be an object");
assert!(
servers.contains_key("trusty-search"),
"existing server must survive injection"
);
assert!(
servers.contains_key("trusty-memory"),
"trusty-memory must be injected"
);
assert_eq!(
value["mcpServers"]["trusty-search"]["command"],
serde_json::json!("trusty-search")
);
}
#[test]
fn inject_trusty_memory_mcp_is_idempotent() {
let tmp = tempdir().unwrap();
let project = tmp.path();
inject_trusty_memory_mcp(project).expect("first injection succeeds");
let after_first = std::fs::read_to_string(project.join(".mcp.json")).expect("file exists");
inject_trusty_memory_mcp(project).expect("second injection succeeds");
let after_second = std::fs::read_to_string(project.join(".mcp.json")).expect("file exists");
assert_eq!(
after_first, after_second,
"re-injecting must leave the file unchanged"
);
let value: serde_json::Value = serde_json::from_str(&after_second).unwrap();
assert_eq!(
value["mcpServers"].as_object().unwrap().len(),
1,
"trusty-memory must not be duplicated"
);
}
#[test]
fn prepare_session_injects_trusty_memory_mcp() {
let tmp_home = tempdir().unwrap();
let tmp = tempdir().unwrap();
let project = tmp.path();
let fw = crate::core::paths::FrameworkPaths::under(tmp_home.path());
prepare_session(&fw, project).expect("prep succeeds");
let mcp_path = project.join(".mcp.json");
assert!(mcp_path.exists(), ".mcp.json must exist after prep");
let value: serde_json::Value =
serde_json::from_str(&std::fs::read_to_string(&mcp_path).unwrap()).unwrap();
assert_eq!(
value["mcpServers"]["trusty-memory"]["command"],
serde_json::json!("trusty-memory")
);
}
#[test]
fn remove_global_hooks_removes_trusty_memory_entries() {
let tmp = tempdir().unwrap();
let settings_path = tmp.path().join("settings.json");
std::fs::write(
&settings_path,
r#"{
"theme": "dark",
"hooks": {
"PostToolUse": [
{ "matcher": "*", "hooks": [ { "type": "command", "command": "bash track.sh" } ] },
{ "matcher": "Write|Edit|Bash", "hooks": [ { "type": "command", "command": "trusty-memory hooks fire claude.post-tool-use" } ] }
],
"Stop": [
{ "matcher": "", "hooks": [ { "type": "command", "command": "trusty-memory hooks fire claude.stop" } ] }
],
"UserPromptSubmit": [
{ "matcher": "", "hooks": [ { "type": "command", "command": "trusty-memory hooks fire claude.user-prompt" } ] }
]
}
}"#,
)
.unwrap();
clean_global_trusty_memory_hooks(&settings_path).expect("clean succeeds");
let value: serde_json::Value =
serde_json::from_str(&std::fs::read_to_string(&settings_path).unwrap()).unwrap();
assert_eq!(value["theme"], serde_json::json!("dark"));
let post = value["hooks"]["PostToolUse"].as_array().unwrap();
assert_eq!(post.len(), 1);
assert!(
post[0]["hooks"][0]["command"]
.as_str()
.unwrap()
.contains("track.sh")
);
assert!(
value["hooks"].get("Stop").is_none(),
"empty Stop event must be removed"
);
assert!(
value["hooks"].get("UserPromptSubmit").is_none(),
"empty UserPromptSubmit event must be removed"
);
}
#[test]
fn remove_global_hooks_tolerates_missing_file() {
let tmp = tempdir().unwrap();
let missing = tmp.path().join("nope.json");
clean_global_trusty_memory_hooks(&missing).expect("missing file is a no-op");
}
#[test]
fn inject_trusty_search_mcp_adds_server() {
let tmp = tempdir().unwrap();
let project = tmp.path();
inject_trusty_search_mcp(project).expect("injection succeeds");
let value: serde_json::Value =
serde_json::from_str(&std::fs::read_to_string(project.join(".mcp.json")).unwrap()).unwrap();
let server = &value["mcpServers"]["trusty-search"];
assert_eq!(server["type"], serde_json::json!("stdio"));
assert_eq!(server["command"], serde_json::json!("trusty-search"));
assert_eq!(server["args"], serde_json::json!(["serve"]));
}
#[test]
fn inject_trusty_search_mcp_preserves_existing() {
let tmp = tempdir().unwrap();
let project = tmp.path();
inject_trusty_memory_mcp(project).expect("memory injection succeeds");
inject_trusty_search_mcp(project).expect("search injection succeeds");
let value: serde_json::Value =
serde_json::from_str(&std::fs::read_to_string(project.join(".mcp.json")).unwrap()).unwrap();
let servers = value["mcpServers"].as_object().expect("object");
assert!(servers.contains_key("trusty-memory"), "memory must survive");
assert!(
servers.contains_key("trusty-search"),
"search must be added"
);
}
#[test]
fn inject_trusty_search_mcp_is_idempotent() {
let tmp = tempdir().unwrap();
let project = tmp.path();
inject_trusty_search_mcp(project).expect("first injection succeeds");
let first = std::fs::read_to_string(project.join(".mcp.json")).unwrap();
inject_trusty_search_mcp(project).expect("second injection succeeds");
let second = std::fs::read_to_string(project.join(".mcp.json")).unwrap();
assert_eq!(first, second, "re-injecting must leave the file unchanged");
}
#[test]
fn inject_both_mcp_servers_coexist() {
let tmp = tempdir().unwrap();
let project = tmp.path();
inject_trusty_memory_mcp(project).expect("memory injection succeeds");
inject_trusty_search_mcp(project).expect("search injection succeeds");
let value: serde_json::Value =
serde_json::from_str(&std::fs::read_to_string(project.join(".mcp.json")).unwrap()).unwrap();
let servers = value["mcpServers"].as_object().expect("object");
assert_eq!(servers.len(), 2, "exactly memory + search");
assert_eq!(
servers["trusty-memory"]["args"],
serde_json::json!(["serve", "--stdio"])
);
assert_eq!(
servers["trusty-search"]["args"],
serde_json::json!(["serve"])
);
}
#[test]
fn prepare_session_injects_both_mcp_servers() {
let tmp_home = tempdir().unwrap();
let tmp = tempdir().unwrap();
let project = tmp.path();
let fw = crate::core::paths::FrameworkPaths::under(tmp_home.path());
prepare_session(&fw, project).expect("prep succeeds");
let value: serde_json::Value =
serde_json::from_str(&std::fs::read_to_string(project.join(".mcp.json")).unwrap()).unwrap();
assert_eq!(
value["mcpServers"]["trusty-memory"]["command"],
serde_json::json!("trusty-memory")
);
assert_eq!(
value["mcpServers"]["trusty-search"]["command"],
serde_json::json!("trusty-search")
);
}
#[test]
fn preseed_trust_marks_directory() {
let tmp = tempdir().unwrap();
let claude_json = tmp.path().join(".claude.json");
let workspace = tmp.path().join("ws");
preseed_workspace_trust(&claude_json, &workspace).expect("seed succeeds");
let value: serde_json::Value =
serde_json::from_str(&std::fs::read_to_string(&claude_json).unwrap()).unwrap();
let key = workspace.to_string_lossy().to_string();
let entry = &value["projects"][&key];
assert_eq!(entry["hasTrustDialogAccepted"], serde_json::json!(true));
assert_eq!(
entry["hasCompletedProjectOnboarding"],
serde_json::json!(true)
);
assert!(
entry["projectOnboardingSeenCount"].as_u64().unwrap() >= 1,
"onboarding counter must be >= 1"
);
}
#[test]
fn preseed_trust_preserves_other_keys() {
let tmp = tempdir().unwrap();
let claude_json = tmp.path().join(".claude.json");
let workspace = tmp.path().join("ws");
std::fs::write(
&claude_json,
r#"{"oauthAccount":{"emailAddress":"r@1mc.io"},"projects":{"/other":{"hasTrustDialogAccepted":true}}}"#,
)
.unwrap();
preseed_workspace_trust(&claude_json, &workspace).expect("seed succeeds");
let value: serde_json::Value =
serde_json::from_str(&std::fs::read_to_string(&claude_json).unwrap()).unwrap();
assert_eq!(
value["oauthAccount"]["emailAddress"],
serde_json::json!("r@1mc.io")
);
assert_eq!(
value["projects"]["/other"]["hasTrustDialogAccepted"],
serde_json::json!(true)
);
let key = workspace.to_string_lossy().to_string();
assert_eq!(
value["projects"][&key]["hasTrustDialogAccepted"],
serde_json::json!(true)
);
}
#[test]
fn preseed_trust_is_idempotent() {
let tmp = tempdir().unwrap();
let claude_json = tmp.path().join(".claude.json");
let workspace = tmp.path().join("ws");
preseed_workspace_trust(&claude_json, &workspace).expect("first seed");
let first = std::fs::read_to_string(&claude_json).unwrap();
preseed_workspace_trust(&claude_json, &workspace).expect("second seed");
let second = std::fs::read_to_string(&claude_json).unwrap();
assert_eq!(first, second, "re-seeding must leave the file unchanged");
}
#[test]
fn preseed_trust_leaves_malformed_file() {
let tmp = tempdir().unwrap();
let claude_json = tmp.path().join(".claude.json");
let workspace = tmp.path().join("ws");
let garbage = "{ this is not valid json ";
std::fs::write(&claude_json, garbage).unwrap();
preseed_workspace_trust(&claude_json, &workspace).expect("soft-fails to Ok");
let after = std::fs::read_to_string(&claude_json).unwrap();
assert_eq!(after, garbage, "malformed file must be left untouched");
}
#[test]
fn preseed_trust_enables_mcp_servers_from_mcp_json() {
let tmp = tempdir().unwrap();
let claude_json = tmp.path().join(".claude.json");
let workspace = tmp.path().join("ws");
std::fs::create_dir_all(&workspace).unwrap();
std::fs::write(
workspace.join(".mcp.json"),
r#"{"mcpServers":{"trusty-search":{"command":"trusty-search"},"trusty-memory":{"command":"trusty-memory"}}}"#,
)
.unwrap();
preseed_workspace_trust(&claude_json, &workspace).expect("seed succeeds");
let value: serde_json::Value =
serde_json::from_str(&std::fs::read_to_string(&claude_json).unwrap()).unwrap();
let key = workspace.to_string_lossy().to_string();
let enabled = value["projects"][&key]["enabledMcpjsonServers"]
.as_array()
.expect("enabledMcpjsonServers is an array");
let mut names: Vec<&str> = enabled.iter().filter_map(|v| v.as_str()).collect();
names.sort_unstable();
assert_eq!(
names,
vec!["trusty-memory", "trusty-search"],
"all .mcp.json server names must be pre-approved"
);
}
#[test]
fn preseed_trust_enables_empty_when_no_mcp_json() {
let tmp = tempdir().unwrap();
let claude_json = tmp.path().join(".claude.json");
let workspace = tmp.path().join("ws");
std::fs::create_dir_all(&workspace).unwrap();
preseed_workspace_trust(&claude_json, &workspace).expect("seed succeeds");
let value: serde_json::Value =
serde_json::from_str(&std::fs::read_to_string(&claude_json).unwrap()).unwrap();
let key = workspace.to_string_lossy().to_string();
let enabled = value["projects"][&key]["enabledMcpjsonServers"]
.as_array()
.expect("enabledMcpjsonServers is an array");
assert!(
enabled.is_empty(),
"no .mcp.json yields an empty approval list, not a crash"
);
}
#[test]
fn prepare_session_preseeds_enabled_mcp_servers() {
let tmp_home = tempdir().unwrap();
let tmp = tempdir().unwrap();
let project = tmp.path();
let fw = crate::core::paths::FrameworkPaths::under(tmp_home.path());
prepare_session(&fw, project).expect("prep succeeds");
let mcp: serde_json::Value =
serde_json::from_str(&std::fs::read_to_string(project.join(".mcp.json")).unwrap()).unwrap();
let servers = mcp["mcpServers"].as_object().expect("mcpServers object");
assert!(servers.contains_key("trusty-memory"));
assert!(servers.contains_key("trusty-search"));
let claude_json = tmp_home.path().join(".claude-iso.json");
preseed_workspace_trust(&claude_json, project).expect("seed succeeds");
let value: serde_json::Value =
serde_json::from_str(&std::fs::read_to_string(&claude_json).unwrap()).unwrap();
let key = project.to_string_lossy().to_string();
let enabled = value["projects"][&key]["enabledMcpjsonServers"]
.as_array()
.expect("enabledMcpjsonServers is an array");
let mut names: Vec<&str> = enabled.iter().filter_map(|v| v.as_str()).collect();
names.sort_unstable();
assert_eq!(names, vec!["trusty-memory", "trusty-search"]);
}
#[test]
fn deploy_output_style_writes_file() {
let home = tempdir().unwrap();
let path = deploy_output_style(home.path()).expect("deploy succeeds");
assert_eq!(
path,
home.path()
.join(".claude")
.join("output-styles")
.join("trusty-mpm.md")
);
let written = std::fs::read_to_string(&path).expect("style file readable");
assert_eq!(written, crate::core::bundle::OUTPUT_STYLE);
assert!(written.contains("name: trusty-mpm"));
}
#[test]
fn deploy_output_style_overwrites() {
let home = tempdir().unwrap();
let first = deploy_output_style(home.path()).expect("first deploy succeeds");
std::fs::write(&first, "stale operator content").unwrap();
let second = deploy_output_style(home.path()).expect("second deploy succeeds");
assert_eq!(first, second);
let written = std::fs::read_to_string(&second).unwrap();
assert_eq!(written, crate::core::bundle::OUTPUT_STYLE);
}
#[test]
fn prepare_session_reports_output_style() {
let tmp_home = tempdir().unwrap();
let tmp = tempdir().unwrap();
let project = tmp.path();
let fw = crate::core::paths::FrameworkPaths::under(tmp_home.path());
let report = prepare_session(&fw, project).expect("prep succeeds");
let style = report
.output_style
.expect("output style deployed when home is resolvable");
assert!(style.ends_with("trusty-mpm.md"));
assert!(style.exists());
}
#[test]
fn prepare_session_reports_skill_deploy() {
let tmp_home = tempdir().unwrap();
let tmp = tempdir().unwrap();
let project = tmp.path();
let fw = crate::core::paths::FrameworkPaths::under(tmp_home.path());
let report = prepare_session(&fw, project).expect("prep succeeds");
let _ = &report.skill_deploy;
}
#[test]
fn prepare_session_is_idempotent() {
let tmp_home = tempdir().unwrap();
let tmp = tempdir().unwrap();
let project = tmp.path();
let fw = crate::core::paths::FrameworkPaths::under(tmp_home.path());
let first = prepare_session(&fw, project).expect("first prep succeeds");
assert!(first.instructions.claude_md_created);
let second = prepare_session(&fw, project).expect("second prep succeeds");
assert!(
!second.instructions.claude_md_created,
"CLAUDE.md already exists on the second run"
);
}