@vitest-agent/sdk 6.0.0 → 7.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/index.d.ts CHANGED
@@ -28,6 +28,35 @@ export declare const FileCoverageReport: Schema.Struct<{
28
28
  }>;
29
29
  /** @public */
30
30
  export type FileCoverageReport = typeof FileCoverageReport.Type;
31
+ /**
32
+ * A threshold glob whose AGGREGATE coverage (summed covered/total across
33
+ * every file the glob matches, the same way Vitest evaluates a glob
34
+ * threshold) is below at least one of the glob's metric numbers.
35
+ *
36
+ * This is independent of the per-file lists: with an object `perFile` every
37
+ * matched file can pass while the glob as a whole still fails.
38
+ * @public
39
+ */
40
+ export declare const GlobShortfall: Schema.Struct<{
41
+ /** The threshold glob pattern as configured. */
42
+ readonly pattern: Schema.String;
43
+ /** Aggregate coverage percentages over the files the glob matches. */
44
+ readonly summary: Schema.Struct<{
45
+ readonly statements: Schema.Number;
46
+ readonly branches: Schema.Number;
47
+ readonly functions: Schema.Number;
48
+ readonly lines: Schema.Number;
49
+ }>;
50
+ /** The glob's own metric numbers that were set (`perFile` excluded). */
51
+ readonly thresholds: Schema.Struct<{
52
+ readonly lines: Schema.optional<Schema.Number>;
53
+ readonly functions: Schema.optional<Schema.Number>;
54
+ readonly branches: Schema.optional<Schema.Number>;
55
+ readonly statements: Schema.optional<Schema.Number>;
56
+ }>;
57
+ }>;
58
+ /** @public */
59
+ export type GlobShortfall = typeof GlobShortfall.Type;
31
60
  /**
32
61
  * Complete coverage report attached to an AgentReport.
33
62
  * @public
@@ -124,6 +153,24 @@ export declare const CoverageReport: Schema.Struct<{
124
153
  readonly uncoveredLines: Schema.String;
125
154
  }>>>;
126
155
  readonly belowTargetFiles: Schema.optional<Schema.$Array<Schema.String>>;
156
+ readonly globShortfalls: Schema.optional<Schema.$Array<Schema.Struct<{
157
+ /** The threshold glob pattern as configured. */
158
+ readonly pattern: Schema.String;
159
+ /** Aggregate coverage percentages over the files the glob matches. */
160
+ readonly summary: Schema.Struct<{
161
+ readonly statements: Schema.Number;
162
+ readonly branches: Schema.Number;
163
+ readonly functions: Schema.Number;
164
+ readonly lines: Schema.Number;
165
+ }>;
166
+ /** The glob's own metric numbers that were set (`perFile` excluded). */
167
+ readonly thresholds: Schema.Struct<{
168
+ readonly lines: Schema.optional<Schema.Number>;
169
+ readonly functions: Schema.optional<Schema.Number>;
170
+ readonly branches: Schema.optional<Schema.Number>;
171
+ readonly statements: Schema.optional<Schema.Number>;
172
+ }>;
173
+ }>>>;
127
174
  }>;
128
175
  /** @public */
129
176
  export type CoverageReport = typeof CoverageReport.Type;
@@ -249,6 +296,21 @@ export declare const CoverageRenderState: Schema.Struct<{
249
296
  readonly scoped: Schema.optional<Schema.Boolean>;
250
297
  readonly scopedFiles: Schema.optional<Schema.Number>;
251
298
  readonly totalFiles: Schema.optional<Schema.Number>;
299
+ readonly globShortfalls: Schema.optional<Schema.$Array<Schema.Struct<{
300
+ readonly pattern: Schema.String;
301
+ readonly summary: Schema.Struct<{
302
+ readonly statements: Schema.Number;
303
+ readonly branches: Schema.Number;
304
+ readonly functions: Schema.Number;
305
+ readonly lines: Schema.Number;
306
+ }>;
307
+ readonly thresholds: Schema.Struct<{
308
+ readonly lines: Schema.optional<Schema.Number>;
309
+ readonly functions: Schema.optional<Schema.Number>;
310
+ readonly branches: Schema.optional<Schema.Number>;
311
+ readonly statements: Schema.optional<Schema.Number>;
312
+ }>;
313
+ }>>>;
252
314
  }>;
253
315
  /** @public */
254
316
  export type CoverageRenderState = typeof CoverageRenderState.Type;
@@ -370,6 +432,21 @@ export declare const RenderState: Schema.Struct<{
370
432
  readonly scoped: Schema.optional<Schema.Boolean>;
371
433
  readonly scopedFiles: Schema.optional<Schema.Number>;
372
434
  readonly totalFiles: Schema.optional<Schema.Number>;
435
+ readonly globShortfalls: Schema.optional<Schema.$Array<Schema.Struct<{
436
+ readonly pattern: Schema.String;
437
+ readonly summary: Schema.Struct<{
438
+ readonly statements: Schema.Number;
439
+ readonly branches: Schema.Number;
440
+ readonly functions: Schema.Number;
441
+ readonly lines: Schema.Number;
442
+ }>;
443
+ readonly thresholds: Schema.Struct<{
444
+ readonly lines: Schema.optional<Schema.Number>;
445
+ readonly functions: Schema.optional<Schema.Number>;
446
+ readonly branches: Schema.optional<Schema.Number>;
447
+ readonly statements: Schema.optional<Schema.Number>;
448
+ }>;
449
+ }>>>;
373
450
  }>>;
374
451
  readonly trend: Schema.NullOr<Schema.Struct<{
375
452
  readonly direction: Schema.Literals<readonly ["improving", "regressing", "stable"]>;
@@ -418,6 +495,21 @@ export declare const RenderState: Schema.Struct<{
418
495
  readonly expected: Schema.optional<Schema.String>;
419
496
  readonly received: Schema.optional<Schema.String>;
420
497
  }>>;
498
+ /**
499
+ * What the run wrote straight to the terminal, past Vitest's console
500
+ * capture, folded from `RunFinished.strayOutput`. Absent when the run
501
+ * wrote nothing stray.
502
+ */
503
+ readonly strayOutput: Schema.optional<Schema.Struct<{
504
+ readonly total: Schema.Number;
505
+ readonly stdout: Schema.Number;
506
+ readonly stderr: Schema.Number;
507
+ readonly bytes: Schema.Number;
508
+ readonly samples: Schema.$Array<Schema.Struct<{
509
+ readonly stream: Schema.Literals<readonly ["stdout", "stderr"]>;
510
+ readonly text: Schema.String;
511
+ }>>;
512
+ }>>;
421
513
  }>;
422
514
  /** @public */
423
515
  export type RenderState = typeof RenderState.Type;
@@ -817,6 +909,21 @@ export declare const AgentReport: Schema.Struct<{
817
909
  readonly uncoveredLines: Schema.String;
818
910
  }>>>;
819
911
  readonly belowTargetFiles: Schema.optional<Schema.$Array<Schema.String>>;
912
+ readonly globShortfalls: Schema.optional<Schema.$Array<Schema.Struct<{
913
+ readonly pattern: Schema.String;
914
+ readonly summary: Schema.Struct<{
915
+ readonly statements: Schema.Number;
916
+ readonly branches: Schema.Number;
917
+ readonly functions: Schema.Number;
918
+ readonly lines: Schema.Number;
919
+ }>;
920
+ readonly thresholds: Schema.Struct<{
921
+ readonly lines: Schema.optional<Schema.Number>;
922
+ readonly functions: Schema.optional<Schema.Number>;
923
+ readonly branches: Schema.optional<Schema.Number>;
924
+ readonly statements: Schema.optional<Schema.Number>;
925
+ }>;
926
+ }>>>;
820
927
  }>>;
821
928
  readonly tagCounts: Schema.optional<Schema.$Record<Schema.String, Schema.Struct<{
822
929
  readonly passed: Schema.optional<Schema.Number>;
@@ -838,6 +945,16 @@ export declare const AgentReport: Schema.Struct<{
838
945
  readonly files: Schema.Number;
839
946
  }>>;
840
947
  }>>;
948
+ readonly strayOutput: Schema.optional<Schema.Struct<{
949
+ readonly total: Schema.Number;
950
+ readonly stdout: Schema.Number;
951
+ readonly stderr: Schema.Number;
952
+ readonly bytes: Schema.Number;
953
+ readonly samples: Schema.$Array<Schema.Struct<{
954
+ readonly stream: Schema.Literals<readonly ["stdout", "stderr"]>;
955
+ readonly text: Schema.String;
956
+ }>>;
957
+ }>>;
841
958
  }>;
842
959
  /** @public */
843
960
  export type AgentReport = typeof AgentReport.Type;
@@ -929,13 +1046,6 @@ export type Environment = typeof Environment.Type;
929
1046
  export declare const Executor: Schema.Literals<readonly ["human", "agent", "ci"]>;
930
1047
  /** @public */
931
1048
  export type Executor = typeof Executor.Type;
932
- /**
933
- * Output format for the reporter pipeline.
934
- * @public
935
- */
936
- export declare const OutputFormat: Schema.Literals<readonly ["terminal", "markdown", "json", "vitest-bypass", "silent", "ci-annotations"]>;
937
- /** @public */
938
- export type OutputFormat = typeof OutputFormat.Type;
939
1049
  /**
940
1050
  * Level of detail in reporter output.
941
1051
  * @public
@@ -1173,6 +1283,21 @@ export declare const RunEvent: Schema.Union<readonly [Schema.TaggedStruct<"RunSt
1173
1283
  readonly scoped: Schema.optional<Schema.Boolean>;
1174
1284
  readonly scopedFiles: Schema.optional<Schema.Number>;
1175
1285
  readonly totalFiles: Schema.optional<Schema.Number>;
1286
+ readonly globShortfalls: Schema.optional<Schema.$Array<Schema.Struct<{
1287
+ readonly pattern: Schema.String;
1288
+ readonly summary: Schema.Struct<{
1289
+ readonly statements: Schema.Number;
1290
+ readonly branches: Schema.Number;
1291
+ readonly functions: Schema.Number;
1292
+ readonly lines: Schema.Number;
1293
+ }>;
1294
+ readonly thresholds: Schema.Struct<{
1295
+ readonly lines: Schema.optional<Schema.Number>;
1296
+ readonly functions: Schema.optional<Schema.Number>;
1297
+ readonly branches: Schema.optional<Schema.Number>;
1298
+ readonly statements: Schema.optional<Schema.Number>;
1299
+ }>;
1300
+ }>>>;
1176
1301
  }>, Schema.TaggedStruct<"ThresholdViolation", {
1177
1302
  readonly metric: Schema.Literals<readonly ["lines", "branches", "functions", "statements"]>;
1178
1303
  readonly expected: Schema.Number;
@@ -1215,6 +1340,21 @@ export declare const RunEvent: Schema.Union<readonly [Schema.TaggedStruct<"RunSt
1215
1340
  readonly expected: Schema.optional<Schema.String>;
1216
1341
  readonly received: Schema.optional<Schema.String>;
1217
1342
  }>>>;
1343
+ /**
1344
+ * What the run wrote straight to the terminal, past Vitest's console
1345
+ * capture (`AgentReport.strayOutput`). Optional: absent when the run
1346
+ * wrote nothing stray, and in older replay data.
1347
+ */
1348
+ readonly strayOutput: Schema.optional<Schema.Struct<{
1349
+ readonly total: Schema.Number;
1350
+ readonly stdout: Schema.Number;
1351
+ readonly stderr: Schema.Number;
1352
+ readonly bytes: Schema.Number;
1353
+ readonly samples: Schema.$Array<Schema.Struct<{
1354
+ readonly stream: Schema.Literals<readonly ["stdout", "stderr"]>;
1355
+ readonly text: Schema.String;
1356
+ }>>;
1357
+ }>>;
1218
1358
  }>]>;
1219
1359
  /** @public */
1220
1360
  export type RunEvent = typeof RunEvent.Type;
@@ -1354,10 +1494,10 @@ export type Transport = typeof Transport.Type;
1354
1494
  * plugin always populates it in practice — the option exists for clarity
1355
1495
  * and to leave room for future "no-persistence" experiments.
1356
1496
  *
1357
- * `format` and `detail` are pre-resolved by the plugin (via
1358
- * `FormatSelector` / `DetailResolver`) but reporters that want to override
1359
- * can ignore them. `noColor` is the resolved value of the `NO_COLOR` env
1360
- * var; reporters use it to gate ANSI escapes and OSC-8 hyperlinks.
1497
+ * `detail` is pre-resolved by the plugin (via `DetailResolver`) but
1498
+ * reporters that want to override can ignore it. `noColor` is the resolved
1499
+ * value of the `NO_COLOR` env var; reporters use it to gate ANSI escapes and
1500
+ * OSC-8 hyperlinks.
1361
1501
  * @public
1362
1502
  */
1363
1503
  export interface ResolvedReporterConfig {
@@ -1396,7 +1536,6 @@ export interface ResolvedReporterConfig {
1396
1536
  * @internal
1397
1537
  */
1398
1538
  readonly coverageMode: "full" | "ui-only";
1399
- readonly format: OutputFormat;
1400
1539
  readonly detail: DetailLevel;
1401
1540
  readonly noColor: boolean;
1402
1541
  readonly runCommand?: string;
@@ -1518,6 +1657,24 @@ export interface VitestAgentReporter {
1518
1657
  * awaits it and treats a rejection as a logged warning.
1519
1658
  */
1520
1659
  readonly close?: () => Promise<void>;
1660
+ /**
1661
+ * Print one whole line of stray output: bytes a test process wrote
1662
+ * straight to the terminal, past Vitest's console capture (a child
1663
+ * process spawned with inherited stdio is the usual source). A reporter
1664
+ * that draws in place (a live view) implements this to print the line
1665
+ * above its drawing instead of under it, where the next redraw would
1666
+ * strand it. Return `true` when the line was taken; `false` sends it to
1667
+ * the terminal unchanged.
1668
+ *
1669
+ * The plugin calls it synchronously, from the factory's return until just
1670
+ * before {@link VitestAgentReporter.close | close}, only for lines bound
1671
+ * for the process's own stdout or stderr (never when Vitest was given
1672
+ * other streams, as MCP's `run_tests` does), and only while it owns the
1673
+ * console (any mode but `passthrough`). The line carries no trailing
1674
+ * newline. Optional; without it every line passes through unchanged. The
1675
+ * run's stray output is reported on `AgentReport.strayOutput` either way.
1676
+ */
1677
+ readonly printStrayLine?: (stream: "stdout" | "stderr", line: string) => boolean;
1521
1678
  }
1522
1679
  /**
1523
1680
  * Factory that the plugin's `reporter` option accepts. The plugin calls
@@ -2789,6 +2946,21 @@ export declare const RunReportFile: Schema.Struct<{
2789
2946
  readonly uncoveredLines: Schema.String;
2790
2947
  }>>>;
2791
2948
  readonly belowTargetFiles: Schema.optional<Schema.$Array<Schema.String>>;
2949
+ readonly globShortfalls: Schema.optional<Schema.$Array<Schema.Struct<{
2950
+ readonly pattern: Schema.String;
2951
+ readonly summary: Schema.Struct<{
2952
+ readonly statements: Schema.Number;
2953
+ readonly branches: Schema.Number;
2954
+ readonly functions: Schema.Number;
2955
+ readonly lines: Schema.Number;
2956
+ }>;
2957
+ readonly thresholds: Schema.Struct<{
2958
+ readonly lines: Schema.optional<Schema.Number>;
2959
+ readonly functions: Schema.optional<Schema.Number>;
2960
+ readonly branches: Schema.optional<Schema.Number>;
2961
+ readonly statements: Schema.optional<Schema.Number>;
2962
+ }>;
2963
+ }>>>;
2792
2964
  }>>;
2793
2965
  readonly tagCounts: Schema.optional<Schema.$Record<Schema.String, Schema.Struct<{
2794
2966
  readonly passed: Schema.optional<Schema.Number>;
@@ -2810,11 +2982,62 @@ export declare const RunReportFile: Schema.Struct<{
2810
2982
  readonly files: Schema.Number;
2811
2983
  }>>;
2812
2984
  }>>;
2985
+ readonly strayOutput: Schema.optional<Schema.Struct<{
2986
+ readonly total: Schema.Number;
2987
+ readonly stdout: Schema.Number;
2988
+ readonly stderr: Schema.Number;
2989
+ readonly bytes: Schema.Number;
2990
+ readonly samples: Schema.$Array<Schema.Struct<{
2991
+ readonly stream: Schema.Literals<readonly ["stdout", "stderr"]>;
2992
+ readonly text: Schema.String;
2993
+ }>>;
2994
+ }>>;
2813
2995
  }>>;
2814
2996
  }>;
2815
2997
  /** @public */
2816
2998
  export type RunReportFile = typeof RunReportFile.Type;
2817
2999
  //#endregion
3000
+ //#region src/schemas/StrayOutput.d.ts
3001
+ /**
3002
+ * One line of stray output, kept as a sample: the stream it was written to,
3003
+ * and its text with escapes stripped, cut to a sane length.
3004
+ * @public
3005
+ */
3006
+ export declare const StrayOutputSample: Schema.Struct<{
3007
+ readonly stream: Schema.Literals<readonly ["stdout", "stderr"]>;
3008
+ readonly text: Schema.String;
3009
+ }>;
3010
+ /** @public */
3011
+ export type StrayOutputSample = typeof StrayOutputSample.Type;
3012
+ /**
3013
+ * Output a test run wrote straight to the terminal, past Vitest's console
3014
+ * capture: bytes a worker process (or a child process it spawned with
3015
+ * inherited stdio, such as `execFileSync("git", ...)`) wrote to its own
3016
+ * stdout or stderr, which Vitest pipes through to the main process
3017
+ * untouched. Distinct from `consoleLeaks`, which counts `console.*` calls
3018
+ * Vitest did capture.
3019
+ *
3020
+ * A run-level signal: the bytes arrive on one stream shared by every project,
3021
+ * so nothing attributes them to a project, file or test. Every project report
3022
+ * of a run carries the same value. Counts are of non-blank lines; `bytes`
3023
+ * counts everything recorded. `samples` holds the first few lines. Omitted
3024
+ * when the run wrote nothing stray (and whenever the plugin does not own the
3025
+ * console: `passthrough` mode records nothing).
3026
+ * @public
3027
+ */
3028
+ export declare const StrayOutput: Schema.Struct<{
3029
+ readonly total: Schema.Number;
3030
+ readonly stdout: Schema.Number;
3031
+ readonly stderr: Schema.Number;
3032
+ readonly bytes: Schema.Number;
3033
+ readonly samples: Schema.$Array<Schema.Struct<{
3034
+ readonly stream: Schema.Literals<readonly ["stdout", "stderr"]>;
3035
+ readonly text: Schema.String;
3036
+ }>>;
3037
+ }>;
3038
+ /** @public */
3039
+ export type StrayOutput = typeof StrayOutput.Type;
3040
+ //#endregion
2818
3041
  //#region src/schemas/Tdd.d.ts
2819
3042
  /** @public */
2820
3043
  export declare const GoalStatus: Schema.Literals<readonly ["pending", "in_progress", "done", "abandoned"]>;
@@ -4022,6 +4245,79 @@ export declare const probeHostMetadataFromEnv: (env: Record<string, string | und
4022
4245
  */
4023
4246
  export declare function safeFilename(name: string): string;
4024
4247
  //#endregion
4248
+ //#region src/utils/stray-output.d.ts
4249
+ /**
4250
+ * A stream stray output was written to.
4251
+ * @public
4252
+ */
4253
+ export type StrayOutputStream = "stdout" | "stderr";
4254
+ /**
4255
+ * Records stray output for one run at a time: counts its non-blank lines per
4256
+ * stream and its bytes, and keeps the first few lines as samples. Memory is
4257
+ * bounded whatever is written: only the samples and one partial line per
4258
+ * stream (itself cut to the sample length) are held.
4259
+ * @public
4260
+ */
4261
+ export interface StrayOutputRecorder {
4262
+ /** Record text written to `stream`. A write made only of escape sequences is ignored. */
4263
+ readonly write: (stream: StrayOutputStream, text: string) => void;
4264
+ /** What the run has written so far, a trailing partial line included; `undefined` when nothing. */
4265
+ readonly snapshot: () => StrayOutput | undefined;
4266
+ /** Forget everything: a new run starts. */
4267
+ readonly reset: () => void;
4268
+ }
4269
+ /**
4270
+ * Options for {@link makeStrayOutputRecorder}.
4271
+ * @public
4272
+ */
4273
+ export interface StrayOutputRecorderOptions {
4274
+ /** How many lines to keep as samples. @defaultValue 5 */
4275
+ readonly maxSamples?: number;
4276
+ /** How many characters of a sample to keep before cutting it with `…`. @defaultValue 160 */
4277
+ readonly maxSampleChars?: number;
4278
+ }
4279
+ /**
4280
+ * True when `text` is nothing but terminal escape sequences: the cursor
4281
+ * hide/show, clear-screen and erase-scrollback writes Vitest's own `Logger`
4282
+ * makes straight to its output stream. Empty text is not escape-only.
4283
+ * @public
4284
+ */
4285
+ export declare const isEscapeOnly: (text: string) => boolean;
4286
+ /**
4287
+ * Make a {@link StrayOutputRecorder}.
4288
+ * @public
4289
+ */
4290
+ export declare const makeStrayOutputRecorder: (options?: StrayOutputRecorderOptions) => StrayOutputRecorder;
4291
+ /**
4292
+ * The key a stray-output capture publishes its {@link StrayOutputSource}
4293
+ * under, on the Vitest `Logger` whose streams it wraps. `Symbol.for`, so a
4294
+ * plugin and an MCP server that load different copies of this package still
4295
+ * meet on it.
4296
+ * @public
4297
+ */
4298
+ export declare const STRAY_OUTPUT_SOURCE: unique symbol;
4299
+ /**
4300
+ * What a stray-output capture publishes for a reader that did not install it.
4301
+ * @public
4302
+ */
4303
+ export interface StrayOutputSource {
4304
+ /** The current run's stray output, or `undefined` when it wrote none. */
4305
+ readonly snapshot: () => StrayOutput | undefined;
4306
+ }
4307
+ /**
4308
+ * Read the current run's stray output from a Vitest `Logger` a capture was
4309
+ * installed on. `undefined` when none was installed, or the run wrote nothing.
4310
+ * @public
4311
+ */
4312
+ export declare const readStrayOutput: (logger: unknown) => StrayOutput | undefined;
4313
+ /**
4314
+ * The note a renderer prints for a run's stray output: one line naming the
4315
+ * count, the streams, the usual cause and the fix, then up to three sample
4316
+ * lines and how many it left out.
4317
+ * @public
4318
+ */
4319
+ export declare const formatStrayOutputNote: (stray: StrayOutput) => ReadonlyArray<string>;
4320
+ //#endregion
4025
4321
  //#region src/utils/test-location.d.ts
4026
4322
  /** Directory holding source files whose tests may be co-located. @public */
4027
4323
  export declare const SRC_DIR = "src";
package/index.js CHANGED
@@ -7,10 +7,11 @@ import { GitCommandError, ProbeMissError } from "./errors/RunContextError.js";
7
7
  import { BehaviorNotFoundError, GoalNotFoundError, IllegalStatusTransitionError, TddTaskAlreadyEndedError, TddTaskNotFoundError } from "./errors/TddErrors.js";
8
8
  import { ActorType, AgentId, ChatId, ConversationId, HostKind, ProjectKey, TddTaskId } from "./schemas/Identity.js";
9
9
  import { Agent, IdempotencyHit } from "./schemas/Agent.js";
10
- import { AgentConsoleMode, CiConsoleMode, ConsoleMode, ConsoleOutputMode, DetailLevel, Environment, Executor, HumanConsoleMode, OutputFormat, PackageManager, ReportError, TestClassification, TestRunReason, TestState } from "./schemas/Common.js";
10
+ import { AgentConsoleMode, CiConsoleMode, ConsoleMode, ConsoleOutputMode, DetailLevel, Environment, Executor, HumanConsoleMode, PackageManager, ReportError, TestClassification, TestRunReason, TestState } from "./schemas/Common.js";
11
11
  import { ConsoleLeakFile, ConsoleLeaks, ConsoleLeaksFromFailingTests } from "./schemas/ConsoleLeaks.js";
12
12
  import { MetricThresholds, PatternMetricThresholds, PatternThresholds, PerFileThresholds, ResolvedThresholds } from "./schemas/Thresholds.js";
13
- import { CoverageReport, CoverageTotals, FileCoverageReport } from "./schemas/Coverage.js";
13
+ import { CoverageReport, CoverageTotals, FileCoverageReport, GlobShortfall } from "./schemas/Coverage.js";
14
+ import { StrayOutput, StrayOutputSample } from "./schemas/StrayOutput.js";
14
15
  import { AgentReport, ModuleReport, ReportSummary, TagCountEntry, TestReport } from "./schemas/AgentReport.js";
15
16
  import { CoverageBaselines } from "./schemas/Baselines.js";
16
17
  import { CacheManifest, CacheManifestEntry } from "./schemas/CacheManifest.js";
@@ -57,9 +58,10 @@ import { normalizeWorkspaceKey } from "./utils/normalize-workspace-key.js";
57
58
  import { basenamePosix, joinPosix, relativePosix, toPosix } from "./utils/posix-path.js";
58
59
  import { probeHostMetadataFromEnv } from "./utils/probe-host-metadata.js";
59
60
  import { safeFilename } from "./utils/safe-filename.js";
61
+ import { STRAY_OUTPUT_SOURCE, formatStrayOutputNote, isEscapeOnly, makeStrayOutputRecorder, readStrayOutput } from "./utils/stray-output.js";
60
62
  import { BATS_FILE_GLOB_SUFFIX, NON_DISCOVERABLE_DIRS, SRC_DIR, TEST_DIR, TEST_FILE_GLOB_SUFFIX, TEST_HELPER_DIRS, classifyTestPath, findOwningWorkspace, isTestFileName } from "./utils/test-location.js";
61
63
  import { validateCoverageTargetsShape } from "./utils/validate-coverage-targets-shape.js";
62
64
  import { requiredArtifactForTransition, transitionEnforcesBehaviorMatch, validatePhaseTransition } from "./utils/validate-phase-transition.js";
63
65
  import { CURRENT_SDK_VERSION } from "./version.js";
64
66
 
65
- export { ActionSeverity, ActorType, Agent, AgentConsoleMode, AgentId, AgentNotFoundError, AgentPluginOptions, AgentReport, AgentReporterOptions, BATS_FILE_GLOB_SUFFIX, BehaviorAbandonedEvent, BehaviorAddedEvent, BehaviorCompletedEvent, BehaviorDetail, BehaviorNotFoundError, BehaviorRow, BehaviorScopedEventTypes, BehaviorStartedEvent, BehaviorStatus, BehaviorsReadyEvent, BlockedEvent, CURRENT_SDK_VERSION, CacheManifest, CacheManifestEntry, ChannelEvent, ChatId, CiConsoleMode, ConsoleLeakFile, ConsoleLeaks, ConsoleLeaksFromFailingTests, ConsoleLogLevel, ConsoleMode, ConsoleOutputMode, ConsoleOutputs, ConversationId, ConversationNotFoundError, CoverageBaselines, FileCoverageReport as CoverageFile, CoverageGap, CoverageLevel, CoverageMetric, CoverageRenderState, CoverageReport, CoverageTargets, CoverageTargetsMetrics, CoverageTotals, DataStoreError, DetailLevel, DiscoveryError, Environment, Executor, FailureRecord, FileCoverageReport, FileEditPayload, GitCommandError, GoalAbandonedEvent, GoalAddedEvent, GoalCompletedEvent, GoalDetail, GoalNotFoundError, GoalRow, GoalStartedEvent, GoalStatus, GoalsReadyEvent, HistoryRecord, HookFirePayload, HookStatus, HookType, HostKind, HostMetadataParseError, HumanConsoleMode, HypothesisPayload, IdempotencyHit, IllegalStatusTransitionError, MetricThresholds, ModuleRecord, ModuleReport, NON_DISCOVERABLE_DIRS, NotePayload, OutputFormat, PackageManager, PathResolutionError, PatternMetricThresholds, PatternThresholds, PerFileThresholds, PhaseTransitionEvent, ProbeMissError, ProjectIdentityNotResolvableError, ProjectKey, RUN_REPORT_FILE_SCHEMA_URL, RegistrationConflictError, RenderState, RenderTotals, ReportError, ReportOption, ReportSettings, ReportSummary, ResolvedThresholds, RunEvent, RunReportFile, SRC_DIR, SessionCompleteEvent, SessionNotFoundError, SessionOutcome, SidecarTimeoutError, SuggestedActionRecord, TEST_DIR, TEST_FILE_GLOB_SUFFIX, TEST_HELPER_DIRS, TagCountEntry, TddTaskAlreadyEndedError, TddTaskId, TddTaskNotFoundError, TestAnnotation, TestArtifact, TestArtifactLocation, TestAttachment, TestClassification, TestHistory, TestRecord, TestReport, TestRun, TestRunReason, TestState, ToolCallPayload, ToolResultPayload, Transport, TrendEntry, TrendRecord, TurnPayload, UserPromptPayload, VitestAgentConfig, ansi, basenamePosix, buildAgentReport, buildConsoleLeaks, buildEnvPrefix, canonicalizeGitUrl, classifyTest, classifyTestPath, coerceErrorField, coerceErrorText, collectConsoleLeakEntries, compressLines, computeTrend, countSuiteFailures, detectNonDefaultDiscoverStrategy, detectPackageManager, detectVitestScripts, determineTier, extractSqlReason, findFunctionBoundary, findOwningWorkspace, formatConsoleMarkdown, formatFatalError, formatGfm, formatScopedCoverageNote, getRecentDirection, getRunCommand, getWorstMetric, gitUrlToProjectKey, hashTargets, initialRenderState, isTestFileName, isTimeoutError, isVitestInvocation, joinPosix, normalizeWorkspaceKey, osc8, probeHostMetadataFromEnv, relativePath, relativePosix, requiredArtifactForTransition, resolveCoverageInput, rewriteBashCommand, safeFilename, stripAnsi, toPosix, transitionEnforcesBehaviorMatch, validateCoverageConfig, validateCoverageTargetsShape, validatePhaseTransition };
67
+ export { ActionSeverity, ActorType, Agent, AgentConsoleMode, AgentId, AgentNotFoundError, AgentPluginOptions, AgentReport, AgentReporterOptions, BATS_FILE_GLOB_SUFFIX, BehaviorAbandonedEvent, BehaviorAddedEvent, BehaviorCompletedEvent, BehaviorDetail, BehaviorNotFoundError, BehaviorRow, BehaviorScopedEventTypes, BehaviorStartedEvent, BehaviorStatus, BehaviorsReadyEvent, BlockedEvent, CURRENT_SDK_VERSION, CacheManifest, CacheManifestEntry, ChannelEvent, ChatId, CiConsoleMode, ConsoleLeakFile, ConsoleLeaks, ConsoleLeaksFromFailingTests, ConsoleLogLevel, ConsoleMode, ConsoleOutputMode, ConsoleOutputs, ConversationId, ConversationNotFoundError, CoverageBaselines, FileCoverageReport as CoverageFile, CoverageGap, CoverageLevel, CoverageMetric, CoverageRenderState, CoverageReport, CoverageTargets, CoverageTargetsMetrics, CoverageTotals, DataStoreError, DetailLevel, DiscoveryError, Environment, Executor, FailureRecord, FileCoverageReport, FileEditPayload, GitCommandError, GlobShortfall, GoalAbandonedEvent, GoalAddedEvent, GoalCompletedEvent, GoalDetail, GoalNotFoundError, GoalRow, GoalStartedEvent, GoalStatus, GoalsReadyEvent, HistoryRecord, HookFirePayload, HookStatus, HookType, HostKind, HostMetadataParseError, HumanConsoleMode, HypothesisPayload, IdempotencyHit, IllegalStatusTransitionError, MetricThresholds, ModuleRecord, ModuleReport, NON_DISCOVERABLE_DIRS, NotePayload, PackageManager, PathResolutionError, PatternMetricThresholds, PatternThresholds, PerFileThresholds, PhaseTransitionEvent, ProbeMissError, ProjectIdentityNotResolvableError, ProjectKey, RUN_REPORT_FILE_SCHEMA_URL, RegistrationConflictError, RenderState, RenderTotals, ReportError, ReportOption, ReportSettings, ReportSummary, ResolvedThresholds, RunEvent, RunReportFile, SRC_DIR, STRAY_OUTPUT_SOURCE, SessionCompleteEvent, SessionNotFoundError, SessionOutcome, SidecarTimeoutError, StrayOutput, StrayOutputSample, SuggestedActionRecord, TEST_DIR, TEST_FILE_GLOB_SUFFIX, TEST_HELPER_DIRS, TagCountEntry, TddTaskAlreadyEndedError, TddTaskId, TddTaskNotFoundError, TestAnnotation, TestArtifact, TestArtifactLocation, TestAttachment, TestClassification, TestHistory, TestRecord, TestReport, TestRun, TestRunReason, TestState, ToolCallPayload, ToolResultPayload, Transport, TrendEntry, TrendRecord, TurnPayload, UserPromptPayload, VitestAgentConfig, ansi, basenamePosix, buildAgentReport, buildConsoleLeaks, buildEnvPrefix, canonicalizeGitUrl, classifyTest, classifyTestPath, coerceErrorField, coerceErrorText, collectConsoleLeakEntries, compressLines, computeTrend, countSuiteFailures, detectNonDefaultDiscoverStrategy, detectPackageManager, detectVitestScripts, determineTier, extractSqlReason, findFunctionBoundary, findOwningWorkspace, formatConsoleMarkdown, formatFatalError, formatGfm, formatScopedCoverageNote, formatStrayOutputNote, getRecentDirection, getRunCommand, getWorstMetric, gitUrlToProjectKey, hashTargets, initialRenderState, isEscapeOnly, isTestFileName, isTimeoutError, isVitestInvocation, joinPosix, makeStrayOutputRecorder, normalizeWorkspaceKey, osc8, probeHostMetadataFromEnv, readStrayOutput, relativePath, relativePosix, requiredArtifactForTransition, resolveCoverageInput, rewriteBashCommand, safeFilename, stripAnsi, toPosix, transitionEnforcesBehaviorMatch, validateCoverageConfig, validateCoverageTargetsShape, validatePhaseTransition };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vitest-agent/sdk",
3
- "version": "6.0.0",
3
+ "version": "7.0.0",
4
4
  "private": false,
5
5
  "description": "Platform-free core for the vitest-agent package family: Effect Schema definitions, reporter and dispatcher contracts, errors, and pure utilities.",
6
6
  "keywords": [
@@ -41,9 +41,9 @@
41
41
  "./package.json": "./package.json"
42
42
  },
43
43
  "dependencies": {
44
- "acorn": "^8.18.0",
44
+ "acorn": "^8.19.0",
45
45
  "acorn-typescript": "^1.4.13",
46
- "effect": "^4.0.0"
46
+ "effect": "^4.0.2"
47
47
  },
48
48
  "engines": {
49
49
  "node": ">=24.11.0"
@@ -526,6 +526,22 @@
526
526
  "required": ["file", "summary", "uncoveredLines"],
527
527
  "additionalProperties": true
528
528
  },
529
+ "GlobShortfall": {
530
+ "type": "object",
531
+ "properties": {
532
+ "pattern": {
533
+ "type": "string"
534
+ },
535
+ "summary": {
536
+ "$ref": "#/$defs/CoverageTotals"
537
+ },
538
+ "thresholds": {
539
+ "$ref": "#/$defs/MetricThresholds"
540
+ }
541
+ },
542
+ "required": ["pattern", "summary", "thresholds"],
543
+ "additionalProperties": true
544
+ },
529
545
  "CoverageReport": {
530
546
  "type": "object",
531
547
  "properties": {
@@ -665,6 +681,19 @@
665
681
  "type": "null"
666
682
  }
667
683
  ]
684
+ },
685
+ "globShortfalls": {
686
+ "anyOf": [
687
+ {
688
+ "type": "array",
689
+ "items": {
690
+ "$ref": "#/$defs/GlobShortfall"
691
+ }
692
+ },
693
+ {
694
+ "type": "null"
695
+ }
696
+ ]
668
697
  }
669
698
  },
670
699
  "required": ["totals", "thresholds", "lowCoverage", "lowCoverageFiles"],
@@ -858,6 +887,77 @@
858
887
  "required": ["total", "byFile"],
859
888
  "additionalProperties": true
860
889
  },
890
+ "StrayOutputSample": {
891
+ "type": "object",
892
+ "properties": {
893
+ "stream": {
894
+ "type": "string",
895
+ "enum": ["stdout", "stderr"]
896
+ },
897
+ "text": {
898
+ "type": "string"
899
+ }
900
+ },
901
+ "required": ["stream", "text"],
902
+ "additionalProperties": true
903
+ },
904
+ "StrayOutput": {
905
+ "type": "object",
906
+ "properties": {
907
+ "total": {
908
+ "anyOf": [
909
+ {
910
+ "type": "number"
911
+ },
912
+ {
913
+ "type": "string",
914
+ "enum": ["Infinity", "-Infinity", "NaN"]
915
+ }
916
+ ]
917
+ },
918
+ "stdout": {
919
+ "anyOf": [
920
+ {
921
+ "type": "number"
922
+ },
923
+ {
924
+ "type": "string",
925
+ "enum": ["Infinity", "-Infinity", "NaN"]
926
+ }
927
+ ]
928
+ },
929
+ "stderr": {
930
+ "anyOf": [
931
+ {
932
+ "type": "number"
933
+ },
934
+ {
935
+ "type": "string",
936
+ "enum": ["Infinity", "-Infinity", "NaN"]
937
+ }
938
+ ]
939
+ },
940
+ "bytes": {
941
+ "anyOf": [
942
+ {
943
+ "type": "number"
944
+ },
945
+ {
946
+ "type": "string",
947
+ "enum": ["Infinity", "-Infinity", "NaN"]
948
+ }
949
+ ]
950
+ },
951
+ "samples": {
952
+ "type": "array",
953
+ "items": {
954
+ "$ref": "#/$defs/StrayOutputSample"
955
+ }
956
+ }
957
+ },
958
+ "required": ["total", "stdout", "stderr", "bytes", "samples"],
959
+ "additionalProperties": true
960
+ },
861
961
  "AgentReport": {
862
962
  "type": "object",
863
963
  "properties": {
@@ -930,6 +1030,16 @@
930
1030
  "type": "null"
931
1031
  }
932
1032
  ]
1033
+ },
1034
+ "strayOutput": {
1035
+ "anyOf": [
1036
+ {
1037
+ "$ref": "#/$defs/StrayOutput"
1038
+ },
1039
+ {
1040
+ "type": "null"
1041
+ }
1042
+ ]
933
1043
  }
934
1044
  },
935
1045
  "required": ["timestamp", "reason", "summary", "failed", "unhandledErrors", "failedFiles"],
@@ -1,6 +1,7 @@
1
1
  import { ReportError, TestClassification, TestRunReason, TestState } from "./Common.js";
2
2
  import { ConsoleLeaks } from "./ConsoleLeaks.js";
3
3
  import { CoverageReport } from "./Coverage.js";
4
+ import { StrayOutput } from "./StrayOutput.js";
4
5
  import { Schema } from "effect";
5
6
 
6
7
  //#region src/schemas/AgentReport.ts
@@ -71,7 +72,8 @@ const AgentReport = Schema.Struct({
71
72
  failedFiles: Schema.Array(Schema.String),
72
73
  coverage: Schema.optional(CoverageReport),
73
74
  tagCounts: Schema.optional(Schema.Record(Schema.String, TagCountEntry)),
74
- consoleLeaks: Schema.optional(ConsoleLeaks)
75
+ consoleLeaks: Schema.optional(ConsoleLeaks),
76
+ strayOutput: Schema.optional(StrayOutput)
75
77
  }).annotate({ identifier: "AgentReport" });
76
78
 
77
79
  //#endregion
package/schemas/Common.js CHANGED
@@ -116,18 +116,6 @@ const Executor = Schema.Literals([
116
116
  "ci"
117
117
  ]).annotate({ identifier: "Executor" });
118
118
  /**
119
- * Output format for the reporter pipeline.
120
- * @public
121
- */
122
- const OutputFormat = Schema.Literals([
123
- "terminal",
124
- "markdown",
125
- "json",
126
- "vitest-bypass",
127
- "silent",
128
- "ci-annotations"
129
- ]).annotate({ identifier: "OutputFormat" });
130
- /**
131
119
  * Level of detail in reporter output.
132
120
  * @public
133
121
  */
@@ -158,4 +146,4 @@ const ReportError = Schema.Struct({
158
146
  }).annotate({ identifier: "ReportError" });
159
147
 
160
148
  //#endregion
161
- export { AgentConsoleMode, CiConsoleMode, ConsoleMode, ConsoleOutputMode, DetailLevel, Environment, Executor, HumanConsoleMode, OutputFormat, PackageManager, ReportError, TestClassification, TestRunReason, TestState };
149
+ export { AgentConsoleMode, CiConsoleMode, ConsoleMode, ConsoleOutputMode, DetailLevel, Environment, Executor, HumanConsoleMode, PackageManager, ReportError, TestClassification, TestRunReason, TestState };
@@ -22,6 +22,23 @@ const FileCoverageReport = Schema.Struct({
22
22
  uncoveredLines: Schema.String
23
23
  }).annotate({ identifier: "FileCoverageReport" });
24
24
  /**
25
+ * A threshold glob whose AGGREGATE coverage (summed covered/total across
26
+ * every file the glob matches, the same way Vitest evaluates a glob
27
+ * threshold) is below at least one of the glob's metric numbers.
28
+ *
29
+ * This is independent of the per-file lists: with an object `perFile` every
30
+ * matched file can pass while the glob as a whole still fails.
31
+ * @public
32
+ */
33
+ const GlobShortfall = Schema.Struct({
34
+ /** The threshold glob pattern as configured. */
35
+ pattern: Schema.String,
36
+ /** Aggregate coverage percentages over the files the glob matches. */
37
+ summary: CoverageTotals,
38
+ /** The glob's own metric numbers that were set (`perFile` excluded). */
39
+ thresholds: MetricThresholds
40
+ }).annotate({ identifier: "GlobShortfall" });
41
+ /**
25
42
  * Complete coverage report attached to an AgentReport.
26
43
  * @public
27
44
  */
@@ -45,8 +62,9 @@ const CoverageReport = Schema.Struct({
45
62
  lowCoverage: Schema.Array(FileCoverageReport),
46
63
  lowCoverageFiles: Schema.Array(Schema.String),
47
64
  belowTarget: Schema.optional(Schema.Array(FileCoverageReport)),
48
- belowTargetFiles: Schema.optional(Schema.Array(Schema.String))
65
+ belowTargetFiles: Schema.optional(Schema.Array(Schema.String)),
66
+ globShortfalls: Schema.optional(Schema.Array(GlobShortfall))
49
67
  }).annotate({ identifier: "CoverageReport" });
50
68
 
51
69
  //#endregion
52
- export { CoverageReport, CoverageTotals, FileCoverageReport };
70
+ export { CoverageReport, CoverageTotals, FileCoverageReport, GlobShortfall };
@@ -1,6 +1,7 @@
1
1
  import { ReportError, TestClassification, TestState } from "./Common.js";
2
2
  import { MetricThresholds } from "./Thresholds.js";
3
- import { CoverageTotals } from "./Coverage.js";
3
+ import { CoverageTotals, GlobShortfall } from "./Coverage.js";
4
+ import { StrayOutput } from "./StrayOutput.js";
4
5
  import { ActionSeverity, CoverageGap, CoverageMetric } from "./RunEvent.js";
5
6
  import { Schema } from "effect";
6
7
 
@@ -79,7 +80,8 @@ const CoverageRenderState = Schema.Struct({
79
80
  })),
80
81
  scoped: Schema.optional(Schema.Boolean),
81
82
  scopedFiles: Schema.optional(Schema.Number),
82
- totalFiles: Schema.optional(Schema.Number)
83
+ totalFiles: Schema.optional(Schema.Number),
84
+ globShortfalls: Schema.optional(Schema.Array(GlobShortfall))
83
85
  }).annotate({ identifier: "CoverageRenderState" });
84
86
  /**
85
87
  * A `SuggestedAction` event, denormalized into the queue the renderer reads.
@@ -155,7 +157,13 @@ const RenderState = Schema.Struct({
155
157
  * (older replay data, hand-built fixtures) defaults to `[]`. See
156
158
  * issue #240.
157
159
  */
158
- unhandledErrors: Schema.Array(ReportError)
160
+ unhandledErrors: Schema.Array(ReportError),
161
+ /**
162
+ * What the run wrote straight to the terminal, past Vitest's console
163
+ * capture, folded from `RunFinished.strayOutput`. Absent when the run
164
+ * wrote nothing stray.
165
+ */
166
+ strayOutput: Schema.optional(StrayOutput)
159
167
  }).annotate({ identifier: "RenderState" });
160
168
  /**
161
169
  * Initial state — what the reducer returns when no events have been
@@ -1,6 +1,7 @@
1
1
  import { ReportError, TestClassification, TestState } from "./Common.js";
2
2
  import { MetricThresholds } from "./Thresholds.js";
3
- import { CoverageTotals, FileCoverageReport } from "./Coverage.js";
3
+ import { CoverageTotals, FileCoverageReport, GlobShortfall } from "./Coverage.js";
4
+ import { StrayOutput } from "./StrayOutput.js";
4
5
  import { TestArtifactLocation, TestAttachment } from "./TestArtifacts.js";
5
6
  import { Schema } from "effect";
6
7
 
@@ -178,7 +179,8 @@ const RunEvent = Schema.Union([
178
179
  gaps: Schema.Array(CoverageGap),
179
180
  scoped: Schema.optional(Schema.Boolean),
180
181
  scopedFiles: Schema.optional(Schema.Number),
181
- totalFiles: Schema.optional(Schema.Number)
182
+ totalFiles: Schema.optional(Schema.Number),
183
+ globShortfalls: Schema.optional(Schema.Array(GlobShortfall))
182
184
  }),
183
185
  Schema.TaggedStruct("ThresholdViolation", {
184
186
  metric: CoverageMetric,
@@ -218,7 +220,13 @@ const RunEvent = Schema.Union([
218
220
  * argument to `onTestRunEnd`. Optional so hand-built fixtures
219
221
  * and older replay data keep decoding. See issue #240.
220
222
  */
221
- unhandledErrors: Schema.optional(Schema.Array(ReportError))
223
+ unhandledErrors: Schema.optional(Schema.Array(ReportError)),
224
+ /**
225
+ * What the run wrote straight to the terminal, past Vitest's console
226
+ * capture (`AgentReport.strayOutput`). Optional: absent when the run
227
+ * wrote nothing stray, and in older replay data.
228
+ */
229
+ strayOutput: Schema.optional(StrayOutput)
222
230
  })
223
231
  ]).annotate({ identifier: "RunEvent" });
224
232
 
@@ -0,0 +1,38 @@
1
+ import { Schema } from "effect";
2
+
3
+ //#region src/schemas/StrayOutput.ts
4
+ /**
5
+ * One line of stray output, kept as a sample: the stream it was written to,
6
+ * and its text with escapes stripped, cut to a sane length.
7
+ * @public
8
+ */
9
+ const StrayOutputSample = Schema.Struct({
10
+ stream: Schema.Literals(["stdout", "stderr"]),
11
+ text: Schema.String
12
+ }).annotate({ identifier: "StrayOutputSample" });
13
+ /**
14
+ * Output a test run wrote straight to the terminal, past Vitest's console
15
+ * capture: bytes a worker process (or a child process it spawned with
16
+ * inherited stdio, such as `execFileSync("git", ...)`) wrote to its own
17
+ * stdout or stderr, which Vitest pipes through to the main process
18
+ * untouched. Distinct from `consoleLeaks`, which counts `console.*` calls
19
+ * Vitest did capture.
20
+ *
21
+ * A run-level signal: the bytes arrive on one stream shared by every project,
22
+ * so nothing attributes them to a project, file or test. Every project report
23
+ * of a run carries the same value. Counts are of non-blank lines; `bytes`
24
+ * counts everything recorded. `samples` holds the first few lines. Omitted
25
+ * when the run wrote nothing stray (and whenever the plugin does not own the
26
+ * console: `passthrough` mode records nothing).
27
+ * @public
28
+ */
29
+ const StrayOutput = Schema.Struct({
30
+ total: Schema.Number,
31
+ stdout: Schema.Number,
32
+ stderr: Schema.Number,
33
+ bytes: Schema.Number,
34
+ samples: Schema.Array(StrayOutputSample)
35
+ }).annotate({ identifier: "StrayOutput" });
36
+
37
+ //#endregion
38
+ export { StrayOutput, StrayOutputSample };
@@ -0,0 +1,143 @@
1
+ //#region src/utils/stray-output.ts
2
+ const ESCAPE = /\x1b(?:\[[0-?]*[ -/]*[@-~]|\][^\x07\x1b]*(?:\x07|\x1b\\)|[@-Z\\-_]|c)/g;
3
+ const CONTROL = /[\x00-\x08\x0b-\x1f\x7f]/g;
4
+ /**
5
+ * True when `text` is nothing but terminal escape sequences: the cursor
6
+ * hide/show, clear-screen and erase-scrollback writes Vitest's own `Logger`
7
+ * makes straight to its output stream. Empty text is not escape-only.
8
+ * @public
9
+ */
10
+ const isEscapeOnly = (text) => text.length > 0 && text.replace(ESCAPE, "").length === 0;
11
+ const encoder = new TextEncoder();
12
+ /**
13
+ * Make a {@link StrayOutputRecorder}.
14
+ * @public
15
+ */
16
+ const makeStrayOutputRecorder = (options = {}) => {
17
+ const maxSamples = options.maxSamples ?? 5;
18
+ const maxChars = options.maxSampleChars ?? 160;
19
+ let counts = {
20
+ stdout: 0,
21
+ stderr: 0
22
+ };
23
+ let bytes = 0;
24
+ let samples = [];
25
+ let pending = {
26
+ stdout: {
27
+ text: "",
28
+ cut: false
29
+ },
30
+ stderr: {
31
+ text: "",
32
+ cut: false
33
+ }
34
+ };
35
+ const clean = (line) => line.replace(ESCAPE, "").replace(CONTROL, "").trim();
36
+ const sampleOf = (line) => {
37
+ const text = clean(line.text);
38
+ return line.cut || text.length > maxChars ? `${text.slice(0, maxChars)}…` : text;
39
+ };
40
+ const finish = (stream, line, into, out) => {
41
+ if (clean(line.text) === "") return;
42
+ into[stream]++;
43
+ if (out.length < maxSamples) out.push({
44
+ stream,
45
+ text: sampleOf(line)
46
+ });
47
+ };
48
+ const append = (line, text) => {
49
+ const room = maxChars * 4 - line.text.length;
50
+ if (text.length <= room) line.text += text;
51
+ else {
52
+ line.text += text.slice(0, Math.max(0, room));
53
+ line.cut = true;
54
+ }
55
+ };
56
+ return {
57
+ write: (stream, text) => {
58
+ if (text.length === 0 || isEscapeOnly(text)) return;
59
+ bytes += encoder.encode(text).length;
60
+ const parts = text.split("\n");
61
+ const line = pending[stream];
62
+ append(line, parts[0] ?? "");
63
+ for (let i = 1; i < parts.length; i++) {
64
+ finish(stream, line, counts, samples);
65
+ line.text = "";
66
+ line.cut = false;
67
+ append(line, parts[i] ?? "");
68
+ }
69
+ },
70
+ snapshot: () => {
71
+ const totals = { ...counts };
72
+ const out = [...samples];
73
+ finish("stdout", pending.stdout, totals, out);
74
+ finish("stderr", pending.stderr, totals, out);
75
+ const total = totals.stdout + totals.stderr;
76
+ if (total === 0) return void 0;
77
+ return {
78
+ total,
79
+ stdout: totals.stdout,
80
+ stderr: totals.stderr,
81
+ bytes,
82
+ samples: out
83
+ };
84
+ },
85
+ reset: () => {
86
+ counts = {
87
+ stdout: 0,
88
+ stderr: 0
89
+ };
90
+ bytes = 0;
91
+ samples = [];
92
+ pending = {
93
+ stdout: {
94
+ text: "",
95
+ cut: false
96
+ },
97
+ stderr: {
98
+ text: "",
99
+ cut: false
100
+ }
101
+ };
102
+ }
103
+ };
104
+ };
105
+ /**
106
+ * The key a stray-output capture publishes its {@link StrayOutputSource}
107
+ * under, on the Vitest `Logger` whose streams it wraps. `Symbol.for`, so a
108
+ * plugin and an MCP server that load different copies of this package still
109
+ * meet on it.
110
+ * @public
111
+ */
112
+ const STRAY_OUTPUT_SOURCE = Symbol.for("vitest-agent/stray-output");
113
+ /**
114
+ * Read the current run's stray output from a Vitest `Logger` a capture was
115
+ * installed on. `undefined` when none was installed, or the run wrote nothing.
116
+ * @public
117
+ */
118
+ const readStrayOutput = (logger) => {
119
+ if (logger === null || typeof logger !== "object") return void 0;
120
+ const source = logger[STRAY_OUTPUT_SOURCE];
121
+ if (source === null || typeof source !== "object") return void 0;
122
+ const snapshot = source.snapshot;
123
+ return typeof snapshot === "function" ? snapshot() : void 0;
124
+ };
125
+ const MAX_NOTE_SAMPLES = 3;
126
+ /**
127
+ * The note a renderer prints for a run's stray output: one line naming the
128
+ * count, the streams, the usual cause and the fix, then up to three sample
129
+ * lines and how many it left out.
130
+ * @public
131
+ */
132
+ const formatStrayOutputNote = (stray) => {
133
+ const streams = stray.stdout > 0 && stray.stderr > 0 ? "stdout and stderr" : stray.stdout > 0 ? "stdout" : "stderr";
134
+ const lines = [`Stray output: tests wrote ${stray.total} ${stray.total === 1 ? "line" : "lines"} directly to ${streams}, bypassing Vitest's console capture. A child process with inherited stdio is the usual cause; pass \`stdio: "pipe"\`.`];
135
+ const shown = stray.samples.slice(0, MAX_NOTE_SAMPLES);
136
+ for (const sample of shown) lines.push(` ${sample.stream}: ${sample.text}`);
137
+ const more = stray.total - shown.length;
138
+ if (more > 0 && shown.length > 0) lines.push(` (+${more} more)`);
139
+ return lines;
140
+ };
141
+
142
+ //#endregion
143
+ export { STRAY_OUTPUT_SOURCE, formatStrayOutputNote, isEscapeOnly, makeStrayOutputRecorder, readStrayOutput };
package/version.js CHANGED
@@ -11,7 +11,7 @@
11
11
  * survives into the published artifact.
12
12
  * @public
13
13
  */
14
- const CURRENT_SDK_VERSION = "6.0.0";
14
+ const CURRENT_SDK_VERSION = "7.0.0";
15
15
 
16
16
  //#endregion
17
17
  export { CURRENT_SDK_VERSION };