@vitest-agent/reporter 6.0.2 → 6.1.1

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.
@@ -4,7 +4,7 @@ import { renderGithubLog, strayOutputOf, toDisplayPath } from "./githubLog.js";
4
4
  import { LiveViewEnv, startLiveView } from "./liveView.js";
5
5
  import { Doc, Fmt, Render, Status } from "@effected/cli";
6
6
  import { RUN_REPORT_FILE_SCHEMA_URL, formatStrayOutputNote } from "@vitest-agent/sdk";
7
- import { classifyOutcome, classifyRunShape, dispatch, reduceRenderStateAll, synthesizeFromAgentReport } from "@vitest-agent/ui";
7
+ import { classifyOutcome, classifyRunShape, dispatch, formatSuggestedActionLines, reduceRenderStateAll, synthesizeFromAgentReport } from "@vitest-agent/ui";
8
8
  import { Effect } from "effect";
9
9
 
10
10
  // src/defaultReporter.ts
@@ -328,13 +328,22 @@ const renderGithubSummary = (input) => {
328
328
  */
329
329
  const DefaultVitestAgentReporter = (kit) => {
330
330
  const live = kit.config.consoleMode === "stream" && kit.runEvents !== void 0 ? startLiveView(kit.runEvents) : void 0;
331
+ let actionLines = [];
332
+ let actionsPrinted = false;
333
+ const closeLive = live !== void 0 ? async () => {
334
+ await live.close();
335
+ if (actionsPrinted || actionLines.length === 0) return;
336
+ actionsPrinted = true;
337
+ process.stdout.write(`\n${actionLines.join("\n")}\n`);
338
+ } : void 0;
331
339
  return {
332
- ...live !== void 0 ? {
333
- close: live.close,
334
- printStrayLine: live.printStrayLine
340
+ ...live !== void 0 && closeLive !== void 0 ? {
341
+ close: closeLive,
342
+ printStrayLine: live.printAbove
335
343
  } : {},
336
344
  render(input, renderKit) {
337
345
  const out = [];
346
+ if (live !== void 0) actionLines = formatSuggestedActionLines(input.suggestedActions ?? []);
338
347
  if (shouldRenderForMode(renderKit.config.consoleMode)) {
339
348
  const events = input.reports.flatMap((r) => synthesizeFromAgentReport(r, { ...input.suggestedActions !== void 0 ? { suggestedActions: input.suggestedActions } : {} }));
340
349
  const state = reduceRenderStateAll(events);
@@ -358,7 +367,8 @@ const DefaultVitestAgentReporter = (kit) => {
358
367
  $schema: RUN_REPORT_FILE_SCHEMA_URL,
359
368
  schemaVersion: 1,
360
369
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
361
- reports: input.reports
370
+ reports: input.reports,
371
+ ...input.runDiagnostics !== void 0 ? { runDiagnostics: input.runDiagnostics } : {}
362
372
  }, null, 2)}\n`
363
373
  });
364
374
  out.push({
package/index.d.ts CHANGED
@@ -616,6 +616,85 @@ type Executor = typeof Executor.Type;
616
616
  declare const DetailLevel: Schema.Literals<readonly ["minimal", "neutral", "standard", "verbose"]>;
617
617
  /** @public */
618
618
  type DetailLevel = typeof DetailLevel.Type;
619
+ /**
620
+ * Which Vitest performance diagnostic a hint came from.
621
+ * @public
622
+ */
623
+ declare const PerformanceHintKind: Schema.Literals<readonly ["environment", "import", "transform", "isolate"]>;
624
+ /** @public */
625
+ type PerformanceHintKind = typeof PerformanceHintKind.Type;
626
+ /**
627
+ * One performance hint derived from Vitest's run diagnostics. `project` is
628
+ * omitted for a hint that applies to the whole run (for example `isolate`).
629
+ * @public
630
+ */
631
+ declare const PerformanceHint: Schema.Struct<{
632
+ readonly kind: Schema.Literals<readonly ["environment", "import", "transform", "isolate"]>;
633
+ readonly project: Schema.optionalKey<Schema.String>;
634
+ readonly message: Schema.String;
635
+ readonly remedy: Schema.String;
636
+ readonly detail: Schema.String;
637
+ readonly learnMoreUrl: Schema.String;
638
+ readonly estimatedSavingMs: Schema.optionalKey<Schema.Finite>;
639
+ readonly share: Schema.optionalKey<Schema.Finite>;
640
+ }>;
641
+ /** @public */
642
+ type PerformanceHint = typeof PerformanceHint.Type;
643
+ /**
644
+ * Wall-clock timing of the plugin's post-run work, in milliseconds.
645
+ * @public
646
+ */
647
+ declare const PostRunTiming: Schema.Struct<{
648
+ readonly persistMs: Schema.Finite;
649
+ readonly coverageScanMs: Schema.Finite;
650
+ readonly totalMs: Schema.Finite;
651
+ readonly renderMs: Schema.optionalKey<Schema.Finite>;
652
+ readonly vitestDurationMs: Schema.optionalKey<Schema.Finite>;
653
+ }>;
654
+ /** @public */
655
+ type PostRunTiming = typeof PostRunTiming.Type;
656
+ /**
657
+ * Whether the run's results were persisted to the database.
658
+ * @public
659
+ */
660
+ declare const PersistenceStatus: Schema.Struct<{
661
+ readonly ok: Schema.Boolean;
662
+ readonly reason: Schema.optionalKey<Schema.String>;
663
+ readonly migrationFailed: Schema.optionalKey<Schema.Boolean>;
664
+ }>;
665
+ /** @public */
666
+ type PersistenceStatus = typeof PersistenceStatus.Type;
667
+ /**
668
+ * Run-level diagnostics: performance hints, post-run timing and persistence
669
+ * status (issue #544).
670
+ * @public
671
+ */
672
+ declare const RunDiagnostics: Schema.Struct<{
673
+ readonly performanceHints: Schema.$Array<Schema.Struct<{
674
+ readonly kind: Schema.Literals<readonly ["environment", "import", "transform", "isolate"]>;
675
+ readonly project: Schema.optionalKey<Schema.String>;
676
+ readonly message: Schema.String;
677
+ readonly remedy: Schema.String;
678
+ readonly detail: Schema.String;
679
+ readonly learnMoreUrl: Schema.String;
680
+ readonly estimatedSavingMs: Schema.optionalKey<Schema.Finite>;
681
+ readonly share: Schema.optionalKey<Schema.Finite>;
682
+ }>>;
683
+ readonly postRun: Schema.optionalKey<Schema.Struct<{
684
+ readonly persistMs: Schema.Finite;
685
+ readonly coverageScanMs: Schema.Finite;
686
+ readonly totalMs: Schema.Finite;
687
+ readonly renderMs: Schema.optionalKey<Schema.Finite>;
688
+ readonly vitestDurationMs: Schema.optionalKey<Schema.Finite>;
689
+ }>>;
690
+ readonly persistence: Schema.optionalKey<Schema.Struct<{
691
+ readonly ok: Schema.Boolean;
692
+ readonly reason: Schema.optionalKey<Schema.String>;
693
+ readonly migrationFailed: Schema.optionalKey<Schema.Boolean>;
694
+ }>>;
695
+ }>;
696
+ /** @public */
697
+ type RunDiagnostics = typeof RunDiagnostics.Type;
619
698
  /**
620
699
  * The reducer projects this union into `RenderState`. Renderers
621
700
  * never read the raw event stream — they read the projected state.
@@ -1058,6 +1137,8 @@ interface ReporterRenderInput {
1058
1137
  * when the plugin has nothing to suggest (issue #544).
1059
1138
  */
1060
1139
  readonly suggestedActions?: ReadonlyArray<SuggestedActionRecord>;
1140
+ /** Run-level diagnostics (performance hints, post-run timing, persistence status); absent when none were collected (issue #544). */
1141
+ readonly runDiagnostics?: RunDiagnostics;
1061
1142
  readonly trendSummary?: {
1062
1143
  readonly direction: "improving" | "regressing" | "stable";
1063
1144
  readonly runCount: number;
@@ -1233,5 +1314,5 @@ export declare const DefaultVitestAgentReporter: VitestAgentReporterFactory;
1233
1314
  * @public
1234
1315
  */
1235
1316
  export declare const CURRENT_REPORTER_VERSION: string;
1236
- export type { AgentReport, CellOptions, ConsoleMode, DetailLevel, DispatchInputs, Environment, Executor, FileCoverageReport, FileLinkTarget, HumanReportRenderOptions, ProjectSummary, RenderState, RenderedOutput, ReportRenderOptions, ReporterKit, ReporterRenderInput, ResolvedReporterConfig, ResolvedThresholds, RunEvent, RunOutcome, RunShape, SuggestedActionRecord, TestClassification, Transport, TrendSummary, VitestAgentReporter, VitestAgentReporterFactory };
1317
+ export type { AgentReport, CellOptions, ConsoleMode, DetailLevel, DispatchInputs, Environment, Executor, FileCoverageReport, FileLinkTarget, HumanReportRenderOptions, PerformanceHint, PerformanceHintKind, PersistenceStatus, PostRunTiming, ProjectSummary, RenderState, RenderedOutput, ReportRenderOptions, ReporterKit, ReporterRenderInput, ResolvedReporterConfig, ResolvedThresholds, RunDiagnostics, RunEvent, RunOutcome, RunShape, SuggestedActionRecord, TestClassification, Transport, TrendSummary, VitestAgentReporter, VitestAgentReporterFactory };
1237
1318
  //# sourceMappingURL=index.d.ts.map
package/index.js CHANGED
@@ -8,6 +8,6 @@ import { DefaultVitestAgentReporter, buildDispatchInputs, renderAgentStringForRe
8
8
  *
9
9
  * @public
10
10
  */
11
- const CURRENT_REPORTER_VERSION = "6.0.2";
11
+ const CURRENT_REPORTER_VERSION = "6.1.1";
12
12
 
13
13
  export { CURRENT_REPORTER_VERSION, DefaultVitestAgentReporter, buildDispatchInputs, renderAgentStringForReport, renderHumanStringForReport };
package/liveView.js CHANGED
@@ -2,7 +2,7 @@ import { plainFileLink, resolveFileLink } from "./fileLinks.js";
2
2
  import { CliEnv, Doc } from "@effected/cli";
3
3
  import { initialRenderState } from "@vitest-agent/sdk";
4
4
  import { SPINNER_FRAME_MS, reduceRenderState, renderAgent } from "@vitest-agent/ui";
5
- import { Cause, Deferred, Effect, Exit, Layer, PubSub, Scope } from "effect";
5
+ import { Cause, Effect, Exit, Layer } from "effect";
6
6
  import * as NodeServices from "@effect/platform-node/NodeServices";
7
7
  import { CliUi } from "@effected/cli/ui";
8
8
 
@@ -29,29 +29,35 @@ import { CliUi } from "@effected/cli/ui";
29
29
  * modules, coverage, suggested actions), each line kept whole; it does not
30
30
  * try to reproduce the Ink frame, and drawing it never renders through Ink.
31
31
  *
32
- * Lifetime: one scope per live view, held for the reporter's whole life. The
33
- * subscription is made in it synchronously, before the factory returns, so
34
- * the first `RunStarted` is never lost, and the view takes from it directly
35
- * (`events: subscription`). At Vitest's close the plugin calls
36
- * {@link LiveRunView.close} before it shuts the channel down: close runs the
37
- * kit's `LiveHandle.close` (it folds every message still queued in the
38
- * subscription and commits the last run, so a tail published just before the
39
- * close is kept), then closes the scope. The view never relies on
32
+ * Lifetime: the kit's `CliUi.liveDetached` runs the view outside Effect for
33
+ * the reporter's whole life. It subscribes to the channel synchronously,
34
+ * before the factory returns, so the first `RunStarted` is never lost. At
35
+ * Vitest's close the plugin calls the view's `close` before it shuts the
36
+ * channel down: `close` folds every message still queued in the
37
+ * subscription and commits the last run (so a tail published just before
38
+ * the close is kept), then releases the view. The view never relies on
40
39
  * `PubSub.shutdown` to end it: shutdown drops whatever a subscriber has not
41
40
  * taken yet. It is never closed at `onTestRunEnd`, which fires on every
42
41
  * watch rerun.
43
42
  */
44
43
  /**
44
+ * The Ink frame's draw function for a view whose paths are drawn through
45
+ * `link`. The one sanctioned dynamic import outside mcp's `main.ts` (with
46
+ * `humanReport.ts`'s): React and Ink load only when a run draws. The view
47
+ * module exports a factory, so the writer reaches the tree without this
48
+ * module importing React.
49
+ */
50
+ const streamRender = (link) => CliUi.lazyView(() => import("./streamView.js").then((view) => view.makeStreamView(link)));
51
+ /**
45
52
  * The live view's options, everything but `events`, for a view whose paths
46
- * are drawn through `link`: what the reporter hands `CliUi.live` once it has
47
- * resolved the writer from its environment (`resolveFileLink`).
53
+ * are drawn through `link`.
48
54
  *
49
55
  * @internal
50
56
  */
51
57
  const liveViewOptionsWith = (link) => ({
52
58
  initial: initialRenderState,
53
59
  reduce: reduceRenderState,
54
- render: CliUi.lazyView(() => import("./streamView.js").then((view) => view.makeStreamView(link))),
60
+ render: streamRender(link),
55
61
  isStart: (event) => event._tag === "RunStarted",
56
62
  isTerminal: (event) => event._tag === "RunFinished" || event._tag === "RunTimedOut",
57
63
  begins: (event, before, after) => event._tag === "RunStarted" || before.phase === "idle" && after.phase !== "idle",
@@ -81,21 +87,17 @@ const liveViewOptions = liveViewOptionsWith(plainFileLink);
81
87
  */
82
88
  const LiveViewEnv = CliEnv.layer().pipe(Layer.provide(NodeServices.layer));
83
89
  /**
84
- * Print one line of stray output (bytes a test process wrote straight to the
85
- * terminal) through the view's `logConsole`: above the frame while a run is
86
- * drawn, straight to the stream otherwise, so it never lands under the frame
87
- * where the next redraw would strand it.
90
+ * Start a live view over `channel` with the kit's `CliUi.liveDetached`. The
91
+ * subscription is taken before this returns, so an event published right
92
+ * after is seen.
88
93
  *
89
- * @internal
90
- */
91
- const printAbove = (handle) => (stream, line) => {
92
- if (stream === "stderr") handle.logConsole.error(line);
93
- else handle.logConsole.log(line);
94
- return true;
95
- };
96
- /**
97
- * Start a live view over `channel`. The subscription is taken before this
98
- * returns, so an event published right after is seen.
94
+ * The returned view's `printAbove` is the reporter's `printStrayLine`: it
95
+ * prints the line above the frame and answers `true` only while a run's
96
+ * frame is mounted, declining (`false`, nothing written) before the view is
97
+ * up, between runs, when the view is not interactive (it never mounts), and
98
+ * once `close` has been called, so the plugin writes the line itself.
99
+ * `close` is idempotent and never rejects; a view that died of anything but
100
+ * an interrupt is logged as a warning.
99
101
  *
100
102
  * @param channel - the plugin's run-event channel
101
103
  * @param env - the environment the view reads; {@link LiveViewEnv} by default (a test
@@ -104,29 +106,15 @@ const printAbove = (handle) => (stream, line) => {
104
106
  * @internal
105
107
  */
106
108
  const startLiveView = (channel, env = LiveViewEnv) => {
107
- const scope = Effect.runSync(Scope.make());
108
- const subscription = Effect.runSync(PubSub.subscribe(channel).pipe(Scope.provide(scope)));
109
- const mounted = Effect.runSync(Deferred.make());
110
- const program = resolveFileLink.pipe(Effect.flatMap((link) => CliUi.live({
111
- ...liveViewOptionsWith(link),
112
- events: subscription
113
- })), Effect.onExit((exit) => Deferred.done(mounted, exit)), Effect.flatMap((handle) => handle.done), Scope.provide(scope), Effect.provide(env));
114
- Effect.runFork(program);
115
- let closing;
116
- let print;
117
- const handle = Effect.runPromise(Deferred.await(mounted));
118
- handle.then((mountedHandle) => {
119
- if (closing === void 0) print = printAbove(mountedHandle);
120
- }, () => void 0);
121
- return {
122
- handle,
123
- printStrayLine: (stream, line) => print?.(stream, line) ?? false,
124
- close: () => {
125
- print = void 0;
126
- closing ??= Effect.runPromise(Deferred.await(mounted).pipe(Effect.flatMap((handle) => handle.close), Effect.catchCause((cause) => Cause.hasInterruptsOnly(cause) ? Effect.void : Effect.logWarning("vitest-agent: the live view ended with an error", cause)), Effect.ensuring(Scope.close(scope, Exit.void))));
127
- return closing;
128
- }
129
- };
109
+ const view = CliUi.liveDetached({
110
+ ...liveViewOptions,
111
+ render: Effect.map(resolveFileLink, streamRender),
112
+ events: channel
113
+ }, env);
114
+ view.exit.then((exit) => {
115
+ if (Exit.isFailure(exit) && !Cause.hasInterruptsOnly(exit.cause)) Effect.runFork(Effect.logWarning("vitest-agent: the live view ended with an error", exit.cause));
116
+ });
117
+ return view;
130
118
  };
131
119
 
132
- export { LiveViewEnv, liveViewOptionsWith, printAbove, startLiveView };
120
+ export { LiveViewEnv, liveViewOptions, liveViewOptionsWith, startLiveView };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vitest-agent/reporter",
3
- "version": "6.0.2",
3
+ "version": "6.1.1",
4
4
  "private": false,
5
5
  "description": "Named VitestAgentReporterFactory implementations for the vitest-agent ecosystem.",
6
6
  "keywords": [
@@ -39,31 +39,23 @@
39
39
  "./package.json": "./package.json"
40
40
  },
41
41
  "dependencies": {
42
- "@effect/platform-node": "^4.0.6",
43
- "@vitest-agent/sdk": "8.0.0",
44
- "@vitest-agent/ui": "5.1.2",
45
- "effect": "^4.0.6",
42
+ "@effect/platform-node": "^4.0.7",
43
+ "@vitest-agent/sdk": "9.0.1",
44
+ "@vitest-agent/ui": "5.2.1",
45
+ "effect": "^4.0.7",
46
46
  "ink": "^8.0.0",
47
47
  "react": "^19.3.1"
48
48
  },
49
49
  "peerDependencies": {
50
- "@effected/cli": "^0.22.2",
51
- "@effected/env": "^0.5.0",
52
- "@effected/glob": "^0.14.0",
53
- "@effected/walker": "^0.20.1",
54
- "@vitest/coverage-istanbul": "^5.0.0",
55
- "@vitest/coverage-v8": "^5.0.0",
56
- "vitest": "^5.0.0"
50
+ "@effected/cli": "^0.24.0",
51
+ "@effected/config-file": "^0.22.0",
52
+ "@effected/env": "^0.7.0",
53
+ "@effected/glob": "^0.16.0",
54
+ "@effected/walker": "^0.22.0"
57
55
  },
58
56
  "peerDependenciesMeta": {
59
- "@vitest/coverage-istanbul": {
57
+ "@effected/config-file": {
60
58
  "optional": true
61
- },
62
- "@vitest/coverage-v8": {
63
- "optional": true
64
- },
65
- "vitest": {
66
- "optional": false
67
59
  }
68
60
  },
69
61
  "engines": {
@@ -5,7 +5,7 @@
5
5
  "toolPackages": [
6
6
  {
7
7
  "packageName": "@microsoft/api-extractor",
8
- "packageVersion": "7.59.4"
8
+ "packageVersion": "7.60.0"
9
9
  }
10
10
  ]
11
11
  }