From 03efb67c80cc0f0af0bb954f280720e3c8ceda1e Mon Sep 17 00:00:00 2001 From: lazy Date: Tue, 15 Sep 2026 18:27:21 -0400 Subject: [PATCH] Require single-handler legacy Claude hook entries --- .../src/activation/AGENTS.md | 2 +- .../src/activation/bridge.rs | 18 ++++++++++++++++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/crates/tree-ring-memory-cli/src/activation/AGENTS.md b/crates/tree-ring-memory-cli/src/activation/AGENTS.md index 9517e8d..03234cb 100644 --- a/crates/tree-ring-memory-cli/src/activation/AGENTS.md +++ b/crates/tree-ring-memory-cli/src/activation/AGENTS.md @@ -21,7 +21,7 @@ runtime and minor series. Coordinate the core allowlist and plugin descriptor before publishing either release; verify the actual pair with CLI activation, preflight, and receipt-backed status. A passing version probe alone is insufficient. -Recognize earlier Claude handler bundles only by exact recorded ownership, commands, and generated entry shape. Preserve custom handlers and settings; bridge reconciliation still follows create-only publication and cannot silently replace existing hooks or manifests. +Recognize earlier Claude handler bundles only by exact recorded ownership, commands, and generated single-handler entry shape. Preserve custom handlers and settings; bridge reconciliation still follows create-only publication and cannot silently replace existing hooks or manifests. When creating root AGENTS.md, record ownership of only the marked Tree Ring block so surrounding project instructions remain editable. Preserve legacy complete-file ownership until explicitly reconciled; never migrate an existing activation manifest automatically. diff --git a/crates/tree-ring-memory-cli/src/activation/bridge.rs b/crates/tree-ring-memory-cli/src/activation/bridge.rs index f495597..14b94bd 100644 --- a/crates/tree-ring-memory-cli/src/activation/bridge.rs +++ b/crates/tree-ring-memory-cli/src/activation/bridge.rs @@ -2591,7 +2591,11 @@ fn replace_exact_claude_handlers( .is_some_and(|handlers| handlers.contains(expected_handler)); if contains_expected && (entry.get("matcher").and_then(Value::as_str) != Some("") - || entry.as_object().is_none_or(|object| object.len() != 2)) + || entry.as_object().is_none_or(|object| object.len() != 2) + || entry + .get("hooks") + .and_then(Value::as_array) + .is_none_or(|handlers| handlers.len() != 1)) { return Ok(false); } @@ -3938,7 +3942,14 @@ mod tests { #[test] fn previous_v4_claude_replacement_rejects_modified_or_duplicate_handlers() { - for change in ["custom", "duplicate", "missing", "matcher", "metadata"] { + for change in [ + "custom", + "duplicate", + "missing", + "matcher", + "metadata", + "mixed", + ] { let mut root = json!({"hooks": {}}).as_object().unwrap().clone(); for (event, handler) in previous_v4_claude_handlers() { root["hooks"].as_object_mut().unwrap().insert( @@ -3960,6 +3971,9 @@ mod tests { handlers[0]["command"] = json!("echo custom tree-ring --harness claude-code") } "duplicate" => handlers.push(handlers[0].clone()), + "mixed" => handlers.push(json!({ + "type": "command", "command": "echo preserve-user-hook", "timeout": 3 + })), "missing" => { handlers.clear(); }