diff --git a/hooks/hooks.json b/hooks/hooks.json index f4c5d59..50d7235 100644 --- a/hooks/hooks.json +++ b/hooks/hooks.json @@ -6,7 +6,7 @@ "hooks": [ { "type": "command", - "command": "\"${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.cmd\" session-start", + "command": "AUTODEV_HOOK=session-start; AUTODEV_ROOT=\"${AUTODEV_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT:-${CODEX_PLUGIN_ROOT:-${CURSOR_PLUGIN_ROOT:-}}}}\"; for AUTODEV_ROOT_CAND in \"$AUTODEV_ROOT\" . \"$HOME/.codex/plugins/cache/autodev-marketplace/autodev\"/* \"$HOME/.claude/plugins/marketplace/autodev\"/*; do [ -n \"$AUTODEV_ROOT_CAND\" ] || continue; if [ -x \"$AUTODEV_ROOT_CAND/hooks/run-hook.cmd\" ]; then exec \"$AUTODEV_ROOT_CAND/hooks/run-hook.cmd\" \"$AUTODEV_HOOK\"; fi; done; printf '{}\\n'", "async": false } ] @@ -18,7 +18,7 @@ "hooks": [ { "type": "command", - "command": "\"${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.cmd\" pre-tool-scope-guard", + "command": "AUTODEV_HOOK=pre-tool-scope-guard; AUTODEV_ROOT=\"${AUTODEV_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT:-${CODEX_PLUGIN_ROOT:-${CURSOR_PLUGIN_ROOT:-}}}}\"; for AUTODEV_ROOT_CAND in \"$AUTODEV_ROOT\" . \"$HOME/.codex/plugins/cache/autodev-marketplace/autodev\"/* \"$HOME/.claude/plugins/marketplace/autodev\"/*; do [ -n \"$AUTODEV_ROOT_CAND\" ] || continue; if [ -x \"$AUTODEV_ROOT_CAND/hooks/run-hook.cmd\" ]; then exec \"$AUTODEV_ROOT_CAND/hooks/run-hook.cmd\" \"$AUTODEV_HOOK\"; fi; done; printf '{}\\n'", "timeout": 15 } ] @@ -28,7 +28,7 @@ "hooks": [ { "type": "command", - "command": "\"${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.cmd\" pretool-pr-review-reminder", + "command": "AUTODEV_HOOK=pretool-pr-review-reminder; AUTODEV_ROOT=\"${AUTODEV_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT:-${CODEX_PLUGIN_ROOT:-${CURSOR_PLUGIN_ROOT:-}}}}\"; for AUTODEV_ROOT_CAND in \"$AUTODEV_ROOT\" . \"$HOME/.codex/plugins/cache/autodev-marketplace/autodev\"/* \"$HOME/.claude/plugins/marketplace/autodev\"/*; do [ -n \"$AUTODEV_ROOT_CAND\" ] || continue; if [ -x \"$AUTODEV_ROOT_CAND/hooks/run-hook.cmd\" ]; then exec \"$AUTODEV_ROOT_CAND/hooks/run-hook.cmd\" \"$AUTODEV_HOOK\"; fi; done; printf '{}\\n'", "timeout": 10 } ] @@ -38,7 +38,7 @@ "hooks": [ { "type": "command", - "command": "\"${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.cmd\" pretool-demo-fidelity-guard", + "command": "AUTODEV_HOOK=pretool-demo-fidelity-guard; AUTODEV_ROOT=\"${AUTODEV_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT:-${CODEX_PLUGIN_ROOT:-${CURSOR_PLUGIN_ROOT:-}}}}\"; for AUTODEV_ROOT_CAND in \"$AUTODEV_ROOT\" . \"$HOME/.codex/plugins/cache/autodev-marketplace/autodev\"/* \"$HOME/.claude/plugins/marketplace/autodev\"/*; do [ -n \"$AUTODEV_ROOT_CAND\" ] || continue; if [ -x \"$AUTODEV_ROOT_CAND/hooks/run-hook.cmd\" ]; then exec \"$AUTODEV_ROOT_CAND/hooks/run-hook.cmd\" \"$AUTODEV_HOOK\"; fi; done; printf '{}\\n'", "timeout": 10 } ] @@ -50,7 +50,7 @@ "hooks": [ { "type": "command", - "command": "\"${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.cmd\" record-activity", + "command": "AUTODEV_HOOK=record-activity; AUTODEV_ROOT=\"${AUTODEV_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT:-${CODEX_PLUGIN_ROOT:-${CURSOR_PLUGIN_ROOT:-}}}}\"; for AUTODEV_ROOT_CAND in \"$AUTODEV_ROOT\" . \"$HOME/.codex/plugins/cache/autodev-marketplace/autodev\"/* \"$HOME/.claude/plugins/marketplace/autodev\"/*; do [ -n \"$AUTODEV_ROOT_CAND\" ] || continue; if [ -x \"$AUTODEV_ROOT_CAND/hooks/run-hook.cmd\" ]; then exec \"$AUTODEV_ROOT_CAND/hooks/run-hook.cmd\" \"$AUTODEV_HOOK\"; fi; done; printf '{}\\n'", "timeout": 10 } ] @@ -60,7 +60,7 @@ "hooks": [ { "type": "command", - "command": "\"${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.cmd\" posttool-pr-created", + "command": "AUTODEV_HOOK=posttool-pr-created; AUTODEV_ROOT=\"${AUTODEV_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT:-${CODEX_PLUGIN_ROOT:-${CURSOR_PLUGIN_ROOT:-}}}}\"; for AUTODEV_ROOT_CAND in \"$AUTODEV_ROOT\" . \"$HOME/.codex/plugins/cache/autodev-marketplace/autodev\"/* \"$HOME/.claude/plugins/marketplace/autodev\"/*; do [ -n \"$AUTODEV_ROOT_CAND\" ] || continue; if [ -x \"$AUTODEV_ROOT_CAND/hooks/run-hook.cmd\" ]; then exec \"$AUTODEV_ROOT_CAND/hooks/run-hook.cmd\" \"$AUTODEV_HOOK\"; fi; done; printf '{}\\n'", "timeout": 10 } ] @@ -72,7 +72,7 @@ "hooks": [ { "type": "command", - "command": "\"${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.cmd\" prompt-strict-interpretation", + "command": "AUTODEV_HOOK=prompt-strict-interpretation; AUTODEV_ROOT=\"${AUTODEV_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT:-${CODEX_PLUGIN_ROOT:-${CURSOR_PLUGIN_ROOT:-}}}}\"; for AUTODEV_ROOT_CAND in \"$AUTODEV_ROOT\" . \"$HOME/.codex/plugins/cache/autodev-marketplace/autodev\"/* \"$HOME/.claude/plugins/marketplace/autodev\"/*; do [ -n \"$AUTODEV_ROOT_CAND\" ] || continue; if [ -x \"$AUTODEV_ROOT_CAND/hooks/run-hook.cmd\" ]; then exec \"$AUTODEV_ROOT_CAND/hooks/run-hook.cmd\" \"$AUTODEV_HOOK\"; fi; done; printf '{}\\n'", "timeout": 10 } ] @@ -82,7 +82,7 @@ "hooks": [ { "type": "command", - "command": "\"${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.cmd\" portfolio-inventory-reminder", + "command": "AUTODEV_HOOK=portfolio-inventory-reminder; AUTODEV_ROOT=\"${AUTODEV_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT:-${CODEX_PLUGIN_ROOT:-${CURSOR_PLUGIN_ROOT:-}}}}\"; for AUTODEV_ROOT_CAND in \"$AUTODEV_ROOT\" . \"$HOME/.codex/plugins/cache/autodev-marketplace/autodev\"/* \"$HOME/.claude/plugins/marketplace/autodev\"/*; do [ -n \"$AUTODEV_ROOT_CAND\" ] || continue; if [ -x \"$AUTODEV_ROOT_CAND/hooks/run-hook.cmd\" ]; then exec \"$AUTODEV_ROOT_CAND/hooks/run-hook.cmd\" \"$AUTODEV_HOOK\"; fi; done; printf '{}\\n'", "timeout": 10 } ] @@ -94,7 +94,7 @@ "hooks": [ { "type": "command", - "command": "\"${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.cmd\" completion-claim-guard", + "command": "AUTODEV_HOOK=completion-claim-guard; AUTODEV_ROOT=\"${AUTODEV_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT:-${CODEX_PLUGIN_ROOT:-${CURSOR_PLUGIN_ROOT:-}}}}\"; for AUTODEV_ROOT_CAND in \"$AUTODEV_ROOT\" . \"$HOME/.codex/plugins/cache/autodev-marketplace/autodev\"/* \"$HOME/.claude/plugins/marketplace/autodev\"/*; do [ -n \"$AUTODEV_ROOT_CAND\" ] || continue; if [ -x \"$AUTODEV_ROOT_CAND/hooks/run-hook.cmd\" ]; then exec \"$AUTODEV_ROOT_CAND/hooks/run-hook.cmd\" \"$AUTODEV_HOOK\"; fi; done; printf '{}\\n'", "timeout": 30 } ] @@ -106,7 +106,7 @@ "hooks": [ { "type": "command", - "command": "\"${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.cmd\" subagent-scope-guard", + "command": "AUTODEV_HOOK=subagent-scope-guard; AUTODEV_ROOT=\"${AUTODEV_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT:-${CODEX_PLUGIN_ROOT:-${CURSOR_PLUGIN_ROOT:-}}}}\"; for AUTODEV_ROOT_CAND in \"$AUTODEV_ROOT\" . \"$HOME/.codex/plugins/cache/autodev-marketplace/autodev\"/* \"$HOME/.claude/plugins/marketplace/autodev\"/*; do [ -n \"$AUTODEV_ROOT_CAND\" ] || continue; if [ -x \"$AUTODEV_ROOT_CAND/hooks/run-hook.cmd\" ]; then exec \"$AUTODEV_ROOT_CAND/hooks/run-hook.cmd\" \"$AUTODEV_HOOK\"; fi; done; printf '{}\\n'", "timeout": 15 } ] @@ -118,7 +118,7 @@ "hooks": [ { "type": "command", - "command": "\"${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.cmd\" pre-compact-snapshot", + "command": "AUTODEV_HOOK=pre-compact-snapshot; AUTODEV_ROOT=\"${AUTODEV_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT:-${CODEX_PLUGIN_ROOT:-${CURSOR_PLUGIN_ROOT:-}}}}\"; for AUTODEV_ROOT_CAND in \"$AUTODEV_ROOT\" . \"$HOME/.codex/plugins/cache/autodev-marketplace/autodev\"/* \"$HOME/.claude/plugins/marketplace/autodev\"/*; do [ -n \"$AUTODEV_ROOT_CAND\" ] || continue; if [ -x \"$AUTODEV_ROOT_CAND/hooks/run-hook.cmd\" ]; then exec \"$AUTODEV_ROOT_CAND/hooks/run-hook.cmd\" \"$AUTODEV_HOOK\"; fi; done; printf '{}\\n'", "timeout": 10 } ] diff --git a/tests/hook-contracts.sh b/tests/hook-contracts.sh index edeca52..52a44d1 100755 --- a/tests/hook-contracts.sh +++ b/tests/hook-contracts.sh @@ -42,6 +42,48 @@ run_hook_wrapper() { hooks/run-hook.cmd "$hook" >"$stdout_file" 2>"$stderr_file" <<<"$payload" } +test_hooks_manifest_commands_do_not_require_claude_plugin_root() { + local tmp payload cmd count stdout_file stderr_file stdout_text status + tmp="$(mktemp -d)" + trap 'rm -rf "$tmp"' RETURN + payload='{"source":"startup","tool_name":"Bash","tool_input":{"command":"echo hi"},"prompt":"continue","cwd":"'"$tmp"'","transcript_path":"'"$tmp"'/session.jsonl"}' + touch "$tmp/session.jsonl" + + count=0 + while IFS= read -r cmd; do + count=$((count + 1)) + stdout_file="$tmp/stdout-${count}.json" + stderr_file="$tmp/stderr-${count}.txt" + set +e + ( + cd "$tmp" + printf '%s' "$payload" | env -u AUTODEV_PLUGIN_ROOT -u CLAUDE_PLUGIN_ROOT -u CODEX_PLUGIN_ROOT -u CURSOR_PLUGIN_ROOT \ + LC_ALL=C LANG=C LC_CTYPE=C sh -c "$cmd" + ) >"$stdout_file" 2>"$stderr_file" + status=$? + set -e + + if [ "$status" = "127" ]; then + fail "hooks.json command ${count}: must not require CLAUDE_PLUGIN_ROOT; stderr: $(cat "$stderr_file")" + continue + fi + if [ "$status" != "0" ]; then + fail "hooks.json command ${count}: expected exit 0, got ${status}; stderr: $(cat "$stderr_file")" + continue + fi + stdout_text="$(cat "$stdout_file")" + if ! printf '%s' "$stdout_text" | jq -e . >/dev/null 2>&1; then + fail "hooks.json command ${count}: expected JSON stdout, got: ${stdout_text}; stderr: $(cat "$stderr_file")" + fi + done < <(jq -r '.. | objects | select(.type? == "command") | .command' hooks/hooks.json) + + if [ "$count" = "0" ]; then + fail "hooks.json command discovery: expected at least one command hook" + return + fi + pass "hooks.json commands: run without CLAUDE_PLUGIN_ROOT and emit JSON" +} + # emit_locked_fixture # Writes a minimal-but-valid locked plan to , then runs # hooks/scope-lock-apply against it from the repo root. The plan body is @@ -1817,6 +1859,7 @@ test_demo_fidelity_fail_open_when_state_unwritable() { } require_jq +test_hooks_manifest_commands_do_not_require_claude_plugin_root test_session_start_json test_session_start_time_dedup_suppresses_rapid_refires test_session_start_resume_target_checkpoint