combine to /src/utils/profiles.js; add codex and claude code profiles
This commit is contained in:
@@ -18,7 +18,7 @@ import { RULES_ACTIONS } from '../../../../src/constants/rules-actions.js';
|
|||||||
import {
|
import {
|
||||||
wouldRemovalLeaveNoProfiles,
|
wouldRemovalLeaveNoProfiles,
|
||||||
getInstalledProfiles
|
getInstalledProfiles
|
||||||
} from '../../../../src/utils/profile-detection.js';
|
} from '../../../../src/utils/profiles.js';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import {
|
|||||||
convertAllRulesToProfileRules,
|
convertAllRulesToProfileRules,
|
||||||
getRulesProfile
|
getRulesProfile
|
||||||
} from '../src/utils/rule-transformer.js';
|
} from '../src/utils/rule-transformer.js';
|
||||||
import { runInteractiveRulesSetup } from '../src/utils/rules-setup.js';
|
import { runInteractiveRulesSetup } from '../src/utils/profiles.js';
|
||||||
import { execSync } from 'child_process';
|
import { execSync } from 'child_process';
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
wouldRemovalLeaveNoProfiles,
|
wouldRemovalLeaveNoProfiles,
|
||||||
getInstalledProfiles
|
getInstalledProfiles
|
||||||
} from '../../src/utils/profile-detection.js';
|
} from '../../src/utils/profiles.js';
|
||||||
|
|
||||||
import { initializeProject } from '../init.js';
|
import { initializeProject } from '../init.js';
|
||||||
import {
|
import {
|
||||||
@@ -102,7 +102,13 @@ import {
|
|||||||
isValidProfile,
|
isValidProfile,
|
||||||
getRulesProfile
|
getRulesProfile
|
||||||
} from '../../src/utils/rule-transformer.js';
|
} from '../../src/utils/rule-transformer.js';
|
||||||
import { runInteractiveRulesSetup } from '../../src/utils/rules-setup.js';
|
import {
|
||||||
|
runInteractiveRulesSetup,
|
||||||
|
generateProfileSummary,
|
||||||
|
categorizeProfileResults,
|
||||||
|
generateProfileRemovalSummary,
|
||||||
|
categorizeRemovalResults
|
||||||
|
} from '../../src/utils/profiles.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Runs the interactive setup process for model configuration.
|
* Runs the interactive setup process for model configuration.
|
||||||
@@ -2701,7 +2707,7 @@ Examples:
|
|||||||
for (const profile of selectedRulesProfiles) {
|
for (const profile of selectedRulesProfiles) {
|
||||||
if (!isValidProfile(profile)) {
|
if (!isValidProfile(profile)) {
|
||||||
console.warn(
|
console.warn(
|
||||||
`Rules profile for "${profile}" not found. Valid profiles: ${RULE_PROFILES.join(', ')}. Skipping.`
|
`Rule profile for "${profile}" not found. Valid profiles: ${RULE_PROFILES.join(', ')}. Skipping.`
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -2713,18 +2719,15 @@ Examples:
|
|||||||
if (typeof profileConfig.onAddRulesProfile === 'function') {
|
if (typeof profileConfig.onAddRulesProfile === 'function') {
|
||||||
profileConfig.onAddRulesProfile(projectDir);
|
profileConfig.onAddRulesProfile(projectDir);
|
||||||
}
|
}
|
||||||
console.log(
|
|
||||||
chalk.green(
|
console.log(chalk.green(generateProfileSummary(profile, addResult)));
|
||||||
`Summary for ${profile}: ${addResult.success} rules added, ${addResult.failed} failed.`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!profiles || profiles.length === 0) {
|
if (!profiles || profiles.length === 0) {
|
||||||
console.error(
|
console.error(
|
||||||
'Please specify at least one rules profile (e.g., windsurf, roo).'
|
'Please specify at least one rule profile (e.g., windsurf, roo).'
|
||||||
);
|
);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
@@ -2760,7 +2763,7 @@ Examples:
|
|||||||
for (const profile of expandedProfiles) {
|
for (const profile of expandedProfiles) {
|
||||||
if (!isValidProfile(profile)) {
|
if (!isValidProfile(profile)) {
|
||||||
console.warn(
|
console.warn(
|
||||||
`Rules profile for "${profile}" not found. Valid profiles: ${RULE_PROFILES.join(', ')}. Skipping.`
|
`Rule profile for "${profile}" not found. Valid profiles: ${RULE_PROFILES.join(', ')}. Skipping.`
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -2786,16 +2789,14 @@ Examples:
|
|||||||
failed: addResult.failed
|
failed: addResult.failed
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(
|
console.log(chalk.green(generateProfileSummary(profile, addResult)));
|
||||||
chalk.green(
|
|
||||||
`Summary for ${profile}: ${addResult.success} rules added, ${addResult.failed} failed.`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
} else if (action === RULES_ACTIONS.REMOVE) {
|
} else if (action === RULES_ACTIONS.REMOVE) {
|
||||||
console.log(chalk.blue(`Removing rules for profile: ${profile}...`));
|
console.log(chalk.blue(`Removing rules for profile: ${profile}...`));
|
||||||
const result = removeProfileRules(projectDir, profileConfig);
|
const result = removeProfileRules(projectDir, profileConfig);
|
||||||
removalResults.push(result);
|
removalResults.push(result);
|
||||||
console.log(chalk.blue(`Completed removal for profile: ${profile}`));
|
console.log(
|
||||||
|
chalk.green(generateProfileRemovalSummary(profile, result))
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
console.error(
|
console.error(
|
||||||
`Unknown action. Use "${RULES_ACTIONS.ADD}" or "${RULES_ACTIONS.REMOVE}".`
|
`Unknown action. Use "${RULES_ACTIONS.ADD}" or "${RULES_ACTIONS.REMOVE}".`
|
||||||
@@ -2806,67 +2807,91 @@ Examples:
|
|||||||
|
|
||||||
// Print summary for additions
|
// Print summary for additions
|
||||||
if (action === RULES_ACTIONS.ADD && addResults.length > 0) {
|
if (action === RULES_ACTIONS.ADD && addResults.length > 0) {
|
||||||
const totalSuccess = addResults.reduce((sum, r) => sum + r.success, 0);
|
const {
|
||||||
const totalFailed = addResults.reduce((sum, r) => sum + r.failed, 0);
|
allSuccessfulProfiles,
|
||||||
const successfulProfiles = addResults
|
totalSuccess,
|
||||||
.filter((r) => r.success > 0)
|
totalFailed,
|
||||||
.map((r) => r.profileName);
|
simpleProfiles
|
||||||
|
} = categorizeProfileResults(addResults);
|
||||||
|
|
||||||
if (successfulProfiles.length > 0) {
|
if (allSuccessfulProfiles.length > 0) {
|
||||||
console.log(
|
console.log(
|
||||||
chalk.green(
|
chalk.green(
|
||||||
`\nSuccessfully added rules for: ${successfulProfiles.join(', ')}`
|
`\nSuccessfully added rules for: ${allSuccessfulProfiles.join(', ')}`
|
||||||
)
|
|
||||||
);
|
|
||||||
console.log(
|
|
||||||
chalk.green(
|
|
||||||
`Total: ${totalSuccess} rules added, ${totalFailed} failed.`
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Create a more descriptive summary
|
||||||
|
if (totalSuccess > 0 && simpleProfiles.length > 0) {
|
||||||
|
console.log(
|
||||||
|
chalk.green(
|
||||||
|
`Total: ${totalSuccess} rules added, ${totalFailed} failed, ${simpleProfiles.length} integration guide(s) copied.`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else if (totalSuccess > 0) {
|
||||||
|
console.log(
|
||||||
|
chalk.green(
|
||||||
|
`Total: ${totalSuccess} rules added, ${totalFailed} failed.`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else if (simpleProfiles.length > 0) {
|
||||||
|
console.log(
|
||||||
|
chalk.green(
|
||||||
|
`Total: ${simpleProfiles.length} integration guide(s) copied.`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print summary for removals
|
// Print summary for removals
|
||||||
if (action === RULES_ACTIONS.REMOVE) {
|
if (action === RULES_ACTIONS.REMOVE && removalResults.length > 0) {
|
||||||
const successes = removalResults
|
const {
|
||||||
.filter((r) => r.success)
|
successfulRemovals,
|
||||||
.map((r) => r.profileName);
|
skippedRemovals,
|
||||||
const skipped = removalResults
|
failedRemovals,
|
||||||
.filter((r) => r.skipped)
|
removalsWithNotices
|
||||||
.map((r) => r.profileName);
|
} = categorizeRemovalResults(removalResults);
|
||||||
const errors = removalResults.filter(
|
|
||||||
(r) => r.error && !r.success && !r.skipped
|
|
||||||
);
|
|
||||||
const withNotices = removalResults.filter((r) => r.notice);
|
|
||||||
|
|
||||||
if (successes.length > 0) {
|
if (successfulRemovals.length > 0) {
|
||||||
console.log(
|
console.log(
|
||||||
chalk.green(
|
chalk.green(
|
||||||
`Successfully removed Task Master rules: ${successes.join(', ')}`
|
`\nSuccessfully removed rules for: ${successfulRemovals.join(', ')}`
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (skipped.length > 0) {
|
if (skippedRemovals.length > 0) {
|
||||||
console.log(
|
console.log(
|
||||||
chalk.yellow(
|
chalk.yellow(
|
||||||
`Skipped (default or protected): ${skipped.join(', ')}`
|
`Skipped (default or protected): ${skippedRemovals.join(', ')}`
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (errors.length > 0) {
|
if (failedRemovals.length > 0) {
|
||||||
errors.forEach((r) => {
|
console.log(chalk.red('\nErrors occurred:'));
|
||||||
console.log(
|
failedRemovals.forEach((r) => {
|
||||||
chalk.red(`Error removing ${r.profileName}: ${r.error}`)
|
console.log(chalk.red(` ${r.profileName}: ${r.error}`));
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// Display notices about preserved files/configurations
|
// Display notices about preserved files/configurations
|
||||||
if (withNotices.length > 0) {
|
if (removalsWithNotices.length > 0) {
|
||||||
console.log(chalk.cyan('\nNotices:'));
|
console.log(chalk.cyan('\nNotices:'));
|
||||||
withNotices.forEach((r) => {
|
removalsWithNotices.forEach((r) => {
|
||||||
console.log(chalk.cyan(` ${r.profileName}: ${r.notice}`));
|
console.log(chalk.cyan(` ${r.profileName}: ${r.notice}`));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Overall summary
|
||||||
|
const totalProcessed = removalResults.length;
|
||||||
|
const totalSuccessful = successfulRemovals.length;
|
||||||
|
const totalSkipped = skippedRemovals.length;
|
||||||
|
const totalFailed = failedRemovals.length;
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
chalk.blue(
|
||||||
|
`\nTotal: ${totalProcessed} profile(s) processed - ${totalSuccessful} removed, ${totalSkipped} skipped, ${totalFailed} failed.`
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
62
scripts/profiles/claude.js
Normal file
62
scripts/profiles/claude.js
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
// Claude Code profile for rule-transformer
|
||||||
|
import path from 'path';
|
||||||
|
import fs from 'fs';
|
||||||
|
import { isSilentMode, log } from '../modules/utils.js';
|
||||||
|
|
||||||
|
// Lifecycle functions for Claude Code profile
|
||||||
|
function onAddRulesProfile(targetDir) {
|
||||||
|
const sourceFile = path.join(process.cwd(), 'assets', 'AGENTS.md');
|
||||||
|
const destFile = path.join(targetDir, 'CLAUDE.md');
|
||||||
|
|
||||||
|
if (fs.existsSync(sourceFile)) {
|
||||||
|
try {
|
||||||
|
fs.copyFileSync(sourceFile, destFile);
|
||||||
|
log('debug', `[Claude] Copied AGENTS.md to ${destFile}`);
|
||||||
|
} catch (err) {
|
||||||
|
log('debug', `[Claude] Failed to copy AGENTS.md: ${err.message}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log('debug', `[Claude] AGENTS.md not found at ${sourceFile}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onRemoveRulesProfile(targetDir) {
|
||||||
|
log('debug', `[Claude] onRemoveRulesProfile called for ${targetDir}`);
|
||||||
|
const claudeFile = path.join(targetDir, 'CLAUDE.md');
|
||||||
|
if (fs.existsSync(claudeFile)) {
|
||||||
|
try {
|
||||||
|
fs.rmSync(claudeFile, { force: true });
|
||||||
|
log('debug', `[Claude] Removed CLAUDE.md from ${targetDir}`);
|
||||||
|
} catch (err) {
|
||||||
|
log('debug', `[Claude] Failed to remove CLAUDE.md: ${err.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log('debug', `[Claude] onRemoveRulesProfile completed for ${targetDir}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onPostConvertRulesProfile(targetDir) {
|
||||||
|
onAddRulesProfile(targetDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simple filename function
|
||||||
|
function getTargetRuleFilename(sourceFilename) {
|
||||||
|
return sourceFilename;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simple profile configuration - bypasses base-profile system
|
||||||
|
export const claudeProfile = {
|
||||||
|
profileName: 'claude',
|
||||||
|
displayName: 'Claude Code',
|
||||||
|
profileDir: '.', // Root directory
|
||||||
|
rulesDir: '.', // No rules directory needed
|
||||||
|
mcpConfig: false, // No MCP config needed
|
||||||
|
mcpConfigName: null,
|
||||||
|
mcpConfigPath: null,
|
||||||
|
conversionConfig: {},
|
||||||
|
fileMap: {},
|
||||||
|
globalReplacements: [],
|
||||||
|
getTargetRuleFilename,
|
||||||
|
onAddRulesProfile,
|
||||||
|
onRemoveRulesProfile,
|
||||||
|
onPostConvertRulesProfile
|
||||||
|
};
|
||||||
62
scripts/profiles/codex.js
Normal file
62
scripts/profiles/codex.js
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
// Codex profile for rule-transformer
|
||||||
|
import path from 'path';
|
||||||
|
import fs from 'fs';
|
||||||
|
import { isSilentMode, log } from '../modules/utils.js';
|
||||||
|
|
||||||
|
// Lifecycle functions for Codex profile
|
||||||
|
function onAddRulesProfile(targetDir) {
|
||||||
|
const sourceFile = path.join(process.cwd(), 'assets', 'AGENTS.md');
|
||||||
|
const destFile = path.join(targetDir, 'AGENTS.md');
|
||||||
|
|
||||||
|
if (fs.existsSync(sourceFile)) {
|
||||||
|
try {
|
||||||
|
fs.copyFileSync(sourceFile, destFile);
|
||||||
|
log('debug', `[Codex] Copied AGENTS.md to ${destFile}`);
|
||||||
|
} catch (err) {
|
||||||
|
log('debug', `[Codex] Failed to copy AGENTS.md: ${err.message}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log('debug', `[Codex] AGENTS.md not found at ${sourceFile}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onRemoveRulesProfile(targetDir) {
|
||||||
|
log('debug', `[Codex] onRemoveRulesProfile called for ${targetDir}`);
|
||||||
|
const agentsFile = path.join(targetDir, 'AGENTS.md');
|
||||||
|
if (fs.existsSync(agentsFile)) {
|
||||||
|
try {
|
||||||
|
fs.rmSync(agentsFile, { force: true });
|
||||||
|
log('debug', `[Codex] Removed AGENTS.md from ${targetDir}`);
|
||||||
|
} catch (err) {
|
||||||
|
log('debug', `[Codex] Failed to remove AGENTS.md: ${err.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log('debug', `[Codex] onRemoveRulesProfile completed for ${targetDir}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onPostConvertRulesProfile(targetDir) {
|
||||||
|
onAddRulesProfile(targetDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simple filename function
|
||||||
|
function getTargetRuleFilename(sourceFilename) {
|
||||||
|
return sourceFilename;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simple profile configuration - bypasses base-profile system
|
||||||
|
export const codexProfile = {
|
||||||
|
profileName: 'codex',
|
||||||
|
displayName: 'Codex',
|
||||||
|
profileDir: '.', // Root directory
|
||||||
|
rulesDir: '.', // No rules directory needed
|
||||||
|
mcpConfig: false, // No MCP config needed
|
||||||
|
mcpConfigName: null,
|
||||||
|
mcpConfigPath: null,
|
||||||
|
conversionConfig: {},
|
||||||
|
fileMap: {},
|
||||||
|
globalReplacements: [],
|
||||||
|
getTargetRuleFilename,
|
||||||
|
onAddRulesProfile,
|
||||||
|
onRemoveRulesProfile,
|
||||||
|
onPostConvertRulesProfile
|
||||||
|
};
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
// Profile exports for centralized importing
|
// Profile exports for centralized importing
|
||||||
export * as clineProfile from './cline.js';
|
export { claudeProfile } from './claude.js';
|
||||||
export * as cursorProfile from './cursor.js';
|
export { clineProfile } from './cline.js';
|
||||||
export * as rooProfile from './roo.js';
|
export { codexProfile } from './codex.js';
|
||||||
export * as traeProfile from './trae.js';
|
export { cursorProfile } from './cursor.js';
|
||||||
export * as windsurfProfile from './windsurf.js';
|
export { rooProfile } from './roo.js';
|
||||||
|
export { traeProfile } from './trae.js';
|
||||||
|
export { windsurfProfile } from './windsurf.js';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* @typedef {'cline' | 'cursor' | 'roo' | 'trae' | 'windsurf'} RulesProfile
|
* @typedef {'claude' | 'cline' | 'codex' | 'cursor' | 'roo' | 'trae' | 'windsurf'} RulesProfile
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -10,8 +10,10 @@
|
|||||||
*
|
*
|
||||||
* @type {RulesProfile[]}
|
* @type {RulesProfile[]}
|
||||||
* @description Defines possible rule profile sets:
|
* @description Defines possible rule profile sets:
|
||||||
|
* - claude: Claude Code integration
|
||||||
* - cline: Cline IDE rules
|
* - cline: Cline IDE rules
|
||||||
* - cursor: Cursor IDE rules (default)
|
* - codex: Codex integration
|
||||||
|
* - cursor: Cursor IDE rules
|
||||||
* - roo: Roo Code IDE rules
|
* - roo: Roo Code IDE rules
|
||||||
* - trae: Trae IDE rules
|
* - trae: Trae IDE rules
|
||||||
* - windsurf: Windsurf IDE rules
|
* - windsurf: Windsurf IDE rules
|
||||||
@@ -21,7 +23,15 @@
|
|||||||
* 2. Create a profile file in scripts/profiles/{profile}.js
|
* 2. Create a profile file in scripts/profiles/{profile}.js
|
||||||
* 3. Export it as {profile}Profile in scripts/profiles/index.js
|
* 3. Export it as {profile}Profile in scripts/profiles/index.js
|
||||||
*/
|
*/
|
||||||
export const RULE_PROFILES = ['cline', 'cursor', 'roo', 'trae', 'windsurf'];
|
export const RULE_PROFILES = [
|
||||||
|
'claude',
|
||||||
|
'cline',
|
||||||
|
'codex',
|
||||||
|
'cursor',
|
||||||
|
'roo',
|
||||||
|
'trae',
|
||||||
|
'windsurf'
|
||||||
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if a given rule profile is valid
|
* Check if a given rule profile is valid
|
||||||
|
|||||||
@@ -1,48 +0,0 @@
|
|||||||
/**
|
|
||||||
* Profile Detection Utility
|
|
||||||
* Helper functions to detect existing profiles in a project
|
|
||||||
*/
|
|
||||||
import fs from 'fs';
|
|
||||||
import path from 'path';
|
|
||||||
import { RULE_PROFILES } from '../constants/profiles.js';
|
|
||||||
import { getRulesProfile } from './rule-transformer.js';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Detect which profiles are currently installed in the project
|
|
||||||
* @param {string} projectRoot - Project root directory
|
|
||||||
* @returns {string[]} Array of installed profile names
|
|
||||||
*/
|
|
||||||
export function getInstalledProfiles(projectRoot) {
|
|
||||||
const installedProfiles = [];
|
|
||||||
|
|
||||||
for (const profileName of RULE_PROFILES) {
|
|
||||||
const profileConfig = getRulesProfile(profileName);
|
|
||||||
if (!profileConfig) continue;
|
|
||||||
|
|
||||||
// Check if the profile directory exists
|
|
||||||
const profileDir = path.join(projectRoot, profileConfig.profileDir);
|
|
||||||
const rulesDir = path.join(projectRoot, profileConfig.rulesDir);
|
|
||||||
|
|
||||||
// A profile is considered installed if either the profile dir or rules dir exists
|
|
||||||
if (fs.existsSync(profileDir) || fs.existsSync(rulesDir)) {
|
|
||||||
installedProfiles.push(profileName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return installedProfiles;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if removing the specified profiles would result in no profiles remaining
|
|
||||||
* @param {string} projectRoot - Project root directory
|
|
||||||
* @param {string[]} profilesToRemove - Array of profile names to remove
|
|
||||||
* @returns {boolean} True if removal would result in no profiles remaining
|
|
||||||
*/
|
|
||||||
export function wouldRemovalLeaveNoProfiles(projectRoot, profilesToRemove) {
|
|
||||||
const installedProfiles = getInstalledProfiles(projectRoot);
|
|
||||||
const remainingProfiles = installedProfiles.filter(
|
|
||||||
(profile) => !profilesToRemove.includes(profile)
|
|
||||||
);
|
|
||||||
|
|
||||||
return remainingProfiles.length === 0 && installedProfiles.length > 0;
|
|
||||||
}
|
|
||||||
228
src/utils/profiles.js
Normal file
228
src/utils/profiles.js
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
/**
|
||||||
|
* Profiles Utility
|
||||||
|
* Consolidated utilities for profile detection, setup, and summary generation
|
||||||
|
*/
|
||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import readline from 'readline';
|
||||||
|
import inquirer from 'inquirer';
|
||||||
|
import chalk from 'chalk';
|
||||||
|
import { log } from '../../scripts/modules/utils.js';
|
||||||
|
import { getRulesProfile } from './rule-transformer.js';
|
||||||
|
import { RULE_PROFILES } from '../constants/profiles.js';
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// PROFILE DETECTION
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect which profiles are currently installed in the project
|
||||||
|
* @param {string} projectRoot - Project root directory
|
||||||
|
* @returns {string[]} Array of installed profile names
|
||||||
|
*/
|
||||||
|
export function getInstalledProfiles(projectRoot) {
|
||||||
|
const installedProfiles = [];
|
||||||
|
|
||||||
|
for (const profileName of RULE_PROFILES) {
|
||||||
|
const profileConfig = getRulesProfile(profileName);
|
||||||
|
if (!profileConfig) continue;
|
||||||
|
|
||||||
|
// Check if the profile directory exists
|
||||||
|
const profileDir = path.join(projectRoot, profileConfig.profileDir);
|
||||||
|
const rulesDir = path.join(projectRoot, profileConfig.rulesDir);
|
||||||
|
|
||||||
|
// A profile is considered installed if either the profile dir or rules dir exists
|
||||||
|
if (fs.existsSync(profileDir) || fs.existsSync(rulesDir)) {
|
||||||
|
installedProfiles.push(profileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return installedProfiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if removing the specified profiles would result in no profiles remaining
|
||||||
|
* @param {string} projectRoot - Project root directory
|
||||||
|
* @param {string[]} profilesToRemove - Array of profile names to remove
|
||||||
|
* @returns {boolean} True if removal would result in no profiles remaining
|
||||||
|
*/
|
||||||
|
export function wouldRemovalLeaveNoProfiles(projectRoot, profilesToRemove) {
|
||||||
|
const installedProfiles = getInstalledProfiles(projectRoot);
|
||||||
|
const remainingProfiles = installedProfiles.filter(
|
||||||
|
(profile) => !profilesToRemove.includes(profile)
|
||||||
|
);
|
||||||
|
|
||||||
|
return remainingProfiles.length === 0 && installedProfiles.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// PROFILE SETUP
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the display name for a profile
|
||||||
|
*/
|
||||||
|
function getProfileDisplayName(name) {
|
||||||
|
const profile = getRulesProfile(name);
|
||||||
|
return profile?.displayName || name.charAt(0).toUpperCase() + name.slice(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dynamically generate availableRulesProfiles from RULE_PROFILES
|
||||||
|
const availableRulesProfiles = RULE_PROFILES.map((name) => {
|
||||||
|
const displayName = getProfileDisplayName(name);
|
||||||
|
return {
|
||||||
|
name: displayName,
|
||||||
|
value: name
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Launches an interactive prompt for selecting which rule profiles to include in your project.
|
||||||
|
*
|
||||||
|
* This function dynamically lists all available profiles (from RULE_PROFILES) and presents them as checkboxes.
|
||||||
|
* The user must select at least one profile (no defaults are pre-selected). The result is an array of selected profile names.
|
||||||
|
*
|
||||||
|
* Used by both project initialization (init) and the CLI 'task-master rules setup' command.
|
||||||
|
*
|
||||||
|
* @returns {Promise<string[]>} Array of selected profile names (e.g., ['cursor', 'windsurf'])
|
||||||
|
*/
|
||||||
|
export async function runInteractiveRulesSetup() {
|
||||||
|
console.log(
|
||||||
|
chalk.cyan(
|
||||||
|
'\nRule profiles help enforce best practices and conventions for Task Master.'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const rulesProfilesQuestion = {
|
||||||
|
type: 'checkbox',
|
||||||
|
name: 'rulesProfiles',
|
||||||
|
message: 'Which tools would you like rule profiles included for?',
|
||||||
|
choices: availableRulesProfiles,
|
||||||
|
validate: (input) => input.length > 0 || 'You must select at least one.'
|
||||||
|
};
|
||||||
|
const { rulesProfiles } = await inquirer.prompt([rulesProfilesQuestion]);
|
||||||
|
return rulesProfiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// PROFILE SUMMARY
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate appropriate summary message for a profile based on its type
|
||||||
|
* @param {string} profileName - Name of the profile
|
||||||
|
* @param {Object} addResult - Result object with success/failed counts
|
||||||
|
* @returns {string} Formatted summary message
|
||||||
|
*/
|
||||||
|
export function generateProfileSummary(profileName, addResult) {
|
||||||
|
const profileConfig = getRulesProfile(profileName);
|
||||||
|
const isSimpleProfile = Object.keys(profileConfig.fileMap).length === 0;
|
||||||
|
|
||||||
|
if (isSimpleProfile) {
|
||||||
|
// Simple profiles like Claude and Codex only copy AGENTS.md
|
||||||
|
const targetFileName = profileName === 'claude' ? 'CLAUDE.md' : 'AGENTS.md';
|
||||||
|
return `Summary for ${profileName}: Integration guide copied to ${targetFileName}`;
|
||||||
|
} else {
|
||||||
|
return `Summary for ${profileName}: ${addResult.success} rules added, ${addResult.failed} failed.`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate appropriate summary message for profile removal
|
||||||
|
* @param {string} profileName - Name of the profile
|
||||||
|
* @param {Object} removeResult - Result object from removal operation
|
||||||
|
* @returns {string} Formatted summary message
|
||||||
|
*/
|
||||||
|
export function generateProfileRemovalSummary(profileName, removeResult) {
|
||||||
|
const profileConfig = getRulesProfile(profileName);
|
||||||
|
const isSimpleProfile = Object.keys(profileConfig.fileMap).length === 0;
|
||||||
|
|
||||||
|
if (removeResult.skipped) {
|
||||||
|
return `Summary for ${profileName}: Skipped (default or protected files)`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (removeResult.error && !removeResult.success) {
|
||||||
|
return `Summary for ${profileName}: Failed to remove - ${removeResult.error}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSimpleProfile) {
|
||||||
|
// Simple profiles like Claude and Codex only have an integration guide
|
||||||
|
const targetFileName = profileName === 'claude' ? 'CLAUDE.md' : 'AGENTS.md';
|
||||||
|
return `Summary for ${profileName}: Integration guide (${targetFileName}) removed`;
|
||||||
|
} else {
|
||||||
|
// Full profiles have rules directories and potentially MCP configs
|
||||||
|
let summary = `Summary for ${profileName}: Rules directory removed`;
|
||||||
|
if (removeResult.notice) {
|
||||||
|
summary += ` (${removeResult.notice})`;
|
||||||
|
}
|
||||||
|
return summary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Categorize profiles and generate final summary statistics
|
||||||
|
* @param {Array} addResults - Array of add result objects
|
||||||
|
* @returns {Object} Object with categorized profiles and totals
|
||||||
|
*/
|
||||||
|
export function categorizeProfileResults(addResults) {
|
||||||
|
const successfulProfiles = [];
|
||||||
|
const simpleProfiles = [];
|
||||||
|
let totalSuccess = 0;
|
||||||
|
let totalFailed = 0;
|
||||||
|
|
||||||
|
addResults.forEach((r) => {
|
||||||
|
totalSuccess += r.success;
|
||||||
|
totalFailed += r.failed;
|
||||||
|
|
||||||
|
const profileConfig = getRulesProfile(r.profileName);
|
||||||
|
const isSimpleProfile = Object.keys(profileConfig.fileMap).length === 0;
|
||||||
|
|
||||||
|
if (isSimpleProfile) {
|
||||||
|
// Simple profiles are successful if they completed without error
|
||||||
|
simpleProfiles.push(r.profileName);
|
||||||
|
} else if (r.success > 0) {
|
||||||
|
// Full profiles are successful if they added rules
|
||||||
|
successfulProfiles.push(r.profileName);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
successfulProfiles,
|
||||||
|
simpleProfiles,
|
||||||
|
allSuccessfulProfiles: [...successfulProfiles, ...simpleProfiles],
|
||||||
|
totalSuccess,
|
||||||
|
totalFailed
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Categorize removal results and generate final summary statistics
|
||||||
|
* @param {Array} removalResults - Array of removal result objects
|
||||||
|
* @returns {Object} Object with categorized removal results
|
||||||
|
*/
|
||||||
|
export function categorizeRemovalResults(removalResults) {
|
||||||
|
const successfulRemovals = [];
|
||||||
|
const skippedRemovals = [];
|
||||||
|
const failedRemovals = [];
|
||||||
|
const removalsWithNotices = [];
|
||||||
|
|
||||||
|
removalResults.forEach((result) => {
|
||||||
|
if (result.success) {
|
||||||
|
successfulRemovals.push(result.profileName);
|
||||||
|
} else if (result.skipped) {
|
||||||
|
skippedRemovals.push(result.profileName);
|
||||||
|
} else if (result.error) {
|
||||||
|
failedRemovals.push(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.notice) {
|
||||||
|
removalsWithNotices.push(result);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
successfulRemovals,
|
||||||
|
skippedRemovals,
|
||||||
|
failedRemovals,
|
||||||
|
removalsWithNotices
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -299,7 +299,23 @@ export function removeProfileRules(projectDir, profile) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Check if profile directory exists at all
|
// Handle simple profiles (Claude, Codex) that just copy files to root
|
||||||
|
const isSimpleProfile = Object.keys(profile.fileMap).length === 0;
|
||||||
|
|
||||||
|
if (isSimpleProfile) {
|
||||||
|
// For simple profiles, just call their removal hook and return
|
||||||
|
if (typeof profile.onRemoveRulesProfile === 'function') {
|
||||||
|
profile.onRemoveRulesProfile(projectDir);
|
||||||
|
}
|
||||||
|
result.success = true;
|
||||||
|
log(
|
||||||
|
'debug',
|
||||||
|
`[Rule Transformer] Successfully removed ${profile.profileName} files from ${projectDir}`
|
||||||
|
);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if profile directory exists at all (for full profiles)
|
||||||
if (!fs.existsSync(profileDir)) {
|
if (!fs.existsSync(profileDir)) {
|
||||||
result.success = true;
|
result.success = true;
|
||||||
result.skipped = true;
|
result.skipped = true;
|
||||||
|
|||||||
@@ -1,54 +0,0 @@
|
|||||||
import readline from 'readline';
|
|
||||||
import inquirer from 'inquirer';
|
|
||||||
import chalk from 'chalk';
|
|
||||||
import { log } from '../../scripts/modules/utils.js';
|
|
||||||
import { getRulesProfile } from './rule-transformer.js';
|
|
||||||
import { RULE_PROFILES } from '../constants/profiles.js';
|
|
||||||
|
|
||||||
// Dynamically generate availableRulesProfiles from RULE_PROFILES
|
|
||||||
const availableRulesProfiles = RULE_PROFILES.map((name) => {
|
|
||||||
const displayName = getProfileDisplayName(name);
|
|
||||||
return {
|
|
||||||
name: displayName,
|
|
||||||
value: name
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the display name for a profile
|
|
||||||
*/
|
|
||||||
function getProfileDisplayName(name) {
|
|
||||||
const profile = getRulesProfile(name);
|
|
||||||
return profile?.displayName || 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 profile rules to include in your project.
|
|
||||||
*
|
|
||||||
* This function dynamically lists all available profiles (from RULE_PROFILES) and presents them as checkboxes.
|
|
||||||
* The user must select at least one profile (no defaults are pre-selected). 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 profile rule names (e.g., ['cursor', 'windsurf'])
|
|
||||||
*/
|
|
||||||
export async function runInteractiveRulesSetup() {
|
|
||||||
console.log(
|
|
||||||
chalk.cyan(
|
|
||||||
'\nRules help enforce best practices and conventions for Task Master.'
|
|
||||||
)
|
|
||||||
);
|
|
||||||
const rulesProfilesQuestion = {
|
|
||||||
type: 'checkbox',
|
|
||||||
name: 'rulesProfiles',
|
|
||||||
message: 'Which IDEs would you like rule profiles included for?',
|
|
||||||
choices: availableRulesProfiles,
|
|
||||||
validate: (input) => input.length > 0 || 'You must select at least one.'
|
|
||||||
};
|
|
||||||
const { rulesProfiles } = await inquirer.prompt([rulesProfilesQuestion]);
|
|
||||||
return rulesProfiles;
|
|
||||||
}
|
|
||||||
@@ -1126,7 +1126,9 @@ describe('rules command', () => {
|
|||||||
expect.stringMatching(/removing rules for profile: roo/i)
|
expect.stringMatching(/removing rules for profile: roo/i)
|
||||||
);
|
);
|
||||||
expect(mockConsoleLog).toHaveBeenCalledWith(
|
expect(mockConsoleLog).toHaveBeenCalledWith(
|
||||||
expect.stringMatching(/completed removal for profile: roo/i)
|
expect.stringMatching(
|
||||||
|
/Summary for roo: (Rules directory removed|Skipped \(default or protected files\))/i
|
||||||
|
)
|
||||||
);
|
);
|
||||||
// Should not exit with error
|
// Should not exit with error
|
||||||
expect(mockExit).not.toHaveBeenCalledWith(1);
|
expect(mockExit).not.toHaveBeenCalledWith(1);
|
||||||
|
|||||||
@@ -117,16 +117,38 @@ describe('MCP Configuration Validation', () => {
|
|||||||
describe('Profile Directory Structure', () => {
|
describe('Profile Directory Structure', () => {
|
||||||
test('should ensure each profile has a unique directory', () => {
|
test('should ensure each profile has a unique directory', () => {
|
||||||
const profileDirs = new Set();
|
const profileDirs = new Set();
|
||||||
|
// Simple profiles that use root directory (can share the same directory)
|
||||||
|
const simpleProfiles = ['claude', 'codex'];
|
||||||
|
|
||||||
RULE_PROFILES.forEach((profileName) => {
|
RULE_PROFILES.forEach((profileName) => {
|
||||||
const profile = getRulesProfile(profileName);
|
const profile = getRulesProfile(profileName);
|
||||||
|
|
||||||
|
// Simple profiles can share the root directory
|
||||||
|
if (simpleProfiles.includes(profileName)) {
|
||||||
|
expect(profile.profileDir).toBe('.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Full profiles should have unique directories
|
||||||
expect(profileDirs.has(profile.profileDir)).toBe(false);
|
expect(profileDirs.has(profile.profileDir)).toBe(false);
|
||||||
profileDirs.add(profile.profileDir);
|
profileDirs.add(profile.profileDir);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should ensure profile directories follow expected naming convention', () => {
|
test('should ensure profile directories follow expected naming convention', () => {
|
||||||
|
// Simple profiles that use root directory
|
||||||
|
const simpleProfiles = ['claude', 'codex'];
|
||||||
|
|
||||||
RULE_PROFILES.forEach((profileName) => {
|
RULE_PROFILES.forEach((profileName) => {
|
||||||
const profile = getRulesProfile(profileName);
|
const profile = getRulesProfile(profileName);
|
||||||
|
|
||||||
|
// Simple profiles use root directory
|
||||||
|
if (simpleProfiles.includes(profileName)) {
|
||||||
|
expect(profile.profileDir).toBe('.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Full profiles should follow the .name pattern
|
||||||
expect(profile.profileDir).toMatch(/^\.[\w-]+$/);
|
expect(profile.profileDir).toMatch(/^\.[\w-]+$/);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -144,6 +166,8 @@ describe('MCP Configuration Validation', () => {
|
|||||||
expect(mcpEnabledProfiles).toContain('roo');
|
expect(mcpEnabledProfiles).toContain('roo');
|
||||||
expect(mcpEnabledProfiles).not.toContain('cline');
|
expect(mcpEnabledProfiles).not.toContain('cline');
|
||||||
expect(mcpEnabledProfiles).not.toContain('trae');
|
expect(mcpEnabledProfiles).not.toContain('trae');
|
||||||
|
expect(mcpEnabledProfiles).not.toContain('claude');
|
||||||
|
expect(mcpEnabledProfiles).not.toContain('codex');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should provide all necessary information for MCP config creation', () => {
|
test('should provide all necessary information for MCP config creation', () => {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import path from 'path';
|
|||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
import { dirname } from 'path';
|
import { dirname } from 'path';
|
||||||
import { convertRuleToProfileRule } from '../../src/utils/rule-transformer.js';
|
import { convertRuleToProfileRule } from '../../src/utils/rule-transformer.js';
|
||||||
import * as clineProfile from '../../scripts/profiles/cline.js';
|
import { clineProfile } from '../../scripts/profiles/cline.js';
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = dirname(__filename);
|
const __dirname = dirname(__filename);
|
||||||
@@ -41,11 +41,7 @@ Also has references to .mdc files.`;
|
|||||||
|
|
||||||
// Convert it
|
// Convert it
|
||||||
const testClineRule = path.join(testDir, 'basic-terms.md');
|
const testClineRule = path.join(testDir, 'basic-terms.md');
|
||||||
convertRuleToProfileRule(
|
convertRuleToProfileRule(testCursorRule, testClineRule, clineProfile);
|
||||||
testCursorRule,
|
|
||||||
testClineRule,
|
|
||||||
clineProfile.clineProfile
|
|
||||||
);
|
|
||||||
|
|
||||||
// Read the converted file
|
// Read the converted file
|
||||||
const convertedContent = fs.readFileSync(testClineRule, 'utf8');
|
const convertedContent = fs.readFileSync(testClineRule, 'utf8');
|
||||||
@@ -76,11 +72,7 @@ alwaysApply: true
|
|||||||
|
|
||||||
// Convert it
|
// Convert it
|
||||||
const testClineRule = path.join(testDir, 'tool-refs.md');
|
const testClineRule = path.join(testDir, 'tool-refs.md');
|
||||||
convertRuleToProfileRule(
|
convertRuleToProfileRule(testCursorRule, testClineRule, clineProfile);
|
||||||
testCursorRule,
|
|
||||||
testClineRule,
|
|
||||||
clineProfile.clineProfile
|
|
||||||
);
|
|
||||||
|
|
||||||
// Read the converted file
|
// Read the converted file
|
||||||
const convertedContent = fs.readFileSync(testClineRule, 'utf8');
|
const convertedContent = fs.readFileSync(testClineRule, 'utf8');
|
||||||
@@ -108,11 +100,7 @@ This references [dev_workflow.mdc](mdc:.cursor/rules/dev_workflow.mdc) and
|
|||||||
|
|
||||||
// Convert it
|
// Convert it
|
||||||
const testClineRule = path.join(testDir, 'file-refs.md');
|
const testClineRule = path.join(testDir, 'file-refs.md');
|
||||||
convertRuleToProfileRule(
|
convertRuleToProfileRule(testCursorRule, testClineRule, clineProfile);
|
||||||
testCursorRule,
|
|
||||||
testClineRule,
|
|
||||||
clineProfile.clineProfile
|
|
||||||
);
|
|
||||||
|
|
||||||
// Read the converted file
|
// Read the converted file
|
||||||
const convertedContent = fs.readFileSync(testClineRule, 'utf8');
|
const convertedContent = fs.readFileSync(testClineRule, 'utf8');
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
convertRuleToProfileRule,
|
convertRuleToProfileRule,
|
||||||
getRulesProfile
|
getRulesProfile
|
||||||
} from '../../src/utils/rule-transformer.js';
|
} from '../../src/utils/rule-transformer.js';
|
||||||
import * as cursorProfile from '../../scripts/profiles/cursor.js';
|
import { cursorProfile } from '../../scripts/profiles/cursor.js';
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = dirname(__filename);
|
const __dirname = dirname(__filename);
|
||||||
@@ -45,11 +45,7 @@ Also has references to .mdc files.`;
|
|||||||
|
|
||||||
// Convert it
|
// Convert it
|
||||||
const testCursorOut = path.join(testDir, 'basic-terms.mdc');
|
const testCursorOut = path.join(testDir, 'basic-terms.mdc');
|
||||||
convertRuleToProfileRule(
|
convertRuleToProfileRule(testCursorRule, testCursorOut, cursorProfile);
|
||||||
testCursorRule,
|
|
||||||
testCursorOut,
|
|
||||||
cursorProfile.cursorProfile
|
|
||||||
);
|
|
||||||
|
|
||||||
// Read the converted file
|
// Read the converted file
|
||||||
const convertedContent = fs.readFileSync(testCursorOut, 'utf8');
|
const convertedContent = fs.readFileSync(testCursorOut, 'utf8');
|
||||||
@@ -80,11 +76,7 @@ alwaysApply: true
|
|||||||
|
|
||||||
// Convert it
|
// Convert it
|
||||||
const testCursorOut = path.join(testDir, 'tool-refs.mdc');
|
const testCursorOut = path.join(testDir, 'tool-refs.mdc');
|
||||||
convertRuleToProfileRule(
|
convertRuleToProfileRule(testCursorRule, testCursorOut, cursorProfile);
|
||||||
testCursorRule,
|
|
||||||
testCursorOut,
|
|
||||||
cursorProfile.cursorProfile
|
|
||||||
);
|
|
||||||
|
|
||||||
// Read the converted file
|
// Read the converted file
|
||||||
const convertedContent = fs.readFileSync(testCursorOut, 'utf8');
|
const convertedContent = fs.readFileSync(testCursorOut, 'utf8');
|
||||||
@@ -114,11 +106,7 @@ This references [dev_workflow.mdc](mdc:.cursor/rules/dev_workflow.mdc) and
|
|||||||
|
|
||||||
// Convert it
|
// Convert it
|
||||||
const testCursorOut = path.join(testDir, 'file-refs.mdc');
|
const testCursorOut = path.join(testDir, 'file-refs.mdc');
|
||||||
convertRuleToProfileRule(
|
convertRuleToProfileRule(testCursorRule, testCursorOut, cursorProfile);
|
||||||
testCursorRule,
|
|
||||||
testCursorOut,
|
|
||||||
cursorProfile.cursorProfile
|
|
||||||
);
|
|
||||||
|
|
||||||
// Read the converted file
|
// Read the converted file
|
||||||
const convertedContent = fs.readFileSync(testCursorOut, 'utf8');
|
const convertedContent = fs.readFileSync(testCursorOut, 'utf8');
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
convertRuleToProfileRule,
|
convertRuleToProfileRule,
|
||||||
getRulesProfile
|
getRulesProfile
|
||||||
} from '../../src/utils/rule-transformer.js';
|
} from '../../src/utils/rule-transformer.js';
|
||||||
import * as rooProfile from '../../scripts/profiles/roo.js';
|
import { rooProfile } from '../../scripts/profiles/roo.js';
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = dirname(__filename);
|
const __dirname = dirname(__filename);
|
||||||
@@ -45,11 +45,7 @@ Also has references to .mdc files.`;
|
|||||||
|
|
||||||
// Convert it
|
// Convert it
|
||||||
const testRooRule = path.join(testDir, 'basic-terms.md');
|
const testRooRule = path.join(testDir, 'basic-terms.md');
|
||||||
convertRuleToProfileRule(
|
convertRuleToProfileRule(testCursorRule, testRooRule, rooProfile);
|
||||||
testCursorRule,
|
|
||||||
testRooRule,
|
|
||||||
rooProfile.rooProfile
|
|
||||||
);
|
|
||||||
|
|
||||||
// Read the converted file
|
// Read the converted file
|
||||||
const convertedContent = fs.readFileSync(testRooRule, 'utf8');
|
const convertedContent = fs.readFileSync(testRooRule, 'utf8');
|
||||||
@@ -80,11 +76,7 @@ alwaysApply: true
|
|||||||
|
|
||||||
// Convert it
|
// Convert it
|
||||||
const testRooRule = path.join(testDir, 'tool-refs.md');
|
const testRooRule = path.join(testDir, 'tool-refs.md');
|
||||||
convertRuleToProfileRule(
|
convertRuleToProfileRule(testCursorRule, testRooRule, rooProfile);
|
||||||
testCursorRule,
|
|
||||||
testRooRule,
|
|
||||||
rooProfile.rooProfile
|
|
||||||
);
|
|
||||||
|
|
||||||
// Read the converted file
|
// Read the converted file
|
||||||
const convertedContent = fs.readFileSync(testRooRule, 'utf8');
|
const convertedContent = fs.readFileSync(testRooRule, 'utf8');
|
||||||
@@ -112,11 +104,7 @@ This references [dev_workflow.mdc](mdc:.cursor/rules/dev_workflow.mdc) and
|
|||||||
|
|
||||||
// Convert it
|
// Convert it
|
||||||
const testRooRule = path.join(testDir, 'file-refs.md');
|
const testRooRule = path.join(testDir, 'file-refs.md');
|
||||||
convertRuleToProfileRule(
|
convertRuleToProfileRule(testCursorRule, testRooRule, rooProfile);
|
||||||
testCursorRule,
|
|
||||||
testRooRule,
|
|
||||||
rooProfile.rooProfile
|
|
||||||
);
|
|
||||||
|
|
||||||
// Read the converted file
|
// Read the converted file
|
||||||
const convertedContent = fs.readFileSync(testRooRule, 'utf8');
|
const convertedContent = fs.readFileSync(testRooRule, 'utf8');
|
||||||
@@ -134,7 +122,7 @@ This references [dev_workflow.mdc](mdc:.cursor/rules/dev_workflow.mdc) and
|
|||||||
const assetRule = path.join(assetsRulesDir, 'dev_workflow.mdc');
|
const assetRule = path.join(assetsRulesDir, 'dev_workflow.mdc');
|
||||||
fs.writeFileSync(assetRule, 'dummy');
|
fs.writeFileSync(assetRule, 'dummy');
|
||||||
// Should create .roo/rules and call post-processing
|
// Should create .roo/rules and call post-processing
|
||||||
convertAllRulesToProfileRules(testDir, rooProfile.rooProfile);
|
convertAllRulesToProfileRules(testDir, rooProfile);
|
||||||
// Check for post-processing artifacts, e.g., rules-* folders or extra files
|
// Check for post-processing artifacts, e.g., rules-* folders or extra files
|
||||||
const rooDir = path.join(testDir, '.roo');
|
const rooDir = path.join(testDir, '.roo');
|
||||||
const found = fs.readdirSync(rooDir).some((f) => f.startsWith('rules-'));
|
const found = fs.readdirSync(rooDir).some((f) => f.startsWith('rules-'));
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import path from 'path';
|
|||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
import { dirname } from 'path';
|
import { dirname } from 'path';
|
||||||
import { convertRuleToProfileRule } from '../../src/utils/rule-transformer.js';
|
import { convertRuleToProfileRule } from '../../src/utils/rule-transformer.js';
|
||||||
import * as traeProfile from '../../scripts/profiles/trae.js';
|
import { traeProfile } from '../../scripts/profiles/trae.js';
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = dirname(__filename);
|
const __dirname = dirname(__filename);
|
||||||
@@ -41,11 +41,7 @@ Also has references to .mdc files.`;
|
|||||||
|
|
||||||
// Convert it
|
// Convert it
|
||||||
const testTraeRule = path.join(testDir, 'basic-terms.md');
|
const testTraeRule = path.join(testDir, 'basic-terms.md');
|
||||||
convertRuleToProfileRule(
|
convertRuleToProfileRule(testCursorRule, testTraeRule, traeProfile);
|
||||||
testCursorRule,
|
|
||||||
testTraeRule,
|
|
||||||
traeProfile.traeProfile
|
|
||||||
);
|
|
||||||
|
|
||||||
// Read the converted file
|
// Read the converted file
|
||||||
const convertedContent = fs.readFileSync(testTraeRule, 'utf8');
|
const convertedContent = fs.readFileSync(testTraeRule, 'utf8');
|
||||||
@@ -76,11 +72,7 @@ alwaysApply: true
|
|||||||
|
|
||||||
// Convert it
|
// Convert it
|
||||||
const testTraeRule = path.join(testDir, 'tool-refs.md');
|
const testTraeRule = path.join(testDir, 'tool-refs.md');
|
||||||
convertRuleToProfileRule(
|
convertRuleToProfileRule(testCursorRule, testTraeRule, traeProfile);
|
||||||
testCursorRule,
|
|
||||||
testTraeRule,
|
|
||||||
traeProfile.traeProfile
|
|
||||||
);
|
|
||||||
|
|
||||||
// Read the converted file
|
// Read the converted file
|
||||||
const convertedContent = fs.readFileSync(testTraeRule, 'utf8');
|
const convertedContent = fs.readFileSync(testTraeRule, 'utf8');
|
||||||
@@ -108,11 +100,7 @@ This references [dev_workflow.mdc](mdc:.cursor/rules/dev_workflow.mdc) and
|
|||||||
|
|
||||||
// Convert it
|
// Convert it
|
||||||
const testTraeRule = path.join(testDir, 'file-refs.md');
|
const testTraeRule = path.join(testDir, 'file-refs.md');
|
||||||
convertRuleToProfileRule(
|
convertRuleToProfileRule(testCursorRule, testTraeRule, traeProfile);
|
||||||
testCursorRule,
|
|
||||||
testTraeRule,
|
|
||||||
traeProfile.traeProfile
|
|
||||||
);
|
|
||||||
|
|
||||||
// Read the converted file
|
// Read the converted file
|
||||||
const convertedContent = fs.readFileSync(testTraeRule, 'utf8');
|
const convertedContent = fs.readFileSync(testTraeRule, 'utf8');
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import path from 'path';
|
|||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
import { dirname } from 'path';
|
import { dirname } from 'path';
|
||||||
import { convertRuleToProfileRule } from '../../src/utils/rule-transformer.js';
|
import { convertRuleToProfileRule } from '../../src/utils/rule-transformer.js';
|
||||||
import * as windsurfProfile from '../../scripts/profiles/windsurf.js';
|
import { windsurfProfile } from '../../scripts/profiles/windsurf.js';
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = dirname(__filename);
|
const __dirname = dirname(__filename);
|
||||||
@@ -41,11 +41,7 @@ Also has references to .mdc files.`;
|
|||||||
|
|
||||||
// Convert it
|
// Convert it
|
||||||
const testWindsurfRule = path.join(testDir, 'basic-terms.md');
|
const testWindsurfRule = path.join(testDir, 'basic-terms.md');
|
||||||
convertRuleToProfileRule(
|
convertRuleToProfileRule(testCursorRule, testWindsurfRule, windsurfProfile);
|
||||||
testCursorRule,
|
|
||||||
testWindsurfRule,
|
|
||||||
windsurfProfile.windsurfProfile
|
|
||||||
);
|
|
||||||
|
|
||||||
// Read the converted file
|
// Read the converted file
|
||||||
const convertedContent = fs.readFileSync(testWindsurfRule, 'utf8');
|
const convertedContent = fs.readFileSync(testWindsurfRule, 'utf8');
|
||||||
@@ -76,11 +72,7 @@ alwaysApply: true
|
|||||||
|
|
||||||
// Convert it
|
// Convert it
|
||||||
const testWindsurfRule = path.join(testDir, 'tool-refs.md');
|
const testWindsurfRule = path.join(testDir, 'tool-refs.md');
|
||||||
convertRuleToProfileRule(
|
convertRuleToProfileRule(testCursorRule, testWindsurfRule, windsurfProfile);
|
||||||
testCursorRule,
|
|
||||||
testWindsurfRule,
|
|
||||||
windsurfProfile.windsurfProfile
|
|
||||||
);
|
|
||||||
|
|
||||||
// Read the converted file
|
// Read the converted file
|
||||||
const convertedContent = fs.readFileSync(testWindsurfRule, 'utf8');
|
const convertedContent = fs.readFileSync(testWindsurfRule, 'utf8');
|
||||||
@@ -108,11 +100,7 @@ This references [dev_workflow.mdc](mdc:.cursor/rules/dev_workflow.mdc) and
|
|||||||
|
|
||||||
// Convert it
|
// Convert it
|
||||||
const testWindsurfRule = path.join(testDir, 'file-refs.md');
|
const testWindsurfRule = path.join(testDir, 'file-refs.md');
|
||||||
convertRuleToProfileRule(
|
convertRuleToProfileRule(testCursorRule, testWindsurfRule, windsurfProfile);
|
||||||
testCursorRule,
|
|
||||||
testWindsurfRule,
|
|
||||||
windsurfProfile.windsurfProfile
|
|
||||||
);
|
|
||||||
|
|
||||||
// Read the converted file
|
// Read the converted file
|
||||||
const convertedContent = fs.readFileSync(testWindsurfRule, 'utf8');
|
const convertedContent = fs.readFileSync(testWindsurfRule, 'utf8');
|
||||||
|
|||||||
@@ -12,7 +12,15 @@ describe('Rule Transformer - General', () => {
|
|||||||
expect(RULE_PROFILES.length).toBeGreaterThan(0);
|
expect(RULE_PROFILES.length).toBeGreaterThan(0);
|
||||||
|
|
||||||
// Verify expected profiles are present
|
// Verify expected profiles are present
|
||||||
const expectedProfiles = ['cline', 'cursor', 'roo', 'trae', 'windsurf'];
|
const expectedProfiles = [
|
||||||
|
'claude',
|
||||||
|
'cline',
|
||||||
|
'codex',
|
||||||
|
'cursor',
|
||||||
|
'roo',
|
||||||
|
'trae',
|
||||||
|
'windsurf'
|
||||||
|
];
|
||||||
expectedProfiles.forEach((profile) => {
|
expectedProfiles.forEach((profile) => {
|
||||||
expect(RULE_PROFILES).toContain(profile);
|
expect(RULE_PROFILES).toContain(profile);
|
||||||
});
|
});
|
||||||
@@ -31,7 +39,7 @@ describe('Rule Transformer - General', () => {
|
|||||||
expect(isValidProfile(undefined)).toBe(false);
|
expect(isValidProfile(undefined)).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return correct rules profile with getRulesProfile', () => {
|
it('should return correct rule profile with getRulesProfile', () => {
|
||||||
// Test valid profiles
|
// Test valid profiles
|
||||||
RULE_PROFILES.forEach((profile) => {
|
RULE_PROFILES.forEach((profile) => {
|
||||||
const profileConfig = getRulesProfile(profile);
|
const profileConfig = getRulesProfile(profile);
|
||||||
@@ -46,6 +54,9 @@ describe('Rule Transformer - General', () => {
|
|||||||
|
|
||||||
describe('Profile Structure', () => {
|
describe('Profile Structure', () => {
|
||||||
it('should have all required properties for each profile', () => {
|
it('should have all required properties for each profile', () => {
|
||||||
|
// Simple profiles that only copy files (no rule transformation)
|
||||||
|
const simpleProfiles = ['claude', 'codex'];
|
||||||
|
|
||||||
RULE_PROFILES.forEach((profile) => {
|
RULE_PROFILES.forEach((profile) => {
|
||||||
const profileConfig = getRulesProfile(profile);
|
const profileConfig = getRulesProfile(profile);
|
||||||
|
|
||||||
@@ -56,7 +67,15 @@ describe('Rule Transformer - General', () => {
|
|||||||
expect(profileConfig).toHaveProperty('rulesDir');
|
expect(profileConfig).toHaveProperty('rulesDir');
|
||||||
expect(profileConfig).toHaveProperty('profileDir');
|
expect(profileConfig).toHaveProperty('profileDir');
|
||||||
|
|
||||||
// Check that conversionConfig has required structure
|
// Simple profiles have minimal structure
|
||||||
|
if (simpleProfiles.includes(profile)) {
|
||||||
|
// For simple profiles, conversionConfig and fileMap can be empty
|
||||||
|
expect(typeof profileConfig.conversionConfig).toBe('object');
|
||||||
|
expect(typeof profileConfig.fileMap).toBe('object');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that conversionConfig has required structure for full profiles
|
||||||
expect(profileConfig.conversionConfig).toHaveProperty('profileTerms');
|
expect(profileConfig.conversionConfig).toHaveProperty('profileTerms');
|
||||||
expect(profileConfig.conversionConfig).toHaveProperty('toolNames');
|
expect(profileConfig.conversionConfig).toHaveProperty('toolNames');
|
||||||
expect(profileConfig.conversionConfig).toHaveProperty('toolContexts');
|
expect(profileConfig.conversionConfig).toHaveProperty('toolContexts');
|
||||||
@@ -89,6 +108,9 @@ describe('Rule Transformer - General', () => {
|
|||||||
'taskmaster.mdc'
|
'taskmaster.mdc'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Simple profiles that only copy files (no rule transformation)
|
||||||
|
const simpleProfiles = ['claude', 'codex'];
|
||||||
|
|
||||||
RULE_PROFILES.forEach((profile) => {
|
RULE_PROFILES.forEach((profile) => {
|
||||||
const profileConfig = getRulesProfile(profile);
|
const profileConfig = getRulesProfile(profile);
|
||||||
|
|
||||||
@@ -97,7 +119,12 @@ describe('Rule Transformer - General', () => {
|
|||||||
expect(typeof profileConfig.fileMap).toBe('object');
|
expect(typeof profileConfig.fileMap).toBe('object');
|
||||||
expect(profileConfig.fileMap).not.toBeNull();
|
expect(profileConfig.fileMap).not.toBeNull();
|
||||||
|
|
||||||
// Check that fileMap is not empty
|
// Simple profiles can have empty fileMap since they don't transform rules
|
||||||
|
if (simpleProfiles.includes(profile)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that fileMap is not empty for full profiles
|
||||||
const fileMapKeys = Object.keys(profileConfig.fileMap);
|
const fileMapKeys = Object.keys(profileConfig.fileMap);
|
||||||
expect(fileMapKeys.length).toBeGreaterThan(0);
|
expect(fileMapKeys.length).toBeGreaterThan(0);
|
||||||
|
|
||||||
@@ -116,6 +143,9 @@ describe('Rule Transformer - General', () => {
|
|||||||
|
|
||||||
describe('MCP Configuration Properties', () => {
|
describe('MCP Configuration Properties', () => {
|
||||||
it('should have all required MCP properties for each profile', () => {
|
it('should have all required MCP properties for each profile', () => {
|
||||||
|
// Simple profiles that only copy files (no MCP configuration)
|
||||||
|
const simpleProfiles = ['claude', 'codex'];
|
||||||
|
|
||||||
RULE_PROFILES.forEach((profile) => {
|
RULE_PROFILES.forEach((profile) => {
|
||||||
const profileConfig = getRulesProfile(profile);
|
const profileConfig = getRulesProfile(profile);
|
||||||
|
|
||||||
@@ -124,7 +154,15 @@ describe('Rule Transformer - General', () => {
|
|||||||
expect(profileConfig).toHaveProperty('mcpConfigName');
|
expect(profileConfig).toHaveProperty('mcpConfigName');
|
||||||
expect(profileConfig).toHaveProperty('mcpConfigPath');
|
expect(profileConfig).toHaveProperty('mcpConfigPath');
|
||||||
|
|
||||||
// Check types
|
// Simple profiles have no MCP configuration
|
||||||
|
if (simpleProfiles.includes(profile)) {
|
||||||
|
expect(profileConfig.mcpConfig).toBe(false);
|
||||||
|
expect(profileConfig.mcpConfigName).toBe(null);
|
||||||
|
expect(profileConfig.mcpConfigPath).toBe(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check types for full profiles
|
||||||
expect(typeof profileConfig.mcpConfig).toBe('boolean');
|
expect(typeof profileConfig.mcpConfig).toBe('boolean');
|
||||||
expect(typeof profileConfig.mcpConfigName).toBe('string');
|
expect(typeof profileConfig.mcpConfigName).toBe('string');
|
||||||
expect(typeof profileConfig.mcpConfigPath).toBe('string');
|
expect(typeof profileConfig.mcpConfigPath).toBe('string');
|
||||||
@@ -138,6 +176,16 @@ describe('Rule Transformer - General', () => {
|
|||||||
|
|
||||||
it('should have correct MCP configuration for each profile', () => {
|
it('should have correct MCP configuration for each profile', () => {
|
||||||
const expectedConfigs = {
|
const expectedConfigs = {
|
||||||
|
claude: {
|
||||||
|
mcpConfig: false,
|
||||||
|
mcpConfigName: null,
|
||||||
|
expectedPath: null
|
||||||
|
},
|
||||||
|
codex: {
|
||||||
|
mcpConfig: false,
|
||||||
|
mcpConfigName: null,
|
||||||
|
expectedPath: null
|
||||||
|
},
|
||||||
cursor: {
|
cursor: {
|
||||||
mcpConfig: true,
|
mcpConfig: true,
|
||||||
mcpConfigName: 'mcp.json',
|
mcpConfigName: 'mcp.json',
|
||||||
@@ -176,9 +224,18 @@ describe('Rule Transformer - General', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should have consistent profileDir and mcpConfigPath relationship', () => {
|
it('should have consistent profileDir and mcpConfigPath relationship', () => {
|
||||||
|
// Simple profiles that only copy files (no MCP configuration)
|
||||||
|
const simpleProfiles = ['claude', 'codex'];
|
||||||
|
|
||||||
RULE_PROFILES.forEach((profile) => {
|
RULE_PROFILES.forEach((profile) => {
|
||||||
const profileConfig = getRulesProfile(profile);
|
const profileConfig = getRulesProfile(profile);
|
||||||
|
|
||||||
|
// Simple profiles have null mcpConfigPath
|
||||||
|
if (simpleProfiles.includes(profile)) {
|
||||||
|
expect(profileConfig.mcpConfigPath).toBe(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// The mcpConfigPath should start with the profileDir
|
// The mcpConfigPath should start with the profileDir
|
||||||
expect(profileConfig.mcpConfigPath).toMatch(
|
expect(profileConfig.mcpConfigPath).toMatch(
|
||||||
new RegExp(
|
new RegExp(
|
||||||
@@ -201,8 +258,11 @@ describe('Rule Transformer - General', () => {
|
|||||||
return profileConfig.profileDir;
|
return profileConfig.profileDir;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Note: Claude and Codex both use "." (root directory) so we expect some duplication
|
||||||
const uniqueProfileDirs = [...new Set(profileDirs)];
|
const uniqueProfileDirs = [...new Set(profileDirs)];
|
||||||
expect(uniqueProfileDirs).toHaveLength(profileDirs.length);
|
// We should have fewer unique directories than total profiles due to simple profiles using root
|
||||||
|
expect(uniqueProfileDirs.length).toBeLessThanOrEqual(profileDirs.length);
|
||||||
|
expect(uniqueProfileDirs.length).toBeGreaterThan(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should have unique MCP config paths', () => {
|
it('should have unique MCP config paths', () => {
|
||||||
@@ -211,8 +271,13 @@ describe('Rule Transformer - General', () => {
|
|||||||
return profileConfig.mcpConfigPath;
|
return profileConfig.mcpConfigPath;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Note: Claude and Codex both have null mcpConfigPath so we expect some duplication
|
||||||
const uniqueMcpConfigPaths = [...new Set(mcpConfigPaths)];
|
const uniqueMcpConfigPaths = [...new Set(mcpConfigPaths)];
|
||||||
expect(uniqueMcpConfigPaths).toHaveLength(mcpConfigPaths.length);
|
// We should have fewer unique paths than total profiles due to simple profiles having null
|
||||||
|
expect(uniqueMcpConfigPaths.length).toBeLessThanOrEqual(
|
||||||
|
mcpConfigPaths.length
|
||||||
|
);
|
||||||
|
expect(uniqueMcpConfigPaths.length).toBeGreaterThan(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
getInstalledProfiles,
|
getInstalledProfiles,
|
||||||
wouldRemovalLeaveNoProfiles
|
wouldRemovalLeaveNoProfiles
|
||||||
} from '../../src/utils/profile-detection.js';
|
} from '../../src/utils/profiles.js';
|
||||||
import { rulesDirect } from '../../mcp-server/src/core/direct-functions/rules.js';
|
import { rulesDirect } from '../../mcp-server/src/core/direct-functions/rules.js';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|||||||
Reference in New Issue
Block a user