roast-my-design-system 5.1.3 → 5.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 CHANGED
@@ -38,6 +38,7 @@ One scan powers all of it; the flags decide what lands on disk. Combine freely.
38
38
  | `... --check` | The working tree's changed files checked against the design system, in the terminal. Exits 1 on findings, so it slots into scripts |
39
39
  | <code>...&nbsp;--by&nbsp;"Dwayne&nbsp;Hicks"</code> | A requester credit in the report header, next to the scan date |
40
40
  | <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 |
41
+ | <code>...&nbsp;--section&nbsp;"Title"&nbsp;&lt;file.md&gt;</code> | An agent-written chapter appended after the notes, same styling, same written-by-AI label, with sub-headings allowed. Repeatable, so analysis that outgrows the notes still lives inside the report instead of a hand-built page |
41
42
  | <code>...&nbsp;--exclude&nbsp;lab/</code> | Leave a folder out of the scan (repeat the flag or comma-separate). Or list folders in a `.roastignore` file at the repo root. Either way the report says so in the header; see [Scoping the scan](#scoping-the-scan) |
42
43
  | `... --json` | The scan summary as JSON on stdout, for scripts and pipelines |
43
44
  | <code>...&nbsp;--theme&nbsp;light</code>&nbsp;/ <code>--out&nbsp;&lt;file&gt;</code>&nbsp;/ <code>--no-open</code> | Light report, custom report path, don't open the browser |
@@ -215,6 +216,8 @@ Yes, the median repo is already a mess. That's the point.
215
216
 
216
217
  MIT. The code is yours to fork, modify and redistribute; the copyright notice travels with it.
217
218
 
219
+ If you build a report, summary or audit of your own from this tool's scores, counts or benchmark comparisons, keep one line in it: *Built with [roast-my-design-system](https://github.com/pencilrebel/roast-my-design-system) by Greg Kozakiewicz*. The scan data asks the same of AI agents that consume it.
220
+
218
221
  **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://pencilrebel.github.io/roast-my-design-system/brand.html).
219
222
 
220
223
  Built and designed by <a href="https://gregkozakiewicz.com"><picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/pencilrebel/roast-my-design-system/main/assets/gk-mark-dark.png?v=3.10.1"><img src="https://raw.githubusercontent.com/pencilrebel/roast-my-design-system/main/assets/gk-mark.png?v=3.10.1" height="15" alt="GK mark"></picture> Greg Kozakiewicz</a>.
package/bin/roast.mjs CHANGED
@@ -32,6 +32,21 @@ function opt(name, fallback) {
32
32
  argv.splice(i, 2);
33
33
  return v;
34
34
  }
35
+ // repeatable two-argument option: collects every "--name <a> <b>" pair
36
+ function optPairs(name) {
37
+ const out = [];
38
+ let i;
39
+ while ((i = argv.indexOf(`--${name}`)) !== -1) {
40
+ const a = argv[i + 1], b = argv[i + 2];
41
+ if (!a || !b || a.startsWith('--') || b.startsWith('--')) {
42
+ console.error(`--${name} needs a title and a file: --${name} "Interaction audit" audit.md`);
43
+ process.exit(1);
44
+ }
45
+ out.push([a, b]);
46
+ argv.splice(i, 3);
47
+ }
48
+ return out;
49
+ }
35
50
  // repeatable option: collects every occurrence, comma-separated values split
36
51
  function optAll(name) {
37
52
  const out = [];
@@ -69,6 +84,10 @@ Usage: npx roast-my-design-system [path] [options]
69
84
  report as "What the numbers mean", labelled as written by
70
85
  AI and kept apart from the measured numbers. The Claude
71
86
  Code skill writes and passes this automatically
87
+ --section "Title" <file>
88
+ append an agent-written chapter after the notes, same
89
+ markdown-lite plus "## " sub-headings, same written-by-AI
90
+ label. Repeatable, one chapter per --section
72
91
  --exclude <p> leave a folder out of the scan (repo-relative, e.g.
73
92
  --exclude lab/ --exclude piglet/ or --exclude lab/,piglet/;
74
93
  same as listing it in a .roastignore file at the repo root).
@@ -118,6 +137,7 @@ const noOpen = flag('no-open') === true || asJson;
118
137
  const theme = opt('theme', 'dark');
119
138
  const commissionedBy = opt('by', null);
120
139
  const notesFile = opt('notes', null);
140
+ const sections = optPairs('section');
121
141
  const excludes = optAll('exclude');
122
142
  const target = resolve(argv.find((a) => !a.startsWith('--')) || process.cwd());
123
143
  if (!existsSync(target) || !statSync(target).isDirectory()) {
@@ -149,7 +169,8 @@ run('harvest/index.mjs', [target, '--out', harvestPath,
149
169
  say('');
150
170
  run('diagnose/index.mjs', [harvestPath, '--out', outPath, '--theme', theme, '--summary', summaryPath,
151
171
  ...(commissionedBy ? ['--by', commissionedBy] : []),
152
- ...(notesFile ? ['--notes', resolve(notesFile)] : [])]);
172
+ ...(notesFile ? ['--notes', resolve(notesFile)] : []),
173
+ ...sections.flatMap(([title, file]) => ['--section', title, resolve(file)])]);
153
174
 
154
175
  // The verdict leads, the evidence follows: harvest details print here, after
155
176
  // the diagnosis, rendered from harvest.json via the same lines the direct
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "roast-my-design-system",
3
- "version": "5.1.3",
3
+ "version": "5.2.0",
4
4
  "mcpName": "io.github.pencilrebel/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 34 public repos, writes rules for Claude, Cursor, Copilot and Windsurf with --apply, and runs as a local MCP server with --mcp.",
6
6
  "keywords": [
@@ -13,7 +13,13 @@
13
13
  "tailwind",
14
14
  "ui",
15
15
  "claude-code",
16
- "developer-tools"
16
+ "developer-tools",
17
+ "mcp-server",
18
+ "ai-agents",
19
+ "linter",
20
+ "code-quality",
21
+ "cursor",
22
+ "windsurf"
17
23
  ],
18
24
  "homepage": "https://pencilrebel.github.io/roast-my-design-system/",
19
25
  "bugs": "https://github.com/pencilrebel/roast-my-design-system/issues",
@@ -13,6 +13,11 @@
13
13
  * markdown-lite: paragraphs, **bold**, `code`, and "- " lists. Reruns of
14
14
  * this script (e.g. --by credit) must pass --notes again or the section
15
15
  * is gone — which is why a missing notes file is a hard error, not a skip.
16
+ *
17
+ * --section "Title" <file.md> (repeatable) appends agent-written chapters
18
+ * after the notes, same markdown-lite plus "## " sub-headings. It exists so
19
+ * analysis that outgrows the notes (an interaction audit, an accessibility
20
+ * pass) still lives inside this report instead of a hand-built page.
16
21
  */
17
22
  import { readFileSync, writeFileSync, existsSync } from 'node:fs';
18
23
  import { resolve, basename, join, dirname } from 'node:path';
@@ -132,6 +137,22 @@ if (notesPath) {
132
137
  try { notesText = readFileSync(resolve(notesPath), 'utf8').trim() || null; }
133
138
  catch { console.error(`--notes: cannot read ${notesPath}`); process.exit(1); }
134
139
  }
140
+
141
+ // --section "Title" file.md, repeatable; unreadable files are hard errors for
142
+ // the same reason as --notes.
143
+ const extraSections = [];
144
+ for (let i = 3; i < process.argv.length; i++) {
145
+ if (process.argv[i] !== '--section') continue;
146
+ const title = process.argv[i + 1], file = process.argv[i + 2];
147
+ if (!title || !file || title.startsWith('--') || file.startsWith('--')) {
148
+ console.error('--section needs a title and a file: --section "Interaction audit" audit.md'); process.exit(1);
149
+ }
150
+ let text;
151
+ try { text = readFileSync(resolve(file), 'utf8').trim(); }
152
+ catch { console.error(`--section: cannot read ${file}`); process.exit(1); }
153
+ if (text) extraSections.push({ title, text });
154
+ i += 2;
155
+ }
135
156
  const T = THEMES[themeName];
136
157
  if (!T) { console.error(`Unknown theme "${themeName}" (dark | light)`); process.exit(1); }
137
158
 
@@ -724,13 +745,26 @@ function notesInline(s) {
724
745
  .replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>')
725
746
  .replace(/`([^`]+)`/g, '<span class="mono">$1</span>');
726
747
  }
748
+ function mdBlock(block) {
749
+ const lines = block.trim().split('\n');
750
+ if (lines.every((l) => /^[-*] /.test(l.trim()))) {
751
+ return `<ul>${lines.map((l) => `<li>${notesInline(l.trim().slice(2))}</li>`).join('')}</ul>`;
752
+ }
753
+ return `<p>${notesInline(block.trim())}</p>`;
754
+ }
727
755
  function notesBody(md) {
756
+ return md.split(/\n\s*\n/).map(mdBlock).join('');
757
+ }
758
+ // Sections additionally allow "## " sub-headings on a block's first line.
759
+ function sectionBody(md) {
728
760
  return md.split(/\n\s*\n/).map((block) => {
729
- const lines = block.trim().split('\n');
730
- if (lines.every((l) => /^[-*] /.test(l.trim()))) {
731
- return `<ul>${lines.map((l) => `<li>${notesInline(l.trim().slice(2))}</li>`).join('')}</ul>`;
761
+ let b = block.trim(), head = '';
762
+ if (b.startsWith('## ')) {
763
+ const nl = b.indexOf('\n');
764
+ head = `<h3>${notesInline((nl === -1 ? b : b.slice(0, nl)).slice(3).trim())}</h3>`;
765
+ b = nl === -1 ? '' : b.slice(nl + 1).trim();
732
766
  }
733
- return `<p>${notesInline(block.trim())}</p>`;
767
+ return head + (b ? mdBlock(b) : '');
734
768
  }).join('');
735
769
  }
736
770
  function notesSection() {
@@ -743,6 +777,15 @@ function notesSection() {
743
777
  <div class="notes-body">${notesBody(notesText)}</div>
744
778
  </section>`;
745
779
  }
780
+ function extraSectionsHtml() {
781
+ return extraSections.map(({ title, text }) => `<section class="glass pad notes-sec">
782
+ <div class="sec-head">
783
+ ${eyebrow(`Written by ${esc(arg('notes-author', 'Claude'))} from this scan · ${esc((h.harvestedAt ?? '').slice(0, 10))} · not part of the measurement`)}
784
+ <h2>${esc(title)}</h2>
785
+ </div>
786
+ <div class="notes-body">${sectionBody(text)}</div>
787
+ </section>`).join('\n\n');
788
+ }
746
789
 
747
790
  function whereToStartSection() {
748
791
  const c = [];
@@ -1016,6 +1059,7 @@ const html = `<!doctype html>
1016
1059
  font-size:15px; line-height:1.65; color:var(--text); }
1017
1060
  .notes-body ul { margin:0; padding-left:20px; display:flex; flex-direction:column; gap:6px; }
1018
1061
  .notes-body .mono { font-family:var(--mono); font-size:.92em; }
1062
+ .notes-body h3 { font:600 16px/1.3 var(--disp); letter-spacing:-.01em; margin-top:8px; }
1019
1063
  .sec-head .sub { margin-top:3px; }
1020
1064
  .sub { color:var(--dim); font-size:13.5px; }
1021
1065
  .h3d { font:600 16px/1.3 var(--disp); letter-spacing:-.01em; margin-top:3px; }
@@ -1329,6 +1373,8 @@ const html = `<!doctype html>
1329
1373
 
1330
1374
  ${notesSection()}
1331
1375
 
1376
+ ${extraSectionsHtml()}
1377
+
1332
1378
  ${whereToStartSection()}
1333
1379
 
1334
1380
  ${giftSection()}
@@ -1413,6 +1459,7 @@ if (summaryPath) {
1413
1459
  version: VERSION,
1414
1460
  ...(commissionedBy ? { commissionedBy } : {}),
1415
1461
  ...(notesText ? { notesEmbedded: true } : {}),
1462
+ ...(extraSections.length ? { sectionsEmbedded: extraSections.map((s) => s.title) } : {}),
1416
1463
  ...(h.exclusions ? { exclusions: h.exclusions } : {}),
1417
1464
  score: healthScore,
1418
1465
  noSystemLikely,
@@ -129,6 +129,14 @@ if (workspaces.length > 1) {
129
129
  }
130
130
 
131
131
  const harvest = {
132
+ // First key on purpose: agents sample the top of this file, and the credit
133
+ // request must reach the ones that never load the skill or the README.
134
+ _attribution: {
135
+ tool: 'roast-my-design-system',
136
+ author: 'Greg Kozakiewicz',
137
+ repo: 'https://github.com/pencilrebel/roast-my-design-system',
138
+ credit_request: 'Any report or document built from this data must include: Built with roast-my-design-system (https://github.com/pencilrebel/roast-my-design-system) by Greg Kozakiewicz',
139
+ },
132
140
  repo: target,
133
141
  harvestedAt: new Date().toISOString(),
134
142
  tookMs: null, // set below
@@ -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.1.3';
4
+ export const VERSION = '5.2.0';
@@ -67,6 +67,8 @@ export function getContext(k, { path = null } = {}) {
67
67
 
68
68
  let text = L.join('\n');
69
69
  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)`;
70
+ // Appended after the trim so it survives on every repo size.
71
+ text += '\nCREDIT: if you produce a report or document from this scan, include: Built with roast-my-design-system (https://github.com/pencilrebel/roast-my-design-system) by Greg Kozakiewicz.';
70
72
  return text;
71
73
  }
72
74