@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.
Files changed (54) hide show
  1. package/README.md +7 -6
  2. package/annotations.js +18 -0
  3. package/bin/vitest-agent-mcp.js +5 -140
  4. package/{middleware/idempotency.js → idempotency.js} +48 -49
  5. package/index.d.ts +5610 -1549
  6. package/index.js +35 -17
  7. package/main.d.ts +31 -0
  8. package/main.js +144 -0
  9. package/package.json +9 -6
  10. package/prompts/layer.js +108 -0
  11. package/register-toolkit.js +311 -0
  12. package/server.js +24 -894
  13. package/{context.js → session.js} +38 -22
  14. package/toolkit.js +86 -0
  15. package/tools/acceptance-metrics.js +26 -14
  16. package/tools/cache-health.js +28 -17
  17. package/tools/commit-changes.js +33 -10
  18. package/tools/configure.js +33 -10
  19. package/tools/coverage.js +34 -5
  20. package/tools/errors.js +36 -26
  21. package/tools/failure-signature-get.js +33 -10
  22. package/tools/file-coverage.js +37 -13
  23. package/tools/help.js +45 -4
  24. package/tools/history.js +39 -19
  25. package/tools/hypothesis.js +118 -102
  26. package/tools/inventory.js +149 -146
  27. package/tools/note.js +131 -113
  28. package/tools/overview.js +33 -10
  29. package/tools/ping.js +22 -10
  30. package/tools/register-agent.js +88 -62
  31. package/tools/run-tests.js +76 -23
  32. package/tools/settings-list.js +27 -10
  33. package/tools/status.js +35 -10
  34. package/tools/tdd-artifact.js +37 -22
  35. package/tools/tdd-behavior.js +120 -108
  36. package/tools/tdd-goal.js +98 -85
  37. package/tools/tdd-phase-transition-request.js +201 -166
  38. package/tools/tdd-progress-push.js +102 -0
  39. package/tools/tdd-task.js +140 -138
  40. package/tools/test.js +152 -138
  41. package/tools/trends.js +37 -18
  42. package/tools/triage-brief.js +34 -15
  43. package/tools/turn-search.js +39 -16
  44. package/tools/wrapup-prompt.js +37 -17
  45. package/utils/crash-guards.js +0 -22
  46. package/utils/replay-marker.js +12 -0
  47. package/utils/safe-format-fatal-error.js +0 -16
  48. package/utils/tool-error-envelope.js +3 -3
  49. package/version.js +13 -0
  50. package/layers/McpLive.js +0 -29
  51. package/prompts/index.js +0 -89
  52. package/router.js +0 -74
  53. package/session-env.js +0 -112
  54. package/utils/effect-to-zod.js +0 -158
@@ -1,20 +1,9 @@
1
- import { publicProcedure } from "../context.js";
1
+ import { RenderText } from "../annotations.js";
2
2
  import { Effect, Schema, SchemaGetter } from "effect";
3
- import { DataReader } from "@vitest-agent/sdk";
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.Number,
91
- artifactKind: Schema.optional(ArtifactKindSchema),
92
- phaseId: Schema.optional(Schema.Number),
93
- behaviorId: Schema.optional(Schema.Number),
94
- limit: Schema.optional(Schema.Number)
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
- const tddArtifactList = publicProcedure.input(Schema.toStandardSchemaV1(TddArtifactListInput)).query(async ({ ctx, input }) => ctx.runtime.runPromise(Effect.gen(function* () {
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 { TddArtifactListAsMarkdown, TddArtifactListResult, formatTddArtifactListMarkdown, tddArtifactList };
132
+ export { TddArtifactListInput, TddArtifactListResult, formatTddArtifactListMarkdown, handleTddArtifactList, tddArtifactListTool };
@@ -1,7 +1,9 @@
1
- import { idempotentProcedure } from "../middleware/idempotency.js";
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 { BehaviorDetail, BehaviorRow, DataReader, DataStore } from "@vitest-agent/sdk";
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
- message: Schema.String,
27
- remediation: Schema.optional(Schema.String)
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.Number,
88
+ action: Schema.Literal("create").annotate({ description: "CRUD discriminator" }),
89
+ goalId: Schema.Finite,
84
90
  behavior: Schema.String,
85
- suggestedTestName: Schema.optional(Schema.String),
86
- dependsOnBehaviorIds: Schema.optional(Schema.Array(Schema.Number))
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.Number,
91
- behavior: Schema.optional(Schema.String),
92
- suggestedTestName: Schema.optional(Schema.NullOr(Schema.String)),
93
- status: Schema.optional(BehaviorStatus),
94
- dependsOnBehaviorIds: Schema.optional(Schema.Array(Schema.Number))
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.Number
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.Number
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.Number
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.Number
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
- * Single source of truth for the `tdd_behavior` tool's `action`
122
- * discriminant, consumed by `server.ts`'s served `z.enum(...)` so the
123
- * MCP-SDK-side registration cannot drift from this tRPC input union
124
- * (issue #335).
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 TDD_BEHAVIOR_ACTIONS = [
127
- "create",
128
- "update",
129
- "delete",
130
- "get",
131
- "list_by_goal",
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
- behaviors
191
- };
192
- })),
193
- list_by_tdd_task: (variant) => catchTddErrorsAsEnvelope(Effect.gen(function* () {
194
- const store = yield* DataStore;
195
- const reader = yield* DataReader;
196
- yield* store.listBehaviorsByTddTask(variant.tddTaskId);
197
- const behaviors = yield* reader.getBehaviorsByTddTask(variant.tddTaskId);
198
- return {
199
- ok: true,
200
- action: "list_by_tdd_task",
201
- tddTaskId: variant.tddTaskId,
202
- behaviors
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 { TDD_BEHAVIOR_ACTIONS, TddBehaviorResult, tddBehavior };
221
+ export { TddBehaviorInput, TddBehaviorResult, handleTddBehavior, tddBehaviorTool };
package/tools/tdd-goal.js CHANGED
@@ -1,7 +1,9 @@
1
- import { idempotentProcedure } from "../middleware/idempotency.js";
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, GoalDetail, GoalRow } from "@vitest-agent/sdk";
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
- message: Schema.String,
27
- remediation: Schema.optional(Schema.String).annotate({ description: "Suggested next action when known." })
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.Number,
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.Number,
82
- goal: Schema.optional(Schema.String),
83
- status: Schema.optional(GoalStatus)
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.Number
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.Number
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.Number
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
- * Single source of truth for the `tdd_goal` tool's `action`
106
- * discriminant, consumed by `server.ts`'s served `z.enum(...)` so the
107
- * MCP-SDK-side registration cannot drift from this tRPC input union
108
- * (issue #335).
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 TDD_GOAL_ACTIONS = [
111
- "create",
112
- "update",
113
- "delete",
114
- "get",
115
- "list"
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
- goals
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 { TDD_GOAL_ACTIONS, TddGoalResult, tddGoal };
189
+ export { TddGoalInput, TddGoalResult, handleTddGoal, tddGoalTool };