From 459acb37aaa4ae6f8f172a79e9ee620718edb0ff Mon Sep 17 00:00:00 2001 From: Clay Good Date: Fri, 14 Aug 2026 16:42:05 -0500 Subject: [PATCH 1/2] feat(tools): add Zed Agent support --- docs/cli.md | 2 +- docs/commands.md | 2 +- docs/how-commands-work.md | 4 ++-- docs/supported-tools.md | 19 ++++++++++------- docs/troubleshooting.md | 2 +- src/core/config.ts | 1 + src/core/init.ts | 29 +++++++++++++++++++++---- test/cli-e2e/basic.test.ts | 28 +++++++++++++++++++++++++ test/core/init.test.ts | 43 +++++++++++++++++++++++++++++++++++--- 9 files changed, 111 insertions(+), 19 deletions(-) diff --git a/docs/cli.md b/docs/cli.md index d17c6d662f..bb22d59125 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -109,7 +109,7 @@ openspec init [path] [options] The welcome animation is also skipped when the `OPENSPEC_NO_ANIMATION` environment variable is set (any value, including empty), when `NO_COLOR` is set to a non-empty value, or when the OS reduced-motion preference is enabled (macOS Reduce Motion, GNOME animations disabled). -**Supported tool IDs (`--tools`)** — `windsurf` is also accepted, as an alias for `devin`: `amazon-q`, `antigravity`, `auggie`, `bob`, `claude`, `cline`, `command-code`, `codeartsagent`, `codex`, `devin`, `forgecode`, `codebuddy`, `continue`, `costrict`, `crush`, `cursor`, `factory`, `gemini`, `github-copilot`, `hermes`, `iflow`, `junie`, `kilocode`, `kimi`, `kiro`, `lingma`, `minimax-code`, `vibe`, `oh-my-pi`, `opencode`, `pi`, `qoder`, `qwen`, `roocode`, `trae`, `zcode`, `agents` +**Supported tool IDs (`--tools`)** — `windsurf` is also accepted, as an alias for `devin`: `amazon-q`, `antigravity`, `auggie`, `bob`, `claude`, `cline`, `command-code`, `codeartsagent`, `codex`, `devin`, `forgecode`, `codebuddy`, `continue`, `costrict`, `crush`, `cursor`, `factory`, `gemini`, `github-copilot`, `hermes`, `iflow`, `junie`, `kilocode`, `kimi`, `kiro`, `lingma`, `minimax-code`, `vibe`, `oh-my-pi`, `opencode`, `pi`, `qoder`, `qwen`, `roocode`, `trae`, `zed`, `zcode`, `agents` > This list mirrors `AI_TOOLS` in `src/core/config.ts`. See [Supported Tools](supported-tools.md) for each tool's skill and command paths. diff --git a/docs/commands.md b/docs/commands.md index 473df68228..7546dae82d 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -673,7 +673,7 @@ Different AI tools use slightly different command syntax. Use the format that ma |--------------------------|----------------|---------------| | `.../commands/opsx/.*` | `/opsx:propose`, `/opsx:apply` | Claude Code, Gemini CLI, Crush | | `.../opsx-.*` | `/opsx-propose`, `/opsx-apply` | Cursor, Devin Desktop, Copilot (IDE), Trae, Oh My Pi | -| none — skills only | `/openspec-propose`, `/openspec-apply-change` | CodeArts, ForgeCode, Hermes, MiniMax Code, Mistral Vibe, shared `.agents` | +| none — skills only | `/openspec-propose`, `/openspec-apply-change` | CodeArts, ForgeCode, Hermes, MiniMax Code, Mistral Vibe, Zed Agent, shared `.agents` | | none — Kimi Code | `/skill:openspec-propose` | Kimi Code | | none — Codex CLI | `$openspec-propose` | Codex | diff --git a/docs/how-commands-work.md b/docs/how-commands-work.md index 328eca6090..17d92e7ee9 100644 --- a/docs/how-commands-work.md +++ b/docs/how-commands-work.md @@ -78,7 +78,7 @@ The intent is identical everywhere. The spelling follows the file your tool load | `.../commands/opsx/.*` | `/opsx:propose` | Claude Code, Gemini CLI, Crush | | `.../opsx-.*` | `/opsx-propose` | Cursor, GitHub Copilot (IDE), Devin Desktop, Trae, Oh My Pi | | `.amazonq/prompts/opsx-.md` | `@opsx-propose` | Amazon Q Developer | -| none — skills only | `/openspec-propose` | CodeArts, ForgeCode, Hermes, Mistral Vibe, shared `.agents` | +| none — skills only | `/openspec-propose` | CodeArts, ForgeCode, Hermes, Mistral Vibe, Zed Agent, shared `.agents` | | none — Kimi Code | `/skill:openspec-propose` | Kimi Code | | none — Codex CLI | `$openspec-propose` | Codex | @@ -114,7 +114,7 @@ See [Supported Tools](supported-tools.md) for the exact paths per tool, and [Mig Quick checks, fastest first: -1. **Type a slash in your AI chat.** Start typing `/opsx` and watch for autocomplete suggestions. If they appear, you're set. On a skills-only tool (Codex, Kimi Code, CodeArts, ForgeCode, Hermes, Mistral Vibe, or the shared `.agents` target) `/opsx` never completes even on a healthy install — try the skill name from the table above instead. +1. **Type a slash in your AI chat.** Start typing `/opsx` and watch for autocomplete suggestions. If they appear, you're set. On a skills-only tool (Codex, Kimi Code, CodeArts, ForgeCode, Hermes, Mistral Vibe, Zed Agent, or the shared `.agents` target) `/opsx` never completes even on a healthy install — try the skill name from the table above instead. 2. **Look for the files.** For Claude Code, check that `.claude/skills/` contains `openspec-*` folders. Other tools use their own directories ([Supported Tools](supported-tools.md) lists them). 3. **Re-run setup.** From your project root, run `openspec update`. This regenerates the skill and command files for whatever tools you configured. 4. **Restart your assistant.** Many tools scan for skills and commands at startup, so a fresh window can be the missing step. diff --git a/docs/supported-tools.md b/docs/supported-tools.md index 1ca52938b1..d235482893 100644 --- a/docs/supported-tools.md +++ b/docs/supported-tools.md @@ -33,7 +33,7 @@ way it loads the file OpenSpec wrote. Find your tool's command path in the | `.../opsx-.*` — the filename is the command | `/opsx-` | Every other tool with generated command files, except Amazon Q and Devin | | `.devin/workflows/opsx-.md` — read by only one of Devin's two agents | `/opsx-` on Devin Desktop, `/openspec-` on Devin Local | Devin Desktop\*\*\*\* | | `.amazonq/prompts/opsx-.md` — a prompt, not a command | `@opsx-` | Amazon Q Developer | -| none — skills only | `/openspec-` | CodeArts, ForgeCode, Hermes, MiniMax Code, Mistral Vibe, shared `.agents` | +| none — skills only | `/openspec-` | CodeArts, ForgeCode, Hermes, MiniMax Code, Mistral Vibe, Zed Agent, shared `.agents` | | none — Kimi Code | `/skill:openspec-` | Kimi Code | | none — Codex CLI | `$openspec-` | Codex ([`/openspec-` is not recognized](https://github.com/openai/codex/issues/11817)) | @@ -100,6 +100,7 @@ to read the hint. | [Rovo Dev CLI](https://support.atlassian.com/rovo/docs/use-rovo-dev-cli/) (`rovodev`) | `.rovodev/skills/openspec-*/SKILL.md` | Not generated. Rovo has no slash-command surface — it matches skills automatically or by prompt (e.g. "use the openspec-propose skill"); `/skills` only manages them. Generated content references skills by name, never as `/openspec-*` commands. | | [Zoo Code](https://github.com/Zoo-Code-Org/Zoo-Code) (`roocode`) | `.roo/skills/openspec-*/SKILL.md` | `.roo/commands/opsx-.md` | | Trae (`trae`) | `.trae/skills/openspec-*/SKILL.md` | `.trae/commands/opsx-.md` | +| [Zed Agent](https://zed.dev/docs/ai/skills) (`zed`) | `.agents/skills/openspec-*/SKILL.md` | Not generated (skills-only; use `/openspec-*` or `@openspec-*`) | | ZCode (`zcode`) | `.zcode/skills/openspec-*/SKILL.md` | `.zcode/commands/opsx/.md` | | Shared `.agents` skills (`agents`) | `.agents/skills/openspec-*/SKILL.md` | Not generated (no command adapter; use skill-based `/openspec-*` invocations) | @@ -145,8 +146,8 @@ shared root many agent tools read, instead of a tool-specific directory. | Your tool isn't listed yet but reads `.agents/skills` | `agents` | Selecting it alongside a tool-specific ID is fine; each normally writes to its -own root. Codex is the exception because it uses the same canonical `.agents` -root. If both `codex` and `agents` are selected, OpenSpec keeps one +own root. Codex and Zed Agent are the exceptions because they use the same canonical +`.agents` root. If Codex is selected with Zed or `agents`, OpenSpec keeps one Codex-led tree. Its handoffs name both `$openspec-*` for Codex and `/openspec-*` for other agents, so `--tools all` and existing multi-agent setups keep working without two writers overwriting the same files. @@ -168,10 +169,14 @@ Two things to know: If your root `AGENTS.md` still carries OpenSpec marker blocks from an older version, `openspec update` strips them — see the [Migration Guide](migration-guide.md). -Because `.agents/skills/` is shared, it is worth knowing what OpenSpec claims there: +Zed support here is for the built-in Zed Agent. Zed External Agents and Terminal +Threads use their own integrations. + +Because `.agents/skills/` is shared by Codex, Zed Agent, and the vendor-neutral target, +it is worth knowing what OpenSpec claims there: it writes, refreshes, and removes only the `openspec-*` skill directories for your -selected workflows, plus an `.openspec-target` marker that records whether Codex -or the vendor-neutral target rendered that shared tree. Anything else in that +selected workflows, plus an `.openspec-target` marker that records whether Codex, +Zed Agent, or the vendor-neutral target rendered that shared tree. Anything else in that directory is left alone. Treat the `openspec-*` names and marker as OpenSpec's — edits inside them are replaced on the next `openspec update`, the same as for every other tool. @@ -206,7 +211,7 @@ openspec init --tools none openspec init --profile core ``` -**Available tool IDs (`--tools`)** — `windsurf` is also accepted, as an alias for `devin`: `amazon-q`, `antigravity`, `auggie`, `bob`, `claude`, `cline`, `command-code`, `codeartsagent`, `codex`, `devin`, `forgecode`, `codebuddy`, `continue`, `costrict`, `crush`, `cursor`, `factory`, `gemini`, `github-copilot`, `hermes`, `iflow`, `junie`, `kilocode`, `kimi`, `kiro`, `lingma`, `minimax-code`, `vibe`, `oh-my-pi`, `opencode`, `pi`, `qoder`, `qwen`, `roocode`, `trae`, `zcode`, `agents` +**Available tool IDs (`--tools`)** — `windsurf` is also accepted, as an alias for `devin`: `amazon-q`, `antigravity`, `auggie`, `bob`, `claude`, `cline`, `command-code`, `codeartsagent`, `codex`, `devin`, `forgecode`, `codebuddy`, `continue`, `costrict`, `crush`, `cursor`, `factory`, `gemini`, `github-copilot`, `hermes`, `iflow`, `junie`, `kilocode`, `kimi`, `kiro`, `lingma`, `minimax-code`, `vibe`, `oh-my-pi`, `opencode`, `pi`, `qoder`, `qwen`, `roocode`, `trae`, `zed`, `zcode`, `agents` ## Workflow-Dependent Installation diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 1097aaadc0..f35ada883a 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -59,7 +59,7 @@ If `/opsx:propose` (or your tool's equivalent) doesn't appear or doesn't do anyt 5. **Check you initialized this project.** Skills are written per project. If you cloned a repo or switched folders, run `openspec init` (or `openspec update`) there. -6. **Confirm your tool supports command files.** Codex, CodeArts, ForgeCode, Hermes, Kimi Code, Mistral Vibe and the shared `.agents` target don't get generated `opsx-*` command files; they use skill-based invocations instead, so `/opsx` will never autocomplete for them. Type `$openspec-propose` in Codex, `/skill:openspec-propose` in Kimi Code, and `/openspec-propose` in the rest. The shared `.agents` target is vendor-neutral, so `/openspec-propose` is the common form rather than a guaranteed one — if your assistant does not answer to it, check its own docs for how it invokes a skill. Amazon Q does get command files, but loads them into its prompt library rather than its slash menu — type `@opsx-propose` there, not `/opsx`. Every tool's form is listed in [How To Invoke](supported-tools.md#how-to-invoke). +6. **Confirm your tool supports command files.** Codex, CodeArts, ForgeCode, Hermes, Kimi Code, Mistral Vibe, Zed Agent, and the shared `.agents` target don't get generated `opsx-*` command files; they use skill-based invocations instead, so `/opsx` will never autocomplete for them. Type `$openspec-propose` in Codex, `/skill:openspec-propose` in Kimi Code, and `/openspec-propose` in the rest. The shared `.agents` target is vendor-neutral, so `/openspec-propose` is the common form rather than a guaranteed one — if your assistant does not answer to it, check its own docs for how it invokes a skill. Amazon Q does get command files, but loads them into its prompt library rather than its slash menu — type `@opsx-propose` there, not `/opsx`. Every tool's form is listed in [How To Invoke](supported-tools.md#how-to-invoke). ## Working with changes diff --git a/src/core/config.ts b/src/core/config.ts index 78dfac3074..7ee1687a3e 100644 --- a/src/core/config.ts +++ b/src/core/config.ts @@ -74,6 +74,7 @@ export const AI_TOOLS: AIToolOption[] = [ { name: 'Rovo Dev CLI', value: 'rovodev', available: true, successLabel: 'Rovo Dev CLI', skillsDir: '.rovodev', detectionPaths: ['.rovodev/skills', '.rovodev'] }, { name: 'Zoo Code', value: 'roocode', available: true, successLabel: 'Zoo Code', skillsDir: '.roo', requiresIdeRestart: true }, { name: 'Trae', value: 'trae', available: true, successLabel: 'Trae', skillsDir: '.trae', requiresIdeRestart: true }, + { name: 'Zed Agent', value: 'zed', available: true, successLabel: 'Zed Agent', skillsDir: '.agents', detectionPaths: ['.agents/skills'] }, { name: 'ZCode', value: 'zcode', available: true, successLabel: 'ZCode', skillsDir: '.zcode' }, // Vendor-neutral target for assistants that read the shared `.agents` root. // Detection keys off `.agents/skills` rather than the bare root: frameworks use diff --git a/src/core/init.ts b/src/core/init.ts index 3684d6a04f..fcafde4ad3 100644 --- a/src/core/init.ts +++ b/src/core/init.ts @@ -751,13 +751,34 @@ export class InitCommand { ): ValidatedInitTool[] { const validatedTools: ValidatedInitTool[] = []; - const reconciledToolIds = toolIds.includes('codex') && toolIds.includes('agents') - ? toolIds.filter((toolId) => toolId !== 'agents') + const sharedAgentsTargets = ['codex', 'zed', 'agents']; + const selectedSharedTargets = sharedAgentsTargets.filter((toolId) => toolIds.includes(toolId)); + // A Codex-rendered tree already serves Zed. Keep it when Zed is added later + // so Codex users do not lose the `$openspec-*` references they require. + const preserveConfiguredCodex = selectedSharedTargets.includes('zed') && + toolStates.get('codex')?.configured; + const sharedTargetCandidates = preserveConfiguredCodex + ? [...new Set([...selectedSharedTargets, 'codex'])] + : selectedSharedTargets; + const sharedTargetOwner = sharedTargetCandidates.includes('codex') + ? 'codex' + : selectedSharedTargets.includes('zed') + ? 'zed' + : selectedSharedTargets[0]; + const firstSharedIndex = toolIds.findIndex((id) => sharedAgentsTargets.includes(id)); + const reconciledToolIds = sharedTargetCandidates.length > 1 + ? toolIds.flatMap((toolId, index) => { + if (!sharedAgentsTargets.includes(toolId)) return [toolId]; + return index === firstSharedIndex && sharedTargetOwner ? [sharedTargetOwner] : []; + }) : toolIds; - if (reconciledToolIds.length !== toolIds.length) { + if ( + reconciledToolIds.length !== toolIds.length || + reconciledToolIds.some((toolId, index) => toolId !== toolIds[index]) + ) { console.log( chalk.dim( - 'Codex and agents share .agents/skills; writing one tree with Codex and generic skill references.' + `Codex, Zed, and agents share .agents/skills; writing one tree for ${sharedTargetOwner}.` ) ); } diff --git a/test/cli-e2e/basic.test.ts b/test/cli-e2e/basic.test.ts index 81eff84388..e6d5d2d012 100644 --- a/test/cli-e2e/basic.test.ts +++ b/test/cli-e2e/basic.test.ts @@ -185,6 +185,34 @@ describe('openspec CLI e2e basics', () => { expect(await fileExists(skillPath)).toBe(true); }); + it('initializes with --tools zed option', async () => { + const projectDir = await prepareFixture('tmp-init'); + const emptyProjectDir = path.join(projectDir, '..', 'empty-project'); + await fs.mkdir(emptyProjectDir, { recursive: true }); + + const result = await runCLI(['init', '--tools', 'zed'], { cwd: emptyProjectDir }); + expect(result.exitCode).toBe(0); + expect(result.stdout).toContain('OpenSpec Setup Complete'); + expect(result.stdout).toContain('Zed Agent'); + + const skillPath = path.join(emptyProjectDir, '.agents', 'skills', 'openspec-explore', 'SKILL.md'); + expect(await fileExists(skillPath)).toBe(true); + expect(await fs.readFile( + path.join(emptyProjectDir, '.agents', 'skills', '.openspec-target'), + 'utf-8' + )).toBe('zed\n'); + + const updateResult = await runCLI(['update'], { cwd: emptyProjectDir }); + expect(updateResult.exitCode).toBe(0); + expect(await fs.readFile( + path.join(emptyProjectDir, '.agents', 'skills', '.openspec-target'), + 'utf-8' + )).toBe('zed\n'); + const updatedSkill = await fs.readFile(skillPath, 'utf-8'); + expect(updatedSkill).toContain('/openspec-explore'); + expect(updatedSkill).not.toContain('$openspec-explore'); + }); + it('initializes with --tools none option', async () => { const projectDir = await prepareFixture('tmp-init'); const emptyProjectDir = path.join(projectDir, '..', 'empty-project'); diff --git a/test/core/init.test.ts b/test/core/init.test.ts index 42a60a79f6..83c0f1e8dd 100644 --- a/test/core/init.test.ts +++ b/test/core/init.test.ts @@ -717,8 +717,8 @@ describe('InitCommand', () => { } ); - it('should reconcile Codex and agents to one tree both consumers can invoke', async () => { - const initCommand = new InitCommand({ tools: 'codex,agents', force: true }); + it('should reconcile Codex, Zed, and agents to one tree all consumers can invoke', async () => { + const initCommand = new InitCommand({ tools: 'codex,zed,agents', force: true }); await initCommand.execute(testDir); const skillsDir = path.join(testDir, '.agents', 'skills'); @@ -734,12 +734,27 @@ describe('InitCommand', () => { .flat() .map(String); expect(logCalls.some((entry) => entry.includes('Created: Codex'))).toBe(true); + expect(logCalls.some((entry) => entry.includes('Created: Zed'))).toBe(false); expect(logCalls.some((entry) => entry.includes('Shared .agents skills'))).toBe(false); expect( - logCalls.some((entry) => entry.includes('writing one tree with Codex and generic')) + logCalls.some((entry) => entry.includes('writing one tree for codex')) ).toBe(true); }); + it('should keep a configured Codex tree compatible when Zed is added later', async () => { + await new InitCommand({ tools: 'codex', force: true }).execute(testDir); + await new InitCommand({ tools: 'zed', force: true }).execute(testDir); + + const skillsDir = path.join(testDir, '.agents', 'skills'); + const proposeSkill = await fs.readFile( + path.join(skillsDir, 'openspec-propose', 'SKILL.md'), + 'utf-8' + ); + expect(proposeSkill).toContain('$openspec-apply-change'); + expect(proposeSkill).toContain('/openspec-apply-change'); + expect(await fs.readFile(path.join(skillsDir, '.openspec-target'), 'utf-8')).toBe('codex\n'); + }); + it('should migrate legacy Codex skills only after init writes their replacements', async () => { await new InitCommand({ tools: 'codex', force: true }).execute(testDir); await fs.rename(path.join(testDir, '.agents'), path.join(testDir, '.codex')); @@ -1382,6 +1397,28 @@ describe('InitCommand - profile and detection features', () => { )).toBe('agents\n'); }); + it('should generate Zed skills in the shared .agents directory', async () => { + const initCommand = new InitCommand({ tools: 'zed,agents', force: true }); + + await initCommand.execute(testDir); + + const skillFile = path.join( + testDir, + '.agents', + 'skills', + 'openspec-apply-change', + 'SKILL.md' + ); + expect(await fileExists(skillFile)).toBe(true); + const skillContent = await fs.readFile(skillFile, 'utf-8'); + expect(skillContent).toContain('/openspec-archive-change'); + expect(skillContent).not.toContain('$openspec-archive-change'); + expect(await fs.readFile( + path.join(testDir, '.agents', 'skills', '.openspec-target'), + 'utf-8' + )).toBe('zed\n'); + }); + it('should preserve legacy Codex prompts without replacement skills during non-interactive init', async () => { const promptDir = path.join(process.env.CODEX_HOME!, 'prompts'); const legacyPrompt = path.join(promptDir, 'opsx-onboard.md'); From 384a6f0252ea32bc54c2dff6588de7f0943c0b4e Mon Sep 17 00:00:00 2001 From: Clay Good Date: Fri, 14 Aug 2026 17:04:51 -0500 Subject: [PATCH 2/2] fix(tools): detect Zed projects --- docs/supported-tools.md | 5 ++++- src/core/config.ts | 2 +- test/cli-e2e/basic.test.ts | 1 + test/core/available-tools.test.ts | 15 +++++++++++++++ 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/docs/supported-tools.md b/docs/supported-tools.md index d235482893..cc4ea8d29c 100644 --- a/docs/supported-tools.md +++ b/docs/supported-tools.md @@ -170,7 +170,10 @@ Two things to know: version, `openspec update` strips them — see the [Migration Guide](migration-guide.md). Zed support here is for the built-in Zed Agent. Zed External Agents and Terminal -Threads use their own integrations. +Threads use their own integrations. Agent Skills require +[Zed v1.4.2](https://github.com/zed-industries/zed/releases/tag/v1.4.2) or newer. +Project-local skills are unavailable in an untrusted worktree until you +[grant trust](https://zed.dev/docs/worktree-trust). Because `.agents/skills/` is shared by Codex, Zed Agent, and the vendor-neutral target, it is worth knowing what OpenSpec claims there: diff --git a/src/core/config.ts b/src/core/config.ts index 7ee1687a3e..813f53c794 100644 --- a/src/core/config.ts +++ b/src/core/config.ts @@ -74,7 +74,7 @@ export const AI_TOOLS: AIToolOption[] = [ { name: 'Rovo Dev CLI', value: 'rovodev', available: true, successLabel: 'Rovo Dev CLI', skillsDir: '.rovodev', detectionPaths: ['.rovodev/skills', '.rovodev'] }, { name: 'Zoo Code', value: 'roocode', available: true, successLabel: 'Zoo Code', skillsDir: '.roo', requiresIdeRestart: true }, { name: 'Trae', value: 'trae', available: true, successLabel: 'Trae', skillsDir: '.trae', requiresIdeRestart: true }, - { name: 'Zed Agent', value: 'zed', available: true, successLabel: 'Zed Agent', skillsDir: '.agents', detectionPaths: ['.agents/skills'] }, + { name: 'Zed Agent', value: 'zed', available: true, successLabel: 'Zed Agent', skillsDir: '.agents', detectionPaths: ['.zed', '.agents/skills'] }, { name: 'ZCode', value: 'zcode', available: true, successLabel: 'ZCode', skillsDir: '.zcode' }, // Vendor-neutral target for assistants that read the shared `.agents` root. // Detection keys off `.agents/skills` rather than the bare root: frameworks use diff --git a/test/cli-e2e/basic.test.ts b/test/cli-e2e/basic.test.ts index e6d5d2d012..424829edc0 100644 --- a/test/cli-e2e/basic.test.ts +++ b/test/cli-e2e/basic.test.ts @@ -194,6 +194,7 @@ describe('openspec CLI e2e basics', () => { expect(result.exitCode).toBe(0); expect(result.stdout).toContain('OpenSpec Setup Complete'); expect(result.stdout).toContain('Zed Agent'); + expect(result.stdout).not.toContain('Restart your IDE'); const skillPath = path.join(emptyProjectDir, '.agents', 'skills', 'openspec-explore', 'SKILL.md'); expect(await fileExists(skillPath)).toBe(true); diff --git a/test/core/available-tools.test.ts b/test/core/available-tools.test.ts index 3678954a32..131a1ec605 100644 --- a/test/core/available-tools.test.ts +++ b/test/core/available-tools.test.ts @@ -129,6 +129,13 @@ describe('available-tools', () => { const toolValues = tools.map((t) => t.value); expect(toolValues).toContain('agents'); expect(toolValues).not.toContain('codex'); + expect(toolValues).not.toContain('zed'); + }); + + it('should detect Zed Agent from its project configuration directory', async () => { + await fs.mkdir(path.join(testDir, '.zed'), { recursive: true }); + + expect(getAvailableTools(testDir).map((tool) => tool.value)).toEqual(['zed']); }); it('should not detect the shared agents target from a bare .agents directory', async () => { @@ -157,6 +164,14 @@ describe('available-tools', () => { const tools = getAvailableTools(testDir); expect(tools.map((tool) => tool.value)).toContain('codex'); expect(tools.map((tool) => tool.value)).not.toContain('agents'); + expect(tools.map((tool) => tool.value)).not.toContain('zed'); + }); + + it('should use the shared-root marker to detect a configured Zed Agent target', async () => { + await fs.mkdir(path.join(testDir, '.agents', 'skills'), { recursive: true }); + await fs.writeFile(path.join(testDir, '.agents', 'skills', '.openspec-target'), 'zed\n'); + + expect(getAvailableTools(testDir).map((tool) => tool.value)).toEqual(['zed']); }); it('should preserve a global tool while reconciling a shared project root', async () => {