@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.
- 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/file-coverage.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { publicProcedure } from "../context.js";
|
|
2
2
|
import { CoverageTotals, DataReader, FileCoverageReport } from "@vitest-agent/sdk";
|
|
3
|
-
import { Effect, Option,
|
|
3
|
+
import { Effect, Option, Schema, SchemaGetter } from "effect";
|
|
4
4
|
|
|
5
5
|
//#region src/tools/file-coverage.ts
|
|
6
6
|
/**
|
|
@@ -21,19 +21,23 @@ const FileCoverageMatched = Schema.Struct({
|
|
|
21
21
|
report: FileCoverageReport,
|
|
22
22
|
globalThresholds: CoverageGlobalThresholds,
|
|
23
23
|
relatedTestFiles: Schema.Array(Schema.String)
|
|
24
|
-
}).
|
|
24
|
+
}).annotate({ identifier: "FileCoverageMatched" });
|
|
25
25
|
const FileCoverageNoMatch = Schema.Struct({
|
|
26
26
|
dataAvailable: Schema.Literal(true),
|
|
27
27
|
matched: Schema.Literal(false),
|
|
28
28
|
filePath: Schema.String,
|
|
29
29
|
totals: CoverageTotals,
|
|
30
30
|
relatedTestFiles: Schema.Array(Schema.String)
|
|
31
|
-
}).
|
|
31
|
+
}).annotate({ identifier: "FileCoverageNoMatch" });
|
|
32
32
|
const FileCoverageAbsent = Schema.Struct({
|
|
33
33
|
dataAvailable: Schema.Literal(false),
|
|
34
34
|
filePath: Schema.String
|
|
35
|
-
}).
|
|
36
|
-
const FileCoverageResult = Schema.Union(
|
|
35
|
+
}).annotate({ identifier: "FileCoverageAbsent" });
|
|
36
|
+
const FileCoverageResult = Schema.Union([
|
|
37
|
+
FileCoverageMatched,
|
|
38
|
+
FileCoverageNoMatch,
|
|
39
|
+
FileCoverageAbsent
|
|
40
|
+
]).annotate({
|
|
37
41
|
identifier: "FileCoverageResult",
|
|
38
42
|
title: "file_coverage result",
|
|
39
43
|
description: "Per-file coverage with related tests. Discriminate on `dataAvailable` then on `matched`."
|
|
@@ -65,12 +69,11 @@ const formatFileCoverageMarkdown = (data) => {
|
|
|
65
69
|
}
|
|
66
70
|
return lines.join("\n");
|
|
67
71
|
};
|
|
68
|
-
const FileCoverageAsMarkdown = Schema.
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const fileCoverage = publicProcedure.input(Schema.standardSchemaV1(Schema.Struct({
|
|
72
|
+
const FileCoverageAsMarkdown = FileCoverageResult.pipe(Schema.decodeTo(Schema.String, {
|
|
73
|
+
decode: SchemaGetter.transform((data) => formatFileCoverageMarkdown(data)),
|
|
74
|
+
encode: SchemaGetter.forbidden(() => "FileCoverageAsMarkdown is one-way.")
|
|
75
|
+
}));
|
|
76
|
+
const fileCoverage = publicProcedure.input(Schema.toStandardSchemaV1(Schema.Struct({
|
|
74
77
|
filePath: Schema.String,
|
|
75
78
|
project: Schema.optional(Schema.String)
|
|
76
79
|
}))).query(async ({ ctx, input }) => ctx.runtime.runPromise(Effect.gen(function* () {
|
package/tools/help.js
CHANGED
|
@@ -2,7 +2,7 @@ import { publicProcedure } from "../context.js";
|
|
|
2
2
|
import { Schema } from "effect";
|
|
3
3
|
|
|
4
4
|
//#region src/tools/help.ts
|
|
5
|
-
const HelpResult = Schema.Struct({ helpText: Schema.String.
|
|
5
|
+
const HelpResult = Schema.Struct({ helpText: Schema.String.annotate({ description: "Markdown table of every MCP tool with parameters and a one-line description." }) }).annotate({
|
|
6
6
|
identifier: "HelpResult",
|
|
7
7
|
title: "help result",
|
|
8
8
|
description: "Static help reference. Read structuredContent.helpText programmatically; the same string lives in content[].text for transcripts."
|
package/tools/history.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { publicProcedure } from "../context.js";
|
|
2
2
|
import { DataReader, HistoryRecord } from "@vitest-agent/sdk";
|
|
3
|
-
import { Effect,
|
|
3
|
+
import { Effect, Schema, SchemaGetter } from "effect";
|
|
4
4
|
|
|
5
5
|
//#region src/tools/history.ts
|
|
6
6
|
/**
|
|
@@ -13,45 +13,45 @@ import { Effect, ParseResult, Schema } from "effect";
|
|
|
13
13
|
* @packageDocumentation
|
|
14
14
|
*/
|
|
15
15
|
const FlakyTestRow = Schema.Struct({
|
|
16
|
-
fullName: Schema.String.
|
|
17
|
-
modulePath: Schema.String.
|
|
16
|
+
fullName: Schema.String.annotate({ description: "Full hierarchical test name (`describe > it`)." }),
|
|
17
|
+
modulePath: Schema.String.annotate({ description: "Project-relative test module path -- disambiguates same-named tests across files." }),
|
|
18
18
|
project: Schema.String,
|
|
19
|
-
passCount: Schema.Number.
|
|
20
|
-
failCount: Schema.Number.
|
|
21
|
-
lastState: Schema.
|
|
22
|
-
lastTimestamp: Schema.String.
|
|
23
|
-
}).
|
|
19
|
+
passCount: Schema.Number.annotate({ description: "Number of passing runs in the recent window." }),
|
|
20
|
+
failCount: Schema.Number.annotate({ description: "Number of failing runs in the recent window." }),
|
|
21
|
+
lastState: Schema.Literals(["passed", "failed"]).annotate({ description: "State of the most recent run." }),
|
|
22
|
+
lastTimestamp: Schema.String.annotate({ description: "ISO-8601 timestamp of the most recent run." })
|
|
23
|
+
}).annotate({
|
|
24
24
|
identifier: "FlakyTestRow",
|
|
25
25
|
description: "A test that produced both passes and failures within the recent run window."
|
|
26
26
|
});
|
|
27
27
|
const PersistentFailureRow = Schema.Struct({
|
|
28
28
|
fullName: Schema.String,
|
|
29
|
-
modulePath: Schema.String.
|
|
29
|
+
modulePath: Schema.String.annotate({ description: "Project-relative test module path -- disambiguates same-named tests across files." }),
|
|
30
30
|
project: Schema.String,
|
|
31
|
-
consecutiveFailures: Schema.Number.
|
|
32
|
-
firstFailedAt: Schema.String.
|
|
33
|
-
lastFailedAt: Schema.String.
|
|
34
|
-
lastErrorMessage: Schema.NullOr(Schema.String).
|
|
35
|
-
}).
|
|
31
|
+
consecutiveFailures: Schema.Number.annotate({ description: "Length of the current uninterrupted failure streak." }),
|
|
32
|
+
firstFailedAt: Schema.String.annotate({ description: "ISO-8601 timestamp of the first failure in this streak." }),
|
|
33
|
+
lastFailedAt: Schema.String.annotate({ description: "ISO-8601 timestamp of the most recent failure." }),
|
|
34
|
+
lastErrorMessage: Schema.NullOr(Schema.String).annotate({ description: "Last error message reported by the failing test, when captured." })
|
|
35
|
+
}).annotate({
|
|
36
36
|
identifier: "PersistentFailureRow",
|
|
37
37
|
description: "A test that has failed in every recent run since `firstFailedAt`."
|
|
38
38
|
});
|
|
39
39
|
const RecoveredTestRow = Schema.Struct({
|
|
40
|
-
modulePath: Schema.String.
|
|
40
|
+
modulePath: Schema.String.annotate({ description: "Project-relative test module path -- disambiguates same-named tests across files." }),
|
|
41
41
|
fullName: Schema.String,
|
|
42
|
-
recentRuns: Schema.Array(Schema.
|
|
43
|
-
}).
|
|
42
|
+
recentRuns: Schema.Array(Schema.Literals(["passed", "failed"])).annotate({ description: "Last 10 run states for this test, oldest first." })
|
|
43
|
+
}).annotate({
|
|
44
44
|
identifier: "RecoveredTestRow",
|
|
45
45
|
description: "A test whose latest run passed after the previous one failed."
|
|
46
46
|
});
|
|
47
47
|
const TestHistoryResult = Schema.Struct({
|
|
48
|
-
project: Schema.String.
|
|
49
|
-
hasData: Schema.Boolean.
|
|
50
|
-
history: HistoryRecord.
|
|
51
|
-
flaky: Schema.Array(FlakyTestRow).
|
|
52
|
-
persistent: Schema.Array(PersistentFailureRow).
|
|
53
|
-
recovered: Schema.Array(RecoveredTestRow).
|
|
54
|
-
}).
|
|
48
|
+
project: Schema.String.annotate({ description: "Workspace project key the history was computed for." }),
|
|
49
|
+
hasData: Schema.Boolean.annotate({ description: "`false` when no history rows exist for the project — agent should suggest running tests first." }),
|
|
50
|
+
history: HistoryRecord.annotate({ description: "Raw per-test history record (stored in `test_runs` joins)." }),
|
|
51
|
+
flaky: Schema.Array(FlakyTestRow).annotate({ description: "Tests with mixed pass/fail outcomes recently." }),
|
|
52
|
+
persistent: Schema.Array(PersistentFailureRow).annotate({ description: "Tests failing across consecutive runs." }),
|
|
53
|
+
recovered: Schema.Array(RecoveredTestRow).annotate({ description: "Tests that just transitioned from failing to passing in the last run." })
|
|
54
|
+
}).annotate({
|
|
55
55
|
identifier: "TestHistoryResult",
|
|
56
56
|
title: "test_history result",
|
|
57
57
|
description: "Per-project flaky/persistent/recovered test classifications computed from `test_runs` history."
|
|
@@ -87,12 +87,11 @@ const formatTestHistoryMarkdown = (data) => {
|
|
|
87
87
|
lines.push(`_History updated: ${data.history.updatedAt}_`);
|
|
88
88
|
return lines.join("\n");
|
|
89
89
|
};
|
|
90
|
-
const TestHistoryAsMarkdown = Schema.
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
})
|
|
95
|
-
const testHistory = publicProcedure.input(Schema.standardSchemaV1(Schema.Struct({ project: Schema.String }))).query(async ({ ctx, input }) => ctx.runtime.runPromise(Effect.gen(function* () {
|
|
90
|
+
const TestHistoryAsMarkdown = TestHistoryResult.pipe(Schema.decodeTo(Schema.String, {
|
|
91
|
+
decode: SchemaGetter.transform((data) => formatTestHistoryMarkdown(data)),
|
|
92
|
+
encode: SchemaGetter.forbidden(() => "TestHistoryAsMarkdown is one-way: markdown cannot be parsed back to TestHistoryResult.")
|
|
93
|
+
}));
|
|
94
|
+
const testHistory = publicProcedure.input(Schema.toStandardSchemaV1(Schema.Struct({ project: Schema.String }))).query(async ({ ctx, input }) => ctx.runtime.runPromise(Effect.gen(function* () {
|
|
96
95
|
const reader = yield* DataReader;
|
|
97
96
|
const [history, flaky, persistent] = yield* Effect.all([
|
|
98
97
|
reader.getHistory(input.project),
|
package/tools/hypothesis.js
CHANGED
|
@@ -16,15 +16,19 @@ import { Effect, Match, Option, Schema } from "effect";
|
|
|
16
16
|
const HypothesisRowSchema = Schema.Struct({
|
|
17
17
|
id: Schema.Number,
|
|
18
18
|
sessionId: Schema.Number,
|
|
19
|
-
content: Schema.String.
|
|
20
|
-
citedTestErrorId: Schema.NullOr(Schema.Number).
|
|
21
|
-
citedStackFrameId: Schema.NullOr(Schema.Number).
|
|
22
|
-
validationOutcome: Schema.NullOr(Schema.
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
content: Schema.String.annotate({ description: "Free-text hypothesis the agent recorded before attempting a fix." }),
|
|
20
|
+
citedTestErrorId: Schema.NullOr(Schema.Number).annotate({ description: "Optional `test_errors.id` the hypothesis cites as the failing observation." }),
|
|
21
|
+
citedStackFrameId: Schema.NullOr(Schema.Number).annotate({ description: "Optional `stack_frames.id` for the specific frame the hypothesis blames." }),
|
|
22
|
+
validationOutcome: Schema.NullOr(Schema.Literals([
|
|
23
|
+
"confirmed",
|
|
24
|
+
"refuted",
|
|
25
|
+
"abandoned"
|
|
26
|
+
])).annotate({ description: "Outcome recorded by `hypothesis (action: validate)`; `null` while still open." }),
|
|
27
|
+
validatedAt: Schema.NullOr(Schema.String).annotate({ description: "ISO-8601 validation timestamp; `null` while open." })
|
|
28
|
+
}).annotate({ identifier: "HypothesisRow" });
|
|
25
29
|
const HypothesisRecordOk = Schema.Struct({
|
|
26
30
|
action: Schema.Literal("record"),
|
|
27
|
-
id: Schema.Number.
|
|
31
|
+
id: Schema.Number.annotate({ description: "Newly inserted hypothesis row primary key." })
|
|
28
32
|
});
|
|
29
33
|
const HypothesisValidateOk = Schema.Struct({ action: Schema.Literal("validate") });
|
|
30
34
|
const HypothesisListOk = Schema.Struct({
|
|
@@ -32,7 +36,11 @@ const HypothesisListOk = Schema.Struct({
|
|
|
32
36
|
count: Schema.Number,
|
|
33
37
|
hypotheses: Schema.Array(HypothesisRowSchema)
|
|
34
38
|
});
|
|
35
|
-
const HypothesisResult = Schema.Union(
|
|
39
|
+
const HypothesisResult = Schema.Union([
|
|
40
|
+
HypothesisRecordOk,
|
|
41
|
+
HypothesisValidateOk,
|
|
42
|
+
HypothesisListOk
|
|
43
|
+
]).annotate({
|
|
36
44
|
identifier: "HypothesisResult",
|
|
37
45
|
title: "hypothesis result",
|
|
38
46
|
description: "Discriminate on `action`. record returns the new id; list returns the matching rows; validate returns an empty acknowledgement."
|
|
@@ -58,18 +66,31 @@ const RecordVariant = Schema.Struct({
|
|
|
58
66
|
const ValidateVariant = Schema.Struct({
|
|
59
67
|
action: Schema.Literal("validate"),
|
|
60
68
|
id: Schema.Number,
|
|
61
|
-
outcome: Schema.
|
|
69
|
+
outcome: Schema.Literals([
|
|
70
|
+
"confirmed",
|
|
71
|
+
"refuted",
|
|
72
|
+
"abandoned"
|
|
73
|
+
]),
|
|
62
74
|
validatedTurnId: Schema.optional(Schema.Number),
|
|
63
75
|
validatedAt: Schema.String
|
|
64
76
|
});
|
|
65
77
|
const ListVariant = Schema.Struct({
|
|
66
78
|
action: Schema.Literal("list"),
|
|
67
79
|
sessionId: Schema.optional(Schema.Number),
|
|
68
|
-
outcome: Schema.optional(Schema.
|
|
80
|
+
outcome: Schema.optional(Schema.Literals([
|
|
81
|
+
"confirmed",
|
|
82
|
+
"refuted",
|
|
83
|
+
"abandoned",
|
|
84
|
+
"open"
|
|
85
|
+
])),
|
|
69
86
|
limit: Schema.optional(Schema.Number)
|
|
70
87
|
});
|
|
71
|
-
const HypothesisInput = Schema.Union(
|
|
72
|
-
|
|
88
|
+
const HypothesisInput = Schema.Union([
|
|
89
|
+
RecordVariant,
|
|
90
|
+
ValidateVariant,
|
|
91
|
+
ListVariant
|
|
92
|
+
]);
|
|
93
|
+
const hypothesis = idempotentProcedure.input(Schema.toStandardSchemaV1(HypothesisInput)).mutation(async ({ ctx, input }) => {
|
|
73
94
|
return ctx.runtime.runPromise(Match.value(input).pipe(Match.discriminatorsExhaustive("action")({
|
|
74
95
|
record: (variant) => Effect.gen(function* () {
|
|
75
96
|
const store = yield* DataStore;
|
package/tools/inventory.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { publicProcedure } from "../context.js";
|
|
2
2
|
import { DataReader } from "@vitest-agent/sdk";
|
|
3
|
-
import { Effect, Match, Option,
|
|
3
|
+
import { Effect, Match, Option, Schema, SchemaGetter } from "effect";
|
|
4
4
|
|
|
5
5
|
//#region src/tools/inventory.ts
|
|
6
6
|
/**
|
|
@@ -18,44 +18,48 @@ import { Effect, Match, Option, ParseResult, Schema } from "effect";
|
|
|
18
18
|
const ProjectRunSummary = Schema.Struct({
|
|
19
19
|
project: Schema.String,
|
|
20
20
|
lastRun: Schema.NullOr(Schema.String),
|
|
21
|
-
lastResult: Schema.NullOr(Schema.
|
|
21
|
+
lastResult: Schema.NullOr(Schema.Literals([
|
|
22
|
+
"passed",
|
|
23
|
+
"failed",
|
|
24
|
+
"interrupted"
|
|
25
|
+
])),
|
|
22
26
|
total: Schema.Number,
|
|
23
27
|
passed: Schema.Number,
|
|
24
28
|
failed: Schema.Number,
|
|
25
29
|
skipped: Schema.Number
|
|
26
|
-
}).
|
|
30
|
+
}).annotate({ identifier: "InventoryProjectRow" });
|
|
27
31
|
const ModuleRow = Schema.Struct({
|
|
28
32
|
id: Schema.Number,
|
|
29
33
|
file: Schema.String,
|
|
30
34
|
state: Schema.String,
|
|
31
35
|
testCount: Schema.Number,
|
|
32
36
|
duration: Schema.NullOr(Schema.Number)
|
|
33
|
-
}).
|
|
37
|
+
}).annotate({ identifier: "InventoryModuleRow" });
|
|
34
38
|
const SuiteRow = Schema.Struct({
|
|
35
39
|
id: Schema.Number,
|
|
36
40
|
name: Schema.String,
|
|
37
41
|
module: Schema.String,
|
|
38
42
|
state: Schema.String,
|
|
39
43
|
testCount: Schema.Number
|
|
40
|
-
}).
|
|
44
|
+
}).annotate({ identifier: "InventorySuiteRow" });
|
|
41
45
|
const SessionRow = Schema.Struct({
|
|
42
46
|
id: Schema.Number,
|
|
43
47
|
chatId: Schema.String,
|
|
44
48
|
project: Schema.String,
|
|
45
49
|
cwd: Schema.String,
|
|
46
|
-
agentKind: Schema.
|
|
50
|
+
agentKind: Schema.Literals(["main", "subagent"]),
|
|
47
51
|
agentType: Schema.NullOr(Schema.String),
|
|
48
52
|
parentSessionId: Schema.NullOr(Schema.Number),
|
|
49
53
|
triageWasNonEmpty: Schema.Boolean,
|
|
50
54
|
startedAt: Schema.String,
|
|
51
55
|
endedAt: Schema.NullOr(Schema.String),
|
|
52
56
|
endReason: Schema.NullOr(Schema.String)
|
|
53
|
-
}).
|
|
57
|
+
}).annotate({ identifier: "InventorySessionRow" });
|
|
54
58
|
const ProjectInventory = Schema.Struct({
|
|
55
59
|
inventoryKind: Schema.Literal("project"),
|
|
56
60
|
count: Schema.Number,
|
|
57
61
|
projects: Schema.Array(ProjectRunSummary)
|
|
58
|
-
}).
|
|
62
|
+
}).annotate({ identifier: "ProjectInventory" });
|
|
59
63
|
const ModuleGroup = Schema.Struct({
|
|
60
64
|
project: Schema.String,
|
|
61
65
|
modules: Schema.Array(ModuleRow)
|
|
@@ -64,7 +68,7 @@ const ModuleInventory = Schema.Struct({
|
|
|
64
68
|
inventoryKind: Schema.Literal("module"),
|
|
65
69
|
count: Schema.Number,
|
|
66
70
|
groups: Schema.Array(ModuleGroup)
|
|
67
|
-
}).
|
|
71
|
+
}).annotate({ identifier: "ModuleInventory" });
|
|
68
72
|
const SuiteGroup = Schema.Struct({
|
|
69
73
|
project: Schema.String,
|
|
70
74
|
suites: Schema.Array(SuiteRow)
|
|
@@ -73,50 +77,59 @@ const SuiteInventory = Schema.Struct({
|
|
|
73
77
|
inventoryKind: Schema.Literal("suite"),
|
|
74
78
|
count: Schema.Number,
|
|
75
79
|
groups: Schema.Array(SuiteGroup)
|
|
76
|
-
}).
|
|
80
|
+
}).annotate({ identifier: "SuiteInventory" });
|
|
77
81
|
const SessionDetailFound = Schema.Struct({
|
|
78
82
|
inventoryKind: Schema.Literal("session_detail"),
|
|
79
83
|
found: Schema.Literal(true),
|
|
80
84
|
session: SessionRow
|
|
81
|
-
}).
|
|
85
|
+
}).annotate({ identifier: "SessionDetailFound" });
|
|
82
86
|
const SessionDetailMissing = Schema.Struct({
|
|
83
87
|
inventoryKind: Schema.Literal("session_detail"),
|
|
84
88
|
found: Schema.Literal(false),
|
|
85
89
|
id: Schema.Number
|
|
86
|
-
}).
|
|
90
|
+
}).annotate({ identifier: "SessionDetailMissing" });
|
|
87
91
|
const SessionListInventory = Schema.Struct({
|
|
88
92
|
inventoryKind: Schema.Literal("session_list"),
|
|
89
93
|
count: Schema.Number,
|
|
90
94
|
sessions: Schema.Array(SessionRow)
|
|
91
|
-
}).
|
|
95
|
+
}).annotate({ identifier: "SessionListInventory" });
|
|
92
96
|
const TagProjectBreakdown = Schema.Struct({
|
|
93
97
|
project: Schema.String,
|
|
94
98
|
moduleCount: Schema.Number,
|
|
95
99
|
testCount: Schema.Number
|
|
96
|
-
}).
|
|
100
|
+
}).annotate({ identifier: "TagProjectBreakdown" });
|
|
97
101
|
const TagRowScoped = Schema.Struct({
|
|
98
102
|
tag: Schema.String,
|
|
99
103
|
moduleCount: Schema.Number,
|
|
100
104
|
testCount: Schema.Number
|
|
101
|
-
}).
|
|
105
|
+
}).annotate({ identifier: "TagRowScoped" });
|
|
102
106
|
const TagRowUnscoped = Schema.Struct({
|
|
103
107
|
tag: Schema.String,
|
|
104
108
|
moduleCount: Schema.Number,
|
|
105
109
|
testCount: Schema.Number,
|
|
106
110
|
byProject: Schema.Array(TagProjectBreakdown)
|
|
107
|
-
}).
|
|
111
|
+
}).annotate({ identifier: "TagRowUnscoped" });
|
|
108
112
|
const TagInventoryScoped = Schema.Struct({
|
|
109
113
|
inventoryKind: Schema.Literal("tag_scoped"),
|
|
110
114
|
project: Schema.String,
|
|
111
115
|
count: Schema.Number,
|
|
112
116
|
tags: Schema.Array(TagRowScoped)
|
|
113
|
-
}).
|
|
117
|
+
}).annotate({ identifier: "TagInventoryScoped" });
|
|
114
118
|
const TagInventoryUnscoped = Schema.Struct({
|
|
115
119
|
inventoryKind: Schema.Literal("tag_unscoped"),
|
|
116
120
|
count: Schema.Number,
|
|
117
121
|
tags: Schema.Array(TagRowUnscoped)
|
|
118
|
-
}).
|
|
119
|
-
const InventoryResult = Schema.Union(
|
|
122
|
+
}).annotate({ identifier: "TagInventoryUnscoped" });
|
|
123
|
+
const InventoryResult = Schema.Union([
|
|
124
|
+
ProjectInventory,
|
|
125
|
+
ModuleInventory,
|
|
126
|
+
SuiteInventory,
|
|
127
|
+
SessionDetailFound,
|
|
128
|
+
SessionDetailMissing,
|
|
129
|
+
SessionListInventory,
|
|
130
|
+
TagInventoryScoped,
|
|
131
|
+
TagInventoryUnscoped
|
|
132
|
+
]).annotate({
|
|
120
133
|
identifier: "InventoryResult",
|
|
121
134
|
title: "inventory result",
|
|
122
135
|
description: "Discriminate on `inventoryKind`. project/module/suite carry counted lists; session_detail discriminates further on `found`; session_list returns the matching sessions; tag_scoped and tag_unscoped carry per-tag counts (the unscoped form also carries a `byProject` breakdown per tag)."
|
|
@@ -209,11 +222,10 @@ const formatInventoryMarkdown = (data) => {
|
|
|
209
222
|
}
|
|
210
223
|
return lines.join("\n");
|
|
211
224
|
};
|
|
212
|
-
const InventoryAsMarkdown = Schema.
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
});
|
|
225
|
+
const InventoryAsMarkdown = InventoryResult.pipe(Schema.decodeTo(Schema.String, {
|
|
226
|
+
decode: SchemaGetter.transform((data) => formatInventoryMarkdown(data)),
|
|
227
|
+
encode: SchemaGetter.forbidden(() => "InventoryAsMarkdown is one-way.")
|
|
228
|
+
}));
|
|
217
229
|
const ProjectVariant = Schema.Struct({ kind: Schema.Literal("project") });
|
|
218
230
|
const ModuleVariant = Schema.Struct({
|
|
219
231
|
kind: Schema.Literal("module"),
|
|
@@ -228,15 +240,21 @@ const SessionVariant = Schema.Struct({
|
|
|
228
240
|
kind: Schema.Literal("session"),
|
|
229
241
|
id: Schema.optional(Schema.Number),
|
|
230
242
|
project: Schema.optional(Schema.String),
|
|
231
|
-
agentKind: Schema.optional(Schema.
|
|
243
|
+
agentKind: Schema.optional(Schema.Literals(["main", "subagent"])),
|
|
232
244
|
limit: Schema.optional(Schema.Number)
|
|
233
245
|
});
|
|
234
246
|
const TagVariant = Schema.Struct({
|
|
235
247
|
kind: Schema.Literal("tag"),
|
|
236
248
|
project: Schema.optional(Schema.String)
|
|
237
249
|
});
|
|
238
|
-
const InventoryInput = Schema.Union(
|
|
239
|
-
|
|
250
|
+
const InventoryInput = Schema.Union([
|
|
251
|
+
ProjectVariant,
|
|
252
|
+
ModuleVariant,
|
|
253
|
+
SuiteVariant,
|
|
254
|
+
SessionVariant,
|
|
255
|
+
TagVariant
|
|
256
|
+
]);
|
|
257
|
+
const inventory = publicProcedure.input(Schema.toStandardSchemaV1(InventoryInput)).query(async ({ ctx, input }) => {
|
|
240
258
|
return ctx.runtime.runPromise(Match.value(input).pipe(Match.discriminatorsExhaustive("kind")({
|
|
241
259
|
project: () => Effect.gen(function* () {
|
|
242
260
|
const projects = yield* (yield* DataReader).getRunsByProject();
|
package/tools/note.js
CHANGED
|
@@ -3,12 +3,19 @@ import { DataReader, DataStore } from "@vitest-agent/sdk";
|
|
|
3
3
|
import { Effect, Match, Option, Schema } from "effect";
|
|
4
4
|
|
|
5
5
|
//#region src/tools/note.ts
|
|
6
|
-
const NoteScope = Schema.
|
|
6
|
+
const NoteScope = Schema.Literals([
|
|
7
|
+
"global",
|
|
8
|
+
"project",
|
|
9
|
+
"module",
|
|
10
|
+
"suite",
|
|
11
|
+
"test",
|
|
12
|
+
"note"
|
|
13
|
+
]);
|
|
7
14
|
const NoteRowSchema = Schema.Struct({
|
|
8
|
-
id: Schema.Number.
|
|
15
|
+
id: Schema.Number.annotate({ description: "Note primary key." }),
|
|
9
16
|
title: Schema.String,
|
|
10
17
|
content: Schema.String,
|
|
11
|
-
scope: NoteScope.
|
|
18
|
+
scope: NoteScope.annotate({ description: "`global` (project-agnostic), `project`, `module`, `suite`, `test` (scoped), or `note` (child note attached via `parentNoteId`)." }),
|
|
12
19
|
project: Schema.NullOr(Schema.String),
|
|
13
20
|
testFullName: Schema.NullOr(Schema.String),
|
|
14
21
|
modulePath: Schema.NullOr(Schema.String),
|
|
@@ -18,15 +25,15 @@ const NoteRowSchema = Schema.Struct({
|
|
|
18
25
|
pinned: Schema.Boolean,
|
|
19
26
|
createdAt: Schema.String,
|
|
20
27
|
updatedAt: Schema.String
|
|
21
|
-
}).
|
|
28
|
+
}).annotate({ identifier: "NoteRow" });
|
|
22
29
|
const NoteCreateOk = Schema.Struct({
|
|
23
30
|
action: Schema.Literal("create"),
|
|
24
|
-
id: Schema.Number.
|
|
31
|
+
id: Schema.Number.annotate({ description: "Primary key of the newly inserted note." })
|
|
25
32
|
});
|
|
26
33
|
const NoteListOk = Schema.Struct({
|
|
27
34
|
action: Schema.Literal("list"),
|
|
28
35
|
count: Schema.Number,
|
|
29
|
-
notes: Schema.Array(NoteRowSchema).
|
|
36
|
+
notes: Schema.Array(NoteRowSchema).annotate({ description: "Notes matching the optional scope/project/test filters." })
|
|
30
37
|
});
|
|
31
38
|
const NoteGetFound = Schema.Struct({
|
|
32
39
|
action: Schema.Literal("get"),
|
|
@@ -50,9 +57,17 @@ const NoteSearchOk = Schema.Struct({
|
|
|
50
57
|
action: Schema.Literal("search"),
|
|
51
58
|
query: Schema.String,
|
|
52
59
|
count: Schema.Number,
|
|
53
|
-
notes: Schema.Array(NoteRowSchema).
|
|
60
|
+
notes: Schema.Array(NoteRowSchema).annotate({ description: "Notes whose title or content match the FTS5 query." })
|
|
54
61
|
});
|
|
55
|
-
const NoteResult = Schema.Union(
|
|
62
|
+
const NoteResult = Schema.Union([
|
|
63
|
+
NoteCreateOk,
|
|
64
|
+
NoteListOk,
|
|
65
|
+
NoteGetFound,
|
|
66
|
+
NoteGetMissing,
|
|
67
|
+
NoteUpdateOk,
|
|
68
|
+
NoteDeleteOk,
|
|
69
|
+
NoteSearchOk
|
|
70
|
+
]).annotate({
|
|
56
71
|
identifier: "NoteResult",
|
|
57
72
|
title: "note result",
|
|
58
73
|
description: "Discriminate on `action`. `get` further discriminates on `found`."
|
|
@@ -129,8 +144,15 @@ const SearchVariant = Schema.Struct({
|
|
|
129
144
|
action: Schema.Literal("search"),
|
|
130
145
|
query: Schema.String
|
|
131
146
|
});
|
|
132
|
-
const NoteInputUnion = Schema.Union(
|
|
133
|
-
|
|
147
|
+
const NoteInputUnion = Schema.Union([
|
|
148
|
+
CreateVariant,
|
|
149
|
+
ListVariant,
|
|
150
|
+
GetVariant,
|
|
151
|
+
UpdateVariant,
|
|
152
|
+
DeleteVariant,
|
|
153
|
+
SearchVariant
|
|
154
|
+
]);
|
|
155
|
+
const note = publicProcedure.input(Schema.toStandardSchemaV1(NoteInputUnion)).mutation(async ({ ctx, input }) => {
|
|
134
156
|
return ctx.runtime.runPromise(Match.value(input).pipe(Match.discriminatorsExhaustive("action")({
|
|
135
157
|
create: (variant) => Effect.gen(function* () {
|
|
136
158
|
const store = yield* DataStore;
|
package/tools/overview.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { publicProcedure } from "../context.js";
|
|
2
2
|
import { DataReader } from "@vitest-agent/sdk";
|
|
3
|
-
import { Effect, Option,
|
|
3
|
+
import { Effect, Option, Schema, SchemaGetter } from "effect";
|
|
4
4
|
|
|
5
5
|
//#region src/tools/overview.ts
|
|
6
6
|
/**
|
|
@@ -11,12 +11,16 @@ import { Effect, Option, ParseResult, Schema } from "effect";
|
|
|
11
11
|
const ProjectRunSummary = Schema.Struct({
|
|
12
12
|
project: Schema.String,
|
|
13
13
|
lastRun: Schema.NullOr(Schema.String),
|
|
14
|
-
lastResult: Schema.NullOr(Schema.
|
|
14
|
+
lastResult: Schema.NullOr(Schema.Literals([
|
|
15
|
+
"passed",
|
|
16
|
+
"failed",
|
|
17
|
+
"interrupted"
|
|
18
|
+
])),
|
|
15
19
|
total: Schema.Number,
|
|
16
20
|
passed: Schema.Number,
|
|
17
21
|
failed: Schema.Number,
|
|
18
22
|
skipped: Schema.Number
|
|
19
|
-
}).
|
|
23
|
+
}).annotate({
|
|
20
24
|
identifier: "ProjectRunSummary",
|
|
21
25
|
description: "One row per project's most recent run summary."
|
|
22
26
|
});
|
|
@@ -24,13 +28,13 @@ const OverviewAvailable = Schema.Struct({
|
|
|
24
28
|
dataAvailable: Schema.Literal(true),
|
|
25
29
|
projectFilter: Schema.optional(Schema.String),
|
|
26
30
|
runs: Schema.Array(ProjectRunSummary)
|
|
27
|
-
}).
|
|
31
|
+
}).annotate({ identifier: "TestOverviewAvailable" });
|
|
28
32
|
const OverviewAbsent = Schema.Struct({
|
|
29
33
|
dataAvailable: Schema.Literal(false),
|
|
30
34
|
projectFilter: Schema.optional(Schema.String),
|
|
31
|
-
reason: Schema.
|
|
32
|
-
}).
|
|
33
|
-
const TestOverviewResult = Schema.Union(OverviewAvailable, OverviewAbsent).
|
|
35
|
+
reason: Schema.Literals(["no_runs", "project_filter_empty"])
|
|
36
|
+
}).annotate({ identifier: "TestOverviewAbsent" });
|
|
37
|
+
const TestOverviewResult = Schema.Union([OverviewAvailable, OverviewAbsent]).annotate({
|
|
34
38
|
identifier: "TestOverviewResult",
|
|
35
39
|
title: "test_overview result",
|
|
36
40
|
description: "Per-project run metrics. Discriminate on `dataAvailable` for cold-start handling."
|
|
@@ -62,12 +66,11 @@ const formatTestOverviewMarkdown = (data) => {
|
|
|
62
66
|
}
|
|
63
67
|
return lines.join("\n");
|
|
64
68
|
};
|
|
65
|
-
const TestOverviewAsMarkdown = Schema.
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
})
|
|
70
|
-
const testOverview = publicProcedure.input(Schema.standardSchemaV1(Schema.Struct({ project: Schema.optional(Schema.String) }))).query(async ({ ctx, input }) => ctx.runtime.runPromise(Effect.gen(function* () {
|
|
69
|
+
const TestOverviewAsMarkdown = TestOverviewResult.pipe(Schema.decodeTo(Schema.String, {
|
|
70
|
+
decode: SchemaGetter.transform((data) => formatTestOverviewMarkdown(data)),
|
|
71
|
+
encode: SchemaGetter.forbidden(() => "TestOverviewAsMarkdown is one-way.")
|
|
72
|
+
}));
|
|
73
|
+
const testOverview = publicProcedure.input(Schema.toStandardSchemaV1(Schema.Struct({ project: Schema.optional(Schema.String) }))).query(async ({ ctx, input }) => ctx.runtime.runPromise(Effect.gen(function* () {
|
|
71
74
|
const reader = yield* DataReader;
|
|
72
75
|
const [manifestOpt, runs] = yield* Effect.all([reader.getManifest(), reader.getRunsByProject()]);
|
|
73
76
|
if (Option.isNone(manifestOpt) || runs.length === 0) return {
|
package/tools/ping.js
CHANGED
|
@@ -11,7 +11,7 @@ import { Schema } from "effect";
|
|
|
11
11
|
*
|
|
12
12
|
* @packageDocumentation
|
|
13
13
|
*/
|
|
14
|
-
const PingResult = Schema.Struct({ message: Schema.Literal("pong").
|
|
14
|
+
const PingResult = Schema.Struct({ message: Schema.Literal("pong").annotate({ description: "Constant `pong`. Presence confirms the MCP server responded." }) }).annotate({
|
|
15
15
|
identifier: "PingResult",
|
|
16
16
|
title: "ping result",
|
|
17
17
|
description: "Liveness probe. Carries no data beyond the constant `pong` discriminant."
|
package/tools/register-agent.js
CHANGED
|
@@ -43,29 +43,34 @@ const RegisterAgentInput = Schema.Struct({
|
|
|
43
43
|
startWorktreeDir: Schema.optional(Schema.String)
|
|
44
44
|
});
|
|
45
45
|
const RegisterAgentSuccess = Schema.Struct({
|
|
46
|
-
ok: Schema.Literal(true).
|
|
47
|
-
agentId: Schema.String.
|
|
46
|
+
ok: Schema.Literal(true).annotate({ description: "Discriminant — `true` when the agent row was inserted (or an existing one recovered)." }),
|
|
47
|
+
agentId: Schema.String.annotate({
|
|
48
48
|
title: "agents.agent_id",
|
|
49
49
|
description: "Canonical UUID for the registered agent — pass to subsequent attribution-bearing calls."
|
|
50
50
|
}),
|
|
51
|
-
conversationId: Schema.NullOr(Schema.String).
|
|
52
|
-
idempotencyKey: Schema.String.
|
|
53
|
-
}).
|
|
51
|
+
conversationId: Schema.NullOr(Schema.String).annotate({ description: "Conversation UUID from the host's transcript when one was supplied; `null` otherwise." }),
|
|
52
|
+
idempotencyKey: Schema.String.annotate({ description: "26-char base32 SHA-256 of (agentType, parentAgentId|sentinel, clientNonce). Stable across retries with identical input." })
|
|
53
|
+
}).annotate({ identifier: "RegisterAgentSuccess" });
|
|
54
54
|
const RegisterAgentFailure = Schema.Struct({
|
|
55
|
-
ok: Schema.Literal(false).
|
|
55
|
+
ok: Schema.Literal(false).annotate({ description: "Discriminant — `false` when registration was refused." }),
|
|
56
56
|
error: Schema.Struct({
|
|
57
|
-
code: Schema.
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
57
|
+
code: Schema.Literals([
|
|
58
|
+
"AGENT_ALREADY_REGISTERED",
|
|
59
|
+
"PARENT_AGENT_NOT_FOUND",
|
|
60
|
+
"SESSION_NOT_FOUND",
|
|
61
|
+
"INVALID_AGENT_TYPE_PREFIX"
|
|
62
|
+
]).annotate({ description: "Refusal reason. AGENT_ALREADY_REGISTERED carries `existingAgentId` so the caller can recover. INVALID_AGENT_TYPE_PREFIX carries `expectedPrefix`." }),
|
|
63
|
+
message: Schema.String.annotate({ description: "Human-readable refusal explanation." }),
|
|
64
|
+
existingAgentId: Schema.optional(Schema.String).annotate({ description: "Present only when `code = AGENT_ALREADY_REGISTERED`. Use this id instead of registering a new one." }),
|
|
65
|
+
expectedPrefix: Schema.optional(Schema.String).annotate({ description: "Present only when `code = INVALID_AGENT_TYPE_PREFIX`. The required `<hostKind>-` prefix." })
|
|
61
66
|
})
|
|
62
|
-
}).
|
|
63
|
-
const RegisterAgentResult = Schema.Union(RegisterAgentSuccess, RegisterAgentFailure).
|
|
67
|
+
}).annotate({ identifier: "RegisterAgentFailure" });
|
|
68
|
+
const RegisterAgentResult = Schema.Union([RegisterAgentSuccess, RegisterAgentFailure]).annotate({
|
|
64
69
|
identifier: "RegisterAgentResult",
|
|
65
70
|
title: "register_agent result",
|
|
66
71
|
description: "Discriminate on `ok`. The four failure codes are documented per their `code` literal."
|
|
67
72
|
});
|
|
68
|
-
const registerAgent = publicProcedure.input(Schema.
|
|
73
|
+
const registerAgent = publicProcedure.input(Schema.toStandardSchemaV1(RegisterAgentInput)).mutation(async ({ ctx, input }) => {
|
|
69
74
|
const expectedPrefix = `${input.hostKind ?? "claude-code"}-`;
|
|
70
75
|
if (!input.agentType.startsWith(expectedPrefix)) return {
|
|
71
76
|
ok: false,
|