diff --git a/docs/reference/file-formats.md b/docs/reference/file-formats.md index 74e7e449b..1a4db2716 100644 --- a/docs/reference/file-formats.md +++ b/docs/reference/file-formats.md @@ -1044,7 +1044,9 @@ Code's settings file as `permissions.deny` entries (`Read()`). Reasonix has no ignore file either, so its deny list goes into the `[permissions]` table of the shared `reasonix.toml` (project) / `~/.reasonix/config.toml` (global, via `--global`) as `Read()` entries — the same Claude-Code-style rule syntax the permissions feature writes there. `deny` is used rather than `[sandbox].forbid_read` because deny rules take glob specifiers and are documented as "a hard block in every mode", while `forbid_read` takes absolute paths with no documented glob support. The file is shared with the MCP and permissions features: only `Read(...)` deny entries are replaced, every other table and deny entry is preserved, and the file is never deleted. When the permissions feature also manages the `Read` category its explicit rules win, and the overwrite is warned about. As with the MCP and permissions features, the file is re-serialized on write, so hand-written comments, blank lines, and key ordering in `reasonix.toml` are not preserved. -Kiro reads `.kiroignore` in project scope and `~/.kiro/settings/kiroignore` in user scope. The `kiro`, `kiro-cli`, and `kiro-ide` targets therefore support `--global` for the deprecated ignore feature, as does `reasonix` (whose config file exists in both scopes); the remaining ignore targets are project-only. +Kiro reads `.kiroignore` in project scope and `~/.kiro/settings/kiroignore` in user scope. The `kiro`, `kiro-cli`, and `kiro-ide` targets therefore support `--global` for the deprecated ignore feature, as do `reasonix` and `zed` (whose config files exist in both scopes); the remaining ignore targets are project-only. + +Zed has no ignore file: its deny list is the `private_files` array inside the shared settings file — `.zed/settings.json` in project scope and `~/.config/zed/settings.json` in global scope (`%APPDATA%\Zed\settings.json` on Windows). `private_files` is a worktree setting, and Zed layers default → user → project, so the key is honored in the user settings file too. The array is **owned wholesale by Rulesync**: it is replaced with the patterns from `.rulesync/.aiignore` on every generation, so a pattern deleted there is retracted from `settings.json` instead of surviving forever. When no patterns remain at all, the key is removed rather than written as `[]` — Zed ships a populated default `private_files` (`**/.env*`, `**/*.pem`, …) that any user or project value replaces wholesale, so an empty array would switch its secret redaction off. Every other key in the file — including the MCP `context_servers` and permissions `agent` blocks and unrelated editor settings — is preserved, and the file is never deleted. Goose retired `.gooseignore` upstream ("removed some time ago in favour of other ignore things like gitignore etc" — [goose#10343](https://github.com/aaif-goose/goose/issues/10343)), so rulesync no longer generates it; the replacement guidance is `.gitignore` plus tool permissions. Stale `.gooseignore` files from earlier versions stay gitignored but are not cleaned up automatically. diff --git a/docs/reference/supported-tools.md b/docs/reference/supported-tools.md index bc313f4bc..85da1d8c9 100644 --- a/docs/reference/supported-tools.md +++ b/docs/reference/supported-tools.md @@ -43,7 +43,7 @@ Rulesync supports both **generation** and **import** for All of the major AI cod | Warp | warp | ✅ | ✅ | ✅ 🌏 | ✅ 🌏 | | ✅ 🌏 | | 🌏 | | | Replit | replit | ✅ | | | | | ✅ 🌏 | | | | | Pi Coding Agent | pi | ✅ 🌏 | | | ✅ 🌏 | | ✅ 🌏 | ✅ 🌏 | | | -| Zed | zed | ✅ 🌏 | ✅ | ✅ 🌏 | | | ✅ 🌏 | | ✅ 🌏 | | +| Zed | zed | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | | | ✅ 🌏 | | ✅ 🌏 | | diff --git a/skills/rulesync/file-formats.md b/skills/rulesync/file-formats.md index 0095bdcd1..5f7130801 100644 --- a/skills/rulesync/file-formats.md +++ b/skills/rulesync/file-formats.md @@ -1044,7 +1044,9 @@ Code's settings file as `permissions.deny` entries (`Read()`). Reasonix has no ignore file either, so its deny list goes into the `[permissions]` table of the shared `reasonix.toml` (project) / `~/.reasonix/config.toml` (global, via `--global`) as `Read()` entries — the same Claude-Code-style rule syntax the permissions feature writes there. `deny` is used rather than `[sandbox].forbid_read` because deny rules take glob specifiers and are documented as "a hard block in every mode", while `forbid_read` takes absolute paths with no documented glob support. The file is shared with the MCP and permissions features: only `Read(...)` deny entries are replaced, every other table and deny entry is preserved, and the file is never deleted. When the permissions feature also manages the `Read` category its explicit rules win, and the overwrite is warned about. As with the MCP and permissions features, the file is re-serialized on write, so hand-written comments, blank lines, and key ordering in `reasonix.toml` are not preserved. -Kiro reads `.kiroignore` in project scope and `~/.kiro/settings/kiroignore` in user scope. The `kiro`, `kiro-cli`, and `kiro-ide` targets therefore support `--global` for the deprecated ignore feature, as does `reasonix` (whose config file exists in both scopes); the remaining ignore targets are project-only. +Kiro reads `.kiroignore` in project scope and `~/.kiro/settings/kiroignore` in user scope. The `kiro`, `kiro-cli`, and `kiro-ide` targets therefore support `--global` for the deprecated ignore feature, as do `reasonix` and `zed` (whose config files exist in both scopes); the remaining ignore targets are project-only. + +Zed has no ignore file: its deny list is the `private_files` array inside the shared settings file — `.zed/settings.json` in project scope and `~/.config/zed/settings.json` in global scope (`%APPDATA%\Zed\settings.json` on Windows). `private_files` is a worktree setting, and Zed layers default → user → project, so the key is honored in the user settings file too. The array is **owned wholesale by Rulesync**: it is replaced with the patterns from `.rulesync/.aiignore` on every generation, so a pattern deleted there is retracted from `settings.json` instead of surviving forever. When no patterns remain at all, the key is removed rather than written as `[]` — Zed ships a populated default `private_files` (`**/.env*`, `**/*.pem`, …) that any user or project value replaces wholesale, so an empty array would switch its secret redaction off. Every other key in the file — including the MCP `context_servers` and permissions `agent` blocks and unrelated editor settings — is preserved, and the file is never deleted. Goose retired `.gooseignore` upstream ("removed some time ago in favour of other ignore things like gitignore etc" — [goose#10343](https://github.com/aaif-goose/goose/issues/10343)), so rulesync no longer generates it; the replacement guidance is `.gitignore` plus tool permissions. Stale `.gooseignore` files from earlier versions stay gitignored but are not cleaned up automatically. diff --git a/skills/rulesync/supported-tools.md b/skills/rulesync/supported-tools.md index bc313f4bc..85da1d8c9 100644 --- a/skills/rulesync/supported-tools.md +++ b/skills/rulesync/supported-tools.md @@ -43,7 +43,7 @@ Rulesync supports both **generation** and **import** for All of the major AI cod | Warp | warp | ✅ | ✅ | ✅ 🌏 | ✅ 🌏 | | ✅ 🌏 | | 🌏 | | | Replit | replit | ✅ | | | | | ✅ 🌏 | | | | | Pi Coding Agent | pi | ✅ 🌏 | | | ✅ 🌏 | | ✅ 🌏 | ✅ 🌏 | | | -| Zed | zed | ✅ 🌏 | ✅ | ✅ 🌏 | | | ✅ 🌏 | | ✅ 🌏 | | +| Zed | zed | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | | | ✅ 🌏 | | ✅ 🌏 | | diff --git a/src/constants/zed-paths.ts b/src/constants/zed-paths.ts index 0906658b7..04329b2cb 100644 --- a/src/constants/zed-paths.ts +++ b/src/constants/zed-paths.ts @@ -11,6 +11,15 @@ export const ZED_GLOBAL_WIN32_DIR = join("AppData", "Roaming", "Zed"); export function getZedGlobalDir(): string { return process.platform === "win32" ? ZED_GLOBAL_WIN32_DIR : ZED_GLOBAL_DIR; } + +/** + * The global config dir of the OTHER platform. `getZedGlobalDir()` resolves one + * spelling per platform, but the shared-write derivation (and the gateway + * ownership table it is checked against) must know both on every platform. + */ +export function getZedOtherPlatformGlobalDir(): string { + return process.platform === "win32" ? ZED_GLOBAL_DIR : ZED_GLOBAL_WIN32_DIR; +} export const ZED_SETTINGS_FILE_NAME = "settings.json"; export const ZED_RULE_FILE_NAME = ".rules"; export const ZED_GLOBAL_RULE_FILE_NAME = "AGENTS.md"; diff --git a/src/e2e/e2e-ignore.spec.ts b/src/e2e/e2e-ignore.spec.ts index 8ab919434..b963227bb 100644 --- a/src/e2e/e2e-ignore.spec.ts +++ b/src/e2e/e2e-ignore.spec.ts @@ -14,6 +14,7 @@ import { REASONIX_PROJECT_PERMISSIONS_FILE_NAME, } from "../constants/reasonix-paths.js"; import { RULESYNC_AIIGNORE_RELATIVE_FILE_PATH } from "../constants/rulesync-paths.js"; +import { getZedGlobalDir, ZED_SETTINGS_FILE_NAME } from "../constants/zed-paths.js"; import { IgnoreProcessor } from "../features/ignore/ignore-processor.js"; import { fileExists, readFileContent, writeFileContent } from "../utils/file.js"; import { @@ -297,6 +298,10 @@ describe("E2E: ignore (global mode)", () => { target: "reasonix", outputPath: join(REASONIX_GLOBAL_DIR, REASONIX_GLOBAL_PERMISSIONS_FILE_NAME), }, + { + target: "zed", + outputPath: join(getZedGlobalDir(), ZED_SETTINGS_FILE_NAME), + }, ] as const; it("global matrix must cover every native global ignore tool target", () => { @@ -330,6 +335,26 @@ describe("E2E: ignore (global mode)", () => { }, ); + it("should import the Zed user-level private_files list", async () => { + const projectDir = getProjectDir(); + const homeDir = getHomeDir(); + await writeFileContent( + join(homeDir, getZedGlobalDir(), ZED_SETTINGS_FILE_NAME), + JSON.stringify({ theme: "One Dark", private_files: ["private/", "*.pem"] }, null, 2), + ); + + await runImport({ + target: "zed", + features: "ignore", + global: true, + env: { HOME_DIR: homeDir }, + }); + + const imported = await readFileContent(join(projectDir, RULESYNC_AIIGNORE_RELATIVE_FILE_PATH)); + expect(imported).toContain("private/"); + expect(imported).toContain("*.pem"); + }); + it("should import the Kiro CLI user-level ignore file", async () => { const projectDir = getProjectDir(); const homeDir = getHomeDir(); diff --git a/src/features/ignore/ignore-processor.test.ts b/src/features/ignore/ignore-processor.test.ts index d4a9ade5e..5e113e51b 100644 --- a/src/features/ignore/ignore-processor.test.ts +++ b/src/features/ignore/ignore-processor.test.ts @@ -442,6 +442,7 @@ describe("IgnoreProcessor", () => { "kiro-cli", "kiro-ide", "reasonix", + "zed", ]); }); }); diff --git a/src/features/ignore/ignore-processor.ts b/src/features/ignore/ignore-processor.ts index ba4e0b396..178b1b451 100644 --- a/src/features/ignore/ignore-processor.ts +++ b/src/features/ignore/ignore-processor.ts @@ -80,7 +80,13 @@ export const toolIgnoreFactories = new Map ToolIgnoreFactory; diff --git a/src/features/ignore/zed-ignore.test.ts b/src/features/ignore/zed-ignore.test.ts index 451a0d57b..b5b6c8c9b 100644 --- a/src/features/ignore/zed-ignore.test.ts +++ b/src/features/ignore/zed-ignore.test.ts @@ -7,6 +7,7 @@ import { RULESYNC_AIIGNORE_RELATIVE_FILE_PATH, RULESYNC_RELATIVE_DIR_PATH, } from "../../constants/rulesync-paths.js"; +import { getZedGlobalDir, getZedOtherPlatformGlobalDir } from "../../constants/zed-paths.js"; import { setupTestDirectory } from "../../test-utils/test-directories.js"; import { ensureDir, writeFileContent } from "../../utils/file.js"; import { RulesyncIgnore } from "./rulesync-ignore.js"; @@ -126,6 +127,30 @@ describe("ZedIgnore", () => { relativeFilePath: "settings.json", }); }); + + it("should return the platform-aware user config dir in global mode", () => { + const paths = ZedIgnore.getSettablePaths({ global: true }); + + expect(paths).toEqual({ + relativeDirPath: getZedGlobalDir(), + relativeFilePath: "settings.json", + }); + }); + }); + + describe("getExtraSharedWritePaths", () => { + it("should declare no extra paths in project mode", () => { + expect(ZedIgnore.getExtraSharedWritePaths()).toEqual([]); + }); + + it("should declare the other platform's global settings file", () => { + expect(ZedIgnore.getExtraSharedWritePaths({ global: true })).toEqual([ + { + relativeDirPath: getZedOtherPlatformGlobalDir(), + relativeFilePath: "settings.json", + }, + ]); + }); }); describe("isDeletable", () => { @@ -231,7 +256,7 @@ describe("ZedIgnore", () => { expect(jsonValue.private_files).toEqual(["*.log", ".env", "node_modules/**"]); }); - it("should preserve existing private_files while adding new ones", async () => { + it("should replace existing private_files so removed patterns are retracted", async () => { const existingJsonContent = JSON.stringify( { private_files: ["existing.log", "*.log"], @@ -256,7 +281,7 @@ describe("ZedIgnore", () => { }); const jsonValue = JSON.parse(zedIgnore.getFileContent()); - expect(jsonValue.private_files).toEqual(["*.log", "existing.log", "node_modules/**"]); + expect(jsonValue.private_files).toEqual(["*.log", "node_modules/**"]); }); it("should handle patterns with comments and empty lines", async () => { @@ -305,7 +330,7 @@ describe("ZedIgnore", () => { const jsonValue = JSON.parse(zedIgnore.getFileContent()); expect(jsonValue.other).toBe("property"); expect(jsonValue.another).toBe("value"); - expect(jsonValue.private_files).toEqual(["*.log", "secret.txt"]); + expect(jsonValue.private_files).toEqual(["*.log"]); }); it("should remove duplicates and sort patterns", async () => { @@ -333,7 +358,7 @@ describe("ZedIgnore", () => { }); const jsonValue = JSON.parse(zedIgnore.getFileContent()); - expect(jsonValue.private_files).toEqual(["*.log", "a.txt", "b.txt", "z.txt"]); + expect(jsonValue.private_files).toEqual(["*.log", "a.txt", "b.txt"]); }); it("should handle Windows line endings", async () => { @@ -353,6 +378,59 @@ describe("ZedIgnore", () => { expect(jsonValue.private_files).toEqual(["*.log", ".env", "node_modules/**"]); }); + it("should write to the global settings file and preserve unrelated keys in global mode", async () => { + const globalDir = join(testDir, getZedGlobalDir()); + await ensureDir(globalDir); + await writeFileContent( + join(globalDir, "settings.json"), + JSON.stringify({ theme: "One Dark", private_files: ["stale.txt"] }, null, 2), + ); + + const rulesyncIgnore = new RulesyncIgnore({ + relativeDirPath: RULESYNC_RELATIVE_DIR_PATH, + relativeFilePath: RULESYNC_AIIGNORE_RELATIVE_FILE_PATH, + fileContent: "*.log\n.env", + }); + + const zedIgnore = await ZedIgnore.fromRulesyncIgnore({ + outputRoot: testDir, + rulesyncIgnore, + global: true, + }); + + expect(zedIgnore.getRelativeDirPath()).toBe(getZedGlobalDir()); + expect(zedIgnore.getRelativeFilePath()).toBe("settings.json"); + const jsonValue = JSON.parse(zedIgnore.getFileContent()); + expect(jsonValue.theme).toBe("One Dark"); + expect(jsonValue.private_files).toEqual(["*.log", ".env"]); + }); + + it("should remove private_files entirely when no patterns remain", async () => { + const zedDir = join(testDir, ".zed"); + await ensureDir(zedDir); + await writeFileContent( + join(zedDir, "settings.json"), + JSON.stringify({ theme: "One Dark", private_files: ["stale.txt"] }, null, 2), + ); + + const rulesyncIgnore = new RulesyncIgnore({ + relativeDirPath: RULESYNC_RELATIVE_DIR_PATH, + relativeFilePath: RULESYNC_AIIGNORE_RELATIVE_FILE_PATH, + fileContent: "# only a comment\n\n", + }); + + const zedIgnore = await ZedIgnore.fromRulesyncIgnore({ + outputRoot: testDir, + rulesyncIgnore, + }); + + // An empty array would replace Zed's populated default `private_files` + // wholesale and switch its secret redaction off, so the key is dropped. + const jsonValue = JSON.parse(zedIgnore.getFileContent()); + expect(jsonValue).not.toHaveProperty("private_files"); + expect(jsonValue.theme).toBe("One Dark"); + }); + it("should create new JSON file when none exists", async () => { const rulesyncIgnore = new RulesyncIgnore({ relativeDirPath: RULESYNC_RELATIVE_DIR_PATH, @@ -415,6 +493,23 @@ describe("ZedIgnore", () => { expect(zedIgnore.getPatterns()).toEqual(["*.log", "node_modules/**"]); }); + it("should read the global settings file and import its patterns", async () => { + const globalDir = join(testDir, getZedGlobalDir()); + await ensureDir(globalDir); + await writeFileContent( + join(globalDir, "settings.json"), + JSON.stringify({ theme: "One Dark", private_files: ["private/", "*.pem"] }, null, 2), + ); + + const zedIgnore = await ZedIgnore.fromFile({ + outputRoot: testDir, + global: true, + }); + + expect(zedIgnore.getRelativeDirPath()).toBe(getZedGlobalDir()); + expect(zedIgnore.toRulesyncIgnore().getFileContent()).toBe("private/\n*.pem"); + }); + it("should read file with validation enabled by default", async () => { const jsonContent = JSON.stringify( { diff --git a/src/features/ignore/zed-ignore.ts b/src/features/ignore/zed-ignore.ts index 3dbc93e06..2d1529da0 100644 --- a/src/features/ignore/zed-ignore.ts +++ b/src/features/ignore/zed-ignore.ts @@ -1,8 +1,12 @@ import { join } from "node:path"; -import { uniq } from "es-toolkit"; - -import { ZED_DIR, ZED_SETTINGS_FILE_NAME } from "../../constants/zed-paths.js"; +import { + getZedGlobalDir, + getZedOtherPlatformGlobalDir, + ZED_DIR, + ZED_SETTINGS_FILE_NAME, +} from "../../constants/zed-paths.js"; +import type { SharedWritePath } from "../../lib/shared-file-derive.js"; import { fileExists, readFileContent } from "../../utils/file.js"; import { applySharedConfigPatch, sharedConfigFileKey } from "../shared/shared-config-gateway.js"; import { RulesyncIgnore } from "./rulesync-ignore.js"; @@ -13,6 +17,7 @@ import { ToolIgnoreFromRulesyncIgnoreParams, ToolIgnoreParams, ToolIgnoreSettablePaths, + ToolIgnoreSettablePathsParams, } from "./tool-ignore.js"; export type ZedIgnoreParams = ToolIgnoreParams; @@ -29,13 +34,30 @@ export class ZedIgnore extends ToolIgnore { this.patterns = jsonValue.private_files ?? []; } - static getSettablePaths(): ToolIgnoreSettablePaths { + static getSettablePaths({ + global = false, + }: ToolIgnoreSettablePathsParams = {}): ToolIgnoreSettablePaths { return { - relativeDirPath: ZED_DIR, + relativeDirPath: global ? getZedGlobalDir() : ZED_DIR, relativeFilePath: ZED_SETTINGS_FILE_NAME, }; } + /** @see getZedOtherPlatformGlobalDir */ + static getExtraSharedWritePaths({ + global = false, + }: ToolIgnoreSettablePathsParams = {}): SharedWritePath[] { + if (!global) { + return []; + } + return [ + { + relativeDirPath: getZedOtherPlatformGlobalDir(), + relativeFilePath: ZED_SETTINGS_FILE_NAME, + }, + ]; + } + /** * ZedIgnore uses settings.json which is a user-managed config file. * It should not be deleted by rulesync. @@ -53,7 +75,9 @@ export class ZedIgnore extends ToolIgnore { const fileContent = rulesyncPatterns.join("\n"); return new RulesyncIgnore({ - outputRoot: this.outputRoot, + // The rulesync source always belongs to the project, even when the + // settings.json it was imported from lives in the user config dir. + outputRoot: ".", relativeDirPath: RulesyncIgnore.getSettablePaths().recommended.relativeDirPath, relativeFilePath: RulesyncIgnore.getSettablePaths().recommended.relativeFilePath, fileContent, @@ -63,6 +87,7 @@ export class ZedIgnore extends ToolIgnore { static async fromRulesyncIgnore({ outputRoot = process.cwd(), rulesyncIgnore, + global = false, }: ToolIgnoreFromRulesyncIgnoreParams): Promise { const fileContent = rulesyncIgnore.getFileContent(); @@ -71,52 +96,56 @@ export class ZedIgnore extends ToolIgnore { .map((line: string) => line.trim()) .filter((line) => line.length > 0 && !line.startsWith("#")); - const filePath = join( - outputRoot, - this.getSettablePaths().relativeDirPath, - this.getSettablePaths().relativeFilePath, - ); + const paths = this.getSettablePaths({ global }); + const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath); const exists = await fileExists(filePath); const existingFileContent = exists ? await readFileContent(filePath) : "{}"; - const existingJsonValue: SettingsJsonValue = JSON.parse(existingFileContent); - const existingPrivateFiles = existingJsonValue.private_files ?? []; - // Merge existing patterns with new ones, removing duplicates and sorting - const mergedPatterns = uniq([...existingPrivateFiles, ...patterns].toSorted()); + // `private_files` is owned wholesale by the ignore feature (declared as + // `replace-owned-keys` in the shared-config gateway), so the generated list + // is authoritative: a pattern deleted from `.rulesync/.aiignore` is + // retracted from settings.json instead of surviving forever. Every other + // key in the file is preserved by the gateway. + // + // With no patterns at all the key is REMOVED rather than written as `[]`: + // Zed's default `private_files` (`**/.env*`, `**/*.pem`, …) is replaced + // wholesale by any value the user or project sets, so an empty array would + // switch its secret redaction off entirely. + const managedPatterns = patterns.length > 0 ? [...new Set(patterns)].toSorted() : undefined; return new ZedIgnore({ outputRoot, - relativeDirPath: this.getSettablePaths().relativeDirPath, - relativeFilePath: this.getSettablePaths().relativeFilePath, + relativeDirPath: paths.relativeDirPath, + relativeFilePath: paths.relativeFilePath, fileContent: applySharedConfigPatch({ - fileKey: sharedConfigFileKey(this.getSettablePaths()), + fileKey: sharedConfigFileKey(paths), feature: "ignore", existingContent: existingFileContent, - patch: { private_files: mergedPatterns }, + patch: { private_files: managedPatterns }, filePath, }), validate: true, + global, }); } static async fromFile({ outputRoot = process.cwd(), validate = true, + global = false, }: ToolIgnoreFromFileParams): Promise { + const paths = this.getSettablePaths({ global }); const fileContent = await readFileContent( - join( - outputRoot, - this.getSettablePaths().relativeDirPath, - this.getSettablePaths().relativeFilePath, - ), + join(outputRoot, paths.relativeDirPath, paths.relativeFilePath), ); return new ZedIgnore({ outputRoot, - relativeDirPath: this.getSettablePaths().relativeDirPath, - relativeFilePath: this.getSettablePaths().relativeFilePath, + relativeDirPath: paths.relativeDirPath, + relativeFilePath: paths.relativeFilePath, fileContent: fileContent, validate, + global, }); } @@ -124,6 +153,7 @@ export class ZedIgnore extends ToolIgnore { outputRoot = process.cwd(), relativeDirPath, relativeFilePath, + global = false, }: ToolIgnoreForDeletionParams): ZedIgnore { return new ZedIgnore({ outputRoot, @@ -131,6 +161,7 @@ export class ZedIgnore extends ToolIgnore { relativeFilePath, fileContent: "{}", validate: false, + global, }); } } diff --git a/src/features/mcp/zed-mcp.ts b/src/features/mcp/zed-mcp.ts index 256d7a181..b712135c2 100644 --- a/src/features/mcp/zed-mcp.ts +++ b/src/features/mcp/zed-mcp.ts @@ -2,9 +2,8 @@ import { join } from "node:path"; import { getZedGlobalDir, + getZedOtherPlatformGlobalDir, ZED_DIR, - ZED_GLOBAL_DIR, - ZED_GLOBAL_WIN32_DIR, ZED_SETTINGS_FILE_NAME, } from "../../constants/zed-paths.js"; import type { SharedWritePath } from "../../lib/shared-file-derive.js"; @@ -194,12 +193,7 @@ export class ZedMcp extends ToolMcp { }; } - /** - * The global settings file of the OTHER platform: `getSettablePaths` resolves - * `~/.config/zed` vs `%APPDATA%\Zed` per platform, but the shared-write - * derivation (and the gateway ownership table it is checked against) must - * know both spellings on every platform. - */ + /** @see getZedOtherPlatformGlobalDir */ static getExtraSharedWritePaths({ global = false, }: { global?: boolean } = {}): SharedWritePath[] { @@ -208,7 +202,7 @@ export class ZedMcp extends ToolMcp { } return [ { - relativeDirPath: process.platform === "win32" ? ZED_GLOBAL_DIR : ZED_GLOBAL_WIN32_DIR, + relativeDirPath: getZedOtherPlatformGlobalDir(), relativeFilePath: ZED_SETTINGS_FILE_NAME, }, ]; diff --git a/src/features/permissions/zed-permissions.ts b/src/features/permissions/zed-permissions.ts index e5827f622..37079cd93 100644 --- a/src/features/permissions/zed-permissions.ts +++ b/src/features/permissions/zed-permissions.ts @@ -4,9 +4,8 @@ import { z } from "zod/mini"; import { getZedGlobalDir, + getZedOtherPlatformGlobalDir, ZED_DIR, - ZED_GLOBAL_DIR, - ZED_GLOBAL_WIN32_DIR, ZED_SETTINGS_FILE_NAME, } from "../../constants/zed-paths.js"; import type { SharedWritePath } from "../../lib/shared-file-derive.js"; @@ -176,12 +175,7 @@ export class ZedPermissions extends ToolPermissions { : { relativeDirPath: ZED_DIR, relativeFilePath: ZED_SETTINGS_FILE_NAME }; } - /** - * The global settings file of the OTHER platform: `getSettablePaths` resolves - * `~/.config/zed` vs `%APPDATA%\Zed` per platform, but the shared-write - * derivation (and the gateway ownership table it is checked against) must - * know both spellings on every platform. - */ + /** @see getZedOtherPlatformGlobalDir */ static getExtraSharedWritePaths({ global = false, }: { global?: boolean } = {}): SharedWritePath[] { @@ -190,7 +184,7 @@ export class ZedPermissions extends ToolPermissions { } return [ { - relativeDirPath: process.platform === "win32" ? ZED_GLOBAL_DIR : ZED_GLOBAL_WIN32_DIR, + relativeDirPath: getZedOtherPlatformGlobalDir(), relativeFilePath: ZED_SETTINGS_FILE_NAME, }, ]; diff --git a/src/features/shared/shared-config-gateway.ts b/src/features/shared/shared-config-gateway.ts index 55361aa63..588c2b97d 100644 --- a/src/features/shared/shared-config-gateway.ts +++ b/src/features/shared/shared-config-gateway.ts @@ -355,11 +355,12 @@ export const SHARED_CONFIG_OWNERSHIP: Readonly { "permissions", ], ".config/zed/settings.json": [ + "ignore", "mcp", "permissions", ], @@ -201,6 +202,7 @@ describe("shared-file write derivation", () => { "permissions", ], "AppData/Roaming/Zed/settings.json": [ + "ignore", "mcp", "permissions", ],