Canonical Package Layout
Use a capability-oriented, boundary-first layout for monorepos. The layout
is a packaging strategy, not a framework convention: choose boundaries by
ownership, public API, independent change, lifecycle, consumers, and release
needs.
Canonical topology
/
├── apps/
│ ├── product_app/
│ └── admin_app/
├── packages/
│ ├── features/
│ │ ├── auth/
│ │ └── profile/
│ ├── core/
│ │ ├── app_core/
│ │ └── design_system/
│ └── platform/
│ ├── networking/
│ └── storage/
└── workspace configuration and lockfiles
Treat the directories as navigational and organizational groups. The package
manager, not the directory name, defines package identity and import or
dependency coordinates. A path such as packages/features/auth may therefore
contain a package named auth, subject to the stack's naming rules.
Use the groups this way:
apps/* contains executable products and owns product composition, routes,
deployment configuration, and product-specific wiring.
packages/features/* contains cohesive product capabilities such as auth,
billing, or profile. A feature is internal by default; extract it for reuse
only when independent consumers or a stable boundary justify that cost.
packages/core/* contains genuinely shared, stable contracts or cross-cutting
product foundations such as application primitives or a design system. Keep
product-specific behavior out of core.
packages/platform/* contains reusable integrations with external systems,
operating-system capabilities, infrastructure, or data sources when their
ownership, lifecycle, API, or consumers make reuse a real concern.
Boundary rules
Keep each package deep: a small public interface should hide meaningful
implementation complexity. A directory is not a boundary merely because it is
named domain, application, data, services, or components.
Inside a feature, keep collaborating roles co-located when they serve one
capability and callers do not need to distinguish them. Split them only when
the split protects a real dependency direction, independent change, public API,
lifecycle, consumer, or release seam. Use these tests before adding a package,
folder, or file:
- Deletion test: does removing the proposed unit eliminate a meaningful
boundary, or only move the same complexity into neighbouring units?
- Interface test: is its public surface materially smaller than the
behavior it hides?
- Locality test: can a change to one concept be understood and tested mostly
within the unit?
- Seam test: is there a real ownership or dependency seam? One adapter is a
hypothesis; multiple genuinely different consumers or implementations are
stronger evidence.
Prefer a few deep capability packages over many shallow role-shaped packages.
The target is not a symmetrical tree or the fewest files; it is a legible set
of boundaries that keeps related decisions local and gives consumers leverage.
Layering and release posture
Treat architecture labels such as presentation, application, domain, and data
as roles, not mandatory package directories. Keep those roles together inside a
feature unless an independent package boundary is justified by the rules above.
Use one-way dependency direction as a default:
app → feature → core contract
app → feature → platform adapter
Keep app composition in apps/*. Place an abstraction with the innermost
consumer that needs the capability rather than with the outer implementation.
Promote a package to an independently releasable artifact only when it has real
independent consumers, a stable public API, and a release or lifecycle reason.
Do not generalize an app-only feature to make the directory tree look uniform.
Process and completion criteria
- Inventory existing applications, packages, dependency edges, public entry
points, workspace configuration, and release settings. Completion criterion:
every proposed move has a known owner, consumer set, and dependency impact.
- Classify each unit as an app, feature, core foundation, platform integration,
or an intentional exception. Completion criterion: every classification is
justified by capability, ownership, lifecycle, or consumer evidence.
- Draw the intended one-way package graph and identify accidental reverse edges,
cycles, and leaked implementation details. Completion criterion: the graph
has no unexplained cycle or app-owned capability hidden in shared packages.
- Apply the deletion, interface, locality, and seam tests to every new package
or internal split. Completion criterion: every retained boundary hides
meaningful complexity and no unit exists solely to mirror a layer label.
- Validate package-manager resolution, imports, tests, build checks, and any
standalone release checks required by the repository. Completion criterion:
all relevant checks pass, or each exception is recorded with an owner and
follow-up condition.
1---2name: canonical-package-layout3description: Design or review a capability-oriented monorepo layout with apps, features, core, and platform packages. Use when deciding where applications, product capabilities, shared contracts/UI, or reusable integrations belong across Node.js, Dart, TypeScript, Python, or other stacks.4---56# Canonical Package Layout78Use a **capability-oriented, boundary-first** layout for monorepos. The layout9is a packaging strategy, not a framework convention: choose boundaries by10ownership, public API, independent change, lifecycle, consumers, and release11needs.1213## Canonical topology1415```text16/17├── apps/18│ ├── product_app/19│ └── admin_app/20├── packages/21│ ├── features/22│ │ ├── auth/23│ │ └── profile/24│ ├── core/25│ │ ├── app_core/26│ │ └── design_system/27│ └── platform/28│ ├── networking/29│ └── storage/30└── workspace configuration and lockfiles31```3233Treat the directories as navigational and organizational groups. The package34manager, not the directory name, defines package identity and import or35dependency coordinates. A path such as `packages/features/auth` may therefore36contain a package named `auth`, subject to the stack's naming rules.3738Use the groups this way:3940- `apps/*` contains executable products and owns product composition, routes,41 deployment configuration, and product-specific wiring.42- `packages/features/*` contains cohesive product capabilities such as auth,43 billing, or profile. A feature is internal by default; extract it for reuse44 only when independent consumers or a stable boundary justify that cost.45- `packages/core/*` contains genuinely shared, stable contracts or cross-cutting46 product foundations such as application primitives or a design system. Keep47 product-specific behavior out of core.48- `packages/platform/*` contains reusable integrations with external systems,49 operating-system capabilities, infrastructure, or data sources when their50 ownership, lifecycle, API, or consumers make reuse a real concern.5152## Boundary rules5354Keep each package deep: a small public interface should hide meaningful55implementation complexity. A directory is not a boundary merely because it is56named `domain`, `application`, `data`, `services`, or `components`.5758Inside a feature, keep collaborating roles co-located when they serve one59capability and callers do not need to distinguish them. Split them only when60the split protects a real dependency direction, independent change, public API,61lifecycle, consumer, or release seam. Use these tests before adding a package,62folder, or file:6364- **Deletion test:** does removing the proposed unit eliminate a meaningful65 boundary, or only move the same complexity into neighbouring units?66- **Interface test:** is its public surface materially smaller than the67 behavior it hides?68- **Locality test:** can a change to one concept be understood and tested mostly69 within the unit?70- **Seam test:** is there a real ownership or dependency seam? One adapter is a71 hypothesis; multiple genuinely different consumers or implementations are72 stronger evidence.7374Prefer a few deep capability packages over many shallow role-shaped packages.75The target is not a symmetrical tree or the fewest files; it is a legible set76of boundaries that keeps related decisions local and gives consumers leverage.7778## Layering and release posture7980Treat architecture labels such as presentation, application, domain, and data81as roles, not mandatory package directories. Keep those roles together inside a82feature unless an independent package boundary is justified by the rules above.8384Use one-way dependency direction as a default:8586```text87app → feature → core contract88app → feature → platform adapter89```9091Keep app composition in `apps/*`. Place an abstraction with the innermost92consumer that needs the capability rather than with the outer implementation.93Promote a package to an independently releasable artifact only when it has real94independent consumers, a stable public API, and a release or lifecycle reason.95Do not generalize an app-only feature to make the directory tree look uniform.9697## Process and completion criteria98991. Inventory existing applications, packages, dependency edges, public entry100 points, workspace configuration, and release settings. Completion criterion:101 every proposed move has a known owner, consumer set, and dependency impact.1022. Classify each unit as an app, feature, core foundation, platform integration,103 or an intentional exception. Completion criterion: every classification is104 justified by capability, ownership, lifecycle, or consumer evidence.1053. Draw the intended one-way package graph and identify accidental reverse edges,106 cycles, and leaked implementation details. Completion criterion: the graph107 has no unexplained cycle or app-owned capability hidden in shared packages.1084. Apply the deletion, interface, locality, and seam tests to every new package109 or internal split. Completion criterion: every retained boundary hides110 meaningful complexity and no unit exists solely to mirror a layer label.1115. Validate package-manager resolution, imports, tests, build checks, and any112 standalone release checks required by the repository. Completion criterion:113 all relevant checks pass, or each exception is recorded with an owner and114 follow-up condition.115