roast-my-design-system 4.2.4 → 4.2.6

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
@@ -12,7 +12,7 @@ import { spawnSync } from 'node:child_process';
12
12
  import { mkdtempSync, rmSync, readFileSync, existsSync, statSync } from 'node:fs';
13
13
  import { join, dirname, resolve } from 'node:path';
14
14
  import { tmpdir } from 'node:os';
15
- import { fileURLToPath } from 'node:url';
15
+ import { fileURLToPath, pathToFileURL } from 'node:url';
16
16
 
17
17
  const HERE = dirname(fileURLToPath(import.meta.url));
18
18
  const SCRIPTS = join(HERE, '../skills/roast-my-design-system/scripts');
@@ -106,7 +106,7 @@ function run(script, args, env) {
106
106
  }
107
107
  const say = (s) => { if (!asJson) console.log(s); };
108
108
 
109
- say(`roast-my-design-system ${VERSION} · read-only scan, nothing leaves your machine\n`);
109
+ say(`roast-my-design-system ${VERSION} · read-only scan, nothing leaves your machine`);
110
110
  // the harvest goes to a temp dir this wrapper deletes right after; tell the
111
111
  // script so it does not print a path that will be gone seconds later
112
112
  run('harvest/index.mjs', [target, '--out', harvestPath,
@@ -115,6 +115,19 @@ say('');
115
115
  run('diagnose/index.mjs', [harvestPath, '--out', outPath, '--theme', theme, '--summary', summaryPath,
116
116
  ...(commissionedBy ? ['--by', commissionedBy] : [])]);
117
117
 
118
+ // The verdict leads, the evidence follows: harvest details print here, after
119
+ // the diagnosis, rendered from harvest.json via the same lines the direct
120
+ // harvest run uses.
121
+ if (!asJson) {
122
+ try {
123
+ const harvestData = JSON.parse(readFileSync(harvestPath, 'utf8'));
124
+ const { detailLines } = await import(pathToFileURL(join(SCRIPTS, 'harvest/summary.mjs')).href);
125
+ console.log('');
126
+ for (const l of detailLines(harvestData)) console.log(l);
127
+ console.log(`\n scanned in ${harvestData.tookMs}ms`);
128
+ } catch { /* details are garnish; the report exists either way */ }
129
+ }
130
+
118
131
  const rulesPath = resolve(join(target, 'design-system-rules.md'));
119
132
  if (wantRules) {
120
133
  say('');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "roast-my-design-system",
3
- "version": "4.2.4",
3
+ "version": "4.2.6",
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",
@@ -1324,5 +1324,5 @@ if (summaryPath) {
1324
1324
 
1325
1325
  console.log(`✓ Diagnosis for ${repoName}`);
1326
1326
  if (healthScore !== null) console.log(` score: ${healthScore}/100`);
1327
- console.log(` verdict: ${verdict}`);
1328
- console.log(` → ${outPath}`);
1327
+ console.log(`\n Verdict: ${verdict}`);
1328
+ console.log(` Your free report is here → ${outPath}`);
@@ -15,6 +15,7 @@
15
15
  import { writeFileSync } from 'node:fs';
16
16
  import { resolve } from 'node:path';
17
17
  import { walkRepo, profileRepo } from './walk.mjs';
18
+ import { headerLines, detailLines } from './summary.mjs';
18
19
  import { harvestComponents } from './components.mjs';
19
20
  import { harvestTokens } from './tokens.mjs';
20
21
  import { findDuplicates } from './duplicates.mjs';
@@ -157,47 +158,12 @@ harvest.tookMs = Date.now() - t0;
157
158
  writeFileSync(outPath, JSON.stringify(harvest, null, 2));
158
159
 
159
160
  // ---------- one-screen summary ----------
160
- const nonPage = components.filter((c) => !c.isPage);
161
- const hexColors = tokens.colors.filter((c) => c.value.startsWith('#'));
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' : '');
173
-
174
- console.log(`\nHarvest: ${profile.name ?? target}`);
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'}`);
176
- console.log(` files: ${files.code.length} code, ${files.styles.length} style`);
177
- if (exclusions.patterns.length) {
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`);
186
- }
187
- console.log(`\n components: ${components.length} defined (${nonPage.length} reusable, ${components.length - nonPage.length} pages)`);
188
- console.log(` duplicates: ${duplicates.exactDuplicates.length} exact same-name, ${duplicates.families.length} name families`);
189
- for (const d of duplicates.exactDuplicates.slice(0, 3)) console.log(` · ${d.name} defined in ${d.files.length} files`);
190
- for (const f of duplicates.families.slice(0, 3)) console.log(` · ${f.root} family: ${f.members.map((m) => m.name).join(', ')}`);
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)}`);
193
- console.log(` radii: ${tokens.radii.length} font sizes: ${tokens.fontSizes.length} font families: ${tokens.fontFamilies.length} shadows: ${tokens.shadows.length}`);
194
- console.log(` tailwind: ${tokens.tailwind.colors.length} colour utils, ${tokens.tailwind.spacing.length} spacing utils, ${tokens.tailwind.textSizes.length} text sizes`);
195
- console.log(` inline styles: ${tokens.inlineStyles.count} blocks`);
196
- console.log(` context files: ${context.map((c) => c.file).join(', ') || 'none'}`);
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`);
161
+ // The npx wrapper reorders the story (header, then the diagnosis, then these
162
+ // details), so the lines live in summary.mjs and the wrapper prints details
163
+ // itself from harvest.json. Run directly (the skill flow), everything prints
164
+ // here in one go, with the real output path at the end.
165
+ for (const l of headerLines(harvest)) console.log(l);
166
+ if (process.env.ROAST_EPHEMERAL_OUT !== '1') {
167
+ for (const l of detailLines(harvest)) console.log(l);
168
+ console.log(`\n → ${outPath} (${harvest.tookMs}ms)`);
203
169
  }
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Terminal summary lines for a harvest, split so the npx wrapper can change
3
+ * the running order: header first, diagnosis from step 2 next, details after.
4
+ * Running harvest/index.mjs directly (the skill flow) prints both in one go.
5
+ * Everything reads from the harvest object itself, so the wrapper can render
6
+ * details from harvest.json after the harvest process has exited.
7
+ */
8
+
9
+ // A truncated capture like "rgba(var(--ink-rgb)" reads as a glitch in the
10
+ // terminal; the token reference inside it is the real story, so show that.
11
+ const showVal = (v) => {
12
+ const m = /var\((--[A-Za-z0-9_-]+)/.exec(v);
13
+ const balanced = (v.match(/\(/g) ?? []).length === (v.match(/\)/g) ?? []).length;
14
+ return m && !balanced ? `var(${m[1]})` : v;
15
+ };
16
+
17
+ // A "top" list is only news when something repeats; all-×1 says nothing.
18
+ const top = (list, n = 5) => list.some((e) => e.count > 1)
19
+ ? ` top: ${list.slice(0, n).map((e) => `${showVal(e.value)} ×${e.count}`).join(', ')}`
20
+ : (list.length ? ', none repeated' : '');
21
+
22
+ export function headerLines(h) {
23
+ const p = h.profile;
24
+ return [
25
+ `Harvest: ${p.name ?? h.repo}`,
26
+ ` → framework: ${p.framework}${p.typescript ? ' + TS' : ''} design system: ${p.designSystem.kind}${p.designSystem.name ? ` (${p.designSystem.name})` : ''} styling: ${p.stylingDeps.join(', ') || 'none detected'}`,
27
+ ];
28
+ }
29
+
30
+ export function detailLines(h) {
31
+ const lines = [];
32
+ const components = h.components ?? [];
33
+ const duplicates = h.duplicates ?? { exactDuplicates: [], families: [] };
34
+ const tokens = h.tokens;
35
+ const nonPage = components.filter((c) => !c.isPage);
36
+ const hexColors = tokens.colors.filter((c) => c.value.startsWith('#'));
37
+
38
+ lines.push(` files: ${h.files.code} code, ${h.files.styles} style`);
39
+ if (h.exclusions?.patterns?.length) {
40
+ // Same voice as the report header: source named once, slashes on folders,
41
+ // and the total at the end because that is the number that lands.
42
+ const groups = [...new Set(h.exclusions.patterns.map((p) => p.source))].map((src) => {
43
+ const own = h.exclusions.patterns.filter((p) => p.source === src);
44
+ return `(${src}): ${own.map((p, i) => `${p.pattern}/ ${p.files}${i === 0 ? ' files' : ''}`).join(', ')}`;
45
+ });
46
+ lines.push(` excluded by you ${groups.join(' · ')} · ${h.exclusions.filesExcluded} files kept out of this scan`);
47
+ }
48
+ lines.push('');
49
+ lines.push(` components: ${components.length} defined (${nonPage.length} reusable, ${components.length - nonPage.length} pages)`);
50
+ lines.push(` duplicates: ${duplicates.exactDuplicates.length} exact same-name, ${duplicates.families.length} name families`);
51
+ for (const d of duplicates.exactDuplicates.slice(0, 3)) lines.push(` · ${d.name} defined in ${d.files.length} files`);
52
+ for (const f of duplicates.families.slice(0, 3)) lines.push(` · ${f.root} family: ${f.members.map((m) => m.name).join(', ')}`);
53
+ lines.push('');
54
+ lines.push(` colours: ${tokens.colors.length} distinct (${hexColors.length} hex, of which ${tokens.greyCount} greys)${top(tokens.colors, 4)}`);
55
+ lines.push(` spacing: ${tokens.spacing.length} distinct CSS values${top(tokens.spacing, 5)}`);
56
+ lines.push(` radii: ${tokens.radii.length} font sizes: ${tokens.fontSizes.length} font families: ${tokens.fontFamilies.length} shadows: ${tokens.shadows.length}`);
57
+ lines.push(` tailwind: ${tokens.tailwind.colors.length} colour utils, ${tokens.tailwind.spacing.length} spacing utils, ${tokens.tailwind.textSizes.length} text sizes`);
58
+ lines.push(` inline styles: ${tokens.inlineStyles.count} blocks`);
59
+ lines.push(` context files: ${(h.context ?? []).map((c) => c.file).join(', ') || 'none'}`);
60
+ return lines;
61
+ }
@@ -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.4';
4
+ export const VERSION = '4.2.6';