roast-my-design-system 9.3.2 → 9.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/README.md
CHANGED
|
@@ -133,20 +133,20 @@ Eleven reports, hosted exactly as the tool writes them. Every number determinist
|
|
|
133
133
|
- **[Metabase](https://gregkozakiewicz.github.io/roast-my-design-system/examples/metabase-mantine.html)** (Mantine): its own wrapper over Mantine counts as the kit, so 2,679 files are read instead of 392; nothing written onto components, 2 spacings per 100 kit files. Score 47.
|
|
134
134
|
- **[SigNoz](https://gregkozakiewicz.github.io/roast-my-design-system/examples/signoz-antd.html)** (Ant Design): 2 colours and 7 spacings per 100 kit files written in style objects where a token exists. Score 51.
|
|
135
135
|
- **[Apache Airflow](https://gregkozakiewicz.github.io/roast-my-design-system/examples/airflow-chakra.html)** (Chakra UI): no colours written onto components, 4 spacings per 100 kit files as pixel strings where a space step exists. Score 43.
|
|
136
|
-
- **[vercel/ai-chatbot](https://gregkozakiewicz.github.io/roast-my-design-system/examples/vercel-ai-chatbot.html)** (shadcn install):
|
|
136
|
+
- **[vercel/ai-chatbot](https://gregkozakiewicz.github.io/roast-my-design-system/examples/vercel-ai-chatbot.html)** (shadcn install): 71 values like [13px] written outside the Tailwind scale, and 66 palette colours per 100 files where a theme variable exists; Claude's notes embedded. Score 80.
|
|
137
137
|
- **[excalidraw/excalidraw](https://gregkozakiewicz.github.io/roast-my-design-system/examples/excalidraw-excalidraw.html)**: 78 off-scale spacing values and 90 !important declarations. Score 55.
|
|
138
|
-
- **[dubinc/dub](https://gregkozakiewicz.github.io/roast-my-design-system/examples/dubinc-dub.html)**:
|
|
138
|
+
- **[dubinc/dub](https://gregkozakiewicz.github.io/roast-my-design-system/examples/dubinc-dub.html)**: 642 arbitrary bracket values, 21 duplicated components, and the chart-palette gap named. Score 20.
|
|
139
139
|
- **[telekom/scale](https://gregkozakiewicz.github.io/roast-my-design-system/examples/telekom-scale.html)** (Stencil): 95 Stencil components read by tag; 66 spacing values outside the scale where about 12 would do; Claude's notes embedded. Score 55.
|
|
140
140
|
- **[magicuidesign/magicui](https://gregkozakiewicz.github.io/roast-my-design-system/examples/magicui.html)** (registry): counted on the components it publishes, 52 off-theme colours per 100 files in the code it ships, its docs site kept out and named. Score 78.
|
|
141
141
|
- **[adobe/spectrum-web-components](https://gregkozakiewicz.github.io/roast-my-design-system/examples/adobe-spectrum.html)** (Lit): hardcoded colours sitting beside 744 colour tokens, and 37 !important declarations. Score 66.
|
|
142
142
|
|
|
143
143
|
The full report for vercel/ai-chatbot. The verdict answers what the agent will learn here, the gap section comes first, then "What the repo teaches the agent", Claude's read of the scan:
|
|
144
144
|
|
|
145
|
-

|
|
146
146
|
|
|
147
147
|
The same report in light mode (one file, built-in toggle):
|
|
148
148
|
|
|
149
|
-

|
|
150
150
|
|
|
151
151
|
## Why the numbers hold
|
|
152
152
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "roast-my-design-system",
|
|
3
|
-
"version": "9.3.
|
|
3
|
+
"version": "9.3.3",
|
|
4
4
|
"mcpName": "io.github.gregkozakiewicz/roast-my-design-system",
|
|
5
5
|
"description": "Your AI can write the UI. This makes sure it writes your UI. A deterministic scanner scores your design system 0-100 against 112 public repos, reads React and web components (Stencil, Lit), hands you a copy-paste fix prompt for each top finding, writes rules for Claude, Cursor, Copilot and Windsurf with --apply, and runs as a local MCP server with --mcp.",
|
|
6
6
|
"keywords": [
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { existsSync } from 'node:fs';
|
|
10
10
|
import {
|
|
11
|
-
isEmail,
|
|
11
|
+
isEmail, foreignStylesheet, CRASH_PAGE_RE, exemptReason, isLibraryClass, WIDGET_CSS_RE, WIDGET_CONFIG_RE,
|
|
12
12
|
} from '../lib/exempt.mjs';
|
|
13
13
|
import { join } from 'node:path';
|
|
14
14
|
import { canonical, parseColor } from '../lib/color.mjs';
|
|
@@ -25,7 +25,10 @@ class Tally {
|
|
|
25
25
|
e.count++;
|
|
26
26
|
e.files.set(file, (e.files.get(file) ?? 0) + 1);
|
|
27
27
|
}
|
|
28
|
-
|
|
28
|
+
// `every` keeps the whole per-file tally beside the capped list shown in
|
|
29
|
+
// the report, for a caller that has to split the count by folder exactly
|
|
30
|
+
// (profiles/splitArbitrary: installed code against the team's own).
|
|
31
|
+
toJSON(limitFiles = 5, { every = false } = {}) {
|
|
29
32
|
return [...this.map.values()]
|
|
30
33
|
.sort((a, b) => b.count - a.count)
|
|
31
34
|
.map((e) => ({
|
|
@@ -33,6 +36,7 @@ class Tally {
|
|
|
33
36
|
count: e.count,
|
|
34
37
|
files: [...e.files.entries()].sort((a, b) => b[1] - a[1]).slice(0, limitFiles)
|
|
35
38
|
.map(([file, count]) => ({ file, count })),
|
|
39
|
+
...(every ? { every: [...e.files.entries()].map(([file, count]) => ({ file, count })) } : {}),
|
|
36
40
|
}));
|
|
37
41
|
}
|
|
38
42
|
}
|
|
@@ -500,7 +504,6 @@ export function harvestTokens(root, styleFiles, codeFiles, { email = null } = {}
|
|
|
500
504
|
// reason to discredit the whole report.
|
|
501
505
|
// Email, artwork, mostly-SVG files and pixel renderers: all imported from
|
|
502
506
|
// lib/exempt.mjs, the one list every checker reads.
|
|
503
|
-
const ARTWORK_RE = ARTWORK_NAME_RE;
|
|
504
507
|
// every file a check skipped, with the reason, so the report can name them
|
|
505
508
|
const exemptFiles = [];
|
|
506
509
|
const skip = (f, src) => { const r = exemptReason(f, src, { email }); if (r) exemptFiles.push({ file: f, reason: r }); return true; };
|
|
@@ -566,18 +569,14 @@ export function harvestTokens(root, styleFiles, codeFiles, { email = null } = {}
|
|
|
566
569
|
paletteFiles.add(f);
|
|
567
570
|
for (const m of src.matchAll(HEX_RE)) tokenDefined.add(normalizeHex(m[0]));
|
|
568
571
|
}
|
|
569
|
-
|
|
570
|
-
//
|
|
571
|
-
//
|
|
572
|
-
//
|
|
573
|
-
//
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
// the walk from the file that prints it (lib/exempt.mjs printedFilesOf)
|
|
578
|
-
if (email?.printed?.includes(f)) { skip(f, src); continue; }
|
|
579
|
-
// an email written with an email kit, wherever it lives (react-email)
|
|
580
|
-
if (EMAIL_KIT_RE.test(src)) { skip(f, src); continue; }
|
|
572
|
+
// Artwork, renderers, render-to-image surfaces (OG cards, PDF invoices),
|
|
573
|
+
// pages a headless browser prints, emails written with a kit: one rule,
|
|
574
|
+
// lib/exempt.mjs, the same answer the live checks and the guard give.
|
|
575
|
+
// Until 9.3.3 this counter kept its own list, and the two had drifted: a
|
|
576
|
+
// file named Badge or IconButton walked free here on its name alone
|
|
577
|
+
// (2,321 files in 173 repos) while an icon in an icons folder was
|
|
578
|
+
// counted here and exempt everywhere else (likec4's 12,203 icon colours).
|
|
579
|
+
if (exemptReason(f, src, { email })) { skip(f, src); continue; }
|
|
581
580
|
|
|
582
581
|
// Tailwind classes
|
|
583
582
|
for (const cls of classStrings(src)) {
|
|
@@ -603,7 +602,7 @@ export function harvestTokens(root, styleFiles, codeFiles, { email = null } = {}
|
|
|
603
602
|
// length inside them is still a hardcoded value — harvest those from every
|
|
604
603
|
// non-trivial block; count only the all-literal blocks.
|
|
605
604
|
const allBlocks = inlineStyleBlocks(src).filter((b) => !TRIVIAL_INLINE_RE.test(b.trim()));
|
|
606
|
-
const staticBlocks =
|
|
605
|
+
const staticBlocks = allBlocks.filter(isStaticInline);
|
|
607
606
|
if (staticBlocks.length) {
|
|
608
607
|
inlineStyleCount += staticBlocks.length;
|
|
609
608
|
inlineStyleFiles.set(f, (inlineStyleFiles.get(f) ?? 0) + staticBlocks.length);
|
|
@@ -810,7 +809,7 @@ export function harvestTokens(root, styleFiles, codeFiles, { email = null } = {}
|
|
|
810
809
|
spacing: twSpacing.toJSON(),
|
|
811
810
|
radii: twRadii.toJSON(),
|
|
812
811
|
textSizes: twTextSizes.toJSON(),
|
|
813
|
-
arbitrary: twArbitrary.toJSON(),
|
|
812
|
+
arbitrary: twArbitrary.toJSON(5, { every: true }),
|
|
814
813
|
},
|
|
815
814
|
important: {
|
|
816
815
|
// aimed at a library's own class names: kept out, named at the top
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Single version constant for the engine — imported by diagnose (report
|
|
2
2
|
// footer) and rules (generated-by line). This is the bump spot that used to
|
|
3
3
|
// live as a const inside diagnose/index.mjs.
|
|
4
|
-
export const VERSION = '9.3.
|
|
4
|
+
export const VERSION = '9.3.3';
|
|
5
5
|
// The shape of harvest.json and summary.json. Bumped only when a field is
|
|
6
6
|
// renamed, removed or changes meaning; a new field is not a new schema. A
|
|
7
7
|
// tool comparing two scans compares like with like by this number, not by VERSION.
|
|
@@ -197,14 +197,18 @@ export function splitArbitrary(entries, dirs) {
|
|
|
197
197
|
const installedValues = new Map();
|
|
198
198
|
let installedUses = 0;
|
|
199
199
|
for (const e of entries) {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
200
|
+
// the whole per-file tally when the harvest kept one (every), else the
|
|
201
|
+
// capped list: a use in a file that fell off the list of five was counted
|
|
202
|
+
// as the team's own, so ai-chatbot's installed badge put one of shadcn's
|
|
203
|
+
// [3px] values in the headline (2026-09-30)
|
|
204
|
+
const all = e.every ?? e.files ?? [];
|
|
205
|
+
const ownFiles = all.filter((f) => !underAny(f.file, dirs)).sort((a, b) => b.count - a.count).slice(0, 5);
|
|
206
|
+
const instCount = all.filter((f) => underAny(f.file, dirs)).reduce((s, f) => s + f.count, 0);
|
|
203
207
|
if (instCount) { installedUses += instCount; installedValues.set(e.value, (installedValues.get(e.value) ?? 0) + instCount); }
|
|
204
|
-
// the
|
|
205
|
-
//
|
|
208
|
+
// own is the remainder of the entry's total, never a sum of the files
|
|
209
|
+
// that happened to be listed
|
|
206
210
|
const ownCount = e.count - instCount;
|
|
207
|
-
if (ownCount > 0) own.push({ ...
|
|
211
|
+
if (ownCount > 0) { const { every, ...rest } = e; own.push({ ...rest, count: ownCount, files: ownFiles }); }
|
|
208
212
|
}
|
|
209
213
|
own.sort((a, b) => b.count - a.count);
|
|
210
214
|
const values = [...installedValues.entries()].sort((a, b) => b[1] - a[1]).map(([value, count]) => ({ value, count }));
|