@vitest-agent/mcp 1.3.6 → 2.0.1
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/bin/vitest-agent-mcp.js +2 -2
- package/index.d.ts +999 -1001
- package/index.js +1 -1
- package/layers/McpLive.js +4 -5
- package/package.json +7 -12
- package/server.js +5 -5
- package/tools/_tdd-error-envelope.js +1 -1
- package/tools/acceptance-metrics.js +23 -24
- package/tools/cache-health.js +12 -13
- package/tools/commit-changes.js +27 -22
- package/tools/configure.js +27 -31
- package/tools/coverage.js +9 -10
- package/tools/errors.js +31 -27
- package/tools/failure-signature-get.js +10 -11
- package/tools/file-coverage.js +14 -11
- package/tools/help.js +1 -1
- package/tools/history.js +29 -30
- package/tools/hypothesis.js +33 -12
- package/tools/inventory.js +45 -27
- package/tools/note.js +32 -10
- package/tools/overview.js +16 -13
- package/tools/ping.js +1 -1
- package/tools/register-agent.js +18 -13
- package/tools/run-tests.js +22 -21
- package/tools/settings-list.js +11 -12
- package/tools/status.js +14 -15
- package/tools/tdd-artifact.js +28 -13
- package/tools/tdd-behavior.js +33 -15
- package/tools/tdd-goal.js +39 -23
- package/tools/tdd-phase-transition-request.js +59 -25
- package/tools/tdd-task.js +40 -21
- package/tools/test.js +28 -18
- package/tools/trends.js +12 -13
- package/tools/triage-brief.js +4 -4
- package/tools/turn-search.js +25 -18
- package/tools/wrapup-prompt.js +18 -6
- package/tsdoc-metadata.json +1 -1
- package/utils/effect-to-zod.js +18 -5
package/tools/run-tests.js
CHANGED
|
@@ -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,
|
|
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").
|
|
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.
|
|
12
|
-
classifications: Schema.Record({
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
}).
|
|
18
|
+
}).annotate({ identifier: "RunTestsTimeout" });
|
|
22
19
|
const RunTestsError = Schema.Struct({
|
|
23
20
|
kind: Schema.Literal("error"),
|
|
24
21
|
message: Schema.String
|
|
25
|
-
}).
|
|
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
|
-
}).
|
|
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").
|
|
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
|
-
}).
|
|
43
|
-
const RunTestsResult = Schema.Union(
|
|
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.
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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.
|
|
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),
|
package/tools/settings-list.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { publicProcedure } from "../context.js";
|
|
2
2
|
import { DataReader } from "@vitest-agent/sdk";
|
|
3
|
-
import { 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.
|
|
13
|
-
capturedAt: Schema.String.
|
|
14
|
-
}).
|
|
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).
|
|
18
|
-
}).
|
|
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.
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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,
|
|
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).
|
|
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).
|
|
15
|
-
entries: Schema.Array(CacheManifestEntry).
|
|
16
|
-
}).
|
|
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).
|
|
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.
|
|
21
|
-
}).
|
|
22
|
-
const TestStatusResult = Schema.Union(StatusAvailable, StatusAbsent).
|
|
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.
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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,
|
package/tools/tdd-artifact.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { publicProcedure } from "../context.js";
|
|
2
2
|
import { DataReader } from "@vitest-agent/sdk";
|
|
3
|
-
import { 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.
|
|
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.
|
|
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.
|
|
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
|
-
}).
|
|
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
|
-
}).
|
|
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
|
-
}).
|
|
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.
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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.
|
|
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 },
|
package/tools/tdd-behavior.js
CHANGED
|
@@ -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.
|
|
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).
|
|
19
|
-
error: Schema.Struct({
|
|
20
|
-
_tag: Schema.String.
|
|
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
|
-
}).
|
|
24
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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(
|
|
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(
|
|
102
|
-
|
|
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.
|
|
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).
|
|
19
|
-
error: Schema.Struct({
|
|
20
|
-
_tag: Schema.String.
|
|
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).
|
|
23
|
-
}).
|
|
24
|
-
|
|
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.
|
|
32
|
-
}).
|
|
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.
|
|
37
|
-
}).
|
|
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
|
-
}).
|
|
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.
|
|
47
|
-
}).
|
|
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
|
-
}).
|
|
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).
|
|
58
|
-
}).
|
|
59
|
-
const TddGoalResult = Schema.Union(
|
|
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(
|
|
88
|
-
|
|
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.
|
|
7
|
-
|
|
8
|
-
|
|
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.
|
|
11
|
-
suggestedArgs: Schema.Record({
|
|
12
|
-
|
|
13
|
-
|
|
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).
|
|
19
|
-
phase: phaseLiteral.
|
|
20
|
-
newPhaseId: Schema.Number.
|
|
21
|
-
previousPhaseId: Schema.NullOr(Schema.Number).
|
|
22
|
-
citedArtifactId: Schema.optional(Schema.Number).
|
|
23
|
-
citedArtifactSource: Schema.optional(Schema.
|
|
24
|
-
|
|
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).
|
|
27
|
-
phase: phaseLiteral.
|
|
28
|
-
denialReason: denialReasonLiteral.
|
|
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
|
-
}).
|
|
31
|
-
const PhaseTransitionResult = Schema.Union(PhaseTransitionAccepted, PhaseTransitionDenied).
|
|
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.
|
|
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.
|
|
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,
|