mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-05-01 06:23:28 +08:00
fix: namespace claude managed install paths
This commit is contained in:
committed by
Affaan Mustafa
parent
08d6c82989
commit
e381c8d8a8
@@ -79,9 +79,11 @@ function writeManifestSourceFixture(root) {
|
||||
kind: 'fixture',
|
||||
description: 'Fixture module',
|
||||
paths: [
|
||||
'rules',
|
||||
'src',
|
||||
'standalone.txt',
|
||||
'missing.txt',
|
||||
'skills/demo',
|
||||
path.join('runtime', 'ecc', 'install-state.json'),
|
||||
'.claude-plugin',
|
||||
],
|
||||
@@ -107,6 +109,8 @@ function writeManifestSourceFixture(root) {
|
||||
writeFile(root, path.join('src', 'node_modules', 'ignored.js'), 'console.log("ignored");\n');
|
||||
writeFile(root, path.join('src', '.git', 'ignored.js'), 'console.log("ignored");\n');
|
||||
writeFile(root, path.join('src', 'nested', 'ecc-install-state.json'), '{}\n');
|
||||
writeFile(root, path.join('rules', 'common', 'coding-style.md'), '# Common\n');
|
||||
writeFile(root, path.join('skills', 'demo', 'SKILL.md'), '# Demo\n');
|
||||
writeFile(root, 'standalone.txt', 'standalone\n');
|
||||
writeFile(root, path.join('runtime', 'ecc', 'install-state.json'), '{}\n');
|
||||
writeJson(root, path.join('.claude-plugin', 'plugin.json'), { name: 'fixture' });
|
||||
@@ -194,6 +198,35 @@ function runTests() {
|
||||
}
|
||||
})) passed++; else failed++;
|
||||
|
||||
if (test('plans Claude legacy rules under the default ECC-managed rules directory', () => {
|
||||
const sourceRoot = createTempDir('install-executor-source-');
|
||||
const homeDir = createTempDir('install-executor-home-');
|
||||
const projectRoot = createTempDir('install-executor-project-');
|
||||
try {
|
||||
writeLegacySourceFixture(sourceRoot);
|
||||
writeFile(homeDir, path.join('.claude', 'rules', 'common', 'coding-style.md'), '# User custom rule\n');
|
||||
|
||||
const plan = createLegacyInstallPlan({
|
||||
sourceRoot,
|
||||
homeDir,
|
||||
projectRoot,
|
||||
target: 'claude',
|
||||
languages: ['typescript'],
|
||||
});
|
||||
|
||||
const managedRulesDir = path.join(homeDir, '.claude', 'rules', 'ecc');
|
||||
assert.strictEqual(plan.installRoot, managedRulesDir);
|
||||
assert.ok(operationFor(plan, path.join('.claude', 'rules', 'ecc', 'common', 'coding-style.md')));
|
||||
assert.ok(operationFor(plan, path.join('.claude', 'rules', 'ecc', 'typescript', 'testing.md')));
|
||||
assert.ok(!operationFor(plan, path.join('.claude', 'rules', 'common', 'coding-style.md')));
|
||||
assert.ok(!plan.warnings.some(warning => warning.includes('files may be overwritten')));
|
||||
} finally {
|
||||
cleanup(sourceRoot);
|
||||
cleanup(homeDir);
|
||||
cleanup(projectRoot);
|
||||
}
|
||||
})) passed++; else failed++;
|
||||
|
||||
if (test('plans Cursor legacy assets and JSON merge payloads', () => {
|
||||
const sourceRoot = createTempDir('install-executor-source-');
|
||||
const projectRoot = createTempDir('install-executor-project-');
|
||||
@@ -307,6 +340,8 @@ function runTests() {
|
||||
));
|
||||
assert.ok(normalizedSources.includes('src/app.js'));
|
||||
assert.ok(normalizedSources.includes('src/nested/feature.js'));
|
||||
assert.ok(normalizedSources.includes('rules/common/coding-style.md'));
|
||||
assert.ok(normalizedSources.includes('skills/demo/SKILL.md'));
|
||||
assert.ok(normalizedSources.includes('standalone.txt'));
|
||||
assert.ok(normalizedSources.includes('.claude-plugin/plugin.json'));
|
||||
assert.ok(!normalizedSources.includes('missing.txt'));
|
||||
@@ -318,6 +353,14 @@ function runTests() {
|
||||
operation.sourceRelativePath === path.join('.claude-plugin', 'plugin.json')
|
||||
&& operation.destinationPath === path.join(homeDir, '.claude', 'plugin.json')
|
||||
)));
|
||||
assert.ok(plan.operations.some(operation => (
|
||||
operation.sourceRelativePath === path.join('rules', 'common', 'coding-style.md')
|
||||
&& operation.destinationPath === path.join(homeDir, '.claude', 'rules', 'ecc', 'common', 'coding-style.md')
|
||||
)));
|
||||
assert.ok(plan.operations.some(operation => (
|
||||
operation.sourceRelativePath === path.join('skills', 'demo', 'SKILL.md')
|
||||
&& operation.destinationPath === path.join(homeDir, '.claude', 'skills', 'ecc', 'demo', 'SKILL.md')
|
||||
)));
|
||||
assert.deepStrictEqual(plan.warnings, ['fixture warning']);
|
||||
assert.strictEqual(plan.statePreview.request.profile, 'minimal');
|
||||
assert.deepStrictEqual(plan.statePreview.request.includeComponents, ['capability:fixture']);
|
||||
@@ -369,6 +412,8 @@ function runTests() {
|
||||
const applied = applyInstallPlan(plan);
|
||||
|
||||
assert.strictEqual(applied.applied, true);
|
||||
assert.ok(fs.existsSync(path.join(homeDir, '.claude', 'rules', 'ecc', 'common', 'coding-style.md')));
|
||||
assert.ok(fs.existsSync(path.join(homeDir, '.claude', 'skills', 'ecc', 'demo', 'SKILL.md')));
|
||||
assert.ok(fs.existsSync(path.join(homeDir, '.claude', 'src', 'app.js')));
|
||||
assert.ok(fs.existsSync(path.join(homeDir, '.claude', 'standalone.txt')));
|
||||
assert.ok(fs.existsSync(path.join(homeDir, '.claude', 'plugin.json')));
|
||||
|
||||
@@ -65,6 +65,42 @@ function runTests() {
|
||||
assert.strictEqual(statePath, path.join(homeDir, '.claude', 'ecc', 'install-state.json'));
|
||||
})) passed++; else failed++;
|
||||
|
||||
if (test('plans claude rules and skills under ECC-managed subdirectories', () => {
|
||||
const repoRoot = path.join(__dirname, '..', '..');
|
||||
const homeDir = '/Users/example';
|
||||
|
||||
const plan = planInstallTargetScaffold({
|
||||
target: 'claude',
|
||||
repoRoot,
|
||||
homeDir,
|
||||
modules: [
|
||||
{
|
||||
id: 'rules-core',
|
||||
paths: ['rules'],
|
||||
},
|
||||
{
|
||||
id: 'workflow-quality',
|
||||
paths: ['skills/tdd-workflow'],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
assert.ok(
|
||||
plan.operations.some(operation => (
|
||||
normalizedRelativePath(operation.sourceRelativePath) === 'rules'
|
||||
&& operation.destinationPath === path.join(homeDir, '.claude', 'rules', 'ecc')
|
||||
)),
|
||||
'Should install bundled Claude rules under rules/ecc'
|
||||
);
|
||||
assert.ok(
|
||||
plan.operations.some(operation => (
|
||||
normalizedRelativePath(operation.sourceRelativePath) === 'skills/tdd-workflow'
|
||||
&& operation.destinationPath === path.join(homeDir, '.claude', 'skills', 'ecc', 'tdd-workflow')
|
||||
)),
|
||||
'Should install bundled Claude skills under skills/ecc'
|
||||
);
|
||||
})) passed++; else failed++;
|
||||
|
||||
if (test('plans scaffold operations and flattens native target roots', () => {
|
||||
const repoRoot = path.join(__dirname, '..', '..');
|
||||
const projectRoot = '/workspace/app';
|
||||
|
||||
@@ -576,10 +576,10 @@ function runTests() {
|
||||
|
||||
const claudeRoot = path.join(homeDir, '.claude');
|
||||
// Security skill should be installed (from --with)
|
||||
assert.ok(fs.existsSync(path.join(claudeRoot, 'skills', 'security-review', 'SKILL.md')),
|
||||
assert.ok(fs.existsSync(path.join(claudeRoot, 'skills', 'ecc', 'security-review', 'SKILL.md')),
|
||||
'Should install security-review skill from --with');
|
||||
// Core profile modules should be installed
|
||||
assert.ok(fs.existsSync(path.join(claudeRoot, 'rules', 'common', 'coding-style.md')),
|
||||
assert.ok(fs.existsSync(path.join(claudeRoot, 'rules', 'ecc', 'common', 'coding-style.md')),
|
||||
'Should install core rules');
|
||||
|
||||
// Install state should record include/exclude
|
||||
@@ -615,12 +615,12 @@ function runTests() {
|
||||
|
||||
const claudeRoot = path.join(homeDir, '.claude');
|
||||
// Orchestration skills should NOT be installed (from --without)
|
||||
assert.ok(!fs.existsSync(path.join(claudeRoot, 'skills', 'dmux-workflows', 'SKILL.md')),
|
||||
assert.ok(!fs.existsSync(path.join(claudeRoot, 'skills', 'ecc', 'dmux-workflows', 'SKILL.md')),
|
||||
'Should not install orchestration skills');
|
||||
// Developer profile base modules should be installed
|
||||
assert.ok(fs.existsSync(path.join(claudeRoot, 'rules', 'common', 'coding-style.md')),
|
||||
assert.ok(fs.existsSync(path.join(claudeRoot, 'rules', 'ecc', 'common', 'coding-style.md')),
|
||||
'Should install core rules');
|
||||
assert.ok(fs.existsSync(path.join(claudeRoot, 'skills', 'tdd-workflow', 'SKILL.md')),
|
||||
assert.ok(fs.existsSync(path.join(claudeRoot, 'skills', 'ecc', 'tdd-workflow', 'SKILL.md')),
|
||||
'Should install workflow skills');
|
||||
|
||||
const statePath = path.join(claudeRoot, 'ecc', 'install-state.json');
|
||||
@@ -653,10 +653,10 @@ function runTests() {
|
||||
|
||||
const claudeRoot = path.join(homeDir, '.claude');
|
||||
// framework-language skill (from lang:typescript) should be installed
|
||||
assert.ok(fs.existsSync(path.join(claudeRoot, 'skills', 'coding-standards', 'SKILL.md')),
|
||||
assert.ok(fs.existsSync(path.join(claudeRoot, 'skills', 'ecc', 'coding-standards', 'SKILL.md')),
|
||||
'Should install framework-language skills');
|
||||
// Its dependencies should be installed
|
||||
assert.ok(fs.existsSync(path.join(claudeRoot, 'rules', 'common', 'coding-style.md')),
|
||||
assert.ok(fs.existsSync(path.join(claudeRoot, 'rules', 'ecc', 'common', 'coding-style.md')),
|
||||
'Should install dependency rules-core');
|
||||
|
||||
const statePath = path.join(claudeRoot, 'ecc', 'install-state.json');
|
||||
|
||||
@@ -94,13 +94,13 @@ function runTests() {
|
||||
assert.strictEqual(result.code, 0, result.stderr);
|
||||
|
||||
const claudeRoot = path.join(homeDir, '.claude');
|
||||
assert.ok(fs.existsSync(path.join(claudeRoot, 'rules', 'common', 'coding-style.md')));
|
||||
assert.ok(fs.existsSync(path.join(claudeRoot, 'rules', 'typescript', 'testing.md')));
|
||||
assert.ok(fs.existsSync(path.join(claudeRoot, 'rules', 'ecc', 'common', 'coding-style.md')));
|
||||
assert.ok(fs.existsSync(path.join(claudeRoot, 'rules', 'ecc', 'typescript', 'testing.md')));
|
||||
assert.ok(fs.existsSync(path.join(claudeRoot, 'commands', 'plan.md')));
|
||||
assert.ok(fs.existsSync(path.join(claudeRoot, 'scripts', 'hooks', 'session-end.js')));
|
||||
assert.ok(fs.existsSync(path.join(claudeRoot, 'scripts', 'lib', 'utils.js')));
|
||||
assert.ok(fs.existsSync(path.join(claudeRoot, 'skills', 'tdd-workflow', 'SKILL.md')));
|
||||
assert.ok(fs.existsSync(path.join(claudeRoot, 'skills', 'coding-standards', 'SKILL.md')));
|
||||
assert.ok(fs.existsSync(path.join(claudeRoot, 'skills', 'ecc', 'tdd-workflow', 'SKILL.md')));
|
||||
assert.ok(fs.existsSync(path.join(claudeRoot, 'skills', 'ecc', 'coding-standards', 'SKILL.md')));
|
||||
assert.ok(fs.existsSync(path.join(claudeRoot, 'plugin.json')));
|
||||
|
||||
const statePath = path.join(homeDir, '.claude', 'ecc', 'install-state.json');
|
||||
@@ -113,7 +113,7 @@ function runTests() {
|
||||
assert.ok(state.resolution.selectedModules.includes('framework-language'));
|
||||
assert.ok(
|
||||
state.operations.some(operation => (
|
||||
operation.destinationPath === path.join(claudeRoot, 'rules', 'common', 'coding-style.md')
|
||||
operation.destinationPath === path.join(claudeRoot, 'rules', 'ecc', 'common', 'coding-style.md')
|
||||
)),
|
||||
'Should record common rule file operation'
|
||||
);
|
||||
@@ -299,7 +299,7 @@ function runTests() {
|
||||
assert.strictEqual(result.code, 0, result.stderr);
|
||||
|
||||
const claudeRoot = path.join(homeDir, '.claude');
|
||||
assert.ok(fs.existsSync(path.join(claudeRoot, 'rules', 'common', 'coding-style.md')));
|
||||
assert.ok(fs.existsSync(path.join(claudeRoot, 'rules', 'ecc', 'common', 'coding-style.md')));
|
||||
assert.ok(fs.existsSync(path.join(claudeRoot, 'agents', 'architect.md')));
|
||||
assert.ok(fs.existsSync(path.join(claudeRoot, 'commands', 'plan.md')));
|
||||
assert.ok(fs.existsSync(path.join(claudeRoot, 'hooks', 'hooks.json')));
|
||||
@@ -324,6 +324,32 @@ function runTests() {
|
||||
}
|
||||
})) passed++; else failed++;
|
||||
|
||||
if (test('preserves existing top-level Claude rules and skills during managed install', () => {
|
||||
const homeDir = createTempDir('install-apply-home-');
|
||||
const projectDir = createTempDir('install-apply-project-');
|
||||
|
||||
try {
|
||||
const claudeRoot = path.join(homeDir, '.claude');
|
||||
const userRulePath = path.join(claudeRoot, 'rules', 'common', 'coding-style.md');
|
||||
const userSkillPath = path.join(claudeRoot, 'skills', 'tdd-workflow', 'SKILL.md');
|
||||
fs.mkdirSync(path.dirname(userRulePath), { recursive: true });
|
||||
fs.mkdirSync(path.dirname(userSkillPath), { recursive: true });
|
||||
fs.writeFileSync(userRulePath, '# User custom rule\n');
|
||||
fs.writeFileSync(userSkillPath, '# User custom skill\n');
|
||||
|
||||
const result = run(['--profile', 'core'], { cwd: projectDir, homeDir });
|
||||
assert.strictEqual(result.code, 0, result.stderr);
|
||||
|
||||
assert.strictEqual(fs.readFileSync(userRulePath, 'utf8'), '# User custom rule\n');
|
||||
assert.strictEqual(fs.readFileSync(userSkillPath, 'utf8'), '# User custom skill\n');
|
||||
assert.ok(fs.existsSync(path.join(claudeRoot, 'rules', 'ecc', 'common', 'coding-style.md')));
|
||||
assert.ok(fs.existsSync(path.join(claudeRoot, 'skills', 'ecc', 'tdd-workflow', 'SKILL.md')));
|
||||
} finally {
|
||||
cleanup(homeDir);
|
||||
cleanup(projectDir);
|
||||
}
|
||||
})) passed++; else failed++;
|
||||
|
||||
if (test('installs antigravity manifest profiles while skipping only unsupported modules', () => {
|
||||
const homeDir = createTempDir('install-apply-home-');
|
||||
const projectDir = createTempDir('install-apply-project-');
|
||||
@@ -727,8 +753,8 @@ function runTests() {
|
||||
const result = run(['--config', configPath], { cwd: projectDir, homeDir });
|
||||
assert.strictEqual(result.code, 0, result.stderr);
|
||||
|
||||
assert.ok(fs.existsSync(path.join(homeDir, '.claude', 'skills', 'security-review', 'SKILL.md')));
|
||||
assert.ok(!fs.existsSync(path.join(homeDir, '.claude', 'skills', 'dmux-workflows', 'SKILL.md')));
|
||||
assert.ok(fs.existsSync(path.join(homeDir, '.claude', 'skills', 'ecc', 'security-review', 'SKILL.md')));
|
||||
assert.ok(!fs.existsSync(path.join(homeDir, '.claude', 'skills', 'ecc', 'dmux-workflows', 'SKILL.md')));
|
||||
|
||||
const state = readJson(path.join(homeDir, '.claude', 'ecc', 'install-state.json'));
|
||||
assert.strictEqual(state.request.profile, 'developer');
|
||||
@@ -759,8 +785,8 @@ function runTests() {
|
||||
const result = run([], { cwd: projectDir, homeDir });
|
||||
assert.strictEqual(result.code, 0, result.stderr);
|
||||
|
||||
assert.ok(fs.existsSync(path.join(homeDir, '.claude', 'skills', 'security-review', 'SKILL.md')));
|
||||
assert.ok(!fs.existsSync(path.join(homeDir, '.claude', 'skills', 'dmux-workflows', 'SKILL.md')));
|
||||
assert.ok(fs.existsSync(path.join(homeDir, '.claude', 'skills', 'ecc', 'security-review', 'SKILL.md')));
|
||||
assert.ok(!fs.existsSync(path.join(homeDir, '.claude', 'skills', 'ecc', 'dmux-workflows', 'SKILL.md')));
|
||||
|
||||
const state = readJson(path.join(homeDir, '.claude', 'ecc', 'install-state.json'));
|
||||
assert.strictEqual(state.request.profile, 'developer');
|
||||
|
||||
@@ -132,6 +132,10 @@ function runTests() {
|
||||
readme.includes('Start with `rules/common` plus one language or framework pack you actually use.'),
|
||||
'README should steer users away from copying every rules directory'
|
||||
);
|
||||
assert.ok(
|
||||
readme.includes('~/.claude/rules/ecc/'),
|
||||
'README should steer plugin-path rules into an ECC-owned namespace'
|
||||
);
|
||||
})) passed++; else failed++;
|
||||
|
||||
console.log(`\nResults: Passed: ${passed}, Failed: ${failed}`);
|
||||
|
||||
Reference in New Issue
Block a user