@open-pets/zed 4.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.
@@ -0,0 +1,747 @@
1
+ import assert from "node:assert/strict";
2
+ import { spawn } from "node:child_process";
3
+ import { createHash } from "node:crypto";
4
+ import { createRequire, syncBuiltinESMExports } from "node:module";
5
+ import { closeSync, existsSync, mkdirSync, mkdtempSync, openSync, readFileSync, realpathSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
6
+ import { dirname, join } from "node:path";
7
+ import { tmpdir } from "node:os";
8
+ import { buildZedMcpEntry, formatZedMcpConfig, getZedGlobalSettingsDir, getZedGlobalSettingsPath, isValidOpenPetsPackageVersion, isValidPetId, isValidZedNodeCommand, validateOpenPetsPackageVersion, validateOpenPetsPetId, } from "./zed-mcp.js";
9
+ import { classifyZedMcpStatus, executeZedMcpWrite, isManagedOpenPetsMcpEntry, maxZedSettingsBytes, parseZedSettings, planZedMcpInstall, planZedMcpRemove, planZedMcpReplace, readZedSettings, updateZedSettingsText, } from "./zed-status.js";
10
+ const root = realpathSync(mkdtempSync(join(tmpdir(), "openpets-zed-")));
11
+ const expected = { mcpVersion: "3.3.0", petId: "fixer" };
12
+ function settingsPath(name) {
13
+ const dir = join(root, name);
14
+ mkdirSync(dir, { recursive: true });
15
+ return join(dir, "settings.json");
16
+ }
17
+ function writeSettings(path, content) {
18
+ writeFileSync(path, content, "utf8");
19
+ }
20
+ function tryCreateSymlink(target, linkPath) {
21
+ try {
22
+ symlinkSync(target, linkPath);
23
+ return true;
24
+ }
25
+ catch (error) {
26
+ if (error && typeof error === "object" && "code" in error && (error.code === "EPERM" || error.code === "EACCES"))
27
+ return false;
28
+ throw error;
29
+ }
30
+ }
31
+ function executePlan(plan) {
32
+ assert.equal("targetPath" in plan, true);
33
+ if ("targetPath" in plan)
34
+ executeZedMcpWrite(plan);
35
+ }
36
+ function writeInterruptedLock(plan, lockPath, lockTempPath, includeClaimPath = true, withdrawalPath, tempPath = plan.tempPath) {
37
+ const hash = (content) => createHash("sha256").update(content, "utf8").digest("hex");
38
+ writeSettings(lockPath, JSON.stringify({
39
+ version: 1,
40
+ token: "interrupted-test-write",
41
+ pid: 999999999,
42
+ lockTempPath,
43
+ targetPath: plan.targetPath,
44
+ ...(plan.backupPath ? { backupPath: plan.backupPath } : {}),
45
+ ...(includeClaimPath && plan.claimPath ? { claimPath: plan.claimPath } : {}),
46
+ ...(withdrawalPath ? { withdrawalPath } : {}),
47
+ tempPath,
48
+ sourceExists: plan.sourceExists,
49
+ sourceHash: hash(plan.sourceContent),
50
+ contentHash: hash(plan.content),
51
+ }));
52
+ }
53
+ try {
54
+ // Path resolution follows Zed's platform-specific global settings locations.
55
+ assert.equal(getZedGlobalSettingsDir({}, root, "darwin"), join(root, ".config", "zed"));
56
+ assert.equal(getZedGlobalSettingsDir({ XDG_CONFIG_HOME: join(root, "xdg") }, root, "darwin"), join(root, ".config", "zed"));
57
+ assert.equal(getZedGlobalSettingsPath({ XDG_CONFIG_HOME: join(root, "xdg") }, root, "linux"), join(root, "xdg", "zed", "settings.json"));
58
+ assert.equal(getZedGlobalSettingsPath({ FLATPAK_XDG_CONFIG_HOME: join(root, "flatpak"), XDG_CONFIG_HOME: join(root, "xdg") }, root, "linux"), join(root, "flatpak", "zed", "settings.json"));
59
+ assert.equal(getZedGlobalSettingsPath({ APPDATA: join(root, "appdata") }, root, "win32"), join(root, "appdata", "Zed", "settings.json"));
60
+ // Pet and package inputs are bounded before they become command arguments.
61
+ assert.equal(isValidPetId("fixer"), true);
62
+ assert.equal(isValidPetId("bad/pet"), false);
63
+ assert.equal(validateOpenPetsPetId("fixer"), "fixer");
64
+ assert.throws(() => validateOpenPetsPetId("bad/pet"));
65
+ assert.equal(validateOpenPetsPackageVersion("3.3.0-beta.1"), "3.3.0-beta.1");
66
+ assert.throws(() => validateOpenPetsPackageVersion("latest"));
67
+ assert.equal(isValidOpenPetsPackageVersion("1.2.3"), true);
68
+ assert.equal(isValidOpenPetsPackageVersion("1.2.3+build.01"), true);
69
+ assert.equal(isValidOpenPetsPackageVersion("01.2.3"), false);
70
+ assert.equal(isValidOpenPetsPackageVersion("1.2.3-alpha..1"), false);
71
+ const published = buildZedMcpEntry(expected);
72
+ assert.deepEqual(published, {
73
+ command: "npx",
74
+ args: ["-y", "@open-pets/mcp@3.3.0", "--pet", "fixer"],
75
+ });
76
+ assert.deepEqual(buildZedMcpEntry({ mcpVersion: "3.3.0" }), {
77
+ command: "npx",
78
+ args: ["-y", "@open-pets/mcp@3.3.0"],
79
+ });
80
+ assert.deepEqual(formatZedMcpConfig(expected), { context_servers: { openpets: published } });
81
+ const localEntryPath = join(root, "node_modules", "@open-pets", "mcp", "dist", "index.js");
82
+ assert.deepEqual(buildZedMcpEntry({ ...expected, commandMode: "local", mcpEntryPath: localEntryPath }), {
83
+ command: "node",
84
+ args: [localEntryPath, "--pet", "fixer"],
85
+ });
86
+ assert.deepEqual(buildZedMcpEntry({ ...expected, commandMode: "bundled", mcpEntryPath: localEntryPath }), {
87
+ command: "node",
88
+ args: [localEntryPath, "--pet", "fixer"],
89
+ });
90
+ const customNodeCommand = join(root, "node-bin");
91
+ const customLocalEntry = buildZedMcpEntry({ ...expected, commandMode: "bundled", mcpEntryPath: localEntryPath, nodeCommand: customNodeCommand });
92
+ assert.equal(customLocalEntry.command, customNodeCommand);
93
+ const customStatusPath = settingsPath("custom-node-status");
94
+ writeSettings(customStatusPath, JSON.stringify({ context_servers: { openpets: customLocalEntry } }, null, 2));
95
+ assert.equal(classifyZedMcpStatus(readZedSettings(customStatusPath), customStatusPath, { ...expected, commandMode: "bundled", mcpEntryPath: localEntryPath, nodeCommand: customNodeCommand }).status, "installed");
96
+ assert.equal(classifyZedMcpStatus(readZedSettings(customStatusPath), customStatusPath, expected).status, "needs-update");
97
+ assert.equal(classifyZedMcpStatus(readZedSettings(customStatusPath), customStatusPath, { ...expected, commandMode: "local", mcpEntryPath: localEntryPath, nodeCommand: join(root, "different-node") }).status, "needs-update");
98
+ assert.equal(isManagedOpenPetsMcpEntry(customLocalEntry), true);
99
+ const customRemovePath = settingsPath("custom-node-remove");
100
+ writeSettings(customRemovePath, JSON.stringify({ context_servers: { openpets: customLocalEntry } }, null, 2));
101
+ const customRemovePlan = planZedMcpRemove(customRemovePath, expected);
102
+ executePlan(customRemovePlan);
103
+ const customRemoved = parseZedSettings(readFileSync(customRemovePath, "utf8"));
104
+ assert.equal(customRemoved.ok, true);
105
+ if (customRemoved.ok)
106
+ assert.equal(customRemoved.value.context_servers.openpets, undefined);
107
+ assert.throws(() => buildZedMcpEntry({ ...expected, commandMode: "local", mcpEntryPath: "relative.js" }));
108
+ assert.throws(() => buildZedMcpEntry({ ...expected, commandMode: "local", mcpEntryPath: join(root, "not-openpets.js") }));
109
+ const traversedNodeCommand = `${root}\\..\\node`;
110
+ assert.equal(isValidZedNodeCommand(traversedNodeCommand), true);
111
+ assert.doesNotThrow(() => buildZedMcpEntry({ ...expected, commandMode: "local", mcpEntryPath: localEntryPath, nodeCommand: traversedNodeCommand }));
112
+ assert.throws(() => buildZedMcpEntry({ ...expected, commandMode: "local", mcpEntryPath: localEntryPath, nodeCommand: "relative-node" }));
113
+ // Clean install creates only the settings file and managed OpenPets entry.
114
+ const cleanPath = settingsPath("clean-install");
115
+ const cleanPlan = planZedMcpInstall(cleanPath, expected);
116
+ executePlan(cleanPlan);
117
+ const cleanText = readFileSync(cleanPath, "utf8");
118
+ const cleanConfig = parseZedSettings(cleanText);
119
+ assert.equal(cleanConfig.ok, true);
120
+ if (cleanConfig.ok)
121
+ assert.deepEqual(cleanConfig.value.context_servers.openpets, published);
122
+ assert.equal(classifyZedMcpStatus(readZedSettings(cleanPath), cleanPath, expected).status, "installed");
123
+ // JSONC comments, trailing commas, unrelated settings, and servers survive a targeted edit.
124
+ const jsoncPath = settingsPath("jsonc-preservation");
125
+ const jsoncSource = `{
126
+ // user setting must survive
127
+ "theme": "dark",
128
+ "context_servers": {
129
+ "other": {
130
+ "command": "other-server",
131
+ "args": [],
132
+ },
133
+ },
134
+ }`;
135
+ writeSettings(jsoncPath, jsoncSource);
136
+ const jsoncPlan = planZedMcpInstall(jsoncPath, expected);
137
+ executePlan(jsoncPlan);
138
+ const jsoncText = readFileSync(jsoncPath, "utf8");
139
+ assert.match(jsoncText, /user setting must survive/);
140
+ assert.match(jsoncText, /"theme": "dark"/);
141
+ assert.match(jsoncText, /"other"/);
142
+ assert.match(jsoncText, /"args": \[\],\s*\}/u);
143
+ assert.equal(classifyZedMcpStatus(readZedSettings(jsoncPath), jsoncPath, expected).status, "installed");
144
+ // Installed state distinguishes exact command and pet/version drift.
145
+ const installedPath = settingsPath("installed");
146
+ writeSettings(installedPath, JSON.stringify({ context_servers: { openpets: published } }, null, 2));
147
+ assert.equal(classifyZedMcpStatus(readZedSettings(installedPath), installedPath, expected).status, "installed");
148
+ const versionDriftPath = settingsPath("version-drift");
149
+ writeSettings(versionDriftPath, JSON.stringify({ context_servers: { openpets: buildZedMcpEntry({ mcpVersion: "3.2.0", petId: "fixer" }) } }, null, 2));
150
+ assert.equal(classifyZedMcpStatus(readZedSettings(versionDriftPath), versionDriftPath, expected).status, "needs-update");
151
+ const petDriftPath = settingsPath("pet-drift");
152
+ writeSettings(petDriftPath, JSON.stringify({ context_servers: { openpets: buildZedMcpEntry({ mcpVersion: "3.3.0", petId: "helper" }) } }, null, 2));
153
+ assert.equal(classifyZedMcpStatus(readZedSettings(petDriftPath), petDriftPath, expected).status, "needs-update");
154
+ const localStatusPath = settingsPath("local-status");
155
+ const localStatusEntryPath = join(root, "local", "packages", "mcp", "dist", "index.js");
156
+ writeSettings(localStatusPath, JSON.stringify({ context_servers: { openpets: { command: "node", args: [localStatusEntryPath, "--pet", "helper"] } } }, null, 2));
157
+ assert.equal(classifyZedMcpStatus(readZedSettings(localStatusPath), localStatusPath, { ...expected, commandMode: "local", mcpEntryPath: localStatusEntryPath }).status, "needs-update");
158
+ // Remote execution is never retained; local OpenPets execution is required.
159
+ const remotePath = settingsPath("remote-corrected");
160
+ writeSettings(remotePath, JSON.stringify({ context_servers: {
161
+ openpets: { ...buildZedMcpEntry({ mcpVersion: "3.2.0", petId: "helper" }), enabled: true, remote: true, env: { OPENPETS_DEBUG: "1" }, timeout: 30 },
162
+ } }, null, 2));
163
+ assert.equal(classifyZedMcpStatus(readZedSettings(remotePath), remotePath, expected).status, "needs-update");
164
+ const remotePlan = planZedMcpInstall(remotePath, expected);
165
+ executePlan(remotePlan);
166
+ const remoteConfig = parseZedSettings(readFileSync(remotePath, "utf8"));
167
+ assert.equal(remoteConfig.ok, true);
168
+ if (remoteConfig.ok) {
169
+ assert.deepEqual(remoteConfig.value.context_servers.openpets, {
170
+ ...published,
171
+ enabled: true,
172
+ env: { OPENPETS_DEBUG: "1" },
173
+ timeout: 30,
174
+ });
175
+ }
176
+ const remoteDisabledPath = settingsPath("remote-disabled");
177
+ const remoteDisabledSource = JSON.stringify({ context_servers: {
178
+ openpets: { ...buildZedMcpEntry({ mcpVersion: "3.2.0", petId: "helper" }), enabled: false, remote: true, env: { OPENPETS_DEBUG: "1" }, timeout: 30 },
179
+ } }, null, 2);
180
+ writeSettings(remoteDisabledPath, remoteDisabledSource);
181
+ const remoteDisabledStatus = classifyZedMcpStatus(readZedSettings(remoteDisabledPath), remoteDisabledPath, expected);
182
+ assert.equal(remoteDisabledStatus.status, "needs-update");
183
+ assert.equal(remoteDisabledStatus.canInstall, false);
184
+ assert.equal(remoteDisabledStatus.canReplace, true);
185
+ const remoteDisabledInstall = planZedMcpInstall(remoteDisabledPath, expected);
186
+ assert.equal("ok" in remoteDisabledInstall && remoteDisabledInstall.ok === false, true);
187
+ assert.equal(readFileSync(remoteDisabledPath, "utf8"), remoteDisabledSource);
188
+ const remoteDisabledReplace = planZedMcpReplace(remoteDisabledPath, expected);
189
+ executePlan(remoteDisabledReplace);
190
+ const remoteReplaced = parseZedSettings(readFileSync(remoteDisabledPath, "utf8"));
191
+ assert.equal(remoteReplaced.ok, true);
192
+ if (remoteReplaced.ok) {
193
+ assert.deepEqual(remoteReplaced.value.context_servers.openpets, {
194
+ ...published,
195
+ enabled: true,
196
+ env: { OPENPETS_DEBUG: "1" },
197
+ timeout: 30,
198
+ });
199
+ }
200
+ // A settings change after planning must not be overwritten by a stale plan.
201
+ const concurrentPath = settingsPath("concurrent-change");
202
+ const concurrentSource = JSON.stringify({ theme: "dark", context_servers: { other: { command: "other", args: [] } } }, null, 2);
203
+ writeSettings(concurrentPath, concurrentSource);
204
+ const stalePlan = planZedMcpInstall(concurrentPath, expected);
205
+ assert.equal("targetPath" in stalePlan, true);
206
+ writeSettings(concurrentPath, JSON.stringify({ theme: "light", context_servers: { other: { command: "other", args: [] } } }, null, 2));
207
+ assert.throws(() => executePlan(stalePlan), /changed since this operation was previewed/);
208
+ assert.match(readFileSync(concurrentPath, "utf8"), /"theme": "light"/);
209
+ // A pre-existing backup artifact is never overwritten by a fresh write.
210
+ const backupCollisionPath = settingsPath("backup-collision");
211
+ const backupCollisionSource = JSON.stringify({ theme: "dark" }, null, 2);
212
+ writeSettings(backupCollisionPath, backupCollisionSource);
213
+ const backupCollisionPlan = planZedMcpInstall(backupCollisionPath, expected);
214
+ assert.equal("targetPath" in backupCollisionPlan, true);
215
+ if ("targetPath" in backupCollisionPlan && backupCollisionPlan.backupPath) {
216
+ writeSettings(backupCollisionPlan.backupPath, "occupied backup\n");
217
+ assert.throws(() => executePlan(backupCollisionPlan), /support path already exists/);
218
+ assert.equal(readFileSync(backupCollisionPath, "utf8"), backupCollisionSource);
219
+ assert.equal(readFileSync(backupCollisionPlan.backupPath, "utf8"), "occupied backup\n");
220
+ rmSync(backupCollisionPlan.backupPath, { force: true });
221
+ }
222
+ // A write through a descriptor opened before publication remains recoverable in the backup.
223
+ const descriptorPath = settingsPath("preexisting-descriptor");
224
+ const descriptorSource = JSON.stringify({ theme: "dark" }, null, 2);
225
+ writeSettings(descriptorPath, descriptorSource);
226
+ const descriptorPlan = planZedMcpInstall(descriptorPath, expected);
227
+ assert.equal("targetPath" in descriptorPlan, true);
228
+ if ("targetPath" in descriptorPlan && descriptorPlan.backupPath) {
229
+ const descriptorFd = openSync(descriptorPath, "r+");
230
+ try {
231
+ executePlan(descriptorPlan);
232
+ const descriptorUpdate = descriptorSource.replace('"dark"', '"light"');
233
+ writeFileSync(descriptorFd, descriptorUpdate, "utf8");
234
+ }
235
+ finally {
236
+ closeSync(descriptorFd);
237
+ }
238
+ assert.equal(readFileSync(descriptorPath, "utf8"), descriptorPlan.content);
239
+ assert.equal(readFileSync(descriptorPlan.backupPath, "utf8"), descriptorSource.replace('"dark"', '"light"'));
240
+ }
241
+ // A replacement that arrives while the original target is withdrawn is preserved and surfaced.
242
+ const replacementPath = settingsPath("replacement-during-withdrawal");
243
+ const replacementSource = JSON.stringify({ theme: "dark" }, null, 2);
244
+ writeSettings(replacementPath, replacementSource);
245
+ const replacementPlan = planZedMcpInstall(replacementPath, expected);
246
+ assert.equal("targetPath" in replacementPlan, true);
247
+ if ("targetPath" in replacementPlan && replacementPlan.claimPath) {
248
+ const replacementContent = replacementPlan.content;
249
+ const fs = createRequire(import.meta.url)("node:fs");
250
+ const originalLinkSync = fs.linkSync;
251
+ let replacementInjected = false;
252
+ fs.linkSync = ((source, destination) => {
253
+ if (!replacementInjected && source === replacementPlan.tempPath && destination === replacementPlan.targetPath) {
254
+ writeSettings(destination, replacementContent);
255
+ replacementInjected = true;
256
+ }
257
+ return originalLinkSync(source, destination);
258
+ });
259
+ syncBuiltinESMExports();
260
+ try {
261
+ assert.throws(() => executePlan(replacementPlan), /changed during this operation/);
262
+ assert.equal(readFileSync(replacementPath, "utf8"), replacementContent);
263
+ assert.equal(existsSync(join(dirname(replacementPlan.targetPath), ".openpets-zed.lock")), true);
264
+ }
265
+ finally {
266
+ fs.linkSync = originalLinkSync;
267
+ syncBuiltinESMExports();
268
+ }
269
+ }
270
+ // Cleanup preserves an artifact replaced after its content was validated.
271
+ const cleanupPath = settingsPath("replacement-during-cleanup");
272
+ const cleanupSource = JSON.stringify({ theme: "dark" }, null, 2);
273
+ const cleanupReplacement = JSON.stringify({ theme: "light" }, null, 2);
274
+ writeSettings(cleanupPath, cleanupSource);
275
+ const cleanupPlan = planZedMcpInstall(cleanupPath, expected);
276
+ assert.equal("targetPath" in cleanupPlan, true);
277
+ if ("targetPath" in cleanupPlan && cleanupPlan.claimPath) {
278
+ const fs = createRequire(import.meta.url)("node:fs");
279
+ const originalRenameSync = fs.renameSync;
280
+ let cleanupReplacementPath;
281
+ fs.renameSync = ((source, destination) => {
282
+ if (!cleanupReplacementPath && source.includes(".openpets-zed-withdraw-") && destination.includes(".openpets-zed-cleanup-")) {
283
+ cleanupReplacementPath = source;
284
+ writeSettings(source, cleanupReplacement);
285
+ }
286
+ return originalRenameSync(source, destination);
287
+ });
288
+ syncBuiltinESMExports();
289
+ try {
290
+ executePlan(cleanupPlan);
291
+ assert.equal(typeof cleanupReplacementPath, "string");
292
+ if (cleanupReplacementPath)
293
+ assert.equal(readFileSync(cleanupReplacementPath, "utf8"), cleanupReplacement);
294
+ assert.equal(readFileSync(cleanupPath, "utf8"), cleanupPlan.content);
295
+ assert.equal(existsSync(join(dirname(cleanupPlan.targetPath), ".openpets-zed.lock")), true);
296
+ }
297
+ finally {
298
+ fs.renameSync = originalRenameSync;
299
+ syncBuiltinESMExports();
300
+ }
301
+ assert.throws(() => executePlan(cleanupPlan), /Zed write recovery is ambiguous/);
302
+ }
303
+ // A retained lock from a transient cleanup failure is recoverable in the same process.
304
+ const retryPath = settingsPath("retained-lock-retry");
305
+ writeSettings(retryPath, JSON.stringify({ theme: "dark" }, null, 2));
306
+ const retryPlan = planZedMcpInstall(retryPath, expected);
307
+ assert.equal("targetPath" in retryPlan, true);
308
+ if ("targetPath" in retryPlan) {
309
+ const fs = createRequire(import.meta.url)("node:fs");
310
+ const originalRenameSync = fs.renameSync;
311
+ let cleanupFailureInjected = false;
312
+ fs.renameSync = ((source, destination) => {
313
+ if (!cleanupFailureInjected && source === retryPlan.tempPath && destination.includes(".openpets-zed-cleanup-")) {
314
+ cleanupFailureInjected = true;
315
+ const error = new Error("injected cleanup failure");
316
+ error.code = "EIO";
317
+ throw error;
318
+ }
319
+ return originalRenameSync(source, destination);
320
+ });
321
+ syncBuiltinESMExports();
322
+ try {
323
+ executePlan(retryPlan);
324
+ }
325
+ finally {
326
+ fs.renameSync = originalRenameSync;
327
+ syncBuiltinESMExports();
328
+ }
329
+ assert.equal(cleanupFailureInjected, true);
330
+ assert.equal(existsSync(join(dirname(retryPlan.targetPath), ".openpets-zed.lock")), true);
331
+ const retryRemovePlan = planZedMcpRemove(retryPath, expected);
332
+ executePlan(retryRemovePlan);
333
+ assert.equal(classifyZedMcpStatus(readZedSettings(retryPath), retryPath, expected).status, "missing");
334
+ assert.equal(existsSync(join(dirname(retryPlan.targetPath), ".openpets-zed.lock")), false);
335
+ }
336
+ // Lock cleanup never deletes a replacement lock that appears after ownership is checked.
337
+ const lockCleanupPath = settingsPath("replacement-during-lock-cleanup");
338
+ const lockCleanupSource = JSON.stringify({ theme: "dark" }, null, 2);
339
+ writeSettings(lockCleanupPath, lockCleanupSource);
340
+ const lockCleanupPlan = planZedMcpInstall(lockCleanupPath, expected);
341
+ assert.equal("targetPath" in lockCleanupPlan, true);
342
+ if ("targetPath" in lockCleanupPlan) {
343
+ const fs = createRequire(import.meta.url)("node:fs");
344
+ const originalRenameSync = fs.renameSync;
345
+ const lockPath = join(dirname(lockCleanupPlan.targetPath), ".openpets-zed.lock");
346
+ const replacementLock = "replacement lock\n";
347
+ fs.renameSync = ((source, destination) => {
348
+ if (source === lockPath && destination.includes(".openpets-zed-cleanup-"))
349
+ writeSettings(source, replacementLock);
350
+ return originalRenameSync(source, destination);
351
+ });
352
+ syncBuiltinESMExports();
353
+ try {
354
+ executePlan(lockCleanupPlan);
355
+ assert.equal(readFileSync(lockPath, "utf8"), replacementLock);
356
+ }
357
+ finally {
358
+ fs.renameSync = originalRenameSync;
359
+ syncBuiltinESMExports();
360
+ rmSync(lockPath, { force: true });
361
+ }
362
+ }
363
+ // Stale-lock claim cleanup never deletes a replacement that appears after the claim is checked.
364
+ const claimCleanupPath = settingsPath("replacement-during-lock-claim-cleanup");
365
+ const claimCleanupSource = JSON.stringify({ theme: "dark" }, null, 2);
366
+ writeSettings(claimCleanupPath, claimCleanupSource);
367
+ const claimCleanupPlan = planZedMcpInstall(claimCleanupPath, expected);
368
+ assert.equal("targetPath" in claimCleanupPlan, true);
369
+ if ("targetPath" in claimCleanupPlan && claimCleanupPlan.backupPath && claimCleanupPlan.claimPath) {
370
+ writeSettings(claimCleanupPlan.backupPath, claimCleanupPlan.sourceContent);
371
+ writeSettings(claimCleanupPlan.claimPath, claimCleanupPlan.sourceContent);
372
+ writeSettings(claimCleanupPlan.tempPath, claimCleanupPlan.content);
373
+ const claimLockPath = join(dirname(claimCleanupPlan.targetPath), ".openpets-zed.lock");
374
+ const claimLockTempPath = join(dirname(claimCleanupPlan.targetPath), ".openpets-zed-lock-claim-cleanup.tmp");
375
+ writeSettings(claimLockTempPath, "stale lock owner");
376
+ writeInterruptedLock(claimCleanupPlan, claimLockPath, claimLockTempPath);
377
+ const fs = createRequire(import.meta.url)("node:fs");
378
+ const originalRenameSync = fs.renameSync;
379
+ const originalRmSync = fs.rmSync;
380
+ const replacementLock = "replacement lock\n";
381
+ let recoveryClaimPath;
382
+ let replacementInjected = false;
383
+ fs.renameSync = ((source, destination) => {
384
+ if (!recoveryClaimPath && source === claimLockPath && destination.includes(".openpets-zed-lock-recovery-"))
385
+ recoveryClaimPath = destination;
386
+ if (!replacementInjected && source.includes(".openpets-zed-lock-recovery-") && destination.includes(".openpets-zed-cleanup-")) {
387
+ writeSettings(source, replacementLock);
388
+ replacementInjected = true;
389
+ }
390
+ return originalRenameSync(source, destination);
391
+ });
392
+ fs.rmSync = ((path, options) => {
393
+ if (!replacementInjected && path.includes(".openpets-zed-lock-recovery-")) {
394
+ writeSettings(path, replacementLock);
395
+ replacementInjected = true;
396
+ }
397
+ return originalRmSync(path, options);
398
+ });
399
+ syncBuiltinESMExports();
400
+ try {
401
+ assert.throws(() => executePlan(claimCleanupPlan), /claim changed during recovery/);
402
+ assert.equal(replacementInjected, true);
403
+ assert.equal(readFileSync(claimLockPath, "utf8"), replacementLock);
404
+ }
405
+ finally {
406
+ fs.renameSync = originalRenameSync;
407
+ fs.rmSync = originalRmSync;
408
+ syncBuiltinESMExports();
409
+ rmSync(claimLockPath, { force: true });
410
+ rmSync(claimLockTempPath, { force: true });
411
+ if (recoveryClaimPath)
412
+ rmSync(recoveryClaimPath, { force: true });
413
+ rmSync(claimCleanupPlan.backupPath, { force: true });
414
+ rmSync(claimCleanupPlan.claimPath, { force: true });
415
+ rmSync(claimCleanupPlan.tempPath, { force: true });
416
+ }
417
+ }
418
+ // A failure after backup creation releases the lock when the original target is still intact.
419
+ const failedWritePath = settingsPath("failed-write-lock-release");
420
+ const failedWriteSource = JSON.stringify({ theme: "dark" }, null, 2);
421
+ writeSettings(failedWritePath, failedWriteSource);
422
+ const failedWritePlan = planZedMcpInstall(failedWritePath, expected);
423
+ assert.equal("targetPath" in failedWritePlan, true);
424
+ if ("targetPath" in failedWritePlan && failedWritePlan.backupPath && failedWritePlan.claimPath) {
425
+ const fs = createRequire(import.meta.url)("node:fs");
426
+ const originalLinkSync = fs.linkSync;
427
+ fs.linkSync = ((source, destination) => {
428
+ if (source === failedWritePlan.targetPath && destination === failedWritePlan.claimPath) {
429
+ const injected = new Error("injected claim failure");
430
+ injected.code = "EIO";
431
+ throw injected;
432
+ }
433
+ return originalLinkSync(source, destination);
434
+ });
435
+ syncBuiltinESMExports();
436
+ try {
437
+ assert.throws(() => executePlan(failedWritePlan), /injected claim failure/);
438
+ assert.equal(readFileSync(failedWritePath, "utf8"), failedWriteSource);
439
+ assert.equal(existsSync(failedWritePlan.backupPath), false);
440
+ assert.equal(existsSync(join(dirname(failedWritePlan.targetPath), ".openpets-zed.lock")), false);
441
+ }
442
+ finally {
443
+ fs.linkSync = originalLinkSync;
444
+ syncBuiltinESMExports();
445
+ }
446
+ executePlan(failedWritePlan);
447
+ }
448
+ const emptyConcurrentPath = settingsPath("empty-concurrent-change");
449
+ writeSettings(emptyConcurrentPath, "");
450
+ const emptyStalePlan = planZedMcpInstall(emptyConcurrentPath, expected);
451
+ assert.equal("targetPath" in emptyStalePlan, true);
452
+ rmSync(emptyConcurrentPath);
453
+ assert.throws(() => executePlan(emptyStalePlan), /changed since this operation was previewed/);
454
+ assert.equal(existsSync(emptyConcurrentPath), false);
455
+ // Unreadable lock metadata must fail closed and remain untouched.
456
+ const lockedPath = settingsPath("locked");
457
+ writeSettings(lockedPath, "{}\n");
458
+ const lockedPlan = planZedMcpInstall(lockedPath, expected);
459
+ const lockPath = join(root, "locked", ".openpets-zed.lock");
460
+ writeSettings(lockPath, "active\n");
461
+ assert.throws(() => executePlan(lockedPlan), /metadata is invalid/);
462
+ assert.equal(existsSync(lockPath), true);
463
+ rmSync(lockPath);
464
+ // Resolved aliases in stale lock metadata are rejected without changing settings or artifacts.
465
+ const aliasCollisionPath = settingsPath("resolved-path-collision");
466
+ const aliasCollisionSource = JSON.stringify({ theme: "dark" }, null, 2);
467
+ writeSettings(aliasCollisionPath, aliasCollisionSource);
468
+ const aliasCollisionPlan = planZedMcpInstall(aliasCollisionPath, expected);
469
+ assert.equal("targetPath" in aliasCollisionPlan, true);
470
+ if ("targetPath" in aliasCollisionPlan && aliasCollisionPlan.backupPath && aliasCollisionPlan.claimPath) {
471
+ const aliasCollisionLockPath = join(dirname(aliasCollisionPlan.targetPath), ".openpets-zed.lock");
472
+ const aliasCollisionLockTempPath = join(dirname(aliasCollisionPlan.targetPath), ".openpets-zed-lock-resolved-alias.tmp");
473
+ const aliasCollisionTempPath = `${dirname(aliasCollisionPlan.targetPath)}/./.openpets-zed.lock`;
474
+ const aliasCollisionLockSource = "stale lock owner\n";
475
+ writeSettings(aliasCollisionLockTempPath, aliasCollisionLockSource);
476
+ writeInterruptedLock(aliasCollisionPlan, aliasCollisionLockPath, aliasCollisionLockTempPath, true, undefined, aliasCollisionTempPath);
477
+ const originalLockMetadata = readFileSync(aliasCollisionLockPath, "utf8");
478
+ assert.throws(() => executePlan(aliasCollisionPlan), /unsafe temp path/);
479
+ assert.equal(readFileSync(aliasCollisionPath, "utf8"), aliasCollisionSource);
480
+ assert.equal(readFileSync(aliasCollisionLockPath, "utf8"), originalLockMetadata);
481
+ assert.equal(readFileSync(aliasCollisionLockTempPath, "utf8"), aliasCollisionLockSource);
482
+ assert.equal(existsSync(aliasCollisionPlan.tempPath), false);
483
+ assert.equal(existsSync(aliasCollisionPlan.backupPath), false);
484
+ assert.equal(existsSync(aliasCollisionPlan.claimPath), false);
485
+ rmSync(aliasCollisionLockPath, { force: true });
486
+ rmSync(aliasCollisionLockTempPath, { force: true });
487
+ }
488
+ // Disabled managed entries are visible and are never silently re-enabled by install.
489
+ const disabledPath = settingsPath("disabled");
490
+ const disabledSource = JSON.stringify({ context_servers: { openpets: { ...published, enabled: false } } }, null, 2);
491
+ writeSettings(disabledPath, disabledSource);
492
+ const disabledStatus = classifyZedMcpStatus(readZedSettings(disabledPath), disabledPath, expected);
493
+ assert.equal(disabledStatus.status, "disabled");
494
+ assert.equal(disabledStatus.canInstall, false);
495
+ const disabledInstall = planZedMcpInstall(disabledPath, expected);
496
+ assert.equal("ok" in disabledInstall && disabledInstall.ok === false, true);
497
+ assert.equal(readFileSync(disabledPath, "utf8"), disabledSource);
498
+ const disabledReplace = planZedMcpReplace(disabledPath, expected);
499
+ executePlan(disabledReplace);
500
+ const reenabled = parseZedSettings(readFileSync(disabledPath, "utf8"));
501
+ assert.equal(reenabled.ok, true);
502
+ if (reenabled.ok)
503
+ assert.equal(reenabled.value.context_servers.openpets.enabled, true);
504
+ // An interrupted pre-commit transaction rolls back its support files before retrying.
505
+ const interruptedPath = settingsPath("interrupted-before-commit");
506
+ const interruptedSource = JSON.stringify({ theme: "dark", context_servers: { other: { command: "other", args: [] } } }, null, 2);
507
+ writeSettings(interruptedPath, interruptedSource);
508
+ const interruptedPlan = planZedMcpInstall(interruptedPath, expected);
509
+ assert.equal("targetPath" in interruptedPlan, true);
510
+ if ("targetPath" in interruptedPlan && interruptedPlan.backupPath) {
511
+ writeSettings(interruptedPlan.tempPath, interruptedPlan.content);
512
+ writeSettings(interruptedPlan.backupPath, interruptedPlan.sourceContent);
513
+ const interruptedLockTemp = join(dirname(interruptedPlan.targetPath), ".openpets-zed-lock-interrupted.tmp");
514
+ writeSettings(interruptedLockTemp, "stale lock owner");
515
+ writeInterruptedLock(interruptedPlan, join(dirname(interruptedPlan.targetPath), ".openpets-zed.lock"), interruptedLockTemp);
516
+ executePlan(interruptedPlan);
517
+ assert.equal(readFileSync(interruptedPath, "utf8"), interruptedPlan.content);
518
+ assert.equal(existsSync(interruptedPlan.tempPath), false);
519
+ assert.equal(existsSync(interruptedPlan.backupPath), true);
520
+ assert.equal(existsSync(interruptedLockTemp), false);
521
+ assert.equal(existsSync(join(dirname(interruptedPlan.targetPath), ".openpets-zed.lock")), false);
522
+ }
523
+ // An interrupted post-commit transaction keeps the committed target and user backup.
524
+ const committedPath = settingsPath("interrupted-after-commit");
525
+ const committedSource = JSON.stringify({ theme: "dark", context_servers: { other: { command: "other", args: [] } } }, null, 2);
526
+ writeSettings(committedPath, committedSource);
527
+ const committedPlan = planZedMcpInstall(committedPath, expected);
528
+ assert.equal("targetPath" in committedPlan, true);
529
+ if ("targetPath" in committedPlan && committedPlan.backupPath) {
530
+ writeSettings(committedPlan.backupPath, committedPlan.sourceContent);
531
+ writeSettings(committedPath, committedPlan.content);
532
+ const committedLockTemp = join(dirname(committedPlan.targetPath), ".openpets-zed-lock-committed.tmp");
533
+ writeInterruptedLock(committedPlan, join(dirname(committedPlan.targetPath), ".openpets-zed.lock"), committedLockTemp);
534
+ const retryPlan = planZedMcpRemove(committedPath);
535
+ executePlan(retryPlan);
536
+ assert.equal(existsSync(committedPlan.backupPath), true);
537
+ assert.equal(readFileSync(committedPlan.backupPath, "utf8"), committedSource);
538
+ assert.equal(existsSync(join(dirname(committedPlan.targetPath), ".openpets-zed.lock")), false);
539
+ }
540
+ // Recovery must not delete a backup that changed outside the journal.
541
+ const tamperedPath = settingsPath("tampered-recovery-artifact");
542
+ const tamperedSource = JSON.stringify({ theme: "dark" }, null, 2);
543
+ writeSettings(tamperedPath, tamperedSource);
544
+ const tamperedPlan = planZedMcpInstall(tamperedPath, expected);
545
+ assert.equal("targetPath" in tamperedPlan, true);
546
+ if ("targetPath" in tamperedPlan && tamperedPlan.backupPath) {
547
+ writeSettings(tamperedPlan.backupPath, "changed backup\n");
548
+ writeInterruptedLock(tamperedPlan, join(dirname(tamperedPlan.targetPath), ".openpets-zed.lock"), join(dirname(tamperedPlan.targetPath), ".openpets-zed-lock-tampered.tmp"));
549
+ assert.throws(() => executePlan(tamperedPlan), /backup is missing or changed/);
550
+ assert.equal(readFileSync(tamperedPath, "utf8"), tamperedSource);
551
+ assert.equal(readFileSync(tamperedPlan.backupPath, "utf8"), "changed backup\n");
552
+ rmSync(join(dirname(tamperedPlan.targetPath), ".openpets-zed.lock"), { force: true });
553
+ rmSync(tamperedPlan.backupPath, { force: true });
554
+ }
555
+ // The old move-first failure shape restores the original before accepting a fresh plan.
556
+ const movedPath = settingsPath("interrupted-move");
557
+ const movedSource = JSON.stringify({ theme: "dark", context_servers: { other: { command: "other", args: [] } } }, null, 2);
558
+ writeSettings(movedPath, movedSource);
559
+ const movedPlan = planZedMcpInstall(movedPath, expected);
560
+ assert.equal("targetPath" in movedPlan, true);
561
+ if ("targetPath" in movedPlan && movedPlan.backupPath) {
562
+ rmSync(movedPath);
563
+ writeSettings(movedPlan.backupPath, movedPlan.sourceContent);
564
+ writeSettings(movedPlan.tempPath, movedPlan.content);
565
+ writeInterruptedLock(movedPlan, join(dirname(movedPlan.targetPath), ".openpets-zed.lock"), join(dirname(movedPlan.targetPath), ".openpets-zed-lock-moved.tmp"), false);
566
+ const staleMissingPlan = planZedMcpInstall(movedPath, expected);
567
+ assert.equal("targetPath" in staleMissingPlan, true);
568
+ assert.throws(() => executePlan(staleMissingPlan), /changed since this operation was previewed/);
569
+ assert.equal(readFileSync(movedPath, "utf8"), movedSource);
570
+ const freshPlan = planZedMcpInstall(movedPath, expected);
571
+ executePlan(freshPlan);
572
+ assert.equal(classifyZedMcpStatus(readZedSettings(movedPath), movedPath, expected).status, "installed");
573
+ }
574
+ // A stale journal with a withdrawn target restores every support artifact before retrying.
575
+ const withdrawnRecoveryPath = settingsPath("interrupted-withdrawal");
576
+ const withdrawnRecoverySource = JSON.stringify({ theme: "dark", context_servers: { other: { command: "other", args: [] } } }, null, 2);
577
+ writeSettings(withdrawnRecoveryPath, withdrawnRecoverySource);
578
+ const withdrawnRecoveryPlan = planZedMcpInstall(withdrawnRecoveryPath, expected);
579
+ assert.equal("targetPath" in withdrawnRecoveryPlan, true);
580
+ if ("targetPath" in withdrawnRecoveryPlan && withdrawnRecoveryPlan.backupPath && withdrawnRecoveryPlan.claimPath) {
581
+ const withdrawnPath = join(dirname(withdrawnRecoveryPlan.targetPath), ".openpets-zed-withdraw-interrupted.tmp");
582
+ writeSettings(withdrawnRecoveryPlan.backupPath, withdrawnRecoveryPlan.sourceContent);
583
+ writeSettings(withdrawnRecoveryPlan.claimPath, withdrawnRecoveryPlan.sourceContent);
584
+ writeSettings(withdrawnRecoveryPlan.tempPath, withdrawnRecoveryPlan.content);
585
+ writeSettings(withdrawnPath, withdrawnRecoveryPlan.sourceContent);
586
+ rmSync(withdrawnRecoveryPath);
587
+ const withdrawnLockPath = join(dirname(withdrawnRecoveryPlan.targetPath), ".openpets-zed.lock");
588
+ writeInterruptedLock(withdrawnRecoveryPlan, withdrawnLockPath, join(dirname(withdrawnRecoveryPlan.targetPath), ".openpets-zed-lock-withdrawn.tmp"), true, withdrawnPath);
589
+ const staleMissingPlan = planZedMcpInstall(withdrawnRecoveryPath, expected);
590
+ assert.equal("targetPath" in staleMissingPlan, true);
591
+ assert.throws(() => executePlan(staleMissingPlan), /changed since this operation was previewed/);
592
+ assert.equal(readFileSync(withdrawnRecoveryPath, "utf8"), withdrawnRecoverySource);
593
+ assert.equal(existsSync(withdrawnRecoveryPlan.backupPath), false);
594
+ assert.equal(existsSync(withdrawnRecoveryPlan.claimPath), false);
595
+ assert.equal(existsSync(withdrawnPath), false);
596
+ assert.equal(existsSync(withdrawnRecoveryPlan.tempPath), false);
597
+ assert.equal(existsSync(withdrawnLockPath), false);
598
+ const freshPlan = planZedMcpInstall(withdrawnRecoveryPath, expected);
599
+ executePlan(freshPlan);
600
+ }
601
+ // An active owner is never mistaken for a stale lock or overwritten.
602
+ const heldPath = settingsPath("held-lock");
603
+ const heldSource = JSON.stringify({ theme: "dark" }, null, 2);
604
+ writeSettings(heldPath, heldSource);
605
+ const heldPlan = planZedMcpInstall(heldPath, expected);
606
+ assert.equal("targetPath" in heldPlan, true);
607
+ if ("targetPath" in heldPlan) {
608
+ const fs = createRequire(import.meta.url)("node:fs");
609
+ const originalLinkSync = fs.linkSync;
610
+ let nestedAttempted = false;
611
+ fs.linkSync = ((source, destination) => {
612
+ if (!nestedAttempted && source === heldPlan.tempPath && destination === heldPlan.targetPath) {
613
+ nestedAttempted = true;
614
+ assert.throws(() => executePlan(heldPlan), /EEXIST/);
615
+ }
616
+ return originalLinkSync(source, destination);
617
+ });
618
+ syncBuiltinESMExports();
619
+ try {
620
+ executePlan(heldPlan);
621
+ }
622
+ finally {
623
+ fs.linkSync = originalLinkSync;
624
+ syncBuiltinESMExports();
625
+ }
626
+ assert.equal(nestedAttempted, true);
627
+ assert.equal(readFileSync(heldPath, "utf8"), heldPlan.content);
628
+ }
629
+ // A live lock owned by another process is never recovered.
630
+ const foreignPath = settingsPath("foreign-live-lock");
631
+ writeSettings(foreignPath, JSON.stringify({ theme: "dark" }, null, 2));
632
+ const foreignPlan = planZedMcpInstall(foreignPath, expected);
633
+ assert.equal("targetPath" in foreignPlan, true);
634
+ if ("targetPath" in foreignPlan) {
635
+ const foreignOwner = spawn(process.execPath, ["-e", "setTimeout(() => {}, 10000)"], { stdio: "ignore" });
636
+ const foreignLockPath = join(dirname(foreignPlan.targetPath), ".openpets-zed.lock");
637
+ try {
638
+ if (foreignOwner.pid === undefined)
639
+ throw new Error("Failed to start foreign lock owner.");
640
+ writeInterruptedLock(foreignPlan, foreignLockPath, join(dirname(foreignPlan.targetPath), ".openpets-zed-lock-foreign.tmp"));
641
+ const foreignLock = JSON.parse(readFileSync(foreignLockPath, "utf8"));
642
+ writeSettings(foreignLockPath, JSON.stringify({ ...foreignLock, pid: foreignOwner.pid }));
643
+ assert.throws(() => executePlan(foreignPlan), /EEXIST/);
644
+ assert.equal(readFileSync(foreignPath, "utf8"), foreignPlan.sourceContent);
645
+ assert.equal(existsSync(foreignLockPath), true);
646
+ }
647
+ finally {
648
+ foreignOwner.kill();
649
+ rmSync(foreignLockPath, { force: true });
650
+ }
651
+ }
652
+ // A non-OpenPets server occupying the key is a conflict and is not overwritten by install.
653
+ const conflictPath = settingsPath("conflict");
654
+ const conflictSource = JSON.stringify({ context_servers: { openpets: { command: "custom-server", args: ["serve"] } } }, null, 2);
655
+ writeSettings(conflictPath, conflictSource);
656
+ assert.equal(classifyZedMcpStatus(readZedSettings(conflictPath), conflictPath, expected).status, "conflict");
657
+ const conflictPlan = planZedMcpInstall(conflictPath, expected);
658
+ assert.equal("ok" in conflictPlan && conflictPlan.ok === false, true);
659
+ assert.equal(readFileSync(conflictPath, "utf8"), conflictSource);
660
+ const malformedEntryPath = settingsPath("malformed-entry");
661
+ writeSettings(malformedEntryPath, JSON.stringify({ context_servers: { openpets: null } }, null, 2));
662
+ assert.equal(classifyZedMcpStatus(readZedSettings(malformedEntryPath), malformedEntryPath, expected).status, "invalid");
663
+ // Malformed JSONC is invalid and cannot produce a write plan.
664
+ const malformedPath = settingsPath("malformed");
665
+ writeSettings(malformedPath, "{\n \"context_servers\": {\n");
666
+ assert.equal(classifyZedMcpStatus(readZedSettings(malformedPath), malformedPath, expected).status, "invalid");
667
+ const malformedPlan = planZedMcpInstall(malformedPath, expected);
668
+ assert.equal("ok" in malformedPlan && malformedPlan.ok === false, true);
669
+ assert.equal(readFileSync(malformedPath, "utf8"), "{\n \"context_servers\": {\n");
670
+ // Unsafe/symlink targets are rejected before any mutation.
671
+ const unsafePath = `${root}\\..\\unsafe\\settings.json`;
672
+ const unsafeRead = readZedSettings(unsafePath);
673
+ assert.equal(unsafeRead.ok, false);
674
+ if (!unsafeRead.ok)
675
+ assert.equal(unsafeRead.reason, "unsafe-path");
676
+ const symlinkDir = join(root, "symlinks");
677
+ mkdirSync(symlinkDir);
678
+ const realSettings = join(symlinkDir, "real.json");
679
+ const linkedSettings = join(symlinkDir, "linked.json");
680
+ writeSettings(realSettings, "{}");
681
+ const realParent = join(symlinkDir, "real-parent");
682
+ const linkedParent = join(symlinkDir, "linked-parent");
683
+ mkdirSync(realParent);
684
+ if (tryCreateSymlink(realSettings, linkedSettings) && tryCreateSymlink(realParent, linkedParent)) {
685
+ const symlinkRead = readZedSettings(linkedSettings);
686
+ assert.equal(symlinkRead.ok, false);
687
+ if (!symlinkRead.ok)
688
+ assert.equal(symlinkRead.reason, "symlink");
689
+ const linkedParentPath = join(linkedParent, "settings.json");
690
+ const linkedParentPlan = planZedMcpInstall(linkedParentPath, expected);
691
+ assert.equal("ok" in linkedParentPlan && linkedParentPlan.ok === false, true);
692
+ }
693
+ const directoryPath = join(symlinkDir, "directory-settings.json");
694
+ mkdirSync(directoryPath);
695
+ const directoryRead = readZedSettings(directoryPath);
696
+ assert.equal(directoryRead.ok, false);
697
+ if (!directoryRead.ok)
698
+ assert.equal(directoryRead.reason, "not-regular");
699
+ const oversizedPath = settingsPath("oversized");
700
+ writeSettings(oversizedPath, `{"value":"${"x".repeat(maxZedSettingsBytes)}"}`);
701
+ const oversizedRead = readZedSettings(oversizedPath);
702
+ assert.equal(oversizedRead.ok, false);
703
+ if (!oversizedRead.ok)
704
+ assert.equal(oversizedRead.reason, "size");
705
+ // Removal targets only context_servers.openpets and keeps all other content.
706
+ const removePath = settingsPath("remove");
707
+ const removeSource = `{
708
+ // keep this comment
709
+ "otherSetting": true,
710
+ "context_servers": {
711
+ "openpets": ${JSON.stringify(published)},
712
+ "other": { "command": "other", "args": [] },
713
+ },
714
+ }`;
715
+ writeSettings(removePath, removeSource);
716
+ const removePlan = planZedMcpRemove(removePath);
717
+ executePlan(removePlan);
718
+ const removedText = readFileSync(removePath, "utf8");
719
+ assert.match(removedText, /keep this comment/);
720
+ assert.match(removedText, /"otherSetting": true/);
721
+ const removedConfig = parseZedSettings(removedText);
722
+ assert.equal(removedConfig.ok, true);
723
+ if (removedConfig.ok) {
724
+ const servers = removedConfig.value.context_servers;
725
+ assert.equal(servers.openpets, undefined);
726
+ assert.deepEqual(servers.other, { command: "other", args: [] });
727
+ }
728
+ // Published, local, bundled, and unpinned command detection stays explicit.
729
+ assert.equal(isManagedOpenPetsMcpEntry(published), true);
730
+ assert.equal(isManagedOpenPetsMcpEntry(buildZedMcpEntry({ ...expected, commandMode: "local", mcpEntryPath: localEntryPath })), true);
731
+ assert.equal(isManagedOpenPetsMcpEntry(customLocalEntry), true);
732
+ assert.equal(isManagedOpenPetsMcpEntry({ command: "npx", args: ["-y", "@open-pets/mcp@latest"] }), false);
733
+ assert.equal(isManagedOpenPetsMcpEntry({ command: customNodeCommand, args: [join(root, "not-openpets.js")] }), false);
734
+ assert.equal(isManagedOpenPetsMcpEntry({ command: "node", args: ["relative/packages/mcp/dist/index.js"] }), false);
735
+ assert.equal(isManagedOpenPetsMcpEntry({ command: customNodeCommand, args: [localEntryPath, "--pet", "bad/pet"] }), false);
736
+ // Direct JSONC edits reject malformed input without returning a mutation.
737
+ assert.equal(typeof updateZedSettingsText(`{ "theme": "dark", }`, ["context_servers", "openpets"], published), "string");
738
+ const invalidEdit = updateZedSettingsText("{", ["context_servers", "openpets"], published);
739
+ assert.equal(typeof invalidEdit, "object");
740
+ if (typeof invalidEdit !== "string")
741
+ assert.equal(invalidEdit.ok, false);
742
+ console.error("Zed validation passed.");
743
+ }
744
+ finally {
745
+ rmSync(root, { recursive: true, force: true });
746
+ }
747
+ //# sourceMappingURL=check-zed.js.map