@usefragments/core 1.4.0 → 1.5.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 (40) hide show
  1. package/dist/{chunk-CBSNXFOD.js → chunk-HXGE3O2F.js} +39 -1
  2. package/dist/chunk-HXGE3O2F.js.map +1 -0
  3. package/dist/codes/index.d.ts +2 -2
  4. package/dist/codes/index.js +1 -1
  5. package/dist/compiled-types/index.d.ts +1 -1
  6. package/dist/generate/index.d.ts +1 -1
  7. package/dist/{governance-DxdJV6lx.d.ts → governance-BLsyk56o.d.ts} +11 -0
  8. package/dist/index.d.ts +359 -139
  9. package/dist/index.js +349 -95
  10. package/dist/index.js.map +1 -1
  11. package/dist/react-types.d.ts +1 -1
  12. package/dist/registry.d.ts +18 -18
  13. package/dist/schemas/index.d.ts +1 -1
  14. package/dist/test-utils.d.ts +1 -1
  15. package/package.json +1 -1
  16. package/src/codes/codes.ts +9 -0
  17. package/src/conform.ts +5 -0
  18. package/src/facts/builders.ts +45 -1
  19. package/src/facts/facts.test.ts +30 -0
  20. package/src/facts/index.ts +6 -0
  21. package/src/facts/types.ts +61 -1
  22. package/src/governance.test.ts +26 -0
  23. package/src/identity/classify.test.ts +185 -0
  24. package/src/identity/classify.ts +257 -0
  25. package/src/identity/component-key.test.ts +32 -0
  26. package/src/identity/component-key.ts +35 -0
  27. package/src/identity/usage.ts +39 -0
  28. package/src/index.ts +25 -0
  29. package/src/rules/components-prefer-library.test.ts +48 -0
  30. package/src/rules/components-prefer-library.ts +16 -0
  31. package/src/rules/components-shadow-component.test.ts +145 -0
  32. package/src/rules/components-shadow-component.ts +81 -0
  33. package/src/rules/fix-availability.ts +1 -0
  34. package/src/rules/index.ts +7 -0
  35. package/src/rules/taxonomy.test.ts +16 -1
  36. package/src/rules/tiers.ts +7 -5
  37. package/src/schema.ts +15 -0
  38. package/src/types.ts +12 -0
  39. package/dist/chunk-CBSNXFOD.js.map +0 -1
  40. package/dist/{index-0lmh0Lbo.d.ts → index-h_yWj15D.d.ts} +8 -8
package/dist/index.js CHANGED
@@ -71,6 +71,8 @@ import {
71
71
  makeClassNameDynamicFact,
72
72
  makeClassNameLiteralFact,
73
73
  makeComponentCapabilityFact,
74
+ makeComponentDefinitionFact,
75
+ makeComponentIdentityFact,
74
76
  makeComponentMetadataFact,
75
77
  makeContractTokenFact,
76
78
  makeJsxComponentPreferredFact,
@@ -109,7 +111,7 @@ import {
109
111
  ownedImportsEqual,
110
112
  resolveComponentGovernance,
111
113
  scaleGovernanceRecordSchema
112
- } from "./chunk-CBSNXFOD.js";
114
+ } from "./chunk-HXGE3O2F.js";
113
115
  import {
114
116
  AGENT_FORMAT_SCHEMA_VERSION,
115
117
  agentErrorEnvelopeSchema,
@@ -1776,7 +1778,18 @@ var fragmentsConfigSchema = z4.object({
1776
1778
  })
1777
1779
  ).optional()
1778
1780
  }).optional(),
1779
- recognizedClassHelpers: z4.array(z4.string().min(1)).optional()
1781
+ recognizedClassHelpers: z4.array(z4.string().min(1)).optional(),
1782
+ identity: z4.object({
1783
+ decisions: z4.array(
1784
+ z4.object({
1785
+ component: z4.string().min(1),
1786
+ kind: z4.enum(["sanction", "reject", "dismiss"]),
1787
+ canonicalTarget: z4.string().min(1).optional(),
1788
+ reason: z4.string().optional(),
1789
+ decisionId: z4.string().min(1).optional()
1790
+ })
1791
+ ).optional()
1792
+ }).optional()
1780
1793
  });
1781
1794
  var compositionMetadataSchema = z4.object({
1782
1795
  pattern: z4.enum(["compound", "simple", "controlled", "wrapper"]).optional(),
@@ -5077,12 +5090,14 @@ function ruleComponentsPreferLibrary(ix) {
5077
5090
  const classTokensByNode = indexClassTokensByNode(ix);
5078
5091
  const reimplTargets = canonicalReimplTargets(sources, mappings);
5079
5092
  const canonicalDirectoryResolvedNodes = indexCanonicalDirectoryResolvedNodes(ix, sources);
5093
+ const shadowRenderRootNodeIds = indexShadowRenderRootNodeIds(ix);
5080
5094
  const findings = [];
5081
5095
  const seenImportFixes = /* @__PURE__ */ new Set();
5082
5096
  for (const node of ix.byKind("usage_node")) {
5083
5097
  if (isCanonicalSourceImplementationFile(node.file, sources)) continue;
5084
5098
  if (node.element.includes(".")) continue;
5085
5099
  if (canonicalDirectoryResolvedNodes.has(node.id)) continue;
5100
+ if (shadowRenderRootNodeIds.has(node.id)) continue;
5086
5101
  const props = propsByNode.get(node.id) ?? [];
5087
5102
  const textChildren = textByNode.get(node.id) ?? [];
5088
5103
  const importsForFile = importsByFileAndLocal.get(node.file);
@@ -5330,6 +5345,18 @@ function ruleComponentsPreferLibrary(ix) {
5330
5345
  }
5331
5346
  return findings;
5332
5347
  }
5348
+ function indexShadowRenderRootNodeIds(ix) {
5349
+ const identities = ix.byKind("component_identity");
5350
+ if (identities.length === 0) return /* @__PURE__ */ new Set();
5351
+ const nodeIds = /* @__PURE__ */ new Set();
5352
+ for (const identity of identities) {
5353
+ if (identity.state !== "shadow") continue;
5354
+ for (const evidenceId of identity.evidence) {
5355
+ if (ix.get(evidenceId)?.kind === "usage_node") nodeIds.add(evidenceId);
5356
+ }
5357
+ }
5358
+ return nodeIds;
5359
+ }
5333
5360
  function findBuiltInRawHtmlMatch(node, props, textChildren, inputType) {
5334
5361
  if (isIconOnlyButton(node, props, textChildren)) {
5335
5362
  return { canonical: "IconButton", tier: "icon-only" };
@@ -5656,9 +5683,95 @@ function isIdentifier(value) {
5656
5683
  return /^[A-Za-z_$][\w$]*$/.test(value);
5657
5684
  }
5658
5685
 
5659
- // src/rules/components-forbidden-prop-value.ts
5660
- var RULE_ID3 = "components/forbidden-prop-value";
5686
+ // src/identity/usage.ts
5687
+ function indexComponentIdentityUsage(ix) {
5688
+ const nodesById = new Map(ix.byKind("usage_node").map((node) => [node.id, node]));
5689
+ const nodesByComponent = /* @__PURE__ */ new Map();
5690
+ for (const usage of ix.byKind("usage_component")) {
5691
+ const node = nodesById.get(usage.nodeId);
5692
+ if (!node) continue;
5693
+ const componentKey = String(usage.componentId);
5694
+ const nodes = nodesByComponent.get(componentKey) ?? /* @__PURE__ */ new Map();
5695
+ nodes.set(node.id, node);
5696
+ nodesByComponent.set(componentKey, nodes);
5697
+ }
5698
+ return new Map(
5699
+ [...nodesByComponent].map(([componentKey, nodesByIdForComponent]) => {
5700
+ const nodes = [...nodesByIdForComponent.values()];
5701
+ return [
5702
+ componentKey,
5703
+ {
5704
+ usageCount: nodes.length,
5705
+ blastRadius: new Set(nodes.map((node) => node.file)).size,
5706
+ nodes
5707
+ }
5708
+ ];
5709
+ })
5710
+ );
5711
+ }
5712
+
5713
+ // src/rules/components-shadow-component.ts
5714
+ var RULE_ID3 = "components/shadow-component";
5661
5715
  var RULE_VERSION3 = "1";
5716
+ function ruleComponentsShadowComponent(ix) {
5717
+ const policy = ix.policy.ruleConfig(RULE_ID3);
5718
+ if (!policy?.enabled) return [];
5719
+ const definitions = new Map(
5720
+ ix.byKind("component_definition").map((definition) => [definition.componentKey, definition])
5721
+ );
5722
+ const usageByComponent = indexComponentIdentityUsage(ix);
5723
+ const findings = [];
5724
+ for (const identity of ix.byKind("component_identity")) {
5725
+ const reviewVariant = identity.state === "variant" && identity.confidence === "review";
5726
+ if (identity.state !== "shadow" && !reviewVariant) continue;
5727
+ const definition = definitions.get(identity.componentKey);
5728
+ if (!definition) continue;
5729
+ const usage = usageByComponent.get(identity.componentKey);
5730
+ const usageCount = usage?.usageCount ?? 0;
5731
+ const blastRadius = usage?.blastRadius ?? 0;
5732
+ findings.push(
5733
+ makeFinding({
5734
+ ruleId: RULE_ID3,
5735
+ ruleVersion: RULE_VERSION3,
5736
+ severity: reviewVariant ? "info" : policy.severity ?? "warn",
5737
+ message: reviewVariant ? `${definition.exportName} wraps ${identity.canonicalTarget ?? "a canonical component"}; sanction it as a variant or consolidate its usages.` : `${definition.exportName} shadows ${identity.canonicalTarget ?? "a canonical component"} across ${usageCount} usage${usageCount === 1 ? "" : "s"}.`,
5738
+ location: definitionLocation(ix, definition, identity),
5739
+ evidence: ix.evidence(identity.evidence),
5740
+ fingerprintIdentity: {
5741
+ componentKey: identity.componentKey,
5742
+ canonicalTarget: identity.canonicalTarget
5743
+ },
5744
+ attributes: {
5745
+ canonicalTarget: identity.canonicalTarget,
5746
+ usageCount,
5747
+ blastRadius,
5748
+ confidence: identity.confidence,
5749
+ state: identity.state,
5750
+ ...reviewVariant ? { advisory: true } : {}
5751
+ }
5752
+ })
5753
+ );
5754
+ }
5755
+ return findings;
5756
+ }
5757
+ function definitionLocation(ix, definition, identity) {
5758
+ const evidence = ix.evidence(identity.evidence).map(({ fact }) => fact);
5759
+ const inDefinitionFile = evidence.find(
5760
+ (fact) => "location" in fact && fact.location?.file === definition.file
5761
+ );
5762
+ if (inDefinitionFile && "location" in inDefinitionFile && inDefinitionFile.location) {
5763
+ return inDefinitionFile.location;
5764
+ }
5765
+ const sourceLocated = evidence.find((fact) => "location" in fact && fact.location);
5766
+ if (sourceLocated && "location" in sourceLocated && sourceLocated.location) {
5767
+ return sourceLocated.location;
5768
+ }
5769
+ return { file: definition.file, line: 1, column: 0 };
5770
+ }
5771
+
5772
+ // src/rules/components-forbidden-prop-value.ts
5773
+ var RULE_ID4 = "components/forbidden-prop-value";
5774
+ var RULE_VERSION4 = "1";
5662
5775
  function ruleComponentsForbiddenPropValue(ix) {
5663
5776
  const findings = [];
5664
5777
  const componentByNode = indexComponentByNodeId(ix);
@@ -5688,8 +5801,8 @@ function ruleComponentsForbiddenPropValue(ix) {
5688
5801
  } : void 0;
5689
5802
  findings.push(
5690
5803
  makeFinding({
5691
- ruleId: RULE_ID3,
5692
- ruleVersion: RULE_VERSION3,
5804
+ ruleId: RULE_ID4,
5805
+ ruleVersion: RULE_VERSION4,
5693
5806
  severity: policy.severity,
5694
5807
  message: buildMessage(
5695
5808
  node.element,
@@ -5733,8 +5846,8 @@ function buildMessage(element, prop, value, because, replacement) {
5733
5846
  }
5734
5847
 
5735
5848
  // src/rules/components-unknown-prop.ts
5736
- var RULE_ID4 = "components/unknown-prop";
5737
- var RULE_VERSION4 = "1";
5849
+ var RULE_ID5 = "components/unknown-prop";
5850
+ var RULE_VERSION5 = "1";
5738
5851
  function ruleComponentsUnknownProp(ix) {
5739
5852
  const policy = ix.policy.jsxUnknownPropsForbidden();
5740
5853
  if (!policy) return [];
@@ -5755,8 +5868,8 @@ function ruleComponentsUnknownProp(ix) {
5755
5868
  if (knownNames.has(propUsage.prop)) continue;
5756
5869
  findings.push(
5757
5870
  makeFinding({
5758
- ruleId: RULE_ID4,
5759
- ruleVersion: RULE_VERSION4,
5871
+ ruleId: RULE_ID5,
5872
+ ruleVersion: RULE_VERSION5,
5760
5873
  severity: policy.severity,
5761
5874
  message: `Unknown prop "${propUsage.prop}" on <${node.element}>. Allowed props: ${formatPropList(knownNames)}.`,
5762
5875
  location: node.location,
@@ -5788,7 +5901,7 @@ function formatPropList(names) {
5788
5901
  // src/rules/composition-pattern.ts
5789
5902
  var CARDINALITY_RULE_ID = "composition/cardinality";
5790
5903
  var CO_OCCURRENCE_RULE_ID = "composition/co-occurrence";
5791
- var RULE_VERSION5 = "1";
5904
+ var RULE_VERSION6 = "1";
5792
5905
  function ruleCompositionCardinality(ix) {
5793
5906
  return evaluate(ix, CARDINALITY_RULE_ID, "cardinality");
5794
5907
  }
@@ -5856,7 +5969,7 @@ function checkCardinality(ctx, region, pattern, constraint, selected) {
5856
5969
  const extras = selected.slice(constraint.max);
5857
5970
  return makeFinding({
5858
5971
  ruleId: ctx.ruleId,
5859
- ruleVersion: RULE_VERSION5,
5972
+ ruleVersion: RULE_VERSION6,
5860
5973
  severity: ctx.severity,
5861
5974
  message: cardinalityMaxMessage(pattern, constraint.max, count),
5862
5975
  location: extras[0].location,
@@ -5867,7 +5980,7 @@ function checkCardinality(ctx, region, pattern, constraint, selected) {
5867
5980
  if (constraint.min !== void 0 && count < constraint.min) {
5868
5981
  return makeFinding({
5869
5982
  ruleId: ctx.ruleId,
5870
- ruleVersion: RULE_VERSION5,
5983
+ ruleVersion: RULE_VERSION6,
5871
5984
  severity: ctx.severity,
5872
5985
  message: cardinalityMinMessage(pattern, constraint.min, count),
5873
5986
  location: region.container.location,
@@ -5888,7 +6001,7 @@ function checkCoOccurrence(ctx, region, pattern, constraint, selected) {
5888
6001
  if (satisfied) return null;
5889
6002
  return makeFinding({
5890
6003
  ruleId: ctx.ruleId,
5891
- ruleVersion: RULE_VERSION5,
6004
+ ruleVersion: RULE_VERSION6,
5892
6005
  severity: ctx.severity,
5893
6006
  message: coOccurrenceMessage(pattern, constraint),
5894
6007
  location: region.container.location,
@@ -5940,8 +6053,8 @@ function coOccurrenceMessage(pattern, constraint) {
5940
6053
  }
5941
6054
 
5942
6055
  // src/rules/jsx-preferred-component.ts
5943
- var RULE_ID5 = "components/preferred-component";
5944
- var RULE_VERSION6 = "1";
6056
+ var RULE_ID6 = "components/preferred-component";
6057
+ var RULE_VERSION7 = "1";
5945
6058
  function ruleJsxPreferredComponent(ix) {
5946
6059
  const policies = ix.policy.jsxComponentPreferred();
5947
6060
  if (policies.length === 0) return [];
@@ -5957,8 +6070,8 @@ function ruleJsxPreferredComponent(ix) {
5957
6070
  if (!replacement) continue;
5958
6071
  findings.push(
5959
6072
  makeFinding({
5960
- ruleId: RULE_ID5,
5961
- ruleVersion: RULE_VERSION6,
6073
+ ruleId: RULE_ID6,
6074
+ ruleVersion: RULE_VERSION7,
5962
6075
  severity: policy.severity,
5963
6076
  message: `<${node.element}> should use <${replacement}>.`,
5964
6077
  location: node.location,
@@ -5995,8 +6108,8 @@ function componentNameFromId(componentId2) {
5995
6108
  }
5996
6109
 
5997
6110
  // src/rules/jsx-preferred-import-path.ts
5998
- var RULE_ID6 = "imports/preferred-path";
5999
- var RULE_VERSION7 = "1";
6111
+ var RULE_ID7 = "imports/preferred-path";
6112
+ var RULE_VERSION8 = "1";
6000
6113
  function ruleJsxPreferredImportPath(ix) {
6001
6114
  const policies = ix.policy.jsxImportPathPreferred();
6002
6115
  if (policies.length === 0) return [];
@@ -6016,8 +6129,8 @@ function ruleJsxPreferredImportPath(ix) {
6016
6129
  seen.add(key);
6017
6130
  findings.push(
6018
6131
  makeFinding({
6019
- ruleId: RULE_ID6,
6020
- ruleVersion: RULE_VERSION7,
6132
+ ruleId: RULE_ID7,
6133
+ ruleVersion: RULE_VERSION8,
6021
6134
  severity: policy.severity,
6022
6135
  message: `Import from "${usage.source}" should use "${policy.to}".`,
6023
6136
  location: usage.location,
@@ -6052,10 +6165,10 @@ function ruleJsxPreferredImportPath(ix) {
6052
6165
  }
6053
6166
 
6054
6167
  // src/rules/props-invalid-value.ts
6055
- var RULE_ID7 = "props/invalid-value";
6056
- var RULE_VERSION8 = "1";
6168
+ var RULE_ID8 = "props/invalid-value";
6169
+ var RULE_VERSION9 = "1";
6057
6170
  function rulePropsInvalidValue(ix) {
6058
- const policy = ix.policy.ruleConfig(RULE_ID7);
6171
+ const policy = ix.policy.ruleConfig(RULE_ID8);
6059
6172
  if (!policy?.enabled) return [];
6060
6173
  const findings = [];
6061
6174
  const componentByNode = indexComponentByNodeId(ix);
@@ -6084,8 +6197,8 @@ function rulePropsInvalidValue(ix) {
6084
6197
  } : void 0;
6085
6198
  findings.push(
6086
6199
  makeFinding({
6087
- ruleId: RULE_ID7,
6088
- ruleVersion: RULE_VERSION8,
6200
+ ruleId: RULE_ID8,
6201
+ ruleVersion: RULE_VERSION9,
6089
6202
  severity: policy.severity ?? "warn",
6090
6203
  message: buildMessage2(node.element, propUsage.prop, propUsage.value, allowedValues),
6091
6204
  location: propUsage.location ?? node.location,
@@ -6261,8 +6374,8 @@ function srgbGamma(linear) {
6261
6374
  }
6262
6375
 
6263
6376
  // src/rules/styles-no-raw-color.ts
6264
- var RULE_ID8 = "styles/no-raw-color";
6265
- var RULE_VERSION9 = "1";
6377
+ var RULE_ID9 = "styles/no-raw-color";
6378
+ var RULE_VERSION10 = "1";
6266
6379
  function ruleStylesNoRawColor(ix) {
6267
6380
  const policy = ix.policy.rawColorPolicy();
6268
6381
  if (!policy) return [];
@@ -6280,8 +6393,8 @@ function ruleStylesNoRawColor(ix) {
6280
6393
  const evidenceIds = resolution ? [decl.id, policy.id, resolution.token.id] : [decl.id, policy.id];
6281
6394
  findings.push(
6282
6395
  makeFinding({
6283
- ruleId: RULE_ID8,
6284
- ruleVersion: RULE_VERSION9,
6396
+ ruleId: RULE_ID9,
6397
+ ruleVersion: RULE_VERSION10,
6285
6398
  severity: policy.severity,
6286
6399
  message: colorMessage(color, `\`${decl.property}\``, resolution, preferLabel),
6287
6400
  location: decl.location,
@@ -6316,8 +6429,8 @@ function ruleStylesNoRawColor(ix) {
6316
6429
  const evidenceIds2 = componentEvidenceId2 ? [node2.id, componentEvidenceId2, inline.id, policy.id] : [node2.id, inline.id, policy.id];
6317
6430
  findings.push(
6318
6431
  makeFinding({
6319
- ruleId: RULE_ID8,
6320
- ruleVersion: RULE_VERSION9,
6432
+ ruleId: RULE_ID9,
6433
+ ruleVersion: RULE_VERSION10,
6321
6434
  severity: advisorySeverity,
6322
6435
  message: `Runtime-constructed raw color \`${inline.value}\` on inline \`${inline.property}\`. Use a ${preferLabel} instead.`,
6323
6436
  location: node2.location,
@@ -6353,8 +6466,8 @@ function ruleStylesNoRawColor(ix) {
6353
6466
  const evidenceIds = resolution ? [...baseEvidence, resolution.token.id] : baseEvidence;
6354
6467
  findings.push(
6355
6468
  makeFinding({
6356
- ruleId: RULE_ID8,
6357
- ruleVersion: RULE_VERSION9,
6469
+ ruleId: RULE_ID9,
6470
+ ruleVersion: RULE_VERSION10,
6358
6471
  severity: policy.severity,
6359
6472
  message: colorMessage(color, `inline \`${inline.property}\``, resolution, preferLabel),
6360
6473
  location: node.location,
@@ -6397,8 +6510,8 @@ function ruleStylesNoRawColor(ix) {
6397
6510
  const evidenceIds = resolution ? [...baseEvidence, resolution.token.id] : baseEvidence;
6398
6511
  findings.push(
6399
6512
  makeFinding({
6400
- ruleId: RULE_ID8,
6401
- ruleVersion: RULE_VERSION9,
6513
+ ruleId: RULE_ID9,
6514
+ ruleVersion: RULE_VERSION10,
6402
6515
  severity: advisorySeverity,
6403
6516
  message: colorMessage(color, `\`${prop.prop}\``, resolution, preferLabel),
6404
6517
  location: prop.location ?? node.location,
@@ -6549,8 +6662,8 @@ function escapeRegExp(value) {
6549
6662
  }
6550
6663
 
6551
6664
  // src/rules/styles-no-raw-dimensions.ts
6552
- var RULE_ID9 = "styles/no-raw-dimensions";
6553
- var RULE_VERSION10 = "1";
6665
+ var RULE_ID10 = "styles/no-raw-dimensions";
6666
+ var RULE_VERSION11 = "1";
6554
6667
  function ruleStylesNoRawDimensions(ix) {
6555
6668
  const policy = ix.policy.rawDimensionPolicy();
6556
6669
  if (!policy) return [];
@@ -6570,8 +6683,8 @@ function ruleStylesNoRawDimensions(ix) {
6570
6683
  const fixEmission = dimensionFix(decl.property, resolution, ix);
6571
6684
  findings.push(
6572
6685
  makeFinding({
6573
- ruleId: RULE_ID9,
6574
- ruleVersion: RULE_VERSION10,
6686
+ ruleId: RULE_ID10,
6687
+ ruleVersion: RULE_VERSION11,
6575
6688
  severity: policy.severity,
6576
6689
  message: dimensionMessage(decl.value, decl.property, preferLabel, resolution, false),
6577
6690
  location: decl.location,
@@ -6612,8 +6725,8 @@ function ruleStylesNoRawDimensions(ix) {
6612
6725
  const fixEmission = dimensionFix(inline.property, resolution, ix);
6613
6726
  findings.push(
6614
6727
  makeFinding({
6615
- ruleId: RULE_ID9,
6616
- ruleVersion: RULE_VERSION10,
6728
+ ruleId: RULE_ID10,
6729
+ ruleVersion: RULE_VERSION11,
6617
6730
  severity: policy.severity,
6618
6731
  message: dimensionMessage(inline.value, inline.property, preferLabel, resolution, true),
6619
6732
  location: node.location,
@@ -6883,8 +6996,8 @@ function parseLengthParts(raw) {
6883
6996
  }
6884
6997
 
6885
6998
  // src/rules/styles-no-raw-spacing.ts
6886
- var RULE_ID10 = "styles/no-raw-spacing";
6887
- var RULE_VERSION11 = "1";
6999
+ var RULE_ID11 = "styles/no-raw-spacing";
7000
+ var RULE_VERSION12 = "1";
6888
7001
  function ruleStylesNoRawSpacing(ix) {
6889
7002
  const findings = [];
6890
7003
  const lookupCache = /* @__PURE__ */ new Map();
@@ -6926,8 +7039,8 @@ function checkDeclaration(ix, decl, lookupFor) {
6926
7039
  if (!checked) return null;
6927
7040
  const fixEmission = buildSpacingFix(decl.property, checked, ix);
6928
7041
  return makeFinding({
6929
- ruleId: RULE_ID10,
6930
- ruleVersion: RULE_VERSION11,
7042
+ ruleId: RULE_ID11,
7043
+ ruleVersion: RULE_VERSION12,
6931
7044
  severity: policy.severity,
6932
7045
  message: spacingMessage(decl.property, decl.value, allowed, scale.unit, checked),
6933
7046
  location: decl.location,
@@ -6981,8 +7094,8 @@ function checkInlineStyle(ix, inline, componentByNode, lookupFor) {
6981
7094
  const componentEvidenceId = componentByNode.get(node.id)?.id;
6982
7095
  const evidenceIds = componentEvidenceId ? [node.id, componentEvidenceId, inline.id, policy.id, scale.id] : [node.id, inline.id, policy.id, scale.id];
6983
7096
  return makeFinding({
6984
- ruleId: RULE_ID10,
6985
- ruleVersion: RULE_VERSION11,
7097
+ ruleId: RULE_ID11,
7098
+ ruleVersion: RULE_VERSION12,
6986
7099
  severity: policy.severity,
6987
7100
  message: spacingMessage(inline.property, inline.value, allowed, scale.unit, checked),
6988
7101
  location: node.location,
@@ -7043,8 +7156,8 @@ function spacingMessage(property, value, allowed, unit, checked) {
7043
7156
  }
7044
7157
 
7045
7158
  // src/rules/styles-no-raw-typography.ts
7046
- var RULE_ID11 = "styles/no-raw-typography";
7047
- var RULE_VERSION12 = "1";
7159
+ var RULE_ID12 = "styles/no-raw-typography";
7160
+ var RULE_VERSION13 = "1";
7048
7161
  var FONT_SIZE_PROPERTIES = /* @__PURE__ */ new Set(["font-size", "fontsize"]);
7049
7162
  function ruleStylesNoRawTypography(ix) {
7050
7163
  const policy = ix.policy.fontSizeScale();
@@ -7062,8 +7175,8 @@ function ruleStylesNoRawTypography(ix) {
7062
7175
  const fixEmission = buildFix(decl.property, checked, ix);
7063
7176
  findings.push(
7064
7177
  makeFinding({
7065
- ruleId: RULE_ID11,
7066
- ruleVersion: RULE_VERSION12,
7178
+ ruleId: RULE_ID12,
7179
+ ruleVersion: RULE_VERSION13,
7067
7180
  severity: policy.severity,
7068
7181
  message: typographyMessage(decl.property, decl.value, allowed, scale.unit, checked),
7069
7182
  location: decl.location,
@@ -7109,8 +7222,8 @@ function ruleStylesNoRawTypography(ix) {
7109
7222
  const evidenceIds = componentEvidenceId ? [node.id, componentEvidenceId, inline.id, policy.id, scale.id] : [node.id, inline.id, policy.id, scale.id];
7110
7223
  findings.push(
7111
7224
  makeFinding({
7112
- ruleId: RULE_ID11,
7113
- ruleVersion: RULE_VERSION12,
7225
+ ruleId: RULE_ID12,
7226
+ ruleVersion: RULE_VERSION13,
7114
7227
  severity: policy.severity,
7115
7228
  message: typographyMessage(inline.property, inline.value, allowed, scale.unit, checked),
7116
7229
  location: node.location,
@@ -7212,15 +7325,15 @@ function typographyMessage(property, value, allowed, unit, checked) {
7212
7325
  }
7213
7326
 
7214
7327
  // src/rules/theme-no-theme-coupled-literal.ts
7215
- var RULE_ID12 = "theme/no-theme-coupled-literal";
7216
- var RULE_VERSION13 = "1";
7328
+ var RULE_ID13 = "theme/no-theme-coupled-literal";
7329
+ var RULE_VERSION14 = "1";
7217
7330
  var COLOR_LITERAL_GLOBAL = /#[0-9a-fA-F]{3,8}\b|\b(?:rgba?|hsla?|oklab|oklch|lab|lch)\(\s*[\d.%\s,\/]+\s*\)/g;
7218
7331
  var WHITE_RGBA = /^rgba?\(\s*255\s*,\s*255\s*,\s*255/i;
7219
7332
  var BLACK_RGBA = /^rgba?\(\s*0\s*,\s*0\s*,\s*0/i;
7220
7333
  var BOX_SHADOW_PROP = /^(?:-webkit-|-moz-)?box-shadow$/i;
7221
7334
  var NEUTRAL_SHADOW = /^(?:none|inherit|initial|unset|revert|revert-layer)\s*$/i;
7222
7335
  function ruleThemeNoThemeCoupledLiteral(ix) {
7223
- const policy = ix.policy.ruleConfig(RULE_ID12);
7336
+ const policy = ix.policy.ruleConfig(RULE_ID13);
7224
7337
  if (!policy?.enabled) return [];
7225
7338
  const findings = [];
7226
7339
  for (const decl of ix.byKind("style_declaration")) {
@@ -7229,8 +7342,8 @@ function ruleThemeNoThemeCoupledLiteral(ix) {
7229
7342
  if (!hit) continue;
7230
7343
  findings.push(
7231
7344
  makeFinding({
7232
- ruleId: RULE_ID12,
7233
- ruleVersion: RULE_VERSION13,
7345
+ ruleId: RULE_ID13,
7346
+ ruleVersion: RULE_VERSION14,
7234
7347
  severity: policy.severity ?? "warn",
7235
7348
  message: hit.message,
7236
7349
  location: decl.location,
@@ -7320,8 +7433,8 @@ function isTokenFile(filePath) {
7320
7433
  }
7321
7434
 
7322
7435
  // src/rules/tailwind-arbitrary-color.ts
7323
- var RULE_ID13 = "tailwind/arbitrary-color";
7324
- var RULE_VERSION14 = "1";
7436
+ var RULE_ID14 = "tailwind/arbitrary-color";
7437
+ var RULE_VERSION15 = "1";
7325
7438
  var COLOR_UTILITIES = /* @__PURE__ */ new Set([
7326
7439
  "accent",
7327
7440
  "bg",
@@ -7363,8 +7476,8 @@ function ruleTailwindArbitraryColor(ix) {
7363
7476
  const evidenceIds = node ? [node.id, klass.id, policy.id] : [klass.id, policy.id];
7364
7477
  findings.push(
7365
7478
  makeFinding({
7366
- ruleId: RULE_ID13,
7367
- ruleVersion: RULE_VERSION14,
7479
+ ruleId: RULE_ID14,
7480
+ ruleVersion: RULE_VERSION15,
7368
7481
  severity: policy.severity,
7369
7482
  message: `\`${klass.raw}\` uses arbitrary raw color ${color}. Use a ${policy.prefer === "token" ? "design token" : "CSS variable"} instead.`,
7370
7483
  location: klass.location,
@@ -7395,8 +7508,8 @@ function fingerprintIdentity(klass, color) {
7395
7508
  }
7396
7509
 
7397
7510
  // src/rules/tailwind-arbitrary-spacing.ts
7398
- var RULE_ID14 = "tailwind/arbitrary-spacing";
7399
- var RULE_VERSION15 = "1";
7511
+ var RULE_ID15 = "tailwind/arbitrary-spacing";
7512
+ var RULE_VERSION16 = "1";
7400
7513
  var SPACING_UTILITY_TO_PROPERTY = /* @__PURE__ */ new Map([
7401
7514
  ["m", "margin"],
7402
7515
  ["mt", "margin"],
@@ -7442,8 +7555,8 @@ function ruleTailwindArbitrarySpacing(ix) {
7442
7555
  const fix = buildFix2(klass);
7443
7556
  findings.push(
7444
7557
  makeFinding({
7445
- ruleId: RULE_ID14,
7446
- ruleVersion: RULE_VERSION15,
7558
+ ruleId: RULE_ID15,
7559
+ ruleVersion: RULE_VERSION16,
7447
7560
  severity: policy.severity,
7448
7561
  message: `\`${klass.raw}\` is an arbitrary spacing value not on the project's spacing scale.`,
7449
7562
  location: klass.location,
@@ -7574,8 +7687,8 @@ function matchesTokenGlob(value, pattern) {
7574
7687
  }
7575
7688
 
7576
7689
  // src/rules/tailwind-forbidden-palette.ts
7577
- var RULE_ID15 = "tailwind/forbidden-palette";
7578
- var RULE_VERSION16 = "1";
7690
+ var RULE_ID16 = "tailwind/forbidden-palette";
7691
+ var RULE_VERSION17 = "1";
7579
7692
  function ruleTailwindForbiddenPalette(ix) {
7580
7693
  const deny = ix.policy.tailwindPaletteDeny();
7581
7694
  const allow = ix.policy.tailwindPaletteAllow();
@@ -7600,8 +7713,8 @@ function ruleTailwindForbiddenPalette(ix) {
7600
7713
  const evidenceIds = node ? [node.id, klass.id, resolved.id, policy.id] : [klass.id, resolved.id, policy.id];
7601
7714
  findings.push(
7602
7715
  makeFinding({
7603
- ruleId: RULE_ID15,
7604
- ruleVersion: RULE_VERSION16,
7716
+ ruleId: RULE_ID16,
7717
+ ruleVersion: RULE_VERSION17,
7605
7718
  severity: policy.severity,
7606
7719
  message: messageFor(klass, token, deniedPattern, allow?.patterns ?? []),
7607
7720
  location: klass.location,
@@ -7648,8 +7761,8 @@ function buildFix3(klass) {
7648
7761
  }
7649
7762
 
7650
7763
  // src/rules/tailwind-off-scale-spacing-token.ts
7651
- var RULE_ID16 = "tailwind/off-scale-spacing-token";
7652
- var RULE_VERSION17 = "1";
7764
+ var RULE_ID17 = "tailwind/off-scale-spacing-token";
7765
+ var RULE_VERSION18 = "1";
7653
7766
  function ruleTailwindOffScaleSpacingToken(ix) {
7654
7767
  const resolvedByKey = indexResolvedTailwindTokens(ix);
7655
7768
  const findings = [];
@@ -7675,8 +7788,8 @@ function ruleTailwindOffScaleSpacingToken(ix) {
7675
7788
  const evidenceIds = node ? [node.id, klass.id, resolved.id, policy.id, scale.id] : [klass.id, resolved.id, policy.id, scale.id];
7676
7789
  findings.push(
7677
7790
  makeFinding({
7678
- ruleId: RULE_ID16,
7679
- ruleVersion: RULE_VERSION17,
7791
+ ruleId: RULE_ID17,
7792
+ ruleVersion: RULE_VERSION18,
7680
7793
  severity: policy.severity,
7681
7794
  message: `\`${klass.raw}\` resolves to ${formatResolvedValue(signedValue, scale.unit)}, which is not on the project's spacing scale [${allowed.join(", ")}].`,
7682
7795
  location: klass.location,
@@ -7725,8 +7838,8 @@ function buildFix4(raw, utility) {
7725
7838
  }
7726
7839
 
7727
7840
  // src/rules/tailwind-raw-color-via-token.ts
7728
- var RULE_ID17 = "tailwind/raw-color-via-token";
7729
- var RULE_VERSION18 = "1";
7841
+ var RULE_ID18 = "tailwind/raw-color-via-token";
7842
+ var RULE_VERSION19 = "1";
7730
7843
  function ruleTailwindRawColorViaToken(ix) {
7731
7844
  const allow = ix.policy.tailwindPaletteAllow();
7732
7845
  if (!allow || allow.patterns.length === 0) return [];
@@ -7746,8 +7859,8 @@ function ruleTailwindRawColorViaToken(ix) {
7746
7859
  const evidenceIds = node ? [node.id, klass.id, resolved.id, allow.id] : [klass.id, resolved.id, allow.id];
7747
7860
  findings.push(
7748
7861
  makeFinding({
7749
- ruleId: RULE_ID17,
7750
- ruleVersion: RULE_VERSION18,
7862
+ ruleId: RULE_ID18,
7863
+ ruleVersion: RULE_VERSION19,
7751
7864
  severity: allow.severity,
7752
7865
  message: `\`${klass.raw}\` resolves to raw color \`${color}\` (${sourceLabel2(resolved.resolved.source)}). Project policy requires \`${allow.patterns.join("`, `")}\` palette tokens.`,
7753
7866
  location: klass.location,
@@ -7780,8 +7893,8 @@ function sourceLabel2(source) {
7780
7893
  }
7781
7894
 
7782
7895
  // src/rules/tailwind-unknown-class.ts
7783
- var RULE_ID18 = "tailwind/unknown-class";
7784
- var RULE_VERSION19 = "1";
7896
+ var RULE_ID19 = "tailwind/unknown-class";
7897
+ var RULE_VERSION20 = "1";
7785
7898
  var UNIVERSAL_COLOR_KEYWORDS = /* @__PURE__ */ new Set([
7786
7899
  "white",
7787
7900
  "black",
@@ -7802,8 +7915,8 @@ function ruleTailwindUnknownClass(ix) {
7802
7915
  const evidenceIds2 = node2 ? [node2.id, klass.id, policy.id] : [klass.id, policy.id];
7803
7916
  findings.push(
7804
7917
  makeFinding({
7805
- ruleId: RULE_ID18,
7806
- ruleVersion: RULE_VERSION19,
7918
+ ruleId: RULE_ID19,
7919
+ ruleVersion: RULE_VERSION20,
7807
7920
  severity: policy.severity,
7808
7921
  message: `\`${klass.raw}\` could not be parsed as a known Tailwind utility.`,
7809
7922
  location: klass.location,
@@ -7837,8 +7950,8 @@ function ruleTailwindUnknownClass(ix) {
7837
7950
  const evidenceIds = node ? [node.id, klass.id, resolved.id, policy.id] : [klass.id, resolved.id, policy.id];
7838
7951
  findings.push(
7839
7952
  makeFinding({
7840
- ruleId: RULE_ID18,
7841
- ruleVersion: RULE_VERSION19,
7953
+ ruleId: RULE_ID19,
7954
+ ruleVersion: RULE_VERSION20,
7842
7955
  severity: policy.severity,
7843
7956
  message: `\`${klass.raw}\` did not resolve to any known palette token.`,
7844
7957
  location: klass.location,
@@ -7866,12 +7979,12 @@ function ruleTailwindUnknownClass(ix) {
7866
7979
  }
7867
7980
 
7868
7981
  // src/rules/tokens-require-dual-fallback.ts
7869
- var RULE_ID19 = "tokens/require-dual-fallback";
7870
- var RULE_VERSION20 = "1";
7982
+ var RULE_ID20 = "tokens/require-dual-fallback";
7983
+ var RULE_VERSION21 = "1";
7871
7984
  var CSS_VAR_WITH_OPTIONAL_FALLBACK = /var\(\s*(--[A-Za-z0-9_-]+)(\s*,\s*[^)]*)?\)/g;
7872
7985
  var SASS_FILE = /\.(scss|sass)$/i;
7873
7986
  function ruleTokensRequireDualFallback(ix) {
7874
- const policy = ix.policy.ruleConfig(RULE_ID19);
7987
+ const policy = ix.policy.ruleConfig(RULE_ID20);
7875
7988
  if (!policy?.enabled) return [];
7876
7989
  const tokensByCssVariable = indexTokensByCssVariable(ix.tokens.list());
7877
7990
  const findings = [];
@@ -7906,8 +8019,8 @@ function ruleTokensRequireDualFallback(ix) {
7906
8019
  ) : void 0;
7907
8020
  findings.push(
7908
8021
  makeFinding({
7909
- ruleId: RULE_ID19,
7910
- ruleVersion: RULE_VERSION20,
8022
+ ruleId: RULE_ID20,
8023
+ ruleVersion: RULE_VERSION21,
7911
8024
  severity: policy.severity ?? "warn",
7912
8025
  message: hasScssVariable ? `var(${tokenName}) is missing an SCSS fallback. Use var(${tokenName}, ${scssVar}).` : `var(${tokenName}) is missing a fallback. No matching SCSS variable was found in the token source; add a fallback manually or define ${scssVar}.`,
7913
8026
  location: decl.location,
@@ -7947,8 +8060,8 @@ function indexTokensByCssVariable(tokens) {
7947
8060
  }
7948
8061
 
7949
8062
  // src/rules/tokens-css-vars-must-be-defined.ts
7950
- var RULE_ID20 = "tokens/css-vars-must-be-defined";
7951
- var RULE_VERSION21 = "1";
8063
+ var RULE_ID21 = "tokens/css-vars-must-be-defined";
8064
+ var RULE_VERSION22 = "1";
7952
8065
  var CSS_VAR_REFERENCE = /var\(\s*(--[A-Za-z0-9_-]+)/gi;
7953
8066
  function ruleTokensCssVarsMustBeDefined(ix) {
7954
8067
  const policy = ix.policy.cssVarsMustBeDefined();
@@ -7975,8 +8088,8 @@ function ruleTokensCssVarsMustBeDefined(ix) {
7975
8088
  seen.add(tokenName);
7976
8089
  findings.push(
7977
8090
  makeFinding({
7978
- ruleId: RULE_ID20,
7979
- ruleVersion: RULE_VERSION21,
8091
+ ruleId: RULE_ID21,
8092
+ ruleVersion: RULE_VERSION22,
7980
8093
  severity: policy.severity,
7981
8094
  message: `var(${tokenName}) is not in the contract token vocabulary. Use a token defined in your contract's token source files.`,
7982
8095
  location: decl.location,
@@ -8056,6 +8169,8 @@ function isDenyEligible(finding, failOnWarnings) {
8056
8169
  // src/rules/tiers.ts
8057
8170
  var RULE_TIER = {
8058
8171
  // ---- Tier A — contract vocabulary -------------------------------------
8172
+ "components/shadow-component": "contract",
8173
+ // canonical-component identity drift
8059
8174
  "components/prefer-library": "contract",
8060
8175
  // canonical-component bypass (FUI1004)
8061
8176
  "tokens/css-vars-must-be-defined": "contract",
@@ -8101,6 +8216,7 @@ function hygieneTierRuleIds() {
8101
8216
  return Object.keys(RULE_TIER).filter((id) => !isContractTierRule(id));
8102
8217
  }
8103
8218
  var VOCABULARY_ORDER = [
8219
+ "components/shadow-component",
8104
8220
  "components/prefer-library",
8105
8221
  "tokens/css-vars-must-be-defined"
8106
8222
  ];
@@ -8161,6 +8277,11 @@ var RULES = [
8161
8277
  version: "1",
8162
8278
  run: ruleComponentsPreferLibrary
8163
8279
  },
8280
+ {
8281
+ id: "components/shadow-component",
8282
+ version: "1",
8283
+ run: ruleComponentsShadowComponent
8284
+ },
8164
8285
  {
8165
8286
  id: "styles/no-raw-color",
8166
8287
  version: "1",
@@ -8755,6 +8876,133 @@ function readPreferLibraryRule(config) {
8755
8876
  const raw = config?.rules?.["components/prefer-library"];
8756
8877
  return raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
8757
8878
  }
8879
+
8880
+ // src/identity/component-key.ts
8881
+ function buildComponentKey({ repoRelativeFile, exportName }) {
8882
+ const file = normalizeRepoRelativeFile(repoRelativeFile);
8883
+ const name = exportName.trim();
8884
+ if (!file) throw new Error("repoRelativeFile must not be empty");
8885
+ if (!name) throw new Error("exportName must not be empty");
8886
+ return `${file}#${name}`;
8887
+ }
8888
+ function normalizeRepoRelativeFile(file) {
8889
+ const slashNormalized = file.trim().replace(/\\/g, "/").replace(/^\.\/+/, "");
8890
+ const absolute = slashNormalized.startsWith("/");
8891
+ const parts = [];
8892
+ for (const part of slashNormalized.split("/")) {
8893
+ if (!part || part === ".") continue;
8894
+ if (part === ".." && parts.length > 0 && parts.at(-1) !== "..") {
8895
+ parts.pop();
8896
+ continue;
8897
+ }
8898
+ parts.push(part);
8899
+ }
8900
+ return `${absolute ? "/" : ""}${parts.join("/")}`;
8901
+ }
8902
+
8903
+ // src/identity/classify.ts
8904
+ function classifyIdentity(input) {
8905
+ const { definition, renderRoot } = input;
8906
+ if (definition.canonical) return { state: "canonical", confidence: "confirmed" };
8907
+ const decision = input.decisions?.find(
8908
+ (candidate) => candidate.componentKey === definition.componentKey
8909
+ );
8910
+ if (decision?.decision === "sanctioned-wrapper") {
8911
+ return withTarget("variant", "confirmed", decision.canonicalTarget ?? rootTarget(input));
8912
+ }
8913
+ if (decision?.decision === "rejected-wrapper") {
8914
+ return withTarget("shadow", "confirmed", decision.canonicalTarget ?? shadowTarget(input));
8915
+ }
8916
+ if (renderRoot.kind === "canonical") {
8917
+ if ((input.usageCounts?.nestedElements ?? 0) > 0 || (input.usageCounts?.staticTextChildren ?? 0) > 0) {
8918
+ return withTarget("composite", "confirmed", renderRoot.target.componentKey);
8919
+ }
8920
+ const confidence = strongOverlap(input.propSurfaceOverlap) ? "likely" : "review";
8921
+ return withTarget("variant", confidence, renderRoot.target.componentKey);
8922
+ }
8923
+ if (renderRoot.kind === "intrinsic") {
8924
+ return classifyIntrinsic(input, renderRoot);
8925
+ }
8926
+ if (renderRoot.kind === "mixed") {
8927
+ if (renderRoot.canonicalTargets.length > 0) {
8928
+ const target = bestCanonicalReference(
8929
+ definition.exportName,
8930
+ renderRoot.canonicalTargets
8931
+ )?.componentKey;
8932
+ const fullyResolved = renderRoot.externalTargets.length === 0 && renderRoot.unresolvedNames.length === 0;
8933
+ return withTarget("composite", fullyResolved ? "confirmed" : "likely", target);
8934
+ }
8935
+ if (renderRoot.externalTargets.length > 0 && renderRoot.localTargets.length === 0 && renderRoot.unresolvedNames.length === 0) {
8936
+ return { state: "external", confidence: "likely" };
8937
+ }
8938
+ return withTarget("unresolved", "review", rootTarget(input));
8939
+ }
8940
+ if (renderRoot.kind === "external") {
8941
+ return { state: "external", confidence: "confirmed" };
8942
+ }
8943
+ return withTarget("unresolved", "review", rootTarget(input));
8944
+ }
8945
+ function classifyIntrinsic(input, root) {
8946
+ const target = bestCanonicalReference(input.definition.exportName, root.canonicalTargets);
8947
+ if (!target || !root.interactive && !root.hasRole) {
8948
+ return withTarget("unresolved", "review", rootTarget(input));
8949
+ }
8950
+ let confidence = root.precisionTier === "exact-html" || root.precisionTier === "role-reimpl" ? "confirmed" : "likely";
8951
+ if (confidence === "likely" && input.stylingOwnership !== "layout-only" && (strongOverlap(input.propSurfaceOverlap) || input.stylingOwnership === "visual-role")) {
8952
+ confidence = "confirmed";
8953
+ }
8954
+ return { state: "shadow", confidence, canonicalTarget: target.role };
8955
+ }
8956
+ function rootTarget(input) {
8957
+ const { renderRoot } = input;
8958
+ if (renderRoot.kind === "canonical") return renderRoot.target.componentKey;
8959
+ if (renderRoot.kind === "intrinsic") {
8960
+ return bestCanonicalReference(input.definition.exportName, renderRoot.canonicalTargets)?.role;
8961
+ }
8962
+ if (renderRoot.kind === "mixed") {
8963
+ return bestCanonicalReference(input.definition.exportName, renderRoot.canonicalTargets)?.componentKey;
8964
+ }
8965
+ return bestMessageTarget(input.definition.exportName, input.nameSimilarityTargets ?? []);
8966
+ }
8967
+ function shadowTarget(input) {
8968
+ const { renderRoot } = input;
8969
+ if (renderRoot.kind === "canonical") return renderRoot.target.role;
8970
+ if (renderRoot.kind === "intrinsic") {
8971
+ return bestCanonicalReference(input.definition.exportName, renderRoot.canonicalTargets)?.role;
8972
+ }
8973
+ if (renderRoot.kind === "mixed") {
8974
+ return bestCanonicalReference(input.definition.exportName, renderRoot.canonicalTargets)?.role;
8975
+ }
8976
+ return bestMessageTarget(input.definition.exportName, input.nameSimilarityTargets ?? []);
8977
+ }
8978
+ function bestCanonicalReference(definitionName, candidates) {
8979
+ return [...candidates].sort(
8980
+ (left, right) => nameSimilarityScore(definitionName, right.role) - nameSimilarityScore(definitionName, left.role) || left.componentKey.localeCompare(right.componentKey)
8981
+ )[0];
8982
+ }
8983
+ function bestMessageTarget(definitionName, candidates) {
8984
+ return [...candidates].sort(
8985
+ (left, right) => nameSimilarityScore(definitionName, right) - nameSimilarityScore(definitionName, left) || left.localeCompare(right)
8986
+ )[0];
8987
+ }
8988
+ function nameSimilarityScore(definitionName, target) {
8989
+ const definition = normalizeName(definitionName);
8990
+ const candidate = normalizeName(target);
8991
+ if (!candidate) return 0;
8992
+ if (definition === candidate) return 3;
8993
+ if (definition.endsWith(candidate) || definition.startsWith(candidate)) return 2;
8994
+ if (definition.includes(candidate) || candidate.includes(definition)) return 1;
8995
+ return 0;
8996
+ }
8997
+ function normalizeName(value) {
8998
+ return value.split(/[.#/]/).at(-1)?.replace(/[^a-z0-9]/gi, "").toLowerCase() ?? "";
8999
+ }
9000
+ function strongOverlap(overlap) {
9001
+ return overlap !== void 0 && overlap >= 0.7;
9002
+ }
9003
+ function withTarget(state, confidence, canonicalTarget) {
9004
+ return canonicalTarget ? { state, confidence, canonicalTarget } : { state, confidence };
9005
+ }
8758
9006
  export {
8759
9007
  AGENT_CONTEXT_RELATIVE_PATH,
8760
9008
  AGENT_FORMAT_SCHEMA_VERSION,
@@ -8805,6 +9053,7 @@ export {
8805
9053
  bridgeSourceViolations,
8806
9054
  budgetBar,
8807
9055
  buildAgentFormat,
9056
+ buildComponentKey,
8808
9057
  buildRegistryArtifact,
8809
9058
  buildRegistryFile,
8810
9059
  buildRegistryInstallPlan,
@@ -8831,6 +9080,7 @@ export {
8831
9080
  checkStoryExclusion,
8832
9081
  clampMaxPasses,
8833
9082
  classifyComplexity,
9083
+ classifyIdentity,
8834
9084
  collectRegistryInstallDependencies,
8835
9085
  colorSimilarity,
8836
9086
  compareByVocabularyRank,
@@ -8926,6 +9176,7 @@ export {
8926
9176
  hashRegistryFileContent,
8927
9177
  hexToRgb,
8928
9178
  hygieneTierRuleIds,
9179
+ indexComponentIdentityUsage,
8929
9180
  inferTokenCategory,
8930
9181
  inferTokenGroup,
8931
9182
  isColorLike,
@@ -8952,6 +9203,8 @@ export {
8952
9203
  makeClassNameDynamicFact,
8953
9204
  makeClassNameLiteralFact,
8954
9205
  makeComponentCapabilityFact,
9206
+ makeComponentDefinitionFact,
9207
+ makeComponentIdentityFact,
8955
9208
  makeComponentMetadataFact,
8956
9209
  makeContractTokenFact,
8957
9210
  makeFinding,
@@ -9076,6 +9329,7 @@ export {
9076
9329
  ruleA11yRequiredAccessibleName,
9077
9330
  ruleComponentsForbiddenPropValue,
9078
9331
  ruleComponentsPreferLibrary,
9332
+ ruleComponentsShadowComponent,
9079
9333
  ruleComponentsUnknownProp,
9080
9334
  ruleJsxPreferredComponent,
9081
9335
  ruleJsxPreferredImportPath,