From 2371a3cf0543365c1c18e84eba786b1abcb28941 Mon Sep 17 00:00:00 2001 From: Affaan Mustafa Date: Sun, 17 May 2026 07:06:49 -0400 Subject: [PATCH] feat: add zed install target --- .zed/settings.json | 41 +++++++ README.md | 21 +++- .../harness-adapter-compliance.md | 2 +- manifests/install-modules.json | 58 ++++++---- package.json | 1 + schemas/ecc-install-config.schema.json | 3 +- schemas/install-modules.schema.json | 3 +- scripts/install-apply.js | 1 + scripts/lib/harness-adapter-compliance.js | 29 +++-- scripts/lib/install-manifests.js | 9 +- scripts/lib/install-targets/helpers.js | 1 + scripts/lib/install-targets/registry.js | 2 + scripts/lib/install-targets/zed-project.js | 50 +++++++++ tests/docs/harness-adapter-compliance.test.js | 10 +- tests/lib/install-manifests.test.js | 26 +++++ tests/lib/install-targets.test.js | 101 ++++++++++++++++++ tests/lib/selective-install.test.js | 40 +++++++ 17 files changed, 361 insertions(+), 37 deletions(-) create mode 100644 .zed/settings.json create mode 100644 scripts/lib/install-targets/zed-project.js diff --git a/.zed/settings.json b/.zed/settings.json new file mode 100644 index 00000000..0d2c34b7 --- /dev/null +++ b/.zed/settings.json @@ -0,0 +1,41 @@ +{ + "agent": { + "tool_permissions": { + "default": "confirm", + "tools": { + "terminal": { + "default": "confirm", + "always_deny": [ + { + "pattern": "rm\\s+-rf\\s+(/|~)" + }, + { + "pattern": "(^|\\s)(cat|sed|grep|rg)\\s+.*\\.(env|pem|key)(\\s|$)" + } + ], + "always_confirm": [ + { + "pattern": "sudo\\s" + }, + { + "pattern": "(npm|pnpm|yarn|bun)\\s+(install|add|dlx|exec|x)\\b" + }, + { + "pattern": "gh\\s+(auth|api|repo|release|pr|issue)\\b" + } + ] + }, + "edit_file": { + "always_deny": [ + { + "pattern": "\\.env" + }, + { + "pattern": "\\.(pem|key|p12|pfx)$" + } + ] + } + } + } + } +} diff --git a/README.md b/README.md index 046237df..948ac50e 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ Not just configs. A complete system: skills, instincts, memory optimization, continuous learning, security scanning, and research-first development. Production-ready agents, skills, hooks, rules, MCP configurations, and legacy command shims evolved over 10+ months of intensive daily use building real products. -Works across **Claude Code**, **Codex**, **Cursor**, **OpenCode**, **Gemini**, **GitHub Copilot**, and other AI agent harnesses. +Works across **Claude Code**, **Codex**, **Cursor**, **OpenCode**, **Gemini**, **Zed**, **GitHub Copilot**, and other AI agent harnesses. ECC v2.0.0-rc.1 adds the public Hermes operator story on top of that reusable layer: start with the [Hermes setup guide](docs/HERMES-SETUP.md), then review the [rc.1 release notes](docs/releases/2.0.0-rc.1/release-notes.md) and [cross-harness architecture](docs/architecture/cross-harness.md). @@ -429,7 +429,7 @@ python3 ./ecc_dashboard.py ## Cross-Platform Support -This plugin now fully supports **Windows, macOS, and Linux**, alongside tight integration across major IDEs (Cursor, OpenCode, Antigravity) and CLI harnesses. All hooks and scripts have been rewritten in Node.js for maximum compatibility. +This plugin now fully supports **Windows, macOS, and Linux**, alongside tight integration across major IDEs (Cursor, Zed, OpenCode, Antigravity) and CLI harnesses. All hooks and scripts have been rewritten in Node.js for maximum compatibility. ### Package Manager Detection @@ -1150,6 +1150,7 @@ Yes. ECC is cross-platform: - **Antigravity**: Tightly integrated setup for workflows, skills, and flattened rules in `.agent/`. See [Antigravity Guide](docs/ANTIGRAVITY-GUIDE.md). - **JoyCode / CodeBuddy**: Project-local selective install adapters for commands, agents, skills, and flattened rules. See [JoyCode Adapter Guide](docs/JOYCODE-GUIDE.md). - **Qwen CLI**: Home-directory selective install adapter for commands, agents, skills, rules, and Qwen config. See [Qwen CLI Adapter Guide](docs/QWEN-GUIDE.md). +- **Zed**: Project-local selective install adapter for `.zed/settings.json`, flattened rules, commands, agents, and skills. - **Non-native harnesses**: Manual fallback path for Grok and similar interfaces. See [Manual Adaptation Guide](docs/MANUAL-ADAPTATION-GUIDE.md). - **Claude Code**: Native — this is the primary target. @@ -1384,6 +1385,22 @@ ECC ships three sample role configs: --- +## Zed Support + +ECC provides Zed project support through a conservative `.zed` adapter for project-local settings, flattened rules, agents, commands, and skills. + +```bash +./install.sh --profile minimal --target zed +``` + +```powershell +.\install.ps1 --profile minimal --target zed +``` + +The adapter writes ECC-managed files under `.zed/` and keeps BYOK/OpenRouter credentials out of the repo. Configure Zed account or API keys through Zed's own settings UI or your local user settings. + +--- + ## OpenCode Support ECC provides **full OpenCode support** including plugins and hooks. diff --git a/docs/architecture/harness-adapter-compliance.md b/docs/architecture/harness-adapter-compliance.md index d557fa51..4d09a830 100644 --- a/docs/architecture/harness-adapter-compliance.md +++ b/docs/architecture/harness-adapter-compliance.md @@ -41,7 +41,7 @@ The matrix below is rendered from | OpenCode | Adapter-backed | OpenCode package/plugin metadata; shared skills; MCP config; event adapter patterns | Event names, plugin packaging, and command dispatch differ from Claude Code | OpenCode package or plugin surface from this repo | `node tests/scripts/build-opencode.test.js`; `npm run harness:audit -- --format json` | Keep hook logic in shared scripts and adapt only event shape at the edge. | | Cursor | Adapter-backed | Cursor rules; project-local skills; hook adapter; shared scripts | Cursor hook events and rule loading differ from Claude Code | `./install.sh --profile minimal --target cursor` | `node tests/lib/install-targets.test.js`; `npm run harness:audit -- --format json` | Cursor adapters must preserve existing project rules and avoid silent overwrite. | | Gemini | Instruction-backed | Gemini project-local instructions; shared skills; rules; compatibility docs | No full ECC hook parity; ecosystem ports must document drift from upstream ECC | `./install.sh --profile minimal --target gemini` | `node tests/lib/install-targets.test.js` | Treat Gemini ports as ecosystem adapters until validated end to end inside Gemini CLI. | -| Zed-adjacent workflows | Instruction-backed | shared skills; `AGENTS.md` style project instructions; verification loops | Zed agent surfaces vary; no first-party ECC installer is shipped today | Manual copy from shared ECC sources until adapter requirements settle | `npm run harness:audit -- --format json` | Do not claim native Zed support before a real adapter and verification path exist. | +| Zed | Adapter-backed | Zed project settings; flattened project rules; shared skills; commands; agents | Zed external agents and native Agent Panel permissions are not Claude hooks | `./install.sh --profile minimal --target zed` | `node tests/lib/install-targets.test.js`; `npm run harness:audit -- --format json` | Keep project settings conservative and do not copy BYOK/OpenRouter secrets into `.zed/`. | | dmux | Adapter-backed | session snapshots; tmux/worktree orchestration status; handoff exports | dmux is an orchestration runtime, not an install target for skills/rules | `node scripts/session-inspect.js --list-adapters`; dmux session target inspection | `node tests/lib/session-adapters.test.js` | Treat dmux events as session/runtime signals, not as a replacement for repo validation. | | Orca | Reference-only | worktree lifecycle; review state; notification; provider-identity design pressure | No ECC installer or direct adapter today | Use as a comparison target for worktree/session state requirements | `npm run observability:ready` | Do not import product-specific assumptions; convert lessons into ECC event fields. | | Superset | Reference-only | workspace presets; parallel-agent review loops; worktree isolation design pressure | No ECC installer or direct adapter today | Use as a comparison target for workspace preset taxonomy | `npm run observability:ready` | Keep ECC portable; do not require a desktop workspace to get basic value. | diff --git a/manifests/install-modules.json b/manifests/install-modules.json index 61d6ae41..3244a644 100644 --- a/manifests/install-modules.json +++ b/manifests/install-modules.json @@ -14,7 +14,8 @@ "antigravity", "codebuddy", "joycode", - "qwen" + "qwen", + "zed" ], "dependencies": [], "defaultInstall": true, @@ -37,7 +38,8 @@ "codex", "codebuddy", "joycode", - "qwen" + "qwen", + "zed" ], "dependencies": [], "defaultInstall": true, @@ -58,7 +60,8 @@ "opencode", "codebuddy", "joycode", - "qwen" + "qwen", + "zed" ], "dependencies": [], "defaultInstall": true, @@ -96,6 +99,7 @@ ".gemini", ".opencode", ".qwen", + ".zed", "mcp-configs", "scripts/auto-update.js", "scripts/setup-package-manager.js" @@ -109,7 +113,8 @@ "opencode", "codebuddy", "joycode", - "qwen" + "qwen", + "zed" ], "dependencies": [], "defaultInstall": true, @@ -178,7 +183,8 @@ "opencode", "codebuddy", "joycode", - "qwen" + "qwen", + "zed" ], "dependencies": [ "rules-core", @@ -210,7 +216,8 @@ "opencode", "codebuddy", "joycode", - "qwen" + "qwen", + "zed" ], "dependencies": [ "platform-configs" @@ -254,7 +261,8 @@ "opencode", "codebuddy", "joycode", - "qwen" + "qwen", + "zed" ], "dependencies": [ "platform-configs" @@ -292,7 +300,8 @@ "opencode", "codebuddy", "joycode", - "qwen" + "qwen", + "zed" ], "dependencies": [ "workflow-quality" @@ -323,7 +332,8 @@ "opencode", "codebuddy", "joycode", - "qwen" + "qwen", + "zed" ], "dependencies": [ "platform-configs" @@ -356,7 +366,8 @@ "opencode", "codebuddy", "joycode", - "qwen" + "qwen", + "zed" ], "dependencies": [ "platform-configs" @@ -397,7 +408,8 @@ "opencode", "codebuddy", "joycode", - "qwen" + "qwen", + "zed" ], "dependencies": [ "platform-configs" @@ -422,7 +434,8 @@ "opencode", "codebuddy", "joycode", - "qwen" + "qwen", + "zed" ], "dependencies": [ "business-content" @@ -450,7 +463,8 @@ "opencode", "codebuddy", "joycode", - "qwen" + "qwen", + "zed" ], "dependencies": [ "platform-configs" @@ -506,7 +520,8 @@ "opencode", "codebuddy", "joycode", - "qwen" + "qwen", + "zed" ], "dependencies": [ "platform-configs" @@ -549,7 +564,8 @@ "opencode", "codebuddy", "joycode", - "qwen" + "qwen", + "zed" ], "dependencies": [ "platform-configs" @@ -581,7 +597,8 @@ "opencode", "codebuddy", "joycode", - "qwen" + "qwen", + "zed" ], "dependencies": [ "platform-configs" @@ -605,7 +622,8 @@ "opencode", "codebuddy", "joycode", - "qwen" + "qwen", + "zed" ], "dependencies": [ "framework-language", @@ -640,7 +658,8 @@ "opencode", "codebuddy", "joycode", - "qwen" + "qwen", + "zed" ], "dependencies": [ "platform-configs" @@ -665,7 +684,8 @@ "opencode", "codebuddy", "joycode", - "qwen" + "qwen", + "zed" ], "dependencies": [ "platform-configs" diff --git a/package.json b/package.json index 94d000c3..4776e902 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ ".gemini/", ".opencode/", ".qwen/", + ".zed/", ".mcp.json", "AGENTS.md", "VERSION", diff --git a/schemas/ecc-install-config.schema.json b/schemas/ecc-install-config.schema.json index 33d1558c..65131fb8 100644 --- a/schemas/ecc-install-config.schema.json +++ b/schemas/ecc-install-config.schema.json @@ -26,7 +26,8 @@ "opencode", "codebuddy", "joycode", - "qwen" + "qwen", + "zed" ] }, "profile": { diff --git a/schemas/install-modules.schema.json b/schemas/install-modules.schema.json index 012e4868..12573377 100644 --- a/schemas/install-modules.schema.json +++ b/schemas/install-modules.schema.json @@ -55,7 +55,8 @@ "opencode", "codebuddy", "joycode", - "qwen" + "qwen", + "zed" ] } }, diff --git a/scripts/install-apply.js b/scripts/install-apply.js index 5cb32134..9c3cff1c 100755 --- a/scripts/install-apply.js +++ b/scripts/install-apply.js @@ -37,6 +37,7 @@ Targets: codebuddy - Install commands, agents, skills, and flattened rules into ./.codebuddy/ joycode - Install commands, agents, skills, and flattened rules into ./.joycode/ qwen - Install commands, agents, skills, rules, and Qwen config into ~/.qwen/ + zed - Install project settings, commands, agents, skills, and flattened rules into ./.zed/ Options: --profile Resolve and install a manifest profile diff --git a/scripts/lib/harness-adapter-compliance.js b/scripts/lib/harness-adapter-compliance.js index 22f09cb6..11bb363f 100644 --- a/scripts/lib/harness-adapter-compliance.js +++ b/scripts/lib/harness-adapter-compliance.js @@ -170,23 +170,30 @@ const ADAPTER_RECORDS = Object.freeze([ ], }, { - id: 'zed-adjacent', - harness: 'Zed-adjacent workflows', - state: 'Instruction-backed', + id: 'zed', + harness: 'Zed', + state: 'Adapter-backed', supported_assets: [ + 'Zed project settings', + 'flattened project rules', 'shared skills', - '`AGENTS.md` style project instructions', - 'verification loops', + 'commands', + 'agents', ], - unsupported_surfaces: ['Zed agent surfaces vary; no first-party ECC installer is shipped today'], - install_or_onramp: ['Manual copy from shared ECC sources until adapter requirements settle'], - verification_commands: ['`npm run harness:audit -- --format json`'], - risk_notes: ['Do not claim native Zed support before a real adapter and verification path exist.'], - last_verified_at: '2026-05-12', + unsupported_surfaces: ['Zed external agents and native Agent Panel permissions are not Claude hooks'], + install_or_onramp: ['`./install.sh --profile minimal --target zed`'], + verification_commands: [ + '`node tests/lib/install-targets.test.js`', + '`npm run harness:audit -- --format json`', + ], + risk_notes: ['Keep project settings conservative and do not copy BYOK/OpenRouter secrets into `.zed/`.'], + last_verified_at: '2026-05-17', owner: 'ECC maintainers', source_docs: [ - 'AGENTS.md', + '.zed/settings.json', + 'scripts/lib/install-targets/zed-project.js', 'docs/architecture/cross-harness.md', + 'tests/lib/install-targets.test.js', ], }, { diff --git a/scripts/lib/install-manifests.js b/scripts/lib/install-manifests.js index c9958b9f..ad5ecccd 100644 --- a/scripts/lib/install-manifests.js +++ b/scripts/lib/install-manifests.js @@ -4,7 +4,7 @@ const path = require('path'); const { getInstallTargetAdapter, planInstallTargetScaffold } = require('./install-targets/registry'); const DEFAULT_REPO_ROOT = path.join(__dirname, '../..'); -const SUPPORTED_INSTALL_TARGETS = ['claude', 'cursor', 'antigravity', 'codex', 'gemini', 'opencode', 'codebuddy', 'joycode', 'qwen']; +const SUPPORTED_INSTALL_TARGETS = ['claude', 'cursor', 'antigravity', 'codex', 'gemini', 'opencode', 'codebuddy', 'joycode', 'qwen', 'zed']; const COMPONENT_FAMILY_PREFIXES = { baseline: 'baseline:', language: 'lang:', @@ -35,6 +35,13 @@ const LEGACY_COMPAT_BASE_MODULE_IDS_BY_TARGET = Object.freeze({ 'agents-core', 'commands-core', ], + zed: [ + 'rules-core', + 'agents-core', + 'commands-core', + 'platform-configs', + 'workflow-quality', + ], }); const LEGACY_LANGUAGE_ALIAS_TO_CANONICAL = Object.freeze({ c: 'c', diff --git a/scripts/lib/install-targets/helpers.js b/scripts/lib/install-targets/helpers.js index a7a39663..fa1b9fec 100644 --- a/scripts/lib/install-targets/helpers.js +++ b/scripts/lib/install-targets/helpers.js @@ -11,6 +11,7 @@ const PLATFORM_SOURCE_PATH_OWNERS = Object.freeze({ '.opencode': 'opencode', '.codebuddy': 'codebuddy', '.qwen': 'qwen', + '.zed': 'zed', }); function normalizeRelativePath(relativePath) { diff --git a/scripts/lib/install-targets/registry.js b/scripts/lib/install-targets/registry.js index f7c4f44e..8e444a0e 100644 --- a/scripts/lib/install-targets/registry.js +++ b/scripts/lib/install-targets/registry.js @@ -7,6 +7,7 @@ const geminiProject = require('./gemini-project'); const joycodeProject = require('./joycode-project'); const opencodeHome = require('./opencode-home'); const qwenHome = require('./qwen-home'); +const zedProject = require('./zed-project'); const ADAPTERS = Object.freeze([ claudeHome, @@ -18,6 +19,7 @@ const ADAPTERS = Object.freeze([ codebuddyProject, joycodeProject, qwenHome, + zedProject, ]); function listInstallTargetAdapters() { diff --git a/scripts/lib/install-targets/zed-project.js b/scripts/lib/install-targets/zed-project.js new file mode 100644 index 00000000..c0d8b982 --- /dev/null +++ b/scripts/lib/install-targets/zed-project.js @@ -0,0 +1,50 @@ +const path = require('path'); + +const { + createFlatRuleOperations, + createInstallTargetAdapter, + isForeignPlatformPath, +} = require('./helpers'); + +module.exports = createInstallTargetAdapter({ + id: 'zed-project', + target: 'zed', + kind: 'project', + rootSegments: ['.zed'], + installStatePathSegments: ['ecc-install-state.json'], + nativeRootRelativePath: '.zed', + planOperations(input, adapter) { + const modules = Array.isArray(input.modules) + ? input.modules + : (input.module ? [input.module] : []); + const { + repoRoot, + projectRoot, + homeDir, + } = input; + const planningInput = { + repoRoot, + projectRoot, + homeDir, + }; + const targetRoot = adapter.resolveRoot(planningInput); + + return modules.flatMap(module => { + const paths = Array.isArray(module.paths) ? module.paths : []; + return paths + .filter(p => !isForeignPlatformPath(p, adapter.target)) + .flatMap(sourceRelativePath => { + if (sourceRelativePath === 'rules') { + return createFlatRuleOperations({ + moduleId: module.id, + repoRoot, + sourceRelativePath, + destinationDir: path.join(targetRoot, 'rules'), + }); + } + + return [adapter.createScaffoldOperation(module.id, sourceRelativePath, planningInput)]; + }); + }); + }, +}); diff --git a/tests/docs/harness-adapter-compliance.test.js b/tests/docs/harness-adapter-compliance.test.js index 7a140f73..4c5bf135 100644 --- a/tests/docs/harness-adapter-compliance.test.js +++ b/tests/docs/harness-adapter-compliance.test.js @@ -43,7 +43,7 @@ test('adapter compliance matrix covers the required harness surfaces', () => { 'OpenCode', 'Cursor', 'Gemini', - 'Zed-adjacent', + 'Zed', 'dmux', 'Orca', 'Superset', @@ -57,6 +57,14 @@ test('adapter compliance matrix covers the required harness surfaces', () => { test('adapter compliance source data validates required evidence fields', () => { assert.deepStrictEqual(validateAdapterRecords(), []); + const zedRecord = ADAPTER_RECORDS.find(record => record.id === 'zed'); + assert.ok(zedRecord, 'Expected Zed adapter record'); + assert.strictEqual(zedRecord.state, 'Adapter-backed'); + assert.ok( + zedRecord.install_or_onramp.includes('`./install.sh --profile minimal --target zed`'), + 'Expected Zed installer onramp' + ); + for (const record of ADAPTER_RECORDS) { assert.ok(record.install_or_onramp.length > 0, `${record.id} needs an install or onramp`); assert.ok(record.verification_commands.length > 0, `${record.id} needs verification commands`); diff --git a/tests/lib/install-manifests.test.js b/tests/lib/install-manifests.test.js index 0db0a312..78fd324c 100644 --- a/tests/lib/install-manifests.test.js +++ b/tests/lib/install-manifests.test.js @@ -280,6 +280,32 @@ function runTests() { ); })) passed++; else failed++; + if (test('resolves Zed minimal profile with project settings and without hooks', () => { + const projectRoot = '/workspace/zed-app'; + const plan = resolveInstallPlan({ + profileId: 'minimal', + target: 'zed', + projectRoot, + }); + + assert.deepStrictEqual( + plan.selectedModuleIds, + ['rules-core', 'agents-core', 'commands-core', 'platform-configs', 'workflow-quality'] + ); + assert.deepStrictEqual(plan.skippedModuleIds, []); + assert.strictEqual(plan.targetAdapterId, 'zed-project'); + assert.strictEqual(plan.targetRoot, path.join(projectRoot, '.zed')); + assert.ok( + plan.operations.some(operation => operation.sourceRelativePath === '.zed'), + 'Should install Zed native project settings' + ); + assert.ok( + !plan.selectedModuleIds.includes('hooks-runtime') + && !plan.operations.some(operation => operation.moduleId === 'hooks-runtime'), + 'Zed minimal profile should not install hook runtime files' + ); + })) passed++; else failed++; + if (test('resolves machine-learning component with workflow dependencies', () => { const plan = resolveInstallPlan({ includeComponentIds: ['capability:machine-learning'], diff --git a/tests/lib/install-targets.test.js b/tests/lib/install-targets.test.js index c5b3b7ce..daa2e722 100644 --- a/tests/lib/install-targets.test.js +++ b/tests/lib/install-targets.test.js @@ -45,6 +45,7 @@ function runTests() { assert.ok(targets.includes('codebuddy'), 'Should include codebuddy target'); assert.ok(targets.includes('joycode'), 'Should include joycode target'); assert.ok(targets.includes('qwen'), 'Should include qwen target'); + assert.ok(targets.includes('zed'), 'Should include zed target'); })) passed++; else failed++; if (test('resolves cursor adapter root and install-state path from project root', () => { @@ -549,6 +550,29 @@ function runTests() { assert.ok(byTarget.supports('qwen-home')); })) passed++; else failed++; + if (test('resolves zed adapter root and install-state path from project root', () => { + const adapter = getInstallTargetAdapter('zed'); + const projectRoot = '/workspace/app'; + const root = adapter.resolveRoot({ projectRoot }); + const statePath = adapter.getInstallStatePath({ projectRoot }); + + assert.strictEqual(adapter.id, 'zed-project'); + assert.strictEqual(adapter.target, 'zed'); + assert.strictEqual(adapter.kind, 'project'); + assert.strictEqual(root, path.join(projectRoot, '.zed')); + assert.strictEqual(statePath, path.join(projectRoot, '.zed', 'ecc-install-state.json')); + })) passed++; else failed++; + + if (test('zed adapter supports lookup by target and adapter id', () => { + const byTarget = getInstallTargetAdapter('zed'); + const byId = getInstallTargetAdapter('zed-project'); + + assert.strictEqual(byTarget.id, 'zed-project'); + assert.strictEqual(byId.id, 'zed-project'); + assert.ok(byTarget.supports('zed')); + assert.ok(byTarget.supports('zed-project')); + })) passed++; else failed++; + if (test('plans codebuddy rules with flat namespaced filenames', () => { const repoRoot = path.join(__dirname, '..', '..'); const projectRoot = '/workspace/app'; @@ -709,6 +733,83 @@ function runTests() { ); })) passed++; else failed++; + if (test('plans zed project settings, commands, agents, skills, and flattened rules', () => { + const repoRoot = path.join(__dirname, '..', '..'); + const projectRoot = '/workspace/app'; + + const plan = planInstallTargetScaffold({ + target: 'zed', + repoRoot, + projectRoot, + modules: [ + { + id: 'rules-core', + paths: ['rules'], + }, + { + id: 'agents-core', + paths: ['agents'], + }, + { + id: 'commands-core', + paths: ['commands'], + }, + { + id: 'platform-configs', + paths: ['.zed', '.cursor', 'mcp-configs'], + }, + { + id: 'workflow-quality', + paths: ['skills/tdd-workflow'], + }, + ], + }); + + assert.strictEqual(plan.adapter.id, 'zed-project'); + assert.strictEqual(plan.targetRoot, path.join(projectRoot, '.zed')); + assert.strictEqual(plan.installStatePath, path.join(projectRoot, '.zed', 'ecc-install-state.json')); + assert.ok( + plan.operations.some(operation => ( + normalizedRelativePath(operation.sourceRelativePath) === '.zed' + && operation.destinationPath === path.join(projectRoot, '.zed') + && operation.strategy === 'sync-root-children' + )), + 'Should sync Zed native project settings into .zed' + ); + assert.ok( + plan.operations.some(operation => ( + normalizedRelativePath(operation.sourceRelativePath) === 'rules/common/coding-style.md' + && operation.destinationPath === path.join(projectRoot, '.zed', 'rules', 'common-coding-style.md') + )), + 'Should flatten common rules into namespaced files for zed' + ); + assert.ok( + plan.operations.some(operation => ( + normalizedRelativePath(operation.sourceRelativePath) === 'agents' + && operation.destinationPath === path.join(projectRoot, '.zed', 'agents') + )), + 'Should install agents under .zed/agents' + ); + assert.ok( + plan.operations.some(operation => ( + normalizedRelativePath(operation.sourceRelativePath) === 'commands' + && operation.destinationPath === path.join(projectRoot, '.zed', 'commands') + )), + 'Should install commands under .zed/commands' + ); + assert.ok( + plan.operations.some(operation => ( + normalizedRelativePath(operation.sourceRelativePath) === 'skills/tdd-workflow' + && operation.destinationPath === path.join(projectRoot, '.zed', 'skills', 'tdd-workflow') + )), + 'Should install skills under .zed/skills' + ); + assert.ok( + !plan.operations.some(operation => normalizedRelativePath(operation.sourceRelativePath) === '.cursor'), + 'Should skip foreign Cursor platform config paths' + ); + })) passed++; else failed++; + if (test('exposes validate and planOperations on codebuddy adapter', () => { const codebuddyAdapter = getInstallTargetAdapter('codebuddy'); diff --git a/tests/lib/selective-install.test.js b/tests/lib/selective-install.test.js index 97914ced..5509d12a 100644 --- a/tests/lib/selective-install.test.js +++ b/tests/lib/selective-install.test.js @@ -480,6 +480,7 @@ function runTests() { assert.ok(result.includes('--with'), 'Help should mention --with'); assert.ok(result.includes('--without'), 'Help should mention --without'); assert.ok(result.includes('component'), 'Help should describe components'); + assert.ok(result.includes('zed - Install project settings'), 'Help should describe Zed target'); })) passed++; else failed++; // ─── End-to-End Dry-Run ─── @@ -515,6 +516,45 @@ function runTests() { } })) passed++; else failed++; + if (test('end-to-end: --profile minimal --target zed --dry-run --json plans project adapter', () => { + const { execFileSync } = require('child_process'); + const scriptPath = path.join(__dirname, '..', '..', 'scripts', 'install-apply.js'); + const homeDir = fs.mkdtempSync(path.join(os.tmpdir(), 'selective-e2e-')); + const projectDir = fs.mkdtempSync(path.join(os.tmpdir(), 'selective-e2e-zed-project-')); + + try { + const result = execFileSync('node', [ + scriptPath, + '--profile', 'minimal', + '--target', 'zed', + '--dry-run', + '--json', + ], { + cwd: projectDir, + env: { ...process.env, HOME: homeDir }, + encoding: 'utf8', + stdio: ['pipe', 'pipe', 'pipe'], + }); + const parsed = JSON.parse(result); + + assert.strictEqual(parsed.dryRun, true); + assert.strictEqual(parsed.plan.target, 'zed'); + assert.strictEqual(parsed.plan.adapter.id, 'zed-project'); + assert.strictEqual(parsed.plan.installRoot, path.join(fs.realpathSync(projectDir), '.zed')); + assert.ok( + parsed.plan.operations.some(operation => operation.sourceRelativePath === '.zed/settings.json'), + 'Should include Zed native settings operation' + ); + assert.ok( + !parsed.plan.operations.some(operation => operation.moduleId === 'hooks-runtime'), + 'Zed minimal dry-run should not install hook runtime files' + ); + } finally { + fs.rmSync(homeDir, { recursive: true, force: true }); + fs.rmSync(projectDir, { recursive: true, force: true }); + } + })) passed++; else failed++; + if (test('end-to-end: --with lang:python --with agent:security-reviewer --dry-run', () => { const { execFileSync } = require('child_process'); const scriptPath = path.join(__dirname, '..', '..', 'scripts', 'install-apply.js');