@skilly-hand/skilly-hand 0.6.1 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,372 @@
1
+ #!/usr/bin/env bash
2
+ # Portable accessibility audit helper aligned to WCAG 2.2 Level AA (W3C)
3
+ # Sources:
4
+ # - https://www.w3.org/WAI/standards-guidelines/wcag/
5
+ # - https://www.w3.org/TR/WCAG22/
6
+ # - https://www.w3.org/WAI/WCAG22/quickref/
7
+
8
+ set -euo pipefail
9
+
10
+ RED='\033[0;31m'
11
+ YELLOW='\033[0;33m'
12
+ CYAN='\033[0;36m'
13
+ GREEN='\033[0;32m'
14
+ BOLD='\033[1m'
15
+ DIM='\033[2m'
16
+ RESET='\033[0m'
17
+
18
+ REPORT_MODE=false
19
+ JSON_MODE=false
20
+ TARGET=""
21
+ ROOT="$(pwd)"
22
+ REPORT_FILE="$ROOT/a11y-audit-report.md"
23
+ REPORT_CONTENT=""
24
+ JSON_ENTRIES=""
25
+
26
+ FILES_SCANNED=0
27
+ FILES_WITH_ISSUES=0
28
+ TOTAL_ISSUES=0
29
+ ISSUES_CRITICAL=0
30
+ ISSUES_SERIOUS=0
31
+ ISSUES_MODERATE=0
32
+
33
+ usage() {
34
+ cat <<USAGE
35
+ Usage:
36
+ bash catalog/skills/accessibility-audit/scripts/audit-a11y.sh [options] [path]
37
+
38
+ Options:
39
+ --report Write markdown report to ./a11y-audit-report.md
40
+ --json Emit JSON findings to stdout
41
+ --help Show this help
42
+
43
+ Default path:
44
+ current directory
45
+ USAGE
46
+ }
47
+
48
+ while [[ $# -gt 0 ]]; do
49
+ case "$1" in
50
+ --report) REPORT_MODE=true; shift ;;
51
+ --json) JSON_MODE=true; shift ;;
52
+ --help|-h) usage; exit 0 ;;
53
+ *) TARGET="$1"; shift ;;
54
+ esac
55
+ done
56
+
57
+ TARGET="${TARGET:-.}"
58
+
59
+ if [[ ! -e "$TARGET" ]]; then
60
+ echo "Target does not exist: $TARGET" >&2
61
+ exit 1
62
+ fi
63
+
64
+ json_escape() {
65
+ echo "$1" | sed 's/\\/\\\\/g; s/"/\\"/g; s/\t/\\t/g; s/\r/\\r/g; s/\n/\\n/g'
66
+ }
67
+
68
+ record_finding() {
69
+ local file="$1"
70
+ local relative_path="$2"
71
+ local category="$3"
72
+ local severity="$4"
73
+ local wcag_sc="$5"
74
+ local lineno="$6"
75
+ local raw_line="$7"
76
+ local message="$8"
77
+
78
+ local color_code=""
79
+ local label=""
80
+
81
+ case "$severity" in
82
+ critical) color_code="$RED"; label="CRITICAL"; ISSUES_CRITICAL=$((ISSUES_CRITICAL + 1)) ;;
83
+ serious) color_code="$YELLOW"; label="SERIOUS"; ISSUES_SERIOUS=$((ISSUES_SERIOUS + 1)) ;;
84
+ moderate) color_code="$CYAN"; label="MODERATE"; ISSUES_MODERATE=$((ISSUES_MODERATE + 1)) ;;
85
+ esac
86
+
87
+ if ! $JSON_MODE; then
88
+ printf " ${color_code}%-14s${RESET} [${DIM}%s${RESET}] line %-4s %s\n" "$category" "$wcag_sc" "$lineno:" "$message"
89
+ echo -e " ${DIM}->${RESET} $raw_line"
90
+ fi
91
+
92
+ if $REPORT_MODE; then
93
+ REPORT_CONTENT+="| \`${relative_path}\` | ${lineno} | ${label} | \`${category}\` | ${wcag_sc} | ${message} |"$'\n'
94
+ fi
95
+
96
+ if $JSON_MODE; then
97
+ [[ -n "$JSON_ENTRIES" ]] && JSON_ENTRIES+=","
98
+ JSON_ENTRIES+="{\"file\":\"$(json_escape "$relative_path")\",\"line\":${lineno},\"severity\":\"${severity}\",\"category\":\"$(json_escape "$category")\",\"wcag\":\"$(json_escape "$wcag_sc")\",\"message\":\"$(json_escape "$message")\",\"code\":\"$(json_escape "$raw_line")\"}"
99
+ fi
100
+ }
101
+
102
+ audit_markup_file() {
103
+ local file="$1"
104
+ local relative_path="${file#$ROOT/}"
105
+ local file_issues=0
106
+
107
+ FILES_SCANNED=$((FILES_SCANNED + 1))
108
+
109
+ while IFS=: read -r lineno line; do
110
+ [[ "$line" =~ ^[[:space:]]*\<\!-- ]] && continue
111
+ local trimmed
112
+ trimmed="$(echo "$line" | sed 's/^[[:space:]]*//')"
113
+ record_finding "$file" "$relative_path" "MISSING-ALT" "critical" "1.1.1" "$lineno" "$trimmed" "Image is missing alt attribute"
114
+ file_issues=$((file_issues + 1))
115
+ done < <(grep -n -E '<img[[:space:]]' "$file" 2>/dev/null | grep -v -E 'alt=' || true)
116
+
117
+ while IFS=: read -r lineno line; do
118
+ [[ "$line" =~ ^[[:space:]]*\<\!-- ]] && continue
119
+ echo "$line" | grep -qE 'aria-label|aria-labelledby|title=' && continue
120
+ local trimmed
121
+ trimmed="$(echo "$line" | sed 's/^[[:space:]]*//')"
122
+ if echo "$trimmed" | grep -qE '<button[^>]*>[[:space:]]*</button>' || echo "$trimmed" | grep -qE '<button[^>]*/>'; then
123
+ record_finding "$file" "$relative_path" "EMPTY-BUTTON" "critical" "4.1.2" "$lineno" "$trimmed" "Button has no text or accessible name"
124
+ file_issues=$((file_issues + 1))
125
+ fi
126
+ done < <(grep -n -E '<button' "$file" 2>/dev/null || true)
127
+
128
+ while IFS=: read -r lineno line; do
129
+ [[ "$line" =~ ^[[:space:]]*\<\!-- ]] && continue
130
+ echo "$line" | grep -qE 'aria-label|aria-labelledby|title=' && continue
131
+ local trimmed
132
+ trimmed="$(echo "$line" | sed 's/^[[:space:]]*//')"
133
+ if echo "$trimmed" | grep -qE '<a[^>]*>[[:space:]]*</a>'; then
134
+ record_finding "$file" "$relative_path" "EMPTY-LINK" "critical" "2.4.4" "$lineno" "$trimmed" "Link has no text or accessible name"
135
+ file_issues=$((file_issues + 1))
136
+ fi
137
+ done < <(grep -n -E '<a[[:space:]]' "$file" 2>/dev/null || true)
138
+
139
+ while IFS=: read -r lineno line; do
140
+ [[ "$line" =~ ^[[:space:]]*\<\!-- ]] && continue
141
+ echo "$line" | grep -qE 'aria-label|aria-labelledby' && continue
142
+ echo "$line" | grep -qE 'type="hidden"|type="submit"|type="button"' && continue
143
+
144
+ local has_label=false
145
+ if echo "$line" | grep -qE 'id="[^"]+"'; then
146
+ local input_id
147
+ input_id="$(echo "$line" | sed -n 's/.*id="\([^"]*\)".*/\1/p' | head -1)"
148
+ if [[ -n "$input_id" ]] && grep -qE "for=\"${input_id}\"" "$file" 2>/dev/null; then
149
+ has_label=true
150
+ fi
151
+ fi
152
+
153
+ if [[ "$has_label" == "false" ]]; then
154
+ local trimmed
155
+ trimmed="$(echo "$line" | sed 's/^[[:space:]]*//')"
156
+ record_finding "$file" "$relative_path" "MISSING-LABEL" "serious" "1.3.1/3.3.2" "$lineno" "$trimmed" "Form control missing label association or ARIA naming"
157
+ file_issues=$((file_issues + 1))
158
+ fi
159
+ done < <(grep -n -E '<(input|select|textarea)[[:space:]]' "$file" 2>/dev/null || true)
160
+
161
+ while IFS=: read -r lineno line; do
162
+ [[ "$line" =~ ^[[:space:]]*\<\!-- ]] && continue
163
+ echo "$line" | grep -qE 'role=|tabindex=' && continue
164
+ local trimmed
165
+ trimmed="$(echo "$line" | sed 's/^[[:space:]]*//')"
166
+ record_finding "$file" "$relative_path" "CLICK-NO-ROLE" "serious" "2.1.1" "$lineno" "$trimmed" "Non-semantic clickable element needs keyboard and role semantics"
167
+ file_issues=$((file_issues + 1))
168
+ done < <(grep -n -E '<(div|span|li)[^>]*((onclick=)|(\(click\)))' "$file" 2>/dev/null || true)
169
+
170
+ while IFS=: read -r lineno line; do
171
+ [[ "$line" =~ ^[[:space:]]*\<\!-- ]] && continue
172
+ local tab_val
173
+ tab_val="$(echo "$line" | grep -oE 'tabindex="[0-9]+"' | grep -oE '[0-9]+' | head -1)"
174
+ if [[ -n "$tab_val" ]] && [[ "$tab_val" -gt 0 ]]; then
175
+ local trimmed
176
+ trimmed="$(echo "$line" | sed 's/^[[:space:]]*//')"
177
+ record_finding "$file" "$relative_path" "TABINDEX-POS" "serious" "2.4.3" "$lineno" "$trimmed" "Positive tabindex can break logical focus order"
178
+ file_issues=$((file_issues + 1))
179
+ fi
180
+ done < <(grep -n -E 'tabindex="[1-9]' "$file" 2>/dev/null || true)
181
+
182
+ local prev_level=0
183
+ while IFS=: read -r lineno line; do
184
+ [[ "$line" =~ ^[[:space:]]*\<\!-- ]] && continue
185
+ local level
186
+ level="$(echo "$line" | grep -oE '<h[1-6]' | grep -oE '[1-6]' | head -1)"
187
+ [[ -z "$level" ]] && continue
188
+
189
+ if [[ "$prev_level" -gt 0 ]] && [[ "$level" -gt $((prev_level + 1)) ]]; then
190
+ local trimmed
191
+ trimmed="$(echo "$line" | sed 's/^[[:space:]]*//')"
192
+ record_finding "$file" "$relative_path" "HEADING-SKIP" "moderate" "1.3.1" "$lineno" "$trimmed" "Heading level skipped"
193
+ file_issues=$((file_issues + 1))
194
+ fi
195
+
196
+ prev_level="$level"
197
+ done < <(grep -n -E '<h[1-6]' "$file" 2>/dev/null || true)
198
+
199
+ if grep -qE '<html[[:space:]]' "$file" 2>/dev/null; then
200
+ if ! grep -qE '<html[^>]*lang=' "$file" 2>/dev/null; then
201
+ local lineno
202
+ lineno="$(grep -n -E '<html' "$file" | head -1 | cut -d: -f1)"
203
+ local trimmed
204
+ trimmed="$(grep -E '<html' "$file" | head -1 | sed 's/^[[:space:]]*//')"
205
+ record_finding "$file" "$relative_path" "MISSING-LANG" "serious" "3.1.1" "$lineno" "$trimmed" "Missing language declaration on html element"
206
+ file_issues=$((file_issues + 1))
207
+ fi
208
+ fi
209
+
210
+ if [[ "$file_issues" -gt 0 ]]; then
211
+ FILES_WITH_ISSUES=$((FILES_WITH_ISSUES + 1))
212
+ TOTAL_ISSUES=$((TOTAL_ISSUES + file_issues))
213
+ if ! $JSON_MODE; then
214
+ echo -e "\n${BOLD}${relative_path}${RESET} - ${RED}${file_issues} issue(s)${RESET}"
215
+ fi
216
+ fi
217
+ }
218
+
219
+ audit_style_file() {
220
+ local file="$1"
221
+ local relative_path="${file#$ROOT/}"
222
+ local file_issues=0
223
+
224
+ FILES_SCANNED=$((FILES_SCANNED + 1))
225
+
226
+ while IFS=: read -r lineno line; do
227
+ [[ "$line" =~ ^[[:space:]]*/\* ]] && continue
228
+
229
+ local context_start=$((lineno > 4 ? lineno - 4 : 1))
230
+ local context_end=$((lineno + 8))
231
+ if sed -n "${context_start},${context_end}p" "$file" 2>/dev/null | grep -qE 'focus-visible'; then
232
+ continue
233
+ fi
234
+
235
+ local trimmed
236
+ trimmed="$(echo "$line" | sed 's/^[[:space:]]*//')"
237
+ record_finding "$file" "$relative_path" "FOCUS-REMOVED" "critical" "2.4.7" "$lineno" "$trimmed" "Focus outline removed without visible focus-visible replacement"
238
+ file_issues=$((file_issues + 1))
239
+ done < <(
240
+ grep -n -E ':focus[[:space:]]*\{' "$file" 2>/dev/null | while IFS=: read -r ln _; do
241
+ local end=$((ln + 5))
242
+ if sed -n "${ln},${end}p" "$file" 2>/dev/null | grep -qE 'outline:[[:space:]]*(none|0)'; then
243
+ echo "${ln}:focus"
244
+ fi
245
+ done || true
246
+ )
247
+
248
+ if [[ "$file_issues" -gt 0 ]]; then
249
+ FILES_WITH_ISSUES=$((FILES_WITH_ISSUES + 1))
250
+ TOTAL_ISSUES=$((TOTAL_ISSUES + file_issues))
251
+ if ! $JSON_MODE; then
252
+ echo -e "\n${BOLD}${relative_path}${RESET} - ${RED}${file_issues} issue(s)${RESET}"
253
+ fi
254
+ fi
255
+ }
256
+
257
+ generate_report() {
258
+ local generated_at
259
+ generated_at="$(date '+%Y-%m-%d %H:%M:%S')"
260
+
261
+ cat > "$REPORT_FILE" <<REPORT_HEAD
262
+ # Accessibility Audit Report (WCAG 2.2 Level AA)
263
+
264
+ Generated: ${generated_at}
265
+ Target: \`${TARGET}\`
266
+
267
+ ## Summary
268
+
269
+ | Metric | Count |
270
+ | --- | --- |
271
+ | Files scanned | ${FILES_SCANNED} |
272
+ | Files with issues | ${FILES_WITH_ISSUES} |
273
+ | Critical | ${ISSUES_CRITICAL} |
274
+ | Serious | ${ISSUES_SERIOUS} |
275
+ | Moderate | ${ISSUES_MODERATE} |
276
+ | Total issues | ${TOTAL_ISSUES} |
277
+
278
+ ## Findings
279
+
280
+ | File | Line | Severity | Category | WCAG SC | Description |
281
+ | --- | --- | --- | --- | --- | --- |
282
+ REPORT_HEAD
283
+
284
+ echo "$REPORT_CONTENT" >> "$REPORT_FILE"
285
+
286
+ cat >> "$REPORT_FILE" <<REPORT_TAIL
287
+
288
+ ## Source References (W3C)
289
+
290
+ - https://www.w3.org/WAI/standards-guidelines/wcag/
291
+ - https://www.w3.org/TR/WCAG22/
292
+ - https://www.w3.org/WAI/WCAG22/quickref/
293
+ - https://www.w3.org/WAI/WCAG22/Understanding/
294
+ - https://www.w3.org/developers/tools/
295
+ REPORT_TAIL
296
+ }
297
+
298
+ if ! $JSON_MODE; then
299
+ echo -e "${BOLD}WCAG 2.2 Level AA accessibility audit${RESET}"
300
+ echo -e "Target: ${CYAN}${TARGET}${RESET}"
301
+ fi
302
+
303
+ MARKUP_FILES=()
304
+ STYLE_FILES=()
305
+
306
+ if [[ -f "$TARGET" ]]; then
307
+ case "$TARGET" in
308
+ *.html|*.htm|*.xhtml|*.jsx|*.tsx) MARKUP_FILES+=("$TARGET") ;;
309
+ *.css|*.scss|*.sass|*.less) STYLE_FILES+=("$TARGET") ;;
310
+ esac
311
+ else
312
+ while IFS= read -r f; do MARKUP_FILES+=("$f"); done < <(
313
+ find "$TARGET" -type f \( -name "*.html" -o -name "*.htm" -o -name "*.xhtml" -o -name "*.jsx" -o -name "*.tsx" \) \
314
+ -not -path "*/node_modules/*" \
315
+ -not -path "*/.git/*" \
316
+ -not -path "*/dist/*" \
317
+ -not -path "*/build/*" \
318
+ -not -path "*/coverage/*" \
319
+ -not -path "*/.next/*" \
320
+ -not -path "*/storybook-static/*" \
321
+ | sort
322
+ )
323
+
324
+ while IFS= read -r f; do STYLE_FILES+=("$f"); done < <(
325
+ find "$TARGET" -type f \( -name "*.css" -o -name "*.scss" -o -name "*.sass" -o -name "*.less" \) \
326
+ -not -path "*/node_modules/*" \
327
+ -not -path "*/.git/*" \
328
+ -not -path "*/dist/*" \
329
+ -not -path "*/build/*" \
330
+ -not -path "*/coverage/*" \
331
+ -not -path "*/.next/*" \
332
+ -not -path "*/storybook-static/*" \
333
+ | sort
334
+ )
335
+ fi
336
+
337
+ for file in "${MARKUP_FILES[@]:-}"; do
338
+ [[ -n "$file" ]] && audit_markup_file "$file"
339
+ done
340
+
341
+ for file in "${STYLE_FILES[@]:-}"; do
342
+ [[ -n "$file" ]] && audit_style_file "$file"
343
+ done
344
+
345
+ if $REPORT_MODE; then
346
+ generate_report
347
+ fi
348
+
349
+ if $JSON_MODE; then
350
+ cat <<JSON
351
+ {"standard":"WCAG 2.2 Level AA","target":"$(json_escape "$TARGET")","summary":{"filesScanned":${FILES_SCANNED},"filesWithIssues":${FILES_WITH_ISSUES},"critical":${ISSUES_CRITICAL},"serious":${ISSUES_SERIOUS},"moderate":${ISSUES_MODERATE},"total":${TOTAL_ISSUES}},"findings":[${JSON_ENTRIES}]}
352
+ JSON
353
+ else
354
+ echo ""
355
+ echo -e "${BOLD}Summary${RESET}"
356
+ echo "Files scanned: $FILES_SCANNED"
357
+ echo "Files with issues: $FILES_WITH_ISSUES"
358
+ echo "Critical: $ISSUES_CRITICAL"
359
+ echo "Serious: $ISSUES_SERIOUS"
360
+ echo "Moderate: $ISSUES_MODERATE"
361
+ echo "Total: $TOTAL_ISSUES"
362
+ if $REPORT_MODE; then
363
+ echo "Report: $REPORT_FILE"
364
+ fi
365
+ if [[ "$TOTAL_ISSUES" -eq 0 ]]; then
366
+ echo -e "${GREEN}No issues found by this heuristic scanner.${RESET}"
367
+ fi
368
+ fi
369
+
370
+ if [[ "$TOTAL_ISSUES" -gt 0 ]]; then
371
+ exit 1
372
+ fi
@@ -0,0 +1,237 @@
1
+ ---
2
+ name: frontend-design
3
+ description: >
4
+ Project-aware frontend design skill that detects the existing tech stack, UI libraries,
5
+ CSS variables, and design tokens before proposing any UI work. Never invents design
6
+ decisions — reads the project first, confirms with the user, then designs.
7
+ Trigger: user asks to build, design, style, or update any UI component, page, or visual element in a frontend project.
8
+ metadata:
9
+ author: skilly-hand
10
+ last-edit: 2026-04-04
11
+ license: Apache-2.0
12
+ version: "1.0.0"
13
+ changelog: "initial release; prevents AI slop design by enforcing project-aware stack detection before any design work; affects all frontend design and styling tasks"
14
+ auto-invoke: "design a component, create a UI, style this page, build a frontend, update the layout"
15
+ allowed-tools: Read, Grep, Glob, Bash, Edit, Write
16
+ allowed-modes:
17
+ - stack-detector # Scan the project for tech stack, UI libs, CSS vars, and design tokens
18
+ - component-designer # Design and implement components following the confirmed project stack
19
+ ---
20
+
21
+ # Frontend Design Guide
22
+
23
+ ## When to Use
24
+
25
+ Use this skill when:
26
+
27
+ - You are designing, building, or restyling a UI component, page, or layout in an existing project.
28
+ - You are adding new visual elements that must match the project's existing design language.
29
+ - You are refactoring styling or structure of frontend code to align with established patterns.
30
+ - You need to choose between UI components, tokens, or styles already available in the project.
31
+
32
+ Do not use this skill for:
33
+
34
+ - Greenfield design system creation with no existing codebase to read from.
35
+ - Backend, API, or data-layer tasks with no UI surface.
36
+ - Design tool work (Figma, Sketch) unrelated to code implementation.
37
+ - Projects where the user has explicitly opted out of stack detection.
38
+
39
+ ---
40
+
41
+ ## Routing Map
42
+
43
+ Always run stack detection first. Never skip to design.
44
+
45
+ | Step | Intent | Sub-agent |
46
+ | --- | --- | --- |
47
+ | 0 (always first) | Detect framework, UI library, CSS approach, tokens, and existing patterns | [agents/stack-detector.md](agents/stack-detector.md) |
48
+ | 1 (only after confirmation) | Design and implement components using confirmed stack | [agents/component-designer.md](agents/component-designer.md) |
49
+
50
+ ---
51
+
52
+ ## Standard Execution Sequence
53
+
54
+ 1. **Run stack detection** — always start with `stack-detector`, no exceptions.
55
+ 2. **Present findings to the user** — surface the detected stack clearly and ask for explicit confirmation.
56
+ 3. **If anything is unclear or ambiguous, ask** — do not proceed with partial or uncertain information.
57
+ 4. **Scan existing tokens and components** — read what already exists before proposing anything.
58
+ 5. **Design with confirmed context only** — hand off to `component-designer` only after step 2 is complete.
59
+
60
+ ---
61
+
62
+ ## Critical Patterns
63
+
64
+ ### Pattern 1: Tech Stack Detection is Non-Negotiable
65
+
66
+ Before writing a single line of UI code or proposing any design, run [agents/stack-detector.md](agents/stack-detector.md).
67
+
68
+ This means:
69
+ - Reading `package.json` to identify the framework and installed libraries.
70
+ - Detecting CSS approach (Tailwind, CSS Modules, styled-components, Sass, vanilla).
71
+ - Finding existing design tokens (CSS custom properties, theme files, `tokens.json`).
72
+ - Sampling real components from the project to understand naming, structure, and styling conventions.
73
+
74
+ If `package.json` is missing or the project root is unclear, stop and ask the user where to look.
75
+
76
+ Never assume a library is present based on file extensions alone — verify it in dependencies.
77
+
78
+ ### Pattern 2: Never Invent Tokens — Read First
79
+
80
+ Before using any color, spacing, font size, border radius, shadow, or z-index value:
81
+
82
+ 1. Search for CSS custom properties: `grep -r "var(--" src/`
83
+ 2. Check for a theme file: `tailwind.config.ts`, `theme.ts`, `tokens.json`, `_variables.scss`
84
+ 3. Check for a design system config: `@mui/material`, `chakra-ui/theme`, `shadcn/ui` config
85
+
86
+ If the token does not exist in the project, do not invent it. Ask the user: "This project doesn't define a token for X. Should I add one, or is there an existing value I missed?"
87
+
88
+ ### Pattern 3: Confirm Before Every Design Decision
89
+
90
+ At every fork — layout choice, component variant, color, interaction pattern — if the right answer is not derivable from the existing code, ask the user.
91
+
92
+ Examples of things that require confirmation:
93
+ - Which existing component to base a new one on.
94
+ - Whether to use Tailwind utility classes or a CSS module.
95
+ - Whether to match a specific existing page's spacing rhythm or start fresh.
96
+ - Which breakpoints the project already targets.
97
+
98
+ Short, specific questions are better than long ambiguous ones. One question at a time if possible.
99
+
100
+ ### Pattern 4: Follow the Project's Visual Language
101
+
102
+ After stack detection, read 3–5 existing components before proposing any design. Identify:
103
+ - The naming convention (PascalCase components, BEM CSS, camelCase tokens, etc.)
104
+ - The composition pattern (atomic components, compound components, render props, slots)
105
+ - The styling approach (co-located styles, global theme, utility-first classes)
106
+
107
+ Every new component or style must feel like it was written by the same team that wrote the existing code — not imported from a different design system.
108
+
109
+ ---
110
+
111
+ ## What Not To Do
112
+
113
+ These are the most critical rules. Violating any of them produces AI slop.
114
+
115
+ - **Never assume a UI library is present** without verifying it in `package.json`. Shadcn and Radix look similar in JSX — check the deps.
116
+ - **Never pick colors, fonts, or spacing values not already in the project**. If the project has no purple, do not introduce purple.
117
+ - **Never use Inter as a default font** unless it is explicitly declared in the project. Inter is a sign of uncontextualized AI output.
118
+ - **Never generate a component without reading at least one existing component first**. The project's conventions must be the template.
119
+ - **Never apply a generic layout** (hero + cards + CTA, standard nav + footer) without verifying the project already uses or wants that structure.
120
+ - **Never chain design decisions silently**. If one decision implies a downstream choice (e.g. using a grid library implies a layout system), surface it.
121
+ - **Never proceed after ambiguity**. If the detected stack is inconsistent (e.g., Tailwind and styled-components both present), stop and ask which one is canonical.
122
+ - **Never treat a partial stack detection as complete**. If `package.json` was readable but no component files were found, say so and ask for the component directory.
123
+ - **Never ship a "placeholder" or "you can customize this later" design**. Every value must be intentional and project-derived.
124
+ - **Never skip the confirmation step** even if the stack looks obvious. One confirmation prevents ten corrections.
125
+
126
+ ---
127
+
128
+ ## Decision Tree
129
+
130
+ ```text
131
+ User asks for UI work
132
+ -> Has stack-detector been run and confirmed by user?
133
+ NO -> Run stack-detector, present findings, ask for confirmation
134
+ YES -> Continue
135
+
136
+ Is the requested component similar to an existing one in the project?
137
+ YES -> Read the existing component, use it as the structural and styling template
138
+ NO -> Ask the user which existing component is closest, or if this is a net-new pattern
139
+
140
+ Does the design require a token/value (color, spacing, font) not yet found in the project?
141
+ YES -> Ask the user: add a new token, use an existing one, or clarify?
142
+ NO -> Use the existing token
143
+
144
+ Is the CSS approach Tailwind?
145
+ YES -> Use only classes declared in tailwind.config; no arbitrary values unless project already uses them
146
+ NO -> Continue
147
+
148
+ Is the CSS approach CSS Modules or Sass?
149
+ YES -> Follow the naming convention of existing .module.css or .scss files exactly
150
+ NO -> Continue
151
+
152
+ Is the CSS approach styled-components or CSS-in-JS?
153
+ YES -> Match the theme structure; use theme.colors/spacing/typography from the existing theme provider
154
+ NO -> Use whatever CSS approach was detected; if none detected, ask the user
155
+
156
+ Ready to implement?
157
+ YES -> Hand off to component-designer with full confirmed context
158
+ ```
159
+
160
+ ---
161
+
162
+ ## Code Examples
163
+
164
+ ### Example 1: Detecting CSS custom properties in a project
165
+
166
+ ```bash
167
+ # Find all CSS variables defined at :root
168
+ grep -rn ":root" src/ --include="*.css" --include="*.scss"
169
+
170
+ # Find all usages of CSS custom properties
171
+ grep -rn "var(--" src/ --include="*.css" --include="*.scss" --include="*.tsx" --include="*.vue"
172
+ ```
173
+
174
+ ### Example 2: Identifying a Tailwind project and reading its token config
175
+
176
+ ```bash
177
+ # Check if Tailwind is installed
178
+ cat package.json | grep tailwind
179
+
180
+ # Read the full color/spacing/font token config
181
+ cat tailwind.config.ts
182
+ ```
183
+
184
+ ### Example 3: Sampling existing components to learn the pattern
185
+
186
+ ```bash
187
+ # Find all component files in a React project
188
+ find src/components -name "*.tsx" | head -5
189
+
190
+ # Read one to understand structure and styling approach
191
+ cat src/components/Button/Button.tsx
192
+ ```
193
+
194
+ ### Example 4: Detecting shadcn/ui vs MUI vs Chakra
195
+
196
+ ```bash
197
+ # shadcn/ui (no package — installed as local files)
198
+ ls src/components/ui/
199
+
200
+ # MUI
201
+ grep '"@mui/material"' package.json
202
+
203
+ # Chakra UI
204
+ grep '"@chakra-ui/react"' package.json
205
+
206
+ # Radix Primitives (often underlies shadcn)
207
+ grep '"@radix-ui' package.json
208
+ ```
209
+
210
+ ---
211
+
212
+ ## Commands
213
+
214
+ ```bash
215
+ # Read project dependencies
216
+ cat package.json | grep -A 50 '"dependencies"'
217
+
218
+ # Detect CSS approach from file extensions
219
+ find src -name "*.module.css" | head -3 # CSS Modules
220
+ find src -name "*.module.scss" | head -3 # Sass Modules
221
+ grep -rl "styled-components\|emotion" src/ # CSS-in-JS
222
+ grep -rl "cn(\|clsx\|classnames" src/ # Tailwind class merging
223
+
224
+ # Find design token files
225
+ find . -name "tokens.json" -o -name "theme.ts" -o -name "_variables.scss" 2>/dev/null
226
+
227
+ # List existing components
228
+ find src/components -maxdepth 2 -name "*.tsx" -o -name "*.vue" | head -10
229
+ ```
230
+
231
+ ---
232
+
233
+ ## Resources
234
+
235
+ - Stack detection procedure: [agents/stack-detector.md](agents/stack-detector.md)
236
+ - Component design rules: [agents/component-designer.md](agents/component-designer.md)
237
+ - Full scan checklist: [assets/stack-scan-checklist.md](assets/stack-scan-checklist.md)