| #!/bin/bash |
| |
| |
|
|
| set -euo pipefail |
|
|
| |
| if [ $# -eq 0 ]; then |
| echo "Usage: $0 <path/to/hooks.json>" |
| echo "" |
| echo "Validates hook configuration file for:" |
| echo " - Valid JSON syntax" |
| echo " - Required fields" |
| echo " - Hook type validity" |
| echo " - Matcher patterns" |
| echo " - Timeout ranges" |
| exit 1 |
| fi |
|
|
| HOOKS_FILE="$1" |
|
|
| if [ ! -f "$HOOKS_FILE" ]; then |
| echo "β Error: File not found: $HOOKS_FILE" |
| exit 1 |
| fi |
|
|
| echo "π Validating hooks configuration: $HOOKS_FILE" |
| echo "" |
|
|
| |
| echo "Checking JSON syntax..." |
| if ! jq empty "$HOOKS_FILE" 2>/dev/null; then |
| echo "β Invalid JSON syntax" |
| exit 1 |
| fi |
| echo "β
Valid JSON" |
|
|
| |
| echo "" |
| echo "Checking root structure..." |
| VALID_EVENTS=("PreToolUse" "PostToolUse" "UserPromptSubmit" "Stop" "SubagentStop" "SessionStart" "SessionEnd" "PreCompact" "Notification") |
|
|
| for event in $(jq -r 'keys[]' "$HOOKS_FILE"); do |
| found=false |
| for valid_event in "${VALID_EVENTS[@]}"; do |
| if [ "$event" = "$valid_event" ]; then |
| found=true |
| break |
| fi |
| done |
|
|
| if [ "$found" = false ]; then |
| echo "β οΈ Unknown event type: $event" |
| fi |
| done |
| echo "β
Root structure valid" |
|
|
| |
| echo "" |
| echo "Validating individual hooks..." |
|
|
| error_count=0 |
| warning_count=0 |
|
|
| for event in $(jq -r 'keys[]' "$HOOKS_FILE"); do |
| hook_count=$(jq -r ".\"$event\" | length" "$HOOKS_FILE") |
|
|
| for ((i=0; i<hook_count; i++)); do |
| |
| matcher=$(jq -r ".\"$event\"[$i].matcher // empty" "$HOOKS_FILE") |
| if [ -z "$matcher" ]; then |
| echo "β $event[$i]: Missing 'matcher' field" |
| ((error_count++)) |
| continue |
| fi |
|
|
| |
| hooks=$(jq -r ".\"$event\"[$i].hooks // empty" "$HOOKS_FILE") |
| if [ -z "$hooks" ] || [ "$hooks" = "null" ]; then |
| echo "β $event[$i]: Missing 'hooks' array" |
| ((error_count++)) |
| continue |
| fi |
|
|
| |
| hook_array_count=$(jq -r ".\"$event\"[$i].hooks | length" "$HOOKS_FILE") |
|
|
| for ((j=0; j<hook_array_count; j++)); do |
| hook_type=$(jq -r ".\"$event\"[$i].hooks[$j].type // empty" "$HOOKS_FILE") |
|
|
| if [ -z "$hook_type" ]; then |
| echo "β $event[$i].hooks[$j]: Missing 'type' field" |
| ((error_count++)) |
| continue |
| fi |
|
|
| if [ "$hook_type" != "command" ] && [ "$hook_type" != "prompt" ]; then |
| echo "β $event[$i].hooks[$j]: Invalid type '$hook_type' (must be 'command' or 'prompt')" |
| ((error_count++)) |
| continue |
| fi |
|
|
| |
| if [ "$hook_type" = "command" ]; then |
| command=$(jq -r ".\"$event\"[$i].hooks[$j].command // empty" "$HOOKS_FILE") |
| if [ -z "$command" ]; then |
| echo "β $event[$i].hooks[$j]: Command hooks must have 'command' field" |
| ((error_count++)) |
| else |
| |
| if [[ "$command" == /* ]] && [[ "$command" != *'${CLAUDE_PLUGIN_ROOT}'* ]]; then |
| echo "β οΈ $event[$i].hooks[$j]: Hardcoded absolute path detected. Consider using \${CLAUDE_PLUGIN_ROOT}" |
| ((warning_count++)) |
| fi |
| fi |
| elif [ "$hook_type" = "prompt" ]; then |
| prompt=$(jq -r ".\"$event\"[$i].hooks[$j].prompt // empty" "$HOOKS_FILE") |
| if [ -z "$prompt" ]; then |
| echo "β $event[$i].hooks[$j]: Prompt hooks must have 'prompt' field" |
| ((error_count++)) |
| fi |
|
|
| |
| if [ "$event" != "Stop" ] && [ "$event" != "SubagentStop" ] && [ "$event" != "UserPromptSubmit" ] && [ "$event" != "PreToolUse" ]; then |
| echo "β οΈ $event[$i].hooks[$j]: Prompt hooks may not be fully supported on $event (best on Stop, SubagentStop, UserPromptSubmit, PreToolUse)" |
| ((warning_count++)) |
| fi |
| fi |
|
|
| |
| timeout=$(jq -r ".\"$event\"[$i].hooks[$j].timeout // empty" "$HOOKS_FILE") |
| if [ -n "$timeout" ] && [ "$timeout" != "null" ]; then |
| if ! [[ "$timeout" =~ ^[0-9]+$ ]]; then |
| echo "β $event[$i].hooks[$j]: Timeout must be a number" |
| ((error_count++)) |
| elif [ "$timeout" -gt 600 ]; then |
| echo "β οΈ $event[$i].hooks[$j]: Timeout $timeout seconds is very high (max 600s)" |
| ((warning_count++)) |
| elif [ "$timeout" -lt 5 ]; then |
| echo "β οΈ $event[$i].hooks[$j]: Timeout $timeout seconds is very low" |
| ((warning_count++)) |
| fi |
| fi |
| done |
| done |
| done |
|
|
| echo "" |
| echo "ββββββββββββββββββββββββββββββββββββββββ" |
| if [ $error_count -eq 0 ] && [ $warning_count -eq 0 ]; then |
| echo "β
All checks passed!" |
| exit 0 |
| elif [ $error_count -eq 0 ]; then |
| echo "β οΈ Validation passed with $warning_count warning(s)" |
| exit 0 |
| else |
| echo "β Validation failed with $error_count error(s) and $warning_count warning(s)" |
| exit 1 |
| fi |
|
|