diff --git a/scripts/profiles/base-profile.js b/scripts/profiles/base-profile.js new file mode 100644 index 00000000..b39dd38a --- /dev/null +++ b/scripts/profiles/base-profile.js @@ -0,0 +1,221 @@ +// Base profile factory for rule-transformer +import path from 'path'; + +/** + * Creates a standardized profile configuration for different editors + * @param {Object} editorConfig - Editor-specific configuration + * @returns {Object} - Complete profile configuration + */ +export function createProfile(editorConfig) { + const { + name, + displayName = name, + url, + docsUrl, + profileDir, + rulesDir = `${profileDir}/rules`, + mcpConfig = true, + mcpConfigName = 'mcp.json', + fileExtension = '.mdc', + targetExtension = '.md', + toolMappings = {}, + customReplacements = [], + customFileMap = {}, + onAdd, + onRemove, + onPostConvert + } = editorConfig; + + const mcpConfigPath = `${profileDir}/${mcpConfigName}`; + + // Standard file mapping with custom overrides + const defaultFileMap = { + 'cursor_rules.mdc': `${name.toLowerCase()}_rules${targetExtension}`, + 'dev_workflow.mdc': `dev_workflow${targetExtension}`, + 'self_improve.mdc': `self_improve${targetExtension}`, + 'taskmaster.mdc': `taskmaster${targetExtension}` + }; + + const fileMap = { ...defaultFileMap, ...customFileMap }; + + // Base global replacements that work for all editors + const baseGlobalReplacements = [ + // Handle URLs in any context + { from: /cursor\.so/gi, to: url }, + { from: /cursor\s*\.\s*so/gi, to: url }, + { from: /https?:\/\/cursor\.so/gi, to: `https://${url}` }, + { from: /https?:\/\/www\.cursor\.so/gi, to: `https://www.${url}` }, + + // Handle tool references + { from: /\bedit_file\b/gi, to: toolMappings.edit_file || 'edit_file' }, + { + from: /\bsearch tool\b/gi, + to: `${toolMappings.search || 'search'} tool` + }, + { from: /\bSearch Tool\b/g, to: `${toolMappings.search || 'Search'} Tool` }, + + // Handle basic terms with proper case handling + { + from: /\bcursor\b/gi, + to: (match) => + match.charAt(0) === 'C' ? displayName : name.toLowerCase() + }, + { from: /Cursor/g, to: displayName }, + { from: /CURSOR/g, to: displayName.toUpperCase() }, + + // Handle file extensions if different + ...(targetExtension !== fileExtension + ? [ + { + from: new RegExp(`\\${fileExtension}\\b`, 'g'), + to: targetExtension + } + ] + : []), + + // Handle documentation URLs + { from: /docs\.cursor\.com/gi, to: docsUrl }, + + // Custom editor-specific replacements + ...customReplacements + ]; + + // Standard tool mappings + const defaultToolMappings = { + search: 'search', + read_file: 'read_file', + edit_file: 'edit_file', + create_file: 'create_file', + run_command: 'run_command', + terminal_command: 'terminal_command', + use_mcp: 'use_mcp', + switch_mode: 'switch_mode', + ...toolMappings + }; + + // Create conversion config + const conversionConfig = { + // Profile name replacements + profileTerms: [ + { from: /cursor\.so/g, to: url }, + { from: /\[cursor\.so\]/g, to: `[${url}]` }, + { from: /href="https:\/\/cursor\.so/g, to: `href="https://${url}` }, + { from: /\(https:\/\/cursor\.so/g, to: `(https://${url}` }, + { + from: /\bcursor\b/gi, + to: (match) => (match === 'Cursor' ? displayName : name.toLowerCase()) + }, + { from: /Cursor/g, to: displayName } + ], + + // File extension replacements + fileExtensions: + targetExtension !== fileExtension + ? [ + { + from: new RegExp(`\\${fileExtension}\\b`, 'g'), + to: targetExtension + } + ] + : [], + + // Documentation URL replacements + docUrls: [ + { + from: new RegExp(`https:\\/\\/docs\\.cursor\\.com\\/[^\\s)'\"]+`, 'g'), + to: (match) => match.replace('docs.cursor.com', docsUrl) + }, + { + from: new RegExp(`https:\\/\\/${docsUrl}\\/`, 'g'), + to: `https://${docsUrl}/` + } + ], + + // Tool references - direct replacements + toolNames: defaultToolMappings, + + // Tool references in context - more specific replacements + toolContexts: Object.entries(defaultToolMappings).flatMap( + ([original, mapped]) => [ + { + from: new RegExp(`\\b${original} tool\\b`, 'g'), + to: `${mapped} tool` + }, + { from: new RegExp(`\\bthe ${original}\\b`, 'g'), to: `the ${mapped}` }, + { from: new RegExp(`\\bThe ${original}\\b`, 'g'), to: `The ${mapped}` }, + { + from: new RegExp(`\\bCursor ${original}\\b`, 'g'), + to: `${displayName} ${mapped}` + } + ] + ), + + // Tool group and category names + toolGroups: [ + { from: /\bSearch tools\b/g, to: 'Read Group tools' }, + { from: /\bEdit tools\b/g, to: 'Edit Group tools' }, + { from: /\bRun tools\b/g, to: 'Command Group tools' }, + { from: /\bMCP servers\b/g, to: 'MCP Group tools' }, + { from: /\bSearch Group\b/g, to: 'Read Group' }, + { from: /\bEdit Group\b/g, to: 'Edit Group' }, + { from: /\bRun Group\b/g, to: 'Command Group' } + ], + + // File references in markdown links + fileReferences: { + pathPattern: /\[(.+?)\]\(mdc:\.cursor\/rules\/(.+?)\.mdc\)/g, + replacement: (match, text, filePath) => { + const baseName = path.basename(filePath, '.mdc'); + const newFileName = + fileMap[`${baseName}.mdc`] || `${baseName}${targetExtension}`; + return `[${text}](mdc:${rulesDir}/${newFileName})`; + } + } + }; + + function getTargetRuleFilename(sourceFilename) { + if (fileMap[sourceFilename]) { + return fileMap[sourceFilename]; + } + return targetExtension !== fileExtension + ? sourceFilename.replace( + new RegExp(`\\${fileExtension}$`), + targetExtension + ) + : sourceFilename; + } + + return { + profileName: name, // Use name for programmatic access (tests expect this) + displayName: displayName, // Keep displayName for UI purposes + profileDir, + rulesDir, + mcpConfig, + mcpConfigName, + mcpConfigPath, + fileMap, + globalReplacements: baseGlobalReplacements, + conversionConfig, + getTargetRuleFilename, + // Optional lifecycle hooks + ...(onAdd && { onAddRulesProfile: onAdd }), + ...(onRemove && { onRemoveRulesProfile: onRemove }), + ...(onPostConvert && { onPostConvertRulesProfile: onPostConvert }) + }; +} + +// Common tool mappings for editors that share similar tool sets +export const COMMON_TOOL_MAPPINGS = { + // Most editors (Cursor, Cline, Windsurf) keep original tool names + STANDARD: {}, + + // Roo Code uses different tool names + ROO_STYLE: { + edit_file: 'apply_diff', + search: 'search_files', + create_file: 'write_to_file', + run_command: 'execute_command', + terminal_command: 'execute_command', + use_mcp: 'use_mcp_tool' + } +}; diff --git a/scripts/profiles/cline.js b/scripts/profiles/cline.js index 46f26e52..ca3a372e 100644 --- a/scripts/profiles/cline.js +++ b/scripts/profiles/cline.js @@ -1,139 +1,26 @@ // Cline conversion profile for rule-transformer -import path from 'path'; +import { createProfile, COMMON_TOOL_MAPPINGS } from './base-profile.js'; -const profileName = 'Cline'; -const profileDir = '.clinerules'; -const rulesDir = '.clinerules'; -const mcpConfig = false; -const mcpConfigName = 'cline_mcp_settings.json'; -const mcpConfigPath = `${profileDir}/${mcpConfigName}`; - -// File name mapping (specific files with naming changes) -const fileMap = { - 'cursor_rules.mdc': 'cline_rules.md', - 'dev_workflow.mdc': 'dev_workflow.md', - 'self_improve.mdc': 'self_improve.md', - 'taskmaster.mdc': 'taskmaster.md' - // Add other mappings as needed -}; - -const globalReplacements = [ - // 1. Handle cursor.so in any possible context - { from: /cursor\.so/gi, to: 'cline.bot' }, - // Edge case: URL with different formatting - { from: /cursor\s*\.\s*so/gi, to: 'cline.bot' }, - { from: /https?:\/\/cursor\.so/gi, to: 'https://cline.bot' }, - { from: /https?:\/\/www\.cursor\.so/gi, to: 'https://www.cline.bot' }, - // 2. Handle tool references - even partial ones - // NOTE: Cline might have different tool names, adjust as needed - { from: /\bedit_file\b/gi, to: 'apply_diff' }, // Example, assuming same as Windsurf for now - { from: /\bsearch tool\b/gi, to: 'search_files tool' }, // Example - { from: /\bSearch Tool\b/g, to: 'Search_Files Tool' }, // Example - // 3. Handle basic terms (with case handling) - { - from: /\bcursor\b/gi, - to: (match) => (match.charAt(0) === 'C' ? 'Cline' : 'cline') - }, - { from: /Cursor/g, to: 'Cline' }, - { from: /CURSOR/g, to: 'CLINE' }, - // 4. Handle file extensions - { from: /\.mdc\b/g, to: '.md' }, - // 5. Handle any missed URL patterns - { from: /docs\.cursor\.com/gi, to: 'docs.cline.bot' }, - { from: /docs\.cline\.com/gi, to: 'docs.cline.bot' } // Keep if Cline also uses docs.cline.bot -]; - -const conversionConfig = { - // Profile name replacements - profileTerms: [ - { from: /cursor\.so/g, to: 'cline.bot' }, - { from: /\[cursor\.so\]/g, to: '[cline.bot]' }, - { from: /href="https:\/\/cursor\.so/g, to: 'href="https://cline.bot' }, - { from: /\(https:\/\/cursor\.so/g, to: '(https://cline.bot' }, - { - from: /\bcursor\b/gi, - to: (match) => (match === 'Cursor' ? 'Cline' : 'cline') - }, - { from: /Cursor/g, to: 'Cline' } - ], - - // File extension replacements - fileExtensions: [{ from: /\.mdc\b/g, to: '.md' }], - - // Documentation URL replacements - docUrls: [ - { - from: /https:\/\/docs\.cursor\.com\/[\^\s)\'"\\]+/g, - to: (match) => match.replace('docs.cursor.com', 'docs.cline.bot') - }, - { - from: /https:\/\/docs\.cline\.com\//g, // Adjusted for Cline - to: 'https://docs.cline.bot/' - } - ], - - // Tool references - direct replacements - // NOTE: These might need to be updated for Cline's specific toolset - toolNames: { - search: 'search_files', // Example, assuming same as Windsurf for now - read_file: 'read_file', - edit_file: 'apply_diff', - create_file: 'write_to_file', - run_command: 'execute_command', - terminal_command: 'execute_command', - use_mcp: 'use_mcp_tool', - switch_mode: 'switch_mode' - }, - - // Tool references in context - more specific replacements - // NOTE: Adjust these based on Cline's tool names and usage - toolContexts: [ - { from: /\bsearch tool\b/g, to: 'search_files tool' }, - { from: /\bedit_file tool\b/g, to: 'apply_diff tool' }, - { from: /\buse the search\b/g, to: 'use the search_files' }, - { from: /\bThe edit_file\b/g, to: 'The apply_diff' }, - { from: /\brun_command executes\b/g, to: 'execute_command executes' }, - { from: /\buse_mcp connects\b/g, to: 'use_mcp_tool connects' }, - { from: /\bCursor search\b/g, to: 'Cline search_files' }, - { from: /\bCursor edit\b/g, to: 'Cline apply_diff' }, - { from: /\bCursor create\b/g, to: 'Cline write_to_file' }, - { from: /\bCursor run\b/g, to: 'Cline execute_command' } - ], - - // Tool group and category names - // NOTE: Adjust these if Cline has different group names - toolGroups: [ - { from: /\bSearch tools\b/g, to: 'Read Group tools' }, - { from: /\bEdit tools\b/g, to: 'Edit Group tools' }, - { from: /\bRun tools\b/g, to: 'Command Group tools' }, - { from: /\bMCP servers\b/g, to: 'MCP Group tools' }, - { from: /\bSearch Group\b/g, to: 'Read Group' }, - { from: /\bEdit Group\b/g, to: 'Edit Group' }, - { from: /\bRun Group\b/g, to: 'Command Group' } - ], - - // File references in markdown links - fileReferences: { - pathPattern: /\[(.+?)\]\(mdc:\.cursor\/rules\/(.+?)\.mdc\)/g, - replacement: (match, text, filePath) => { - // Get the base filename - const baseName = path.basename(filePath, '.mdc'); - // Get the new filename (either from mapping or by replacing extension) - const newFileName = fileMap[`${baseName}.mdc`] || `${baseName}.md`; // Uses 'cline_rules.md' for cursor_rules.mdc - // Return the updated link - return `[${text}](mdc:.clinerules/${newFileName})`; // Adjusted rulesDir - } +// Create cline profile using the base factory +const clineProfile = createProfile({ + name: 'cline', + displayName: 'Cline', + url: 'cline.bot', + docsUrl: 'docs.cline.bot', + profileDir: '.clinerules', + rulesDir: '.clinerules', + mcpConfig: false, + mcpConfigName: 'cline_mcp_settings.json', + fileExtension: '.mdc', + targetExtension: '.md', + toolMappings: COMMON_TOOL_MAPPINGS.ROO_STYLE, // Cline uses transformed tool names + customFileMap: { + 'cursor_rules.mdc': 'cline_rules.md' } -}; +}); -function getTargetRuleFilename(sourceFilename) { - if (fileMap[sourceFilename]) { - return fileMap[sourceFilename]; - } - return sourceFilename.replace(/\.mdc$/, '.md'); -} - -export { +// Export all the standard profile properties +export const { conversionConfig, fileMap, globalReplacements, @@ -144,4 +31,4 @@ export { mcpConfigName, mcpConfigPath, getTargetRuleFilename -}; +} = clineProfile; diff --git a/scripts/profiles/cursor.js b/scripts/profiles/cursor.js index 372c836b..00d7d167 100644 --- a/scripts/profiles/cursor.js +++ b/scripts/profiles/cursor.js @@ -1,85 +1,26 @@ // Cursor conversion profile for rule-transformer -import path from 'path'; +import { createProfile, COMMON_TOOL_MAPPINGS } from './base-profile.js'; -const profileName = 'Cursor'; -const profileDir = '.cursor'; -const rulesDir = '.cursor/rules'; -const mcpConfig = true; -const mcpConfigName = 'mcp.json'; -const mcpConfigPath = `${profileDir}/${mcpConfigName}`; - -// File name mapping (specific files with naming changes) -const fileMap = { - 'cursor_rules.mdc': 'cursor_rules.mdc', - 'dev_workflow.mdc': 'dev_workflow.mdc', - 'self_improve.mdc': 'self_improve.mdc', - 'taskmaster.mdc': 'taskmaster.mdc' - // Add other mappings as needed -}; - -const globalReplacements = [ - // 1. Handle cursor.so in any possible context - { from: /cursor\.so/gi, to: 'cursor.so' }, - // Edge case: URL with different formatting - { from: /cursor\s*\.\s*so/gi, to: 'cursor.so' }, - { from: /https?:\/\/cursor\.so/gi, to: 'https://cursor.so' }, - { from: /https?:\/\/www\.cursor\.so/gi, to: 'https://www.cursor.so' }, - // 2. Handle tool references - even partial ones - { from: /\bedit_file\b/gi, to: 'edit_file' }, - { from: /\bsearch tool\b/gi, to: 'search tool' }, - { from: /\bSearch Tool\b/g, to: 'Search Tool' }, - // 3. Handle basic terms (with case handling) - { - from: /\bcursor\b/gi, - to: (match) => (match.charAt(0) === 'C' ? 'Cursor' : 'cursor') - }, - { from: /Cursor/g, to: 'Cursor' }, - { from: /CURSOR/g, to: 'CURSOR' }, - // 5. Handle any missed URL patterns - { from: /docs\.cursor\.com/gi, to: 'docs.cursor.com' } -]; - -const conversionConfig = { - // Profile name replacements - profileTerms: [ - { from: /cursor\.so/g, to: 'cursor.so' }, - { from: /\[cursor\.so\]/g, to: '[cursor.so]' }, - { from: /href="https:\/\/cursor\.so/g, to: 'href="https://cursor.so' }, - { from: /\(https:\/\/cursor\.so/g, to: '(https://cursor.so' }, - { - from: /\bcursor\b/gi, - to: (match) => (match === 'Cursor' ? 'Cursor' : 'cursor') - }, - { from: /Cursor/g, to: 'Cursor' } - ], - - // File extension replacements - fileExtensions: [], - - // Documentation URL replacements - docUrls: [ - { - from: /https:\/\/docs\.cursor\.com\/[\^\s)\'"\\]+/g, - to: (match) => match - }, - { from: /https:\/\/docs\.cursor\.com\//g, to: 'https://docs.cursor.com/' } - ], - - // Required for transformer compatibility - toolNames: {}, - toolContexts: [], - toolGroups: [], - fileReferences: { - pathPattern: /$^/, // matches nothing - replacement: '' +// Create cursor profile using the base factory +const cursorProfile = createProfile({ + name: 'cursor', + displayName: 'Cursor', + url: 'cursor.so', + docsUrl: 'docs.cursor.com', + profileDir: '.cursor', + rulesDir: '.cursor/rules', + mcpConfig: true, + mcpConfigName: 'mcp.json', + fileExtension: '.mdc', + targetExtension: '.mdc', // Cursor keeps .mdc extension + toolMappings: COMMON_TOOL_MAPPINGS.STANDARD, + customFileMap: { + 'cursor_rules.mdc': 'cursor_rules.mdc' // Keep the same name for cursor } -}; +}); -function getTargetRuleFilename(sourceFilename) { - return fileMap[sourceFilename] || sourceFilename; -} - -export { +// Export all the standard profile properties +export const { conversionConfig, fileMap, globalReplacements, @@ -90,4 +31,4 @@ export { mcpConfigName, mcpConfigPath, getTargetRuleFilename -}; +} = cursorProfile; diff --git a/scripts/profiles/roo.js b/scripts/profiles/roo.js index 24731361..231ca045 100644 --- a/scripts/profiles/roo.js +++ b/scripts/profiles/roo.js @@ -1,134 +1,12 @@ // Roo Code conversion profile for rule-transformer -import { fileURLToPath } from 'url'; import path from 'path'; import fs from 'fs'; import { isSilentMode, log } from '../modules/utils.js'; +import { createProfile, COMMON_TOOL_MAPPINGS } from './base-profile.js'; -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); - -const profileName = 'Roo'; -const profileDir = '.roo'; -const rulesDir = '.roo/rules'; -const mcpConfig = true; -const mcpConfigName = 'mcp.json'; -const mcpConfigPath = `${profileDir}/${mcpConfigName}`; - -// File name mapping (specific files with naming changes) -const fileMap = { - 'cursor_rules.mdc': 'roo_rules.md', - 'dev_workflow.mdc': 'dev_workflow.md', - 'self_improve.mdc': 'self_improve.md', - 'taskmaster.mdc': 'taskmaster.md' - // Add other mappings as needed -}; - -const globalReplacements = [ - // 1. Handle cursor.so in any possible context - { from: /cursor\.so/gi, to: 'roocode.com' }, - // Edge case: URL with different formatting - { from: /cursor\s*\.\s*so/gi, to: 'roocode.com' }, - { from: /https?:\/\/cursor\.so/gi, to: 'https://roocode.com' }, - { from: /https?:\/\/www\.cursor\.so/gi, to: 'https://www.roocode.com' }, - // 2. Handle tool references - even partial ones - { from: /\bedit_file\b/gi, to: 'apply_diff' }, - { from: /\bsearch tool\b/gi, to: 'search_files tool' }, - { from: /\bSearch Tool\b/g, to: 'Search_Files Tool' }, - // 3. Handle basic terms (with case handling) - { - from: /\bcursor\b/gi, - to: (match) => (match.charAt(0) === 'C' ? 'Roo Code' : 'roo') - }, - { from: /Cursor/g, to: 'Roo Code' }, - { from: /CURSOR/g, to: 'ROO CODE' }, - // 4. Handle file extensions - { from: /\.mdc\b/g, to: '.md' }, - // 5. Handle any missed URL patterns - { from: /docs\.cursor\.com/gi, to: 'docs.roocode.com' }, - { from: /docs\.roo\.com/gi, to: 'docs.roocode.com' } -]; - -const conversionConfig = { - // Profile name replacements - profileTerms: [ - { from: /cursor\.so/g, to: 'roocode.com' }, - { from: /\[cursor\.so\]/g, to: '[roocode.com]' }, - { from: /href="https:\/\/cursor\.so/g, to: 'href="https://roocode.com' }, - { from: /\(https:\/\/cursor\.so/g, to: '(https://roocode.com' }, - { - from: /\bcursor\b/gi, - to: (match) => (match === 'Cursor' ? 'Roo Code' : 'roo') - }, - { from: /Cursor/g, to: 'Roo Code' } - ], - - // File extension replacements - fileExtensions: [{ from: /\.mdc\b/g, to: '.md' }], - - // Documentation URL replacements - docUrls: [ - { - from: /https:\/\/docs\.cursor\.com\/[^\s)'\"]+/g, - to: (match) => match.replace('docs.cursor.com', 'docs.roocode.com') - }, - { from: /https:\/\/docs\.roo\.com\//g, to: 'https://docs.roocode.com/' } - ], - - // Tool references - direct replacements - toolNames: { - search: 'search_files', - read_file: 'read_file', - edit_file: 'apply_diff', - create_file: 'write_to_file', - run_command: 'execute_command', - terminal_command: 'execute_command', - use_mcp: 'use_mcp_tool', - switch_mode: 'switch_mode' - }, - - // Tool references in context - more specific replacements - toolContexts: [ - { from: /\bsearch tool\b/g, to: 'search_files tool' }, - { from: /\bedit_file tool\b/g, to: 'apply_diff tool' }, - { from: /\buse the search\b/g, to: 'use the search_files' }, - { from: /\bThe edit_file\b/g, to: 'The apply_diff' }, - { from: /\brun_command executes\b/g, to: 'execute_command executes' }, - { from: /\buse_mcp connects\b/g, to: 'use_mcp_tool connects' }, - { from: /\bCursor search\b/g, to: 'Roo Code search_files' }, - { from: /\bCursor edit\b/g, to: 'Roo Code apply_diff' }, - { from: /\bCursor create\b/g, to: 'Roo Code write_to_file' }, - { from: /\bCursor run\b/g, to: 'Roo Code execute_command' } - ], - - // Tool group and category names - toolGroups: [ - { from: /\bSearch tools\b/g, to: 'Read Group tools' }, - { from: /\bEdit tools\b/g, to: 'Edit Group tools' }, - { from: /\bRun tools\b/g, to: 'Command Group tools' }, - { from: /\bMCP servers\b/g, to: 'MCP Group tools' }, - { from: /\bSearch Group\b/g, to: 'Read Group' }, - { from: /\bEdit Group\b/g, to: 'Edit Group' }, - { from: /\bRun Group\b/g, to: 'Command Group' } - ], - - // File references in markdown links - fileReferences: { - pathPattern: /\[(.+?)\]\(mdc:\.cursor\/rules\/(.+?)\.mdc\)/g, - replacement: (match, text, filePath) => { - // Get the base filename - const baseName = path.basename(filePath, '.mdc'); - // Get the new filename (either from mapping or by replacing extension) - const newFileName = fileMap[`${baseName}.mdc`] || `${baseName}.md`; - // Return the updated link - return `[${text}](mdc:.roo/rules/${newFileName})`; - } - } -}; - -// Recursively copy everything from assets/roocode to the project root - -export function onAddRulesProfile(targetDir) { - const sourceDir = path.resolve(__dirname, '../../assets/roocode'); +// Lifecycle functions for Roo profile +function onAddRulesProfile(targetDir) { + const sourceDir = path.join(process.cwd(), 'assets', 'roocode'); copyRecursiveSync(sourceDir, targetDir); const rooModesDir = path.join(sourceDir, '.roo'); @@ -183,7 +61,7 @@ function copyRecursiveSync(src, dest) { } } -export function onRemoveRulesProfile(targetDir) { +function onRemoveRulesProfile(targetDir) { log('debug', `[Roo] onRemoveRulesProfile called for ${targetDir}`); const roomodesPath = path.join(targetDir, '.roomodes'); if (fs.existsSync(roomodesPath)) { @@ -220,22 +98,33 @@ export function onRemoveRulesProfile(targetDir) { log('debug', `[Roo] onRemoveRulesProfile completed for ${targetDir}`); } -function isDirectoryEmpty(dirPath) { - return fs.readdirSync(dirPath).length === 0; -} - function onPostConvertRulesProfile(targetDir) { onAddRulesProfile(targetDir); } -function getTargetRuleFilename(sourceFilename) { - if (fileMap[sourceFilename]) { - return fileMap[sourceFilename]; - } - return sourceFilename.replace(/\.mdc$/, '.md'); -} +// Create roo profile using the base factory +const rooProfile = createProfile({ + name: 'roo', + displayName: 'Roo Code', + url: 'roocode.com', + docsUrl: 'docs.roocode.com', + profileDir: '.roo', + rulesDir: '.roo/rules', + mcpConfig: true, + mcpConfigName: 'mcp.json', + fileExtension: '.mdc', + targetExtension: '.md', + toolMappings: COMMON_TOOL_MAPPINGS.ROO_STYLE, + customFileMap: { + 'cursor_rules.mdc': 'roo_rules.md' + }, + onAdd: onAddRulesProfile, + onRemove: onRemoveRulesProfile, + onPostConvert: onPostConvertRulesProfile +}); -export { +// Export all the standard profile properties and lifecycle functions +export const { conversionConfig, fileMap, globalReplacements, @@ -245,6 +134,8 @@ export { mcpConfig, mcpConfigName, mcpConfigPath, - getTargetRuleFilename, - onPostConvertRulesProfile -}; + getTargetRuleFilename +} = rooProfile; + +// Export lifecycle functions separately to avoid naming conflicts +export { onAddRulesProfile, onRemoveRulesProfile, onPostConvertRulesProfile }; diff --git a/scripts/profiles/windsurf.js b/scripts/profiles/windsurf.js index 21dd3e7e..2f030e8d 100644 --- a/scripts/profiles/windsurf.js +++ b/scripts/profiles/windsurf.js @@ -1,135 +1,23 @@ // Windsurf conversion profile for rule-transformer -import path from 'path'; +import { createProfile, COMMON_TOOL_MAPPINGS } from './base-profile.js'; -const profileName = 'Windsurf'; -const profileDir = '.windsurf'; -const rulesDir = '.windsurf/rules'; -const mcpConfig = true; -const mcpConfigName = 'mcp.json'; -const mcpConfigPath = `${profileDir}/${mcpConfigName}`; +// Create windsurf profile using the base factory +const windsurfProfile = createProfile({ + name: 'windsurf', + displayName: 'Windsurf', + url: 'windsurf.com', + docsUrl: 'docs.windsurf.com', + profileDir: '.windsurf', + rulesDir: '.windsurf/rules', + mcpConfig: true, + mcpConfigName: 'mcp.json', + fileExtension: '.mdc', + targetExtension: '.md', + toolMappings: COMMON_TOOL_MAPPINGS.ROO_STYLE // Windsurf uses transformed tool names +}); -// File name mapping (specific files with naming changes) -const fileMap = { - 'cursor_rules.mdc': 'windsurf_rules.md', - 'dev_workflow.mdc': 'dev_workflow.md', - 'self_improve.mdc': 'self_improve.md', - 'taskmaster.mdc': 'taskmaster.md' - // Add other mappings as needed -}; - -const globalReplacements = [ - // 1. Handle cursor.so in any possible context - { from: /cursor\.so/gi, to: 'windsurf.com' }, - // Edge case: URL with different formatting - { from: /cursor\s*\.\s*so/gi, to: 'windsurf.com' }, - { from: /https?:\/\/cursor\.so/gi, to: 'https://windsurf.com' }, - { from: /https?:\/\/www\.cursor\.so/gi, to: 'https://www.windsurf.com' }, - // 2. Handle tool references - even partial ones - { from: /\bedit_file\b/gi, to: 'apply_diff' }, - { from: /\bsearch tool\b/gi, to: 'search_files tool' }, - { from: /\bSearch Tool\b/g, to: 'Search_Files Tool' }, - // 3. Handle basic terms (with case handling) - { - from: /\bcursor\b/gi, - to: (match) => (match.charAt(0) === 'C' ? 'Windsurf' : 'windsurf') - }, - { from: /Cursor/g, to: 'Windsurf' }, - { from: /CURSOR/g, to: 'WINDSURF' }, - // 4. Handle file extensions - { from: /\.mdc\b/g, to: '.md' }, - // 5. Handle any missed URL patterns - { from: /docs\.cursor\.com/gi, to: 'docs.windsurf.com' }, - { from: /docs\.windsurf\.com/gi, to: 'docs.windsurf.com' } -]; - -const conversionConfig = { - // Profile name replacements - profileTerms: [ - { from: /cursor\.so/g, to: 'windsurf.com' }, - { from: /\[cursor\.so\]/g, to: '[windsurf.com]' }, - { from: /href="https:\/\/cursor\.so/g, to: 'href="https://windsurf.com' }, - { from: /\(https:\/\/cursor\.so/g, to: '(https://windsurf.com' }, - { - from: /\bcursor\b/gi, - to: (match) => (match === 'Cursor' ? 'Windsurf' : 'windsurf') - }, - { from: /Cursor/g, to: 'Windsurf' } - ], - - // File extension replacements - fileExtensions: [{ from: /\.mdc\b/g, to: '.md' }], - - // Documentation URL replacements - docUrls: [ - { - from: /https:\/\/docs\.cursor\.com\/[\^\s)\'"\\]+/g, - to: (match) => match.replace('docs.cursor.com', 'docs.windsurf.com') - }, - { - from: /https:\/\/docs\.windsurf\.com\//g, - to: 'https://docs.windsurf.com/' - } - ], - - // Tool references - direct replacements - toolNames: { - search: 'search_files', - read_file: 'read_file', - edit_file: 'apply_diff', - create_file: 'write_to_file', - run_command: 'execute_command', - terminal_command: 'execute_command', - use_mcp: 'use_mcp_tool', - switch_mode: 'switch_mode' - }, - - // Tool references in context - more specific replacements - toolContexts: [ - { from: /\bsearch tool\b/g, to: 'search_files tool' }, - { from: /\bedit_file tool\b/g, to: 'apply_diff tool' }, - { from: /\buse the search\b/g, to: 'use the search_files' }, - { from: /\bThe edit_file\b/g, to: 'The apply_diff' }, - { from: /\brun_command executes\b/g, to: 'execute_command executes' }, - { from: /\buse_mcp connects\b/g, to: 'use_mcp_tool connects' }, - { from: /\bCursor search\b/g, to: 'Windsurf search_files' }, - { from: /\bCursor edit\b/g, to: 'Windsurf apply_diff' }, - { from: /\bCursor create\b/g, to: 'Windsurf write_to_file' }, - { from: /\bCursor run\b/g, to: 'Windsurf execute_command' } - ], - - // Tool group and category names - toolGroups: [ - { from: /\bSearch tools\b/g, to: 'Read Group tools' }, - { from: /\bEdit tools\b/g, to: 'Edit Group tools' }, - { from: /\bRun tools\b/g, to: 'Command Group tools' }, - { from: /\bMCP servers\b/g, to: 'MCP Group tools' }, - { from: /\bSearch Group\b/g, to: 'Read Group' }, - { from: /\bEdit Group\b/g, to: 'Edit Group' }, - { from: /\bRun Group\b/g, to: 'Command Group' } - ], - - // File references in markdown links - fileReferences: { - pathPattern: /\[(.+?)\]\(mdc:\.cursor\/rules\/(.+?)\.mdc\)/g, - replacement: (match, text, filePath) => { - // Get the base filename - const baseName = path.basename(filePath, '.mdc'); - // Get the new filename (either from mapping or by replacing extension) - const newFileName = fileMap[`${baseName}.mdc`] || `${baseName}.md`; - // Return the updated link - return `[${text}](mdc:.windsurf/rules/${newFileName})`; - } - } -}; - -function getTargetRuleFilename(sourceFilename) { - if (fileMap[sourceFilename]) { - return fileMap[sourceFilename]; - } - return sourceFilename.replace(/\.mdc$/, '.md'); -} - -export { +// Export all the standard profile properties +export const { conversionConfig, fileMap, globalReplacements, @@ -140,4 +28,4 @@ export { mcpConfigName, mcpConfigPath, getTargetRuleFilename -}; +} = windsurfProfile; diff --git a/tests/integration/cursor-init-functionality.test.js b/tests/integration/cursor-init-functionality.test.js index 0e8a9e49..c5426f79 100644 --- a/tests/integration/cursor-init-functionality.test.js +++ b/tests/integration/cursor-init-functionality.test.js @@ -14,25 +14,31 @@ describe('Cursor Profile Initialization Functionality', () => { cursorProfileContent = fs.readFileSync(cursorJsPath, 'utf8'); }); - test('cursor.js exports correct profileName and rulesDir', () => { - expect(cursorProfileContent).toContain("const profileName = 'Cursor'"); - expect(cursorProfileContent).toContain("const rulesDir = '.cursor/rules'"); + test('cursor.js uses factory pattern with correct configuration', () => { + expect(cursorProfileContent).toContain("name: 'cursor'"); + expect(cursorProfileContent).toContain("displayName: 'Cursor'"); + expect(cursorProfileContent).toContain("rulesDir: '.cursor/rules'"); + expect(cursorProfileContent).toContain("profileDir: '.cursor'"); }); - test('cursor.js preserves .mdc filenames in fileMap', () => { - expect(cursorProfileContent).toContain('fileMap = {'); - // Should NOT contain any .md mapping - expect(cursorProfileContent).not.toMatch(/\.md'/); + test('cursor.js preserves .mdc extension in both input and output', () => { + expect(cursorProfileContent).toContain("fileExtension: '.mdc'"); + expect(cursorProfileContent).toContain("targetExtension: '.mdc'"); + // Should preserve cursor_rules.mdc filename + expect(cursorProfileContent).toContain( + "'cursor_rules.mdc': 'cursor_rules.mdc'" + ); }); - test('cursor.js contains tool naming logic and global replacements', () => { - expect(cursorProfileContent).toContain('edit_file'); - expect(cursorProfileContent).toContain('search tool'); + test('cursor.js uses standard tool mappings (no tool renaming)', () => { + expect(cursorProfileContent).toContain('COMMON_TOOL_MAPPINGS.STANDARD'); + // Should not contain custom tool mappings since cursor keeps original names + expect(cursorProfileContent).not.toContain('edit_file'); expect(cursorProfileContent).not.toContain('apply_diff'); - expect(cursorProfileContent).not.toContain('search_files tool'); }); - test('cursor.js contains correct documentation URL logic', () => { - expect(cursorProfileContent).toContain('docs.cursor.com'); + test('cursor.js contains correct URL configuration', () => { + expect(cursorProfileContent).toContain("url: 'cursor.so'"); + expect(cursorProfileContent).toContain("docsUrl: 'docs.cursor.com'"); }); }); diff --git a/tests/integration/roo-files-inclusion.test.js b/tests/integration/roo-files-inclusion.test.js index deea8265..b1dfe3b3 100644 --- a/tests/integration/roo-files-inclusion.test.js +++ b/tests/integration/roo-files-inclusion.test.js @@ -29,6 +29,11 @@ describe('Roo Files Inclusion in Package', () => { rooJsContent.includes('copyRecursiveSync(sourceDir, targetDir)') ).toBe(true); + // Check for updated path handling + expect( + rooJsContent.includes("path.join(process.cwd(), 'assets', 'roocode')") + ).toBe(true); + // Check for .roomodes file copying logic (source and destination paths) expect(rooJsContent.includes("path.join(sourceDir, '.roomodes')")).toBe( true diff --git a/tests/integration/roo-init-functionality.test.js b/tests/integration/roo-init-functionality.test.js index 02513ac1..7eb516a6 100644 --- a/tests/integration/roo-init-functionality.test.js +++ b/tests/integration/roo-init-functionality.test.js @@ -17,7 +17,7 @@ describe('Roo Profile Initialization Functionality', () => { // Check for the general copy of assets/roocode which includes .roo base structure expect(rooProfileContent).toContain( - "const sourceDir = path.resolve(__dirname, '../../assets/roocode');" + "const sourceDir = path.join(process.cwd(), 'assets', 'roocode');" ); expect(rooProfileContent).toContain( 'copyRecursiveSync(sourceDir, targetDir);' diff --git a/tests/integration/rules-files-inclusion.test.js b/tests/integration/rules-files-inclusion.test.js index 9c7d24c4..ba468c9e 100644 --- a/tests/integration/rules-files-inclusion.test.js +++ b/tests/integration/rules-files-inclusion.test.js @@ -48,6 +48,11 @@ describe('Rules Files Inclusion in Package', () => { rooJsContent.includes('copyRecursiveSync(sourceDir, targetDir)') ).toBe(true); + // Check for updated path handling + expect( + rooJsContent.includes("path.join(process.cwd(), 'assets', 'roocode')") + ).toBe(true); + // Check for .roomodes file copying logic (source and destination paths) expect(rooJsContent.includes("path.join(sourceDir, '.roomodes')")).toBe( true diff --git a/tests/integration/windsurf-init-functionality.test.js b/tests/integration/windsurf-init-functionality.test.js index 28ab0ea5..f49b20fa 100644 --- a/tests/integration/windsurf-init-functionality.test.js +++ b/tests/integration/windsurf-init-functionality.test.js @@ -14,30 +14,26 @@ describe('Windsurf Profile Initialization Functionality', () => { windsurfProfileContent = fs.readFileSync(windsurfJsPath, 'utf8'); }); - test('windsurf.js exports correct profileName and rulesDir', () => { - expect(windsurfProfileContent).toContain("const profileName = 'Windsurf'"); - expect(windsurfProfileContent).toContain( - "const rulesDir = '.windsurf/rules'" - ); + test('windsurf.js uses factory pattern with correct configuration', () => { + expect(windsurfProfileContent).toContain("name: 'windsurf'"); + expect(windsurfProfileContent).toContain("displayName: 'Windsurf'"); + expect(windsurfProfileContent).toContain("rulesDir: '.windsurf/rules'"); + expect(windsurfProfileContent).toContain("profileDir: '.windsurf'"); }); - test('windsurf.js contains fileMap for .mdc to .md mapping', () => { - expect(windsurfProfileContent).toContain('fileMap = {'); - expect(windsurfProfileContent).toContain(".mdc'"); - expect(windsurfProfileContent).toContain(".md'"); + test('windsurf.js configures .mdc to .md extension mapping', () => { + expect(windsurfProfileContent).toContain("fileExtension: '.mdc'"); + expect(windsurfProfileContent).toContain("targetExtension: '.md'"); }); - test('windsurf.js contains tool renaming and extension logic', () => { - expect(windsurfProfileContent).toContain('edit_file'); - expect(windsurfProfileContent).toContain('apply_diff'); - expect(windsurfProfileContent).toContain('search tool'); - expect(windsurfProfileContent).toContain('search_files tool'); - expect(windsurfProfileContent).toContain('.mdc'); - expect(windsurfProfileContent).toContain('.md'); + test('windsurf.js uses transformed tool mappings', () => { + expect(windsurfProfileContent).toContain('COMMON_TOOL_MAPPINGS.ROO_STYLE'); + // Should contain comment about transformed tool names + expect(windsurfProfileContent).toContain('transformed tool names'); }); - test('windsurf.js contains correct documentation URL transformation', () => { - expect(windsurfProfileContent).toContain('docs.cursor.com'); - expect(windsurfProfileContent).toContain('docs.windsurf.com'); + test('windsurf.js contains correct URL configuration', () => { + expect(windsurfProfileContent).toContain("url: 'windsurf.com'"); + expect(windsurfProfileContent).toContain("docsUrl: 'docs.windsurf.com'"); }); });