From 69e104dffcbf1e19ae27dcda31094bf87e9580ed Mon Sep 17 00:00:00 2001 From: Tibsfox Date: Thu, 26 Mar 2026 01:13:46 -0700 Subject: [PATCH] fix(windsurf): remove trailing slash from .windsurf/rules path Node v25 preserves trailing slashes in path.join, causing writeFileSync to fail with ENOENT when the converted path ends in '/'. Affects all Windsurf users on Node v25+. Fixes gsd-build/get-shit-done#1392 Co-Authored-By: Claude Opus 4.6 (1M context) --- bin/install.js | 10 +++++----- tests/windsurf-conversion.test.cjs | 5 +++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/bin/install.js b/bin/install.js index c65b7ac52..a8dee4cb7 100755 --- a/bin/install.js +++ b/bin/install.js @@ -919,10 +919,10 @@ function convertClaudeToWindsurfMarkdown(content) { converted = converted.replace(/subagent_type="general-purpose"/g, 'subagent_type="generalPurpose"'); converted = converted.replace(/\$ARGUMENTS\b/g, '{{GSD_ARGS}}'); // Replace project-level Claude conventions with Windsurf equivalents - converted = converted.replace(/`\.\/CLAUDE\.md`/g, '`.windsurf/rules/`'); - converted = converted.replace(/\.\/CLAUDE\.md/g, '.windsurf/rules/'); - converted = converted.replace(/`CLAUDE\.md`/g, '`.windsurf/rules/`'); - converted = converted.replace(/\bCLAUDE\.md\b/g, '.windsurf/rules/'); + converted = converted.replace(/`\.\/CLAUDE\.md`/g, '`.windsurf/rules`'); + converted = converted.replace(/\.\/CLAUDE\.md/g, '.windsurf/rules'); + converted = converted.replace(/`CLAUDE\.md`/g, '`.windsurf/rules`'); + converted = converted.replace(/\bCLAUDE\.md\b/g, '.windsurf/rules'); converted = converted.replace(/\.claude\/skills\//g, '.windsurf/skills/'); // Remove Claude Code-specific bug workarounds before brand replacement converted = converted.replace(/\*\*Known Claude Code bug \(classifyHandoffIfNeeded\):\*\*[^\n]*\n/g, ''); @@ -3127,7 +3127,7 @@ function copyWithPathReplacement(srcDir, destDir, pathPrefix, runtime, isCommand let jsContent = fs.readFileSync(srcPath, 'utf8'); jsContent = jsContent.replace(/gsd:/gi, 'gsd-'); jsContent = jsContent.replace(/\.claude\/skills\//g, '.windsurf/skills/'); - jsContent = jsContent.replace(/CLAUDE\.md/g, '.windsurf/rules/'); + jsContent = jsContent.replace(/CLAUDE\.md/g, '.windsurf/rules'); jsContent = jsContent.replace(/\bClaude Code\b/g, 'Windsurf'); fs.writeFileSync(destPath, jsContent); } else { diff --git a/tests/windsurf-conversion.test.cjs b/tests/windsurf-conversion.test.cjs index 06834d5c9..3df82ed1b 100644 --- a/tests/windsurf-conversion.test.cjs +++ b/tests/windsurf-conversion.test.cjs @@ -105,10 +105,11 @@ describe('convertClaudeToWindsurfMarkdown', () => { assert.ok(!result.includes('Claude Code'), 'original brand removed'); }); - test('replaces CLAUDE.md with .windsurf/rules/', () => { + test('replaces CLAUDE.md with .windsurf/rules (no trailing slash)', () => { const input = 'See `CLAUDE.md` for configuration. Also check ./CLAUDE.md file.'; const result = convertClaudeToWindsurfMarkdown(input); - assert.ok(result.includes('.windsurf/rules/'), 'CLAUDE.md replaced'); + assert.ok(result.includes('.windsurf/rules'), 'CLAUDE.md replaced'); + assert.ok(!result.includes('.windsurf/rules/'), 'no trailing slash (Node v25 compat)'); }); test('replaces .claude/skills/ with .windsurf/skills/', () => {