fix formatting

This commit is contained in:
Joe Danziger
2025-05-11 13:32:42 -04:00
parent d28170ee03
commit 7169296c24
12 changed files with 279 additions and 133 deletions

View File

@@ -446,9 +446,15 @@ async function initializeProject(options = {}) {
try { try {
const mcpResult = await options.mcpServer.call('rules', mcpArgs); const mcpResult = await options.mcpServer.call('rules', mcpArgs);
if (mcpResult && mcpResult.success) { if (mcpResult && mcpResult.success) {
log('success', `Brand rules added via MCP: ${selectedBrandRules.join(', ')}`); log(
'success',
`Brand rules added via MCP: ${selectedBrandRules.join(', ')}`
);
} else { } else {
log('error', `MCP rules add failed: ${mcpResult?.error?.message || 'Unknown error'}`); log(
'error',
`MCP rules add failed: ${mcpResult?.error?.message || 'Unknown error'}`
);
} }
} catch (err) { } catch (err) {
log('error', `MCP server error: ${err.message}`); log('error', `MCP server error: ${err.message}`);

View File

@@ -2,32 +2,37 @@ import fs from 'fs';
import path from 'path'; import path from 'path';
describe('Cursor Profile Initialization Functionality', () => { describe('Cursor Profile Initialization Functionality', () => {
let cursorProfileContent; let cursorProfileContent;
beforeAll(() => { beforeAll(() => {
const cursorJsPath = path.join(process.cwd(), 'scripts', 'profiles', 'cursor.js'); const cursorJsPath = path.join(
cursorProfileContent = fs.readFileSync(cursorJsPath, 'utf8'); process.cwd(),
}); 'scripts',
'profiles',
'cursor.js'
);
cursorProfileContent = fs.readFileSync(cursorJsPath, 'utf8');
});
test('cursor.js exports correct brandName and rulesDir', () => { test('cursor.js exports correct brandName and rulesDir', () => {
expect(cursorProfileContent).toContain("const brandName = 'Cursor'"); expect(cursorProfileContent).toContain("const brandName = 'Cursor'");
expect(cursorProfileContent).toContain("const rulesDir = '.cursor/rules'"); expect(cursorProfileContent).toContain("const rulesDir = '.cursor/rules'");
}); });
test('cursor.js preserves .mdc filenames in fileMap', () => { test('cursor.js preserves .mdc filenames in fileMap', () => {
expect(cursorProfileContent).toContain('fileMap = {'); expect(cursorProfileContent).toContain('fileMap = {');
// Should NOT contain any .md mapping // Should NOT contain any .md mapping
expect(cursorProfileContent).not.toMatch(/\.md'/); expect(cursorProfileContent).not.toMatch(/\.md'/);
}); });
test('cursor.js contains tool naming logic and global replacements', () => { test('cursor.js contains tool naming logic and global replacements', () => {
expect(cursorProfileContent).toContain('edit_file'); expect(cursorProfileContent).toContain('edit_file');
expect(cursorProfileContent).toContain('search tool'); expect(cursorProfileContent).toContain('search tool');
expect(cursorProfileContent).not.toContain('apply_diff'); expect(cursorProfileContent).not.toContain('apply_diff');
expect(cursorProfileContent).not.toContain('search_files tool'); expect(cursorProfileContent).not.toContain('search_files tool');
}); });
test('cursor.js contains correct documentation URL logic', () => { test('cursor.js contains correct documentation URL logic', () => {
expect(cursorProfileContent).toContain('docs.cursor.com'); expect(cursorProfileContent).toContain('docs.cursor.com');
}); });
}); });

View File

@@ -0,0 +1,76 @@
// Integration tests for rules transformer functions in MCP server context
import { jest } from '@jest/globals';
import {
convertAllRulesToBrandRules,
removeBrandRules
} from '../../../scripts/modules/rule-transformer.js';
import * as windsurfProfile from '../../../scripts/profiles/windsurf.js';
// Mock fs functions as in direct-functions.test.js
const mockExistsSync = jest.fn();
const mockWriteFileSync = jest.fn();
const mockReadFileSync = jest.fn();
const mockUnlinkSync = jest.fn();
const mockMkdirSync = jest.fn();
const mockRmSync = jest.fn();
const mockReaddirSync = jest.fn();
jest.mock('fs', () => ({
existsSync: mockExistsSync,
writeFileSync: mockWriteFileSync,
readFileSync: mockReadFileSync,
unlinkSync: mockUnlinkSync,
mkdirSync: mockMkdirSync,
rmSync: mockRmSync,
readdirSync: mockReaddirSync
}));
describe('rules transformer', () => {
const mockProjectDir = '/mock/project/root';
beforeEach(() => {
jest.clearAllMocks();
});
test('should convert all Cursor rules to windsurf brand rules', () => {
// Arrange
mockExistsSync.mockImplementation((p) => {
// Simulate that the cursor rules directory exists
if (p === '/mock/project/root/assets/rules') return true;
// Simulate that the brand rules directory does not exist initially
if (p === '/mock/project/root/.windsurf/rules') return false;
return false;
});
mockReadFileSync.mockImplementation((p) => 'mock rule content');
mockReaddirSync.mockImplementation((dir) => ['sample-rule.mdc']);
mockWriteFileSync.mockImplementation(() => {});
mockMkdirSync.mockImplementation(() => {});
// Act
const result = convertAllRulesToBrandRules(mockProjectDir, windsurfProfile);
// Assert
expect(result.success).toBeGreaterThanOrEqual(0);
expect(mockWriteFileSync).toHaveBeenCalled();
expect(mockMkdirSync).toHaveBeenCalled();
});
test('should remove windsurf brand rules', () => {
// Arrange
mockExistsSync.mockImplementation((p) => {
// Simulate that the brand rules directory exists
if (p === '/mock/project/root/.windsurf/rules') return true;
return false;
});
mockRmSync.mockImplementation(() => {});
mockUnlinkSync.mockImplementation(() => {});
mockReaddirSync.mockImplementation((dir) => []);
// Act
const removed = removeBrandRules(mockProjectDir, windsurfProfile);
// Assert
expect(removed).toBe(true);
expect(mockRmSync).toHaveBeenCalled();
});
});

View File

@@ -22,20 +22,34 @@ describe('Roo Files Inclusion in Package', () => {
const rooJsContent = fs.readFileSync(rooJsPath, 'utf8'); const rooJsContent = fs.readFileSync(rooJsPath, 'utf8');
// Check for the main handler function // Check for the main handler function
expect(rooJsContent.includes("onAddBrandRules(targetDir)")).toBe(true); expect(rooJsContent.includes('onAddBrandRules(targetDir)')).toBe(true);
// Check for general recursive copy of assets/roocode // Check for general recursive copy of assets/roocode
expect(rooJsContent.includes("copyRecursiveSync(sourceDir, targetDir)")).toBe(true); expect(
rooJsContent.includes('copyRecursiveSync(sourceDir, targetDir)')
).toBe(true);
// Check for .roomodes file copying logic (source and destination paths) // Check for .roomodes file copying logic (source and destination paths)
expect(rooJsContent.includes("path.join(sourceDir, '.roomodes')")).toBe(true); expect(rooJsContent.includes("path.join(sourceDir, '.roomodes')")).toBe(
expect(rooJsContent.includes("path.join(targetDir, '.roomodes')")).toBe(true); true
);
expect(rooJsContent.includes("path.join(targetDir, '.roomodes')")).toBe(
true
);
// Check for mode-specific rule file copying logic // Check for mode-specific rule file copying logic
expect(rooJsContent.includes("for (const mode of rooModes)")).toBe(true); expect(rooJsContent.includes('for (const mode of rooModes)')).toBe(true);
expect(rooJsContent.includes("path.join(rooModesDir, `rules-${mode}`, `${mode}-rules`)")).toBe(true); expect(
expect(rooJsContent.includes("path.join(targetDir, '.roo', `rules-${mode}`, `${mode}-rules`)")).toBe(true); rooJsContent.includes(
'path.join(rooModesDir, `rules-${mode}`, `${mode}-rules`)'
)
).toBe(true);
expect(
rooJsContent.includes(
"path.join(targetDir, '.roo', `rules-${mode}`, `${mode}-rules`)"
)
).toBe(true);
// Check for definition of rooModes array and all modes // Check for definition of rooModes array and all modes
const rooModesArrayRegex = /const rooModes\s*=\s*\[([^\]]+)\]\s*;?/; const rooModesArrayRegex = /const rooModes\s*=\s*\[([^\]]+)\]\s*;?/;
const rooModesMatch = rooJsContent.match(rooModesArrayRegex); const rooModesMatch = rooJsContent.match(rooModesArrayRegex);

View File

@@ -19,26 +19,37 @@ describe('Roo Profile Initialization Functionality', () => {
expect(rooProfileContent).toContain( expect(rooProfileContent).toContain(
'copyRecursiveSync(sourceDir, targetDir)' 'copyRecursiveSync(sourceDir, targetDir)'
); );
expect(rooProfileContent).toContain("path.resolve(__dirname, '../../assets/roocode')"); // Verifies sourceDir definition expect(rooProfileContent).toContain(
"path.resolve(__dirname, '../../assets/roocode')"
); // Verifies sourceDir definition
// Check for the loop that processes rooModes // Check for the loop that processes rooModes
expect(rooProfileContent).toContain('for (const mode of rooModes)'); expect(rooProfileContent).toContain('for (const mode of rooModes)');
// Check for creation of mode-specific rule directories (e.g., .roo/rules-architect) // Check for creation of mode-specific rule directories (e.g., .roo/rules-architect)
// This is the line: if (!fs.existsSync(destDir)) fs.mkdirSync(destDir, { recursive: true }); // This is the line: if (!fs.existsSync(destDir)) fs.mkdirSync(destDir, { recursive: true });
expect(rooProfileContent).toContain("fs.mkdirSync(destDir, { recursive: true });"); expect(rooProfileContent).toContain(
expect(rooProfileContent).toContain("const destDir = path.dirname(dest);"); // part of the same logic block 'fs.mkdirSync(destDir, { recursive: true });'
);
expect(rooProfileContent).toContain('const destDir = path.dirname(dest);'); // part of the same logic block
}); });
test('roo.js profile copies .roomodes file via onAddBrandRules', () => { test('roo.js profile copies .roomodes file via onAddBrandRules', () => {
expect(rooProfileContent).toContain('onAddBrandRules(targetDir)'); expect(rooProfileContent).toContain('onAddBrandRules(targetDir)');
// Check for the specific .roomodes copy logic // Check for the specific .roomodes copy logic
expect(rooProfileContent).toContain('fs.copyFileSync(roomodesSrc, roomodesDest);'); expect(rooProfileContent).toContain(
expect(rooProfileContent).toContain("const roomodesSrc = path.join(sourceDir, '.roomodes');"); 'fs.copyFileSync(roomodesSrc, roomodesDest);'
expect(rooProfileContent).toContain("const roomodesDest = path.join(targetDir, '.roomodes');"); );
expect(rooProfileContent).toContain("path.resolve(__dirname, '../../assets/roocode')"); // sourceDir for roomodesSrc expect(rooProfileContent).toContain(
"const roomodesSrc = path.join(sourceDir, '.roomodes');"
);
expect(rooProfileContent).toContain(
"const roomodesDest = path.join(targetDir, '.roomodes');"
);
expect(rooProfileContent).toContain(
"path.resolve(__dirname, '../../assets/roocode')"
); // sourceDir for roomodesSrc
}); });
test('roo.js profile copies mode-specific rule files via onAddBrandRules', () => { test('roo.js profile copies mode-specific rule files via onAddBrandRules', () => {
@@ -47,11 +58,17 @@ describe('Roo Profile Initialization Functionality', () => {
// Check for the specific mode rule file copy logic // Check for the specific mode rule file copy logic
expect(rooProfileContent).toContain('fs.copyFileSync(src, dest);'); expect(rooProfileContent).toContain('fs.copyFileSync(src, dest);');
// Check source path construction for mode rules // Check source path construction for mode rules
expect(rooProfileContent).toContain("const src = path.join(rooModesDir, `rules-${mode}`, `${mode}-rules`);"); expect(rooProfileContent).toContain(
'const src = path.join(rooModesDir, `rules-${mode}`, `${mode}-rules`);'
);
// Check destination path construction for mode rules // Check destination path construction for mode rules
expect(rooProfileContent).toContain("const dest = path.join(targetDir, '.roo', `rules-${mode}`, `${mode}-rules`);"); expect(rooProfileContent).toContain(
expect(rooProfileContent).toContain("const rooModesDir = path.join(sourceDir, '.roo');"); // part of src path "const dest = path.join(targetDir, '.roo', `rules-${mode}`, `${mode}-rules`);"
);
expect(rooProfileContent).toContain(
"const rooModesDir = path.join(sourceDir, '.roo');"
); // part of src path
}); });
}); });

View File

@@ -2,41 +2,41 @@ import fs from 'fs';
import path from 'path'; import path from 'path';
describe('Rules Files Inclusion in Package', () => { describe('Rules Files Inclusion in Package', () => {
test('package.json includes assets/** in the "files" array for rules files', () => { test('package.json includes assets/** in the "files" array for rules files', () => {
const packageJsonPath = path.join(process.cwd(), 'package.json'); const packageJsonPath = path.join(process.cwd(), 'package.json');
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
expect(packageJson.files).toContain('assets/**'); expect(packageJson.files).toContain('assets/**');
}); });
test('all rules files exist in assets/rules directory', () => { test('all rules files exist in assets/rules directory', () => {
const rulesDir = path.join(process.cwd(), 'assets', 'rules'); const rulesDir = path.join(process.cwd(), 'assets', 'rules');
const expectedFiles = [ const expectedFiles = [
'ai_providers.mdc', 'ai_providers.mdc',
'ai_services.mdc', 'ai_services.mdc',
'architecture.mdc', 'architecture.mdc',
'changeset.mdc', 'changeset.mdc',
'commands.mdc', 'commands.mdc',
'cursor_rules.mdc', 'cursor_rules.mdc',
'dependencies.mdc', 'dependencies.mdc',
'dev_workflow.mdc', 'dev_workflow.mdc',
'glossary.mdc', 'glossary.mdc',
'mcp.mdc', 'mcp.mdc',
'new_features.mdc', 'new_features.mdc',
'self_improve.mdc', 'self_improve.mdc',
'taskmaster.mdc', 'taskmaster.mdc',
'tasks.mdc', 'tasks.mdc',
'tests.mdc', 'tests.mdc',
'ui.mdc', 'ui.mdc',
'utilities.mdc', 'utilities.mdc'
]; ];
for (const file of expectedFiles) { for (const file of expectedFiles) {
expect(fs.existsSync(path.join(rulesDir, file))).toBe(true); expect(fs.existsSync(path.join(rulesDir, file))).toBe(true);
} }
}); });
test('assets/rules directory is not empty', () => { test('assets/rules directory is not empty', () => {
const rulesDir = path.join(process.cwd(), 'assets', 'rules'); const rulesDir = path.join(process.cwd(), 'assets', 'rules');
const files = fs.readdirSync(rulesDir).filter(f => !f.startsWith('.')); const files = fs.readdirSync(rulesDir).filter((f) => !f.startsWith('.'));
expect(files.length).toBeGreaterThan(0); expect(files.length).toBeGreaterThan(0);
}); });
}); });

View File

@@ -2,35 +2,42 @@ import fs from 'fs';
import path from 'path'; import path from 'path';
describe('Windsurf Profile Initialization Functionality', () => { describe('Windsurf Profile Initialization Functionality', () => {
let windsurfProfileContent; let windsurfProfileContent;
beforeAll(() => { beforeAll(() => {
const windsurfJsPath = path.join(process.cwd(), 'scripts', 'profiles', 'windsurf.js'); const windsurfJsPath = path.join(
windsurfProfileContent = fs.readFileSync(windsurfJsPath, 'utf8'); process.cwd(),
}); 'scripts',
'profiles',
'windsurf.js'
);
windsurfProfileContent = fs.readFileSync(windsurfJsPath, 'utf8');
});
test('windsurf.js exports correct brandName and rulesDir', () => { test('windsurf.js exports correct brandName and rulesDir', () => {
expect(windsurfProfileContent).toContain("const brandName = 'Windsurf'"); expect(windsurfProfileContent).toContain("const brandName = 'Windsurf'");
expect(windsurfProfileContent).toContain("const rulesDir = '.windsurf/rules'"); expect(windsurfProfileContent).toContain(
}); "const rulesDir = '.windsurf/rules'"
);
});
test('windsurf.js contains fileMap for .mdc to .md mapping', () => { test('windsurf.js contains fileMap for .mdc to .md mapping', () => {
expect(windsurfProfileContent).toContain("fileMap = {"); expect(windsurfProfileContent).toContain('fileMap = {');
expect(windsurfProfileContent).toContain(".mdc'"); expect(windsurfProfileContent).toContain(".mdc'");
expect(windsurfProfileContent).toContain(".md'"); expect(windsurfProfileContent).toContain(".md'");
}); });
test('windsurf.js contains tool renaming and extension logic', () => { test('windsurf.js contains tool renaming and extension logic', () => {
expect(windsurfProfileContent).toContain("edit_file"); expect(windsurfProfileContent).toContain('edit_file');
expect(windsurfProfileContent).toContain("apply_diff"); expect(windsurfProfileContent).toContain('apply_diff');
expect(windsurfProfileContent).toContain("search tool"); expect(windsurfProfileContent).toContain('search tool');
expect(windsurfProfileContent).toContain("search_files tool"); expect(windsurfProfileContent).toContain('search_files tool');
expect(windsurfProfileContent).toContain(".mdc"); expect(windsurfProfileContent).toContain('.mdc');
expect(windsurfProfileContent).toContain(".md"); expect(windsurfProfileContent).toContain('.md');
}); });
test('windsurf.js contains correct documentation URL transformation', () => { test('windsurf.js contains correct documentation URL transformation', () => {
expect(windsurfProfileContent).toContain('docs.cursor.com'); expect(windsurfProfileContent).toContain('docs.cursor.com');
expect(windsurfProfileContent).toContain('docs.windsurf.com'); expect(windsurfProfileContent).toContain('docs.windsurf.com');
}); });
}); });

View File

@@ -59,7 +59,14 @@ describe('Cursor Integration', () => {
fs.mkdirSync(path.join(tempDir, '.cursor', 'rules'), { recursive: true }); fs.mkdirSync(path.join(tempDir, '.cursor', 'rules'), { recursive: true });
// Create mode-specific rule directories // Create mode-specific rule directories
const cursorModes = ['architect', 'ask', 'boomerang', 'code', 'debug', 'test']; const cursorModes = [
'architect',
'ask',
'boomerang',
'code',
'debug',
'test'
];
for (const mode of cursorModes) { for (const mode of cursorModes) {
fs.mkdirSync(path.join(tempDir, '.cursor', `rules-${mode}`), { fs.mkdirSync(path.join(tempDir, '.cursor', `rules-${mode}`), {
recursive: true recursive: true
@@ -71,7 +78,10 @@ describe('Cursor Integration', () => {
} }
// Copy .cursormodes file // Copy .cursormodes file
fs.writeFileSync(path.join(tempDir, '.cursormodes'), 'Cursormodes file content'); fs.writeFileSync(
path.join(tempDir, '.cursormodes'),
'Cursormodes file content'
);
} }
test('creates all required .cursor directories', () => { test('creates all required .cursor directories', () => {

View File

@@ -2,7 +2,10 @@ import fs from 'fs';
import path from 'path'; import path from 'path';
import { fileURLToPath } from 'url'; import { fileURLToPath } from 'url';
import { dirname } from 'path'; import { dirname } from 'path';
import { convertRuleToBrandRule, convertAllRulesToBrandRules } from '../../scripts/modules/rule-transformer.js'; import {
convertRuleToBrandRule,
convertAllRulesToBrandRules
} from '../../scripts/modules/rule-transformer.js';
import * as cursorProfile from '../../scripts/profiles/cursor.js'; import * as cursorProfile from '../../scripts/profiles/cursor.js';
const __filename = fileURLToPath(import.meta.url); const __filename = fileURLToPath(import.meta.url);
@@ -113,7 +116,4 @@ This references [dev_workflow.mdc](mdc:.cursor/rules/dev_workflow.mdc) and
expect(convertedContent).not.toContain('(mdc:.roo/rules/'); expect(convertedContent).not.toContain('(mdc:.roo/rules/');
expect(convertedContent).not.toContain('(mdc:.windsurf/rules/'); expect(convertedContent).not.toContain('(mdc:.windsurf/rules/');
}); });
}); });

View File

@@ -2,7 +2,10 @@ import fs from 'fs';
import path from 'path'; import path from 'path';
import { fileURLToPath } from 'url'; import { fileURLToPath } from 'url';
import { dirname } from 'path'; import { dirname } from 'path';
import { convertRuleToBrandRule, convertAllRulesToBrandRules } from '../../scripts/modules/rule-transformer.js'; import {
convertRuleToBrandRule,
convertAllRulesToBrandRules
} from '../../scripts/modules/rule-transformer.js';
import * as rooProfile from '../../scripts/profiles/roo.js'; import * as rooProfile from '../../scripts/profiles/roo.js';
const __filename = fileURLToPath(import.meta.url); const __filename = fileURLToPath(import.meta.url);
@@ -111,17 +114,17 @@ This references [dev_workflow.mdc](mdc:.cursor/rules/dev_workflow.mdc) and
expect(convertedContent).not.toContain('(mdc:.cursor/rules/'); expect(convertedContent).not.toContain('(mdc:.cursor/rules/');
}); });
it('should run post-processing when converting all rules for Roo', () => { it('should run post-processing when converting all rules for Roo', () => {
// Simulate a rules directory with a .mdc file // Simulate a rules directory with a .mdc file
const assetsRulesDir = path.join(testDir, 'assets', 'rules'); const assetsRulesDir = path.join(testDir, 'assets', 'rules');
fs.mkdirSync(assetsRulesDir, { recursive: true }); fs.mkdirSync(assetsRulesDir, { recursive: true });
const assetRule = path.join(assetsRulesDir, 'dev_workflow.mdc'); const assetRule = path.join(assetsRulesDir, 'dev_workflow.mdc');
fs.writeFileSync(assetRule, 'dummy'); fs.writeFileSync(assetRule, 'dummy');
// Should create .roo/rules and call post-processing // Should create .roo/rules and call post-processing
convertAllRulesToBrandRules(testDir, rooProfile); convertAllRulesToBrandRules(testDir, rooProfile);
// Check for post-processing artifacts, e.g., rules-* folders or extra files // Check for post-processing artifacts, e.g., rules-* folders or extra files
const rooDir = path.join(testDir, '.roo'); const rooDir = path.join(testDir, '.roo');
const found = fs.readdirSync(rooDir).some(f => f.startsWith('rules-')); const found = fs.readdirSync(rooDir).some((f) => f.startsWith('rules-'));
expect(found).toBe(true); // There should be at least one rules-* folder expect(found).toBe(true); // There should be at least one rules-* folder
}); });
}); });

View File

@@ -110,6 +110,4 @@ This references [dev_workflow.mdc](mdc:.cursor/rules/dev_workflow.mdc) and
expect(convertedContent).toContain('(mdc:.windsurf/rules/taskmaster.md)'); expect(convertedContent).toContain('(mdc:.windsurf/rules/taskmaster.md)');
expect(convertedContent).not.toContain('(mdc:.cursor/rules/'); expect(convertedContent).not.toContain('(mdc:.cursor/rules/');
}); });
}); });

View File

@@ -59,7 +59,14 @@ describe('Windsurf Integration', () => {
fs.mkdirSync(path.join(tempDir, '.windsurf', 'rules'), { recursive: true }); fs.mkdirSync(path.join(tempDir, '.windsurf', 'rules'), { recursive: true });
// Create mode-specific rule directories // Create mode-specific rule directories
const windsurfModes = ['architect', 'ask', 'boomerang', 'code', 'debug', 'test']; const windsurfModes = [
'architect',
'ask',
'boomerang',
'code',
'debug',
'test'
];
for (const mode of windsurfModes) { for (const mode of windsurfModes) {
fs.mkdirSync(path.join(tempDir, '.windsurf', `rules-${mode}`), { fs.mkdirSync(path.join(tempDir, '.windsurf', `rules-${mode}`), {
recursive: true recursive: true
@@ -71,7 +78,10 @@ describe('Windsurf Integration', () => {
} }
// Copy .windsurfmodes file // Copy .windsurfmodes file
fs.writeFileSync(path.join(tempDir, '.windsurfmodes'), 'Windsurfmodes file content'); fs.writeFileSync(
path.join(tempDir, '.windsurfmodes'),
'Windsurfmodes file content'
);
} }
test('creates all required .windsurf directories', () => { test('creates all required .windsurf directories', () => {