update MCP responses, centralize rules profiles & helpers
This commit is contained in:
@@ -3,11 +3,11 @@
|
|||||||
* Direct function implementation for adding or removing brand rules
|
* Direct function implementation for adding or removing brand rules
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { execSync } from 'child_process';
|
|
||||||
import {
|
import {
|
||||||
enableSilentMode,
|
enableSilentMode,
|
||||||
disableSilentMode
|
disableSilentMode
|
||||||
} from '../../../../scripts/modules/utils.js';
|
} from '../../../../scripts/modules/utils.js';
|
||||||
|
import { removeBrandRules, convertAllRulesToBrandRules, BRAND_NAMES, isValidBrand, getBrandProfile } from '../../../../scripts/modules/rule-transformer.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Direct function wrapper for adding or removing brand rules.
|
* Direct function wrapper for adding or removing brand rules.
|
||||||
@@ -38,13 +38,58 @@ export async function rulesDirect(args, log, context = {}) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const rulesList = rules.join(',');
|
|
||||||
const cmd = `npx task-master rules ${action} ${rulesList}`.trim();
|
const removalResults = [];
|
||||||
log.info(`[rulesDirect] Running: ${cmd} in ${projectRoot}`);
|
const addResults = [];
|
||||||
const output = execSync(cmd, { cwd: projectRoot, encoding: 'utf8' });
|
|
||||||
log.info(`[rulesDirect] Output: ${output}`);
|
if (action === 'remove') {
|
||||||
|
for (const brand of rules) {
|
||||||
|
if (!isValidBrand(brand)) {
|
||||||
|
removalResults.push({ brandName: brand, success: false, error: `Profile not found: static import missing for '${brand}'. Valid brands: ${BRAND_NAMES.join(', ')}` });
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const profile = getBrandProfile(brand);
|
||||||
|
const result = removeBrandRules(projectRoot, profile);
|
||||||
|
removalResults.push(result);
|
||||||
|
}
|
||||||
|
const successes = removalResults.filter(r => r.success).map(r => r.brandName);
|
||||||
|
const skipped = removalResults.filter(r => r.skipped).map(r => r.brandName);
|
||||||
|
const errors = removalResults.filter(r => r.error && !r.success && !r.skipped);
|
||||||
|
|
||||||
|
let summary = '';
|
||||||
|
if (successes.length > 0) {
|
||||||
|
summary += `Successfully removed rules: ${successes.join(', ')}. `;
|
||||||
|
}
|
||||||
|
if (skipped.length > 0) {
|
||||||
|
summary += `Skipped (default or protected): ${skipped.join(', ')}. `;
|
||||||
|
}
|
||||||
|
if (errors.length > 0) {
|
||||||
|
summary += errors.map(r => `Error removing ${r.brandName}: ${r.error}`).join(' ');
|
||||||
|
}
|
||||||
disableSilentMode();
|
disableSilentMode();
|
||||||
return { success: true, data: { output } };
|
return { success: errors.length === 0, data: { summary, results: removalResults } };
|
||||||
|
} else if (action === 'add') {
|
||||||
|
for (const brand of rules) {
|
||||||
|
if (!isValidBrand(brand)) {
|
||||||
|
addResults.push({ brandName: brand, success: false, error: `Profile not found: static import missing for '${brand}'. Valid brands: ${BRAND_NAMES.join(', ')}` });
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const profile = getBrandProfile(brand);
|
||||||
|
const result = convertAllRulesToBrandRules(projectRoot, profile);
|
||||||
|
addResults.push({ brandName: brand, ...result });
|
||||||
|
}
|
||||||
|
disableSilentMode();
|
||||||
|
return { success: true, data: { results: addResults } };
|
||||||
|
} else {
|
||||||
|
disableSilentMode();
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: {
|
||||||
|
code: 'INVALID_ACTION',
|
||||||
|
message: 'Unknown action. Use "add" or "remove".'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
disableSilentMode();
|
disableSilentMode();
|
||||||
log.error(`[rulesDirect] Error: ${error.message}`);
|
log.error(`[rulesDirect] Error: ${error.message}`);
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ import {
|
|||||||
getApiKeyStatusReport
|
getApiKeyStatusReport
|
||||||
} from './task-manager/models.js';
|
} from './task-manager/models.js';
|
||||||
import { findProjectRoot } from './utils.js';
|
import { findProjectRoot } from './utils.js';
|
||||||
|
import { convertAllRulesToBrandRules, removeBrandRules, BRAND_NAMES, isValidBrand, getBrandProfile } from './rule-transformer.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Runs the interactive setup process for model configuration.
|
* Runs the interactive setup process for model configuration.
|
||||||
@@ -511,38 +512,24 @@ function registerCommands(programInstance) {
|
|||||||
const expandedBrands = brands
|
const expandedBrands = brands
|
||||||
.flatMap((b) => b.split(',').map((s) => s.trim()))
|
.flatMap((b) => b.split(',').map((s) => s.trim()))
|
||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
|
|
||||||
|
const removalResults = [];
|
||||||
|
|
||||||
for (const brand of expandedBrands) {
|
for (const brand of expandedBrands) {
|
||||||
let profile;
|
if (!isValidBrand(brand)) {
|
||||||
try {
|
console.warn(`Rules profile for brand "${brand}" not found. Valid brands: ${BRAND_NAMES.join(', ')}. Skipping.`);
|
||||||
// Use pathToFileURL for correct ESM dynamic import
|
|
||||||
const { pathToFileURL } = await import('url');
|
|
||||||
const profilePath = path.resolve(
|
|
||||||
process.cwd(),
|
|
||||||
'scripts',
|
|
||||||
'profiles',
|
|
||||||
`${brand}.js`
|
|
||||||
);
|
|
||||||
const profileModule = await import(pathToFileURL(profilePath).href);
|
|
||||||
profile = profileModule.default || profileModule;
|
|
||||||
} catch (e) {
|
|
||||||
console.warn(
|
|
||||||
`Rules profile for brand "${brand}" not found. Skipping.`
|
|
||||||
);
|
|
||||||
console.warn(`Import error: ${e && e.message ? e.message : e}`);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
const profile = getBrandProfile(brand);
|
||||||
|
|
||||||
if (action === 'add') {
|
if (action === 'add') {
|
||||||
const { convertAllRulesToBrandRules } = await import(
|
|
||||||
'./rule-transformer.js'
|
|
||||||
);
|
|
||||||
convertAllRulesToBrandRules(projectDir, profile);
|
convertAllRulesToBrandRules(projectDir, profile);
|
||||||
if (typeof profile.onAddBrandRules === 'function') {
|
if (typeof profile.onAddBrandRules === 'function') {
|
||||||
profile.onAddBrandRules(projectDir);
|
profile.onAddBrandRules(projectDir);
|
||||||
}
|
}
|
||||||
} else if (action === 'remove') {
|
} else if (action === 'remove') {
|
||||||
const { removeBrandRules } = await import('./rule-transformer.js');
|
const result = removeBrandRules(projectDir, profile);
|
||||||
removeBrandRules(projectDir, profile);
|
removalResults.push(result);
|
||||||
if (typeof profile.onRemoveBrandRules === 'function') {
|
if (typeof profile.onRemoveBrandRules === 'function') {
|
||||||
profile.onRemoveBrandRules(projectDir);
|
profile.onRemoveBrandRules(projectDir);
|
||||||
}
|
}
|
||||||
@@ -551,6 +538,25 @@ function registerCommands(programInstance) {
|
|||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Print summary for removals
|
||||||
|
if (action === 'remove') {
|
||||||
|
const successes = removalResults.filter(r => r.success).map(r => r.brandName);
|
||||||
|
const skipped = removalResults.filter(r => r.skipped).map(r => r.brandName);
|
||||||
|
const errors = removalResults.filter(r => r.error && !r.success && !r.skipped);
|
||||||
|
|
||||||
|
if (successes.length > 0) {
|
||||||
|
console.log(chalk.green(`Successfully removed rules: ${successes.join(', ')}`));
|
||||||
|
}
|
||||||
|
if (skipped.length > 0) {
|
||||||
|
console.log(chalk.yellow(`Skipped (default or protected): ${skipped.join(', ')}`));
|
||||||
|
}
|
||||||
|
if (errors.length > 0) {
|
||||||
|
errors.forEach(r => {
|
||||||
|
console.log(chalk.red(`Error removing ${r.brandName}: ${r.error}`));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// parse-prd command
|
// parse-prd command
|
||||||
|
|||||||
@@ -12,6 +12,27 @@ import { log } from './utils.js';
|
|||||||
// Import the shared MCP configuration helper
|
// Import the shared MCP configuration helper
|
||||||
import { setupMCPConfiguration } from './mcp-utils.js';
|
import { setupMCPConfiguration } from './mcp-utils.js';
|
||||||
|
|
||||||
|
// --- Centralized Brand Helpers ---
|
||||||
|
export const BRAND_NAMES = ['cursor', 'roo', 'windsurf'];
|
||||||
|
|
||||||
|
import * as cursorProfile from '../profiles/cursor.js';
|
||||||
|
import * as rooProfile from '../profiles/roo.js';
|
||||||
|
import * as windsurfProfile from '../profiles/windsurf.js';
|
||||||
|
|
||||||
|
export const BRAND_PROFILES = {
|
||||||
|
cursor: cursorProfile,
|
||||||
|
roo: rooProfile,
|
||||||
|
windsurf: windsurfProfile
|
||||||
|
};
|
||||||
|
|
||||||
|
export function isValidBrand(brand) {
|
||||||
|
return BRAND_NAMES.includes(brand);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getBrandProfile(brand) {
|
||||||
|
return BRAND_PROFILES[brand];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Replace basic Cursor terms with brand equivalents
|
* Replace basic Cursor terms with brand equivalents
|
||||||
*/
|
*/
|
||||||
@@ -223,6 +244,7 @@ function convertAllRulesToBrandRules(projectDir, profile) {
|
|||||||
|
|
||||||
return { success, failed };
|
return { success, failed };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove a brand's rules directory and, if empty, the parent brand folder (except .cursor)
|
* Remove a brand's rules directory and, if empty, the parent brand folder (except .cursor)
|
||||||
* @param {string} projectDir - The root directory of the project
|
* @param {string} projectDir - The root directory of the project
|
||||||
@@ -233,41 +255,63 @@ function removeBrandRules(projectDir, profile) {
|
|||||||
const { brandName, rulesDir } = profile;
|
const { brandName, rulesDir } = profile;
|
||||||
const brandRulesDir = path.join(projectDir, rulesDir);
|
const brandRulesDir = path.join(projectDir, rulesDir);
|
||||||
const brandDir = path.dirname(brandRulesDir);
|
const brandDir = path.dirname(brandRulesDir);
|
||||||
// Also remove the mcp.json file if it exists in the brand directory
|
|
||||||
const mcpPath = path.join(brandDir, 'mcp.json');
|
const mcpPath = path.join(brandDir, 'mcp.json');
|
||||||
|
|
||||||
|
const result = {
|
||||||
|
brandName,
|
||||||
|
mcpConfigRemoved: false,
|
||||||
|
rulesDirRemoved: false,
|
||||||
|
brandFolderRemoved: false,
|
||||||
|
skipped: false,
|
||||||
|
error: null,
|
||||||
|
success: false // Overall success for this brand
|
||||||
|
};
|
||||||
|
|
||||||
if (fs.existsSync(mcpPath)) {
|
if (fs.existsSync(mcpPath)) {
|
||||||
try {
|
try {
|
||||||
fs.unlinkSync(mcpPath);
|
fs.unlinkSync(mcpPath);
|
||||||
log('info', `Removed MCP configuration: ${mcpPath}`);
|
result.mcpConfigRemoved = true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(
|
const errorMessage = `Failed to remove MCP configuration at ${mcpPath}: ${e.message}`;
|
||||||
'warn',
|
log('warn', errorMessage);
|
||||||
`Failed to remove MCP configuration at ${mcpPath}: ${e.message}`
|
result.error = result.error ? `${result.error}; ${errorMessage}` : errorMessage;
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Do not allow removal of the default Cursor rules directory
|
|
||||||
if (brandName.toLowerCase() === 'cursor') {
|
if (brandName.toLowerCase() === 'cursor') {
|
||||||
log('warn', 'Cannot remove default Cursor rules directory. Skipping.');
|
const skipMessage = 'Cannot remove default Cursor rules directory. Skipping.';
|
||||||
return false;
|
log('warn', skipMessage);
|
||||||
|
result.skipped = true;
|
||||||
|
result.error = skipMessage;
|
||||||
|
return result; // Early exit for cursor brand
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fs.existsSync(brandRulesDir)) {
|
if (fs.existsSync(brandRulesDir)) {
|
||||||
|
try {
|
||||||
fs.rmSync(brandRulesDir, { recursive: true, force: true });
|
fs.rmSync(brandRulesDir, { recursive: true, force: true });
|
||||||
log('info', `Removed rules directory: ${brandRulesDir}`);
|
result.rulesDirRemoved = true;
|
||||||
// Check if parent brand folder is empty
|
|
||||||
if (
|
if (
|
||||||
fs.existsSync(brandDir) &&
|
fs.existsSync(brandDir) &&
|
||||||
path.basename(brandDir) !== '.cursor' &&
|
path.basename(brandDir) !== '.cursor' &&
|
||||||
fs.readdirSync(brandDir).length === 0
|
fs.readdirSync(brandDir).length === 0
|
||||||
) {
|
) {
|
||||||
fs.rmdirSync(brandDir);
|
fs.rmdirSync(brandDir);
|
||||||
log('info', `Removed empty brand folder: ${brandDir}`);
|
result.brandFolderRemoved = true;
|
||||||
|
}
|
||||||
|
result.success = true; // Mark overall success if rules dir was removed
|
||||||
|
} catch (e) {
|
||||||
|
const errorMessage = `Failed to remove rules directory ${brandRulesDir} or brand folder ${brandDir}: ${e.message}`;
|
||||||
|
log('error', errorMessage); // Log as error since this is a primary operation failing
|
||||||
|
result.error = result.error ? `${result.error}; ${errorMessage}` : errorMessage;
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
} else {
|
} else {
|
||||||
log('warn', `Rules directory not found: ${brandRulesDir}`);
|
const warnMessage = `Rules directory not found: ${brandRulesDir}`;
|
||||||
return false;
|
log('warn', warnMessage);
|
||||||
|
result.error = result.error ? `${result.error}; ${warnMessage}` : warnMessage;
|
||||||
|
// success remains false as the primary target (rulesDir) was not found
|
||||||
}
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
|
import { isSilentMode } from '../modules/utils.js';
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = path.dirname(__filename);
|
const __dirname = path.dirname(__filename);
|
||||||
@@ -136,12 +137,12 @@ export function onAddBrandRules(targetDir) {
|
|||||||
if (fs.existsSync(roomodesSrc)) {
|
if (fs.existsSync(roomodesSrc)) {
|
||||||
try {
|
try {
|
||||||
fs.copyFileSync(roomodesSrc, roomodesDest);
|
fs.copyFileSync(roomodesSrc, roomodesDest);
|
||||||
console.log(`[Roo] Copied .roomodes to ${roomodesDest}`);
|
if (!isSilentMode()) console.log(`[Roo] Copied .roomodes to ${roomodesDest}`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn(`[Roo] Failed to copy .roomodes: ${err.message}`);
|
if (!isSilentMode()) console.warn(`[Roo] Failed to copy .roomodes: ${err.message}`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.warn(`[Roo] .roomodes not found at ${roomodesSrc}`);
|
if (!isSilentMode()) console.warn(`[Roo] .roomodes not found at ${roomodesSrc}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy each <mode>-rules file into the corresponding .roo/rules-<mode>/ folder
|
// Copy each <mode>-rules file into the corresponding .roo/rules-<mode>/ folder
|
||||||
@@ -154,12 +155,12 @@ export function onAddBrandRules(targetDir) {
|
|||||||
const destDir = path.dirname(dest);
|
const destDir = path.dirname(dest);
|
||||||
if (!fs.existsSync(destDir)) fs.mkdirSync(destDir, { recursive: true });
|
if (!fs.existsSync(destDir)) fs.mkdirSync(destDir, { recursive: true });
|
||||||
fs.copyFileSync(src, dest);
|
fs.copyFileSync(src, dest);
|
||||||
console.log(`[Roo] Copied ${src} to ${dest}`);
|
if (!isSilentMode()) console.log(`[Roo] Copied ${src} to ${dest}`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn(`[Roo] Failed to copy ${src} to ${dest}: ${err.message}`);
|
if (!isSilentMode()) console.warn(`[Roo] Failed to copy ${src} to ${dest}: ${err.message}`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.warn(`[Roo] Roo rule file not found for mode '${mode}': ${src}`);
|
if (!isSilentMode()) console.warn(`[Roo] Roo rule file not found for mode '${mode}': ${src}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user