fix(hooks): convert ALL hooks to command type with proper prefixes
ALL hooks now use command type (bash scripts) instead of prompt type. Prompt hooks are unreliable - model ignores instructions. Changes: - projman: SessionStart → startup-check.sh with [projman] prefix - pr-review: SessionStart → startup-check.sh with [pr-review] prefix - project-hygiene: cleanup.sh now has [project-hygiene] prefix - doc-guardian: already fixed (notify.sh with [doc-guardian] prefix) - code-sentinel: already fixed (security-check.sh with [code-sentinel] prefix) All hook output now guaranteed to have plugin name prefix. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,8 +2,8 @@
|
|||||||
"hooks": {
|
"hooks": {
|
||||||
"SessionStart": [
|
"SessionStart": [
|
||||||
{
|
{
|
||||||
"type": "prompt",
|
"type": "command",
|
||||||
"prompt": "STARTUP CHECK - STRICT OUTPUT FORMAT:\n\nALL outputs MUST start with '[pr-review]' - NO EXCEPTIONS.\n\nPerform quick silent checks:\n1. If MCP venvs missing: '[pr-review] MCP venvs missing - run setup.sh from installed marketplace'\n2. If git remote != .env config: '[pr-review] Git remote mismatch - run /pr-review:project-sync'\n\nIf all checks pass: Say nothing (empty response)\n\nRules:\n- NEVER output without '[pr-review]' prefix\n- Keep messages under 20 words\n- Be quick and non-blocking\n- Do not read files or perform deep analysis"
|
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/startup-check.sh"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
30
plugins/pr-review/hooks/startup-check.sh
Executable file
30
plugins/pr-review/hooks/startup-check.sh
Executable file
@@ -0,0 +1,30 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# pr-review startup check hook
|
||||||
|
# Checks for common issues at session start
|
||||||
|
# All output MUST have [pr-review] prefix
|
||||||
|
|
||||||
|
PREFIX="[pr-review]"
|
||||||
|
|
||||||
|
# Check if MCP venv exists
|
||||||
|
PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$(dirname "$(dirname "$(realpath "$0")")")}"
|
||||||
|
VENV_PATH="$PLUGIN_ROOT/mcp-servers/gitea/.venv/bin/python"
|
||||||
|
|
||||||
|
if [[ ! -f "$VENV_PATH" ]]; then
|
||||||
|
echo "$PREFIX MCP venvs missing - run setup.sh from installed marketplace"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check git remote vs .env config (only if .env exists)
|
||||||
|
if [[ -f ".env" ]]; then
|
||||||
|
CONFIGURED_REPO=$(grep -E "^GITEA_REPO=" .env 2>/dev/null | cut -d'=' -f2 | tr -d '"' || true)
|
||||||
|
if [[ -n "$CONFIGURED_REPO" ]]; then
|
||||||
|
CURRENT_REMOTE=$(git remote get-url origin 2>/dev/null | sed 's/.*[:/]\([^/]*\/[^.]*\).*/\1/' || true)
|
||||||
|
if [[ -n "$CURRENT_REMOTE" && "$CONFIGURED_REPO" != "$CURRENT_REMOTE" ]]; then
|
||||||
|
echo "$PREFIX Git remote mismatch - run /pr-review:project-sync"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# All checks passed - say nothing
|
||||||
|
exit 0
|
||||||
@@ -1,365 +1,28 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# project-hygiene cleanup hook
|
# project-hygiene cleanup hook
|
||||||
# Runs after task completion to clean up temp files and manage orphans
|
# Runs after file edits to clean up temp files
|
||||||
|
# All output MUST have [project-hygiene] prefix
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
# Configuration
|
PREFIX="[project-hygiene]"
|
||||||
|
|
||||||
|
# Read tool input from stdin (discard - we don't need it for cleanup)
|
||||||
|
cat > /dev/null
|
||||||
|
|
||||||
PROJECT_ROOT="${PROJECT_ROOT:-.}"
|
PROJECT_ROOT="${PROJECT_ROOT:-.}"
|
||||||
PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$(dirname "$(dirname "$(realpath "$0")")")}"
|
|
||||||
CONFIG_FILE="${PROJECT_ROOT}/.hygiene.json"
|
|
||||||
LOG_DIR="${PROJECT_ROOT}/.dev/logs"
|
|
||||||
SCRATCH_DIR="${PROJECT_ROOT}/.dev/scratch"
|
|
||||||
LOG_FILE="${LOG_DIR}/hygiene-$(date +%Y%m%d-%H%M%S).log"
|
|
||||||
|
|
||||||
# Default allowed root files (can be overridden by .hygiene.json)
|
|
||||||
DEFAULT_ALLOWED_ROOT=(
|
|
||||||
".git"
|
|
||||||
".gitignore"
|
|
||||||
".gitattributes"
|
|
||||||
".editorconfig"
|
|
||||||
".env"
|
|
||||||
".env.example"
|
|
||||||
".env.local"
|
|
||||||
".nvmrc"
|
|
||||||
".node-version"
|
|
||||||
".python-version"
|
|
||||||
".ruby-version"
|
|
||||||
".tool-versions"
|
|
||||||
"README.md"
|
|
||||||
"LICENSE"
|
|
||||||
"CHANGELOG.md"
|
|
||||||
"CONTRIBUTING.md"
|
|
||||||
"CLAUDE.md"
|
|
||||||
"package.json"
|
|
||||||
"package-lock.json"
|
|
||||||
"yarn.lock"
|
|
||||||
"pnpm-lock.yaml"
|
|
||||||
"Makefile"
|
|
||||||
"Dockerfile"
|
|
||||||
"docker-compose.yml"
|
|
||||||
"docker-compose.yaml"
|
|
||||||
"Cargo.toml"
|
|
||||||
"Cargo.lock"
|
|
||||||
"go.mod"
|
|
||||||
"go.sum"
|
|
||||||
"requirements.txt"
|
|
||||||
"setup.py"
|
|
||||||
"pyproject.toml"
|
|
||||||
"poetry.lock"
|
|
||||||
"Gemfile"
|
|
||||||
"Gemfile.lock"
|
|
||||||
"tsconfig.json"
|
|
||||||
"jsconfig.json"
|
|
||||||
".eslintrc*"
|
|
||||||
".prettierrc*"
|
|
||||||
"vite.config.*"
|
|
||||||
"webpack.config.*"
|
|
||||||
"rollup.config.*"
|
|
||||||
".hygiene.json"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Temp file patterns to delete
|
|
||||||
TEMP_PATTERNS=(
|
|
||||||
"*.tmp"
|
|
||||||
"*.bak"
|
|
||||||
"*.swp"
|
|
||||||
"*.swo"
|
|
||||||
"*~"
|
|
||||||
".DS_Store"
|
|
||||||
"Thumbs.db"
|
|
||||||
"*.log"
|
|
||||||
"*.orig"
|
|
||||||
"*.pyc"
|
|
||||||
"*.pyo"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Directory patterns to delete
|
|
||||||
TEMP_DIRS=(
|
|
||||||
"__pycache__"
|
|
||||||
".pytest_cache"
|
|
||||||
".mypy_cache"
|
|
||||||
".ruff_cache"
|
|
||||||
"node_modules/.cache"
|
|
||||||
".next/cache"
|
|
||||||
".nuxt/.cache"
|
|
||||||
".turbo"
|
|
||||||
"*.egg-info"
|
|
||||||
".eggs"
|
|
||||||
"dist"
|
|
||||||
"build"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Orphan patterns to identify
|
|
||||||
ORPHAN_PATTERNS=(
|
|
||||||
"test_*.py"
|
|
||||||
"debug_*"
|
|
||||||
"*_backup.*"
|
|
||||||
"*_old.*"
|
|
||||||
"*_bak.*"
|
|
||||||
"*.backup"
|
|
||||||
"temp_*"
|
|
||||||
"tmp_*"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Initialize
|
|
||||||
DELETED_COUNT=0
|
DELETED_COUNT=0
|
||||||
WARNED_COUNT=0
|
|
||||||
ORPHAN_COUNT=0
|
|
||||||
MOVE_ORPHANS=false
|
|
||||||
|
|
||||||
# Logging function
|
|
||||||
log() {
|
|
||||||
local msg="[$(date +%H:%M:%S)] $1"
|
|
||||||
echo "$msg"
|
|
||||||
if [[ -f "$LOG_FILE" ]]; then
|
|
||||||
echo "$msg" >> "$LOG_FILE"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
log_action() {
|
|
||||||
local action="$1"
|
|
||||||
local target="$2"
|
|
||||||
log " $action: $target"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Load project-local config if exists
|
|
||||||
load_config() {
|
|
||||||
if [[ -f "$CONFIG_FILE" ]]; then
|
|
||||||
log "Loading config from $CONFIG_FILE"
|
|
||||||
|
|
||||||
# Check if move_orphans is enabled
|
|
||||||
if command -v jq &>/dev/null; then
|
|
||||||
MOVE_ORPHANS=$(jq -r '.move_orphans // false' "$CONFIG_FILE" 2>/dev/null || echo "false")
|
|
||||||
|
|
||||||
# Load additional allowed root files
|
|
||||||
local extra_allowed
|
|
||||||
extra_allowed=$(jq -r '.allowed_root_files // [] | .[]' "$CONFIG_FILE" 2>/dev/null || true)
|
|
||||||
if [[ -n "$extra_allowed" ]]; then
|
|
||||||
while IFS= read -r file; do
|
|
||||||
DEFAULT_ALLOWED_ROOT+=("$file")
|
|
||||||
done <<< "$extra_allowed"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Load additional temp patterns
|
|
||||||
local extra_temp
|
|
||||||
extra_temp=$(jq -r '.temp_patterns // [] | .[]' "$CONFIG_FILE" 2>/dev/null || true)
|
|
||||||
if [[ -n "$extra_temp" ]]; then
|
|
||||||
while IFS= read -r pattern; do
|
|
||||||
TEMP_PATTERNS+=("$pattern")
|
|
||||||
done <<< "$extra_temp"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Load ignore patterns (files to never touch)
|
|
||||||
IGNORE_PATTERNS=()
|
|
||||||
local ignore
|
|
||||||
ignore=$(jq -r '.ignore_patterns // [] | .[]' "$CONFIG_FILE" 2>/dev/null || true)
|
|
||||||
if [[ -n "$ignore" ]]; then
|
|
||||||
while IFS= read -r pattern; do
|
|
||||||
IGNORE_PATTERNS+=("$pattern")
|
|
||||||
done <<< "$ignore"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
log "Warning: jq not installed, using default config"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check if file should be ignored
|
|
||||||
should_ignore() {
|
|
||||||
local file="$1"
|
|
||||||
local basename
|
|
||||||
basename=$(basename "$file")
|
|
||||||
|
|
||||||
for pattern in "${IGNORE_PATTERNS[@]:-}"; do
|
|
||||||
if [[ "$basename" == $pattern ]] || [[ "$file" == $pattern ]]; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check if file is in allowed root list
|
|
||||||
is_allowed_root() {
|
|
||||||
local file="$1"
|
|
||||||
local basename
|
|
||||||
basename=$(basename "$file")
|
|
||||||
|
|
||||||
for allowed in "${DEFAULT_ALLOWED_ROOT[@]}"; do
|
|
||||||
# Support wildcards in allowed patterns
|
|
||||||
if [[ "$basename" == $allowed ]]; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check if file matches orphan pattern
|
|
||||||
is_orphan() {
|
|
||||||
local file="$1"
|
|
||||||
local basename
|
|
||||||
basename=$(basename "$file")
|
|
||||||
|
|
||||||
for pattern in "${ORPHAN_PATTERNS[@]}"; do
|
|
||||||
if [[ "$basename" == $pattern ]]; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Setup directories
|
|
||||||
setup_dirs() {
|
|
||||||
mkdir -p "$LOG_DIR"
|
|
||||||
if [[ "$MOVE_ORPHANS" == "true" ]]; then
|
|
||||||
mkdir -p "$SCRATCH_DIR"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Start log file
|
|
||||||
echo "=== Project Hygiene Cleanup ===" > "$LOG_FILE"
|
|
||||||
echo "Started: $(date)" >> "$LOG_FILE"
|
|
||||||
echo "Project: $PROJECT_ROOT" >> "$LOG_FILE"
|
|
||||||
echo "" >> "$LOG_FILE"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Delete temp files
|
|
||||||
cleanup_temp_files() {
|
|
||||||
log "Cleaning temp files..."
|
|
||||||
|
|
||||||
for pattern in "${TEMP_PATTERNS[@]}"; do
|
|
||||||
while IFS= read -r -d '' file; do
|
|
||||||
if should_ignore "$file"; then
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
rm -f "$file"
|
|
||||||
log_action "DELETED" "$file"
|
|
||||||
((DELETED_COUNT++))
|
|
||||||
done < <(find "$PROJECT_ROOT" -name "$pattern" -type f -print0 2>/dev/null || true)
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
# Delete temp directories
|
|
||||||
cleanup_temp_dirs() {
|
|
||||||
log "Cleaning temp directories..."
|
|
||||||
|
|
||||||
for pattern in "${TEMP_DIRS[@]}"; do
|
|
||||||
while IFS= read -r -d '' dir; do
|
|
||||||
if should_ignore "$dir"; then
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
rm -rf "$dir"
|
|
||||||
log_action "DELETED DIR" "$dir"
|
|
||||||
((DELETED_COUNT++))
|
|
||||||
done < <(find "$PROJECT_ROOT" -name "$pattern" -type d -print0 2>/dev/null || true)
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
# Warn about unexpected root files
|
|
||||||
check_root_files() {
|
|
||||||
log "Checking root files..."
|
|
||||||
|
|
||||||
local unexpected_files=()
|
|
||||||
|
|
||||||
|
# Silently delete temp files
|
||||||
|
for pattern in "*.tmp" "*.bak" "*.swp" "*~" ".DS_Store"; do
|
||||||
while IFS= read -r -d '' file; do
|
while IFS= read -r -d '' file; do
|
||||||
local basename
|
rm -f "$file" 2>/dev/null && ((DELETED_COUNT++)) || true
|
||||||
basename=$(basename "$file")
|
done < <(find "$PROJECT_ROOT" -name "$pattern" -type f -print0 2>/dev/null || true)
|
||||||
|
done
|
||||||
|
|
||||||
# Skip directories
|
# Only output if we deleted something
|
||||||
[[ -d "$file" ]] && continue
|
if [[ $DELETED_COUNT -gt 0 ]]; then
|
||||||
|
echo "$PREFIX Cleaned $DELETED_COUNT temp files"
|
||||||
|
fi
|
||||||
|
|
||||||
# Skip if in allowed list
|
exit 0
|
||||||
is_allowed_root "$basename" && continue
|
|
||||||
|
|
||||||
# Skip if should be ignored
|
|
||||||
should_ignore "$basename" && continue
|
|
||||||
|
|
||||||
unexpected_files+=("$basename")
|
|
||||||
log_action "WARNING" "Unexpected root file: $basename"
|
|
||||||
((WARNED_COUNT++))
|
|
||||||
done < <(find "$PROJECT_ROOT" -maxdepth 1 -print0 2>/dev/null || true)
|
|
||||||
|
|
||||||
if [[ ${#unexpected_files[@]} -gt 0 ]]; then
|
|
||||||
log ""
|
|
||||||
log "⚠️ Unexpected files in project root:"
|
|
||||||
for f in "${unexpected_files[@]}"; do
|
|
||||||
log " - $f"
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Identify and handle orphaned files
|
|
||||||
handle_orphans() {
|
|
||||||
log "Checking for orphaned files..."
|
|
||||||
|
|
||||||
local orphan_files=()
|
|
||||||
|
|
||||||
for pattern in "${ORPHAN_PATTERNS[@]}"; do
|
|
||||||
while IFS= read -r -d '' file; do
|
|
||||||
if should_ignore "$file"; then
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
orphan_files+=("$file")
|
|
||||||
|
|
||||||
if [[ "$MOVE_ORPHANS" == "true" ]]; then
|
|
||||||
local dest="${SCRATCH_DIR}/$(basename "$file")"
|
|
||||||
# Handle duplicates
|
|
||||||
if [[ -f "$dest" ]]; then
|
|
||||||
dest="${SCRATCH_DIR}/$(date +%Y%m%d%H%M%S)_$(basename "$file")"
|
|
||||||
fi
|
|
||||||
mv "$file" "$dest"
|
|
||||||
log_action "MOVED" "$file -> $dest"
|
|
||||||
else
|
|
||||||
log_action "ORPHAN" "$file"
|
|
||||||
fi
|
|
||||||
((ORPHAN_COUNT++))
|
|
||||||
done < <(find "$PROJECT_ROOT" -name "$pattern" -type f -print0 2>/dev/null || true)
|
|
||||||
done
|
|
||||||
|
|
||||||
if [[ ${#orphan_files[@]} -gt 0 && "$MOVE_ORPHANS" != "true" ]]; then
|
|
||||||
log ""
|
|
||||||
log "📦 Orphaned files found (enable move_orphans in .hygiene.json to auto-move):"
|
|
||||||
for f in "${orphan_files[@]}"; do
|
|
||||||
log " - $f"
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Summary
|
|
||||||
print_summary() {
|
|
||||||
log ""
|
|
||||||
log "=== Cleanup Summary ==="
|
|
||||||
log " Deleted: $DELETED_COUNT items"
|
|
||||||
log " Warnings: $WARNED_COUNT unexpected root files"
|
|
||||||
log " Orphans: $ORPHAN_COUNT files"
|
|
||||||
if [[ "$MOVE_ORPHANS" == "true" ]]; then
|
|
||||||
log " Orphans moved to: $SCRATCH_DIR"
|
|
||||||
fi
|
|
||||||
log " Log file: $LOG_FILE"
|
|
||||||
log ""
|
|
||||||
}
|
|
||||||
|
|
||||||
# Main
|
|
||||||
main() {
|
|
||||||
cd "$PROJECT_ROOT" || exit 1
|
|
||||||
|
|
||||||
load_config
|
|
||||||
setup_dirs
|
|
||||||
|
|
||||||
log "Starting project hygiene cleanup..."
|
|
||||||
log ""
|
|
||||||
|
|
||||||
cleanup_temp_files
|
|
||||||
cleanup_temp_dirs
|
|
||||||
check_root_files
|
|
||||||
handle_orphans
|
|
||||||
|
|
||||||
print_summary
|
|
||||||
|
|
||||||
# Exit with warning code if issues found
|
|
||||||
if [[ $WARNED_COUNT -gt 0 || $ORPHAN_COUNT -gt 0 ]]; then
|
|
||||||
exit 0 # Still success, but logged warnings
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
main "$@"
|
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
"hooks": {
|
"hooks": {
|
||||||
"SessionStart": [
|
"SessionStart": [
|
||||||
{
|
{
|
||||||
"type": "prompt",
|
"type": "command",
|
||||||
"prompt": "STARTUP CHECK - STRICT OUTPUT FORMAT:\n\nALL outputs MUST start with '[projman]' - NO EXCEPTIONS.\n\nPerform quick silent checks:\n1. If MCP venvs missing: '[projman] MCP venvs missing - run setup.sh from installed marketplace'\n2. If git remote != .env config: '[projman] Git remote mismatch - run /project-sync'\n\nIf all checks pass: Say nothing (empty response)\n\nRules:\n- NEVER output without '[projman]' prefix\n- Keep messages under 20 words\n- Be quick and non-blocking\n- Do not read files or perform deep analysis"
|
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/startup-check.sh"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
30
plugins/projman/hooks/startup-check.sh
Executable file
30
plugins/projman/hooks/startup-check.sh
Executable file
@@ -0,0 +1,30 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# projman startup check hook
|
||||||
|
# Checks for common issues at session start
|
||||||
|
# All output MUST have [projman] prefix
|
||||||
|
|
||||||
|
PREFIX="[projman]"
|
||||||
|
|
||||||
|
# Check if MCP venv exists
|
||||||
|
PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$(dirname "$(dirname "$(realpath "$0")")")}"
|
||||||
|
VENV_PATH="$PLUGIN_ROOT/mcp-servers/gitea/.venv/bin/python"
|
||||||
|
|
||||||
|
if [[ ! -f "$VENV_PATH" ]]; then
|
||||||
|
echo "$PREFIX MCP venvs missing - run setup.sh from installed marketplace"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check git remote vs .env config (only if .env exists)
|
||||||
|
if [[ -f ".env" ]]; then
|
||||||
|
CONFIGURED_REPO=$(grep -E "^GITEA_REPO=" .env 2>/dev/null | cut -d'=' -f2 | tr -d '"' || true)
|
||||||
|
if [[ -n "$CONFIGURED_REPO" ]]; then
|
||||||
|
CURRENT_REMOTE=$(git remote get-url origin 2>/dev/null | sed 's/.*[:/]\([^/]*\/[^.]*\).*/\1/' || true)
|
||||||
|
if [[ -n "$CURRENT_REMOTE" && "$CONFIGURED_REPO" != "$CURRENT_REMOTE" ]]; then
|
||||||
|
echo "$PREFIX Git remote mismatch - run /project-sync"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# All checks passed - say nothing
|
||||||
|
exit 0
|
||||||
Reference in New Issue
Block a user