@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,422 @@
1
+ /**
2
+ * Compile facts from the existing IR.
3
+ *
4
+ * Two entry points:
5
+ * - `compileGlobalGovernanceFacts(govern)` turns a `GovernanceConfig` into
6
+ * scale + scale_value + style_* + jsx_* policy facts.
7
+ * - `compileComponentFacts(componentId, fragment)` turns a governed
8
+ * fragment definition (or a compiled fragment) into component metadata,
9
+ * prop metadata, capability, and component-scoped policy facts.
10
+ *
11
+ * Both functions are pure and produce arrays sorted only by source order; the
12
+ * caller is free to add them to a `FactIndex` in any order.
13
+ */
14
+
15
+ import type {
16
+ ComponentGovernanceRecord,
17
+ GovernanceConfig,
18
+ GovernedFragmentDefinition,
19
+ ResolvedGovernedFragmentDefinition,
20
+ } from "../governance.js";
21
+ import { resolveComponentGovernance } from "../governance.js";
22
+ import type { CompiledFragment, PropDefinition } from "../compiled-types/index.js";
23
+ import { asComponentId } from "./ids.js";
24
+ import {
25
+ makeA11yNameRequiredFact,
26
+ makeComponentCapabilityFact,
27
+ makeComponentMetadataFact,
28
+ makeGovernanceRuleConfigFact,
29
+ makeJsxComponentPreferredFact,
30
+ makeJsxInlineStyleForbiddenRawFact,
31
+ makeJsxImportPathPreferredFact,
32
+ makeJsxUnknownPropsForbiddenFact,
33
+ makePropMetadataFact,
34
+ makePropValueAvoidedFact,
35
+ makePropValueForbiddenFact,
36
+ makeScaleFact,
37
+ makeScaleValueFact,
38
+ makeStyleCssVarsMustBeDefinedFact,
39
+ makeStyleFontSizeScaleFact,
40
+ makeStylePropertyScaleFact,
41
+ makeStyleRawColorForbiddenFact,
42
+ makeStyleRawDimensionForbiddenFact,
43
+ makeTailwindPaletteAllowFact,
44
+ makeTailwindPaletteDenyFact,
45
+ makeTailwindUnknownClassEnabledFact,
46
+ } from "./builders.js";
47
+ import type { ComponentId, Fact, PolicyFact } from "./types.js";
48
+
49
+ // ---------------------------------------------------------------------------
50
+ // Global governance → facts
51
+ // ---------------------------------------------------------------------------
52
+
53
+ export function compileGlobalGovernanceFacts(govern: GovernanceConfig | undefined): PolicyFact[] {
54
+ if (!govern) return [];
55
+ const out: PolicyFact[] = [];
56
+
57
+ if (govern.scales) {
58
+ for (const [name, scale] of Object.entries(govern.scales)) {
59
+ out.push(
60
+ makeScaleFact({
61
+ name,
62
+ unit: scale.unit,
63
+ rootFontSizePx: scale.rootFontSizePx,
64
+ emBasePx: scale.emBasePx,
65
+ })
66
+ );
67
+ for (const value of scale.values) {
68
+ out.push(makeScaleValueFact({ scale: name, value }));
69
+ }
70
+ }
71
+ }
72
+
73
+ if (govern.styles) {
74
+ for (const style of govern.styles) {
75
+ switch (style.kind) {
76
+ case "style.rawColors.forbid":
77
+ out.push(
78
+ makeStyleRawColorForbiddenFact({
79
+ except: style.except,
80
+ prefer: style.prefer,
81
+ severity: style.severity,
82
+ })
83
+ );
84
+ break;
85
+ case "style.rawDimensions.forbid":
86
+ out.push(
87
+ makeStyleRawDimensionForbiddenFact({
88
+ appliesTo: style.appliesTo,
89
+ prefer: style.prefer,
90
+ severity: style.severity,
91
+ })
92
+ );
93
+ break;
94
+ case "style.rawSpacing.mustMatchScale":
95
+ for (const property of style.appliesTo) {
96
+ out.push(
97
+ makeStylePropertyScaleFact({
98
+ property,
99
+ scale: style.scale,
100
+ severity: style.severity,
101
+ })
102
+ );
103
+ }
104
+ break;
105
+ case "style.fontSize.mustMatchScale":
106
+ out.push(makeStyleFontSizeScaleFact({ scale: style.scale, severity: style.severity }));
107
+ break;
108
+ case "style.cssVars.mustBeDefined":
109
+ out.push(makeStyleCssVarsMustBeDefinedFact({ severity: style.severity }));
110
+ break;
111
+ }
112
+ }
113
+ }
114
+
115
+ if (govern.jsx) {
116
+ for (const jsx of govern.jsx) {
117
+ switch (jsx.kind) {
118
+ case "jsx.unknownProps.forbid":
119
+ out.push(makeJsxUnknownPropsForbiddenFact({ severity: jsx.severity }));
120
+ break;
121
+ case "jsx.inlineStyle.forbidRaw":
122
+ for (const property of jsx.properties) {
123
+ out.push(
124
+ makeJsxInlineStyleForbiddenRawFact({
125
+ property,
126
+ severity: jsx.severity,
127
+ })
128
+ );
129
+ }
130
+ break;
131
+ case "jsx.importPath.prefer":
132
+ out.push(
133
+ makeJsxImportPathPreferredFact({
134
+ from: jsx.from,
135
+ to: jsx.to,
136
+ imported: jsx.imported,
137
+ because: jsx.because,
138
+ severity: jsx.severity,
139
+ })
140
+ );
141
+ break;
142
+ case "jsx.component.prefer":
143
+ out.push(
144
+ makeJsxComponentPreferredFact({
145
+ from: asComponentId(jsx.from) as ComponentId,
146
+ to: asComponentId(jsx.to) as ComponentId,
147
+ because: jsx.because,
148
+ severity: jsx.severity,
149
+ })
150
+ );
151
+ break;
152
+ }
153
+ }
154
+ }
155
+
156
+ const tailwindPalette = govern.tailwind?.palette;
157
+ const forbiddenPaletteSeverity = ruleSeverity(
158
+ govern.rules?.["tailwind/forbidden-palette"],
159
+ govern.severity ?? "warn"
160
+ );
161
+ if (tailwindPalette?.allow?.length) {
162
+ out.push(
163
+ makeTailwindPaletteAllowFact({
164
+ patterns: tailwindPalette.allow,
165
+ severity: forbiddenPaletteSeverity,
166
+ })
167
+ );
168
+ }
169
+ if (tailwindPalette?.deny?.length) {
170
+ out.push(
171
+ makeTailwindPaletteDenyFact({
172
+ patterns: tailwindPalette.deny,
173
+ severity: forbiddenPaletteSeverity,
174
+ })
175
+ );
176
+ }
177
+
178
+ const unknownClassSeverity = enabledRuleSeverity(
179
+ govern.rules?.["tailwind/unknown-class"],
180
+ "info"
181
+ );
182
+ if (unknownClassSeverity) {
183
+ out.push(makeTailwindUnknownClassEnabledFact({ severity: unknownClassSeverity }));
184
+ }
185
+
186
+ out.push(...compileRuleConfigFacts(govern));
187
+
188
+ return out;
189
+ }
190
+
191
+ const RULE_FAMILY_MEMBERS: Record<string, readonly string[]> = {
192
+ "tokens/hardcoded-values": [
193
+ "styles/no-raw-color",
194
+ "styles/no-raw-spacing",
195
+ "tokens/require-dual-fallback",
196
+ "theme/no-theme-coupled-literal",
197
+ ],
198
+ "components/usage": [
199
+ "components/forbidden-prop-value",
200
+ "components/preferred-component",
201
+ "components/unknown-prop",
202
+ "props/invalid-value",
203
+ ],
204
+ "a11y/wcag": ["a11y/required-accessible-name"],
205
+ };
206
+
207
+ function compileRuleConfigFacts(govern: GovernanceConfig): PolicyFact[] {
208
+ const rules = govern.rules;
209
+ if (!rules && !govern.canonicalSources?.length) return [];
210
+
211
+ const configs = new Map<string, ReturnType<typeof ruleConfigFromValue>>();
212
+ for (const [ruleId, value] of Object.entries(rules ?? {})) {
213
+ const family = RULE_FAMILY_MEMBERS[ruleId];
214
+ if (!family) continue;
215
+ const config = ruleConfigFromValue(value, govern.severity ?? "warn");
216
+ for (const memberRuleId of family) configs.set(memberRuleId, config);
217
+ }
218
+
219
+ for (const [ruleId, value] of Object.entries(rules ?? {})) {
220
+ configs.set(ruleId, ruleConfigFromValue(value, govern.severity ?? "warn"));
221
+ }
222
+
223
+ if (
224
+ govern.canonicalSources?.length &&
225
+ configs.get("components/prefer-library")?.enabled !== false
226
+ ) {
227
+ const existing = configs.get("components/prefer-library");
228
+ configs.set("components/prefer-library", {
229
+ enabled: true,
230
+ severity: existing?.severity ?? govern.severity ?? "warn",
231
+ options: {
232
+ ...existing?.options,
233
+ canonicalSources:
234
+ existing?.options?.canonicalSources ?? govern.canonicalSources,
235
+ },
236
+ });
237
+ }
238
+
239
+ return [...configs.entries()].map(([ruleId, config]) =>
240
+ makeGovernanceRuleConfigFact({
241
+ ruleId,
242
+ enabled: config.enabled,
243
+ severity: config.severity,
244
+ options: config.options,
245
+ })
246
+ );
247
+ }
248
+
249
+ function ruleConfigFromValue(
250
+ value: unknown,
251
+ fallbackSeverity: "error" | "warn" | "info"
252
+ ): { enabled: boolean; severity?: "error" | "warn" | "info"; options?: Record<string, unknown> } {
253
+ if (value === false) return { enabled: false };
254
+ if (value === true) return { enabled: true, severity: fallbackSeverity };
255
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
256
+ return { enabled: true, severity: fallbackSeverity };
257
+ }
258
+ const record = value as {
259
+ enabled?: unknown;
260
+ severity?: unknown;
261
+ options?: unknown;
262
+ };
263
+ const options =
264
+ record.options && typeof record.options === "object" && !Array.isArray(record.options)
265
+ ? (record.options as Record<string, unknown>)
266
+ : undefined;
267
+ return {
268
+ enabled: record.enabled !== false,
269
+ severity: parseSeverity(record.severity) ?? fallbackSeverity,
270
+ options,
271
+ };
272
+ }
273
+
274
+ function enabledRuleSeverity(
275
+ value: unknown,
276
+ fallback: "error" | "warn" | "info"
277
+ ): "error" | "warn" | "info" | null {
278
+ if (value === true) return fallback;
279
+ if (!value || typeof value !== "object") return null;
280
+ const record = value as { enabled?: unknown; severity?: unknown };
281
+ if (record.enabled === false) return null;
282
+ if (record.enabled !== true) return null;
283
+ return parseSeverity(record.severity) ?? fallback;
284
+ }
285
+
286
+ function ruleSeverity(
287
+ value: unknown,
288
+ fallback: "error" | "warn" | "info"
289
+ ): "error" | "warn" | "info" {
290
+ if (!value || typeof value !== "object") return fallback;
291
+ return parseSeverity((value as { severity?: unknown }).severity) ?? fallback;
292
+ }
293
+
294
+ function parseSeverity(value: unknown): "error" | "warn" | "info" | undefined {
295
+ if (value === "warning") return "warn";
296
+ return value === "error" || value === "warn" || value === "info" ? value : undefined;
297
+ }
298
+
299
+ // ---------------------------------------------------------------------------
300
+ // Component → facts
301
+ // ---------------------------------------------------------------------------
302
+
303
+ interface ComponentFactSource {
304
+ meta: { name: string; description?: string; category?: string };
305
+ guidance?: { when?: string[]; whenNot?: string[] };
306
+ props?: Record<string, PropDefinition>;
307
+ governance?: ComponentGovernanceRecord[];
308
+ filePath?: string;
309
+ }
310
+
311
+ function isResolvedGovernedDefinition(value: unknown): value is ResolvedGovernedFragmentDefinition {
312
+ if (!value || typeof value !== "object") return false;
313
+ return "governance" in value && Array.isArray((value as { governance: unknown }).governance);
314
+ }
315
+
316
+ function isGovernedDefinition(value: unknown): value is GovernedFragmentDefinition {
317
+ if (!value || typeof value !== "object") return false;
318
+ return "govern" in value || "governance" in value;
319
+ }
320
+
321
+ function toComponentFactSource(input: ComponentFactInput): ComponentFactSource {
322
+ if (isResolvedGovernedDefinition(input)) {
323
+ return {
324
+ meta: input.meta,
325
+ guidance: input.guidance,
326
+ props: (input.props ?? {}) as Record<string, PropDefinition>,
327
+ governance: input.governance,
328
+ };
329
+ }
330
+ if (isGovernedDefinition(input)) {
331
+ return {
332
+ meta: input.meta,
333
+ guidance: input.guidance,
334
+ props: (input.props ?? {}) as Record<string, PropDefinition>,
335
+ governance: resolveComponentGovernance(input),
336
+ };
337
+ }
338
+ // CompiledFragment
339
+ return {
340
+ meta: input.meta,
341
+ guidance: input.guidance ?? input.usage,
342
+ props: input.props,
343
+ governance: input.governance,
344
+ filePath: input.filePath,
345
+ };
346
+ }
347
+
348
+ export type ComponentFactInput =
349
+ | CompiledFragment
350
+ | GovernedFragmentDefinition
351
+ | ResolvedGovernedFragmentDefinition;
352
+
353
+ export function compileComponentFacts(componentId: ComponentId, input: ComponentFactInput): Fact[] {
354
+ const source = toComponentFactSource(input);
355
+ const facts: Fact[] = [];
356
+
357
+ facts.push(
358
+ makeComponentMetadataFact({
359
+ componentId,
360
+ name: source.meta.name,
361
+ description: source.meta.description,
362
+ category: source.meta.category,
363
+ filePath: source.filePath,
364
+ })
365
+ );
366
+
367
+ for (const [propName, prop] of Object.entries(source.props ?? {})) {
368
+ facts.push(
369
+ makePropMetadataFact({
370
+ componentId,
371
+ prop: propName,
372
+ type: prop.type,
373
+ values: prop.values ? [...prop.values] : undefined,
374
+ required: prop.required ?? false,
375
+ default: prop.default,
376
+ description: prop.description,
377
+ })
378
+ );
379
+ }
380
+
381
+ for (const record of source.governance ?? []) {
382
+ const fact = compileComponentGovernanceRecord(componentId, record);
383
+ if (fact) facts.push(fact);
384
+ }
385
+
386
+ return facts;
387
+ }
388
+
389
+ function compileComponentGovernanceRecord(
390
+ componentId: ComponentId,
391
+ record: ComponentGovernanceRecord
392
+ ): Fact | undefined {
393
+ switch (record.kind) {
394
+ case "capability":
395
+ return makeComponentCapabilityFact({ componentId, capability: record.capability });
396
+ case "prop.value.avoid":
397
+ return makePropValueAvoidedFact({
398
+ componentId,
399
+ prop: record.prop,
400
+ value: record.value,
401
+ because: record.because,
402
+ suggest: record.suggest,
403
+ severity: record.severity,
404
+ });
405
+ case "prop.value.forbid":
406
+ return makePropValueForbiddenFact({
407
+ componentId,
408
+ prop: record.prop,
409
+ value: record.value,
410
+ pathPattern: record.when?.path,
411
+ because: record.because,
412
+ replaceWith: record.fix?.replaceWith,
413
+ severity: record.severity,
414
+ });
415
+ case "a11y.requireName":
416
+ return makeA11yNameRequiredFact({
417
+ componentId,
418
+ because: record.because,
419
+ severity: record.severity,
420
+ });
421
+ }
422
+ }