Unify and streamline profile system architecture (#853)
* move claude rules and commands to assets/claude * update claude profile to copy assets/claude to .claude * fix formatting * feat(profiles): Implement unified profile system - Convert Claude and Codex profiles to use createProfile() factory - Remove simple vs complex profile distinction in rule transformer - Unify convertAllRulesToProfileRules() to handle all profiles consistently - Fix mcpConfigPath construction in base-profile.js for null mcpConfigName - Update terminology from 'simpleProfiles' to 'assetOnlyProfiles' throughout - Ensure Claude .claude directory copying works in both CLI and MCP contexts - All profiles now follow same execution flow with proper lifecycle functions Changes: - src/profiles/claude.js: Convert to createProfile() factory pattern - src/profiles/codex.js: Convert to createProfile() factory pattern - src/utils/rule-transformer.js: Unified profile handling logic - src/utils/profiles.js: Remove simple profile categorization - src/profiles/base-profile.js: Fix mcpConfigPath construction - scripts/modules/commands.js: Update variable naming - tests/: Update all tests for unified system and terminology Fixes Claude profile asset copying issue in MCP context. All tests passing (617 passed, 11 skipped). * re-checkin claude files * fix formatting * chore: clean up test Claude rules files * chore: add changeset for unified profile system * add claude files back * add changeset * restore proper gitignore * remove claude agents file from root * remove incorrect doc * simplify profiles and update tests * update changeset * update changeset * remove profile specific code * streamline profiles with defaults and update tests * update changeset * add newline at end of gitignore * restore changes * streamline profiles with defaults; update tests and add vscode test * update rule profile tests * update wording for clearer profile management * refactor and clarify terminology * use original projectRoot var name * revert param desc * use updated claude assets from neno * add "YOUR_" before api key here * streamline codex profile * add gemini profile * update gemini profile * update tests * relocate function * update rules interactive setup Gemini desc * remove duplicative code * add comma
This commit is contained in:
@@ -17,6 +17,7 @@ describe('Rule Transformer - General', () => {
|
||||
'cline',
|
||||
'codex',
|
||||
'cursor',
|
||||
'gemini',
|
||||
'roo',
|
||||
'trae',
|
||||
'vscode',
|
||||
@@ -55,9 +56,6 @@ describe('Rule Transformer - General', () => {
|
||||
|
||||
describe('Profile Structure', () => {
|
||||
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) => {
|
||||
const profileConfig = getRulesProfile(profile);
|
||||
|
||||
@@ -68,50 +66,50 @@ describe('Rule Transformer - General', () => {
|
||||
expect(profileConfig).toHaveProperty('rulesDir');
|
||||
expect(profileConfig).toHaveProperty('profileDir');
|
||||
|
||||
// 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;
|
||||
// All profiles should have conversionConfig and fileMap objects
|
||||
expect(typeof profileConfig.conversionConfig).toBe('object');
|
||||
expect(typeof profileConfig.fileMap).toBe('object');
|
||||
|
||||
// Check that conversionConfig has required structure for profiles with rules
|
||||
const hasRules = Object.keys(profileConfig.fileMap).length > 0;
|
||||
if (hasRules) {
|
||||
expect(profileConfig.conversionConfig).toHaveProperty('profileTerms');
|
||||
expect(profileConfig.conversionConfig).toHaveProperty('toolNames');
|
||||
expect(profileConfig.conversionConfig).toHaveProperty('toolContexts');
|
||||
expect(profileConfig.conversionConfig).toHaveProperty('toolGroups');
|
||||
expect(profileConfig.conversionConfig).toHaveProperty('docUrls');
|
||||
expect(profileConfig.conversionConfig).toHaveProperty(
|
||||
'fileReferences'
|
||||
);
|
||||
|
||||
// Verify arrays are actually arrays
|
||||
expect(
|
||||
Array.isArray(profileConfig.conversionConfig.profileTerms)
|
||||
).toBe(true);
|
||||
expect(typeof profileConfig.conversionConfig.toolNames).toBe(
|
||||
'object'
|
||||
);
|
||||
expect(
|
||||
Array.isArray(profileConfig.conversionConfig.toolContexts)
|
||||
).toBe(true);
|
||||
expect(Array.isArray(profileConfig.conversionConfig.toolGroups)).toBe(
|
||||
true
|
||||
);
|
||||
expect(Array.isArray(profileConfig.conversionConfig.docUrls)).toBe(
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
// Check that conversionConfig has required structure for full profiles
|
||||
expect(profileConfig.conversionConfig).toHaveProperty('profileTerms');
|
||||
expect(profileConfig.conversionConfig).toHaveProperty('toolNames');
|
||||
expect(profileConfig.conversionConfig).toHaveProperty('toolContexts');
|
||||
expect(profileConfig.conversionConfig).toHaveProperty('toolGroups');
|
||||
expect(profileConfig.conversionConfig).toHaveProperty('docUrls');
|
||||
expect(profileConfig.conversionConfig).toHaveProperty('fileReferences');
|
||||
|
||||
// Verify arrays are actually arrays
|
||||
expect(Array.isArray(profileConfig.conversionConfig.profileTerms)).toBe(
|
||||
true
|
||||
);
|
||||
expect(typeof profileConfig.conversionConfig.toolNames).toBe('object');
|
||||
expect(Array.isArray(profileConfig.conversionConfig.toolContexts)).toBe(
|
||||
true
|
||||
);
|
||||
expect(Array.isArray(profileConfig.conversionConfig.toolGroups)).toBe(
|
||||
true
|
||||
);
|
||||
expect(Array.isArray(profileConfig.conversionConfig.docUrls)).toBe(
|
||||
true
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should have valid fileMap with required files for each profile', () => {
|
||||
const expectedFiles = [
|
||||
const expectedRuleFiles = [
|
||||
'cursor_rules.mdc',
|
||||
'dev_workflow.mdc',
|
||||
'self_improve.mdc',
|
||||
'taskmaster.mdc'
|
||||
];
|
||||
|
||||
// Simple profiles that only copy files (no rule transformation)
|
||||
const simpleProfiles = ['claude', 'codex'];
|
||||
|
||||
RULE_PROFILES.forEach((profile) => {
|
||||
const profileConfig = getRulesProfile(profile);
|
||||
|
||||
@@ -120,33 +118,43 @@ describe('Rule Transformer - General', () => {
|
||||
expect(typeof profileConfig.fileMap).toBe('object');
|
||||
expect(profileConfig.fileMap).not.toBeNull();
|
||||
|
||||
// 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);
|
||||
|
||||
// All profiles should have some fileMap entries now
|
||||
expect(fileMapKeys.length).toBeGreaterThan(0);
|
||||
|
||||
// Check that all expected source files are defined in fileMap
|
||||
expectedFiles.forEach((expectedFile) => {
|
||||
expect(fileMapKeys).toContain(expectedFile);
|
||||
expect(typeof profileConfig.fileMap[expectedFile]).toBe('string');
|
||||
expect(profileConfig.fileMap[expectedFile].length).toBeGreaterThan(0);
|
||||
});
|
||||
// Check if this profile has rule files or asset files
|
||||
const hasRuleFiles = expectedRuleFiles.some((file) =>
|
||||
fileMapKeys.includes(file)
|
||||
);
|
||||
const hasAssetFiles = fileMapKeys.some(
|
||||
(file) => !expectedRuleFiles.includes(file)
|
||||
);
|
||||
|
||||
// Verify fileMap has exactly the expected files
|
||||
expect(fileMapKeys.sort()).toEqual(expectedFiles.sort());
|
||||
if (hasRuleFiles) {
|
||||
// Profiles with rule files should have all expected rule files
|
||||
expectedRuleFiles.forEach((expectedFile) => {
|
||||
expect(fileMapKeys).toContain(expectedFile);
|
||||
expect(typeof profileConfig.fileMap[expectedFile]).toBe('string');
|
||||
expect(profileConfig.fileMap[expectedFile].length).toBeGreaterThan(
|
||||
0
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
if (hasAssetFiles) {
|
||||
// Profiles with asset files (like Claude/Codex) should have valid asset mappings
|
||||
fileMapKeys.forEach((key) => {
|
||||
expect(typeof profileConfig.fileMap[key]).toBe('string');
|
||||
expect(profileConfig.fileMap[key].length).toBeGreaterThan(0);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('MCP Configuration Properties', () => {
|
||||
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) => {
|
||||
const profileConfig = getRulesProfile(profile);
|
||||
|
||||
@@ -155,23 +163,23 @@ describe('Rule Transformer - General', () => {
|
||||
expect(profileConfig).toHaveProperty('mcpConfigName');
|
||||
expect(profileConfig).toHaveProperty('mcpConfigPath');
|
||||
|
||||
// Simple profiles have no MCP configuration
|
||||
if (simpleProfiles.includes(profile)) {
|
||||
expect(profileConfig.mcpConfig).toBe(false);
|
||||
// Check types based on MCP configuration
|
||||
expect(typeof profileConfig.mcpConfig).toBe('boolean');
|
||||
|
||||
if (profileConfig.mcpConfig === false) {
|
||||
// Profiles without MCP configuration
|
||||
expect(profileConfig.mcpConfigName).toBe(null);
|
||||
expect(profileConfig.mcpConfigPath).toBe(null);
|
||||
return;
|
||||
} else {
|
||||
// Profiles with MCP configuration
|
||||
expect(typeof profileConfig.mcpConfigName).toBe('string');
|
||||
expect(typeof profileConfig.mcpConfigPath).toBe('string');
|
||||
|
||||
// Check that mcpConfigPath is properly constructed
|
||||
expect(profileConfig.mcpConfigPath).toBe(
|
||||
`${profileConfig.profileDir}/${profileConfig.mcpConfigName}`
|
||||
);
|
||||
}
|
||||
|
||||
// Check types for full profiles
|
||||
expect(typeof profileConfig.mcpConfig).toBe('boolean');
|
||||
expect(typeof profileConfig.mcpConfigName).toBe('string');
|
||||
expect(typeof profileConfig.mcpConfigPath).toBe('string');
|
||||
|
||||
// Check that mcpConfigPath is properly constructed
|
||||
expect(profileConfig.mcpConfigPath).toBe(
|
||||
`${profileConfig.profileDir}/${profileConfig.mcpConfigName}`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -184,8 +192,8 @@ describe('Rule Transformer - General', () => {
|
||||
},
|
||||
cline: {
|
||||
mcpConfig: false,
|
||||
mcpConfigName: 'cline_mcp_settings.json',
|
||||
expectedPath: '.clinerules/cline_mcp_settings.json'
|
||||
mcpConfigName: null,
|
||||
expectedPath: null
|
||||
},
|
||||
codex: {
|
||||
mcpConfig: false,
|
||||
@@ -197,6 +205,11 @@ describe('Rule Transformer - General', () => {
|
||||
mcpConfigName: 'mcp.json',
|
||||
expectedPath: '.cursor/mcp.json'
|
||||
},
|
||||
gemini: {
|
||||
mcpConfig: true,
|
||||
mcpConfigName: 'settings.json',
|
||||
expectedPath: '.gemini/settings.json'
|
||||
},
|
||||
roo: {
|
||||
mcpConfig: true,
|
||||
mcpConfigName: 'mcp.json',
|
||||
@@ -204,8 +217,8 @@ describe('Rule Transformer - General', () => {
|
||||
},
|
||||
trae: {
|
||||
mcpConfig: false,
|
||||
mcpConfigName: 'trae_mcp_settings.json',
|
||||
expectedPath: '.trae/trae_mcp_settings.json'
|
||||
mcpConfigName: null,
|
||||
expectedPath: null
|
||||
},
|
||||
vscode: {
|
||||
mcpConfig: true,
|
||||
@@ -230,31 +243,28 @@ describe('Rule Transformer - General', () => {
|
||||
});
|
||||
|
||||
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) => {
|
||||
const profileConfig = getRulesProfile(profile);
|
||||
|
||||
// Simple profiles have null mcpConfigPath
|
||||
if (simpleProfiles.includes(profile)) {
|
||||
if (profileConfig.mcpConfig === false) {
|
||||
// Profiles without MCP configuration have null mcpConfigPath
|
||||
expect(profileConfig.mcpConfigPath).toBe(null);
|
||||
return;
|
||||
} else {
|
||||
// Profiles with MCP configuration should have valid paths
|
||||
// The mcpConfigPath should start with the profileDir
|
||||
expect(profileConfig.mcpConfigPath).toMatch(
|
||||
new RegExp(
|
||||
`^${profileConfig.profileDir.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}/`
|
||||
)
|
||||
);
|
||||
|
||||
// The mcpConfigPath should end with the mcpConfigName
|
||||
expect(profileConfig.mcpConfigPath).toMatch(
|
||||
new RegExp(
|
||||
`${profileConfig.mcpConfigName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// The mcpConfigPath should start with the profileDir
|
||||
expect(profileConfig.mcpConfigPath).toMatch(
|
||||
new RegExp(
|
||||
`^${profileConfig.profileDir.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}/`
|
||||
)
|
||||
);
|
||||
|
||||
// The mcpConfigPath should end with the mcpConfigName
|
||||
expect(profileConfig.mcpConfigPath).toMatch(
|
||||
new RegExp(
|
||||
`${profileConfig.mcpConfigName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$`
|
||||
)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user