layered source organization
rule
- placement is derived from the consumer graph, never scaffolded in advance.
- a block lives in or beside the owner that uses it; it moves outward one rung only when more owners consume it.
- layer names (
domains/<keyword>/,repositories/,components/<domain>/) are vocabulary for where derived sharing lands — not folders to pre-create. - derivation overrides vocabulary on conflict: one consumer → stay beside it; a category appears only when a second owner appears.
- every move is forward-only: all consumers' imports update in the same change and the old path is deleted — including the reverse move when consumers collapse back to one.
why
- the folder tree is the first navigation surface for humans and agents; a tree imposed before code exists drifts into empty monuments and wrapper hops.
- structure stays honest when every folder is the residue of a real sharing decision: its existence proves at least two consumers needed one home, and its name states the boundary those consumers share.
- derivation preserves the two navigation questions: high-level destinations answer "which user or domain outcome is this?"; lower-level destinations answer "which mechanism must be inspected or changed, and for which domain?"
- the vocabulary reserves those answers; the consumer ladder decides when each destination may exist.
placement ladder
- write the block in or beside its first consumer; create no folder for code that does not exist.
- a second owner inside the same boundary consumes it → move to the owner-level common folder in the same change.
- consumers cross owner boundaries → move to the cross-owner category named by its exact responsibility.
- policy, use cases, and flows accumulate on one domain noun → give them the keyword home; route derived mechanisms to their categories.
- consumers appear in another workspace project → extract a package.
- consumers collapse back to one → move the block home and delete the emptied folder in the same forward-only change.
do not
- create a category, domain folder, or package before code consumes it.
- keep a category as an empty monument after its consumers leave.
- repeat
repositories/,providers/, ormappers/inside every domain when one category owns the mechanism as a shared destination. - move business policies or use cases into a technical category.
- create a category whose boundary cannot be stated without words such as utility, helper, common, or miscellaneous.
- leave a re-export shim at an old path.
good and bad
bad
src/domains/billing/services/repositories/providers/channel/services/repositories/providers/utils/common/
every category was scaffolded before any code needed it. utils/ and
common/ name no responsibility; each domain repeats role folders that share
nothing yet. a reader cannot tell real sharing from empty ritual, so the tree
stops being trusted and grep replaces navigation.
good
src/domains/billing/checkout.tschannel/exploration.tsrepositories/ # appeared when channel consumed the repositorybillingRepository.ts
the tree is residue, not scaffold. billingRepository.ts started inside
domains/billing/ beside the only flow that used it; when the channel domain
consumed it, it moved to repositories/ in one forward-only change. the
category holds only blocks with cross-owner consumers; the tree reads as a map
of real sharing decisions.
verification
- count each placed block's consumers with an import search before and after the move: one consumer → beside its owner; cross-owner consumers → inside the category.
- list every category and destination folder: none empty, none forwarding, none without a consuming block.
- search for the technical operation outside its category and for business policy inside it — both result sets empty apart from explicit framework or generated boundaries.