docs/SELECTIVE-INSTALL-ARCHITECTURE.md
This document turns the March 11 mega-plan selective-install requirement into a concrete ECC 2.0 discovery design.
The goal is not just "fewer files copied during install." The actual target is an install system that can answer, deterministically:
That is the missing contract between ECC 1.x installation and an ECC 2.0 control plane.
The first selective-install substrate already exists in-repo:
manifests/install-modules.jsonmanifests/install-profiles.jsonschemas/install-modules.schema.jsonschemas/install-profiles.schema.jsonschemas/install-state.schema.jsonscripts/ci/validate-install-manifests.jsscripts/lib/install-manifests.jsscripts/lib/install/request.jsscripts/lib/install/runtime.jsscripts/lib/install/apply.jsscripts/lib/install-targets/scripts/lib/install-state.jsscripts/lib/install-executor.jsscripts/lib/install-lifecycle.jsscripts/ecc.jsscripts/install-apply.jsscripts/install-plan.jsscripts/list-installed.jsscripts/doctor.jsCurrent capabilities:
ecc CLI routing install, planning, and lifecycle commandslist-installed, doctor, repair,
and uninstallCurrent limitation:
ecc-install compatibility still points at install.shpackage.jsonThe current installer stack is already much healthier than the original language-first shell installer, but it still concentrates too much responsibility in a few files.
The runtime flow today is:
install.sh
thin shell wrapper that resolves the real package rootscripts/install-apply.js
user-facing installer CLI for legacy and manifest modesscripts/lib/install/request.js
CLI parsing plus canonical request normalizationscripts/lib/install/runtime.js
runtime dispatch from normalized requests into install plansscripts/lib/install-executor.js
argument translation, legacy compatibility, operation materialization,
filesystem mutation, and install-state writescripts/lib/install-manifests.js
module/profile catalog loading plus dependency expansionscripts/lib/install-targets/
target root and destination-path scaffoldingscripts/lib/install-state.js
schema-backed install-state read/writescripts/lib/install-lifecycle.js
doctor/repair/uninstall behavior derived from stored operationsThat is enough to prove the selective-install substrate, but not enough to make the installer architecture feel settled.
--profile and --modulesecc and
install-apply.jsinstall-executor.js is smaller than before, but still carrying too many
planning and materialization layers at once.
The request boundary is now extracted, but legacy request translation,
manifest-plan expansion, and operation materialization still live together.install-manifests.js resolves modules, but the final install operation set
is still partly constructed in executor-specific logic.The next architectural step is to separate the installer into explicit layers, with each layer returning stable data instead of immediately mutating files.
The desired install pipeline is:
The main idea is simple:
Responsibility:
Should not own:
Suggested files:
scripts/ecc.js
scripts/install-apply.js
scripts/install-plan.js
scripts/doctor.js
scripts/repair.js
scripts/uninstall.js
These stay as entrypoints, but become thin wrappers around library modules.
Responsibility:
Suggested canonical request:
{
"mode": "manifest",
"target": "cursor",
"profile": "developer",
"modules": [],
"legacyLanguages": [],
"dryRun": false
}
or, in compatibility mode:
{
"mode": "legacy-compat",
"target": "claude",
"profile": null,
"modules": [],
"legacyLanguages": ["typescript", "python"],
"dryRun": false
}
This lets the rest of the pipeline ignore whether the request came from old or new CLI syntax.
Responsibility:
This layer should stay pure and read-only.
It should not know:
Current nearest file:
scripts/lib/install-manifests.jsSuggested split:
scripts/lib/install/catalog.js
scripts/lib/install/resolve-request.js
scripts/lib/install/resolve-modules.js
Responsibility:
This is where target-specific meaning should live.
Examples:
~/.claude.cursor root children differently from rulesCurrent nearest files:
scripts/lib/install-targets/helpers.jsscripts/lib/install-targets/registry.jsSuggested evolution:
scripts/lib/install/targets/registry.js
scripts/lib/install/targets/claude-home.js
scripts/lib/install/targets/cursor-project.js
scripts/lib/install/targets/antigravity-project.js
Each adapter should eventually expose more than resolveRoot.
It should own path and strategy mapping for its target family.
Responsibility:
copy-filecopy-treemerge-jsonrender-templateremoveThis is the missing architectural seam in the current installer.
Today, operations are partly scaffold-level and partly executor-specific. ECC 2.0 should make operation planning a standalone phase so that:
plan becomes a true preview of executiondoctor can validate intended behavior, not just current filesrepair can rebuild exact missing work safelyuninstall can reverse only managed operationsResponsibility:
This layer should not decide what to do. It should only decide how to apply a provided operation kind safely.
Current nearest file:
scripts/lib/install-executor.jsRecommended refactor:
scripts/lib/install/executor/apply-plan.js
scripts/lib/install/executor/apply-copy.js
scripts/lib/install/executor/apply-merge-json.js
scripts/lib/install/executor/apply-remove.js
That turns executor logic from one large branching runtime into a set of small operation handlers.
Responsibility:
Current nearest file:
scripts/lib/install-state.jsThis layer is already close to the right shape. The main remaining change is to store richer operation metadata once merge/generate semantics are real.
Responsibility:
list-installed: inspect state onlydoctor: compare desired/install-state view against current filesystemrepair: regenerate a plan from state and reapply safe operationsuninstall: remove only ECC-owned outputsCurrent nearest file:
scripts/lib/install-lifecycle.jsThis layer should eventually operate on operation kinds and ownership policies,
not just on raw copy-file records.
The clean modular end state should look roughly like this:
scripts/lib/install/
catalog.js
request.js
resolve-modules.js
plan-operations.js
state-store.js
targets/
registry.js
claude-home.js
cursor-project.js
antigravity-project.js
codex-home.js
opencode-home.js
executor/
apply-plan.js
apply-copy.js
apply-merge-json.js
apply-render-template.js
apply-remove.js
lifecycle/
discover.js
doctor.js
repair.js
uninstall.js
This is not a packaging split. It is a code-ownership split inside the current repo so each layer has one job.
The lowest-risk migration path is evolutionary, not a rewrite.
install.sh as the public compatibility shimscripts/ecc.js as the unified CLIscripts/lib/install-state.js as the starting point for the state storescripts/lib/install-executor.jsIf the goal is ECC 2.0 and not just “working enough,” the next modularization steps should be:
install-executor.js into request normalization, operation planning,
and execution modulesrepair and uninstall operate on typed operation handlers rather than
only plain copy-file recordsToday ECC still behaves like a broad payload copier:
install.sh is language-first and target-branch-heavypackage.jsonThat creates the problems already called out in the mega plan:
Selective install should be modeled as:
That means ECC 2.0 needs two contracts, not one:
The current repo only had the first half in early form. The current repo now has the first full vertical slice, but not the full target-specific semantics.
everything-claude-code as the canonical source repo.install.sh flows during migration.The module catalog is the canonical content graph.
Current fields already implemented:
idkinddescriptionpathstargetsdependenciesdefaultInstallcoststabilityFields still needed for ECC 2.0:
installStrategy
for example copy, flatten-rules, generate, merge-configownership
whether ECC fully owns the target path or only generated files under itpathMode
for example preserve, flatten, target-templateconflicts
modules or path families that cannot coexist on one targetpublish
whether the module is packaged by default, optional, or generated post-installSuggested future shape:
{
"id": "hooks-runtime",
"kind": "hooks",
"paths": ["hooks", "scripts/hooks"],
"targets": ["claude", "cursor", "opencode"],
"dependencies": [],
"installStrategy": "copy",
"pathMode": "preserve",
"ownership": "managed",
"defaultInstall": true,
"cost": "medium",
"stability": "stable"
}
Profiles stay thin.
They should express user intent, not duplicate target logic.
Current examples already implemented:
coredevelopersecurityresearchfullFields still needed:
defaultTargetsrecommendedForexcludesrequiresConfirmationThat lets ECC 2.0 say things like:
developer is the recommended default for Claude and Cursorresearch may be heavy for narrow local installsfull is allowed but not defaultThis is the main missing layer.
The module graph should not know:
That belongs to a target adapter.
Suggested interface:
type InstallTargetAdapter = {
id: string;
kind: "home" | "project";
supports(target: string): boolean;
resolveRoot(input?: string): Promise<string>;
planOperations(input: InstallOperationInput): Promise<InstallOperation[]>;
validate?(input: InstallOperationInput): Promise<ValidationIssue[]>;
};
Suggested first adapters:
claude-home
writes into ~/.claude/...cursor-project
writes into ./.cursor/...antigravity-project
writes into ./.agent/...codex-home
lateropencode-home
laterThis matches the same pattern already proposed in the session-adapter discovery doc: canonical contract first, harness-specific adapter second.
The current scripts/install-plan.js CLI proves the repo can resolve requested
modules into a filtered module set.
ECC 2.0 needs the next layer: operation planning.
Suggested phases:
--target--profile--modulesSuggested operation shape:
{
"kind": "copy",
"moduleId": "rules-core",
"source": "rules/common/coding-style.md",
"destination": "/Users/example/.claude/rules/common/coding-style.md",
"ownership": "managed",
"overwritePolicy": "replace"
}
Other operation kinds:
copycopy-treeflatten-copyrender-templatemerge-jsonmerge-jsoncmkdirremoveInstall-state is the durable contract that ECC 1.x is missing.
Suggested path conventions:
~/.claude/ecc/install-state.json./.cursor/ecc-install-state.json./.agent/ecc-install-state.json~/.codex/ecc-install-state.jsonSuggested payload:
{
"schemaVersion": "ecc.install.v1",
"installedAt": "2026-03-13T00:00:00Z",
"lastValidatedAt": "2026-03-13T00:00:00Z",
"target": {
"id": "claude-home",
"root": "/Users/example/.claude"
},
"request": {
"profile": "developer",
"modules": ["orchestration"],
"legacyLanguages": ["typescript", "python"]
},
"resolution": {
"selectedModules": [
"rules-core",
"agents-core",
"commands-core",
"hooks-runtime",
"platform-configs",
"workflow-quality",
"framework-language",
"database",
"orchestration"
],
"skippedModules": []
},
"source": {
"repoVersion": "1.10.0",
"repoCommit": "git-sha",
"manifestVersion": 1
},
"operations": [
{
"kind": "copy",
"moduleId": "rules-core",
"destination": "/Users/example/.claude/rules/common/coding-style.md",
"digest": "sha256:..."
}
]
}
State requirements:
The following commands are the lifecycle surface for install-state:
ecc list-installedecc uninstallecc doctorecc repairCurrent implementation status:
ecc list-installed routes to node scripts/list-installed.jsecc uninstall routes to node scripts/uninstall.jsecc doctor routes to node scripts/doctor.jsecc repair routes to node scripts/repair.jslist-installedResponsibilities:
uninstallResponsibilities:
doctorResponsibilities:
repairResponsibilities:
Current install.sh accepts:
--target <claude|cursor|antigravity>That behavior cannot disappear in one cut because users already depend on it.
ECC 2.0 should translate legacy language arguments into a compatibility request.
Suggested approach:
rules-corelegacyMode: trueExample:
{
"request": {
"legacyMode": true,
"legacyLanguages": ["typescript", "python"]
}
}
This keeps old behavior available while moving all installs onto the same state contract.
The current npm package still publishes a broad payload through package.json.
ECC 2.0 should improve this carefully.
Recommended sequence:
Why:
Possible later directions:
Those are Phase 3 or later, not prerequisites for profile-aware installs.
Suggested next files:
scripts/lib/install-targets/
claude-home.js
cursor-project.js
antigravity-project.js
registry.js
scripts/lib/install-state.js
scripts/ecc.js
scripts/install-apply.js
scripts/list-installed.js
scripts/uninstall.js
scripts/doctor.js
scripts/repair.js
tests/lib/install-targets.test.js
tests/lib/install-state.test.js
tests/lib/install-lifecycle.test.js
install.sh can remain the user-facing entry point during migration, but it
should become a thin shell around a Node-based planner and executor rather than
keep growing per-target shell branches.
ecc.install.v1 state schemaclaude-home adaptercursor-project adapterantigravity-project adapterinstall.sh to argument parsing plus adapter invocationecc-install should become a thin alias for ecc installpackage.json publish surfacecodex-homeopencode-homeThe highest-signal next implementation moves in this repo are:
ecc-install remains separate or becomes ecc installecc CLI routing and compatibility guaranteesplatform-configs always install with core, or be split into
smaller target-specific modules?Treat the current manifest resolver as adapter 0 for installs:
That is the shortest path from ECC 1.x installer sprawl to an ECC 2.0 install/control contract that is deterministic, supportable, and extensible.