diff --git a/.claude/hooks/guard-process-kill.selftest.sh b/.claude/hooks/guard-process-kill.selftest.sh new file mode 100755 index 0000000000..42729cb262 --- /dev/null +++ b/.claude/hooks/guard-process-kill.selftest.sh @@ -0,0 +1,220 @@ +#!/usr/bin/env bash +# Self-test for guard-process-kill.sh — run it after touching that hook: +# +# .claude/hooks/guard-process-kill.selftest.sh +# +# Feeds the hook the same JSON payload shape Claude Code delivers on PreToolUse and asserts +# the block/allow verdict per command. Needs jq (to build payloads) and nothing else: no +# install, no build, no network. Exit 0 = all cases hold. +# +# The harness is guard-shared-stash.selftest.sh's, one-for-one — same verdict(), expect(), +# stderr_of(), says() and lacks() — because this guard is that guard's shape applied to the +# other shared object. The case matrix is this guard's own, and it pins BOTH sides on +# purpose: a guard for a class this wide is worth nothing if the PID-scoped teardown the +# repo already prescribes comes back red. + +set -uo pipefail + +here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +hook="$here/guard-process-kill.sh" +pass=0 +fail=0 + +command -v jq >/dev/null 2>&1 || { echo "selftest needs jq to build payloads" >&2; exit 1; } + +# verdict [env assignments…] -> prints "block" or "allow" +verdict() { + local cmd="$1"; shift + local payload out rc + payload="$(jq -nc --arg c "$cmd" '{tool_name:"Bash",tool_input:{command:$c}}')" + out="$(printf '%s' "$payload" | env "$@" "$hook" 2>/dev/null)" + rc=$? + case "$rc" in + 0) printf 'allow' ;; + 2) printf 'block' ;; + *) printf 'exit%s' "$rc" ;; + esac +} + +expect() { # expect [env…] + local want="$1" cmd="$2"; shift 2 + local got; got="$(verdict "$cmd" "$@")" + if [ "$got" = "$want" ]; then + pass=$((pass + 1)); printf ' ok %-5s %s\n' "$got" "$cmd" + else + fail=$((fail + 1)); printf ' FAIL want=%s got=%s %s\n' "$want" "$got" "$cmd" + fi +} + +stderr_of() { # stderr_of [env…] -> the refusal text an agent actually reads + local cmd="$1"; shift + local payload + payload="$(jq -nc --arg c "$cmd" '{tool_name:"Bash",tool_input:{command:$c}}')" + printf '%s' "$payload" | env "$@" "$hook" 2>&1 >/dev/null +} + +says() { # says