plugins/claude/agentic/scripts/prompt-context.sh
Claude 481c1c557f
feat(claude): add agentic plugin — agents, flows, patterns
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>
2026-02-15 15:05:56 +00:00

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