@usefragments/core 1.4.0 → 1.5.1

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 (72) hide show
  1. package/dist/{chunk-CBSNXFOD.js → chunk-AOG4FTV6.js} +146 -6
  2. package/dist/chunk-AOG4FTV6.js.map +1 -0
  3. package/dist/{chunk-57QDBEHQ.js → chunk-ZHS52OT4.js} +3 -9
  4. package/dist/chunk-ZHS52OT4.js.map +1 -0
  5. package/dist/codes/index.d.ts +2 -2
  6. package/dist/codes/index.js +2 -2
  7. package/dist/compiled-types/index.d.ts +1 -1
  8. package/dist/generate/index.d.ts +1 -1
  9. package/dist/{governance-DxdJV6lx.d.ts → governance-B88uR3Zq.d.ts} +229 -1
  10. package/dist/{index-0lmh0Lbo.d.ts → index-DbkPE46t.d.ts} +48 -8
  11. package/dist/index.d.ts +779 -158
  12. package/dist/index.js +674 -127
  13. package/dist/index.js.map +1 -1
  14. package/dist/react-types.d.ts +1 -1
  15. package/dist/registry.d.ts +18 -18
  16. package/dist/schemas/index.d.ts +1 -1
  17. package/dist/schemas/index.js +1 -1
  18. package/dist/test-utils.d.ts +1 -1
  19. package/package.json +1 -1
  20. package/src/agent-format.test.ts +1 -0
  21. package/src/canonical-bridge.ts +46 -0
  22. package/src/canonical-direction.ts +118 -0
  23. package/src/codes/codes.ts +18 -0
  24. package/src/conform.ts +5 -0
  25. package/src/contract/preimage.test.ts +40 -0
  26. package/src/contract/preimage.ts +20 -0
  27. package/src/effective-governance-inputs.test.ts +52 -0
  28. package/src/effective-governance-inputs.ts +106 -0
  29. package/src/facts/builders.ts +70 -2
  30. package/src/facts/compile.ts +20 -2
  31. package/src/facts/fact-index.ts +4 -0
  32. package/src/facts/facts.test.ts +30 -0
  33. package/src/facts/index.ts +6 -0
  34. package/src/facts/types.ts +86 -7
  35. package/src/governance-integrity.test.ts +22 -0
  36. package/src/governance-integrity.ts +10 -4
  37. package/src/governance.test.ts +93 -0
  38. package/src/governance.ts +76 -0
  39. package/src/identity/classify.test.ts +185 -0
  40. package/src/identity/classify.ts +257 -0
  41. package/src/identity/component-key.test.ts +32 -0
  42. package/src/identity/component-key.ts +35 -0
  43. package/src/identity/usage.ts +39 -0
  44. package/src/index.ts +48 -0
  45. package/src/rules/__tests__/fix-emission-invariant.test.ts +35 -4
  46. package/src/rules/components-prefer-library.test.ts +151 -0
  47. package/src/rules/components-prefer-library.ts +46 -4
  48. package/src/rules/components-shadow-component.test.ts +145 -0
  49. package/src/rules/components-shadow-component.ts +81 -0
  50. package/src/rules/fix-availability.ts +2 -0
  51. package/src/rules/index.ts +14 -0
  52. package/src/rules/jsx-preferred-import-path.ts +45 -9
  53. package/src/rules/rules.test.ts +202 -6
  54. package/src/rules/spacing-resolution.ts +2 -2
  55. package/src/rules/styles-no-raw-color.test.ts +6 -1
  56. package/src/rules/styles-no-raw-color.ts +3 -2
  57. package/src/rules/styles-no-raw-dimensions.ts +5 -7
  58. package/src/rules/styles-no-raw-spacing.ts +5 -2
  59. package/src/rules/styles-no-raw-typography.ts +7 -4
  60. package/src/rules/taxonomy.test.ts +19 -1
  61. package/src/rules/tiers.ts +9 -5
  62. package/src/rules/token-candidates.ts +36 -0
  63. package/src/rules/tokens-require-dual-fallback.ts +2 -1
  64. package/src/rules/tokens-upstream-drift.test.ts +111 -0
  65. package/src/rules/tokens-upstream-drift.ts +57 -0
  66. package/src/rules/utils.ts +22 -14
  67. package/src/schema.ts +28 -0
  68. package/src/schemas/index.ts +5 -8
  69. package/src/token-types.ts +71 -1
  70. package/src/types.ts +18 -0
  71. package/dist/chunk-57QDBEHQ.js.map +0 -1
  72. package/dist/chunk-CBSNXFOD.js.map +0 -1
package/dist/index.js CHANGED
@@ -49,6 +49,7 @@ import {
49
49
  bridgeSourceViolations,
50
50
  byCode,
51
51
  byRuleId,
52
+ canonicalBridgeV1Schema,
52
53
  canonicalJson,
53
54
  compileComponentFacts,
54
55
  compileGlobalGovernanceFacts,
@@ -71,6 +72,8 @@ import {
71
72
  makeClassNameDynamicFact,
72
73
  makeClassNameLiteralFact,
73
74
  makeComponentCapabilityFact,
75
+ makeComponentDefinitionFact,
76
+ makeComponentIdentityFact,
74
77
  makeComponentMetadataFact,
75
78
  makeContractTokenFact,
76
79
  makeJsxComponentPreferredFact,
@@ -109,7 +112,7 @@ import {
109
112
  ownedImportsEqual,
110
113
  resolveComponentGovernance,
111
114
  scaleGovernanceRecordSchema
112
- } from "./chunk-CBSNXFOD.js";
115
+ } from "./chunk-AOG4FTV6.js";
113
116
  import {
114
117
  AGENT_FORMAT_SCHEMA_VERSION,
115
118
  agentErrorEnvelopeSchema,
@@ -134,7 +137,7 @@ import {
134
137
  suppressionDirectiveSchema,
135
138
  validatorResultSchema,
136
139
  violationSchema
137
- } from "./chunk-57QDBEHQ.js";
140
+ } from "./chunk-ZHS52OT4.js";
138
141
  import {
139
142
  OWNED_PACKAGE_IDENTITY_EPOCH,
140
143
  OWNED_PACKAGE_IDENTITY_SOURCE_SHA256,
@@ -1685,6 +1688,14 @@ var tokenConfigSchema = z4.object({
1685
1688
  // vocabulary seeds the known-token set (#7c). A packages-only tokens block
1686
1689
  // is valid — see the superRefine below.
1687
1690
  packages: z4.array(z4.string().min(1)).optional(),
1691
+ aliases: z4.record(z4.string().min(1)).optional(),
1692
+ upstream: z4.array(
1693
+ z4.object({
1694
+ packageName: z4.string().min(1),
1695
+ version: z4.string().min(1).optional(),
1696
+ digest: z4.string().regex(/^[a-f0-9]{64}$/u).optional()
1697
+ })
1698
+ ).optional(),
1688
1699
  exclude: z4.array(repoRelativePathSchema).optional(),
1689
1700
  themeSelectors: z4.record(z4.string()).optional(),
1690
1701
  enabled: z4.boolean().optional(),
@@ -1776,7 +1787,18 @@ var fragmentsConfigSchema = z4.object({
1776
1787
  })
1777
1788
  ).optional()
1778
1789
  }).optional(),
1779
- recognizedClassHelpers: z4.array(z4.string().min(1)).optional()
1790
+ recognizedClassHelpers: z4.array(z4.string().min(1)).optional(),
1791
+ identity: z4.object({
1792
+ decisions: z4.array(
1793
+ z4.object({
1794
+ component: z4.string().min(1),
1795
+ kind: z4.enum(["sanction", "reject", "dismiss"]),
1796
+ canonicalTarget: z4.string().min(1).optional(),
1797
+ reason: z4.string().optional(),
1798
+ decisionId: z4.string().min(1).optional()
1799
+ })
1800
+ ).optional()
1801
+ }).optional()
1780
1802
  });
1781
1803
  var compositionMetadataSchema = z4.object({
1782
1804
  pattern: z4.enum(["compound", "simple", "controlled", "wrapper"]).optional(),
@@ -4762,13 +4784,20 @@ function isExemptColor(value, except) {
4762
4784
  }
4763
4785
  return false;
4764
4786
  }
4765
- function tokenReference(name) {
4766
- if (name.startsWith("$")) return name;
4767
- const cssVarName = name.startsWith("--") ? name : `--${name.replace(/\./g, "-")}`;
4768
- return `var(${cssVarName})`;
4787
+ function tokenReference(token) {
4788
+ if (token.referenceFormat === "css-var") {
4789
+ const cssVarName = [token.name, ...token.sourceNames ?? []].find(
4790
+ (name) => name.startsWith("--")
4791
+ );
4792
+ return cssVarName ? `var(${cssVarName})` : token.name;
4793
+ }
4794
+ if (token.referenceFormat === "scss-var") {
4795
+ return [token.name, ...token.sourceNames ?? []].find((name) => name.startsWith("$")) ?? (token.name.startsWith("--") ? `$${token.name.slice(2)}` : token.name);
4796
+ }
4797
+ return token.sourceNames?.[0] ?? token.name;
4769
4798
  }
4770
4799
  function isApplicableTokenReference(referenceFormat) {
4771
- return referenceFormat === void 0 || referenceFormat === "css-var" || referenceFormat === "scss-var";
4800
+ return referenceFormat === "css-var" || referenceFormat === "scss-var";
4772
4801
  }
4773
4802
 
4774
4803
  // src/rules/a11y-required-accessible-name.ts
@@ -4925,6 +4954,77 @@ function isEnforceableHtmlEquivalent(args) {
4925
4954
  return builtIn !== null && builtIn.canonical === args.canonical;
4926
4955
  }
4927
4956
 
4957
+ // src/canonical-direction.ts
4958
+ function projectCanonicalDirectionConflicts(ix, canonicalSources) {
4959
+ const npmSources = canonicalSources.filter(
4960
+ (source) => source.kind === "npm"
4961
+ );
4962
+ if (npmSources.length === 0) return [];
4963
+ const identityByComponent = new Map(
4964
+ ix.byKind("component_identity").map((identity) => [identity.componentKey, identity])
4965
+ );
4966
+ const conflicts = [];
4967
+ for (const definition of ix.byKind("component_definition")) {
4968
+ if (!definition.exported || definition.renderRoot.resolution !== "canonical") continue;
4969
+ const target = npmTarget(definition.renderRoot.canonical, npmSources);
4970
+ if (!target) continue;
4971
+ const identity = identityByComponent.get(definition.componentKey);
4972
+ if (identity?.decisionId && decisionTargetsExport(identity.canonicalTarget, target.exportName)) {
4973
+ if (identity.state === "shadow") continue;
4974
+ if (identity.state === "variant" && identity.confidence === "confirmed") {
4975
+ conflicts.push({
4976
+ ...target,
4977
+ wrapperComponentKey: definition.componentKey,
4978
+ wrapperFile: definition.file,
4979
+ wrapperExportName: definition.exportName,
4980
+ state: "sanctioned"
4981
+ });
4982
+ continue;
4983
+ }
4984
+ }
4985
+ conflicts.push({
4986
+ ...target,
4987
+ wrapperComponentKey: definition.componentKey,
4988
+ wrapperFile: definition.file,
4989
+ wrapperExportName: definition.exportName,
4990
+ state: "unresolved"
4991
+ });
4992
+ }
4993
+ return conflicts.sort(
4994
+ (left, right) => left.packageName.localeCompare(right.packageName) || left.exportName.localeCompare(right.exportName) || left.wrapperComponentKey.localeCompare(right.wrapperComponentKey)
4995
+ );
4996
+ }
4997
+ function canonicalDirectionConflictsTarget(conflicts, importPath, exportName) {
4998
+ if (!importPath) return false;
4999
+ return conflicts.some(
5000
+ (conflict) => ownedImportMatchesRoot(importPath, conflict.packageName) && conflict.exportName === exportName
5001
+ );
5002
+ }
5003
+ function npmTarget(canonicalTarget, sources) {
5004
+ const separator = canonicalTarget.lastIndexOf("#");
5005
+ if (separator <= 0 || separator === canonicalTarget.length - 1) return null;
5006
+ const importPath = canonicalTarget.slice(0, separator);
5007
+ const imported = canonicalTarget.slice(separator + 1).split(".").at(-1);
5008
+ if (!imported) return null;
5009
+ for (const source of sources) {
5010
+ if (!ownedImportMatchesRoot(importPath, source.specifier)) continue;
5011
+ const subpath = packageSubpath(importPath, source.specifier);
5012
+ const exportName = imported === "default" ? subpath.split("/").filter(Boolean).at(-1) : imported;
5013
+ if (!exportName) continue;
5014
+ if (source.exclude?.includes(exportName)) continue;
5015
+ if (source.include && !source.include.includes(exportName)) continue;
5016
+ return { packageName: source.specifier, exportName };
5017
+ }
5018
+ return null;
5019
+ }
5020
+ function packageSubpath(importPath, packageName) {
5021
+ return importPath === packageName ? "" : importPath.slice(packageName.length + 1);
5022
+ }
5023
+ function decisionTargetsExport(target, exportName) {
5024
+ if (!target) return true;
5025
+ return target === exportName || target.split("#").at(-1)?.split(".").at(-1) === exportName;
5026
+ }
5027
+
4928
5028
  // src/rules/components-prefer-library.ts
4929
5029
  var RULE_ID2 = "components/prefer-library";
4930
5030
  var RULE_VERSION2 = "1";
@@ -5068,8 +5168,12 @@ function findClassNameReimpl(node, classTokensByNode, targets) {
5068
5168
  function ruleComponentsPreferLibrary(ix) {
5069
5169
  const policy = ix.policy.ruleConfig(RULE_ID2);
5070
5170
  if (!policy?.enabled) return [];
5071
- const sources = canonicalSourcesFromPolicy(policy.options?.canonicalSources);
5072
- const mappings = canonicalMappingsFromPolicy(policy.options?.canonicalMappings);
5171
+ const configuredSources = canonicalSourcesFromPolicy(policy.options?.canonicalSources);
5172
+ const conflicts = projectCanonicalDirectionConflicts(ix, configuredSources);
5173
+ const sources = configuredSources.map((source) => suppressConflictedExports(source, conflicts));
5174
+ const mappings = canonicalMappingsFromPolicy(policy.options?.canonicalMappings).filter(
5175
+ (mapping) => !canonicalDirectionConflictsTarget(conflicts, mapping.importPath, mapping.name)
5176
+ );
5073
5177
  if (sources.length === 0 && mappings.length === 0) return [];
5074
5178
  const importsByFileAndLocal = indexImportsByFileAndLocal(ix);
5075
5179
  const propsByNode = indexPropsByNodeId(ix);
@@ -5077,12 +5181,14 @@ function ruleComponentsPreferLibrary(ix) {
5077
5181
  const classTokensByNode = indexClassTokensByNode(ix);
5078
5182
  const reimplTargets = canonicalReimplTargets(sources, mappings);
5079
5183
  const canonicalDirectoryResolvedNodes = indexCanonicalDirectoryResolvedNodes(ix, sources);
5184
+ const shadowRenderRootNodeIds = indexShadowRenderRootNodeIds(ix);
5080
5185
  const findings = [];
5081
5186
  const seenImportFixes = /* @__PURE__ */ new Set();
5082
5187
  for (const node of ix.byKind("usage_node")) {
5083
5188
  if (isCanonicalSourceImplementationFile(node.file, sources)) continue;
5084
5189
  if (node.element.includes(".")) continue;
5085
5190
  if (canonicalDirectoryResolvedNodes.has(node.id)) continue;
5191
+ if (shadowRenderRootNodeIds.has(node.id)) continue;
5086
5192
  const props = propsByNode.get(node.id) ?? [];
5087
5193
  const textChildren = textByNode.get(node.id) ?? [];
5088
5194
  const importsForFile = importsByFileAndLocal.get(node.file);
@@ -5330,6 +5436,28 @@ function ruleComponentsPreferLibrary(ix) {
5330
5436
  }
5331
5437
  return findings;
5332
5438
  }
5439
+ function suppressConflictedExports(source, conflicts) {
5440
+ if (source.kind !== "npm") return source;
5441
+ const names = conflicts.filter((conflict) => ownedImportMatchesRoot(source.specifier, conflict.packageName)).map((conflict) => conflict.exportName);
5442
+ if (names.length === 0) return source;
5443
+ const blocked = new Set(names);
5444
+ if (source.include) {
5445
+ return { ...source, include: source.include.filter((name) => !blocked.has(name)) };
5446
+ }
5447
+ return { ...source, exclude: [.../* @__PURE__ */ new Set([...source.exclude ?? [], ...names])].sort() };
5448
+ }
5449
+ function indexShadowRenderRootNodeIds(ix) {
5450
+ const identities = ix.byKind("component_identity");
5451
+ if (identities.length === 0) return /* @__PURE__ */ new Set();
5452
+ const nodeIds = /* @__PURE__ */ new Set();
5453
+ for (const identity of identities) {
5454
+ if (identity.state !== "shadow") continue;
5455
+ for (const evidenceId of identity.evidence) {
5456
+ if (ix.get(evidenceId)?.kind === "usage_node") nodeIds.add(evidenceId);
5457
+ }
5458
+ }
5459
+ return nodeIds;
5460
+ }
5333
5461
  function findBuiltInRawHtmlMatch(node, props, textChildren, inputType) {
5334
5462
  if (isIconOnlyButton(node, props, textChildren)) {
5335
5463
  return { canonical: "IconButton", tier: "icon-only" };
@@ -5656,9 +5784,95 @@ function isIdentifier(value) {
5656
5784
  return /^[A-Za-z_$][\w$]*$/.test(value);
5657
5785
  }
5658
5786
 
5659
- // src/rules/components-forbidden-prop-value.ts
5660
- var RULE_ID3 = "components/forbidden-prop-value";
5787
+ // src/identity/usage.ts
5788
+ function indexComponentIdentityUsage(ix) {
5789
+ const nodesById = new Map(ix.byKind("usage_node").map((node) => [node.id, node]));
5790
+ const nodesByComponent = /* @__PURE__ */ new Map();
5791
+ for (const usage of ix.byKind("usage_component")) {
5792
+ const node = nodesById.get(usage.nodeId);
5793
+ if (!node) continue;
5794
+ const componentKey = String(usage.componentId);
5795
+ const nodes = nodesByComponent.get(componentKey) ?? /* @__PURE__ */ new Map();
5796
+ nodes.set(node.id, node);
5797
+ nodesByComponent.set(componentKey, nodes);
5798
+ }
5799
+ return new Map(
5800
+ [...nodesByComponent].map(([componentKey, nodesByIdForComponent]) => {
5801
+ const nodes = [...nodesByIdForComponent.values()];
5802
+ return [
5803
+ componentKey,
5804
+ {
5805
+ usageCount: nodes.length,
5806
+ blastRadius: new Set(nodes.map((node) => node.file)).size,
5807
+ nodes
5808
+ }
5809
+ ];
5810
+ })
5811
+ );
5812
+ }
5813
+
5814
+ // src/rules/components-shadow-component.ts
5815
+ var RULE_ID3 = "components/shadow-component";
5661
5816
  var RULE_VERSION3 = "1";
5817
+ function ruleComponentsShadowComponent(ix) {
5818
+ const policy = ix.policy.ruleConfig(RULE_ID3);
5819
+ if (!policy?.enabled) return [];
5820
+ const definitions = new Map(
5821
+ ix.byKind("component_definition").map((definition) => [definition.componentKey, definition])
5822
+ );
5823
+ const usageByComponent = indexComponentIdentityUsage(ix);
5824
+ const findings = [];
5825
+ for (const identity of ix.byKind("component_identity")) {
5826
+ const reviewVariant = identity.state === "variant" && identity.confidence === "review";
5827
+ if (identity.state !== "shadow" && !reviewVariant) continue;
5828
+ const definition = definitions.get(identity.componentKey);
5829
+ if (!definition) continue;
5830
+ const usage = usageByComponent.get(identity.componentKey);
5831
+ const usageCount = usage?.usageCount ?? 0;
5832
+ const blastRadius = usage?.blastRadius ?? 0;
5833
+ findings.push(
5834
+ makeFinding({
5835
+ ruleId: RULE_ID3,
5836
+ ruleVersion: RULE_VERSION3,
5837
+ severity: reviewVariant ? "info" : policy.severity ?? "warn",
5838
+ 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"}.`,
5839
+ location: definitionLocation(ix, definition, identity),
5840
+ evidence: ix.evidence(identity.evidence),
5841
+ fingerprintIdentity: {
5842
+ componentKey: identity.componentKey,
5843
+ canonicalTarget: identity.canonicalTarget
5844
+ },
5845
+ attributes: {
5846
+ canonicalTarget: identity.canonicalTarget,
5847
+ usageCount,
5848
+ blastRadius,
5849
+ confidence: identity.confidence,
5850
+ state: identity.state,
5851
+ ...reviewVariant ? { advisory: true } : {}
5852
+ }
5853
+ })
5854
+ );
5855
+ }
5856
+ return findings;
5857
+ }
5858
+ function definitionLocation(ix, definition, identity) {
5859
+ const evidence = ix.evidence(identity.evidence).map(({ fact }) => fact);
5860
+ const inDefinitionFile = evidence.find(
5861
+ (fact) => "location" in fact && fact.location?.file === definition.file
5862
+ );
5863
+ if (inDefinitionFile && "location" in inDefinitionFile && inDefinitionFile.location) {
5864
+ return inDefinitionFile.location;
5865
+ }
5866
+ const sourceLocated = evidence.find((fact) => "location" in fact && fact.location);
5867
+ if (sourceLocated && "location" in sourceLocated && sourceLocated.location) {
5868
+ return sourceLocated.location;
5869
+ }
5870
+ return { file: definition.file, line: 1, column: 0 };
5871
+ }
5872
+
5873
+ // src/rules/components-forbidden-prop-value.ts
5874
+ var RULE_ID4 = "components/forbidden-prop-value";
5875
+ var RULE_VERSION4 = "1";
5662
5876
  function ruleComponentsForbiddenPropValue(ix) {
5663
5877
  const findings = [];
5664
5878
  const componentByNode = indexComponentByNodeId(ix);
@@ -5688,8 +5902,8 @@ function ruleComponentsForbiddenPropValue(ix) {
5688
5902
  } : void 0;
5689
5903
  findings.push(
5690
5904
  makeFinding({
5691
- ruleId: RULE_ID3,
5692
- ruleVersion: RULE_VERSION3,
5905
+ ruleId: RULE_ID4,
5906
+ ruleVersion: RULE_VERSION4,
5693
5907
  severity: policy.severity,
5694
5908
  message: buildMessage(
5695
5909
  node.element,
@@ -5733,8 +5947,8 @@ function buildMessage(element, prop, value, because, replacement) {
5733
5947
  }
5734
5948
 
5735
5949
  // src/rules/components-unknown-prop.ts
5736
- var RULE_ID4 = "components/unknown-prop";
5737
- var RULE_VERSION4 = "1";
5950
+ var RULE_ID5 = "components/unknown-prop";
5951
+ var RULE_VERSION5 = "1";
5738
5952
  function ruleComponentsUnknownProp(ix) {
5739
5953
  const policy = ix.policy.jsxUnknownPropsForbidden();
5740
5954
  if (!policy) return [];
@@ -5755,8 +5969,8 @@ function ruleComponentsUnknownProp(ix) {
5755
5969
  if (knownNames.has(propUsage.prop)) continue;
5756
5970
  findings.push(
5757
5971
  makeFinding({
5758
- ruleId: RULE_ID4,
5759
- ruleVersion: RULE_VERSION4,
5972
+ ruleId: RULE_ID5,
5973
+ ruleVersion: RULE_VERSION5,
5760
5974
  severity: policy.severity,
5761
5975
  message: `Unknown prop "${propUsage.prop}" on <${node.element}>. Allowed props: ${formatPropList(knownNames)}.`,
5762
5976
  location: node.location,
@@ -5788,7 +6002,7 @@ function formatPropList(names) {
5788
6002
  // src/rules/composition-pattern.ts
5789
6003
  var CARDINALITY_RULE_ID = "composition/cardinality";
5790
6004
  var CO_OCCURRENCE_RULE_ID = "composition/co-occurrence";
5791
- var RULE_VERSION5 = "1";
6005
+ var RULE_VERSION6 = "1";
5792
6006
  function ruleCompositionCardinality(ix) {
5793
6007
  return evaluate(ix, CARDINALITY_RULE_ID, "cardinality");
5794
6008
  }
@@ -5856,7 +6070,7 @@ function checkCardinality(ctx, region, pattern, constraint, selected) {
5856
6070
  const extras = selected.slice(constraint.max);
5857
6071
  return makeFinding({
5858
6072
  ruleId: ctx.ruleId,
5859
- ruleVersion: RULE_VERSION5,
6073
+ ruleVersion: RULE_VERSION6,
5860
6074
  severity: ctx.severity,
5861
6075
  message: cardinalityMaxMessage(pattern, constraint.max, count),
5862
6076
  location: extras[0].location,
@@ -5867,7 +6081,7 @@ function checkCardinality(ctx, region, pattern, constraint, selected) {
5867
6081
  if (constraint.min !== void 0 && count < constraint.min) {
5868
6082
  return makeFinding({
5869
6083
  ruleId: ctx.ruleId,
5870
- ruleVersion: RULE_VERSION5,
6084
+ ruleVersion: RULE_VERSION6,
5871
6085
  severity: ctx.severity,
5872
6086
  message: cardinalityMinMessage(pattern, constraint.min, count),
5873
6087
  location: region.container.location,
@@ -5888,7 +6102,7 @@ function checkCoOccurrence(ctx, region, pattern, constraint, selected) {
5888
6102
  if (satisfied) return null;
5889
6103
  return makeFinding({
5890
6104
  ruleId: ctx.ruleId,
5891
- ruleVersion: RULE_VERSION5,
6105
+ ruleVersion: RULE_VERSION6,
5892
6106
  severity: ctx.severity,
5893
6107
  message: coOccurrenceMessage(pattern, constraint),
5894
6108
  location: region.container.location,
@@ -5940,8 +6154,8 @@ function coOccurrenceMessage(pattern, constraint) {
5940
6154
  }
5941
6155
 
5942
6156
  // src/rules/jsx-preferred-component.ts
5943
- var RULE_ID5 = "components/preferred-component";
5944
- var RULE_VERSION6 = "1";
6157
+ var RULE_ID6 = "components/preferred-component";
6158
+ var RULE_VERSION7 = "1";
5945
6159
  function ruleJsxPreferredComponent(ix) {
5946
6160
  const policies = ix.policy.jsxComponentPreferred();
5947
6161
  if (policies.length === 0) return [];
@@ -5957,8 +6171,8 @@ function ruleJsxPreferredComponent(ix) {
5957
6171
  if (!replacement) continue;
5958
6172
  findings.push(
5959
6173
  makeFinding({
5960
- ruleId: RULE_ID5,
5961
- ruleVersion: RULE_VERSION6,
6174
+ ruleId: RULE_ID6,
6175
+ ruleVersion: RULE_VERSION7,
5962
6176
  severity: policy.severity,
5963
6177
  message: `<${node.element}> should use <${replacement}>.`,
5964
6178
  location: node.location,
@@ -5995,8 +6209,8 @@ function componentNameFromId(componentId2) {
5995
6209
  }
5996
6210
 
5997
6211
  // src/rules/jsx-preferred-import-path.ts
5998
- var RULE_ID6 = "imports/preferred-path";
5999
- var RULE_VERSION7 = "1";
6212
+ var RULE_ID7 = "imports/preferred-path";
6213
+ var RULE_VERSION8 = "1";
6000
6214
  function ruleJsxPreferredImportPath(ix) {
6001
6215
  const policies = ix.policy.jsxImportPathPreferred();
6002
6216
  if (policies.length === 0) return [];
@@ -6004,6 +6218,18 @@ function ruleJsxPreferredImportPath(ix) {
6004
6218
  const seen = /* @__PURE__ */ new Set();
6005
6219
  for (const usage of ix.byKind("usage_import")) {
6006
6220
  const matches = policies.filter((policy) => {
6221
+ if (policy.bridge) {
6222
+ if (policy.bridge.implementationFiles.some((file) => sameRepoPath(file, usage.file))) {
6223
+ return false;
6224
+ }
6225
+ if (!ownedImportMatchesRoot(usage.source, policy.from)) return false;
6226
+ if (ownedImportsEqual(usage.source, policy.from)) {
6227
+ return usage.imported === policy.bridge.underlyingExportName;
6228
+ }
6229
+ const subpath = usage.source.slice(policy.from.length + 1);
6230
+ const leaf = subpath.split("/").at(-1);
6231
+ return leaf === policy.bridge.underlyingExportName && (usage.imported === "default" || usage.imported === policy.bridge.underlyingExportName);
6232
+ }
6007
6233
  if (!ownedImportsEqual(policy.from, usage.source)) return false;
6008
6234
  if (policy.imported !== void 0 && policy.imported !== usage.imported) return false;
6009
6235
  return true;
@@ -6016,10 +6242,10 @@ function ruleJsxPreferredImportPath(ix) {
6016
6242
  seen.add(key);
6017
6243
  findings.push(
6018
6244
  makeFinding({
6019
- ruleId: RULE_ID6,
6020
- ruleVersion: RULE_VERSION7,
6245
+ ruleId: RULE_ID7,
6246
+ ruleVersion: RULE_VERSION8,
6021
6247
  severity: policy.severity,
6022
- message: `Import from "${usage.source}" should use "${policy.to}".`,
6248
+ message: policy.bridge ? `Import ${policy.bridge.underlyingExportName} through the confirmed local ${policy.bridge.localExportName} wrapper from "${policy.to}".` : `Import from "${usage.source}" should use "${policy.to}".`,
6023
6249
  location: usage.location,
6024
6250
  evidence: ix.evidence([usage.id, policy.id]),
6025
6251
  fingerprintIdentity: {
@@ -6030,7 +6256,7 @@ function ruleJsxPreferredImportPath(ix) {
6030
6256
  line: usage.location.line,
6031
6257
  column: usage.location.column
6032
6258
  },
6033
- fix: {
6259
+ fix: policy.bridge ? void 0 : {
6034
6260
  kind: "replaceImport",
6035
6261
  title: policy.because ?? `Replace import path with "${policy.to}"`,
6036
6262
  from: usage.source,
@@ -6042,7 +6268,14 @@ function ruleJsxPreferredImportPath(ix) {
6042
6268
  suggestedImport: policy.to,
6043
6269
  imported: usage.imported,
6044
6270
  local: usage.local,
6045
- rawValue: usage.source
6271
+ rawValue: usage.source,
6272
+ ...policy.bridge ? {
6273
+ canonicalBridge: true,
6274
+ canonicalTarget: policy.bridge.componentKey,
6275
+ suggestedComponent: policy.bridge.localExportName,
6276
+ implementationFiles: policy.bridge.implementationFiles,
6277
+ decisionSource: policy.bridge.decisionSource
6278
+ } : {}
6046
6279
  }
6047
6280
  })
6048
6281
  );
@@ -6050,12 +6283,18 @@ function ruleJsxPreferredImportPath(ix) {
6050
6283
  }
6051
6284
  return findings;
6052
6285
  }
6286
+ function sameRepoPath(left, right) {
6287
+ return normalizeRepoPath(left) === normalizeRepoPath(right);
6288
+ }
6289
+ function normalizeRepoPath(path) {
6290
+ return path.replace(/\\/gu, "/").replace(/^\.\//u, "");
6291
+ }
6053
6292
 
6054
6293
  // src/rules/props-invalid-value.ts
6055
- var RULE_ID7 = "props/invalid-value";
6056
- var RULE_VERSION8 = "1";
6294
+ var RULE_ID8 = "props/invalid-value";
6295
+ var RULE_VERSION9 = "1";
6057
6296
  function rulePropsInvalidValue(ix) {
6058
- const policy = ix.policy.ruleConfig(RULE_ID7);
6297
+ const policy = ix.policy.ruleConfig(RULE_ID8);
6059
6298
  if (!policy?.enabled) return [];
6060
6299
  const findings = [];
6061
6300
  const componentByNode = indexComponentByNodeId(ix);
@@ -6084,8 +6323,8 @@ function rulePropsInvalidValue(ix) {
6084
6323
  } : void 0;
6085
6324
  findings.push(
6086
6325
  makeFinding({
6087
- ruleId: RULE_ID7,
6088
- ruleVersion: RULE_VERSION8,
6326
+ ruleId: RULE_ID8,
6327
+ ruleVersion: RULE_VERSION9,
6089
6328
  severity: policy.severity ?? "warn",
6090
6329
  message: buildMessage2(node.element, propUsage.prop, propUsage.value, allowedValues),
6091
6330
  location: propUsage.location ?? node.location,
@@ -6260,13 +6499,36 @@ function srgbGamma(linear) {
6260
6499
  return clamped <= 31308e-7 ? 12.92 * clamped : 1.055 * Math.pow(clamped, 1 / 2.4) - 0.055;
6261
6500
  }
6262
6501
 
6502
+ // src/rules/token-candidates.ts
6503
+ function normalizedTokenValue(token) {
6504
+ const value = token.value.trim();
6505
+ return token.category === "color" ? normalizeColor(value) : value.replace(/\s+/g, " ");
6506
+ }
6507
+ function localTokenCandidates(ix, category) {
6508
+ const all = ix.tokens.list();
6509
+ const upstreamByName = /* @__PURE__ */ new Map();
6510
+ for (const token of all) {
6511
+ if (token.role !== "upstream") continue;
6512
+ const matches = upstreamByName.get(token.name) ?? [];
6513
+ matches.push(token);
6514
+ upstreamByName.set(token.name, matches);
6515
+ }
6516
+ return all.filter((token) => {
6517
+ if (token.role === "upstream" || category !== void 0 && token.category !== category) {
6518
+ return false;
6519
+ }
6520
+ const matches = upstreamByName.get(token.upstreamName ?? token.name) ?? [];
6521
+ return matches.length !== 1 || normalizedTokenValue(token) === normalizedTokenValue(matches[0]);
6522
+ });
6523
+ }
6524
+
6263
6525
  // src/rules/styles-no-raw-color.ts
6264
- var RULE_ID8 = "styles/no-raw-color";
6265
- var RULE_VERSION9 = "1";
6526
+ var RULE_ID9 = "styles/no-raw-color";
6527
+ var RULE_VERSION10 = "1";
6266
6528
  function ruleStylesNoRawColor(ix) {
6267
6529
  const policy = ix.policy.rawColorPolicy();
6268
6530
  if (!policy) return [];
6269
- const colorTokens = ix.tokens.byCategory("color");
6531
+ const colorTokens = localTokenCandidates(ix, "color");
6270
6532
  const preferLabel = policy.prefer === "token" ? "design token" : "CSS variable";
6271
6533
  const findings = [];
6272
6534
  const advisorySeverity = policy.severity === "error" ? "warn" : policy.severity;
@@ -6280,8 +6542,8 @@ function ruleStylesNoRawColor(ix) {
6280
6542
  const evidenceIds = resolution ? [decl.id, policy.id, resolution.token.id] : [decl.id, policy.id];
6281
6543
  findings.push(
6282
6544
  makeFinding({
6283
- ruleId: RULE_ID8,
6284
- ruleVersion: RULE_VERSION9,
6545
+ ruleId: RULE_ID9,
6546
+ ruleVersion: RULE_VERSION10,
6285
6547
  severity: policy.severity,
6286
6548
  message: colorMessage(color, `\`${decl.property}\``, resolution, preferLabel),
6287
6549
  location: decl.location,
@@ -6316,8 +6578,8 @@ function ruleStylesNoRawColor(ix) {
6316
6578
  const evidenceIds2 = componentEvidenceId2 ? [node2.id, componentEvidenceId2, inline.id, policy.id] : [node2.id, inline.id, policy.id];
6317
6579
  findings.push(
6318
6580
  makeFinding({
6319
- ruleId: RULE_ID8,
6320
- ruleVersion: RULE_VERSION9,
6581
+ ruleId: RULE_ID9,
6582
+ ruleVersion: RULE_VERSION10,
6321
6583
  severity: advisorySeverity,
6322
6584
  message: `Runtime-constructed raw color \`${inline.value}\` on inline \`${inline.property}\`. Use a ${preferLabel} instead.`,
6323
6585
  location: node2.location,
@@ -6353,8 +6615,8 @@ function ruleStylesNoRawColor(ix) {
6353
6615
  const evidenceIds = resolution ? [...baseEvidence, resolution.token.id] : baseEvidence;
6354
6616
  findings.push(
6355
6617
  makeFinding({
6356
- ruleId: RULE_ID8,
6357
- ruleVersion: RULE_VERSION9,
6618
+ ruleId: RULE_ID9,
6619
+ ruleVersion: RULE_VERSION10,
6358
6620
  severity: policy.severity,
6359
6621
  message: colorMessage(color, `inline \`${inline.property}\``, resolution, preferLabel),
6360
6622
  location: node.location,
@@ -6397,8 +6659,8 @@ function ruleStylesNoRawColor(ix) {
6397
6659
  const evidenceIds = resolution ? [...baseEvidence, resolution.token.id] : baseEvidence;
6398
6660
  findings.push(
6399
6661
  makeFinding({
6400
- ruleId: RULE_ID8,
6401
- ruleVersion: RULE_VERSION9,
6662
+ ruleId: RULE_ID9,
6663
+ ruleVersion: RULE_VERSION10,
6402
6664
  severity: advisorySeverity,
6403
6665
  message: colorMessage(color, `\`${prop.prop}\``, resolution, preferLabel),
6404
6666
  location: prop.location ?? node.location,
@@ -6520,7 +6782,7 @@ function normalizeColor2(value) {
6520
6782
  return lower;
6521
6783
  }
6522
6784
  function buildTokenFix(property, rawValue, rawColor, match, ix) {
6523
- const reference = tokenReference(match.token.name);
6785
+ const reference = tokenReference(match.token);
6524
6786
  const value = replaceRawColor(rawValue, rawColor, reference);
6525
6787
  return emitFix(
6526
6788
  {
@@ -6549,14 +6811,14 @@ function escapeRegExp(value) {
6549
6811
  }
6550
6812
 
6551
6813
  // src/rules/styles-no-raw-dimensions.ts
6552
- var RULE_ID9 = "styles/no-raw-dimensions";
6553
- var RULE_VERSION10 = "1";
6814
+ var RULE_ID10 = "styles/no-raw-dimensions";
6815
+ var RULE_VERSION11 = "1";
6554
6816
  function ruleStylesNoRawDimensions(ix) {
6555
6817
  const policy = ix.policy.rawDimensionPolicy();
6556
6818
  if (!policy) return [];
6557
6819
  const appliesTo = new Set(policy.appliesTo);
6558
- const spacingTokens = ix.tokens.byCategory("spacing");
6559
- const radiusTokens = ix.tokens.byCategory("radius");
6820
+ const spacingTokens = localTokenCandidates(ix, "spacing");
6821
+ const radiusTokens = localTokenCandidates(ix, "radius");
6560
6822
  const preferLabel = policy.prefer === "token" ? "design token" : "CSS variable";
6561
6823
  const findings = [];
6562
6824
  for (const decl of ix.byKind("style_declaration")) {
@@ -6570,8 +6832,8 @@ function ruleStylesNoRawDimensions(ix) {
6570
6832
  const fixEmission = dimensionFix(decl.property, resolution, ix);
6571
6833
  findings.push(
6572
6834
  makeFinding({
6573
- ruleId: RULE_ID9,
6574
- ruleVersion: RULE_VERSION10,
6835
+ ruleId: RULE_ID10,
6836
+ ruleVersion: RULE_VERSION11,
6575
6837
  severity: policy.severity,
6576
6838
  message: dimensionMessage(decl.value, decl.property, preferLabel, resolution, false),
6577
6839
  location: decl.location,
@@ -6612,8 +6874,8 @@ function ruleStylesNoRawDimensions(ix) {
6612
6874
  const fixEmission = dimensionFix(inline.property, resolution, ix);
6613
6875
  findings.push(
6614
6876
  makeFinding({
6615
- ruleId: RULE_ID9,
6616
- ruleVersion: RULE_VERSION10,
6877
+ ruleId: RULE_ID10,
6878
+ ruleVersion: RULE_VERSION11,
6617
6879
  severity: policy.severity,
6618
6880
  message: dimensionMessage(inline.value, inline.property, preferLabel, resolution, true),
6619
6881
  location: node.location,
@@ -6767,9 +7029,7 @@ function normalizeDimension(value) {
6767
7029
  return `${parsed.value}${parsed.unit ?? "px"}`;
6768
7030
  }
6769
7031
  function tokenVar(token) {
6770
- if (token.name.startsWith("$")) return token.name;
6771
- const cssVarName = token.name.startsWith("--") ? token.name : `--${token.name.replace(/\./g, "-")}`;
6772
- return `var(${cssVarName})`;
7032
+ return tokenReference(token);
6773
7033
  }
6774
7034
 
6775
7035
  // src/rules/spacing-resolution.ts
@@ -6807,7 +7067,7 @@ function resolveSpacingValue(input) {
6807
7067
  continue;
6808
7068
  }
6809
7069
  anyViolation = true;
6810
- const reference = tokenReference(exactToken.name);
7070
+ const reference = tokenReference(exactToken);
6811
7071
  if (!normalized.deterministicFix) allValuePreserving = false;
6812
7072
  suggestedParts.push(reference);
6813
7073
  if (!firstViolation) {
@@ -6839,7 +7099,7 @@ function resolveSpacingValue(input) {
6839
7099
  } else {
6840
7100
  const nearestToken = input.tokens.get(Math.abs(nearest));
6841
7101
  suggestedToken = nearestToken?.name;
6842
- suggestedPart = nearestToken ? tokenReference(nearestToken.name) : input.bareNumberFix ? `${nearest}` : `${nearest}${input.scale.unit}`;
7102
+ suggestedPart = nearestToken ? tokenReference(nearestToken) : input.bareNumberFix ? `${nearest}` : `${nearest}${input.scale.unit}`;
6843
7103
  suggestedParts.push(suggestedPart);
6844
7104
  if (!normalized.deterministicFix) reliableSuggestion = false;
6845
7105
  }
@@ -6883,15 +7143,15 @@ function parseLengthParts(raw) {
6883
7143
  }
6884
7144
 
6885
7145
  // src/rules/styles-no-raw-spacing.ts
6886
- var RULE_ID10 = "styles/no-raw-spacing";
6887
- var RULE_VERSION11 = "1";
7146
+ var RULE_ID11 = "styles/no-raw-spacing";
7147
+ var RULE_VERSION12 = "1";
6888
7148
  function ruleStylesNoRawSpacing(ix) {
6889
7149
  const findings = [];
6890
7150
  const lookupCache = /* @__PURE__ */ new Map();
6891
7151
  const lookupFor = (scale) => {
6892
7152
  let lookup = lookupCache.get(scale.name);
6893
7153
  if (!lookup) {
6894
- lookup = buildSpacingTokenLookup(ix.tokens.byCategory("spacing"), scale);
7154
+ lookup = buildSpacingTokenLookup(localTokenCandidates(ix, "spacing"), scale);
6895
7155
  lookupCache.set(scale.name, lookup);
6896
7156
  }
6897
7157
  return lookup;
@@ -6926,8 +7186,8 @@ function checkDeclaration(ix, decl, lookupFor) {
6926
7186
  if (!checked) return null;
6927
7187
  const fixEmission = buildSpacingFix(decl.property, checked, ix);
6928
7188
  return makeFinding({
6929
- ruleId: RULE_ID10,
6930
- ruleVersion: RULE_VERSION11,
7189
+ ruleId: RULE_ID11,
7190
+ ruleVersion: RULE_VERSION12,
6931
7191
  severity: policy.severity,
6932
7192
  message: spacingMessage(decl.property, decl.value, allowed, scale.unit, checked),
6933
7193
  location: decl.location,
@@ -6981,8 +7241,8 @@ function checkInlineStyle(ix, inline, componentByNode, lookupFor) {
6981
7241
  const componentEvidenceId = componentByNode.get(node.id)?.id;
6982
7242
  const evidenceIds = componentEvidenceId ? [node.id, componentEvidenceId, inline.id, policy.id, scale.id] : [node.id, inline.id, policy.id, scale.id];
6983
7243
  return makeFinding({
6984
- ruleId: RULE_ID10,
6985
- ruleVersion: RULE_VERSION11,
7244
+ ruleId: RULE_ID11,
7245
+ ruleVersion: RULE_VERSION12,
6986
7246
  severity: policy.severity,
6987
7247
  message: spacingMessage(inline.property, inline.value, allowed, scale.unit, checked),
6988
7248
  location: node.location,
@@ -7025,7 +7285,7 @@ function buildSpacingFix(property, checked, ix) {
7025
7285
  deterministic: true
7026
7286
  },
7027
7287
  {
7028
- symbols: tokenSymbolsInText(checked.suggestedValue),
7288
+ symbols: checked.matchedToken ? [checked.matchedToken] : tokenSymbolsInText(checked.suggestedValue),
7029
7289
  editKind: "replaceStyleValue",
7030
7290
  valuePreserving: checked.deterministicFix
7031
7291
  },
@@ -7043,8 +7303,8 @@ function spacingMessage(property, value, allowed, unit, checked) {
7043
7303
  }
7044
7304
 
7045
7305
  // src/rules/styles-no-raw-typography.ts
7046
- var RULE_ID11 = "styles/no-raw-typography";
7047
- var RULE_VERSION12 = "1";
7306
+ var RULE_ID12 = "styles/no-raw-typography";
7307
+ var RULE_VERSION13 = "1";
7048
7308
  var FONT_SIZE_PROPERTIES = /* @__PURE__ */ new Set(["font-size", "fontsize"]);
7049
7309
  function ruleStylesNoRawTypography(ix) {
7050
7310
  const policy = ix.policy.fontSizeScale();
@@ -7062,8 +7322,8 @@ function ruleStylesNoRawTypography(ix) {
7062
7322
  const fixEmission = buildFix(decl.property, checked, ix);
7063
7323
  findings.push(
7064
7324
  makeFinding({
7065
- ruleId: RULE_ID11,
7066
- ruleVersion: RULE_VERSION12,
7325
+ ruleId: RULE_ID12,
7326
+ ruleVersion: RULE_VERSION13,
7067
7327
  severity: policy.severity,
7068
7328
  message: typographyMessage(decl.property, decl.value, allowed, scale.unit, checked),
7069
7329
  location: decl.location,
@@ -7109,8 +7369,8 @@ function ruleStylesNoRawTypography(ix) {
7109
7369
  const evidenceIds = componentEvidenceId ? [node.id, componentEvidenceId, inline.id, policy.id, scale.id] : [node.id, inline.id, policy.id, scale.id];
7110
7370
  findings.push(
7111
7371
  makeFinding({
7112
- ruleId: RULE_ID11,
7113
- ruleVersion: RULE_VERSION12,
7372
+ ruleId: RULE_ID12,
7373
+ ruleVersion: RULE_VERSION13,
7114
7374
  severity: policy.severity,
7115
7375
  message: typographyMessage(inline.property, inline.value, allowed, scale.unit, checked),
7116
7376
  location: node.location,
@@ -7141,7 +7401,7 @@ function ruleStylesNoRawTypography(ix) {
7141
7401
  }
7142
7402
  function buildTypographyTokenLookup(ix, scale) {
7143
7403
  const out = /* @__PURE__ */ new Map();
7144
- for (const token of ix.tokens.byCategory("typography")) {
7404
+ for (const token of localTokenCandidates(ix, "typography")) {
7145
7405
  const parsed = parseLengthValue(token.value);
7146
7406
  if (!parsed || parsed.unit === null) continue;
7147
7407
  const normalized = normalizeLengthForScale(parsed, scale);
@@ -7163,7 +7423,7 @@ function checkFontSize(raw, allowed, scale, tokens, bareNumberFix = false) {
7163
7423
  if (matchesScale(normalized.value, allowed)) {
7164
7424
  if (!exactToken) return null;
7165
7425
  return {
7166
- suggestedValue: tokenReference(exactToken.name),
7426
+ suggestedValue: tokenReference(exactToken),
7167
7427
  fixEmittable: true,
7168
7428
  deterministicFix: normalized.deterministicFix,
7169
7429
  reason: "token-equivalent",
@@ -7175,7 +7435,7 @@ function checkFontSize(raw, allowed, scale, tokens, bareNumberFix = false) {
7175
7435
  return { fixEmittable: false, deterministicFix: false, reason: "off-scale" };
7176
7436
  }
7177
7437
  const nearestToken = tokens.get(Math.abs(nearest));
7178
- const suggestedValue = nearestToken ? tokenReference(nearestToken.name) : bareNumberFix ? `${nearest}` : `${nearest}${scale.unit}`;
7438
+ const suggestedValue = nearestToken ? tokenReference(nearestToken) : bareNumberFix ? `${nearest}` : `${nearest}${scale.unit}`;
7179
7439
  return {
7180
7440
  suggestedValue,
7181
7441
  fixEmittable: normalized.deterministicFix,
@@ -7194,7 +7454,7 @@ function buildFix(property, checked, ix) {
7194
7454
  deterministic: true
7195
7455
  },
7196
7456
  {
7197
- symbols: tokenSymbolsInText(checked.suggestedValue),
7457
+ symbols: checked.matchedToken ? [checked.matchedToken] : tokenSymbolsInText(checked.suggestedValue),
7198
7458
  editKind: "replaceStyleValue",
7199
7459
  valuePreserving: checked.deterministicFix
7200
7460
  },
@@ -7212,15 +7472,15 @@ function typographyMessage(property, value, allowed, unit, checked) {
7212
7472
  }
7213
7473
 
7214
7474
  // src/rules/theme-no-theme-coupled-literal.ts
7215
- var RULE_ID12 = "theme/no-theme-coupled-literal";
7216
- var RULE_VERSION13 = "1";
7475
+ var RULE_ID13 = "theme/no-theme-coupled-literal";
7476
+ var RULE_VERSION14 = "1";
7217
7477
  var COLOR_LITERAL_GLOBAL = /#[0-9a-fA-F]{3,8}\b|\b(?:rgba?|hsla?|oklab|oklch|lab|lch)\(\s*[\d.%\s,\/]+\s*\)/g;
7218
7478
  var WHITE_RGBA = /^rgba?\(\s*255\s*,\s*255\s*,\s*255/i;
7219
7479
  var BLACK_RGBA = /^rgba?\(\s*0\s*,\s*0\s*,\s*0/i;
7220
7480
  var BOX_SHADOW_PROP = /^(?:-webkit-|-moz-)?box-shadow$/i;
7221
7481
  var NEUTRAL_SHADOW = /^(?:none|inherit|initial|unset|revert|revert-layer)\s*$/i;
7222
7482
  function ruleThemeNoThemeCoupledLiteral(ix) {
7223
- const policy = ix.policy.ruleConfig(RULE_ID12);
7483
+ const policy = ix.policy.ruleConfig(RULE_ID13);
7224
7484
  if (!policy?.enabled) return [];
7225
7485
  const findings = [];
7226
7486
  for (const decl of ix.byKind("style_declaration")) {
@@ -7229,8 +7489,8 @@ function ruleThemeNoThemeCoupledLiteral(ix) {
7229
7489
  if (!hit) continue;
7230
7490
  findings.push(
7231
7491
  makeFinding({
7232
- ruleId: RULE_ID12,
7233
- ruleVersion: RULE_VERSION13,
7492
+ ruleId: RULE_ID13,
7493
+ ruleVersion: RULE_VERSION14,
7234
7494
  severity: policy.severity ?? "warn",
7235
7495
  message: hit.message,
7236
7496
  location: decl.location,
@@ -7320,8 +7580,8 @@ function isTokenFile(filePath) {
7320
7580
  }
7321
7581
 
7322
7582
  // src/rules/tailwind-arbitrary-color.ts
7323
- var RULE_ID13 = "tailwind/arbitrary-color";
7324
- var RULE_VERSION14 = "1";
7583
+ var RULE_ID14 = "tailwind/arbitrary-color";
7584
+ var RULE_VERSION15 = "1";
7325
7585
  var COLOR_UTILITIES = /* @__PURE__ */ new Set([
7326
7586
  "accent",
7327
7587
  "bg",
@@ -7363,8 +7623,8 @@ function ruleTailwindArbitraryColor(ix) {
7363
7623
  const evidenceIds = node ? [node.id, klass.id, policy.id] : [klass.id, policy.id];
7364
7624
  findings.push(
7365
7625
  makeFinding({
7366
- ruleId: RULE_ID13,
7367
- ruleVersion: RULE_VERSION14,
7626
+ ruleId: RULE_ID14,
7627
+ ruleVersion: RULE_VERSION15,
7368
7628
  severity: policy.severity,
7369
7629
  message: `\`${klass.raw}\` uses arbitrary raw color ${color}. Use a ${policy.prefer === "token" ? "design token" : "CSS variable"} instead.`,
7370
7630
  location: klass.location,
@@ -7395,8 +7655,8 @@ function fingerprintIdentity(klass, color) {
7395
7655
  }
7396
7656
 
7397
7657
  // src/rules/tailwind-arbitrary-spacing.ts
7398
- var RULE_ID14 = "tailwind/arbitrary-spacing";
7399
- var RULE_VERSION15 = "1";
7658
+ var RULE_ID15 = "tailwind/arbitrary-spacing";
7659
+ var RULE_VERSION16 = "1";
7400
7660
  var SPACING_UTILITY_TO_PROPERTY = /* @__PURE__ */ new Map([
7401
7661
  ["m", "margin"],
7402
7662
  ["mt", "margin"],
@@ -7442,8 +7702,8 @@ function ruleTailwindArbitrarySpacing(ix) {
7442
7702
  const fix = buildFix2(klass);
7443
7703
  findings.push(
7444
7704
  makeFinding({
7445
- ruleId: RULE_ID14,
7446
- ruleVersion: RULE_VERSION15,
7705
+ ruleId: RULE_ID15,
7706
+ ruleVersion: RULE_VERSION16,
7447
7707
  severity: policy.severity,
7448
7708
  message: `\`${klass.raw}\` is an arbitrary spacing value not on the project's spacing scale.`,
7449
7709
  location: klass.location,
@@ -7574,8 +7834,8 @@ function matchesTokenGlob(value, pattern) {
7574
7834
  }
7575
7835
 
7576
7836
  // src/rules/tailwind-forbidden-palette.ts
7577
- var RULE_ID15 = "tailwind/forbidden-palette";
7578
- var RULE_VERSION16 = "1";
7837
+ var RULE_ID16 = "tailwind/forbidden-palette";
7838
+ var RULE_VERSION17 = "1";
7579
7839
  function ruleTailwindForbiddenPalette(ix) {
7580
7840
  const deny = ix.policy.tailwindPaletteDeny();
7581
7841
  const allow = ix.policy.tailwindPaletteAllow();
@@ -7600,8 +7860,8 @@ function ruleTailwindForbiddenPalette(ix) {
7600
7860
  const evidenceIds = node ? [node.id, klass.id, resolved.id, policy.id] : [klass.id, resolved.id, policy.id];
7601
7861
  findings.push(
7602
7862
  makeFinding({
7603
- ruleId: RULE_ID15,
7604
- ruleVersion: RULE_VERSION16,
7863
+ ruleId: RULE_ID16,
7864
+ ruleVersion: RULE_VERSION17,
7605
7865
  severity: policy.severity,
7606
7866
  message: messageFor(klass, token, deniedPattern, allow?.patterns ?? []),
7607
7867
  location: klass.location,
@@ -7648,8 +7908,8 @@ function buildFix3(klass) {
7648
7908
  }
7649
7909
 
7650
7910
  // src/rules/tailwind-off-scale-spacing-token.ts
7651
- var RULE_ID16 = "tailwind/off-scale-spacing-token";
7652
- var RULE_VERSION17 = "1";
7911
+ var RULE_ID17 = "tailwind/off-scale-spacing-token";
7912
+ var RULE_VERSION18 = "1";
7653
7913
  function ruleTailwindOffScaleSpacingToken(ix) {
7654
7914
  const resolvedByKey = indexResolvedTailwindTokens(ix);
7655
7915
  const findings = [];
@@ -7675,8 +7935,8 @@ function ruleTailwindOffScaleSpacingToken(ix) {
7675
7935
  const evidenceIds = node ? [node.id, klass.id, resolved.id, policy.id, scale.id] : [klass.id, resolved.id, policy.id, scale.id];
7676
7936
  findings.push(
7677
7937
  makeFinding({
7678
- ruleId: RULE_ID16,
7679
- ruleVersion: RULE_VERSION17,
7938
+ ruleId: RULE_ID17,
7939
+ ruleVersion: RULE_VERSION18,
7680
7940
  severity: policy.severity,
7681
7941
  message: `\`${klass.raw}\` resolves to ${formatResolvedValue(signedValue, scale.unit)}, which is not on the project's spacing scale [${allowed.join(", ")}].`,
7682
7942
  location: klass.location,
@@ -7725,8 +7985,8 @@ function buildFix4(raw, utility) {
7725
7985
  }
7726
7986
 
7727
7987
  // src/rules/tailwind-raw-color-via-token.ts
7728
- var RULE_ID17 = "tailwind/raw-color-via-token";
7729
- var RULE_VERSION18 = "1";
7988
+ var RULE_ID18 = "tailwind/raw-color-via-token";
7989
+ var RULE_VERSION19 = "1";
7730
7990
  function ruleTailwindRawColorViaToken(ix) {
7731
7991
  const allow = ix.policy.tailwindPaletteAllow();
7732
7992
  if (!allow || allow.patterns.length === 0) return [];
@@ -7746,8 +8006,8 @@ function ruleTailwindRawColorViaToken(ix) {
7746
8006
  const evidenceIds = node ? [node.id, klass.id, resolved.id, allow.id] : [klass.id, resolved.id, allow.id];
7747
8007
  findings.push(
7748
8008
  makeFinding({
7749
- ruleId: RULE_ID17,
7750
- ruleVersion: RULE_VERSION18,
8009
+ ruleId: RULE_ID18,
8010
+ ruleVersion: RULE_VERSION19,
7751
8011
  severity: allow.severity,
7752
8012
  message: `\`${klass.raw}\` resolves to raw color \`${color}\` (${sourceLabel2(resolved.resolved.source)}). Project policy requires \`${allow.patterns.join("`, `")}\` palette tokens.`,
7753
8013
  location: klass.location,
@@ -7780,8 +8040,8 @@ function sourceLabel2(source) {
7780
8040
  }
7781
8041
 
7782
8042
  // src/rules/tailwind-unknown-class.ts
7783
- var RULE_ID18 = "tailwind/unknown-class";
7784
- var RULE_VERSION19 = "1";
8043
+ var RULE_ID19 = "tailwind/unknown-class";
8044
+ var RULE_VERSION20 = "1";
7785
8045
  var UNIVERSAL_COLOR_KEYWORDS = /* @__PURE__ */ new Set([
7786
8046
  "white",
7787
8047
  "black",
@@ -7802,8 +8062,8 @@ function ruleTailwindUnknownClass(ix) {
7802
8062
  const evidenceIds2 = node2 ? [node2.id, klass.id, policy.id] : [klass.id, policy.id];
7803
8063
  findings.push(
7804
8064
  makeFinding({
7805
- ruleId: RULE_ID18,
7806
- ruleVersion: RULE_VERSION19,
8065
+ ruleId: RULE_ID19,
8066
+ ruleVersion: RULE_VERSION20,
7807
8067
  severity: policy.severity,
7808
8068
  message: `\`${klass.raw}\` could not be parsed as a known Tailwind utility.`,
7809
8069
  location: klass.location,
@@ -7837,8 +8097,8 @@ function ruleTailwindUnknownClass(ix) {
7837
8097
  const evidenceIds = node ? [node.id, klass.id, resolved.id, policy.id] : [klass.id, resolved.id, policy.id];
7838
8098
  findings.push(
7839
8099
  makeFinding({
7840
- ruleId: RULE_ID18,
7841
- ruleVersion: RULE_VERSION19,
8100
+ ruleId: RULE_ID19,
8101
+ ruleVersion: RULE_VERSION20,
7842
8102
  severity: policy.severity,
7843
8103
  message: `\`${klass.raw}\` did not resolve to any known palette token.`,
7844
8104
  location: klass.location,
@@ -7866,14 +8126,14 @@ function ruleTailwindUnknownClass(ix) {
7866
8126
  }
7867
8127
 
7868
8128
  // src/rules/tokens-require-dual-fallback.ts
7869
- var RULE_ID19 = "tokens/require-dual-fallback";
7870
- var RULE_VERSION20 = "1";
8129
+ var RULE_ID20 = "tokens/require-dual-fallback";
8130
+ var RULE_VERSION21 = "1";
7871
8131
  var CSS_VAR_WITH_OPTIONAL_FALLBACK = /var\(\s*(--[A-Za-z0-9_-]+)(\s*,\s*[^)]*)?\)/g;
7872
8132
  var SASS_FILE = /\.(scss|sass)$/i;
7873
8133
  function ruleTokensRequireDualFallback(ix) {
7874
- const policy = ix.policy.ruleConfig(RULE_ID19);
8134
+ const policy = ix.policy.ruleConfig(RULE_ID20);
7875
8135
  if (!policy?.enabled) return [];
7876
- const tokensByCssVariable = indexTokensByCssVariable(ix.tokens.list());
8136
+ const tokensByCssVariable = indexTokensByCssVariable(localTokenCandidates(ix));
7877
8137
  const findings = [];
7878
8138
  for (const decl of ix.byKind("style_declaration")) {
7879
8139
  if (!SASS_FILE.test(decl.file)) continue;
@@ -7906,8 +8166,8 @@ function ruleTokensRequireDualFallback(ix) {
7906
8166
  ) : void 0;
7907
8167
  findings.push(
7908
8168
  makeFinding({
7909
- ruleId: RULE_ID19,
7910
- ruleVersion: RULE_VERSION20,
8169
+ ruleId: RULE_ID20,
8170
+ ruleVersion: RULE_VERSION21,
7911
8171
  severity: policy.severity ?? "warn",
7912
8172
  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
8173
  location: decl.location,
@@ -7947,8 +8207,8 @@ function indexTokensByCssVariable(tokens) {
7947
8207
  }
7948
8208
 
7949
8209
  // src/rules/tokens-css-vars-must-be-defined.ts
7950
- var RULE_ID20 = "tokens/css-vars-must-be-defined";
7951
- var RULE_VERSION21 = "1";
8210
+ var RULE_ID21 = "tokens/css-vars-must-be-defined";
8211
+ var RULE_VERSION22 = "1";
7952
8212
  var CSS_VAR_REFERENCE = /var\(\s*(--[A-Za-z0-9_-]+)/gi;
7953
8213
  function ruleTokensCssVarsMustBeDefined(ix) {
7954
8214
  const policy = ix.policy.cssVarsMustBeDefined();
@@ -7975,8 +8235,8 @@ function ruleTokensCssVarsMustBeDefined(ix) {
7975
8235
  seen.add(tokenName);
7976
8236
  findings.push(
7977
8237
  makeFinding({
7978
- ruleId: RULE_ID20,
7979
- ruleVersion: RULE_VERSION21,
8238
+ ruleId: RULE_ID21,
8239
+ ruleVersion: RULE_VERSION22,
7980
8240
  severity: policy.severity,
7981
8241
  message: `var(${tokenName}) is not in the contract token vocabulary. Use a token defined in your contract's token source files.`,
7982
8242
  location: decl.location,
@@ -8022,6 +8282,57 @@ function skipToCloseParen(value, openParenIndex) {
8022
8282
  return value.length;
8023
8283
  }
8024
8284
 
8285
+ // src/rules/tokens-upstream-drift.ts
8286
+ var RULE_ID22 = "tokens/upstream-drift";
8287
+ var RULE_VERSION23 = "1";
8288
+ function ruleTokensUpstreamDrift(ix) {
8289
+ const local = ix.tokens.list().filter((token) => token.role !== "upstream");
8290
+ const upstream = ix.tokens.list().filter((token) => token.role === "upstream");
8291
+ const upstreamByName = /* @__PURE__ */ new Map();
8292
+ for (const token of upstream) {
8293
+ const matches = upstreamByName.get(token.name) ?? [];
8294
+ matches.push(token);
8295
+ upstreamByName.set(token.name, matches);
8296
+ }
8297
+ const findings = [];
8298
+ for (const token of local) {
8299
+ const upstreamName = token.upstreamName ?? token.name;
8300
+ const matches = (upstreamByName.get(upstreamName) ?? []).sort(
8301
+ (a, b) => (a.sourceIdentity ?? "").localeCompare(b.sourceIdentity ?? "")
8302
+ );
8303
+ if (matches.length !== 1) continue;
8304
+ const expected = matches[0];
8305
+ if (normalizedTokenValue(token) === normalizedTokenValue(expected)) continue;
8306
+ const location = token.location ?? {
8307
+ file: token.sourceIdentity ?? "tokens",
8308
+ line: 1,
8309
+ column: 1
8310
+ };
8311
+ findings.push(
8312
+ makeFinding({
8313
+ ruleId: RULE_ID22,
8314
+ ruleVersion: RULE_VERSION23,
8315
+ severity: "warn",
8316
+ message: `Local token ${token.name} is ${token.value}, but upstream ${upstreamName} is ${expected.value}.`,
8317
+ location,
8318
+ evidence: ix.evidence([token.id, expected.id]),
8319
+ fingerprintIdentity: {
8320
+ local: token.name,
8321
+ upstream: upstreamName,
8322
+ localSource: token.sourceIdentity,
8323
+ upstreamSource: expected.sourceIdentity
8324
+ },
8325
+ attributes: {
8326
+ local: { name: token.name, value: token.value, location: token.location },
8327
+ upstream: { name: expected.name, value: expected.value, location: expected.location },
8328
+ autoFix: false
8329
+ }
8330
+ })
8331
+ );
8332
+ }
8333
+ return findings;
8334
+ }
8335
+
8025
8336
  // src/rules/emit-gate.ts
8026
8337
  var BLOCKING_RULE_ALLOWLIST = /* @__PURE__ */ new Set([
8027
8338
  "styles/no-raw-color",
@@ -8056,10 +8367,14 @@ function isDenyEligible(finding, failOnWarnings) {
8056
8367
  // src/rules/tiers.ts
8057
8368
  var RULE_TIER = {
8058
8369
  // ---- Tier A — contract vocabulary -------------------------------------
8370
+ "components/shadow-component": "contract",
8371
+ // canonical-component identity drift
8059
8372
  "components/prefer-library": "contract",
8060
8373
  // canonical-component bypass (FUI1004)
8061
8374
  "tokens/css-vars-must-be-defined": "contract",
8062
8375
  // token drift (FUI2015)
8376
+ "tokens/upstream-drift": "contract",
8377
+ // authored local/upstream value drift (FUI2017)
8063
8378
  // ---- Tier B — generic hygiene -----------------------------------------
8064
8379
  "components/unknown-prop": "hygiene",
8065
8380
  "components/forbidden-prop-value": "hygiene",
@@ -8101,8 +8416,10 @@ function hygieneTierRuleIds() {
8101
8416
  return Object.keys(RULE_TIER).filter((id) => !isContractTierRule(id));
8102
8417
  }
8103
8418
  var VOCABULARY_ORDER = [
8419
+ "components/shadow-component",
8104
8420
  "components/prefer-library",
8105
- "tokens/css-vars-must-be-defined"
8421
+ "tokens/css-vars-must-be-defined",
8422
+ "tokens/upstream-drift"
8106
8423
  ];
8107
8424
  function vocabularyRank(ruleId) {
8108
8425
  const index = VOCABULARY_ORDER.indexOf(ruleId);
@@ -8161,6 +8478,11 @@ var RULES = [
8161
8478
  version: "1",
8162
8479
  run: ruleComponentsPreferLibrary
8163
8480
  },
8481
+ {
8482
+ id: "components/shadow-component",
8483
+ version: "1",
8484
+ run: ruleComponentsShadowComponent
8485
+ },
8164
8486
  {
8165
8487
  id: "styles/no-raw-color",
8166
8488
  version: "1",
@@ -8221,6 +8543,11 @@ var RULES = [
8221
8543
  version: "1",
8222
8544
  run: ruleTokensCssVarsMustBeDefined
8223
8545
  },
8546
+ {
8547
+ id: "tokens/upstream-drift",
8548
+ version: "1",
8549
+ run: ruleTokensUpstreamDrift
8550
+ },
8224
8551
  {
8225
8552
  id: "theme/no-theme-coupled-literal",
8226
8553
  version: "1",
@@ -8310,19 +8637,23 @@ function evaluateGovernanceIntegrity(input) {
8310
8637
  policyFamily.reason = "no governance policy resolved";
8311
8638
  }
8312
8639
  const componentsConfig = configs.get("components/prefer-library");
8313
- const componentsArmed = componentsConfig?.enabled === true && hasEffectiveComponentVocabulary(componentsConfig.options);
8640
+ const confirmedBridges = input.policy?.canonicalBridges ?? [];
8641
+ const componentsArmed = confirmedBridges.length > 0 || componentsConfig?.enabled === true && hasEffectiveComponentVocabulary(componentsConfig.options);
8314
8642
  const componentsFamily = {
8315
8643
  id: "components",
8316
8644
  armed: componentsArmed,
8317
- rules: ["components/prefer-library"]
8645
+ rules: [
8646
+ ...confirmedBridges.length > 0 ? ["imports/preferred-path"] : [],
8647
+ ...componentsConfig?.enabled === true ? ["components/prefer-library"] : []
8648
+ ]
8318
8649
  };
8319
8650
  if (!componentsArmed) {
8320
8651
  if (componentsConfig?.enabled === true) {
8321
8652
  componentsFamily.reason = "components/prefer-library enabled but no effective canonical source";
8322
- componentsFamily.remediation = "add govern.canonicalSources (a directory source, or npm/registry with a non-empty include) or designSystem.path/packageName";
8653
+ componentsFamily.remediation = "add govern.canonicalBridges for local wrappers, govern.canonicalSources, or designSystem.path/packageName";
8323
8654
  } else {
8324
8655
  componentsFamily.reason = "components/prefer-library not enabled";
8325
- componentsFamily.remediation = "add govern.canonicalSources (a directory source, or npm/registry with a non-empty include) or designSystem.path/packageName";
8656
+ componentsFamily.remediation = "add govern.canonicalBridges for local wrappers, govern.canonicalSources, or designSystem.path/packageName";
8326
8657
  }
8327
8658
  }
8328
8659
  const cssVarsActive = input.cssVarsActive ?? policyDeclaresCssVars(input.policy);
@@ -8408,6 +8739,74 @@ function evaluateGovernanceIntegrity(input) {
8408
8739
  };
8409
8740
  }
8410
8741
 
8742
+ // src/canonical-bridge.ts
8743
+ function canonicalBridgeContractMappings(bridges) {
8744
+ return (bridges ?? []).map((bridge) => ({
8745
+ component: `${bridge.underlying.packageName}#${bridge.underlying.exportName}`,
8746
+ canonical: bridge.local.componentKey,
8747
+ status: "confirmed",
8748
+ importPath: bridge.local.moduleSpecifier,
8749
+ bridge: {
8750
+ underlyingPackageName: bridge.underlying.packageName,
8751
+ underlyingExportName: bridge.underlying.exportName,
8752
+ localComponentKey: bridge.local.componentKey,
8753
+ localExportName: bridge.local.exportName,
8754
+ implementationFiles: [...bridge.local.implementationFiles],
8755
+ decisionSource: bridge.decision.source
8756
+ }
8757
+ }));
8758
+ }
8759
+
8760
+ // src/effective-governance-inputs.ts
8761
+ var EFFECTIVE_GOVERNANCE_INPUTS_SCHEMA = "effective-governance-inputs:v1";
8762
+ function compileEffectiveGovernanceInputs(options) {
8763
+ const projection = {
8764
+ schema: EFFECTIVE_GOVERNANCE_INPUTS_SCHEMA,
8765
+ policySource: options.policySource,
8766
+ config: {
8767
+ path: options.configPath ? normalizePath2(options.configPath) : null,
8768
+ digest: options.configDigest
8769
+ },
8770
+ components: {
8771
+ canonicalSources: sortRecords(options.policy?.canonicalSources ?? []),
8772
+ canonicalBridges: sortRecords(options.policy?.canonicalBridges ?? []),
8773
+ definitionFiles: normalizePaths(options.componentDefinitionFiles)
8774
+ },
8775
+ tokens: {
8776
+ declared: normalizePaths(options.tokens.declared),
8777
+ loadedDefinitions: options.tokens.loadedDefinitions,
8778
+ diagnostics: [...options.tokens.diagnostics].sort(),
8779
+ ...options.tokens.catalog ? {
8780
+ catalog: {
8781
+ ...options.tokens.catalog,
8782
+ configuredSources: normalizePaths(options.tokens.catalog.configuredSources),
8783
+ parsedSources: normalizePaths(options.tokens.catalog.parsedSources),
8784
+ definitions: sortRecords(options.tokens.catalog.definitions),
8785
+ diagnostics: sortRecords(options.tokens.catalog.diagnostics),
8786
+ references: sortRecords(options.tokens.catalog.references),
8787
+ ...options.tokens.catalog.sourceStates ? { sourceStates: sortRecords(options.tokens.catalog.sourceStates) } : {}
8788
+ }
8789
+ } : {}
8790
+ },
8791
+ eligibility: {
8792
+ include: normalizePaths(options.eligibility.include),
8793
+ exclude: normalizePaths(options.eligibility.exclude)
8794
+ }
8795
+ };
8796
+ return { ...projection, inputsHash: contractHash(projection) };
8797
+ }
8798
+ function normalizePaths(values) {
8799
+ return [...new Set(values.map(normalizePath2))].sort();
8800
+ }
8801
+ function normalizePath2(value) {
8802
+ return value.replace(/\\/gu, "/").replace(/^\.\//u, "");
8803
+ }
8804
+ function sortRecords(values) {
8805
+ return [...values].sort(
8806
+ (left, right) => canonicalPreimage(left).localeCompare(canonicalPreimage(right))
8807
+ );
8808
+ }
8809
+
8411
8810
  // src/contract/preimage.ts
8412
8811
  var CONTRACT_DOMAINS = ["components", "tokens", "canonicalMap", "policy"];
8413
8812
  var CONTRACT_PREIMAGE_SCHEMA = "fcid-preimage:v1";
@@ -8463,6 +8862,15 @@ function projectCanonicalMap(mappings) {
8463
8862
  component: mapping.component,
8464
8863
  canonical: mapping.canonical,
8465
8864
  importPath: mapping.importPath ? projectV1OwnedImportIdentity(mapping.importPath) : void 0,
8865
+ bridge: mapping.bridge ? {
8866
+ underlyingPackageName: projectV1OwnedImportIdentity(
8867
+ mapping.bridge.underlyingPackageName
8868
+ ),
8869
+ underlyingExportName: mapping.bridge.underlyingExportName,
8870
+ localComponentKey: projectV1OwnedComponentId(mapping.bridge.localComponentKey),
8871
+ localExportName: mapping.bridge.localExportName,
8872
+ implementationFiles: [...mapping.bridge.implementationFiles].sort()
8873
+ } : void 0,
8466
8874
  resolves: mapping.resolves ? sortCanonical(mapping.resolves) : void 0,
8467
8875
  propMapping: mapping.propMapping ? sortCanonical(
8468
8876
  mapping.propMapping.map((entry) => ({
@@ -8755,6 +9163,133 @@ function readPreferLibraryRule(config) {
8755
9163
  const raw = config?.rules?.["components/prefer-library"];
8756
9164
  return raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
8757
9165
  }
9166
+
9167
+ // src/identity/component-key.ts
9168
+ function buildComponentKey({ repoRelativeFile, exportName }) {
9169
+ const file = normalizeRepoRelativeFile(repoRelativeFile);
9170
+ const name = exportName.trim();
9171
+ if (!file) throw new Error("repoRelativeFile must not be empty");
9172
+ if (!name) throw new Error("exportName must not be empty");
9173
+ return `${file}#${name}`;
9174
+ }
9175
+ function normalizeRepoRelativeFile(file) {
9176
+ const slashNormalized = file.trim().replace(/\\/g, "/").replace(/^\.\/+/, "");
9177
+ const absolute = slashNormalized.startsWith("/");
9178
+ const parts = [];
9179
+ for (const part of slashNormalized.split("/")) {
9180
+ if (!part || part === ".") continue;
9181
+ if (part === ".." && parts.length > 0 && parts.at(-1) !== "..") {
9182
+ parts.pop();
9183
+ continue;
9184
+ }
9185
+ parts.push(part);
9186
+ }
9187
+ return `${absolute ? "/" : ""}${parts.join("/")}`;
9188
+ }
9189
+
9190
+ // src/identity/classify.ts
9191
+ function classifyIdentity(input) {
9192
+ const { definition, renderRoot } = input;
9193
+ if (definition.canonical) return { state: "canonical", confidence: "confirmed" };
9194
+ const decision = input.decisions?.find(
9195
+ (candidate) => candidate.componentKey === definition.componentKey
9196
+ );
9197
+ if (decision?.decision === "sanctioned-wrapper") {
9198
+ return withTarget("variant", "confirmed", decision.canonicalTarget ?? rootTarget(input));
9199
+ }
9200
+ if (decision?.decision === "rejected-wrapper") {
9201
+ return withTarget("shadow", "confirmed", decision.canonicalTarget ?? shadowTarget(input));
9202
+ }
9203
+ if (renderRoot.kind === "canonical") {
9204
+ if ((input.usageCounts?.nestedElements ?? 0) > 0 || (input.usageCounts?.staticTextChildren ?? 0) > 0) {
9205
+ return withTarget("composite", "confirmed", renderRoot.target.componentKey);
9206
+ }
9207
+ const confidence = strongOverlap(input.propSurfaceOverlap) ? "likely" : "review";
9208
+ return withTarget("variant", confidence, renderRoot.target.componentKey);
9209
+ }
9210
+ if (renderRoot.kind === "intrinsic") {
9211
+ return classifyIntrinsic(input, renderRoot);
9212
+ }
9213
+ if (renderRoot.kind === "mixed") {
9214
+ if (renderRoot.canonicalTargets.length > 0) {
9215
+ const target = bestCanonicalReference(
9216
+ definition.exportName,
9217
+ renderRoot.canonicalTargets
9218
+ )?.componentKey;
9219
+ const fullyResolved = renderRoot.externalTargets.length === 0 && renderRoot.unresolvedNames.length === 0;
9220
+ return withTarget("composite", fullyResolved ? "confirmed" : "likely", target);
9221
+ }
9222
+ if (renderRoot.externalTargets.length > 0 && renderRoot.localTargets.length === 0 && renderRoot.unresolvedNames.length === 0) {
9223
+ return { state: "external", confidence: "likely" };
9224
+ }
9225
+ return withTarget("unresolved", "review", rootTarget(input));
9226
+ }
9227
+ if (renderRoot.kind === "external") {
9228
+ return { state: "external", confidence: "confirmed" };
9229
+ }
9230
+ return withTarget("unresolved", "review", rootTarget(input));
9231
+ }
9232
+ function classifyIntrinsic(input, root) {
9233
+ const target = bestCanonicalReference(input.definition.exportName, root.canonicalTargets);
9234
+ if (!target || !root.interactive && !root.hasRole) {
9235
+ return withTarget("unresolved", "review", rootTarget(input));
9236
+ }
9237
+ let confidence = root.precisionTier === "exact-html" || root.precisionTier === "role-reimpl" ? "confirmed" : "likely";
9238
+ if (confidence === "likely" && input.stylingOwnership !== "layout-only" && (strongOverlap(input.propSurfaceOverlap) || input.stylingOwnership === "visual-role")) {
9239
+ confidence = "confirmed";
9240
+ }
9241
+ return { state: "shadow", confidence, canonicalTarget: target.role };
9242
+ }
9243
+ function rootTarget(input) {
9244
+ const { renderRoot } = input;
9245
+ if (renderRoot.kind === "canonical") return renderRoot.target.componentKey;
9246
+ if (renderRoot.kind === "intrinsic") {
9247
+ return bestCanonicalReference(input.definition.exportName, renderRoot.canonicalTargets)?.role;
9248
+ }
9249
+ if (renderRoot.kind === "mixed") {
9250
+ return bestCanonicalReference(input.definition.exportName, renderRoot.canonicalTargets)?.componentKey;
9251
+ }
9252
+ return bestMessageTarget(input.definition.exportName, input.nameSimilarityTargets ?? []);
9253
+ }
9254
+ function shadowTarget(input) {
9255
+ const { renderRoot } = input;
9256
+ if (renderRoot.kind === "canonical") return renderRoot.target.role;
9257
+ if (renderRoot.kind === "intrinsic") {
9258
+ return bestCanonicalReference(input.definition.exportName, renderRoot.canonicalTargets)?.role;
9259
+ }
9260
+ if (renderRoot.kind === "mixed") {
9261
+ return bestCanonicalReference(input.definition.exportName, renderRoot.canonicalTargets)?.role;
9262
+ }
9263
+ return bestMessageTarget(input.definition.exportName, input.nameSimilarityTargets ?? []);
9264
+ }
9265
+ function bestCanonicalReference(definitionName, candidates) {
9266
+ return [...candidates].sort(
9267
+ (left, right) => nameSimilarityScore(definitionName, right.role) - nameSimilarityScore(definitionName, left.role) || left.componentKey.localeCompare(right.componentKey)
9268
+ )[0];
9269
+ }
9270
+ function bestMessageTarget(definitionName, candidates) {
9271
+ return [...candidates].sort(
9272
+ (left, right) => nameSimilarityScore(definitionName, right) - nameSimilarityScore(definitionName, left) || left.localeCompare(right)
9273
+ )[0];
9274
+ }
9275
+ function nameSimilarityScore(definitionName, target) {
9276
+ const definition = normalizeName(definitionName);
9277
+ const candidate = normalizeName(target);
9278
+ if (!candidate) return 0;
9279
+ if (definition === candidate) return 3;
9280
+ if (definition.endsWith(candidate) || definition.startsWith(candidate)) return 2;
9281
+ if (definition.includes(candidate) || candidate.includes(definition)) return 1;
9282
+ return 0;
9283
+ }
9284
+ function normalizeName(value) {
9285
+ return value.split(/[.#/]/).at(-1)?.replace(/[^a-z0-9]/gi, "").toLowerCase() ?? "";
9286
+ }
9287
+ function strongOverlap(overlap) {
9288
+ return overlap !== void 0 && overlap >= 0.7;
9289
+ }
9290
+ function withTarget(state, confidence, canonicalTarget) {
9291
+ return canonicalTarget ? { state, confidence, canonicalTarget } : { state, confidence };
9292
+ }
8758
9293
  export {
8759
9294
  AGENT_CONTEXT_RELATIVE_PATH,
8760
9295
  AGENT_FORMAT_SCHEMA_VERSION,
@@ -8768,6 +9303,7 @@ export {
8768
9303
  DEFAULTS,
8769
9304
  DEFAULT_ENHANCED_STYLE_PROPERTIES,
8770
9305
  DEFAULT_STYLE_PROPERTIES,
9306
+ EFFECTIVE_GOVERNANCE_INPUTS_SCHEMA,
8771
9307
  EVIDENCE_ORDER,
8772
9308
  EXPLAIN_URL_BASE,
8773
9309
  FRAGMENTS_INTERNAL_RULE_IDS,
@@ -8805,6 +9341,7 @@ export {
8805
9341
  bridgeSourceViolations,
8806
9342
  budgetBar,
8807
9343
  buildAgentFormat,
9344
+ buildComponentKey,
8808
9345
  buildRegistryArtifact,
8809
9346
  buildRegistryFile,
8810
9347
  buildRegistryInstallPlan,
@@ -8821,6 +9358,9 @@ export {
8821
9358
  byRuleId,
8822
9359
  calculateDeltaE,
8823
9360
  canBlock,
9361
+ canonicalBridgeContractMappings,
9362
+ canonicalBridgeV1Schema,
9363
+ canonicalDirectionConflictsTarget,
8824
9364
  canonicalJson,
8825
9365
  canonicalPreimage,
8826
9366
  canonicalizeOwnedComponentId,
@@ -8831,6 +9371,7 @@ export {
8831
9371
  checkStoryExclusion,
8832
9372
  clampMaxPasses,
8833
9373
  classifyComplexity,
9374
+ classifyIdentity,
8834
9375
  collectRegistryInstallDependencies,
8835
9376
  colorSimilarity,
8836
9377
  compareByVocabularyRank,
@@ -8842,6 +9383,7 @@ export {
8842
9383
  compareStylesWithTokens,
8843
9384
  compileBlock,
8844
9385
  compileComponentFacts,
9386
+ compileEffectiveGovernanceInputs,
8845
9387
  compileFragment,
8846
9388
  compileGlobalGovernanceFacts,
8847
9389
  compileRecipe,
@@ -8926,6 +9468,7 @@ export {
8926
9468
  hashRegistryFileContent,
8927
9469
  hexToRgb,
8928
9470
  hygieneTierRuleIds,
9471
+ indexComponentIdentityUsage,
8929
9472
  inferTokenCategory,
8930
9473
  inferTokenGroup,
8931
9474
  isColorLike,
@@ -8952,6 +9495,8 @@ export {
8952
9495
  makeClassNameDynamicFact,
8953
9496
  makeClassNameLiteralFact,
8954
9497
  makeComponentCapabilityFact,
9498
+ makeComponentDefinitionFact,
9499
+ makeComponentIdentityFact,
8955
9500
  makeComponentMetadataFact,
8956
9501
  makeContractTokenFact,
8957
9502
  makeFinding,
@@ -9034,6 +9579,7 @@ export {
9034
9579
  parseTokenFile,
9035
9580
  parseTokens,
9036
9581
  portableRepoPathError,
9582
+ projectCanonicalDirectionConflicts,
9037
9583
  projectContractPreimage,
9038
9584
  projectV1OwnedComponentId,
9039
9585
  projectV1OwnedImportIdentity,
@@ -9076,6 +9622,7 @@ export {
9076
9622
  ruleA11yRequiredAccessibleName,
9077
9623
  ruleComponentsForbiddenPropValue,
9078
9624
  ruleComponentsPreferLibrary,
9625
+ ruleComponentsShadowComponent,
9079
9626
  ruleComponentsUnknownProp,
9080
9627
  ruleJsxPreferredComponent,
9081
9628
  ruleJsxPreferredImportPath,