diff --git a/.claude/hooks/no-aggregator-hallucination.sh b/.claude/hooks/no-aggregator-hallucination.sh index 7a73b86..ea522b4 100755 --- a/.claude/hooks/no-aggregator-hallucination.sh +++ b/.claude/hooks/no-aggregator-hallucination.sh @@ -30,6 +30,43 @@ if ! printf '%s' "$INPUT" | jq -e . >/dev/null 2>&1; then exit 0 fi +# Rust path: prefer agentcloseout-physics when available. +if command -v agentcloseout-physics >/dev/null 2>&1; then + RULES_DIR="${LLM_DARK_PATTERNS_RULES_DIR:-}" + if [ -z "$RULES_DIR" ]; then + for candidate in \ + "$(dirname "$0")/../../agent-closeout-bench/rules/closeout" \ + "/home/fer/Documents/agent-closeout-bench/rules/closeout" \ + "${XDG_CONFIG_HOME:-$HOME/.config}/agentcloseout-physics/rules/closeout"; do + if [ -d "$candidate" ]; then RULES_DIR="$candidate"; break; fi + done + fi + if [ -n "$RULES_DIR" ] && [ -d "$RULES_DIR" ] && [ -f "$RULES_DIR/no_aggregator_hallucination.yaml" ]; then + TMP_INPUT="$(mktemp)"; printf '%s' "$INPUT" > "$TMP_INPUT" + VERDICT_JSON="$(agentcloseout-physics scan --category no_aggregator_hallucination --rules "$RULES_DIR" --input "$TMP_INPUT" 2>/dev/null || true)" + rm -f "$TMP_INPUT" + if [ -n "$VERDICT_JSON" ]; then + DECISION="$(printf '%s' "$VERDICT_JSON" | jq -r '.decision // empty' 2>/dev/null)" + if [ "$DECISION" = "block" ]; then + RULE="$(printf '%s' "$VERDICT_JSON" | jq -r '.matched_rules[0].rule_id // "no_aggregator_hallucination"' 2>/dev/null)" + EVIDENCE="$(printf '%s' "$VERDICT_JSON" | jq -r '.redacted_evidence[0] // ""' 2>/dev/null)" + echo "BLOCKED: aggregator hallucination: synthesis claim without per-worker evidence." >&2 + echo "Matched rule: $RULE" >&2 + [ -n "$EVIDENCE" ] && echo "Evidence: $EVIDENCE" >&2 + echo "" >&2 + echo "Repair guidance:" >&2 + echo "- Quote the per-worker output that justifies the synthesis (e.g. worker_1: pass, worker_2: pass)." >&2 + echo "- Or drop the synthesis framing and report as a single agent's work." >&2 + echo "- Or close as Status: partial / Verification: pending." >&2 + exit 2 + fi + if [ "$DECISION" = "pass" ]; then + exit 0 + fi + fi + fi +fi + _HOOK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" if [ -f "$_HOOK_DIR/../lib/packs.sh" ]; then # shellcheck source=../lib/packs.sh diff --git a/.claude/hooks/no-cherry-pick-rollup.sh b/.claude/hooks/no-cherry-pick-rollup.sh index a78c4a6..7f74028 100755 --- a/.claude/hooks/no-cherry-pick-rollup.sh +++ b/.claude/hooks/no-cherry-pick-rollup.sh @@ -25,6 +25,43 @@ if ! printf '%s' "$INPUT" | jq -e . >/dev/null 2>&1; then exit 0 fi +# Rust path: prefer agentcloseout-physics when available. +if command -v agentcloseout-physics >/dev/null 2>&1; then + RULES_DIR="${LLM_DARK_PATTERNS_RULES_DIR:-}" + if [ -z "$RULES_DIR" ]; then + for candidate in \ + "$(dirname "$0")/../../agent-closeout-bench/rules/closeout" \ + "/home/fer/Documents/agent-closeout-bench/rules/closeout" \ + "${XDG_CONFIG_HOME:-$HOME/.config}/agentcloseout-physics/rules/closeout"; do + if [ -d "$candidate" ]; then RULES_DIR="$candidate"; break; fi + done + fi + if [ -n "$RULES_DIR" ] && [ -d "$RULES_DIR" ] && [ -f "$RULES_DIR/no_cherry_pick_rollup.yaml" ]; then + TMP_INPUT="$(mktemp)"; printf '%s' "$INPUT" > "$TMP_INPUT" + VERDICT_JSON="$(agentcloseout-physics scan --category no_cherry_pick_rollup --rules "$RULES_DIR" --input "$TMP_INPUT" 2>/dev/null || true)" + rm -f "$TMP_INPUT" + if [ -n "$VERDICT_JSON" ]; then + DECISION="$(printf '%s' "$VERDICT_JSON" | jq -r '.decision // empty' 2>/dev/null)" + if [ "$DECISION" = "block" ]; then + RULE="$(printf '%s' "$VERDICT_JSON" | jq -r '.matched_rules[0].rule_id // "no_cherry_pick_rollup"' 2>/dev/null)" + EVIDENCE="$(printf '%s' "$VERDICT_JSON" | jq -r '.redacted_evidence[0] // ""' 2>/dev/null)" + echo "BLOCKED: cherry-pick rollup: partial worker success + positive closeout WITHOUT handling failed workers." >&2 + echo "Matched rule: $RULE" >&2 + [ -n "$EVIDENCE" ] && echo "Evidence: $EVIDENCE" >&2 + echo "" >&2 + echo "Repair guidance:" >&2 + echo "- Explicitly handle failed workers (retried, blocking, ignored-with-reason)." >&2 + echo "- Or close as Status: partial / Next step: investigate failed worker." >&2 + echo "- Or drop the rollup framing and report only the verified-succeeded portion." >&2 + exit 2 + fi + if [ "$DECISION" = "pass" ]; then + exit 0 + fi + fi + fi +fi + _HOOK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" if [ -f "$_HOOK_DIR/../lib/packs.sh" ]; then # shellcheck source=../lib/packs.sh diff --git a/.claude/hooks/no-credential-leak-in-handoff.sh b/.claude/hooks/no-credential-leak-in-handoff.sh index f1962d4..7f47f53 100755 --- a/.claude/hooks/no-credential-leak-in-handoff.sh +++ b/.claude/hooks/no-credential-leak-in-handoff.sh @@ -18,6 +18,44 @@ if ! printf '%s' "$INPUT" | jq -e . >/dev/null 2>&1; then exit 0 fi +# Rust path: only for Stop/SubagentStop. TaskCreated stays on bash path +# (the Rust v0.1 engine inspects last_assistant_message only; TaskCreated +# payload requires bash jq-based extraction of .task.description / .task.prompt). +EVENT_FILTER="$(printf '%s' "$INPUT" | jq -r '.hook_event_name // empty' 2>/dev/null)" +if command -v agentcloseout-physics >/dev/null 2>&1 && { [ "$EVENT_FILTER" = "Stop" ] || [ "$EVENT_FILTER" = "SubagentStop" ]; }; then + RULES_DIR="${LLM_DARK_PATTERNS_RULES_DIR:-}" + if [ -z "$RULES_DIR" ]; then + for candidate in \ + "$(dirname "$0")/../../agent-closeout-bench/rules/closeout" \ + "/home/fer/Documents/agent-closeout-bench/rules/closeout" \ + "${XDG_CONFIG_HOME:-$HOME/.config}/agentcloseout-physics/rules/closeout"; do + if [ -d "$candidate" ]; then RULES_DIR="$candidate"; break; fi + done + fi + if [ -n "$RULES_DIR" ] && [ -d "$RULES_DIR" ] && [ -f "$RULES_DIR/no_credential_leak_in_handoff.yaml" ]; then + TMP_INPUT="$(mktemp)"; printf '%s' "$INPUT" > "$TMP_INPUT" + VERDICT_JSON="$(agentcloseout-physics scan --category no_credential_leak_in_handoff --rules "$RULES_DIR" --input "$TMP_INPUT" 2>/dev/null || true)" + rm -f "$TMP_INPUT" + if [ -n "$VERDICT_JSON" ]; then + DECISION="$(printf '%s' "$VERDICT_JSON" | jq -r '.decision // empty' 2>/dev/null)" + if [ "$DECISION" = "block" ]; then + RULE="$(printf '%s' "$VERDICT_JSON" | jq -r '.matched_rules[0].rule_id // "no_credential_leak_in_handoff"' 2>/dev/null)" + echo "BLOCKED: credential leak in closeout message." >&2 + echo "Matched rule: $RULE" >&2 + echo "" >&2 + echo "Repair guidance:" >&2 + echo "- Refer to credentials by env-var name (e.g. \$ANTHROPIC_API_KEY) instead of inlining the value." >&2 + echo "- Or have the subagent read from a secrets manager." >&2 + echo "- Reference: arXiv:2602.11510 AgentLeak benchmark." >&2 + exit 2 + fi + if [ "$DECISION" = "pass" ]; then + exit 0 + fi + fi + fi +fi + json_get() { printf '%s' "$INPUT" | jq -r "$1 // empty" 2>/dev/null || true; } block() { diff --git a/.claude/hooks/no-silent-worker-success.sh b/.claude/hooks/no-silent-worker-success.sh index d6d2af4..073cafd 100755 --- a/.claude/hooks/no-silent-worker-success.sh +++ b/.claude/hooks/no-silent-worker-success.sh @@ -25,6 +25,43 @@ if ! printf '%s' "$INPUT" | jq -e . >/dev/null 2>&1; then exit 0 fi +# Rust path: prefer agentcloseout-physics when available. +if command -v agentcloseout-physics >/dev/null 2>&1; then + RULES_DIR="${LLM_DARK_PATTERNS_RULES_DIR:-}" + if [ -z "$RULES_DIR" ]; then + for candidate in \ + "$(dirname "$0")/../../agent-closeout-bench/rules/closeout" \ + "/home/fer/Documents/agent-closeout-bench/rules/closeout" \ + "${XDG_CONFIG_HOME:-$HOME/.config}/agentcloseout-physics/rules/closeout"; do + if [ -d "$candidate" ]; then RULES_DIR="$candidate"; break; fi + done + fi + if [ -n "$RULES_DIR" ] && [ -d "$RULES_DIR" ] && [ -f "$RULES_DIR/no_silent_worker_success.yaml" ]; then + TMP_INPUT="$(mktemp)"; printf '%s' "$INPUT" > "$TMP_INPUT" + VERDICT_JSON="$(agentcloseout-physics scan --category no_silent_worker_success --rules "$RULES_DIR" --input "$TMP_INPUT" 2>/dev/null || true)" + rm -f "$TMP_INPUT" + if [ -n "$VERDICT_JSON" ]; then + DECISION="$(printf '%s' "$VERDICT_JSON" | jq -r '.decision // empty' 2>/dev/null)" + if [ "$DECISION" = "block" ]; then + RULE="$(printf '%s' "$VERDICT_JSON" | jq -r '.matched_rules[0].rule_id // "no_silent_worker_success"' 2>/dev/null)" + EVIDENCE="$(printf '%s' "$VERDICT_JSON" | jq -r '.redacted_evidence[0] // ""' 2>/dev/null)" + echo "BLOCKED: silent worker rollup: 'all N workers completed' claim without per-worker evidence." >&2 + echo "Matched rule: $RULE" >&2 + [ -n "$EVIDENCE" ] && echo "Evidence: $EVIDENCE" >&2 + echo "" >&2 + echo "Repair guidance:" >&2 + echo "- Enumerate per-worker status (worker_1: exit=0, worker_2: exit=0, ...)." >&2 + echo "- Or report only the workers whose output was verified." >&2 + echo "- Or close as Status: partial / Verification: pending." >&2 + exit 2 + fi + if [ "$DECISION" = "pass" ]; then + exit 0 + fi + fi + fi +fi + _HOOK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" if [ -f "$_HOOK_DIR/../lib/packs.sh" ]; then # shellcheck source=../lib/packs.sh