@orkestrel/probe 0.0.25 → 0.0.27

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.
@@ -5,19 +5,20 @@ import { createHash, randomUUID } from "node:crypto";
5
5
  import { basename, dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
6
6
  import { fileURLToPath, pathToFileURL } from "node:url";
7
7
  import { attempt, compileGuard, compileSchema, isArray, isError, isFunction, isNumber, isObject, isRecord, isString, parseJSON, schemaToParameters } from "@orkestrel/contract";
8
- import { CLAIM_SHAPE, LINT_DEADLINE, LINT_TEARDOWN, PROBE_DEADLINE, PROBE_KEYS, PROBE_RESTARTS, PROBE_SPECIFICATIONS, PROBE_WARM, ProbeError, RUNTIME_PLUGIN, TYPE_MIRROR, computeReceipt, createDestroyedError, formatCheck, formatReceipt, formatSpecification, formatVerdict, isClaim, isDraft, isProbeError, isVerdict, matchesSpecification } from "../core/index.js";
8
+ import { CLAIM_SHAPE, LINT_DEADLINE, LINT_TEARDOWN, PROBE_DEADLINE, PROBE_KEYS, PROBE_RESTARTS, PROBE_SPECIFICATIONS, PROBE_WARM, ProbeError, RUNTIME_PLUGIN, TYPE_MIRROR, computeReceipt, createDestroyedError, formatReceipt, formatSpecification, formatStageResult, formatVerdict, isClaim, isDraft, isProbeError, isVerdict, matchesSpecification } from "../core/index.js";
9
+ import { inspectProcess, killTree } from "@orkestrel/process/server";
9
10
  import { Emitter } from "@orkestrel/emitter";
10
11
  import { createPool, isPoolError } from "@orkestrel/pool";
11
12
  import { createLSPClient, isLSPError } from "@orkestrel/lsp";
12
13
  import { addAbortListener } from "node:events";
13
14
  import { createTimeout } from "@orkestrel/timeout";
14
- import { createStdioClientTransport } from "@orkestrel/lsp/server";
15
+ import { createLSPStdioTransport } from "@orkestrel/lsp/server";
15
16
  import { PassThrough } from "node:stream";
16
17
  import { AsyncLocalStorage } from "node:async_hooks";
17
- import { spawn, spawnSync } from "node:child_process";
18
+ import { spawn } from "node:child_process";
18
19
  import { createStdioServer } from "@orkestrel/mcp/server";
19
20
  import { createTool, createToolManager } from "@orkestrel/tool";
20
- //#region src/server/errors.ts
21
+ // src/server/errors.ts
21
22
  /**
22
23
  * Creates the protocol failure returned when the MCP handshake cannot arm the probe.
23
24
  *
@@ -35,8 +36,7 @@ function createHandshakeError(error) {
35
36
  code: error.code
36
37
  });
37
38
  }
38
- //#endregion
39
- //#region src/server/helpers.ts
39
+ // src/server/helpers.ts
40
40
  /**
41
41
  * Rewrites one path into the forward-slash spelling this package compares and reports paths in.
42
42
  *
@@ -151,13 +151,13 @@ function resolveWorkspaceFile(workspace, target, mutate = false) {
151
151
  if (!mutate) return file;
152
152
  try {
153
153
  const canonical = realpathSync(root);
154
- let descendant = root;
155
- for (const segment of path.split(sep)) {
156
- descendant = resolve(descendant, segment);
154
+ path.split(sep).reduce((parent, segment) => {
155
+ if (parent === void 0) return void 0;
156
+ const descendant = resolve(parent, segment);
157
157
  const outcome = attempt(() => lstatSync(descendant));
158
158
  if (!outcome.success) {
159
159
  const code = readFaultCode(outcome.error);
160
- if (code === "ENOENT" || code === "ENOTDIR") break;
160
+ if (code === "ENOENT" || code === "ENOTDIR") return void 0;
161
161
  throw outcome.error;
162
162
  }
163
163
  if (outcome.value.isSymbolicLink()) throw new ProbeError(`Path crosses a symbolic link: ${target}`, {
@@ -165,12 +165,14 @@ function resolveWorkspaceFile(workspace, target, mutate = false) {
165
165
  code: "refused",
166
166
  context: { path: target }
167
167
  });
168
- if (escapesRoot(canonical, realpathSync(descendant))) throw new ProbeError(`Path escapes the workspace: ${target}`, {
168
+ const resolved = realpathSync(descendant);
169
+ if (escapesRoot(canonical, resolved)) throw new ProbeError(`Path escapes the workspace: ${target}`, {
169
170
  origin: "claimant",
170
171
  code: "refused",
171
172
  context: { path: target }
172
173
  });
173
- }
174
+ return descendant;
175
+ }, root);
174
176
  } catch (error) {
175
177
  if (isProbeError(error)) throw error;
176
178
  const code = readFaultCode(error);
@@ -335,14 +337,11 @@ function relativeWorkspaceMessage(workspace, message) {
335
337
  const root = resolve(workspace);
336
338
  const forward = normalizePath(root);
337
339
  const url = pathToFileURL(forward).href;
338
- const spellings = /* @__PURE__ */ new Set([
340
+ return [.../* @__PURE__ */ new Set([
339
341
  url.endsWith("/") ? url : `${url}/`,
340
342
  forward.endsWith("/") ? forward : `${forward}/`,
341
343
  root.endsWith(sep) ? root : `${root}${sep}`
342
- ]);
343
- let text = message;
344
- for (const spelling of spellings) text = text.replaceAll(spelling, (match, offset, source) => /[^\s"'`()[\]{}<>,;:=|]/u.test(source.charAt(offset - 1)) ? match : "");
345
- return text;
344
+ ])].reduce((text, spelling) => text.replaceAll(spelling, (match, offset, source) => /[^\s"'`()[\]{}<>,;:=|]/u.test(source.charAt(offset - 1)) ? match : ""), message);
346
345
  }
347
346
  /**
348
347
  * Scans the plain-text output of one compiler run into the diagnostics it reported.
@@ -371,37 +370,36 @@ function relativeWorkspaceMessage(workspace, message) {
371
370
  * ```
372
371
  */
373
372
  function scanDiagnostics(text) {
374
- const diagnostics = [];
375
- for (const line of text.split(/\r\n|\n/)) {
373
+ const lines = text.split(/\r\n|\n/);
374
+ const headers = lines.map((line, index) => {
376
375
  const located = /^(.+)\((\d+),(\d+)\): (?:error|warning) TS\d+: (.*)$/u.exec(line);
377
376
  if (located !== null) {
378
377
  const point = {
379
378
  line: Number.parseInt(located[2] ?? "1", 10) - 1,
380
379
  character: Number.parseInt(located[3] ?? "1", 10) - 1
381
380
  };
382
- diagnostics.push({
383
- path: normalizePath(located[1] ?? ""),
384
- range: {
385
- start: point,
386
- end: point
387
- },
388
- message: located[4] ?? ""
389
- });
390
- continue;
381
+ return {
382
+ index,
383
+ diagnostic: {
384
+ path: normalizePath(located[1] ?? ""),
385
+ range: {
386
+ start: point,
387
+ end: point
388
+ },
389
+ message: located[4] ?? ""
390
+ }
391
+ };
391
392
  }
392
393
  const bare = /^(?:error|warning) TS\d+: (.*)$/u.exec(line);
393
- if (bare !== null) {
394
- diagnostics.push({ message: bare[1] ?? "" });
395
- continue;
396
- }
397
- const previous = diagnostics.at(-1);
398
- if (previous === void 0 || !/^\s+\S/u.test(line)) continue;
399
- diagnostics[diagnostics.length - 1] = {
400
- ...previous,
401
- message: `${previous.message}\n${line}`
394
+ return bare === null ? void 0 : {
395
+ index,
396
+ diagnostic: { message: bare[1] ?? "" }
402
397
  };
403
- }
404
- return diagnostics;
398
+ }).filter((header) => header !== void 0);
399
+ return headers.map((header, index) => ({
400
+ ...header.diagnostic,
401
+ message: [header.diagnostic.message, ...lines.slice(header.index + 1, headers[index + 1]?.index).filter((line) => /^\s+\S/u.test(line))].join("\n")
402
+ }));
405
403
  }
406
404
  /**
407
405
  * Resolves one installed module from the target workspace.
@@ -476,10 +474,9 @@ function loadWorkspaceVitest(workspace) {
476
474
  * ```
477
475
  */
478
476
  function readWorkspaceManifest(workspace, name) {
479
- let path;
480
- try {
481
- path = resolveWorkspaceModule(workspace, `${name}/package.json`);
482
- } catch (error) {
477
+ const resolved = attempt(() => resolveWorkspaceModule(workspace, `${name}/package.json`));
478
+ if (!resolved.success) {
479
+ const error = resolved.error;
483
480
  if (!isProbeError(error)) throw error;
484
481
  throw new ProbeError(`${name} does not publish a readable manifest`, {
485
482
  origin: "workspace",
@@ -488,6 +485,7 @@ function readWorkspaceManifest(workspace, name) {
488
485
  cause: error.cause
489
486
  });
490
487
  }
488
+ const path = resolved.value;
491
489
  const reading = attempt(() => readFileSync(path, "utf8"));
492
490
  if (!reading.success) {
493
491
  const error = reading.error;
@@ -503,17 +501,17 @@ function readWorkspaceManifest(workspace, name) {
503
501
  cause: error
504
502
  });
505
503
  }
506
- const parsing = attempt(() => JSON.parse(reading.value));
507
- if (!parsing.success) throw new ProbeError(`${name} does not publish a readable manifest`, {
504
+ const parsed = attempt(() => JSON.parse(reading.value));
505
+ if (!parsed.success) throw new ProbeError(`${name} does not publish a readable manifest`, {
508
506
  origin: "workspace",
509
507
  code: "malformed",
510
508
  context: {
511
509
  name,
512
510
  path
513
511
  },
514
- cause: parsing.error
512
+ cause: parsed.error
515
513
  });
516
- const contents = parsing.value;
514
+ const contents = parsed.value;
517
515
  if (!isRecord(contents)) throw new ProbeError(`${name} does not publish a readable manifest`, {
518
516
  origin: "workspace",
519
517
  code: "malformed",
@@ -684,20 +682,13 @@ function buildRevisionPath(workspace, path, revision) {
684
682
  * ```
685
683
  */
686
684
  function filterUniqueIssues(issues) {
687
- const seen = /* @__PURE__ */ new Set();
688
- const distinct = [];
689
- for (const issue of issues) {
690
- const key = JSON.stringify([
691
- issue.origin,
692
- issue.path,
693
- issue.message,
694
- issue.range
695
- ]);
696
- if (seen.has(key)) continue;
697
- seen.add(key);
698
- distinct.push(issue);
699
- }
700
- return distinct;
685
+ const groups = Map.groupBy(issues, (issue) => JSON.stringify([
686
+ issue.origin,
687
+ issue.path,
688
+ issue.message,
689
+ issue.range
690
+ ]));
691
+ return Iterator.from(groups.values()).flatMap((group) => group.slice(0, 1)).toArray();
701
692
  }
702
693
  /**
703
694
  * Reports whether a path is a workspace module Vitest can cache.
@@ -715,13 +706,11 @@ function matchesWorkspaceModule(path) {
715
706
  return /\.(?:[cm]?[jt]sx?|vue|json)$/.test(path);
716
707
  }
717
708
  /**
718
- * Reports whether the host that wrote one file is still running.
709
+ * Reports whether a file's owning host is running or cannot be judged.
719
710
  *
720
711
  * @remarks
721
- * Signal 0 delivers nothing and reports reachability alone. A process this one may not signal
722
- * reports `EPERM` and reads as running, and a non-positive identity names a process group rather
723
- * than a process, so both answer true: the safe direction is to keep a file this package cannot
724
- * account for.
712
+ * Keeps a file when its identity is invalid or `inspectProcess` reports existence, denied
713
+ * permission, or an indeterminate host failure. Only a confirmed absent process answers false.
725
714
  *
726
715
  * @param id - The process id a file's own revision names
727
716
  * @returns True if that process is running or cannot be judged; false otherwise
@@ -734,12 +723,7 @@ function matchesWorkspaceModule(path) {
734
723
  */
735
724
  function matchesLiveProcess(id) {
736
725
  if (!isNumber(id) || !Number.isSafeInteger(id) || id <= 0) return true;
737
- try {
738
- process.kill(id, 0);
739
- return true;
740
- } catch (error) {
741
- return readFaultCode(error) === "EPERM";
742
- }
726
+ return inspectProcess(id) !== false;
743
727
  }
744
728
  /**
745
729
  * Collects every regular file a target workspace holds, skipping the trees no inspection reads.
@@ -767,10 +751,8 @@ function collectWorkspaceFiles(workspace) {
767
751
  while (directories.length > 0) {
768
752
  const directory = directories.pop();
769
753
  if (directory === void 0) break;
770
- let entries = [];
771
- try {
772
- entries = readdirSync(directory, { withFileTypes: true });
773
- } catch {}
754
+ const reading = attempt(() => readdirSync(directory, { withFileTypes: true }));
755
+ const entries = reading.success ? reading.value : [];
774
756
  for (const entry of entries) {
775
757
  if (entry.name === ".git" || entry.name === "dist" || entry.name === "node_modules") continue;
776
758
  const path = join(directory, entry.name);
@@ -859,17 +841,10 @@ async function guardStage(stage, operation) {
859
841
  */
860
842
  function findRefusedPaths(value) {
861
843
  if (!compileGuard(CLAIM_SHAPE)(value)) return [];
862
- const members = [];
863
- for (const phase of ["case", "control"]) {
844
+ return ["case", "control"].flatMap((phase) => {
864
845
  const subject = value[phase];
865
- const drafts = /* @__PURE__ */ new Map([[`${phase}.test`, subject.test]]);
866
- for (const [index, file] of subject.files.entries()) drafts.set(`${phase}.files.${index}`, file);
867
- for (const [member, draft] of drafts) {
868
- if (isDraft(draft)) continue;
869
- members.push(`${member}.path`);
870
- }
871
- }
872
- return members;
846
+ return [[`${phase}.test`, subject.test], ...Iterator.from(subject.files.entries()).map(([index, file]) => [`${phase}.files.${index}`, file])].flatMap(([member, draft]) => isDraft(draft) ? [] : [`${member}.path`]);
847
+ });
873
848
  }
874
849
  /**
875
850
  * Rewrites every workspace-contained absolute path in a value to its workspace-relative form and
@@ -881,7 +856,8 @@ function findRefusedPaths(value) {
881
856
  * its relative form keeps one commit checked out at two roots reading to one digest. A path that
882
857
  * escapes the workspace is left as it stands, because rewriting it would name a file outside the
883
858
  * tree by a relative spelling. Key order is the compiler's own and it differs between the supported
884
- * majors, so sorting removes a difference that means nothing.
859
+ * majors, so sorting removes a difference that means nothing. A `__proto__` key is dropped, as
860
+ * the earlier assignment-built record dropped it, so a digest stays the same across releases.
885
861
  *
886
862
  * @param workspace - The target workspace root
887
863
  * @param value - The value to rewrite
@@ -903,11 +879,7 @@ function normalizeValue(workspace, value) {
903
879
  return normalizePath(path);
904
880
  }
905
881
  if (isArray(value)) return value.map((entry) => normalizeValue(workspace, entry));
906
- if (isRecord(value)) {
907
- const record = {};
908
- for (const key of Object.keys(value).sort()) record[key] = normalizeValue(workspace, value[key]);
909
- return record;
910
- }
882
+ if (isRecord(value)) return Object.fromEntries(Object.keys(value).sort().filter((key) => key !== "__proto__").map((key) => [key, normalizeValue(workspace, value[key])]));
911
883
  return value;
912
884
  }
913
885
  /**
@@ -924,10 +896,10 @@ function normalizeValue(workspace, value) {
924
896
  *
925
897
  * @example
926
898
  * ```ts
927
- * computeDigest('/srv/checkout', { strict: true }).length // 32
899
+ * computeWorkspaceDigest('/srv/checkout', { strict: true }).length // 32
928
900
  * ```
929
901
  */
930
- function computeDigest(workspace, value) {
902
+ function computeWorkspaceDigest(workspace, value) {
931
903
  return createHash("sha256").update(JSON.stringify(normalizeValue(workspace, value))).digest("hex").slice(0, 32);
932
904
  }
933
905
  /**
@@ -944,9 +916,7 @@ function computeDigest(workspace, value) {
944
916
  * ```
945
917
  */
946
918
  function captureListeners(emitter, events) {
947
- const capture = /* @__PURE__ */ new Map();
948
- for (const event of events) capture.set(event, emitter.listeners(event));
949
- return capture;
919
+ return new Map(Iterator.from(events).map((event) => [event, emitter.listeners(event)]));
950
920
  }
951
921
  /**
952
922
  * Removes every listener one emitter gained for the captured events since its capture.
@@ -981,8 +951,7 @@ function releaseListeners(emitter, capture) {
981
951
  emitter.removeListener(event, listener);
982
952
  }
983
953
  }
984
- //#endregion
985
- //#region src/server/parsers.ts
954
+ // src/server/parsers.ts
986
955
  /**
987
956
  * Parses the configuration one compiler run printed for a TypeScript project.
988
957
  *
@@ -1037,8 +1006,7 @@ function parseRevisionOwner(revision) {
1037
1006
  const owner = /^(\d+)-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/u.exec(revision)?.[1];
1038
1007
  return owner === void 0 ? void 0 : Number.parseInt(owner, 10);
1039
1008
  }
1040
- //#endregion
1041
- //#region src/server/Overlay.ts
1009
+ // src/server/Overlay.ts
1042
1010
  /**
1043
1011
  * Implements `OverlayInterface` over a private map from normalized absolute path to candidate text,
1044
1012
  * minting at construction the `revision` a resident tool caches its answers against.
@@ -1124,21 +1092,19 @@ var Overlay = class {
1124
1092
  this.#candidates.clear();
1125
1093
  }
1126
1094
  };
1127
- //#endregion
1128
- //#region package.json
1129
- var version = "0.0.25";
1095
+ // package.json
1096
+ var version = "0.0.27";
1130
1097
  var peerDependencies = {
1131
1098
  "typescript": "^6.0.3 || ^7.0.2",
1132
1099
  "vitest": "^4.1.12 || ^5.0.5"
1133
1100
  };
1134
- //#endregion
1135
- //#region src/server/stages/LintStage.ts
1101
+ // src/server/stages/LintStage.ts
1136
1102
  /**
1137
1103
  * Inspects virtual documents through one resident Oxlint language server.
1138
1104
  *
1139
1105
  * @remarks
1140
1106
  * Starting launches the target workspace's Oxlint binary with its Language Server Protocol mode
1141
- * and hands the conversation to `@orkestrel/lsp`: `createStdioClientTransport` owns the child
1107
+ * and hands the conversation to `@orkestrel/lsp`: `createLSPStdioTransport` owns the child
1142
1108
  * process and its bytes, and `createLSPClient` owns the framing, the request correlation, the
1143
1109
  * capabilities this stage advertises, and the choice between pulled and pushed diagnostics. This
1144
1110
  * stage owns the workspace, the candidate identity, and the projection from a diagnostic to an
@@ -1166,7 +1132,7 @@ var peerDependencies = {
1166
1132
  * @example
1167
1133
  * ```ts
1168
1134
  * const stage = new LintStage('/srv/checkout')
1169
- * const check = await stage.inspect(subject, { signal: AbortSignal.timeout(30_000) })
1135
+ * const result = await stage.inspect(subject, { signal: AbortSignal.timeout(30_000) })
1170
1136
  * await stage.destroy()
1171
1137
  * ```
1172
1138
  */
@@ -1239,7 +1205,7 @@ var LintStage = class {
1239
1205
  }
1240
1206
  async #warm() {
1241
1207
  const binary = resolveWorkspaceBinary(this.#workspace, "oxlint");
1242
- const transport = createStdioClientTransport({
1208
+ const transport = createLSPStdioTransport({
1243
1209
  server: {
1244
1210
  command: [
1245
1211
  process.execPath,
@@ -1345,8 +1311,7 @@ var LintStage = class {
1345
1311
  }));
1346
1312
  }
1347
1313
  };
1348
- //#endregion
1349
- //#region src/server/stages/RuntimeStage.ts
1314
+ // src/server/stages/RuntimeStage.ts
1350
1315
  /**
1351
1316
  * Inspects tests through one resident Vitest service from the target workspace.
1352
1317
  *
@@ -1408,7 +1373,7 @@ var LintStage = class {
1408
1373
  * @example
1409
1374
  * ```ts
1410
1375
  * const stage = new RuntimeStage('/srv/checkout')
1411
- * const check = await stage.inspect(subject)
1376
+ * const result = await stage.inspect(subject)
1412
1377
  * await stage.destroy()
1413
1378
  * ```
1414
1379
  */
@@ -1509,14 +1474,13 @@ var RuntimeStage = class {
1509
1474
  const file = sep === "\\" ? normalizePath(specification) : specification;
1510
1475
  this.#specifications += 1;
1511
1476
  this.#revisions.add(file);
1512
- let issues = [];
1513
- let cleanup = [];
1477
+ const issues = [];
1514
1478
  const task = project.createSpecification(file, void 0, "threads");
1515
1479
  this.#progress += 1;
1516
1480
  try {
1517
1481
  try {
1518
1482
  const result = await vitest.runTestSpecifications([task], false);
1519
- issues = [...this.#issues(result, file, subject.test.path), ...this.#misses(project, file)];
1483
+ issues.push(...this.#issues(result, file, subject.test.path), ...this.#misses(project, file));
1520
1484
  } catch (error) {
1521
1485
  if (isProbeError(error)) throw error;
1522
1486
  throw new ProbeError(`The runtime stage could not run the generated specification (${describeUnknown(error)})`, {
@@ -1533,22 +1497,22 @@ var RuntimeStage = class {
1533
1497
  this.#progress -= 1;
1534
1498
  process.exitCode = exitCode;
1535
1499
  this.#revisions.delete(file);
1536
- cleanup = await this.#evict(vitest, file);
1500
+ issues.push(...await this.#evict(vitest, file));
1537
1501
  try {
1538
1502
  const contained = resolveWorkspaceFile(this.#workspace, relative(this.#workspace, file), true);
1539
1503
  if (existsSync(contained)) unlinkSync(contained);
1540
1504
  } catch (error) {
1541
- cleanup = [...cleanup, {
1505
+ issues.push({
1542
1506
  origin: isProbeError(error) && error.origin === "workspace" ? "workspace" : "instrument",
1543
1507
  path: relativeWorkspaceFile(this.#workspace, file),
1544
1508
  message: `The runtime stage could not delete the generated specification (${describeUnknown(error)})`
1545
- }];
1509
+ });
1546
1510
  }
1547
1511
  }
1548
1512
  return {
1549
1513
  stage: this.stage,
1550
1514
  elapsed: Math.round(performance.now() - started),
1551
- issues: [...issues, ...cleanup]
1515
+ issues
1552
1516
  };
1553
1517
  } finally {
1554
1518
  this.#loads.clear();
@@ -1675,47 +1639,52 @@ var RuntimeStage = class {
1675
1639
  return text;
1676
1640
  }
1677
1641
  #specification(test) {
1678
- let creating = false;
1679
- let generated;
1680
- const outcome = attempt(() => {
1642
+ const location = attempt(() => {
1681
1643
  const revision = `${process.pid}-${randomUUID()}`;
1682
- const file = buildRevisionPath(this.#workspace, test.path, revision);
1683
- generated = file;
1684
- const target = relative(this.#workspace, file);
1685
- resolveWorkspaceFile(this.#workspace, target, true);
1686
- creating = true;
1687
- mkdirSync(dirname(file), { recursive: true });
1688
- creating = false;
1644
+ return {
1645
+ revision,
1646
+ file: buildRevisionPath(this.#workspace, test.path, revision)
1647
+ };
1648
+ });
1649
+ if (!location.success) return this.#specificationFault(test, location.error);
1650
+ const { revision, file } = location.value;
1651
+ const target = relative(this.#workspace, file);
1652
+ const containment = attempt(() => resolveWorkspaceFile(this.#workspace, target, true));
1653
+ if (!containment.success) return this.#specificationFault(test, containment.error, file);
1654
+ const directory = attempt(() => mkdirSync(dirname(file), { recursive: true }));
1655
+ if (!directory.success) return this.#specificationFault(test, directory.error, file, true);
1656
+ const written = attempt(() => {
1689
1657
  resolveWorkspaceFile(this.#workspace, target, true);
1690
1658
  writeFileSync(file, formatSpecification(test.text, revision), {
1691
1659
  encoding: "utf8",
1692
1660
  flag: "wx"
1693
1661
  });
1694
- return file;
1695
1662
  });
1696
- if (outcome.success) return outcome.value;
1697
- if (isProbeError(outcome.error) && outcome.error.origin === "claimant") throw new ProbeError(outcome.error.message, {
1698
- origin: outcome.error.origin,
1699
- code: outcome.error.code,
1663
+ return written.success ? file : this.#specificationFault(test, written.error, file);
1664
+ }
1665
+ #specificationFault(test, error, generated, directory = false) {
1666
+ if (isProbeError(error) && error.origin === "claimant") throw new ProbeError(error.message, {
1667
+ origin: error.origin,
1668
+ code: error.code,
1700
1669
  context: {
1701
- ...outcome.error.context,
1670
+ ...error.context,
1702
1671
  path: test.path
1703
1672
  },
1704
- cause: outcome.error.cause
1673
+ cause: error.cause
1705
1674
  });
1706
- if (!creating && generated !== void 0 && isRefusedName(generated, outcome.error)) throw new ProbeError(`The runtime stage cannot write a specification beside a test path the host refuses (${describeUnknown(outcome.error)})`, {
1675
+ if (!directory && generated !== void 0 && isRefusedName(generated, error)) throw new ProbeError(`The runtime stage cannot write a specification beside a test path the host refuses (${describeUnknown(error)})`, {
1707
1676
  origin: "claimant",
1708
1677
  code: "refused",
1709
1678
  context: {
1710
1679
  stage: this.stage,
1711
1680
  path: test.path
1712
1681
  },
1713
- cause: outcome.error
1682
+ cause: error
1714
1683
  });
1715
1684
  return {
1716
- origin: isProbeError(outcome.error) && outcome.error.origin === "workspace" || creating ? "workspace" : "instrument",
1685
+ origin: isProbeError(error) && error.origin === "workspace" || directory ? "workspace" : "instrument",
1717
1686
  path: test.path,
1718
- message: `The runtime stage could not write the generated specification (${describeUnknown(outcome.error)})`
1687
+ message: `The runtime stage could not write the generated specification (${describeUnknown(error)})`
1719
1688
  };
1720
1689
  }
1721
1690
  #project(vitest, path) {
@@ -1746,44 +1715,35 @@ var RuntimeStage = class {
1746
1715
  }
1747
1716
  #misses(project, specification) {
1748
1717
  const file = normalizePath(specification);
1749
- const issues = [];
1750
- for (const path of this.#overlay.paths) {
1751
- if (this.#reads.has(path)) continue;
1752
- let served = false;
1753
- let loaded = false;
1754
- for (const environment of Object.values(project.vite.environments)) {
1718
+ return this.#overlay.paths.flatMap((path) => {
1719
+ if (this.#reads.has(path)) return [];
1720
+ const served = Object.values(project.vite.environments).flatMap((environment) => {
1755
1721
  const modules = environment.moduleGraph.getModulesByFile(path);
1756
- if (modules === void 0) continue;
1757
- for (const module of modules) {
1722
+ if (modules === void 0) return [];
1723
+ return Iterator.from(modules).flatMap((module) => {
1758
1724
  const pending = [...module.importers];
1759
1725
  const seen = new Set(pending);
1760
- let reaches = false;
1761
1726
  while (pending.length > 0) {
1762
1727
  const importer = pending.pop();
1763
1728
  if (importer === void 0) break;
1764
- if (importer.file !== null && normalizePath(importer.file) === file) {
1765
- reaches = true;
1766
- break;
1767
- }
1729
+ if (importer.file !== null && normalizePath(importer.file) === file) return [module.id !== null && this.#loads.has(normalizePath(module.id))];
1768
1730
  for (const parent of importer.importers) {
1769
1731
  if (seen.has(parent)) continue;
1770
1732
  seen.add(parent);
1771
1733
  pending.push(parent);
1772
1734
  }
1773
1735
  }
1774
- if (!reaches) continue;
1775
- served = true;
1776
- if (module.id !== null && this.#loads.has(normalizePath(module.id))) loaded = true;
1777
- }
1778
- }
1779
- if (!served) continue;
1780
- issues.push({
1736
+ return [];
1737
+ }).toArray();
1738
+ });
1739
+ if (served.length === 0) return [];
1740
+ const loaded = served.some((value) => value);
1741
+ return [{
1781
1742
  origin: loaded ? "instrument" : "workspace",
1782
1743
  path: relativeWorkspaceFile(this.#workspace, path),
1783
1744
  message: loaded ? "The runtime overlay did not resolve this module" : "The workspace configuration served this module before the runtime overlay"
1784
- });
1785
- }
1786
- return issues;
1745
+ }];
1746
+ });
1787
1747
  }
1788
1748
  async #evict(vitest, file) {
1789
1749
  try {
@@ -2025,8 +1985,7 @@ var RuntimeStage = class {
2025
1985
  };
2026
1986
  }
2027
1987
  };
2028
- //#endregion
2029
- //#region src/server/stages/TypeStage.ts
1988
+ // src/server/stages/TypeStage.ts
2030
1989
  /**
2031
1990
  * Inspects TypeScript source by running the target workspace's own compiler over a mirror of it.
2032
1991
  *
@@ -2060,7 +2019,7 @@ var RuntimeStage = class {
2060
2019
  * @example
2061
2020
  * ```ts
2062
2021
  * const stage = new TypeStage('/srv/checkout')
2063
- * const check = await stage.inspect(subject, 'configs/src/tsconfig.core.json')
2022
+ * const result = await stage.inspect(subject, 'configs/src/tsconfig.core.json')
2064
2023
  * await stage.destroy()
2065
2024
  * ```
2066
2025
  */
@@ -2135,6 +2094,14 @@ var TypeStage = class {
2135
2094
  resolve(project) {
2136
2095
  return guardStage(this.stage, this.#resolve(project));
2137
2096
  }
2097
+ /**
2098
+ * Tears down the compiler processes and releases the mirror without awaiting inspections.
2099
+ *
2100
+ * @remarks
2101
+ * On Windows, awaits each tree termination attempt before removing the mirror. Each attempt
2102
+ * uses `PROBE_DEADLINE`, the existing default inspection budget, as its utility timeout.
2103
+ * On POSIX, sends `SIGTERM` to each compiler. Warming settles before the mirror is removed.
2104
+ */
2138
2105
  destroy() {
2139
2106
  if (this.#closing !== void 0) return this.#closing;
2140
2107
  this.#closing = guardStage(this.stage, this.#destroy());
@@ -2191,13 +2158,13 @@ var TypeStage = class {
2191
2158
  }
2192
2159
  return {
2193
2160
  path: contained,
2194
- digest: computeDigest(this.#workspace, config.compilerOptions)
2161
+ digest: computeWorkspaceDigest(this.#workspace, config.compilerOptions)
2195
2162
  };
2196
2163
  }
2197
2164
  async #destroy() {
2198
2165
  const children = [...this.#children];
2199
2166
  this.#children.clear();
2200
- for (const child of children) this.#terminate(child);
2167
+ await Promise.all(children.map((child) => this.#terminate(child)));
2201
2168
  await this.#warming?.catch(() => void 0);
2202
2169
  try {
2203
2170
  rmSync(this.#mirror, {
@@ -2221,18 +2188,11 @@ var TypeStage = class {
2221
2188
  await Promise.all(this.#projects().map((project) => this.#check(project, [])));
2222
2189
  }
2223
2190
  #projects() {
2224
- const projects = ["tsconfig.json"];
2225
- for (const axis of ["src", "app"]) {
2191
+ return ["tsconfig.json", ...["src", "app"].flatMap((axis) => {
2226
2192
  const directory = resolveWorkspaceFile(this.#workspace, `configs/${axis}`);
2227
- let names;
2228
- try {
2229
- names = readdirSync(directory);
2230
- } catch {
2231
- continue;
2232
- }
2233
- for (const name of names.filter((entry) => /^tsconfig\.[^.]+\.json$/.test(entry)).sort()) projects.push(`configs/${axis}/${name}`);
2234
- }
2235
- return projects;
2193
+ const reading = attempt(() => readdirSync(directory));
2194
+ return reading.success ? reading.value.filter((entry) => /^tsconfig\.[^.]+\.json$/.test(entry)).sort().map((name) => `configs/${axis}/${name}`) : [];
2195
+ })];
2236
2196
  }
2237
2197
  #refuseDestroyed() {
2238
2198
  if (this.#closing !== void 0) throw createDestroyedError("type stage");
@@ -2247,13 +2207,9 @@ var TypeStage = class {
2247
2207
  }
2248
2208
  #sweep() {
2249
2209
  const root = resolveWorkspaceFile(this.#workspace, TYPE_MIRROR);
2250
- let entries = [];
2251
- try {
2252
- entries = readdirSync(root, { withFileTypes: true });
2253
- } catch {
2254
- return;
2255
- }
2256
- for (const entry of entries) {
2210
+ const reading = attempt(() => readdirSync(root, { withFileTypes: true }));
2211
+ if (!reading.success) return;
2212
+ for (const entry of reading.value) {
2257
2213
  if (!entry.isDirectory()) continue;
2258
2214
  const owner = parseRevisionOwner(entry.name);
2259
2215
  if (owner === void 0 || matchesLiveProcess(owner)) continue;
@@ -2307,12 +2263,7 @@ var TypeStage = class {
2307
2263
  return contained;
2308
2264
  }
2309
2265
  #displace(target) {
2310
- const ancestors = [];
2311
- let directory = dirname(target);
2312
- while (directory !== this.#mirror && directory !== dirname(directory)) {
2313
- ancestors.unshift(directory);
2314
- directory = dirname(directory);
2315
- }
2266
+ const ancestors = relative(this.#mirror, target).split(sep).slice(0, -1).map((_segment, index, segments) => join(this.#mirror, ...segments.slice(0, index + 1)));
2316
2267
  for (const ancestor of [...ancestors, target]) {
2317
2268
  const reading = attempt(() => lstatSync(ancestor));
2318
2269
  if (!reading.success) continue;
@@ -2448,40 +2399,43 @@ var TypeStage = class {
2448
2399
  }
2449
2400
  });
2450
2401
  }
2451
- #spawn(args, cwd) {
2402
+ async #spawn(args, cwd) {
2452
2403
  this.#refuseDestroyed();
2453
2404
  const compiler = this.#compiler;
2454
2405
  if (compiler === void 0) throw createDestroyedError("type stage");
2455
- return new Promise((settle, refuse) => {
2456
- const child = spawn(process.execPath, [compiler, ...args], {
2457
- cwd,
2458
- stdio: [
2459
- "ignore",
2460
- "pipe",
2461
- "pipe"
2462
- ]
2463
- });
2464
- const output = child.stdout;
2465
- const errors = child.stderr;
2466
- if (output === null || errors === null) {
2467
- this.#terminate(child);
2468
- refuse(new ProbeError("The compiler was spawned without its own output streams", {
2469
- origin: "instrument",
2470
- code: "malformed",
2471
- context: { stage: this.stage }
2472
- }));
2473
- return;
2406
+ const child = spawn(process.execPath, [compiler, ...args], {
2407
+ cwd,
2408
+ stdio: [
2409
+ "ignore",
2410
+ "pipe",
2411
+ "pipe"
2412
+ ]
2413
+ });
2414
+ this.#children.add(child);
2415
+ const output = child.stdout;
2416
+ const errors = child.stderr;
2417
+ if (output === null || errors === null) {
2418
+ try {
2419
+ await this.#terminate(child);
2420
+ } finally {
2421
+ this.#children.delete(child);
2474
2422
  }
2475
- this.#children.add(child);
2476
- let stdout = "";
2477
- let stderr = "";
2423
+ throw new ProbeError("The compiler was spawned without its own output streams", {
2424
+ origin: "instrument",
2425
+ code: "malformed",
2426
+ context: { stage: this.stage }
2427
+ });
2428
+ }
2429
+ return new Promise((settle, refuse) => {
2430
+ const stdout = [];
2431
+ const stderr = [];
2478
2432
  output.setEncoding("utf8");
2479
2433
  errors.setEncoding("utf8");
2480
2434
  output.on("data", (chunk) => {
2481
- stdout += chunk;
2435
+ stdout.push(chunk);
2482
2436
  });
2483
2437
  errors.on("data", (chunk) => {
2484
- stderr += chunk;
2438
+ stderr.push(chunk);
2485
2439
  });
2486
2440
  child.on("error", (error) => {
2487
2441
  this.#children.delete(child);
@@ -2499,28 +2453,22 @@ var TypeStage = class {
2499
2453
  this.#children.delete(child);
2500
2454
  settle({
2501
2455
  ...code === null ? {} : { status: code },
2502
- stdout,
2503
- stderr
2456
+ stdout: stdout.join(""),
2457
+ stderr: stderr.join("")
2504
2458
  });
2505
2459
  });
2506
2460
  });
2507
2461
  }
2508
- #terminate(child) {
2462
+ async #terminate(child) {
2509
2463
  const id = child.pid;
2510
2464
  if (process.platform === "win32" && id !== void 0) {
2511
- spawnSync("taskkill", [
2512
- "/pid",
2513
- String(id),
2514
- "/t",
2515
- "/f"
2516
- ], { stdio: "ignore" });
2465
+ await killTree(id, PROBE_DEADLINE);
2517
2466
  return;
2518
2467
  }
2519
2468
  child.kill("SIGTERM");
2520
2469
  }
2521
2470
  };
2522
- //#endregion
2523
- //#region src/server/Probe.ts
2471
+ // src/server/Probe.ts
2524
2472
  /**
2525
2473
  * Answers claims through its type, lint, and runtime stages.
2526
2474
  *
@@ -2661,7 +2609,7 @@ var Probe = class {
2661
2609
  const started = performance.now();
2662
2610
  const id = randomUUID();
2663
2611
  const project = await this.#resolve(claim);
2664
- const digest = computeDigest(this.#workspace, {
2612
+ const digest = computeWorkspaceDigest(this.#workspace, {
2665
2613
  case: claim.case,
2666
2614
  control: claim.control
2667
2615
  });
@@ -2710,11 +2658,12 @@ var Probe = class {
2710
2658
  return this.#starting;
2711
2659
  }
2712
2660
  async #start(filling, warming) {
2713
- let created = false;
2714
- try {
2661
+ const preparation = attempt(() => {
2715
2662
  this.#support();
2716
- if (this.#arming === void 0) created = this.#workbench();
2717
- } catch (error) {
2663
+ return this.#arming === void 0 && this.#workbench();
2664
+ });
2665
+ if (!preparation.success) {
2666
+ const error = preparation.error;
2718
2667
  this.#surface(error);
2719
2668
  throw error;
2720
2669
  }
@@ -2725,7 +2674,7 @@ var Probe = class {
2725
2674
  throw this.#refuseArm(error);
2726
2675
  }
2727
2676
  if (this.#arming === void 0) {
2728
- this.#arming = this.#arm(warming, created);
2677
+ this.#arming = this.#arm(warming, preparation.value);
2729
2678
  this.#arming.catch(() => {
2730
2679
  if (this.#refusal === void 0) this.#arming = void 0;
2731
2680
  });
@@ -2824,21 +2773,21 @@ var Probe = class {
2824
2773
  const beforeType = await this.#inspect(typeClaim.case, typeClaim);
2825
2774
  const beforeRuntime = await this.#inspect(runtimeClaim.case, runtimeClaim);
2826
2775
  const before = [...beforeType, ...beforeRuntime];
2827
- if (before.some((check) => check.issues.length > 0)) throw new ProbeError(`The probe boot control did not begin clean\n${before.map(formatCheck).join("\n")}`, {
2776
+ if (before.some((result) => result.issues.length > 0)) throw new ProbeError(`The probe boot control did not begin clean\n${before.map(formatStageResult).join("\n")}`, {
2828
2777
  origin: "instrument",
2829
2778
  code: "malformed"
2830
2779
  });
2831
2780
  resolveWorkspaceFile(this.#workspace, relative(this.#workspace, typeDependency), true);
2832
2781
  overwriteFile(typeDependency, formatSpecification("export type Signal = number\n", revision));
2833
2782
  const afterType = await this.#inspect(typeClaim.control, typeClaim);
2834
- const type = afterType.find((check) => check.stage === typeClaim.control.stage);
2835
- const tolerant = afterType.find((check) => check.stage === "runtime");
2836
- if (type === void 0 || type.issues.length === 0) throw new ProbeError(`The probe boot type control did not detect a mutated dependency\n${afterType.map(formatCheck).join("\n")}`, {
2783
+ const type = afterType.find((result) => result.stage === typeClaim.control.stage);
2784
+ const tolerant = afterType.find((result) => result.stage === "runtime");
2785
+ if (type === void 0 || type.issues.length === 0) throw new ProbeError(`The probe boot type control did not detect a mutated dependency\n${afterType.map(formatStageResult).join("\n")}`, {
2837
2786
  origin: "instrument",
2838
2787
  code: "malformed",
2839
2788
  context: { stage: typeClaim.control.stage }
2840
2789
  });
2841
- if (tolerant === void 0 || tolerant.issues.length > 0) throw new ProbeError(`The probe boot type control did not remain runtime-clean\n${afterType.map(formatCheck).join("\n")}`, {
2790
+ if (tolerant === void 0 || tolerant.issues.length > 0) throw new ProbeError(`The probe boot type control did not remain runtime-clean\n${afterType.map(formatStageResult).join("\n")}`, {
2842
2791
  origin: "instrument",
2843
2792
  code: "malformed",
2844
2793
  context: { stage: "runtime" }
@@ -2846,8 +2795,8 @@ var Probe = class {
2846
2795
  resolveWorkspaceFile(this.#workspace, relative(this.#workspace, runtimeDependency), true);
2847
2796
  overwriteFile(runtimeDependency, formatSpecification("export const SIGNAL = 'after'\n", revision));
2848
2797
  const afterRuntime = await this.#inspect(runtimeClaim.control, runtimeClaim);
2849
- const runtime = afterRuntime.find((check) => check.stage === runtimeClaim.control.stage);
2850
- if (runtime === void 0 || runtime.issues.length === 0) throw new ProbeError(`The probe boot runtime control did not detect a mutated dependency\n${afterRuntime.map(formatCheck).join("\n")}`, {
2798
+ const runtime = afterRuntime.find((result) => result.stage === runtimeClaim.control.stage);
2799
+ if (runtime === void 0 || runtime.issues.length === 0) throw new ProbeError(`The probe boot runtime control did not detect a mutated dependency\n${afterRuntime.map(formatStageResult).join("\n")}`, {
2851
2800
  origin: "instrument",
2852
2801
  code: "malformed",
2853
2802
  context: { stage: runtimeClaim.control.stage }
@@ -3063,8 +3012,7 @@ var Probe = class {
3063
3012
  });
3064
3013
  }
3065
3014
  };
3066
- //#endregion
3067
- //#region src/server/ProbeServer.ts
3015
+ // src/server/ProbeServer.ts
3068
3016
  /**
3069
3017
  * Implements `ProbeServerInterface` over a `PassThrough` stream this server owns, binding the
3070
3018
  * published `prove` tool and the dual-era dispatcher to this process's Model Context Protocol
@@ -3303,7 +3251,6 @@ var ProbeServer = class {
3303
3251
  ] : [rendered, minimal]).find((arm) => isBoundedJSON(arm, bounds)) ?? minimal;
3304
3252
  }
3305
3253
  };
3306
- //#endregion
3307
- export { LintStage, Overlay, Probe, ProbeServer, RuntimeStage, TypeStage, buildRevisionPath, captureListeners, collectWorkspaceFiles, computeDigest, createHandshakeError, describeUnknown, escapesRoot, filterUniqueIssues, findRefusedPaths, guardStage, inferDocumentLanguage, inferTestProject, inferTypeProject, isRefusedName, loadWorkspaceVitest, matchesLiveProcess, matchesWorkspaceModule, normalizePath, normalizeValue, overwriteFile, parseProjectConfig, parseRevisionOwner, readFaultCode, readWorkspaceManifest, relativeWorkspaceFile, relativeWorkspaceMessage, releaseListeners, resolveWorkspaceBinary, resolveWorkspaceFile, resolveWorkspaceModule, scanDiagnostics };
3254
+ export { LintStage, Overlay, Probe, ProbeServer, RuntimeStage, TypeStage, buildRevisionPath, captureListeners, collectWorkspaceFiles, computeWorkspaceDigest, createHandshakeError, describeUnknown, escapesRoot, filterUniqueIssues, findRefusedPaths, guardStage, inferDocumentLanguage, inferTestProject, inferTypeProject, isRefusedName, loadWorkspaceVitest, matchesLiveProcess, matchesWorkspaceModule, normalizePath, normalizeValue, overwriteFile, parseProjectConfig, parseRevisionOwner, readFaultCode, readWorkspaceManifest, relativeWorkspaceFile, relativeWorkspaceMessage, releaseListeners, resolveWorkspaceBinary, resolveWorkspaceFile, resolveWorkspaceModule, scanDiagnostics };
3308
3255
 
3309
3256
  //# sourceMappingURL=index.js.map