@savvy-web/mcp 2.4.10 → 2.5.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/package.json +1 -1
- package/server.js +1 -1
- package/tools/biome-check.js +72 -12
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@savvy-web/mcp",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.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",
|
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
|
|
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) => {
|
package/tools/biome-check.js
CHANGED
|
@@ -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
|
-
/**
|
|
73
|
-
|
|
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
|
-
|
|
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 };
|