fix: namespace claude managed install paths

This commit is contained in:
Affaan Mustafa
2026-04-30 07:43:20 -04:00
committed by Affaan Mustafa
parent 08d6c82989
commit e381c8d8a8
10 changed files with 243 additions and 53 deletions

View File

@@ -245,7 +245,7 @@ This is intentional. Anthropic marketplace/plugin installs are keyed by a canoni
>
> If you already installed ECC via `/plugin install`, **do not run `./install.sh --profile full`, `.\install.ps1 --profile full`, or `npx ecc-install --profile full` afterward**. The plugin already loads ECC skills, commands, and hooks. Running the full installer after a plugin install copies those same surfaces into your user directories and can create duplicate skills plus duplicate runtime behavior.
>
> For plugin installs, manually copy only the `rules/` directories you want. Start with `rules/common` plus one language or framework pack you actually use. Do not copy every rules directory unless you explicitly want all of that context in Claude.
> For plugin installs, manually copy only the `rules/` directories you want under `~/.claude/rules/ecc/`. Start with `rules/common` plus one language or framework pack you actually use. Do not copy every rules directory unless you explicitly want all of that context in Claude.
>
> Use the full installer only when you are doing a fully manual ECC install instead of the plugin path.
>
@@ -259,10 +259,10 @@ cd everything-claude-code
# Install dependencies (pick your package manager)
npm install # or: pnpm install | yarn install | bun install
# Plugin install path: copy only rules
mkdir -p ~/.claude/rules
cp -R rules/common ~/.claude/rules/
cp -R rules/typescript ~/.claude/rules/
# Plugin install path: copy only ECC rules into an ECC-owned namespace
mkdir -p ~/.claude/rules/ecc
cp -R rules/common ~/.claude/rules/ecc/
cp -R rules/typescript ~/.claude/rules/ecc/
# Fully manual ECC install path (use this instead of /plugin install)
# ./install.sh --profile full
@@ -271,10 +271,10 @@ cp -R rules/typescript ~/.claude/rules/
```powershell
# Windows PowerShell
# Plugin install path: copy only rules
New-Item -ItemType Directory -Force -Path "$HOME/.claude/rules" | Out-Null
Copy-Item -Recurse rules/common "$HOME/.claude/rules/"
Copy-Item -Recurse rules/typescript "$HOME/.claude/rules/"
# Plugin install path: copy only ECC rules into an ECC-owned namespace
New-Item -ItemType Directory -Force -Path "$HOME/.claude/rules/ecc" | Out-Null
Copy-Item -Recurse rules/common "$HOME/.claude/rules/ecc/"
Copy-Item -Recurse rules/typescript "$HOME/.claude/rules/ecc/"
# Fully manual ECC install path (use this instead of /plugin install)
# .\install.ps1 --profile full
@@ -303,7 +303,7 @@ If you choose this path, stop there. Do not also run `/plugin install`.
If ECC feels duplicated, intrusive, or broken, do not keep reinstalling it on top of itself.
- **Plugin path:** remove the plugin from Claude Code, then delete the specific rule folders you manually copied under `~/.claude/rules/`.
- **Plugin path:** remove the plugin from Claude Code, then delete the specific rule folders you manually copied under `~/.claude/rules/ecc/`.
- **Manual installer / CLI path:** from the repo root, preview removal first:
```bash
@@ -574,7 +574,7 @@ everything-claude-code/
| |-- verify.md # /verify - Prefer the verification-loop skill
| |-- orchestrate.md # /orchestrate - Prefer dmux-workflows or multi-workflow
|
|-- rules/ # Always-follow guidelines (copy to ~/.claude/rules/)
|-- rules/ # Always-follow guidelines (copy to ~/.claude/rules/ecc/)
| |-- README.md # Structure overview and installation guide
| |-- common/ # Language-agnostic principles
| | |-- coding-style.md # Immutability, file organization
@@ -791,17 +791,17 @@ This gives you instant access to all commands, agents, skills, and hooks.
> git clone https://github.com/affaan-m/everything-claude-code.git
>
> # Option A: User-level rules (applies to all projects)
> mkdir -p ~/.claude/rules
> cp -r everything-claude-code/rules/common ~/.claude/rules/
> cp -r everything-claude-code/rules/typescript ~/.claude/rules/ # pick your stack
> cp -r everything-claude-code/rules/python ~/.claude/rules/
> cp -r everything-claude-code/rules/golang ~/.claude/rules/
> cp -r everything-claude-code/rules/php ~/.claude/rules/
> mkdir -p ~/.claude/rules/ecc
> cp -r everything-claude-code/rules/common ~/.claude/rules/ecc/
> cp -r everything-claude-code/rules/typescript ~/.claude/rules/ecc/ # pick your stack
> cp -r everything-claude-code/rules/python ~/.claude/rules/ecc/
> cp -r everything-claude-code/rules/golang ~/.claude/rules/ecc/
> cp -r everything-claude-code/rules/php ~/.claude/rules/ecc/
>
> # Option B: Project-level rules (applies to current project only)
> mkdir -p .claude/rules
> cp -r everything-claude-code/rules/common .claude/rules/
> cp -r everything-claude-code/rules/typescript .claude/rules/ # pick your stack
> mkdir -p .claude/rules/ecc
> cp -r everything-claude-code/rules/common .claude/rules/ecc/
> cp -r everything-claude-code/rules/typescript .claude/rules/ecc/ # pick your stack
> ```
---
@@ -818,21 +818,22 @@ git clone https://github.com/affaan-m/everything-claude-code.git
cp everything-claude-code/agents/*.md ~/.claude/agents/
# Copy rules directories (common + language-specific)
mkdir -p ~/.claude/rules
cp -r everything-claude-code/rules/common ~/.claude/rules/
cp -r everything-claude-code/rules/typescript ~/.claude/rules/ # pick your stack
cp -r everything-claude-code/rules/python ~/.claude/rules/
cp -r everything-claude-code/rules/golang ~/.claude/rules/
cp -r everything-claude-code/rules/php ~/.claude/rules/
mkdir -p ~/.claude/rules/ecc
cp -r everything-claude-code/rules/common ~/.claude/rules/ecc/
cp -r everything-claude-code/rules/typescript ~/.claude/rules/ecc/ # pick your stack
cp -r everything-claude-code/rules/python ~/.claude/rules/ecc/
cp -r everything-claude-code/rules/golang ~/.claude/rules/ecc/
cp -r everything-claude-code/rules/php ~/.claude/rules/ecc/
# Copy skills first (primary workflow surface)
# Recommended (new users): core/general skills only
cp -r everything-claude-code/.agents/skills/* ~/.claude/skills/
cp -r everything-claude-code/skills/search-first ~/.claude/skills/
mkdir -p ~/.claude/skills/ecc
cp -r everything-claude-code/.agents/skills/* ~/.claude/skills/ecc/
cp -r everything-claude-code/skills/search-first ~/.claude/skills/ecc/
# Optional: add niche/framework-specific skills only when needed
# for s in django-patterns django-tdd laravel-patterns springboot-patterns; do
# cp -r everything-claude-code/skills/$s ~/.claude/skills/
# cp -r everything-claude-code/skills/$s ~/.claude/skills/ecc/
# done
# Optional: keep maintained slash-command compatibility during migration
@@ -1059,8 +1060,8 @@ Yes. Use Option 2 (manual installation) and copy only what you need:
cp everything-claude-code/agents/*.md ~/.claude/agents/
# Just rules
mkdir -p ~/.claude/rules/
cp -r everything-claude-code/rules/common ~/.claude/rules/
mkdir -p ~/.claude/rules/ecc/
cp -r everything-claude-code/rules/common ~/.claude/rules/ecc/
```
Each component is fully independent.

View File

@@ -640,7 +640,7 @@ Suggested operation shape:
"kind": "copy",
"moduleId": "rules-core",
"source": "rules/common/coding-style.md",
"destination": "/Users/example/.claude/rules/common/coding-style.md",
"destination": "/Users/example/.claude/rules/ecc/common/coding-style.md",
"ownership": "managed",
"overwritePolicy": "replace"
}
@@ -711,7 +711,7 @@ Suggested payload:
{
"kind": "copy",
"moduleId": "rules-core",
"destination": "/Users/example/.claude/rules/common/coding-style.md",
"destination": "/Users/example/.claude/rules/ecc/common/coding-style.md",
"digest": "sha256:..."
}
]

View File

@@ -27,7 +27,7 @@ Usage: install.sh [--target <${LEGACY_INSTALL_TARGETS.join('|')}>] [--dry-run] [
install.sh [--dry-run] [--json] --config <path>
Targets:
claude (default) - Install ECC into ~/.claude/ (hooks, commands, agents, rules, skills)
claude (default) - Install ECC into ~/.claude/ with managed rules/skills under rules/ecc and skills/ecc
cursor - Install rules, hooks, and bundled Cursor configs to ./.cursor/
antigravity - Install rules, workflows, skills, and agents to ./.agent/

View File

@@ -14,6 +14,7 @@ const {
const { getInstallTargetAdapter } = require('./install-targets/registry');
const LANGUAGE_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/;
const CLAUDE_ECC_NAMESPACE = 'ecc';
const EXCLUDED_GENERATED_SOURCE_SUFFIXES = [
'/ecc-install-state.json',
'/ecc/install-state.json',
@@ -264,7 +265,7 @@ function isDirectoryNonEmpty(dirPath) {
function planClaudeLegacyInstall(context) {
const adapter = getInstallTargetAdapter('claude');
const targetRoot = adapter.resolveRoot({ homeDir: context.homeDir });
const rulesDir = context.claudeRulesDir || path.join(targetRoot, 'rules');
const rulesDir = context.claudeRulesDir || path.join(targetRoot, 'rules', CLAUDE_ECC_NAMESPACE);
const installStatePath = adapter.getInstallStatePath({ homeDir: context.homeDir });
const operations = [];
const warnings = [];

View File

@@ -1,4 +1,46 @@
const { createInstallTargetAdapter } = require('./helpers');
const path = require('path');
const {
createInstallTargetAdapter,
createRemappedOperation,
isForeignPlatformPath,
normalizeRelativePath,
} = require('./helpers');
const CLAUDE_ECC_NAMESPACE = 'ecc';
function getClaudeManagedDestinationPath(adapter, sourceRelativePath, input) {
const normalizedSourcePath = normalizeRelativePath(sourceRelativePath);
const targetRoot = adapter.resolveRoot(input);
if (normalizedSourcePath === 'rules') {
return path.join(targetRoot, 'rules', CLAUDE_ECC_NAMESPACE);
}
if (normalizedSourcePath.startsWith('rules/')) {
return path.join(
targetRoot,
'rules',
CLAUDE_ECC_NAMESPACE,
normalizedSourcePath.slice('rules/'.length)
);
}
if (normalizedSourcePath === 'skills') {
return path.join(targetRoot, 'skills', CLAUDE_ECC_NAMESPACE);
}
if (normalizedSourcePath.startsWith('skills/')) {
return path.join(
targetRoot,
'skills',
CLAUDE_ECC_NAMESPACE,
normalizedSourcePath.slice('skills/'.length)
);
}
return null;
}
module.exports = createInstallTargetAdapter({
id: 'claude-home',
@@ -7,4 +49,39 @@ module.exports = createInstallTargetAdapter({
rootSegments: ['.claude'],
installStatePathSegments: ['ecc', 'install-state.json'],
nativeRootRelativePath: '.claude-plugin',
planOperations(input, adapter) {
const modules = Array.isArray(input.modules)
? input.modules
: (input.module ? [input.module] : []);
const planningInput = {
repoRoot: input.repoRoot,
projectRoot: input.projectRoot,
homeDir: input.homeDir,
};
return modules.flatMap(module => {
const paths = Array.isArray(module.paths) ? module.paths : [];
return paths
.filter(p => !isForeignPlatformPath(p, adapter.target))
.map(sourceRelativePath => {
const managedDestinationPath = getClaudeManagedDestinationPath(
adapter,
sourceRelativePath,
planningInput
);
if (managedDestinationPath) {
return createRemappedOperation(
adapter,
module.id,
sourceRelativePath,
managedDestinationPath,
{ strategy: 'preserve-relative-path' }
);
}
return adapter.createScaffoldOperation(module.id, sourceRelativePath, planningInput);
});
});
},
});

View File

@@ -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')));

View File

@@ -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';

View File

@@ -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');

View File

@@ -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');

View File

@@ -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}`);