@orkestrel/probe 0.0.6 → 0.0.9

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.
@@ -202,6 +202,29 @@ export declare interface Inspection {
202
202
  readonly claim: Claim;
203
203
  }
204
204
 
205
+ /**
206
+ * Carries the bound a caller holds over one stage inspection.
207
+ *
208
+ * @remarks
209
+ * `LintStageInterface.inspect` is the only inspection in this package that reads the signal. Its
210
+ * wait is a foreign language server's silence, and abandoning that wait would leave the client
211
+ * holding a pending diagnostics request. The type and runtime stages accept no options and honor no
212
+ * cancellation: a coordinator abandons an overrunning one and replaces it, which is what `Probe`
213
+ * does when its own deadline expires.
214
+ *
215
+ * Supply the signal a coordinator already armed rather than minting a second bound here. A bound
216
+ * minted beside the coordinator's races it, and which one answers then depends on scheduling.
217
+ *
218
+ * @example
219
+ * ```ts
220
+ * const options: InspectionOptions = { signal: AbortSignal.timeout(30_000) }
221
+ * ```
222
+ */
223
+ export declare interface InspectionOptions {
224
+ /** Aborts the inspection's wait for the resident tool's answer. */
225
+ readonly signal: AbortSignal;
226
+ }
227
+
205
228
  /**
206
229
  * Reports whether a fault means the host refuses the name a caller supplied for a file to create.
207
230
  *
@@ -243,13 +266,22 @@ export declare function isRefusedName(file: string, error: unknown): boolean;
243
266
  * Inspects virtual documents through one resident Oxlint language server.
244
267
  *
245
268
  * @remarks
246
- * Construction starts the target workspace's Oxlint binary with its Language Server Protocol
247
- * mode. Each inspection opens the supplied text by URI, waits for published diagnostics, and
248
- * closes the document without writing it to disk.
269
+ * Construction starts the target workspace's Oxlint binary with its Language Server Protocol mode
270
+ * and hands the conversation to `@orkestrel/lsp`: `createStdioTransport` owns the child process and
271
+ * its bytes, and `createLSPClient` owns the framing, the request correlation, the capabilities this
272
+ * stage advertises, and the choice between pulled and pushed diagnostics. This stage owns the
273
+ * workspace, the candidate identity, and the projection from a diagnostic to an `Issue`.
274
+ *
275
+ * Each inspection opens the supplied text by URI, waits for the client's diagnostics, and closes the
276
+ * document without writing it to disk. The caller's own signal is what bounds that wait, so a server
277
+ * that admits a document and publishes nothing refuses the inspection when the caller's bound
278
+ * expires rather than holding it for the life of the process. This stage mints no bound of its own
279
+ * for the wait: a second one would race the caller's, and the answer would depend on scheduling.
249
280
  *
250
281
  * Teardown is bounded at both ends. Warming waits for the server's `initialize` response and the
251
- * ending waits for its `shutdown` reply, and the protocol leaves both answers to the server, so
252
- * `destroy` bounds each exchange and takes the ending back with a signal when the bound passes.
282
+ * ending waits for its `shutdown` reply, and the protocol leaves both answers to the server, so the
283
+ * client's deadline bounds each exchange and the transport's cooperative window takes the ending
284
+ * back with a signal when that window passes.
253
285
  *
254
286
  * The URI is the path the candidate declared, so the candidate receives exactly the rule set the
255
287
  * workspace's own lint gate applies to that path, including an override anchored to one directory,
@@ -262,11 +294,11 @@ export declare function isRefusedName(file: string, error: unknown): boolean;
262
294
  * @example
263
295
  * ```ts
264
296
  * const stage = new LintStage('/srv/checkout')
265
- * const check = await stage.inspect(subject)
297
+ * const check = await stage.inspect(subject, { signal: AbortSignal.timeout(30_000) })
266
298
  * await stage.destroy()
267
299
  * ```
268
300
  */
269
- export declare class LintStage implements StageInterface {
301
+ export declare class LintStage implements LintStageInterface {
270
302
  #private;
271
303
  /**
272
304
  * Starts warming the target workspace's Oxlint language server.
@@ -276,10 +308,42 @@ export declare class LintStage implements StageInterface {
276
308
  constructor(workspace?: string);
277
309
  get stage(): Stage_2;
278
310
  get progress(): number;
279
- inspect(subject: Case_2): Promise<Check_2>;
311
+ inspect(subject: Case_2, options?: InspectionOptions): Promise<Check_2>;
280
312
  destroy(): Promise<void>;
281
313
  }
282
314
 
315
+ /**
316
+ * Inspects one case under a bound the caller supplies.
317
+ *
318
+ * @remarks
319
+ * The lint stage carries a member the shared stage contract cannot. Its inspection waits for a
320
+ * foreign language server to publish, and only the caller knows how long that wait may run, so the
321
+ * bound belongs here rather than on `StageInterface`. The type and runtime stages read no signal
322
+ * and keep the shared one-argument `inspect`.
323
+ *
324
+ * `options` is optional because `StageInterface.inspect` accepts one argument and this contract
325
+ * extends it. An inspection that omits `options` is refused rather than served: a bound this stage
326
+ * minted for itself would race the coordinator's, and the client's own `timeout` covers the
327
+ * lifecycle exchanges alone.
328
+ *
329
+ * @example
330
+ * ```ts
331
+ * const check = await stage.inspect(subject, { signal: AbortSignal.timeout(30_000) })
332
+ * ```
333
+ */
334
+ export declare interface LintStageInterface extends StageInterface {
335
+ /**
336
+ * Inspects one case, under the bound the caller supplies.
337
+ *
338
+ * @param subject - The candidate drafts and test to inspect
339
+ * @param options - The bound this inspection waits under
340
+ * @returns One outcome for this stage
341
+ * @throws When the caller supplies no bound, when the resident language server cannot start, or
342
+ * when the stage has already been destroyed
343
+ */
344
+ inspect(subject: Case, options?: InspectionOptions): Promise<Check>;
345
+ }
346
+
283
347
  /**
284
348
  * Holds the listeners one emitter carried for a set of events at the moment it was captured.
285
349
  *
@@ -384,6 +448,15 @@ export declare function normalizeValue(workspace: string, value: unknown): unkno
384
448
  * is what makes a resident tool re-read a path this overlay holds, so two inspections that supply
385
449
  * different text for one path never share a cached answer.
386
450
  *
451
+ * This overlay matches a lookup key the way the host its stage declares to its tool matches file
452
+ * names. A host that resolves two spellings of one file name to one file leaves the tool holding
453
+ * whichever spelling it met first — the disk one, where the file is on disk — and the tool asks
454
+ * under that, so an overlay matching keys exactly answers nothing and the tool reads the committed
455
+ * file instead. Mint the overlay with the sensitivity the stage declares, and one reading decides
456
+ * both. The recorded spelling is what `paths` reports and what `covers` matches a directory
457
+ * against, so a folded key never reaches the paths a tool is handed and containment stays an exact
458
+ * comparison.
459
+ *
387
460
  * @example
388
461
  * ```ts
389
462
  * const overlay = new Overlay()
@@ -394,11 +467,21 @@ export declare function normalizeValue(workspace: string, value: unknown): unkno
394
467
  */
395
468
  export declare class Overlay implements OverlayInterface {
396
469
  #private;
470
+ /**
471
+ * Creates an empty candidate set under a fresh identity.
472
+ *
473
+ * @param options - Construction options
474
+ */
475
+ constructor(options?: OverlayOptions);
397
476
  get revision(): string;
398
477
  get paths(): readonly string[];
399
478
  /**
400
479
  * Records one candidate's text against the absolute path it stands in for.
401
480
  *
481
+ * @remarks
482
+ * Recording a candidate under a path this overlay's own matching reads as one already recorded
483
+ * replaces that candidate, the way one file holds one text.
484
+ *
402
485
  * @param path - The absolute path the candidate replaces
403
486
  * @param text - The candidate's full contents
404
487
  * @returns Nothing
@@ -418,7 +501,9 @@ export declare class Overlay implements OverlayInterface {
418
501
  * The answer is derived from the paths the overlay holds rather than stored, so it stops being
419
502
  * true exactly when the inspection that declared those candidates clears them. Both sides pass
420
503
  * through `normalizePath` first, because a tool that normalizes its own paths asks about a
421
- * directory in a spelling the recorded path may not share.
504
+ * directory in a spelling the recorded path may not share. Case is never folded here: this is a
505
+ * containment comparison, and it reads the recorded spelling whatever an overlay minted for a
506
+ * case-folding host matches its lookup keys by.
422
507
  *
423
508
  * @param directory - The absolute directory path to check
424
509
  * @returns True if a candidate path sits beneath the directory; false otherwise
@@ -497,6 +582,22 @@ export declare interface OverlayInterface {
497
582
  clear(): void;
498
583
  }
499
584
 
585
+ /**
586
+ * Carries the construction options `Overlay` accepts.
587
+ *
588
+ * @example
589
+ * ```ts
590
+ * const options: OverlayOptions = { sensitive: false }
591
+ * ```
592
+ */
593
+ export declare interface OverlayOptions {
594
+ /**
595
+ * If `true`, a lookup key matches a recorded path exactly; if `false`, it matches with file-name
596
+ * case folded. Default: `true`
597
+ */
598
+ readonly sensitive?: boolean;
599
+ }
600
+
500
601
  /**
501
602
  * Overwrites a file that already exists, through a descriptor that refuses a symbolic link at the
502
603
  * final component.
@@ -505,12 +606,18 @@ export declare interface OverlayInterface {
505
606
  * A containment walk and the write that follows it are separate calls, so a process that swaps the
506
607
  * final component between them decides where the bytes land. `writeFileSync` opens with the default
507
608
  * `w` flag, which follows a symbolic link at that component and creates the file when it is absent,
508
- * so the walk's reading does not bind the write. Opening `O_WRONLY | O_TRUNC | O_NOFOLLOW` moves
509
- * that decision into the call that writes: a link at the final component refuses the open, and the
609
+ * so the walk's reading does not bind the write. Opening `O_WRONLY | O_NOFOLLOW` moves that
610
+ * decision into the call that writes: a link at the final component refuses the open, and the
510
611
  * omitted `O_CREAT` fails `ENOENT` for a target that has gone since the walk saw it. This is the
511
612
  * mutating counterpart of the `wx` flag every create in this package uses. Which code the refusal
512
613
  * carries is the host's; Linux with Node v22.22.2 reported `ELOOP` on 2026-08-24.
513
614
  *
615
+ * Truncation runs through the held descriptor rather than through an `O_TRUNC` flag, so it reaches
616
+ * the file the open bound. A Windows host refuses the numeric `O_TRUNC` without `O_CREAT` outright:
617
+ * measured on 2026-08-26 on Windows 11 with Node v24.19.0 over NTFS, `openSync` reported `EINVAL`
618
+ * for `O_WRONLY | O_TRUNC` and for `O_RDWR | O_TRUNC` while `O_WRONLY` alone opened, and adding
619
+ * `O_CREAT` would trade that refusal for the create this function must never perform.
620
+ *
514
621
  * A directory component stays open, because Node exposes no descriptor-relative call to walk and
515
622
  * write through one set of descriptors. Where a host's Node build defines no `O_NOFOLLOW`, that flag
516
623
  * contributes nothing to the flag set and the final component is open on that host too.
@@ -531,20 +638,6 @@ export declare interface OverlayInterface {
531
638
  */
532
639
  export declare function overwriteFile(file: string, text: string): void;
533
640
 
534
- /**
535
- * Reads a JSON-RPC frame's declared byte length.
536
- *
537
- * @param header - The frame header text without its terminating empty line
538
- * @returns The non-negative content length, or `undefined` for an invalid header
539
- *
540
- * @example
541
- * ```ts
542
- * parseContentLength('Content-Length: 128') // 128
543
- * parseContentLength('Content-Type: application/json') // undefined
544
- * ```
545
- */
546
- export declare function parseContentLength(header: string): number | undefined;
547
-
548
641
  /**
549
642
  * Answers claims through resident TypeScript, Oxlint, and Vitest stages.
550
643
  *
@@ -202,6 +202,29 @@ export declare interface Inspection {
202
202
  readonly claim: Claim;
203
203
  }
204
204
 
205
+ /**
206
+ * Carries the bound a caller holds over one stage inspection.
207
+ *
208
+ * @remarks
209
+ * `LintStageInterface.inspect` is the only inspection in this package that reads the signal. Its
210
+ * wait is a foreign language server's silence, and abandoning that wait would leave the client
211
+ * holding a pending diagnostics request. The type and runtime stages accept no options and honor no
212
+ * cancellation: a coordinator abandons an overrunning one and replaces it, which is what `Probe`
213
+ * does when its own deadline expires.
214
+ *
215
+ * Supply the signal a coordinator already armed rather than minting a second bound here. A bound
216
+ * minted beside the coordinator's races it, and which one answers then depends on scheduling.
217
+ *
218
+ * @example
219
+ * ```ts
220
+ * const options: InspectionOptions = { signal: AbortSignal.timeout(30_000) }
221
+ * ```
222
+ */
223
+ export declare interface InspectionOptions {
224
+ /** Aborts the inspection's wait for the resident tool's answer. */
225
+ readonly signal: AbortSignal;
226
+ }
227
+
205
228
  /**
206
229
  * Reports whether a fault means the host refuses the name a caller supplied for a file to create.
207
230
  *
@@ -243,13 +266,22 @@ export declare function isRefusedName(file: string, error: unknown): boolean;
243
266
  * Inspects virtual documents through one resident Oxlint language server.
244
267
  *
245
268
  * @remarks
246
- * Construction starts the target workspace's Oxlint binary with its Language Server Protocol
247
- * mode. Each inspection opens the supplied text by URI, waits for published diagnostics, and
248
- * closes the document without writing it to disk.
269
+ * Construction starts the target workspace's Oxlint binary with its Language Server Protocol mode
270
+ * and hands the conversation to `@orkestrel/lsp`: `createStdioTransport` owns the child process and
271
+ * its bytes, and `createLSPClient` owns the framing, the request correlation, the capabilities this
272
+ * stage advertises, and the choice between pulled and pushed diagnostics. This stage owns the
273
+ * workspace, the candidate identity, and the projection from a diagnostic to an `Issue`.
274
+ *
275
+ * Each inspection opens the supplied text by URI, waits for the client's diagnostics, and closes the
276
+ * document without writing it to disk. The caller's own signal is what bounds that wait, so a server
277
+ * that admits a document and publishes nothing refuses the inspection when the caller's bound
278
+ * expires rather than holding it for the life of the process. This stage mints no bound of its own
279
+ * for the wait: a second one would race the caller's, and the answer would depend on scheduling.
249
280
  *
250
281
  * Teardown is bounded at both ends. Warming waits for the server's `initialize` response and the
251
- * ending waits for its `shutdown` reply, and the protocol leaves both answers to the server, so
252
- * `destroy` bounds each exchange and takes the ending back with a signal when the bound passes.
282
+ * ending waits for its `shutdown` reply, and the protocol leaves both answers to the server, so the
283
+ * client's deadline bounds each exchange and the transport's cooperative window takes the ending
284
+ * back with a signal when that window passes.
253
285
  *
254
286
  * The URI is the path the candidate declared, so the candidate receives exactly the rule set the
255
287
  * workspace's own lint gate applies to that path, including an override anchored to one directory,
@@ -262,11 +294,11 @@ export declare function isRefusedName(file: string, error: unknown): boolean;
262
294
  * @example
263
295
  * ```ts
264
296
  * const stage = new LintStage('/srv/checkout')
265
- * const check = await stage.inspect(subject)
297
+ * const check = await stage.inspect(subject, { signal: AbortSignal.timeout(30_000) })
266
298
  * await stage.destroy()
267
299
  * ```
268
300
  */
269
- export declare class LintStage implements StageInterface {
301
+ export declare class LintStage implements LintStageInterface {
270
302
  #private;
271
303
  /**
272
304
  * Starts warming the target workspace's Oxlint language server.
@@ -276,10 +308,42 @@ export declare class LintStage implements StageInterface {
276
308
  constructor(workspace?: string);
277
309
  get stage(): Stage_2;
278
310
  get progress(): number;
279
- inspect(subject: Case_2): Promise<Check_2>;
311
+ inspect(subject: Case_2, options?: InspectionOptions): Promise<Check_2>;
280
312
  destroy(): Promise<void>;
281
313
  }
282
314
 
315
+ /**
316
+ * Inspects one case under a bound the caller supplies.
317
+ *
318
+ * @remarks
319
+ * The lint stage carries a member the shared stage contract cannot. Its inspection waits for a
320
+ * foreign language server to publish, and only the caller knows how long that wait may run, so the
321
+ * bound belongs here rather than on `StageInterface`. The type and runtime stages read no signal
322
+ * and keep the shared one-argument `inspect`.
323
+ *
324
+ * `options` is optional because `StageInterface.inspect` accepts one argument and this contract
325
+ * extends it. An inspection that omits `options` is refused rather than served: a bound this stage
326
+ * minted for itself would race the coordinator's, and the client's own `timeout` covers the
327
+ * lifecycle exchanges alone.
328
+ *
329
+ * @example
330
+ * ```ts
331
+ * const check = await stage.inspect(subject, { signal: AbortSignal.timeout(30_000) })
332
+ * ```
333
+ */
334
+ export declare interface LintStageInterface extends StageInterface {
335
+ /**
336
+ * Inspects one case, under the bound the caller supplies.
337
+ *
338
+ * @param subject - The candidate drafts and test to inspect
339
+ * @param options - The bound this inspection waits under
340
+ * @returns One outcome for this stage
341
+ * @throws When the caller supplies no bound, when the resident language server cannot start, or
342
+ * when the stage has already been destroyed
343
+ */
344
+ inspect(subject: Case, options?: InspectionOptions): Promise<Check>;
345
+ }
346
+
283
347
  /**
284
348
  * Holds the listeners one emitter carried for a set of events at the moment it was captured.
285
349
  *
@@ -384,6 +448,15 @@ export declare function normalizeValue(workspace: string, value: unknown): unkno
384
448
  * is what makes a resident tool re-read a path this overlay holds, so two inspections that supply
385
449
  * different text for one path never share a cached answer.
386
450
  *
451
+ * This overlay matches a lookup key the way the host its stage declares to its tool matches file
452
+ * names. A host that resolves two spellings of one file name to one file leaves the tool holding
453
+ * whichever spelling it met first — the disk one, where the file is on disk — and the tool asks
454
+ * under that, so an overlay matching keys exactly answers nothing and the tool reads the committed
455
+ * file instead. Mint the overlay with the sensitivity the stage declares, and one reading decides
456
+ * both. The recorded spelling is what `paths` reports and what `covers` matches a directory
457
+ * against, so a folded key never reaches the paths a tool is handed and containment stays an exact
458
+ * comparison.
459
+ *
387
460
  * @example
388
461
  * ```ts
389
462
  * const overlay = new Overlay()
@@ -394,11 +467,21 @@ export declare function normalizeValue(workspace: string, value: unknown): unkno
394
467
  */
395
468
  export declare class Overlay implements OverlayInterface {
396
469
  #private;
470
+ /**
471
+ * Creates an empty candidate set under a fresh identity.
472
+ *
473
+ * @param options - Construction options
474
+ */
475
+ constructor(options?: OverlayOptions);
397
476
  get revision(): string;
398
477
  get paths(): readonly string[];
399
478
  /**
400
479
  * Records one candidate's text against the absolute path it stands in for.
401
480
  *
481
+ * @remarks
482
+ * Recording a candidate under a path this overlay's own matching reads as one already recorded
483
+ * replaces that candidate, the way one file holds one text.
484
+ *
402
485
  * @param path - The absolute path the candidate replaces
403
486
  * @param text - The candidate's full contents
404
487
  * @returns Nothing
@@ -418,7 +501,9 @@ export declare class Overlay implements OverlayInterface {
418
501
  * The answer is derived from the paths the overlay holds rather than stored, so it stops being
419
502
  * true exactly when the inspection that declared those candidates clears them. Both sides pass
420
503
  * through `normalizePath` first, because a tool that normalizes its own paths asks about a
421
- * directory in a spelling the recorded path may not share.
504
+ * directory in a spelling the recorded path may not share. Case is never folded here: this is a
505
+ * containment comparison, and it reads the recorded spelling whatever an overlay minted for a
506
+ * case-folding host matches its lookup keys by.
422
507
  *
423
508
  * @param directory - The absolute directory path to check
424
509
  * @returns True if a candidate path sits beneath the directory; false otherwise
@@ -497,6 +582,22 @@ export declare interface OverlayInterface {
497
582
  clear(): void;
498
583
  }
499
584
 
585
+ /**
586
+ * Carries the construction options `Overlay` accepts.
587
+ *
588
+ * @example
589
+ * ```ts
590
+ * const options: OverlayOptions = { sensitive: false }
591
+ * ```
592
+ */
593
+ export declare interface OverlayOptions {
594
+ /**
595
+ * If `true`, a lookup key matches a recorded path exactly; if `false`, it matches with file-name
596
+ * case folded. Default: `true`
597
+ */
598
+ readonly sensitive?: boolean;
599
+ }
600
+
500
601
  /**
501
602
  * Overwrites a file that already exists, through a descriptor that refuses a symbolic link at the
502
603
  * final component.
@@ -505,12 +606,18 @@ export declare interface OverlayInterface {
505
606
  * A containment walk and the write that follows it are separate calls, so a process that swaps the
506
607
  * final component between them decides where the bytes land. `writeFileSync` opens with the default
507
608
  * `w` flag, which follows a symbolic link at that component and creates the file when it is absent,
508
- * so the walk's reading does not bind the write. Opening `O_WRONLY | O_TRUNC | O_NOFOLLOW` moves
509
- * that decision into the call that writes: a link at the final component refuses the open, and the
609
+ * so the walk's reading does not bind the write. Opening `O_WRONLY | O_NOFOLLOW` moves that
610
+ * decision into the call that writes: a link at the final component refuses the open, and the
510
611
  * omitted `O_CREAT` fails `ENOENT` for a target that has gone since the walk saw it. This is the
511
612
  * mutating counterpart of the `wx` flag every create in this package uses. Which code the refusal
512
613
  * carries is the host's; Linux with Node v22.22.2 reported `ELOOP` on 2026-08-24.
513
614
  *
615
+ * Truncation runs through the held descriptor rather than through an `O_TRUNC` flag, so it reaches
616
+ * the file the open bound. A Windows host refuses the numeric `O_TRUNC` without `O_CREAT` outright:
617
+ * measured on 2026-08-26 on Windows 11 with Node v24.19.0 over NTFS, `openSync` reported `EINVAL`
618
+ * for `O_WRONLY | O_TRUNC` and for `O_RDWR | O_TRUNC` while `O_WRONLY` alone opened, and adding
619
+ * `O_CREAT` would trade that refusal for the create this function must never perform.
620
+ *
514
621
  * A directory component stays open, because Node exposes no descriptor-relative call to walk and
515
622
  * write through one set of descriptors. Where a host's Node build defines no `O_NOFOLLOW`, that flag
516
623
  * contributes nothing to the flag set and the final component is open on that host too.
@@ -531,20 +638,6 @@ export declare interface OverlayInterface {
531
638
  */
532
639
  export declare function overwriteFile(file: string, text: string): void;
533
640
 
534
- /**
535
- * Reads a JSON-RPC frame's declared byte length.
536
- *
537
- * @param header - The frame header text without its terminating empty line
538
- * @returns The non-negative content length, or `undefined` for an invalid header
539
- *
540
- * @example
541
- * ```ts
542
- * parseContentLength('Content-Length: 128') // 128
543
- * parseContentLength('Content-Type: application/json') // undefined
544
- * ```
545
- */
546
- export declare function parseContentLength(header: string): number | undefined;
547
-
548
641
  /**
549
642
  * Answers claims through resident TypeScript, Oxlint, and Vitest stages.
550
643
  *