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.

shared-destination vocabulary

destinationearned whennames
consumer file or folderthe block has exactly one consumerthe block lives inside or beside its consumer.
owner-level folderconsumers exist inside one ownerthe owner's own components/, mappers/, or concept folder.
cross-owner categoryconsumers cross owner boundaries in one projectrepositories/, providers/, mappers/, schemas/.
domain keyword homepolicy, use cases, and flows accumulate on one noundomains/<keyword>/ per domain-keyword organization.
workspace packageconsumers cross project boundariesa package named by the shortest exact keyword.
  • a leaf in a cross-owner category retains its domain keyword (repositories/billingRepository.ts); nest under the domain only with multiple focused parts (repositories/billing/readInvoice.ts).
  • a category names one exact technical responsibility: utils, helpers, common, catch-all services are not destinations.
  • a destination never relocates business policy into a technical category — lower-level code implements domain contracts but does not own the policy.
  • framework entrypoints and generated output stay where their tools require; derivation does not override tool contracts.

placement ladder

  1. write the block in or beside its first consumer; create no folder for code that does not exist.
  2. a second owner inside the same boundary consumes it → move to the owner-level common folder in the same change.
  3. consumers cross owner boundaries → move to the cross-owner category named by its exact responsibility.
  4. policy, use cases, and flows accumulate on one domain noun → give them the keyword home; route derived mechanisms to their categories.
  5. consumers appear in another workspace project → extract a package.
  6. 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/, or mappers/ 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.ts
channel/
exploration.ts
repositories/ # appeared when channel consumed the repository
billingRepository.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.

search pages

go to any page