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.
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
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.
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.
- 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
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.
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:
- 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
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.
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.
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
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.
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
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.
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.
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.
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.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
- 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
- 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
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
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
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.
- 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.
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 itpathModefor examplepreserve,flatten,target-templateconflictsmodules or path families that cannot coexist on one targetpublishwhether the module is packaged by default, optional, or generated post-install
Suggested 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:
coredevelopersecurityresearchfull
Fields still needed:
defaultTargetsrecommendedForexcludesrequiresConfirmation
That lets ECC 2.0 say things like:
developeris the recommended default for Claude and Cursorresearchmay be heavy for narrow local installsfullis allowed but not default
This is the main missing layer.
The module graph should not know:
- where Claude home lives
- how Cursor flattens or remaps content
- which config files need merge semantics instead of blind copy
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-homewrites into~/.claude/...cursor-projectwrites into./.cursor/...antigravity-projectwrites into./.agent/...codex-homelateropencode-homelater
This 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:
- input normalization
- parse
--target - parse
--profile - parse
--modules - optionally translate legacy language args
- parse
- module resolution
- expand dependencies
- reject conflicts
- filter by supported targets
- adapter planning
- resolve target root
- derive exact copy or generation operations
- identify config merges and target remaps
- dry-run output
- show selected modules
- show skipped modules
- show exact file operations
- mutation
- execute the operation plan
- state write
- persist install-state only after successful completion
Suggested 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-jsoncmkdirremove
Install-state is the durable contract that ECC 1.x is missing.
Suggested path conventions:
- Claude target:
~/.claude/ecc/install-state.json - Cursor target:
./.cursor/ecc-install-state.json - Antigravity target:
./.agent/ecc-install-state.json - future Codex target:
~/.codex/ecc-install-state.json
Suggested 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.9.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:
- enough detail for uninstall to remove only ECC-managed outputs
- enough detail for repair to compare desired versus actual installed files
- enough detail for doctor to explain drift instead of guessing
The following commands are the lifecycle surface for install-state:
ecc list-installedecc uninstallecc doctorecc repair
Current implementation status:
ecc list-installedroutes tonode scripts/list-installed.jsecc uninstallroutes tonode scripts/uninstall.jsecc doctorroutes tonode scripts/doctor.jsecc repairroutes tonode scripts/repair.js- legacy script entrypoints remain available during migration
Responsibilities:
- show target id and root
- show requested profile/modules
- show resolved modules
- show source version and install time
Responsibilities:
- load install-state
- remove only ECC-managed destinations recorded in state
- leave user-authored unrelated files untouched
- delete install-state only after successful cleanup
Responsibilities:
- detect missing managed files
- detect unexpected config drift
- detect target roots that no longer exist
- detect manifest/version mismatch
Responsibilities:
- rebuild the desired operation plan from install-state
- re-copy missing or drifted managed files
- refuse repair if requested modules no longer exist in the current manifest unless a compatibility map exists
Current install.sh accepts:
--target <claude|cursor|antigravity>- a list of language names
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:
- keep existing CLI shape for legacy mode
- map language names to module requests such as:
rules-core- target-compatible rule subsets
- write install-state even for legacy installs
- label the request as
legacyMode: true
Example:
{
"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:
- keep one canonical npm package first
- use manifests to drive install-time selection before changing publish shape
- only later consider reducing packaged surface where safe
Why:
- selective install can ship before aggressive package surgery
- uninstall and repair depend on install-state more than publish changes
- Codex/OpenCode support is easier if the package source remains unified
Possible later directions:
- generated slim bundles per profile
- generated target-specific tarballs
- optional remote fetch of heavy modules
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.
- keep current manifest schema and resolver
- add operation planning on top of resolved modules
- define
ecc.install.v1state schema - write install-state on successful install
- extract Claude install behavior into
claude-homeadapter - extract Cursor install behavior into
cursor-projectadapter - extract Antigravity install behavior into
antigravity-projectadapter - reduce
install.shto argument parsing plus adapter invocation
- add stronger target-specific merge/remove semantics
- extend repair/uninstall coverage for non-copy operations
- reduce package shipping surface to the module graph instead of broad folders
- decide when
ecc-installshould become a thin alias forecc install
- evaluate safe reduction of
package.jsonpublish surface - add
codex-home - add
opencode-home - consider generated profile bundles if packaging pressure remains high
The highest-signal next implementation moves in this repo are:
- add target-specific merge/remove semantics for config-like modules
- extend repair and uninstall beyond simple copy-file operations
- reduce package shipping surface to the module graph instead of broad folders
- decide whether
ecc-installremains separate or becomesecc install - add tests that lock down:
- target-specific merge/remove behavior
- repair and uninstall safety for non-copy operations
- unified
eccCLI routing and compatibility guarantees
- Should rules stay language-addressable in legacy mode forever, or only during the migration window?
- Should
platform-configsalways install withcore, or be split into smaller target-specific modules? - Do we want config merge semantics recorded at the operation level or only in adapter logic?
- Should heavy skill families eventually move to fetch-on-demand rather than package-time inclusion?
- Should Codex and OpenCode target adapters ship only after the Claude/Cursor lifecycle commands are stable?
Treat the current manifest resolver as adapter 0 for installs:
- preserve the current install surface
- move real copy behavior behind target adapters
- write install-state for every successful install
- make uninstall, doctor, and repair depend only on install-state
- only then shrink packaging or add more targets
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.