roast-my-design-system 10.1.2 → 10.1.4

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
@@ -142,20 +142,20 @@ Eleven reports, hosted exactly as the tool writes them. Every number determinist
142
142
  - **[dubinc/dub](https://gregkozakiewicz.github.io/roast-my-design-system/examples/dubinc-dub.html)**: 642 arbitrary bracket values, 21 duplicated components, and the chart-palette gap named. Score 20.
143
143
  - **[telekom/scale](https://gregkozakiewicz.github.io/roast-my-design-system/examples/telekom-scale.html)** (Stencil): 95 Stencil components read by tag; 66 spacing values outside the scale where about 12 would do; Claude's notes embedded. Score 55.
144
144
  - **[magicuidesign/magicui](https://gregkozakiewicz.github.io/roast-my-design-system/examples/magicui.html)** (registry): counted on the components it publishes, 52 off-theme colours per 100 files in the code it ships, its docs site kept out and named. Score 78.
145
- - **[adobe/spectrum-web-components](https://gregkozakiewicz.github.io/roast-my-design-system/examples/adobe-spectrum.html)** (Lit): hardcoded colours sitting beside 740 colour tokens, and 37 !important declarations. Score 72.
145
+ - **[adobe/spectrum-web-components](https://gregkozakiewicz.github.io/roast-my-design-system/examples/adobe-spectrum.html)** (Lit): 740 colour tokens with 8 hardcoded colours beside them, and 37 !important declarations. Score 83.
146
146
 
147
147
  The full report for vercel/ai-chatbot. The verdict answers what the agent will learn here, the gap section comes first, then "What the repo teaches the agent", Claude's read of the scan:
148
148
 
149
- ![The full diagnosis report for vercel/ai-chatbot in dark mode: a fixed side panel with the health score and what it measures, the stack, how the repo was read as a shadcn install, an index of every section and what is not the team's and not counted; then where the agent will have to guess, the fixes you can make right now, each with what it is worth and its copy-the-fix-prompt button, the wrapped present with the agent rules, an agent trap callout, 3-yardstick tiles including the 2 shadcn tiles, the adoption map treemap, palette forensics, the shadcn theme variable by variable, spacing receipts, typography specimens, offenders, duplicates, and the component usage ledger](assets/report-full-dark.jpg?v=10.1.1)
149
+ ![The full diagnosis report for vercel/ai-chatbot in dark mode: a fixed side panel with the health score and what it measures, the stack, how the repo was read as a shadcn install, an index of every section and what is not the team's and not counted; then where the agent will have to guess, the fixes you can make right now, each with what it is worth and its copy-the-fix-prompt button, the wrapped present with the agent rules, an agent trap callout, 3-yardstick tiles including the 2 shadcn tiles, the adoption map treemap, palette forensics, the shadcn theme variable by variable, spacing receipts, typography specimens, offenders, duplicates, and the component usage ledger](assets/report-full-dark.jpg?v=10.1.4)
150
150
 
151
151
  The same report in light mode (one file, built-in toggle):
152
152
 
153
- ![The diagnosis report in light mode](assets/report-light-hero.png?v=10.1.1)
153
+ ![The diagnosis report in light mode](assets/report-light-hero.png?v=10.1.4)
154
154
 
155
155
  ## Why the numbers hold
156
156
 
157
157
  - **Deterministic.** A zero-dependency Node script reads every file and returns the same numbers every run. About a second on a normal repo.
158
- - **Read-only. No network. No telemetry.** The test suite fails if package.json ever declares a dependency.
158
+ - **Read-only. No network. No telemetry.** The test suite fails if package.json ever declares a dependency. Every release runs 461 checks against 29 sample repos before it is tagged; the fixtures and their snapshots live in `tests/`, one JSON file each.
159
159
  - **Honest gaps.** What the scan cannot read says "not measured" and drops out of the score.
160
160
  - **Honest exclusions.** Tests, stories, docs sites, artwork and email templates are left out. Your own exclusions are printed in the report header with file counts.
161
161
  - **A real benchmark.** 119 public repos. A core fleet of 34 React repos sets the medians; the rest feed the kit and web-component groups, so a shadcn repo is compared with shadcn repos and a Lit system with Lit and Stencil systems. The builder is in `tools/benchmark/`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "roast-my-design-system",
3
- "version": "10.1.2",
3
+ "version": "10.1.4",
4
4
  "mcpName": "io.github.gregkozakiewicz/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 119 public repos, reads React and web components (Stencil, Lit), hands you a copy-paste fix prompt for each top finding, writes rules for Claude, Cursor, Copilot and Windsurf with --apply, and runs as a local MCP server with --mcp.",
6
6
  "keywords": [
@@ -32,6 +32,7 @@ import { fixPrompt } from '../lib/fixprompt.mjs';
32
32
  import { WHY } from './why.mjs';
33
33
  import { parseColor, luminance, isGrey } from '../lib/color.mjs';
34
34
  import { loadBenchmark, benchHelpers, makeHealthOf, coreMetrics, tileHealths, scoreOfTiles, scoreBreakdown, scorePackage as scorePackageOf, ZERO_IDEAL, WARN_TOLERANCE, SCORE_OF, SCHEMA_VERSION, benchKind } from './score.mjs';
35
+ import { REPO_URL, AUTHOR_URL } from '../lib/links.mjs';
35
36
  import { ownSpacing, profileOf, installedOf, splitArbitrary, installedByOwner } from '../profiles/index.mjs';
36
37
  import { KITS } from '../profiles/kit-common.mjs';
37
38
  import { rulesOn as lintRulesOn, describeRule as lintDescribe, entryMatcher as lintMatcher, RULE_TILE as LINT_TILE } from '../profiles/shadcn-lint.mjs';
@@ -67,10 +68,13 @@ const benchGroup = B.sliceInfo ? { name: GROUP_NAMES[B.sliceInfo.kind] ?? B.slic
67
68
  const healthOf = makeHealthOf(B);
68
69
  const M = coreMetrics(h);
69
70
 
70
- // The GK mark (32px favicon, also gregkozakiewicz.com). Reused in the footer
71
- // as a CSS mask filled with currentColor so it works in both themes.
71
+ // The GK mark (also gregkozakiewicz.com), in the footer beside the author's
72
+ // name as a CSS mask filled with currentColor so it works in both themes.
72
73
  const GK_MASK = 'data:image/png;base64,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'; // alpha version of the mark, for CSS mask
73
- const GK_MARK = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAACXBIWXMAAAAAAAAAAQCEeRdzAAACPklEQVR4nOVWTYh5URR/PlJYEKV8LKwkZalsmI2dZmMnK8pKmaZ87JSanexEkayUhSxsZyfJNBQ7UYqVhSShfM+v/6mXmtU81+p/Frfze+/d3++ce88993Hcf2cikehZ1GazWafTPUVDLBZjLBQKnU6HMTVvcrl8Op3ebrdsNgsokUiYUVP4Pp/v9s96vd79cwZGK95sNkkgGAwC2u12lhrY3u12C/b1ei2TyV5eXjabjV6vZ6BBa51MJin8arUKWKlU4H99fSkUCo5JUQ2HQxJwuVzIYLVawcdIVSs8CQoNpMQ+Go0Aw+EwwXw+D+h2u4Vr0LRSqUSMqVQKsN1uE3Q4HEql8ng8BgIBTnDhqlSqxWIBuvP5bDAYLBbL5XIBHAwGeBuJRODvdjun0/nnPOjraDRK8TYaDcBMJkMwFosBfn9/E0wkEn9OgjYgnU6fTidQwAGcTCbw9/u9VqvFUYB/vV5RskajkRNcTiaTKR6PI9j5fE7xlstlPM/lcgTr9ToneJ/5oOB4PB70O8Tb7/f9fv9sNqPkXl9fhQsQ9f1ktVqNvUVCxA6ZRxsf5pMAxnsuVBSOwvv7+6Phk0PsBNn0OOLC9haLReqdvBL/wUNKNPnt7Q0LjeVutVqhUEij0TwU9W8BVA4JUEUul8tarcZGhpbIarWiSeA0HQ4Hkvn4+GB56VMvov6DA+z1eplR89btdsE+Ho9tNhv3jOv+8/MTFz06M3ypVMqMnTfcwPhn4djG/tue+Mf4RGom9gMt6lAx16huIwAAAABJRU5ErkJggg==';
74
+ // The product icon (assets/roastmds.svg) for the browser tab, and the same
75
+ // drawing as a 180px PNG for a phone home screen, which takes no SVG.
76
+ const ROAST_ICON = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgdmlld0JveD0iMCAwIDEwMCAxMDAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHJlY3Qgd2lkdGg9IjEwMCIgaGVpZ2h0PSIxMDAiIHJ4PSIxMiIgZmlsbD0iI0Y2RUZFNSIvPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMF84Ml8zMSkiPjxwYXRoIGQ9Ik0zLjczODI4IDIwLjU2MDdWMTEuMjE0OUMzLjczODI4IDcuMDg0MDggNy4wODQwOCAzLjczODI4IDExLjIxNDkgMy43MzgyOEgyMC41NjA3IiBzdHJva2U9IiM2NTUxOTIiIHN0cm9rZS13aWR0aD0iNy40NzY2NCIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIiBzdHJva2UtbGluZWNhcD0ic3F1YXJlIi8+PHBhdGggZD0iTTIwLjU2MDcgOTYuMjYxNkgxMS4yMTQ5QzcuMDg0MDggOTYuMjYxNiAzLjczODI4IDkyLjkxNTggMy43MzgyOCA4OC43ODVWNzkuNDM5MiIgc3Ryb2tlPSIjNjU1MTkyIiBzdHJva2Utd2lkdGg9IjcuNDc2NjQiIHN0cm9rZS1taXRlcmxpbWl0PSIxMCIgc3Ryb2tlLWxpbmVjYXA9InNxdWFyZSIvPjxwYXRoIGQ9Ik05Ni4yNjE5IDc5LjQzOTJWODguNzg1Qzk2LjI2MTkgOTIuOTE1OCA5Mi45MTYxIDk2LjI2MTYgODguNzg1MiA5Ni4yNjE2SDc5LjQzOTUiIHN0cm9rZT0iIzY1NTE5MiIgc3Ryb2tlLXdpZHRoPSI3LjQ3NjY0IiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHN0cm9rZS1saW5lY2FwPSJzcXVhcmUiLz48cGF0aCBkPSJNNzkuNDM5NSAzLjczODI4SDg4Ljc4NTJDOTIuOTE2MSAzLjczODI4IDk2LjI2MTkgNy4wODQwOCA5Ni4yNjE5IDExLjIxNDlWMjAuNTYwNyIgc3Ryb2tlPSIjNjU1MTkyIiBzdHJva2Utd2lkdGg9IjcuNDc2NjQiIHN0cm9rZS1taXRlcmxpbWl0PSIxMCIgc3Ryb2tlLWxpbmVjYXA9InNxdWFyZSIvPjxwYXRoIGQ9Ik0zOS41NDY1IDI3LjIwMzZIMjMuMDYzNkMyMC44MTkzIDI3LjIwMzYgMTkgMjkuMDIyOSAxOSAzMS4yNjcyVjQ3Ljc1MDFDMTkgNDkuOTk0NCAyMC44MTkzIDUxLjgxMzcgMjMuMDYzNiA1MS44MTM3SDM5LjU0NjVDNDEuNzkwNyA1MS44MTM3IDQzLjYxMDEgNDkuOTk0NCA0My42MTAxIDQ3Ljc1MDFWMzEuMjY3MkM0My42MTAxIDI5LjAyMjkgNDEuNzkwNyAyNy4yMDM2IDM5LjU0NjUgMjcuMjAzNloiIGZpbGw9IiNGMjMxNzAiLz48cGF0aCBkPSJNNjYuMDIxMiAyOC4xNTMzTDU0LjYzODEgNDcuODY2OEM1My40ODUgNDkuODY4MSA1NC45Mjc2IDUyLjM2NzIgNTcuMjMzNyA1Mi4zNjcySDc5Ljk5OTlDODIuMzA2IDUyLjM2NzIgODMuNzQ4NSA0OS44NjgxIDgyLjU5NTUgNDcuODY2OEw3MS4yMTI0IDI4LjE1MzNDNzAuMDU5MyAyNi4xNTIgNjcuMTc0MiAyNi4xNTIgNjYuMDIxMiAyOC4xNTMzWiIgZmlsbD0iIzNCQURDNiIvPjxwYXRoIGQ9Ik0zMC40OTQxIDY3LjY2NjdDNDMuMTE2NiA3NS4yNDAyIDU4Ljg4ODQgNzUuMjQwMiA3MS41MTA5IDY3LjY2NjciIHN0cm9rZT0iIzY1NTE5MiIgc3Ryb2tlLXdpZHRoPSI5LjExMjU4IiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiLz48L2c+PGRlZnM+PGNsaXBQYXRoIGlkPSJjbGlwMF84Ml8zMSI+PHJlY3Qgd2lkdGg9IjEwMCIgaGVpZ2h0PSIxMDAiIGZpbGw9IndoaXRlIi8+PC9jbGlwUGF0aD48L2RlZnM+PC9zdmc+';
77
+ const ROAST_TOUCH_ICON = 'data:image/png;base64,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';
74
78
 
75
79
  // Report identity. The scan id is a stable digest of this scan's shape, salted
76
80
  // with the report namespace, so the same repo scanned twice reads the same and
@@ -1970,9 +1974,8 @@ const html = `<!doctype html>
1970
1974
  <title>Design-system diagnosis:${nsMark} ${esc(repoName)}</title>
1971
1975
  ${ogTags()}
1972
1976
  <meta name="author" content="Greg Kozakiewicz · gregkozakiewicz.com">
1973
- <link rel="icon" type="image/png" sizes="32x32" href="${GK_MARK}">
1974
- <link rel="icon" type="image/png" sizes="192x192" href="data:image/png;base64,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">
1975
- <link rel="apple-touch-icon" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAALQAAAC0CAIAAACyr5FlAAAACXBIWXMAAAAAAAAAAQCEeRdzAAAQAElEQVR4nO1de2yN9///dDOxISipS4WZ1GUps2xqWZkwFrHJVqSmS2rENTUsY6STyS4WYTK3bDOpITbEgrhMbCXrlglDGI1rXFMt0VajLqHnrL/X73nleX8/nuf0tK6nPX2//nhyejz99Djv1/O+fd7v98cYhUKhUCgUCoVCoVAoFAqFQqFQKBQKhUKhUCgUCoVCoVAoahpiYmLq1KmD65NPPlm3bl1cn3rqqfr160f6cymqBxo2bNi/f/82bdrwR1Aksp9HUV3wzDPPxMbG/vXXXytWrGjQoIG8DxUSwU+lqBZ44oknBg4cmJ+fX15ePnHiRL4JywJDE9kPpogYwAm+AAnWrVt39epVkOPs2bMjR47Em/BC5AZFrQPEzxcdO3Y8deoUmFFYWIjr0aNHe/bsyRtUedRSQDFA9vXq1fvggw/y8vJAi1u3bl27du327dt//PHHCy+8YBylovyojYDUwY+4uLhdu3YFAgGQIxgMlpSUlJWV4fXq1asbN24c6c+oiBwQj3Tq1Km0tBRs+O+//0COGzduwPm4c+fOlStX5syZ8/TTTxuHRpH+pIrHBTEWDRs2nDt3LlVF0EXAAd4BUd577z0EurbrGtEPrnj0gIyZ5mrTps3p06f/cyDk4GtcwY+CgoIePXogrOUvavwS/YA1oZjffvtt8TY8wPu44p/279+PcAY308QoohxMfTZp0uTnn3/2qA0PP5j82LBhQ9u2bY2TGYv0Z1c8SsCmUMZJSUlwRakh7KtNDkS29EjgmnArDsSCClH7EoVg+ArUqVMnIyMDUg/4YPMDN4Af169fLyoqyszM5J6tbthGJ0RtJCQkwFjQ26iIGSRHSUkJwlq8OHXq1DvvvGPULY0+MHyFXOvVq4cfhw0bdvv2bbABViOM5kA0S+2C64ULF0aMGGGcXVxJvSuiAUxRUKhwRZcuXQp5w17cunUrDDmoPG7evInrpk2bGLZ4sh2aYq/xYIRCtdG3b98TJ04wzUX9URE5SAvELHj/888/N3cXeUhNkBqaGg/W/+HF+PHjQYs7d+5AbVRkVhDfMpSFzwF+nDx5sn///sYiBwnB3bsI/qcUDwe0KS1btly7di3VBvgRPloBReiNZmVlwRgZy6aQJVL2oRSpwajjAC+GDBly9uxZehJ+teEPZWF3cFt6errxmQ/ww+9wKEtqHqQ4dOHChWDAbQdlDsKQg3YnJyfnxRdfNG75j4hfEqZ0ZYzWB9VQ0HmEjPfu3Ss2pVJyXLt2DdcZM2ZwY0VUhTAAYW2jRo1ADr6jnmkNxuTJk7ldQptSFc1RUFDw2muvGXe7LsaCcbbupk6d+txzzxlHf2jBek0FLMvGjRvBjDsOQpLD3lgpdwBXtHnz5sbdlRVmQGfgx/nz52OF2bNnG4c9jRs3VrNS84CHvnv37qdPn6ZNKbPgIUfASZsyVAFGjx5N38KTFQUVwLbs7GwseOnSpUmTJplQka2myKo1RDZffvmlVARWFKfA3MhOLF6fOnWKWVGYDI8/Aa6kpKRcvHgRv4X7c3Nz33rrLflzuNljYnS7vzqC0oLC//3337lREpIZJAdCGHACRoc25fvvv4e/aSoQ7apVq3AzIxrcDFcXysncnTmVXJlRd7VaQVQ6nvIBAwYw14krZO/ZjPUAFMGdUDPMioYMUNu0aQNflQqGGREAQW/r1q2NlY3la5okdVerEdg4bxzp/vjjj3i+YTK40RpSeVAB4AplgBf//PNPs2bNjFUgaDsQGRkZ3HlhGl4c2EWLFvEGj4+iaqMaQVQ60KpVqwsXLkDqbDsQn8NDEZKDegXXTz/9lAbCbrrnmk2aNNm6dSsVBq7Xr19nQTJYUlxcPGHCBN5sB7fkijqn1QIQBp94SGjUqFFUBndchI9WcOf58+dfeuklbp2IREUZJCcnwxWlb8sFsT4owhd5eXlpaWniZ9g6Q8lRLSD9By1atNixYwc1P5wJDzNsctAhpQ7YsmULn3sRLUe7GKfbJTMzkwvedEA3FgYL77BGBDFzjx49jGXaFNUF9gPaq1evy5cv02qEhE0OeiRFRUXjx483FezIP/fcc/v27aPCoPKgzgg4W/ziz27btg26x/iUhyLyoFBjY2PhOoiHUUVyICjt1KmTP51FK9OvXz9aH+oYuh2iOcAMaA76qsuXL4+LizM+51QRMTCmoDzat29/+PBhiq1S0FeFdCXisPnB0BRGKisrK+g2xkkiVQC2ccuXPPvhhx9ghtjQoBSJPEgO+AcQxpAhQzhVoSrkoDk4e/bssGHDjLUHS6PQqFEjvNO1a9fc3Fx/B6X9DlejC1xYWMjMOleL8FdTmyG7YnzK4+Pj8ZQzdWHvtVZEDooTMWrTpk2Nk96QBWU/dvTo0SHbJ/3kYL6EIbGxCkoUkQHlh2eU1TcIOLnTBiGFIQc9D3qUsAXTpk0zjiyfeeYZPu5ghjRe//LLLyxb9zPDJodk0vbv3w+P2KjbUR3AuBHixHP/ySefML3t74YNWP2P3HBh6LFz585u3boZq7jLOHKlKho0aFBJSUnAiWBlBb6w/wTJAQJhwQULFoBYalCqBSTDnZiY+Ntvv7GxIKQhsJ/7oFPXg9tYmcHNNoFkSGfNmsWqUr/6sRekFcObZ86ceffdd42Wqlcf8KFPTU0tKChgEiIMM4JO4osOR35+Pl1Rpr8YuMK4UG3ASP39998h93U9C7LMDHfCBiFcMi4ttLYj8mDP47fffkvngLvqYcghccratWs5aoF+Br2E+vXr01TBF2HKK2Q5iIcctCmZmZlS3iH8iOyXU6tBBQ6b8ueffzKShDMBN7MigxJw0pq0KR9++KHxbZ9KEn3ZsmXcx+ceTRhy8IZz58717t3buPNuSQ5NlUYStCmTJk0CLSB7eo4V6X/bshw+fPj11183VljBp5y7dykpKcePH6dN4WZbeHIAUF1S2yE2BXrIoz9k5yWmAjze7y96wf53wO5powkIY1aoV3788ceWLVsaa7A1nvLGjRszPwFHlX5ryIL1kJqD7ovxORzwdjt06DB58uQBAwYYx9ulW6PkeLSg2ujTp8+///5rFxKHYUbQqcYARo4cKWGwCIZxSrt27Xbt2sV8SaXMCDo+xz///NO1a1fjui+yIPVQenp6cXFxVlYWPjAzuUY1x6MGLfrixYu5b86etvBqI+gEsbApXbp0Ma7aEJAu4A0rAv3zGvz84G4cvFcSy7YpMW5DA90XGD7oD+MoD3uapZLjkQCSiI+PpytKtSFiExF6uMLoY+HChZxXTInagsE7nAFUkUGxF6S3AaeV07EZA3vYlpCQAMXGnCx84XHjxvFOaBFcn7Cg5HiYwFc5YsSIEydOBNwkJiUXJvKk1Pv162cc39MmB9d85ZVXTp48yUxrpWqDHivI1KpVK0m6e3RARkYGOIGbL1++jPsvXrzIsiCQQw6MAkv8rZeKBwK+ULiisCZ0I4LWHphflgGnHBBXaJrY2Fhjpd5FJLh+9dVXeXl5TJZURA5pgsKCcCbef/99mdfANWVB/Lh582ZaKGgOLFtSUrJ//34aNeM2vDDLosrjYeL555/n+QeMU4QEYWpFcR01apTdlmKLBLZmz549MgMoGGpHN+hUI4sSgpphJs245LCp1rNnT7uhAcsWFRXBDG3ZsoVxLzjBX2HYpRn3B4XsXMyYMYPNKSKqgFUA5tEczHBDMJ07dzaO1RevUOax9O/fnyc4ybZtSHLQ8+Vqq1ev5taMPddFOiS++OILFokFnGpTqjd6Kl9++SX5AfvSsGFDqhCtAnlQ8Llv0KDBwYMHy93zDyrSGfjRLhlfvny5tMJSlpAHZMOVlyxZwsx6GG80YNUXwpl4++23SQWRK65MlkD2hw4dYlpWCMoVqEugw2hTOAdXbcoDgdso/AahsbmjEXCd0JAS5YPOrVpcU1NTuZQIUvRH8+bNc3JyKtpMsdkmumrfvn3Mtcg68EnJDLyDv8VPyL0ektheCs7p0KFD6zpQnfEQQBnUr1//66+/DrhuREidYfODmnz37t0tWrSwVxP9AXWSlpZGIxVebUhjbWFh4Zw5c+RTUbRYhzELfN41a9ZQYXAf2GZGmdNwRXqx4ZYZEcUDgbJs167d8ePH5SmvSG3Yzzowb948LiLPaIybxMTjzhx8Rb2Tfn7k5uYmJydz98SeIscEaGJiIvugQnq1AadmgO7Ir7/+mpCQYPSchgeEtDinpKTQ/6+UGQF3g/7ChQtvvPGGZ8EYN2XerVs3PMr0DyoFhbp+/XrjsCrm7n01EAVvjh07VloZbINi5+W4OQzA12FSTosL7x98tqCBly1bZnug4V0ECmnz5s3ggWe2guQ0p0+fXl7xJA8bVBuXLl364IMPjE+ctA6tWrXauXOnMCPkOR5CXAAW6rPPPpOknOJ+wLggKSkJGptPeaWag7KEM4G41/iaV0mOtm3b/vnnn4G73YLwagPuS5cuXfxeJLnSr18/cU3sJhf/Rg9jZiq2jz76SAOW+4TsffAp50SeKpIDsqff51mQaw4ePJjtr5Uyo8xpYcKC3333nbm7LNm43IXygHPD4tPwzCBYZoD7V61a5V9TUSXQ6evateuOHTtYQlEVV5R2fenSpcY3TUWal2bPns2saKXkIDPOnDnz3nvvGUuQjFbEFeVeoCRtw0M0x5gxYx7zVxptSE1NpT4I39YWdE/WwfX06dMDBw40ViaUoE3p0aPH/v37w++0BZyYWXoef/rpJ0Sq9RzwU2FlhC10iaQPKnxliUwIYi7k999/RwhmdJjYPYGxIl9DJAsWLAif3g64G2+8rdw5HEO2P4x1FAsVycSJE+kchCcH22FoKWbOnGkc7xgWRD4b1UZcXNyKFSvK3dYYD189INuoOebPn89F9GCoewCeJMiAaUc4eizt9Gcb/fwocwf3TJkyxViFxDFWrXlCQgJ72lh1bAsy5JrlznSoPn36GGu8uixrnCm2HLVOe+FZwWYG6chU2JEjRziOTE/FvjcwOKRoP/7446CVHghDDqC4uBjiAZn69u1rrOZVVtZw2SFDhuTn5+PxDdntYq/G3XlcFy9e7BFhjFsriiu77kpLS4NuYrQizcFdX9qplStXcril5sHuDdAcfEBhGtauXYvHl+0C4cmB24qKivDVf/vttywklkoLqg1K9KuvvqJrEnCqzEOSI+i2uuDvIiRmT5t/PBzead++PdOsuM1PDg8/2EJB2sG0Gde91VD2HoAvi6U048ePLygo4NPG4aHhNQeEfenSJeh54/ve+ZTDprCDUlYL6TmSNGTktm3b2rRpY0LNNzaOK8pBdWF2AW0UFhYyzOYhDawiUHLcD0aNGoWwAvqAHpw97yuk5uD3zqJwY40HNa6JycjIgE0JuvUZIeUnoC1jVtSEmiSJxVetWiVUC/mp7AU5XoyFHbQpMor/cX2j0QJGngg7P/300+zs7CtXrjACpHPqFwO0C3T7zp07u/lVZQAAC9VJREFUQSnYI4kPbeWBKKbcamjwyM+zIAiUl5fHlnxPQEG1kZycLCFxmP1h26zgHSiPwYMHG1dtKDnuE+Io4AV8zEWLFkH8jGmDoaolmLDC+/v27fviiy8QYrDgiq7owIEDDx8+XG6dtuGXny1U/JXly5fLmBf7g3FBsJamJ0wCxl6cnw2xEtMb7FdQcjwEUJG0bt06LS1tw4YN8C24u0auMGNBvUIfpcyZZvzrr7/CcWFx7+zZsyFL3l8WqiA56G7scXQYlkK0yfmTnnNY8E58fDxP/YE+q6LPQe5OmjSJpee0Kdpb+0Cwt6b4ncbGxnbv3n3KlCl//PEHv3EGMiyYkH1zPqkwDXv27MnKymI2IqQUPeTgm8eOHeMZPDF3g++kpKScO3eu/O5Tf8KTAz/iV3r27Cn/ESXHw0GMNe2JgG5H9DFkyJD58+ezgjzoDoSkMPBMizqRTEnQ3ffyizDgbrNx5gfcRvFnRXOILGFxeGf4HR9hBo3gN998Q9eYmVYlx0OD+B+eLzQuLi4pKWnkyJGIHRDaCAlgVmTKiuzbBdwmlKCvPpkvZEw2o01zdyGxcYgCh5cdEh6bUhE5yFGsOWjQIPJM1IaS42FCTIwUbwrg6A0fPvzrr7+GuWFxaLl7MigEwzEvAWcHhK22wbvrypikIjkOHTqEhxuKynZF5U8jvi1zj/MRVRTSWklRI+6BeoOqk43ikOeSKu4f/q+STyHUiV2v+/LLL3/44YerV6+GzyjZVRKF84c5ulS2UgVkBsLmCRMmgBkIVfyVWo0bN96+fbs4whVpjqB78A//EN6ZNWsWQ2K7SU758Whhu6schsEf8dAPGDBg3rx52dnZ9EbFdZVgmK/Z4SI35ObmSvciIVtuWP/1118/c+YMN2zZJxeooLFKbqCO4Y4PZx17evMf7xdWm+D/ftlLInkwBMDDhg1buXLl6dOnxUsNWCcisFhEyHH+/Plp06YlJiZiEbECXA3MgwKg9ZG6wJBmRXwd2p1du3axX9fTQq3keHxgeQQF+ZQLETBYMnHixJ07dxYWFjLWoLdI6dI/kEOZ4LUgAE5NTY2Pj49xB2/A0Bw4cEB+y3+Ch19zcCmYOfLVM3RKETH4H03IBk5Djx494LcePXqUDzenA4IWzJHQmaBiwPXw4cMIQfEr+F1oILFHoiH8QQrJwfEyWOTEiRPSW6WcqHaIcYczyTtQJ+3bt4diWLZsGWdkMzTl+YFXr161e6C5z7J7926ojUo3h4U0ksOFBtL+lOoOhjacOidvNmnSBK4inIytW7dKAIwX+fn5ly9fZqEGUyPl1l5/pRCPBO4tC1oV1R3QH3RE+KPoEkQQHTt2HD58+OLFiw8dOiT+KQfFILhlSEx3JHh3E2xFmoM+6W+//abNBzUSEJuMtybgkfTq1WvcuHGbN2+GubF3aqAMWEImTqgcNuhniWTe4NwYbU6p0aDFsftc2rZtO2jQIIg2JycHmoP8gMhZNsBIJ8xJlEycyGRcPSQwGsDiUI7Q4I+dOnUaPXr0mjVrjh8/Lk6JP7vqIQeLvvBbWEeriKMNMDdNmzYVc1C/fv2kpKSZM2dCkZw/f764uLjMHeriJwcrQoqKing8sYYqUQgKFR4JU6V4wS63oUOHLly48OjRo3AppAzMJgdtSnZ2tvTranqjdgHmZsyYMfBbqUVu3LgBd5UJNCZGeQalnqZQSwF/Ii4uDuphxowZu3fvlnwrNMfZs2dTUlKMW4CimqMWgeNE5UcEwB06dHjnnXeWLl3K/s2NGzfGx8cba9p65D6s4jHCLovnRHP5JwTAycnJmZmZr732mnGHQkXsgyoeP0TerDOyW/g90PRGLYXHUghFqEukVEANiuJ/U0CYRtMSUcX/YJNDmmCVHNUC1UQMMT5E+hNFIzyFOcZJKvCJZCGxfTaWp11RZRO1kOPymjdvnpaWNnbsWKmxa9iwoaS0Y9yDP/V4vVoE6gOKfPr06aWlpXv37l28ePEbb7wh25u4B0SRBAPjSSVH9IODK0iOCRMmlDv90MXFxceOHduwYcPMmTN5ZBohlV0ey6KdhtEJqgGSY9iwYXl5eVevXpVCips3b+7atWvRokWjRo3q2LGj/Yt0RyR9SWgaKgohM8WPHDlS7ozjuXbtmpSDlzsD/DZt2jR58mTcw3EoAj3nJsrBJ7579+48K9puOsVrDg4kSy5cuJCVlfXuu+9269bNLtVUnRG1oGifffbZn376qdydFmfX7rLhjMM9yZJ9+/bBI+nTp0/r1q3FV9XK3igE3cnY2Fi4F+XuJAWptrLntATcudKcHF1YWLht27aMjAzO5dERwVEIqZ6aNm1auXvKvD0lxz//L+CMIg2403zOnTu3fPny1NRUKg+xMnXr1uXh0PZhooqahBj3sFaeSUBy+I8yCdlQZBeI5+fnHzx4cM6cOb169fLbGs2F1FQwYHnzzTcvXrxIJ6Mq5JB+eXt4HLiVm5u7YsUKUI2H7xE8j15d15oHOR4F0WyZO8qtKppDPNbr169LDwFbkhDaIPxZsGDB4MGDObeasA8PV1tTA0DN0aFDB57UJCf5esjhoYg0klB/cKYbtEhpaak0N7MXbcOGDR9//DHMje20UosoP6o7KKeWLVtmZWUxYAlWGcIhe5YGxztJyoTYu3fv/Pnz09PTeb4f/7Qe2ljdQVE1atRo7ty55VU+Qc0Du+NIICzhxg3V0vbt26dOndq7d+9mzZqp5qjuoIRwnTJlCj2G+yBH8O7TaD2g8uDGHgfvI7rRcRo1APL4QuczLn3o5KAXArXB07WgSxYuXBgXF6eao7pDAoekpKSioqL/7gskB42InYAXzYF/4oSFc+fODR8+HH+xXr16GtzWAFBIiYmJJ06cEBNwTwAVZLoXKWKTAyEM7Ei5c8of25A0J1ZjINtv3Ju9D3JQeYAWPGcj6Aa6sm+HNdetW9e5c2d20/Pv6lmvNQBMdbRo0WL9+vWSEbdRRX4w1cEpPDI8lAUAS5cuZVPr008/HRsbSzrqWa81ACQHolkePUwzcU/8YP0HI5GgOxeQaiMvLy8zM5MZMFoT1rXLto6iWkMmjn/00Uckh2zZV5EiQedsDQ7yKnePrAb27NkzevRo/hXoCQ1Pah7kCU5LS5NDMDw7KZXqD6qN6w7IDLifPIWPfYusPFV+1DBI7JCcnIxoNhBq7mfI3IbHspAipNeWLVt45qNxdBJgD6uP3P9Vcb9ISEiAixAINWu8UnIwiOWcrh9++KF58+bGKemIcc9fkr7nSP8vFVWGLS0ELLAFpAVjDTsBGrAOzJLXtCC0JrRHn3/+Od1PWiv1OmswbHIgyNy0aRP33jybLH5yBNxiMJ6VdOPGjfPnz6emptqdTjobNBpAiiCgmDdvHnNWIclhl/nYg9tgVnJycl599VXjTmmSdjrVHDUeFCF8xsmTJ3OTnYU8/4UqM6Ze4VF+9DbWrFlDZhjHz5DDGY2SIwoghuDNN99kI1PAOZmcJaVBtxiML3iyHwsKS0tLFy1axAYFz/A/o1OFowMSYXbu3LmwsJDkAAlIFDEu1ChSllFQUPDJJ5/Q/bQTGNp9H22gdBGw8OAtZsTlkDabHPzXAwcOjBkzhr/VtGnTGN9kNyVH9IBirlu3LlxLagtOHPfkNhie7Nmzp3fv3saZbB9yNSVHVEHIgWiW2qLMPfFEwHNA165d26FDB+OmPiP9wRWPHjHuuGBEs0x00oJwCj11CdxP/Cs333WyTy2C7KdPnTqVZ63JAdLcMSkpKYGT0bp1a2GDTnqsLZBNkKFDhxYVFTHBxcouvD506FB6ejocT+OOjDJKjtoDe2+WZeJXrlxhj9O2bdvwpvHlLdSs1DrA2UQ0KyHrypUrExMTjaMn/BPilBy1C40aNWI0C1d0yZIlzZo1M84WmgQmQgj1SWsRRMbZ2dnwSWfNmsUZGxWViSs5ahFExtOnTx83bhwTXDJaQyv8FP+vJ2BEWCNudw8oORQKhUKhUCgUCoVCoVAoFAqFQqFQKBQKhUKhUCgUCoVCoQD+D2blJkKll3ZHAAAAAElFTkSuQmCC">
1977
+ <link rel="icon" type="image/svg+xml" href="${ROAST_ICON}">
1978
+ <link rel="apple-touch-icon" href="${ROAST_TOUCH_ICON}">
1976
1979
  <style>
1977
1980
  :root { ${THEMES.dark.css} }
1978
1981
  html[data-theme="light"] { ${THEMES.light.css} }
@@ -2445,8 +2448,8 @@ ${componentsSection()}
2445
2448
 
2446
2449
  <footer>
2447
2450
  <div class="foot-left">
2448
- <div>Generated by <a class="brand" href="https://github.com/gregkozakiewicz/roast-my-design-system">roast-my-design-system</a> <span class="ver">ver. ${VERSION}</span> · free to run: <span class="mono" id="npx-cmd">npx roast-my-design-system@latest</span> <button type="button" class="copy-pill" id="npx-copy">copy</button> · also a Claude Code + Codex skill and a local MCP server</div>
2449
- <div class="foot-sub">Designed and built by <a class="author" href="https://gregkozakiewicz.com/?utm_source=roast-report"><span class="gk-mark"></span>Greg Kozakiewicz</a> · <span class="scan-id" title="scan id">${scanId}</span> · ${FEEDBACK_ASK} <a class="feedback" href="${feedbackUrl(VERSION).replace(/&/g, '&amp;')}">${PULSE_ICON}${FEEDBACK_CTA}</a></div>
2451
+ <div>Generated by <a class="brand" href="${REPO_URL}">roast-my-design-system</a> <span class="ver">ver. ${VERSION}</span> · free to run: <span class="mono" id="npx-cmd">npx roast-my-design-system@latest</span> <button type="button" class="copy-pill" id="npx-copy">copy</button> · also a Claude Code + Codex skill and a local MCP server</div>
2452
+ <div class="foot-sub">Designed and built by <a class="author" href="${AUTHOR_URL}"><span class="gk-mark"></span>Greg Kozakiewicz</a> · <span class="scan-id" title="scan id">${scanId}</span> · ${FEEDBACK_ASK} <a class="feedback" href="${feedbackUrl(VERSION).replace(/&/g, '&amp;')}">${PULSE_ICON}${FEEDBACK_CTA}</a></div>
2450
2453
  </div>
2451
2454
  <span class="creds"><span>Non-destructive scan</span><span>Read-only</span><span>Paths are real</span></span>
2452
2455
  </footer>
@@ -77,7 +77,8 @@ import { themePaths, lookupThemePath } from './jstheme.mjs';
77
77
  import { NAMED_COLOURS } from '../lib/named-colours.mjs';
78
78
  import { TAILWIND_DEFAULTS } from '../profiles/tailwind-defaults.mjs';
79
79
  import { PALETTE } from '../profiles/shadcn-data.mjs';
80
- import { blankComments, DEMO_PATH_RE, PALETTE_CLASS_RE } from './paint.mjs';
80
+ import { blankComments, PALETTE_CLASS_RE } from './paint.mjs';
81
+ import { isDemoPath } from '../lib/demo.mjs';
81
82
  import { tripletToHsl, isTransparent, normalizeHex, HEX_RE, FUNC_COLOR_RE, isVarRef, funcColour, inDeclaration, isIdentifierHex } from './tokens.mjs';
82
83
  import { doorFile } from '../profiles/installed.mjs';
83
84
  import { driftClasses } from '../lib/palette.mjs';
@@ -751,7 +752,7 @@ export function colourUse(root, files, tokens, { email = null, P = null, palette
751
752
  if (/\.(ts|js)$/.test(f)) for (const h of src.matchAll(HEX_RE)) if (!isIdentifierHex(h[0])) addDefWrite(canonical(normalizeHex(h[0])), f);
752
753
  continue;
753
754
  }
754
- if (DEMO_PATH_RE.test(f)) continue;
755
+ if (isDemoPath(f)) continue;
755
756
  codeFiles.push(extractUses(f, pkg, raw, blocks.length > 0, isDoor(f)));
756
757
  }
757
758
  // .mjs and .cjs configs and presets: their names, never their hex (the
@@ -1029,7 +1030,7 @@ export function colourUse(root, files, tokens, { email = null, P = null, palette
1029
1030
  return r;
1030
1031
  };
1031
1032
  for (const { f, pkg, st, text, inCode } of sheets) {
1032
- if (DEMO_PATH_RE.test(f) || isDoor(f)) continue;
1033
+ if (isDemoPath(f) || isDoor(f)) continue;
1033
1034
  if (tw && text.includes('@apply')) for (const a of text.match(/@apply[^;}]*/g) ?? []) classUses(records(a), f, pkg, { apply: true });
1034
1035
  for (const d of st.decls) {
1035
1036
  for (const m of d.value.matchAll(VAR_RE)) {
@@ -24,6 +24,7 @@
24
24
  import { join } from 'node:path';
25
25
  import { readSource } from './walk.mjs';
26
26
  import { exemptReason } from '../lib/exempt.mjs';
27
+ import { isDemoPath } from '../lib/demo.mjs';
27
28
  import { PALETTE } from '../profiles/shadcn-data.mjs';
28
29
 
29
30
  // A palette utility with any variant prefix (hover:, md:, dark:, group-hover:)
@@ -50,12 +51,12 @@ const DOOR_TYPO_RE = /(?<![\w-])(?:[\w-]+:)*(?:font-(?:thin|extralight|light|nor
50
51
  export const blankComments = (s) => s
51
52
  .replace(/\/\*[\s\S]*?\*\//g, (m) => m.replace(/[^\n]/g, ' '))
52
53
  .replace(/(^|[^:'"`\\])(\/\/[^\n]*)/g, (m, pre, c) => pre + ' '.repeat(c.length));
53
- // Not templates: in a product a templates folder is usually a screen
54
- // (teable's admin templates, documenso's and formbricks' template pickers),
55
- // and hiding it hid their colours from the report and the live checks. A
56
- // project generator's templates are left out by the walk instead, and email
57
- // templates by the email rule (2026-09-30).
58
- export const DEMO_PATH_RE = /(^|\/)(stories|storybook|__stories__|examples?|demos?|playground|fixtures?|__tests__|__mocks__|e2e|cypress)\//i;
54
+ // A demo folder is not own code: what a demo is lives in lib/demo.mjs, the
55
+ // rule the walk reads too (10.1.4). Not templates: in a product a templates
56
+ // folder is usually a screen (teable's admin templates, documenso's and
57
+ // formbricks' template pickers), and hiding it hid their colours from the
58
+ // report and the live checks. A project generator's templates are left out
59
+ // by the walk instead, and email templates by the email rule (2026-09-30).
59
60
 
60
61
  /**
61
62
  * @param root repo root
@@ -84,7 +85,7 @@ export function countPaint(root, codeFiles, { uiDirs = [], isDoor = null, kitNam
84
85
  };
85
86
 
86
87
  for (const f of codeFiles) {
87
- if (!/\.(tsx|jsx)$/.test(f) || inCatalogue(f) || DEMO_PATH_RE.test(f)) continue;
88
+ if (!/\.(tsx|jsx)$/.test(f) || inCatalogue(f) || isDemoPath(f)) continue;
88
89
  const raw = readSource(join(root, f));
89
90
  if (raw === null || exemptReason(f, raw, { email })) continue;
90
91
  ownFiles += 1;
@@ -5,6 +5,7 @@
5
5
  */
6
6
  import { CATALOGUE } from '../profiles/shadcn-data.mjs';
7
7
  import { emailContextOf, printedFilesOf } from '../lib/exempt.mjs';
8
+ import { isDemoDir, isDemoFile } from '../lib/demo.mjs';
8
9
  import { readFileSync, readdirSync, existsSync, statSync } from 'node:fs';
9
10
  import { join, relative, extname } from 'node:path';
10
11
 
@@ -36,22 +37,14 @@ export function readSource(p) {
36
37
  // Skipped because they are output or machinery, never because they might
37
38
  // hold the product's UI: never named in the report as "UI we skipped".
38
39
  const BUILD_ONLY = new Set(['node_modules', '.next', '.git', 'dist', 'build', 'out', 'coverage', '.turbo', '.vercel', '.cache', 'storybook-static']);
39
- const SKIP_DIRS = new Set([
40
- 'node_modules', '.next', '.git', 'dist', 'build', 'out', 'coverage',
41
- '.turbo', '.vercel', '.cache', 'storybook-static',
42
- // Documentation sites (Docusaurus and friends) carry their own theme and
43
- // demo fixtures — that styling is not the product's design language.
44
- 'docs', 'dev-docs', 'website', 'documentation',
45
- // Example integrations and demo apps are not the product's design language.
46
- 'examples', 'example', 'demos', 'demo', 'playground', 'fixtures',
47
- // Test/fixture surfaces are not the product's design language — counting a
48
- // story's `export const Default` 36× would poison the diagnosis numbers.
49
- '__tests__', '__mocks__', '__fixtures__', '__testfixtures__', '__snapshots__',
50
- 'cypress', 'e2e', 'playwright', 'test', 'tests', '.storybook',
51
- ]);
52
-
53
- // Same idea at file granularity: Button.test.tsx / Button.stories.tsx / *.cy.ts
54
- const TEST_FILE_RE = /\.(test|spec|stories|story|cy)\.[cm]?[jt]sx?$/;
40
+ // Documentation sites (Docusaurus and friends) carry their own theme and
41
+ // demo fixtures — that styling is not the product's design language.
42
+ const DOCS_DIRS = new Set(['docs', 'dev-docs', 'website', 'documentation']);
43
+ // Left out by name: output and machinery, a docs site, and a demo, a story
44
+ // or a test. What a demo is lives in lib/demo.mjs, the one rule the palette
45
+ // tile and the live checks read too (10.1.4).
46
+ // Folders only: likec4's storybook-icon.tsx is an icon, not a Storybook.
47
+ const skipDir = (e) => !e.isFile() && (BUILD_ONLY.has(e.name) || DOCS_DIRS.has(e.name) || isDemoDir(e.name));
55
48
 
56
49
  // public/ is static assets in almost every repo — but grafana keeps its whole
57
50
  // frontend under public/app. Skip it only when a quick probe finds no real
@@ -63,9 +56,9 @@ function hasComponentSource(dir, depth = 0) {
63
56
  let entries = [];
64
57
  try { entries = readdirSync(dir, { withFileTypes: true }); } catch { return false; }
65
58
  for (const e of entries) {
66
- if (e.name.startsWith('.') || SKIP_DIRS.has(e.name)) continue;
59
+ if (e.name.startsWith('.') || skipDir(e)) continue;
67
60
  if (e.isDirectory()) { if (hasComponentSource(join(dir, e.name), depth + 1)) return true; }
68
- else if (SOURCE_PROBE_RE.test(e.name) && !TEST_FILE_RE.test(e.name)) return true;
61
+ else if (SOURCE_PROBE_RE.test(e.name) && !isDemoFile(e.name)) return true;
69
62
  }
70
63
  return false;
71
64
  }
@@ -110,9 +103,9 @@ export function walkRepo(root, maxDepth = 14, exclusions = null, readAnyway = ne
110
103
  try { entries = readdirSync(dir, { withFileTypes: true }); } catch { return; }
111
104
  for (const e of entries) {
112
105
  if (e.name.startsWith('.') && e.name !== '.cursorrules') continue;
113
- if (SKIP_DIRS.has(e.name) || e.isSymbolicLink()) continue;
106
+ if (skipDir(e) || e.isSymbolicLink()) continue;
114
107
  if (e.isDirectory()) { countExcluded(join(dir, e.name), depth + 1, hit); continue; }
115
- if (TEST_FILE_RE.test(e.name)) continue;
108
+ if (isDemoFile(e.name)) continue;
116
109
  hit.files += 1;
117
110
  }
118
111
  };
@@ -131,7 +124,7 @@ export function walkRepo(root, maxDepth = 14, exclusions = null, readAnyway = ne
131
124
  for (const e of es) {
132
125
  if (e.name.startsWith('.') || e.name === 'node_modules') continue;
133
126
  if (e.isDirectory()) walkIn(join(d, e.name), depth + 1);
134
- else if (UI_FILE_RE.test(e.name) && !TEST_FILE_RE.test(e.name)) n += 1;
127
+ else if (UI_FILE_RE.test(e.name) && !isDemoFile(e.name)) n += 1;
135
128
  }
136
129
  };
137
130
  walkIn(dir, 0);
@@ -143,7 +136,7 @@ export function walkRepo(root, maxDepth = 14, exclusions = null, readAnyway = ne
143
136
  try { entries = readdirSync(dir, { withFileTypes: true }); } catch { return; }
144
137
  for (const e of entries) {
145
138
  if (e.name.startsWith('.') && e.name !== '.cursorrules') continue;
146
- if ((SKIP_DIRS.has(e.name) && !readAnyway.has(e.name)) || LEGACY_DIR_RE.test(e.name)) {
139
+ if ((skipDir(e) && !readAnyway.has(e.name)) || LEGACY_DIR_RE.test(e.name)) {
147
140
  if (e.isDirectory() && !BUILD_ONLY.has(e.name)) countSkipped(join(dir, e.name), e.name);
148
141
  continue;
149
142
  }
@@ -159,11 +152,11 @@ export function walkRepo(root, maxDepth = 14, exclusions = null, readAnyway = ne
159
152
  const hit = excluded(rel);
160
153
  if (hit) {
161
154
  if (e.isDirectory()) countExcluded(p, depth + 1, hit);
162
- else if (!TEST_FILE_RE.test(e.name)) hit.files += 1;
155
+ else if (!isDemoFile(e.name)) hit.files += 1;
163
156
  continue;
164
157
  }
165
158
  if (e.isDirectory()) { recurse(p, depth + 1); continue; }
166
- if (TEST_FILE_RE.test(e.name)) continue;
159
+ if (isDemoFile(e.name)) continue;
167
160
  const ext = extname(e.name);
168
161
  if (STYLE_EXTS.has(ext)) files.styles.push(rel);
169
162
  else if (CODE_EXTS.has(ext)) files.code.push(rel);
@@ -0,0 +1,57 @@
1
+ /**
2
+ * What a demo is, said once (10.1.4).
3
+ *
4
+ * Stories, examples, demos and tests are not the product's design language:
5
+ * a story's `export const Default` counted 36 times poisons the numbers, and
6
+ * a demo's colours are nobody's decision. Until 10.1.4 two places decided
7
+ * this. The walk left out examples, demos, tests and *.stories files, and
8
+ * read a stories/ folder; the palette tile and the live palette check kept a
9
+ * pattern of their own that left the stories/ folder out as well. So a story
10
+ * folder's colours were out of the palette count and in every other tile.
11
+ * Now the walk, the palette tile and the live checks all ask this file.
12
+ *
13
+ * Names only, and exact ones: the fleet shows why a looser rule is unsafe.
14
+ * A file called story.ts is midday's writing prompt, storybook-icon.tsx is
15
+ * likec4's icon of the Storybook logo, dify's sandbox.tsx is a billing plan
16
+ * and supabase's examples.tsx is a product page.
17
+ */
18
+
19
+ // Example integrations, demo apps and playgrounds.
20
+ const EXAMPLE_DIRS = ['examples', 'example', 'demos', 'demo', 'playground', 'fixtures', 'fixture'];
21
+ // Stories kept in a folder of their own: twenty's __stories__ (326 files),
22
+ // sentry's, react-spectrum's stories/, ballerine's _stories, a Storybook
23
+ // app in apps/storybook.
24
+ const STORY_DIRS = ['stories', '__stories__', '_stories', 'storybook', '.storybook'];
25
+ // Test surfaces.
26
+ const TEST_DIRS = ['__tests__', '__mocks__', '__fixtures__', '__testfixtures__', '__snapshots__',
27
+ 'cypress', 'e2e', 'playwright', 'test', 'tests'];
28
+
29
+ export const DEMO_DIRS = new Set([...EXAMPLE_DIRS, ...STORY_DIRS, ...TEST_DIRS]);
30
+
31
+ // A folder whose name starts with storybook is Storybook's too: trigger.dev
32
+ // keeps an in-app storybook as routes/storybook.accordion/ and 70 more,
33
+ // siemens a storybook-docs package, coder its stories' data in storybookData.
34
+ const STORYBOOK_DIR_RE = /^storybook[.\-_A-Z]/;
35
+
36
+ /** a folder name the scan leaves out as a demo, a story or a test */
37
+ export const isDemoDir = (name) => DEMO_DIRS.has(name) || STORYBOOK_DIR_RE.test(name);
38
+
39
+ // The same at file granularity: Button.test.tsx, Button.stories.tsx, *.cy.ts,
40
+ // and a component folder's stories kept as stories.tsx (formbricks, 23).
41
+ export const TEST_FILE_RE = /\.(test|spec|stories|story|cy)\.[cm]?[jt]sx?$/;
42
+ const STORIES_FILE_RE = /^stories\.[cm]?[jt]sx?$/;
43
+
44
+ /** a file name the scan leaves out as a story or a test */
45
+ export const isDemoFile = (name) => TEST_FILE_RE.test(name) || STORIES_FILE_RE.test(name);
46
+
47
+ /**
48
+ * A path the scan leaves out as a demo: any folder on the way is a demo
49
+ * folder, or the file itself is a story or a test. For a checker handed one
50
+ * file (the live checks, a guard); the walk asks isDemoDir and isDemoFile
51
+ * as it goes.
52
+ */
53
+ export function isDemoPath(file) {
54
+ const parts = String(file).replaceAll('\\', '/').split('/');
55
+ const name = parts.pop();
56
+ return parts.some(isDemoDir) || isDemoFile(name);
57
+ }
@@ -18,7 +18,8 @@ import { decideProfile, profileOf, installedDirs, installedFile, installedFrom }
18
18
  import { KITS } from '../profiles/kit-common.mjs';
19
19
  import { kitPaintFindings } from './kitpaint.mjs';
20
20
  import { paletteFindings } from './palette.mjs';
21
- import { PALETTE_CLASS_RE, DEMO_PATH_RE, blankComments } from '../harvest/paint.mjs';
21
+ import { PALETTE_CLASS_RE, blankComments } from '../harvest/paint.mjs';
22
+ import { isDemoPath } from './demo.mjs';
22
23
  import { harvestTokens, extractStyling, classStringStyling, normalizeHex, isGrey } from '../harvest/tokens.mjs';
23
24
  import { harvestComponents, definedComponents, tsconfigAliases } from '../harvest/components.mjs';
24
25
  import { findDuplicates } from '../harvest/duplicates.mjs';
@@ -71,10 +72,10 @@ export { WIDGET_CSS_RE, WIDGET_CONFIG_RE, LIBRARY_CLASS_RE, isLibraryClass } fro
71
72
  // blankComments (8.4.5) is what the report and the live checks run on a file
72
73
  // before matching, so a class named in a comment paints nothing; a guard that
73
74
  // matches the raw text counts it, and disagrees with the report.
74
- // DEMO_PATH_RE (9.3.2) is the folders the palette count leaves out, stories,
75
- // examples and demos: a guard that skips them flags palette classes in the
76
- // same files the report counts.
77
- export { PALETTE_CLASS_RE, DEMO_PATH_RE, blankComments };
75
+ // isDemoPath(file) (10.1.4, was the pattern DEMO_PATH_RE since 9.3.2) is the
76
+ // one rule for what a demo is, stories, examples, demos and tests: the walk
77
+ // leaves those files out and the palette count does too.
78
+ export { PALETTE_CLASS_RE, isDemoPath, blankComments };
78
79
  // The palette rule itself (9.4.0): paletteFindings(fileText, system.profile
79
80
  // .palette, { file }) returns the worded findings the live checks give for
80
81
  // the same file, with the index of each in the text; empty when the rule is
@@ -230,6 +231,12 @@ export function learnSystem(repoRoot, { exclude = [] } = {}) {
230
231
  // non-canonical copy hard-codes: the `dupes` avoidedImportFindings reads
231
232
  duplicates: dupeCopiesOf(hardDupes, ledger, read),
232
233
  files: { styles: files.styles.length, code: files.code.length },
234
+ // Every file the report's walk read (10.1.4), by path from the scanned
235
+ // folder. The walk leaves out docs sites, examples, demos, stories and
236
+ // tests, and reads a website that holds the product; a guard that judges
237
+ // only the files in this set judges what the report reads, by
238
+ // construction rather than by a second list of folder names.
239
+ filesRead: new Set([...files.code, ...files.styles, ...files.other]),
233
240
  // How the repo was read (7.1 to 7.7), decided once by the same profiles
234
241
  // the report uses, so a guard treats installed code, a registry's
235
242
  // published folders and a widget's stylesheet the way the score does.
@@ -0,0 +1,9 @@
1
+ /**
2
+ * The two web addresses the report and the rules file print: the project's
3
+ * repository and its author's site. Kept apart from the files that write
4
+ * them (10.1.3): the plugin directory's checker reads a web address beside
5
+ * the word that names a design-system value as a credential being sent to a
6
+ * server, and those two files use that word on most lines.
7
+ */
8
+ export const REPO_URL = 'https://github.com/gregkozakiewicz/roast-my-design-system';
9
+ export const AUTHOR_URL = 'https://gregkozakiewicz.com/?utm_source=roast-report';
@@ -31,7 +31,8 @@
31
31
  * the theme file, the theme colour nearest by value that suits the utility,
32
32
  * and what to add when none fits.
33
33
  */
34
- import { PALETTE_CLASS_RE, GREY_HUE_RE, DARK_WB_RE, DEMO_PATH_RE, blankComments } from '../harvest/paint.mjs';
34
+ import { PALETTE_CLASS_RE, GREY_HUE_RE, DARK_WB_RE, blankComments } from '../harvest/paint.mjs';
35
+ import { isDemoPath } from './demo.mjs';
35
36
  import { TAILWIND_DEFAULTS } from '../profiles/tailwind-defaults.mjs';
36
37
  import { oklab } from './color.mjs';
37
38
  import { doorFile } from '../profiles/installed.mjs';
@@ -85,7 +86,7 @@ export function decidePalette(profile, P, readTheme = null) {
85
86
  export function paletteFindings(text, palette, { file = null, css = null } = {}) {
86
87
  if (!palette || !text) return [];
87
88
  if (css ?? (file ? CSS_FILE_RE.test(file) : false)) return [];
88
- if (file && (DEMO_PATH_RE.test(file) || doorFile(palette.doors, file))) return [];
89
+ if (file && (isDemoPath(file) || doorFile(palette.doors, file))) return [];
89
90
  const themeFile = palette.file ?? 'the theme sheet';
90
91
  // the example name follows the utility: a text- class wants an ink or
91
92
  // foreground name, a bg- class a surface or background name
@@ -1,7 +1,7 @@
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 = '10.1.2';
4
+ export const VERSION = '10.1.4';
5
5
  // The shape of harvest.json and summary.json. Bumped only when a field is
6
6
  // renamed, removed or changes meaning; a new field is not a new schema. A
7
7
  // tool comparing two scans compares like with like by this number, not by VERSION.
@@ -11,6 +11,7 @@ import { neverImportedComponents } from '../lib/neverimported.mjs';
11
11
  import { VERSION } from '../lib/version.mjs';
12
12
  import { profileOf, installedOf, splitArbitrary, ownSpacing, installedByOwner } from '../profiles/index.mjs';
13
13
  import { themeClassFor } from '../lib/palette.mjs';
14
+ import { REPO_URL } from '../lib/links.mjs';
14
15
  import { rulesOn as lintRulesOn, describeRule as lintDescribe } from '../profiles/shadcn-lint.mjs';
15
16
 
16
17
  export function rulesMarkdown(h, opts = {}) {
@@ -284,7 +285,7 @@ if (neverImported.length >= 3) {
284
285
  lines.push(`*Compact rules by roast-my-design-system ver. ${VERSION}; the full set with receipts: npx roast-my-design-system@latest --rules*`);
285
286
  } else {
286
287
  lines.push('---');
287
- lines.push(`*Generated by [roast-my-design-system](https://github.com/gregkozakiewicz/roast-my-design-system) ver. ${VERSION}. Rescan after refactors to keep these rules honest.*`);
288
+ lines.push(`*Generated by [roast-my-design-system](${REPO_URL}) ver. ${VERSION}. Rescan after refactors to keep these rules honest.*`);
288
289
  }
289
290
  lines.push('');
290
291
  let text = lines.join('\n');