Files
claude-task-master/scripts/modules/rule-transformer.js
2025-05-08 23:11:29 -04:00

210 lines
5.9 KiB
JavaScript

/**
* Rule Transformer Module
* Handles conversion of Cursor rules to brand rules
*
* This module procedurally generates .{brand}/rules files from .cursor/rules files,
* eliminating the need to maintain both sets of files manually.
*/
import fs from 'fs';
import path from 'path';
import { log } from './utils.js';
// Import Roo Code conversionConfig and fileMap from profiles
import { conversionConfig, fileMap } from '../profiles/roo.js';
/**
* Replace basic Cursor terms with brand equivalents
*/
function replaceBasicTerms(content) {
let result = content;
// Apply brand term replacements
conversionConfig.brandTerms.forEach((pattern) => {
if (typeof pattern.to === 'function') {
result = result.replace(pattern.from, pattern.to);
} else {
result = result.replace(pattern.from, pattern.to);
}
});
// Apply file extension replacements
conversionConfig.fileExtensions.forEach((pattern) => {
result = result.replace(pattern.from, pattern.to);
});
return result;
}
/**
* Replace Cursor tool references with brand tool equivalents
*/
function replaceToolReferences(content) {
let result = content;
// Basic pattern for direct tool name replacements
const toolNames = conversionConfig.toolNames;
const toolReferencePattern = new RegExp(
`\\b(${Object.keys(toolNames).join('|')})\\b`,
'g'
);
// Apply direct tool name replacements
result = result.replace(toolReferencePattern, (match, toolName) => {
return toolNames[toolName] || toolName;
});
// Apply contextual tool replacements
conversionConfig.toolContexts.forEach((pattern) => {
result = result.replace(pattern.from, pattern.to);
});
// Apply tool group replacements
conversionConfig.toolGroups.forEach((pattern) => {
result = result.replace(pattern.from, pattern.to);
});
return result;
}
/**
* Update documentation URLs to point to brand documentation
*/
function updateDocReferences(content) {
let result = content;
// Apply documentation URL replacements
conversionConfig.docUrls.forEach((pattern) => {
if (typeof pattern.to === 'function') {
result = result.replace(pattern.from, pattern.to);
} else {
result = result.replace(pattern.from, pattern.to);
}
});
return result;
}
/**
* Update file references in markdown links
*/
function updateFileReferences(content) {
const { pathPattern, replacement } = conversionConfig.fileReferences;
return content.replace(pathPattern, replacement);
}
/**
* Main transformation function that applies all conversions
*/
// Main transformation function that applies all conversions, now brand-generic
function transformCursorToBrandRules(content, conversionConfig, globalReplacements = []) {
// Apply all transformations in appropriate order
let result = content;
result = replaceBasicTerms(result, conversionConfig);
result = replaceToolReferences(result, conversionConfig);
result = updateDocReferences(result, conversionConfig);
result = updateFileReferences(result, conversionConfig);
// Apply any global/catch-all replacements from the brand profile
// Super aggressive failsafe pass to catch any variations we might have missed
// This ensures critical transformations are applied even in contexts we didn't anticipate
globalReplacements.forEach((pattern) => {
if (typeof pattern.to === 'function') {
result = result.replace(pattern.from, pattern.to);
} else {
result = result.replace(pattern.from, pattern.to);
}
});
return result;
}
/**
* Convert a single Cursor rule file to brand rule format
*/
function convertCursorRuleToBrandRule(sourcePath, targetPath, profile) {
const { conversionConfig, brandName, globalReplacements } = profile;
try {
log(
'info',
`Converting Cursor rule ${path.basename(sourcePath)} to ${brandName} rule ${path.basename(targetPath)}`
);
// Read source content
const content = fs.readFileSync(sourcePath, 'utf8');
// Transform content
const transformedContent = transformCursorToBrandRules(content, conversionConfig, globalReplacements);
// Ensure target directory exists
const targetDir = path.dirname(targetPath);
if (!fs.existsSync(targetDir)) {
fs.mkdirSync(targetDir, { recursive: true });
}
// Write transformed content
fs.writeFileSync(targetPath, transformedContent);
log(
'success',
`Successfully converted ${path.basename(sourcePath)} to ${path.basename(targetPath)}`
);
return true;
} catch (error) {
log(
'error',
`Failed to convert rule file ${path.basename(sourcePath)}: ${error.message}`
);
return false;
}
}
/**
* Process all Cursor rules and convert to brand rules
*/
function convertAllCursorRulesToBrandRules(projectDir, profile) {
const { fileMap, brandName, rulesDir } = profile;
const cursorRulesDir = path.join(projectDir, '.cursor', 'rules');
const brandRulesDir = path.join(projectDir, rulesDir);
if (!fs.existsSync(cursorRulesDir)) {
log('warn', `Cursor rules directory not found: ${cursorRulesDir}`);
return { success: 0, failed: 0 };
}
// Ensure brand rules directory exists
if (!fs.existsSync(brandRulesDir)) {
fs.mkdirSync(brandRulesDir, { recursive: true });
log('info', `Created ${brandName} rules directory: ${brandRulesDir}`);
}
// Count successful and failed conversions
let success = 0;
let failed = 0;
// Process each file in the Cursor rules directory
fs.readdirSync(cursorRulesDir).forEach((file) => {
if (file.endsWith('.mdc')) {
const sourcePath = path.join(cursorRulesDir, file);
// Determine target file name (either from mapping or by replacing extension)
const targetFilename = fileMap[file] || file.replace('.mdc', '.md');
const targetPath = path.join(brandRulesDir, targetFilename);
// Convert the file
if (convertCursorRuleToBrandRule(sourcePath, targetPath, profile)) {
success++;
} else {
failed++;
}
}
});
log(
'info',
`Rule conversion complete: ${success} successful, ${failed} failed`
);
return { success, failed };
}
export { convertAllCursorRulesToBrandRules, convertCursorRuleToBrandRule };