use crate::harness::*;
const ENC: &str = "-Users-dev-example-project";
const BINDER: &str = "11111111-2222-4333-8444-555566667777";
const AUDITEE: &str = "aaaaaaaa-bbbb-4ccc-8ddd-eeeeffff0000";
const COBINDER: &str = "ffffffff-1111-4222-8333-444455556666";
const SHARED_PLAN: &str = "/Users/dev/.claude/plans/amber-drifting-kite.md";
const OWN_PLAN: &str = "/Users/dev/.claude/plans/copper-still-pond.md";
fn audit_scenario(h: &Home) {
h.write(
&format!("{ENC}/{BINDER}.jsonl"),
&format!(concat!(
r#"{{"type":"user","uuid":"b1","timestamp":"2026-06-07T04:00:00.000Z","message":{{"role":"user","content":"plan the kite"}}}}"#, "\n",
r#"{{"type":"attachment","isSidechain":false,"attachment":{{"type":"plan_mode","reminderType":"full","isSubAgent":false,"planFilePath":"{sp}","planExists":false}},"uuid":"batt","timestamp":"2026-06-07T04:00:01.000Z","userType":"external","entrypoint":"cli","cwd":"/p"}}"#, "\n",
), sp = SHARED_PLAN),
);
h.write(
&format!("{ENC}/{AUDITEE}.jsonl"),
&format!(concat!(
r#"{{"type":"user","uuid":"u1","timestamp":"2026-06-07T05:00:00.000Z","message":{{"role":"user","content":"work"}}}}"#, "\n",
r#"{{"type":"attachment","isSidechain":false,"attachment":{{"type":"plan_mode","reminderType":"full","isSubAgent":false,"planFilePath":"{op}","planExists":false}},"uuid":"aatt","timestamp":"2026-06-07T05:00:01.000Z","userType":"external","entrypoint":"cli","cwd":"/p"}}"#, "\n",
r#"{{"type":"assistant","uuid":"a1","parentUuid":"u1","timestamp":"2026-06-07T05:00:02.000Z","message":{{"role":"assistant","content":[{{"type":"tool_use","id":"t1","name":"Edit","input":{{"file_path":"{op}","old_string":"a","new_string":"b"}}}}]}}}}"#, "\n",
r#"{{"type":"assistant","uuid":"a2","parentUuid":"u1","timestamp":"2026-06-07T05:00:03.000Z","message":{{"role":"assistant","content":[{{"type":"tool_use","id":"t2","name":"Edit","input":{{"file_path":"{sp}","old_string":"c","new_string":"d"}}}}]}}}}"#, "\n",
r#"{{"type":"assistant","uuid":"a3","parentUuid":"u1","timestamp":"2026-06-07T05:00:04.000Z","message":{{"role":"assistant","content":[{{"type":"tool_use","id":"t3","name":"Write","input":{{"file_path":"{sp}","content":"whole"}}}}]}}}}"#, "\n",
r#"{{"type":"assistant","uuid":"a4","parentUuid":"u1","timestamp":"2026-06-07T05:00:05.000Z","message":{{"role":"assistant","content":[{{"type":"tool_use","id":"t4","name":"Edit","input":{{"file_path":"/Users/dev/example-project/src/main.rs","old_string":"e","new_string":"f"}}}}]}}}}"#, "\n",
"not json here", "\n",
), op = OWN_PLAN, sp = SHARED_PLAN),
);
h.write(
&format!("{ENC}/{COBINDER}.jsonl"),
&format!(concat!(
r#"{{"type":"user","uuid":"c1","timestamp":"2026-06-07T03:00:00.000Z","message":{{"role":"user","content":"resume the pond plan"}}}}"#, "\n",
r#"{{"type":"attachment","isSidechain":false,"attachment":{{"type":"plan_mode","reminderType":"full","isSubAgent":false,"planFilePath":"{op}","planExists":true}},"uuid":"catt","timestamp":"2026-06-07T03:00:01.000Z","userType":"external","entrypoint":"cli","cwd":"/p"}}"#, "\n",
r#"{{"type":"assistant","uuid":"ca1","parentUuid":"c1","timestamp":"2026-06-07T03:00:02.000Z","message":{{"role":"assistant","content":[{{"type":"tool_use","id":"ct1","name":"Edit","input":{{"file_path":"{op}","old_string":"x","new_string":"y"}}}}]}}}}"#, "\n",
), op = OWN_PLAN),
);
}
#[test]
fn plan_audit_warns_on_edits_to_an_unbound_plan() {
let h = Home::new();
audit_scenario(&h);
let out = h.run(&["plan", &at(AUDITEE), "--audit"]);
assert!(out.success, "stderr: {}", out.stderr);
assert!(
out.stdout
.contains(&format!("binds {AUDITEE} -> {OWN_PLAN}")),
"own binding status:\n{}",
out.stdout
);
assert!(
out.stdout
.contains(&format!("edits {OWN_PLAN} 1 mutation(s)"))
&& out.stdout.contains("[bound by this session]"),
"own-plan edits are fine:\n{}",
out.stdout
);
assert!(
out.stdout
.contains(&format!("edits {SHARED_PLAN} 2 mutation(s)"))
&& out.stdout.contains("[NOT bound by the mutating session]"),
"unbound-plan edits flagged:\n{}",
out.stdout
);
assert!(
out.stdout.contains(&format!(
"warning: 2 mutation(s) to {SHARED_PLAN} by session {AUDITEE}, which does NOT \
bind it (bound by {BINDER}, L2). Only the BOUND plan is re-injected in full \
after a compaction."
)),
"the exact warning wording:\n{}",
out.stdout
);
assert!(
!out.stdout.contains("src/main.rs"),
"an ordinary file is never audited:\n{}",
out.stdout
);
assert!(
!out.stdout.contains("ok: every audited"),
"no all-clear line while a warning stands:\n{}",
out.stdout
);
assert!(
out.stdout.contains("(1 malformed line(s) skipped)"),
"the scan's malformed line is booked:\n{}",
out.stdout
);
let outj = h.run(&["plan", &at(AUDITEE), "--audit", "--format", "json"]);
let rows: Vec<serde_json::Value> = outj
.stdout
.lines()
.map(|l| serde_json::from_str(l).unwrap())
.collect();
assert_eq!(rows[0]["kind"], "header");
assert_eq!(rows[0]["mode"], "audit");
let shared = rows
.iter()
.find(|r| r["kind"] == "plan-edit" && r["path"] == SHARED_PLAN)
.expect("shared-plan row");
assert_eq!(shared["mutations"], 2);
assert_eq!(shared["bound_by_owner"], false);
assert_eq!(shared["binder_session_id"], BINDER);
assert_eq!(shared["binder_line"], 2);
let own = rows
.iter()
.find(|r| r["kind"] == "plan-edit" && r["path"] == OWN_PLAN)
.expect("own-plan row");
assert_eq!(own["bound_by_owner"], true);
assert_eq!(
own["binder_session_id"], COBINDER,
"the displayed binder prefers a NON-owner co-binder: {}",
outj.stdout
);
let summary = rows.last().unwrap();
assert_eq!(summary["warnings"], 1);
assert_eq!(summary["plan_files_touched"], 2);
}
#[test]
fn plan_audit_clean_session_reports_ok_and_none() {
let h = Home::new();
audit_scenario(&h);
let out = h.run(&["plan", &at(BINDER), "--audit"]);
assert!(out.success, "stderr: {}", out.stderr);
assert!(
out.stdout.contains("edits none")
&& out
.stdout
.contains("bash-side edits are outside this audit"),
"honest empty + the audit's stated blind spot:\n{}",
out.stdout
);
assert!(
!out.stdout.contains("ok: every audited"),
"the all-clear line needs audited rows, not an empty set:\n{}",
out.stdout
);
}
#[test]
fn plan_audit_all_clear_and_multi_owner_attribution() {
let h = Home::new();
audit_scenario(&h);
let ok = h.run(&["plan", &at(COBINDER), "--audit"]);
assert!(ok.success, "stderr: {}", ok.stderr);
assert!(
ok.stdout.contains("ok: every audited") && !ok.stdout.contains("warning:"),
"all-clear on own-plan-only edits:\n{}",
ok.stdout
);
let multi = h.run(&["plan", ENC, "--audit"]);
assert!(multi.success, "stderr: {}", multi.stderr);
assert!(
multi
.stdout
.contains(&format!("2 mutation(s) by {AUDITEE}")),
"edits rows carry the owner in a multi-owner scope:\n{}",
multi.stdout
);
}
const SLUG_ENC: &str = "-Users-dev-example-project";
#[test]
fn plan_slug_only_binding_minted_at_compaction() {
let h = Home::new();
let sess = "4b3a2c1d-9e8f-4765-b432-10fedcba9877";
h.write(
&format!("{SLUG_ENC}/{sess}.jsonl"),
concat!(
r#"{"type":"user","uuid":"u1","timestamp":"2026-06-07T05:00:00.000Z","message":{"role":"user","content":"start"}}"#, "\n",
r#"{"type":"system","subtype":"compact_boundary","uuid":"cb1","timestamp":"2026-06-07T06:00:00.000Z","slug":"minted-harbor-lantern","compactMetadata":{"trigger":"auto","preTokens":1000,"postTokens":100}}"#, "\n",
r#"{"type":"assistant","uuid":"a1","timestamp":"2026-06-07T06:00:01.000Z","slug":"minted-harbor-lantern","message":{"role":"assistant","content":[{"type":"text","text":"resumed"}]}}"#, "\n",
),
);
let out = h.run(&["plan", &format!("@{sess}")]);
assert!(out.success, "stderr: {}", out.stderr);
assert!(
out.stdout.contains("minted-harbor-lantern.md")
&& out.stdout.contains("[missing]")
&& out.stdout.contains("slug only")
&& out.stdout.contains("MINTED at a compaction boundary"),
"{}",
out.stdout
);
let j = h.run(&["plan", &format!("@{sess}"), "--format", "json"]);
let row: serde_json::Value = j
.stdout
.lines()
.filter_map(|l| serde_json::from_str(l).ok())
.find(|v: &serde_json::Value| v["kind"] == "plan")
.expect("plan row");
assert_eq!(row["binding_source"], "slug-only", "{}", j.stdout);
assert_eq!(row["minted_at_compaction"], true, "{}", j.stdout);
assert_eq!(row["slug"], "minted-harbor-lantern", "{}", j.stdout);
assert_eq!(row["line"], 2, "{}", j.stdout);
}
#[test]
fn plan_slug_only_ordinary_carrier_and_invalid_slug() {
let h = Home::new();
let s1 = "5c4b3a2d-8f9e-4654-a321-0fedcba98766";
h.write(
&format!("{SLUG_ENC}/{s1}.jsonl"),
concat!(
r#"{"type":"assistant","uuid":"a1","timestamp":"2026-06-07T05:00:01.000Z","slug":"steady-reef-charter","message":{"role":"assistant","content":[{"type":"text","text":"working"}]}}"#, "\n",
),
);
let out = h.run(&["plan", &format!("@{s1}"), "--format", "json"]);
let row: serde_json::Value = out
.stdout
.lines()
.filter_map(|l| serde_json::from_str(l).ok())
.find(|v: &serde_json::Value| v["kind"] == "plan")
.expect("row");
assert_eq!(row["binding_source"], "slug-only", "{}", out.stdout);
assert_eq!(row["minted_at_compaction"], false, "{}", out.stdout);
let s2 = "6d5c4b3a-7e8f-4543-b210-fedcba987655";
h.write(
&format!("{SLUG_ENC}/{s2}.jsonl"),
concat!(
r#"{"type":"assistant","uuid":"b1","timestamp":"2026-06-07T05:00:01.000Z","slug":"Not_A_Valid_Slug","message":{"role":"assistant","content":[{"type":"text","text":"working"}]}}"#, "\n",
),
);
let bad = h.run(&["plan", &format!("@{s2}")]);
assert!(bad.success);
assert!(
bad.stderr.contains("no plan file is bound"),
"{} / {}",
bad.stdout,
bad.stderr
);
}
#[test]
fn plan_mode_attachment_still_outranks_the_slug_law() {
let h = Home::new();
let sess = "7e6d5c4b-6a9f-4432-a109-edcba9876544";
h.write(
&format!("{SLUG_ENC}/{sess}.jsonl"),
concat!(
r#"{"type":"assistant","uuid":"a1","timestamp":"2026-06-07T05:00:01.000Z","slug":"quiet-shoal-beacon","message":{"role":"assistant","content":[{"type":"text","text":"planning"}]}}"#, "\n",
r#"{"type":"attachment","uuid":"p1","timestamp":"2026-06-07T05:00:02.000Z","slug":"quiet-shoal-beacon","attachment":{"type":"plan_mode","planFilePath":"/Users/dev/plans/quiet-shoal-beacon.md","isSubAgent":false}}"#, "\n",
),
);
let out = h.run(&["plan", &format!("@{sess}"), "--format", "json"]);
let row: serde_json::Value = out
.stdout
.lines()
.filter_map(|l| serde_json::from_str(l).ok())
.find(|v: &serde_json::Value| v["kind"] == "plan")
.expect("row");
assert_eq!(row["binding_source"], "plan_mode", "{}", out.stdout);
assert_eq!(
row["plan_file"], "/Users/dev/plans/quiet-shoal-beacon.md",
"{}",
out.stdout
);
}
#[test]
fn plan_slug_only_honors_plans_directory_and_reverse() {
const SLUGSESS: &str = "dddd4444-5555-4666-8777-888899990000";
let h = Home::new();
let proj = h.root.join("harbor-project");
std::fs::create_dir_all(proj.join("my-plans")).unwrap();
std::fs::write(
proj.join("my-plans").join("quiet-harbor-lantern.md"),
"plan body\n",
)
.unwrap();
let cwd = serde_json::to_string(proj.to_str().unwrap()).unwrap();
h.write_claude("settings.json", r#"{"plansDirectory":"my-plans"}"#);
let transcript = format!(
concat!(
r#"{{"type":"user","uuid":"u1","timestamp":"2026-06-07T05:00:00.000Z","cwd":{cwd},"slug":"quiet-harbor-lantern","message":{{"role":"user","content":"forked work"}}}}"#,
"\n",
r#"{{"type":"assistant","uuid":"a1","timestamp":"2026-06-07T05:00:01.000Z","cwd":{cwd},"slug":"quiet-harbor-lantern","message":{{"role":"assistant","content":[{{"type":"text","text":"ok"}}]}}}}"#,
"\n",
),
cwd = cwd
);
h.write(&format!("{ENC}/{SLUGSESS}.jsonl"), &transcript);
let out = h.run(&["plan", &at(SLUGSESS)]);
assert!(out.success, "stderr: {}", out.stderr);
let expected = proj.join("my-plans").join("quiet-harbor-lantern.md");
assert!(
out.stdout.contains(expected.to_str().unwrap()) && out.stdout.contains("slug only"),
"plansDirectory joins the project root on the slug law:\n{}",
out.stdout
);
const SLUGSESS2: &str = "eeee4444-5555-4666-8777-888899990000";
h.write(
&format!("{ENC}/{SLUGSESS2}.jsonl"),
&(format!(
r#"{{"type":"user","uuid":"u1","timestamp":"2026-06-07T06:00:00.000Z","cwd":{cwd},"slug":"quiet-harbor-lantern","message":{{"role":"user","content":"second fork"}}}}"#,
) + "\n"),
);
let rev = h.run(&["plan", "--reverse", expected.to_str().unwrap()]);
assert!(
rev.stdout.contains(SLUGSESS) && rev.stdout.contains(SLUGSESS2),
"reverse finds both slug-only binders:\n{}",
rev.stdout
);
let a = rev.stdout.find(SLUGSESS).unwrap();
let b = rev.stdout.find(SLUGSESS2).unwrap();
assert!(a < b, "deterministic id order:\n{}", rev.stdout);
std::fs::create_dir_all(proj.join(".claude")).unwrap();
std::fs::write(
proj.join(".claude").join("settings.local.json"),
r#"{"plansDirectory":"local-plans"}"#,
)
.unwrap();
let local = h.run(&["plan", &at(SLUGSESS)]);
let expected_local = proj.join("local-plans").join("quiet-harbor-lantern.md");
assert!(
local.stdout.contains(expected_local.to_str().unwrap()),
"project-local plansDirectory wins:\n{}",
local.stdout
);
std::fs::write(
proj.join(".claude").join("settings.local.json"),
r#"{"plansDirectory":"../escape-hatch"}"#,
)
.unwrap();
let escaped = h.run(&["plan", &at(SLUGSESS)]);
let fallback = h
.root
.join(".claude")
.join("plans")
.join("quiet-harbor-lantern.md");
assert!(
escaped.stdout.contains(fallback.to_str().unwrap())
&& !escaped.stdout.contains("escape-hatch"),
"an escaping plansDirectory falls back to the default plans dir:\n{}",
escaped.stdout
);
}
#[test]
fn plan_slug_over_length_never_binds() {
const LSESS: &str = "ffff4444-5555-4666-8777-888899990000";
let h = Home::new();
let long = "a".repeat(121);
h.write(
&format!("{ENC}/{LSESS}.jsonl"),
&format!(
"{}\n",
format_args!(
r#"{{"type":"user","uuid":"u1","timestamp":"2026-06-07T05:00:00.000Z","slug":"{long}","message":{{"role":"user","content":"work"}}}}"#
)
),
);
let out = h.run(&["plan", &at(LSESS)]);
assert!(
!out.stdout.contains("slug only"),
"a 121-char slug fails the validity rule and never binds:\n{}",
out.stdout
);
}
#[test]
fn plan_prefers_the_top_level_binding_over_a_subagent_one() {
const PSESS: &str = "abab4444-5555-4666-8777-888899990000";
let h = Home::new();
h.write(
&format!("{ENC}/{PSESS}.jsonl"),
concat!(
r#"{"type":"user","uuid":"u1","timestamp":"2026-06-07T05:00:00.000Z","message":{"role":"user","content":"plan it"}}"#, "\n",
r#"{"type":"attachment","isSidechain":false,"attachment":{"type":"plan_mode","reminderType":"full","isSubAgent":false,"planFilePath":"/plans/top-level-plan.md","planExists":false},"uuid":"att1","timestamp":"2026-06-07T05:00:01.000Z","userType":"external","entrypoint":"cli","cwd":"/p"}"#, "\n",
),
);
h.write(
&format!("{ENC}/{PSESS}/subagents/agent-abcdef0123456789.jsonl"),
concat!(
r#"{"type":"user","isSidechain":true,"agentId":"abcdef0123456789","timestamp":"2026-06-07T05:01:00.000Z","message":{"role":"user","content":"sub work"}}"#, "\n",
r#"{"type":"attachment","isSidechain":true,"attachment":{"type":"plan_mode","reminderType":"full","isSubAgent":true,"planFilePath":"/plans/subagent-plan.md","planExists":false},"uuid":"att2","timestamp":"2026-06-07T05:01:01.000Z","userType":"external","entrypoint":"cli","cwd":"/p"}"#, "\n",
),
);
let out = h.run(&["plan", &at(PSESS)]);
assert!(out.success, "stderr: {}", out.stderr);
assert!(
out.stdout.contains("top-level-plan.md"),
"the top-level lane's own plan wins:\n{}",
out.stdout
);
}