roast-my-design-system 8.4.2 → 8.4.4
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,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "roast-my-design-system",
|
|
3
|
-
"version": "8.4.
|
|
3
|
+
"version": "8.4.4",
|
|
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": [
|
|
@@ -43,6 +43,13 @@ const DOOR_COLOR_RE = new RegExp(`(?<![\\w-])(?:[\\w-]+:)*(?:bg|text|border)-(?:
|
|
|
43
43
|
const DOOR_TYPO_RE = /(?<![\w-])(?:[\w-]+:)*(?:font-(?:thin|extralight|light|normal|medium|semibold|bold|extrabold|black)|text-(?:xs|sm|base|lg|xl|[2-9]xl))(?![\w-])/g;
|
|
44
44
|
|
|
45
45
|
// exported for the checkers: a demo folder is not own code there either
|
|
46
|
+
// A comment is not paint (2026-09-20: an agent's note `{/* border-green-500
|
|
47
|
+
// is deliberate */}` counted as a second use). Block and line comments are
|
|
48
|
+
// blanked to spaces, never cut, so every index still points at its line.
|
|
49
|
+
// The `//` guard keeps `https://` inside a string intact, as kitpaint does.
|
|
50
|
+
export const blankComments = (s) => s
|
|
51
|
+
.replace(/\/\*[\s\S]*?\*\//g, (m) => m.replace(/[^\n]/g, ' '))
|
|
52
|
+
.replace(/(^|[^:'"`\\])(\/\/[^\n]*)/g, (m, pre, c) => pre + ' '.repeat(c.length));
|
|
46
53
|
export const DEMO_PATH_RE = /(^|\/)(stories|storybook|__stories__|examples?|demos?|templates?|playground|fixtures?|__tests__|__mocks__|e2e|cypress)\//i;
|
|
47
54
|
|
|
48
55
|
/**
|
|
@@ -71,9 +78,10 @@ export function countPaint(root, codeFiles, { uiDirs = [], kitNames = new Set(),
|
|
|
71
78
|
|
|
72
79
|
for (const f of codeFiles) {
|
|
73
80
|
if (!/\.(tsx|jsx)$/.test(f) || inCatalogue(f) || DEMO_PATH_RE.test(f)) continue;
|
|
74
|
-
const
|
|
75
|
-
if (
|
|
81
|
+
const raw = readSource(join(root, f));
|
|
82
|
+
if (raw === null || exemptReason(f, raw)) continue;
|
|
76
83
|
ownFiles += 1;
|
|
84
|
+
const src = blankComments(raw);
|
|
77
85
|
|
|
78
86
|
// a palette name the theme gave its own colour is the theme, not drift
|
|
79
87
|
// and a palette grey is only drift where the theme has a grey of its own
|
|
@@ -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 = '8.4.
|
|
4
|
+
export const VERSION = '8.4.4';
|
|
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.
|
|
@@ -17,7 +17,7 @@ import { exemptReason } from '../lib/exempt.mjs';
|
|
|
17
17
|
import { extraDeclarations, fontDeclarations } from '../lib/declarations.mjs';
|
|
18
18
|
import { typefaceOf, GENERIC_FONTS } from '../lib/typefaces.mjs';
|
|
19
19
|
import { kitPaintInSource } from '../lib/kitpaint.mjs';
|
|
20
|
-
import { PALETTE_CLASS_RE, GREY_HUE_RE, DARK_WB_RE, DEMO_PATH_RE } from '../harvest/paint.mjs';
|
|
20
|
+
import { PALETTE_CLASS_RE, GREY_HUE_RE, DARK_WB_RE, DEMO_PATH_RE, blankComments } from '../harvest/paint.mjs';
|
|
21
21
|
|
|
22
22
|
// What this engine measures — shipped with every result, clean or not.
|
|
23
23
|
export const CHECKS = [
|
|
@@ -164,10 +164,13 @@ export function validateContent(content, k) {
|
|
|
164
164
|
// foreground name, a bg- class a surface or background name
|
|
165
165
|
const names = paletteRule === 'tailwind' ? (tw.names ?? []) : ['primary', 'foreground', 'muted-foreground', 'background', 'border'];
|
|
166
166
|
const pick = (re) => names.find((n) => re.test(n)) ?? names[0] ?? 'brand';
|
|
167
|
-
|
|
167
|
+
// a class named in a comment paints nothing; blanked, not cut, so the
|
|
168
|
+
// line numbers below still hold
|
|
169
|
+
const code = blankComments(text);
|
|
170
|
+
const hits = [...code.matchAll(PALETTE_CLASS_RE)];
|
|
168
171
|
// the evening override painted by hand (dark:bg-black) is the same sin
|
|
169
172
|
// on a shadcn sheet, which always has a dark row of its own
|
|
170
|
-
if (paletteRule === 'shadcn') hits.push(...
|
|
173
|
+
if (paletteRule === 'shadcn') hits.push(...code.matchAll(DARK_WB_RE));
|
|
171
174
|
for (const m of hits.sort((a, b) => a.index - b.index)) {
|
|
172
175
|
const cls = m[0];
|
|
173
176
|
const util = cls.replace(/^((?:[\w-]+:)*[a-z]+)-.*$/, '$1');
|
|
@@ -176,7 +179,7 @@ export function validateContent(content, k) {
|
|
|
176
179
|
if (!hasFamily(cls)) continue;
|
|
177
180
|
add('palette-class', 'violation', m.index,
|
|
178
181
|
`Palette class ${cls} where the theme names its colours (${themeFile}).`,
|
|
179
|
-
`Use a theme
|
|
182
|
+
`Use a theme token as the class (${util}-${example}). If no token fits, add one to the theme once, for example success, and use ${util}-success.`);
|
|
180
183
|
}
|
|
181
184
|
}
|
|
182
185
|
|
|
@@ -60,7 +60,15 @@ export function getContext(k, { path = null } = {}) {
|
|
|
60
60
|
const canon = k.canonical.filter((c) => inPkg(c.file)).slice(0, 5)
|
|
61
61
|
.map((c) => ` <${c.name}> from ${c.file} (${c.usageCount}x)`);
|
|
62
62
|
const dupes = [...k.dupeByName.values()].filter((d) => d.files.some((f) => inPkg(typeof f === 'string' ? f : f.file))).slice(0, 3)
|
|
63
|
-
|
|
63
|
+
// Name the places (2026-09-20): an agent given a count and no paths goes
|
|
64
|
+
// looking in src/, finds one copy and calls the scan wrong; the second
|
|
65
|
+
// copy was under a templates folder it never opened. A receipt ends the
|
|
66
|
+
// argument and tells the agent which copy not to import.
|
|
67
|
+
.map((d) => {
|
|
68
|
+
const paths = d.files.map((f) => (typeof f === 'string' ? f : f.file));
|
|
69
|
+
const shown = paths.slice(0, 3).join(', ') + (paths.length > 3 ? `, +${paths.length - 3} more` : '');
|
|
70
|
+
return ` <${d.name}> exists in ${d.files.length} places (${shown}); match what the surrounding code imports, never create another.`;
|
|
71
|
+
});
|
|
64
72
|
const LISTS = Symbol('lists');
|
|
65
73
|
L.push(LISTS);
|
|
66
74
|
|