@usefragments/core 1.5.0 → 1.5.2
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-HXGE3O2F.js → chunk-BAHCOAVG.js} +133 -7
- package/dist/chunk-BAHCOAVG.js.map +1 -0
- package/dist/{chunk-57QDBEHQ.js → chunk-ZHS52OT4.js} +3 -9
- package/dist/chunk-ZHS52OT4.js.map +1 -0
- package/dist/codes/index.d.ts +2 -2
- package/dist/codes/index.js +2 -2
- package/dist/compiled-types/index.d.ts +1 -1
- package/dist/generate/index.d.ts +1 -1
- package/dist/{governance-BLsyk56o.d.ts → governance-pKrfh517.d.ts} +264 -4
- package/dist/{index-h_yWj15D.d.ts → index-DbkPE46t.d.ts} +40 -0
- package/dist/index.d.ts +472 -28
- package/dist/index.js +561 -39
- package/dist/index.js.map +1 -1
- package/dist/react-types.d.ts +1 -1
- package/dist/schemas/index.d.ts +1 -1
- package/dist/schemas/index.js +1 -1
- package/dist/test-utils.d.ts +1 -1
- package/package.json +1 -1
- package/src/agent-format.test.ts +1 -0
- package/src/canonical-bridge.ts +46 -0
- package/src/canonical-direction.ts +118 -0
- package/src/codes/__tests__/codes.test.ts +1 -1
- package/src/codes/codes.ts +29 -0
- package/src/config.ts +20 -0
- package/src/contract/preimage.test.ts +40 -0
- package/src/contract/preimage.ts +20 -0
- package/src/effective-governance-inputs.test.ts +52 -0
- package/src/effective-governance-inputs.ts +106 -0
- package/src/facts/builders.ts +25 -1
- package/src/facts/compile.ts +20 -2
- package/src/facts/fact-index.ts +12 -1
- package/src/facts/facts.test.ts +14 -18
- package/src/facts/types.ts +25 -6
- package/src/governance-integrity.test.ts +127 -0
- package/src/governance-integrity.ts +326 -4
- package/src/governance.test.ts +87 -1
- package/src/governance.ts +121 -0
- package/src/index.ts +34 -5
- package/src/rules/__tests__/fix-emission-invariant.test.ts +35 -4
- package/src/rules/components-prefer-library.test.ts +103 -0
- package/src/rules/components-prefer-library.ts +30 -4
- package/src/rules/fix-availability.ts +1 -0
- package/src/rules/index.ts +7 -0
- package/src/rules/jsx-preferred-import-path.ts +45 -9
- package/src/rules/rules.test.ts +202 -6
- package/src/rules/spacing-resolution.ts +2 -2
- package/src/rules/styles-no-raw-color.test.ts +6 -1
- package/src/rules/styles-no-raw-color.ts +3 -2
- package/src/rules/styles-no-raw-dimensions.ts +5 -7
- package/src/rules/styles-no-raw-spacing.test.ts +48 -0
- package/src/rules/styles-no-raw-spacing.ts +10 -7
- package/src/rules/styles-no-raw-typography.ts +7 -4
- package/src/rules/taxonomy.test.ts +3 -0
- package/src/rules/tiers.ts +2 -0
- package/src/rules/token-candidates.ts +36 -0
- package/src/rules/tokens-require-dual-fallback.ts +2 -1
- package/src/rules/tokens-upstream-drift.test.ts +111 -0
- package/src/rules/tokens-upstream-drift.ts +57 -0
- package/src/rules/utils.ts +22 -14
- package/src/schema.ts +13 -0
- package/src/schemas/index.ts +5 -8
- package/src/token-types.ts +71 -1
- package/src/types.ts +27 -3
- package/dist/chunk-57QDBEHQ.js.map +0 -1
- package/dist/chunk-HXGE3O2F.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -49,6 +49,7 @@ import {
|
|
|
49
49
|
bridgeSourceViolations,
|
|
50
50
|
byCode,
|
|
51
51
|
byRuleId,
|
|
52
|
+
canonicalBridgeV1Schema,
|
|
52
53
|
canonicalJson,
|
|
53
54
|
compileComponentFacts,
|
|
54
55
|
compileGlobalGovernanceFacts,
|
|
@@ -111,7 +112,7 @@ import {
|
|
|
111
112
|
ownedImportsEqual,
|
|
112
113
|
resolveComponentGovernance,
|
|
113
114
|
scaleGovernanceRecordSchema
|
|
114
|
-
} from "./chunk-
|
|
115
|
+
} from "./chunk-BAHCOAVG.js";
|
|
115
116
|
import {
|
|
116
117
|
AGENT_FORMAT_SCHEMA_VERSION,
|
|
117
118
|
agentErrorEnvelopeSchema,
|
|
@@ -136,7 +137,7 @@ import {
|
|
|
136
137
|
suppressionDirectiveSchema,
|
|
137
138
|
validatorResultSchema,
|
|
138
139
|
violationSchema
|
|
139
|
-
} from "./chunk-
|
|
140
|
+
} from "./chunk-ZHS52OT4.js";
|
|
140
141
|
import {
|
|
141
142
|
OWNED_PACKAGE_IDENTITY_EPOCH,
|
|
142
143
|
OWNED_PACKAGE_IDENTITY_SOURCE_SHA256,
|
|
@@ -1687,6 +1688,14 @@ var tokenConfigSchema = z4.object({
|
|
|
1687
1688
|
// vocabulary seeds the known-token set (#7c). A packages-only tokens block
|
|
1688
1689
|
// is valid — see the superRefine below.
|
|
1689
1690
|
packages: z4.array(z4.string().min(1)).optional(),
|
|
1691
|
+
aliases: z4.record(z4.string().min(1)).optional(),
|
|
1692
|
+
upstream: z4.array(
|
|
1693
|
+
z4.object({
|
|
1694
|
+
packageName: z4.string().min(1),
|
|
1695
|
+
version: z4.string().min(1).optional(),
|
|
1696
|
+
digest: z4.string().regex(/^[a-f0-9]{64}$/u).optional()
|
|
1697
|
+
})
|
|
1698
|
+
).optional(),
|
|
1690
1699
|
exclude: z4.array(repoRelativePathSchema).optional(),
|
|
1691
1700
|
themeSelectors: z4.record(z4.string()).optional(),
|
|
1692
1701
|
enabled: z4.boolean().optional(),
|
|
@@ -1832,9 +1841,14 @@ var governedFragmentDefinitionSchema = z4.object({
|
|
|
1832
1841
|
var recipeDefinitionSchema = blockDefinitionSchema;
|
|
1833
1842
|
|
|
1834
1843
|
// src/config.ts
|
|
1844
|
+
var RAW_CONFIG_DECLARATION = /* @__PURE__ */ Symbol.for("@usefragments/core/raw-config-declaration");
|
|
1835
1845
|
function formatZodErrors(errors) {
|
|
1836
1846
|
return errors.map((error) => ` - ${error.path.join(".")}: ${error.message}`).join("\n");
|
|
1837
1847
|
}
|
|
1848
|
+
function configDeclarationForDiagnostics(config) {
|
|
1849
|
+
if (!config || typeof config !== "object") return config;
|
|
1850
|
+
return config[RAW_CONFIG_DECLARATION] ?? config;
|
|
1851
|
+
}
|
|
1838
1852
|
function defineConfig(config) {
|
|
1839
1853
|
const normalized = normalizeGovernanceConfig(config);
|
|
1840
1854
|
const result = fragmentsConfigSchema.safeParse(normalized);
|
|
@@ -1842,6 +1856,12 @@ function defineConfig(config) {
|
|
|
1842
1856
|
throw new Error(`Invalid fragments config:
|
|
1843
1857
|
${formatZodErrors(result.error.errors)}`);
|
|
1844
1858
|
}
|
|
1859
|
+
Object.defineProperty(result.data, RAW_CONFIG_DECLARATION, {
|
|
1860
|
+
value: normalized,
|
|
1861
|
+
enumerable: false,
|
|
1862
|
+
configurable: false,
|
|
1863
|
+
writable: false
|
|
1864
|
+
});
|
|
1845
1865
|
return result.data;
|
|
1846
1866
|
}
|
|
1847
1867
|
|
|
@@ -4775,13 +4795,20 @@ function isExemptColor(value, except) {
|
|
|
4775
4795
|
}
|
|
4776
4796
|
return false;
|
|
4777
4797
|
}
|
|
4778
|
-
function tokenReference(
|
|
4779
|
-
if (
|
|
4780
|
-
|
|
4781
|
-
|
|
4798
|
+
function tokenReference(token) {
|
|
4799
|
+
if (token.referenceFormat === "css-var") {
|
|
4800
|
+
const cssVarName = [token.name, ...token.sourceNames ?? []].find(
|
|
4801
|
+
(name) => name.startsWith("--")
|
|
4802
|
+
);
|
|
4803
|
+
return cssVarName ? `var(${cssVarName})` : token.name;
|
|
4804
|
+
}
|
|
4805
|
+
if (token.referenceFormat === "scss-var") {
|
|
4806
|
+
return [token.name, ...token.sourceNames ?? []].find((name) => name.startsWith("$")) ?? (token.name.startsWith("--") ? `$${token.name.slice(2)}` : token.name);
|
|
4807
|
+
}
|
|
4808
|
+
return token.sourceNames?.[0] ?? token.name;
|
|
4782
4809
|
}
|
|
4783
4810
|
function isApplicableTokenReference(referenceFormat) {
|
|
4784
|
-
return referenceFormat ===
|
|
4811
|
+
return referenceFormat === "css-var" || referenceFormat === "scss-var";
|
|
4785
4812
|
}
|
|
4786
4813
|
|
|
4787
4814
|
// src/rules/a11y-required-accessible-name.ts
|
|
@@ -4938,6 +4965,77 @@ function isEnforceableHtmlEquivalent(args) {
|
|
|
4938
4965
|
return builtIn !== null && builtIn.canonical === args.canonical;
|
|
4939
4966
|
}
|
|
4940
4967
|
|
|
4968
|
+
// src/canonical-direction.ts
|
|
4969
|
+
function projectCanonicalDirectionConflicts(ix, canonicalSources) {
|
|
4970
|
+
const npmSources = canonicalSources.filter(
|
|
4971
|
+
(source) => source.kind === "npm"
|
|
4972
|
+
);
|
|
4973
|
+
if (npmSources.length === 0) return [];
|
|
4974
|
+
const identityByComponent = new Map(
|
|
4975
|
+
ix.byKind("component_identity").map((identity) => [identity.componentKey, identity])
|
|
4976
|
+
);
|
|
4977
|
+
const conflicts = [];
|
|
4978
|
+
for (const definition of ix.byKind("component_definition")) {
|
|
4979
|
+
if (!definition.exported || definition.renderRoot.resolution !== "canonical") continue;
|
|
4980
|
+
const target = npmTarget(definition.renderRoot.canonical, npmSources);
|
|
4981
|
+
if (!target) continue;
|
|
4982
|
+
const identity = identityByComponent.get(definition.componentKey);
|
|
4983
|
+
if (identity?.decisionId && decisionTargetsExport(identity.canonicalTarget, target.exportName)) {
|
|
4984
|
+
if (identity.state === "shadow") continue;
|
|
4985
|
+
if (identity.state === "variant" && identity.confidence === "confirmed") {
|
|
4986
|
+
conflicts.push({
|
|
4987
|
+
...target,
|
|
4988
|
+
wrapperComponentKey: definition.componentKey,
|
|
4989
|
+
wrapperFile: definition.file,
|
|
4990
|
+
wrapperExportName: definition.exportName,
|
|
4991
|
+
state: "sanctioned"
|
|
4992
|
+
});
|
|
4993
|
+
continue;
|
|
4994
|
+
}
|
|
4995
|
+
}
|
|
4996
|
+
conflicts.push({
|
|
4997
|
+
...target,
|
|
4998
|
+
wrapperComponentKey: definition.componentKey,
|
|
4999
|
+
wrapperFile: definition.file,
|
|
5000
|
+
wrapperExportName: definition.exportName,
|
|
5001
|
+
state: "unresolved"
|
|
5002
|
+
});
|
|
5003
|
+
}
|
|
5004
|
+
return conflicts.sort(
|
|
5005
|
+
(left, right) => left.packageName.localeCompare(right.packageName) || left.exportName.localeCompare(right.exportName) || left.wrapperComponentKey.localeCompare(right.wrapperComponentKey)
|
|
5006
|
+
);
|
|
5007
|
+
}
|
|
5008
|
+
function canonicalDirectionConflictsTarget(conflicts, importPath, exportName) {
|
|
5009
|
+
if (!importPath) return false;
|
|
5010
|
+
return conflicts.some(
|
|
5011
|
+
(conflict) => ownedImportMatchesRoot(importPath, conflict.packageName) && conflict.exportName === exportName
|
|
5012
|
+
);
|
|
5013
|
+
}
|
|
5014
|
+
function npmTarget(canonicalTarget, sources) {
|
|
5015
|
+
const separator = canonicalTarget.lastIndexOf("#");
|
|
5016
|
+
if (separator <= 0 || separator === canonicalTarget.length - 1) return null;
|
|
5017
|
+
const importPath = canonicalTarget.slice(0, separator);
|
|
5018
|
+
const imported = canonicalTarget.slice(separator + 1).split(".").at(-1);
|
|
5019
|
+
if (!imported) return null;
|
|
5020
|
+
for (const source of sources) {
|
|
5021
|
+
if (!ownedImportMatchesRoot(importPath, source.specifier)) continue;
|
|
5022
|
+
const subpath = packageSubpath(importPath, source.specifier);
|
|
5023
|
+
const exportName = imported === "default" ? subpath.split("/").filter(Boolean).at(-1) : imported;
|
|
5024
|
+
if (!exportName) continue;
|
|
5025
|
+
if (source.exclude?.includes(exportName)) continue;
|
|
5026
|
+
if (source.include && !source.include.includes(exportName)) continue;
|
|
5027
|
+
return { packageName: source.specifier, exportName };
|
|
5028
|
+
}
|
|
5029
|
+
return null;
|
|
5030
|
+
}
|
|
5031
|
+
function packageSubpath(importPath, packageName) {
|
|
5032
|
+
return importPath === packageName ? "" : importPath.slice(packageName.length + 1);
|
|
5033
|
+
}
|
|
5034
|
+
function decisionTargetsExport(target, exportName) {
|
|
5035
|
+
if (!target) return true;
|
|
5036
|
+
return target === exportName || target.split("#").at(-1)?.split(".").at(-1) === exportName;
|
|
5037
|
+
}
|
|
5038
|
+
|
|
4941
5039
|
// src/rules/components-prefer-library.ts
|
|
4942
5040
|
var RULE_ID2 = "components/prefer-library";
|
|
4943
5041
|
var RULE_VERSION2 = "1";
|
|
@@ -5081,8 +5179,12 @@ function findClassNameReimpl(node, classTokensByNode, targets) {
|
|
|
5081
5179
|
function ruleComponentsPreferLibrary(ix) {
|
|
5082
5180
|
const policy = ix.policy.ruleConfig(RULE_ID2);
|
|
5083
5181
|
if (!policy?.enabled) return [];
|
|
5084
|
-
const
|
|
5085
|
-
const
|
|
5182
|
+
const configuredSources = canonicalSourcesFromPolicy(policy.options?.canonicalSources);
|
|
5183
|
+
const conflicts = projectCanonicalDirectionConflicts(ix, configuredSources);
|
|
5184
|
+
const sources = configuredSources.map((source) => suppressConflictedExports(source, conflicts));
|
|
5185
|
+
const mappings = canonicalMappingsFromPolicy(policy.options?.canonicalMappings).filter(
|
|
5186
|
+
(mapping) => !canonicalDirectionConflictsTarget(conflicts, mapping.importPath, mapping.name)
|
|
5187
|
+
);
|
|
5086
5188
|
if (sources.length === 0 && mappings.length === 0) return [];
|
|
5087
5189
|
const importsByFileAndLocal = indexImportsByFileAndLocal(ix);
|
|
5088
5190
|
const propsByNode = indexPropsByNodeId(ix);
|
|
@@ -5345,6 +5447,16 @@ function ruleComponentsPreferLibrary(ix) {
|
|
|
5345
5447
|
}
|
|
5346
5448
|
return findings;
|
|
5347
5449
|
}
|
|
5450
|
+
function suppressConflictedExports(source, conflicts) {
|
|
5451
|
+
if (source.kind !== "npm") return source;
|
|
5452
|
+
const names = conflicts.filter((conflict) => ownedImportMatchesRoot(source.specifier, conflict.packageName)).map((conflict) => conflict.exportName);
|
|
5453
|
+
if (names.length === 0) return source;
|
|
5454
|
+
const blocked = new Set(names);
|
|
5455
|
+
if (source.include) {
|
|
5456
|
+
return { ...source, include: source.include.filter((name) => !blocked.has(name)) };
|
|
5457
|
+
}
|
|
5458
|
+
return { ...source, exclude: [.../* @__PURE__ */ new Set([...source.exclude ?? [], ...names])].sort() };
|
|
5459
|
+
}
|
|
5348
5460
|
function indexShadowRenderRootNodeIds(ix) {
|
|
5349
5461
|
const identities = ix.byKind("component_identity");
|
|
5350
5462
|
if (identities.length === 0) return /* @__PURE__ */ new Set();
|
|
@@ -6117,6 +6229,18 @@ function ruleJsxPreferredImportPath(ix) {
|
|
|
6117
6229
|
const seen = /* @__PURE__ */ new Set();
|
|
6118
6230
|
for (const usage of ix.byKind("usage_import")) {
|
|
6119
6231
|
const matches = policies.filter((policy) => {
|
|
6232
|
+
if (policy.bridge) {
|
|
6233
|
+
if (policy.bridge.implementationFiles.some((file) => sameRepoPath(file, usage.file))) {
|
|
6234
|
+
return false;
|
|
6235
|
+
}
|
|
6236
|
+
if (!ownedImportMatchesRoot(usage.source, policy.from)) return false;
|
|
6237
|
+
if (ownedImportsEqual(usage.source, policy.from)) {
|
|
6238
|
+
return usage.imported === policy.bridge.underlyingExportName;
|
|
6239
|
+
}
|
|
6240
|
+
const subpath = usage.source.slice(policy.from.length + 1);
|
|
6241
|
+
const leaf = subpath.split("/").at(-1);
|
|
6242
|
+
return leaf === policy.bridge.underlyingExportName && (usage.imported === "default" || usage.imported === policy.bridge.underlyingExportName);
|
|
6243
|
+
}
|
|
6120
6244
|
if (!ownedImportsEqual(policy.from, usage.source)) return false;
|
|
6121
6245
|
if (policy.imported !== void 0 && policy.imported !== usage.imported) return false;
|
|
6122
6246
|
return true;
|
|
@@ -6132,7 +6256,7 @@ function ruleJsxPreferredImportPath(ix) {
|
|
|
6132
6256
|
ruleId: RULE_ID7,
|
|
6133
6257
|
ruleVersion: RULE_VERSION8,
|
|
6134
6258
|
severity: policy.severity,
|
|
6135
|
-
message: `Import from "${usage.source}" should use "${policy.to}".`,
|
|
6259
|
+
message: policy.bridge ? `Import ${policy.bridge.underlyingExportName} through the confirmed local ${policy.bridge.localExportName} wrapper from "${policy.to}".` : `Import from "${usage.source}" should use "${policy.to}".`,
|
|
6136
6260
|
location: usage.location,
|
|
6137
6261
|
evidence: ix.evidence([usage.id, policy.id]),
|
|
6138
6262
|
fingerprintIdentity: {
|
|
@@ -6143,7 +6267,7 @@ function ruleJsxPreferredImportPath(ix) {
|
|
|
6143
6267
|
line: usage.location.line,
|
|
6144
6268
|
column: usage.location.column
|
|
6145
6269
|
},
|
|
6146
|
-
fix: {
|
|
6270
|
+
fix: policy.bridge ? void 0 : {
|
|
6147
6271
|
kind: "replaceImport",
|
|
6148
6272
|
title: policy.because ?? `Replace import path with "${policy.to}"`,
|
|
6149
6273
|
from: usage.source,
|
|
@@ -6155,7 +6279,14 @@ function ruleJsxPreferredImportPath(ix) {
|
|
|
6155
6279
|
suggestedImport: policy.to,
|
|
6156
6280
|
imported: usage.imported,
|
|
6157
6281
|
local: usage.local,
|
|
6158
|
-
rawValue: usage.source
|
|
6282
|
+
rawValue: usage.source,
|
|
6283
|
+
...policy.bridge ? {
|
|
6284
|
+
canonicalBridge: true,
|
|
6285
|
+
canonicalTarget: policy.bridge.componentKey,
|
|
6286
|
+
suggestedComponent: policy.bridge.localExportName,
|
|
6287
|
+
implementationFiles: policy.bridge.implementationFiles,
|
|
6288
|
+
decisionSource: policy.bridge.decisionSource
|
|
6289
|
+
} : {}
|
|
6159
6290
|
}
|
|
6160
6291
|
})
|
|
6161
6292
|
);
|
|
@@ -6163,6 +6294,12 @@ function ruleJsxPreferredImportPath(ix) {
|
|
|
6163
6294
|
}
|
|
6164
6295
|
return findings;
|
|
6165
6296
|
}
|
|
6297
|
+
function sameRepoPath(left, right) {
|
|
6298
|
+
return normalizeRepoPath(left) === normalizeRepoPath(right);
|
|
6299
|
+
}
|
|
6300
|
+
function normalizeRepoPath(path) {
|
|
6301
|
+
return path.replace(/\\/gu, "/").replace(/^\.\//u, "");
|
|
6302
|
+
}
|
|
6166
6303
|
|
|
6167
6304
|
// src/rules/props-invalid-value.ts
|
|
6168
6305
|
var RULE_ID8 = "props/invalid-value";
|
|
@@ -6373,13 +6510,36 @@ function srgbGamma(linear) {
|
|
|
6373
6510
|
return clamped <= 31308e-7 ? 12.92 * clamped : 1.055 * Math.pow(clamped, 1 / 2.4) - 0.055;
|
|
6374
6511
|
}
|
|
6375
6512
|
|
|
6513
|
+
// src/rules/token-candidates.ts
|
|
6514
|
+
function normalizedTokenValue(token) {
|
|
6515
|
+
const value = token.value.trim();
|
|
6516
|
+
return token.category === "color" ? normalizeColor(value) : value.replace(/\s+/g, " ");
|
|
6517
|
+
}
|
|
6518
|
+
function localTokenCandidates(ix, category) {
|
|
6519
|
+
const all = ix.tokens.list();
|
|
6520
|
+
const upstreamByName = /* @__PURE__ */ new Map();
|
|
6521
|
+
for (const token of all) {
|
|
6522
|
+
if (token.role !== "upstream") continue;
|
|
6523
|
+
const matches = upstreamByName.get(token.name) ?? [];
|
|
6524
|
+
matches.push(token);
|
|
6525
|
+
upstreamByName.set(token.name, matches);
|
|
6526
|
+
}
|
|
6527
|
+
return all.filter((token) => {
|
|
6528
|
+
if (token.role === "upstream" || category !== void 0 && token.category !== category) {
|
|
6529
|
+
return false;
|
|
6530
|
+
}
|
|
6531
|
+
const matches = upstreamByName.get(token.upstreamName ?? token.name) ?? [];
|
|
6532
|
+
return matches.length !== 1 || normalizedTokenValue(token) === normalizedTokenValue(matches[0]);
|
|
6533
|
+
});
|
|
6534
|
+
}
|
|
6535
|
+
|
|
6376
6536
|
// src/rules/styles-no-raw-color.ts
|
|
6377
6537
|
var RULE_ID9 = "styles/no-raw-color";
|
|
6378
6538
|
var RULE_VERSION10 = "1";
|
|
6379
6539
|
function ruleStylesNoRawColor(ix) {
|
|
6380
6540
|
const policy = ix.policy.rawColorPolicy();
|
|
6381
6541
|
if (!policy) return [];
|
|
6382
|
-
const colorTokens = ix
|
|
6542
|
+
const colorTokens = localTokenCandidates(ix, "color");
|
|
6383
6543
|
const preferLabel = policy.prefer === "token" ? "design token" : "CSS variable";
|
|
6384
6544
|
const findings = [];
|
|
6385
6545
|
const advisorySeverity = policy.severity === "error" ? "warn" : policy.severity;
|
|
@@ -6633,7 +6793,7 @@ function normalizeColor2(value) {
|
|
|
6633
6793
|
return lower;
|
|
6634
6794
|
}
|
|
6635
6795
|
function buildTokenFix(property, rawValue, rawColor, match, ix) {
|
|
6636
|
-
const reference = tokenReference(match.token
|
|
6796
|
+
const reference = tokenReference(match.token);
|
|
6637
6797
|
const value = replaceRawColor(rawValue, rawColor, reference);
|
|
6638
6798
|
return emitFix(
|
|
6639
6799
|
{
|
|
@@ -6668,8 +6828,8 @@ function ruleStylesNoRawDimensions(ix) {
|
|
|
6668
6828
|
const policy = ix.policy.rawDimensionPolicy();
|
|
6669
6829
|
if (!policy) return [];
|
|
6670
6830
|
const appliesTo = new Set(policy.appliesTo);
|
|
6671
|
-
const spacingTokens = ix
|
|
6672
|
-
const radiusTokens = ix
|
|
6831
|
+
const spacingTokens = localTokenCandidates(ix, "spacing");
|
|
6832
|
+
const radiusTokens = localTokenCandidates(ix, "radius");
|
|
6673
6833
|
const preferLabel = policy.prefer === "token" ? "design token" : "CSS variable";
|
|
6674
6834
|
const findings = [];
|
|
6675
6835
|
for (const decl of ix.byKind("style_declaration")) {
|
|
@@ -6880,9 +7040,7 @@ function normalizeDimension(value) {
|
|
|
6880
7040
|
return `${parsed.value}${parsed.unit ?? "px"}`;
|
|
6881
7041
|
}
|
|
6882
7042
|
function tokenVar(token) {
|
|
6883
|
-
|
|
6884
|
-
const cssVarName = token.name.startsWith("--") ? token.name : `--${token.name.replace(/\./g, "-")}`;
|
|
6885
|
-
return `var(${cssVarName})`;
|
|
7043
|
+
return tokenReference(token);
|
|
6886
7044
|
}
|
|
6887
7045
|
|
|
6888
7046
|
// src/rules/spacing-resolution.ts
|
|
@@ -6920,7 +7078,7 @@ function resolveSpacingValue(input) {
|
|
|
6920
7078
|
continue;
|
|
6921
7079
|
}
|
|
6922
7080
|
anyViolation = true;
|
|
6923
|
-
const reference = tokenReference(exactToken
|
|
7081
|
+
const reference = tokenReference(exactToken);
|
|
6924
7082
|
if (!normalized.deterministicFix) allValuePreserving = false;
|
|
6925
7083
|
suggestedParts.push(reference);
|
|
6926
7084
|
if (!firstViolation) {
|
|
@@ -6952,7 +7110,7 @@ function resolveSpacingValue(input) {
|
|
|
6952
7110
|
} else {
|
|
6953
7111
|
const nearestToken = input.tokens.get(Math.abs(nearest));
|
|
6954
7112
|
suggestedToken = nearestToken?.name;
|
|
6955
|
-
suggestedPart = nearestToken ? tokenReference(nearestToken
|
|
7113
|
+
suggestedPart = nearestToken ? tokenReference(nearestToken) : input.bareNumberFix ? `${nearest}` : `${nearest}${input.scale.unit}`;
|
|
6956
7114
|
suggestedParts.push(suggestedPart);
|
|
6957
7115
|
if (!normalized.deterministicFix) reliableSuggestion = false;
|
|
6958
7116
|
}
|
|
@@ -7004,7 +7162,7 @@ function ruleStylesNoRawSpacing(ix) {
|
|
|
7004
7162
|
const lookupFor = (scale) => {
|
|
7005
7163
|
let lookup = lookupCache.get(scale.name);
|
|
7006
7164
|
if (!lookup) {
|
|
7007
|
-
lookup = buildSpacingTokenLookup(ix
|
|
7165
|
+
lookup = buildSpacingTokenLookup(localTokenCandidates(ix, "spacing"), scale);
|
|
7008
7166
|
lookupCache.set(scale.name, lookup);
|
|
7009
7167
|
}
|
|
7010
7168
|
return lookup;
|
|
@@ -7042,7 +7200,7 @@ function checkDeclaration(ix, decl, lookupFor) {
|
|
|
7042
7200
|
ruleId: RULE_ID11,
|
|
7043
7201
|
ruleVersion: RULE_VERSION12,
|
|
7044
7202
|
severity: policy.severity,
|
|
7045
|
-
message: spacingMessage(decl.property, decl.value, allowed, scale
|
|
7203
|
+
message: spacingMessage(decl.property, decl.value, allowed, scale, checked),
|
|
7046
7204
|
location: decl.location,
|
|
7047
7205
|
evidence: ix.evidence([decl.id, policy.id, scale.id]),
|
|
7048
7206
|
fingerprintIdentity: {
|
|
@@ -7097,7 +7255,7 @@ function checkInlineStyle(ix, inline, componentByNode, lookupFor) {
|
|
|
7097
7255
|
ruleId: RULE_ID11,
|
|
7098
7256
|
ruleVersion: RULE_VERSION12,
|
|
7099
7257
|
severity: policy.severity,
|
|
7100
|
-
message: spacingMessage(inline.property, inline.value, allowed, scale
|
|
7258
|
+
message: spacingMessage(inline.property, inline.value, allowed, scale, checked),
|
|
7101
7259
|
location: node.location,
|
|
7102
7260
|
evidence: ix.evidence(evidenceIds),
|
|
7103
7261
|
fingerprintIdentity: {
|
|
@@ -7138,21 +7296,21 @@ function buildSpacingFix(property, checked, ix) {
|
|
|
7138
7296
|
deterministic: true
|
|
7139
7297
|
},
|
|
7140
7298
|
{
|
|
7141
|
-
symbols: tokenSymbolsInText(checked.suggestedValue),
|
|
7299
|
+
symbols: checked.matchedToken ? [checked.matchedToken] : tokenSymbolsInText(checked.suggestedValue),
|
|
7142
7300
|
editKind: "replaceStyleValue",
|
|
7143
7301
|
valuePreserving: checked.deterministicFix
|
|
7144
7302
|
},
|
|
7145
7303
|
ix
|
|
7146
7304
|
);
|
|
7147
7305
|
}
|
|
7148
|
-
function spacingMessage(property, value, allowed,
|
|
7306
|
+
function spacingMessage(property, value, allowed, scale, checked) {
|
|
7149
7307
|
if (checked.reason === "token-equivalent" && checked.matchedToken) {
|
|
7150
7308
|
return `\`${property}: ${value}\` is a raw literal that matches token \`${checked.matchedToken}\`. Reference the token instead.`;
|
|
7151
7309
|
}
|
|
7152
7310
|
const sample = allowed.slice().sort((a, b) => a - b).slice(0, 6);
|
|
7153
|
-
const suffix = `${sample.join(unit + ", ")}${unit}`;
|
|
7311
|
+
const suffix = `${sample.join(scale.unit + ", ")}${scale.unit}`;
|
|
7154
7312
|
const ellipsis = allowed.length > sample.length ? ", \u2026" : "";
|
|
7155
|
-
return `\`${property}: ${value}\` is not on the
|
|
7313
|
+
return `\`${property}: ${value}\` is not on the \`${scale.name}\` scale. Allowed: ${suffix}${ellipsis}. Configure \`govern.scales.${scale.name}\` to change it.`;
|
|
7156
7314
|
}
|
|
7157
7315
|
|
|
7158
7316
|
// src/rules/styles-no-raw-typography.ts
|
|
@@ -7254,7 +7412,7 @@ function ruleStylesNoRawTypography(ix) {
|
|
|
7254
7412
|
}
|
|
7255
7413
|
function buildTypographyTokenLookup(ix, scale) {
|
|
7256
7414
|
const out = /* @__PURE__ */ new Map();
|
|
7257
|
-
for (const token of ix
|
|
7415
|
+
for (const token of localTokenCandidates(ix, "typography")) {
|
|
7258
7416
|
const parsed = parseLengthValue(token.value);
|
|
7259
7417
|
if (!parsed || parsed.unit === null) continue;
|
|
7260
7418
|
const normalized = normalizeLengthForScale(parsed, scale);
|
|
@@ -7276,7 +7434,7 @@ function checkFontSize(raw, allowed, scale, tokens, bareNumberFix = false) {
|
|
|
7276
7434
|
if (matchesScale(normalized.value, allowed)) {
|
|
7277
7435
|
if (!exactToken) return null;
|
|
7278
7436
|
return {
|
|
7279
|
-
suggestedValue: tokenReference(exactToken
|
|
7437
|
+
suggestedValue: tokenReference(exactToken),
|
|
7280
7438
|
fixEmittable: true,
|
|
7281
7439
|
deterministicFix: normalized.deterministicFix,
|
|
7282
7440
|
reason: "token-equivalent",
|
|
@@ -7288,7 +7446,7 @@ function checkFontSize(raw, allowed, scale, tokens, bareNumberFix = false) {
|
|
|
7288
7446
|
return { fixEmittable: false, deterministicFix: false, reason: "off-scale" };
|
|
7289
7447
|
}
|
|
7290
7448
|
const nearestToken = tokens.get(Math.abs(nearest));
|
|
7291
|
-
const suggestedValue = nearestToken ? tokenReference(nearestToken
|
|
7449
|
+
const suggestedValue = nearestToken ? tokenReference(nearestToken) : bareNumberFix ? `${nearest}` : `${nearest}${scale.unit}`;
|
|
7292
7450
|
return {
|
|
7293
7451
|
suggestedValue,
|
|
7294
7452
|
fixEmittable: normalized.deterministicFix,
|
|
@@ -7307,7 +7465,7 @@ function buildFix(property, checked, ix) {
|
|
|
7307
7465
|
deterministic: true
|
|
7308
7466
|
},
|
|
7309
7467
|
{
|
|
7310
|
-
symbols: tokenSymbolsInText(checked.suggestedValue),
|
|
7468
|
+
symbols: checked.matchedToken ? [checked.matchedToken] : tokenSymbolsInText(checked.suggestedValue),
|
|
7311
7469
|
editKind: "replaceStyleValue",
|
|
7312
7470
|
valuePreserving: checked.deterministicFix
|
|
7313
7471
|
},
|
|
@@ -7986,7 +8144,7 @@ var SASS_FILE = /\.(scss|sass)$/i;
|
|
|
7986
8144
|
function ruleTokensRequireDualFallback(ix) {
|
|
7987
8145
|
const policy = ix.policy.ruleConfig(RULE_ID20);
|
|
7988
8146
|
if (!policy?.enabled) return [];
|
|
7989
|
-
const tokensByCssVariable = indexTokensByCssVariable(ix
|
|
8147
|
+
const tokensByCssVariable = indexTokensByCssVariable(localTokenCandidates(ix));
|
|
7990
8148
|
const findings = [];
|
|
7991
8149
|
for (const decl of ix.byKind("style_declaration")) {
|
|
7992
8150
|
if (!SASS_FILE.test(decl.file)) continue;
|
|
@@ -8135,6 +8293,57 @@ function skipToCloseParen(value, openParenIndex) {
|
|
|
8135
8293
|
return value.length;
|
|
8136
8294
|
}
|
|
8137
8295
|
|
|
8296
|
+
// src/rules/tokens-upstream-drift.ts
|
|
8297
|
+
var RULE_ID22 = "tokens/upstream-drift";
|
|
8298
|
+
var RULE_VERSION23 = "1";
|
|
8299
|
+
function ruleTokensUpstreamDrift(ix) {
|
|
8300
|
+
const local = ix.tokens.list().filter((token) => token.role !== "upstream");
|
|
8301
|
+
const upstream = ix.tokens.list().filter((token) => token.role === "upstream");
|
|
8302
|
+
const upstreamByName = /* @__PURE__ */ new Map();
|
|
8303
|
+
for (const token of upstream) {
|
|
8304
|
+
const matches = upstreamByName.get(token.name) ?? [];
|
|
8305
|
+
matches.push(token);
|
|
8306
|
+
upstreamByName.set(token.name, matches);
|
|
8307
|
+
}
|
|
8308
|
+
const findings = [];
|
|
8309
|
+
for (const token of local) {
|
|
8310
|
+
const upstreamName = token.upstreamName ?? token.name;
|
|
8311
|
+
const matches = (upstreamByName.get(upstreamName) ?? []).sort(
|
|
8312
|
+
(a, b) => (a.sourceIdentity ?? "").localeCompare(b.sourceIdentity ?? "")
|
|
8313
|
+
);
|
|
8314
|
+
if (matches.length !== 1) continue;
|
|
8315
|
+
const expected = matches[0];
|
|
8316
|
+
if (normalizedTokenValue(token) === normalizedTokenValue(expected)) continue;
|
|
8317
|
+
const location = token.location ?? {
|
|
8318
|
+
file: token.sourceIdentity ?? "tokens",
|
|
8319
|
+
line: 1,
|
|
8320
|
+
column: 1
|
|
8321
|
+
};
|
|
8322
|
+
findings.push(
|
|
8323
|
+
makeFinding({
|
|
8324
|
+
ruleId: RULE_ID22,
|
|
8325
|
+
ruleVersion: RULE_VERSION23,
|
|
8326
|
+
severity: "warn",
|
|
8327
|
+
message: `Local token ${token.name} is ${token.value}, but upstream ${upstreamName} is ${expected.value}.`,
|
|
8328
|
+
location,
|
|
8329
|
+
evidence: ix.evidence([token.id, expected.id]),
|
|
8330
|
+
fingerprintIdentity: {
|
|
8331
|
+
local: token.name,
|
|
8332
|
+
upstream: upstreamName,
|
|
8333
|
+
localSource: token.sourceIdentity,
|
|
8334
|
+
upstreamSource: expected.sourceIdentity
|
|
8335
|
+
},
|
|
8336
|
+
attributes: {
|
|
8337
|
+
local: { name: token.name, value: token.value, location: token.location },
|
|
8338
|
+
upstream: { name: expected.name, value: expected.value, location: expected.location },
|
|
8339
|
+
autoFix: false
|
|
8340
|
+
}
|
|
8341
|
+
})
|
|
8342
|
+
);
|
|
8343
|
+
}
|
|
8344
|
+
return findings;
|
|
8345
|
+
}
|
|
8346
|
+
|
|
8138
8347
|
// src/rules/emit-gate.ts
|
|
8139
8348
|
var BLOCKING_RULE_ALLOWLIST = /* @__PURE__ */ new Set([
|
|
8140
8349
|
"styles/no-raw-color",
|
|
@@ -8175,6 +8384,8 @@ var RULE_TIER = {
|
|
|
8175
8384
|
// canonical-component bypass (FUI1004)
|
|
8176
8385
|
"tokens/css-vars-must-be-defined": "contract",
|
|
8177
8386
|
// token drift (FUI2015)
|
|
8387
|
+
"tokens/upstream-drift": "contract",
|
|
8388
|
+
// authored local/upstream value drift (FUI2017)
|
|
8178
8389
|
// ---- Tier B — generic hygiene -----------------------------------------
|
|
8179
8390
|
"components/unknown-prop": "hygiene",
|
|
8180
8391
|
"components/forbidden-prop-value": "hygiene",
|
|
@@ -8218,7 +8429,8 @@ function hygieneTierRuleIds() {
|
|
|
8218
8429
|
var VOCABULARY_ORDER = [
|
|
8219
8430
|
"components/shadow-component",
|
|
8220
8431
|
"components/prefer-library",
|
|
8221
|
-
"tokens/css-vars-must-be-defined"
|
|
8432
|
+
"tokens/css-vars-must-be-defined",
|
|
8433
|
+
"tokens/upstream-drift"
|
|
8222
8434
|
];
|
|
8223
8435
|
function vocabularyRank(ruleId) {
|
|
8224
8436
|
const index = VOCABULARY_ORDER.indexOf(ruleId);
|
|
@@ -8342,6 +8554,11 @@ var RULES = [
|
|
|
8342
8554
|
version: "1",
|
|
8343
8555
|
run: ruleTokensCssVarsMustBeDefined
|
|
8344
8556
|
},
|
|
8557
|
+
{
|
|
8558
|
+
id: "tokens/upstream-drift",
|
|
8559
|
+
version: "1",
|
|
8560
|
+
run: ruleTokensUpstreamDrift
|
|
8561
|
+
},
|
|
8345
8562
|
{
|
|
8346
8563
|
id: "theme/no-theme-coupled-literal",
|
|
8347
8564
|
version: "1",
|
|
@@ -8406,6 +8623,207 @@ function policyDeclaresCssVars(policy) {
|
|
|
8406
8623
|
function dedupe(values) {
|
|
8407
8624
|
return [...new Set(values)];
|
|
8408
8625
|
}
|
|
8626
|
+
var RULE_FAMILY_IDS = /* @__PURE__ */ new Set(["tokens/hardcoded-values", "components/usage", "a11y/wcag"]);
|
|
8627
|
+
var CONSUMED_RULE_IDS = new Set(Object.keys(RULE_TIER));
|
|
8628
|
+
var RECOGNIZED_RULE_IDS = /* @__PURE__ */ new Set([...CONSUMED_RULE_IDS, ...RULE_FAMILY_IDS]);
|
|
8629
|
+
var RULE_CONFIG_KEYS = /* @__PURE__ */ new Set(["enabled", "severity", "options"]);
|
|
8630
|
+
var PASSTHROUGH_KEYS = [
|
|
8631
|
+
{
|
|
8632
|
+
path: ["tokens"],
|
|
8633
|
+
keys: [
|
|
8634
|
+
"include",
|
|
8635
|
+
"sources",
|
|
8636
|
+
"packages",
|
|
8637
|
+
"aliases",
|
|
8638
|
+
"upstream",
|
|
8639
|
+
"exclude",
|
|
8640
|
+
"themeSelectors",
|
|
8641
|
+
"enabled",
|
|
8642
|
+
"format",
|
|
8643
|
+
"namespace"
|
|
8644
|
+
]
|
|
8645
|
+
},
|
|
8646
|
+
{
|
|
8647
|
+
path: ["screenshots"],
|
|
8648
|
+
keys: ["viewport", "threshold", "delay", "outputDir", "themes"]
|
|
8649
|
+
},
|
|
8650
|
+
{
|
|
8651
|
+
path: ["service"],
|
|
8652
|
+
keys: ["poolSize", "idleTimeout"]
|
|
8653
|
+
},
|
|
8654
|
+
{
|
|
8655
|
+
path: ["registry"],
|
|
8656
|
+
keys: ["requireStory", "publicOnly", "categoryDepth", "includeProps", "embedFragments"]
|
|
8657
|
+
},
|
|
8658
|
+
{
|
|
8659
|
+
path: ["govern", "tailwind"],
|
|
8660
|
+
keys: ["palette"]
|
|
8661
|
+
},
|
|
8662
|
+
{
|
|
8663
|
+
path: ["govern", "agent"],
|
|
8664
|
+
keys: ["repairOrder"]
|
|
8665
|
+
},
|
|
8666
|
+
{
|
|
8667
|
+
path: ["govern", "ci"],
|
|
8668
|
+
keys: ["failOnWarnings"]
|
|
8669
|
+
}
|
|
8670
|
+
];
|
|
8671
|
+
function objectRecord(value) {
|
|
8672
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
8673
|
+
return value;
|
|
8674
|
+
}
|
|
8675
|
+
function valueAtPath(value, path) {
|
|
8676
|
+
let current = value;
|
|
8677
|
+
for (const segment of path) {
|
|
8678
|
+
const record = objectRecord(current);
|
|
8679
|
+
if (!record) return void 0;
|
|
8680
|
+
current = record[segment];
|
|
8681
|
+
}
|
|
8682
|
+
return current;
|
|
8683
|
+
}
|
|
8684
|
+
function configPath(path) {
|
|
8685
|
+
return path.reduce(
|
|
8686
|
+
(output, segment) => typeof segment === "number" ? `${output}[${segment}]` : output ? `${output}.${segment}` : segment,
|
|
8687
|
+
""
|
|
8688
|
+
);
|
|
8689
|
+
}
|
|
8690
|
+
function unconsumedKeyDiagnostic(path) {
|
|
8691
|
+
const renderedPath = configPath(path);
|
|
8692
|
+
return {
|
|
8693
|
+
code: "FUI9004",
|
|
8694
|
+
kind: "unconsumed-key",
|
|
8695
|
+
severity: "warn",
|
|
8696
|
+
path: renderedPath,
|
|
8697
|
+
message: `${renderedPath} is not consumed by Fragments and has no effect. Remove it or use a supported config key.`
|
|
8698
|
+
};
|
|
8699
|
+
}
|
|
8700
|
+
function collectStrippedKeys(authored, parsed, path, diagnostics) {
|
|
8701
|
+
if (Array.isArray(authored)) {
|
|
8702
|
+
if (!Array.isArray(parsed)) return;
|
|
8703
|
+
authored.forEach((item, index) => {
|
|
8704
|
+
collectStrippedKeys(item, parsed[index], [...path, index], diagnostics);
|
|
8705
|
+
});
|
|
8706
|
+
return;
|
|
8707
|
+
}
|
|
8708
|
+
const authoredRecord = objectRecord(authored);
|
|
8709
|
+
const parsedRecord = objectRecord(parsed);
|
|
8710
|
+
if (!authoredRecord || !parsedRecord) return;
|
|
8711
|
+
for (const key of Object.keys(authoredRecord).sort()) {
|
|
8712
|
+
if (!Object.prototype.hasOwnProperty.call(parsedRecord, key)) {
|
|
8713
|
+
diagnostics.push(unconsumedKeyDiagnostic([...path, key]));
|
|
8714
|
+
continue;
|
|
8715
|
+
}
|
|
8716
|
+
collectStrippedKeys(authoredRecord[key], parsedRecord[key], [...path, key], diagnostics);
|
|
8717
|
+
}
|
|
8718
|
+
}
|
|
8719
|
+
function collectPassthroughKeys(authored, path, allowed, diagnostics) {
|
|
8720
|
+
const record = objectRecord(valueAtPath(authored, path));
|
|
8721
|
+
if (!record) return;
|
|
8722
|
+
for (const key of Object.keys(record).sort()) {
|
|
8723
|
+
if (!allowed.has(key)) diagnostics.push(unconsumedKeyDiagnostic([...path, key]));
|
|
8724
|
+
}
|
|
8725
|
+
}
|
|
8726
|
+
function collectRuleConfigKeys(authored, path, diagnostics) {
|
|
8727
|
+
const rules = objectRecord(valueAtPath(authored, path));
|
|
8728
|
+
if (!rules) return;
|
|
8729
|
+
for (const ruleId of Object.keys(rules).sort()) {
|
|
8730
|
+
if (!RECOGNIZED_RULE_IDS.has(ruleId)) {
|
|
8731
|
+
diagnostics.push(unconsumedKeyDiagnostic([...path, ruleId]));
|
|
8732
|
+
continue;
|
|
8733
|
+
}
|
|
8734
|
+
const config = objectRecord(rules[ruleId]);
|
|
8735
|
+
if (!config) continue;
|
|
8736
|
+
for (const key of Object.keys(config).sort()) {
|
|
8737
|
+
if (!RULE_CONFIG_KEYS.has(key)) {
|
|
8738
|
+
diagnostics.push(unconsumedKeyDiagnostic([...path, ruleId, key]));
|
|
8739
|
+
}
|
|
8740
|
+
}
|
|
8741
|
+
}
|
|
8742
|
+
}
|
|
8743
|
+
function collectPassthroughRecordValues(authored, path, allowed, diagnostics) {
|
|
8744
|
+
const entries = objectRecord(valueAtPath(authored, path));
|
|
8745
|
+
if (!entries) return;
|
|
8746
|
+
for (const [entryName, value] of Object.entries(entries).sort(
|
|
8747
|
+
([left], [right]) => left.localeCompare(right)
|
|
8748
|
+
)) {
|
|
8749
|
+
const record = objectRecord(value);
|
|
8750
|
+
if (!record) continue;
|
|
8751
|
+
for (const key of Object.keys(record).sort()) {
|
|
8752
|
+
if (!allowed.has(key)) {
|
|
8753
|
+
diagnostics.push(unconsumedKeyDiagnostic([...path, entryName, key]));
|
|
8754
|
+
}
|
|
8755
|
+
}
|
|
8756
|
+
}
|
|
8757
|
+
}
|
|
8758
|
+
function stableConfigDiagnostics(diagnostics) {
|
|
8759
|
+
const unique = /* @__PURE__ */ new Map();
|
|
8760
|
+
for (const diagnostic of diagnostics) {
|
|
8761
|
+
unique.set(`${diagnostic.code}\0${diagnostic.path}`, diagnostic);
|
|
8762
|
+
}
|
|
8763
|
+
return [...unique.values()].sort(
|
|
8764
|
+
(left, right) => left.path.localeCompare(right.path) || left.code.localeCompare(right.code) || left.message.localeCompare(right.message)
|
|
8765
|
+
);
|
|
8766
|
+
}
|
|
8767
|
+
function detectUnconsumedConfigKeys(authoredConfig, parsedConfig) {
|
|
8768
|
+
const diagnostics = [];
|
|
8769
|
+
collectStrippedKeys(authoredConfig, parsedConfig, [], diagnostics);
|
|
8770
|
+
for (const boundary of PASSTHROUGH_KEYS) {
|
|
8771
|
+
collectPassthroughKeys(
|
|
8772
|
+
authoredConfig,
|
|
8773
|
+
boundary.path,
|
|
8774
|
+
new Set(boundary.keys),
|
|
8775
|
+
diagnostics
|
|
8776
|
+
);
|
|
8777
|
+
}
|
|
8778
|
+
collectRuleConfigKeys(authoredConfig, ["govern", "rules"], diagnostics);
|
|
8779
|
+
collectPassthroughRecordValues(
|
|
8780
|
+
authoredConfig,
|
|
8781
|
+
["govern", "agents"],
|
|
8782
|
+
/* @__PURE__ */ new Set(["rules"]),
|
|
8783
|
+
diagnostics
|
|
8784
|
+
);
|
|
8785
|
+
collectPassthroughKeys(authoredConfig, ["govern", "audit"], /* @__PURE__ */ new Set(), diagnostics);
|
|
8786
|
+
collectPassthroughRecordValues(authoredConfig, ["govern", "runners"], /* @__PURE__ */ new Set(), diagnostics);
|
|
8787
|
+
return stableConfigDiagnostics(diagnostics);
|
|
8788
|
+
}
|
|
8789
|
+
function effectiveScaleBindings(policy) {
|
|
8790
|
+
const bindings = [];
|
|
8791
|
+
for (const style of policy?.styles ?? []) {
|
|
8792
|
+
if (style.kind === "style.rawSpacing.mustMatchScale") {
|
|
8793
|
+
bindings.push({
|
|
8794
|
+
label: "Spacing properties",
|
|
8795
|
+
mechanism: "style.rawSpacing.mustMatchScale",
|
|
8796
|
+
scale: style.scale
|
|
8797
|
+
});
|
|
8798
|
+
} else if (style.kind === "style.fontSize.mustMatchScale") {
|
|
8799
|
+
bindings.push({
|
|
8800
|
+
label: "Font-size properties",
|
|
8801
|
+
mechanism: "style.fontSize.mustMatchScale",
|
|
8802
|
+
scale: style.scale
|
|
8803
|
+
});
|
|
8804
|
+
}
|
|
8805
|
+
}
|
|
8806
|
+
return bindings;
|
|
8807
|
+
}
|
|
8808
|
+
function detectOrphanGovernanceScales(declaredPolicy, effectivePolicy) {
|
|
8809
|
+
const bindings = effectiveScaleBindings(effectivePolicy);
|
|
8810
|
+
const referenced = new Set(bindings.map((binding) => binding.scale));
|
|
8811
|
+
const diagnostics = [];
|
|
8812
|
+
for (const scale of Object.keys(declaredPolicy?.scales ?? {}).sort()) {
|
|
8813
|
+
if (referenced.has(scale)) continue;
|
|
8814
|
+
const path = `govern.scales.${scale}`;
|
|
8815
|
+
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];
|
|
8816
|
+
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.`;
|
|
8817
|
+
diagnostics.push({
|
|
8818
|
+
code: "FUI9005",
|
|
8819
|
+
kind: "orphan-scale",
|
|
8820
|
+
severity: "warn",
|
|
8821
|
+
path,
|
|
8822
|
+
message: `${path} is not referenced by any property policy. ${remediation}`
|
|
8823
|
+
});
|
|
8824
|
+
}
|
|
8825
|
+
return stableConfigDiagnostics(diagnostics);
|
|
8826
|
+
}
|
|
8409
8827
|
function summarize(status, flags) {
|
|
8410
8828
|
if (status === "inert") {
|
|
8411
8829
|
return "Governance inert \u2014 no enforceable rule family is armed";
|
|
@@ -8421,6 +8839,7 @@ function summarize(status, flags) {
|
|
|
8421
8839
|
}
|
|
8422
8840
|
function evaluateGovernanceIntegrity(input) {
|
|
8423
8841
|
const configs = effectiveRuleConfigs(input.policy);
|
|
8842
|
+
const configDiagnostics = stableConfigDiagnostics(input.configDiagnostics ?? []);
|
|
8424
8843
|
const policyArmed = input.policy !== void 0 && input.policySource !== "none";
|
|
8425
8844
|
const policyFamily = {
|
|
8426
8845
|
id: "policy",
|
|
@@ -8431,19 +8850,23 @@ function evaluateGovernanceIntegrity(input) {
|
|
|
8431
8850
|
policyFamily.reason = "no governance policy resolved";
|
|
8432
8851
|
}
|
|
8433
8852
|
const componentsConfig = configs.get("components/prefer-library");
|
|
8434
|
-
const
|
|
8853
|
+
const confirmedBridges = input.policy?.canonicalBridges ?? [];
|
|
8854
|
+
const componentsArmed = confirmedBridges.length > 0 || componentsConfig?.enabled === true && hasEffectiveComponentVocabulary(componentsConfig.options);
|
|
8435
8855
|
const componentsFamily = {
|
|
8436
8856
|
id: "components",
|
|
8437
8857
|
armed: componentsArmed,
|
|
8438
|
-
rules: [
|
|
8858
|
+
rules: [
|
|
8859
|
+
...confirmedBridges.length > 0 ? ["imports/preferred-path"] : [],
|
|
8860
|
+
...componentsConfig?.enabled === true ? ["components/prefer-library"] : []
|
|
8861
|
+
]
|
|
8439
8862
|
};
|
|
8440
8863
|
if (!componentsArmed) {
|
|
8441
8864
|
if (componentsConfig?.enabled === true) {
|
|
8442
8865
|
componentsFamily.reason = "components/prefer-library enabled but no effective canonical source";
|
|
8443
|
-
componentsFamily.remediation = "add govern.
|
|
8866
|
+
componentsFamily.remediation = "add govern.canonicalBridges for local wrappers, govern.canonicalSources, or designSystem.path/packageName";
|
|
8444
8867
|
} else {
|
|
8445
8868
|
componentsFamily.reason = "components/prefer-library not enabled";
|
|
8446
|
-
componentsFamily.remediation = "add govern.
|
|
8869
|
+
componentsFamily.remediation = "add govern.canonicalBridges for local wrappers, govern.canonicalSources, or designSystem.path/packageName";
|
|
8447
8870
|
}
|
|
8448
8871
|
}
|
|
8449
8872
|
const cssVarsActive = input.cssVarsActive ?? policyDeclaresCssVars(input.policy);
|
|
@@ -8517,6 +8940,17 @@ function evaluateGovernanceIntegrity(input) {
|
|
|
8517
8940
|
families.map((family) => family.remediation).filter((remediation) => remediation !== void 0)
|
|
8518
8941
|
);
|
|
8519
8942
|
const summary = summarize(status, { componentsArmed, tokensArmed, blockingArmed });
|
|
8943
|
+
const configuredRuleCount = [...configs.keys()].filter(
|
|
8944
|
+
(ruleId) => CONSUMED_RULE_IDS.has(ruleId)
|
|
8945
|
+
).length;
|
|
8946
|
+
const activeRuleCount = [...configs.entries()].filter(
|
|
8947
|
+
([ruleId, config]) => CONSUMED_RULE_IDS.has(ruleId) && config.enabled
|
|
8948
|
+
).length;
|
|
8949
|
+
const roster = {
|
|
8950
|
+
configured: configuredRuleCount + configDiagnostics.length,
|
|
8951
|
+
active: activeRuleCount,
|
|
8952
|
+
inert: configDiagnostics.length
|
|
8953
|
+
};
|
|
8520
8954
|
return {
|
|
8521
8955
|
status,
|
|
8522
8956
|
declared: input.declared,
|
|
@@ -8525,10 +8959,80 @@ function evaluateGovernanceIntegrity(input) {
|
|
|
8525
8959
|
families,
|
|
8526
8960
|
armed,
|
|
8527
8961
|
summary,
|
|
8528
|
-
remediations
|
|
8962
|
+
remediations,
|
|
8963
|
+
configDiagnostics,
|
|
8964
|
+
roster
|
|
8529
8965
|
};
|
|
8530
8966
|
}
|
|
8531
8967
|
|
|
8968
|
+
// src/canonical-bridge.ts
|
|
8969
|
+
function canonicalBridgeContractMappings(bridges) {
|
|
8970
|
+
return (bridges ?? []).map((bridge) => ({
|
|
8971
|
+
component: `${bridge.underlying.packageName}#${bridge.underlying.exportName}`,
|
|
8972
|
+
canonical: bridge.local.componentKey,
|
|
8973
|
+
status: "confirmed",
|
|
8974
|
+
importPath: bridge.local.moduleSpecifier,
|
|
8975
|
+
bridge: {
|
|
8976
|
+
underlyingPackageName: bridge.underlying.packageName,
|
|
8977
|
+
underlyingExportName: bridge.underlying.exportName,
|
|
8978
|
+
localComponentKey: bridge.local.componentKey,
|
|
8979
|
+
localExportName: bridge.local.exportName,
|
|
8980
|
+
implementationFiles: [...bridge.local.implementationFiles],
|
|
8981
|
+
decisionSource: bridge.decision.source
|
|
8982
|
+
}
|
|
8983
|
+
}));
|
|
8984
|
+
}
|
|
8985
|
+
|
|
8986
|
+
// src/effective-governance-inputs.ts
|
|
8987
|
+
var EFFECTIVE_GOVERNANCE_INPUTS_SCHEMA = "effective-governance-inputs:v1";
|
|
8988
|
+
function compileEffectiveGovernanceInputs(options) {
|
|
8989
|
+
const projection = {
|
|
8990
|
+
schema: EFFECTIVE_GOVERNANCE_INPUTS_SCHEMA,
|
|
8991
|
+
policySource: options.policySource,
|
|
8992
|
+
config: {
|
|
8993
|
+
path: options.configPath ? normalizePath2(options.configPath) : null,
|
|
8994
|
+
digest: options.configDigest
|
|
8995
|
+
},
|
|
8996
|
+
components: {
|
|
8997
|
+
canonicalSources: sortRecords(options.policy?.canonicalSources ?? []),
|
|
8998
|
+
canonicalBridges: sortRecords(options.policy?.canonicalBridges ?? []),
|
|
8999
|
+
definitionFiles: normalizePaths(options.componentDefinitionFiles)
|
|
9000
|
+
},
|
|
9001
|
+
tokens: {
|
|
9002
|
+
declared: normalizePaths(options.tokens.declared),
|
|
9003
|
+
loadedDefinitions: options.tokens.loadedDefinitions,
|
|
9004
|
+
diagnostics: [...options.tokens.diagnostics].sort(),
|
|
9005
|
+
...options.tokens.catalog ? {
|
|
9006
|
+
catalog: {
|
|
9007
|
+
...options.tokens.catalog,
|
|
9008
|
+
configuredSources: normalizePaths(options.tokens.catalog.configuredSources),
|
|
9009
|
+
parsedSources: normalizePaths(options.tokens.catalog.parsedSources),
|
|
9010
|
+
definitions: sortRecords(options.tokens.catalog.definitions),
|
|
9011
|
+
diagnostics: sortRecords(options.tokens.catalog.diagnostics),
|
|
9012
|
+
references: sortRecords(options.tokens.catalog.references),
|
|
9013
|
+
...options.tokens.catalog.sourceStates ? { sourceStates: sortRecords(options.tokens.catalog.sourceStates) } : {}
|
|
9014
|
+
}
|
|
9015
|
+
} : {}
|
|
9016
|
+
},
|
|
9017
|
+
eligibility: {
|
|
9018
|
+
include: normalizePaths(options.eligibility.include),
|
|
9019
|
+
exclude: normalizePaths(options.eligibility.exclude)
|
|
9020
|
+
}
|
|
9021
|
+
};
|
|
9022
|
+
return { ...projection, inputsHash: contractHash(projection) };
|
|
9023
|
+
}
|
|
9024
|
+
function normalizePaths(values) {
|
|
9025
|
+
return [...new Set(values.map(normalizePath2))].sort();
|
|
9026
|
+
}
|
|
9027
|
+
function normalizePath2(value) {
|
|
9028
|
+
return value.replace(/\\/gu, "/").replace(/^\.\//u, "");
|
|
9029
|
+
}
|
|
9030
|
+
function sortRecords(values) {
|
|
9031
|
+
return [...values].sort(
|
|
9032
|
+
(left, right) => canonicalPreimage(left).localeCompare(canonicalPreimage(right))
|
|
9033
|
+
);
|
|
9034
|
+
}
|
|
9035
|
+
|
|
8532
9036
|
// src/contract/preimage.ts
|
|
8533
9037
|
var CONTRACT_DOMAINS = ["components", "tokens", "canonicalMap", "policy"];
|
|
8534
9038
|
var CONTRACT_PREIMAGE_SCHEMA = "fcid-preimage:v1";
|
|
@@ -8584,6 +9088,15 @@ function projectCanonicalMap(mappings) {
|
|
|
8584
9088
|
component: mapping.component,
|
|
8585
9089
|
canonical: mapping.canonical,
|
|
8586
9090
|
importPath: mapping.importPath ? projectV1OwnedImportIdentity(mapping.importPath) : void 0,
|
|
9091
|
+
bridge: mapping.bridge ? {
|
|
9092
|
+
underlyingPackageName: projectV1OwnedImportIdentity(
|
|
9093
|
+
mapping.bridge.underlyingPackageName
|
|
9094
|
+
),
|
|
9095
|
+
underlyingExportName: mapping.bridge.underlyingExportName,
|
|
9096
|
+
localComponentKey: projectV1OwnedComponentId(mapping.bridge.localComponentKey),
|
|
9097
|
+
localExportName: mapping.bridge.localExportName,
|
|
9098
|
+
implementationFiles: [...mapping.bridge.implementationFiles].sort()
|
|
9099
|
+
} : void 0,
|
|
8587
9100
|
resolves: mapping.resolves ? sortCanonical(mapping.resolves) : void 0,
|
|
8588
9101
|
propMapping: mapping.propMapping ? sortCanonical(
|
|
8589
9102
|
mapping.propMapping.map((entry) => ({
|
|
@@ -9016,6 +9529,7 @@ export {
|
|
|
9016
9529
|
DEFAULTS,
|
|
9017
9530
|
DEFAULT_ENHANCED_STYLE_PROPERTIES,
|
|
9018
9531
|
DEFAULT_STYLE_PROPERTIES,
|
|
9532
|
+
EFFECTIVE_GOVERNANCE_INPUTS_SCHEMA,
|
|
9019
9533
|
EVIDENCE_ORDER,
|
|
9020
9534
|
EXPLAIN_URL_BASE,
|
|
9021
9535
|
FRAGMENTS_INTERNAL_RULE_IDS,
|
|
@@ -9070,6 +9584,9 @@ export {
|
|
|
9070
9584
|
byRuleId,
|
|
9071
9585
|
calculateDeltaE,
|
|
9072
9586
|
canBlock,
|
|
9587
|
+
canonicalBridgeContractMappings,
|
|
9588
|
+
canonicalBridgeV1Schema,
|
|
9589
|
+
canonicalDirectionConflictsTarget,
|
|
9073
9590
|
canonicalJson,
|
|
9074
9591
|
canonicalPreimage,
|
|
9075
9592
|
canonicalizeOwnedComponentId,
|
|
@@ -9092,6 +9609,7 @@ export {
|
|
|
9092
9609
|
compareStylesWithTokens,
|
|
9093
9610
|
compileBlock,
|
|
9094
9611
|
compileComponentFacts,
|
|
9612
|
+
compileEffectiveGovernanceInputs,
|
|
9095
9613
|
compileFragment,
|
|
9096
9614
|
compileGlobalGovernanceFacts,
|
|
9097
9615
|
compileRecipe,
|
|
@@ -9104,6 +9622,7 @@ export {
|
|
|
9104
9622
|
compositionMetadataSchema,
|
|
9105
9623
|
compositionPatternSchema,
|
|
9106
9624
|
computeRegistryHash,
|
|
9625
|
+
configDeclarationForDiagnostics,
|
|
9107
9626
|
containsTailwindV4Theme,
|
|
9108
9627
|
contractComponentsFromFragments,
|
|
9109
9628
|
contractHash,
|
|
@@ -9114,7 +9633,9 @@ export {
|
|
|
9114
9633
|
defineConfig,
|
|
9115
9634
|
defineFragment,
|
|
9116
9635
|
defineRecipe,
|
|
9636
|
+
detectOrphanGovernanceScales,
|
|
9117
9637
|
detectSubComponentPaths,
|
|
9638
|
+
detectUnconsumedConfigKeys,
|
|
9118
9639
|
diffContractDomains,
|
|
9119
9640
|
discoverComponents,
|
|
9120
9641
|
dtcgTokenFileSchema,
|
|
@@ -9287,6 +9808,7 @@ export {
|
|
|
9287
9808
|
parseTokenFile,
|
|
9288
9809
|
parseTokens,
|
|
9289
9810
|
portableRepoPathError,
|
|
9811
|
+
projectCanonicalDirectionConflicts,
|
|
9290
9812
|
projectContractPreimage,
|
|
9291
9813
|
projectV1OwnedComponentId,
|
|
9292
9814
|
projectV1OwnedImportIdentity,
|