ECC 2.0 Selective Install Discovery
This document turns the March 11 mega-plan selective-install requirement into a concrete ECC 2.0 discovery design.
Overview
ECC 2.0 Selective Install Discovery
Purpose
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:
- what was requested
- what was resolved
- what was copied or generated
- what target-specific transforms were applied
- what ECC owns and may safely remove or repair later
That is the missing contract between ECC 1.x installation and an ECC 2.0 control plane.
Current Implemented Foundation
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.js
Current capabilities:
- machine-readable module and profile catalogs
- CI validation that manifest entries point at real repo paths
- dependency expansion and target filtering
- adapter-aware operation planning
- canonical request normalization for legacy and manifest install modes
- explicit runtime dispatch from normalized requests into plan creation
- legacy and manifest installs both write durable install-state
- read-only inspection of install plans before any mutation
- unified
eccCLI routing install, planning, and lifecycle commands - lifecycle inspection and mutation via
list-installed,doctor,repair, anduninstall
Current limitation:
- target-specific merge/remove semantics are still scaffold-level for some modules
- legacy
ecc-installcompatibility still points atinstall.sh - publish surface is still broad in
package.json
Current Code Review
The 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.
Current Runtime Path
The runtime flow today is:
install.shthin shell wrapper that resolves the real package rootscripts/install-apply.jsuser-facing installer CLI for legacy and manifest modesscripts/lib/install/request.jsCLI parsing plus canonical request normalizationscripts/lib/install/runtime.jsruntime dispatch from normalized requests into install plansscripts/lib/install-executor.jsargument translation, legacy compatibility, operation materialization, filesystem mutation, and install-state writescripts/lib/install-manifests.jsmodule/profile catalog loading plus dependency expansionscripts/lib/install-targets/target root and destination-path scaffoldingscripts/lib/install-state.jsschema-backed install-state read/writescripts/lib/install-lifecycle.jsdoctor/repair/uninstall behavior derived from stored operations
That is enough to prove the selective-install substrate, but not enough to make the installer architecture feel settled.
Current Strengths
- install intent is now explicit through
--profileand--modules - request parsing and request normalization are now split from the CLI shell
- target root resolution is already adapterized
- lifecycle commands now use durable install-state instead of guessing
- the repo already has a unified Node entrypoint through
eccandinstall-apply.js
Current Coupling Still Present
install-executor.jsis 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.- target adapters are still too thin. Today they mostly resolve roots and scaffold destination paths. The real install semantics still live in executor branches and path heuristics.
- the planner/executor boundary is not clean enough yet.
install-manifests.jsresolves modules, but the final install operation set is still partly constructed in executor-specific logic. - lifecycle behavior depends on low-level recorded operations more than on stable module semantics. That works for plain file copy, but becomes brittle for merge/generate/remove behaviors.
- compatibility mode is mixed directly into the main installer runtime. Legacy language installs should behave like a request adapter, not as a parallel installer architecture.
Proposed Modular Architecture Changes
The next architectural step is to separate the installer into explicit layers, with each layer returning stable data instead of immediately mutating files.
Target State
The desired install pipeline is:
- CLI surface
- request normalization
- module resolution
- target planning
- operation planning
- execution
- install-state persistence
- lifecycle services built on the same operation contract
The main idea is simple:
- manifests describe content
- adapters describe target-specific landing semantics
- planners describe what should happen
- executors apply those plans
- lifecycle commands reuse the same plan/state model instead of reinventing it
Proposed Runtime Layers
1. CLI Surface
Responsibility:
- parse user intent only
- route to install, plan, doctor, repair, uninstall
- render human or JSON output
Should not own:
- legacy language translation
- target-specific install rules
- operation construction
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.
2. Request Normalizer
Responsibility:
- translate raw CLI flags into a canonical install request
- convert legacy language installs into a compatibility request shape
- reject mixed or ambiguous inputs early
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.
3. Module Resolver
Responsibility:
- load manifest catalogs
- expand dependencies
- reject conflicts
- filter unsupported modules per target
- return a canonical resolution object
This layer should stay pure and read-only.
It should not know:
- destination filesystem paths
- merge semantics
- copy strategies
Current nearest file:
scripts/lib/install-manifests.js
Suggested split:
scripts/lib/install/catalog.js
scripts/lib/install/resolve-request.js
scripts/lib/install/resolve-modules.js
4. Target Planner
Responsibility:
- select the install target adapter
- resolve target root
- resolve install-state path
- expand module-to-target mapping rules
- emit target-aware operation intents
This is where target-specific meaning should live.
Examples:
- Claude may preserve native hierarchy under
~/.claude - Cursor may sync bundled
.cursorroot children differently from rules - generated configs may require merge or replace semantics depending on target
Current nearest files:
scripts/lib/install-targets/helpers.jsscripts/lib/install-targets/registry.js
Suggested 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.
5. Operation Planner
Responsibility:
- turn module resolution plus adapter rules into a typed operation graph
- emit first-class operations such as:
copy-filecopy-treemerge-jsonrender-templateremove
- attach ownership and validation metadata
This 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:
planbecomes a true preview of executiondoctorcan validate intended behavior, not just current filesrepaircan rebuild exact missing work safelyuninstallcan reverse only managed operations
6. Execution Engine
Responsibility:
- apply a typed operation graph
- enforce overwrite and ownership rules
- stage writes safely
- collect final applied-operation results
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.js
Recommended 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.
7. Install-State Store
Responsibility:
- validate and persist install-state
- record canonical request, resolution, and applied operations
- support lifecycle commands without forcing them to reverse-engineer installs
Current nearest file:
scripts/lib/install-state.js
This layer is already close to the right shape. The main remaining change is to store richer operation metadata once merge/generate semantics are real.
8. Lifecycle Services
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 outputs
Current nearest file:
scripts/lib/install-lifecycle.js
This layer should eventually operate on operation kinds and ownership policies,
not just on raw copy-file records.
Proposed File Layout
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.
Migration Map From Current Files
The lowest-risk migration path is evolutionary, not a rewrite.
Keep
install.shas the public compatibility shimscripts/ecc.jsas the unified CLIscripts/lib/install-state.jsas the starting point for the state store- current target adapter IDs and state locations
Extract
- request parsing and compatibility translation out of
scripts/lib/install-executor.js - target-aware operation planning out of executor branches and into target adapters plus planner modules
- lifecycle-specific analysis out of the shared lifecycle monolith into smaller services
Replace Gradually
- broad path-copy heuristics with typed operations
- scaffold-only adapter planning with adapter-owned semantics
- legacy language install branches with legacy request translation into the same planner/executor pipeline
Immediate Architecture Changes To Make Next
If the goal is ECC 2.0 and not just “working enough,” the next modularization steps should be:
- split
install-executor.jsinto request normalization, operation planning, and execution modules - move target-specific strategy decisions into adapter-owned planning methods
- make
repairanduninstalloperate on typed operation handlers rather than only plaincopy-filerecords - teach manifests about install strategy and ownership so the planner no longer depends on path heuristics
- narrow the npm publish surface only after the internal module boundaries are stable
Why The Current Model Is Not Enough
Today ECC still behaves like a broad payload copier:
install.shis language-first and target-branch-heavy- targets are partly implicit in directory layout
- uninstall, repair, and doctor now exist but are still early lifecycle commands
- the repo cannot prove what a prior install actually wrote
- publish surface is still broad in
package.json
That creates the problems already called out in the mega plan:
- users pull more content than their harness or workflow needs
- support and upgrades are harder because installs are not recorded
- target behavior drifts because install logic is duplicated in shell branches
- future targets like Codex or OpenCode require more special-case logic instead of reusing a stable install contract
ECC 2.0 Design Thesis
Selective install should be modeled as:
- resolve requested intent into a canonical module graph
- translate that graph through a target adapter
- execute a deterministic install operation set
- write install-state as the durable source of truth
That means ECC 2.0 needs two contracts, not one:
- a content contract what modules exist and how they depend on each other
- a target contract how those modules land inside Claude, Cursor, Antigravity, Codex, or OpenCode
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.
Design Constraints
- Keep
everything-claude-codeas the canonical source repo. - Preserve existing
install.shflows during migration. - Support home-scoped and project-scoped targets from the same planner.
- Make uninstall/repair/doctor possible without guessing.
- Avoid per-target copy logic leaking back into module definitions.
- Keep future Codex and OpenCode support additive, not a rewrite.
Canonical Artifacts
1. Module Catalog
The module catalog is the canonical content graph.
Current fields already implemented:
idkinddescriptionpathstargetsdependenciesdefaultInstallcoststability
Fields still needed for ECC 2.0:
installStrategyfor examplecopy,flatten-rules,generate,merge-configownershipwhether ECC fully owns the target path or only generated files under itpathModefo