Read the script
#!/usr/bin/env bash
set -u
export LC_ALL=C
m="${1:-}"
MAX_HOOK_INPUT_BYTES=4000000
HOT_CONTEXT_BYTES=9000
CHECKPOINT_CONTEXT_BYTES=3000
RUN_CONTEXT_BYTES=3072
in="$(head -c $((MAX_HOOK_INPUT_BYTES+1)) 2>/dev/null || true)"
esc(){ LC_ALL=C tr -d '\000-\010\013\014\016-\037'|awk 'BEGIN{ORS=""}{gsub(/\\/,"\\\\");gsub(/"/,"\\\"");gsub(/\t/,"\\t");gsub(/\r/,"\\r");if(NR>1)printf "\\n";printf "%s",$0}'; }
ctx(){ [ -n "$2" ] || exit 0; b="$2"; [ "${#b}" -gt 24000 ]&&b="${b:0:24000}...[truncated]"; e="$(printf "%s" "$b"|esc)"; printf '{"hookSpecificOutput":{"hookEventName":"%s","additionalContext":"%s"}}\n' "$1" "$e"; }
block(){ r="$(printf "%s" "$1"|esc)"; printf '{"decision":"block","reason":"%s"}\n' "$r"; exit 0; }
deny(){ r="$(printf "%s" "$1"|esc)"; printf '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"%s"}}\n' "$r"; exit 0; }
if [ "${#in}" -gt "$MAX_HOOK_INPUT_BYTES" ]; then
case "$m" in pre-tool-use) deny "Hook input exceeds the bounded validation limit.";; post-tool-use|post-tool-failure|post-tool-batch|stop) block "Hook input exceeds the bounded validation limit.";; *) exit 0;; esac
fi
jg(){ if command -v jq >/dev/null 2>&1; then printf "%s" "$in"|jq -r "$1 // empty" 2>/dev/null; elif command -v python3 >/dev/null 2>&1; then printf "%s" "$in"|python3 -c 'import sys,json
try:
d=json.load(sys.stdin)
except Exception:
sys.exit(0)
for k in sys.argv[1].lstrip(".").replace("?","").split("."):
if isinstance(d,dict) and k in d: d=d[k]
else: sys.exit(0)
if isinstance(d,str): print(d)
elif isinstance(d,bool): print(str(d).lower())
elif isinstance(d,(int,float)): print(d)' "$1"; else k="$(printf "%s" "$1"|sed 's/.*\.//;s/[? ].*//')"; v="$(printf "%s" "$in"|tr '\n' ' '|grep -o "\"$k\"[[:space:]]*:[[:space:]]*\"[^\"]*\""|head -1|sed "s/^\"$k\"[[:space:]]*:[[:space:]]*\"\(.*\)\"$/\1/")"; [ -n "$v" ] || v="$(printf "%s" "$in"|tr '\n' ' '|grep -oE "\"$k\"[[:space:]]*:[[:space:]]*(true|false|-?[0-9][0-9.]*)"|head -1|sed -E "s/^\"$k\"[[:space:]]*:[[:space:]]*//")"; printf "%s\n" "$v"; fi; }
root(){ r="${CLAUDE_PROJECT_DIR:-}"; [ -n "$r" ] || r="$(jg '.cwd')"; [ -n "$r" ] || r="$(pwd)"; (cd "$r" 2>/dev/null && pwd -P) || return 1; }
sf(){ rt="$1"; raw="$2"; [ -n "$raw" ] || return 1; case "$raw" in /*) p="$raw";; *) p="$rt/$raw";; esac; d="$(dirname "$p")"; b="$(basename "$p")"; ad="$(cd "$d" 2>/dev/null && pwd -P)" || return 1; a="$ad/$b"; [ ! -L "$a" ] || return 1; case "$a" in "$rt"/*) printf "%s\n" "$a";; *) return 1;; esac; }
bf(){ base="$1"; rel="$2"; [ ! -L "$base" ]||return 1; p="$base"; IFS=/ read -r -a parts <<< "$rel"; for part in "${parts[@]}"; do [ -n "$part" ]&&[ "$part" != "." ]&&[ "$part" != ".." ]||return 1; p="$p/$part"; [ ! -L "$p" ]||return 1; done; d="$(dirname "$p")"; bd="$(cd "$base" 2>/dev/null&&pwd -P)"||return 1; ad="$(cd "$d" 2>/dev/null&&pwd -P)"||return 1; a="$ad/$(basename "$p")"; case "$a" in "$bd"/*) printf "%s\n" "$a";; *) return 1;; esac; }
mf(){ rt="$1"; [ ! -L "$rt/memory" ]||return 1; bf "$rt/memory" "$2"; }
sr(){ LC_ALL=C tr -d '\000-\010\013\014\016-\037' < "$1" 2>/dev/null|awk -v n="$2" 'NR>n{exit}{x=$0;l=tolower(x);if(l~/(^|[^a-z])(system|developer|assistant):|ignore (previous|all previous)|disregard previous instructions|follow these instructions|treat this as.*system|you are chatgpt|reveal (the |your )?(secret|system prompt)|<\/?system>|<\/?developer>|<\/?assistant>|tooluse|tool_use)/)x="[redacted directive-like project record line]";if(l~/^project:[[:space:]]*(\/|\.\.)/)x="project: [redacted invalid project scope]";if(length(x)>500)x=substr(x,1,500)"...";print x}'|head -c "$3"; }
tjd(){ date +%Y-%m-%d 2>/dev/null|awk -F- 'NF==3{a=int((14-$2)/12);yy=$1+4800-a;mm=$2+12*a-3;print $3+int((153*mm+2)/5)+365*yy+int(yy/4)-int(yy/100)+int(yy/400)-32045}'; }
sod(){ awk -v t="$2" -v thr="$3" 'function j(y,m,d, a,yy,mm){a=int((14-m)/12);yy=y+4800-a;mm=m+12*a-3;return d+int((153*mm+2)/5)+365*yy+int(yy/4)-int(yy/100)+int(yy/400)-32045} {s=$0;while(match(s,/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/)){dd=substr(s,RSTART,10);s=substr(s,RSTART+RLENGTH);split(dd,p,"-");if(p[2]>=1&&p[2]<=12&&p[3]>=1&&p[3]<=31){v=j(p[1],p[2],p[3]);if(mn==""||v<mn){mn=v;md=dd}}}} END{if(mn!=""){a=t-mn;if(a>thr)print md" "a}}' "$1"; }
vaf(){ rt="$1"; f="$2"; rel="${f#"$rt"/}"; [ -f "$f" ] || return 0; [ ! -L "$f" ] || block "Artifact Gate failure in $rel: symlinked artifacts are not allowed."; pr="${CLAUDE_PLUGIN_ROOT:-$(cd "$(dirname "$0")/.." 2>/dev/null && pwd -P)}"; av="$pr/scripts/validate-audit-artifact.py"; [ -f "$av" ] || block "Artifact Gate failure in $rel: validator unavailable at $av."; ve="$(python3 "$av" "$f" --relative-path "$rel" 2>&1)" || block "Artifact Gate failure in $rel. The v3 audit contract is fail-closed: $ve"; }
saa(){ rt="$1"; rawbase="$rt/memory/audits"; [ -e "$rawbase" ] || return 0; base="$(mf "$rt" "audits" || true)"; [ -n "$base" ] && [ -d "$base" ] || block "Artifact Gate failure: memory/audits must be a real directory inside the project."; pr="${CLAUDE_PLUGIN_ROOT:-$(cd "$(dirname "$0")/.." 2>/dev/null && pwd -P)}"; av="$pr/scripts/validate-audit-artifact.py"; [ -f "$av" ] || block "Artifact Gate failure: validator unavailable at $av."; ve="$(python3 "$av" --scan-root "$base" 2>&1)" || block "Artifact Gate reserved-sink sweep failed: $ve"; }
pmp(){ rt="$1"; pr="${CLAUDE_PLUGIN_ROOT:-$(cd "$(dirname "$0")/.." 2>/dev/null && pwd -P)}"; pv="$pr/scripts/check-memory-private.py"; [ -f "$pv" ] || deny "Memory privacy preflight unavailable at $pv; refusing an unverified write-capable tool call."; if ! command -v python3 >/dev/null 2>&1; then case "$in" in *[mM][eE][mM][oO][rR][yY][/\\]*|*[/\\][mM][eE][mM][oO][rR][yY]*) deny "python3 is required to verify memory-namespace operations; install python3 or avoid memory/ paths.";; *) return 0;; esac; fi; pe="$(printf "%s" "$in" | python3 "$pv" --root "$rt" --hook-input 2>&1)" || deny "$pe"; }
paw(){ rt="$1"; pr="${CLAUDE_PLUGIN_ROOT:-$(cd "$(dirname "$0")/.." 2