roast-my-design-system 4.4.0 → 4.5.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 CHANGED
@@ -81,6 +81,18 @@ playground/
81
81
 
82
82
  Both routes merge, and both are loud on purpose. The harvest JSON records every active pattern and how many files it removed, and the report prints a line in the header ("2 folders excluded by .roastignore (lab/, playground/) · 946 files kept out of this scan"). You can narrow the question, but the report always says which question was asked, so a scoped score can't be quietly gamed. There is no negation and no glob syntax: plain folder prefixes, nothing clever.
83
83
 
84
+ ## In CI
85
+
86
+ The scanner already speaks SARIF, so wiring it into GitHub code scanning is six lines. Findings appear in the Security tab, annotated on the files themselves:
87
+
88
+ ```yaml
89
+ - uses: actions/checkout@v4
90
+ - run: npx roast-my-design-system . --sarif --no-open
91
+ - uses: github/codeql-action/upload-sarif@v3
92
+ with:
93
+ sarif_file: design-system-roast.sarif
94
+ ```
95
+
84
96
  ## Install
85
97
 
86
98
  **No install, no Claude needed — just try it:**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "roast-my-design-system",
3
- "version": "4.4.0",
3
+ "version": "4.5.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",
@@ -449,6 +449,32 @@ function paletteSection() {
449
449
  </section>`;
450
450
  }
451
451
 
452
+ // Agent traps: findings that do not just sit there but multiply, because an
453
+ // agent reads the repo as instruction. Copy only, no new measurement, and a
454
+ // trap renders only when its mechanism is real for this repo, so the marker
455
+ // stays scarce enough to mean something.
456
+ function trapBox(text) {
457
+ return `<div class="trap">${ICONS.warn}<span><b>Agent trap.</b> ${text}</span></div>`;
458
+ }
459
+ function dupesTrap() {
460
+ const hard = exactDupes.filter((d) => !d.wrapped);
461
+ if (hard.length < 2) return '';
462
+ const top = hard[0];
463
+ return trapBox(`&lt;${esc(top.name)}&gt; alone has ${top.files.length} implementations. An agent asked for one cannot tell which is canonical, so it picks at random or writes another, and every wrong pick becomes the example the next agent copies. This is the finding that multiplies itself.`);
464
+ }
465
+ function spacingTrap() {
466
+ const candidates = [...spacing, ...(arbitrary ?? [])].filter((s) => s.count >= 15)
467
+ .sort((a, b) => b.count - a.count);
468
+ if (candidates.length) {
469
+ const t = candidates[0];
470
+ return trapBox(`${esc(t.value)} appears ${t.count} times. An agent looking for how this repo does spacing reads repetition as intent, so it will write occurrence ${t.count + 1}. The most copied pattern here is the one you least want copied.`);
471
+ }
472
+ if (twSpacing.length > 0 && spacing.length >= 10) {
473
+ return trapBox(`Two spacing dialects coexist here: Tailwind steps and ${spacing.length} raw CSS values. A human knows which is legacy. An agent sees two valid options and matches whichever file it opened last, so every edit is a coin toss between systems.`);
474
+ }
475
+ return '';
476
+ }
477
+
452
478
  function spacingBars() {
453
479
  const hasTw = twSpacing.length > 0;
454
480
  const merged = [...spacing.map((s) => ({ label: s.value, count: s.count, off: hasTw })),
@@ -465,7 +491,7 @@ function spacingBars() {
465
491
  <div class="chips-row">${arbitrary.slice(0, 10).map((a) => `<span class="vchip bad" title="${esc(a.files?.[0]?.file ?? '')}">${esc(a.value)} ×${a.count}</span>`).join('')}${arbitrary.length > 10 ? `<span class="vchip dim">+${arbitrary.length - 10} more</span>` : ''}</div></div>` : '';
466
492
  return `<section class="glass pad">
467
493
  ${sectionHead(`${n(spacingTotal)} off-scale spacing values`, `a disciplined repo keeps these around a dozen · on-scale Tailwind steps (·) shown for context · off-scale in coral`)}
468
- <div class="bars">${rows}</div>${arb}</section>`;
494
+ <div class="bars">${rows}</div>${arb}${spacingTrap()}</section>`;
469
495
  }
470
496
 
471
497
  function duplicatesSection() {
@@ -494,6 +520,7 @@ function duplicatesSection() {
494
520
  </div>`).join('');
495
521
  return `<div class="glass pad half">
496
522
  ${sectionHead('Duplicated components', 'an agent asking which one is canonical gets several plausible answers. Paths open in VS Code')}
523
+ ${dupesTrap()}
497
524
  <div class="fams">${iconCard}${dupeCards}${famCards}</div></div>`;
498
525
  }
499
526
 
@@ -967,6 +994,10 @@ const html = `<!doctype html>
967
994
  .nods { display:flex; gap:12px; align-items:flex-start; background:var(--nods-bg); border:1px solid var(--nods-line);
968
995
  border-radius:16px; padding:14px 16px; margin-top:20px; font-size:14px; }
969
996
  .nods .ic { position:static; flex-shrink:0; width:22px; height:22px; }
997
+ .trap { display:flex; gap:11px; align-items:flex-start; background:var(--coral-soft); border:1px solid var(--bad-ring);
998
+ border-radius:14px; padding:12px 14px; margin-top:16px; font-size:13px; line-height:1.6; color:var(--dim); }
999
+ .trap b { color:var(--coral); font-weight:700; letter-spacing:.02em; }
1000
+ .trap .ic { position:static; flex-shrink:0; width:18px; height:18px; margin-top:2px; }
970
1001
 
971
1002
  /* verdict hero card */
972
1003
  .verdict-card { position:relative; overflow:hidden; margin-top:22px; padding:26px 28px; }
@@ -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 = '4.4.0';
4
+ export const VERSION = '4.5.0';