@usefragments/core 1.5.1 → 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.
- package/dist/{chunk-AOG4FTV6.js → chunk-WVFNDPM4.js} +448 -190
- package/dist/chunk-WVFNDPM4.js.map +1 -0
- package/dist/codes/index.d.ts +1 -1
- package/dist/codes/index.js +1 -1
- package/dist/compiled-types/index.d.ts +1 -1
- package/dist/generate/index.d.ts +1 -1
- package/dist/{governance-B88uR3Zq.d.ts → governance-DxFipN5V.d.ts} +654 -22
- package/dist/index.d.ts +678 -40
- package/dist/index.js +534 -38
- package/dist/index.js.map +1 -1
- package/dist/react-types.d.ts +1 -1
- package/dist/test-utils.d.ts +1 -1
- package/package.json +1 -1
- package/src/__tests__/policy-exclude.test.ts +180 -0
- package/src/canonical-bridge.ts +69 -1
- package/src/canonical-direction.test.ts +118 -0
- package/src/canonical-direction.ts +43 -2
- package/src/codes/__tests__/codes.test.ts +14 -1
- package/src/codes/codes.ts +60 -0
- package/src/config.ts +20 -0
- package/src/facts/builders.ts +35 -0
- package/src/facts/compile.ts +135 -21
- package/src/facts/fact-index.ts +22 -2
- package/src/facts/facts.test.ts +19 -19
- package/src/facts/index.ts +9 -6
- package/src/facts/types.ts +45 -9
- package/src/governance-integrity.test.ts +277 -1
- package/src/governance-integrity.ts +616 -0
- package/src/governance.test.ts +20 -1
- package/src/governance.ts +131 -0
- package/src/index.ts +45 -2
- package/src/policy-exclude.ts +113 -0
- package/src/rules/families.test.ts +69 -0
- package/src/rules/families.ts +52 -0
- package/src/rules/index.ts +6 -0
- package/src/rules/jsx-preferred-import-path.ts +29 -11
- package/src/rules/rules.test.ts +125 -1
- package/src/rules/styles-no-raw-color.ts +13 -4
- package/src/rules/styles-no-raw-dimensions.ts +13 -4
- package/src/rules/styles-no-raw-spacing.test.ts +48 -0
- package/src/rules/styles-no-raw-spacing.ts +15 -7
- package/src/rules/styles-no-raw-typography.ts +13 -4
- package/src/rules/utils.ts +39 -0
- package/src/types.ts +21 -3
- package/dist/chunk-AOG4FTV6.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-
|
|
129
|
+
} from "./chunk-WVFNDPM4.js";
|
|
116
130
|
import {
|
|
117
131
|
AGENT_FORMAT_SCHEMA_VERSION,
|
|
118
132
|
agentErrorEnvelopeSchema,
|
|
@@ -1841,9 +1855,14 @@ var governedFragmentDefinitionSchema = z4.object({
|
|
|
1841
1855
|
var recipeDefinitionSchema = blockDefinitionSchema;
|
|
1842
1856
|
|
|
1843
1857
|
// src/config.ts
|
|
1858
|
+
var RAW_CONFIG_DECLARATION = /* @__PURE__ */ Symbol.for("@usefragments/core/raw-config-declaration");
|
|
1844
1859
|
function formatZodErrors(errors) {
|
|
1845
1860
|
return errors.map((error) => ` - ${error.path.join(".")}: ${error.message}`).join("\n");
|
|
1846
1861
|
}
|
|
1862
|
+
function configDeclarationForDiagnostics(config) {
|
|
1863
|
+
if (!config || typeof config !== "object") return config;
|
|
1864
|
+
return config[RAW_CONFIG_DECLARATION] ?? config;
|
|
1865
|
+
}
|
|
1847
1866
|
function defineConfig(config) {
|
|
1848
1867
|
const normalized = normalizeGovernanceConfig(config);
|
|
1849
1868
|
const result = fragmentsConfigSchema.safeParse(normalized);
|
|
@@ -1851,6 +1870,12 @@ function defineConfig(config) {
|
|
|
1851
1870
|
throw new Error(`Invalid fragments config:
|
|
1852
1871
|
${formatZodErrors(result.error.errors)}`);
|
|
1853
1872
|
}
|
|
1873
|
+
Object.defineProperty(result.data, RAW_CONFIG_DECLARATION, {
|
|
1874
|
+
value: normalized,
|
|
1875
|
+
enumerable: false,
|
|
1876
|
+
configurable: false,
|
|
1877
|
+
writable: false
|
|
1878
|
+
});
|
|
1854
1879
|
return result.data;
|
|
1855
1880
|
}
|
|
1856
1881
|
|
|
@@ -4485,6 +4510,19 @@ function readUsageNode(ix, id) {
|
|
|
4485
4510
|
const fact = ix.get(id);
|
|
4486
4511
|
return fact && fact.kind === "usage_node" ? fact : void 0;
|
|
4487
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
|
+
}
|
|
4488
4526
|
function contractPrefixFamilies(names) {
|
|
4489
4527
|
const families = /* @__PURE__ */ new Set();
|
|
4490
4528
|
for (const name of names) {
|
|
@@ -4966,7 +5004,7 @@ function projectCanonicalDirectionConflicts(ix, canonicalSources) {
|
|
|
4966
5004
|
const conflicts = [];
|
|
4967
5005
|
for (const definition of ix.byKind("component_definition")) {
|
|
4968
5006
|
if (!definition.exported || definition.renderRoot.resolution !== "canonical") continue;
|
|
4969
|
-
const target = npmTarget(definition.renderRoot
|
|
5007
|
+
const target = npmTarget(definition.renderRoot, npmSources);
|
|
4970
5008
|
if (!target) continue;
|
|
4971
5009
|
const identity = identityByComponent.get(definition.componentKey);
|
|
4972
5010
|
if (identity?.decisionId && decisionTargetsExport(identity.canonicalTarget, target.exportName)) {
|
|
@@ -5000,7 +5038,10 @@ function canonicalDirectionConflictsTarget(conflicts, importPath, exportName) {
|
|
|
5000
5038
|
(conflict) => ownedImportMatchesRoot(importPath, conflict.packageName) && conflict.exportName === exportName
|
|
5001
5039
|
);
|
|
5002
5040
|
}
|
|
5003
|
-
function npmTarget(
|
|
5041
|
+
function npmTarget(renderRoot, sources) {
|
|
5042
|
+
return npmTargetFromCanonicalKey(renderRoot.canonical, sources) ?? npmTargetFromImportSource(renderRoot, sources);
|
|
5043
|
+
}
|
|
5044
|
+
function npmTargetFromCanonicalKey(canonicalTarget, sources) {
|
|
5004
5045
|
const separator = canonicalTarget.lastIndexOf("#");
|
|
5005
5046
|
if (separator <= 0 || separator === canonicalTarget.length - 1) return null;
|
|
5006
5047
|
const importPath = canonicalTarget.slice(0, separator);
|
|
@@ -5017,6 +5058,19 @@ function npmTarget(canonicalTarget, sources) {
|
|
|
5017
5058
|
}
|
|
5018
5059
|
return null;
|
|
5019
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
|
+
}
|
|
5020
5074
|
function packageSubpath(importPath, packageName) {
|
|
5021
5075
|
return importPath === packageName ? "" : importPath.slice(packageName.length + 1);
|
|
5022
5076
|
}
|
|
@@ -6217,19 +6271,15 @@ function ruleJsxPreferredImportPath(ix) {
|
|
|
6217
6271
|
const findings = [];
|
|
6218
6272
|
const seen = /* @__PURE__ */ new Set();
|
|
6219
6273
|
for (const usage of ix.byKind("usage_import")) {
|
|
6274
|
+
const bridgeGoverned = policies.some((policy) => bridgeGovernsUnderlyingImport(policy, usage));
|
|
6220
6275
|
const matches = policies.filter((policy) => {
|
|
6221
6276
|
if (policy.bridge) {
|
|
6222
6277
|
if (policy.bridge.implementationFiles.some((file) => sameRepoPath(file, usage.file))) {
|
|
6223
6278
|
return false;
|
|
6224
6279
|
}
|
|
6225
|
-
|
|
6226
|
-
if (ownedImportsEqual(usage.source, policy.from)) {
|
|
6227
|
-
return usage.imported === policy.bridge.underlyingExportName;
|
|
6228
|
-
}
|
|
6229
|
-
const subpath = usage.source.slice(policy.from.length + 1);
|
|
6230
|
-
const leaf = subpath.split("/").at(-1);
|
|
6231
|
-
return leaf === policy.bridge.underlyingExportName && (usage.imported === "default" || usage.imported === policy.bridge.underlyingExportName);
|
|
6280
|
+
return bridgeGovernsUnderlyingImport(policy, usage);
|
|
6232
6281
|
}
|
|
6282
|
+
if (bridgeGoverned) return false;
|
|
6233
6283
|
if (!ownedImportsEqual(policy.from, usage.source)) return false;
|
|
6234
6284
|
if (policy.imported !== void 0 && policy.imported !== usage.imported) return false;
|
|
6235
6285
|
return true;
|
|
@@ -6283,6 +6333,17 @@ function ruleJsxPreferredImportPath(ix) {
|
|
|
6283
6333
|
}
|
|
6284
6334
|
return findings;
|
|
6285
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
|
+
}
|
|
6286
6347
|
function sameRepoPath(left, right) {
|
|
6287
6348
|
return normalizeRepoPath(left) === normalizeRepoPath(right);
|
|
6288
6349
|
}
|
|
@@ -6538,14 +6599,15 @@ function ruleStylesNoRawColor(ix) {
|
|
|
6538
6599
|
if (!color) continue;
|
|
6539
6600
|
if (isExemptColor(color, policy.except)) continue;
|
|
6540
6601
|
const resolution = resolveColorToken(decl.property, color, colorTokens);
|
|
6541
|
-
const
|
|
6602
|
+
const hopped = hoppedValue(decl);
|
|
6603
|
+
const fixEmission = resolution?.kind === "exact" && !hopped ? buildTokenFix(decl.property, decl.value, color, resolution, ix) : void 0;
|
|
6542
6604
|
const evidenceIds = resolution ? [decl.id, policy.id, resolution.token.id] : [decl.id, policy.id];
|
|
6543
6605
|
findings.push(
|
|
6544
6606
|
makeFinding({
|
|
6545
6607
|
ruleId: RULE_ID9,
|
|
6546
6608
|
ruleVersion: RULE_VERSION10,
|
|
6547
6609
|
severity: policy.severity,
|
|
6548
|
-
message: colorMessage(color, `\`${decl.property}\``, resolution, preferLabel),
|
|
6610
|
+
message: colorMessage(color, `\`${decl.property}\``, resolution, preferLabel) + (hopped?.suffix ?? ""),
|
|
6549
6611
|
location: decl.location,
|
|
6550
6612
|
evidence: ix.evidence(evidenceIds),
|
|
6551
6613
|
fingerprintIdentity: {
|
|
@@ -6564,7 +6626,8 @@ function ruleStylesNoRawColor(ix) {
|
|
|
6564
6626
|
source: "css",
|
|
6565
6627
|
suggestedToken: resolution?.token.name,
|
|
6566
6628
|
...resolution ? { tokenMatch: resolution.kind } : {},
|
|
6567
|
-
...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {}
|
|
6629
|
+
...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {},
|
|
6630
|
+
...hopped?.attributes ?? {}
|
|
6568
6631
|
}
|
|
6569
6632
|
})
|
|
6570
6633
|
);
|
|
@@ -6610,7 +6673,8 @@ function ruleStylesNoRawColor(ix) {
|
|
|
6610
6673
|
if (!node) continue;
|
|
6611
6674
|
const componentEvidenceId = componentByNode.get(node.id)?.id;
|
|
6612
6675
|
const resolution = resolveColorToken(inline.property, color, colorTokens);
|
|
6613
|
-
const
|
|
6676
|
+
const hopped = hoppedValue(inline);
|
|
6677
|
+
const fixEmission = resolution?.kind === "exact" && !hopped ? buildTokenFix(inline.property, inline.value, color, resolution, ix) : void 0;
|
|
6614
6678
|
const baseEvidence = componentEvidenceId ? [node.id, componentEvidenceId, inline.id, policy.id] : [node.id, inline.id, policy.id];
|
|
6615
6679
|
const evidenceIds = resolution ? [...baseEvidence, resolution.token.id] : baseEvidence;
|
|
6616
6680
|
findings.push(
|
|
@@ -6618,7 +6682,7 @@ function ruleStylesNoRawColor(ix) {
|
|
|
6618
6682
|
ruleId: RULE_ID9,
|
|
6619
6683
|
ruleVersion: RULE_VERSION10,
|
|
6620
6684
|
severity: policy.severity,
|
|
6621
|
-
message: colorMessage(color, `inline \`${inline.property}\``, resolution, preferLabel),
|
|
6685
|
+
message: colorMessage(color, `inline \`${inline.property}\``, resolution, preferLabel) + (hopped?.suffix ?? ""),
|
|
6622
6686
|
location: node.location,
|
|
6623
6687
|
evidence: ix.evidence(evidenceIds),
|
|
6624
6688
|
fingerprintIdentity: {
|
|
@@ -6637,7 +6701,8 @@ function ruleStylesNoRawColor(ix) {
|
|
|
6637
6701
|
source: "jsx",
|
|
6638
6702
|
suggestedToken: resolution?.token.name,
|
|
6639
6703
|
...resolution ? { tokenMatch: resolution.kind } : {},
|
|
6640
|
-
...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {}
|
|
6704
|
+
...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {},
|
|
6705
|
+
...hopped?.attributes ?? {}
|
|
6641
6706
|
}
|
|
6642
6707
|
})
|
|
6643
6708
|
);
|
|
@@ -6829,13 +6894,14 @@ function ruleStylesNoRawDimensions(ix) {
|
|
|
6829
6894
|
if (!resolution) continue;
|
|
6830
6895
|
const tokenIds = resolutionTokenIds(resolution);
|
|
6831
6896
|
const evidenceIds = tokenIds.length ? [decl.id, policy.id, ...tokenIds] : [decl.id, policy.id];
|
|
6832
|
-
const
|
|
6897
|
+
const hopped = hoppedValue(decl);
|
|
6898
|
+
const fixEmission = hopped ? void 0 : dimensionFix(decl.property, resolution, ix);
|
|
6833
6899
|
findings.push(
|
|
6834
6900
|
makeFinding({
|
|
6835
6901
|
ruleId: RULE_ID10,
|
|
6836
6902
|
ruleVersion: RULE_VERSION11,
|
|
6837
6903
|
severity: policy.severity,
|
|
6838
|
-
message: dimensionMessage(decl.value, decl.property, preferLabel, resolution, false),
|
|
6904
|
+
message: dimensionMessage(decl.value, decl.property, preferLabel, resolution, false) + (hopped?.suffix ?? ""),
|
|
6839
6905
|
location: decl.location,
|
|
6840
6906
|
evidence: ix.evidence(evidenceIds),
|
|
6841
6907
|
fingerprintIdentity: {
|
|
@@ -6852,7 +6918,8 @@ function ruleStylesNoRawDimensions(ix) {
|
|
|
6852
6918
|
rawValue: decl.value,
|
|
6853
6919
|
source: "css",
|
|
6854
6920
|
suggestedToken: resolutionSuggestedToken(resolution),
|
|
6855
|
-
...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {}
|
|
6921
|
+
...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {},
|
|
6922
|
+
...hopped?.attributes ?? {}
|
|
6856
6923
|
}
|
|
6857
6924
|
})
|
|
6858
6925
|
);
|
|
@@ -6871,13 +6938,14 @@ function ruleStylesNoRawDimensions(ix) {
|
|
|
6871
6938
|
const baseEvidence = componentEvidenceId ? [node.id, componentEvidenceId, inline.id, policy.id] : [node.id, inline.id, policy.id];
|
|
6872
6939
|
const tokenIds = resolutionTokenIds(resolution);
|
|
6873
6940
|
const evidenceIds = tokenIds.length ? [...baseEvidence, ...tokenIds] : baseEvidence;
|
|
6874
|
-
const
|
|
6941
|
+
const hopped = hoppedValue(inline);
|
|
6942
|
+
const fixEmission = hopped ? void 0 : dimensionFix(inline.property, resolution, ix);
|
|
6875
6943
|
findings.push(
|
|
6876
6944
|
makeFinding({
|
|
6877
6945
|
ruleId: RULE_ID10,
|
|
6878
6946
|
ruleVersion: RULE_VERSION11,
|
|
6879
6947
|
severity: policy.severity,
|
|
6880
|
-
message: dimensionMessage(inline.value, inline.property, preferLabel, resolution, true),
|
|
6948
|
+
message: dimensionMessage(inline.value, inline.property, preferLabel, resolution, true) + (hopped?.suffix ?? ""),
|
|
6881
6949
|
location: node.location,
|
|
6882
6950
|
evidence: ix.evidence(evidenceIds),
|
|
6883
6951
|
fingerprintIdentity: {
|
|
@@ -6894,7 +6962,8 @@ function ruleStylesNoRawDimensions(ix) {
|
|
|
6894
6962
|
rawValue: inline.value,
|
|
6895
6963
|
source: "jsx",
|
|
6896
6964
|
suggestedToken: resolutionSuggestedToken(resolution),
|
|
6897
|
-
...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {}
|
|
6965
|
+
...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {},
|
|
6966
|
+
...hopped?.attributes ?? {}
|
|
6898
6967
|
}
|
|
6899
6968
|
})
|
|
6900
6969
|
);
|
|
@@ -7184,12 +7253,13 @@ function checkDeclaration(ix, decl, lookupFor) {
|
|
|
7184
7253
|
tokens: lookupFor(scale)
|
|
7185
7254
|
});
|
|
7186
7255
|
if (!checked) return null;
|
|
7187
|
-
const
|
|
7256
|
+
const hopped = hoppedValue(decl);
|
|
7257
|
+
const fixEmission = hopped ? void 0 : buildSpacingFix(decl.property, checked, ix);
|
|
7188
7258
|
return makeFinding({
|
|
7189
7259
|
ruleId: RULE_ID11,
|
|
7190
7260
|
ruleVersion: RULE_VERSION12,
|
|
7191
7261
|
severity: policy.severity,
|
|
7192
|
-
message: spacingMessage(decl.property, decl.value, allowed, scale
|
|
7262
|
+
message: spacingMessage(decl.property, decl.value, allowed, scale, checked) + (hopped?.suffix ?? ""),
|
|
7193
7263
|
location: decl.location,
|
|
7194
7264
|
evidence: ix.evidence([decl.id, policy.id, scale.id]),
|
|
7195
7265
|
fingerprintIdentity: {
|
|
@@ -7215,7 +7285,8 @@ function checkDeclaration(ix, decl, lookupFor) {
|
|
|
7215
7285
|
assumedRootFontSizePx: checked.assumedRootFontSizePx,
|
|
7216
7286
|
assumedEmBasePx: checked.assumedEmBasePx,
|
|
7217
7287
|
source: "css",
|
|
7218
|
-
...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {}
|
|
7288
|
+
...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {},
|
|
7289
|
+
...hopped?.attributes ?? {}
|
|
7219
7290
|
}
|
|
7220
7291
|
});
|
|
7221
7292
|
}
|
|
@@ -7235,7 +7306,8 @@ function checkInlineStyle(ix, inline, componentByNode, lookupFor) {
|
|
|
7235
7306
|
bareNumberFix: inline.valueKind === "number"
|
|
7236
7307
|
});
|
|
7237
7308
|
if (!checked) return null;
|
|
7238
|
-
const
|
|
7309
|
+
const hopped = hoppedValue(inline);
|
|
7310
|
+
const fixEmission = hopped ? void 0 : buildSpacingFix(inline.property, checked, ix);
|
|
7239
7311
|
const node = readUsageNode(ix, inline.nodeId);
|
|
7240
7312
|
if (!node) return null;
|
|
7241
7313
|
const componentEvidenceId = componentByNode.get(node.id)?.id;
|
|
@@ -7244,7 +7316,7 @@ function checkInlineStyle(ix, inline, componentByNode, lookupFor) {
|
|
|
7244
7316
|
ruleId: RULE_ID11,
|
|
7245
7317
|
ruleVersion: RULE_VERSION12,
|
|
7246
7318
|
severity: policy.severity,
|
|
7247
|
-
message: spacingMessage(inline.property, inline.value, allowed, scale
|
|
7319
|
+
message: spacingMessage(inline.property, inline.value, allowed, scale, checked) + (hopped?.suffix ?? ""),
|
|
7248
7320
|
location: node.location,
|
|
7249
7321
|
evidence: ix.evidence(evidenceIds),
|
|
7250
7322
|
fingerprintIdentity: {
|
|
@@ -7270,7 +7342,8 @@ function checkInlineStyle(ix, inline, componentByNode, lookupFor) {
|
|
|
7270
7342
|
assumedRootFontSizePx: checked.assumedRootFontSizePx,
|
|
7271
7343
|
assumedEmBasePx: checked.assumedEmBasePx,
|
|
7272
7344
|
source: "jsx",
|
|
7273
|
-
...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {}
|
|
7345
|
+
...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {},
|
|
7346
|
+
...hopped?.attributes ?? {}
|
|
7274
7347
|
}
|
|
7275
7348
|
});
|
|
7276
7349
|
}
|
|
@@ -7292,14 +7365,14 @@ function buildSpacingFix(property, checked, ix) {
|
|
|
7292
7365
|
ix
|
|
7293
7366
|
);
|
|
7294
7367
|
}
|
|
7295
|
-
function spacingMessage(property, value, allowed,
|
|
7368
|
+
function spacingMessage(property, value, allowed, scale, checked) {
|
|
7296
7369
|
if (checked.reason === "token-equivalent" && checked.matchedToken) {
|
|
7297
7370
|
return `\`${property}: ${value}\` is a raw literal that matches token \`${checked.matchedToken}\`. Reference the token instead.`;
|
|
7298
7371
|
}
|
|
7299
7372
|
const sample = allowed.slice().sort((a, b) => a - b).slice(0, 6);
|
|
7300
|
-
const suffix = `${sample.join(unit + ", ")}${unit}`;
|
|
7373
|
+
const suffix = `${sample.join(scale.unit + ", ")}${scale.unit}`;
|
|
7301
7374
|
const ellipsis = allowed.length > sample.length ? ", \u2026" : "";
|
|
7302
|
-
return `\`${property}: ${value}\` is not on the
|
|
7375
|
+
return `\`${property}: ${value}\` is not on the \`${scale.name}\` scale. Allowed: ${suffix}${ellipsis}. Configure \`govern.scales.${scale.name}\` to change it.`;
|
|
7303
7376
|
}
|
|
7304
7377
|
|
|
7305
7378
|
// src/rules/styles-no-raw-typography.ts
|
|
@@ -7319,13 +7392,14 @@ function ruleStylesNoRawTypography(ix) {
|
|
|
7319
7392
|
if (decl.property.toLowerCase() !== "font-size") continue;
|
|
7320
7393
|
const checked = checkFontSize(decl.value, allowed, scale, tokens);
|
|
7321
7394
|
if (!checked) continue;
|
|
7322
|
-
const
|
|
7395
|
+
const hopped = hoppedValue(decl);
|
|
7396
|
+
const fixEmission = hopped ? void 0 : buildFix(decl.property, checked, ix);
|
|
7323
7397
|
findings.push(
|
|
7324
7398
|
makeFinding({
|
|
7325
7399
|
ruleId: RULE_ID12,
|
|
7326
7400
|
ruleVersion: RULE_VERSION13,
|
|
7327
7401
|
severity: policy.severity,
|
|
7328
|
-
message: typographyMessage(decl.property, decl.value, allowed, scale.unit, checked),
|
|
7402
|
+
message: typographyMessage(decl.property, decl.value, allowed, scale.unit, checked) + (hopped?.suffix ?? ""),
|
|
7329
7403
|
location: decl.location,
|
|
7330
7404
|
evidence: ix.evidence([decl.id, policy.id, scale.id]),
|
|
7331
7405
|
fingerprintIdentity: {
|
|
@@ -7345,7 +7419,8 @@ function ruleStylesNoRawTypography(ix) {
|
|
|
7345
7419
|
suggestedValue: checked.suggestedValue,
|
|
7346
7420
|
matchedToken: checked.matchedToken,
|
|
7347
7421
|
source: "css",
|
|
7348
|
-
...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {}
|
|
7422
|
+
...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {},
|
|
7423
|
+
...hopped?.attributes ?? {}
|
|
7349
7424
|
}
|
|
7350
7425
|
})
|
|
7351
7426
|
);
|
|
@@ -7362,7 +7437,8 @@ function ruleStylesNoRawTypography(ix) {
|
|
|
7362
7437
|
inline.valueKind === "number"
|
|
7363
7438
|
);
|
|
7364
7439
|
if (!checked) continue;
|
|
7365
|
-
const
|
|
7440
|
+
const hopped = hoppedValue(inline);
|
|
7441
|
+
const fixEmission = hopped ? void 0 : buildFix(inline.property, checked, ix);
|
|
7366
7442
|
const node = readUsageNode(ix, inline.nodeId);
|
|
7367
7443
|
if (!node) continue;
|
|
7368
7444
|
const componentEvidenceId = componentByNode.get(node.id)?.id;
|
|
@@ -7372,7 +7448,7 @@ function ruleStylesNoRawTypography(ix) {
|
|
|
7372
7448
|
ruleId: RULE_ID12,
|
|
7373
7449
|
ruleVersion: RULE_VERSION13,
|
|
7374
7450
|
severity: policy.severity,
|
|
7375
|
-
message: typographyMessage(inline.property, inline.value, allowed, scale.unit, checked),
|
|
7451
|
+
message: typographyMessage(inline.property, inline.value, allowed, scale.unit, checked) + (hopped?.suffix ?? ""),
|
|
7376
7452
|
location: node.location,
|
|
7377
7453
|
evidence: ix.evidence(evidenceIds),
|
|
7378
7454
|
fingerprintIdentity: {
|
|
@@ -7392,7 +7468,8 @@ function ruleStylesNoRawTypography(ix) {
|
|
|
7392
7468
|
suggestedValue: checked.suggestedValue,
|
|
7393
7469
|
matchedToken: checked.matchedToken,
|
|
7394
7470
|
source: "jsx",
|
|
7395
|
-
...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {}
|
|
7471
|
+
...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {},
|
|
7472
|
+
...hopped?.attributes ?? {}
|
|
7396
7473
|
}
|
|
7397
7474
|
})
|
|
7398
7475
|
);
|
|
@@ -8612,6 +8689,374 @@ function policyDeclaresCssVars(policy) {
|
|
|
8612
8689
|
function dedupe(values) {
|
|
8613
8690
|
return [...new Set(values)];
|
|
8614
8691
|
}
|
|
8692
|
+
var CONSUMED_RULE_IDS = new Set(Object.keys(RULE_TIER));
|
|
8693
|
+
var RECOGNIZED_RULE_IDS = /* @__PURE__ */ new Set([...CONSUMED_RULE_IDS, ...RULE_FAMILY_IDS]);
|
|
8694
|
+
var RULE_CONFIG_KEYS = /* @__PURE__ */ new Set(["enabled", "severity", "options", "exclude"]);
|
|
8695
|
+
var PASSTHROUGH_KEYS = [
|
|
8696
|
+
{
|
|
8697
|
+
path: ["tokens"],
|
|
8698
|
+
keys: [
|
|
8699
|
+
"include",
|
|
8700
|
+
"sources",
|
|
8701
|
+
"packages",
|
|
8702
|
+
"aliases",
|
|
8703
|
+
"upstream",
|
|
8704
|
+
"exclude",
|
|
8705
|
+
"themeSelectors",
|
|
8706
|
+
"enabled",
|
|
8707
|
+
"format",
|
|
8708
|
+
"namespace"
|
|
8709
|
+
]
|
|
8710
|
+
},
|
|
8711
|
+
{
|
|
8712
|
+
path: ["screenshots"],
|
|
8713
|
+
keys: ["viewport", "threshold", "delay", "outputDir", "themes"]
|
|
8714
|
+
},
|
|
8715
|
+
{
|
|
8716
|
+
path: ["service"],
|
|
8717
|
+
keys: ["poolSize", "idleTimeout"]
|
|
8718
|
+
},
|
|
8719
|
+
{
|
|
8720
|
+
path: ["registry"],
|
|
8721
|
+
keys: ["requireStory", "publicOnly", "categoryDepth", "includeProps", "embedFragments"]
|
|
8722
|
+
},
|
|
8723
|
+
{
|
|
8724
|
+
path: ["govern", "tailwind"],
|
|
8725
|
+
keys: ["palette"]
|
|
8726
|
+
},
|
|
8727
|
+
{
|
|
8728
|
+
path: ["govern", "agent"],
|
|
8729
|
+
keys: ["repairOrder"]
|
|
8730
|
+
},
|
|
8731
|
+
{
|
|
8732
|
+
path: ["govern", "ci"],
|
|
8733
|
+
keys: ["failOnWarnings", "failOnInert"]
|
|
8734
|
+
}
|
|
8735
|
+
];
|
|
8736
|
+
function objectRecord(value) {
|
|
8737
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
8738
|
+
return value;
|
|
8739
|
+
}
|
|
8740
|
+
function valueAtPath(value, path) {
|
|
8741
|
+
let current = value;
|
|
8742
|
+
for (const segment of path) {
|
|
8743
|
+
const record = objectRecord(current);
|
|
8744
|
+
if (!record) return void 0;
|
|
8745
|
+
current = record[segment];
|
|
8746
|
+
}
|
|
8747
|
+
return current;
|
|
8748
|
+
}
|
|
8749
|
+
function configPath(path) {
|
|
8750
|
+
return path.reduce(
|
|
8751
|
+
(output, segment) => typeof segment === "number" ? `${output}[${segment}]` : output ? `${output}.${segment}` : segment,
|
|
8752
|
+
""
|
|
8753
|
+
);
|
|
8754
|
+
}
|
|
8755
|
+
function unconsumedKeyDiagnostic(path) {
|
|
8756
|
+
const renderedPath = configPath(path);
|
|
8757
|
+
return {
|
|
8758
|
+
code: "FUI9004",
|
|
8759
|
+
kind: "unconsumed-key",
|
|
8760
|
+
severity: "warn",
|
|
8761
|
+
path: renderedPath,
|
|
8762
|
+
message: `${renderedPath} is not consumed by Fragments and has no effect. Remove it or use a supported config key.`
|
|
8763
|
+
};
|
|
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
|
+
}
|
|
8898
|
+
function collectStrippedKeys(authored, parsed, path, diagnostics) {
|
|
8899
|
+
if (Array.isArray(authored)) {
|
|
8900
|
+
if (!Array.isArray(parsed)) return;
|
|
8901
|
+
authored.forEach((item, index) => {
|
|
8902
|
+
collectStrippedKeys(item, parsed[index], [...path, index], diagnostics);
|
|
8903
|
+
});
|
|
8904
|
+
return;
|
|
8905
|
+
}
|
|
8906
|
+
const authoredRecord = objectRecord(authored);
|
|
8907
|
+
const parsedRecord = objectRecord(parsed);
|
|
8908
|
+
if (!authoredRecord || !parsedRecord) return;
|
|
8909
|
+
for (const key of Object.keys(authoredRecord).sort()) {
|
|
8910
|
+
if (!Object.prototype.hasOwnProperty.call(parsedRecord, key)) {
|
|
8911
|
+
diagnostics.push(unconsumedKeyDiagnostic([...path, key]));
|
|
8912
|
+
continue;
|
|
8913
|
+
}
|
|
8914
|
+
collectStrippedKeys(authoredRecord[key], parsedRecord[key], [...path, key], diagnostics);
|
|
8915
|
+
}
|
|
8916
|
+
}
|
|
8917
|
+
function collectPassthroughKeys(authored, path, allowed, diagnostics) {
|
|
8918
|
+
const record = objectRecord(valueAtPath(authored, path));
|
|
8919
|
+
if (!record) return;
|
|
8920
|
+
for (const key of Object.keys(record).sort()) {
|
|
8921
|
+
if (!allowed.has(key)) diagnostics.push(unconsumedKeyDiagnostic([...path, key]));
|
|
8922
|
+
}
|
|
8923
|
+
}
|
|
8924
|
+
function collectRuleConfigKeys(authored, path, diagnostics) {
|
|
8925
|
+
const rules = objectRecord(valueAtPath(authored, path));
|
|
8926
|
+
if (!rules) return;
|
|
8927
|
+
for (const ruleId of Object.keys(rules).sort()) {
|
|
8928
|
+
if (!RECOGNIZED_RULE_IDS.has(ruleId)) {
|
|
8929
|
+
diagnostics.push(unconsumedKeyDiagnostic([...path, ruleId]));
|
|
8930
|
+
continue;
|
|
8931
|
+
}
|
|
8932
|
+
const config = objectRecord(rules[ruleId]);
|
|
8933
|
+
if (!config) continue;
|
|
8934
|
+
for (const key of Object.keys(config).sort()) {
|
|
8935
|
+
if (!RULE_CONFIG_KEYS.has(key)) {
|
|
8936
|
+
diagnostics.push(unconsumedKeyDiagnostic([...path, ruleId, key]));
|
|
8937
|
+
}
|
|
8938
|
+
}
|
|
8939
|
+
}
|
|
8940
|
+
}
|
|
8941
|
+
function collectPassthroughRecordValues(authored, path, allowed, diagnostics) {
|
|
8942
|
+
const entries = objectRecord(valueAtPath(authored, path));
|
|
8943
|
+
if (!entries) return;
|
|
8944
|
+
for (const [entryName, value] of Object.entries(entries).sort(
|
|
8945
|
+
([left], [right]) => left.localeCompare(right)
|
|
8946
|
+
)) {
|
|
8947
|
+
const record = objectRecord(value);
|
|
8948
|
+
if (!record) continue;
|
|
8949
|
+
for (const key of Object.keys(record).sort()) {
|
|
8950
|
+
if (!allowed.has(key)) {
|
|
8951
|
+
diagnostics.push(unconsumedKeyDiagnostic([...path, entryName, key]));
|
|
8952
|
+
}
|
|
8953
|
+
}
|
|
8954
|
+
}
|
|
8955
|
+
}
|
|
8956
|
+
function stableConfigDiagnostics(diagnostics) {
|
|
8957
|
+
const unique = /* @__PURE__ */ new Map();
|
|
8958
|
+
for (const diagnostic of diagnostics) {
|
|
8959
|
+
unique.set(`${diagnostic.code}\0${diagnostic.path}`, diagnostic);
|
|
8960
|
+
}
|
|
8961
|
+
return [...unique.values()].sort(
|
|
8962
|
+
(left, right) => left.path.localeCompare(right.path) || left.code.localeCompare(right.code) || left.message.localeCompare(right.message)
|
|
8963
|
+
);
|
|
8964
|
+
}
|
|
8965
|
+
function detectUnconsumedConfigKeys(authoredConfig, parsedConfig) {
|
|
8966
|
+
const diagnostics = [];
|
|
8967
|
+
collectStrippedKeys(authoredConfig, parsedConfig, [], diagnostics);
|
|
8968
|
+
for (const boundary of PASSTHROUGH_KEYS) {
|
|
8969
|
+
collectPassthroughKeys(
|
|
8970
|
+
authoredConfig,
|
|
8971
|
+
boundary.path,
|
|
8972
|
+
new Set(boundary.keys),
|
|
8973
|
+
diagnostics
|
|
8974
|
+
);
|
|
8975
|
+
}
|
|
8976
|
+
collectRuleConfigKeys(authoredConfig, ["govern", "rules"], diagnostics);
|
|
8977
|
+
collectPassthroughRecordValues(
|
|
8978
|
+
authoredConfig,
|
|
8979
|
+
["govern", "agents"],
|
|
8980
|
+
/* @__PURE__ */ new Set(["rules"]),
|
|
8981
|
+
diagnostics
|
|
8982
|
+
);
|
|
8983
|
+
collectPassthroughKeys(authoredConfig, ["govern", "audit"], /* @__PURE__ */ new Set(), diagnostics);
|
|
8984
|
+
collectPassthroughRecordValues(authoredConfig, ["govern", "runners"], /* @__PURE__ */ new Set(), diagnostics);
|
|
8985
|
+
return stableConfigDiagnostics(diagnostics);
|
|
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
|
+
}
|
|
9022
|
+
function effectiveScaleBindings(policy) {
|
|
9023
|
+
const bindings = [];
|
|
9024
|
+
for (const style of policy?.styles ?? []) {
|
|
9025
|
+
if (style.kind === "style.rawSpacing.mustMatchScale") {
|
|
9026
|
+
bindings.push({
|
|
9027
|
+
label: "Spacing properties",
|
|
9028
|
+
mechanism: "style.rawSpacing.mustMatchScale",
|
|
9029
|
+
scale: style.scale
|
|
9030
|
+
});
|
|
9031
|
+
} else if (style.kind === "style.fontSize.mustMatchScale") {
|
|
9032
|
+
bindings.push({
|
|
9033
|
+
label: "Font-size properties",
|
|
9034
|
+
mechanism: "style.fontSize.mustMatchScale",
|
|
9035
|
+
scale: style.scale
|
|
9036
|
+
});
|
|
9037
|
+
}
|
|
9038
|
+
}
|
|
9039
|
+
return bindings;
|
|
9040
|
+
}
|
|
9041
|
+
function detectOrphanGovernanceScales(declaredPolicy, effectivePolicy) {
|
|
9042
|
+
const bindings = effectiveScaleBindings(effectivePolicy);
|
|
9043
|
+
const referenced = new Set(bindings.map((binding) => binding.scale));
|
|
9044
|
+
const diagnostics = [];
|
|
9045
|
+
for (const scale of Object.keys(declaredPolicy?.scales ?? {}).sort()) {
|
|
9046
|
+
if (referenced.has(scale)) continue;
|
|
9047
|
+
const path = `govern.scales.${scale}`;
|
|
9048
|
+
const preferred = (scale.toLowerCase().includes("spac") ? bindings.find((binding) => binding.mechanism === "style.rawSpacing.mustMatchScale") : void 0) ?? (scale.toLowerCase().includes("font") ? bindings.find((binding) => binding.mechanism === "style.fontSize.mustMatchScale") : void 0) ?? bindings[0];
|
|
9049
|
+
const remediation = preferred ? `${preferred.label} are bound to the scale named "${preferred.scale}" \u2014 rename the key to "${preferred.scale}" or bind it via ${preferred.mechanism}.` : `Bind it via style.rawSpacing.mustMatchScale or style.fontSize.mustMatchScale, or remove it.`;
|
|
9050
|
+
diagnostics.push({
|
|
9051
|
+
code: "FUI9005",
|
|
9052
|
+
kind: "orphan-scale",
|
|
9053
|
+
severity: "warn",
|
|
9054
|
+
path,
|
|
9055
|
+
message: `${path} is not referenced by any property policy. ${remediation}`
|
|
9056
|
+
});
|
|
9057
|
+
}
|
|
9058
|
+
return stableConfigDiagnostics(diagnostics);
|
|
9059
|
+
}
|
|
8615
9060
|
function summarize(status, flags) {
|
|
8616
9061
|
if (status === "inert") {
|
|
8617
9062
|
return "Governance inert \u2014 no enforceable rule family is armed";
|
|
@@ -8627,6 +9072,7 @@ function summarize(status, flags) {
|
|
|
8627
9072
|
}
|
|
8628
9073
|
function evaluateGovernanceIntegrity(input) {
|
|
8629
9074
|
const configs = effectiveRuleConfigs(input.policy);
|
|
9075
|
+
const configDiagnostics = stableConfigDiagnostics(input.configDiagnostics ?? []);
|
|
8630
9076
|
const policyArmed = input.policy !== void 0 && input.policySource !== "none";
|
|
8631
9077
|
const policyFamily = {
|
|
8632
9078
|
id: "policy",
|
|
@@ -8727,6 +9173,17 @@ function evaluateGovernanceIntegrity(input) {
|
|
|
8727
9173
|
families.map((family) => family.remediation).filter((remediation) => remediation !== void 0)
|
|
8728
9174
|
);
|
|
8729
9175
|
const summary = summarize(status, { componentsArmed, tokensArmed, blockingArmed });
|
|
9176
|
+
const configuredRuleCount = [...configs.keys()].filter(
|
|
9177
|
+
(ruleId) => CONSUMED_RULE_IDS.has(ruleId)
|
|
9178
|
+
).length;
|
|
9179
|
+
const activeRuleCount = [...configs.entries()].filter(
|
|
9180
|
+
([ruleId, config]) => CONSUMED_RULE_IDS.has(ruleId) && config.enabled
|
|
9181
|
+
).length;
|
|
9182
|
+
const roster = {
|
|
9183
|
+
configured: configuredRuleCount + configDiagnostics.length,
|
|
9184
|
+
active: activeRuleCount,
|
|
9185
|
+
inert: configDiagnostics.length
|
|
9186
|
+
};
|
|
8730
9187
|
return {
|
|
8731
9188
|
status,
|
|
8732
9189
|
declared: input.declared,
|
|
@@ -8735,7 +9192,9 @@ function evaluateGovernanceIntegrity(input) {
|
|
|
8735
9192
|
families,
|
|
8736
9193
|
armed,
|
|
8737
9194
|
summary,
|
|
8738
|
-
remediations
|
|
9195
|
+
remediations,
|
|
9196
|
+
configDiagnostics,
|
|
9197
|
+
roster
|
|
8739
9198
|
};
|
|
8740
9199
|
}
|
|
8741
9200
|
|
|
@@ -8756,6 +9215,19 @@ function canonicalBridgeContractMappings(bridges) {
|
|
|
8756
9215
|
}
|
|
8757
9216
|
}));
|
|
8758
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
|
+
}
|
|
8759
9231
|
|
|
8760
9232
|
// src/effective-governance-inputs.ts
|
|
8761
9233
|
var EFFECTIVE_GOVERNANCE_INPUTS_SCHEMA = "effective-governance-inputs:v1";
|
|
@@ -9322,6 +9794,8 @@ export {
|
|
|
9322
9794
|
REGISTRY_INSTALL_RECEIPT_SCHEMA_VERSION,
|
|
9323
9795
|
REGISTRY_MANIFEST_SCHEMA_VERSION,
|
|
9324
9796
|
RULES,
|
|
9797
|
+
RULE_FAMILY_IDS,
|
|
9798
|
+
RULE_FAMILY_MEMBERS,
|
|
9325
9799
|
RULE_TIER,
|
|
9326
9800
|
SEVERITIES,
|
|
9327
9801
|
SEVERITY_RANK,
|
|
@@ -9359,6 +9833,8 @@ export {
|
|
|
9359
9833
|
calculateDeltaE,
|
|
9360
9834
|
canBlock,
|
|
9361
9835
|
canonicalBridgeContractMappings,
|
|
9836
|
+
canonicalBridgeIdentityBindings,
|
|
9837
|
+
canonicalBridgeIdentitySources,
|
|
9362
9838
|
canonicalBridgeV1Schema,
|
|
9363
9839
|
canonicalDirectionConflictsTarget,
|
|
9364
9840
|
canonicalJson,
|
|
@@ -9373,6 +9849,8 @@ export {
|
|
|
9373
9849
|
classifyComplexity,
|
|
9374
9850
|
classifyIdentity,
|
|
9375
9851
|
collectRegistryInstallDependencies,
|
|
9852
|
+
collidingRecordDiagnostic,
|
|
9853
|
+
collidingRecordDiagnostics,
|
|
9376
9854
|
colorSimilarity,
|
|
9377
9855
|
compareByVocabularyRank,
|
|
9378
9856
|
compareColors,
|
|
@@ -9396,6 +9874,8 @@ export {
|
|
|
9396
9874
|
compositionMetadataSchema,
|
|
9397
9875
|
compositionPatternSchema,
|
|
9398
9876
|
computeRegistryHash,
|
|
9877
|
+
configDeclarationForDiagnostics,
|
|
9878
|
+
configRecordShape,
|
|
9399
9879
|
containsTailwindV4Theme,
|
|
9400
9880
|
contractComponentsFromFragments,
|
|
9401
9881
|
contractHash,
|
|
@@ -9406,12 +9886,17 @@ export {
|
|
|
9406
9886
|
defineConfig,
|
|
9407
9887
|
defineFragment,
|
|
9408
9888
|
defineRecipe,
|
|
9889
|
+
describePolicyExclude,
|
|
9890
|
+
detectOrphanGovernanceScales,
|
|
9409
9891
|
detectSubComponentPaths,
|
|
9892
|
+
detectUnconsumedConfigKeys,
|
|
9893
|
+
detectUnmatchedPolicyExcludes,
|
|
9410
9894
|
diffContractDomains,
|
|
9411
9895
|
discoverComponents,
|
|
9412
9896
|
dtcgTokenFileSchema,
|
|
9413
9897
|
emptyConformResult,
|
|
9414
9898
|
evaluateGovernanceIntegrity,
|
|
9899
|
+
excludeGlobToRegExp,
|
|
9415
9900
|
executeVariantLoaders,
|
|
9416
9901
|
explainUrlForCode,
|
|
9417
9902
|
factEvidenceSchema,
|
|
@@ -9484,8 +9969,10 @@ export {
|
|
|
9484
9969
|
isFigmaPropMapping,
|
|
9485
9970
|
isForceIncluded,
|
|
9486
9971
|
isPortableRepoPath,
|
|
9972
|
+
isPresetSourcedRule,
|
|
9487
9973
|
isRawHtmlAdvisoryTier,
|
|
9488
9974
|
isReactComponent,
|
|
9975
|
+
isRuleFamilyId,
|
|
9489
9976
|
localCanonicalContractMappings,
|
|
9490
9977
|
lowerCompositionContract,
|
|
9491
9978
|
makeA11yNameRequiredFact,
|
|
@@ -9529,6 +10016,8 @@ export {
|
|
|
9529
10016
|
makeUsageNodeFact,
|
|
9530
10017
|
makeUsagePropResolvedFact,
|
|
9531
10018
|
makeUsageTextChildFact,
|
|
10019
|
+
markPresetSourcedRules,
|
|
10020
|
+
matchPolicyExclude,
|
|
9532
10021
|
matchesGlob,
|
|
9533
10022
|
maxSeverity,
|
|
9534
10023
|
mcpBlockSchema,
|
|
@@ -9554,8 +10043,10 @@ export {
|
|
|
9554
10043
|
nearestSignedScaleValue,
|
|
9555
10044
|
normalizeColor,
|
|
9556
10045
|
normalizeConfigPath,
|
|
10046
|
+
normalizeExcludePath,
|
|
9557
10047
|
normalizeFinding,
|
|
9558
10048
|
normalizeGovernanceConfig,
|
|
10049
|
+
normalizePolicyExcludes,
|
|
9559
10050
|
normalizeRegistryRepoPath,
|
|
9560
10051
|
normalizeSeverity,
|
|
9561
10052
|
normalizeStyleValue,
|
|
@@ -9563,6 +10054,7 @@ export {
|
|
|
9563
10054
|
normalizeTokenGroupComment,
|
|
9564
10055
|
normalizeTokenValue,
|
|
9565
10056
|
normalizeViolation,
|
|
10057
|
+
overriddenRecordSeverityDiagnostic,
|
|
9566
10058
|
ownedImportEquivalents,
|
|
9567
10059
|
parseColor,
|
|
9568
10060
|
parseColorToRgb,
|
|
@@ -9578,9 +10070,12 @@ export {
|
|
|
9578
10070
|
parseTailwindV4Theme,
|
|
9579
10071
|
parseTokenFile,
|
|
9580
10072
|
parseTokens,
|
|
10073
|
+
policyExcludeMatchesPath,
|
|
10074
|
+
policyExcludeSchema,
|
|
9581
10075
|
portableRepoPathError,
|
|
9582
10076
|
projectCanonicalDirectionConflicts,
|
|
9583
10077
|
projectContractPreimage,
|
|
10078
|
+
projectSupersededImportPathPreferences,
|
|
9584
10079
|
projectV1OwnedComponentId,
|
|
9585
10080
|
projectV1OwnedImportIdentity,
|
|
9586
10081
|
propDefinitionSchema,
|
|
@@ -9624,6 +10119,7 @@ export {
|
|
|
9624
10119
|
ruleComponentsPreferLibrary,
|
|
9625
10120
|
ruleComponentsShadowComponent,
|
|
9626
10121
|
ruleComponentsUnknownProp,
|
|
10122
|
+
ruleFamilyMembers,
|
|
9627
10123
|
ruleJsxPreferredComponent,
|
|
9628
10124
|
ruleJsxPreferredImportPath,
|
|
9629
10125
|
rulePropsInvalidValue,
|