roast-my-design-system 8.1.0 → 8.2.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 +77 -2
- package/package.json +1 -1
- package/skills/roast-my-design-system/scripts/harvest/tokens.mjs +32 -3
- package/skills/roast-my-design-system/scripts/lib/color.mjs +6 -1
- package/skills/roast-my-design-system/scripts/lib/kitpaint.mjs +7 -2
- 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/skills/roast-my-design-system/scripts/mcp/server.mjs +12 -0
- package/skills/roast-my-design-system/scripts/mcp/tools.mjs +2 -2
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<img src="https://raw.githubusercontent.com/gregkozakiewicz/roast-my-design-system/main/assets/
|
|
1
|
+
<img src="https://raw.githubusercontent.com/gregkozakiewicz/roast-my-design-system/main/assets/roster_logo_v1.png?v=8.2.0" width="100" alt="roast-my-design-system">
|
|
2
2
|
|
|
3
3
|
# roast-my-design-system
|
|
4
4
|
|
|
@@ -149,6 +149,51 @@ The loop: context before building, find while building, validate before saving,
|
|
|
149
149
|
|
|
150
150
|
The server reads the repo the way the report does. On a product built on MUI, Mantine, Chakra UI or Ant Design, the context names the theme file and the kit's own way of reading it, `roast_find_token` answers in spacing steps (`12px` is `p: 3` on a 4px MUI theme), and `roast_validate` and `roast_review` flag a colour or a pixel size written onto a kit component where the theme has a value. On a Tailwind theme they flag a palette class such as `text-gray-500` where the theme names a colour of that kind.
|
|
151
151
|
|
|
152
|
+
### What a session looks like
|
|
153
|
+
|
|
154
|
+
A real exchange against [Unleash](https://github.com/Unleash/unleash), an MUI product that scores 64/100. The agent was asked to add a small usage hint to a feature page. Every answer is the server's own text, unedited.
|
|
155
|
+
|
|
156
|
+
Before writing anything, the agent calls `roast_get_context`:
|
|
157
|
+
|
|
158
|
+
```
|
|
159
|
+
KIT: MUI (1109 files import it, the theme is read 6162x). A colour, a spacing step or a radius is decided in frontend/src/themes/dark-theme.ts. On a component, read it: sx paths (color: 'text.secondary', p: 2) or theme.palette / theme.spacing() in styled().
|
|
160
|
+
43 colours are already written onto components (#6c65e5, #f1f0fc, #302e42); do not add one.
|
|
161
|
+
USE THESE, DO NOT REBUILD THEM:
|
|
162
|
+
<Dialogue> from frontend/src/component/common/Dialogue/Dialogue.tsx (97x)
|
|
163
|
+
<TextCell> from frontend/src/component/common/Table/cells/TextCell/TextCell.tsx (96x)
|
|
164
|
+
<PageContent> from frontend/src/component/common/PageContent/PageContent.tsx (84x)
|
|
165
|
+
DUPLICATES, DO NOT MAKE IT WORSE:
|
|
166
|
+
<StyledSuccessIcon> exists in 6 places; match what the surrounding code imports, never create another.
|
|
167
|
+
SPACING: Use spacing steps (p: 2), not pixels. 17 pixel sizes are already written onto components; do not add one.
|
|
168
|
+
TYPE: Sen. No new typefaces, no hand-declared font stacks.
|
|
169
|
+
Before finishing: call roast_validate on what you wrote, then roast_review.
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
It has a grey in hand and a padding in mind, so `roast_find_token` twice:
|
|
173
|
+
|
|
174
|
+
```
|
|
175
|
+
#6b7280 → Nearest token: #607d8b, 11 channel steps from #6b7280. Unless the difference is a deliberate decision, use the token.
|
|
176
|
+
12px → 12px is between steps 1 (8px) and 2 (16px) on the default theme. If the design needs 12px exactly, keep it with a comment; otherwise use the nearest step in sx.
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
It writes a first draft anyway and runs `roast_validate` before saving:
|
|
180
|
+
|
|
181
|
+
```
|
|
182
|
+
2 findings:
|
|
183
|
+
✕ L5 Colour #6b7280 written onto an MUI component, and the theme has no such colour.
|
|
184
|
+
Fix: Add it to the theme once (frontend/src/themes/dark-theme.ts), then read it there. On an MUI component, point at it: color: 'text.secondary' in sx, or theme.palette.primary.main in a styled() call.
|
|
185
|
+
✕ L5 Pixel size p: 12px on an MUI component.
|
|
186
|
+
Fix: 12px is between steps 1 (8px) and 2 (16px) on the default theme. If the design needs 12px exactly, keep it with a comment; otherwise use the nearest step in sx.
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Second draft: `color: 'text.secondary'`, `p: 1.5`, the radius read from the theme. `roast_validate` again:
|
|
190
|
+
|
|
191
|
+
```
|
|
192
|
+
No measured violations found. Checked: hardcoded colours vs the token set, near-identical colour twins, off-scale spacing, off-scale radii, font sizes and shadows, typefaces outside the system, arbitrary bracket values, static inline style blocks, !important, duplicate component definitions, colours and pixel sizes written onto kit components where the theme has a value.
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Four calls, under 800 tokens in total, and the new component reads the theme instead of adding colour number 44. `roast_review` then checks the whole diff the same way before the agent says it is done.
|
|
196
|
+
|
|
152
197
|
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. It is 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.
|
|
153
198
|
|
|
154
199
|
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, and 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.
|
|
@@ -250,6 +295,36 @@ You get the roast in chat plus `design-system-roast.html` at your repo root: a s
|
|
|
250
295
|
|
|
251
296
|
After the roast, the skill also offers to write `design-system-rules.md` to disk and merge it into your CLAUDE.md, `.cursor/rules` or AGENTS.md.
|
|
252
297
|
|
|
298
|
+
### Three prompts to try
|
|
299
|
+
|
|
300
|
+
Type any of these in Claude Code, inside the repo:
|
|
301
|
+
|
|
302
|
+
```
|
|
303
|
+
Roast my design system.
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
```
|
|
307
|
+
How bad is my CSS? Scan this repo and show me the receipts.
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
With the MCP server connected (see [Live answers over MCP](#live-answers-over-mcp)):
|
|
311
|
+
|
|
312
|
+
```
|
|
313
|
+
Is there already a Button component in this repo, and which one should I use?
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
## Troubleshooting
|
|
317
|
+
|
|
318
|
+
- **"Command not found" or the plugin will not install.** Update Claude Code; the plugin marketplace needs a recent version. The manual install above works on any version.
|
|
319
|
+
- **"Nothing to roast" or a near-empty report.** The scan found almost no colours or spacing. The styling probably lives in another repo, a CDN or an installed package. Run it from the repo that holds the styles.
|
|
320
|
+
- **A score that looks wrong.** Check the header of the report: it names how the repo was read (product, library, shadcn, a kit, a Tailwind theme) and every folder that was left out. Scope the scan with a `.roastignore` file or `--exclude` if a playground or an old app is blurring the numbers.
|
|
321
|
+
- **The report did not open.** It is written to `design-system-roast.html` at the repo root. Open it in any browser; it needs no server and makes no requests.
|
|
322
|
+
- **The MCP server does not appear.** Restart the client after adding it. In Claude Code, `claude mcp list` shows whether it connected. Everything it needs is Node 18 or newer.
|
|
323
|
+
|
|
324
|
+
## Support
|
|
325
|
+
|
|
326
|
+
Bugs and questions go to [GitHub Issues](https://github.com/gregkozakiewicz/roast-my-design-system/issues). Everything else reaches Greg through [gregkozakiewicz.com](https://gregkozakiewicz.com).
|
|
327
|
+
|
|
253
328
|
## Live examples
|
|
254
329
|
|
|
255
330
|
7 real roasts of public repos, hosted as-is (the same self-contained HTML the skill generates), spanning React, Stencil and Lit:
|
|
@@ -288,7 +363,7 @@ Yes, the median repo is already a mess. That's the point.
|
|
|
288
363
|
|
|
289
364
|
MIT. The code is yours to fork, modify and redistribute; the copyright notice travels with it.
|
|
290
365
|
|
|
291
|
-
Building your own report, summary or audit from this tool's scores, counts or benchmark comparisons? Keep one line in it: *Built with [roast-my-design-system](https://github.com/gregkozakiewicz/roast-my-design-system) by Greg Kozakiewicz*. The
|
|
366
|
+
Building your own report, summary or audit from this tool's scores, counts or benchmark comparisons? Keep one line in it: *Built with [roast-my-design-system](https://github.com/gregkozakiewicz/roast-my-design-system) by Greg Kozakiewicz*. The skill asks the same of an AI agent that writes such a document from the scan.
|
|
292
367
|
|
|
293
368
|
**roast-my-design-system**™ and the GK mark are trademarks of Greg Kozakiewicz. Forking is welcome, republishing under this name is not: see [brand and attribution](https://gregkozakiewicz.github.io/roast-my-design-system/brand.html).
|
|
294
369
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "roast-my-design-system",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.2.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 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": [
|
|
@@ -51,9 +51,18 @@ const FUNC_COLOR_RE = /\b(?:rgba?|hsla?|oklch|oklab|lab|lch)\(\s*[^)]{1,80}\)|\b
|
|
|
51
51
|
// scanned as 0 tokens until 5.10.0. Recognised here and normalised to hsl()
|
|
52
52
|
// so it flows through greys, twins and the rest like any other colour.
|
|
53
53
|
const HSL_TRIPLET_RE = /^\s*(-?\d+(?:\.\d+)?)(?:deg)?\s+(\d+(?:\.\d+)?)%\s+(\d+(?:\.\d+)?)%(?:\s*\/\s*(\d+(?:\.\d+)?%?))?\s*$/;
|
|
54
|
+
// The same convention with RGB channels: --bg-default: 255 255 255, wrapped
|
|
55
|
+
// later as rgb(var(--bg-default) / <alpha-value>). dub keeps its whole theme
|
|
56
|
+
// this way, and until 8.2.0 it scanned as a repo without a token file, so an
|
|
57
|
+
// avatar colour list took the title (2026-09-18). Three integers 0-255, and
|
|
58
|
+
// nothing else, so a bare "16px" or "1 2" never reads as a colour.
|
|
59
|
+
const RGB_TRIPLET_RE = /^\s*(\d{1,3})\s+(\d{1,3})\s+(\d{1,3})(?:\s*\/\s*(\d+(?:\.\d+)?%?))?\s*$/;
|
|
54
60
|
export const tripletToHsl = (v) => {
|
|
55
61
|
const m = HSL_TRIPLET_RE.exec(v);
|
|
56
|
-
|
|
62
|
+
if (m) return `hsl(${m[1]} ${m[2]}% ${m[3]}%${m[4] ? ` / ${m[4]}` : ''})`;
|
|
63
|
+
const r = RGB_TRIPLET_RE.exec(v);
|
|
64
|
+
if (r && [r[1], r[2], r[3]].every((n) => Number(n) <= 255)) return `rgb(${r[1]} ${r[2]} ${r[3]}${r[4] ? ` / ${r[4]}` : ''})`;
|
|
65
|
+
return null;
|
|
57
66
|
};
|
|
58
67
|
// Loose hex in CODE (not in a style block, a class string or a stylesheet) is
|
|
59
68
|
// ambiguous: #RGBA shorthand is legal CSS but vanishingly rare in code, while
|
|
@@ -284,6 +293,16 @@ export function extractStyling(src, { css = false } = {}) {
|
|
|
284
293
|
* Returns { colors, greys, spacing, radii, fontSizes, fontFamilies, shadows,
|
|
285
294
|
* tailwind: {...same buckets from tw classes...}, inlineStyleCount }
|
|
286
295
|
*/
|
|
296
|
+
/** Share of a code file's quoted colours that sit inside an array holding 8 or more of them. */
|
|
297
|
+
export function colourListShare(src) {
|
|
298
|
+
const QUOTED = /(['"`])(?:#(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})|(?:rgba?|hsla?)\([^)'"`]*\))\1/g;
|
|
299
|
+
const all = (src.match(QUOTED) ?? []).length;
|
|
300
|
+
if (!all) return 0;
|
|
301
|
+
let inLists = 0;
|
|
302
|
+
for (const a of src.match(/\[[^[\]]*\]/g) ?? []) { const n = (a.match(QUOTED) ?? []).length; if (n >= 8) inLists += n; }
|
|
303
|
+
return inLists / all;
|
|
304
|
+
}
|
|
305
|
+
|
|
287
306
|
export function harvestTokens(root, styleFiles, codeFiles) {
|
|
288
307
|
const colors = new Tally(), spacing = new Tally(), radii = new Tally(),
|
|
289
308
|
fontSizes = new Tally(), fontFamilies = new Tally(), shadows = new Tally();
|
|
@@ -349,6 +368,7 @@ export function harvestTokens(root, styleFiles, codeFiles) {
|
|
|
349
368
|
return owner;
|
|
350
369
|
};
|
|
351
370
|
const paletteFiles = new Set();
|
|
371
|
+
const listShare = new Map(); // code file → share of its colour literals inside 8+ colour arrays
|
|
352
372
|
const nsDefs = new Map(); // --telekom-x: value → 'telekom' (definitions)
|
|
353
373
|
const nsRefs = new Map(); // var(--telekom-x) → 'telekom' (references)
|
|
354
374
|
|
|
@@ -468,6 +488,7 @@ export function harvestTokens(root, styleFiles, codeFiles) {
|
|
|
468
488
|
let src = readSource(join(root, f));
|
|
469
489
|
if (src === null) continue;
|
|
470
490
|
for (const m of src.matchAll(/[A-Za-z_][\w-]+/g)) ownClasses.add(m[0]);
|
|
491
|
+
listShare.set(f, colourListShare(src));
|
|
471
492
|
|
|
472
493
|
// CSS-in-tagged-templates (Lit css``, styled-components css``) IS the
|
|
473
494
|
// stylesheet in those worlds: Shoelace keeps its entire component styling
|
|
@@ -639,11 +660,19 @@ export function harvestTokens(root, styleFiles, codeFiles) {
|
|
|
639
660
|
else straysIn.set(f, (straysIn.get(f) ?? 0) + 1);
|
|
640
661
|
}
|
|
641
662
|
}
|
|
663
|
+
// A code palette whose colours sit mostly inside arrays is a list handed to
|
|
664
|
+
// something (dub's avatar pairs, plane's chart series), not the vocabulary
|
|
665
|
+
// an agent should add a colour to. It keeps its palette status for the
|
|
666
|
+
// counts, but it does not take the token-file title while any other
|
|
667
|
+
// candidate qualifies. Probe, 72 repos with a code token file (2026-09-18):
|
|
668
|
+
// 10 switch, 4 have nothing else and keep it. A Tailwind config or a
|
|
669
|
+
// stylesheet is never a list, whatever shape its values take.
|
|
670
|
+
const isList = (f) => /\.[jt]sx?$/.test(f) && !/(^|\/)tailwind\.config\.[mc]?[jt]s$/.test(f) && (listShare.get(f) ?? 0) >= 0.5;
|
|
642
671
|
const candidates = new Set([...tokenColorDefsPerFile.keys(), ...paletteFiles]);
|
|
643
672
|
const tokenFile = [...candidates]
|
|
644
|
-
.map((f) => ({ f, tokens: tokenColoursIn.get(f) ?? 0, strays: straysIn.get(f) ?? 0 }))
|
|
673
|
+
.map((f) => ({ f, tokens: tokenColoursIn.get(f) ?? 0, strays: straysIn.get(f) ?? 0, list: isList(f) ? 1 : 0 }))
|
|
645
674
|
.filter((c) => c.tokens >= 3)
|
|
646
|
-
.sort((a, b) => b.tokens - a.tokens || a.strays - b.strays)[0]?.f
|
|
675
|
+
.sort((a, b) => a.list - b.list || b.tokens - a.tokens || a.strays - b.strays)[0]?.f
|
|
647
676
|
?? [...tokenDefsPerFile.entries()].sort((a, b) => b[1] - a[1])[0]?.[0] ?? null;
|
|
648
677
|
|
|
649
678
|
// Token namespace: the brand stem the repo's custom properties answer to
|
|
@@ -184,10 +184,15 @@ export function canonical(raw) {
|
|
|
184
184
|
const v = String(raw).trim();
|
|
185
185
|
if (!v || /var\(|\$\{|calc\(/.test(v)) return null;
|
|
186
186
|
const t = HSL_TRIPLET.exec(v);
|
|
187
|
-
const
|
|
187
|
+
const r = t ? null : RGB_TRIPLET.exec(v);
|
|
188
|
+
const c = parseColor(t ? `hsl(${t[1]} ${t[2]}% ${t[3]}%)`
|
|
189
|
+
: r && [r[1], r[2], r[3]].every((n) => Number(n) <= 255) ? `rgb(${r[1]} ${r[2]} ${r[3]})`
|
|
190
|
+
: v.replace(/\s+/g, ' ').toLowerCase());
|
|
188
191
|
if (!c) return null;
|
|
189
192
|
return [c.r, c.g, c.b].map((x) => Math.round(x)).join(',') + ',' + Math.round(c.a * 100);
|
|
190
193
|
}
|
|
191
194
|
|
|
192
195
|
const HSL_TRIPLET =
|
|
193
196
|
/^\s*(-?\d+(?:\.\d+)?)(?:deg)?\s+(\d+(?:\.\d+)?)%\s+(\d+(?:\.\d+)?)%(?:\s*\/\s*(\d+(?:\.\d+)?%?))?\s*$/;
|
|
197
|
+
// bare RGB channels (dub: --bg-default: 255 255 255), see harvest/tokens.mjs
|
|
198
|
+
const RGB_TRIPLET = /^\s*(\d{1,3})\s+(\d{1,3})\s+(\d{1,3})(?:\s*\/\s*\d+(?:\.\d+)?%?)?\s*$/;
|
|
@@ -206,8 +206,13 @@ export function countKitPaint(root, codeFiles, { importRe: kitImportRe, themeRe,
|
|
|
206
206
|
top: b.top.sort((a, c) => c.count - a.count).slice(0, 10),
|
|
207
207
|
samples: [...b.samples.entries()].sort((a, c) => c[1] - a[1]).slice(0, 12).map(([value, count]) => ({ value, count })),
|
|
208
208
|
});
|
|
209
|
-
// the theme a reader should open first: a
|
|
210
|
-
|
|
209
|
+
// the theme a reader should open first: not a component's own theme or a
|
|
210
|
+
// provider (Open-Assistant's Theme/components/Badge.ts and Metabase's
|
|
211
|
+
// ThemeProvider.tsx both outranked the root theme, 2026-09-18), then a
|
|
212
|
+
// theme-named path, then the most colours
|
|
213
|
+
const componentLevel = (f) => /(^|\/)(components?|providers?)\//i.test(f);
|
|
214
|
+
const ranked = themeFiles.sort((a, b) => (componentLevel(a.f) - componentLevel(b.f))
|
|
215
|
+
|| (THEME_NAME_RE.test(b.f) - THEME_NAME_RE.test(a.f)) || b.n - a.n).map((t) => t.f);
|
|
211
216
|
const colourOut = finish(colour);
|
|
212
217
|
// a written colour the theme already holds: the move can say where it lives
|
|
213
218
|
for (const s of colourOut.samples) if (themeValues.has(s.value)) s.inTheme = true;
|
|
@@ -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.
|
|
4
|
+
export const VERSION = '8.2.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.
|
|
@@ -60,10 +60,13 @@ export function loadKnowledge(root) {
|
|
|
60
60
|
const themeSet = new Set(kit.themeValues ?? []);
|
|
61
61
|
tokens = {
|
|
62
62
|
...tokens,
|
|
63
|
-
|
|
63
|
+
// the theme first: on a kit repo it is where a colour is decided, whatever
|
|
64
|
+
// stylesheet or palette file holds the most literals
|
|
65
|
+
tokenFile: kit.themeFiles[0] ?? tokens.tokenFile ?? null,
|
|
64
66
|
colors: tokens.colors.map((c) => (themeSet.has(c.value) ? { ...c, isToken: true } : c)),
|
|
65
67
|
};
|
|
66
|
-
|
|
68
|
+
// same shape as a harvested colour, so every reader of `files` keeps working
|
|
69
|
+
for (const v of themeSet) if (!tokens.colors.some((c) => c.value === v)) tokens.colors.push({ value: v, count: 1, files: [{ file: kit.themeFiles[0] ?? '', count: 1 }], isToken: true });
|
|
67
70
|
}
|
|
68
71
|
const duplicates = findDuplicates(components, profile.uiDir, root);
|
|
69
72
|
const context = harvestContext(root);
|
|
@@ -28,29 +28,41 @@ const PROTOCOL = '2025-06-18';
|
|
|
28
28
|
// ---------- tool + resource + prompt catalogue ----------
|
|
29
29
|
// Descriptions are budgeted: every client loads them into every session, so
|
|
30
30
|
// each one carries only what changes an agent's tool choice (5.0.1 trim).
|
|
31
|
+
// Every tool is read-only and local: the annotations say so, so a client can
|
|
32
|
+
// run them without a per-call prompt (directory policy, 2026-09-18).
|
|
31
33
|
const TOOLS = [
|
|
32
34
|
{
|
|
33
35
|
name: 'roast_get_context',
|
|
36
|
+
title: 'Design system context',
|
|
37
|
+
annotations: { title: 'Design system context', readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
34
38
|
description: 'Design-system context before writing UI in this repo: tokens or the kit theme (MUI, Mantine, Chakra, Ant Design, shadcn, a Tailwind theme), canonical components, duplicates, spacing and type rules, from a real scan. Optional path ("packages/ui") narrows the slice.',
|
|
35
39
|
inputSchema: { type: 'object', properties: { path: { type: 'string', description: 'Repo-relative folder (optional)' } } },
|
|
36
40
|
},
|
|
37
41
|
{
|
|
38
42
|
name: 'roast_find_component',
|
|
43
|
+
title: 'Find the canonical component',
|
|
44
|
+
annotations: { title: 'Find the canonical component', readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
39
45
|
description: 'Find the canonical component for a name or intent ("icon button"). Returns import path, usage count and a real usage example, or an honest zero. Ties are reported, never guessed.',
|
|
40
46
|
inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Component name or intent' } }, required: ['query'] },
|
|
41
47
|
},
|
|
42
48
|
{
|
|
43
49
|
name: 'roast_find_token',
|
|
50
|
+
title: 'Snap a value to a token',
|
|
51
|
+
annotations: { title: 'Snap a value to a token', readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
44
52
|
description: 'Snap a raw value (#111111, 13px) to this repo\'s nearest token, theme colour or spacing step, in the kit\'s own terms on a kit repo. Says so when no scale exists.',
|
|
45
53
|
inputSchema: { type: 'object', properties: { value: { type: 'string', description: 'Colour or length value' } }, required: ['value'] },
|
|
46
54
|
},
|
|
47
55
|
{
|
|
48
56
|
name: 'roast_validate',
|
|
57
|
+
title: 'Validate code before saving',
|
|
58
|
+
annotations: { title: 'Validate code before saving', readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
49
59
|
description: 'Check code before saving: hardcoded colours, near-token twins, off-scale spacing, arbitrary brackets, inline styles, !important, duplicate components; on a kit repo, colours and pixel sizes written onto kit components where the theme has a value; on a Tailwind theme, palette classes. Findings name the fix.',
|
|
50
60
|
inputSchema: { type: 'object', properties: { code: { type: 'string', description: 'The code to check' }, file: { type: 'string', description: 'Intended file path (optional)' } }, required: ['code'] },
|
|
51
61
|
},
|
|
52
62
|
{
|
|
53
63
|
name: 'roast_review',
|
|
64
|
+
title: 'Review changed files',
|
|
65
|
+
annotations: { title: 'Review changed files', readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
54
66
|
description: 'Review the working tree\'s changed files (git diff + untracked) against the design system. Reads the diff itself; send no code. Call before finishing UI work.',
|
|
55
67
|
inputSchema: { type: 'object', properties: {} },
|
|
56
68
|
},
|
|
@@ -96,8 +96,8 @@ export function getContext(k, { path = null } = {}) {
|
|
|
96
96
|
text = assemble(nCanon, nDupes) + shortened();
|
|
97
97
|
}
|
|
98
98
|
if (text.length > CONTEXT_BUDGET) text = `${text.slice(0, text.lastIndexOf('\n', CONTEXT_BUDGET))}\n(trimmed to budget; ask roast_find_component / roast_find_token for specifics)`;
|
|
99
|
-
//
|
|
100
|
-
|
|
99
|
+
// No credit line here (8.1.1): a tool answers the question and nothing
|
|
100
|
+
// else. Attribution lives on the report and in the skill's citation rule.
|
|
101
101
|
return text;
|
|
102
102
|
}
|
|
103
103
|
|