Skip to content

Commit 116589b

Browse files
ARHAEEMclaude
andcommitted
feat(ide): relabel Windsurf as Devin Desktop + install AI files to .devin (legacy .windsurf kept)
Cognition rebranded the Windsurf editor to "Devin Desktop" (2026-06-02, in-place OTA rename) and moved workspace AI assets from .windsurf/ to .devin/. - skills/installer.ts: windsurf/windsurf-next now write skills/rules/workflows to .devin/ (primary) AND .windsurf/ (legacy) via a new optional AiInstallConfig.legacy; checkAiFiles reports "ok" if either exists. - auto-config/ide-configs.ts + webview Setup/Prompts: relabel to "Devin Desktop (Windsurf)" / "Devin Desktop Next (Windsurf Next)". - Unchanged: windsurf/windsurf-next IDE ids + .codeium/windsurf*/mcp_config.json paths (the .codeium tree is unchanged per vendor docs). Tests: installer.test.ts (.devin primary, .windsurf legacy, dual-write both ids). Full build + extension (65) + webview (35) tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 8c96129 commit 116589b

6 files changed

Lines changed: 103 additions & 32 deletions

File tree

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,15 @@ Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how
66

77
## [Unreleased]
88

9+
### Extension — Windsurf renamed to Devin Desktop, legacy-compatible (2026-06-05)
10+
11+
Cognition rebranded the Windsurf editor to **Devin Desktop** on 2026-06-02 (in-place OTA rename) and moved workspace AI assets from `.windsurf/` to `.devin/`, keeping `.windsurf/` as a read fallback (Windsurf-import is on by default).
12+
13+
- **Relabel:** the Setup IDE picker and auto-config now show **"Devin Desktop (Windsurf)"** / **"Devin Desktop Next (Windsurf Next)"** ([`ide-configs.ts`](packages/extension/src/auto-config/ide-configs.ts), [`Setup.tsx`](packages/webview/src/tabs/Setup.tsx), [`Prompts.tsx`](packages/webview/src/tabs/Prompts.tsx)).
14+
- **AI-files install** ([`skills/installer.ts`](packages/extension/src/skills/installer.ts)) now **dual-writes** skills/rules/workflows to both `.devin/{skills,rules,workflows}/airtable-formula.md` (new primary) and `.windsurf/…` (legacy — for pre-rebrand Windsurf + the vendor import path); `checkAiFiles` reports "ok" if either exists. New optional `AiInstallConfig.legacy` field drives this.
15+
- **Unchanged (back-compat):** the `windsurf` / `windsurf-next` IDE ids and the `.codeium/windsurf*/mcp_config.json` MCP config paths — the `.codeium` tree is unchanged per the vendor docs.
16+
- Tests: [`installer.test.ts`](packages/extension/src/test/installer.test.ts) covers the `.devin` primary, the `.windsurf` legacy fallback, and dual-write for both ids. Full build + extension (65) + webview (35) tests pass.
17+
918
### MCP Server 2.5.0 — Record Templates (9 new tools) + round-4 user-report fixes (2026-05-01)
1019

1120
Promotes record templates to a first-class MCP capability. Templates are

packages/extension/src/auto-config/ide-configs.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,15 +40,18 @@ export const IDE_CONFIGS: Record<IdeId, IdeConfig> = {
4040
path.join(localAppData, 'Programs', 'cursor'),
4141
],
4242
},
43+
// Windsurf was rebranded to "Devin Desktop" (2026-06-02, in-place OTA rename).
44+
// The `.codeium/windsurf*` paths are unchanged per the vendor docs, so the
45+
// MCP config + detection paths stay; only the display label updates.
4346
'windsurf': {
44-
label: 'Windsurf',
47+
label: 'Devin Desktop (Windsurf)',
4548
capabilities: ['mcp'],
4649
mcpConfigPath: path.join(home, '.codeium', 'windsurf', 'mcp_config.json'),
4750
mcpServersKey: 'mcpServers',
4851
detectionPaths: [path.join(home, '.codeium', 'windsurf')],
4952
},
5053
'windsurf-next': {
51-
label: 'Windsurf Next',
54+
label: 'Devin Desktop Next (Windsurf Next)',
5255
capabilities: ['mcp'],
5356
mcpConfigPath: path.join(home, '.codeium', 'windsurf-next', 'mcp_config.json'),
5457
mcpServersKey: 'mcpServers',

packages/extension/src/skills/installer.ts

Lines changed: 57 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,18 @@ interface AiInstallConfig {
1111
workflowPath: string | null;
1212
agentsPath: string | null;
1313
wrapCursor: boolean;
14+
/**
15+
* Deprecated-but-still-honored paths written alongside the primary ones and
16+
* recognized on check. Used when a client rebrands its config directory but
17+
* keeps reading the old one — e.g. Windsurf → Devin Desktop (2026-06-02):
18+
* primary `.devin/…`, legacy `.windsurf/…` (read by Devin's on-by-default
19+
* Windsurf import + by users still on pre-rebrand Windsurf).
20+
*/
21+
legacy?: {
22+
skillPath?: string;
23+
rulesPath?: string;
24+
workflowPath?: string;
25+
};
1426
}
1527

1628
const home = os.homedir();
@@ -26,19 +38,31 @@ const AI_CONFIGS: Record<IdeId, AiInstallConfig> = {
2638
},
2739
'windsurf': {
2840
baseDir: 'workspace',
29-
skillPath: '.windsurf/skills/airtable-formula.md',
30-
rulesPath: '.windsurf/rules/airtable-formula.md',
31-
workflowPath: '.windsurf/workflows/airtable-formula.md',
41+
// Windsurf was rebranded to "Devin Desktop" (2026-06-02). Devin-native
42+
// workspace assets live under .devin/; .windsurf/ is still read as legacy.
43+
skillPath: '.devin/skills/airtable-formula.md',
44+
rulesPath: '.devin/rules/airtable-formula.md',
45+
workflowPath: '.devin/workflows/airtable-formula.md',
3246
agentsPath: null,
3347
wrapCursor: false,
48+
legacy: {
49+
skillPath: '.windsurf/skills/airtable-formula.md',
50+
rulesPath: '.windsurf/rules/airtable-formula.md',
51+
workflowPath: '.windsurf/workflows/airtable-formula.md',
52+
},
3453
},
3554
'windsurf-next': {
3655
baseDir: 'workspace',
37-
skillPath: '.windsurf/skills/airtable-formula.md',
38-
rulesPath: '.windsurf/rules/airtable-formula.md',
39-
workflowPath: '.windsurf/workflows/airtable-formula.md',
56+
skillPath: '.devin/skills/airtable-formula.md',
57+
rulesPath: '.devin/rules/airtable-formula.md',
58+
workflowPath: '.devin/workflows/airtable-formula.md',
4059
agentsPath: null,
4160
wrapCursor: false,
61+
legacy: {
62+
skillPath: '.windsurf/skills/airtable-formula.md',
63+
rulesPath: '.windsurf/rules/airtable-formula.md',
64+
workflowPath: '.windsurf/workflows/airtable-formula.md',
65+
},
4266
},
4367
'claude-code': {
4468
baseDir: 'home',
@@ -92,15 +116,20 @@ export async function installAiFiles(ideId: IdeId, workspaceRoot: string, force
92116
const cfg = AI_CONFIGS[ideId];
93117
const base = resolveBase(cfg, workspaceRoot);
94118

95-
const written = async (rel: string | null, content: string): Promise<AiFileStatus> => {
119+
// Write the primary path and any legacy paths (e.g. .devin/ + .windsurf/).
120+
// 'ok' if at least one location ends up present.
121+
const written = async (rel: string | null, legacyRel: string | undefined, content: string): Promise<AiFileStatus> => {
96122
if (!rel) return 'missing';
97-
const abs = cfg.baseDir === 'home' ? rel : path.join(base, rel);
98-
try {
99-
await writeIfMissing(abs, content, force);
100-
return 'ok';
101-
} catch {
102-
return 'missing';
123+
let present = false;
124+
for (const r of [rel, legacyRel]) {
125+
if (!r) continue;
126+
const abs = cfg.baseDir === 'home' ? r : path.join(base, r);
127+
try {
128+
await writeIfMissing(abs, content, force);
129+
present = true;
130+
} catch { /* skip this location */ }
103131
}
132+
return present ? 'ok' : 'missing';
104133
};
105134

106135
const skillContent = cfg.wrapCursor ? cursorWrap(SKILL_CONTENT, 'Airtable Formula skill') : SKILL_CONTENT;
@@ -109,27 +138,31 @@ export async function installAiFiles(ideId: IdeId, workspaceRoot: string, force
109138
const agentContent = AGENTS_CONTENT;
110139

111140
return {
112-
skills: await written(cfg.skillPath, skillContent),
113-
rules: await written(cfg.rulesPath, rulesContent),
114-
workflows: await written(cfg.workflowPath, wfContent),
115-
agents: includeAgents ? await written(cfg.agentsPath, agentContent) : 'missing',
141+
skills: await written(cfg.skillPath, cfg.legacy?.skillPath, skillContent),
142+
rules: await written(cfg.rulesPath, cfg.legacy?.rulesPath, rulesContent),
143+
workflows: await written(cfg.workflowPath, cfg.legacy?.workflowPath, wfContent),
144+
agents: includeAgents ? await written(cfg.agentsPath, undefined, agentContent) : 'missing',
116145
};
117146
}
118147

119148
export async function checkAiFiles(ideId: IdeId, workspaceRoot: string): Promise<AiFiles> {
120149
const cfg = AI_CONFIGS[ideId];
121150
const base = resolveBase(cfg, workspaceRoot);
122151

123-
const check = async (rel: string | null): Promise<AiFileStatus> => {
124-
if (!rel) return 'missing';
125-
const abs = cfg.baseDir === 'home' ? rel : path.join(base, rel);
126-
try { await fs.promises.access(abs); return 'ok'; } catch { return 'missing'; }
152+
// 'ok' if the primary OR any legacy path exists.
153+
const check = async (rel: string | null, legacyRel?: string): Promise<AiFileStatus> => {
154+
for (const r of [rel, legacyRel]) {
155+
if (!r) continue;
156+
const abs = cfg.baseDir === 'home' ? r : path.join(base, r);
157+
try { await fs.promises.access(abs); return 'ok'; } catch { /* keep checking */ }
158+
}
159+
return 'missing';
127160
};
128161

129162
return {
130-
skills: await check(cfg.skillPath),
131-
rules: await check(cfg.rulesPath),
132-
workflows: await check(cfg.workflowPath),
163+
skills: await check(cfg.skillPath, cfg.legacy?.skillPath),
164+
rules: await check(cfg.rulesPath, cfg.legacy?.rulesPath),
165+
workflows: await check(cfg.workflowPath, cfg.legacy?.workflowPath),
133166
agents: await check(cfg.agentsPath),
134167
};
135168
}

packages/extension/src/test/installer.test.ts

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import * as fs from 'fs';
33
import * as os from 'os';
44
import * as path from 'path';
55

6-
import { writeIfMissing, cursorWrap, checkAiFiles } from '../skills/installer.js';
6+
import { writeIfMissing, cursorWrap, checkAiFiles, installAiFiles } from '../skills/installer.js';
77

88
const tmpDir = path.join(os.tmpdir(), 'airtable-test-' + Date.now());
99

@@ -52,11 +52,37 @@ describe('checkAiFiles', () => {
5252
expect(result.rules).toBe('missing');
5353
});
5454

55-
it('returns "ok" for present files', async () => {
55+
it('returns "ok" for present files (legacy .windsurf path)', async () => {
5656
const skillPath = path.join(tmpDir, '.windsurf', 'skills', 'airtable-formula.md');
5757
fs.mkdirSync(path.dirname(skillPath), { recursive: true });
5858
fs.writeFileSync(skillPath, 'skill');
5959
const result = await checkAiFiles('windsurf', tmpDir);
6060
expect(result.skills).toBe('ok');
6161
});
62+
63+
it('returns "ok" for the .devin primary path', async () => {
64+
const skillPath = path.join(tmpDir, '.devin', 'skills', 'airtable-formula.md');
65+
fs.mkdirSync(path.dirname(skillPath), { recursive: true });
66+
fs.writeFileSync(skillPath, 'skill');
67+
const result = await checkAiFiles('windsurf', tmpDir);
68+
expect(result.skills).toBe('ok');
69+
});
70+
});
71+
72+
// Windsurf → Devin Desktop (2026-06-02): primary .devin/, legacy .windsurf/.
73+
describe('Devin Desktop dual-write', () => {
74+
it('installs both .devin (primary) and .windsurf (legacy) for skills/rules/workflows', async () => {
75+
await installAiFiles('windsurf', tmpDir, false, false);
76+
for (const dir of ['.devin', '.windsurf']) {
77+
expect(fs.existsSync(path.join(tmpDir, dir, 'skills', 'airtable-formula.md'))).toBe(true);
78+
expect(fs.existsSync(path.join(tmpDir, dir, 'rules', 'airtable-formula.md'))).toBe(true);
79+
expect(fs.existsSync(path.join(tmpDir, dir, 'workflows', 'airtable-formula.md'))).toBe(true);
80+
}
81+
});
82+
83+
it('windsurf-next installs the same dual paths', async () => {
84+
await installAiFiles('windsurf-next', tmpDir, false, false);
85+
expect(fs.existsSync(path.join(tmpDir, '.devin', 'rules', 'airtable-formula.md'))).toBe(true);
86+
expect(fs.existsSync(path.join(tmpDir, '.windsurf', 'rules', 'airtable-formula.md'))).toBe(true);
87+
});
6288
});

packages/webview/src/tabs/Prompts.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ function ReconnectNotice() {
1717
<span style={{ color: 'var(--fg-info)', fontWeight: 600 }}>Reconnect required after changes. </span>
1818
Clients cache MCP capabilities at connect-time. After saving or adding prompts:
1919
<ul style={{ marginTop: 4, paddingLeft: 14 }}>
20-
<li><b>Cursor / Windsurf</b> — Reload Window (Ctrl+Shift+P)</li>
20+
<li><b>Cursor / Devin Desktop (Windsurf)</b> — Reload Window (Ctrl+Shift+P)</li>
2121
<li><b>Claude Code</b> — Restart session or run <code style={{ fontFamily: 'var(--font-mono)', fontSize: '0.64rem' }}>/mcp</code></li>
2222
<li><b>Claude Desktop</b> — Restart the app</li>
2323
</ul>

packages/webview/src/tabs/Setup.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ const MCP_IDE_TABS = [
191191
{ id: 'claude-code', label: 'Claude Code' },
192192
{ id: 'claude-desktop', label: 'Claude Desktop' },
193193
{ id: 'cursor', label: 'Cursor' },
194-
{ id: 'windsurf', label: 'Windsurf' },
194+
{ id: 'windsurf', label: 'Devin Desktop (Windsurf)' },
195195
{ id: 'cline', label: 'Cline' },
196196
] as const;
197197

@@ -200,8 +200,8 @@ const OFFICIAL_MCP_IDES = [
200200
{ id: 'claude-code', label: 'Claude Code' },
201201
{ id: 'claude-desktop', label: 'Claude Desktop' },
202202
{ id: 'cursor', label: 'Cursor' },
203-
{ id: 'windsurf', label: 'Windsurf' },
204-
{ id: 'windsurf-next', label: 'Windsurf Next' },
203+
{ id: 'windsurf', label: 'Devin Desktop (Windsurf)' },
204+
{ id: 'windsurf-next', label: 'Devin Desktop Next (Windsurf Next)' },
205205
{ id: 'cline', label: 'Cline' },
206206
{ id: 'amp', label: 'Amp' },
207207
{ id: 'opencode', label: 'OpenCode' },

0 commit comments

Comments
 (0)