fix(#1615): install Windsurf slash workflows

This commit is contained in:
Tom Boucher
2026-06-23 12:10:21 -04:00
parent c28cccbf85
commit fc2a7c0555
18 changed files with 222 additions and 162 deletions

View File

@@ -941,20 +941,18 @@ function convertClaudeToWindsurfMarkdown(content) {
// Replace subagent_type from Claude to Windsurf format
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/Devin equivalents
// Workspace skills install to .devin/ (Devin Desktop preferred dir, #1085).
// Legacy .windsurf/ is still recognized on read but new installs use .devin/.
converted = converted.replace(/`\.\/CLAUDE\.md`/g, '`.devin/rules`');
converted = converted.replace(/\.\/CLAUDE\.md/g, '.devin/rules');
converted = converted.replace(/`CLAUDE\.md`/g, '`.devin/rules`');
converted = converted.replace(/\bCLAUDE\.md\b/g, '.devin/rules');
converted = converted.replace(/\.claude\/skills\//g, '.devin/skills/');
converted = converted.replace(/\.\/\.claude\//g, './.devin/');
converted = converted.replace(/\.claude\//g, '.devin/');
// 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\/skills\//g, '.windsurf/skills/');
converted = converted.replace(/\.\/\.claude\//g, './.windsurf/');
converted = converted.replace(/\.claude\//g, '.windsurf/');
// Bare forms (no trailing slash) — after slash forms to avoid double-rewrite.
// Use negative lookahead (?![\w-]) to preserve .claude-plugin and .claudeignore.
converted = converted.replace(/~\/\.claude(?![\w-])/g, '~/.devin');
converted = converted.replace(/\$HOME\/\.claude(?![\w-])/g, '$HOME/.devin');
converted = converted.replace(/~\/\.claude(?![\w-])/g, '~/.windsurf');
converted = converted.replace(/\$HOME\/\.claude(?![\w-])/g, '$HOME/.windsurf');
// Environment variable name rewrite
converted = converted.replace(/\bCLAUDE_CONFIG_DIR\b/g, 'WINDSURF_CONFIG_DIR');
// Remove Claude Code-specific bug workarounds before brand replacement
@@ -1008,6 +1006,19 @@ function convertClaudeCommandToWindsurfSkill(content, skillName) {
return `---\nname: ${yamlIdentifier(skillName)}\ndescription: ${yamlQuote(shortDescription)}\n---\n\n${adapter}\n\n${body.trimStart()}`;
}
function convertClaudeCommandToWindsurfWorkflow(content, commandName) {
const converted = convertClaudeToWindsurfMarkdown(content);
const { frontmatter } = extractFrontmatterAndBody(converted);
const description = frontmatter ? extractFrontmatterField(frontmatter, 'description') : '';
const stem = commandName.startsWith('gsd-') ? commandName.slice(4) : commandName;
const workflow = `# ${commandName}\n\n${toSingleLine(description || `Run ${commandName}.`)}\n\nRead and execute the GSD command at @~/.claude/gsd-core/commands/gsd/${stem}.md end-to-end. Treat the user's message after /${commandName} as the command arguments.`;
const byteLength = Buffer.byteLength(workflow, 'utf8');
if (byteLength > 12000) {
throw new Error(`Windsurf workflow ${commandName} exceeds 12000 bytes (${byteLength}); extract references before installing`);
}
return workflow;
}
// --- Augment converters ---
// Augment uses a tool set similar to Cursor/Windsurf.
// Config lives in .augment/ (local) and ~/.augment/ (global).
@@ -2568,6 +2579,7 @@ export = {
convertClaudeCommandToCursorCommand,
convertClaudeToWindsurfMarkdown,
convertClaudeCommandToWindsurfSkill,
convertClaudeCommandToWindsurfWorkflow,
convertClaudeToAugmentMarkdown,
convertClaudeCommandToAugmentSkill,
convertClaudeToTraeMarkdown,