roast-my-design-system 5.6.0 → 5.6.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
CHANGED
|
@@ -40,7 +40,7 @@ One scan powers all of it; the flags decide what lands on disk. Combine freely.
|
|
|
40
40
|
| `... --rules` | The same rules written to `design-system-rules.md` instead, for pasting by hand |
|
|
41
41
|
| `... --card` | `roast-card.svg`: a shareable 1200x630 card with the score and worst findings. Pure SVG, embeds in a README |
|
|
42
42
|
| `... --sarif` | `design-system-roast.sarif` for GitHub code scanning: upload it in CI and findings appear in the Security tab, annotated on files |
|
|
43
|
-
| `... --mcp` | The scan as a local MCP server: five tools your agent calls while writing UI, from "is there a Button already?" to "review my changes". See [Live answers over MCP](#live-answers-over-mcp) |
|
|
43
|
+
| `... --mcp` | The scan as a local MCP server: five tools your agent calls while writing UI, from "is there a Button already?" to "review my changes", plus the `roast-fix` prompt that serves the top fix from a fresh scan. See [Live answers over MCP](#live-answers-over-mcp) |
|
|
44
44
|
| `... --check` | The working tree's changed files checked against the design system, in the terminal. Exits 1 on findings, so it slots into scripts |
|
|
45
45
|
| <code>... --by "Dwayne Hicks"</code> | A requester credit in the report header, next to the scan date |
|
|
46
46
|
| <code>... --notes <file.md></code> | An agent-written analysis embedded in the report as **"What the numbers mean"**: labelled as written by AI, kept apart from the measured numbers. The Claude Code skill writes and passes this automatically; the flag is here so any agent can |
|
|
@@ -61,7 +61,7 @@ One scan powers all of it; the flags decide what lands on disk. Combine freely.
|
|
|
61
61
|
|
|
62
62
|
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:
|
|
63
63
|
|
|
64
|
-

|
|
65
65
|
|
|
66
66
|
The same report in light mode (one file, built-in toggle):
|
|
67
67
|
|
|
@@ -110,6 +110,10 @@ The report and the rules file describe the repo as it was at scan time. `--mcp`
|
|
|
110
110
|
|
|
111
111
|
The loop: context before building, find while building, validate before saving, review before finishing.
|
|
112
112
|
|
|
113
|
+
And when the goal is fixing the system rather than building on it, the `roast-fix` prompt serves the top Where-to-start move from a fresh scan, as a ready-made fix prompt (byte-identical to the report's copy buttons). Fix it, ask again, and the next move has risen to the top: the scan is the progress bar. Pass `move: 2` to jump the queue.
|
|
114
|
+
|
|
115
|
+
To use it in Claude Code, type `/mcp__roast__roast-fix` in the chat (MCP prompts appear as slash commands, named after whatever you registered the server as; the `/` autocomplete menu lists them too). Add the move number to jump the queue: `/mcp__roast__roast-fix 2`. Other clients list server prompts in their own prompt picker; wherever `roast-build-ui` and `roast-review-ui` show up, `roast-fix` sits beside them.
|
|
116
|
+
|
|
113
117
|
Add it to Claude Code:
|
|
114
118
|
|
|
115
119
|
```bash
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "roast-my-design-system",
|
|
3
|
-
"version": "5.6.
|
|
3
|
+
"version": "5.6.2",
|
|
4
4
|
"mcpName": "io.github.gregkozakiewicz/roast-my-design-system",
|
|
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, writes rules for Claude, Cursor, Copilot and Windsurf with --apply, and runs as a local MCP server with --mcp.",
|
|
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, 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": [
|
|
7
7
|
"design-system",
|
|
8
8
|
"mcp",
|
|
@@ -890,6 +890,12 @@ function extraSectionsHtml() {
|
|
|
890
890
|
</section>`).join('\n\n');
|
|
891
891
|
}
|
|
892
892
|
|
|
893
|
+
// Filled by whereToStartSection when the template renders (which happens
|
|
894
|
+
// before the summary is written): the same moves and prompts the report's
|
|
895
|
+
// buttons hold, published in --summary so the MCP server's roast-fix prompt
|
|
896
|
+
// hands out byte-identical text. One composer, two doors.
|
|
897
|
+
let startMoves = [];
|
|
898
|
+
|
|
893
899
|
function whereToStartSection() {
|
|
894
900
|
const c = [];
|
|
895
901
|
if (agentFiles.length === 0) c.push({ score: 60, metric: null, title: 'Write the agent rules file',
|
|
@@ -1019,6 +1025,10 @@ function whereToStartSection() {
|
|
|
1019
1025
|
deltaText: item.delta > 0 ? `about +${item.delta} points` : (item.target ? `about +${item.target.gain} points once ${item.target.target === 0 ? 'they are all cleared' : `the count is under ${n(item.target.target)}`}` : ''),
|
|
1020
1026
|
repoName: h.repo ? String(h.repo).split('/').pop() : '',
|
|
1021
1027
|
});
|
|
1028
|
+
startMoves = top3.map((item) => ({
|
|
1029
|
+
title: unesc(item.title), sub: unesc(item.sub),
|
|
1030
|
+
delta: item.delta || 0, prompt: promptFor(item),
|
|
1031
|
+
}));
|
|
1022
1032
|
return `<section class="glass pad">
|
|
1023
1033
|
${sectionHead('Where to start', head)}
|
|
1024
1034
|
<div class="ledger">${top3.map((item, i) => `
|
|
@@ -1636,6 +1646,7 @@ if (summaryPath) {
|
|
|
1636
1646
|
noSystemLikely,
|
|
1637
1647
|
verdict,
|
|
1638
1648
|
tiles: bigStats.map((s) => ({ label: s.label, value: s.num, health: s.health })),
|
|
1649
|
+
...(startMoves.length ? { moves: startMoves } : {}),
|
|
1639
1650
|
packages: (h.packages ?? []).filter((p) => p.scored && p.metrics)
|
|
1640
1651
|
.map((p) => ({ dir: p.dir, name: p.name, ...(scorePackage(p.metrics) ?? {}) }))
|
|
1641
1652
|
.filter((p) => p.score !== undefined)
|
|
@@ -13,7 +13,11 @@
|
|
|
13
13
|
* node src/mcp/server.mjs <repo-path>
|
|
14
14
|
*/
|
|
15
15
|
import { createInterface } from 'node:readline';
|
|
16
|
-
import { resolve } from 'node:path';
|
|
16
|
+
import { resolve, join, dirname } from 'node:path';
|
|
17
|
+
import { readFileSync, mkdtempSync, rmSync } from 'node:fs';
|
|
18
|
+
import { tmpdir } from 'node:os';
|
|
19
|
+
import { spawnSync } from 'node:child_process';
|
|
20
|
+
import { fileURLToPath } from 'node:url';
|
|
17
21
|
import { loadKnowledge, freshKnowledge } from './knowledge.mjs';
|
|
18
22
|
import { getContext, findComponent, findToken, validate, review } from './tools.mjs';
|
|
19
23
|
import { rulesMarkdown } from '../rules/build.mjs';
|
|
@@ -69,8 +73,43 @@ const PROMPTS = [
|
|
|
69
73
|
description: 'Review the current UI changes against the design system',
|
|
70
74
|
text: 'Call roast_review to check the working tree\'s changed files against this repository\'s design system. For each finding, apply the named fix (use roast_find_component and roast_find_token to find the canonical replacement). Rerun roast_review until it reports no measured violations, then summarise what changed.',
|
|
71
75
|
},
|
|
76
|
+
{
|
|
77
|
+
name: 'roast-fix',
|
|
78
|
+
description: 'Fix the top Where-to-start move from a fresh scan; call again for the next one',
|
|
79
|
+
dynamic: true,
|
|
80
|
+
arguments: [{ name: 'move', description: 'Which move to fix (1-3); default is the top of the current list', required: false }],
|
|
81
|
+
},
|
|
72
82
|
];
|
|
73
83
|
|
|
84
|
+
// roast-fix runs the real report pipeline (harvest, then diagnose --summary)
|
|
85
|
+
// so the prompt is byte-identical to what the report's copy buttons hold: one
|
|
86
|
+
// composer, two doors. Fresh scan every call, deliberately: fix the top move,
|
|
87
|
+
// ask again, and the next move has risen to the top. The two temp files match
|
|
88
|
+
// the promise the README already makes (a temp JSON and the report).
|
|
89
|
+
function fixMovePrompt(root, moveArg) {
|
|
90
|
+
const dir = mkdtempSync(join(tmpdir(), 'roast-fix-'));
|
|
91
|
+
try {
|
|
92
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
93
|
+
const hPath = join(dir, 'h.json'), sPath = join(dir, 's.json');
|
|
94
|
+
const run = (script, args) => {
|
|
95
|
+
const r = spawnSync(process.execPath, [join(here, script), ...args], { encoding: 'utf8', timeout: 120000 });
|
|
96
|
+
if (r.status !== 0) throw new Error(`${script} exited ${r.status}`);
|
|
97
|
+
};
|
|
98
|
+
run('../harvest/index.mjs', [root, '--out', hPath]);
|
|
99
|
+
run('../diagnose/index.mjs', [hPath, '--out', join(dir, 'report.html'), '--summary', sPath]);
|
|
100
|
+
const summary = JSON.parse(readFileSync(sPath, 'utf8'));
|
|
101
|
+
const moves = summary.moves ?? [];
|
|
102
|
+
if (!moves.length) {
|
|
103
|
+
return `A fresh scan of this repository found no Where-to-start moves: nothing at the top level is worth a fix prompt right now${summary.score !== null && summary.score !== undefined ? ` (score ${summary.score}/100)` : ''}. Run roast_review on your changed files if you want the working tree checked instead.`;
|
|
104
|
+
}
|
|
105
|
+
const idx = Math.min(Math.max(parseInt(moveArg, 10) || 1, 1), moves.length) - 1;
|
|
106
|
+
const extra = moves.length > 1 ? `\n\n(${moves.length} moves on the current list; this is number ${idx + 1}. Ask for roast-fix again after fixing: the scan refreshes and the next move rises to the top.)` : '';
|
|
107
|
+
return moves[idx].prompt + extra;
|
|
108
|
+
} finally {
|
|
109
|
+
rmSync(dir, { recursive: true, force: true });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
74
113
|
// ---------- resource bodies ----------
|
|
75
114
|
function resourceBody(uri, k) {
|
|
76
115
|
if (uri === 'roast://rules') {
|
|
@@ -173,12 +212,13 @@ export function serve(root) {
|
|
|
173
212
|
return;
|
|
174
213
|
}
|
|
175
214
|
case 'prompts/list':
|
|
176
|
-
reply(id, { prompts: PROMPTS.map(({ name, description }) => ({ name, description })) });
|
|
215
|
+
reply(id, { prompts: PROMPTS.map(({ name, description, arguments: args }) => ({ name, description, ...(args ? { arguments: args } : {}) })) });
|
|
177
216
|
return;
|
|
178
217
|
case 'prompts/get': {
|
|
179
218
|
const p = PROMPTS.find((x) => x.name === params?.name);
|
|
180
219
|
if (!p) { fail(id, -32602, `Unknown prompt: ${params?.name}`); return; }
|
|
181
|
-
|
|
220
|
+
const text = p.dynamic ? fixMovePrompt(root, params?.arguments?.move) : p.text;
|
|
221
|
+
reply(id, { description: p.description, messages: [{ role: 'user', content: { type: 'text', text } }] });
|
|
182
222
|
return;
|
|
183
223
|
}
|
|
184
224
|
default:
|