@usefragments/core 1.10.2 → 2.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 (130) hide show
  1. package/dist/{chunk-MZ4SW3TP.js → chunk-3IOWHECM.js} +8 -1
  2. package/dist/{chunk-MZ4SW3TP.js.map → chunk-3IOWHECM.js.map} +1 -1
  3. package/dist/chunk-7ULGH74M.js +66 -0
  4. package/dist/chunk-7ULGH74M.js.map +1 -0
  5. package/dist/{chunk-XN3LSDPY.js → chunk-BMPYIUZE.js} +612 -344
  6. package/dist/chunk-BMPYIUZE.js.map +1 -0
  7. package/dist/{chunk-YF65VYRY.js → chunk-ML5S6QNU.js} +191 -169
  8. package/dist/chunk-ML5S6QNU.js.map +1 -0
  9. package/dist/{chunk-3LLRNCPX.js → chunk-MZ2FS7U4.js} +1 -1
  10. package/dist/chunk-MZ2FS7U4.js.map +1 -0
  11. package/dist/chunk-PWIJMOI4.js +202 -0
  12. package/dist/chunk-PWIJMOI4.js.map +1 -0
  13. package/dist/chunk-RYFULE43.js +578 -0
  14. package/dist/chunk-RYFULE43.js.map +1 -0
  15. package/dist/codes/index.d.ts +2 -2
  16. package/dist/codes/index.js +3 -2
  17. package/dist/compiled-types/index.d.ts +327 -2
  18. package/dist/compiled-types/index.js +1 -1
  19. package/dist/generate/index.d.ts +3 -2
  20. package/dist/{governance-CLk_wkP9.d.ts → governance-hOPXGbbs.d.ts} +474 -515
  21. package/dist/governance-telemetry.d.ts +6 -0
  22. package/dist/governance-telemetry.js +1 -1
  23. package/dist/{index-_sxhUNqx.d.ts → index-C8bcXVav.d.ts} +448 -448
  24. package/dist/index.d.ts +3280 -1305
  25. package/dist/index.js +2414 -350
  26. package/dist/index.js.map +1 -1
  27. package/dist/manifest.d.ts +228 -0
  28. package/dist/manifest.js +24 -0
  29. package/dist/manifest.js.map +1 -0
  30. package/dist/preview/index.js +45 -1
  31. package/dist/preview/index.js.map +1 -1
  32. package/dist/preview-runtime.d.ts +1 -2
  33. package/dist/preview-runtime.js +150 -14
  34. package/dist/preview-runtime.js.map +1 -1
  35. package/dist/react-types.d.ts +1 -2
  36. package/dist/registry.d.ts +1412 -203
  37. package/dist/registry.js +30 -3
  38. package/dist/schemas/index.d.ts +1 -1
  39. package/dist/schemas/index.js +3 -2
  40. package/dist/storyAdapter.d.ts +1 -2
  41. package/dist/storyAdapter.js +11 -49
  42. package/dist/storyAdapter.js.map +1 -1
  43. package/dist/test-utils.d.ts +3 -2
  44. package/dist/topology/index.d.ts +1 -1
  45. package/dist/topology/index.js +1 -1
  46. package/package.json +8 -2
  47. package/src/__tests__/contract-parser.test.ts +318 -277
  48. package/src/__tests__/preview-runtime-hook.test.tsx +315 -0
  49. package/src/__tests__/preview-runtime.test.tsx +30 -8
  50. package/src/__tests__/schema.test.ts +191 -14
  51. package/src/analysis-plan/analysis-plan-v1.test.ts +320 -0
  52. package/src/analysis-plan/coverage.ts +181 -0
  53. package/src/analysis-plan/digest.ts +141 -0
  54. package/src/analysis-plan/index.ts +34 -0
  55. package/src/analysis-plan/types.ts +207 -0
  56. package/src/approved-contract-tokens.test.ts +39 -0
  57. package/src/approved-contract-tokens.ts +18 -0
  58. package/src/codes/__tests__/codes.test.ts +13 -0
  59. package/src/codes/codes.ts +40 -0
  60. package/src/compiled-types/index.ts +640 -39
  61. package/src/compiled-types/parse.test.ts +145 -4
  62. package/src/component-contract.ts +95 -53
  63. package/src/composition.ts +7 -13
  64. package/src/constants.ts +3 -6
  65. package/src/contract/hash.test.ts +20 -0
  66. package/src/contract/hash.ts +66 -9
  67. package/src/contract/index.ts +24 -1
  68. package/src/contract/manifest.test.ts +94 -0
  69. package/src/contract/manifest.ts +68 -0
  70. package/src/contract/preimage.test.ts +219 -1
  71. package/src/contract/preimage.ts +326 -6
  72. package/src/contract/stamp.test.ts +3 -0
  73. package/src/contract/stamp.ts +1 -1
  74. package/src/contract-parser.ts +54 -30
  75. package/src/defineFragment.test.ts +476 -91
  76. package/src/defineFragment.ts +204 -114
  77. package/src/domain-ids.test.ts +35 -0
  78. package/src/domain-ids.ts +61 -0
  79. package/src/evaluation/evaluate.test.ts +522 -0
  80. package/src/evaluation/evaluate.ts +690 -0
  81. package/src/evaluation/evaluation-v2-receipt-v1.test.ts +772 -0
  82. package/src/evaluation/index.ts +58 -0
  83. package/src/evaluation/receipt.ts +753 -0
  84. package/src/evaluation/types.ts +406 -0
  85. package/src/facts/builders.ts +2 -0
  86. package/src/facts/compile.ts +29 -6
  87. package/src/facts/fact-index.ts +13 -3
  88. package/src/facts/fact-integrity-v1.test.ts +172 -0
  89. package/src/facts/facts.test.ts +15 -0
  90. package/src/facts/ids.ts +46 -3
  91. package/src/facts/index.ts +14 -1
  92. package/src/facts/integrity.ts +134 -0
  93. package/src/facts/types.ts +36 -0
  94. package/src/governance-integrity.test.ts +1 -0
  95. package/src/governance-integrity.ts +5 -3
  96. package/src/governance-telemetry.ts +8 -0
  97. package/src/governance.ts +70 -8
  98. package/src/index.ts +230 -37
  99. package/src/preview/validation.test.ts +62 -0
  100. package/src/preview/validation.ts +48 -2
  101. package/src/preview-runtime.tsx +227 -20
  102. package/src/registry-install-plan.ts +200 -109
  103. package/src/registry-shards.test.ts +263 -0
  104. package/src/registry.ts +237 -0
  105. package/src/repository-binding.test.ts +50 -0
  106. package/src/repository-binding.ts +96 -0
  107. package/src/rules/families.test.ts +36 -0
  108. package/src/rules/finding.ts +7 -2
  109. package/src/rules/index.ts +17 -1
  110. package/src/rules/rule-config.test.ts +66 -0
  111. package/src/rules/rule-config.ts +73 -0
  112. package/src/rules/rules.test.ts +26 -0
  113. package/src/rules/tokens-css-vars-must-be-defined.test.ts +51 -2
  114. package/src/rules/tokens-css-vars-must-be-defined.ts +34 -1
  115. package/src/schema.ts +293 -113
  116. package/src/schemas/index.ts +1 -1
  117. package/src/storyAdapter.test.ts +68 -12
  118. package/src/storyAdapter.ts +44 -75
  119. package/src/topology/resolve-area.ts +1 -1
  120. package/src/types.ts +258 -40
  121. package/dist/chunk-3LLRNCPX.js.map +0 -1
  122. package/dist/chunk-RANPUC6C.js +0 -72
  123. package/dist/chunk-RANPUC6C.js.map +0 -1
  124. package/dist/chunk-XN3LSDPY.js.map +0 -1
  125. package/dist/chunk-YF65VYRY.js.map +0 -1
  126. package/src/fragment-types.ts +0 -214
  127. package/src/react-create-element.test.ts +0 -22
  128. package/src/react-create-element.ts +0 -12
  129. package/src/storyFilters.test.ts +0 -350
  130. package/src/storyFilters.ts +0 -253
@@ -5,7 +5,7 @@
5
5
  * by AI agents, MCP servers, and context generators.
6
6
  */
7
7
 
8
- import type { ComponentGovernanceRecord } from '../governance.js';
8
+ import type { ComponentGovernanceRecord } from "../governance.js";
9
9
 
10
10
  /**
11
11
  * Component metadata
@@ -14,6 +14,7 @@ export interface FragmentMeta {
14
14
  name: string;
15
15
  description: string;
16
16
  category: string;
17
+ aliases?: string[];
17
18
  tags?: string[];
18
19
  status?: "stable" | "beta" | "deprecated" | "experimental";
19
20
  since?: string;
@@ -28,10 +29,29 @@ export interface FragmentMeta {
28
29
  export interface FragmentUsage {
29
30
  when: string[];
30
31
  whenNot: string[];
32
+ choose?: Record<string, string>;
33
+ /** Serialized v3 anti-pattern guidance; `good` is checked JSX source. */
34
+ dont?: Array<{
35
+ reason: string;
36
+ bad: string;
37
+ good: string;
38
+ }>;
31
39
  guidelines?: string[];
32
40
  accessibility?: string[];
33
41
  }
34
42
 
43
+ /** Static state-space hints carried from a v3 fragment definition. */
44
+ export interface CompiledFragmentMatrix {
45
+ axes?: Record<string, "auto" | readonly string[]>;
46
+ forced?: string[];
47
+ worstCase?: Record<string, unknown>;
48
+ }
49
+
50
+ /** Optional design-tool reference carried from a v3 fragment definition. */
51
+ export interface CompiledFragmentDesign {
52
+ figmaNode?: string;
53
+ }
54
+
35
55
  /**
36
56
  * Definition for a single prop
37
57
  */
@@ -50,6 +70,8 @@ export interface PropDefinition {
50
70
  step?: number;
51
71
  presetColors?: string[];
52
72
  };
73
+ /** Visibility annotation carried from a v3 fragment definition. */
74
+ visibility?: "public" | "internal" | "hidden";
53
75
  }
54
76
 
55
77
  /**
@@ -72,20 +94,38 @@ export interface FragmentContract {
72
94
  /** Per-component performance budget override in bytes (gzipped) */
73
95
  performanceBudget?: number;
74
96
  /** Sub-component slot metadata for compound components */
75
- compoundChildren?: Record<string, {
76
- required?: boolean;
77
- accepts?: string[];
78
- description?: string;
79
- }>;
97
+ compoundChildren?: Record<
98
+ string,
99
+ {
100
+ required?: boolean;
101
+ accepts?: string[];
102
+ description?: string;
103
+ }
104
+ >;
80
105
  /** Canonical JSX usage examples showing how to assemble the component */
81
106
  canonicalUsage?: string[];
82
107
  }
83
108
 
109
+ /**
110
+ * Per-component preview configuration carried from either contract input.
111
+ * JSON contracts use module/css hints; v3 definitions use provider and
112
+ * visual-diff-region hints. Both are projections of the same compiled object.
113
+ */
114
+ export interface CompiledFragmentPreview {
115
+ setupModule?: string;
116
+ wrapperModule?: string;
117
+ wrapperExport?: string;
118
+ css?: string[];
119
+ theme?: "light" | "dark";
120
+ providers?: unknown[];
121
+ dynamicRegions?: string[];
122
+ }
123
+
84
124
  /**
85
125
  * Provenance tracking for generated fragments
86
126
  */
87
127
  export interface FragmentGenerated {
88
- source: "storybook" | "manual" | "ai" | "extracted" | "merged" | "migrated";
128
+ source: "storybook" | "manual" | "ai" | "scan" | "extracted" | "merged" | "migrated";
89
129
  sourceFile?: string;
90
130
  /** @deprecated Use provenance.verified instead */
91
131
  confidence?: number;
@@ -124,7 +164,7 @@ export interface PerformanceData {
124
164
  /** Raw (minified, not gzipped) bundle size in bytes */
125
165
  rawSize: number;
126
166
  /** Complexity classification */
127
- complexity: 'lightweight' | 'moderate' | 'heavy';
167
+ complexity: "lightweight" | "moderate" | "heavy";
128
168
  /** Percentage of budget used (0-100+) */
129
169
  budgetPercent: number;
130
170
  /** Whether the component exceeds its budget */
@@ -137,7 +177,7 @@ export interface PerformanceData {
137
177
 
138
178
  export interface ObservedUsageProp {
139
179
  name: string;
140
- kind: 'static' | 'dynamic' | 'spread' | 'jsx' | 'boolean' | 'null';
180
+ kind: "static" | "dynamic" | "spread" | "jsx" | "boolean" | "null";
141
181
  value?: string | number | boolean | null;
142
182
  }
143
183
 
@@ -171,9 +211,18 @@ export interface CompiledFragment {
171
211
  description: string;
172
212
  code?: string;
173
213
  figma?: string;
214
+ canonical?: boolean;
174
215
  args?: Record<string, unknown>;
175
216
  }>;
176
217
  contract?: FragmentContract;
218
+ /** Per-component preview/runtime hints from the authored contract. */
219
+ preview?: CompiledFragmentPreview;
220
+ /** Authored state-space hints for the Workshop matrix. */
221
+ matrix?: CompiledFragmentMatrix;
222
+ /** Optional design-tool comparison reference. */
223
+ design?: CompiledFragmentDesign;
224
+ /** Design-token references used by this component. */
225
+ tokens?: string[];
177
226
  ai?: AIMetadata;
178
227
  /** Framework hint from contract, used by preview adapters and Cloud */
179
228
  framework?: string;
@@ -260,7 +309,7 @@ export interface CompiledFragmentsFile {
260
309
  blocks?: Record<string, CompiledBlock>;
261
310
  tokens?: CompiledTokenData;
262
311
  /** Component relationship graph for AI structural queries */
263
- graph?: import('../graph/types.js').SerializedComponentGraph;
312
+ graph?: import("../graph/types.js").SerializedComponentGraph;
264
313
  /** Performance measurement summary */
265
314
  performanceSummary?: PerformanceSummary;
266
315
  /** @deprecated Use blocks instead */
@@ -268,12 +317,15 @@ export interface CompiledFragmentsFile {
268
317
  }
269
318
 
270
319
  export class CompiledFragmentsFileValidationError extends Error {
271
- constructor(
272
- readonly source: string,
273
- readonly issues: string[]
274
- ) {
275
- super(`Invalid compiled Fragments catalog at ${source}: ${issues.join("; ")}`);
320
+ readonly source: string;
321
+ readonly issues: string[];
322
+
323
+ constructor(source: string, issues: readonly string[]) {
324
+ const orderedIssues = [...issues].sort(compareStrings);
325
+ super(`Invalid compiled Fragments catalog at ${source}: ${orderedIssues.join("; ")}`);
276
326
  this.name = "CompiledFragmentsFileValidationError";
327
+ this.source = source;
328
+ this.issues = orderedIssues;
277
329
  }
278
330
  }
279
331
 
@@ -285,10 +337,10 @@ export function parseCompiledFragmentsFile(
285
337
  if (typeof input === "string") {
286
338
  try {
287
339
  value = JSON.parse(input);
288
- } catch (error) {
289
- throw new CompiledFragmentsFileValidationError(source, [
290
- `invalid JSON (${error instanceof Error ? error.message : String(error)})`,
291
- ]);
340
+ } catch {
341
+ // Native JSON.parse messages differ across Node and browsers. The public
342
+ // contract error must not vary with the runtime that validates it.
343
+ throw new CompiledFragmentsFileValidationError(source, ["invalid JSON"]);
292
344
  }
293
345
  }
294
346
 
@@ -305,20 +357,34 @@ export function parseCompiledFragmentsFile(
305
357
  if (!isRecord(value.fragments)) {
306
358
  issues.push("fragments must be an object");
307
359
  } else {
308
- for (const [key, fragment] of Object.entries(value.fragments)) {
360
+ const names = new Map<string, string>();
361
+ for (const [key, fragment] of sortedEntries(value.fragments)) {
309
362
  validateCompiledFragment(key, fragment, issues);
363
+ if (
364
+ !isRecord(fragment) ||
365
+ !isRecord(fragment.meta) ||
366
+ typeof fragment.meta.name !== "string"
367
+ ) {
368
+ continue;
369
+ }
370
+ const previousKey = names.get(fragment.meta.name);
371
+ if (previousKey !== undefined) {
372
+ issues.push(
373
+ `fragments.${key}.meta.name duplicates fragments.${previousKey}.meta.name (${JSON.stringify(fragment.meta.name)})`
374
+ );
375
+ } else {
376
+ names.set(fragment.meta.name, key);
377
+ }
310
378
  }
311
379
  }
312
- if (value.graph !== undefined) {
313
- if (
314
- !isRecord(value.graph) ||
315
- !Array.isArray(value.graph.nodes) ||
316
- !Array.isArray(value.graph.edges) ||
317
- !isRecord(value.graph.health)
318
- ) {
319
- issues.push("graph must contain nodes[], edges[], and health");
320
- }
321
- }
380
+ validateOptionalString(value.generatorVersion, "generatorVersion", issues);
381
+ validateOptionalString(value.packageName, "packageName", issues);
382
+ validateOptionalStringArray(value.buildInputs, "buildInputs", issues);
383
+ validateCompiledBlockMap(value.blocks, "blocks", issues);
384
+ validateCompiledBlockMap(value.recipes, "recipes", issues);
385
+ validateCompiledTokens(value.tokens, issues);
386
+ validateSerializedGraph(value.graph, issues);
387
+ validatePerformanceSummary(value.performanceSummary, issues);
322
388
  if (issues.length > 0) throw new CompiledFragmentsFileValidationError(source, issues);
323
389
  return value as unknown as CompiledFragmentsFile;
324
390
  }
@@ -329,19 +395,554 @@ function validateCompiledFragment(key: string, value: unknown, issues: string[])
329
395
  issues.push(`${path} must be an object`);
330
396
  return;
331
397
  }
332
- if (typeof value.filePath !== "string") issues.push(`${path}.filePath must be a string`);
333
- if (!isRecord(value.meta) || typeof value.meta.name !== "string") {
334
- issues.push(`${path}.meta.name must be a string`);
398
+ validateString(value.filePath, `${path}.filePath`, issues);
399
+ validateFragmentMeta(value.meta, `${path}.meta`, issues);
400
+ validateFragmentUsage(value.usage, `${path}.usage`, issues);
401
+ if (value.guidance !== undefined) {
402
+ validateFragmentUsage(value.guidance, `${path}.guidance`, issues);
403
+ }
404
+ validateFragmentProps(value.props, `${path}.props`, issues);
405
+ validateFragmentVariants(value.variants, `${path}.variants`, issues);
406
+ validateOptionalRecordArray(value.governance, `${path}.governance`, issues);
407
+ validateRelations(value.relations, `${path}.relations`, issues);
408
+ validateFragmentPreview(value.preview, `${path}.preview`, issues);
409
+ validateFragmentMatrix(value.matrix, `${path}.matrix`, issues);
410
+ validateFragmentDesign(value.design, `${path}.design`, issues);
411
+ validateOptionalStringArray(value.tokens, `${path}.tokens`, issues);
412
+ validateOptionalRecord(value.ai, `${path}.ai`, issues);
413
+ validateOptionalString(value.framework, `${path}.framework`, issues);
414
+ validateOptionalStringArray(value.propsSummary, `${path}.propsSummary`, issues);
415
+ validateOptionalString(value.sourcePath, `${path}.sourcePath`, issues);
416
+ validateOptionalString(value.exportName, `${path}.exportName`, issues);
417
+ validateFragmentProvenance(value.provenance, `${path}.provenance`, issues);
418
+ validateFragmentGenerated(value._generated, `${path}._generated`, issues);
419
+ validateObservedUsages(value.usages, `${path}.usages`, issues);
420
+ validatePerformanceData(value.performance, `${path}.performance`, issues);
421
+ }
422
+
423
+ function validateFragmentMeta(value: unknown, path: string, issues: string[]): void {
424
+ if (!isRecord(value)) {
425
+ issues.push(`${path} must be an object`);
426
+ return;
335
427
  }
336
- if (!isRecord(value.props)) issues.push(`${path}.props must be an object`);
337
- if (!Array.isArray(value.variants)) issues.push(`${path}.variants must be an array`);
428
+ validateString(value.name, `${path}.name`, issues);
429
+ validateString(value.description, `${path}.description`, issues);
430
+ validateString(value.category, `${path}.category`, issues);
431
+ validateOptionalStringArray(value.aliases, `${path}.aliases`, issues);
432
+ validateOptionalStringArray(value.tags, `${path}.tags`, issues);
338
433
  if (
339
- !isRecord(value.usage) ||
340
- !Array.isArray(value.usage.when) ||
341
- !Array.isArray(value.usage.whenNot)
434
+ value.status !== undefined &&
435
+ !["stable", "beta", "deprecated", "experimental"].includes(String(value.status))
342
436
  ) {
343
- issues.push(`${path}.usage must contain when[] and whenNot[]`);
437
+ issues.push(`${path}.status must be stable, beta, deprecated, or experimental`);
438
+ }
439
+ validateOptionalString(value.since, `${path}.since`, issues);
440
+ validateOptionalString(value.figma, `${path}.figma`, issues);
441
+ validateOptionalRecord(value.figmaProps, `${path}.figmaProps`, issues);
442
+ if (value.dependencies !== undefined) {
443
+ if (!Array.isArray(value.dependencies)) {
444
+ issues.push(`${path}.dependencies must be an array`);
445
+ } else {
446
+ value.dependencies.forEach((dependency, index) => {
447
+ const dependencyPath = `${path}.dependencies.${index}`;
448
+ if (!isRecord(dependency)) {
449
+ issues.push(`${dependencyPath} must be an object`);
450
+ return;
451
+ }
452
+ validateString(dependency.name, `${dependencyPath}.name`, issues);
453
+ validateString(dependency.version, `${dependencyPath}.version`, issues);
454
+ validateOptionalString(dependency.reason, `${dependencyPath}.reason`, issues);
455
+ });
456
+ }
457
+ }
458
+ }
459
+
460
+ function validateFragmentUsage(value: unknown, path: string, issues: string[]): void {
461
+ if (!isRecord(value)) {
462
+ issues.push(`${path} must be an object`);
463
+ return;
464
+ }
465
+ validateStringArray(value.when, `${path}.when`, issues);
466
+ validateStringArray(value.whenNot, `${path}.whenNot`, issues);
467
+ validateOptionalStringArray(value.guidelines, `${path}.guidelines`, issues);
468
+ validateOptionalStringArray(value.accessibility, `${path}.accessibility`, issues);
469
+ if (value.choose !== undefined) {
470
+ validateStringRecord(value.choose, `${path}.choose`, issues);
471
+ }
472
+ if (value.dont !== undefined) {
473
+ if (!Array.isArray(value.dont)) {
474
+ issues.push(`${path}.dont must be an array`);
475
+ } else {
476
+ value.dont.forEach((entry, index) => {
477
+ const entryPath = `${path}.dont.${index}`;
478
+ if (!isRecord(entry)) {
479
+ issues.push(`${entryPath} must be an object`);
480
+ return;
481
+ }
482
+ validateString(entry.reason, `${entryPath}.reason`, issues);
483
+ validateString(entry.bad, `${entryPath}.bad`, issues);
484
+ validateString(entry.good, `${entryPath}.good`, issues);
485
+ });
486
+ }
487
+ }
488
+ }
489
+
490
+ function validateFragmentProps(value: unknown, path: string, issues: string[]): void {
491
+ if (!isRecord(value)) {
492
+ issues.push(`${path} must be an object`);
493
+ return;
494
+ }
495
+ for (const [name, prop] of sortedEntries(value)) {
496
+ const propPath = `${path}.${name}`;
497
+ if (!isRecord(prop)) {
498
+ issues.push(`${propPath} must be an object`);
499
+ continue;
500
+ }
501
+ validateString(prop.type, `${propPath}.type`, issues);
502
+ validateString(prop.description, `${propPath}.description`, issues);
503
+ validateOptionalStringArray(prop.values, `${propPath}.values`, issues);
504
+ validateOptionalBoolean(prop.required, `${propPath}.required`, issues);
505
+ validateOptionalStringArray(prop.constraints, `${propPath}.constraints`, issues);
506
+ validateOptionalRecord(prop.typeDetails, `${propPath}.typeDetails`, issues);
507
+ validateOptionalString(prop.controlType, `${propPath}.controlType`, issues);
508
+ validateOptionalRecord(prop.controlOptions, `${propPath}.controlOptions`, issues);
509
+ if (
510
+ prop.visibility !== undefined &&
511
+ prop.visibility !== "public" &&
512
+ prop.visibility !== "internal" &&
513
+ prop.visibility !== "hidden"
514
+ ) {
515
+ issues.push(`${propPath}.visibility must be public, internal, or hidden`);
516
+ }
517
+ }
518
+ }
519
+
520
+ function validateFragmentVariants(value: unknown, path: string, issues: string[]): void {
521
+ if (!Array.isArray(value)) {
522
+ issues.push(`${path} must be an array`);
523
+ return;
524
+ }
525
+ const names = new Set<string>();
526
+ value.forEach((variant, index) => {
527
+ const variantPath = `${path}.${index}`;
528
+ if (!isRecord(variant)) {
529
+ issues.push(`${variantPath} must be an object`);
530
+ return;
531
+ }
532
+ validateString(variant.name, `${variantPath}.name`, issues);
533
+ validateString(variant.description, `${variantPath}.description`, issues);
534
+ validateOptionalString(variant.code, `${variantPath}.code`, issues);
535
+ validateOptionalString(variant.figma, `${variantPath}.figma`, issues);
536
+ validateOptionalBoolean(variant.canonical, `${variantPath}.canonical`, issues);
537
+ validateOptionalRecord(variant.args, `${variantPath}.args`, issues);
538
+ if (typeof variant.name === "string") {
539
+ if (names.has(variant.name)) {
540
+ issues.push(
541
+ `${variantPath}.name duplicates an earlier variant (${JSON.stringify(variant.name)})`
542
+ );
543
+ }
544
+ names.add(variant.name);
545
+ }
546
+ });
547
+ }
548
+
549
+ function validateRelations(value: unknown, path: string, issues: string[]): void {
550
+ if (value === undefined) return;
551
+ if (!Array.isArray(value)) {
552
+ issues.push(`${path} must be an array`);
553
+ return;
554
+ }
555
+ value.forEach((relation, index) => {
556
+ const relationPath = `${path}.${index}`;
557
+ if (!isRecord(relation)) {
558
+ issues.push(`${relationPath} must be an object`);
559
+ return;
560
+ }
561
+ validateString(relation.component, `${relationPath}.component`, issues);
562
+ validateString(relation.relationship, `${relationPath}.relationship`, issues);
563
+ validateString(relation.note, `${relationPath}.note`, issues);
564
+ });
565
+ }
566
+
567
+ function validateFragmentPreview(value: unknown, path: string, issues: string[]): void {
568
+ if (value === undefined) return;
569
+ if (!isRecord(value)) {
570
+ issues.push(`${path} must be an object`);
571
+ return;
572
+ }
573
+ validateOptionalString(value.setupModule, `${path}.setupModule`, issues);
574
+ validateOptionalString(value.wrapperModule, `${path}.wrapperModule`, issues);
575
+ validateOptionalString(value.wrapperExport, `${path}.wrapperExport`, issues);
576
+ validateOptionalStringArray(value.css, `${path}.css`, issues);
577
+ validateOptionalStringArray(value.dynamicRegions, `${path}.dynamicRegions`, issues);
578
+ if (value.providers !== undefined && !Array.isArray(value.providers)) {
579
+ issues.push(`${path}.providers must be an array`);
580
+ }
581
+ if (value.theme !== undefined && value.theme !== "light" && value.theme !== "dark") {
582
+ issues.push(`${path}.theme must be light or dark`);
583
+ }
584
+ }
585
+
586
+ function validateFragmentMatrix(value: unknown, path: string, issues: string[]): void {
587
+ if (value === undefined) return;
588
+ if (!isRecord(value)) {
589
+ issues.push(`${path} must be an object`);
590
+ return;
591
+ }
592
+ if (value.axes !== undefined) {
593
+ if (!isRecord(value.axes)) {
594
+ issues.push(`${path}.axes must be an object`);
595
+ } else {
596
+ for (const [axis, values] of sortedEntries(value.axes)) {
597
+ if (values !== "auto" && !isStringArray(values)) {
598
+ issues.push(`${path}.axes.${axis} must be auto or an array of strings`);
599
+ }
600
+ }
601
+ }
602
+ }
603
+ validateOptionalStringArray(value.forced, `${path}.forced`, issues);
604
+ validateOptionalRecord(value.worstCase, `${path}.worstCase`, issues);
605
+ }
606
+
607
+ function validateFragmentDesign(value: unknown, path: string, issues: string[]): void {
608
+ if (value === undefined) return;
609
+ if (!isRecord(value)) {
610
+ issues.push(`${path} must be an object`);
611
+ return;
612
+ }
613
+ validateOptionalString(value.figmaNode, `${path}.figmaNode`, issues);
614
+ }
615
+
616
+ function validateFragmentProvenance(value: unknown, path: string, issues: string[]): void {
617
+ if (value === undefined) return;
618
+ if (!isRecord(value)) {
619
+ issues.push(`${path} must be an object`);
620
+ return;
621
+ }
622
+ validateString(value.source, `${path}.source`, issues);
623
+ validateBoolean(value.verified, `${path}.verified`, issues);
624
+ validateOptionalString(value.frameworkSupport, `${path}.frameworkSupport`, issues);
625
+ validateOptionalString(value.sourceHash, `${path}.sourceHash`, issues);
626
+ validateOptionalString(value.extractedAt, `${path}.extractedAt`, issues);
627
+ }
628
+
629
+ function validateFragmentGenerated(value: unknown, path: string, issues: string[]): void {
630
+ if (value === undefined) return;
631
+ if (!isRecord(value)) {
632
+ issues.push(`${path} must be an object`);
633
+ return;
344
634
  }
635
+ const sources = ["storybook", "manual", "ai", "scan", "extracted", "merged", "migrated"];
636
+ if (typeof value.source !== "string" || !sources.includes(value.source)) {
637
+ issues.push(`${path}.source must be a supported provenance source`);
638
+ }
639
+ validateOptionalString(value.sourceFile, `${path}.sourceFile`, issues);
640
+ validateOptionalNumber(value.confidence, `${path}.confidence`, issues);
641
+ validateOptionalBoolean(value.verified, `${path}.verified`, issues);
642
+ validateOptionalString(value.timestamp, `${path}.timestamp`, issues);
643
+ }
644
+
645
+ function validateObservedUsages(value: unknown, path: string, issues: string[]): void {
646
+ if (value === undefined) return;
647
+ if (!Array.isArray(value)) {
648
+ issues.push(`${path} must be an array`);
649
+ return;
650
+ }
651
+ value.forEach((usage, index) => {
652
+ const usagePath = `${path}.${index}`;
653
+ if (!isRecord(usage)) {
654
+ issues.push(`${usagePath} must be an object`);
655
+ return;
656
+ }
657
+ validateString(usage.file, `${usagePath}.file`, issues);
658
+ validateNumber(usage.line, `${usagePath}.line`, issues);
659
+ validateNumber(usage.column, `${usagePath}.column`, issues);
660
+ validateOptionalString(usage.parentElement, `${usagePath}.parentElement`, issues);
661
+ validateOptionalBoolean(usage.conditional, `${usagePath}.conditional`, issues);
662
+ if (!Array.isArray(usage.props)) {
663
+ issues.push(`${usagePath}.props must be an array`);
664
+ return;
665
+ }
666
+ usage.props.forEach((prop, propIndex) => {
667
+ const propPath = `${usagePath}.props.${propIndex}`;
668
+ if (!isRecord(prop)) {
669
+ issues.push(`${propPath} must be an object`);
670
+ return;
671
+ }
672
+ validateString(prop.name, `${propPath}.name`, issues);
673
+ if (
674
+ typeof prop.kind !== "string" ||
675
+ !["static", "dynamic", "spread", "jsx", "boolean", "null"].includes(prop.kind)
676
+ ) {
677
+ issues.push(`${propPath}.kind must be a supported observation kind`);
678
+ }
679
+ if (
680
+ prop.value !== undefined &&
681
+ prop.value !== null &&
682
+ typeof prop.value !== "string" &&
683
+ typeof prop.value !== "number" &&
684
+ typeof prop.value !== "boolean"
685
+ ) {
686
+ issues.push(`${propPath}.value must be a JSON scalar`);
687
+ }
688
+ });
689
+ });
690
+ }
691
+
692
+ function validatePerformanceData(value: unknown, path: string, issues: string[]): void {
693
+ if (value === undefined) return;
694
+ if (!isRecord(value)) {
695
+ issues.push(`${path} must be an object`);
696
+ return;
697
+ }
698
+ validateNumber(value.bundleSize, `${path}.bundleSize`, issues);
699
+ validateNumber(value.rawSize, `${path}.rawSize`, issues);
700
+ validateString(value.complexity, `${path}.complexity`, issues);
701
+ validateNumber(value.budgetPercent, `${path}.budgetPercent`, issues);
702
+ validateBoolean(value.overBudget, `${path}.overBudget`, issues);
703
+ validateString(value.measuredAt, `${path}.measuredAt`, issues);
704
+ if (value.imports !== undefined) {
705
+ if (!Array.isArray(value.imports)) {
706
+ issues.push(`${path}.imports must be an array`);
707
+ } else {
708
+ value.imports.forEach((entry, index) => {
709
+ const entryPath = `${path}.imports.${index}`;
710
+ if (!isRecord(entry)) {
711
+ issues.push(`${entryPath} must be an object`);
712
+ return;
713
+ }
714
+ validateString(entry.path, `${entryPath}.path`, issues);
715
+ validateNumber(entry.bytes, `${entryPath}.bytes`, issues);
716
+ validateNumber(entry.percent, `${entryPath}.percent`, issues);
717
+ });
718
+ }
719
+ }
720
+ }
721
+
722
+ function validateCompiledBlockMap(value: unknown, path: string, issues: string[]): void {
723
+ if (value === undefined) return;
724
+ if (!isRecord(value)) {
725
+ issues.push(`${path} must be an object`);
726
+ return;
727
+ }
728
+ const names = new Set<string>();
729
+ for (const [key, block] of sortedEntries(value)) {
730
+ const blockPath = `${path}.${key}`;
731
+ if (!isRecord(block)) {
732
+ issues.push(`${blockPath} must be an object`);
733
+ continue;
734
+ }
735
+ validateString(block.filePath, `${blockPath}.filePath`, issues);
736
+ validateString(block.name, `${blockPath}.name`, issues);
737
+ validateString(block.description, `${blockPath}.description`, issues);
738
+ validateString(block.category, `${blockPath}.category`, issues);
739
+ validateStringArray(block.components, `${blockPath}.components`, issues);
740
+ validateString(block.code, `${blockPath}.code`, issues);
741
+ validateOptionalStringArray(block.tags, `${blockPath}.tags`, issues);
742
+ if (typeof block.name === "string") {
743
+ if (names.has(block.name)) {
744
+ issues.push(
745
+ `${blockPath}.name duplicates an earlier block (${JSON.stringify(block.name)})`
746
+ );
747
+ }
748
+ names.add(block.name);
749
+ }
750
+ }
751
+ }
752
+
753
+ function validateCompiledTokens(value: unknown, issues: string[]): void {
754
+ if (value === undefined) return;
755
+ if (!isRecord(value)) {
756
+ issues.push("tokens must be an object");
757
+ return;
758
+ }
759
+ validateString(value.prefix, "tokens.prefix", issues);
760
+ validateNumber(value.total, "tokens.total", issues);
761
+ if (!isRecord(value.categories)) {
762
+ issues.push("tokens.categories must be an object");
763
+ return;
764
+ }
765
+ for (const [category, entries] of sortedEntries(value.categories)) {
766
+ const categoryPath = `tokens.categories.${category}`;
767
+ if (!Array.isArray(entries)) {
768
+ issues.push(`${categoryPath} must be an array`);
769
+ continue;
770
+ }
771
+ entries.forEach((entry, index) => {
772
+ const entryPath = `${categoryPath}.${index}`;
773
+ if (!isRecord(entry)) {
774
+ issues.push(`${entryPath} must be an object`);
775
+ return;
776
+ }
777
+ validateString(entry.name, `${entryPath}.name`, issues);
778
+ validateOptionalString(entry.value, `${entryPath}.value`, issues);
779
+ validateOptionalString(entry.description, `${entryPath}.description`, issues);
780
+ });
781
+ }
782
+ }
783
+
784
+ function validateSerializedGraph(value: unknown, issues: string[]): void {
785
+ if (value === undefined) return;
786
+ if (!isRecord(value)) {
787
+ issues.push("graph must be an object");
788
+ return;
789
+ }
790
+ if (!Array.isArray(value.nodes)) {
791
+ issues.push("graph.nodes must be an array");
792
+ } else {
793
+ const nodeNames = new Set<string>();
794
+ value.nodes.forEach((node, index) => {
795
+ const nodePath = `graph.nodes.${index}`;
796
+ if (!isRecord(node)) {
797
+ issues.push(`${nodePath} must be an object`);
798
+ return;
799
+ }
800
+ validateString(node.name, `${nodePath}.name`, issues);
801
+ validateString(node.category, `${nodePath}.category`, issues);
802
+ validateString(node.status, `${nodePath}.status`, issues);
803
+ validateOptionalStringArray(node.subComponents, `${nodePath}.subComponents`, issues);
804
+ if (typeof node.name === "string") {
805
+ if (nodeNames.has(node.name)) {
806
+ issues.push(
807
+ `${nodePath}.name duplicates an earlier graph node (${JSON.stringify(node.name)})`
808
+ );
809
+ }
810
+ nodeNames.add(node.name);
811
+ }
812
+ });
813
+ }
814
+ if (!Array.isArray(value.edges)) {
815
+ issues.push("graph.edges must be an array");
816
+ } else {
817
+ value.edges.forEach((edge, index) => {
818
+ const edgePath = `graph.edges.${index}`;
819
+ if (!isRecord(edge)) {
820
+ issues.push(`${edgePath} must be an object`);
821
+ return;
822
+ }
823
+ validateString(edge.s, `${edgePath}.s`, issues);
824
+ validateString(edge.t, `${edgePath}.t`, issues);
825
+ validateString(edge.ty, `${edgePath}.ty`, issues);
826
+ validateNumber(edge.w, `${edgePath}.w`, issues);
827
+ validateOptionalString(edge.no, `${edgePath}.no`, issues);
828
+ validateString(edge.p, `${edgePath}.p`, issues);
829
+ });
830
+ }
831
+ if (!isRecord(value.health)) {
832
+ issues.push("graph.health must be an object");
833
+ return;
834
+ }
835
+ validateStringArray(value.health.orphans, "graph.health.orphans", issues);
836
+ validateNumber(value.health.compositionCoverage, "graph.health.compositionCoverage", issues);
837
+ validateNumber(value.health.averageDegree, "graph.health.averageDegree", issues);
838
+ validateNumber(value.health.nodeCount, "graph.health.nodeCount", issues);
839
+ validateNumber(value.health.edgeCount, "graph.health.edgeCount", issues);
840
+ if (!Array.isArray(value.health.connectedComponents)) {
841
+ issues.push("graph.health.connectedComponents must be an array");
842
+ } else {
843
+ value.health.connectedComponents.forEach((component, index) =>
844
+ validateStringArray(component, `graph.health.connectedComponents.${index}`, issues)
845
+ );
846
+ }
847
+ if (!Array.isArray(value.health.hubs)) {
848
+ issues.push("graph.health.hubs must be an array");
849
+ } else {
850
+ value.health.hubs.forEach((hub, index) => {
851
+ const hubPath = `graph.health.hubs.${index}`;
852
+ if (!isRecord(hub)) {
853
+ issues.push(`${hubPath} must be an object`);
854
+ return;
855
+ }
856
+ validateString(hub.name, `${hubPath}.name`, issues);
857
+ validateNumber(hub.degree, `${hubPath}.degree`, issues);
858
+ });
859
+ }
860
+ }
861
+
862
+ function validatePerformanceSummary(value: unknown, issues: string[]): void {
863
+ if (value === undefined) return;
864
+ if (!isRecord(value)) {
865
+ issues.push("performanceSummary must be an object");
866
+ return;
867
+ }
868
+ validateString(value.preset, "performanceSummary.preset", issues);
869
+ validateNumber(value.budget, "performanceSummary.budget", issues);
870
+ validateNumber(value.total, "performanceSummary.total", issues);
871
+ validateNumber(value.overBudget, "performanceSummary.overBudget", issues);
872
+ if (!isRecord(value.tiers)) {
873
+ issues.push("performanceSummary.tiers must be an object");
874
+ } else {
875
+ for (const [tier, count] of sortedEntries(value.tiers)) {
876
+ validateNumber(count, `performanceSummary.tiers.${tier}`, issues);
877
+ }
878
+ }
879
+ }
880
+
881
+ function validateString(value: unknown, path: string, issues: string[]): void {
882
+ if (typeof value !== "string") issues.push(`${path} must be a string`);
883
+ }
884
+
885
+ function validateOptionalString(value: unknown, path: string, issues: string[]): void {
886
+ if (value !== undefined) validateString(value, path, issues);
887
+ }
888
+
889
+ function validateNumber(value: unknown, path: string, issues: string[]): void {
890
+ if (typeof value !== "number" || !Number.isFinite(value)) {
891
+ issues.push(`${path} must be a finite number`);
892
+ }
893
+ }
894
+
895
+ function validateOptionalNumber(value: unknown, path: string, issues: string[]): void {
896
+ if (value !== undefined) validateNumber(value, path, issues);
897
+ }
898
+
899
+ function validateBoolean(value: unknown, path: string, issues: string[]): void {
900
+ if (typeof value !== "boolean") issues.push(`${path} must be a boolean`);
901
+ }
902
+
903
+ function validateOptionalBoolean(value: unknown, path: string, issues: string[]): void {
904
+ if (value !== undefined) validateBoolean(value, path, issues);
905
+ }
906
+
907
+ function validateStringArray(value: unknown, path: string, issues: string[]): void {
908
+ if (!isStringArray(value)) issues.push(`${path} must be an array of strings`);
909
+ }
910
+
911
+ function validateOptionalStringArray(value: unknown, path: string, issues: string[]): void {
912
+ if (value !== undefined) validateStringArray(value, path, issues);
913
+ }
914
+
915
+ function validateStringRecord(value: unknown, path: string, issues: string[]): void {
916
+ if (!isRecord(value)) {
917
+ issues.push(`${path} must be an object of strings`);
918
+ return;
919
+ }
920
+ for (const [key, entry] of sortedEntries(value)) {
921
+ validateString(entry, `${path}.${key}`, issues);
922
+ }
923
+ }
924
+
925
+ function validateOptionalRecord(value: unknown, path: string, issues: string[]): void {
926
+ if (value !== undefined && !isRecord(value)) issues.push(`${path} must be an object`);
927
+ }
928
+
929
+ function validateOptionalRecordArray(value: unknown, path: string, issues: string[]): void {
930
+ if (value === undefined) return;
931
+ if (!Array.isArray(value) || value.some((entry) => !isRecord(entry))) {
932
+ issues.push(`${path} must be an array of objects`);
933
+ }
934
+ }
935
+
936
+ function isStringArray(value: unknown): value is string[] {
937
+ return Array.isArray(value) && value.every((entry) => typeof entry === "string");
938
+ }
939
+
940
+ function sortedEntries(value: Record<string, unknown>): Array<[string, unknown]> {
941
+ return Object.entries(value).sort(([left], [right]) => compareStrings(left, right));
942
+ }
943
+
944
+ function compareStrings(left: string, right: string): number {
945
+ return left < right ? -1 : left > right ? 1 : 0;
345
946
  }
346
947
 
347
948
  function isRecord(value: unknown): value is Record<string, unknown> {