@orkestrel/probe 0.0.12 → 0.0.14

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.
@@ -12,7 +12,7 @@ let _orkestrel_timeout = require("@orkestrel/timeout");
12
12
  let _orkestrel_lsp = require("@orkestrel/lsp");
13
13
  let _orkestrel_lsp_server = require("@orkestrel/lsp/server");
14
14
  let node_stream = require("node:stream");
15
- let node_timers_promises = require("node:timers/promises");
15
+ let node_child_process = require("node:child_process");
16
16
  let _orkestrel_mcp = require("@orkestrel/mcp");
17
17
  let _orkestrel_mcp_server = require("@orkestrel/mcp/server");
18
18
  let _orkestrel_tool = require("@orkestrel/tool");
@@ -325,6 +325,65 @@ function relativeWorkspaceMessage(workspace, message) {
325
325
  return text;
326
326
  }
327
327
  /**
328
+ * Scans the plain-text output of one compiler run into the diagnostics it reported.
329
+ *
330
+ * @remarks
331
+ * Reads the shape `tsc --pretty false` prints: `path(line,column): error TSnnnn: message` for a
332
+ * diagnostic about a file, and `error TSnnnn: message` for one about a project. A line that carries
333
+ * neither prefix and begins with whitespace is an elaboration of the diagnostic above it and joins
334
+ * that message with a newline; any other line is skipped, so a runtime's own failure text yields no
335
+ * diagnostic and a caller reads that as the fault it is. The compiler counts lines and columns from
336
+ * one and columns in UTF-16 code units, so each location is lowered to the zero-based UTF-16 point
337
+ * `Diagnostic.range` fixes and `end` resolves to `start`, which is the extent this output carries.
338
+ * A warning reads as a diagnostic, because the workspace's own gate reports it the same way. The
339
+ * `TS\d+` token that follows `error` or `warning` is what recognizes the line; nothing is captured
340
+ * from it, because `Diagnostic` carries no field for it.
341
+ *
342
+ * @param text - The compiler's output
343
+ * @returns One record per reported diagnostic, in the compiler's own order
344
+ *
345
+ * @example
346
+ * ```ts
347
+ * scanDiagnostics("src/a.ts(1,14): error TS2322: Type 'string' is not assignable to type 'number'.")
348
+ * // [{ path: 'src/a.ts', range: { start: { line: 0, character: 13 }, end: { line: 0, character: 13 } }, message: "Type 'string' is not assignable to type 'number'." }]
349
+ * scanDiagnostics('error TS5058: The specified path does not exist.')
350
+ * // [{ message: 'The specified path does not exist.' }]
351
+ * ```
352
+ */
353
+ function scanDiagnostics(text) {
354
+ const diagnostics = [];
355
+ for (const line of text.split(/\r\n|\n/)) {
356
+ const located = /^(.+)\((\d+),(\d+)\): (?:error|warning) TS\d+: (.*)$/u.exec(line);
357
+ if (located !== null) {
358
+ const point = {
359
+ line: Number.parseInt(located[2] ?? "1", 10) - 1,
360
+ character: Number.parseInt(located[3] ?? "1", 10) - 1
361
+ };
362
+ diagnostics.push({
363
+ path: normalizePath(located[1] ?? ""),
364
+ range: {
365
+ start: point,
366
+ end: point
367
+ },
368
+ message: located[4] ?? ""
369
+ });
370
+ continue;
371
+ }
372
+ const bare = /^(?:error|warning) TS\d+: (.*)$/u.exec(line);
373
+ if (bare !== null) {
374
+ diagnostics.push({ message: bare[1] ?? "" });
375
+ continue;
376
+ }
377
+ const previous = diagnostics.at(-1);
378
+ if (previous === void 0 || !/^\s+\S/u.test(line)) continue;
379
+ diagnostics[diagnostics.length - 1] = {
380
+ ...previous,
381
+ message: `${previous.message}\n${line}`
382
+ };
383
+ }
384
+ return diagnostics;
385
+ }
386
+ /**
328
387
  * Resolves one installed module from the target workspace.
329
388
  *
330
389
  * @param workspace - The target workspace root
@@ -350,18 +409,35 @@ function resolveWorkspaceModule(workspace, specifier) {
350
409
  cause: error
351
410
  });
352
411
  }
353
- function loadWorkspaceModule(workspace, specifier) {
412
+ /**
413
+ * Loads the installed `vitest/node` module from a target workspace.
414
+ *
415
+ * @remarks
416
+ * The only tool this package drives in process. The compiler is a process this package spawns
417
+ * rather than a module it loads, so `typescript` is resolved through {@link resolveWorkspaceBinary}
418
+ * instead.
419
+ *
420
+ * @param workspace - The target workspace root
421
+ * @returns The installed `vitest/node` module
422
+ * @throws When the workspace cannot load `vitest/node`
423
+ *
424
+ * @example
425
+ * ```ts
426
+ * const vitest = loadWorkspaceVitest(process.cwd())
427
+ * typeof vitest.createVitest === 'function' // true
428
+ * ```
429
+ */
430
+ function loadWorkspaceVitest(workspace) {
354
431
  const outcome = (0, _orkestrel_contract.attempt)(() => {
355
- const require$1 = (0, node_module.createRequire)((0, node_path.resolve)(workspace, "package.json"));
356
- return specifier === "typescript" ? require$1("typescript") : require$1("vitest/node");
432
+ return (0, node_module.createRequire)((0, node_path.resolve)(workspace, "package.json"))("vitest/node");
357
433
  });
358
434
  if (outcome.success) return outcome.value;
359
435
  const error = outcome.error;
360
436
  const missing = readFaultCode(error) === "MODULE_NOT_FOUND";
361
- throw new _src_core.ProbeError(`The workspace cannot load ${specifier}`, {
437
+ throw new _src_core.ProbeError("The workspace cannot load vitest/node", {
362
438
  origin: "workspace",
363
439
  code: missing ? "missing" : "malformed",
364
- context: { name: specifier },
440
+ context: { name: "vitest/node" },
365
441
  cause: error
366
442
  });
367
443
  }
@@ -434,20 +510,26 @@ function readWorkspaceManifest(workspace, name) {
434
510
  /**
435
511
  * Resolves a package's portable JavaScript binary from the target workspace.
436
512
  *
513
+ * @remarks
514
+ * A package publishes its binaries under keys of its own choosing, and that key is often not the
515
+ * package name: `typescript` publishes `tsc`. Name the key as `command` wherever the two differ.
516
+ * Default: the package name.
517
+ *
437
518
  * @param workspace - The target workspace root
438
- * @param name - The installed package name and binary key
519
+ * @param name - The installed package name
520
+ * @param command - The `bin` key to read. Default: `name`
439
521
  * @returns The absolute JavaScript entry named by the package's `bin` field
440
- * @throws When the package does not publish a binary under the requested name
522
+ * @throws When the package does not publish a binary under the requested key
441
523
  *
442
524
  * @example
443
525
  * ```ts
444
526
  * relativeWorkspaceFile(process.cwd(), resolveWorkspaceBinary(process.cwd(), 'oxlint'))
445
527
  * // 'node_modules/oxlint/bin/oxlint'
446
- * resolveWorkspaceBinary(process.cwd(), 'typescript')
447
- * // throws: typescript does not publish the typescript binary
528
+ * relativeWorkspaceFile(process.cwd(), resolveWorkspaceBinary(process.cwd(), 'typescript', 'tsc'))
529
+ * // 'node_modules/typescript/bin/tsc'
448
530
  * ```
449
531
  */
450
- function resolveWorkspaceBinary(workspace, name) {
532
+ function resolveWorkspaceBinary(workspace, name, command = name) {
451
533
  const manifest = readWorkspaceManifest(workspace, name);
452
534
  const bin = manifest.contents.bin;
453
535
  if (bin === void 0) throw new _src_core.ProbeError(`${name} does not publish a bin field`, {
@@ -456,13 +538,13 @@ function resolveWorkspaceBinary(workspace, name) {
456
538
  context: { name }
457
539
  });
458
540
  if (typeof bin === "string") return (0, node_path.resolve)(manifest.path, "..", bin);
459
- if (!(0, _orkestrel_contract.isRecord)(bin) || !(name in bin)) throw new _src_core.ProbeError(`${name} does not publish the ${name} binary`, {
541
+ if (!(0, _orkestrel_contract.isRecord)(bin) || !(command in bin)) throw new _src_core.ProbeError(`${name} does not publish the ${command} binary`, {
460
542
  origin: "workspace",
461
543
  code: "missing",
462
544
  context: { name }
463
545
  });
464
- const entry = bin[name];
465
- if (typeof entry !== "string") throw new _src_core.ProbeError(`${name} publishes an invalid ${name} binary`, {
546
+ const entry = bin[command];
547
+ if (typeof entry !== "string") throw new _src_core.ProbeError(`${name} publishes an invalid ${command} binary`, {
466
548
  origin: "workspace",
467
549
  code: "malformed",
468
550
  context: {
@@ -563,6 +645,39 @@ function buildRevisionPath(workspace, path, revision) {
563
645
  return `${extension === "" ? file : file.slice(0, -extension.length)}.probe-${revision}${extension}`;
564
646
  }
565
647
  /**
648
+ * Filters one issue list to the distinct issues it carries, in the order they arrived.
649
+ *
650
+ * @remarks
651
+ * A stage that reads one candidate through more than one tool run reports that candidate's
652
+ * diagnostic once per run. Two issues are the same issue when their party, path, message, and range
653
+ * all agree, because those are every value an issue carries.
654
+ *
655
+ * @param issues - The issues to filter
656
+ * @returns The distinct issues, in the order the list carried them
657
+ *
658
+ * @example
659
+ * ```ts
660
+ * const issue: Issue = { origin: 'claimant', path: 'src/core/greeting.ts', message: 'not assignable' }
661
+ * filterUniqueIssues([issue, { ...issue }]).length // 1
662
+ * ```
663
+ */
664
+ function filterUniqueIssues(issues) {
665
+ const seen = /* @__PURE__ */ new Set();
666
+ const distinct = [];
667
+ for (const issue of issues) {
668
+ const key = JSON.stringify([
669
+ issue.origin,
670
+ issue.path,
671
+ issue.message,
672
+ issue.range
673
+ ]);
674
+ if (seen.has(key)) continue;
675
+ seen.add(key);
676
+ distinct.push(issue);
677
+ }
678
+ return distinct;
679
+ }
680
+ /**
566
681
  * Reports whether a path is a workspace module Vitest can cache.
567
682
  *
568
683
  * @param path - The candidate file path
@@ -578,6 +693,75 @@ function matchesWorkspaceModule(path) {
578
693
  return /\.(?:[cm]?[jt]sx?|vue|json)$/.test(path);
579
694
  }
580
695
  /**
696
+ * Reports whether the host that wrote one file is still running.
697
+ *
698
+ * @remarks
699
+ * Signal 0 delivers nothing and reports reachability alone. A process this one may not signal
700
+ * reports `EPERM` and reads as running, and a non-positive identity names a process group rather
701
+ * than a process, so both answer true: the safe direction is to keep a file this package cannot
702
+ * account for.
703
+ *
704
+ * @param id - The process id a file's own revision names
705
+ * @returns True if that process is running or cannot be judged; false otherwise
706
+ *
707
+ * @example
708
+ * ```ts
709
+ * matchesLiveProcess(process.pid) // true
710
+ * matchesLiveProcess(0) // true
711
+ * ```
712
+ */
713
+ function matchesLiveProcess(id) {
714
+ if (!Number.isSafeInteger(id) || id <= 0) return true;
715
+ try {
716
+ process.kill(id, 0);
717
+ return true;
718
+ } catch (error) {
719
+ return readFaultCode(error) === "EPERM";
720
+ }
721
+ }
722
+ /**
723
+ * Collects every regular file a target workspace holds, skipping the trees no inspection reads.
724
+ *
725
+ * @remarks
726
+ * Skips a directory named `.git`, `dist`, or `node_modules` at any depth, and the directory
727
+ * {@link TYPE_MIRROR} names, which would otherwise carry this package's own mirror into the walk. A
728
+ * directory this host cannot list is skipped rather than raised, so one unreadable directory in a
729
+ * consumer's tree is not a reason to refuse the whole walk. A symbolic link is not carried, whether
730
+ * it names a file or a directory, so a file reached only through one is absent from the result; a
731
+ * `Dirent` that is neither a directory nor a regular file is skipped the same way.
732
+ *
733
+ * @param workspace - The target workspace root
734
+ * @returns The absolute path of every regular file the walk found
735
+ *
736
+ * @example
737
+ * ```ts
738
+ * collectWorkspaceFiles(process.cwd()).some((path) => path.endsWith('package.json')) // true
739
+ * ```
740
+ */
741
+ function collectWorkspaceFiles(workspace) {
742
+ const excluded = normalizePath(resolveWorkspaceFile(workspace, _src_core.TYPE_MIRROR));
743
+ const files = [];
744
+ const directories = [(0, node_path.resolve)(workspace)];
745
+ while (directories.length > 0) {
746
+ const directory = directories.pop();
747
+ if (directory === void 0) break;
748
+ let entries = [];
749
+ try {
750
+ entries = (0, node_fs.readdirSync)(directory, { withFileTypes: true });
751
+ } catch {}
752
+ for (const entry of entries) {
753
+ if (entry.name === ".git" || entry.name === "dist" || entry.name === "node_modules") continue;
754
+ const path = (0, node_path.join)(directory, entry.name);
755
+ if (entry.isDirectory()) {
756
+ if (normalizePath(path) !== excluded) directories.push(path);
757
+ continue;
758
+ }
759
+ if (entry.isFile()) files.push(path);
760
+ }
761
+ }
762
+ return files;
763
+ }
764
+ /**
581
765
  * Normalizes a caught or foreign error into readable text.
582
766
  *
583
767
  * @param value - The value to describe
@@ -596,9 +780,9 @@ function describeUnknown(value) {
596
780
  return String(value);
597
781
  }
598
782
  /**
599
- * Guards one resident-stage operation with the stage failure contract.
783
+ * Guards one stage operation with the stage failure contract.
600
784
  *
601
- * @param stage - The resident stage serving the operation
785
+ * @param stage - The stage serving the operation
602
786
  * @param operation - The operation to settle
603
787
  * @returns The operation's fulfilled value
604
788
  * @throws The original `ProbeError`, or an instrument-owned malformed failure retaining the cause
@@ -670,12 +854,12 @@ function findRefusedPaths(value) {
670
854
  * sorts every record's keys.
671
855
  *
672
856
  * @remarks
673
- * A parsed TypeScript project carries the absolute root it was parsed at, so the same commit
674
- * checked out at two paths produces two values that describe one configuration. Rewriting those
675
- * members makes the value portable and stops a host path leaking into anything derived from it. A
676
- * path that escapes the workspace is left as it stands, because rewriting it would name a file
677
- * outside the tree by a relative spelling. Key order is the parser's, not the configuration's, so
678
- * sorting removes a difference that means nothing.
857
+ * `tsc --showConfig` spells a path relative to the project file, so a printed record carries an
858
+ * absolute path only where a project itself declares one. Rewriting a contained absolute path to
859
+ * its relative form keeps one commit checked out at two roots reading to one digest. A path that
860
+ * escapes the workspace is left as it stands, because rewriting it would name a file outside the
861
+ * tree by a relative spelling. Key order is the compiler's own and it differs between the supported
862
+ * majors, so sorting removes a difference that means nothing.
679
863
  *
680
864
  * @param workspace - The target workspace root
681
865
  * @param value - The value to rewrite
@@ -776,9 +960,66 @@ function releaseListeners(emitter, capture) {
776
960
  }
777
961
  }
778
962
  //#endregion
963
+ //#region src/server/parsers.ts
964
+ /**
965
+ * Parses the configuration one compiler run printed for a TypeScript project.
966
+ *
967
+ * @remarks
968
+ * Reads what `tsc --showConfig` writes: a JSON record carrying the resolved `compilerOptions`
969
+ * beside the project's own file selection. `compilerOptions` is carried unvalidated, because a
970
+ * caller digests it rather than reading a member out of it. `files` and `include` are read only
971
+ * where the printed record carries one whose entries are all strings, so a member the compiler
972
+ * printed in another shape reads as absent rather than as an empty selection. Text that is not a
973
+ * JSON record, including the diagnostic the compiler prints for a project it refuses, yields
974
+ * `undefined`.
975
+ *
976
+ * @param text - The compiler's printed configuration
977
+ * @returns The project's resolved configuration, or `undefined` when the text carries no record
978
+ *
979
+ * @example
980
+ * ```ts
981
+ * parseProjectConfig('{"compilerOptions":{"strict":true}}')?.compilerOptions // { strict: true }
982
+ * parseProjectConfig("error TS5058: The specified path does not exist.") // undefined
983
+ * ```
984
+ */
985
+ function parseProjectConfig(text) {
986
+ const parsed = (0, _orkestrel_contract.attempt)(() => JSON.parse(text));
987
+ if (!parsed.success || !(0, _orkestrel_contract.isRecord)(parsed.value)) return void 0;
988
+ const files = parsed.value.files;
989
+ const include = parsed.value.include;
990
+ return {
991
+ compilerOptions: parsed.value.compilerOptions,
992
+ ...(0, _orkestrel_contract.isArray)(files) && files.every((entry) => typeof entry === "string") ? { files } : {},
993
+ ...(0, _orkestrel_contract.isArray)(include) && include.every((entry) => typeof entry === "string") ? { include } : {}
994
+ };
995
+ }
996
+ /**
997
+ * Parses the process id one revision identity names.
998
+ *
999
+ * @remarks
1000
+ * Every file and directory this package writes into a target carries the writing host's process id
1001
+ * and a fresh UUID, joined by `-`. A sweep reads that id to decide whether the host that wrote the
1002
+ * name is gone. The whole identity must match, so a name carrying anything else yields `undefined`
1003
+ * and the sweep leaves what it found alone.
1004
+ *
1005
+ * @param revision - The revision identity a name carries
1006
+ * @returns The process id it names, or `undefined` when the text is not a revision identity
1007
+ *
1008
+ * @example
1009
+ * ```ts
1010
+ * parseRevisionOwner('4821-1b4e28ba-2fa1-11d2-883f-0016d3cca427') // 4821
1011
+ * parseRevisionOwner('4821-9f0c') // undefined
1012
+ * ```
1013
+ */
1014
+ function parseRevisionOwner(revision) {
1015
+ 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];
1016
+ return owner === void 0 ? void 0 : Number.parseInt(owner, 10);
1017
+ }
1018
+ //#endregion
779
1019
  //#region src/server/Overlay.ts
780
1020
  /**
781
- * Holds the candidate drafts one inspection substitutes for the files a tool would read from disk.
1021
+ * Implements `OverlayInterface` over a private map from normalized absolute path to candidate text,
1022
+ * minting at construction the `revision` a resident tool caches its answers against.
782
1023
  *
783
1024
  * @remarks
784
1025
  * One overlay belongs to one inspection: the inspection creates it, records every candidate it
@@ -787,55 +1028,44 @@ function releaseListeners(emitter, capture) {
787
1028
  * is what makes a resident tool re-read a path this overlay holds, so two inspections that supply
788
1029
  * different text for one path never share a cached answer.
789
1030
  *
790
- * This overlay matches a lookup key the way the host its stage declares to its tool matches file
791
- * names. A host that resolves two spellings of one file name to one file leaves the tool holding
792
- * whichever spelling it met first — the disk one, where the file is on disk — and the tool asks
793
- * under that, so an overlay matching keys exactly answers nothing and the tool reads the committed
794
- * file instead. Mint the overlay with the sensitivity the stage declares, and one reading decides
795
- * both. The recorded spelling is what `paths` reports and what `covers` matches a directory
796
- * against, so a folded key never reaches the paths a tool is handed and containment stays an exact
797
- * comparison.
1031
+ * A lookup key matches a recorded path exactly, after both pass through `normalizePath`, so a tool
1032
+ * that reports its own paths with backslashes still reaches a candidate recorded with forward
1033
+ * slashes. Case is never folded: the recorded spelling is what `paths` reports, what `covers`
1034
+ * compares a directory against, and what `text` answers under. A stage whose tool resolves two
1035
+ * spellings of one file name to one file therefore reports the path its tool served rather than
1036
+ * matching the two spellings here.
798
1037
  *
799
1038
  * @example
800
1039
  * ```ts
801
1040
  * const overlay = new Overlay()
802
- * overlay.set('/srv/checkout/src/core/greeting.ts', "export const GREETING = 'hi'\n")
803
- * console.log(overlay.text('/srv/checkout/src/core/greeting.ts'))
1041
+ * const path = '/srv/checkout/src/core/factories.ts'
1042
+ * overlay.set(path, "export function createGreeting(): string {\n\treturn 'hi'\n}\n")
1043
+ * console.log(overlay.text(path))
804
1044
  * overlay.clear()
805
1045
  * ```
806
1046
  */
807
1047
  var Overlay = class {
808
1048
  #revision = (0, node_crypto.randomUUID)();
809
- #sensitive;
810
1049
  #candidates = /* @__PURE__ */ new Map();
811
- /**
812
- * Creates an empty candidate set under a fresh identity.
813
- *
814
- * @param options - Construction options
815
- */
816
- constructor(options = {}) {
817
- this.#sensitive = options.sensitive ?? true;
818
- }
819
1050
  get revision() {
820
1051
  return this.#revision;
821
1052
  }
822
1053
  get paths() {
823
- return [...this.#candidates.values()].map(([path]) => path);
1054
+ return [...this.#candidates.keys()];
824
1055
  }
825
1056
  /**
826
1057
  * Records one candidate's text against the absolute path it stands in for.
827
1058
  *
828
1059
  * @remarks
829
- * Recording a candidate under a path this overlay's own matching reads as one already recorded
830
- * replaces that candidate, the way one file holds one text.
1060
+ * Recording a candidate under a path this overlay already holds replaces that candidate, the way
1061
+ * one file holds one text.
831
1062
  *
832
1063
  * @param path - The absolute path the candidate replaces
833
1064
  * @param text - The candidate's full contents
834
1065
  * @returns Nothing
835
1066
  */
836
1067
  set(path, text) {
837
- const recorded = normalizePath(path);
838
- this.#candidates.set(this.#key(recorded), [recorded, text]);
1068
+ this.#candidates.set(normalizePath(path), text);
839
1069
  }
840
1070
  /**
841
1071
  * Reads the candidate text recorded for one absolute path.
@@ -844,7 +1074,7 @@ var Overlay = class {
844
1074
  * @returns The recorded text, or `undefined` when this overlay holds no candidate there
845
1075
  */
846
1076
  text(path) {
847
- return this.#candidates.get(this.#key(normalizePath(path)))?.[1];
1077
+ return this.#candidates.get(normalizePath(path));
848
1078
  }
849
1079
  /**
850
1080
  * Checks whether a candidate sits beneath one directory.
@@ -853,16 +1083,14 @@ var Overlay = class {
853
1083
  * The answer is derived from the paths the overlay holds rather than stored, so it stops being
854
1084
  * true exactly when the inspection that declared those candidates clears them. Both sides pass
855
1085
  * through `normalizePath` first, because a tool that normalizes its own paths asks about a
856
- * directory in a spelling the recorded path may not share. Case is never folded here: this is a
857
- * containment comparison, and it reads the recorded spelling whatever an overlay minted for a
858
- * case-folding host matches its lookup keys by.
1086
+ * directory in a spelling the recorded path may not share.
859
1087
  *
860
1088
  * @param directory - The absolute directory path to check
861
1089
  * @returns True if a candidate path sits beneath the directory; false otherwise
862
1090
  */
863
1091
  covers(directory) {
864
1092
  const base = `${normalizePath(directory).replace(/\/+$/, "")}/`;
865
- for (const [path] of this.#candidates.values()) if (path.startsWith(base)) return true;
1093
+ for (const path of this.#candidates.keys()) if (path.startsWith(base)) return true;
866
1094
  return false;
867
1095
  }
868
1096
  /**
@@ -873,15 +1101,12 @@ var Overlay = class {
873
1101
  clear() {
874
1102
  this.#candidates.clear();
875
1103
  }
876
- #key(path) {
877
- return this.#sensitive ? path : path.toLowerCase();
878
- }
879
1104
  };
880
1105
  //#endregion
881
1106
  //#region package.json
882
- var version = "0.0.12";
1107
+ var version = "0.0.14";
883
1108
  var peerDependencies = {
884
- "oxlint": "^1.80.0",
1109
+ "oxlint": "^1.82.0",
885
1110
  "typescript": "^6.0.3",
886
1111
  "vitest": "^4.1.11"
887
1112
  };
@@ -1162,12 +1387,14 @@ var RuntimeStage = class {
1162
1387
  /**
1163
1388
  * Starts warming the target workspace's Vitest service.
1164
1389
  *
1390
+ * @remarks Loading starts during this call. A loader refusal rejects the owned warm operation
1391
+ * rather than construction, so `inspect` reports it and `destroy` remains available.
1392
+ *
1165
1393
  * @param workspace - The target workspace root. Default: the current working directory
1166
1394
  */
1167
1395
  constructor(workspace = process.cwd()) {
1168
1396
  this.#workspace = workspace;
1169
- const vitest = loadWorkspaceModule(this.#workspace, "vitest/node");
1170
- this.#store(this.#warm(vitest.createVitest));
1397
+ this.#store(this.#warm());
1171
1398
  }
1172
1399
  get stage() {
1173
1400
  return "runtime";
@@ -1175,9 +1402,29 @@ var RuntimeStage = class {
1175
1402
  get progress() {
1176
1403
  return this.#progress;
1177
1404
  }
1405
+ /**
1406
+ * Inspects one case.
1407
+ *
1408
+ * @param subject - The candidate drafts and test to inspect
1409
+ * @returns One outcome for this stage
1410
+ * @throws When the workspace's own tool cannot start or has already been destroyed
1411
+ */
1178
1412
  inspect(subject) {
1179
1413
  return guardStage(this.stage, this.#inspect(subject));
1180
1414
  }
1415
+ /**
1416
+ * Tears down the resident tool or the mirror and releases its resources.
1417
+ *
1418
+ * @remarks
1419
+ * A stage abandons every inspection it holds rather than waiting behind one, so teardown never
1420
+ * waits for an inspection to return. An abandoned inspection rejects, either at the stage's own
1421
+ * guard or as the owned tool closes. Teardown is bounded whatever the stage's own tool does: a
1422
+ * tool that answers neither its warming exchange nor its ending is signalled and released at the
1423
+ * stage's own deadline. A coordinator replaces a stage whose worker no longer returns because
1424
+ * teardown neither waits for an inspection nor waits past that deadline.
1425
+ *
1426
+ * @returns A promise that settles after the resident tool or the mirror releases its resources
1427
+ */
1181
1428
  destroy() {
1182
1429
  if (this.#closing !== void 0) return this.#closing;
1183
1430
  this.#closing = guardStage(this.stage, this.#destroy());
@@ -1278,13 +1525,14 @@ var RuntimeStage = class {
1278
1525
  }
1279
1526
  }
1280
1527
  async #warm(create) {
1528
+ const factory = create ?? loadWorkspaceVitest(this.#workspace).createVitest;
1281
1529
  this.#sweep();
1282
1530
  const output = new node_stream.PassThrough();
1283
1531
  output.resume();
1284
1532
  const signals = captureListeners(process, ["SIGINT", "SIGTERM"]);
1285
1533
  let warming;
1286
1534
  try {
1287
- warming = create("test", {
1535
+ warming = factory("test", {
1288
1536
  root: this.#workspace,
1289
1537
  config: resolveWorkspaceFile(this.#workspace, "vite.config.ts"),
1290
1538
  watch: false,
@@ -1521,13 +1769,13 @@ var RuntimeStage = class {
1521
1769
  const current = this.#vitest;
1522
1770
  if (current !== void 0 && this.#specifications < _src_core.PROBE_SPECIFICATIONS) return current;
1523
1771
  if (current !== void 0) return this.#store(this.#replace(current));
1524
- const runner = loadWorkspaceModule(this.#workspace, "vitest/node");
1772
+ const runner = loadWorkspaceVitest(this.#workspace);
1525
1773
  return this.#store(this.#specifications < _src_core.PROBE_SPECIFICATIONS ? this.#warm(runner.createVitest) : this.#replace(void 0, runner.createVitest));
1526
1774
  }
1527
1775
  async #replace(current, create) {
1528
1776
  if (current !== void 0) await (await current).close();
1529
1777
  if (this.#closing !== void 0) throw (0, _src_core.createDestroyedError)("runtime stage");
1530
- const runner = create ?? loadWorkspaceModule(this.#workspace, "vitest/node").createVitest;
1778
+ const runner = create ?? loadWorkspaceVitest(this.#workspace).createVitest;
1531
1779
  const replacement = await this.#warm(runner);
1532
1780
  this.#specifications = 0;
1533
1781
  return replacement;
@@ -1571,7 +1819,7 @@ var RuntimeStage = class {
1571
1819
  }
1572
1820
  #snapshot() {
1573
1821
  const modules = /* @__PURE__ */ new Map();
1574
- for (const path of this.#walk()) {
1822
+ for (const path of collectWorkspaceFiles(this.#workspace)) {
1575
1823
  if (!matchesWorkspaceModule(path)) continue;
1576
1824
  try {
1577
1825
  const digest = (0, node_crypto.createHash)("sha256").update((0, node_fs.readFileSync)(path)).digest("hex");
@@ -1582,12 +1830,12 @@ var RuntimeStage = class {
1582
1830
  return modules;
1583
1831
  }
1584
1832
  #sweep() {
1585
- for (const path of this.#walk()) {
1833
+ for (const path of collectWorkspaceFiles(this.#workspace)) {
1586
1834
  const match = [...(0, node_path.basename)(path).matchAll(/\.probe-((\d+)-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?=\.|$)/gu)].at(-1);
1587
1835
  const revision = match?.[1];
1588
1836
  const owner = match?.[2];
1589
1837
  if (revision === void 0 || owner === void 0) continue;
1590
- if (this.#alive(Number.parseInt(owner, 10))) continue;
1838
+ if (matchesLiveProcess(Number.parseInt(owner, 10))) continue;
1591
1839
  if (!this.#owned(path, revision)) continue;
1592
1840
  try {
1593
1841
  (0, node_fs.unlinkSync)(resolveWorkspaceFile(this.#workspace, (0, node_path.relative)(this.#workspace, path), true));
@@ -1598,35 +1846,6 @@ var RuntimeStage = class {
1598
1846
  const outcome = (0, _orkestrel_contract.attempt)(() => (0, node_fs.readFileSync)(path, "utf8"));
1599
1847
  return outcome.success && (0, _src_core.matchesSpecification)(outcome.value, revision);
1600
1848
  }
1601
- #alive(id) {
1602
- if (!Number.isSafeInteger(id) || id <= 0) return true;
1603
- try {
1604
- process.kill(id, 0);
1605
- return true;
1606
- } catch (error) {
1607
- return readFaultCode(error) === "EPERM";
1608
- }
1609
- }
1610
- *#walk() {
1611
- const directories = [(0, node_path.resolve)(this.#workspace)];
1612
- while (directories.length > 0) {
1613
- const directory = directories.pop();
1614
- if (directory === void 0) break;
1615
- let entries = [];
1616
- try {
1617
- entries = (0, node_fs.readdirSync)(directory, { withFileTypes: true });
1618
- } catch {}
1619
- for (const entry of entries) {
1620
- if (entry.name === ".git" || entry.name === "dist" || entry.name === "node_modules") continue;
1621
- const path = (0, node_path.join)(directory, entry.name);
1622
- if (entry.isDirectory()) {
1623
- directories.push(path);
1624
- continue;
1625
- }
1626
- if (entry.isFile()) yield path;
1627
- }
1628
- }
1629
- }
1630
1849
  #issues(result, file, original) {
1631
1850
  const specification = this.#real(file);
1632
1851
  const issues = [];
@@ -1742,21 +1961,32 @@ var RuntimeStage = class {
1742
1961
  //#endregion
1743
1962
  //#region src/server/stages/TypeStage.ts
1744
1963
  /**
1745
- * Inspects TypeScript source through resident language services from the target workspace.
1964
+ * Inspects TypeScript source by running the target workspace's own compiler over a mirror of it.
1746
1965
  *
1747
1966
  * @remarks
1748
- * Construction starts loading the workspace's compiler and warming one service per project the
1749
- * workspace declares. A candidate draft is checked against the project a call names, or
1750
- * against its own scoped environment project when a call names none, while the test uses the root
1751
- * project. Disk snapshots use their modification time as the service version so dependency edits
1752
- * cannot leave a warm answer stale. Candidate text lives in an overlay the inspection owns, and
1753
- * the language-service host answers existence, reads, and versions from it, so a candidate that
1754
- * exists only as text is importable and a candidate that shadows a disk file is checked as the
1755
- * text the case supplied. A project outside the declared set holds one recycled slot, so a caller
1756
- * varying the project cannot grow the resident set. A language service checks one candidate
1757
- * synchronously, so the stage hands the host's event loop back at each candidate boundary: a
1758
- * caller's deadline is answered within one candidate's check rather than after the whole
1759
- * inspection, and an inspection abandoned at that deadline stops at the next boundary.
1967
+ * The compiler is a process this stage spawns rather than a module it loads, so nothing here holds
1968
+ * a resident program and the host's event loop is free for the whole check. Construction sweeps a
1969
+ * mirror an earlier host left behind, copies the workspace into a fresh one under
1970
+ * {@link TYPE_MIRROR}, and builds each declared project's incremental state there, so the first
1971
+ * inspection is warm.
1972
+ *
1973
+ * Every inspection refreshes the mirror by content digest, writes each candidate draft and the test
1974
+ * at its mirrored declared path, and runs `tsc --noEmit --pretty false` once per distinct selected
1975
+ * project. A draft therefore shadows the file it replaces: a consumer of that path is checked
1976
+ * against the draft's text, and a draft importing a sibling draft resolves to the sibling draft.
1977
+ * Nothing is written outside `tmp/`, and the workspace's own copy of a drafted file never moves.
1978
+ *
1979
+ * Each run reads a scratch project this stage writes beside the mirrored project it extends, so
1980
+ * every relative path the project declares — its own `rootDir`, `include`, `paths`, and the
1981
+ * projects it extends — resolves inside the mirror without being rewritten. The scratch adds the
1982
+ * drafts as `files`, which the project's own `exclude` does not reach, and its incremental state
1983
+ * lives in the mirror so reuse survives the refresh.
1984
+ *
1985
+ * The diagnostics decide the outcome, never the exit code, which the supported compiler majors
1986
+ * disagree on. A diagnostic naming a project file, and one naming no file at all, is the target
1987
+ * tree's own configuration fault and raises rather than reporting, unless the `.json` file is one
1988
+ * the claim itself drafted; every other diagnostic is a claimant issue at the point the compiler
1989
+ * reported, which is the extent the plain-text output carries.
1760
1990
  *
1761
1991
  * @example
1762
1992
  * ```ts
@@ -1767,26 +1997,27 @@ var RuntimeStage = class {
1767
1997
  */
1768
1998
  var TypeStage = class {
1769
1999
  #workspace;
1770
- #typescript;
1771
- #services = /* @__PURE__ */ new Map();
1772
- #resident = /* @__PURE__ */ new Set();
1773
- #options = /* @__PURE__ */ new Map();
1774
- #files = /* @__PURE__ */ new Map();
1775
- #diagnostics = /* @__PURE__ */ new Map();
1776
- #overlay = new Overlay();
1777
- #recycled;
2000
+ #compiler;
2001
+ #revision = `${process.pid}-${(0, node_crypto.randomUUID)()}`;
2002
+ #mirror;
2003
+ #configs = /* @__PURE__ */ new Map();
2004
+ #mirrored = /* @__PURE__ */ new Map();
2005
+ #drafts = /* @__PURE__ */ new Set();
2006
+ #children = /* @__PURE__ */ new Set();
2007
+ #warming;
1778
2008
  #closing;
1779
2009
  #progress = 0;
1780
2010
  /**
1781
- * Starts warming the target workspace's TypeScript service.
2011
+ * Resolves the target workspace's compiler and starts warming its mirror.
1782
2012
  *
1783
2013
  * @param workspace - The target workspace root. Default: the current working directory
1784
2014
  */
1785
2015
  constructor(workspace = process.cwd()) {
1786
2016
  this.#workspace = workspace;
1787
- const typescript = loadWorkspaceModule(this.#workspace, "typescript");
1788
- this.#typescript = this.#warm(typescript);
1789
- this.#typescript.catch(() => {});
2017
+ this.#compiler = resolveWorkspaceBinary(workspace, "typescript", "tsc");
2018
+ this.#mirror = resolveWorkspaceFile(workspace, `${_src_core.TYPE_MIRROR}/${this.#revision}`);
2019
+ this.#warming = this.#warm();
2020
+ this.#warming.catch(() => {});
1790
2021
  }
1791
2022
  get stage() {
1792
2023
  return "type";
@@ -1806,7 +2037,8 @@ var TypeStage = class {
1806
2037
  * @param project - The workspace-relative TypeScript project the candidate drafts are checked
1807
2038
  * against. Default: the scoped project each candidate path infers
1808
2039
  * @returns One outcome for this stage
1809
- * @throws When the resident compiler cannot start or the stage has already been destroyed
2040
+ * @throws When the workspace refuses the mirror, when a project the run reads is malformed, or
2041
+ * when the stage has already been destroyed
1810
2042
  */
1811
2043
  inspect(subject, project) {
1812
2044
  return guardStage(this.stage, this.#inspect(subject, project));
@@ -1815,15 +2047,15 @@ var TypeStage = class {
1815
2047
  * Resolves one project to the path and digest this stage applies for it.
1816
2048
  *
1817
2049
  * @remarks
1818
- * Reads the parse this stage itself applies, filling its cache when the project is not already
1819
- * resident, so the reported digest is the configuration the inspection is judged under rather
1820
- * than a second parse a caller ran. The returned record is a value copy, so a later eviction
1821
- * does not move it.
2050
+ * Reads the configuration the compiler itself prints for the project as the mirror holds it,
2051
+ * refreshed from the workspace first on a cache miss, so the digest names the configuration the
2052
+ * check applies. The reading is cached per project for the life of the stage, so a draft written
2053
+ * afterward cannot move it.
1822
2054
  *
1823
2055
  * @param project - The workspace-relative TypeScript project to resolve
1824
2056
  * @returns The resolved workspace-relative path and the digest of its compiler options
1825
- * @throws When the project escapes the workspace, cannot be parsed, or the stage has already
1826
- * been destroyed
2057
+ * @throws When the project escapes the workspace, when the compiler refuses it, or when the
2058
+ * stage has already been destroyed
1827
2059
  */
1828
2060
  resolve(project) {
1829
2061
  return guardStage(this.stage, this.#resolve(project));
@@ -1834,81 +2066,76 @@ var TypeStage = class {
1834
2066
  return this.#closing;
1835
2067
  }
1836
2068
  async #inspect(subject, project) {
1837
- if (this.#closing !== void 0) throw (0, _src_core.createDestroyedError)("type stage");
2069
+ this.#refuseDestroyed();
1838
2070
  const started = performance.now();
1839
- const typescript = await this.#typescript;
1840
- if (this.#closing !== void 0) throw (0, _src_core.createDestroyedError)("type stage");
1841
- const selections = subject.files.map((draft) => ({
2071
+ await this.#warming;
2072
+ this.#refuseDestroyed();
2073
+ const test = resolveWorkspaceFile(this.#workspace, subject.test.path);
2074
+ const resolved = subject.files.map((draft) => ({
1842
2075
  draft,
1843
2076
  path: resolveWorkspaceFile(this.#workspace, draft.path)
1844
- })).map((candidate) => ({
1845
- draft: candidate.draft,
1846
- project: project ?? inferTypeProject(relativeWorkspaceFile(this.#workspace, candidate.path))
1847
2077
  }));
1848
- const root = this.#service(typescript, "tsconfig.json");
1849
- this.#configure(root, "tsconfig.json");
1850
- await this.#unblock();
1851
- for (const selection of selections) {
1852
- this.#configure(this.#service(typescript, selection.project), selection.project);
1853
- await this.#unblock();
2078
+ const groups = /* @__PURE__ */ new Map([[this.#contain("tsconfig.json"), [subject.test]]]);
2079
+ for (const candidate of resolved) {
2080
+ const selected = this.#contain(project ?? inferTypeProject(relativeWorkspaceFile(this.#workspace, candidate.path)));
2081
+ const drafts = groups.get(selected);
2082
+ if (drafts === void 0) groups.set(selected, [candidate.draft]);
2083
+ else drafts.push(candidate.draft);
1854
2084
  }
1855
- const overlay = new Overlay({ sensitive: this.#caseSensitive(typescript) });
1856
- this.#overlay = overlay;
2085
+ this.#progress += 1;
2086
+ for (const selected of groups.keys()) await this.#configure(selected);
2087
+ this.#refuseDestroyed();
1857
2088
  try {
1858
- this.#record(subject.test, overlay);
1859
- for (const draft of subject.files) this.#record(draft, overlay);
1860
- this.#progress += 1;
2089
+ this.#refresh();
2090
+ this.#drafts.add(this.#place(subject.test.text, test));
2091
+ for (const candidate of resolved) this.#drafts.add(this.#place(candidate.draft.text, candidate.path));
1861
2092
  const issues = [];
1862
- const projects = /* @__PURE__ */ new Set();
1863
- issues.push(...this.#issues(typescript, root, subject.test, "tsconfig.json", false, true));
1864
- projects.add("tsconfig.json");
1865
- await this.#unblock();
1866
- for (const selection of selections) {
1867
- const draft = selection.draft;
1868
- const selected = selection.project;
1869
- const service = this.#service(typescript, selected);
1870
- issues.push(...this.#issues(typescript, service, draft, selected, project !== void 0, !projects.has(selected)));
1871
- projects.add(selected);
1872
- await this.#unblock();
2093
+ for (const [selected, drafts] of groups) {
2094
+ this.#refuseDestroyed();
2095
+ issues.push(...await this.#check(selected, drafts));
1873
2096
  }
1874
2097
  return {
1875
2098
  stage: this.stage,
1876
2099
  elapsed: Math.round(performance.now() - started),
1877
- issues
2100
+ issues: filterUniqueIssues(issues)
1878
2101
  };
1879
2102
  } finally {
1880
- overlay.clear();
2103
+ this.#release();
1881
2104
  }
1882
2105
  }
1883
2106
  async #resolve(project) {
1884
- if (this.#closing !== void 0) throw (0, _src_core.createDestroyedError)("type stage");
1885
- const typescript = await this.#typescript;
1886
- if (this.#closing !== void 0) throw (0, _src_core.createDestroyedError)("type stage");
1887
- const resolved = resolveWorkspaceFile(this.#workspace, project);
2107
+ this.#refuseDestroyed();
2108
+ const contained = this.#contain(project);
1888
2109
  this.#progress += 1;
1889
- this.#service(typescript, project);
1890
- await this.#unblock();
2110
+ const config = await this.#configure(contained);
1891
2111
  return {
1892
- path: relativeWorkspaceFile(this.#workspace, resolved),
1893
- digest: computeDigest(this.#workspace, this.#options.get(resolved) ?? {})
2112
+ path: contained,
2113
+ digest: computeDigest(this.#workspace, config.compilerOptions)
1894
2114
  };
1895
2115
  }
1896
2116
  async #destroy() {
1897
- await this.#typescript.catch(() => void 0);
1898
- for (const service of this.#services.values()) service.dispose();
1899
- this.#services.clear();
1900
- this.#resident.clear();
1901
- this.#recycled = void 0;
1902
- this.#options.clear();
1903
- this.#files.clear();
1904
- this.#diagnostics.clear();
1905
- }
1906
- async #warm(typescript) {
1907
- for (const project of this.#projects()) {
1908
- this.#resident.add(resolveWorkspaceFile(this.#workspace, project));
1909
- this.#service(typescript, project);
1910
- }
1911
- return typescript;
2117
+ const children = [...this.#children];
2118
+ this.#children.clear();
2119
+ for (const child of children) this.#terminate(child);
2120
+ await this.#warming.catch(() => void 0);
2121
+ try {
2122
+ (0, node_fs.rmSync)(this.#mirror, {
2123
+ recursive: true,
2124
+ force: true,
2125
+ maxRetries: 5,
2126
+ retryDelay: 20
2127
+ });
2128
+ } catch {}
2129
+ this.#configs.clear();
2130
+ this.#mirrored.clear();
2131
+ this.#drafts.clear();
2132
+ }
2133
+ async #warm() {
2134
+ this.#sweep();
2135
+ this.#createMirror();
2136
+ this.#refresh();
2137
+ this.#refuseDestroyed();
2138
+ await Promise.all(this.#projects().map((project) => this.#check(project, [])));
1912
2139
  }
1913
2140
  #projects() {
1914
2141
  const projects = ["tsconfig.json"];
@@ -1924,162 +2151,291 @@ var TypeStage = class {
1924
2151
  }
1925
2152
  return projects;
1926
2153
  }
1927
- async #unblock() {
1928
- await (0, node_timers_promises.setTimeout)(0);
2154
+ #refuseDestroyed() {
1929
2155
  if (this.#closing !== void 0) throw (0, _src_core.createDestroyedError)("type stage");
1930
2156
  }
1931
- #record(draft, overlay) {
1932
- overlay.set(resolveWorkspaceFile(this.#workspace, draft.path), draft.text);
1933
- }
1934
- #service(typescript, project) {
1935
- const path = resolveWorkspaceFile(this.#workspace, project);
1936
- const existing = this.#services.get(path);
1937
- if (existing !== void 0) return existing;
1938
- const spelling = normalizePath(path);
1939
- const config = typescript.readConfigFile(spelling, typescript.sys.readFile);
1940
- if (config.error !== void 0) throw new _src_core.ProbeError(this.#translate(typescript, config.error.messageText), {
1941
- origin: "workspace",
1942
- code: "malformed",
1943
- context: {
1944
- stage: this.stage,
1945
- project
1946
- }
2157
+ #createMirror() {
2158
+ const marker = resolveWorkspaceFile(this.#workspace, `${_src_core.TYPE_MIRROR}/${this.#revision}/.probe/mirror.txt`, true);
2159
+ (0, node_fs.mkdirSync)((0, node_path.dirname)(marker), { recursive: true });
2160
+ (0, node_fs.writeFileSync)(marker, (0, _src_core.formatSpecification)("", this.#revision), {
2161
+ encoding: "utf8",
2162
+ flag: "wx"
1947
2163
  });
1948
- const parsed = typescript.parseJsonConfigFileContent(config.config, typescript.sys, (0, node_path.dirname)(spelling), void 0, spelling);
1949
- if (parsed.errors.length > 0) throw new _src_core.ProbeError(this.#translate(typescript, parsed.errors[0]?.messageText), {
1950
- origin: "workspace",
1951
- code: "malformed",
1952
- context: {
1953
- stage: this.stage,
1954
- project
1955
- }
1956
- });
1957
- this.#options.set(path, parsed.options);
1958
- this.#files.set(path, parsed.fileNames);
1959
- const host = {
1960
- getCompilationSettings: this.#compilationSettings.bind(this, path),
1961
- getScriptFileNames: this.#scriptFiles.bind(this, path),
1962
- getScriptVersion: this.#version.bind(this),
1963
- getScriptSnapshot: this.#snapshot.bind(this, typescript),
1964
- getCurrentDirectory: this.#directory.bind(this),
1965
- getDefaultLibFileName: this.#defaultLibrary.bind(this, typescript),
1966
- fileExists: this.#fileExists.bind(this, typescript),
1967
- readFile: this.#readFile.bind(this, typescript),
1968
- readDirectory: typescript.sys.readDirectory,
1969
- directoryExists: this.#directoryExists.bind(this, typescript),
1970
- getDirectories: typescript.sys.getDirectories,
1971
- useCaseSensitiveFileNames: this.#caseSensitive.bind(this, typescript),
1972
- getNewLine: this.#newline.bind(this, typescript)
1973
- };
1974
- const service = typescript.createLanguageService(host);
1975
- this.#services.set(path, service);
1976
- if (!this.#resident.has(path)) this.#recycle(path);
1977
- return service;
1978
- }
1979
- #compilationSettings(path) {
1980
- return this.#options.get(path) ?? {};
1981
- }
1982
- #scriptFiles(path) {
1983
- return [...this.#files.get(path) ?? [], ...this.#overlay.paths];
1984
2164
  }
1985
- #directory() {
1986
- return this.#workspace;
1987
- }
1988
- #defaultLibrary(typescript, options) {
1989
- return typescript.getDefaultLibFilePath(options);
1990
- }
1991
- #fileExists(typescript, file) {
1992
- return this.#overlay.text(file) !== void 0 || typescript.sys.fileExists(file);
1993
- }
1994
- #readFile(typescript, file) {
1995
- return this.#overlay.text(file) ?? typescript.sys.readFile(file);
1996
- }
1997
- #directoryExists(typescript, directory) {
1998
- return typescript.sys.directoryExists(directory) || this.#overlay.covers(directory);
1999
- }
2000
- #caseSensitive(typescript) {
2001
- return typescript.sys.useCaseSensitiveFileNames;
2165
+ #sweep() {
2166
+ const root = resolveWorkspaceFile(this.#workspace, _src_core.TYPE_MIRROR);
2167
+ let entries = [];
2168
+ try {
2169
+ entries = (0, node_fs.readdirSync)(root, { withFileTypes: true });
2170
+ } catch {
2171
+ return;
2172
+ }
2173
+ for (const entry of entries) {
2174
+ if (!entry.isDirectory()) continue;
2175
+ const owner = parseRevisionOwner(entry.name);
2176
+ if (owner === void 0 || matchesLiveProcess(owner)) continue;
2177
+ const marker = (0, _orkestrel_contract.attempt)(() => (0, node_fs.readFileSync)((0, node_path.join)(root, entry.name, ".probe", "mirror.txt"), "utf8"));
2178
+ if (!marker.success || !(0, _src_core.matchesSpecification)(marker.value, entry.name)) continue;
2179
+ try {
2180
+ (0, node_fs.rmSync)((0, node_path.join)(root, entry.name), {
2181
+ recursive: true,
2182
+ force: true,
2183
+ maxRetries: 5,
2184
+ retryDelay: 20
2185
+ });
2186
+ } catch {}
2187
+ }
2002
2188
  }
2003
- #newline(typescript) {
2004
- return typescript.sys.newLine;
2189
+ #refresh() {
2190
+ const present = /* @__PURE__ */ new Set();
2191
+ for (const path of collectWorkspaceFiles(this.#workspace)) {
2192
+ const contained = relativeWorkspaceFile(this.#workspace, path);
2193
+ present.add(contained);
2194
+ const reading = (0, _orkestrel_contract.attempt)(() => (0, node_fs.readFileSync)(path));
2195
+ if (!reading.success) continue;
2196
+ const digest = (0, node_crypto.createHash)("sha256").update(reading.value).digest("hex");
2197
+ if (this.#mirrored.get(contained) === digest) continue;
2198
+ this.#place(reading.value, path);
2199
+ this.#mirrored.set(contained, digest);
2200
+ }
2201
+ for (const contained of [...this.#mirrored.keys()]) {
2202
+ if (present.has(contained)) continue;
2203
+ this.#remove(contained);
2204
+ this.#mirrored.delete(contained);
2205
+ }
2005
2206
  }
2006
- #translate(typescript, message) {
2007
- return relativeWorkspaceMessage(this.#workspace, typescript.flattenDiagnosticMessageText(message, "\n"));
2207
+ #release() {
2208
+ for (const contained of this.#drafts) {
2209
+ this.#remove(contained);
2210
+ this.#mirrored.delete(contained);
2211
+ }
2212
+ this.#drafts.clear();
2213
+ }
2214
+ #place(text, path) {
2215
+ const contained = relativeWorkspaceFile(this.#workspace, path);
2216
+ const target = this.#mirrorPath(contained);
2217
+ if ((0, _orkestrel_contract.attempt)(() => {
2218
+ (0, node_fs.mkdirSync)((0, node_path.dirname)(target), { recursive: true });
2219
+ (0, node_fs.writeFileSync)(target, text);
2220
+ }).success) return contained;
2221
+ this.#displace(target);
2222
+ (0, node_fs.mkdirSync)((0, node_path.dirname)(target), { recursive: true });
2223
+ (0, node_fs.writeFileSync)(target, text);
2224
+ return contained;
2225
+ }
2226
+ #displace(target) {
2227
+ const ancestors = [];
2228
+ let directory = (0, node_path.dirname)(target);
2229
+ while (directory !== this.#mirror && directory !== (0, node_path.dirname)(directory)) {
2230
+ ancestors.unshift(directory);
2231
+ directory = (0, node_path.dirname)(directory);
2232
+ }
2233
+ for (const ancestor of [...ancestors, target]) {
2234
+ const reading = (0, _orkestrel_contract.attempt)(() => (0, node_fs.lstatSync)(ancestor));
2235
+ if (!reading.success) continue;
2236
+ if (ancestor !== target && reading.value.isDirectory()) continue;
2237
+ if (ancestor === target && !reading.value.isDirectory()) continue;
2238
+ (0, node_fs.rmSync)(ancestor, {
2239
+ recursive: true,
2240
+ force: true,
2241
+ maxRetries: 5,
2242
+ retryDelay: 20
2243
+ });
2244
+ this.#mirrored.delete(relativeWorkspaceFile(this.#mirror, ancestor));
2245
+ }
2008
2246
  }
2009
- #configure(service, project) {
2010
- const path = resolveWorkspaceFile(this.#workspace, project);
2011
- if (this.#diagnostics.has(path)) return;
2012
- this.#diagnostics.set(path, service.getCompilerOptionsDiagnostics());
2247
+ #mirrorPath(contained) {
2248
+ return (0, node_path.join)(this.#mirror, contained);
2013
2249
  }
2014
- #recycle(path) {
2015
- const previous = this.#recycled;
2016
- this.#recycled = path;
2017
- if (previous === void 0 || previous === path) return;
2018
- this.#services.get(previous)?.dispose();
2019
- this.#services.delete(previous);
2020
- this.#options.delete(previous);
2021
- this.#files.delete(previous);
2022
- this.#diagnostics.delete(previous);
2250
+ #contain(project) {
2251
+ return relativeWorkspaceFile(this.#workspace, resolveWorkspaceFile(this.#workspace, project));
2023
2252
  }
2024
- #version(file) {
2025
- if (this.#overlay.text(file) !== void 0) return `virtual:${this.#overlay.revision}`;
2253
+ #remove(contained) {
2026
2254
  try {
2027
- return `disk:${(0, node_fs.statSync)(file).mtimeMs}`;
2028
- } catch {
2029
- return "missing";
2030
- }
2255
+ (0, node_fs.rmSync)(this.#mirrorPath(contained), {
2256
+ force: true,
2257
+ maxRetries: 5,
2258
+ retryDelay: 20
2259
+ });
2260
+ } catch {}
2031
2261
  }
2032
- #snapshot(typescript, file) {
2033
- const text = this.#overlay.text(file) ?? typescript.sys.readFile(file);
2034
- return text === void 0 ? void 0 : typescript.ScriptSnapshot.fromString(text);
2035
- }
2036
- #issues(typescript, service, draft, project, selected, configure) {
2037
- const path = resolveWorkspaceFile(this.#workspace, draft.path);
2038
- const configuration = resolveWorkspaceFile(this.#workspace, project);
2039
- return [
2040
- ...configure ? this.#diagnostics.get(configuration) ?? [] : [],
2041
- ...service.getSyntacticDiagnostics(path),
2042
- ...service.getSemanticDiagnostics(path)
2043
- ].map((diagnostic) => this.#issue(typescript, diagnostic, project, selected));
2044
- }
2045
- #issue(typescript, diagnostic, project, selected) {
2046
- const message = this.#translate(typescript, diagnostic.messageText);
2047
- if (diagnostic.file === void 0) {
2048
- if (selected) throw new _src_core.ProbeError(message, {
2049
- origin: "claimant",
2050
- code: "refused",
2262
+ async #configure(project) {
2263
+ const contained = this.#contain(project);
2264
+ const existing = this.#configs.get(contained);
2265
+ if (existing !== void 0) return existing;
2266
+ this.#refresh();
2267
+ const execution = await this.#spawn([
2268
+ "--showConfig",
2269
+ "-p",
2270
+ contained
2271
+ ], this.#mirror);
2272
+ this.#refuseDestroyed();
2273
+ const config = parseProjectConfig(execution.stdout);
2274
+ if (config === void 0) throw this.#fault(execution, contained);
2275
+ this.#configs.set(contained, config);
2276
+ return config;
2277
+ }
2278
+ async #check(project, drafts) {
2279
+ const config = await this.#configure(project);
2280
+ const scratch = this.#scratch(this.#contain(project), config, drafts);
2281
+ const execution = await this.#spawn([
2282
+ "--noEmit",
2283
+ "--pretty",
2284
+ "false",
2285
+ "-p",
2286
+ `./${scratch}`
2287
+ ], this.#mirror);
2288
+ this.#refuseDestroyed();
2289
+ const diagnostics = scanDiagnostics(execution.stdout);
2290
+ if (diagnostics.length === 0 && execution.status !== 0) {
2291
+ const stderr = execution.stderr.trim();
2292
+ const message = stderr !== "" ? stderr : execution.status === void 0 ? "The compiler reported no diagnostic and was ended by a signal" : `The compiler reported no diagnostic and exited ${execution.status}`;
2293
+ throw new _src_core.ProbeError(this.#translate(message), {
2294
+ origin: "instrument",
2295
+ code: "malformed",
2051
2296
  context: {
2052
2297
  stage: this.stage,
2053
2298
  project
2054
2299
  }
2055
2300
  });
2056
- return {
2057
- origin: "workspace",
2058
- path: project,
2059
- message
2060
- };
2061
2301
  }
2062
- const path = relativeWorkspaceFile(this.#workspace, diagnostic.file.fileName);
2063
- if (diagnostic.start === void 0) return {
2064
- origin: "claimant",
2065
- path,
2066
- message
2302
+ return this.#issues(diagnostics, project);
2303
+ }
2304
+ #scratch(project, config, drafts) {
2305
+ const directory = (0, node_path.dirname)(project);
2306
+ const stem = `${(0, node_path.basename)(project, (0, node_path.extname)(project))}.probe.json`;
2307
+ const contained = directory === "." ? stem : `${directory}/${stem}`;
2308
+ const target = this.#mirrorPath(contained);
2309
+ const buildinfo = (0, node_path.join)(this.#mirror, ".probe", `${project.replaceAll("/", "-")}.tsbuildinfo`);
2310
+ const files = [...(config.files ?? []).filter((entry) => (0, node_fs.existsSync)((0, node_path.resolve)((0, node_path.dirname)(target), entry))), ...drafts.map((draft) => relativeWorkspaceFile((0, node_path.dirname)(target), this.#mirrorPath(this.#contain(draft.path))))];
2311
+ const body = {
2312
+ extends: `./${(0, node_path.basename)(project)}`,
2313
+ compilerOptions: {
2314
+ noEmit: true,
2315
+ declaration: false,
2316
+ emitDeclarationOnly: false,
2317
+ composite: false,
2318
+ incremental: true,
2319
+ tsBuildInfoFile: relativeWorkspaceFile((0, node_path.dirname)(target), buildinfo)
2320
+ },
2321
+ ...files.length === 0 ? {} : { files }
2067
2322
  };
2068
- return {
2069
- origin: "claimant",
2070
- path,
2071
- message,
2072
- range: {
2073
- start: diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start),
2074
- end: diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start + (diagnostic.length ?? 0))
2323
+ (0, node_fs.mkdirSync)((0, node_path.dirname)(target), { recursive: true });
2324
+ (0, node_fs.writeFileSync)(target, `${JSON.stringify(body, void 0, " ")}\n`, "utf8");
2325
+ return contained;
2326
+ }
2327
+ #issues(diagnostics, project) {
2328
+ const issues = [];
2329
+ for (const diagnostic of diagnostics) {
2330
+ const message = this.#translate(diagnostic.message);
2331
+ const resolved = diagnostic.path === void 0 ? void 0 : (0, node_path.resolve)(this.#mirror, diagnostic.path);
2332
+ const drafted = resolved !== void 0 && this.#drafts.has(escapesRoot(this.#mirror, resolved) ? relativeWorkspaceFile(this.#workspace, resolved) : relativeWorkspaceFile(this.#mirror, resolved));
2333
+ if (resolved === void 0 || !drafted && (0, node_path.extname)(resolved) === ".json") throw new _src_core.ProbeError(message, {
2334
+ origin: "workspace",
2335
+ code: "malformed",
2336
+ context: {
2337
+ stage: this.stage,
2338
+ project
2339
+ }
2340
+ });
2341
+ const path = escapesRoot(this.#mirror, resolved) ? escapesRoot(this.#workspace, resolved) ? normalizePath(resolved) : relativeWorkspaceFile(this.#workspace, resolved) : relativeWorkspaceFile(this.#mirror, resolved);
2342
+ issues.push({
2343
+ origin: "claimant",
2344
+ path,
2345
+ message,
2346
+ ...diagnostic.range === void 0 ? {} : { range: diagnostic.range }
2347
+ });
2348
+ }
2349
+ return issues;
2350
+ }
2351
+ #translate(message) {
2352
+ return relativeWorkspaceMessage(this.#workspace, relativeWorkspaceMessage(this.#mirror, message));
2353
+ }
2354
+ #fault(execution, project) {
2355
+ const reported = scanDiagnostics(execution.stdout).map((diagnostic) => diagnostic.message);
2356
+ const text = reported.length > 0 ? reported.join("\n") : execution.stdout.trim();
2357
+ return new _src_core.ProbeError(this.#translate(text === "" ? "The compiler printed no configuration" : text), {
2358
+ origin: "workspace",
2359
+ code: "malformed",
2360
+ context: {
2361
+ stage: this.stage,
2362
+ project
2075
2363
  }
2076
- };
2364
+ });
2365
+ }
2366
+ #spawn(args, cwd) {
2367
+ this.#refuseDestroyed();
2368
+ return new Promise((settle, refuse) => {
2369
+ const child = (0, node_child_process.spawn)(process.execPath, [this.#compiler, ...args], {
2370
+ cwd,
2371
+ stdio: [
2372
+ "ignore",
2373
+ "pipe",
2374
+ "pipe"
2375
+ ]
2376
+ });
2377
+ const output = child.stdout;
2378
+ const errors = child.stderr;
2379
+ if (output === null || errors === null) {
2380
+ this.#terminate(child);
2381
+ refuse(new _src_core.ProbeError("The compiler was spawned without its own output streams", {
2382
+ origin: "instrument",
2383
+ code: "malformed",
2384
+ context: { stage: this.stage }
2385
+ }));
2386
+ return;
2387
+ }
2388
+ this.#children.add(child);
2389
+ let stdout = "";
2390
+ let stderr = "";
2391
+ output.setEncoding("utf8");
2392
+ errors.setEncoding("utf8");
2393
+ output.on("data", (chunk) => {
2394
+ stdout += chunk;
2395
+ });
2396
+ errors.on("data", (chunk) => {
2397
+ stderr += chunk;
2398
+ });
2399
+ child.on("error", (error) => {
2400
+ this.#children.delete(child);
2401
+ refuse(new _src_core.ProbeError("The workspace compiler could not be started", {
2402
+ origin: "workspace",
2403
+ code: "malformed",
2404
+ context: {
2405
+ stage: this.stage,
2406
+ path: this.#compiler
2407
+ },
2408
+ cause: error
2409
+ }));
2410
+ });
2411
+ child.on("close", (code) => {
2412
+ this.#children.delete(child);
2413
+ settle({
2414
+ ...code === null ? {} : { status: code },
2415
+ stdout,
2416
+ stderr
2417
+ });
2418
+ });
2419
+ });
2420
+ }
2421
+ #terminate(child) {
2422
+ const id = child.pid;
2423
+ if (process.platform === "win32" && id !== void 0) {
2424
+ (0, node_child_process.spawnSync)("taskkill", [
2425
+ "/pid",
2426
+ String(id),
2427
+ "/t",
2428
+ "/f"
2429
+ ], { stdio: "ignore" });
2430
+ return;
2431
+ }
2432
+ child.kill("SIGTERM");
2077
2433
  }
2078
2434
  };
2079
2435
  //#endregion
2080
2436
  //#region src/server/Probe.ts
2081
2437
  /**
2082
- * Answers claims through resident TypeScript, Oxlint, and Vitest stages.
2438
+ * Answers claims through its type, lint, and runtime stages.
2083
2439
  *
2084
2440
  * @remarks
2085
2441
  * Construction resolves the target workspace's toolchain and begins warming every stage. The boot
@@ -2091,10 +2447,45 @@ var TypeStage = class {
2091
2447
  * claim costs that claim rather than the process. A failed boot is replaced the same way: the next
2092
2448
  * claim runs the controls again rather than inheriting a refusal.
2093
2449
  *
2094
- * @example
2450
+ * @example The claim that earns a receipt
2095
2451
  * ```ts
2096
- * const probe = new Probe({ workspace: '/srv/checkout' })
2452
+ * import type { Claim } from '@orkestrel/probe'
2453
+ * import { Probe } from '@orkestrel/probe/server'
2454
+ *
2455
+ * const claim: Claim = {
2456
+ * project: 'configs/src/tsconfig.core.json',
2457
+ * case: {
2458
+ * files: [
2459
+ * {
2460
+ * path: 'src/core/factories.ts',
2461
+ * text: "export function createGreeting(): string {\n\treturn 'hi'\n}\n",
2462
+ * },
2463
+ * ],
2464
+ * test: {
2465
+ * path: 'tmp/probe/greeting.test.ts',
2466
+ * text: "import { expect, test } from 'vitest'\nimport { createGreeting } from '../../src/core/factories.js'\ntest('greets', () => expect(createGreeting()).toBe('hi'))\n",
2467
+ * },
2468
+ * },
2469
+ * control: {
2470
+ * files: [
2471
+ * {
2472
+ * path: 'src/core/factories.ts',
2473
+ * text: "export function createGreeting(): number {\n\treturn 'hi'\n}\n",
2474
+ * },
2475
+ * ],
2476
+ * test: {
2477
+ * path: 'tmp/probe/greeting.test.ts',
2478
+ * text: "import { expect, test } from 'vitest'\nimport { createGreeting } from '../../src/core/factories.js'\ntest('greets', () => expect(createGreeting()).toBe('hi'))\n",
2479
+ * },
2480
+ * stage: 'type',
2481
+ * reason: 'a string returned as a number must not compile',
2482
+ * },
2483
+ * }
2484
+ *
2485
+ * const probe = new Probe({ workspace: process.cwd() })
2097
2486
  * const verdict = await probe.prove(claim)
2487
+ * verdict.digest // 'fcb88a2dee987b8673c1fc7107979470'
2488
+ * verdict.receipt // 'probe:fcb88a2dee987b8673c1fc7107979470:type:typescript@6.0.3:oxlint@1.83.0:vitest@4.1.11:configs/src/tsconfig.core.json@434f59254d58cf2683d453a26bd0d837'
2098
2489
  * await probe.destroy()
2099
2490
  * ```
2100
2491
  */
@@ -2115,7 +2506,7 @@ var Probe = class {
2115
2506
  #arming;
2116
2507
  #closing;
2117
2508
  /**
2118
- * Resolves the target toolchain and starts warming the resident stages.
2509
+ * Resolves the target toolchain and starts warming every stage.
2119
2510
  *
2120
2511
  * @param options - Workspace, deadline, and initial observation hooks
2121
2512
  */
@@ -2280,7 +2671,7 @@ var Probe = class {
2280
2671
  files: [],
2281
2672
  test: typeTest,
2282
2673
  stage: "type",
2283
- reason: "the imported type changed after the resident type host cached it"
2674
+ reason: "the imported type changed on disk between the two inspections"
2284
2675
  }
2285
2676
  };
2286
2677
  const runtimeClaim = {
@@ -2527,12 +2918,16 @@ var Probe = class {
2527
2918
  //#endregion
2528
2919
  //#region src/server/ProbeServer.ts
2529
2920
  /**
2530
- * Serves one probe over this process's Model Context Protocol stdio transport.
2921
+ * Implements `ProbeServerInterface` over a `PassThrough` stream this server owns, binding the
2922
+ * published `prove` tool and the dual-era dispatcher to this process's Model Context Protocol
2923
+ * stdio transport.
2531
2924
  *
2532
2925
  * @remarks
2533
- * Construction creates the probe, publishes the `prove` tool, and binds the dual-era dispatcher to
2534
- * the stdio transport. The probe begins warming there, so a harness that spawns the entry pays
2535
- * arming while its client is still handshaking.
2926
+ * Construction snapshots the probe options, publishes the `prove` tool, and binds the dual-era
2927
+ * dispatcher to the stdio transport. An admitted `prove` call creates the probe and begins
2928
+ * workspace arming, so discovery does not depend on the workspace toolchain. Construction is held
2929
+ * before it begins, so teardown entered through a construction callback waits for the resulting
2930
+ * probe and releases it. A constructor refusal clears that held operation for a later retry.
2536
2931
  *
2537
2932
  * `start` seizes standard input and registers the signals a harness ends a child with, and records
2538
2933
  * whether standard input was already flowing. Every listener it attaches is held as a field, and
@@ -2554,7 +2949,7 @@ var Probe = class {
2554
2949
  * ```
2555
2950
  */
2556
2951
  var ProbeServer = class {
2557
- #probe;
2952
+ #options;
2558
2953
  #stream;
2559
2954
  #transport;
2560
2955
  #data;
@@ -2563,14 +2958,25 @@ var ProbeServer = class {
2563
2958
  #signal;
2564
2959
  #server;
2565
2960
  #owns;
2961
+ #construction;
2566
2962
  #closing;
2567
2963
  /**
2568
- * Creates the probe this server publishes and binds it to this process's stdio transport.
2964
+ * Snapshots the probe options this server will use and binds the published schema to this
2965
+ * process's stdio transport. An admitted `prove` call creates the probe.
2569
2966
  *
2570
- * @param options - Workspace, deadline, and initial observation hooks for the probe it creates
2967
+ * @param options - Workspace, deadline, and initial observation hooks for the deferred probe
2571
2968
  */
2572
2969
  constructor(options) {
2573
- this.#probe = new Probe(options);
2970
+ const workspace = options?.workspace;
2971
+ const deadline = options?.deadline;
2972
+ const on = options?.on;
2973
+ const error = options?.error;
2974
+ this.#options = Object.freeze({
2975
+ workspace: (0, node_path.resolve)(workspace ?? process.cwd()),
2976
+ ...deadline === void 0 ? {} : { deadline },
2977
+ ...on === void 0 ? {} : { on: Object.freeze({ ...on }) },
2978
+ ...error === void 0 ? {} : { error }
2979
+ });
2574
2980
  this.#stream = new node_stream.PassThrough();
2575
2981
  this.#server = this.#publish();
2576
2982
  this.#transport = (0, _orkestrel_mcp_server.createStdioServer)((0, _orkestrel_mcp.createMCPLegacy)(this.#server), { input: this.#stream });
@@ -2605,7 +3011,10 @@ var ProbeServer = class {
2605
3011
  if (this.#owns === true && process.stdin.listenerCount("data") === 0) process.stdin.pause();
2606
3012
  this.#owns = void 0;
2607
3013
  this.#stream.destroy();
2608
- await this.#probe.destroy();
3014
+ const construction = this.#construction;
3015
+ if (construction === void 0) return;
3016
+ const probe = await construction.catch(() => void 0);
3017
+ if (probe !== void 0) await probe.destroy();
2609
3018
  }
2610
3019
  #receive(chunk) {
2611
3020
  this.#stream.write(chunk);
@@ -2651,7 +3060,20 @@ var ProbeServer = class {
2651
3060
  context: { value: input }
2652
3061
  });
2653
3062
  }
2654
- return this.#probe.prove(input);
3063
+ return (await this.#resolveProbe()).prove(input);
3064
+ }
3065
+ async #resolveProbe() {
3066
+ if (this.#closing !== void 0) throw (0, _src_core.createDestroyedError)("probe server");
3067
+ const current = this.#construction;
3068
+ if (current !== void 0) return await current;
3069
+ const construction = Promise.resolve().then(() => new Probe(this.#options));
3070
+ this.#construction = construction;
3071
+ try {
3072
+ return await construction;
3073
+ } catch (error) {
3074
+ if (this.#construction === construction) this.#construction = void 0;
3075
+ throw error;
3076
+ }
2655
3077
  }
2656
3078
  async #execute(context) {
2657
3079
  const result = await context.tools.execute(context.call);
@@ -2701,20 +3123,25 @@ exports.RuntimeStage = RuntimeStage;
2701
3123
  exports.TypeStage = TypeStage;
2702
3124
  exports.buildRevisionPath = buildRevisionPath;
2703
3125
  exports.captureListeners = captureListeners;
3126
+ exports.collectWorkspaceFiles = collectWorkspaceFiles;
2704
3127
  exports.computeDigest = computeDigest;
2705
3128
  exports.describeUnknown = describeUnknown;
2706
3129
  exports.escapesRoot = escapesRoot;
3130
+ exports.filterUniqueIssues = filterUniqueIssues;
2707
3131
  exports.findRefusedPaths = findRefusedPaths;
2708
3132
  exports.guardStage = guardStage;
2709
3133
  exports.inferDocumentLanguage = inferDocumentLanguage;
2710
3134
  exports.inferTestProject = inferTestProject;
2711
3135
  exports.inferTypeProject = inferTypeProject;
2712
3136
  exports.isRefusedName = isRefusedName;
2713
- exports.loadWorkspaceModule = loadWorkspaceModule;
3137
+ exports.loadWorkspaceVitest = loadWorkspaceVitest;
3138
+ exports.matchesLiveProcess = matchesLiveProcess;
2714
3139
  exports.matchesWorkspaceModule = matchesWorkspaceModule;
2715
3140
  exports.normalizePath = normalizePath;
2716
3141
  exports.normalizeValue = normalizeValue;
2717
3142
  exports.overwriteFile = overwriteFile;
3143
+ exports.parseProjectConfig = parseProjectConfig;
3144
+ exports.parseRevisionOwner = parseRevisionOwner;
2718
3145
  exports.readFaultCode = readFaultCode;
2719
3146
  exports.readWorkspaceManifest = readWorkspaceManifest;
2720
3147
  exports.relativeWorkspaceFile = relativeWorkspaceFile;
@@ -2723,5 +3150,6 @@ exports.releaseListeners = releaseListeners;
2723
3150
  exports.resolveWorkspaceBinary = resolveWorkspaceBinary;
2724
3151
  exports.resolveWorkspaceFile = resolveWorkspaceFile;
2725
3152
  exports.resolveWorkspaceModule = resolveWorkspaceModule;
3153
+ exports.scanDiagnostics = scanDiagnostics;
2726
3154
 
2727
3155
  //# sourceMappingURL=index.cjs.map