@vitest-agent/mcp 1.3.6 → 2.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.
@@ -1,46 +1,48 @@
1
1
  import { publicProcedure } from "../context.js";
2
2
  import { AgentReport, DataReader, DataStore, buildAgentReport, buildConsoleLeaks, collectConsoleLeakEntries } from "@vitest-agent/sdk";
3
- import { Effect, ParseResult, Schema } from "effect";
3
+ import { Effect, Schema, SchemaGetter } from "effect";
4
4
  import { AsyncLocalStorage } from "node:async_hooks";
5
5
  import { Writable } from "node:stream";
6
6
 
7
7
  //#region src/tools/run-tests.ts
8
8
  const RunTestsOk = Schema.Struct({
9
- kind: Schema.Literal("ok").annotations({ description: "Discriminant — `true` test run completed (with or without failures)." }),
9
+ kind: Schema.Literal("ok").annotate({ description: "Discriminant — `true` test run completed (with or without failures)." }),
10
10
  project: Schema.optional(Schema.String),
11
- report: AgentReport.annotations({ description: "Full AgentReport including pass/fail counts and per-module errors." }),
12
- classifications: Schema.Record({
13
- key: Schema.String,
14
- value: Schema.String
15
- }).annotations({ description: "Per-test classification labels: stable, new-failure, persistent, flaky, recovered." }),
16
- discoveryLastScannedAt: Schema.optional(Schema.NullOr(Schema.String)).annotations({ description: "ISO timestamp of the most recent real disk scan performed by discoverProjects() in this process (issue #100). `null`/absent means discovery has not scanned disk in this process yet (e.g. a config that doesn't call AgentPlugin.discover()). A stale-looking test count is self-explaining when compared against this value." })
17
- }).annotations({ identifier: "RunTestsOk" });
11
+ report: AgentReport.annotate({ description: "Full AgentReport including pass/fail counts and per-module errors." }),
12
+ classifications: Schema.Record(Schema.String, Schema.String).annotate({ description: "Per-test classification labels: stable, new-failure, persistent, flaky, recovered." }),
13
+ discoveryLastScannedAt: Schema.optional(Schema.NullOr(Schema.String)).annotate({ description: "ISO timestamp of the most recent real disk scan performed by discoverProjects() in this process (issue #100). `null`/absent means discovery has not scanned disk in this process yet (e.g. a config that doesn't call AgentPlugin.discover()). A stale-looking test count is self-explaining when compared against this value." })
14
+ }).annotate({ identifier: "RunTestsOk" });
18
15
  const RunTestsTimeout = Schema.Struct({
19
16
  kind: Schema.Literal("timeout"),
20
17
  timeoutSeconds: Schema.Number
21
- }).annotations({ identifier: "RunTestsTimeout" });
18
+ }).annotate({ identifier: "RunTestsTimeout" });
22
19
  const RunTestsError = Schema.Struct({
23
20
  kind: Schema.Literal("error"),
24
21
  message: Schema.String
25
- }).annotations({ identifier: "RunTestsError" });
22
+ }).annotate({ identifier: "RunTestsError" });
26
23
  const TagFilter = Schema.Struct({
27
24
  all: Schema.optional(Schema.Array(Schema.String)),
28
25
  any: Schema.optional(Schema.Array(Schema.String)),
29
26
  none: Schema.optional(Schema.Array(Schema.String))
30
- }).annotations({
27
+ }).annotate({
31
28
  identifier: "TagFilter",
32
29
  description: "All three sub-filters AND together with `project` and `files`. `all` requires every listed tag on the test. `any` requires at least one. `none` excludes any test carrying a listed tag."
33
30
  });
34
31
  const RunTestsNoMatch = Schema.Struct({
35
- kind: Schema.Literal("no-match").annotations({ description: "Discriminant — the resolved filter set matched zero test cases. Tests did not run; this is independent of passWithNoTests policy." }),
32
+ kind: Schema.Literal("no-match").annotate({ description: "Discriminant — the resolved filter set matched zero test cases. Tests did not run; this is independent of passWithNoTests policy." }),
36
33
  filter: Schema.Struct({
37
34
  project: Schema.NullOr(Schema.String),
38
35
  files: Schema.Array(Schema.String),
39
36
  tags: Schema.NullOr(TagFilter),
40
37
  resolvedExpression: Schema.NullOr(Schema.String)
41
38
  })
42
- }).annotations({ identifier: "RunTestsNoMatch" });
43
- const RunTestsResult = Schema.Union(RunTestsOk, RunTestsTimeout, RunTestsError, RunTestsNoMatch).annotations({
39
+ }).annotate({ identifier: "RunTestsNoMatch" });
40
+ const RunTestsResult = Schema.Union([
41
+ RunTestsOk,
42
+ RunTestsTimeout,
43
+ RunTestsError,
44
+ RunTestsNoMatch
45
+ ]).annotate({
44
46
  identifier: "RunTestsResult",
45
47
  title: "run_tests result",
46
48
  description: "Discriminate on `kind`. ok carries the full AgentReport plus per-test classifications; timeout / error are the two failure modes; no-match indicates that the resolved filter set matched zero test cases."
@@ -194,11 +196,10 @@ function formatNoMatchMarkdown(filter) {
194
196
  if (filter.project !== null) lines.push("- Verify the project name with `inventory({ kind: \"project\" })`");
195
197
  return lines.join("\n");
196
198
  }
197
- const RunTestsAsMarkdown = Schema.transformOrFail(RunTestsResult, Schema.String, {
198
- strict: true,
199
- decode: (data) => ParseResult.succeed(formatRunTestsMarkdown(data)),
200
- encode: (text, _options, ast) => ParseResult.fail(new ParseResult.Forbidden(ast, text, "RunTestsAsMarkdown is one-way."))
201
- });
199
+ const RunTestsAsMarkdown = RunTestsResult.pipe(Schema.decodeTo(Schema.String, {
200
+ decode: SchemaGetter.transform((data) => formatRunTestsMarkdown(data)),
201
+ encode: SchemaGetter.forbidden(() => "RunTestsAsMarkdown is one-way.")
202
+ }));
202
203
  /**
203
204
  * Format an AgentReport as concise markdown suitable for MCP tool output.
204
205
  *
@@ -270,7 +271,7 @@ function formatReportMarkdown(report, classifications) {
270
271
  }
271
272
  return lines.join("\n");
272
273
  }
273
- const runTests = publicProcedure.input(Schema.standardSchemaV1(Schema.Struct({
274
+ const runTests = publicProcedure.input(Schema.toStandardSchemaV1(Schema.Struct({
274
275
  files: Schema.optional(Schema.Array(Schema.String)),
275
276
  project: Schema.optional(Schema.String),
276
277
  tags: Schema.optional(TagFilter),
@@ -1,6 +1,6 @@
1
1
  import { publicProcedure } from "../context.js";
2
2
  import { DataReader } from "@vitest-agent/sdk";
3
- import { Effect, ParseResult, Schema } from "effect";
3
+ import { Effect, Schema, SchemaGetter } from "effect";
4
4
 
5
5
  //#region src/tools/settings-list.ts
6
6
  /**
@@ -9,13 +9,13 @@ import { Effect, ParseResult, Schema } from "effect";
9
9
  * @packageDocumentation
10
10
  */
11
11
  const SettingsRow = Schema.Struct({
12
- hash: Schema.String.annotations({ description: "Stable SHA-1 of the captured Vitest settings; FK target on test_runs." }),
13
- capturedAt: Schema.String.annotations({ description: "ISO-8601 timestamp the settings row was first written." })
14
- }).annotations({ identifier: "SettingsListRow" });
12
+ hash: Schema.String.annotate({ description: "Stable SHA-1 of the captured Vitest settings; FK target on test_runs." }),
13
+ capturedAt: Schema.String.annotate({ description: "ISO-8601 timestamp the settings row was first written." })
14
+ }).annotate({ identifier: "SettingsListRow" });
15
15
  const SettingsListResult = Schema.Struct({
16
16
  count: Schema.Number,
17
- settings: Schema.Array(SettingsRow).annotations({ description: "Distinct captured settings hashes the reporter has written, newest first." })
18
- }).annotations({
17
+ settings: Schema.Array(SettingsRow).annotate({ description: "Distinct captured settings hashes the reporter has written, newest first." })
18
+ }).annotate({
19
19
  identifier: "SettingsListResult",
20
20
  title: "settings_list result",
21
21
  description: "Roster of distinct Vitest settings hashes the reporter has captured."
@@ -31,12 +31,11 @@ const formatSettingsListMarkdown = (data) => {
31
31
  for (const s of data.settings) lines.push(`| ${s.hash} | ${s.capturedAt} |`);
32
32
  return lines.join("\n");
33
33
  };
34
- const SettingsListAsMarkdown = Schema.transformOrFail(SettingsListResult, Schema.String, {
35
- strict: true,
36
- decode: (data) => ParseResult.succeed(formatSettingsListMarkdown(data)),
37
- encode: (text, _options, ast) => ParseResult.fail(new ParseResult.Forbidden(ast, text, "SettingsListAsMarkdown is one-way."))
38
- });
39
- const settingsList = publicProcedure.input(Schema.standardSchemaV1(Schema.Struct({}))).query(async ({ ctx }) => ctx.runtime.runPromise(Effect.gen(function* () {
34
+ const SettingsListAsMarkdown = SettingsListResult.pipe(Schema.decodeTo(Schema.String, {
35
+ decode: SchemaGetter.transform((data) => formatSettingsListMarkdown(data)),
36
+ encode: SchemaGetter.forbidden(() => "SettingsListAsMarkdown is one-way.")
37
+ }));
38
+ const settingsList = publicProcedure.input(Schema.toStandardSchemaV1(Schema.Struct({}))).query(async ({ ctx }) => ctx.runtime.runPromise(Effect.gen(function* () {
40
39
  const settings = yield* (yield* DataReader).listSettings();
41
40
  return {
42
41
  count: settings.length,
package/tools/status.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { publicProcedure } from "../context.js";
2
2
  import { CacheManifestEntry, DataReader } from "@vitest-agent/sdk";
3
- import { Effect, Option, ParseResult, Schema } from "effect";
3
+ import { Effect, Option, Schema, SchemaGetter } from "effect";
4
4
 
5
5
  //#region src/tools/status.ts
6
6
  /**
@@ -9,17 +9,17 @@ import { Effect, Option, ParseResult, Schema } from "effect";
9
9
  * @packageDocumentation
10
10
  */
11
11
  const StatusAvailable = Schema.Struct({
12
- dataAvailable: Schema.Literal(true).annotations({ description: "Discriminant — `true` when at least one project entry exists in the manifest." }),
12
+ dataAvailable: Schema.Literal(true).annotate({ description: "Discriminant — `true` when at least one project entry exists in the manifest." }),
13
13
  manifestUpdatedAt: Schema.String,
14
- projectFilter: Schema.optional(Schema.String).annotations({ description: "Echo of the optional `project` filter." }),
15
- entries: Schema.Array(CacheManifestEntry).annotations({ description: "Per-project last-run summary rows. Filtered by `projectFilter` when set." })
16
- }).annotations({ identifier: "TestStatusAvailable" });
14
+ projectFilter: Schema.optional(Schema.String).annotate({ description: "Echo of the optional `project` filter." }),
15
+ entries: Schema.Array(CacheManifestEntry).annotate({ description: "Per-project last-run summary rows. Filtered by `projectFilter` when set." })
16
+ }).annotate({ identifier: "TestStatusAvailable" });
17
17
  const StatusAbsent = Schema.Struct({
18
- dataAvailable: Schema.Literal(false).annotations({ description: "Discriminant — `false` when no manifest exists or the project filter matched nothing." }),
18
+ dataAvailable: Schema.Literal(false).annotate({ description: "Discriminant — `false` when no manifest exists or the project filter matched nothing." }),
19
19
  projectFilter: Schema.optional(Schema.String),
20
- reason: Schema.Literal("no_manifest", "project_filter_empty")
21
- }).annotations({ identifier: "TestStatusAbsent" });
22
- const TestStatusResult = Schema.Union(StatusAvailable, StatusAbsent).annotations({
20
+ reason: Schema.Literals(["no_manifest", "project_filter_empty"])
21
+ }).annotate({ identifier: "TestStatusAbsent" });
22
+ const TestStatusResult = Schema.Union([StatusAvailable, StatusAbsent]).annotate({
23
23
  identifier: "TestStatusResult",
24
24
  title: "test_status result",
25
25
  description: "Per-project last-run summary. Discriminate on `dataAvailable` for cold-start handling."
@@ -43,12 +43,11 @@ const formatTestStatusMarkdown = (data) => {
43
43
  lines.push("", `_Cache updated: ${data.manifestUpdatedAt}_`);
44
44
  return lines.join("\n");
45
45
  };
46
- const TestStatusAsMarkdown = Schema.transformOrFail(TestStatusResult, Schema.String, {
47
- strict: true,
48
- decode: (data) => ParseResult.succeed(formatTestStatusMarkdown(data)),
49
- encode: (text, _options, ast) => ParseResult.fail(new ParseResult.Forbidden(ast, text, "TestStatusAsMarkdown is one-way."))
50
- });
51
- const testStatus = publicProcedure.input(Schema.standardSchemaV1(Schema.Struct({ project: Schema.optional(Schema.String) }))).query(async ({ ctx, input }) => ctx.runtime.runPromise(Effect.gen(function* () {
46
+ const TestStatusAsMarkdown = TestStatusResult.pipe(Schema.decodeTo(Schema.String, {
47
+ decode: SchemaGetter.transform((data) => formatTestStatusMarkdown(data)),
48
+ encode: SchemaGetter.forbidden(() => "TestStatusAsMarkdown is one-way.")
49
+ }));
50
+ const testStatus = publicProcedure.input(Schema.toStandardSchemaV1(Schema.Struct({ project: Schema.optional(Schema.String) }))).query(async ({ ctx, input }) => ctx.runtime.runPromise(Effect.gen(function* () {
52
51
  const manifestOpt = yield* (yield* DataReader).getManifest();
53
52
  if (Option.isNone(manifestOpt)) return {
54
53
  dataAvailable: false,
@@ -1,6 +1,6 @@
1
1
  import { publicProcedure } from "../context.js";
2
2
  import { DataReader } from "@vitest-agent/sdk";
3
- import { Effect, ParseResult, Schema } from "effect";
3
+ import { Effect, Schema, SchemaGetter } from "effect";
4
4
 
5
5
  //#region src/tools/tdd-artifact.ts
6
6
  /**
@@ -15,33 +15,49 @@ import { Effect, ParseResult, Schema } from "effect";
15
15
  *
16
16
  * @packageDocumentation
17
17
  */
18
- const ArtifactKindSchema = Schema.Literal("test_written", "test_failed_run", "code_written", "test_passed_run", "refactor", "test_weakened");
18
+ const ArtifactKindSchema = Schema.Literals([
19
+ "test_written",
20
+ "test_failed_run",
21
+ "code_written",
22
+ "test_passed_run",
23
+ "refactor",
24
+ "test_weakened"
25
+ ]);
19
26
  const TddArtifactRow = Schema.Struct({
20
- id: Schema.Number.annotations({
27
+ id: Schema.Number.annotate({
21
28
  title: "tdd_artifacts.id",
22
29
  description: "Pass as `citedArtifactId` to `tdd_phase_transition_request`."
23
30
  }),
24
31
  tddTaskId: Schema.Number,
25
32
  phaseId: Schema.Number,
26
- phaseName: Schema.Literal("spike", "red", "red.triangulate", "green", "green.fake-it", "refactor", "extended-red", "green-without-red"),
33
+ phaseName: Schema.Literals([
34
+ "spike",
35
+ "red",
36
+ "red.triangulate",
37
+ "green",
38
+ "green.fake-it",
39
+ "refactor",
40
+ "extended-red",
41
+ "green-without-red"
42
+ ]),
27
43
  artifactKind: ArtifactKindSchema,
28
44
  behaviorId: Schema.NullOr(Schema.Number),
29
45
  testCaseId: Schema.NullOr(Schema.Number),
30
46
  testRunId: Schema.NullOr(Schema.Number),
31
47
  testFirstFailureRunId: Schema.NullOr(Schema.Number),
32
48
  recordedAt: Schema.String
33
- }).annotations({ identifier: "TddArtifactListRow" });
49
+ }).annotate({ identifier: "TddArtifactListRow" });
34
50
  const ArtifactFilters = Schema.Struct({
35
51
  artifactKind: Schema.optional(ArtifactKindSchema),
36
52
  phaseId: Schema.optional(Schema.Number),
37
53
  behaviorId: Schema.optional(Schema.Number)
38
- }).annotations({ identifier: "TddArtifactFilters" });
54
+ }).annotate({ identifier: "TddArtifactFilters" });
39
55
  const TddArtifactListResult = Schema.Struct({
40
56
  tddTaskId: Schema.Number,
41
57
  filters: ArtifactFilters,
42
58
  count: Schema.Number,
43
59
  artifacts: Schema.Array(TddArtifactRow)
44
- }).annotations({
60
+ }).annotate({
45
61
  identifier: "TddArtifactListResult",
46
62
  title: "tdd_artifact_list result",
47
63
  description: "Newest-first artifact rows for a TDD task. Echoes the filters that were applied so callers can reason about what is/isn't included."
@@ -65,11 +81,10 @@ const formatTddArtifactListMarkdown = (data) => {
65
81
  }
66
82
  return lines.join("\n");
67
83
  };
68
- const TddArtifactListAsMarkdown = Schema.transformOrFail(TddArtifactListResult, Schema.String, {
69
- strict: true,
70
- decode: (data) => ParseResult.succeed(formatTddArtifactListMarkdown(data)),
71
- encode: (text, _options, ast) => ParseResult.fail(new ParseResult.Forbidden(ast, text, "TddArtifactListAsMarkdown is one-way."))
72
- });
84
+ const TddArtifactListAsMarkdown = TddArtifactListResult.pipe(Schema.decodeTo(Schema.String, {
85
+ decode: SchemaGetter.transform((data) => formatTddArtifactListMarkdown(data)),
86
+ encode: SchemaGetter.forbidden(() => "TddArtifactListAsMarkdown is one-way.")
87
+ }));
73
88
  const TddArtifactListInput = Schema.Struct({
74
89
  tddTaskId: Schema.Number,
75
90
  artifactKind: Schema.optional(ArtifactKindSchema),
@@ -77,7 +92,7 @@ const TddArtifactListInput = Schema.Struct({
77
92
  behaviorId: Schema.optional(Schema.Number),
78
93
  limit: Schema.optional(Schema.Number)
79
94
  });
80
- const tddArtifactList = publicProcedure.input(Schema.standardSchemaV1(TddArtifactListInput)).query(async ({ ctx, input }) => ctx.runtime.runPromise(Effect.gen(function* () {
95
+ const tddArtifactList = publicProcedure.input(Schema.toStandardSchemaV1(TddArtifactListInput)).query(async ({ ctx, input }) => ctx.runtime.runPromise(Effect.gen(function* () {
81
96
  const rows = yield* (yield* DataReader).listTddArtifactsForTask({
82
97
  tddTaskId: input.tddTaskId,
83
98
  ...input.artifactKind !== void 0 && { artifactKind: input.artifactKind },
@@ -13,27 +13,29 @@ import { Effect, Match, Option, Schema } from "effect";
13
13
  * folds into the action discriminator here as `list_by_goal` /
14
14
  * `list_by_tdd_task`).
15
15
  */
16
- const BehaviorStatus = Schema.Literal("pending", "in_progress", "done", "abandoned");
16
+ const BehaviorStatus = Schema.Literals([
17
+ "pending",
18
+ "in_progress",
19
+ "done",
20
+ "abandoned"
21
+ ]);
17
22
  const TddBehaviorErrorEnvelope = Schema.Struct({
18
- ok: Schema.Literal(false).annotations({ description: "Discriminant — `false` when a tagged TDD error was caught." }),
19
- error: Schema.Struct({
20
- _tag: Schema.String.annotations({ description: "Tagged error name (e.g. BehaviorNotFoundError, GoalNotFoundError)." }),
23
+ ok: Schema.Literal(false).annotate({ description: "Discriminant — `false` when a tagged TDD error was caught." }),
24
+ error: Schema.StructWithRest(Schema.Struct({
25
+ _tag: Schema.String.annotate({ description: "Tagged error name (e.g. BehaviorNotFoundError, GoalNotFoundError)." }),
21
26
  message: Schema.String,
22
27
  remediation: Schema.optional(Schema.String)
23
- }).pipe(Schema.extend(Schema.Record({
24
- key: Schema.String,
25
- value: Schema.Unknown
26
- })))
27
- }).annotations({ identifier: "TddBehaviorErrorEnvelope" });
28
+ }), [Schema.Record(Schema.String, Schema.Unknown)])
29
+ }).annotate({ identifier: "TddBehaviorErrorEnvelope" });
28
30
  const TddBehaviorCreateOk = Schema.Struct({
29
31
  ok: Schema.Literal(true),
30
32
  action: Schema.Literal("create"),
31
- behavior: BehaviorRow.annotations({ description: "Newly inserted behavior row." })
33
+ behavior: BehaviorRow.annotate({ description: "Newly inserted behavior row." })
32
34
  });
33
35
  const TddBehaviorUpdateOk = Schema.Struct({
34
36
  ok: Schema.Literal(true),
35
37
  action: Schema.Literal("update"),
36
- behavior: BehaviorRow.annotations({ description: "Updated behavior row." })
38
+ behavior: BehaviorRow.annotate({ description: "Updated behavior row." })
37
39
  });
38
40
  const TddBehaviorDeleteOk = Schema.Struct({
39
41
  ok: Schema.Literal(true),
@@ -43,7 +45,7 @@ const TddBehaviorDeleteOk = Schema.Struct({
43
45
  const TddBehaviorGetFound = Schema.Struct({
44
46
  action: Schema.Literal("get"),
45
47
  found: Schema.Literal(true),
46
- behavior: BehaviorDetail.annotations({ description: "Behavior with parentGoal + dependencies[]." })
48
+ behavior: BehaviorDetail.annotate({ description: "Behavior with parentGoal + dependencies[]." })
47
49
  });
48
50
  const TddBehaviorGetMissing = Schema.Struct({
49
51
  action: Schema.Literal("get"),
@@ -62,7 +64,16 @@ const TddBehaviorListByTddTaskOk = Schema.Struct({
62
64
  tddTaskId: Schema.Number,
63
65
  behaviors: Schema.Array(BehaviorRow)
64
66
  });
65
- const TddBehaviorResult = Schema.Union(TddBehaviorCreateOk, TddBehaviorUpdateOk, TddBehaviorDeleteOk, TddBehaviorGetFound, TddBehaviorGetMissing, TddBehaviorListByGoalOk, TddBehaviorListByTddTaskOk, TddBehaviorErrorEnvelope).annotations({
67
+ const TddBehaviorResult = Schema.Union([
68
+ TddBehaviorCreateOk,
69
+ TddBehaviorUpdateOk,
70
+ TddBehaviorDeleteOk,
71
+ TddBehaviorGetFound,
72
+ TddBehaviorGetMissing,
73
+ TddBehaviorListByGoalOk,
74
+ TddBehaviorListByTddTaskOk,
75
+ TddBehaviorErrorEnvelope
76
+ ]).annotate({
66
77
  identifier: "TddBehaviorResult",
67
78
  title: "tdd_behavior result",
68
79
  description: "Discriminate on `action` (or `ok=false` for the tagged-error envelope)."
@@ -98,8 +109,15 @@ const ListByTddTaskVariant = Schema.Struct({
98
109
  action: Schema.Literal("list_by_tdd_task"),
99
110
  tddTaskId: Schema.Number
100
111
  });
101
- const TddBehaviorInput = Schema.Union(CreateVariant, UpdateVariant, DeleteVariant, GetVariant, ListByGoalVariant, ListByTddTaskVariant);
102
- const tddBehavior = idempotentProcedure.input(Schema.standardSchemaV1(TddBehaviorInput)).mutation(async ({ ctx, input }) => {
112
+ const TddBehaviorInput = Schema.Union([
113
+ CreateVariant,
114
+ UpdateVariant,
115
+ DeleteVariant,
116
+ GetVariant,
117
+ ListByGoalVariant,
118
+ ListByTddTaskVariant
119
+ ]);
120
+ const tddBehavior = idempotentProcedure.input(Schema.toStandardSchemaV1(TddBehaviorInput)).mutation(async ({ ctx, input }) => {
103
121
  return ctx.runtime.runPromise(Match.value(input).pipe(Match.discriminatorsExhaustive("action")({
104
122
  create: (variant) => catchTddErrorsAsEnvelope(Effect.gen(function* () {
105
123
  return {
package/tools/tdd-goal.js CHANGED
@@ -13,50 +13,60 @@ import { Effect, Match, Option, Schema } from "effect";
13
13
  * `{ ok: false, error: { _tag, ..., remediation } }` envelopes via
14
14
  * the shared helper.
15
15
  */
16
- const GoalStatus = Schema.Literal("pending", "in_progress", "done", "abandoned");
16
+ const GoalStatus = Schema.Literals([
17
+ "pending",
18
+ "in_progress",
19
+ "done",
20
+ "abandoned"
21
+ ]);
17
22
  const TddErrorEnvelope = Schema.Struct({
18
- ok: Schema.Literal(false).annotations({ description: "Discriminant — `false` when a tagged TDD error was caught." }),
19
- error: Schema.Struct({
20
- _tag: Schema.String.annotations({ description: "Tagged error name (e.g. GoalNotFoundError, TddTaskNotFoundError)." }),
23
+ ok: Schema.Literal(false).annotate({ description: "Discriminant — `false` when a tagged TDD error was caught." }),
24
+ error: Schema.StructWithRest(Schema.Struct({
25
+ _tag: Schema.String.annotate({ description: "Tagged error name (e.g. GoalNotFoundError, TddTaskNotFoundError)." }),
21
26
  message: Schema.String,
22
- remediation: Schema.optional(Schema.String).annotations({ description: "Suggested next action when known." })
23
- }).pipe(Schema.extend(Schema.Record({
24
- key: Schema.String,
25
- value: Schema.Unknown
26
- })))
27
- }).annotations({ identifier: "TddErrorEnvelope" });
27
+ remediation: Schema.optional(Schema.String).annotate({ description: "Suggested next action when known." })
28
+ }), [Schema.Record(Schema.String, Schema.Unknown)])
29
+ }).annotate({ identifier: "TddErrorEnvelope" });
28
30
  const TddGoalCreateOk = Schema.Struct({
29
31
  ok: Schema.Literal(true),
30
32
  action: Schema.Literal("create"),
31
- goal: GoalRow.annotations({ description: "Newly inserted goal row." })
32
- }).annotations({ identifier: "TddGoalCreateOk" });
33
+ goal: GoalRow.annotate({ description: "Newly inserted goal row." })
34
+ }).annotate({ identifier: "TddGoalCreateOk" });
33
35
  const TddGoalUpdateOk = Schema.Struct({
34
36
  ok: Schema.Literal(true),
35
37
  action: Schema.Literal("update"),
36
- goal: GoalRow.annotations({ description: "Updated goal row." })
37
- }).annotations({ identifier: "TddGoalUpdateOk" });
38
+ goal: GoalRow.annotate({ description: "Updated goal row." })
39
+ }).annotate({ identifier: "TddGoalUpdateOk" });
38
40
  const TddGoalDeleteOk = Schema.Struct({
39
41
  ok: Schema.Literal(true),
40
42
  action: Schema.Literal("delete"),
41
43
  id: Schema.Number
42
- }).annotations({ identifier: "TddGoalDeleteOk" });
44
+ }).annotate({ identifier: "TddGoalDeleteOk" });
43
45
  const TddGoalGetFound = Schema.Struct({
44
46
  action: Schema.Literal("get"),
45
47
  found: Schema.Literal(true),
46
- goal: GoalDetail.annotations({ description: "Goal with nested behaviors[]." })
47
- }).annotations({ identifier: "TddGoalGetFound" });
48
+ goal: GoalDetail.annotate({ description: "Goal with nested behaviors[]." })
49
+ }).annotate({ identifier: "TddGoalGetFound" });
48
50
  const TddGoalGetMissing = Schema.Struct({
49
51
  action: Schema.Literal("get"),
50
52
  found: Schema.Literal(false),
51
53
  id: Schema.Number
52
- }).annotations({ identifier: "TddGoalGetMissing" });
54
+ }).annotate({ identifier: "TddGoalGetMissing" });
53
55
  const TddGoalListOk = Schema.Struct({
54
56
  ok: Schema.Literal(true),
55
57
  action: Schema.Literal("list"),
56
58
  tddTaskId: Schema.Number,
57
- goals: Schema.Array(GoalDetail).annotations({ description: "All goals for the TDD task, with their behaviors." })
58
- }).annotations({ identifier: "TddGoalListOk" });
59
- const TddGoalResult = Schema.Union(TddGoalCreateOk, TddGoalUpdateOk, TddGoalDeleteOk, TddGoalGetFound, TddGoalGetMissing, TddGoalListOk, TddErrorEnvelope).annotations({
59
+ goals: Schema.Array(GoalDetail).annotate({ description: "All goals for the TDD task, with their behaviors." })
60
+ }).annotate({ identifier: "TddGoalListOk" });
61
+ const TddGoalResult = Schema.Union([
62
+ TddGoalCreateOk,
63
+ TddGoalUpdateOk,
64
+ TddGoalDeleteOk,
65
+ TddGoalGetFound,
66
+ TddGoalGetMissing,
67
+ TddGoalListOk,
68
+ TddErrorEnvelope
69
+ ]).annotate({
60
70
  identifier: "TddGoalResult",
61
71
  title: "tdd_goal result",
62
72
  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."
@@ -84,8 +94,14 @@ const ListVariant = Schema.Struct({
84
94
  action: Schema.Literal("list"),
85
95
  tddTaskId: Schema.Number
86
96
  });
87
- const TddGoalInput = Schema.Union(CreateVariant, UpdateVariant, DeleteVariant, GetVariant, ListVariant);
88
- const tddGoal = idempotentProcedure.input(Schema.standardSchemaV1(TddGoalInput)).mutation(async ({ ctx, input }) => {
97
+ const TddGoalInput = Schema.Union([
98
+ CreateVariant,
99
+ UpdateVariant,
100
+ DeleteVariant,
101
+ GetVariant,
102
+ ListVariant
103
+ ]);
104
+ const tddGoal = idempotentProcedure.input(Schema.toStandardSchemaV1(TddGoalInput)).mutation(async ({ ctx, input }) => {
89
105
  return ctx.runtime.runPromise(Match.value(input).pipe(Match.discriminatorsExhaustive("action")({
90
106
  create: (variant) => catchTddErrorsAsEnvelope(Effect.gen(function* () {
91
107
  return {
@@ -3,37 +3,71 @@ import { DataReader, DataStore, requiredArtifactForTransition, transitionEnforce
3
3
  import { Effect, Option, Schema } from "effect";
4
4
 
5
5
  //#region src/tools/tdd-phase-transition-request.ts
6
- const phaseLiteral = Schema.Literal("spike", "red", "red.triangulate", "green", "green.fake-it", "refactor", "extended-red", "green-without-red");
7
- const artifactKindLiteral = Schema.Literal("test_written", "test_failed_run", "code_written", "test_passed_run", "refactor", "test_weakened");
8
- const denialReasonLiteral = Schema.Literal("missing_artifact_evidence", "wrong_artifact_kind", "wrong_source_phase", "unknown_tdd_task", "tdd_task_already_ended", "goal_not_started", "goal_not_found", "goal_not_in_progress", "goal_not_in_tdd_task", "behavior_not_found", "behavior_not_in_goal", "refactor_without_passing_run", "evidence_not_in_phase_window", "evidence_not_for_behavior", "evidence_test_was_already_failing");
6
+ const phaseLiteral = Schema.Literals([
7
+ "spike",
8
+ "red",
9
+ "red.triangulate",
10
+ "green",
11
+ "green.fake-it",
12
+ "refactor",
13
+ "extended-red",
14
+ "green-without-red"
15
+ ]);
16
+ const artifactKindLiteral = Schema.Literals([
17
+ "test_written",
18
+ "test_failed_run",
19
+ "code_written",
20
+ "test_passed_run",
21
+ "refactor",
22
+ "test_weakened"
23
+ ]);
24
+ const denialReasonLiteral = Schema.Literals([
25
+ "missing_artifact_evidence",
26
+ "wrong_artifact_kind",
27
+ "wrong_source_phase",
28
+ "unknown_tdd_task",
29
+ "tdd_task_already_ended",
30
+ "goal_not_started",
31
+ "goal_not_found",
32
+ "goal_not_in_progress",
33
+ "goal_not_in_tdd_task",
34
+ "behavior_not_found",
35
+ "behavior_not_in_goal",
36
+ "refactor_without_passing_run",
37
+ "evidence_not_in_phase_window",
38
+ "evidence_not_for_behavior",
39
+ "evidence_test_was_already_failing"
40
+ ]);
9
41
  const RemediationSchema = Schema.Struct({
10
- suggestedTool: Schema.String.annotations({ description: "Next tool the agent should call to make progress." }),
11
- suggestedArgs: Schema.Record({
12
- key: Schema.String,
13
- value: Schema.Unknown
14
- }).annotations({ description: "Concrete arguments for `suggestedTool` that fix the underlying issue." }),
15
- humanHint: Schema.String.annotations({ description: "Plain-language explanation of what to do next." })
16
- }).annotations({ identifier: "PhaseTransitionRemediation" });
42
+ suggestedTool: Schema.String.annotate({ description: "Next tool the agent should call to make progress." }),
43
+ suggestedArgs: Schema.Record(Schema.String, Schema.Unknown).annotate({ description: "Concrete arguments for `suggestedTool` that fix the underlying issue." }),
44
+ humanHint: Schema.String.annotate({ description: "Plain-language explanation of what to do next." })
45
+ }).annotate({ identifier: "PhaseTransitionRemediation" });
17
46
  const PhaseTransitionAccepted = Schema.Struct({
18
- accepted: Schema.Literal(true).annotations({ description: "Discriminant — `true` when the transition was granted." }),
19
- phase: phaseLiteral.annotations({ description: "Phase the session is now in (echo of `requestedPhase`)." }),
20
- newPhaseId: Schema.Number.annotations({ description: "`tdd_phases.id` of the freshly opened row." }),
21
- previousPhaseId: Schema.NullOr(Schema.Number).annotations({ description: "`tdd_phases.id` of the phase that was closed (`null` for the first transition)." }),
22
- citedArtifactId: Schema.optional(Schema.Number).annotations({ description: "Resolved `tdd_artifacts.id` actually used. Absent only for transitions that need no artifact (e.g. spike→red)." }),
23
- citedArtifactSource: Schema.optional(Schema.Literal("explicit-id", "explicit-kind", "transition-derived", "none")).annotations({ description: "Where `citedArtifactId` came from: `explicit-id` (caller passed it), `explicit-kind` (resolved from caller's `citedArtifactKind`), `transition-derived` (resolved from the transition's required-evidence rule), or `none` (no artifact needed)." })
24
- }).annotations({ identifier: "PhaseTransitionAccepted" });
47
+ accepted: Schema.Literal(true).annotate({ description: "Discriminant — `true` when the transition was granted." }),
48
+ phase: phaseLiteral.annotate({ description: "Phase the session is now in (echo of `requestedPhase`)." }),
49
+ newPhaseId: Schema.Number.annotate({ description: "`tdd_phases.id` of the freshly opened row." }),
50
+ previousPhaseId: Schema.NullOr(Schema.Number).annotate({ description: "`tdd_phases.id` of the phase that was closed (`null` for the first transition)." }),
51
+ citedArtifactId: Schema.optional(Schema.Number).annotate({ description: "Resolved `tdd_artifacts.id` actually used. Absent only for transitions that need no artifact (e.g. spike→red)." }),
52
+ citedArtifactSource: Schema.optional(Schema.Literals([
53
+ "explicit-id",
54
+ "explicit-kind",
55
+ "transition-derived",
56
+ "none"
57
+ ])).annotate({ description: "Where `citedArtifactId` came from: `explicit-id` (caller passed it), `explicit-kind` (resolved from caller's `citedArtifactKind`), `transition-derived` (resolved from the transition's required-evidence rule), or `none` (no artifact needed)." })
58
+ }).annotate({ identifier: "PhaseTransitionAccepted" });
25
59
  const PhaseTransitionDenied = Schema.Struct({
26
- accepted: Schema.Literal(false).annotations({ description: "Discriminant — `false` when the transition was refused." }),
27
- phase: phaseLiteral.annotations({ description: "Phase the session remains in (the current phase, unchanged)." }),
28
- denialReason: denialReasonLiteral.annotations({ description: "Categorical refusal reason; see `remediation` for the suggested fix." }),
60
+ accepted: Schema.Literal(false).annotate({ description: "Discriminant — `false` when the transition was refused." }),
61
+ phase: phaseLiteral.annotate({ description: "Phase the session remains in (the current phase, unchanged)." }),
62
+ denialReason: denialReasonLiteral.annotate({ description: "Categorical refusal reason; see `remediation` for the suggested fix." }),
29
63
  remediation: RemediationSchema
30
- }).annotations({ identifier: "PhaseTransitionDenied" });
31
- const PhaseTransitionResult = Schema.Union(PhaseTransitionAccepted, PhaseTransitionDenied).annotations({
64
+ }).annotate({ identifier: "PhaseTransitionDenied" });
65
+ const PhaseTransitionResult = Schema.Union([PhaseTransitionAccepted, PhaseTransitionDenied]).annotate({
32
66
  identifier: "PhaseTransitionResult",
33
67
  title: "tdd_phase_transition_request result",
34
68
  description: "Discriminate on `accepted`. Acceptance carries the new phaseId; denial carries a typed reason and a remediation pointer."
35
69
  });
36
- const tddPhaseTransitionRequest = publicProcedure.input(Schema.standardSchemaV1(Schema.Struct({
70
+ const tddPhaseTransitionRequest = publicProcedure.input(Schema.toStandardSchemaV1(Schema.Struct({
37
71
  tddTaskId: Schema.Number,
38
72
  goalId: Schema.Number,
39
73
  requestedPhase: phaseLiteral,
@@ -190,13 +224,13 @@ const tddPhaseTransitionRequest = publicProcedure.input(Schema.standardSchemaV1(
190
224
  ...input.behaviorId !== void 0 && { behaviorId: input.behaviorId },
191
225
  ...input.reason !== void 0 && { transitionReason: input.reason }
192
226
  });
193
- if (input.behaviorId !== void 0) yield* Effect.ignoreLogged(Effect.gen(function* () {
227
+ if (input.behaviorId !== void 0) yield* Effect.gen(function* () {
194
228
  const behOpt = yield* reader.getBehaviorById(input.behaviorId);
195
229
  if (Option.isSome(behOpt) && behOpt.value.status === "pending") yield* store.updateBehavior({
196
230
  id: input.behaviorId,
197
231
  status: "in_progress"
198
232
  });
199
- }));
233
+ }).pipe(Effect.catchCause((cause) => Effect.logDebug("behavior auto-promotion failed", cause)));
200
234
  return {
201
235
  accepted: true,
202
236
  phase: result.phase,