start-vibing 4.3.4 → 4.4.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/package.json +2 -2
- package/template/.claude/agents/sd-audit.md +32 -0
- package/template/.claude/skills/e2e-audit/DESIGN.md +294 -0
- package/template/.claude/skills/e2e-audit/SKILL.md +660 -0
- package/template/.claude/skills/e2e-audit/e2e/fixtures/auth.setup.ts +70 -0
- package/template/.claude/skills/e2e-audit/e2e/fixtures/auth.ts +21 -0
- package/template/.claude/skills/e2e-audit/e2e/fixtures/base.ts +90 -0
- package/template/.claude/skills/e2e-audit/e2e/fixtures/storage/.gitkeep +0 -0
- package/template/.claude/skills/e2e-audit/e2e/fixtures/storage/admin.json +50 -0
- package/template/.claude/skills/e2e-audit/e2e/fixtures/storage/manager.json +50 -0
- package/template/.claude/skills/e2e-audit/e2e/fixtures/storage/member.json +50 -0
- package/template/.claude/skills/e2e-audit/e2e/fixtures/storage/owner.json +50 -0
- package/template/.claude/skills/e2e-audit/e2e/pages/dashboard-admin.page.ts +141 -0
- package/template/.claude/skills/e2e-audit/e2e/pages/dashboard-billing.page.ts +47 -0
- package/template/.claude/skills/e2e-audit/e2e/pages/dashboard-chat.page.ts +35 -0
- package/template/.claude/skills/e2e-audit/e2e/pages/dashboard-home.page.ts +134 -0
- package/template/.claude/skills/e2e-audit/e2e/pages/dashboard-integrations.page.ts +334 -0
- package/template/.claude/skills/e2e-audit/e2e/pages/dashboard-knowledge.page.ts +30 -0
- package/template/.claude/skills/e2e-audit/e2e/pages/dashboard-ontology.page.ts +71 -0
- package/template/.claude/skills/e2e-audit/e2e/pages/dashboard-profile.page.ts +38 -0
- package/template/.claude/skills/e2e-audit/e2e/pages/dashboard-teams.page.ts +123 -0
- package/template/.claude/skills/e2e-audit/e2e/pages/dashboard-transcripts.page.ts +109 -0
- package/template/.claude/skills/e2e-audit/e2e/specs/auth/login.spec.ts +59 -0
- package/template/.claude/skills/e2e-audit/e2e/specs/dashboard-admin.spec.ts +233 -0
- package/template/.claude/skills/e2e-audit/e2e/specs/dashboard-billing.spec.ts +44 -0
- package/template/.claude/skills/e2e-audit/e2e/specs/dashboard-chat.spec.ts +50 -0
- package/template/.claude/skills/e2e-audit/e2e/specs/dashboard-home.spec.ts +243 -0
- package/template/.claude/skills/e2e-audit/e2e/specs/dashboard-integrations.spec.ts +472 -0
- package/template/.claude/skills/e2e-audit/e2e/specs/dashboard-knowledge.spec.ts +57 -0
- package/template/.claude/skills/e2e-audit/e2e/specs/dashboard-ontology.spec.ts +72 -0
- package/template/.claude/skills/e2e-audit/e2e/specs/dashboard-profile.spec.ts +48 -0
- package/template/.claude/skills/e2e-audit/e2e/specs/dashboard-teams.spec.ts +247 -0
- package/template/.claude/skills/e2e-audit/e2e/specs/dashboard-transcripts.spec.ts +122 -0
- package/template/.claude/skills/e2e-audit/e2e/specs/security/headers.spec.ts +39 -0
- package/template/.claude/skills/e2e-audit/e2e/specs/security/rbac.spec.ts +92 -0
- package/template/.claude/skills/e2e-audit/e2e/specs/security/xss.spec.ts +74 -0
- package/template/.claude/skills/e2e-audit/e2e/utils/console-collector.ts +89 -0
- package/template/.claude/skills/e2e-audit/e2e/utils/security-helpers.ts +114 -0
- package/template/.claude/skills/e2e-audit/e2e/utils/test-data.ts +64 -0
- package/template/.claude/skills/e2e-audit/runbook.md +115 -0
- package/template/.claude/skills/super-design/SKILL.md +42 -4
- package/template/.claude/skills/super-design/scripts/discover-surfaces.sh +197 -0
- package/template/.claude/skills/super-design/scripts/extract-project-rules.sh +240 -0
- package/template/.claude/skills/super-design/scripts/verify-audit.sh +34 -1
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# extract-project-rules.sh — parse project FORBIDDEN rules from
|
|
3
|
+
# CLAUDE.md / AGENTS.md / .cursorrules into an authoritative rule
|
|
4
|
+
# source for sd-audit Step 3h.
|
|
5
|
+
#
|
|
6
|
+
# User-owned FORBIDDEN tables ("Use Cards on mobile", "Waterfall data
|
|
7
|
+
# fetching", "Make responsive UI only") are HIGHER-PRIORITY than generic
|
|
8
|
+
# Nielsen/WCAG heuristics because the project owner has already
|
|
9
|
+
# codified them as the right answer for this codebase. sd-audit treats
|
|
10
|
+
# violations as primary findings — NOT as a severity bump or a tag on
|
|
11
|
+
# another finding. If CLAUDE.md says "no cards on mobile" and we see a
|
|
12
|
+
# Card in a mobile flex-col, the rule fires as
|
|
13
|
+
# `project-forbidden-use-cards-on-mobile` with the project's own wording.
|
|
14
|
+
#
|
|
15
|
+
# Output: JSON on stdout
|
|
16
|
+
# {
|
|
17
|
+
# "source_files": ["CLAUDE.md", ".claude/CLAUDE.md"],
|
|
18
|
+
# "rules": [
|
|
19
|
+
# {
|
|
20
|
+
# "raw": "Use Cards on mobile",
|
|
21
|
+
# "reason": "Waste space in flex-col",
|
|
22
|
+
# "slug": "use-cards-on-mobile",
|
|
23
|
+
# "audit_applicable": true,
|
|
24
|
+
# "detector_family": "mobile",
|
|
25
|
+
# "template_id": "M2",
|
|
26
|
+
# "detector_hint": "flag <Card> inside flex-col at viewport ≤ 768"
|
|
27
|
+
# }
|
|
28
|
+
# ]
|
|
29
|
+
# }
|
|
30
|
+
#
|
|
31
|
+
# `audit_applicable: false` means the rule is code-level (e.g. "Files >
|
|
32
|
+
# 400 lines", "Use `any` type", "'use client' at top level") and does
|
|
33
|
+
# NOT produce an audit finding. sd-audit only iterates applicable rules.
|
|
34
|
+
set -euo pipefail
|
|
35
|
+
|
|
36
|
+
log() { printf '[extract-project-rules] %s\n' "$*" >&2; }
|
|
37
|
+
|
|
38
|
+
# Files to scan in order of authority.
|
|
39
|
+
CANDIDATE_FILES=(
|
|
40
|
+
"CLAUDE.md"
|
|
41
|
+
"AGENTS.md"
|
|
42
|
+
"GEMINI.md"
|
|
43
|
+
".claude/CLAUDE.md"
|
|
44
|
+
".cursorrules"
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
SOURCE_FILES=()
|
|
48
|
+
for f in "${CANDIDATE_FILES[@]}"; do
|
|
49
|
+
[[ -f "$f" ]] && SOURCE_FILES+=("$f")
|
|
50
|
+
done
|
|
51
|
+
|
|
52
|
+
if [[ ${#SOURCE_FILES[@]} -eq 0 ]]; then
|
|
53
|
+
log "no project rule files found; emitting empty rule set"
|
|
54
|
+
jq -n '{source_files:[],rules:[]}'
|
|
55
|
+
exit 0
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
# Extract FORBIDDEN table rows. We look for Markdown tables under any
|
|
59
|
+
# heading that contains the word "FORBIDDEN" (case-insensitive). Rows
|
|
60
|
+
# have shape: `| <action> | <reason> |`. We skip header, separator, and
|
|
61
|
+
# empty rows.
|
|
62
|
+
#
|
|
63
|
+
# Implementation: awk state machine — enter "in table" when we see a
|
|
64
|
+
# FORBIDDEN-ish heading followed by a table header; exit when we see a
|
|
65
|
+
# blank line followed by a new heading or the next "---" separator
|
|
66
|
+
# OUTSIDE the table.
|
|
67
|
+
extract_rows() {
|
|
68
|
+
local file="$1"
|
|
69
|
+
awk '
|
|
70
|
+
BEGIN { mode=0; seen_header=0 }
|
|
71
|
+
/^#+[[:space:]]+.*[Ff][Oo][Rr][Bb][Ii][Dd][Dd][Ee][Nn]/ { mode=1; seen_header=0; next }
|
|
72
|
+
# Close on next top-level or section heading when we were inside
|
|
73
|
+
mode == 1 && /^#+[[:space:]]/ && !/[Ff][Oo][Rr][Bb][Ii][Dd][Dd][Ee][Nn]/ { mode=0; seen_header=0; next }
|
|
74
|
+
mode == 1 && /^\|.*\|.*\|/ {
|
|
75
|
+
if (seen_header == 0) { seen_header=1; next }
|
|
76
|
+
# skip separator row
|
|
77
|
+
if ($0 ~ /^\|[[:space:]]*[-]+/) { next }
|
|
78
|
+
# split on | and take cells 1 and 2 (cells 0 and last are empty)
|
|
79
|
+
n = split($0, cells, "|");
|
|
80
|
+
# cells[2] = action, cells[3] = reason (after leading |)
|
|
81
|
+
action = cells[2]; reason = cells[3];
|
|
82
|
+
gsub(/^[[:space:]]+|[[:space:]]+$/, "", action);
|
|
83
|
+
gsub(/^[[:space:]]+|[[:space:]]+$/, "", reason);
|
|
84
|
+
if (action != "" && action !~ /^-+$/ && action !~ /^Action$/i) {
|
|
85
|
+
printf "%s\t%s\n", action, reason;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
' "$file"
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
# Slugify: lowercase, non-word → dash, collapse, trim.
|
|
92
|
+
slugify() {
|
|
93
|
+
printf '%s' "$1" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9]+/-/g; s/^-+|-+$//g'
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
# Classify a rule into {audit_applicable, detector_family, template_id,
|
|
97
|
+
# detector_hint}. Keyword-based — additive over time.
|
|
98
|
+
#
|
|
99
|
+
# Audit-applicable families:
|
|
100
|
+
# mobile → M1-M15 (cards on mobile, responsive-only, card grid on mobile, etc.)
|
|
101
|
+
# design → V1-V8 (Cards used everywhere, masonry on mobile, etc.)
|
|
102
|
+
# ux → U1-U10 (no loading states, waterfall fetching hits UX)
|
|
103
|
+
# perf → P1-P10 (bundle size, waterfall as perf issue)
|
|
104
|
+
# a11y → A1-A15 (contrast, focus, etc.)
|
|
105
|
+
#
|
|
106
|
+
# Non-audit (code-level, skip): "Files >", "'use client'", "Use any
|
|
107
|
+
# type", "wildcard imports", "skip typecheck", etc.
|
|
108
|
+
classify() {
|
|
109
|
+
local raw="$1"
|
|
110
|
+
local low
|
|
111
|
+
low="$(printf '%s' "$raw" | tr '[:upper:]' '[:lower:]')"
|
|
112
|
+
|
|
113
|
+
# -- code-level (NOT audit-applicable) --
|
|
114
|
+
case "$low" in
|
|
115
|
+
*"files >"*|*"> 400 lines"*|*"> 300 lines"*|*"wildcard icon"*|\
|
|
116
|
+
*"'use client'"*|*"use \`any\`"*|*"use any type"*|\
|
|
117
|
+
*"relative imports"*|*"skip typecheck"*|*"skip lint"*|\
|
|
118
|
+
*"define types in"*|*"@types"*|*"skip docker"*|*"write in non-english"*|\
|
|
119
|
+
*"commit directly to main"*|*"skip code-simplifier"*|\
|
|
120
|
+
*"auto-document"*|*"mix doc types"*|*"leave docs unlinked"*|\
|
|
121
|
+
*"skip claude.md"*|*"use mui"*|*"skip todo"*|*"stack last change"*|\
|
|
122
|
+
*"skip research"*|*"skip superpowers"*|*"skip documenter"*|\
|
|
123
|
+
*"skip planning"*|*"skip domain"*|*"skip cn utility"*|\
|
|
124
|
+
*"skip flow documentation"*|*"waterfall data fetching"*)
|
|
125
|
+
# note: waterfall IS audit-adjacent via perf, but the primary
|
|
126
|
+
# signal is in package code not in rendered UI. Keep as code-level.
|
|
127
|
+
echo "false code-level - -"
|
|
128
|
+
return
|
|
129
|
+
;;
|
|
130
|
+
esac
|
|
131
|
+
|
|
132
|
+
# -- mobile (M family) --
|
|
133
|
+
case "$low" in
|
|
134
|
+
*"card"*"mobile"*|*"mobile"*"card"*|*"cards on mobile"*|\
|
|
135
|
+
*"masonry grid on mobile"*|*"3d elements on mobile"*)
|
|
136
|
+
echo "true mobile M2 flag <Card>/card components inside vertical stack at viewport ≤ 768"
|
|
137
|
+
return
|
|
138
|
+
;;
|
|
139
|
+
*"responsive ui only"*|*"responsive only"*|*"make responsive"*)
|
|
140
|
+
echo "true mobile M-distinct check mobile viewport is a distinct layout not a scaled-down desktop"
|
|
141
|
+
return
|
|
142
|
+
;;
|
|
143
|
+
*"hamburger-only"*|*"no bottom tabs"*|*"bottom nav"*)
|
|
144
|
+
echo "true mobile M1 require bottom tabs on mobile for primary nav"
|
|
145
|
+
return
|
|
146
|
+
;;
|
|
147
|
+
*"centered modal on mobile"*|*"centered dialog on mobile"*)
|
|
148
|
+
echo "true mobile M6 require bottom-sheet on mobile, not centered dialog"
|
|
149
|
+
return
|
|
150
|
+
;;
|
|
151
|
+
esac
|
|
152
|
+
|
|
153
|
+
# -- design (V family) --
|
|
154
|
+
case "$low" in
|
|
155
|
+
*"neumorphism"*)
|
|
156
|
+
echo "true design V-neumorphism flag neumorphic styles on form surfaces (accessibility risk)"
|
|
157
|
+
return
|
|
158
|
+
;;
|
|
159
|
+
*"scroll animations on dashboard"*)
|
|
160
|
+
echo "true design V-scroll flag scroll-triggered animations on dashboard/app routes"
|
|
161
|
+
return
|
|
162
|
+
;;
|
|
163
|
+
*"mix animation libraries"*)
|
|
164
|
+
echo "true design V-animmix flag presence of multiple animation libs (framer + gsap + lottie)"
|
|
165
|
+
return
|
|
166
|
+
;;
|
|
167
|
+
esac
|
|
168
|
+
|
|
169
|
+
# -- ux (U family) --
|
|
170
|
+
case "$low" in
|
|
171
|
+
*"skip loading.tsx"*|*"no loading state"*|*"skip skeleton"*)
|
|
172
|
+
echo "true ux U3 flag absence of loading state on data-fetching page"
|
|
173
|
+
return
|
|
174
|
+
;;
|
|
175
|
+
*"skip empty state"*|*"no empty state"*)
|
|
176
|
+
echo "true ux U4 flag absence of empty state on zero-data views"
|
|
177
|
+
return
|
|
178
|
+
;;
|
|
179
|
+
esac
|
|
180
|
+
|
|
181
|
+
# -- default: unknown but still emit, detector_family=generic --
|
|
182
|
+
echo "true generic - match rule text against page copy / component names"
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
# Build the rules array.
|
|
186
|
+
rules_tsv=""
|
|
187
|
+
for f in "${SOURCE_FILES[@]}"; do
|
|
188
|
+
rows="$(extract_rows "$f" || true)"
|
|
189
|
+
[[ -z "$rows" ]] && continue
|
|
190
|
+
while IFS=$'\t' read -r action reason; do
|
|
191
|
+
[[ -z "$action" ]] && continue
|
|
192
|
+
cls="$(classify "$action")"
|
|
193
|
+
slug="$(slugify "$action")"
|
|
194
|
+
# pack: raw \t reason \t slug \t applicable \t family \t template \t hint \t source
|
|
195
|
+
IFS=$'\t' read -r applicable family tpl hint <<<"$cls"
|
|
196
|
+
printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' \
|
|
197
|
+
"$action" "$reason" "$slug" "$applicable" "$family" "$tpl" "$hint" "$f"
|
|
198
|
+
rules_tsv+="1"
|
|
199
|
+
done <<<"$rows"
|
|
200
|
+
done
|
|
201
|
+
|
|
202
|
+
# Emit JSON. jq slurps TSV lines from stdin.
|
|
203
|
+
rules_json="$(
|
|
204
|
+
{
|
|
205
|
+
for f in "${SOURCE_FILES[@]}"; do
|
|
206
|
+
rows="$(extract_rows "$f" || true)"
|
|
207
|
+
[[ -z "$rows" ]] && continue
|
|
208
|
+
while IFS=$'\t' read -r action reason; do
|
|
209
|
+
[[ -z "$action" ]] && continue
|
|
210
|
+
cls="$(classify "$action")"
|
|
211
|
+
slug="$(slugify "$action")"
|
|
212
|
+
IFS=$'\t' read -r applicable family tpl hint <<<"$cls"
|
|
213
|
+
jq -n \
|
|
214
|
+
--arg raw "$action" \
|
|
215
|
+
--arg reason "$reason" \
|
|
216
|
+
--arg slug "$slug" \
|
|
217
|
+
--arg applicable "$applicable" \
|
|
218
|
+
--arg family "$family" \
|
|
219
|
+
--arg tpl "$tpl" \
|
|
220
|
+
--arg hint "$hint" \
|
|
221
|
+
--arg source "$f" \
|
|
222
|
+
'{
|
|
223
|
+
raw: $raw,
|
|
224
|
+
reason: $reason,
|
|
225
|
+
slug: $slug,
|
|
226
|
+
source_file: $source,
|
|
227
|
+
audit_applicable: ($applicable == "true"),
|
|
228
|
+
detector_family: $family,
|
|
229
|
+
template_id: $tpl,
|
|
230
|
+
detector_hint: $hint
|
|
231
|
+
}'
|
|
232
|
+
done <<<"$rows"
|
|
233
|
+
done
|
|
234
|
+
} | jq -s 'unique_by(.slug)'
|
|
235
|
+
)"
|
|
236
|
+
|
|
237
|
+
jq -n \
|
|
238
|
+
--argjson sources "$(printf '%s\n' "${SOURCE_FILES[@]}" | jq -Rn '[inputs]')" \
|
|
239
|
+
--argjson rules "${rules_json:-[]}" \
|
|
240
|
+
'{ source_files: $sources, rules: $rules }'
|
|
@@ -54,7 +54,9 @@ while IFS=$'\t' read -r id shot snap; do
|
|
|
54
54
|
done
|
|
55
55
|
|
|
56
56
|
# 4. snapshot_quote must appear verbatim in the snapshot.
|
|
57
|
-
|
|
57
|
+
# Skipped for meta findings (coverage, craft-summary, project-rule)
|
|
58
|
+
# where evidence is aggregate, not a single DOM quote.
|
|
59
|
+
jq -c '.[] | select(.rule | test("^(audit-coverage-|design-intelligence-craft-summary|modal-coverage-gap|form-coverage-gap|project-forbidden-)") | not)' "$FINDINGS" | while read -r f; do
|
|
58
60
|
id=$(echo "$f" | jq -r .id)
|
|
59
61
|
q=$(echo "$f" | jq -r .snapshot_quote)
|
|
60
62
|
s=$(echo "$f" | jq -r .snapshot_path)
|
|
@@ -63,6 +65,37 @@ jq -c '.[]' "$FINDINGS" | while read -r f; do
|
|
|
63
65
|
fi
|
|
64
66
|
done
|
|
65
67
|
|
|
68
|
+
# 5. design-intelligence/<slug>_<vp>.json MUST exist for every
|
|
69
|
+
# page × viewport combination in MATRIX. This enforces the
|
|
70
|
+
# 0.7.0 mandatory per-combo craft scoring.
|
|
71
|
+
DIS_DIR="$SESSION_DIR/design-intelligence"
|
|
72
|
+
if [ -d "$DIS_DIR" ]; then
|
|
73
|
+
DIS_COUNT=$(find "$DIS_DIR" -maxdepth 1 -type f -name '*.json' | wc -l)
|
|
74
|
+
if [ "$DIS_COUNT" -lt 1 ]; then
|
|
75
|
+
warn "design-intelligence/ exists but is empty — Step 3g skipped"
|
|
76
|
+
fi
|
|
77
|
+
else
|
|
78
|
+
warn "no design-intelligence/ directory — Step 3g (craft scoring) did not run"
|
|
79
|
+
fi
|
|
80
|
+
|
|
81
|
+
# 6. Viewport quota check: if >= 10 total findings, mobile must be
|
|
82
|
+
# >= 30% unless an explicit audit-coverage-skewed meta finding
|
|
83
|
+
# was emitted (which acknowledges the gap).
|
|
84
|
+
TOTAL=$(jq '[.[] | select(.viewport)] | length' "$FINDINGS")
|
|
85
|
+
if [ "$TOTAL" -ge 10 ]; then
|
|
86
|
+
MOBILE=$(jq '[.[] | select(.viewport == "mobile")] | length' "$FINDINGS")
|
|
87
|
+
COVERAGE_ACK=$(jq '[.[] | select(.rule == "audit-coverage-skewed")] | length' "$FINDINGS")
|
|
88
|
+
# integer math: mobile * 100 / total < 30 means < 30%
|
|
89
|
+
if [ "$COVERAGE_ACK" -eq 0 ] && [ $(( MOBILE * 100 / TOTAL )) -lt 30 ]; then
|
|
90
|
+
warn "mobile viewport is $MOBILE / $TOTAL (< 30%) and no audit-coverage-skewed meta finding was emitted"
|
|
91
|
+
fi
|
|
92
|
+
fi
|
|
93
|
+
|
|
94
|
+
# 7. surfaces.json + project-rules.json should exist (0.7.0 contract).
|
|
95
|
+
# Absence is a warning, not a fatal — allows stale sessions to still verify.
|
|
96
|
+
[ -f "$SESSION_DIR/surfaces.json" ] || warn "missing surfaces.json (Step 1.5 skipped)"
|
|
97
|
+
[ -f "$SESSION_DIR/project-rules.json" ] || warn "missing project-rules.json (Step 1.5 skipped)"
|
|
98
|
+
|
|
66
99
|
COUNT=$(jq 'length' "$FINDINGS")
|
|
67
100
|
if [ "$STRICT" -eq 1 ] && [ "$WARNINGS" -gt 0 ]; then
|
|
68
101
|
echo "STRICT: $COUNT findings verified, $WARNINGS warning(s)" >&2
|