@vitest-agent/mcp 5.4.0 → 6.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/index.d.ts +707 -363
- package/index.js +1 -2
- package/main.js +8 -44
- package/package.json +11 -10
- package/replay.js +93 -0
- package/server.js +1 -1
- package/toolkit.js +25 -9
- package/tools/_tdd-error-envelope.js +119 -70
- package/tools/acceptance-metrics.js +1 -1
- package/tools/cache-health.js +1 -1
- package/tools/commit-changes.js +1 -1
- package/tools/configure.js +1 -1
- package/tools/coverage.js +1 -1
- package/tools/errors.js +1 -1
- package/tools/failure-signature-get.js +1 -1
- package/tools/file-coverage.js +1 -1
- package/tools/history.js +1 -1
- package/tools/hypothesis.js +1 -1
- package/tools/inventory.js +1 -1
- package/tools/note.js +1 -1
- package/tools/overview.js +1 -1
- package/tools/register-agent.js +2 -2
- package/tools/run-tests.js +310 -252
- package/tools/settings-list.js +1 -1
- package/tools/status.js +1 -1
- package/tools/tdd-artifact.js +1 -1
- package/tools/tdd-behavior.js +23 -23
- package/tools/tdd-goal.js +22 -24
- package/tools/tdd-phase-transition-request.js +1 -1
- package/tools/tdd-progress-push.js +1 -1
- package/tools/tdd-task.js +1 -1
- package/tools/test.js +1 -1
- package/tools/trends.js +1 -1
- package/tools/triage-brief.js +1 -1
- package/tools/turn-search.js +1 -1
- package/tools/wrapup-prompt.js +1 -1
- package/tsdoc-metadata.json +1 -1
- package/utils/safe-format-fatal-error.js +1 -1
- package/version.js +1 -1
- package/idempotency.js +0 -123
package/index.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { RenderText } from "./annotations.js";
|
|
2
|
-
import { withIdempotency } from "./idempotency.js";
|
|
3
2
|
import { McpSession, createCurrentSessionIdRef, createSessionContextRef, sessionContextFromEnv } from "./session.js";
|
|
4
3
|
import { PromptsLayer } from "./prompts/layer.js";
|
|
5
4
|
import { AcceptanceMetricsResult, acceptanceMetricsTool, handleAcceptanceMetrics } from "./tools/acceptance-metrics.js";
|
|
@@ -37,4 +36,4 @@ import { SERVER_INSTRUCTIONS, ServerLayer } from "./server.js";
|
|
|
37
36
|
import { CURRENT_MCP_VERSION } from "./version.js";
|
|
38
37
|
import { ToolRefusal } from "@effected/mcp";
|
|
39
38
|
|
|
40
|
-
export { AcceptanceMetricsResult, CURRENT_MCP_VERSION, CacheHealthResult, CommitChangesInput, CommitChangesResult, ConfigureInput, ConfigureResult, FailureSignatureGetInput, FailureSignatureGetResult, FileCoverageInput, FileCoverageResult, HelpResult, HypothesisInput, HypothesisResult, InventoryInput, InventoryResult, Kit, McpSession, NoteParams, NoteResult, PhaseTransitionInput, PhaseTransitionResult, PingResult, PromptsLayer, RegisterAgentInput, RegisterAgentResult, RenderText, RunTestsInput, RunTestsResult, SERVER_INSTRUCTIONS, ServerLayer, SettingsListResult, TddArtifactListInput, TddArtifactListResult, TddBehaviorInput, TddBehaviorResult, TddGoalInput, TddGoalResult, TddProgressPushInput, TddProgressPushResult, TddTaskInput, TddTaskResult, TestCoverageInput, TestCoverageResult, TestErrorsInput, TestErrorsResult, TestHistoryInput, TestHistoryResult, TestInput, TestOverviewInput, TestOverviewResult, TestResult, TestStatusInput, TestStatusResult, TestTrendsInput, TestTrendsResult, ToolRefusal, ToolsLayer, TriageBriefInput, TriageBriefResult, TurnSearchInput, TurnSearchResult, WrapupPromptInput, WrapupPromptResult, acceptanceMetricsTool, cacheHealthTool, commitChangesTool, configureTool, createCurrentSessionIdRef, createSessionContextRef, failureSignatureGetTool, fileCoverageTool, handleAcceptanceMetrics, handleCacheHealth, handleCommitChanges, handleConfigure, handleFailureSignatureGet, handleFileCoverage, handleHelp, handleHypothesis, handleInventory, handleNote, handlePhaseTransitionRequest, handlePing, handleRegisterAgent, handleRunTests, handleSettingsList, handleTddArtifactList, handleTddBehavior, handleTddGoal, handleTddProgressPush, handleTddTask, handleTest, handleTestCoverage, handleTestErrors, handleTestHistory, handleTestOverview, handleTestStatus, handleTestTrends, handleTriageBrief, handleTurnSearch, handleWrapupPrompt, helpTool, hypothesisTool, inventoryTool, noteTool, pingTool, registerAgentTool, runTestsTool, sessionContextFromEnv, settingsListTool, tddArtifactListTool, tddBehaviorTool, tddGoalTool, tddPhaseTransitionRequestTool, tddProgressPushTool, tddTaskTool, testCoverageTool, testErrorsTool, testHistoryTool, testOverviewTool, testStatusTool, testTool, testTrendsTool, toolHandlers, triageBriefTool, turnSearchTool,
|
|
39
|
+
export { AcceptanceMetricsResult, CURRENT_MCP_VERSION, CacheHealthResult, CommitChangesInput, CommitChangesResult, ConfigureInput, ConfigureResult, FailureSignatureGetInput, FailureSignatureGetResult, FileCoverageInput, FileCoverageResult, HelpResult, HypothesisInput, HypothesisResult, InventoryInput, InventoryResult, Kit, McpSession, NoteParams, NoteResult, PhaseTransitionInput, PhaseTransitionResult, PingResult, PromptsLayer, RegisterAgentInput, RegisterAgentResult, RenderText, RunTestsInput, RunTestsResult, SERVER_INSTRUCTIONS, ServerLayer, SettingsListResult, TddArtifactListInput, TddArtifactListResult, TddBehaviorInput, TddBehaviorResult, TddGoalInput, TddGoalResult, TddProgressPushInput, TddProgressPushResult, TddTaskInput, TddTaskResult, TestCoverageInput, TestCoverageResult, TestErrorsInput, TestErrorsResult, TestHistoryInput, TestHistoryResult, TestInput, TestOverviewInput, TestOverviewResult, TestResult, TestStatusInput, TestStatusResult, TestTrendsInput, TestTrendsResult, ToolRefusal, ToolsLayer, TriageBriefInput, TriageBriefResult, TurnSearchInput, TurnSearchResult, WrapupPromptInput, WrapupPromptResult, acceptanceMetricsTool, cacheHealthTool, commitChangesTool, configureTool, createCurrentSessionIdRef, createSessionContextRef, failureSignatureGetTool, fileCoverageTool, handleAcceptanceMetrics, handleCacheHealth, handleCommitChanges, handleConfigure, handleFailureSignatureGet, handleFileCoverage, handleHelp, handleHypothesis, handleInventory, handleNote, handlePhaseTransitionRequest, handlePing, handleRegisterAgent, handleRunTests, handleSettingsList, handleTddArtifactList, handleTddBehavior, handleTddGoal, handleTddProgressPush, handleTddTask, handleTest, handleTestCoverage, handleTestErrors, handleTestHistory, handleTestOverview, handleTestStatus, handleTestTrends, handleTriageBrief, handleTurnSearch, handleWrapupPrompt, helpTool, hypothesisTool, inventoryTool, noteTool, pingTool, registerAgentTool, runTestsTool, sessionContextFromEnv, settingsListTool, tddArtifactListTool, tddBehaviorTool, tddGoalTool, tddPhaseTransitionRequestTool, tddProgressPushTool, tddTaskTool, testCoverageTool, testErrorsTool, testHistoryTool, testOverviewTool, testStatusTool, testTool, testTrendsTool, toolHandlers, triageBriefTool, turnSearchTool, wrapupPromptTool };
|
package/main.js
CHANGED
|
@@ -2,45 +2,6 @@ import { McpGuard } from "@effected/mcp/guard";
|
|
|
2
2
|
|
|
3
3
|
// src/main.ts
|
|
4
4
|
/**
|
|
5
|
-
* Optional first positional argument: an initial Claude Code chat UUID (the
|
|
6
|
-
* host's `chatId`) to seed the MCP server's session association. Claude Code
|
|
7
|
-
* passes unknown `${...}` variables through as literal text in some surfaces,
|
|
8
|
-
* so an unsubstituted placeholder is treated as absent rather than seeding
|
|
9
|
-
* garbage.
|
|
10
|
-
*
|
|
11
|
-
* @param argv - the raw `process.argv`
|
|
12
|
-
* @param isUnsubstituted - `LaunchContext.isUnsubstituted`, passed in because
|
|
13
|
-
* this module may not statically import the server graph
|
|
14
|
-
* @returns the trimmed seed, or `null` when absent, empty, or a placeholder
|
|
15
|
-
*/
|
|
16
|
-
const resolveInitialSessionId = (argv, isUnsubstituted) => {
|
|
17
|
-
const trimmed = argv[2]?.trim();
|
|
18
|
-
if (trimmed === void 0 || trimmed.length === 0 || isUnsubstituted(trimmed)) return null;
|
|
19
|
-
return trimmed;
|
|
20
|
-
};
|
|
21
|
-
const INJECT_AT = ["load", "connected"];
|
|
22
|
-
const INJECT_KIND = ["uncaughtException", "unhandledRejection"];
|
|
23
|
-
/**
|
|
24
|
-
* Parse the test-only `VITEST_AGENT_MCP_TEST_INJECT_CRASH` value into the
|
|
25
|
-
* guard's `injectCrash`. Accepts `<at>:<kind>` (`at` is `load` or
|
|
26
|
-
* `connected`, `kind` is `uncaughtException` or `unhandledRejection`), and a
|
|
27
|
-
* bare `<kind>` as the legacy spelling of `connected:<kind>`. Anything else,
|
|
28
|
-
* or no value, is `undefined`: no injection.
|
|
29
|
-
*/
|
|
30
|
-
const parseInjectCrash = (value) => {
|
|
31
|
-
if (value === void 0) return void 0;
|
|
32
|
-
const [first, second, ...rest] = value.split(":");
|
|
33
|
-
if (rest.length > 0) return void 0;
|
|
34
|
-
const at = second === void 0 ? "connected" : first;
|
|
35
|
-
const kind = second === void 0 ? first : second;
|
|
36
|
-
const validAt = INJECT_AT.find((candidate) => candidate === at);
|
|
37
|
-
const validKind = INJECT_KIND.find((candidate) => candidate === kind);
|
|
38
|
-
return validAt === void 0 || validKind === void 0 ? void 0 : {
|
|
39
|
-
at: validAt,
|
|
40
|
-
kind: validKind
|
|
41
|
-
};
|
|
42
|
-
};
|
|
43
|
-
/**
|
|
44
5
|
* Run the vitest-agent MCP server over stdio. Owns the process: registers
|
|
45
6
|
* the crash guards through `McpGuard.run`, resolves the project directory and `data.db` path,
|
|
46
7
|
* recovers the host session context, and launches the server layer under
|
|
@@ -59,7 +20,7 @@ const main = (options = {}) => McpGuard.run({
|
|
|
59
20
|
onUncaught: "exitBeforeConnect",
|
|
60
21
|
onRejection: "log"
|
|
61
22
|
},
|
|
62
|
-
injectCrash: parseInjectCrash(process.env.VITEST_AGENT_MCP_TEST_INJECT_CRASH),
|
|
23
|
+
injectCrash: McpGuard.parseInjectCrash(process.env.VITEST_AGENT_MCP_TEST_INJECT_CRASH),
|
|
63
24
|
load: async () => {
|
|
64
25
|
const { safeFormatFatalError } = await import("./utils/safe-format-fatal-error.js");
|
|
65
26
|
const NodeRuntime = await import("@effect/platform-node/NodeRuntime");
|
|
@@ -67,22 +28,25 @@ const main = (options = {}) => McpGuard.run({
|
|
|
67
28
|
const NodeStdio = await import("@effect/platform-node/NodeStdio");
|
|
68
29
|
const { Effect, Layer, Option } = await import("effect");
|
|
69
30
|
const { CurrentDistribution, LaunchContext } = await import("@effected/engine");
|
|
31
|
+
const { StdioCapture } = await import("@effected/mcp/node");
|
|
70
32
|
const { PathResolutionLive, PlatformLive, recoverSessionContextFromSessionEnv, resolveDataPath, resolveLogFile, resolveLogLevel, resolveProjectDir } = await import("@vitest-agent/engine");
|
|
71
33
|
const { McpSession, sessionContextFromEnv } = await import("./session.js");
|
|
72
34
|
const { ServerLayer } = await import("./server.js");
|
|
35
|
+
const { RunTestsStdio } = await import("./tools/run-tests.js");
|
|
73
36
|
const { CURRENT_MCP_VERSION } = await import("./version.js");
|
|
74
37
|
const env = process.env;
|
|
75
38
|
const projectDir = resolveProjectDir({
|
|
76
39
|
env,
|
|
77
40
|
cwd: process.cwd()
|
|
78
41
|
});
|
|
79
|
-
const initialSessionId =
|
|
42
|
+
const initialSessionId = LaunchContext.usable(process.argv[2]);
|
|
43
|
+
const stdioCapture = StdioCapture.install(process);
|
|
80
44
|
const dbPath = await Effect.runPromise(resolveDataPath(projectDir).pipe(Effect.provide(PathResolutionLive(projectDir)), Effect.provide(NodeServices.layer)));
|
|
81
|
-
const recovered = sessionContextFromEnv(env);
|
|
82
45
|
const homeDir = env.HOME ?? env.USERPROFILE ?? "";
|
|
46
|
+
const recovered = sessionContextFromEnv(env);
|
|
83
47
|
const Session = McpSession.layer({
|
|
84
48
|
cwd: projectDir,
|
|
85
|
-
initialSessionId: initialSessionId ?? recovered?.chatId ?? null,
|
|
49
|
+
initialSessionId: Option.getOrNull(initialSessionId) ?? recovered?.chatId ?? null,
|
|
86
50
|
initialContext: recovered,
|
|
87
51
|
recover: () => recoverSessionContextFromSessionEnv({
|
|
88
52
|
projectDir,
|
|
@@ -95,7 +59,7 @@ const main = (options = {}) => McpGuard.run({
|
|
|
95
59
|
env,
|
|
96
60
|
logLevel: resolveLogLevel(env),
|
|
97
61
|
logFile: resolveLogFile(env)
|
|
98
|
-
})), Layer.provide(Layer.succeed(CurrentDistribution, Option.fromNullishOr(options.distribution))), Layer.provide(NodeStdio.layer)),
|
|
62
|
+
})), Layer.provide(Layer.succeed(CurrentDistribution, Option.fromNullishOr(options.distribution))), Layer.provide(Layer.succeed(RunTestsStdio, stdioCapture)), Layer.provide(NodeStdio.layer)),
|
|
99
63
|
runMain: NodeRuntime.runMain,
|
|
100
64
|
format: safeFormatFatalError
|
|
101
65
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vitest-agent/mcp",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.0.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Model Context Protocol server for vitest-agent. Exposes 30 tools for agent access to test data, TDD lifecycle, and session management.",
|
|
6
6
|
"keywords": [
|
|
@@ -43,15 +43,16 @@
|
|
|
43
43
|
"vitest-agent-mcp": "bin/vitest-agent-mcp.js"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@effect/platform-node": "^4.0.
|
|
47
|
-
"@
|
|
48
|
-
"@effected/engine": "^0.
|
|
49
|
-
"@effected/git": "^0.
|
|
50
|
-
"@effected/
|
|
51
|
-
"@effected/
|
|
52
|
-
"@
|
|
53
|
-
"@vitest-agent/
|
|
54
|
-
"
|
|
46
|
+
"@effect/platform-node": "^4.0.7",
|
|
47
|
+
"@effected/commands": "^0.18.0",
|
|
48
|
+
"@effected/engine": "^0.10.0",
|
|
49
|
+
"@effected/git": "^0.27.0",
|
|
50
|
+
"@effected/glob": "^0.16.0",
|
|
51
|
+
"@effected/mcp": "^0.11.0",
|
|
52
|
+
"@effected/walker": "^0.22.0",
|
|
53
|
+
"@vitest-agent/engine": "3.0.1",
|
|
54
|
+
"@vitest-agent/sdk": "9.0.1",
|
|
55
|
+
"effect": "^4.0.7"
|
|
55
56
|
},
|
|
56
57
|
"peerDependencies": {
|
|
57
58
|
"vitest": "^5.0.0"
|
package/replay.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { ToolReplay } from "@effected/mcp";
|
|
2
|
+
import { Effect, Option } from "effect";
|
|
3
|
+
import { DataReader, DataStore } from "@vitest-agent/engine";
|
|
4
|
+
|
|
5
|
+
// src/replay.ts
|
|
6
|
+
/**
|
|
7
|
+
* `hypothesis`: `validate` keys on `validate:<id>:<outcome>`. `record` is
|
|
8
|
+
* deliberately NOT idempotent: a hypothesis is an append-only observation
|
|
9
|
+
* whose binding session is resolved server-side (and so is absent from the
|
|
10
|
+
* input), leaving no safe per-call discriminator — content-only keying would
|
|
11
|
+
* collide across sessions and replay a stale row under the wrong one. A
|
|
12
|
+
* genuine retry creating a duplicate hypothesis row is harmless by comparison.
|
|
13
|
+
*
|
|
14
|
+
* @internal
|
|
15
|
+
*/
|
|
16
|
+
const hypothesisReplayKey = (input) => input.action === "validate" ? Option.some(`validate:${input.id}:${input.outcome}`) : Option.none();
|
|
17
|
+
/**
|
|
18
|
+
* `tdd_task`: `start` keys on whichever id form is present (`sid:` for
|
|
19
|
+
* `sessionId`, `chat:` for `chatId`, so a chat id can never collide with an
|
|
20
|
+
* integer id) plus the `runId` when supplied, else the goal text — a new
|
|
21
|
+
* `runId` lets the same goal be retried in one chat; `end` keys on
|
|
22
|
+
* `end:<tddTaskId>:<outcome>`.
|
|
23
|
+
*
|
|
24
|
+
* @internal
|
|
25
|
+
*/
|
|
26
|
+
const tddTaskReplayKey = (input) => {
|
|
27
|
+
if (input.action === "start") {
|
|
28
|
+
const id = input.sessionId !== void 0 ? `sid:${input.sessionId}` : input.chatId !== void 0 ? `chat:${input.chatId}` : void 0;
|
|
29
|
+
if (id === void 0) return Option.none();
|
|
30
|
+
return Option.some(input.runId !== void 0 ? `start:${id}:run:${input.runId}` : `start:${id}:${input.goal}`);
|
|
31
|
+
}
|
|
32
|
+
if (input.action === "end") return Option.some(`end:${input.tddTaskId}:${input.outcome}`);
|
|
33
|
+
return Option.none();
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* `tdd_goal`: `create` keys on `create:<tddTaskId>:<goal>`.
|
|
37
|
+
*
|
|
38
|
+
* @internal
|
|
39
|
+
*/
|
|
40
|
+
const tddGoalReplayKey = (input) => input.action === "create" ? Option.some(`create:${input.tddTaskId}:${input.goal}`) : Option.none();
|
|
41
|
+
/**
|
|
42
|
+
* `tdd_behavior`: `create` keys on `create:<goalId>:<behavior>`.
|
|
43
|
+
*
|
|
44
|
+
* @internal
|
|
45
|
+
*/
|
|
46
|
+
const tddBehaviorReplayKey = (input) => input.action === "create" ? Option.some(`create:${input.goalId}:${input.behavior}`) : Option.none();
|
|
47
|
+
/**
|
|
48
|
+
* Splits a `ToolReplay.storeKey` (`<tool>:<key>`) back into the table's
|
|
49
|
+
* `(procedure_path, key)` pair, so rows written before `ToolReplay` keep
|
|
50
|
+
* replaying under the same coordinates.
|
|
51
|
+
*/
|
|
52
|
+
const splitStoreKey = (storeKey) => {
|
|
53
|
+
const at = storeKey.indexOf(":");
|
|
54
|
+
return [storeKey.slice(0, at), storeKey.slice(at + 1)];
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* A `ToolReplayStore` over `mcp_idempotent_responses`. A store fault fails
|
|
58
|
+
* the `get` / `set`, which `ToolReplay` treats as a miss or a skipped write.
|
|
59
|
+
*
|
|
60
|
+
* @internal
|
|
61
|
+
*/
|
|
62
|
+
const makeReplayStore = Effect.gen(function* () {
|
|
63
|
+
const reader = yield* DataReader;
|
|
64
|
+
const store = yield* DataStore;
|
|
65
|
+
return {
|
|
66
|
+
get: (storeKey) => {
|
|
67
|
+
const [tool, key] = splitStoreKey(storeKey);
|
|
68
|
+
return reader.findIdempotentResponse(tool, key).pipe(Effect.map(Option.getOrUndefined));
|
|
69
|
+
},
|
|
70
|
+
set: (storeKey, value) => {
|
|
71
|
+
const [tool, key] = splitStoreKey(storeKey);
|
|
72
|
+
return store.recordIdempotentResponse({
|
|
73
|
+
procedurePath: tool,
|
|
74
|
+
key,
|
|
75
|
+
resultJson: value,
|
|
76
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
});
|
|
81
|
+
/**
|
|
82
|
+
* `ToolReplay.wrap` for one write tool, with the replay store built from the
|
|
83
|
+
* handler's own `DataReader` / `DataStore`, so the wrapped handler requires
|
|
84
|
+
* exactly what the bare one did.
|
|
85
|
+
*
|
|
86
|
+
* @internal
|
|
87
|
+
*/
|
|
88
|
+
const replayed = (options) => (handler) => (params) => Effect.flatMap(makeReplayStore, (store) => ToolReplay.wrap({
|
|
89
|
+
...options,
|
|
90
|
+
store
|
|
91
|
+
})(handler)(params));
|
|
92
|
+
|
|
93
|
+
export { hypothesisReplayKey, makeReplayStore, replayed, tddBehaviorReplayKey, tddGoalReplayKey, tddTaskReplayKey };
|
package/server.js
CHANGED
|
@@ -20,7 +20,7 @@ const SERVER_INSTRUCTIONS = [
|
|
|
20
20
|
"Call the help tool first for the full tool reference; triage_brief orients on the current landscape; run_tests executes Vitest and persists the run so every query tool reads it back.",
|
|
21
21
|
"Every input is strict at every object level: an unknown key is rejected with a message naming the unrecognized key(s) and the accepted params, so fix the call rather than retrying it.",
|
|
22
22
|
"Every successful result carries the typed object in structuredContent; read structuredContent for every field.",
|
|
23
|
-
"An expected domain error is NOT an isError result: it is a normal result whose structuredContent has ok: false and an error object. tdd_goal and tdd_behavior put _tag, the failing ids and a remediation naming the tool to call next in that object; register_agent puts a code and a message in it, plus existingAgentId or expectedPrefix when the code calls for one.",
|
|
23
|
+
"An expected domain error is NOT an isError result: it is a normal result whose structuredContent has ok: false and an error object. tdd_goal and tdd_behavior put _tag, a one-sentence message, the failing ids and a remediation naming the tool to call next in that object; register_agent puts a code and a message in it, plus existingAgentId or expectedPrefix when the code calls for one.",
|
|
24
24
|
"An isError result means the call itself failed and carries no structuredContent. Its text either names what to fix (invalid params, or a refused call such as an unknown id, often ending 'Try <tool>.' with the tool to call next) or is a generic internal-error message (the server logged the detail). Fix the call, or report the internal failure rather than retrying it unchanged."
|
|
25
25
|
].join("\n");
|
|
26
26
|
/**
|
package/toolkit.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { hypothesisReplayKey, replayed, tddBehaviorReplayKey, tddGoalReplayKey, tddTaskReplayKey } from "./replay.js";
|
|
2
2
|
import { acceptanceMetricsTool, handleAcceptanceMetrics } from "./tools/acceptance-metrics.js";
|
|
3
3
|
import { cacheHealthTool, handleCacheHealth } from "./tools/cache-health.js";
|
|
4
4
|
import { commitChangesTool, handleCommitChanges } from "./tools/commit-changes.js";
|
|
@@ -9,7 +9,7 @@ import { failureSignatureGetTool, handleFailureSignatureGet } from "./tools/fail
|
|
|
9
9
|
import { fileCoverageTool, handleFileCoverage } from "./tools/file-coverage.js";
|
|
10
10
|
import { handleHelp, helpTool } from "./tools/help.js";
|
|
11
11
|
import { handleTestHistory, testHistoryTool } from "./tools/history.js";
|
|
12
|
-
import { handleHypothesis, hypothesisTool } from "./tools/hypothesis.js";
|
|
12
|
+
import { HypothesisResult, handleHypothesis, hypothesisTool } from "./tools/hypothesis.js";
|
|
13
13
|
import { handleInventory, inventoryTool } from "./tools/inventory.js";
|
|
14
14
|
import { handleNote, noteTool } from "./tools/note.js";
|
|
15
15
|
import { handleTestOverview, testOverviewTool } from "./tools/overview.js";
|
|
@@ -19,11 +19,11 @@ import { handleRunTests, runTestsTool } from "./tools/run-tests.js";
|
|
|
19
19
|
import { handleSettingsList, settingsListTool } from "./tools/settings-list.js";
|
|
20
20
|
import { handleTestStatus, testStatusTool } from "./tools/status.js";
|
|
21
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";
|
|
22
|
+
import { TddBehaviorResult, handleTddBehavior, tddBehaviorTool } from "./tools/tdd-behavior.js";
|
|
23
|
+
import { TddGoalResult, handleTddGoal, tddGoalTool } from "./tools/tdd-goal.js";
|
|
24
24
|
import { handlePhaseTransitionRequest, tddPhaseTransitionRequestTool } from "./tools/tdd-phase-transition-request.js";
|
|
25
25
|
import { handleTddProgressPush, tddProgressPushTool } from "./tools/tdd-progress-push.js";
|
|
26
|
-
import { handleTddTask, tddTaskTool } from "./tools/tdd-task.js";
|
|
26
|
+
import { TddTaskResult, handleTddTask, tddTaskTool } from "./tools/tdd-task.js";
|
|
27
27
|
import { handleTest, testTool } from "./tools/test.js";
|
|
28
28
|
import { handleTestTrends, testTrendsTool } from "./tools/trends.js";
|
|
29
29
|
import { handleTriageBrief, triageBriefTool } from "./tools/triage-brief.js";
|
|
@@ -70,11 +70,27 @@ const toolHandlers = {
|
|
|
70
70
|
test: McpToolkit.unionHandler(testTool, handleTest),
|
|
71
71
|
register_agent: handleRegisterAgent,
|
|
72
72
|
note: McpToolkit.unionHandler(noteTool, handleNote),
|
|
73
|
-
hypothesis: McpToolkit.unionHandler(hypothesisTool,
|
|
74
|
-
|
|
73
|
+
hypothesis: McpToolkit.unionHandler(hypothesisTool, replayed({
|
|
74
|
+
tool: "hypothesis",
|
|
75
|
+
success: HypothesisResult,
|
|
76
|
+
key: hypothesisReplayKey
|
|
77
|
+
})(handleHypothesis)),
|
|
78
|
+
tdd_task: McpToolkit.unionHandler(tddTaskTool, replayed({
|
|
79
|
+
tool: "tdd_task",
|
|
80
|
+
success: TddTaskResult,
|
|
81
|
+
key: tddTaskReplayKey
|
|
82
|
+
})(handleTddTask)),
|
|
75
83
|
tdd_phase_transition_request: handlePhaseTransitionRequest,
|
|
76
|
-
tdd_goal: McpToolkit.unionHandler(tddGoalTool,
|
|
77
|
-
|
|
84
|
+
tdd_goal: McpToolkit.unionHandler(tddGoalTool, replayed({
|
|
85
|
+
tool: "tdd_goal",
|
|
86
|
+
success: TddGoalResult,
|
|
87
|
+
key: tddGoalReplayKey
|
|
88
|
+
})(handleTddGoal)),
|
|
89
|
+
tdd_behavior: McpToolkit.unionHandler(tddBehaviorTool, replayed({
|
|
90
|
+
tool: "tdd_behavior",
|
|
91
|
+
success: TddBehaviorResult,
|
|
92
|
+
key: tddBehaviorReplayKey
|
|
93
|
+
})(handleTddBehavior)),
|
|
78
94
|
tdd_artifact_list: handleTddArtifactList,
|
|
79
95
|
tdd_progress_push: handleTddProgressPush,
|
|
80
96
|
run_tests: handleRunTests
|
|
@@ -1,88 +1,137 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { ToolOutcome } from "@effected/mcp";
|
|
2
|
+
import { Schema } from "effect";
|
|
3
3
|
|
|
4
4
|
// src/tools/_tdd-error-envelope.ts
|
|
5
|
+
const TddTaskEndOutcome = Schema.Literals([
|
|
6
|
+
"succeeded",
|
|
7
|
+
"blocked",
|
|
8
|
+
"abandoned"
|
|
9
|
+
]);
|
|
10
|
+
const StatusEntity = Schema.Literals([
|
|
11
|
+
"goal",
|
|
12
|
+
"behavior",
|
|
13
|
+
"task"
|
|
14
|
+
]);
|
|
15
|
+
/**
|
|
16
|
+
* The five envelope errors, in the order they are served. Each is a named
|
|
17
|
+
* `$defs` entry in the served `outputSchema`, open to extra keys as the
|
|
18
|
+
* envelope always was.
|
|
19
|
+
*/
|
|
20
|
+
const TddEnvelopeErrors = [
|
|
21
|
+
ToolOutcome.error("GoalNotFoundError", {
|
|
22
|
+
id: Schema.Number,
|
|
23
|
+
reason: Schema.String
|
|
24
|
+
}).annotate({
|
|
25
|
+
identifier: "GoalNotFoundError",
|
|
26
|
+
description: "No goal with this id. `remediation` names the tool that lists goals."
|
|
27
|
+
}),
|
|
28
|
+
ToolOutcome.error("BehaviorNotFoundError", {
|
|
29
|
+
id: Schema.Number,
|
|
30
|
+
reason: Schema.String
|
|
31
|
+
}).annotate({
|
|
32
|
+
identifier: "BehaviorNotFoundError",
|
|
33
|
+
description: "No behavior with this id. `remediation` names the tool that lists behaviors."
|
|
34
|
+
}),
|
|
35
|
+
ToolOutcome.error("TddTaskNotFoundError", {
|
|
36
|
+
id: Schema.Number,
|
|
37
|
+
reason: Schema.String
|
|
38
|
+
}).annotate({
|
|
39
|
+
identifier: "TddTaskNotFoundError",
|
|
40
|
+
description: "No TDD task with this id. `remediation` names the tool that opens one."
|
|
41
|
+
}),
|
|
42
|
+
ToolOutcome.error("TddTaskAlreadyEndedError", {
|
|
43
|
+
id: Schema.Number,
|
|
44
|
+
endedAt: Schema.String,
|
|
45
|
+
outcome: TddTaskEndOutcome
|
|
46
|
+
}).annotate({
|
|
47
|
+
identifier: "TddTaskAlreadyEndedError",
|
|
48
|
+
description: "The TDD task has already ended, so nothing more can be added to it."
|
|
49
|
+
}),
|
|
50
|
+
ToolOutcome.error("IllegalStatusTransitionError", {
|
|
51
|
+
entity: StatusEntity,
|
|
52
|
+
id: Schema.Number,
|
|
53
|
+
from: Schema.String,
|
|
54
|
+
to: Schema.String,
|
|
55
|
+
reason: Schema.String
|
|
56
|
+
}).annotate({
|
|
57
|
+
identifier: "IllegalStatusTransitionError",
|
|
58
|
+
description: "The requested status change is not allowed; `remediation` suggests abandoning instead."
|
|
59
|
+
})
|
|
60
|
+
];
|
|
61
|
+
/**
|
|
62
|
+
* A goal / behavior tool's success schema: its success members, or the
|
|
63
|
+
* `{ ok: false, error }` envelope carrying one of the five TDD errors.
|
|
64
|
+
* `identifier` names the envelope in the served `$defs`; each tool keeps
|
|
65
|
+
* the name it served before the envelope moved onto `ToolOutcome`.
|
|
66
|
+
*
|
|
67
|
+
* @internal
|
|
68
|
+
*/
|
|
69
|
+
const tddEnvelope = (success, identifier) => ToolOutcome.envelope(success, TddEnvelopeErrors, {
|
|
70
|
+
identifier,
|
|
71
|
+
description: "Returned instead of a result when a known TDD error is caught: `ok` is `false` and `error` names it."
|
|
72
|
+
});
|
|
73
|
+
/** @internal */
|
|
5
74
|
const goalNotFound = (e) => ({
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
suggestedArgs: { action: "list" },
|
|
14
|
-
hint: `No tdd_session_goals row with id=${e.id}. Call tdd_goal({ action: "list", tddTaskId }) to find the correct goal id.`
|
|
15
|
-
}
|
|
75
|
+
id: e.id,
|
|
76
|
+
reason: e.reason,
|
|
77
|
+
message: `No goal with id ${e.id}.`,
|
|
78
|
+
remediation: {
|
|
79
|
+
suggestedTool: "tdd_goal",
|
|
80
|
+
suggestedArgs: { action: "list" },
|
|
81
|
+
hint: `No tdd_session_goals row with id=${e.id}. Call tdd_goal({ action: "list", tddTaskId }) to find the correct goal id.`
|
|
16
82
|
}
|
|
17
83
|
});
|
|
84
|
+
/** @internal */
|
|
18
85
|
const behaviorNotFound = (e) => ({
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
suggestedArgs: { action: "list_by_goal" },
|
|
27
|
-
hint: `No tdd_session_behaviors row with id=${e.id}. Call tdd_behavior({ action: "list_by_goal", goalId }) or tdd_behavior({ action: "list_by_tdd_task", tddTaskId }) to find the correct behavior id.`
|
|
28
|
-
}
|
|
86
|
+
id: e.id,
|
|
87
|
+
reason: e.reason,
|
|
88
|
+
message: `No behavior with id ${e.id}.`,
|
|
89
|
+
remediation: {
|
|
90
|
+
suggestedTool: "tdd_behavior",
|
|
91
|
+
suggestedArgs: { action: "list_by_goal" },
|
|
92
|
+
hint: `No tdd_session_behaviors row with id=${e.id}. Call tdd_behavior({ action: "list_by_goal", goalId }) or tdd_behavior({ action: "list_by_tdd_task", tddTaskId }) to find the correct behavior id.`
|
|
29
93
|
}
|
|
30
94
|
});
|
|
95
|
+
/** @internal */
|
|
31
96
|
const tddTaskNotFound = (e) => ({
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
suggestedArgs: { action: "start" },
|
|
40
|
-
hint: `No tdd_tasks row with id=${e.id}. Call tdd_task({ action: "start" }) to open a TDD task before creating goals or behaviors.`
|
|
41
|
-
}
|
|
97
|
+
id: e.id,
|
|
98
|
+
reason: e.reason,
|
|
99
|
+
message: `No TDD task with id ${e.id}.`,
|
|
100
|
+
remediation: {
|
|
101
|
+
suggestedTool: "tdd_task",
|
|
102
|
+
suggestedArgs: { action: "start" },
|
|
103
|
+
hint: `No tdd_tasks row with id=${e.id}. Call tdd_task({ action: "start" }) to open a TDD task before creating goals or behaviors.`
|
|
42
104
|
}
|
|
43
105
|
});
|
|
106
|
+
/** @internal */
|
|
44
107
|
const tddTaskAlreadyEnded = (e) => ({
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
suggestedArgs: { action: "start" },
|
|
54
|
-
hint: `tdd_tasks row id=${e.id} is already ended (outcome=${e.outcome}). Open a new TDD task if you need to add more goals or behaviors.`
|
|
55
|
-
}
|
|
108
|
+
id: e.id,
|
|
109
|
+
endedAt: e.endedAt,
|
|
110
|
+
outcome: e.outcome,
|
|
111
|
+
message: `TDD task ${e.id} has already ended (${e.outcome}).`,
|
|
112
|
+
remediation: {
|
|
113
|
+
suggestedTool: "tdd_task",
|
|
114
|
+
suggestedArgs: { action: "start" },
|
|
115
|
+
hint: `tdd_tasks row id=${e.id} is already ended (outcome=${e.outcome}). Open a new TDD task if you need to add more goals or behaviors.`
|
|
56
116
|
}
|
|
57
117
|
});
|
|
118
|
+
/** @internal */
|
|
58
119
|
const illegalStatusTransition = (e) => ({
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
},
|
|
74
|
-
hint: `Cannot transition ${e.entity} id=${e.id} from ${e.from} to ${e.to}. Use status:'abandoned' to drop work; do not delete unless the entity was created by mistake.`
|
|
75
|
-
}
|
|
120
|
+
entity: e.entity,
|
|
121
|
+
id: e.id,
|
|
122
|
+
from: e.from,
|
|
123
|
+
to: e.to,
|
|
124
|
+
reason: e.reason,
|
|
125
|
+
message: `Cannot move ${e.entity} ${e.id} from ${e.from} to ${e.to}.`,
|
|
126
|
+
remediation: {
|
|
127
|
+
suggestedTool: e.entity === "goal" ? "tdd_goal" : "tdd_behavior",
|
|
128
|
+
suggestedArgs: {
|
|
129
|
+
action: "update",
|
|
130
|
+
id: e.id,
|
|
131
|
+
status: "abandoned"
|
|
132
|
+
},
|
|
133
|
+
hint: `Cannot transition ${e.entity} id=${e.id} from ${e.from} to ${e.to}. Use status:'abandoned' to drop work; do not delete unless the entity was created by mistake.`
|
|
76
134
|
}
|
|
77
135
|
});
|
|
78
|
-
const isKnownTddError = (e) => e instanceof GoalNotFoundError || e instanceof BehaviorNotFoundError || e instanceof TddTaskNotFoundError || e instanceof TddTaskAlreadyEndedError || e instanceof IllegalStatusTransitionError;
|
|
79
|
-
const tddErrorToEnvelope = (e) => {
|
|
80
|
-
if (e instanceof GoalNotFoundError) return goalNotFound(e);
|
|
81
|
-
if (e instanceof BehaviorNotFoundError) return behaviorNotFound(e);
|
|
82
|
-
if (e instanceof TddTaskNotFoundError) return tddTaskNotFound(e);
|
|
83
|
-
if (e instanceof TddTaskAlreadyEndedError) return tddTaskAlreadyEnded(e);
|
|
84
|
-
return illegalStatusTransition(e);
|
|
85
|
-
};
|
|
86
|
-
const catchTddErrorsAsEnvelope = (effect) => effect.pipe(Effect.catch((e) => isKnownTddError(e) ? Effect.succeed(tddErrorToEnvelope(e)) : Effect.fail(e)));
|
|
87
136
|
|
|
88
|
-
export {
|
|
137
|
+
export { behaviorNotFound, goalNotFound, illegalStatusTransition, tddEnvelope, tddTaskAlreadyEnded, tddTaskNotFound };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Effect, Schema } from "effect";
|
|
2
|
-
import { DataReader } from "@vitest-agent/engine";
|
|
3
2
|
import { Tool } from "effect/ai";
|
|
3
|
+
import { DataReader } from "@vitest-agent/engine";
|
|
4
4
|
|
|
5
5
|
// src/tools/acceptance-metrics.ts
|
|
6
6
|
const totalAnnotation = { description: "Sample size — number of observations the metric ratio is computed over." };
|
package/tools/cache-health.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ToolOutputSchema } from "@effected/mcp";
|
|
2
2
|
import { Effect, Option, Schema } from "effect";
|
|
3
|
-
import { DataReader } from "@vitest-agent/engine";
|
|
4
3
|
import { Tool } from "effect/ai";
|
|
4
|
+
import { DataReader } from "@vitest-agent/engine";
|
|
5
5
|
import { CacheManifest } from "@vitest-agent/sdk";
|
|
6
6
|
|
|
7
7
|
// src/tools/cache-health.ts
|
package/tools/commit-changes.js
CHANGED
package/tools/configure.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ToolOutputSchema } from "@effected/mcp";
|
|
2
2
|
import { Effect, Option, Schema } from "effect";
|
|
3
|
-
import { DataReader } from "@vitest-agent/engine";
|
|
4
3
|
import { Tool } from "effect/ai";
|
|
4
|
+
import { DataReader } from "@vitest-agent/engine";
|
|
5
5
|
|
|
6
6
|
// src/tools/configure.ts
|
|
7
7
|
const SettingsRowSchema = Schema.Struct({
|
package/tools/coverage.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ToolOutputSchema } from "@effected/mcp";
|
|
2
2
|
import { Effect, Option, Schema } from "effect";
|
|
3
|
-
import { DataReader } from "@vitest-agent/engine";
|
|
4
3
|
import { Tool } from "effect/ai";
|
|
4
|
+
import { DataReader } from "@vitest-agent/engine";
|
|
5
5
|
import { CoverageReport } from "@vitest-agent/sdk";
|
|
6
6
|
|
|
7
7
|
// src/tools/coverage.ts
|
package/tools/errors.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Effect, Schema } from "effect";
|
|
2
|
-
import { DataReader } from "@vitest-agent/engine";
|
|
3
2
|
import { Tool } from "effect/ai";
|
|
3
|
+
import { DataReader } from "@vitest-agent/engine";
|
|
4
4
|
|
|
5
5
|
// src/tools/errors.ts
|
|
6
6
|
/** One annotation attached to a failing test, surfaced with its error. */
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ToolOutputSchema } from "@effected/mcp";
|
|
2
2
|
import { Effect, Option, Schema } from "effect";
|
|
3
|
-
import { DataReader } from "@vitest-agent/engine";
|
|
4
3
|
import { Tool } from "effect/ai";
|
|
4
|
+
import { DataReader } from "@vitest-agent/engine";
|
|
5
5
|
|
|
6
6
|
// src/tools/failure-signature-get.ts
|
|
7
7
|
const RecentError = Schema.Struct({
|
package/tools/file-coverage.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ToolOutputSchema } from "@effected/mcp";
|
|
2
2
|
import { Effect, Option, Schema } from "effect";
|
|
3
|
-
import { DataReader } from "@vitest-agent/engine";
|
|
4
3
|
import { Tool } from "effect/ai";
|
|
4
|
+
import { DataReader } from "@vitest-agent/engine";
|
|
5
5
|
import { CoverageTotals, FileCoverageReport } from "@vitest-agent/sdk";
|
|
6
6
|
|
|
7
7
|
// src/tools/file-coverage.ts
|
package/tools/history.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Effect, Schema } from "effect";
|
|
2
|
-
import { DataReader } from "@vitest-agent/engine";
|
|
3
2
|
import { Tool } from "effect/ai";
|
|
3
|
+
import { DataReader } from "@vitest-agent/engine";
|
|
4
4
|
import { HistoryRecord, StrayOutput } from "@vitest-agent/sdk";
|
|
5
5
|
|
|
6
6
|
// src/tools/history.ts
|
package/tools/hypothesis.js
CHANGED
|
@@ -2,8 +2,8 @@ import { McpSession } from "../session.js";
|
|
|
2
2
|
import { IdempotentReplayMarker } from "../utils/replay-marker.js";
|
|
3
3
|
import { McpToolkit, ToolOutputSchema, ToolRefusal } from "@effected/mcp";
|
|
4
4
|
import { Effect, Match, Option, Schema } from "effect";
|
|
5
|
-
import { DataReader, DataStore } from "@vitest-agent/engine";
|
|
6
5
|
import { Tool } from "effect/ai";
|
|
6
|
+
import { DataReader, DataStore } from "@vitest-agent/engine";
|
|
7
7
|
|
|
8
8
|
// src/tools/hypothesis.ts
|
|
9
9
|
const HypothesisRowSchema = Schema.Struct({
|
package/tools/inventory.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { collectProjectRows, resolveProjectTargets } from "./_project-groups.js";
|
|
2
2
|
import { McpToolkit, ToolOutputSchema, ToolRefusal } from "@effected/mcp";
|
|
3
3
|
import { Effect, Match, Option, Schema } from "effect";
|
|
4
|
-
import { DataReader } from "@vitest-agent/engine";
|
|
5
4
|
import { Tool } from "effect/ai";
|
|
5
|
+
import { DataReader } from "@vitest-agent/engine";
|
|
6
6
|
|
|
7
7
|
// src/tools/inventory.ts
|
|
8
8
|
const ProjectRunSummary = Schema.Struct({
|
package/tools/note.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { McpToolkit, ToolOutputSchema, ToolRefusal } from "@effected/mcp";
|
|
2
2
|
import { Effect, Match, Option, Schema } from "effect";
|
|
3
|
-
import { DataReader, DataStore } from "@vitest-agent/engine";
|
|
4
3
|
import { Tool } from "effect/ai";
|
|
4
|
+
import { DataReader, DataStore } from "@vitest-agent/engine";
|
|
5
5
|
|
|
6
6
|
// src/tools/note.ts
|
|
7
7
|
const NoteScope = Schema.Literals([
|