@vitest-agent/mcp 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +44 -0
  3. package/bin/vitest-agent-mcp.js +93 -0
  4. package/context.js +72 -0
  5. package/index.d.ts +1577 -0
  6. package/index.js +19 -0
  7. package/layers/McpLive.js +30 -0
  8. package/middleware/idempotency.js +128 -0
  9. package/package.json +58 -0
  10. package/prompts/explain-failure.js +27 -0
  11. package/prompts/index.js +89 -0
  12. package/prompts/regression-since-pass.js +28 -0
  13. package/prompts/tdd-resume.js +28 -0
  14. package/prompts/triage.js +24 -0
  15. package/prompts/why-flaky.js +30 -0
  16. package/prompts/wrapup.js +19 -0
  17. package/resources/index.js +155 -0
  18. package/resources/indexes.js +77 -0
  19. package/resources/manifest-schema.js +46 -0
  20. package/resources/paths.js +20 -0
  21. package/resources/patterns.js +22 -0
  22. package/resources/upstream-docs.js +22 -0
  23. package/router.js +74 -0
  24. package/server.js +838 -0
  25. package/tools/_tdd-error-envelope.js +98 -0
  26. package/tools/acceptance-metrics.js +75 -0
  27. package/tools/cache-health.js +83 -0
  28. package/tools/commit-changes.js +64 -0
  29. package/tools/configure.js +107 -0
  30. package/tools/coverage.js +76 -0
  31. package/tools/errors.js +151 -0
  32. package/tools/failure-signature-get.js +73 -0
  33. package/tools/file-coverage.js +106 -0
  34. package/tools/help.js +146 -0
  35. package/tools/history.js +121 -0
  36. package/tools/hypothesis.js +127 -0
  37. package/tools/inventory.js +377 -0
  38. package/tools/note.js +208 -0
  39. package/tools/overview.js +92 -0
  40. package/tools/ping.js +22 -0
  41. package/tools/register-agent.js +135 -0
  42. package/tools/run-tests.js +359 -0
  43. package/tools/settings-list.js +48 -0
  44. package/tools/status.js +74 -0
  45. package/tools/tdd-artifact.js +101 -0
  46. package/tools/tdd-behavior.js +177 -0
  47. package/tools/tdd-goal.js +147 -0
  48. package/tools/tdd-phase-transition-request.js +212 -0
  49. package/tools/tdd-task.js +278 -0
  50. package/tools/test.js +281 -0
  51. package/tools/trends.js +112 -0
  52. package/tools/triage-brief.js +42 -0
  53. package/tools/turn-search.js +60 -0
  54. package/tools/wrapup-prompt.js +49 -0
  55. package/tsdoc-metadata.json +11 -0
  56. package/utils/effect-to-zod.js +81 -0
@@ -0,0 +1,42 @@
1
+ import { publicProcedure } from "../context.js";
2
+ import { formatTriageEffect } from "@vitest-agent/sdk";
3
+ import { Effect, Schema } from "effect";
4
+
5
+ //#region src/tools/triage-brief.ts
6
+ /**
7
+ * `triage_brief` MCP tool — Schema-driven implementation.
8
+ *
9
+ * The structured payload is a thin envelope around the markdown
10
+ * rendering since this is a narrative tool — there's no underlying
11
+ * record set the agent would parse separately. The `hasContent` flag
12
+ * lets callers branch on the cold-start case without grepping prose.
13
+ *
14
+ * @packageDocumentation
15
+ */
16
+ const TriageBriefResult = Schema.Struct({
17
+ hasContent: Schema.Boolean.annotations({ description: "`false` when no orientation signal is available yet (run tests to populate)." }),
18
+ markdown: Schema.String.annotations({ description: "Pre-rendered markdown brief or the empty-state message." })
19
+ }).annotations({
20
+ identifier: "TriageBriefResult",
21
+ title: "triage_brief result",
22
+ description: "Orientation triage envelope. Branch on `hasContent` for cold-start; consume `markdown` for rendering."
23
+ });
24
+ const triageBrief = publicProcedure.input(Schema.standardSchemaV1(Schema.Struct({
25
+ project: Schema.optional(Schema.String),
26
+ maxLines: Schema.optional(Schema.Number)
27
+ }))).query(async ({ ctx, input }) => ctx.runtime.runPromise(Effect.gen(function* () {
28
+ const md = yield* formatTriageEffect({
29
+ ...input.project !== void 0 && { project: input.project },
30
+ ...input.maxLines !== void 0 && { maxLines: input.maxLines }
31
+ });
32
+ return md.length > 0 ? {
33
+ hasContent: true,
34
+ markdown: md
35
+ } : {
36
+ hasContent: false,
37
+ markdown: "No orientation signal yet — run tests to populate the database."
38
+ };
39
+ })));
40
+
41
+ //#endregion
42
+ export { TriageBriefResult, triageBrief };
@@ -0,0 +1,60 @@
1
+ import { publicProcedure } from "../context.js";
2
+ import { DataReader } from "@vitest-agent/sdk";
3
+ import { Effect, ParseResult, Schema } from "effect";
4
+
5
+ //#region src/tools/turn-search.ts
6
+ /**
7
+ * `turn_search` MCP tool — Schema-driven implementation.
8
+ *
9
+ * @packageDocumentation
10
+ */
11
+ const TurnRow = Schema.Struct({
12
+ id: Schema.Number.annotations({ description: "Numeric primary key of this turn row." }),
13
+ sessionId: Schema.Number.annotations({ description: "Owning `sessions.id` (integer FK)." }),
14
+ turnNo: Schema.Number.annotations({ description: "Turn ordinal within the session (1-based)." }),
15
+ type: Schema.String.annotations({ description: "Turn category (`user_prompt`, `tool_call`, `tool_result`, `file_edit`, `hook_fire`, `note`, `hypothesis`)." }),
16
+ payload: Schema.String.annotations({ description: "Type-specific payload as a JSON-encoded string. Decode shape depends on `type`." }),
17
+ occurredAt: Schema.String.annotations({ description: "ISO-8601 timestamp the turn was recorded at." })
18
+ }).annotations({
19
+ identifier: "TurnRow",
20
+ description: "One row from the turns log."
21
+ });
22
+ const TurnSearchResult = Schema.Struct({
23
+ count: Schema.Number.annotations({ description: "Number of matching turn rows returned." }),
24
+ turns: Schema.Array(TurnRow).annotations({ description: "Matching turns ordered by `occurredAt` ascending." })
25
+ }).annotations({
26
+ identifier: "TurnSearchResult",
27
+ title: "turn_search result",
28
+ description: "Turn-log search results across all sessions, optionally filtered by session, time, type."
29
+ });
30
+ const formatTurnSearchMarkdown = (data) => {
31
+ if (data.turns.length === 0) return "No turns matched.";
32
+ const lines = ["# Turns", ""];
33
+ for (const t of data.turns) lines.push(`- session=${t.sessionId} turn=${t.turnNo} type=${t.type} at=${t.occurredAt}`);
34
+ return lines.join("\n");
35
+ };
36
+ const TurnSearchAsMarkdown = Schema.transformOrFail(TurnSearchResult, Schema.String, {
37
+ strict: true,
38
+ decode: (data) => ParseResult.succeed(formatTurnSearchMarkdown(data)),
39
+ encode: (text, _options, ast) => ParseResult.fail(new ParseResult.Forbidden(ast, text, "TurnSearchAsMarkdown is one-way: markdown cannot be parsed back to TurnSearchResult."))
40
+ });
41
+ const turnSearch = publicProcedure.input(Schema.standardSchemaV1(Schema.Struct({
42
+ sessionId: Schema.optional(Schema.Number),
43
+ since: Schema.optional(Schema.String),
44
+ type: Schema.optional(Schema.Literal("user_prompt", "tool_call", "tool_result", "file_edit", "hook_fire", "note", "hypothesis")),
45
+ limit: Schema.optional(Schema.Number)
46
+ }))).query(async ({ ctx, input }) => ctx.runtime.runPromise(Effect.gen(function* () {
47
+ const rows = yield* (yield* DataReader).searchTurns({
48
+ ...input.sessionId !== void 0 && { sessionId: input.sessionId },
49
+ ...input.since !== void 0 && { since: input.since },
50
+ ...input.type !== void 0 && { type: input.type },
51
+ limit: input.limit ?? 100
52
+ });
53
+ return {
54
+ count: rows.length,
55
+ turns: rows
56
+ };
57
+ })));
58
+
59
+ //#endregion
60
+ export { TurnSearchAsMarkdown, TurnSearchResult, turnSearch };
@@ -0,0 +1,49 @@
1
+ import { publicProcedure } from "../context.js";
2
+ import { formatWrapupEffect } from "@vitest-agent/sdk";
3
+ import { Effect, Schema } from "effect";
4
+
5
+ //#region src/tools/wrapup-prompt.ts
6
+ /**
7
+ * `wrapup_prompt` MCP tool — Schema-driven implementation.
8
+ *
9
+ * Same envelope shape as `triage_brief`: thin wrapper around the
10
+ * markdown rendering with a `hasContent` discriminant for the empty
11
+ * case.
12
+ *
13
+ * @packageDocumentation
14
+ */
15
+ const WrapupPromptResult = Schema.Struct({
16
+ hasContent: Schema.Boolean.annotations({ description: "`false` when there is nothing to wrap up for the named session/kind." }),
17
+ kind: Schema.Literal("stop", "session_end", "pre_compact", "tdd_handoff", "user_prompt_nudge").annotations({ description: "Echo of the wrap-up kind that was rendered (defaulted to `session_end` when omitted)." }),
18
+ markdown: Schema.String.annotations({ description: "Pre-rendered wrap-up markdown or the empty-state message." })
19
+ }).annotations({
20
+ identifier: "WrapupPromptResult",
21
+ title: "wrapup_prompt result",
22
+ description: "Wrap-up envelope. Branch on `hasContent` for the empty case; consume `markdown` for rendering."
23
+ });
24
+ const wrapupPrompt = publicProcedure.input(Schema.standardSchemaV1(Schema.Struct({
25
+ sessionId: Schema.optional(Schema.Number),
26
+ chatId: Schema.optional(Schema.String),
27
+ kind: Schema.optional(Schema.Literal("stop", "session_end", "pre_compact", "tdd_handoff", "user_prompt_nudge")),
28
+ userPromptHint: Schema.optional(Schema.String)
29
+ }))).query(async ({ ctx, input }) => ctx.runtime.runPromise(Effect.gen(function* () {
30
+ const kind = input.kind ?? "session_end";
31
+ const md = yield* formatWrapupEffect({
32
+ ...input.sessionId !== void 0 && { sessionId: input.sessionId },
33
+ ...input.chatId !== void 0 && { chatId: input.chatId },
34
+ kind,
35
+ ...input.userPromptHint !== void 0 && { userPromptHint: input.userPromptHint }
36
+ });
37
+ return md.length > 0 ? {
38
+ hasContent: true,
39
+ kind,
40
+ markdown: md
41
+ } : {
42
+ hasContent: false,
43
+ kind,
44
+ markdown: "Nothing to wrap up."
45
+ };
46
+ })));
47
+
48
+ //#endregion
49
+ export { WrapupPromptResult, wrapupPrompt };
@@ -0,0 +1,11 @@
1
+ // This file is read by tools that parse documentation comments conforming to the TSDoc standard.
2
+ // It should be published with your NPM package. It should not be tracked by Git.
3
+ {
4
+ "tsdocVersion": "0.12",
5
+ "toolPackages": [
6
+ {
7
+ "packageName": "@microsoft/api-extractor",
8
+ "packageVersion": "7.58.9"
9
+ }
10
+ ]
11
+ }
@@ -0,0 +1,81 @@
1
+ import { JSONSchema } from "effect";
2
+ import { z } from "zod";
3
+
4
+ //#region src/utils/effect-to-zod.ts
5
+ /**
6
+ * Convert an Effect `Schema.Schema<A, I, never>` to a zod schema by
7
+ * serializing it to JSON Schema (`JSONSchema.make`) and ingesting the
8
+ * result via zod 4's `z.fromJSONSchema`.
9
+ *
10
+ * Trade-offs:
11
+ * - Effect-Schema-only refinements (custom predicates, Brand types)
12
+ * erase to plain JSON Schema primitives during the round-trip,
13
+ * so the resulting zod schema does not enforce them. Tools that
14
+ * need refinement enforcement at the MCP boundary should declare
15
+ * zod directly.
16
+ * - `Schema.NullOr(...)` round-trips correctly via JSON Schema's
17
+ * `oneOf` / nullable representation that zod understands.
18
+ * - `z.fromJSONSchema` is marked experimental in zod 4. The bridge
19
+ * contains a smoke-test in the corresponding test file so an
20
+ * incompatible upgrade surfaces immediately instead of in
21
+ * production tool registrations.
22
+ *
23
+ * Implementation note: zod 4's `z.fromJSONSchema` does not resolve
24
+ * `$ref` lookups into `$defs` — every `{ $ref: "#/$defs/X" }` it
25
+ * encounters throws "Reference not found". Effect's `JSONSchema.make`
26
+ * emits a `$ref`-and-`$defs` representation whenever a Schema carries
27
+ * an `identifier` annotation. The bridge therefore inlines every
28
+ * `$ref` in the document before handing it to zod (recursive
29
+ * substitution, then drop `$defs`). The schemas don't use
30
+ * `Schema.suspend`, so the substitution is acyclic.
31
+ *
32
+ * MCP SDK constraint: `outputSchema` must normalise to a Zod object
33
+ * schema (`normalizeObjectSchema` returns `undefined` for unions, then
34
+ * `safeParseAsync(undefined, ...)` crashes with "Cannot read properties
35
+ * of undefined (reading '_zod')"). When the resulting zod schema is not
36
+ * object-typed (e.g. came from `Schema.Union` of discriminated
37
+ * variants), the bridge wraps it in a permissive `z.object({}).catchall(z.unknown())`
38
+ * so the SDK accepts it. The structured content the tool emits still
39
+ * conforms to the original Effect Schema; consumers just don't get a
40
+ * rich JSON Schema for the union in the tool listing. Restructure the
41
+ * source schema as a single `Schema.Struct` with a discriminator field
42
+ * if the rich listing matters.
43
+ */
44
+ const effectToZodSchema = (schema) => {
45
+ const inlined = inlineAllRefs(JSONSchema.make(schema));
46
+ const zodSchema = z.fromJSONSchema(inlined);
47
+ if (isObjectLike(zodSchema)) return zodSchema;
48
+ return z.object({}).catchall(z.unknown());
49
+ };
50
+ const isObjectLike = (schema) => {
51
+ const def = schema._zod?.def;
52
+ return def?.type === "object" || def?.shape !== void 0;
53
+ };
54
+ const REF_PREFIX = "#/$defs/";
55
+ /**
56
+ * Walk a JSON Schema tree and replace every `$ref: "#/$defs/X"` node
57
+ * with the contents of `$defs.X`, recursively. The `$defs` table is
58
+ * dropped from the returned root.
59
+ */
60
+ const inlineAllRefs = (root) => {
61
+ const defs = root.$defs ?? {};
62
+ const visit = (value) => {
63
+ if (Array.isArray(value)) return value.map(visit);
64
+ if (value === null || typeof value !== "object") return value;
65
+ const obj = value;
66
+ if (typeof obj.$ref === "string" && obj.$ref.startsWith(REF_PREFIX)) {
67
+ const target = defs[obj.$ref.slice(8)];
68
+ if (target !== void 0) return visit(target);
69
+ }
70
+ const out = {};
71
+ for (const [k, v] of Object.entries(obj)) {
72
+ if (k === "$defs") continue;
73
+ out[k] = visit(v);
74
+ }
75
+ return out;
76
+ };
77
+ return visit(root);
78
+ };
79
+
80
+ //#endregion
81
+ export { effectToZodSchema };