diff --git a/claude/commands/qa.md b/claude/commands/qa.md index 1c78a81..8137441 100644 --- a/claude/commands/qa.md +++ b/claude/commands/qa.md @@ -6,11 +6,11 @@ hooks: - matcher: "Bash" hooks: - type: command - command: "core ai qa-filter" + command: "${CLAUDE_PLUGIN_ROOT}/scripts/qa-filter.sh" Stop: - hooks: - type: command - command: "core ai qa-verify" + command: "${CLAUDE_PLUGIN_ROOT}/scripts/qa-verify.sh" once: true --- diff --git a/claude/commands/yes.md b/claude/commands/yes.md index 5f613ad..efb4d3f 100644 --- a/claude/commands/yes.md +++ b/claude/commands/yes.md @@ -6,11 +6,11 @@ hooks: PermissionRequest: - hooks: - type: command - command: "core ai auto-approve" + command: "${CLAUDE_PLUGIN_ROOT}/scripts/auto-approve.sh" Stop: - hooks: - type: command - command: "core ai ensure-commit" + command: "${CLAUDE_PLUGIN_ROOT}/scripts/ensure-commit.sh" once: true --- diff --git a/claude/scripts/auto-approve.sh b/claude/scripts/auto-approve.sh new file mode 100755 index 0000000..2ddc891 --- /dev/null +++ b/claude/scripts/auto-approve.sh @@ -0,0 +1,23 @@ +#!/bin/bash +# Auto-approve all permission requests during /core:yes mode +# +# PermissionRequest hook that returns allow decision for all tools. +# Used by the /core:yes skill for autonomous task completion. + +read -r input +TOOL=$(echo "$input" | jq -r '.tool_name // empty') + +# Log what we're approving (visible in terminal) +echo "[yes-mode] Auto-approving: $TOOL" >&2 + +# Return allow decision +cat << 'EOF' +{ + "hookSpecificOutput": { + "hookEventName": "PermissionRequest", + "decision": { + "behavior": "allow" + } + } +} +EOF diff --git a/claude/scripts/ensure-commit.sh b/claude/scripts/ensure-commit.sh new file mode 100755 index 0000000..32b210f --- /dev/null +++ b/claude/scripts/ensure-commit.sh @@ -0,0 +1,44 @@ +#!/bin/bash +# Ensure work ends with a commit during /core:yes mode +# +# Stop hook that blocks if uncommitted changes exist. +# Prevents Claude from stopping before work is committed. + +read -r input +STOP_ACTIVE=$(echo "$input" | jq -r '.stop_hook_active // false') + +# Prevent infinite loop - if we already blocked once, allow stop +if [ "$STOP_ACTIVE" = "true" ]; then + exit 0 +fi + +# Check for 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 | grep -v '^\.idea/' | wc -l | tr -d ' ') + +TOTAL=$((UNSTAGED + STAGED + UNTRACKED)) + +if [ "$TOTAL" -gt 0 ]; then + # Build file list for context + FILES="" + if [ "$UNSTAGED" -gt 0 ]; then + FILES="$FILES\nModified: $(git diff --name-only 2>/dev/null | head -3 | tr '\n' ' ')" + fi + if [ "$STAGED" -gt 0 ]; then + FILES="$FILES\nStaged: $(git diff --cached --name-only 2>/dev/null | head -3 | tr '\n' ' ')" + fi + if [ "$UNTRACKED" -gt 0 ]; then + FILES="$FILES\nUntracked: $(git ls-files --others --exclude-standard 2>/dev/null | grep -v '^\.idea/' | head -3 | tr '\n' ' ')" + fi + + cat << EOF +{ + "decision": "block", + "reason": "You have $TOTAL uncommitted changes. Please commit them before stopping.\n$FILES\n\nUse: git add && git commit -m 'type(scope): description'" +} +EOF +else + # No changes, allow stop + exit 0 +fi diff --git a/claude/scripts/qa-filter.sh b/claude/scripts/qa-filter.sh new file mode 100755 index 0000000..6a83930 --- /dev/null +++ b/claude/scripts/qa-filter.sh @@ -0,0 +1,62 @@ +#!/bin/bash +# Filter QA output to show only actionable issues during /core:qa mode +# +# PostToolUse hook that processes QA command output and extracts +# only the failures, hiding verbose success output. + +read -r input +COMMAND=$(echo "$input" | jq -r '.tool_input.command // empty') +OUTPUT=$(echo "$input" | jq -r '.tool_response.stdout // .tool_response.output // empty') +EXIT_CODE=$(echo "$input" | jq -r '.tool_response.exit_code // 0') + +# Only process QA-related commands +case "$COMMAND" in + "core go qa"*|"core php qa"*|"core go test"*|"core php test"*|"core go lint"*|"core php stan"*) + ;; + *) + # Not a QA command, pass through unchanged + echo "$input" + exit 0 + ;; +esac + +# Extract failures from output +FAILURES=$(echo "$OUTPUT" | grep -E "^(FAIL|---\s*FAIL|✗|ERROR|undefined:|error:|panic:)" | head -20) +SUMMARY=$(echo "$OUTPUT" | grep -E "^(fmt:|lint:|test:|pint:|stan:|=== RESULT ===)" | tail -5) + +# Also grab specific error lines with file:line references +FILE_ERRORS=$(echo "$OUTPUT" | grep -E "^[a-zA-Z0-9_/.-]+\.(go|php):[0-9]+:" | head -10) + +if [ -z "$FAILURES" ] && [ "$EXIT_CODE" = "0" ]; then + # All passed - show brief confirmation + cat << 'EOF' +{ + "suppressOutput": true, + "hookSpecificOutput": { + "hookEventName": "PostToolUse", + "additionalContext": "✓ QA passed" + } +} +EOF +else + # Combine failures and file errors + ISSUES="$FAILURES" + if [ -n "$FILE_ERRORS" ]; then + ISSUES="$ISSUES +$FILE_ERRORS" + fi + + # Escape for JSON + ISSUES_ESCAPED=$(echo "$ISSUES" | sed 's/\\/\\\\/g' | sed 's/"/\\"/g' | sed ':a;N;$!ba;s/\n/\\n/g') + SUMMARY_ESCAPED=$(echo "$SUMMARY" | sed 's/\\/\\\\/g' | sed 's/"/\\"/g' | sed ':a;N;$!ba;s/\n/ | /g') + + cat << EOF +{ + "suppressOutput": true, + "hookSpecificOutput": { + "hookEventName": "PostToolUse", + "additionalContext": "## QA Issues\n\n\`\`\`\n$ISSUES_ESCAPED\n\`\`\`\n\n**Summary:** $SUMMARY_ESCAPED" + } +} +EOF +fi diff --git a/claude/scripts/qa-verify.sh b/claude/scripts/qa-verify.sh new file mode 100755 index 0000000..c9257a2 --- /dev/null +++ b/claude/scripts/qa-verify.sh @@ -0,0 +1,44 @@ +#!/bin/bash +# Verify QA passes before stopping during /core:qa mode +# +# Stop hook that runs QA checks and blocks if any failures exist. +# Ensures Claude fixes all issues before completing the task. + +read -r input +STOP_ACTIVE=$(echo "$input" | jq -r '.stop_hook_active // false') + +# Prevent infinite loop +if [ "$STOP_ACTIVE" = "true" ]; then + exit 0 +fi + +# Detect project type and run QA +if [ -f "go.mod" ]; then + PROJECT="go" + RESULT=$(core go qa 2>&1) || true +elif [ -f "composer.json" ]; then + PROJECT="php" + RESULT=$(core php qa 2>&1) || true +else + # Not a Go or PHP project, allow stop + exit 0 +fi + +# Check if QA passed +if echo "$RESULT" | grep -qE "FAIL|ERROR|✗|panic:|undefined:"; then + # Extract top issues for context + ISSUES=$(echo "$RESULT" | grep -E "^(FAIL|ERROR|✗|undefined:|panic:)|^[a-zA-Z0-9_/.-]+\.(go|php):[0-9]+:" | head -5) + + # Escape for JSON + ISSUES_ESCAPED=$(echo "$ISSUES" | sed 's/\\/\\\\/g' | sed 's/"/\\"/g' | sed ':a;N;$!ba;s/\n/\\n/g') + + cat << EOF +{ + "decision": "block", + "reason": "QA still has issues:\n\n$ISSUES_ESCAPED\n\nPlease fix these before stopping." +} +EOF +else + # QA passed, allow stop + exit 0 +fi