import type { Plugin } from 'vite'; export const PYGMALION_QA_CAPTURE_ENDPOINT: '/__pygmalion-qa/capture'; export const PYGMALION_QA_ARTIFACT_PREFIX: '/__pygmalion-qa/artifacts/'; export const PYGMALION_QA_REVIEW_PREFIX: '/__pygmalion-qa/reviews/'; export const DEFAULT_QA_CAPTURE_MAX_FRAMES: number; export interface QaCaptureHashes { domStructureHash: string; screenshotHash: string; screenshotUrl?: string; } export interface QaCaptureFrame extends QaCaptureHashes { id: string; /** Runner-only PNG bytes; never serialized in a capture response. */ screenshotBytes?: Uint8Array; } export interface QaCaptureFailure { id: string; error: string; } export interface QaCaptureRunnerInput { root: string; scriptPath: string; basePath: string; frameIds: string[]; variant?: string; frames?: QaCaptureInputFrame[]; timeoutMs: number; maxPngBytes?: number; maxTotalPngBytes?: number; signal?: AbortSignal; } export interface QaCaptureInputFrame { id: string; fingerprint?: string; recipe?: Record; } export interface QaCaptureRunnerResult { screens: QaCaptureFrame[]; failures: QaCaptureFailure[]; } export type QaCaptureRunner = ( input: QaCaptureRunnerInput, ) => Promise; export interface QaCaptureBaselineRequest { phase: 'baseline'; basePath: string; frameIds: string[]; variant?: string; frames?: QaCaptureInputFrame[]; } export interface QaCaptureChangedRequest { phase: 'changed'; basePath: string; frameIds: string[]; baselineId: string; variant?: string; frames?: QaCaptureInputFrame[]; } export type QaCaptureRequest = QaCaptureBaselineRequest | QaCaptureChangedRequest; export interface QaCaptureBaselineResponse { ok: true; baselineId: string; frames: QaCaptureFrame[]; } export interface QaCaptureChangedFrame { id: string; before: QaCaptureHashes; after: QaCaptureHashes | null; domChanged: boolean | null; screenshotChanged: boolean | null; pixelDiff?: QaPixelDiff; } export interface QaPixelDimensions { width: number; height: number; } export interface QaPixelDiff { before: QaPixelDimensions; after: QaPixelDimensions; dimensionsMatch: boolean; changedPixels: number; totalPixels: number; diffRatio: number; threshold: number; heatmapUrl: string | null; } export interface QaCaptureChangedResponse { ok: true; baselineId: string; beforeBasePath: string; afterBasePath: string; frames: QaCaptureChangedFrame[]; complete: boolean; failures: QaCaptureFailure[]; /** Present only when the complete review was saved to local persistent storage. */ reviewId?: string; reviewUrl?: string; expiresAt?: number; } export interface QaCaptureIdentity { sessionId: string; sourceRevision: string; /** Exact session snapshot or draft revision captured by the runner. */ revision: string; } export interface QaReviewRecord { version: 1; namespace: string; kind: 'review'; id: string; identity: QaCaptureIdentity; beforeIdentity: QaCaptureIdentity; createdAt: number; expiresAt: number; request: QaCaptureChangedRequest; result: QaCaptureChangedResponse; images: Array<{ id: string; sha256: string; byteLength: number }>; } export interface QaCaptureErrorResponse { ok: false; error: string; message: string; failures?: QaCaptureFailure[]; } export type QaCaptureResponse = | QaCaptureBaselineResponse | QaCaptureChangedResponse | QaCaptureErrorResponse; export class QaCaptureError extends Error { statusCode: number; code: string; failures?: QaCaptureFailure[]; constructor( statusCode: number, code: string, message: string, failures?: QaCaptureFailure[], ); } export interface QaCaptureServiceOptions { root?: string; scriptPath?: string; captureRunner?: QaCaptureRunner; timeoutMs?: number; maxFrameIds?: number; maxBaselines?: number; baselineTtlMs?: number; maxBaselineBytes?: number; maxArtifacts?: number; maxArtifactBytes?: number; artifactTtlMs?: number; maxPngBytes?: number; maxPngPixels?: number; pixelThreshold?: number; now?: () => number; createBaselineId?: () => string; createArtifactId?: () => string; createReviewId?: () => string; /** Opt-in local persistence; namespaces must identify separate projects. */ storage?: { root: string; namespace: string }; /** Required with storage. Resolve and validate the exact host session behind a capture URL. */ resolveIdentity?: (request: QaCaptureRequest, context: { signal?: AbortSignal }) => QaCaptureIdentity | Promise; } export interface QaCaptureService { capture( request: QaCaptureRequest | unknown, context?: { allowedHost?: string | string[]; signal?: AbortSignal }, ): Promise; getArtifact( id: string, ): { bytes: Uint8Array; contentType: 'image/png' } | null; /** Restore unexpired evidence before synchronous artifact access; capture awaits this automatically. */ ready(): Promise; readReview(id: string, identity: QaCaptureIdentity): Promise; readonly baselineCount: number; readonly artifactCount: number; readonly busy: boolean; } export interface QaPngComparison { before: QaPixelDimensions; after: QaPixelDimensions; dimensionsMatch: boolean; changedPixels: number; totalPixels: number; diffRatio: number; threshold: number; heatmapBytes: Uint8Array; } export function normalizeLocalCaptureBasePath( value: unknown, allowedHost?: string | string[], ): string; export function validateQaCaptureRequest( value: unknown, options?: { allowedHost?: string | string[]; maxFrameIds?: number; }, ): QaCaptureRequest; export function runCaptureScript( input: QaCaptureRunnerInput, ): Promise; export function comparePngScreenshots( beforeBytes: Uint8Array, afterBytes: Uint8Array, options?: { maxPngBytes?: number; maxPngPixels?: number; threshold?: number; }, ): QaPngComparison; export function createQaCaptureService( options?: QaCaptureServiceOptions, ): QaCaptureService; export function pygmalionQaCapturePlugin( options?: QaCaptureServiceOptions, ): Plugin; export type QaCheckName = 'recipe' | 'viewport' | 'theme' | 'accessibility' | 'dom' | 'pixel'; export interface QaCheck { status: 'passed' | 'failed' | 'blocked'; [key: string]: unknown } export interface QaVariant { id: string; componentId: string; width: number; height: number; theme: 'light' | 'dark' | 'no-preference'; /** Required for a surface built with a trusted screen manifest. */ screenId?: string; maxOverflowPx?: number; [key: string]: unknown; } export interface QaComponentPolicy { required?: readonly QaCheckName[]; maxPixelRatio?: number; allowDomChanges?: boolean; maxAccessibilityViolations?: number; approval?: 'none' | 'required'; } export interface QaPolicy { defaults?: QaComponentPolicy; components?: Record } export interface QaPolicyResult { id: string; checks?: Partial>; domChanged?: boolean; pixel?: { dimensionsMatch: boolean; diffRatio: number; [key: string]: unknown }; accessibility?: { violations: unknown[]; [key: string]: unknown }; } export interface QaApproval { variantId: string; evidenceHash: string; policyHash: string; reviewer: string } export interface QaPolicyReport { [key: string]: unknown; version: 1; identity: TIdentity; policyHash: string; status: 'passed' | 'failed' | 'blocked'; results: Array<{ id: string; componentId: string; status: 'passed' | 'failed' | 'blocked'; required: QaCheckName[]; checks: Record; evidenceHash: string; approval: 'satisfied' | 'required' }>; } export const QA_CHECKS: readonly QaCheckName[]; export declare function evaluateQaPolicy(input: { identity: TIdentity; variants: readonly QaVariant[]; results: readonly QaPolicyResult[]; policy?: QaPolicy; approvals?: readonly QaApproval[]; }): QaPolicyReport; export declare function auditBasicAccessibility(page: { evaluate(callback: () => unknown): Promise }): Promise<{ scope: string; violations: unknown[] }>; export declare function captureDraftProposalQa(input: { browser: import('./storyboard.js').StoryboardCaptureOptions['browser']; surface: Pick; identity: TIdentity; variants: readonly QaVariant[]; policy?: QaPolicy; approvals?: readonly QaApproval[]; baselines?: readonly { id: string; domStructureHash?: string; screenshot?: Uint8Array }[]; auditAccessibility?(page: unknown, context: { variant: QaVariant; signal?: AbortSignal }): Promise<{ violations: unknown[]; [key: string]: unknown }>; onCapture?(capture: { id: string; domStructureHash?: string; screenshotHash?: string; screenshot?: Uint8Array; heatmap?: Uint8Array }, context: { identity: TIdentity; variant: QaVariant; signal?: AbortSignal }): void | Promise; hooks?: import('./storyboard.js').StoryboardCaptureOptions['hooks']; timeoutMs?: number; signal?: AbortSignal; }): Promise & { screenManifestHash?: string; captures: Array<{ id: string; domStructureHash?: string; screenshotHash?: string; heatmapHash?: string }> }>; export declare function resolveAffectedQaScreens(input: { graph: import('./vite.js').PygmalionSourceGraph; changedFiles: readonly string[]; screens: readonly { id: string; sourcePaths: readonly string[] }[]; globalFiles?: readonly string[]; globalDirectories?: readonly string[]; }): { status: 'ready' | 'blocked'; changedFiles: string[]; unmatchedFiles: string[]; global: boolean; affectedFiles: string[]; screenIds: string[] }; export declare const PROPOSAL_QA_EVIDENCE_PREFIX: string; export interface ProposalQaEvidence { id: string; expiresAt: number; variants: { id: string; screenshotUrl?: string; heatmapUrl?: string }[]; } export interface ProposalQaEvidenceStore { capture(identity: { sourceRevision: string; [key: string]: unknown }, signal: AbortSignal | undefined, run: (onCapture: NonNullable[0]['onCapture']>) => Promise): Promise; readReview(id: string, expectedIdentity: { sourceRevision: string; [key: string]: unknown }): Promise | null>; handleRequest(request: import('node:http').IncomingMessage, response: import('node:http').ServerResponse): Promise; } export declare function createProposalQaEvidenceStore(options: { root: string; namespace: string; ttlMs?: number; maxReviews?: number; maxBytes?: number; now?: () => number }): ProposalQaEvidenceStore; export declare function proposalQaEvidencePlugin(store: ProposalQaEvidenceStore): import('vite').Plugin;