start-vibing 4.3.2 → 4.3.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "start-vibing",
3
- "version": "4.3.2",
4
- "description": "Setup Claude Code with 9 plugins, 6 community skills, and 8 MCP servers. Parallel install, auto-accept, superpowers + ralph-loop. super-design 0.6.2: canonical audit-state.schema.json + verify --strict, per-viewport {sha256,phash,png_path} hashes with sharp/fpr fallback + MASK_SELECTORS, visual-regression.sh (pixelmatch→odiff→sha256-fallback), DTCG *.tokens.json + Tokens Studio aliases, @fixture-<id> dynamic routes + madge import-graph N=3, per-app monorepo state (pnpm/npm/yarn/Bun/Nx/Turbo).",
3
+ "version": "4.3.3",
4
+ "description": "Setup Claude Code with 9 plugins, 6 community skills, and 8 MCP servers. Parallel install, auto-accept, superpowers + ralph-loop. super-design 0.6.3: DSC-choice active selection — score-typeui.mjs ranks 12 typeui-* skills by 7-axis fingerprint (density/contrast/geometry/color/typography/motion/audience) from findings+DIS, top-3 written to typeui-proposal.json with copy-paste CLI; harvest-typeui.sh pulls typeui.sh catalog + vercel-labs/anthropic fallbacks; sd-fix --typeui <name> rebrands V1-V8 fixes to snap spacing/color/radius to chosen direction's tokens.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "start-vibing": "./dist/cli.js"
@@ -8,7 +8,7 @@ description: >
8
8
  UX audit (WCAG 2.2 AA, Nielsen heuristics, Baymard, CWV), and synthesized
9
9
  overview. Re-audits only what changed since last run. On explicit user request,
10
10
  applies surgical fixes with full rollback.
11
- version: 0.6.2
11
+ version: 0.6.3
12
12
  ---
13
13
 
14
14
  # super-design
@@ -33,17 +33,25 @@ Four-phase pipeline with 6 specialist agents:
33
33
  low density, weak CTA hierarchy, vibecode smell).
34
34
  - **Step 3h mobile-native audit** (21-item Duolingo/Linear/Arc/Cash-App
35
35
  checklist) — replaces "responsive-web-on-a-phone" thinking.
36
- - C16 ≤ 4 → design-skill advisory finding citing typeui-* selection matrix.
36
+ - C16 ≤ 4 → **DSC-choice** proposal: sd-synthesis runs
37
+ `scripts/score-typeui.mjs --from-audit <dir>` to derive a 7-axis site
38
+ fingerprint (density/contrast/geometry/color/typography/motion/audience)
39
+ and rank all installed typeui-* skills by fit 0-1. Top-3 are written to
40
+ `typeui-proposal.json` + shown in overview.md with a copy-paste CLI.
37
41
  Produces findings.json + design-intelligence.json with SHOT+QUOTE+SEL+VAL.
38
42
  3. **Synthesis** (sd-synthesis) — unifies research + audit + design-intelligence
39
- into overview.md (per-page DIS table + executive summary).
43
+ into overview.md (per-page DIS table + executive summary + typeui proposal
44
+ when craft ≤ 4).
40
45
  4. **Fix** (sd-fix + two-stage verify) — optional. Applies safe fixes with
41
46
  technical gates (types/lint/tests) AND semantic verification ("does this
42
47
  fix actually resolve the finding, or just mask it?"). Template families:
43
48
  A1-A15 a11y · V1-V8 design · U1-U10 ux · P1-P10 perf · **M1-M15 mobile**
44
49
  (cards-in-flex-col → compact list, table-on-mobile → card-per-row,
45
50
  centered-modal → bottom-sheet, etc.) · **DSC-1 design-skill advisory**
46
- (proposes typeui-* direction, never auto-applies HIGH risk). After each
51
+ (proposes typeui-* direction). With `--typeui <name>` flag, sd-fix loads
52
+ the chosen typeui skill (tokens: primary, radius, spacing scale,
53
+ typography) and rebrands V1-V8 fixes so every spacing/color/radius target
54
+ snaps to that direction's scale — turning advisory into applied. After each
47
55
  successful fix, re-drives Playwright to capture an after-screenshot (full
48
56
  page + element crop) and emits `docs/super-design/sessions/<id>/fix-report.md`:
49
57
  a self-contained visual diff with before/after images, file diffs,
@@ -109,6 +117,15 @@ Do NOT paste overview into chat.
109
117
  `name` entry from `scripts/detect-apps.sh`). Required when `--scope <url>`
110
118
  is ambiguous between multiple apps.
111
119
  - `--fix` — run sd-fix after audit
120
+ - `--typeui <name>` — combine with `--fix` to apply fixes aligned to a
121
+ chosen typeui direction (e.g. `--fix --typeui application`). Loads tokens
122
+ from `~/.claude/skills/typeui-<name>/SKILL.md` and rebrands V1-V8 targets
123
+ to that scale. Picked from the top-3 proposed in overview.md typeui
124
+ block. Without this flag, DSC-1 stays advisory. Run
125
+ `bash scripts/score-typeui.mjs --list` to see all installed directions.
126
+ - `--harvest-typeui` — run `scripts/harvest-typeui.sh` first to pull
127
+ missing typeui-* and related design skills (typeui.sh registry with
128
+ built-in catalog fallback). Idempotent; add `--refresh` to re-download.
112
129
  - `--dry-run` — artifacts without committing state
113
130
  - `--ci` — non-interactive, create PR, exit non-zero on blockers
114
131
  - `--update-baselines` — Re-hash pages and tokens without re-auditing (use after accepted cosmetic drift). Also accepted by `scripts/visual-regression.sh` to overwrite `.super-design/baselines/*.png` with the current capture.
@@ -0,0 +1,131 @@
1
+ #!/usr/bin/env bash
2
+ # harvest-typeui.sh — discover + install typeui-* and related design skills.
3
+ #
4
+ # Sources, in order:
5
+ # 1. typeui.sh registry (if reachable) — fetch catalog.json, install missing
6
+ # 2. vercel-labs/agent-skills design skills (composition, web-design-guidelines)
7
+ # 3. anthropics/skills webapp-testing + mcp-builder (already covered, idempotent)
8
+ #
9
+ # Usage:
10
+ # harvest-typeui.sh # install everything missing (user-scope ~/.claude/skills/)
11
+ # harvest-typeui.sh --list # print catalog without installing
12
+ # harvest-typeui.sh --refresh # re-download even if already installed
13
+ # harvest-typeui.sh --dest <dir> # install to custom dir (default: ~/.claude/skills)
14
+
15
+ set -euo pipefail
16
+
17
+ DEST="${HOME}/.claude/skills"
18
+ LIST_ONLY=0
19
+ REFRESH=0
20
+
21
+ while (( $# )); do
22
+ case "$1" in
23
+ --list) LIST_ONLY=1 ;;
24
+ --refresh) REFRESH=1 ;;
25
+ --dest) DEST="$2"; shift ;;
26
+ -h|--help)
27
+ sed -n '2,18p' "$0"
28
+ exit 0 ;;
29
+ *) echo "unknown flag: $1" >&2; exit 2 ;;
30
+ esac
31
+ shift
32
+ done
33
+
34
+ mkdir -p "$DEST"
35
+
36
+ TYPEUI_CATALOG_URL="${TYPEUI_CATALOG_URL:-https://typeui.sh/catalog.json}"
37
+ TMP=$(mktemp)
38
+ trap 'rm -f "$TMP"' EXIT
39
+
40
+ log() { echo "[harvest-typeui] $*"; }
41
+
42
+ fetch_catalog() {
43
+ if command -v curl >/dev/null 2>&1; then
44
+ curl -sSfL --max-time 10 "$TYPEUI_CATALOG_URL" -o "$TMP" 2>/dev/null && return 0
45
+ fi
46
+ if command -v wget >/dev/null 2>&1; then
47
+ wget -q --timeout=10 -O "$TMP" "$TYPEUI_CATALOG_URL" 2>/dev/null && return 0
48
+ fi
49
+ return 1
50
+ }
51
+
52
+ # Fallback catalog — built-in list of known typeui skills + companions.
53
+ # Format: name|base_url (raw SKILL.md location)
54
+ DEFAULT_CATALOG=$(cat <<'EOF'
55
+ typeui-ant|https://raw.githubusercontent.com/typeui-sh/skills/main/typeui-ant/SKILL.md
56
+ typeui-application|https://raw.githubusercontent.com/typeui-sh/skills/main/typeui-application/SKILL.md
57
+ typeui-artistic|https://raw.githubusercontent.com/typeui-sh/skills/main/typeui-artistic/SKILL.md
58
+ typeui-bento|https://raw.githubusercontent.com/typeui-sh/skills/main/typeui-bento/SKILL.md
59
+ typeui-bold|https://raw.githubusercontent.com/typeui-sh/skills/main/typeui-bold/SKILL.md
60
+ typeui-clean|https://raw.githubusercontent.com/typeui-sh/skills/main/typeui-clean/SKILL.md
61
+ typeui-dashboard|https://raw.githubusercontent.com/typeui-sh/skills/main/typeui-dashboard/SKILL.md
62
+ typeui-doodle|https://raw.githubusercontent.com/typeui-sh/skills/main/typeui-doodle/SKILL.md
63
+ typeui-dramatic|https://raw.githubusercontent.com/typeui-sh/skills/main/typeui-dramatic/SKILL.md
64
+ typeui-enterprise|https://raw.githubusercontent.com/typeui-sh/skills/main/typeui-enterprise/SKILL.md
65
+ typeui-neobrutalism|https://raw.githubusercontent.com/typeui-sh/skills/main/typeui-neobrutalism/SKILL.md
66
+ typeui-paper|https://raw.githubusercontent.com/typeui-sh/skills/main/typeui-paper/SKILL.md
67
+ composition-patterns|https://raw.githubusercontent.com/vercel-labs/agent-skills/main/skills/composition-patterns/SKILL.md
68
+ web-design-guidelines|https://raw.githubusercontent.com/vercel-labs/agent-skills/main/skills/web-design-guidelines/SKILL.md
69
+ react-best-practices|https://raw.githubusercontent.com/vercel-labs/agent-skills/main/skills/react-best-practices/SKILL.md
70
+ webapp-testing|https://raw.githubusercontent.com/anthropics/skills/main/skills/webapp-testing/SKILL.md
71
+ mcp-builder|https://raw.githubusercontent.com/anthropics/skills/main/skills/mcp-builder/SKILL.md
72
+ EOF
73
+ )
74
+
75
+ CATALOG=""
76
+ if fetch_catalog; then
77
+ # typeui.sh returned JSON — extract {name,url} tuples via node
78
+ if command -v node >/dev/null 2>&1; then
79
+ CATALOG=$(node -e "
80
+ const c = JSON.parse(require('fs').readFileSync('$TMP','utf8'));
81
+ const items = c.skills || c.items || c;
82
+ for (const it of items) {
83
+ const n = it.name || it.slug;
84
+ const u = it.skill_url || it.url || it.raw || it.source;
85
+ if (n && u) console.log(n + '|' + u);
86
+ }
87
+ " 2>/dev/null || echo "")
88
+ fi
89
+ fi
90
+
91
+ if [[ -z "$CATALOG" ]]; then
92
+ log "registry unreachable or empty — using built-in catalog"
93
+ CATALOG="$DEFAULT_CATALOG"
94
+ fi
95
+
96
+ if (( LIST_ONLY )); then
97
+ echo "$CATALOG"
98
+ exit 0
99
+ fi
100
+
101
+ INSTALLED=0
102
+ SKIPPED=0
103
+ FAILED=0
104
+
105
+ while IFS='|' read -r name url; do
106
+ [[ -z "$name" ]] && continue
107
+ target_dir="$DEST/$name"
108
+ target_file="$target_dir/SKILL.md"
109
+
110
+ if [[ -f "$target_file" && $REFRESH -eq 0 ]]; then
111
+ SKIPPED=$((SKIPPED + 1))
112
+ continue
113
+ fi
114
+
115
+ mkdir -p "$target_dir"
116
+ if command -v curl >/dev/null 2>&1; then
117
+ if curl -sSfL --max-time 15 "$url" -o "$target_file" 2>/dev/null; then
118
+ log "installed $name"
119
+ INSTALLED=$((INSTALLED + 1))
120
+ else
121
+ log "failed $name ($url)"
122
+ FAILED=$((FAILED + 1))
123
+ rmdir "$target_dir" 2>/dev/null || true
124
+ fi
125
+ else
126
+ log "curl missing — cannot fetch $name"
127
+ FAILED=$((FAILED + 1))
128
+ fi
129
+ done <<< "$CATALOG"
130
+
131
+ log "done: installed=$INSTALLED skipped=$SKIPPED failed=$FAILED dest=$DEST"
@@ -0,0 +1,224 @@
1
+ #!/usr/bin/env node
2
+ // score-typeui.mjs — rank typeui-* skills by fit against a site fingerprint.
3
+ //
4
+ // Usage:
5
+ // node score-typeui.mjs <fingerprint.json> [--top 3]
6
+ // node score-typeui.mjs --from-audit <audit-dir> # derive fingerprint from sd-audit findings + DIS
7
+ // node score-typeui.mjs --list # list available typeui skills with axes
8
+ //
9
+ // Fingerprint input (JSON):
10
+ // { density: "low|medium|high", contrast: "low|medium|high",
11
+ // geometry: "round|square|mixed", color: "muted|vibrant|bold",
12
+ // typography: "neutral|expressive|display", motion: "static|subtle|dynamic",
13
+ // audience: "enterprise|developer|consumer|creative" }
14
+ //
15
+ // Output (stdout): JSON { ranked: [{ name, fit, reasons, applies_tokens }] }
16
+
17
+ import fs from 'node:fs';
18
+ import path from 'node:path';
19
+ import os from 'node:os';
20
+
21
+ const AXES = ['density', 'contrast', 'geometry', 'color', 'typography', 'motion', 'audience'];
22
+
23
+ // Static fingerprint matrix — derived from each typeui SKILL.md style foundations.
24
+ // Keep in sync with skills at ~/.claude/skills/typeui-*/SKILL.md. Expand via harvest-typeui.sh.
25
+ const TYPEUI = {
26
+ ant: {
27
+ density: 'high', contrast: 'medium', geometry: 'square',
28
+ color: 'muted', typography: 'neutral', motion: 'static', audience: 'enterprise',
29
+ tokens: { primary: '#1677ff', radius: 6, spacing: [4, 8, 12, 16, 24, 32] },
30
+ summary: 'Structured, enterprise-focused design. Data-dense tables, forms.',
31
+ },
32
+ application: {
33
+ density: 'medium', contrast: 'high', geometry: 'round',
34
+ color: 'vibrant', typography: 'neutral', motion: 'subtle', audience: 'developer',
35
+ tokens: { primary: '#9333ea', radius: 8, spacing: [4, 8, 12, 16, 24, 32] },
36
+ summary: 'Vercel/GitHub purple aesthetic. Top-bar nav, card layouts.',
37
+ },
38
+ artistic: {
39
+ density: 'low', contrast: 'high', geometry: 'mixed',
40
+ color: 'bold', typography: 'expressive', motion: 'dynamic', audience: 'creative',
41
+ tokens: { primary: '#000000', radius: 0, spacing: [8, 16, 24, 40, 64, 96] },
42
+ summary: 'High-contrast, expressive. Creative typography, bold color.',
43
+ },
44
+ bento: {
45
+ density: 'medium', contrast: 'medium', geometry: 'round',
46
+ color: 'muted', typography: 'neutral', motion: 'subtle', audience: 'consumer',
47
+ tokens: { primary: '#0ea5e9', radius: 16, spacing: [8, 16, 24, 32, 48, 64] },
48
+ summary: 'Modular grid of card-blocks. Clear hierarchy, soft spacing.',
49
+ },
50
+ bold: {
51
+ density: 'low', contrast: 'high', geometry: 'square',
52
+ color: 'bold', typography: 'display', motion: 'dynamic', audience: 'consumer',
53
+ tokens: { primary: '#111111', radius: 4, spacing: [8, 16, 24, 48, 80, 128] },
54
+ summary: 'Strong visual presence. Heavyweight type, high-contrast.',
55
+ },
56
+ clean: {
57
+ density: 'low', contrast: 'medium', geometry: 'round',
58
+ color: 'muted', typography: 'neutral', motion: 'static', audience: 'consumer',
59
+ tokens: { primary: '#18181b', radius: 8, spacing: [8, 16, 24, 32, 48, 72] },
60
+ summary: 'Simplicity-focused. Whitespace, legible type, limited palette.',
61
+ },
62
+ dashboard: {
63
+ density: 'high', contrast: 'high', geometry: 'round',
64
+ color: 'vibrant', typography: 'neutral', motion: 'subtle', audience: 'developer',
65
+ tokens: { primary: '#3b82f6', radius: 6, spacing: [4, 8, 12, 16, 24, 32] },
66
+ summary: 'Dark cloud-platform. Modular grids, glass panels, data hierarchy.',
67
+ },
68
+ doodle: {
69
+ density: 'low', contrast: 'medium', geometry: 'mixed',
70
+ color: 'vibrant', typography: 'expressive', motion: 'dynamic', audience: 'creative',
71
+ tokens: { primary: '#f59e0b', radius: 12, spacing: [8, 16, 24, 40, 64, 96] },
72
+ summary: 'Hand-drawn, sketch-like. Doodles, handwritten fonts.',
73
+ },
74
+ dramatic: {
75
+ density: 'low', contrast: 'high', geometry: 'mixed',
76
+ color: 'bold', typography: 'display', motion: 'dynamic', audience: 'creative',
77
+ tokens: { primary: '#dc2626', radius: 0, spacing: [16, 32, 48, 80, 128, 200] },
78
+ summary: 'Theatrical. Bold layouts, immersive visuals, unconventional.',
79
+ },
80
+ enterprise: {
81
+ density: 'high', contrast: 'high', geometry: 'square',
82
+ color: 'muted', typography: 'neutral', motion: 'static', audience: 'enterprise',
83
+ tokens: { primary: '#0f172a', radius: 4, spacing: [4, 8, 12, 16, 24, 32] },
84
+ summary: 'Clean, high-contrast enterprise. Drag-drop, structured layouts.',
85
+ },
86
+ neobrutalism: {
87
+ density: 'medium', contrast: 'high', geometry: 'square',
88
+ color: 'bold', typography: 'display', motion: 'subtle', audience: 'creative',
89
+ tokens: { primary: '#ffd700', radius: 0, spacing: [8, 16, 24, 32, 48, 64] },
90
+ summary: 'Bold borders, vivid accents, raw high-contrast on warm surfaces.',
91
+ },
92
+ paper: {
93
+ density: 'medium', contrast: 'medium', geometry: 'round',
94
+ color: 'muted', typography: 'expressive', motion: 'static', audience: 'consumer',
95
+ tokens: { primary: '#78350f', radius: 4, spacing: [8, 16, 24, 32, 48, 64] },
96
+ summary: 'Paper-textured, print-inspired. Serif/sans, tactile.',
97
+ },
98
+ };
99
+
100
+ // Axis weights — density/contrast/geometry dominate; audience is secondary.
101
+ const WEIGHTS = {
102
+ density: 0.22, contrast: 0.18, geometry: 0.16, color: 0.14,
103
+ typography: 0.12, motion: 0.10, audience: 0.08,
104
+ };
105
+
106
+ function matchScore(a, b) {
107
+ if (a === b) return 1.0;
108
+ const pairs = {
109
+ 'low|medium': 0.5, 'medium|high': 0.5,
110
+ 'muted|vibrant': 0.5, 'vibrant|bold': 0.5,
111
+ 'neutral|expressive': 0.5, 'expressive|display': 0.5,
112
+ 'static|subtle': 0.5, 'subtle|dynamic': 0.5,
113
+ 'round|mixed': 0.6, 'square|mixed': 0.6,
114
+ };
115
+ const key = [a, b].sort().join('|');
116
+ return pairs[key] ?? 0;
117
+ }
118
+
119
+ function scoreFor(fp, typeui) {
120
+ let total = 0;
121
+ const reasons = [];
122
+ for (const axis of AXES) {
123
+ const fpv = fp[axis];
124
+ const tuv = typeui[axis];
125
+ if (!fpv || !tuv) continue;
126
+ const s = matchScore(fpv, tuv);
127
+ total += s * WEIGHTS[axis];
128
+ if (s >= 0.5) reasons.push(`${axis}:${tuv}`);
129
+ }
130
+ return { fit: Math.round(total * 100) / 100, reasons };
131
+ }
132
+
133
+ function rank(fp, topN = 3) {
134
+ const scored = Object.entries(TYPEUI).map(([name, tu]) => ({
135
+ name: `typeui-${name}`,
136
+ ...scoreFor(fp, tu),
137
+ applies_tokens: tu.tokens,
138
+ summary: tu.summary,
139
+ }));
140
+ scored.sort((a, b) => b.fit - a.fit);
141
+ return scored.slice(0, topN);
142
+ }
143
+
144
+ // Derive fingerprint from sd-audit findings.json + design-intelligence.json.
145
+ function fingerprintFromAudit(auditDir) {
146
+ const fp = {};
147
+ const diPath = path.join(auditDir, 'design-intelligence.json');
148
+ const fPath = path.join(auditDir, 'findings.json');
149
+ if (!fs.existsSync(diPath)) {
150
+ throw new Error(`design-intelligence.json not found in ${auditDir}`);
151
+ }
152
+ const di = JSON.parse(fs.readFileSync(diPath, 'utf8'));
153
+ const findings = fs.existsSync(fPath) ? JSON.parse(fs.readFileSync(fPath, 'utf8')) : [];
154
+
155
+ const scores = di.per_page?.[0]?.categories || di.categories || {};
156
+ const s = (k) => scores[k]?.score ?? scores[k] ?? 5;
157
+
158
+ // Density: C1 density + C11 information scent
159
+ const densityScore = (s('C1') + s('C11')) / 2;
160
+ fp.density = densityScore >= 7 ? 'high' : densityScore >= 4 ? 'medium' : 'low';
161
+
162
+ // Contrast: C6 contrast (inverse — low craft = low contrast detected)
163
+ fp.contrast = s('C6') >= 7 ? 'high' : s('C6') >= 4 ? 'medium' : 'low';
164
+
165
+ // Geometry: C14 border-radius variance (heuristic)
166
+ const radiusVar = di.summary?.radius_variance ?? 0;
167
+ fp.geometry = radiusVar > 8 ? 'mixed' : radiusVar > 2 ? 'round' : 'square';
168
+
169
+ // Color: C5 palette saturation
170
+ const satScore = di.summary?.color_saturation ?? 0.5;
171
+ fp.color = satScore > 0.7 ? 'bold' : satScore > 0.4 ? 'vibrant' : 'muted';
172
+
173
+ // Typography: C7 type scale variance
174
+ const typeVariance = di.summary?.type_variance ?? 0.5;
175
+ fp.typography = typeVariance > 0.7 ? 'display' : typeVariance > 0.4 ? 'expressive' : 'neutral';
176
+
177
+ // Motion: C13 motion/transitions
178
+ fp.motion = s('C13') >= 7 ? 'dynamic' : s('C13') >= 4 ? 'subtle' : 'static';
179
+
180
+ // Audience: inferred from findings categories — best-effort
181
+ const mobileCount = findings.filter((f) => f.id?.startsWith('M')).length;
182
+ const dataCount = findings.filter((f) => /table|dense|data/i.test(f.quote || '')).length;
183
+ fp.audience = dataCount > mobileCount ? 'enterprise' : 'consumer';
184
+
185
+ return fp;
186
+ }
187
+
188
+ function listSkills() {
189
+ return Object.entries(TYPEUI).map(([name, tu]) => ({
190
+ name: `typeui-${name}`,
191
+ axes: AXES.reduce((o, a) => ({ ...o, [a]: tu[a] }), {}),
192
+ summary: tu.summary,
193
+ }));
194
+ }
195
+
196
+ // CLI
197
+ const args = process.argv.slice(2);
198
+ if (args.includes('--list')) {
199
+ console.log(JSON.stringify(listSkills(), null, 2));
200
+ process.exit(0);
201
+ }
202
+
203
+ let fp;
204
+ let topN = 3;
205
+ const topIdx = args.indexOf('--top');
206
+ if (topIdx >= 0) topN = parseInt(args[topIdx + 1], 10) || 3;
207
+
208
+ const fromAuditIdx = args.indexOf('--from-audit');
209
+ if (fromAuditIdx >= 0) {
210
+ const dir = args[fromAuditIdx + 1];
211
+ if (!dir) {
212
+ console.error('--from-audit requires a directory path');
213
+ process.exit(2);
214
+ }
215
+ fp = fingerprintFromAudit(dir);
216
+ } else if (args[0] && fs.existsSync(args[0])) {
217
+ fp = JSON.parse(fs.readFileSync(args[0], 'utf8'));
218
+ } else {
219
+ console.error('Usage: score-typeui.mjs <fingerprint.json> | --from-audit <dir> | --list');
220
+ process.exit(2);
221
+ }
222
+
223
+ const ranked = rank(fp, topN);
224
+ console.log(JSON.stringify({ fingerprint: fp, ranked }, null, 2));