# ADR-0004: Package modules by domain, not by stage **Status:** Accepted **Date:** 2026-07-13 ## Context Module packages can be grouped first by pipeline stage, such as `modules/chunk/dnd/scenes`, or first by domain, such as `modules/dnd/chunk/scenes`. A domain's extract, merge, normalize, validation, schema, prompt, and artifact-codec implementations collaborate around the same artifact types and are likely to evolve together. Go package dependencies also constrain registration. If shared types live in a domain root package, that package cannot import child implementation packages to register them because the children already import the root types. ## Decision Production extensions are grouped by domain under: ```text internal/modules/// ``` Shared artifact types live at the domain root, for example `internal/modules/dnd/types.go`. Domain-specific validators, prompt fragments, schemas, reference helpers, and codecs also live within that domain tree. Each domain exposes one production registration entry point from a sibling registrar package, for example `internal/modules/dnd/register`. The registrar may import the domain root and its child implementations; the domain root does not import its registrar or child packages. This keeps shared types available as `dnd.SpellList` without creating a Go import cycle. The `generic` tree is a peer extension family for reusable implementations that contain no concrete source-format or artifact-domain knowledge. Source-format and output-format families, such as Seriatim and JSON output, follow the same domain-first organization even when they do not define a Zone-B artifact type. Concrete domain implementation packages do not import another concrete domain. Generic extension packages never import concrete domains. A domain registrar may import domain-neutral generic extension packages to instantiate a reusable strategy for that domain's artifact type; the generic implementation remains unaware of the concrete type's domain semantics. Reuse needed directly by a domain implementation lives in a domain-neutral framework or helper package, not in a peer extension package. The application composition root may import multiple registrar packages, and black-box integration tests may compose multiple domains. Other cross-domain reuse occurs through engine contracts and composition-time registration rather than concrete peer-domain imports. A domain registrar owns registration of that domain's modules, validators, default validator chains, artifact codecs, schemas, and prompt assets. It does not take ownership of application execution or process behavior. ## Alternatives considered - Group modules by stage. This keeps interchangeable strategies side by side, but scatters a domain's shared artifact model and collaborating extensions across the repository. It is preferable when generic strategy libraries dominate or when the project is primarily a stage-extension framework rather than an application composed from domain suites. - Put both shared types and `Register` in the domain root. This gives the shortest import path but creates an import cycle once child implementations import the root artifact types. ## Consequences The repository layout makes supported domains immediately visible, and adding or extracting a domain affects one cohesive subtree. The CLI composition root depends on a small set of domain registrars instead of every leaf package. Package moves must preserve user-visible module and validator keys unless a separate compatibility decision changes them. Shared behavior that cannot be expressed through framework contracts may need to move into a domain-neutral framework package rather than creating a concrete peer-domain import. Registrar packages become explicit composition points for instantiating generic typed strategies, in addition to registering domain-owned implementations.