mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-05-01 09:24:28 +08:00
fix: namespace claude managed install paths
This commit is contained in:
parent
08d6c82989
commit
e381c8d8a8
63
README.md
63
README.md
@ -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.
|
> 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.
|
> 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)
|
# Install dependencies (pick your package manager)
|
||||||
npm install # or: pnpm install | yarn install | bun install
|
npm install # or: pnpm install | yarn install | bun install
|
||||||
|
|
||||||
# Plugin install path: copy only rules
|
# Plugin install path: copy only ECC rules into an ECC-owned namespace
|
||||||
mkdir -p ~/.claude/rules
|
mkdir -p ~/.claude/rules/ecc
|
||||||
cp -R rules/common ~/.claude/rules/
|
cp -R rules/common ~/.claude/rules/ecc/
|
||||||
cp -R rules/typescript ~/.claude/rules/
|
cp -R rules/typescript ~/.claude/rules/ecc/
|
||||||
|
|
||||||
# Fully manual ECC install path (use this instead of /plugin install)
|
# Fully manual ECC install path (use this instead of /plugin install)
|
||||||
# ./install.sh --profile full
|
# ./install.sh --profile full
|
||||||
@ -271,10 +271,10 @@ cp -R rules/typescript ~/.claude/rules/
|
|||||||
```powershell
|
```powershell
|
||||||
# Windows PowerShell
|
# Windows PowerShell
|
||||||
|
|
||||||
# Plugin install path: copy only rules
|
# Plugin install path: copy only ECC rules into an ECC-owned namespace
|
||||||
New-Item -ItemType Directory -Force -Path "$HOME/.claude/rules" | Out-Null
|
New-Item -ItemType Directory -Force -Path "$HOME/.claude/rules/ecc" | Out-Null
|
||||||
Copy-Item -Recurse rules/common "$HOME/.claude/rules/"
|
Copy-Item -Recurse rules/common "$HOME/.claude/rules/ecc/"
|
||||||
Copy-Item -Recurse rules/typescript "$HOME/.claude/rules/"
|
Copy-Item -Recurse rules/typescript "$HOME/.claude/rules/ecc/"
|
||||||
|
|
||||||
# Fully manual ECC install path (use this instead of /plugin install)
|
# Fully manual ECC install path (use this instead of /plugin install)
|
||||||
# .\install.ps1 --profile full
|
# .\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.
|
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:
|
- **Manual installer / CLI path:** from the repo root, preview removal first:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@ -574,7 +574,7 @@ everything-claude-code/
|
|||||||
| |-- verify.md # /verify - Prefer the verification-loop skill
|
| |-- verify.md # /verify - Prefer the verification-loop skill
|
||||||
| |-- orchestrate.md # /orchestrate - Prefer dmux-workflows or multi-workflow
|
| |-- 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
|
| |-- README.md # Structure overview and installation guide
|
||||||
| |-- common/ # Language-agnostic principles
|
| |-- common/ # Language-agnostic principles
|
||||||
| | |-- coding-style.md # Immutability, file organization
|
| | |-- 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
|
> git clone https://github.com/affaan-m/everything-claude-code.git
|
||||||
>
|
>
|
||||||
> # Option A: User-level rules (applies to all projects)
|
> # Option A: User-level rules (applies to all projects)
|
||||||
> mkdir -p ~/.claude/rules
|
> mkdir -p ~/.claude/rules/ecc
|
||||||
> cp -r everything-claude-code/rules/common ~/.claude/rules/
|
> cp -r everything-claude-code/rules/common ~/.claude/rules/ecc/
|
||||||
> cp -r everything-claude-code/rules/typescript ~/.claude/rules/ # pick your stack
|
> cp -r everything-claude-code/rules/typescript ~/.claude/rules/ecc/ # pick your stack
|
||||||
> cp -r everything-claude-code/rules/python ~/.claude/rules/
|
> cp -r everything-claude-code/rules/python ~/.claude/rules/ecc/
|
||||||
> cp -r everything-claude-code/rules/golang ~/.claude/rules/
|
> cp -r everything-claude-code/rules/golang ~/.claude/rules/ecc/
|
||||||
> cp -r everything-claude-code/rules/php ~/.claude/rules/
|
> cp -r everything-claude-code/rules/php ~/.claude/rules/ecc/
|
||||||
>
|
>
|
||||||
> # Option B: Project-level rules (applies to current project only)
|
> # Option B: Project-level rules (applies to current project only)
|
||||||
> mkdir -p .claude/rules
|
> mkdir -p .claude/rules/ecc
|
||||||
> cp -r everything-claude-code/rules/common .claude/rules/
|
> cp -r everything-claude-code/rules/common .claude/rules/ecc/
|
||||||
> cp -r everything-claude-code/rules/typescript .claude/rules/ # pick your stack
|
> 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/
|
cp everything-claude-code/agents/*.md ~/.claude/agents/
|
||||||
|
|
||||||
# Copy rules directories (common + language-specific)
|
# Copy rules directories (common + language-specific)
|
||||||
mkdir -p ~/.claude/rules
|
mkdir -p ~/.claude/rules/ecc
|
||||||
cp -r everything-claude-code/rules/common ~/.claude/rules/
|
cp -r everything-claude-code/rules/common ~/.claude/rules/ecc/
|
||||||
cp -r everything-claude-code/rules/typescript ~/.claude/rules/ # pick your stack
|
cp -r everything-claude-code/rules/typescript ~/.claude/rules/ecc/ # pick your stack
|
||||||
cp -r everything-claude-code/rules/python ~/.claude/rules/
|
cp -r everything-claude-code/rules/python ~/.claude/rules/ecc/
|
||||||
cp -r everything-claude-code/rules/golang ~/.claude/rules/
|
cp -r everything-claude-code/rules/golang ~/.claude/rules/ecc/
|
||||||
cp -r everything-claude-code/rules/php ~/.claude/rules/
|
cp -r everything-claude-code/rules/php ~/.claude/rules/ecc/
|
||||||
|
|
||||||
# Copy skills first (primary workflow surface)
|
# Copy skills first (primary workflow surface)
|
||||||
# Recommended (new users): core/general skills only
|
# Recommended (new users): core/general skills only
|
||||||
cp -r everything-claude-code/.agents/skills/* ~/.claude/skills/
|
mkdir -p ~/.claude/skills/ecc
|
||||||
cp -r everything-claude-code/skills/search-first ~/.claude/skills/
|
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
|
# Optional: add niche/framework-specific skills only when needed
|
||||||
# for s in django-patterns django-tdd laravel-patterns springboot-patterns; do
|
# 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
|
# done
|
||||||
|
|
||||||
# Optional: keep maintained slash-command compatibility during migration
|
# 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/
|
cp everything-claude-code/agents/*.md ~/.claude/agents/
|
||||||
|
|
||||||
# Just rules
|
# Just rules
|
||||||
mkdir -p ~/.claude/rules/
|
mkdir -p ~/.claude/rules/ecc/
|
||||||
cp -r everything-claude-code/rules/common ~/.claude/rules/
|
cp -r everything-claude-code/rules/common ~/.claude/rules/ecc/
|
||||||
```
|
```
|
||||||
|
|
||||||
Each component is fully independent.
|
Each component is fully independent.
|
||||||
|
|||||||
@ -640,7 +640,7 @@ Suggested operation shape:
|
|||||||
"kind": "copy",
|
"kind": "copy",
|
||||||
"moduleId": "rules-core",
|
"moduleId": "rules-core",
|
||||||
"source": "rules/common/coding-style.md",
|
"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",
|
"ownership": "managed",
|
||||||
"overwritePolicy": "replace"
|
"overwritePolicy": "replace"
|
||||||
}
|
}
|
||||||
@ -711,7 +711,7 @@ Suggested payload:
|
|||||||
{
|
{
|
||||||
"kind": "copy",
|
"kind": "copy",
|
||||||
"moduleId": "rules-core",
|
"moduleId": "rules-core",
|
||||||
"destination": "/Users/example/.claude/rules/common/coding-style.md",
|
"destination": "/Users/example/.claude/rules/ecc/common/coding-style.md",
|
||||||
"digest": "sha256:..."
|
"digest": "sha256:..."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@ -27,7 +27,7 @@ Usage: install.sh [--target <${LEGACY_INSTALL_TARGETS.join('|')}>] [--dry-run] [
|
|||||||
install.sh [--dry-run] [--json] --config <path>
|
install.sh [--dry-run] [--json] --config <path>
|
||||||
|
|
||||||
Targets:
|
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/
|
cursor - Install rules, hooks, and bundled Cursor configs to ./.cursor/
|
||||||
antigravity - Install rules, workflows, skills, and agents to ./.agent/
|
antigravity - Install rules, workflows, skills, and agents to ./.agent/
|
||||||
|
|
||||||
|
|||||||
@ -14,6 +14,7 @@ const {
|
|||||||
const { getInstallTargetAdapter } = require('./install-targets/registry');
|
const { getInstallTargetAdapter } = require('./install-targets/registry');
|
||||||
|
|
||||||
const LANGUAGE_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/;
|
const LANGUAGE_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/;
|
||||||
|
const CLAUDE_ECC_NAMESPACE = 'ecc';
|
||||||
const EXCLUDED_GENERATED_SOURCE_SUFFIXES = [
|
const EXCLUDED_GENERATED_SOURCE_SUFFIXES = [
|
||||||
'/ecc-install-state.json',
|
'/ecc-install-state.json',
|
||||||
'/ecc/install-state.json',
|
'/ecc/install-state.json',
|
||||||
@ -264,7 +265,7 @@ function isDirectoryNonEmpty(dirPath) {
|
|||||||
function planClaudeLegacyInstall(context) {
|
function planClaudeLegacyInstall(context) {
|
||||||
const adapter = getInstallTargetAdapter('claude');
|
const adapter = getInstallTargetAdapter('claude');
|
||||||
const targetRoot = adapter.resolveRoot({ homeDir: context.homeDir });
|
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 installStatePath = adapter.getInstallStatePath({ homeDir: context.homeDir });
|
||||||
const operations = [];
|
const operations = [];
|
||||||
const warnings = [];
|
const warnings = [];
|
||||||
|
|||||||
@ -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({
|
module.exports = createInstallTargetAdapter({
|
||||||
id: 'claude-home',
|
id: 'claude-home',
|
||||||
@ -7,4 +49,39 @@ module.exports = createInstallTargetAdapter({
|
|||||||
rootSegments: ['.claude'],
|
rootSegments: ['.claude'],
|
||||||
installStatePathSegments: ['ecc', 'install-state.json'],
|
installStatePathSegments: ['ecc', 'install-state.json'],
|
||||||
nativeRootRelativePath: '.claude-plugin',
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -79,9 +79,11 @@ function writeManifestSourceFixture(root) {
|
|||||||
kind: 'fixture',
|
kind: 'fixture',
|
||||||
description: 'Fixture module',
|
description: 'Fixture module',
|
||||||
paths: [
|
paths: [
|
||||||
|
'rules',
|
||||||
'src',
|
'src',
|
||||||
'standalone.txt',
|
'standalone.txt',
|
||||||
'missing.txt',
|
'missing.txt',
|
||||||
|
'skills/demo',
|
||||||
path.join('runtime', 'ecc', 'install-state.json'),
|
path.join('runtime', 'ecc', 'install-state.json'),
|
||||||
'.claude-plugin',
|
'.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', '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', '.git', 'ignored.js'), 'console.log("ignored");\n');
|
||||||
writeFile(root, path.join('src', 'nested', 'ecc-install-state.json'), '{}\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, 'standalone.txt', 'standalone\n');
|
||||||
writeFile(root, path.join('runtime', 'ecc', 'install-state.json'), '{}\n');
|
writeFile(root, path.join('runtime', 'ecc', 'install-state.json'), '{}\n');
|
||||||
writeJson(root, path.join('.claude-plugin', 'plugin.json'), { name: 'fixture' });
|
writeJson(root, path.join('.claude-plugin', 'plugin.json'), { name: 'fixture' });
|
||||||
@ -194,6 +198,35 @@ function runTests() {
|
|||||||
}
|
}
|
||||||
})) passed++; else failed++;
|
})) 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', () => {
|
if (test('plans Cursor legacy assets and JSON merge payloads', () => {
|
||||||
const sourceRoot = createTempDir('install-executor-source-');
|
const sourceRoot = createTempDir('install-executor-source-');
|
||||||
const projectRoot = createTempDir('install-executor-project-');
|
const projectRoot = createTempDir('install-executor-project-');
|
||||||
@ -307,6 +340,8 @@ function runTests() {
|
|||||||
));
|
));
|
||||||
assert.ok(normalizedSources.includes('src/app.js'));
|
assert.ok(normalizedSources.includes('src/app.js'));
|
||||||
assert.ok(normalizedSources.includes('src/nested/feature.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('standalone.txt'));
|
||||||
assert.ok(normalizedSources.includes('.claude-plugin/plugin.json'));
|
assert.ok(normalizedSources.includes('.claude-plugin/plugin.json'));
|
||||||
assert.ok(!normalizedSources.includes('missing.txt'));
|
assert.ok(!normalizedSources.includes('missing.txt'));
|
||||||
@ -318,6 +353,14 @@ function runTests() {
|
|||||||
operation.sourceRelativePath === path.join('.claude-plugin', 'plugin.json')
|
operation.sourceRelativePath === path.join('.claude-plugin', 'plugin.json')
|
||||||
&& operation.destinationPath === path.join(homeDir, '.claude', '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.deepStrictEqual(plan.warnings, ['fixture warning']);
|
||||||
assert.strictEqual(plan.statePreview.request.profile, 'minimal');
|
assert.strictEqual(plan.statePreview.request.profile, 'minimal');
|
||||||
assert.deepStrictEqual(plan.statePreview.request.includeComponents, ['capability:fixture']);
|
assert.deepStrictEqual(plan.statePreview.request.includeComponents, ['capability:fixture']);
|
||||||
@ -369,6 +412,8 @@ function runTests() {
|
|||||||
const applied = applyInstallPlan(plan);
|
const applied = applyInstallPlan(plan);
|
||||||
|
|
||||||
assert.strictEqual(applied.applied, true);
|
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', 'src', 'app.js')));
|
||||||
assert.ok(fs.existsSync(path.join(homeDir, '.claude', 'standalone.txt')));
|
assert.ok(fs.existsSync(path.join(homeDir, '.claude', 'standalone.txt')));
|
||||||
assert.ok(fs.existsSync(path.join(homeDir, '.claude', 'plugin.json')));
|
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'));
|
assert.strictEqual(statePath, path.join(homeDir, '.claude', 'ecc', 'install-state.json'));
|
||||||
})) passed++; else failed++;
|
})) 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', () => {
|
if (test('plans scaffold operations and flattens native target roots', () => {
|
||||||
const repoRoot = path.join(__dirname, '..', '..');
|
const repoRoot = path.join(__dirname, '..', '..');
|
||||||
const projectRoot = '/workspace/app';
|
const projectRoot = '/workspace/app';
|
||||||
|
|||||||
@ -576,10 +576,10 @@ function runTests() {
|
|||||||
|
|
||||||
const claudeRoot = path.join(homeDir, '.claude');
|
const claudeRoot = path.join(homeDir, '.claude');
|
||||||
// Security skill should be installed (from --with)
|
// 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');
|
'Should install security-review skill from --with');
|
||||||
// Core profile modules should be installed
|
// 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');
|
'Should install core rules');
|
||||||
|
|
||||||
// Install state should record include/exclude
|
// Install state should record include/exclude
|
||||||
@ -615,12 +615,12 @@ function runTests() {
|
|||||||
|
|
||||||
const claudeRoot = path.join(homeDir, '.claude');
|
const claudeRoot = path.join(homeDir, '.claude');
|
||||||
// Orchestration skills should NOT be installed (from --without)
|
// 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');
|
'Should not install orchestration skills');
|
||||||
// Developer profile base modules should be installed
|
// 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');
|
'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');
|
'Should install workflow skills');
|
||||||
|
|
||||||
const statePath = path.join(claudeRoot, 'ecc', 'install-state.json');
|
const statePath = path.join(claudeRoot, 'ecc', 'install-state.json');
|
||||||
@ -653,10 +653,10 @@ function runTests() {
|
|||||||
|
|
||||||
const claudeRoot = path.join(homeDir, '.claude');
|
const claudeRoot = path.join(homeDir, '.claude');
|
||||||
// framework-language skill (from lang:typescript) should be installed
|
// 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');
|
'Should install framework-language skills');
|
||||||
// Its dependencies should be installed
|
// 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');
|
'Should install dependency rules-core');
|
||||||
|
|
||||||
const statePath = path.join(claudeRoot, 'ecc', 'install-state.json');
|
const statePath = path.join(claudeRoot, 'ecc', 'install-state.json');
|
||||||
|
|||||||
@ -94,13 +94,13 @@ function runTests() {
|
|||||||
assert.strictEqual(result.code, 0, result.stderr);
|
assert.strictEqual(result.code, 0, result.stderr);
|
||||||
|
|
||||||
const claudeRoot = path.join(homeDir, '.claude');
|
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, 'rules', 'typescript', 'testing.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, 'commands', 'plan.md')));
|
||||||
assert.ok(fs.existsSync(path.join(claudeRoot, 'scripts', 'hooks', 'session-end.js')));
|
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, 'scripts', 'lib', 'utils.js')));
|
||||||
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')));
|
||||||
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')));
|
||||||
assert.ok(fs.existsSync(path.join(claudeRoot, 'plugin.json')));
|
assert.ok(fs.existsSync(path.join(claudeRoot, 'plugin.json')));
|
||||||
|
|
||||||
const statePath = path.join(homeDir, '.claude', 'ecc', 'install-state.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.resolution.selectedModules.includes('framework-language'));
|
||||||
assert.ok(
|
assert.ok(
|
||||||
state.operations.some(operation => (
|
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'
|
'Should record common rule file operation'
|
||||||
);
|
);
|
||||||
@ -299,7 +299,7 @@ function runTests() {
|
|||||||
assert.strictEqual(result.code, 0, result.stderr);
|
assert.strictEqual(result.code, 0, result.stderr);
|
||||||
|
|
||||||
const claudeRoot = path.join(homeDir, '.claude');
|
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, 'agents', 'architect.md')));
|
||||||
assert.ok(fs.existsSync(path.join(claudeRoot, 'commands', 'plan.md')));
|
assert.ok(fs.existsSync(path.join(claudeRoot, 'commands', 'plan.md')));
|
||||||
assert.ok(fs.existsSync(path.join(claudeRoot, 'hooks', 'hooks.json')));
|
assert.ok(fs.existsSync(path.join(claudeRoot, 'hooks', 'hooks.json')));
|
||||||
@ -324,6 +324,32 @@ function runTests() {
|
|||||||
}
|
}
|
||||||
})) passed++; else failed++;
|
})) 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', () => {
|
if (test('installs antigravity manifest profiles while skipping only unsupported modules', () => {
|
||||||
const homeDir = createTempDir('install-apply-home-');
|
const homeDir = createTempDir('install-apply-home-');
|
||||||
const projectDir = createTempDir('install-apply-project-');
|
const projectDir = createTempDir('install-apply-project-');
|
||||||
@ -727,8 +753,8 @@ function runTests() {
|
|||||||
const result = run(['--config', configPath], { cwd: projectDir, homeDir });
|
const result = run(['--config', configPath], { cwd: projectDir, homeDir });
|
||||||
assert.strictEqual(result.code, 0, result.stderr);
|
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', 'ecc', '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', 'dmux-workflows', 'SKILL.md')));
|
||||||
|
|
||||||
const state = readJson(path.join(homeDir, '.claude', 'ecc', 'install-state.json'));
|
const state = readJson(path.join(homeDir, '.claude', 'ecc', 'install-state.json'));
|
||||||
assert.strictEqual(state.request.profile, 'developer');
|
assert.strictEqual(state.request.profile, 'developer');
|
||||||
@ -759,8 +785,8 @@ function runTests() {
|
|||||||
const result = run([], { cwd: projectDir, homeDir });
|
const result = run([], { cwd: projectDir, homeDir });
|
||||||
assert.strictEqual(result.code, 0, result.stderr);
|
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', 'ecc', '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', 'dmux-workflows', 'SKILL.md')));
|
||||||
|
|
||||||
const state = readJson(path.join(homeDir, '.claude', 'ecc', 'install-state.json'));
|
const state = readJson(path.join(homeDir, '.claude', 'ecc', 'install-state.json'));
|
||||||
assert.strictEqual(state.request.profile, 'developer');
|
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.includes('Start with `rules/common` plus one language or framework pack you actually use.'),
|
||||||
'README should steer users away from copying every rules directory'
|
'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++;
|
})) passed++; else failed++;
|
||||||
|
|
||||||
console.log(`\nResults: Passed: ${passed}, Failed: ${failed}`);
|
console.log(`\nResults: Passed: ${passed}, Failed: ${failed}`);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user