@usefragments/core 1.0.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/LICENSE +21 -0
- package/dist/chunk-243QYRUF.js +2145 -0
- package/dist/chunk-243QYRUF.js.map +1 -0
- package/dist/chunk-3LLRNCPX.js +111 -0
- package/dist/chunk-3LLRNCPX.js.map +1 -0
- package/dist/chunk-EIYNNS77.js +48 -0
- package/dist/chunk-EIYNNS77.js.map +1 -0
- package/dist/chunk-JNBFJ34I.js +326 -0
- package/dist/chunk-JNBFJ34I.js.map +1 -0
- package/dist/chunk-R3X7UBHU.js +228 -0
- package/dist/chunk-R3X7UBHU.js.map +1 -0
- package/dist/chunk-SH4KPIYH.js +393 -0
- package/dist/chunk-SH4KPIYH.js.map +1 -0
- package/dist/chunk-V4VQB57N.js +56 -0
- package/dist/chunk-V4VQB57N.js.map +1 -0
- package/dist/chunk-X34IA4LR.js +319 -0
- package/dist/chunk-X34IA4LR.js.map +1 -0
- package/dist/chunk-YF65VYRY.js +493 -0
- package/dist/chunk-YF65VYRY.js.map +1 -0
- package/dist/codes/index.d.ts +40 -0
- package/dist/codes/index.js +23 -0
- package/dist/codes/index.js.map +1 -0
- package/dist/compiled-types/index.d.ts +5 -0
- package/dist/compiled-types/index.js +1 -0
- package/dist/compiled-types/index.js.map +1 -0
- package/dist/generate/index.d.ts +47 -0
- package/dist/generate/index.js +11 -0
- package/dist/generate/index.js.map +1 -0
- package/dist/governance-BOa3KyiJ.d.ts +3279 -0
- package/dist/graph/index.d.ts +66 -0
- package/dist/graph/index.js +17 -0
- package/dist/graph/index.js.map +1 -0
- package/dist/index.d.ts +11388 -0
- package/dist/index.js +8711 -0
- package/dist/index.js.map +1 -0
- package/dist/package-identity.d.ts +42 -0
- package/dist/package-identity.js +23 -0
- package/dist/package-identity.js.map +1 -0
- package/dist/preview/index.d.ts +484 -0
- package/dist/preview/index.js +1129 -0
- package/dist/preview/index.js.map +1 -0
- package/dist/react-types.d.ts +22 -0
- package/dist/react-types.js +1 -0
- package/dist/react-types.js.map +1 -0
- package/dist/registry.d.ts +2367 -0
- package/dist/registry.js +62 -0
- package/dist/registry.js.map +1 -0
- package/dist/schemas/index.d.ts +2584 -0
- package/dist/schemas/index.js +55 -0
- package/dist/schemas/index.js.map +1 -0
- package/dist/severity.d.ts +18 -0
- package/dist/severity.js +27 -0
- package/dist/severity.js.map +1 -0
- package/dist/test-utils.d.ts +14 -0
- package/dist/test-utils.js +63 -0
- package/dist/test-utils.js.map +1 -0
- package/dist/topology/index.d.ts +48 -0
- package/dist/topology/index.js +7 -0
- package/dist/topology/index.js.map +1 -0
- package/dist/types-xJ2xyp_G.d.ts +119 -0
- package/package.json +120 -0
- package/src/__tests__/contract-parser.test.ts +614 -0
- package/src/__tests__/dtcg-generators.test.ts +439 -0
- package/src/__tests__/dtcg-parser.test.ts +884 -0
- package/src/__tests__/preview-runtime.test.tsx +111 -0
- package/src/__tests__/schema.test.ts +31 -0
- package/src/__tests__/severity.test.ts +34 -0
- package/src/__tests__/style-comparison.test.ts +662 -0
- package/src/__tests__/tokens-compat.test.ts +51 -0
- package/src/agent-format.test.ts +105 -0
- package/src/agent-format.ts +141 -0
- package/src/bundle.ts +149 -0
- package/src/codes/__tests__/codes.test.ts +73 -0
- package/src/codes/codes.ts +463 -0
- package/src/codes/index.ts +7 -0
- package/src/codes/source-finding-bridge.ts +113 -0
- package/src/compiled-types/index.ts +293 -0
- package/src/component-contract.ts +231 -0
- package/src/component-discovery.ts +80 -0
- package/src/composition.test.ts +262 -0
- package/src/composition.ts +318 -0
- package/src/config-paths.test.ts +78 -0
- package/src/config-paths.ts +47 -0
- package/src/config.ts +18 -0
- package/src/conform-prove.test.ts +274 -0
- package/src/conform-prove.ts +326 -0
- package/src/conform.ts +160 -0
- package/src/constants.ts +114 -0
- package/src/context.ts +2 -0
- package/src/contract/fixture.ts +110 -0
- package/src/contract/hash.test.ts +86 -0
- package/src/contract/hash.ts +195 -0
- package/src/contract/index.ts +43 -0
- package/src/contract/preimage.test.ts +254 -0
- package/src/contract/preimage.ts +365 -0
- package/src/contract/stamp.test.ts +100 -0
- package/src/contract/stamp.ts +105 -0
- package/src/contract-parser.ts +141 -0
- package/src/defineFragment.test.ts +257 -0
- package/src/defineFragment.ts +293 -0
- package/src/dtcg-generators.ts +324 -0
- package/src/dtcg-parser.ts +1 -0
- package/src/dtcg.ts +237 -0
- package/src/facts/__snapshots__/facts.test.ts.snap +124 -0
- package/src/facts/builders.ts +835 -0
- package/src/facts/compile.ts +422 -0
- package/src/facts/fact-index.ts +394 -0
- package/src/facts/facts.test.ts +494 -0
- package/src/facts/ids.ts +125 -0
- package/src/facts/index.ts +119 -0
- package/src/facts/types.ts +614 -0
- package/src/figma.ts +263 -0
- package/src/fragment-types.ts +214 -0
- package/src/generate/generate.test.ts +594 -0
- package/src/generate/index.ts +438 -0
- package/src/governance-integrity.test.ts +186 -0
- package/src/governance-integrity.ts +282 -0
- package/src/governance.test.ts +300 -0
- package/src/governance.ts +605 -0
- package/src/governance.type-test.ts +75 -0
- package/src/graph/engine.ts +446 -0
- package/src/graph/graph.test.ts +746 -0
- package/src/graph/index.ts +32 -0
- package/src/graph/serialization.ts +55 -0
- package/src/graph/types.ts +178 -0
- package/src/index.ts +891 -0
- package/src/local-canonical.ts +102 -0
- package/src/mcp-snapshot.ts +222 -0
- package/src/package-identity-match.ts +33 -0
- package/src/package-identity.generated.ts +233 -0
- package/src/package-identity.test.ts +99 -0
- package/src/package-identity.ts +157 -0
- package/src/performance-presets.ts +142 -0
- package/src/preview/comparison-artifact.test.ts +228 -0
- package/src/preview/comparison-artifact.ts +181 -0
- package/src/preview/comparison-context.ts +93 -0
- package/src/preview/comparison-evidence.ts +112 -0
- package/src/preview/comparison-format.ts +21 -0
- package/src/preview/comparison-presentation.ts +187 -0
- package/src/preview/comparison-references.test.ts +430 -0
- package/src/preview/comparison-references.ts +72 -0
- package/src/preview/comparison-score.ts +59 -0
- package/src/preview/comparison-styles.ts +197 -0
- package/src/preview/comparison.test.ts +559 -0
- package/src/preview/comparison.ts +488 -0
- package/src/preview/index.ts +10 -0
- package/src/preview/types.ts +309 -0
- package/src/preview/validation.test.ts +290 -0
- package/src/preview/validation.ts +356 -0
- package/src/preview-runtime.tsx +144 -0
- package/src/raw-html-canonical.test.ts +41 -0
- package/src/raw-html-canonical.ts +179 -0
- package/src/react-types.ts +21 -0
- package/src/registry-install-plan.ts +344 -0
- package/src/registry.test.ts +245 -0
- package/src/registry.ts +369 -0
- package/src/rules/a11y-required-accessible-name.ts +93 -0
- package/src/rules/color-math.test.ts +72 -0
- package/src/rules/color-math.ts +192 -0
- package/src/rules/components-forbidden-prop-value.ts +108 -0
- package/src/rules/components-prefer-library.ts +943 -0
- package/src/rules/components-unknown-prop.ts +94 -0
- package/src/rules/composition-pattern-schema.ts +125 -0
- package/src/rules/composition-pattern.test.ts +320 -0
- package/src/rules/composition-pattern.ts +271 -0
- package/src/rules/emit-gate.test.ts +42 -0
- package/src/rules/emit-gate.ts +60 -0
- package/src/rules/finding.ts +54 -0
- package/src/rules/index.ts +235 -0
- package/src/rules/jsx-preferred-component.ts +68 -0
- package/src/rules/jsx-preferred-import-path.ts +67 -0
- package/src/rules/presets.ts +57 -0
- package/src/rules/props-invalid-value.ts +115 -0
- package/src/rules/rules.test.ts +3636 -0
- package/src/rules/spacing-resolution.ts +184 -0
- package/src/rules/styles-no-raw-color.ts +329 -0
- package/src/rules/styles-no-raw-dimensions.ts +357 -0
- package/src/rules/styles-no-raw-spacing.ts +236 -0
- package/src/rules/styles-no-raw-typography.ts +237 -0
- package/src/rules/tailwind-arbitrary-color.test.ts +128 -0
- package/src/rules/tailwind-arbitrary-color.ts +93 -0
- package/src/rules/tailwind-arbitrary-spacing.test.ts +157 -0
- package/src/rules/tailwind-arbitrary-spacing.ts +120 -0
- package/src/rules/tailwind-forbidden-palette.test.ts +116 -0
- package/src/rules/tailwind-forbidden-palette.ts +107 -0
- package/src/rules/tailwind-off-scale-spacing-token.test.ts +100 -0
- package/src/rules/tailwind-off-scale-spacing-token.ts +108 -0
- package/src/rules/tailwind-raw-color-via-token.test.ts +108 -0
- package/src/rules/tailwind-raw-color-via-token.ts +82 -0
- package/src/rules/tailwind-rule-utils.ts +106 -0
- package/src/rules/tailwind-unknown-class.test.ts +157 -0
- package/src/rules/tailwind-unknown-class.ts +121 -0
- package/src/rules/taxonomy.test.ts +238 -0
- package/src/rules/theme-no-theme-coupled-literal.ts +132 -0
- package/src/rules/tiers.ts +111 -0
- package/src/rules/tokens-css-vars-must-be-defined.test.ts +244 -0
- package/src/rules/tokens-css-vars-must-be-defined.ts +156 -0
- package/src/rules/tokens-require-dual-fallback.ts +78 -0
- package/src/rules/types.ts +27 -0
- package/src/rules/utils.test.ts +110 -0
- package/src/rules/utils.ts +599 -0
- package/src/schema.ts +427 -0
- package/src/schemas/index.ts +275 -0
- package/src/schemas/schemas.test.ts +124 -0
- package/src/severity.ts +54 -0
- package/src/storyAdapter.test.ts +571 -0
- package/src/storyAdapter.ts +761 -0
- package/src/storyFilters.test.ts +350 -0
- package/src/storyFilters.ts +253 -0
- package/src/storybook-csf.ts +11 -0
- package/src/style-comparison.ts +423 -0
- package/src/test-utils.ts +68 -0
- package/src/token-parser.ts +2 -0
- package/src/token-types.ts +287 -0
- package/src/tokens/categories.ts +131 -0
- package/src/tokens/color.ts +193 -0
- package/src/tokens/design-token-parser.test.ts +159 -0
- package/src/tokens/design-token-parser.ts +402 -0
- package/src/tokens/dtcg.ts +428 -0
- package/src/tokens/index.ts +130 -0
- package/src/tokens/lookup.ts +19 -0
- package/src/tokens/scss.ts +171 -0
- package/src/tokens/tailwind-v4.ts +187 -0
- package/src/tokens/types.ts +43 -0
- package/src/topology/index.ts +9 -0
- package/src/topology/resolve-area.test.ts +174 -0
- package/src/topology/resolve-area.ts +215 -0
- package/src/types.ts +1057 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { buildAgentFormat } from "./agent-format.js";
|
|
4
|
+
import { agentFormatSchema, type Finding } from "./schemas/index.js";
|
|
5
|
+
|
|
6
|
+
const DEAD_TOOL_NAMES = ["apply_fix", "swap_to_canonical", "tokens.suggest"];
|
|
7
|
+
|
|
8
|
+
function finding(overrides: Partial<Finding> = {}): Finding {
|
|
9
|
+
return {
|
|
10
|
+
ruleId: "styles/no-raw-color",
|
|
11
|
+
ruleVersion: "1.0.0",
|
|
12
|
+
severity: "serious",
|
|
13
|
+
level: "error",
|
|
14
|
+
code: "FUI2001",
|
|
15
|
+
message: "Use a token.",
|
|
16
|
+
fingerprint: "fp_1",
|
|
17
|
+
location: { file: "src/Button.tsx", line: 1, column: 1 },
|
|
18
|
+
evidence: [
|
|
19
|
+
{
|
|
20
|
+
factId: "fact_1",
|
|
21
|
+
fact: {
|
|
22
|
+
kind: "style",
|
|
23
|
+
location: { file: "src/Button.tsx", line: 1, column: 1 },
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
...overrides,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
describe("buildAgentFormat", () => {
|
|
32
|
+
it("names only hosted MCP tools in fix plans", () => {
|
|
33
|
+
const agentFormat = buildAgentFormat({
|
|
34
|
+
filesScanned: 1,
|
|
35
|
+
findings: [
|
|
36
|
+
finding({
|
|
37
|
+
fix: {
|
|
38
|
+
kind: "replaceStyleValue",
|
|
39
|
+
title: "Use token",
|
|
40
|
+
property: "color",
|
|
41
|
+
value: "var(--color-brand)",
|
|
42
|
+
deterministic: true,
|
|
43
|
+
},
|
|
44
|
+
}),
|
|
45
|
+
finding({
|
|
46
|
+
ruleId: "components/prefer-library",
|
|
47
|
+
code: "FUI1001",
|
|
48
|
+
fingerprint: "fp_2",
|
|
49
|
+
}),
|
|
50
|
+
finding({
|
|
51
|
+
code: "FUI2002",
|
|
52
|
+
fingerprint: "fp_3",
|
|
53
|
+
attributes: { property: "color", rawValue: "#2563eb" },
|
|
54
|
+
}),
|
|
55
|
+
],
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
agentFormatSchema.parse(agentFormat);
|
|
59
|
+
const toolNames = [...agentFormat.fix_first, ...agentFormat.remaining].map(
|
|
60
|
+
(entry) => entry.plan.tool
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
expect(new Set(toolNames)).toEqual(new Set(["design_system/conform"]));
|
|
64
|
+
for (const deadName of DEAD_TOOL_NAMES) {
|
|
65
|
+
expect(toolNames).not.toContain(deadName);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("keeps warn-only scans passed when the report summary passed", () => {
|
|
70
|
+
const agentFormat = buildAgentFormat({
|
|
71
|
+
filesScanned: 1,
|
|
72
|
+
passed: true,
|
|
73
|
+
findings: [
|
|
74
|
+
finding({
|
|
75
|
+
severity: "moderate",
|
|
76
|
+
level: "warn",
|
|
77
|
+
}),
|
|
78
|
+
],
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
expect(agentFormat.passed).toBe(true);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("omits full findings and evidence by default", () => {
|
|
85
|
+
const agentFormat = buildAgentFormat({
|
|
86
|
+
filesScanned: 1,
|
|
87
|
+
findings: [finding()],
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
expect(agentFormat.findings).toBeUndefined();
|
|
91
|
+
expect([...agentFormat.fix_first, ...agentFormat.remaining][0]?.evidence).toEqual([]);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("keeps a gradient score for noisy scans", () => {
|
|
95
|
+
const findings = Array.from({ length: 80 }, (_, index) =>
|
|
96
|
+
finding({
|
|
97
|
+
fingerprint: `fp_${index}`,
|
|
98
|
+
severity: "moderate",
|
|
99
|
+
level: "warn",
|
|
100
|
+
})
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
expect(buildAgentFormat({ filesScanned: 3, findings }).score).toBeGreaterThan(0);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { severityLevel } from "./severity.js";
|
|
2
|
+
import type { AgentFormat, Finding } from "./schemas/index.js";
|
|
3
|
+
import type { SuppressionDirectiveFact } from "./facts/index.js";
|
|
4
|
+
|
|
5
|
+
export interface BuildAgentFormatInput {
|
|
6
|
+
findings: readonly Finding[];
|
|
7
|
+
filesScanned: number;
|
|
8
|
+
passed?: boolean;
|
|
9
|
+
suppressions?: readonly SuppressionDirectiveFact[];
|
|
10
|
+
iteration?: number;
|
|
11
|
+
maxIterations?: number;
|
|
12
|
+
previousScore?: number;
|
|
13
|
+
maxFindings?: number;
|
|
14
|
+
includeEvidence?: boolean;
|
|
15
|
+
includeFindings?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function buildAgentFormat(input: BuildAgentFormatInput): AgentFormat {
|
|
19
|
+
const score = scoreFindings(input.findings);
|
|
20
|
+
const visibleFindings = uniqueFindings(input.findings).slice(0, input.maxFindings ?? 50);
|
|
21
|
+
const agentFindings = visibleFindings.map((finding) => ({
|
|
22
|
+
code: finding.code,
|
|
23
|
+
ruleId: finding.ruleId,
|
|
24
|
+
filePath: finding.location.file,
|
|
25
|
+
line: finding.location.line,
|
|
26
|
+
col: finding.location.column,
|
|
27
|
+
severity: finding.severity,
|
|
28
|
+
level: severityLevel(finding.severity),
|
|
29
|
+
message: finding.message,
|
|
30
|
+
evidence: input.includeEvidence ? finding.evidence : [],
|
|
31
|
+
plan: planForFinding(finding),
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
// Keep this aligned with the report summary: warn-level findings are debt, not
|
|
35
|
+
// scan failure, unless the caller has already folded fail-on-warnings into
|
|
36
|
+
// `passed:false`.
|
|
37
|
+
const hasErrors = input.findings.some((finding) => severityLevel(finding.severity) === "error");
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
schemaVersion: "typestyle.agent.v1",
|
|
41
|
+
passed: (input.passed ?? true) && !hasErrors,
|
|
42
|
+
score,
|
|
43
|
+
summary: agentSummary(input.findings.length, input.filesScanned, score),
|
|
44
|
+
fix_first: agentFindings.filter((finding) => finding.plan.confidence >= 0.8),
|
|
45
|
+
remaining: agentFindings.filter((finding) => finding.plan.confidence < 0.8),
|
|
46
|
+
suppression_template: {
|
|
47
|
+
directive: '// @fragments-expect-error {code} reason="..." expires="YYYY-MM-DD"',
|
|
48
|
+
requiredFields: ["reason"],
|
|
49
|
+
example:
|
|
50
|
+
'// @fragments-expect-error FUI2001 reason="hero brand red approved" expires="2026-08-01"',
|
|
51
|
+
},
|
|
52
|
+
budget: {
|
|
53
|
+
iteration: input.iteration ?? 0,
|
|
54
|
+
maxIterations: input.maxIterations ?? 3,
|
|
55
|
+
scoreDelta: input.previousScore === undefined ? 0 : score - input.previousScore,
|
|
56
|
+
},
|
|
57
|
+
...(input.includeFindings ? { findings: [...input.findings] } : {}),
|
|
58
|
+
...(input.suppressions
|
|
59
|
+
? {
|
|
60
|
+
suppressions: input.suppressions.map((suppression) => ({
|
|
61
|
+
code: suppression.code,
|
|
62
|
+
reason: suppression.reason,
|
|
63
|
+
...(suppression.expiresOn ? { expiresOn: suppression.expiresOn } : {}),
|
|
64
|
+
scope: "line" as const,
|
|
65
|
+
})),
|
|
66
|
+
}
|
|
67
|
+
: {}),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function uniqueFindings(findings: readonly Finding[]): Finding[] {
|
|
72
|
+
const seen = new Set<string>();
|
|
73
|
+
const out: Finding[] = [];
|
|
74
|
+
for (const finding of findings) {
|
|
75
|
+
const key =
|
|
76
|
+
finding.fingerprint ??
|
|
77
|
+
`${finding.ruleId}\0${finding.location.file}\0${finding.location.line}\0${finding.message}`;
|
|
78
|
+
if (seen.has(key)) continue;
|
|
79
|
+
seen.add(key);
|
|
80
|
+
out.push(finding);
|
|
81
|
+
}
|
|
82
|
+
return out;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function scoreFindings(findings: readonly Finding[]): number {
|
|
86
|
+
if (findings.length === 0) return 100;
|
|
87
|
+
let penalty = 0;
|
|
88
|
+
for (const finding of findings) {
|
|
89
|
+
const level = severityLevel(finding.severity);
|
|
90
|
+
penalty += level === "error" ? 10 : level === "warn" ? 3 : 1;
|
|
91
|
+
}
|
|
92
|
+
return Math.max(1, Math.round(100 / (1 + penalty / 40)));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function agentSummary(count: number, filesScanned: number, score: number): string {
|
|
96
|
+
if (count === 0) return `passed with score ${score}; no findings`;
|
|
97
|
+
return `${count} finding${count === 1 ? "" : "s"} across ${filesScanned} file${filesScanned === 1 ? "" : "s"}; score ${score}`;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function planForFinding(finding: Finding): AgentFormat["fix_first"][number]["plan"] {
|
|
101
|
+
if (finding.fix?.deterministic === true) {
|
|
102
|
+
return {
|
|
103
|
+
tool: "design_system/conform",
|
|
104
|
+
args: {
|
|
105
|
+
filePath: finding.location.file,
|
|
106
|
+
fingerprint: finding.fingerprint,
|
|
107
|
+
apply: "deterministic",
|
|
108
|
+
},
|
|
109
|
+
confidence: 0.95,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
if (
|
|
113
|
+
finding.code?.startsWith("FUI1") ||
|
|
114
|
+
finding.ruleId.includes("component") ||
|
|
115
|
+
finding.ruleId.includes("canonical")
|
|
116
|
+
) {
|
|
117
|
+
return {
|
|
118
|
+
tool: "design_system/conform",
|
|
119
|
+
args: { filePath: finding.location.file, apply: "deterministic" },
|
|
120
|
+
confidence: 0.65,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
tool: "design_system/conform",
|
|
125
|
+
args: {
|
|
126
|
+
filePath: finding.location.file,
|
|
127
|
+
property:
|
|
128
|
+
stringAttribute(finding.attributes?.property) ??
|
|
129
|
+
stringAttribute(finding.attributes?.prop) ??
|
|
130
|
+
"color",
|
|
131
|
+
value:
|
|
132
|
+
stringAttribute(finding.attributes?.rawValue) ?? stringAttribute(finding.attributes?.color),
|
|
133
|
+
apply: "deterministic",
|
|
134
|
+
},
|
|
135
|
+
confidence: 0.55,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function stringAttribute(value: unknown): string | undefined {
|
|
140
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
141
|
+
}
|
package/src/bundle.ts
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
export const bundleSchemaVersionSchema = z.literal(1);
|
|
4
|
+
export const bundleTargetSchema = z.enum([
|
|
5
|
+
'cursor',
|
|
6
|
+
'agents',
|
|
7
|
+
'claude',
|
|
8
|
+
'copilot',
|
|
9
|
+
]);
|
|
10
|
+
|
|
11
|
+
export const bundleArtifactMetadataSchema = z.object({
|
|
12
|
+
kind: z.enum([
|
|
13
|
+
'manifest',
|
|
14
|
+
'tokens',
|
|
15
|
+
'design-system',
|
|
16
|
+
'readme',
|
|
17
|
+
'helper',
|
|
18
|
+
'component',
|
|
19
|
+
]),
|
|
20
|
+
key: z.string().min(1),
|
|
21
|
+
label: z.string().min(1),
|
|
22
|
+
path: z.string().min(1),
|
|
23
|
+
etag: z.string().min(1),
|
|
24
|
+
updatedAt: z.string().datetime(),
|
|
25
|
+
target: bundleTargetSchema.optional(),
|
|
26
|
+
componentId: z.string().optional(),
|
|
27
|
+
contentType: z.enum(['application/json', 'text/markdown']),
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const bundleManifestRelationSchema = z.object({
|
|
31
|
+
type: z.string().min(1),
|
|
32
|
+
componentId: z.string().min(1),
|
|
33
|
+
name: z.string().min(1),
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const bundleManifestCompoundChildSchema = z.object({
|
|
37
|
+
componentId: z.string().min(1),
|
|
38
|
+
name: z.string().min(1),
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
export const bundleManifestComponentEntrySchema = z.object({
|
|
42
|
+
componentId: z.string().min(1),
|
|
43
|
+
file: z.string().min(1),
|
|
44
|
+
name: z.string().min(1),
|
|
45
|
+
publicSlug: z.string().nullable(),
|
|
46
|
+
tier: z.enum(['core', 'composition']),
|
|
47
|
+
category: z.string().min(1),
|
|
48
|
+
status: z.string().min(1),
|
|
49
|
+
description: z.string(),
|
|
50
|
+
propCount: z.number().int().nonnegative(),
|
|
51
|
+
hasExamples: z.boolean(),
|
|
52
|
+
hasCompoundChildren: z.boolean(),
|
|
53
|
+
relations: z.array(bundleManifestRelationSchema),
|
|
54
|
+
compoundChildren: z.array(bundleManifestCompoundChildSchema),
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
export const bundleManifestSchema = z.object({
|
|
58
|
+
schemaVersion: bundleSchemaVersionSchema,
|
|
59
|
+
catalogRevision: z.string().min(1),
|
|
60
|
+
catalogUpdatedAt: z.string().datetime(),
|
|
61
|
+
org: z.object({
|
|
62
|
+
id: z.string().min(1),
|
|
63
|
+
name: z.string().min(1),
|
|
64
|
+
slug: z.string().min(1),
|
|
65
|
+
}),
|
|
66
|
+
designSystem: z.object({
|
|
67
|
+
name: z.string().min(1),
|
|
68
|
+
packageName: z.string().nullable(),
|
|
69
|
+
importPath: z.string().nullable(),
|
|
70
|
+
}),
|
|
71
|
+
sourceBinding: z.object({
|
|
72
|
+
bindingId: z.string().min(1),
|
|
73
|
+
projectId: z.string().min(1),
|
|
74
|
+
projectName: z.string().min(1),
|
|
75
|
+
repoFullName: z.string().min(1),
|
|
76
|
+
resolution: z.enum(['explicit', 'auto']),
|
|
77
|
+
}),
|
|
78
|
+
totalComponents: z.number().int().nonnegative(),
|
|
79
|
+
totalTokens: z.number().int().nonnegative(),
|
|
80
|
+
tokenCategories: z.record(z.string(), z.number().int().nonnegative()),
|
|
81
|
+
components: z.record(z.string(), bundleManifestComponentEntrySchema),
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const bundleTokenEntrySchema = z.object({
|
|
85
|
+
name: z.string().min(1),
|
|
86
|
+
path: z.array(z.string().min(1)),
|
|
87
|
+
category: z.string().min(1),
|
|
88
|
+
value: z.string(),
|
|
89
|
+
type: z.string().optional(),
|
|
90
|
+
description: z.string().optional(),
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
export const bundleTokenFileSchema = z.object({
|
|
94
|
+
schemaVersion: bundleSchemaVersionSchema,
|
|
95
|
+
catalogRevision: z.string().min(1),
|
|
96
|
+
catalogUpdatedAt: z.string().datetime(),
|
|
97
|
+
categories: z.record(
|
|
98
|
+
z.string(),
|
|
99
|
+
z.object({
|
|
100
|
+
count: z.number().int().nonnegative(),
|
|
101
|
+
tokens: z.array(bundleTokenEntrySchema),
|
|
102
|
+
}),
|
|
103
|
+
),
|
|
104
|
+
flat: z.array(bundleTokenEntrySchema),
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
export const bundleComponentShardSchema = z.object({
|
|
108
|
+
schemaVersion: bundleSchemaVersionSchema,
|
|
109
|
+
catalogRevision: z.string().min(1),
|
|
110
|
+
catalogUpdatedAt: z.string().datetime(),
|
|
111
|
+
componentId: z.string().min(1),
|
|
112
|
+
file: z.string().min(1),
|
|
113
|
+
component: z.object({
|
|
114
|
+
componentKey: z.string().min(1),
|
|
115
|
+
publicRef: z.string().min(1),
|
|
116
|
+
publicId: z.string().nullable(),
|
|
117
|
+
publicSlug: z.string().nullable(),
|
|
118
|
+
isCanonical: z.boolean().optional(),
|
|
119
|
+
name: z.string().min(1),
|
|
120
|
+
tier: z.enum(['core', 'composition']),
|
|
121
|
+
category: z.string().min(1),
|
|
122
|
+
status: z.string().min(1),
|
|
123
|
+
description: z.string(),
|
|
124
|
+
usageGuidance: z.string(),
|
|
125
|
+
dos: z.array(z.string()),
|
|
126
|
+
donts: z.array(z.string()),
|
|
127
|
+
props: z.record(z.string(), z.unknown()),
|
|
128
|
+
relations: z.array(z.record(z.string(), z.unknown())),
|
|
129
|
+
compoundChildren: z.array(bundleManifestCompoundChildSchema),
|
|
130
|
+
examples: z.array(z.record(z.string(), z.unknown())),
|
|
131
|
+
source: z.literal('design-system'),
|
|
132
|
+
sourcePath: z.string().optional(),
|
|
133
|
+
sourceRepoFullName: z.string().optional(),
|
|
134
|
+
parentComponentName: z.string().optional(),
|
|
135
|
+
parentComponentKey: z.string().optional(),
|
|
136
|
+
}),
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
export type BundleSchemaVersion = z.infer<typeof bundleSchemaVersionSchema>;
|
|
140
|
+
export type BundleTarget = z.infer<typeof bundleTargetSchema>;
|
|
141
|
+
export type BundleArtifactMetadata = z.infer<
|
|
142
|
+
typeof bundleArtifactMetadataSchema
|
|
143
|
+
>;
|
|
144
|
+
export type BundleManifestComponentEntry = z.infer<
|
|
145
|
+
typeof bundleManifestComponentEntrySchema
|
|
146
|
+
>;
|
|
147
|
+
export type BundleManifest = z.infer<typeof bundleManifestSchema>;
|
|
148
|
+
export type BundleComponentShard = z.infer<typeof bundleComponentShardSchema>;
|
|
149
|
+
export type BundleTokenFile = z.infer<typeof bundleTokenFileSchema>;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { describe, expect, it } from "vitest";
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
CODES,
|
|
8
|
+
EXPLAIN_URL_BASE,
|
|
9
|
+
bridgeSourceViolation,
|
|
10
|
+
byCode,
|
|
11
|
+
byRuleId,
|
|
12
|
+
} from "../index.js";
|
|
13
|
+
|
|
14
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
15
|
+
const __dirname = path.dirname(__filename);
|
|
16
|
+
const registryPath = path.resolve(__dirname, "../../../../../docs/codes/registry.json");
|
|
17
|
+
|
|
18
|
+
describe("FUI code registry", () => {
|
|
19
|
+
it("does not contain duplicate codes", () => {
|
|
20
|
+
expect(new Set(CODES.map((entry) => entry.code)).size).toBe(CODES.length);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("does not contain duplicate rule IDs", () => {
|
|
24
|
+
expect(new Set(CODES.map((entry) => entry.ruleId)).size).toBe(CODES.length);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("builds lookup maps from the registry", () => {
|
|
28
|
+
for (const entry of CODES) {
|
|
29
|
+
expect(byCode.get(entry.code)).toBe(entry);
|
|
30
|
+
expect(byRuleId.get(entry.ruleId)).toBe(entry);
|
|
31
|
+
expect(entry.explainUrl).toBe(`${EXPLAIN_URL_BASE}${entry.code}`);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("matches the committed docs registry snapshot", () => {
|
|
36
|
+
const snapshot = JSON.parse(readFileSync(registryPath, "utf8"));
|
|
37
|
+
expect(snapshot).toEqual(CODES);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("bridges source violations into canonical findings with code metadata", () => {
|
|
41
|
+
const bridged = bridgeSourceViolation({
|
|
42
|
+
validator: "scss",
|
|
43
|
+
violation: {
|
|
44
|
+
nodeId: "scss:src/app.css:2:4",
|
|
45
|
+
nodeType: "ScssDeclaration",
|
|
46
|
+
rule: "tokens/require-dual-fallback",
|
|
47
|
+
severity: "serious",
|
|
48
|
+
message: "Token fallback is missing",
|
|
49
|
+
filePath: "src/app.css",
|
|
50
|
+
line: 2,
|
|
51
|
+
column: 4,
|
|
52
|
+
rawValue: "var(--fui-color-accent)",
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
expect(bridged.finding).toMatchObject({
|
|
57
|
+
ruleId: "tokens/require-dual-fallback",
|
|
58
|
+
code: "FUI2003",
|
|
59
|
+
helpUrl: `${EXPLAIN_URL_BASE}FUI2003`,
|
|
60
|
+
location: { file: "src/app.css", line: 2, column: 4 },
|
|
61
|
+
attributes: {
|
|
62
|
+
source: "source-validator",
|
|
63
|
+
validator: "scss",
|
|
64
|
+
rawValue: "var(--fui-color-accent)",
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
expect(bridged.violation).toMatchObject({
|
|
68
|
+
code: "FUI2003",
|
|
69
|
+
helpUrl: `${EXPLAIN_URL_BASE}FUI2003`,
|
|
70
|
+
level: "error",
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
});
|