Add rulespec extensions: new predicate rules, when conditions, null handling, solon agent

Features:
- New predicate rules: NotContains, AnyOf, NoneOf
- Conditional predicates via when clauses (WhenCondition/CompiledWhenCondition)
- Null handling: YAML null treated as absent for exists/not_exists
- Solon agent for rulespec authoring (agents/solon.md)
- Rulespec schema documentation (prompts/schemas/rulespec.schema.md)

Bugfix:
- Fixed when condition evaluation in datalog path: catch-all branch did
  naive string contains instead of delegating to evaluate_predicate_datalog().
  Rules like matches (regex) were silently ignored, causing vacuous pass
  and letting violations through. Now delegates to evaluate_predicate_datalog()
  which handles all 12 rule types correctly.

Tests: 34 new tests covering all new rules, null handling, when conditions,
and the when+matches bugfix (butler rulespec pattern).
This commit is contained in:
Dhanji R. Prasanna
2026-02-07 16:38:27 +11:00
parent 328eecfcad
commit edbae60ff3
8 changed files with 1958 additions and 16 deletions

View File

@@ -22,6 +22,7 @@ static EMBEDDED_AGENTS: &[(&str, &str)] = &[
("huffman", include_str!("../../../agents/huffman.md")),
("lamport", include_str!("../../../agents/lamport.md")),
("scout", include_str!("../../../agents/scout.md")),
("solon", include_str!("../../../agents/solon.md")),
];
/// Get an embedded agent prompt by name.
@@ -89,7 +90,7 @@ mod tests {
#[test]
fn test_embedded_agents_exist() {
// Verify all expected agents are embedded
let expected = ["breaker", "carmack", "euler", "fowler", "hopper", "huffman", "lamport", "scout"];
let expected = ["breaker", "carmack", "euler", "fowler", "hopper", "huffman", "lamport", "scout", "solon"];
for name in expected {
assert!(
get_embedded_agent(name).is_some(),
@@ -102,7 +103,7 @@ mod tests {
#[test]
fn test_list_embedded_agents() {
let agents = list_embedded_agents();
assert!(agents.len() >= 8, "Should have at least 8 embedded agents");
assert!(agents.len() >= 9, "Should have at least 9 embedded agents");
assert!(agents.contains(&"carmack"));
assert!(agents.contains(&"hopper"));
}