use super::*;
use crate::prompt::compactor::state;
use crate::template::{GitRunner, RealGit};
use tempfile::TempDir;
fn g() -> RealGit {
RealGit::new()
}
fn repo(files: &[(&str, &str)]) -> TempDir {
let dir = TempDir::new().unwrap();
let wt = dir.path();
let git = g();
git.run(wt, &["init", "-b", "agents/p1"]).unwrap();
git.run(wt, &["config", "user.email", "t@t"]).unwrap();
git.run(wt, &["config", "core.hooksPath", "/dev/null"])
.unwrap();
git.run(wt, &["config", "user.name", "t"]).unwrap();
commit(wt, "step 001: dispatch [p1]", files, &[]);
dir
}
fn write(wt: &Path, rel: &str, content: &str) {
let path = wt.join(rel);
std::fs::create_dir_all(path.parent().unwrap()).unwrap();
std::fs::write(path, content).unwrap();
}
fn commit(wt: &Path, subject: &str, writes: &[(&str, &str)], deletes: &[&str]) {
let git = g();
for (rel, content) in writes {
write(wt, rel, content);
}
for rel in deletes {
git.run(wt, &["rm", "-q", "--", rel]).unwrap();
}
git.run(wt, &["add", "-A"]).unwrap();
git.run(wt, &["commit", "--allow-empty", "-m", subject])
.unwrap();
}
fn compactor(
wt: &Path,
summary: &[(&str, &str)],
deletions: &[&str],
dialog: &[(&str, &str)],
prune: &[&str],
) {
let git = g();
git.run(wt, &["checkout", "-q", "-b", "agents/p1-cmp"])
.unwrap();
commit(
wt,
"dispatch: compactor [p1-cmp]",
&[("goal.md", "compact the branch\n")],
prune,
);
let writes: Vec<(&str, &str)> = summary.iter().chain(dialog).copied().collect();
commit(wt, "compaction", &writes, deletions);
git.run(wt, &["checkout", "-q", "agents/p1"]).unwrap();
}
fn head(wt: &Path) -> String {
g().run_capture(wt, &["rev-parse", "HEAD"]).unwrap()
}
fn subjects(wt: &Path) -> String {
g().run_capture(wt, &["log", "--format=%s"]).unwrap()
}
#[test]
fn a_landing_squashes_the_span_and_replays_the_live_tail() {
let dir = repo(&[("messages/001-user.md", "hi\n")]);
let wt = dir.path();
commit(wt, "step 002", &[("messages/002-a.md", "a\n")], &[]);
commit(wt, "step 003", &[("messages/003-b.md", "b\n")], &[]);
compactor(
wt,
&[("summary/001.md", "digest\n")],
&["messages/001-user.md"],
&[],
&[],
);
commit(wt, "step 004", &[("messages/004-c.md", "c\n")], &[]);
assert_eq!(
land(wt, "p1", "p1-cmp", None, &g()).unwrap(),
LandOutcome::Landed
);
assert!(!wt.join("messages/001-user.md").exists(), "deletion landed");
assert!(wt.join("summary/001.md").exists(), "summary landed");
assert!(wt.join("messages/004-c.md").exists(), "live append kept");
let log = subjects(wt);
assert_eq!(
log.lines().collect::<Vec<_>>(),
vec![
"step 004",
"compaction base [p1-cmp]",
"step 001: dispatch [p1]"
],
"{log}"
);
assert!(
g().run_capture(wt, &["rev-list", "--merges", "HEAD"])
.unwrap()
.trim()
.is_empty(),
"nothing merges anywhere (§2.6)"
);
assert_eq!(
g().run_capture(wt, &["symbolic-ref", "--short", "HEAD"])
.unwrap(),
"agents/p1"
);
assert_eq!(g().run_capture(wt, &["status", "--porcelain"]).unwrap(), "");
let s = state(wt, "p1", 0, false, &g()).unwrap();
assert_eq!(s.commits_since_checkpoint, 1, "the replayed tail counts");
assert!(
g().run_capture(
wt,
&["cat-file", "-e", "agents/p1-cmp~1:messages/001-user.md"]
)
.is_ok()
);
}
#[test]
fn live_branch_wins_on_a_work_product_the_compaction_deleted() {
let dir = repo(&[("code.txt", "v1\n")]);
let wt = dir.path();
compactor(
wt,
&[("summary/001.md", "digest\n")],
&["code.txt"],
&[],
&[],
);
commit(wt, "step 002 rewrite", &[("code.txt", "v2\n")], &[]);
assert_eq!(
land(wt, "p1", "p1-cmp", None, &g()).unwrap(),
LandOutcome::Landed
);
assert_eq!(
std::fs::read_to_string(wt.join("code.txt")).unwrap(),
"v2\n",
"live-branch-wins: the rewritten work product survives"
);
assert!(wt.join("summary/001.md").exists(), "summary still landed");
assert_eq!(g().run_capture(wt, &["status", "--porcelain"]).unwrap(), "");
}
fn assert_op(err: Error, want: &str) {
match err {
Error::Git { op, .. } => assert_eq!(op, want),
other => panic!("{other:?}"),
}
}
mod edges;
mod extract;
mod reclaim;
mod stub;