@systemfsoftware/stryker-js-cli 4.0.2 → 5.0.0

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.
package/dist/main.mjs CHANGED
@@ -1,15 +1,16 @@
1
1
  #!/usr/bin/env node
2
- import { createRequire } from "node:module";
2
+ import { n as nodePlatformLayer } from "./node-D-ynY_kk.mjs";
3
3
  import * as NodeFileSystem from "@effect/platform-node-shared/NodeFileSystem";
4
4
  import * as NodePath from "@effect/platform-node-shared/NodePath";
5
5
  import * as NodeRuntime from "@effect/platform-node/NodeRuntime";
6
6
  import * as NodeStdio from "@effect/platform-node/NodeStdio";
7
- import { ConfigFileUnreadableError, buildVerdictEnvelope, defaultOptions, generateRunId, makeRunLayer, readConfig, runMutationTest, strykerEngines, strykerVersion, toRelativeNormalizedFileName } from "@systemfsoftware/stryker-js-platform-node";
8
7
  import * as Effect from "effect/Effect";
9
8
  import * as Exit from "effect/Exit";
10
9
  import * as Layer from "effect/Layer";
11
10
  import * as Logger from "effect/Logger";
12
11
  import * as NodeChildProcessSpawner from "@effect/platform-node-shared/NodeChildProcessSpawner";
12
+ import { ConfigFileUnreadableError, buildVerdictEnvelope, defaultOptions, generateRunId, makeRunLayer, readConfig, runMutationTest, strykerVersion, toRelativeNormalizedFileName } from "@systemfsoftware/stryker-js-engine";
13
+ import { Mutant, causeText } from "@systemfsoftware/stryker-js/Mutant";
13
14
  import { Heartbeat, HelpRendered, ManifestRendered, RunEvents, RunFailed, RunStarted, VerdictReached } from "@systemfsoftware/stryker-js/Run";
14
15
  import { RENDERED_OPTION_DEFAULTS } from "@systemfsoftware/stryker-js/Schema";
15
16
  import * as Cause from "effect/Cause";
@@ -30,94 +31,21 @@ import * as CliError from "effect/unstable/cli/CliError";
30
31
  import * as Command from "effect/unstable/cli/Command";
31
32
  import * as Flag from "effect/unstable/cli/Flag";
32
33
  import * as GlobalFlag from "effect/unstable/cli/GlobalFlag";
33
- import { readFileSync } from "node:fs";
34
- import { resolve } from "node:path";
34
+ import { Cell, Workflow } from "@systemfsoftware/effect-cell-types";
35
35
  import { ExitClass, highestExitClass } from "@systemfsoftware/stryker-js/ExitClass";
36
- import { Mutant, causeText } from "@systemfsoftware/stryker-js/Mutant";
37
36
  import * as Clock from "effect/Clock";
38
37
  import * as Formatter from "effect/Formatter";
39
38
  import * as Predicate from "effect/Predicate";
40
- import { Cell, Workflow } from "@systemfsoftware/effect-cell-types";
41
39
  import * as Context from "effect/Context";
42
- import { pipe } from "effect/Function";
43
40
  import * as Stdio from "effect/Stdio";
44
41
  import * as Stream from "effect/Stream";
42
+ import "@systemfsoftware/stryker-js/Module";
45
43
  import { sha256 } from "@noble/hashes/sha256";
46
44
  import { bytesToHex, utf8ToBytes } from "@noble/hashes/utils";
47
- //#region src/RunOutcome.workflow.ts
48
- var RunOutcomeCommand = class extends S.TaggedClass()("RunOutcomeCommand", {
49
- succeeded: S.Boolean,
50
- signal: S.optional(S.Finite),
51
- interrupted: S.Boolean,
52
- helpErrorCount: S.optional(S.Finite),
53
- cliError: S.Boolean,
54
- unrecognized: S.optional(S.String),
55
- survivorsReason: S.optional(S.Literals(["no-report", "mismatch"])),
56
- survivorsDiagnostic: S.optional(S.String),
57
- schemaError: S.Boolean,
58
- successExitClass: S.optional(ExitClass),
59
- highestExitClass: S.optional(ExitClass),
60
- configDetail: S.optional(S.String),
61
- diagnostic: S.optional(S.String)
62
- }) {};
63
- const CONFIG_CODE$1 = 2;
64
- const classCode = (exitClass) => Match.value(exitClass).pipe(Match.when("VerdictFail", () => 1), Match.when("ConfigError", () => CONFIG_CODE$1), Match.when("RuntimeError", () => 3), Match.when("InternalError", () => 4), Match.exhaustive);
65
- var RunOk = class extends S.TaggedClass()("RunOk", { help: S.Boolean }) {};
66
- var RunInterrupted = class extends S.TaggedError()("RunInterrupted", { code: S.Finite }) {};
67
- var RunParseFailed = class extends S.TaggedError()("RunParseFailed", { unrecognized: S.optional(S.String) }) {};
68
- var RunSurvivorsRejected = class extends S.TaggedError()("RunSurvivorsRejected", {
69
- reason: S.Literals(["no-report", "mismatch"]),
70
- diagnostic: S.optional(S.String)
71
- }) {};
72
- var RunConfigFailed = class extends S.TaggedError()("RunConfigFailed", { detail: S.optional(S.String) }) {};
73
- var RunFailed$1 = class extends S.TaggedError()("RunFailed", {
74
- code: S.Finite,
75
- diagnostic: S.optional(S.String)
76
- }) {};
77
- function classify(command) {
78
- if (command.signal !== void 0) return RunInterrupted.make({ code: 128 + command.signal });
79
- if (command.succeeded) {
80
- if (command.successExitClass !== void 0) return RunFailed$1.make({
81
- code: classCode(command.successExitClass),
82
- diagnostic: command.diagnostic
83
- });
84
- return RunOk.make({ help: false });
85
- }
86
- if (command.interrupted) return RunInterrupted.make({ code: 1 });
87
- if (command.helpErrorCount !== void 0) {
88
- if (command.helpErrorCount > 0) return RunParseFailed.make({ unrecognized: command.unrecognized });
89
- return RunOk.make({ help: true });
90
- }
91
- if (command.cliError) return RunParseFailed.make({ unrecognized: command.unrecognized });
92
- if (command.survivorsReason !== void 0) return RunSurvivorsRejected.make({
93
- reason: command.survivorsReason,
94
- diagnostic: command.survivorsDiagnostic
95
- });
96
- if (command.schemaError) return RunConfigFailed.make({ detail: command.configDetail });
97
- if (command.highestExitClass !== void 0) {
98
- if (command.highestExitClass === "ConfigError") return RunConfigFailed.make({ detail: command.configDetail });
99
- return RunFailed$1.make({
100
- code: classCode(command.highestExitClass),
101
- diagnostic: command.diagnostic
102
- });
103
- }
104
- return RunFailed$1.make({
105
- code: 1,
106
- diagnostic: command.diagnostic
107
- });
108
- }
109
- function succeedRun(ok) {
110
- return Result.succeed(ok);
111
- }
112
- function failRun(error) {
113
- return Result.fail(error);
114
- }
115
- function runOutcomeDecision(command) {
116
- return Match.value(classify(command)).pipe(Match.tag("RunOk", succeedRun), Match.tag("RunInterrupted", failRun), Match.tag("RunParseFailed", failRun), Match.tag("RunSurvivorsRejected", failRun), Match.tag("RunConfigFailed", failRun), Match.tag("RunFailed", failRun), Match.exhaustive);
117
- }
118
- const runOutcomeWorkflow = Workflow.make(RunOutcomeCommand, runOutcomeDecision);
45
+ //#region package.json
46
+ var engines = { "node": ">=20.0.0" };
119
47
  //#endregion
120
- //#region src/Survivors.workflow.ts
48
+ //#region src/admit-survivors-run.workflow.ts
121
49
  /**
122
50
  * The mutant shape the admission carries, named once because both the decision's
123
51
  * `Admitted` payload and the command's precomputed survivor list are the same shape.
@@ -317,20 +245,91 @@ function admissionDecision(command) {
317
245
  }
318
246
  const admitSurvivorsRun = Workflow.make(AdmitSurvivorsRunCommand, admissionDecision);
319
247
  //#endregion
248
+ //#region src/classify-run-outcome.workflow.ts
249
+ var RunOutcomeCommand = class extends S.TaggedClass()("RunOutcomeCommand", {
250
+ succeeded: S.Boolean,
251
+ signal: S.optional(S.Finite),
252
+ interrupted: S.Boolean,
253
+ helpErrorCount: S.optional(S.Finite),
254
+ cliError: S.Boolean,
255
+ unrecognized: S.optional(S.String),
256
+ survivorsReason: S.optional(S.Literals(["no-report", "mismatch"])),
257
+ survivorsDiagnostic: S.optional(S.String),
258
+ schemaError: S.Boolean,
259
+ successExitClass: S.optional(ExitClass),
260
+ highestExitClass: S.optional(ExitClass),
261
+ configDetail: S.optional(S.String),
262
+ diagnostic: S.optional(S.String)
263
+ }) {};
264
+ const CONFIG_CODE$1 = 2;
265
+ const classCode = (exitClass) => Match.value(exitClass).pipe(Match.when("VerdictFail", () => 1), Match.when("ConfigError", () => CONFIG_CODE$1), Match.when("RuntimeError", () => 3), Match.when("InternalError", () => 4), Match.exhaustive);
266
+ const RunOutcomeTypeId = Symbol.for("@systemfsoftware/stryker-js-cli/RunOutcome");
267
+ var RunOk = class extends S.TaggedClass()("RunOk", { help: S.Boolean }) {
268
+ [RunOutcomeTypeId] = RunOutcomeTypeId;
269
+ };
270
+ var RunInterrupted = class extends S.TaggedError()("RunInterrupted", { code: S.Finite }) {
271
+ [RunOutcomeTypeId] = RunOutcomeTypeId;
272
+ };
273
+ var RunParseFailed = class extends S.TaggedClass()("RunParseFailed", { unrecognized: S.optional(S.String) }) {
274
+ [RunOutcomeTypeId] = RunOutcomeTypeId;
275
+ };
276
+ var RunSurvivorsRejected = class extends S.TaggedClass()("RunSurvivorsRejected", {
277
+ reason: S.Literals(["no-report", "mismatch"]),
278
+ diagnostic: S.optional(S.String)
279
+ }) {
280
+ [RunOutcomeTypeId] = RunOutcomeTypeId;
281
+ };
282
+ var RunConfigFailed = class extends S.TaggedClass()("RunConfigFailed", { detail: S.optional(S.String) }) {
283
+ [RunOutcomeTypeId] = RunOutcomeTypeId;
284
+ };
285
+ var RunFailed$1 = class extends S.TaggedClass()("RunFailed", {
286
+ code: S.Finite,
287
+ diagnostic: S.optional(S.String)
288
+ }) {
289
+ [RunOutcomeTypeId] = RunOutcomeTypeId;
290
+ };
291
+ function classify(command) {
292
+ if (command.signal !== void 0) return RunInterrupted.make({ code: 128 + command.signal });
293
+ if (command.succeeded) {
294
+ if (command.successExitClass !== void 0) return RunFailed$1.make({
295
+ code: classCode(command.successExitClass),
296
+ diagnostic: command.diagnostic
297
+ });
298
+ return RunOk.make({ help: false });
299
+ }
300
+ if (command.interrupted) return RunInterrupted.make({ code: 1 });
301
+ if (command.helpErrorCount !== void 0) {
302
+ if (command.helpErrorCount > 0) return RunParseFailed.make({ unrecognized: command.unrecognized });
303
+ return RunOk.make({ help: true });
304
+ }
305
+ if (command.cliError) return RunParseFailed.make({ unrecognized: command.unrecognized });
306
+ if (command.survivorsReason !== void 0) return RunSurvivorsRejected.make({
307
+ reason: command.survivorsReason,
308
+ diagnostic: command.survivorsDiagnostic
309
+ });
310
+ if (command.schemaError) return RunConfigFailed.make({ detail: command.configDetail });
311
+ if (command.highestExitClass !== void 0) {
312
+ if (command.highestExitClass === "ConfigError") return RunConfigFailed.make({ detail: command.configDetail });
313
+ return RunFailed$1.make({
314
+ code: classCode(command.highestExitClass),
315
+ diagnostic: command.diagnostic
316
+ });
317
+ }
318
+ return RunFailed$1.make({
319
+ code: 1,
320
+ diagnostic: command.diagnostic
321
+ });
322
+ }
323
+ const classifyRunOutcome$1 = Workflow.make(RunOutcomeCommand, (command) => Match.value(classify(command)).pipe(Match.tag("RunInterrupted", (error) => Result.fail(error)), Match.when((outcome) => !(outcome instanceof RunInterrupted), (decision) => Result.succeed(decision)), Match.exhaustive));
324
+ //#endregion
320
325
  //#region src/Envelope.ts
321
- /**
322
- * Envelope — the failure envelope and console capture leaf.
323
- *
324
- * Extracted from Cli.ts to break the import cycle Cli <-> Output.
325
- * Both Cli and Output import from this leaf, so neither depends on the other
326
- * for these values. This file imports only from external packages and from
327
- * StreamVersion (leaf) and Survivors.workflow (leaf), never from Cli or Output
328
- * themselves.
329
- */
330
326
  const CONFIG_CODE = 2;
331
327
  function runOutcomeCode(result) {
332
- if (Result.isSuccess(result)) return 0;
333
- return Match.value(result.failure).pipe(Match.tag("RunInterrupted", (error) => error.code), Match.tag("RunParseFailed", () => CONFIG_CODE), Match.tag("RunSurvivorsRejected", () => CONFIG_CODE), Match.tag("RunConfigFailed", () => CONFIG_CODE), Match.tag("RunFailed", (error) => error.code), Match.exhaustive);
328
+ const outcome = Result.match(result, {
329
+ onSuccess: (success) => success,
330
+ onFailure: (failure) => failure
331
+ });
332
+ return Match.value(outcome).pipe(Match.tag("RunOk", () => 0), Match.tag("RunInterrupted", (error) => error.code), Match.tag("RunParseFailed", () => CONFIG_CODE), Match.tag("RunSurvivorsRejected", () => CONFIG_CODE), Match.tag("RunConfigFailed", () => CONFIG_CODE), Match.tag("RunFailed", (failed) => failed.code), Match.exhaustive);
334
333
  }
335
334
  function isExitClass(value) {
336
335
  return S.is(ExitClass)(value);
@@ -577,7 +576,7 @@ function shapeEnvelope(error, captured) {
577
576
  };
578
577
  }
579
578
  function classifyRunOutcome(exit, signal, argv) {
580
- return runOutcomeWorkflow(gatherRunOutcome(exit, signal, argv));
579
+ return classifyRunOutcome$1(gatherRunOutcome(exit, signal, argv));
581
580
  }
582
581
  const capturedConsoleChunks = [];
583
582
  const countByLabel = /* @__PURE__ */ new Map();
@@ -684,12 +683,8 @@ function resetCapturedConsole() {
684
683
  timeByLabel.clear();
685
684
  }
686
685
  //#endregion
687
- //#region src/Output.workflow.ts
686
+ //#region src/resolve-output-mode.workflow.ts
688
687
  const TOOL_VARIABLES$1 = ["CLAUDECODE", "CODEX_SANDBOX"];
689
- /**
690
- * The command of the output-mode workflow: a schema class, because `Workflow.make`
691
- * derives the command type from it and pins the error channel at the construction site.
692
- */
693
688
  var ResolveModeCommand = class extends S.TaggedClass()("ResolveModeCommand", {
694
689
  stdoutIsTTY: S.Boolean,
695
690
  text: S.optional(S.Boolean),
@@ -698,17 +693,33 @@ var ResolveModeCommand = class extends S.TaggedClass()("ResolveModeCommand", {
698
693
  agent: S.optional(S.String),
699
694
  toolVars: S.optional(S.Record(S.String, S.String))
700
695
  }) {};
701
- /**
702
- * The conflict error for mutually exclusive format flags. Defined locally so the
703
- * workflow decision remains pure — the sealed effect surface does not include
704
- * `effect/unstable/cli/CliError`, so the decision returns this local error and
705
- * the shell maps it to `CliError.InvalidValue` at the boundary.
706
- */
696
+ const ResolveModeTypeId = Symbol.for("@systemfsoftware/stryker-js-cli/ResolveMode");
707
697
  var ModeConflictError = class extends S.TaggedError()("ModeConflictError", {
708
698
  option: S.String,
709
699
  value: S.String,
710
700
  expected: S.String
711
- }) {};
701
+ }) {
702
+ [ResolveModeTypeId] = ResolveModeTypeId;
703
+ };
704
+ const ModeSignal = S.Literals([
705
+ "flag",
706
+ "env",
707
+ "tty",
708
+ "agent",
709
+ "tool"
710
+ ]);
711
+ var HumanOutput = class extends S.TaggedClass()("HumanOutput", {
712
+ signal: ModeSignal,
713
+ stdoutIsTTY: S.Boolean
714
+ }) {
715
+ [ResolveModeTypeId] = ResolveModeTypeId;
716
+ };
717
+ var MachineOutput = class extends S.TaggedClass()("MachineOutput", {
718
+ signal: ModeSignal,
719
+ stdoutIsTTY: S.Boolean
720
+ }) {
721
+ [ResolveModeTypeId] = ResolveModeTypeId;
722
+ };
712
723
  const CONFLICT_EXPECTED = "the \"--format text\" and \"--json\" flags are mutually exclusive — use one or the other";
713
724
  function r4(command) {
714
725
  if (command.text === true && command.json === true) return Result.fail(ModeConflictError.make({
@@ -716,71 +727,51 @@ function r4(command) {
716
727
  value: "text",
717
728
  expected: CONFLICT_EXPECTED
718
729
  }));
719
- if (command.text === true) return Result.succeed({
720
- mode: "human",
730
+ if (command.text === true) return Result.succeed(HumanOutput.make({
721
731
  signal: "flag",
722
732
  stdoutIsTTY: command.stdoutIsTTY
723
- });
724
- if (command.json === true) return Result.succeed({
725
- mode: "machine",
733
+ }));
734
+ if (command.json === true) return Result.succeed(MachineOutput.make({
726
735
  signal: "flag",
727
736
  stdoutIsTTY: command.stdoutIsTTY
728
- });
737
+ }));
729
738
  if (command.envMode !== void 0 && command.envMode.length > 0) {
730
- if (command.envMode === "machine") return Result.succeed({
731
- mode: "machine",
739
+ if (command.envMode === "machine") return Result.succeed(MachineOutput.make({
732
740
  signal: "env",
733
741
  stdoutIsTTY: command.stdoutIsTTY
734
- });
735
- return Result.succeed({
736
- mode: "human",
742
+ }));
743
+ return Result.succeed(HumanOutput.make({
737
744
  signal: "env",
738
745
  stdoutIsTTY: command.stdoutIsTTY
739
- });
746
+ }));
740
747
  }
741
- if (!command.stdoutIsTTY) return Result.succeed({
742
- mode: "machine",
748
+ if (!command.stdoutIsTTY) return Result.succeed(MachineOutput.make({
743
749
  signal: "tty",
744
750
  stdoutIsTTY: false
745
- });
746
- if (command.agent !== void 0 && command.agent.length > 0) return Result.succeed({
747
- mode: "machine",
751
+ }));
752
+ if (command.agent !== void 0 && command.agent.length > 0) return Result.succeed(MachineOutput.make({
748
753
  signal: "agent",
749
754
  stdoutIsTTY: true
750
- });
755
+ }));
751
756
  const toolVars = command.toolVars ?? {};
752
757
  for (const variable of TOOL_VARIABLES$1) {
753
758
  const value = toolVars[variable];
754
- if (typeof value === "string" && value.length > 0) return Result.succeed({
755
- mode: "machine",
759
+ if (typeof value === "string" && value.length > 0) return Result.succeed(MachineOutput.make({
756
760
  signal: "tool",
757
761
  stdoutIsTTY: true
758
- });
762
+ }));
759
763
  }
760
- return Result.succeed({
761
- mode: "human",
764
+ return Result.succeed(HumanOutput.make({
762
765
  signal: "tty",
763
766
  stdoutIsTTY: true
764
- });
767
+ }));
765
768
  }
766
769
  function modeDecision(command) {
767
770
  return r4(command);
768
771
  }
769
- const resolveModeWorkflow = Workflow.make(ResolveModeCommand, modeDecision);
772
+ const resolveOutputMode = Workflow.make(ResolveModeCommand, modeDecision);
770
773
  //#endregion
771
774
  //#region src/Output.ts
772
- /**
773
- * Output — the machine/human output capability.
774
- *
775
- * The NDJSON run-event stream, wire protocol constants, mode resolution probes,
776
- * and machine-mode terminal output. Pure mode resolution lives in
777
- * Output.workflow.ts.
778
- */
779
- /**
780
- * The heartbeat interval (R19), matching Terraform's `apply_progress`
781
- * cadence: long enough that a slow phase is not noisy, short enough that a
782
- * consumer can tell "slow" from "hung" without waiting for a mutant event.
783
- */
784
775
  const TICK_INTERVAL_MS = 1e4;
785
776
  var RunEventStreamPortTag = class extends Context.Service()("@systemfsoftware/stryker-js-cli/Output/RunEventStreamPortTag") {};
786
777
  const RunEventStreamPort = RunEventStreamPortTag;
@@ -892,12 +883,15 @@ const makeRunEventStream = (stdio, resolved, drainFramed = drainOf.bind(null, st
892
883
  });
893
884
  Layer.effect(RunEventStreamPort, Effect.map(Stdio.Stdio, (stdio) => RunEventStreamPort.of({ createRunEventStream: (resolved) => makeRunEventStream(stdio, resolved) })));
894
885
  const TOOL_VARIABLES = ["CLAUDECODE", "CODEX_SANDBOX"];
895
- /**
896
- * The log colouriser's gate (R8). Machine mode never emits colour, so a
897
- * harness merging `2>&1` is not handed escape sequences it must strip, and
898
- * `NO_COLOR` is honoured for the human path per the convention: any value
899
- * other than an unset or empty variable disables colour.
900
- */
886
+ const decisionToResolvedMode = (decision) => Match.value(decision).pipe(Match.tag("HumanOutput", (human) => ({
887
+ mode: "human",
888
+ signal: human.signal,
889
+ stdoutIsTTY: human.stdoutIsTTY
890
+ })), Match.tag("MachineOutput", (machine) => ({
891
+ mode: "machine",
892
+ signal: machine.signal,
893
+ stdoutIsTTY: machine.stdoutIsTTY
894
+ })), Match.exhaustive);
901
895
  function isColorEnabled(resolved, noColor) {
902
896
  if (resolved.mode !== "human") return false;
903
897
  if (noColor === void 0) return true;
@@ -906,83 +900,80 @@ function isColorEnabled(resolved, noColor) {
906
900
  }
907
901
  var OutputModeProbeTag = class extends Context.Service()("@systemfsoftware/stryker-js-cli/Output/OutputModeProbeTag") {};
908
902
  const OutputModeProbe = OutputModeProbeTag;
909
- const outputModeProbeDescription = pipe(Cell.read((command) => Effect.succeed((() => {
910
- const toolVarsRecord = {};
911
- for (const variable of TOOL_VARIABLES) {
912
- const value = process.env[variable];
913
- if (value !== void 0) toolVarsRecord[variable] = value;
914
- }
915
- const envMode = process.env["STRYKER_MODE"];
916
- const agent = process.env["AGENT"];
917
- let result = { stdoutIsTTY: process.stdout.isTTY === true };
918
- if (Object.keys(toolVarsRecord).length > 0) result = {
919
- ...result,
920
- toolVars: toolVarsRecord
921
- };
922
- if (envMode !== void 0) result = {
923
- ...result,
924
- envMode
925
- };
926
- if (agent !== void 0) result = {
927
- ...result,
928
- agent
929
- };
930
- if (command.text !== void 0) result = {
931
- ...result,
932
- text: command.text
933
- };
934
- if (command.json !== void 0) result = {
935
- ...result,
936
- json: command.json
937
- };
938
- return result;
939
- })())), Cell.decode((raw) => Result.succeed((() => {
940
- const filteredToolVars = {};
941
- if (raw.toolVars !== void 0) {
942
- for (const [key, value] of Object.entries(raw.toolVars)) if (value !== void 0) filteredToolVars[key] = value;
943
- }
944
- let commandInput = { stdoutIsTTY: raw.stdoutIsTTY };
945
- if (raw.text !== void 0) commandInput = {
946
- ...commandInput,
947
- text: raw.text
948
- };
949
- if (raw.json !== void 0) commandInput = {
950
- ...commandInput,
951
- json: raw.json
952
- };
953
- if (raw.envMode !== void 0) commandInput = {
954
- ...commandInput,
955
- envMode: raw.envMode
956
- };
957
- if (raw.agent !== void 0) commandInput = {
958
- ...commandInput,
959
- agent: raw.agent
960
- };
961
- if (Object.keys(filteredToolVars).length > 0) commandInput = {
962
- ...commandInput,
963
- toolVars: filteredToolVars
964
- };
965
- return ResolveModeCommand.make(commandInput);
966
- })())), Cell.decide(resolveModeWorkflow), Cell.encode((outcome) => outcome), Cell.write((outcome) => Result.match(outcome, {
967
- onFailure: (error) => Effect.fail(error),
968
- onSuccess: (mode) => Effect.succeed(mode)
969
- })));
970
- const detectModeWithProbe = (flags = {}) => Cell.apply(outputModeProbeDescription, flags).pipe(Effect.mapError((error) => CliError.InvalidValue.make({
903
+ const outputModeProbeCell = Cell.layer({
904
+ read: (command) => Effect.succeed((() => {
905
+ const toolVarsRecord = {};
906
+ for (const variable of TOOL_VARIABLES) {
907
+ const value = process.env[variable];
908
+ if (value !== void 0) toolVarsRecord[variable] = value;
909
+ }
910
+ const envMode = process.env["STRYKER_MODE"];
911
+ const agent = process.env["AGENT"];
912
+ let result = { stdoutIsTTY: process.stdout.isTTY === true };
913
+ if (Object.keys(toolVarsRecord).length > 0) result = {
914
+ ...result,
915
+ toolVars: toolVarsRecord
916
+ };
917
+ if (envMode !== void 0) result = {
918
+ ...result,
919
+ envMode
920
+ };
921
+ if (agent !== void 0) result = {
922
+ ...result,
923
+ agent
924
+ };
925
+ if (command.text !== void 0) result = {
926
+ ...result,
927
+ text: command.text
928
+ };
929
+ if (command.json !== void 0) result = {
930
+ ...result,
931
+ json: command.json
932
+ };
933
+ return result;
934
+ })()),
935
+ decode: (raw) => Result.succeed((() => {
936
+ const filteredToolVars = {};
937
+ if (raw.toolVars !== void 0) {
938
+ for (const [key, value] of Object.entries(raw.toolVars)) if (value !== void 0) filteredToolVars[key] = value;
939
+ }
940
+ let commandInput = { stdoutIsTTY: raw.stdoutIsTTY };
941
+ if (raw.text !== void 0) commandInput = {
942
+ ...commandInput,
943
+ text: raw.text
944
+ };
945
+ if (raw.json !== void 0) commandInput = {
946
+ ...commandInput,
947
+ json: raw.json
948
+ };
949
+ if (raw.envMode !== void 0) commandInput = {
950
+ ...commandInput,
951
+ envMode: raw.envMode
952
+ };
953
+ if (raw.agent !== void 0) commandInput = {
954
+ ...commandInput,
955
+ agent: raw.agent
956
+ };
957
+ if (Object.keys(filteredToolVars).length > 0) commandInput = {
958
+ ...commandInput,
959
+ toolVars: filteredToolVars
960
+ };
961
+ return ResolveModeCommand.make(commandInput);
962
+ })()),
963
+ decide: resolveOutputMode,
964
+ encode: (outcome) => Result.map(outcome, decisionToResolvedMode),
965
+ write: (outcome) => Result.match(outcome, {
966
+ onFailure: (error) => Effect.fail(error),
967
+ onSuccess: (mode) => Effect.succeed(mode)
968
+ })
969
+ });
970
+ const detectModeWithProbe = (flags = {}) => Cell.run(outputModeProbeCell, flags).pipe(Effect.mapError((error) => CliError.InvalidValue.make({
971
971
  option: error.option,
972
972
  value: error.value,
973
973
  expected: error.expected,
974
974
  kind: "flag"
975
975
  })));
976
976
  const OutputModeProbeLive = Layer.succeed(OutputModeProbe, OutputModeProbe.of({ detectMode: detectModeWithProbe({}) }));
977
- /**
978
- * Machine mode emits the U4 verdict envelope for a run that produced no
979
- * mutants and no report file: a `--survivors` run with zero survivors (AE3)
980
- * or a successful `--dryRunOnly` run that ended before the mutation
981
- * pipeline. The envelope carries a null score and an empty mutant list and is
982
- * written as the terminal `verdict` line of the stdout stream (U6), carrying
983
- * the run id the stream header already opened with (KTD11 — never a fresh
984
- * id). Human mode prints nothing (the sink drops in human mode).
985
- */
986
977
  function emitNullScoreVerdict(stream, mode, thresholds, config, basePath, pathService) {
987
978
  const envelope = buildVerdictEnvelope({
988
979
  schemaVersion: "1.0",
@@ -1007,49 +998,41 @@ function emitNullScoreVerdict(stream, mode, thresholds, config, basePath, pathSe
1007
998
  mutants: envelope.mutants
1008
999
  }));
1009
1000
  }
1010
- /**
1011
- * Emits the machine-mode output from the run's finalizer — it runs on
1012
- * success, failure and interruption alike (R30): a failed run writes the
1013
- * `error` terminal event as the last line of the stdout stream; a successful
1014
- * run whose only console output was the framework's help/version rendering
1015
- * emits that captured document as the `help` terminal event, so `--help` in
1016
- * machine mode never leaks an ANSI document. A successful run with an empty
1017
- * buffer (the normal verdict path) emits nothing extra — the run already
1018
- * wrote its terminal `verdict` line through the same module — unless the
1019
- * stream is still open, which means the run never reached a verdict (the
1020
- * `--dryRunOnly` early return): then a null-score `verdict` closes the
1021
- * stream so the last stdout line is always a terminal event (R5).
1022
- */
1001
+ function offerFailureEnvelope(stream, failed, captured) {
1002
+ const envelope = shapeEnvelope(failed, captured);
1003
+ return Queue.offer(stream.queue, RunFailed.make({
1004
+ schemaVersion: envelope.schemaVersion,
1005
+ code: envelope.code,
1006
+ error: envelope.error,
1007
+ remediation: envelope.remediation
1008
+ }));
1009
+ }
1023
1010
  function emitMachineModeOutput(stream, mode, outcome, basePath, pathService) {
1024
1011
  return Effect.gen(function* () {
1025
1012
  const captured = readCapturedConsole();
1026
1013
  if (Result.isSuccess(outcome)) {
1027
- if (outcome.success.help) {
1028
- yield* Queue.offer(stream.queue, HelpRendered.make({
1029
- schemaVersion: "1.0",
1030
- code: 0,
1031
- help: captured
1032
- }));
1033
- return;
1034
- }
1035
- if (captured.length > 0) {
1036
- yield* Queue.offer(stream.queue, HelpRendered.make({
1037
- schemaVersion: "1.0",
1038
- code: 0,
1039
- help: captured
1040
- }));
1041
- return;
1042
- }
1043
- if (stream.isOpen()) yield* emitNullScoreVerdict(stream, mode, (yield* defaultOptions).thresholds, {}, basePath, pathService);
1044
- return;
1014
+ const decision = outcome.success;
1015
+ return yield* Match.value(decision).pipe(Match.tag("RunOk", (ok) => Effect.gen(function* () {
1016
+ if (ok.help) {
1017
+ yield* Queue.offer(stream.queue, HelpRendered.make({
1018
+ schemaVersion: "1.0",
1019
+ code: 0,
1020
+ help: captured
1021
+ }));
1022
+ return;
1023
+ }
1024
+ if (captured.length > 0) {
1025
+ yield* Queue.offer(stream.queue, HelpRendered.make({
1026
+ schemaVersion: "1.0",
1027
+ code: 0,
1028
+ help: captured
1029
+ }));
1030
+ return;
1031
+ }
1032
+ if (stream.isOpen()) yield* emitNullScoreVerdict(stream, mode, (yield* defaultOptions).thresholds, {}, basePath, pathService);
1033
+ })), Match.tag("RunParseFailed", (failed) => offerFailureEnvelope(stream, failed, captured)), Match.tag("RunSurvivorsRejected", (failed) => offerFailureEnvelope(stream, failed, captured)), Match.tag("RunConfigFailed", (failed) => offerFailureEnvelope(stream, failed, captured)), Match.tag("RunFailed", (failed) => offerFailureEnvelope(stream, failed, captured)), Match.exhaustive);
1045
1034
  }
1046
- const envelope = shapeEnvelope(outcome.failure, captured);
1047
- yield* Queue.offer(stream.queue, RunFailed.make({
1048
- schemaVersion: envelope.schemaVersion,
1049
- code: envelope.code,
1050
- error: envelope.error,
1051
- remediation: envelope.remediation
1052
- }));
1035
+ return yield* offerFailureEnvelope(stream, outcome.failure, captured);
1053
1036
  });
1054
1037
  }
1055
1038
  //#endregion
@@ -1088,7 +1071,7 @@ const RunEventStreamFileLive = Layer.effect(RunEventStreamPort, Effect.gen(funct
1088
1071
  *
1089
1072
  * The prior-report decoding, source hashing, mutant conversion, and admission
1090
1073
  * pipeline for --survivors runs. Pure admission decision lives in
1091
- * Survivors.workflow.ts.
1074
+ * admit-survivors-run.workflow.ts.
1092
1075
  */
1093
1076
  const DEFAULT_SURVIVORS_PRIOR_REPORT = "reports/mutation-report.json";
1094
1077
  /**
@@ -1184,87 +1167,57 @@ function survivorMutateSpans(survivors, basePath) {
1184
1167
  return spans;
1185
1168
  }
1186
1169
  const hashContent = (content) => bytesToHex(sha256(utf8ToBytes(content)));
1187
- const resolveAbsolutePath = (file) => resolve(file);
1188
- /**
1189
- * The survivors admission, as a description whose phases chain by type and
1190
- * read in the order they run. The read gathers the admission's whole input
1191
- * product resolved options, prior report and the current source hashes —
1192
- * across its interior and stashes the shell context the write dispatches on
1193
- * into the executor-owned `runContext` ref; `decode` packages exactly the
1194
- * workflow input; `admitSurvivorsRun` is the decide phase; `encode` is the
1195
- * identity because write receives the outcome as-is; the write reads the
1196
- * stashed context back and dispatches the decision to the verdict/run,
1197
- * failing the run with a rejection.
1198
- */
1199
- const survivorsAdmissionDescription = (runMutationTest, stream, mode, runContext, basePath, services) => pipe(Cell.read((cliOptions) => Effect.provideContext(Effect.flatMap(Path.Path, (pathService) => resolveSurvivorsRunOptions(cliOptions, basePath).pipe(Effect.flatMap((resolvedOptions) => {
1200
- const priorReportPath = priorReportPathOf(resolvedOptions);
1201
- return Effect.flatMap(readPriorReport(priorReportPath), (read) => Effect.flatMap(currentSourceHashesFor(priorReportFileKeys(read.raw)), (sourceContentHashes) => Ref.set(runContext, {
1202
- resolvedOptions,
1203
- priorReportPath,
1204
- pathService
1205
- }).pipe(Effect.as({
1206
- resolvedOptions,
1207
- priorReportRaw: read.raw,
1208
- priorReportFound: read.found,
1209
- priorReportPath,
1210
- sourceContentHashes
1211
- }))));
1212
- }))), services)), Cell.decode(({ resolvedOptions, priorReportRaw, priorReportFound, sourceContentHashes }) => {
1213
- if (!priorReportFound) return Result.succeed(AdmitSurvivorsRunCommand.make({
1214
- priorReport: void 0,
1215
- currentConfig: resolvedOptions,
1216
- frameworkVersion: strykerVersion,
1217
- sourceContentHashes,
1218
- priorSourceHashes: {},
1219
- priorSurvivors: []
1220
- }));
1221
- return Result.map(decodePriorReport(priorReportRaw), (document) => AdmitSurvivorsRunCommand.make({
1222
- priorReport: PriorReportFacts.make({
1223
- config: document.config ?? {},
1224
- frameworkVersion: document.framework?.version
1170
+ const survivorsAdmissionCell = (basePath) => Cell.layer({
1171
+ read: (cliOptions) => Effect.gen(function* () {
1172
+ const pathService = yield* Path.Path;
1173
+ const resolvedOptions = yield* resolveSurvivorsRunOptions(cliOptions, basePath);
1174
+ const priorReportPath = priorReportPathOf(resolvedOptions);
1175
+ const resolveAbsolutePath = (file) => pathService.resolve(file);
1176
+ const read = yield* readPriorReport(priorReportPath);
1177
+ const sourceContentHashes = yield* currentSourceHashesFor(priorReportFileKeys(read.raw));
1178
+ return {
1179
+ resolvedOptions,
1180
+ priorReportRaw: read.raw,
1181
+ priorReportFound: read.found,
1182
+ priorReportPath,
1183
+ sourceContentHashes,
1184
+ resolveAbsolutePath
1185
+ };
1186
+ }),
1187
+ decode: ({ resolvedOptions, priorReportRaw, priorReportFound, sourceContentHashes, resolveAbsolutePath }) => {
1188
+ if (!priorReportFound) return Result.succeed(AdmitSurvivorsRunCommand.make({
1189
+ priorReport: void 0,
1190
+ currentConfig: resolvedOptions,
1191
+ frameworkVersion: strykerVersion,
1192
+ sourceContentHashes,
1193
+ priorSourceHashes: {},
1194
+ priorSurvivors: []
1195
+ }));
1196
+ return Result.map(decodePriorReport(priorReportRaw), (document) => AdmitSurvivorsRunCommand.make({
1197
+ priorReport: PriorReportFacts.make({
1198
+ config: document.config ?? {},
1199
+ frameworkVersion: document.framework?.version
1200
+ }),
1201
+ currentConfig: resolvedOptions,
1202
+ frameworkVersion: strykerVersion,
1203
+ sourceContentHashes,
1204
+ priorSourceHashes: priorSourceHashes(document, hashContent),
1205
+ priorSurvivors: extractSurvivors(document, resolveAbsolutePath)
1206
+ }));
1207
+ },
1208
+ decide: admitSurvivorsRun,
1209
+ encode: (outcome) => outcome,
1210
+ write: (outcome, raw) => Result.match(outcome, {
1211
+ onSuccess: (admission) => Effect.succeed({
1212
+ admission,
1213
+ resolvedOptions: raw.resolvedOptions,
1214
+ priorReportPath: raw.priorReportPath
1225
1215
  }),
1226
- currentConfig: resolvedOptions,
1227
- frameworkVersion: strykerVersion,
1228
- sourceContentHashes,
1229
- priorSourceHashes: priorSourceHashes(document, hashContent),
1230
- priorSurvivors: extractSurvivors(document, resolveAbsolutePath)
1231
- }));
1232
- }), Cell.decide(admitSurvivorsRun), Cell.encode((outcome) => outcome), Cell.write((outcome) => Effect.flatMap(Ref.get(runContext), (context) => {
1233
- if (context === void 0) return Effect.die("the survivors admission read must run before its write");
1234
- const { resolvedOptions, priorReportPath, pathService } = context;
1235
- return Result.match(outcome, {
1236
- onSuccess: (decision) => Match.value(decision).pipe(Match.tag("NoSurvivors", () => emitNullScoreVerdict(stream, mode, resolvedOptions.thresholds, resolvedOptions, basePath, pathService)), Match.tag("Admitted", (admitted) => {
1237
- const admittedMutants = admitted.survivors.map((s) => Mutant.make(s));
1238
- return runMutationTest({
1239
- ...resolvedOptions,
1240
- survivors: admittedMutants,
1241
- mutate: survivorMutateSpans(admittedMutants, basePath),
1242
- survivorsPriorReport: priorReportPath,
1243
- incremental: false
1244
- }).pipe(Effect.orDie);
1245
- }), Match.orElse(() => Effect.die("unreachable admission decision variant"))),
1246
- onFailure: (rejection) => Effect.fail(rejection)
1247
- });
1248
- })));
1249
- /**
1250
- * The `--survivors` request: re-test exactly the prior report's survivor set.
1251
- * The survivors flag was parsed as a boolean; the admission decides between
1252
- * running the survivors and the plain pipeline. The chain's order is carried by
1253
- * the description's phase types; the run's resolved context cell is created
1254
- * here, beside the description it feeds.
1255
- *
1256
- * Two failures reach the caller, and they are not the same thing. A rejection is the
1257
- * decision's own outcome — the run was inspected and refused. A `SchemaError` is a prior
1258
- * report that was present and did not decode, which stops the chain before any decision
1259
- * is made; it is in this signature because the phase types put it there, not because the
1260
- * admission chose it.
1261
- */
1262
- function runSurvivorsAdmission(runMutationTest, stream, mode, cliOptions, basePath) {
1263
- return Effect.gen(function* () {
1264
- const services = yield* Effect.context();
1265
- const admissionContext = yield* Ref.make(void 0);
1266
- return yield* Cell.apply(survivorsAdmissionDescription(runMutationTest, stream, mode, admissionContext, basePath, services), cliOptions);
1267
- });
1216
+ onFailure: Effect.fail
1217
+ })
1218
+ });
1219
+ function runSurvivorsAdmission(cliOptions, basePath) {
1220
+ return Cell.run(survivorsAdmissionCell(basePath), cliOptions);
1268
1221
  }
1269
1222
  function resolveSurvivorsRunOptions(cliOptions, basePath) {
1270
1223
  return readConfig(cliOptions, basePath);
@@ -1513,37 +1466,47 @@ function describeCommandNode(node) {
1513
1466
  };
1514
1467
  }
1515
1468
  function readCoreEntries() {
1516
- try {
1517
- const manifestPath = createRequire(import.meta.url).resolve("@systemfsoftware/stryker-js-platform-node/package.json");
1518
- const raw = readFileSync(manifestPath, "utf-8");
1519
- const parsed = JSON.parse(raw);
1469
+ return Effect.gen(function* () {
1470
+ const path = yield* Path.Path;
1471
+ const fs = yield* FileSystem.FileSystem;
1472
+ const resolved = import.meta.resolve("@systemfsoftware/stryker-js-engine/package.json");
1473
+ const manifestPath = yield* path.fromFileUrl(new URL(resolved));
1474
+ const raw = yield* fs.readFileString(manifestPath);
1475
+ let parsed;
1476
+ try {
1477
+ parsed = JSON.parse(raw);
1478
+ } catch {
1479
+ return [];
1480
+ }
1520
1481
  const ExportsSchema = S.Struct({ exports: S.Record(S.String, S.Unknown) });
1521
1482
  const decoded = S.decodeUnknownResult(ExportsSchema)(parsed);
1522
1483
  if (Result.isSuccess(decoded)) return Object.keys(decoded.success.exports).filter((key) => key !== "./package.json");
1523
1484
  return [];
1524
- } catch {
1525
- return [];
1526
- }
1485
+ }).pipe(Effect.catchCause(() => {
1486
+ return Effect.succeed([]);
1487
+ }));
1527
1488
  }
1528
1489
  function buildLLMSManifest(command, version) {
1529
- const root = describeCommandNode(command) ?? {
1530
- name: "",
1531
- description: "",
1532
- options: [],
1533
- args: [],
1534
- subcommands: []
1535
- };
1536
- return {
1537
- schemaVersion: "1.0",
1538
- tool: root.name,
1539
- version,
1540
- commands: [root],
1541
- entries: readCoreEntries()
1542
- };
1490
+ return Effect.gen(function* () {
1491
+ const root = describeCommandNode(command) ?? {
1492
+ name: "",
1493
+ description: "",
1494
+ options: [],
1495
+ args: [],
1496
+ subcommands: []
1497
+ };
1498
+ const entries = yield* readCoreEntries();
1499
+ return {
1500
+ schemaVersion: "1.0",
1501
+ tool: root.name,
1502
+ version,
1503
+ commands: [root],
1504
+ entries
1505
+ };
1506
+ });
1543
1507
  }
1544
- /** The manifest as one JSON document, ready for stdout — the U4 convention. */
1545
1508
  function emitLLMSManifest(command, version) {
1546
- return JSON.stringify(buildLLMSManifest(command, version));
1509
+ return Effect.map(buildLLMSManifest(command, version), (manifest) => JSON.stringify(manifest));
1547
1510
  }
1548
1511
  function createSplitter(separator) {
1549
1512
  return (value) => value.split(separator).filter(Boolean);
@@ -1716,39 +1679,26 @@ function makeStrykerCommand(requestRef) {
1716
1679
  if (Option.isSome(config["configFile"])) options["configFile"] = config["configFile"].value;
1717
1680
  return options;
1718
1681
  }
1719
- const strykerCommand = Command.make("stryker", rootConfig, (config) => {
1682
+ const strykerCommand = Command.make("stryker", rootConfig, (config) => Effect.gen(function* () {
1720
1683
  if (config.llms === true) {
1721
1684
  const document = {
1722
1685
  _tag: "manifest",
1723
1686
  schemaVersion: "1.0",
1724
1687
  code: 0,
1725
- manifest: emitLLMSManifest(strykerCommand, strykerVersion)
1688
+ manifest: yield* emitLLMSManifest(strykerCommand, strykerVersion)
1726
1689
  };
1727
- return Ref.set(requestRef, Option.some({
1690
+ return yield* Ref.set(requestRef, Option.some({
1728
1691
  _tag: "llms",
1729
1692
  document
1730
1693
  }));
1731
1694
  }
1732
- return Effect.failSync(() => CliError.ShowHelp.make({
1695
+ return yield* Effect.failSync(() => CliError.ShowHelp.make({
1733
1696
  commandPath: ["stryker"],
1734
1697
  errors: []
1735
1698
  }));
1736
- }).pipe(Command.withSubcommands([runCommand]));
1699
+ })).pipe(Command.withSubcommands([runCommand]));
1737
1700
  return strykerCommand;
1738
1701
  }
1739
- /**
1740
- * The CLI parses only text/number/choice options, so the framework's platform
1741
- * services are never read at runtime; the v4 runner still demands them in its
1742
- * environment. The bootstrap provides `Path.layer` (the universal
1743
- * implementation in `effect/Path`), an *empty* file system (`layerNoop`:
1744
- * every operation reports not-found), and a process-stdio `Terminal` whose
1745
- * interactive input primitives fail loudly — the run-only surface (R14) has
1746
- * no prompts. `@effect/platform-node` is a declared dependency of this
1747
- * package (it provides the `NodeRuntime` the bin runs through), but no
1748
- * platform-node service is wired into the command environment: the parser
1749
- * never reads a real file system at run time, so the noop layers are
1750
- * sufficient.
1751
- */
1752
1702
  const terminalLayer = Layer.succeed(Terminal.Terminal, Terminal.make({
1753
1703
  columns: Effect.sync(() => process.stdout.columns),
1754
1704
  rows: Effect.sync(() => process.stdout.rows),
@@ -1767,7 +1717,7 @@ const cliLayer = Layer.mergeAll(CliConfig.layer({ builtIns: [
1767
1717
  GlobalFlag.Wizard,
1768
1718
  GlobalFlag.Completions,
1769
1719
  GlobalFlag.LogLevel
1770
- ] }), Path.layer, FileSystem.layerNoop({}), terminalLayer, NodeStdio.layer, NodeChildProcessSpawner.layer.pipe(Layer.provideMerge(Layer.mergeAll(NodeFileSystem.layer, NodePath.layer))));
1720
+ ] }), Path.layer, NodeFileSystem.layer, terminalLayer, NodeStdio.layer, NodeChildProcessSpawner.layer.pipe(Layer.provideMerge(Layer.mergeAll(NodeFileSystem.layer, NodePath.layer))));
1771
1721
  function strykerCliEffect(argv, runMutationTest, detectMode, createRunEventStream, lastSignal) {
1772
1722
  return Effect.gen(function* () {
1773
1723
  const mode = yield* detectMode;
@@ -1790,14 +1740,15 @@ function strykerCliEffect(argv, runMutationTest, detectMode, createRunEventStrea
1790
1740
  return result.success;
1791
1741
  }).pipe(Effect.orElseSucceed(() => 2));
1792
1742
  }
1793
- const defaultRunMutationTest = (hostOptions, queue) => (options) => Effect.scoped(runMutationTest(options)).pipe(Effect.provideService(RunEvents, queue), Effect.provide(makeRunLayer(hostOptions)));
1743
+ const hostRunLayer = (hostOptions, queue) => makeRunLayer(hostOptions, queue).pipe(Layer.provideMerge(nodePlatformLayer));
1744
+ const defaultRunMutationTest = (hostOptions, queue) => (...args) => Effect.scoped(runMutationTest(...args)).pipe(Effect.provide(hostRunLayer(hostOptions, queue)), Effect.provideService(RunEvents, queue));
1794
1745
  function hostOptionsOf(mode, stream) {
1795
1746
  return {
1796
1747
  runId: stream.runId,
1797
1748
  resolvedMode: mode,
1798
1749
  runStartedAt: stream.startedAt,
1799
- basePath: resolve(process.cwd()),
1800
- reporterPluginModules: [import.meta.resolve("@systemfsoftware/stryker-js-html-reporter"), import.meta.resolve("@systemfsoftware/stryker-js-platform-node/builtin-reporters")],
1750
+ basePath: process.cwd(),
1751
+ reporterPluginModules: [import.meta.resolve("@systemfsoftware/stryker-js-html-reporter"), import.meta.resolve("@systemfsoftware/stryker-js-engine/builtin-reporters")],
1801
1752
  allowConsoleColors: isColorEnabled(mode, process.env["NO_COLOR"])
1802
1753
  };
1803
1754
  }
@@ -1814,7 +1765,20 @@ const runStrykerCli = (input, createRunEventStream) => Effect.gen(function* () {
1814
1765
  if (currentFiber !== null) currentFiber.interruptUnsafe(currentFiber.id);
1815
1766
  };
1816
1767
  const dispatch = (request) => Match.value(request).pipe(Match.tag("run", (runRequest) => (() => {
1817
- if (runRequest.survivors) return runSurvivorsAdmission(runMutationTestImpl, stream, input.mode, runRequest.options, basePath).pipe(Effect.provide(makeRunLayer(hostOptions)));
1768
+ if (runRequest.survivors) return Effect.gen(function* () {
1769
+ const { admission, resolvedOptions, priorReportPath } = yield* runSurvivorsAdmission(runRequest.options, basePath).pipe(Effect.provide(hostRunLayer(hostOptions)));
1770
+ return yield* Match.value(admission).pipe(Match.tag("NoSurvivors", () => emitNullScoreVerdict(stream, input.mode, resolvedOptions.thresholds, resolvedOptions, basePath, pathService)), Match.tag("Admitted", (admitted) => {
1771
+ const admittedMutants = admitted.survivors.map((s) => Mutant.make(s));
1772
+ const restricted = {
1773
+ ...resolvedOptions,
1774
+ survivors: admittedMutants,
1775
+ mutate: survivorMutateSpans(admittedMutants, basePath),
1776
+ survivorsPriorReport: priorReportPath,
1777
+ incremental: false
1778
+ };
1779
+ return runMutationTestImpl(restricted).pipe(Effect.orDie);
1780
+ }), Match.exhaustive);
1781
+ });
1818
1782
  return runMutationTestImpl(runRequest.options).pipe(Effect.orDie);
1819
1783
  })()), Match.tag("llms", (llmsRequest) => Effect.gen(function* () {
1820
1784
  stream.ensureOpen({
@@ -1868,24 +1832,13 @@ const EXIT_CODE_RUN_NEVER_REACHED_ITS_FINALIZER = 1;
1868
1832
  process.title = "stryker";
1869
1833
  const lastSignal = observeTerminatingSignal();
1870
1834
  function isSupportedNodeVersion(version) {
1871
- let withoutV = version;
1872
- if (version.startsWith("v")) withoutV = version.slice(1);
1873
- const dashBaseRaw = withoutV.split("-")[0];
1874
- let dashBase = withoutV;
1875
- if (dashBaseRaw !== void 0) dashBase = dashBaseRaw;
1876
- const baseRaw = dashBase.split("+")[0];
1877
- let base = dashBase;
1878
- if (baseRaw !== void 0) base = baseRaw;
1879
- const parts = base.split(".").map((p) => Number.parseInt(p, 10));
1880
- const major = parts[0] ?? 0;
1881
- const minor = parts[1] ?? 0;
1882
- const patch = parts[2] ?? 0;
1835
+ const [major = 0, minor = 0, patch = 0] = (version.replace(/^v/, "").split(/[-+]/)[0] ?? "").split(".").map((p) => Number.parseInt(p, 10));
1883
1836
  if (Number.isNaN(major) || Number.isNaN(minor) || Number.isNaN(patch)) return false;
1884
1837
  if (major !== 20) return major > 20;
1885
1838
  if (minor !== 0) return minor > 0;
1886
1839
  return patch >= 0;
1887
1840
  }
1888
- if (!isSupportedNodeVersion(process.version)) throw new Error(`Node.js version ${process.version} detected. StrykerJS requires version to match ${strykerEngines.node}. Please update your Node.js version or visit https://nodejs.org/ for additional instructions`);
1841
+ if (!isSupportedNodeVersion(process.version)) throw new Error(`Node.js version ${process.version} detected. StrykerJS requires version to match ${engines.node}. Please update your Node.js version or visit https://nodejs.org/ for additional instructions`);
1889
1842
  const program = Effect.gen(function* () {
1890
1843
  const outputMode = yield* OutputModeProbe;
1891
1844
  const runEvents = yield* RunEventStreamPort;