@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,394 @@
1
+ /**
2
+ * In-memory index over Fact records.
3
+ *
4
+ * Rules consume the index, never raw fact arrays. The query surface is
5
+ * intentionally narrow: components.byId / propsOf / propOf / capabilitiesOf,
6
+ * and policy.<predicate>(componentId, opts?). This keeps Phase 5 rules pure
7
+ * and content-addressed.
8
+ */
9
+
10
+ import type {
11
+ A11yNameRequiredFact,
12
+ ComponentCapabilityFact,
13
+ ComponentId,
14
+ ComponentMetadataFact,
15
+ Fact,
16
+ FactId,
17
+ FactKind,
18
+ FactOfKind,
19
+ GovernanceRuleConfigFact,
20
+ JsxComponentPreferredFact,
21
+ JsxImportPathPreferredFact,
22
+ JsxInlineStyleForbiddenRawFact,
23
+ JsxUnknownPropsForbiddenFact,
24
+ PropMetadataFact,
25
+ PropValueAvoidedFact,
26
+ PropValueForbiddenFact,
27
+ ScaleFact,
28
+ ScaleValueFact,
29
+ StyleCssVarsMustBeDefinedFact,
30
+ StyleFontSizeScaleFact,
31
+ StylePropertyScaleFact,
32
+ StyleRawColorForbiddenFact,
33
+ StyleRawDimensionForbiddenFact,
34
+ TailwindPaletteAllowFact,
35
+ TailwindPaletteDenyFact,
36
+ TailwindUnknownClassEnabledFact,
37
+ TokenDefinitionFact,
38
+ } from "./types.js";
39
+ import { canonicalJson } from "./ids.js";
40
+ import { canonicalizeOwnedComponentId, canonicalizeOwnedImport } from "../package-identity.js";
41
+ import { ownedComponentIdsEqual, ownedImportsEqual } from "../package-identity-match.js";
42
+
43
+ export interface FactEvidence {
44
+ factId: FactId;
45
+ fact: Fact;
46
+ }
47
+
48
+ interface FactWithComponent {
49
+ componentId: ComponentId;
50
+ }
51
+
52
+ function hasComponentId(fact: Fact): fact is Fact & FactWithComponent {
53
+ return "componentId" in fact && typeof (fact as FactWithComponent).componentId === "string";
54
+ }
55
+
56
+ function logicalComponentId(componentId: ComponentId): ComponentId {
57
+ return canonicalizeOwnedComponentId(componentId) as ComponentId;
58
+ }
59
+
60
+ function logicalFactForComparison(fact: Fact): Record<string, unknown> {
61
+ if (fact.kind === "jsx_import_path_preferred") {
62
+ return {
63
+ ...fact,
64
+ from: canonicalizeOwnedImport(fact.from),
65
+ to: canonicalizeOwnedImport(fact.to),
66
+ };
67
+ }
68
+ if (fact.kind === "jsx_component_preferred") {
69
+ return {
70
+ ...fact,
71
+ from: logicalComponentId(fact.from),
72
+ to: logicalComponentId(fact.to),
73
+ };
74
+ }
75
+ if (fact.kind === "usage_import") {
76
+ return { ...fact, source: canonicalizeOwnedImport(fact.source) };
77
+ }
78
+ if (hasComponentId(fact)) {
79
+ return { ...fact, componentId: logicalComponentId(fact.componentId) };
80
+ }
81
+ return { ...fact };
82
+ }
83
+
84
+ function uniqueByLogicalIdentity<T>(items: readonly T[], keyOf: (item: T) => string): T[] {
85
+ const seen = new Set<string>();
86
+ return items.filter((item) => {
87
+ const key = keyOf(item);
88
+ if (seen.has(key)) return false;
89
+ seen.add(key);
90
+ return true;
91
+ });
92
+ }
93
+
94
+ /**
95
+ * Match a literal path against a simple glob pattern. `*` matches any run of
96
+ * non-separator characters; `**` matches any run of characters including
97
+ * separators.
98
+ */
99
+ export function matchesGlob(path: string, pattern: string): boolean {
100
+ const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&");
101
+ const expanded = escaped
102
+ .replace(/\*\*/g, "::DOUBLE::")
103
+ .replace(/\*/g, "[^/]*")
104
+ .replace(/::DOUBLE::/g, ".*");
105
+ return new RegExp(`^${expanded}$`).test(path);
106
+ }
107
+
108
+ export class FactIndex {
109
+ private readonly facts = new Map<FactId, Fact>();
110
+ private readonly idsByKind = new Map<FactKind, Set<FactId>>();
111
+ private readonly idsByComponent = new Map<ComponentId, Set<FactId>>();
112
+
113
+ add(fact: Fact): void {
114
+ const existing = this.facts.get(fact.id);
115
+ if (existing) {
116
+ if (
117
+ canonicalJson(logicalFactForComparison(existing)) !==
118
+ canonicalJson(logicalFactForComparison(fact))
119
+ ) {
120
+ console.warn(
121
+ `FactIndex: conflicting facts for id ${fact.id} — keeping first fact, skipping ${describeFactForConflict(fact)}`
122
+ );
123
+ }
124
+ return;
125
+ }
126
+ this.facts.set(fact.id, fact);
127
+
128
+ let byKind = this.idsByKind.get(fact.kind);
129
+ if (!byKind) {
130
+ byKind = new Set();
131
+ this.idsByKind.set(fact.kind, byKind);
132
+ }
133
+ byKind.add(fact.id);
134
+
135
+ if (hasComponentId(fact)) {
136
+ const componentId = logicalComponentId(fact.componentId);
137
+ let byComponent = this.idsByComponent.get(componentId);
138
+ if (!byComponent) {
139
+ byComponent = new Set();
140
+ this.idsByComponent.set(componentId, byComponent);
141
+ }
142
+ byComponent.add(fact.id);
143
+ }
144
+ }
145
+
146
+ addMany(facts: Iterable<Fact>): void {
147
+ for (const fact of facts) this.add(fact);
148
+ }
149
+
150
+ get(id: FactId): Fact | undefined {
151
+ return this.facts.get(id);
152
+ }
153
+
154
+ has(id: FactId): boolean {
155
+ return this.facts.has(id);
156
+ }
157
+
158
+ size(): number {
159
+ return this.facts.size;
160
+ }
161
+
162
+ all(): Fact[] {
163
+ return [...this.facts.values()];
164
+ }
165
+
166
+ byKind<K extends FactKind>(kind: K): Array<FactOfKind<K>> {
167
+ const ids = this.idsByKind.get(kind);
168
+ if (!ids) return [];
169
+ const out: FactOfKind<K>[] = [];
170
+ for (const id of ids) {
171
+ const fact = this.facts.get(id);
172
+ if (fact && fact.kind === kind) out.push(fact as FactOfKind<K>);
173
+ }
174
+ return out;
175
+ }
176
+
177
+ /**
178
+ * Resolve evidence for a list of fact IDs. Throws if any ID is missing —
179
+ * findings must never reference facts that aren't in the index.
180
+ */
181
+ evidence(ids: readonly FactId[]): FactEvidence[] {
182
+ const missing: FactId[] = [];
183
+ const out: FactEvidence[] = [];
184
+ for (const id of ids) {
185
+ const fact = this.facts.get(id);
186
+ if (!fact) {
187
+ missing.push(id);
188
+ continue;
189
+ }
190
+ out.push({ factId: id, fact });
191
+ }
192
+ if (missing.length) {
193
+ throw new Error(`FactIndex.evidence: missing facts in index: ${missing.join(", ")}`);
194
+ }
195
+ return out;
196
+ }
197
+
198
+ // ---------------------------------------------------------------------
199
+ // Component queries
200
+ // ---------------------------------------------------------------------
201
+
202
+ components = {
203
+ list: (): ComponentMetadataFact[] =>
204
+ uniqueByLogicalIdentity(this.byKind("component"), (fact) =>
205
+ canonicalJson({
206
+ ...logicalFactForComparison(fact),
207
+ id: undefined,
208
+ })
209
+ ),
210
+ byId: (componentId: ComponentId): ComponentMetadataFact | undefined =>
211
+ this.byComponentOfKind<ComponentMetadataFact>(componentId, "component")[0],
212
+ propsOf: (componentId: ComponentId): PropMetadataFact[] =>
213
+ this.byComponentOfKind<PropMetadataFact>(componentId, "prop_metadata"),
214
+ propOf: (componentId: ComponentId, prop: string): PropMetadataFact | undefined =>
215
+ this.components.propsOf(componentId).find((p) => p.prop === prop),
216
+ capabilitiesOf: (componentId: ComponentId): ComponentCapabilityFact[] =>
217
+ this.byComponentOfKind<ComponentCapabilityFact>(componentId, "component_capability"),
218
+ };
219
+
220
+ // ---------------------------------------------------------------------
221
+ // Policy queries
222
+ // ---------------------------------------------------------------------
223
+
224
+ policy = {
225
+ forbiddenPropValues: (
226
+ componentId: ComponentId,
227
+ opts: { prop?: string; path?: string; value?: unknown } = {}
228
+ ): PropValueForbiddenFact[] => {
229
+ const all = this.byComponentOfKind<PropValueForbiddenFact>(
230
+ componentId,
231
+ "prop_value_forbidden"
232
+ );
233
+ return all.filter((fact) => {
234
+ if (opts.prop !== undefined && fact.prop !== opts.prop) return false;
235
+ if (opts.value !== undefined && fact.value !== opts.value) return false;
236
+ if (opts.path !== undefined && fact.pathPattern) {
237
+ if (!matchesGlob(opts.path, fact.pathPattern)) return false;
238
+ }
239
+ return true;
240
+ });
241
+ },
242
+
243
+ avoidedPropValues: (
244
+ componentId: ComponentId,
245
+ opts: { prop?: string; value?: unknown } = {}
246
+ ): PropValueAvoidedFact[] => {
247
+ const all = this.byComponentOfKind<PropValueAvoidedFact>(componentId, "prop_value_avoided");
248
+ return all.filter((fact) => {
249
+ if (opts.prop !== undefined && fact.prop !== opts.prop) return false;
250
+ if (opts.value !== undefined && fact.value !== opts.value) return false;
251
+ return true;
252
+ });
253
+ },
254
+
255
+ a11yNameRequired: (componentId: ComponentId): A11yNameRequiredFact | undefined =>
256
+ this.byComponentOfKind<A11yNameRequiredFact>(componentId, "a11y_name_required")[0],
257
+
258
+ scales: (): ScaleFact[] => this.byKind("scale"),
259
+
260
+ scale: (name: string): ScaleFact | undefined =>
261
+ this.byKind("scale").find((s) => s.name === name),
262
+
263
+ scaleValues: (name: string): ScaleValueFact[] =>
264
+ this.byKind("scale_value").filter((v) => v.scale === name),
265
+
266
+ rawColorPolicy: (): StyleRawColorForbiddenFact | undefined =>
267
+ this.byKind("style_raw_color_forbidden")[0],
268
+
269
+ rawDimensionPolicy: (): StyleRawDimensionForbiddenFact | undefined =>
270
+ this.byKind("style_raw_dimension_forbidden")[0],
271
+
272
+ propertyScale: (property: string): StylePropertyScaleFact | undefined =>
273
+ this.byKind("style_property_scale").find((p) => p.property === property),
274
+
275
+ fontSizeScale: (): StyleFontSizeScaleFact | undefined =>
276
+ this.byKind("style_font_size_scale")[0],
277
+
278
+ cssVarsMustBeDefined: (): StyleCssVarsMustBeDefinedFact | undefined =>
279
+ this.byKind("style_css_vars_must_be_defined")[0],
280
+
281
+ tailwindPaletteAllow: (): TailwindPaletteAllowFact | undefined =>
282
+ this.byKind("tailwind_palette_allow")[0],
283
+
284
+ tailwindPaletteDeny: (): TailwindPaletteDenyFact | undefined =>
285
+ this.byKind("tailwind_palette_deny")[0],
286
+
287
+ tailwindUnknownClassEnabled: (): TailwindUnknownClassEnabledFact | undefined =>
288
+ this.byKind("tailwind_unknown_class_enabled")[0],
289
+
290
+ ruleConfig: (ruleId: string): GovernanceRuleConfigFact | undefined =>
291
+ this.byKind("governance_rule_config").find((fact) => fact.ruleId === ruleId),
292
+
293
+ jsxUnknownPropsForbidden: (): JsxUnknownPropsForbiddenFact | undefined =>
294
+ this.byKind("jsx_unknown_props_forbidden")[0],
295
+
296
+ jsxInlineStyleForbiddenRaw: (
297
+ opts: { property?: string } = {}
298
+ ): JsxInlineStyleForbiddenRawFact[] => {
299
+ const all = this.byKind("jsx_inline_style_forbidden_raw");
300
+ return opts.property === undefined ? all : all.filter((f) => f.property === opts.property);
301
+ },
302
+
303
+ jsxImportPathPreferred: (
304
+ opts: { from?: string; imported?: string } = {}
305
+ ): JsxImportPathPreferredFact[] => {
306
+ const all = uniqueByLogicalIdentity(this.byKind("jsx_import_path_preferred"), (fact) =>
307
+ canonicalJson({
308
+ from: canonicalizeOwnedImport(fact.from),
309
+ to: canonicalizeOwnedImport(fact.to),
310
+ imported: fact.imported,
311
+ because: fact.because,
312
+ severity: fact.severity,
313
+ })
314
+ );
315
+ return all.filter((fact) => {
316
+ if (opts.from !== undefined && !ownedImportsEqual(fact.from, opts.from)) return false;
317
+ if (opts.imported !== undefined && fact.imported !== opts.imported) return false;
318
+ return true;
319
+ });
320
+ },
321
+
322
+ jsxComponentPreferred: (opts: { from?: ComponentId } = {}): JsxComponentPreferredFact[] => {
323
+ const all = uniqueByLogicalIdentity(this.byKind("jsx_component_preferred"), (fact) =>
324
+ canonicalJson({
325
+ from: canonicalizeOwnedComponentId(fact.from),
326
+ to: canonicalizeOwnedComponentId(fact.to),
327
+ because: fact.because,
328
+ severity: fact.severity,
329
+ })
330
+ );
331
+ return opts.from === undefined
332
+ ? all
333
+ : all.filter((fact) => ownedComponentIdsEqual(fact.from, opts.from!));
334
+ },
335
+ };
336
+
337
+ // ---------------------------------------------------------------------
338
+ // Token registry queries
339
+ // ---------------------------------------------------------------------
340
+
341
+ tokens = {
342
+ list: (): TokenDefinitionFact[] => this.byKind("token_definition"),
343
+ byCategory: (category: TokenDefinitionFact["category"]): TokenDefinitionFact[] =>
344
+ this.byKind("token_definition").filter((t) => t.category === category),
345
+ };
346
+
347
+ // ---------------------------------------------------------------------
348
+ // Internals
349
+ // ---------------------------------------------------------------------
350
+
351
+ private byComponentOfKind<F extends Fact>(componentId: ComponentId, kind: FactKind): F[] {
352
+ const ids = this.idsByComponent.get(logicalComponentId(componentId));
353
+ if (!ids) return [];
354
+ const out: F[] = [];
355
+ const seen = new Set<string>();
356
+ for (const id of ids) {
357
+ const fact = this.facts.get(id);
358
+ if (fact && fact.kind === kind) {
359
+ const logicalFact = hasComponentId(fact)
360
+ ? {
361
+ ...fact,
362
+ id: undefined,
363
+ componentId: logicalComponentId(fact.componentId),
364
+ }
365
+ : { ...fact, id: undefined };
366
+ const key = canonicalJson(logicalFact);
367
+ if (seen.has(key)) continue;
368
+ seen.add(key);
369
+ out.push(fact as F);
370
+ }
371
+ }
372
+ return out;
373
+ }
374
+ }
375
+
376
+ function describeFactForConflict(fact: Fact): string {
377
+ const parts = [`kind=${fact.kind}`];
378
+ const maybeLocation = "location" in fact ? fact.location : undefined;
379
+ if (maybeLocation && typeof maybeLocation === "object") {
380
+ const loc = maybeLocation as { file?: unknown; line?: unknown; column?: unknown };
381
+ if (typeof loc.file === "string") {
382
+ const line = typeof loc.line === "number" ? `:${loc.line}` : "";
383
+ const column = typeof loc.column === "number" ? `:${loc.column}` : "";
384
+ parts.push(`location=${loc.file}${line}${column}`);
385
+ }
386
+ }
387
+ if ("nodePath" in fact && typeof fact.nodePath === "string") {
388
+ parts.push(`nodePath=${fact.nodePath}`);
389
+ }
390
+ if ("element" in fact && typeof fact.element === "string") {
391
+ parts.push(`element=${fact.element}`);
392
+ }
393
+ return parts.join(" ");
394
+ }