@savvy-web/mcp 2.2.1 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -49,8 +49,8 @@ npx @modelcontextprotocol/inspector savvy-mcp .
49
49
  - `changeset_deps_detect` — read-only preview of the cumulative dependency diff (merge-base to working tree): one entry per affected workspace package with its resolved dependency-table rows, `catalog:`/`workspace:` specifiers resolved to concrete versions. It never writes a changeset. Backed by `silk-effects`' `Changesets.DepsRegen.plan`.
50
50
  - `changeset_deps_regen` — regenerates pure-dependency changesets: deletes stale ones and writes fresh single-package, patch-bump changesets from the cumulative dependency diff. Mutating unless `dryRun` is set, in which case it reports what it would delete and write without touching the filesystem. Backed by `silk-effects`' `Changesets.DepsRegen`.
51
51
  - `biome_check` — run Biome over a path and get structured diagnostics back: `mode=check` (lint, format and organize-imports) or `mode=lint`. Unlike most of the other tools it can mutate — pass `write` for safe fixes or `unsafe` for unsafe ones (both git-reversible) — so it returns the same diagnostics the Biome LSP surfaces for files you have edited.
52
- - `repos_inspect` — read-only inspection of vendored repositories: `mode=status` reports per-repo presence, the gitlink commit, working-tree dirtiness, and stale note ids; `mode=config` surfaces the validated `.repos/config.json` manifest and its entries; `mode=drift` reconciles the manifest, `.gitmodules`, the worktree, and `git submodule status`, reporting any mismatch as a typed drift kind; `mode=gitmodules` lists the parsed `.gitmodules` entries. Returns markdown-escaped output since vendored-repo content is untrusted input. Backed by the same `silk-effects` `Repos` services the `savvy` CLI uses.
53
- - `repos_manage` — manages vendored repositories (mutating counterpart to repos_inspect): `action=sync` initializes any missing submodules (`git submodule update --init --depth 1`), applies sparse-checkout from the manifest, and clears stale git locks; `action=pin` fetches and checks out the new ref, staging the updated gitlink and manifest; `action=add` adds a new repo entry; `action=note` appends a short note to a repo; `action=remove` unvendors an entry; `action=rename` renames one in place; `action=restore` hard-resets one or more dirty checkouts back to their pinned commit. Pin, add, remove and rename stage git changes for the caller to commit; restore only resets the submodule's own working tree to its already-staged or already-committed gitlink commit, so there is nothing new to stage. `sync`, `pin`, `add`, `remove`, `rename` and `restore` unlock the vendored tree for the duration of their git work and leave it read-only again afterwards (files `0444`, directories `0555` — an executable file locks at `0555` instead, unlocking back to `0755` rather than losing its executable bit), so an agent that hits `EACCES` writing into `.repos/` should route the change through this tool rather than `chmod`. Backed by the same `silk-effects` `Repos` services the `savvy` CLI uses.
52
+ - `repos_inspect` — read-only inspection of vendored repositories: `mode=status` reports per-repo presence, the gitlink as a staged/committed/checked-out triple, working-tree dirtiness, and stale note ids; `mode=config` surfaces the validated `.repos/config.json` manifest and its entries; `mode=drift` reconciles the manifest, `.gitmodules`, the worktree, `git submodule status` and the repo's local git config, reporting any mismatch as a typed drift kind; `mode=gitmodules` lists the parsed `.gitmodules` entries. Returns markdown-escaped output since vendored-repo content is untrusted input. Backed by the same `silk-effects` `Repos` services the `savvy` CLI uses.
53
+ - `repos_manage` — manages vendored repositories (mutating counterpart to repos_inspect): `action=sync` initializes any missing submodules (`git submodule update --init --depth 1`), applies sparse-checkout from the manifest, and clears stale git locks; `action=pin` fetches and checks out the new ref, staging the updated gitlink and manifest; `action=add` adds a new repo entry, optionally with the `orientation` block a preceding `remove` reported, so a re-vendor keeps it; `action=note` appends a short note to a repo; `action=remove` unvendors an entry and echoes back the entry it removed; `action=rename` renames one in place; `action=restore` hard-resets one or more dirty checkouts back to their pinned commit, and names any that stayed dirty afterwards instead of reporting the reset as a success. Pin, add, remove and rename stage git changes for the caller to commit; restore only resets the submodule's own working tree to its already-staged or already-committed gitlink commit, so there is nothing new to stage. `sync`, `pin`, `add`, `remove`, `rename` and `restore` unlock the vendored worktree for the duration of their git work and leave it read-only again afterwards (files `0444`, directories `0555` — an executable file locks at `0555` instead, unlocking back to `0755` rather than losing its executable bit), so an agent that hits `EACCES` writing into `.repos/` should route the change through this tool rather than `chmod`. The submodule gitdir stays writable and `sync`/`add` mark the boundary in local git config, so ordinary git commands skip these trees rather than failing against them. Backed by the same `silk-effects` `Repos` services the `savvy` CLI uses.
54
54
 
55
55
  ## License
56
56
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@savvy-web/mcp",
3
- "version": "2.2.1",
3
+ "version": "2.3.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",
@@ -34,9 +34,9 @@
34
34
  "@effect/platform-node": "4.0.0-beta.107",
35
35
  "@effected/commands": "^0.4.0",
36
36
  "@effected/git": "^0.7.0",
37
- "@effected/workspaces": "^0.11.0",
37
+ "@effected/workspaces": "^0.11.1",
38
38
  "@modelcontextprotocol/sdk": "^1.29.0",
39
- "@savvy-web/silk-effects": "5.5.1",
39
+ "@savvy-web/silk-effects": "5.6.0",
40
40
  "effect": "4.0.0-beta.107",
41
41
  "zod": "^4.4.3"
42
42
  }
package/server.js CHANGED
@@ -170,7 +170,7 @@ function buildServer(ctx) {
170
170
  });
171
171
  server.registerTool("repos_manage", {
172
172
  title: "Manage vendored repos",
173
- 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), or 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). Pass action plus the fields that action needs: pin needs name+ref; add needs url+ref+purpose (name/sparse optional); 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. A decode failure names the missing field. The pin result's markdown surfaces commitMessage and staleNoteIds — review and commit after pinning. The remove result's markdown surfaces commitMessage and removedNotes — promote any durable notes elsewhere, then review and commit. The rename result's markdown surfaces commitMessage — review and commit after renaming. The restore result's markdown names exactly what was discarded.",
173
+ 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), or 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). 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. A decode failure names the missing field. The pin result's markdown surfaces commitMessage and staleNoteIds — review and commit after pinning. The remove result's markdown 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's markdown surfaces commitMessage — review and commit after renaming. The restore result's markdown names exactly what was discarded.",
174
174
  inputSchema: {
175
175
  action: z.enum([
176
176
  "sync",
@@ -187,6 +187,11 @@ function buildServer(ctx) {
187
187
  url: z.optional(z.string()).describe("Repo URL to vendor (add)."),
188
188
  purpose: z.optional(z.string()).describe("One-line purpose for the manifest (add)."),
189
189
  sparse: z.optional(z.array(z.string())).describe("Sparse-checkout patterns (add)."),
190
+ orientation: z.optional(z.object({
191
+ layout: z.optional(z.string()),
192
+ keyPaths: z.optional(z.record(z.string(), z.string())),
193
+ startHere: z.optional(z.string())
194
+ })).describe("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."),
190
195
  op: z.optional(z.enum([
191
196
  "add",
192
197
  "remove",
@@ -11,13 +11,18 @@ const PinRequest = Schema.TaggedStruct("pin", {
11
11
  name: Schema.String,
12
12
  ref: Schema.String
13
13
  });
14
- /** `add` requires `url`/`ref`/`purpose`; `name` and `sparse` are optional. */
14
+ /**
15
+ * `add` requires `url`/`ref`/`purpose`; `name`, `sparse` and `orientation`
16
+ * are optional. `orientation` is what makes a re-vendor lossless — pass back
17
+ * the block a preceding `remove` reported.
18
+ */
15
19
  const AddRequest = Schema.TaggedStruct("add", {
16
20
  url: Schema.String,
17
21
  ref: Schema.String,
18
22
  purpose: Schema.String,
19
23
  name: Schema.optional(Schema.String),
20
- sparse: Schema.optional(Schema.Array(Schema.String))
24
+ sparse: Schema.optional(Schema.Array(Schema.String)),
25
+ orientation: Schema.optional(Repos.RepoOrientation)
21
26
  });
22
27
  /**
23
28
  * `note` requires `name` and `op`; the fields required beyond that depend on
@@ -127,7 +132,9 @@ const renderMarkdown = (data) => {
127
132
  ``,
128
133
  ...section("URL synced", r.urlSynced),
129
134
  ``,
130
- ...section("Registered", r.registered)
135
+ ...section("Registered", r.registered),
136
+ ``,
137
+ ...section("Boundary marked", r.boundaryMarked)
131
138
  ].join("\n");
132
139
  }
133
140
  case "pin": {
@@ -186,6 +193,7 @@ const renderMarkdown = (data) => {
186
193
  ];
187
194
  if (r.removedNotes.length > 0) lines.push(`Promote any durable ones elsewhere before committing:`, ...r.removedNotes.map((note) => `- ${mdInline(note.id)} (${mdInline(note.ref)}): ${mdInline(note.note)}`));
188
195
  else lines.push("(none)");
196
+ if (r.removedEntry.orientation) lines.push(``, `## Removed orientation`, ``, `\`add\` does NOT restore this. If you are re-vendoring ${mdInline(r.name)}, pass it back as the \`orientation\` argument:`, ``, "```json", JSON.stringify(r.removedEntry.orientation, null, 2), "```");
189
197
  lines.push(``, `REVIEW AND COMMIT: the manifest, .gitmodules, and gitlink removal are already staged — review and commit using the message above.`);
190
198
  return lines.join("\n");
191
199
  }
@@ -216,6 +224,7 @@ const renderMarkdown = (data) => {
216
224
  lines.push(``, `## Skipped (clean)`, ``);
217
225
  if (r.skippedClean.length > 0) lines.push(...r.skippedClean.map((name) => `- ${mdInline(name)}`));
218
226
  else lines.push("(none)");
227
+ if (r.stillDirty.length > 0) lines.push(``, `## Still dirty — RESTORE DID NOT FULLY SUCCEED`, ``, `The reset ran but these worktrees remain dirty, so treat the restore as incomplete rather than done:`, ``, ...r.stillDirty.map((name) => `- ${mdInline(name)}`), ``, `Run repos_inspect (mode: drift) — a nestedSubmoduleDivergence or a permission problem is the usual cause.`);
219
228
  return lines.join("\n");
220
229
  }
221
230
  }
@@ -254,7 +263,8 @@ const reposManage = (args, fallbackCwd) => Effect.gen(function* () {
254
263
  ref: request.ref,
255
264
  purpose: request.purpose,
256
265
  ...request.name !== void 0 ? { name: request.name } : {},
257
- ...request.sparse !== void 0 ? { sparse: request.sparse } : {}
266
+ ...request.sparse !== void 0 ? { sparse: request.sparse } : {},
267
+ ...request.orientation !== void 0 ? { orientation: request.orientation } : {}
258
268
  })
259
269
  };
260
270
  case "note": {