roast-my-design-system 4.2.2 → 4.2.3

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/bin/roast.mjs CHANGED
@@ -95,9 +95,10 @@ const tmp = mkdtempSync(join(tmpdir(), 'roast-'));
95
95
  const harvestPath = join(tmp, 'harvest.json');
96
96
  const summaryPath = join(tmp, 'summary.json');
97
97
 
98
- function run(script, args) {
98
+ function run(script, args, env) {
99
99
  // --json keeps stdout clean for the JSON payload; child chatter is dropped
100
- const r = spawnSync(process.execPath, [join(SCRIPTS, script), ...args], { stdio: asJson ? 'ignore' : 'inherit' });
100
+ const r = spawnSync(process.execPath, [join(SCRIPTS, script), ...args],
101
+ { stdio: asJson ? 'ignore' : 'inherit', ...(env ? { env: { ...process.env, ...env } } : {}) });
101
102
  if (r.status !== 0) {
102
103
  rmSync(tmp, { recursive: true, force: true });
103
104
  process.exit(r.status ?? 1);
@@ -106,8 +107,10 @@ function run(script, args) {
106
107
  const say = (s) => { if (!asJson) console.log(s); };
107
108
 
108
109
  say(`roast-my-design-system ${VERSION} · read-only scan, nothing leaves your machine\n`);
110
+ // the harvest goes to a temp dir this wrapper deletes right after; tell the
111
+ // script so it does not print a path that will be gone seconds later
109
112
  run('harvest/index.mjs', [target, '--out', harvestPath,
110
- ...excludes.flatMap((e) => ['--exclude', e])]);
113
+ ...excludes.flatMap((e) => ['--exclude', e])], { ROAST_EPHEMERAL_OUT: '1' });
111
114
  say('');
112
115
  run('diagnose/index.mjs', [harvestPath, '--out', outPath, '--theme', theme, '--summary', summaryPath,
113
116
  ...(commissionedBy ? ['--by', commissionedBy] : [])]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "roast-my-design-system",
3
- "version": "4.2.2",
3
+ "version": "4.2.3",
4
4
  "description": "Your AI can write the UI. This makes sure it writes your UI. A deterministic scanner counts every colour, spacing value and duplicate component, scores you 0-100 against 34 public repos, scopes the scan with .roastignore, injects agent rules with --apply.",
5
5
  "keywords": [
6
6
  "design-system",
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * Diagnose — step 2 of the Proffer pipeline. Renders a harvest.json into a
3
+ * Diagnose — step 2 of the pipeline. Renders a harvest.json into a
4
4
  * single self-contained HTML report: the visceral, shareable "here's your
5
5
  * mess" page with real file paths. No dependencies, no server — one file.
6
6
  *
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Component harvest — every component defined in the repo, with its REAL prop
3
3
  * signature and where it is actually used. Parsers lifted from 1.0's
4
- * compile-skill.mjs (proffer-spine/tools) — brace/string-aware regex parsing,
4
+ * compile-skill.mjs — brace/string-aware regex parsing,
5
5
  * no AST dependency — then extended to scan the whole repo, not one ui/ dir.
6
6
  */
7
7
  import { readFileSync } from 'node:fs';
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * Harvest — step 1 of the Proffer pipeline. Non-destructive scan of a repo:
3
+ * Harvest — step 1 of the pipeline. Non-destructive scan of a repo:
4
4
  * components (real props, real usages), all styling, implicit tokens with
5
5
  * frequency counts, duplicates, context files. Writes harvest.json; prints a
6
6
  * one-screen summary (the seed of the step-2 diagnosis).
@@ -159,23 +159,45 @@ writeFileSync(outPath, JSON.stringify(harvest, null, 2));
159
159
  // ---------- one-screen summary ----------
160
160
  const nonPage = components.filter((c) => !c.isPage);
161
161
  const hexColors = tokens.colors.filter((c) => c.value.startsWith('#'));
162
- const top = (list, n = 5) => list.slice(0, n).map((e) => `${e.value} ×${e.count}`).join(', ');
162
+ // A truncated capture like "rgba(var(--ink-rgb)" reads as a glitch in the
163
+ // terminal; the token reference inside it is the real story, so show that.
164
+ const showVal = (v) => {
165
+ const m = /var\((--[A-Za-z0-9_-]+)/.exec(v);
166
+ const balanced = (v.match(/\(/g) ?? []).length === (v.match(/\)/g) ?? []).length;
167
+ return m && !balanced ? `var(${m[1]})` : v;
168
+ };
169
+ // A "top" list is only news when something repeats; all-×1 says nothing.
170
+ const top = (list, n = 5) => list.some((e) => e.count > 1)
171
+ ? ` top: ${list.slice(0, n).map((e) => `${showVal(e.value)} ×${e.count}`).join(', ')}`
172
+ : (list.length ? ', none repeated' : '');
163
173
 
164
174
  console.log(`\nHarvest: ${profile.name ?? target}`);
165
175
  console.log(` framework: ${profile.framework}${profile.typescript ? ' + TS' : ''} design system: ${profile.designSystem.kind}${profile.designSystem.name ? ` (${profile.designSystem.name})` : ''} styling: ${profile.stylingDeps.join(', ') || 'none detected'}`);
166
176
  console.log(` files: ${files.code.length} code, ${files.styles.length} style`);
167
177
  if (exclusions.patterns.length) {
168
- const list = exclusions.patterns.map((p) => `${p.pattern} (${p.files} files, ${p.source})`).join(', ');
169
- console.log(` excluded by you: ${list}`);
178
+ // Same voice as the report header: source named once, slashes on folders,
179
+ // and the total at the end because that is the number that lands.
180
+ const groups = [...new Set(exclusions.patterns.map((p) => p.source))].map((src) => {
181
+ const own = exclusions.patterns.filter((p) => p.source === src);
182
+ return `(${src}): ${own.map((p, i) => `${p.pattern}/ ${p.files}${i === 0 ? ' files' : ''}`).join(', ')}`;
183
+ });
184
+ const total = exclusions.patterns.reduce((sum, p) => sum + p.files, 0);
185
+ console.log(` excluded by you ${groups.join(' · ')} · ${total} files kept out of this scan`);
170
186
  }
171
187
  console.log(`\n components: ${components.length} defined (${nonPage.length} reusable, ${components.length - nonPage.length} pages)`);
172
188
  console.log(` duplicates: ${duplicates.exactDuplicates.length} exact same-name, ${duplicates.families.length} name families`);
173
189
  for (const d of duplicates.exactDuplicates.slice(0, 3)) console.log(` · ${d.name} defined in ${d.files.length} files`);
174
190
  for (const f of duplicates.families.slice(0, 3)) console.log(` · ${f.root} family: ${f.members.map((m) => m.name).join(', ')}`);
175
- console.log(`\n colours: ${tokens.colors.length} distinct (${hexColors.length} hex, of which ${tokens.greyCount} greys) top: ${top(tokens.colors, 4)}`);
176
- console.log(` spacing: ${tokens.spacing.length} distinct CSS values top: ${top(tokens.spacing, 5)}`);
191
+ console.log(`\n colours: ${tokens.colors.length} distinct (${hexColors.length} hex, of which ${tokens.greyCount} greys)${top(tokens.colors, 4)}`);
192
+ console.log(` spacing: ${tokens.spacing.length} distinct CSS values${top(tokens.spacing, 5)}`);
177
193
  console.log(` radii: ${tokens.radii.length} font sizes: ${tokens.fontSizes.length} font families: ${tokens.fontFamilies.length} shadows: ${tokens.shadows.length}`);
178
194
  console.log(` tailwind: ${tokens.tailwind.colors.length} colour utils, ${tokens.tailwind.spacing.length} spacing utils, ${tokens.tailwind.textSizes.length} text sizes`);
179
195
  console.log(` inline styles: ${tokens.inlineStyles.count} blocks`);
180
196
  console.log(` context files: ${context.map((c) => c.file).join(', ') || 'none'}`);
181
- console.log(`\n → ${outPath} (${harvest.tookMs}ms)\n`);
197
+ // The npx wrapper writes the harvest to a temp dir it deletes right after;
198
+ // pointing the user at a path that will not exist is noise there.
199
+ if (process.env.ROAST_EPHEMERAL_OUT === '1') {
200
+ console.log(`\n scanned in ${harvest.tookMs}ms\n`);
201
+ } else {
202
+ console.log(`\n → ${outPath} (${harvest.tookMs}ms)\n`);
203
+ }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * File walker + repo profile. Adapted from 1.0's repo-inspect (proffer-spine),
2
+ * File walker + repo profile. Adapted from an earlier internal repo inspector,
3
3
  * widened: the harvester must see EVERYTHING that styles the app — code,
4
4
  * stylesheets of any flavor, and config — not just a happy-path shadcn layout.
5
5
  */
@@ -142,11 +142,15 @@ export function profileRepo(root, files) {
142
142
  deps = { ...deps, ...pkg.dependencies, ...pkg.devDependencies };
143
143
  const monorepo = pkgFiles.some((f) => f !== 'package.json');
144
144
 
145
+ // Hand-built sites are a real category, not a detection failure: "unknown"
146
+ // next to a good score reads like a shrug, so name what is actually there.
147
+ const htmlFiles = files.other.filter((f) => /\.html?$/.test(f)).length;
145
148
  const framework =
146
149
  deps.next ? 'next'
147
150
  : deps['@remix-run/react'] || deps['@react-router/dev'] ? 'remix'
148
151
  : deps.vite && deps.react ? 'vite-react'
149
152
  : deps.react ? 'react'
153
+ : htmlFiles > 0 ? 'static HTML/CSS'
150
154
  : 'unknown';
151
155
 
152
156
  const componentsJson = readJSON(join(root, 'components.json'));
@@ -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.2.2';
4
+ export const VERSION = '4.2.3';