45 lines
3.8 KiB
TypeScript
45 lines
3.8 KiB
TypeScript
export type Severity = "hint" | "info" | "warning";
|
|
|
|
export interface Finding {
|
|
readonly ruleId: string;
|
|
readonly severity: Severity;
|
|
readonly message: string;
|
|
readonly note?: string;
|
|
readonly path: string;
|
|
readonly range: Readonly<{ start: Readonly<{ line: number; column: number }>; end: Readonly<{ line: number; column: number }> }>;
|
|
readonly matchedTextHash: string;
|
|
}
|
|
|
|
export interface AnalyzerFailure { readonly kind: "missing" | "incompatible" | "timeout" | "nonzero" | "malformed" | "killed" | "internal"; readonly message: string }
|
|
export interface AnalysisSnapshot { readonly analyzerKey: string; readonly workspaceRevision: number; readonly paths: readonly string[]; readonly findings: readonly Finding[]; readonly durationMs: number; readonly failure?: AnalyzerFailure }
|
|
export interface ProjectContext { readonly root: string; readonly packageName?: string; readonly identity: string; readonly git: boolean }
|
|
export interface PolicyContext { readonly project: ProjectContext; readonly changedPaths: readonly string[]; readonly workspaceRevision: number }
|
|
export interface Diagnostic { readonly policyId: string; readonly ruleId: string; readonly path: string; readonly line: number; readonly column: number; readonly message: string; readonly guidance: string; readonly exception?: string; readonly matchedTextHash: string }
|
|
export interface DiagnosticPolicy { readonly kind: "diagnostic"; readonly id: string; readonly analyzerKeys: readonly string[]; appliesTo(project: ProjectContext, paths: readonly string[]): boolean; evaluate(analysis: ReadonlyMap<string, AnalysisSnapshot>, context: PolicyContext): readonly Diagnostic[] }
|
|
export interface ProjectMatcher { matches(project: ProjectContext): boolean }
|
|
export interface VerificationRequirement { readonly id: string; readonly command: string; readonly cwd: "project-root"; affects(path: string): boolean }
|
|
export interface VerificationPolicy { readonly kind: "verification"; readonly id: string; readonly project: ProjectMatcher; readonly requirements: readonly VerificationRequirement[] }
|
|
export type PersonalPolicy = DiagnosticPolicy | VerificationPolicy;
|
|
|
|
const isFn = (value: unknown): value is (...args: never[]) => unknown => typeof value === "function";
|
|
export function validatePolicy(value: unknown): { ok: true; policy: PersonalPolicy } | { ok: false; error: string } {
|
|
if (!value || typeof value !== "object") return { ok: false, error: "default export must be an object" };
|
|
const candidate = value as Record<string, unknown>;
|
|
if (typeof candidate.id !== "string" || !candidate.id.trim()) return { ok: false, error: "policy id must be non-empty" };
|
|
if (candidate.kind === "diagnostic") {
|
|
if (!Array.isArray(candidate.analyzerKeys) || candidate.analyzerKeys.some((key) => typeof key !== "string" || !key)) return { ok: false, error: "diagnostic analyzerKeys must be non-empty strings" };
|
|
if (!isFn(candidate.appliesTo) || !isFn(candidate.evaluate)) return { ok: false, error: "diagnostic policy requires appliesTo and evaluate functions" };
|
|
return { ok: true, policy: value as DiagnosticPolicy };
|
|
}
|
|
if (candidate.kind === "verification") {
|
|
const project = candidate.project as Record<string, unknown> | undefined;
|
|
if (!project || !isFn(project.matches) || !Array.isArray(candidate.requirements)) return { ok: false, error: "verification policy requires project.matches and requirements" };
|
|
for (const requirement of candidate.requirements as unknown[]) {
|
|
const req = requirement as Record<string, unknown> | undefined;
|
|
if (!req || typeof req.id !== "string" || !req.id || typeof req.command !== "string" || !req.command.trim() || req.cwd !== "project-root" || !isFn(req.affects)) return { ok: false, error: "invalid verification requirement" };
|
|
}
|
|
return { ok: true, policy: value as VerificationPolicy };
|
|
}
|
|
return { ok: false, error: "policy kind must be diagnostic or verification" };
|
|
}
|