import { z } from "zod"; import { legacySeverityLevelSchema, severityFromLevel, severityLevel, severityLevelSchema, severitySchema, } from "../severity.js"; import type { LegacySeverityLevel, Severity } from "../severity.js"; export { legacySeverityLevelSchema, severityLevelSchema, severitySchema }; export type { LegacySeverityLevel, Severity, SeverityLevel } from "../severity.js"; export const factLocationSchema = z.object({ file: z.string(), line: z.number().int().nonnegative(), column: z.number().int().nonnegative(), endLine: z.number().int().nonnegative().optional(), endColumn: z.number().int().nonnegative().optional(), }); export type FactLocation = z.infer; export const findingReplacePropValueFixSchema = z.object({ kind: z.literal("replacePropValue"), title: z.string(), prop: z.string(), value: z.unknown(), deterministic: z.boolean().optional(), }); export const findingReplaceStyleValueFixSchema = z.object({ kind: z.literal("replaceStyleValue"), title: z.string(), property: z.string(), value: z.string(), deterministic: z.boolean().optional(), }); export const findingReplaceImportFixSchema = z.object({ kind: z.literal("replaceImport"), title: z.string(), from: z.string(), to: z.string(), deterministic: z.boolean().optional(), }); export const findingReplaceComponentFixSchema = z.object({ kind: z.literal("replaceComponent"), title: z.string(), from: z.string(), to: z.string(), deterministic: z.boolean().optional(), }); export const findingReplaceClassTokenFixSchema = z.object({ kind: z.literal("replaceClassToken"), title: z.string(), from: z.string(), to: z.string().nullable(), utility: z.string(), deterministic: z.boolean().optional(), }); export const findingFixSchema = z.discriminatedUnion("kind", [ findingReplacePropValueFixSchema, findingReplaceStyleValueFixSchema, findingReplaceImportFixSchema, findingReplaceComponentFixSchema, findingReplaceClassTokenFixSchema, ]); export type FindingFix = z.infer; export type FindingReplacePropValueFix = z.infer; export type FindingReplaceStyleValueFix = z.infer; export type FindingReplaceImportFix = z.infer; export type FindingReplaceComponentFix = z.infer; export type FindingReplaceClassTokenFix = z.infer; export interface FactEvidence { factId: string; fact: { kind: string; location?: FactLocation; }; } export const factEvidenceSchema: z.ZodType = z.object({ factId: z.string(), fact: z .object({ kind: z.string(), location: factLocationSchema.optional(), }) .passthrough(), }); export const findingSchema = z.object({ ruleId: z.string(), ruleVersion: z.string(), severity: severitySchema, level: severityLevelSchema, code: z .string() .regex(/^FUI\d{4}$/) .optional(), helpUrl: z.string().url().optional(), message: z.string(), fingerprint: z.string(), location: factLocationSchema, evidence: z.array(factEvidenceSchema).min(1), fix: findingFixSchema.optional(), attributes: z.record(z.unknown()).optional(), /** * Product area this finding's file resolved to (topology layer). Optional — * absent when the repo has no `topology` config. See `topology/resolve-area`. */ area: z .object({ areaId: z.string(), areaName: z.string(), criticality: z.enum(["low", "medium", "high", "revenue", "regulated"]), owners: z.array(z.string()), matchedGlob: z.string(), }) .optional(), }); export type Finding = z.infer; export const violationSchema = z.object({ nodeId: z.string(), nodeType: z.string(), rule: z.string(), severity: severitySchema, level: severityLevelSchema.optional(), code: z .string() .regex(/^FUI\d{4}$/) .optional(), helpUrl: z.string().url().optional(), message: z.string(), path: z.string().optional(), suggestion: z.string().optional(), prop: z.string().optional(), filePath: z.string().optional(), line: z.number().int().nonnegative().optional(), column: z.number().int().nonnegative().optional(), rawValue: z.string().optional(), extra: z.record(z.unknown()).optional(), }); export type Violation = z.infer; export const fixSchema = z.object({ nodeId: z.string(), prop: z.string().optional(), action: z.enum(["remove", "replace", "add"]), value: z.unknown().optional(), description: z.string(), }); export type Fix = z.infer; export const validatorResultSchema = z.object({ validator: z.string(), severity: severitySchema, passed: z.boolean(), violations: z.array(violationSchema), findings: z.array(findingSchema).optional(), suggestions: z.array(fixSchema).optional(), }); export type ValidatorResult = z.infer; export const governanceVerdictMetadataSchema = z.object({ runner: z.string(), policyVersion: z.string().optional(), duration: z.number().nonnegative(), nodeCount: z.number().int().nonnegative(), componentTypes: z.array(z.string()), }); export type GovernanceVerdictMetadata = z.infer; export const governanceVerdictSchema = z.object({ verdict: z.enum(["pass", "warn", "fail"]).optional(), passed: z.boolean(), score: z.number(), results: z.array(validatorResultSchema), metadata: governanceVerdictMetadataSchema, }); export type GovernanceVerdict = z.infer; export const suppressionDirectiveSchema = z.object({ ruleId: z.string().optional(), reason: z.string().min(1), expiresAt: z.string().datetime().optional(), expiresOn: z .string() .regex(/^\d{4}-\d{2}-\d{2}$/) .optional(), scope: z.enum(["line", "block", "file"]).default("line"), code: z .string() .regex(/^FUI\d{4}$/) .optional(), }); export type SuppressionDirective = z.infer; export const agentPlanSchema = z.object({ tool: z.enum([ "design_system/list_primitives", "design_system/conform", "design_system/prove_compliant", ]), args: z.record(z.unknown()), confidence: z.number().min(0).max(1), }); export const agentFindingSchema = z.object({ code: z .string() .regex(/^FUI\d{4}$/) .optional(), ruleId: z.string(), filePath: z.string(), line: z.number().int().nonnegative(), col: z.number().int().nonnegative(), severity: severitySchema, level: severityLevelSchema, message: z.string(), evidence: z.array(factEvidenceSchema), plan: agentPlanSchema, }); export const agentFormatSchema = z.object({ schemaVersion: z.literal("typestyle.agent.v1"), passed: z.boolean(), score: z.number(), summary: z.string(), fix_first: z.array(agentFindingSchema), remaining: z.array(agentFindingSchema), suppression_template: z.object({ directive: z.string(), requiredFields: z.array(z.string()), example: z.string(), }), budget: z.object({ iteration: z.number().int().nonnegative(), maxIterations: z.number().int().positive(), scoreDelta: z.number(), }), findings: z.array(findingSchema).optional(), suppressions: z.array(suppressionDirectiveSchema).optional(), }); export type AgentFormat = z.infer; export function normalizeSeverity(severity: Severity | LegacySeverityLevel): Severity { return severitySchema.safeParse(severity).success ? (severity as Severity) : severityFromLevel(legacySeverityLevelSchema.parse(severity)); } export function normalizeFinding( input: Omit & { level?: Finding["level"] } ): Finding { return { ...input, level: input.level ?? severityLevel(input.severity), }; } export function normalizeViolation( input: Omit & { level?: Violation["level"] } ): Violation { return { ...input, level: input.level ?? severityLevel(input.severity), }; }