@vitest-agent/mcp 3.0.3 → 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
package/tools/tdd-task.js
CHANGED
|
@@ -1,20 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { RenderText } from "../annotations.js";
|
|
2
|
+
import { IdempotentReplayMarker } from "../utils/replay-marker.js";
|
|
2
3
|
import { Effect, Match, Option, Schema, SchemaGetter } from "effect";
|
|
3
|
-
import { DataReader, DataStore
|
|
4
|
+
import { DataReader, DataStore } from "@vitest-agent/engine";
|
|
5
|
+
import { Tool } from "effect/unstable/ai";
|
|
6
|
+
import { GoalDetail } from "@vitest-agent/sdk";
|
|
4
7
|
|
|
5
8
|
//#region src/tools/tdd-task.ts
|
|
6
|
-
/**
|
|
7
|
-
* Consolidated `tdd_task` MCP tool — Schema-driven implementation.
|
|
8
|
-
*
|
|
9
|
-
* `start` and `end` mutate; `get` and `resume` read. Every action
|
|
10
|
-
* now returns a structured payload — `get` carries the full nested
|
|
11
|
-
* `TddTaskDetail` tree plus the `currentPhase` lookup, and `resume`
|
|
12
|
-
* carries a compact summary discriminated by `phaseAvailable`. The
|
|
13
|
-
* boundary in server.ts uses `formatTddTaskMarkdown` to render
|
|
14
|
-
* `get` / `resume` text.
|
|
15
|
-
*
|
|
16
|
-
* @packageDocumentation
|
|
17
|
-
*/
|
|
18
9
|
const TddPhaseRow = Schema.Struct({
|
|
19
10
|
id: Schema.Number,
|
|
20
11
|
behaviorId: Schema.NullOr(Schema.Number),
|
|
@@ -53,7 +44,8 @@ const TddTaskStartOk = Schema.Struct({
|
|
|
53
44
|
action: Schema.Literal("start"),
|
|
54
45
|
tddTaskId: Schema.Number,
|
|
55
46
|
goal: Schema.String,
|
|
56
|
-
runId: Schema.optional(Schema.String)
|
|
47
|
+
runId: Schema.optional(Schema.String),
|
|
48
|
+
...IdempotentReplayMarker
|
|
57
49
|
}).annotate({ identifier: "TddTaskStartOk" });
|
|
58
50
|
const TddTaskEndOk = Schema.Struct({
|
|
59
51
|
action: Schema.Literal("end"),
|
|
@@ -62,7 +54,8 @@ const TddTaskEndOk = Schema.Struct({
|
|
|
62
54
|
"succeeded",
|
|
63
55
|
"blocked",
|
|
64
56
|
"abandoned"
|
|
65
|
-
])
|
|
57
|
+
]),
|
|
58
|
+
...IdempotentReplayMarker
|
|
66
59
|
}).annotate({ identifier: "TddTaskEndOk" });
|
|
67
60
|
const TddTaskGetFound = Schema.Struct({
|
|
68
61
|
action: Schema.Literal("get"),
|
|
@@ -162,32 +155,37 @@ const TddTaskAsMarkdown = TddTaskResult.pipe(Schema.decodeTo(Schema.String, {
|
|
|
162
155
|
encode: SchemaGetter.forbidden(() => "TddTaskAsMarkdown is one-way.")
|
|
163
156
|
}));
|
|
164
157
|
const StartVariant = Schema.Struct({
|
|
165
|
-
action: Schema.Literal("start"),
|
|
166
|
-
goal: Schema.String,
|
|
167
|
-
sessionId: Schema.
|
|
168
|
-
chatId: Schema.
|
|
169
|
-
parentTddTaskId: Schema.
|
|
170
|
-
startedAt: Schema.
|
|
171
|
-
runId: Schema.
|
|
158
|
+
action: Schema.Literal("start").annotate({ description: "Lifecycle discriminator" }),
|
|
159
|
+
goal: Schema.String.annotate({ description: "start: goal text" }),
|
|
160
|
+
sessionId: Schema.optionalKey(Schema.Finite).annotate({ description: "start: sessions.id (alternative to chatId)" }),
|
|
161
|
+
chatId: Schema.optionalKey(Schema.String).annotate({ description: "start: host chat UUID" }),
|
|
162
|
+
parentTddTaskId: Schema.optionalKey(Schema.Finite).annotate({ description: "start: parent task id when decomposing" }),
|
|
163
|
+
startedAt: Schema.optionalKey(Schema.String),
|
|
164
|
+
runId: Schema.optionalKey(Schema.String)
|
|
172
165
|
});
|
|
173
166
|
const EndVariant = Schema.Struct({
|
|
174
|
-
action: Schema.Literal("end"),
|
|
175
|
-
tddTaskId: Schema.
|
|
167
|
+
action: Schema.Literal("end").annotate({ description: "Lifecycle discriminator" }),
|
|
168
|
+
tddTaskId: Schema.Finite.annotate({ description: "end/get/resume: tdd task id" }),
|
|
176
169
|
outcome: Schema.Literals([
|
|
177
170
|
"succeeded",
|
|
178
171
|
"blocked",
|
|
179
172
|
"abandoned"
|
|
180
|
-
]),
|
|
181
|
-
summaryNoteId: Schema.
|
|
173
|
+
]).annotate({ description: "end: final outcome" }),
|
|
174
|
+
summaryNoteId: Schema.optionalKey(Schema.Finite)
|
|
182
175
|
});
|
|
183
176
|
const GetVariant = Schema.Struct({
|
|
184
|
-
action: Schema.Literal("get"),
|
|
185
|
-
tddTaskId: Schema.
|
|
177
|
+
action: Schema.Literal("get").annotate({ description: "Lifecycle discriminator" }),
|
|
178
|
+
tddTaskId: Schema.Finite.annotate({ description: "end/get/resume: tdd task id" })
|
|
186
179
|
});
|
|
187
180
|
const ResumeVariant = Schema.Struct({
|
|
188
|
-
action: Schema.Literal("resume"),
|
|
189
|
-
tddTaskId: Schema.
|
|
181
|
+
action: Schema.Literal("resume").annotate({ description: "Lifecycle discriminator" }),
|
|
182
|
+
tddTaskId: Schema.Finite.annotate({ description: "end/get/resume: tdd task id" })
|
|
190
183
|
});
|
|
184
|
+
/**
|
|
185
|
+
* The `tdd_task` tool's parameters — a union discriminated on `action`.
|
|
186
|
+
*
|
|
187
|
+
* @public
|
|
188
|
+
*/
|
|
191
189
|
const TddTaskInput = Schema.Union([
|
|
192
190
|
StartVariant,
|
|
193
191
|
EndVariant,
|
|
@@ -195,115 +193,119 @@ const TddTaskInput = Schema.Union([
|
|
|
195
193
|
ResumeVariant
|
|
196
194
|
]);
|
|
197
195
|
/**
|
|
198
|
-
*
|
|
199
|
-
*
|
|
200
|
-
*
|
|
201
|
-
*
|
|
196
|
+
* Handler for {@link tddTaskTool}. A `start` with neither `sessionId` nor
|
|
197
|
+
* `chatId`, an unknown `chatId`, or a blank `runId` fails as a defect (the
|
|
198
|
+
* `UnexpectedToolError` envelope on the wire), as the retired tRPC procedure did.
|
|
199
|
+
*
|
|
200
|
+
* @public
|
|
202
201
|
*/
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
sessionId = opt.value.id;
|
|
220
|
-
} else return yield* Effect.fail(/* @__PURE__ */ new Error("tdd_task action=start: provide sessionId or chatId"));
|
|
221
|
-
if (variant.runId !== void 0 && variant.runId.trim().length === 0) return yield* Effect.fail(/* @__PURE__ */ new Error("tdd_task action=start: runId must not be blank"));
|
|
222
|
-
return {
|
|
223
|
-
action: "start",
|
|
224
|
-
tddTaskId: yield* store.writeTddTask({
|
|
225
|
-
sessionId,
|
|
226
|
-
goal: variant.goal,
|
|
227
|
-
startedAt: variant.startedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
228
|
-
...variant.runId !== void 0 && { runId: variant.runId },
|
|
229
|
-
...variant.parentTddTaskId !== void 0 && { parentTddTaskId: variant.parentTddTaskId }
|
|
230
|
-
}),
|
|
202
|
+
const handleTddTask = (input) => Match.value(input).pipe(Match.discriminatorsExhaustive("action")({
|
|
203
|
+
start: (variant) => Effect.gen(function* () {
|
|
204
|
+
const reader = yield* DataReader;
|
|
205
|
+
const store = yield* DataStore;
|
|
206
|
+
let sessionId;
|
|
207
|
+
if (variant.sessionId !== void 0) sessionId = variant.sessionId;
|
|
208
|
+
else if (variant.chatId !== void 0) {
|
|
209
|
+
const opt = yield* reader.getSessionByChatId(variant.chatId);
|
|
210
|
+
if (Option.isNone(opt)) return yield* Effect.fail(/* @__PURE__ */ new Error(`Unknown chatId: ${variant.chatId}. Run record session-start first.`));
|
|
211
|
+
sessionId = opt.value.id;
|
|
212
|
+
} else return yield* Effect.fail(/* @__PURE__ */ new Error("tdd_task action=start: provide sessionId or chatId"));
|
|
213
|
+
if (variant.runId !== void 0 && variant.runId.trim().length === 0) return yield* Effect.fail(/* @__PURE__ */ new Error("tdd_task action=start: runId must not be blank"));
|
|
214
|
+
return {
|
|
215
|
+
action: "start",
|
|
216
|
+
tddTaskId: yield* store.writeTddTask({
|
|
217
|
+
sessionId,
|
|
231
218
|
goal: variant.goal,
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
219
|
+
startedAt: variant.startedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
220
|
+
...variant.runId !== void 0 && { runId: variant.runId },
|
|
221
|
+
...variant.parentTddTaskId !== void 0 && { parentTddTaskId: variant.parentTddTaskId }
|
|
222
|
+
}),
|
|
223
|
+
goal: variant.goal,
|
|
224
|
+
...variant.runId !== void 0 && { runId: variant.runId }
|
|
225
|
+
};
|
|
226
|
+
}),
|
|
227
|
+
end: (variant) => Effect.gen(function* () {
|
|
228
|
+
yield* (yield* DataStore).endTddTask({
|
|
229
|
+
id: variant.tddTaskId,
|
|
230
|
+
outcome: variant.outcome,
|
|
231
|
+
endedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
232
|
+
...variant.summaryNoteId !== void 0 && { summaryNoteId: variant.summaryNoteId }
|
|
233
|
+
});
|
|
234
|
+
return {
|
|
235
|
+
action: "end",
|
|
236
|
+
tddTaskId: variant.tddTaskId,
|
|
237
|
+
outcome: variant.outcome
|
|
238
|
+
};
|
|
239
|
+
}),
|
|
240
|
+
get: (variant) => Effect.gen(function* () {
|
|
241
|
+
const reader = yield* DataReader;
|
|
242
|
+
const opt = yield* reader.getTddTaskById(variant.tddTaskId);
|
|
243
|
+
if (Option.isNone(opt)) return {
|
|
244
|
+
action: "get",
|
|
245
|
+
found: false,
|
|
246
|
+
tddTaskId: variant.tddTaskId
|
|
247
|
+
};
|
|
248
|
+
const currentOpt = yield* reader.getCurrentTddPhase(variant.tddTaskId);
|
|
249
|
+
const { id, ...rest } = opt.value;
|
|
250
|
+
return {
|
|
251
|
+
action: "get",
|
|
252
|
+
found: true,
|
|
253
|
+
task: {
|
|
254
|
+
tddTaskId: id,
|
|
255
|
+
...rest
|
|
256
|
+
},
|
|
257
|
+
currentPhase: Option.match(currentOpt, {
|
|
258
|
+
onNone: () => null,
|
|
259
|
+
onSome: (p) => ({
|
|
260
|
+
id: p.id,
|
|
261
|
+
phase: p.phase,
|
|
262
|
+
startedAt: p.startedAt,
|
|
263
|
+
behaviorId: p.behaviorId
|
|
273
264
|
})
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
})
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
}
|
|
305
|
-
})
|
|
306
|
-
});
|
|
265
|
+
})
|
|
266
|
+
};
|
|
267
|
+
}),
|
|
268
|
+
resume: (variant) => Effect.gen(function* () {
|
|
269
|
+
const reader = yield* DataReader;
|
|
270
|
+
const tddOpt = yield* reader.getTddTaskById(variant.tddTaskId);
|
|
271
|
+
if (Option.isNone(tddOpt)) return {
|
|
272
|
+
action: "resume",
|
|
273
|
+
found: false,
|
|
274
|
+
tddTaskId: variant.tddTaskId
|
|
275
|
+
};
|
|
276
|
+
const tdd = tddOpt.value;
|
|
277
|
+
const currentOpt = yield* reader.getCurrentTddPhase(variant.tddTaskId);
|
|
278
|
+
return {
|
|
279
|
+
action: "resume",
|
|
280
|
+
found: true,
|
|
281
|
+
tddTaskId: tdd.id,
|
|
282
|
+
goal: tdd.goal,
|
|
283
|
+
status: tdd.outcome ?? "in progress",
|
|
284
|
+
currentPhase: Option.match(currentOpt, {
|
|
285
|
+
onNone: () => null,
|
|
286
|
+
onSome: (p) => ({
|
|
287
|
+
id: p.id,
|
|
288
|
+
phase: p.phase,
|
|
289
|
+
startedAt: p.startedAt,
|
|
290
|
+
behaviorId: p.behaviorId
|
|
291
|
+
})
|
|
292
|
+
}),
|
|
293
|
+
phasesRecorded: tdd.phases.length,
|
|
294
|
+
artifactsRecorded: tdd.artifacts.length
|
|
295
|
+
};
|
|
296
|
+
})
|
|
297
|
+
})).pipe(Effect.orDie);
|
|
298
|
+
/**
|
|
299
|
+
* The Effect-native `tdd_task` tool.
|
|
300
|
+
*
|
|
301
|
+
* @public
|
|
302
|
+
*/
|
|
303
|
+
const tddTaskTool = Tool.make("tdd_task", {
|
|
304
|
+
description: "Use to manage a TDD task lifecycle, with an action discriminator: action='start' (goal, sessionId|chatId, parentTddTaskId?, startedAt?, runId?) opens a new task; action='end' (tddTaskId, outcome, summaryNoteId?) closes one; action='get' (tddTaskId) returns markdown details; action='resume' (tddTaskId) returns a compact digest.",
|
|
305
|
+
parameters: TddTaskInput,
|
|
306
|
+
success: TddTaskResult,
|
|
307
|
+
dependencies: [DataReader, DataStore]
|
|
308
|
+
}).annotate(Tool.Title, "TDD task").annotate(Tool.Readonly, false).annotate(Tool.Destructive, false).annotate(Tool.OpenWorld, false).annotate(Tool.Idempotent, true).annotate(RenderText, (encoded) => formatTddTaskMarkdown(encoded));
|
|
307
309
|
|
|
308
310
|
//#endregion
|
|
309
|
-
export {
|
|
311
|
+
export { TddTaskInput, TddTaskResult, formatTddTaskMarkdown, handleTddTask, tddTaskTool };
|