@orkestrel/probe 0.0.5 → 0.0.8

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.
@@ -1,5 +1,5 @@
1
1
  import { createRequire } from "node:module";
2
- import { closeSync, constants, existsSync, lstatSync, mkdirSync, openSync, readFileSync, readdirSync, realpathSync, rmSync, rmdirSync, statSync, unlinkSync, writeFileSync } from "node:fs";
2
+ import { closeSync, constants, existsSync, ftruncateSync, lstatSync, mkdirSync, openSync, readFileSync, readdirSync, realpathSync, rmSync, rmdirSync, statSync, unlinkSync, writeFileSync } from "node:fs";
3
3
  import { createHash, randomUUID } from "node:crypto";
4
4
  import { basename, dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
5
5
  import { fileURLToPath, pathToFileURL } from "node:url";
@@ -8,7 +8,8 @@ import { CLAIM_SHAPE, ProbeError, computeReceipt, createDestroyedError, formatCh
8
8
  import { Emitter } from "@orkestrel/emitter";
9
9
  import { createQueue } from "@orkestrel/queue";
10
10
  import { createTimeout } from "@orkestrel/timeout";
11
- import { spawn } from "node:child_process";
11
+ import { createLSPClient } from "@orkestrel/lsp";
12
+ import { createStdioTransport } from "@orkestrel/lsp/server";
12
13
  import { PassThrough } from "node:stream";
13
14
  import { setTimeout } from "node:timers/promises";
14
15
  import { createMCPLegacy, createMCPServer } from "@orkestrel/mcp";
@@ -113,12 +114,18 @@ function resolveWorkspaceFile(workspace, target, mutate = false) {
113
114
  * A containment walk and the write that follows it are separate calls, so a process that swaps the
114
115
  * final component between them decides where the bytes land. `writeFileSync` opens with the default
115
116
  * `w` flag, which follows a symbolic link at that component and creates the file when it is absent,
116
- * so the walk's reading does not bind the write. Opening `O_WRONLY | O_TRUNC | O_NOFOLLOW` moves
117
- * that decision into the call that writes: a link at the final component refuses the open, and the
117
+ * so the walk's reading does not bind the write. Opening `O_WRONLY | O_NOFOLLOW` moves that
118
+ * decision into the call that writes: a link at the final component refuses the open, and the
118
119
  * omitted `O_CREAT` fails `ENOENT` for a target that has gone since the walk saw it. This is the
119
120
  * mutating counterpart of the `wx` flag every create in this package uses. Which code the refusal
120
121
  * carries is the host's; Linux with Node v22.22.2 reported `ELOOP` on 2026-08-24.
121
122
  *
123
+ * Truncation runs through the held descriptor rather than through an `O_TRUNC` flag, so it reaches
124
+ * the file the open bound. A Windows host refuses the numeric `O_TRUNC` without `O_CREAT` outright:
125
+ * measured on 2026-08-26 on Windows 11 with Node v24.19.0 over NTFS, `openSync` reported `EINVAL`
126
+ * for `O_WRONLY | O_TRUNC` and for `O_RDWR | O_TRUNC` while `O_WRONLY` alone opened, and adding
127
+ * `O_CREAT` would trade that refusal for the create this function must never perform.
128
+ *
122
129
  * A directory component stays open, because Node exposes no descriptor-relative call to walk and
123
130
  * write through one set of descriptors. Where a host's Node build defines no `O_NOFOLLOW`, that flag
124
131
  * contributes nothing to the flag set and the final component is open on that host too.
@@ -138,8 +145,9 @@ function resolveWorkspaceFile(workspace, target, mutate = false) {
138
145
  * ```
139
146
  */
140
147
  function overwriteFile(file, text) {
141
- const descriptor = openSync(file, constants.O_WRONLY | constants.O_TRUNC | constants.O_NOFOLLOW);
148
+ const descriptor = openSync(file, constants.O_WRONLY | constants.O_NOFOLLOW);
142
149
  try {
150
+ ftruncateSync(descriptor, 0);
143
151
  writeFileSync(descriptor, text, "utf8");
144
152
  } finally {
145
153
  closeSync(descriptor);
@@ -514,24 +522,6 @@ function matchesWorkspaceModule(path) {
514
522
  return /\.(?:[cm]?[jt]sx?|vue|json)$/.test(path);
515
523
  }
516
524
  /**
517
- * Reads a JSON-RPC frame's declared byte length.
518
- *
519
- * @param header - The frame header text without its terminating empty line
520
- * @returns The non-negative content length, or `undefined` for an invalid header
521
- *
522
- * @example
523
- * ```ts
524
- * parseContentLength('Content-Length: 128') // 128
525
- * parseContentLength('Content-Type: application/json') // undefined
526
- * ```
527
- */
528
- function parseContentLength(header) {
529
- const text = /(?:^|\r\n)Content-Length:\s*(\d+)(?:\r\n|$)/i.exec(header)?.[1];
530
- if (text === void 0) return void 0;
531
- const length = Number.parseInt(text, 10);
532
- return Number.isSafeInteger(length) && length >= 0 ? length : void 0;
533
- }
534
- /**
535
525
  * Normalizes a caught or foreign error into readable text.
536
526
  *
537
527
  * @param value - The value to describe
@@ -805,7 +795,7 @@ var Overlay = class {
805
795
  };
806
796
  //#endregion
807
797
  //#region package.json
808
- var version = "0.0.5";
798
+ var version = "0.0.8";
809
799
  var peerDependencies = {
810
800
  "oxlint": "^1.77.0",
811
801
  "typescript": "^6.0.0",
@@ -817,13 +807,22 @@ var peerDependencies = {
817
807
  * Inspects virtual documents through one resident Oxlint language server.
818
808
  *
819
809
  * @remarks
820
- * Construction starts the target workspace's Oxlint binary with its Language Server Protocol
821
- * mode. Each inspection opens the supplied text by URI, waits for published diagnostics, and
822
- * closes the document without writing it to disk.
810
+ * Construction starts the target workspace's Oxlint binary with its Language Server Protocol mode
811
+ * and hands the conversation to `@orkestrel/lsp`: `createStdioTransport` owns the child process and
812
+ * its bytes, and `createLSPClient` owns the framing, the request correlation, the capabilities this
813
+ * stage advertises, and the choice between pulled and pushed diagnostics. This stage owns the
814
+ * workspace, the candidate identity, and the projection from a diagnostic to an `Issue`.
815
+ *
816
+ * Each inspection opens the supplied text by URI, waits for the client's diagnostics, and closes the
817
+ * document without writing it to disk. The caller's own signal is what bounds that wait, so a server
818
+ * that admits a document and publishes nothing refuses the inspection when the caller's bound
819
+ * expires rather than holding it for the life of the process. This stage mints no bound of its own
820
+ * for the wait: a second one would race the caller's, and the answer would depend on scheduling.
823
821
  *
824
822
  * Teardown is bounded at both ends. Warming waits for the server's `initialize` response and the
825
- * ending waits for its `shutdown` reply, and the protocol leaves both answers to the server, so
826
- * `destroy` bounds each exchange and takes the ending back with a signal when the bound passes.
823
+ * ending waits for its `shutdown` reply, and the protocol leaves both answers to the server, so the
824
+ * client's deadline bounds each exchange and the transport's cooperative window takes the ending
825
+ * back with a signal when that window passes.
827
826
  *
828
827
  * The URI is the path the candidate declared, so the candidate receives exactly the rule set the
829
828
  * workspace's own lint gate applies to that path, including an override anchored to one directory,
@@ -836,24 +835,20 @@ var peerDependencies = {
836
835
  * @example
837
836
  * ```ts
838
837
  * const stage = new LintStage('/srv/checkout')
839
- * const check = await stage.inspect(subject)
838
+ * const check = await stage.inspect(subject, { signal: AbortSignal.timeout(30_000) })
840
839
  * await stage.destroy()
841
840
  * ```
842
841
  */
843
842
  var LintStage = class {
844
843
  #workspace;
845
844
  #deadline = 2e3;
845
+ #documents = /* @__PURE__ */ new Set();
846
846
  #warmth;
847
- #responses = /* @__PURE__ */ new Map();
848
- #failures = /* @__PURE__ */ new Map();
849
- #documents = /* @__PURE__ */ new Map();
850
- #publishes = /* @__PURE__ */ new Map();
851
- #refusals = /* @__PURE__ */ new Map();
852
- #child;
853
- #buffer = Buffer.alloc(0);
854
- #sequence = 0;
847
+ #client;
848
+ #ending;
855
849
  #closing;
856
850
  #progress = 0;
851
+ #revision = 0;
857
852
  #destroyed = false;
858
853
  /**
859
854
  * Starts warming the target workspace's Oxlint language server.
@@ -871,8 +866,8 @@ var LintStage = class {
871
866
  get progress() {
872
867
  return this.#progress;
873
868
  }
874
- inspect(subject) {
875
- return guardStage(this.stage, this.#inspect(subject));
869
+ inspect(subject, options) {
870
+ return guardStage(this.stage, this.#inspect(subject, options));
876
871
  }
877
872
  destroy() {
878
873
  if (this.#closing !== void 0) return this.#closing;
@@ -880,13 +875,18 @@ var LintStage = class {
880
875
  this.#closing = guardStage(this.stage, this.#destroy());
881
876
  return this.#closing;
882
877
  }
883
- async #inspect(subject) {
878
+ async #inspect(subject, options) {
884
879
  if (this.#destroyed) throw createDestroyedError("lint stage");
880
+ if (options === void 0) throw new ProbeError("The lint stage inspects only under a bound its caller supplies", {
881
+ origin: "claimant",
882
+ code: "refused",
883
+ context: { stage: this.stage }
884
+ });
885
885
  const started = performance.now();
886
- await this.#warmth;
886
+ const client = await this.#warmed();
887
887
  if (this.#destroyed) throw createDestroyedError("lint stage");
888
888
  const issues = [];
889
- for (const draft of [...subject.files, subject.test]) issues.push(...await this.#document(draft));
889
+ for (const draft of [...subject.files, subject.test]) issues.push(...await this.#document(client, draft, options.signal));
890
890
  return {
891
891
  stage: this.stage,
892
892
  elapsed: Math.round(performance.now() - started),
@@ -894,118 +894,86 @@ var LintStage = class {
894
894
  };
895
895
  }
896
896
  async #destroy() {
897
- const child = await this.#warmed();
898
- this.#fail(createDestroyedError("lint stage"));
899
- if (child === void 0 || this.#ending !== void 0) return;
900
- const released = new Promise((resolve) => {
901
- child.once("exit", () => resolve());
902
- child.once("close", () => resolve());
897
+ await this.#client?.destroy();
898
+ }
899
+ async #warm() {
900
+ const binary = resolveWorkspaceBinary(this.#workspace, "oxlint");
901
+ const client = createLSPClient({
902
+ transport: createStdioTransport({
903
+ server: {
904
+ command: [
905
+ process.execPath,
906
+ binary,
907
+ "--lsp"
908
+ ],
909
+ directory: this.#workspace
910
+ },
911
+ grace: this.#deadline / 2
912
+ }),
913
+ workspace: pathToFileURL(this.#workspace).href,
914
+ timeout: this.#deadline
903
915
  });
904
- await this.#release(child, released);
916
+ this.#client = client;
917
+ client.emitter.on("exit", (exit) => this.#retire(exit));
918
+ await client.start();
919
+ return client;
905
920
  }
906
921
  async #warmed() {
907
- const timeout = createTimeout({ ms: this.#deadline });
908
- timeout.start();
909
- try {
910
- return await Promise.race([this.#warmth, this.#expiry(timeout)]);
911
- } catch {
912
- return this.#child;
913
- } finally {
914
- timeout.clear();
915
- }
916
- }
917
- async #release(child, released) {
918
- const timeout = createTimeout({ ms: this.#deadline });
919
- timeout.start();
920
- try {
921
- await Promise.race([this.#retire(child).then(() => released), this.#expiry(timeout)]);
922
- } catch {
923
- child.kill("SIGKILL");
924
- await released;
925
- } finally {
926
- timeout.clear();
927
- }
928
- }
929
- async #retire(child) {
930
922
  try {
931
- await this.#request("shutdown", void 0);
932
- this.#notify("exit", void 0);
933
- } catch {
934
- child.kill("SIGKILL");
923
+ return await this.#warmth;
924
+ } catch (error) {
925
+ throw this.#translate(error);
935
926
  }
936
927
  }
937
- #expiry(timeout) {
938
- return new Promise((_resolve, reject) => {
939
- timeout.signal.addEventListener("abort", () => reject(new ProbeError(`The Oxlint language server did not exit within ${timeout.ms} ms`, {
940
- origin: "instrument",
941
- code: "deadline",
942
- context: {
943
- stage: this.stage,
944
- deadline: timeout.ms
945
- }
946
- })), { once: true });
947
- });
948
- }
949
- async #warm() {
950
- const binary = resolveWorkspaceBinary(this.#workspace, "oxlint");
951
- const child = spawn(process.execPath, [binary, "--lsp"], {
952
- cwd: this.#workspace,
953
- stdio: "pipe"
954
- });
955
- this.#child = child;
956
- child.stderr.resume();
957
- child.stdout.on("data", (chunk) => this.#read(chunk));
958
- child.stdin.on("error", (error) => this.#fail(error));
959
- child.on("error", (error) => this.#fail(error));
960
- child.on("exit", (code, signal) => this.#exit(code, signal));
961
- await this.#request("initialize", {
962
- processId: process.pid,
963
- rootUri: pathToFileURL(this.#workspace).href,
964
- capabilities: {},
965
- workspaceFolders: [{
966
- uri: pathToFileURL(this.#workspace).href,
967
- name: "workspace"
968
- }]
969
- });
970
- this.#notify("initialized", {});
971
- return child;
928
+ #retire(exit) {
929
+ this.#ending = exit.signal === null ? `code ${exit.code}` : `signal ${exit.signal}`;
972
930
  }
973
- #document(draft) {
931
+ async #document(client, draft, signal) {
974
932
  const uri = pathToFileURL(resolveWorkspaceFile(this.#workspace, draft.path)).href;
975
- if (this.#publishes.has(uri)) return Promise.reject(new ProbeError(`The lint stage is already inspecting ${draft.path}`, {
933
+ if (this.#documents.has(uri)) throw new ProbeError(`The lint stage is already inspecting ${draft.path}`, {
976
934
  origin: "claimant",
977
935
  code: "refused",
978
936
  context: {
979
937
  stage: this.stage,
980
938
  path: draft.path
981
939
  }
982
- }));
983
- const inspected = new Promise((resolve, reject) => {
984
- this.#documents.set(uri, normalizePath(draft.path));
985
- this.#publishes.set(uri, resolve);
986
- this.#refusals.set(uri, reject);
987
- }).finally(() => this.#close(uri));
940
+ });
941
+ this.#documents.add(uri);
942
+ this.#progress += 1;
943
+ this.#revision += 1;
988
944
  try {
989
- this.#notify("textDocument/didOpen", { textDocument: {
945
+ const diagnostics = await client.open({
990
946
  uri,
991
947
  languageId: inferDocumentLanguage(draft.path),
992
- version: this.#sequence,
948
+ version: this.#revision,
993
949
  text: draft.text
994
- } });
995
- this.#progress += 1;
950
+ }, { signal });
951
+ return this.#issues(normalizePath(draft.path), diagnostics);
996
952
  } catch (error) {
997
- this.#refusals.get(uri)?.(this.#fault(describeUnknown(error), error, draft.path));
953
+ if (signal.aborted) throw this.#abandoned(draft.path);
954
+ throw this.#translate(error, draft.path);
955
+ } finally {
956
+ this.#progress -= 1;
957
+ this.#documents.delete(uri);
958
+ await client.close(uri).catch(() => void 0);
998
959
  }
999
- return inspected;
1000
960
  }
1001
- get #ending() {
1002
- const child = this.#child;
1003
- if (child === void 0) return void 0;
1004
- if (child.exitCode === null && child.signalCode === null) return void 0;
1005
- return this.#describe(child.exitCode, child.signalCode);
961
+ #abandoned(path) {
962
+ return new ProbeError("The lint stage inspection stopped at the bound its caller supplied", {
963
+ origin: "claimant",
964
+ code: "deadline",
965
+ context: {
966
+ stage: this.stage,
967
+ path
968
+ }
969
+ });
1006
970
  }
1007
- #describe(code, signal) {
1008
- return signal === null ? `code ${code}` : `signal ${signal}`;
971
+ #translate(error, path) {
972
+ if (this.#destroyed) return createDestroyedError("lint stage");
973
+ const ending = this.#ending;
974
+ if (ending !== void 0) return this.#fault(`The Oxlint language server exited with ${ending}`, error, path);
975
+ if (error instanceof Error) return error;
976
+ return this.#fault(describeUnknown(error), error, path);
1009
977
  }
1010
978
  #fault(message, cause, path) {
1011
979
  return new ProbeError(message, {
@@ -1018,153 +986,22 @@ var LintStage = class {
1018
986
  ...cause === void 0 ? {} : { cause }
1019
987
  });
1020
988
  }
1021
- get #reachable() {
1022
- return this.#ending === void 0 && this.#child?.stdin.writable === true;
1023
- }
1024
- #close(uri) {
1025
- this.#documents.delete(uri);
1026
- this.#publishes.delete(uri);
1027
- this.#refusals.delete(uri);
1028
- if (this.#destroyed || !this.#reachable) return;
1029
- this.#notify("textDocument/didClose", { textDocument: { uri } });
1030
- }
1031
- #request(method, params) {
1032
- this.#sequence += 1;
1033
- const id = this.#sequence;
1034
- const response = new Promise((resolve, reject) => {
1035
- this.#responses.set(id, resolve);
1036
- this.#failures.set(id, reject);
1037
- });
1038
- try {
1039
- this.#send({
1040
- jsonrpc: "2.0",
1041
- id,
1042
- method,
1043
- params
1044
- });
1045
- } catch (error) {
1046
- const reject = this.#failures.get(id);
1047
- this.#responses.delete(id);
1048
- this.#failures.delete(id);
1049
- reject?.(this.#fault(describeUnknown(error), error));
1050
- }
1051
- return response;
1052
- }
1053
- #notify(method, params) {
1054
- this.#sequence += 1;
1055
- this.#send({
1056
- jsonrpc: "2.0",
1057
- method,
1058
- params
1059
- });
1060
- }
1061
- #send(message) {
1062
- const child = this.#child;
1063
- if (this.#ending !== void 0) throw this.#fault(`The Oxlint language server exited with ${this.#ending}`);
1064
- if (child === void 0) throw this.#fault("The Oxlint language server is not running");
1065
- if (!child.stdin.writable) throw this.#fault("The Oxlint language server closed its input");
1066
- const content = JSON.stringify(message);
1067
- const header = `Content-Length: ${Buffer.byteLength(content)}\r\n\r\n`;
1068
- child.stdin.write(header + content);
1069
- }
1070
- #read(chunk) {
1071
- this.#buffer = Buffer.concat([this.#buffer, chunk]);
1072
- while (this.#frame());
1073
- }
1074
- #frame() {
1075
- const boundary = this.#buffer.indexOf("\r\n\r\n");
1076
- if (boundary < 0) return false;
1077
- const length = parseContentLength(this.#buffer.subarray(0, boundary).toString("ascii"));
1078
- if (length === void 0) {
1079
- this.#fail(this.#fault("Oxlint sent an invalid JSON-RPC frame header"));
1080
- return false;
1081
- }
1082
- const start = boundary + 4;
1083
- const end = start + length;
1084
- if (this.#buffer.length < end) return false;
1085
- const content = this.#buffer.subarray(start, end).toString("utf8");
1086
- this.#buffer = this.#buffer.subarray(end);
1087
- try {
1088
- const message = JSON.parse(content);
1089
- this.#receive(message);
1090
- } catch (error) {
1091
- this.#fail(this.#fault(`Oxlint sent invalid JSON: ${describeUnknown(error)}`, error));
1092
- }
1093
- return true;
1094
- }
1095
- #receive(message) {
1096
- if (typeof message !== "object" || message === null) return;
1097
- if ("id" in message && typeof message.id === "number") {
1098
- const id = message.id;
1099
- const resolve = this.#responses.get(id);
1100
- const reject = this.#failures.get(id);
1101
- this.#responses.delete(id);
1102
- this.#failures.delete(id);
1103
- if ("error" in message && message.error !== void 0) reject?.(this.#fault(describeUnknown(message.error)));
1104
- else resolve?.("result" in message ? message.result : void 0);
1105
- return;
1106
- }
1107
- if (!("method" in message) || message.method !== "textDocument/publishDiagnostics") return;
1108
- if (!("params" in message) || typeof message.params !== "object" || message.params === null) return;
1109
- const params = message.params;
1110
- if (!("uri" in params) || typeof params.uri !== "string") return;
1111
- if (!("diagnostics" in params) || !Array.isArray(params.diagnostics)) return;
1112
- const publish = this.#publishes.get(params.uri);
1113
- if (publish === void 0) return;
1114
- publish(this.#issues(params.uri, params.diagnostics));
1115
- }
1116
- #issues(uri, diagnostics) {
1117
- const path = this.#documents.get(uri);
1118
- if (path === void 0) return [];
1119
- const issues = [];
1120
- for (const diagnostic of diagnostics) {
1121
- if (typeof diagnostic !== "object" || diagnostic === null) continue;
1122
- if (!("message" in diagnostic) || typeof diagnostic.message !== "string") continue;
1123
- if (!("range" in diagnostic) || typeof diagnostic.range !== "object" || diagnostic.range === null) {
1124
- issues.push({
1125
- origin: "claimant",
1126
- path,
1127
- message: diagnostic.message
1128
- });
1129
- continue;
1130
- }
1131
- const range = diagnostic.range;
1132
- if (!("start" in range) || typeof range.start !== "object" || range.start === null) {
1133
- issues.push({
1134
- origin: "claimant",
1135
- path,
1136
- message: diagnostic.message
1137
- });
1138
- continue;
1139
- }
1140
- const start = range.start;
1141
- if (!("line" in start) || typeof start.line !== "number") {
1142
- issues.push({
1143
- origin: "claimant",
1144
- path,
1145
- message: diagnostic.message
1146
- });
1147
- continue;
989
+ #issues(path, diagnostics) {
990
+ return diagnostics.map((diagnostic) => ({
991
+ origin: "claimant",
992
+ path,
993
+ message: diagnostic.message,
994
+ range: {
995
+ start: {
996
+ line: diagnostic.range.start.line,
997
+ character: diagnostic.range.start.character
998
+ },
999
+ end: {
1000
+ line: diagnostic.range.end.line,
1001
+ character: diagnostic.range.end.character
1002
+ }
1148
1003
  }
1149
- issues.push({
1150
- origin: "claimant",
1151
- path,
1152
- message: diagnostic.message,
1153
- line: start.line + 1
1154
- });
1155
- }
1156
- return issues;
1157
- }
1158
- #fail(error) {
1159
- for (const reject of this.#failures.values()) reject(error);
1160
- for (const reject of this.#refusals.values()) reject(error);
1161
- this.#responses.clear();
1162
- this.#failures.clear();
1163
- this.#publishes.clear();
1164
- this.#refusals.clear();
1165
- }
1166
- #exit(code, signal) {
1167
- this.#fail(this.#fault(`The Oxlint language server exited with ${this.#describe(code, signal)}`));
1004
+ }));
1168
1005
  }
1169
1006
  };
1170
1007
  //#endregion
@@ -1795,11 +1632,19 @@ var RuntimeStage = class {
1795
1632
  path,
1796
1633
  message
1797
1634
  };
1635
+ const character = "column" in stack && typeof stack.column === "number" ? stack.column - 1 : 0;
1636
+ const position = {
1637
+ line: stack.line - 1,
1638
+ character
1639
+ };
1798
1640
  return {
1799
1641
  origin: "claimant",
1800
1642
  path,
1801
1643
  message,
1802
- line: stack.line
1644
+ range: {
1645
+ start: position,
1646
+ end: position
1647
+ }
1803
1648
  };
1804
1649
  }
1805
1650
  return {
@@ -2141,7 +1986,10 @@ var TypeStage = class {
2141
1986
  origin: "claimant",
2142
1987
  path,
2143
1988
  message,
2144
- line: diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start).line + 1
1989
+ range: {
1990
+ start: diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start),
1991
+ end: diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start + (diagnostic.length ?? 0))
1992
+ }
2145
1993
  };
2146
1994
  }
2147
1995
  };
@@ -2179,6 +2027,7 @@ var Probe = class {
2179
2027
  #lintQueue;
2180
2028
  #runtimeQueue;
2181
2029
  #deadlines = /* @__PURE__ */ new WeakSet();
2030
+ #surfaced = /* @__PURE__ */ new WeakSet();
2182
2031
  #typeTail = Promise.resolve();
2183
2032
  #arming;
2184
2033
  #closing;
@@ -2260,7 +2109,7 @@ var Probe = class {
2260
2109
  this.#emitter.emit("prove", verdict);
2261
2110
  return verdict;
2262
2111
  } catch (error) {
2263
- this.#emitter.emit("error", error);
2112
+ if (!(error instanceof ProbeError) || !this.#surfaced.has(error)) this.#emitter.emit("error", error);
2264
2113
  throw error;
2265
2114
  }
2266
2115
  }
@@ -2285,18 +2134,30 @@ var Probe = class {
2285
2134
  await this.#arming;
2286
2135
  }
2287
2136
  async #arm() {
2288
- const created = this.#workbench();
2137
+ let created;
2138
+ try {
2139
+ created = this.#workbench();
2140
+ } catch (error) {
2141
+ this.#surface(error);
2142
+ throw error;
2143
+ }
2289
2144
  try {
2290
2145
  await this.#boot(created);
2291
2146
  } catch (error) {
2292
- throw new ProbeError(`The probe could not arm: ${describeUnknown(error)}`, {
2147
+ const failure = new ProbeError(`The probe could not arm: ${describeUnknown(error)}`, {
2293
2148
  origin: "instrument",
2294
2149
  code: "malformed",
2295
2150
  cause: error
2296
2151
  });
2152
+ this.#surface(failure);
2153
+ throw failure;
2297
2154
  }
2298
2155
  this.#emitter.emit("arm", this.#toolchain);
2299
2156
  }
2157
+ #surface(error) {
2158
+ if (error instanceof ProbeError) this.#surfaced.add(error);
2159
+ this.#emitter.emit("error", error);
2160
+ }
2300
2161
  #workbench() {
2301
2162
  const path = "tmp/probe";
2302
2163
  const directory = resolveWorkspaceFile(this.#workspace, path);
@@ -2421,16 +2282,18 @@ var Probe = class {
2421
2282
  const release = await this.#admitType();
2422
2283
  try {
2423
2284
  const stage = this.#type;
2424
- return await this.#inspectStage(stage, stage.progress, stage.inspect(inspection.subject, inspection.claim.project), inspection.claim);
2285
+ return await this.#inspectStage(stage, stage.progress, () => stage.inspect(inspection.subject, inspection.claim.project), inspection.claim);
2425
2286
  } finally {
2426
2287
  release();
2427
2288
  }
2428
2289
  }
2429
2290
  #inspectLint(inspection) {
2430
- return this.#inspectStage(this.#lint, this.#lint.progress, this.#lint.inspect(inspection.subject), inspection.claim);
2291
+ const stage = this.#lint;
2292
+ return this.#inspectStage(stage, stage.progress, (signal) => stage.inspect(inspection.subject, { signal }), inspection.claim);
2431
2293
  }
2432
2294
  #inspectRuntime(inspection) {
2433
- return this.#inspectStage(this.#runtime, this.#runtime.progress, this.#runtime.inspect(inspection.subject), inspection.claim);
2295
+ const stage = this.#runtime;
2296
+ return this.#inspectStage(stage, stage.progress, () => stage.inspect(inspection.subject), inspection.claim);
2434
2297
  }
2435
2298
  async #inspectStage(stage, progress, operation, claim) {
2436
2299
  try {
@@ -2443,12 +2306,14 @@ var Probe = class {
2443
2306
  async #bound(operation, message, stage, progress) {
2444
2307
  const timeout = createTimeout({ ms: this.#deadline });
2445
2308
  timeout.start();
2309
+ const expiry = this.#expiry(timeout, message, stage, progress);
2310
+ const refusal = expiry.catch((error) => error);
2446
2311
  try {
2447
- return await Promise.race([operation, this.#expiry(timeout, message, stage, progress)]);
2312
+ return await Promise.race([operation(timeout.signal), expiry]);
2448
2313
  } catch (error) {
2449
2314
  if (!timeout.expired) throw error;
2450
2315
  await this.#recycle(stage);
2451
- throw error;
2316
+ throw await refusal;
2452
2317
  } finally {
2453
2318
  timeout.clear();
2454
2319
  }
@@ -2489,7 +2354,7 @@ var Probe = class {
2489
2354
  try {
2490
2355
  const stage = this.#type;
2491
2356
  try {
2492
- return await this.#bound(stage.resolve(claim.project), `The type stage project resolution exceeded ${this.#deadline} ms`, stage, stage.progress);
2357
+ return await this.#bound(() => stage.resolve(claim.project), `The type stage project resolution exceeded ${this.#deadline} ms`, stage, stage.progress);
2493
2358
  } catch (error) {
2494
2359
  this.#emitExpiry(stage, claim);
2495
2360
  throw error;
@@ -2530,7 +2395,7 @@ var Probe = class {
2530
2395
  }
2531
2396
  async #destroyStage(stage) {
2532
2397
  try {
2533
- await this.#bound(stage.destroy(), `The ${stage.stage} stage teardown exceeded ${this.#deadline} ms`, stage, stage.progress);
2398
+ await this.#bound(() => stage.destroy(), `The ${stage.stage} stage teardown exceeded ${this.#deadline} ms`, stage, stage.progress);
2534
2399
  } catch (error) {
2535
2400
  if (!(error instanceof ProbeError) || !this.#deadlines.has(error)) throw error;
2536
2401
  }
@@ -2718,6 +2583,6 @@ var ProbeServer = class {
2718
2583
  }
2719
2584
  };
2720
2585
  //#endregion
2721
- export { LintStage, Overlay, Probe, ProbeServer, RuntimeStage, TypeStage, captureListeners, computeDigest, createRevisionFile, describeUnknown, findRefusedPaths, guardStage, inferDocumentLanguage, inferTestProject, inferTypeProject, isRefusedName, loadWorkspaceModule, matchesWorkspaceModule, normalizePath, normalizeValue, overwriteFile, parseContentLength, readWorkspaceManifest, relativeWorkspaceFile, relativeWorkspaceMessage, releaseListeners, resolveWorkspaceBinary, resolveWorkspaceFile, resolveWorkspaceModule };
2586
+ export { LintStage, Overlay, Probe, ProbeServer, RuntimeStage, TypeStage, captureListeners, computeDigest, createRevisionFile, describeUnknown, findRefusedPaths, guardStage, inferDocumentLanguage, inferTestProject, inferTypeProject, isRefusedName, loadWorkspaceModule, matchesWorkspaceModule, normalizePath, normalizeValue, overwriteFile, readWorkspaceManifest, relativeWorkspaceFile, relativeWorkspaceMessage, releaseListeners, resolveWorkspaceBinary, resolveWorkspaceFile, resolveWorkspaceModule };
2722
2587
 
2723
2588
  //# sourceMappingURL=index.js.map