@orkestrel/probe 0.0.20 → 0.0.22

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,4 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let _orkestrel_mcp = require("@orkestrel/mcp");
2
3
  let node_fs = require("node:fs");
3
4
  let node_crypto = require("node:crypto");
4
5
  let node_module = require("node:module");
@@ -7,15 +8,34 @@ let node_url = require("node:url");
7
8
  let _orkestrel_contract = require("@orkestrel/contract");
8
9
  let _src_core = require("../core/index.cjs");
9
10
  let _orkestrel_emitter = require("@orkestrel/emitter");
10
- let _orkestrel_queue = require("@orkestrel/queue");
11
- let _orkestrel_timeout = require("@orkestrel/timeout");
11
+ let _orkestrel_pool = require("@orkestrel/pool");
12
12
  let _orkestrel_lsp = require("@orkestrel/lsp");
13
+ let node_events = require("node:events");
14
+ let _orkestrel_timeout = require("@orkestrel/timeout");
13
15
  let _orkestrel_lsp_server = require("@orkestrel/lsp/server");
14
16
  let node_stream = require("node:stream");
15
17
  let node_child_process = require("node:child_process");
16
- let _orkestrel_mcp = require("@orkestrel/mcp");
17
18
  let _orkestrel_mcp_server = require("@orkestrel/mcp/server");
18
19
  let _orkestrel_tool = require("@orkestrel/tool");
20
+ //#region src/server/errors.ts
21
+ /**
22
+ * Creates the protocol failure returned when the MCP handshake cannot arm the probe.
23
+ *
24
+ * @param error - The classified probe failure that ended initialization
25
+ * @returns An MCP server error carrying the probe failure's origin and code as protocol data
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * createHandshakeError(createDestroyedError('probe server')).code // -32000
30
+ * ```
31
+ */
32
+ function createHandshakeError(error) {
33
+ return new _orkestrel_mcp.MCPError(`[${error.origin}] ${error.code}: ${error.message}`, _orkestrel_mcp.JSONRPC_SERVER_ERROR, {
34
+ origin: error.origin,
35
+ code: error.code
36
+ });
37
+ }
38
+ //#endregion
19
39
  //#region src/server/helpers.ts
20
40
  /**
21
41
  * Rewrites one path into the forward-slash spelling this package compares and reports paths in.
@@ -1104,7 +1124,7 @@ var Overlay = class {
1104
1124
  };
1105
1125
  //#endregion
1106
1126
  //#region package.json
1107
- var version = "0.0.20";
1127
+ var version = "0.0.22";
1108
1128
  var peerDependencies = {
1109
1129
  "typescript": "^6.0.3",
1110
1130
  "vitest": "^4.1.11"
@@ -1115,7 +1135,7 @@ var peerDependencies = {
1115
1135
  * Inspects virtual documents through one resident Oxlint language server.
1116
1136
  *
1117
1137
  * @remarks
1118
- * Construction starts the target workspace's Oxlint binary with its Language Server Protocol mode
1138
+ * Starting launches the target workspace's Oxlint binary with its Language Server Protocol mode
1119
1139
  * and hands the conversation to `@orkestrel/lsp`: `createStdioClientTransport` owns the child
1120
1140
  * process and its bytes, and `createLSPClient` owns the framing, the request correlation, the
1121
1141
  * capabilities this stage advertises, and the choice between pulled and pushed diagnostics. This
@@ -1151,21 +1171,22 @@ var peerDependencies = {
1151
1171
  var LintStage = class {
1152
1172
  #workspace;
1153
1173
  #documents = /* @__PURE__ */ new Set();
1154
- #warmth;
1174
+ #warming;
1175
+ #exited = Promise.withResolvers();
1155
1176
  #client;
1177
+ #transport;
1156
1178
  #ending;
1157
1179
  #closing;
1158
1180
  #progress = 0;
1159
1181
  #revision = 0;
1160
1182
  /**
1161
- * Starts warming the target workspace's Oxlint language server.
1183
+ * Records the target workspace without starting its language server.
1162
1184
  *
1163
1185
  * @param workspace - The target workspace root. Default: the current working directory
1164
1186
  */
1165
1187
  constructor(workspace = process.cwd()) {
1166
1188
  this.#workspace = workspace;
1167
- this.#warmth = this.#warm();
1168
- this.#warmth.catch(() => {});
1189
+ this.#exited.promise.catch(() => {});
1169
1190
  }
1170
1191
  get stage() {
1171
1192
  return "lint";
@@ -1173,6 +1194,15 @@ var LintStage = class {
1173
1194
  get progress() {
1174
1195
  return this.#progress;
1175
1196
  }
1197
+ get exit() {
1198
+ return this.#exited.promise;
1199
+ }
1200
+ start() {
1201
+ if (this.#closing !== void 0) return Promise.reject((0, _src_core.createDestroyedError)("lint stage"));
1202
+ this.#warming ??= guardStage(this.stage, this.#warm());
1203
+ this.#warming.catch(() => {});
1204
+ return this.#warming;
1205
+ }
1176
1206
  inspect(subject, options) {
1177
1207
  return guardStage(this.stage, this.#inspect(subject, options));
1178
1208
  }
@@ -1189,8 +1219,10 @@ var LintStage = class {
1189
1219
  context: { stage: this.stage }
1190
1220
  });
1191
1221
  const started = performance.now();
1192
- const client = await this.#warmed();
1222
+ await this.start();
1193
1223
  if (this.#closing !== void 0) throw (0, _src_core.createDestroyedError)("lint stage");
1224
+ const client = this.#client;
1225
+ if (client === void 0) throw this.#fault("The Oxlint language server did not initialize");
1194
1226
  const issues = [];
1195
1227
  for (const draft of [...subject.files, subject.test]) issues.push(...await this.#document(client, draft, options.signal));
1196
1228
  return {
@@ -1201,38 +1233,39 @@ var LintStage = class {
1201
1233
  }
1202
1234
  async #destroy() {
1203
1235
  await this.#client?.destroy();
1236
+ await this.#transport?.close();
1204
1237
  }
1205
1238
  async #warm() {
1206
1239
  const binary = resolveWorkspaceBinary(this.#workspace, "oxlint");
1240
+ const transport = (0, _orkestrel_lsp_server.createStdioClientTransport)({
1241
+ server: {
1242
+ command: [
1243
+ process.execPath,
1244
+ binary,
1245
+ "--lsp"
1246
+ ],
1247
+ directory: this.#workspace
1248
+ },
1249
+ grace: _src_core.LINT_DEADLINE / 2
1250
+ });
1251
+ this.#transport = transport;
1207
1252
  const client = (0, _orkestrel_lsp.createLSPClient)({
1208
- transport: (0, _orkestrel_lsp_server.createStdioClientTransport)({
1209
- server: {
1210
- command: [
1211
- process.execPath,
1212
- binary,
1213
- "--lsp"
1214
- ],
1215
- directory: this.#workspace
1216
- },
1217
- grace: _src_core.LINT_DEADLINE / 2
1218
- }),
1253
+ transport,
1219
1254
  workspace: (0, node_url.pathToFileURL)(this.#workspace).href,
1220
1255
  timeout: _src_core.LINT_DEADLINE
1221
1256
  });
1222
1257
  this.#client = client;
1223
1258
  client.emitter.on("exit", (exit) => this.#retire(exit));
1224
- await client.start();
1225
- return client;
1226
- }
1227
- async #warmed() {
1228
1259
  try {
1229
- return await this.#warmth;
1260
+ await client.start();
1230
1261
  } catch (error) {
1231
1262
  throw this.#translate(error);
1232
1263
  }
1233
1264
  }
1234
1265
  #retire(exit) {
1266
+ if (this.#closing !== void 0) return;
1235
1267
  this.#ending = exit.signal === null ? `code ${exit.code}` : `signal ${exit.signal}`;
1268
+ this.#exited.reject(this.#fault(`The Oxlint language server exited with ${this.#ending}`));
1236
1269
  }
1237
1270
  async #document(client, draft, signal) {
1238
1271
  const uri = (0, node_url.pathToFileURL)(resolveWorkspaceFile(this.#workspace, draft.path)).href;
@@ -1275,9 +1308,9 @@ var LintStage = class {
1275
1308
  });
1276
1309
  }
1277
1310
  #translate(error, path) {
1278
- if (this.#closing !== void 0) return (0, _src_core.createDestroyedError)("lint stage");
1279
1311
  const ending = this.#ending;
1280
1312
  if (ending !== void 0) return this.#fault(`The Oxlint language server exited with ${ending}`, error, path);
1313
+ if (this.#closing !== void 0) return (0, _src_core.createDestroyedError)("lint stage");
1281
1314
  if ((0, _orkestrel_contract.isError)(error)) return error;
1282
1315
  return this.#fault(describeUnknown(error), error, path);
1283
1316
  }
@@ -1316,7 +1349,7 @@ var LintStage = class {
1316
1349
  * Inspects tests through one resident Vitest service from the target workspace.
1317
1350
  *
1318
1351
  * @remarks
1319
- * Construction starts Vitest with the threads pool and instruments each inline or function-declared
1352
+ * Starting launches Vitest with the threads pool and instruments each inline or function-declared
1320
1353
  * project with a Vite plugin that reads the active inspection's candidate overlay. A selected
1321
1354
  * string-declared project reports a workspace issue because its configuration carries no runtime
1322
1355
  * overlay plugin. Every inspection writes one fresh sibling specification, invalidates each
@@ -1375,6 +1408,7 @@ var LintStage = class {
1375
1408
  var RuntimeStage = class {
1376
1409
  #workspace;
1377
1410
  #vitest;
1411
+ #warming;
1378
1412
  #overlay = new Overlay();
1379
1413
  #loads = /* @__PURE__ */ new Set();
1380
1414
  #reads = /* @__PURE__ */ new Set();
@@ -1384,16 +1418,12 @@ var RuntimeStage = class {
1384
1418
  #closing;
1385
1419
  #progress = 0;
1386
1420
  /**
1387
- * Starts warming the target workspace's Vitest service.
1388
- *
1389
- * @remarks Loading starts during this call. A loader refusal rejects the owned warm operation
1390
- * rather than construction, so `inspect` reports it and `destroy` remains available.
1421
+ * Records the target workspace without loading its Vitest service.
1391
1422
  *
1392
1423
  * @param workspace - The target workspace root. Default: the current working directory
1393
1424
  */
1394
1425
  constructor(workspace = process.cwd()) {
1395
1426
  this.#workspace = workspace;
1396
- this.#store(this.#warm());
1397
1427
  }
1398
1428
  get stage() {
1399
1429
  return "runtime";
@@ -1402,6 +1432,20 @@ var RuntimeStage = class {
1402
1432
  return this.#progress;
1403
1433
  }
1404
1434
  /**
1435
+ * Begins the warm or joins the warm already begun.
1436
+ *
1437
+ * @returns A promise that resolves after the resident Vitest service is ready
1438
+ * @throws When warming fails or teardown has begun
1439
+ */
1440
+ start() {
1441
+ if (this.#closing !== void 0) return Promise.reject((0, _src_core.createDestroyedError)("runtime stage"));
1442
+ this.#warming ??= guardStage(this.stage, this.#store(this.#warm()).then(() => {}));
1443
+ this.#warming.catch(() => {
1444
+ this.#warming = void 0;
1445
+ });
1446
+ return this.#warming;
1447
+ }
1448
+ /**
1405
1449
  * Inspects one case.
1406
1450
  *
1407
1451
  * @param subject - The candidate drafts and test to inspect
@@ -1432,6 +1476,7 @@ var RuntimeStage = class {
1432
1476
  async #inspect(subject) {
1433
1477
  if (this.#closing !== void 0) throw (0, _src_core.createDestroyedError)("runtime stage");
1434
1478
  const started = performance.now();
1479
+ await this.start();
1435
1480
  const exitCode = process.exitCode;
1436
1481
  const vitest = await this.#runner();
1437
1482
  if (this.#closing !== void 0) throw (0, _src_core.createDestroyedError)("runtime stage");
@@ -1964,7 +2009,7 @@ var RuntimeStage = class {
1964
2009
  *
1965
2010
  * @remarks
1966
2011
  * The compiler is a process this stage spawns rather than a module it loads, so nothing here holds
1967
- * a resident program and the host's event loop is free for the whole check. Construction sweeps a
2012
+ * a resident program and the host's event loop is free for the whole check. Starting sweeps a
1968
2013
  * mirror an earlier host left behind, copies the workspace into a fresh one under
1969
2014
  * {@link TYPE_MIRROR}, and builds each declared project's incremental state there, so the first
1970
2015
  * inspection is warm.
@@ -2007,16 +2052,13 @@ var TypeStage = class {
2007
2052
  #closing;
2008
2053
  #progress = 0;
2009
2054
  /**
2010
- * Resolves the target workspace's compiler and starts warming its mirror.
2055
+ * Records the workspace and the mirror path without starting its compiler.
2011
2056
  *
2012
2057
  * @param workspace - The target workspace root. Default: the current working directory
2013
2058
  */
2014
2059
  constructor(workspace = process.cwd()) {
2015
2060
  this.#workspace = workspace;
2016
- this.#compiler = resolveWorkspaceBinary(workspace, "typescript", "tsc");
2017
- this.#mirror = resolveWorkspaceFile(workspace, `${_src_core.TYPE_MIRROR}/${this.#revision}`);
2018
- this.#warming = this.#warm();
2019
- this.#warming.catch(() => {});
2061
+ this.#mirror = (0, node_path.resolve)(workspace, _src_core.TYPE_MIRROR, this.#revision);
2020
2062
  }
2021
2063
  get stage() {
2022
2064
  return "type";
@@ -2024,6 +2066,12 @@ var TypeStage = class {
2024
2066
  get progress() {
2025
2067
  return this.#progress;
2026
2068
  }
2069
+ start() {
2070
+ if (this.#closing !== void 0) return Promise.reject((0, _src_core.createDestroyedError)("type stage"));
2071
+ this.#warming ??= guardStage(this.stage, this.#warm());
2072
+ this.#warming.catch(() => {});
2073
+ return this.#warming;
2074
+ }
2027
2075
  /**
2028
2076
  * Inspects one case, against a caller-named project where the caller names one.
2029
2077
  *
@@ -2067,7 +2115,7 @@ var TypeStage = class {
2067
2115
  async #inspect(subject, project) {
2068
2116
  this.#refuseDestroyed();
2069
2117
  const started = performance.now();
2070
- await this.#warming;
2118
+ await this.start();
2071
2119
  this.#refuseDestroyed();
2072
2120
  const test = resolveWorkspaceFile(this.#workspace, subject.test.path);
2073
2121
  const resolved = subject.files.map((draft) => ({
@@ -2103,6 +2151,8 @@ var TypeStage = class {
2103
2151
  }
2104
2152
  }
2105
2153
  async #resolve(project) {
2154
+ this.#refuseDestroyed();
2155
+ await this.start();
2106
2156
  this.#refuseDestroyed();
2107
2157
  const contained = this.#contain(project);
2108
2158
  this.#progress += 1;
@@ -2116,7 +2166,7 @@ var TypeStage = class {
2116
2166
  const children = [...this.#children];
2117
2167
  this.#children.clear();
2118
2168
  for (const child of children) this.#terminate(child);
2119
- await this.#warming.catch(() => void 0);
2169
+ await this.#warming?.catch(() => void 0);
2120
2170
  try {
2121
2171
  (0, node_fs.rmSync)(this.#mirror, {
2122
2172
  recursive: true,
@@ -2130,6 +2180,7 @@ var TypeStage = class {
2130
2180
  this.#drafts.clear();
2131
2181
  }
2132
2182
  async #warm() {
2183
+ this.#compiler = resolveWorkspaceBinary(this.#workspace, "typescript", "tsc");
2133
2184
  this.#sweep();
2134
2185
  this.#createMirror();
2135
2186
  this.#refresh();
@@ -2364,8 +2415,10 @@ var TypeStage = class {
2364
2415
  }
2365
2416
  #spawn(args, cwd) {
2366
2417
  this.#refuseDestroyed();
2418
+ const compiler = this.#compiler;
2419
+ if (compiler === void 0) throw (0, _src_core.createDestroyedError)("type stage");
2367
2420
  return new Promise((settle, refuse) => {
2368
- const child = (0, node_child_process.spawn)(process.execPath, [this.#compiler, ...args], {
2421
+ const child = (0, node_child_process.spawn)(process.execPath, [compiler, ...args], {
2369
2422
  cwd,
2370
2423
  stdio: [
2371
2424
  "ignore",
@@ -2402,7 +2455,7 @@ var TypeStage = class {
2402
2455
  code: "malformed",
2403
2456
  context: {
2404
2457
  stage: this.stage,
2405
- path: this.#compiler
2458
+ path: compiler
2406
2459
  },
2407
2460
  cause: error
2408
2461
  }));
@@ -2437,9 +2490,10 @@ var TypeStage = class {
2437
2490
  * Answers claims through its type, lint, and runtime stages.
2438
2491
  *
2439
2492
  * @remarks
2440
- * Construction resolves the target workspace's toolchain and begins warming every stage. The boot
2493
+ * Construction resolves the target workspace's toolchain. Starting warms every stage and waits
2494
+ * for lint and runtime; type warming and the boot controls continue behind that gate. The boot
2441
2495
  * controls mutate imported dependencies and refuse service unless the type and runtime stages
2442
- * report their respective changes. One queue per stage admits inspections in arrival order, one at
2496
+ * report their respective changes. One pool per stage admits inspections in arrival order, one at
2443
2497
  * a time, so a stage never serves two claims at once and the deadline covers active work rather
2444
2498
  * than queue wait. Each active stage inspection has a coordinator-owned deadline. An expiry at any
2445
2499
  * stage abandons that stage and replaces it before the next queued inspection begins, so one slow
@@ -2484,34 +2538,36 @@ var TypeStage = class {
2484
2538
  * const probe = new Probe({ workspace: process.cwd() })
2485
2539
  * const verdict = await probe.prove(claim)
2486
2540
  * verdict.digest // 'bdf03e5dfd6bd413ead671c7a2940fcf'
2487
- * verdict.receipt // 'probe:bdf03e5dfd6bd413ead671c7a2940fcf:type:typescript@6.0.3:oxlint@1.86.0:vitest@4.1.11:configs/src/tsconfig.core.json@434f59254d58cf2683d453a26bd0d837'
2541
+ * verdict.receipt // 'probe:bdf03e5dfd6bd413ead671c7a2940fcf:type:typescript@6.0.3:oxlint@1.87.0:vitest@4.1.11:configs/src/tsconfig.core.json@434f59254d58cf2683d453a26bd0d837'
2488
2542
  * await probe.destroy()
2489
2543
  * ```
2490
2544
  */
2491
2545
  var Probe = class {
2492
2546
  #workspace;
2493
2547
  #deadline;
2548
+ #warmth;
2494
2549
  #emitter;
2495
2550
  #toolchain;
2496
2551
  #type;
2497
2552
  #lint;
2498
2553
  #runtime;
2499
- #typeQueue;
2500
- #lintQueue;
2501
- #runtimeQueue;
2502
2554
  #deadlines = /* @__PURE__ */ new WeakSet();
2503
2555
  #surfaced = /* @__PURE__ */ new WeakSet();
2504
- #typeTail = Promise.resolve();
2556
+ #survivors = /* @__PURE__ */ new Map();
2557
+ #abort = new AbortController();
2505
2558
  #arming;
2559
+ #refusal;
2560
+ #starting;
2506
2561
  #closing;
2507
2562
  /**
2508
- * Resolves the target toolchain and starts warming every stage.
2563
+ * Resolves the target toolchain and constructs the stage pools without warming them.
2509
2564
  *
2510
- * @param options - Workspace, deadline, and initial observation hooks
2565
+ * @param options - Workspace, inspection and warm bounds, and initial observation hooks
2511
2566
  */
2512
2567
  constructor(options) {
2513
2568
  this.#workspace = options?.workspace ?? process.cwd();
2514
2569
  this.#deadline = (0, _orkestrel_timeout.createTimeout)({ ms: options?.deadline ?? _src_core.PROBE_DEADLINE }).ms;
2570
+ this.#warmth = (0, _orkestrel_timeout.createTimeout)({ ms: options?.warm ?? _src_core.PROBE_WARM }).ms;
2515
2571
  this.#emitter = new _orkestrel_emitter.Emitter({
2516
2572
  ...options?.on === void 0 ? {} : { on: options.on },
2517
2573
  ...options?.error === void 0 ? {} : { error: options.error }
@@ -2521,26 +2577,28 @@ var Probe = class {
2521
2577
  oxlint: this.#version("oxlint"),
2522
2578
  vitest: this.#version("vitest")
2523
2579
  });
2524
- this.#type = new TypeStage(this.#workspace);
2525
- this.#lint = new LintStage(this.#workspace);
2526
- this.#runtime = new RuntimeStage(this.#workspace);
2527
- this.#typeQueue = (0, _orkestrel_queue.createQueue)({
2528
- concurrency: 1,
2529
- retries: 0,
2530
- handler: this.#inspectType.bind(this)
2580
+ this.#type = (0, _orkestrel_pool.createPool)({
2581
+ create: () => this.#warm(new TypeStage(this.#workspace)),
2582
+ destroy: this.#dispose.bind(this),
2583
+ error: this.#surface.bind(this),
2584
+ min: 1,
2585
+ restarts: _src_core.PROBE_RESTARTS
2531
2586
  });
2532
- this.#lintQueue = (0, _orkestrel_queue.createQueue)({
2533
- concurrency: 1,
2534
- retries: 0,
2535
- handler: this.#inspectLint.bind(this)
2587
+ this.#lint = (0, _orkestrel_pool.createPool)({
2588
+ create: this.#createLint.bind(this),
2589
+ destroy: this.#dispose.bind(this),
2590
+ watch: (stage) => stage.exit,
2591
+ error: this.#surface.bind(this),
2592
+ min: 1,
2593
+ restarts: _src_core.PROBE_RESTARTS
2536
2594
  });
2537
- this.#runtimeQueue = (0, _orkestrel_queue.createQueue)({
2538
- concurrency: 1,
2539
- retries: 0,
2540
- handler: this.#inspectRuntime.bind(this)
2595
+ this.#runtime = (0, _orkestrel_pool.createPool)({
2596
+ create: () => this.#warm(new RuntimeStage(this.#workspace)),
2597
+ destroy: this.#dispose.bind(this),
2598
+ error: this.#surface.bind(this),
2599
+ min: 1,
2600
+ restarts: _src_core.PROBE_RESTARTS
2541
2601
  });
2542
- this.#arming = this.#arm();
2543
- this.#arming.catch(() => {});
2544
2602
  }
2545
2603
  get emitter() {
2546
2604
  return this.#emitter;
@@ -2552,7 +2610,16 @@ var Probe = class {
2552
2610
  try {
2553
2611
  this.#support();
2554
2612
  this.#admit(claim);
2555
- await this.#ready();
2613
+ await this.start();
2614
+ const arming = this.#arming;
2615
+ try {
2616
+ await arming;
2617
+ } catch (error) {
2618
+ if (this.#arming === arming) this.#arming = void 0;
2619
+ const refusal = this.#refusal;
2620
+ this.#refusal = void 0;
2621
+ throw refusal === void 0 ? error : refusal.cause;
2622
+ }
2556
2623
  if (this.#closing !== void 0) throw (0, _src_core.createDestroyedError)("probe");
2557
2624
  const started = performance.now();
2558
2625
  const id = (0, node_crypto.randomUUID)();
@@ -2587,45 +2654,65 @@ var Probe = class {
2587
2654
  }
2588
2655
  destroy() {
2589
2656
  if (this.#closing !== void 0) return this.#closing;
2590
- this.#closing = this.#destroy();
2657
+ this.#closing = Promise.resolve().then(this.#destroy.bind(this));
2591
2658
  return this.#closing;
2592
2659
  }
2593
- async #ready() {
2594
- const attempt = this.#arming;
2660
+ start() {
2661
+ if (this.#closing !== void 0) return Promise.reject((0, _src_core.createDestroyedError)("probe"));
2662
+ const warming = this.#type.start();
2663
+ warming.catch(() => {});
2664
+ const filling = Promise.all([this.#lint.start(), this.#runtime.start()]);
2665
+ filling.catch(() => {});
2666
+ if (this.#starting !== void 0) return this.#starting;
2667
+ this.#starting = this.#start(filling, warming);
2668
+ this.#starting.then(() => {
2669
+ this.#starting = void 0;
2670
+ }, () => {
2671
+ this.#starting = void 0;
2672
+ });
2673
+ return this.#starting;
2674
+ }
2675
+ async #start(filling, warming) {
2676
+ let created = false;
2595
2677
  try {
2596
- await attempt;
2597
- return;
2678
+ this.#support();
2679
+ if (this.#arming === void 0) created = this.#workbench();
2598
2680
  } catch (error) {
2599
- if (this.#closing !== void 0) throw error;
2681
+ this.#surface(error);
2682
+ throw error;
2683
+ }
2684
+ try {
2685
+ await filling;
2686
+ if (this.#closing !== void 0) throw (0, _src_core.createDestroyedError)("probe");
2687
+ } catch (error) {
2688
+ throw this.#refuseArm(error);
2600
2689
  }
2601
- if (this.#arming === attempt) {
2602
- this.#arming = this.#arm();
2603
- this.#arming.catch(() => {});
2690
+ if (this.#arming === void 0) {
2691
+ this.#arming = this.#arm(warming, created);
2692
+ this.#arming.catch(() => {
2693
+ if (this.#refusal === void 0) this.#arming = void 0;
2694
+ });
2604
2695
  }
2605
- await this.#arming;
2606
2696
  }
2607
- async #arm() {
2608
- let created;
2697
+ async #arm(warming, created) {
2609
2698
  try {
2610
- created = this.#workbench();
2699
+ await warming;
2611
2700
  } catch (error) {
2612
- this.#surface(error);
2613
- throw error;
2701
+ if (this.#closing !== void 0) throw (0, _src_core.createDestroyedError)("probe");
2702
+ const cause = this.#refusal?.cause ?? ((0, _orkestrel_pool.isPoolError)(error) ? error.cause : error);
2703
+ this.#surface(cause);
2704
+ throw cause;
2614
2705
  }
2615
2706
  try {
2707
+ if (this.#closing !== void 0) throw (0, _src_core.createDestroyedError)("probe");
2616
2708
  await this.#boot(created);
2617
2709
  } catch (error) {
2618
- const failure = new _src_core.ProbeError(`The probe could not arm: ${describeUnknown(error)}`, {
2619
- origin: "instrument",
2620
- code: "malformed",
2621
- cause: error
2622
- });
2623
- this.#surface(failure);
2624
- throw failure;
2710
+ throw this.#refuseArm(error);
2625
2711
  }
2626
2712
  this.#emitter.emit("arm", this.#toolchain);
2627
2713
  }
2628
2714
  #surface(error) {
2715
+ if (this.#closing !== void 0) return;
2629
2716
  if ((0, _src_core.isProbeError)(error)) this.#surfaced.add(error);
2630
2717
  this.#emitter.emit("error", error);
2631
2718
  }
@@ -2741,102 +2828,130 @@ var Probe = class {
2741
2828
  subject,
2742
2829
  claim
2743
2830
  };
2744
- const admitted = [
2745
- this.#typeQueue.enqueue(inspection),
2746
- this.#lintQueue.enqueue(inspection),
2747
- this.#runtimeQueue.enqueue(inspection)
2748
- ];
2749
- for (const pending of admitted) pending.catch(() => {});
2750
- return Promise.all(admitted);
2831
+ return Promise.all([
2832
+ this.#inspectType(inspection),
2833
+ this.#inspectLint(inspection),
2834
+ this.#inspectRuntime(inspection)
2835
+ ]);
2751
2836
  }
2752
2837
  async #inspectType(inspection) {
2753
- const release = await this.#admitType();
2754
- try {
2755
- const stage = this.#type;
2756
- return await this.#inspectStage(stage, stage.progress, () => stage.inspect(inspection.subject, inspection.claim.project), inspection.claim);
2757
- } finally {
2758
- release();
2759
- }
2838
+ const token = await this.#lease(this.#type);
2839
+ return this.#inspectStage(token, () => token.value.inspect(inspection.subject, inspection.claim.project), inspection.claim);
2760
2840
  }
2761
- #inspectLint(inspection) {
2762
- const stage = this.#lint;
2763
- return this.#inspectStage(stage, stage.progress, (signal) => stage.inspect(inspection.subject, { signal }), inspection.claim);
2841
+ async #inspectLint(inspection) {
2842
+ const token = await this.#lease(this.#lint);
2843
+ return this.#inspectStage(token, (signal) => token.value.inspect(inspection.subject, { signal }), inspection.claim);
2764
2844
  }
2765
- #inspectRuntime(inspection) {
2766
- const stage = this.#runtime;
2767
- return this.#inspectStage(stage, stage.progress, () => stage.inspect(inspection.subject), inspection.claim);
2845
+ async #inspectRuntime(inspection) {
2846
+ const token = await this.#lease(this.#runtime);
2847
+ return this.#inspectStage(token, () => token.value.inspect(inspection.subject), inspection.claim);
2768
2848
  }
2769
- async #inspectStage(stage, progress, operation, claim) {
2849
+ async #inspectStage(token, operation, claim, message = `The ${token.value.stage} stage exceeded ${this.#deadline} ms`) {
2850
+ const stage = token.value;
2770
2851
  try {
2771
- return await this.#bound(operation, `The ${stage.stage} stage exceeded ${this.#deadline} ms`, stage, progress);
2852
+ return await this.#bound(operation, message, stage, stage.progress);
2772
2853
  } catch (error) {
2773
- this.#emitExpiry(stage, claim);
2854
+ if ((0, _src_core.isProbeError)(error) && this.#deadlines.has(error)) {
2855
+ await token.destroy().catch(() => {});
2856
+ if (this.#closing === void 0) this.#emitter.emit("expire", claim);
2857
+ }
2774
2858
  throw error;
2859
+ } finally {
2860
+ token.release();
2775
2861
  }
2776
2862
  }
2777
- async #bound(operation, message, stage, progress) {
2778
- const timeout = (0, _orkestrel_timeout.createTimeout)({ ms: this.#deadline });
2863
+ async #bound(operation, message, stage, progress, ms = this.#deadline) {
2864
+ const timeout = (0, _orkestrel_timeout.createTimeout)({ ms });
2779
2865
  timeout.start();
2780
2866
  const expiry = this.#expiry(timeout, message, stage, progress);
2781
2867
  const refusal = expiry.catch((error) => error);
2782
2868
  try {
2783
2869
  return await Promise.race([operation(timeout.signal), expiry]);
2784
2870
  } catch (error) {
2785
- if (!timeout.expired) throw error;
2786
- await this.#recycle(stage);
2787
- throw await refusal;
2871
+ if (timeout.expired) throw await refusal;
2872
+ throw error;
2788
2873
  } finally {
2789
2874
  timeout.clear();
2790
2875
  }
2791
2876
  }
2792
- async #recycle(stage) {
2793
- if (this.#closing !== void 0) return false;
2794
- const timeout = (0, _orkestrel_timeout.createTimeout)({ ms: this.#deadline });
2795
- timeout.start();
2796
- try {
2797
- await Promise.race([stage.destroy(), this.#expiry(timeout, `The ${stage.stage} stage recovery exceeded ${this.#deadline} ms`, stage, stage.progress)]);
2798
- } catch {} finally {
2799
- timeout.clear();
2800
- }
2801
- if (this.#closing !== void 0) return false;
2802
- if (stage === this.#type) {
2803
- this.#type = new TypeStage(this.#workspace);
2804
- return true;
2805
- }
2806
- if (stage === this.#lint) {
2807
- this.#lint = new LintStage(this.#workspace);
2808
- return true;
2877
+ async #resolve(claim) {
2878
+ const token = await this.#lease(this.#type);
2879
+ if (this.#refusal !== void 0) {
2880
+ const refusal = this.#refusal;
2881
+ this.#refusal = void 0;
2882
+ token.release();
2883
+ throw refusal.cause;
2809
2884
  }
2810
- if (stage === this.#runtime) {
2811
- this.#runtime = new RuntimeStage(this.#workspace);
2812
- return true;
2885
+ const stage = token.value;
2886
+ return this.#inspectStage(token, () => stage.resolve(claim.project), claim, `The type stage project resolution exceeded ${this.#deadline} ms`);
2887
+ }
2888
+ async #lease(pool) {
2889
+ try {
2890
+ return await pool.acquire();
2891
+ } catch (error) {
2892
+ if ((0, _orkestrel_pool.isPoolError)(error)) {
2893
+ if (error.code === "destroyed") throw (0, _src_core.createDestroyedError)("probe");
2894
+ if (error.code === "create" && pool === this.#type) {
2895
+ const refusal = this.#refusal;
2896
+ this.#refusal = void 0;
2897
+ throw refusal === void 0 ? error.cause : refusal.cause;
2898
+ }
2899
+ if (error.code === "create" || error.code === "cleanup") throw error.cause;
2900
+ }
2901
+ throw error;
2813
2902
  }
2814
- return false;
2815
2903
  }
2816
- async #admitType() {
2817
- const previous = this.#typeTail;
2818
- const turn = Promise.withResolvers();
2819
- this.#typeTail = turn.promise;
2820
- await previous;
2821
- return turn.resolve;
2904
+ #createLint() {
2905
+ for (const cause of this.#survivors.values()) throw new _src_core.ProbeError("The Oxlint language server survived cleanup; restart the probe server", {
2906
+ origin: "instrument",
2907
+ code: "malformed",
2908
+ context: { stage: "lint" },
2909
+ cause
2910
+ });
2911
+ return this.#warm(new LintStage(this.#workspace));
2822
2912
  }
2823
- async #resolve(claim) {
2824
- const release = await this.#admitType();
2913
+ async #warm(stage) {
2914
+ const bound = stage.stage === "type" ? this.#warmth : this.#deadline;
2915
+ const aborted = Promise.withResolvers();
2916
+ aborted.promise.catch(() => {});
2917
+ const subscription = (0, node_events.addAbortListener)(this.#abort.signal, () => aborted.reject((0, _src_core.createDestroyedError)("probe")));
2825
2918
  try {
2826
- const stage = this.#type;
2919
+ if (this.#closing !== void 0) throw (0, _src_core.createDestroyedError)("probe");
2920
+ await this.#bound(() => Promise.race([stage.start(), aborted.promise]), `The ${stage.stage} stage warm exceeded ${bound} ms`, stage, stage.progress, bound);
2921
+ return stage;
2922
+ } catch (error) {
2923
+ if (stage.stage === "type" && this.#closing === void 0) this.#refusal ??= { cause: error };
2827
2924
  try {
2828
- return await this.#bound(() => stage.resolve(claim.project), `The type stage project resolution exceeded ${this.#deadline} ms`, stage, stage.progress);
2829
- } catch (error) {
2830
- this.#emitExpiry(stage, claim);
2831
- throw error;
2925
+ await this.#dispose(stage);
2926
+ } catch (failure) {
2927
+ this.#survivors.set(stage, failure);
2928
+ throw failure;
2832
2929
  }
2930
+ throw error;
2833
2931
  } finally {
2834
- release();
2932
+ subscription[Symbol.dispose]();
2835
2933
  }
2836
2934
  }
2837
- #emitExpiry(stage, claim) {
2838
- if (stage === this.#type || stage === this.#lint || stage === this.#runtime) return;
2839
- this.#emitter.emit("expire", claim);
2935
+ async #dispose(stage) {
2936
+ const bound = stage.stage === "lint" ? Math.max(this.#deadline, _src_core.LINT_TEARDOWN) : this.#deadline;
2937
+ try {
2938
+ await this.#bound(() => stage.destroy(), `The ${stage.stage} stage teardown exceeded ${bound} ms`, stage, stage.progress, bound);
2939
+ } catch (error) {
2940
+ if ((0, _src_core.isProbeError)(error) && this.#deadlines.has(error)) return;
2941
+ if (this.#closing !== void 0 || stage.stage === "lint" && (0, _src_core.isProbeError)(error) && (0, _orkestrel_lsp.isLSPError)(error.cause) && error.cause.code === "timeout") throw error;
2942
+ this.#surface(error);
2943
+ }
2944
+ }
2945
+ #refuseArm(error) {
2946
+ if (this.#closing !== void 0) return (0, _src_core.createDestroyedError)("probe");
2947
+ const cause = (0, _orkestrel_pool.isPoolError)(error) ? error.cause : error;
2948
+ const failure = new _src_core.ProbeError(`The probe could not arm: ${describeUnknown(cause)}`, {
2949
+ origin: "instrument",
2950
+ code: "malformed",
2951
+ cause
2952
+ });
2953
+ this.#surface(failure);
2954
+ return failure;
2840
2955
  }
2841
2956
  #expiry(timeout, message, stage, progress) {
2842
2957
  return new Promise((_resolve, reject) => {
@@ -2846,7 +2961,7 @@ var Probe = class {
2846
2961
  code: "deadline",
2847
2962
  context: {
2848
2963
  stage: stage.stage,
2849
- deadline: this.#deadline
2964
+ deadline: timeout.ms
2850
2965
  }
2851
2966
  });
2852
2967
  this.#deadlines.add(error);
@@ -2855,26 +2970,23 @@ var Probe = class {
2855
2970
  });
2856
2971
  }
2857
2972
  async #destroy() {
2973
+ this.#abort.abort();
2974
+ const barriers = Promise.allSettled([
2975
+ this.#type.destroy(),
2976
+ this.#lint.destroy(),
2977
+ this.#runtime.destroy()
2978
+ ]);
2858
2979
  try {
2859
- try {
2860
- await this.#arming;
2861
- } catch {}
2862
- await Promise.all([
2863
- this.#destroyStage(this.#type),
2864
- this.#destroyStage(this.#lint),
2865
- this.#destroyStage(this.#runtime)
2866
- ]);
2980
+ await this.#starting?.catch(() => {});
2981
+ await this.#arming?.catch(() => {});
2982
+ const results = await barriers;
2983
+ const survivors = await Promise.allSettled([...this.#survivors.keys()].map((stage) => this.#dispose(stage)));
2984
+ this.#survivors.clear();
2985
+ for (const result of [...results, ...survivors]) if (result.status === "rejected") throw (0, _orkestrel_pool.isPoolError)(result.reason) ? result.reason.cause : result.reason;
2867
2986
  } finally {
2868
2987
  this.#emitter.destroy();
2869
2988
  }
2870
2989
  }
2871
- async #destroyStage(stage) {
2872
- try {
2873
- await this.#bound(() => stage.destroy(), `The ${stage.stage} stage teardown exceeded ${this.#deadline} ms`, stage, stage.progress);
2874
- } catch (error) {
2875
- if (!(0, _src_core.isProbeError)(error) || !this.#deadlines.has(error)) throw error;
2876
- }
2877
- }
2878
2990
  #version(name) {
2879
2991
  const version = readWorkspaceManifest(this.#workspace, name).contents.version;
2880
2992
  if (!(0, _orkestrel_contract.isString)(version)) throw new _src_core.ProbeError(`${name} publishes no readable version`, {
@@ -2919,12 +3031,13 @@ var Probe = class {
2919
3031
  /**
2920
3032
  * Implements `ProbeServerInterface` over a `PassThrough` stream this server owns, binding the
2921
3033
  * published `prove` tool and the dual-era dispatcher to this process's Model Context Protocol
2922
- * stdio transport.
3034
+ * stdio transport. Starting creates the probe and awaits its lint and runtime onset; type warming
3035
+ * and the boot controls continue behind it.
2923
3036
  *
2924
3037
  * @remarks
2925
3038
  * Construction snapshots the probe options, publishes the `prove` tool, and binds the dual-era
2926
- * dispatcher to the stdio transport. An admitted `prove` call creates the probe and begins
2927
- * workspace arming, so discovery does not depend on the workspace toolchain. Construction is held
3039
+ * dispatcher to the stdio transport.
3040
+ * Discovery remains available while the legacy handshake awaits that onset. Construction is held
2928
3041
  * before it begins, so teardown entered through a construction callback waits for the resulting
2929
3042
  * probe and releases it. A constructor refusal clears that held operation for a later retry.
2930
3043
  *
@@ -2934,7 +3047,8 @@ var Probe = class {
2934
3047
  * serving is still attached and still fires afterwards. The transport reads a stream this server
2935
3048
  * owns rather than this process's standard input, which is what makes that possible: the
2936
3049
  * transport's own listeners land on that stream, and the only listeners this server ever puts on
2937
- * `process.stdin` are the `data`, `close`, and `error` forwarders into it. `destroy` pauses the
3050
+ * `process.stdin` are the `data`, `close`, and `error` forwarders and the input-end teardown.
3051
+ * `destroy` pauses the
2938
3052
  * stream only when this server is what set it flowing and nothing else is reading it, so a process
2939
3053
  * that outlives this server reads its own standard input again and receives its own signals.
2940
3054
  * Teardown removes the signal handlers first: it takes seconds when a boot is in flight, and a
@@ -2943,7 +3057,7 @@ var Probe = class {
2943
3057
  * @example
2944
3058
  * ```ts
2945
3059
  * const server = new ProbeServer({ workspace: process.cwd() })
2946
- * server.start()
3060
+ * await server.start()
2947
3061
  * await server.destroy()
2948
3062
  * ```
2949
3063
  */
@@ -2955,24 +3069,28 @@ var ProbeServer = class {
2955
3069
  #close;
2956
3070
  #error;
2957
3071
  #signal;
3072
+ #end;
3073
+ #starting;
2958
3074
  #server;
2959
3075
  #owns;
2960
3076
  #construction;
2961
3077
  #closing;
2962
3078
  /**
2963
3079
  * Snapshots the probe options this server will use and binds the published schema to this
2964
- * process's stdio transport. An admitted `prove` call creates the probe.
3080
+ * process's stdio transport without warming the workspace.
2965
3081
  *
2966
- * @param options - Workspace, deadline, and initial observation hooks for the deferred probe
3082
+ * @param options - Workspace, deadline, and initial observation hooks for the probe
2967
3083
  */
2968
3084
  constructor(options) {
2969
3085
  const workspace = options?.workspace;
2970
3086
  const deadline = options?.deadline;
3087
+ const warm = options?.warm;
2971
3088
  const on = options?.on;
2972
3089
  const error = options?.error;
2973
3090
  this.#options = Object.freeze({
2974
3091
  workspace: (0, node_path.resolve)(workspace ?? process.cwd()),
2975
3092
  ...deadline === void 0 ? {} : { deadline },
3093
+ ...warm === void 0 ? {} : { warm },
2976
3094
  ...on === void 0 ? {} : { on: Object.freeze({ ...on }) },
2977
3095
  ...error === void 0 ? {} : { error }
2978
3096
  });
@@ -2983,22 +3101,52 @@ var ProbeServer = class {
2983
3101
  this.#close = this.#finish.bind(this);
2984
3102
  this.#error = this.#fail.bind(this);
2985
3103
  this.#signal = this.#release.bind(this);
3104
+ this.#end = this.#release.bind(this);
2986
3105
  }
2987
3106
  start() {
2988
- if (this.#closing !== void 0) throw (0, _src_core.createDestroyedError)("probe server");
2989
- if (this.#owns !== void 0) return;
3107
+ if (this.#closing !== void 0) return Promise.reject((0, _src_core.createDestroyedError)("probe server"));
3108
+ this.#starting ??= this.#setup();
3109
+ this.#starting.catch(() => {});
3110
+ return this.#starting;
3111
+ }
3112
+ destroy() {
3113
+ if (this.#closing !== void 0) return this.#closing;
3114
+ this.#closing = this.#destroy();
3115
+ return this.#closing;
3116
+ }
3117
+ async #setup() {
2990
3118
  this.#owns = process.stdin.readableFlowing !== true;
2991
3119
  this.#transport.start();
2992
3120
  process.stdin.on("data", this.#data);
2993
3121
  process.stdin.on("close", this.#close);
2994
3122
  process.stdin.on("error", this.#error);
3123
+ process.stdin.on("end", this.#end);
2995
3124
  process.on("SIGINT", this.#signal);
2996
3125
  process.on("SIGTERM", this.#signal);
3126
+ try {
3127
+ const probe = await this.#resolveProbe();
3128
+ if (this.#closing !== void 0) return;
3129
+ await probe.start();
3130
+ if (this.#closing !== void 0) return;
3131
+ } catch (error) {
3132
+ if (this.#closing !== void 0) return;
3133
+ throw error;
3134
+ }
2997
3135
  }
2998
- destroy() {
2999
- if (this.#closing !== void 0) return this.#closing;
3000
- this.#closing = this.#destroy();
3001
- return this.#closing;
3136
+ async #handshake(options) {
3137
+ options.signal.throwIfAborted();
3138
+ const aborted = Promise.withResolvers();
3139
+ const listener = (0, node_events.addAbortListener)(options.signal, () => aborted.reject(options.signal.reason));
3140
+ try {
3141
+ await Promise.race([this.#starting, aborted.promise]);
3142
+ if (this.#closing !== void 0) throw (0, _src_core.createDestroyedError)("probe server");
3143
+ } catch (error) {
3144
+ options.signal.throwIfAborted();
3145
+ if ((0, _src_core.isProbeError)(error)) throw createHandshakeError(error);
3146
+ throw error;
3147
+ } finally {
3148
+ listener[Symbol.dispose]();
3149
+ }
3002
3150
  }
3003
3151
  async #destroy() {
3004
3152
  process.removeListener("SIGINT", this.#signal);
@@ -3007,13 +3155,17 @@ var ProbeServer = class {
3007
3155
  process.stdin.removeListener("data", this.#data);
3008
3156
  process.stdin.removeListener("close", this.#close);
3009
3157
  process.stdin.removeListener("error", this.#error);
3158
+ process.stdin.removeListener("end", this.#end);
3010
3159
  if (this.#owns === true && process.stdin.listenerCount("data") === 0) process.stdin.pause();
3011
3160
  this.#owns = void 0;
3012
3161
  this.#stream.destroy();
3013
3162
  const construction = this.#construction;
3014
- if (construction === void 0) return;
3015
- const probe = await construction.catch(() => void 0);
3016
- if (probe !== void 0) await probe.destroy();
3163
+ try {
3164
+ const probe = await construction?.catch(() => void 0);
3165
+ if (probe !== void 0) await probe.destroy();
3166
+ } finally {
3167
+ await this.#starting?.catch(() => {});
3168
+ }
3017
3169
  }
3018
3170
  #receive(chunk) {
3019
3171
  this.#stream.write(chunk);
@@ -3047,7 +3199,8 @@ var ProbeServer = class {
3047
3199
  },
3048
3200
  tools,
3049
3201
  limit: { keys: _src_core.PROBE_KEYS },
3050
- execution: this.#execute.bind(this)
3202
+ execution: this.#execute.bind(this),
3203
+ handshake: this.#handshake.bind(this)
3051
3204
  });
3052
3205
  }
3053
3206
  async #prove(input) {
@@ -3124,6 +3277,7 @@ exports.buildRevisionPath = buildRevisionPath;
3124
3277
  exports.captureListeners = captureListeners;
3125
3278
  exports.collectWorkspaceFiles = collectWorkspaceFiles;
3126
3279
  exports.computeDigest = computeDigest;
3280
+ exports.createHandshakeError = createHandshakeError;
3127
3281
  exports.describeUnknown = describeUnknown;
3128
3282
  exports.escapesRoot = escapesRoot;
3129
3283
  exports.filterUniqueIssues = filterUniqueIssues;