@vitest-agent/sdk 7.1.0 → 9.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.
@@ -1,3 +1,4 @@
1
+ import { FiniteNumber } from "./Common.js";
1
2
  import { Schema } from "effect";
2
3
 
3
4
  // src/schemas/ConsoleLeaks.ts
@@ -8,8 +9,8 @@ import { Schema } from "effect";
8
9
  */
9
10
  const ConsoleLeakFile = Schema.Struct({
10
11
  file: Schema.String,
11
- stdout: Schema.Number,
12
- stderr: Schema.Number,
12
+ stdout: FiniteNumber,
13
+ stderr: FiniteNumber,
13
14
  tests: Schema.optional(Schema.Array(Schema.String)),
14
15
  sample: Schema.optional(Schema.String)
15
16
  }).annotate({ identifier: "ConsoleLeakFile" });
@@ -22,8 +23,8 @@ const ConsoleLeakFile = Schema.Struct({
22
23
  * @public
23
24
  */
24
25
  const ConsoleLeaksFromFailingTests = Schema.Struct({
25
- total: Schema.Number,
26
- files: Schema.Number
26
+ total: FiniteNumber,
27
+ files: FiniteNumber
27
28
  }).annotate({ identifier: "ConsoleLeaksFromFailingTests" });
28
29
  /**
29
30
  * Aggregate stray-console-output signal for a single test run. Omitted from
@@ -35,7 +36,7 @@ const ConsoleLeaksFromFailingTests = Schema.Struct({
35
36
  * @public
36
37
  */
37
38
  const ConsoleLeaks = Schema.Struct({
38
- total: Schema.Number,
39
+ total: FiniteNumber,
39
40
  byFile: Schema.Array(ConsoleLeakFile),
40
41
  truncated: Schema.optional(Schema.Boolean),
41
42
  fromFailingTests: Schema.optional(ConsoleLeaksFromFailingTests)
@@ -1,3 +1,4 @@
1
+ import { FiniteNumber } from "./Common.js";
1
2
  import { MetricThresholds, PatternThresholds } from "./Thresholds.js";
2
3
  import { Effect, Schema } from "effect";
3
4
 
@@ -7,10 +8,10 @@ import { Effect, Schema } from "effect";
7
8
  * @public
8
9
  */
9
10
  const CoverageTotals = Schema.Struct({
10
- statements: Schema.Number,
11
- branches: Schema.Number,
12
- functions: Schema.Number,
13
- lines: Schema.Number
11
+ statements: FiniteNumber,
12
+ branches: FiniteNumber,
13
+ functions: FiniteNumber,
14
+ lines: FiniteNumber
14
15
  }).annotate({ identifier: "CoverageTotals" });
15
16
  /**
16
17
  * Per-file coverage data including uncovered line ranges.
@@ -58,7 +59,7 @@ const CoverageReport = Schema.Struct({
58
59
  })),
59
60
  scoped: Schema.Boolean.pipe(Schema.withDecodingDefaultKey(Effect.succeed(false))),
60
61
  scopedFiles: Schema.optional(Schema.Array(Schema.String)),
61
- totalFiles: Schema.optional(Schema.Number),
62
+ totalFiles: Schema.optional(FiniteNumber),
62
63
  lowCoverage: Schema.Array(FileCoverageReport),
63
64
  lowCoverageFiles: Schema.Array(Schema.String),
64
65
  belowTarget: Schema.optional(Schema.Array(FileCoverageReport)),
@@ -1,4 +1,5 @@
1
1
  import { ReportError, TestClassification, TestState } from "./Common.js";
2
+ import { ConsoleLeaks } from "./ConsoleLeaks.js";
2
3
  import { MetricThresholds } from "./Thresholds.js";
3
4
  import { CoverageTotals, GlobShortfall } from "./Coverage.js";
4
5
  import { StrayOutput } from "./StrayOutput.js";
@@ -163,7 +164,13 @@ const RenderState = Schema.Struct({
163
164
  * capture, folded from `RunFinished.strayOutput`. Absent when the run
164
165
  * wrote nothing stray.
165
166
  */
166
- strayOutput: Schema.optional(StrayOutput)
167
+ strayOutput: Schema.optional(StrayOutput),
168
+ /**
169
+ * `console.*` output Vitest captured from tests that did not fail, folded
170
+ * from `RunFinished.consoleLeaks`. Absent when the run logged nothing
171
+ * (issue #544).
172
+ */
173
+ consoleLeaks: Schema.optional(ConsoleLeaks)
167
174
  }).annotate({ identifier: "RenderState" });
168
175
  /**
169
176
  * Initial state — what the reducer returns when no events have been
@@ -0,0 +1,61 @@
1
+ import { FiniteNumber } from "./Common.js";
2
+ import { Schema } from "effect";
3
+
4
+ // src/schemas/RunDiagnostics.ts
5
+ /**
6
+ * Which Vitest performance diagnostic a hint came from.
7
+ * @public
8
+ */
9
+ const PerformanceHintKind = Schema.Literals([
10
+ "environment",
11
+ "import",
12
+ "transform",
13
+ "isolate"
14
+ ]).annotate({ identifier: "PerformanceHintKind" });
15
+ /**
16
+ * One performance hint derived from Vitest's run diagnostics. `project` is
17
+ * omitted for a hint that applies to the whole run (for example `isolate`).
18
+ * @public
19
+ */
20
+ const PerformanceHint = Schema.Struct({
21
+ kind: PerformanceHintKind,
22
+ project: Schema.optionalKey(Schema.String),
23
+ message: Schema.String,
24
+ remedy: Schema.String,
25
+ detail: Schema.String,
26
+ learnMoreUrl: Schema.String,
27
+ estimatedSavingMs: Schema.optionalKey(FiniteNumber),
28
+ share: Schema.optionalKey(FiniteNumber)
29
+ }).annotate({ identifier: "PerformanceHint" });
30
+ /**
31
+ * Wall-clock timing of the plugin's post-run work, in milliseconds.
32
+ * @public
33
+ */
34
+ const PostRunTiming = Schema.Struct({
35
+ persistMs: FiniteNumber,
36
+ coverageScanMs: FiniteNumber,
37
+ totalMs: FiniteNumber,
38
+ renderMs: Schema.optionalKey(FiniteNumber),
39
+ vitestDurationMs: Schema.optionalKey(FiniteNumber)
40
+ }).annotate({ identifier: "PostRunTiming" });
41
+ /**
42
+ * Whether the run's results were persisted to the database.
43
+ * @public
44
+ */
45
+ const PersistenceStatus = Schema.Struct({
46
+ ok: Schema.Boolean,
47
+ reason: Schema.optionalKey(Schema.String),
48
+ migrationFailed: Schema.optionalKey(Schema.Boolean)
49
+ }).annotate({ identifier: "PersistenceStatus" });
50
+ /**
51
+ * Run-level diagnostics: performance hints, post-run timing and persistence
52
+ * status (issue #544).
53
+ * @public
54
+ */
55
+ const RunDiagnostics = Schema.Struct({
56
+ performanceHints: Schema.Array(PerformanceHint),
57
+ postRun: Schema.optionalKey(PostRunTiming),
58
+ persistence: Schema.optionalKey(PersistenceStatus)
59
+ }).annotate({ identifier: "RunDiagnostics" });
60
+
61
+ export { PerformanceHint, PerformanceHintKind, PersistenceStatus, PostRunTiming, RunDiagnostics };
@@ -1,4 +1,5 @@
1
1
  import { ReportError, TestClassification, TestState } from "./Common.js";
2
+ import { ConsoleLeaks } from "./ConsoleLeaks.js";
2
3
  import { MetricThresholds } from "./Thresholds.js";
3
4
  import { CoverageTotals, FileCoverageReport, GlobShortfall } from "./Coverage.js";
4
5
  import { StrayOutput } from "./StrayOutput.js";
@@ -226,7 +227,13 @@ const RunEvent = Schema.Union([
226
227
  * capture (`AgentReport.strayOutput`). Optional: absent when the run
227
228
  * wrote nothing stray, and in older replay data.
228
229
  */
229
- strayOutput: Schema.optional(StrayOutput)
230
+ strayOutput: Schema.optional(StrayOutput),
231
+ /**
232
+ * `console.*` output Vitest captured from tests that did not fail
233
+ * (`AgentReport.consoleLeaks`). Optional: absent when the run logged
234
+ * nothing, and in older replay data (issue #544).
235
+ */
236
+ consoleLeaks: Schema.optional(ConsoleLeaks)
230
237
  })
231
238
  ]).annotate({ identifier: "RunEvent" });
232
239
 
@@ -1,4 +1,5 @@
1
1
  import { AgentReport } from "./AgentReport.js";
2
+ import { RunDiagnostics } from "./RunDiagnostics.js";
2
3
  import { Schema } from "effect";
3
4
 
4
5
  // src/schemas/RunReportFile.ts
@@ -47,7 +48,8 @@ const RunReportFile = Schema.Struct({
47
48
  $schema: Schema.optionalKey(Schema.String),
48
49
  schemaVersion: Schema.Literal(1),
49
50
  generatedAt: IsoInstant,
50
- reports: Schema.Array(AgentReport)
51
+ reports: Schema.Array(AgentReport),
52
+ runDiagnostics: Schema.optionalKey(RunDiagnostics)
51
53
  }).annotate({
52
54
  identifier: "RunReportFile",
53
55
  description: "The vitest-agent run report file (.vitest/vitest-agent/run.json): one AgentReport per Vitest project from the most recent run. Read reports[].summary for pass/fail counts and reports[].failed for the failing modules and their tests. schemaVersion is the envelope contract version; generatedAt is when the run finished."
@@ -1,3 +1,4 @@
1
+ import { FiniteNumber } from "./Common.js";
1
2
  import { Schema } from "effect";
2
3
 
3
4
  // src/schemas/StrayOutput.ts
@@ -27,10 +28,10 @@ const StrayOutputSample = Schema.Struct({
27
28
  * @public
28
29
  */
29
30
  const StrayOutput = Schema.Struct({
30
- total: Schema.Number,
31
- stdout: Schema.Number,
32
- stderr: Schema.Number,
33
- bytes: Schema.Number,
31
+ total: FiniteNumber,
32
+ stdout: FiniteNumber,
33
+ stderr: FiniteNumber,
34
+ bytes: FiniteNumber,
34
35
  samples: Schema.Array(StrayOutputSample)
35
36
  }).annotate({ identifier: "StrayOutput" });
36
37
 
@@ -1,3 +1,4 @@
1
+ import { FiniteNumber } from "./Common.js";
1
2
  import { Effect, Schema } from "effect";
2
3
 
3
4
  // src/schemas/Thresholds.ts
@@ -6,10 +7,10 @@ import { Effect, Schema } from "effect";
6
7
  * @public
7
8
  */
8
9
  const MetricThresholds = Schema.Struct({
9
- lines: Schema.optional(Schema.Number),
10
- functions: Schema.optional(Schema.Number),
11
- branches: Schema.optional(Schema.Number),
12
- statements: Schema.optional(Schema.Number)
10
+ lines: Schema.optional(FiniteNumber),
11
+ functions: Schema.optional(FiniteNumber),
12
+ branches: Schema.optional(FiniteNumber),
13
+ statements: Schema.optional(FiniteNumber)
13
14
  }).annotate({ identifier: "MetricThresholds" });
14
15
  /**
15
16
  * A `perFile` setting. Vitest 5 allows either a plain boolean or a
@@ -5,7 +5,7 @@
5
5
  "toolPackages": [
6
6
  {
7
7
  "packageName": "@microsoft/api-extractor",
8
- "packageVersion": "7.59.1"
8
+ "packageVersion": "7.59.4"
9
9
  }
10
10
  ]
11
11
  }
package/utils/ansi.js CHANGED
@@ -1,38 +1,4 @@
1
1
  // src/utils/ansi.ts
2
- const ANSI_CODES = {
3
- reset: [0, 0],
4
- bold: [1, 22],
5
- dim: [2, 22],
6
- red: [31, 39],
7
- green: [32, 39],
8
- yellow: [33, 39],
9
- cyan: [36, 39]
10
- };
11
- /**
12
- * Wrap text with an ANSI color/style escape code.
13
- *
14
- * No-ops when `options.noColor` is true, keeping output as valid plain
15
- * markdown when captured by agents or piped to files.
16
- *
17
- * @privateRemarks
18
- * Uses raw ANSI escape sequences (`\x1b[...m`) rather than a dependency
19
- * like `chalk` or `picocolors` to keep the package zero-dependency. The
20
- * `noColor` flag respects the `NO_COLOR` environment variable convention.
21
- *
22
- * @param text - Text to wrap
23
- * @param color - ANSI code name: `reset`, `bold`, `dim`, `red`, `green`, `yellow`, `cyan`
24
- * @param options - Control color output
25
- * @returns Text with ANSI escape codes, or plain text if noColor
26
- *
27
- * @see {@link https://no-color.org/ | NO_COLOR convention}
28
- * @internal
29
- */
30
- function ansi(text, color, options) {
31
- if (options?.noColor) return text;
32
- const codes = ANSI_CODES[color];
33
- if (!codes) return text;
34
- return `\x1b[${codes[0]}m${text}\x1b[${codes[1]}m`;
35
- }
36
2
  /**
37
3
  * Strip all ANSI escape codes from a string.
38
4
  *
@@ -45,4 +11,4 @@ function stripAnsi(text) {
45
11
  return text.replace(/\x1b\[[0-9;]*m/g, "");
46
12
  }
47
13
 
48
- export { ansi, stripAnsi };
14
+ export { stripAnsi };
@@ -99,5 +99,31 @@ function collectConsoleLeakEntries(files) {
99
99
  }
100
100
  return entries;
101
101
  }
102
+ const MAX_NOTE_FILES = 3;
103
+ /**
104
+ * The note a renderer prints for a run's console leaks: one line naming the
105
+ * write count, the file count and the fix, then up to three files with
106
+ * their stream split and sample, how many files it left out, and the output
107
+ * from failing tests that the leak count excludes. Empty when the run has no
108
+ * leak from a passing test (output from failing tests alone is not a leak).
109
+ * @public
110
+ */
111
+ const formatConsoleLeaksNote = (leaks) => {
112
+ if (leaks.total === 0) return [];
113
+ const files = leaks.byFile.length;
114
+ const lines = [`Console leaks: passing tests wrote ${leaks.total} console ${leaks.total === 1 ? "line" : "lines"} in ${files} ${files === 1 ? "file" : "files"}; Vitest captured them, so they are noise an agent never sees. Remove the logging or route it through a logger the tests silence.`];
115
+ const shown = leaks.byFile.slice(0, MAX_NOTE_FILES);
116
+ for (const file of shown) {
117
+ const streams = [file.stdout > 0 ? `${file.stdout} stdout` : "", file.stderr > 0 ? `${file.stderr} stderr` : ""].filter((part) => part.length > 0).join(", ");
118
+ const sample = file.sample !== void 0 ? `: ${file.sample}` : "";
119
+ lines.push(` ${file.file} (${streams})${sample}`);
120
+ }
121
+ const more = leaks.byFile.length - shown.length;
122
+ if (more > 0) lines.push(` (+${more} more ${more === 1 ? "file" : "files"}${leaks.truncated === true ? ", list truncated" : ""})`);
123
+ else if (leaks.truncated === true) lines.push(" (file list truncated)");
124
+ const failing = leaks.fromFailingTests;
125
+ if (failing !== void 0 && failing.total > 0) lines.push(` (${failing.total} more ${failing.total === 1 ? "line" : "lines"} from failing tests in ${failing.files} ${failing.files === 1 ? "file" : "files"}, not counted as leaks)`);
126
+ return lines;
127
+ };
102
128
 
103
- export { buildConsoleLeaks, collectConsoleLeakEntries };
129
+ export { buildConsoleLeaks, collectConsoleLeakEntries, formatConsoleLeaksNote };
@@ -0,0 +1,58 @@
1
+ // src/utils/run-diagnostics-actions.ts
2
+ /**
3
+ * Post-run work at or above this many milliseconds is always "slow".
4
+ * @public
5
+ */
6
+ const POST_RUN_SLOW_MS = 5e3;
7
+ /**
8
+ * Post-run work at or above this share of Vitest's own duration is "slow"
9
+ * (once it also clears {@link POST_RUN_SLOW_MIN_MS}).
10
+ * @public
11
+ */
12
+ const POST_RUN_SLOW_SHARE = .1;
13
+ /**
14
+ * Minimum post-run milliseconds before the share rule applies.
15
+ * @public
16
+ */
17
+ const POST_RUN_SLOW_MIN_MS = 1e3;
18
+ /**
19
+ * Whether the plugin's post-run work was slow enough to surface.
20
+ * @public
21
+ */
22
+ function isPostRunSlow(timing) {
23
+ if (timing.totalMs >= 5e3) return true;
24
+ return timing.totalMs >= 1e3 && timing.vitestDurationMs !== void 0 && timing.totalMs >= .1 * timing.vitestDurationMs;
25
+ }
26
+ const fmt = (ms) => `${(ms / 1e3).toFixed(1)}s`;
27
+ /**
28
+ * Map run diagnostics onto suggested actions: persistence failure first, then
29
+ * slow post-run work, then performance hints in input order.
30
+ * @public
31
+ */
32
+ function suggestedActionsFromRunDiagnostics(d) {
33
+ const out = [];
34
+ if (d.persistence !== void 0 && !d.persistence.ok) {
35
+ const { reason, migrationFailed } = d.persistence;
36
+ out.push({
37
+ severity: "warn",
38
+ title: "Results were not saved",
39
+ detail: migrationFailed ? `${reason ?? "unknown error"}. Run \`vitest-agent db reset\` to recreate the database.` : reason ?? "unknown error"
40
+ });
41
+ }
42
+ if (d.postRun !== void 0 && isPostRunSlow(d.postRun)) {
43
+ const { totalMs, persistMs, coverageScanMs } = d.postRun;
44
+ out.push({
45
+ severity: "warn",
46
+ title: `Post-run work took ${fmt(totalMs)} (persist ${fmt(persistMs)}, coverage scan ${fmt(coverageScanMs)})`,
47
+ detail: "Time vitest-agent spent after Vitest finished. Set persist: false where history is not needed (e.g. CI), or narrow coverage.include."
48
+ });
49
+ }
50
+ for (const hint of d.performanceHints) out.push({
51
+ severity: "info",
52
+ title: hint.project !== void 0 ? `${hint.project}: ${hint.message}` : hint.message,
53
+ detail: `${hint.remedy} — ${hint.detail} (${hint.learnMoreUrl})`
54
+ });
55
+ return out;
56
+ }
57
+
58
+ export { POST_RUN_SLOW_MIN_MS, POST_RUN_SLOW_MS, POST_RUN_SLOW_SHARE, isPostRunSlow, suggestedActionsFromRunDiagnostics };
@@ -0,0 +1,24 @@
1
+ // src/utils/vitest-config-files.ts
2
+ /**
3
+ * The config filenames Vitest 5 itself loads, in the order it prefers them:
4
+ * `vitest.config.*` before `vite.config.*`, each across
5
+ * `ts` / `mts` / `cts` / `js` / `mjs` / `cjs`. Mirrors `configFiles` in
6
+ * Vitest's own `constants.ts`. `vitest.workspace.*` is absent on purpose:
7
+ * Vitest no longer reads a workspace file (projects live in the config's
8
+ * `test.projects`).
9
+ *
10
+ * The single source both front ends derive their config lookups from, so the
11
+ * CLI's `check-test-path` and the MCP `run_tests` root walk cannot drift.
12
+ *
13
+ * @public
14
+ */
15
+ const VITEST_CONFIG_FILES = ["vitest.config", "vite.config"].flatMap((name) => [
16
+ "ts",
17
+ "mts",
18
+ "cts",
19
+ "js",
20
+ "mjs",
21
+ "cjs"
22
+ ].map((ext) => `${name}.${ext}`));
23
+
24
+ export { VITEST_CONFIG_FILES };
package/version.js CHANGED
@@ -11,6 +11,6 @@
11
11
  * survives into the published artifact.
12
12
  * @public
13
13
  */
14
- const CURRENT_SDK_VERSION = "7.1.0";
14
+ const CURRENT_SDK_VERSION = "9.0.0";
15
15
 
16
16
  export { CURRENT_SDK_VERSION };
@@ -1,44 +0,0 @@
1
- // src/utils/compress-lines.ts
2
- /**
3
- * Compress an array of line numbers into a compact range string.
4
- *
5
- * Consecutive line numbers are collapsed into `start-end` ranges,
6
- * separated by commas. Duplicate values are removed and the input
7
- * is sorted ascending before processing.
8
- *
9
- * @param lines - Array of line numbers to compress
10
- * @returns Compressed range string, or empty string for empty input
11
- *
12
- * @example
13
- * ```typescript
14
- * import { compressLines } from "@vitest-agent/sdk/utils";
15
- *
16
- * compressLines([1, 2, 3, 5, 10, 11, 12]);
17
- * // Returns: "1-3,5,10-12"
18
- *
19
- * compressLines([42]);
20
- * // Returns: "42"
21
- *
22
- * compressLines([]);
23
- * // Returns: ""
24
- * ```
25
- *
26
- * @public
27
- */
28
- function compressLines(lines) {
29
- if (lines.length === 0) return "";
30
- const sorted = [...new Set(lines)].sort((a, b) => a - b);
31
- const ranges = [];
32
- let start = sorted[0];
33
- let end = sorted[0];
34
- for (let i = 1; i < sorted.length; i++) if (sorted[i] === end + 1) end = sorted[i];
35
- else {
36
- ranges.push(start === end ? `${start}` : `${start}-${end}`);
37
- start = sorted[i];
38
- end = sorted[i];
39
- }
40
- ranges.push(start === end ? `${start}` : `${start}-${end}`);
41
- return ranges.join(",");
42
- }
43
-
44
- export { compressLines };
@@ -1,57 +0,0 @@
1
- // src/utils/detect-pm.ts
2
- const LOCKFILE_MAP = [
3
- ["pnpm-lock.yaml", "pnpm"],
4
- ["package-lock.json", "npm"],
5
- ["yarn.lock", "yarn"],
6
- ["bun.lock", "bun"]
7
- ];
8
- const RUN_COMMANDS = {
9
- pnpm: "pnpm vitest run",
10
- npm: "npx vitest run",
11
- yarn: "yarn vitest run",
12
- bun: "bun vitest run"
13
- };
14
- /**
15
- * Detect the package manager used in a project by inspecting the root directory.
16
- *
17
- * @remarks
18
- * Detection order:
19
- * 1. The `packageManager` field in `package.json` (e.g., `"pnpm\@10.32.1"`)
20
- * 2. Lockfile presence: `pnpm-lock.yaml`, `package-lock.json`, `yarn.lock`, `bun.lock`
21
- *
22
- * Returns `null` when no package manager can be determined.
23
- *
24
- * @param root - Absolute path to the project root directory
25
- * @param fs - Filesystem adapter for reading files and checking existence
26
- * @returns The detected package manager, or `null`
27
- *
28
- * @internal
29
- */
30
- async function detectPackageManager(root, fs) {
31
- try {
32
- const raw = await fs.readFile(`${root}/package.json`);
33
- const pkg = JSON.parse(raw);
34
- if (pkg.packageManager) {
35
- const name = pkg.packageManager.split("@")[0];
36
- if (name in RUN_COMMANDS) return name;
37
- }
38
- } catch {}
39
- for (const [lockfile, pm] of LOCKFILE_MAP) if (await fs.exists(`${root}/${lockfile}`)) return pm;
40
- return null;
41
- }
42
- /**
43
- * Return the vitest run command for the given package manager.
44
- *
45
- * Falls back to `"npx vitest run"` when `pm` is `null`.
46
- *
47
- * @param pm - Detected package manager, or `null` for unknown
48
- * @returns Shell command string for running vitest
49
- *
50
- * @internal
51
- */
52
- function getRunCommand(pm) {
53
- if (!pm) return "npx vitest run";
54
- return RUN_COMMANDS[pm];
55
- }
56
-
57
- export { detectPackageManager, getRunCommand };