fix(hooks): tighten design-toolchain trigger + add fire-log counter
The 07-02 tightening left bare tokens common in non-UI talk (design, component, theme, transition, frontend, palette) -> ~6 false-fires/session during the ECC config audit. Dropped them; dashboard now word-boundary matched (kills the ecc_dashboard.py filename match, keeps 'admin dashboard'); kept animation; added 'front-end design' bigram. Each fire now logs time+token+excerpt to a light file so 're-firing?' is measured, not argued. Regression test 18/18, shellcheck clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XpphkdTosUzokBDNG7PToS
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
9f4cc8e090
commit
3d0ee262c1
@@ -9,6 +9,10 @@
|
||||
# This is a soft nudge: the tiered rule itself says trivial work uses NO
|
||||
# toolchain, so a false positive (e.g. "API design") costs only a reminder the
|
||||
# model can disregard. Always exits 0 so it never blocks prompt submission.
|
||||
#
|
||||
# Every fire appends one line (time, matched token, prompt excerpt) to
|
||||
# ~/.claude/logs/design-toolchain-fires.log — a counter so the next "is it
|
||||
# over-firing?" decision is measured, not anecdotal.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
@@ -24,15 +28,25 @@ prompt="$(printf '%s' "$input" \
|
||||
lc="$(printf '%s' "$prompt" | tr '[:upper:]' '[:lower:]')"
|
||||
|
||||
# UI/design build and review signals (FR + EN). Word boundaries (\b) avoid
|
||||
# substring false matches like perform/platform/information. Tightened
|
||||
# 2026-07-02: ultra-generic English tokens (page, form, menu, card, style,
|
||||
# look, screen, interface, color) fired on a large share of NON-UI prompts —
|
||||
# ~200 tokens of reminder each time (measured: 6 fires during a pure config
|
||||
# audit). Kept: unambiguous design vocabulary + FR aesthetic words; specific
|
||||
# compounds (stylesheet, styling, formulaire, écran) still match.
|
||||
pattern='design|redesign|refonte|refont|ui/ux|ux/ui|\bui\b|\bux\b|ui kit|design system|design-system|frontend|front-end|front end|composant|component|\bnavbar\b|\bsidebar\b|\bmodal\b|\bbouton\b|\bbutton\b|formulaire|\bhero\b|\bheader\b|\bfooter\b|dropdown|tooltip|\bbadge\b|\bchart\b|graphique|accordion|carousel|\bslider\b|landing|dashboard|homepage|home page|\baccueil\b|\bécran\b|\becran\b|portfolio|maquette|mockup|wireframe|prototype|\bjoli\b|\bjolie\b|\bbeau\b|\bbelle\b|esth[eé]tique|aesthetic|\bvisuel\b|\bvisual\b|embellir|fignol|peaufin|polish|styliser|styling|stylesheet|\bskin\b|charte graphique|\bbrand\b|branding|\blogo\b|favicon|ic[oô]ne|\bicon\b|\bcss\b|tailwind|shadcn|couleur|palette|gradient|d[eé]grad[eé]|\bombre\b|spacing|espacement|\bmarge\b|\bpadding\b|\bmargin\b|\bradius\b|arrondi|\bhover\b|dark mode|light mode|\btheme\b|th[eè]me|typograph|\bfont\b|\bfonts\b|font pairing|\bpolice\b|animation|\bmotion\b|transition|micro-interaction|keyframe|glassmorph|neumorph|claymorph|skeuomorph|brutalis|bento|minimalis|responsive|figma'
|
||||
# substring false matches like perform/platform/information.
|
||||
# Tightened 2026-07-02: dropped ultra-generic tokens (page, form, menu, card,
|
||||
# style, look, screen, interface, color) that fired on non-UI prompts.
|
||||
# Tightened again 2026-07-03: dropped bare design|component|composant|theme|
|
||||
# thème|transition|frontend|front-end|palette — all common in non-UI technical
|
||||
# talk (a design decision, a system component, the theme of a discussion, a
|
||||
# state transition, frontend architecture). Kept as UI-specific compounds:
|
||||
# "design system", "redesign", "front-?end design". dashboard -> \bdashboard\b
|
||||
# so a filename like ecc_dashboard.py no longer matches while "admin dashboard"
|
||||
# still does. animation kept (rarely non-UI).
|
||||
pattern='redesign|refonte|refont|ui/ux|ux/ui|\bui\b|\bux\b|ui kit|design system|design-system|front-?end design|\bnavbar\b|\bsidebar\b|\bmodal\b|\bbouton\b|\bbutton\b|formulaire|\bhero\b|\bheader\b|\bfooter\b|dropdown|tooltip|\bbadge\b|\bchart\b|graphique|accordion|carousel|\bslider\b|landing|\bdashboard\b|homepage|home page|\baccueil\b|\bécran\b|\becran\b|portfolio|maquette|mockup|wireframe|prototype|\bjoli\b|\bjolie\b|\bbeau\b|\bbelle\b|esth[eé]tique|aesthetic|\bvisuel\b|\bvisual\b|embellir|fignol|peaufin|polish|styliser|styling|stylesheet|\bskin\b|charte graphique|\bbrand\b|branding|\blogo\b|favicon|ic[oô]ne|\bicon\b|\bcss\b|tailwind|shadcn|couleur|gradient|d[eé]grad[eé]|\bombre\b|spacing|espacement|\bmarge\b|\bpadding\b|\bmargin\b|\bradius\b|arrondi|\bhover\b|dark mode|light mode|typograph|\bfont\b|\bfonts\b|font pairing|\bpolice\b|animation|\bmotion\b|micro-interaction|keyframe|glassmorph|neumorph|claymorph|skeuomorph|brutalis|bento|minimalis|responsive|figma'
|
||||
|
||||
if printf '%s' "$lc" | grep -Eq "$pattern"; then
|
||||
# Counter: log the fire (time, matched token, excerpt) — best-effort, never blocks.
|
||||
logf="${HOME}/.claude/logs/design-toolchain-fires.log"
|
||||
mkdir -p "$(dirname "$logf")" 2>/dev/null || true
|
||||
printf '%s\t%s\t%s\n' "$(date -Iseconds)" \
|
||||
"$(printf '%s' "$lc" | grep -oiE "$pattern" | head -1 || true)" \
|
||||
"$(printf '%s' "$prompt" | tr '\n\t' ' ' | cut -c1-100)" >> "$logf" 2>/dev/null || true
|
||||
cat <<'EOF'
|
||||
[design-toolchain] UI/design signal detected. Apply CLAUDE.md "Design work — full toolchain (tiered by scope)":
|
||||
- Trivial (≤2 files, single cosmetic value, CSS tweak) → /hotfix, NO toolchain.
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
#!/usr/bin/env bash
|
||||
# lib/tests/design-toolchain-reminder.test.sh
|
||||
set -u
|
||||
H="$(cd "$(dirname "$0")/../.." && pwd)/hooks/design-toolchain-reminder.sh"
|
||||
pass=0; fail=0
|
||||
check() { if [ "$2" = "$3" ]; then pass=$((pass+1)); else fail=$((fail+1));
|
||||
printf 'FAIL %s: got[%s] want[%s]\n' "$1" "$2" "$3"; fi; }
|
||||
# fire() -> "fire" if the hook emits the reminder, else "quiet".
|
||||
fire() { if printf '{"prompt":"%s"}' "$1" | bash "$H" | grep -q "design-toolchain"; then
|
||||
echo fire; else echo quiet; fi; }
|
||||
|
||||
# --- Dropped/neutralized tokens must be QUIET (non-UI senses) ---
|
||||
check D1-design "$(fire 'a design decision for the API')" quiet
|
||||
check D2-component "$(fire 'this system component')" quiet
|
||||
check D3-composant "$(fire 'le composant backend')" quiet
|
||||
check D4-theme "$(fire 'the theme of the audit')" quiet
|
||||
check D5-transition "$(fire 'state transition to develop')" quiet
|
||||
check D6-frontend "$(fire 'frontend architecture')" quiet
|
||||
check D7-palette "$(fire 'a palette of options')" quiet
|
||||
check D8-dash-file "$(fire 'ecc_dashboard.py')" quiet
|
||||
|
||||
# --- Real UI signals must still FIRE ---
|
||||
check F1-button "$(fire 'add a button')" fire
|
||||
check F2-navbar "$(fire 'the navbar layout')" fire
|
||||
check F3-landing "$(fire 'build a landing page')" fire
|
||||
check F4-glass "$(fire 'a glassmorphism card')" fire
|
||||
check F5-redesign "$(fire 'redesign the app')" fire
|
||||
check F6-frontdesign "$(fire 'frontend design work')" fire
|
||||
check F7-admin-dash "$(fire 'admin dashboard screen')" fire
|
||||
check F8-animation "$(fire 'add an animation')" fire
|
||||
check F9-designsys "$(fire 'our design system')" fire
|
||||
|
||||
# --- Fire is logged (time + token + excerpt) ---
|
||||
tmp="$(mktemp -d)"
|
||||
printf '{"prompt":"a glassmorphism card"}' | HOME="$tmp" bash "$H" >/dev/null 2>&1
|
||||
check L1-logged "$(grep -c 'glassmorph' "$tmp/.claude/logs/design-toolchain-fires.log" 2>/dev/null)" 1
|
||||
rm -rf "$tmp"
|
||||
|
||||
printf 'PASS=%s FAIL=%s\n' "$pass" "$fail"; [ "$fail" -eq 0 ]
|
||||
Reference in New Issue
Block a user