roast-my-design-system 4.3.1 → 4.4.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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "roast-my-design-system",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.4.0",
|
|
4
4
|
"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, scopes the scan with .roastignore, and writes rules for Claude, Cursor, GitHub Copilot and Windsurf with --apply.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"design-system",
|
|
@@ -185,5 +185,69 @@ export function harvestComponents(root, codeFiles) {
|
|
|
185
185
|
}
|
|
186
186
|
|
|
187
187
|
components.sort((a, b) => b.usageCount - a.usageCount || a.name.localeCompare(b.name));
|
|
188
|
+
|
|
189
|
+
// Pass 3: golden examples — for the most-used components, quote the repo's
|
|
190
|
+
// own most common usage, verbatim, with a receipt. Descriptive, never
|
|
191
|
+
// invented: we photograph the plate that leaves the kitchen most often.
|
|
192
|
+
// A component with no dominant pattern gets no example (printing one would
|
|
193
|
+
// be a lie), and a component used a handful of times is not a pattern yet.
|
|
194
|
+
const uniqueNames = new Set([...byName].filter(([, d]) => d.length === 1).map(([n]) => n));
|
|
195
|
+
const candidates = components
|
|
196
|
+
.filter((c) => !c.isPage && c.usageCount >= 6 && uniqueNames.has(c.name))
|
|
197
|
+
.slice(0, 12);
|
|
198
|
+
for (const c of candidates) {
|
|
199
|
+
const found = []; // { tag, file }
|
|
200
|
+
for (const [file, src] of sources) {
|
|
201
|
+
if (file === c.file) continue;
|
|
202
|
+
let from = 0, open;
|
|
203
|
+
while ((open = src.indexOf(`<${c.name}`, from)) !== -1) {
|
|
204
|
+
from = open + c.name.length + 1;
|
|
205
|
+
const after = src[open + c.name.length + 1];
|
|
206
|
+
if (after && !/[\s/>]/.test(after)) continue;
|
|
207
|
+
const tag = sliceTag(src, open);
|
|
208
|
+
if (tag && tag.length <= 220 && !tag.includes('`')) found.push({ tag, file });
|
|
209
|
+
if (found.length > 400) break;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
if (found.length < 5) continue;
|
|
213
|
+
// Group by prop-name signature; the winner must be a real majority habit.
|
|
214
|
+
const sigOf = (tag) => [...tag.matchAll(/[\s({]([A-Za-z_][\w-]*)=/g)].map((m) => m[1]).sort().join(',');
|
|
215
|
+
const groups = new Map();
|
|
216
|
+
for (const u of found) {
|
|
217
|
+
const s = sigOf(u.tag);
|
|
218
|
+
(groups.get(s) ?? groups.set(s, []).get(s)).push(u);
|
|
219
|
+
}
|
|
220
|
+
const [sig, top] = [...groups.entries()].sort((a, b) => b[1].length - a[1].length)[0];
|
|
221
|
+
if (top.length < 3 || top.length / found.length < 0.4) continue;
|
|
222
|
+
// The shortest real tag with the dominant signature is the cleanest quote.
|
|
223
|
+
const best = [...top].sort((a, b) => a.tag.length - b.tag.length)[0];
|
|
224
|
+
const selfClosed = /\/>\s*$/.test(best.tag);
|
|
225
|
+
c.usageExample = {
|
|
226
|
+
snippet: `${best.tag.replace(/\s+/g, ' ').trim()}${selfClosed ? '' : `…</${c.name}>`}`,
|
|
227
|
+
file: best.file,
|
|
228
|
+
matches: top.length,
|
|
229
|
+
total: found.length,
|
|
230
|
+
props: sig || null,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
188
234
|
return { components, totalDefined: components.length };
|
|
189
235
|
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Slice a JSX opening tag from `<` to its matching `>`, string- and
|
|
239
|
+
* brace-aware so `onClick={() => a > b}` cannot end the tag early.
|
|
240
|
+
*/
|
|
241
|
+
function sliceTag(src, open) {
|
|
242
|
+
let depth = 0, str = null;
|
|
243
|
+
const limit = Math.min(src.length, open + 400);
|
|
244
|
+
for (let i = open; i < limit; i++) {
|
|
245
|
+
const c = src[i];
|
|
246
|
+
if (str) { if (c === str && src[i - 1] !== '\\') str = null; continue; }
|
|
247
|
+
if (QUOTES.has(c)) { str = c; continue; }
|
|
248
|
+
if (c === '{') depth++;
|
|
249
|
+
else if (c === '}') depth--;
|
|
250
|
+
else if (c === '>' && depth === 0) return src.slice(open, i + 1);
|
|
251
|
+
}
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
@@ -78,10 +78,18 @@ const repoName = h.profile?.name ?? 'this repo';
|
|
|
78
78
|
.sort((a, b) => b.usageCount - a.usageCount).slice(0, CAP.components);
|
|
79
79
|
if (top.length) {
|
|
80
80
|
section('Canonical components');
|
|
81
|
-
rule(
|
|
81
|
+
rule(compact
|
|
82
|
+
? 'Use these existing components instead of writing new ones:'
|
|
83
|
+
: 'Use these existing components instead of writing new ones, the way this repo already uses them:');
|
|
82
84
|
for (const c of top) {
|
|
83
85
|
const props = !compact && c.propsHint?.named?.length ? ` · props: ${c.propsHint.named.slice(0, 4).join(', ')}` : '';
|
|
84
86
|
lines.push(` - \`<${c.name}>\` from \`${c.file}\` (used ${c.usageCount}x${props})`);
|
|
87
|
+
// Golden example: the repo's own most common real usage, quoted with a
|
|
88
|
+
// receipt. Full variant only; compact hosts trade examples for size.
|
|
89
|
+
const ex = c.usageExample;
|
|
90
|
+
if (!compact && ex) {
|
|
91
|
+
lines.push(` - most common usage, as in \`${ex.file}\` (matching ${ex.matches} of ${ex.total} usages): \`${ex.snippet}\``);
|
|
92
|
+
}
|
|
85
93
|
}
|
|
86
94
|
}
|
|
87
95
|
|