fix: flatten Claude skill installs (#2582)

Flatten managed Claude skill destinations, preserve user-owned conflicts, and migrate legacy nested installs through the lifecycle tooling.
This commit is contained in:
Affaan Mustafa 2026-07-26 03:20:06 -07:00 committed by GitHub
parent 71438391e8
commit f3afd59045
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 1620 additions and 119 deletions

View file

@ -32,8 +32,8 @@ Usage: install.sh [--target <${LEGACY_INSTALL_TARGETS.join('|')}>] [--dry-run] [
install.sh [--dry-run] [--json] --config <path>
Targets:
claude (default) - Install ECC into ~/.claude/ with managed rules/skills under rules/ecc and skills/ecc
claude-project - Install ECC into ./.claude/ (per-project) with managed rules/skills under rules/ecc and skills/ecc
claude (default) - Install ECC into ~/.claude/ with managed rules under rules/ecc and flat skills under skills/
claude-project - Install ECC into ./.claude/ (per-project) with managed rules under rules/ecc and flat skills under skills/
cursor - Install rules, hooks, and bundled Cursor configs to ./.cursor/
antigravity - Install rules, workflows, skills, and agents to ./.agent/
codex - Install shared agents/config into ~/.codex/
@ -102,7 +102,10 @@ function printHumanPlan(plan, dryRun) {
console.log(`Excluded modules: ${plan.excludedModuleIds.join(', ')}`);
}
}
console.log(`Operations: ${plan.operations.length}`);
console.log(`${dryRun ? 'Operations' : 'Applied operations'}: ${plan.operations.length}`);
if (Array.isArray(plan.skippedOperations) && plan.skippedOperations.length > 0) {
console.log(`Skipped operations: ${plan.skippedOperations.length}`);
}
if (plan.warnings.length > 0) {
console.log('\nWarnings:');
@ -111,11 +114,18 @@ function printHumanPlan(plan, dryRun) {
}
}
console.log('\nPlanned file operations:');
console.log(`\n${dryRun ? 'Planned' : 'Applied'} file operations:`);
for (const operation of plan.operations) {
console.log(`- ${operation.sourceRelativePath} -> ${operation.destinationPath}`);
}
if (Array.isArray(plan.skippedOperations) && plan.skippedOperations.length > 0) {
console.log('\nSkipped file operations:');
for (const operation of plan.skippedOperations) {
console.log(`- ${operation.sourceRelativePath} -> ${operation.destinationPath}`);
}
}
if (!dryRun) {
console.log(`\nDone. Install-state written to ${plan.installStatePath}`);
}
@ -135,7 +145,10 @@ function main() {
findDefaultInstallConfigPath,
loadInstallConfig,
} = require('./lib/install/config');
const { applyInstallPlan } = require('./lib/install-executor');
const {
applyInstallPlan,
previewInstallPlan,
} = require('./lib/install-executor');
const { createInstallPlanFromRequest } = require('./lib/install/runtime');
const defaultConfigPath = options.configPath || options.languages.length > 0
? null
@ -147,13 +160,14 @@ function main() {
...options,
config,
});
const plan = createInstallPlanFromRequest(request, {
const rawPlan = createInstallPlanFromRequest(request, {
projectRoot: process.cwd(),
homeDir: process.env.HOME || os.homedir(),
claudeRulesDir: process.env.CLAUDE_RULES_DIR || null,
});
if (options.dryRun) {
const plan = previewInstallPlan(rawPlan);
if (options.json) {
console.log(JSON.stringify({ dryRun: true, plan }, null, 2));
} else {
@ -162,7 +176,7 @@ function main() {
return;
}
const result = applyInstallPlan(plan);
const result = applyInstallPlan(rawPlan);
if (options.json) {
console.log(JSON.stringify({ dryRun: false, result }, null, 2));
} else {