@vitest-agent/plugin 1.1.6 → 1.1.7

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/README.md CHANGED
@@ -4,12 +4,12 @@
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-4caf50.svg)](https://opensource.org/licenses/MIT)
5
5
  [![TypeScript 6.0](https://img.shields.io/badge/TypeScript-6.0-3178c6.svg)](https://www.typescriptlang.org/)
6
6
 
7
- Vitest plugin that turns your test suite into a live data source for LLM coding agents. Handles persistence, failure classification, coverage policy enforcement, project discovery and a configurable reporter chain — the CLI and MCP server arrive as required peers and are wired automatically.
7
+ Vitest plugin that turns your test suite into a live data source for LLM coding agents. Handles persistence, failure classification, coverage policy enforcement, project discovery and a configurable reporter chain — the CLI and MCP server ship as dependencies and are wired automatically.
8
8
 
9
9
  ## Features
10
10
 
11
11
  - **`AgentPlugin`** — drop into `vitest.config.ts`; auto-detects human, agent and CI executors and adapts console output accordingly
12
- - **Project discovery** — `AgentPlugin.discover()` scans workspace packages, returns `{ projects, tags }` ready for `test.projects` and `test.tags`
12
+ - **Project discovery** — `AgentPlugin.discover()` scans workspace packages, returns `{ projects, tags }` ready for `test.projects` and `test.tags`; classification tags apply at collection time, so every test declaration form inherits them, including wrapper testers like `@effect/vitest`'s `it.effect`
13
13
  - **Coverage presets** — `COVERAGE_LEVELS` and `COVERAGE_LEVELS_PER_FILE` return dual-output `{ thresholds, coverageTargets }` objects; `COVERAGE_AUTOUPDATE` tolerance functions plug into Vitest's native `autoUpdate`
14
14
  - **Failure classification** — persists per-test errors, computes failure signatures, classifies tests as stable, new-failure, persistent, flaky or recovered across runs
15
15
  - **Custom reporters** — pass any `VitestAgentReporterFactory` as the `reporter` option; the default wires `DefaultVitestAgentReporter` from `@vitest-agent/reporter`
@@ -23,7 +23,7 @@ npm install --save-dev @vitest-agent/plugin
23
23
  pnpm add -D @vitest-agent/plugin
24
24
  ```
25
25
 
26
- The required peers `@vitest-agent/cli` and `@vitest-agent/mcp` install automatically with npm 7+ and pnpm (`autoInstallPeers: true`).
26
+ `@vitest-agent/cli` and `@vitest-agent/mcp` ship as regular dependencies of the plugin, so they install with it on every package manager.
27
27
 
28
28
  ## Quick start
29
29
 
package/index.d.ts CHANGED
@@ -157,7 +157,7 @@ declare class DefaultDiscoverStrategy extends DiscoverStrategy {
157
157
  * @public
158
158
  */
159
159
  interface InjectTagsResult {
160
- /** The transformed source code string with injected `tags` arguments. */
160
+ /** The transformed source code string with the prepended tag prelude. */
161
161
  code: string;
162
162
  /** Source map correlating transformed positions back to the original source. */
163
163
  map: SourceMap;
@@ -193,8 +193,9 @@ interface AgentPluginConstructorOptions extends AgentPluginOptions {
193
193
  */
194
194
  onRunEvent?: (event: RunEvent) => void;
195
195
  /**
196
- * Controls the Vite transform hook that rewrites test() and it() call
197
- * options to inject filename-derived tags. Pass a DiscoverStrategy
196
+ * Controls the Vite transform hook that prepends a tag prelude to each
197
+ * classified test file, applying filename-derived tags at collection
198
+ * time so every declaration form inherits them. Pass a DiscoverStrategy
198
199
  * instance to customize classification, or false to disable the
199
200
  * transform entirely.
200
201
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vitest-agent/plugin",
3
- "version": "1.1.6",
3
+ "version": "1.1.7",
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": [
@@ -46,17 +46,15 @@
46
46
  "@effect/sql": "^0.51.1",
47
47
  "@effect/sql-sqlite-node": "^0.52.0",
48
48
  "@effect/workflow": "^0.18.2",
49
+ "@vitest-agent/cli": "1.0.6",
50
+ "@vitest-agent/mcp": "1.3.4",
49
51
  "@vitest-agent/reporter": "1.0.6",
50
52
  "@vitest-agent/sdk": "1.3.2",
51
- "acorn": "^8.17.0",
52
- "acorn-typescript": "^1.4.13",
53
53
  "effect": "^3.21.4",
54
54
  "magic-string": "^0.30.21",
55
55
  "workspaces-effect": "^2.0.2"
56
56
  },
57
57
  "peerDependencies": {
58
- "@vitest-agent/cli": "1.0.6",
59
- "@vitest-agent/mcp": "1.3.4",
60
58
  "@vitest/coverage-istanbul": "^4.1.0",
61
59
  "@vitest/coverage-v8": "^4.1.0",
62
60
  "vitest": "^4.1.0"
package/plugin.js CHANGED
@@ -98,7 +98,7 @@ const aggregatedReporterByVitest = /* @__PURE__ */ new WeakSet();
98
98
  *
99
99
  * @public
100
100
  */
101
- const CURRENT_PLUGIN_VERSION = "1.1.6";
101
+ const CURRENT_PLUGIN_VERSION = "1.1.7";
102
102
  const TEST_FILE_SUFFIX_RE = /\.(?:test|spec)\.(?:ts|tsx|js|jsx)$/;
103
103
  const TEST_FILE_DIR_RE = /\/(?:src|__test__)\//;
104
104
  const isTestFile = (id) => TEST_FILE_SUFFIX_RE.test(id) && TEST_FILE_DIR_RE.test(id);
@@ -1,92 +1,36 @@
1
- import * as acorn from "acorn";
2
- import { tsPlugin } from "acorn-typescript";
3
1
  import MagicString from "magic-string";
4
2
 
5
3
  //#region src/utils/inject-tags.ts
6
- const Parser = acorn.Parser.extend(tsPlugin());
7
- const TEST_NAMES = /* @__PURE__ */ new Set(["test", "it"]);
8
- function rootIdentifier(n) {
9
- if (n.type === "Identifier") return n;
10
- if (n.type === "MemberExpression") return rootIdentifier(n.object);
11
- if (n.type === "CallExpression") return rootIdentifier(n.callee);
12
- return null;
13
- }
14
- function isTestCallee(callee) {
15
- const root = rootIdentifier(callee);
16
- return !!root && TEST_NAMES.has(root.name);
17
- }
18
- function hasTagsField(objectExpression) {
19
- const props = objectExpression.properties ?? [];
20
- for (const p of props) {
21
- if (p.type !== "Property") continue;
22
- const key = p.key;
23
- if (key.type === "Identifier" && key.name === "tags") return true;
24
- if (key.type === "Literal" && key.value === "tags") return true;
25
- }
26
- return false;
27
- }
28
4
  function tagsLiteral(tags) {
29
5
  return `[${tags.map((t) => JSON.stringify(t)).join(", ")}]`;
30
6
  }
31
- function walk(node, visit) {
32
- visit(node);
33
- for (const key of Object.keys(node)) {
34
- if (key === "type" || key === "start" || key === "end" || key === "loc") continue;
35
- const v = node[key];
36
- if (Array.isArray(v)) {
37
- for (const item of v) if (item && typeof item === "object" && "type" in item) walk(item, visit);
38
- } else if (v && typeof v === "object" && "type" in v) walk(v, visit);
39
- }
7
+ /**
8
+ * Builds the guarded prelude that applies the classified tags to the file
9
+ * task at collection time. `TestRunner.getCurrentSuite()` is a public
10
+ * static on the main vitest entry (>= 4.1, the plugin's peer floor); the
11
+ * `.suite ?? .file` fallback mirrors vitest's own parent-task resolution.
12
+ * The runner unions parent tags into every suite and test it registers,
13
+ * so tags set here are inherited by every declaration form in the file
14
+ * native it/test, wrapper testers like @effect/vitest's it.effect, aliases
15
+ * from test.extend, and dynamically registered tests (issue #133). The
16
+ * namespace import plus optional chaining on `TestRunner` means environments
17
+ * whose "vitest" entry lacks the `TestRunner` export (e.g. vitest's browser-
18
+ * mode entry) degrade to untagged tests instead of a module-instantiation
19
+ * failure. If a future vitest changes the collector shape, the try/catch
20
+ * degrades to "tests carry no tags" — never a crash.
21
+ */
22
+ function preludeFor(tags) {
23
+ return `import * as __vitestAgentVitest from "vitest";
24
+ try { const __vitestAgentCollector = __vitestAgentVitest.TestRunner?.getCurrentSuite?.(); const __vitestAgentTask = __vitestAgentCollector?.suite ?? __vitestAgentCollector?.file; if (__vitestAgentTask) { __vitestAgentTask.tags = [...new Set([...(__vitestAgentTask.tags ?? []), ...${tagsLiteral(tags)}])]; } } catch {}
25
+ `;
40
26
  }
41
27
  function injectTags(source, tags) {
42
28
  if (tags.length === 0) return null;
43
- let ast;
44
- try {
45
- ast = Parser.parse(source, {
46
- ecmaVersion: "latest",
47
- sourceType: "module",
48
- locations: true
49
- });
50
- } catch {
51
- return null;
52
- }
53
29
  const ms = new MagicString(source);
54
- let mutated = false;
55
- walk(ast, (node) => {
56
- if (node.type !== "CallExpression") return;
57
- const callee = node.callee;
58
- if (!isTestCallee(callee)) return;
59
- const args = node.arguments ?? [];
60
- if (args.length < 2) return;
61
- const last = args[args.length - 1];
62
- if (!(last.type === "FunctionExpression" || last.type === "ArrowFunctionExpression")) return;
63
- const optsCandidate = args.length >= 3 ? args[args.length - 2] : null;
64
- if (optsCandidate) {
65
- if (optsCandidate.type === "ObjectExpression") {
66
- if (hasTagsField(optsCandidate)) return;
67
- const lastProp = (optsCandidate.properties ?? []).at(-1);
68
- if (lastProp === void 0) {
69
- const insertPoint = optsCandidate.end - 1;
70
- ms.appendLeft(insertPoint, `tags: ${tagsLiteral(tags)} `);
71
- } else ms.appendLeft(lastProp.end, `, tags: ${tagsLiteral(tags)}`);
72
- mutated = true;
73
- return;
74
- }
75
- const optsStart = optsCandidate.start;
76
- const optsEnd = optsCandidate.end;
77
- const origExpr = source.slice(optsStart, optsEnd);
78
- ms.overwrite(optsStart, optsEnd, `{ ...(${origExpr}), tags: ${tagsLiteral(tags)} }`);
79
- mutated = true;
80
- return;
81
- }
82
- const nameArg = args[0];
83
- ms.appendRight(nameArg.end, `, { tags: ${tagsLiteral(tags)} }`);
84
- mutated = true;
85
- });
86
- if (!mutated) return null;
30
+ ms.prepend(preludeFor(tags));
87
31
  return {
88
32
  code: ms.toString(),
89
- map: ms.generateMap({ hires: true })
33
+ map: ms.generateMap()
90
34
  };
91
35
  }
92
36