use super::*;
use crate::config::manifest::OverflowPolicy;
use tempfile::TempDir;
fn rules(pinned: &[&str], order: &[&str], budget: u32, overflow: OverflowPolicy) -> RoleRules {
RoleRules {
pinned: pinned.iter().map(|s| s.to_string()).collect(),
order: order.iter().map(|s| s.to_string()).collect(),
budget_tokens: budget,
overflow,
}
}
fn write(wt: &Path, rel: &str, bytes: &[u8]) {
let path = wt.join(rel);
std::fs::create_dir_all(path.parent().unwrap()).unwrap();
std::fs::write(path, bytes).unwrap();
}
fn paths(blocks: &[String]) -> Vec<String> {
blocks
.iter()
.map(|b| {
let start = b.find('"').unwrap() + 1;
b[start..b[start..].find('"').unwrap() + start].to_string()
})
.collect()
}
#[test]
fn no_manifest_rules_compose_nothing() {
let wt = TempDir::new().unwrap();
write(wt.path(), "summary/001.md", b"data");
assert!(compose(wt.path(), None).unwrap().is_empty());
}
#[test]
fn an_absent_worktree_composes_nothing() {
let r = rules(&[], &["**"], 100, OverflowPolicy::Drop);
let out = compose(Path::new("/no/such/worktree"), Some(&r)).unwrap();
assert!(out.is_empty());
}
#[test]
fn a_worktree_that_is_a_file_surfaces_io_error() {
let dir = TempDir::new().unwrap();
let file = dir.path().join("not-a-dir");
std::fs::write(&file, b"x").unwrap();
let r = rules(&[], &["**"], 100, OverflowPolicy::Drop);
assert!(matches!(
compose(&file, Some(&r)).unwrap_err(),
Error::Io(_)
));
}
#[test]
fn an_unreadable_selected_file_surfaces_io_error() {
let wt = TempDir::new().unwrap();
std::os::unix::fs::symlink("/no/such/target", wt.path().join("dangling.md")).unwrap();
let r = rules(&["dangling.md"], &[], 100, OverflowPolicy::Drop);
assert!(matches!(
compose(wt.path(), Some(&r)).unwrap_err(),
Error::Io(_)
));
}
#[test]
fn structurally_homed_trees_never_compose_as_body_text() {
let wt = TempDir::new().unwrap();
write(wt.path(), "goal.md", b"goal");
write(wt.path(), "soul.md", b"soul");
write(wt.path(), "messages/001-user.md", b"hi");
write(wt.path(), "descriptions/tools/bash.json", b"{}");
write(wt.path(), "descriptions/skills/bash.md", b"name: bash");
write(wt.path(), ".git/config", b"[core]");
write(wt.path(), "notes.md", b"kept");
let r = rules(&["**"], &[], 100, OverflowPolicy::Drop);
let out = compose(wt.path(), Some(&r)).unwrap();
assert_eq!(paths(&out), vec!["notes.md"]);
assert_eq!(out[0], "<file path=\"notes.md\">\nkept\n</file>");
}
#[test]
fn a_standalone_skills_description_composes_as_a_head_block() {
let wt = TempDir::new().unwrap();
write(wt.path(), "descriptions/tools/bash.json", b"{}");
write(wt.path(), "descriptions/skills/bash.md", b"tool");
write(wt.path(), "descriptions/skills/git.md", b"alone");
write(wt.path(), "descriptions/skills/README", b"n/a");
let r = rules(&["descriptions/**"], &[], 100, OverflowPolicy::Drop);
let out = compose(wt.path(), Some(&r)).unwrap();
let kept = ["descriptions/skills/README", "descriptions/skills/git.md"];
assert_eq!(paths(&out), kept);
}
#[test]
fn body_fills_in_category_order_lexical_within_each() {
let wt = TempDir::new().unwrap();
write(wt.path(), "skills/z/SKILL.md", b"zzzz");
write(wt.path(), "skills/a/SKILL.md", b"aaaa");
write(wt.path(), "summary/002.md", b"s2s2");
write(wt.path(), "summary/001.md", b"s1s1");
let r = rules(&[], &["summary/**", "skills/**"], 100, OverflowPolicy::Drop);
let out = compose(wt.path(), Some(&r)).unwrap();
assert_eq!(
paths(&out),
vec![
"summary/001.md",
"summary/002.md",
"skills/a/SKILL.md",
"skills/z/SKILL.md"
]
);
}
#[test]
fn a_pinned_file_never_reenters_through_order() {
let wt = TempDir::new().unwrap();
write(wt.path(), "notes.md", b"once");
let r = rules(&["notes.md"], &["**"], 100, OverflowPolicy::Drop);
let out = compose(wt.path(), Some(&r)).unwrap();
assert_eq!(paths(&out), vec!["notes.md"]);
}
#[test]
fn pinned_rides_over_budget_and_counts_toward_it() {
let wt = TempDir::new().unwrap();
write(wt.path(), "notes.md", b"12345678"); write(wt.path(), "docs/a.md", b"data");
let r = rules(&["notes.md"], &["docs/**"], 1, OverflowPolicy::Drop);
let out = compose(wt.path(), Some(&r)).unwrap();
assert_eq!(paths(&out), vec!["notes.md"]);
}
#[test]
fn a_fitting_body_passes_untouched() {
let wt = TempDir::new().unwrap();
write(wt.path(), "docs/a.md", b"data");
write(wt.path(), "docs/b.md", b"data");
let r = rules(&[], &["docs/**"], 2, OverflowPolicy::Drop);
assert_eq!(
paths(&compose(wt.path(), Some(&r)).unwrap()),
vec!["docs/a.md", "docs/b.md"]
);
}
#[test]
fn drop_oldest_summaries_sheds_lexically_first_until_the_body_fits() {
let wt = TempDir::new().unwrap();
write(wt.path(), "summary/001.md", b"12345678"); write(wt.path(), "summary/002.md", b"12345678"); write(wt.path(), "skills/a.md", b"data"); let r = rules(
&[],
&["summary/**", "skills/**"],
3,
OverflowPolicy::DropOldestSummaries,
);
let out = compose(wt.path(), Some(&r)).unwrap();
assert_eq!(paths(&out), vec!["summary/002.md", "skills/a.md"]);
}
#[test]
fn drop_oldest_summaries_with_none_left_lets_the_residue_ride() {
let wt = TempDir::new().unwrap();
write(wt.path(), "skills/a.md", b"12345678"); let r = rules(&[], &["skills/**"], 1, OverflowPolicy::DropOldestSummaries);
assert_eq!(
paths(&compose(wt.path(), Some(&r)).unwrap()),
vec!["skills/a.md"]
);
}
#[test]
fn truncate_cuts_the_overflowing_entry_at_a_char_boundary_and_stops() {
let wt = TempDir::new().unwrap();
write(wt.path(), "docs/a.md", b"12345678"); write(wt.path(), "docs/b.md", "abc\u{e9}xxxx".as_bytes()); write(wt.path(), "docs/c.md", b"data"); let r = rules(&[], &["docs/**"], 3, OverflowPolicy::Truncate);
let out = compose(wt.path(), Some(&r)).unwrap();
assert_eq!(
out,
vec![
"<file path=\"docs/a.md\">\n12345678\n</file>".to_string(),
"<file path=\"docs/b.md\">\nabc\n</file>".to_string(),
]
);
}
#[test]
fn truncate_with_no_allowance_left_drops_the_overflowing_entry() {
let wt = TempDir::new().unwrap();
write(wt.path(), "docs/a.md", b"12345678"); write(wt.path(), "docs/b.md", b"data");
let r = rules(&[], &["docs/**"], 2, OverflowPolicy::Truncate);
assert_eq!(
paths(&compose(wt.path(), Some(&r)).unwrap()),
vec!["docs/a.md"]
);
}
#[test]
fn drop_stops_filling_at_the_first_entry_that_does_not_fit() {
let wt = TempDir::new().unwrap();
write(wt.path(), "docs/a.md", b"12345678"); write(wt.path(), "docs/b.md", b"12345678"); write(wt.path(), "docs/c.md", b"data"); let r = rules(&[], &["docs/**"], 3, OverflowPolicy::Drop);
assert_eq!(
paths(&compose(wt.path(), Some(&r)).unwrap()),
vec!["docs/a.md"]
);
}
#[test]
fn non_utf8_content_composes_lossily() {
let wt = TempDir::new().unwrap();
write(wt.path(), "blob.bin", &[0xff, b'o', b'k']);
let r = rules(&["blob.bin"], &[], 100, OverflowPolicy::Drop);
let out = compose(wt.path(), Some(&r)).unwrap();
assert!(out[0].contains("\u{fffd}ok"));
}
#[test]
fn glob_vocabulary_literals_star_and_doublestar() {
for (pattern, path, expect) in [
("goal.md", "goal.md", true),
("goal.md", "soul.md", false),
("*.md", "notes.md", true),
("*.md", "docs/notes.md", false),
("docs/*", "docs/a.md", true),
("docs/*", "docs/sub/a.md", false),
("summary/**", "summary/001.md", true),
("summary/**", "summary/a/b.md", true),
("summary/**", "skills/a.md", false),
("**/SKILL.md", "skills/x/SKILL.md", true),
("**/SKILL.md", "skills/x/notes.md", false),
("a*c", "abc", true),
("a*c", "ac", true),
("a*c", "abd", false),
("a*b*c", "aXbYc", true),
("abc", "ab", false),
] {
assert_eq!(
glob_match(pattern, path),
expect,
"{pattern} vs {path} should be {expect}"
);
}
}