@vitest-agent/mcp 1.3.6 → 2.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.js CHANGED
@@ -11,7 +11,7 @@ import { startMcpServer } from "./server.js";
11
11
  *
12
12
  * @public
13
13
  */
14
- const CURRENT_MCP_VERSION = "1.3.6";
14
+ const CURRENT_MCP_VERSION = "2.0.0";
15
15
 
16
16
  //#endregion
17
17
  export { CURRENT_MCP_VERSION, McpLive, appRouter, createCallerFactory, createCurrentSessionIdRef, createSessionContextRef, startMcpServer };
package/layers/McpLive.js CHANGED
@@ -1,5 +1,4 @@
1
- import { NodeFileSystem } from "@effect/platform-node";
2
- import * as NodeContext$1 from "@effect/platform-node/NodeContext";
1
+ import * as NodeServices from "@effect/platform-node/NodeServices";
3
2
  import { layer } from "@effect/sql-sqlite-node/SqliteClient";
4
3
  import * as SqliteMigrator from "@effect/sql-sqlite-node/SqliteMigrator";
5
4
  import { DataReaderLive, DataStoreLive, LoggerLive, OutputPipelineLive, ProjectDiscoveryLive, migration0001 } from "@vitest-agent/sdk";
@@ -10,7 +9,7 @@ import { Layer } from "effect";
10
9
  * Builds the Effect Layer that provides all services required by the MCP server.
11
10
  *
12
11
  * Composes DataReader, DataStore, ProjectDiscovery, OutputPipeline, SQLite
13
- * client, migrator, NodeContext, NodeFileSystem, and the logger into a single
12
+ * client, migrator, NodeServices, and the logger into a single
14
13
  * layer suitable for `ManagedRuntime.make`.
15
14
  *
16
15
  * @param dbPath - absolute path to the SQLite database file
@@ -21,9 +20,9 @@ import { Layer } from "effect";
21
20
  */
22
21
  const McpLive = (dbPath, logLevel, logFile) => {
23
22
  const SqliteLayer = layer({ filename: dbPath });
24
- const PlatformLayer = NodeContext$1.layer;
23
+ const PlatformLayer = NodeServices.layer;
25
24
  const MigratorLayer = SqliteMigrator.layer({ loader: SqliteMigrator.fromRecord({ "0001_initial": migration0001 }) }).pipe(Layer.provide(Layer.merge(SqliteLayer, PlatformLayer)));
26
- return Layer.mergeAll(DataReaderLive, DataStoreLive, ProjectDiscoveryLive, OutputPipelineLive).pipe(Layer.provideMerge(MigratorLayer), Layer.provideMerge(SqliteLayer), Layer.provideMerge(PlatformLayer), Layer.provideMerge(NodeFileSystem.layer), Layer.provideMerge(LoggerLive(logLevel, logFile)));
25
+ return Layer.mergeAll(DataReaderLive, DataStoreLive, ProjectDiscoveryLive, OutputPipelineLive).pipe(Layer.provideMerge(MigratorLayer), Layer.provideMerge(SqliteLayer), Layer.provideMerge(PlatformLayer), Layer.provideMerge(LoggerLive(logLevel, logFile)));
27
26
  };
28
27
 
29
28
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vitest-agent/mcp",
3
- "version": "1.3.6",
3
+ "version": "2.0.0",
4
4
  "private": false,
5
5
  "description": "Model Context Protocol server for vitest-agent. Exposes 53 tools for agent access to test data, TDD lifecycle, and session management.",
6
6
  "keywords": [
@@ -29,7 +29,8 @@
29
29
  "exports": {
30
30
  ".": {
31
31
  "types": "./index.d.ts",
32
- "import": "./index.js"
32
+ "import": "./index.js",
33
+ "default": "./index.js"
33
34
  },
34
35
  "./package.json": "./package.json"
35
36
  },
@@ -37,18 +38,12 @@
37
38
  "vitest-agent-mcp": "bin/vitest-agent-mcp.js"
38
39
  },
39
40
  "dependencies": {
40
- "@effect/cluster": "^0.59.0",
41
- "@effect/experimental": "^0.60.0",
42
- "@effect/platform": "^0.96.2",
43
- "@effect/platform-node": "^0.107.0",
44
- "@effect/rpc": "^0.75.1",
45
- "@effect/sql": "^0.51.1",
46
- "@effect/sql-sqlite-node": "^0.52.0",
47
- "@effect/workflow": "^0.18.2",
41
+ "@effect/platform-node": "4.0.0-beta.98",
42
+ "@effect/sql-sqlite-node": "4.0.0-beta.98",
48
43
  "@modelcontextprotocol/sdk": "^1.29.0",
49
44
  "@trpc/server": "^11.18.0",
50
- "@vitest-agent/sdk": "1.3.4",
51
- "effect": "^3.21.4",
45
+ "@vitest-agent/sdk": "2.0.0",
46
+ "effect": "4.0.0-beta.98",
52
47
  "zod": "^4.4.3"
53
48
  },
54
49
  "peerDependencies": {
package/server.js CHANGED
@@ -32,7 +32,7 @@ import { appRouter } from "./router.js";
32
32
  import { registerAllPrompts } from "./prompts/index.js";
33
33
  import { effectToZodSchema } from "./utils/effect-to-zod.js";
34
34
  import { ChannelEvent, DataReader } from "@vitest-agent/sdk";
35
- import { Effect, Option, Schema } from "effect";
35
+ import { Effect, Exit, Option, Schema } from "effect";
36
36
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
37
37
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
38
38
  import { z } from "zod";
@@ -45,9 +45,9 @@ import { z } from "zod";
45
45
  * Returns the enriched event object or the original on resolution failure.
46
46
  */
47
47
  async function resolveChannelEvent(ctx, raw) {
48
- const decoded = Schema.decodeUnknownEither(ChannelEvent)(raw);
49
- if (decoded._tag === "Left") return raw;
50
- const event = decoded.right;
48
+ const decoded = Schema.decodeUnknownExit(ChannelEvent)(raw);
49
+ if (Exit.isFailure(decoded)) return raw;
50
+ const event = decoded.value;
51
51
  return ctx.runtime.runPromise(Effect.gen(function* () {
52
52
  const reader = yield* DataReader;
53
53
  switch (event.type) {
@@ -114,7 +114,7 @@ function structuredResult(text, structured) {
114
114
  * tools that previously rendered their result as JSON.stringify; the
115
115
  * structured payload is identical, so the agent gets the typed object
116
116
  * via MCP's structuredContent channel without paying the markdown
117
- * formatter ceremony of `Schema.transformOrFail`.
117
+ * formatter ceremony of `Schema.decodeTo`.
118
118
  *
119
119
  * @internal
120
120
  */
@@ -92,7 +92,7 @@ const tddErrorToEnvelope = (e) => {
92
92
  if (e instanceof TddTaskAlreadyEndedError) return tddTaskAlreadyEnded(e);
93
93
  return illegalStatusTransition(e);
94
94
  };
95
- const catchTddErrorsAsEnvelope = (effect) => effect.pipe(Effect.catchAll((e) => isKnownTddError(e) ? Effect.succeed(tddErrorToEnvelope(e)) : Effect.fail(e)));
95
+ const catchTddErrorsAsEnvelope = (effect) => effect.pipe(Effect.catch((e) => isKnownTddError(e) ? Effect.succeed(tddErrorToEnvelope(e)) : Effect.fail(e)));
96
96
 
97
97
  //#endregion
98
98
  export { catchTddErrorsAsEnvelope };
@@ -1,6 +1,6 @@
1
1
  import { publicProcedure } from "../context.js";
2
2
  import { DataReader } from "@vitest-agent/sdk";
3
- import { Effect, ParseResult, Schema } from "effect";
3
+ import { Effect, Schema, SchemaGetter } from "effect";
4
4
 
5
5
  //#region src/tools/acceptance-metrics.ts
6
6
  /**
@@ -17,38 +17,38 @@ const totalAnnotation = { description: "Sample size — number of observations t
17
17
  const ratioAnnotation = { description: "Compliance ratio in [0, 1]. Multiply by 100 for the percentage form rendered in the markdown view." };
18
18
  const AcceptanceMetricsResult = Schema.Struct({
19
19
  phaseEvidenceIntegrity: Schema.Struct({
20
- total: Schema.Number.annotations(totalAnnotation),
21
- compliant: Schema.Number.annotations({ description: "Phase transitions that cited a valid artifact and passed binding-rule validation." }),
22
- ratio: Schema.Number.annotations(ratioAnnotation)
23
- }).annotations({
20
+ total: Schema.Number.annotate(totalAnnotation),
21
+ compliant: Schema.Number.annotate({ description: "Phase transitions that cited a valid artifact and passed binding-rule validation." }),
22
+ ratio: Schema.Number.annotate(ratioAnnotation)
23
+ }).annotate({
24
24
  title: "Phase-evidence integrity",
25
25
  description: "Fraction of accepted TDD phase transitions whose cited artifact satisfied the D2 binding rules. Spec target ≥80%."
26
26
  }),
27
27
  complianceHookResponsiveness: Schema.Struct({
28
- total: Schema.Number.annotations(totalAnnotation),
29
- withFollowup: Schema.Number.annotations({ description: "PreToolUse denials / `additionalContext` reminders the orchestrator acknowledged in the next turn." }),
30
- ratio: Schema.Number.annotations(ratioAnnotation)
31
- }).annotations({
28
+ total: Schema.Number.annotate(totalAnnotation),
29
+ withFollowup: Schema.Number.annotate({ description: "PreToolUse denials / `additionalContext` reminders the orchestrator acknowledged in the next turn." }),
30
+ ratio: Schema.Number.annotate(ratioAnnotation)
31
+ }).annotate({
32
32
  title: "Compliance-hook responsiveness",
33
33
  description: "Fraction of compliance signals from PreToolUse hooks the orchestrator acted on. Spec target ≥40%."
34
34
  }),
35
35
  orientationUsefulness: Schema.Struct({
36
- total: Schema.Number.annotations(totalAnnotation),
37
- referencedCount: Schema.Number.annotations({ description: "Sessions where `triage_brief` / `wrapup_prompt` content was referenced in subsequent decisions." }),
38
- ratio: Schema.Number.annotations(ratioAnnotation)
39
- }).annotations({
36
+ total: Schema.Number.annotate(totalAnnotation),
37
+ referencedCount: Schema.Number.annotate({ description: "Sessions where `triage_brief` / `wrapup_prompt` content was referenced in subsequent decisions." }),
38
+ ratio: Schema.Number.annotate(ratioAnnotation)
39
+ }).annotate({
40
40
  title: "Orientation usefulness",
41
41
  description: "Fraction of sessions where orientation prompts measurably steered orchestrator behaviour. Spec target ≥50%."
42
42
  }),
43
43
  antiPatternDetectionRate: Schema.Struct({
44
- total: Schema.Number.annotations(totalAnnotation),
45
- cleanSessions: Schema.Number.annotations({ description: "Sessions that produced no `tdd_artifacts(kind='test_weakened')` rows or DATABASE_BYPASS notes." }),
46
- ratio: Schema.Number.annotations(ratioAnnotation)
47
- }).annotations({
44
+ total: Schema.Number.annotate(totalAnnotation),
45
+ cleanSessions: Schema.Number.annotate({ description: "Sessions that produced no `tdd_artifacts(kind='test_weakened')` rows or DATABASE_BYPASS notes." }),
46
+ ratio: Schema.Number.annotate(ratioAnnotation)
47
+ }).annotate({
48
48
  title: "Anti-pattern detection rate",
49
49
  description: "Fraction of sessions free of weakening edits or sqlite3 bypass attempts. Spec target ≥95%."
50
50
  })
51
- }).annotations({
51
+ }).annotate({
52
52
  identifier: "AcceptanceMetricsResult",
53
53
  title: "Acceptance metrics",
54
54
  description: "The four spec Annex A metrics computed from the current database. Each carries a sample size, a count, and a ratio."
@@ -62,12 +62,11 @@ const formatAcceptanceMetricsMarkdown = (m) => [
62
62
  `3. Orientation usefulness: ${fmtBucket(m.orientationUsefulness)} — target ≥50%`,
63
63
  `4. Anti-pattern detection rate: ${fmtBucket(m.antiPatternDetectionRate)} — target ≥95%`
64
64
  ].join("\n");
65
- const AcceptanceMetricsAsMarkdown = Schema.transformOrFail(AcceptanceMetricsResult, Schema.String, {
66
- strict: true,
67
- decode: (data) => ParseResult.succeed(formatAcceptanceMetricsMarkdown(data)),
68
- encode: (text, _options, ast) => ParseResult.fail(new ParseResult.Forbidden(ast, text, "AcceptanceMetricsAsMarkdown is one-way: markdown cannot be parsed back to AcceptanceMetricsResult."))
69
- });
70
- const acceptanceMetrics = publicProcedure.input(Schema.standardSchemaV1(Schema.Struct({}))).query(async ({ ctx }) => ctx.runtime.runPromise(Effect.gen(function* () {
65
+ const AcceptanceMetricsAsMarkdown = AcceptanceMetricsResult.pipe(Schema.decodeTo(Schema.String, {
66
+ decode: SchemaGetter.transform((data) => formatAcceptanceMetricsMarkdown(data)),
67
+ encode: SchemaGetter.forbidden(() => "AcceptanceMetricsAsMarkdown is one-way: markdown cannot be parsed back to AcceptanceMetricsResult.")
68
+ }));
69
+ const acceptanceMetrics = publicProcedure.input(Schema.toStandardSchemaV1(Schema.Struct({}))).query(async ({ ctx }) => ctx.runtime.runPromise(Effect.gen(function* () {
71
70
  return yield* (yield* DataReader).computeAcceptanceMetrics();
72
71
  })));
73
72
 
@@ -1,6 +1,6 @@
1
1
  import { publicProcedure } from "../context.js";
2
2
  import { CacheManifest, DataReader } from "@vitest-agent/sdk";
3
- import { Effect, Option, ParseResult, Schema } from "effect";
3
+ import { Effect, Option, Schema, SchemaGetter } from "effect";
4
4
 
5
5
  //#region src/tools/cache-health.ts
6
6
  /**
@@ -16,19 +16,19 @@ import { Effect, Option, ParseResult, Schema } from "effect";
16
16
  * @packageDocumentation
17
17
  */
18
18
  const ManifestPresent = Schema.Struct({
19
- manifestPresent: Schema.Literal(true).annotations({ description: "Discriminant — `true` when a cache manifest exists." }),
20
- manifest: CacheManifest.annotations({ description: "Full cache manifest content as written by the reporter." }),
21
- ageMs: Schema.Number.annotations({ description: "Milliseconds since the manifest was last updated. Computed at query time, not stored." }),
22
- stale: Schema.Boolean.annotations({ description: "Convenience flag — `true` when `ageMs` exceeds 24 hours, otherwise `false`." })
23
- }).annotations({
19
+ manifestPresent: Schema.Literal(true).annotate({ description: "Discriminant — `true` when a cache manifest exists." }),
20
+ manifest: CacheManifest.annotate({ description: "Full cache manifest content as written by the reporter." }),
21
+ ageMs: Schema.Number.annotate({ description: "Milliseconds since the manifest was last updated. Computed at query time, not stored." }),
22
+ stale: Schema.Boolean.annotate({ description: "Convenience flag — `true` when `ageMs` exceeds 24 hours, otherwise `false`." })
23
+ }).annotate({
24
24
  identifier: "CacheHealthPresent",
25
25
  title: "Cache manifest present"
26
26
  });
27
- const ManifestAbsent = Schema.Struct({ manifestPresent: Schema.Literal(false).annotations({ description: "Discriminant — `false` when no manifest has been written yet (run tests to populate the cache)." }) }).annotations({
27
+ const ManifestAbsent = Schema.Struct({ manifestPresent: Schema.Literal(false).annotate({ description: "Discriminant — `false` when no manifest has been written yet (run tests to populate the cache)." }) }).annotate({
28
28
  identifier: "CacheHealthAbsent",
29
29
  title: "Cache manifest absent"
30
30
  });
31
- const CacheHealthResult = Schema.Union(ManifestPresent, ManifestAbsent).annotations({
31
+ const CacheHealthResult = Schema.Union([ManifestPresent, ManifestAbsent]).annotate({
32
32
  identifier: "CacheHealthResult",
33
33
  title: "cache_health result",
34
34
  description: "Cache health snapshot. Discriminate on `manifestPresent` to see whether the manifest exists."
@@ -61,11 +61,10 @@ const formatCacheHealthMarkdown = (data) => {
61
61
  }
62
62
  return lines.join("\n");
63
63
  };
64
- const CacheHealthAsMarkdown = Schema.transformOrFail(CacheHealthResult, Schema.String, {
65
- strict: true,
66
- decode: (data) => ParseResult.succeed(formatCacheHealthMarkdown(data)),
67
- encode: (text, _options, ast) => ParseResult.fail(new ParseResult.Forbidden(ast, text, "CacheHealthAsMarkdown is one-way: markdown cannot be parsed back to CacheHealthResult."))
68
- });
64
+ const CacheHealthAsMarkdown = CacheHealthResult.pipe(Schema.decodeTo(Schema.String, {
65
+ decode: SchemaGetter.transform((data) => formatCacheHealthMarkdown(data)),
66
+ encode: SchemaGetter.forbidden(() => "CacheHealthAsMarkdown is one-way: markdown cannot be parsed back to CacheHealthResult.")
67
+ }));
69
68
  const cacheHealth = publicProcedure.query(async ({ ctx }) => ctx.runtime.runPromise(Effect.gen(function* () {
70
69
  const manifestOpt = yield* (yield* DataReader).getManifest();
71
70
  if (Option.isNone(manifestOpt)) return { manifestPresent: false };
@@ -1,6 +1,6 @@
1
1
  import { publicProcedure } from "../context.js";
2
2
  import { DataReader } from "@vitest-agent/sdk";
3
- import { Effect, ParseResult, Schema } from "effect";
3
+ import { Effect, Schema, SchemaGetter } from "effect";
4
4
 
5
5
  //#region src/tools/commit-changes.ts
6
6
  /**
@@ -9,23 +9,29 @@ import { Effect, ParseResult, Schema } from "effect";
9
9
  * @packageDocumentation
10
10
  */
11
11
  const FileRow = Schema.Struct({
12
- filePath: Schema.String.annotations({ description: "Repo-relative path of the changed file." }),
13
- changeKind: Schema.Literal("added", "modified", "deleted", "renamed", "untracked-modified").annotations({ description: "How the file changed in this commit (or `untracked-modified` for working-tree changes attributed to a commit)." })
14
- }).annotations({ identifier: "CommitFileRow" });
12
+ filePath: Schema.String.annotate({ description: "Repo-relative path of the changed file." }),
13
+ changeKind: Schema.Literals([
14
+ "added",
15
+ "modified",
16
+ "deleted",
17
+ "renamed",
18
+ "untracked-modified"
19
+ ]).annotate({ description: "How the file changed in this commit (or `untracked-modified` for working-tree changes attributed to a commit)." })
20
+ }).annotate({ identifier: "CommitFileRow" });
15
21
  const CommitRow = Schema.Struct({
16
- sha: Schema.String.annotations({ description: "Full git commit SHA-1." }),
17
- parentSha: Schema.NullOr(Schema.String).annotations({ description: "Parent commit SHA, or `null` for the root commit / when no parent was recorded." }),
18
- message: Schema.NullOr(Schema.String).annotations({ description: "Commit message subject + body, or `null` if not captured." }),
19
- author: Schema.NullOr(Schema.String).annotations({ description: "Commit author in `Name <email>` form when captured." }),
20
- committedAt: Schema.NullOr(Schema.String).annotations({ description: "ISO-8601 commit timestamp." }),
21
- branch: Schema.NullOr(Schema.String).annotations({ description: "Branch the commit was recorded on at hook fire time." }),
22
- files: Schema.Array(FileRow).annotations({ description: "Files this commit changed, with per-file change kinds." })
23
- }).annotations({ identifier: "CommitRow" });
22
+ sha: Schema.String.annotate({ description: "Full git commit SHA-1." }),
23
+ parentSha: Schema.NullOr(Schema.String).annotate({ description: "Parent commit SHA, or `null` for the root commit / when no parent was recorded." }),
24
+ message: Schema.NullOr(Schema.String).annotate({ description: "Commit message subject + body, or `null` if not captured." }),
25
+ author: Schema.NullOr(Schema.String).annotate({ description: "Commit author in `Name <email>` form when captured." }),
26
+ committedAt: Schema.NullOr(Schema.String).annotate({ description: "ISO-8601 commit timestamp." }),
27
+ branch: Schema.NullOr(Schema.String).annotate({ description: "Branch the commit was recorded on at hook fire time." }),
28
+ files: Schema.Array(FileRow).annotate({ description: "Files this commit changed, with per-file change kinds." })
29
+ }).annotate({ identifier: "CommitRow" });
24
30
  const CommitChangesResult = Schema.Struct({
25
- filterSha: Schema.optional(Schema.String).annotations({ description: "Echo of the optional `sha` filter the caller passed; absent when no filter was applied (recent commits returned)." }),
26
- count: Schema.Number.annotations({ description: "Number of commit rows returned." }),
27
- commits: Schema.Array(CommitRow).annotations({ description: "Matching commits, newest first when `sha` was omitted; up to 20 rows." })
28
- }).annotations({
31
+ filterSha: Schema.optional(Schema.String).annotate({ description: "Echo of the optional `sha` filter the caller passed; absent when no filter was applied (recent commits returned)." }),
32
+ count: Schema.Number.annotate({ description: "Number of commit rows returned." }),
33
+ commits: Schema.Array(CommitRow).annotate({ description: "Matching commits, newest first when `sha` was omitted; up to 20 rows." })
34
+ }).annotate({
29
35
  identifier: "CommitChangesResult",
30
36
  title: "commit_changes result",
31
37
  description: "Commit metadata + per-file changes captured by the post-commit Bash hook."
@@ -46,12 +52,11 @@ const formatCommitChangesMarkdown = (data) => {
46
52
  }
47
53
  return lines.join("\n").trim();
48
54
  };
49
- const CommitChangesAsMarkdown = Schema.transformOrFail(CommitChangesResult, Schema.String, {
50
- strict: true,
51
- decode: (data) => ParseResult.succeed(formatCommitChangesMarkdown(data)),
52
- encode: (text, _options, ast) => ParseResult.fail(new ParseResult.Forbidden(ast, text, "CommitChangesAsMarkdown is one-way: markdown cannot be parsed back to CommitChangesResult."))
53
- });
54
- const commitChanges = publicProcedure.input(Schema.standardSchemaV1(Schema.Struct({ sha: Schema.optional(Schema.String) }))).query(async ({ ctx, input }) => ctx.runtime.runPromise(Effect.gen(function* () {
55
+ const CommitChangesAsMarkdown = CommitChangesResult.pipe(Schema.decodeTo(Schema.String, {
56
+ decode: SchemaGetter.transform((data) => formatCommitChangesMarkdown(data)),
57
+ encode: SchemaGetter.forbidden(() => "CommitChangesAsMarkdown is one-way: markdown cannot be parsed back to CommitChangesResult.")
58
+ }));
59
+ const commitChanges = publicProcedure.input(Schema.toStandardSchemaV1(Schema.Struct({ sha: Schema.optional(Schema.String) }))).query(async ({ ctx, input }) => ctx.runtime.runPromise(Effect.gen(function* () {
55
60
  const entries = yield* (yield* DataReader).getCommitChanges(input.sha);
56
61
  return {
57
62
  ...input.sha !== void 0 && { filterSha: input.sha },
@@ -1,6 +1,6 @@
1
1
  import { publicProcedure } from "../context.js";
2
2
  import { DataReader } from "@vitest-agent/sdk";
3
- import { Effect, Option, ParseResult, Schema } from "effect";
3
+ import { Effect, Option, Schema, SchemaGetter } from "effect";
4
4
 
5
5
  //#region src/tools/configure.ts
6
6
  /**
@@ -9,36 +9,33 @@ import { Effect, Option, ParseResult, Schema } from "effect";
9
9
  * @packageDocumentation
10
10
  */
11
11
  const SettingsRowSchema = Schema.Struct({
12
- hash: Schema.String.annotations({ description: "Stable SHA-1 of the captured Vitest settings; `test_runs.settings_hash` foreign key." }),
13
- reporters: Schema.NullOr(Schema.String).annotations({ description: "Comma-separated reporter list as resolved from the user's vitest config." }),
14
- coverageEnabled: Schema.Boolean.annotations({ description: "Whether coverage was on for this run." }),
15
- coverageProvider: Schema.NullOr(Schema.String).annotations({ description: "Coverage provider (`v8` or `istanbul`)." }),
16
- coverageThresholds: Schema.NullOr(Schema.String).annotations({ description: "JSON-encoded threshold table when present; `null` when no thresholds were configured." }),
17
- coverageTargets: Schema.NullOr(Schema.String).annotations({ description: "JSON-encoded aspirational target table when present." }),
18
- pool: Schema.NullOr(Schema.String).annotations({ description: "Vitest pool (`forks` / `threads` / `vmThreads`)." }),
19
- shard: Schema.NullOr(Schema.String).annotations({ description: "Shard descriptor when running sharded (`1/4` form)." }),
20
- project: Schema.NullOr(Schema.String).annotations({ description: "Project name within a multi-project setup." }),
21
- environment: Schema.NullOr(Schema.String).annotations({ description: "Test environment (`node`, `jsdom`, etc.)." }),
22
- envVars: Schema.Record({
23
- key: Schema.String,
24
- value: Schema.String
25
- }).annotations({ description: "Captured CI / test env vars associated with this settings hash." }),
26
- capturedAt: Schema.String.annotations({ description: "ISO-8601 timestamp the settings row was first written." })
27
- }).annotations({
12
+ hash: Schema.String.annotate({ description: "Stable SHA-1 of the captured Vitest settings; `test_runs.settings_hash` foreign key." }),
13
+ reporters: Schema.NullOr(Schema.String).annotate({ description: "Comma-separated reporter list as resolved from the user's vitest config." }),
14
+ coverageEnabled: Schema.Boolean.annotate({ description: "Whether coverage was on for this run." }),
15
+ coverageProvider: Schema.NullOr(Schema.String).annotate({ description: "Coverage provider (`v8` or `istanbul`)." }),
16
+ coverageThresholds: Schema.NullOr(Schema.String).annotate({ description: "JSON-encoded threshold table when present; `null` when no thresholds were configured." }),
17
+ coverageTargets: Schema.NullOr(Schema.String).annotate({ description: "JSON-encoded aspirational target table when present." }),
18
+ pool: Schema.NullOr(Schema.String).annotate({ description: "Vitest pool (`forks` / `threads` / `vmThreads`)." }),
19
+ shard: Schema.NullOr(Schema.String).annotate({ description: "Shard descriptor when running sharded (`1/4` form)." }),
20
+ project: Schema.NullOr(Schema.String).annotate({ description: "Project name within a multi-project setup." }),
21
+ environment: Schema.NullOr(Schema.String).annotate({ description: "Test environment (`node`, `jsdom`, etc.)." }),
22
+ envVars: Schema.Record(Schema.String, Schema.String).annotate({ description: "Captured CI / test env vars associated with this settings hash." }),
23
+ capturedAt: Schema.String.annotate({ description: "ISO-8601 timestamp the settings row was first written." })
24
+ }).annotate({
28
25
  identifier: "SettingsRowSchema",
29
26
  title: "Vitest settings snapshot"
30
27
  });
31
28
  const SettingsFound = Schema.Struct({
32
- found: Schema.Literal(true).annotations({ description: "Discriminant — `true` when settings were located." }),
33
- source: Schema.Literal("requested", "latest").annotations({ description: "`requested` when the caller supplied `settingsHash`; `latest` when the most-recent row was returned." }),
29
+ found: Schema.Literal(true).annotate({ description: "Discriminant — `true` when settings were located." }),
30
+ source: Schema.Literals(["requested", "latest"]).annotate({ description: "`requested` when the caller supplied `settingsHash`; `latest` when the most-recent row was returned." }),
34
31
  settings: SettingsRowSchema
35
- }).annotations({ identifier: "ConfigureFound" });
32
+ }).annotate({ identifier: "ConfigureFound" });
36
33
  const SettingsAbsent = Schema.Struct({
37
- found: Schema.Literal(false).annotations({ description: "Discriminant — `false` when no settings matched." }),
38
- source: Schema.Literal("requested", "latest"),
39
- requestedHash: Schema.optional(Schema.String).annotations({ description: "Echo of the hash the caller passed; absent when the empty `latest` lookup found nothing." })
40
- }).annotations({ identifier: "ConfigureAbsent" });
41
- const ConfigureResult = Schema.Union(SettingsFound, SettingsAbsent).annotations({
34
+ found: Schema.Literal(false).annotate({ description: "Discriminant — `false` when no settings matched." }),
35
+ source: Schema.Literals(["requested", "latest"]),
36
+ requestedHash: Schema.optional(Schema.String).annotate({ description: "Echo of the hash the caller passed; absent when the empty `latest` lookup found nothing." })
37
+ }).annotate({ identifier: "ConfigureAbsent" });
38
+ const ConfigureResult = Schema.Union([SettingsFound, SettingsAbsent]).annotate({
42
39
  identifier: "ConfigureResult",
43
40
  title: "configure result",
44
41
  description: "Captured Vitest settings for a run, or an absence record when the lookup found nothing."
@@ -73,12 +70,11 @@ const formatConfigureMarkdown = (data) => {
73
70
  ].join("\n");
74
71
  return `No settings found for hash \`${data.requestedHash ?? "(unknown)"}\`.`;
75
72
  };
76
- const ConfigureAsMarkdown = Schema.transformOrFail(ConfigureResult, Schema.String, {
77
- strict: true,
78
- decode: (data) => ParseResult.succeed(formatConfigureMarkdown(data)),
79
- encode: (text, _options, ast) => ParseResult.fail(new ParseResult.Forbidden(ast, text, "ConfigureAsMarkdown is one-way: markdown cannot be parsed back to ConfigureResult."))
80
- });
81
- const configure = publicProcedure.input(Schema.standardSchemaV1(Schema.Struct({ settingsHash: Schema.optional(Schema.String) }))).query(async ({ ctx, input }) => ctx.runtime.runPromise(Effect.gen(function* () {
73
+ const ConfigureAsMarkdown = ConfigureResult.pipe(Schema.decodeTo(Schema.String, {
74
+ decode: SchemaGetter.transform((data) => formatConfigureMarkdown(data)),
75
+ encode: SchemaGetter.forbidden(() => "ConfigureAsMarkdown is one-way: markdown cannot be parsed back to ConfigureResult.")
76
+ }));
77
+ const configure = publicProcedure.input(Schema.toStandardSchemaV1(Schema.Struct({ settingsHash: Schema.optional(Schema.String) }))).query(async ({ ctx, input }) => ctx.runtime.runPromise(Effect.gen(function* () {
82
78
  const reader = yield* DataReader;
83
79
  if (input.settingsHash === void 0) {
84
80
  const latestOpt = yield* reader.getLatestSettings();
package/tools/coverage.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { publicProcedure } from "../context.js";
2
2
  import { CoverageReport, DataReader } from "@vitest-agent/sdk";
3
- import { Effect, Option, ParseResult, Schema } from "effect";
3
+ import { Effect, Option, Schema, SchemaGetter } from "effect";
4
4
 
5
5
  //#region src/tools/coverage.ts
6
6
  /**
@@ -12,12 +12,12 @@ const CoverageAvailable = Schema.Struct({
12
12
  dataAvailable: Schema.Literal(true),
13
13
  project: Schema.String,
14
14
  coverage: CoverageReport
15
- }).annotations({ identifier: "TestCoverageAvailable" });
15
+ }).annotate({ identifier: "TestCoverageAvailable" });
16
16
  const CoverageAbsent = Schema.Struct({
17
17
  dataAvailable: Schema.Literal(false),
18
18
  project: Schema.String
19
- }).annotations({ identifier: "TestCoverageAbsent" });
20
- const TestCoverageResult = Schema.Union(CoverageAvailable, CoverageAbsent).annotations({
19
+ }).annotate({ identifier: "TestCoverageAbsent" });
20
+ const TestCoverageResult = Schema.Union([CoverageAvailable, CoverageAbsent]).annotate({
21
21
  identifier: "TestCoverageResult",
22
22
  title: "test_coverage result",
23
23
  description: "Per-project coverage report. Discriminate on `dataAvailable` for cold-start handling."
@@ -52,12 +52,11 @@ const formatTestCoverageMarkdown = (data) => {
52
52
  } else lines.push("✅ All files meet coverage thresholds.", "");
53
53
  return lines.join("\n");
54
54
  };
55
- const TestCoverageAsMarkdown = Schema.transformOrFail(TestCoverageResult, Schema.String, {
56
- strict: true,
57
- decode: (data) => ParseResult.succeed(formatTestCoverageMarkdown(data)),
58
- encode: (text, _options, ast) => ParseResult.fail(new ParseResult.Forbidden(ast, text, "TestCoverageAsMarkdown is one-way."))
59
- });
60
- const testCoverage = publicProcedure.input(Schema.standardSchemaV1(Schema.Struct({ project: Schema.optional(Schema.String) }))).query(async ({ ctx, input }) => ctx.runtime.runPromise(Effect.gen(function* () {
55
+ const TestCoverageAsMarkdown = TestCoverageResult.pipe(Schema.decodeTo(Schema.String, {
56
+ decode: SchemaGetter.transform((data) => formatTestCoverageMarkdown(data)),
57
+ encode: SchemaGetter.forbidden(() => "TestCoverageAsMarkdown is one-way.")
58
+ }));
59
+ const testCoverage = publicProcedure.input(Schema.toStandardSchemaV1(Schema.Struct({ project: Schema.optional(Schema.String) }))).query(async ({ ctx, input }) => ctx.runtime.runPromise(Effect.gen(function* () {
61
60
  const reader = yield* DataReader;
62
61
  const project = input.project ?? "default";
63
62
  const coverageOpt = yield* reader.getCoverage(project);
package/tools/errors.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { publicProcedure } from "../context.js";
2
2
  import { DataReader } from "@vitest-agent/sdk";
3
- import { Effect, ParseResult, Schema } from "effect";
3
+ import { Effect, Schema, SchemaGetter } from "effect";
4
4
 
5
5
  //#region src/tools/errors.ts
6
6
  /**
@@ -11,8 +11,8 @@ import { Effect, ParseResult, Schema } from "effect";
11
11
  * - types the procedure's return value;
12
12
  * - drives `formatTestErrorsMarkdown` (input typed via `Schema.Type`);
13
13
  * - composes into `TestErrorsAsMarkdown`, a one-way
14
- * `Schema.transformOrFail` whose `encode` direction renders the
15
- * markdown the text channel carries (decode is forbidden because
14
+ * `Schema.decodeTo` whose `decode` direction renders the
15
+ * markdown the text channel carries (encode is forbidden because
16
16
  * markdown rendering is lossy);
17
17
  * - bridges to zod via `effectToZodSchema` for the SDK's
18
18
  * `outputSchema` field, so the structured shape we declare to MCP
@@ -22,39 +22,44 @@ import { Effect, ParseResult, Schema } from "effect";
22
22
  */
23
23
  /** One row in the structured `errors[]` array. */
24
24
  const TestErrorRow = Schema.Struct({
25
- id: Schema.Number.annotations({
25
+ id: Schema.Number.annotate({
26
26
  title: "test_errors.id",
27
27
  description: "Numeric primary key of this error row. Pass as `citedTestErrorId` when calling `hypothesis (action: record)`."
28
28
  }),
29
- topStackFrameId: Schema.NullOr(Schema.Number).annotations({
29
+ topStackFrameId: Schema.NullOr(Schema.Number).annotate({
30
30
  title: "stack_frames.id (top frame)",
31
31
  description: "`stack_frames.id` of the top frame (ordinal=0); `null` when no frames were captured. Pass as `citedStackFrameId` to `hypothesis (action: record)`."
32
32
  }),
33
- name: Schema.NullOr(Schema.String).annotations({ description: "Error class name (e.g. `AssertionError`, `TypeError`); `null` when the underlying throw provided no name." }),
34
- message: Schema.String.annotations({ description: "Error message text as the test framework reported it." }),
35
- diff: Schema.NullOr(Schema.String).annotations({ description: "Unified-diff representation of expected vs. actual when the assertion produced one; `null` otherwise." }),
36
- actual: Schema.NullOr(Schema.String).annotations({ description: "Actual value the assertion received, when captured." }),
37
- expected: Schema.NullOr(Schema.String).annotations({ description: "Expected value the assertion compared against, when captured." }),
38
- stack: Schema.NullOr(Schema.String).annotations({ description: "Newline-joined stack frames as the framework formatted them; structured frames live in `stack_frames`." }),
39
- scope: Schema.Literal("test", "suite", "module", "unhandled").annotations({ description: "Where the error fired: `test` (a single test case), `suite` (a `describe` setup), `module` (collection / import time), or `unhandled` (uncaught from a background context)." }),
40
- testFullName: Schema.NullOr(Schema.String).annotations({ description: "Full hierarchical test name (`describe > it`); `null` for non-test scopes (`module`, `unhandled`)." }),
41
- moduleFile: Schema.NullOr(Schema.String).annotations({ description: "Repo-relative path of the test module the error originated in." })
42
- }).annotations({
33
+ name: Schema.NullOr(Schema.String).annotate({ description: "Error class name (e.g. `AssertionError`, `TypeError`); `null` when the underlying throw provided no name." }),
34
+ message: Schema.String.annotate({ description: "Error message text as the test framework reported it." }),
35
+ diff: Schema.NullOr(Schema.String).annotate({ description: "Unified-diff representation of expected vs. actual when the assertion produced one; `null` otherwise." }),
36
+ actual: Schema.NullOr(Schema.String).annotate({ description: "Actual value the assertion received, when captured." }),
37
+ expected: Schema.NullOr(Schema.String).annotate({ description: "Expected value the assertion compared against, when captured." }),
38
+ stack: Schema.NullOr(Schema.String).annotate({ description: "Newline-joined stack frames as the framework formatted them; structured frames live in `stack_frames`." }),
39
+ scope: Schema.Literals([
40
+ "test",
41
+ "suite",
42
+ "module",
43
+ "unhandled"
44
+ ]).annotate({ description: "Where the error fired: `test` (a single test case), `suite` (a `describe` setup), `module` (collection / import time), or `unhandled` (uncaught from a background context)." }),
45
+ testFullName: Schema.NullOr(Schema.String).annotate({ description: "Full hierarchical test name (`describe > it`); `null` for non-test scopes (`module`, `unhandled`)." }),
46
+ moduleFile: Schema.NullOr(Schema.String).annotate({ description: "Repo-relative path of the test module the error originated in." })
47
+ }).annotate({
43
48
  identifier: "TestErrorRow",
44
49
  title: "Test error row",
45
50
  description: "Single error captured during a test run, joined with stack frame and source-location context."
46
51
  });
47
52
  /** Top-level structured payload — populates `structuredContent`. */
48
53
  const TestErrorsResult = Schema.Struct({
49
- project: Schema.String.annotations({
54
+ project: Schema.String.annotate({
50
55
  title: "Project name",
51
56
  description: "Workspace project key the run was attributed to (e.g. `playground`, `@org/pkg`).",
52
57
  examples: ["playground", "@org/pkg"]
53
58
  }),
54
- errorName: Schema.optional(Schema.String).annotations({ description: "Echo of the optional `errorName` filter the caller passed; absent when no filter was applied." }),
55
- count: Schema.Number.annotations({ description: "Total error rows in `errors`." }),
56
- errors: Schema.Array(TestErrorRow).annotations({ description: "Errors from the most recent test run for this project, optionally filtered by `errorName`. Empty when no errors matched." })
57
- }).annotations({
59
+ errorName: Schema.optional(Schema.String).annotate({ description: "Echo of the optional `errorName` filter the caller passed; absent when no filter was applied." }),
60
+ count: Schema.Number.annotate({ description: "Total error rows in `errors`." }),
61
+ errors: Schema.Array(TestErrorRow).annotate({ description: "Errors from the most recent test run for this project, optionally filtered by `errorName`. Empty when no errors matched." })
62
+ }).annotate({
58
63
  identifier: "TestErrorsResult",
59
64
  title: "test_errors result",
60
65
  description: "Structured payload of the `test_errors` MCP tool. Carries the cite-able test_errors.id and stack_frames.id values agents need for `hypothesis (action: record)`."
@@ -115,7 +120,7 @@ const formatTestErrorsMarkdown = (data) => {
115
120
  /**
116
121
  * One-way codec: structured `TestErrorsResult` → markdown text.
117
122
  *
118
- * `Schema.transformOrFail`'s `decode` direction goes
123
+ * `Schema.decodeTo`'s `decode` direction goes
119
124
  * `From.Type → To.Encoded`; in this transform `From = TestErrorsResult`
120
125
  * and `To = Schema.String`, so the resulting schema is
121
126
  * `Schema<string, TestErrorsResultType>` — its parsed Type is the
@@ -129,12 +134,11 @@ const formatTestErrorsMarkdown = (data) => {
129
134
  * suites can drive the same path without mocking anything — the
130
135
  * transform IS the rendering contract.
131
136
  */
132
- const TestErrorsAsMarkdown = Schema.transformOrFail(TestErrorsResult, Schema.String, {
133
- strict: true,
134
- decode: (data) => ParseResult.succeed(formatTestErrorsMarkdown(data)),
135
- encode: (text, _options, ast) => ParseResult.fail(new ParseResult.Forbidden(ast, text, "TestErrorsAsMarkdown is one-way: markdown cannot be parsed back to TestErrorsResult. Consume the procedure's structured output (or MCP structuredContent) directly."))
136
- });
137
- const testErrors = publicProcedure.input(Schema.standardSchemaV1(Schema.Struct({
137
+ const TestErrorsAsMarkdown = TestErrorsResult.pipe(Schema.decodeTo(Schema.String, {
138
+ decode: SchemaGetter.transform((data) => formatTestErrorsMarkdown(data)),
139
+ encode: SchemaGetter.forbidden(() => "TestErrorsAsMarkdown is one-way: markdown cannot be parsed back to TestErrorsResult. Consume the procedure's structured output (or MCP structuredContent) directly.")
140
+ }));
141
+ const testErrors = publicProcedure.input(Schema.toStandardSchemaV1(Schema.Struct({
138
142
  project: Schema.String,
139
143
  errorName: Schema.optional(Schema.String)
140
144
  }))).query(async ({ ctx, input }) => ctx.runtime.runPromise(Effect.gen(function* () {
@@ -1,6 +1,6 @@
1
1
  import { publicProcedure } from "../context.js";
2
2
  import { DataReader } from "@vitest-agent/sdk";
3
- import { Effect, Option, ParseResult, Schema } from "effect";
3
+ import { Effect, Option, Schema, SchemaGetter } from "effect";
4
4
 
5
5
  //#region src/tools/failure-signature-get.ts
6
6
  /**
@@ -14,22 +14,22 @@ const RecentError = Schema.Struct({
14
14
  message: Schema.String
15
15
  });
16
16
  const SignatureFound = Schema.Struct({
17
- found: Schema.Literal(true).annotations({ description: "Discriminant — `true` when a signature row matched." }),
18
- signatureHash: Schema.String.annotations({
17
+ found: Schema.Literal(true).annotate({ description: "Discriminant — `true` when a signature row matched." }),
18
+ signatureHash: Schema.String.annotate({
19
19
  title: "failure_signatures.signature_hash",
20
20
  description: "16-char SHA-256 over (error_name, normalized assertion shape, top-frame function name, function-boundary line)."
21
21
  }),
22
22
  firstSeenRunId: Schema.NullOr(Schema.Number),
23
23
  firstSeenAt: Schema.String,
24
24
  lastSeenAt: Schema.NullOr(Schema.String),
25
- occurrenceCount: Schema.Number.annotations({ description: "Total times this signature has been observed." }),
25
+ occurrenceCount: Schema.Number.annotate({ description: "Total times this signature has been observed." }),
26
26
  recentErrors: Schema.Array(RecentError)
27
27
  });
28
28
  const SignatureMissing = Schema.Struct({
29
29
  found: Schema.Literal(false),
30
30
  requestedHash: Schema.String
31
31
  });
32
- const FailureSignatureGetResult = Schema.Union(SignatureFound, SignatureMissing).annotations({
32
+ const FailureSignatureGetResult = Schema.Union([SignatureFound, SignatureMissing]).annotate({
33
33
  identifier: "FailureSignatureGetResult",
34
34
  title: "failure_signature_get result",
35
35
  description: "Discriminate on `found`. Found rows carry first/last-seen timestamps and recent occurrences."
@@ -52,12 +52,11 @@ const formatFailureSignatureMarkdown = (data) => {
52
52
  }
53
53
  return lines.join("\n");
54
54
  };
55
- const FailureSignatureGetAsMarkdown = Schema.transformOrFail(FailureSignatureGetResult, Schema.String, {
56
- strict: true,
57
- decode: (data) => ParseResult.succeed(formatFailureSignatureMarkdown(data)),
58
- encode: (text, _options, ast) => ParseResult.fail(new ParseResult.Forbidden(ast, text, "FailureSignatureGetAsMarkdown is one-way."))
59
- });
60
- const failureSignatureGet = publicProcedure.input(Schema.standardSchemaV1(Schema.Struct({ hash: Schema.String }))).query(async ({ ctx, input }) => ctx.runtime.runPromise(Effect.gen(function* () {
55
+ const FailureSignatureGetAsMarkdown = FailureSignatureGetResult.pipe(Schema.decodeTo(Schema.String, {
56
+ decode: SchemaGetter.transform((data) => formatFailureSignatureMarkdown(data)),
57
+ encode: SchemaGetter.forbidden(() => "FailureSignatureGetAsMarkdown is one-way.")
58
+ }));
59
+ const failureSignatureGet = publicProcedure.input(Schema.toStandardSchemaV1(Schema.Struct({ hash: Schema.String }))).query(async ({ ctx, input }) => ctx.runtime.runPromise(Effect.gen(function* () {
61
60
  const opt = yield* (yield* DataReader).getFailureSignatureByHash(input.hash);
62
61
  if (Option.isNone(opt)) return {
63
62
  found: false,