Consolidates agentic-flows plugin (v0.5.0) into claude/ folder: - 14 autonomous agents (engineer tiers, PR resolver, training collector) - 11 skills (flow orchestration, pattern library, KB learning) - 8 commands (/junior, /senior, /engineer, /qa, /analyze, /delegate, /seed, /learn) - 3 patterns (agent-memory, capability-tiers, handoff-protocol) - MCP server config, hooks, marketplace metadata Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
168 lines
5.3 KiB
Bash
Executable file
168 lines
5.3 KiB
Bash
Executable file
#!/bin/bash
|
|
# UserPromptSubmit Hook - Inject context before processing user prompt
|
|
# Provides current git state, project type, and active task context
|
|
#
|
|
# Input: JSON with user's prompt text
|
|
# Output: JSON with optional systemMessage containing context
|
|
#
|
|
# Timeout: 5 seconds - must be lightweight
|
|
|
|
set -euo pipefail
|
|
|
|
# Read input from stdin
|
|
input=$(cat)
|
|
|
|
# Get project directory
|
|
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(pwd)}"
|
|
|
|
# Quick exit if not a valid directory
|
|
if [[ ! -d "$PROJECT_DIR" ]]; then
|
|
echo '{"continue": true}'
|
|
exit 0
|
|
fi
|
|
|
|
# =============================================================================
|
|
# GATHER CONTEXT (keep it fast!)
|
|
# =============================================================================
|
|
|
|
CONTEXT_PARTS=()
|
|
|
|
# --- Git State (fast operations only) ---
|
|
if [[ -d "$PROJECT_DIR/.git" ]]; then
|
|
GIT_BRANCH=$(git -C "$PROJECT_DIR" branch --show-current 2>/dev/null || echo "")
|
|
|
|
if [[ -n "$GIT_BRANCH" ]]; then
|
|
# Count uncommitted changes (staged + unstaged)
|
|
CHANGES=$(git -C "$PROJECT_DIR" status --porcelain 2>/dev/null | wc -l | tr -d ' ')
|
|
|
|
# Build git context
|
|
GIT_INFO="Branch: \`$GIT_BRANCH\`"
|
|
|
|
if [[ "$CHANGES" -gt 0 ]]; then
|
|
GIT_INFO+=" | $CHANGES uncommitted change(s)"
|
|
fi
|
|
|
|
# Check for unpushed commits (fast check)
|
|
AHEAD=$(git -C "$PROJECT_DIR" rev-list --count @{u}..HEAD 2>/dev/null || echo "0")
|
|
if [[ "$AHEAD" -gt 0 ]]; then
|
|
GIT_INFO+=" | $AHEAD unpushed commit(s)"
|
|
fi
|
|
|
|
CONTEXT_PARTS+=("$GIT_INFO")
|
|
fi
|
|
fi
|
|
|
|
# --- Project Type Detection (cached from session-start, or quick detect) ---
|
|
PROJECT_TYPE="${PROJECT_TYPE:-}"
|
|
|
|
if [[ -z "$PROJECT_TYPE" ]]; then
|
|
# Quick detection - only check most common markers
|
|
if [[ -f "$PROJECT_DIR/go.mod" ]]; then
|
|
PROJECT_TYPE="go"
|
|
elif [[ -f "$PROJECT_DIR/package.json" ]]; then
|
|
PROJECT_TYPE="node"
|
|
elif [[ -f "$PROJECT_DIR/composer.json" ]]; then
|
|
PROJECT_TYPE="php"
|
|
elif [[ -f "$PROJECT_DIR/pyproject.toml" ]] || [[ -f "$PROJECT_DIR/requirements.txt" ]]; then
|
|
PROJECT_TYPE="python"
|
|
elif [[ -f "$PROJECT_DIR/Cargo.toml" ]]; then
|
|
PROJECT_TYPE="rust"
|
|
fi
|
|
fi
|
|
|
|
if [[ -n "$PROJECT_TYPE" ]]; then
|
|
CONTEXT_PARTS+=("Project: $PROJECT_TYPE")
|
|
fi
|
|
|
|
# --- Active Task/TODO Detection ---
|
|
# Check for common task list files that might exist in the project
|
|
TASK_CONTEXT=""
|
|
|
|
# Check for Claude's task tracking (if any)
|
|
if [[ -f "$PROJECT_DIR/.claude/tasks.json" ]]; then
|
|
# Count pending tasks from Claude's task list
|
|
PENDING=$(jq -r '[.[] | select(.status == "pending" or .status == "in_progress")] | length' "$PROJECT_DIR/.claude/tasks.json" 2>/dev/null || echo "0")
|
|
if [[ "$PENDING" -gt 0 ]]; then
|
|
TASK_CONTEXT="$PENDING active task(s)"
|
|
fi
|
|
fi
|
|
|
|
# Check for TODO.md or similar
|
|
if [[ -z "$TASK_CONTEXT" ]]; then
|
|
for todofile in TODO.md TODO.txt todo.md todo.txt; do
|
|
if [[ -f "$PROJECT_DIR/$todofile" ]]; then
|
|
# Count unchecked items (- [ ] pattern)
|
|
UNCHECKED=$(grep -c '^\s*-\s*\[ \]' "$PROJECT_DIR/$todofile" 2>/dev/null || echo "0")
|
|
if [[ "$UNCHECKED" -gt 0 ]]; then
|
|
TASK_CONTEXT="$UNCHECKED TODO item(s) in $todofile"
|
|
break
|
|
fi
|
|
fi
|
|
done
|
|
fi
|
|
|
|
if [[ -n "$TASK_CONTEXT" ]]; then
|
|
CONTEXT_PARTS+=("$TASK_CONTEXT")
|
|
fi
|
|
|
|
# --- Recently Modified Files (last 5 minutes, max 3 files) ---
|
|
# Only if git is available and we have recent changes
|
|
if [[ -d "$PROJECT_DIR/.git" ]]; then
|
|
# Get files modified in the last 5 minutes (tracked by git)
|
|
RECENT_FILES=$(git -C "$PROJECT_DIR" diff --name-only 2>/dev/null | head -3 | tr '\n' ', ' | sed 's/,$//')
|
|
if [[ -n "$RECENT_FILES" ]]; then
|
|
CONTEXT_PARTS+=("Recent edits: $RECENT_FILES")
|
|
fi
|
|
fi
|
|
|
|
# =============================================================================
|
|
# BUILD OUTPUT
|
|
# =============================================================================
|
|
|
|
# If no context gathered, allow silently
|
|
if [[ ${#CONTEXT_PARTS[@]} -eq 0 ]]; then
|
|
echo '{"continue": true}'
|
|
exit 0
|
|
fi
|
|
|
|
# Build a compact context line
|
|
CONTEXT_MSG="**Context:** "
|
|
CONTEXT_MSG+=$(IFS=' | '; echo "${CONTEXT_PARTS[*]}")
|
|
|
|
# Add project-specific quick command hints based on type
|
|
case "$PROJECT_TYPE" in
|
|
go)
|
|
if command -v core &>/dev/null; then
|
|
CONTEXT_MSG+="\\n**Quick:** \`core go qa --fix\` | \`core go qa --only=test\`"
|
|
else
|
|
CONTEXT_MSG+="\\n**Quick:** \`go test ./...\` | \`go fmt ./...\`"
|
|
fi
|
|
;;
|
|
node)
|
|
CONTEXT_MSG+="\\n**Quick:** \`npm test\` | \`npm run lint\`"
|
|
;;
|
|
php)
|
|
if command -v core &>/dev/null; then
|
|
CONTEXT_MSG+="\\n**Quick:** \`core php qa --fix\` | \`core php test\`"
|
|
else
|
|
CONTEXT_MSG+="\\n**Quick:** \`composer test\` | \`./vendor/bin/pint\`"
|
|
fi
|
|
;;
|
|
python)
|
|
CONTEXT_MSG+="\\n**Quick:** \`pytest\` | \`ruff check .\`"
|
|
;;
|
|
rust)
|
|
CONTEXT_MSG+="\\n**Quick:** \`cargo test\` | \`cargo clippy\`"
|
|
;;
|
|
esac
|
|
|
|
# Use jq for proper JSON string escaping (jq -sRr @json produces a quoted JSON string)
|
|
ESCAPED_MSG=$(printf '%s' "$CONTEXT_MSG" | jq -sRr @json)
|
|
|
|
# Output JSON response (ESCAPED_MSG already includes quotes from jq)
|
|
cat << EOF
|
|
{
|
|
"continue": true,
|
|
"systemMessage": $ESCAPED_MSG
|
|
}
|
|
EOF
|