@vitest-agent/plugin 2.5.4 → 2.5.5

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/index.d.ts CHANGED
@@ -643,6 +643,35 @@ declare class AgentReporter {
643
643
  * @internal
644
644
  */
645
645
  private hookStartedAt;
646
+ /**
647
+ * Count of test specifications Vitest started for the current run,
648
+ * captured from `onTestRunStart`'s `specifications` argument. Used
649
+ * alongside a fresh `globTestSpecifications()` total in `onTestRunEnd`
650
+ * as one of `isPartialRun`'s signals (issue #160 gap 2) — a tags-only
651
+ * `run_tests` filter narrows the run without setting `filenamePattern`
652
+ * or `projectFilter`, so the spec-count comparison is the only signal
653
+ * that catches it. `undefined` when `onTestRunStart` never fired
654
+ * (tests invoking `onTestRunEnd` directly); `onTestRunEnd` falls back
655
+ * to the executed module count in that case.
656
+ *
657
+ * @internal
658
+ */
659
+ private startedSpecCount;
660
+ /**
661
+ * Original values of coverage-threshold keys deleted from
662
+ * `vitest.coverageProvider.options.thresholds` while neutralizing a
663
+ * partial run (issue #160). In `run` mode Vitest re-initializes the
664
+ * provider on every `vitest.start`, so the snapshot is moot — but in
665
+ * watch mode the provider is created once and scoped reruns go through
666
+ * `rerunFiles` without re-initializing it, so a deleted key would stay
667
+ * gone for the rest of the watch session. `onTestRunStart` restores
668
+ * these keys (only if still absent — a legitimately re-initialized
669
+ * provider is left alone) and clears the map. Empty when the last run
670
+ * was not partial.
671
+ *
672
+ * @internal
673
+ */
674
+ private neutralizedThresholdSnapshot;
646
675
  constructor(options?: AgentReporterConstructorOptions);
647
676
  /**
648
677
  * The resolved reporter config built at construction time. Exposed for
@@ -1134,6 +1163,13 @@ interface CoverageOptions {
1134
1163
  readonly baselines?: CoverageBaselines;
1135
1164
  /** When true, include files with zero coverage rather than omitting them. */
1136
1165
  readonly includeBareZero: boolean;
1166
+ /**
1167
+ * Total test-file count for the project, when known (issue #160 gap 1).
1168
+ * Only meaningful on a scoped run — threaded onto the returned
1169
+ * `CoverageReport.totalFiles` so the scoped-coverage note can render
1170
+ * "N of M test files" instead of just "N".
1171
+ */
1172
+ readonly totalFiles?: number;
1137
1173
  }
1138
1174
  declare const CoverageAnalyzer_base: Context.ServiceClass<CoverageAnalyzer, "vitest-agent/CoverageAnalyzer", {
1139
1175
  readonly process: (coverage: unknown, options: CoverageOptions) => Effect.Effect<Option.Option<CoverageReport>>;
@@ -112,6 +112,7 @@ function processCoverageInternal(coverageMap, options, testedFiles) {
112
112
  } } : {},
113
113
  scoped,
114
114
  ...scoped && testedFiles ? { scopedFiles: [...testedFiles] } : {},
115
+ ...scoped && options.totalFiles !== void 0 ? { totalFiles: options.totalFiles } : {},
115
116
  lowCoverage,
116
117
  lowCoverageFiles: lowCoverage.map((f) => f.file),
117
118
  ...options.targets ? {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vitest-agent/plugin",
3
- "version": "2.5.4",
3
+ "version": "2.5.5",
4
4
  "private": false,
5
5
  "description": "Vitest plugin for the vitest-agent ecosystem: owns persistence, classification, baselines, trends, and dispatches rendering to a configurable reporter.",
6
6
  "keywords": [
@@ -41,11 +41,11 @@
41
41
  "dependencies": {
42
42
  "@effect/platform-node": "4.0.0-rc.109",
43
43
  "@effect/sql-sqlite-node": "4.0.0-rc.109",
44
- "@effected/workspaces": "^0.18.3",
45
- "@vitest-agent/cli": "2.2.13",
46
- "@vitest-agent/mcp": "2.4.12",
47
- "@vitest-agent/reporter": "2.2.2",
48
- "@vitest-agent/sdk": "2.4.13",
44
+ "@effected/workspaces": "^0.19.0",
45
+ "@vitest-agent/cli": "2.2.14",
46
+ "@vitest-agent/mcp": "2.4.13",
47
+ "@vitest-agent/reporter": "2.2.3",
48
+ "@vitest-agent/sdk": "2.5.0",
49
49
  "effect": "4.0.0-rc.109",
50
50
  "magic-string": "^1.2.3"
51
51
  },
package/plugin.js CHANGED
@@ -8,9 +8,13 @@ import { discoverProjects } from "./utils/discover-projects.js";
8
8
  import { ensureGithubActionsReporter } from "./utils/ensure-github-reporter.js";
9
9
  import { injectTags } from "./utils/inject-tags.js";
10
10
  import { isBenignViteSourceMapWarning } from "./utils/is-benign-vite-source-map-warning.js";
11
+ import { resolveCoverageDirIsolation } from "./utils/resolve-coverage-dir-isolation.js";
11
12
  import { DEFAULT_BUILT_RECENTLY_MS, DEFAULT_LOCK_STALE_MS, DEFAULT_LOCK_WAIT_TIMEOUT_MS, acquireRunScriptLock, markRunScriptDone, parseLockTimingOverride, releaseRunScriptLock } from "./utils/run-script-lock.js";
12
13
  import { stripConsoleReporters } from "./utils/strip-console-reporters.js";
13
14
  import { execSync } from "node:child_process";
15
+ import { mkdtempSync, rmSync } from "node:fs";
16
+ import { tmpdir } from "node:os";
17
+ import { join } from "node:path";
14
18
  import { AgentConsoleMode, CiConsoleMode, CoverageLevel, EnvironmentDetector, EnvironmentDetectorLive, HumanConsoleMode, SRC_DIR, TEST_DIR, formatFatalError, isTestFileName, resolveLogLevel } from "@vitest-agent/sdk";
15
19
  import { Effect, Schema } from "effect";
16
20
 
@@ -94,6 +98,15 @@ function resolveFormat(mode) {
94
98
  */
95
99
  const aggregatedReporterByVitest = /* @__PURE__ */ new WeakSet();
96
100
  /**
101
+ * Guards the coverage.reportsDirectory isolation decision (issue #194) to
102
+ * run at most once per Vitest run — `configureVitest` fires once per
103
+ * project, but `coverage.reportsDirectory` is root-level config shared by
104
+ * every project in that run.
105
+ *
106
+ * @internal
107
+ */
108
+ const coverageDirDecidedByVitest = /* @__PURE__ */ new WeakSet();
109
+ /**
97
110
  * The version of this package, inlined at build time from
98
111
  * `package.json#version` via rslib-builder's `__PACKAGE_VERSION__` substitution.
99
112
  * Re-exported from the package barrel as the public symbol; defined here so
@@ -101,7 +114,7 @@ const aggregatedReporterByVitest = /* @__PURE__ */ new WeakSet();
101
114
  *
102
115
  * @public
103
116
  */
104
- const CURRENT_PLUGIN_VERSION = "2.5.4";
117
+ const CURRENT_PLUGIN_VERSION = "2.5.5";
105
118
  const TEST_FILE_DIR_RE = new RegExp(`/(?:${SRC_DIR}|${TEST_DIR})/`);
106
119
  const isTestFile = (id) => isTestFileName(id) && TEST_FILE_DIR_RE.test(id);
107
120
  /**
@@ -203,6 +216,31 @@ function AgentPlugin(options = {}, _layer) {
203
216
  const rawTargets = options.coverageTargets;
204
217
  const coverageTargets = rawTargets ? resolveThresholds(rawTargets) : void 0;
205
218
  const coverageMode = coverageConfig?.enabled === false ? "ui-only" : "full";
219
+ if (coverageConfig && !coverageDirDecidedByVitest.has(vitest)) {
220
+ coverageDirDecidedByVitest.add(vitest);
221
+ const dirDecision = resolveCoverageDirIsolation({
222
+ executor,
223
+ coverageEnabled: coverageMode === "full",
224
+ env: process.env,
225
+ configured: coverageConfig.reportsDirectory
226
+ });
227
+ if (dirDecision.kind === "isolate") {
228
+ const isolatedDir = mkdtempSync(join(tmpdir(), "vitest-agent-cov-"));
229
+ log("isolating coverage.reportsDirectory ->", isolatedDir);
230
+ coverageConfig.reportsDirectory = isolatedDir;
231
+ vitest.onClose(() => {
232
+ try {
233
+ rmSync(isolatedDir, {
234
+ recursive: true,
235
+ force: true
236
+ });
237
+ } catch {}
238
+ });
239
+ } else if (dirDecision.kind === "explicit") {
240
+ log("using explicit VITEST_AGENT_COVERAGE_DIR ->", dirDecision.dir);
241
+ coverageConfig.reportsDirectory = dirDecision.dir;
242
+ }
243
+ }
206
244
  const transport = options.transport ?? { kind: "local" };
207
245
  log("transport.kind:", transport.kind);
208
246
  const passWithNoTestsRaw = vitest.config.passWithNoTests;
package/reporter.js CHANGED
@@ -4,14 +4,15 @@ import { ReporterLive } from "./layers/ReporterLive.js";
4
4
  import { buildReporterKit, normalizeReporters } from "./utils/build-reporter-kit.js";
5
5
  import { captureEnvVars } from "./utils/capture-env.js";
6
6
  import { captureSettings, hashSettings } from "./utils/capture-settings.js";
7
+ import { isPartialRun } from "./utils/is-partial-run.js";
7
8
  import { processFailure } from "./utils/process-failure.js";
8
9
  import { routeRenderedOutput } from "./utils/route-rendered-output.js";
9
10
  import { stringifyFailureValue } from "./utils/stringify-failure-value.js";
11
+ import { mkdirSync } from "node:fs";
12
+ import { dirname } from "node:path";
10
13
  import { DataReader, DataStore, DetailResolver, EnvironmentDetector, ExecutorResolver, FormatSelector, HistoryTracker, OutputPipelineLive, PathResolutionLive, buildAgentReport, coerceErrorField, computeTrend, ensureMigrated, formatFatalError, historyKey, isTimeoutError, probeHostMetadataFromEnv, resolveDataPath, resolveLogFile, resolveLogLevel } from "@vitest-agent/sdk";
11
14
  import { Effect, Option, PubSub } from "effect";
12
15
  import { randomUUID } from "node:crypto";
13
- import { mkdirSync } from "node:fs";
14
- import { dirname } from "node:path";
15
16
  import { NodeServices } from "@effect/platform-node";
16
17
  import { DefaultVitestAgentReporter } from "@vitest-agent/reporter";
17
18
 
@@ -199,6 +200,35 @@ var AgentReporter = class {
199
200
  * @internal
200
201
  */
201
202
  hookStartedAt = /* @__PURE__ */ new Map();
203
+ /**
204
+ * Count of test specifications Vitest started for the current run,
205
+ * captured from `onTestRunStart`'s `specifications` argument. Used
206
+ * alongside a fresh `globTestSpecifications()` total in `onTestRunEnd`
207
+ * as one of `isPartialRun`'s signals (issue #160 gap 2) — a tags-only
208
+ * `run_tests` filter narrows the run without setting `filenamePattern`
209
+ * or `projectFilter`, so the spec-count comparison is the only signal
210
+ * that catches it. `undefined` when `onTestRunStart` never fired
211
+ * (tests invoking `onTestRunEnd` directly); `onTestRunEnd` falls back
212
+ * to the executed module count in that case.
213
+ *
214
+ * @internal
215
+ */
216
+ startedSpecCount;
217
+ /**
218
+ * Original values of coverage-threshold keys deleted from
219
+ * `vitest.coverageProvider.options.thresholds` while neutralizing a
220
+ * partial run (issue #160). In `run` mode Vitest re-initializes the
221
+ * provider on every `vitest.start`, so the snapshot is moot — but in
222
+ * watch mode the provider is created once and scoped reruns go through
223
+ * `rerunFiles` without re-initializing it, so a deleted key would stay
224
+ * gone for the rest of the watch session. `onTestRunStart` restores
225
+ * these keys (only if still absent — a legitimately re-initialized
226
+ * provider is left alone) and clears the map. Empty when the last run
227
+ * was not partial.
228
+ *
229
+ * @internal
230
+ */
231
+ neutralizedThresholdSnapshot = /* @__PURE__ */ new Map();
202
232
  constructor(options = {}) {
203
233
  this.logLevel = resolveLogLevel();
204
234
  this.logFile = resolveLogFile();
@@ -397,6 +427,16 @@ var AgentReporter = class {
397
427
  * `RunStarted` event for live subscribers.
398
428
  */
399
429
  onTestRunStart(_specifications) {
430
+ this.startedSpecCount = _specifications.length;
431
+ if (this.neutralizedThresholdSnapshot.size > 0) {
432
+ try {
433
+ const thresholds = (this._vitest?.coverageProvider)?.options?.thresholds;
434
+ if (thresholds !== void 0 && typeof thresholds === "object") {
435
+ for (const [key, value] of this.neutralizedThresholdSnapshot) if (!(key in thresholds)) thresholds[key] = value;
436
+ }
437
+ } catch {}
438
+ this.neutralizedThresholdSnapshot = /* @__PURE__ */ new Map();
439
+ }
400
440
  if (!this.wantsRunEvents()) return;
401
441
  this.currentRunId = randomUUID();
402
442
  this.moduleStartedAt.clear();
@@ -791,6 +831,7 @@ var AgentReporter = class {
791
831
  async onTestRunEnd(testModules, unhandledErrors, reason) {
792
832
  if (this.rendered) return;
793
833
  this.rendered = true;
834
+ const errors = unhandledErrors;
794
835
  if (this.wantsRunEvents() && this.currentRunId !== null) {
795
836
  let pass = 0;
796
837
  let fail = 0;
@@ -820,11 +861,11 @@ var AgentReporter = class {
820
861
  timeoutCount: timeout,
821
862
  durationMs: totalDuration,
822
863
  // @vitest-agent/ui, which both populate `collectedModules` on their
823
- collectedModules: testModules.length
864
+ collectedModules: testModules.length,
865
+ ...errors.length > 0 && { unhandledErrors: errors }
824
866
  });
825
867
  }
826
868
  const modules = testModules;
827
- const errors = unhandledErrors;
828
869
  const opts = this.options;
829
870
  const stashedCoverage = this.coverage;
830
871
  const stashedVitest = this._vitest;
@@ -832,6 +873,31 @@ var AgentReporter = class {
832
873
  const logFile = this.logFile;
833
874
  const runEvents = this.runEvents;
834
875
  const preBuiltReporters = this.reporters;
876
+ const vitestForPartialCheck = stashedVitest;
877
+ const startedSpecCount = this.startedSpecCount ?? modules.length;
878
+ let totalSpecCount = startedSpecCount;
879
+ try {
880
+ const specs = await vitestForPartialCheck?.globTestSpecifications?.();
881
+ if (specs !== void 0) totalSpecCount = specs.length;
882
+ } catch {}
883
+ const isPartial = isPartialRun({
884
+ filenamePattern: vitestForPartialCheck?.filenamePattern,
885
+ startedSpecCount,
886
+ totalSpecCount,
887
+ projectFilter: opts.projectFilter
888
+ });
889
+ const testedFiles = isPartial ? Array.from(new Set(modules.map((m) => m.relativeModuleId.replace(/\.test\.([^.]+)$/, ".$1").replace(/\.spec\.([^.]+)$/, ".$1")))) : void 0;
890
+ if (isPartial) {
891
+ this.neutralizedThresholdSnapshot = /* @__PURE__ */ new Map();
892
+ try {
893
+ const thresholds = (stashedVitest?.coverageProvider)?.options?.thresholds;
894
+ if (thresholds !== void 0 && typeof thresholds === "object") for (const key of Object.keys(thresholds)) {
895
+ if (key === "perFile" || key === "autoUpdate" || key === "100") continue;
896
+ this.neutralizedThresholdSnapshot.set(key, thresholds[key]);
897
+ delete thresholds[key];
898
+ }
899
+ } catch {}
900
+ }
835
901
  const emitEvent = (event) => {
836
902
  this.emit(event);
837
903
  };
@@ -964,9 +1030,10 @@ var AgentReporter = class {
964
1030
  thresholds: opts.coverageThresholds,
965
1031
  includeBareZero: opts.includeBareZero,
966
1032
  ...opts.coverageTargets ? { targets: opts.coverageTargets } : {},
967
- ...baselines ? { baselines } : {}
1033
+ ...baselines ? { baselines } : {},
1034
+ ...isPartial ? { totalFiles: totalSpecCount } : {}
968
1035
  };
969
- const coverageResult = stashedCoverage && isFirstProject ? yield* analyzer.process(stashedCoverage, coverageOpts) : Option.none();
1036
+ const coverageResult = stashedCoverage && isFirstProject ? isPartial ? yield* analyzer.processScoped(stashedCoverage, coverageOpts, testedFiles ?? []) : yield* analyzer.process(stashedCoverage, coverageOpts) : Option.none();
970
1037
  const coverageReport = Option.getOrUndefined(coverageResult);
971
1038
  if (wantsRunEvents && coverageReport !== void 0) {
972
1039
  const globalThresholds = coverageReport.thresholds.global;
@@ -978,9 +1045,12 @@ var AgentReporter = class {
978
1045
  file: fc.file,
979
1046
  missing: fc.summary,
980
1047
  uncoveredLines: fc.uncoveredLines
981
- }))
1048
+ })),
1049
+ ...coverageReport.scoped ? { scoped: coverageReport.scoped } : {},
1050
+ ...coverageReport.scopedFiles !== void 0 ? { scopedFiles: coverageReport.scopedFiles.length } : {},
1051
+ ...coverageReport.totalFiles !== void 0 ? { totalFiles: coverageReport.totalFiles } : {}
982
1052
  });
983
- for (const metric of [
1053
+ if (!coverageReport.scoped) for (const metric of [
984
1054
  "lines",
985
1055
  "branches",
986
1056
  "functions",
@@ -1029,7 +1099,7 @@ var AgentReporter = class {
1029
1099
  passed: baseReport.summary.passed,
1030
1100
  failed: baseReport.summary.failed,
1031
1101
  skipped: baseReport.summary.skipped,
1032
- scoped: false,
1102
+ scoped: isPartial,
1033
1103
  actorType: attribution.actorType,
1034
1104
  agentId: attribution.agentId,
1035
1105
  conversationId: attribution.conversationId,
@@ -1311,6 +1381,10 @@ var AgentReporter = class {
1311
1381
  const newBaselines = computeUpdatedBaselines(baselines, coverageReport.totals, opts.coverageTargets);
1312
1382
  yield* store.writeBaselines(newBaselines);
1313
1383
  }
1384
+ if (coverageReport && !coverageReport.scoped) {
1385
+ if (opts.coverageThresholds !== void 0) yield* store.writeThresholds(opts.coverageThresholds);
1386
+ if (opts.coverageTargets !== void 0) yield* store.writeTargets(opts.coverageTargets);
1387
+ }
1314
1388
  let trendSummary;
1315
1389
  if (coverageReport && !coverageReport.scoped) {
1316
1390
  const firstProjectKey = Array.from(projectGroups.keys())[0];
@@ -2,8 +2,8 @@ import { toPosixPath } from "./to-posix-path.js";
2
2
  import { nodeWalkerFs } from "./walker-fs.js";
3
3
  import { DefaultDiscoverStrategy } from "./discover-strategy.js";
4
4
  import { isTestShapedPackage } from "./is-test-shaped-package.js";
5
- import { NON_DISCOVERABLE_DIRS, SRC_DIR, TEST_DIR } from "@vitest-agent/sdk";
6
5
  import { isAbsolute, join, normalize, relative } from "node:path";
6
+ import { NON_DISCOVERABLE_DIRS, SRC_DIR, TEST_DIR } from "@vitest-agent/sdk";
7
7
  import { findWorkspaceRootSync, getWorkspacePackagesSync } from "@effected/workspaces";
8
8
  import { nodeSyncOps } from "@effected/workspaces/node-sync";
9
9
 
@@ -1,8 +1,8 @@
1
1
  import { nodeWalkerFs } from "./walker-fs.js";
2
2
  import { findTestFiles } from "./find-test-files.js";
3
3
  import { Tag } from "./tag.js";
4
- import { NON_DISCOVERABLE_DIRS, SRC_DIR, TEST_DIR, TEST_FILE_GLOB_SUFFIX, TEST_HELPER_DIRS } from "@vitest-agent/sdk";
5
4
  import { join, sep } from "node:path";
5
+ import { NON_DISCOVERABLE_DIRS, SRC_DIR, TEST_DIR, TEST_FILE_GLOB_SUFFIX, TEST_HELPER_DIRS } from "@vitest-agent/sdk";
6
6
  import { configDefaults } from "vitest/config";
7
7
 
8
8
  //#region src/utils/discover-strategy.ts
@@ -1,7 +1,7 @@
1
1
  import { toPosixPath } from "./to-posix-path.js";
2
2
  import { nodeWalkerFs } from "./walker-fs.js";
3
- import { NON_DISCOVERABLE_DIRS } from "@vitest-agent/sdk";
4
3
  import { join, relative } from "node:path";
4
+ import { NON_DISCOVERABLE_DIRS } from "@vitest-agent/sdk";
5
5
 
6
6
  //#region src/utils/find-test-files.ts
7
7
  function globToRegex(pattern) {
@@ -0,0 +1,26 @@
1
+ //#region src/utils/is-partial-run.ts
2
+ /**
3
+ * Pure decision function: was this Vitest run scoped to a subset of the
4
+ * project's test files?
5
+ *
6
+ * @remarks
7
+ * A run is partial when any of the following holds:
8
+ * - Vitest's `filenamePattern` was set (a non-empty array) for this run.
9
+ * - Fewer specifications started than exist in total for the same project set.
10
+ * - An explicit `--project` filter was supplied.
11
+ *
12
+ * Any of these makes coverage's whole-project denominator meaningless for
13
+ * threshold enforcement (issue #160).
14
+ *
15
+ * @public
16
+ */
17
+ function isPartialRun(input) {
18
+ const { filenamePattern, startedSpecCount, totalSpecCount, projectFilter } = input;
19
+ if (filenamePattern !== void 0 && filenamePattern.length > 0) return true;
20
+ if (startedSpecCount < totalSpecCount) return true;
21
+ if (projectFilter !== void 0) return true;
22
+ return false;
23
+ }
24
+
25
+ //#endregion
26
+ export { isPartialRun };
@@ -1,7 +1,7 @@
1
1
  import { nodeWalkerFs } from "./walker-fs.js";
2
2
  import { findTestFiles } from "./find-test-files.js";
3
- import { SRC_DIR, TEST_DIR, TEST_FILE_GLOB_SUFFIX } from "@vitest-agent/sdk";
4
3
  import { join } from "node:path";
4
+ import { SRC_DIR, TEST_DIR, TEST_FILE_GLOB_SUFFIX } from "@vitest-agent/sdk";
5
5
 
6
6
  //#region src/utils/is-test-shaped-package.ts
7
7
  /**
@@ -1,5 +1,5 @@
1
- import { computeFailureSignature, findFunctionBoundary } from "@vitest-agent/sdk";
2
1
  import { readFileSync } from "node:fs";
2
+ import { computeFailureSignature, findFunctionBoundary } from "@vitest-agent/sdk";
3
3
 
4
4
  //#region src/utils/process-failure.ts
5
5
  const FRAME_LINE_REGEX = /^\s*at\s+(?:([\w$.<>[\] ]+?)\s+)?\(?([^\n)]+):(\d+):(\d+)\)?\s*$/;
@@ -0,0 +1,44 @@
1
+ //#region src/utils/resolve-coverage-dir-isolation.ts
2
+ /**
3
+ * Values of `VITEST_AGENT_COVERAGE_DIR_ISOLATION` that opt out of the
4
+ * per-process coverage directory rewrite.
5
+ */
6
+ const OPT_OUT_VALUES = /* @__PURE__ */ new Set([
7
+ "off",
8
+ "0",
9
+ "false"
10
+ ]);
11
+ /**
12
+ * Pure decision function: should this Vitest run's `coverage.reportsDirectory`
13
+ * be isolated to a per-process temp directory?
14
+ *
15
+ * @remarks
16
+ * Two concurrent plain-CLI `vitest run` invocations in one checkout share
17
+ * `coverage.reportsDirectory` by default; the v8 provider's `clean: true`
18
+ * default `rm -rf`s that directory at run start, so one run can delete the
19
+ * other's `.tmp` files mid-run (issue #194). The MCP `run_tests` path
20
+ * already isolates via `makeCoverageDirOverride()`; this function drives
21
+ * the equivalent decision for the plain-CLI (`AgentPlugin.configureVitest`)
22
+ * path.
23
+ *
24
+ * Only the `agent` executor is ever isolated — a human's `./coverage`
25
+ * artifacts, and CI's configured directory, are never relocated.
26
+ *
27
+ * @public
28
+ */
29
+ function resolveCoverageDirIsolation(input) {
30
+ const { executor, coverageEnabled, env } = input;
31
+ if (!coverageEnabled) return { kind: "keep" };
32
+ if (executor !== "agent") return { kind: "keep" };
33
+ const isolationOverride = env.VITEST_AGENT_COVERAGE_DIR_ISOLATION;
34
+ if (isolationOverride !== void 0 && OPT_OUT_VALUES.has(isolationOverride)) return { kind: "keep" };
35
+ const explicitDir = env.VITEST_AGENT_COVERAGE_DIR;
36
+ if (explicitDir !== void 0 && explicitDir.length > 0) return {
37
+ kind: "explicit",
38
+ dir: explicitDir
39
+ };
40
+ return { kind: "isolate" };
41
+ }
42
+
43
+ //#endregion
44
+ export { resolveCoverageDirIsolation };
@@ -1,7 +1,7 @@
1
- import { createHash, randomBytes } from "node:crypto";
2
1
  import { closeSync, mkdirSync, openSync, readFileSync, rmSync, statSync, writeFileSync, writeSync } from "node:fs";
3
- import { join } from "node:path";
4
2
  import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { createHash, randomBytes } from "node:crypto";
5
5
 
6
6
  //#region src/utils/run-script-lock.ts
7
7
  /**