skeptic-cli 0.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.
@@ -0,0 +1,1252 @@
1
+ import { Command } from 'commander';
2
+ import { Page, Locator } from 'playwright';
3
+ import * as _playwright_test from '@playwright/test';
4
+ import { z } from 'zod';
5
+
6
+ type Engine = "chromium" | "firefox" | "webkit";
7
+
8
+ interface ConnectDaemonOptions {
9
+ engine: Engine;
10
+ headed: boolean;
11
+ cliVersion: string;
12
+ /** Override the auto-spawn behavior (tests use this to assert no-spawn paths). */
13
+ autoSpawn?: boolean;
14
+ /** Override max wait for socket-ready, ms. */
15
+ spawnTimeoutMs?: number;
16
+ /** Override the daemon idle timeout in seconds. Forwarded to the spawned daemon. */
17
+ idleTimeoutSeconds?: number;
18
+ }
19
+
20
+ /**
21
+ * Single source of truth for whether a given argv invokes a Commander command
22
+ * that needs a browser. Used by the auto-spawn discipline (plan §B10 invariant
23
+ * 8) — only `run` / `tui` (without `--list`) and `inspect` should auto-launch
24
+ * the daemon. Every other command (init/audit/comment/cookies/browsers
25
+ * install/run --list/mcp/acp/add/generate/help) returns false.
26
+ *
27
+ * Also returns false when `--no-daemon` is present — that flag is the
28
+ * opt-out. The caller is responsible for re-checking this predicate before
29
+ * any pre-warm so the dead-code regression caught in the B10 audit doesn't
30
+ * recur.
31
+ *
32
+ * Argv shape is whatever `process.argv` would look like — typically
33
+ * `["node", "skeptic", "<cmd>", ...]` or just `["<cmd>", ...]`.
34
+ */
35
+ declare const commandUsesBrowser: (argv: readonly string[]) => boolean;
36
+ interface PrewarmOptions {
37
+ /** Engine the upcoming workers / inspect will request. */
38
+ engine: ConnectDaemonOptions["engine"];
39
+ /** Headed flag the upcoming workers / inspect will request. */
40
+ headed: boolean;
41
+ /** CLI version for the handshake. */
42
+ cliVersion: string;
43
+ /** When true, the caller already ran `--no-daemon` — skip pre-warm. */
44
+ noDaemon: boolean;
45
+ /** Forwarded to the spawned daemon. */
46
+ idleTimeoutSeconds?: number;
47
+ }
48
+ /**
49
+ * Main-process pre-warm. When the predicate matches AND `--no-daemon` is
50
+ * absent, spawn the daemon now (before any workers fork) so subsequent
51
+ * worker-side `connectDaemon` calls hit the fast path (`isSocketConnectable`
52
+ * returns true and no spawn happens inside the worker).
53
+ *
54
+ * Returns true when the daemon is alive (or was made alive), false when we
55
+ * skipped the pre-warm. On spawn failure the function logs a warning and
56
+ * returns false — workers will then fall back to fresh launches per the
57
+ * `--no-daemon` semantics, so the run still proceeds.
58
+ */
59
+ declare const prewarmDaemonIfNeeded: (argv: readonly string[], opts: PrewarmOptions) => Promise<boolean>;
60
+
61
+ type AIProvider = "gemini" | "openai" | "anthropic";
62
+ /**
63
+ * Result of an AI call. `retryCount` is the number of transient retries that
64
+ * `withRetry` performed before success; 0 means the first attempt worked.
65
+ * Surfacing this lets step handlers add a `StepResult.warnings` entry like
66
+ * "AI retried 2× before success" so users can see when CI was rescued.
67
+ */
68
+ interface AIResult {
69
+ text: string;
70
+ retryCount: number;
71
+ }
72
+ interface AIClient {
73
+ readonly provider: AIProvider;
74
+ analyzeImage(imageBuffer: Buffer, prompt: string, temperature?: number): Promise<AIResult>;
75
+ generateText(prompt: string, system?: string, temperature?: number): Promise<AIResult>;
76
+ }
77
+
78
+ /**
79
+ * ARIA snapshot-ref entry — one record per minted ref.
80
+ *
81
+ * Refs are sequential identifiers (e1, e2, …) produced by Playwright's
82
+ * `Locator.ariaSnapshot({mode:"ai"})` plus a parallel cursor-interactive pass
83
+ * (ported from agent-browser, Apache 2.0).
84
+ *
85
+ * Resolution is `kind`-discriminated:
86
+ * - `aria` → `getByRole(role, { name, exact: true }).nth(nth)` against `scopeSelector`.
87
+ * - `cursor-interactive` → `resolveElement(page, selectorHint)` (the existing
88
+ * skeptic-grammar resolver: `testid=…` / `role=…` / `css=…` / raw text).
89
+ *
90
+ * `selectorHint` is the **stable, cross-process artifact** an agent copies into a
91
+ * `*.spec.ts` file. `@eN` refs are volatile — only valid inside one test run after a
92
+ * `snapshot(page)` call.
93
+ *
94
+ * `matchCountAtSnapshot` records the size of the (role, name) candidate group at
95
+ * capture time. The resolver re-counts the live group; if it changed AND the
96
+ * recorded `nth === 0`, we emit a warning that an insertion may have silently
97
+ * retargeted the ref.
98
+ */
99
+ type AriaRefKind = "aria" | "cursor-interactive";
100
+ interface AriaRefEntry {
101
+ ref: string;
102
+ kind: AriaRefKind;
103
+ role: string;
104
+ name: string;
105
+ nth: number;
106
+ scopeSelector: string;
107
+ /** Optional stable selector hint (skeptic grammar). Required for cursor-interactive. */
108
+ selectorHint?: string;
109
+ /** Optional href for link refs — extracted post-snapshot, first 50 link refs. */
110
+ href?: string;
111
+ /** Number of (role, name) candidates at snapshot time — used by the resolver
112
+ * to surface silent insertion-retargeting via a `[aria-ref] eN silently retargeted` log. */
113
+ matchCountAtSnapshot: number;
114
+ }
115
+
116
+ /**
117
+ * Configuration for the visual-settle helper used before screenshots and (optionally) before
118
+ * video finalization. The helper is a no-op fast-path when `enabled` is false — keeps the
119
+ * non-observability default zero-cost.
120
+ */
121
+ interface VisualSettleConfig {
122
+ enabled: boolean;
123
+ /** Wait for `networkidle` with this ceiling (ms). 0 disables the network wait. */
124
+ networkIdleMs: number;
125
+ /** Number of double-RAF cycles to await. 0 disables. The single most effective settle
126
+ * for canvas / WebGL preloaders in practice (most clear by the second RAF). */
127
+ animationFrames: number;
128
+ /** Optional pixel-stability poll budget (ms). 0 disables; the cost is one extra
129
+ * centred screenshot per poll iteration plus a CRC32 hash. */
130
+ pixelStableMs: number;
131
+ /** Overall hard ceiling (ms). Wraps the whole helper in a Node-side Promise.race so a
132
+ * misbehaving page can't blow the step's hardTimeout. */
133
+ hardCeilingMs: number;
134
+ }
135
+
136
+ interface StepDiagnostic {
137
+ kind: "blank-screenshot" | "settle-timeout" | "path-rejected" | "auto-a11y-skipped" | string;
138
+ message: string;
139
+ meta?: Record<string, unknown>;
140
+ }
141
+ /**
142
+ * Per-test runtime config for the screenshot, settle, and blank-frame pipeline.
143
+ * The engine resolves it once and freezes it onto `ExecutionContext.artifactConfig`.
144
+ */
145
+ interface ArtifactRuntimeConfig {
146
+ fullPageScreenshots: boolean;
147
+ visualSettle: VisualSettleConfig;
148
+ blankFrameDetection: "off" | "warn" | "fail";
149
+ writeSidecars: boolean;
150
+ }
151
+
152
+ declare class ExecutionContext {
153
+ readonly page: Page;
154
+ readonly screenshots: string[];
155
+ private baseUrl;
156
+ lastElement: Locator | null;
157
+ readonly testDir: string;
158
+ readonly sourceDir: string;
159
+ readonly aiClient?: AIClient;
160
+ readonly aiProvider?: AIProvider;
161
+ readonly defaultTimeout: number;
162
+ activeTimeout: number;
163
+ readonly collectors: Map<CollectorName, Collector>;
164
+ /** Per-test artifact runtime config — set by the engine when constructing the context.
165
+ * Read by the screenshot handler and by the engine's pre-video-finalize settle hook. */
166
+ readonly artifactConfig: ArtifactRuntimeConfig;
167
+ /**
168
+ * ARIA snapshot ref registry — populated by the `ariaSnapshot` step, consumed by
169
+ * `resolveSelectorArg`'s `@`-prefix branch. Each `ariaSnapshot` step clears and refills the map;
170
+ * refs do not persist across snapshots (matches Expect's ephemeral-ref design).
171
+ */
172
+ readonly ariaRefs: Map<string, AriaRefEntry>;
173
+ /**
174
+ * Last captured snapshot YAML (verbatim Playwright output). In-memory only — never logged or
175
+ * serialized into StepResult, since snapshots may include user-typed PII. Used by error messages
176
+ * to indicate whether a snapshot has run yet.
177
+ */
178
+ ariaSnapshotYaml: string | null;
179
+ /**
180
+ * Set when a hardTimeout fires. Fixture actions check this before each await
181
+ * and short-circuit unless `inTeardown` is set.
182
+ */
183
+ abortReason: string | null;
184
+ /**
185
+ * Set while running teardown hooks so cleanup can still execute after an
186
+ * aborted test body.
187
+ */
188
+ inTeardown: boolean;
189
+ constructor(page: Page, baseUrl: string, testDir?: string, sourceDir?: string, aiClient?: AIClient, aiProvider?: AIProvider, defaultTimeout?: number, collectors?: Collector[], artifactConfig?: ArtifactRuntimeConfig);
190
+ resolveUrl(path: string): string;
191
+ addScreenshot(path: string): void;
192
+ }
193
+
194
+ type CollectorName = "performance" | "network" | "accessibility" | "console";
195
+ interface Collector {
196
+ readonly name: CollectorName;
197
+ attach(page: Page, ctx: ExecutionContext): Promise<void>;
198
+ snapshot(): Promise<unknown>;
199
+ detach(): Promise<void>;
200
+ }
201
+ interface LongAnimationFrame {
202
+ startTime: number;
203
+ duration: number;
204
+ blockingDuration: number;
205
+ scripts: Array<{
206
+ invoker: string;
207
+ sourceURL: string;
208
+ sourceFunctionName: string;
209
+ duration: number;
210
+ /**
211
+ * Per-script forced style/layout time (ms). Per Chromium's LoAF spec this lives on
212
+ * `PerformanceScriptTiming` entries (script-level), not on the frame itself.
213
+ * Frame-level "forced layout" rendering is computed as
214
+ * `Math.max(...scripts.map(s => s.forcedStyleAndLayoutDuration))`.
215
+ */
216
+ forcedStyleAndLayoutDuration: number;
217
+ }>;
218
+ }
219
+ interface NavigationTiming {
220
+ /** ms from navigation start to first byte. */
221
+ ttfb: number | null;
222
+ /** ms from navigation start to DOMContentLoadedEventEnd. */
223
+ domContentLoaded: number | null;
224
+ /** ms from navigation start to loadEventEnd. */
225
+ loadComplete: number | null;
226
+ /**
227
+ * `PerformanceServerTiming` entries from the navigation response, when the origin
228
+ * sent a `Server-Timing` header. Most pages don't emit this; rendered only when present.
229
+ */
230
+ serverTiming?: Array<{
231
+ name: string;
232
+ duration?: number;
233
+ description?: string;
234
+ }>;
235
+ }
236
+ interface ResourceTiming {
237
+ name: string;
238
+ initiatorType: string;
239
+ /** ms — `responseEnd - startTime`. */
240
+ duration: number;
241
+ /** Bytes transferred over the wire (compressed). 0 when the entry was served from cache. */
242
+ transferSize: number;
243
+ encodedBodySize: number;
244
+ decodedBodySize: number;
245
+ }
246
+ interface PerformanceSnapshot {
247
+ fcp: number | null;
248
+ lcp: number | null;
249
+ cls: number | null;
250
+ inp: number | null;
251
+ ttfb: number | null;
252
+ longAnimationFrames: LongAnimationFrame[];
253
+ /** Best-effort navigation-timing read at snapshot. */
254
+ navigationTiming?: NavigationTiming;
255
+ /** PerformanceResourceTiming entries, captured once at snapshot. Optional. */
256
+ resources?: ResourceTiming[];
257
+ }
258
+ interface ConsoleMessage {
259
+ /** Playwright console message type — log/warning/error/info/debug/trace etc. */
260
+ type: string;
261
+ /** Post-redacted text, truncated to 4 KB. */
262
+ text: string;
263
+ location?: {
264
+ url?: string;
265
+ lineNumber?: number;
266
+ columnNumber?: number;
267
+ };
268
+ timestamp: number;
269
+ }
270
+ interface ConsoleSnapshot {
271
+ messages: ConsoleMessage[];
272
+ summary: {
273
+ total: number;
274
+ errorCount: number;
275
+ warningCount: number;
276
+ infoCount: number;
277
+ /** True when redaction was disabled via config (report should show a banner). */
278
+ redactionDisabled: boolean;
279
+ };
280
+ }
281
+ interface NetworkRequest {
282
+ url: string;
283
+ method: string;
284
+ status?: number;
285
+ resourceType: string;
286
+ duration?: number;
287
+ timestamp: number;
288
+ failure?: string;
289
+ frameUrl?: string;
290
+ }
291
+ interface NetworkSnapshot {
292
+ requests: NetworkRequest[];
293
+ issues: {
294
+ failedRequests: Array<{
295
+ url: string;
296
+ method: string;
297
+ status: number;
298
+ }>;
299
+ networkFailures: Array<{
300
+ url: string;
301
+ method: string;
302
+ reason: string;
303
+ }>;
304
+ duplicates: Array<{
305
+ url: string;
306
+ method: string;
307
+ count: number;
308
+ windowMs: number;
309
+ }>;
310
+ mixedContent: string[];
311
+ corsErrors: Array<{
312
+ url: string;
313
+ method: string;
314
+ reason: string;
315
+ }>;
316
+ };
317
+ summary?: {
318
+ requestCount: number;
319
+ failedRequestCount: number;
320
+ networkFailureCount: number;
321
+ duplicateGroupCount: number;
322
+ mixedContentCount: number;
323
+ corsErrorCount: number;
324
+ issueCount: number;
325
+ captureLimit: number;
326
+ truncated: boolean;
327
+ resourceTypes: Record<string, number>;
328
+ methods: Record<string, number>;
329
+ statusCodes: Record<string, number>;
330
+ };
331
+ }
332
+ interface AccessibilityViolation {
333
+ ruleId: string;
334
+ impact: "critical" | "serious" | "moderate" | "minor";
335
+ engine: "axe" | "equal-access";
336
+ help: string;
337
+ helpUrl?: string;
338
+ nodes: Array<{
339
+ target: string[];
340
+ html: string;
341
+ failureSummary?: string;
342
+ }>;
343
+ }
344
+ interface AccessibilitySnapshot {
345
+ violations: AccessibilityViolation[];
346
+ summary: {
347
+ violations: number;
348
+ passes: number;
349
+ incomplete: number;
350
+ dualEngine: boolean;
351
+ /**
352
+ * Engines the audit was configured to run. Always includes "axe"; includes
353
+ * "equal-access" when dualEngine is true and the optional peer is installed.
354
+ */
355
+ enginesRequested?: Array<"axe" | "equal-access">;
356
+ /**
357
+ * Engines whose audit threw or otherwise failed to produce results.
358
+ */
359
+ enginesErrored?: Array<{
360
+ engine: "axe" | "equal-access";
361
+ reason: string;
362
+ }>;
363
+ };
364
+ standard: string;
365
+ }
366
+
367
+ interface AnnotationBox {
368
+ x: number;
369
+ y: number;
370
+ width: number;
371
+ height: number;
372
+ }
373
+
374
+ interface ScreenshotOptions {
375
+ fullPage?: boolean;
376
+ /** When true, inject numbered badges over interactive refs before capture and
377
+ * attach an `annotation-map` diagnostic to the result. */
378
+ annotate?: boolean;
379
+ /** CSS selector scoping the annotated set. Defaults to `body`. */
380
+ annotateScope?: string;
381
+ }
382
+ interface AnnotationMapEntry {
383
+ label: number;
384
+ ref: string;
385
+ role: string;
386
+ boundingBox: AnnotationBox;
387
+ selectorHint?: string;
388
+ }
389
+ interface ScreenshotResult {
390
+ path: string;
391
+ diagnostics: StepDiagnostic[];
392
+ /** Populated only when `opts.annotate === true`. Contains one entry per labeled
393
+ * badge — refs whose bbox could not be resolved are skipped (off-screen / detached). */
394
+ annotations?: AnnotationMapEntry[];
395
+ }
396
+
397
+ interface SnapshotOptions {
398
+ /** Match `agent-browser snapshot -i` — only nodes with refs. */
399
+ interactive?: boolean;
400
+ /** Match `agent-browser snapshot -c` — interactive + minimal ancestors. */
401
+ compact?: boolean;
402
+ /** Scope to a CSS selector. Defaults to `body` for full-page snapshots. */
403
+ selector?: string;
404
+ /** Emit "N items hidden above/below" markers. Default true on the public API. */
405
+ viewportAware?: boolean;
406
+ /** Run the cursor-interactive heuristic. Default true on the public API. */
407
+ includeCursorInteractive?: boolean;
408
+ }
409
+ interface ByRoleOptions {
410
+ name?: string | RegExp;
411
+ hrefIncludes?: string;
412
+ /** When duplicates exist, pick this match (default 0). */
413
+ index?: number;
414
+ }
415
+ interface SnapshotTree {
416
+ /** Rendered YAML matching agent-browser's format (full / interactive / compact). */
417
+ yaml: string;
418
+ /** Raw Playwright YAML before rendering — kept for diagnostics. */
419
+ rawYaml: string;
420
+ refs: Map<string, AriaRefEntry>;
421
+ stats: SnapshotStats;
422
+ byRef(ref: string): Locator | Promise<Locator>;
423
+ byRole(role: string, opts?: ByRoleOptions): Locator;
424
+ byText(text: string | RegExp): Locator;
425
+ byTestId(id: string): Locator;
426
+ cursorInteractiveCount: number;
427
+ ariaRefCount: number;
428
+ }
429
+ interface SnapshotStats {
430
+ /** Rendered YAML line count. Empty output follows Expect parity: one empty line. */
431
+ lines: number;
432
+ /** Rendered YAML character count. */
433
+ characters: number;
434
+ /** Rough model-token estimate using the common 4 chars/token heuristic. */
435
+ estimatedTokens: number;
436
+ /** All refs captured into the registry, including low-signal refs hidden from compact output. */
437
+ totalRefs: number;
438
+ /** Unique refs visible in the rendered YAML. */
439
+ renderedRefs: number;
440
+ /** Captured action-oriented refs. Cursor-interactive entries count as interactive. */
441
+ interactiveRefs: number;
442
+ /** Action-oriented refs visible in the rendered YAML. */
443
+ renderedInteractiveRefs: number;
444
+ ariaRefs: number;
445
+ cursorInteractiveRefs: number;
446
+ }
447
+
448
+ interface AiAssertOpts {
449
+ target?: Locator;
450
+ }
451
+ interface AiDefectsOpts extends AiAssertOpts {
452
+ /** Future hook for severity filtering — wired in B5 along with the AI rewrite. */
453
+ minSeverity?: "low" | "medium" | "high" | "critical";
454
+ }
455
+ interface AiExtractOpts<T> extends AiAssertOpts {
456
+ /** Optional Zod-or-similar schema. We accept any object exposing `.parse(value)`. */
457
+ schema?: {
458
+ parse: (value: unknown) => T;
459
+ };
460
+ }
461
+ interface AiFixture {
462
+ assert(claim: string, opts?: AiAssertOpts): Promise<void>;
463
+ assertNoDefects(opts?: AiDefectsOpts): Promise<void>;
464
+ extract<T = string>(query: string, opts?: AiExtractOpts<T>): Promise<T>;
465
+ }
466
+
467
+ interface PerfThresholds {
468
+ /** Largest Contentful Paint, e.g. "<2500ms". */
469
+ lcp?: string;
470
+ /** Cumulative Layout Shift, e.g. "<0.1". */
471
+ cls?: string;
472
+ /** Interaction to Next Paint, e.g. "<200ms". */
473
+ inp?: string;
474
+ /** First Contentful Paint, e.g. "<1800ms". */
475
+ fcp?: string;
476
+ /** Time to First Byte, e.g. "<800ms". */
477
+ ttfb?: string;
478
+ }
479
+ interface NetworkAssertOpts {
480
+ /** URL substrings or regexes that should be ignored. */
481
+ allow?: Array<string | RegExp>;
482
+ }
483
+ interface ConsoleAssertOpts {
484
+ allow?: Array<string | RegExp>;
485
+ }
486
+ interface AxeAuditOpts {
487
+ standard?: "WCAG2A" | "WCAG2AA" | "WCAG21A" | "WCAG21AA" | "WCAG22AA";
488
+ include?: string[];
489
+ exclude?: string[];
490
+ impacts?: Array<"critical" | "serious" | "moderate" | "minor">;
491
+ }
492
+ interface ObservabilityFixture {
493
+ expectPerformance(thresholds: PerfThresholds): Promise<void>;
494
+ expectNoNetworkErrors(opts?: NetworkAssertOpts): Promise<void>;
495
+ expectNoConsoleErrors(opts?: ConsoleAssertOpts): Promise<void>;
496
+ expectAccessible(opts?: AxeAuditOpts): Promise<void>;
497
+ snapshot(): Promise<{
498
+ performance?: PerformanceSnapshot;
499
+ network?: NetworkSnapshot;
500
+ console?: ConsoleSnapshot;
501
+ accessibility?: AccessibilitySnapshot;
502
+ }>;
503
+ }
504
+
505
+ interface SkepticFixture {
506
+ page: Page;
507
+ ctx: ExecutionContext;
508
+ /** Wraps a fixture-method body in the abort-aware boundary. Re-checks ctx.abortReason
509
+ * before each await, surfaces a clean error after a hard-timeout, and emits a
510
+ * `runner:action` marker the runner uses for video/cursor sidechannels. */
511
+ runAction: <T>(label: string, fn: () => Promise<T>) => Promise<T>;
512
+ snapshot: (target?: Page | Locator, opts?: SnapshotOptions) => Promise<SnapshotTree>;
513
+ screenshot: (name: string, opts?: ScreenshotOptions) => Promise<ScreenshotResult>;
514
+ settle: () => Promise<void>;
515
+ observability: ObservabilityFixture;
516
+ ai: AiFixture;
517
+ }
518
+
519
+ interface TestUseOptions {
520
+ /** Base URL — used by the runner to set the page's default base. Per-test override via `await page.goto(absoluteUrl)`. */
521
+ url?: string;
522
+ viewport?: {
523
+ width: number;
524
+ height: number;
525
+ };
526
+ /**
527
+ * Video recording resolution. When set, overrides the default of using the
528
+ * viewport size for `recordVideo.size`. Page rendering still happens at
529
+ * viewport dimensions; this only changes the WebM resolution. Precedence
530
+ * is CLI `--video-size` > `test.use({ videoSize })` > viewport.
531
+ */
532
+ videoSize?: {
533
+ width: number;
534
+ height: number;
535
+ };
536
+ device?: string;
537
+ cookies?: boolean | {
538
+ browser?: string;
539
+ };
540
+ env?: Record<string, string>;
541
+ tags?: string[];
542
+ /** Declarative collector attach. `--observability` overrides this with the full set. */
543
+ collectors?: CollectorName[];
544
+ /** Soft per-action default timeout (Playwright `setDefaultTimeout`). */
545
+ timeout?: number;
546
+ /** Hard per-test ceiling enforced by the runner via Promise.race + worker.terminate(). */
547
+ hardTimeout?: number;
548
+ retries?: number;
549
+ }
550
+ type TestFn = (fixture: SkepticFixture) => Promise<void> | void;
551
+ type HookFn = (fixture: SkepticFixture) => Promise<void> | void;
552
+ interface TestApi {
553
+ (name: string, fn: TestFn, use?: TestUseOptions): void;
554
+ skip: (name: string, fn: TestFn, use?: TestUseOptions) => void;
555
+ only: (name: string, fn: TestFn, use?: TestUseOptions) => void;
556
+ use: (options: TestUseOptions) => void;
557
+ beforeEach: (fn: HookFn, use?: TestUseOptions) => void;
558
+ afterEach: (fn: HookFn, use?: TestUseOptions) => void;
559
+ }
560
+ declare const test: TestApi;
561
+
562
+ /**
563
+ * Re-export Playwright's `expect` so test authors get the full matcher catalog
564
+ * (toHaveURL, toBeVisible, etc.) with one import:
565
+ *
566
+ * import { test, expect } from "skeptic-cli";
567
+ *
568
+ * Skeptic-specific matchers — toMatchSnapshot, toBeAccessible — land in B5/B6.
569
+ * They're defined here so the surface is stable; B5 plugs in the real bodies.
570
+ */
571
+ declare const expect: _playwright_test.Expect<{}>;
572
+
573
+ /** Browser engine configuration. */
574
+ declare const BrowserConfigSchema: z.ZodObject<{
575
+ engine: z.ZodDefault<z.ZodEnum<["chromium", "firefox", "webkit"]>>;
576
+ headless: z.ZodDefault<z.ZodBoolean>;
577
+ slowMo: z.ZodDefault<z.ZodNumber>;
578
+ timeout: z.ZodDefault<z.ZodNumber>;
579
+ viewport: z.ZodDefault<z.ZodObject<{
580
+ width: z.ZodDefault<z.ZodNumber>;
581
+ height: z.ZodDefault<z.ZodNumber>;
582
+ }, "strip", z.ZodTypeAny, {
583
+ width: number;
584
+ height: number;
585
+ }, {
586
+ width?: number | undefined;
587
+ height?: number | undefined;
588
+ }>>;
589
+ device: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
590
+ }, "strip", z.ZodTypeAny, {
591
+ engine: "chromium" | "firefox" | "webkit";
592
+ headless: boolean;
593
+ slowMo: number;
594
+ timeout: number;
595
+ viewport: {
596
+ width: number;
597
+ height: number;
598
+ };
599
+ device?: string | undefined;
600
+ }, {
601
+ engine?: "chromium" | "firefox" | "webkit" | undefined;
602
+ headless?: boolean | undefined;
603
+ slowMo?: number | undefined;
604
+ timeout?: number | undefined;
605
+ viewport?: {
606
+ width?: number | undefined;
607
+ height?: number | undefined;
608
+ } | undefined;
609
+ device?: string | undefined;
610
+ }>;
611
+ /** Auth configuration (cookie extraction). */
612
+ declare const AuthConfigSchema: z.ZodObject<{
613
+ cookies: z.ZodDefault<z.ZodBoolean>;
614
+ browsers: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
615
+ }, "strip", z.ZodTypeAny, {
616
+ cookies: boolean;
617
+ browsers: string[];
618
+ }, {
619
+ cookies?: boolean | undefined;
620
+ browsers?: string[] | undefined;
621
+ }>;
622
+ /** Execution configuration (retries, parallelism, bail). */
623
+ declare const ExecutionConfigSchema: z.ZodObject<{
624
+ retries: z.ZodDefault<z.ZodNumber>;
625
+ bail: z.ZodDefault<z.ZodBoolean>;
626
+ screenshotOnFailure: z.ZodDefault<z.ZodBoolean>;
627
+ parallel: z.ZodDefault<z.ZodNumber>;
628
+ grep: z.ZodOptional<z.ZodString>;
629
+ tags: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
630
+ }, "strip", z.ZodTypeAny, {
631
+ retries: number;
632
+ bail: boolean;
633
+ screenshotOnFailure: boolean;
634
+ parallel: number;
635
+ tags: string[];
636
+ grep?: string | undefined;
637
+ }, {
638
+ retries?: number | undefined;
639
+ bail?: boolean | undefined;
640
+ screenshotOnFailure?: boolean | undefined;
641
+ parallel?: number | undefined;
642
+ grep?: string | undefined;
643
+ tags?: string[] | undefined;
644
+ }>;
645
+ /** Output configuration (reporters, output dir). */
646
+ declare const OutputConfigSchema: z.ZodObject<{
647
+ dir: z.ZodDefault<z.ZodString>;
648
+ reporters: z.ZodDefault<z.ZodArray<z.ZodEnum<["console", "html", "json", "junit"]>, "many">>;
649
+ open: z.ZodDefault<z.ZodBoolean>;
650
+ verbose: z.ZodDefault<z.ZodBoolean>;
651
+ }, "strip", z.ZodTypeAny, {
652
+ dir: string;
653
+ reporters: ("console" | "html" | "json" | "junit")[];
654
+ open: boolean;
655
+ verbose: boolean;
656
+ }, {
657
+ dir?: string | undefined;
658
+ reporters?: ("console" | "html" | "json" | "junit")[] | undefined;
659
+ open?: boolean | undefined;
660
+ verbose?: boolean | undefined;
661
+ }>;
662
+ /** Safety controls for agent-driven browser work. */
663
+ declare const SafetyConfigSchema: z.ZodObject<{
664
+ /**
665
+ * Empty means unrestricted. Entries accept exact hostnames, URLs whose host is
666
+ * used, "*" for unrestricted, or "*.example.com" for a domain and children.
667
+ */
668
+ allowedDomains: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
669
+ /**
670
+ * Optional JSON policy file, resolved relative to the working directory:
671
+ * { "default": "allow"|"deny", "allow": ["browser_open"], "deny": [...] }.
672
+ */
673
+ actionPolicy: z.ZodOptional<z.ZodString>;
674
+ /**
675
+ * Actions that require interactive confirmation. MCP/browser sessions fail
676
+ * closed for these because there is no safe prompt channel in stdio tools.
677
+ */
678
+ confirmActions: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
679
+ /** Maximum characters returned inline from agent-facing JSON/text tools. */
680
+ maxOutputChars: z.ZodDefault<z.ZodNumber>;
681
+ /** Wrap inline tool output in XML-ish boundaries for prompt-injection hygiene. */
682
+ contentBoundaries: z.ZodDefault<z.ZodBoolean>;
683
+ }, "strip", z.ZodTypeAny, {
684
+ allowedDomains: string[];
685
+ confirmActions: string[];
686
+ maxOutputChars: number;
687
+ contentBoundaries: boolean;
688
+ actionPolicy?: string | undefined;
689
+ }, {
690
+ allowedDomains?: string[] | undefined;
691
+ actionPolicy?: string | undefined;
692
+ confirmActions?: string[] | undefined;
693
+ maxOutputChars?: number | undefined;
694
+ contentBoundaries?: boolean | undefined;
695
+ }>;
696
+ /** AI configuration. */
697
+ declare const AIConfigSchema: z.ZodObject<{
698
+ provider: z.ZodDefault<z.ZodEnum<["gemini", "openai", "anthropic"]>>;
699
+ apiKey: z.ZodOptional<z.ZodString>;
700
+ model: z.ZodOptional<z.ZodString>;
701
+ maxRequestsPerMinute: z.ZodDefault<z.ZodNumber>;
702
+ baseBranch: z.ZodDefault<z.ZodString>;
703
+ excludePaths: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
704
+ }, "strip", z.ZodTypeAny, {
705
+ provider: "gemini" | "openai" | "anthropic";
706
+ maxRequestsPerMinute: number;
707
+ baseBranch: string;
708
+ excludePaths: string[];
709
+ apiKey?: string | undefined;
710
+ model?: string | undefined;
711
+ }, {
712
+ provider?: "gemini" | "openai" | "anthropic" | undefined;
713
+ apiKey?: string | undefined;
714
+ model?: string | undefined;
715
+ maxRequestsPerMinute?: number | undefined;
716
+ baseBranch?: string | undefined;
717
+ excludePaths?: string[] | undefined;
718
+ }>;
719
+ /** Top-level skeptic.config.yaml schema. */
720
+ declare const skepticConfigSchema: z.ZodObject<{
721
+ url: z.ZodOptional<z.ZodString>;
722
+ tests: z.ZodDefault<z.ZodUnion<[z.ZodString, z.ZodArray<z.ZodString, "many">]>>;
723
+ browser: z.ZodDefault<z.ZodObject<{
724
+ engine: z.ZodDefault<z.ZodEnum<["chromium", "firefox", "webkit"]>>;
725
+ headless: z.ZodDefault<z.ZodBoolean>;
726
+ slowMo: z.ZodDefault<z.ZodNumber>;
727
+ timeout: z.ZodDefault<z.ZodNumber>;
728
+ viewport: z.ZodDefault<z.ZodObject<{
729
+ width: z.ZodDefault<z.ZodNumber>;
730
+ height: z.ZodDefault<z.ZodNumber>;
731
+ }, "strip", z.ZodTypeAny, {
732
+ width: number;
733
+ height: number;
734
+ }, {
735
+ width?: number | undefined;
736
+ height?: number | undefined;
737
+ }>>;
738
+ device: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
739
+ }, "strip", z.ZodTypeAny, {
740
+ engine: "chromium" | "firefox" | "webkit";
741
+ headless: boolean;
742
+ slowMo: number;
743
+ timeout: number;
744
+ viewport: {
745
+ width: number;
746
+ height: number;
747
+ };
748
+ device?: string | undefined;
749
+ }, {
750
+ engine?: "chromium" | "firefox" | "webkit" | undefined;
751
+ headless?: boolean | undefined;
752
+ slowMo?: number | undefined;
753
+ timeout?: number | undefined;
754
+ viewport?: {
755
+ width?: number | undefined;
756
+ height?: number | undefined;
757
+ } | undefined;
758
+ device?: string | undefined;
759
+ }>>;
760
+ auth: z.ZodDefault<z.ZodObject<{
761
+ cookies: z.ZodDefault<z.ZodBoolean>;
762
+ browsers: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
763
+ }, "strip", z.ZodTypeAny, {
764
+ cookies: boolean;
765
+ browsers: string[];
766
+ }, {
767
+ cookies?: boolean | undefined;
768
+ browsers?: string[] | undefined;
769
+ }>>;
770
+ execution: z.ZodDefault<z.ZodObject<{
771
+ retries: z.ZodDefault<z.ZodNumber>;
772
+ bail: z.ZodDefault<z.ZodBoolean>;
773
+ screenshotOnFailure: z.ZodDefault<z.ZodBoolean>;
774
+ parallel: z.ZodDefault<z.ZodNumber>;
775
+ grep: z.ZodOptional<z.ZodString>;
776
+ tags: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
777
+ }, "strip", z.ZodTypeAny, {
778
+ retries: number;
779
+ bail: boolean;
780
+ screenshotOnFailure: boolean;
781
+ parallel: number;
782
+ tags: string[];
783
+ grep?: string | undefined;
784
+ }, {
785
+ retries?: number | undefined;
786
+ bail?: boolean | undefined;
787
+ screenshotOnFailure?: boolean | undefined;
788
+ parallel?: number | undefined;
789
+ grep?: string | undefined;
790
+ tags?: string[] | undefined;
791
+ }>>;
792
+ output: z.ZodDefault<z.ZodObject<{
793
+ dir: z.ZodDefault<z.ZodString>;
794
+ reporters: z.ZodDefault<z.ZodArray<z.ZodEnum<["console", "html", "json", "junit"]>, "many">>;
795
+ open: z.ZodDefault<z.ZodBoolean>;
796
+ verbose: z.ZodDefault<z.ZodBoolean>;
797
+ }, "strip", z.ZodTypeAny, {
798
+ dir: string;
799
+ reporters: ("console" | "html" | "json" | "junit")[];
800
+ open: boolean;
801
+ verbose: boolean;
802
+ }, {
803
+ dir?: string | undefined;
804
+ reporters?: ("console" | "html" | "json" | "junit")[] | undefined;
805
+ open?: boolean | undefined;
806
+ verbose?: boolean | undefined;
807
+ }>>;
808
+ ai: z.ZodDefault<z.ZodObject<{
809
+ provider: z.ZodDefault<z.ZodEnum<["gemini", "openai", "anthropic"]>>;
810
+ apiKey: z.ZodOptional<z.ZodString>;
811
+ model: z.ZodOptional<z.ZodString>;
812
+ maxRequestsPerMinute: z.ZodDefault<z.ZodNumber>;
813
+ baseBranch: z.ZodDefault<z.ZodString>;
814
+ excludePaths: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
815
+ }, "strip", z.ZodTypeAny, {
816
+ provider: "gemini" | "openai" | "anthropic";
817
+ maxRequestsPerMinute: number;
818
+ baseBranch: string;
819
+ excludePaths: string[];
820
+ apiKey?: string | undefined;
821
+ model?: string | undefined;
822
+ }, {
823
+ provider?: "gemini" | "openai" | "anthropic" | undefined;
824
+ apiKey?: string | undefined;
825
+ model?: string | undefined;
826
+ maxRequestsPerMinute?: number | undefined;
827
+ baseBranch?: string | undefined;
828
+ excludePaths?: string[] | undefined;
829
+ }>>;
830
+ observability: z.ZodDefault<z.ZodObject<{
831
+ collectors: z.ZodDefault<z.ZodArray<z.ZodEnum<["performance", "network", "accessibility", "console"]>, "many">>;
832
+ networkCaptureLimit: z.ZodDefault<z.ZodNumber>;
833
+ duplicateWindowMs: z.ZodDefault<z.ZodNumber>;
834
+ accessibilityDualEngine: z.ZodDefault<z.ZodBoolean>;
835
+ accessibilityHtmlSnippetLimit: z.ZodDefault<z.ZodNumber>;
836
+ /**
837
+ * Reporter-aware default-on policy. `passive` auto-attaches perf+net+console when an HTML
838
+ * reporter is active. `full` also auto-attaches accessibility (with auto-audit). `none`
839
+ * disables the policy entirely. The merge is done in `commands/test.ts` where reporter
840
+ * formats are in scope; the registry stays reporter-agnostic.
841
+ */
842
+ defaultsForReports: z.ZodDefault<z.ZodEnum<["none", "passive", "full"]>>;
843
+ consoleCaptureLimit: z.ZodDefault<z.ZodNumber>;
844
+ consoleRedaction: z.ZodDefault<z.ZodBoolean>;
845
+ /**
846
+ * When true, the engine fires `AccessibilityCollector.audit()` once per test before
847
+ * `onTestComplete`. `--observability` flips this on.
848
+ */
849
+ autoAccessibilityAudit: z.ZodDefault<z.ZodBoolean>;
850
+ accessibilityStandard: z.ZodDefault<z.ZodEnum<["WCAG2A", "WCAG2AA", "WCAG21A", "WCAG21AA", "WCAG22AA"]>>;
851
+ accessibilityImpacts: z.ZodOptional<z.ZodArray<z.ZodEnum<["critical", "serious", "moderate", "minor"]>, "many">>;
852
+ /**
853
+ * Per-impact-bucket cap for the `perf-trace.md` Accessibility section. The
854
+ * full per-test `audit.md` sidecar always lists every rule regardless of
855
+ * this cap. Default 100 covers common pages without truncation; bump it
856
+ * higher (or to 0 for "show all in perf-trace.md") for compliance work.
857
+ */
858
+ accessibilityMaxRulesPerImpact: z.ZodDefault<z.ZodNumber>;
859
+ /** Default fullPage for screenshot steps when a step omits the option. */
860
+ fullPageScreenshots: z.ZodDefault<z.ZodBoolean>;
861
+ /** Default blank-frame mode for screenshot steps. Per-step overrides win. */
862
+ blankFrameDetection: z.ZodDefault<z.ZodEnum<["off", "warn", "fail"]>>;
863
+ }, "strip", z.ZodTypeAny, {
864
+ collectors: ("console" | "performance" | "network" | "accessibility")[];
865
+ networkCaptureLimit: number;
866
+ duplicateWindowMs: number;
867
+ accessibilityDualEngine: boolean;
868
+ accessibilityHtmlSnippetLimit: number;
869
+ defaultsForReports: "none" | "passive" | "full";
870
+ consoleCaptureLimit: number;
871
+ consoleRedaction: boolean;
872
+ autoAccessibilityAudit: boolean;
873
+ accessibilityStandard: "WCAG2A" | "WCAG2AA" | "WCAG21A" | "WCAG21AA" | "WCAG22AA";
874
+ accessibilityMaxRulesPerImpact: number;
875
+ fullPageScreenshots: boolean;
876
+ blankFrameDetection: "warn" | "off" | "fail";
877
+ accessibilityImpacts?: ("critical" | "serious" | "moderate" | "minor")[] | undefined;
878
+ }, {
879
+ collectors?: ("console" | "performance" | "network" | "accessibility")[] | undefined;
880
+ networkCaptureLimit?: number | undefined;
881
+ duplicateWindowMs?: number | undefined;
882
+ accessibilityDualEngine?: boolean | undefined;
883
+ accessibilityHtmlSnippetLimit?: number | undefined;
884
+ defaultsForReports?: "none" | "passive" | "full" | undefined;
885
+ consoleCaptureLimit?: number | undefined;
886
+ consoleRedaction?: boolean | undefined;
887
+ autoAccessibilityAudit?: boolean | undefined;
888
+ accessibilityStandard?: "WCAG2A" | "WCAG2AA" | "WCAG21A" | "WCAG21AA" | "WCAG22AA" | undefined;
889
+ accessibilityImpacts?: ("critical" | "serious" | "moderate" | "minor")[] | undefined;
890
+ accessibilityMaxRulesPerImpact?: number | undefined;
891
+ fullPageScreenshots?: boolean | undefined;
892
+ blankFrameDetection?: "warn" | "off" | "fail" | undefined;
893
+ }>>;
894
+ safety: z.ZodDefault<z.ZodObject<{
895
+ /**
896
+ * Empty means unrestricted. Entries accept exact hostnames, URLs whose host is
897
+ * used, "*" for unrestricted, or "*.example.com" for a domain and children.
898
+ */
899
+ allowedDomains: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
900
+ /**
901
+ * Optional JSON policy file, resolved relative to the working directory:
902
+ * { "default": "allow"|"deny", "allow": ["browser_open"], "deny": [...] }.
903
+ */
904
+ actionPolicy: z.ZodOptional<z.ZodString>;
905
+ /**
906
+ * Actions that require interactive confirmation. MCP/browser sessions fail
907
+ * closed for these because there is no safe prompt channel in stdio tools.
908
+ */
909
+ confirmActions: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
910
+ /** Maximum characters returned inline from agent-facing JSON/text tools. */
911
+ maxOutputChars: z.ZodDefault<z.ZodNumber>;
912
+ /** Wrap inline tool output in XML-ish boundaries for prompt-injection hygiene. */
913
+ contentBoundaries: z.ZodDefault<z.ZodBoolean>;
914
+ }, "strip", z.ZodTypeAny, {
915
+ allowedDomains: string[];
916
+ confirmActions: string[];
917
+ maxOutputChars: number;
918
+ contentBoundaries: boolean;
919
+ actionPolicy?: string | undefined;
920
+ }, {
921
+ allowedDomains?: string[] | undefined;
922
+ actionPolicy?: string | undefined;
923
+ confirmActions?: string[] | undefined;
924
+ maxOutputChars?: number | undefined;
925
+ contentBoundaries?: boolean | undefined;
926
+ }>>;
927
+ notifications: z.ZodOptional<z.ZodObject<{
928
+ slack: z.ZodOptional<z.ZodObject<{
929
+ onSuccess: z.ZodDefault<z.ZodBoolean>;
930
+ onFailure: z.ZodDefault<z.ZodBoolean>;
931
+ webhookUrl: z.ZodString;
932
+ mention: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
933
+ }, "strip", z.ZodTypeAny, {
934
+ webhookUrl: string;
935
+ mention: string[];
936
+ onSuccess: boolean;
937
+ onFailure: boolean;
938
+ }, {
939
+ webhookUrl: string;
940
+ mention?: string[] | undefined;
941
+ onSuccess?: boolean | undefined;
942
+ onFailure?: boolean | undefined;
943
+ }>>;
944
+ webhook: z.ZodOptional<z.ZodObject<{
945
+ onSuccess: z.ZodDefault<z.ZodBoolean>;
946
+ onFailure: z.ZodDefault<z.ZodBoolean>;
947
+ url: z.ZodString;
948
+ headers: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodString>>;
949
+ }, "strip", z.ZodTypeAny, {
950
+ onSuccess: boolean;
951
+ onFailure: boolean;
952
+ url: string;
953
+ headers: Record<string, string>;
954
+ }, {
955
+ url: string;
956
+ onSuccess?: boolean | undefined;
957
+ onFailure?: boolean | undefined;
958
+ headers?: Record<string, string> | undefined;
959
+ }>>;
960
+ }, "strip", z.ZodTypeAny, {
961
+ slack?: {
962
+ webhookUrl: string;
963
+ mention: string[];
964
+ onSuccess: boolean;
965
+ onFailure: boolean;
966
+ } | undefined;
967
+ webhook?: {
968
+ onSuccess: boolean;
969
+ onFailure: boolean;
970
+ url: string;
971
+ headers: Record<string, string>;
972
+ } | undefined;
973
+ }, {
974
+ slack?: {
975
+ webhookUrl: string;
976
+ mention?: string[] | undefined;
977
+ onSuccess?: boolean | undefined;
978
+ onFailure?: boolean | undefined;
979
+ } | undefined;
980
+ webhook?: {
981
+ url: string;
982
+ onSuccess?: boolean | undefined;
983
+ onFailure?: boolean | undefined;
984
+ headers?: Record<string, string> | undefined;
985
+ } | undefined;
986
+ }>>;
987
+ env: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodString>>;
988
+ }, "strip", z.ZodTypeAny, {
989
+ tests: string | string[];
990
+ browser: {
991
+ engine: "chromium" | "firefox" | "webkit";
992
+ headless: boolean;
993
+ slowMo: number;
994
+ timeout: number;
995
+ viewport: {
996
+ width: number;
997
+ height: number;
998
+ };
999
+ device?: string | undefined;
1000
+ };
1001
+ auth: {
1002
+ cookies: boolean;
1003
+ browsers: string[];
1004
+ };
1005
+ execution: {
1006
+ retries: number;
1007
+ bail: boolean;
1008
+ screenshotOnFailure: boolean;
1009
+ parallel: number;
1010
+ tags: string[];
1011
+ grep?: string | undefined;
1012
+ };
1013
+ output: {
1014
+ dir: string;
1015
+ reporters: ("console" | "html" | "json" | "junit")[];
1016
+ open: boolean;
1017
+ verbose: boolean;
1018
+ };
1019
+ ai: {
1020
+ provider: "gemini" | "openai" | "anthropic";
1021
+ maxRequestsPerMinute: number;
1022
+ baseBranch: string;
1023
+ excludePaths: string[];
1024
+ apiKey?: string | undefined;
1025
+ model?: string | undefined;
1026
+ };
1027
+ observability: {
1028
+ collectors: ("console" | "performance" | "network" | "accessibility")[];
1029
+ networkCaptureLimit: number;
1030
+ duplicateWindowMs: number;
1031
+ accessibilityDualEngine: boolean;
1032
+ accessibilityHtmlSnippetLimit: number;
1033
+ defaultsForReports: "none" | "passive" | "full";
1034
+ consoleCaptureLimit: number;
1035
+ consoleRedaction: boolean;
1036
+ autoAccessibilityAudit: boolean;
1037
+ accessibilityStandard: "WCAG2A" | "WCAG2AA" | "WCAG21A" | "WCAG21AA" | "WCAG22AA";
1038
+ accessibilityMaxRulesPerImpact: number;
1039
+ fullPageScreenshots: boolean;
1040
+ blankFrameDetection: "warn" | "off" | "fail";
1041
+ accessibilityImpacts?: ("critical" | "serious" | "moderate" | "minor")[] | undefined;
1042
+ };
1043
+ safety: {
1044
+ allowedDomains: string[];
1045
+ confirmActions: string[];
1046
+ maxOutputChars: number;
1047
+ contentBoundaries: boolean;
1048
+ actionPolicy?: string | undefined;
1049
+ };
1050
+ env: Record<string, string>;
1051
+ url?: string | undefined;
1052
+ notifications?: {
1053
+ slack?: {
1054
+ webhookUrl: string;
1055
+ mention: string[];
1056
+ onSuccess: boolean;
1057
+ onFailure: boolean;
1058
+ } | undefined;
1059
+ webhook?: {
1060
+ onSuccess: boolean;
1061
+ onFailure: boolean;
1062
+ url: string;
1063
+ headers: Record<string, string>;
1064
+ } | undefined;
1065
+ } | undefined;
1066
+ }, {
1067
+ url?: string | undefined;
1068
+ tests?: string | string[] | undefined;
1069
+ browser?: {
1070
+ engine?: "chromium" | "firefox" | "webkit" | undefined;
1071
+ headless?: boolean | undefined;
1072
+ slowMo?: number | undefined;
1073
+ timeout?: number | undefined;
1074
+ viewport?: {
1075
+ width?: number | undefined;
1076
+ height?: number | undefined;
1077
+ } | undefined;
1078
+ device?: string | undefined;
1079
+ } | undefined;
1080
+ auth?: {
1081
+ cookies?: boolean | undefined;
1082
+ browsers?: string[] | undefined;
1083
+ } | undefined;
1084
+ execution?: {
1085
+ retries?: number | undefined;
1086
+ bail?: boolean | undefined;
1087
+ screenshotOnFailure?: boolean | undefined;
1088
+ parallel?: number | undefined;
1089
+ grep?: string | undefined;
1090
+ tags?: string[] | undefined;
1091
+ } | undefined;
1092
+ output?: {
1093
+ dir?: string | undefined;
1094
+ reporters?: ("console" | "html" | "json" | "junit")[] | undefined;
1095
+ open?: boolean | undefined;
1096
+ verbose?: boolean | undefined;
1097
+ } | undefined;
1098
+ ai?: {
1099
+ provider?: "gemini" | "openai" | "anthropic" | undefined;
1100
+ apiKey?: string | undefined;
1101
+ model?: string | undefined;
1102
+ maxRequestsPerMinute?: number | undefined;
1103
+ baseBranch?: string | undefined;
1104
+ excludePaths?: string[] | undefined;
1105
+ } | undefined;
1106
+ observability?: {
1107
+ collectors?: ("console" | "performance" | "network" | "accessibility")[] | undefined;
1108
+ networkCaptureLimit?: number | undefined;
1109
+ duplicateWindowMs?: number | undefined;
1110
+ accessibilityDualEngine?: boolean | undefined;
1111
+ accessibilityHtmlSnippetLimit?: number | undefined;
1112
+ defaultsForReports?: "none" | "passive" | "full" | undefined;
1113
+ consoleCaptureLimit?: number | undefined;
1114
+ consoleRedaction?: boolean | undefined;
1115
+ autoAccessibilityAudit?: boolean | undefined;
1116
+ accessibilityStandard?: "WCAG2A" | "WCAG2AA" | "WCAG21A" | "WCAG21AA" | "WCAG22AA" | undefined;
1117
+ accessibilityImpacts?: ("critical" | "serious" | "moderate" | "minor")[] | undefined;
1118
+ accessibilityMaxRulesPerImpact?: number | undefined;
1119
+ fullPageScreenshots?: boolean | undefined;
1120
+ blankFrameDetection?: "warn" | "off" | "fail" | undefined;
1121
+ } | undefined;
1122
+ safety?: {
1123
+ allowedDomains?: string[] | undefined;
1124
+ actionPolicy?: string | undefined;
1125
+ confirmActions?: string[] | undefined;
1126
+ maxOutputChars?: number | undefined;
1127
+ contentBoundaries?: boolean | undefined;
1128
+ } | undefined;
1129
+ notifications?: {
1130
+ slack?: {
1131
+ webhookUrl: string;
1132
+ mention?: string[] | undefined;
1133
+ onSuccess?: boolean | undefined;
1134
+ onFailure?: boolean | undefined;
1135
+ } | undefined;
1136
+ webhook?: {
1137
+ url: string;
1138
+ onSuccess?: boolean | undefined;
1139
+ onFailure?: boolean | undefined;
1140
+ headers?: Record<string, string> | undefined;
1141
+ } | undefined;
1142
+ } | undefined;
1143
+ env?: Record<string, string> | undefined;
1144
+ }>;
1145
+ type skepticConfig = z.infer<typeof skepticConfigSchema>;
1146
+ type BrowserConfig = z.infer<typeof BrowserConfigSchema>;
1147
+ type AuthConfig = z.infer<typeof AuthConfigSchema>;
1148
+ type ExecutionConfig = z.infer<typeof ExecutionConfigSchema>;
1149
+ type OutputConfig = z.infer<typeof OutputConfigSchema>;
1150
+ type AIConfig = z.infer<typeof AIConfigSchema>;
1151
+ type SafetyConfig = z.infer<typeof SafetyConfigSchema>;
1152
+
1153
+ /**
1154
+ * Device profiles for responsive testing.
1155
+ * Ported from backend/device_profiles.py — Chrome 134 stable user agents.
1156
+ */
1157
+ type DeviceCategory = "desktop" | "phone" | "tablet";
1158
+ interface DeviceProfile {
1159
+ label: string;
1160
+ category: DeviceCategory;
1161
+ width: number;
1162
+ height: number;
1163
+ dpr: number;
1164
+ userAgent: string | null;
1165
+ }
1166
+ declare const DEVICE_PROFILES: Record<string, DeviceProfile>;
1167
+ /** Look up a device profile by ID, or return undefined. */
1168
+ declare function getDeviceProfile(id: string): DeviceProfile | undefined;
1169
+ /** List profiles filtered by category. */
1170
+ declare function getProfilesByCategory(category: DeviceCategory): Record<string, DeviceProfile>;
1171
+
1172
+ /** Detect CI environment, AI agent environment, and interactivity. */
1173
+ interface EnvironmentInfo {
1174
+ /** True when running in a known CI provider. */
1175
+ isCI: boolean;
1176
+ /** Name of the detected CI provider, if any. */
1177
+ ciProvider: string | null;
1178
+ /** True when running inside an AI coding agent. */
1179
+ isAgentEnv: boolean;
1180
+ /** Name of the detected agent, if any. */
1181
+ agentName: string | null;
1182
+ /** True when stdin is a TTY (interactive terminal). */
1183
+ isInteractive: boolean;
1184
+ }
1185
+ declare function detectCI(): EnvironmentInfo;
1186
+
1187
+ interface LoadConfigOptions {
1188
+ /** Explicit path to config file (overrides search). */
1189
+ configPath?: string;
1190
+ /** CLI overrides merged on top of file config. */
1191
+ overrides?: Record<string, unknown>;
1192
+ /**
1193
+ * Override the directory from which the upward walk-up starts when no
1194
+ * `configPath` is provided. Defaults to `process.cwd()`.
1195
+ *
1196
+ * Used by long-lived servers (ACP) that load config relative to a session's
1197
+ * working directory without mutating `process.cwd()`. Ignored when
1198
+ * `configPath` is set.
1199
+ */
1200
+ searchCwd?: string;
1201
+ }
1202
+ /**
1203
+ * Load and validate skeptic.config.yaml.
1204
+ *
1205
+ * Precedence (highest wins):
1206
+ * 1. CLI flags (overrides)
1207
+ * 2. SKEPTIC_* environment variables
1208
+ * 3. Config file values
1209
+ * 4. Schema defaults
1210
+ *
1211
+ * Environment variable interpolation runs on the raw YAML before validation.
1212
+ */
1213
+ declare function loadConfig(opts?: LoadConfigOptions): skepticConfig;
1214
+
1215
+ type LogLevel = "debug" | "info" | "warn" | "error" | "silent";
1216
+ declare function setLogLevel(level: LogLevel): void;
1217
+ declare function getLogLevel(): LogLevel;
1218
+ declare const logger: {
1219
+ debug(...args: unknown[]): void;
1220
+ info(...args: unknown[]): void;
1221
+ success(...args: unknown[]): void;
1222
+ warn(...args: unknown[]): void;
1223
+ error(...args: unknown[]): void;
1224
+ /** Print without prefix — for raw output like tables. */
1225
+ raw(...args: unknown[]): void;
1226
+ /** Like raw(), but error-level gated and written to stderr so --quiet still shows it. */
1227
+ errorRaw(...args: unknown[]): void;
1228
+ /** Styled step header, e.g. "Step 1/5 — Navigate to /login" */
1229
+ step(current: number, total: number, label: string): void;
1230
+ };
1231
+
1232
+ /** Lightweight duration tracker. */
1233
+ declare class Timer {
1234
+ private readonly start;
1235
+ constructor();
1236
+ /** Elapsed time in milliseconds. */
1237
+ elapsedMs(): number;
1238
+ /** Human-readable elapsed time, e.g. "1.23s" or "456ms". */
1239
+ format(): string;
1240
+ }
1241
+
1242
+ /**
1243
+ * Environment variable interpolation for config values.
1244
+ * Supports ${VAR} and ${VAR:-default} syntax.
1245
+ */
1246
+ declare function interpolateEnv(value: string): string;
1247
+ /** Recursively interpolate all string values in an object. */
1248
+ declare function interpolateEnvDeep(obj: unknown): unknown;
1249
+
1250
+ declare const program: Command;
1251
+
1252
+ export { type AIConfig, type AiAssertOpts, type AiDefectsOpts, type AiExtractOpts, type AiFixture, type AuthConfig, type AxeAuditOpts, type BrowserConfig, type ConsoleAssertOpts, DEVICE_PROFILES, type DeviceCategory, type DeviceProfile, type EnvironmentInfo, type ExecutionConfig, type HookFn, type NetworkAssertOpts, type ObservabilityFixture, type OutputConfig, type PerfThresholds, type SafetyConfig, type ScreenshotOptions, type ScreenshotResult, type SkepticFixture, type SnapshotOptions, type SnapshotTree, type TestFn, type TestUseOptions, Timer, commandUsesBrowser, detectCI, expect, getDeviceProfile, getLogLevel, getProfilesByCategory, interpolateEnv, interpolateEnvDeep, loadConfig, logger, prewarmDaemonIfNeeded, program, setLogLevel, type skepticConfig, test };