roast-my-design-system 8.4.4 → 8.4.6
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 +1 -1
- package/skills/roast-my-design-system/scripts/lib/guard-api.mjs +22 -3
- package/skills/roast-my-design-system/scripts/lib/kitpaint.mjs +44 -0
- package/skills/roast-my-design-system/scripts/lib/version.mjs +1 -1
- package/skills/roast-my-design-system/scripts/mcp/engine.mjs +6 -25
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.6",
|
|
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": [
|
|
@@ -12,7 +12,9 @@
|
|
|
12
12
|
import { extname, join } from 'node:path';
|
|
13
13
|
import { walkRepo, readSource, profileRepo } from '../harvest/walk.mjs';
|
|
14
14
|
import { decideProfile, profileOf, installedDirs } from '../profiles/index.mjs';
|
|
15
|
-
import {
|
|
15
|
+
import { KITS } from '../profiles/kit-common.mjs';
|
|
16
|
+
import { kitPaintFindings } from './kitpaint.mjs';
|
|
17
|
+
import { PALETTE_CLASS_RE, blankComments } from '../harvest/paint.mjs';
|
|
16
18
|
import { harvestTokens, extractStyling, normalizeHex, isGrey } from '../harvest/tokens.mjs';
|
|
17
19
|
import { harvestComponents, definedComponents } from '../harvest/components.mjs';
|
|
18
20
|
import { loadExclusions } from './exclusions.mjs';
|
|
@@ -35,7 +37,17 @@ export { EMAIL_PRINT_RE, ARTWORK_NAME_RE, SVG_MARKUP_RE, exemptReason } from './
|
|
|
35
37
|
export { WIDGET_CSS_RE, WIDGET_CONFIG_RE, LIBRARY_CLASS_RE, isLibraryClass } from './exempt.mjs';
|
|
36
38
|
// A palette class (bg-blue-500, text-gray-600) where a theme variable exists:
|
|
37
39
|
// the shadcn paint check (7.2). Only meaningful when system.profile.paletteReady.
|
|
38
|
-
|
|
40
|
+
// blankComments (8.4.5) is what the report and the live checks run on a file
|
|
41
|
+
// before matching, so a class named in a comment paints nothing; a guard that
|
|
42
|
+
// matches the raw text counts it, and disagrees with the report.
|
|
43
|
+
export { PALETTE_CLASS_RE, blankComments };
|
|
44
|
+
// A product built on a kit (MUI, Mantine, Chakra UI, Ant Design), 8.4.6: a
|
|
45
|
+
// colour or a pixel size written onto a kit component where the theme has a
|
|
46
|
+
// value. kitPaintFindings(fileText, system.profile.kit, { file }) returns the
|
|
47
|
+
// worded findings the report's live checks give for the same file, with the
|
|
48
|
+
// index of each in the text; null when the file is not a kit file, { exempt }
|
|
49
|
+
// when it is not judged. Only meaningful when system.profile.kit is set.
|
|
50
|
+
export { kitPaintFindings };
|
|
39
51
|
|
|
40
52
|
// Radius, font size, shadow and typeface: the patterns, so both checkers agree
|
|
41
53
|
// on what a declaration is and what counts as a disciplined value.
|
|
@@ -78,6 +90,10 @@ export function learnSystem(repoRoot, { exclude = [] } = {}) {
|
|
|
78
90
|
const profile = profileRepo(repoRoot, files);
|
|
79
91
|
decideProfile(profile, components, files, repoRoot);
|
|
80
92
|
const P = profileOf(profile);
|
|
93
|
+
// the kit the product is built on, with its definition attached, the way
|
|
94
|
+
// the MCP knowledge reads it (mcp/knowledge.mjs): the theme's colours are
|
|
95
|
+
// the token set on a kit repo, whatever stylesheet holds the most literals
|
|
96
|
+
const kit = P.isKit && profile.kit && KITS[profile.kit.name] ? { ...profile.kit, def: KITS[profile.kit.name] } : null;
|
|
81
97
|
const widgetDirs = files.code.filter((f) => /(^|\/)tailwind\.config\.[mc]?[jt]s$/.test(f))
|
|
82
98
|
.filter((f) => WIDGET_CONFIG_RE.test(readSource(join(repoRoot, f)) ?? ''))
|
|
83
99
|
.map((f) => f.slice(0, f.lastIndexOf('/') + 1));
|
|
@@ -104,7 +120,7 @@ export function learnSystem(repoRoot, { exclude = [] } = {}) {
|
|
|
104
120
|
// report counts a token's base statement only, because a dark theme is
|
|
105
121
|
// the system working rather than sprawl; a guard needs the opposite, the
|
|
106
122
|
// full set, or it cannot recognise a dark-theme value as on-system.
|
|
107
|
-
tokens: t.tokenColors ?? t.colors.filter((c) => c.isToken).map((c) => c.value),
|
|
123
|
+
tokens: [...new Set([...(t.tokenColors ?? t.colors.filter((c) => c.isToken).map((c) => c.value)), ...(kit?.themeValues ?? [])])],
|
|
108
124
|
tokenNames,
|
|
109
125
|
spacing: t.spacing,
|
|
110
126
|
radii: t.radii,
|
|
@@ -129,6 +145,9 @@ export function learnSystem(repoRoot, { exclude = [] } = {}) {
|
|
|
129
145
|
// only a shadcn kit has installed code; a hand-written components/ui on a
|
|
130
146
|
// plain product is the team's own (the report splits nothing there either)
|
|
131
147
|
installedDirs: P.isShadcn && !P.isRegistry ? installedDirs(P) : [],
|
|
148
|
+
// a product built on a kit: name, theme files and values, spacing step,
|
|
149
|
+
// the team's own layers over it, and the definition kitPaintFindings reads
|
|
150
|
+
kit,
|
|
132
151
|
// a shadcn kit whose theme file holds the variables, in CSS-variable
|
|
133
152
|
// mode: a palette class in own code is paint from a tin
|
|
134
153
|
paletteReady: P.isShadcn && (P.shadcn?.sheet?.shadcnPresent ?? 0) >= 5 && P.designSystem?.cssVariables !== false,
|
|
@@ -234,3 +234,47 @@ export function countKitPaint(root, codeFiles, { importRe: kitImportRe, themeRe,
|
|
|
234
234
|
exempt: exempt.slice(0, 20),
|
|
235
235
|
};
|
|
236
236
|
}
|
|
237
|
+
|
|
238
|
+
const aKit = (name) => (/^(?:[AEIOU]|MUI)/.test(name) ? `an ${name}` : `a ${name}`);
|
|
239
|
+
const plain = (html) => String(html ?? '').replace(/<\/?code>/g, '').replace(/"/g, '"');
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* One file's kit findings, worded: what the MCP validate and review say about
|
|
243
|
+
* a line, and what guard-my-design-system says about the same line. One
|
|
244
|
+
* function, so the three never disagree. `kit` is the profile's kit with its
|
|
245
|
+
* definition attached ({ name, def, themeFiles, themeValues, spacingUnit,
|
|
246
|
+
* layers }). Returns null when the file is not a kit file or is the theme;
|
|
247
|
+
* { exempt } when the file is not judged, and why; otherwise
|
|
248
|
+
* { exempt: null, findings: [{ rule, index, value, label, note, message, fix }] }:
|
|
249
|
+
* label is the finding in a few words ("colour written onto an MUI
|
|
250
|
+
* component"), note what the theme says about it or null, message the two
|
|
251
|
+
* as one sentence for the live checks.
|
|
252
|
+
*/
|
|
253
|
+
export function kitPaintFindings(src, kit, { file = null } = {}) {
|
|
254
|
+
if (!kit?.def) return null;
|
|
255
|
+
const paint = kitPaintInSource(src, kit.def, { file, layers: kit.layers ?? [] });
|
|
256
|
+
if (!paint) return null;
|
|
257
|
+
if (paint.exempt) return { exempt: paint.exempt, findings: [] };
|
|
258
|
+
const adv = kit.def.advice, themeFile = kit.themeFiles?.[0] ?? null;
|
|
259
|
+
const themeSet = new Set(kit.themeValues ?? []);
|
|
260
|
+
const findings = [];
|
|
261
|
+
const colourLabel = `colour written onto ${aKit(kit.name)} component`;
|
|
262
|
+
for (const c of paint.colours) {
|
|
263
|
+
findings.push(themeSet.has(c.value)
|
|
264
|
+
? { rule: 'kit-colour', index: c.index, value: c.value, label: colourLabel,
|
|
265
|
+
note: `the theme already holds it${themeFile ? ` (${themeFile})` : ''}`,
|
|
266
|
+
message: `Colour ${c.value} written onto ${aKit(kit.name)} component. The theme already holds it${themeFile ? ` (${themeFile})` : ''}.`,
|
|
267
|
+
fix: plain(adv.colourHow) }
|
|
268
|
+
: { rule: 'kit-colour', index: c.index, value: c.value, label: colourLabel,
|
|
269
|
+
note: 'the theme has no such colour',
|
|
270
|
+
message: `Colour ${c.value} written onto ${aKit(kit.name)} component, and the theme has no such colour.`,
|
|
271
|
+
fix: `Add it to the theme once${themeFile ? ` (${themeFile})` : ` with ${adv.themeCall}`}, then read it there. ${plain(adv.colourHow)}` });
|
|
272
|
+
}
|
|
273
|
+
for (const h of paint.px) {
|
|
274
|
+
const raw = h.value.split(': ')[1];
|
|
275
|
+
findings.push({ rule: 'kit-px', index: h.index, value: h.value, label: `pixel size on ${aKit(kit.name)} component`, note: null,
|
|
276
|
+
message: `Pixel size ${h.value} on ${aKit(kit.name)} component.`,
|
|
277
|
+
fix: adv.step ? adv.step(kit, parseFloat(raw)) : plain(adv.spacingHow?.(kit)) });
|
|
278
|
+
}
|
|
279
|
+
return { exempt: null, findings };
|
|
280
|
+
}
|
|
@@ -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.6';
|
|
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.
|
|
@@ -16,7 +16,7 @@ import { hexRgb } from '../lib/nearpairs.mjs';
|
|
|
16
16
|
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
|
-
import {
|
|
19
|
+
import { kitPaintFindings } from '../lib/kitpaint.mjs';
|
|
20
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.
|
|
@@ -41,8 +41,6 @@ export function checksFor(k) {
|
|
|
41
41
|
return CHECKS;
|
|
42
42
|
}
|
|
43
43
|
// "an MUI component", "an Ant Design component", "a Mantine component"
|
|
44
|
-
const aKit = (name) => (/^(?:[AEIOU]|MUI)/.test(name) ? `an ${name}` : `a ${name}`);
|
|
45
|
-
const plain = (html) => String(html ?? '').replace(/<\/?code>/g, '').replace(/"/g, '"');
|
|
46
44
|
|
|
47
45
|
const CSS_FILE_RE = /\.(css|scss|sass|less|styl)$/i;
|
|
48
46
|
const lineOf = (text, index) => text.slice(0, index).split('\n').length;
|
|
@@ -115,29 +113,12 @@ export function validateContent(content, k) {
|
|
|
115
113
|
// compare, SVG artwork), so the generic check stays out of its way.
|
|
116
114
|
let kitOwnsColours = false;
|
|
117
115
|
if (k.kit?.def && !css) {
|
|
118
|
-
const
|
|
119
|
-
if (
|
|
116
|
+
const judged = kitPaintFindings(text, k.kit, { file });
|
|
117
|
+
if (judged && !judged.exempt) {
|
|
120
118
|
kitOwnsColours = true;
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
if (themeSet.has(c.value)) {
|
|
125
|
-
add('kit-colour', 'violation', c.index,
|
|
126
|
-
`Colour ${c.value} written onto ${aKit(kitName)} component. The theme already holds it${themeFile ? ` (${themeFile})` : ''}.`,
|
|
127
|
-
plain(adv.colourHow));
|
|
128
|
-
} else {
|
|
129
|
-
add('kit-colour', 'violation', c.index,
|
|
130
|
-
`Colour ${c.value} written onto ${aKit(kitName)} component, and the theme has no such colour.`,
|
|
131
|
-
`Add it to the theme once${themeFile ? ` (${themeFile})` : ` with ${adv.themeCall}`}, then read it there. ${plain(adv.colourHow)}`);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
for (const h of paint.px) {
|
|
135
|
-
const [prop, raw] = h.value.split(': ');
|
|
136
|
-
kitPx.add(raw);
|
|
137
|
-
add('kit-px', 'violation', h.index,
|
|
138
|
-
`Pixel size ${h.value} on ${aKit(kitName)} component.`,
|
|
139
|
-
adv.step ? adv.step(k.kit, parseFloat(raw)) : plain(adv.spacingHow?.(k.kit)));
|
|
140
|
-
void prop;
|
|
119
|
+
for (const f of judged.findings) {
|
|
120
|
+
if (f.rule === 'kit-px') kitPx.add(f.value.split(': ')[1]);
|
|
121
|
+
add(f.rule, 'violation', f.index, f.message, f.fix);
|
|
141
122
|
}
|
|
142
123
|
}
|
|
143
124
|
}
|