@usefragments/core 1.6.0 → 1.7.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 (50) hide show
  1. package/dist/chunk-RANPUC6C.js +72 -0
  2. package/dist/chunk-RANPUC6C.js.map +1 -0
  3. package/dist/{chunk-WVFNDPM4.js → chunk-WNMWKUYG.js} +26 -14
  4. package/dist/chunk-WNMWKUYG.js.map +1 -0
  5. package/dist/codes/index.d.ts +1 -1
  6. package/dist/codes/index.js +1 -1
  7. package/dist/compiled-types/index.d.ts +1 -1
  8. package/dist/compiled-types/index.js +8 -0
  9. package/dist/generate/index.d.ts +1 -1
  10. package/dist/{governance-DxFipN5V.d.ts → governance-D9KtH-vg.d.ts} +9 -3
  11. package/dist/index.d.ts +165 -139
  12. package/dist/index.js +700 -201
  13. package/dist/index.js.map +1 -1
  14. package/dist/react-types.d.ts +1 -1
  15. package/dist/registry.d.ts +36 -36
  16. package/dist/schemas/index.d.ts +1 -1
  17. package/dist/test-utils.d.ts +1 -1
  18. package/package.json +2 -1
  19. package/src/agent-format.test.ts +13 -0
  20. package/src/agent-format.ts +9 -3
  21. package/src/codes/__tests__/codes.test.ts +0 -1
  22. package/src/codes/codes.ts +1 -2
  23. package/src/compiled-types/index.ts +81 -0
  24. package/src/compiled-types/parse.test.ts +47 -0
  25. package/src/facts/builders.ts +13 -0
  26. package/src/facts/compile.ts +13 -13
  27. package/src/facts/facts.test.ts +38 -1
  28. package/src/facts/index.ts +2 -0
  29. package/src/facts/types.ts +15 -0
  30. package/src/governance-integrity.test.ts +98 -1
  31. package/src/governance-integrity.ts +40 -20
  32. package/src/index.ts +8 -0
  33. package/src/rules/a11y-required-accessible-name.ts +175 -28
  34. package/src/rules/a11y-standard.ts +102 -0
  35. package/src/rules/a11y-utils.ts +7 -0
  36. package/src/rules/components-prefer-library.test.ts +75 -28
  37. package/src/rules/components-prefer-library.ts +35 -15
  38. package/src/rules/components-shadow-component.test.ts +21 -9
  39. package/src/rules/emit-gate.test.ts +74 -4
  40. package/src/rules/emit-gate.ts +24 -9
  41. package/src/rules/families.ts +1 -1
  42. package/src/rules/fix-availability.ts +1 -0
  43. package/src/rules/index.ts +12 -2
  44. package/src/rules/rules.test.ts +63 -7
  45. package/src/rules/tiers.ts +1 -0
  46. package/src/tokens/design-token-parser.test.ts +131 -0
  47. package/src/tokens/design-token-parser.ts +362 -49
  48. package/src/types.ts +2 -2
  49. package/dist/chunk-WVFNDPM4.js.map +0 -1
  50. package/dist/{index-DbkPE46t.d.ts → index-hZAlYCli.d.ts} +8 -8
package/dist/index.js CHANGED
@@ -105,6 +105,7 @@ import {
105
105
  makeTailwindUnknownClassEnabledFact,
106
106
  makeThemeDeclarationFact,
107
107
  makeTokenDefinitionFact,
108
+ makeUsageChildContentFact,
108
109
  makeUsageComponentFact,
109
110
  makeUsageImportFact,
110
111
  makeUsageInlineStyleFact,
@@ -126,7 +127,7 @@ import {
126
127
  resolveComponentGovernance,
127
128
  ruleFamilyMembers,
128
129
  scaleGovernanceRecordSchema
129
- } from "./chunk-WVFNDPM4.js";
130
+ } from "./chunk-WNMWKUYG.js";
130
131
  import {
131
132
  AGENT_FORMAT_SCHEMA_VERSION,
132
133
  agentErrorEnvelopeSchema,
@@ -181,6 +182,10 @@ import {
181
182
  severitySchema,
182
183
  sortBySeverity
183
184
  } from "./chunk-V4VQB57N.js";
185
+ import {
186
+ CompiledFragmentsFileValidationError,
187
+ parseCompiledFragmentsFile
188
+ } from "./chunk-RANPUC6C.js";
184
189
 
185
190
  // src/constants.ts
186
191
  var BRAND = {
@@ -420,6 +425,7 @@ function buildAgentFormat(input) {
420
425
  const zeroRulesLoaded = input.integrity.rulesLoaded === 0;
421
426
  const integrityFailed = !input.integrity.healthy || input.integrity.inert;
422
427
  const score = integrityFailed ? 0 : scoreFindings(input.findings);
428
+ const filesAffected = new Set(input.findings.map((finding) => finding.location.file)).size;
423
429
  const visibleFindings = uniqueFindings(input.findings).slice(0, input.maxFindings ?? 50);
424
430
  const agentFindings = visibleFindings.map((finding) => ({
425
431
  code: finding.code,
@@ -437,7 +443,7 @@ function buildAgentFormat(input) {
437
443
  const hasErrors = input.findings.some((finding) => severityLevel(finding.severity) === "error");
438
444
  const shared = {
439
445
  schemaVersion: AGENT_FORMAT_SCHEMA_VERSION,
440
- summary: zeroRulesLoaded ? "indeterminate; zero_rules_loaded" : integrityFailed ? `failed with score ${score}; ${input.integrity.reasons.join("; ") || "governance integrity is unhealthy"}` : agentSummary(input.findings.length, input.filesScanned, score),
446
+ summary: zeroRulesLoaded ? "indeterminate; zero_rules_loaded" : integrityFailed ? `failed with score ${score}; ${input.integrity.reasons.join("; ") || "governance integrity is unhealthy"}` : agentSummary(input.findings.length, filesAffected, input.filesScanned, score),
441
447
  integrity: input.integrity,
442
448
  fix_first: agentFindings.filter((finding) => finding.plan.confidence >= 0.8),
443
449
  remaining: agentFindings.filter((finding) => finding.plan.confidence < 0.8),
@@ -498,9 +504,9 @@ function scoreFindings(findings) {
498
504
  }
499
505
  return Math.max(1, Math.round(100 / (1 + penalty / 40)));
500
506
  }
501
- function agentSummary(count, filesScanned, score) {
507
+ function agentSummary(count, filesAffected, filesScanned, score) {
502
508
  if (count === 0) return `passed with score ${score}; no findings`;
503
- return `${count} finding${count === 1 ? "" : "s"} across ${filesScanned} file${filesScanned === 1 ? "" : "s"}; score ${score}`;
509
+ return `${count} finding${count === 1 ? "" : "s"} in ${filesAffected} file${filesAffected === 1 ? "" : "s"}; ${filesScanned} scanned; score ${score}`;
504
510
  }
505
511
  function planForFinding(finding) {
506
512
  if (finding.fix?.deterministic === true) {
@@ -3302,7 +3308,7 @@ function buildToken(name, rawValue, lineNumber, source) {
3302
3308
  }
3303
3309
 
3304
3310
  // src/tokens/design-token-parser.ts
3305
- var TOKEN_DECLARATION_PATTERN = /--([a-zA-Z0-9_-]+)\s*:\s*([^;{}]+?)\s*(?:;|(?=\})|$)/g;
3311
+ var CSS_CUSTOM_PROPERTY_NAME_CHARACTER = /[a-zA-Z0-9_-]/u;
3306
3312
  var VAR_REFERENCE_PATTERN = /var\(\s*--([a-zA-Z0-9_-]+)(?:\s*,\s*([^)]+))?\s*\)/;
3307
3313
  function parseDesignTokenContent(content, options = {}) {
3308
3314
  const startTime = performance.now();
@@ -3354,19 +3360,48 @@ function parseDesignTokenContent(content, options = {}) {
3354
3360
  errors.push({ message: err, file: filePath });
3355
3361
  }
3356
3362
  }
3357
- const tokensByName = /* @__PURE__ */ new Map();
3358
- for (const match of content.matchAll(TOKEN_DECLARATION_PATTERN)) {
3359
- const [, name, rawValue] = match;
3360
- const fullName = `--${name}`;
3361
- const lineNumber = countNewlines(content, match.index ?? 0) + 1;
3362
- tokensByName.set(fullName, {
3363
- rawValue: rawValue.trim().replace(/\s+/g, " "),
3364
- line: lineNumber
3363
+ const declarationsByThemeAndName = /* @__PURE__ */ new Map();
3364
+ const declarationMatches = scanCssCustomPropertyDeclarations(content);
3365
+ for (const match of declarationMatches) {
3366
+ const fullName = match.name;
3367
+ const lineNumber = match.line;
3368
+ const selector = match.selector;
3369
+ const theme = themeSelectors[selector] || "default";
3370
+ declarationsByThemeAndName.set(`${theme}\0${fullName}`, {
3371
+ name: fullName,
3372
+ rawValue: normalizeCssCustomPropertyValue(match.rawValue),
3373
+ line: lineNumber,
3374
+ selector,
3375
+ theme
3365
3376
  });
3366
3377
  }
3367
- for (const [name, { rawValue, line }] of tokensByName) {
3368
- const selector = findSelectorForLine(content, line ?? 1);
3369
- const theme = themeSelectors[selector] || "default";
3378
+ const declarations = [...declarationsByThemeAndName.values()];
3379
+ const defaults = /* @__PURE__ */ new Map();
3380
+ const declarationsByTheme = /* @__PURE__ */ new Map();
3381
+ for (const declaration of declarations) {
3382
+ if (declaration.theme === "default") defaults.set(declaration.name, declaration);
3383
+ let themeDeclarations = declarationsByTheme.get(declaration.theme);
3384
+ if (!themeDeclarations) {
3385
+ themeDeclarations = /* @__PURE__ */ new Map();
3386
+ declarationsByTheme.set(declaration.theme, themeDeclarations);
3387
+ }
3388
+ themeDeclarations.set(declaration.name, declaration);
3389
+ }
3390
+ const tokensByTheme = /* @__PURE__ */ new Map();
3391
+ for (const [theme, themeDeclarations] of declarationsByTheme) {
3392
+ if (theme === "default") {
3393
+ tokensByTheme.set(theme, defaults);
3394
+ continue;
3395
+ }
3396
+ const tokensByName = new Map(defaults);
3397
+ for (const [name, declaration] of themeDeclarations) {
3398
+ tokensByName.set(name, declaration);
3399
+ }
3400
+ tokensByTheme.set(theme, tokensByName);
3401
+ }
3402
+ const contentLines = content.split("\n");
3403
+ for (const { name, rawValue, line, selector, theme } of declarations) {
3404
+ const tokensByName = tokensByTheme.get(theme) ?? defaults;
3370
3405
  const { resolvedValue, chain, hasCircular, unresolvedRef } = resolveDesignTokenValue(
3371
3406
  rawValue,
3372
3407
  tokensByName
@@ -3388,7 +3423,7 @@ function parseDesignTokenContent(content, options = {}) {
3388
3423
  lineNumber: line,
3389
3424
  theme,
3390
3425
  selector,
3391
- description: extractDescription(content, line ?? 1)
3426
+ description: extractDescription(contentLines, line ?? 1)
3392
3427
  });
3393
3428
  }
3394
3429
  if (tokens.length === 0 && trimmed.length > 0) {
@@ -3403,13 +3438,6 @@ function parseDesignTokenContent(content, options = {}) {
3403
3438
  parseTimeMs: performance.now() - startTime
3404
3439
  };
3405
3440
  }
3406
- function countNewlines(content, index) {
3407
- let count = 0;
3408
- for (let i = 0; i < index && i < content.length; i++) {
3409
- if (content.charCodeAt(i) === 10) count++;
3410
- }
3411
- return count;
3412
- }
3413
3441
  function parsedOutputToDesignTokens(output, filePath) {
3414
3442
  const tokens = [];
3415
3443
  for (const cat of Object.values(output.categories)) {
@@ -3564,29 +3592,284 @@ function inferTokenLevel(name, rawValue, referenceChain) {
3564
3592
  }
3565
3593
  return 2;
3566
3594
  }
3567
- function findSelectorForLine(content, targetLine) {
3568
- const lines = content.split("\n");
3595
+ function scanCssCustomPropertyDeclarations(content) {
3596
+ const declarations = [];
3597
+ const parentSelectors = [];
3569
3598
  let currentSelector = ":root";
3570
- let braceDepth = 0;
3571
- for (let i = 0; i < Math.min(targetLine, lines.length); i++) {
3572
- const line = lines[i];
3573
- const selectorMatch = line.match(/^\s*([^{]+)\s*\{/);
3574
- if (selectorMatch) {
3575
- const selector = selectorMatch[1].trim();
3576
- if ((line.match(/\{/g) || []).length > (line.match(/\}/g) || []).length) {
3577
- currentSelector = selector;
3599
+ let boundary = 0;
3600
+ let statementHasContent = false;
3601
+ let line = 1;
3602
+ let quote = null;
3603
+ let escaped = false;
3604
+ let inComment = false;
3605
+ let parentheses = 0;
3606
+ let brackets = 0;
3607
+ for (let index = 0; index < content.length; index++) {
3608
+ const character = content[index];
3609
+ const next = content[index + 1];
3610
+ if (character === "\n") line++;
3611
+ if (inComment) {
3612
+ if (character === "*" && next === "/") {
3613
+ inComment = false;
3614
+ index++;
3615
+ }
3616
+ continue;
3617
+ }
3618
+ if (quote) {
3619
+ if (escaped) {
3620
+ escaped = false;
3621
+ } else if (character === "\\") {
3622
+ escaped = true;
3623
+ } else if (character === quote) {
3624
+ quote = null;
3625
+ }
3626
+ continue;
3627
+ }
3628
+ if (character === "/" && next === "*") {
3629
+ inComment = true;
3630
+ index++;
3631
+ continue;
3632
+ }
3633
+ if (character === "'" || character === '"') {
3634
+ quote = character;
3635
+ statementHasContent = true;
3636
+ continue;
3637
+ }
3638
+ if (character === "\\") {
3639
+ statementHasContent = true;
3640
+ if (next === "\n") line++;
3641
+ index++;
3642
+ continue;
3643
+ }
3644
+ if (character === "(") {
3645
+ parentheses++;
3646
+ statementHasContent = true;
3647
+ continue;
3648
+ }
3649
+ if (character === ")") {
3650
+ parentheses = Math.max(0, parentheses - 1);
3651
+ statementHasContent = true;
3652
+ continue;
3653
+ }
3654
+ if (character === "[") {
3655
+ brackets++;
3656
+ statementHasContent = true;
3657
+ continue;
3658
+ }
3659
+ if (character === "]") {
3660
+ brackets = Math.max(0, brackets - 1);
3661
+ statementHasContent = true;
3662
+ continue;
3663
+ }
3664
+ if (parentheses > 0 || brackets > 0) {
3665
+ if (!/\s/u.test(character)) statementHasContent = true;
3666
+ continue;
3667
+ }
3668
+ if (character === "{") {
3669
+ const header = stripCssComments(content.slice(boundary, index)).trim();
3670
+ parentSelectors.push(currentSelector);
3671
+ if (header && !header.startsWith("@")) currentSelector = header;
3672
+ boundary = index + 1;
3673
+ statementHasContent = false;
3674
+ } else if (character === "}") {
3675
+ currentSelector = parentSelectors.pop() ?? ":root";
3676
+ boundary = index + 1;
3677
+ statementHasContent = false;
3678
+ } else if (character === ";") {
3679
+ boundary = index + 1;
3680
+ statementHasContent = false;
3681
+ } else if (character === "-" && next === "-" && !statementHasContent) {
3682
+ const declaration = scanCssCustomPropertyAt(content, index);
3683
+ if (!declaration) {
3684
+ statementHasContent = true;
3685
+ continue;
3686
+ }
3687
+ const rawValue = content.slice(declaration.valueStart, declaration.end).trim();
3688
+ if (rawValue) {
3689
+ declarations.push({
3690
+ name: declaration.name,
3691
+ rawValue,
3692
+ line,
3693
+ selector: currentSelector
3694
+ });
3695
+ }
3696
+ line += countNewlinesInRange(content, index + 1, declaration.end);
3697
+ statementHasContent = true;
3698
+ index = declaration.end - 1;
3699
+ } else if (!/\s/u.test(character)) {
3700
+ statementHasContent = true;
3701
+ }
3702
+ }
3703
+ return declarations;
3704
+ }
3705
+ function scanCssCustomPropertyAt(content, start) {
3706
+ let cursor = start + 2;
3707
+ while (cursor < content.length && CSS_CUSTOM_PROPERTY_NAME_CHARACTER.test(content[cursor] ?? "")) {
3708
+ cursor++;
3709
+ }
3710
+ if (cursor === start + 2) return null;
3711
+ const nameEnd = cursor;
3712
+ cursor = skipCssWhitespaceAndComments(content, cursor);
3713
+ if (content[cursor] !== ":") return null;
3714
+ const valueStart = cursor + 1;
3715
+ return {
3716
+ name: content.slice(start, nameEnd),
3717
+ valueStart,
3718
+ end: findCssCustomPropertyValueEnd(content, valueStart)
3719
+ };
3720
+ }
3721
+ function findCssCustomPropertyValueEnd(content, start) {
3722
+ let quote = null;
3723
+ let escaped = false;
3724
+ let inComment = false;
3725
+ let parentheses = 0;
3726
+ let brackets = 0;
3727
+ let braces = 0;
3728
+ for (let index = start; index < content.length; index++) {
3729
+ const character = content[index];
3730
+ const next = content[index + 1];
3731
+ if (inComment) {
3732
+ if (character === "*" && next === "/") {
3733
+ inComment = false;
3734
+ index++;
3735
+ }
3736
+ continue;
3737
+ }
3738
+ if (quote) {
3739
+ if (escaped) {
3740
+ escaped = false;
3741
+ } else if (character === "\\") {
3742
+ escaped = true;
3743
+ } else if (character === quote) {
3744
+ quote = null;
3745
+ }
3746
+ continue;
3747
+ }
3748
+ if (character === "/" && next === "*") {
3749
+ inComment = true;
3750
+ index++;
3751
+ continue;
3752
+ }
3753
+ if (character === "'" || character === '"') {
3754
+ quote = character;
3755
+ continue;
3756
+ }
3757
+ if (character === "\\") {
3758
+ index++;
3759
+ continue;
3760
+ }
3761
+ if (character === "(") {
3762
+ parentheses++;
3763
+ } else if (character === ")") {
3764
+ parentheses = Math.max(0, parentheses - 1);
3765
+ } else if (character === "[") {
3766
+ brackets++;
3767
+ } else if (character === "]") {
3768
+ brackets = Math.max(0, brackets - 1);
3769
+ } else if (character === "{") {
3770
+ braces++;
3771
+ } else if (character === "}") {
3772
+ if (braces > 0) {
3773
+ braces--;
3774
+ } else if (parentheses === 0 && brackets === 0) {
3775
+ return index;
3776
+ }
3777
+ } else if (character === ";" && parentheses === 0 && brackets === 0 && braces === 0) {
3778
+ return index;
3779
+ }
3780
+ }
3781
+ return content.length;
3782
+ }
3783
+ function skipCssWhitespaceAndComments(content, start) {
3784
+ let cursor = start;
3785
+ while (cursor < content.length) {
3786
+ if (/\s/u.test(content[cursor] ?? "")) {
3787
+ cursor++;
3788
+ continue;
3789
+ }
3790
+ if (content[cursor] !== "/" || content[cursor + 1] !== "*") break;
3791
+ const commentEnd = content.indexOf("*/", cursor + 2);
3792
+ if (commentEnd < 0) return content.length;
3793
+ cursor = commentEnd + 2;
3794
+ }
3795
+ return cursor;
3796
+ }
3797
+ function stripCssComments(content) {
3798
+ let output = "";
3799
+ let quote = null;
3800
+ let escaped = false;
3801
+ let inComment = false;
3802
+ for (let index = 0; index < content.length; index++) {
3803
+ const character = content[index];
3804
+ const next = content[index + 1];
3805
+ if (inComment) {
3806
+ if (character === "*" && next === "/") {
3807
+ inComment = false;
3808
+ index++;
3578
3809
  }
3810
+ continue;
3579
3811
  }
3580
- braceDepth += (line.match(/\{/g) || []).length;
3581
- braceDepth -= (line.match(/\}/g) || []).length;
3582
- if (braceDepth === 0) {
3583
- currentSelector = ":root";
3812
+ if (quote) {
3813
+ output += character;
3814
+ if (escaped) {
3815
+ escaped = false;
3816
+ } else if (character === "\\") {
3817
+ escaped = true;
3818
+ } else if (character === quote) {
3819
+ quote = null;
3820
+ }
3821
+ continue;
3822
+ }
3823
+ if (character === "/" && next === "*") {
3824
+ inComment = true;
3825
+ index++;
3826
+ continue;
3584
3827
  }
3828
+ output += character;
3829
+ if (character === "'" || character === '"') quote = character;
3585
3830
  }
3586
- return currentSelector;
3831
+ return output;
3587
3832
  }
3588
- function extractDescription(content, line) {
3589
- const lines = content.split("\n");
3833
+ function countNewlinesInRange(content, start, end) {
3834
+ let count = 0;
3835
+ for (let index = start; index < end; index++) {
3836
+ if (content.charCodeAt(index) === 10) count++;
3837
+ }
3838
+ return count;
3839
+ }
3840
+ function normalizeCssCustomPropertyValue(value) {
3841
+ let output = "";
3842
+ let quote = null;
3843
+ let escaped = false;
3844
+ let pendingWhitespace = false;
3845
+ for (const character of value.trim()) {
3846
+ if (quote) {
3847
+ output += character;
3848
+ if (escaped) {
3849
+ escaped = false;
3850
+ } else if (character === "\\") {
3851
+ escaped = true;
3852
+ } else if (character === quote) {
3853
+ quote = null;
3854
+ }
3855
+ continue;
3856
+ }
3857
+ if (character === "'" || character === '"') {
3858
+ if (pendingWhitespace && output) output += " ";
3859
+ pendingWhitespace = false;
3860
+ quote = character;
3861
+ output += character;
3862
+ } else if (/\s/u.test(character)) {
3863
+ pendingWhitespace = true;
3864
+ } else {
3865
+ if (pendingWhitespace && output) output += " ";
3866
+ pendingWhitespace = false;
3867
+ output += character;
3868
+ }
3869
+ }
3870
+ return output;
3871
+ }
3872
+ function extractDescription(lines, line) {
3590
3873
  if (line <= 1) return void 0;
3591
3874
  const prevLine = lines[line - 2]?.trim();
3592
3875
  const singleLineMatch = prevLine?.match(/\/\/\s*(.+)$/);
@@ -4382,6 +4665,13 @@ function discoverComponents(moduleExports, options) {
4382
4665
  return names.sort();
4383
4666
  }
4384
4667
 
4668
+ // src/rules/a11y-utils.ts
4669
+ function isAriaHidden(props) {
4670
+ const prop = props.find((candidate) => candidate.prop === "aria-hidden");
4671
+ if (!prop || prop.resolution !== "static") return false;
4672
+ return prop.value === true || prop.value === "true";
4673
+ }
4674
+
4385
4675
  // src/rules/finding.ts
4386
4676
  function makeFinding(input) {
4387
4677
  if (input.evidence.length === 0) {
@@ -4840,36 +5130,71 @@ function isApplicableTokenReference(referenceFormat) {
4840
5130
 
4841
5131
  // src/rules/a11y-required-accessible-name.ts
4842
5132
  var RULE_ID = "a11y/required-accessible-name";
4843
- var RULE_VERSION = "1";
5133
+ var RULE_VERSION = "2";
4844
5134
  var NAME_SOURCE_PROPS = /* @__PURE__ */ new Set(["aria-label", "aria-labelledby", "title"]);
5135
+ var COMPONENT_NAME_SOURCE_PROPS = /* @__PURE__ */ new Set([...NAME_SOURCE_PROPS, "label"]);
5136
+ var NAME_REQUIRED_TAGS = /* @__PURE__ */ new Set(["button", "input", "select", "textarea"]);
5137
+ var NAME_REQUIRED_ROLES = /* @__PURE__ */ new Set(["button", "checkbox", "radio", "switch", "tab"]);
5138
+ var NAME_FROM_CONTENT_TAGS = /* @__PURE__ */ new Set(["button"]);
5139
+ var NAME_FROM_CONTENT_ROLES = /* @__PURE__ */ new Set(["button", "checkbox", "radio", "switch", "tab"]);
5140
+ var NAME_REQUIRED_COMPONENT_SUFFIXES = [
5141
+ "button",
5142
+ "input",
5143
+ "select",
5144
+ "textarea",
5145
+ "checkbox",
5146
+ "radio",
5147
+ "switch"
5148
+ ];
4845
5149
  function ruleA11yRequiredAccessibleName(ix) {
4846
5150
  const findings = [];
4847
5151
  const propsByNode = indexPropsByNodeId(ix);
4848
5152
  const textByNode = indexTextChildrenByNodeId(ix);
4849
- for (const usageComponent of ix.byKind("usage_component")) {
4850
- const policy = ix.policy.a11yNameRequired(usageComponent.componentId);
4851
- if (!policy) continue;
4852
- const node = readUsageNode(ix, usageComponent.nodeId);
4853
- if (!node) continue;
5153
+ const childContentByNode = new Map(
5154
+ ix.byKind("usage_child_content").map((content) => [content.nodeId, content])
5155
+ );
5156
+ const globalPolicy = ix.policy.ruleConfig(RULE_ID);
5157
+ const usageByNode = new Map(ix.byKind("usage_component").map((usage) => [usage.nodeId, usage]));
5158
+ const definitions = new Map(
5159
+ ix.byKind("component_definition").map((definition) => [definition.componentKey, definition])
5160
+ );
5161
+ for (const node of ix.byKind("usage_node")) {
5162
+ const usageComponent = usageByNode.get(node.id);
5163
+ const componentPolicy = usageComponent ? ix.policy.a11yNameRequired(usageComponent.componentId) : void 0;
4854
5164
  const props = propsByNode.get(node.id) ?? [];
4855
- if (props.some(isAriaHidden)) continue;
4856
- if (hasAccessibleName(props, textByNode.get(node.id) ?? [])) continue;
5165
+ if (isAriaHidden(props)) continue;
5166
+ const genericControl = globalPolicy?.enabled === true && requiresAccessibleName(node, usageComponent, definitions, props);
5167
+ if (!componentPolicy && !genericControl) continue;
5168
+ if (hasAccessibleName(
5169
+ props,
5170
+ textByNode.get(node.id) ?? [],
5171
+ childContentByNode.get(node.id),
5172
+ usageComponent !== void 0,
5173
+ canNameFromContent(node, usageComponent, definitions)
5174
+ )) {
5175
+ continue;
5176
+ }
4857
5177
  findings.push(
4858
5178
  makeFinding({
4859
5179
  ruleId: RULE_ID,
4860
5180
  ruleVersion: RULE_VERSION,
4861
- severity: policy.severity,
4862
- message: `<${node.element}> has no accessible name. ${policy.because}`.trim(),
5181
+ severity: componentPolicy?.severity ?? globalPolicy?.severity ?? "warn",
5182
+ message: `<${node.element}> has no accessible name. ${componentPolicy?.because ?? "Add visible text, aria-label, aria-labelledby, or a component label."}`.trim(),
4863
5183
  location: node.location,
4864
- evidence: ix.evidence([node.id, usageComponent.id, policy.id]),
5184
+ evidence: ix.evidence([
5185
+ node.id,
5186
+ ...usageComponent ? [usageComponent.id] : [],
5187
+ ...componentPolicy ? [componentPolicy.id] : [],
5188
+ ...genericControl && globalPolicy ? [globalPolicy.id] : []
5189
+ ]),
4865
5190
  fingerprintIdentity: {
4866
5191
  file: node.file,
4867
- componentId: usageComponent.componentId,
5192
+ componentId: usageComponent?.componentId,
4868
5193
  element: node.element,
4869
5194
  nodePath: node.nodePath
4870
5195
  },
4871
5196
  attributes: {
4872
- componentId: usageComponent.componentId,
5197
+ ...usageComponent ? { componentId: usageComponent.componentId } : {},
4873
5198
  element: node.element
4874
5199
  }
4875
5200
  })
@@ -4877,12 +5202,18 @@ function ruleA11yRequiredAccessibleName(ix) {
4877
5202
  }
4878
5203
  return findings;
4879
5204
  }
4880
- function hasAccessibleName(props, textChildren) {
4881
- for (const child of textChildren) {
4882
- if (typeof child.text === "string" && child.text.trim().length > 0) return true;
5205
+ function hasAccessibleName(props, textChildren, childContent, componentControl, nameFromContent) {
5206
+ const sourceProps = componentControl ? COMPONENT_NAME_SOURCE_PROPS : NAME_SOURCE_PROPS;
5207
+ if (nameFromContent) {
5208
+ for (const child of textChildren) {
5209
+ if (typeof child.text === "string" && child.text.trim().length > 0) return true;
5210
+ }
5211
+ if (childContent?.content === "static-text" || childContent?.content === "runtime") {
5212
+ return true;
5213
+ }
4883
5214
  }
4884
5215
  for (const prop of props) {
4885
- if (!NAME_SOURCE_PROPS.has(prop.prop)) continue;
5216
+ if (!sourceProps.has(prop.prop)) continue;
4886
5217
  if (prop.resolution === "spread") continue;
4887
5218
  if (prop.resolution === "dynamic") return true;
4888
5219
  if (prop.resolution === "static") {
@@ -4892,10 +5223,137 @@ function hasAccessibleName(props, textChildren) {
4892
5223
  }
4893
5224
  return false;
4894
5225
  }
4895
- function isAriaHidden(prop) {
4896
- if (prop.prop !== "aria-hidden") return false;
4897
- if (prop.resolution !== "static") return false;
4898
- return prop.value === true || prop.value === "true";
5226
+ function canNameFromContent(node, usage, definitions) {
5227
+ const element = node.element.toLowerCase();
5228
+ if (NAME_REQUIRED_TAGS.has(element)) return NAME_FROM_CONTENT_TAGS.has(element);
5229
+ if (node.role && NAME_FROM_CONTENT_ROLES.has(node.role.toLowerCase())) return true;
5230
+ if (!usage) return false;
5231
+ const definition = definitions.get(String(usage.componentId));
5232
+ if (definition) {
5233
+ const root = definition.renderRoot;
5234
+ if (root.resolution === "intrinsic") {
5235
+ return intrinsicCanNameFromContent(root.tag, root.role);
5236
+ }
5237
+ if (root.resolution === "canonical") return isContentNamedControl(root.canonical);
5238
+ if (root.resolution === "mixed") {
5239
+ return root.intrinsics.some((tag) => intrinsicCanNameFromContent(tag)) || root.canonicals.some(isContentNamedControl);
5240
+ }
5241
+ }
5242
+ return isContentNamedControl(String(usage.componentId));
5243
+ }
5244
+ function intrinsicCanNameFromContent(tag, role) {
5245
+ const normalizedTag = tag.toLowerCase();
5246
+ if (NAME_REQUIRED_TAGS.has(normalizedTag)) {
5247
+ return NAME_FROM_CONTENT_TAGS.has(normalizedTag);
5248
+ }
5249
+ return role !== void 0 && NAME_FROM_CONTENT_ROLES.has(role.toLowerCase());
5250
+ }
5251
+ function requiresAccessibleName(node, usage, definitions, props) {
5252
+ if (node.role && NAME_REQUIRED_ROLES.has(node.role.toLowerCase())) return true;
5253
+ if (NAME_REQUIRED_TAGS.has(node.element.toLowerCase())) {
5254
+ if (node.element.toLowerCase() === "input" && props.some(
5255
+ (prop) => prop.prop === "type" && prop.resolution === "static" && String(prop.value).toLowerCase() === "hidden"
5256
+ )) {
5257
+ return false;
5258
+ }
5259
+ return true;
5260
+ }
5261
+ if (!usage) return false;
5262
+ const definition = definitions.get(String(usage.componentId));
5263
+ if (definition) {
5264
+ const root = definition.renderRoot;
5265
+ if (root.resolution === "intrinsic") {
5266
+ return NAME_REQUIRED_TAGS.has(root.tag.toLowerCase()) || root.role !== void 0 && NAME_REQUIRED_ROLES.has(root.role.toLowerCase());
5267
+ }
5268
+ if (root.resolution === "canonical") return isNamedControl(root.canonical);
5269
+ if (root.resolution === "mixed") {
5270
+ return root.intrinsics.some((tag) => NAME_REQUIRED_TAGS.has(tag.toLowerCase())) || root.canonicals.some(isNamedControl);
5271
+ }
5272
+ }
5273
+ return isNamedControl(String(usage.componentId));
5274
+ }
5275
+ function isContentNamedControl(identity) {
5276
+ return normalizedControlName(identity).endsWith("button");
5277
+ }
5278
+ function isNamedControl(identity) {
5279
+ const normalized = normalizedControlName(identity);
5280
+ return NAME_REQUIRED_COMPONENT_SUFFIXES.some((suffix) => normalized.endsWith(suffix));
5281
+ }
5282
+ function normalizedControlName(identity) {
5283
+ const exported = identity.split("#").at(-1)?.split(".").at(-1) ?? identity;
5284
+ return exported.replace(/[^a-z0-9]/gi, "").toLowerCase();
5285
+ }
5286
+
5287
+ // src/rules/a11y-standard.ts
5288
+ var RULE_ID2 = "a11y/standard";
5289
+ var RULE_VERSION2 = "1";
5290
+ var KEY_HANDLERS = /* @__PURE__ */ new Set(["onKeyDown", "onKeyUp", "onKeyPress"]);
5291
+ function ruleA11yStandard(ix) {
5292
+ const policy = ix.policy.ruleConfig(RULE_ID2);
5293
+ if (!policy?.enabled) return [];
5294
+ const propsByNode = indexPropsByNodeId(ix);
5295
+ const findings = [];
5296
+ for (const node of ix.byKind("usage_node")) {
5297
+ if (node.role?.trim().toLowerCase() !== "button") continue;
5298
+ const props = propsByNode.get(node.id) ?? [];
5299
+ if (isAriaHidden(props) || isNativeButton(node.element, props)) continue;
5300
+ const missing = [
5301
+ ...hasFocusableTabIndex(props) ? [] : ["tabIndex"],
5302
+ ...hasKeyHandler(props) ? [] : ["keyboard handler"]
5303
+ ];
5304
+ if (missing.length === 0) continue;
5305
+ findings.push(
5306
+ makeFinding({
5307
+ ruleId: RULE_ID2,
5308
+ ruleVersion: RULE_VERSION2,
5309
+ severity: policy.severity ?? "warn",
5310
+ message: `<${node.element} role="button"> is missing ${missing.join(
5311
+ " and "
5312
+ )}. Non-native buttons need tabIndex plus keyboard activation handling.`,
5313
+ location: node.location,
5314
+ evidence: ix.evidence([
5315
+ node.id,
5316
+ policy.id,
5317
+ ...props.filter(
5318
+ (prop) => prop.prop === "tabIndex" || prop.prop === "tabindex" || KEY_HANDLERS.has(prop.prop)
5319
+ ).map((prop) => prop.id)
5320
+ ]),
5321
+ fingerprintIdentity: {
5322
+ file: node.file,
5323
+ element: node.element,
5324
+ nodePath: node.nodePath,
5325
+ standard: "keyboard-role-button"
5326
+ },
5327
+ attributes: {
5328
+ standard: "keyboard-role-button",
5329
+ role: "button",
5330
+ missing
5331
+ }
5332
+ })
5333
+ );
5334
+ }
5335
+ return findings;
5336
+ }
5337
+ function isNativeButton(element, props) {
5338
+ if (element.toLowerCase() === "button") return true;
5339
+ if (element.toLowerCase() !== "input") return false;
5340
+ return props.some(
5341
+ (prop) => prop.prop === "type" && prop.resolution === "static" && ["button", "submit", "reset"].includes(String(prop.value).toLowerCase())
5342
+ );
5343
+ }
5344
+ function hasFocusableTabIndex(props) {
5345
+ return props.some((prop) => {
5346
+ if (prop.prop !== "tabIndex" && prop.prop !== "tabindex") return false;
5347
+ if (prop.resolution === "dynamic") return true;
5348
+ if (prop.resolution !== "static") return false;
5349
+ const value = typeof prop.value === "number" ? prop.value : Number(prop.value);
5350
+ return Number.isFinite(value) && value >= 0;
5351
+ });
5352
+ }
5353
+ function hasKeyHandler(props) {
5354
+ return props.some(
5355
+ (prop) => KEY_HANDLERS.has(prop.prop) && prop.resolution !== "spread" && prop.value !== null && prop.value !== false
5356
+ );
4899
5357
  }
4900
5358
 
4901
5359
  // src/raw-html-canonical.ts
@@ -5080,8 +5538,8 @@ function decisionTargetsExport(target, exportName) {
5080
5538
  }
5081
5539
 
5082
5540
  // src/rules/components-prefer-library.ts
5083
- var RULE_ID2 = "components/prefer-library";
5084
- var RULE_VERSION2 = "1";
5541
+ var RULE_ID3 = "components/prefer-library";
5542
+ var RULE_VERSION3 = "1";
5085
5543
  function isExplicitTagDeclaration(mapping) {
5086
5544
  return mapping.declaredHtmlEquivalent === true;
5087
5545
  }
@@ -5220,7 +5678,7 @@ function findClassNameReimpl(node, classTokensByNode, targets) {
5220
5678
  return null;
5221
5679
  }
5222
5680
  function ruleComponentsPreferLibrary(ix) {
5223
- const policy = ix.policy.ruleConfig(RULE_ID2);
5681
+ const policy = ix.policy.ruleConfig(RULE_ID3);
5224
5682
  if (!policy?.enabled) return [];
5225
5683
  const configuredSources = canonicalSourcesFromPolicy(policy.options?.canonicalSources);
5226
5684
  const conflicts = projectCanonicalDirectionConflicts(ix, configuredSources);
@@ -5235,7 +5693,7 @@ function ruleComponentsPreferLibrary(ix) {
5235
5693
  const classTokensByNode = indexClassTokensByNode(ix);
5236
5694
  const reimplTargets = canonicalReimplTargets(sources, mappings);
5237
5695
  const canonicalDirectoryResolvedNodes = indexCanonicalDirectoryResolvedNodes(ix, sources);
5238
- const shadowRenderRootNodeIds = indexShadowRenderRootNodeIds(ix);
5696
+ const shadowRenderRootNodeIds = ix.policy.ruleConfig("components/shadow-component")?.enabled === true ? indexShadowRenderRootNodeIds(ix) : /* @__PURE__ */ new Set();
5239
5697
  const findings = [];
5240
5698
  const seenImportFixes = /* @__PURE__ */ new Set();
5241
5699
  for (const node of ix.byKind("usage_node")) {
@@ -5267,8 +5725,8 @@ function ruleComponentsPreferLibrary(ix) {
5267
5725
  seenImportFixes.add(key);
5268
5726
  findings.push(
5269
5727
  makeFinding({
5270
- ruleId: RULE_ID2,
5271
- ruleVersion: RULE_VERSION2,
5728
+ ruleId: RULE_ID3,
5729
+ ruleVersion: RULE_VERSION3,
5272
5730
  severity: capAdvisorySeverity(policy.severity),
5273
5731
  message: `<${node.element}> may need to import from ${importPath}; verify that the local component contract is compatible first.`,
5274
5732
  location: imported.location,
@@ -5302,8 +5760,8 @@ function ruleComponentsPreferLibrary(ix) {
5302
5760
  if (node.element === suggestedComponent2) continue;
5303
5761
  findings.push(
5304
5762
  makeFinding({
5305
- ruleId: RULE_ID2,
5306
- ruleVersion: RULE_VERSION2,
5763
+ ruleId: RULE_ID3,
5764
+ ruleVersion: RULE_VERSION3,
5307
5765
  severity: severityForTier(policy.severity, precisionTier2),
5308
5766
  message: node.element === "button" ? `Bespoke <button> has a library equivalent. Swap to <${suggestedComponent2}> from ${suggestedImport2}.` : roleMatch ? `Custom <${node.element} role="${node.role}"> reimplements a primitive. Use <${suggestedComponent2}> from ${suggestedImport2}.` : `Bespoke <${node.element}> should use <${suggestedComponent2}> from ${suggestedImport2}.`,
5309
5767
  location: node.location,
@@ -5354,8 +5812,8 @@ function ruleComponentsPreferLibrary(ix) {
5354
5812
  const suggestedImport2 = reimpl.import ?? "the canonical library";
5355
5813
  findings.push(
5356
5814
  makeFinding({
5357
- ruleId: RULE_ID2,
5358
- ruleVersion: RULE_VERSION2,
5815
+ ruleId: RULE_ID3,
5816
+ ruleVersion: RULE_VERSION3,
5359
5817
  severity: capAdvisorySeverity(policy.severity),
5360
5818
  message: `<${node.element} class="${reimpl.matchedClass}"> looks like a hand-rolled <${reimpl.canonical}>. Use <${reimpl.canonical}> from ${suggestedImport2} instead of restyling a <${node.element}>.`,
5361
5819
  location: node.location,
@@ -5395,8 +5853,8 @@ function ruleComponentsPreferLibrary(ix) {
5395
5853
  seenImportFixes.add(key);
5396
5854
  findings.push(
5397
5855
  makeFinding({
5398
- ruleId: RULE_ID2,
5399
- ruleVersion: RULE_VERSION2,
5856
+ ruleId: RULE_ID3,
5857
+ ruleVersion: RULE_VERSION3,
5400
5858
  severity: capAdvisorySeverity(policy.severity),
5401
5859
  message: `<${node.element}> may need to import from ${importPath}; verify that the local component contract is compatible first.`,
5402
5860
  location: imported.location,
@@ -5438,14 +5896,15 @@ function ruleComponentsPreferLibrary(ix) {
5438
5896
  continue;
5439
5897
  }
5440
5898
  }
5441
- const suggestedImport = sourceLabel(suggestion);
5899
+ const importReady = suggestion.kind !== "directory";
5900
+ const suggestedImport = importReady ? sourceLabel(suggestion) : "the canonical component directory (resolve its configured import alias)";
5442
5901
  const precisionTier = builtIn?.tier ?? "exact-html";
5443
5902
  const rawElement = formatRawElement(node, props);
5444
5903
  findings.push(
5445
5904
  makeFinding({
5446
- ruleId: RULE_ID2,
5447
- ruleVersion: RULE_VERSION2,
5448
- severity: severityForTier(policy.severity, precisionTier),
5905
+ ruleId: RULE_ID3,
5906
+ ruleVersion: RULE_VERSION3,
5907
+ severity: importReady ? severityForTier(policy.severity, precisionTier) : capAdvisorySeverity(policy.severity),
5449
5908
  message: messageForBuiltInMatch({
5450
5909
  node,
5451
5910
  rawElement,
@@ -5462,7 +5921,7 @@ function ruleComponentsPreferLibrary(ix) {
5462
5921
  suggestedComponent,
5463
5922
  suggestedImport
5464
5923
  },
5465
- ...!isRawHtmlAdvisoryTier(precisionTier) && isIdentifier(node.element) && isIdentifier(suggestedComponent) ? {
5924
+ ...importReady && !isRawHtmlAdvisoryTier(precisionTier) && isIdentifier(node.element) && isIdentifier(suggestedComponent) ? {
5466
5925
  fix: {
5467
5926
  kind: "replaceComponent",
5468
5927
  title: `Replace ${rawElement} with <${suggestedComponent}>`,
@@ -5477,13 +5936,13 @@ function ruleComponentsPreferLibrary(ix) {
5477
5936
  attributes: {
5478
5937
  rawValue: node.element,
5479
5938
  suggestedComponent,
5480
- suggestedImport,
5481
5939
  suggestedImportSourceKind: suggestion.kind,
5940
+ ...importReady ? { suggestedImport } : { canonicalDirectory: suggestion.path, importReady: false },
5482
5941
  precisionTier,
5483
5942
  propCompatibility: "unknown",
5484
5943
  ...builtIn?.matchedRole ? { matchedRole: builtIn.matchedRole } : {},
5485
5944
  ...builtIn?.inputType ? { matchedInputType: builtIn.inputType } : {},
5486
- ...isRawHtmlAdvisoryTier(precisionTier) ? { advisory: true } : {}
5945
+ ...isRawHtmlAdvisoryTier(precisionTier) || !importReady ? { advisory: true } : {}
5487
5946
  }
5488
5947
  })
5489
5948
  );
@@ -5503,11 +5962,19 @@ function suppressConflictedExports(source, conflicts) {
5503
5962
  function indexShadowRenderRootNodeIds(ix) {
5504
5963
  const identities = ix.byKind("component_identity");
5505
5964
  if (identities.length === 0) return /* @__PURE__ */ new Set();
5965
+ const definitions = new Map(
5966
+ ix.byKind("component_definition").map((definition) => [definition.componentKey, definition])
5967
+ );
5506
5968
  const nodeIds = /* @__PURE__ */ new Set();
5507
5969
  for (const identity of identities) {
5508
5970
  if (identity.state !== "shadow") continue;
5971
+ const definition = definitions.get(identity.componentKey);
5972
+ if (!definition) continue;
5509
5973
  for (const evidenceId of identity.evidence) {
5510
- if (ix.get(evidenceId)?.kind === "usage_node") nodeIds.add(evidenceId);
5974
+ const evidence = ix.get(evidenceId);
5975
+ if (evidence?.kind === "usage_node" && evidence.file === definition.file) {
5976
+ nodeIds.add(evidenceId);
5977
+ }
5511
5978
  }
5512
5979
  }
5513
5980
  return nodeIds;
@@ -5790,7 +6257,7 @@ function sourceLabel(source) {
5790
6257
  function canonicalImportPath(source) {
5791
6258
  if (source.kind === "npm") return source.specifier;
5792
6259
  if (source.kind === "registry") return source.importPath;
5793
- return source.include?.length ? source.path : void 0;
6260
+ return void 0;
5794
6261
  }
5795
6262
  function indexCanonicalDirectoryResolvedNodes(ix, sources) {
5796
6263
  const out = /* @__PURE__ */ new Set();
@@ -5866,10 +6333,10 @@ function indexComponentIdentityUsage(ix) {
5866
6333
  }
5867
6334
 
5868
6335
  // src/rules/components-shadow-component.ts
5869
- var RULE_ID3 = "components/shadow-component";
5870
- var RULE_VERSION3 = "1";
6336
+ var RULE_ID4 = "components/shadow-component";
6337
+ var RULE_VERSION4 = "1";
5871
6338
  function ruleComponentsShadowComponent(ix) {
5872
- const policy = ix.policy.ruleConfig(RULE_ID3);
6339
+ const policy = ix.policy.ruleConfig(RULE_ID4);
5873
6340
  if (!policy?.enabled) return [];
5874
6341
  const definitions = new Map(
5875
6342
  ix.byKind("component_definition").map((definition) => [definition.componentKey, definition])
@@ -5886,8 +6353,8 @@ function ruleComponentsShadowComponent(ix) {
5886
6353
  const blastRadius = usage?.blastRadius ?? 0;
5887
6354
  findings.push(
5888
6355
  makeFinding({
5889
- ruleId: RULE_ID3,
5890
- ruleVersion: RULE_VERSION3,
6356
+ ruleId: RULE_ID4,
6357
+ ruleVersion: RULE_VERSION4,
5891
6358
  severity: reviewVariant ? "info" : policy.severity ?? "warn",
5892
6359
  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"}.`,
5893
6360
  location: definitionLocation(ix, definition, identity),
@@ -5925,8 +6392,8 @@ function definitionLocation(ix, definition, identity) {
5925
6392
  }
5926
6393
 
5927
6394
  // src/rules/components-forbidden-prop-value.ts
5928
- var RULE_ID4 = "components/forbidden-prop-value";
5929
- var RULE_VERSION4 = "1";
6395
+ var RULE_ID5 = "components/forbidden-prop-value";
6396
+ var RULE_VERSION5 = "1";
5930
6397
  function ruleComponentsForbiddenPropValue(ix) {
5931
6398
  const findings = [];
5932
6399
  const componentByNode = indexComponentByNodeId(ix);
@@ -5956,8 +6423,8 @@ function ruleComponentsForbiddenPropValue(ix) {
5956
6423
  } : void 0;
5957
6424
  findings.push(
5958
6425
  makeFinding({
5959
- ruleId: RULE_ID4,
5960
- ruleVersion: RULE_VERSION4,
6426
+ ruleId: RULE_ID5,
6427
+ ruleVersion: RULE_VERSION5,
5961
6428
  severity: policy.severity,
5962
6429
  message: buildMessage(
5963
6430
  node.element,
@@ -6001,8 +6468,8 @@ function buildMessage(element, prop, value, because, replacement) {
6001
6468
  }
6002
6469
 
6003
6470
  // src/rules/components-unknown-prop.ts
6004
- var RULE_ID5 = "components/unknown-prop";
6005
- var RULE_VERSION5 = "1";
6471
+ var RULE_ID6 = "components/unknown-prop";
6472
+ var RULE_VERSION6 = "1";
6006
6473
  function ruleComponentsUnknownProp(ix) {
6007
6474
  const policy = ix.policy.jsxUnknownPropsForbidden();
6008
6475
  if (!policy) return [];
@@ -6023,8 +6490,8 @@ function ruleComponentsUnknownProp(ix) {
6023
6490
  if (knownNames.has(propUsage.prop)) continue;
6024
6491
  findings.push(
6025
6492
  makeFinding({
6026
- ruleId: RULE_ID5,
6027
- ruleVersion: RULE_VERSION5,
6493
+ ruleId: RULE_ID6,
6494
+ ruleVersion: RULE_VERSION6,
6028
6495
  severity: policy.severity,
6029
6496
  message: `Unknown prop "${propUsage.prop}" on <${node.element}>. Allowed props: ${formatPropList(knownNames)}.`,
6030
6497
  location: node.location,
@@ -6056,7 +6523,7 @@ function formatPropList(names) {
6056
6523
  // src/rules/composition-pattern.ts
6057
6524
  var CARDINALITY_RULE_ID = "composition/cardinality";
6058
6525
  var CO_OCCURRENCE_RULE_ID = "composition/co-occurrence";
6059
- var RULE_VERSION6 = "1";
6526
+ var RULE_VERSION7 = "1";
6060
6527
  function ruleCompositionCardinality(ix) {
6061
6528
  return evaluate(ix, CARDINALITY_RULE_ID, "cardinality");
6062
6529
  }
@@ -6124,7 +6591,7 @@ function checkCardinality(ctx, region, pattern, constraint, selected) {
6124
6591
  const extras = selected.slice(constraint.max);
6125
6592
  return makeFinding({
6126
6593
  ruleId: ctx.ruleId,
6127
- ruleVersion: RULE_VERSION6,
6594
+ ruleVersion: RULE_VERSION7,
6128
6595
  severity: ctx.severity,
6129
6596
  message: cardinalityMaxMessage(pattern, constraint.max, count),
6130
6597
  location: extras[0].location,
@@ -6135,7 +6602,7 @@ function checkCardinality(ctx, region, pattern, constraint, selected) {
6135
6602
  if (constraint.min !== void 0 && count < constraint.min) {
6136
6603
  return makeFinding({
6137
6604
  ruleId: ctx.ruleId,
6138
- ruleVersion: RULE_VERSION6,
6605
+ ruleVersion: RULE_VERSION7,
6139
6606
  severity: ctx.severity,
6140
6607
  message: cardinalityMinMessage(pattern, constraint.min, count),
6141
6608
  location: region.container.location,
@@ -6156,7 +6623,7 @@ function checkCoOccurrence(ctx, region, pattern, constraint, selected) {
6156
6623
  if (satisfied) return null;
6157
6624
  return makeFinding({
6158
6625
  ruleId: ctx.ruleId,
6159
- ruleVersion: RULE_VERSION6,
6626
+ ruleVersion: RULE_VERSION7,
6160
6627
  severity: ctx.severity,
6161
6628
  message: coOccurrenceMessage(pattern, constraint),
6162
6629
  location: region.container.location,
@@ -6208,8 +6675,8 @@ function coOccurrenceMessage(pattern, constraint) {
6208
6675
  }
6209
6676
 
6210
6677
  // src/rules/jsx-preferred-component.ts
6211
- var RULE_ID6 = "components/preferred-component";
6212
- var RULE_VERSION7 = "1";
6678
+ var RULE_ID7 = "components/preferred-component";
6679
+ var RULE_VERSION8 = "1";
6213
6680
  function ruleJsxPreferredComponent(ix) {
6214
6681
  const policies = ix.policy.jsxComponentPreferred();
6215
6682
  if (policies.length === 0) return [];
@@ -6225,8 +6692,8 @@ function ruleJsxPreferredComponent(ix) {
6225
6692
  if (!replacement) continue;
6226
6693
  findings.push(
6227
6694
  makeFinding({
6228
- ruleId: RULE_ID6,
6229
- ruleVersion: RULE_VERSION7,
6695
+ ruleId: RULE_ID7,
6696
+ ruleVersion: RULE_VERSION8,
6230
6697
  severity: policy.severity,
6231
6698
  message: `<${node.element}> should use <${replacement}>.`,
6232
6699
  location: node.location,
@@ -6263,8 +6730,8 @@ function componentNameFromId(componentId2) {
6263
6730
  }
6264
6731
 
6265
6732
  // src/rules/jsx-preferred-import-path.ts
6266
- var RULE_ID7 = "imports/preferred-path";
6267
- var RULE_VERSION8 = "1";
6733
+ var RULE_ID8 = "imports/preferred-path";
6734
+ var RULE_VERSION9 = "1";
6268
6735
  function ruleJsxPreferredImportPath(ix) {
6269
6736
  const policies = ix.policy.jsxImportPathPreferred();
6270
6737
  if (policies.length === 0) return [];
@@ -6292,8 +6759,8 @@ function ruleJsxPreferredImportPath(ix) {
6292
6759
  seen.add(key);
6293
6760
  findings.push(
6294
6761
  makeFinding({
6295
- ruleId: RULE_ID7,
6296
- ruleVersion: RULE_VERSION8,
6762
+ ruleId: RULE_ID8,
6763
+ ruleVersion: RULE_VERSION9,
6297
6764
  severity: policy.severity,
6298
6765
  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}".`,
6299
6766
  location: usage.location,
@@ -6352,10 +6819,10 @@ function normalizeRepoPath(path) {
6352
6819
  }
6353
6820
 
6354
6821
  // src/rules/props-invalid-value.ts
6355
- var RULE_ID8 = "props/invalid-value";
6356
- var RULE_VERSION9 = "1";
6822
+ var RULE_ID9 = "props/invalid-value";
6823
+ var RULE_VERSION10 = "1";
6357
6824
  function rulePropsInvalidValue(ix) {
6358
- const policy = ix.policy.ruleConfig(RULE_ID8);
6825
+ const policy = ix.policy.ruleConfig(RULE_ID9);
6359
6826
  if (!policy?.enabled) return [];
6360
6827
  const findings = [];
6361
6828
  const componentByNode = indexComponentByNodeId(ix);
@@ -6384,8 +6851,8 @@ function rulePropsInvalidValue(ix) {
6384
6851
  } : void 0;
6385
6852
  findings.push(
6386
6853
  makeFinding({
6387
- ruleId: RULE_ID8,
6388
- ruleVersion: RULE_VERSION9,
6854
+ ruleId: RULE_ID9,
6855
+ ruleVersion: RULE_VERSION10,
6389
6856
  severity: policy.severity ?? "warn",
6390
6857
  message: buildMessage2(node.element, propUsage.prop, propUsage.value, allowedValues),
6391
6858
  location: propUsage.location ?? node.location,
@@ -6584,8 +7051,8 @@ function localTokenCandidates(ix, category) {
6584
7051
  }
6585
7052
 
6586
7053
  // src/rules/styles-no-raw-color.ts
6587
- var RULE_ID9 = "styles/no-raw-color";
6588
- var RULE_VERSION10 = "1";
7054
+ var RULE_ID10 = "styles/no-raw-color";
7055
+ var RULE_VERSION11 = "1";
6589
7056
  function ruleStylesNoRawColor(ix) {
6590
7057
  const policy = ix.policy.rawColorPolicy();
6591
7058
  if (!policy) return [];
@@ -6604,8 +7071,8 @@ function ruleStylesNoRawColor(ix) {
6604
7071
  const evidenceIds = resolution ? [decl.id, policy.id, resolution.token.id] : [decl.id, policy.id];
6605
7072
  findings.push(
6606
7073
  makeFinding({
6607
- ruleId: RULE_ID9,
6608
- ruleVersion: RULE_VERSION10,
7074
+ ruleId: RULE_ID10,
7075
+ ruleVersion: RULE_VERSION11,
6609
7076
  severity: policy.severity,
6610
7077
  message: colorMessage(color, `\`${decl.property}\``, resolution, preferLabel) + (hopped?.suffix ?? ""),
6611
7078
  location: decl.location,
@@ -6641,8 +7108,8 @@ function ruleStylesNoRawColor(ix) {
6641
7108
  const evidenceIds2 = componentEvidenceId2 ? [node2.id, componentEvidenceId2, inline.id, policy.id] : [node2.id, inline.id, policy.id];
6642
7109
  findings.push(
6643
7110
  makeFinding({
6644
- ruleId: RULE_ID9,
6645
- ruleVersion: RULE_VERSION10,
7111
+ ruleId: RULE_ID10,
7112
+ ruleVersion: RULE_VERSION11,
6646
7113
  severity: advisorySeverity,
6647
7114
  message: `Runtime-constructed raw color \`${inline.value}\` on inline \`${inline.property}\`. Use a ${preferLabel} instead.`,
6648
7115
  location: node2.location,
@@ -6679,8 +7146,8 @@ function ruleStylesNoRawColor(ix) {
6679
7146
  const evidenceIds = resolution ? [...baseEvidence, resolution.token.id] : baseEvidence;
6680
7147
  findings.push(
6681
7148
  makeFinding({
6682
- ruleId: RULE_ID9,
6683
- ruleVersion: RULE_VERSION10,
7149
+ ruleId: RULE_ID10,
7150
+ ruleVersion: RULE_VERSION11,
6684
7151
  severity: policy.severity,
6685
7152
  message: colorMessage(color, `inline \`${inline.property}\``, resolution, preferLabel) + (hopped?.suffix ?? ""),
6686
7153
  location: node.location,
@@ -6724,8 +7191,8 @@ function ruleStylesNoRawColor(ix) {
6724
7191
  const evidenceIds = resolution ? [...baseEvidence, resolution.token.id] : baseEvidence;
6725
7192
  findings.push(
6726
7193
  makeFinding({
6727
- ruleId: RULE_ID9,
6728
- ruleVersion: RULE_VERSION10,
7194
+ ruleId: RULE_ID10,
7195
+ ruleVersion: RULE_VERSION11,
6729
7196
  severity: advisorySeverity,
6730
7197
  message: colorMessage(color, `\`${prop.prop}\``, resolution, preferLabel),
6731
7198
  location: prop.location ?? node.location,
@@ -6876,8 +7343,8 @@ function escapeRegExp(value) {
6876
7343
  }
6877
7344
 
6878
7345
  // src/rules/styles-no-raw-dimensions.ts
6879
- var RULE_ID10 = "styles/no-raw-dimensions";
6880
- var RULE_VERSION11 = "1";
7346
+ var RULE_ID11 = "styles/no-raw-dimensions";
7347
+ var RULE_VERSION12 = "1";
6881
7348
  function ruleStylesNoRawDimensions(ix) {
6882
7349
  const policy = ix.policy.rawDimensionPolicy();
6883
7350
  if (!policy) return [];
@@ -6898,8 +7365,8 @@ function ruleStylesNoRawDimensions(ix) {
6898
7365
  const fixEmission = hopped ? void 0 : dimensionFix(decl.property, resolution, ix);
6899
7366
  findings.push(
6900
7367
  makeFinding({
6901
- ruleId: RULE_ID10,
6902
- ruleVersion: RULE_VERSION11,
7368
+ ruleId: RULE_ID11,
7369
+ ruleVersion: RULE_VERSION12,
6903
7370
  severity: policy.severity,
6904
7371
  message: dimensionMessage(decl.value, decl.property, preferLabel, resolution, false) + (hopped?.suffix ?? ""),
6905
7372
  location: decl.location,
@@ -6942,8 +7409,8 @@ function ruleStylesNoRawDimensions(ix) {
6942
7409
  const fixEmission = hopped ? void 0 : dimensionFix(inline.property, resolution, ix);
6943
7410
  findings.push(
6944
7411
  makeFinding({
6945
- ruleId: RULE_ID10,
6946
- ruleVersion: RULE_VERSION11,
7412
+ ruleId: RULE_ID11,
7413
+ ruleVersion: RULE_VERSION12,
6947
7414
  severity: policy.severity,
6948
7415
  message: dimensionMessage(inline.value, inline.property, preferLabel, resolution, true) + (hopped?.suffix ?? ""),
6949
7416
  location: node.location,
@@ -7212,8 +7679,8 @@ function parseLengthParts(raw) {
7212
7679
  }
7213
7680
 
7214
7681
  // src/rules/styles-no-raw-spacing.ts
7215
- var RULE_ID11 = "styles/no-raw-spacing";
7216
- var RULE_VERSION12 = "1";
7682
+ var RULE_ID12 = "styles/no-raw-spacing";
7683
+ var RULE_VERSION13 = "1";
7217
7684
  function ruleStylesNoRawSpacing(ix) {
7218
7685
  const findings = [];
7219
7686
  const lookupCache = /* @__PURE__ */ new Map();
@@ -7256,8 +7723,8 @@ function checkDeclaration(ix, decl, lookupFor) {
7256
7723
  const hopped = hoppedValue(decl);
7257
7724
  const fixEmission = hopped ? void 0 : buildSpacingFix(decl.property, checked, ix);
7258
7725
  return makeFinding({
7259
- ruleId: RULE_ID11,
7260
- ruleVersion: RULE_VERSION12,
7726
+ ruleId: RULE_ID12,
7727
+ ruleVersion: RULE_VERSION13,
7261
7728
  severity: policy.severity,
7262
7729
  message: spacingMessage(decl.property, decl.value, allowed, scale, checked) + (hopped?.suffix ?? ""),
7263
7730
  location: decl.location,
@@ -7313,8 +7780,8 @@ function checkInlineStyle(ix, inline, componentByNode, lookupFor) {
7313
7780
  const componentEvidenceId = componentByNode.get(node.id)?.id;
7314
7781
  const evidenceIds = componentEvidenceId ? [node.id, componentEvidenceId, inline.id, policy.id, scale.id] : [node.id, inline.id, policy.id, scale.id];
7315
7782
  return makeFinding({
7316
- ruleId: RULE_ID11,
7317
- ruleVersion: RULE_VERSION12,
7783
+ ruleId: RULE_ID12,
7784
+ ruleVersion: RULE_VERSION13,
7318
7785
  severity: policy.severity,
7319
7786
  message: spacingMessage(inline.property, inline.value, allowed, scale, checked) + (hopped?.suffix ?? ""),
7320
7787
  location: node.location,
@@ -7376,8 +7843,8 @@ function spacingMessage(property, value, allowed, scale, checked) {
7376
7843
  }
7377
7844
 
7378
7845
  // src/rules/styles-no-raw-typography.ts
7379
- var RULE_ID12 = "styles/no-raw-typography";
7380
- var RULE_VERSION13 = "1";
7846
+ var RULE_ID13 = "styles/no-raw-typography";
7847
+ var RULE_VERSION14 = "1";
7381
7848
  var FONT_SIZE_PROPERTIES = /* @__PURE__ */ new Set(["font-size", "fontsize"]);
7382
7849
  function ruleStylesNoRawTypography(ix) {
7383
7850
  const policy = ix.policy.fontSizeScale();
@@ -7396,8 +7863,8 @@ function ruleStylesNoRawTypography(ix) {
7396
7863
  const fixEmission = hopped ? void 0 : buildFix(decl.property, checked, ix);
7397
7864
  findings.push(
7398
7865
  makeFinding({
7399
- ruleId: RULE_ID12,
7400
- ruleVersion: RULE_VERSION13,
7866
+ ruleId: RULE_ID13,
7867
+ ruleVersion: RULE_VERSION14,
7401
7868
  severity: policy.severity,
7402
7869
  message: typographyMessage(decl.property, decl.value, allowed, scale.unit, checked) + (hopped?.suffix ?? ""),
7403
7870
  location: decl.location,
@@ -7445,8 +7912,8 @@ function ruleStylesNoRawTypography(ix) {
7445
7912
  const evidenceIds = componentEvidenceId ? [node.id, componentEvidenceId, inline.id, policy.id, scale.id] : [node.id, inline.id, policy.id, scale.id];
7446
7913
  findings.push(
7447
7914
  makeFinding({
7448
- ruleId: RULE_ID12,
7449
- ruleVersion: RULE_VERSION13,
7915
+ ruleId: RULE_ID13,
7916
+ ruleVersion: RULE_VERSION14,
7450
7917
  severity: policy.severity,
7451
7918
  message: typographyMessage(inline.property, inline.value, allowed, scale.unit, checked) + (hopped?.suffix ?? ""),
7452
7919
  location: node.location,
@@ -7549,15 +8016,15 @@ function typographyMessage(property, value, allowed, unit, checked) {
7549
8016
  }
7550
8017
 
7551
8018
  // src/rules/theme-no-theme-coupled-literal.ts
7552
- var RULE_ID13 = "theme/no-theme-coupled-literal";
7553
- var RULE_VERSION14 = "1";
8019
+ var RULE_ID14 = "theme/no-theme-coupled-literal";
8020
+ var RULE_VERSION15 = "1";
7554
8021
  var COLOR_LITERAL_GLOBAL = /#[0-9a-fA-F]{3,8}\b|\b(?:rgba?|hsla?|oklab|oklch|lab|lch)\(\s*[\d.%\s,\/]+\s*\)/g;
7555
8022
  var WHITE_RGBA = /^rgba?\(\s*255\s*,\s*255\s*,\s*255/i;
7556
8023
  var BLACK_RGBA = /^rgba?\(\s*0\s*,\s*0\s*,\s*0/i;
7557
8024
  var BOX_SHADOW_PROP = /^(?:-webkit-|-moz-)?box-shadow$/i;
7558
8025
  var NEUTRAL_SHADOW = /^(?:none|inherit|initial|unset|revert|revert-layer)\s*$/i;
7559
8026
  function ruleThemeNoThemeCoupledLiteral(ix) {
7560
- const policy = ix.policy.ruleConfig(RULE_ID13);
8027
+ const policy = ix.policy.ruleConfig(RULE_ID14);
7561
8028
  if (!policy?.enabled) return [];
7562
8029
  const findings = [];
7563
8030
  for (const decl of ix.byKind("style_declaration")) {
@@ -7566,8 +8033,8 @@ function ruleThemeNoThemeCoupledLiteral(ix) {
7566
8033
  if (!hit) continue;
7567
8034
  findings.push(
7568
8035
  makeFinding({
7569
- ruleId: RULE_ID13,
7570
- ruleVersion: RULE_VERSION14,
8036
+ ruleId: RULE_ID14,
8037
+ ruleVersion: RULE_VERSION15,
7571
8038
  severity: policy.severity ?? "warn",
7572
8039
  message: hit.message,
7573
8040
  location: decl.location,
@@ -7657,8 +8124,8 @@ function isTokenFile(filePath) {
7657
8124
  }
7658
8125
 
7659
8126
  // src/rules/tailwind-arbitrary-color.ts
7660
- var RULE_ID14 = "tailwind/arbitrary-color";
7661
- var RULE_VERSION15 = "1";
8127
+ var RULE_ID15 = "tailwind/arbitrary-color";
8128
+ var RULE_VERSION16 = "1";
7662
8129
  var COLOR_UTILITIES = /* @__PURE__ */ new Set([
7663
8130
  "accent",
7664
8131
  "bg",
@@ -7700,8 +8167,8 @@ function ruleTailwindArbitraryColor(ix) {
7700
8167
  const evidenceIds = node ? [node.id, klass.id, policy.id] : [klass.id, policy.id];
7701
8168
  findings.push(
7702
8169
  makeFinding({
7703
- ruleId: RULE_ID14,
7704
- ruleVersion: RULE_VERSION15,
8170
+ ruleId: RULE_ID15,
8171
+ ruleVersion: RULE_VERSION16,
7705
8172
  severity: policy.severity,
7706
8173
  message: `\`${klass.raw}\` uses arbitrary raw color ${color}. Use a ${policy.prefer === "token" ? "design token" : "CSS variable"} instead.`,
7707
8174
  location: klass.location,
@@ -7732,8 +8199,8 @@ function fingerprintIdentity(klass, color) {
7732
8199
  }
7733
8200
 
7734
8201
  // src/rules/tailwind-arbitrary-spacing.ts
7735
- var RULE_ID15 = "tailwind/arbitrary-spacing";
7736
- var RULE_VERSION16 = "1";
8202
+ var RULE_ID16 = "tailwind/arbitrary-spacing";
8203
+ var RULE_VERSION17 = "1";
7737
8204
  var SPACING_UTILITY_TO_PROPERTY = /* @__PURE__ */ new Map([
7738
8205
  ["m", "margin"],
7739
8206
  ["mt", "margin"],
@@ -7779,8 +8246,8 @@ function ruleTailwindArbitrarySpacing(ix) {
7779
8246
  const fix = buildFix2(klass);
7780
8247
  findings.push(
7781
8248
  makeFinding({
7782
- ruleId: RULE_ID15,
7783
- ruleVersion: RULE_VERSION16,
8249
+ ruleId: RULE_ID16,
8250
+ ruleVersion: RULE_VERSION17,
7784
8251
  severity: policy.severity,
7785
8252
  message: `\`${klass.raw}\` is an arbitrary spacing value not on the project's spacing scale.`,
7786
8253
  location: klass.location,
@@ -7911,8 +8378,8 @@ function matchesTokenGlob(value, pattern) {
7911
8378
  }
7912
8379
 
7913
8380
  // src/rules/tailwind-forbidden-palette.ts
7914
- var RULE_ID16 = "tailwind/forbidden-palette";
7915
- var RULE_VERSION17 = "1";
8381
+ var RULE_ID17 = "tailwind/forbidden-palette";
8382
+ var RULE_VERSION18 = "1";
7916
8383
  function ruleTailwindForbiddenPalette(ix) {
7917
8384
  const deny = ix.policy.tailwindPaletteDeny();
7918
8385
  const allow = ix.policy.tailwindPaletteAllow();
@@ -7937,8 +8404,8 @@ function ruleTailwindForbiddenPalette(ix) {
7937
8404
  const evidenceIds = node ? [node.id, klass.id, resolved.id, policy.id] : [klass.id, resolved.id, policy.id];
7938
8405
  findings.push(
7939
8406
  makeFinding({
7940
- ruleId: RULE_ID16,
7941
- ruleVersion: RULE_VERSION17,
8407
+ ruleId: RULE_ID17,
8408
+ ruleVersion: RULE_VERSION18,
7942
8409
  severity: policy.severity,
7943
8410
  message: messageFor(klass, token, deniedPattern, allow?.patterns ?? []),
7944
8411
  location: klass.location,
@@ -7985,8 +8452,8 @@ function buildFix3(klass) {
7985
8452
  }
7986
8453
 
7987
8454
  // src/rules/tailwind-off-scale-spacing-token.ts
7988
- var RULE_ID17 = "tailwind/off-scale-spacing-token";
7989
- var RULE_VERSION18 = "1";
8455
+ var RULE_ID18 = "tailwind/off-scale-spacing-token";
8456
+ var RULE_VERSION19 = "1";
7990
8457
  function ruleTailwindOffScaleSpacingToken(ix) {
7991
8458
  const resolvedByKey = indexResolvedTailwindTokens(ix);
7992
8459
  const findings = [];
@@ -8012,8 +8479,8 @@ function ruleTailwindOffScaleSpacingToken(ix) {
8012
8479
  const evidenceIds = node ? [node.id, klass.id, resolved.id, policy.id, scale.id] : [klass.id, resolved.id, policy.id, scale.id];
8013
8480
  findings.push(
8014
8481
  makeFinding({
8015
- ruleId: RULE_ID17,
8016
- ruleVersion: RULE_VERSION18,
8482
+ ruleId: RULE_ID18,
8483
+ ruleVersion: RULE_VERSION19,
8017
8484
  severity: policy.severity,
8018
8485
  message: `\`${klass.raw}\` resolves to ${formatResolvedValue(signedValue, scale.unit)}, which is not on the project's spacing scale [${allowed.join(", ")}].`,
8019
8486
  location: klass.location,
@@ -8062,8 +8529,8 @@ function buildFix4(raw, utility) {
8062
8529
  }
8063
8530
 
8064
8531
  // src/rules/tailwind-raw-color-via-token.ts
8065
- var RULE_ID18 = "tailwind/raw-color-via-token";
8066
- var RULE_VERSION19 = "1";
8532
+ var RULE_ID19 = "tailwind/raw-color-via-token";
8533
+ var RULE_VERSION20 = "1";
8067
8534
  function ruleTailwindRawColorViaToken(ix) {
8068
8535
  const allow = ix.policy.tailwindPaletteAllow();
8069
8536
  if (!allow || allow.patterns.length === 0) return [];
@@ -8083,8 +8550,8 @@ function ruleTailwindRawColorViaToken(ix) {
8083
8550
  const evidenceIds = node ? [node.id, klass.id, resolved.id, allow.id] : [klass.id, resolved.id, allow.id];
8084
8551
  findings.push(
8085
8552
  makeFinding({
8086
- ruleId: RULE_ID18,
8087
- ruleVersion: RULE_VERSION19,
8553
+ ruleId: RULE_ID19,
8554
+ ruleVersion: RULE_VERSION20,
8088
8555
  severity: allow.severity,
8089
8556
  message: `\`${klass.raw}\` resolves to raw color \`${color}\` (${sourceLabel2(resolved.resolved.source)}). Project policy requires \`${allow.patterns.join("`, `")}\` palette tokens.`,
8090
8557
  location: klass.location,
@@ -8117,8 +8584,8 @@ function sourceLabel2(source) {
8117
8584
  }
8118
8585
 
8119
8586
  // src/rules/tailwind-unknown-class.ts
8120
- var RULE_ID19 = "tailwind/unknown-class";
8121
- var RULE_VERSION20 = "1";
8587
+ var RULE_ID20 = "tailwind/unknown-class";
8588
+ var RULE_VERSION21 = "1";
8122
8589
  var UNIVERSAL_COLOR_KEYWORDS = /* @__PURE__ */ new Set([
8123
8590
  "white",
8124
8591
  "black",
@@ -8139,8 +8606,8 @@ function ruleTailwindUnknownClass(ix) {
8139
8606
  const evidenceIds2 = node2 ? [node2.id, klass.id, policy.id] : [klass.id, policy.id];
8140
8607
  findings.push(
8141
8608
  makeFinding({
8142
- ruleId: RULE_ID19,
8143
- ruleVersion: RULE_VERSION20,
8609
+ ruleId: RULE_ID20,
8610
+ ruleVersion: RULE_VERSION21,
8144
8611
  severity: policy.severity,
8145
8612
  message: `\`${klass.raw}\` could not be parsed as a known Tailwind utility.`,
8146
8613
  location: klass.location,
@@ -8174,8 +8641,8 @@ function ruleTailwindUnknownClass(ix) {
8174
8641
  const evidenceIds = node ? [node.id, klass.id, resolved.id, policy.id] : [klass.id, resolved.id, policy.id];
8175
8642
  findings.push(
8176
8643
  makeFinding({
8177
- ruleId: RULE_ID19,
8178
- ruleVersion: RULE_VERSION20,
8644
+ ruleId: RULE_ID20,
8645
+ ruleVersion: RULE_VERSION21,
8179
8646
  severity: policy.severity,
8180
8647
  message: `\`${klass.raw}\` did not resolve to any known palette token.`,
8181
8648
  location: klass.location,
@@ -8203,12 +8670,12 @@ function ruleTailwindUnknownClass(ix) {
8203
8670
  }
8204
8671
 
8205
8672
  // src/rules/tokens-require-dual-fallback.ts
8206
- var RULE_ID20 = "tokens/require-dual-fallback";
8207
- var RULE_VERSION21 = "1";
8673
+ var RULE_ID21 = "tokens/require-dual-fallback";
8674
+ var RULE_VERSION22 = "1";
8208
8675
  var CSS_VAR_WITH_OPTIONAL_FALLBACK = /var\(\s*(--[A-Za-z0-9_-]+)(\s*,\s*[^)]*)?\)/g;
8209
8676
  var SASS_FILE = /\.(scss|sass)$/i;
8210
8677
  function ruleTokensRequireDualFallback(ix) {
8211
- const policy = ix.policy.ruleConfig(RULE_ID20);
8678
+ const policy = ix.policy.ruleConfig(RULE_ID21);
8212
8679
  if (!policy?.enabled) return [];
8213
8680
  const tokensByCssVariable = indexTokensByCssVariable(localTokenCandidates(ix));
8214
8681
  const findings = [];
@@ -8243,8 +8710,8 @@ function ruleTokensRequireDualFallback(ix) {
8243
8710
  ) : void 0;
8244
8711
  findings.push(
8245
8712
  makeFinding({
8246
- ruleId: RULE_ID20,
8247
- ruleVersion: RULE_VERSION21,
8713
+ ruleId: RULE_ID21,
8714
+ ruleVersion: RULE_VERSION22,
8248
8715
  severity: policy.severity ?? "warn",
8249
8716
  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}.`,
8250
8717
  location: decl.location,
@@ -8284,8 +8751,8 @@ function indexTokensByCssVariable(tokens) {
8284
8751
  }
8285
8752
 
8286
8753
  // src/rules/tokens-css-vars-must-be-defined.ts
8287
- var RULE_ID21 = "tokens/css-vars-must-be-defined";
8288
- var RULE_VERSION22 = "1";
8754
+ var RULE_ID22 = "tokens/css-vars-must-be-defined";
8755
+ var RULE_VERSION23 = "1";
8289
8756
  var CSS_VAR_REFERENCE = /var\(\s*(--[A-Za-z0-9_-]+)/gi;
8290
8757
  function ruleTokensCssVarsMustBeDefined(ix) {
8291
8758
  const policy = ix.policy.cssVarsMustBeDefined();
@@ -8312,8 +8779,8 @@ function ruleTokensCssVarsMustBeDefined(ix) {
8312
8779
  seen.add(tokenName);
8313
8780
  findings.push(
8314
8781
  makeFinding({
8315
- ruleId: RULE_ID21,
8316
- ruleVersion: RULE_VERSION22,
8782
+ ruleId: RULE_ID22,
8783
+ ruleVersion: RULE_VERSION23,
8317
8784
  severity: policy.severity,
8318
8785
  message: `var(${tokenName}) is not in the contract token vocabulary. Use a token defined in your contract's token source files.`,
8319
8786
  location: decl.location,
@@ -8360,8 +8827,8 @@ function skipToCloseParen(value, openParenIndex) {
8360
8827
  }
8361
8828
 
8362
8829
  // src/rules/tokens-upstream-drift.ts
8363
- var RULE_ID22 = "tokens/upstream-drift";
8364
- var RULE_VERSION23 = "1";
8830
+ var RULE_ID23 = "tokens/upstream-drift";
8831
+ var RULE_VERSION24 = "1";
8365
8832
  function ruleTokensUpstreamDrift(ix) {
8366
8833
  const local = ix.tokens.list().filter((token) => token.role !== "upstream");
8367
8834
  const upstream = ix.tokens.list().filter((token) => token.role === "upstream");
@@ -8387,8 +8854,8 @@ function ruleTokensUpstreamDrift(ix) {
8387
8854
  };
8388
8855
  findings.push(
8389
8856
  makeFinding({
8390
- ruleId: RULE_ID22,
8391
- ruleVersion: RULE_VERSION23,
8857
+ ruleId: RULE_ID23,
8858
+ ruleVersion: RULE_VERSION24,
8392
8859
  severity: "warn",
8393
8860
  message: `Local token ${token.name} is ${token.value}, but upstream ${upstreamName} is ${expected.value}.`,
8394
8861
  location,
@@ -8420,8 +8887,12 @@ var BLOCKING_RULE_ALLOWLIST = /* @__PURE__ */ new Set([
8420
8887
  // off-scale Tailwind spacing utility
8421
8888
  "components/prefer-library",
8422
8889
  // raw element bypasses a canonical component
8423
- "components/preferred-component"
8890
+ "components/shadow-component",
8891
+ // confirmed local semantic control shadows a canonical
8892
+ "components/preferred-component",
8424
8893
  // imported component bypasses its canonical
8894
+ "tokens/css-vars-must-be-defined"
8895
+ // off-contract custom property reference
8425
8896
  ]);
8426
8897
  function gatesCi(finding, failOnWarnings) {
8427
8898
  if (!canBlock(finding.evidenceGrade ?? "source_backed")) return false;
@@ -8432,9 +8903,13 @@ function gatesCi(finding, failOnWarnings) {
8432
8903
  function isDenyEligible(finding, failOnWarnings) {
8433
8904
  if (!BLOCKING_RULE_ALLOWLIST.has(finding.ruleId)) return false;
8434
8905
  if (finding.attributes?.advisory === true) return false;
8906
+ if (finding.ruleId === "components/shadow-component" && (finding.attributes?.state !== "shadow" || finding.attributes?.confidence !== "confirmed" || typeof finding.attributes?.canonicalTarget !== "string")) {
8907
+ return false;
8908
+ }
8435
8909
  if (!gatesCi(finding, failOnWarnings)) return false;
8436
8910
  if (finding.fix?.deterministic === false) {
8437
- if (finding.ruleId !== "components/prefer-library" || finding.attributes?.propCompatibility !== "observed-complete") {
8911
+ const exactCanonicalBypass = finding.ruleId === "components/prefer-library" && finding.attributes?.precisionTier === "exact-html" && typeof finding.attributes?.suggestedComponent === "string";
8912
+ if (!exactCanonicalBypass) {
8438
8913
  return false;
8439
8914
  }
8440
8915
  }
@@ -8471,6 +8946,7 @@ var RULE_TIER = {
8471
8946
  "tokens/require-dual-fallback": "hygiene",
8472
8947
  "theme/no-theme-coupled-literal": "hygiene",
8473
8948
  "a11y/required-accessible-name": "hygiene",
8949
+ "a11y/standard": "hygiene",
8474
8950
  // Composition constraints are authored contracts, but not part of the
8475
8951
  // two-choice headline contract; they self-activate when patterns are declared
8476
8952
  // (the compiler injects their configs), so off-by-default here is harmless.
@@ -8632,9 +9108,14 @@ var RULES = [
8632
9108
  },
8633
9109
  {
8634
9110
  id: "a11y/required-accessible-name",
8635
- version: "1",
9111
+ version: RULE_VERSION,
8636
9112
  run: ruleA11yRequiredAccessibleName
8637
9113
  },
9114
+ {
9115
+ id: "a11y/standard",
9116
+ version: "1",
9117
+ run: ruleA11yStandard
9118
+ },
8638
9119
  {
8639
9120
  id: "composition/cardinality",
8640
9121
  version: "1",
@@ -8673,12 +9154,21 @@ function hasEffectiveComponentVocabulary(options) {
8673
9154
  }
8674
9155
  function effectiveRuleConfigs(policy) {
8675
9156
  const configs = /* @__PURE__ */ new Map();
8676
- for (const fact of compileGlobalGovernanceFacts(policy)) {
8677
- if (fact.kind !== "governance_rule_config") continue;
8678
- configs.set(fact.ruleId, {
8679
- enabled: fact.enabled,
8680
- severity: fact.severity,
8681
- options: fact.options
9157
+ const facts = compileGlobalGovernanceFacts(policy);
9158
+ for (const fact of facts) {
9159
+ if (fact.kind === "governance_rule_config") {
9160
+ configs.set(fact.ruleId, {
9161
+ enabled: fact.enabled,
9162
+ severity: fact.severity,
9163
+ options: fact.options
9164
+ });
9165
+ }
9166
+ }
9167
+ const cssVarsPolicy = facts.find((fact) => fact.kind === "style_css_vars_must_be_defined");
9168
+ if (cssVarsPolicy?.kind === "style_css_vars_must_be_defined") {
9169
+ configs.set("tokens/css-vars-must-be-defined", {
9170
+ enabled: true,
9171
+ severity: cssVarsPolicy.severity
8682
9172
  });
8683
9173
  }
8684
9174
  return configs;
@@ -9082,23 +9572,28 @@ function evaluateGovernanceIntegrity(input) {
9082
9572
  if (!policyArmed) {
9083
9573
  policyFamily.reason = "no governance policy resolved";
9084
9574
  }
9085
- const componentsConfig = configs.get("components/prefer-library");
9575
+ const preferLibraryConfig = configs.get("components/prefer-library");
9576
+ const shadowComponentConfig = configs.get("components/shadow-component");
9086
9577
  const confirmedBridges = input.policy?.canonicalBridges ?? [];
9087
- const componentsArmed = confirmedBridges.length > 0 || componentsConfig?.enabled === true && hasEffectiveComponentVocabulary(componentsConfig.options);
9578
+ const componentRuleEnabled = preferLibraryConfig?.enabled === true || shadowComponentConfig?.enabled === true;
9579
+ const preferLibraryArmed = preferLibraryConfig?.enabled === true && hasEffectiveComponentVocabulary(preferLibraryConfig.options);
9580
+ const shadowComponentArmed = shadowComponentConfig?.enabled === true && hasEffectiveComponentVocabulary(shadowComponentConfig.options);
9581
+ const componentsArmed = confirmedBridges.length > 0 || preferLibraryArmed || shadowComponentArmed;
9088
9582
  const componentsFamily = {
9089
9583
  id: "components",
9090
9584
  armed: componentsArmed,
9091
9585
  rules: [
9092
9586
  ...confirmedBridges.length > 0 ? ["imports/preferred-path"] : [],
9093
- ...componentsConfig?.enabled === true ? ["components/prefer-library"] : []
9587
+ ...preferLibraryConfig?.enabled === true ? ["components/prefer-library"] : [],
9588
+ ...shadowComponentConfig?.enabled === true ? ["components/shadow-component"] : []
9094
9589
  ]
9095
9590
  };
9096
9591
  if (!componentsArmed) {
9097
- if (componentsConfig?.enabled === true) {
9098
- componentsFamily.reason = "components/prefer-library enabled but no effective canonical source";
9592
+ if (componentRuleEnabled) {
9593
+ componentsFamily.reason = "component governance enabled but no effective canonical source";
9099
9594
  componentsFamily.remediation = "add govern.canonicalBridges for local wrappers, govern.canonicalSources, or designSystem.path/packageName";
9100
9595
  } else {
9101
- componentsFamily.reason = "components/prefer-library not enabled";
9596
+ componentsFamily.reason = "canonical-component rules not enabled";
9102
9597
  componentsFamily.remediation = "add govern.canonicalBridges for local wrappers, govern.canonicalSources, or designSystem.path/packageName";
9103
9598
  }
9104
9599
  }
@@ -9141,7 +9636,7 @@ function evaluateGovernanceIntegrity(input) {
9141
9636
  for (const ruleId of BLOCKING_RULE_ALLOWLIST) {
9142
9637
  const config = configs.get(ruleId);
9143
9638
  if (config?.enabled !== true) continue;
9144
- const familyArmed = ruleId === "components/prefer-library" ? componentsArmed : hygieneArmed;
9639
+ const familyArmed = ruleId === "components/prefer-library" || ruleId === "components/shadow-component" ? componentsArmed : ruleId === "tokens/css-vars-must-be-defined" ? tokensArmed : hygieneArmed;
9145
9640
  if (!familyArmed) continue;
9146
9641
  if (config.severity !== "error" && !failOnWarnings) continue;
9147
9642
  blockingRules.push(ruleId);
@@ -9772,6 +10267,7 @@ export {
9772
10267
  CONTRACT_DOMAINS,
9773
10268
  CONTRACT_PREIMAGE_CAPABILITY_HEADER,
9774
10269
  CONTRACT_PREIMAGE_SCHEMA,
10270
+ CompiledFragmentsFileValidationError,
9775
10271
  DEFAULTS,
9776
10272
  DEFAULT_ENHANCED_STYLE_PROPERTIES,
9777
10273
  DEFAULT_STYLE_PROPERTIES,
@@ -10010,6 +10506,7 @@ export {
10010
10506
  makeTailwindUnknownClassEnabledFact,
10011
10507
  makeThemeDeclarationFact,
10012
10508
  makeTokenDefinitionFact,
10509
+ makeUsageChildContentFact,
10013
10510
  makeUsageComponentFact,
10014
10511
  makeUsageImportFact,
10015
10512
  makeUsageInlineStyleFact,
@@ -10058,6 +10555,7 @@ export {
10058
10555
  ownedImportEquivalents,
10059
10556
  parseColor,
10060
10557
  parseColorToRgb,
10558
+ parseCompiledFragmentsFile,
10061
10559
  parseComponentContract,
10062
10560
  parseContractStamp,
10063
10561
  parseCssTokens,
@@ -10115,6 +10613,7 @@ export {
10115
10613
  resolveTokenValue,
10116
10614
  rgbToHex,
10117
10615
  ruleA11yRequiredAccessibleName,
10616
+ ruleA11yStandard,
10118
10617
  ruleComponentsForbiddenPropValue,
10119
10618
  ruleComponentsPreferLibrary,
10120
10619
  ruleComponentsShadowComponent,