Architecture
Understand the registry-build lifecycle, build context, cache model, and extension boundaries.
The design is simple: static inputs live in config, the core runner produces deterministic base artifacts, and extensions add opt-in behavior after that base state exists. New non-UI consumers should think in named collections first — legacy UI fields normalize into that model for compatibility.
Lifecycle
Build context
The runtime context is the shared contract between the config loader, the core phases, and every extension.
It carries:
- resolved config
- resolved collections
- config path and config directory
- output paths
- path registry helpers
- collected build artifacts
- output registration records
- a per-build
ts-morphproject - cache access
- changed-only flags and resolved changed paths
The pipeline stays explicit — phases and extensions don't rediscover global state on their own.
Core responsibilities
Artifact model
The runner exposes two related concepts:
| Concept | Meaning |
|---|---|
artifacts | In-memory values shared between phases and extensions |
outputs | Files or file groups registered as generated outputs |
Typical examples:
- artifact:
collections - artifact:
index - output:
public/r/index.json - output:
public/r/components/*.json - output:
__ui_registry__/index.tsx - output:
dist/arch/repos/core.db.json - output:
public/r/themes.css
This split keeps extensions data-first. They read artifacts and register outputs without pretending to be part of the core phase graph.
Cache model
The cache stores:
- file hashes
- phase-specific manifest data
The cache is local, incremental state. Not the source of truth — always safe to delete.
What the cache is for
- skip rehashing unchanged files
- skip rematerializing unchanged registry items
- skip rewriting identical generated outputs
- support
--changed-onlylocal rebuilds
What the cache is not for
- long-term artifact storage
- cross-machine shared caching
- replacing deterministic generation
Design rules
- Keep config declarative and extensions imperative.
- Keep the core generic and small.
- Make
collectionsthe first stop for new non-UI consumers. - Resolve paths relative to the file that declared them.
- Prefer stable artifacts over framework-specific shortcuts in the core.
- Treat generated output ordering as part of correctness.