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>...&nbsp;--by&nbsp;"Dwayne&nbsp;Hicks"</code> | A requester credit in the report header, next to the scan date |
46
46
  | <code>...&nbsp;--notes&nbsp;&lt;file.md&gt;</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
- ![The full diagnosis report for vercel/ai-chatbot in dark mode: health score, the What the numbers mean analysis written by Claude, priced Where to start moves, the wrapped present with the agent rules, an agent trap callout, three-yardstick tiles, the adoption map treemap, palette forensics, spacing receipts, typography specimens, offenders, duplicates, and the component usage ledger](https://raw.githubusercontent.com/gregkozakiewicz/roast-my-design-system/main/assets/report-full-dark.png?v=5.5.1)
64
+ ![The full diagnosis report for vercel/ai-chatbot in dark mode: health score, the What the numbers mean analysis written by Claude, priced Where to start moves each with its copy-the-fix-prompt button, the wrapped present with the agent rules, an agent trap callout, three-yardstick tiles, the adoption map treemap, palette forensics, spacing receipts, typography specimens, offenders, duplicates, and the component usage ledger](https://raw.githubusercontent.com/gregkozakiewicz/roast-my-design-system/main/assets/report-full-dark.png?v=5.6.1)
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.0",
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)
@@ -1,4 +1,4 @@
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 = '5.6.0';
4
+ export const VERSION = '5.6.2';
@@ -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
- reply(id, { description: p.description, messages: [{ role: 'user', content: { type: 'text', text: p.text } }] });
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: