@vitest-agent/mcp 5.4.0 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js CHANGED
@@ -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, withIdempotency, wrapupPromptTool };
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 = resolveInitialSessionId(process.argv, LaunchContext.isUnsubstituted);
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": "5.4.0",
3
+ "version": "6.0.0",
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": [
@@ -44,13 +44,14 @@
44
44
  },
45
45
  "dependencies": {
46
46
  "@effect/platform-node": "^4.0.6",
47
- "@effect/sql-sqlite-node": "^4.0.6",
48
- "@effected/engine": "^0.8.0",
49
- "@effected/git": "^0.25.1",
50
- "@effected/mcp": "^0.9.0",
51
- "@effected/walker": "^0.20.1",
52
- "@vitest-agent/engine": "2.1.0",
53
- "@vitest-agent/sdk": "8.0.0",
47
+ "@effected/commands": "^0.17.0",
48
+ "@effected/engine": "^0.9.0",
49
+ "@effected/git": "^0.26.0",
50
+ "@effected/glob": "^0.15.0",
51
+ "@effected/mcp": "^0.10.0",
52
+ "@effected/walker": "^0.21.0",
53
+ "@vitest-agent/engine": "3.0.0",
54
+ "@vitest-agent/sdk": "9.0.0",
54
55
  "effect": "^4.0.6"
55
56
  },
56
57
  "peerDependencies": {
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 { withIdempotency } from "./idempotency.js";
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, withIdempotency("hypothesis", handleHypothesis)),
74
- tdd_task: McpToolkit.unionHandler(tddTaskTool, withIdempotency("tdd_task", handleTddTask)),
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, withIdempotency("tdd_goal", handleTddGoal)),
77
- tdd_behavior: McpToolkit.unionHandler(tddBehaviorTool, withIdempotency("tdd_behavior", handleTddBehavior)),
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 { Effect } from "effect";
2
- import { BehaviorNotFoundError, GoalNotFoundError, IllegalStatusTransitionError, TddTaskAlreadyEndedError, TddTaskNotFoundError } from "@vitest-agent/sdk";
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
- ok: false,
7
- error: {
8
- _tag: e._tag,
9
- id: e.id,
10
- reason: e.reason,
11
- remediation: {
12
- suggestedTool: "tdd_goal",
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
- ok: false,
20
- error: {
21
- _tag: e._tag,
22
- id: e.id,
23
- reason: e.reason,
24
- remediation: {
25
- suggestedTool: "tdd_behavior",
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
- ok: false,
33
- error: {
34
- _tag: e._tag,
35
- id: e.id,
36
- reason: e.reason,
37
- remediation: {
38
- suggestedTool: "tdd_task",
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
- ok: false,
46
- error: {
47
- _tag: e._tag,
48
- id: e.id,
49
- endedAt: e.endedAt,
50
- outcome: e.outcome,
51
- remediation: {
52
- suggestedTool: "tdd_task",
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
- ok: false,
60
- error: {
61
- _tag: e._tag,
62
- entity: e.entity,
63
- id: e.id,
64
- from: e.from,
65
- to: e.to,
66
- reason: e.reason,
67
- remediation: {
68
- suggestedTool: e.entity === "goal" ? "tdd_goal" : "tdd_behavior",
69
- suggestedArgs: {
70
- action: "update",
71
- id: e.id,
72
- status: "abandoned"
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 { catchTddErrorsAsEnvelope };
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." };
@@ -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
@@ -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/commit-changes.ts
6
6
  const FileRow = Schema.Struct({
@@ -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({
@@ -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
@@ -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({
@@ -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([
package/tools/overview.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/overview.ts
7
7
  const ProjectRunSummary = Schema.Struct({