From 88e5fc6f49fe9c57da8f07ec3f10f5e90e88b104 Mon Sep 17 00:00:00 2001 From: Snider Date: Mon, 16 Mar 2026 05:37:52 +0000 Subject: [PATCH] refactor(plugin): remove restrictive hooks, clean up orphaned scripts Removed: - prefer-core.sh: blocked raw go/php commands unnecessarily - post-commit-check.sh: noisy warnings after every commit - block-docs.sh: blocked writing specs and RFCs - capture-context.sh, extract-actionables.sh, pr-created.sh, suggest-compact.sh: orphaned scripts not referenced by any hook Kept: - go-format.sh, php-format.sh: auto-format after edits (helpful) - check-debug.sh: warns about dd()/fmt.Print* (lightweight) - session-start.sh, pre-compact.sh, session-save.sh: essential Co-Authored-By: Virgil --- claude/core/hooks.json | 22 ----- claude/core/hooks/hooks.json | 22 ----- claude/core/hooks/prefer-core.sh | 108 --------------------- claude/core/scripts/capture-context.sh | 44 --------- claude/core/scripts/extract-actionables.sh | 34 ------- claude/core/scripts/post-commit-check.sh | 51 ---------- claude/core/scripts/pr-created.sh | 18 ---- claude/core/scripts/suggest-compact.sh | 28 ------ 8 files changed, 327 deletions(-) delete mode 100755 claude/core/hooks/prefer-core.sh delete mode 100755 claude/core/scripts/capture-context.sh delete mode 100755 claude/core/scripts/extract-actionables.sh delete mode 100755 claude/core/scripts/post-commit-check.sh delete mode 100755 claude/core/scripts/pr-created.sh delete mode 100755 claude/core/scripts/suggest-compact.sh diff --git a/claude/core/hooks.json b/claude/core/hooks.json index 69d886c..b8c713d 100644 --- a/claude/core/hooks.json +++ b/claude/core/hooks.json @@ -1,18 +1,6 @@ { "$schema": "https://claude.ai/schemas/hooks.json", "hooks": { - "PreToolUse": [ - { - "matcher": "Bash", - "hooks": [ - { - "type": "command", - "command": "${CLAUDE_PLUGIN_ROOT}/hooks/prefer-core.sh" - } - ], - "description": "Block destructive commands (rm -rf, sed -i, xargs rm) and enforce core CLI" - }, - ], "PostToolUse": [ { "matcher": "tool == \"Edit\" && tool_input.file_path matches \"\\.go$\"", @@ -43,16 +31,6 @@ } ], "description": "Warn about debug statements (dd, dump, fmt.Println)" - }, - { - "matcher": "tool == \"Bash\" && tool_input.command matches \"^git commit\"", - "hooks": [ - { - "type": "command", - "command": "${CLAUDE_PLUGIN_ROOT}/scripts/post-commit-check.sh" - } - ], - "description": "Warn about uncommitted work after git commit" } ], "Stop": [ diff --git a/claude/core/hooks/hooks.json b/claude/core/hooks/hooks.json index c14cfe7..f2beb30 100644 --- a/claude/core/hooks/hooks.json +++ b/claude/core/hooks/hooks.json @@ -1,18 +1,6 @@ { "$schema": "https://claude.ai/schemas/hooks.json", "hooks": { - "PreToolUse": [ - { - "matcher": "Bash", - "hooks": [ - { - "type": "command", - "command": "${CLAUDE_PLUGIN_ROOT}/hooks/prefer-core.sh" - } - ], - "description": "Block destructive commands (rm -rf, sed -i, xargs rm) and enforce core CLI" - }, - ], "PostToolUse": [ { "matcher": "tool == \"Edit\" && tool_input.file_path matches \"\\.go$\"", @@ -43,16 +31,6 @@ } ], "description": "Warn about debug statements (dd, dump, fmt.Println)" - }, - { - "matcher": "tool == \"Bash\" && tool_input.command matches \"^git commit\"", - "hooks": [ - { - "type": "command", - "command": "${CLAUDE_PLUGIN_ROOT}/scripts/post-commit-check.sh" - } - ], - "description": "Warn about uncommitted work after git commit" } ], "PreCompact": [ diff --git a/claude/core/hooks/prefer-core.sh b/claude/core/hooks/prefer-core.sh deleted file mode 100755 index 349a598..0000000 --- a/claude/core/hooks/prefer-core.sh +++ /dev/null @@ -1,108 +0,0 @@ -#!/bin/bash -# PreToolUse hook: Block dangerous commands, enforce core CLI -# -# BLOCKS: -# - Raw go commands (use core go *) -# - Destructive patterns (sed -i, xargs rm, etc.) -# - Mass file operations (rm -rf, mv/cp with wildcards) -# -# This prevents "efficient shortcuts" that nuke codebases - -read -r input -full_command=$(echo "$input" | jq -r '.tool_input.command // empty') - -# Strip heredoc content — only check the actual command, not embedded text -# This prevents false positives from code/docs inside heredocs -command=$(echo "$full_command" | sed -n '1p') -if echo "$command" | grep -qE "<<\s*['\"]?[A-Z_]+"; then - # First line has heredoc marker — only check the command portion before << - command=$(echo "$command" | sed -E 's/\s*<<.*$//') -fi - -# For multi-line commands joined with && or ;, check each segment -# But still only the first line (not heredoc body) - -# === HARD BLOCKS - Never allow these === - -# Block rm -rf, rm -r (except for known safe paths like node_modules, vendor, .cache) -# Allow git rm -r (safe — git tracks everything, easily reversible) -if echo "$command" | grep -qE 'rm\s+(-[a-zA-Z]*r[a-zA-Z]*|-[a-zA-Z]*f[a-zA-Z]*r|--recursive)'; then - # git rm -r is safe — everything is tracked and recoverable - if echo "$command" | grep -qE 'git\s+rm\s'; then - : # allow git rm through - # Allow only specific safe directories for raw rm - elif ! echo "$command" | grep -qE 'rm\s+(-rf|-r)\s+(node_modules|vendor|\.cache|dist|build|__pycache__|\.pytest_cache|/tmp/)'; then - echo '{"decision": "block", "message": "BLOCKED: Recursive delete is not allowed. Delete files individually or ask the user to run this command."}' - exit 0 - fi -fi - -# Block mv/cp with dangerous wildcards (e.g. `cp * /tmp`, `mv ./* /dest`) -# Allow specific file copies that happen to use glob in a for loop or path -if echo "$command" | grep -qE '(mv|cp)\s+(\.\/)?\*\s'; then - echo '{"decision": "block", "message": "BLOCKED: Mass file move/copy with bare wildcards is not allowed. Copy files individually."}' - exit 0 -fi - -# Block xargs with rm, mv, cp (mass operations) -if echo "$command" | grep -qE 'xargs\s+.*(rm|mv|cp)'; then - echo '{"decision": "block", "message": "BLOCKED: xargs with file operations is not allowed. Too risky for mass changes."}' - exit 0 -fi - -# Block find -exec with rm, mv, cp -if echo "$command" | grep -qE 'find\s+.*-exec\s+.*(rm|mv|cp)'; then - echo '{"decision": "block", "message": "BLOCKED: find -exec with file operations is not allowed. Too risky for mass changes."}' - exit 0 -fi - -# Block sed -i on LOCAL files only (allow on remote via ssh/docker exec) -if echo "$command" | grep -qE '^sed\s+(-[a-zA-Z]*i|--in-place)'; then - echo '{"decision": "block", "message": "BLOCKED: sed -i (in-place edit) on local files. Use the Edit tool."}' - exit 0 -fi - -# Block grep -l piped to destructive commands only (not head, wc, etc.) -if echo "$command" | grep -qE 'grep\s+.*-l.*\|\s*(xargs|sed|rm|mv)'; then - echo '{"decision": "block", "message": "BLOCKED: grep -l piped to destructive commands. Too risky."}' - exit 0 -fi - -# Block perl -i on local files -if echo "$command" | grep -qE '^perl\s+-[a-zA-Z]*i'; then - echo '{"decision": "block", "message": "BLOCKED: In-place file editing with perl. Use the Edit tool."}' - exit 0 -fi - -# === REQUIRE CORE CLI === - -# Suggest core CLI for common go commands, but don't block -# go work sync, go mod edit, go get, go install, go list etc. have no core wrapper -case "$command" in - "go test"*|"go build"*|"go fmt"*|"go vet"*) - echo '{"decision": "block", "message": "Use `core go test`, `core build`, `core go fmt --fix`, `core go vet`. Raw go commands bypass quality checks."}' - exit 0 - ;; -esac -# Allow all other go commands (go mod tidy, go work sync, go get, go run, etc.) - -# Block raw php commands -case "$command" in - "php artisan serve"*|"./vendor/bin/pest"*|"./vendor/bin/pint"*|"./vendor/bin/phpstan"*) - echo '{"decision": "block", "message": "Use `core php dev`, `core php test`, `core php fmt`, `core php analyse`. Raw php commands are not allowed."}' - exit 0 - ;; - "composer test"*|"composer lint"*) - echo '{"decision": "block", "message": "Use `core php test` or `core php fmt`. Raw composer commands are not allowed."}' - exit 0 - ;; -esac - -# Block golangci-lint directly -if echo "$command" | grep -qE '^golangci-lint'; then - echo '{"decision": "block", "message": "Use `core go lint` instead of golangci-lint directly."}' - exit 0 -fi - -# === APPROVED === -echo '{"decision": "approve"}' diff --git a/claude/core/scripts/capture-context.sh b/claude/core/scripts/capture-context.sh deleted file mode 100755 index 288e9be..0000000 --- a/claude/core/scripts/capture-context.sh +++ /dev/null @@ -1,44 +0,0 @@ -#!/bin/bash -# Capture context facts from tool output or conversation -# Called by PostToolUse hooks to extract actionable items -# -# Stores in ~/.claude/sessions/context.json as: -# [{"fact": "...", "source": "core go qa", "ts": 1234567890}, ...] - -CONTEXT_FILE="${HOME}/.claude/sessions/context.json" -TIMESTAMP=$(date '+%s') -THREE_HOURS=10800 - -mkdir -p "${HOME}/.claude/sessions" - -# Initialize if missing or stale -if [[ -f "$CONTEXT_FILE" ]]; then - FIRST_TS=$(jq -r '.[0].ts // 0' "$CONTEXT_FILE" 2>/dev/null) - NOW=$(date '+%s') - AGE=$((NOW - FIRST_TS)) - if [[ $AGE -gt $THREE_HOURS ]]; then - echo "[]" > "$CONTEXT_FILE" - fi -else - echo "[]" > "$CONTEXT_FILE" -fi - -# Read input (fact and source passed as args or stdin) -FACT="${1:-}" -SOURCE="${2:-manual}" - -if [[ -z "$FACT" ]]; then - # Try reading from stdin - read -r FACT -fi - -if [[ -n "$FACT" ]]; then - # Append to context (keep last 20 items) - jq --arg fact "$FACT" --arg source "$SOURCE" --argjson ts "$TIMESTAMP" \ - '. + [{"fact": $fact, "source": $source, "ts": $ts}] | .[-20:]' \ - "$CONTEXT_FILE" > "${CONTEXT_FILE}.tmp" && mv "${CONTEXT_FILE}.tmp" "$CONTEXT_FILE" - - echo "[Context] Saved: $FACT" >&2 -fi - -exit 0 diff --git a/claude/core/scripts/extract-actionables.sh b/claude/core/scripts/extract-actionables.sh deleted file mode 100755 index 86a2bbb..0000000 --- a/claude/core/scripts/extract-actionables.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/bin/bash -# Extract actionable items from core CLI output -# Called PostToolUse on Bash commands that run core - -read -r input -COMMAND=$(echo "$input" | jq -r '.tool_input.command // empty') -OUTPUT=$(echo "$input" | jq -r '.tool_output.output // empty') - -CONTEXT_SCRIPT="$(dirname "$0")/capture-context.sh" - -# Extract actionables from specific core commands -case "$COMMAND" in - "core go qa"*|"core go test"*|"core go lint"*) - # Extract error/warning lines - echo "$OUTPUT" | grep -E "^(ERROR|WARN|FAIL|---)" | head -5 | while read -r line; do - "$CONTEXT_SCRIPT" "$line" "core go" - done - ;; - "core php test"*|"core php analyse"*) - # Extract PHP errors - echo "$OUTPUT" | grep -E "^(FAIL|Error|×)" | head -5 | while read -r line; do - "$CONTEXT_SCRIPT" "$line" "core php" - done - ;; - "core build"*) - # Extract build errors - echo "$OUTPUT" | grep -E "^(error|cannot|undefined)" | head -5 | while read -r line; do - "$CONTEXT_SCRIPT" "$line" "core build" - done - ;; -esac - -# Pass through -echo "$input" diff --git a/claude/core/scripts/post-commit-check.sh b/claude/core/scripts/post-commit-check.sh deleted file mode 100755 index 42418b6..0000000 --- a/claude/core/scripts/post-commit-check.sh +++ /dev/null @@ -1,51 +0,0 @@ -#!/bin/bash -# Post-commit hook: Check for uncommitted work that might get lost -# -# After committing task-specific files, check if there's other work -# in the repo that should be committed or stashed - -read -r input -COMMAND=$(echo "$input" | jq -r '.tool_input.command // empty') - -# Only run after git commit -if ! echo "$COMMAND" | grep -qE '^git commit'; then - echo "$input" - exit 0 -fi - -# Check for remaining uncommitted changes -UNSTAGED=$(git diff --name-only 2>/dev/null | wc -l | tr -d ' ') -STAGED=$(git diff --cached --name-only 2>/dev/null | wc -l | tr -d ' ') -UNTRACKED=$(git ls-files --others --exclude-standard 2>/dev/null | wc -l | tr -d ' ') - -TOTAL=$((UNSTAGED + STAGED + UNTRACKED)) - -if [[ $TOTAL -gt 0 ]]; then - echo "" >&2 - echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" >&2 - echo "[PostCommit] WARNING: Uncommitted work remains" >&2 - echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" >&2 - - if [[ $UNSTAGED -gt 0 ]]; then - echo " Modified (unstaged): $UNSTAGED files" >&2 - git diff --name-only 2>/dev/null | head -5 | sed 's/^/ /' >&2 - [[ $UNSTAGED -gt 5 ]] && echo " ... and $((UNSTAGED - 5)) more" >&2 - fi - - if [[ $STAGED -gt 0 ]]; then - echo " Staged (not committed): $STAGED files" >&2 - git diff --cached --name-only 2>/dev/null | head -5 | sed 's/^/ /' >&2 - fi - - if [[ $UNTRACKED -gt 0 ]]; then - echo " Untracked: $UNTRACKED files" >&2 - git ls-files --others --exclude-standard 2>/dev/null | head -5 | sed 's/^/ /' >&2 - [[ $UNTRACKED -gt 5 ]] && echo " ... and $((UNTRACKED - 5)) more" >&2 - fi - - echo "" >&2 - echo "Consider: commit these, stash them, or confirm they're intentionally left" >&2 - echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" >&2 -fi - -echo "$input" diff --git a/claude/core/scripts/pr-created.sh b/claude/core/scripts/pr-created.sh deleted file mode 100755 index 82dd975..0000000 --- a/claude/core/scripts/pr-created.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash -# Log PR URL and provide review command after PR creation - -read -r input -COMMAND=$(echo "$input" | jq -r '.tool_input.command // empty') -OUTPUT=$(echo "$input" | jq -r '.tool_output.output // empty') - -if [[ "$COMMAND" == *"gh pr create"* ]]; then - PR_URL=$(echo "$OUTPUT" | grep -oE 'https://github.com/[^/]+/[^/]+/pull/[0-9]+' | head -1) - if [[ -n "$PR_URL" ]]; then - REPO=$(echo "$PR_URL" | sed -E 's|https://github.com/([^/]+/[^/]+)/pull/[0-9]+|\1|') - PR_NUM=$(echo "$PR_URL" | sed -E 's|.*/pull/([0-9]+)|\1|') - echo "[Hook] PR created: $PR_URL" >&2 - echo "[Hook] To review: gh pr review $PR_NUM --repo $REPO" >&2 - fi -fi - -echo "$input" diff --git a/claude/core/scripts/suggest-compact.sh b/claude/core/scripts/suggest-compact.sh deleted file mode 100755 index e958c50..0000000 --- a/claude/core/scripts/suggest-compact.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/bash -# Suggest /compact at logical intervals to manage context window -# Tracks tool calls per session, suggests compaction every 50 calls - -SESSION_ID="${CLAUDE_SESSION_ID:-$$}" -COUNTER_FILE="/tmp/claude-tool-count-${SESSION_ID}" -THRESHOLD="${COMPACT_THRESHOLD:-50}" - -# Read or initialize counter -if [[ -f "$COUNTER_FILE" ]]; then - COUNT=$(($(cat "$COUNTER_FILE") + 1)) -else - COUNT=1 -fi - -echo "$COUNT" > "$COUNTER_FILE" - -# Suggest compact at threshold -if [[ $COUNT -eq $THRESHOLD ]]; then - echo "[Compact] ${THRESHOLD} tool calls - consider /compact if transitioning phases" >&2 -fi - -# Suggest at intervals after threshold -if [[ $COUNT -gt $THRESHOLD ]] && [[ $((COUNT % 25)) -eq 0 ]]; then - echo "[Compact] ${COUNT} tool calls - good checkpoint for /compact" >&2 -fi - -exit 0