feat(design-gate): profile-based toolchain gate + design-tool-gate.sh
design-tool-gate.sh: deterministic design-toolchain state check. Reads the design-core tools from design.profile's `# GATE-BLOCK:` allowlist + their types via `profile.sh show design --plain` (claude-free parse contract), checks each on its own channel (skill symlink / claude plugin list / claude mcp list / command -v). Never reads disabledMcpServers. Exit 0 ready · 10 incomplete · 2 error. Remedy is always a profile (/profile design), never an atomic tool toggle — the profile system stays the single source of truth for activation. magic is required-but-manual: it TRIPS the gate (not advisory) and the output names the MAGIC_API_KEY step. Non-design tools bundled in the profile (browse, plan-*, design-shotgun, graphify) are excluded from the trip via GATE-BLOCK, so the gate fires only on real design tools. design-gate.md: §DECISION rewritten profile-based (tier → run script → branch on 3 groups), replacing the old atomic "ask user to activate ui-ux-pro-max". §DETECTION unchanged. design.profile: add the `# GATE-BLOCK:` allowlist (8 design-core tools); it is a comment, so read_profile/--plain are unaffected. Verified: shellcheck clean; magic-off (real design profile) → exit 10 + API-key line; all active → exit 0; browse-off (non-GATE-BLOCK) → exit 0, no trip. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9d8b4cc38c
commit
3eefb8ad7c
@ -1,4 +1,4 @@
|
|||||||
# DESIGN GATE — Auto-detect design tasks, activate ui-ux-pro-max
|
# DESIGN GATE — Auto-detect design tasks, ensure the design toolchain is active
|
||||||
|
|
||||||
Inline snippet. Include in any agent STEP 0 that may touch UI/design.
|
Inline snippet. Include in any agent STEP 0 that may touch UI/design.
|
||||||
|
|
||||||
@ -25,41 +25,79 @@ Check BOTH the task description AND the filesystem:
|
|||||||
|
|
||||||
## DECISION
|
## DECISION
|
||||||
|
|
||||||
If **at least one signal** is detected:
|
Source of truth for activation is the **profile system** — never an atomic
|
||||||
|
per-tool toggle. The gate's whole job: confirm the design toolchain is active,
|
||||||
|
and if not, point at ONE command — `/profile design`.
|
||||||
|
|
||||||
1. Check if `ui-ux-pro-max`, `frontend-design`, and `design-motion-principles` are active:
|
### 1. Tier — does the gate even apply?
|
||||||
```bash
|
|
||||||
source "$HOME/.claude/lib/detect-plugins.sh"
|
|
||||||
detect_uiux_pro_max && echo "ui-ux-pro-max: ACTIVE" || echo "ui-ux-pro-max: INACTIVE"
|
|
||||||
[ -L "$HOME/.claude/skills/frontend-design" ] && echo "frontend-design: ACTIVE" || echo "frontend-design: INACTIVE"
|
|
||||||
[ -L "$HOME/.claude/skills/design-motion-principles" ] && echo "design-motion-principles: ACTIVE" || echo "design-motion-principles: INACTIVE"
|
|
||||||
```
|
|
||||||
|
|
||||||
2. If **all three ACTIVE** → proceed silently. Design context is fully available.
|
- **Trivial** (≤2 files, single cosmetic value, one CSS tweak — same scope as
|
||||||
|
`/hotfix`) → no design tools required. Skip the gate, proceed.
|
||||||
|
- **Build UI / design system / review-audit** → toolchain required, continue.
|
||||||
|
- In doubt (trivial tweak vs real UI change) → do NOT silently skip: ask the
|
||||||
|
user, or default to the Build tier.
|
||||||
|
|
||||||
3. If **ui-ux-pro-max INACTIVE** → ask the user:
|
Tier does NOT change WHAT gets checked. Every non-trivial design tier draws from
|
||||||
```
|
the one `design` profile — so the gate checks that profile's **design-core
|
||||||
🎨 DESIGN DETECTED — task touches UI/styling.
|
tools** (the `# GATE-BLOCK:` allowlist in `design.profile`: ui-ux-pro-max,
|
||||||
ui-ux-pro-max is not active. Activate it for design-aware guidance?
|
frontend-design, emil-design-eng, design-motion-principles, design-html,
|
||||||
(yes / no)
|
design-review, design-consultation, magic). The profile also bundles
|
||||||
```
|
browser/plan/shotgun tooling and graphify for convenience; those never trip the
|
||||||
- On **yes** → print `⚡ Activating ui-ux-pro-max...` and proceed with design context.
|
gate. Motion (`design-motion-principles`) and static-HTML (`design-html`) are
|
||||||
- On **no** → print `Proceeding without design plugin.` and continue normally.
|
already in the core set — checked regardless; their CLAUDE.md "+motion /
|
||||||
|
+static" notes say which tool you'll lean on, not a separate activation step.
|
||||||
|
|
||||||
4. If **frontend-design INACTIVE** → warn (non-blocking):
|
### 2. State — run the deterministic check
|
||||||
```
|
|
||||||
ℹ️ frontend-design skill not installed — anti-AI-slop design guidelines unavailable.
|
|
||||||
Install: run install-plugins.sh or symlink skills-external/frontend-design to ~/.claude/skills/frontend-design
|
|
||||||
```
|
|
||||||
|
|
||||||
5. If **design-motion-principles INACTIVE** and task mentions animation/motion/transition → warn (non-blocking):
|
bash "$HOME/.claude/lib/design-tool-gate.sh"
|
||||||
```
|
|
||||||
ℹ️ design-motion-principles skill not installed — motion design guidelines unavailable.
|
It reads the design-core tools (`# GATE-BLOCK:` in `design.profile`) plus their
|
||||||
Install: run install-plugins.sh or symlink skills-external/design-motion-principles to ~/.claude/skills/design-motion-principles
|
types (`profile.sh show design --plain`) and checks each on its own channel —
|
||||||
```
|
skill symlink, `claude plugin list`, `claude mcp list`, `command -v`. It never
|
||||||
|
reads `disabledMcpServers` (unreliable for bi-modal servers like magic/context7).
|
||||||
|
The core set lives in `design.profile`, not in the script or here — single source.
|
||||||
|
|
||||||
|
Exit codes: `0` = ready (proceed) · `10` = incomplete (gate trips) · `2` = error.
|
||||||
|
|
||||||
|
### 3. Branch on the result
|
||||||
|
|
||||||
|
- **0 / `READY`** → proceed silently. Toolchain is active.
|
||||||
|
- **10 / `INCOMPLETE`** → STOP. The script reports up to three groups; relay
|
||||||
|
them and the remedy to the user:
|
||||||
|
|
||||||
|
🎨 DESIGN DETECTED — the design toolchain isn't fully active.
|
||||||
|
activate with /profile design: <skills / ui-ux-pro-max>
|
||||||
|
required + manual step: <e.g. magic — needs MAGIC_API_KEY>
|
||||||
|
→ run /profile design to activate it, then continue.
|
||||||
|
|
||||||
|
- **activate with /profile design** → skills + the plugin; `/profile design`
|
||||||
|
turns them on directly.
|
||||||
|
- **required + manual step** → required tools the profile can't flip silently.
|
||||||
|
**magic lands here: it TRIPS the gate** (it's required for Build), it is NOT
|
||||||
|
a silent "optional". `/profile design` runs `toggle-external.sh` for magic,
|
||||||
|
which needs a valid `MAGIC_API_KEY` in `.env` — tell the user to verify it.
|
||||||
|
- Do NOT hand-activate individual tools. The profile is the unit of activation.
|
||||||
|
- **`unverified` line** (claude CLI absent) → the state of a plugin/mcp couldn't
|
||||||
|
be checked; it does not block. Mention it, proceed.
|
||||||
|
|
||||||
|
### Other toolchains
|
||||||
|
|
||||||
|
The script defaults to the `design` profile. A task needing another profile's
|
||||||
|
toolchain passes it: `design-tool-gate.sh <profile>`. Scope comes from that
|
||||||
|
profile's `# GATE-BLOCK:` line (absent → every skill/plugin/mcp entry). The
|
||||||
|
remedy is always `/profile <that>` — a profile, never a lone tool.
|
||||||
|
|
||||||
## IMPORTANT
|
## IMPORTANT
|
||||||
|
|
||||||
- This gate adds ~5 seconds overhead. Worth it for design quality.
|
- Remedy is ALWAYS a profile (`/profile design`), never an atomic tool toggle —
|
||||||
|
the profile system is the single source of truth for what's active.
|
||||||
|
- magic is REQUIRED (it trips the gate), but `/profile design` only enables it
|
||||||
|
if `MAGIC_API_KEY` is in `.env` — the gate says so; surface that to the user.
|
||||||
|
- The design-core set (what trips the gate) is declared in `design.profile` on
|
||||||
|
the `# GATE-BLOCK:` line(s) — edit there to add/remove a blocking design tool,
|
||||||
|
not in the script.
|
||||||
|
- The state check shells out to `claude` (plugin/mcp list): a few seconds.
|
||||||
|
Trivial / non-design tasks skip it entirely (no signal, or trivial tier).
|
||||||
|
- `design-tool-gate.sh`'s per-type state checks MIRROR
|
||||||
|
`profile.sh:skill_status()` — change one, sync the other.
|
||||||
- Do NOT run this gate on pure backend/API/CLI tasks (no signals = no gate).
|
- Do NOT run this gate on pure backend/API/CLI tasks (no signals = no gate).
|
||||||
- If no signal detected → skip entirely, zero overhead.
|
|
||||||
|
|||||||
141
lib/design-tool-gate.sh
Executable file
141
lib/design-tool-gate.sh
Executable file
@ -0,0 +1,141 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# ============================================================
|
||||||
|
# lib/design-tool-gate.sh — Deterministic design-toolchain state check.
|
||||||
|
#
|
||||||
|
# Answers ONE question for the design gate (design-gate.md §DECISION):
|
||||||
|
# "Is the design toolchain active enough to proceed?"
|
||||||
|
#
|
||||||
|
# Source of truth = the profile system. The gate never activates a single
|
||||||
|
# tool atomically; it checks whether a profile's DESIGN-CORE tools (default
|
||||||
|
# profile: `design`) are active and, if not, points at `/profile <name>`.
|
||||||
|
#
|
||||||
|
# Two inputs from the profile, both claude-free:
|
||||||
|
# - structure: profile.sh show <profile> --plain -> "<type>\t<name>"
|
||||||
|
# - gate scope: the "# GATE-BLOCK:" line(s) in <profile>.profile — the
|
||||||
|
# allowlist of tools the gate trips on. A comment, so
|
||||||
|
# read_profile strips it and --plain never shows it. Absent
|
||||||
|
# -> fall back to every skill/plugin/mcp entry (coarse).
|
||||||
|
#
|
||||||
|
# State (active or not) is checked per channel, by type. These per-type
|
||||||
|
# checks MIRROR profile.sh:skill_status() — change one, sync the other.
|
||||||
|
#
|
||||||
|
# type channel class
|
||||||
|
# gstack|external|personal skill symlink in skills/ blocking
|
||||||
|
# plugin `claude plugin list` -> enabled blocking
|
||||||
|
# mcp | cli `claude mcp list` / command -v required-manual
|
||||||
|
#
|
||||||
|
# Class:
|
||||||
|
# blocking required + `/profile design` activates it directly.
|
||||||
|
# required-manual required but the profile can't flip it silently (API
|
||||||
|
# key / external install) — the gate STILL trips, names
|
||||||
|
# it, and the remedy is `/profile design` + a manual step.
|
||||||
|
# This is where magic lands: required, never silent.
|
||||||
|
# Both classes trip the gate. Tools NOT on the GATE-BLOCK allowlist are
|
||||||
|
# ignored entirely (browser/plan/shotgun tooling, graphify).
|
||||||
|
#
|
||||||
|
# disabledMcpServers is NEVER read — unreliable for bi-modal servers
|
||||||
|
# (magic/context7 can appear there yet be active via another channel).
|
||||||
|
#
|
||||||
|
# Exit: 0 = ready (proceed) · 10 = incomplete (gate trips) · 2 = error.
|
||||||
|
# Usage: design-tool-gate.sh [profile] (default profile: design)
|
||||||
|
# ============================================================
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
REPO="$(cd -P "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
PROFILE_SH="$REPO/lib/profile.sh"
|
||||||
|
PROFILES_DIR="$REPO/lib/profiles"
|
||||||
|
SKILLS_DIR="$REPO/skills"
|
||||||
|
PROFILE="${1:-design}"
|
||||||
|
PROFILE_FILE="$PROFILES_DIR/$PROFILE.profile"
|
||||||
|
|
||||||
|
[ -x "$PROFILE_SH" ] || { echo "design-gate: profile.sh not executable at $PROFILE_SH" >&2; exit 2; }
|
||||||
|
[ -f "$PROFILE_FILE" ] || { echo "design-gate: profile '$PROFILE' not found" >&2; exit 2; }
|
||||||
|
|
||||||
|
# Gate scope: the "# GATE-BLOCK:" allowlist (one or more lines, concatenated).
|
||||||
|
# Empty => fall back to "every gate-relevant entry is in scope" (coarse).
|
||||||
|
core_set="$(grep '^# GATE-BLOCK:' "$PROFILE_FILE" 2>/dev/null \
|
||||||
|
| sed 's/^# GATE-BLOCK:[[:space:]]*//' | tr '\n' ' ' || true)"
|
||||||
|
|
||||||
|
# Membership in the allowlist. Empty allowlist = everything in scope.
|
||||||
|
in_scope() {
|
||||||
|
[ -z "$core_set" ] && return 0
|
||||||
|
case " $core_set " in *" $1 "*) return 0 ;; *) return 1 ;; esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# State of one tool, by type. Mirrors profile.sh:skill_status() — keep in sync.
|
||||||
|
# Echoes: active | inactive | unknown (unknown = can't verify, claude absent)
|
||||||
|
tool_active() {
|
||||||
|
local name="$1" type="$2"
|
||||||
|
case "$type" in
|
||||||
|
gstack|external|personal)
|
||||||
|
if [ -e "$SKILLS_DIR/$name" ]; then echo active; else echo inactive; fi
|
||||||
|
;;
|
||||||
|
plugin)
|
||||||
|
if ! command -v claude >/dev/null 2>&1; then echo unknown; return; fi
|
||||||
|
if claude plugin list 2>/dev/null \
|
||||||
|
| awk -v p="^[[:space:]]*❯ ${name}@" '$0 ~ p {f=1; next} f && /Status:/ {print; exit}' \
|
||||||
|
| grep -q "✔ enabled"
|
||||||
|
then echo active; else echo inactive; fi
|
||||||
|
;;
|
||||||
|
mcp)
|
||||||
|
if ! command -v claude >/dev/null 2>&1; then echo unknown; return; fi
|
||||||
|
if claude mcp list 2>/dev/null | grep -q "^${name}"; then echo active; else echo inactive; fi
|
||||||
|
;;
|
||||||
|
cli)
|
||||||
|
if command -v "$name" >/dev/null 2>&1; then echo active; else echo inactive; fi
|
||||||
|
;;
|
||||||
|
*) echo inactive ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# Structure via the parse contract (claude-free). Fail loud on a bad profile —
|
||||||
|
# an empty read must NOT silently report "ready".
|
||||||
|
plain="$("$PROFILE_SH" show "$PROFILE" --plain 2>/dev/null)" \
|
||||||
|
|| { echo "design-gate: 'profile.sh show $PROFILE --plain' failed" >&2; exit 2; }
|
||||||
|
[ -n "$plain" ] || { echo "design-gate: profile '$PROFILE' is empty or unreadable" >&2; exit 2; }
|
||||||
|
|
||||||
|
blocking=() # inactive, /profile design activates it (skill/plugin)
|
||||||
|
manual=() # inactive, required but needs a manual step (mcp key / cli install)
|
||||||
|
unverified=() # can't check (claude CLI absent)
|
||||||
|
while IFS=$'\t' read -r type name; do
|
||||||
|
[ -n "$type" ] || continue
|
||||||
|
in_scope "$name" || continue # ignore non-core tooling (browser, plan-*, graphify)
|
||||||
|
case "$(tool_active "$name" "$type")" in
|
||||||
|
active) ;;
|
||||||
|
unknown) unverified+=("$name") ;;
|
||||||
|
*)
|
||||||
|
case "$type" in
|
||||||
|
gstack|external|personal|plugin) blocking+=("$name") ;;
|
||||||
|
*) manual+=("$name") ;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done <<< "$plain"
|
||||||
|
|
||||||
|
# Verdict. Either class trips the gate.
|
||||||
|
trip=0
|
||||||
|
if [ "${#blocking[@]}" -gt 0 ] || [ "${#manual[@]}" -gt 0 ]; then trip=1; fi
|
||||||
|
|
||||||
|
if [ "$trip" -eq 0 ]; then
|
||||||
|
echo "design toolchain: READY — profile '$PROFILE' design tools active"
|
||||||
|
if [ "${#unverified[@]}" -gt 0 ]; then
|
||||||
|
echo " note: could not verify (claude CLI absent): ${unverified[*]}"
|
||||||
|
fi
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "design toolchain: INCOMPLETE"
|
||||||
|
if [ "${#blocking[@]}" -gt 0 ]; then
|
||||||
|
echo " activate with /profile $PROFILE: ${blocking[*]}"
|
||||||
|
fi
|
||||||
|
if [ "${#manual[@]}" -gt 0 ]; then
|
||||||
|
echo " required + manual step (API key / external install): ${manual[*]}"
|
||||||
|
case " ${manual[*]} " in
|
||||||
|
*" magic "*) echo " magic needs MAGIC_API_KEY in .env (/profile $PROFILE runs toggle-external.sh)" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
if [ "${#unverified[@]}" -gt 0 ]; then
|
||||||
|
echo " unverified (claude CLI absent): ${unverified[*]}"
|
||||||
|
fi
|
||||||
|
echo " → run: /profile $PROFILE"
|
||||||
|
exit 10
|
||||||
@ -1,6 +1,13 @@
|
|||||||
# DESC: Design work — visual QA, design systems, mockups, polish
|
# DESC: Design work — visual QA, design systems, mockups, polish
|
||||||
# Activate when: building/reviewing UI, picking aesthetics, design tokens.
|
# Activate when: building/reviewing UI, picking aesthetics, design tokens.
|
||||||
# Companion CLIs (advisory): graphify (visual structure).
|
# Companion CLIs (advisory): graphify (visual structure).
|
||||||
|
#
|
||||||
|
# Gate scope (design-tool-gate.sh): only the tools on the GATE-BLOCK lines
|
||||||
|
# below trip the design gate. The rest of this profile (browser/plan/shotgun
|
||||||
|
# tooling, graphify) is bundled for convenience but never blocks. Keep these
|
||||||
|
# lines in sync when adding/removing a core design tool.
|
||||||
|
# GATE-BLOCK: frontend-design ui-ux-pro-max emil-design-eng design-html
|
||||||
|
# GATE-BLOCK: design-motion-principles design-review design-consultation magic
|
||||||
|
|
||||||
# Core design skills (gstack)
|
# Core design skills (gstack)
|
||||||
design-shotgun
|
design-shotgun
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user