roast-my-design-system 7.2.0 → 7.3.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 +5 -3
- package/package.json +1 -1
- package/skills/roast-my-design-system/scripts/benchmark/benchmark.json +878 -0
- package/skills/roast-my-design-system/scripts/diagnose/index.mjs +13 -9
- package/skills/roast-my-design-system/scripts/diagnose/score.mjs +13 -16
- package/skills/roast-my-design-system/scripts/harvest/index.mjs +11 -2
- package/skills/roast-my-design-system/scripts/lib/version.mjs +1 -1
- package/skills/roast-my-design-system/scripts/profiles/index.mjs +0 -10
- package/skills/roast-my-design-system/scripts/profiles/shadcn-data.mjs +9 -0
- package/skills/roast-my-design-system/scripts/profiles/shadcn.mjs +17 -17
- package/skills/roast-my-design-system/scripts/rules/build.mjs +1 -0
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<img src="https://raw.githubusercontent.com/gregkozakiewicz/roast-my-design-system/main/assets/roaster_logo_300px.png?v=7.
|
|
1
|
+
<img src="https://raw.githubusercontent.com/gregkozakiewicz/roast-my-design-system/main/assets/roaster_logo_300px.png?v=7.3.0" width="72" alt="roast-my-design-system">
|
|
2
2
|
|
|
3
3
|
# roast-my-design-system
|
|
4
4
|
|
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
|
|
11
11
|
A free CLI tool (and Claude Code skill) that roasts your repo's design system with real data, then generates the rules that keep your AI agent on-system.
|
|
12
12
|
|
|
13
|
+
> **New in 7.3: shadcn repos are compared with shadcn repos.** The benchmark carries a shadcn slice, 17 of the 34 fleet repos, and on a shadcn repo every fleet line reads against it: "Avg shadcn repo", "cleaner than 60% of shadcn repos". Ideals and the reputable-systems line stay the same for everyone.
|
|
14
|
+
|
|
13
15
|
> **New in 7.2: shadcn repos are read as shadcn repos.** The scanner finds the installed catalogue where it actually lives, reads the theme file `components.json` names, and prints how it decided under the report header. 2 new tiles appear on shadcn repos only, both from shadcn's own rules for agents: palette colours in your own code where a theme row exists, and kit components repainted through `className`. Counted per 100 of your own files, never inside the catalogue. The rules file gains a shadcn section. Scores on other repos do not move.
|
|
14
16
|
|
|
15
17
|
> **New in 7.0.** The scoring code is now a separate file that other tools can import, so a CI check gets the same score as the report. Every `summary.json` now records its schema version and which benchmark it was scored against. Five counting bugs are fixed: a bracket spacing class like `p-[13px]` was counted twice, a hex colour inside a CSS comment was counted, and three smaller ones. Most repos lose a few counts. shadcn/ui goes from 325 colours to 323 and keeps its score of 65. Large monorepos scan about ten times faster. If you use the score as a CI threshold, run a fresh scan before comparing. If you read the JSON, a tile's `value` is now a number and the printed text is in `display`.
|
|
@@ -75,11 +77,11 @@ Not to be confused with each other: **"Why this matters"** is generic, ships wit
|
|
|
75
77
|
|
|
76
78
|
The full report for vercel/ai-chatbot, top to bottom, including "What the numbers mean", Claude's read of the scan, embedded right under the verdict:
|
|
77
79
|
|
|
78
|
-

|
|
79
81
|
|
|
80
82
|
The same report in light mode (one file, built-in toggle):
|
|
81
83
|
|
|
82
|
-

|
|
83
85
|
|
|
84
86
|
## What makes the numbers trustworthy
|
|
85
87
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "roast-my-design-system",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.3.0",
|
|
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 34 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": [
|
|
@@ -85,6 +85,14 @@
|
|
|
85
85
|
"opacities": {
|
|
86
86
|
"value": 5,
|
|
87
87
|
"note": "disabled, overlay, hover tints"
|
|
88
|
+
},
|
|
89
|
+
"paintTin": {
|
|
90
|
+
"value": 25,
|
|
91
|
+
"note": "palette colours in own code where a theme variable exists, per 100 files"
|
|
92
|
+
},
|
|
93
|
+
"doorOverrides": {
|
|
94
|
+
"value": 15,
|
|
95
|
+
"note": "shadcn components given a colour or font through className, per 100 files"
|
|
88
96
|
}
|
|
89
97
|
},
|
|
90
98
|
"stats": {
|
|
@@ -1841,5 +1849,875 @@
|
|
|
1841
1849
|
}
|
|
1842
1850
|
}
|
|
1843
1851
|
]
|
|
1852
|
+
},
|
|
1853
|
+
"slices": {
|
|
1854
|
+
"shadcn": {
|
|
1855
|
+
"builtAt": "2026-09-12T00:06:00.881Z",
|
|
1856
|
+
"repoCount": 17,
|
|
1857
|
+
"note": "every repo in the fleet the scanner reads as shadcn; the \"cleaner than\" line on a shadcn repo reads against these",
|
|
1858
|
+
"stats": {
|
|
1859
|
+
"colors": {
|
|
1860
|
+
"values": [
|
|
1861
|
+
8,
|
|
1862
|
+
12,
|
|
1863
|
+
17,
|
|
1864
|
+
31,
|
|
1865
|
+
41,
|
|
1866
|
+
56,
|
|
1867
|
+
56,
|
|
1868
|
+
66,
|
|
1869
|
+
115,
|
|
1870
|
+
118,
|
|
1871
|
+
132,
|
|
1872
|
+
174,
|
|
1873
|
+
187,
|
|
1874
|
+
254,
|
|
1875
|
+
265,
|
|
1876
|
+
310,
|
|
1877
|
+
1030
|
|
1878
|
+
],
|
|
1879
|
+
"p25": 41,
|
|
1880
|
+
"median": 115,
|
|
1881
|
+
"p75": 187,
|
|
1882
|
+
"p90": 283
|
|
1883
|
+
},
|
|
1884
|
+
"greys": {
|
|
1885
|
+
"values": [
|
|
1886
|
+
0,
|
|
1887
|
+
0,
|
|
1888
|
+
3,
|
|
1889
|
+
3,
|
|
1890
|
+
4,
|
|
1891
|
+
6,
|
|
1892
|
+
9,
|
|
1893
|
+
12,
|
|
1894
|
+
14,
|
|
1895
|
+
16,
|
|
1896
|
+
22,
|
|
1897
|
+
33,
|
|
1898
|
+
38,
|
|
1899
|
+
51,
|
|
1900
|
+
52,
|
|
1901
|
+
66,
|
|
1902
|
+
111
|
|
1903
|
+
],
|
|
1904
|
+
"p25": 4,
|
|
1905
|
+
"median": 14,
|
|
1906
|
+
"p75": 38,
|
|
1907
|
+
"p90": 58
|
|
1908
|
+
},
|
|
1909
|
+
"spacing": {
|
|
1910
|
+
"values": [
|
|
1911
|
+
5,
|
|
1912
|
+
6,
|
|
1913
|
+
10,
|
|
1914
|
+
10,
|
|
1915
|
+
11,
|
|
1916
|
+
14,
|
|
1917
|
+
21,
|
|
1918
|
+
25,
|
|
1919
|
+
32,
|
|
1920
|
+
34,
|
|
1921
|
+
40,
|
|
1922
|
+
45,
|
|
1923
|
+
49,
|
|
1924
|
+
59,
|
|
1925
|
+
66,
|
|
1926
|
+
120,
|
|
1927
|
+
220
|
|
1928
|
+
],
|
|
1929
|
+
"p25": 11,
|
|
1930
|
+
"median": 32,
|
|
1931
|
+
"p75": 49,
|
|
1932
|
+
"p90": 88
|
|
1933
|
+
},
|
|
1934
|
+
"typefaces": {
|
|
1935
|
+
"values": [
|
|
1936
|
+
0,
|
|
1937
|
+
1,
|
|
1938
|
+
1,
|
|
1939
|
+
2,
|
|
1940
|
+
2,
|
|
1941
|
+
2,
|
|
1942
|
+
3,
|
|
1943
|
+
3,
|
|
1944
|
+
3,
|
|
1945
|
+
3,
|
|
1946
|
+
3,
|
|
1947
|
+
3,
|
|
1948
|
+
3,
|
|
1949
|
+
5,
|
|
1950
|
+
6,
|
|
1951
|
+
8,
|
|
1952
|
+
19
|
|
1953
|
+
],
|
|
1954
|
+
"p25": 2,
|
|
1955
|
+
"median": 3,
|
|
1956
|
+
"p75": 3,
|
|
1957
|
+
"p90": 7
|
|
1958
|
+
},
|
|
1959
|
+
"fontSizes": {
|
|
1960
|
+
"values": [
|
|
1961
|
+
5,
|
|
1962
|
+
9,
|
|
1963
|
+
12,
|
|
1964
|
+
12,
|
|
1965
|
+
12,
|
|
1966
|
+
13,
|
|
1967
|
+
13,
|
|
1968
|
+
20,
|
|
1969
|
+
21,
|
|
1970
|
+
24,
|
|
1971
|
+
27,
|
|
1972
|
+
27,
|
|
1973
|
+
42,
|
|
1974
|
+
43,
|
|
1975
|
+
49,
|
|
1976
|
+
49,
|
|
1977
|
+
79
|
|
1978
|
+
],
|
|
1979
|
+
"p25": 12,
|
|
1980
|
+
"median": 21,
|
|
1981
|
+
"p75": 42,
|
|
1982
|
+
"p90": 49
|
|
1983
|
+
},
|
|
1984
|
+
"radii": {
|
|
1985
|
+
"values": [
|
|
1986
|
+
7,
|
|
1987
|
+
8,
|
|
1988
|
+
9,
|
|
1989
|
+
11,
|
|
1990
|
+
11,
|
|
1991
|
+
12,
|
|
1992
|
+
13,
|
|
1993
|
+
14,
|
|
1994
|
+
14,
|
|
1995
|
+
19,
|
|
1996
|
+
20,
|
|
1997
|
+
20,
|
|
1998
|
+
21,
|
|
1999
|
+
26,
|
|
2000
|
+
28,
|
|
2001
|
+
34,
|
|
2002
|
+
50
|
|
2003
|
+
],
|
|
2004
|
+
"p25": 11,
|
|
2005
|
+
"median": 14,
|
|
2006
|
+
"p75": 21,
|
|
2007
|
+
"p90": 30
|
|
2008
|
+
},
|
|
2009
|
+
"shadows": {
|
|
2010
|
+
"values": [
|
|
2011
|
+
0,
|
|
2012
|
+
0,
|
|
2013
|
+
0,
|
|
2014
|
+
0,
|
|
2015
|
+
0,
|
|
2016
|
+
0,
|
|
2017
|
+
0,
|
|
2018
|
+
0,
|
|
2019
|
+
1,
|
|
2020
|
+
1,
|
|
2021
|
+
2,
|
|
2022
|
+
2,
|
|
2023
|
+
4,
|
|
2024
|
+
6,
|
|
2025
|
+
10,
|
|
2026
|
+
10,
|
|
2027
|
+
14
|
|
2028
|
+
],
|
|
2029
|
+
"p25": 0,
|
|
2030
|
+
"median": 1,
|
|
2031
|
+
"p75": 4,
|
|
2032
|
+
"p90": 10
|
|
2033
|
+
},
|
|
2034
|
+
"exactDuplicates": {
|
|
2035
|
+
"values": [
|
|
2036
|
+
0,
|
|
2037
|
+
1,
|
|
2038
|
+
1,
|
|
2039
|
+
1,
|
|
2040
|
+
14,
|
|
2041
|
+
16,
|
|
2042
|
+
17,
|
|
2043
|
+
20,
|
|
2044
|
+
21,
|
|
2045
|
+
23,
|
|
2046
|
+
25,
|
|
2047
|
+
61,
|
|
2048
|
+
65,
|
|
2049
|
+
102,
|
|
2050
|
+
104,
|
|
2051
|
+
134,
|
|
2052
|
+
288
|
|
2053
|
+
],
|
|
2054
|
+
"p25": 14,
|
|
2055
|
+
"median": 21,
|
|
2056
|
+
"p75": 65,
|
|
2057
|
+
"p90": 116
|
|
2058
|
+
},
|
|
2059
|
+
"inlineStyles": {
|
|
2060
|
+
"values": [
|
|
2061
|
+
0,
|
|
2062
|
+
2,
|
|
2063
|
+
5,
|
|
2064
|
+
7,
|
|
2065
|
+
11,
|
|
2066
|
+
22,
|
|
2067
|
+
24,
|
|
2068
|
+
42,
|
|
2069
|
+
47,
|
|
2070
|
+
47,
|
|
2071
|
+
49,
|
|
2072
|
+
58,
|
|
2073
|
+
74,
|
|
2074
|
+
79,
|
|
2075
|
+
117,
|
|
2076
|
+
120,
|
|
2077
|
+
263
|
|
2078
|
+
],
|
|
2079
|
+
"p25": 11,
|
|
2080
|
+
"median": 47,
|
|
2081
|
+
"p75": 74,
|
|
2082
|
+
"p90": 118
|
|
2083
|
+
},
|
|
2084
|
+
"arbitrary": {
|
|
2085
|
+
"values": [
|
|
2086
|
+
12,
|
|
2087
|
+
38,
|
|
2088
|
+
54,
|
|
2089
|
+
100,
|
|
2090
|
+
101,
|
|
2091
|
+
126,
|
|
2092
|
+
128,
|
|
2093
|
+
163,
|
|
2094
|
+
169,
|
|
2095
|
+
245,
|
|
2096
|
+
257,
|
|
2097
|
+
385,
|
|
2098
|
+
386,
|
|
2099
|
+
614,
|
|
2100
|
+
622,
|
|
2101
|
+
1625,
|
|
2102
|
+
1673
|
|
2103
|
+
],
|
|
2104
|
+
"p25": 101,
|
|
2105
|
+
"median": 169,
|
|
2106
|
+
"p75": 386,
|
|
2107
|
+
"p90": 1023
|
|
2108
|
+
},
|
|
2109
|
+
"nearPairs": {
|
|
2110
|
+
"values": [
|
|
2111
|
+
0,
|
|
2112
|
+
0,
|
|
2113
|
+
0,
|
|
2114
|
+
0,
|
|
2115
|
+
1,
|
|
2116
|
+
2,
|
|
2117
|
+
7,
|
|
2118
|
+
8,
|
|
2119
|
+
10,
|
|
2120
|
+
11,
|
|
2121
|
+
15,
|
|
2122
|
+
16,
|
|
2123
|
+
18,
|
|
2124
|
+
18,
|
|
2125
|
+
21,
|
|
2126
|
+
21,
|
|
2127
|
+
59
|
|
2128
|
+
],
|
|
2129
|
+
"p25": 1,
|
|
2130
|
+
"median": 10,
|
|
2131
|
+
"p75": 18,
|
|
2132
|
+
"p90": 21
|
|
2133
|
+
},
|
|
2134
|
+
"important": {
|
|
2135
|
+
"values": [
|
|
2136
|
+
0,
|
|
2137
|
+
0,
|
|
2138
|
+
0,
|
|
2139
|
+
0,
|
|
2140
|
+
0,
|
|
2141
|
+
0,
|
|
2142
|
+
1,
|
|
2143
|
+
1,
|
|
2144
|
+
2,
|
|
2145
|
+
9,
|
|
2146
|
+
9,
|
|
2147
|
+
12,
|
|
2148
|
+
42,
|
|
2149
|
+
42,
|
|
2150
|
+
55,
|
|
2151
|
+
93,
|
|
2152
|
+
157
|
|
2153
|
+
],
|
|
2154
|
+
"p25": 0,
|
|
2155
|
+
"median": 2,
|
|
2156
|
+
"p75": 42,
|
|
2157
|
+
"p90": 70
|
|
2158
|
+
},
|
|
2159
|
+
"neverImported": {
|
|
2160
|
+
"values": [
|
|
2161
|
+
0,
|
|
2162
|
+
0,
|
|
2163
|
+
0,
|
|
2164
|
+
0,
|
|
2165
|
+
1,
|
|
2166
|
+
2,
|
|
2167
|
+
5,
|
|
2168
|
+
13,
|
|
2169
|
+
16,
|
|
2170
|
+
19,
|
|
2171
|
+
23,
|
|
2172
|
+
26,
|
|
2173
|
+
30,
|
|
2174
|
+
44,
|
|
2175
|
+
94,
|
|
2176
|
+
99,
|
|
2177
|
+
100
|
|
2178
|
+
],
|
|
2179
|
+
"p25": 1,
|
|
2180
|
+
"median": 16,
|
|
2181
|
+
"p75": 30,
|
|
2182
|
+
"p90": 96
|
|
2183
|
+
},
|
|
2184
|
+
"components": {
|
|
2185
|
+
"values": [
|
|
2186
|
+
60,
|
|
2187
|
+
182,
|
|
2188
|
+
246,
|
|
2189
|
+
272,
|
|
2190
|
+
316,
|
|
2191
|
+
700,
|
|
2192
|
+
777,
|
|
2193
|
+
811,
|
|
2194
|
+
911,
|
|
2195
|
+
971,
|
|
2196
|
+
1085,
|
|
2197
|
+
1137,
|
|
2198
|
+
1204,
|
|
2199
|
+
1453,
|
|
2200
|
+
1494,
|
|
2201
|
+
1594,
|
|
2202
|
+
3630
|
|
2203
|
+
],
|
|
2204
|
+
"p25": 316,
|
|
2205
|
+
"median": 911,
|
|
2206
|
+
"p75": 1204,
|
|
2207
|
+
"p90": 1534
|
|
2208
|
+
},
|
|
2209
|
+
"paintTin": {
|
|
2210
|
+
"values": [
|
|
2211
|
+
5,
|
|
2212
|
+
8,
|
|
2213
|
+
20,
|
|
2214
|
+
21,
|
|
2215
|
+
22,
|
|
2216
|
+
32,
|
|
2217
|
+
33,
|
|
2218
|
+
42,
|
|
2219
|
+
57,
|
|
2220
|
+
58,
|
|
2221
|
+
62,
|
|
2222
|
+
67,
|
|
2223
|
+
100,
|
|
2224
|
+
183,
|
|
2225
|
+
296,
|
|
2226
|
+
491,
|
|
2227
|
+
658
|
|
2228
|
+
],
|
|
2229
|
+
"p25": 22,
|
|
2230
|
+
"median": 57,
|
|
2231
|
+
"p75": 100,
|
|
2232
|
+
"p90": 374
|
|
2233
|
+
},
|
|
2234
|
+
"doorOverrides": {
|
|
2235
|
+
"values": [
|
|
2236
|
+
0,
|
|
2237
|
+
3,
|
|
2238
|
+
5,
|
|
2239
|
+
13,
|
|
2240
|
+
13,
|
|
2241
|
+
15,
|
|
2242
|
+
16,
|
|
2243
|
+
16,
|
|
2244
|
+
17,
|
|
2245
|
+
18,
|
|
2246
|
+
22,
|
|
2247
|
+
26,
|
|
2248
|
+
28,
|
|
2249
|
+
31,
|
|
2250
|
+
40,
|
|
2251
|
+
44,
|
|
2252
|
+
55
|
|
2253
|
+
],
|
|
2254
|
+
"p25": 13,
|
|
2255
|
+
"median": 17,
|
|
2256
|
+
"p75": 28,
|
|
2257
|
+
"p90": 42
|
|
2258
|
+
}
|
|
2259
|
+
},
|
|
2260
|
+
"repos": [
|
|
2261
|
+
{
|
|
2262
|
+
"repo": "calcom/cal.com",
|
|
2263
|
+
"confidence": "high",
|
|
2264
|
+
"style": "default",
|
|
2265
|
+
"baseColor": "zinc",
|
|
2266
|
+
"tailwind": null,
|
|
2267
|
+
"codeFiles": 4391,
|
|
2268
|
+
"ownFiles": 811,
|
|
2269
|
+
"metrics": {
|
|
2270
|
+
"colors": 310,
|
|
2271
|
+
"greys": 66,
|
|
2272
|
+
"spacing": 120,
|
|
2273
|
+
"typefaces": 8,
|
|
2274
|
+
"fontSizes": 49,
|
|
2275
|
+
"radii": 50,
|
|
2276
|
+
"shadows": 10,
|
|
2277
|
+
"exactDuplicates": 134,
|
|
2278
|
+
"inlineStyles": 79,
|
|
2279
|
+
"arbitrary": 257,
|
|
2280
|
+
"nearPairs": 18,
|
|
2281
|
+
"important": 157,
|
|
2282
|
+
"neverImported": 99,
|
|
2283
|
+
"components": 1453,
|
|
2284
|
+
"paintTin": 22,
|
|
2285
|
+
"doorOverrides": 5
|
|
2286
|
+
}
|
|
2287
|
+
},
|
|
2288
|
+
{
|
|
2289
|
+
"repo": "shadcn-ui/taxonomy",
|
|
2290
|
+
"confidence": "medium",
|
|
2291
|
+
"style": null,
|
|
2292
|
+
"baseColor": null,
|
|
2293
|
+
"tailwind": null,
|
|
2294
|
+
"codeFiles": 129,
|
|
2295
|
+
"ownFiles": 54,
|
|
2296
|
+
"metrics": {
|
|
2297
|
+
"colors": 8,
|
|
2298
|
+
"greys": 0,
|
|
2299
|
+
"spacing": 5,
|
|
2300
|
+
"typefaces": 2,
|
|
2301
|
+
"fontSizes": 13,
|
|
2302
|
+
"radii": 7,
|
|
2303
|
+
"shadows": 1,
|
|
2304
|
+
"exactDuplicates": 0,
|
|
2305
|
+
"inlineStyles": 0,
|
|
2306
|
+
"arbitrary": 54,
|
|
2307
|
+
"nearPairs": 0,
|
|
2308
|
+
"important": 1,
|
|
2309
|
+
"neverImported": 94,
|
|
2310
|
+
"components": 182,
|
|
2311
|
+
"paintTin": 20,
|
|
2312
|
+
"doorOverrides": 0
|
|
2313
|
+
}
|
|
2314
|
+
},
|
|
2315
|
+
{
|
|
2316
|
+
"repo": "steven-tey/novel",
|
|
2317
|
+
"confidence": "medium",
|
|
2318
|
+
"style": "default",
|
|
2319
|
+
"baseColor": "slate",
|
|
2320
|
+
"tailwind": "3",
|
|
2321
|
+
"codeFiles": 61,
|
|
2322
|
+
"ownFiles": 23,
|
|
2323
|
+
"metrics": {
|
|
2324
|
+
"colors": 31,
|
|
2325
|
+
"greys": 6,
|
|
2326
|
+
"spacing": 10,
|
|
2327
|
+
"typefaces": 5,
|
|
2328
|
+
"fontSizes": 5,
|
|
2329
|
+
"radii": 11,
|
|
2330
|
+
"shadows": 2,
|
|
2331
|
+
"exactDuplicates": 1,
|
|
2332
|
+
"inlineStyles": 2,
|
|
2333
|
+
"arbitrary": 12,
|
|
2334
|
+
"nearPairs": 0,
|
|
2335
|
+
"important": 2,
|
|
2336
|
+
"neverImported": 0,
|
|
2337
|
+
"components": 60,
|
|
2338
|
+
"paintTin": 57,
|
|
2339
|
+
"doorOverrides": 13
|
|
2340
|
+
}
|
|
2341
|
+
},
|
|
2342
|
+
{
|
|
2343
|
+
"repo": "vercel/ai-chatbot",
|
|
2344
|
+
"confidence": "high",
|
|
2345
|
+
"style": "radix-maia",
|
|
2346
|
+
"baseColor": "neutral",
|
|
2347
|
+
"tailwind": "4",
|
|
2348
|
+
"codeFiles": 146,
|
|
2349
|
+
"ownFiles": 60,
|
|
2350
|
+
"metrics": {
|
|
2351
|
+
"colors": 41,
|
|
2352
|
+
"greys": 0,
|
|
2353
|
+
"spacing": 6,
|
|
2354
|
+
"typefaces": 3,
|
|
2355
|
+
"fontSizes": 12,
|
|
2356
|
+
"radii": 14,
|
|
2357
|
+
"shadows": 4,
|
|
2358
|
+
"exactDuplicates": 1,
|
|
2359
|
+
"inlineStyles": 7,
|
|
2360
|
+
"arbitrary": 100,
|
|
2361
|
+
"nearPairs": 0,
|
|
2362
|
+
"important": 9,
|
|
2363
|
+
"neverImported": 0,
|
|
2364
|
+
"components": 316,
|
|
2365
|
+
"paintTin": 67,
|
|
2366
|
+
"doorOverrides": 18
|
|
2367
|
+
}
|
|
2368
|
+
},
|
|
2369
|
+
{
|
|
2370
|
+
"repo": "mckaywrigley/chatbot-ui",
|
|
2371
|
+
"confidence": "high",
|
|
2372
|
+
"style": "default",
|
|
2373
|
+
"baseColor": "gray",
|
|
2374
|
+
"tailwind": "3",
|
|
2375
|
+
"codeFiles": 258,
|
|
2376
|
+
"ownFiles": 102,
|
|
2377
|
+
"metrics": {
|
|
2378
|
+
"colors": 12,
|
|
2379
|
+
"greys": 3,
|
|
2380
|
+
"spacing": 25,
|
|
2381
|
+
"typefaces": 1,
|
|
2382
|
+
"fontSizes": 9,
|
|
2383
|
+
"radii": 8,
|
|
2384
|
+
"shadows": 0,
|
|
2385
|
+
"exactDuplicates": 1,
|
|
2386
|
+
"inlineStyles": 11,
|
|
2387
|
+
"arbitrary": 126,
|
|
2388
|
+
"nearPairs": 2,
|
|
2389
|
+
"important": 0,
|
|
2390
|
+
"neverImported": 100,
|
|
2391
|
+
"components": 272,
|
|
2392
|
+
"paintTin": 33,
|
|
2393
|
+
"doorOverrides": 17
|
|
2394
|
+
}
|
|
2395
|
+
},
|
|
2396
|
+
{
|
|
2397
|
+
"repo": "documenso/documenso",
|
|
2398
|
+
"confidence": "medium",
|
|
2399
|
+
"style": null,
|
|
2400
|
+
"baseColor": null,
|
|
2401
|
+
"tailwind": null,
|
|
2402
|
+
"codeFiles": 1934,
|
|
2403
|
+
"ownFiles": 466,
|
|
2404
|
+
"metrics": {
|
|
2405
|
+
"colors": 56,
|
|
2406
|
+
"greys": 12,
|
|
2407
|
+
"spacing": 34,
|
|
2408
|
+
"typefaces": 6,
|
|
2409
|
+
"fontSizes": 24,
|
|
2410
|
+
"radii": 13,
|
|
2411
|
+
"shadows": 0,
|
|
2412
|
+
"exactDuplicates": 16,
|
|
2413
|
+
"inlineStyles": 74,
|
|
2414
|
+
"arbitrary": 245,
|
|
2415
|
+
"nearPairs": 18,
|
|
2416
|
+
"important": 0,
|
|
2417
|
+
"neverImported": 44,
|
|
2418
|
+
"components": 811,
|
|
2419
|
+
"paintTin": 62,
|
|
2420
|
+
"doorOverrides": 22
|
|
2421
|
+
}
|
|
2422
|
+
},
|
|
2423
|
+
{
|
|
2424
|
+
"repo": "dubinc/dub",
|
|
2425
|
+
"confidence": "medium",
|
|
2426
|
+
"style": null,
|
|
2427
|
+
"baseColor": null,
|
|
2428
|
+
"tailwind": null,
|
|
2429
|
+
"codeFiles": 4160,
|
|
2430
|
+
"ownFiles": 1389,
|
|
2431
|
+
"metrics": {
|
|
2432
|
+
"colors": 118,
|
|
2433
|
+
"greys": 22,
|
|
2434
|
+
"spacing": 49,
|
|
2435
|
+
"typefaces": 3,
|
|
2436
|
+
"fontSizes": 43,
|
|
2437
|
+
"radii": 28,
|
|
2438
|
+
"shadows": 0,
|
|
2439
|
+
"exactDuplicates": 23,
|
|
2440
|
+
"inlineStyles": 49,
|
|
2441
|
+
"arbitrary": 622,
|
|
2442
|
+
"nearPairs": 15,
|
|
2443
|
+
"important": 1,
|
|
2444
|
+
"neverImported": 16,
|
|
2445
|
+
"components": 1594,
|
|
2446
|
+
"paintTin": 658,
|
|
2447
|
+
"doorOverrides": 40
|
|
2448
|
+
}
|
|
2449
|
+
},
|
|
2450
|
+
{
|
|
2451
|
+
"repo": "formbricks/formbricks",
|
|
2452
|
+
"confidence": "high",
|
|
2453
|
+
"style": "new-york",
|
|
2454
|
+
"baseColor": "slate",
|
|
2455
|
+
"tailwind": null,
|
|
2456
|
+
"codeFiles": 2604,
|
|
2457
|
+
"ownFiles": 788,
|
|
2458
|
+
"metrics": {
|
|
2459
|
+
"colors": 132,
|
|
2460
|
+
"greys": 16,
|
|
2461
|
+
"spacing": 32,
|
|
2462
|
+
"typefaces": 2,
|
|
2463
|
+
"fontSizes": 21,
|
|
2464
|
+
"radii": 20,
|
|
2465
|
+
"shadows": 6,
|
|
2466
|
+
"exactDuplicates": 61,
|
|
2467
|
+
"inlineStyles": 24,
|
|
2468
|
+
"arbitrary": 169,
|
|
2469
|
+
"nearPairs": 8,
|
|
2470
|
+
"important": 55,
|
|
2471
|
+
"neverImported": 2,
|
|
2472
|
+
"components": 1085,
|
|
2473
|
+
"paintTin": 296,
|
|
2474
|
+
"doorOverrides": 31
|
|
2475
|
+
}
|
|
2476
|
+
},
|
|
2477
|
+
{
|
|
2478
|
+
"repo": "openstatusHQ/openstatus",
|
|
2479
|
+
"confidence": "high",
|
|
2480
|
+
"style": "new-york",
|
|
2481
|
+
"baseColor": "neutral",
|
|
2482
|
+
"tailwind": null,
|
|
2483
|
+
"codeFiles": 2141,
|
|
2484
|
+
"ownFiles": 775,
|
|
2485
|
+
"metrics": {
|
|
2486
|
+
"colors": 174,
|
|
2487
|
+
"greys": 38,
|
|
2488
|
+
"spacing": 11,
|
|
2489
|
+
"typefaces": 3,
|
|
2490
|
+
"fontSizes": 13,
|
|
2491
|
+
"radii": 11,
|
|
2492
|
+
"shadows": 0,
|
|
2493
|
+
"exactDuplicates": 102,
|
|
2494
|
+
"inlineStyles": 120,
|
|
2495
|
+
"arbitrary": 163,
|
|
2496
|
+
"nearPairs": 11,
|
|
2497
|
+
"important": 0,
|
|
2498
|
+
"neverImported": 5,
|
|
2499
|
+
"components": 1137,
|
|
2500
|
+
"paintTin": 5,
|
|
2501
|
+
"doorOverrides": 16
|
|
2502
|
+
}
|
|
2503
|
+
},
|
|
2504
|
+
{
|
|
2505
|
+
"repo": "lukevella/rallly",
|
|
2506
|
+
"confidence": "medium",
|
|
2507
|
+
"style": "base-vega",
|
|
2508
|
+
"baseColor": "gray",
|
|
2509
|
+
"tailwind": null,
|
|
2510
|
+
"codeFiles": 875,
|
|
2511
|
+
"ownFiles": 410,
|
|
2512
|
+
"metrics": {
|
|
2513
|
+
"colors": 17,
|
|
2514
|
+
"greys": 3,
|
|
2515
|
+
"spacing": 14,
|
|
2516
|
+
"typefaces": 3,
|
|
2517
|
+
"fontSizes": 12,
|
|
2518
|
+
"radii": 14,
|
|
2519
|
+
"shadows": 0,
|
|
2520
|
+
"exactDuplicates": 17,
|
|
2521
|
+
"inlineStyles": 22,
|
|
2522
|
+
"arbitrary": 38,
|
|
2523
|
+
"nearPairs": 1,
|
|
2524
|
+
"important": 0,
|
|
2525
|
+
"neverImported": 19,
|
|
2526
|
+
"components": 700,
|
|
2527
|
+
"paintTin": 100,
|
|
2528
|
+
"doorOverrides": 3
|
|
2529
|
+
}
|
|
2530
|
+
},
|
|
2531
|
+
{
|
|
2532
|
+
"repo": "mfts/papermark",
|
|
2533
|
+
"confidence": "high",
|
|
2534
|
+
"style": "default",
|
|
2535
|
+
"baseColor": "gray",
|
|
2536
|
+
"tailwind": "3",
|
|
2537
|
+
"codeFiles": 1379,
|
|
2538
|
+
"ownFiles": 531,
|
|
2539
|
+
"metrics": {
|
|
2540
|
+
"colors": 66,
|
|
2541
|
+
"greys": 14,
|
|
2542
|
+
"spacing": 45,
|
|
2543
|
+
"typefaces": 1,
|
|
2544
|
+
"fontSizes": 27,
|
|
2545
|
+
"radii": 19,
|
|
2546
|
+
"shadows": 1,
|
|
2547
|
+
"exactDuplicates": 20,
|
|
2548
|
+
"inlineStyles": 47,
|
|
2549
|
+
"arbitrary": 386,
|
|
2550
|
+
"nearPairs": 7,
|
|
2551
|
+
"important": 42,
|
|
2552
|
+
"neverImported": 13,
|
|
2553
|
+
"components": 777,
|
|
2554
|
+
"paintTin": 491,
|
|
2555
|
+
"doorOverrides": 55
|
|
2556
|
+
}
|
|
2557
|
+
},
|
|
2558
|
+
{
|
|
2559
|
+
"repo": "elie222/inbox-zero",
|
|
2560
|
+
"confidence": "medium",
|
|
2561
|
+
"style": "default",
|
|
2562
|
+
"baseColor": "slate",
|
|
2563
|
+
"tailwind": "3",
|
|
2564
|
+
"codeFiles": 2120,
|
|
2565
|
+
"ownFiles": 340,
|
|
2566
|
+
"metrics": {
|
|
2567
|
+
"colors": 254,
|
|
2568
|
+
"greys": 52,
|
|
2569
|
+
"spacing": 21,
|
|
2570
|
+
"typefaces": 3,
|
|
2571
|
+
"fontSizes": 27,
|
|
2572
|
+
"radii": 21,
|
|
2573
|
+
"shadows": 0,
|
|
2574
|
+
"exactDuplicates": 21,
|
|
2575
|
+
"inlineStyles": 5,
|
|
2576
|
+
"arbitrary": 128,
|
|
2577
|
+
"nearPairs": 21,
|
|
2578
|
+
"important": 9,
|
|
2579
|
+
"neverImported": 0,
|
|
2580
|
+
"components": 911,
|
|
2581
|
+
"paintTin": 183,
|
|
2582
|
+
"doorOverrides": 15
|
|
2583
|
+
}
|
|
2584
|
+
},
|
|
2585
|
+
{
|
|
2586
|
+
"repo": "linkwarden/linkwarden",
|
|
2587
|
+
"confidence": "medium",
|
|
2588
|
+
"style": "default",
|
|
2589
|
+
"baseColor": "neutral",
|
|
2590
|
+
"tailwind": "3",
|
|
2591
|
+
"codeFiles": 519,
|
|
2592
|
+
"ownFiles": 226,
|
|
2593
|
+
"metrics": {
|
|
2594
|
+
"colors": 56,
|
|
2595
|
+
"greys": 9,
|
|
2596
|
+
"spacing": 10,
|
|
2597
|
+
"typefaces": 2,
|
|
2598
|
+
"fontSizes": 12,
|
|
2599
|
+
"radii": 9,
|
|
2600
|
+
"shadows": 0,
|
|
2601
|
+
"exactDuplicates": 25,
|
|
2602
|
+
"inlineStyles": 42,
|
|
2603
|
+
"arbitrary": 101,
|
|
2604
|
+
"nearPairs": 0,
|
|
2605
|
+
"important": 0,
|
|
2606
|
+
"neverImported": 1,
|
|
2607
|
+
"components": 246,
|
|
2608
|
+
"paintTin": 42,
|
|
2609
|
+
"doorOverrides": 13
|
|
2610
|
+
}
|
|
2611
|
+
},
|
|
2612
|
+
{
|
|
2613
|
+
"repo": "langfuse/langfuse",
|
|
2614
|
+
"confidence": "high",
|
|
2615
|
+
"style": "default",
|
|
2616
|
+
"baseColor": "slate",
|
|
2617
|
+
"tailwind": "4",
|
|
2618
|
+
"codeFiles": 3749,
|
|
2619
|
+
"ownFiles": 1205,
|
|
2620
|
+
"metrics": {
|
|
2621
|
+
"colors": 115,
|
|
2622
|
+
"greys": 4,
|
|
2623
|
+
"spacing": 40,
|
|
2624
|
+
"typefaces": 0,
|
|
2625
|
+
"fontSizes": 20,
|
|
2626
|
+
"radii": 12,
|
|
2627
|
+
"shadows": 0,
|
|
2628
|
+
"exactDuplicates": 14,
|
|
2629
|
+
"inlineStyles": 47,
|
|
2630
|
+
"arbitrary": 385,
|
|
2631
|
+
"nearPairs": 10,
|
|
2632
|
+
"important": 12,
|
|
2633
|
+
"neverImported": 0,
|
|
2634
|
+
"components": 1204,
|
|
2635
|
+
"paintTin": 21,
|
|
2636
|
+
"doorOverrides": 26
|
|
2637
|
+
}
|
|
2638
|
+
},
|
|
2639
|
+
{
|
|
2640
|
+
"repo": "teableio/teable",
|
|
2641
|
+
"confidence": "high",
|
|
2642
|
+
"style": "new-york",
|
|
2643
|
+
"baseColor": "gray",
|
|
2644
|
+
"tailwind": "3",
|
|
2645
|
+
"codeFiles": 5553,
|
|
2646
|
+
"ownFiles": 1234,
|
|
2647
|
+
"metrics": {
|
|
2648
|
+
"colors": 265,
|
|
2649
|
+
"greys": 33,
|
|
2650
|
+
"spacing": 66,
|
|
2651
|
+
"typefaces": 3,
|
|
2652
|
+
"fontSizes": 42,
|
|
2653
|
+
"radii": 26,
|
|
2654
|
+
"shadows": 10,
|
|
2655
|
+
"exactDuplicates": 65,
|
|
2656
|
+
"inlineStyles": 58,
|
|
2657
|
+
"arbitrary": 614,
|
|
2658
|
+
"nearPairs": 16,
|
|
2659
|
+
"important": 42,
|
|
2660
|
+
"neverImported": 30,
|
|
2661
|
+
"components": 1494,
|
|
2662
|
+
"paintTin": 58,
|
|
2663
|
+
"doorOverrides": 28
|
|
2664
|
+
}
|
|
2665
|
+
},
|
|
2666
|
+
{
|
|
2667
|
+
"repo": "midday-ai/midday",
|
|
2668
|
+
"confidence": "medium",
|
|
2669
|
+
"style": "new-york",
|
|
2670
|
+
"baseColor": "neutral",
|
|
2671
|
+
"tailwind": "4",
|
|
2672
|
+
"codeFiles": 1775,
|
|
2673
|
+
"ownFiles": 605,
|
|
2674
|
+
"metrics": {
|
|
2675
|
+
"colors": 187,
|
|
2676
|
+
"greys": 51,
|
|
2677
|
+
"spacing": 59,
|
|
2678
|
+
"typefaces": 3,
|
|
2679
|
+
"fontSizes": 49,
|
|
2680
|
+
"radii": 20,
|
|
2681
|
+
"shadows": 2,
|
|
2682
|
+
"exactDuplicates": 104,
|
|
2683
|
+
"inlineStyles": 117,
|
|
2684
|
+
"arbitrary": 1673,
|
|
2685
|
+
"nearPairs": 21,
|
|
2686
|
+
"important": 0,
|
|
2687
|
+
"neverImported": 26,
|
|
2688
|
+
"components": 971,
|
|
2689
|
+
"paintTin": 32,
|
|
2690
|
+
"doorOverrides": 44
|
|
2691
|
+
}
|
|
2692
|
+
},
|
|
2693
|
+
{
|
|
2694
|
+
"repo": "supabase/supabase",
|
|
2695
|
+
"confidence": "high",
|
|
2696
|
+
"style": "default",
|
|
2697
|
+
"baseColor": "gray",
|
|
2698
|
+
"tailwind": null,
|
|
2699
|
+
"codeFiles": 5954,
|
|
2700
|
+
"ownFiles": 3325,
|
|
2701
|
+
"metrics": {
|
|
2702
|
+
"colors": 1030,
|
|
2703
|
+
"greys": 111,
|
|
2704
|
+
"spacing": 220,
|
|
2705
|
+
"typefaces": 19,
|
|
2706
|
+
"fontSizes": 79,
|
|
2707
|
+
"radii": 34,
|
|
2708
|
+
"shadows": 14,
|
|
2709
|
+
"exactDuplicates": 288,
|
|
2710
|
+
"inlineStyles": 263,
|
|
2711
|
+
"arbitrary": 1625,
|
|
2712
|
+
"nearPairs": 59,
|
|
2713
|
+
"important": 93,
|
|
2714
|
+
"neverImported": 23,
|
|
2715
|
+
"components": 3630,
|
|
2716
|
+
"paintTin": 8,
|
|
2717
|
+
"doorOverrides": 16
|
|
2718
|
+
}
|
|
2719
|
+
}
|
|
2720
|
+
]
|
|
2721
|
+
}
|
|
1844
2722
|
}
|
|
1845
2723
|
}
|
|
@@ -32,7 +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, scorePackage as scorePackageOf, ZERO_IDEAL, WARN_TOLERANCE, SCORE_OF, SCHEMA_VERSION } from './score.mjs';
|
|
35
|
-
import { profileOf
|
|
35
|
+
import { profileOf } from '../profiles/index.mjs';
|
|
36
36
|
|
|
37
37
|
// The benchmark and every judgement made against it live in score.mjs; this
|
|
38
38
|
// file only draws. The same numbers reach summary.json through scoreHarvest.
|
|
@@ -47,9 +47,10 @@ if (!inPath) { console.error('Usage: node src/diagnose/index.mjs <harvest.json>
|
|
|
47
47
|
const outPath = resolve(arg('out', 'diagnosis.html'));
|
|
48
48
|
|
|
49
49
|
const h = JSON.parse(readFileSync(inPath, 'utf8'));
|
|
50
|
-
// The yardstick: the general benchmark,
|
|
51
|
-
//
|
|
52
|
-
|
|
50
|
+
// The yardstick: the general benchmark, read through the slice for this
|
|
51
|
+
// repo's kind when the benchmark carries one (a shadcn repo is compared with
|
|
52
|
+
// the shadcn repos in the fleet).
|
|
53
|
+
const B = benchHelpers(bench, profileOf(h).kind);
|
|
53
54
|
const { percentile, cleanerPct, ideal, median, displayAvg, refMedian } = B;
|
|
54
55
|
const healthOf = makeHealthOf(B);
|
|
55
56
|
const M = coreMetrics(h);
|
|
@@ -361,7 +362,7 @@ function tile(t, pLabel, pMetric, pFallback) {
|
|
|
361
362
|
const avgNote = pct !== null && pct >= 60 && health !== 'good' ? ` · messier than ${pct}%`
|
|
362
363
|
: clean !== null && clean >= 60 ? ` · cleaner than ${clean}%` : '';
|
|
363
364
|
const av = displayAvg(metric);
|
|
364
|
-
if (av !== null) rows.push(row('Avg Design System', `${n(av)}${avgNote}`, av, value));
|
|
365
|
+
if (av !== null) rows.push(row(B.sliceInfo ? `Avg ${B.sliceInfo.kind} repo` : 'Avg Design System', `${n(av)}${avgNote}`, av, value));
|
|
365
366
|
if (rm !== null && (rm > 0 || ZERO_IDEAL.has(metric) || metric === 'arbitrary')) rows.push(row('Reputable systems', n(rm), rm, value));
|
|
366
367
|
// A vendored catalogue is never judged for what it left on the shelf, and a
|
|
367
368
|
// library's internal use says nothing about adoption: shown, not scored.
|
|
@@ -602,7 +603,8 @@ function sheetSection() {
|
|
|
602
603
|
if (paint) {
|
|
603
604
|
const tinChips = (paint.tin.samples ?? []).slice(0, 8).map((s) => `<span class="vchip bad">${esc(s.value)} ×${s.count}</span>`).join('');
|
|
604
605
|
const tinFiles = (paint.tin.top ?? []).slice(0, 5).map((f) => `<span class="vchip" title="${esc(f.file)}">${esc(basename(f.file))} ×${f.count}</span>`).join('');
|
|
605
|
-
|
|
606
|
+
const rp = sc.registryPaint ?? null;
|
|
607
|
+
parts.push(`<div class="receipts">${eyebrow(`${n(paint.tin.uses)} colours from outside the theme in ${n(paint.tin.files)} of ${n(paint.ownFiles)} own files · ${n(paint.tin.per100)} per 100 files`)}${paint.tin.uses ? `<div class="chips-row">${tinChips}</div><div class="chips-row">${tinFiles}</div>` : '<p class="sub">Your own code takes every colour from the theme file. This is what shadcn is designed for.</p>'}${rp?.tinUses ? `<p class="sub">Not counted above: ${n(rp.tinUses)} palette colour${rp.tinUses === 1 ? '' : 's'} inside ${esc(rp.dirs.map((d) => basename(d)).join(', '))}, installed by a registry rather than written here. An agent reading those files will still copy them.</p>` : ''}</div>`);
|
|
606
608
|
parts.push(whyToggle('paintTin'));
|
|
607
609
|
const doorChips = (paint.doors.samples ?? []).slice(0, 6).map((s) => `<span class="vchip bad">${esc(s.value)} ×${s.count}</span>`).join('');
|
|
608
610
|
parts.push(`<div class="receipts">${eyebrow(`${n(paint.doors.uses)} shadcn components restyled through className · ${n(paint.doors.per100)} per 100 files`)}${paint.doors.uses ? `<div class="chips-row">${doorChips}</div>` : '<p class="sub">No shadcn component is given a colour or a font through className. Variants are doing their job.</p>'}</div>`);
|
|
@@ -1263,7 +1265,9 @@ function shadcnReceipt() {
|
|
|
1263
1265
|
].filter(Boolean);
|
|
1264
1266
|
const unmatched = (k?.fellBack ?? []).filter((f) => f === 'theme' || f === 'chartColor');
|
|
1265
1267
|
const fell = unmatched.length ? ` · ${unmatched.length === 2 ? 'accent and chart colour' : unmatched[0] === 'theme' ? 'accent' : 'chart colour'} not matched to a named shadcn theme` : '';
|
|
1266
|
-
|
|
1268
|
+
const rp = P.shadcn?.registryPaint ?? null;
|
|
1269
|
+
const regLine = rp ? `<div class="excl">Installed registries kept out of the own-code counts: ${esc(rp.dirs.map((d) => basename(d)).join(', '))} (${n(rp.files)} file${rp.files === 1 ? '' : 's'}${rp.tinUses ? `, ${n(rp.tinUses)} palette colour${rp.tinUses === 1 ? '' : 's'} of their own` : ''}). Installed code, not written here, but your agent reads it like everything else and copies what it finds there.</div>` : '';
|
|
1270
|
+
return `<div class="excl">Read as a shadcn install (${esc(P.confidence ?? 'medium')} confidence): ${esc(P.evidence.join(' · '))}${facts.length ? ` · ${esc(facts.join(', '))}` : ''}${esc(fell)}</div>${regLine}`;
|
|
1267
1271
|
}
|
|
1268
1272
|
|
|
1269
1273
|
// User exclusions are printed in the header, never hidden: a scoped scan must
|
|
@@ -1839,7 +1843,7 @@ if (summaryPath) {
|
|
|
1839
1843
|
// The contract two scans are compared by: schemaVersion for the
|
|
1840
1844
|
// shape, benchmark for the ruler each scan was measured against.
|
|
1841
1845
|
schemaVersion: SCHEMA_VERSION,
|
|
1842
|
-
benchmark: bench ? { builtAt: bench.builtAt, repoCount: bench.repoCount, referenceSystems: bench.referenceSystems?.count ?? 0 } : null,
|
|
1846
|
+
benchmark: bench ? { builtAt: bench.builtAt, repoCount: bench.repoCount, referenceSystems: bench.referenceSystems?.count ?? 0, ...(B.sliceInfo ? { slice: B.sliceInfo } : {}) } : null,
|
|
1843
1847
|
...(commissionedBy ? { commissionedBy } : {}),
|
|
1844
1848
|
...(notesText ? { notesEmbedded: true } : {}),
|
|
1845
1849
|
...(extraSections.length ? { sectionsEmbedded: extraSections.map((s) => s.title) } : {}),
|
|
@@ -1849,7 +1853,7 @@ if (summaryPath) {
|
|
|
1849
1853
|
verdict,
|
|
1850
1854
|
role: P.role,
|
|
1851
1855
|
kind: P.kind,
|
|
1852
|
-
...(P.isShadcn && P.shadcn ? { shadcn: { confidence: P.confidence, evidence: P.evidence, style: P.shadcn.kit?.style ?? null, baseColor: P.shadcn.kit?.baseColor ?? null, tailwind: P.shadcn.kit?.tailwind ?? null, catalogues: P.uiDirs, ownFiles: P.shadcn.paint?.ownFiles ?? null } } : {}),
|
|
1856
|
+
...(P.isShadcn && P.shadcn ? { shadcn: { confidence: P.confidence, evidence: P.evidence, style: P.shadcn.kit?.style ?? null, baseColor: P.shadcn.kit?.baseColor ?? null, tailwind: P.shadcn.kit?.tailwind ?? null, catalogues: P.uiDirs, registries: P.shadcn.registryDirs ?? [], ...(P.shadcn.registryPaint ? { registryFiles: P.shadcn.registryPaint.files, registryPaletteColours: P.shadcn.registryPaint.tinUses } : {}), ownFiles: P.shadcn.paint?.ownFiles ?? null } } : {}),
|
|
1853
1857
|
componentsMeasured,
|
|
1854
1858
|
metrics: (({ colors, colorTokens, colorStrays, greys, greyStrays, spacing, exactDuplicates, inlineStyles, nearPairs, important, neverImported, arbitrary, tokenLed }) =>
|
|
1855
1859
|
({ colors, colorTokens, colorStrays, greys, greyStrays, spacing, exactDuplicates, inlineStyles, nearPairs, important, neverImported, arbitrary, tokenLed }))(M),
|
|
@@ -26,7 +26,7 @@ import { isGrey } from '../lib/color.mjs';
|
|
|
26
26
|
import { nearColorPairs } from '../lib/nearpairs.mjs';
|
|
27
27
|
import { neverImportedComponents } from '../lib/neverimported.mjs';
|
|
28
28
|
import { SCHEMA_VERSION } from '../lib/version.mjs';
|
|
29
|
-
import { profileOf
|
|
29
|
+
import { profileOf } from '../profiles/index.mjs';
|
|
30
30
|
|
|
31
31
|
export { SCHEMA_VERSION };
|
|
32
32
|
|
|
@@ -38,19 +38,16 @@ export function loadBenchmark() {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
/**
|
|
41
|
-
* The yardstick readers for one benchmark object (null-safe).
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
41
|
+
* The yardstick readers for one benchmark object (null-safe). When the
|
|
42
|
+
* benchmark carries a slice for the repo's kind (benchmark.slices.shadcn,
|
|
43
|
+
* built by tools/benchmark/build-slice.mjs), the fleet lines read against
|
|
44
|
+
* that slice: a shadcn repo is compared with shadcn repos. The curated
|
|
45
|
+
* ideals and the reputable-systems line are the same for every kind.
|
|
45
46
|
*/
|
|
46
|
-
export function benchHelpers(bench,
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
ideal2026: { ...(yardstick.ideals ?? {}), ...(bench?.ideal2026 ?? {}) },
|
|
51
|
-
stats: { ...(yardstick.stats ?? {}), ...(bench?.stats ?? {}) },
|
|
52
|
-
};
|
|
53
|
-
}
|
|
47
|
+
export function benchHelpers(bench, kind = 'product') {
|
|
48
|
+
const slice = bench?.slices?.[kind] ?? null;
|
|
49
|
+
if (slice?.stats) bench = { ...bench, stats: { ...(bench?.stats ?? {}), ...slice.stats } };
|
|
50
|
+
const sliceInfo = slice ? { kind, repoCount: slice.repoCount, builtAt: slice.builtAt } : null;
|
|
54
51
|
// where does this value sit among the scanned fleet? ("more colours than 90%")
|
|
55
52
|
const percentile = (metric, value) => {
|
|
56
53
|
const vals = bench?.stats?.[metric]?.values;
|
|
@@ -77,7 +74,7 @@ export function benchHelpers(bench, yardstick = null) {
|
|
|
77
74
|
return mean >= 0.5 ? Math.round(mean) : null;
|
|
78
75
|
};
|
|
79
76
|
const refMedian = (metric) => bench?.referenceSystems?.stats?.[metric]?.median ?? null;
|
|
80
|
-
return { percentile, cleanerPct, ideal, median, displayAvg, refMedian };
|
|
77
|
+
return { percentile, cleanerPct, ideal, median, displayAvg, refMedian, sliceInfo };
|
|
81
78
|
}
|
|
82
79
|
|
|
83
80
|
export const ZERO_IDEAL = new Set(['exactDuplicates', 'inlineStyles', 'nearPairs', 'important', 'neverImported']);
|
|
@@ -230,7 +227,7 @@ export function scorePackage(m, healthOf, b) {
|
|
|
230
227
|
|
|
231
228
|
/** The whole judgement of one harvest, as data. */
|
|
232
229
|
export function scoreHarvest(h, bench = loadBenchmark()) {
|
|
233
|
-
const b = benchHelpers(bench,
|
|
230
|
+
const b = benchHelpers(bench, profileOf(h).kind);
|
|
234
231
|
const healthOf = makeHealthOf(b);
|
|
235
232
|
const metrics = coreMetrics(h);
|
|
236
233
|
const tiles = tileHealths(metrics, healthOf);
|
|
@@ -238,7 +235,7 @@ export function scoreHarvest(h, bench = loadBenchmark()) {
|
|
|
238
235
|
schemaVersion: SCHEMA_VERSION,
|
|
239
236
|
// A rebuilt ruler moves every score with no change in the repo, so a
|
|
240
237
|
// history must know which ruler each scan was measured with.
|
|
241
|
-
benchmark: bench ? { builtAt: bench.builtAt, repoCount: bench.repoCount, referenceSystems: bench.referenceSystems?.count ?? 0 } : null,
|
|
238
|
+
benchmark: bench ? { builtAt: bench.builtAt, repoCount: bench.repoCount, referenceSystems: bench.referenceSystems?.count ?? 0, ...(b.sliceInfo ? { slice: b.sliceInfo } : {}) } : null,
|
|
242
239
|
metrics,
|
|
243
240
|
tiles,
|
|
244
241
|
score: scoreOfTiles(tiles),
|
|
@@ -68,9 +68,18 @@ decideProfile(profile, components, files, target);
|
|
|
68
68
|
{
|
|
69
69
|
const P = profileOf(profile);
|
|
70
70
|
if (P.isShadcn) {
|
|
71
|
-
const
|
|
71
|
+
const installed = [...P.uiDirs, ...(profile.shadcn?.registryDirs ?? [])];
|
|
72
|
+
const doorFiles = new Set([...installed.flatMap((d) => files.code.filter((f) => f.startsWith(`${d}/`))), ...(profile.shadcn?.blockFiles ?? [])]);
|
|
72
73
|
const kitNames = new Set(components.filter((c) => doorFiles.has(c.file)).map((c) => c.name));
|
|
73
|
-
profile.shadcn.paint = countPaint(target, files.code, { uiDirs: [...
|
|
74
|
+
profile.shadcn.paint = countPaint(target, files.code, { uiDirs: [...installed, ...(profile.shadcn.blockFiles ?? [])], kitNames });
|
|
75
|
+
// what the installed registries carry themselves: kept out of the own-code
|
|
76
|
+
// counts, shown in the header, because an agent reads them all the same
|
|
77
|
+
const regDirs = profile.shadcn.registryDirs ?? [];
|
|
78
|
+
if (regDirs.length) {
|
|
79
|
+
const regFiles = files.code.filter((f) => regDirs.some((d) => f.startsWith(`${d}/`)));
|
|
80
|
+
const rp = countPaint(target, regFiles, { uiDirs: [], kitNames });
|
|
81
|
+
profile.shadcn.registryPaint = { files: rp.ownFiles, tinUses: rp.tin.uses, dirs: regDirs };
|
|
82
|
+
}
|
|
74
83
|
}
|
|
75
84
|
}
|
|
76
85
|
|
|
@@ -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 = '7.
|
|
4
|
+
export const VERSION = '7.3.0';
|
|
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.
|
|
@@ -100,13 +100,3 @@ export function profileOf(h) {
|
|
|
100
100
|
};
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
/**
|
|
104
|
-
* Question 6, per kind: ideals and fleet stats a profile owns until the
|
|
105
|
-
* benchmark slice for that kind exists. Overlaid on the general benchmark by
|
|
106
|
-
* score.mjs; the general profile owns none, so nothing moves for it.
|
|
107
|
-
*/
|
|
108
|
-
export function profileYardstick(h) {
|
|
109
|
-
const kind = profileOf(h).kind;
|
|
110
|
-
const p = PROFILES.find((x) => x.kind === kind);
|
|
111
|
-
return { ideals: p?.ideals ?? {}, stats: p?.stats ?? {} };
|
|
112
|
-
}
|
|
@@ -28,6 +28,15 @@ export const BLOCK_COMPONENTS = new Set(['app-sidebar', 'calendars', 'chart-area
|
|
|
28
28
|
'search-form', 'section-cards', 'settings-dialog', 'sidebar-left', 'sidebar-opt-in-form',
|
|
29
29
|
'sidebar-right', 'signup-form', 'site-header', 'team-switcher', 'version-switcher']);
|
|
30
30
|
|
|
31
|
+
// Third-party registries that install through the shadcn CLI into a folder of
|
|
32
|
+
// their own under the components alias (ai-elements from Vercel, kibo-ui,
|
|
33
|
+
// magicui, motion-primitives...). Installed code the team did not write,
|
|
34
|
+
// judged like the catalogue. Folder names as the CLI creates them; a
|
|
35
|
+
// components.json `registries` key ("@kibo-ui") adds its own name at scan.
|
|
36
|
+
export const REGISTRY_DIRS = new Set(['ai-elements', 'kibo-ui', 'magicui', 'magic-ui', 'aceternity',
|
|
37
|
+
'aceternity-ui', 'originui', 'origin-ui', 'cult-ui', 'motion-primitives', 'animate-ui', 'tailark',
|
|
38
|
+
'shadcn-blocks', 'skiper-ui', 'react-bits', 'eldora-ui', 'cuicui', 'spectrum-ui']);
|
|
39
|
+
|
|
31
40
|
// The sheet: rows shadcn's theming docs define, each in :root and .dark.
|
|
32
41
|
export const SHADCN_ROWS = ['background', 'foreground', 'card', 'card-foreground', 'popover',
|
|
33
42
|
'popover-foreground', 'primary', 'primary-foreground', 'secondary', 'secondary-foreground',
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
*/
|
|
28
28
|
import { existsSync, readFileSync } from 'node:fs';
|
|
29
29
|
import { join, dirname, basename } from 'node:path';
|
|
30
|
-
import { CATALOGUE, BLOCK_COMPONENTS, KNOWN_ROWS, TWEAKCN_ROWS, LIGHT_ONLY_ROWS, FACTORY_SPACING,
|
|
30
|
+
import { CATALOGUE, BLOCK_COMPONENTS, REGISTRY_DIRS, KNOWN_ROWS, TWEAKCN_ROWS, LIGHT_ONLY_ROWS, FACTORY_SPACING,
|
|
31
31
|
FRONTS, LEGACY_FRONTS, BASES, BASE_COLORS, ICON_LIBRARIES, RADIUS_MAP, THEMES, SHADCN_ROWS } from './shadcn-data.mjs';
|
|
32
32
|
|
|
33
33
|
const readJSON = (p) => { try { return JSON.parse(readFileSync(p, 'utf8')); } catch { return null; } };
|
|
@@ -239,29 +239,29 @@ export default {
|
|
|
239
239
|
profile.designSystem = { kind: 'shadcn', name: 'shadcn/ui', confidence: 'high', cssVariables: cssVars };
|
|
240
240
|
// kit doors installed as blocks into own code
|
|
241
241
|
const blockFiles = files.code.filter((f) => /\.[jt]sx$/.test(f) && !profile.uiDirs.some((d) => f.startsWith(`${d}/`)) && BLOCK_COMPONENTS.has(stripExt(basename(f))));
|
|
242
|
+
// third-party registries installed beside the catalogue: a folder named
|
|
243
|
+
// as the CLI creates it, or as a `registries` key in components.json
|
|
244
|
+
// names it. Installed code, judged like the catalogue.
|
|
245
|
+
const registryNames = new Set(REGISTRY_DIRS);
|
|
246
|
+
for (const { cfg } of configs) for (const k of Object.keys(cfg.registries ?? {})) registryNames.add(k.replace(/^@/, '').toLowerCase());
|
|
247
|
+
const registryDirs = [...new Set(files.code
|
|
248
|
+
.filter((f) => /\.[jt]sx$/.test(f) && !/(^|\/)(node_modules|public|docs?|content|posts?)\//.test(f))
|
|
249
|
+
.map((f) => dirname(f))
|
|
250
|
+
.filter((d) => registryNames.has(basename(d).toLowerCase()) && !profile.uiDirs.some((u) => d === u || d.startsWith(`${u}/`))))]
|
|
251
|
+
.filter((d) => files.code.filter((f) => f.startsWith(`${d}/`) && /\.[jt]sx$/.test(f)).length >= 2);
|
|
252
|
+
if (registryDirs.length) evidence.push(`${registryDirs.length} installed registr${registryDirs.length === 1 ? 'y' : 'ies'} beside it: ${registryDirs.map((d) => basename(d)).join(', ')}`);
|
|
242
253
|
profile.shadcn = {
|
|
243
254
|
installs: installs.map((i) => ({ config: i.config, uiDir: i.uiDir, catalogueNames: i.catalogueNames })),
|
|
244
255
|
kit,
|
|
245
256
|
sheet,
|
|
246
257
|
blockFiles,
|
|
258
|
+
registryDirs,
|
|
247
259
|
};
|
|
248
260
|
return { confidence, evidence };
|
|
249
261
|
},
|
|
250
262
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
* the stats are the whole set so the "cleaner than" line is honest. The
|
|
256
|
-
* step 4 slice replaces these with a rebuilt ruler.
|
|
257
|
-
*/
|
|
258
|
-
ideals: {
|
|
259
|
-
paintTin: { value: 25, note: 'palette colours per 100 own-code files; the tidiest third of 15 shadcn repos sit under this' },
|
|
260
|
-
doorOverrides: { value: 15, note: 'kit components repainted through className, per 100 own-code files; the tidiest third sit under this' },
|
|
261
|
-
},
|
|
262
|
-
// the 15 shadcn kitchens in the benchmark fleet, 2026-09-11, per 100 own-code files
|
|
263
|
-
stats: {
|
|
264
|
-
paintTin: { values: [5, 8, 20, 21, 22, 32, 33, 58, 62, 66, 100, 179, 296, 488, 658], median: 62 },
|
|
265
|
-
doorOverrides: { values: [0, 3, 5, 14, 16, 16, 16, 17, 22, 26, 28, 31, 40, 44, 55], median: 22 },
|
|
266
|
-
},
|
|
263
|
+
// Question 6 and 9 live in benchmark.json: the curated ideals for the 2
|
|
264
|
+
// shadcn tiles sit in ideal2026, and the shadcn slice (built by
|
|
265
|
+
// tools/benchmark/build-slice.mjs from every fleet repo read as shadcn)
|
|
266
|
+
// gives the fleet lines. Nothing here owns a number.
|
|
267
267
|
};
|
|
@@ -177,6 +177,7 @@ if (neverImported.length >= 3) {
|
|
|
177
177
|
if (paint?.tin?.uses) lines.push(` (${paint.tin.uses} palette colour${paint.tin.uses === 1 ? '' : 's'} already sit in own code, ${paint.tin.samples.slice(0, 3).map((s) => `\`${s.value}\` ×${s.count}`).join(', ')}; do not add to them.)`);
|
|
178
178
|
rule('Before adding classes to a shadcn component, use one of its variants (`variant="outline"`, `size="sm"`). `className` on a shadcn component is for layout only: width, margin, position. Never colour, never typography.');
|
|
179
179
|
if (paint?.doors?.uses) lines.push(` (${paint.doors.uses} shadcn component${paint.doors.uses === 1 ? '' : 's'} already restyled through className, like ${paint.doors.samples.slice(0, 2).map((s) => `\`${s.value}\``).join(' and ')}; do not add to them.)`);
|
|
180
|
+
if (sc.registryDirs?.length) rule(`${sc.registryDirs.map((d) => `\`${d}\``).join(', ')} ${sc.registryDirs.length === 1 ? 'is an installed registry' : 'are installed registries'} (added through the shadcn CLI, not written here). Treat ${sc.registryDirs.length === 1 ? 'it' : 'them'} like \`${P.uiDir ?? 'components/ui'}\`: reach for what is there before building your own${sc.registryPaint?.tinUses ? `, and do not copy the ${sc.registryPaint.tinUses} palette colour${sc.registryPaint.tinUses === 1 ? '' : 's'} inside into own code` : ''}.`);
|
|
180
181
|
rule(`Edit the component you own in \`${P.uiDir ?? 'components/ui'}\`. Never build a second one beside it under another name. A wrapper that composes shadcn components is fine; a second implementation is not.`);
|
|
181
182
|
rule('Merge classes with `cn()`. Never concatenate strings and never write a ternary inside a className string.');
|
|
182
183
|
rule('Add a component with `npx shadcn@latest add <name>`, then edit it. To see what changed upstream, run `npx shadcn@latest add <name> --diff`.');
|