@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.
- package/CHANGELOG.md +32 -0
- package/README.md +1 -0
- package/catalog/README.md +4 -0
- package/catalog/catalog-index.json +4 -0
- package/catalog/skills/accessibility-audit/SKILL.md +154 -0
- package/catalog/skills/accessibility-audit/manifest.json +37 -0
- package/catalog/skills/accessibility-audit/references/w3c-wcag22-checklist.md +80 -0
- package/catalog/skills/accessibility-audit/scripts/audit-a11y.sh +372 -0
- package/catalog/skills/frontend-design/SKILL.md +237 -0
- package/catalog/skills/frontend-design/agents/component-designer.md +95 -0
- package/catalog/skills/frontend-design/agents/stack-detector.md +154 -0
- package/catalog/skills/frontend-design/assets/stack-scan-checklist.md +58 -0
- package/catalog/skills/frontend-design/manifest.json +27 -0
- package/catalog/skills/life-guard/SKILL.md +180 -0
- package/catalog/skills/life-guard/assets/committee-member-template.md +44 -0
- package/catalog/skills/life-guard/assets/safety-guard-template.md +47 -0
- package/catalog/skills/life-guard/manifest.json +33 -0
- package/catalog/skills/test-driven-development/SKILL.md +159 -0
- package/catalog/skills/test-driven-development/assets/tdd-cycle.md +487 -0
- package/catalog/skills/test-driven-development/manifest.json +25 -0
- package/package.json +1 -1
|
@@ -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)
|