@vitest-agent/mcp 4.2.1 → 4.3.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/annotations.js +7 -7
- package/index.d.ts +7 -7
- package/package.json +2 -2
- package/prompts/layer.js +6 -0
- package/register-toolkit.js +28 -58
- package/server.js +1 -1
- package/tools/acceptance-metrics.js +5 -19
- package/tools/cache-health.js +4 -36
- package/tools/commit-changes.js +4 -25
- package/tools/configure.js +4 -39
- package/tools/coverage.js +4 -54
- package/tools/errors.js +4 -83
- package/tools/failure-signature-get.js +4 -27
- package/tools/file-coverage.js +4 -36
- package/tools/help.js +6 -8
- package/tools/history.js +4 -40
- package/tools/hypothesis.js +3 -16
- package/tools/inventory.js +3 -96
- package/tools/note.js +3 -43
- package/tools/overview.js +4 -36
- package/tools/run-tests.js +3 -129
- package/tools/settings-list.js +4 -20
- package/tools/status.js +4 -28
- package/tools/tdd-artifact.js +3 -27
- package/tools/tdd-phase-transition-request.js +1 -2
- package/tools/tdd-progress-push.js +1 -2
- package/tools/tdd-task.js +4 -64
- package/tools/test.js +3 -145
- package/tools/trends.js +4 -70
- package/tools/triage-brief.js +3 -5
- package/tools/turn-search.js +4 -15
- package/tools/wrapup-prompt.js +3 -5
- package/version.js +1 -1
package/tools/errors.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Effect, Schema, SchemaGetter } from "effect";
|
|
1
|
+
import { Effect, Schema } from "effect";
|
|
3
2
|
import { DataReader } from "@vitest-agent/engine";
|
|
4
3
|
import { Tool } from "effect/unstable/ai";
|
|
5
4
|
|
|
@@ -63,84 +62,6 @@ const TestErrorsResult = Schema.Struct({
|
|
|
63
62
|
title: "test_errors result",
|
|
64
63
|
description: "Structured payload of the `test_errors` MCP tool. Carries the cite-able test_errors.id and stack_frames.id values agents need for `hypothesis (action: record)`."
|
|
65
64
|
});
|
|
66
|
-
const TRUNCATION_LIMIT = 500;
|
|
67
|
-
const truncate = (s) => s.length <= TRUNCATION_LIMIT ? {
|
|
68
|
-
value: s,
|
|
69
|
-
truncated: false
|
|
70
|
-
} : {
|
|
71
|
-
value: s.slice(0, TRUNCATION_LIMIT),
|
|
72
|
-
truncated: true
|
|
73
|
-
};
|
|
74
|
-
/**
|
|
75
|
-
* Pure markdown renderer. Exposed so the formatter tests can exercise
|
|
76
|
-
* it without rebuilding a `Schema.encode` runtime.
|
|
77
|
-
*/
|
|
78
|
-
const formatTestErrorsMarkdown = (data) => {
|
|
79
|
-
if (data.errors.length === 0) return `No errors found for project \`${data.project}\`.`;
|
|
80
|
-
const lines = [`# Test Errors — ${data.project}`, ""];
|
|
81
|
-
for (const error of data.errors) {
|
|
82
|
-
const name = error.name ?? "(unnamed)";
|
|
83
|
-
const idTokens = `[testErrorId=${error.id}${error.topStackFrameId !== null ? ` topStackFrameId=${error.topStackFrameId}` : ""}]`;
|
|
84
|
-
lines.push(`## ${name} ${idTokens}`);
|
|
85
|
-
lines.push("");
|
|
86
|
-
lines.push(`**Scope:** ${error.scope}`);
|
|
87
|
-
if (error.testFullName !== null) lines.push(`**Test:** ${error.testFullName}`);
|
|
88
|
-
if (error.moduleFile !== null) lines.push(`**File:** \`${error.moduleFile}\``);
|
|
89
|
-
if (error.annotations.length > 0) {
|
|
90
|
-
lines.push("**Annotations:**");
|
|
91
|
-
for (const annotation of error.annotations) lines.push(`- [${annotation.type}] ${annotation.message}`);
|
|
92
|
-
}
|
|
93
|
-
lines.push("");
|
|
94
|
-
lines.push("**Cite-able IDs (for `hypothesis (action: record)`):**");
|
|
95
|
-
lines.push(`- citedTestErrorId: ${error.id}`);
|
|
96
|
-
if (error.topStackFrameId !== null) lines.push(`- citedStackFrameId: ${error.topStackFrameId}`);
|
|
97
|
-
else lines.push("- citedStackFrameId: (none — no stack frames recorded for this error)");
|
|
98
|
-
lines.push("");
|
|
99
|
-
lines.push("**Message:**");
|
|
100
|
-
lines.push(`> ${error.message.split("\n").join("\n> ")}`);
|
|
101
|
-
if (error.diff !== null) {
|
|
102
|
-
lines.push("");
|
|
103
|
-
lines.push("**Diff:**");
|
|
104
|
-
lines.push("```diff");
|
|
105
|
-
const t = truncate(error.diff);
|
|
106
|
-
lines.push(t.value);
|
|
107
|
-
if (t.truncated) lines.push("... (truncated)");
|
|
108
|
-
lines.push("```");
|
|
109
|
-
}
|
|
110
|
-
if (error.stack !== null && error.diff === null) {
|
|
111
|
-
lines.push("");
|
|
112
|
-
lines.push("**Stack:**");
|
|
113
|
-
lines.push("```");
|
|
114
|
-
const t = truncate(error.stack);
|
|
115
|
-
lines.push(t.value);
|
|
116
|
-
if (t.truncated) lines.push("... (truncated)");
|
|
117
|
-
lines.push("```");
|
|
118
|
-
}
|
|
119
|
-
lines.push("");
|
|
120
|
-
}
|
|
121
|
-
return lines.join("\n");
|
|
122
|
-
};
|
|
123
|
-
/**
|
|
124
|
-
* One-way codec: structured `TestErrorsResult` → markdown text.
|
|
125
|
-
*
|
|
126
|
-
* `Schema.decodeTo`'s `decode` direction goes
|
|
127
|
-
* `From.Type → To.Encoded`; in this transform `From = TestErrorsResult`
|
|
128
|
-
* and `To = Schema.String`, so the resulting schema is
|
|
129
|
-
* `Schema<string, TestErrorsResultType>` — its parsed Type is the
|
|
130
|
-
* markdown string and its Encoded form is the structured row. That
|
|
131
|
-
* means `Schema.decode(TestErrorsAsMarkdown)(data)` produces markdown
|
|
132
|
-
* (the rendering direction) and `Schema.encode(...)` would attempt
|
|
133
|
-
* the lossy reverse, which is forbidden here.
|
|
134
|
-
*
|
|
135
|
-
* Boundary callers should use
|
|
136
|
-
* `Schema.decodeSync(TestErrorsAsMarkdown)(data)` to render. Test
|
|
137
|
-
* suites can drive the same path without mocking anything — the
|
|
138
|
-
* transform IS the rendering contract.
|
|
139
|
-
*/
|
|
140
|
-
const TestErrorsAsMarkdown = TestErrorsResult.pipe(Schema.decodeTo(Schema.String, {
|
|
141
|
-
decode: SchemaGetter.transform((data) => formatTestErrorsMarkdown(data)),
|
|
142
|
-
encode: SchemaGetter.forbidden(() => "TestErrorsAsMarkdown is one-way: markdown cannot be parsed back to TestErrorsResult. Consume the procedure's structured output (or MCP structuredContent) directly.")
|
|
143
|
-
}));
|
|
144
65
|
/**
|
|
145
66
|
* The `test_errors` tool's parameters.
|
|
146
67
|
*
|
|
@@ -196,11 +117,11 @@ const handleTestErrors = (input) => Effect.gen(function* () {
|
|
|
196
117
|
* @public
|
|
197
118
|
*/
|
|
198
119
|
const testErrorsTool = Tool.make("test_errors", {
|
|
199
|
-
description: "Use when a test fails and you need error detail, diffs, and the cite-able test_errors.id / stack_frames.id values needed by hypothesis (action: record). Returns
|
|
120
|
+
description: "Use when a test fails and you need error detail, diffs, and the cite-able test_errors.id / stack_frames.id values needed by hypothesis (action: record). Returns a typed JSON object in structuredContent; read structuredContent.errors[].",
|
|
200
121
|
parameters: TestErrorsInput,
|
|
201
122
|
success: TestErrorsResult,
|
|
202
123
|
dependencies: [DataReader]
|
|
203
|
-
}).annotate(Tool.Title, "Test errors").annotate(Tool.Readonly, true).annotate(Tool.Destructive, false).annotate(Tool.OpenWorld, false).annotate(Tool.Idempotent, true)
|
|
124
|
+
}).annotate(Tool.Title, "Test errors").annotate(Tool.Readonly, true).annotate(Tool.Destructive, false).annotate(Tool.OpenWorld, false).annotate(Tool.Idempotent, true);
|
|
204
125
|
|
|
205
126
|
//#endregion
|
|
206
|
-
export { TestErrorAnnotation, TestErrorRow, TestErrorsInput, TestErrorsResult,
|
|
127
|
+
export { TestErrorAnnotation, TestErrorRow, TestErrorsInput, TestErrorsResult, handleTestErrors, testErrorsTool };
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Effect, Option, Schema, SchemaGetter } from "effect";
|
|
1
|
+
import { Effect, Option, Schema } from "effect";
|
|
3
2
|
import { DataReader } from "@vitest-agent/engine";
|
|
4
3
|
import { Tool } from "effect/unstable/ai";
|
|
5
4
|
|
|
@@ -35,28 +34,6 @@ const FailureSignatureGetResult = Schema.Union([SignatureFound, SignatureMissing
|
|
|
35
34
|
title: "failure_signature_get result",
|
|
36
35
|
description: "Discriminate on `found`. Found rows carry first/last-seen timestamps and recent occurrences."
|
|
37
36
|
});
|
|
38
|
-
const formatFailureSignatureMarkdown = (data) => {
|
|
39
|
-
if (!data.found) return `No failure signature found with hash=${data.requestedHash}.`;
|
|
40
|
-
const lines = [
|
|
41
|
-
`# Failure Signature \`${data.signatureHash}\``,
|
|
42
|
-
"",
|
|
43
|
-
`**Hash:** ${data.signatureHash}`,
|
|
44
|
-
"",
|
|
45
|
-
`- first_seen_at: ${data.firstSeenAt}`,
|
|
46
|
-
`- last_seen_at: ${data.lastSeenAt ?? "unknown"}`,
|
|
47
|
-
`- first_seen_run_id: ${data.firstSeenRunId ?? "unknown"}`,
|
|
48
|
-
`- occurrence_count: ${data.occurrenceCount}`
|
|
49
|
-
];
|
|
50
|
-
if (data.recentErrors.length > 0) {
|
|
51
|
-
lines.push("", "## Recent Errors", "");
|
|
52
|
-
for (const e of data.recentErrors) lines.push(`- run=${e.runId} name=${e.errorName ?? "(none)"}: ${e.message.slice(0, 120)}`);
|
|
53
|
-
}
|
|
54
|
-
return lines.join("\n");
|
|
55
|
-
};
|
|
56
|
-
const FailureSignatureGetAsMarkdown = FailureSignatureGetResult.pipe(Schema.decodeTo(Schema.String, {
|
|
57
|
-
decode: SchemaGetter.transform((data) => formatFailureSignatureMarkdown(data)),
|
|
58
|
-
encode: SchemaGetter.forbidden(() => "FailureSignatureGetAsMarkdown is one-way.")
|
|
59
|
-
}));
|
|
60
37
|
/**
|
|
61
38
|
* The `failure_signature_get` tool's parameters.
|
|
62
39
|
*
|
|
@@ -85,11 +62,11 @@ const handleFailureSignatureGet = (input) => Effect.gen(function* () {
|
|
|
85
62
|
* @public
|
|
86
63
|
*/
|
|
87
64
|
const failureSignatureGetTool = Tool.make("failure_signature_get", {
|
|
88
|
-
description: "Use when you have a failure-signature hash and need its first-seen date and occurrence history. Returns
|
|
65
|
+
description: "Use when you have a failure-signature hash and need its first-seen date and occurrence history. Returns a typed JSON object in structuredContent ({ found, signatureHash?, firstSeenAt?, occurrenceCount?, recentErrors?[] } or absent variant).",
|
|
89
66
|
parameters: FailureSignatureGetInput,
|
|
90
67
|
success: FailureSignatureGetResult,
|
|
91
68
|
dependencies: [DataReader]
|
|
92
|
-
}).annotate(Tool.Title, "Failure signature").annotate(Tool.Readonly, true).annotate(Tool.Destructive, false).annotate(Tool.OpenWorld, false).annotate(Tool.Idempotent, true)
|
|
69
|
+
}).annotate(Tool.Title, "Failure signature").annotate(Tool.Readonly, true).annotate(Tool.Destructive, false).annotate(Tool.OpenWorld, false).annotate(Tool.Idempotent, true);
|
|
93
70
|
|
|
94
71
|
//#endregion
|
|
95
|
-
export { FailureSignatureGetInput, FailureSignatureGetResult, failureSignatureGetTool,
|
|
72
|
+
export { FailureSignatureGetInput, FailureSignatureGetResult, failureSignatureGetTool, handleFailureSignatureGet };
|
package/tools/file-coverage.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Effect, Option, Schema, SchemaGetter } from "effect";
|
|
1
|
+
import { Effect, Option, Schema } from "effect";
|
|
3
2
|
import { DataReader } from "@vitest-agent/engine";
|
|
4
3
|
import { Tool } from "effect/unstable/ai";
|
|
5
4
|
import { CoverageTotals, FileCoverageReport } from "@vitest-agent/sdk";
|
|
@@ -44,37 +43,6 @@ const FileCoverageResult = Schema.Union([
|
|
|
44
43
|
title: "file_coverage result",
|
|
45
44
|
description: "Per-file coverage with related tests. Discriminate on `dataAvailable` then on `matched`."
|
|
46
45
|
});
|
|
47
|
-
const formatFileCoverageMarkdown = (data) => {
|
|
48
|
-
if (!data.dataAvailable) return "No coverage data available. Run tests with coverage enabled.";
|
|
49
|
-
const lines = [`# Coverage: \`${data.filePath}\``, ""];
|
|
50
|
-
const metrics = [
|
|
51
|
-
"statements",
|
|
52
|
-
"branches",
|
|
53
|
-
"functions",
|
|
54
|
-
"lines"
|
|
55
|
-
];
|
|
56
|
-
if (data.matched) {
|
|
57
|
-
lines.push("## Metrics", "", "| Metric | Value | Threshold |", "| --- | --- | --- |");
|
|
58
|
-
for (const metric of metrics) {
|
|
59
|
-
const value = data.report.summary[metric];
|
|
60
|
-
const threshold = data.globalThresholds[metric];
|
|
61
|
-
const thresholdStr = threshold !== void 0 ? `${threshold}%` : "—";
|
|
62
|
-
const icon = threshold !== void 0 && value < threshold ? "❌" : "✅";
|
|
63
|
-
lines.push(`| ${metric} | ${icon} ${value.toFixed(2)}% | ${thresholdStr} |`);
|
|
64
|
-
}
|
|
65
|
-
if (data.report.uncoveredLines) lines.push("", "## Uncovered Lines", "", `\`${data.report.uncoveredLines}\``);
|
|
66
|
-
lines.push("", "## Next steps", "", "- Use test({ action: \"for_file\" }) to find tests covering this file", "- Write tests targeting the uncovered lines");
|
|
67
|
-
} else lines.push("This file is not in the low-coverage list.", "", "Possible reasons:", "- File meets all coverage thresholds", "- File was not included in the coverage run", "- File path does not match any tracked source file", "", "## Project Coverage Totals", "", "| Metric | Value |", "| --- | --- |", `| statements | ${data.totals.statements.toFixed(2)}% |`, `| branches | ${data.totals.branches.toFixed(2)}% |`, `| functions | ${data.totals.functions.toFixed(2)}% |`, `| lines | ${data.totals.lines.toFixed(2)}% |`);
|
|
68
|
-
if (data.relatedTestFiles.length > 0) {
|
|
69
|
-
lines.push("", "## Tests Covering This File", "");
|
|
70
|
-
for (const tf of data.relatedTestFiles) lines.push(`- \`${tf}\``);
|
|
71
|
-
}
|
|
72
|
-
return lines.join("\n");
|
|
73
|
-
};
|
|
74
|
-
const FileCoverageAsMarkdown = FileCoverageResult.pipe(Schema.decodeTo(Schema.String, {
|
|
75
|
-
decode: SchemaGetter.transform((data) => formatFileCoverageMarkdown(data)),
|
|
76
|
-
encode: SchemaGetter.forbidden(() => "FileCoverageAsMarkdown is one-way.")
|
|
77
|
-
}));
|
|
78
46
|
/**
|
|
79
47
|
* The `file_coverage` tool's parameters.
|
|
80
48
|
*
|
|
@@ -123,11 +91,11 @@ const handleFileCoverage = (input) => Effect.gen(function* () {
|
|
|
123
91
|
* @public
|
|
124
92
|
*/
|
|
125
93
|
const fileCoverageTool = Tool.make("file_coverage", {
|
|
126
|
-
description: "Use when you need coverage for one source file: per-metric values, uncovered lines, and related tests. Returns
|
|
94
|
+
description: "Use when you need coverage for one source file: per-metric values, uncovered lines, and related tests. Returns a typed JSON object in structuredContent ({ dataAvailable, matched?, filePath, report?, totals?, relatedTestFiles[] }).",
|
|
127
95
|
parameters: FileCoverageInput,
|
|
128
96
|
success: FileCoverageResult,
|
|
129
97
|
dependencies: [DataReader]
|
|
130
|
-
}).annotate(Tool.Title, "File coverage").annotate(Tool.Readonly, true).annotate(Tool.Destructive, false).annotate(Tool.OpenWorld, false).annotate(Tool.Idempotent, true)
|
|
98
|
+
}).annotate(Tool.Title, "File coverage").annotate(Tool.Readonly, true).annotate(Tool.Destructive, false).annotate(Tool.OpenWorld, false).annotate(Tool.Idempotent, true);
|
|
131
99
|
|
|
132
100
|
//#endregion
|
|
133
|
-
export { FileCoverageInput, FileCoverageResult, fileCoverageTool,
|
|
101
|
+
export { FileCoverageInput, FileCoverageResult, fileCoverageTool, handleFileCoverage };
|
package/tools/help.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { RenderText } from "../annotations.js";
|
|
2
1
|
import { Effect, Schema } from "effect";
|
|
3
2
|
import { Tool } from "effect/unstable/ai";
|
|
4
3
|
|
|
@@ -11,7 +10,7 @@ import { Tool } from "effect/unstable/ai";
|
|
|
11
10
|
const HelpResult = Schema.Struct({ helpText: Schema.String.annotate({ description: "Markdown table of every MCP tool with parameters and a one-line description." }) }).annotate({
|
|
12
11
|
identifier: "HelpResult",
|
|
13
12
|
title: "help result",
|
|
14
|
-
description: "Static help reference. Read structuredContent.helpText
|
|
13
|
+
description: "Static help reference. Read structuredContent.helpText."
|
|
15
14
|
});
|
|
16
15
|
/**
|
|
17
16
|
* The static markdown `help` returns. Exported so `__test__/help-drift.test.ts`
|
|
@@ -43,7 +42,7 @@ const HELP_TEXT = `# vitest-agent MCP Tools
|
|
|
43
42
|
| \`file_coverage\` | \`filePath\`, \`project?\` | Per-file coverage with uncovered lines and related tests |
|
|
44
43
|
| \`test_history\` | \`project\` | Flaky/persistent/recovered tests |
|
|
45
44
|
| \`test_trends\` | \`project\`, \`limit?\` | Coverage trajectory over time |
|
|
46
|
-
| \`test_errors\` | \`project\`, \`errorName
|
|
45
|
+
| \`test_errors\` | \`project\`, \`errorName?\` | Errors with diffs, stacks, each row's \`annotations[]\` (the test annotations the author recorded), and the cite-able \`testErrorId\` / \`topStackFrameId\` values needed by \`hypothesis (action: record)\` |
|
|
47
46
|
| \`test\` | \`action\` (\`list\`/\`get\`/\`for_file\`/\`for_tag\`/\`annotations\`/\`artifacts\`), plus per-action params | Consolidated test inspection: list/get/for_file/for_tag/annotations/artifacts |
|
|
48
47
|
|
|
49
48
|
\`test\` actions:
|
|
@@ -169,16 +168,15 @@ Six framing-only prompts (\`prompts/get\`; Claude Code surfaces them as slash co
|
|
|
169
168
|
- \`scope\` accepts: \`global\`, \`project\`, \`module\`, \`suite\`, \`test\`, \`note\`
|
|
170
169
|
`;
|
|
171
170
|
/**
|
|
172
|
-
* The Effect-native `help` tool.
|
|
173
|
-
*
|
|
174
|
-
* same string for programmatic readers.
|
|
171
|
+
* The Effect-native `help` tool. `structuredContent.helpText` carries the
|
|
172
|
+
* markdown reference.
|
|
175
173
|
*
|
|
176
174
|
* @public
|
|
177
175
|
*/
|
|
178
176
|
const helpTool = Tool.make("help", {
|
|
179
|
-
description: "List all available MCP tools with parameters.
|
|
177
|
+
description: "List all available MCP tools with parameters. The markdown reference is in structuredContent.helpText.",
|
|
180
178
|
success: HelpResult
|
|
181
|
-
}).annotate(Tool.Title, "Help").annotate(Tool.Readonly, true).annotate(Tool.Destructive, false).annotate(Tool.OpenWorld, false).annotate(Tool.Idempotent, true)
|
|
179
|
+
}).annotate(Tool.Title, "Help").annotate(Tool.Readonly, true).annotate(Tool.Destructive, false).annotate(Tool.OpenWorld, false).annotate(Tool.Idempotent, true);
|
|
182
180
|
/**
|
|
183
181
|
* Handler for {@link helpTool}.
|
|
184
182
|
*
|
package/tools/history.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Effect, Schema, SchemaGetter } from "effect";
|
|
1
|
+
import { Effect, Schema } from "effect";
|
|
3
2
|
import { DataReader } from "@vitest-agent/engine";
|
|
4
3
|
import { Tool } from "effect/unstable/ai";
|
|
5
4
|
import { HistoryRecord } from "@vitest-agent/sdk";
|
|
@@ -54,41 +53,6 @@ const TestHistoryResult = Schema.Struct({
|
|
|
54
53
|
title: "test_history result",
|
|
55
54
|
description: "Per-project flaky/persistent/recovered test classifications computed from `test_runs` history."
|
|
56
55
|
});
|
|
57
|
-
const formatTestHistoryMarkdown = (data) => {
|
|
58
|
-
if (!data.hasData) return `No history data available for project \`${data.project}\`. Run tests first.`;
|
|
59
|
-
const lines = [`# Test History: ${data.project}`, ""];
|
|
60
|
-
if (data.flaky.length > 0) {
|
|
61
|
-
lines.push("## Flaky Tests", "", "Tests with mixed pass/fail results across recent runs:", "");
|
|
62
|
-
for (const test of data.flaky) {
|
|
63
|
-
const total = test.passCount + test.failCount;
|
|
64
|
-
const passRate = total > 0 ? (test.passCount / total * 100).toFixed(0) : "0";
|
|
65
|
-
lines.push(`### ⚠️ ${test.fullName}`, "", `- Module: \`${test.modulePath}\``, `- Pass rate: ${passRate}% (${test.passCount}/${total})`, `- Last state: ${test.lastState}`, `- Last run: ${new Date(test.lastTimestamp).toLocaleString()}`, "");
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
if (data.persistent.length > 0) {
|
|
69
|
-
lines.push("## Persistent Failures", "", "Tests that have failed in consecutive runs:", "");
|
|
70
|
-
for (const failure of data.persistent) {
|
|
71
|
-
lines.push(`### ❌ ${failure.fullName}`, "", `- Module: \`${failure.modulePath}\``, `- Consecutive failures: ${failure.consecutiveFailures}`, `- First failed: ${new Date(failure.firstFailedAt).toLocaleString()}`, `- Last failed: ${new Date(failure.lastFailedAt).toLocaleString()}`);
|
|
72
|
-
if (failure.lastErrorMessage !== null) lines.push(`- Last error: ${failure.lastErrorMessage}`);
|
|
73
|
-
lines.push("");
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
if (data.recovered.length > 0) {
|
|
77
|
-
lines.push("## Recovered Tests", "", "Tests that previously failed but are now passing:", "");
|
|
78
|
-
for (const test of data.recovered) {
|
|
79
|
-
const runViz = test.recentRuns.map((s) => s === "passed" ? "P" : "F").join("");
|
|
80
|
-
lines.push(`- ✅ **${test.fullName}** (${test.modulePath}) — recent runs: \`${runViz}\``);
|
|
81
|
-
}
|
|
82
|
-
lines.push("");
|
|
83
|
-
}
|
|
84
|
-
if (data.flaky.length === 0 && data.persistent.length === 0 && data.recovered.length === 0) lines.push("✅ No flaky, persistent, or recently recovered tests.", "");
|
|
85
|
-
lines.push(`_History updated: ${data.history.updatedAt}_`);
|
|
86
|
-
return lines.join("\n");
|
|
87
|
-
};
|
|
88
|
-
const TestHistoryAsMarkdown = TestHistoryResult.pipe(Schema.decodeTo(Schema.String, {
|
|
89
|
-
decode: SchemaGetter.transform((data) => formatTestHistoryMarkdown(data)),
|
|
90
|
-
encode: SchemaGetter.forbidden(() => "TestHistoryAsMarkdown is one-way: markdown cannot be parsed back to TestHistoryResult.")
|
|
91
|
-
}));
|
|
92
56
|
/**
|
|
93
57
|
* The `test_history` tool's parameters.
|
|
94
58
|
*
|
|
@@ -147,11 +111,11 @@ const handleTestHistory = (input) => Effect.gen(function* () {
|
|
|
147
111
|
* @public
|
|
148
112
|
*/
|
|
149
113
|
const testHistoryTool = Tool.make("test_history", {
|
|
150
|
-
description: "Use when failures recur and you need flaky, persistent, and recovered test classifications. Returns
|
|
114
|
+
description: "Use when failures recur and you need flaky, persistent, and recovered test classifications. Returns a typed JSON object in structuredContent (project, hasData, history, flaky[], persistent[], recovered[]). Optional testName/modulePath narrow to a single test; limit caps runs kept per test (default 20) — omit all three only when you actually need the whole project's history.",
|
|
151
115
|
parameters: TestHistoryInput,
|
|
152
116
|
success: TestHistoryResult,
|
|
153
117
|
dependencies: [DataReader]
|
|
154
|
-
}).annotate(Tool.Title, "Test history").annotate(Tool.Readonly, true).annotate(Tool.Destructive, false).annotate(Tool.OpenWorld, false).annotate(Tool.Idempotent, true)
|
|
118
|
+
}).annotate(Tool.Title, "Test history").annotate(Tool.Readonly, true).annotate(Tool.Destructive, false).annotate(Tool.OpenWorld, false).annotate(Tool.Idempotent, true);
|
|
155
119
|
|
|
156
120
|
//#endregion
|
|
157
|
-
export { TestHistoryInput, TestHistoryResult,
|
|
121
|
+
export { TestHistoryInput, TestHistoryResult, handleTestHistory, testHistoryTool };
|
package/tools/hypothesis.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { RenderText } from "../annotations.js";
|
|
2
1
|
import { McpSession } from "../session.js";
|
|
3
2
|
import { IdempotentReplayMarker } from "../utils/replay-marker.js";
|
|
4
3
|
import { Effect, Match, Option, Schema } from "effect";
|
|
@@ -42,20 +41,8 @@ const HypothesisResult = Schema.Union([
|
|
|
42
41
|
title: "hypothesis result",
|
|
43
42
|
description: "Discriminate on `action`. record returns the new id; list returns the matching rows; validate returns an empty acknowledgement."
|
|
44
43
|
});
|
|
45
|
-
const formatHypothesisListMarkdown = (data) => {
|
|
46
|
-
if (data.action !== "list") return JSON.stringify(data, null, 2);
|
|
47
|
-
if (data.hypotheses.length === 0) return "No hypotheses matched.";
|
|
48
|
-
const lines = ["# Hypotheses", ""];
|
|
49
|
-
for (const h of data.hypotheses) {
|
|
50
|
-
const status = h.validationOutcome ?? "open";
|
|
51
|
-
lines.push(`- [${status}] id=${h.id} session=${h.sessionId}: ${h.content.slice(0, 120)}`);
|
|
52
|
-
}
|
|
53
|
-
return lines.join("\n");
|
|
54
|
-
};
|
|
55
44
|
/** Number-or-numeric-string id: LLM orchestrators routinely stringify numeric tool inputs. */
|
|
56
45
|
const CoercibleId = Schema.Union([Schema.Finite, Schema.FiniteFromString]);
|
|
57
|
-
/** The text channel: `list` renders markdown; `record` / `validate` render the pretty-printed JSON. */
|
|
58
|
-
const renderHypothesisText = (data) => data.action === "list" ? formatHypothesisListMarkdown(data) : JSON.stringify(data, null, 2);
|
|
59
46
|
const RecordVariant = Schema.Struct({
|
|
60
47
|
action: Schema.Literal("record").annotate({ description: "CRUD discriminator" }),
|
|
61
48
|
tddTaskId: Schema.optionalKey(CoercibleId).annotate({ description: "record: tdd task id returned by tdd_task action='start' — binds the hypothesis to that task's session deterministically" }),
|
|
@@ -173,7 +160,7 @@ const handleHypothesis = (input) => Match.value(input).pipe(Match.discriminators
|
|
|
173
160
|
* @public
|
|
174
161
|
*/
|
|
175
162
|
const hypothesisTool = Tool.make("hypothesis", {
|
|
176
|
-
description: "Use to manage debugging hypotheses, with a CRUD action discriminator: action='record' (content, tddTaskId?, optional citation ids) writes a hypothesis — the binding session is resolved server-side from the recovered host context (active TDD subagent, else main session); pass tddTaskId (returned by tdd_task action='start') to bind deterministically to that task's session, and do not pass sessionId when recording; action='validate' (id, outcome, validatedAt?) records a validation outcome — validatedAt is optional and defaults server-side to now when omitted, or is honored verbatim when supplied; action='list' (sessionId?, outcome?, limit?) returns matching hypotheses
|
|
163
|
+
description: "Use to manage debugging hypotheses, with a CRUD action discriminator: action='record' (content, tddTaskId?, optional citation ids) writes a hypothesis — the binding session is resolved server-side from the recovered host context (active TDD subagent, else main session); pass tddTaskId (returned by tdd_task action='start') to bind deterministically to that task's session, and do not pass sessionId when recording; action='validate' (id, outcome, validatedAt?) records a validation outcome — validatedAt is optional and defaults server-side to now when omitted, or is honored verbatim when supplied; action='list' (sessionId?, outcome?, limit?) returns matching hypotheses.",
|
|
177
164
|
parameters: HypothesisInput,
|
|
178
165
|
success: HypothesisResult,
|
|
179
166
|
dependencies: [
|
|
@@ -181,7 +168,7 @@ const hypothesisTool = Tool.make("hypothesis", {
|
|
|
181
168
|
DataStore,
|
|
182
169
|
McpSession
|
|
183
170
|
]
|
|
184
|
-
}).annotate(Tool.Title, "Hypothesis").annotate(Tool.Readonly, false).annotate(Tool.Destructive, false).annotate(Tool.OpenWorld, false).annotate(Tool.Idempotent, false)
|
|
171
|
+
}).annotate(Tool.Title, "Hypothesis").annotate(Tool.Readonly, false).annotate(Tool.Destructive, false).annotate(Tool.OpenWorld, false).annotate(Tool.Idempotent, false);
|
|
185
172
|
|
|
186
173
|
//#endregion
|
|
187
|
-
export { HypothesisInput, HypothesisResult,
|
|
174
|
+
export { HypothesisInput, HypothesisResult, handleHypothesis, hypothesisTool };
|
package/tools/inventory.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { RenderText } from "../annotations.js";
|
|
2
1
|
import { collectProjectRows, resolveProjectTargets } from "./_project-groups.js";
|
|
3
|
-
import { Effect, Match, Option, Schema
|
|
2
|
+
import { Effect, Match, Option, Schema } from "effect";
|
|
4
3
|
import { DataReader } from "@vitest-agent/engine";
|
|
5
4
|
import { Tool } from "effect/unstable/ai";
|
|
6
5
|
|
|
@@ -129,98 +128,6 @@ const InventoryResult = Schema.Union([
|
|
|
129
128
|
title: "inventory result",
|
|
130
129
|
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)."
|
|
131
130
|
});
|
|
132
|
-
const formatInventoryMarkdown = (data) => {
|
|
133
|
-
if (data.inventoryKind === "project") {
|
|
134
|
-
if (data.count === 0) return "No projects found. Run tests first.";
|
|
135
|
-
const lines = [
|
|
136
|
-
"## Projects",
|
|
137
|
-
"",
|
|
138
|
-
"| Project | Last Run | Result | Total | Passed | Failed | Skipped |",
|
|
139
|
-
"| --- | --- | --- | --- | --- | --- | --- |"
|
|
140
|
-
];
|
|
141
|
-
for (const p of data.projects) {
|
|
142
|
-
const lastRun = p.lastRun ? p.lastRun.split("T")[0] : "—";
|
|
143
|
-
const result = p.lastResult ?? "—";
|
|
144
|
-
lines.push(`| ${p.project} | ${lastRun} | ${result} | ${p.total} | ${p.passed} | ${p.failed} | ${p.skipped} |`);
|
|
145
|
-
}
|
|
146
|
-
return lines.join("\n");
|
|
147
|
-
}
|
|
148
|
-
if (data.inventoryKind === "module") {
|
|
149
|
-
if (data.count === 0) return "No modules found. Run run_tests({}) to execute tests and populate the database.";
|
|
150
|
-
const lines = ["## Modules", ""];
|
|
151
|
-
for (const g of data.groups) {
|
|
152
|
-
lines.push(`### ${g.project}`, "", "| ID | File | State | Tests | Duration |", "| --- | --- | --- | --- | --- |");
|
|
153
|
-
for (const m of g.modules) {
|
|
154
|
-
const duration = m.duration !== null ? `${m.duration}ms` : "—";
|
|
155
|
-
lines.push(`| ${m.id} | ${m.file} | ${m.state} | ${m.testCount} | ${duration} |`);
|
|
156
|
-
}
|
|
157
|
-
lines.push("");
|
|
158
|
-
}
|
|
159
|
-
return lines.join("\n").trimEnd();
|
|
160
|
-
}
|
|
161
|
-
if (data.inventoryKind === "suite") {
|
|
162
|
-
if (data.count === 0) return "No suites found. Run run_tests({}) to execute tests and populate the database.";
|
|
163
|
-
const lines = ["## Suites", ""];
|
|
164
|
-
for (const g of data.groups) {
|
|
165
|
-
lines.push(`### ${g.project}`, "", "| ID | Name | Module | State | Tests |", "| --- | --- | --- | --- | --- |");
|
|
166
|
-
for (const s of g.suites) lines.push(`| ${s.id} | ${s.name} | ${s.module} | ${s.state} | ${s.testCount} |`);
|
|
167
|
-
lines.push("");
|
|
168
|
-
}
|
|
169
|
-
return lines.join("\n").trimEnd();
|
|
170
|
-
}
|
|
171
|
-
if (data.inventoryKind === "session_detail") {
|
|
172
|
-
if (!data.found) return `No session with id=${data.id}.`;
|
|
173
|
-
const s = data.session;
|
|
174
|
-
const lines = [
|
|
175
|
-
`# Session ${s.id}`,
|
|
176
|
-
"",
|
|
177
|
-
`- chatId: \`${s.chatId}\``,
|
|
178
|
-
`- project: ${s.project}`,
|
|
179
|
-
`- agentKind: ${s.agentKind}${s.agentType !== null ? ` (${s.agentType})` : ""}`,
|
|
180
|
-
`- started: ${s.startedAt}`,
|
|
181
|
-
`- ended: ${s.endedAt ?? "still open"}`,
|
|
182
|
-
`- triageWasNonEmpty: ${s.triageWasNonEmpty}`
|
|
183
|
-
];
|
|
184
|
-
if (s.parentSessionId !== null) lines.push(`- parentSessionId: ${s.parentSessionId}`);
|
|
185
|
-
return lines.join("\n");
|
|
186
|
-
}
|
|
187
|
-
if (data.inventoryKind === "tag_scoped") {
|
|
188
|
-
if (data.count === 0) return `No tags recorded for project \`${data.project}\`. Run run_tests({}) to populate.`;
|
|
189
|
-
const lines = [
|
|
190
|
-
`## Tags — ${data.project}`,
|
|
191
|
-
"",
|
|
192
|
-
"| Tag | Modules | Tests |",
|
|
193
|
-
"| --- | --- | --- |"
|
|
194
|
-
];
|
|
195
|
-
for (const t of data.tags) lines.push(`| ${t.tag} | ${t.moduleCount} | ${t.testCount} |`);
|
|
196
|
-
return lines.join("\n");
|
|
197
|
-
}
|
|
198
|
-
if (data.inventoryKind === "tag_unscoped") {
|
|
199
|
-
if (data.count === 0) return "No tags recorded. Run run_tests({}) to populate.";
|
|
200
|
-
const lines = [
|
|
201
|
-
"## Tags",
|
|
202
|
-
"",
|
|
203
|
-
"| Tag | Modules | Tests | Projects |",
|
|
204
|
-
"| --- | --- | --- | --- |"
|
|
205
|
-
];
|
|
206
|
-
for (const t of data.tags) {
|
|
207
|
-
const projectsBreakdown = t.byProject.map((p) => `${p.project} (${p.testCount})`).join(", ");
|
|
208
|
-
lines.push(`| ${t.tag} | ${t.moduleCount} | ${t.testCount} | ${projectsBreakdown} |`);
|
|
209
|
-
}
|
|
210
|
-
return lines.join("\n");
|
|
211
|
-
}
|
|
212
|
-
if (data.count === 0) return "No sessions recorded yet.";
|
|
213
|
-
const lines = ["# Sessions", ""];
|
|
214
|
-
for (const s of data.sessions) {
|
|
215
|
-
const ended = s.endedAt ? `ended ${s.endedAt}` : "open";
|
|
216
|
-
lines.push(`- **${s.chatId}** [${s.agentKind}] project=${s.project} started=${s.startedAt} ${ended}`);
|
|
217
|
-
}
|
|
218
|
-
return lines.join("\n");
|
|
219
|
-
};
|
|
220
|
-
const InventoryAsMarkdown = InventoryResult.pipe(Schema.decodeTo(Schema.String, {
|
|
221
|
-
decode: SchemaGetter.transform((data) => formatInventoryMarkdown(data)),
|
|
222
|
-
encode: SchemaGetter.forbidden(() => "InventoryAsMarkdown is one-way.")
|
|
223
|
-
}));
|
|
224
131
|
const ProjectVariant = Schema.Struct({ kind: Schema.Literal("project") });
|
|
225
132
|
const ModuleVariant = Schema.Struct({
|
|
226
133
|
kind: Schema.Literal("module"),
|
|
@@ -389,7 +296,7 @@ const inventoryTool = Tool.make("inventory", {
|
|
|
389
296
|
parameters: InventoryInput,
|
|
390
297
|
success: InventoryResult,
|
|
391
298
|
dependencies: [DataReader]
|
|
392
|
-
}).annotate(Tool.Title, "Inventory").annotate(Tool.Readonly, true).annotate(Tool.Destructive, false).annotate(Tool.OpenWorld, false).annotate(Tool.Idempotent, true)
|
|
299
|
+
}).annotate(Tool.Title, "Inventory").annotate(Tool.Readonly, true).annotate(Tool.Destructive, false).annotate(Tool.OpenWorld, false).annotate(Tool.Idempotent, true);
|
|
393
300
|
|
|
394
301
|
//#endregion
|
|
395
|
-
export { InventoryInput, InventoryResult,
|
|
302
|
+
export { InventoryInput, InventoryResult, handleInventory, inventoryTool };
|
package/tools/note.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { RenderText } from "../annotations.js";
|
|
2
1
|
import { Effect, Match, Option, Schema } from "effect";
|
|
3
2
|
import { DataReader, DataStore } from "@vitest-agent/engine";
|
|
4
3
|
import { Tool } from "effect/unstable/ai";
|
|
@@ -78,45 +77,6 @@ const NoteResult = Schema.Union([
|
|
|
78
77
|
title: "note result",
|
|
79
78
|
description: "Discriminate on `action`. `get` further discriminates on `found`."
|
|
80
79
|
});
|
|
81
|
-
const renderNoteTable = (notes) => {
|
|
82
|
-
const lines = ["| ID | Title | Scope | Project | Created |", "| --- | --- | --- | --- | --- |"];
|
|
83
|
-
for (const n of notes) {
|
|
84
|
-
const proj = n.project ?? "—";
|
|
85
|
-
const created = n.createdAt.split("T")[0];
|
|
86
|
-
lines.push(`| ${n.id} | ${n.title} | ${n.scope} | ${proj} | ${created} |`);
|
|
87
|
-
}
|
|
88
|
-
return lines.join("\n");
|
|
89
|
-
};
|
|
90
|
-
/**
|
|
91
|
-
* Markdown rendering used at the boundary for note list/search
|
|
92
|
-
* results. Mutations (create/get/update/delete) get JSON-stringify
|
|
93
|
-
* via `structuredJsonResult` instead of a markdown view.
|
|
94
|
-
*/
|
|
95
|
-
const formatNoteListMarkdown = (data) => {
|
|
96
|
-
if (data.action === "list") {
|
|
97
|
-
if (data.notes.length === 0) return "No notes found. Use note({ action: \"create\", ... }) to add notes.";
|
|
98
|
-
return [
|
|
99
|
-
"## Notes",
|
|
100
|
-
"",
|
|
101
|
-
renderNoteTable(data.notes)
|
|
102
|
-
].join("\n");
|
|
103
|
-
}
|
|
104
|
-
if (data.action === "search") {
|
|
105
|
-
if (data.notes.length === 0) return "No notes matched.";
|
|
106
|
-
return [
|
|
107
|
-
`## Notes matching "${data.query}"`,
|
|
108
|
-
"",
|
|
109
|
-
renderNoteTable(data.notes)
|
|
110
|
-
].join("\n");
|
|
111
|
-
}
|
|
112
|
-
return JSON.stringify(data, null, 2);
|
|
113
|
-
};
|
|
114
|
-
/**
|
|
115
|
-
* The text channel: list/search render markdown; the mutation actions
|
|
116
|
-
* (create/get/update/delete) render the pretty-printed JSON, exactly as
|
|
117
|
-
* the old `structuredJsonResult` boundary did.
|
|
118
|
-
*/
|
|
119
|
-
const renderNoteText = (data) => data.action === "list" || data.action === "search" ? formatNoteListMarkdown(data) : JSON.stringify(data, null, 2);
|
|
120
80
|
const CreateVariant = Schema.Struct({
|
|
121
81
|
action: Schema.Literal("create").annotate({ description: "CRUD discriminator" }),
|
|
122
82
|
title: Schema.String,
|
|
@@ -251,11 +211,11 @@ const handleNote = (input) => Match.value(input).pipe(Match.discriminatorsExhaus
|
|
|
251
211
|
* @public
|
|
252
212
|
*/
|
|
253
213
|
const noteTool = Tool.make("note", {
|
|
254
|
-
description: "Use to manage notes, with a CRUD action discriminator: action='create' writes a scoped note; action='list' (scope?, project?, testFullName?) returns matching notes; action='get' (id) returns a structured note; action='update' (id, ...patch) edits; action='delete' (id) removes; action='search' (query) does FTS5 across title and content. structuredContent always carries the typed result (discriminate on `action`)
|
|
214
|
+
description: "Use to manage notes, with a CRUD action discriminator: action='create' writes a scoped note; action='list' (scope?, project?, testFullName?) returns matching notes; action='get' (id) returns a structured note; action='update' (id, ...patch) edits; action='delete' (id) removes; action='search' (query) does FTS5 across title and content. structuredContent always carries the typed result (discriminate on `action`).",
|
|
255
215
|
parameters: NoteParams,
|
|
256
216
|
success: NoteResult,
|
|
257
217
|
dependencies: [DataReader, DataStore]
|
|
258
|
-
}).annotate(Tool.Title, "Note").annotate(Tool.Readonly, false).annotate(Tool.Destructive, true).annotate(Tool.OpenWorld, false).annotate(Tool.Idempotent, false)
|
|
218
|
+
}).annotate(Tool.Title, "Note").annotate(Tool.Readonly, false).annotate(Tool.Destructive, true).annotate(Tool.OpenWorld, false).annotate(Tool.Idempotent, false);
|
|
259
219
|
|
|
260
220
|
//#endregion
|
|
261
|
-
export { NoteParams, NoteResult,
|
|
221
|
+
export { NoteParams, NoteResult, handleNote, noteTool };
|
package/tools/overview.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Effect, Option, Schema, SchemaGetter } from "effect";
|
|
1
|
+
import { Effect, Option, Schema } from "effect";
|
|
3
2
|
import { DataReader } from "@vitest-agent/engine";
|
|
4
3
|
import { Tool } from "effect/unstable/ai";
|
|
5
4
|
|
|
@@ -40,37 +39,6 @@ const TestOverviewResult = Schema.Union([OverviewAvailable, OverviewAbsent]).ann
|
|
|
40
39
|
title: "test_overview result",
|
|
41
40
|
description: "Per-project run metrics. Discriminate on `dataAvailable` for cold-start handling."
|
|
42
41
|
});
|
|
43
|
-
const iconForResult = (r) => {
|
|
44
|
-
if (r === "passed") return "✅";
|
|
45
|
-
if (r === "failed") return "❌";
|
|
46
|
-
if (r === "interrupted") return "⚠️";
|
|
47
|
-
return "⬜";
|
|
48
|
-
};
|
|
49
|
-
const formatTestOverviewMarkdown = (data) => {
|
|
50
|
-
if (!data.dataAvailable) {
|
|
51
|
-
if (data.reason === "project_filter_empty") return `No test data found for project \`${data.projectFilter ?? "(unknown)"}\`. Run tests first.`;
|
|
52
|
-
return "No test data available. Run tests first.";
|
|
53
|
-
}
|
|
54
|
-
const lines = ["# Test Overview", ""];
|
|
55
|
-
const projectGroups = /* @__PURE__ */ new Map();
|
|
56
|
-
for (const run of data.runs) {
|
|
57
|
-
const group = projectGroups.get(run.project) ?? [];
|
|
58
|
-
group.push(run);
|
|
59
|
-
projectGroups.set(run.project, group);
|
|
60
|
-
}
|
|
61
|
-
for (const [projectName, projectRuns] of projectGroups) {
|
|
62
|
-
lines.push(`## ${projectName}`, "");
|
|
63
|
-
for (const run of projectRuns) {
|
|
64
|
-
const lastRun = run.lastRun ? new Date(run.lastRun).toLocaleString() : "never";
|
|
65
|
-
lines.push(`### ${iconForResult(run.lastResult)} ${run.project}`, "", "| Metric | Count |", "| --- | --- |", `| Total | ${run.total} |`, `| Passed | ${run.passed} |`, `| Failed | ${run.failed} |`, `| Skipped | ${run.skipped} |`, `| Last run | ${lastRun} |`, "");
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
return lines.join("\n");
|
|
69
|
-
};
|
|
70
|
-
const TestOverviewAsMarkdown = TestOverviewResult.pipe(Schema.decodeTo(Schema.String, {
|
|
71
|
-
decode: SchemaGetter.transform((data) => formatTestOverviewMarkdown(data)),
|
|
72
|
-
encode: SchemaGetter.forbidden(() => "TestOverviewAsMarkdown is one-way.")
|
|
73
|
-
}));
|
|
74
42
|
/**
|
|
75
43
|
* The `test_overview` tool's parameters.
|
|
76
44
|
*
|
|
@@ -108,11 +76,11 @@ const handleTestOverview = (input) => Effect.gen(function* () {
|
|
|
108
76
|
* @public
|
|
109
77
|
*/
|
|
110
78
|
const testOverviewTool = Tool.make("test_overview", {
|
|
111
|
-
description: "Use when you want a summary of the test landscape with per-project run metrics. Returns
|
|
79
|
+
description: "Use when you want a summary of the test landscape with per-project run metrics. Returns a typed JSON object in structuredContent ({ dataAvailable, projectFilter?, runs[] } or absent variant).",
|
|
112
80
|
parameters: TestOverviewInput,
|
|
113
81
|
success: TestOverviewResult,
|
|
114
82
|
dependencies: [DataReader]
|
|
115
|
-
}).annotate(Tool.Title, "Test overview").annotate(Tool.Readonly, true).annotate(Tool.Destructive, false).annotate(Tool.OpenWorld, false).annotate(Tool.Idempotent, true)
|
|
83
|
+
}).annotate(Tool.Title, "Test overview").annotate(Tool.Readonly, true).annotate(Tool.Destructive, false).annotate(Tool.OpenWorld, false).annotate(Tool.Idempotent, true);
|
|
116
84
|
|
|
117
85
|
//#endregion
|
|
118
|
-
export { TestOverviewInput, TestOverviewResult,
|
|
86
|
+
export { TestOverviewInput, TestOverviewResult, handleTestOverview, testOverviewTool };
|