Skip to main content
Supersedes 0040. It builds on the member shape in 0126, which amends 0040 first.
Amended by 0259: the kind-folder layout applies in every home, in services, OS packages and Stackra packages alike.
Amended by 0264: The kind folders hooks/, widgets/, pages/, links/, drivers/ and ai/ join decision 1’s list, each created with its first file.

Context

0040 organised services as DDD layers. Accepted 0091 and 0126 already removed the generated folder and set the feature-module path. The owner’s intent-map item P9 and the audit’s module-layout answer (audit Q9) replace the DDD layers with kind folders, organised around what each file does.

Decision

  1. A feature module lives at src/modules/<feature>/. Its code sits in kind folders: models/, actions/, queries/, services/, events/, listeners/, workflows/, policies/, permissions/, settings/ and contracts/.
  2. interfaces/, types/, constants/, tokens/, errors/ and decorators/ appear once a module has two or more files of that kind (0128 decision 4).
  3. raw/ holds only @Controller raw endpoints (webhooks, streaming, /api/auth/*). Actions never use it.
  4. There are no controllers/, dtos/, repositories/ or presentation/ folders.
  5. Dependencies point inward. Actions, listeners and workflows depend on services and models. Models import from no other folder.
  6. Where the sources disagree, the later one wins. P9 names a permissions.ts file; the audit (L547) names a permissions/ folder, and the folder wins.

Consequences

  • Services are organised by what they do, and an agent finds an action in actions/.
  • The services/encore scaffold template, which still uses the old shape, is replaced with plain TypeScript (gap D).
  • The validator’s checkServiceUnit must describe this shape. Until it does, the validator and this ADR disagree.

Enforcement

planned — checkServiceUnit in tools/scripts/validate-repository.mjs checks the kind-folder set and forbids the controllers/, dtos/, repositories/ and presentation/ folders. Dependency direction is planned for the packages/tooling/oxlint/boundaries.jsonc rules.

Alternatives

  • Keep DDD layers (0040): superseded by this ADR, per the owner’s audit answer.
  • One flat modules/<feature>/ folder with no kinds: rejected, because it hides the action-first shape.

Supersedes and sources

Supersedes: 0040. Builds on: 0126. Sources: owner intent-map item P9 (stackra-notes/owner-docs-intent-map.md, Part 4, accepted L584); audit Q9 (stackra-decisions-2026-10-19.md L547, 2026-10-20); gap B4 (layout half).