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.2",
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 src = readSource(join(root, f));
75
- if (src === null || exemptReason(f, src)) continue;
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.2';
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
- const hits = [...text.matchAll(PALETTE_CLASS_RE)];
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(...text.matchAll(DARK_WB_RE));
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 name as the class (${util}-${example}); if the colour is missing, add it to the theme once.`);
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
- .map((d) => ` <${d.name}> exists in ${d.files.length} places; match what the surrounding code imports, never create another.`);
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