@usefragments/core 1.5.2 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/dist/{chunk-BAHCOAVG.js → chunk-WVFNDPM4.js} +423 -189
  2. package/dist/chunk-WVFNDPM4.js.map +1 -0
  3. package/dist/codes/index.d.ts +1 -1
  4. package/dist/codes/index.js +1 -1
  5. package/dist/compiled-types/index.d.ts +1 -1
  6. package/dist/generate/index.d.ts +1 -1
  7. package/dist/{governance-pKrfh517.d.ts → governance-DxFipN5V.d.ts} +609 -20
  8. package/dist/index.d.ts +638 -43
  9. package/dist/index.js +304 -37
  10. package/dist/index.js.map +1 -1
  11. package/dist/react-types.d.ts +1 -1
  12. package/dist/test-utils.d.ts +1 -1
  13. package/package.json +1 -1
  14. package/src/__tests__/policy-exclude.test.ts +180 -0
  15. package/src/canonical-bridge.ts +69 -1
  16. package/src/canonical-direction.test.ts +118 -0
  17. package/src/canonical-direction.ts +43 -2
  18. package/src/codes/__tests__/codes.test.ts +14 -1
  19. package/src/codes/codes.ts +40 -0
  20. package/src/facts/builders.ts +35 -0
  21. package/src/facts/compile.ts +135 -21
  22. package/src/facts/fact-index.ts +15 -2
  23. package/src/facts/facts.test.ts +6 -2
  24. package/src/facts/index.ts +9 -6
  25. package/src/facts/types.ts +45 -9
  26. package/src/governance-integrity.test.ts +174 -3
  27. package/src/governance-integrity.ts +305 -5
  28. package/src/governance.ts +87 -1
  29. package/src/index.ts +38 -1
  30. package/src/policy-exclude.ts +113 -0
  31. package/src/rules/families.test.ts +69 -0
  32. package/src/rules/families.ts +52 -0
  33. package/src/rules/index.ts +6 -0
  34. package/src/rules/jsx-preferred-import-path.ts +29 -11
  35. package/src/rules/rules.test.ts +125 -1
  36. package/src/rules/styles-no-raw-color.ts +13 -4
  37. package/src/rules/styles-no-raw-dimensions.ts +13 -4
  38. package/src/rules/styles-no-raw-spacing.ts +12 -4
  39. package/src/rules/styles-no-raw-typography.ts +13 -4
  40. package/src/rules/utils.ts +39 -0
  41. package/dist/chunk-BAHCOAVG.js.map +0 -1
package/dist/index.js CHANGED
@@ -44,6 +44,8 @@ import {
44
44
  CODES,
45
45
  EXPLAIN_URL_BASE,
46
46
  FactIndex,
47
+ RULE_FAMILY_IDS,
48
+ RULE_FAMILY_MEMBERS,
47
49
  asComponentId,
48
50
  bridgeSourceViolation,
49
51
  bridgeSourceViolations,
@@ -56,6 +58,8 @@ import {
56
58
  componentGovernanceRecordSchema,
57
59
  componentGovernanceRecordsSchema,
58
60
  componentId,
61
+ describePolicyExclude,
62
+ excludeGlobToRegExp,
59
63
  explainUrlForCode,
60
64
  factId,
61
65
  g,
@@ -65,6 +69,8 @@ import {
65
69
  governanceConfigSchema,
66
70
  governanceSeveritySchema,
67
71
  hash64Hex,
72
+ isPresetSourcedRule,
73
+ isRuleFamilyId,
68
74
  makeA11yNameRequiredFact,
69
75
  makeCanonicalCandidateFact,
70
76
  makeCanonicalMappingFact,
@@ -105,14 +111,22 @@ import {
105
111
  makeUsageNodeFact,
106
112
  makeUsagePropResolvedFact,
107
113
  makeUsageTextChildFact,
114
+ markPresetSourcedRules,
115
+ matchPolicyExclude,
108
116
  matchesGlob,
117
+ normalizeExcludePath,
109
118
  normalizeGovernanceConfig,
119
+ normalizePolicyExcludes,
110
120
  ownedComponentIdsEqual,
111
121
  ownedImportMatchesRoot,
112
122
  ownedImportsEqual,
123
+ policyExcludeMatchesPath,
124
+ policyExcludeSchema,
125
+ projectSupersededImportPathPreferences,
113
126
  resolveComponentGovernance,
127
+ ruleFamilyMembers,
114
128
  scaleGovernanceRecordSchema
115
- } from "./chunk-BAHCOAVG.js";
129
+ } from "./chunk-WVFNDPM4.js";
116
130
  import {
117
131
  AGENT_FORMAT_SCHEMA_VERSION,
118
132
  agentErrorEnvelopeSchema,
@@ -4496,6 +4510,19 @@ function readUsageNode(ix, id) {
4496
4510
  const fact = ix.get(id);
4497
4511
  return fact && fact.kind === "usage_node" ? fact : void 0;
4498
4512
  }
4513
+ function hoppedValue(fact) {
4514
+ const from = fact.valueFrom;
4515
+ if (!from) return void 0;
4516
+ const declaredAt = `${from.location.file}:${from.location.line}`;
4517
+ return {
4518
+ suffix: ` Value comes from \`${from.name}\`, declared at ${declaredAt}.`,
4519
+ attributes: {
4520
+ valueFrom: "const-binding",
4521
+ declaredAs: from.name,
4522
+ declaredAt
4523
+ }
4524
+ };
4525
+ }
4499
4526
  function contractPrefixFamilies(names) {
4500
4527
  const families = /* @__PURE__ */ new Set();
4501
4528
  for (const name of names) {
@@ -4977,7 +5004,7 @@ function projectCanonicalDirectionConflicts(ix, canonicalSources) {
4977
5004
  const conflicts = [];
4978
5005
  for (const definition of ix.byKind("component_definition")) {
4979
5006
  if (!definition.exported || definition.renderRoot.resolution !== "canonical") continue;
4980
- const target = npmTarget(definition.renderRoot.canonical, npmSources);
5007
+ const target = npmTarget(definition.renderRoot, npmSources);
4981
5008
  if (!target) continue;
4982
5009
  const identity = identityByComponent.get(definition.componentKey);
4983
5010
  if (identity?.decisionId && decisionTargetsExport(identity.canonicalTarget, target.exportName)) {
@@ -5011,7 +5038,10 @@ function canonicalDirectionConflictsTarget(conflicts, importPath, exportName) {
5011
5038
  (conflict) => ownedImportMatchesRoot(importPath, conflict.packageName) && conflict.exportName === exportName
5012
5039
  );
5013
5040
  }
5014
- function npmTarget(canonicalTarget, sources) {
5041
+ function npmTarget(renderRoot, sources) {
5042
+ return npmTargetFromCanonicalKey(renderRoot.canonical, sources) ?? npmTargetFromImportSource(renderRoot, sources);
5043
+ }
5044
+ function npmTargetFromCanonicalKey(canonicalTarget, sources) {
5015
5045
  const separator = canonicalTarget.lastIndexOf("#");
5016
5046
  if (separator <= 0 || separator === canonicalTarget.length - 1) return null;
5017
5047
  const importPath = canonicalTarget.slice(0, separator);
@@ -5028,6 +5058,19 @@ function npmTarget(canonicalTarget, sources) {
5028
5058
  }
5029
5059
  return null;
5030
5060
  }
5061
+ function npmTargetFromImportSource(renderRoot, sources) {
5062
+ const separator = renderRoot.canonical.lastIndexOf("#");
5063
+ if (separator <= 0 || separator === renderRoot.canonical.length - 1) return null;
5064
+ const exportName = renderRoot.canonical.slice(separator + 1).split(".").at(-1);
5065
+ if (!exportName || exportName === "default") return null;
5066
+ for (const source of sources) {
5067
+ if (!ownedImportMatchesRoot(renderRoot.importSource, source.specifier)) continue;
5068
+ if (source.exclude?.includes(exportName)) continue;
5069
+ if (source.include && !source.include.includes(exportName)) continue;
5070
+ return { packageName: source.specifier, exportName };
5071
+ }
5072
+ return null;
5073
+ }
5031
5074
  function packageSubpath(importPath, packageName) {
5032
5075
  return importPath === packageName ? "" : importPath.slice(packageName.length + 1);
5033
5076
  }
@@ -6228,19 +6271,15 @@ function ruleJsxPreferredImportPath(ix) {
6228
6271
  const findings = [];
6229
6272
  const seen = /* @__PURE__ */ new Set();
6230
6273
  for (const usage of ix.byKind("usage_import")) {
6274
+ const bridgeGoverned = policies.some((policy) => bridgeGovernsUnderlyingImport(policy, usage));
6231
6275
  const matches = policies.filter((policy) => {
6232
6276
  if (policy.bridge) {
6233
6277
  if (policy.bridge.implementationFiles.some((file) => sameRepoPath(file, usage.file))) {
6234
6278
  return false;
6235
6279
  }
6236
- if (!ownedImportMatchesRoot(usage.source, policy.from)) return false;
6237
- if (ownedImportsEqual(usage.source, policy.from)) {
6238
- return usage.imported === policy.bridge.underlyingExportName;
6239
- }
6240
- const subpath = usage.source.slice(policy.from.length + 1);
6241
- const leaf = subpath.split("/").at(-1);
6242
- return leaf === policy.bridge.underlyingExportName && (usage.imported === "default" || usage.imported === policy.bridge.underlyingExportName);
6280
+ return bridgeGovernsUnderlyingImport(policy, usage);
6243
6281
  }
6282
+ if (bridgeGoverned) return false;
6244
6283
  if (!ownedImportsEqual(policy.from, usage.source)) return false;
6245
6284
  if (policy.imported !== void 0 && policy.imported !== usage.imported) return false;
6246
6285
  return true;
@@ -6294,6 +6333,17 @@ function ruleJsxPreferredImportPath(ix) {
6294
6333
  }
6295
6334
  return findings;
6296
6335
  }
6336
+ function bridgeGovernsUnderlyingImport(policy, usage) {
6337
+ const bridge = policy.bridge;
6338
+ if (!bridge) return false;
6339
+ if (!ownedImportMatchesRoot(usage.source, policy.from)) return false;
6340
+ if (ownedImportsEqual(usage.source, policy.from)) {
6341
+ return usage.imported === bridge.underlyingExportName;
6342
+ }
6343
+ const subpath = usage.source.slice(policy.from.length + 1);
6344
+ const leaf = subpath.split("/").at(-1);
6345
+ return leaf === bridge.underlyingExportName && (usage.imported === "default" || usage.imported === bridge.underlyingExportName);
6346
+ }
6297
6347
  function sameRepoPath(left, right) {
6298
6348
  return normalizeRepoPath(left) === normalizeRepoPath(right);
6299
6349
  }
@@ -6549,14 +6599,15 @@ function ruleStylesNoRawColor(ix) {
6549
6599
  if (!color) continue;
6550
6600
  if (isExemptColor(color, policy.except)) continue;
6551
6601
  const resolution = resolveColorToken(decl.property, color, colorTokens);
6552
- const fixEmission = resolution?.kind === "exact" ? buildTokenFix(decl.property, decl.value, color, resolution, ix) : void 0;
6602
+ const hopped = hoppedValue(decl);
6603
+ const fixEmission = resolution?.kind === "exact" && !hopped ? buildTokenFix(decl.property, decl.value, color, resolution, ix) : void 0;
6553
6604
  const evidenceIds = resolution ? [decl.id, policy.id, resolution.token.id] : [decl.id, policy.id];
6554
6605
  findings.push(
6555
6606
  makeFinding({
6556
6607
  ruleId: RULE_ID9,
6557
6608
  ruleVersion: RULE_VERSION10,
6558
6609
  severity: policy.severity,
6559
- message: colorMessage(color, `\`${decl.property}\``, resolution, preferLabel),
6610
+ message: colorMessage(color, `\`${decl.property}\``, resolution, preferLabel) + (hopped?.suffix ?? ""),
6560
6611
  location: decl.location,
6561
6612
  evidence: ix.evidence(evidenceIds),
6562
6613
  fingerprintIdentity: {
@@ -6575,7 +6626,8 @@ function ruleStylesNoRawColor(ix) {
6575
6626
  source: "css",
6576
6627
  suggestedToken: resolution?.token.name,
6577
6628
  ...resolution ? { tokenMatch: resolution.kind } : {},
6578
- ...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {}
6629
+ ...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {},
6630
+ ...hopped?.attributes ?? {}
6579
6631
  }
6580
6632
  })
6581
6633
  );
@@ -6621,7 +6673,8 @@ function ruleStylesNoRawColor(ix) {
6621
6673
  if (!node) continue;
6622
6674
  const componentEvidenceId = componentByNode.get(node.id)?.id;
6623
6675
  const resolution = resolveColorToken(inline.property, color, colorTokens);
6624
- const fixEmission = resolution?.kind === "exact" ? buildTokenFix(inline.property, inline.value, color, resolution, ix) : void 0;
6676
+ const hopped = hoppedValue(inline);
6677
+ const fixEmission = resolution?.kind === "exact" && !hopped ? buildTokenFix(inline.property, inline.value, color, resolution, ix) : void 0;
6625
6678
  const baseEvidence = componentEvidenceId ? [node.id, componentEvidenceId, inline.id, policy.id] : [node.id, inline.id, policy.id];
6626
6679
  const evidenceIds = resolution ? [...baseEvidence, resolution.token.id] : baseEvidence;
6627
6680
  findings.push(
@@ -6629,7 +6682,7 @@ function ruleStylesNoRawColor(ix) {
6629
6682
  ruleId: RULE_ID9,
6630
6683
  ruleVersion: RULE_VERSION10,
6631
6684
  severity: policy.severity,
6632
- message: colorMessage(color, `inline \`${inline.property}\``, resolution, preferLabel),
6685
+ message: colorMessage(color, `inline \`${inline.property}\``, resolution, preferLabel) + (hopped?.suffix ?? ""),
6633
6686
  location: node.location,
6634
6687
  evidence: ix.evidence(evidenceIds),
6635
6688
  fingerprintIdentity: {
@@ -6648,7 +6701,8 @@ function ruleStylesNoRawColor(ix) {
6648
6701
  source: "jsx",
6649
6702
  suggestedToken: resolution?.token.name,
6650
6703
  ...resolution ? { tokenMatch: resolution.kind } : {},
6651
- ...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {}
6704
+ ...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {},
6705
+ ...hopped?.attributes ?? {}
6652
6706
  }
6653
6707
  })
6654
6708
  );
@@ -6840,13 +6894,14 @@ function ruleStylesNoRawDimensions(ix) {
6840
6894
  if (!resolution) continue;
6841
6895
  const tokenIds = resolutionTokenIds(resolution);
6842
6896
  const evidenceIds = tokenIds.length ? [decl.id, policy.id, ...tokenIds] : [decl.id, policy.id];
6843
- const fixEmission = dimensionFix(decl.property, resolution, ix);
6897
+ const hopped = hoppedValue(decl);
6898
+ const fixEmission = hopped ? void 0 : dimensionFix(decl.property, resolution, ix);
6844
6899
  findings.push(
6845
6900
  makeFinding({
6846
6901
  ruleId: RULE_ID10,
6847
6902
  ruleVersion: RULE_VERSION11,
6848
6903
  severity: policy.severity,
6849
- message: dimensionMessage(decl.value, decl.property, preferLabel, resolution, false),
6904
+ message: dimensionMessage(decl.value, decl.property, preferLabel, resolution, false) + (hopped?.suffix ?? ""),
6850
6905
  location: decl.location,
6851
6906
  evidence: ix.evidence(evidenceIds),
6852
6907
  fingerprintIdentity: {
@@ -6863,7 +6918,8 @@ function ruleStylesNoRawDimensions(ix) {
6863
6918
  rawValue: decl.value,
6864
6919
  source: "css",
6865
6920
  suggestedToken: resolutionSuggestedToken(resolution),
6866
- ...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {}
6921
+ ...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {},
6922
+ ...hopped?.attributes ?? {}
6867
6923
  }
6868
6924
  })
6869
6925
  );
@@ -6882,13 +6938,14 @@ function ruleStylesNoRawDimensions(ix) {
6882
6938
  const baseEvidence = componentEvidenceId ? [node.id, componentEvidenceId, inline.id, policy.id] : [node.id, inline.id, policy.id];
6883
6939
  const tokenIds = resolutionTokenIds(resolution);
6884
6940
  const evidenceIds = tokenIds.length ? [...baseEvidence, ...tokenIds] : baseEvidence;
6885
- const fixEmission = dimensionFix(inline.property, resolution, ix);
6941
+ const hopped = hoppedValue(inline);
6942
+ const fixEmission = hopped ? void 0 : dimensionFix(inline.property, resolution, ix);
6886
6943
  findings.push(
6887
6944
  makeFinding({
6888
6945
  ruleId: RULE_ID10,
6889
6946
  ruleVersion: RULE_VERSION11,
6890
6947
  severity: policy.severity,
6891
- message: dimensionMessage(inline.value, inline.property, preferLabel, resolution, true),
6948
+ message: dimensionMessage(inline.value, inline.property, preferLabel, resolution, true) + (hopped?.suffix ?? ""),
6892
6949
  location: node.location,
6893
6950
  evidence: ix.evidence(evidenceIds),
6894
6951
  fingerprintIdentity: {
@@ -6905,7 +6962,8 @@ function ruleStylesNoRawDimensions(ix) {
6905
6962
  rawValue: inline.value,
6906
6963
  source: "jsx",
6907
6964
  suggestedToken: resolutionSuggestedToken(resolution),
6908
- ...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {}
6965
+ ...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {},
6966
+ ...hopped?.attributes ?? {}
6909
6967
  }
6910
6968
  })
6911
6969
  );
@@ -7195,12 +7253,13 @@ function checkDeclaration(ix, decl, lookupFor) {
7195
7253
  tokens: lookupFor(scale)
7196
7254
  });
7197
7255
  if (!checked) return null;
7198
- const fixEmission = buildSpacingFix(decl.property, checked, ix);
7256
+ const hopped = hoppedValue(decl);
7257
+ const fixEmission = hopped ? void 0 : buildSpacingFix(decl.property, checked, ix);
7199
7258
  return makeFinding({
7200
7259
  ruleId: RULE_ID11,
7201
7260
  ruleVersion: RULE_VERSION12,
7202
7261
  severity: policy.severity,
7203
- message: spacingMessage(decl.property, decl.value, allowed, scale, checked),
7262
+ message: spacingMessage(decl.property, decl.value, allowed, scale, checked) + (hopped?.suffix ?? ""),
7204
7263
  location: decl.location,
7205
7264
  evidence: ix.evidence([decl.id, policy.id, scale.id]),
7206
7265
  fingerprintIdentity: {
@@ -7226,7 +7285,8 @@ function checkDeclaration(ix, decl, lookupFor) {
7226
7285
  assumedRootFontSizePx: checked.assumedRootFontSizePx,
7227
7286
  assumedEmBasePx: checked.assumedEmBasePx,
7228
7287
  source: "css",
7229
- ...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {}
7288
+ ...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {},
7289
+ ...hopped?.attributes ?? {}
7230
7290
  }
7231
7291
  });
7232
7292
  }
@@ -7246,7 +7306,8 @@ function checkInlineStyle(ix, inline, componentByNode, lookupFor) {
7246
7306
  bareNumberFix: inline.valueKind === "number"
7247
7307
  });
7248
7308
  if (!checked) return null;
7249
- const fixEmission = buildSpacingFix(inline.property, checked, ix);
7309
+ const hopped = hoppedValue(inline);
7310
+ const fixEmission = hopped ? void 0 : buildSpacingFix(inline.property, checked, ix);
7250
7311
  const node = readUsageNode(ix, inline.nodeId);
7251
7312
  if (!node) return null;
7252
7313
  const componentEvidenceId = componentByNode.get(node.id)?.id;
@@ -7255,7 +7316,7 @@ function checkInlineStyle(ix, inline, componentByNode, lookupFor) {
7255
7316
  ruleId: RULE_ID11,
7256
7317
  ruleVersion: RULE_VERSION12,
7257
7318
  severity: policy.severity,
7258
- message: spacingMessage(inline.property, inline.value, allowed, scale, checked),
7319
+ message: spacingMessage(inline.property, inline.value, allowed, scale, checked) + (hopped?.suffix ?? ""),
7259
7320
  location: node.location,
7260
7321
  evidence: ix.evidence(evidenceIds),
7261
7322
  fingerprintIdentity: {
@@ -7281,7 +7342,8 @@ function checkInlineStyle(ix, inline, componentByNode, lookupFor) {
7281
7342
  assumedRootFontSizePx: checked.assumedRootFontSizePx,
7282
7343
  assumedEmBasePx: checked.assumedEmBasePx,
7283
7344
  source: "jsx",
7284
- ...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {}
7345
+ ...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {},
7346
+ ...hopped?.attributes ?? {}
7285
7347
  }
7286
7348
  });
7287
7349
  }
@@ -7330,13 +7392,14 @@ function ruleStylesNoRawTypography(ix) {
7330
7392
  if (decl.property.toLowerCase() !== "font-size") continue;
7331
7393
  const checked = checkFontSize(decl.value, allowed, scale, tokens);
7332
7394
  if (!checked) continue;
7333
- const fixEmission = buildFix(decl.property, checked, ix);
7395
+ const hopped = hoppedValue(decl);
7396
+ const fixEmission = hopped ? void 0 : buildFix(decl.property, checked, ix);
7334
7397
  findings.push(
7335
7398
  makeFinding({
7336
7399
  ruleId: RULE_ID12,
7337
7400
  ruleVersion: RULE_VERSION13,
7338
7401
  severity: policy.severity,
7339
- message: typographyMessage(decl.property, decl.value, allowed, scale.unit, checked),
7402
+ message: typographyMessage(decl.property, decl.value, allowed, scale.unit, checked) + (hopped?.suffix ?? ""),
7340
7403
  location: decl.location,
7341
7404
  evidence: ix.evidence([decl.id, policy.id, scale.id]),
7342
7405
  fingerprintIdentity: {
@@ -7356,7 +7419,8 @@ function ruleStylesNoRawTypography(ix) {
7356
7419
  suggestedValue: checked.suggestedValue,
7357
7420
  matchedToken: checked.matchedToken,
7358
7421
  source: "css",
7359
- ...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {}
7422
+ ...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {},
7423
+ ...hopped?.attributes ?? {}
7360
7424
  }
7361
7425
  })
7362
7426
  );
@@ -7373,7 +7437,8 @@ function ruleStylesNoRawTypography(ix) {
7373
7437
  inline.valueKind === "number"
7374
7438
  );
7375
7439
  if (!checked) continue;
7376
- const fixEmission = buildFix(inline.property, checked, ix);
7440
+ const hopped = hoppedValue(inline);
7441
+ const fixEmission = hopped ? void 0 : buildFix(inline.property, checked, ix);
7377
7442
  const node = readUsageNode(ix, inline.nodeId);
7378
7443
  if (!node) continue;
7379
7444
  const componentEvidenceId = componentByNode.get(node.id)?.id;
@@ -7383,7 +7448,7 @@ function ruleStylesNoRawTypography(ix) {
7383
7448
  ruleId: RULE_ID12,
7384
7449
  ruleVersion: RULE_VERSION13,
7385
7450
  severity: policy.severity,
7386
- message: typographyMessage(inline.property, inline.value, allowed, scale.unit, checked),
7451
+ message: typographyMessage(inline.property, inline.value, allowed, scale.unit, checked) + (hopped?.suffix ?? ""),
7387
7452
  location: node.location,
7388
7453
  evidence: ix.evidence(evidenceIds),
7389
7454
  fingerprintIdentity: {
@@ -7403,7 +7468,8 @@ function ruleStylesNoRawTypography(ix) {
7403
7468
  suggestedValue: checked.suggestedValue,
7404
7469
  matchedToken: checked.matchedToken,
7405
7470
  source: "jsx",
7406
- ...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {}
7471
+ ...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {},
7472
+ ...hopped?.attributes ?? {}
7407
7473
  }
7408
7474
  })
7409
7475
  );
@@ -8623,10 +8689,9 @@ function policyDeclaresCssVars(policy) {
8623
8689
  function dedupe(values) {
8624
8690
  return [...new Set(values)];
8625
8691
  }
8626
- var RULE_FAMILY_IDS = /* @__PURE__ */ new Set(["tokens/hardcoded-values", "components/usage", "a11y/wcag"]);
8627
8692
  var CONSUMED_RULE_IDS = new Set(Object.keys(RULE_TIER));
8628
8693
  var RECOGNIZED_RULE_IDS = /* @__PURE__ */ new Set([...CONSUMED_RULE_IDS, ...RULE_FAMILY_IDS]);
8629
- var RULE_CONFIG_KEYS = /* @__PURE__ */ new Set(["enabled", "severity", "options"]);
8694
+ var RULE_CONFIG_KEYS = /* @__PURE__ */ new Set(["enabled", "severity", "options", "exclude"]);
8630
8695
  var PASSTHROUGH_KEYS = [
8631
8696
  {
8632
8697
  path: ["tokens"],
@@ -8665,7 +8730,7 @@ var PASSTHROUGH_KEYS = [
8665
8730
  },
8666
8731
  {
8667
8732
  path: ["govern", "ci"],
8668
- keys: ["failOnWarnings"]
8733
+ keys: ["failOnWarnings", "failOnInert"]
8669
8734
  }
8670
8735
  ];
8671
8736
  function objectRecord(value) {
@@ -8697,6 +8762,139 @@ function unconsumedKeyDiagnostic(path) {
8697
8762
  message: `${renderedPath} is not consumed by Fragments and has no effect. Remove it or use a supported config key.`
8698
8763
  };
8699
8764
  }
8765
+ var CONFIG_RECORD_FACT_KINDS = {
8766
+ scale: { section: "govern.scales", record: "scale", keys: ["name"] },
8767
+ scale_value: { section: "govern.scales", record: "scale.values", keys: ["scale", "value"] },
8768
+ style_raw_color_forbidden: {
8769
+ section: "govern.styles",
8770
+ record: "style.rawColors.forbid",
8771
+ keys: []
8772
+ },
8773
+ style_raw_dimension_forbidden: {
8774
+ section: "govern.styles",
8775
+ record: "style.rawDimensions.forbid",
8776
+ keys: []
8777
+ },
8778
+ style_property_scale: {
8779
+ section: "govern.styles",
8780
+ record: "style.rawSpacing.mustMatchScale",
8781
+ keys: ["property"]
8782
+ },
8783
+ style_font_size_scale: {
8784
+ section: "govern.styles",
8785
+ record: "style.fontSize.mustMatchScale",
8786
+ keys: []
8787
+ },
8788
+ style_css_vars_must_be_defined: {
8789
+ section: "govern.styles",
8790
+ record: "style.cssVars.mustBeDefined",
8791
+ keys: []
8792
+ },
8793
+ jsx_unknown_props_forbidden: {
8794
+ section: "govern.jsx",
8795
+ record: "jsx.unknownProps.forbid",
8796
+ keys: []
8797
+ },
8798
+ jsx_inline_style_forbidden_raw: {
8799
+ section: "govern.jsx",
8800
+ record: "jsx.inlineStyle.forbidRaw",
8801
+ keys: ["property"]
8802
+ },
8803
+ jsx_import_path_preferred: {
8804
+ section: "govern.jsx",
8805
+ record: "jsx.importPath.prefer",
8806
+ keys: ["from", "to", "imported"]
8807
+ },
8808
+ jsx_component_preferred: {
8809
+ section: "govern.jsx",
8810
+ record: "jsx.component.prefer",
8811
+ keys: ["from", "to"]
8812
+ },
8813
+ prop_value_avoided: {
8814
+ section: "govern.components",
8815
+ record: "prop.value.avoid",
8816
+ keys: ["componentId", "prop", "value"]
8817
+ },
8818
+ prop_value_forbidden: {
8819
+ section: "govern.components",
8820
+ record: "prop.value.forbid",
8821
+ keys: ["componentId", "prop", "value", "pathPattern"]
8822
+ },
8823
+ a11y_name_required: {
8824
+ section: "govern.components",
8825
+ record: "a11y.requireName",
8826
+ keys: ["componentId"]
8827
+ },
8828
+ tailwind_palette_allow: {
8829
+ section: "govern.tailwind",
8830
+ record: "palette.allow",
8831
+ keys: []
8832
+ },
8833
+ tailwind_palette_deny: { section: "govern.tailwind", record: "palette.deny", keys: [] },
8834
+ tailwind_unknown_class_enabled: {
8835
+ section: "govern.tailwind",
8836
+ record: "unknownClass",
8837
+ keys: []
8838
+ },
8839
+ governance_rule_config: { section: "govern.rules", record: "rule", keys: ["ruleId"] }
8840
+ };
8841
+ var COLLISION_DETAIL_KEYS = [
8842
+ "severity",
8843
+ "scale",
8844
+ "prefer",
8845
+ "enabled",
8846
+ "appliesTo",
8847
+ "except",
8848
+ "properties",
8849
+ "because"
8850
+ ];
8851
+ function renderFactFields(fact, keys) {
8852
+ return keys.filter((key) => fact[key] !== void 0).map((key) => `${key}=${Array.isArray(fact[key]) ? JSON.stringify(fact[key]) : fact[key]}`).join(" ");
8853
+ }
8854
+ function collidingRecordDiagnostic(conflict) {
8855
+ return collidingRecordDiagnostics([conflict])[0] ?? null;
8856
+ }
8857
+ function collidingRecordDiagnostics(conflicts) {
8858
+ const groups = /* @__PURE__ */ new Map();
8859
+ for (const conflict of conflicts) {
8860
+ const shape = CONFIG_RECORD_FACT_KINDS[conflict.kept.kind];
8861
+ if (!shape || conflict.kept.kind !== conflict.skipped.kind) continue;
8862
+ const keptFact = conflict.kept;
8863
+ const skippedFact = conflict.skipped;
8864
+ const identity = renderFactFields(keptFact, shape.keys);
8865
+ const path = `${shape.section}[${identity ? `${shape.record} ${identity}` : shape.record}]`;
8866
+ const group = groups.get(path) ?? {
8867
+ record: shape.record,
8868
+ kept: renderFactFields(keptFact, COLLISION_DETAIL_KEYS),
8869
+ dropped: [],
8870
+ keptFallback: "the first record"
8871
+ };
8872
+ const dropped = renderFactFields(skippedFact, COLLISION_DETAIL_KEYS);
8873
+ group.dropped.push(dropped || `record ${group.dropped.length + 2}`);
8874
+ groups.set(path, group);
8875
+ }
8876
+ return [...groups].map(([path, group]) => ({
8877
+ code: "FUI9007",
8878
+ kind: "colliding-record",
8879
+ severity: "warn",
8880
+ path,
8881
+ message: `${path} has ${group.dropped.length + 1} \`${group.record}\` records that compile to the same policy fact \u2014 enforcing ${group.kept || group.keptFallback} and dropping ${group.dropped.join("; ")}. Author one record for this policy, or scope them apart, so the enforced setting is the one you can read.`
8882
+ }));
8883
+ }
8884
+ function configRecordShape(kind) {
8885
+ const shape = CONFIG_RECORD_FACT_KINDS[kind];
8886
+ return shape ? { section: shape.section, record: shape.record } : null;
8887
+ }
8888
+ function overriddenRecordSeverityDiagnostic(input) {
8889
+ const path = `${input.section}[${input.record}]`;
8890
+ return {
8891
+ code: "FUI9008",
8892
+ kind: "overridden-record-severity",
8893
+ severity: "warn",
8894
+ path,
8895
+ message: `${path} authored severity=${input.authored}, but ${input.source} enforces severity=${input.enforced} on \`${input.ruleId}\`. The record's severity is not what gates CI \u2014 drop the rule override, or author the record at the severity you want enforced.`
8896
+ };
8897
+ }
8700
8898
  function collectStrippedKeys(authored, parsed, path, diagnostics) {
8701
8899
  if (Array.isArray(authored)) {
8702
8900
  if (!Array.isArray(parsed)) return;
@@ -8786,6 +8984,41 @@ function detectUnconsumedConfigKeys(authoredConfig, parsedConfig) {
8786
8984
  collectPassthroughRecordValues(authoredConfig, ["govern", "runners"], /* @__PURE__ */ new Set(), diagnostics);
8787
8985
  return stableConfigDiagnostics(diagnostics);
8788
8986
  }
8987
+ function authoredPolicyExcludes(policy) {
8988
+ const out = [];
8989
+ const collect = (path, scope, raw) => {
8990
+ for (const exclude of normalizePolicyExcludes(raw) ?? []) {
8991
+ out.push({ path, scope, exclude });
8992
+ }
8993
+ };
8994
+ (policy?.styles ?? []).forEach((record, index) => {
8995
+ collect(`govern.styles[${index}].exclude`, record.kind, record.exclude);
8996
+ });
8997
+ (policy?.jsx ?? []).forEach((record, index) => {
8998
+ collect(`govern.jsx[${index}].exclude`, record.kind, record.exclude);
8999
+ });
9000
+ for (const ruleId of Object.keys(policy?.rules ?? {}).sort()) {
9001
+ const record = objectRecord(policy?.rules?.[ruleId]);
9002
+ if (!record) continue;
9003
+ collect(`govern.rules.${ruleId}.exclude`, ruleId, record.exclude);
9004
+ }
9005
+ return out;
9006
+ }
9007
+ function detectUnmatchedPolicyExcludes(policy, scannedFiles) {
9008
+ if (scannedFiles.length === 0) return [];
9009
+ const diagnostics = [];
9010
+ for (const { path, scope, exclude } of authoredPolicyExcludes(policy)) {
9011
+ if (scannedFiles.some((file) => policyExcludeMatchesPath(exclude, file))) continue;
9012
+ diagnostics.push({
9013
+ code: "FUI9006",
9014
+ kind: "unmatched-exclude",
9015
+ severity: "warn",
9016
+ path,
9017
+ message: `${path} pattern "${exclude.glob}" matched no scanned file, so ${scope} is not actually scoped by it. Correct the glob to a path this scan covers, or remove it.`
9018
+ });
9019
+ }
9020
+ return stableConfigDiagnostics(diagnostics);
9021
+ }
8789
9022
  function effectiveScaleBindings(policy) {
8790
9023
  const bindings = [];
8791
9024
  for (const style of policy?.styles ?? []) {
@@ -8982,6 +9215,19 @@ function canonicalBridgeContractMappings(bridges) {
8982
9215
  }
8983
9216
  }));
8984
9217
  }
9218
+ function canonicalBridgeIdentityBindings(bridges) {
9219
+ return (bridges ?? []).map((bridge) => ({
9220
+ source: {
9221
+ kind: "npm",
9222
+ specifier: bridge.underlying.packageName,
9223
+ include: [bridge.underlying.exportName]
9224
+ },
9225
+ localComponentKey: bridge.local.componentKey
9226
+ }));
9227
+ }
9228
+ function canonicalBridgeIdentitySources(bridges) {
9229
+ return canonicalBridgeIdentityBindings(bridges).map((binding) => binding.source);
9230
+ }
8985
9231
 
8986
9232
  // src/effective-governance-inputs.ts
8987
9233
  var EFFECTIVE_GOVERNANCE_INPUTS_SCHEMA = "effective-governance-inputs:v1";
@@ -9548,6 +9794,8 @@ export {
9548
9794
  REGISTRY_INSTALL_RECEIPT_SCHEMA_VERSION,
9549
9795
  REGISTRY_MANIFEST_SCHEMA_VERSION,
9550
9796
  RULES,
9797
+ RULE_FAMILY_IDS,
9798
+ RULE_FAMILY_MEMBERS,
9551
9799
  RULE_TIER,
9552
9800
  SEVERITIES,
9553
9801
  SEVERITY_RANK,
@@ -9585,6 +9833,8 @@ export {
9585
9833
  calculateDeltaE,
9586
9834
  canBlock,
9587
9835
  canonicalBridgeContractMappings,
9836
+ canonicalBridgeIdentityBindings,
9837
+ canonicalBridgeIdentitySources,
9588
9838
  canonicalBridgeV1Schema,
9589
9839
  canonicalDirectionConflictsTarget,
9590
9840
  canonicalJson,
@@ -9599,6 +9849,8 @@ export {
9599
9849
  classifyComplexity,
9600
9850
  classifyIdentity,
9601
9851
  collectRegistryInstallDependencies,
9852
+ collidingRecordDiagnostic,
9853
+ collidingRecordDiagnostics,
9602
9854
  colorSimilarity,
9603
9855
  compareByVocabularyRank,
9604
9856
  compareColors,
@@ -9623,6 +9875,7 @@ export {
9623
9875
  compositionPatternSchema,
9624
9876
  computeRegistryHash,
9625
9877
  configDeclarationForDiagnostics,
9878
+ configRecordShape,
9626
9879
  containsTailwindV4Theme,
9627
9880
  contractComponentsFromFragments,
9628
9881
  contractHash,
@@ -9633,14 +9886,17 @@ export {
9633
9886
  defineConfig,
9634
9887
  defineFragment,
9635
9888
  defineRecipe,
9889
+ describePolicyExclude,
9636
9890
  detectOrphanGovernanceScales,
9637
9891
  detectSubComponentPaths,
9638
9892
  detectUnconsumedConfigKeys,
9893
+ detectUnmatchedPolicyExcludes,
9639
9894
  diffContractDomains,
9640
9895
  discoverComponents,
9641
9896
  dtcgTokenFileSchema,
9642
9897
  emptyConformResult,
9643
9898
  evaluateGovernanceIntegrity,
9899
+ excludeGlobToRegExp,
9644
9900
  executeVariantLoaders,
9645
9901
  explainUrlForCode,
9646
9902
  factEvidenceSchema,
@@ -9713,8 +9969,10 @@ export {
9713
9969
  isFigmaPropMapping,
9714
9970
  isForceIncluded,
9715
9971
  isPortableRepoPath,
9972
+ isPresetSourcedRule,
9716
9973
  isRawHtmlAdvisoryTier,
9717
9974
  isReactComponent,
9975
+ isRuleFamilyId,
9718
9976
  localCanonicalContractMappings,
9719
9977
  lowerCompositionContract,
9720
9978
  makeA11yNameRequiredFact,
@@ -9758,6 +10016,8 @@ export {
9758
10016
  makeUsageNodeFact,
9759
10017
  makeUsagePropResolvedFact,
9760
10018
  makeUsageTextChildFact,
10019
+ markPresetSourcedRules,
10020
+ matchPolicyExclude,
9761
10021
  matchesGlob,
9762
10022
  maxSeverity,
9763
10023
  mcpBlockSchema,
@@ -9783,8 +10043,10 @@ export {
9783
10043
  nearestSignedScaleValue,
9784
10044
  normalizeColor,
9785
10045
  normalizeConfigPath,
10046
+ normalizeExcludePath,
9786
10047
  normalizeFinding,
9787
10048
  normalizeGovernanceConfig,
10049
+ normalizePolicyExcludes,
9788
10050
  normalizeRegistryRepoPath,
9789
10051
  normalizeSeverity,
9790
10052
  normalizeStyleValue,
@@ -9792,6 +10054,7 @@ export {
9792
10054
  normalizeTokenGroupComment,
9793
10055
  normalizeTokenValue,
9794
10056
  normalizeViolation,
10057
+ overriddenRecordSeverityDiagnostic,
9795
10058
  ownedImportEquivalents,
9796
10059
  parseColor,
9797
10060
  parseColorToRgb,
@@ -9807,9 +10070,12 @@ export {
9807
10070
  parseTailwindV4Theme,
9808
10071
  parseTokenFile,
9809
10072
  parseTokens,
10073
+ policyExcludeMatchesPath,
10074
+ policyExcludeSchema,
9810
10075
  portableRepoPathError,
9811
10076
  projectCanonicalDirectionConflicts,
9812
10077
  projectContractPreimage,
10078
+ projectSupersededImportPathPreferences,
9813
10079
  projectV1OwnedComponentId,
9814
10080
  projectV1OwnedImportIdentity,
9815
10081
  propDefinitionSchema,
@@ -9853,6 +10119,7 @@ export {
9853
10119
  ruleComponentsPreferLibrary,
9854
10120
  ruleComponentsShadowComponent,
9855
10121
  ruleComponentsUnknownProp,
10122
+ ruleFamilyMembers,
9856
10123
  ruleJsxPreferredComponent,
9857
10124
  ruleJsxPreferredImportPath,
9858
10125
  rulePropsInvalidValue,