claude-code-ultimate-guide/examples/hooks/bash/smart-suggest.sh
Florian BRUNIAUX b6ce1ef72f docs: add RPI workflow, changelog fragments, smart-suggest hook + LLM variance
- guide/workflows/rpi.md (new): Research → Plan → Implement, 3-phase pattern
  with explicit GO gates, slash command templates, worked example
- guide/workflows/changelog-fragments.md (new): per-PR YAML fragment enforcement,
  3-layer system (CLAUDE.md rule + UserPromptSubmit hook + CI gate)
- examples/hooks/bash/smart-suggest.sh (new): UserPromptSubmit behavioral coach,
  3-tier priority (enforcement/discovery/contextual), ROI logging
- guide/core/known-issues.md: LLM Day-to-Day Performance Variance section,
  4 root causes (probabilistic inference, MoE routing, infra, context sensitivity)
- guide/workflows/README.md: added RPI entry + quick selection row
- machine-readable/reference.yaml: added entries for changelog_fragments, smart_suggest
- CHANGELOG.md: [Unreleased] entries for all 4 new items
- IDEAS.md: prompt-caching MCP plugin research note (testing in progress)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 16:22:57 +01:00

176 lines
7.6 KiB
Bash

#!/bin/bash
# .claude/hooks/smart-suggest.sh
# Event: UserPromptSubmit
# Non-blocking behavioral coach: suggests the right command or agent based on detected intent
#
# Architecture: 3-tier priority system
# Tier 0 — Enforcement: mandatory workflow gates (runs first, exits on match)
# Tier 1 — Discovery: high-value tools the developer may not know exist
# Tier 2 — Contextual: opportunistic suggestions for common patterns
#
# Properties:
# - Max 1 suggestion per prompt (first match wins)
# - Dedup guard: never suggest a command already in the prompt
# - ROI logging: records suggestions to ~/.claude/logs/smart-suggest.jsonl
# - Silent exit on no match (non-blocking, never fails)
#
# Customization: replace patterns with your own commands/agents.
# Keep Tier 0 small and precise — it intercepts before everything else.
set -euo pipefail
INPUT=$(cat)
PROMPT=$(echo "$INPUT" | jq -r '.prompt // empty' 2>/dev/null || true)
# Skip prompts that are too short to match anything meaningful
if [[ -z "$PROMPT" || ${#PROMPT} -lt 8 ]]; then
exit 0
fi
PROMPT_LC=$(echo "$PROMPT" | tr '[:upper:]' '[:lower:]')
# Skip slash commands — user already knows what they want
if [[ "$PROMPT_LC" =~ ^/ ]]; then
exit 0
fi
LABEL_CMD="Command"
LABEL_AGENT="Agent"
# ─── suggest() ────────────────────────────────────────────────────────────────
# Emits one suggestion then exits (guarantees max 1 suggestion per prompt).
# Dedup: if the command/agent name is already in the prompt, exits silently.
suggest() {
local label="$1" name="$2" reason="$3"
# Strip leading slash, take first token — prevents matching "pr" in "improve"
# e.g. "/changelog:add" → "changelog:add", "code-reviewer" → "code-reviewer"
local check
check=$(echo "$name" | tr '[:upper:]' '[:lower:]' | sed 's|^/||' | awk '{print $1}')
if echo "$PROMPT_LC" | grep -qF "$check"; then
exit 0
fi
# Append to JSONL log for ROI measurement (non-blocking — failure is safe)
local logdir="${HOME}/.claude/logs"
mkdir -p "$logdir" 2>/dev/null || true
printf '{"ts":"%s","suggested":"%s","prompt_len":%d}\n' \
"$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$name" "${#PROMPT}" \
>> "$logdir/smart-suggest.jsonl" 2>/dev/null || true
cat << EOF
{
"hookSpecificOutput": {
"hookEventName": "UserPromptSubmit",
"additionalContext": "[Suggestion] $label: $name -- $reason"
}
}
EOF
exit 0
}
# ==============================================================================
# TIER 0 — Enforcement
# Intercepts workflow violations before any other suggestion.
# Keep patterns tight: a false positive here is more disruptive than a miss.
# ==============================================================================
# Changelog fragment enforcement
# If the developer signals intent to create a PR without mentioning a changelog
# fragment, redirect to the fragment creation step first.
# When fragment/skip-changelog is already mentioned → suggest the PR command normally.
if echo "$PROMPT_LC" | grep -qE '(create.*pr|open.*pr|make.*pr|pull.?request|push.*pr)'; then
if ! echo "$PROMPT_LC" | grep -qE '(changelog|fragment|skip-changelog)'; then
suggest "$LABEL_CMD" "pnpm changelog:add" \
"REQUIRED before merge — creates changelog/fragments/{PR}-{slug}.yml (or add label 'skip-changelog' for tooling PRs)"
else
suggest "$LABEL_CMD" "/pr" \
"PR creation with structured description and auto-detected scope"
fi
fi
# Plan-before-code enforcement
# Intercepts implementation intent when no planning context is present.
# Negative lookahead excludes: plan, test, fix, review, explain, refactor.
if echo "$PROMPT_LC" | grep -qE '(implement|add (the|a|an)|create (the|a|an) (service|router|endpoint|feature|component)|write (the )?(code|logic|service))'; then
if ! echo "$PROMPT_LC" | grep -qE '(plan|test|fix|bug|review|refactor|explain|why|how)'; then
suggest "$LABEL_CMD" "/plan" \
"Run /plan BEFORE coding — Research → Spec → Implement reduces rework"
fi
fi
# ==============================================================================
# TIER 1 — Discovery
# High-value tools the developer may not know about.
# Use multi-word patterns to reduce false positives.
# ==============================================================================
# Failing tests — suggest auto-fix loop before generic debug
if echo "$PROMPT_LC" | grep -qE '(tests? (fail|broken|red)|fix (the |these )?tests?|tests? (are )?failing)'; then
if ! echo "$PROMPT_LC" | grep -qE '(api|server|endpoint|500|404)'; then
suggest "$LABEL_CMD" "/test-loop" \
"Auto-fix loop: 1 test per cycle with convergence stats"
fi
fi
# Lesson learned / rollback scenario
if echo "$PROMPT_LC" | grep -qE '(wrong approach|bad approach|should have|dead end|root cause was|lesson learned|introduced.*bug|regression)'; then
suggest "$LABEL_CMD" "/retex" \
"Capture this lesson in persistent memory (searchable across sessions)"
fi
# Code duplication detected
if echo "$PROMPT_LC" | grep -qE '(duplicat|copy.?past|same code|repeated (code|logic)|identical (code|function))'; then
suggest "$LABEL_CMD" "/dupes" \
"Detect copy-paste code with jscpd before the refactor"
fi
# Monitoring / polling intent — suggest background loop
if echo "$PROMPT_LC" | grep -qE '(check (every|each|all)|wait (until|for)|poll|monitor|watch.*for|when (it.?s |it is )?done|once (the )?deploy|once (the )?ci)'; then
suggest "$LABEL_CMD" "/loop [interval] [prompt]" \
"Run in background while you work: /loop 5m check the deploy status"
fi
# Security-sensitive keywords
if echo "$PROMPT_LC" | grep -qE '(sql injection|xss|owasp|vulnerability|security audit|auth bypass)'; then
suggest "$LABEL_AGENT" "security-auditor" \
"Read-only OWASP audit — detects injection, auth, and data exposure issues"
fi
# Release assembly — suggest structured release command
if echo "$PROMPT_LC" | grep -qE '(cut.*release|prepare.*release|new (version|release)|release.*[0-9]+\.[0-9]|tag.*version|deploy.*main|merge.*main)'; then
suggest "$LABEL_CMD" "/release" \
"Assembles fragments → CHANGELOG section, bumps version, creates release branch"
fi
# ==============================================================================
# TIER 2 — Contextual
# General patterns, lower priority, regex kept tight to avoid noise.
# ==============================================================================
# Code review before merge
if echo "$PROMPT_LC" | grep -qE '(review (the |this )?(code|pr|file)|code review|before (merge|merging))'; then
suggest "$LABEL_AGENT" "code-reviewer" \
"Quality + security + performance review"
fi
# Debugging — generic
if echo "$PROMPT_LC" | grep -qE '(debug (this|the)|fix (this |the )?(bug|crash|error)|stack trace|not (working|loading))'; then
suggest "$LABEL_AGENT" "debugger" \
"Systematic root cause analysis"
fi
# Architecture decision
if echo "$PROMPT_LC" | grep -qE '(architecture (decision|review)|system design|big refactor|large.*refactor)'; then
suggest "$LABEL_AGENT" "architect-review" \
"Validates architectural decisions across scalability, coupling, and tradeoffs"
fi
# Resume previous session
if echo "$PROMPT_LC" | grep -qE '(resume (session|work)|pick up (where|from)|continue (from )?(yesterday|last session))'; then
suggest "$LABEL_CMD" "/resume" \
"Restores session context from persistent memory"
fi
# No match — silent exit (never blocks the user)
exit 0