roast-my-design-system 8.9.1 → 8.9.2
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 +1 -1
- package/package.json +1 -1
- package/skills/roast-my-design-system/scripts/harvest/index.mjs +5 -1
- package/skills/roast-my-design-system/scripts/harvest/tokens.mjs +9 -1
- package/skills/roast-my-design-system/scripts/lib/charts.mjs +7 -3
- package/skills/roast-my-design-system/scripts/lib/guard-api.mjs +2 -1
- package/skills/roast-my-design-system/scripts/lib/version.mjs +1 -1
- package/skills/roast-my-design-system/scripts/mcp/knowledge.mjs +5 -2
package/README.md
CHANGED
|
@@ -49,7 +49,7 @@ Run it on your codebase and get, in about a second:
|
|
|
49
49
|
|
|
50
50
|
## Why this exists
|
|
51
51
|
|
|
52
|
-
Your AI agent (Claude, Cursor, Copilot) builds UI by
|
|
52
|
+
Your AI agent (Claude, Cursor, Copilot) builds UI by copying what is already in your repo. We measured it: 164 agent sessions on 10 real products, with and without this tool. On everyday work the agent reused the components and tokens it found and stayed on-system. It went off-system where the design system had no answer, a chart with no palette or a whole new theme, and it filled the gap the way the repo's own code did: by hand. So the mess an agent adds is a map of the gaps in your system. This tool measures the mess, names the gaps, and puts the check inside the agent's editor, so it runs whether or not the agent thinks to ask.
|
|
53
53
|
|
|
54
54
|
## Live examples
|
|
55
55
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "roast-my-design-system",
|
|
3
|
-
"version": "8.9.
|
|
3
|
+
"version": "8.9.2",
|
|
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": [
|
|
@@ -159,7 +159,11 @@ const duplicates = findDuplicates(components, profile.uiDir, target, profile.uiD
|
|
|
159
159
|
const context = harvestContext(target);
|
|
160
160
|
// The chart palette the repo keeps, or the charts painting by hand without
|
|
161
161
|
// one, and the gaps an agent will fill by inventing (lib/charts, lib/gaps).
|
|
162
|
-
const charts =
|
|
162
|
+
const charts = (() => {
|
|
163
|
+
const installed = installedDirs(profileOf(profile));
|
|
164
|
+
const own = (f) => !installed.some((d) => f === d || f.startsWith(`${d}/`));
|
|
165
|
+
return chartSystemOf(files, (f) => readSource(resolve(target, f)), { own });
|
|
166
|
+
})();
|
|
163
167
|
const gaps = designGaps({ charts, tokenFile: tokens.tokenFile ?? null });
|
|
164
168
|
const staleRules = ruleStaleness(target, components,
|
|
165
169
|
new Set(neverImportedComponents(components, profile.uiDir).map((c) => c.name)),
|
|
@@ -137,6 +137,9 @@ const isTokenRef = (v) =>
|
|
|
137
137
|
const FONTFAMILY_PROPS = /font-family\s*:\s*([^;{}]+)[;}]/g;
|
|
138
138
|
const SHADOW_PROPS = /box-shadow\s*:\s*([^;{}]+)[;}]/g;
|
|
139
139
|
const LENGTH_RE = /-?\d*\.?\d+(?:px|rem|em|%|vh|vw|ch)\b/g;
|
|
140
|
+
// the spacing keys of a style object, camelCase: padding, paddingTop, margin,
|
|
141
|
+
// marginInline, gap, rowGap, top, inset, insetBlock
|
|
142
|
+
const INLINE_SPACING_RE = /\b(padding(?:Top|Right|Bottom|Left|Block|Inline|X|Y)?|margin(?:Top|Right|Bottom|Left|Block|Inline|X|Y)?|gap|rowGap|columnGap|top|right|bottom|left|inset(?:Block|Inline)?)\s*:\s*(['"`][^'"`]*['"`])/g;
|
|
140
143
|
|
|
141
144
|
// ---------- Tailwind utility classes ----------
|
|
142
145
|
// Colour-bearing utilities: bg-red-500, text-zinc-400, border-gray-200, arbitrary bg-[#1a1a1a]
|
|
@@ -274,7 +277,12 @@ export function extractStyling(src, { css = false } = {}) {
|
|
|
274
277
|
const at = src.indexOf(b);
|
|
275
278
|
if (isStaticInline(b)) out.inlineBlocks.push({ index: at });
|
|
276
279
|
for (const m of b.matchAll(HEX_RE)) out.colors.push({ value: normalizeHex(m[0]), index: at + m.index });
|
|
277
|
-
|
|
280
|
+
// Only the spacing properties, the same set the CSS rule reads: a width,
|
|
281
|
+
// a font size or a shadow's offsets (boxShadow: '0 3px 9px …') are not
|
|
282
|
+
// spacing, and the live check reported them as such until 8.9.2.
|
|
283
|
+
for (const m of b.matchAll(INLINE_SPACING_RE)) {
|
|
284
|
+
for (const len of (m[2].match(LENGTH_RE) ?? [])) out.spacing.push({ value: len, index: at + m.index });
|
|
285
|
+
}
|
|
278
286
|
}
|
|
279
287
|
// hex literals outside class strings / style blocks (styled-components,
|
|
280
288
|
// colour consts) — everything not already collected above
|
|
@@ -62,11 +62,13 @@ const refRe = (name) => name.startsWith('--')
|
|
|
62
62
|
|
|
63
63
|
/**
|
|
64
64
|
* What the repo already knows about charts, from the same file set the rest
|
|
65
|
-
* of the knowledge reads.
|
|
65
|
+
* of the knowledge reads. `own` says whether a file is the team's own code;
|
|
66
|
+
* installed kit code (a shadcn catalogue, a registry block) is left out of
|
|
67
|
+
* the precedents, and a fresh install therefore has no chart gap.
|
|
66
68
|
* @returns { palette: { names, file, shadcnDefault, referenced } | null,
|
|
67
69
|
* precedents: [{ file, count, sample }], chartFiles }
|
|
68
70
|
*/
|
|
69
|
-
export function chartSystemOf(files, read) {
|
|
71
|
+
export function chartSystemOf(files, read, { own = () => true } = {}) {
|
|
70
72
|
const names = [], where = new Map();
|
|
71
73
|
const noteToken = (name, file) => { if (!where.has(name)) { names.push(name); where.set(name, file); } };
|
|
72
74
|
for (const f of files.styles ?? []) {
|
|
@@ -84,7 +86,9 @@ export function chartSystemOf(files, read) {
|
|
|
84
86
|
if (COLOUR_LITERAL_RE.test(text.slice(m.index, m.index + 600))) noteToken(m[1], f);
|
|
85
87
|
}
|
|
86
88
|
}
|
|
87
|
-
|
|
89
|
+
// a chart inside installed kit code (shadcn's components/ui/chart.tsx, a
|
|
90
|
+
// registry block) is the kit's, not a precedent the team set
|
|
91
|
+
if (own(f) && isChartFile(f, text)) chartTexts.push({ f, text });
|
|
88
92
|
}
|
|
89
93
|
const palette = names.length ? { names, file: where.get(names[0]), shadcnDefault: names.every((n) => SHADCN_DEFAULT_RE.test(n)), referenced: false } : null;
|
|
90
94
|
const precedents = [];
|
|
@@ -110,9 +110,10 @@ export function learnSystem(repoRoot, { exclude = [] } = {}) {
|
|
|
110
110
|
// the same two lists the MCP knowledge builds (mcp/knowledge.mjs), from the
|
|
111
111
|
// same helpers, for tokenTwinFindings and avoidedImportFindings (8.6.1)
|
|
112
112
|
const read = (f) => readSource(join(repoRoot, f));
|
|
113
|
-
const charts = chartSystemOf(files, read);
|
|
114
113
|
const hardDupes = (findDuplicates(ledger, profile.uiDir, repoRoot).exactDuplicates ?? []).filter((d) => !d.wrapped);
|
|
115
114
|
const P = profileOf(profile);
|
|
115
|
+
const installedNow = installedDirs(P);
|
|
116
|
+
const charts = chartSystemOf(files, read, { own: (f) => !installedNow.some((d) => f === d || f.startsWith(`${d}/`)) });
|
|
116
117
|
// the kit the product is built on, with its definition attached, the way
|
|
117
118
|
// the MCP knowledge reads it (mcp/knowledge.mjs): the theme's colours are
|
|
118
119
|
// the token set on a kit repo, whatever stylesheet holds the most literals
|
|
@@ -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.9.
|
|
4
|
+
export const VERSION = '8.9.2';
|
|
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.
|
|
@@ -22,7 +22,7 @@ import { resolveWorkspaces } from '../lib/workspaces.mjs';
|
|
|
22
22
|
import { neverImportedComponents } from '../lib/neverimported.mjs';
|
|
23
23
|
import { typefaceOf } from '../lib/typefaces.mjs';
|
|
24
24
|
import { hexRgb } from '../lib/nearpairs.mjs';
|
|
25
|
-
import { decideProfile, profileOf } from '../profiles/index.mjs';
|
|
25
|
+
import { decideProfile, profileOf, installedDirs } from '../profiles/index.mjs';
|
|
26
26
|
import { KITS } from '../profiles/kit-common.mjs';
|
|
27
27
|
import { repoTokenDefs } from '../lib/tokentwins.mjs';
|
|
28
28
|
import { dupeCopiesOf } from '../lib/avoidedimports.mjs';
|
|
@@ -132,7 +132,10 @@ export function loadKnowledge(root) {
|
|
|
132
132
|
const tokenDefs = repoTokenDefs(files.styles, read);
|
|
133
133
|
// the chart palette the repo keeps, or the charts that paint by hand
|
|
134
134
|
// without one (lib/charts.mjs)
|
|
135
|
-
|
|
135
|
+
// installed kit code is the kit's own door, never a precedent (profiles/index)
|
|
136
|
+
const installed = installedDirs(P);
|
|
137
|
+
const ownCode = (f) => !installed.some((d) => f === d || f.startsWith(`${d}/`));
|
|
138
|
+
const charts = chartSystemOf(files, read, { own: ownCode });
|
|
136
139
|
const gaps = designGaps({ charts, tokenFile: tokens.tokenFile ?? null });
|
|
137
140
|
|
|
138
141
|
return {
|