@usefragments/core 1.5.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 (61) hide show
  1. package/dist/{chunk-HXGE3O2F.js → chunk-AOG4FTV6.js} +108 -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-BLsyk56o.d.ts → governance-B88uR3Zq.d.ts} +218 -1
  10. package/dist/{index-h_yWj15D.d.ts → index-DbkPE46t.d.ts} +40 -0
  11. package/dist/index.d.ts +429 -28
  12. package/dist/index.js +326 -33
  13. package/dist/index.js.map +1 -1
  14. package/dist/react-types.d.ts +1 -1
  15. package/dist/schemas/index.d.ts +1 -1
  16. package/dist/schemas/index.js +1 -1
  17. package/dist/test-utils.d.ts +1 -1
  18. package/package.json +1 -1
  19. package/src/agent-format.test.ts +1 -0
  20. package/src/canonical-bridge.ts +46 -0
  21. package/src/canonical-direction.ts +118 -0
  22. package/src/codes/codes.ts +9 -0
  23. package/src/contract/preimage.test.ts +40 -0
  24. package/src/contract/preimage.ts +20 -0
  25. package/src/effective-governance-inputs.test.ts +52 -0
  26. package/src/effective-governance-inputs.ts +106 -0
  27. package/src/facts/builders.ts +25 -1
  28. package/src/facts/compile.ts +20 -2
  29. package/src/facts/fact-index.ts +4 -0
  30. package/src/facts/types.ts +25 -6
  31. package/src/governance-integrity.test.ts +22 -0
  32. package/src/governance-integrity.ts +10 -4
  33. package/src/governance.test.ts +67 -0
  34. package/src/governance.ts +76 -0
  35. package/src/index.ts +27 -4
  36. package/src/rules/__tests__/fix-emission-invariant.test.ts +35 -4
  37. package/src/rules/components-prefer-library.test.ts +103 -0
  38. package/src/rules/components-prefer-library.ts +30 -4
  39. package/src/rules/fix-availability.ts +1 -0
  40. package/src/rules/index.ts +7 -0
  41. package/src/rules/jsx-preferred-import-path.ts +45 -9
  42. package/src/rules/rules.test.ts +202 -6
  43. package/src/rules/spacing-resolution.ts +2 -2
  44. package/src/rules/styles-no-raw-color.test.ts +6 -1
  45. package/src/rules/styles-no-raw-color.ts +3 -2
  46. package/src/rules/styles-no-raw-dimensions.ts +5 -7
  47. package/src/rules/styles-no-raw-spacing.ts +5 -2
  48. package/src/rules/styles-no-raw-typography.ts +7 -4
  49. package/src/rules/taxonomy.test.ts +3 -0
  50. package/src/rules/tiers.ts +2 -0
  51. package/src/rules/token-candidates.ts +36 -0
  52. package/src/rules/tokens-require-dual-fallback.ts +2 -1
  53. package/src/rules/tokens-upstream-drift.test.ts +111 -0
  54. package/src/rules/tokens-upstream-drift.ts +57 -0
  55. package/src/rules/utils.ts +22 -14
  56. package/src/schema.ts +13 -0
  57. package/src/schemas/index.ts +5 -8
  58. package/src/token-types.ts +71 -1
  59. package/src/types.ts +6 -0
  60. package/dist/chunk-57QDBEHQ.js.map +0 -1
  61. package/dist/chunk-HXGE3O2F.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,
@@ -111,7 +112,7 @@ import {
111
112
  ownedImportsEqual,
112
113
  resolveComponentGovernance,
113
114
  scaleGovernanceRecordSchema
114
- } from "./chunk-HXGE3O2F.js";
115
+ } from "./chunk-AOG4FTV6.js";
115
116
  import {
116
117
  AGENT_FORMAT_SCHEMA_VERSION,
117
118
  agentErrorEnvelopeSchema,
@@ -136,7 +137,7 @@ import {
136
137
  suppressionDirectiveSchema,
137
138
  validatorResultSchema,
138
139
  violationSchema
139
- } from "./chunk-57QDBEHQ.js";
140
+ } from "./chunk-ZHS52OT4.js";
140
141
  import {
141
142
  OWNED_PACKAGE_IDENTITY_EPOCH,
142
143
  OWNED_PACKAGE_IDENTITY_SOURCE_SHA256,
@@ -1687,6 +1688,14 @@ var tokenConfigSchema = z4.object({
1687
1688
  // vocabulary seeds the known-token set (#7c). A packages-only tokens block
1688
1689
  // is valid — see the superRefine below.
1689
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(),
1690
1699
  exclude: z4.array(repoRelativePathSchema).optional(),
1691
1700
  themeSelectors: z4.record(z4.string()).optional(),
1692
1701
  enabled: z4.boolean().optional(),
@@ -4775,13 +4784,20 @@ function isExemptColor(value, except) {
4775
4784
  }
4776
4785
  return false;
4777
4786
  }
4778
- function tokenReference(name) {
4779
- if (name.startsWith("$")) return name;
4780
- const cssVarName = name.startsWith("--") ? name : `--${name.replace(/\./g, "-")}`;
4781
- 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;
4782
4798
  }
4783
4799
  function isApplicableTokenReference(referenceFormat) {
4784
- return referenceFormat === void 0 || referenceFormat === "css-var" || referenceFormat === "scss-var";
4800
+ return referenceFormat === "css-var" || referenceFormat === "scss-var";
4785
4801
  }
4786
4802
 
4787
4803
  // src/rules/a11y-required-accessible-name.ts
@@ -4938,6 +4954,77 @@ function isEnforceableHtmlEquivalent(args) {
4938
4954
  return builtIn !== null && builtIn.canonical === args.canonical;
4939
4955
  }
4940
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
+
4941
5028
  // src/rules/components-prefer-library.ts
4942
5029
  var RULE_ID2 = "components/prefer-library";
4943
5030
  var RULE_VERSION2 = "1";
@@ -5081,8 +5168,12 @@ function findClassNameReimpl(node, classTokensByNode, targets) {
5081
5168
  function ruleComponentsPreferLibrary(ix) {
5082
5169
  const policy = ix.policy.ruleConfig(RULE_ID2);
5083
5170
  if (!policy?.enabled) return [];
5084
- const sources = canonicalSourcesFromPolicy(policy.options?.canonicalSources);
5085
- 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
+ );
5086
5177
  if (sources.length === 0 && mappings.length === 0) return [];
5087
5178
  const importsByFileAndLocal = indexImportsByFileAndLocal(ix);
5088
5179
  const propsByNode = indexPropsByNodeId(ix);
@@ -5345,6 +5436,16 @@ function ruleComponentsPreferLibrary(ix) {
5345
5436
  }
5346
5437
  return findings;
5347
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
+ }
5348
5449
  function indexShadowRenderRootNodeIds(ix) {
5349
5450
  const identities = ix.byKind("component_identity");
5350
5451
  if (identities.length === 0) return /* @__PURE__ */ new Set();
@@ -6117,6 +6218,18 @@ function ruleJsxPreferredImportPath(ix) {
6117
6218
  const seen = /* @__PURE__ */ new Set();
6118
6219
  for (const usage of ix.byKind("usage_import")) {
6119
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
+ }
6120
6233
  if (!ownedImportsEqual(policy.from, usage.source)) return false;
6121
6234
  if (policy.imported !== void 0 && policy.imported !== usage.imported) return false;
6122
6235
  return true;
@@ -6132,7 +6245,7 @@ function ruleJsxPreferredImportPath(ix) {
6132
6245
  ruleId: RULE_ID7,
6133
6246
  ruleVersion: RULE_VERSION8,
6134
6247
  severity: policy.severity,
6135
- 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}".`,
6136
6249
  location: usage.location,
6137
6250
  evidence: ix.evidence([usage.id, policy.id]),
6138
6251
  fingerprintIdentity: {
@@ -6143,7 +6256,7 @@ function ruleJsxPreferredImportPath(ix) {
6143
6256
  line: usage.location.line,
6144
6257
  column: usage.location.column
6145
6258
  },
6146
- fix: {
6259
+ fix: policy.bridge ? void 0 : {
6147
6260
  kind: "replaceImport",
6148
6261
  title: policy.because ?? `Replace import path with "${policy.to}"`,
6149
6262
  from: usage.source,
@@ -6155,7 +6268,14 @@ function ruleJsxPreferredImportPath(ix) {
6155
6268
  suggestedImport: policy.to,
6156
6269
  imported: usage.imported,
6157
6270
  local: usage.local,
6158
- 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
+ } : {}
6159
6279
  }
6160
6280
  })
6161
6281
  );
@@ -6163,6 +6283,12 @@ function ruleJsxPreferredImportPath(ix) {
6163
6283
  }
6164
6284
  return findings;
6165
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
+ }
6166
6292
 
6167
6293
  // src/rules/props-invalid-value.ts
6168
6294
  var RULE_ID8 = "props/invalid-value";
@@ -6373,13 +6499,36 @@ function srgbGamma(linear) {
6373
6499
  return clamped <= 31308e-7 ? 12.92 * clamped : 1.055 * Math.pow(clamped, 1 / 2.4) - 0.055;
6374
6500
  }
6375
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
+
6376
6525
  // src/rules/styles-no-raw-color.ts
6377
6526
  var RULE_ID9 = "styles/no-raw-color";
6378
6527
  var RULE_VERSION10 = "1";
6379
6528
  function ruleStylesNoRawColor(ix) {
6380
6529
  const policy = ix.policy.rawColorPolicy();
6381
6530
  if (!policy) return [];
6382
- const colorTokens = ix.tokens.byCategory("color");
6531
+ const colorTokens = localTokenCandidates(ix, "color");
6383
6532
  const preferLabel = policy.prefer === "token" ? "design token" : "CSS variable";
6384
6533
  const findings = [];
6385
6534
  const advisorySeverity = policy.severity === "error" ? "warn" : policy.severity;
@@ -6633,7 +6782,7 @@ function normalizeColor2(value) {
6633
6782
  return lower;
6634
6783
  }
6635
6784
  function buildTokenFix(property, rawValue, rawColor, match, ix) {
6636
- const reference = tokenReference(match.token.name);
6785
+ const reference = tokenReference(match.token);
6637
6786
  const value = replaceRawColor(rawValue, rawColor, reference);
6638
6787
  return emitFix(
6639
6788
  {
@@ -6668,8 +6817,8 @@ function ruleStylesNoRawDimensions(ix) {
6668
6817
  const policy = ix.policy.rawDimensionPolicy();
6669
6818
  if (!policy) return [];
6670
6819
  const appliesTo = new Set(policy.appliesTo);
6671
- const spacingTokens = ix.tokens.byCategory("spacing");
6672
- const radiusTokens = ix.tokens.byCategory("radius");
6820
+ const spacingTokens = localTokenCandidates(ix, "spacing");
6821
+ const radiusTokens = localTokenCandidates(ix, "radius");
6673
6822
  const preferLabel = policy.prefer === "token" ? "design token" : "CSS variable";
6674
6823
  const findings = [];
6675
6824
  for (const decl of ix.byKind("style_declaration")) {
@@ -6880,9 +7029,7 @@ function normalizeDimension(value) {
6880
7029
  return `${parsed.value}${parsed.unit ?? "px"}`;
6881
7030
  }
6882
7031
  function tokenVar(token) {
6883
- if (token.name.startsWith("$")) return token.name;
6884
- const cssVarName = token.name.startsWith("--") ? token.name : `--${token.name.replace(/\./g, "-")}`;
6885
- return `var(${cssVarName})`;
7032
+ return tokenReference(token);
6886
7033
  }
6887
7034
 
6888
7035
  // src/rules/spacing-resolution.ts
@@ -6920,7 +7067,7 @@ function resolveSpacingValue(input) {
6920
7067
  continue;
6921
7068
  }
6922
7069
  anyViolation = true;
6923
- const reference = tokenReference(exactToken.name);
7070
+ const reference = tokenReference(exactToken);
6924
7071
  if (!normalized.deterministicFix) allValuePreserving = false;
6925
7072
  suggestedParts.push(reference);
6926
7073
  if (!firstViolation) {
@@ -6952,7 +7099,7 @@ function resolveSpacingValue(input) {
6952
7099
  } else {
6953
7100
  const nearestToken = input.tokens.get(Math.abs(nearest));
6954
7101
  suggestedToken = nearestToken?.name;
6955
- suggestedPart = nearestToken ? tokenReference(nearestToken.name) : input.bareNumberFix ? `${nearest}` : `${nearest}${input.scale.unit}`;
7102
+ suggestedPart = nearestToken ? tokenReference(nearestToken) : input.bareNumberFix ? `${nearest}` : `${nearest}${input.scale.unit}`;
6956
7103
  suggestedParts.push(suggestedPart);
6957
7104
  if (!normalized.deterministicFix) reliableSuggestion = false;
6958
7105
  }
@@ -7004,7 +7151,7 @@ function ruleStylesNoRawSpacing(ix) {
7004
7151
  const lookupFor = (scale) => {
7005
7152
  let lookup = lookupCache.get(scale.name);
7006
7153
  if (!lookup) {
7007
- lookup = buildSpacingTokenLookup(ix.tokens.byCategory("spacing"), scale);
7154
+ lookup = buildSpacingTokenLookup(localTokenCandidates(ix, "spacing"), scale);
7008
7155
  lookupCache.set(scale.name, lookup);
7009
7156
  }
7010
7157
  return lookup;
@@ -7138,7 +7285,7 @@ function buildSpacingFix(property, checked, ix) {
7138
7285
  deterministic: true
7139
7286
  },
7140
7287
  {
7141
- symbols: tokenSymbolsInText(checked.suggestedValue),
7288
+ symbols: checked.matchedToken ? [checked.matchedToken] : tokenSymbolsInText(checked.suggestedValue),
7142
7289
  editKind: "replaceStyleValue",
7143
7290
  valuePreserving: checked.deterministicFix
7144
7291
  },
@@ -7254,7 +7401,7 @@ function ruleStylesNoRawTypography(ix) {
7254
7401
  }
7255
7402
  function buildTypographyTokenLookup(ix, scale) {
7256
7403
  const out = /* @__PURE__ */ new Map();
7257
- for (const token of ix.tokens.byCategory("typography")) {
7404
+ for (const token of localTokenCandidates(ix, "typography")) {
7258
7405
  const parsed = parseLengthValue(token.value);
7259
7406
  if (!parsed || parsed.unit === null) continue;
7260
7407
  const normalized = normalizeLengthForScale(parsed, scale);
@@ -7276,7 +7423,7 @@ function checkFontSize(raw, allowed, scale, tokens, bareNumberFix = false) {
7276
7423
  if (matchesScale(normalized.value, allowed)) {
7277
7424
  if (!exactToken) return null;
7278
7425
  return {
7279
- suggestedValue: tokenReference(exactToken.name),
7426
+ suggestedValue: tokenReference(exactToken),
7280
7427
  fixEmittable: true,
7281
7428
  deterministicFix: normalized.deterministicFix,
7282
7429
  reason: "token-equivalent",
@@ -7288,7 +7435,7 @@ function checkFontSize(raw, allowed, scale, tokens, bareNumberFix = false) {
7288
7435
  return { fixEmittable: false, deterministicFix: false, reason: "off-scale" };
7289
7436
  }
7290
7437
  const nearestToken = tokens.get(Math.abs(nearest));
7291
- const suggestedValue = nearestToken ? tokenReference(nearestToken.name) : bareNumberFix ? `${nearest}` : `${nearest}${scale.unit}`;
7438
+ const suggestedValue = nearestToken ? tokenReference(nearestToken) : bareNumberFix ? `${nearest}` : `${nearest}${scale.unit}`;
7292
7439
  return {
7293
7440
  suggestedValue,
7294
7441
  fixEmittable: normalized.deterministicFix,
@@ -7307,7 +7454,7 @@ function buildFix(property, checked, ix) {
7307
7454
  deterministic: true
7308
7455
  },
7309
7456
  {
7310
- symbols: tokenSymbolsInText(checked.suggestedValue),
7457
+ symbols: checked.matchedToken ? [checked.matchedToken] : tokenSymbolsInText(checked.suggestedValue),
7311
7458
  editKind: "replaceStyleValue",
7312
7459
  valuePreserving: checked.deterministicFix
7313
7460
  },
@@ -7986,7 +8133,7 @@ var SASS_FILE = /\.(scss|sass)$/i;
7986
8133
  function ruleTokensRequireDualFallback(ix) {
7987
8134
  const policy = ix.policy.ruleConfig(RULE_ID20);
7988
8135
  if (!policy?.enabled) return [];
7989
- const tokensByCssVariable = indexTokensByCssVariable(ix.tokens.list());
8136
+ const tokensByCssVariable = indexTokensByCssVariable(localTokenCandidates(ix));
7990
8137
  const findings = [];
7991
8138
  for (const decl of ix.byKind("style_declaration")) {
7992
8139
  if (!SASS_FILE.test(decl.file)) continue;
@@ -8135,6 +8282,57 @@ function skipToCloseParen(value, openParenIndex) {
8135
8282
  return value.length;
8136
8283
  }
8137
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
+
8138
8336
  // src/rules/emit-gate.ts
8139
8337
  var BLOCKING_RULE_ALLOWLIST = /* @__PURE__ */ new Set([
8140
8338
  "styles/no-raw-color",
@@ -8175,6 +8373,8 @@ var RULE_TIER = {
8175
8373
  // canonical-component bypass (FUI1004)
8176
8374
  "tokens/css-vars-must-be-defined": "contract",
8177
8375
  // token drift (FUI2015)
8376
+ "tokens/upstream-drift": "contract",
8377
+ // authored local/upstream value drift (FUI2017)
8178
8378
  // ---- Tier B — generic hygiene -----------------------------------------
8179
8379
  "components/unknown-prop": "hygiene",
8180
8380
  "components/forbidden-prop-value": "hygiene",
@@ -8218,7 +8418,8 @@ function hygieneTierRuleIds() {
8218
8418
  var VOCABULARY_ORDER = [
8219
8419
  "components/shadow-component",
8220
8420
  "components/prefer-library",
8221
- "tokens/css-vars-must-be-defined"
8421
+ "tokens/css-vars-must-be-defined",
8422
+ "tokens/upstream-drift"
8222
8423
  ];
8223
8424
  function vocabularyRank(ruleId) {
8224
8425
  const index = VOCABULARY_ORDER.indexOf(ruleId);
@@ -8342,6 +8543,11 @@ var RULES = [
8342
8543
  version: "1",
8343
8544
  run: ruleTokensCssVarsMustBeDefined
8344
8545
  },
8546
+ {
8547
+ id: "tokens/upstream-drift",
8548
+ version: "1",
8549
+ run: ruleTokensUpstreamDrift
8550
+ },
8345
8551
  {
8346
8552
  id: "theme/no-theme-coupled-literal",
8347
8553
  version: "1",
@@ -8431,19 +8637,23 @@ function evaluateGovernanceIntegrity(input) {
8431
8637
  policyFamily.reason = "no governance policy resolved";
8432
8638
  }
8433
8639
  const componentsConfig = configs.get("components/prefer-library");
8434
- 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);
8435
8642
  const componentsFamily = {
8436
8643
  id: "components",
8437
8644
  armed: componentsArmed,
8438
- rules: ["components/prefer-library"]
8645
+ rules: [
8646
+ ...confirmedBridges.length > 0 ? ["imports/preferred-path"] : [],
8647
+ ...componentsConfig?.enabled === true ? ["components/prefer-library"] : []
8648
+ ]
8439
8649
  };
8440
8650
  if (!componentsArmed) {
8441
8651
  if (componentsConfig?.enabled === true) {
8442
8652
  componentsFamily.reason = "components/prefer-library enabled but no effective canonical source";
8443
- 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";
8444
8654
  } else {
8445
8655
  componentsFamily.reason = "components/prefer-library not enabled";
8446
- 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";
8447
8657
  }
8448
8658
  }
8449
8659
  const cssVarsActive = input.cssVarsActive ?? policyDeclaresCssVars(input.policy);
@@ -8529,6 +8739,74 @@ function evaluateGovernanceIntegrity(input) {
8529
8739
  };
8530
8740
  }
8531
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
+
8532
8810
  // src/contract/preimage.ts
8533
8811
  var CONTRACT_DOMAINS = ["components", "tokens", "canonicalMap", "policy"];
8534
8812
  var CONTRACT_PREIMAGE_SCHEMA = "fcid-preimage:v1";
@@ -8584,6 +8862,15 @@ function projectCanonicalMap(mappings) {
8584
8862
  component: mapping.component,
8585
8863
  canonical: mapping.canonical,
8586
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,
8587
8874
  resolves: mapping.resolves ? sortCanonical(mapping.resolves) : void 0,
8588
8875
  propMapping: mapping.propMapping ? sortCanonical(
8589
8876
  mapping.propMapping.map((entry) => ({
@@ -9016,6 +9303,7 @@ export {
9016
9303
  DEFAULTS,
9017
9304
  DEFAULT_ENHANCED_STYLE_PROPERTIES,
9018
9305
  DEFAULT_STYLE_PROPERTIES,
9306
+ EFFECTIVE_GOVERNANCE_INPUTS_SCHEMA,
9019
9307
  EVIDENCE_ORDER,
9020
9308
  EXPLAIN_URL_BASE,
9021
9309
  FRAGMENTS_INTERNAL_RULE_IDS,
@@ -9070,6 +9358,9 @@ export {
9070
9358
  byRuleId,
9071
9359
  calculateDeltaE,
9072
9360
  canBlock,
9361
+ canonicalBridgeContractMappings,
9362
+ canonicalBridgeV1Schema,
9363
+ canonicalDirectionConflictsTarget,
9073
9364
  canonicalJson,
9074
9365
  canonicalPreimage,
9075
9366
  canonicalizeOwnedComponentId,
@@ -9092,6 +9383,7 @@ export {
9092
9383
  compareStylesWithTokens,
9093
9384
  compileBlock,
9094
9385
  compileComponentFacts,
9386
+ compileEffectiveGovernanceInputs,
9095
9387
  compileFragment,
9096
9388
  compileGlobalGovernanceFacts,
9097
9389
  compileRecipe,
@@ -9287,6 +9579,7 @@ export {
9287
9579
  parseTokenFile,
9288
9580
  parseTokens,
9289
9581
  portableRepoPathError,
9582
+ projectCanonicalDirectionConflicts,
9290
9583
  projectContractPreimage,
9291
9584
  projectV1OwnedComponentId,
9292
9585
  projectV1OwnedImportIdentity,