@usefragments/core 1.0.0 → 1.2.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-R3X7UBHU.js → chunk-BQQTMGLA.js} +24 -2
- package/dist/chunk-BQQTMGLA.js.map +1 -0
- package/dist/{chunk-243QYRUF.js → chunk-DBVTSUMT.js} +71 -44
- package/dist/chunk-DBVTSUMT.js.map +1 -0
- package/dist/codes/index.d.ts +1 -1
- 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-BOa3KyiJ.d.ts → governance-DYSirwJu.d.ts} +22 -1
- package/dist/index.d.ts +8 -6
- package/dist/index.js +156 -19
- package/dist/index.js.map +1 -1
- package/dist/react-types.d.ts +1 -1
- package/dist/schemas/index.d.ts +797 -4
- package/dist/schemas/index.js +9 -1
- package/dist/test-utils.d.ts +1 -1
- package/package.json +1 -1
- package/src/agent-format.test.ts +72 -2
- package/src/agent-format.ts +10 -5
- package/src/codes/__tests__/codes.test.ts +57 -7
- package/src/codes/codes.ts +44 -42
- package/src/conform-prove.ts +4 -8
- package/src/contract/index.ts +3 -0
- package/src/contract/preimage.test.ts +50 -0
- package/src/contract/preimage.ts +56 -0
- package/src/facts/builders.ts +2 -0
- package/src/facts/fact-index.ts +4 -0
- package/src/facts/facts.test.ts +49 -0
- package/src/facts/types.ts +1 -0
- package/src/governance-integrity.test.ts +2 -0
- package/src/governance-integrity.ts +7 -1
- package/src/index.ts +9 -0
- package/src/rules/components-prefer-library.ts +165 -7
- package/src/rules/emit-gate.test.ts +24 -2
- package/src/rules/emit-gate.ts +15 -7
- package/src/rules/fix-availability.ts +33 -0
- package/src/rules/rules.test.ts +178 -23
- package/src/rules/taxonomy.test.ts +6 -3
- package/src/rules/tiers.ts +1 -1
- package/src/schemas/index.ts +25 -1
- package/dist/chunk-243QYRUF.js.map +0 -1
- package/dist/chunk-R3X7UBHU.js.map +0 -1
package/src/rules/rules.test.ts
CHANGED
|
@@ -1775,7 +1775,7 @@ describe("components/prefer-library", () => {
|
|
|
1775
1775
|
expect(ruleComponentsPreferLibrary(ix)).toHaveLength(0);
|
|
1776
1776
|
});
|
|
1777
1777
|
|
|
1778
|
-
it("
|
|
1778
|
+
it("keeps same-name local import redirects advisory without a verified source contract", () => {
|
|
1779
1779
|
const ix = new FactIndex();
|
|
1780
1780
|
ix.addMany(
|
|
1781
1781
|
compileGlobalGovernanceFacts({
|
|
@@ -1812,13 +1812,57 @@ describe("components/prefer-library", () => {
|
|
|
1812
1812
|
expect(findings[0]).toMatchObject({
|
|
1813
1813
|
ruleId: "components/prefer-library",
|
|
1814
1814
|
code: "FUI1004",
|
|
1815
|
+
fix: {
|
|
1816
|
+
kind: "replaceImport",
|
|
1817
|
+
from: "../components/Card",
|
|
1818
|
+
to: "@acme/ui",
|
|
1819
|
+
deterministic: false,
|
|
1820
|
+
},
|
|
1815
1821
|
attributes: {
|
|
1816
1822
|
suggestedComponent: "Card",
|
|
1817
1823
|
suggestedImport: "@acme/ui",
|
|
1824
|
+
advisory: true,
|
|
1825
|
+
contractProof: "missing",
|
|
1818
1826
|
},
|
|
1819
1827
|
});
|
|
1820
1828
|
});
|
|
1821
1829
|
|
|
1830
|
+
it("does not mistake TanStack Router Link for the canonical Link by name alone", () => {
|
|
1831
|
+
const ix = new FactIndex();
|
|
1832
|
+
ix.addMany(
|
|
1833
|
+
compileGlobalGovernanceFacts({
|
|
1834
|
+
rules: {
|
|
1835
|
+
"components/prefer-library": {
|
|
1836
|
+
enabled: true,
|
|
1837
|
+
severity: "error",
|
|
1838
|
+
options: {
|
|
1839
|
+
canonicalSources: [{ kind: "npm", specifier: "@acme/ui", include: ["Link"] }],
|
|
1840
|
+
},
|
|
1841
|
+
},
|
|
1842
|
+
},
|
|
1843
|
+
})
|
|
1844
|
+
);
|
|
1845
|
+
ix.add(
|
|
1846
|
+
makeUsageNodeFact({
|
|
1847
|
+
file: "src/app.tsx",
|
|
1848
|
+
nodePath: "0:0",
|
|
1849
|
+
element: "Link",
|
|
1850
|
+
location: { file: "src/app.tsx", line: 5, column: 4 },
|
|
1851
|
+
})
|
|
1852
|
+
);
|
|
1853
|
+
ix.add(
|
|
1854
|
+
makeUsageImportFact({
|
|
1855
|
+
file: "src/app.tsx",
|
|
1856
|
+
local: "Link",
|
|
1857
|
+
imported: "Link",
|
|
1858
|
+
source: "@tanstack/react-router",
|
|
1859
|
+
location: { file: "src/app.tsx", line: 1, column: 0 },
|
|
1860
|
+
})
|
|
1861
|
+
);
|
|
1862
|
+
|
|
1863
|
+
expect(ruleComponentsPreferLibrary(ix)).toHaveLength(0);
|
|
1864
|
+
});
|
|
1865
|
+
|
|
1822
1866
|
it("recognizes a current import through legacy npm source and mapping spellings", () => {
|
|
1823
1867
|
const ix = new FactIndex();
|
|
1824
1868
|
ix.addMany(
|
|
@@ -2159,7 +2203,7 @@ describe("components/prefer-library", () => {
|
|
|
2159
2203
|
expect(ruleComponentsPreferLibrary(ix)).toHaveLength(0);
|
|
2160
2204
|
});
|
|
2161
2205
|
|
|
2162
|
-
it("
|
|
2206
|
+
it("keeps a complete mapped replacement guided until the conform AST preflight", () => {
|
|
2163
2207
|
const ix = new FactIndex();
|
|
2164
2208
|
ix.addMany(
|
|
2165
2209
|
compileGlobalGovernanceFacts({
|
|
@@ -2183,6 +2227,15 @@ describe("components/prefer-library", () => {
|
|
|
2183
2227
|
},
|
|
2184
2228
|
})
|
|
2185
2229
|
);
|
|
2230
|
+
ix.add(
|
|
2231
|
+
makeUsageImportFact({
|
|
2232
|
+
file: "src/app.tsx",
|
|
2233
|
+
local: "Button",
|
|
2234
|
+
imported: "Button",
|
|
2235
|
+
source: "@acme/ui",
|
|
2236
|
+
location: { file: "src/app.tsx", line: 1, column: 0 },
|
|
2237
|
+
})
|
|
2238
|
+
);
|
|
2186
2239
|
ix.add(
|
|
2187
2240
|
makeUsageNodeFact({
|
|
2188
2241
|
file: "src/app.tsx",
|
|
@@ -2200,14 +2253,102 @@ describe("components/prefer-library", () => {
|
|
|
2200
2253
|
kind: "replaceComponent",
|
|
2201
2254
|
from: "button",
|
|
2202
2255
|
to: "Button",
|
|
2203
|
-
deterministic:
|
|
2256
|
+
deterministic: false,
|
|
2204
2257
|
},
|
|
2205
2258
|
attributes: {
|
|
2206
2259
|
suggestedComponent: "Button",
|
|
2207
2260
|
suggestedImport: "@acme/ui",
|
|
2208
2261
|
propMapping: [{ rawProp: "className", canonicalProp: "variant" }],
|
|
2262
|
+
propCompatibility: "observed-complete",
|
|
2209
2263
|
},
|
|
2210
2264
|
});
|
|
2265
|
+
expect(findings[0].attributes).not.toHaveProperty("canonicalMappingConfirmed");
|
|
2266
|
+
});
|
|
2267
|
+
|
|
2268
|
+
it("withholds a native-element fix when a native-only prop has no confirmed mapping", () => {
|
|
2269
|
+
const ix = new FactIndex();
|
|
2270
|
+
ix.addMany(
|
|
2271
|
+
compileGlobalGovernanceFacts({
|
|
2272
|
+
rules: {
|
|
2273
|
+
"components/prefer-library": {
|
|
2274
|
+
enabled: true,
|
|
2275
|
+
severity: "warning",
|
|
2276
|
+
options: {
|
|
2277
|
+
canonicalMappings: [
|
|
2278
|
+
{
|
|
2279
|
+
name: "Button",
|
|
2280
|
+
canonical: "Button",
|
|
2281
|
+
htmlEquivalent: "button",
|
|
2282
|
+
importPath: "@acme/ui",
|
|
2283
|
+
},
|
|
2284
|
+
],
|
|
2285
|
+
},
|
|
2286
|
+
},
|
|
2287
|
+
},
|
|
2288
|
+
})
|
|
2289
|
+
);
|
|
2290
|
+
ix.add(
|
|
2291
|
+
makeUsageImportFact({
|
|
2292
|
+
file: "src/app.tsx",
|
|
2293
|
+
local: "Button",
|
|
2294
|
+
imported: "Button",
|
|
2295
|
+
source: "@acme/ui",
|
|
2296
|
+
location: { file: "src/app.tsx", line: 1, column: 0 },
|
|
2297
|
+
})
|
|
2298
|
+
);
|
|
2299
|
+
addRawNode(ix, {
|
|
2300
|
+
element: "button",
|
|
2301
|
+
nodePath: "0:0",
|
|
2302
|
+
props: [{ prop: "formAction", value: "/save" }],
|
|
2303
|
+
});
|
|
2304
|
+
|
|
2305
|
+
const findings = ruleComponentsPreferLibrary(ix);
|
|
2306
|
+
expect(findings).toHaveLength(1);
|
|
2307
|
+
expect(findings[0]).toMatchObject({
|
|
2308
|
+
fix: { kind: "replaceComponent", deterministic: false },
|
|
2309
|
+
attributes: {
|
|
2310
|
+
propCompatibility: "incomplete",
|
|
2311
|
+
unprovenProps: ["formAction"],
|
|
2312
|
+
},
|
|
2313
|
+
});
|
|
2314
|
+
});
|
|
2315
|
+
|
|
2316
|
+
it("keeps replacement metadata but marks it non-deterministic when its import is absent", () => {
|
|
2317
|
+
const ix = new FactIndex();
|
|
2318
|
+
ix.addMany(
|
|
2319
|
+
compileGlobalGovernanceFacts({
|
|
2320
|
+
rules: {
|
|
2321
|
+
"components/prefer-library": {
|
|
2322
|
+
enabled: true,
|
|
2323
|
+
severity: "warning",
|
|
2324
|
+
options: {
|
|
2325
|
+
canonicalMappings: [
|
|
2326
|
+
{
|
|
2327
|
+
name: "Button",
|
|
2328
|
+
canonical: "Button",
|
|
2329
|
+
htmlEquivalent: "button",
|
|
2330
|
+
importPath: "@acme/ui",
|
|
2331
|
+
},
|
|
2332
|
+
],
|
|
2333
|
+
},
|
|
2334
|
+
},
|
|
2335
|
+
},
|
|
2336
|
+
})
|
|
2337
|
+
);
|
|
2338
|
+
addRawNode(ix, { element: "button", nodePath: "0:0", text: "Save" });
|
|
2339
|
+
|
|
2340
|
+
const findings = ruleComponentsPreferLibrary(ix);
|
|
2341
|
+
expect(findings).toHaveLength(1);
|
|
2342
|
+
expect(findings[0].attributes).toMatchObject({
|
|
2343
|
+
suggestedComponent: "Button",
|
|
2344
|
+
suggestedImport: "@acme/ui",
|
|
2345
|
+
});
|
|
2346
|
+
expect(findings[0].fix).toMatchObject({
|
|
2347
|
+
kind: "replaceComponent",
|
|
2348
|
+
from: "button",
|
|
2349
|
+
to: "Button",
|
|
2350
|
+
deterministic: false,
|
|
2351
|
+
});
|
|
2211
2352
|
});
|
|
2212
2353
|
|
|
2213
2354
|
it("does not flag generic <div>/<span> from an inferred component primitive (anti-flood)", () => {
|
|
@@ -2561,29 +2702,38 @@ describe("components/prefer-library", () => {
|
|
|
2561
2702
|
expect(findings.filter((f) => f.attributes?.suggestedComponent === "Hint")).toHaveLength(0);
|
|
2562
2703
|
});
|
|
2563
2704
|
|
|
2564
|
-
it("STILL fires the legit name-based import
|
|
2705
|
+
it("STILL fires the legit local name-based import redirect (<Modal> → import @acme/ui)", () => {
|
|
2565
2706
|
// Regression guard: removing the canonical selector must not kill the
|
|
2566
2707
|
// real name-match import-redirect. <Modal> (the component's own name)
|
|
2567
2708
|
// imported from the wrong path is genuine drift.
|
|
2568
2709
|
const findings = importedFindings(
|
|
2569
2710
|
[{ name: "Modal", canonical: "Dialog", importPath: "@acme/ui" }],
|
|
2570
2711
|
"Modal",
|
|
2571
|
-
"
|
|
2712
|
+
"../Modal"
|
|
2572
2713
|
);
|
|
2573
2714
|
expect(findings).toHaveLength(1);
|
|
2574
2715
|
expect(findings[0].fix?.kind).toBe("replaceImport");
|
|
2575
2716
|
expect(findings[0].attributes?.suggestedComponent).toBe("Modal");
|
|
2576
2717
|
});
|
|
2577
2718
|
|
|
2578
|
-
it("STILL fires when name === canonical (<Dialog> for name 'Dialog')
|
|
2719
|
+
it("STILL fires for a local import when name === canonical (<Dialog> for name 'Dialog')", () => {
|
|
2579
2720
|
const findings = importedFindings(
|
|
2580
2721
|
[{ name: "Dialog", canonical: "Dialog", importPath: "@acme/ui" }],
|
|
2581
2722
|
"Dialog",
|
|
2582
|
-
"
|
|
2723
|
+
"../Dialog"
|
|
2583
2724
|
);
|
|
2584
2725
|
expect(findings).toHaveLength(1);
|
|
2585
2726
|
expect(findings[0].attributes?.suggestedComponent).toBe("Dialog");
|
|
2586
2727
|
});
|
|
2728
|
+
|
|
2729
|
+
it("does not redirect a package-owned same-name Link without an explicit import policy", () => {
|
|
2730
|
+
const findings = importedFindings(
|
|
2731
|
+
[{ name: "Link", canonical: "Link", importPath: "@acme/ui" }],
|
|
2732
|
+
"Link",
|
|
2733
|
+
"@tanstack/react-router"
|
|
2734
|
+
);
|
|
2735
|
+
expect(findings).toHaveLength(0);
|
|
2736
|
+
});
|
|
2587
2737
|
});
|
|
2588
2738
|
|
|
2589
2739
|
describe("directory canonical source (unfiltered include — dogfood shape)", () => {
|
|
@@ -2672,7 +2822,7 @@ describe("components/prefer-library", () => {
|
|
|
2672
2822
|
expect(ruleComponentsPreferLibrary(ix)).toHaveLength(0);
|
|
2673
2823
|
});
|
|
2674
2824
|
|
|
2675
|
-
it("flags raw <button> → Button when Button is
|
|
2825
|
+
it("flags raw <button> → Button without advertising it as deterministic when Button is not imported", () => {
|
|
2676
2826
|
const ix = new FactIndex();
|
|
2677
2827
|
ix.addMany(
|
|
2678
2828
|
compileGlobalGovernanceFacts({
|
|
@@ -2704,18 +2854,18 @@ describe("components/prefer-library", () => {
|
|
|
2704
2854
|
expect(findings).toHaveLength(1);
|
|
2705
2855
|
expect(findings[0]).toMatchObject({
|
|
2706
2856
|
ruleId: "components/prefer-library",
|
|
2707
|
-
fix: {
|
|
2708
|
-
kind: "replaceComponent",
|
|
2709
|
-
from: "button",
|
|
2710
|
-
to: "Button",
|
|
2711
|
-
deterministic: true,
|
|
2712
|
-
},
|
|
2713
2857
|
attributes: {
|
|
2714
2858
|
rawValue: "button",
|
|
2715
2859
|
suggestedComponent: "Button",
|
|
2716
2860
|
suggestedImportSourceKind: "directory",
|
|
2717
2861
|
},
|
|
2718
2862
|
});
|
|
2863
|
+
expect(findings[0].fix).toMatchObject({
|
|
2864
|
+
kind: "replaceComponent",
|
|
2865
|
+
from: "button",
|
|
2866
|
+
to: "Button",
|
|
2867
|
+
deterministic: false,
|
|
2868
|
+
});
|
|
2719
2869
|
});
|
|
2720
2870
|
|
|
2721
2871
|
it("flags a raw element by canonical vocab id when display name diverges (Modal → Dialog)", () => {
|
|
@@ -2825,7 +2975,9 @@ describe("components/prefer-library", () => {
|
|
|
2825
2975
|
|
|
2826
2976
|
const findings = ruleComponentsPreferLibrary(ix);
|
|
2827
2977
|
|
|
2828
|
-
//
|
|
2978
|
+
// Type-specific mappings win over the generic tag-level one; text falls
|
|
2979
|
+
// back. No canonical bindings exist in this fixture, so suggestions remain
|
|
2980
|
+
// useful but are not advertised as deterministic edits.
|
|
2829
2981
|
expect(findings.map((finding) => finding.attributes?.suggestedComponent)).toEqual([
|
|
2830
2982
|
"Checkbox",
|
|
2831
2983
|
"RadioGroup",
|
|
@@ -2836,7 +2988,7 @@ describe("components/prefer-library", () => {
|
|
|
2836
2988
|
"RadioGroup",
|
|
2837
2989
|
"TextField",
|
|
2838
2990
|
]);
|
|
2839
|
-
expect(findings.every((finding) => finding.fix?.deterministic ===
|
|
2991
|
+
expect(findings.every((finding) => finding.fix?.deterministic === false)).toBe(true);
|
|
2840
2992
|
});
|
|
2841
2993
|
|
|
2842
2994
|
it("does not let a type-constrained mapping match a different or untyped input", () => {
|
|
@@ -2872,7 +3024,7 @@ describe("components/prefer-library", () => {
|
|
|
2872
3024
|
expect(ruleComponentsPreferLibrary(ix)).toHaveLength(0);
|
|
2873
3025
|
});
|
|
2874
3026
|
|
|
2875
|
-
it("
|
|
3027
|
+
it("keeps a same-name local import redirect advisory without source-contract proof", () => {
|
|
2876
3028
|
const ix = new FactIndex();
|
|
2877
3029
|
ix.addMany(
|
|
2878
3030
|
compileGlobalGovernanceFacts({
|
|
@@ -2918,9 +3070,10 @@ describe("components/prefer-library", () => {
|
|
|
2918
3070
|
kind: "replaceImport",
|
|
2919
3071
|
from: "../Button",
|
|
2920
3072
|
to: "@acme/ui",
|
|
2921
|
-
deterministic:
|
|
3073
|
+
deterministic: false,
|
|
2922
3074
|
},
|
|
2923
3075
|
location: { line: 1, column: 0 },
|
|
3076
|
+
attributes: { advisory: true, contractProof: "missing" },
|
|
2924
3077
|
});
|
|
2925
3078
|
});
|
|
2926
3079
|
|
|
@@ -2967,13 +3120,14 @@ describe("components/prefer-library", () => {
|
|
|
2967
3120
|
suggestedComponent: "Alert",
|
|
2968
3121
|
matchedRole: "status",
|
|
2969
3122
|
confidence: 0.9,
|
|
3123
|
+
advisory: true,
|
|
2970
3124
|
},
|
|
2971
3125
|
});
|
|
2972
3126
|
// Role matches are advisory: no deterministic tag→component rewrite.
|
|
2973
3127
|
expect(findings[0].fix).toBeUndefined();
|
|
2974
3128
|
});
|
|
2975
3129
|
|
|
2976
|
-
it("flags input type primitives config-free
|
|
3130
|
+
it("flags input type primitives config-free without unsafe missing-import fixes", () => {
|
|
2977
3131
|
const ix = preferLibraryIndex();
|
|
2978
3132
|
addRawNode(ix, {
|
|
2979
3133
|
element: "input",
|
|
@@ -3003,7 +3157,7 @@ describe("components/prefer-library", () => {
|
|
|
3003
3157
|
"input-type",
|
|
3004
3158
|
"input-type",
|
|
3005
3159
|
]);
|
|
3006
|
-
expect(findings.
|
|
3160
|
+
expect(findings.every((finding) => finding.fix?.deterministic === false)).toBe(true);
|
|
3007
3161
|
});
|
|
3008
3162
|
|
|
3009
3163
|
it("does not infer generic text inputs as deterministic primitives", () => {
|
|
@@ -3093,7 +3247,7 @@ describe("components/prefer-library", () => {
|
|
|
3093
3247
|
kind: "replaceComponent",
|
|
3094
3248
|
from: "button",
|
|
3095
3249
|
to: "Button",
|
|
3096
|
-
deterministic:
|
|
3250
|
+
deterministic: false,
|
|
3097
3251
|
});
|
|
3098
3252
|
});
|
|
3099
3253
|
|
|
@@ -3139,13 +3293,13 @@ describe("components/prefer-library", () => {
|
|
|
3139
3293
|
kind: "replaceComponent",
|
|
3140
3294
|
from: "button",
|
|
3141
3295
|
to: "Button",
|
|
3142
|
-
deterministic:
|
|
3296
|
+
deterministic: false,
|
|
3143
3297
|
});
|
|
3144
3298
|
expect(findings[2].fix).toMatchObject({
|
|
3145
3299
|
kind: "replaceComponent",
|
|
3146
3300
|
from: "button",
|
|
3147
3301
|
to: "Button",
|
|
3148
|
-
deterministic:
|
|
3302
|
+
deterministic: false,
|
|
3149
3303
|
});
|
|
3150
3304
|
});
|
|
3151
3305
|
|
|
@@ -3430,6 +3584,7 @@ describe("styles/no-raw-typography (BUG 10)", () => {
|
|
|
3430
3584
|
);
|
|
3431
3585
|
const findings = ruleStylesNoRawTypography(ix);
|
|
3432
3586
|
expect(findings).toHaveLength(1);
|
|
3587
|
+
expect(findings[0].code).toBe("FUI2016");
|
|
3433
3588
|
expect(findings[0].fix).toMatchObject({
|
|
3434
3589
|
value: "var(--font-size-md)",
|
|
3435
3590
|
deterministic: true,
|
|
@@ -217,18 +217,21 @@ describe("§3 contract authored", () => {
|
|
|
217
217
|
expect(ruleIds.every(isContractTierRule)).toBe(true);
|
|
218
218
|
});
|
|
219
219
|
|
|
220
|
-
it("ranks canonical-component bypass first
|
|
220
|
+
it("ranks canonical-component bypass first without claiming an unsafe missing-import fix", () => {
|
|
221
221
|
const findings = runRules(withButtonText(contractIndex()));
|
|
222
222
|
const ranked = findings.map((f) => f.ruleId).sort(compareByVocabularyRank);
|
|
223
223
|
expect(ranked).toEqual(["components/prefer-library", "tokens/css-vars-must-be-defined"]);
|
|
224
224
|
|
|
225
225
|
const canonical = findings.find((f) => f.ruleId === "components/prefer-library");
|
|
226
|
-
//
|
|
226
|
+
// The direction is explicit, but this fixture has no Button binding. A
|
|
227
|
+
// generic source patcher cannot safely rename the element without also
|
|
228
|
+
// importing Button, so the replacement stays visible but is not advertised
|
|
229
|
+
// as deterministic.
|
|
227
230
|
expect(canonical?.fix).toMatchObject({
|
|
228
231
|
kind: "replaceComponent",
|
|
229
232
|
from: "button",
|
|
230
233
|
to: "Button",
|
|
231
|
-
deterministic:
|
|
234
|
+
deterministic: false,
|
|
232
235
|
});
|
|
233
236
|
expect(canonical?.attributes).toMatchObject({
|
|
234
237
|
rawValue: "button",
|
package/src/rules/tiers.ts
CHANGED
|
@@ -27,7 +27,7 @@ export type RuleTier = "contract" | "hygiene";
|
|
|
27
27
|
*/
|
|
28
28
|
export const RULE_TIER: Readonly<Record<string, RuleTier>> = {
|
|
29
29
|
// ---- Tier A — contract vocabulary -------------------------------------
|
|
30
|
-
"components/prefer-library": "contract", // canonical-component bypass (
|
|
30
|
+
"components/prefer-library": "contract", // canonical-component bypass (FUI1004)
|
|
31
31
|
"tokens/css-vars-must-be-defined": "contract", // token drift (FUI2015)
|
|
32
32
|
|
|
33
33
|
// ---- Tier B — generic hygiene -----------------------------------------
|
package/src/schemas/index.ts
CHANGED
|
@@ -228,11 +228,22 @@ export const agentFindingSchema = z.object({
|
|
|
228
228
|
plan: agentPlanSchema,
|
|
229
229
|
});
|
|
230
230
|
|
|
231
|
+
export const AGENT_FORMAT_SCHEMA_VERSION = "typestyle.agent.v2" as const;
|
|
232
|
+
|
|
233
|
+
export const agentIntegritySchema = z.object({
|
|
234
|
+
rulesLoaded: z.number().int().nonnegative(),
|
|
235
|
+
status: z.enum(["healthy", "degraded", "inert"]),
|
|
236
|
+
healthy: z.boolean(),
|
|
237
|
+
inert: z.boolean(),
|
|
238
|
+
reasons: z.array(z.string()),
|
|
239
|
+
});
|
|
240
|
+
|
|
231
241
|
export const agentFormatSchema = z.object({
|
|
232
|
-
schemaVersion: z.literal(
|
|
242
|
+
schemaVersion: z.literal(AGENT_FORMAT_SCHEMA_VERSION),
|
|
233
243
|
passed: z.boolean(),
|
|
234
244
|
score: z.number(),
|
|
235
245
|
summary: z.string(),
|
|
246
|
+
integrity: agentIntegritySchema,
|
|
236
247
|
fix_first: z.array(agentFindingSchema),
|
|
237
248
|
remaining: z.array(agentFindingSchema),
|
|
238
249
|
suppression_template: z.object({
|
|
@@ -250,6 +261,19 @@ export const agentFormatSchema = z.object({
|
|
|
250
261
|
});
|
|
251
262
|
export type AgentFormat = z.infer<typeof agentFormatSchema>;
|
|
252
263
|
|
|
264
|
+
export const agentErrorEnvelopeSchema = z.object({
|
|
265
|
+
schemaVersion: z.literal(AGENT_FORMAT_SCHEMA_VERSION),
|
|
266
|
+
passed: z.literal(false),
|
|
267
|
+
error: z.object({
|
|
268
|
+
code: z.string().min(1),
|
|
269
|
+
message: z.string().min(1),
|
|
270
|
+
}),
|
|
271
|
+
});
|
|
272
|
+
export type AgentErrorEnvelope = z.infer<typeof agentErrorEnvelopeSchema>;
|
|
273
|
+
|
|
274
|
+
export const agentOutputSchema = z.union([agentFormatSchema, agentErrorEnvelopeSchema]);
|
|
275
|
+
export type AgentOutput = z.infer<typeof agentOutputSchema>;
|
|
276
|
+
|
|
253
277
|
export function normalizeSeverity(severity: Severity | LegacySeverityLevel): Severity {
|
|
254
278
|
return severitySchema.safeParse(severity).success
|
|
255
279
|
? (severity as Severity)
|