use super::*;
use crate::prompt::dispatch::assembler;
use brazen::Content;
fn prompt_bytes(wt: &Path) -> usize {
let messages = assembler::assemble(wt, None).unwrap().messages;
serde_json::to_vec(&messages).unwrap().len()
}
fn talkative(n: u32) -> TempDir {
let dir = repo(&[("messages/001-user.md", "the opening prompt\n")]);
let wt = dir.path();
for seq in 2..=n {
let body = format!("entry {seq}: {}\n", "chatter ".repeat(40));
commit(
wt,
&format!("step {seq:03}"),
&[(&format!("messages/{seq:03}-user.md"), body.as_str())],
&[],
);
}
dir
}
fn entries(wt: &Path) -> Vec<String> {
let mut found: Vec<String> = std::fs::read_dir(wt.join("messages"))
.map(|rd| {
rd.filter_map(|e| e.ok())
.map(|e| e.file_name().to_string_lossy().into_owned())
.collect()
})
.unwrap_or_default();
found.sort();
found
}
#[test]
fn a_landed_compaction_shrinks_the_assembled_prompt() {
let dir = talkative(9);
let wt = dir.path();
let before = prompt_bytes(wt);
compactor(
wt,
&[("summary/001.md", "we talked about widgets\n")],
&[],
&[],
&[],
);
assert_eq!(
land(wt, "p1", "p1-cmp", None, &g()).unwrap(),
LandOutcome::Landed
);
let after = prompt_bytes(wt);
assert!(
after < before,
"a landed compaction must shrink the prompt: {before} -> {after}"
);
assert_eq!(entries(wt), vec!["001-user.md".to_string()]);
assert!(wt.join("summary/001.md").exists(), "the summary landed");
assert_eq!(g().run_capture(wt, &["status", "--porcelain"]).unwrap(), "");
}
#[test]
fn the_live_tail_past_the_compaction_point_is_never_swept() {
let dir = talkative(4);
let wt = dir.path();
compactor(wt, &[("summary/001.md", "digest\n")], &[], &[], &[]);
commit(wt, "step 005", &[("messages/005-user.md", "live\n")], &[]);
assert_eq!(
land(wt, "p1", "p1-cmp", None, &g()).unwrap(),
LandOutcome::Landed
);
assert_eq!(
entries(wt),
vec!["001-user.md".to_string(), "005-user.md".to_string()]
);
}
#[test]
fn a_pass_that_wrote_no_summary_sweeps_nothing() {
let dir = talkative(4);
let wt = dir.path();
compactor(wt, &[], &["messages/003-user.md"], &[], &[]);
assert_eq!(
land(wt, "p1", "p1-cmp", None, &g()).unwrap(),
LandOutcome::Landed
);
assert_eq!(
entries(wt),
vec![
"001-user.md".to_string(),
"002-user.md".to_string(),
"004-user.md".to_string()
]
);
}
#[test]
fn the_extract_reports_the_swept_span_not_only_what_was_nominated() {
let dir = repo(&[
("messages/001-user.md", "the opening prompt\n"),
("messages/002-user.md", "ship the widget\n"),
]);
let wt = dir.path();
compactor(wt, &[("summary/001.md", "digest\n")], &[], &[], &[]);
assert_eq!(
land(wt, "p1", "p1-cmp", Some(4096), &g()).unwrap(),
LandOutcome::Landed
);
let refs = std::fs::read_to_string(wt.join("summary/001.refs.md")).unwrap();
assert!(refs.contains("ship the widget"), "{refs}");
assert!(
!refs.contains("the opening prompt"),
"the dispatch entry never leaves context: {refs}"
);
}
fn call_entry(id: &str) -> String {
serde_json::to_string(&[Content::ToolUse {
id: id.to_string(),
name: "bash".into(),
input: serde_json::json!({"command": "true"}),
signature: None,
}])
.unwrap()
}
fn result_entry(id: &str) -> String {
serde_json::to_string(&[Content::ToolResult {
tool_use_id: id.to_string(),
content: vec![Content::Text("ok".into())],
is_error: false,
}])
.unwrap()
}
fn orphans(wt: &Path) -> Vec<String> {
let mut called = std::collections::HashSet::new();
let mut orphaned = Vec::new();
for name in entries(wt).iter().filter(|n| n.ends_with(".json")) {
let Ok(bytes) = std::fs::read(wt.join("messages").join(name)) else {
continue;
};
for b in crate::prompt::dispatch::entry::blocks(&bytes) {
match b {
Content::ToolUse { id, .. } => {
called.insert(id);
}
Content::ToolResult { tool_use_id, .. } if !called.contains(&tool_use_id) => {
orphaned.push(tool_use_id);
}
_ => {}
}
}
}
orphaned
}
#[test]
fn a_compaction_point_inside_a_tool_window_never_orphans_the_result() {
let dir = repo(&[("messages/001-user.md", "the opening prompt\n")]);
let wt = dir.path();
commit(
wt,
"step 002",
&[("messages/002-m.json", call_entry("call_1").as_str())],
&[],
);
compactor(wt, &[("summary/001.md", "digest\n")], &[], &[], &[]);
commit(
wt,
"step 003",
&[("messages/003-tool.json", result_entry("call_1").as_str())],
&[],
);
assert_eq!(
land(wt, "p1", "p1-cmp", None, &g()).unwrap(),
LandOutcome::Landed
);
assert_eq!(orphans(wt), Vec::<String>::new());
assert_eq!(
entries(wt),
vec![
"001-user.md".to_string(),
"002-m.json".to_string(),
"003-tool.json".to_string()
]
);
}
#[test]
fn a_settled_window_inside_the_span_is_swept_whole() {
let dir = repo(&[("messages/001-user.md", "the opening prompt\n")]);
let wt = dir.path();
commit(
wt,
"step 002",
&[("messages/002-m.json", call_entry("call_1").as_str())],
&[],
);
commit(
wt,
"step 003",
&[("messages/003-tool.json", result_entry("call_1").as_str())],
&[],
);
compactor(wt, &[("summary/001.md", "digest\n")], &[], &[], &[]);
assert_eq!(
land(wt, "p1", "p1-cmp", None, &g()).unwrap(),
LandOutcome::Landed
);
assert_eq!(entries(wt), vec!["001-user.md".to_string()]);
}