@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/bin/vitest-agent-mcp.js +2 -2
- package/index.d.ts +999 -1001
- package/index.js +1 -1
- package/layers/McpLive.js +4 -5
- package/package.json +7 -12
- package/server.js +5 -5
- package/tools/_tdd-error-envelope.js +1 -1
- package/tools/acceptance-metrics.js +23 -24
- package/tools/cache-health.js +12 -13
- package/tools/commit-changes.js +27 -22
- package/tools/configure.js +27 -31
- package/tools/coverage.js +9 -10
- package/tools/errors.js +31 -27
- package/tools/failure-signature-get.js +10 -11
- package/tools/file-coverage.js +14 -11
- package/tools/help.js +1 -1
- package/tools/history.js +29 -30
- package/tools/hypothesis.js +33 -12
- package/tools/inventory.js +45 -27
- package/tools/note.js +32 -10
- package/tools/overview.js +16 -13
- package/tools/ping.js +1 -1
- package/tools/register-agent.js +18 -13
- package/tools/run-tests.js +22 -21
- package/tools/settings-list.js +11 -12
- package/tools/status.js +14 -15
- package/tools/tdd-artifact.js +28 -13
- package/tools/tdd-behavior.js +33 -15
- package/tools/tdd-goal.js +39 -23
- package/tools/tdd-phase-transition-request.js +59 -25
- package/tools/tdd-task.js +40 -21
- package/tools/test.js +28 -18
- package/tools/trends.js +12 -13
- package/tools/triage-brief.js +4 -4
- package/tools/turn-search.js +25 -18
- package/tools/wrapup-prompt.js +18 -6
- package/tsdoc-metadata.json +1 -1
- package/utils/effect-to-zod.js +18 -5
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 = "
|
|
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
|
|
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,
|
|
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 =
|
|
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(
|
|
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": "
|
|
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/
|
|
41
|
-
"@effect/
|
|
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": "
|
|
51
|
-
"effect": "
|
|
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.
|
|
49
|
-
if (decoded
|
|
50
|
-
const event = decoded.
|
|
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.
|
|
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.
|
|
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,
|
|
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.
|
|
21
|
-
compliant: Schema.Number.
|
|
22
|
-
ratio: Schema.Number.
|
|
23
|
-
}).
|
|
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.
|
|
29
|
-
withFollowup: Schema.Number.
|
|
30
|
-
ratio: Schema.Number.
|
|
31
|
-
}).
|
|
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.
|
|
37
|
-
referencedCount: Schema.Number.
|
|
38
|
-
ratio: Schema.Number.
|
|
39
|
-
}).
|
|
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.
|
|
45
|
-
cleanSessions: Schema.Number.
|
|
46
|
-
ratio: Schema.Number.
|
|
47
|
-
}).
|
|
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
|
-
}).
|
|
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.
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
|
package/tools/cache-health.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { publicProcedure } from "../context.js";
|
|
2
2
|
import { CacheManifest, DataReader } from "@vitest-agent/sdk";
|
|
3
|
-
import { Effect, Option,
|
|
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).
|
|
20
|
-
manifest: CacheManifest.
|
|
21
|
-
ageMs: Schema.Number.
|
|
22
|
-
stale: Schema.Boolean.
|
|
23
|
-
}).
|
|
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).
|
|
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).
|
|
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.
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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 };
|
package/tools/commit-changes.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { publicProcedure } from "../context.js";
|
|
2
2
|
import { DataReader } from "@vitest-agent/sdk";
|
|
3
|
-
import { 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.
|
|
13
|
-
changeKind: Schema.
|
|
14
|
-
|
|
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.
|
|
17
|
-
parentSha: Schema.NullOr(Schema.String).
|
|
18
|
-
message: Schema.NullOr(Schema.String).
|
|
19
|
-
author: Schema.NullOr(Schema.String).
|
|
20
|
-
committedAt: Schema.NullOr(Schema.String).
|
|
21
|
-
branch: Schema.NullOr(Schema.String).
|
|
22
|
-
files: Schema.Array(FileRow).
|
|
23
|
-
}).
|
|
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).
|
|
26
|
-
count: Schema.Number.
|
|
27
|
-
commits: Schema.Array(CommitRow).
|
|
28
|
-
}).
|
|
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.
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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 },
|
package/tools/configure.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { publicProcedure } from "../context.js";
|
|
2
2
|
import { DataReader } from "@vitest-agent/sdk";
|
|
3
|
-
import { Effect, Option,
|
|
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.
|
|
13
|
-
reporters: Schema.NullOr(Schema.String).
|
|
14
|
-
coverageEnabled: Schema.Boolean.
|
|
15
|
-
coverageProvider: Schema.NullOr(Schema.String).
|
|
16
|
-
coverageThresholds: Schema.NullOr(Schema.String).
|
|
17
|
-
coverageTargets: Schema.NullOr(Schema.String).
|
|
18
|
-
pool: Schema.NullOr(Schema.String).
|
|
19
|
-
shard: Schema.NullOr(Schema.String).
|
|
20
|
-
project: Schema.NullOr(Schema.String).
|
|
21
|
-
environment: Schema.NullOr(Schema.String).
|
|
22
|
-
envVars: Schema.Record({
|
|
23
|
-
|
|
24
|
-
|
|
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).
|
|
33
|
-
source: Schema.
|
|
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
|
-
}).
|
|
32
|
+
}).annotate({ identifier: "ConfigureFound" });
|
|
36
33
|
const SettingsAbsent = Schema.Struct({
|
|
37
|
-
found: Schema.Literal(false).
|
|
38
|
-
source: Schema.
|
|
39
|
-
requestedHash: Schema.optional(Schema.String).
|
|
40
|
-
}).
|
|
41
|
-
const ConfigureResult = Schema.Union(SettingsFound, SettingsAbsent).
|
|
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.
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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,
|
|
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
|
-
}).
|
|
15
|
+
}).annotate({ identifier: "TestCoverageAvailable" });
|
|
16
16
|
const CoverageAbsent = Schema.Struct({
|
|
17
17
|
dataAvailable: Schema.Literal(false),
|
|
18
18
|
project: Schema.String
|
|
19
|
-
}).
|
|
20
|
-
const TestCoverageResult = Schema.Union(CoverageAvailable, CoverageAbsent).
|
|
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.
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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,
|
|
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.
|
|
15
|
-
* markdown the text channel carries (
|
|
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.
|
|
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).
|
|
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).
|
|
34
|
-
message: Schema.String.
|
|
35
|
-
diff: Schema.NullOr(Schema.String).
|
|
36
|
-
actual: Schema.NullOr(Schema.String).
|
|
37
|
-
expected: Schema.NullOr(Schema.String).
|
|
38
|
-
stack: Schema.NullOr(Schema.String).
|
|
39
|
-
scope: Schema.
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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.
|
|
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).
|
|
55
|
-
count: Schema.Number.
|
|
56
|
-
errors: Schema.Array(TestErrorRow).
|
|
57
|
-
}).
|
|
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.
|
|
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.
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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,
|
|
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).
|
|
18
|
-
signatureHash: Schema.String.
|
|
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.
|
|
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).
|
|
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.
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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,
|