@orkestrel/probe 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,941 @@
1
+ import { Case } from '@orkestrel/probe';
2
+ import { Case as Case_2 } from '@orkestrel/probe';
3
+ import { Check } from '@orkestrel/probe';
4
+ import { Check as Check_2 } from '@orkestrel/probe';
5
+ import { Claim } from '@orkestrel/probe';
6
+ import { EmitterInterface } from '@orkestrel/emitter';
7
+ import { EventEmitter } from 'node:events';
8
+ import { ProbeEventMap } from '@orkestrel/probe';
9
+ import { ProbeInterface } from '@orkestrel/probe';
10
+ import { ProbeOptions } from '@orkestrel/probe';
11
+ import { Project } from '@orkestrel/probe';
12
+ import { Project as Project_2 } from '@orkestrel/probe';
13
+ import { Stage } from '@orkestrel/probe';
14
+ import { Stage as Stage_2 } from '@orkestrel/probe';
15
+ import { Toolchain } from '@orkestrel/probe';
16
+ import type * as TypeScript from 'typescript';
17
+ import { Verdict } from '@orkestrel/probe';
18
+ import type * as VitestNode from 'vitest/node';
19
+
20
+ /**
21
+ * Records the listeners one emitter carries for a set of events.
22
+ *
23
+ * @param emitter - The emitter to read
24
+ * @param events - The event names to record
25
+ * @returns The listeners each named event carries at the moment of the call
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * const capture = captureListeners(process, ['SIGTERM'])
30
+ * capture.get('SIGTERM')?.length === process.listenerCount('SIGTERM') // true
31
+ * ```
32
+ */
33
+ export declare function captureListeners(emitter: EventEmitter, events: readonly string[]): ListenerCapture;
34
+
35
+ /**
36
+ * Computes the canonical digest of one value as it stands in a target workspace.
37
+ *
38
+ * @remarks
39
+ * Digests the normalized value rather than the value itself, so one commit read at two absolute
40
+ * roots produces one digest and a digest carries no host path. The result is the leading 32 hex
41
+ * characters of a SHA-256 over the normalized JSON.
42
+ *
43
+ * @param workspace - The target workspace root the value's paths are read against
44
+ * @param value - The value to digest
45
+ * @returns 32 lowercase hex characters
46
+ *
47
+ * @example
48
+ * ```ts
49
+ * computeDigest('/srv/checkout', { strict: true }).length // 32
50
+ * ```
51
+ */
52
+ export declare function computeDigest(workspace: string, value: unknown): string;
53
+
54
+ /**
55
+ * Creates a fresh sibling identity while preserving a test's resolution directory.
56
+ *
57
+ * @param workspace - The target workspace root
58
+ * @param path - The workspace-relative test path
59
+ * @param revision - The fresh revision identity
60
+ * @returns An absolute sibling file path
61
+ *
62
+ * @example
63
+ * ```ts
64
+ * relativeWorkspaceFile(
65
+ * '/srv/checkout',
66
+ * createRevisionFile('/srv/checkout', 'tmp/probe/greeting.test.ts', '4821-9f0c'),
67
+ * )
68
+ * // 'tmp/probe/greeting.test.probe-4821-9f0c.ts'
69
+ * ```
70
+ */
71
+ export declare function createRevisionFile(workspace: string, path: string, revision: string): string;
72
+
73
+ /**
74
+ * Normalizes a caught or foreign error into readable text.
75
+ *
76
+ * @param value - The value to describe
77
+ * @returns Its message when present, or its string representation
78
+ *
79
+ * @example
80
+ * ```ts
81
+ * describeUnknown(new Error('The lint stage has been destroyed'))
82
+ * // 'The lint stage has been destroyed'
83
+ * describeUnknown(17) // '17'
84
+ * ```
85
+ */
86
+ export declare function describeUnknown(value: unknown): string;
87
+
88
+ /**
89
+ * Names every draft member of a claim-shaped value whose `path` this package's guard refuses.
90
+ *
91
+ * @remarks
92
+ * The published claim schema constrains `Draft.path` to a non-empty string and nothing else, while
93
+ * `isDraft` also refuses an absolute path and one that escapes the workspace. That one member is
94
+ * the whole of the difference between the advertised contract and the enforced one, so a caller
95
+ * refused after satisfying the schema is refused here and nowhere else. The whole input must first
96
+ * satisfy that schema. Each draft is then tested with `isDraft` itself rather than with a second
97
+ * copy of its rule. Reports nothing for a value carrying no draft member, including one refused for
98
+ * a member this contract does not declare.
99
+ *
100
+ * This function lives in the server helpers rather than in core: core's leaf pair, `helpers.ts` and
101
+ * `validators.ts`, may not consume the shapes module this function reads `CLAIM_SHAPE` from, and
102
+ * `ProbeServer` is the sole consumer of the result it returns.
103
+ *
104
+ * @param value - The rejected tool input
105
+ * @returns The dotted member names, in `case` then `control` order, or an empty list
106
+ *
107
+ * @example
108
+ * ```ts
109
+ * const test = { path: 'tmp/probe/greeting.test.ts', text: '' }
110
+ * findRefusedPaths({
111
+ * project: 'tsconfig.json',
112
+ * case: { files: [{ path: '../../etc/hosts', text: '' }], test },
113
+ * control: { files: [], test, stage: 'type', reason: 'must not compile' },
114
+ * })
115
+ * // ['case.files.0.path']
116
+ * ```
117
+ */
118
+ export declare function findRefusedPaths(value: unknown): readonly string[];
119
+
120
+ /**
121
+ * Selects the Language Server Protocol language identifier for a source path.
122
+ *
123
+ * @param path - The source path whose extension selects the language
124
+ * @returns The matching JavaScript or TypeScript language identifier
125
+ *
126
+ * @example
127
+ * ```ts
128
+ * inferDocumentLanguage('src/core/greeting.ts') // 'typescript'
129
+ * inferDocumentLanguage('src/browser/Panel.tsx') // 'typescriptreact'
130
+ * ```
131
+ */
132
+ export declare function inferDocumentLanguage(path: string): string;
133
+
134
+ /**
135
+ * Selects the Vitest project whose environment matches one test path.
136
+ *
137
+ * @remarks
138
+ * There is no root-project fallback. A path no configured project collects returns `undefined`,
139
+ * and the runtime stage refuses that claimant test with `code: 'missing'` rather than running it
140
+ * somewhere the workspace never configured.
141
+ *
142
+ * @param path - The workspace-relative test path
143
+ * @returns The matching project's name, or `undefined` when no configured project collects the path
144
+ *
145
+ * @example
146
+ * ```ts
147
+ * inferTestProject('tmp/probe/greeting.test.ts') // 'probe'
148
+ * inferTestProject('tests/src/server/helpers.test.ts') // 'src:server'
149
+ * inferTestProject('tests/config.test.ts') // undefined
150
+ * ```
151
+ */
152
+ export declare function inferTestProject(path: string): string | undefined;
153
+
154
+ /**
155
+ * Selects the scoped TypeScript project for one candidate draft path.
156
+ *
157
+ * @param path - The workspace-relative candidate draft path
158
+ * @returns The workspace-relative scoped project path
159
+ * @throws When the path does not name a configured source or application environment
160
+ *
161
+ * @example
162
+ * ```ts
163
+ * inferTypeProject('src/core/greeting.ts') // 'configs/src/tsconfig.core.json'
164
+ * inferTypeProject('tests/src/core/greeting.test.ts')
165
+ * // throws: Cannot infer a scoped TypeScript project for tests/src/core/greeting.test.ts
166
+ * ```
167
+ */
168
+ export declare function inferTypeProject(path: string): string;
169
+
170
+ /**
171
+ * Carries one queued inspection: the case a stage reads and the claim it belongs to.
172
+ *
173
+ * @remarks
174
+ * The coordinator admits one of these per stage at a time. Every stage reads `subject`; the type
175
+ * stage also reads `claim.project`, and the runtime stage reports `claim` when its deadline fires.
176
+ * A coordinator such as `Probe` mints an inspection when it admits a claim to a stage.
177
+ *
178
+ * @example
179
+ * ```ts
180
+ * const inspection: Inspection = { subject: claim.case, claim }
181
+ * ```
182
+ */
183
+ export declare interface Inspection {
184
+ /** The candidate drafts and test one stage inspects. */
185
+ readonly subject: Case;
186
+ /** The claim the subject belongs to. */
187
+ readonly claim: Claim;
188
+ }
189
+
190
+ /**
191
+ * Inspects virtual documents through one resident Oxlint language server.
192
+ *
193
+ * @remarks
194
+ * Construction starts the target workspace's Oxlint binary with its Language Server Protocol
195
+ * mode. Each inspection opens the supplied text by URI, waits for published diagnostics, and
196
+ * closes the document without writing it to disk.
197
+ *
198
+ * Teardown is bounded at both ends. Warming waits for the server's `initialize` response and the
199
+ * ending waits for its `shutdown` reply, and the protocol leaves both answers to the server, so
200
+ * `destroy` bounds each exchange and takes the ending back with a signal when the bound passes.
201
+ *
202
+ * The URI is the path the candidate declared, so the candidate receives exactly the rule set the
203
+ * workspace's own lint gate applies to that path, including an override anchored to one directory,
204
+ * one file name, or one exact file. A path the workspace's version-control ignore files exclude is
205
+ * a path that gate never lints, and this stage reports the same nothing for it. Reuse of one
206
+ * declared path across inspections is what the caller's own serialization protects: the stage
207
+ * refuses a second inspection of a path it already has open rather than answering either one with
208
+ * the other's diagnostics.
209
+ *
210
+ * @example
211
+ * ```ts
212
+ * const stage = new LintStage('/srv/checkout')
213
+ * const check = await stage.inspect(subject)
214
+ * await stage.destroy()
215
+ * ```
216
+ */
217
+ export declare class LintStage implements StageInterface {
218
+ #private;
219
+ /**
220
+ * Starts warming the target workspace's Oxlint language server.
221
+ *
222
+ * @param workspace - The target workspace root. Default: the current working directory
223
+ */
224
+ constructor(workspace?: string);
225
+ get stage(): Stage_2;
226
+ get progress(): number;
227
+ inspect(subject: Case_2): Promise<Check_2>;
228
+ destroy(): Promise<void>;
229
+ }
230
+
231
+ /**
232
+ * Holds the listeners one emitter carried for a set of events at the moment it was captured.
233
+ *
234
+ * @remarks
235
+ * A capture is the before half of a listener diff. `captureListeners` takes one, and
236
+ * `releaseListeners` removes whatever the emitter has gained since. Identity is what the pair
237
+ * compares, so a listener a capture holds survives the release whatever it is named.
238
+ *
239
+ * The pair reads a gain as the callee's own, so it binds its caller to a window nothing else can
240
+ * attach in. Where a caller cannot promise that window, hold each handler as a field and remove it
241
+ * by reference instead.
242
+ *
243
+ * @example
244
+ * ```ts
245
+ * const capture: ListenerCapture = new Map([['SIGTERM', process.listeners('SIGTERM')]])
246
+ * ```
247
+ */
248
+ export declare type ListenerCapture = ReadonlyMap<string, readonly Function[]>;
249
+
250
+ /**
251
+ * Loads one installed tool module from a target workspace.
252
+ *
253
+ * @param workspace - The target workspace root
254
+ * @param specifier - The module specifier to load
255
+ * @returns The installed module
256
+ * @throws When the workspace cannot load the module
257
+ *
258
+ * @example
259
+ * ```ts
260
+ * const typescript = loadWorkspaceModule(process.cwd(), 'typescript')
261
+ * console.log(typescript.version)
262
+ * ```
263
+ */
264
+ export declare function loadWorkspaceModule(workspace: string, specifier: 'typescript'): typeof TypeScript;
265
+
266
+ export declare function loadWorkspaceModule(workspace: string, specifier: 'vitest/node'): typeof VitestNode;
267
+
268
+ /**
269
+ * Reports whether a path is a workspace module Vitest can cache.
270
+ *
271
+ * @param path - The candidate file path
272
+ * @returns True for script, TypeScript, Vue, and JSON modules; false otherwise
273
+ *
274
+ * @example
275
+ * ```ts
276
+ * matchesWorkspaceModule('src/core/greeting.ts') // true
277
+ * matchesWorkspaceModule('src/styles/tokens.css') // false
278
+ * ```
279
+ */
280
+ export declare function matchesWorkspaceModule(path: string): boolean;
281
+
282
+ /**
283
+ * Rewrites one path into the forward-slash spelling this package compares and reports paths in.
284
+ *
285
+ * @remarks
286
+ * Windows reports a path with backslashes and accepts either separator, so two spellings of one
287
+ * file compare unequal until they are rewritten. Every path this package keys a map on, matches a
288
+ * prefix against, or hands a caller passes through here first.
289
+ *
290
+ * @param path - The path to rewrite
291
+ * @returns The same path with every backslash replaced by a forward slash
292
+ *
293
+ * @example
294
+ * ```ts
295
+ * normalizePath('src\\core\\greeting.ts') // 'src/core/greeting.ts'
296
+ * normalizePath('src/core/greeting.ts') // 'src/core/greeting.ts'
297
+ * ```
298
+ */
299
+ export declare function normalizePath(path: string): string;
300
+
301
+ /**
302
+ * Rewrites every workspace-contained absolute path in a value to its workspace-relative form and
303
+ * sorts every record's keys.
304
+ *
305
+ * @remarks
306
+ * A parsed TypeScript project carries the absolute root it was parsed at, so the same commit
307
+ * checked out at two paths produces two values that describe one configuration. Rewriting those
308
+ * members makes the value portable and stops a host path leaking into anything derived from it. A
309
+ * path that escapes the workspace is left as it stands, because rewriting it would name a file
310
+ * outside the tree by a relative spelling. Key order is the parser's, not the configuration's, so
311
+ * sorting removes a difference that means nothing.
312
+ *
313
+ * @param workspace - The target workspace root
314
+ * @param value - The value to rewrite
315
+ * @returns The value with contained absolute paths made relative and every record key sorted
316
+ *
317
+ * @example
318
+ * ```ts
319
+ * normalizeValue('/srv/checkout', { rootDir: '/srv/checkout/src/core', strict: true })
320
+ * // { rootDir: 'src/core', strict: true }
321
+ * ```
322
+ */
323
+ export declare function normalizeValue(workspace: string, value: unknown): unknown;
324
+
325
+ /**
326
+ * Holds the candidate drafts one inspection substitutes for the files a tool would read from disk.
327
+ *
328
+ * @remarks
329
+ * One overlay belongs to one inspection: the inspection creates it, records every candidate it
330
+ * carries, and clears it when the inspection ends. Each stage adapts the same set to the host its
331
+ * own tool expects rather than sharing one filesystem across tools. A fresh identity per instance
332
+ * is what makes a resident tool re-read a path this overlay holds, so two inspections that supply
333
+ * different text for one path never share a cached answer.
334
+ *
335
+ * @example
336
+ * ```ts
337
+ * const overlay = new Overlay()
338
+ * overlay.set('/srv/checkout/src/core/greeting.ts', "export const GREETING = 'hi'\n")
339
+ * console.log(overlay.text('/srv/checkout/src/core/greeting.ts'))
340
+ * overlay.clear()
341
+ * ```
342
+ */
343
+ export declare class Overlay implements OverlayInterface {
344
+ #private;
345
+ get revision(): string;
346
+ get paths(): readonly string[];
347
+ /**
348
+ * Records one candidate's text against the absolute path it stands in for.
349
+ *
350
+ * @param path - The absolute path the candidate replaces
351
+ * @param text - The candidate's full contents
352
+ * @returns Nothing
353
+ */
354
+ set(path: string, text: string): void;
355
+ /**
356
+ * Reads the candidate text recorded for one absolute path.
357
+ *
358
+ * @param path - The absolute path to read
359
+ * @returns The recorded text, or `undefined` when this overlay holds no candidate there
360
+ */
361
+ text(path: string): string | undefined;
362
+ /**
363
+ * Checks whether a candidate sits beneath one directory.
364
+ *
365
+ * @remarks
366
+ * The answer is derived from the paths the overlay holds rather than stored, so it stops being
367
+ * true exactly when the inspection that declared those candidates clears them. Both sides pass
368
+ * through `normalizePath` first, because a tool that normalizes its own paths asks about a
369
+ * directory in a spelling the recorded path may not share.
370
+ *
371
+ * @param directory - The absolute directory path to check
372
+ * @returns True if a candidate path sits beneath the directory; false otherwise
373
+ */
374
+ covers(directory: string): boolean;
375
+ /**
376
+ * Releases every candidate.
377
+ *
378
+ * @returns Nothing
379
+ */
380
+ clear(): void;
381
+ }
382
+
383
+ /**
384
+ * Holds the candidate drafts one inspection substitutes for the files a tool would read from disk.
385
+ *
386
+ * @remarks
387
+ * A stage records every candidate the inspection carries before it reads any of them, and clears
388
+ * the set when the inspection ends, whatever ended it. The entity names no tool: each stage adapts
389
+ * one overlay to the host its own tool expects, so a language service, a document protocol, and a
390
+ * module resolver read one candidate set through their own adapters rather than through one shared
391
+ * filesystem. Paths are absolute and the stage resolves them, because only the stage knows the
392
+ * workspace a candidate's declared path is relative to. `revision` identifies the set, so a
393
+ * resident tool that caches by version reads fresh text for a path this overlay holds and reads
394
+ * disk again after `clear`.
395
+ *
396
+ * Mint one overlay per inspection and release it when that inspection ends. An instance shared
397
+ * across inspections keeps the identity a resident tool caches against, so the second inspection
398
+ * reads the first one's answer as a fresh one. `Overlay` implements this contract.
399
+ *
400
+ * @example
401
+ * ```ts
402
+ * const overlay: OverlayInterface = new Overlay()
403
+ * overlay.set('/srv/checkout/src/core/greeting.ts', "export const GREETING = 'hi'\n")
404
+ * overlay.covers('/srv/checkout/src/core') // true
405
+ * overlay.clear()
406
+ * ```
407
+ */
408
+ export declare interface OverlayInterface {
409
+ /** Identity of the candidate set this overlay holds. */
410
+ readonly revision: string;
411
+ /** Absolute path of every candidate this overlay holds. */
412
+ readonly paths: readonly string[];
413
+ /**
414
+ * Records one candidate's text against the absolute path it stands in for.
415
+ *
416
+ * @param path - The absolute path the candidate replaces
417
+ * @param text - The candidate's full contents
418
+ * @returns Nothing
419
+ */
420
+ set(path: string, text: string): void;
421
+ /**
422
+ * Reads the candidate text recorded for one absolute path.
423
+ *
424
+ * @param path - The absolute path to read
425
+ * @returns The recorded text, or `undefined` when this overlay holds no candidate there
426
+ */
427
+ text(path: string): string | undefined;
428
+ /**
429
+ * Checks whether a candidate sits beneath one directory.
430
+ *
431
+ * @remarks
432
+ * A tool asking whether a directory exists is answered from disk first, so this reports only
433
+ * the directories the candidate set adds. A directory listing is a separate question and stays
434
+ * on disk: a candidate that entered one would outlive the inspection that declared it.
435
+ *
436
+ * @param directory - The absolute directory path to check
437
+ * @returns True if a candidate path sits beneath the directory; false otherwise
438
+ */
439
+ covers(directory: string): boolean;
440
+ /**
441
+ * Releases every candidate.
442
+ *
443
+ * @returns Nothing
444
+ */
445
+ clear(): void;
446
+ }
447
+
448
+ /**
449
+ * Reads a JSON-RPC frame's declared byte length.
450
+ *
451
+ * @param header - The frame header text without its terminating empty line
452
+ * @returns The non-negative content length, or `undefined` for an invalid header
453
+ *
454
+ * @example
455
+ * ```ts
456
+ * parseContentLength('Content-Length: 128') // 128
457
+ * parseContentLength('Content-Type: application/json') // undefined
458
+ * ```
459
+ */
460
+ export declare function parseContentLength(header: string): number | undefined;
461
+
462
+ /**
463
+ * Answers claims through resident TypeScript, Oxlint, and Vitest stages.
464
+ *
465
+ * @remarks
466
+ * Construction resolves the target workspace's toolchain and begins warming every stage. The boot
467
+ * controls mutate imported dependencies and refuse service unless the type and runtime stages
468
+ * report their respective changes. One queue per stage admits inspections in arrival order, one at
469
+ * a time, so a stage never serves two claims at once and the deadline covers active work rather
470
+ * than queue wait. Each active stage inspection has a coordinator-owned deadline. An expiry at any
471
+ * stage abandons that stage and replaces it before the next queued inspection begins, so one slow
472
+ * claim costs that claim rather than the process. A failed boot is replaced the same way: the next
473
+ * claim runs the controls again rather than inheriting a refusal.
474
+ *
475
+ * @example
476
+ * ```ts
477
+ * const probe = new Probe({ workspace: '/srv/checkout' })
478
+ * const verdict = await probe.prove(claim)
479
+ * await probe.destroy()
480
+ * ```
481
+ */
482
+ export declare class Probe implements ProbeInterface {
483
+ #private;
484
+ /**
485
+ * Resolves the target toolchain and starts warming the resident stages.
486
+ *
487
+ * @param options - Workspace, deadline, and initial observation hooks
488
+ */
489
+ constructor(options?: ProbeOptions);
490
+ get emitter(): EmitterInterface<ProbeEventMap>;
491
+ get toolchain(): Toolchain;
492
+ prove(claim: Claim): Promise<Verdict>;
493
+ destroy(): Promise<void>;
494
+ }
495
+
496
+ /**
497
+ * Serves one probe over this process's Model Context Protocol stdio transport.
498
+ *
499
+ * @remarks
500
+ * Construction creates the probe, publishes the `prove` tool, and binds the dual-era dispatcher to
501
+ * the stdio transport. The probe begins warming there, so a harness that spawns the entry pays
502
+ * arming while its client is still handshaking.
503
+ *
504
+ * `start` seizes standard input and registers the signals a harness ends a child with, and records
505
+ * whether standard input was already flowing. Every listener it attaches is held as a field, and
506
+ * `destroy` removes exactly those by reference, so a listener a host registers while this server is
507
+ * serving is still attached and still fires afterwards. The transport reads a stream this server
508
+ * owns rather than this process's standard input, which is what makes that possible: the
509
+ * transport's own listeners land on that stream, and the only listeners this server ever puts on
510
+ * `process.stdin` are the `data`, `close`, and `error` forwarders into it. `destroy` pauses the
511
+ * stream only when this server is what set it flowing and nothing else is reading it, so a process
512
+ * that outlives this server reads its own standard input again and receives its own signals.
513
+ * Teardown removes the signal handlers first: it takes seconds when a boot is in flight, and a
514
+ * harness that signals again means to kill rather than to queue.
515
+ *
516
+ * @example
517
+ * ```ts
518
+ * const server = new ProbeServer({ workspace: process.cwd() })
519
+ * server.start()
520
+ * await server.destroy()
521
+ * ```
522
+ */
523
+ export declare class ProbeServer implements ProbeServerInterface {
524
+ #private;
525
+ /**
526
+ * Creates the probe this server publishes and binds it to this process's stdio transport.
527
+ *
528
+ * @param options - Workspace, deadline, and initial observation hooks for the probe it creates
529
+ */
530
+ constructor(options?: ProbeOptions);
531
+ start(): void;
532
+ destroy(): Promise<void>;
533
+ }
534
+
535
+ /**
536
+ * Serves one probe over this process's Model Context Protocol stdio transport.
537
+ *
538
+ * @remarks
539
+ * The server owns the process it runs in. `start` seizes standard input and standard output for
540
+ * the transport and registers the termination handlers a harness signals, so a host that starts one
541
+ * has already given the process to it. `destroy` reverses all of that and tears the probe down with
542
+ * it, which is why there is no verb that stops serving and leaves the resident engines running: a
543
+ * probe nothing is reading from holds its resident tools for nobody.
544
+ *
545
+ * @example
546
+ * ```ts
547
+ * const server = new ProbeServer({ workspace: process.cwd() })
548
+ * server.start()
549
+ * await server.destroy()
550
+ * ```
551
+ */
552
+ export declare interface ProbeServerInterface {
553
+ /**
554
+ * Serves the probe over this process's standard input and output.
555
+ *
556
+ * @remarks
557
+ * Reads newline-delimited JSON requests from standard input, and answers a `SIGINT` or a
558
+ * `SIGTERM` by destroying the server. Calling this on a server already serving does nothing.
559
+ * Calling it after teardown begins throws a claimant-owned `destroyed` failure.
560
+ *
561
+ * @returns Nothing
562
+ * @throws When this call comes after teardown begins
563
+ */
564
+ start(): void;
565
+ /**
566
+ * Releases the transport, the process listeners, and the probe behind them.
567
+ *
568
+ * @remarks
569
+ * Settling is idempotent: a call made while teardown is running joins it and returns the same
570
+ * promise, and a call made afterwards returns that settled promise. The server removes exactly the
571
+ * listeners it attached, holding each one as a field rather than choosing by absence from a
572
+ * capture, so a host that keeps running after this call reads its own standard input again and
573
+ * receives its own signals, and a listener the host registered while
574
+ * the server was serving is still attached and still fires. That covers the stream's flow as
575
+ * well as its listeners: standard input is left flowing when it was already flowing before
576
+ * `start` and when something else is reading it at release, and a stream nothing had read yet
577
+ * and nothing else reads is paused.
578
+ *
579
+ * @returns A promise that settles after the probe releases its resident engines
580
+ */
581
+ destroy(): Promise<void>;
582
+ }
583
+
584
+ /**
585
+ * Reads one installed package manifest from the target workspace.
586
+ *
587
+ * @param workspace - The target workspace root
588
+ * @param name - The installed package name
589
+ * @returns The manifest's absolute path and parsed record
590
+ * @throws When the package cannot be resolved, read, parsed, or does not publish a record manifest
591
+ *
592
+ * @example
593
+ * ```ts
594
+ * const manifest = readWorkspaceManifest(process.cwd(), 'typescript')
595
+ * console.log(manifest.contents.version)
596
+ * ```
597
+ */
598
+ export declare function readWorkspaceManifest(workspace: string, name: string): WorkspaceManifest;
599
+
600
+ /**
601
+ * Projects an absolute tool path into the workspace-relative form issues expose.
602
+ *
603
+ * @param workspace - The target workspace root
604
+ * @param file - The path a tool reported
605
+ * @returns A forward-slash workspace-relative path
606
+ *
607
+ * @example
608
+ * ```ts
609
+ * relativeWorkspaceFile('/srv/checkout', '/srv/checkout/src/core/greeting.ts')
610
+ * // 'src/core/greeting.ts'
611
+ * ```
612
+ */
613
+ export declare function relativeWorkspaceFile(workspace: string, file: string): string;
614
+
615
+ /**
616
+ * Removes every listener one emitter gained for the captured events since its capture.
617
+ *
618
+ * @remarks
619
+ * Compares identity rather than name, so a listener whose name a dependency changes between
620
+ * releases is still recognized and a listener that merely shares a name is still left alone. A
621
+ * listener the capture already held survives, and a listener the capture held that has since been
622
+ * removed is not restored.
623
+ *
624
+ * A gain is not evidence of who caused it, so this reads every gain as the callee's. Call it only
625
+ * across a window nothing else can attach in — a synchronous call, or a call whose handlers land
626
+ * before its first await and that is released as it returns — because a listener anyone else
627
+ * attaches inside that window is removed with the rest. Where a caller cannot promise that window,
628
+ * hold each handler as a field and remove it by reference instead of capturing at all.
629
+ *
630
+ * @param emitter - The emitter to release
631
+ * @param capture - The listeners the emitter carried at capture time
632
+ * @returns Nothing
633
+ *
634
+ * @example
635
+ * ```ts
636
+ * const capture = captureListeners(process, ['SIGTERM'])
637
+ * process.on('SIGTERM', () => {})
638
+ * releaseListeners(process, capture)
639
+ * process.listenerCount('SIGTERM') === capture.get('SIGTERM')?.length // true
640
+ * ```
641
+ */
642
+ export declare function releaseListeners(emitter: EventEmitter, capture: ListenerCapture): void;
643
+
644
+ /**
645
+ * Resolves a package's portable JavaScript binary from the target workspace.
646
+ *
647
+ * @param workspace - The target workspace root
648
+ * @param name - The installed package name and binary key
649
+ * @returns The absolute JavaScript entry named by the package's `bin` field
650
+ * @throws When the package does not publish a binary under the requested name
651
+ *
652
+ * @example
653
+ * ```ts
654
+ * relativeWorkspaceFile(process.cwd(), resolveWorkspaceBinary(process.cwd(), 'oxlint'))
655
+ * // 'node_modules/oxlint/bin/oxlint'
656
+ * resolveWorkspaceBinary(process.cwd(), 'typescript')
657
+ * // throws: typescript does not publish the typescript binary
658
+ * ```
659
+ */
660
+ export declare function resolveWorkspaceBinary(workspace: string, name: string): string;
661
+
662
+ /**
663
+ * Resolves a path inside a target workspace and rejects traversal outside it.
664
+ *
665
+ * @remarks
666
+ * A native `ENAMETOOLONG` or NUL-byte `ERR_INVALID_ARG_VALUE` fault is a claimant refusal. This
667
+ * classifies the fault, not the author: a workspace nested deeply enough that this package's short
668
+ * generated names overflow the host limit also reads as a claimant refusal.
669
+ *
670
+ * @param workspace - The target workspace root
671
+ * @param target - A workspace-relative path
672
+ * @param mutate - If `true`, refuses a symbolic link in an existing descendant; if `false`,
673
+ * resolves lexically. Default: `false`
674
+ * @returns The absolute contained path
675
+ * @throws When the path resolves outside the workspace, mutation would cross a symbolic link, or
676
+ * the target tree cannot be inspected
677
+ *
678
+ * @example
679
+ * ```ts
680
+ * resolveWorkspaceFile('/srv/checkout', 'src/core/greeting.ts')
681
+ * // '/srv/checkout/src/core/greeting.ts'
682
+ * resolveWorkspaceFile('/srv/checkout', '../secrets.env')
683
+ * // throws: Path escapes the workspace: ../secrets.env
684
+ * ```
685
+ */
686
+ export declare function resolveWorkspaceFile(workspace: string, target: string, mutate?: boolean): string;
687
+
688
+ /**
689
+ * Resolves one installed module from the target workspace.
690
+ *
691
+ * @param workspace - The target workspace root
692
+ * @param specifier - The module specifier to resolve
693
+ * @returns The installed module entry path
694
+ * @throws When the workspace does not install the module
695
+ *
696
+ * @example
697
+ * ```ts
698
+ * resolveWorkspaceModule(process.cwd(), 'typescript/package.json') ===
699
+ * readWorkspaceManifest(process.cwd(), 'typescript').path // true
700
+ * ```
701
+ */
702
+ export declare function resolveWorkspaceModule(workspace: string, specifier: string): string;
703
+
704
+ /**
705
+ * Inspects tests through one resident Vitest service from the target workspace.
706
+ *
707
+ * @remarks
708
+ * Construction starts Vitest with the threads pool and instruments each inline or function-declared
709
+ * project with a Vite plugin that reads the active inspection's candidate overlay. A selected
710
+ * string-declared project reports a workspace issue because its configuration carries no runtime
711
+ * overlay plugin. Every inspection writes one fresh sibling specification, invalidates each
712
+ * workspace module whose disk content or candidate revision changed, runs that specification,
713
+ * evicts its result, and deletes the file. Clearing the overlay makes the next snapshot differ from
714
+ * the candidate revision, so the next inspection invalidates that module and reads disk again.
715
+ *
716
+ * Vite retains one unresolved URL for every specification path, so the stage replaces its whole
717
+ * Vitest service after 64 specifications rather than deleting from each map that service owns. Any
718
+ * bound holds the retention flat, because the replacement releases everything the instance held,
719
+ * and 64 is the value chosen. The inspection that crosses the bound pays the replacement
720
+ * synchronously: it closes the resident service and warms a new one before it runs. On 2026-08-20,
721
+ * over this package's own workspace on the host `guides/probe.md` § Cost names, two runs of 66
722
+ * inspections put that one at 480 ms and 269 ms against median inspections of 156 ms and 155 ms, so
723
+ * budget one call in 64 at 110 ms to 330 ms above the rest.
724
+ *
725
+ * Every issue this stage emits names its party, and the stage emits an issue for every party
726
+ * `Party` declares. An `origin: 'claimant'` issue is a failure Vitest reported about the candidate.
727
+ * An `origin: 'workspace'` issue names the target tree: a project the root configuration declares
728
+ * as a path string, into which the stage can install no overlay, and a specification path that
729
+ * crosses a symbolic link, whose existing components this host cannot inspect, or whose directory
730
+ * the target tree blocks probe from creating. An `origin: 'instrument'` issue names this package's
731
+ * own machinery: a specification it could not write after its directory exists, or one it could
732
+ * not run, evict, or delete, in each case for a reason the target tree does not own, and a run
733
+ * that reported no test. An unmapped caller test and a test whose named project is absent are claimant failures with
734
+ * `code: 'missing'`, thrown rather than reported, because a test that never ran is no evidence
735
+ * about the candidate.
736
+ *
737
+ * The write path's physical-containment guarantee covers the claim inputs and the target tree as
738
+ * inspected. A concurrent process that mutates a path component between the final inspection and
739
+ * the write is outside that guarantee.
740
+ *
741
+ * @example
742
+ * ```ts
743
+ * const stage = new RuntimeStage('/srv/checkout')
744
+ * const check = await stage.inspect(subject)
745
+ * await stage.destroy()
746
+ * ```
747
+ */
748
+ export declare class RuntimeStage implements StageInterface {
749
+ #private;
750
+ /**
751
+ * Starts warming the target workspace's Vitest service.
752
+ *
753
+ * @param workspace - The target workspace root. Default: the current working directory
754
+ */
755
+ constructor(workspace?: string);
756
+ get stage(): Stage_2;
757
+ get progress(): number;
758
+ inspect(subject: Case_2): Promise<Check_2>;
759
+ destroy(): Promise<void>;
760
+ }
761
+
762
+ /**
763
+ * Inspects one case with a resident workspace tool.
764
+ *
765
+ * @remarks
766
+ * Warming begins at construction. The `inspect` method awaits that one warm operation and reuses
767
+ * the resulting tool across calls. A stage serves one inspection at a time and admits none itself.
768
+ * Await an inspection before starting the next one, or admit through one queue per stage the way
769
+ * `Probe` does: a second concurrent call reaches the same resident tool and the same overlay,
770
+ * document, and specification state the first is still using. A stage never holds a later
771
+ * inspection behind an earlier one, so a caller that abandons an inspection at its own deadline
772
+ * can still use the stage. The `destroy` method permanently tears the stage down and releases
773
+ * every resource it owns.
774
+ *
775
+ * @example
776
+ * ```ts
777
+ * const check = await stage.inspect(subject)
778
+ * console.log(check.stage)
779
+ * await stage.destroy()
780
+ * ```
781
+ */
782
+ export declare interface StageInterface {
783
+ /** The inspection this resident stage performs. */
784
+ readonly stage: Stage;
785
+ /**
786
+ * Claimant-owned progress the coordinator compares with its inspection snapshot.
787
+ *
788
+ * @remarks
789
+ * When claimant-owned work is admitted, raise `progress` before awaiting its result. When this
790
+ * stage later performs stage-owned awaited work, return `progress` to its pre-inspection reading
791
+ * before that work starts. `RuntimeStage` does this before eviction and cleanup, so an expiry
792
+ * during that work reads level with the coordinator's snapshot and is attributed to the
793
+ * instrument rather than to the claimant.
794
+ */
795
+ readonly progress: number;
796
+ /**
797
+ * Inspects one case.
798
+ *
799
+ * @param subject - The candidate drafts and test to inspect
800
+ * @returns One outcome for this stage
801
+ * @throws When the resident tool cannot start or has already been destroyed
802
+ */
803
+ inspect(subject: Case): Promise<Check>;
804
+ /**
805
+ * Tears down the resident tool and releases its resources.
806
+ *
807
+ * @remarks
808
+ * A stage abandons every inspection it holds rather than waiting behind one, so teardown never
809
+ * waits for an inspection to return. An abandoned inspection rejects, either at the stage's own
810
+ * guard or as the owned tool closes. Teardown is bounded whatever the resident tool does: a tool
811
+ * that answers neither its warming exchange nor its ending is signalled and released at the
812
+ * stage's own deadline. A coordinator replaces a stage whose worker no longer returns because
813
+ * teardown neither waits for an inspection nor waits past that deadline.
814
+ *
815
+ * @returns A promise that settles after the resident tool releases its resources
816
+ */
817
+ destroy(): Promise<void>;
818
+ }
819
+
820
+ /**
821
+ * Inspects TypeScript source through resident language services from the target workspace.
822
+ *
823
+ * @remarks
824
+ * Construction starts loading the workspace's compiler and warming one service per project the
825
+ * workspace declares. A candidate draft is checked against the project a call names, or
826
+ * against its own scoped environment project when a call names none, while the test uses the root
827
+ * project. Disk snapshots use their modification time as the service version so dependency edits
828
+ * cannot leave a warm answer stale. Candidate text lives in an overlay the inspection owns, and
829
+ * the language-service host answers existence, reads, and versions from it, so a candidate that
830
+ * exists only as text is importable and a candidate that shadows a disk file is checked as the
831
+ * text the case supplied. A project outside the declared set holds one recycled slot, so a caller
832
+ * varying the project cannot grow the resident set. A language service checks one candidate
833
+ * synchronously, so the stage hands the host's event loop back at each candidate boundary: a
834
+ * caller's deadline is answered within one candidate's check rather than after the whole
835
+ * inspection, and an inspection abandoned at that deadline stops at the next boundary.
836
+ *
837
+ * @example
838
+ * ```ts
839
+ * const stage = new TypeStage('/srv/checkout')
840
+ * const check = await stage.inspect(subject, 'configs/src/tsconfig.core.json')
841
+ * await stage.destroy()
842
+ * ```
843
+ */
844
+ export declare class TypeStage implements TypeStageInterface {
845
+ #private;
846
+ /**
847
+ * Starts warming the target workspace's TypeScript service.
848
+ *
849
+ * @param workspace - The target workspace root. Default: the current working directory
850
+ */
851
+ constructor(workspace?: string);
852
+ get stage(): Stage_2;
853
+ get progress(): number;
854
+ /**
855
+ * Inspects one case, against a caller-named project where the caller names one.
856
+ *
857
+ * @remarks
858
+ * The `project` parameter is this stage's own, not the stage contract's: the lint and runtime
859
+ * stages read no project, so `StageInterface` declares one parameter and every caller that
860
+ * needs this one holds a `TypeStage`.
861
+ *
862
+ * @param subject - The candidate drafts and test to inspect
863
+ * @param project - The workspace-relative TypeScript project the candidate drafts are checked
864
+ * against. Default: the scoped project each candidate path infers
865
+ * @returns One outcome for this stage
866
+ * @throws When the resident compiler cannot start or the stage has already been destroyed
867
+ */
868
+ inspect(subject: Case_2, project?: string): Promise<Check_2>;
869
+ /**
870
+ * Resolves one project to the path and digest this stage applies for it.
871
+ *
872
+ * @remarks
873
+ * Reads the parse this stage itself applies, filling its cache when the project is not already
874
+ * resident, so the reported digest is the configuration the inspection is judged under rather
875
+ * than a second parse a caller ran. The returned record is a value copy, so a later eviction
876
+ * does not move it.
877
+ *
878
+ * @param project - The workspace-relative TypeScript project to resolve
879
+ * @returns The resolved workspace-relative path and the digest of its compiler options
880
+ * @throws When the project escapes the workspace, cannot be parsed, or the stage has already
881
+ * been destroyed
882
+ */
883
+ resolve(project: string): Promise<Project_2>;
884
+ destroy(): Promise<void>;
885
+ }
886
+
887
+ /**
888
+ * Inspects TypeScript source against a caller-named project and reports what that project is.
889
+ *
890
+ * @remarks
891
+ * The type stage carries members the shared stage contract cannot: the lint and runtime stages
892
+ * read no project, so a project parameter and a project lookup belong here rather than on
893
+ * `StageInterface`.
894
+ *
895
+ * @example
896
+ * ```ts
897
+ * const project = await stage.resolve('configs/src/tsconfig.core.json')
898
+ * const check = await stage.inspect(subject, project.path)
899
+ * ```
900
+ */
901
+ export declare interface TypeStageInterface extends StageInterface {
902
+ /**
903
+ * Inspects one case, against a caller-named project where the caller names one.
904
+ *
905
+ * @param subject - The candidate drafts and test to inspect
906
+ * @param project - The workspace-relative TypeScript project the candidate drafts are checked
907
+ * against. Default: the scoped project each candidate path infers
908
+ * @returns One outcome for this stage
909
+ * @throws When the resident compiler cannot start or the stage has already been destroyed
910
+ */
911
+ inspect(subject: Case, project?: string): Promise<Check>;
912
+ /**
913
+ * Resolves one project to the path and digest the stage applies for it.
914
+ *
915
+ * @param project - The workspace-relative TypeScript project to resolve
916
+ * @returns The resolved workspace-relative path and the digest of its compiler options
917
+ * @throws When the project escapes the workspace, cannot be parsed, or the stage has already
918
+ * been destroyed
919
+ */
920
+ resolve(project: string): Promise<Project>;
921
+ }
922
+
923
+ /**
924
+ * Carries one parsed package manifest and the path it came from.
925
+ *
926
+ * @example
927
+ * ```ts
928
+ * const manifest: WorkspaceManifest = {
929
+ * path: '/srv/checkout/node_modules/typescript/package.json',
930
+ * contents: { version: '6.0.3' },
931
+ * }
932
+ * ```
933
+ */
934
+ export declare interface WorkspaceManifest {
935
+ /** Absolute path of the package manifest. */
936
+ readonly path: string;
937
+ /** Parsed manifest record. */
938
+ readonly contents: Readonly<Record<string, unknown>>;
939
+ }
940
+
941
+ export { }