use serde_json::json;
use crate::error::Result;
const GW_BIN: &str = "gw";
pub fn guard_settings_json() -> Result<String> {
let payload = json!({
"hooks": {
"PreToolUse": [
pre_tool_use_bash_entry()?
]
}
});
Ok(payload.to_string())
}
pub fn pre_tool_use_bash_entry() -> Result<serde_json::Value> {
Ok(json!({
"matcher": "Bash",
"hooks": [
{ "type": "command", "command": format!("{GW_BIN} guard --tool-input -") }
]
}))
}
pub fn worktree_create_entry() -> Result<serde_json::Value> {
Ok(json!({
"hooks": [
{ "type": "command", "command": format!("{GW_BIN} _claude-worktree-create") }
]
}))
}
pub fn worktree_remove_entry() -> Result<serde_json::Value> {
Ok(json!({
"hooks": [
{ "type": "command", "command": format!("{GW_BIN} _claude-worktree-remove") }
]
}))
}
pub(crate) fn is_legacy_gw_command(command: &str, suffix: &str) -> bool {
if !command.ends_with(suffix) {
return false;
}
let prefix = command.trim_end_matches(suffix).trim_end();
!prefix.is_empty() && prefix != GW_BIN
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::Value;
fn parse(json: &str) -> Value {
serde_json::from_str(json).expect("valid json")
}
#[test]
fn payload_has_pretooluse_bash_hook() {
let s = guard_settings_json().expect("ok");
let v = parse(&s);
let arr = v["hooks"]["PreToolUse"]
.as_array()
.expect("PreToolUse array");
assert_eq!(arr.len(), 1);
assert_eq!(arr[0]["matcher"], "Bash");
let inner = arr[0]["hooks"].as_array().expect("inner hooks");
assert_eq!(inner.len(), 1);
assert_eq!(inner[0]["type"], "command");
let cmd = inner[0]["command"].as_str().expect("command str");
assert_eq!(cmd, "gw guard --tool-input -");
}
#[test]
fn pre_tool_use_bash_entry_is_bare_gw() {
let entry = pre_tool_use_bash_entry().expect("ok");
assert_eq!(entry["matcher"], "Bash");
let inner = entry["hooks"].as_array().expect("inner hooks array");
assert_eq!(inner[0]["type"], "command");
assert_eq!(inner[0]["command"], "gw guard --tool-input -");
}
#[test]
fn worktree_create_entry_is_bare_gw() {
let entry = worktree_create_entry().expect("ok");
assert!(entry.get("matcher").is_none());
let inner = entry["hooks"].as_array().expect("inner hooks array");
assert_eq!(inner[0]["type"], "command");
assert_eq!(inner[0]["command"], "gw _claude-worktree-create");
}
#[test]
fn worktree_remove_entry_is_bare_gw() {
let entry = worktree_remove_entry().expect("ok");
assert!(entry.get("matcher").is_none());
let inner = entry["hooks"].as_array().expect("inner hooks array");
assert_eq!(inner[0]["type"], "command");
assert_eq!(inner[0]["command"], "gw _claude-worktree-remove");
}
#[test]
fn is_legacy_recognizes_absolute_paths() {
assert!(is_legacy_gw_command(
"/usr/local/bin/gw guard --tool-input -",
" guard --tool-input -",
));
assert!(is_legacy_gw_command(
"/opt/homebrew/bin/gw _claude-worktree-create",
" _claude-worktree-create",
));
assert!(is_legacy_gw_command(
"/Users/dave/proj/target/release/gw _claude-worktree-remove",
" _claude-worktree-remove",
));
}
#[test]
fn is_legacy_rejects_current_bare_form() {
assert!(!is_legacy_gw_command(
"gw guard --tool-input -",
" guard --tool-input -",
));
assert!(!is_legacy_gw_command(
"gw _claude-worktree-create",
" _claude-worktree-create",
));
}
#[test]
fn is_legacy_rejects_unrelated_commands() {
assert!(!is_legacy_gw_command(
"/usr/local/bin/some-other-tool guard --tool-input -",
" _claude-worktree-create", ));
assert!(!is_legacy_gw_command("", " guard --tool-input -"));
}
}