roast-my-design-system 5.8.0 → 5.9.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/README.md +5 -5
- package/package.json +1 -1
- package/skills/roast-my-design-system/scripts/diagnose/index.mjs +44 -6
- package/skills/roast-my-design-system/scripts/harvest/context.mjs +73 -2
- package/skills/roast-my-design-system/scripts/lib/version.mjs +1 -1
- package/skills/roast-my-design-system/scripts/rules/build.mjs +3 -1
package/README.md
CHANGED
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
|
|
9
9
|
A free CLI tool (and Claude Code skill) that roasts your repo's design system with real data, then generates the rules that keep your AI agent on-system.
|
|
10
10
|
|
|
11
|
-
> **New in 5.
|
|
11
|
+
> **New in 5.9: the agent card knows which doors your rules actually reach.** The report now states, as plain fact, which tools can read the rules files you have: Claude Code reads CLAUDE.md, Codex reads AGENTS.md, Cursor reads AGENTS.md and `.cursor/rules`. Where a door is missing, the card shows the one-line fix (Claude Code skips AGENTS.md; a CLAUDE.md containing the single line `@AGENTS.md` closes the gap). The scan also finds rules files nested in subfolders now, which is how monorepos really do it (twenty carries 35 AGENTS.md files; a root-only look reported 1), and it recognises GEMINI.md, `.windsurf/rules` and `.github/instructions`.
|
|
12
12
|
|
|
13
|
-
> **New in 5.
|
|
13
|
+
> **New in 5.8: every finding can explain itself.** A "why this matters" toggle under each finding unfolds the calm story behind the brutal number: how the mess arrives innocently, what it costs later, how an agent multiplies it, and why the ideal sits where it sits, benchmark medians included. Written to a plain-language standard, reviewed word by word.
|
|
14
14
|
|
|
15
15
|
> **New in 5.0: it runs as a local MCP server.** One command, and your agent asks the design system before writing UI, then gets the work checked after: which Button is canonical, which token holds that colour, review my changes. Local, deterministic, nothing leaves your machine. See [Live answers over MCP](#live-answers-over-mcp).
|
|
16
16
|
|
|
@@ -21,7 +21,7 @@ Run it on your codebase and get, in about a second:
|
|
|
21
21
|
- **Reads React and web components alike.** Stencil, Lit and custom elements detected by tag registration, counted by kebab tag; libraries get composition maps, not adoption accusations; earned token namespaces named in the header; and anything the scan cannot read is declared "not measured" instead of scored.
|
|
22
22
|
- **The receipts behind it.** Every colour and its near-identical twin, every spacing value, typeface, duplicated or never-imported component, inline style and !important, with real file paths, in one self-contained HTML report you can open, Slack or email.
|
|
23
23
|
- **The first fixes ranked by payoff, each with its prompt.** A "Where to start" list derived from your own numbers, and every move carries a copy button with a ready-made fix prompt for your agent: the finding, the files, the expected payoff, and rules that respect deliberate craft. Fix, re-run the scan, press the next button.
|
|
24
|
-
- **Rules that stop the mess coming back.** A generated `design-system-rules.md` with canonical components, your token file, and known duplicates to avoid, so your AI agent follows your system instead of guessing at it. `--apply` injects them into every agent file you have: Claude, Cursor, GitHub Copilot, and Windsurf. Every scan also checks the rules you already have for stale references: paths that no longer exist, components named canonical that nothing imports anymore.
|
|
24
|
+
- **Rules that stop the mess coming back.** A generated `design-system-rules.md` with canonical components, your token file, and known duplicates to avoid, so your AI agent follows your system instead of guessing at it. `--apply` injects them into every agent file you have: Claude, Cursor, GitHub Copilot, and Windsurf. Every scan also checks the rules you already have for stale references: paths that no longer exist, components named canonical that nothing imports anymore. And the agent card states which tools can actually read the rules you have (Claude Code, Codex, Cursor), with the one-line fix where a door is missing.
|
|
25
25
|
|
|
26
26
|
## Why this exists
|
|
27
27
|
|
|
@@ -60,11 +60,11 @@ One scan powers all of it; the flags decide what lands on disk. Combine freely.
|
|
|
60
60
|
|
|
61
61
|
The full report for vercel/ai-chatbot, top to bottom — including "What the numbers mean", Claude's read of the scan, embedded right under the verdict:
|
|
62
62
|
|
|
63
|
-

|
|
64
64
|
|
|
65
65
|
The same report in light mode (one file, built-in toggle):
|
|
66
66
|
|
|
67
|
-

|
|
68
68
|
|
|
69
69
|
## What makes the numbers trustworthy
|
|
70
70
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "roast-my-design-system",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.9.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": [
|
|
@@ -1168,10 +1168,29 @@ function packagesSection() {
|
|
|
1168
1168
|
}
|
|
1169
1169
|
|
|
1170
1170
|
function agentSection() {
|
|
1171
|
-
|
|
1171
|
+
// Root files get chips; nested ones (monorepo packages) fold into a single
|
|
1172
|
+
// count chip, because 35 chips is wallpaper and one number is a finding.
|
|
1173
|
+
const rootAgent = agentFiles.filter((c) => !c.nested);
|
|
1174
|
+
const nestedAgent = agentFiles.filter((c) => c.nested);
|
|
1175
|
+
const overflow = nestedAgent.find((c) => c.file.startsWith('(+'));
|
|
1176
|
+
const nestedCount = nestedAgent.filter((c) => !c.file.startsWith('(+')).length
|
|
1177
|
+
+ (overflow ? parseInt(overflow.file.slice(2), 10) || 0 : 0);
|
|
1178
|
+
const have = rootAgent.map((c) => `<span class="chip chip-agent">${esc(c.file)} · ${esc(c.tool ?? c.kind)}</span>`).join('')
|
|
1179
|
+
+ (nestedCount ? `<span class="chip chip-agent">+${nestedCount} nested in subfolders</span>` : '');
|
|
1180
|
+
const readsList = (rootAgent.length ? rootAgent : nestedAgent.slice(0, 3)).map((c) => `<span class="mono">${esc(c.file)}</span>`).join(', ');
|
|
1172
1181
|
const msg = agentFiles.length
|
|
1173
|
-
? `<p class="sub">Your agent reads ${
|
|
1182
|
+
? `<p class="sub">Your agent reads ${readsList}${nestedCount && rootAgent.length ? `, plus ${nestedCount} rules file${nestedCount === 1 ? '' : 's'} nested in subfolders` : ''}. But none of it points at a single source of truth for components and tokens, because there isn't one yet. The numbers below are what your agent actually works from.</p>`
|
|
1174
1183
|
: `<p class="sub">No <span class="mono">CLAUDE.md</span>, no <span class="mono">AGENTS.md</span>, no <span class="mono">.cursorrules</span>. Every time your AI builds UI here, it guesses, from everything below. This is why its output looks almost-but-not-quite right.</p>`;
|
|
1184
|
+
// Door coverage, stated as fact: which tools can read what exists. Claude
|
|
1185
|
+
// Code reads CLAUDE.md only; Codex reads AGENTS.md; Cursor reads AGENTS.md
|
|
1186
|
+
// and .cursor/rules (its legacy .cursorrules is labelled, not judged).
|
|
1187
|
+
const hasFile = (name) => agentFiles.some((c) => c.file === name || c.file.endsWith(`/${name}`));
|
|
1188
|
+
const claudeOk = hasFile('CLAUDE.md');
|
|
1189
|
+
const agentsOk = hasFile('AGENTS.md');
|
|
1190
|
+
const cursorOk = agentsOk || agentFiles.some((c) => c.file.endsWith('.cursor/rules') || c.file === '.cursor/rules');
|
|
1191
|
+
const cursorLegacyOnly = !cursorOk && hasFile('.cursorrules');
|
|
1192
|
+
const mark = (ok) => (ok ? '<b class="cov-yes">✓</b>' : '<b class="cov-no">—</b>');
|
|
1193
|
+
const coverage = agentFiles.length ? `<p class="sub coverage">Readable by: Claude Code ${mark(claudeOk)} · Codex ${mark(agentsOk)} · Cursor ${cursorLegacyOnly ? '<b class="cov-no">legacy file only</b>' : mark(cursorOk)}${claudeOk && !agentsOk ? ' · <span class="dim">copy the same rules into an AGENTS.md and the other tools see them too</span>' : ''}${agentsOk && !claudeOk ? ' · <span class="dim">Claude Code skips AGENTS.md: add a CLAUDE.md containing the single line <span class="mono">@AGENTS.md</span> and it reads the same rules</span>' : ''}</p>` : '';
|
|
1175
1194
|
const stale = h.staleRules ?? [];
|
|
1176
1195
|
const staleRows = stale.map((s) => `
|
|
1177
1196
|
<div class="ledger-row">
|
|
@@ -1186,7 +1205,7 @@ function agentSection() {
|
|
|
1186
1205
|
</div>` : '';
|
|
1187
1206
|
return `<section class="glass pad agent">
|
|
1188
1207
|
${sectionHead('What your AI agent sees today', '')}
|
|
1189
|
-
${msg}${have ? `<div class="chips">${have}</div>` : ''}${staleBlock}</section>`;
|
|
1208
|
+
${msg}${have ? `<div class="chips">${have}</div>` : ''}${coverage}${staleBlock}</section>`;
|
|
1190
1209
|
}
|
|
1191
1210
|
|
|
1192
1211
|
// ---------- page ----------
|
|
@@ -1294,6 +1313,9 @@ const html = `<!doctype html>
|
|
|
1294
1313
|
.chip { border:1px solid var(--line); background:var(--card); border-radius:99px;
|
|
1295
1314
|
padding:4px 12px; font:500 11.5px/1.5 var(--sans); color:var(--text); }
|
|
1296
1315
|
.chip-agent { background:var(--text); color:var(--bg); border-color:var(--text); font-weight:600; }
|
|
1316
|
+
.coverage { margin-top:12px; }
|
|
1317
|
+
.cov-yes { color:var(--ok); font-weight:700; }
|
|
1318
|
+
.cov-no { color:var(--coral); font-weight:700; }
|
|
1297
1319
|
.excl { margin-top:10px; font:500 12px/1.6 var(--sans); color:var(--dim); }
|
|
1298
1320
|
.excl .mono { font-size:11.5px; color:var(--text); }
|
|
1299
1321
|
.stale { margin-top:18px; }
|
|
@@ -1537,6 +1559,11 @@ const html = `<!doctype html>
|
|
|
1537
1559
|
.foot-sub .pulse polyline { animation:none; stroke-dashoffset:0; }
|
|
1538
1560
|
}
|
|
1539
1561
|
.scan-id { font-family:var(--mono); font-size:11px; letter-spacing:.04em; }
|
|
1562
|
+
.copy-pill { font:700 9.5px/1 var(--sans); letter-spacing:.06em; text-transform:uppercase; padding:3px 8px;
|
|
1563
|
+
border-radius:99px; border:1px solid var(--line); background:transparent; color:var(--dim);
|
|
1564
|
+
cursor:pointer; vertical-align:1px; }
|
|
1565
|
+
.copy-pill:hover { color:var(--accent); border-color:var(--accent); }
|
|
1566
|
+
@media print { .copy-pill { display:none; } }
|
|
1540
1567
|
footer .brand { color:var(--accent); font-weight:700; text-decoration:none; }
|
|
1541
1568
|
footer .brand:hover { text-decoration:underline; }
|
|
1542
1569
|
footer .creds { display:flex; gap:16px; font:700 9.5px/1.6 var(--sans); letter-spacing:.16em; text-transform:uppercase; color:var(--dim2); }
|
|
@@ -1646,6 +1673,8 @@ ${extraSectionsHtml()}
|
|
|
1646
1673
|
|
|
1647
1674
|
${whereToStartSection()}
|
|
1648
1675
|
|
|
1676
|
+
${agentSection()}
|
|
1677
|
+
|
|
1649
1678
|
${giftSection()}
|
|
1650
1679
|
|
|
1651
1680
|
${trapsBlock()}
|
|
@@ -1654,8 +1683,6 @@ ${trapsBlock()}
|
|
|
1654
1683
|
|
|
1655
1684
|
${packagesSection()}
|
|
1656
1685
|
|
|
1657
|
-
${agentSection()}
|
|
1658
|
-
|
|
1659
1686
|
<section style="margin-top:16px">${paletteSection()}</section>
|
|
1660
1687
|
|
|
1661
1688
|
${spacingBars()}
|
|
@@ -1669,7 +1696,7 @@ ${componentsSection()}
|
|
|
1669
1696
|
|
|
1670
1697
|
<footer>
|
|
1671
1698
|
<div class="foot-left">
|
|
1672
|
-
<div>Generated by <a class="brand" href="https://github.com/gregkozakiewicz/roast-my-design-system">roast-my-design-system</a> <span class="ver">ver. ${VERSION}</span
|
|
1699
|
+
<div>Generated by <a class="brand" href="https://github.com/gregkozakiewicz/roast-my-design-system">roast-my-design-system</a> <span class="ver">ver. ${VERSION}</span> · free to run: <a class="brand" href="https://www.npmjs.com/package/roast-my-design-system"><span class="mono" id="npx-cmd">npx roast-my-design-system</span></a> <button type="button" class="copy-pill" id="npx-copy">copy</button> · also a Claude Code + Codex skill and a local MCP server</div>
|
|
1673
1700
|
<div class="foot-sub">Designed and built by <a class="author" href="https://gregkozakiewicz.com/?utm_source=roast-report"><span class="gk-mark"></span>Greg Kozakiewicz</a> · <span class="scan-id" title="scan id">${scanId}</span> · ${FEEDBACK_ASK} <a class="feedback" href="${feedbackUrl(VERSION).replace(/&/g, '&')}">${PULSE_ICON}${FEEDBACK_CTA}</a></div>
|
|
1674
1701
|
</div>
|
|
1675
1702
|
<span class="creds"><span>Non-destructive scan</span><span>Read-only</span><span>Paths are real</span></span>
|
|
@@ -1728,6 +1755,17 @@ ${componentsSection()}
|
|
|
1728
1755
|
v.textContent=show?'hide it':'view it first';
|
|
1729
1756
|
});
|
|
1730
1757
|
});
|
|
1758
|
+
var npxCopy=document.getElementById('npx-copy');
|
|
1759
|
+
if(npxCopy){
|
|
1760
|
+
npxCopy.addEventListener('click', function(){
|
|
1761
|
+
var text=document.getElementById('npx-cmd').textContent;
|
|
1762
|
+
var done=function(){ npxCopy.textContent='copied'; setTimeout(function(){ npxCopy.textContent='copy'; },1600); };
|
|
1763
|
+
var manual=function(){ var r=document.createRange(); r.selectNodeContents(document.getElementById('npx-cmd'));
|
|
1764
|
+
var s=getSelection(); s.removeAllRanges(); s.addRange(r); npxCopy.textContent='select + copy'; setTimeout(function(){ npxCopy.textContent='copy'; },2600); };
|
|
1765
|
+
if(navigator.clipboard && navigator.clipboard.writeText){ navigator.clipboard.writeText(text).then(done, manual); }
|
|
1766
|
+
else { manual(); }
|
|
1767
|
+
});
|
|
1768
|
+
}
|
|
1731
1769
|
document.querySelectorAll('.whybtn').forEach(function(b){
|
|
1732
1770
|
b.addEventListener('click', function(){
|
|
1733
1771
|
var holder=document.getElementById('why-'+b.getAttribute('data-why'));
|
|
@@ -2,17 +2,26 @@
|
|
|
2
2
|
* Context-file harvest — the agent-facing and design-facing docs the repo
|
|
3
3
|
* already has. Step 5 (wire up their agent) needs to know what exists so it
|
|
4
4
|
* extends rather than clobbers; the diagnosis mentions what's missing.
|
|
5
|
+
*
|
|
6
|
+
* Two passes: the root checklist below (known doors at known paths), then a
|
|
7
|
+
* nested sweep for rules files living inside subfolders, because that is how
|
|
8
|
+
* monorepos actually do it (twenty carries 35 AGENTS.md files; a root-only
|
|
9
|
+
* look reported 1 and called it the whole story). Nested entries carry
|
|
10
|
+
* nested:true so the report can summarise them instead of flooding chips.
|
|
5
11
|
*/
|
|
6
|
-
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
12
|
+
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
|
|
7
13
|
import { join } from 'node:path';
|
|
8
14
|
|
|
9
15
|
const CONTEXT_FILES = [
|
|
10
16
|
{ file: 'CLAUDE.md', kind: 'agent-rules', tool: 'Claude Code' },
|
|
11
17
|
{ file: 'AGENTS.md', kind: 'agent-rules', tool: 'generic (AGENTS.md standard)' },
|
|
18
|
+
{ file: 'GEMINI.md', kind: 'agent-rules', tool: 'Gemini CLI' },
|
|
12
19
|
{ file: '.cursorrules', kind: 'agent-rules', tool: 'Cursor (legacy format)' },
|
|
13
20
|
{ file: '.cursor/rules', kind: 'agent-rules', tool: 'Cursor', dir: true },
|
|
14
21
|
{ file: '.github/copilot-instructions.md', kind: 'agent-rules', tool: 'GitHub Copilot' },
|
|
15
|
-
{ file: '.
|
|
22
|
+
{ file: '.github/instructions', kind: 'agent-rules', tool: 'GitHub Copilot (scoped)', dir: true },
|
|
23
|
+
{ file: '.windsurfrules', kind: 'agent-rules', tool: 'Windsurf (legacy format)' },
|
|
24
|
+
{ file: '.windsurf/rules', kind: 'agent-rules', tool: 'Windsurf', dir: true },
|
|
16
25
|
{ file: 'components.json', kind: 'design-config', tool: 'shadcn/ui' },
|
|
17
26
|
{ file: 'tailwind.config.js', kind: 'design-config', tool: 'Tailwind' },
|
|
18
27
|
{ file: 'tailwind.config.ts', kind: 'design-config', tool: 'Tailwind' },
|
|
@@ -21,6 +30,63 @@ const CONTEXT_FILES = [
|
|
|
21
30
|
{ file: 'STYLEGUIDE.md', kind: 'design-doc' },
|
|
22
31
|
];
|
|
23
32
|
|
|
33
|
+
// Nested sweep targets: the doors that tools officially read from
|
|
34
|
+
// subdirectories (closer file wins, per the AGENTS.md standard and Cursor's
|
|
35
|
+
// docs). Only these — a nested tailwind.config is build detail, not a door.
|
|
36
|
+
const NESTED_FILES = {
|
|
37
|
+
'CLAUDE.md': 'Claude Code',
|
|
38
|
+
'AGENTS.md': 'generic (AGENTS.md standard)',
|
|
39
|
+
'GEMINI.md': 'Gemini CLI',
|
|
40
|
+
};
|
|
41
|
+
const NESTED_DIRS = {
|
|
42
|
+
'.cursor': { child: 'rules', tool: 'Cursor' },
|
|
43
|
+
'.windsurf': { child: 'rules', tool: 'Windsurf' },
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const SKIP_DIRS = new Set([
|
|
47
|
+
'node_modules', '.next', '.git', 'dist', 'build', 'out', 'coverage',
|
|
48
|
+
'.turbo', '.cache', 'vendor', 'tmp',
|
|
49
|
+
]);
|
|
50
|
+
const MAX_DEPTH = 6;
|
|
51
|
+
const MAX_NESTED = 60; // entries recorded; the true count keeps counting
|
|
52
|
+
|
|
53
|
+
function sweepNested(root) {
|
|
54
|
+
const found = [];
|
|
55
|
+
let total = 0;
|
|
56
|
+
const walk = (dir, rel, depth) => {
|
|
57
|
+
if (depth > MAX_DEPTH) return;
|
|
58
|
+
let entries;
|
|
59
|
+
try { entries = readdirSync(dir, { withFileTypes: true }); } catch { return; }
|
|
60
|
+
for (const e of entries) {
|
|
61
|
+
const relPath = rel ? `${rel}/${e.name}` : e.name;
|
|
62
|
+
if (e.isDirectory()) {
|
|
63
|
+
if (SKIP_DIRS.has(e.name)) continue;
|
|
64
|
+
const nd = NESTED_DIRS[e.name];
|
|
65
|
+
if (nd) {
|
|
66
|
+
// rel is empty at the root, where the checklist above already owns
|
|
67
|
+
// these paths; the sweep only reports the nested ones.
|
|
68
|
+
if (rel && existsSync(join(dir, e.name, nd.child))) {
|
|
69
|
+
total++;
|
|
70
|
+
if (found.length < MAX_NESTED) found.push({ file: `${relPath}/${nd.child}`, kind: 'agent-rules', tool: nd.tool, nested: true, size: null, mentionsDesign: null });
|
|
71
|
+
}
|
|
72
|
+
continue; // never descend into tool dot-dirs
|
|
73
|
+
}
|
|
74
|
+
if (e.name.startsWith('.')) continue;
|
|
75
|
+
walk(join(dir, e.name), relPath, depth + 1);
|
|
76
|
+
} else if (depth > 0 && NESTED_FILES[e.name]) {
|
|
77
|
+
total++;
|
|
78
|
+
if (found.length < MAX_NESTED) {
|
|
79
|
+
let size = 0;
|
|
80
|
+
try { size = statSync(join(dir, e.name)).size; } catch { /* unreadable */ }
|
|
81
|
+
found.push({ file: relPath, kind: 'agent-rules', tool: NESTED_FILES[e.name], nested: true, size, mentionsDesign: null });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
walk(root, '', 0);
|
|
87
|
+
return { found, total };
|
|
88
|
+
}
|
|
89
|
+
|
|
24
90
|
export function harvestContext(root) {
|
|
25
91
|
const found = [];
|
|
26
92
|
for (const c of CONTEXT_FILES) {
|
|
@@ -38,5 +104,10 @@ export function harvestContext(root) {
|
|
|
38
104
|
} catch { /* unreadable */ }
|
|
39
105
|
found.push({ file: c.file, kind: c.kind, tool: c.tool ?? null, size, mentionsDesign });
|
|
40
106
|
}
|
|
107
|
+
const nested = sweepNested(root);
|
|
108
|
+
found.push(...nested.found);
|
|
109
|
+
if (nested.total > nested.found.length) {
|
|
110
|
+
found.push({ file: `(+${nested.total - nested.found.length} more nested rules files)`, kind: 'agent-rules', tool: null, nested: true, size: null, mentionsDesign: null });
|
|
111
|
+
}
|
|
41
112
|
return found;
|
|
42
113
|
}
|
|
@@ -30,7 +30,9 @@ const repoName = h.profile?.name ?? 'this repo';
|
|
|
30
30
|
if (!compact) {
|
|
31
31
|
lines.push(`<!-- Generated by roast-my-design-system from a scan of ${repoName} on ${scanDate}.`);
|
|
32
32
|
lines.push(' Paste into CLAUDE.md, .cursor/rules or AGENTS.md. Regenerate after big refactors:');
|
|
33
|
-
lines.push(' npx roast-my-design-system@latest --rules
|
|
33
|
+
lines.push(' npx roast-my-design-system@latest --rules');
|
|
34
|
+
lines.push(' Door note: Codex and Cursor read AGENTS.md; Claude Code reads CLAUDE.md only.');
|
|
35
|
+
lines.push(' If this lives in AGENTS.md, add the line "@AGENTS.md" to CLAUDE.md so Claude sees it too. -->');
|
|
34
36
|
lines.push('');
|
|
35
37
|
}
|
|
36
38
|
// Mirror of the report's "no design system" banner: with almost no colour or
|