Appearance
suggest-skills.sh
.claude/hooks/suggest-skills.sh
PreToolUse
Nudges the workflow gates at their natural boundaries. It only injects a reminder into context; Claude decides whether the change is consequential enough to actually run the skill. Deterministic on the event, judgment on the response. • green composer ci:check → suggest qa-code (diff conformance) before committing • PR raised (gh / GitHub MCP) → suggest find-learnings if the work taught something • about to create issues → suggest check-reasoning (plan critique) on the plan first Registered in settings.json under PreToolUse (Bash, issue_write) and PostToolUse (Bash, create_pull_request). jq parses the hook's stdin JSON.
Source
bash
#!/usr/bin/env bash
#
# PreToolUse / PostToolUse hook — nudges the workflow gates at their natural
# boundaries. It only *injects a reminder* into context; Claude decides whether
# the change is consequential enough to actually run the skill. Deterministic on
# the event, judgment on the response.
#
# • green `composer ci:check` → suggest `qa-code` (diff conformance) before committing
# • PR raised (gh / GitHub MCP) → suggest `find-learnings` if the work taught something
# • about to create issues → suggest `check-reasoning` (plan critique) on the plan first
#
# Registered in settings.json under PreToolUse (Bash, issue_write) and
# PostToolUse (Bash, create_pull_request). jq parses the hook's stdin JSON.
set -euo pipefail
input="$(cat)"
event="$(jq -r '.hook_event_name // empty' <<<"$input")"
tool="$(jq -r '.tool_name // empty' <<<"$input")"
cmd="$(jq -r '.tool_input.command // empty' <<<"$input")"
emit() {
jq -n --arg ctx "$1" --arg ev "$event" \
'{hookSpecificOutput: {hookEventName: $ev, additionalContext: $ctx}}'
exit 0
}
# A phrase is only a real invocation if it sits at a command boundary (start of
# line or after ; && || |), not buried in an echo/grep argument. Anchoring this
# way stops the hook firing on commands that merely mention the phrase.
at_boundary() { grep -qE "(^|[;&|])[[:space:]]*$1" <<<"$cmd"; }
case "$event" in
PreToolUse)
if [ "$tool" = "mcp__github__issue_write" ] || { [ "$tool" = "Bash" ] && at_boundary 'gh[[:space:]]+issue[[:space:]]+create'; }; then
emit "Gate: you're about to create issues. If these come from a fresh plan or brainstorm that has NOT been red-teamed, run the \`check-reasoning\` skill (plan critique) on it first — it's far cheaper to fix a plan than the issues it spawns. Skip if the plan was already critiqued or the change is trivial."
fi
;;
PostToolUse)
if [ "$tool" = "mcp__github__create_pull_request" ] || { [ "$tool" = "Bash" ] && at_boundary 'gh[[:space:]]+pr[[:space:]]+create'; }; then
emit "Gate: a PR was just raised. If this work revealed a non-obvious root cause, a footgun, or an emerging convention, run the \`find-learnings\` skill to record it in docs/learnings/. Skip if it taught nothing reusable."
fi
if [ "$tool" = "mcp__github__issue_read" ] || { [ "$tool" = "Bash" ] && at_boundary 'gh[[:space:]]+issue[[:space:]]+view'; }; then
emit "Gate: a GitHub issue was just read. If you're about to IMPLEMENT it (not planning/triaging/grinding a batch), consider \`be-complete\` so it ships as one complete, tested change — not a stub you finish later. Skip during PLAN sweeps and for trivial issues."
fi
if [ "$tool" = "Bash" ] && at_boundary 'composer[[:space:]]+(run[[:space:]]+)?ci:check'; then
out="$(jq -r '(.tool_response.stdout // "") + "\n" + (.tool_response.stderr // "")' <<<"$input")"
# Suppress on a failed gate — only nudge when it looks green.
if ! grep -qE 'FAILED|FAILURES|\[ERROR\]|did not pass' <<<"$out"; then
emit "Gate: ci:check passed. Before committing a feature or anything architectural/risky, run \`qa-code\` to check the diff against our rules + architecture — and \`check-reasoning\` to red-team the decision if it's a consequential call. Skip for routine changes."
fi
fi
;;
esac
exit 0