@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,105 @@
1
+ import { describe, expect, it } from "vitest";
2
+
3
+ import { buildAgentFormat } from "./agent-format.js";
4
+ import { agentFormatSchema, type Finding } from "./schemas/index.js";
5
+
6
+ const DEAD_TOOL_NAMES = ["apply_fix", "swap_to_canonical", "tokens.suggest"];
7
+
8
+ function finding(overrides: Partial<Finding> = {}): Finding {
9
+ return {
10
+ ruleId: "styles/no-raw-color",
11
+ ruleVersion: "1.0.0",
12
+ severity: "serious",
13
+ level: "error",
14
+ code: "FUI2001",
15
+ message: "Use a token.",
16
+ fingerprint: "fp_1",
17
+ location: { file: "src/Button.tsx", line: 1, column: 1 },
18
+ evidence: [
19
+ {
20
+ factId: "fact_1",
21
+ fact: {
22
+ kind: "style",
23
+ location: { file: "src/Button.tsx", line: 1, column: 1 },
24
+ },
25
+ },
26
+ ],
27
+ ...overrides,
28
+ };
29
+ }
30
+
31
+ describe("buildAgentFormat", () => {
32
+ it("names only hosted MCP tools in fix plans", () => {
33
+ const agentFormat = buildAgentFormat({
34
+ filesScanned: 1,
35
+ findings: [
36
+ finding({
37
+ fix: {
38
+ kind: "replaceStyleValue",
39
+ title: "Use token",
40
+ property: "color",
41
+ value: "var(--color-brand)",
42
+ deterministic: true,
43
+ },
44
+ }),
45
+ finding({
46
+ ruleId: "components/prefer-library",
47
+ code: "FUI1001",
48
+ fingerprint: "fp_2",
49
+ }),
50
+ finding({
51
+ code: "FUI2002",
52
+ fingerprint: "fp_3",
53
+ attributes: { property: "color", rawValue: "#2563eb" },
54
+ }),
55
+ ],
56
+ });
57
+
58
+ agentFormatSchema.parse(agentFormat);
59
+ const toolNames = [...agentFormat.fix_first, ...agentFormat.remaining].map(
60
+ (entry) => entry.plan.tool
61
+ );
62
+
63
+ expect(new Set(toolNames)).toEqual(new Set(["design_system/conform"]));
64
+ for (const deadName of DEAD_TOOL_NAMES) {
65
+ expect(toolNames).not.toContain(deadName);
66
+ }
67
+ });
68
+
69
+ it("keeps warn-only scans passed when the report summary passed", () => {
70
+ const agentFormat = buildAgentFormat({
71
+ filesScanned: 1,
72
+ passed: true,
73
+ findings: [
74
+ finding({
75
+ severity: "moderate",
76
+ level: "warn",
77
+ }),
78
+ ],
79
+ });
80
+
81
+ expect(agentFormat.passed).toBe(true);
82
+ });
83
+
84
+ it("omits full findings and evidence by default", () => {
85
+ const agentFormat = buildAgentFormat({
86
+ filesScanned: 1,
87
+ findings: [finding()],
88
+ });
89
+
90
+ expect(agentFormat.findings).toBeUndefined();
91
+ expect([...agentFormat.fix_first, ...agentFormat.remaining][0]?.evidence).toEqual([]);
92
+ });
93
+
94
+ it("keeps a gradient score for noisy scans", () => {
95
+ const findings = Array.from({ length: 80 }, (_, index) =>
96
+ finding({
97
+ fingerprint: `fp_${index}`,
98
+ severity: "moderate",
99
+ level: "warn",
100
+ })
101
+ );
102
+
103
+ expect(buildAgentFormat({ filesScanned: 3, findings }).score).toBeGreaterThan(0);
104
+ });
105
+ });
@@ -0,0 +1,141 @@
1
+ import { severityLevel } from "./severity.js";
2
+ import type { AgentFormat, Finding } from "./schemas/index.js";
3
+ import type { SuppressionDirectiveFact } from "./facts/index.js";
4
+
5
+ export interface BuildAgentFormatInput {
6
+ findings: readonly Finding[];
7
+ filesScanned: number;
8
+ passed?: boolean;
9
+ suppressions?: readonly SuppressionDirectiveFact[];
10
+ iteration?: number;
11
+ maxIterations?: number;
12
+ previousScore?: number;
13
+ maxFindings?: number;
14
+ includeEvidence?: boolean;
15
+ includeFindings?: boolean;
16
+ }
17
+
18
+ export function buildAgentFormat(input: BuildAgentFormatInput): AgentFormat {
19
+ const score = scoreFindings(input.findings);
20
+ const visibleFindings = uniqueFindings(input.findings).slice(0, input.maxFindings ?? 50);
21
+ const agentFindings = visibleFindings.map((finding) => ({
22
+ code: finding.code,
23
+ ruleId: finding.ruleId,
24
+ filePath: finding.location.file,
25
+ line: finding.location.line,
26
+ col: finding.location.column,
27
+ severity: finding.severity,
28
+ level: severityLevel(finding.severity),
29
+ message: finding.message,
30
+ evidence: input.includeEvidence ? finding.evidence : [],
31
+ plan: planForFinding(finding),
32
+ }));
33
+
34
+ // Keep this aligned with the report summary: warn-level findings are debt, not
35
+ // scan failure, unless the caller has already folded fail-on-warnings into
36
+ // `passed:false`.
37
+ const hasErrors = input.findings.some((finding) => severityLevel(finding.severity) === "error");
38
+
39
+ return {
40
+ schemaVersion: "typestyle.agent.v1",
41
+ passed: (input.passed ?? true) && !hasErrors,
42
+ score,
43
+ summary: agentSummary(input.findings.length, input.filesScanned, score),
44
+ fix_first: agentFindings.filter((finding) => finding.plan.confidence >= 0.8),
45
+ remaining: agentFindings.filter((finding) => finding.plan.confidence < 0.8),
46
+ suppression_template: {
47
+ directive: '// @fragments-expect-error {code} reason="..." expires="YYYY-MM-DD"',
48
+ requiredFields: ["reason"],
49
+ example:
50
+ '// @fragments-expect-error FUI2001 reason="hero brand red approved" expires="2026-08-01"',
51
+ },
52
+ budget: {
53
+ iteration: input.iteration ?? 0,
54
+ maxIterations: input.maxIterations ?? 3,
55
+ scoreDelta: input.previousScore === undefined ? 0 : score - input.previousScore,
56
+ },
57
+ ...(input.includeFindings ? { findings: [...input.findings] } : {}),
58
+ ...(input.suppressions
59
+ ? {
60
+ suppressions: input.suppressions.map((suppression) => ({
61
+ code: suppression.code,
62
+ reason: suppression.reason,
63
+ ...(suppression.expiresOn ? { expiresOn: suppression.expiresOn } : {}),
64
+ scope: "line" as const,
65
+ })),
66
+ }
67
+ : {}),
68
+ };
69
+ }
70
+
71
+ function uniqueFindings(findings: readonly Finding[]): Finding[] {
72
+ const seen = new Set<string>();
73
+ const out: Finding[] = [];
74
+ for (const finding of findings) {
75
+ const key =
76
+ finding.fingerprint ??
77
+ `${finding.ruleId}\0${finding.location.file}\0${finding.location.line}\0${finding.message}`;
78
+ if (seen.has(key)) continue;
79
+ seen.add(key);
80
+ out.push(finding);
81
+ }
82
+ return out;
83
+ }
84
+
85
+ function scoreFindings(findings: readonly Finding[]): number {
86
+ if (findings.length === 0) return 100;
87
+ let penalty = 0;
88
+ for (const finding of findings) {
89
+ const level = severityLevel(finding.severity);
90
+ penalty += level === "error" ? 10 : level === "warn" ? 3 : 1;
91
+ }
92
+ return Math.max(1, Math.round(100 / (1 + penalty / 40)));
93
+ }
94
+
95
+ function agentSummary(count: number, filesScanned: number, score: number): string {
96
+ if (count === 0) return `passed with score ${score}; no findings`;
97
+ return `${count} finding${count === 1 ? "" : "s"} across ${filesScanned} file${filesScanned === 1 ? "" : "s"}; score ${score}`;
98
+ }
99
+
100
+ function planForFinding(finding: Finding): AgentFormat["fix_first"][number]["plan"] {
101
+ if (finding.fix?.deterministic === true) {
102
+ return {
103
+ tool: "design_system/conform",
104
+ args: {
105
+ filePath: finding.location.file,
106
+ fingerprint: finding.fingerprint,
107
+ apply: "deterministic",
108
+ },
109
+ confidence: 0.95,
110
+ };
111
+ }
112
+ if (
113
+ finding.code?.startsWith("FUI1") ||
114
+ finding.ruleId.includes("component") ||
115
+ finding.ruleId.includes("canonical")
116
+ ) {
117
+ return {
118
+ tool: "design_system/conform",
119
+ args: { filePath: finding.location.file, apply: "deterministic" },
120
+ confidence: 0.65,
121
+ };
122
+ }
123
+ return {
124
+ tool: "design_system/conform",
125
+ args: {
126
+ filePath: finding.location.file,
127
+ property:
128
+ stringAttribute(finding.attributes?.property) ??
129
+ stringAttribute(finding.attributes?.prop) ??
130
+ "color",
131
+ value:
132
+ stringAttribute(finding.attributes?.rawValue) ?? stringAttribute(finding.attributes?.color),
133
+ apply: "deterministic",
134
+ },
135
+ confidence: 0.55,
136
+ };
137
+ }
138
+
139
+ function stringAttribute(value: unknown): string | undefined {
140
+ return typeof value === "string" && value.length > 0 ? value : undefined;
141
+ }
package/src/bundle.ts ADDED
@@ -0,0 +1,149 @@
1
+ import { z } from 'zod';
2
+
3
+ export const bundleSchemaVersionSchema = z.literal(1);
4
+ export const bundleTargetSchema = z.enum([
5
+ 'cursor',
6
+ 'agents',
7
+ 'claude',
8
+ 'copilot',
9
+ ]);
10
+
11
+ export const bundleArtifactMetadataSchema = z.object({
12
+ kind: z.enum([
13
+ 'manifest',
14
+ 'tokens',
15
+ 'design-system',
16
+ 'readme',
17
+ 'helper',
18
+ 'component',
19
+ ]),
20
+ key: z.string().min(1),
21
+ label: z.string().min(1),
22
+ path: z.string().min(1),
23
+ etag: z.string().min(1),
24
+ updatedAt: z.string().datetime(),
25
+ target: bundleTargetSchema.optional(),
26
+ componentId: z.string().optional(),
27
+ contentType: z.enum(['application/json', 'text/markdown']),
28
+ });
29
+
30
+ const bundleManifestRelationSchema = z.object({
31
+ type: z.string().min(1),
32
+ componentId: z.string().min(1),
33
+ name: z.string().min(1),
34
+ });
35
+
36
+ const bundleManifestCompoundChildSchema = z.object({
37
+ componentId: z.string().min(1),
38
+ name: z.string().min(1),
39
+ });
40
+
41
+ export const bundleManifestComponentEntrySchema = z.object({
42
+ componentId: z.string().min(1),
43
+ file: z.string().min(1),
44
+ name: z.string().min(1),
45
+ publicSlug: z.string().nullable(),
46
+ tier: z.enum(['core', 'composition']),
47
+ category: z.string().min(1),
48
+ status: z.string().min(1),
49
+ description: z.string(),
50
+ propCount: z.number().int().nonnegative(),
51
+ hasExamples: z.boolean(),
52
+ hasCompoundChildren: z.boolean(),
53
+ relations: z.array(bundleManifestRelationSchema),
54
+ compoundChildren: z.array(bundleManifestCompoundChildSchema),
55
+ });
56
+
57
+ export const bundleManifestSchema = z.object({
58
+ schemaVersion: bundleSchemaVersionSchema,
59
+ catalogRevision: z.string().min(1),
60
+ catalogUpdatedAt: z.string().datetime(),
61
+ org: z.object({
62
+ id: z.string().min(1),
63
+ name: z.string().min(1),
64
+ slug: z.string().min(1),
65
+ }),
66
+ designSystem: z.object({
67
+ name: z.string().min(1),
68
+ packageName: z.string().nullable(),
69
+ importPath: z.string().nullable(),
70
+ }),
71
+ sourceBinding: z.object({
72
+ bindingId: z.string().min(1),
73
+ projectId: z.string().min(1),
74
+ projectName: z.string().min(1),
75
+ repoFullName: z.string().min(1),
76
+ resolution: z.enum(['explicit', 'auto']),
77
+ }),
78
+ totalComponents: z.number().int().nonnegative(),
79
+ totalTokens: z.number().int().nonnegative(),
80
+ tokenCategories: z.record(z.string(), z.number().int().nonnegative()),
81
+ components: z.record(z.string(), bundleManifestComponentEntrySchema),
82
+ });
83
+
84
+ const bundleTokenEntrySchema = z.object({
85
+ name: z.string().min(1),
86
+ path: z.array(z.string().min(1)),
87
+ category: z.string().min(1),
88
+ value: z.string(),
89
+ type: z.string().optional(),
90
+ description: z.string().optional(),
91
+ });
92
+
93
+ export const bundleTokenFileSchema = z.object({
94
+ schemaVersion: bundleSchemaVersionSchema,
95
+ catalogRevision: z.string().min(1),
96
+ catalogUpdatedAt: z.string().datetime(),
97
+ categories: z.record(
98
+ z.string(),
99
+ z.object({
100
+ count: z.number().int().nonnegative(),
101
+ tokens: z.array(bundleTokenEntrySchema),
102
+ }),
103
+ ),
104
+ flat: z.array(bundleTokenEntrySchema),
105
+ });
106
+
107
+ export const bundleComponentShardSchema = z.object({
108
+ schemaVersion: bundleSchemaVersionSchema,
109
+ catalogRevision: z.string().min(1),
110
+ catalogUpdatedAt: z.string().datetime(),
111
+ componentId: z.string().min(1),
112
+ file: z.string().min(1),
113
+ component: z.object({
114
+ componentKey: z.string().min(1),
115
+ publicRef: z.string().min(1),
116
+ publicId: z.string().nullable(),
117
+ publicSlug: z.string().nullable(),
118
+ isCanonical: z.boolean().optional(),
119
+ name: z.string().min(1),
120
+ tier: z.enum(['core', 'composition']),
121
+ category: z.string().min(1),
122
+ status: z.string().min(1),
123
+ description: z.string(),
124
+ usageGuidance: z.string(),
125
+ dos: z.array(z.string()),
126
+ donts: z.array(z.string()),
127
+ props: z.record(z.string(), z.unknown()),
128
+ relations: z.array(z.record(z.string(), z.unknown())),
129
+ compoundChildren: z.array(bundleManifestCompoundChildSchema),
130
+ examples: z.array(z.record(z.string(), z.unknown())),
131
+ source: z.literal('design-system'),
132
+ sourcePath: z.string().optional(),
133
+ sourceRepoFullName: z.string().optional(),
134
+ parentComponentName: z.string().optional(),
135
+ parentComponentKey: z.string().optional(),
136
+ }),
137
+ });
138
+
139
+ export type BundleSchemaVersion = z.infer<typeof bundleSchemaVersionSchema>;
140
+ export type BundleTarget = z.infer<typeof bundleTargetSchema>;
141
+ export type BundleArtifactMetadata = z.infer<
142
+ typeof bundleArtifactMetadataSchema
143
+ >;
144
+ export type BundleManifestComponentEntry = z.infer<
145
+ typeof bundleManifestComponentEntrySchema
146
+ >;
147
+ export type BundleManifest = z.infer<typeof bundleManifestSchema>;
148
+ export type BundleComponentShard = z.infer<typeof bundleComponentShardSchema>;
149
+ export type BundleTokenFile = z.infer<typeof bundleTokenFileSchema>;
@@ -0,0 +1,73 @@
1
+ import { readFileSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { describe, expect, it } from "vitest";
5
+
6
+ import {
7
+ CODES,
8
+ EXPLAIN_URL_BASE,
9
+ bridgeSourceViolation,
10
+ byCode,
11
+ byRuleId,
12
+ } from "../index.js";
13
+
14
+ const __filename = fileURLToPath(import.meta.url);
15
+ const __dirname = path.dirname(__filename);
16
+ const registryPath = path.resolve(__dirname, "../../../../../docs/codes/registry.json");
17
+
18
+ describe("FUI code registry", () => {
19
+ it("does not contain duplicate codes", () => {
20
+ expect(new Set(CODES.map((entry) => entry.code)).size).toBe(CODES.length);
21
+ });
22
+
23
+ it("does not contain duplicate rule IDs", () => {
24
+ expect(new Set(CODES.map((entry) => entry.ruleId)).size).toBe(CODES.length);
25
+ });
26
+
27
+ it("builds lookup maps from the registry", () => {
28
+ for (const entry of CODES) {
29
+ expect(byCode.get(entry.code)).toBe(entry);
30
+ expect(byRuleId.get(entry.ruleId)).toBe(entry);
31
+ expect(entry.explainUrl).toBe(`${EXPLAIN_URL_BASE}${entry.code}`);
32
+ }
33
+ });
34
+
35
+ it("matches the committed docs registry snapshot", () => {
36
+ const snapshot = JSON.parse(readFileSync(registryPath, "utf8"));
37
+ expect(snapshot).toEqual(CODES);
38
+ });
39
+
40
+ it("bridges source violations into canonical findings with code metadata", () => {
41
+ const bridged = bridgeSourceViolation({
42
+ validator: "scss",
43
+ violation: {
44
+ nodeId: "scss:src/app.css:2:4",
45
+ nodeType: "ScssDeclaration",
46
+ rule: "tokens/require-dual-fallback",
47
+ severity: "serious",
48
+ message: "Token fallback is missing",
49
+ filePath: "src/app.css",
50
+ line: 2,
51
+ column: 4,
52
+ rawValue: "var(--fui-color-accent)",
53
+ },
54
+ });
55
+
56
+ expect(bridged.finding).toMatchObject({
57
+ ruleId: "tokens/require-dual-fallback",
58
+ code: "FUI2003",
59
+ helpUrl: `${EXPLAIN_URL_BASE}FUI2003`,
60
+ location: { file: "src/app.css", line: 2, column: 4 },
61
+ attributes: {
62
+ source: "source-validator",
63
+ validator: "scss",
64
+ rawValue: "var(--fui-color-accent)",
65
+ },
66
+ });
67
+ expect(bridged.violation).toMatchObject({
68
+ code: "FUI2003",
69
+ helpUrl: `${EXPLAIN_URL_BASE}FUI2003`,
70
+ level: "error",
71
+ });
72
+ });
73
+ });