update semantics and terminology from 'brand rules' to 'rules profiles'
This commit is contained in:
32
src/constants/profiles.js
Normal file
32
src/constants/profiles.js
Normal file
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* @typedef {'cline' | 'cursor' | 'roo' | 'windsurf'} RulesProfile
|
||||
*/
|
||||
|
||||
/**
|
||||
* Available rules profiles for project initialization and rules command
|
||||
*
|
||||
* ⚠️ SINGLE SOURCE OF TRUTH: This is the authoritative list of all supported rules profiles.
|
||||
* This constant is used directly throughout the codebase (previously aliased as PROFILE_NAMES).
|
||||
*
|
||||
* @type {RulesProfile[]}
|
||||
* @description Defines possible rules profile sets:
|
||||
* - cline: Cline IDE rules
|
||||
* - cursor: Cursor IDE rules (default)
|
||||
* - roo: Roo Code IDE rules
|
||||
* - windsurf: Windsurf IDE rules
|
||||
*
|
||||
* To add a new rules profile:
|
||||
* 1. Add the profile name to this array
|
||||
* 2. Create a profile file in scripts/profiles/{profile}.js
|
||||
* 3. Export it as {profile}Profile in scripts/profiles/index.js
|
||||
*/
|
||||
export const RULES_PROFILES = ['cline', 'cursor', 'roo', 'windsurf'];
|
||||
|
||||
/**
|
||||
* Check if a given rules profile is valid
|
||||
* @param {string} rulesProfile - The rules profile to check
|
||||
* @returns {boolean} True if the rules profile is valid, false otherwise
|
||||
*/
|
||||
export function isValidRulesProfile(rulesProfile) {
|
||||
return RULES_PROFILES.includes(rulesProfile);
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
/**
|
||||
* @typedef {'cursor' | 'roo' | 'windsurf' | 'cline'} BrandRule
|
||||
*/
|
||||
|
||||
/**
|
||||
* Available brand rules for project initialization
|
||||
*
|
||||
* @type {BrandRule[]}
|
||||
* @description Defines possible brand rule sets:
|
||||
* - cursor: Cursor IDE rules (default)
|
||||
* - roo: Roo Code IDE rules
|
||||
* - windsurf: Windsurf IDE rules
|
||||
* - cline: Cline IDE rules
|
||||
*
|
||||
* To add a new brand:
|
||||
* 1. Add the brand name to this array
|
||||
* 2. Create a profile file in scripts/profiles/{brand}.js
|
||||
* 3. Export it in scripts/profiles/index.js
|
||||
* 4. Add it to BRAND_PROFILES in src/utils/rule-transformer.js
|
||||
*/
|
||||
export const BRAND_RULE_OPTIONS = [
|
||||
'cursor',
|
||||
'roo',
|
||||
'windsurf',
|
||||
'cline'
|
||||
];
|
||||
|
||||
/**
|
||||
* Check if a given brand rule is valid
|
||||
* @param {string} brandRule - The brand rule to check
|
||||
* @returns {boolean} True if the brand rule is valid, false otherwise
|
||||
*/
|
||||
export function isValidBrandRule(brandRule) {
|
||||
return BRAND_RULE_OPTIONS.includes(brandRule);
|
||||
}
|
||||
@@ -2,19 +2,19 @@ import chalk from 'chalk';
|
||||
import boxen from 'boxen';
|
||||
|
||||
/**
|
||||
* Confirm removing brand rules (destructive operation)
|
||||
* @param {string[]} brands - Array of brand names to remove
|
||||
* Confirm removing profile rules (destructive operation)
|
||||
* @param {string[]} profiles - Array of profile names to remove
|
||||
* @returns {Promise<boolean>} - Promise resolving to true if user confirms, false otherwise
|
||||
*/
|
||||
async function confirmRulesRemove(brands) {
|
||||
const brandList = brands
|
||||
async function confirmProfilesRemove(profiles) {
|
||||
const profileList = profiles
|
||||
.map((b) => b.charAt(0).toUpperCase() + b.slice(1))
|
||||
.join(', ');
|
||||
console.log(
|
||||
boxen(
|
||||
chalk.yellow(
|
||||
`WARNING: This will permanently delete all rules and configuration for: ${brandList}.
|
||||
This will remove the entire .[brand] directory for each selected brand.\n\nAre you sure you want to proceed?`
|
||||
`WARNING: This will permanently delete all rules and configuration for: ${profileList}.
|
||||
This will remove the entire .[profile] directory for each selected profile.\n\nAre you sure you want to proceed?`
|
||||
),
|
||||
{ padding: 1, borderColor: 'yellow', borderStyle: 'round' }
|
||||
)
|
||||
@@ -31,4 +31,4 @@ This will remove the entire .[brand] directory for each selected brand.\n\nAre y
|
||||
return confirm;
|
||||
}
|
||||
|
||||
export { confirmRulesRemove };
|
||||
export { confirmProfilesRemove };
|
||||
|
||||
@@ -3,10 +3,12 @@ import path from 'path';
|
||||
import { log } from '../../scripts/modules/utils.js';
|
||||
|
||||
// Structure matches project conventions (see scripts/init.js)
|
||||
export function setupMCPConfiguration(configDir) {
|
||||
const mcpPath = path.join(configDir, 'mcp.json');
|
||||
export function setupMCPConfiguration(projectDir, mcpConfigPath) {
|
||||
// Build the full path to the MCP config file
|
||||
const mcpPath = path.join(projectDir, mcpConfigPath);
|
||||
const configDir = path.dirname(mcpPath);
|
||||
|
||||
log('info', 'Setting up MCP configuration for brand integration...');
|
||||
log('info', `Setting up MCP configuration at ${mcpPath}...`);
|
||||
|
||||
// New MCP config to be added - references the installed package
|
||||
const newMCPServer = {
|
||||
@@ -26,10 +28,12 @@ export function setupMCPConfiguration(configDir) {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Create config directory if it doesn't exist
|
||||
if (!fs.existsSync(configDir)) {
|
||||
fs.mkdirSync(configDir, { recursive: true });
|
||||
}
|
||||
|
||||
if (fs.existsSync(mcpPath)) {
|
||||
log(
|
||||
'info',
|
||||
@@ -94,6 +98,6 @@ export function setupMCPConfiguration(configDir) {
|
||||
mcpServers: newMCPServer
|
||||
};
|
||||
fs.writeFileSync(mcpPath, JSON.stringify(newMCPConfig, null, 4));
|
||||
log('success', 'Created MCP configuration file');
|
||||
log('success', `Created MCP configuration file at ${mcpPath}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,45 +1,59 @@
|
||||
/**
|
||||
* Rule Transformer Module
|
||||
* Handles conversion of Cursor rules to brand rules
|
||||
* Handles conversion of Cursor rules to profile rules
|
||||
*
|
||||
* This module procedurally generates .{brand}/rules files from assets/rules files,
|
||||
* This module procedurally generates .{profile}/rules files from assets/rules files,
|
||||
* eliminating the need to maintain both sets of files manually.
|
||||
*/
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { log } from '../../scripts/modules/utils.js';
|
||||
|
||||
// Import the shared MCP configuration helper
|
||||
import { setupMCPConfiguration } from './mcp-utils.js';
|
||||
|
||||
// --- Centralized Brand Helpers ---
|
||||
import { clineProfile, cursorProfile, rooProfile, windsurfProfile } from '../../scripts/profiles/index.js';
|
||||
// Import profile constants (single source of truth)
|
||||
import { RULES_PROFILES } from '../constants/profiles.js';
|
||||
|
||||
export const BRAND_PROFILES = {
|
||||
cline: clineProfile,
|
||||
cursor: cursorProfile,
|
||||
roo: rooProfile,
|
||||
windsurf: windsurfProfile
|
||||
};
|
||||
// --- Profile Imports ---
|
||||
import * as profilesModule from '../../scripts/profiles/index.js';
|
||||
|
||||
export const BRAND_NAMES = Object.keys(BRAND_PROFILES);
|
||||
|
||||
export function isValidBrand(brand) {
|
||||
return BRAND_NAMES.includes(brand);
|
||||
}
|
||||
|
||||
export function getBrandProfile(brand) {
|
||||
return BRAND_PROFILES[brand];
|
||||
export function isValidProfile(profile) {
|
||||
return RULES_PROFILES.includes(profile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace basic Cursor terms with brand equivalents
|
||||
* Get rules profile by name
|
||||
* @param {string} name - Profile name
|
||||
* @returns {Object|null} Profile object or null if not found
|
||||
*/
|
||||
export function getRulesProfile(name) {
|
||||
if (!isValidProfile(name)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get the profile from the imported profiles module
|
||||
const profileKey = `${name}Profile`;
|
||||
const profile = profilesModule[profileKey];
|
||||
|
||||
if (!profile) {
|
||||
throw new Error(
|
||||
`Profile not found: static import missing for '${name}'. Valid profiles: ${RULES_PROFILES.join(', ')}`
|
||||
);
|
||||
}
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace basic Cursor terms with profile equivalents
|
||||
*/
|
||||
function replaceBasicTerms(content, conversionConfig) {
|
||||
let result = content;
|
||||
|
||||
// Apply brand term replacements
|
||||
conversionConfig.brandTerms.forEach((pattern) => {
|
||||
// Apply profile term replacements
|
||||
conversionConfig.profileTerms.forEach((pattern) => {
|
||||
if (typeof pattern.to === 'function') {
|
||||
result = result.replace(pattern.from, pattern.to);
|
||||
} else {
|
||||
@@ -56,7 +70,7 @@ function replaceBasicTerms(content, conversionConfig) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace Cursor tool references with brand tool equivalents
|
||||
* Replace Cursor tool references with profile tool equivalents
|
||||
*/
|
||||
function replaceToolReferences(content, conversionConfig) {
|
||||
let result = content;
|
||||
@@ -87,7 +101,7 @@ function replaceToolReferences(content, conversionConfig) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Update documentation URLs to point to brand documentation
|
||||
* Update documentation URLs to point to profile documentation
|
||||
*/
|
||||
function updateDocReferences(content, conversionConfig) {
|
||||
let result = content;
|
||||
@@ -115,8 +129,7 @@ function updateFileReferences(content, conversionConfig) {
|
||||
/**
|
||||
* Main transformation function that applies all conversions
|
||||
*/
|
||||
// Main transformation function that applies all conversions, now brand-generic
|
||||
function transformCursorToBrandRules(
|
||||
function transformCursorToProfileRules(
|
||||
content,
|
||||
conversionConfig,
|
||||
globalReplacements = []
|
||||
@@ -128,7 +141,7 @@ function transformCursorToBrandRules(
|
||||
result = updateDocReferences(result, conversionConfig);
|
||||
result = updateFileReferences(result, conversionConfig);
|
||||
|
||||
// Apply any global/catch-all replacements from the brand profile
|
||||
// Apply any global/catch-all replacements from the 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) => {
|
||||
@@ -143,21 +156,21 @@ function transformCursorToBrandRules(
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a single Cursor rule file to brand rule format
|
||||
* Convert a single Cursor rule file to profile rule format
|
||||
*/
|
||||
function convertRuleToBrandRule(sourcePath, targetPath, profile) {
|
||||
const { conversionConfig, brandName, globalReplacements } = profile;
|
||||
export function convertRuleToProfileRule(sourcePath, targetPath, profile) {
|
||||
const { conversionConfig, globalReplacements } = profile;
|
||||
try {
|
||||
log(
|
||||
'debug',
|
||||
`Converting Cursor rule ${path.basename(sourcePath)} to ${brandName} rule ${path.basename(targetPath)}`
|
||||
`Converting Cursor rule ${path.basename(sourcePath)} to ${profile.profileName} rule ${path.basename(targetPath)}`
|
||||
);
|
||||
|
||||
// Read source content
|
||||
const content = fs.readFileSync(sourcePath, 'utf8');
|
||||
|
||||
// Transform content
|
||||
const transformedContent = transformCursorToBrandRules(
|
||||
const transformedContent = transformCursorToProfileRules(
|
||||
content,
|
||||
conversionConfig,
|
||||
globalReplacements
|
||||
@@ -187,152 +200,141 @@ function convertRuleToBrandRule(sourcePath, targetPath, profile) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Process all Cursor rules and convert to brand rules
|
||||
* Convert all Cursor rules to profile rules for a specific profile
|
||||
*/
|
||||
function convertAllRulesToBrandRules(projectDir, profile) {
|
||||
const { fileMap, brandName, rulesDir, mcpConfig, mcpConfigName } = profile;
|
||||
// Use assets/rules as the source of rules instead of .cursor/rules
|
||||
const cursorRulesDir = path.join(projectDir, 'assets', 'rules');
|
||||
const brandRulesDir = path.join(projectDir, rulesDir);
|
||||
export function convertAllRulesToProfileRules(projectDir, profile) {
|
||||
const sourceDir = fileURLToPath(new URL('../../assets/rules', import.meta.url));
|
||||
const targetDir = path.join(projectDir, profile.rulesDir);
|
||||
|
||||
if (!fs.existsSync(cursorRulesDir)) {
|
||||
log('warn', `Cursor rules directory not found: ${cursorRulesDir}`);
|
||||
return { success: 0, failed: 0 };
|
||||
// Ensure target directory exists
|
||||
if (!fs.existsSync(targetDir)) {
|
||||
fs.mkdirSync(targetDir, { recursive: true });
|
||||
}
|
||||
|
||||
// Ensure brand rules directory exists
|
||||
if (!fs.existsSync(brandRulesDir)) {
|
||||
fs.mkdirSync(brandRulesDir, { recursive: true });
|
||||
log('debug', `Created ${brandName} rules directory: ${brandRulesDir}`);
|
||||
// Also create MCP configuration in the brand directory if enabled
|
||||
if (mcpConfig !== false) {
|
||||
const brandDir = profile.brandDir;
|
||||
setupMCPConfiguration(path.join(projectDir, brandDir), mcpConfigName);
|
||||
}
|
||||
// Setup MCP configuration if enabled
|
||||
if (profile.mcpConfig !== false) {
|
||||
setupMCPConfiguration(
|
||||
projectDir,
|
||||
profile.mcpConfigPath
|
||||
);
|
||||
}
|
||||
|
||||
// Count successful and failed conversions
|
||||
let success = 0;
|
||||
let failed = 0;
|
||||
|
||||
// Process each file from assets/rules listed in fileMap
|
||||
const getTargetRuleFilename = profile.getTargetRuleFilename || ((f) => f);
|
||||
Object.keys(profile.fileMap).forEach((file) => {
|
||||
const sourcePath = path.join(cursorRulesDir, file);
|
||||
if (fs.existsSync(sourcePath)) {
|
||||
const targetFilename = getTargetRuleFilename(file);
|
||||
const targetPath = path.join(brandRulesDir, targetFilename);
|
||||
// Use fileMap to determine which files to copy
|
||||
const sourceFiles = Object.keys(profile.fileMap);
|
||||
|
||||
// Convert the file
|
||||
if (convertRuleToBrandRule(sourcePath, targetPath, profile)) {
|
||||
success++;
|
||||
} else {
|
||||
failed++;
|
||||
for (const sourceFile of sourceFiles) {
|
||||
try {
|
||||
const sourcePath = path.join(sourceDir, sourceFile);
|
||||
|
||||
// Check if source file exists
|
||||
if (!fs.existsSync(sourcePath)) {
|
||||
log('warn', `[Rule Transformer] Source file not found: ${sourceFile}, skipping`);
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
|
||||
const targetFilename = profile.getTargetRuleFilename
|
||||
? profile.getTargetRuleFilename(sourceFile)
|
||||
: sourceFile;
|
||||
const targetPath = path.join(targetDir, targetFilename);
|
||||
|
||||
// Read source content
|
||||
let content = fs.readFileSync(sourcePath, 'utf8');
|
||||
|
||||
// Apply transformations
|
||||
content = transformCursorToProfileRules(
|
||||
content,
|
||||
profile.conversionConfig,
|
||||
profile.globalReplacements
|
||||
);
|
||||
|
||||
// Write to target
|
||||
fs.writeFileSync(targetPath, content, 'utf8');
|
||||
success++;
|
||||
|
||||
log(
|
||||
'warn',
|
||||
`File listed in fileMap not found in rules dir: ${sourcePath}`
|
||||
'debug',
|
||||
`[Rule Transformer] Converted ${sourceFile} -> ${targetFilename} for ${profile.profileName}`
|
||||
);
|
||||
} catch (error) {
|
||||
failed++;
|
||||
log(
|
||||
'error',
|
||||
`[Rule Transformer] Failed to convert ${sourceFile} for ${profile.profileName}: ${error.message}`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
log(
|
||||
'debug',
|
||||
`Rule conversion complete: ${success} successful, ${failed} failed`
|
||||
);
|
||||
}
|
||||
|
||||
// Call post-processing hook if defined (e.g., for Roo's rules-*mode* folders)
|
||||
if (typeof profile.onPostConvertBrandRules === 'function') {
|
||||
profile.onPostConvertBrandRules(projectDir);
|
||||
if (typeof profile.onPostConvertRulesProfile === 'function') {
|
||||
profile.onPostConvertRulesProfile(projectDir);
|
||||
}
|
||||
|
||||
return { success, failed };
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a brand's rules directory, its mcp.json, and the parent brand folder recursively.
|
||||
* @param {string} projectDir - The root directory of the project
|
||||
* @param {object} profile - The brand profile object
|
||||
* @returns {boolean} - True if removal succeeded, false otherwise
|
||||
* Remove profile rules for a specific profile
|
||||
* @param {string} projectDir - Target project directory
|
||||
* @param {Object} profile - Profile configuration
|
||||
* @returns {Object} Result object
|
||||
*/
|
||||
function removeBrandRules(projectDir, profile) {
|
||||
const { brandName, rulesDir, mcpConfig, mcpConfigName } = profile;
|
||||
const brandDir = profile.brandDir;
|
||||
const brandRulesDir = path.join(projectDir, rulesDir);
|
||||
const mcpPath = path.join(projectDir, brandDir, mcpConfigName);
|
||||
export function removeProfileRules(projectDir, profile) {
|
||||
const targetDir = path.join(projectDir, profile.rulesDir);
|
||||
const profileDir = path.join(projectDir, profile.profileDir);
|
||||
const mcpConfigPath = path.join(projectDir, profile.mcpConfigPath);
|
||||
|
||||
const result = {
|
||||
brandName,
|
||||
mcpConfigRemoved: false,
|
||||
rulesDirRemoved: false,
|
||||
brandFolderRemoved: false,
|
||||
let result = {
|
||||
profileName: profile.profileName,
|
||||
success: false,
|
||||
skipped: false,
|
||||
error: null,
|
||||
success: false // Overall success for this brand
|
||||
error: null
|
||||
};
|
||||
|
||||
if (mcpConfig !== false && fs.existsSync(mcpPath)) {
|
||||
try {
|
||||
fs.unlinkSync(mcpPath);
|
||||
result.mcpConfigRemoved = true;
|
||||
} catch (e) {
|
||||
const errorMessage = `Failed to remove MCP configuration at ${mcpPath}: ${e.message}`;
|
||||
log('warn', errorMessage);
|
||||
result.error = result.error
|
||||
? `${result.error}; ${errorMessage}`
|
||||
: errorMessage;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove rules directory
|
||||
if (fs.existsSync(brandRulesDir)) {
|
||||
try {
|
||||
fs.rmSync(brandRulesDir, { recursive: true, force: true });
|
||||
result.rulesDirRemoved = true;
|
||||
} catch (e) {
|
||||
const errorMessage = `Failed to remove rules directory at ${brandRulesDir}: ${e.message}`;
|
||||
log('warn', errorMessage);
|
||||
result.error = result.error
|
||||
? `${result.error}; ${errorMessage}`
|
||||
: errorMessage;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove brand folder
|
||||
try {
|
||||
fs.rmSync(brandDir, { recursive: true, force: true });
|
||||
result.brandFolderRemoved = true;
|
||||
} catch (e) {
|
||||
const errorMessage = `Failed to remove brand folder at ${brandDir}: ${e.message}`;
|
||||
log('warn', errorMessage);
|
||||
result.error = result.error
|
||||
? `${result.error}; ${errorMessage}`
|
||||
: errorMessage;
|
||||
}
|
||||
|
||||
// Call onRemoveBrandRules hook if present
|
||||
if (typeof profile.onRemoveBrandRules === 'function') {
|
||||
try {
|
||||
profile.onRemoveBrandRules(projectDir);
|
||||
} catch (e) {
|
||||
const errorMessage = `Error in onRemoveBrandRules for ${brandName}: ${e.message}`;
|
||||
log('warn', errorMessage);
|
||||
result.error = result.error
|
||||
? `${result.error}; ${errorMessage}`
|
||||
: errorMessage;
|
||||
// Remove rules directory
|
||||
if (fs.existsSync(targetDir)) {
|
||||
fs.rmSync(targetDir, { recursive: true, force: true });
|
||||
log('debug', `[Rule Transformer] Removed rules directory: ${targetDir}`);
|
||||
}
|
||||
|
||||
// Remove MCP config if it exists
|
||||
if (fs.existsSync(mcpConfigPath)) {
|
||||
fs.rmSync(mcpConfigPath, { force: true });
|
||||
log('debug', `[Rule Transformer] Removed MCP config: ${mcpConfigPath}`);
|
||||
}
|
||||
|
||||
// Call removal hook if defined
|
||||
if (typeof profile.onRemoveRulesProfile === 'function') {
|
||||
profile.onRemoveRulesProfile(projectDir);
|
||||
}
|
||||
|
||||
// Remove profile directory if empty
|
||||
if (fs.existsSync(profileDir)) {
|
||||
const remaining = fs.readdirSync(profileDir);
|
||||
if (remaining.length === 0) {
|
||||
fs.rmSync(profileDir, { recursive: true, force: true });
|
||||
log(
|
||||
'debug',
|
||||
`[Rule Transformer] Removed empty profile directory: ${profileDir}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
result.success = true;
|
||||
log(
|
||||
'debug',
|
||||
`[Rule Transformer] Successfully removed ${profile.profileName} rules from ${projectDir}`
|
||||
);
|
||||
} catch (error) {
|
||||
result.error = error.message;
|
||||
log(
|
||||
'error',
|
||||
`[Rule Transformer] Failed to remove ${profile.profileName} rules: ${error.message}`
|
||||
);
|
||||
}
|
||||
|
||||
result.success =
|
||||
result.mcpConfigRemoved ||
|
||||
result.rulesDirRemoved ||
|
||||
result.brandFolderRemoved;
|
||||
return result;
|
||||
}
|
||||
|
||||
export {
|
||||
convertAllRulesToBrandRules,
|
||||
convertRuleToBrandRule,
|
||||
removeBrandRules
|
||||
};
|
||||
@@ -1,13 +1,13 @@
|
||||
import readline from 'readline';
|
||||
import inquirer from 'inquirer';
|
||||
import chalk from 'chalk';
|
||||
import { BRAND_PROFILES, BRAND_NAMES } from './rule-transformer.js';
|
||||
import { log } from '../../scripts/modules/utils.js';
|
||||
import { getRulesProfile } from './rule-transformer.js';
|
||||
import { RULES_PROFILES } from '../constants/profiles.js';
|
||||
|
||||
// Dynamically generate availableBrandRules from BRAND_NAMES and brand profiles
|
||||
const availableBrandRules = BRAND_NAMES.map((name) => {
|
||||
const displayName =
|
||||
BRAND_PROFILES[name]?.brandName ||
|
||||
name.charAt(0).toUpperCase() + name.slice(1);
|
||||
// Dynamically generate availableRulesProfiles from RULES_PROFILES
|
||||
const availableRulesProfiles = RULES_PROFILES.map((name) => {
|
||||
const displayName = getProfileDisplayName(name);
|
||||
return {
|
||||
name: name === 'cursor' ? `${displayName} (default)` : displayName,
|
||||
value: name
|
||||
@@ -15,18 +15,26 @@ const availableBrandRules = BRAND_NAMES.map((name) => {
|
||||
});
|
||||
|
||||
/**
|
||||
* Runs the interactive rules setup flow (brand rules selection only)
|
||||
* @returns {Promise<string[]>} The selected brand rules
|
||||
* Get the display name for a profile
|
||||
*/
|
||||
function getProfileDisplayName(name) {
|
||||
const profile = getRulesProfile(name);
|
||||
return profile?.profileName || name.charAt(0).toUpperCase() + name.slice(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the interactive rules setup flow (profile rules selection only)
|
||||
* @returns {Promise<string[]>} The selected profile rules
|
||||
*/
|
||||
/**
|
||||
* Launches an interactive prompt for selecting which brand rules to include in your project.
|
||||
* Launches an interactive prompt for selecting which profile rules to include in your project.
|
||||
*
|
||||
* This function dynamically lists all available brands (from BRAND_PROFILES) and presents them as checkboxes.
|
||||
* The user must select at least one brand (default: cursor). The result is an array of selected brand names.
|
||||
* This function dynamically lists all available profiles (from RULES_PROFILES) and presents them as checkboxes.
|
||||
* The user must select at least one profile (default: cursor). The result is an array of selected profile names.
|
||||
*
|
||||
* Used by both project initialization (init) and the CLI 'task-master rules setup' command to ensure DRY, consistent UX.
|
||||
*
|
||||
* @returns {Promise<string[]>} Array of selected brand rule names (e.g., ['cursor', 'windsurf'])
|
||||
* @returns {Promise<string[]>} Array of selected profile rule names (e.g., ['cursor', 'windsurf'])
|
||||
*/
|
||||
export async function runInteractiveRulesSetup() {
|
||||
console.log(
|
||||
@@ -34,14 +42,14 @@ export async function runInteractiveRulesSetup() {
|
||||
'\nRules help enforce best practices and conventions for Task Master.'
|
||||
)
|
||||
);
|
||||
const brandRulesQuestion = {
|
||||
const rulesProfilesQuestion = {
|
||||
type: 'checkbox',
|
||||
name: 'brandRules',
|
||||
name: 'rulesProfiles',
|
||||
message: 'Which IDEs would you like rules included for?',
|
||||
choices: availableBrandRules,
|
||||
choices: availableRulesProfiles,
|
||||
default: ['cursor'],
|
||||
validate: (input) => input.length > 0 || 'You must select at least one.'
|
||||
};
|
||||
const { brandRules } = await inquirer.prompt([brandRulesQuestion]);
|
||||
return brandRules;
|
||||
}
|
||||
const { rulesProfiles } = await inquirer.prompt([rulesProfilesQuestion]);
|
||||
return rulesProfiles;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user