pub mod checkpoint;
pub mod merge;
pub mod tools;
pub use checkpoint::{due, state};
pub use merge::merge;
use super::Error;
use brazen::Tool;
use serde_json::json;
pub const COMPACTOR_ROLE: &str = "compactor";
pub fn builtin_tool_schemas() -> Vec<Tool> {
vec![
Tool::Custom {
name: tools::WRITE_SUMMARY.to_string(),
description: Some(
"Write a signal-preserving summary to the next summary/<NNN>.md \
on this branch."
.to_string(),
),
input_schema: json!({
"type": "object",
"properties": {
"content": {
"type": "string",
"description": "The summary body, written verbatim."
}
},
"required": ["content"],
"additionalProperties": false
}),
strict: None,
},
Tool::Custom {
name: tools::MARK_FOR_DELETION.to_string(),
description: Some(
"Nominate a branch-relative path for removal (deletion-only: this \
can remove, never rewrite)."
.to_string(),
),
input_schema: json!({
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "Branch-relative path to remove, e.g. messages/003-user.md."
}
},
"required": ["path"],
"additionalProperties": false
}),
strict: None,
},
]
}
pub fn refusal(role: &str, tool: &str) -> Option<String> {
if role != COMPACTOR_ROLE || tool == tools::WRITE_SUMMARY || tool == tools::MARK_FOR_DELETION {
return None;
}
Some(format!(
"{tool:?} is not callable by a compactor: it is declared only \
because the inherited transcript references it. The compactor \
toolset is {} and {} (ARCH §2.7, deletion-only).",
tools::WRITE_SUMMARY,
tools::MARK_FOR_DELETION,
))
}
pub fn compactor_goal(parent_branch: &str) -> String {
format!(
"You are the compactor for branch `{parent_branch}`.\n\
\n\
Read the branch's transcript, prior summaries under `summary/`, and\n\
work products, and produce a signal-preserving, minimal view of the\n\
branch's history using the `write_summary` tool. The harness writes it\n\
to the next `summary/<NNN>.md` on this branch.\n\
\n\
Use `mark_for_deletion` to nominate superseded files — stale transcript\n\
entries under `messages/`, a prior `summary/` you are replacing, spent\n\
`skills/` bodies. Your toolset is deletion-only: you can remove and\n\
summarize, never rewrite, so the worst case is lost information, never\n\
corrupted information. A work product the live branch has rewritten\n\
since you forked is kept regardless of your nomination (live-branch-wins).\n\
\n\
Decide relevance against the branch's goal at `goal.md`.\n"
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn compactor_goal_names_the_branch_and_both_tools() {
let g = compactor_goal("20260101-p1");
assert!(g.contains("`20260101-p1`"), "{g}");
assert!(g.contains("write_summary"));
assert!(g.contains("mark_for_deletion"));
assert!(g.contains("goal.md"));
assert!(g.contains("summary/<NNN>.md"));
assert!(g.contains("live-branch-wins"));
}
#[test]
fn compactor_role_is_the_soul_key() {
assert_eq!(COMPACTOR_ROLE, "compactor");
}
#[test]
fn builtin_tool_schemas_are_the_two_named_tools_with_required_inputs() {
let schemas = builtin_tool_schemas();
let custom = |t: &Tool| match t {
Tool::Custom {
name,
description,
input_schema,
..
} => (name.clone(), description.clone(), input_schema.clone()),
_ => panic!("builtin tools are Custom"),
};
let parts: Vec<_> = schemas.iter().map(custom).collect();
let names: Vec<&str> = parts.iter().map(|(n, _, _)| n.as_str()).collect();
assert_eq!(names, vec![tools::WRITE_SUMMARY, tools::MARK_FOR_DELETION]);
assert!(parts.iter().all(|(_, d, _)| d.is_some()));
assert_eq!(parts[0].2["required"][0], "content");
assert_eq!(parts[1].2["required"][0], "path");
}
#[test]
fn a_compactor_may_call_its_own_two_tools_and_nothing_else() {
assert_eq!(refusal(COMPACTOR_ROLE, tools::WRITE_SUMMARY), None);
assert_eq!(refusal(COMPACTOR_ROLE, tools::MARK_FOR_DELETION), None);
let declined = refusal(COMPACTOR_ROLE, "bash").expect("a foreign tool is declined");
assert!(declined.contains("\"bash\""), "{declined}");
assert!(declined.contains(tools::WRITE_SUMMARY), "{declined}");
assert!(declined.contains(tools::MARK_FOR_DELETION), "{declined}");
}
#[test]
fn every_other_role_calls_what_it_emits() {
assert_eq!(refusal("worker", "bash"), None);
assert_eq!(refusal("verifier", tools::WRITE_SUMMARY), None);
}
}