aboutsummaryrefslogtreecommitdiffstats
path: root/.agents/skills/hook-development/scripts/hook-linter.sh
diff options
context:
space:
mode:
authorsillylaird <sillyfanboy@gmail.com>2026-09-03 00:33:59 +0000
committersillylaird <sillyfanboy@gmail.com>2026-09-03 00:33:59 +0000
commit898b52edcb47bcb3e9d6106e74ca73e74ea01e70 (patch)
tree85c6ee5ad58b860144551184d4cf86b560c62b91 /.agents/skills/hook-development/scripts/hook-linter.sh
downloadwww-898b52edcb47bcb3e9d6106e74ca73e74ea01e70.tar.gz
www-898b52edcb47bcb3e9d6106e74ca73e74ea01e70.zip
import live www.sillylaird.ca webrootHEADmain
Diffstat (limited to '.agents/skills/hook-development/scripts/hook-linter.sh')
-rw-r--r--.agents/skills/hook-development/scripts/hook-linter.sh153
1 files changed, 153 insertions, 0 deletions
diff --git a/.agents/skills/hook-development/scripts/hook-linter.sh b/.agents/skills/hook-development/scripts/hook-linter.sh
new file mode 100644
index 0000000..64f6041
--- /dev/null
+++ b/.agents/skills/hook-development/scripts/hook-linter.sh
@@ -0,0 +1,153 @@
+#!/bin/bash
+# Hook Linter
+# Checks hook scripts for common issues and best practices
+
+set -euo pipefail
+
+# Usage
+if [ $# -eq 0 ]; then
+ echo "Usage: $0 <hook-script.sh> [hook-script2.sh ...]"
+ echo ""
+ echo "Checks hook scripts for:"
+ echo " - Shebang presence"
+ echo " - set -euo pipefail usage"
+ echo " - Input reading from stdin"
+ echo " - Proper error handling"
+ echo " - Variable quoting"
+ echo " - Exit code usage"
+ echo " - Hardcoded paths"
+ echo " - Timeout considerations"
+ exit 1
+fi
+
+check_script() {
+ local script="$1"
+ local warnings=0
+ local errors=0
+
+ echo "🔍 Linting: $script"
+ echo ""
+
+ if [ ! -f "$script" ]; then
+ echo "❌ Error: File not found"
+ return 1
+ fi
+
+ # Check 1: Executable
+ if [ ! -x "$script" ]; then
+ echo "⚠️ Not executable (chmod +x $script)"
+ ((warnings++))
+ fi
+
+ # Check 2: Shebang
+ first_line=$(head -1 "$script")
+ if [[ ! "$first_line" =~ ^#!/ ]]; then
+ echo "❌ Missing shebang (#!/bin/bash)"
+ ((errors++))
+ fi
+
+ # Check 3: set -euo pipefail
+ if ! grep -q "set -euo pipefail" "$script"; then
+ echo "⚠️ Missing 'set -euo pipefail' (recommended for safety)"
+ ((warnings++))
+ fi
+
+ # Check 4: Reads from stdin
+ if ! grep -q "cat\|read" "$script"; then
+ echo "⚠️ Doesn't appear to read input from stdin"
+ ((warnings++))
+ fi
+
+ # Check 5: Uses jq for JSON parsing
+ if grep -q "tool_input\|tool_name" "$script" && ! grep -q "jq" "$script"; then
+ echo "⚠️ Parses hook input but doesn't use jq"
+ ((warnings++))
+ fi
+
+ # Check 6: Unquoted variables
+ if grep -E '\$[A-Za-z_][A-Za-z0-9_]*[^"]' "$script" | grep -v '#' | grep -q .; then
+ echo "⚠️ Potentially unquoted variables detected (injection risk)"
+ echo " Always use double quotes: \"\$variable\" not \$variable"
+ ((warnings++))
+ fi
+
+ # Check 7: Hardcoded paths
+ if grep -E '^[^#]*/home/|^[^#]*/usr/|^[^#]*/opt/' "$script" | grep -q .; then
+ echo "⚠️ Hardcoded absolute paths detected"
+ echo " Use \$CLAUDE_PROJECT_DIR or \$CLAUDE_PLUGIN_ROOT"
+ ((warnings++))
+ fi
+
+ # Check 8: Uses CLAUDE_PLUGIN_ROOT
+ if ! grep -q "CLAUDE_PLUGIN_ROOT\|CLAUDE_PROJECT_DIR" "$script"; then
+ echo "💡 Tip: Use \$CLAUDE_PLUGIN_ROOT for plugin-relative paths"
+ fi
+
+ # Check 9: Exit codes
+ if ! grep -q "exit 0\|exit 2" "$script"; then
+ echo "⚠️ No explicit exit codes (should exit 0 or 2)"
+ ((warnings++))
+ fi
+
+ # Check 10: JSON output for decision hooks
+ if grep -q "PreToolUse\|Stop" "$script"; then
+ if ! grep -q "permissionDecision\|decision" "$script"; then
+ echo "💡 Tip: PreToolUse/Stop hooks should output decision JSON"
+ fi
+ fi
+
+ # Check 11: Long-running commands
+ if grep -E 'sleep [0-9]{3,}|while true' "$script" | grep -v '#' | grep -q .; then
+ echo "⚠️ Potentially long-running code detected"
+ echo " Hooks should complete quickly (< 60s)"
+ ((warnings++))
+ fi
+
+ # Check 12: Error messages to stderr
+ if grep -q 'echo.*".*error\|Error\|denied\|Denied' "$script"; then
+ if ! grep -q '>&2' "$script"; then
+ echo "⚠️ Error messages should be written to stderr (>&2)"
+ ((warnings++))
+ fi
+ fi
+
+ # Check 13: Input validation
+ if ! grep -q "if.*empty\|if.*null\|if.*-z" "$script"; then
+ echo "💡 Tip: Consider validating input fields aren't empty"
+ fi
+
+ echo ""
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
+
+ if [ $errors -eq 0 ] && [ $warnings -eq 0 ]; then
+ echo "✅ No issues found"
+ return 0
+ elif [ $errors -eq 0 ]; then
+ echo "⚠️ Found $warnings warning(s)"
+ return 0
+ else
+ echo "❌ Found $errors error(s) and $warnings warning(s)"
+ return 1
+ fi
+}
+
+echo "🔎 Hook Script Linter"
+echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
+echo ""
+
+total_errors=0
+
+for script in "$@"; do
+ if ! check_script "$script"; then
+ ((total_errors++))
+ fi
+ echo ""
+done
+
+if [ $total_errors -eq 0 ]; then
+ echo "✅ All scripts passed linting"
+ exit 0
+else
+ echo "❌ $total_errors script(s) had errors"
+ exit 1
+fi