@jterrazz/test 15.0.0 → 15.1.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/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Scalar } from "yaml";
2
2
  import { DeepMockProxy } from "vitest-mock-extended";
3
- //#region src/core/matching/match.d.ts
3
+ //#region src/specification/matching/match.d.ts
4
4
  /**
5
5
  * Code-side dynamic-value matchers (`match.*`) and the capture scope shared
6
6
  * by every assertion of a single spec execution.
@@ -12,30 +12,36 @@ import { DeepMockProxy } from "vitest-mock-extended";
12
12
  */
13
13
  /** The frozen token vocabulary (CONVENTIONS D4) plus the code-only kinds. */
14
14
  type MatcherKind = 'any' | 'base64' | 'date' | 'duration' | 'email' | 'float' | 'hex' | 'includes' | 'int' | 'ip' | 'iso8601' | 'number' | 'path' | 'port' | 'ref' | 'regex' | 'semver' | 'sha' | 'string' | 'time' | 'ulid' | 'url' | 'uuid' | 'workdir';
15
+ /** Every kind usable as a `{{token}}` in fixture files (all but ref/regex/includes). */
16
+ declare const TOKEN_KINDS: readonly ["any", "base64", "date", "duration", "email", "float", "hex", "int", "ip", "iso8601", "number", "path", "port", "semver", "sha", "string", "time", "ulid", "url", "uuid", "workdir"];
17
+ /** A token kind valid inside a fixture `{{...}}` placeholder. */
18
+ type TokenKind = (typeof TOKEN_KINDS)[number];
15
19
  /**
16
20
  * A dynamic-value matcher. Created via the {@link match} factories — never
17
21
  * constructed directly by user code.
18
22
  */
19
23
  declare class Matcher {
20
24
  /** For kind 'includes': the substring the actual value must contain. */
21
- readonly includes?: string;
25
+ readonly includes?: string | undefined;
22
26
  readonly kind: MatcherKind;
23
27
  /** For kind 'ref': the capture to assert inequality against. */
24
- readonly notRef?: string;
28
+ readonly notRef?: string | undefined;
25
29
  /** For kind 'ref': the capture name. */
26
- readonly refName?: string;
30
+ readonly refName?: string | undefined;
27
31
  /** For kind 'regex': the pattern the actual value must match. */
28
- readonly regex?: RegExp;
32
+ readonly regex?: RegExp | undefined;
29
33
  constructor(kind: MatcherKind, options?: {
30
- includes?: string;
31
- notRef?: string;
32
- refName?: string;
33
- regex?: RegExp;
34
+ includes?: string | undefined;
35
+ notRef?: string | undefined;
36
+ refName?: string | undefined;
37
+ regex?: RegExp | undefined;
34
38
  });
35
39
  /** Placeholder-style rendering used in failure diffs and serialization. */
36
40
  toString(): string;
37
41
  toJSON(): string;
38
42
  }
43
+ /** A no-argument matcher factory — what every typed entry of `match` is. */
44
+ type TypedMatcher = () => Matcher;
39
45
  /**
40
46
  * Dynamic-value matchers for structural comparisons — the code-side mirror of
41
47
  * the `{{token}}` fixture grammar (CONVENTIONS D4).
@@ -46,72 +52,18 @@ declare class Matcher {
46
52
  * rows: [[match.uuid(), 'Alice']],
47
53
  * });
48
54
  */
49
- declare const match: {
50
- /** Matches anything. */
51
- any: () => Matcher;
52
- /** Matches a base64 string. */
53
- base64: () => Matcher;
54
- /** Matches a calendar date (`YYYY-MM-DD`). */
55
- date: () => Matcher;
56
- /** Matches a human duration (`12ms`, `1.5s`, `2m`, `3h`). */
57
- duration: () => Matcher;
58
- /** Matches an email address. */
59
- email: () => Matcher;
60
- /**
61
- * Matches a float. In JSON contexts any finite number passes (JSON does
62
- * not distinguish `42` from `42.0`); in text contexts the decimal part
63
- * is required (`4.2`, never `42`).
64
- */
65
- float: () => Matcher;
66
- /** Matches a hexadecimal string. */
67
- hex: () => Matcher;
68
- /**
69
- * Matches a string CONTAINING the given substring. Code-only, like
70
- * {@link match.regex} — the `{{token}}` fixture vocabulary does not grow.
71
- * The explicit escape hatch now that contract string filters
72
- * (`openai.chat({ user })`) mean exact equality.
73
- */
55
+ /**
56
+ * The matcher roster: one no-argument factory per token kind, plus the three
57
+ * code-only forms the `{{token}}` grammar does not carry.
58
+ */
59
+ type MatchFactories = Record<TokenKind, TypedMatcher> & {
74
60
  includes: (substring: string) => Matcher;
75
- /** Matches an integer (or an integer string in text contexts). */
76
- int: () => Matcher;
77
- /** Matches an IPv4 address. */
78
- ip: () => Matcher;
79
- /** Matches an ISO-8601 timestamp string. */
80
- iso8601: () => Matcher;
81
- /** Matches a number (or a numeric string in text contexts). */
82
- number: () => Matcher;
83
- /** Matches a filesystem path (`/...` or `./...`). */
84
- path: () => Matcher;
85
- /** Matches a TCP/UDP port number (0-65535). */
86
- port: () => Matcher;
87
- /**
88
- * Capture-and-compare. The first occurrence of `ref(name)` captures the
89
- * actual value; every later occurrence must strictly equal the capture.
90
- * `{ not: other }` additionally asserts inequality with the capture named
91
- * `other`. Scope: the current spec execution (reset per chain).
92
- */
93
61
  ref: (name: string, options?: {
94
62
  not?: string;
95
63
  }) => Matcher;
96
- /** Matches a string against the given regular expression. */
97
64
  regex: (regex: RegExp) => Matcher;
98
- /** Matches a semantic version (`1.2.3`, `2.0.0-rc.1`). */
99
- semver: () => Matcher;
100
- /** Matches a git SHA (7-64 hex chars). */
101
- sha: () => Matcher;
102
- /** Matches any string. */
103
- string: () => Matcher;
104
- /** Matches a wall-clock time (`HH:MM` or `HH:MM:SS`). */
105
- time: () => Matcher;
106
- /** Matches a ULID. */
107
- ulid: () => Matcher;
108
- /** Matches an http(s) URL. */
109
- url: () => Matcher;
110
- /** Matches a UUID string. */
111
- uuid: () => Matcher;
112
- /** Matches the exact working directory of the current spec. */
113
- workdir: () => Matcher;
114
65
  };
66
+ declare const match: MatchFactories;
115
67
  /**
116
68
  * Named captures recorded by `match.ref()` / `{{type#ref}}` placeholders.
117
69
  * One scope lives on each spec result — every assertion chained off the same
@@ -123,59 +75,14 @@ declare class CaptureScope {
123
75
  * The exact working directory of the current spec, when the framework
124
76
  * knows it (cli mode). Drives the `{{workdir}}` token / `match.workdir()`.
125
77
  */
126
- workdir?: string;
78
+ workdir?: string | undefined;
127
79
  constructor(workdir?: string);
128
80
  get(name: string): unknown;
129
81
  has(name: string): boolean;
130
82
  set(name: string, value: unknown): void;
131
83
  }
132
84
  //#endregion
133
- //#region src/core/specification/shared/result/directory.d.ts
134
- /**
135
- * Read-only accessor for a directory produced by a spec action.
136
- *
137
- * Assertions go through `expect()` (async — they walk the disk):
138
- * `await expect(result.directory('out')).toMatch('scaffold/out')`
139
- * compares the tree against the fixture directory `_expected/<name>/`.
140
- */
141
- declare class DirectoryAccessor {
142
- /** @internal Ref-capture scope shared by the current spec execution. */
143
- readonly captures: CaptureScope;
144
- /** @internal Absolute path of the directory under assertion. */
145
- readonly root: string;
146
- /** @internal Test-file directory — fixture resolution root for matchers. */
147
- readonly testDir: string;
148
- constructor(absPath: string, testDir: string, captures?: CaptureScope);
149
- /** List all files (recursively) under the directory, sorted. */
150
- files(options?: {
151
- ignore?: string[];
152
- }): Promise<string[]>;
153
- }
154
- //#endregion
155
- //#region src/core/specification/shared/result/filesystem.d.ts
156
- /**
157
- * Read-only accessor for the whole temporary working directory used by a
158
- * command spec.
159
- *
160
- * Assertions go through `expect()` (async — they walk the disk):
161
- * `await expect(result.filesystem).toMatch('scaffolded')` compares the
162
- * tree against the fixture directory `_expected/<name>/`.
163
- */
164
- declare class FilesystemAccessor {
165
- /** @internal Ref-capture scope shared by the current spec execution. */
166
- readonly captures: CaptureScope;
167
- /** The absolute path of the temporary working directory. */
168
- readonly cwd: string;
169
- /** @internal Test-file directory — fixture resolution root for matchers. */
170
- readonly testDir: string;
171
- constructor(cwd: string, testDir: string, captures?: CaptureScope);
172
- /** List all files (recursively) under the working directory, sorted. */
173
- files(options?: {
174
- ignore?: string[];
175
- }): Promise<string[]>;
176
- }
177
- //#endregion
178
- //#region src/core/specification/shared/result/json.d.ts
85
+ //#region src/specification/facets/_common/result/json.d.ts
179
86
  /**
180
87
  * Read-only accessor for a JSON payload parsed from a text stream (stdout).
181
88
  *
@@ -205,218 +112,14 @@ declare class JsonAccessor {
205
112
  get value(): unknown;
206
113
  }
207
114
  //#endregion
208
- //#region src/core/ports/server.port.d.ts
209
- /**
210
- * HTTP response returned by a server port, with parsed JSON body.
211
- */
212
- interface ServerResponse {
213
- /** HTTP status code. */
214
- status: number;
215
- /** Parsed JSON response body, or null if parsing failed. */
216
- body: unknown;
217
- /** Response headers as a flat key-value map. */
218
- headers: Record<string, string>;
219
- }
220
- /**
221
- * Abstract server interface for specification runners.
222
- * Integration mode uses an in-process Hono app; E2E mode uses real HTTP via fetch.
223
- */
224
- interface ServerPort {
225
- /** Send an HTTP request and return the parsed response. */
226
- request: (method: string, path: string, body?: unknown, headers?: Record<string, string>) => Promise<ServerResponse>;
227
- }
228
- //#endregion
229
- //#region src/core/specification/shared/result/response.d.ts
230
- /**
231
- * Read-only accessor for an HTTP response.
232
- *
233
- * Assertions go through `expect()`:
234
- * `expect(result.response).toMatch('created.http')` compares status,
235
- * a subset of headers, and the JSON body against `_expected/<name>` —
236
- * with `{{placeholder}}` support in all three.
237
- */
238
- declare class ResponseAccessor {
239
- /** The parsed JSON response body (null when parsing failed). */
240
- readonly body: unknown;
241
- /** @internal Ref-capture scope shared by the current spec execution. */
242
- readonly captures: CaptureScope;
243
- /** Response headers as a flat, lower-cased key-value map. */
244
- readonly headers: Record<string, string>;
245
- /** The HTTP response status code. */
246
- readonly status: number;
247
- /** @internal Test-file directory — fixture resolution root for matchers. */
248
- readonly testDir: string;
249
- constructor(response: ServerResponse, testDir: string, captures?: CaptureScope);
250
- }
251
- //#endregion
252
- //#region src/core/ports/database.port.d.ts
253
- /**
254
- * Abstract database interface for specification runners.
255
- * Implement this to plug in your database stack (e.g. Postgres, SQLite).
256
- */
257
- interface DatabasePort {
258
- /** Execute raw SQL (for seeding test data). */
259
- seed: (sql: string) => Promise<void>;
260
- /** Query a table and return rows as arrays of values. */
261
- query: (table: string, columns: string[]) => Promise<unknown[][]>;
262
- /** Reset database to clean state between tests. */
263
- reset: () => Promise<void>;
264
- }
265
- //#endregion
266
- //#region src/core/specification/shared/result/table.d.ts
267
- /**
268
- * Read-only accessor for a database table after a specification run.
269
- *
270
- * Assertions go through `expect()` (async — they query the database):
271
- *
272
- * await expect(result.table('users', { database: 'db' })).toMatchRows({
273
- * columns: ['name'],
274
- * rows: [['Alice']],
275
- * });
276
- * await expect(result.table('users', { database: 'db' })).toBeEmpty();
277
- */
278
- declare class TableAccessor {
279
- /** @internal Ref-capture scope shared by the current spec execution. */
280
- readonly captures: CaptureScope;
281
- /** @internal */
282
- private readonly db;
283
- /** The table name this accessor reads. */
284
- readonly name: string;
285
- constructor(name: string, db: DatabasePort, captures?: CaptureScope);
286
- /** @internal Query the table — used by the toMatchRows / toBeEmpty matchers. */
287
- query(columns: string[]): Promise<unknown[][]>;
288
- }
289
- //#endregion
290
- //#region src/core/specification/shared/result/text.d.ts
291
- /**
292
- * Read-only accessor for a captured text handle — THE universal one: stdout,
293
- * stderr, container logs, and file text all surface as a `TextAccessor`.
294
- *
295
- * Backed by a primitive string, exposed via {@link text}, but also provides
296
- * `toString()` / `valueOf()` for string coercion, so common patterns like
297
- * `String(result.stdout)` and template-literal interpolation still work.
298
- *
299
- * ANSI escape sequences are stripped by default before every comparison
300
- * (CONVENTIONS D6) — `.text` stays raw. The runner `transform` option remains
301
- * an escape hatch for application noise not covered by the `{{token}}`
302
- * grammar.
303
- *
304
- * Text operations are **closed** over the type: `.grep(pattern)` returns a
305
- * `TextAccessor` (not a bare string), preserving the `_expected/`-resolution
306
- * context and the `transform`, so results are chainable
307
- * (`result.stdout.grep(a).grep(b)`) and snapshot-able
308
- * (`expect(result.stdout.grep('users.ts')).toMatch('block.txt')`).
309
- *
310
- * Assertions go through `expect()` matchers: `expect(result.stdout).toContain(...)`
311
- * and `expect(result.stdout).toMatch('name.txt')` (resolved against
312
- * `_expected/<name>`, flat — with `{{token}}` support, CONVENTIONS D4).
313
- */
314
- declare class TextAccessor {
315
- /** @internal Ref-capture scope shared by the current spec execution. */
316
- readonly captures: CaptureScope;
317
- /** @internal Stream label used in failure messages ("stdout" / "stderr"). */
318
- readonly streamName: string;
319
- /** @internal Test-file directory — fixture resolution root for matchers. */
320
- readonly testDir: string;
321
- /** The raw captured text (never transformed, ANSI preserved). */
322
- readonly text: string;
323
- /** @internal Normaliser applied before comparisons, never to fixtures. */
324
- readonly transform?: (text: string) => string;
325
- constructor(value: string, streamName: string, testDir: string, options?: {
326
- captures?: CaptureScope;
327
- transform?: (text: string) => string;
328
- });
329
- /** @internal The text as compared by matchers — ANSI stripped, transform applied. */
330
- get comparableText(): string;
331
- /**
332
- * Keep only the blank-line-separated blocks of the text that contain
333
- * `pattern` (how linter/compiler output is structured), returned as a new
334
- * `TextAccessor` — the same subject type, so the result is chainable and
335
- * snapshot-able. The scalpel for probing large tool outputs; snapshot the
336
- * whole surface by default, reach for `.grep()` for targeted checks.
337
- */
338
- grep(pattern: string): TextAccessor;
339
- toString(): string;
340
- valueOf(): string;
341
- }
342
- /**
343
- * Wrap an arbitrary string into a {@link TextAccessor} anchored on the calling
344
- * test's directory — the same caller-detection the builders use.
345
- *
346
- * The product surface of a test framework is its own error messages, checker
347
- * output, and reports; those deserve the same goldening as any other output.
348
- * `text()` makes an ad-hoc string a first-class snapshot subject:
349
- *
350
- * ```typescript
351
- * const message = await catchMessage(() => expect(result.response).toMatch('wrong-body.http'));
352
- * expect(text(message)).toMatch('wrong-body-error.txt'); // resolves to _expected/
353
- * ```
354
- *
355
- * ANSI is stripped before every comparison (the raw form stays on `.text`),
356
- * the `{{token}}` grammar applies to the fixture, and `.grep()` composition
357
- * works exactly as on any stream accessor.
358
- */
359
- declare function text(value: string): TextAccessor;
360
- //#endregion
361
- //#region src/core/ports/cli.port.d.ts
362
- /**
363
- * Raw output from a command execution, including exit code and captured output streams.
364
- */
365
- interface CliOutput {
366
- /** Process exit code (0 = success). */
367
- exitCode: number;
368
- /** Captured standard output. */
369
- stdout: string;
370
- /** Captured standard error. */
371
- stderr: string;
372
- }
373
- /**
374
- * Options for the long-running form of `.exec()` (CONVENTIONS B2). When
375
- * either option is present the process is spawned and observed: it resolves
376
- * as soon as `waitFor` appears in stdout/stderr, and is killed when `timeout`
377
- * elapses (exit code 124).
378
- */
379
- interface ExecOptions {
380
- /** Resolve (exit code 0) when stdout/stderr contains this string. */
381
- waitFor?: string;
382
- /** Kill the process after this many milliseconds. Defaults to 10 000. */
383
- timeout?: number;
384
- }
385
- /**
386
- * Extra environment variables to set for the child process.
387
- * Values are merged on top of process.env. A `null` value unsets the variable.
388
- */
389
- type CliEnv = Record<string, null | string>;
390
- /**
391
- * Abstract command interface for specification runners.
392
- * Implement this to plug in your command execution strategy.
393
- */
394
- /**
395
- * What one invocation is handed beyond its arguments — the two per-run inputs a
396
- * spec document may state. Both are absent by default: the child gets an
397
- * immediately-closed stdin (never a TTY) and the adapter's own timeout.
398
- */
399
- interface CliInput {
400
- /** Written to the child's stdin, which is then closed. */
401
- stdin?: string;
402
- /** Milliseconds after which the run is killed (exit code 124). */
403
- timeout?: number;
404
- }
405
- interface CliPort {
406
- /** Execute a command with the given arguments in the given working directory. */
407
- exec: (args: string, cwd: string, env?: CliEnv, input?: CliInput) => Promise<CliOutput>;
408
- /** Run a long-running process and wait for a pattern or timeout. */
409
- watch: (args: string, cwd: string, options: ExecOptions, env?: CliEnv) => Promise<CliOutput>;
410
- }
411
- //#endregion
412
- //#region src/core/contracts/types.d.ts
115
+ //#region src/specification/contracts/types.d.ts
413
116
  /**
414
117
  * The observed outgoing request, reduced to what contract matchers inspect.
415
118
  * Built once per request by the engine (MSW on api/jobs, the stub backend on
416
119
  * website/mobile) and handed to {@link ContractRequest.match} and to a
417
120
  * {@link ContractResponder}.
418
121
  */
419
- interface MatchableRequest {
122
+ type MatchableRequest = {
420
123
  /** Parsed JSON body when the payload is JSON, the raw text otherwise, or `null` when absent. */
421
124
  body: unknown;
422
125
  /** Request headers, keyed by lowercased header name. */
@@ -425,7 +128,7 @@ interface MatchableRequest {
425
128
  method: string;
426
129
  /** The request URL — fully-qualified, or origin-relative for the stub backend. */
427
130
  url: string;
428
- }
131
+ };
429
132
  /**
430
133
  * The request half of a contract: which outgoing call it speaks for.
431
134
  *
@@ -434,7 +137,7 @@ interface MatchableRequest {
434
137
  * website/mobile app's own backend calls take. `{{token}}` segments compare
435
138
  * structurally, declared query params are a subset of the observed ones.
436
139
  */
437
- interface ContractRequest {
140
+ type ContractRequest = {
438
141
  /** Adapter name — `http` | `openai` | `anthropic`. */
439
142
  adapter: string;
440
143
  /** HTTP method to match. `*` matches any method. */
@@ -442,23 +145,23 @@ interface ContractRequest {
442
145
  /** Absolute URL (string | RegExp) or an any-origin path form (`/articles/{{uuid}}`). */
443
146
  url: RegExp | string;
444
147
  /** Optional request matcher — the contract only fires if this returns true. */
445
- match?: (request: MatchableRequest) => boolean;
148
+ match?: ((request: MatchableRequest) => boolean) | undefined;
446
149
  /** Transform raw data into a provider-specific response envelope. */
447
150
  wrap: (data: unknown) => ContractResponse;
448
- }
151
+ };
449
152
  /**
450
153
  * The response half of a contract: what to reply when the request matches.
451
154
  */
452
- interface ContractResponse {
155
+ type ContractResponse = {
453
156
  /** HTTP status code (default: 200). */
454
157
  status?: number;
455
158
  /** Response body — an object is JSON, a string is text, `null`/`undefined` is empty. */
456
159
  body: unknown;
457
160
  /** Response headers. */
458
- headers?: Record<string, string>;
161
+ headers?: Record<string, string> | undefined;
459
162
  /** Delay in ms before responding (for timeout testing). */
460
- delay?: number;
461
- }
163
+ delay?: number | undefined;
164
+ };
462
165
  /**
463
166
  * A dynamic response: computed from the observed request at the moment the
464
167
  * contract is served, rather than fixed ahead of time. Handed the same
@@ -472,7 +175,7 @@ type ContractResponder = (request: MatchableRequest) => ContractResponse;
472
175
  */
473
176
  type ContractResponseValue = ContractResponder | ContractResponse;
474
177
  //#endregion
475
- //#region src/core/contracts/contract.d.ts
178
+ //#region src/specification/contracts/contract.d.ts
476
179
  /**
477
180
  * A declared external interaction: what to match (`request`) and what to reply
478
181
  * (`response`), together in one named artifact. Contracts live in TypeScript
@@ -480,7 +183,7 @@ type ContractResponseValue = ContractResponder | ContractResponse;
480
183
  * payload (prompts, JSON responses) is visible at a glance while the real HTTP
481
184
  * call stays mocked underneath.
482
185
  */
483
- interface Contract {
186
+ type Contract = {
484
187
  /** Which outgoing call this contract speaks for. */
485
188
  request: ContractRequest;
486
189
  /**
@@ -501,12 +204,12 @@ interface Contract {
501
204
  * silently-unused declaration into a spec failure.
502
205
  */
503
206
  required?: boolean;
504
- }
207
+ };
505
208
  /**
506
209
  * A composite of contracts — the unit tests import. Flat, ordered, immutable;
507
210
  * `.with()` derives a variant without touching the original.
508
211
  */
509
- interface Contracts {
212
+ type Contracts = {
510
213
  /** The flattened contracts, in selection order. */
511
214
  readonly contracts: readonly Contract[];
512
215
  /**
@@ -516,7 +219,7 @@ interface Contracts {
516
219
  * route it does not replace.
517
220
  */
518
221
  with: (...overrides: (Contract | Contract[] | Contracts)[]) => Contracts;
519
- }
222
+ };
520
223
  /** Any accepted contract input: one, a list, or a composite. */
521
224
  type ContractInput = Contract | Contract[] | Contracts;
522
225
  /**
@@ -557,7 +260,7 @@ declare function defineContract(contract: Contract): Contract;
557
260
  */
558
261
  declare function defineContracts(...items: ContractInput[]): Contracts;
559
262
  //#endregion
560
- //#region src/core/ports/browser.port.d.ts
263
+ //#region src/specification/ports/browser.port.d.ts
561
264
  /**
562
265
  * The landmark roles — the standard set of page regions, and the only
563
266
  * containers a scope can name. Closed on purpose: ARIA defines exactly these,
@@ -575,7 +278,7 @@ type ElementKind = 'button' | 'field' | 'heading' | 'link' | 'testId' | 'text';
575
278
  * A descriptor must designate exactly ONE element at action time; see
576
279
  * {@link ElementMatch} and CONVENTIONS W3.
577
280
  */
578
- interface ElementRef {
281
+ type ElementRef = {
579
282
  /**
580
283
  * Match the accessible name as a whole string rather than a substring.
581
284
  * Default (`false`) mirrors playwright: `link('Articles')` also matches
@@ -590,47 +293,47 @@ interface ElementRef {
590
293
  * `within(scope, target)`. Chains: a scope may itself carry a scope.
591
294
  */
592
295
  scope?: ElementRef;
593
- }
296
+ };
594
297
  /**
595
298
  * One candidate captured when a descriptor matched more than one element —
596
299
  * the evidence the ambiguity error enumerates so the author can disambiguate
597
300
  * without opening a browser.
598
301
  */
599
- interface ElementMatch {
302
+ type ElementMatch = {
600
303
  /** Nearest landmark ancestor (`nav`, `footer`, `main`…), when there is one. */
601
- context?: string;
304
+ context?: string | undefined;
602
305
  /** The attribute that disambiguates most — `href` for links, `name` for fields. */
603
- detail?: string;
306
+ detail?: string | undefined;
604
307
  /** Tag name, lower-cased. */
605
308
  tag: string;
606
309
  /** Text content, whitespace-collapsed and truncated. */
607
310
  text: string;
608
- }
311
+ };
609
312
  /** A `<link>` element captured from the rendered document's head. */
610
- interface BrowserLinkElement {
313
+ type BrowserLinkElement = {
611
314
  href: string;
612
- hreflang?: string;
315
+ hreflang?: string | undefined;
613
316
  rel: string;
614
- type?: string;
615
- }
317
+ type?: string | undefined;
318
+ };
616
319
  /** A `<meta>` element captured from the rendered document's head. */
617
- interface BrowserMetaElement {
320
+ type BrowserMetaElement = {
618
321
  content: string;
619
- name?: string;
620
- property?: string;
621
- }
322
+ name?: string | undefined;
323
+ property?: string | undefined;
324
+ };
622
325
  /** A console message emitted while the page loaded or the scenario ran. */
623
- interface BrowserConsoleMessage {
326
+ type BrowserConsoleMessage = {
624
327
  text: string;
625
328
  type: string;
626
- }
329
+ };
627
330
  /**
628
331
  * The visitor — the interaction vocabulary handed to a visit scenario.
629
332
  * Every action auto-waits (playwright actionability); `see()` is the single
630
333
  * synchronization primitive: it retries until the element is visible and
631
334
  * fails at the timeout. There is no sleep and no conditional helper.
632
335
  */
633
- interface Visitor {
336
+ type Visitor = {
634
337
  /** Check a checkbox or radio. */
635
338
  check: (element: ElementRef) => Promise<void>;
636
339
  /** Click the element. */
@@ -647,16 +350,16 @@ interface Visitor {
647
350
  see: (element: ElementRef) => Promise<void>;
648
351
  /** Select an option in a select field. */
649
352
  select: (element: ElementRef, option: string) => Promise<void>;
650
- }
353
+ };
651
354
  /** The behavior of a visit — the When of the spec; assertions stay in the Then. */
652
355
  type VisitScenario = (visitor: Visitor) => Promise<void>;
653
356
  /** Per-visit options forwarded to the browser context. */
654
- interface BrowserOpenOptions {
357
+ type BrowserOpenOptions = {
655
358
  /**
656
359
  * Extra origins the `external: 'block'` policy lets through — the
657
360
  * declared stub backend the page legitimately fetches from.
658
361
  */
659
- allowedOrigins?: string[];
362
+ allowedOrigins?: string[] | undefined;
660
363
  /**
661
364
  * Base URL of the site under test — the origin `goto()` resolves against
662
365
  * and the boundary of the `external` policy.
@@ -669,16 +372,16 @@ interface BrowserOpenOptions {
669
372
  */
670
373
  external: 'allow' | 'block';
671
374
  /** Extra HTTP headers sent with every request of the visit (incl. User-Agent overrides). */
672
- headers?: Record<string, string>;
375
+ headers?: Record<string, string> | undefined;
673
376
  /** The interaction scenario to run after load; the capture reflects the final state. */
674
- scenario?: VisitScenario;
675
- }
377
+ scenario?: undefined | VisitScenario;
378
+ };
676
379
  /**
677
380
  * The rendered page captured by a browser visit — the FINAL state when a
678
381
  * scenario ran. Extraction happens in-page (the browser IS the HTML
679
382
  * parser); interpretation of the raw elements belongs to core.
680
383
  */
681
- interface BrowserPage {
384
+ type BrowserPage = {
682
385
  /** Console messages emitted while loading and interacting, in order. */
683
386
  consoleMessages: BrowserConsoleMessage[];
684
387
  /** Serialized DOM after rendering (`document.documentElement.outerHTML`). */
@@ -697,20 +400,85 @@ interface BrowserPage {
697
400
  title: string;
698
401
  /** Final URL after redirects and scenario navigation. */
699
402
  url: string;
700
- }
403
+ };
701
404
  /**
702
405
  * Abstract browser interface for the website specification runner.
703
406
  * One implementation lives in `integrations/playwright/` — a single shared
704
407
  * browser instance per runner; each `open()` gets a fresh, isolated context.
705
408
  */
706
- interface BrowserPort {
409
+ type BrowserPort = {
707
410
  /** Close the shared browser instance (idempotent). */
708
411
  close: () => Promise<void>;
709
412
  /** Load `url` in a fresh context, run the scenario, capture the final page. */
710
413
  open: (url: string, options: BrowserOpenOptions) => Promise<BrowserPage>;
711
- }
414
+ };
712
415
  //#endregion
713
- //#region src/core/ports/device.port.d.ts
416
+ //#region src/specification/ports/cli.port.d.ts
417
+ /**
418
+ * Raw output from a command execution, including exit code and captured output streams.
419
+ */
420
+ type CliOutput = {
421
+ /** Process exit code (0 = success). */
422
+ exitCode: number;
423
+ /** Captured standard output. */
424
+ stdout: string;
425
+ /** Captured standard error. */
426
+ stderr: string;
427
+ };
428
+ /**
429
+ * Options for the long-running form of `.exec()` (CONVENTIONS B2). When
430
+ * either option is present the process is spawned and observed: it resolves
431
+ * as soon as `waitFor` appears in stdout/stderr, and is killed when `timeout`
432
+ * elapses (exit code 124).
433
+ */
434
+ type ExecOptions = {
435
+ /** Resolve (exit code 0) when stdout/stderr contains this string. */
436
+ waitFor?: string | undefined;
437
+ /** Kill the process after this many milliseconds. Defaults to 10 000. */
438
+ timeout?: number | undefined;
439
+ };
440
+ /**
441
+ * Extra environment variables to set for the child process.
442
+ * Values are merged on top of process.env. A `null` value unsets the variable.
443
+ */
444
+ type CliEnv = Record<string, null | string>;
445
+ /**
446
+ * Abstract command interface for specification runners.
447
+ * Implement this to plug in your command execution strategy.
448
+ */
449
+ /**
450
+ * What one invocation is handed beyond its arguments — the two per-run inputs a
451
+ * spec document may state. Both are absent by default: the child gets an
452
+ * immediately-closed stdin (never a TTY) and the adapter's own timeout.
453
+ */
454
+ type CliInput = {
455
+ /** Written to the child's stdin, which is then closed. */
456
+ stdin?: string | undefined;
457
+ /** Milliseconds after which the run is killed (exit code 124). */
458
+ timeout?: number | undefined;
459
+ };
460
+ type CliPort = {
461
+ /** Execute a command with the given arguments in the given working directory. */
462
+ exec: (args: string, cwd: string, env?: CliEnv, input?: CliInput) => Promise<CliOutput>;
463
+ /** Run a long-running process and wait for a pattern or timeout. */
464
+ watch: (args: string, cwd: string, options: ExecOptions, env?: CliEnv) => Promise<CliOutput>;
465
+ };
466
+ //#endregion
467
+ //#region src/specification/ports/database.port.d.ts
468
+ /**
469
+ * Abstract database interface for specification runners.
470
+ * Implement this to plug in your database stack (e.g. Postgres, SQLite).
471
+ */
472
+ type DatabasePort = {
473
+ /** Execute raw SQL (for seeding test data). */
474
+ seed: (sql: string) => Promise<void>;
475
+ /** Query a table and return rows as arrays of values. */
476
+ query: (table: string, columns: string[]) => Promise<unknown[][]>;
477
+ /** Reset database to clean state between tests. */
478
+ reset: () => Promise<void>;
479
+ };
480
+ //#endregion
481
+ //#region src/specification/ports/device.port.d.ts
714
482
  /**
715
483
  * The element kinds a mobile screen can designate — the structural subset of
716
484
  * {@link ElementRef} kinds that map onto the XCUITest accessibility tree.
@@ -738,7 +506,7 @@ type MobileElementRef = ElementRef;
738
506
  * the evidence the ambiguity error enumerates so the author can disambiguate
739
507
  * without opening the simulator.
740
508
  */
741
- interface MobileElementMatch {
509
+ type MobileElementMatch = {
742
510
  /** The accessibility identifier (`testId()` target), when one is set. */
743
511
  identifier?: string;
744
512
  /** The accessible label, whitespace-collapsed and truncated. */
@@ -747,7 +515,7 @@ interface MobileElementMatch {
747
515
  type: string;
748
516
  /** The element value (text-field content, adjustable value), when present. */
749
517
  value?: string;
750
- }
518
+ };
751
519
  /**
752
520
  * The visitor — the interaction vocabulary handed to a mobile scenario.
753
521
  * Every verb auto-waits by polling until a visible match exists; acting
@@ -755,14 +523,14 @@ interface MobileElementMatch {
755
523
  * synchronization primitive — is satisfied by any visible match. There is
756
524
  * no sleep and no conditional helper.
757
525
  */
758
- interface MobileVisitor {
526
+ type MobileVisitor = {
759
527
  /** Fill a text field with a value. */
760
528
  fill: (element: MobileElementRef, value: string) => Promise<void>;
761
529
  /** Wait until the element is visible — the only synchronization primitive. */
762
530
  see: (element: MobileElementRef) => Promise<void>;
763
531
  /** Tap the element. */
764
532
  tap: (element: MobileElementRef) => Promise<void>;
765
- }
533
+ };
766
534
  /** The behavior of an open — the When of the spec; assertions stay in the Then. */
767
535
  type MobileScenario = (visitor: MobileVisitor) => Promise<void>;
768
536
  /**
@@ -771,7 +539,7 @@ type MobileScenario = (visitor: MobileVisitor) => Promise<void>;
771
539
  * nodes are dropped and their children hoisted, so the projection stays
772
540
  * stable and golden-friendly. Type names lose the `XCUIElementType` prefix.
773
541
  */
774
- interface ScreenNode {
542
+ type ScreenNode = {
775
543
  children?: ScreenNode[];
776
544
  /** The accessibility identifier, only when it differs from the label. */
777
545
  identifier?: string;
@@ -779,57 +547,78 @@ interface ScreenNode {
779
547
  /** Element type without the `XCUIElementType` prefix — `Button`, `StaticText`. */
780
548
  type: string;
781
549
  value?: string;
782
- }
550
+ };
783
551
  /**
784
552
  * The screen captured by a device open — the FINAL state when a scenario
785
553
  * ran. The tree is the projected page source; `texts` are the visible
786
554
  * labels/values in document order, consecutive duplicates collapsed.
787
555
  */
788
- interface DeviceScreen {
556
+ type DeviceScreen = {
789
557
  texts: string[];
790
558
  tree: ScreenNode;
791
- }
559
+ };
792
560
  /** Per-open options forwarded to the device session. */
793
- interface DeviceOpenOptions {
561
+ type DeviceOpenOptions = {
794
562
  /** The app under test — terminated and relaunched for a fresh, deterministic state. */
795
563
  bundleId: string;
796
564
  /** Deep link applied after the relaunch (`news://events`); absent opens the app plainly. */
797
- deepLink?: string;
565
+ deepLink?: string | undefined;
798
566
  /** The interaction scenario to run after launch; the capture reflects the final state. */
799
- scenario?: MobileScenario;
800
- }
567
+ scenario?: MobileScenario | undefined;
568
+ };
801
569
  /**
802
570
  * How long a device session waits, in milliseconds. Every field is optional
803
571
  * and falls back to the framework's own default, so a runner states only the
804
572
  * wait it needs to move — the defaults suit a release build, and a project
805
573
  * that drives a DEV build pays for its bundler's cold boot.
806
574
  */
807
- interface DeviceTimeouts {
575
+ type DeviceTimeouts = {
808
576
  /**
809
577
  * How long every verb polls for a visible match before refusing.
810
578
  * Default: `30_000` — enough for a cold app boot on a release bundle.
811
579
  */
812
- action?: number;
580
+ action?: number | undefined;
813
581
  /**
814
582
  * How long the FIRST session waits for WebDriverAgent to build and
815
583
  * launch on the simulator. Default: `240_000`.
816
584
  */
817
- launch?: number;
818
- }
585
+ launch?: number | undefined;
586
+ };
819
587
  /**
820
588
  * Abstract device interface for the mobile specification runner.
821
589
  * One implementation lives in `integrations/appium/` — a single driver
822
590
  * session per runner, created on the first `open()` and reused; each open
823
591
  * terminates and relaunches the app for a deterministic fresh state.
824
592
  */
825
- interface DevicePort {
593
+ type DevicePort = {
826
594
  /** End the driver session (idempotent). */
827
595
  close: () => Promise<void>;
828
596
  /** Relaunch the app, apply the deep link, run the scenario, capture the final screen. */
829
597
  open: (options: DeviceOpenOptions) => Promise<DeviceScreen>;
830
- }
598
+ };
599
+ //#endregion
600
+ //#region src/specification/ports/server.port.d.ts
601
+ /**
602
+ * HTTP response returned by a server port, with parsed JSON body.
603
+ */
604
+ type ServerResponse = {
605
+ /** HTTP status code. */
606
+ status: number;
607
+ /** Parsed JSON response body, or null if parsing failed. */
608
+ body: unknown;
609
+ /** Response headers as a flat key-value map. */
610
+ headers: Record<string, string>;
611
+ };
612
+ /**
613
+ * Abstract server interface for specification runners.
614
+ * Integration mode uses an in-process Hono app; E2E mode uses real HTTP via fetch.
615
+ */
616
+ type ServerPort = {
617
+ /** Send an HTTP request and return the parsed response. */
618
+ request: (method: string, path: string, body?: unknown, headers?: Record<string, string>) => Promise<ServerResponse>;
619
+ };
831
620
  //#endregion
832
- //#region src/core/ports/isolation.port.d.ts
621
+ //#region src/specification/ports/isolation.port.d.ts
833
622
  /**
834
623
  * Strategy for isolating service state across parallel test workers.
835
624
  *
@@ -840,7 +629,7 @@ interface DevicePort {
840
629
  * Implement this interface to support new service types (e.g. MongoDB,
841
630
  * Elasticsearch, S3).
842
631
  */
843
- interface IsolationStrategy {
632
+ type IsolationStrategy = {
844
633
  /**
845
634
  * Create an isolated namespace for this worker.
846
635
  * Called once when the worker starts — e.g. clone a template database,
@@ -859,14 +648,14 @@ interface IsolationStrategy {
859
648
  * Called once when the worker shuts down — e.g. drop the cloned database.
860
649
  */
861
650
  release: () => Promise<void>;
862
- }
651
+ };
863
652
  //#endregion
864
- //#region src/core/ports/service.port.d.ts
653
+ //#region src/specification/ports/service.port.d.ts
865
654
  /**
866
655
  * A service handle — returned by factory functions like postgres(), redis().
867
656
  * Mutable: connectionString is populated after the orchestrator starts containers.
868
657
  */
869
- interface ServiceHandle {
658
+ type ServiceHandle = {
870
659
  /** Service type identifier. */
871
660
  readonly type: string;
872
661
  /**
@@ -909,63 +698,200 @@ interface ServiceHandle {
909
698
  reset: () => Promise<void>;
910
699
  /** Get the isolation strategy for parallel test execution. */
911
700
  isolation: () => IsolationStrategy;
701
+ };
702
+ //#endregion
703
+ //#region src/specification/facets/_common/result/response.d.ts
704
+ /**
705
+ * Read-only accessor for an HTTP response.
706
+ *
707
+ * Assertions go through `expect()`:
708
+ * `expect(result.response).toMatch('created.http')` compares status,
709
+ * a subset of headers, and the JSON body against `_expected/<name>` —
710
+ * with `{{placeholder}}` support in all three.
711
+ */
712
+ declare class ResponseAccessor {
713
+ /** The parsed JSON response body (null when parsing failed). */
714
+ readonly body: unknown;
715
+ /** @internal Ref-capture scope shared by the current spec execution. */
716
+ readonly captures: CaptureScope;
717
+ /** Response headers as a flat, lower-cased key-value map. */
718
+ readonly headers: Record<string, string>;
719
+ /** The HTTP response status code. */
720
+ readonly status: number;
721
+ /** @internal Test-file directory — fixture resolution root for matchers. */
722
+ readonly testDir: string;
723
+ constructor(response: ServerResponse, testDir: string, captures?: CaptureScope);
912
724
  }
913
725
  //#endregion
914
- //#region src/core/specification/shared/result/result.d.ts
915
- /** Read-only handle to a single file produced by a spec action. */
916
- interface FileAccessor {
917
- /** The UTF-8 text content. Throws if the file does not exist. */
918
- readonly content: string;
919
- readonly exists: boolean;
920
- /** The file text as a {@link TextAccessor}, keeping only blocks matching `pattern`. */
921
- grep: (pattern: string) => TextAccessor;
726
+ //#region src/specification/facets/api/result.d.ts
727
+ /** Result from an HTTP action (.request(), .get(), .post(), .put(), .delete()). */
728
+ declare class HttpResult extends BaseResult {
729
+ private readonly responseData;
730
+ constructor(options: {
731
+ config: SpecificationConfig;
732
+ response: ServerResponse;
733
+ testDir: string;
734
+ });
735
+ /** Access the HTTP response (status, headers, body) for assertions. */
736
+ get response(): ResponseAccessor;
737
+ /** The HTTP response status code. */
738
+ get status(): number;
922
739
  }
923
- interface BaseResultOptions {
924
- config: SpecificationConfig;
925
- testDir: string;
926
- workDir?: string;
740
+ //#endregion
741
+ //#region src/specification/facets/_common/result/filesystem.d.ts
742
+ /**
743
+ * Read-only accessor for the whole temporary working directory used by a
744
+ * command spec.
745
+ *
746
+ * Assertions go through `expect()` (async — they walk the disk):
747
+ * `await expect(result.filesystem).toMatch('scaffolded')` compares the
748
+ * tree against the fixture directory `_expected/<name>/`.
749
+ */
750
+ declare class FilesystemAccessor {
751
+ /** @internal Ref-capture scope shared by the current spec execution. */
752
+ readonly captures: CaptureScope;
753
+ /** The absolute path of the temporary working directory. */
754
+ readonly cwd: string;
755
+ /** @internal Test-file directory — fixture resolution root for matchers. */
756
+ readonly testDir: string;
757
+ constructor(cwd: string, testDir: string, captures?: CaptureScope);
758
+ /** List all files (recursively) under the working directory, sorted. */
759
+ files(options?: {
760
+ ignore?: string[];
761
+ }): Promise<string[]>;
927
762
  }
763
+ //#endregion
764
+ //#region src/specification/facets/_common/result/text.d.ts
928
765
  /**
929
- * Base result - common accessors available after any action type.
930
- * Extended by HttpResult, CliResult, and used directly by job results.
766
+ * Read-only accessor for a captured text handle — THE universal one: stdout,
767
+ * stderr, container logs, and file text all surface as a `TextAccessor`.
931
768
  *
932
- * Every result carries a fresh {@link CaptureScope}: `match.ref()` captures
933
- * and `{{type#ref}}` placeholders are scoped to one spec execution.
769
+ * Backed by a primitive string, exposed via {@link text}, but also provides
770
+ * `toString()` / `valueOf()` for string coercion, so common patterns like
771
+ * `String(result.stdout)` and template-literal interpolation still work.
772
+ *
773
+ * ANSI escape sequences are stripped by default before every comparison
774
+ * (CONVENTIONS D6) — `.text` stays raw. The runner `transform` option remains
775
+ * an escape hatch for application noise not covered by the `{{token}}`
776
+ * grammar.
777
+ *
778
+ * Text operations are **closed** over the type: `.grep(pattern)` returns a
779
+ * `TextAccessor` (not a bare string), preserving the `_expected/`-resolution
780
+ * context and the `transform`, so results are chainable
781
+ * (`result.stdout.grep(a).grep(b)`) and snapshot-able
782
+ * (`expect(result.stdout.grep('users.ts')).toMatch('block.txt')`).
783
+ *
784
+ * Assertions go through `expect()` matchers: `expect(result.stdout).toContain(...)`
785
+ * and `expect(result.stdout).toMatch('name.txt')` (resolved against
786
+ * `_expected/<name>`, flat — with `{{token}}` support, CONVENTIONS D4).
934
787
  */
935
- declare class BaseResult {
936
- /** @internal Ref-capture scope shared by every assertion on this result. */
788
+ declare class TextAccessor {
789
+ /** @internal Ref-capture scope shared by the current spec execution. */
937
790
  readonly captures: CaptureScope;
938
- protected config: SpecificationConfig;
939
- protected testDir: string;
940
- protected workDir?: string;
941
- constructor(options: BaseResultOptions);
942
- /** Access a directory (relative to the working directory) for snapshot assertions. */
943
- directory(path?: string): DirectoryAccessor;
944
- /** Access a single file (relative to the working directory) for content assertions. */
945
- file(path: string): FileAccessor;
946
- /** Access a database table for row-level assertions via expect() matchers. */
947
- table(tableName: string, options?: {
948
- database?: string;
949
- }): TableAccessor;
950
- private resolveDatabase;
791
+ /** @internal Stream label used in failure messages ("stdout" / "stderr"). */
792
+ readonly streamName: string;
793
+ /** @internal Test-file directory — fixture resolution root for matchers. */
794
+ readonly testDir: string;
795
+ /** The raw captured text (never transformed, ANSI preserved). */
796
+ readonly text: string;
797
+ /** @internal Normaliser applied before comparisons, never to fixtures. */
798
+ readonly transform?: ((text: string) => string) | undefined;
799
+ constructor(value: string, streamName: string, testDir: string, options?: {
800
+ captures?: CaptureScope | undefined;
801
+ transform?: ((text: string) => string) | undefined;
802
+ });
803
+ /** @internal The text as compared by matchers — ANSI stripped, transform applied. */
804
+ get comparableText(): string;
805
+ /**
806
+ * Keep only the blank-line-separated blocks of the text that contain
807
+ * `pattern` (how linter/compiler output is structured), returned as a new
808
+ * `TextAccessor` — the same subject type, so the result is chainable and
809
+ * snapshot-able. The scalpel for probing large tool outputs; snapshot the
810
+ * whole surface by default, reach for `.grep()` for targeted checks.
811
+ */
812
+ grep(pattern: string): TextAccessor;
813
+ toString(): string;
814
+ valueOf(): string;
951
815
  }
816
+ /**
817
+ * Wrap an arbitrary string into a {@link TextAccessor} anchored on the calling
818
+ * test's directory — the same caller-detection the builders use.
819
+ *
820
+ * The product surface of a test framework is its own error messages, checker
821
+ * output, and reports; those deserve the same goldening as any other output.
822
+ * `text()` makes an ad-hoc string a first-class snapshot subject:
823
+ *
824
+ * ```typescript
825
+ * const message = await catchMessage(() => expect(result.response).toMatch('wrong-body.http'));
826
+ * expect(text(message)).toMatch('wrong-body-error.txt'); // resolves to _expected/
827
+ * ```
828
+ *
829
+ * ANSI is stripped before every comparison (the raw form stays on `.text`),
830
+ * the `{{token}}` grammar applies to the fixture, and `.grep()` composition
831
+ * works exactly as on any stream accessor.
832
+ */
833
+ declare function text(value: string): TextAccessor;
952
834
  //#endregion
953
- //#region src/core/specification/api/result.d.ts
954
- /** Result from an HTTP action (.request(), .get(), .post(), .put(), .delete()). */
955
- declare class HttpResult extends BaseResult {
956
- private responseData;
835
+ //#region src/specification/facets/cli/result.d.ts
836
+ /**
837
+ * Result from a command action (`.exec()`).
838
+ *
839
+ * When the runner was configured with a `docker` option, the result also
840
+ * exposes container accessors and participates in `Symbol.asyncDispose`
841
+ * cleanup. The Docker shell-outs are **lazy**: a test that never calls
842
+ * `.container(...)` never queries the Docker daemon, so command-only tests
843
+ * pay zero Docker cost even when the runner is Docker-aware.
844
+ *
845
+ * Dispose always runs one final label-filtered `docker ps` to catch
846
+ * containers that were spawned during `.exec` but never asserted on —
847
+ * tests still get leak-free cleanup even if they forget to reach into
848
+ * a container.
849
+ */
850
+ declare class CliResult extends BaseResult {
851
+ private readonly commandOutput;
852
+ private containersCache;
853
+ private readonly dockerConfig?;
854
+ private readonly testRunId?;
855
+ private readonly transform?;
957
856
  constructor(options: {
857
+ commandOutput: CliOutput;
958
858
  config: SpecificationConfig;
959
- response: ServerResponse;
859
+ dockerConfig?: DockerSpecConfig | undefined;
960
860
  testDir: string;
861
+ testRunId?: string | undefined;
862
+ transform?: ((text: string) => string) | undefined;
863
+ workDir: string;
961
864
  });
962
- /** Access the HTTP response (status, headers, body) for assertions. */
963
- get response(): ResponseAccessor;
964
- /** The HTTP response status code. */
965
- get status(): number;
865
+ /** The process exit code. */
866
+ get exitCode(): number;
867
+ /** Accessor for the captured standard output with file-based assertions. */
868
+ get stdout(): TextAccessor;
869
+ /** Accessor for the captured standard error with file-based assertions. */
870
+ get stderr(): TextAccessor;
871
+ /** Accessor for parsing stdout as JSON and asserting against JSON fixtures. */
872
+ get json(): JsonAccessor;
873
+ /** Accessor for the temporary working directory the command ran in. */
874
+ get filesystem(): FilesystemAccessor;
875
+ /**
876
+ * Look up a container the command spawned during this run by the value of
877
+ * its name-label (as declared in the `docker.nameLabel` of the
878
+ * `specification.cli()` options).
879
+ * First access triggers a one-shot `docker ps` + `docker inspect`
880
+ * query and caches the result for the rest of the result's lifetime.
881
+ * Tests that don't call this never touch Docker.
882
+ *
883
+ * Returns an accessor with `.exists === false` when no container
884
+ * carries that name — callers can still assert absence without a try.
885
+ */
886
+ container(name: string): ContainerAccessor;
887
+ /** All captured container IDs. Triggers the same lazy query as `container()`. */
888
+ get containerIds(): string[];
889
+ [Symbol.asyncDispose](): Promise<void>;
890
+ private ensureDockerAware;
891
+ private loadContainers;
966
892
  }
967
893
  //#endregion
968
- //#region src/core/specification/cli/literate.d.ts
894
+ //#region src/specification/facets/cli/literate.d.ts
969
895
  /**
970
896
  * Running a `<case>.spec.yaml` document: one scenario, one working directory,
971
897
  * one set of servers, every run executed and asserted.
@@ -983,7 +909,7 @@ declare class HttpResult extends BaseResult {
983
909
  * A server a document may start by name (`serve: mcp`), registered once per app
984
910
  * in the `serve` option of `specification.cli()`.
985
911
  */
986
- interface LiterateServeRegistration {
912
+ type LiterateServeRegistration = {
987
913
  /** Shell command that starts the server, run from the project root. */
988
914
  command: string;
989
915
  /** The variable the resolved URL is bound to in every run's child env. */
@@ -995,9 +921,9 @@ interface LiterateServeRegistration {
995
921
  ready: RegExp;
996
922
  /** Builds the URL bound to {@link env} from the announced port. */
997
923
  url: (port: number) => string;
998
- }
924
+ };
999
925
  /** Per-call options for {@link runSpecDocument} / `cli.run()`. */
1000
- interface LiterateRunFlags {
926
+ type LiterateRunFlags = {
1001
927
  /**
1002
928
  * Opt this document OUT of the update-mode rewrite. A frozen document is
1003
929
  * NEVER written under `TEST_UPDATE=1`: its mismatch still throws its diff.
@@ -1006,10 +932,10 @@ interface LiterateRunFlags {
1006
932
  * instead of being silently corrected into a passing file. The document's
1007
933
  * mirror of `toMatch(name, { frozen: true })`.
1008
934
  */
1009
- frozen?: boolean;
1010
- }
935
+ frozen?: boolean | undefined;
936
+ };
1011
937
  //#endregion
1012
- //#region src/core/specification/mobile/result.d.ts
938
+ //#region src/specification/facets/mobile/result.d.ts
1013
939
  /** Result from a `.open()` action — the screen as the device saw it, final state. */
1014
940
  declare class ScreenResult extends BaseResult {
1015
941
  private readonly capturedScreen;
@@ -1030,13 +956,13 @@ declare class ScreenResult extends BaseResult {
1030
956
  get screen(): JsonAccessor;
1031
957
  }
1032
958
  //#endregion
1033
- //#region src/core/specification/website/result.d.ts
959
+ //#region src/specification/facets/website/result.d.ts
1034
960
  /** A raw HTTP exchange captured by `.fetch()` — redirects are NOT followed. */
1035
- interface FetchExchange {
961
+ type FetchExchange = {
1036
962
  body: string;
1037
963
  headers: Record<string, string>;
1038
964
  status: number;
1039
- }
965
+ };
1040
966
  /** Result from a raw `.fetch()` action (robots.txt, sitemaps, redirects). */
1041
967
  declare class FetchResult extends BaseResult {
1042
968
  private readonly exchange;
@@ -1107,15 +1033,15 @@ declare class PageResult extends BaseResult {
1107
1033
  get url(): string;
1108
1034
  }
1109
1035
  //#endregion
1110
- //#region src/core/specification/shared/stub-backend.d.ts
1036
+ //#region src/specification/facets/_common/stub-backend.d.ts
1111
1037
  /** Options for the stub backend server. */
1112
- interface StubBackendOptions {
1038
+ type StubBackendOptions = {
1113
1039
  /**
1114
1040
  * Fixed port — pins a stable stub URL across runs (a bundler that inlines
1115
1041
  * the URL at serve time survives warm). Default: a free OS-assigned port.
1116
1042
  */
1117
- port?: number;
1118
- }
1043
+ port?: number | undefined;
1044
+ };
1119
1045
  declare class StubBackend {
1120
1046
  /** Guarded once the current chain declared at least one contract. */
1121
1047
  private guarded;
@@ -1149,107 +1075,107 @@ declare class StubBackend {
1149
1075
  private record;
1150
1076
  }
1151
1077
  //#endregion
1152
- //#region src/core/specification/shared/builder.d.ts
1078
+ //#region src/specification/facets/_common/builder.d.ts
1153
1079
  /** A named job that can be triggered via jobs.trigger(). */
1154
- interface JobHandle {
1080
+ type JobHandle = {
1155
1081
  name: string;
1156
1082
  execute: () => Promise<void>;
1157
- }
1083
+ };
1158
1084
  /**
1159
1085
  * Configuration for the docker-aware cli mode. When set on
1160
1086
  * {@link SpecificationConfig}, the cli runner generates a test-run id, injects
1161
1087
  * it into the child env under `envVar`, then queries Docker for every
1162
1088
  * container carrying `testRunLabel=<id>` after the command exits.
1163
1089
  */
1164
- interface DockerSpecConfig {
1090
+ type DockerSpecConfig = {
1165
1091
  envVar: string;
1166
1092
  nameLabel: string;
1167
1093
  testRunLabel: string;
1168
- }
1094
+ };
1169
1095
  /** Adapter configuration passed to the specification facets at setup time. */
1170
- interface SpecificationConfig {
1096
+ type SpecificationConfig = {
1171
1097
  /**
1172
1098
  * The declared stub backend (website/mobile facets) — armed with the
1173
1099
  * chain's contracts before every terminal action.
1174
1100
  */
1175
- backend?: StubBackend;
1101
+ backend?: StubBackend | undefined;
1176
1102
  /**
1177
1103
  * Base URL of the running stub backend — allow-listed through the
1178
1104
  * browser's `external: 'block'` policy so client-side fetches reach it.
1179
1105
  */
1180
- backendUrl?: string;
1106
+ backendUrl?: string | undefined;
1181
1107
  /** Base URL of the website under test (website facet only). */
1182
- baseUrl?: string;
1108
+ baseUrl?: string | undefined;
1183
1109
  /**
1184
1110
  * Lazy browser accessor (website facet only). The first `.visit()`
1185
1111
  * launches the shared browser instance; `.fetch()`-only spec files never
1186
1112
  * pay the browser cost.
1187
1113
  */
1188
- browser?: () => Promise<BrowserPort>;
1114
+ browser?: (() => Promise<BrowserPort>) | undefined;
1189
1115
  /**
1190
1116
  * Cross-origin request policy for visits (website facet only): `'block'`
1191
1117
  * aborts requests leaving the site under test — the browser-side analog
1192
1118
  * of strict intercepts.
1193
1119
  */
1194
- external?: 'allow' | 'block';
1120
+ external?: 'allow' | 'block' | undefined;
1195
1121
  /** Bundle id of the app under test (mobile facet only) — the app `.open()` relaunches. */
1196
- bundleId?: string;
1197
- command?: CliPort;
1198
- database?: DatabasePort;
1122
+ bundleId?: string | undefined;
1123
+ command?: CliPort | undefined;
1124
+ database?: DatabasePort | undefined;
1199
1125
  /**
1200
1126
  * Keys of the declared services record that are databases. Drives the
1201
1127
  * CONVENTIONS A7 rule: with 2+ databases the `database` option is
1202
1128
  * mandatory on `.seed()` / `.table()`; with exactly one it is forbidden.
1203
1129
  */
1204
- databaseKeys?: string[];
1205
- databases?: Map<string, DatabasePort>;
1130
+ databaseKeys?: string[] | undefined;
1131
+ databases?: Map<string, DatabasePort> | undefined;
1206
1132
  /**
1207
1133
  * Lazy device accessor (mobile facet only). The first `.open()` creates
1208
1134
  * the shared driver session; the appium/webdriverio integration stays a
1209
1135
  * lazy import so the optional peer is only loaded when a spec opens the
1210
1136
  * app.
1211
1137
  */
1212
- device?: () => Promise<DevicePort>;
1213
- dockerConfig?: DockerSpecConfig;
1138
+ device?: (() => Promise<DevicePort>) | undefined;
1139
+ dockerConfig?: DockerSpecConfig | undefined;
1214
1140
  /**
1215
1141
  * Unique id shared by every spec from this runner instance.
1216
1142
  * Stable for the runner's lifetime so multi-step tests (spawn in
1217
1143
  * one run, inspect in another) see the same container scope. The
1218
1144
  * facet factories auto-populate this when `dockerConfig` is present.
1219
1145
  */
1220
- dockerTestRunId?: string;
1146
+ dockerTestRunId?: string | undefined;
1221
1147
  /**
1222
1148
  * Named environment SETS a spec document may name by bare word
1223
1149
  * (`env: frozen`). Declared once per app in `specification.cli()`.
1224
1150
  */
1225
- envSets?: Record<string, CliEnv>;
1151
+ envSets?: Record<string, CliEnv> | undefined;
1226
1152
  /**
1227
1153
  * When set, `.intercept()` is unavailable on this runner and throws this
1228
1154
  * reason immediately (compose mode — MSW is in-process, CONVENTIONS I3).
1229
1155
  */
1230
- interceptDisabledReason?: string;
1231
- jobs?: JobHandle[];
1156
+ interceptDisabledReason?: string | undefined;
1157
+ jobs?: JobHandle[] | undefined;
1232
1158
  /** The project root — the working directory a document's `serve:` command runs from. */
1233
- root?: string;
1234
- server?: ServerPort;
1159
+ root?: string | undefined;
1160
+ server?: ServerPort | undefined;
1235
1161
  /**
1236
1162
  * Named servers a spec document may start (`serve: [mcp]`). Declared once
1237
1163
  * per app in `specification.cli()`.
1238
1164
  */
1239
- serveRegistry?: Record<string, LiterateServeRegistration>;
1165
+ serveRegistry?: Record<string, LiterateServeRegistration> | undefined;
1240
1166
  /**
1241
1167
  * The declared services record. In cli mode, drives the automatic
1242
1168
  * connection-URL injection into the child env (CONVENTIONS B6):
1243
1169
  * `<KEY>_URL` per service, plus `DATABASE_URL` / `REDIS_URL` when
1244
1170
  * unambiguous.
1245
1171
  */
1246
- services?: Record<string, ServiceHandle>;
1172
+ services?: Record<string, ServiceHandle> | undefined;
1247
1173
  /**
1248
1174
  * Optional normaliser applied to command stdout/stderr before every
1249
1175
  * comparison. Does not mutate the raw `.text` accessor.
1250
1176
  */
1251
- transform?: (text: string) => string;
1252
- }
1177
+ transform?: ((text: string) => string) | undefined;
1178
+ };
1253
1179
  /**
1254
1180
  * The `.intercept()` overload set, identical on every facet: one contract, a
1255
1181
  * list, a composite — or the inline `(request, response)` pair for a one-off.
@@ -1263,7 +1189,7 @@ type InterceptMethod<T> = ((contracts: ContractInput) => T) & ((request: Contrac
1263
1189
  * The `DatabaseKey` parameter is the typed vocabulary of `.seed()`: the keys
1264
1190
  * of the declared services record that are databases.
1265
1191
  */
1266
- interface ApiSpecification<DatabaseKey extends string = string> {
1192
+ type ApiSpecification<DatabaseKey extends string = string> = {
1267
1193
  /** Set HTTP headers for the request. Multiple calls merge. */
1268
1194
  headers: (headers: Record<string, string>) => ApiSpecification<DatabaseKey>;
1269
1195
  /** Declare outgoing calls — a contract, a list, a composite, or an inline request + response pair. */
@@ -1282,12 +1208,12 @@ interface ApiSpecification<DatabaseKey extends string = string> {
1282
1208
  put: (path: string, body?: unknown) => Promise<HttpResult>;
1283
1209
  /** Send the complete request described by `_requests/<file>` (.http format). */
1284
1210
  request: (file: string) => Promise<HttpResult>;
1285
- }
1211
+ };
1286
1212
  /**
1287
1213
  * The `jobs` facet — job chain entry handed out by `specification.jobs()`.
1288
1214
  * Jobs run in-process by definition (CONVENTIONS A5/A8).
1289
1215
  */
1290
- interface JobsSpecification<DatabaseKey extends string = string> {
1216
+ type JobsSpecification<DatabaseKey extends string = string> = {
1291
1217
  /** Declare outgoing calls — a contract, a list, a composite, or an inline request + response pair. */
1292
1218
  intercept: InterceptMethod<JobsSpecification<DatabaseKey>>;
1293
1219
  /** Queue a SQL seed file from `_seeds/` to run before the action. */
@@ -1296,13 +1222,13 @@ interface JobsSpecification<DatabaseKey extends string = string> {
1296
1222
  }) => JobsSpecification<DatabaseKey>;
1297
1223
  /** Execute the named job registered via the `jobs` option and resolve with the result. */
1298
1224
  trigger: (name: string) => Promise<BaseResult>;
1299
- }
1225
+ };
1300
1226
  /**
1301
1227
  * The `cli` facet — command chain entry handed out by `specification.cli()`.
1302
1228
  * Setup methods chain; `.exec()` is the single terminal action (CONVENTIONS
1303
1229
  * B2) — `{ waitFor?, timeout? }` covers long-running processes.
1304
1230
  */
1305
- interface CliSpecification<DatabaseKey extends string = string> {
1231
+ type CliSpecification<DatabaseKey extends string = string> = {
1306
1232
  /** Set environment variables on the child process. `$WORKDIR` expands; `null` unsets. */
1307
1233
  env: (env: CliEnv) => CliSpecification<DatabaseKey>;
1308
1234
  /**
@@ -1333,14 +1259,14 @@ interface CliSpecification<DatabaseKey extends string = string> {
1333
1259
  * directory, where the document lives.
1334
1260
  */
1335
1261
  run: (file: string, options?: LiterateRunFlags) => Promise<CliResult>;
1336
- }
1262
+ };
1337
1263
  /**
1338
1264
  * The `website` facet — page chain entry handed out by
1339
1265
  * `specification.website()`. Setup methods chain; action methods are
1340
1266
  * terminal. `.visit()` renders the page in the shared browser; `.fetch()`
1341
1267
  * performs one raw HTTP exchange and never follows redirects.
1342
1268
  */
1343
- interface WebsiteSpecification {
1269
+ type WebsiteSpecification = {
1344
1270
  /** Set HTTP headers for the exchange (incl. User-Agent overrides). Multiple calls merge. */
1345
1271
  headers: (headers: Record<string, string>) => WebsiteSpecification;
1346
1272
  /**
@@ -1356,14 +1282,14 @@ interface WebsiteSpecification {
1356
1282
  * the capture reflects the FINAL page state.
1357
1283
  */
1358
1284
  visit: (path: string, scenario?: VisitScenario) => Promise<PageResult>;
1359
- }
1285
+ };
1360
1286
  /**
1361
1287
  * The `mobile` facet — screen chain entry handed out by
1362
1288
  * `specification.mobile()`. `.open()` is the single, terminal action: it
1363
1289
  * terminates and relaunches the app (deterministic fresh state), applies the
1364
1290
  * deep link, runs the scenario, and captures the final screen.
1365
1291
  */
1366
- interface MobileSpecification {
1292
+ type MobileSpecification = {
1367
1293
  /**
1368
1294
  * Declare the chain's backend contracts — served by the declared stub
1369
1295
  * backend (requires the runner's `backend` option). Multiple calls append.
@@ -1375,65 +1301,91 @@ interface MobileSpecification {
1375
1301
  * first (the When) and the capture reflects the FINAL screen state.
1376
1302
  */
1377
1303
  open: (deepLink?: string, scenario?: MobileScenario) => Promise<ScreenResult>;
1304
+ };
1305
+ //#endregion
1306
+ //#region src/specification/facets/_common/result/directory.d.ts
1307
+ /**
1308
+ * Read-only accessor for a directory produced by a spec action.
1309
+ *
1310
+ * Assertions go through `expect()` (async — they walk the disk):
1311
+ * `await expect(result.directory('out')).toMatch('scaffold/out')`
1312
+ * compares the tree against the fixture directory `_expected/<name>/`.
1313
+ */
1314
+ declare class DirectoryAccessor {
1315
+ /** @internal Ref-capture scope shared by the current spec execution. */
1316
+ readonly captures: CaptureScope;
1317
+ /** @internal Absolute path of the directory under assertion. */
1318
+ readonly root: string;
1319
+ /** @internal Test-file directory — fixture resolution root for matchers. */
1320
+ readonly testDir: string;
1321
+ constructor(absPath: string, testDir: string, captures?: CaptureScope);
1322
+ /** List all files (recursively) under the directory, sorted. */
1323
+ files(options?: {
1324
+ ignore?: string[];
1325
+ }): Promise<string[]>;
1378
1326
  }
1379
1327
  //#endregion
1380
- //#region src/core/specification/cli/result.d.ts
1328
+ //#region src/specification/facets/_common/result/table.d.ts
1381
1329
  /**
1382
- * Result from a command action (`.exec()`).
1330
+ * Read-only accessor for a database table after a specification run.
1383
1331
  *
1384
- * When the runner was configured with a `docker` option, the result also
1385
- * exposes container accessors and participates in `Symbol.asyncDispose`
1386
- * cleanup. The Docker shell-outs are **lazy**: a test that never calls
1387
- * `.container(...)` never queries the Docker daemon, so command-only tests
1388
- * pay zero Docker cost even when the runner is Docker-aware.
1332
+ * Assertions go through `expect()` (async — they query the database):
1389
1333
  *
1390
- * Dispose always runs one final label-filtered `docker ps` to catch
1391
- * containers that were spawned during `.exec` but never asserted on —
1392
- * tests still get leak-free cleanup even if they forget to reach into
1393
- * a container.
1334
+ * await expect(result.table('users', { database: 'db' })).toMatchRows({
1335
+ * columns: ['name'],
1336
+ * rows: [['Alice']],
1337
+ * });
1338
+ * await expect(result.table('users', { database: 'db' })).toBeEmpty();
1394
1339
  */
1395
- declare class CliResult extends BaseResult {
1396
- private readonly commandOutput;
1397
- private containersCache;
1398
- private readonly dockerConfig?;
1399
- private readonly testRunId?;
1400
- private readonly transform?;
1401
- constructor(options: {
1402
- commandOutput: CliOutput;
1403
- config: SpecificationConfig;
1404
- dockerConfig?: DockerSpecConfig;
1405
- testDir: string;
1406
- testRunId?: string;
1407
- transform?: (text: string) => string;
1408
- workDir: string;
1409
- });
1410
- /** The process exit code. */
1411
- get exitCode(): number;
1412
- /** Accessor for the captured standard output with file-based assertions. */
1413
- get stdout(): TextAccessor;
1414
- /** Accessor for the captured standard error with file-based assertions. */
1415
- get stderr(): TextAccessor;
1416
- /** Accessor for parsing stdout as JSON and asserting against JSON fixtures. */
1417
- get json(): JsonAccessor;
1418
- /** Accessor for the temporary working directory the command ran in. */
1419
- get filesystem(): FilesystemAccessor;
1420
- /**
1421
- * Look up a container the command spawned during this run by the value of
1422
- * its name-label (as declared in the `docker.nameLabel` of the
1423
- * `specification.cli()` options).
1424
- * First access triggers a one-shot `docker ps` + `docker inspect`
1425
- * query and caches the result for the rest of the result's lifetime.
1426
- * Tests that don't call this never touch Docker.
1427
- *
1428
- * Returns an accessor with `.exists === false` when no container
1429
- * carries that name — callers can still assert absence without a try.
1430
- */
1431
- container(name: string): ContainerAccessor;
1432
- /** All captured container IDs. Triggers the same lazy query as `container()`. */
1433
- get containerIds(): string[];
1434
- [Symbol.asyncDispose](): Promise<void>;
1435
- private ensureDockerAware;
1436
- private loadContainers;
1340
+ declare class TableAccessor {
1341
+ /** @internal Ref-capture scope shared by the current spec execution. */
1342
+ readonly captures: CaptureScope;
1343
+ /** @internal */
1344
+ private readonly db;
1345
+ /** The table name this accessor reads. */
1346
+ readonly name: string;
1347
+ constructor(name: string, db: DatabasePort, captures?: CaptureScope);
1348
+ /** @internal Query the table — used by the toMatchRows / toBeEmpty matchers. */
1349
+ query(columns: string[]): Promise<unknown[][]>;
1350
+ }
1351
+ //#endregion
1352
+ //#region src/specification/facets/_common/result/result.d.ts
1353
+ /** Read-only handle to a single file produced by a spec action. */
1354
+ type FileAccessor = {
1355
+ /** The UTF-8 text content. Throws if the file does not exist. */
1356
+ readonly content: string;
1357
+ readonly exists: boolean;
1358
+ /** The file text as a {@link TextAccessor}, keeping only blocks matching `pattern`. */
1359
+ grep: (pattern: string) => TextAccessor;
1360
+ };
1361
+ type BaseResultOptions = {
1362
+ config: SpecificationConfig;
1363
+ testDir: string;
1364
+ workDir?: string | undefined;
1365
+ };
1366
+ /**
1367
+ * Base result - common accessors available after any action type.
1368
+ * Extended by HttpResult, CliResult, and used directly by job results.
1369
+ *
1370
+ * Every result carries a fresh {@link CaptureScope}: `match.ref()` captures
1371
+ * and `{{type#ref}}` placeholders are scoped to one spec execution.
1372
+ */
1373
+ declare class BaseResult {
1374
+ /** @internal Ref-capture scope shared by every assertion on this result. */
1375
+ readonly captures: CaptureScope;
1376
+ protected config: SpecificationConfig;
1377
+ protected testDir: string;
1378
+ protected workDir?: string | undefined;
1379
+ constructor(options: BaseResultOptions);
1380
+ /** Access a directory (relative to the working directory) for snapshot assertions. */
1381
+ directory(path?: string): DirectoryAccessor;
1382
+ /** Access a single file (relative to the working directory) for content assertions. */
1383
+ file(path: string): FileAccessor;
1384
+ /** Access a database table for row-level assertions via expect() matchers. */
1385
+ table(tableName: string, options?: {
1386
+ database?: string;
1387
+ }): TableAccessor;
1388
+ private resolveDatabase;
1437
1389
  }
1438
1390
  //#endregion
1439
1391
  //#region src/integrations/docker/container-accessor.d.ts
@@ -1500,12 +1452,12 @@ declare class ContainerAccessor {
1500
1452
  * survivable across update runs instead of being silently overwritten with the
1501
1453
  * actual output.
1502
1454
  */
1503
- interface MatchFixtureOptions {
1455
+ type MatchFixtureOptions = {
1504
1456
  frozen?: boolean;
1505
- }
1457
+ };
1506
1458
  //#endregion
1507
- //#region src/core/specification/shared/orchestrator.d.ts
1508
- interface OrchestratorOptions {
1459
+ //#region src/specification/facets/_common/orchestrator.d.ts
1460
+ type OrchestratorOptions = {
1509
1461
  /**
1510
1462
  * Named infrastructure record. Keys become the database vocabulary of the
1511
1463
  * spec (`.seed()` / `.table()` `database` option) and drive the compose
@@ -1526,17 +1478,17 @@ interface OrchestratorOptions {
1526
1478
  root: string;
1527
1479
  /** Compose project name — used for per-worker stack isolation. */
1528
1480
  projectName?: string;
1529
- }
1481
+ };
1530
1482
  /**
1531
1483
  * Orchestrator for test infrastructure.
1532
1484
  * Integration: starts services via testcontainers.
1533
1485
  * E2E: runs full docker compose up.
1534
1486
  */
1535
1487
  declare class Orchestrator {
1536
- private services;
1537
- private mode;
1538
- private root;
1539
- private projectName;
1488
+ private readonly services;
1489
+ private readonly mode;
1490
+ private readonly root;
1491
+ private readonly projectName;
1540
1492
  private running;
1541
1493
  private composeStack;
1542
1494
  private composeHandles;
@@ -1585,7 +1537,7 @@ declare class Orchestrator {
1585
1537
  getAppUrl(): null | string;
1586
1538
  }
1587
1539
  //#endregion
1588
- //#region src/core/specification/shared/services.d.ts
1540
+ //#region src/specification/facets/_common/services.d.ts
1589
1541
  /**
1590
1542
  * Infrastructure services declared as a named record. Keys become the typed
1591
1543
  * vocabulary of the whole spec: the server factory receives the same record,
@@ -1595,7 +1547,7 @@ type ServiceRecord = Record<string, ServiceHandle>;
1595
1547
  /** Keys of a services record whose handles are databases. */
1596
1548
  type DatabaseKeys<Services extends ServiceRecord> = { [K in keyof Services]: Services[K] extends DatabasePort ? K & string : never; }[keyof Services];
1597
1549
  //#endregion
1598
- //#region src/core/specification/api/start-api.d.ts
1550
+ //#region src/specification/facets/api/start-api.d.ts
1599
1551
  /** Any object with a request method compatible with Hono's app.request(). */
1600
1552
  type HonoApp = {
1601
1553
  request: (path: string, init?: RequestInit) => Promise<Response> | Response;
@@ -1603,7 +1555,7 @@ type HonoApp = {
1603
1555
  /** Execution mode — exists ONLY on `specification.api()` (CONVENTIONS A5). */
1604
1556
  type SpecificationMode = 'compose' | 'node';
1605
1557
  /** Options for {@link startApi | specification.api}. */
1606
- interface ApiSpecificationOptions<Services extends ServiceRecord = ServiceRecord> {
1558
+ type ApiSpecificationOptions<Services extends ServiceRecord = ServiceRecord> = {
1607
1559
  /**
1608
1560
  * Execution mode override. Resolution: `options.mode` >
1609
1561
  * `process.env.TEST_MODE` > `'node'`. Never hardcode this in a
@@ -1632,14 +1584,14 @@ interface ApiSpecificationOptions<Services extends ServiceRecord = ServiceRecord
1632
1584
  * its key, else the kebab-case conversion of the key (CONVENTIONS A6).
1633
1585
  */
1634
1586
  services?: Services;
1635
- }
1587
+ };
1636
1588
  /**
1637
1589
  * The record returned by {@link startApi | specification.api}. Destructure
1638
1590
  * with the canonical names (CONVENTIONS A3):
1639
1591
  *
1640
1592
  * const { api, cleanup, docker } = await specification.api(…);
1641
1593
  */
1642
- interface ApiHandle<DatabaseKey extends string = string> {
1594
+ type ApiHandle<DatabaseKey extends string = string> = {
1643
1595
  api: ApiSpecification<DatabaseKey>;
1644
1596
  /** Stop all infrastructure started by this specification. */
1645
1597
  cleanup: () => Promise<void>;
@@ -1650,12 +1602,12 @@ interface ApiHandle<DatabaseKey extends string = string> {
1650
1602
  docker: (containerId: string) => ContainerAccessor;
1651
1603
  /** The orchestrator managing the test infrastructure lifecycle. */
1652
1604
  orchestrator: Orchestrator;
1653
- }
1605
+ };
1654
1606
  declare function startApi<Services extends ServiceRecord>(options: ApiSpecificationOptions<Services>): Promise<ApiHandle<DatabaseKeys<Services>>>;
1655
1607
  //#endregion
1656
- //#region src/core/specification/cli/start-cli.d.ts
1608
+ //#region src/specification/facets/cli/start-cli.d.ts
1657
1609
  /** Options for {@link startCli | specification.cli}. */
1658
- interface CliSpecificationOptions<Services extends ServiceRecord = ServiceRecord> {
1610
+ type CliSpecificationOptions<Services extends ServiceRecord = ServiceRecord> = {
1659
1611
  /**
1660
1612
  * Opt-in Docker awareness. When set, every spec generates a unique
1661
1613
  * test-run id, injects it into the child process env under `envVar`,
@@ -1699,15 +1651,15 @@ interface CliSpecificationOptions<Services extends ServiceRecord = ServiceRecord
1699
1651
  * D6). Does NOT mutate the raw `.text` accessor. Prefer `{{token}}`
1700
1652
  * placeholders in fixtures.
1701
1653
  */
1702
- transform?: (text: string) => string;
1703
- }
1654
+ transform?: ((text: string) => string) | undefined;
1655
+ };
1704
1656
  /**
1705
1657
  * The record returned by {@link startCli | specification.cli}. Destructure
1706
1658
  * with the canonical names (CONVENTIONS A3):
1707
1659
  *
1708
1660
  * const { cli, cleanup, docker } = await specification.cli(…);
1709
1661
  */
1710
- interface CliHandle<DatabaseKey extends string = string> {
1662
+ type CliHandle<DatabaseKey extends string = string> = {
1711
1663
  /** Stop all infrastructure started by this specification. */
1712
1664
  cleanup: () => Promise<void>;
1713
1665
  cli: CliSpecification<DatabaseKey>;
@@ -1718,12 +1670,12 @@ interface CliHandle<DatabaseKey extends string = string> {
1718
1670
  docker: (containerId: string) => ContainerAccessor;
1719
1671
  /** The orchestrator managing the test infrastructure lifecycle. */
1720
1672
  orchestrator: null | Orchestrator;
1721
- }
1673
+ };
1722
1674
  declare function startCli<Services extends ServiceRecord>(bin: string, options?: CliSpecificationOptions<Services>): Promise<CliHandle<DatabaseKeys<Services>>>;
1723
1675
  //#endregion
1724
- //#region src/core/specification/jobs/start-jobs.d.ts
1676
+ //#region src/specification/facets/jobs/start-jobs.d.ts
1725
1677
  /** Options for {@link startJobs | specification.jobs}. */
1726
- interface JobsSpecificationOptions<Services extends ServiceRecord = ServiceRecord> {
1678
+ type JobsSpecificationOptions<Services extends ServiceRecord = ServiceRecord> = {
1727
1679
  /**
1728
1680
  * Named jobs triggerable via `jobs.trigger(name)` — a factory receiving
1729
1681
  * the started services record, or a static array. Jobs run in-process by
@@ -1734,23 +1686,23 @@ interface JobsSpecificationOptions<Services extends ServiceRecord = ServiceRecor
1734
1686
  root?: string;
1735
1687
  /** Named infrastructure record started via testcontainers. */
1736
1688
  services?: Services;
1737
- }
1689
+ };
1738
1690
  /**
1739
1691
  * The record returned by {@link startJobs | specification.jobs}. Destructure
1740
1692
  * with the canonical names (CONVENTIONS A3):
1741
1693
  *
1742
1694
  * const { jobs, cleanup } = await specification.jobs(…);
1743
1695
  */
1744
- interface JobsHandle<DatabaseKey extends string = string> {
1696
+ type JobsHandle<DatabaseKey extends string = string> = {
1745
1697
  /** Stop all infrastructure started by this specification. */
1746
1698
  cleanup: () => Promise<void>;
1747
1699
  jobs: JobsSpecification<DatabaseKey>;
1748
1700
  /** The orchestrator managing the test infrastructure lifecycle. */
1749
1701
  orchestrator: null | Orchestrator;
1750
- }
1702
+ };
1751
1703
  declare function startJobs<Services extends ServiceRecord>(options: JobsSpecificationOptions<Services>): Promise<JobsHandle<DatabaseKeys<Services>>>;
1752
1704
  //#endregion
1753
- //#region src/core/specification/mobile/start-mobile.d.ts
1705
+ //#region src/specification/facets/mobile/start-mobile.d.ts
1754
1706
  /**
1755
1707
  * The declared stub backend behind the app under test. The framework owns
1756
1708
  * the simulator and appium but NOT the JS bundler (Metro belongs to the
@@ -1758,16 +1710,16 @@ declare function startJobs<Services extends ServiceRecord>(options: JobsSpecific
1758
1710
  * injected: the handle exposes `backendUrl` and the CALLER wires it into its
1759
1711
  * own bundler env.
1760
1712
  */
1761
- interface MobileBackendOptions {
1713
+ type MobileBackendOptions = {
1762
1714
  /**
1763
1715
  * Fixed port — pins a stable stub URL across runs. Metro inlines
1764
1716
  * `EXPO_PUBLIC_*` values at bundle-serve time; a stable port lets a warm
1765
1717
  * Metro survive between runs. Default: a free OS-assigned port.
1766
1718
  */
1767
1719
  port?: number;
1768
- }
1720
+ };
1769
1721
  /** Options for {@link startMobile | specification.mobile}. */
1770
- interface MobileSpecificationOptions {
1722
+ type MobileSpecificationOptions = {
1771
1723
  /** The app under test — terminated and relaunched by every `.open()`. */
1772
1724
  app: {
1773
1725
  /** Bundle id of the installed app (`com.example.app`). */
@@ -1801,31 +1753,31 @@ interface MobileSpecificationOptions {
1801
1753
  * (`{ action: 45_000 }`) instead of sleeping inside its scenarios.
1802
1754
  */
1803
1755
  timeouts?: DeviceTimeouts;
1804
- }
1756
+ };
1805
1757
  /**
1806
1758
  * The record returned by {@link startMobile | specification.mobile}.
1807
1759
  * Destructure with the canonical names (CONVENTIONS A3):
1808
1760
  *
1809
1761
  * const { mobile, cleanup, udid } = await specification.mobile(…);
1810
1762
  */
1811
- interface MobileHandle {
1763
+ type MobileHandle = {
1812
1764
  /**
1813
1765
  * Base URL of the declared stub backend — present only with the
1814
1766
  * `backend` option. The caller injects it into its own bundler env
1815
1767
  * (e.g. `EXPO_PUBLIC_API_URL`); the framework never touches the bundler.
1816
1768
  */
1817
- backendUrl?: string;
1769
+ backendUrl?: string | undefined;
1818
1770
  /** End the driver session, stop the appium server and the stub backend. */
1819
1771
  cleanup: () => Promise<void>;
1820
1772
  mobile: MobileSpecification;
1821
1773
  /** The resolved simulator UDID the specs run against. */
1822
1774
  udid: string;
1823
- }
1775
+ };
1824
1776
  declare function startMobile(options: MobileSpecificationOptions): Promise<MobileHandle>;
1825
1777
  //#endregion
1826
- //#region src/core/specification/website/serve.adapter.d.ts
1778
+ //#region src/specification/facets/website/serve.adapter.d.ts
1827
1779
  /** Options for a local server started by the framework. */
1828
- interface ServeOptions {
1780
+ type ServeOptions = {
1829
1781
  /** Shell command that starts the site. Receives the chosen port as `PORT`. */
1830
1782
  command: string;
1831
1783
  /** Fixed port. Default: an OS-assigned free port, injected as `PORT`. */
@@ -1844,21 +1796,21 @@ interface ServeOptions {
1844
1796
  ready?: RegExp | string;
1845
1797
  /** Readiness budget in milliseconds. Default 30 000. */
1846
1798
  timeout?: number;
1847
- }
1799
+ };
1848
1800
  //#endregion
1849
- //#region src/core/specification/website/start-website.d.ts
1801
+ //#region src/specification/facets/website/start-website.d.ts
1850
1802
  /**
1851
1803
  * The declared stub backend behind the site under test — started before the
1852
1804
  * server command, torn down with the runner. Its URL is injected into the
1853
1805
  * server child's environment under `env`; the contracts each chain declares
1854
1806
  * via `.intercept(...)` are what it serves.
1855
1807
  */
1856
- interface WebsiteBackendOptions {
1808
+ type WebsiteBackendOptions = {
1857
1809
  /** Env var receiving the stub's URL in the server child (e.g. `'API_URL'`). */
1858
1810
  env: string;
1859
1811
  /** Fixed port — pins a stable stub URL across runs. Default: a free OS-assigned port. */
1860
1812
  port?: number;
1861
- }
1813
+ };
1862
1814
  /**
1863
1815
  * Options for {@link startWebsite | specification.website}. `server` (start
1864
1816
  * the site locally) and `url` (target a running site) are mutually
@@ -1904,16 +1856,16 @@ type WebsiteSpecificationOptions = {
1904
1856
  *
1905
1857
  * const { website, cleanup } = await specification.website(…);
1906
1858
  */
1907
- interface WebsiteHandle {
1859
+ type WebsiteHandle = {
1908
1860
  /** Stop the server process and the shared browser instance. */
1909
1861
  cleanup: () => Promise<void>;
1910
1862
  /** The base URL the specs run against. */
1911
1863
  url: string;
1912
1864
  website: WebsiteSpecification;
1913
- }
1865
+ };
1914
1866
  declare function startWebsite(options: WebsiteSpecificationOptions): Promise<WebsiteHandle>;
1915
1867
  //#endregion
1916
- //#region src/core/specification/shared/specification.d.ts
1868
+ //#region src/specification/facets/_common/specification.d.ts
1917
1869
  /**
1918
1870
  * The five specification constructors (CONVENTIONS A2) — created in a
1919
1871
  * `*.specification.ts` file under `specs/`, destructured with canonical
@@ -1938,44 +1890,17 @@ declare function startWebsite(options: WebsiteSpecificationOptions): Promise<Web
1938
1890
  * export const { cli, cleanup } = await specification.cli('my-cli');
1939
1891
  * afterAll(cleanup);
1940
1892
  */
1941
- declare const specification: {
1942
- /**
1943
- * Test an HTTP app. Mode `'node'` (default) starts the declared services
1944
- * via testcontainers and runs the app in-process; mode `'compose'` runs
1945
- * `docker compose up` on `docker/compose.test.yaml` and sends real HTTP
1946
- * requests to the app service. Resolution: `options.mode` > `TEST_MODE`
1947
- * env var > `'node'`. Only `.api()` has a mode.
1948
- */
1893
+ /** The five constructors, and only five — the framework's whole entry surface. */
1894
+ type Specification = {
1949
1895
  api: typeof startApi;
1950
- /**
1951
- * Test a command binary. Each spec runs in a fresh temp directory.
1952
- *
1953
- * @param bin - Path to the binary (resolved from node_modules/.bin or PATH).
1954
- */
1955
1896
  cli: typeof startCli;
1956
- /**
1957
- * Test background jobs. Jobs run in-process by definition — no HTTP
1958
- * server, no mode. `.trigger(name)` is the terminal action.
1959
- */
1960
1897
  jobs: typeof startJobs;
1961
- /**
1962
- * Test a native app on the iOS simulator. `device` names the simulator
1963
- * (resolved and booted via `simctl`); `app` names the bundle under
1964
- * test. `.open(deepLink?, scenario?)` relaunches the app fresh, runs
1965
- * the scenario, and captures the final screen.
1966
- */
1967
1898
  mobile: typeof startMobile;
1968
- /**
1969
- * Test a deployed or locally-served website. `server` starts the site
1970
- * (a shell command receiving `PORT`); `url` targets a running one.
1971
- * `.visit(path)` renders the page in a single shared browser instance;
1972
- * `.fetch(path)` performs one raw HTTP exchange (redirects never
1973
- * followed).
1974
- */
1975
1899
  website: typeof startWebsite;
1976
1900
  };
1901
+ declare const specification: Specification;
1977
1902
  //#endregion
1978
- //#region src/core/literate/spec-document.d.ts
1903
+ //#region src/specification/literate/spec-document.d.ts
1979
1904
  /**
1980
1905
  * The facets a document may describe. `cli` is the only one implemented; the
1981
1906
  * key exists so a second facet can arrive without a second grammar.
@@ -1994,22 +1919,22 @@ type SpecEnvToken = {
1994
1919
  name: string;
1995
1920
  };
1996
1921
  /** One `serve:` entry — a registered server plus the extra env it is started with. */
1997
- interface SpecServeEntry {
1922
+ type SpecServeEntry = {
1998
1923
  env: Record<string, string>;
1999
1924
  line: number;
2000
1925
  name: string;
2001
- }
1926
+ };
2002
1927
  /** One `fixture:` entry — a path with the line it was declared on. */
2003
- interface SpecFixture {
1928
+ type SpecFixture = {
2004
1929
  line: number;
2005
1930
  path: string;
2006
- }
1931
+ };
2007
1932
  /** An expected stream: its text, and the first line of that text in the file. */
2008
- interface SpecStream {
1933
+ type SpecStream = {
2009
1934
  /** 1-based line the scalar's content starts on — where a token defect is reported. */
2010
1935
  line: number;
2011
1936
  text: string;
2012
- }
1937
+ };
2013
1938
  /** One `files:` assertion, keyed by a workdir-relative path. */
2014
1939
  type SpecFileAssertion = {
2015
1940
  contains: SpecStream[];
@@ -2027,7 +1952,7 @@ type SpecFileAssertion = {
2027
1952
  path: string;
2028
1953
  };
2029
1954
  /** One run: a command, what it must exit with, and what it must have produced. */
2030
- interface SpecRun {
1955
+ type SpecRun = {
2031
1956
  command: string;
2032
1957
  /** 1-based line of the `command:` key — the frame a failure points at. */
2033
1958
  commandLine: number;
@@ -2041,9 +1966,9 @@ interface SpecRun {
2041
1966
  timeout: null | number;
2042
1967
  /** Only ever on the LAST run — a long-running command ends the session. */
2043
1968
  waitFor: null | string;
2044
- }
1969
+ };
2045
1970
  /** A parsed `<case>.spec.yaml`. */
2046
- interface SpecDocument {
1971
+ type SpecDocument = {
2047
1972
  /** The vitest test title. */
2048
1973
  description: string;
2049
1974
  /** 1-based line of the `description:` key. */
@@ -2053,7 +1978,7 @@ interface SpecDocument {
2053
1978
  kind: SpecKind;
2054
1979
  runs: SpecRun[];
2055
1980
  serve: SpecServeEntry[];
2056
- }
1981
+ };
2057
1982
  //#endregion
2058
1983
  //#region src/integrations/docker/docker-lookup.d.ts
2059
1984
  /** Return all container IDs (running or stopped) that carry `key=value`. */
@@ -2063,12 +1988,12 @@ declare function inspectContainer(id: string): unknown;
2063
1988
  /** Force-remove the given container IDs in a single call. Errors are swallowed. */
2064
1989
  declare function removeContainers(ids: string[]): void;
2065
1990
  //#endregion
2066
- //#region src/core/ports/container.port.d.ts
1991
+ //#region src/specification/ports/container.port.d.ts
2067
1992
  /**
2068
1993
  * Abstract container interface.
2069
1994
  * Represents a running service (database, cache, etc.)
2070
1995
  */
2071
- interface ContainerPort {
1996
+ type ContainerPort = {
2072
1997
  /** Start the container and wait until ready. */
2073
1998
  start: () => Promise<void>;
2074
1999
  /** Stop and remove the container. */
@@ -2081,9 +2006,9 @@ interface ContainerPort {
2081
2006
  getConnectionString: () => string;
2082
2007
  /** Get container logs (stdout + stderr). */
2083
2008
  getLogs: () => Promise<string>;
2084
- }
2009
+ };
2085
2010
  //#endregion
2086
- //#region src/core/specification/website/elements.d.ts
2011
+ //#region src/specification/facets/website/elements.d.ts
2087
2012
  /**
2088
2013
  * The element vocabulary — user-facing descriptors for visit scenarios.
2089
2014
  * Reads like English (`click(link('Articles'))`), translates to
@@ -2099,41 +2024,45 @@ interface ContainerPort {
2099
2024
  * substring, when two names genuinely overlap.
2100
2025
  */
2101
2026
  /** Options accepted by every named descriptor. */
2102
- interface ElementOptions {
2027
+ type ElementOptions = {
2103
2028
  /**
2104
2029
  * Match the accessible name as a whole string. Default is substring —
2105
2030
  * `link('Articles')` also matches "Read Articles".
2106
2031
  */
2107
2032
  exact?: boolean;
2108
- }
2033
+ };
2034
+ /** A user-facing element factory — what every name in this module is. */
2035
+ type ElementFactory = (name: string, options?: ElementOptions) => ElementRef;
2109
2036
  /** A button (or element with the button role), by accessible name. */
2110
- declare const button: (name: string, options?: ElementOptions) => ElementRef;
2037
+ declare const button: ElementFactory;
2111
2038
  /** A form field, by label. */
2112
- declare const field: (name: string, options?: ElementOptions) => ElementRef;
2039
+ declare const field: ElementFactory;
2113
2040
  /** A heading, by accessible name. */
2114
- declare const heading: (name: string, options?: ElementOptions) => ElementRef;
2041
+ declare const heading: ElementFactory;
2115
2042
  /** A link, by accessible name. */
2116
- declare const link: (name: string, options?: ElementOptions) => ElementRef;
2043
+ declare const link: ElementFactory;
2117
2044
  /** An element containing the given text. */
2118
- declare const content: (name: string, options?: ElementOptions) => ElementRef;
2045
+ declare const content: ElementFactory;
2119
2046
  /** The escape hatch: an element by `data-testid`. Prefer user-facing elements. */
2120
2047
  declare const testId: (id: string) => ElementRef;
2048
+ /** A landmark factory — the optionally-named containers `within()` accepts. */
2049
+ type LandmarkFactory = (name?: string, options?: ElementOptions) => ElementRef;
2121
2050
  /** The `banner` landmark — the page header. */
2122
- declare const banner: (name?: string, options?: ElementOptions) => ElementRef;
2051
+ declare const banner: LandmarkFactory;
2123
2052
  /** The `complementary` landmark — an `<aside>`, a sidebar. */
2124
- declare const complementary: (name?: string, options?: ElementOptions) => ElementRef;
2053
+ declare const complementary: LandmarkFactory;
2125
2054
  /** The `contentinfo` landmark — the page footer. */
2126
- declare const contentinfo: (name?: string, options?: ElementOptions) => ElementRef;
2055
+ declare const contentinfo: LandmarkFactory;
2127
2056
  /** The `form` landmark — a form carrying an accessible name. */
2128
- declare const form: (name?: string, options?: ElementOptions) => ElementRef;
2057
+ declare const form: LandmarkFactory;
2129
2058
  /** The `main` landmark — the primary content of the document. */
2130
- declare const main: (name?: string, options?: ElementOptions) => ElementRef;
2059
+ declare const main: LandmarkFactory;
2131
2060
  /** The `navigation` landmark — a `<nav>`. Name it when a page has several. */
2132
- declare const navigation: (name?: string, options?: ElementOptions) => ElementRef;
2061
+ declare const navigation: LandmarkFactory;
2133
2062
  /** The `region` landmark — a `<section>` carrying an accessible name. */
2134
- declare const region: (name?: string, options?: ElementOptions) => ElementRef;
2063
+ declare const region: LandmarkFactory;
2135
2064
  /** The `search` landmark. */
2136
- declare const search: (name?: string, options?: ElementOptions) => ElementRef;
2065
+ declare const search: LandmarkFactory;
2137
2066
  /**
2138
2067
  * Restrict a descriptor to the inside of another — the answer to ambiguity,
2139
2068
  * and the one the framework prefers over a test id.
@@ -2148,7 +2077,7 @@ declare const search: (name?: string, options?: ElementOptions) => ElementRef;
2148
2077
  declare const within: (scope: ElementRef, target: ElementRef) => ElementRef;
2149
2078
  //#endregion
2150
2079
  //#region src/integrations/postgres/postgres.d.ts
2151
- interface PostgresOptions {
2080
+ type PostgresOptions = {
2152
2081
  /**
2153
2082
  * Map to a service in docker/compose.test.yaml. Defaults to the handle's
2154
2083
  * key in the declared services record.
@@ -2158,7 +2087,7 @@ interface PostgresOptions {
2158
2087
  image?: string;
2159
2088
  /** Override environment variables. */
2160
2089
  env?: Record<string, string>;
2161
- }
2090
+ };
2162
2091
  declare class PostgresHandle implements DatabasePort, ServiceHandle {
2163
2092
  readonly type = "postgres";
2164
2093
  composeName: null | string;
@@ -2195,7 +2124,7 @@ declare class PostgresHandle implements DatabasePort, ServiceHandle {
2195
2124
  declare function postgres(options?: PostgresOptions): PostgresHandle;
2196
2125
  //#endregion
2197
2126
  //#region src/integrations/redis/redis.d.ts
2198
- interface RedisOptions {
2127
+ type RedisOptions = {
2199
2128
  /**
2200
2129
  * Map to a service in docker/compose.test.yaml. Defaults to the handle's
2201
2130
  * key in the declared services record.
@@ -2203,7 +2132,7 @@ interface RedisOptions {
2203
2132
  composeService?: string;
2204
2133
  /** Override image. */
2205
2134
  image?: string;
2206
- }
2135
+ };
2207
2136
  declare class RedisHandle implements ServiceHandle {
2208
2137
  readonly type = "redis";
2209
2138
  composeName: null | string;
@@ -2232,7 +2161,7 @@ declare class RedisHandle implements ServiceHandle {
2232
2161
  declare function redis(options?: RedisOptions): RedisHandle;
2233
2162
  //#endregion
2234
2163
  //#region src/integrations/sqlite/sqlite.d.ts
2235
- interface SqliteOptions {
2164
+ type SqliteOptions = {
2236
2165
  /**
2237
2166
  * Path to a SQL file used to initialize the database schema.
2238
2167
  * Mutually exclusive with `prismaSchema`.
@@ -2245,7 +2174,7 @@ interface SqliteOptions {
2245
2174
  * Mutually exclusive with `init`.
2246
2175
  */
2247
2176
  prismaSchema?: string;
2248
- }
2177
+ };
2249
2178
  declare class SqliteHandle implements DatabasePort, ServiceHandle {
2250
2179
  readonly type = "sqlite";
2251
2180
  composeName: null | string;
@@ -2257,8 +2186,8 @@ declare class SqliteHandle implements DatabasePort, ServiceHandle {
2257
2186
  private db;
2258
2187
  private templatePath;
2259
2188
  private workerDbPath;
2260
- private initSql;
2261
- private prismaSchema;
2189
+ private readonly initSql;
2190
+ private readonly prismaSchema;
2262
2191
  private readonly options;
2263
2192
  constructor(options?: SqliteOptions);
2264
2193
  buildConnectionString(): string;
@@ -2311,7 +2240,7 @@ declare class SqliteHandle implements DatabasePort, ServiceHandle {
2311
2240
  */
2312
2241
  declare function sqlite(options?: SqliteOptions): SqliteHandle;
2313
2242
  //#endregion
2314
- //#region src/core/contracts/filters.d.ts
2243
+ //#region src/specification/contracts/filters.d.ts
2315
2244
  /**
2316
2245
  * A text filter on a provider request builder (`openai.chat({ user })`,
2317
2246
  * `anthropic.messages({ system })`, …).
@@ -2330,13 +2259,12 @@ declare function sqlite(options?: SqliteOptions): SqliteHandle;
2330
2259
  type TextFilter = Matcher | RegExp | string;
2331
2260
  //#endregion
2332
2261
  //#region src/integrations/anthropic/anthropic.d.ts
2333
- interface AnthropicMessagesFilter {
2262
+ type AnthropicMessagesFilter = {
2334
2263
  model?: TextFilter;
2335
2264
  system?: TextFilter;
2336
2265
  user?: TextFilter;
2337
2266
  tools?: string[];
2338
- }
2339
- declare function buildReply(data: unknown): ContractResponse;
2267
+ };
2340
2268
  /**
2341
2269
  * Anthropic API intercept helpers.
2342
2270
  */
@@ -2355,19 +2283,19 @@ declare const anthropic: {
2355
2283
  */
2356
2284
  messages(filter?: AnthropicMessagesFilter, url?: string): ContractRequest;
2357
2285
  /** Response: wrap data in Anthropic messages format. */
2358
- reply: typeof buildReply;
2286
+ reply(data: unknown): ContractResponse;
2359
2287
  /** Response: return an Anthropic error. */
2360
2288
  error(status: number, message?: string): ContractResponse;
2361
2289
  /** Response: simulate a timeout. */
2362
2290
  timeout(): ContractResponse;
2363
2291
  };
2364
2292
  //#endregion
2365
- //#region src/core/contracts/http.d.ts
2293
+ //#region src/specification/contracts/http.d.ts
2366
2294
  /**
2367
2295
  * Request filters for the generic HTTP provider. Every field is a subset
2368
2296
  * constraint — a request matches when all provided fields match.
2369
2297
  */
2370
- interface HttpContractFilter {
2298
+ type HttpContractFilter = {
2371
2299
  /**
2372
2300
  * Body constraint. An object is a deep SUBSET match (toMatchObject-style)
2373
2301
  * whose leaf values may be `match.*` matchers; a string is a containment
@@ -2378,16 +2306,16 @@ interface HttpContractFilter {
2378
2306
  headers?: Record<string, RegExp | string>;
2379
2307
  /** Query-param subset. string = exact value, RegExp = `test()`. */
2380
2308
  query?: Record<string, RegExp | string>;
2381
- }
2309
+ };
2382
2310
  /** Init options shared by the response builders. */
2383
- interface HttpResponseInit {
2311
+ type HttpResponseInit = {
2384
2312
  /** Delay in ms before responding (for timeout testing). */
2385
2313
  delay?: number;
2386
2314
  /** Response headers, merged over the builder's own. */
2387
2315
  headers?: Record<string, string>;
2388
2316
  /** HTTP status code. */
2389
2317
  status?: number;
2390
- }
2318
+ };
2391
2319
  /**
2392
2320
  * Generic HTTP contract helpers for any URL. The url is absolute (string or
2393
2321
  * RegExp), or a PATH FORM starting with `/` — `http.get('/articles/{{uuid}}')`
@@ -2418,20 +2346,19 @@ declare const http: {
2418
2346
  };
2419
2347
  //#endregion
2420
2348
  //#region src/integrations/openai/openai.d.ts
2421
- interface OpenAIChatFilter {
2349
+ type OpenAIChatFilter = {
2422
2350
  model?: TextFilter;
2423
2351
  system?: TextFilter;
2424
2352
  user?: TextFilter;
2425
2353
  tools?: string[];
2426
2354
  temperature?: number;
2427
- }
2428
- interface OpenAIResponsesFilter {
2355
+ };
2356
+ type OpenAIResponsesFilter = {
2429
2357
  model?: TextFilter;
2430
2358
  system?: TextFilter;
2431
2359
  user?: TextFilter;
2432
2360
  tools?: string[];
2433
- }
2434
- declare function buildChatReply(data: unknown): ContractResponse;
2361
+ };
2435
2362
  /**
2436
2363
  * OpenAI API intercept helpers.
2437
2364
  */
@@ -2467,7 +2394,7 @@ declare const openai: {
2467
2394
  * @example
2468
2395
  * openai.reply({ categories: ['TECH'] })
2469
2396
  */
2470
- reply: typeof buildChatReply;
2397
+ reply(data: unknown): ContractResponse;
2471
2398
  /** Response: return an OpenAI error. */
2472
2399
  error(status: number, message?: string): ContractResponse;
2473
2400
  /** Response: return malformed content. */
@@ -2487,12 +2414,12 @@ declare const mockOf: MockPort;
2487
2414
  //#endregion
2488
2415
  //#region src/vitest/mock-of-date.d.ts
2489
2416
  /** Interface for freezing and resetting the global Date in tests. */
2490
- interface MockDatePort {
2417
+ type MockDatePort = {
2491
2418
  /** Restore the real Date object. */
2492
2419
  reset: () => void;
2493
2420
  /** Freeze `Date.now()` and `new Date()` to the given value. */
2494
2421
  set: (date: Date | number | string) => void;
2495
- }
2422
+ };
2496
2423
  /**
2497
2424
  * Freeze or reset the global Date for deterministic time-dependent tests.
2498
2425
  * Wraps the `mockdate` package.