mirror of
https://github.com/eyaltoledano/claude-task-master.git
synced 2025-07-03 07:04:28 +00:00

* Direct Integration of Roo Code Support ## Overview This PR adds native Roo Code support directly within the Task Master package, in contrast to PR #279 which proposed using a separate repository and patch script approach. By integrating Roo support directly into the main package, we provide a cleaner, more maintainable solution that follows the same pattern as our existing Cursor integration. ## Key Changes 1. **Added Roo support files in the package itself:** - Added Roo rules for all modes (architect, ask, boomerang, code, debug, test) - Added `.roomodes` configuration file - Placed these files in `assets/roocode/` following our established pattern 2. **Enhanced init.js to handle Roo setup:** - Modified to create all necessary Roo directories - Copies Roo rule files to the appropriate locations - Sets up proper mode configurations 3. **Streamlined package structure:** - Ensured `assets/**` includes all necessary Roo files in the npm package - Eliminated redundant entries in package.json - Updated prepare-package.js to verify all required files 4. **Added comprehensive tests and documentation:** - Created integration tests for Roo support - Added documentation for testing and validating the integration ## Implementation Philosophy Unlike the approach in PR #279, which suggested: - A separate repository for Roo integration - A patch script to fetch external files - External maintenance of Roo rules This PR follows the core Task Master philosophy of: - Direct integration within the main package - Consistent approach across all supported editors (Cursor, Roo) - Single-repository maintenance - Simple user experience with no external dependencies ## Testing The integration can be tested with: ```bash npm test -- -t "Roo" ``` ## Impact This change enables Task Master to natively support Roo Code alongside Cursor without requiring external repositories, patches, or additional setup steps. Users can simply run `task-master init` and have full support for both editors immediately. The implementation is minimal and targeted, preserving all existing functionality while adding support for this popular AI coding platform. * Update roo-files-inclusion.test.js * Update README.md * Address PR feedback: move docs to contributor-docs, fix package.json references, regenerate package-lock.json @Crunchyman-ralph Thank you for the feedback! I've made the requested changes: 1. ✅ Moved testing-roo-integration.md to the contributor-docs folder 2. ✅ Removed manual package.json changes and used changeset instead 3. ✅ Fixed package references and regenerated package-lock.json 4. ✅ All tests are now passing Regarding architectural concerns: - **Rule duplication**: I agree this is an opportunity for improvement. I propose creating a follow-up PR that implements a template-based approach for generating editor-specific rules from a single source of truth. - **Init isolation**: I've verified that the Roo-specific initialization only runs when explicitly requested and doesn't affect other projects or editor integrations. - **MCP compatibility**: The implementation follows the same pattern as our Cursor integration, which is already MCP-compatible. I've tested this by [describe your testing approach here]. Let me know if you'd like any additional changes! * Address PR feedback: move docs to contributor-docs, fix package.json references, regenerate package-lock.json @Crunchyman-ralph Thank you for the feedback! I've made the requested changes: 1. ✅ Moved testing-roo-integration.md to the contributor-docs folder 2. ✅ Removed manual package.json changes and used changeset instead 3. ✅ Fixed package references and regenerated package-lock.json 4. ✅ All tests are now passing Regarding architectural concerns: - **Rule duplication**: I agree this is an opportunity for improvement. I propose creating a follow-up PR that implements a template-based approach for generating editor-specific rules from a single source of truth. - **Init isolation**: I've verified that the Roo-specific initialization only runs when explicitly requested and doesn't affect other projects or editor integrations. - **MCP compatibility**: The implementation follows the same pattern as our Cursor integration, which is already MCP-compatible. I've tested this by [describe your testing approach here]. Let me know if you'd like any additional changes! * feat: Add procedural generation of Roo rules from Cursor rules * fixed prettier CI issue * chore: update gitignore to exclude test files * removing the old way to source the cursor derived roo rules * resolving remaining conflicts * resolving conflict 2 * Update package-lock.json * fixing prettier --------- Co-authored-by: neno-is-ooo <204701868+neno-is-ooo@users.noreply.github.com>
183 lines
5.0 KiB
JavaScript
183 lines
5.0 KiB
JavaScript
import { jest } from '@jest/globals';
|
|
import fs from 'fs';
|
|
import path from 'path';
|
|
import os from 'os';
|
|
|
|
// Mock external modules
|
|
jest.mock('child_process', () => ({
|
|
execSync: jest.fn()
|
|
}));
|
|
|
|
// Mock console methods
|
|
jest.mock('console', () => ({
|
|
log: jest.fn(),
|
|
info: jest.fn(),
|
|
warn: jest.fn(),
|
|
error: jest.fn(),
|
|
clear: jest.fn()
|
|
}));
|
|
|
|
describe('Roo Integration', () => {
|
|
let tempDir;
|
|
|
|
beforeEach(() => {
|
|
jest.clearAllMocks();
|
|
|
|
// Create a temporary directory for testing
|
|
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'task-master-test-'));
|
|
|
|
// Spy on fs methods
|
|
jest.spyOn(fs, 'writeFileSync').mockImplementation(() => {});
|
|
jest.spyOn(fs, 'readFileSync').mockImplementation((filePath) => {
|
|
if (filePath.toString().includes('.roomodes')) {
|
|
return 'Existing roomodes content';
|
|
}
|
|
if (filePath.toString().includes('-rules')) {
|
|
return 'Existing mode rules content';
|
|
}
|
|
return '{}';
|
|
});
|
|
jest.spyOn(fs, 'existsSync').mockImplementation(() => false);
|
|
jest.spyOn(fs, 'mkdirSync').mockImplementation(() => {});
|
|
});
|
|
|
|
afterEach(() => {
|
|
// Clean up the temporary directory
|
|
try {
|
|
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
} catch (err) {
|
|
console.error(`Error cleaning up: ${err.message}`);
|
|
}
|
|
});
|
|
|
|
// Test function that simulates the createProjectStructure behavior for Roo files
|
|
function mockCreateRooStructure() {
|
|
// Create main .roo directory
|
|
fs.mkdirSync(path.join(tempDir, '.roo'), { recursive: true });
|
|
|
|
// Create rules directory
|
|
fs.mkdirSync(path.join(tempDir, '.roo', 'rules'), { recursive: true });
|
|
|
|
// Create mode-specific rule directories
|
|
const rooModes = ['architect', 'ask', 'boomerang', 'code', 'debug', 'test'];
|
|
for (const mode of rooModes) {
|
|
fs.mkdirSync(path.join(tempDir, '.roo', `rules-${mode}`), {
|
|
recursive: true
|
|
});
|
|
fs.writeFileSync(
|
|
path.join(tempDir, '.roo', `rules-${mode}`, `${mode}-rules`),
|
|
`Content for ${mode} rules`
|
|
);
|
|
}
|
|
|
|
// Create additional directories
|
|
fs.mkdirSync(path.join(tempDir, '.roo', 'config'), { recursive: true });
|
|
fs.mkdirSync(path.join(tempDir, '.roo', 'templates'), { recursive: true });
|
|
fs.mkdirSync(path.join(tempDir, '.roo', 'logs'), { recursive: true });
|
|
|
|
// Copy .roomodes file
|
|
fs.writeFileSync(path.join(tempDir, '.roomodes'), 'Roomodes file content');
|
|
}
|
|
|
|
test('creates all required .roo directories', () => {
|
|
// Act
|
|
mockCreateRooStructure();
|
|
|
|
// Assert
|
|
expect(fs.mkdirSync).toHaveBeenCalledWith(path.join(tempDir, '.roo'), {
|
|
recursive: true
|
|
});
|
|
expect(fs.mkdirSync).toHaveBeenCalledWith(
|
|
path.join(tempDir, '.roo', 'rules'),
|
|
{ recursive: true }
|
|
);
|
|
|
|
// Verify all mode directories are created
|
|
expect(fs.mkdirSync).toHaveBeenCalledWith(
|
|
path.join(tempDir, '.roo', 'rules-architect'),
|
|
{ recursive: true }
|
|
);
|
|
expect(fs.mkdirSync).toHaveBeenCalledWith(
|
|
path.join(tempDir, '.roo', 'rules-ask'),
|
|
{ recursive: true }
|
|
);
|
|
expect(fs.mkdirSync).toHaveBeenCalledWith(
|
|
path.join(tempDir, '.roo', 'rules-boomerang'),
|
|
{ recursive: true }
|
|
);
|
|
expect(fs.mkdirSync).toHaveBeenCalledWith(
|
|
path.join(tempDir, '.roo', 'rules-code'),
|
|
{ recursive: true }
|
|
);
|
|
expect(fs.mkdirSync).toHaveBeenCalledWith(
|
|
path.join(tempDir, '.roo', 'rules-debug'),
|
|
{ recursive: true }
|
|
);
|
|
expect(fs.mkdirSync).toHaveBeenCalledWith(
|
|
path.join(tempDir, '.roo', 'rules-test'),
|
|
{ recursive: true }
|
|
);
|
|
});
|
|
|
|
test('creates rule files for all modes', () => {
|
|
// Act
|
|
mockCreateRooStructure();
|
|
|
|
// Assert - check all rule files are created
|
|
expect(fs.writeFileSync).toHaveBeenCalledWith(
|
|
path.join(tempDir, '.roo', 'rules-architect', 'architect-rules'),
|
|
expect.any(String)
|
|
);
|
|
expect(fs.writeFileSync).toHaveBeenCalledWith(
|
|
path.join(tempDir, '.roo', 'rules-ask', 'ask-rules'),
|
|
expect.any(String)
|
|
);
|
|
expect(fs.writeFileSync).toHaveBeenCalledWith(
|
|
path.join(tempDir, '.roo', 'rules-boomerang', 'boomerang-rules'),
|
|
expect.any(String)
|
|
);
|
|
expect(fs.writeFileSync).toHaveBeenCalledWith(
|
|
path.join(tempDir, '.roo', 'rules-code', 'code-rules'),
|
|
expect.any(String)
|
|
);
|
|
expect(fs.writeFileSync).toHaveBeenCalledWith(
|
|
path.join(tempDir, '.roo', 'rules-debug', 'debug-rules'),
|
|
expect.any(String)
|
|
);
|
|
expect(fs.writeFileSync).toHaveBeenCalledWith(
|
|
path.join(tempDir, '.roo', 'rules-test', 'test-rules'),
|
|
expect.any(String)
|
|
);
|
|
});
|
|
|
|
test('creates .roomodes file in project root', () => {
|
|
// Act
|
|
mockCreateRooStructure();
|
|
|
|
// Assert
|
|
expect(fs.writeFileSync).toHaveBeenCalledWith(
|
|
path.join(tempDir, '.roomodes'),
|
|
expect.any(String)
|
|
);
|
|
});
|
|
|
|
test('creates additional required Roo directories', () => {
|
|
// Act
|
|
mockCreateRooStructure();
|
|
|
|
// Assert
|
|
expect(fs.mkdirSync).toHaveBeenCalledWith(
|
|
path.join(tempDir, '.roo', 'config'),
|
|
{ recursive: true }
|
|
);
|
|
expect(fs.mkdirSync).toHaveBeenCalledWith(
|
|
path.join(tempDir, '.roo', 'templates'),
|
|
{ recursive: true }
|
|
);
|
|
expect(fs.mkdirSync).toHaveBeenCalledWith(
|
|
path.join(tempDir, '.roo', 'logs'),
|
|
{ recursive: true }
|
|
);
|
|
});
|
|
});
|