@savvy-web/mcp 2.4.10 → 2.5.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@savvy-web/mcp",
3
- "version": "2.4.10",
3
+ "version": "2.5.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",
@@ -34,9 +34,9 @@
34
34
  "@effect/platform-node": "4.0.0-rc.109",
35
35
  "@effected/commands": "^0.5.0",
36
36
  "@effected/git": "^0.9.0",
37
- "@effected/workspaces": "^0.17.0",
37
+ "@effected/workspaces": "^0.17.1",
38
38
  "@modelcontextprotocol/sdk": "^1.30.0",
39
- "@savvy-web/silk-effects": "6.0.5",
39
+ "@savvy-web/silk-effects": "7.0.0",
40
40
  "effect": "4.0.0-rc.109",
41
41
  "zod": "^4.4.3"
42
42
  }
package/server.js CHANGED
@@ -223,7 +223,7 @@ function buildServer(ctx) {
223
223
  write: z.optional(z.boolean()).describe("Apply safe fixes (--write)."),
224
224
  unsafe: z.optional(z.boolean()).describe("Apply unsafe fixes (--write --unsafe); implies write."),
225
225
  strict: z.optional(z.boolean()).describe("Report project warnings as errors (marked with originalSeverity). Default: honor project config."),
226
- cwd: z.optional(z.string()).describe("Directory to resolve the workspace root from.")
226
+ cwd: z.optional(z.string()).describe("Directory to run from. May be the server's workspace root, a directory inside it, or a git worktree of the SAME repository — a worktree contains the run to that worktree instead of the main checkout. Anything else is rejected.")
227
227
  },
228
228
  outputSchema: effectToZodSchema(BiomeCheckResult)
229
229
  }, async (args) => {
@@ -69,8 +69,17 @@ const GitlabDiagnostic = Schema.Struct({
69
69
  });
70
70
  const GitlabArray = Schema.Array(GitlabDiagnostic);
71
71
  const decodeGitlab = Schema.decodeUnknownSync(GitlabArray);
72
- /** Map a gitlab severity onto our three-level scale. */
73
- const mapSeverity = (s) => s === "info" ? "info" : s === "minor" ? "warning" : "error";
72
+ /**
73
+ * Map a gitlab severity back onto the Biome severity it was produced from.
74
+ *
75
+ * @remarks Biome's gitlab reporter encodes its own `Severity` onto GitLab's
76
+ * codequality scale (`crates/biome_cli/src/reporter/gitlab.rs`):
77
+ * `Hint => info`, `Information => minor`, `Warning => major`, `Error => critical`,
78
+ * `Fatal => blocker`. This function is the exact inverse. Reading `major` as an
79
+ * error (as this did before systems#516) reports every diagnostic one step more
80
+ * severe than the project's own `biome check` does, turning a green repo red.
81
+ */
82
+ const mapSeverity = (s) => s === "info" || s === "minor" ? "info" : s === "major" ? "warning" : "error";
74
83
  /**
75
84
  * Parse Biome `--reporter=gitlab` stdout into normalized diagnostics. Returns []
76
85
  * for empty, non-JSON, or shape-mismatched input (never throws).
@@ -143,6 +152,63 @@ const BiomeCheckAsMarkdown = BiomeCheckResult.pipe(Schema.decodeTo(Schema.String
143
152
  encode: SchemaGetter.forbidden(() => "BiomeCheckAsMarkdown is one-way: markdown cannot be parsed back.")
144
153
  }));
145
154
  /**
155
+ * Canonicalize a path, falling back to lexical resolution when it does not exist
156
+ * (a non-existent target cannot be a symlink pointing out of tree).
157
+ */
158
+ const canonicalize = (p) => {
159
+ try {
160
+ return realpathSync(p);
161
+ } catch {
162
+ return resolve(p);
163
+ }
164
+ };
165
+ /** Real git probe. One `rev-parse` yielding both paths already absolute. */
166
+ const gitWorktreeProbe = (dir) => {
167
+ const res = spawnSync("git", [
168
+ "-C",
169
+ dir,
170
+ "rev-parse",
171
+ "--path-format=absolute",
172
+ "--git-common-dir",
173
+ "--show-toplevel"
174
+ ], {
175
+ encoding: "utf8",
176
+ timeout: 1e4
177
+ });
178
+ if (res.error || (res.status ?? 1) !== 0) return null;
179
+ const [commonDir, topLevel] = (res.stdout ?? "").trim().split("\n");
180
+ if (!commonDir || !topLevel) return null;
181
+ return {
182
+ commonDir: canonicalize(commonDir.trim()),
183
+ topLevel: canonicalize(topLevel.trim())
184
+ };
185
+ };
186
+ /**
187
+ * Decide which directory tree this run may touch.
188
+ *
189
+ * @remarks A cwd inside the server's root keeps that root. A cwd OUTSIDE it is
190
+ * accepted only when it belongs to a DIFFERENT worktree of the same repository —
191
+ * a shared git common dir but a different top level — and containment then follows
192
+ * that worktree rather than the server's start directory (systems#482). Binding to the start directory
193
+ * meant a call from a sibling worktree silently mutated the main checkout, which in
194
+ * a parallel multi-agent session is another agent's tree. Returns null to reject.
195
+ *
196
+ * @param root - the server's workspace root, already canonicalized
197
+ * @param cwd - the requested working directory, already canonicalized
198
+ * @param probe - git identity probe; injectable for tests
199
+ * @returns the directory tree to contain to, or null if the cwd must be rejected
200
+ */
201
+ const resolveContainmentRoot = (root, cwd, probe = gitWorktreeProbe) => {
202
+ if (cwd === root || cwd.startsWith(`${root}${sep}`)) return root;
203
+ const from = probe(cwd);
204
+ if (!from) return null;
205
+ const home = probe(root);
206
+ if (!home) return null;
207
+ if (from.commonDir !== home.commonDir) return null;
208
+ if (from.topLevel === home.topLevel) return null;
209
+ return from.topLevel;
210
+ };
211
+ /**
146
212
  * Run Biome and return structured diagnostics. When `write`/`unsafe` is set,
147
213
  * runs a fix pass first, then a read-only gitlab pass to report what remains.
148
214
  *
@@ -152,17 +218,11 @@ const BiomeCheckAsMarkdown = BiomeCheckResult.pipe(Schema.decodeTo(Schema.String
152
218
  */
153
219
  const runBiomeCheck = async (args, fallbackCwd) => {
154
220
  const mode = args.mode ?? "check";
155
- const canonicalize = (p) => {
156
- try {
157
- return realpathSync(p);
158
- } catch {
159
- return resolve(p);
160
- }
161
- };
162
221
  const root = canonicalize(fallbackCwd);
163
- const within = (abs) => abs === root || abs.startsWith(`${root}${sep}`);
164
222
  const cwd = canonicalize(args.cwd ?? fallbackCwd);
165
- if (!within(cwd)) throw new Error(`cwd escapes the workspace root: ${args.cwd}`);
223
+ const containmentRoot = resolveContainmentRoot(root, cwd);
224
+ if (containmentRoot === null) throw new Error(`cwd escapes the workspace root: ${args.cwd}`);
225
+ const within = (abs) => abs === containmentRoot || abs.startsWith(`${containmentRoot}${sep}`);
166
226
  const paths = (args.paths && args.paths.length > 0 ? args.paths : ["."]).map((p) => {
167
227
  const lexical = resolve(cwd, p);
168
228
  if (!within(canonicalize(lexical))) throw new Error(`path escapes the workspace root: ${p}`);
@@ -222,4 +282,4 @@ const runBiomeCheck = async (args, fallbackCwd) => {
222
282
  };
223
283
 
224
284
  //#endregion
225
- export { BiomeCheckAsMarkdown, BiomeCheckResult, BiomeDiagnostic, BiomeSeverity, buildBiomeResult, parseBiomeGitlab, runBiomeCheck };
285
+ export { BiomeCheckAsMarkdown, BiomeCheckResult, BiomeDiagnostic, BiomeSeverity, buildBiomeResult, parseBiomeGitlab, resolveContainmentRoot, runBiomeCheck };