@savvy-web/mcp 1.8.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/savvy-mcp.js +5 -5
- package/index.d.ts +27 -15
- package/index.js +2 -2
- package/package.json +7 -12
- package/runtime.js +48 -57
- package/schema/effect-to-zod.js +165 -14
- package/server.js +10 -10
- package/tools/biome-check.js +19 -10
- package/tools/changeset-deps-detect.js +8 -19
- package/tools/changeset-deps-regen.js +7 -17
- package/tools/changeset-inspect.js +14 -19
- package/tools/changeset-preview.js +7 -15
- package/tools/changeset-validate.js +8 -9
- package/tools/repos-inspect.js +9 -18
- package/tools/repos-manage.js +30 -26
- package/tools/turbo-inspect.js +14 -18
- package/tools/workspace-info.js +18 -14
package/bin/savvy-mcp.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { makeSilkRuntimeLayer } from "../runtime.js";
|
|
3
3
|
import { startMcpServer } from "../server.js";
|
|
4
4
|
import { Layer, ManagedRuntime } from "effect";
|
|
5
|
-
import {
|
|
5
|
+
import { NodeServices } from "@effect/platform-node";
|
|
6
6
|
|
|
7
7
|
//#region src/bin.ts
|
|
8
8
|
/**
|
|
9
9
|
* Binary entrypoint for the `savvy-mcp` server.
|
|
10
10
|
*
|
|
11
|
-
* Resolves the project working directory, builds the long-lived runtime
|
|
12
|
-
* starts the MCP server over stdio.
|
|
11
|
+
* Resolves the project working directory, builds the long-lived runtime
|
|
12
|
+
* (root-bound to that directory), and starts the MCP server over stdio.
|
|
13
13
|
*
|
|
14
14
|
* @internal
|
|
15
15
|
*/
|
|
@@ -20,7 +20,7 @@ function resolveProjectDir() {
|
|
|
20
20
|
}
|
|
21
21
|
async function main() {
|
|
22
22
|
const cwd = resolveProjectDir();
|
|
23
|
-
const appLayer =
|
|
23
|
+
const appLayer = makeSilkRuntimeLayer(cwd).pipe(Layer.provide(NodeServices.layer));
|
|
24
24
|
const ctx = {
|
|
25
25
|
runtime: ManagedRuntime.make(appLayer),
|
|
26
26
|
cwd
|
package/index.d.ts
CHANGED
|
@@ -1,29 +1,41 @@
|
|
|
1
|
+
import { WorkspaceRoot } from "@effected/workspaces";
|
|
1
2
|
import { Changesets, Repos, SilkWorkspaceAnalyzer, Turbo } from "@savvy-web/silk-effects";
|
|
2
|
-
import { Layer, ManagedRuntime } from "effect";
|
|
3
|
-
import {
|
|
3
|
+
import { FileSystem, Layer, ManagedRuntime, Path } from "effect";
|
|
4
|
+
import { ChildProcessSpawner } from "effect/unstable/process";
|
|
4
5
|
import "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
6
|
//#region src/context.d.ts
|
|
7
|
+
/** Every service the MCP runtime provides to the tool handlers. */
|
|
8
|
+
type McpServices = SilkWorkspaceAnalyzer | WorkspaceRoot | Turbo.TurboInspector | Changesets.BranchAnalyzer | Changesets.ConfigInspector | Changesets.ReleasePlanner | Changesets.DepsRegen | Repos.ReposManager | Repos.ReposConfigStore;
|
|
6
9
|
/** The long-lived runtime and the project working directory. */
|
|
7
10
|
interface McpContext {
|
|
8
|
-
readonly runtime: ManagedRuntime.ManagedRuntime<
|
|
11
|
+
readonly runtime: ManagedRuntime.ManagedRuntime<McpServices, never>;
|
|
9
12
|
readonly cwd: string;
|
|
10
13
|
}
|
|
11
14
|
//#endregion
|
|
12
15
|
//#region src/runtime.d.ts
|
|
13
16
|
/**
|
|
14
|
-
*
|
|
15
|
-
* `
|
|
16
|
-
* `Changesets.
|
|
17
|
-
* `Changesets.
|
|
18
|
-
*
|
|
19
|
-
* layer (`
|
|
17
|
+
* Build the MCP runtime layer for a workspace root. Provides
|
|
18
|
+
* `SilkWorkspaceAnalyzer`, `WorkspaceRoot`, `Turbo.TurboInspector`,
|
|
19
|
+
* `Changesets.BranchAnalyzer`, `Changesets.ConfigInspector`,
|
|
20
|
+
* `Changesets.ReleasePlanner`, `Changesets.DepsRegen`, `Repos.ReposManager`,
|
|
21
|
+
* and `Repos.ReposConfigStore`; requires `ChildProcessSpawner` + `FileSystem`
|
|
22
|
+
* + `Path` from the host's platform layer (`NodeServices.layer` in bin.ts).
|
|
20
23
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
24
|
+
* @remarks
|
|
25
|
+
* The kit graph (`Workspaces.layerWithGit`) mints a fresh layer reference per
|
|
26
|
+
* call, so it is bound to a `const` and provided ONCE via `Layer.provideMerge`
|
|
27
|
+
* — layer memoization by reference then constructs each kit service exactly
|
|
28
|
+
* once and exposes `WorkspaceRoot` on the runtime. The same discipline gives
|
|
29
|
+
* the whole runtime a SINGLE `ConfigInspector` (the MCP server holds one for
|
|
30
|
+
* its whole process lifetime, #229 — `changeset_inspect` refreshes it before
|
|
31
|
+
* every call, and `BranchAnalyzer`, `ReleasePlanner`, and `DepsRegen` all read
|
|
32
|
+
* through that shared instance). `DepsRegen` is gated by silk's adaptive
|
|
33
|
+
* publishability detector — provided closer than the kit graph, so it wins
|
|
34
|
+
* over the kit's npm-semantics default — mirroring
|
|
35
|
+
* `Changesets.makeDepsRegenDefault`'s composition ("versionable minus
|
|
36
|
+
* ignored", identical to the savvy CLI).
|
|
25
37
|
*/
|
|
26
|
-
declare const
|
|
38
|
+
declare const makeSilkRuntimeLayer: (cwd: string) => Layer.Layer<McpServices, never, FileSystem.FileSystem | Path.Path | ChildProcessSpawner.ChildProcessSpawner>;
|
|
27
39
|
//#endregion
|
|
28
40
|
//#region src/server.d.ts
|
|
29
41
|
/** Build the server and connect it over stdio. */
|
|
@@ -39,5 +51,5 @@ declare function startMcpServer(ctx: McpContext): Promise<void>;
|
|
|
39
51
|
*/
|
|
40
52
|
declare const CURRENT_MCP_VERSION = "0.0.0";
|
|
41
53
|
//#endregion
|
|
42
|
-
export { CURRENT_MCP_VERSION, type McpContext,
|
|
54
|
+
export { CURRENT_MCP_VERSION, type McpContext, type McpServices, makeSilkRuntimeLayer, startMcpServer };
|
|
43
55
|
//# sourceMappingURL=index.d.ts.map
|
package/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { makeSilkRuntimeLayer } from "./runtime.js";
|
|
2
2
|
import { CURRENT_MCP_VERSION } from "./version.js";
|
|
3
3
|
import { startMcpServer } from "./server.js";
|
|
4
4
|
|
|
5
|
-
export { CURRENT_MCP_VERSION,
|
|
5
|
+
export { CURRENT_MCP_VERSION, makeSilkRuntimeLayer, startMcpServer };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@savvy-web/mcp",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
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",
|
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
"exports": {
|
|
23
23
|
".": {
|
|
24
24
|
"types": "./index.d.ts",
|
|
25
|
-
"import": "./index.js"
|
|
25
|
+
"import": "./index.js",
|
|
26
|
+
"default": "./index.js"
|
|
26
27
|
},
|
|
27
28
|
"./package.json": "./package.json"
|
|
28
29
|
},
|
|
@@ -30,17 +31,11 @@
|
|
|
30
31
|
"savvy-mcp": "bin/savvy-mcp.js"
|
|
31
32
|
},
|
|
32
33
|
"dependencies": {
|
|
33
|
-
"@effect/
|
|
34
|
-
"@
|
|
35
|
-
"@effect/platform": "^0.96.2",
|
|
36
|
-
"@effect/platform-node": "^0.107.0",
|
|
37
|
-
"@effect/rpc": "^0.75.1",
|
|
38
|
-
"@effect/sql": "^0.51.1",
|
|
39
|
-
"@effect/workflow": "^0.18.2",
|
|
34
|
+
"@effect/platform-node": "4.0.0-beta.98",
|
|
35
|
+
"@effected/workspaces": "^0.3.0",
|
|
40
36
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
41
|
-
"@savvy-web/silk-effects": "
|
|
42
|
-
"effect": "
|
|
43
|
-
"workspaces-effect": "^2.0.3",
|
|
37
|
+
"@savvy-web/silk-effects": "4.0.0",
|
|
38
|
+
"effect": "4.0.0-beta.98",
|
|
44
39
|
"zod": "^4.4.3"
|
|
45
40
|
}
|
|
46
41
|
}
|
package/runtime.js
CHANGED
|
@@ -1,71 +1,62 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Workspaces } from "@effected/workspaces";
|
|
2
|
+
import { ChangesetConfigLive, ChangesetConfigReaderLive, Changesets, PublishabilityDetectorAdaptiveLive, Repos, SilkWorkspaceAnalyzerLive, TagStrategyLive, ToolDiscoveryLive, Turbo, VersioningStrategyLive } from "@savvy-web/silk-effects";
|
|
2
3
|
import { Layer } from "effect";
|
|
3
|
-
import { PointInTimeWorkspaceLive, WorkspaceRootLive, WorkspacesLive } from "workspaces-effect";
|
|
4
4
|
|
|
5
5
|
//#region src/runtime.ts
|
|
6
6
|
/**
|
|
7
7
|
* Composes the long-lived Effect runtime layer for the MCP server.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* (bin.ts)
|
|
9
|
+
* {@link makeSilkRuntimeLayer} builds the full service graph for ONE workspace
|
|
10
|
+
* root: the `@effected/workspaces` kit layers are root-bound at layer build
|
|
11
|
+
* (single-root by design), so the server resolves its project directory once
|
|
12
|
+
* at startup (bin.ts) and builds the layer with that root. The layer still
|
|
13
|
+
* requires the platform services (`FileSystem` + `Path` +
|
|
14
|
+
* `ChildProcessSpawner`); the host supplies them via `NodeServices.layer`.
|
|
13
15
|
*
|
|
14
16
|
* @packageDocumentation
|
|
15
17
|
*/
|
|
16
18
|
/**
|
|
17
|
-
*
|
|
19
|
+
* Build the MCP runtime layer for a workspace root. Provides
|
|
20
|
+
* `SilkWorkspaceAnalyzer`, `WorkspaceRoot`, `Turbo.TurboInspector`,
|
|
21
|
+
* `Changesets.BranchAnalyzer`, `Changesets.ConfigInspector`,
|
|
22
|
+
* `Changesets.ReleasePlanner`, `Changesets.DepsRegen`, `Repos.ReposManager`,
|
|
23
|
+
* and `Repos.ReposConfigStore`; requires `ChildProcessSpawner` + `FileSystem`
|
|
24
|
+
* + `Path` from the host's platform layer (`NodeServices.layer` in bin.ts).
|
|
18
25
|
*
|
|
19
|
-
*
|
|
20
|
-
* `
|
|
21
|
-
* is
|
|
22
|
-
*
|
|
26
|
+
* @remarks
|
|
27
|
+
* The kit graph (`Workspaces.layerWithGit`) mints a fresh layer reference per
|
|
28
|
+
* call, so it is bound to a `const` and provided ONCE via `Layer.provideMerge`
|
|
29
|
+
* — layer memoization by reference then constructs each kit service exactly
|
|
30
|
+
* once and exposes `WorkspaceRoot` on the runtime. The same discipline gives
|
|
31
|
+
* the whole runtime a SINGLE `ConfigInspector` (the MCP server holds one for
|
|
32
|
+
* its whole process lifetime, #229 — `changeset_inspect` refreshes it before
|
|
33
|
+
* every call, and `BranchAnalyzer`, `ReleasePlanner`, and `DepsRegen` all read
|
|
34
|
+
* through that shared instance). `DepsRegen` is gated by silk's adaptive
|
|
35
|
+
* publishability detector — provided closer than the kit graph, so it wins
|
|
36
|
+
* over the kit's npm-semantics default — mirroring
|
|
37
|
+
* `Changesets.makeDepsRegenDefault`'s composition ("versionable minus
|
|
38
|
+
* ignored", identical to the savvy CLI).
|
|
23
39
|
*/
|
|
24
|
-
const
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
* `
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const DepsRegenGroupLive = Changesets.DepsRegenLive.pipe(Layer.provide(InspectorAndAnalyzerLive), Layer.provide(PointInTimeWorkspaceLive), Layer.provide(ChangesetConfigLive.pipe(Layer.provide(ChangesetConfigReaderLive))));
|
|
45
|
-
/**
|
|
46
|
-
* `Repos.ReposManager` + `Repos.ReposConfigStore`, both exposed on the
|
|
47
|
-
* runtime. `ReposManagerLive` requires `ReposConfigStore`, so it is given its
|
|
48
|
-
* own `ReposConfigStoreLive` reference here (`Layer.mergeAll` does not
|
|
49
|
-
* cross-feed sibling layers); `ReposConfigStore` is ALSO merged in directly so
|
|
50
|
-
* `repos_inspect`'s config mode can resolve it on its own. The remaining
|
|
51
|
-
* `CommandExecutor` + `FileSystem` + `Path` requirements flow up to the host
|
|
52
|
-
* platform layer (`NodeContext.layer` in bin.ts).
|
|
53
|
-
*/
|
|
54
|
-
const ReposGroupLive = Layer.mergeAll(Repos.ReposConfigStoreLive, Repos.ReposManagerLive.pipe(Layer.provide(Repos.ReposConfigStoreLive)));
|
|
55
|
-
/**
|
|
56
|
-
* The MCP runtime layer. Provides `SilkWorkspaceAnalyzer`, `WorkspaceRoot`,
|
|
57
|
-
* `Turbo.TurboInspector`, `Changesets.BranchAnalyzer`,
|
|
58
|
-
* `Changesets.ConfigInspector`, `Changesets.ReleasePlanner`,
|
|
59
|
-
* `Changesets.DepsRegen`, `Repos.ReposManager`, and `Repos.ReposConfigStore`;
|
|
60
|
-
* requires `CommandExecutor` + `FileSystem` + `Path` from the host's platform
|
|
61
|
-
* layer (`NodeContext.layer` in bin.ts).
|
|
62
|
-
*
|
|
63
|
-
* `TurboInspectorLive` is fed its own `ToolDiscoveryLive`, whose
|
|
64
|
-
* `PackageManagerDetector` + `WorkspaceRoot` requirements are satisfied by
|
|
65
|
-
* {@link DepsLive}; the leftover `CommandExecutor` + `FileSystem` flow up to the
|
|
66
|
-
* host platform layer.
|
|
67
|
-
*/
|
|
68
|
-
const SilkRuntimeLive = Layer.mergeAll(SilkWorkspaceAnalyzerLive, WorkspaceRootLive, Turbo.TurboInspectorLive.pipe(Layer.provide(ToolDiscoveryLive)), InspectorAndAnalyzerLive, DepsRegenGroupLive, ReposGroupLive).pipe(Layer.provide(DepsLive));
|
|
40
|
+
const makeSilkRuntimeLayer = (cwd) => {
|
|
41
|
+
const kitGraph = Workspaces.layerWithGit({ cwd });
|
|
42
|
+
const analyzerDeps = Layer.mergeAll(ChangesetConfigReaderLive, TagStrategyLive, VersioningStrategyLive.pipe(Layer.provide(ChangesetConfigReaderLive)));
|
|
43
|
+
/**
|
|
44
|
+
* `BranchAnalyzer` + `ReleasePlanner` + the ONE shared `ConfigInspector`,
|
|
45
|
+
* merged so all three land on the runtime and every internal consumer
|
|
46
|
+
* memoizes onto the same inspector reference.
|
|
47
|
+
*/
|
|
48
|
+
const inspectorAndAnalyzer = Changesets.BranchAnalyzerLive.pipe(Layer.provideMerge(Changesets.ReleasePlannerLive), Layer.provideMerge(Changesets.ConfigInspectorLive.pipe(Layer.provide(ChangesetConfigReaderLive))));
|
|
49
|
+
const changesetConfig = ChangesetConfigLive.pipe(Layer.provide(ChangesetConfigReaderLive));
|
|
50
|
+
const depsRegen = Changesets.DepsRegenLive.pipe(Layer.provide(inspectorAndAnalyzer), Layer.provide(PublishabilityDetectorAdaptiveLive.pipe(Layer.provide(changesetConfig))), Layer.provide(changesetConfig));
|
|
51
|
+
/**
|
|
52
|
+
* `ReposManagerLive` requires `ReposConfigStore`, so it is given its own
|
|
53
|
+
* `ReposConfigStoreLive` reference; the store is ALSO merged in directly so
|
|
54
|
+
* `repos_inspect`'s config mode can resolve it on its own. Same reference —
|
|
55
|
+
* one store instance.
|
|
56
|
+
*/
|
|
57
|
+
const repos = Layer.mergeAll(Repos.ReposConfigStoreLive, Repos.ReposManagerLive.pipe(Layer.provide(Repos.ReposConfigStoreLive)));
|
|
58
|
+
return Layer.mergeAll(SilkWorkspaceAnalyzerLive.pipe(Layer.provide(analyzerDeps)), Turbo.TurboInspectorLive.pipe(Layer.provide(ToolDiscoveryLive)), inspectorAndAnalyzer, depsRegen, repos).pipe(Layer.provideMerge(kitGraph));
|
|
59
|
+
};
|
|
69
60
|
|
|
70
61
|
//#endregion
|
|
71
|
-
export {
|
|
62
|
+
export { makeSilkRuntimeLayer };
|
package/schema/effect-to-zod.js
CHANGED
|
@@ -1,54 +1,205 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Schema } from "effect";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
|
|
4
4
|
//#region src/schema/effect-to-zod.ts
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
* Bridge an Effect Schema to a zod schema by routing through JSON Schema, so a
|
|
7
|
+
* tool keeps Effect Schema as the canonical source of truth while the MCP SDK
|
|
8
|
+
* receives the zod instance its `registerTool` API requires.
|
|
9
|
+
*
|
|
10
|
+
* @packageDocumentation
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Convert an Effect `Schema.Codec<A, I>` to a zod schema:
|
|
14
|
+
* `Schema.toJsonSchemaDocument`, then inline every `#/$defs/*` `$ref` and
|
|
15
|
+
* normalize back to the wire contract, then `z.fromJSONSchema`.
|
|
8
16
|
*
|
|
9
17
|
* @remarks
|
|
10
18
|
* - Effect-only refinements (custom predicates, brands) erase during the
|
|
11
19
|
* round-trip; declare zod directly if boundary enforcement matters.
|
|
12
20
|
* - The source schema must not use `Schema.suspend` (recursive `$ref`s would
|
|
13
|
-
* make
|
|
21
|
+
* make the inlining pass non-terminating). MCP tool output schemas in this
|
|
14
22
|
* package are non-recursive projections by construction.
|
|
15
23
|
* - The MCP SDK normalises `outputSchema` to an object; non-object results
|
|
16
24
|
* (e.g. a bare union) are wrapped in a permissive object so the SDK accepts
|
|
17
25
|
* them.
|
|
18
26
|
*/
|
|
19
27
|
const effectToZodSchema = (schema) => {
|
|
20
|
-
const
|
|
21
|
-
const inlined = inlineAllRefs(jsonSchema);
|
|
28
|
+
const inlined = effectSchemaToInlinedJsonSchema(schema);
|
|
22
29
|
const zodSchema = z.fromJSONSchema(inlined);
|
|
23
30
|
if (isObjectLike(zodSchema)) return zodSchema;
|
|
24
31
|
return z.object({}).catchall(z.unknown());
|
|
25
32
|
};
|
|
26
33
|
const isObjectLike = (schema) => schema instanceof z.ZodObject;
|
|
34
|
+
/**
|
|
35
|
+
* Produce the inlined, normalized JSON Schema object handed to
|
|
36
|
+
* `z.fromJSONSchema`. Exposed separately so schema-snapshot tooling can
|
|
37
|
+
* serialize exactly what the bridge feeds zod.
|
|
38
|
+
*
|
|
39
|
+
* @remarks
|
|
40
|
+
* v4's `Schema.toJsonSchemaDocument` returns `{ dialect, schema, definitions }`
|
|
41
|
+
* with every identifier-annotated subschema hoisted into `definitions` and
|
|
42
|
+
* referenced as `#/$defs/<name>` — including the root itself. Two v4 encoding
|
|
43
|
+
* changes are normalized back to the v3-era wire contract here (in the bridge,
|
|
44
|
+
* never in the public schemas):
|
|
45
|
+
*
|
|
46
|
+
* - `Schema.Number` now encodes non-finite values as strings, emitting
|
|
47
|
+
* `anyOf: [number, "NaN", "Infinity", "-Infinity"]`. Tool results never
|
|
48
|
+
* carry non-finite numbers, so this collapses to `{ type: "number" }`.
|
|
49
|
+
* - `Schema.optional(S)` now admits `undefined`, emitting
|
|
50
|
+
* `anyOf: [S, { type: "null" }]` on the (already non-required) key. The
|
|
51
|
+
* handlers build results with conditional spreads and never emit
|
|
52
|
+
* `undefined`/`null` for optional keys, so the null arm is dropped.
|
|
53
|
+
* Deliberate `Schema.NullOr` fields are all on required keys and keep
|
|
54
|
+
* their null arm.
|
|
55
|
+
* - Filter checks (`isMinLength`, `isPattern`, …) now emit as
|
|
56
|
+
* `allOf: [{ minLength: 1 }]` instead of inline keywords. Bare-constraint
|
|
57
|
+
* `allOf` members are folded back into the parent node.
|
|
58
|
+
*/
|
|
59
|
+
const effectSchemaToInlinedJsonSchema = (schema) => {
|
|
60
|
+
const doc = Schema.toJsonSchemaDocument(schema);
|
|
61
|
+
return inlineAllRefs(doc.schema, doc.definitions);
|
|
62
|
+
};
|
|
27
63
|
const REF_PREFIX = "#/$defs/";
|
|
64
|
+
/** A JSON-schema node matching `{ "type": "null" }` exactly. */
|
|
65
|
+
const isNullSchema = (value) => {
|
|
66
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
|
|
67
|
+
const obj = value;
|
|
68
|
+
return obj.type === "null" && Object.keys(obj).length === 1;
|
|
69
|
+
};
|
|
70
|
+
/** A JSON-schema node matching `{ "type": "string", "enum": ["NaN" | "Infinity" | "-Infinity"] }`. */
|
|
71
|
+
const isNonFiniteArm = (value) => {
|
|
72
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
|
|
73
|
+
const obj = value;
|
|
74
|
+
if (obj.type !== "string" || !Array.isArray(obj.enum) || obj.enum.length !== 1) return false;
|
|
75
|
+
const literal = obj.enum[0];
|
|
76
|
+
return literal === "NaN" || literal === "Infinity" || literal === "-Infinity";
|
|
77
|
+
};
|
|
78
|
+
/** A JSON-schema node matching `{ "type": "number" }` exactly. */
|
|
79
|
+
const isPlainNumberArm = (value) => {
|
|
80
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
|
|
81
|
+
const obj = value;
|
|
82
|
+
return obj.type === "number" && Object.keys(obj).length === 1;
|
|
83
|
+
};
|
|
84
|
+
/** JSON-schema keywords that are pure value constraints (no structural meaning). */
|
|
85
|
+
const CONSTRAINT_KEYS = /* @__PURE__ */ new Set([
|
|
86
|
+
"minLength",
|
|
87
|
+
"maxLength",
|
|
88
|
+
"pattern",
|
|
89
|
+
"minimum",
|
|
90
|
+
"maximum",
|
|
91
|
+
"exclusiveMinimum",
|
|
92
|
+
"exclusiveMaximum",
|
|
93
|
+
"multipleOf",
|
|
94
|
+
"minItems",
|
|
95
|
+
"maxItems",
|
|
96
|
+
"format"
|
|
97
|
+
]);
|
|
98
|
+
/** A node whose every key is a pure value-constraint keyword. */
|
|
99
|
+
const isBareConstraint = (value) => {
|
|
100
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
|
|
101
|
+
const keys = Object.keys(value);
|
|
102
|
+
return keys.length > 0 && keys.every((k) => CONSTRAINT_KEYS.has(k));
|
|
103
|
+
};
|
|
104
|
+
/**
|
|
105
|
+
* Fold `allOf` members that are bare constraint objects — v4's encoding of
|
|
106
|
+
* filter checks — back into the parent node (v3 inlined them). Keys already
|
|
107
|
+
* present on the parent are left inside `allOf` untouched.
|
|
108
|
+
*/
|
|
109
|
+
const flattenConstraintAllOf = (node) => {
|
|
110
|
+
const allOf = node.allOf;
|
|
111
|
+
if (!Array.isArray(allOf)) return node;
|
|
112
|
+
const remaining = [];
|
|
113
|
+
const folded = {};
|
|
114
|
+
for (const member of allOf) {
|
|
115
|
+
if (!isBareConstraint(member) || Object.keys(member).some((k) => k in node || k in folded)) {
|
|
116
|
+
remaining.push(member);
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
Object.assign(folded, member);
|
|
120
|
+
}
|
|
121
|
+
if (Object.keys(folded).length === 0) return node;
|
|
122
|
+
const { allOf: _dropped, ...rest } = node;
|
|
123
|
+
return remaining.length > 0 ? {
|
|
124
|
+
...rest,
|
|
125
|
+
...folded,
|
|
126
|
+
allOf: remaining
|
|
127
|
+
} : {
|
|
128
|
+
...rest,
|
|
129
|
+
...folded
|
|
130
|
+
};
|
|
131
|
+
};
|
|
132
|
+
/**
|
|
133
|
+
* Collapse v4's non-finite number encoding — `anyOf: [{ type: "number" },
|
|
134
|
+
* "NaN", "Infinity", "-Infinity"]` — to `{ type: "number" }`, keeping any
|
|
135
|
+
* sibling annotations (description, title) on the node.
|
|
136
|
+
*/
|
|
137
|
+
const collapseNonFiniteNumber = (node) => {
|
|
138
|
+
const anyOf = node.anyOf;
|
|
139
|
+
if (!Array.isArray(anyOf) || anyOf.length !== 4) return node;
|
|
140
|
+
const numberArms = anyOf.filter(isPlainNumberArm);
|
|
141
|
+
const nonFiniteArms = anyOf.filter(isNonFiniteArm);
|
|
142
|
+
if (numberArms.length !== 1 || nonFiniteArms.length !== 3) return node;
|
|
143
|
+
const { anyOf: _dropped, ...siblings } = node;
|
|
144
|
+
return {
|
|
145
|
+
type: "number",
|
|
146
|
+
...siblings
|
|
147
|
+
};
|
|
148
|
+
};
|
|
149
|
+
/**
|
|
150
|
+
* Drop the `{ type: "null" }` arm that v4's `Schema.optional` adds for its
|
|
151
|
+
* `undefined` case. Applied only to non-required properties, so deliberate
|
|
152
|
+
* `Schema.NullOr` fields (all on required keys) keep their null arm.
|
|
153
|
+
*/
|
|
154
|
+
const dropUndefinedArm = (node) => {
|
|
155
|
+
if (node === null || typeof node !== "object" || Array.isArray(node)) return node;
|
|
156
|
+
const obj = node;
|
|
157
|
+
if (!Array.isArray(obj.anyOf)) return obj;
|
|
158
|
+
const arms = obj.anyOf.filter((arm) => !isNullSchema(arm));
|
|
159
|
+
if (arms.length === obj.anyOf.length) return obj;
|
|
160
|
+
const { anyOf: _dropped, ...siblings } = obj;
|
|
161
|
+
if (arms.length === 1 && arms[0] !== null && typeof arms[0] === "object" && !Array.isArray(arms[0])) return collapseNonFiniteNumber({
|
|
162
|
+
...arms[0],
|
|
163
|
+
...siblings
|
|
164
|
+
});
|
|
165
|
+
return collapseNonFiniteNumber({
|
|
166
|
+
anyOf: arms,
|
|
167
|
+
...siblings
|
|
168
|
+
});
|
|
169
|
+
};
|
|
28
170
|
/**
|
|
29
|
-
* Replace every `$ref: "#/$defs/X"` node with the contents of
|
|
30
|
-
* recursively, and
|
|
171
|
+
* Replace every `$ref: "#/$defs/X"` node with the contents of
|
|
172
|
+
* `definitions.X`, recursively, and normalize the v4 encoding deltas back to
|
|
173
|
+
* the wire contract. Assumes acyclic refs.
|
|
31
174
|
*/
|
|
32
|
-
const inlineAllRefs = (root) => {
|
|
33
|
-
const defs = root.$defs ?? {};
|
|
175
|
+
const inlineAllRefs = (root, defs) => {
|
|
34
176
|
const visit = (value) => {
|
|
35
177
|
if (Array.isArray(value)) return value.map(visit);
|
|
36
178
|
if (value === null || typeof value !== "object") return value;
|
|
37
179
|
const obj = value;
|
|
38
180
|
if (typeof obj.$ref === "string" && obj.$ref.startsWith(REF_PREFIX)) {
|
|
39
|
-
const
|
|
40
|
-
const target = defs[defName];
|
|
181
|
+
const target = defs[obj.$ref.slice(8)];
|
|
41
182
|
if (target !== void 0) return visit(target);
|
|
42
183
|
}
|
|
184
|
+
const required = Array.isArray(obj.required) ? obj.required : [];
|
|
43
185
|
const out = {};
|
|
44
186
|
for (const [k, v] of Object.entries(obj)) {
|
|
45
187
|
if (k === "$defs") continue;
|
|
188
|
+
if (k === "properties" && v !== null && typeof v === "object" && !Array.isArray(v)) {
|
|
189
|
+
const props = {};
|
|
190
|
+
for (const [propKey, propValue] of Object.entries(v)) {
|
|
191
|
+
const visited = visit(propValue);
|
|
192
|
+
props[propKey] = required.includes(propKey) ? visited : dropUndefinedArm(visited);
|
|
193
|
+
}
|
|
194
|
+
out[k] = props;
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
46
197
|
out[k] = visit(v);
|
|
47
198
|
}
|
|
48
|
-
return out;
|
|
199
|
+
return collapseNonFiniteNumber(flattenConstraintAllOf(out));
|
|
49
200
|
};
|
|
50
201
|
return visit(root);
|
|
51
202
|
};
|
|
52
203
|
|
|
53
204
|
//#endregion
|
|
54
|
-
export { effectToZodSchema };
|
|
205
|
+
export { effectSchemaToInlinedJsonSchema, effectToZodSchema };
|
package/server.js
CHANGED
|
@@ -43,7 +43,7 @@ function buildServer(ctx) {
|
|
|
43
43
|
}, async (args) => {
|
|
44
44
|
const root = args.cwd ?? ctx.cwd;
|
|
45
45
|
const data = await ctx.runtime.runPromise(workspaceInfo(root));
|
|
46
|
-
const text = Schema.
|
|
46
|
+
const text = Schema.decodeUnknownSync(WorkspaceInfoAsMarkdown)(data);
|
|
47
47
|
return structuredResult(text, data);
|
|
48
48
|
});
|
|
49
49
|
server.registerTool("turbo_inspect", {
|
|
@@ -62,7 +62,7 @@ function buildServer(ctx) {
|
|
|
62
62
|
annotations: { readOnlyHint: true }
|
|
63
63
|
}, async (args) => {
|
|
64
64
|
const data = await ctx.runtime.runPromise(turboInspect(args, ctx.cwd));
|
|
65
|
-
const text = Schema.
|
|
65
|
+
const text = Schema.decodeUnknownSync(TurboInspectAsMarkdown)(data);
|
|
66
66
|
return structuredResult(text, data);
|
|
67
67
|
});
|
|
68
68
|
server.registerTool("changeset_inspect", {
|
|
@@ -81,7 +81,7 @@ function buildServer(ctx) {
|
|
|
81
81
|
annotations: { readOnlyHint: true }
|
|
82
82
|
}, async (args) => {
|
|
83
83
|
const data = await ctx.runtime.runPromise(changesetInspect(args, ctx.cwd));
|
|
84
|
-
const text = Schema.
|
|
84
|
+
const text = Schema.decodeUnknownSync(ChangesetInspectAsMarkdown)(data);
|
|
85
85
|
return structuredResult(text, data);
|
|
86
86
|
});
|
|
87
87
|
server.registerTool("changeset_validate", {
|
|
@@ -94,7 +94,7 @@ function buildServer(ctx) {
|
|
|
94
94
|
annotations: { readOnlyHint: true }
|
|
95
95
|
}, async (args) => {
|
|
96
96
|
const data = await ctx.runtime.runPromise(changesetValidate(args, ctx.cwd));
|
|
97
|
-
const text = Schema.
|
|
97
|
+
const text = Schema.decodeUnknownSync(ChangesetValidateAsMarkdown)(data);
|
|
98
98
|
return structuredResult(text, data);
|
|
99
99
|
});
|
|
100
100
|
server.registerTool("changeset_deps_detect", {
|
|
@@ -110,7 +110,7 @@ function buildServer(ctx) {
|
|
|
110
110
|
annotations: { readOnlyHint: true }
|
|
111
111
|
}, async (args) => {
|
|
112
112
|
const data = await ctx.runtime.runPromise(changesetDepsDetect(args, ctx.cwd));
|
|
113
|
-
const text = Schema.
|
|
113
|
+
const text = Schema.decodeUnknownSync(ChangesetDepsDetectAsMarkdown)(data);
|
|
114
114
|
return structuredResult(text, data);
|
|
115
115
|
});
|
|
116
116
|
server.registerTool("changeset_preview", {
|
|
@@ -120,7 +120,7 @@ function buildServer(ctx) {
|
|
|
120
120
|
annotations: { readOnlyHint: true }
|
|
121
121
|
}, async (args) => {
|
|
122
122
|
const data = await ctx.runtime.runPromise(changesetPreview(args, ctx.cwd));
|
|
123
|
-
const text = Schema.
|
|
123
|
+
const text = Schema.decodeUnknownSync(ChangesetPreviewAsMarkdown)(data);
|
|
124
124
|
return structuredResult(text, data);
|
|
125
125
|
});
|
|
126
126
|
server.registerTool("changeset_deps_regen", {
|
|
@@ -140,7 +140,7 @@ function buildServer(ctx) {
|
|
|
140
140
|
}
|
|
141
141
|
}, async (args) => {
|
|
142
142
|
const data = await ctx.runtime.runPromise(changesetDepsRegen(args, ctx.cwd));
|
|
143
|
-
const text = Schema.
|
|
143
|
+
const text = Schema.decodeUnknownSync(ChangesetDepsRegenAsMarkdown)(data);
|
|
144
144
|
return structuredResult(text, data);
|
|
145
145
|
});
|
|
146
146
|
server.registerTool("repos_inspect", {
|
|
@@ -154,7 +154,7 @@ function buildServer(ctx) {
|
|
|
154
154
|
annotations: { readOnlyHint: true }
|
|
155
155
|
}, async (args) => {
|
|
156
156
|
const data = await ctx.runtime.runPromise(reposInspect(args, ctx.cwd));
|
|
157
|
-
const text = Schema.
|
|
157
|
+
const text = Schema.decodeUnknownSync(ReposInspectAsMarkdown)(data);
|
|
158
158
|
return structuredResult(text, data);
|
|
159
159
|
});
|
|
160
160
|
server.registerTool("repos_manage", {
|
|
@@ -189,7 +189,7 @@ function buildServer(ctx) {
|
|
|
189
189
|
}
|
|
190
190
|
}, async (args) => {
|
|
191
191
|
const data = await ctx.runtime.runPromise(reposManage(args, ctx.cwd));
|
|
192
|
-
const text = Schema.
|
|
192
|
+
const text = Schema.decodeUnknownSync(ReposManageAsMarkdown)(data);
|
|
193
193
|
return structuredResult(text, data);
|
|
194
194
|
});
|
|
195
195
|
server.registerTool("biome_check", {
|
|
@@ -205,7 +205,7 @@ function buildServer(ctx) {
|
|
|
205
205
|
outputSchema: effectToZodSchema(BiomeCheckResult)
|
|
206
206
|
}, async (args) => {
|
|
207
207
|
const data = await runBiomeCheck(args, ctx.cwd);
|
|
208
|
-
const text = Schema.
|
|
208
|
+
const text = Schema.decodeUnknownSync(BiomeCheckAsMarkdown)(data);
|
|
209
209
|
return structuredResult(text, data);
|
|
210
210
|
});
|
|
211
211
|
return server;
|
package/tools/biome-check.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Lint } from "@savvy-web/silk-effects";
|
|
2
|
-
import {
|
|
2
|
+
import { Schema, SchemaGetter } from "effect";
|
|
3
3
|
import { spawnSync } from "node:child_process";
|
|
4
4
|
import { realpathSync } from "node:fs";
|
|
5
5
|
import { relative, resolve, sep } from "node:path";
|
|
@@ -14,7 +14,11 @@ import { relative, resolve, sep } from "node:path";
|
|
|
14
14
|
* @packageDocumentation
|
|
15
15
|
*/
|
|
16
16
|
/** Normalized diagnostic severity. */
|
|
17
|
-
const BiomeSeverity = Schema.
|
|
17
|
+
const BiomeSeverity = Schema.Literals([
|
|
18
|
+
"error",
|
|
19
|
+
"warning",
|
|
20
|
+
"info"
|
|
21
|
+
]);
|
|
18
22
|
/** A single normalized Biome diagnostic. */
|
|
19
23
|
const BiomeDiagnostic = Schema.Struct({
|
|
20
24
|
file: Schema.String,
|
|
@@ -24,7 +28,7 @@ const BiomeDiagnostic = Schema.Struct({
|
|
|
24
28
|
message: Schema.String,
|
|
25
29
|
/** Present only when `strict` upgraded this diagnostic; holds the project-configured severity. */
|
|
26
30
|
originalSeverity: Schema.optional(BiomeSeverity)
|
|
27
|
-
}).
|
|
31
|
+
}).annotate({ identifier: "BiomeDiagnostic" });
|
|
28
32
|
/** The `biome_check` tool result. */
|
|
29
33
|
const BiomeCheckResult = Schema.Struct({
|
|
30
34
|
summary: Schema.Struct({
|
|
@@ -36,7 +40,7 @@ const BiomeCheckResult = Schema.Struct({
|
|
|
36
40
|
diagnostics: Schema.Array(BiomeDiagnostic),
|
|
37
41
|
wrote: Schema.Boolean,
|
|
38
42
|
guidance: Schema.String
|
|
39
|
-
}).
|
|
43
|
+
}).annotate({
|
|
40
44
|
identifier: "BiomeCheckResult",
|
|
41
45
|
title: "biome_check result",
|
|
42
46
|
description: "Structured Biome diagnostics, with a flag for whether a --write pass ran."
|
|
@@ -51,7 +55,13 @@ const GUIDANCE_STRICT_NOTE = "Diagnostics marked with originalSeverity are proje
|
|
|
51
55
|
const GitlabDiagnostic = Schema.Struct({
|
|
52
56
|
description: Schema.String,
|
|
53
57
|
check_name: Schema.String,
|
|
54
|
-
severity: Schema.
|
|
58
|
+
severity: Schema.Literals([
|
|
59
|
+
"info",
|
|
60
|
+
"minor",
|
|
61
|
+
"major",
|
|
62
|
+
"critical",
|
|
63
|
+
"blocker"
|
|
64
|
+
]),
|
|
55
65
|
location: Schema.Struct({
|
|
56
66
|
path: Schema.String,
|
|
57
67
|
lines: Schema.Struct({ begin: Schema.Number })
|
|
@@ -128,11 +138,10 @@ const renderMarkdown = (data) => {
|
|
|
128
138
|
return lines.join("\n");
|
|
129
139
|
};
|
|
130
140
|
/** One-way transform: result to markdown. Encoding back is forbidden. */
|
|
131
|
-
const BiomeCheckAsMarkdown = Schema.
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
});
|
|
141
|
+
const BiomeCheckAsMarkdown = BiomeCheckResult.pipe(Schema.decodeTo(Schema.String, {
|
|
142
|
+
decode: SchemaGetter.transform(renderMarkdown),
|
|
143
|
+
encode: SchemaGetter.forbidden(() => "BiomeCheckAsMarkdown is one-way: markdown cannot be parsed back.")
|
|
144
|
+
}));
|
|
136
145
|
/**
|
|
137
146
|
* Run Biome and return structured diagnostics. When `write`/`unsafe` is set,
|
|
138
147
|
* runs a fix pass first, then a read-only gitlab pass to report what remains.
|
|
@@ -1,29 +1,19 @@
|
|
|
1
|
+
import { WorkspaceRoot } from "@effected/workspaces";
|
|
1
2
|
import { Changesets } from "@savvy-web/silk-effects";
|
|
2
|
-
import { Effect,
|
|
3
|
-
import { WorkspaceRoot } from "workspaces-effect";
|
|
3
|
+
import { Effect, Schema, SchemaGetter } from "effect";
|
|
4
4
|
|
|
5
5
|
//#region src/tools/changeset-deps-detect.ts
|
|
6
|
-
/**
|
|
7
|
-
* The `changeset_deps_detect` MCP tool: a read-only preview of the cumulative
|
|
8
|
-
* dependency diff (merge-base → working tree) over silk-effects'
|
|
9
|
-
* `Changesets.DepsRegen.plan`. Returns one entry per affected workspace package
|
|
10
|
-
* — its resolved dependency-table rows (devDependencies retained) — plus a
|
|
11
|
-
* one-way markdown transform. Read-only: no changeset file is written or
|
|
12
|
-
* deleted.
|
|
13
|
-
*
|
|
14
|
-
* @packageDocumentation
|
|
15
|
-
*/
|
|
16
6
|
/** One affected workspace package's resolved dependency diff. */
|
|
17
7
|
const ChangesetDepsDetectPackage = Schema.Struct({
|
|
18
8
|
package: Schema.String,
|
|
19
9
|
relativePath: Schema.String,
|
|
20
10
|
rows: Schema.Array(Changesets.DependencyTableRowSchema)
|
|
21
|
-
}).
|
|
11
|
+
}).annotate({ identifier: "ChangesetDepsDetectPackage" });
|
|
22
12
|
/** The `changeset_deps_detect` tool result. */
|
|
23
13
|
const ChangesetDepsDetectResult = Schema.Struct({
|
|
24
14
|
root: Schema.String,
|
|
25
15
|
packages: Schema.Array(ChangesetDepsDetectPackage)
|
|
26
|
-
}).
|
|
16
|
+
}).annotate({
|
|
27
17
|
identifier: "ChangesetDepsDetectResult",
|
|
28
18
|
title: "changeset_deps_detect result",
|
|
29
19
|
description: "Read-only per-package dependency diff (devDependencies retained). No files are written."
|
|
@@ -56,11 +46,10 @@ const renderMarkdown = (data) => {
|
|
|
56
46
|
return lines.join("\n").trimEnd();
|
|
57
47
|
};
|
|
58
48
|
/** One-way transform: result to markdown. Encoding back is forbidden. */
|
|
59
|
-
const ChangesetDepsDetectAsMarkdown = Schema.
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
});
|
|
49
|
+
const ChangesetDepsDetectAsMarkdown = ChangesetDepsDetectResult.pipe(Schema.decodeTo(Schema.String, {
|
|
50
|
+
decode: SchemaGetter.transform(renderMarkdown),
|
|
51
|
+
encode: SchemaGetter.forbidden(() => "ChangesetDepsDetectAsMarkdown is one-way: markdown cannot be parsed back.")
|
|
52
|
+
}));
|
|
64
53
|
/**
|
|
65
54
|
* Effect handler: resolve the workspace root, then compute the cumulative
|
|
66
55
|
* dependency diff via {@link Changesets.DepsRegen.plan} with `includeDevDeps`
|
|
@@ -1,17 +1,8 @@
|
|
|
1
|
+
import { WorkspaceRoot } from "@effected/workspaces";
|
|
1
2
|
import { Changesets } from "@savvy-web/silk-effects";
|
|
2
|
-
import { Effect,
|
|
3
|
-
import { WorkspaceRoot } from "workspaces-effect";
|
|
3
|
+
import { Effect, Schema, SchemaGetter } from "effect";
|
|
4
4
|
|
|
5
5
|
//#region src/tools/changeset-deps-regen.ts
|
|
6
|
-
/**
|
|
7
|
-
* The `changeset_deps_regen` MCP tool: delete stale pure-dependency changesets
|
|
8
|
-
* and write fresh single-package, patch-bump changesets from the cumulative
|
|
9
|
-
* dependency diff, over silk-effects' `Changesets.DepsRegen`. Mutating (writes
|
|
10
|
-
* and deletes `.changeset/*.md`) unless `dryRun` is set. The second mutating
|
|
11
|
-
* tool after `biome_check`; no `readOnlyHint`.
|
|
12
|
-
*
|
|
13
|
-
* @packageDocumentation
|
|
14
|
-
*/
|
|
15
6
|
/** The `changeset_deps_regen` tool result. */
|
|
16
7
|
const ChangesetDepsRegenResult = Schema.Struct({
|
|
17
8
|
root: Schema.String,
|
|
@@ -19,7 +10,7 @@ const ChangesetDepsRegenResult = Schema.Struct({
|
|
|
19
10
|
written: Schema.Array(Schema.String),
|
|
20
11
|
skippedMixed: Schema.Array(Schema.String),
|
|
21
12
|
dryRun: Schema.Boolean
|
|
22
|
-
}).
|
|
13
|
+
}).annotate({
|
|
23
14
|
identifier: "ChangesetDepsRegenResult",
|
|
24
15
|
title: "changeset_deps_regen result",
|
|
25
16
|
description: "Regenerated pure-dependency changesets. Mutates .changeset/*.md unless dryRun is set."
|
|
@@ -60,11 +51,10 @@ const renderMarkdown = (data) => {
|
|
|
60
51
|
return lines.join("\n").trimEnd();
|
|
61
52
|
};
|
|
62
53
|
/** One-way transform: result to markdown. Encoding back is forbidden. */
|
|
63
|
-
const ChangesetDepsRegenAsMarkdown = Schema.
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
});
|
|
54
|
+
const ChangesetDepsRegenAsMarkdown = ChangesetDepsRegenResult.pipe(Schema.decodeTo(Schema.String, {
|
|
55
|
+
decode: SchemaGetter.transform(renderMarkdown),
|
|
56
|
+
encode: SchemaGetter.forbidden(() => "ChangesetDepsRegenAsMarkdown is one-way: markdown cannot be parsed back.")
|
|
57
|
+
}));
|
|
68
58
|
/**
|
|
69
59
|
* Effect handler: resolve the workspace root, compute a {@link Changesets.RegenPlan}
|
|
70
60
|
* via {@link Changesets.DepsRegen.plan}, then — unless `dryRun` — apply it via
|
|
@@ -1,33 +1,29 @@
|
|
|
1
|
+
import { WorkspaceRoot } from "@effected/workspaces";
|
|
1
2
|
import { Changesets } from "@savvy-web/silk-effects";
|
|
2
|
-
import { Effect,
|
|
3
|
-
import { WorkspaceRoot } from "workspaces-effect";
|
|
3
|
+
import { Effect, Schema, SchemaGetter } from "effect";
|
|
4
4
|
|
|
5
5
|
//#region src/tools/changeset-inspect.ts
|
|
6
|
-
/**
|
|
7
|
-
* The `changeset_inspect` MCP tool: a discriminated-union result keyed by `mode`
|
|
8
|
-
* (branch | config), each variant embedding the corresponding resolved-output
|
|
9
|
-
* schema from silk-effects' Changesets namespace, plus a one-way markdown
|
|
10
|
-
* transform. Read-only.
|
|
11
|
-
*
|
|
12
|
-
* @packageDocumentation
|
|
13
|
-
*/
|
|
14
6
|
/** Branch-analysis variant. */
|
|
15
7
|
const ChangesetBranchResult = Schema.Struct({
|
|
16
8
|
mode: Schema.Literal("branch"),
|
|
17
9
|
result: Changesets.BranchAnalysisSchema
|
|
18
|
-
}).
|
|
10
|
+
}).annotate({ identifier: "ChangesetBranchResult" });
|
|
19
11
|
/** Config-inspection variant. */
|
|
20
12
|
const ChangesetConfigResult = Schema.Struct({
|
|
21
13
|
mode: Schema.Literal("config"),
|
|
22
14
|
result: Changesets.InspectedConfigSchema
|
|
23
|
-
}).
|
|
15
|
+
}).annotate({ identifier: "ChangesetConfigResult" });
|
|
24
16
|
/** Classify variant — arbitrary paths to owning package. */
|
|
25
17
|
const ChangesetClassifyResult = Schema.Struct({
|
|
26
18
|
mode: Schema.Literal("classify"),
|
|
27
19
|
result: Schema.Array(Changesets.ClassificationSchema)
|
|
28
|
-
}).
|
|
20
|
+
}).annotate({ identifier: "ChangesetClassifyResult" });
|
|
29
21
|
/** The `changeset_inspect` tool result — a discriminated union keyed by `mode`. */
|
|
30
|
-
const ChangesetInspectResult = Schema.Union(
|
|
22
|
+
const ChangesetInspectResult = Schema.Union([
|
|
23
|
+
ChangesetBranchResult,
|
|
24
|
+
ChangesetConfigResult,
|
|
25
|
+
ChangesetClassifyResult
|
|
26
|
+
]).annotate({
|
|
31
27
|
identifier: "ChangesetInspectResult",
|
|
32
28
|
title: "changeset_inspect result",
|
|
33
29
|
description: "Read-only changeset analysis grouped by mode (branch | config | classify)."
|
|
@@ -95,11 +91,10 @@ const renderMarkdown = (data) => {
|
|
|
95
91
|
}
|
|
96
92
|
};
|
|
97
93
|
/** One-way transform: result to markdown. Encoding back is forbidden. */
|
|
98
|
-
const ChangesetInspectAsMarkdown = Schema.
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
});
|
|
94
|
+
const ChangesetInspectAsMarkdown = ChangesetInspectResult.pipe(Schema.decodeTo(Schema.String, {
|
|
95
|
+
decode: SchemaGetter.transform(renderMarkdown),
|
|
96
|
+
encode: SchemaGetter.forbidden(() => "ChangesetInspectAsMarkdown is one-way: markdown cannot be parsed back.")
|
|
97
|
+
}));
|
|
103
98
|
/**
|
|
104
99
|
* Effect handler: resolve the workspace root, then dispatch to the matching
|
|
105
100
|
* Changesets service keyed by `mode`. Mirrors `turboInspect`.
|
|
@@ -1,17 +1,10 @@
|
|
|
1
|
+
import { WorkspaceRoot } from "@effected/workspaces";
|
|
1
2
|
import { Changesets } from "@savvy-web/silk-effects";
|
|
2
|
-
import { Effect,
|
|
3
|
-
import { WorkspaceRoot } from "workspaces-effect";
|
|
3
|
+
import { Effect, Schema, SchemaGetter } from "effect";
|
|
4
4
|
|
|
5
5
|
//#region src/tools/changeset-preview.ts
|
|
6
|
-
/**
|
|
7
|
-
* The `changeset_preview` MCP tool: a read-only preview of the next release's
|
|
8
|
-
* CHANGELOG, produced by the genuine changesets engine via silk-effects'
|
|
9
|
-
* ReleasePlanner. Structured result + one-way markdown transform. Read-only.
|
|
10
|
-
*
|
|
11
|
-
* @packageDocumentation
|
|
12
|
-
*/
|
|
13
6
|
/** The `changeset_preview` result — the silk-effects preview shape. */
|
|
14
|
-
const ChangesetPreviewResult = Changesets.ChangesetPreviewSchema.
|
|
7
|
+
const ChangesetPreviewResult = Changesets.ChangesetPreviewSchema.annotate({
|
|
15
8
|
identifier: "ChangesetPreviewResult",
|
|
16
9
|
title: "changeset_preview result",
|
|
17
10
|
description: "Read-only preview of the next release: version bumps + rendered CHANGELOG blocks."
|
|
@@ -34,11 +27,10 @@ const renderMarkdown = (data) => {
|
|
|
34
27
|
return lines.join("\n").trimEnd();
|
|
35
28
|
};
|
|
36
29
|
/** One-way transform: result to markdown. Encoding back is forbidden. */
|
|
37
|
-
const ChangesetPreviewAsMarkdown = Schema.
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
});
|
|
30
|
+
const ChangesetPreviewAsMarkdown = ChangesetPreviewResult.pipe(Schema.decodeTo(Schema.String, {
|
|
31
|
+
decode: SchemaGetter.transform(renderMarkdown),
|
|
32
|
+
encode: SchemaGetter.forbidden(() => "ChangesetPreviewAsMarkdown is one-way: markdown cannot be parsed back.")
|
|
33
|
+
}));
|
|
42
34
|
/**
|
|
43
35
|
* Effect handler: resolve the workspace root, then render the preview via
|
|
44
36
|
* ReleasePlanner. Mirrors `changesetInspect`.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { WorkspaceRoot } from "@effected/workspaces";
|
|
1
2
|
import { Changesets } from "@savvy-web/silk-effects";
|
|
2
|
-
import { Data, Effect,
|
|
3
|
-
import { WorkspaceRoot } from "workspaces-effect";
|
|
3
|
+
import { Data, Effect, Schema, SchemaGetter } from "effect";
|
|
4
4
|
import { resolve } from "node:path";
|
|
5
5
|
|
|
6
6
|
//#region src/tools/changeset-validate.ts
|
|
@@ -20,14 +20,14 @@ const ChangesetLintMessage = Schema.Struct({
|
|
|
20
20
|
line: Schema.Number,
|
|
21
21
|
column: Schema.Number,
|
|
22
22
|
message: Schema.String
|
|
23
|
-
}).
|
|
23
|
+
}).annotate({ identifier: "ChangesetLintMessage" });
|
|
24
24
|
/** The `changeset_validate` tool result. */
|
|
25
25
|
const ChangesetValidateResult = Schema.Struct({
|
|
26
26
|
dir: Schema.String,
|
|
27
27
|
ok: Schema.Boolean,
|
|
28
28
|
errorCount: Schema.Number,
|
|
29
29
|
messages: Schema.Array(ChangesetLintMessage)
|
|
30
|
-
}).
|
|
30
|
+
}).annotate({
|
|
31
31
|
identifier: "ChangesetValidateResult",
|
|
32
32
|
title: "changeset_validate result",
|
|
33
33
|
description: "Read-only validation of changeset files against the section-aware rules."
|
|
@@ -51,11 +51,10 @@ const renderMarkdown = (data) => {
|
|
|
51
51
|
return lines.join("\n");
|
|
52
52
|
};
|
|
53
53
|
/** One-way transform: result to markdown. Encoding back is forbidden. */
|
|
54
|
-
const ChangesetValidateAsMarkdown = Schema.
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
});
|
|
54
|
+
const ChangesetValidateAsMarkdown = ChangesetValidateResult.pipe(Schema.decodeTo(Schema.String, {
|
|
55
|
+
decode: SchemaGetter.transform(renderMarkdown),
|
|
56
|
+
encode: SchemaGetter.forbidden(() => "ChangesetValidateAsMarkdown is one-way: markdown cannot be parsed back.")
|
|
57
|
+
}));
|
|
59
58
|
/**
|
|
60
59
|
* Effect handler: resolve the workspace root, then validate the changeset
|
|
61
60
|
* directory via the pure {@link Changesets.ChangesetLinter.validate}. The
|
package/tools/repos-inspect.js
CHANGED
|
@@ -1,29 +1,21 @@
|
|
|
1
1
|
import { mdInline } from "./md-inline.js";
|
|
2
|
+
import { WorkspaceRoot } from "@effected/workspaces";
|
|
2
3
|
import { Repos } from "@savvy-web/silk-effects";
|
|
3
|
-
import { Effect,
|
|
4
|
-
import { WorkspaceRoot } from "workspaces-effect";
|
|
4
|
+
import { Effect, Schema, SchemaGetter } from "effect";
|
|
5
5
|
|
|
6
6
|
//#region src/tools/repos-inspect.ts
|
|
7
|
-
/**
|
|
8
|
-
* The `repos_inspect` MCP tool: a discriminated-union result keyed by `mode`
|
|
9
|
-
* (status | config), each variant embedding the corresponding resolved-output
|
|
10
|
-
* schema from silk-effects' Repos namespace, plus a one-way markdown
|
|
11
|
-
* transform. Read-only.
|
|
12
|
-
*
|
|
13
|
-
* @packageDocumentation
|
|
14
|
-
*/
|
|
15
7
|
/** Status-report variant. */
|
|
16
8
|
const ReposStatusResult = Schema.Struct({
|
|
17
9
|
mode: Schema.Literal("status"),
|
|
18
10
|
result: Repos.ReposStatusReport
|
|
19
|
-
}).
|
|
11
|
+
}).annotate({ identifier: "ReposStatusResult" });
|
|
20
12
|
/** Manifest-config variant. */
|
|
21
13
|
const ReposConfigResult = Schema.Struct({
|
|
22
14
|
mode: Schema.Literal("config"),
|
|
23
15
|
result: Repos.ReposManifestFile
|
|
24
|
-
}).
|
|
16
|
+
}).annotate({ identifier: "ReposConfigResult" });
|
|
25
17
|
/** The `repos_inspect` tool result — a discriminated union keyed by `mode`. */
|
|
26
|
-
const ReposInspectResult = Schema.Union(ReposStatusResult, ReposConfigResult).
|
|
18
|
+
const ReposInspectResult = Schema.Union([ReposStatusResult, ReposConfigResult]).annotate({
|
|
27
19
|
identifier: "ReposInspectResult",
|
|
28
20
|
title: "repos_inspect result",
|
|
29
21
|
description: "Drift report (status) or the parsed manifest with purposes, orientation, and notes (config)."
|
|
@@ -77,11 +69,10 @@ const renderMarkdown = (data) => {
|
|
|
77
69
|
}
|
|
78
70
|
};
|
|
79
71
|
/** One-way transform: result to markdown. Encoding back is forbidden. */
|
|
80
|
-
const ReposInspectAsMarkdown = Schema.
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
});
|
|
72
|
+
const ReposInspectAsMarkdown = ReposInspectResult.pipe(Schema.decodeTo(Schema.String, {
|
|
73
|
+
decode: SchemaGetter.transform(renderMarkdown),
|
|
74
|
+
encode: SchemaGetter.forbidden(() => "ReposInspectAsMarkdown is one-way: markdown cannot be parsed back.")
|
|
75
|
+
}));
|
|
85
76
|
/**
|
|
86
77
|
* Effect handler: resolve the workspace root, then dispatch to the matching
|
|
87
78
|
* Repos service keyed by `mode`. Mirrors `changesetInspect`.
|
package/tools/repos-manage.js
CHANGED
|
@@ -1,18 +1,9 @@
|
|
|
1
1
|
import { mdInline } from "./md-inline.js";
|
|
2
|
+
import { WorkspaceRoot } from "@effected/workspaces";
|
|
2
3
|
import { Repos } from "@savvy-web/silk-effects";
|
|
3
|
-
import { Effect,
|
|
4
|
-
import { WorkspaceRoot } from "workspaces-effect";
|
|
4
|
+
import { Effect, Schema, SchemaGetter } from "effect";
|
|
5
5
|
|
|
6
6
|
//#region src/tools/repos-manage.ts
|
|
7
|
-
/**
|
|
8
|
-
* The `repos_manage` MCP tool: one action-discriminated mutating tool
|
|
9
|
-
* covering `sync`, `pin`, `add`, and `note` against the vendored `.repos/`
|
|
10
|
-
* submodules. The wire schema is flat (no `oneOf`); the handler maps it into
|
|
11
|
-
* an internal `Schema.TaggedStruct` request union that names the missing
|
|
12
|
-
* field per action on decode failure. Mutating — no `readOnlyHint`.
|
|
13
|
-
*
|
|
14
|
-
* @packageDocumentation
|
|
15
|
-
*/
|
|
16
7
|
/** `sync` has no extra fields. */
|
|
17
8
|
const SyncRequest = Schema.TaggedStruct("sync", {});
|
|
18
9
|
/** `pin` requires both `name` and `ref`. */
|
|
@@ -35,40 +26,54 @@ const AddRequest = Schema.TaggedStruct("add", {
|
|
|
35
26
|
*/
|
|
36
27
|
const NoteRequest = Schema.TaggedStruct("note", {
|
|
37
28
|
name: Schema.String,
|
|
38
|
-
op: Schema.
|
|
29
|
+
op: Schema.Literals([
|
|
30
|
+
"add",
|
|
31
|
+
"remove",
|
|
32
|
+
"promote"
|
|
33
|
+
]),
|
|
39
34
|
note: Schema.optional(Schema.String),
|
|
40
35
|
id: Schema.optional(Schema.String),
|
|
41
|
-
into: Schema.optional(Schema.
|
|
42
|
-
}).
|
|
36
|
+
into: Schema.optional(Schema.Literals(["layout", "startHere"]))
|
|
37
|
+
}).check(Schema.makeFilter((request) => {
|
|
43
38
|
if (request.op === "add" && request.note === void 0) return "note op \"add\" requires `note`";
|
|
44
39
|
if (request.op === "remove" && request.id === void 0) return "note op \"remove\" requires `id`";
|
|
45
40
|
if (request.op === "promote" && (request.id === void 0 || request.into === void 0)) return "note op \"promote\" requires both `id` and `into`";
|
|
46
41
|
return true;
|
|
47
42
|
}));
|
|
48
43
|
/** Internal tagged-union request the flat wire args decode into. */
|
|
49
|
-
const ReposManageRequest = Schema.Union(
|
|
44
|
+
const ReposManageRequest = Schema.Union([
|
|
45
|
+
SyncRequest,
|
|
46
|
+
PinRequest,
|
|
47
|
+
AddRequest,
|
|
48
|
+
NoteRequest
|
|
49
|
+
]);
|
|
50
50
|
/** `sync` result variant. */
|
|
51
51
|
const ReposManageSyncResult = Schema.Struct({
|
|
52
52
|
action: Schema.Literal("sync"),
|
|
53
53
|
result: Repos.ReposSyncReport
|
|
54
|
-
}).
|
|
54
|
+
}).annotate({ identifier: "ReposManageSyncResult" });
|
|
55
55
|
/** `pin` result variant. */
|
|
56
56
|
const ReposManagePinResult = Schema.Struct({
|
|
57
57
|
action: Schema.Literal("pin"),
|
|
58
58
|
result: Repos.ReposPinResult
|
|
59
|
-
}).
|
|
59
|
+
}).annotate({ identifier: "ReposManagePinResult" });
|
|
60
60
|
/** `add` result variant. */
|
|
61
61
|
const ReposManageAddResult = Schema.Struct({
|
|
62
62
|
action: Schema.Literal("add"),
|
|
63
63
|
result: Repos.ReposAddResult
|
|
64
|
-
}).
|
|
64
|
+
}).annotate({ identifier: "ReposManageAddResult" });
|
|
65
65
|
/** `note` result variant. */
|
|
66
66
|
const ReposManageNoteResult = Schema.Struct({
|
|
67
67
|
action: Schema.Literal("note"),
|
|
68
68
|
result: Repos.ReposNoteResult
|
|
69
|
-
}).
|
|
69
|
+
}).annotate({ identifier: "ReposManageNoteResult" });
|
|
70
70
|
/** The `repos_manage` tool result — a discriminated union keyed by `action`. */
|
|
71
|
-
const ReposManageResult = Schema.Union(
|
|
71
|
+
const ReposManageResult = Schema.Union([
|
|
72
|
+
ReposManageSyncResult,
|
|
73
|
+
ReposManagePinResult,
|
|
74
|
+
ReposManageAddResult,
|
|
75
|
+
ReposManageNoteResult
|
|
76
|
+
]).annotate({
|
|
72
77
|
identifier: "ReposManageResult",
|
|
73
78
|
title: "repos_manage result",
|
|
74
79
|
description: "Result of a mutating repos action: sync, pin, add, or note."
|
|
@@ -134,11 +139,10 @@ const renderMarkdown = (data) => {
|
|
|
134
139
|
}
|
|
135
140
|
};
|
|
136
141
|
/** One-way transform: result to markdown. Encoding back is forbidden. */
|
|
137
|
-
const ReposManageAsMarkdown = Schema.
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
});
|
|
142
|
+
const ReposManageAsMarkdown = ReposManageResult.pipe(Schema.decodeTo(Schema.String, {
|
|
143
|
+
decode: SchemaGetter.transform(renderMarkdown),
|
|
144
|
+
encode: SchemaGetter.forbidden(() => "ReposManageAsMarkdown is one-way: markdown cannot be parsed back.")
|
|
145
|
+
}));
|
|
142
146
|
/**
|
|
143
147
|
* Effect handler: resolve the workspace root, decode the flat wire args into
|
|
144
148
|
* the internal per-action request (naming the missing field on failure), then
|
|
@@ -148,7 +152,7 @@ const reposManage = (args, fallbackCwd) => Effect.gen(function* () {
|
|
|
148
152
|
const root = yield* (yield* WorkspaceRoot).find(args.cwd ?? fallbackCwd);
|
|
149
153
|
const manager = yield* Repos.ReposManager;
|
|
150
154
|
const { action, cwd: _cwd, ...rest } = args;
|
|
151
|
-
const request = yield* Schema.
|
|
155
|
+
const request = yield* Schema.decodeUnknownEffect(ReposManageRequest)({
|
|
152
156
|
_tag: action,
|
|
153
157
|
...rest
|
|
154
158
|
});
|
package/tools/turbo-inspect.js
CHANGED
|
@@ -1,32 +1,29 @@
|
|
|
1
|
+
import { WorkspaceRoot } from "@effected/workspaces";
|
|
1
2
|
import { Turbo } from "@savvy-web/silk-effects";
|
|
2
|
-
import { Effect,
|
|
3
|
-
import { WorkspaceRoot } from "workspaces-effect";
|
|
3
|
+
import { Effect, Schema, SchemaGetter } from "effect";
|
|
4
4
|
|
|
5
5
|
//#region src/tools/turbo-inspect.ts
|
|
6
|
-
/**
|
|
7
|
-
* The `turbo_inspect` MCP tool: a discriminated-union result schema keyed by
|
|
8
|
-
* `mode` (cache | graph | affected), each variant embedding the corresponding
|
|
9
|
-
* `Turbo` result schema from silk-effects, plus a one-way markdown transform.
|
|
10
|
-
*
|
|
11
|
-
* @packageDocumentation
|
|
12
|
-
*/
|
|
13
6
|
/** Cache-diagnosis variant of the `turbo_inspect` result. */
|
|
14
7
|
const TurboCacheResult = Schema.Struct({
|
|
15
8
|
mode: Schema.Literal("cache"),
|
|
16
9
|
result: Turbo.CacheDiagnosis
|
|
17
|
-
}).
|
|
10
|
+
}).annotate({ identifier: "TurboCacheResult" });
|
|
18
11
|
/** Task-graph variant of the `turbo_inspect` result. */
|
|
19
12
|
const TurboGraphResult = Schema.Struct({
|
|
20
13
|
mode: Schema.Literal("graph"),
|
|
21
14
|
result: Turbo.TaskGraphResult
|
|
22
|
-
}).
|
|
15
|
+
}).annotate({ identifier: "TurboGraphResult" });
|
|
23
16
|
/** Affected-packages variant of the `turbo_inspect` result. */
|
|
24
17
|
const TurboAffectedResult = Schema.Struct({
|
|
25
18
|
mode: Schema.Literal("affected"),
|
|
26
19
|
result: Turbo.AffectedResult
|
|
27
|
-
}).
|
|
20
|
+
}).annotate({ identifier: "TurboAffectedResult" });
|
|
28
21
|
/** The `turbo_inspect` tool result — a discriminated union keyed by `mode`. */
|
|
29
|
-
const TurboInspectResult = Schema.Union(
|
|
22
|
+
const TurboInspectResult = Schema.Union([
|
|
23
|
+
TurboCacheResult,
|
|
24
|
+
TurboGraphResult,
|
|
25
|
+
TurboAffectedResult
|
|
26
|
+
]).annotate({
|
|
30
27
|
identifier: "TurboInspectResult",
|
|
31
28
|
title: "turbo_inspect result",
|
|
32
29
|
description: "Read-only Turborepo inspection grouped by mode (cache | graph | affected)."
|
|
@@ -80,11 +77,10 @@ const renderMarkdown = (data) => {
|
|
|
80
77
|
}
|
|
81
78
|
};
|
|
82
79
|
/** One-way transform: result to markdown. Encoding back is forbidden. */
|
|
83
|
-
const TurboInspectAsMarkdown = Schema.
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
});
|
|
80
|
+
const TurboInspectAsMarkdown = TurboInspectResult.pipe(Schema.decodeTo(Schema.String, {
|
|
81
|
+
decode: SchemaGetter.transform(renderMarkdown),
|
|
82
|
+
encode: SchemaGetter.forbidden(() => "TurboInspectAsMarkdown is one-way: markdown cannot be parsed back.")
|
|
83
|
+
}));
|
|
88
84
|
/**
|
|
89
85
|
* Effect handler: resolve the workspace root by walking up from the requested
|
|
90
86
|
* directory, then dispatch to the matching {@link Turbo.TurboInspector} method
|
package/tools/workspace-info.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { WorkspaceRoot } from "@effected/workspaces";
|
|
1
2
|
import { SilkWorkspaceAnalyzer } from "@savvy-web/silk-effects";
|
|
2
|
-
import { Effect,
|
|
3
|
-
import { WorkspaceRoot } from "workspaces-effect";
|
|
3
|
+
import { Effect, Schema, SchemaGetter } from "effect";
|
|
4
4
|
|
|
5
5
|
//#region src/tools/workspace-info.ts
|
|
6
6
|
/** A flattened, non-recursive summary of one analyzed workspace. */
|
|
@@ -10,24 +10,29 @@ const WorkspaceSummary = Schema.Struct({
|
|
|
10
10
|
path: Schema.String,
|
|
11
11
|
root: Schema.Boolean,
|
|
12
12
|
publishable: Schema.Boolean,
|
|
13
|
-
targets: Schema.Array(Schema.String).
|
|
13
|
+
targets: Schema.Array(Schema.String).annotate({ description: "Publish registry URLs." }),
|
|
14
14
|
versioned: Schema.Boolean,
|
|
15
15
|
tagged: Schema.Boolean,
|
|
16
16
|
released: Schema.Boolean,
|
|
17
|
-
linked: Schema.Array(Schema.String).
|
|
18
|
-
fixed: Schema.Array(Schema.String).
|
|
19
|
-
}).
|
|
17
|
+
linked: Schema.Array(Schema.String).annotate({ description: "Names of linked workspaces." }),
|
|
18
|
+
fixed: Schema.Array(Schema.String).annotate({ description: "Names of fixed-group siblings." })
|
|
19
|
+
}).annotate({ identifier: "WorkspaceSummary" });
|
|
20
20
|
/** The `workspace_info` tool result — a projection of `WorkspaceAnalysis`. */
|
|
21
21
|
const WorkspaceInfoResult = Schema.Struct({
|
|
22
22
|
root: Schema.String,
|
|
23
|
-
runtime: Schema.
|
|
23
|
+
runtime: Schema.Literals(["node", "bun"]),
|
|
24
24
|
packageManager: Schema.Struct({
|
|
25
|
-
type: Schema.
|
|
25
|
+
type: Schema.Literals([
|
|
26
|
+
"npm",
|
|
27
|
+
"pnpm",
|
|
28
|
+
"yarn",
|
|
29
|
+
"bun"
|
|
30
|
+
]),
|
|
26
31
|
version: Schema.optional(Schema.String)
|
|
27
32
|
}),
|
|
28
33
|
workspaceCount: Schema.Number,
|
|
29
34
|
workspaces: Schema.Array(WorkspaceSummary)
|
|
30
|
-
}).
|
|
35
|
+
}).annotate({
|
|
31
36
|
identifier: "WorkspaceInfoResult",
|
|
32
37
|
title: "workspace_info result",
|
|
33
38
|
description: "Structured snapshot of the Silk workspace: runtime, package manager, and a per-workspace summary (publishability, versioning, tag/release state, linked/fixed relations)."
|
|
@@ -73,11 +78,10 @@ const formatWorkspaceInfoMarkdown = (data) => {
|
|
|
73
78
|
return lines.join("\n");
|
|
74
79
|
};
|
|
75
80
|
/** One-way transform: result to markdown. Encoding back is forbidden. */
|
|
76
|
-
const WorkspaceInfoAsMarkdown = Schema.
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
});
|
|
81
|
+
const WorkspaceInfoAsMarkdown = WorkspaceInfoResult.pipe(Schema.decodeTo(Schema.String, {
|
|
82
|
+
decode: SchemaGetter.transform(formatWorkspaceInfoMarkdown),
|
|
83
|
+
encode: SchemaGetter.forbidden(() => "WorkspaceInfoAsMarkdown is one-way: markdown cannot be parsed back.")
|
|
84
|
+
}));
|
|
81
85
|
/**
|
|
82
86
|
* Effect handler: resolve the workspace root by walking up from `base`, analyze
|
|
83
87
|
* that root, and project to the tool result. Fails with `WorkspaceRootNotFoundError`
|