@vitest-agent/mcp 3.0.4 → 4.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/README.md +7 -6
- package/annotations.js +18 -0
- package/bin/vitest-agent-mcp.js +5 -140
- package/{middleware/idempotency.js → idempotency.js} +48 -49
- package/index.d.ts +5610 -1549
- package/index.js +35 -17
- package/main.d.ts +31 -0
- package/main.js +144 -0
- package/package.json +9 -6
- package/prompts/layer.js +108 -0
- package/register-toolkit.js +311 -0
- package/server.js +24 -894
- package/{context.js → session.js} +38 -22
- package/toolkit.js +86 -0
- package/tools/acceptance-metrics.js +26 -14
- package/tools/cache-health.js +28 -17
- package/tools/commit-changes.js +33 -10
- package/tools/configure.js +33 -10
- package/tools/coverage.js +34 -5
- package/tools/errors.js +36 -26
- package/tools/failure-signature-get.js +33 -10
- package/tools/file-coverage.js +37 -13
- package/tools/help.js +45 -4
- package/tools/history.js +39 -19
- package/tools/hypothesis.js +118 -102
- package/tools/inventory.js +149 -146
- package/tools/note.js +131 -113
- package/tools/overview.js +33 -10
- package/tools/ping.js +22 -10
- package/tools/register-agent.js +88 -62
- package/tools/run-tests.js +76 -23
- package/tools/settings-list.js +27 -10
- package/tools/status.js +35 -10
- package/tools/tdd-artifact.js +37 -22
- package/tools/tdd-behavior.js +120 -108
- package/tools/tdd-goal.js +98 -85
- package/tools/tdd-phase-transition-request.js +201 -166
- package/tools/tdd-progress-push.js +102 -0
- package/tools/tdd-task.js +140 -138
- package/tools/test.js +152 -138
- package/tools/trends.js +37 -18
- package/tools/triage-brief.js +34 -15
- package/tools/turn-search.js +39 -16
- package/tools/wrapup-prompt.js +37 -17
- package/utils/crash-guards.js +0 -22
- package/utils/replay-marker.js +12 -0
- package/utils/safe-format-fatal-error.js +0 -16
- package/utils/tool-error-envelope.js +3 -3
- package/version.js +13 -0
- package/layers/McpLive.js +0 -29
- package/prompts/index.js +0 -89
- package/router.js +0 -74
- package/session-env.js +0 -112
- package/utils/effect-to-zod.js +0 -158
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { MutableRef } from "effect";
|
|
1
|
+
import { Context, Layer, MutableRef } from "effect";
|
|
3
2
|
|
|
4
|
-
//#region src/
|
|
3
|
+
//#region src/session.ts
|
|
5
4
|
/**
|
|
6
|
-
* Creates a new
|
|
5
|
+
* Creates a new `CurrentSessionIdRef` with an optional initial value.
|
|
7
6
|
*
|
|
8
7
|
* @param initial - the starting chat id, or `null` when unknown at construction time
|
|
9
8
|
* @returns a mutable ref holding the current session id
|
|
@@ -19,7 +18,7 @@ const createCurrentSessionIdRef = (initial = null) => {
|
|
|
19
18
|
};
|
|
20
19
|
};
|
|
21
20
|
/**
|
|
22
|
-
* Creates a new
|
|
21
|
+
* Creates a new `SessionContextRef` with an optional initial value.
|
|
23
22
|
*
|
|
24
23
|
* When a `recover` thunk is supplied, `get()` invokes it lazily while the
|
|
25
24
|
* held value is `null` and caches the first non-null result. This is how
|
|
@@ -48,14 +47,17 @@ const createSessionContextRef = (initial = null, recover) => {
|
|
|
48
47
|
};
|
|
49
48
|
};
|
|
50
49
|
/**
|
|
51
|
-
* Resolve the boot-time SessionContext from
|
|
52
|
-
* primary path: SessionStart wrote the exports to `CLAUDE_ENV_FILE`
|
|
53
|
-
*
|
|
50
|
+
* Resolve the boot-time SessionContext from an environment map (the
|
|
51
|
+
* primary path: SessionStart wrote the exports to `CLAUDE_ENV_FILE` and
|
|
52
|
+
* Claude Code auto-sources that file into the MCP server child).
|
|
54
53
|
*
|
|
55
|
-
* Returns `null` when any required value is absent — callers can
|
|
56
|
-
*
|
|
54
|
+
* Returns `null` when any required value is absent — callers can still
|
|
55
|
+
* attempt the session-map fallback before giving up.
|
|
56
|
+
*
|
|
57
|
+
* @param env - the environment map to read; the bin passes `process.env`
|
|
58
|
+
* @public
|
|
57
59
|
*/
|
|
58
|
-
const sessionContextFromEnv = (env
|
|
60
|
+
const sessionContextFromEnv = (env) => {
|
|
59
61
|
const chatId = env.VITEST_AGENT_CHAT_ID;
|
|
60
62
|
const conversationId = env.VITEST_AGENT_CONVERSATION_ID;
|
|
61
63
|
const mainAgentId = env.VITEST_AGENT_MAIN_AGENT_ID ?? env.VITEST_AGENT_AGENT_ID;
|
|
@@ -67,20 +69,34 @@ const sessionContextFromEnv = (env = process.env) => {
|
|
|
67
69
|
mainAgentId
|
|
68
70
|
};
|
|
69
71
|
};
|
|
70
|
-
const t = initTRPC.context().create();
|
|
71
|
-
const router = t.router;
|
|
72
|
-
const publicProcedure = t.procedure;
|
|
73
72
|
/**
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
* Use with {@link appRouter} in tests or programmatic contexts to invoke
|
|
77
|
-
* tool procedures without starting the MCP server.
|
|
73
|
+
* The per-process MCP session service consumed by tool handlers.
|
|
78
74
|
*
|
|
79
75
|
* @public
|
|
80
76
|
*/
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
|
|
77
|
+
var McpSession = class McpSession extends Context.Service()("@vitest-agent/mcp/McpSession") {
|
|
78
|
+
/**
|
|
79
|
+
* The live session: seeded from the bin's boot-time recovery, healing
|
|
80
|
+
* lazily through `recover` on the first `sessionContext.get()` that
|
|
81
|
+
* finds a `null` context.
|
|
82
|
+
*/
|
|
83
|
+
static layer = (options) => Layer.succeed(McpSession, {
|
|
84
|
+
cwd: options.cwd,
|
|
85
|
+
currentSessionId: createCurrentSessionIdRef(options.initialSessionId),
|
|
86
|
+
sessionContext: createSessionContextRef(options.initialContext, options.recover)
|
|
87
|
+
});
|
|
88
|
+
/**
|
|
89
|
+
* A test session: `cwd` is the caller's (this module is process-free,
|
|
90
|
+
* so the test harness passes `process.cwd()` itself), both refs start
|
|
91
|
+
* `null`, and either can be overridden.
|
|
92
|
+
*/
|
|
93
|
+
static layerTest = (overrides) => McpSession.layer({
|
|
94
|
+
cwd: overrides.cwd,
|
|
95
|
+
initialSessionId: overrides.initialSessionId ?? null,
|
|
96
|
+
initialContext: overrides.initialContext ?? null,
|
|
97
|
+
...overrides.recover === void 0 ? {} : { recover: overrides.recover }
|
|
98
|
+
});
|
|
99
|
+
};
|
|
84
100
|
|
|
85
101
|
//#endregion
|
|
86
|
-
export {
|
|
102
|
+
export { McpSession, createCurrentSessionIdRef, createSessionContextRef, sessionContextFromEnv };
|
package/toolkit.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { withIdempotency } from "./idempotency.js";
|
|
2
|
+
import { acceptanceMetricsTool, handleAcceptanceMetrics } from "./tools/acceptance-metrics.js";
|
|
3
|
+
import { cacheHealthTool, handleCacheHealth } from "./tools/cache-health.js";
|
|
4
|
+
import { commitChangesTool, handleCommitChanges } from "./tools/commit-changes.js";
|
|
5
|
+
import { configureTool, handleConfigure } from "./tools/configure.js";
|
|
6
|
+
import { handleTestCoverage, testCoverageTool } from "./tools/coverage.js";
|
|
7
|
+
import { handleTestErrors, testErrorsTool } from "./tools/errors.js";
|
|
8
|
+
import { failureSignatureGetTool, handleFailureSignatureGet } from "./tools/failure-signature-get.js";
|
|
9
|
+
import { fileCoverageTool, handleFileCoverage } from "./tools/file-coverage.js";
|
|
10
|
+
import { handleHelp, helpTool } from "./tools/help.js";
|
|
11
|
+
import { handleTestHistory, testHistoryTool } from "./tools/history.js";
|
|
12
|
+
import { handleHypothesis, hypothesisTool } from "./tools/hypothesis.js";
|
|
13
|
+
import { handleInventory, inventoryTool } from "./tools/inventory.js";
|
|
14
|
+
import { handleNote, noteTool } from "./tools/note.js";
|
|
15
|
+
import { handleTestOverview, testOverviewTool } from "./tools/overview.js";
|
|
16
|
+
import { handlePing, pingTool } from "./tools/ping.js";
|
|
17
|
+
import { handleRegisterAgent, registerAgentTool } from "./tools/register-agent.js";
|
|
18
|
+
import { handleRunTests, runTestsTool } from "./tools/run-tests.js";
|
|
19
|
+
import { handleSettingsList, settingsListTool } from "./tools/settings-list.js";
|
|
20
|
+
import { handleTestStatus, testStatusTool } from "./tools/status.js";
|
|
21
|
+
import { handleTddArtifactList, tddArtifactListTool } from "./tools/tdd-artifact.js";
|
|
22
|
+
import { handleTddBehavior, tddBehaviorTool } from "./tools/tdd-behavior.js";
|
|
23
|
+
import { handleTddGoal, tddGoalTool } from "./tools/tdd-goal.js";
|
|
24
|
+
import { handlePhaseTransitionRequest, tddPhaseTransitionRequestTool } from "./tools/tdd-phase-transition-request.js";
|
|
25
|
+
import { handleTddProgressPush, tddProgressPushTool } from "./tools/tdd-progress-push.js";
|
|
26
|
+
import { handleTddTask, tddTaskTool } from "./tools/tdd-task.js";
|
|
27
|
+
import { handleTest, testTool } from "./tools/test.js";
|
|
28
|
+
import { handleTestTrends, testTrendsTool } from "./tools/trends.js";
|
|
29
|
+
import { handleTriageBrief, triageBriefTool } from "./tools/triage-brief.js";
|
|
30
|
+
import { handleTurnSearch, turnSearchTool } from "./tools/turn-search.js";
|
|
31
|
+
import { handleWrapupPrompt, wrapupPromptTool } from "./tools/wrapup-prompt.js";
|
|
32
|
+
import { Toolkit } from "effect/unstable/ai";
|
|
33
|
+
|
|
34
|
+
//#region src/toolkit.ts
|
|
35
|
+
/**
|
|
36
|
+
* Every tool the server registers.
|
|
37
|
+
*
|
|
38
|
+
* @public
|
|
39
|
+
*/
|
|
40
|
+
const Kit = Toolkit.make(pingTool, helpTool, testStatusTool, testOverviewTool, testCoverageTool, testHistoryTool, testTrendsTool, testErrorsTool, fileCoverageTool, settingsListTool, cacheHealthTool, configureTool, commitChangesTool, turnSearchTool, failureSignatureGetTool, acceptanceMetricsTool, triageBriefTool, wrapupPromptTool, inventoryTool, testTool, registerAgentTool, noteTool, hypothesisTool, tddTaskTool, tddPhaseTransitionRequestTool, tddGoalTool, tddBehaviorTool, tddArtifactListTool, tddProgressPushTool, runTestsTool);
|
|
41
|
+
/**
|
|
42
|
+
* The handler for each tool in {@link Kit}, keyed by tool name.
|
|
43
|
+
*
|
|
44
|
+
* @public
|
|
45
|
+
*/
|
|
46
|
+
const toolHandlers = {
|
|
47
|
+
ping: handlePing,
|
|
48
|
+
help: handleHelp,
|
|
49
|
+
test_status: handleTestStatus,
|
|
50
|
+
test_overview: handleTestOverview,
|
|
51
|
+
test_coverage: handleTestCoverage,
|
|
52
|
+
test_history: handleTestHistory,
|
|
53
|
+
test_trends: handleTestTrends,
|
|
54
|
+
test_errors: handleTestErrors,
|
|
55
|
+
file_coverage: handleFileCoverage,
|
|
56
|
+
settings_list: handleSettingsList,
|
|
57
|
+
cache_health: handleCacheHealth,
|
|
58
|
+
configure: handleConfigure,
|
|
59
|
+
commit_changes: handleCommitChanges,
|
|
60
|
+
turn_search: handleTurnSearch,
|
|
61
|
+
failure_signature_get: handleFailureSignatureGet,
|
|
62
|
+
acceptance_metrics: handleAcceptanceMetrics,
|
|
63
|
+
triage_brief: handleTriageBrief,
|
|
64
|
+
wrapup_prompt: handleWrapupPrompt,
|
|
65
|
+
inventory: handleInventory,
|
|
66
|
+
test: handleTest,
|
|
67
|
+
register_agent: handleRegisterAgent,
|
|
68
|
+
note: handleNote,
|
|
69
|
+
hypothesis: withIdempotency("hypothesis", handleHypothesis),
|
|
70
|
+
tdd_task: withIdempotency("tdd_task", handleTddTask),
|
|
71
|
+
tdd_phase_transition_request: handlePhaseTransitionRequest,
|
|
72
|
+
tdd_goal: withIdempotency("tdd_goal", handleTddGoal),
|
|
73
|
+
tdd_behavior: withIdempotency("tdd_behavior", handleTddBehavior),
|
|
74
|
+
tdd_artifact_list: handleTddArtifactList,
|
|
75
|
+
tdd_progress_push: handleTddProgressPush,
|
|
76
|
+
run_tests: handleRunTests
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* The handlers layer: provides `Tool.HandlersFor<typeof Kit.tools>`.
|
|
80
|
+
*
|
|
81
|
+
* @public
|
|
82
|
+
*/
|
|
83
|
+
const ToolsLayer = Kit.toLayer(toolHandlers);
|
|
84
|
+
|
|
85
|
+
//#endregion
|
|
86
|
+
export { Kit, ToolsLayer, toolHandlers };
|
|
@@ -1,20 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { RenderText } from "../annotations.js";
|
|
2
2
|
import { Effect, Schema, SchemaGetter } from "effect";
|
|
3
|
-
import { DataReader } from "@vitest-agent/
|
|
3
|
+
import { DataReader } from "@vitest-agent/engine";
|
|
4
|
+
import { Tool } from "effect/unstable/ai";
|
|
4
5
|
|
|
5
6
|
//#region src/tools/acceptance-metrics.ts
|
|
7
|
+
const totalAnnotation = { description: "Sample size — number of observations the metric ratio is computed over." };
|
|
8
|
+
const ratioAnnotation = { description: "Compliance ratio in [0, 1]. Multiply by 100 for the percentage form rendered in the markdown view." };
|
|
6
9
|
/**
|
|
7
|
-
* `acceptance_metrics`
|
|
8
|
-
*
|
|
9
|
-
* Mirrors `DataReader.AcceptanceMetrics` as an Effect Schema so the
|
|
10
|
-
* structured payload the agent receives, the markdown rendering on
|
|
11
|
-
* the text channel, and the SDK-side `outputSchema` all derive from
|
|
12
|
-
* one canonical contract.
|
|
10
|
+
* The `acceptance_metrics` tool's success payload.
|
|
13
11
|
*
|
|
14
|
-
* @
|
|
12
|
+
* @public
|
|
15
13
|
*/
|
|
16
|
-
const totalAnnotation = { description: "Sample size — number of observations the metric ratio is computed over." };
|
|
17
|
-
const ratioAnnotation = { description: "Compliance ratio in [0, 1]. Multiply by 100 for the percentage form rendered in the markdown view." };
|
|
18
14
|
const AcceptanceMetricsResult = Schema.Struct({
|
|
19
15
|
phaseEvidenceIntegrity: Schema.Struct({
|
|
20
16
|
total: Schema.Finite.annotate(totalAnnotation),
|
|
@@ -66,9 +62,25 @@ const AcceptanceMetricsAsMarkdown = AcceptanceMetricsResult.pipe(Schema.decodeTo
|
|
|
66
62
|
decode: SchemaGetter.transform((data) => formatAcceptanceMetricsMarkdown(data)),
|
|
67
63
|
encode: SchemaGetter.forbidden(() => "AcceptanceMetricsAsMarkdown is one-way: markdown cannot be parsed back to AcceptanceMetricsResult.")
|
|
68
64
|
}));
|
|
69
|
-
|
|
65
|
+
/**
|
|
66
|
+
* Handler for {@link acceptanceMetricsTool}.
|
|
67
|
+
*
|
|
68
|
+
* @public
|
|
69
|
+
*/
|
|
70
|
+
const handleAcceptanceMetrics = () => Effect.gen(function* () {
|
|
70
71
|
return yield* (yield* DataReader).computeAcceptanceMetrics();
|
|
71
|
-
}))
|
|
72
|
+
}).pipe(Effect.orDie);
|
|
73
|
+
/**
|
|
74
|
+
* The Effect-native `acceptance_metrics` tool. No parameters (the default
|
|
75
|
+
* `Tool.EmptyParams` serves as a strict empty object).
|
|
76
|
+
*
|
|
77
|
+
* @public
|
|
78
|
+
*/
|
|
79
|
+
const acceptanceMetricsTool = Tool.make("acceptance_metrics", {
|
|
80
|
+
description: "Use when you need the four spec Annex A acceptance metrics computed from the current database. Returns markdown in content[] and a typed JSON object in structuredContent (per-metric { total, ratio, ... }).",
|
|
81
|
+
success: AcceptanceMetricsResult,
|
|
82
|
+
dependencies: [DataReader]
|
|
83
|
+
}).annotate(Tool.Title, "Acceptance metrics").annotate(Tool.Readonly, true).annotate(Tool.Destructive, false).annotate(Tool.OpenWorld, false).annotate(Tool.Idempotent, true).annotate(RenderText, (encoded) => formatAcceptanceMetricsMarkdown(encoded));
|
|
72
84
|
|
|
73
85
|
//#endregion
|
|
74
|
-
export {
|
|
86
|
+
export { AcceptanceMetricsResult, acceptanceMetricsTool, formatAcceptanceMetricsMarkdown, handleAcceptanceMetrics };
|
package/tools/cache-health.js
CHANGED
|
@@ -1,20 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { RenderText } from "../annotations.js";
|
|
2
2
|
import { Effect, Option, Schema, SchemaGetter } from "effect";
|
|
3
|
-
import {
|
|
3
|
+
import { DataReader } from "@vitest-agent/engine";
|
|
4
|
+
import { Tool } from "effect/unstable/ai";
|
|
5
|
+
import { CacheManifest } from "@vitest-agent/sdk";
|
|
4
6
|
|
|
5
7
|
//#region src/tools/cache-health.ts
|
|
6
|
-
/**
|
|
7
|
-
* `cache_health` MCP tool — Schema-driven implementation.
|
|
8
|
-
*
|
|
9
|
-
* Wraps the cache manifest in a `CacheHealthResult` Schema that
|
|
10
|
-
* captures both the present and absent cases. The text channel
|
|
11
|
-
* renders the same markdown the previous implementation produced;
|
|
12
|
-
* the structured payload now exposes `manifestPresent` plus the
|
|
13
|
-
* computed `ageMs` so agents can branch on freshness without parsing
|
|
14
|
-
* prose.
|
|
15
|
-
*
|
|
16
|
-
* @packageDocumentation
|
|
17
|
-
*/
|
|
18
8
|
const ManifestPresent = Schema.Struct({
|
|
19
9
|
manifestPresent: Schema.Literal(true).annotate({ description: "Discriminant — `true` when a cache manifest exists." }),
|
|
20
10
|
manifest: CacheManifest.annotate({ description: "Full cache manifest content as written by the reporter." }),
|
|
@@ -28,6 +18,11 @@ const ManifestAbsent = Schema.Struct({ manifestPresent: Schema.Literal(false).an
|
|
|
28
18
|
identifier: "CacheHealthAbsent",
|
|
29
19
|
title: "Cache manifest absent"
|
|
30
20
|
});
|
|
21
|
+
/**
|
|
22
|
+
* The `cache_health` tool's success payload.
|
|
23
|
+
*
|
|
24
|
+
* @public
|
|
25
|
+
*/
|
|
31
26
|
const CacheHealthResult = Schema.Union([ManifestPresent, ManifestAbsent]).annotate({
|
|
32
27
|
identifier: "CacheHealthResult",
|
|
33
28
|
title: "cache_health result",
|
|
@@ -65,7 +60,12 @@ const CacheHealthAsMarkdown = CacheHealthResult.pipe(Schema.decodeTo(Schema.Stri
|
|
|
65
60
|
decode: SchemaGetter.transform((data) => formatCacheHealthMarkdown(data)),
|
|
66
61
|
encode: SchemaGetter.forbidden(() => "CacheHealthAsMarkdown is one-way: markdown cannot be parsed back to CacheHealthResult.")
|
|
67
62
|
}));
|
|
68
|
-
|
|
63
|
+
/**
|
|
64
|
+
* Handler for {@link cacheHealthTool}.
|
|
65
|
+
*
|
|
66
|
+
* @public
|
|
67
|
+
*/
|
|
68
|
+
const handleCacheHealth = () => Effect.gen(function* () {
|
|
69
69
|
const manifestOpt = yield* (yield* DataReader).getManifest();
|
|
70
70
|
if (Option.isNone(manifestOpt)) return { manifestPresent: false };
|
|
71
71
|
const manifest = manifestOpt.value;
|
|
@@ -76,7 +76,18 @@ const cacheHealth = publicProcedure.query(async ({ ctx }) => ctx.runtime.runProm
|
|
|
76
76
|
ageMs,
|
|
77
77
|
stale: ageMs > STALE_AFTER_MS
|
|
78
78
|
};
|
|
79
|
-
}))
|
|
79
|
+
}).pipe(Effect.orDie);
|
|
80
|
+
/**
|
|
81
|
+
* The Effect-native `cache_health` tool. No parameters (the default
|
|
82
|
+
* `Tool.EmptyParams` serves as a strict empty object).
|
|
83
|
+
*
|
|
84
|
+
* @public
|
|
85
|
+
*/
|
|
86
|
+
const cacheHealthTool = Tool.make("cache_health", {
|
|
87
|
+
description: "Use when you suspect stale data and need manifest presence, project states, and staleness. Returns markdown in content[] and a typed JSON object in structuredContent ({ manifestPresent, manifest?, ageMs?, stale? }).",
|
|
88
|
+
success: CacheHealthResult,
|
|
89
|
+
dependencies: [DataReader]
|
|
90
|
+
}).annotate(Tool.Title, "Cache health").annotate(Tool.Readonly, true).annotate(Tool.Destructive, false).annotate(Tool.OpenWorld, false).annotate(Tool.Idempotent, true).annotate(RenderText, (encoded) => formatCacheHealthMarkdown(encoded));
|
|
80
91
|
|
|
81
92
|
//#endregion
|
|
82
|
-
export {
|
|
93
|
+
export { CacheHealthResult, cacheHealthTool, formatCacheHealthMarkdown, handleCacheHealth };
|
package/tools/commit-changes.js
CHANGED
|
@@ -1,13 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { RenderText } from "../annotations.js";
|
|
2
2
|
import { Effect, Schema, SchemaGetter } from "effect";
|
|
3
|
-
import { DataReader } from "@vitest-agent/
|
|
3
|
+
import { DataReader } from "@vitest-agent/engine";
|
|
4
|
+
import { Tool } from "effect/unstable/ai";
|
|
4
5
|
|
|
5
6
|
//#region src/tools/commit-changes.ts
|
|
6
|
-
/**
|
|
7
|
-
* `commit_changes` MCP tool — Schema-driven implementation.
|
|
8
|
-
*
|
|
9
|
-
* @packageDocumentation
|
|
10
|
-
*/
|
|
11
7
|
const FileRow = Schema.Struct({
|
|
12
8
|
filePath: Schema.String.annotate({ description: "Repo-relative path of the changed file." }),
|
|
13
9
|
changeKind: Schema.Literals([
|
|
@@ -27,6 +23,11 @@ const CommitRow = Schema.Struct({
|
|
|
27
23
|
branch: Schema.NullOr(Schema.String).annotate({ description: "Branch the commit was recorded on at hook fire time." }),
|
|
28
24
|
files: Schema.Array(FileRow).annotate({ description: "Files this commit changed, with per-file change kinds." })
|
|
29
25
|
}).annotate({ identifier: "CommitRow" });
|
|
26
|
+
/**
|
|
27
|
+
* The `commit_changes` tool's success payload.
|
|
28
|
+
*
|
|
29
|
+
* @public
|
|
30
|
+
*/
|
|
30
31
|
const CommitChangesResult = Schema.Struct({
|
|
31
32
|
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
33
|
count: Schema.Finite.annotate({ description: "Number of commit rows returned." }),
|
|
@@ -56,14 +57,36 @@ const CommitChangesAsMarkdown = CommitChangesResult.pipe(Schema.decodeTo(Schema.
|
|
|
56
57
|
decode: SchemaGetter.transform((data) => formatCommitChangesMarkdown(data)),
|
|
57
58
|
encode: SchemaGetter.forbidden(() => "CommitChangesAsMarkdown is one-way: markdown cannot be parsed back to CommitChangesResult.")
|
|
58
59
|
}));
|
|
59
|
-
|
|
60
|
+
/**
|
|
61
|
+
* The `commit_changes` tool's parameters.
|
|
62
|
+
*
|
|
63
|
+
* @public
|
|
64
|
+
*/
|
|
65
|
+
const CommitChangesInput = Schema.Struct({ sha: Schema.optionalKey(Schema.String).annotate({ description: "Specific commit sha to fetch; omit for recent commits" }) });
|
|
66
|
+
/**
|
|
67
|
+
* Handler for {@link commitChangesTool}.
|
|
68
|
+
*
|
|
69
|
+
* @public
|
|
70
|
+
*/
|
|
71
|
+
const handleCommitChanges = (input) => Effect.gen(function* () {
|
|
60
72
|
const entries = yield* (yield* DataReader).getCommitChanges(input.sha);
|
|
61
73
|
return {
|
|
62
74
|
...input.sha !== void 0 && { filterSha: input.sha },
|
|
63
75
|
count: entries.length,
|
|
64
76
|
commits: entries
|
|
65
77
|
};
|
|
66
|
-
}))
|
|
78
|
+
}).pipe(Effect.orDie);
|
|
79
|
+
/**
|
|
80
|
+
* The Effect-native `commit_changes` tool.
|
|
81
|
+
*
|
|
82
|
+
* @public
|
|
83
|
+
*/
|
|
84
|
+
const commitChangesTool = Tool.make("commit_changes", {
|
|
85
|
+
description: "Use when you need commit metadata and changed files captured by the post-commit hook. Returns up to 20 most-recent when sha is omitted. Returns markdown in content[] and a typed JSON object in structuredContent ({ filterSha?, count, commits[] }).",
|
|
86
|
+
parameters: CommitChangesInput,
|
|
87
|
+
success: CommitChangesResult,
|
|
88
|
+
dependencies: [DataReader]
|
|
89
|
+
}).annotate(Tool.Title, "Commit changes").annotate(Tool.Readonly, true).annotate(Tool.Destructive, false).annotate(Tool.OpenWorld, false).annotate(Tool.Idempotent, true).annotate(RenderText, (encoded) => formatCommitChangesMarkdown(encoded));
|
|
67
90
|
|
|
68
91
|
//#endregion
|
|
69
|
-
export {
|
|
92
|
+
export { CommitChangesInput, CommitChangesResult, commitChangesTool, formatCommitChangesMarkdown, handleCommitChanges };
|
package/tools/configure.js
CHANGED
|
@@ -1,13 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { RenderText } from "../annotations.js";
|
|
2
2
|
import { Effect, Option, Schema, SchemaGetter } from "effect";
|
|
3
|
-
import { DataReader } from "@vitest-agent/
|
|
3
|
+
import { DataReader } from "@vitest-agent/engine";
|
|
4
|
+
import { Tool } from "effect/unstable/ai";
|
|
4
5
|
|
|
5
6
|
//#region src/tools/configure.ts
|
|
6
|
-
/**
|
|
7
|
-
* `configure` MCP tool — Schema-driven implementation.
|
|
8
|
-
*
|
|
9
|
-
* @packageDocumentation
|
|
10
|
-
*/
|
|
11
7
|
const SettingsRowSchema = Schema.Struct({
|
|
12
8
|
hash: Schema.String.annotate({ description: "Stable SHA-1 of the captured Vitest settings; `test_runs.settings_hash` foreign key." }),
|
|
13
9
|
reporters: Schema.NullOr(Schema.String).annotate({ description: "Comma-separated reporter list as resolved from the user's vitest config." }),
|
|
@@ -35,6 +31,11 @@ const SettingsAbsent = Schema.Struct({
|
|
|
35
31
|
source: Schema.Literals(["requested", "latest"]),
|
|
36
32
|
requestedHash: Schema.optional(Schema.String).annotate({ description: "Echo of the hash the caller passed; absent when the empty `latest` lookup found nothing." })
|
|
37
33
|
}).annotate({ identifier: "ConfigureAbsent" });
|
|
34
|
+
/**
|
|
35
|
+
* The `configure` tool's success payload.
|
|
36
|
+
*
|
|
37
|
+
* @public
|
|
38
|
+
*/
|
|
38
39
|
const ConfigureResult = Schema.Union([SettingsFound, SettingsAbsent]).annotate({
|
|
39
40
|
identifier: "ConfigureResult",
|
|
40
41
|
title: "configure result",
|
|
@@ -74,7 +75,18 @@ const ConfigureAsMarkdown = ConfigureResult.pipe(Schema.decodeTo(Schema.String,
|
|
|
74
75
|
decode: SchemaGetter.transform((data) => formatConfigureMarkdown(data)),
|
|
75
76
|
encode: SchemaGetter.forbidden(() => "ConfigureAsMarkdown is one-way: markdown cannot be parsed back to ConfigureResult.")
|
|
76
77
|
}));
|
|
77
|
-
|
|
78
|
+
/**
|
|
79
|
+
* The `configure` tool's parameters.
|
|
80
|
+
*
|
|
81
|
+
* @public
|
|
82
|
+
*/
|
|
83
|
+
const ConfigureInput = Schema.Struct({ settingsHash: Schema.optionalKey(Schema.String).annotate({ description: "Settings hash from a manifest entry or test run" }) });
|
|
84
|
+
/**
|
|
85
|
+
* Handler for {@link configureTool}.
|
|
86
|
+
*
|
|
87
|
+
* @public
|
|
88
|
+
*/
|
|
89
|
+
const handleConfigure = (input) => Effect.gen(function* () {
|
|
78
90
|
const reader = yield* DataReader;
|
|
79
91
|
if (input.settingsHash === void 0) {
|
|
80
92
|
const latestOpt = yield* reader.getLatestSettings();
|
|
@@ -97,7 +109,18 @@ const configure = publicProcedure.input(Schema.toStandardSchemaV1(Schema.Struct(
|
|
|
97
109
|
source: "requested",
|
|
98
110
|
settings: settingsOpt.value
|
|
99
111
|
};
|
|
100
|
-
}))
|
|
112
|
+
}).pipe(Effect.orDie);
|
|
113
|
+
/**
|
|
114
|
+
* The Effect-native `configure` tool.
|
|
115
|
+
*
|
|
116
|
+
* @public
|
|
117
|
+
*/
|
|
118
|
+
const configureTool = Tool.make("configure", {
|
|
119
|
+
description: "Use when you need the captured Vitest settings for a test run. Returns markdown in content[] and a typed JSON object in structuredContent ({ found, source, settings?, requestedHash? }).",
|
|
120
|
+
parameters: ConfigureInput,
|
|
121
|
+
success: ConfigureResult,
|
|
122
|
+
dependencies: [DataReader]
|
|
123
|
+
}).annotate(Tool.Title, "Configure").annotate(Tool.Readonly, true).annotate(Tool.Destructive, false).annotate(Tool.OpenWorld, false).annotate(Tool.Idempotent, true).annotate(RenderText, (encoded) => formatConfigureMarkdown(encoded));
|
|
101
124
|
|
|
102
125
|
//#endregion
|
|
103
|
-
export {
|
|
126
|
+
export { ConfigureInput, ConfigureResult, configureTool, formatConfigureMarkdown, handleConfigure };
|
package/tools/coverage.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { RenderText } from "../annotations.js";
|
|
2
2
|
import { Effect, Option, Schema, SchemaGetter } from "effect";
|
|
3
|
-
import {
|
|
3
|
+
import { DataReader } from "@vitest-agent/engine";
|
|
4
|
+
import { Tool } from "effect/unstable/ai";
|
|
5
|
+
import { CoverageReport } from "@vitest-agent/sdk";
|
|
4
6
|
|
|
5
7
|
//#region src/tools/coverage.ts
|
|
6
8
|
const CoverageAvailable = Schema.Struct({
|
|
@@ -12,6 +14,11 @@ const CoverageAbsent = Schema.Struct({
|
|
|
12
14
|
dataAvailable: Schema.Literal(false),
|
|
13
15
|
project: Schema.String
|
|
14
16
|
}).annotate({ identifier: "TestCoverageAbsent" });
|
|
17
|
+
/**
|
|
18
|
+
* The `test_coverage` tool's success payload.
|
|
19
|
+
*
|
|
20
|
+
* @public
|
|
21
|
+
*/
|
|
15
22
|
const TestCoverageResult = Schema.Union([CoverageAvailable, CoverageAbsent]).annotate({
|
|
16
23
|
identifier: "TestCoverageResult",
|
|
17
24
|
title: "test_coverage result",
|
|
@@ -66,7 +73,18 @@ const TestCoverageAsMarkdown = TestCoverageResult.pipe(Schema.decodeTo(Schema.St
|
|
|
66
73
|
decode: SchemaGetter.transform((data) => formatTestCoverageMarkdown(data)),
|
|
67
74
|
encode: SchemaGetter.forbidden(() => "TestCoverageAsMarkdown is one-way.")
|
|
68
75
|
}));
|
|
69
|
-
|
|
76
|
+
/**
|
|
77
|
+
* The `test_coverage` tool's parameters.
|
|
78
|
+
*
|
|
79
|
+
* @public
|
|
80
|
+
*/
|
|
81
|
+
const TestCoverageInput = Schema.Struct({ project: Schema.optionalKey(Schema.String).annotate({ description: "Project name" }) });
|
|
82
|
+
/**
|
|
83
|
+
* Handler for {@link testCoverageTool}.
|
|
84
|
+
*
|
|
85
|
+
* @public
|
|
86
|
+
*/
|
|
87
|
+
const handleTestCoverage = (input) => Effect.gen(function* () {
|
|
70
88
|
const reader = yield* DataReader;
|
|
71
89
|
const project = input.project ?? "default";
|
|
72
90
|
const coverageOpt = yield* reader.getCoverage(project);
|
|
@@ -79,7 +97,18 @@ const testCoverage = publicProcedure.input(Schema.toStandardSchemaV1(Schema.Stru
|
|
|
79
97
|
project,
|
|
80
98
|
coverage: coverageOpt.value
|
|
81
99
|
};
|
|
82
|
-
}))
|
|
100
|
+
}).pipe(Effect.orDie);
|
|
101
|
+
/**
|
|
102
|
+
* The Effect-native `test_coverage` tool.
|
|
103
|
+
*
|
|
104
|
+
* @public
|
|
105
|
+
*/
|
|
106
|
+
const testCoverageTool = Tool.make("test_coverage", {
|
|
107
|
+
description: "Use when coverage drops and you need per-metric gap analysis against thresholds and targets. Returns markdown in content[] and a typed JSON object in structuredContent ({ dataAvailable, project, coverage } or absent variant).",
|
|
108
|
+
parameters: TestCoverageInput,
|
|
109
|
+
success: TestCoverageResult,
|
|
110
|
+
dependencies: [DataReader]
|
|
111
|
+
}).annotate(Tool.Title, "Test coverage").annotate(Tool.Readonly, true).annotate(Tool.Destructive, false).annotate(Tool.OpenWorld, false).annotate(Tool.Idempotent, true).annotate(RenderText, (encoded) => formatTestCoverageMarkdown(encoded));
|
|
83
112
|
|
|
84
113
|
//#endregion
|
|
85
|
-
export {
|
|
114
|
+
export { TestCoverageInput, TestCoverageResult, formatTestCoverageMarkdown, handleTestCoverage, testCoverageTool };
|
package/tools/errors.js
CHANGED
|
@@ -1,25 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { RenderText } from "../annotations.js";
|
|
2
2
|
import { Effect, Schema, SchemaGetter } from "effect";
|
|
3
|
-
import { DataReader } from "@vitest-agent/
|
|
3
|
+
import { DataReader } from "@vitest-agent/engine";
|
|
4
|
+
import { Tool } from "effect/unstable/ai";
|
|
4
5
|
|
|
5
6
|
//#region src/tools/errors.ts
|
|
6
|
-
/**
|
|
7
|
-
* `test_errors` MCP tool — Schema-driven implementation.
|
|
8
|
-
*
|
|
9
|
-
* The Effect Schema `TestErrorsResult` is the canonical contract for
|
|
10
|
-
* the tool's output. The same Schema:
|
|
11
|
-
* - types the procedure's return value;
|
|
12
|
-
* - drives `formatTestErrorsMarkdown` (input typed via `Schema.Type`);
|
|
13
|
-
* - composes into `TestErrorsAsMarkdown`, a one-way
|
|
14
|
-
* `Schema.decodeTo` whose `decode` direction renders the
|
|
15
|
-
* markdown the text channel carries (encode is forbidden because
|
|
16
|
-
* markdown rendering is lossy);
|
|
17
|
-
* - bridges to zod via `effectToZodSchema` for the SDK's
|
|
18
|
-
* `outputSchema` field, so the structured shape we declare to MCP
|
|
19
|
-
* stays in lockstep with what the procedure actually emits.
|
|
20
|
-
*
|
|
21
|
-
* @packageDocumentation
|
|
22
|
-
*/
|
|
23
7
|
/** One annotation attached to a failing test, surfaced with its error. */
|
|
24
8
|
const TestErrorAnnotation = Schema.Struct({
|
|
25
9
|
type: Schema.String.annotate({ description: "Annotation type as the test author wrote it — an arbitrary string, not an enum." }),
|
|
@@ -60,7 +44,11 @@ const TestErrorRow = Schema.Struct({
|
|
|
60
44
|
title: "Test error row",
|
|
61
45
|
description: "Single error captured during a test run, joined with stack frame and source-location context."
|
|
62
46
|
});
|
|
63
|
-
/**
|
|
47
|
+
/**
|
|
48
|
+
* The `test_errors` tool's success payload — populates `structuredContent`.
|
|
49
|
+
*
|
|
50
|
+
* @public
|
|
51
|
+
*/
|
|
64
52
|
const TestErrorsResult = Schema.Struct({
|
|
65
53
|
project: Schema.String.annotate({
|
|
66
54
|
title: "Project name",
|
|
@@ -153,10 +141,21 @@ const TestErrorsAsMarkdown = TestErrorsResult.pipe(Schema.decodeTo(Schema.String
|
|
|
153
141
|
decode: SchemaGetter.transform((data) => formatTestErrorsMarkdown(data)),
|
|
154
142
|
encode: SchemaGetter.forbidden(() => "TestErrorsAsMarkdown is one-way: markdown cannot be parsed back to TestErrorsResult. Consume the procedure's structured output (or MCP structuredContent) directly.")
|
|
155
143
|
}));
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
144
|
+
/**
|
|
145
|
+
* The `test_errors` tool's parameters.
|
|
146
|
+
*
|
|
147
|
+
* @public
|
|
148
|
+
*/
|
|
149
|
+
const TestErrorsInput = Schema.Struct({
|
|
150
|
+
project: Schema.String.annotate({ description: "Project name (required)" }),
|
|
151
|
+
errorName: Schema.optionalKey(Schema.String).annotate({ description: "Filter to a specific error name" })
|
|
152
|
+
});
|
|
153
|
+
/**
|
|
154
|
+
* Handler for {@link testErrorsTool}.
|
|
155
|
+
*
|
|
156
|
+
* @public
|
|
157
|
+
*/
|
|
158
|
+
const handleTestErrors = (input) => Effect.gen(function* () {
|
|
160
159
|
const reader = yield* DataReader;
|
|
161
160
|
const errors = yield* reader.getErrors(input.project, input.errorName);
|
|
162
161
|
const cache = /* @__PURE__ */ new Map();
|
|
@@ -190,7 +189,18 @@ const testErrors = publicProcedure.input(Schema.toStandardSchemaV1(Schema.Struct
|
|
|
190
189
|
count: rows.length,
|
|
191
190
|
errors: rows
|
|
192
191
|
};
|
|
193
|
-
}))
|
|
192
|
+
}).pipe(Effect.orDie);
|
|
193
|
+
/**
|
|
194
|
+
* The Effect-native `test_errors` tool.
|
|
195
|
+
*
|
|
196
|
+
* @public
|
|
197
|
+
*/
|
|
198
|
+
const testErrorsTool = Tool.make("test_errors", {
|
|
199
|
+
description: "Use when a test fails and you need error detail, diffs, and the cite-able test_errors.id / stack_frames.id values needed by hypothesis (action: record). Returns both a markdown rendering (in content[].text) and a typed JSON object (in structuredContent) — agents should prefer structuredContent.errors[].",
|
|
200
|
+
parameters: TestErrorsInput,
|
|
201
|
+
success: TestErrorsResult,
|
|
202
|
+
dependencies: [DataReader]
|
|
203
|
+
}).annotate(Tool.Title, "Test errors").annotate(Tool.Readonly, true).annotate(Tool.Destructive, false).annotate(Tool.OpenWorld, false).annotate(Tool.Idempotent, true).annotate(RenderText, (encoded) => formatTestErrorsMarkdown(encoded));
|
|
194
204
|
|
|
195
205
|
//#endregion
|
|
196
|
-
export { TestErrorAnnotation, TestErrorRow,
|
|
206
|
+
export { TestErrorAnnotation, TestErrorRow, TestErrorsInput, TestErrorsResult, formatTestErrorsMarkdown, handleTestErrors, testErrorsTool };
|