feat(hooks): add hook scripts for /core:yes and /core:qa skills
Scripts: - auto-approve.sh: PermissionRequest hook that allows all tools - ensure-commit.sh: Stop hook that blocks with uncommitted changes - qa-filter.sh: PostToolUse hook that extracts QA failures only - qa-verify.sh: Stop hook that runs QA and blocks on failures Updated skill files to reference script paths. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
1b8f4de543
commit
e5de667849
6 changed files with 177 additions and 4 deletions
|
|
@ -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
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
---
|
||||
|
||||
|
|
|
|||
23
claude/scripts/auto-approve.sh
Executable file
23
claude/scripts/auto-approve.sh
Executable file
|
|
@ -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
|
||||
44
claude/scripts/ensure-commit.sh
Executable file
44
claude/scripts/ensure-commit.sh
Executable file
|
|
@ -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 <files> && git commit -m 'type(scope): description'"
|
||||
}
|
||||
EOF
|
||||
else
|
||||
# No changes, allow stop
|
||||
exit 0
|
||||
fi
|
||||
62
claude/scripts/qa-filter.sh
Executable file
62
claude/scripts/qa-filter.sh
Executable file
|
|
@ -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
|
||||
44
claude/scripts/qa-verify.sh
Executable file
44
claude/scripts/qa-verify.sh
Executable file
|
|
@ -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
|
||||
Loading…
Add table
Reference in a new issue