@savvy-web/mcp 3.3.0 → 3.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/errors.js +18 -40
- package/index.d.ts +146 -152
- package/index.js +2 -2
- package/main.d.ts +2 -1
- package/main.js +24 -21
- package/package.json +4 -4
- package/toolkit.js +1 -1
- package/tools/biome-check.js +18 -15
- package/tools/changeset-deps-detect.js +1 -1
- package/tools/changeset-deps-regen.js +1 -1
- package/tools/changeset-inspect.js +14 -3
- package/tools/changeset-validate.js +4 -4
- package/tools/repos-inspect.js +7 -3
- package/tools/repos-manage.js +123 -115
- package/tools/turbo-inspect.js +14 -3
- package/version.js +1 -1
package/errors.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ToolFailure } from "@effected/mcp";
|
|
1
|
+
import { ToolFailure, ToolRefusal } from "@effected/mcp";
|
|
2
2
|
import { Schema } from "effect";
|
|
3
3
|
|
|
4
4
|
//#region src/errors.ts
|
|
@@ -14,18 +14,6 @@ var WorkspaceNotFound = class extends Schema.TaggedError()("WorkspaceNotFound",
|
|
|
14
14
|
cwd: Schema.String
|
|
15
15
|
}) {};
|
|
16
16
|
/**
|
|
17
|
-
* A tool argument was structurally acceptable but semantically invalid — a
|
|
18
|
-
* `repos_manage` action missing the field it needs, a `biome_check` path
|
|
19
|
-
* outside the workspace, a `changeset_validate` directory that does not
|
|
20
|
-
* exist. `message` is composed through `ToolFailure.message`.
|
|
21
|
-
*
|
|
22
|
-
* @public
|
|
23
|
-
*/
|
|
24
|
-
var InvalidArgument = class extends Schema.TaggedError()("InvalidArgument", {
|
|
25
|
-
...ToolFailure.fields,
|
|
26
|
-
argument: Schema.String
|
|
27
|
-
}) {};
|
|
28
|
-
/**
|
|
29
17
|
* A silk-effects engine program failed with one of its own typed errors
|
|
30
18
|
* (`TurboError`, `GitError`, `ReposConfigError`, …). `source` carries that
|
|
31
19
|
* error's `_tag`; `message` is its own rendering plus the remediation,
|
|
@@ -38,13 +26,6 @@ var EngineError = class extends Schema.TaggedError()("EngineError", {
|
|
|
38
26
|
source: Schema.String
|
|
39
27
|
}) {};
|
|
40
28
|
/**
|
|
41
|
-
* No Biome binary could be located. `message` is composed through
|
|
42
|
-
* `ToolFailure.message`.
|
|
43
|
-
*
|
|
44
|
-
* @public
|
|
45
|
-
*/
|
|
46
|
-
var BiomeUnavailable = class extends Schema.TaggedError()("BiomeUnavailable", { ...ToolFailure.fields }) {};
|
|
47
|
-
/**
|
|
48
29
|
* Biome itself failed (exit status above 1, a spawn error, or a timeout) —
|
|
49
30
|
* distinct from "lint issues found", which is a successful result. `message`
|
|
50
31
|
* is composed through `ToolFailure.message`.
|
|
@@ -58,10 +39,9 @@ var BiomeFailed = class extends Schema.TaggedError()("BiomeFailed", {
|
|
|
58
39
|
/** The one failure schema every savvy-mcp tool declares. @public */
|
|
59
40
|
const McpToolError = Schema.Union([
|
|
60
41
|
WorkspaceNotFound,
|
|
61
|
-
InvalidArgument,
|
|
62
42
|
EngineError,
|
|
63
|
-
|
|
64
|
-
|
|
43
|
+
BiomeFailed,
|
|
44
|
+
ToolRefusal
|
|
65
45
|
]);
|
|
66
46
|
/** The remediation every `WorkspaceNotFound` carries. */
|
|
67
47
|
const WORKSPACE_REMEDIATION = {
|
|
@@ -82,30 +62,28 @@ const workspaceNotFound = (cwd) => new WorkspaceNotFound({
|
|
|
82
62
|
remediation: WORKSPACE_REMEDIATION
|
|
83
63
|
});
|
|
84
64
|
/**
|
|
85
|
-
* Build an {@link InvalidArgument}: `raw` is the human message (already
|
|
86
|
-
* truncated by the caller where it echoes an argument).
|
|
87
|
-
*
|
|
88
|
-
* @public
|
|
89
|
-
*/
|
|
90
|
-
const invalidArgument = (argument, raw, remediation) => new InvalidArgument({
|
|
91
|
-
argument,
|
|
92
|
-
message: ToolFailure.message(raw, remediation),
|
|
93
|
-
remediation
|
|
94
|
-
});
|
|
95
|
-
/**
|
|
96
65
|
* Build an {@link EngineError} from a silk-effects typed error. Every engine
|
|
97
66
|
* error in this server renders itself through a `message` getter, so that
|
|
98
67
|
* rendering is the raw message — passed through `ToolFailure.truncate` at
|
|
99
68
|
* `ToolFailure.ENGINE_ECHO_LIMIT`, since the kit embeds caller values in it;
|
|
100
69
|
* `source` keeps the tag for anything that inspects the typed error directly.
|
|
70
|
+
* An `Error` in the engine error's own `cause` is appended to that rendering
|
|
71
|
+
* unless the rendering already contains it: a kit error that renders only a
|
|
72
|
+
* one-line summary keeps the actionable detail in `cause`, while one that
|
|
73
|
+
* already folds its cause in (`CatalogAssemblyError` since `@effected/npm`
|
|
74
|
+
* 0.17.0) would otherwise print the detail twice.
|
|
101
75
|
*
|
|
102
76
|
* @public
|
|
103
77
|
*/
|
|
104
|
-
const engineError = (cause, remediation) =>
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
78
|
+
const engineError = (cause, remediation) => {
|
|
79
|
+
const detail = cause.cause instanceof Error ? cause.cause.message : "";
|
|
80
|
+
const rendered = detail && !cause.message.includes(detail) ? `${cause.message}: ${detail}` : cause.message;
|
|
81
|
+
return new EngineError({
|
|
82
|
+
source: cause._tag,
|
|
83
|
+
message: ToolFailure.message(ToolFailure.truncate(rendered, ToolFailure.ENGINE_ECHO_LIMIT), remediation),
|
|
84
|
+
remediation
|
|
85
|
+
});
|
|
86
|
+
};
|
|
109
87
|
/**
|
|
110
88
|
* The shared mapping every handler applies to its engine error channel: the
|
|
111
89
|
* kit's `WorkspaceRootNotFoundError` becomes {@link WorkspaceNotFound} for the
|
|
@@ -121,4 +99,4 @@ const engineError = (cause, remediation) => new EngineError({
|
|
|
121
99
|
const mapEngineError = (requestedCwd, remediation) => (cause) => cause._tag === "WorkspaceRootNotFoundError" ? workspaceNotFound(requestedCwd) : engineError(cause, remediation);
|
|
122
100
|
|
|
123
101
|
//#endregion
|
|
124
|
-
export { BiomeFailed,
|
|
102
|
+
export { BiomeFailed, EngineError, McpToolError, WorkspaceNotFound, engineError, mapEngineError, workspaceNotFound };
|
package/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Distribution } from "@effected/engine";
|
|
2
|
+
import { ToolRefusal } from "@effected/mcp";
|
|
2
3
|
import { FileSystem, Layer, Path, Schema, Stdio } from "effect";
|
|
3
4
|
import { WorkspaceRoot } from "@effected/workspaces";
|
|
4
5
|
import { Changesets, Repos, SilkWorkspaceAnalyzer, Turbo } from "@savvy-web/silk-effects";
|
|
@@ -22,24 +23,6 @@ declare const WorkspaceNotFound_base: Schema.Class<WorkspaceNotFound, Schema.Tag
|
|
|
22
23
|
* @public
|
|
23
24
|
*/
|
|
24
25
|
export declare class WorkspaceNotFound extends WorkspaceNotFound_base {}
|
|
25
|
-
declare const InvalidArgument_base: Schema.Class<InvalidArgument, Schema.TaggedStruct<"InvalidArgument", {
|
|
26
|
-
readonly message: Schema.String;
|
|
27
|
-
readonly remediation: Schema.Struct<{
|
|
28
|
-
readonly hint: Schema.String;
|
|
29
|
-
readonly suggestedTool: Schema.optionalKey<Schema.String>;
|
|
30
|
-
readonly suggestedArgs: Schema.optionalKey<Schema.$Record<Schema.String, Schema.Unknown>>;
|
|
31
|
-
}>;
|
|
32
|
-
readonly argument: Schema.String;
|
|
33
|
-
}>, import("effect/Cause").YieldableError>;
|
|
34
|
-
/**
|
|
35
|
-
* A tool argument was structurally acceptable but semantically invalid — a
|
|
36
|
-
* `repos_manage` action missing the field it needs, a `biome_check` path
|
|
37
|
-
* outside the workspace, a `changeset_validate` directory that does not
|
|
38
|
-
* exist. `message` is composed through `ToolFailure.message`.
|
|
39
|
-
*
|
|
40
|
-
* @public
|
|
41
|
-
*/
|
|
42
|
-
export declare class InvalidArgument extends InvalidArgument_base {}
|
|
43
26
|
declare const EngineError_base: Schema.Class<EngineError, Schema.TaggedStruct<"EngineError", {
|
|
44
27
|
readonly message: Schema.String;
|
|
45
28
|
readonly remediation: Schema.Struct<{
|
|
@@ -58,21 +41,6 @@ declare const EngineError_base: Schema.Class<EngineError, Schema.TaggedStruct<"E
|
|
|
58
41
|
* @public
|
|
59
42
|
*/
|
|
60
43
|
export declare class EngineError extends EngineError_base {}
|
|
61
|
-
declare const BiomeUnavailable_base: Schema.Class<BiomeUnavailable, Schema.TaggedStruct<"BiomeUnavailable", {
|
|
62
|
-
readonly message: Schema.String;
|
|
63
|
-
readonly remediation: Schema.Struct<{
|
|
64
|
-
readonly hint: Schema.String;
|
|
65
|
-
readonly suggestedTool: Schema.optionalKey<Schema.String>;
|
|
66
|
-
readonly suggestedArgs: Schema.optionalKey<Schema.$Record<Schema.String, Schema.Unknown>>;
|
|
67
|
-
}>;
|
|
68
|
-
}>, import("effect/Cause").YieldableError>;
|
|
69
|
-
/**
|
|
70
|
-
* No Biome binary could be located. `message` is composed through
|
|
71
|
-
* `ToolFailure.message`.
|
|
72
|
-
*
|
|
73
|
-
* @public
|
|
74
|
-
*/
|
|
75
|
-
export declare class BiomeUnavailable extends BiomeUnavailable_base {}
|
|
76
44
|
declare const BiomeFailed_base: Schema.Class<BiomeFailed, Schema.TaggedStruct<"BiomeFailed", {
|
|
77
45
|
readonly message: Schema.String;
|
|
78
46
|
readonly remediation: Schema.Struct<{
|
|
@@ -91,7 +59,7 @@ declare const BiomeFailed_base: Schema.Class<BiomeFailed, Schema.TaggedStruct<"B
|
|
|
91
59
|
*/
|
|
92
60
|
export declare class BiomeFailed extends BiomeFailed_base {}
|
|
93
61
|
/** The one failure schema every savvy-mcp tool declares. @public */
|
|
94
|
-
export declare const McpToolError: Schema.Union<readonly [typeof WorkspaceNotFound, typeof
|
|
62
|
+
export declare const McpToolError: Schema.Union<readonly [typeof WorkspaceNotFound, typeof EngineError, typeof BiomeFailed, typeof ToolRefusal]>;
|
|
95
63
|
/** @public */
|
|
96
64
|
export type McpToolError = typeof McpToolError.Type;
|
|
97
65
|
//#endregion
|
|
@@ -207,7 +175,7 @@ declare const biomeCheckTool: Tool.Tool<"biome_check", {
|
|
|
207
175
|
readonly wrote: Schema.Boolean;
|
|
208
176
|
readonly guidance: Schema.String;
|
|
209
177
|
}>;
|
|
210
|
-
readonly failure: Schema.Union<readonly [typeof WorkspaceNotFound, typeof
|
|
178
|
+
readonly failure: Schema.Union<readonly [typeof WorkspaceNotFound, typeof EngineError, typeof BiomeFailed, typeof ToolRefusal]>;
|
|
211
179
|
readonly failureMode: "error";
|
|
212
180
|
}, never>;
|
|
213
181
|
//#endregion
|
|
@@ -239,7 +207,7 @@ declare const changesetDepsDetectTool: Tool.Tool<"changeset_deps_detect", {
|
|
|
239
207
|
readonly packages: Schema.$Array<Schema.String>;
|
|
240
208
|
}>>;
|
|
241
209
|
}>;
|
|
242
|
-
readonly failure: Schema.Union<readonly [typeof WorkspaceNotFound, typeof
|
|
210
|
+
readonly failure: Schema.Union<readonly [typeof WorkspaceNotFound, typeof EngineError, typeof BiomeFailed, typeof import("@effected/mcp").ToolRefusal]>;
|
|
243
211
|
readonly failureMode: "error";
|
|
244
212
|
}, Changesets.DepsRegen | WorkspaceRoot>;
|
|
245
213
|
//#endregion
|
|
@@ -265,7 +233,7 @@ declare const changesetDepsRegenTool: Tool.Tool<"changeset_deps_regen", {
|
|
|
265
233
|
}>>;
|
|
266
234
|
readonly dryRun: Schema.Boolean;
|
|
267
235
|
}>;
|
|
268
|
-
readonly failure: Schema.Union<readonly [typeof WorkspaceNotFound, typeof
|
|
236
|
+
readonly failure: Schema.Union<readonly [typeof WorkspaceNotFound, typeof EngineError, typeof BiomeFailed, typeof import("@effected/mcp").ToolRefusal]>;
|
|
269
237
|
readonly failureMode: "error";
|
|
270
238
|
}, Changesets.DepsRegen | WorkspaceRoot>;
|
|
271
239
|
//#endregion
|
|
@@ -341,7 +309,7 @@ declare const changesetInspectTool: Tool.Tool<"changeset_inspect", {
|
|
|
341
309
|
}>, Schema.Null]>;
|
|
342
310
|
}>>;
|
|
343
311
|
}>]>;
|
|
344
|
-
readonly failure: Schema.Union<readonly [typeof WorkspaceNotFound, typeof
|
|
312
|
+
readonly failure: Schema.Union<readonly [typeof WorkspaceNotFound, typeof EngineError, typeof BiomeFailed, typeof import("@effected/mcp").ToolRefusal]>;
|
|
345
313
|
readonly failureMode: "error";
|
|
346
314
|
}, Changesets.BranchAnalyzer | Changesets.ConfigInspector | WorkspaceRoot>;
|
|
347
315
|
//#endregion
|
|
@@ -370,7 +338,7 @@ declare const changesetPreviewTool: Tool.Tool<"changeset_preview", {
|
|
|
370
338
|
}>>;
|
|
371
339
|
}>>;
|
|
372
340
|
}>;
|
|
373
|
-
readonly failure: Schema.Union<readonly [typeof WorkspaceNotFound, typeof
|
|
341
|
+
readonly failure: Schema.Union<readonly [typeof WorkspaceNotFound, typeof EngineError, typeof BiomeFailed, typeof import("@effected/mcp").ToolRefusal]>;
|
|
374
342
|
readonly failureMode: "error";
|
|
375
343
|
}, Changesets.ReleasePlanner | WorkspaceRoot>;
|
|
376
344
|
//#endregion
|
|
@@ -393,7 +361,7 @@ declare const changesetValidateTool: Tool.Tool<"changeset_validate", {
|
|
|
393
361
|
readonly message: Schema.String;
|
|
394
362
|
}>>;
|
|
395
363
|
}>;
|
|
396
|
-
readonly failure: Schema.Union<readonly [typeof WorkspaceNotFound, typeof
|
|
364
|
+
readonly failure: Schema.Union<readonly [typeof WorkspaceNotFound, typeof EngineError, typeof BiomeFailed, typeof ToolRefusal]>;
|
|
397
365
|
readonly failureMode: "error";
|
|
398
366
|
}, WorkspaceRoot>;
|
|
399
367
|
//#endregion
|
|
@@ -458,128 +426,154 @@ declare const reposInspectTool: Tool.Tool<"repos_inspect", {
|
|
|
458
426
|
}>>;
|
|
459
427
|
readonly parseError: Schema.optionalKey<Schema.String>;
|
|
460
428
|
}>]>;
|
|
461
|
-
readonly failure: Schema.Union<readonly [typeof WorkspaceNotFound, typeof
|
|
429
|
+
readonly failure: Schema.Union<readonly [typeof WorkspaceNotFound, typeof EngineError, typeof BiomeFailed, typeof import("@effected/mcp").ToolRefusal]>;
|
|
462
430
|
readonly failureMode: "error";
|
|
463
431
|
}, FileSystem.FileSystem | Path.Path | Repos.ReposConfigStore | Repos.ReposDrift | Repos.ReposManager | WorkspaceRoot>;
|
|
464
432
|
//#endregion
|
|
465
433
|
//#region src/tools/repos-manage.d.ts
|
|
466
|
-
/**
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
434
|
+
/**
|
|
435
|
+
* The `repos_manage` tool value: a `McpToolkit.unionTool`, so the served input
|
|
436
|
+
* schema is the request union's strict `oneOf` keyed by `action`, and a bad
|
|
437
|
+
* call is rejected as invalid parameters before the handler runs. Mutating:
|
|
438
|
+
* not read-only, not idempotent.
|
|
439
|
+
*/
|
|
440
|
+
declare const reposManageTool: import("@effected/mcp").UnionTool<"repos_manage", Schema.Union<readonly [Schema.Struct<{
|
|
441
|
+
readonly action: Schema.Literal<"sync">;
|
|
442
|
+
readonly cwd: Schema.optionalKey<Schema.String>;
|
|
443
|
+
}>, Schema.Struct<{
|
|
444
|
+
readonly action: Schema.Literal<"pin">;
|
|
445
|
+
readonly name: Schema.String;
|
|
446
|
+
readonly ref: Schema.String;
|
|
447
|
+
readonly cwd: Schema.optionalKey<Schema.String>;
|
|
448
|
+
}>, Schema.Struct<{
|
|
449
|
+
readonly action: Schema.Literal<"add">;
|
|
450
|
+
readonly url: Schema.String;
|
|
451
|
+
readonly ref: Schema.String;
|
|
452
|
+
readonly purpose: Schema.String;
|
|
453
|
+
readonly name: Schema.optionalKey<Schema.String>;
|
|
454
|
+
readonly sparse: Schema.optionalKey<Schema.$Array<Schema.String>>;
|
|
455
|
+
readonly orientation: Schema.optionalKey<Schema.Struct<{
|
|
456
|
+
readonly layout: Schema.optional<Schema.String>;
|
|
457
|
+
readonly keyPaths: Schema.optional<Schema.$Record<Schema.String, Schema.String>>;
|
|
458
|
+
readonly startHere: Schema.optional<Schema.String>;
|
|
459
|
+
}>>;
|
|
460
|
+
readonly cwd: Schema.optionalKey<Schema.String>;
|
|
461
|
+
}>, Schema.Struct<{
|
|
462
|
+
readonly action: Schema.Literal<"note">;
|
|
463
|
+
readonly name: Schema.String;
|
|
464
|
+
readonly op: Schema.Literals<readonly ["add", "remove", "promote"]>;
|
|
465
|
+
readonly note: Schema.optionalKey<Schema.String>;
|
|
466
|
+
readonly id: Schema.optionalKey<Schema.String>;
|
|
467
|
+
readonly into: Schema.optionalKey<Schema.Literals<readonly ["layout", "startHere"]>>;
|
|
468
|
+
readonly cwd: Schema.optionalKey<Schema.String>;
|
|
469
|
+
}>, Schema.Struct<{
|
|
470
|
+
readonly action: Schema.Literal<"remove">;
|
|
471
|
+
readonly name: Schema.String;
|
|
472
|
+
readonly cwd: Schema.optionalKey<Schema.String>;
|
|
473
|
+
}>, Schema.Struct<{
|
|
474
|
+
readonly action: Schema.Literal<"rename">;
|
|
475
|
+
readonly name: Schema.String;
|
|
476
|
+
readonly newName: Schema.String;
|
|
477
|
+
readonly cwd: Schema.optionalKey<Schema.String>;
|
|
478
|
+
}>, Schema.Struct<{
|
|
479
|
+
readonly action: Schema.Literal<"restore">;
|
|
480
|
+
readonly names: Schema.optionalKey<Schema.$Array<Schema.String>>;
|
|
481
|
+
readonly cwd: Schema.optionalKey<Schema.String>;
|
|
482
|
+
}>, Schema.Struct<{
|
|
483
|
+
readonly action: Schema.Literal<"deregister">;
|
|
484
|
+
readonly section: Schema.String;
|
|
485
|
+
readonly cwd: Schema.optionalKey<Schema.String>;
|
|
486
|
+
}>]>, Schema.Union<readonly [Schema.Struct<{
|
|
487
|
+
readonly action: Schema.Literal<"sync">;
|
|
488
|
+
readonly result: Schema.Struct<{
|
|
489
|
+
readonly initialized: Schema.$Array<Schema.String>;
|
|
490
|
+
readonly sparseApplied: Schema.$Array<Schema.String>;
|
|
491
|
+
readonly upToDate: Schema.$Array<Schema.String>;
|
|
492
|
+
readonly clearedLocks: Schema.$Array<Schema.String>;
|
|
493
|
+
readonly urlSynced: Schema.$Array<Schema.String>;
|
|
494
|
+
readonly registered: Schema.$Array<Schema.String>;
|
|
495
|
+
readonly boundaryMarked: Schema.$Array<Schema.String>;
|
|
488
496
|
}>;
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
readonly
|
|
503
|
-
|
|
497
|
+
}>, Schema.Struct<{
|
|
498
|
+
readonly action: Schema.Literal<"pin">;
|
|
499
|
+
readonly result: Schema.Struct<{
|
|
500
|
+
readonly name: Schema.String;
|
|
501
|
+
readonly ref: Schema.String;
|
|
502
|
+
readonly oldCommit: Schema.NullOr<Schema.String>;
|
|
503
|
+
readonly newCommit: Schema.String;
|
|
504
|
+
readonly commitMessage: Schema.String;
|
|
505
|
+
readonly staleNoteIds: Schema.$Array<Schema.String>;
|
|
506
|
+
}>;
|
|
507
|
+
}>, Schema.Struct<{
|
|
508
|
+
readonly action: Schema.Literal<"add">;
|
|
509
|
+
readonly result: Schema.Struct<{
|
|
510
|
+
readonly name: Schema.String;
|
|
511
|
+
readonly ref: Schema.String;
|
|
512
|
+
readonly path: Schema.String;
|
|
513
|
+
}>;
|
|
514
|
+
}>, Schema.Struct<{
|
|
515
|
+
readonly action: Schema.Literal<"note">;
|
|
516
|
+
readonly result: Schema.Struct<{
|
|
517
|
+
readonly name: Schema.String;
|
|
518
|
+
readonly op: Schema.Literals<readonly ["add", "remove", "promote"]>;
|
|
519
|
+
readonly id: Schema.String;
|
|
520
|
+
readonly noteCount: Schema.Number;
|
|
521
|
+
}>;
|
|
522
|
+
}>, Schema.Struct<{
|
|
523
|
+
readonly action: Schema.Literal<"remove">;
|
|
524
|
+
readonly result: Schema.Struct<{
|
|
525
|
+
readonly name: Schema.String;
|
|
526
|
+
readonly path: Schema.String;
|
|
527
|
+
readonly commitMessage: Schema.String;
|
|
528
|
+
readonly removedNotes: Schema.$Array<Schema.Struct<{
|
|
529
|
+
readonly id: Schema.String;
|
|
530
|
+
readonly date: Schema.String;
|
|
504
531
|
readonly ref: Schema.String;
|
|
505
|
-
readonly
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
readonly
|
|
509
|
-
}>;
|
|
510
|
-
}>, Schema.Struct<{
|
|
511
|
-
readonly action: Schema.Literal<"add">;
|
|
512
|
-
readonly result: Schema.Struct<{
|
|
513
|
-
readonly name: Schema.String;
|
|
532
|
+
readonly note: Schema.String;
|
|
533
|
+
}>>;
|
|
534
|
+
readonly removedEntry: Schema.Struct<{
|
|
535
|
+
readonly url: Schema.String;
|
|
514
536
|
readonly ref: Schema.String;
|
|
515
|
-
readonly
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
readonly
|
|
523
|
-
readonly noteCount: Schema.Number;
|
|
524
|
-
}>;
|
|
525
|
-
}>, Schema.Struct<{
|
|
526
|
-
readonly action: Schema.Literal<"remove">;
|
|
527
|
-
readonly result: Schema.Struct<{
|
|
528
|
-
readonly name: Schema.String;
|
|
529
|
-
readonly path: Schema.String;
|
|
530
|
-
readonly commitMessage: Schema.String;
|
|
531
|
-
readonly removedNotes: Schema.$Array<Schema.Struct<{
|
|
537
|
+
readonly purpose: Schema.String;
|
|
538
|
+
readonly sparse: Schema.optional<Schema.$Array<Schema.String>>;
|
|
539
|
+
readonly orientation: Schema.optional<Schema.Struct<{
|
|
540
|
+
readonly layout: Schema.optional<Schema.String>;
|
|
541
|
+
readonly keyPaths: Schema.optional<Schema.$Record<Schema.String, Schema.String>>;
|
|
542
|
+
readonly startHere: Schema.optional<Schema.String>;
|
|
543
|
+
}>>;
|
|
544
|
+
readonly notes: Schema.optional<Schema.$Array<Schema.Struct<{
|
|
532
545
|
readonly id: Schema.String;
|
|
533
546
|
readonly date: Schema.String;
|
|
534
547
|
readonly ref: Schema.String;
|
|
535
548
|
readonly note: Schema.String;
|
|
536
|
-
}
|
|
537
|
-
readonly removedEntry: Schema.Struct<{
|
|
538
|
-
readonly url: Schema.String;
|
|
539
|
-
readonly ref: Schema.String;
|
|
540
|
-
readonly purpose: Schema.String;
|
|
541
|
-
readonly sparse: Schema.optional<Schema.$Array<Schema.String>>;
|
|
542
|
-
readonly orientation: Schema.optional<Schema.Struct<{
|
|
543
|
-
readonly layout: Schema.optional<Schema.String>;
|
|
544
|
-
readonly keyPaths: Schema.optional<Schema.$Record<Schema.String, Schema.String>>;
|
|
545
|
-
readonly startHere: Schema.optional<Schema.String>;
|
|
546
|
-
}>>;
|
|
547
|
-
readonly notes: Schema.optional<Schema.$Array<Schema.Struct<{
|
|
548
|
-
readonly id: Schema.String;
|
|
549
|
-
readonly date: Schema.String;
|
|
550
|
-
readonly ref: Schema.String;
|
|
551
|
-
readonly note: Schema.String;
|
|
552
|
-
}>>>;
|
|
553
|
-
}>;
|
|
554
|
-
}>;
|
|
555
|
-
}>, Schema.Struct<{
|
|
556
|
-
readonly action: Schema.Literal<"rename">;
|
|
557
|
-
readonly result: Schema.Struct<{
|
|
558
|
-
readonly oldName: Schema.String;
|
|
559
|
-
readonly newName: Schema.String;
|
|
560
|
-
readonly path: Schema.String;
|
|
561
|
-
readonly commitMessage: Schema.String;
|
|
562
|
-
}>;
|
|
563
|
-
}>, Schema.Struct<{
|
|
564
|
-
readonly action: Schema.Literal<"restore">;
|
|
565
|
-
readonly result: Schema.Struct<{
|
|
566
|
-
readonly restored: Schema.$Array<Schema.Struct<{
|
|
567
|
-
readonly name: Schema.String;
|
|
568
|
-
readonly commit: Schema.String;
|
|
569
|
-
}>>;
|
|
570
|
-
readonly skippedClean: Schema.$Array<Schema.String>;
|
|
571
|
-
readonly stillDirty: Schema.$Array<Schema.String>;
|
|
549
|
+
}>>>;
|
|
572
550
|
}>;
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
}
|
|
551
|
+
}>;
|
|
552
|
+
}>, Schema.Struct<{
|
|
553
|
+
readonly action: Schema.Literal<"rename">;
|
|
554
|
+
readonly result: Schema.Struct<{
|
|
555
|
+
readonly oldName: Schema.String;
|
|
556
|
+
readonly newName: Schema.String;
|
|
557
|
+
readonly path: Schema.String;
|
|
558
|
+
readonly commitMessage: Schema.String;
|
|
559
|
+
}>;
|
|
560
|
+
}>, Schema.Struct<{
|
|
561
|
+
readonly action: Schema.Literal<"restore">;
|
|
562
|
+
readonly result: Schema.Struct<{
|
|
563
|
+
readonly restored: Schema.$Array<Schema.Struct<{
|
|
564
|
+
readonly name: Schema.String;
|
|
565
|
+
readonly commit: Schema.String;
|
|
566
|
+
}>>;
|
|
567
|
+
readonly skippedClean: Schema.$Array<Schema.String>;
|
|
568
|
+
readonly stillDirty: Schema.$Array<Schema.String>;
|
|
569
|
+
}>;
|
|
570
|
+
}>, Schema.Struct<{
|
|
571
|
+
readonly action: Schema.Literal<"deregister">;
|
|
572
|
+
readonly result: Schema.Struct<{
|
|
573
|
+
readonly section: Schema.String;
|
|
574
|
+
readonly removedKeys: Schema.$Array<Schema.String>;
|
|
575
|
+
}>;
|
|
576
|
+
}>]>, Schema.Union<readonly [typeof WorkspaceNotFound, typeof EngineError, typeof BiomeFailed, typeof ToolRefusal]>, Repos.ReposManager | WorkspaceRoot>;
|
|
583
577
|
//#endregion
|
|
584
578
|
//#region src/tools/turbo-inspect.d.ts
|
|
585
579
|
/** The `turbo_inspect` tool value. */
|
|
@@ -641,7 +635,7 @@ declare const turboInspectTool: Tool.Tool<"turbo_inspect", {
|
|
|
641
635
|
readonly dependents: Schema.$Array<Schema.String>;
|
|
642
636
|
}>;
|
|
643
637
|
}>]>;
|
|
644
|
-
readonly failure: Schema.Union<readonly [typeof WorkspaceNotFound, typeof
|
|
638
|
+
readonly failure: Schema.Union<readonly [typeof WorkspaceNotFound, typeof EngineError, typeof BiomeFailed, typeof import("@effected/mcp").ToolRefusal]>;
|
|
645
639
|
readonly failureMode: "error";
|
|
646
640
|
}, Turbo.TurboInspector | WorkspaceRoot>;
|
|
647
641
|
//#endregion
|
|
@@ -678,7 +672,7 @@ declare const workspaceInfoTool: Tool.Tool<"workspace_info", {
|
|
|
678
672
|
readonly fixed: Schema.$Array<Schema.String>;
|
|
679
673
|
}>>;
|
|
680
674
|
}>;
|
|
681
|
-
readonly failure: Schema.Union<readonly [typeof WorkspaceNotFound, typeof
|
|
675
|
+
readonly failure: Schema.Union<readonly [typeof WorkspaceNotFound, typeof EngineError, typeof BiomeFailed, typeof import("@effected/mcp").ToolRefusal]>;
|
|
682
676
|
readonly failureMode: "error";
|
|
683
677
|
}, SilkWorkspaceAnalyzer | WorkspaceRoot>;
|
|
684
678
|
//#endregion
|
package/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { BiomeFailed,
|
|
1
|
+
import { BiomeFailed, EngineError, McpToolError, WorkspaceNotFound } from "./errors.js";
|
|
2
2
|
import { makeSilkRuntimeLayer } from "./runtime.js";
|
|
3
3
|
import { SilkToolkit, ToolsLayer } from "./toolkit.js";
|
|
4
4
|
import { CURRENT_MCP_VERSION } from "./version.js";
|
|
5
5
|
import { ServerLayer } from "./server.js";
|
|
6
6
|
|
|
7
|
-
export { BiomeFailed,
|
|
7
|
+
export { BiomeFailed, CURRENT_MCP_VERSION, EngineError, McpToolError, ServerLayer, SilkToolkit, ToolsLayer, WorkspaceNotFound, makeSilkRuntimeLayer };
|
package/main.d.ts
CHANGED
|
@@ -21,7 +21,8 @@ export interface MainOptions {
|
|
|
21
21
|
* `SAVVY_MCP_PROJECT_DIR`, then `CLAUDE_PROJECT_DIR`, then the working
|
|
22
22
|
* directory — an empty value or an unsubstituted `${VAR}` placeholder is
|
|
23
23
|
* skipped (`LaunchContext.projectDir`). NO static imports of the server
|
|
24
|
-
* graph — see the module remarks.
|
|
24
|
+
* graph — see the module remarks. The guard policy is the kit's default:
|
|
25
|
+
* a stray exception or rejection exits 1, before or after connecting.
|
|
25
26
|
*
|
|
26
27
|
* @public
|
|
27
28
|
*/
|
package/main.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
|
+
import { McpGuard } from "@effected/mcp/guard";
|
|
2
|
+
|
|
1
3
|
//#region src/main.ts
|
|
2
|
-
const fatal = (label, error) => {
|
|
3
|
-
process.stderr.write(`savvy-mcp: ${label}: ${error instanceof Error ? error.stack ?? error.message : String(error)}\n`);
|
|
4
|
-
process.exit(1);
|
|
5
|
-
};
|
|
6
4
|
/**
|
|
7
5
|
* Run the savvy MCP server over stdio. Owns the process.
|
|
8
6
|
*
|
|
@@ -11,26 +9,31 @@ const fatal = (label, error) => {
|
|
|
11
9
|
* `SAVVY_MCP_PROJECT_DIR`, then `CLAUDE_PROJECT_DIR`, then the working
|
|
12
10
|
* directory — an empty value or an unsubstituted `${VAR}` placeholder is
|
|
13
11
|
* skipped (`LaunchContext.projectDir`). NO static imports of the server
|
|
14
|
-
* graph — see the module remarks.
|
|
12
|
+
* graph — see the module remarks. The guard policy is the kit's default:
|
|
13
|
+
* a stray exception or rejection exits 1, before or after connecting.
|
|
15
14
|
*
|
|
16
15
|
* @public
|
|
17
16
|
*/
|
|
18
|
-
const main =
|
|
19
|
-
|
|
20
|
-
process
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
17
|
+
const main = (options = {}) => McpGuard.run({
|
|
18
|
+
label: "savvy-mcp",
|
|
19
|
+
host: process,
|
|
20
|
+
load: async () => {
|
|
21
|
+
const NodeRuntime = await import("@effect/platform-node/NodeRuntime");
|
|
22
|
+
const NodeServices = await import("@effect/platform-node/NodeServices");
|
|
23
|
+
const { Layer } = await import("effect");
|
|
24
|
+
const { LaunchContext } = await import("@effected/engine");
|
|
25
|
+
const { ServerLayer } = await import("./server.js");
|
|
26
|
+
return {
|
|
27
|
+
layer: ServerLayer(LaunchContext.projectDir({
|
|
28
|
+
argv: process.argv.slice(2, 3),
|
|
29
|
+
env: process.env,
|
|
30
|
+
keys: ["SAVVY_MCP_PROJECT_DIR", "CLAUDE_PROJECT_DIR"],
|
|
31
|
+
cwd: process.cwd()
|
|
32
|
+
}), options).pipe(Layer.provide(NodeServices.layer)),
|
|
33
|
+
runMain: NodeRuntime.runMain
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
});
|
|
34
37
|
/* v8 ignore stop */
|
|
35
38
|
|
|
36
39
|
//#endregion
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@savvy-web/mcp",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.4.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "The savvy MCP server — Silk Suite tooling and library knowledge for coding agents",
|
|
6
6
|
"homepage": "https://github.com/savvy-web/systems/tree/main/packages/mcp",
|
|
@@ -40,10 +40,10 @@
|
|
|
40
40
|
"@effected/commands": "^0.9.0",
|
|
41
41
|
"@effected/engine": "^0.1.0",
|
|
42
42
|
"@effected/git": "^0.17.0",
|
|
43
|
-
"@effected/mcp": "^0.
|
|
43
|
+
"@effected/mcp": "^0.2.0",
|
|
44
44
|
"@effected/templates": "^0.8.0",
|
|
45
|
-
"@effected/workspaces": "^0.
|
|
46
|
-
"@savvy-web/silk-effects": "9.
|
|
45
|
+
"@effected/workspaces": "^0.28.0",
|
|
46
|
+
"@savvy-web/silk-effects": "9.2.0",
|
|
47
47
|
"effect": "4.0.0-rc.117"
|
|
48
48
|
}
|
|
49
49
|
}
|
package/toolkit.js
CHANGED
|
@@ -36,7 +36,7 @@ const ToolsLayer = (cwd) => SilkToolkit.toLayer({
|
|
|
36
36
|
changeset_preview: (params) => handleChangesetPreview(cwd, params),
|
|
37
37
|
changeset_deps_regen: (params) => handleChangesetDepsRegen(cwd, params),
|
|
38
38
|
repos_inspect: (params) => handleReposInspect(cwd, params),
|
|
39
|
-
repos_manage:
|
|
39
|
+
repos_manage: handleReposManage(cwd),
|
|
40
40
|
biome_check: (params) => handleBiomeCheck(cwd, params)
|
|
41
41
|
});
|
|
42
42
|
|
package/tools/biome-check.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { BiomeFailed,
|
|
2
|
-
import { ToolFailure } from "@effected/mcp";
|
|
1
|
+
import { BiomeFailed, McpToolError } from "../errors.js";
|
|
2
|
+
import { ToolFailure, ToolRefusal } from "@effected/mcp";
|
|
3
3
|
import { Effect, Schema } from "effect";
|
|
4
4
|
import { Lint } from "@savvy-web/silk-effects";
|
|
5
5
|
import { Tool } from "effect/unstable/ai";
|
|
@@ -210,30 +210,27 @@ const biomeFailed = (raw, exitCode) => new BiomeFailed({
|
|
|
210
210
|
*
|
|
211
211
|
* @remarks Resolves the Biome binary via {@link Lint.Biome.findBiome} (global
|
|
212
212
|
* first, then the project's package manager). Throws a typed
|
|
213
|
-
* {@link McpToolError} member —
|
|
214
|
-
*
|
|
215
|
-
*
|
|
216
|
-
*
|
|
217
|
-
*
|
|
213
|
+
* {@link McpToolError} member — a `ToolRefusal` for a cwd/path outside the
|
|
214
|
+
* containment tree or when no binary is found, {@link BiomeFailed} when Biome
|
|
215
|
+
* exits with status > 1 (Biome itself failed, vs. status 1 = lint issues
|
|
216
|
+
* found) — which {@link handleBiomeCheck} lifts into the Effect error channel
|
|
217
|
+
* unchanged.
|
|
218
218
|
*/
|
|
219
219
|
const runBiomeCheck = async (args, fallbackCwd) => {
|
|
220
220
|
const mode = args.mode ?? "check";
|
|
221
221
|
const root = canonicalize(fallbackCwd);
|
|
222
222
|
const cwd = canonicalize(args.cwd ?? fallbackCwd);
|
|
223
223
|
const containmentRoot = resolveContainmentRoot(root, cwd);
|
|
224
|
-
if (containmentRoot === null) throw
|
|
224
|
+
if (containmentRoot === null) throw ToolRefusal.refuse(`cwd escapes the workspace root: ${ToolFailure.truncate(args.cwd ?? fallbackCwd)}.`, CONTAINMENT_REMEDIATION);
|
|
225
225
|
const within = (abs) => abs === containmentRoot || abs.startsWith(`${containmentRoot}${sep}`);
|
|
226
226
|
const paths = (args.paths && args.paths.length > 0 ? args.paths : ["."]).map((p) => {
|
|
227
227
|
const lexical = resolve(cwd, p);
|
|
228
|
-
if (!within(canonicalize(lexical))) throw
|
|
228
|
+
if (!within(canonicalize(lexical))) throw ToolRefusal.refuse(`path escapes the workspace root: ${ToolFailure.truncate(p)}.`, CONTAINMENT_REMEDIATION);
|
|
229
229
|
return relative(cwd, lexical) || ".";
|
|
230
230
|
});
|
|
231
231
|
const doWrite = Boolean(args.write || args.unsafe);
|
|
232
232
|
const biomeCmd = Lint.Biome.findBiome();
|
|
233
|
-
if (!biomeCmd) throw
|
|
234
|
-
message: ToolFailure.message("Biome not found.", BIOME_REMEDIATION),
|
|
235
|
-
remediation: BIOME_REMEDIATION
|
|
236
|
-
});
|
|
233
|
+
if (!biomeCmd) throw ToolRefusal.refuse("Biome not found.", BIOME_REMEDIATION);
|
|
237
234
|
const parts = biomeCmd.split(" ");
|
|
238
235
|
const bin = parts[0];
|
|
239
236
|
const prefix = parts.slice(1);
|
|
@@ -303,12 +300,18 @@ const biomeCheckTool = Tool.make("biome_check", {
|
|
|
303
300
|
success: BiomeCheckResult,
|
|
304
301
|
failure: McpToolError
|
|
305
302
|
}).annotate(Tool.Title, "Biome check").annotate(Tool.Readonly, false).annotate(Tool.Destructive, false).annotate(Tool.Idempotent, false).annotate(Tool.OpenWorld, false);
|
|
306
|
-
|
|
303
|
+
/**
|
|
304
|
+
* Every declared failure, recognized from the one union the tool declares, so
|
|
305
|
+
* a member added to {@link McpToolError} is passed through here without a
|
|
306
|
+
* second list to keep in step.
|
|
307
|
+
*/
|
|
308
|
+
const isMcpToolError = Schema.is(McpToolError);
|
|
307
309
|
/**
|
|
308
310
|
* Wire handler: {@link runBiomeCheck} lifted into Effect. The typed members it
|
|
309
311
|
* throws pass through unchanged; anything else (a defect in the parser, say)
|
|
310
312
|
* is reported as {@link BiomeFailed} so the error channel stays closed over
|
|
311
|
-
* {@link McpToolError}
|
|
313
|
+
* {@link McpToolError} — nothing undeclared reaches core, which would scrub
|
|
314
|
+
* it to a generic internal-error text.
|
|
312
315
|
*/
|
|
313
316
|
const handleBiomeCheck = (fallbackCwd, params) => Effect.tryPromise({
|
|
314
317
|
try: () => runBiomeCheck(params, fallbackCwd),
|
|
@@ -68,7 +68,7 @@ const ChangesetDepsDetectParams = Schema.Struct({
|
|
|
68
68
|
cwd: Schema.optionalKey(Schema.String.annotate({ description: "Directory to resolve the workspace root from." }))
|
|
69
69
|
});
|
|
70
70
|
const REMEDIATION = {
|
|
71
|
-
hint: "The dependency diff could not be planned; check that the base branch exists locally
|
|
71
|
+
hint: "The dependency diff could not be planned; check that the base branch exists locally, that every named package is a workspace member, and that every config dependency the base side declares is installed or in the pnpm store (a bumped config dependency's old version usually is not).",
|
|
72
72
|
suggestedTool: "workspace_info"
|
|
73
73
|
};
|
|
74
74
|
/** The `changeset_deps_detect` tool value. */
|
|
@@ -77,7 +77,7 @@ const ChangesetDepsRegenParams = Schema.Struct({
|
|
|
77
77
|
cwd: Schema.optionalKey(Schema.String.annotate({ description: "Directory to resolve the workspace root from." }))
|
|
78
78
|
});
|
|
79
79
|
const REMEDIATION = {
|
|
80
|
-
hint: "The dependency changesets could not be regenerated; preview the plan with dryRun=true, and check that the base branch exists locally
|
|
80
|
+
hint: "The dependency changesets could not be regenerated; preview the plan with dryRun=true, and check that the base branch exists locally, that every named package is a workspace member, and that every config dependency the base side declares is installed or in the pnpm store (a bumped config dependency's old version usually is not).",
|
|
81
81
|
suggestedTool: "changeset_deps_detect"
|
|
82
82
|
};
|
|
83
83
|
/** The `changeset_deps_regen` tool value. Mutating: not read-only, not idempotent. */
|
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
import { McpToolError, mapEngineError } from "../errors.js";
|
|
2
|
+
import { ToolOutputSchema } from "@effected/mcp";
|
|
2
3
|
import { Effect, Schema } from "effect";
|
|
3
4
|
import { WorkspaceRoot } from "@effected/workspaces";
|
|
4
5
|
import { Changesets } from "@savvy-web/silk-effects";
|
|
5
6
|
import { Tool } from "effect/unstable/ai";
|
|
6
7
|
|
|
7
8
|
//#region src/tools/changeset-inspect.ts
|
|
9
|
+
/**
|
|
10
|
+
* The `changeset_inspect` MCP tool: a discriminated-union result keyed by `mode`
|
|
11
|
+
* (branch | config), each variant embedding the corresponding resolved-output
|
|
12
|
+
* schema from silk-effects' Changesets namespace. Read-only.
|
|
13
|
+
*
|
|
14
|
+
* @packageDocumentation
|
|
15
|
+
*/
|
|
8
16
|
/** Branch-analysis variant. */
|
|
9
17
|
const ChangesetBranchResult = Schema.Struct({
|
|
10
18
|
mode: Schema.Literal("branch"),
|
|
@@ -20,12 +28,15 @@ const ChangesetClassifyResult = Schema.Struct({
|
|
|
20
28
|
mode: Schema.Literal("classify"),
|
|
21
29
|
result: Schema.Array(Changesets.ClassificationSchema)
|
|
22
30
|
}).annotate({ identifier: "ChangesetClassifyResult" });
|
|
23
|
-
/**
|
|
24
|
-
|
|
31
|
+
/**
|
|
32
|
+
* The `changeset_inspect` tool result — a discriminated union keyed by `mode`,
|
|
33
|
+
* object-rooted so its `outputSchema` is served.
|
|
34
|
+
*/
|
|
35
|
+
const ChangesetInspectResult = ToolOutputSchema.objectRooted(Schema.Union([
|
|
25
36
|
ChangesetBranchResult,
|
|
26
37
|
ChangesetConfigResult,
|
|
27
38
|
ChangesetClassifyResult
|
|
28
|
-
]).annotate({
|
|
39
|
+
])).annotate({
|
|
29
40
|
identifier: "ChangesetInspectResult",
|
|
30
41
|
title: "changeset_inspect result",
|
|
31
42
|
description: "Read-only changeset analysis grouped by mode (branch | config | classify)."
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { McpToolError,
|
|
2
|
-
import { ToolFailure } from "@effected/mcp";
|
|
1
|
+
import { McpToolError, mapEngineError } from "../errors.js";
|
|
2
|
+
import { ToolFailure, ToolRefusal } from "@effected/mcp";
|
|
3
3
|
import { Data, Effect, Schema } from "effect";
|
|
4
4
|
import { WorkspaceRoot } from "@effected/workspaces";
|
|
5
5
|
import { Changesets } from "@savvy-web/silk-effects";
|
|
@@ -77,10 +77,10 @@ const changesetValidateTool = Tool.make("changeset_validate", {
|
|
|
77
77
|
* Wire handler: {@link changesetValidate} with its error channel mapped onto
|
|
78
78
|
* {@link McpToolError}. The typed {@link ChangesetValidateError} (a thrown
|
|
79
79
|
* validate — in practice a missing directory) is an argument problem, so it
|
|
80
|
-
* becomes
|
|
80
|
+
* becomes a `ToolRefusal` naming the directory; the echoed directory is
|
|
81
81
|
* truncated.
|
|
82
82
|
*/
|
|
83
|
-
const handleChangesetValidate = (fallbackCwd, params) => changesetValidate(params, fallbackCwd).pipe(Effect.mapError((error) => error._tag === "ChangesetValidateError" ?
|
|
83
|
+
const handleChangesetValidate = (fallbackCwd, params) => changesetValidate(params, fallbackCwd).pipe(Effect.mapError((error) => error._tag === "ChangesetValidateError" ? ToolRefusal.refuse(`Changeset directory "${ToolFailure.truncate(error.dir)}" could not be validated: ${describeCause(error.cause)}`, DIR_REMEDIATION) : mapEngineError(params.cwd ?? fallbackCwd, DIR_REMEDIATION)(error)));
|
|
84
84
|
const describeCause = (cause) => cause instanceof Error ? ToolFailure.truncate(cause.message) : ToolFailure.truncate(String(cause));
|
|
85
85
|
|
|
86
86
|
//#endregion
|
package/tools/repos-inspect.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { McpToolError, mapEngineError } from "../errors.js";
|
|
2
|
+
import { ToolOutputSchema } from "@effected/mcp";
|
|
2
3
|
import { Effect, FileSystem, Option, Path, Result, Schema } from "effect";
|
|
3
4
|
import { WorkspaceRoot } from "@effected/workspaces";
|
|
4
5
|
import { Repos } from "@savvy-web/silk-effects";
|
|
@@ -50,13 +51,16 @@ const ReposGitmodulesResult = Schema.Struct({
|
|
|
50
51
|
entries: Schema.Array(GitmodulesEntrySchema),
|
|
51
52
|
parseError: Schema.optionalKey(Schema.String)
|
|
52
53
|
}).annotate({ identifier: "ReposGitmodulesResult" });
|
|
53
|
-
/**
|
|
54
|
-
|
|
54
|
+
/**
|
|
55
|
+
* The `repos_inspect` tool result — a discriminated union keyed by `mode`,
|
|
56
|
+
* object-rooted so its `outputSchema` is served.
|
|
57
|
+
*/
|
|
58
|
+
const ReposInspectResult = ToolOutputSchema.objectRooted(Schema.Union([
|
|
55
59
|
ReposStatusResult,
|
|
56
60
|
ReposConfigResult,
|
|
57
61
|
ReposDriftResult,
|
|
58
62
|
ReposGitmodulesResult
|
|
59
|
-
]).annotate({
|
|
63
|
+
])).annotate({
|
|
60
64
|
identifier: "ReposInspectResult",
|
|
61
65
|
title: "repos_inspect result",
|
|
62
66
|
description: "Drift report (status), the parsed manifest (config), the five-authority reconciliation report (drift), or the decoded .gitmodules sections (gitmodules)."
|
package/tools/repos-manage.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { McpToolError,
|
|
2
|
-
import {
|
|
3
|
-
import { Effect, Schema } from "effect";
|
|
1
|
+
import { McpToolError, mapEngineError } from "../errors.js";
|
|
2
|
+
import { McpToolkit, ToolOutputSchema, ToolRefusal } from "@effected/mcp";
|
|
3
|
+
import { Effect, Result, Schema } from "effect";
|
|
4
4
|
import { WorkspaceRoot } from "@effected/workspaces";
|
|
5
5
|
import { Repos } from "@savvy-web/silk-effects";
|
|
6
6
|
import { Tool } from "effect/unstable/ai";
|
|
@@ -9,70 +9,117 @@ import { Tool } from "effect/unstable/ai";
|
|
|
9
9
|
/**
|
|
10
10
|
* The `repos_manage` MCP tool: one action-discriminated mutating tool
|
|
11
11
|
* covering `sync`, `pin`, `add`, `note`, `remove`, `rename`, `restore`, and
|
|
12
|
-
* `deregister` against the vendored `.repos/` submodules.
|
|
13
|
-
* `
|
|
14
|
-
*
|
|
15
|
-
* Mutating — no `readOnlyHint`.
|
|
12
|
+
* `deregister` against the vendored `.repos/` submodules. Its parameters are
|
|
13
|
+
* a `Schema.Union` of one struct per action, served by `@effected/mcp`'s
|
|
14
|
+
* `McpToolkit.unionTool` as a strict `oneOf` keyed by `action`; the handler
|
|
15
|
+
* receives the decoded member. Mutating — no `readOnlyHint`.
|
|
16
16
|
*
|
|
17
17
|
* @packageDocumentation
|
|
18
18
|
*/
|
|
19
|
-
/** `
|
|
20
|
-
const
|
|
19
|
+
/** The optional `cwd` every action takes. */
|
|
20
|
+
const cwd = Schema.optionalKey(Schema.String.annotate({ description: "Directory to resolve the workspace root from." }));
|
|
21
|
+
/** `sync` has no fields of its own. */
|
|
22
|
+
const SyncRequest = Schema.Struct({
|
|
23
|
+
action: Schema.Literal("sync"),
|
|
24
|
+
cwd
|
|
25
|
+
}).annotate({ description: "Initialize/reconcile submodules per the manifest." });
|
|
21
26
|
/** `pin` requires both `name` and `ref`. */
|
|
22
|
-
const PinRequest = Schema.
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
})
|
|
27
|
+
const PinRequest = Schema.Struct({
|
|
28
|
+
action: Schema.Literal("pin"),
|
|
29
|
+
name: Schema.String.annotate({ description: "Repo name to re-pin." }),
|
|
30
|
+
ref: Schema.String.annotate({ description: "Git ref to pin to." }),
|
|
31
|
+
cwd
|
|
32
|
+
}).annotate({ description: "Re-pin a repo to a new ref." });
|
|
26
33
|
/**
|
|
27
34
|
* `add` requires `url`/`ref`/`purpose`; `name`, `sparse` and `orientation`
|
|
28
35
|
* are optional. `orientation` is what makes a re-vendor lossless — pass back
|
|
29
36
|
* the block a preceding `remove` reported.
|
|
30
37
|
*/
|
|
31
|
-
const AddRequest = Schema.
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
})
|
|
38
|
+
const AddRequest = Schema.Struct({
|
|
39
|
+
action: Schema.Literal("add"),
|
|
40
|
+
url: Schema.String.annotate({ description: "Repo URL to vendor." }),
|
|
41
|
+
ref: Schema.String.annotate({ description: "Git ref to vendor." }),
|
|
42
|
+
purpose: Schema.String.annotate({ description: "One-line purpose for the manifest." }),
|
|
43
|
+
name: Schema.optionalKey(Schema.String.annotate({ description: "Override the repo name." })),
|
|
44
|
+
sparse: Schema.optionalKey(Schema.Array(Schema.String).annotate({ description: "Sparse-checkout patterns." })),
|
|
45
|
+
orientation: Schema.optionalKey(Repos.RepoOrientation.annotate({ description: "Orientation block to write. Pass back what a preceding remove reported as removedEntry.orientation — add does NOT restore it on its own, so a re-vendor loses it otherwise." })),
|
|
46
|
+
cwd
|
|
47
|
+
}).annotate({ description: "Vendor a new repo." });
|
|
48
|
+
/**
|
|
49
|
+
* The note operation a `note` request names, or the reason it is incomplete.
|
|
50
|
+
* The fields each `op` needs are enforced here rather than as three members
|
|
51
|
+
* sharing `action: "note"`, so the served `oneOf` keeps one member per
|
|
52
|
+
* action; the same function is the decode-time filter and the handler's
|
|
53
|
+
* narrowing, so the two cannot disagree.
|
|
54
|
+
*/
|
|
55
|
+
const noteOperation = (request) => {
|
|
56
|
+
switch (request.op) {
|
|
57
|
+
case "add": return request.note === void 0 ? Result.fail("note op \"add\" requires `note`") : Result.succeed({
|
|
58
|
+
op: "add",
|
|
59
|
+
note: request.note
|
|
60
|
+
});
|
|
61
|
+
case "remove": return request.id === void 0 ? Result.fail("note op \"remove\" requires `id`") : Result.succeed({
|
|
62
|
+
op: "remove",
|
|
63
|
+
id: request.id
|
|
64
|
+
});
|
|
65
|
+
case "promote": return request.id === void 0 || request.into === void 0 ? Result.fail("note op \"promote\" requires both `id` and `into`") : Result.succeed({
|
|
66
|
+
op: "promote",
|
|
67
|
+
id: request.id,
|
|
68
|
+
into: request.into
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
};
|
|
39
72
|
/**
|
|
40
73
|
* `note` requires `name` and `op`; the fields required beyond that depend on
|
|
41
74
|
* `op` — enforced by the trailing filter so the decode error names exactly
|
|
42
75
|
* what's missing for the chosen op.
|
|
43
76
|
*/
|
|
44
|
-
const NoteRequest = Schema.
|
|
45
|
-
|
|
77
|
+
const NoteRequest = Schema.Struct({
|
|
78
|
+
action: Schema.Literal("note"),
|
|
79
|
+
name: Schema.String.annotate({ description: "Repo name the note belongs to." }),
|
|
46
80
|
op: Schema.Literals([
|
|
47
81
|
"add",
|
|
48
82
|
"remove",
|
|
49
83
|
"promote"
|
|
50
|
-
]),
|
|
51
|
-
note: Schema.
|
|
52
|
-
id: Schema.
|
|
53
|
-
into: Schema.
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}));
|
|
84
|
+
]).annotate({ description: "Note operation." }),
|
|
85
|
+
note: Schema.optionalKey(Schema.String.annotate({ description: "Note text (op=add)." })),
|
|
86
|
+
id: Schema.optionalKey(Schema.String.annotate({ description: "Note id (op=remove|promote)." })),
|
|
87
|
+
into: Schema.optionalKey(Schema.Literals(["layout", "startHere"]).annotate({ description: "Orientation target (op=promote)." })),
|
|
88
|
+
cwd
|
|
89
|
+
}).check(Schema.makeFilter((request) => Result.match(noteOperation(request), {
|
|
90
|
+
onFailure: (reason) => reason,
|
|
91
|
+
onSuccess: () => true
|
|
92
|
+
}))).annotate({ description: "Add, remove or promote an agent note." });
|
|
60
93
|
/** `remove` requires only `name`. */
|
|
61
|
-
const RemoveRequest = Schema.
|
|
94
|
+
const RemoveRequest = Schema.Struct({
|
|
95
|
+
action: Schema.Literal("remove"),
|
|
96
|
+
name: Schema.String.annotate({ description: "Repo name to unvendor." }),
|
|
97
|
+
cwd
|
|
98
|
+
}).annotate({ description: "Unvendor a repo." });
|
|
62
99
|
/** `rename` requires `name` (the old name) and `newName`. */
|
|
63
|
-
const RenameRequest = Schema.
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
})
|
|
100
|
+
const RenameRequest = Schema.Struct({
|
|
101
|
+
action: Schema.Literal("rename"),
|
|
102
|
+
name: Schema.String.annotate({ description: "The repo's current name." }),
|
|
103
|
+
newName: Schema.String.annotate({ description: "New repo name." }),
|
|
104
|
+
cwd
|
|
105
|
+
}).annotate({ description: "Rename a vendored repo's manifest key and worktree." });
|
|
67
106
|
/** `restore`'s `names` is optional and repeatable, mirroring `add`'s `sparse`; omitted means "every dirty entry". */
|
|
68
|
-
const RestoreRequest = Schema.
|
|
107
|
+
const RestoreRequest = Schema.Struct({
|
|
108
|
+
action: Schema.Literal("restore"),
|
|
109
|
+
names: Schema.optionalKey(Schema.Array(Schema.String).annotate({ description: "Repo names to restore; omitted restores every dirty repo." })),
|
|
110
|
+
cwd
|
|
111
|
+
}).annotate({ description: "Hard-reset worktrees back to their pinned gitlink commits." });
|
|
69
112
|
/**
|
|
70
113
|
* `deregister` requires `section` — the stale registration name exactly as the
|
|
71
114
|
* drift report states it (e.g. `.repos/old-name`); the `submodule.` prefix is
|
|
72
115
|
* implied, never passed.
|
|
73
116
|
*/
|
|
74
|
-
const DeregisterRequest = Schema.
|
|
75
|
-
|
|
117
|
+
const DeregisterRequest = Schema.Struct({
|
|
118
|
+
action: Schema.Literal("deregister"),
|
|
119
|
+
section: Schema.String.annotate({ description: "Stale registration name to clear, exactly as the drift report states it (e.g. .repos/old-name); the submodule. prefix is implied." }),
|
|
120
|
+
cwd
|
|
121
|
+
}).annotate({ description: "Clear a stale submodule registration from local git config." });
|
|
122
|
+
/** The `repos_manage` parameters: one struct per action, discriminated by `action`. */
|
|
76
123
|
const ReposManageRequest = Schema.Union([
|
|
77
124
|
SyncRequest,
|
|
78
125
|
PinRequest,
|
|
@@ -123,8 +170,11 @@ const ReposManageDeregisterResult = Schema.Struct({
|
|
|
123
170
|
action: Schema.Literal("deregister"),
|
|
124
171
|
result: Repos.ReposDeregisterResult
|
|
125
172
|
}).annotate({ identifier: "ReposManageDeregisterResult" });
|
|
126
|
-
/**
|
|
127
|
-
|
|
173
|
+
/**
|
|
174
|
+
* The `repos_manage` tool result — a discriminated union keyed by `action`,
|
|
175
|
+
* object-rooted so its `outputSchema` is served (every member is a struct).
|
|
176
|
+
*/
|
|
177
|
+
const ReposManageResult = ToolOutputSchema.objectRooted(Schema.Union([
|
|
128
178
|
ReposManageSyncResult,
|
|
129
179
|
ReposManagePinResult,
|
|
130
180
|
ReposManageAddResult,
|
|
@@ -133,25 +183,24 @@ const ReposManageResult = Schema.Union([
|
|
|
133
183
|
ReposManageRenameResult,
|
|
134
184
|
ReposManageRestoreResult,
|
|
135
185
|
ReposManageDeregisterResult
|
|
136
|
-
]).annotate({
|
|
186
|
+
])).annotate({
|
|
137
187
|
identifier: "ReposManageResult",
|
|
138
188
|
title: "repos_manage result",
|
|
139
189
|
description: "Result of a mutating repos action: sync, pin, add, note, remove, rename, restore, or deregister."
|
|
140
190
|
});
|
|
191
|
+
const REMEDIATION = {
|
|
192
|
+
hint: "The mutation did not complete; inspect the vendored-repo state before retrying.",
|
|
193
|
+
suggestedTool: "repos_inspect"
|
|
194
|
+
};
|
|
195
|
+
const REQUEST_REMEDIATION = { hint: "Pass the fields the chosen note op needs: note (op=add), id (op=remove), or id and into (op=promote)." };
|
|
141
196
|
/**
|
|
142
|
-
* Effect handler: resolve the workspace root,
|
|
143
|
-
*
|
|
144
|
-
* dispatch to the matching `ReposManager` method.
|
|
197
|
+
* Effect handler: resolve the workspace root, then dispatch the decoded
|
|
198
|
+
* request to the matching `ReposManager` method.
|
|
145
199
|
*/
|
|
146
|
-
const reposManage = (
|
|
147
|
-
const root = yield* (yield* WorkspaceRoot).find(
|
|
200
|
+
const reposManage = (request, fallbackCwd) => Effect.gen(function* () {
|
|
201
|
+
const root = yield* (yield* WorkspaceRoot).find(request.cwd ?? fallbackCwd);
|
|
148
202
|
const manager = yield* Repos.ReposManager;
|
|
149
|
-
|
|
150
|
-
const request = yield* Schema.decodeUnknownEffect(ReposManageRequest)({
|
|
151
|
-
_tag: action,
|
|
152
|
-
...rest
|
|
153
|
-
});
|
|
154
|
-
switch (request._tag) {
|
|
203
|
+
switch (request.action) {
|
|
155
204
|
case "sync": return {
|
|
156
205
|
action: "sync",
|
|
157
206
|
result: yield* manager.sync(root)
|
|
@@ -172,20 +221,11 @@ const reposManage = (args, fallbackCwd) => Effect.gen(function* () {
|
|
|
172
221
|
})
|
|
173
222
|
};
|
|
174
223
|
case "note": {
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
note: request.note
|
|
178
|
-
} : request.op === "remove" ? {
|
|
179
|
-
op: "remove",
|
|
180
|
-
id: request.id
|
|
181
|
-
} : {
|
|
182
|
-
op: "promote",
|
|
183
|
-
id: request.id,
|
|
184
|
-
into: request.into
|
|
185
|
-
};
|
|
224
|
+
const operation = noteOperation(request);
|
|
225
|
+
if (Result.isFailure(operation)) return yield* ToolRefusal.refuse(`repos_manage note: ${operation.failure}.`, REQUEST_REMEDIATION);
|
|
186
226
|
return {
|
|
187
227
|
action: "note",
|
|
188
|
-
result: yield* manager.note(root, request.name,
|
|
228
|
+
result: yield* manager.note(root, request.name, operation.success)
|
|
189
229
|
};
|
|
190
230
|
}
|
|
191
231
|
case "remove": return {
|
|
@@ -206,59 +246,27 @@ const reposManage = (args, fallbackCwd) => Effect.gen(function* () {
|
|
|
206
246
|
};
|
|
207
247
|
}
|
|
208
248
|
});
|
|
209
|
-
/**
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
"restore",
|
|
219
|
-
"deregister"
|
|
220
|
-
]).annotate({ description: "Which mutation to perform." }),
|
|
221
|
-
name: Schema.optionalKey(Schema.String.annotate({ description: "Repo name (pin, note, remove, rename; optional override for add)." })),
|
|
222
|
-
newName: Schema.optionalKey(Schema.String.annotate({ description: "New repo name (rename)." })),
|
|
223
|
-
ref: Schema.optionalKey(Schema.String.annotate({ description: "Git ref to pin/vendor to (pin, add)." })),
|
|
224
|
-
url: Schema.optionalKey(Schema.String.annotate({ description: "Repo URL to vendor (add)." })),
|
|
225
|
-
purpose: Schema.optionalKey(Schema.String.annotate({ description: "One-line purpose for the manifest (add)." })),
|
|
226
|
-
sparse: Schema.optionalKey(Schema.Array(Schema.String).annotate({ description: "Sparse-checkout patterns (add)." })),
|
|
227
|
-
orientation: Schema.optionalKey(Repos.RepoOrientation.annotate({ description: "Orientation block to write (add). Pass back what a preceding remove reported as removedEntry.orientation — add does NOT restore it on its own, so a re-vendor loses it otherwise." })),
|
|
228
|
-
op: Schema.optionalKey(Schema.Literals([
|
|
229
|
-
"add",
|
|
230
|
-
"remove",
|
|
231
|
-
"promote"
|
|
232
|
-
]).annotate({ description: "Note operation (note)." })),
|
|
233
|
-
note: Schema.optionalKey(Schema.String.annotate({ description: "Note text (note, op=add)." })),
|
|
234
|
-
id: Schema.optionalKey(Schema.String.annotate({ description: "Note id (note, op=remove|promote)." })),
|
|
235
|
-
into: Schema.optionalKey(Schema.Literals(["layout", "startHere"]).annotate({ description: "Orientation target (note, op=promote)." })),
|
|
236
|
-
names: Schema.optionalKey(Schema.Array(Schema.String).annotate({ description: "Repo names to restore (restore); omitted restores every dirty repo." })),
|
|
237
|
-
section: Schema.optionalKey(Schema.String.annotate({ description: "Stale registration name to clear (deregister), exactly as the drift report states it (e.g. .repos/old-name); the submodule. prefix is implied." })),
|
|
238
|
-
cwd: Schema.optionalKey(Schema.String.annotate({ description: "Directory to resolve the workspace root from." }))
|
|
239
|
-
});
|
|
240
|
-
const REMEDIATION = {
|
|
241
|
-
hint: "The mutation did not complete; inspect the vendored-repo state before retrying.",
|
|
242
|
-
suggestedTool: "repos_inspect"
|
|
243
|
-
};
|
|
244
|
-
const REQUEST_REMEDIATION = { hint: "Pass the fields the chosen action needs (pin: name+ref; add: url+ref+purpose; note: name+op plus note/id/into; remove: name; rename: name+newName; deregister: section)." };
|
|
245
|
-
/** The `repos_manage` tool value. Mutating: not read-only, not idempotent. */
|
|
246
|
-
const reposManageTool = Tool.make("repos_manage", {
|
|
247
|
-
description: "Mutating: sync (initialize/reconcile submodules per the manifest), pin (re-pin a repo to a new ref), add (vendor a new repo), note (add/remove/promote an agent note), remove (unvendor a repo), rename (rename a vendored repo's manifest key and worktree), restore (hard-reset a repo's worktree back to its pinned gitlink commit and re-apply sparse paths — DESTRUCTIVE to uncommitted worktree edits; never run implicitly), or deregister (clear a STALE submodule.<section> registration from the superproject's local git config — the phantom entry repos_inspect drift reports as localRegistrationDivergence with no matching manifest entry; refuses a section outside .repos/ and any section still backing a live manifest entry — canonically named or gitdir-diverged — and touches local config only, so nothing is staged). Pass action plus the fields that action needs: pin needs name+ref; add needs url+ref+purpose (name/sparse/orientation optional — pass orientation back from a preceding remove's removedEntry to make a re-vendor lossless); note needs name+op, plus note (op=add), id (op=remove), or id+into (op=promote); remove needs name; rename needs name (the old name) + newName; restore takes an optional names list — omitted, it restores every dirty repo and reports the clean ones as skipped; given, it restores exactly those repos even if already clean; deregister needs section (the registration name exactly as the drift report states it, e.g. .repos/old-name — no submodule. prefix). A decode failure names the missing field. The pin result surfaces commitMessage and staleNoteIds — review and commit after pinning. The remove result surfaces commitMessage, removedNotes and the removed entry's orientation block — promote any durable notes elsewhere, keep the orientation if you intend to re-vendor, then review and commit. The rename result surfaces commitMessage — review and commit after renaming. The restore result names exactly what was discarded. The deregister result lists the config keys the removed section carried — nothing to commit afterwards. Returns a typed object in structuredContent (content[] carries the same object as JSON).",
|
|
248
|
-
parameters: ReposManageParams,
|
|
249
|
+
/**
|
|
250
|
+
* The `repos_manage` tool value: a `McpToolkit.unionTool`, so the served input
|
|
251
|
+
* schema is the request union's strict `oneOf` keyed by `action`, and a bad
|
|
252
|
+
* call is rejected as invalid parameters before the handler runs. Mutating:
|
|
253
|
+
* not read-only, not idempotent.
|
|
254
|
+
*/
|
|
255
|
+
const reposManageTool = McpToolkit.unionTool("repos_manage", {
|
|
256
|
+
description: "Mutating: sync (initialize/reconcile submodules per the manifest), pin (re-pin a repo to a new ref), add (vendor a new repo), note (add/remove/promote an agent note), remove (unvendor a repo), rename (rename a vendored repo's manifest key and worktree), restore (hard-reset a repo's worktree back to its pinned gitlink commit and re-apply sparse paths — DESTRUCTIVE to uncommitted worktree edits; never run implicitly), or deregister (clear a STALE submodule.<section> registration from the superproject's local git config — the phantom entry repos_inspect drift reports as localRegistrationDivergence with no matching manifest entry; refuses a section outside .repos/ and any section still backing a live manifest entry — canonically named or gitdir-diverged — and touches local config only, so nothing is staged). Pass action plus the fields that action needs: pin needs name+ref; add needs url+ref+purpose (name/sparse/orientation optional — pass orientation back from a preceding remove's removedEntry to make a re-vendor lossless); note needs name+op, plus note (op=add), id (op=remove), or id+into (op=promote); remove needs name; rename needs name (the old name) + newName; restore takes an optional names list — omitted, it restores every dirty repo and reports the clean ones as skipped; given, it restores exactly those repos even if already clean; deregister needs section (the registration name exactly as the drift report states it, e.g. .repos/old-name — no submodule. prefix). Each action's fields are served as one member of a strict oneOf keyed by action; a missing field or a key the chosen action does not take is rejected as invalid parameters naming it. The pin result surfaces commitMessage and staleNoteIds — review and commit after pinning. The remove result surfaces commitMessage, removedNotes and the removed entry's orientation block — promote any durable notes elsewhere, keep the orientation if you intend to re-vendor, then review and commit. The rename result surfaces commitMessage — review and commit after renaming. The restore result names exactly what was discarded. The deregister result lists the config keys the removed section carried — nothing to commit afterwards. Returns a typed object in structuredContent (content[] carries the same object as JSON).",
|
|
257
|
+
parameters: ReposManageRequest,
|
|
249
258
|
success: ReposManageResult,
|
|
250
259
|
failure: McpToolError,
|
|
251
260
|
dependencies: [Repos.ReposManager, WorkspaceRoot]
|
|
252
261
|
}).annotate(Tool.Title, "Manage vendored repos").annotate(Tool.Readonly, false).annotate(Tool.Destructive, true).annotate(Tool.Idempotent, false).annotate(Tool.OpenWorld, false);
|
|
253
262
|
/**
|
|
254
|
-
* Wire handler:
|
|
255
|
-
* {@link
|
|
256
|
-
*
|
|
257
|
-
*
|
|
258
|
-
*
|
|
259
|
-
* through {@link mapEngineError}.
|
|
263
|
+
* Wire handler: `McpToolkit.unionHandler` decodes the raw payload into one
|
|
264
|
+
* {@link ReposManageRequest} member (an unknown key or a missing field fails
|
|
265
|
+
* `InvalidParams`, as a `Tool.make` decode failure does), then
|
|
266
|
+
* {@link reposManage} runs with its engine errors mapped onto
|
|
267
|
+
* {@link McpToolError} through {@link mapEngineError}.
|
|
260
268
|
*/
|
|
261
|
-
const handleReposManage = (fallbackCwd,
|
|
269
|
+
const handleReposManage = (fallbackCwd) => McpToolkit.unionHandler(reposManageTool, (request) => reposManage(request, fallbackCwd).pipe(Effect.mapError((error) => error._tag === "ToolRefusal" ? error : mapEngineError(request.cwd ?? fallbackCwd, REMEDIATION)(error))));
|
|
262
270
|
|
|
263
271
|
//#endregion
|
|
264
|
-
export { ReposManageAddResult, ReposManageDeregisterResult, ReposManageNoteResult,
|
|
272
|
+
export { ReposManageAddResult, ReposManageDeregisterResult, ReposManageNoteResult, ReposManagePinResult, ReposManageRemoveResult, ReposManageRenameResult, ReposManageRequest, ReposManageRestoreResult, ReposManageResult, ReposManageSyncResult, handleReposManage, reposManage, reposManageTool };
|
package/tools/turbo-inspect.js
CHANGED
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
import { McpToolError, mapEngineError } from "../errors.js";
|
|
2
|
+
import { ToolOutputSchema } from "@effected/mcp";
|
|
2
3
|
import { Effect, Schema } from "effect";
|
|
3
4
|
import { WorkspaceRoot } from "@effected/workspaces";
|
|
4
5
|
import { Turbo } from "@savvy-web/silk-effects";
|
|
5
6
|
import { Tool } from "effect/unstable/ai";
|
|
6
7
|
|
|
7
8
|
//#region src/tools/turbo-inspect.ts
|
|
9
|
+
/**
|
|
10
|
+
* The `turbo_inspect` MCP tool: a discriminated-union result schema keyed by
|
|
11
|
+
* `mode` (cache | graph | affected), each variant embedding the corresponding
|
|
12
|
+
* `Turbo` result schema from silk-effects. Read-only.
|
|
13
|
+
*
|
|
14
|
+
* @packageDocumentation
|
|
15
|
+
*/
|
|
8
16
|
/** Cache-diagnosis variant of the `turbo_inspect` result. */
|
|
9
17
|
const TurboCacheResult = Schema.Struct({
|
|
10
18
|
mode: Schema.Literal("cache"),
|
|
@@ -20,12 +28,15 @@ const TurboAffectedResult = Schema.Struct({
|
|
|
20
28
|
mode: Schema.Literal("affected"),
|
|
21
29
|
result: Turbo.AffectedResult
|
|
22
30
|
}).annotate({ identifier: "TurboAffectedResult" });
|
|
23
|
-
/**
|
|
24
|
-
|
|
31
|
+
/**
|
|
32
|
+
* The `turbo_inspect` tool result — a discriminated union keyed by `mode`,
|
|
33
|
+
* object-rooted so its `outputSchema` is served.
|
|
34
|
+
*/
|
|
35
|
+
const TurboInspectResult = ToolOutputSchema.objectRooted(Schema.Union([
|
|
25
36
|
TurboCacheResult,
|
|
26
37
|
TurboGraphResult,
|
|
27
38
|
TurboAffectedResult
|
|
28
|
-
]).annotate({
|
|
39
|
+
])).annotate({
|
|
29
40
|
identifier: "TurboInspectResult",
|
|
30
41
|
title: "turbo_inspect result",
|
|
31
42
|
description: "Read-only Turborepo inspection grouped by mode (cache | graph | affected)."
|