From 93c5ecd64581f6f2e7db41cf9b27e357eb838ca1 Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Thu, 11 Jun 2026 22:32:49 -0400 Subject: [PATCH] feat(#792): add devin-desktop runtime alias for windsurf (#1086) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit windsurf now also answers to devin-desktop (CLI --devin-desktop) for the Windsurf→Devin Desktop rebrand; all paths unchanged. The .devin/skills/ workspace migration is split to #1085. Closes #792. --- .changeset/792-devin-desktop-alias.md | 6 +++ bin/install.js | 2 +- docs/CONFIGURATION.md | 2 +- docs/USER-GUIDE.md | 2 +- docs/how-to/install-on-your-runtime.md | 10 ++-- .../bin/shared/runtime-aliases.manifest.json | 3 +- src/runtime-name-policy.cts | 2 +- tests/install.test.cjs | 15 ++++++ tests/runtime-name-policy.test.cjs | 53 +++++++++++++++++++ 9 files changed, 87 insertions(+), 8 deletions(-) create mode 100644 .changeset/792-devin-desktop-alias.md diff --git a/.changeset/792-devin-desktop-alias.md b/.changeset/792-devin-desktop-alias.md new file mode 100644 index 000000000..080f28f84 --- /dev/null +++ b/.changeset/792-devin-desktop-alias.md @@ -0,0 +1,6 @@ +--- +type: Changed +pr: 1086 +--- + +**`devin-desktop` runtime alias for the Windsurf→Devin Desktop rebrand** — the `windsurf` runtime now also answers to `devin-desktop` (CLI `--devin-desktop`), aiding discoverability after Cognition rebranded Windsurf as Devin Desktop. All paths are unchanged — global skills still install to `~/.codeium/windsurf/skills/`. (#1086) diff --git a/bin/install.js b/bin/install.js index a95e144a5..9e9c10635 100755 --- a/bin/install.js +++ b/bin/install.js @@ -410,7 +410,7 @@ function selectRuntimesFromArgs(runtimeArgs) { if (runtimeArgs.includes('--copilot')) selected.push('copilot'); if (runtimeArgs.includes('--antigravity')) selected.push('antigravity'); if (runtimeArgs.includes('--cursor')) selected.push('cursor'); - if (runtimeArgs.includes('--windsurf')) selected.push('windsurf'); + if (runtimeArgs.includes('--windsurf') || runtimeArgs.includes('--devin-desktop')) selected.push('windsurf'); if (runtimeArgs.includes('--augment')) selected.push('augment'); if (runtimeArgs.includes('--trae')) selected.push('trae'); if (runtimeArgs.includes('--qwen')) selected.push('qwen'); diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index 64396a17f..c45a34450 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -1209,7 +1209,7 @@ When `runtime` is set, profile tiers (`opus`/`sonnet`/`haiku`) resolve to runtim | `opencode` | `anthropic/claude-opus-4-8` | `anthropic/claude-sonnet-4-6` | `anthropic/claude-haiku-4-5` | (not used) | | `copilot` | `claude-opus-4-8` | `claude-sonnet-4-6` | `claude-haiku-4-5` | (not used) | | `hermes` | `anthropic/claude-opus-4-8` | `anthropic/claude-sonnet-4-6` | `anthropic/claude-haiku-4-5` | (not used) | -| Group B (`kilo`, `cline`, `cursor`, `windsurf`, `augment`, `trae`, `codebuddy`, `antigravity`) | (no built-in default — your runtime handles model selection) | | | | +| Group B (`kilo`, `cline`, `cursor`, `windsurf` (alias: `devin-desktop`), `augment`, `trae`, `codebuddy`, `antigravity`) | (no built-in default — your runtime handles model selection) | | | | > **How these model IDs are sourced.** The catalog (`bin/shared/model-catalog.json`) pins each runtime's tier defaults to that provider's current frontier IDs, and may intentionally carry forward-dated IDs ahead of a provider's public docs. To verify an ID is live before changing it, check the provider's own source/API — e.g. Gemini: gemini-cli `packages/core/src/config/models.ts` or `gemini --model --prompt ping`; Codex: `codex debug models` or the OpenAI Codex models page; Qwen: Alibaba Model Studio model list. Only change an ID that the provider actually rejects — absence from documentation alone is not proof of invalidity. diff --git a/docs/USER-GUIDE.md b/docs/USER-GUIDE.md index 33e894224..15bf5d51e 100644 --- a/docs/USER-GUIDE.md +++ b/docs/USER-GUIDE.md @@ -826,7 +826,7 @@ WINDSURF_CONFIG_DIR=~/.codeium/windsurf-next npx @opengsd/gsd-core@latest --wind | Codex | (per Codex CLI) | `--config-dir` flag | | Copilot | `~/.copilot` | `COPILOT_CONFIG_DIR` (or `COPILOT_HOME`) | | Cursor | `~/.cursor` | `CURSOR_CONFIG_DIR` | -| Windsurf | `~/.codeium/windsurf` | `WINDSURF_CONFIG_DIR` | +| Windsurf / Devin Desktop | `~/.codeium/windsurf` | `WINDSURF_CONFIG_DIR` | | Antigravity | auto-detected | `ANTIGRAVITY_CONFIG_DIR` | | Augment | `~/.augment` | `AUGMENT_CONFIG_DIR` | | Trae | `~/.trae` | `TRAE_CONFIG_DIR` | diff --git a/docs/how-to/install-on-your-runtime.md b/docs/how-to/install-on-your-runtime.md index 6a89f9c1c..59d361209 100644 --- a/docs/how-to/install-on-your-runtime.md +++ b/docs/how-to/install-on-your-runtime.md @@ -308,13 +308,17 @@ CURSOR_CONFIG_DIR=~/.cursor-alt npx @opengsd/gsd-core@latest --cursor --global --- -### Windsurf +### Windsurf / Devin Desktop + +Windsurf has rebranded to **Devin Desktop**. Both runtime names are accepted — use either `--windsurf` or `--devin-desktop`. ```bash npx @opengsd/gsd-core@latest --windsurf --global +# or equivalently: +npx @opengsd/gsd-core@latest --devin-desktop --global ``` -Skills land in `~/.codeium/windsurf/`. GSD installs skills, agents, and workspace rules. +Skills land in `~/.codeium/windsurf/` (global path unchanged). GSD installs skills, agents, and workspace rules. **Override the install directory:** @@ -456,7 +460,7 @@ A local install writes into the `.claude/` directory at your project root. Local ## Installing prerelease editions (Next / Nightly / Insiders / Preview) -Prerelease editions of runtimes (Windsurf Next, Cursor Nightly, VS Code Insiders, Codex preview channels, etc.) read from a sibling config directory. Set the matching `*_CONFIG_DIR` env var before running the installer: +Prerelease editions of runtimes (Windsurf Next / Devin Desktop Next, Cursor Nightly, VS Code Insiders, Codex preview channels, etc.) read from a sibling config directory. Set the matching `*_CONFIG_DIR` env var before running the installer: ```bash WINDSURF_CONFIG_DIR=~/.codeium/windsurf-next npx @opengsd/gsd-core@latest --windsurf --global diff --git a/gsd-core/bin/shared/runtime-aliases.manifest.json b/gsd-core/bin/shared/runtime-aliases.manifest.json index 3192f64ce..e7264048d 100644 --- a/gsd-core/bin/shared/runtime-aliases.manifest.json +++ b/gsd-core/bin/shared/runtime-aliases.manifest.json @@ -43,7 +43,8 @@ "windsurf": [ "windsurf", "windsurf-cli", - "windsurf-next" + "windsurf-next", + "devin-desktop" ], "augment": [ "augment", diff --git a/src/runtime-name-policy.cts b/src/runtime-name-policy.cts index 68cb1a850..8134ef5b8 100644 --- a/src/runtime-name-policy.cts +++ b/src/runtime-name-policy.cts @@ -23,7 +23,7 @@ const FALLBACK_ALIASES: Readonly> = { copilot: ['copilot', 'copilot-cli', 'github-copilot'], antigravity: ['antigravity', 'antigravity-cli', 'antigravity-agent'], cursor: ['cursor', 'cursor-cli', 'cursor-nightly'], - windsurf: ['windsurf', 'windsurf-cli', 'windsurf-next'], + windsurf: ['windsurf', 'windsurf-cli', 'windsurf-next', 'devin-desktop'], augment: ['augment', 'augment-code', 'augment-cli'], trae: ['trae', 'trae-cli'], qwen: ['qwen', 'qwen-code', 'qwen-cli'], diff --git a/tests/install.test.cjs b/tests/install.test.cjs index 9b4659688..0dfa93091 100644 --- a/tests/install.test.cjs +++ b/tests/install.test.cjs @@ -43,6 +43,7 @@ const { buildRuntimePromptText, resolveKiloConfigPath, configureKiloPermissions, + selectRuntimesFromArgs, } = require('../bin/install.js'); const { getGlobalConfigDir } = require('../gsd-core/bin/lib/runtime-homes.cjs'); @@ -835,6 +836,20 @@ describe('install — changeset CLI lands at scripts/changeset/cli.cjs (#935)', }); }); +// ─── Section 6: Windsurf / devin-desktop alias (#792) ─────────────────────── + +describe('install — --devin-desktop CLI flag routes to windsurf runtime (#792)', () => { + test('--devin-desktop resolves to ["windsurf"] via selectRuntimesFromArgs', () => { + const runtimes = selectRuntimesFromArgs(['--devin-desktop']); + assert.deepStrictEqual(runtimes, ['windsurf'], + '--devin-desktop must resolve to ["windsurf"] via selectRuntimesFromArgs'); + }); + + test('--windsurf and --devin-desktop both resolve to ["windsurf"]', () => { + assert.deepStrictEqual(selectRuntimesFromArgs(['--windsurf']), ['windsurf']); + assert.deepStrictEqual(selectRuntimesFromArgs(['--devin-desktop']), ['windsurf']); + }); +}); // ─── Section N+1: #767 — disallowedTools injection for read-only agents ────── // // Verifies (installer-behavioral test — drives install() to a temp dir): diff --git a/tests/runtime-name-policy.test.cjs b/tests/runtime-name-policy.test.cjs index abc6ee11d..2d8ce6874 100644 --- a/tests/runtime-name-policy.test.cjs +++ b/tests/runtime-name-policy.test.cjs @@ -3,6 +3,7 @@ const { describe, test } = require('node:test'); const assert = require('node:assert/strict'); const path = require('node:path'); +const fs = require('node:fs'); const ROOT = path.join(__dirname, '..'); const { @@ -17,4 +18,56 @@ describe('runtime-name-policy canonical runtime ids', () => { assert.strictEqual(resolveRuntimeNameFromCandidates('', null, 'kimi'), 'kimi'); assert.strictEqual(canonicalizeRuntimeName('kimi-cli'), null); }); + + test('canonicalizes devin-desktop to windsurf (#792)', () => { + assert.strictEqual(canonicalizeRuntimeName('devin-desktop'), 'windsurf'); + assert.strictEqual(canonicalizeRuntimeName('DEVIN-DESKTOP'), 'windsurf'); + assert.strictEqual(resolveRuntimeNameFromCandidates('devin-desktop'), 'windsurf'); + }); +}); + +describe('runtime-name-policy windsurf alias parity — manifest vs FALLBACK_ALIASES (#792)', () => { + // DEFECT.GENERATIVE-FIX: manifest and FALLBACK_ALIASES are manually mirrored; + // this test fails if they diverge for the windsurf key. + const manifestPath = path.join(ROOT, 'gsd-core', 'bin', 'shared', 'runtime-aliases.manifest.json'); + const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8')); + + test('manifest windsurf array includes devin-desktop', () => { + assert.ok( + Array.isArray(manifest.windsurf) && manifest.windsurf.includes('devin-desktop'), + `runtime-aliases.manifest.json windsurf array must include 'devin-desktop'; got: ${JSON.stringify(manifest.windsurf)}`, + ); + }); + + test('FALLBACK_ALIASES windsurf includes devin-desktop (via canonicalization round-trip)', () => { + // The built module merges manifest over FALLBACK_ALIASES; if the manifest is present + // this verifies the combined set. The manifest test above separately guards the manifest. + // Here we verify the live canonicalizer sees devin-desktop -> windsurf. + assert.strictEqual( + canonicalizeRuntimeName('devin-desktop'), + 'windsurf', + 'devin-desktop must resolve to windsurf via alias lookup', + ); + }); + + test('manifest and FALLBACK_ALIASES windsurf alias sets are identical', () => { + // Read FALLBACK_ALIASES from source to detect manual drift before a build. + const srcPath = path.join(ROOT, 'src', 'runtime-name-policy.cts'); + // allow-test-rule: runtime-contract-is-the-product — FALLBACK_ALIASES source text IS the + // product contract for runtimes that can't load the manifest at runtime; verifying + // both surfaces contain the same windsurf aliases catches manual-mirror drift. + const src = fs.readFileSync(srcPath, 'utf8'); + const match = src.match(/windsurf:\s*\[([^\]]+)\]/); + assert.ok(match, 'FALLBACK_ALIASES windsurf row must exist in src/runtime-name-policy.cts'); + const srcAliases = match[1] + .split(',') + .map(s => s.trim().replace(/^['"]|['"]$/g, '')) + .filter(Boolean); + const manifestAliases = [...manifest.windsurf].sort(); + assert.deepStrictEqual( + [...srcAliases].sort(), + manifestAliases, + `FALLBACK_ALIASES windsurf=${JSON.stringify(srcAliases.sort())} must match manifest windsurf=${JSON.stringify(manifestAliases)}`, + ); + }); });