roast-my-design-system 7.7.0 → 7.8.0

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": "7.7.0",
3
+ "version": "7.8.0",
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 34 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": [
@@ -30,7 +30,8 @@ import { PALETTE } from '../profiles/shadcn-data.mjs';
30
30
  // and any opacity suffix. Word boundary at the front stops `text-primary` and
31
31
  // `bg-sidebar` from matching; the shade stops `border-gray` (no shade) which
32
32
  // is not a Tailwind class.
33
- const TIN_RE = new RegExp(`(?<![\\w-])(?:[\\w-]+:)*(?:bg|text|border|ring|outline|from|to|via|fill|stroke|divide|decoration|placeholder|caret|accent|shadow)-(?:${PALETTE})-(?:50|[1-9]00|950)(?:/\\d+)?(?![\\w-])`, 'g');
33
+ // exported for the guard: the same pattern judges an added line
34
+ export const PALETTE_CLASS_RE = new RegExp(`(?<![\\w-])(?:[\\w-]+:)*(?:bg|text|border|ring|outline|from|to|via|fill|stroke|divide|decoration|placeholder|caret|accent|shadow)-(?:${PALETTE})-(?:50|[1-9]00|950)(?:/\\d+)?(?![\\w-])`, 'g');
34
35
  // Evening overrides painted by hand with white or black (the palette shades
35
36
  // are already caught above with their dark: prefix).
36
37
  const DARK_WB_RE = /(?<![\w-])dark:(?:bg|text|border)-(?:white|black)(?:\/\d+)?(?![\w-])/g;
@@ -70,7 +71,7 @@ export function countPaint(root, codeFiles, { uiDirs = [], kitNames = new Set()
70
71
  if (src === null || exemptReason(f, src)) continue;
71
72
  ownFiles += 1;
72
73
 
73
- const tinHits = [...src.matchAll(TIN_RE)].map((m) => m[0]);
74
+ const tinHits = [...src.matchAll(PALETTE_CLASS_RE)].map((m) => m[0]);
74
75
  const wbHits = [...src.matchAll(DARK_WB_RE)].map((m) => m[0]);
75
76
  const all = [...tinHits, ...wbHits];
76
77
  if (all.length) {
@@ -8,7 +8,7 @@
8
8
  */
9
9
  import { existsSync } from 'node:fs';
10
10
  import {
11
- EMAIL_PRINT_RE, ARTWORK_NAME_RE, RENDER_TO_IMAGE_RE, OG_ROUTE_RE, RENDERER_PATH_RE, CRASH_PAGE_RE, svgHeavy, exemptReason, isLibraryClass,
11
+ EMAIL_PRINT_RE, ARTWORK_NAME_RE, RENDER_TO_IMAGE_RE, OG_ROUTE_RE, RENDERER_PATH_RE, CRASH_PAGE_RE, svgHeavy, 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';
@@ -297,8 +297,6 @@ export function harvestTokens(root, styleFiles, codeFiles) {
297
297
  // page and must beat the host's CSS: Tailwind imported with the important
298
298
  // flag, or a config scoping every utility under an id. There, !important is
299
299
  // the medium, like inline styles in email. Skipped, named with the reason.
300
- const WIDGET_CSS_RE = /@import\s+["']tailwindcss(?:\/utilities\.css)?["'][^;]*\bimportant\b/;
301
- const WIDGET_CONFIG_RE = /\bimportant\s*:\s*["']#/;
302
300
  const widgetDirs = codeFiles.filter((f) => /(^|\/)tailwind\.config\.[mc]?[jt]s$/.test(f))
303
301
  .filter((f) => WIDGET_CONFIG_RE.test(readSource(join(root, f)) ?? ''))
304
302
  .map((f) => f.slice(0, f.lastIndexOf('/') + 1));
@@ -64,3 +64,11 @@ export function exemptReason(file, text = '') {
64
64
  */
65
65
  export const LIBRARY_CLASS_RE = /^(cm-|ͼ|ProseMirror|monaco-|sp-|react-datepicker|react-tel-input|DateInput_|DayPicker|react-grid-|react-resizable|react-flow|react-select|react-toastify|Toastify|select2|EmojiPickerReact|epr-|notion-|fc-|fc$|tippy-|hljs|language-|ps__|ace_|ng-|docsearch|DocSearch|grecaptcha|rr-|sbdocs|docs-story|mapboxgl-|leaflet-|swiper-|slick-|rc-|ant-|Mui|ck-|ql-|tox-|mce-|DraftEditor|public-Draft|w-md-editor|rdp-|recharts-|apexcharts-|intercom-|crisp-|hubspot|shiki|katex|mermaid|prism-)/;
66
66
  export const isLibraryClass = (c) => LIBRARY_CLASS_RE.test(c);
67
+
68
+ /**
69
+ * An embedded widget (a survey, a chat bubble) lives inside a stranger's page
70
+ * and must beat the host's CSS: Tailwind imported with the important flag, or
71
+ * a config scoping every utility under an id. There, !important is the medium.
72
+ */
73
+ export const WIDGET_CSS_RE = /@import\s+["']tailwindcss(?:\/utilities\.css)?["'][^;]*\bimportant\b/;
74
+ export const WIDGET_CONFIG_RE = /\bimportant\s*:\s*["']#/;
@@ -10,10 +10,13 @@
10
10
  * text for styling, and say which on-system value a stray most resembles.
11
11
  */
12
12
  import { extname, join } from 'node:path';
13
- import { walkRepo, readSource } from '../harvest/walk.mjs';
13
+ import { walkRepo, readSource, profileRepo } from '../harvest/walk.mjs';
14
+ import { decideProfile, profileOf, installedDirs } from '../profiles/index.mjs';
15
+ import { PALETTE_CLASS_RE } from '../harvest/paint.mjs';
14
16
  import { harvestTokens, extractStyling, normalizeHex, isGrey } from '../harvest/tokens.mjs';
15
17
  import { harvestComponents, definedComponents } from '../harvest/components.mjs';
16
18
  import { loadExclusions } from './exclusions.mjs';
19
+ import { WIDGET_CONFIG_RE } from './exempt.mjs';
17
20
  import { hexRgb } from './nearpairs.mjs';
18
21
  import { typefaceOf, GENERIC_FONTS } from './typefaces.mjs';
19
22
 
@@ -26,6 +29,13 @@ export { definedComponents };
26
29
  // The files no checker should judge. Exported so a guard reads the same list
27
30
  // as the engine rather than keeping a copy that drifts.
28
31
  export { EMAIL_PRINT_RE, ARTWORK_NAME_RE, SVG_MARKUP_RE, exemptReason } from './exempt.mjs';
32
+ // !important as the medium (7.5): a widget stylesheet that must beat its host
33
+ // page, and a selector aimed at a library's own class names. The guard reads
34
+ // the same patterns, so the two checkers agree about the same declaration.
35
+ export { WIDGET_CSS_RE, WIDGET_CONFIG_RE, LIBRARY_CLASS_RE, isLibraryClass } from './exempt.mjs';
36
+ // A palette class (bg-blue-500, text-gray-600) where a theme variable exists:
37
+ // the shadcn paint check (7.2). Only meaningful when system.profile.paletteReady.
38
+ export { PALETTE_CLASS_RE };
29
39
 
30
40
  // Radius, font size, shadow and typeface: the patterns, so both checkers agree
31
41
  // on what a declaration is and what counts as a disciplined value.
@@ -63,6 +73,14 @@ export function learnSystem(repoRoot, { exclude = [] } = {}) {
63
73
  const exclusions = loadExclusions(repoRoot, exclude);
64
74
  const files = walkRepo(repoRoot, 14, exclusions);
65
75
  const t = harvestTokens(repoRoot, files.styles, files.code);
76
+ const components = harvestComponents(repoRoot, files.code).components
77
+ .map(({ name, file, usageCount, isPage }) => ({ name, file, usageCount, isPage }));
78
+ const profile = profileRepo(repoRoot, files);
79
+ decideProfile(profile, components, files, repoRoot);
80
+ const P = profileOf(profile);
81
+ const widgetDirs = files.code.filter((f) => /(^|\/)tailwind\.config\.[mc]?[jt]s$/.test(f))
82
+ .filter((f) => WIDGET_CONFIG_RE.test(readSource(join(repoRoot, f)) ?? ''))
83
+ .map((f) => f.slice(0, f.lastIndexOf('/') + 1));
66
84
 
67
85
  // Every --var definition, as normalizedValue → name, so a guard can say
68
86
  // "use var(--blue-500)" instead of leaving the reader to hunt the hex.
@@ -97,9 +115,34 @@ export function learnSystem(repoRoot, { exclude = [] } = {}) {
97
115
  // Trimmed to the four fields a guard can act on: what it is called, where
98
116
  // it lives, how much the repo leans on it, and whether it is a page (pages
99
117
  // are routes, not reusable parts, so two of a name is not a duplicate).
100
- components: harvestComponents(repoRoot, files.code).components
101
- .map(({ name, file, usageCount, isPage }) => ({ name, file, usageCount, isPage })),
118
+ components,
102
119
  files: { styles: files.styles.length, code: files.code.length },
120
+ // How the repo was read (7.1 to 7.7), decided once by the same profiles
121
+ // the report uses, so a guard treats installed code, a registry's
122
+ // published folders and a widget's stylesheet the way the score does.
123
+ profile: {
124
+ kind: P.kind,
125
+ role: P.role,
126
+ // folders the team did not write (a shadcn catalogue, installed
127
+ // registries, kit blocks): their brackets and copies are not the PR's
128
+ // sin. Empty on a registry: what it publishes is its own work.
129
+ // only a shadcn kit has installed code; a hand-written components/ui on a
130
+ // plain product is the team's own (the report splits nothing there either)
131
+ installedDirs: P.isShadcn && !P.isRegistry ? installedDirs(P) : [],
132
+ // a shadcn kit whose theme file holds the variables, in CSS-variable
133
+ // mode: a palette class in own code is paint from a tin
134
+ paletteReady: P.isShadcn && (P.shadcn?.sheet?.shadcnPresent ?? 0) >= 5 && P.designSystem?.cssVariables !== false,
135
+ sheetFile: P.shadcn?.sheet?.found ? P.shadcn.sheet.file : null,
136
+ // packages whose Tailwind config scopes utilities under an id: widgets
137
+ widgetDirs,
138
+ // a registry: only the published folders are counted; sibling variants
139
+ // and blocks hold the same names by design
140
+ registry: P.isRegistry && P.registry ? {
141
+ countedDirs: P.registry.publishedDirs ?? [],
142
+ variants: P.registry.variants ?? [],
143
+ blockDirs: P.registry.blockDirs ?? [],
144
+ } : null,
145
+ },
103
146
  };
104
147
  }
105
148
 
@@ -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 = '7.7.0';
4
+ export const VERSION = '7.8.0';
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.