update MCP responses, centralize rules profiles & helpers

This commit is contained in:
Joe Danziger
2025-05-11 10:37:41 -04:00
parent c559e1d3fa
commit 0543ba3057
4 changed files with 154 additions and 58 deletions

View File

@@ -73,6 +73,7 @@ import {
getApiKeyStatusReport
} from './task-manager/models.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.
@@ -511,38 +512,24 @@ function registerCommands(programInstance) {
const expandedBrands = brands
.flatMap((b) => b.split(',').map((s) => s.trim()))
.filter(Boolean);
const removalResults = [];
for (const brand of expandedBrands) {
let profile;
try {
// 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}`);
if (!isValidBrand(brand)) {
console.warn(`Rules profile for brand "${brand}" not found. Valid brands: ${BRAND_NAMES.join(', ')}. Skipping.`);
continue;
}
const profile = getBrandProfile(brand);
if (action === 'add') {
const { convertAllRulesToBrandRules } = await import(
'./rule-transformer.js'
);
convertAllRulesToBrandRules(projectDir, profile);
if (typeof profile.onAddBrandRules === 'function') {
profile.onAddBrandRules(projectDir);
}
} else if (action === 'remove') {
const { removeBrandRules } = await import('./rule-transformer.js');
removeBrandRules(projectDir, profile);
const result = removeBrandRules(projectDir, profile);
removalResults.push(result);
if (typeof profile.onRemoveBrandRules === 'function') {
profile.onRemoveBrandRules(projectDir);
}
@@ -551,6 +538,25 @@ function registerCommands(programInstance) {
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

View File

@@ -12,6 +12,27 @@ import { log } from './utils.js';
// Import the shared MCP configuration helper
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
*/
@@ -223,6 +244,7 @@ function convertAllRulesToBrandRules(projectDir, profile) {
return { success, failed };
}
/**
* Remove a brand's rules directory and, if empty, the parent brand folder (except .cursor)
* @param {string} projectDir - The root directory of the project
@@ -233,41 +255,63 @@ function removeBrandRules(projectDir, profile) {
const { brandName, rulesDir } = profile;
const brandRulesDir = path.join(projectDir, rulesDir);
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 result = {
brandName,
mcpConfigRemoved: false,
rulesDirRemoved: false,
brandFolderRemoved: false,
skipped: false,
error: null,
success: false // Overall success for this brand
};
if (fs.existsSync(mcpPath)) {
try {
fs.unlinkSync(mcpPath);
log('info', `Removed MCP configuration: ${mcpPath}`);
result.mcpConfigRemoved = true;
} catch (e) {
log(
'warn',
`Failed to remove MCP configuration at ${mcpPath}: ${e.message}`
);
const errorMessage = `Failed to remove MCP configuration at ${mcpPath}: ${e.message}`;
log('warn', errorMessage);
result.error = result.error ? `${result.error}; ${errorMessage}` : errorMessage;
}
}
// Do not allow removal of the default Cursor rules directory
if (brandName.toLowerCase() === 'cursor') {
log('warn', 'Cannot remove default Cursor rules directory. Skipping.');
return false;
const skipMessage = 'Cannot remove default Cursor rules directory. Skipping.';
log('warn', skipMessage);
result.skipped = true;
result.error = skipMessage;
return result; // Early exit for cursor brand
}
if (fs.existsSync(brandRulesDir)) {
fs.rmSync(brandRulesDir, { recursive: true, force: true });
log('info', `Removed rules directory: ${brandRulesDir}`);
// Check if parent brand folder is empty
if (
fs.existsSync(brandDir) &&
path.basename(brandDir) !== '.cursor' &&
fs.readdirSync(brandDir).length === 0
) {
fs.rmdirSync(brandDir);
log('info', `Removed empty brand folder: ${brandDir}`);
try {
fs.rmSync(brandRulesDir, { recursive: true, force: true });
result.rulesDirRemoved = true;
if (
fs.existsSync(brandDir) &&
path.basename(brandDir) !== '.cursor' &&
fs.readdirSync(brandDir).length === 0
) {
fs.rmdirSync(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 {
log('warn', `Rules directory not found: ${brandRulesDir}`);
return false;
const warnMessage = `Rules directory not found: ${brandRulesDir}`;
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 {

View File

@@ -2,6 +2,7 @@
import { fileURLToPath } from 'url';
import path from 'path';
import fs from 'fs';
import { isSilentMode } from '../modules/utils.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
@@ -136,12 +137,12 @@ export function onAddBrandRules(targetDir) {
if (fs.existsSync(roomodesSrc)) {
try {
fs.copyFileSync(roomodesSrc, roomodesDest);
console.log(`[Roo] Copied .roomodes to ${roomodesDest}`);
if (!isSilentMode()) console.log(`[Roo] Copied .roomodes to ${roomodesDest}`);
} catch (err) {
console.warn(`[Roo] Failed to copy .roomodes: ${err.message}`);
if (!isSilentMode()) console.warn(`[Roo] Failed to copy .roomodes: ${err.message}`);
}
} 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
@@ -154,12 +155,12 @@ export function onAddBrandRules(targetDir) {
const destDir = path.dirname(dest);
if (!fs.existsSync(destDir)) fs.mkdirSync(destDir, { recursive: true });
fs.copyFileSync(src, dest);
console.log(`[Roo] Copied ${src} to ${dest}`);
if (!isSilentMode()) console.log(`[Roo] Copied ${src} to ${dest}`);
} 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 {
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}`);
}
}
}