@usefragments/core 1.0.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.
Files changed (228) hide show
  1. package/LICENSE +21 -0
  2. package/dist/chunk-243QYRUF.js +2145 -0
  3. package/dist/chunk-243QYRUF.js.map +1 -0
  4. package/dist/chunk-3LLRNCPX.js +111 -0
  5. package/dist/chunk-3LLRNCPX.js.map +1 -0
  6. package/dist/chunk-EIYNNS77.js +48 -0
  7. package/dist/chunk-EIYNNS77.js.map +1 -0
  8. package/dist/chunk-JNBFJ34I.js +326 -0
  9. package/dist/chunk-JNBFJ34I.js.map +1 -0
  10. package/dist/chunk-R3X7UBHU.js +228 -0
  11. package/dist/chunk-R3X7UBHU.js.map +1 -0
  12. package/dist/chunk-SH4KPIYH.js +393 -0
  13. package/dist/chunk-SH4KPIYH.js.map +1 -0
  14. package/dist/chunk-V4VQB57N.js +56 -0
  15. package/dist/chunk-V4VQB57N.js.map +1 -0
  16. package/dist/chunk-X34IA4LR.js +319 -0
  17. package/dist/chunk-X34IA4LR.js.map +1 -0
  18. package/dist/chunk-YF65VYRY.js +493 -0
  19. package/dist/chunk-YF65VYRY.js.map +1 -0
  20. package/dist/codes/index.d.ts +40 -0
  21. package/dist/codes/index.js +23 -0
  22. package/dist/codes/index.js.map +1 -0
  23. package/dist/compiled-types/index.d.ts +5 -0
  24. package/dist/compiled-types/index.js +1 -0
  25. package/dist/compiled-types/index.js.map +1 -0
  26. package/dist/generate/index.d.ts +47 -0
  27. package/dist/generate/index.js +11 -0
  28. package/dist/generate/index.js.map +1 -0
  29. package/dist/governance-BOa3KyiJ.d.ts +3279 -0
  30. package/dist/graph/index.d.ts +66 -0
  31. package/dist/graph/index.js +17 -0
  32. package/dist/graph/index.js.map +1 -0
  33. package/dist/index.d.ts +11388 -0
  34. package/dist/index.js +8711 -0
  35. package/dist/index.js.map +1 -0
  36. package/dist/package-identity.d.ts +42 -0
  37. package/dist/package-identity.js +23 -0
  38. package/dist/package-identity.js.map +1 -0
  39. package/dist/preview/index.d.ts +484 -0
  40. package/dist/preview/index.js +1129 -0
  41. package/dist/preview/index.js.map +1 -0
  42. package/dist/react-types.d.ts +22 -0
  43. package/dist/react-types.js +1 -0
  44. package/dist/react-types.js.map +1 -0
  45. package/dist/registry.d.ts +2367 -0
  46. package/dist/registry.js +62 -0
  47. package/dist/registry.js.map +1 -0
  48. package/dist/schemas/index.d.ts +2584 -0
  49. package/dist/schemas/index.js +55 -0
  50. package/dist/schemas/index.js.map +1 -0
  51. package/dist/severity.d.ts +18 -0
  52. package/dist/severity.js +27 -0
  53. package/dist/severity.js.map +1 -0
  54. package/dist/test-utils.d.ts +14 -0
  55. package/dist/test-utils.js +63 -0
  56. package/dist/test-utils.js.map +1 -0
  57. package/dist/topology/index.d.ts +48 -0
  58. package/dist/topology/index.js +7 -0
  59. package/dist/topology/index.js.map +1 -0
  60. package/dist/types-xJ2xyp_G.d.ts +119 -0
  61. package/package.json +120 -0
  62. package/src/__tests__/contract-parser.test.ts +614 -0
  63. package/src/__tests__/dtcg-generators.test.ts +439 -0
  64. package/src/__tests__/dtcg-parser.test.ts +884 -0
  65. package/src/__tests__/preview-runtime.test.tsx +111 -0
  66. package/src/__tests__/schema.test.ts +31 -0
  67. package/src/__tests__/severity.test.ts +34 -0
  68. package/src/__tests__/style-comparison.test.ts +662 -0
  69. package/src/__tests__/tokens-compat.test.ts +51 -0
  70. package/src/agent-format.test.ts +105 -0
  71. package/src/agent-format.ts +141 -0
  72. package/src/bundle.ts +149 -0
  73. package/src/codes/__tests__/codes.test.ts +73 -0
  74. package/src/codes/codes.ts +463 -0
  75. package/src/codes/index.ts +7 -0
  76. package/src/codes/source-finding-bridge.ts +113 -0
  77. package/src/compiled-types/index.ts +293 -0
  78. package/src/component-contract.ts +231 -0
  79. package/src/component-discovery.ts +80 -0
  80. package/src/composition.test.ts +262 -0
  81. package/src/composition.ts +318 -0
  82. package/src/config-paths.test.ts +78 -0
  83. package/src/config-paths.ts +47 -0
  84. package/src/config.ts +18 -0
  85. package/src/conform-prove.test.ts +274 -0
  86. package/src/conform-prove.ts +326 -0
  87. package/src/conform.ts +160 -0
  88. package/src/constants.ts +114 -0
  89. package/src/context.ts +2 -0
  90. package/src/contract/fixture.ts +110 -0
  91. package/src/contract/hash.test.ts +86 -0
  92. package/src/contract/hash.ts +195 -0
  93. package/src/contract/index.ts +43 -0
  94. package/src/contract/preimage.test.ts +254 -0
  95. package/src/contract/preimage.ts +365 -0
  96. package/src/contract/stamp.test.ts +100 -0
  97. package/src/contract/stamp.ts +105 -0
  98. package/src/contract-parser.ts +141 -0
  99. package/src/defineFragment.test.ts +257 -0
  100. package/src/defineFragment.ts +293 -0
  101. package/src/dtcg-generators.ts +324 -0
  102. package/src/dtcg-parser.ts +1 -0
  103. package/src/dtcg.ts +237 -0
  104. package/src/facts/__snapshots__/facts.test.ts.snap +124 -0
  105. package/src/facts/builders.ts +835 -0
  106. package/src/facts/compile.ts +422 -0
  107. package/src/facts/fact-index.ts +394 -0
  108. package/src/facts/facts.test.ts +494 -0
  109. package/src/facts/ids.ts +125 -0
  110. package/src/facts/index.ts +119 -0
  111. package/src/facts/types.ts +614 -0
  112. package/src/figma.ts +263 -0
  113. package/src/fragment-types.ts +214 -0
  114. package/src/generate/generate.test.ts +594 -0
  115. package/src/generate/index.ts +438 -0
  116. package/src/governance-integrity.test.ts +186 -0
  117. package/src/governance-integrity.ts +282 -0
  118. package/src/governance.test.ts +300 -0
  119. package/src/governance.ts +605 -0
  120. package/src/governance.type-test.ts +75 -0
  121. package/src/graph/engine.ts +446 -0
  122. package/src/graph/graph.test.ts +746 -0
  123. package/src/graph/index.ts +32 -0
  124. package/src/graph/serialization.ts +55 -0
  125. package/src/graph/types.ts +178 -0
  126. package/src/index.ts +891 -0
  127. package/src/local-canonical.ts +102 -0
  128. package/src/mcp-snapshot.ts +222 -0
  129. package/src/package-identity-match.ts +33 -0
  130. package/src/package-identity.generated.ts +233 -0
  131. package/src/package-identity.test.ts +99 -0
  132. package/src/package-identity.ts +157 -0
  133. package/src/performance-presets.ts +142 -0
  134. package/src/preview/comparison-artifact.test.ts +228 -0
  135. package/src/preview/comparison-artifact.ts +181 -0
  136. package/src/preview/comparison-context.ts +93 -0
  137. package/src/preview/comparison-evidence.ts +112 -0
  138. package/src/preview/comparison-format.ts +21 -0
  139. package/src/preview/comparison-presentation.ts +187 -0
  140. package/src/preview/comparison-references.test.ts +430 -0
  141. package/src/preview/comparison-references.ts +72 -0
  142. package/src/preview/comparison-score.ts +59 -0
  143. package/src/preview/comparison-styles.ts +197 -0
  144. package/src/preview/comparison.test.ts +559 -0
  145. package/src/preview/comparison.ts +488 -0
  146. package/src/preview/index.ts +10 -0
  147. package/src/preview/types.ts +309 -0
  148. package/src/preview/validation.test.ts +290 -0
  149. package/src/preview/validation.ts +356 -0
  150. package/src/preview-runtime.tsx +144 -0
  151. package/src/raw-html-canonical.test.ts +41 -0
  152. package/src/raw-html-canonical.ts +179 -0
  153. package/src/react-types.ts +21 -0
  154. package/src/registry-install-plan.ts +344 -0
  155. package/src/registry.test.ts +245 -0
  156. package/src/registry.ts +369 -0
  157. package/src/rules/a11y-required-accessible-name.ts +93 -0
  158. package/src/rules/color-math.test.ts +72 -0
  159. package/src/rules/color-math.ts +192 -0
  160. package/src/rules/components-forbidden-prop-value.ts +108 -0
  161. package/src/rules/components-prefer-library.ts +943 -0
  162. package/src/rules/components-unknown-prop.ts +94 -0
  163. package/src/rules/composition-pattern-schema.ts +125 -0
  164. package/src/rules/composition-pattern.test.ts +320 -0
  165. package/src/rules/composition-pattern.ts +271 -0
  166. package/src/rules/emit-gate.test.ts +42 -0
  167. package/src/rules/emit-gate.ts +60 -0
  168. package/src/rules/finding.ts +54 -0
  169. package/src/rules/index.ts +235 -0
  170. package/src/rules/jsx-preferred-component.ts +68 -0
  171. package/src/rules/jsx-preferred-import-path.ts +67 -0
  172. package/src/rules/presets.ts +57 -0
  173. package/src/rules/props-invalid-value.ts +115 -0
  174. package/src/rules/rules.test.ts +3636 -0
  175. package/src/rules/spacing-resolution.ts +184 -0
  176. package/src/rules/styles-no-raw-color.ts +329 -0
  177. package/src/rules/styles-no-raw-dimensions.ts +357 -0
  178. package/src/rules/styles-no-raw-spacing.ts +236 -0
  179. package/src/rules/styles-no-raw-typography.ts +237 -0
  180. package/src/rules/tailwind-arbitrary-color.test.ts +128 -0
  181. package/src/rules/tailwind-arbitrary-color.ts +93 -0
  182. package/src/rules/tailwind-arbitrary-spacing.test.ts +157 -0
  183. package/src/rules/tailwind-arbitrary-spacing.ts +120 -0
  184. package/src/rules/tailwind-forbidden-palette.test.ts +116 -0
  185. package/src/rules/tailwind-forbidden-palette.ts +107 -0
  186. package/src/rules/tailwind-off-scale-spacing-token.test.ts +100 -0
  187. package/src/rules/tailwind-off-scale-spacing-token.ts +108 -0
  188. package/src/rules/tailwind-raw-color-via-token.test.ts +108 -0
  189. package/src/rules/tailwind-raw-color-via-token.ts +82 -0
  190. package/src/rules/tailwind-rule-utils.ts +106 -0
  191. package/src/rules/tailwind-unknown-class.test.ts +157 -0
  192. package/src/rules/tailwind-unknown-class.ts +121 -0
  193. package/src/rules/taxonomy.test.ts +238 -0
  194. package/src/rules/theme-no-theme-coupled-literal.ts +132 -0
  195. package/src/rules/tiers.ts +111 -0
  196. package/src/rules/tokens-css-vars-must-be-defined.test.ts +244 -0
  197. package/src/rules/tokens-css-vars-must-be-defined.ts +156 -0
  198. package/src/rules/tokens-require-dual-fallback.ts +78 -0
  199. package/src/rules/types.ts +27 -0
  200. package/src/rules/utils.test.ts +110 -0
  201. package/src/rules/utils.ts +599 -0
  202. package/src/schema.ts +427 -0
  203. package/src/schemas/index.ts +275 -0
  204. package/src/schemas/schemas.test.ts +124 -0
  205. package/src/severity.ts +54 -0
  206. package/src/storyAdapter.test.ts +571 -0
  207. package/src/storyAdapter.ts +761 -0
  208. package/src/storyFilters.test.ts +350 -0
  209. package/src/storyFilters.ts +253 -0
  210. package/src/storybook-csf.ts +11 -0
  211. package/src/style-comparison.ts +423 -0
  212. package/src/test-utils.ts +68 -0
  213. package/src/token-parser.ts +2 -0
  214. package/src/token-types.ts +287 -0
  215. package/src/tokens/categories.ts +131 -0
  216. package/src/tokens/color.ts +193 -0
  217. package/src/tokens/design-token-parser.test.ts +159 -0
  218. package/src/tokens/design-token-parser.ts +402 -0
  219. package/src/tokens/dtcg.ts +428 -0
  220. package/src/tokens/index.ts +130 -0
  221. package/src/tokens/lookup.ts +19 -0
  222. package/src/tokens/scss.ts +171 -0
  223. package/src/tokens/tailwind-v4.ts +187 -0
  224. package/src/tokens/types.ts +43 -0
  225. package/src/topology/index.ts +9 -0
  226. package/src/topology/resolve-area.test.ts +174 -0
  227. package/src/topology/resolve-area.ts +215 -0
  228. package/src/types.ts +1057 -0
@@ -0,0 +1,605 @@
1
+ import { z } from "zod";
2
+ import type {
3
+ ComponentRelation,
4
+ FragmentComponent,
5
+ FragmentContract,
6
+ FragmentExample,
7
+ FragmentGuidance,
8
+ FragmentMeta,
9
+ FragmentProvenance,
10
+ PropDefinition,
11
+ } from "./types.js";
12
+ import { portableRepoPathError } from "./config-paths.js";
13
+
14
+ export const governanceSeveritySchema = z.enum(["error", "warn", "info"]);
15
+ export type GovernanceSeverity = z.infer<typeof governanceSeveritySchema>;
16
+
17
+ export const scaleGovernanceRecordSchema = z.object({
18
+ kind: z.literal("scale").default("scale"),
19
+ name: z.string().min(1).optional(),
20
+ unit: z.enum(["px", "rem"]),
21
+ values: z.array(z.number()),
22
+ rootFontSizePx: z.number().positive().optional(),
23
+ emBasePx: z.number().positive().optional(),
24
+ });
25
+
26
+ const styleRawColorsForbidRecordSchema = z.object({
27
+ kind: z.literal("style.rawColors.forbid"),
28
+ except: z.array(z.string()),
29
+ prefer: z.enum(["token", "css-variable"]),
30
+ severity: governanceSeveritySchema,
31
+ });
32
+
33
+ const styleRawDimensionsForbidRecordSchema = z.object({
34
+ kind: z.literal("style.rawDimensions.forbid"),
35
+ appliesTo: z.array(z.string().min(1)),
36
+ prefer: z.enum(["token", "css-variable"]),
37
+ severity: governanceSeveritySchema,
38
+ });
39
+
40
+ const styleRawSpacingMustMatchScaleRecordSchema = z.object({
41
+ kind: z.literal("style.rawSpacing.mustMatchScale"),
42
+ scale: z.string().min(1),
43
+ appliesTo: z.array(z.string().min(1)),
44
+ severity: governanceSeveritySchema,
45
+ });
46
+
47
+ const styleFontSizeMustMatchScaleRecordSchema = z.object({
48
+ kind: z.literal("style.fontSize.mustMatchScale"),
49
+ scale: z.string().min(1),
50
+ severity: governanceSeveritySchema,
51
+ });
52
+
53
+ const styleCssVarsMustBeDefinedRecordSchema = z.object({
54
+ kind: z.literal("style.cssVars.mustBeDefined"),
55
+ severity: governanceSeveritySchema,
56
+ });
57
+
58
+ export const globalStyleGovernanceRecordSchema = z.discriminatedUnion("kind", [
59
+ styleRawColorsForbidRecordSchema,
60
+ styleRawDimensionsForbidRecordSchema,
61
+ styleRawSpacingMustMatchScaleRecordSchema,
62
+ styleFontSizeMustMatchScaleRecordSchema,
63
+ styleCssVarsMustBeDefinedRecordSchema,
64
+ ]);
65
+
66
+ const jsxUnknownPropsForbidRecordSchema = z.object({
67
+ kind: z.literal("jsx.unknownProps.forbid"),
68
+ severity: governanceSeveritySchema,
69
+ });
70
+
71
+ const jsxInlineStyleForbidRawRecordSchema = z.object({
72
+ kind: z.literal("jsx.inlineStyle.forbidRaw"),
73
+ properties: z.array(z.string().min(1)),
74
+ severity: governanceSeveritySchema,
75
+ });
76
+
77
+ const jsxImportPathPreferRecordSchema = z.object({
78
+ kind: z.literal("jsx.importPath.prefer"),
79
+ from: z.string().min(1),
80
+ to: z.string().min(1),
81
+ imported: z.string().min(1).optional(),
82
+ because: z.string().optional(),
83
+ severity: governanceSeveritySchema,
84
+ });
85
+
86
+ const jsxComponentPreferRecordSchema = z.object({
87
+ kind: z.literal("jsx.component.prefer"),
88
+ from: z.string().min(1),
89
+ to: z.string().min(1),
90
+ because: z.string().optional(),
91
+ severity: governanceSeveritySchema,
92
+ });
93
+
94
+ export const globalJsxGovernanceRecordSchema = z.discriminatedUnion("kind", [
95
+ jsxUnknownPropsForbidRecordSchema,
96
+ jsxInlineStyleForbidRawRecordSchema,
97
+ jsxImportPathPreferRecordSchema,
98
+ jsxComponentPreferRecordSchema,
99
+ ]);
100
+
101
+ export const globalGovernanceRecordSchema = z.discriminatedUnion("kind", [
102
+ scaleGovernanceRecordSchema,
103
+ styleRawColorsForbidRecordSchema,
104
+ styleRawDimensionsForbidRecordSchema,
105
+ styleRawSpacingMustMatchScaleRecordSchema,
106
+ styleFontSizeMustMatchScaleRecordSchema,
107
+ styleCssVarsMustBeDefinedRecordSchema,
108
+ jsxUnknownPropsForbidRecordSchema,
109
+ jsxInlineStyleForbidRawRecordSchema,
110
+ jsxImportPathPreferRecordSchema,
111
+ jsxComponentPreferRecordSchema,
112
+ ]);
113
+
114
+ const componentCapabilityRecordSchema = z.object({
115
+ kind: z.literal("capability"),
116
+ capability: z.string().min(1),
117
+ });
118
+
119
+ const propValueAvoidRecordSchema = z.object({
120
+ kind: z.literal("prop.value.avoid"),
121
+ prop: z.string().min(1),
122
+ value: z.unknown(),
123
+ because: z.string(),
124
+ suggest: z.string().optional(),
125
+ severity: governanceSeveritySchema,
126
+ });
127
+
128
+ const propValueForbidRecordSchema = z.object({
129
+ kind: z.literal("prop.value.forbid"),
130
+ prop: z.string().min(1),
131
+ value: z.unknown(),
132
+ when: z.object({ path: z.string().min(1).optional() }).optional(),
133
+ because: z.string(),
134
+ fix: z.object({ replaceWith: z.unknown() }).optional(),
135
+ severity: governanceSeveritySchema,
136
+ });
137
+
138
+ const a11yRequireNameRecordSchema = z.object({
139
+ kind: z.literal("a11y.requireName"),
140
+ because: z.string(),
141
+ severity: governanceSeveritySchema,
142
+ });
143
+
144
+ export const componentGovernanceRecordSchema = z.discriminatedUnion("kind", [
145
+ componentCapabilityRecordSchema,
146
+ propValueAvoidRecordSchema,
147
+ propValueForbidRecordSchema,
148
+ a11yRequireNameRecordSchema,
149
+ ]);
150
+
151
+ export const componentGovernanceRecordsSchema = z.array(componentGovernanceRecordSchema);
152
+
153
+ const componentPolicyMatchSchema = z
154
+ .object({
155
+ componentKey: z.string().min(1).optional(),
156
+ componentId: z.string().min(1).optional(),
157
+ publicRef: z.string().min(1).optional(),
158
+ importPath: z.string().min(1).optional(),
159
+ sourcePath: z.string().min(1).optional(),
160
+ path: z.string().min(1).optional(),
161
+ exportName: z.string().min(1).optional(),
162
+ export: z.string().min(1).optional(),
163
+ name: z.string().min(1).optional(),
164
+ scope: z.enum(["one", "all"]).optional(),
165
+ })
166
+ .strict();
167
+
168
+ const componentPolicyRecordSchema = z
169
+ .object({
170
+ rules: componentGovernanceRecordsSchema,
171
+ status: z.enum(["active", "draft", "disabled"]).optional(),
172
+ })
173
+ .strict();
174
+
175
+ const componentPolicyOverrideSchema = z
176
+ .object({
177
+ match: componentPolicyMatchSchema,
178
+ rules: componentGovernanceRecordsSchema,
179
+ status: z.enum(["active", "draft", "disabled"]).optional(),
180
+ })
181
+ .strict();
182
+
183
+ const canonicalSourcePathSchema = z
184
+ .string()
185
+ .min(1)
186
+ .superRefine((value, ctx) => {
187
+ const error = portableRepoPathError(value);
188
+ if (error) {
189
+ ctx.addIssue({ code: z.ZodIssueCode.custom, message: error });
190
+ }
191
+ });
192
+
193
+ const canonicalSourceRegistryHashSchema = z.string().regex(/^[0-9a-f]{64}$/);
194
+
195
+ const canonicalSourceSchema = z.discriminatedUnion("kind", [
196
+ z.object({
197
+ kind: z.literal("npm"),
198
+ specifier: z.string().min(1),
199
+ implementationPath: z.string().min(1).optional(),
200
+ include: z.array(z.string().min(1)).optional(),
201
+ exclude: z.array(z.string().min(1)).optional(),
202
+ }),
203
+ z.object({
204
+ kind: z.literal("directory"),
205
+ path: z.string().min(1),
206
+ include: z.array(z.string().min(1)).optional(),
207
+ exclude: z.array(z.string().min(1)).optional(),
208
+ }),
209
+ z.object({
210
+ kind: z.literal("registry"),
211
+ registryId: z.string().min(1),
212
+ registryHash: canonicalSourceRegistryHashSchema.optional(),
213
+ receiptPath: canonicalSourcePathSchema.default(".fragments/registry-lock.json"),
214
+ installPath: canonicalSourcePathSchema,
215
+ importPath: z.string().min(1).optional(),
216
+ include: z.array(z.string().min(1)).optional(),
217
+ exclude: z.array(z.string().min(1)).optional(),
218
+ severity: governanceSeveritySchema.optional(),
219
+ }),
220
+ ]);
221
+
222
+ export const governanceConfigSchema = z
223
+ .object({
224
+ extends: z.array(z.string()).optional(),
225
+ severity: governanceSeveritySchema.optional(),
226
+ rules: z.record(z.string(), z.unknown()).optional(),
227
+ agents: z
228
+ .record(
229
+ z.string(),
230
+ z
231
+ .object({
232
+ rules: z.record(z.string(), z.unknown()).optional(),
233
+ })
234
+ .passthrough()
235
+ )
236
+ .optional(),
237
+ audit: z.object({}).passthrough().optional(),
238
+ runners: z.record(z.string(), z.object({}).passthrough()).optional(),
239
+ canonicalSources: z.array(canonicalSourceSchema).optional(),
240
+ presets: z.array(z.string()).optional(),
241
+ scales: z.record(z.string(), scaleGovernanceRecordSchema).optional(),
242
+ styles: z.array(globalStyleGovernanceRecordSchema).optional(),
243
+ jsx: z.array(globalJsxGovernanceRecordSchema).optional(),
244
+ tailwind: z
245
+ .object({
246
+ palette: z
247
+ .object({
248
+ allow: z.array(z.string().min(1)).optional(),
249
+ deny: z.array(z.string().min(1)).optional(),
250
+ })
251
+ .optional(),
252
+ })
253
+ .passthrough()
254
+ .optional(),
255
+ agent: z
256
+ .object({
257
+ repairOrder: z.array(z.string().min(1)).optional(),
258
+ })
259
+ .passthrough()
260
+ .optional(),
261
+ components: z.record(componentPolicyRecordSchema).optional(),
262
+ overrides: z.array(componentPolicyOverrideSchema).optional(),
263
+ ci: z
264
+ .object({
265
+ failOnWarnings: z.boolean().optional(),
266
+ })
267
+ .passthrough()
268
+ .optional(),
269
+ })
270
+ .strict();
271
+
272
+ export type ScaleGovernanceRecord = z.infer<typeof scaleGovernanceRecordSchema>;
273
+ export type GlobalStyleGovernanceRecord = z.infer<typeof globalStyleGovernanceRecordSchema>;
274
+ export type GlobalJsxGovernanceRecord = z.infer<typeof globalJsxGovernanceRecordSchema>;
275
+ export type GlobalGovernanceRecord = z.infer<typeof globalGovernanceRecordSchema>;
276
+ export type ComponentGovernanceRecord = z.infer<typeof componentGovernanceRecordSchema>;
277
+ export type ComponentPolicyMatch = z.infer<typeof componentPolicyMatchSchema>;
278
+ export type ComponentPolicyRecord = z.infer<typeof componentPolicyRecordSchema>;
279
+ export type ComponentPolicyOverride = z.infer<typeof componentPolicyOverrideSchema>;
280
+ export type CanonicalSource = z.infer<typeof canonicalSourceSchema>;
281
+
282
+ export interface GovernanceConfig {
283
+ extends?: string[];
284
+ severity?: GovernanceSeverity;
285
+ rules?: Record<string, unknown>;
286
+ agents?: Record<string, { rules?: Record<string, unknown> }>;
287
+ audit?: Record<string, unknown>;
288
+ runners?: Record<string, Record<string, unknown>>;
289
+ canonicalSources?: CanonicalSource[];
290
+ presets?: string[];
291
+ scales?: Record<string, ScaleGovernanceRecord>;
292
+ styles?: GlobalStyleGovernanceRecord[];
293
+ jsx?: GlobalJsxGovernanceRecord[];
294
+ tailwind?: {
295
+ palette?: {
296
+ allow?: string[];
297
+ deny?: string[];
298
+ };
299
+ [key: string]: unknown;
300
+ };
301
+ agent?: {
302
+ repairOrder?: string[];
303
+ [key: string]: unknown;
304
+ };
305
+ components?: Record<string, ComponentPolicyRecord>;
306
+ overrides?: ComponentPolicyOverride[];
307
+ ci?: {
308
+ failOnWarnings?: boolean;
309
+ [key: string]: unknown;
310
+ };
311
+ [key: string]: unknown;
312
+ }
313
+
314
+ type SeverityOption = {
315
+ severity?: GovernanceSeverity;
316
+ };
317
+
318
+ type PropContext = {
319
+ path?: string;
320
+ };
321
+
322
+ type NonNullish<T> = T extends null | undefined ? never : T;
323
+
324
+ export type GovernancePropValue<T> =
325
+ NonNullish<T> extends string
326
+ ? string extends NonNullish<T>
327
+ ? string
328
+ : NonNullish<T>
329
+ : NonNullish<T> extends number
330
+ ? number extends NonNullish<T>
331
+ ? number
332
+ : NonNullish<T>
333
+ : NonNullish<T> extends boolean
334
+ ? boolean extends NonNullish<T>
335
+ ? boolean
336
+ : NonNullish<T>
337
+ : NonNullish<T>;
338
+
339
+ export type PropKey<TProps> = Extract<keyof TProps, string>;
340
+
341
+ export interface PropGovernanceOptions<TValue> extends SeverityOption {
342
+ because?: string;
343
+ suggest?: string;
344
+ when?: PropContext;
345
+ fix?: {
346
+ replaceWith: GovernancePropValue<TValue>;
347
+ };
348
+ }
349
+
350
+ export interface PropGovernanceBuilder<TValue> {
351
+ avoid(
352
+ value: GovernancePropValue<TValue>,
353
+ options?: Omit<PropGovernanceOptions<TValue>, "fix" | "when">
354
+ ): ComponentGovernanceRecord;
355
+ forbid(
356
+ value: GovernancePropValue<TValue>,
357
+ options?: PropGovernanceOptions<TValue>
358
+ ): ComponentGovernanceRecord;
359
+ }
360
+
361
+ export interface AccessibilityGovernanceBuilder {
362
+ requireName(options?: SeverityOption & { because?: string }): ComponentGovernanceRecord;
363
+ }
364
+
365
+ export interface ComponentGovernanceBuilder<TProps> {
366
+ capability(capability: string): ComponentGovernanceRecord;
367
+ prop<TKey extends PropKey<TProps>>(prop: TKey): PropGovernanceBuilder<TProps[TKey]>;
368
+ accessibility(): AccessibilityGovernanceBuilder;
369
+ }
370
+
371
+ export interface GovernedFragmentDefinition<
372
+ TProps = unknown,
373
+ TComponent extends FragmentComponent<TProps> = FragmentComponent<TProps>,
374
+ > {
375
+ component: TComponent;
376
+ meta: FragmentMeta;
377
+ guidance: FragmentGuidance;
378
+ props?: Record<string, PropDefinition>;
379
+ relations?: ComponentRelation[];
380
+ examples?: FragmentExample[];
381
+ composition?: {
382
+ pattern?: "compound" | "simple" | "controlled" | "wrapper";
383
+ subComponents?: string[];
384
+ requiredChildren?: string[];
385
+ commonPatterns?: string[];
386
+ };
387
+ contract?: FragmentContract;
388
+ _provenance?: FragmentProvenance;
389
+ govern?: (g: ComponentGovernanceBuilder<TProps>) => ComponentGovernanceRecord[];
390
+ governance?: ComponentGovernanceRecord[];
391
+ }
392
+
393
+ export type ResolvedGovernedFragmentDefinition<
394
+ TProps = unknown,
395
+ TComponent extends FragmentComponent<TProps> = FragmentComponent<TProps>,
396
+ > = GovernedFragmentDefinition<TProps, TComponent> & {
397
+ governance: ComponentGovernanceRecord[];
398
+ };
399
+
400
+ export function createComponentGovernanceBuilder<TProps>(): ComponentGovernanceBuilder<TProps> {
401
+ return {
402
+ capability(capability) {
403
+ return { kind: "capability", capability };
404
+ },
405
+ prop(prop) {
406
+ return {
407
+ avoid(value, options = {}) {
408
+ return {
409
+ kind: "prop.value.avoid",
410
+ prop,
411
+ value,
412
+ because: options.because ?? "",
413
+ suggest: options.suggest,
414
+ severity: options.severity ?? "warn",
415
+ };
416
+ },
417
+ forbid(value, options = {}) {
418
+ return {
419
+ kind: "prop.value.forbid",
420
+ prop,
421
+ value,
422
+ when: options.when,
423
+ because: options.because ?? "",
424
+ fix: options.fix,
425
+ severity: options.severity ?? "error",
426
+ };
427
+ },
428
+ };
429
+ },
430
+ accessibility() {
431
+ return {
432
+ requireName(options = {}) {
433
+ return {
434
+ kind: "a11y.requireName",
435
+ because: options.because ?? "",
436
+ severity: options.severity ?? "error",
437
+ };
438
+ },
439
+ };
440
+ },
441
+ };
442
+ }
443
+
444
+ export function resolveComponentGovernance<TProps>(
445
+ definition: GovernedFragmentDefinition<TProps>
446
+ ): ComponentGovernanceRecord[] {
447
+ const builder = createComponentGovernanceBuilder<TProps>();
448
+ const records = definition.govern ? definition.govern(builder) : (definition.governance ?? []);
449
+ return componentGovernanceRecordsSchema.parse(records);
450
+ }
451
+
452
+ export function normalizeGovernanceConfig<TConfig extends { govern?: GovernanceConfig }>(
453
+ config: TConfig
454
+ ): TConfig {
455
+ if (!config.govern?.scales) {
456
+ return config;
457
+ }
458
+
459
+ const scales = Object.fromEntries(
460
+ Object.entries(config.govern.scales).map(([name, scale]) => {
461
+ if (!scale || typeof scale !== "object") {
462
+ return [name, scale];
463
+ }
464
+
465
+ return [name, { ...scale, name: scale.name ?? name }];
466
+ })
467
+ );
468
+
469
+ return {
470
+ ...config,
471
+ govern: {
472
+ ...config.govern,
473
+ scales,
474
+ },
475
+ };
476
+ }
477
+
478
+ export const g = {
479
+ scale: {
480
+ px(values: readonly number[]): ScaleGovernanceRecord {
481
+ return { kind: "scale", unit: "px", values: [...values] };
482
+ },
483
+ rem(values: readonly number[]): ScaleGovernanceRecord {
484
+ return { kind: "scale", unit: "rem", values: [...values] };
485
+ },
486
+ },
487
+ styles: {
488
+ rawColors() {
489
+ return {
490
+ forbid(
491
+ options: {
492
+ except?: string[];
493
+ prefer?: "token" | "css-variable";
494
+ severity?: GovernanceSeverity;
495
+ } = {}
496
+ ): GlobalStyleGovernanceRecord {
497
+ return {
498
+ kind: "style.rawColors.forbid",
499
+ except: options.except ?? [],
500
+ prefer: options.prefer ?? "token",
501
+ severity: options.severity ?? "error",
502
+ };
503
+ },
504
+ };
505
+ },
506
+ rawSpacing() {
507
+ return {
508
+ mustMatchScale(
509
+ scale: string,
510
+ options: SeverityOption & { appliesTo?: string[] } = {}
511
+ ): GlobalStyleGovernanceRecord {
512
+ return {
513
+ kind: "style.rawSpacing.mustMatchScale",
514
+ scale,
515
+ appliesTo: options.appliesTo ?? [],
516
+ severity: options.severity ?? "error",
517
+ };
518
+ },
519
+ };
520
+ },
521
+ fontSize() {
522
+ return {
523
+ mustMatchScale(scale: string, options: SeverityOption = {}): GlobalStyleGovernanceRecord {
524
+ return {
525
+ kind: "style.fontSize.mustMatchScale",
526
+ scale,
527
+ severity: options.severity ?? "warn",
528
+ };
529
+ },
530
+ };
531
+ },
532
+ cssVars() {
533
+ return {
534
+ mustBeDefined(options: SeverityOption = {}): GlobalStyleGovernanceRecord {
535
+ return {
536
+ kind: "style.cssVars.mustBeDefined",
537
+ severity: options.severity ?? "error",
538
+ };
539
+ },
540
+ };
541
+ },
542
+ },
543
+ jsx: {
544
+ unknownProps() {
545
+ return {
546
+ forbid(options: SeverityOption = {}): GlobalJsxGovernanceRecord {
547
+ return {
548
+ kind: "jsx.unknownProps.forbid",
549
+ severity: options.severity ?? "error",
550
+ };
551
+ },
552
+ };
553
+ },
554
+ inlineStyle() {
555
+ return {
556
+ forbidRaw(
557
+ properties: readonly string[],
558
+ options: SeverityOption = {}
559
+ ): GlobalJsxGovernanceRecord {
560
+ return {
561
+ kind: "jsx.inlineStyle.forbidRaw",
562
+ properties: [...properties],
563
+ severity: options.severity ?? "error",
564
+ };
565
+ },
566
+ };
567
+ },
568
+ importPath() {
569
+ return {
570
+ prefer(
571
+ from: string,
572
+ to: string,
573
+ options: SeverityOption & { imported?: string; because?: string } = {}
574
+ ): GlobalJsxGovernanceRecord {
575
+ return {
576
+ kind: "jsx.importPath.prefer",
577
+ from,
578
+ to,
579
+ imported: options.imported,
580
+ because: options.because,
581
+ severity: options.severity ?? "error",
582
+ };
583
+ },
584
+ };
585
+ },
586
+ component() {
587
+ return {
588
+ prefer(
589
+ from: string,
590
+ to: string,
591
+ options: SeverityOption & { because?: string } = {}
592
+ ): GlobalJsxGovernanceRecord {
593
+ return {
594
+ kind: "jsx.component.prefer",
595
+ from,
596
+ to,
597
+ because: options.because,
598
+ severity: options.severity ?? "error",
599
+ };
600
+ },
601
+ };
602
+ },
603
+ },
604
+ component: createComponentGovernanceBuilder<Record<string, unknown>>(),
605
+ };
@@ -0,0 +1,75 @@
1
+ import { defineConfig, defineFragment, g } from "./index.js";
2
+
3
+ type ButtonProps = {
4
+ variant?: "primary" | "secondary" | "ghost" | "link";
5
+ size?: "sm" | "md" | "lg";
6
+ disabled?: boolean;
7
+ children?: unknown;
8
+ };
9
+
10
+ declare function Button(props: ButtonProps): null;
11
+
12
+ defineConfig({
13
+ include: ["src/**/*.fragment.ts"],
14
+ govern: {
15
+ presets: ["fragments/recommended"],
16
+ scales: {
17
+ space: g.scale.px([0, 4, 8]),
18
+ },
19
+ styles: [
20
+ g.styles.rawColors().forbid({ severity: "error" }),
21
+ g.styles.rawSpacing().mustMatchScale("space", {
22
+ appliesTo: ["gap"],
23
+ severity: "error",
24
+ }),
25
+ ],
26
+ jsx: [g.jsx.unknownProps().forbid({ severity: "error" })],
27
+ },
28
+ });
29
+
30
+ defineFragment({
31
+ component: Button,
32
+ meta: { name: "Button", description: "Button", category: "forms" },
33
+ guidance: { when: [], whenNot: [] },
34
+ govern: (govern) => {
35
+ // @ts-expect-error unknown prop
36
+ govern.prop("missing");
37
+
38
+ // @ts-expect-error value not in variant union
39
+ govern.prop("variant").forbid("made-up");
40
+
41
+ // @ts-expect-error wrong boolean value type
42
+ govern.prop("disabled").forbid("true");
43
+
44
+ return [
45
+ govern.prop("variant").forbid("secondary"),
46
+ govern.prop("size").avoid("lg"),
47
+ govern.prop("disabled").forbid(true),
48
+ ];
49
+ },
50
+ });
51
+
52
+ defineFragment<ButtonProps>({
53
+ component: Button,
54
+ meta: { name: "ExplicitButton", description: "Button", category: "forms" },
55
+ guidance: { when: [], whenNot: [] },
56
+ govern: (govern) => [govern.prop("variant").forbid("ghost")],
57
+ });
58
+
59
+ type LinkProps = {
60
+ href: string;
61
+ };
62
+
63
+ declare function Link(props: LinkProps): null;
64
+
65
+ defineFragment({
66
+ component: Link,
67
+ meta: { name: "Link", description: "Link", category: "navigation" },
68
+ guidance: { when: [], whenNot: [] },
69
+ govern: (govern) => {
70
+ // @ts-expect-error unknown prop
71
+ govern.prop("to");
72
+
73
+ return [govern.prop("href").forbid("javascript:*")];
74
+ },
75
+ });