@vitest-agent/plugin 2.5.4 → 2.5.6

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
@@ -17,7 +17,7 @@ type TagOptions = Omit<TestTagDefinition, "name">;
17
17
  * A validated Vitest tag with its `name` string and a `TestTagDefinition` for registration.
18
18
  * @public
19
19
  */
20
- declare class Tag {
20
+ export declare class Tag {
21
21
  /** The tag name string (validated on construction). */
22
22
  readonly name: string;
23
23
  /** The full `TestTagDefinition` object to pass to Vitest's `test.tags` config. */
@@ -109,7 +109,7 @@ interface WalkerEntryStat {
109
109
  * an explicit port.
110
110
  * @public
111
111
  */
112
- declare const nodeWalkerFs: WalkerFileSystem;
112
+ export declare const nodeWalkerFs: WalkerFileSystem;
113
113
  //#endregion
114
114
  //#region src/utils/discover-strategy.d.ts
115
115
  /**
@@ -209,7 +209,7 @@ interface DiscoverStrategyExtendOptions {
209
209
  * the built-in unit/int/e2e heuristics.
210
210
  * @public
211
211
  */
212
- declare abstract class DiscoverStrategy {
212
+ export declare abstract class DiscoverStrategy {
213
213
  abstract readonly tags: ReadonlyArray<Tag>;
214
214
  abstract get tagDefinitions(): ReadonlyArray<TestTagDefinition>;
215
215
  abstract buildProject(input: DiscoverInput): Promise<TestProjectInlineConfiguration | null>;
@@ -226,7 +226,7 @@ declare abstract class DiscoverStrategy {
226
226
  * (`.int.test.*` → `"int"`, `.e2e.test.*` → `"e2e"`, everything else → `"unit"`).
227
227
  * @public
228
228
  */
229
- declare class DefaultDiscoverStrategy extends DiscoverStrategy {
229
+ export declare class DefaultDiscoverStrategy extends DiscoverStrategy {
230
230
  readonly tags: ReadonlyArray<Tag>;
231
231
  get tagDefinitions(): ReadonlyArray<TestTagDefinition>;
232
232
  classify(ctx: {
@@ -297,7 +297,7 @@ interface AgentPluginConstructorOptions extends AgentPluginOptions {
297
297
  *
298
298
  * @public
299
299
  */
300
- declare const CURRENT_PLUGIN_VERSION: string;
300
+ export declare const CURRENT_PLUGIN_VERSION: string;
301
301
  /**
302
302
  * Vitest plugin that injects `AgentReporter` into the reporter chain.
303
303
  *
@@ -307,7 +307,7 @@ declare const CURRENT_PLUGIN_VERSION: string;
307
307
  *
308
308
  * @public
309
309
  */
310
- declare function AgentPlugin(options?: AgentPluginConstructorOptions, _layer?: Layer.Layer<EnvironmentDetector>): {
310
+ export declare function AgentPlugin(options?: AgentPluginConstructorOptions, _layer?: Layer.Layer<EnvironmentDetector>): {
311
311
  name: "vitest-agent";
312
312
  configResolved: (resolvedConfig: {
313
313
  logger: {
@@ -378,7 +378,7 @@ interface DiscoverBuilder extends PromiseLike<DiscoverResult> {
378
378
  * Exposes coverage-level preset maps, auto-update tolerance functions, and the `discover` thenable builder.
379
379
  * @public
380
380
  */
381
- declare namespace AgentPlugin {
381
+ export declare namespace AgentPlugin {
382
382
  const COVERAGE_LEVELS: Readonly<Record<CoverageLevelName$1, CoverageLevelPreset>>;
383
383
  const COVERAGE_LEVELS_PER_FILE: Readonly<Record<CoverageLevelName$1, CoverageLevelPreset>>;
384
384
  /**
@@ -576,7 +576,7 @@ interface AgentReporterConstructorOptions extends AgentReporterOptions {
576
576
  * @see {@link https://vitest.dev/api/advanced/reporters.html | Vitest Reporter API}
577
577
  * @public
578
578
  */
579
- declare class AgentReporter {
579
+ export declare class AgentReporter {
580
580
  private options;
581
581
  private dbPath;
582
582
  /**
@@ -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
@@ -1049,7 +1078,7 @@ interface DiscoverProjectsOptions {
1049
1078
  * @returns Resolved projects and tag definitions
1050
1079
  * @public
1051
1080
  */
1052
- declare function discoverProjects(options?: DiscoverProjectsOptions): Promise<DiscoverProjectsResult>;
1081
+ export declare function discoverProjects(options?: DiscoverProjectsOptions): Promise<DiscoverProjectsResult>;
1053
1082
  //#endregion
1054
1083
  //#region src/utils/classify-helpers.d.ts
1055
1084
  /**
@@ -1066,7 +1095,7 @@ declare function discoverProjects(options?: DiscoverProjectsOptions): Promise<Di
1066
1095
  * No match returns an empty array.
1067
1096
  * @public
1068
1097
  */
1069
- declare function classifyByFilename(suffixMap: Record<string, ReadonlyArray<string>> | ReadonlyArray<readonly [RegExp, ReadonlyArray<string>]>): ClassifyFn;
1098
+ export declare function classifyByFilename(suffixMap: Record<string, ReadonlyArray<string>> | ReadonlyArray<readonly [RegExp, ReadonlyArray<string>]>): ClassifyFn;
1070
1099
  /**
1071
1100
  * Creates a ClassifyFn that maps directory segment paths to tag arrays.
1072
1101
  *
@@ -1078,7 +1107,7 @@ declare function classifyByFilename(suffixMap: Record<string, ReadonlyArray<stri
1078
1107
  * No match returns `[]`.
1079
1108
  * @public
1080
1109
  */
1081
- declare function classifyByDirectory(dirMap: Record<string, ReadonlyArray<string>>): ClassifyFn;
1110
+ export declare function classifyByDirectory(dirMap: Record<string, ReadonlyArray<string>>): ClassifyFn;
1082
1111
  /**
1083
1112
  * Composes multiple `ClassifyFn` values into one. Each classifier is called with
1084
1113
  * the same context; results are concatenated in order and deduplicated by tag
@@ -1086,7 +1115,7 @@ declare function classifyByDirectory(dirMap: Record<string, ReadonlyArray<string
1086
1115
  * returns `[]`.
1087
1116
  * @public
1088
1117
  */
1089
- declare function combineClassifiers(...fns: ReadonlyArray<ClassifyFn>): ClassifyFn;
1118
+ export declare function combineClassifiers(...fns: ReadonlyArray<ClassifyFn>): ClassifyFn;
1090
1119
  //#endregion
1091
1120
  //#region src/utils/find-test-files.d.ts
1092
1121
  /**
@@ -1110,14 +1139,14 @@ declare function combineClassifiers(...fns: ReadonlyArray<ClassifyFn>): Classify
1110
1139
  * @returns Absolute paths of matched test files
1111
1140
  * @public
1112
1141
  */
1113
- declare function findTestFiles(dir: string, patterns: ReadonlyArray<string>, fs?: WalkerFileSystem): Promise<ReadonlyArray<string>>;
1142
+ export declare function findTestFiles(dir: string, patterns: ReadonlyArray<string>, fs?: WalkerFileSystem): Promise<ReadonlyArray<string>>;
1114
1143
  //#endregion
1115
1144
  //#region src/layers/ReporterLive.d.ts
1116
1145
  /**
1117
1146
  * Composition layer for a single `AgentReporter` run. Wires SQLite, migrations, and all service layers.
1118
1147
  * @public
1119
1148
  */
1120
- declare const ReporterLive: (dbPath: string, logLevel?: LogLevel.LogLevel, logFile?: string) => Layer.Layer<CoverageAnalyzer | import("@vitest-agent/sdk").DataReader | import("@vitest-agent/sdk").DataStore | import("@vitest-agent/sdk").DetailResolver | import("@vitest-agent/sdk").EnvironmentDetector | import("@vitest-agent/sdk").ExecutorResolver | import("@vitest-agent/sdk").FormatSelector | import("@vitest-agent/sdk").HistoryTracker | import("@vitest-agent/sdk").OutputRenderer | import("effect/unstable/sql/SqlClient").SqlClient | import("@effect/sql-sqlite-node/SqliteClient").SqliteClient | NodeServices.NodeServices, SqliteMigrator.MigrationError | import("effect/unstable/sql/SqlError").SqlError, never>;
1149
+ export declare const ReporterLive: (dbPath: string, logLevel?: LogLevel.LogLevel, logFile?: string) => Layer.Layer<CoverageAnalyzer | import("@vitest-agent/sdk").DataReader | import("@vitest-agent/sdk").DataStore | import("@vitest-agent/sdk").DetailResolver | import("@vitest-agent/sdk").EnvironmentDetector | import("@vitest-agent/sdk").ExecutorResolver | import("@vitest-agent/sdk").FormatSelector | import("@vitest-agent/sdk").HistoryTracker | import("@vitest-agent/sdk").OutputRenderer | import("effect/unstable/sql/SqlClient").SqlClient | import("@effect/sql-sqlite-node/SqliteClient").SqliteClient | NodeServices.NodeServices, SqliteMigrator.MigrationError | import("effect/unstable/sql/SqlError").SqlError, never>;
1121
1150
  //#endregion
1122
1151
  //#region src/services/CoverageAnalyzer.d.ts
1123
1152
  /**
@@ -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>>;
@@ -1143,21 +1179,21 @@ declare const CoverageAnalyzer_base: Context.ServiceClass<CoverageAnalyzer, "vit
1143
1179
  * Effect service for processing istanbul coverage maps into structured reports.
1144
1180
  * @public
1145
1181
  */
1146
- declare class CoverageAnalyzer extends CoverageAnalyzer_base {}
1182
+ export declare class CoverageAnalyzer extends CoverageAnalyzer_base {}
1147
1183
  //#endregion
1148
1184
  //#region src/layers/CoverageAnalyzerLive.d.ts
1149
1185
  /**
1150
1186
  * Live implementation of the CoverageAnalyzer service backed by istanbul.
1151
1187
  * @public
1152
1188
  */
1153
- declare const CoverageAnalyzerLive: Layer.Layer<CoverageAnalyzer>;
1189
+ export declare const CoverageAnalyzerLive: Layer.Layer<CoverageAnalyzer>;
1154
1190
  //#endregion
1155
1191
  //#region src/layers/CoverageAnalyzerTest.d.ts
1156
1192
  /**
1157
1193
  * Test-double layer factory for CoverageAnalyzer. Pass a pre-built `CoverageReport` to inject.
1158
1194
  * @public
1159
1195
  */
1160
- declare const CoverageAnalyzerTest: {
1196
+ export declare const CoverageAnalyzerTest: {
1161
1197
  readonly layer: (data?: CoverageReport) => Layer.Layer<CoverageAnalyzer>;
1162
1198
  };
1163
1199
  //#endregion
@@ -1214,21 +1250,21 @@ declare const ConfigValidation_base: Context.ServiceClass<ConfigValidation, "vit
1214
1250
  * Effect service for validating Vitest + plugin coverage configuration.
1215
1251
  * @public
1216
1252
  */
1217
- declare class ConfigValidation extends ConfigValidation_base {}
1253
+ export declare class ConfigValidation extends ConfigValidation_base {}
1218
1254
  //#endregion
1219
1255
  //#region src/layers/ConfigValidationLive.d.ts
1220
1256
  /**
1221
1257
  * Live implementation of the ConfigValidation service running the built-in rule registry.
1222
1258
  * @public
1223
1259
  */
1224
- declare const ConfigValidationLive: Layer.Layer<ConfigValidation>;
1260
+ export declare const ConfigValidationLive: Layer.Layer<ConfigValidation>;
1225
1261
  //#endregion
1226
1262
  //#region src/layers/ConfigValidationTest.d.ts
1227
1263
  /**
1228
1264
  * Test-double layer factory for ConfigValidation. Pass a pre-built `ValidationResult` to inject.
1229
1265
  * @public
1230
1266
  */
1231
- declare const ConfigValidationTest: {
1267
+ export declare const ConfigValidationTest: {
1232
1268
  readonly layer: (override?: ValidationResult) => Layer.Layer<ConfigValidation>;
1233
1269
  };
1234
1270
  //#endregion
@@ -1239,7 +1275,7 @@ declare const ConfigValidationTest: {
1239
1275
  * @returns A filtered map of relevant environment variable keys and values
1240
1276
  * @public
1241
1277
  */
1242
- declare function captureEnvVars(env: Record<string, string | undefined>): Record<string, string>;
1278
+ export declare function captureEnvVars(env: Record<string, string | undefined>): Record<string, string>;
1243
1279
  //#endregion
1244
1280
  //#region src/utils/capture-settings.d.ts
1245
1281
  /**
@@ -1249,14 +1285,14 @@ declare function captureEnvVars(env: Record<string, string | undefined>): Record
1249
1285
  * @returns A `SettingsInput` ready for persistence
1250
1286
  * @public
1251
1287
  */
1252
- declare function captureSettings(config: Record<string, unknown>, vitestVersion: string): SettingsInput;
1288
+ export declare function captureSettings(config: Record<string, unknown>, vitestVersion: string): SettingsInput;
1253
1289
  /**
1254
1290
  * Compute a stable SHA-256 hash of a settings record for change detection.
1255
1291
  * @param settings - The settings record to hash (keys are sorted for stability)
1256
1292
  * @returns A hex-encoded SHA-256 digest
1257
1293
  * @public
1258
1294
  */
1259
- declare function hashSettings(settings: Record<string, unknown>): string;
1295
+ export declare function hashSettings(settings: Record<string, unknown>): string;
1260
1296
  //#endregion
1261
1297
  //#region src/utils/process-failure.d.ts
1262
1298
  /**
@@ -1292,7 +1328,7 @@ interface VitestErrorLike {
1292
1328
  * still be populated even when the signature is null.
1293
1329
  * @public
1294
1330
  */
1295
- declare const processFailure: (error: VitestErrorLike) => {
1331
+ export declare const processFailure: (error: VitestErrorLike) => {
1296
1332
  frames: ReadonlyArray<StackFrameInput>;
1297
1333
  signatureHash: string | null;
1298
1334
  };
@@ -1309,7 +1345,7 @@ type VitestThresholdsInput = Record<string, unknown>;
1309
1345
  * @returns Normalized thresholds with global, perFile, and pattern entries
1310
1346
  * @public
1311
1347
  */
1312
- declare function resolveThresholds(input: VitestThresholdsInput | undefined): ResolvedThresholds;
1348
+ export declare function resolveThresholds(input: VitestThresholdsInput | undefined): ResolvedThresholds;
1313
1349
  //#endregion
1314
1350
  //#region src/utils/strip-console-reporters.d.ts
1315
1351
  /**
@@ -1324,7 +1360,7 @@ declare function resolveThresholds(input: VitestThresholdsInput | undefined): Re
1324
1360
  * @see {@link https://vitest.dev/api/advanced/reporters.html | Vitest Reporter docs}
1325
1361
  * @internal
1326
1362
  */
1327
- declare const CONSOLE_REPORTERS: Set<string>;
1363
+ export declare const CONSOLE_REPORTERS: Set<string>;
1328
1364
  /**
1329
1365
  * Filter out built-in console reporters from a Vitest reporters array.
1330
1366
  *
@@ -1337,19 +1373,19 @@ declare const CONSOLE_REPORTERS: Set<string>;
1337
1373
  *
1338
1374
  * @internal
1339
1375
  */
1340
- declare function stripConsoleReporters(reporters: unknown[]): unknown[];
1376
+ export declare function stripConsoleReporters(reporters: unknown[]): unknown[];
1341
1377
  //#endregion
1342
1378
  //#region src/index.d.ts
1343
1379
  /** Preset map for coverage levels without per-file enforcement. Mirrors `AgentPlugin.COVERAGE_LEVELS`. @public */
1344
- declare const COVERAGE_LEVELS: Readonly<Record<import("@vitest-agent/sdk").CoverageLevelName, CoverageLevelPreset>>;
1380
+ export declare const COVERAGE_LEVELS: Readonly<Record<import("@vitest-agent/sdk").CoverageLevelName, CoverageLevelPreset>>;
1345
1381
  /** Preset map for coverage levels with per-file enforcement. Mirrors `AgentPlugin.COVERAGE_LEVELS_PER_FILE`. @public */
1346
- declare const COVERAGE_LEVELS_PER_FILE: Readonly<Record<import("@vitest-agent/sdk").CoverageLevelName, CoverageLevelPreset>>;
1382
+ export declare const COVERAGE_LEVELS_PER_FILE: Readonly<Record<import("@vitest-agent/sdk").CoverageLevelName, CoverageLevelPreset>>;
1347
1383
  /** Auto-update tolerance functions for `coverage.thresholds.autoUpdate`. Mirrors `AgentPlugin.COVERAGE_AUTOUPDATE`. @public */
1348
- declare const COVERAGE_AUTOUPDATE: Readonly<{
1384
+ export declare const COVERAGE_AUTOUPDATE: Readonly<{
1349
1385
  standard: (n: number) => number;
1350
1386
  strict: (n: number) => number;
1351
1387
  lenient: (n: number) => number;
1352
1388
  }>;
1353
1389
  //#endregion
1354
- export { type AddProjectInput, AgentPlugin, type AgentPluginConstructorOptions, AgentReporter, type AgentReporterConstructorOptions, CONSOLE_REPORTERS, COVERAGE_AUTOUPDATE, COVERAGE_LEVELS, COVERAGE_LEVELS_PER_FILE, CURRENT_PLUGIN_VERSION, type ClassifyContext, type ClassifyFn, ConfigValidation, ConfigValidationLive, ConfigValidationTest, CoverageAnalyzer, CoverageAnalyzerLive, CoverageAnalyzerTest, type CoverageInput, CoverageLevel, type CoverageLevelName, type CoverageLevelPreset, type CoverageOptions, DefaultDiscoverStrategy, type DiscoverBuilder, type DiscoverInput, type PackageJson as DiscoverPackageJson, type DiscoverProjectsOptions, type DiscoverProjectsResult, type DiscoverResult, DiscoverStrategy, type DiscoverStrategyCreateOptions, type DiscoverStrategyExtendOptions, type InjectTagsResult, type ModuleInfo, ReporterLive, Tag, type TagOptions, type ValidationError, type ValidationInfo, type ValidationInput, type ValidationResult, type ValidationWarning, type VitestErrorLike, type VitestStackFrameLike, type VitestThresholdsInput, type WalkerEntry, type WalkerEntryStat, type WalkerFileSystem, captureEnvVars, captureSettings, classifyByDirectory, classifyByFilename, combineClassifiers, discoverProjects, findTestFiles, hashSettings, nodeWalkerFs, processFailure, resolveCoverageInput, resolveThresholds, stripConsoleReporters, validateCoverageConfig };
1390
+ export { type AddProjectInput, type AgentPluginConstructorOptions, type AgentReporterConstructorOptions, type ClassifyContext, type ClassifyFn, type CoverageInput, CoverageLevel, type CoverageLevelName, type CoverageLevelPreset, type CoverageOptions, type DiscoverBuilder, type DiscoverInput, type PackageJson as DiscoverPackageJson, type DiscoverProjectsOptions, type DiscoverProjectsResult, type DiscoverResult, type DiscoverStrategyCreateOptions, type DiscoverStrategyExtendOptions, type InjectTagsResult, type ModuleInfo, type TagOptions, type ValidationError, type ValidationInfo, type ValidationInput, type ValidationResult, type ValidationWarning, type VitestErrorLike, type VitestStackFrameLike, type VitestThresholdsInput, type WalkerEntry, type WalkerEntryStat, type WalkerFileSystem, resolveCoverageInput, validateCoverageConfig };
1355
1391
  //# sourceMappingURL=index.d.ts.map
@@ -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.6",
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.14",
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.6";
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
  /**