@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
package/tools/tdd-artifact.js
CHANGED
|
@@ -1,20 +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/tdd-artifact.ts
|
|
6
|
-
/**
|
|
7
|
-
* `tdd_artifact_list` MCP tool — Schema-driven implementation.
|
|
8
|
-
*
|
|
9
|
-
* Returns the artifacts recorded for a TDD task, ordered with the
|
|
10
|
-
* most recent first. The structuredContent payload carries
|
|
11
|
-
* tddTaskId, the applied filters, the count, and the artifact
|
|
12
|
-
* rows so the orchestrator can extract artifact ids without parsing
|
|
13
|
-
* markdown. The legacy `format` input was dropped because
|
|
14
|
-
* structuredContent supersedes it.
|
|
15
|
-
*
|
|
16
|
-
* @packageDocumentation
|
|
17
|
-
*/
|
|
18
7
|
const ArtifactKindSchema = Schema.Literals([
|
|
19
8
|
"test_written",
|
|
20
9
|
"test_failed_run",
|
|
@@ -53,6 +42,11 @@ const ArtifactFilters = Schema.Struct({
|
|
|
53
42
|
phaseId: Schema.optional(Schema.Number),
|
|
54
43
|
behaviorId: Schema.optional(Schema.Number)
|
|
55
44
|
}).annotate({ identifier: "TddArtifactFilters" });
|
|
45
|
+
/**
|
|
46
|
+
* The `tdd_artifact_list` tool's success payload.
|
|
47
|
+
*
|
|
48
|
+
* @public
|
|
49
|
+
*/
|
|
56
50
|
const TddArtifactListResult = Schema.Struct({
|
|
57
51
|
tddTaskId: Schema.Number,
|
|
58
52
|
filters: ArtifactFilters,
|
|
@@ -86,14 +80,24 @@ const TddArtifactListAsMarkdown = TddArtifactListResult.pipe(Schema.decodeTo(Sch
|
|
|
86
80
|
decode: SchemaGetter.transform((data) => formatTddArtifactListMarkdown(data)),
|
|
87
81
|
encode: SchemaGetter.forbidden(() => "TddArtifactListAsMarkdown is one-way.")
|
|
88
82
|
}));
|
|
83
|
+
/**
|
|
84
|
+
* The `tdd_artifact_list` tool's parameters.
|
|
85
|
+
*
|
|
86
|
+
* @public
|
|
87
|
+
*/
|
|
89
88
|
const TddArtifactListInput = Schema.Struct({
|
|
90
|
-
tddTaskId: Schema.
|
|
91
|
-
artifactKind: Schema.
|
|
92
|
-
phaseId: Schema.
|
|
93
|
-
behaviorId: Schema.
|
|
94
|
-
limit: Schema.
|
|
89
|
+
tddTaskId: Schema.Finite.annotate({ description: "tdd_tasks.id" }),
|
|
90
|
+
artifactKind: Schema.optionalKey(ArtifactKindSchema).annotate({ description: "Restrict to one artifact kind" }),
|
|
91
|
+
phaseId: Schema.optionalKey(Schema.Finite).annotate({ description: "Restrict to artifacts recorded in one phase" }),
|
|
92
|
+
behaviorId: Schema.optionalKey(Schema.Finite).annotate({ description: "Restrict to artifacts recorded in phases bound to one behavior" }),
|
|
93
|
+
limit: Schema.optionalKey(Schema.Finite).annotate({ description: "Max rows (default 50)" })
|
|
95
94
|
});
|
|
96
|
-
|
|
95
|
+
/**
|
|
96
|
+
* Handler for {@link tddArtifactListTool}.
|
|
97
|
+
*
|
|
98
|
+
* @public
|
|
99
|
+
*/
|
|
100
|
+
const handleTddArtifactList = (input) => Effect.gen(function* () {
|
|
97
101
|
const rows = yield* (yield* DataReader).listTddArtifactsForTask({
|
|
98
102
|
tddTaskId: input.tddTaskId,
|
|
99
103
|
...input.artifactKind !== void 0 && { artifactKind: input.artifactKind },
|
|
@@ -111,7 +115,18 @@ const tddArtifactList = publicProcedure.input(Schema.toStandardSchemaV1(TddArtif
|
|
|
111
115
|
count: rows.length,
|
|
112
116
|
artifacts: rows
|
|
113
117
|
};
|
|
114
|
-
}))
|
|
118
|
+
}).pipe(Effect.orDie);
|
|
119
|
+
/**
|
|
120
|
+
* The Effect-native `tdd_artifact_list` tool.
|
|
121
|
+
*
|
|
122
|
+
* @public
|
|
123
|
+
*/
|
|
124
|
+
const tddArtifactListTool = Tool.make("tdd_artifact_list", {
|
|
125
|
+
description: "Use when you need the artifact id to cite in tdd_phase_transition_request without querying SQLite directly. Lists TDD artifacts (test_written, test_failed_run, code_written, test_passed_run, refactor, test_weakened) for a tdd_task, newest first. Filters: artifactKind, phaseId, behaviorId, limit (default 50).",
|
|
126
|
+
parameters: TddArtifactListInput,
|
|
127
|
+
success: TddArtifactListResult,
|
|
128
|
+
dependencies: [DataReader]
|
|
129
|
+
}).annotate(Tool.Title, "TDD artifact list").annotate(Tool.Readonly, true).annotate(Tool.Destructive, false).annotate(Tool.OpenWorld, false).annotate(Tool.Idempotent, true).annotate(RenderText, (encoded) => formatTddArtifactListMarkdown(encoded));
|
|
115
130
|
|
|
116
131
|
//#endregion
|
|
117
|
-
export {
|
|
132
|
+
export { TddArtifactListInput, TddArtifactListResult, formatTddArtifactListMarkdown, handleTddArtifactList, tddArtifactListTool };
|
package/tools/tdd-behavior.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { IdempotentReplayMarker } from "../utils/replay-marker.js";
|
|
2
2
|
import { catchTddErrorsAsEnvelope } from "./_tdd-error-envelope.js";
|
|
3
3
|
import { Effect, Match, Option, Schema } from "effect";
|
|
4
|
-
import {
|
|
4
|
+
import { DataReader, DataStore } from "@vitest-agent/engine";
|
|
5
|
+
import { Tool } from "effect/unstable/ai";
|
|
6
|
+
import { BehaviorDetail, BehaviorRow } from "@vitest-agent/sdk";
|
|
5
7
|
|
|
6
8
|
//#region src/tools/tdd-behavior.ts
|
|
7
9
|
/**
|
|
@@ -23,14 +25,18 @@ const TddBehaviorErrorEnvelope = Schema.Struct({
|
|
|
23
25
|
ok: Schema.Literal(false).annotate({ description: "Discriminant — `false` when a tagged TDD error was caught." }),
|
|
24
26
|
error: Schema.StructWithRest(Schema.Struct({
|
|
25
27
|
_tag: Schema.String.annotate({ description: "Tagged error name (e.g. BehaviorNotFoundError, GoalNotFoundError)." }),
|
|
26
|
-
|
|
27
|
-
|
|
28
|
+
remediation: Schema.Struct({
|
|
29
|
+
suggestedTool: Schema.String,
|
|
30
|
+
suggestedArgs: Schema.Record(Schema.String, Schema.Unknown),
|
|
31
|
+
humanHint: Schema.String
|
|
32
|
+
}).annotate({ description: "Suggested next action: the tool to call, its arguments, and a plain-language hint." })
|
|
28
33
|
}), [Schema.Record(Schema.String, Schema.Unknown)])
|
|
29
34
|
}).annotate({ identifier: "TddBehaviorErrorEnvelope" });
|
|
30
35
|
const TddBehaviorCreateOk = Schema.Struct({
|
|
31
36
|
ok: Schema.Literal(true),
|
|
32
37
|
action: Schema.Literal("create"),
|
|
33
|
-
behavior: BehaviorRow.annotate({ description: "Newly inserted behavior row." })
|
|
38
|
+
behavior: BehaviorRow.annotate({ description: "Newly inserted behavior row." }),
|
|
39
|
+
...IdempotentReplayMarker
|
|
34
40
|
});
|
|
35
41
|
const TddBehaviorUpdateOk = Schema.Struct({
|
|
36
42
|
ok: Schema.Literal(true),
|
|
@@ -79,36 +85,41 @@ const TddBehaviorResult = Schema.Union([
|
|
|
79
85
|
description: "Discriminate on `action` (or `ok=false` for the tagged-error envelope)."
|
|
80
86
|
});
|
|
81
87
|
const CreateVariant = Schema.Struct({
|
|
82
|
-
action: Schema.Literal("create"),
|
|
83
|
-
goalId: Schema.
|
|
88
|
+
action: Schema.Literal("create").annotate({ description: "CRUD discriminator" }),
|
|
89
|
+
goalId: Schema.Finite,
|
|
84
90
|
behavior: Schema.String,
|
|
85
|
-
suggestedTestName: Schema.
|
|
86
|
-
dependsOnBehaviorIds: Schema.
|
|
91
|
+
suggestedTestName: Schema.optionalKey(Schema.String),
|
|
92
|
+
dependsOnBehaviorIds: Schema.optionalKey(Schema.Array(Schema.Finite))
|
|
87
93
|
});
|
|
88
94
|
const UpdateVariant = Schema.Struct({
|
|
89
|
-
action: Schema.Literal("update"),
|
|
90
|
-
id: Schema.
|
|
91
|
-
behavior: Schema.
|
|
92
|
-
suggestedTestName: Schema.
|
|
93
|
-
status: Schema.
|
|
94
|
-
dependsOnBehaviorIds: Schema.
|
|
95
|
+
action: Schema.Literal("update").annotate({ description: "CRUD discriminator" }),
|
|
96
|
+
id: Schema.Finite,
|
|
97
|
+
behavior: Schema.optionalKey(Schema.String),
|
|
98
|
+
suggestedTestName: Schema.optionalKey(Schema.NullOr(Schema.String)),
|
|
99
|
+
status: Schema.optionalKey(BehaviorStatus),
|
|
100
|
+
dependsOnBehaviorIds: Schema.optionalKey(Schema.Array(Schema.Finite))
|
|
95
101
|
});
|
|
96
102
|
const DeleteVariant = Schema.Struct({
|
|
97
|
-
action: Schema.Literal("delete"),
|
|
98
|
-
id: Schema.
|
|
103
|
+
action: Schema.Literal("delete").annotate({ description: "CRUD discriminator" }),
|
|
104
|
+
id: Schema.Finite
|
|
99
105
|
});
|
|
100
106
|
const GetVariant = Schema.Struct({
|
|
101
|
-
action: Schema.Literal("get"),
|
|
102
|
-
id: Schema.
|
|
107
|
+
action: Schema.Literal("get").annotate({ description: "CRUD discriminator" }),
|
|
108
|
+
id: Schema.Finite
|
|
103
109
|
});
|
|
104
110
|
const ListByGoalVariant = Schema.Struct({
|
|
105
|
-
action: Schema.Literal("list_by_goal"),
|
|
106
|
-
goalId: Schema.
|
|
111
|
+
action: Schema.Literal("list_by_goal").annotate({ description: "CRUD discriminator" }),
|
|
112
|
+
goalId: Schema.Finite
|
|
107
113
|
});
|
|
108
114
|
const ListByTddTaskVariant = Schema.Struct({
|
|
109
|
-
action: Schema.Literal("list_by_tdd_task"),
|
|
110
|
-
tddTaskId: Schema.
|
|
115
|
+
action: Schema.Literal("list_by_tdd_task").annotate({ description: "CRUD discriminator" }),
|
|
116
|
+
tddTaskId: Schema.Finite
|
|
111
117
|
});
|
|
118
|
+
/**
|
|
119
|
+
* The `tdd_behavior` tool's parameters — a union discriminated on `action`.
|
|
120
|
+
*
|
|
121
|
+
* @public
|
|
122
|
+
*/
|
|
112
123
|
const TddBehaviorInput = Schema.Union([
|
|
113
124
|
CreateVariant,
|
|
114
125
|
UpdateVariant,
|
|
@@ -118,92 +129,93 @@ const TddBehaviorInput = Schema.Union([
|
|
|
118
129
|
ListByTddTaskVariant
|
|
119
130
|
]);
|
|
120
131
|
/**
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
132
|
+
* Handler for {@link tddBehaviorTool}. The five tagged TDD errors come
|
|
133
|
+
* back as the `{ ok: false, error }` envelope; anything else is a defect.
|
|
134
|
+
*
|
|
135
|
+
* @public
|
|
125
136
|
*/
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
"list_by_tdd_task"
|
|
133
|
-
];
|
|
134
|
-
const tddBehavior = idempotentProcedure.input(Schema.toStandardSchemaV1(TddBehaviorInput)).mutation(async ({ ctx, input }) => {
|
|
135
|
-
return ctx.runtime.runPromise(Match.value(input).pipe(Match.discriminatorsExhaustive("action")({
|
|
136
|
-
create: (variant) => catchTddErrorsAsEnvelope(Effect.gen(function* () {
|
|
137
|
-
return {
|
|
138
|
-
ok: true,
|
|
139
|
-
action: "create",
|
|
140
|
-
behavior: yield* (yield* DataStore).createBehavior({
|
|
141
|
-
goalId: variant.goalId,
|
|
142
|
-
behavior: variant.behavior,
|
|
143
|
-
...variant.suggestedTestName !== void 0 && { suggestedTestName: variant.suggestedTestName },
|
|
144
|
-
...variant.dependsOnBehaviorIds !== void 0 && { dependsOnBehaviorIds: variant.dependsOnBehaviorIds }
|
|
145
|
-
})
|
|
146
|
-
};
|
|
147
|
-
})),
|
|
148
|
-
update: (variant) => catchTddErrorsAsEnvelope(Effect.gen(function* () {
|
|
149
|
-
return {
|
|
150
|
-
ok: true,
|
|
151
|
-
action: "update",
|
|
152
|
-
behavior: yield* (yield* DataStore).updateBehavior({
|
|
153
|
-
id: variant.id,
|
|
154
|
-
...variant.behavior !== void 0 && { behavior: variant.behavior },
|
|
155
|
-
...variant.suggestedTestName !== void 0 && { suggestedTestName: variant.suggestedTestName },
|
|
156
|
-
...variant.status !== void 0 && { status: variant.status },
|
|
157
|
-
...variant.dependsOnBehaviorIds !== void 0 && { dependsOnBehaviorIds: variant.dependsOnBehaviorIds }
|
|
158
|
-
})
|
|
159
|
-
};
|
|
160
|
-
})),
|
|
161
|
-
delete: (variant) => catchTddErrorsAsEnvelope(Effect.gen(function* () {
|
|
162
|
-
yield* (yield* DataStore).deleteBehavior(variant.id);
|
|
163
|
-
return {
|
|
164
|
-
ok: true,
|
|
165
|
-
action: "delete",
|
|
166
|
-
id: variant.id
|
|
167
|
-
};
|
|
168
|
-
})),
|
|
169
|
-
get: (variant) => Effect.gen(function* () {
|
|
170
|
-
const opt = yield* (yield* DataReader).getBehaviorById(variant.id);
|
|
171
|
-
return Option.isNone(opt) ? {
|
|
172
|
-
action: "get",
|
|
173
|
-
found: false,
|
|
174
|
-
id: variant.id
|
|
175
|
-
} : {
|
|
176
|
-
action: "get",
|
|
177
|
-
found: true,
|
|
178
|
-
behavior: opt.value
|
|
179
|
-
};
|
|
180
|
-
}),
|
|
181
|
-
list_by_goal: (variant) => catchTddErrorsAsEnvelope(Effect.gen(function* () {
|
|
182
|
-
const store = yield* DataStore;
|
|
183
|
-
const reader = yield* DataReader;
|
|
184
|
-
yield* store.listBehaviorsByGoal(variant.goalId);
|
|
185
|
-
const behaviors = yield* reader.getBehaviorsByGoal(variant.goalId);
|
|
186
|
-
return {
|
|
187
|
-
ok: true,
|
|
188
|
-
action: "list_by_goal",
|
|
137
|
+
const handleTddBehavior = (input) => Match.value(input).pipe(Match.discriminatorsExhaustive("action")({
|
|
138
|
+
create: (variant) => catchTddErrorsAsEnvelope(Effect.gen(function* () {
|
|
139
|
+
return {
|
|
140
|
+
ok: true,
|
|
141
|
+
action: "create",
|
|
142
|
+
behavior: yield* (yield* DataStore).createBehavior({
|
|
189
143
|
goalId: variant.goalId,
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
})
|
|
144
|
+
behavior: variant.behavior,
|
|
145
|
+
...variant.suggestedTestName !== void 0 && { suggestedTestName: variant.suggestedTestName },
|
|
146
|
+
...variant.dependsOnBehaviorIds !== void 0 && { dependsOnBehaviorIds: variant.dependsOnBehaviorIds }
|
|
147
|
+
})
|
|
148
|
+
};
|
|
149
|
+
})),
|
|
150
|
+
update: (variant) => catchTddErrorsAsEnvelope(Effect.gen(function* () {
|
|
151
|
+
return {
|
|
152
|
+
ok: true,
|
|
153
|
+
action: "update",
|
|
154
|
+
behavior: yield* (yield* DataStore).updateBehavior({
|
|
155
|
+
id: variant.id,
|
|
156
|
+
...variant.behavior !== void 0 && { behavior: variant.behavior },
|
|
157
|
+
...variant.suggestedTestName !== void 0 && { suggestedTestName: variant.suggestedTestName },
|
|
158
|
+
...variant.status !== void 0 && { status: variant.status },
|
|
159
|
+
...variant.dependsOnBehaviorIds !== void 0 && { dependsOnBehaviorIds: variant.dependsOnBehaviorIds }
|
|
160
|
+
})
|
|
161
|
+
};
|
|
162
|
+
})),
|
|
163
|
+
delete: (variant) => catchTddErrorsAsEnvelope(Effect.gen(function* () {
|
|
164
|
+
yield* (yield* DataStore).deleteBehavior(variant.id);
|
|
165
|
+
return {
|
|
166
|
+
ok: true,
|
|
167
|
+
action: "delete",
|
|
168
|
+
id: variant.id
|
|
169
|
+
};
|
|
170
|
+
})),
|
|
171
|
+
get: (variant) => Effect.gen(function* () {
|
|
172
|
+
const opt = yield* (yield* DataReader).getBehaviorById(variant.id);
|
|
173
|
+
return Option.isNone(opt) ? {
|
|
174
|
+
action: "get",
|
|
175
|
+
found: false,
|
|
176
|
+
id: variant.id
|
|
177
|
+
} : {
|
|
178
|
+
action: "get",
|
|
179
|
+
found: true,
|
|
180
|
+
behavior: opt.value
|
|
181
|
+
};
|
|
182
|
+
}),
|
|
183
|
+
list_by_goal: (variant) => catchTddErrorsAsEnvelope(Effect.gen(function* () {
|
|
184
|
+
const store = yield* DataStore;
|
|
185
|
+
const reader = yield* DataReader;
|
|
186
|
+
yield* store.listBehaviorsByGoal(variant.goalId);
|
|
187
|
+
const behaviors = yield* reader.getBehaviorsByGoal(variant.goalId);
|
|
188
|
+
return {
|
|
189
|
+
ok: true,
|
|
190
|
+
action: "list_by_goal",
|
|
191
|
+
goalId: variant.goalId,
|
|
192
|
+
behaviors
|
|
193
|
+
};
|
|
194
|
+
})),
|
|
195
|
+
list_by_tdd_task: (variant) => catchTddErrorsAsEnvelope(Effect.gen(function* () {
|
|
196
|
+
const store = yield* DataStore;
|
|
197
|
+
const reader = yield* DataReader;
|
|
198
|
+
yield* store.listBehaviorsByTddTask(variant.tddTaskId);
|
|
199
|
+
const behaviors = yield* reader.getBehaviorsByTddTask(variant.tddTaskId);
|
|
200
|
+
return {
|
|
201
|
+
ok: true,
|
|
202
|
+
action: "list_by_tdd_task",
|
|
203
|
+
tddTaskId: variant.tddTaskId,
|
|
204
|
+
behaviors
|
|
205
|
+
};
|
|
206
|
+
}))
|
|
207
|
+
})).pipe(Effect.orDie);
|
|
208
|
+
/**
|
|
209
|
+
* The Effect-native `tdd_behavior` tool.
|
|
210
|
+
*
|
|
211
|
+
* @public
|
|
212
|
+
*/
|
|
213
|
+
const tddBehaviorTool = Tool.make("tdd_behavior", {
|
|
214
|
+
description: "Use to manage TDD behaviors, with a CRUD action discriminator: action='create' (goalId, behavior, suggestedTestName?, dependsOnBehaviorIds?) is idempotent on (goalId, behavior); action='update' (id, ...patch) edits; action='delete' (id) hard-deletes; action='get' (id) reads; action='list_by_goal' (goalId) lists one goal's behaviors; action='list_by_tdd_task' (tddTaskId) lists across all goals.",
|
|
215
|
+
parameters: TddBehaviorInput,
|
|
216
|
+
success: TddBehaviorResult,
|
|
217
|
+
dependencies: [DataReader, DataStore]
|
|
218
|
+
}).annotate(Tool.Title, "TDD behavior").annotate(Tool.Readonly, false).annotate(Tool.Destructive, true).annotate(Tool.OpenWorld, false).annotate(Tool.Idempotent, false);
|
|
207
219
|
|
|
208
220
|
//#endregion
|
|
209
|
-
export {
|
|
221
|
+
export { TddBehaviorInput, TddBehaviorResult, handleTddBehavior, tddBehaviorTool };
|
package/tools/tdd-goal.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { IdempotentReplayMarker } from "../utils/replay-marker.js";
|
|
2
2
|
import { catchTddErrorsAsEnvelope } from "./_tdd-error-envelope.js";
|
|
3
3
|
import { Effect, Match, Option, Schema } from "effect";
|
|
4
|
-
import { DataReader, DataStore
|
|
4
|
+
import { DataReader, DataStore } from "@vitest-agent/engine";
|
|
5
|
+
import { Tool } from "effect/unstable/ai";
|
|
6
|
+
import { GoalDetail, GoalRow } from "@vitest-agent/sdk";
|
|
5
7
|
|
|
6
8
|
//#region src/tools/tdd-goal.ts
|
|
7
9
|
/**
|
|
@@ -23,14 +25,18 @@ const TddErrorEnvelope = Schema.Struct({
|
|
|
23
25
|
ok: Schema.Literal(false).annotate({ description: "Discriminant — `false` when a tagged TDD error was caught." }),
|
|
24
26
|
error: Schema.StructWithRest(Schema.Struct({
|
|
25
27
|
_tag: Schema.String.annotate({ description: "Tagged error name (e.g. GoalNotFoundError, TddTaskNotFoundError)." }),
|
|
26
|
-
|
|
27
|
-
|
|
28
|
+
remediation: Schema.Struct({
|
|
29
|
+
suggestedTool: Schema.String,
|
|
30
|
+
suggestedArgs: Schema.Record(Schema.String, Schema.Unknown),
|
|
31
|
+
humanHint: Schema.String
|
|
32
|
+
}).annotate({ description: "Suggested next action: the tool to call, its arguments, and a plain-language hint." })
|
|
28
33
|
}), [Schema.Record(Schema.String, Schema.Unknown)])
|
|
29
34
|
}).annotate({ identifier: "TddErrorEnvelope" });
|
|
30
35
|
const TddGoalCreateOk = Schema.Struct({
|
|
31
36
|
ok: Schema.Literal(true),
|
|
32
37
|
action: Schema.Literal("create"),
|
|
33
|
-
goal: GoalRow.annotate({ description: "Newly inserted goal row." })
|
|
38
|
+
goal: GoalRow.annotate({ description: "Newly inserted goal row." }),
|
|
39
|
+
...IdempotentReplayMarker
|
|
34
40
|
}).annotate({ identifier: "TddGoalCreateOk" });
|
|
35
41
|
const TddGoalUpdateOk = Schema.Struct({
|
|
36
42
|
ok: Schema.Literal(true),
|
|
@@ -72,28 +78,33 @@ const TddGoalResult = Schema.Union([
|
|
|
72
78
|
description: "Discriminate first on `action` (or on `ok=false` for the tagged-error envelope). Get returns `found:true|false`; create/update/delete/list return `ok:true` on success."
|
|
73
79
|
});
|
|
74
80
|
const CreateVariant = Schema.Struct({
|
|
75
|
-
action: Schema.Literal("create"),
|
|
76
|
-
tddTaskId: Schema.
|
|
81
|
+
action: Schema.Literal("create").annotate({ description: "CRUD discriminator" }),
|
|
82
|
+
tddTaskId: Schema.Finite.annotate({ description: "create/list: tdd task id" }),
|
|
77
83
|
goal: Schema.String
|
|
78
84
|
});
|
|
79
85
|
const UpdateVariant = Schema.Struct({
|
|
80
|
-
action: Schema.Literal("update"),
|
|
81
|
-
id: Schema.
|
|
82
|
-
goal: Schema.
|
|
83
|
-
status: Schema.
|
|
86
|
+
action: Schema.Literal("update").annotate({ description: "CRUD discriminator" }),
|
|
87
|
+
id: Schema.Finite.annotate({ description: "update/delete/get: goal id" }),
|
|
88
|
+
goal: Schema.optionalKey(Schema.String),
|
|
89
|
+
status: Schema.optionalKey(GoalStatus)
|
|
84
90
|
});
|
|
85
91
|
const DeleteVariant = Schema.Struct({
|
|
86
|
-
action: Schema.Literal("delete"),
|
|
87
|
-
id: Schema.
|
|
92
|
+
action: Schema.Literal("delete").annotate({ description: "CRUD discriminator" }),
|
|
93
|
+
id: Schema.Finite.annotate({ description: "update/delete/get: goal id" })
|
|
88
94
|
});
|
|
89
95
|
const GetVariant = Schema.Struct({
|
|
90
|
-
action: Schema.Literal("get"),
|
|
91
|
-
id: Schema.
|
|
96
|
+
action: Schema.Literal("get").annotate({ description: "CRUD discriminator" }),
|
|
97
|
+
id: Schema.Finite.annotate({ description: "update/delete/get: goal id" })
|
|
92
98
|
});
|
|
93
99
|
const ListVariant = Schema.Struct({
|
|
94
|
-
action: Schema.Literal("list"),
|
|
95
|
-
tddTaskId: Schema.
|
|
100
|
+
action: Schema.Literal("list").annotate({ description: "CRUD discriminator" }),
|
|
101
|
+
tddTaskId: Schema.Finite.annotate({ description: "create/list: tdd task id" })
|
|
96
102
|
});
|
|
103
|
+
/**
|
|
104
|
+
* The `tdd_goal` tool's parameters — a union discriminated on `action`.
|
|
105
|
+
*
|
|
106
|
+
* @public
|
|
107
|
+
*/
|
|
97
108
|
const TddGoalInput = Schema.Union([
|
|
98
109
|
CreateVariant,
|
|
99
110
|
UpdateVariant,
|
|
@@ -102,75 +113,77 @@ const TddGoalInput = Schema.Union([
|
|
|
102
113
|
ListVariant
|
|
103
114
|
]);
|
|
104
115
|
/**
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
116
|
+
* Handler for {@link tddGoalTool}. The five tagged TDD errors come back as
|
|
117
|
+
* the `{ ok: false, error }` envelope; anything else is a defect.
|
|
118
|
+
*
|
|
119
|
+
* @public
|
|
109
120
|
*/
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
];
|
|
117
|
-
const tddGoal = idempotentProcedure.input(Schema.toStandardSchemaV1(TddGoalInput)).mutation(async ({ ctx, input }) => {
|
|
118
|
-
return ctx.runtime.runPromise(Match.value(input).pipe(Match.discriminatorsExhaustive("action")({
|
|
119
|
-
create: (variant) => catchTddErrorsAsEnvelope(Effect.gen(function* () {
|
|
120
|
-
return {
|
|
121
|
-
ok: true,
|
|
122
|
-
action: "create",
|
|
123
|
-
goal: yield* (yield* DataStore).createGoal({
|
|
124
|
-
tddTaskId: variant.tddTaskId,
|
|
125
|
-
goal: variant.goal
|
|
126
|
-
})
|
|
127
|
-
};
|
|
128
|
-
})),
|
|
129
|
-
update: (variant) => catchTddErrorsAsEnvelope(Effect.gen(function* () {
|
|
130
|
-
return {
|
|
131
|
-
ok: true,
|
|
132
|
-
action: "update",
|
|
133
|
-
goal: yield* (yield* DataStore).updateGoal({
|
|
134
|
-
id: variant.id,
|
|
135
|
-
...variant.goal !== void 0 && { goal: variant.goal },
|
|
136
|
-
...variant.status !== void 0 && { status: variant.status }
|
|
137
|
-
})
|
|
138
|
-
};
|
|
139
|
-
})),
|
|
140
|
-
delete: (variant) => catchTddErrorsAsEnvelope(Effect.gen(function* () {
|
|
141
|
-
yield* (yield* DataStore).deleteGoal(variant.id);
|
|
142
|
-
return {
|
|
143
|
-
ok: true,
|
|
144
|
-
action: "delete",
|
|
145
|
-
id: variant.id
|
|
146
|
-
};
|
|
147
|
-
})),
|
|
148
|
-
get: (variant) => Effect.gen(function* () {
|
|
149
|
-
const opt = yield* (yield* DataReader).getGoalById(variant.id);
|
|
150
|
-
return Option.isNone(opt) ? {
|
|
151
|
-
action: "get",
|
|
152
|
-
found: false,
|
|
153
|
-
id: variant.id
|
|
154
|
-
} : {
|
|
155
|
-
action: "get",
|
|
156
|
-
found: true,
|
|
157
|
-
goal: opt.value
|
|
158
|
-
};
|
|
159
|
-
}),
|
|
160
|
-
list: (variant) => catchTddErrorsAsEnvelope(Effect.gen(function* () {
|
|
161
|
-
const store = yield* DataStore;
|
|
162
|
-
const reader = yield* DataReader;
|
|
163
|
-
yield* store.listGoalsByTddTask(variant.tddTaskId);
|
|
164
|
-
const goals = yield* reader.getGoalsByTddTask(variant.tddTaskId);
|
|
165
|
-
return {
|
|
166
|
-
ok: true,
|
|
167
|
-
action: "list",
|
|
121
|
+
const handleTddGoal = (input) => Match.value(input).pipe(Match.discriminatorsExhaustive("action")({
|
|
122
|
+
create: (variant) => catchTddErrorsAsEnvelope(Effect.gen(function* () {
|
|
123
|
+
return {
|
|
124
|
+
ok: true,
|
|
125
|
+
action: "create",
|
|
126
|
+
goal: yield* (yield* DataStore).createGoal({
|
|
168
127
|
tddTaskId: variant.tddTaskId,
|
|
169
|
-
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
}))
|
|
173
|
-
|
|
128
|
+
goal: variant.goal
|
|
129
|
+
})
|
|
130
|
+
};
|
|
131
|
+
})),
|
|
132
|
+
update: (variant) => catchTddErrorsAsEnvelope(Effect.gen(function* () {
|
|
133
|
+
return {
|
|
134
|
+
ok: true,
|
|
135
|
+
action: "update",
|
|
136
|
+
goal: yield* (yield* DataStore).updateGoal({
|
|
137
|
+
id: variant.id,
|
|
138
|
+
...variant.goal !== void 0 && { goal: variant.goal },
|
|
139
|
+
...variant.status !== void 0 && { status: variant.status }
|
|
140
|
+
})
|
|
141
|
+
};
|
|
142
|
+
})),
|
|
143
|
+
delete: (variant) => catchTddErrorsAsEnvelope(Effect.gen(function* () {
|
|
144
|
+
yield* (yield* DataStore).deleteGoal(variant.id);
|
|
145
|
+
return {
|
|
146
|
+
ok: true,
|
|
147
|
+
action: "delete",
|
|
148
|
+
id: variant.id
|
|
149
|
+
};
|
|
150
|
+
})),
|
|
151
|
+
get: (variant) => Effect.gen(function* () {
|
|
152
|
+
const opt = yield* (yield* DataReader).getGoalById(variant.id);
|
|
153
|
+
return Option.isNone(opt) ? {
|
|
154
|
+
action: "get",
|
|
155
|
+
found: false,
|
|
156
|
+
id: variant.id
|
|
157
|
+
} : {
|
|
158
|
+
action: "get",
|
|
159
|
+
found: true,
|
|
160
|
+
goal: opt.value
|
|
161
|
+
};
|
|
162
|
+
}),
|
|
163
|
+
list: (variant) => catchTddErrorsAsEnvelope(Effect.gen(function* () {
|
|
164
|
+
const store = yield* DataStore;
|
|
165
|
+
const reader = yield* DataReader;
|
|
166
|
+
yield* store.listGoalsByTddTask(variant.tddTaskId);
|
|
167
|
+
const goals = yield* reader.getGoalsByTddTask(variant.tddTaskId);
|
|
168
|
+
return {
|
|
169
|
+
ok: true,
|
|
170
|
+
action: "list",
|
|
171
|
+
tddTaskId: variant.tddTaskId,
|
|
172
|
+
goals
|
|
173
|
+
};
|
|
174
|
+
}))
|
|
175
|
+
})).pipe(Effect.orDie);
|
|
176
|
+
/**
|
|
177
|
+
* The Effect-native `tdd_goal` tool.
|
|
178
|
+
*
|
|
179
|
+
* @public
|
|
180
|
+
*/
|
|
181
|
+
const tddGoalTool = Tool.make("tdd_goal", {
|
|
182
|
+
description: "Use to manage TDD goals, with a CRUD action discriminator: action='create' (tddTaskId, goal) is idempotent on (tddTaskId, goal); action='update' (id, goal?, status?) edits text and/or lifecycle status; action='delete' (id) hard-deletes (prefer status:'abandoned'); action='get' (id) reads with nested behaviors; action='list' (tddTaskId) returns all goals for a TDD task.",
|
|
183
|
+
parameters: TddGoalInput,
|
|
184
|
+
success: TddGoalResult,
|
|
185
|
+
dependencies: [DataReader, DataStore]
|
|
186
|
+
}).annotate(Tool.Title, "TDD goal").annotate(Tool.Readonly, false).annotate(Tool.Destructive, true).annotate(Tool.OpenWorld, false).annotate(Tool.Idempotent, false);
|
|
174
187
|
|
|
175
188
|
//#endregion
|
|
176
|
-
export {
|
|
189
|
+
export { TddGoalInput, TddGoalResult, handleTddGoal, tddGoalTool };
|