diff --git a/scripts/profiles/base-profile.js b/scripts/profiles/base-profile.js index c8cac3d4..1ef63507 100644 --- a/scripts/profiles/base-profile.js +++ b/scripts/profiles/base-profile.js @@ -4,6 +4,23 @@ import path from 'path'; /** * Creates a standardized profile configuration for different editors * @param {Object} editorConfig - Editor-specific configuration + * @param {string} editorConfig.name - Profile name (e.g., 'cursor', 'vscode') + * @param {string} [editorConfig.displayName] - Display name for the editor (defaults to name) + * @param {string} editorConfig.url - Editor website URL + * @param {string} editorConfig.docsUrl - Editor documentation URL + * @param {string} editorConfig.profileDir - Directory for profile configuration + * @param {string} [editorConfig.rulesDir] - Directory for rules files (defaults to profileDir/rules) + * @param {boolean} [editorConfig.mcpConfig=true] - Whether to create MCP configuration + * @param {string} [editorConfig.mcpConfigName='mcp.json'] - Name of MCP config file + * @param {string} [editorConfig.fileExtension='.mdc'] - Source file extension + * @param {string} [editorConfig.targetExtension='.md'] - Target file extension + * @param {Object} [editorConfig.toolMappings={}] - Tool name mappings + * @param {Array} [editorConfig.customReplacements=[]] - Custom text replacements + * @param {Object} [editorConfig.customFileMap={}] - Custom file name mappings + * @param {boolean} [editorConfig.supportsRulesSubdirectories=false] - Whether to use taskmaster/ subdirectory for taskmaster-specific rules (only Cursor uses this by default) + * @param {Function} [editorConfig.onAdd] - Lifecycle hook for profile addition + * @param {Function} [editorConfig.onRemove] - Lifecycle hook for profile removal + * @param {Function} [editorConfig.onPostConvert] - Lifecycle hook for post-conversion * @returns {Object} - Complete profile configuration */ export function createProfile(editorConfig) { @@ -21,6 +38,7 @@ export function createProfile(editorConfig) { toolMappings = {}, customReplacements = [], customFileMap = {}, + supportsRulesSubdirectories = false, onAdd, onRemove, onPostConvert @@ -29,11 +47,13 @@ export function createProfile(editorConfig) { const mcpConfigPath = `${profileDir}/${mcpConfigName}`; // Standard file mapping with custom overrides + // Use taskmaster subdirectory only if profile supports it + const taskmasterPrefix = supportsRulesSubdirectories ? 'taskmaster/' : ''; const defaultFileMap = { 'cursor_rules.mdc': `${name.toLowerCase()}_rules${targetExtension}`, - 'dev_workflow.mdc': `taskmaster/dev_workflow${targetExtension}`, + 'dev_workflow.mdc': `${taskmasterPrefix}dev_workflow${targetExtension}`, 'self_improve.mdc': `self_improve${targetExtension}`, - 'taskmaster.mdc': `taskmaster/taskmaster${targetExtension}` + 'taskmaster.mdc': `${taskmasterPrefix}taskmaster${targetExtension}` }; const fileMap = { ...defaultFileMap, ...customFileMap }; @@ -200,6 +220,7 @@ export function createProfile(editorConfig) { mcpConfig, mcpConfigName, mcpConfigPath, + supportsRulesSubdirectories, fileMap, globalReplacements: baseGlobalReplacements, conversionConfig, diff --git a/scripts/profiles/cursor.js b/scripts/profiles/cursor.js index f4f0b165..d17da8bb 100644 --- a/scripts/profiles/cursor.js +++ b/scripts/profiles/cursor.js @@ -14,6 +14,7 @@ export const cursorProfile = createProfile({ fileExtension: '.mdc', targetExtension: '.mdc', // Cursor keeps .mdc extension toolMappings: COMMON_TOOL_MAPPINGS.STANDARD, + supportsRulesSubdirectories: true, customFileMap: { 'cursor_rules.mdc': 'cursor_rules.mdc' // Keep the same name for cursor } diff --git a/tests/unit/profiles/rule-transformer-cline.test.js b/tests/unit/profiles/rule-transformer-cline.test.js index f1fb2803..c9f38fae 100644 --- a/tests/unit/profiles/rule-transformer-cline.test.js +++ b/tests/unit/profiles/rule-transformer-cline.test.js @@ -138,13 +138,9 @@ This references [dev_workflow.mdc](mdc:.cursor/rules/dev_workflow.mdc) and const writeCall = mockWriteFileSync.mock.calls[0]; const transformedContent = writeCall[1]; - // Verify transformations - files should now be in taskmaster subdirectory - expect(transformedContent).toContain( - '(.clinerules/taskmaster/dev_workflow.md)' - ); - expect(transformedContent).toContain( - '(.clinerules/taskmaster/taskmaster.md)' - ); + // Verify file path transformations - no taskmaster subdirectory for Cline + expect(transformedContent).toContain('(.clinerules/dev_workflow.md)'); + expect(transformedContent).toContain('(.clinerules/taskmaster.md)'); expect(transformedContent).not.toContain('(mdc:.cursor/rules/'); }); diff --git a/tests/unit/profiles/rule-transformer-roo.test.js b/tests/unit/profiles/rule-transformer-roo.test.js index 196d3461..320e115f 100644 --- a/tests/unit/profiles/rule-transformer-roo.test.js +++ b/tests/unit/profiles/rule-transformer-roo.test.js @@ -138,13 +138,9 @@ This references [dev_workflow.mdc](mdc:.cursor/rules/dev_workflow.mdc) and const writeCall = mockWriteFileSync.mock.calls[0]; const transformedContent = writeCall[1]; - // Verify transformations - files should now be in taskmaster subdirectory - expect(transformedContent).toContain( - '(.roo/rules/taskmaster/dev_workflow.md)' - ); - expect(transformedContent).toContain( - '(.roo/rules/taskmaster/taskmaster.md)' - ); + // Verify transformations - no taskmaster subdirectory for Roo + expect(transformedContent).toContain('(.roo/rules/dev_workflow.md)'); // File path transformation - no taskmaster subdirectory for Roo + expect(transformedContent).toContain('(.roo/rules/taskmaster.md)'); // File path transformation - no taskmaster subdirectory for Roo expect(transformedContent).not.toContain('(mdc:.cursor/rules/'); }); diff --git a/tests/unit/profiles/rule-transformer-trae.test.js b/tests/unit/profiles/rule-transformer-trae.test.js index a2913403..ddd00e2a 100644 --- a/tests/unit/profiles/rule-transformer-trae.test.js +++ b/tests/unit/profiles/rule-transformer-trae.test.js @@ -138,13 +138,9 @@ This references [dev_workflow.mdc](mdc:.cursor/rules/dev_workflow.mdc) and const writeCall = mockWriteFileSync.mock.calls[0]; const transformedContent = writeCall[1]; - // Verify transformations - files should now be in taskmaster subdirectory - expect(transformedContent).toContain( - '(.trae/rules/taskmaster/dev_workflow.md)' - ); - expect(transformedContent).toContain( - '(.trae/rules/taskmaster/taskmaster.md)' - ); + // Verify transformations - no taskmaster subdirectory for Trae + expect(transformedContent).toContain('(.trae/rules/dev_workflow.md)'); // File path transformation - no taskmaster subdirectory for Trae + expect(transformedContent).toContain('(.trae/rules/taskmaster.md)'); // File path transformation - no taskmaster subdirectory for Trae expect(transformedContent).not.toContain('(mdc:.cursor/rules/'); }); diff --git a/tests/unit/profiles/rule-transformer-vscode.test.js b/tests/unit/profiles/rule-transformer-vscode.test.js index 5e846523..8dfdb0dd 100644 --- a/tests/unit/profiles/rule-transformer-vscode.test.js +++ b/tests/unit/profiles/rule-transformer-vscode.test.js @@ -148,11 +148,11 @@ Files are in the .cursor/rules directory and we should reference the rules direc 'applyTo: ".github/instructions/*.md"' ); // globs -> applyTo with path transformation expect(transformedContent).toContain( - '(.github/instructions/taskmaster/dev_workflow.md)' - ); // File path transformation + '(.github/instructions/dev_workflow.md)' + ); // File path transformation - no taskmaster subdirectory for VS Code expect(transformedContent).toContain( - '(.github/instructions/taskmaster/taskmaster.md)' - ); // File path transformation + '(.github/instructions/taskmaster.md)' + ); // File path transformation - no taskmaster subdirectory for VS Code expect(transformedContent).toContain('instructions directory'); // "rules directory" -> "instructions directory" expect(transformedContent).not.toContain('(mdc:.cursor/rules/'); expect(transformedContent).not.toContain('.cursor/rules'); diff --git a/tests/unit/profiles/rule-transformer-windsurf.test.js b/tests/unit/profiles/rule-transformer-windsurf.test.js index afb70e2a..b5a1f607 100644 --- a/tests/unit/profiles/rule-transformer-windsurf.test.js +++ b/tests/unit/profiles/rule-transformer-windsurf.test.js @@ -138,13 +138,9 @@ This references [dev_workflow.mdc](mdc:.cursor/rules/dev_workflow.mdc) and const writeCall = mockWriteFileSync.mock.calls[0]; const transformedContent = writeCall[1]; - // Verify transformations - files should now be in taskmaster subdirectory - expect(transformedContent).toContain( - '(.windsurf/rules/taskmaster/dev_workflow.md)' - ); - expect(transformedContent).toContain( - '(.windsurf/rules/taskmaster/taskmaster.md)' - ); + // Verify transformations - no taskmaster subdirectory for Windsurf + expect(transformedContent).toContain('(.windsurf/rules/dev_workflow.md)'); // File path transformation - no taskmaster subdirectory for Windsurf + expect(transformedContent).toContain('(.windsurf/rules/taskmaster.md)'); // File path transformation - no taskmaster subdirectory for Windsurf expect(transformedContent).not.toContain('(mdc:.cursor/rules/'); }); diff --git a/tests/unit/profiles/subdirectory-support.test.js b/tests/unit/profiles/subdirectory-support.test.js new file mode 100644 index 00000000..5570c6e8 --- /dev/null +++ b/tests/unit/profiles/subdirectory-support.test.js @@ -0,0 +1,64 @@ +// Test for supportsRulesSubdirectories feature +import { getRulesProfile } from '../../../src/utils/rule-transformer.js'; + +describe('Rules Subdirectory Support Feature', () => { + it('should support taskmaster subdirectories only for Cursor profile', () => { + // Test Cursor profile - should use subdirectories + const cursorProfile = getRulesProfile('cursor'); + expect(cursorProfile.supportsRulesSubdirectories).toBe(true); + + // Verify that Cursor uses taskmaster subdirectories in its file mapping + expect(cursorProfile.fileMap['dev_workflow.mdc']).toBe( + 'taskmaster/dev_workflow.mdc' + ); + expect(cursorProfile.fileMap['taskmaster.mdc']).toBe( + 'taskmaster/taskmaster.mdc' + ); + }); + + it('should not use taskmaster subdirectories for other profiles', () => { + // Test profiles that should NOT use subdirectories (new default) + const profiles = ['roo', 'vscode', 'cline', 'windsurf', 'trae']; + + profiles.forEach((profileName) => { + const profile = getRulesProfile(profileName); + expect(profile.supportsRulesSubdirectories).toBe(false); + + // Verify that these profiles do NOT use taskmaster subdirectories in their file mapping + const expectedExt = profile.targetExtension || '.md'; + expect(profile.fileMap['dev_workflow.mdc']).toBe( + `dev_workflow${expectedExt}` + ); + expect(profile.fileMap['taskmaster.mdc']).toBe( + `taskmaster${expectedExt}` + ); + }); + }); + + it('should have supportsRulesSubdirectories property accessible on all profiles', () => { + const allProfiles = [ + 'cursor', + 'roo', + 'vscode', + 'cline', + 'windsurf', + 'trae' + ]; + + allProfiles.forEach((profileName) => { + const profile = getRulesProfile(profileName); + expect(profile).toBeDefined(); + expect(typeof profile.supportsRulesSubdirectories).toBe('boolean'); + }); + }); + + it('should default to false for supportsRulesSubdirectories when not specified', () => { + // Most profiles should now default to NOT supporting subdirectories + const profiles = ['roo', 'windsurf', 'trae', 'vscode', 'cline']; + + profiles.forEach((profileName) => { + const profile = getRulesProfile(profileName); + expect(profile.supportsRulesSubdirectories).toBe(false); + }); + }); +});