@vitest-agent/mcp 3.0.3 → 4.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/README.md +7 -6
- package/annotations.js +18 -0
- package/bin/vitest-agent-mcp.js +5 -140
- package/{middleware/idempotency.js → idempotency.js} +48 -49
- package/index.d.ts +5610 -1549
- package/index.js +35 -17
- package/main.d.ts +31 -0
- package/main.js +144 -0
- package/package.json +9 -6
- package/prompts/layer.js +108 -0
- package/register-toolkit.js +311 -0
- package/server.js +24 -894
- package/{context.js → session.js} +38 -22
- package/toolkit.js +86 -0
- package/tools/acceptance-metrics.js +26 -14
- package/tools/cache-health.js +28 -17
- package/tools/commit-changes.js +33 -10
- package/tools/configure.js +33 -10
- package/tools/coverage.js +34 -5
- package/tools/errors.js +36 -26
- package/tools/failure-signature-get.js +33 -10
- package/tools/file-coverage.js +37 -13
- package/tools/help.js +45 -4
- package/tools/history.js +39 -19
- package/tools/hypothesis.js +118 -102
- package/tools/inventory.js +149 -146
- package/tools/note.js +131 -113
- package/tools/overview.js +33 -10
- package/tools/ping.js +22 -10
- package/tools/register-agent.js +88 -62
- package/tools/run-tests.js +76 -23
- package/tools/settings-list.js +27 -10
- package/tools/status.js +35 -10
- package/tools/tdd-artifact.js +37 -22
- package/tools/tdd-behavior.js +120 -108
- package/tools/tdd-goal.js +98 -85
- package/tools/tdd-phase-transition-request.js +201 -166
- package/tools/tdd-progress-push.js +102 -0
- package/tools/tdd-task.js +140 -138
- package/tools/test.js +152 -138
- package/tools/trends.js +37 -18
- package/tools/triage-brief.js +34 -15
- package/tools/turn-search.js +39 -16
- package/tools/wrapup-prompt.js +37 -17
- package/utils/crash-guards.js +0 -22
- package/utils/replay-marker.js +12 -0
- package/utils/safe-format-fatal-error.js +0 -16
- package/utils/tool-error-envelope.js +3 -3
- package/version.js +13 -0
- package/layers/McpLive.js +0 -29
- package/prompts/index.js +0 -89
- package/router.js +0 -74
- package/session-env.js +0 -112
- package/utils/effect-to-zod.js +0 -158
package/tools/inventory.js
CHANGED
|
@@ -1,21 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { RenderText } from "../annotations.js";
|
|
2
2
|
import { collectProjectRows, resolveProjectTargets } from "./_project-groups.js";
|
|
3
3
|
import { Effect, Match, Option, Schema, SchemaGetter } from "effect";
|
|
4
|
-
import { DataReader } from "@vitest-agent/
|
|
4
|
+
import { DataReader } from "@vitest-agent/engine";
|
|
5
|
+
import { Tool } from "effect/unstable/ai";
|
|
5
6
|
|
|
6
7
|
//#region src/tools/inventory.ts
|
|
7
|
-
/**
|
|
8
|
-
* Consolidated `inventory` MCP tool — Schema-driven implementation.
|
|
9
|
-
*
|
|
10
|
-
* Each `kind` produces a structured result that the boundary in
|
|
11
|
-
* server.ts can render as markdown via the exported
|
|
12
|
-
* `formatInventoryMarkdown` helper. The structured payload uses an
|
|
13
|
-
* `inventoryKind` discriminant (named separately from the input
|
|
14
|
-
* `kind` because `session` collapses to two output shapes — one for
|
|
15
|
-
* single-id lookup and one for list).
|
|
16
|
-
*
|
|
17
|
-
* @packageDocumentation
|
|
18
|
-
*/
|
|
19
8
|
const ProjectRunSummary = Schema.Struct({
|
|
20
9
|
project: Schema.String,
|
|
21
10
|
lastRun: Schema.NullOr(Schema.String),
|
|
@@ -121,6 +110,11 @@ const TagInventoryUnscoped = Schema.Struct({
|
|
|
121
110
|
count: Schema.Number,
|
|
122
111
|
tags: Schema.Array(TagRowUnscoped)
|
|
123
112
|
}).annotate({ identifier: "TagInventoryUnscoped" });
|
|
113
|
+
/**
|
|
114
|
+
* The `inventory` tool's success payload.
|
|
115
|
+
*
|
|
116
|
+
* @public
|
|
117
|
+
*/
|
|
124
118
|
const InventoryResult = Schema.Union([
|
|
125
119
|
ProjectInventory,
|
|
126
120
|
ModuleInventory,
|
|
@@ -230,24 +224,31 @@ const InventoryAsMarkdown = InventoryResult.pipe(Schema.decodeTo(Schema.String,
|
|
|
230
224
|
const ProjectVariant = Schema.Struct({ kind: Schema.Literal("project") });
|
|
231
225
|
const ModuleVariant = Schema.Struct({
|
|
232
226
|
kind: Schema.Literal("module"),
|
|
233
|
-
project: Schema.
|
|
227
|
+
project: Schema.optionalKey(Schema.String)
|
|
234
228
|
});
|
|
235
229
|
const SuiteVariant = Schema.Struct({
|
|
236
230
|
kind: Schema.Literal("suite"),
|
|
237
|
-
project: Schema.
|
|
238
|
-
module: Schema.
|
|
231
|
+
project: Schema.optionalKey(Schema.String),
|
|
232
|
+
module: Schema.optionalKey(Schema.String).annotate({ description: "suite: filter by module path" })
|
|
239
233
|
});
|
|
240
234
|
const SessionVariant = Schema.Struct({
|
|
241
235
|
kind: Schema.Literal("session"),
|
|
242
|
-
id: Schema.
|
|
243
|
-
project: Schema.
|
|
244
|
-
agentKind: Schema.
|
|
245
|
-
limit: Schema.
|
|
236
|
+
id: Schema.optionalKey(Schema.Finite).annotate({ description: "session: single-row lookup by id" }),
|
|
237
|
+
project: Schema.optionalKey(Schema.String),
|
|
238
|
+
agentKind: Schema.optionalKey(Schema.Literals(["main", "subagent"])).annotate({ description: "session: filter by agent kind" }),
|
|
239
|
+
limit: Schema.optionalKey(Schema.Finite).annotate({ description: "session: max rows" })
|
|
246
240
|
});
|
|
247
241
|
const TagVariant = Schema.Struct({
|
|
248
242
|
kind: Schema.Literal("tag"),
|
|
249
|
-
project: Schema.
|
|
243
|
+
project: Schema.optionalKey(Schema.String)
|
|
250
244
|
});
|
|
245
|
+
/**
|
|
246
|
+
* The `inventory` tool's parameters — a union discriminated on `kind`.
|
|
247
|
+
* The served JSON Schema is a `oneOf` over the variants with
|
|
248
|
+
* `x-discriminator: "kind"`.
|
|
249
|
+
*
|
|
250
|
+
* @public
|
|
251
|
+
*/
|
|
251
252
|
const InventoryInput = Schema.Union([
|
|
252
253
|
ProjectVariant,
|
|
253
254
|
ModuleVariant,
|
|
@@ -256,137 +257,139 @@ const InventoryInput = Schema.Union([
|
|
|
256
257
|
TagVariant
|
|
257
258
|
]);
|
|
258
259
|
/**
|
|
259
|
-
*
|
|
260
|
-
*
|
|
261
|
-
*
|
|
260
|
+
* Handler for {@link inventoryTool}.
|
|
261
|
+
*
|
|
262
|
+
* @public
|
|
262
263
|
*/
|
|
263
|
-
const
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
264
|
+
const handleInventory = (input) => Match.value(input).pipe(Match.discriminatorsExhaustive("kind")({
|
|
265
|
+
project: () => Effect.gen(function* () {
|
|
266
|
+
const projects = yield* (yield* DataReader).getRunsByProject();
|
|
267
|
+
return {
|
|
268
|
+
inventoryKind: "project",
|
|
269
|
+
count: projects.length,
|
|
270
|
+
projects: projects.map((p) => ({
|
|
271
|
+
project: p.project,
|
|
272
|
+
lastRun: p.lastRun,
|
|
273
|
+
lastResult: p.lastResult,
|
|
274
|
+
total: p.total,
|
|
275
|
+
passed: p.passed,
|
|
276
|
+
failed: p.failed,
|
|
277
|
+
skipped: p.skipped
|
|
278
|
+
}))
|
|
279
|
+
};
|
|
280
|
+
}),
|
|
281
|
+
module: (variant) => Effect.gen(function* () {
|
|
282
|
+
const reader = yield* DataReader;
|
|
283
|
+
const targets = yield* resolveProjectTargets(variant.project, () => reader.getRunsByProject());
|
|
284
|
+
const grouped = yield* collectProjectRows(targets, (project) => reader.listModules(project));
|
|
285
|
+
return {
|
|
286
|
+
inventoryKind: "module",
|
|
287
|
+
count: grouped.total,
|
|
288
|
+
groups: grouped.groups.map((group) => ({
|
|
289
|
+
project: group.project,
|
|
290
|
+
modules: group.rows
|
|
291
|
+
}))
|
|
292
|
+
};
|
|
293
|
+
}),
|
|
294
|
+
suite: (variant) => Effect.gen(function* () {
|
|
295
|
+
const reader = yield* DataReader;
|
|
296
|
+
const opts = {};
|
|
297
|
+
if (variant.module !== void 0) opts.module = variant.module;
|
|
298
|
+
const targets = yield* resolveProjectTargets(variant.project, () => reader.getRunsByProject());
|
|
299
|
+
const grouped = yield* collectProjectRows(targets, (project) => reader.listSuites(project, opts));
|
|
300
|
+
return {
|
|
301
|
+
inventoryKind: "suite",
|
|
302
|
+
count: grouped.total,
|
|
303
|
+
groups: grouped.groups.map((group) => ({
|
|
304
|
+
project: group.project,
|
|
305
|
+
suites: group.rows
|
|
306
|
+
}))
|
|
307
|
+
};
|
|
308
|
+
}),
|
|
309
|
+
session: (variant) => Effect.gen(function* () {
|
|
310
|
+
const reader = yield* DataReader;
|
|
311
|
+
if (variant.id !== void 0) {
|
|
312
|
+
const opt = yield* reader.getSessionById(variant.id);
|
|
313
|
+
return Option.isNone(opt) ? {
|
|
314
|
+
inventoryKind: "session_detail",
|
|
315
|
+
found: false,
|
|
316
|
+
id: variant.id
|
|
317
|
+
} : {
|
|
318
|
+
inventoryKind: "session_detail",
|
|
319
|
+
found: true,
|
|
320
|
+
session: opt.value
|
|
286
321
|
};
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
const
|
|
303
|
-
const opts = {};
|
|
304
|
-
if (variant.module !== void 0) opts.module = variant.module;
|
|
305
|
-
const targets = yield* resolveProjectTargets(variant.project, () => reader.getRunsByProject());
|
|
306
|
-
const grouped = yield* collectProjectRows(targets, (project) => reader.listSuites(project, opts));
|
|
322
|
+
}
|
|
323
|
+
const rows = yield* reader.listSessions({
|
|
324
|
+
...variant.project !== void 0 && { project: variant.project },
|
|
325
|
+
...variant.agentKind !== void 0 && { agentKind: variant.agentKind },
|
|
326
|
+
...variant.limit !== void 0 && { limit: variant.limit }
|
|
327
|
+
});
|
|
328
|
+
return {
|
|
329
|
+
inventoryKind: "session_list",
|
|
330
|
+
count: rows.length,
|
|
331
|
+
sessions: rows
|
|
332
|
+
};
|
|
333
|
+
}),
|
|
334
|
+
tag: (variant) => Effect.gen(function* () {
|
|
335
|
+
const reader = yield* DataReader;
|
|
336
|
+
if (variant.project !== void 0) {
|
|
337
|
+
const rows = yield* reader.listTagInventory({ project: variant.project });
|
|
307
338
|
return {
|
|
308
|
-
inventoryKind: "
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
339
|
+
inventoryKind: "tag_scoped",
|
|
340
|
+
project: variant.project,
|
|
341
|
+
count: rows.length,
|
|
342
|
+
tags: rows.map((r) => ({
|
|
343
|
+
tag: r.tag,
|
|
344
|
+
moduleCount: r.moduleCount,
|
|
345
|
+
testCount: r.testCount
|
|
313
346
|
}))
|
|
314
347
|
};
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
inventoryKind: "session_detail",
|
|
326
|
-
found: true,
|
|
327
|
-
session: opt.value
|
|
348
|
+
}
|
|
349
|
+
const rows = yield* reader.listTagInventory();
|
|
350
|
+
const byTag = /* @__PURE__ */ new Map();
|
|
351
|
+
for (const r of rows) {
|
|
352
|
+
let entry = byTag.get(r.tag);
|
|
353
|
+
if (entry === void 0) {
|
|
354
|
+
entry = {
|
|
355
|
+
moduleCount: 0,
|
|
356
|
+
testCount: 0,
|
|
357
|
+
byProject: []
|
|
328
358
|
};
|
|
359
|
+
byTag.set(r.tag, entry);
|
|
329
360
|
}
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
361
|
+
entry.moduleCount += r.moduleCount;
|
|
362
|
+
entry.testCount += r.testCount;
|
|
363
|
+
entry.byProject.push({
|
|
364
|
+
project: r.project,
|
|
365
|
+
moduleCount: r.moduleCount,
|
|
366
|
+
testCount: r.testCount
|
|
334
367
|
});
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
if (entry === void 0) {
|
|
361
|
-
entry = {
|
|
362
|
-
moduleCount: 0,
|
|
363
|
-
testCount: 0,
|
|
364
|
-
byProject: []
|
|
365
|
-
};
|
|
366
|
-
byTag.set(r.tag, entry);
|
|
367
|
-
}
|
|
368
|
-
entry.moduleCount += r.moduleCount;
|
|
369
|
-
entry.testCount += r.testCount;
|
|
370
|
-
entry.byProject.push({
|
|
371
|
-
project: r.project,
|
|
372
|
-
moduleCount: r.moduleCount,
|
|
373
|
-
testCount: r.testCount
|
|
374
|
-
});
|
|
375
|
-
}
|
|
376
|
-
const tags = Array.from(byTag.entries()).sort(([a], [b]) => a.localeCompare(b)).map(([tag, e]) => ({
|
|
377
|
-
tag,
|
|
378
|
-
moduleCount: e.moduleCount,
|
|
379
|
-
testCount: e.testCount,
|
|
380
|
-
byProject: e.byProject
|
|
381
|
-
}));
|
|
382
|
-
return {
|
|
383
|
-
inventoryKind: "tag_unscoped",
|
|
384
|
-
count: tags.length,
|
|
385
|
-
tags
|
|
386
|
-
};
|
|
387
|
-
})
|
|
388
|
-
})));
|
|
389
|
-
});
|
|
368
|
+
}
|
|
369
|
+
const tags = Array.from(byTag.entries()).sort(([a], [b]) => a.localeCompare(b)).map(([tag, e]) => ({
|
|
370
|
+
tag,
|
|
371
|
+
moduleCount: e.moduleCount,
|
|
372
|
+
testCount: e.testCount,
|
|
373
|
+
byProject: e.byProject
|
|
374
|
+
}));
|
|
375
|
+
return {
|
|
376
|
+
inventoryKind: "tag_unscoped",
|
|
377
|
+
count: tags.length,
|
|
378
|
+
tags
|
|
379
|
+
};
|
|
380
|
+
})
|
|
381
|
+
})).pipe(Effect.orDie);
|
|
382
|
+
/**
|
|
383
|
+
* The Effect-native `inventory` tool.
|
|
384
|
+
*
|
|
385
|
+
* @public
|
|
386
|
+
*/
|
|
387
|
+
const inventoryTool = Tool.make("inventory", {
|
|
388
|
+
description: "Use to discover what exists in the workspace, with a kind discriminator: project / module / suite / session / tag. structuredContent discriminates on `inventoryKind` (project, module, suite, session_detail, session_list, tag_scoped, tag_unscoped) so callers can branch on the response shape without parsing markdown.",
|
|
389
|
+
parameters: InventoryInput,
|
|
390
|
+
success: InventoryResult,
|
|
391
|
+
dependencies: [DataReader]
|
|
392
|
+
}).annotate(Tool.Title, "Inventory").annotate(Tool.Readonly, true).annotate(Tool.Destructive, false).annotate(Tool.OpenWorld, false).annotate(Tool.Idempotent, true).annotate(RenderText, (encoded) => formatInventoryMarkdown(encoded));
|
|
390
393
|
|
|
391
394
|
//#endregion
|
|
392
|
-
export {
|
|
395
|
+
export { InventoryInput, InventoryResult, formatInventoryMarkdown, handleInventory, inventoryTool };
|
package/tools/note.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { RenderText } from "../annotations.js";
|
|
2
2
|
import { Effect, Match, Option, Schema } from "effect";
|
|
3
|
-
import { DataReader, DataStore } from "@vitest-agent/
|
|
3
|
+
import { DataReader, DataStore } from "@vitest-agent/engine";
|
|
4
|
+
import { Tool } from "effect/unstable/ai";
|
|
4
5
|
|
|
5
6
|
//#region src/tools/note.ts
|
|
6
7
|
const NoteScope = Schema.Literals([
|
|
@@ -59,6 +60,11 @@ const NoteSearchOk = Schema.Struct({
|
|
|
59
60
|
count: Schema.Number,
|
|
60
61
|
notes: Schema.Array(NoteRowSchema).annotate({ description: "Notes whose title or content match the FTS5 query." })
|
|
61
62
|
});
|
|
63
|
+
/**
|
|
64
|
+
* The `note` tool's success payload.
|
|
65
|
+
*
|
|
66
|
+
* @public
|
|
67
|
+
*/
|
|
62
68
|
const NoteResult = Schema.Union([
|
|
63
69
|
NoteCreateOk,
|
|
64
70
|
NoteListOk,
|
|
@@ -105,46 +111,57 @@ const formatNoteListMarkdown = (data) => {
|
|
|
105
111
|
}
|
|
106
112
|
return JSON.stringify(data, null, 2);
|
|
107
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);
|
|
108
120
|
const CreateVariant = Schema.Struct({
|
|
109
|
-
action: Schema.Literal("create"),
|
|
121
|
+
action: Schema.Literal("create").annotate({ description: "CRUD discriminator" }),
|
|
110
122
|
title: Schema.String,
|
|
111
123
|
content: Schema.String,
|
|
112
|
-
scope: NoteScope,
|
|
113
|
-
project: Schema.
|
|
114
|
-
testFullName: Schema.
|
|
115
|
-
modulePath: Schema.
|
|
116
|
-
parentNoteId: Schema.
|
|
117
|
-
createdBy: Schema.
|
|
118
|
-
expiresAt: Schema.
|
|
119
|
-
pinned: Schema.
|
|
124
|
+
scope: NoteScope.annotate({ description: "create: required scope; list: optional filter" }),
|
|
125
|
+
project: Schema.optionalKey(Schema.String),
|
|
126
|
+
testFullName: Schema.optionalKey(Schema.String),
|
|
127
|
+
modulePath: Schema.optionalKey(Schema.String),
|
|
128
|
+
parentNoteId: Schema.optionalKey(Schema.Finite),
|
|
129
|
+
createdBy: Schema.optionalKey(Schema.String),
|
|
130
|
+
expiresAt: Schema.optionalKey(Schema.String),
|
|
131
|
+
pinned: Schema.optionalKey(Schema.Boolean)
|
|
120
132
|
});
|
|
121
133
|
const ListVariant = Schema.Struct({
|
|
122
|
-
action: Schema.Literal("list"),
|
|
123
|
-
scope: Schema.
|
|
124
|
-
project: Schema.
|
|
125
|
-
testFullName: Schema.
|
|
134
|
+
action: Schema.Literal("list").annotate({ description: "CRUD discriminator" }),
|
|
135
|
+
scope: Schema.optionalKey(NoteScope).annotate({ description: "create: required scope; list: optional filter" }),
|
|
136
|
+
project: Schema.optionalKey(Schema.String),
|
|
137
|
+
testFullName: Schema.optionalKey(Schema.String)
|
|
126
138
|
});
|
|
127
139
|
const GetVariant = Schema.Struct({
|
|
128
|
-
action: Schema.Literal("get"),
|
|
129
|
-
id: Schema.
|
|
140
|
+
action: Schema.Literal("get").annotate({ description: "CRUD discriminator" }),
|
|
141
|
+
id: Schema.Finite.annotate({ description: "get/update/delete: note id" })
|
|
130
142
|
});
|
|
131
143
|
const UpdateVariant = Schema.Struct({
|
|
132
|
-
action: Schema.Literal("update"),
|
|
133
|
-
id: Schema.
|
|
134
|
-
title: Schema.
|
|
135
|
-
content: Schema.
|
|
136
|
-
pinned: Schema.
|
|
137
|
-
expiresAt: Schema.
|
|
144
|
+
action: Schema.Literal("update").annotate({ description: "CRUD discriminator" }),
|
|
145
|
+
id: Schema.Finite.annotate({ description: "get/update/delete: note id" }),
|
|
146
|
+
title: Schema.optionalKey(Schema.String),
|
|
147
|
+
content: Schema.optionalKey(Schema.String),
|
|
148
|
+
pinned: Schema.optionalKey(Schema.Boolean),
|
|
149
|
+
expiresAt: Schema.optionalKey(Schema.String)
|
|
138
150
|
});
|
|
139
151
|
const DeleteVariant = Schema.Struct({
|
|
140
|
-
action: Schema.Literal("delete"),
|
|
141
|
-
id: Schema.
|
|
152
|
+
action: Schema.Literal("delete").annotate({ description: "CRUD discriminator" }),
|
|
153
|
+
id: Schema.Finite.annotate({ description: "get/update/delete: note id" })
|
|
142
154
|
});
|
|
143
155
|
const SearchVariant = Schema.Struct({
|
|
144
|
-
action: Schema.Literal("search"),
|
|
145
|
-
query: Schema.String
|
|
156
|
+
action: Schema.Literal("search").annotate({ description: "CRUD discriminator" }),
|
|
157
|
+
query: Schema.String.annotate({ description: "search: FTS5 query" })
|
|
146
158
|
});
|
|
147
|
-
|
|
159
|
+
/**
|
|
160
|
+
* The `note` tool's parameters — a union discriminated on `action`.
|
|
161
|
+
*
|
|
162
|
+
* @public
|
|
163
|
+
*/
|
|
164
|
+
const NoteParams = Schema.Union([
|
|
148
165
|
CreateVariant,
|
|
149
166
|
ListVariant,
|
|
150
167
|
GetVariant,
|
|
@@ -153,91 +170,92 @@ const NoteInputUnion = Schema.Union([
|
|
|
153
170
|
SearchVariant
|
|
154
171
|
]);
|
|
155
172
|
/**
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
*
|
|
173
|
+
* Handler for {@link noteTool}.
|
|
174
|
+
*
|
|
175
|
+
* @public
|
|
159
176
|
*/
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
177
|
+
const handleNote = (input) => Match.value(input).pipe(Match.discriminatorsExhaustive("action")({
|
|
178
|
+
create: (variant) => Effect.gen(function* () {
|
|
179
|
+
const store = yield* DataStore;
|
|
180
|
+
const noteInput = {
|
|
181
|
+
title: variant.title,
|
|
182
|
+
content: variant.content,
|
|
183
|
+
scope: variant.scope,
|
|
184
|
+
...variant.project !== void 0 && { project: variant.project },
|
|
185
|
+
...variant.testFullName !== void 0 && { testFullName: variant.testFullName },
|
|
186
|
+
...variant.modulePath !== void 0 && { modulePath: variant.modulePath },
|
|
187
|
+
...variant.parentNoteId !== void 0 && { parentNoteId: variant.parentNoteId },
|
|
188
|
+
...variant.createdBy !== void 0 && { createdBy: variant.createdBy },
|
|
189
|
+
...variant.expiresAt !== void 0 && { expiresAt: variant.expiresAt },
|
|
190
|
+
...variant.pinned !== void 0 && { pinned: variant.pinned }
|
|
191
|
+
};
|
|
192
|
+
return {
|
|
193
|
+
action: "create",
|
|
194
|
+
id: yield* store.writeNote(noteInput)
|
|
195
|
+
};
|
|
196
|
+
}),
|
|
197
|
+
list: (variant) => Effect.gen(function* () {
|
|
198
|
+
const notes = yield* (yield* DataReader).getNotes(variant.scope, variant.project, variant.testFullName);
|
|
199
|
+
return {
|
|
200
|
+
action: "list",
|
|
201
|
+
count: notes.length,
|
|
202
|
+
notes
|
|
203
|
+
};
|
|
204
|
+
}),
|
|
205
|
+
get: (variant) => Effect.gen(function* () {
|
|
206
|
+
const noteOpt = yield* (yield* DataReader).getNoteById(variant.id);
|
|
207
|
+
return Option.isNone(noteOpt) ? {
|
|
208
|
+
action: "get",
|
|
209
|
+
found: false,
|
|
210
|
+
id: variant.id
|
|
211
|
+
} : {
|
|
212
|
+
action: "get",
|
|
213
|
+
found: true,
|
|
214
|
+
note: noteOpt.value
|
|
215
|
+
};
|
|
216
|
+
}),
|
|
217
|
+
update: (variant) => Effect.gen(function* () {
|
|
218
|
+
const store = yield* DataStore;
|
|
219
|
+
const fields = {
|
|
220
|
+
...variant.title !== void 0 && { title: variant.title },
|
|
221
|
+
...variant.content !== void 0 && { content: variant.content },
|
|
222
|
+
...variant.pinned !== void 0 && { pinned: variant.pinned },
|
|
223
|
+
...variant.expiresAt !== void 0 && { expiresAt: variant.expiresAt }
|
|
224
|
+
};
|
|
225
|
+
yield* store.updateNote(variant.id, fields);
|
|
226
|
+
return {
|
|
227
|
+
action: "update",
|
|
228
|
+
success: true
|
|
229
|
+
};
|
|
230
|
+
}),
|
|
231
|
+
delete: (variant) => Effect.gen(function* () {
|
|
232
|
+
yield* (yield* DataStore).deleteNote(variant.id);
|
|
233
|
+
return {
|
|
234
|
+
action: "delete",
|
|
235
|
+
success: true
|
|
236
|
+
};
|
|
237
|
+
}),
|
|
238
|
+
search: (variant) => Effect.gen(function* () {
|
|
239
|
+
const notes = yield* (yield* DataReader).searchNotes(variant.query);
|
|
240
|
+
return {
|
|
241
|
+
action: "search",
|
|
242
|
+
query: variant.query,
|
|
243
|
+
count: notes.length,
|
|
244
|
+
notes
|
|
245
|
+
};
|
|
246
|
+
})
|
|
247
|
+
})).pipe(Effect.orDie);
|
|
248
|
+
/**
|
|
249
|
+
* The Effect-native `note` tool.
|
|
250
|
+
*
|
|
251
|
+
* @public
|
|
252
|
+
*/
|
|
253
|
+
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`); list/search additionally render markdown in the text channel.",
|
|
255
|
+
parameters: NoteParams,
|
|
256
|
+
success: NoteResult,
|
|
257
|
+
dependencies: [DataReader, DataStore]
|
|
258
|
+
}).annotate(Tool.Title, "Note").annotate(Tool.Readonly, false).annotate(Tool.Destructive, true).annotate(Tool.OpenWorld, false).annotate(Tool.Idempotent, false).annotate(RenderText, (encoded) => renderNoteText(encoded));
|
|
241
259
|
|
|
242
260
|
//#endregion
|
|
243
|
-
export {
|
|
261
|
+
export { NoteParams, NoteResult, formatNoteListMarkdown, handleNote, noteTool };
|