ADR-0038: melos monorepo with multi-app + shared core_* packages, structured from day one
Status
Accepted (pending FieldForce Phase 1 implementation)Tags
fieldforce, digital-worker, mobile, flutter, monorepo, melos, modules, dddDecision
The Flutter codebase is a melos-managed monorepo with anapps/ + packages/ split, structured for multiple apps from day one even though FieldForce ships first.
- Each
apps/*is a full Flutter app with its own bundle ID, flavors, and release cadence. - Shared infrastructure lives in
core_*packages, depended on via path. - Infrastructure is shared from day one; features earn sharing only when a second app needs them (start in the app, graduate to
packages/feature_*on the second consumer — never pre-share). - Apps deploy separately (separate bundle IDs, versions, signing, store listings, CI triggers). The monorepo is a development-time convenience, not a deployment coupling.
Why
Two apps are already planned (FieldForce, Digital Worker) and they share substantial infrastructure: offline-sync wiring, auth/session, brand UI, domain models, notifications. Building these once in shared packages — rather than copy-pasting across two single-project apps — is the core leverage of a monorepo, and it mirrors the backend’s shared-kernel / bounded-context approach (ADR-0004, ADR-0009). Structuring for the monorepo shape now, with one app, costs almost nothing and makes the second app agit mv + a pubspec path entry rather than an extract-everything migration later. The project explicitly wants to “get it right at the beginning” and avoid retrofits.
Crucially for a vibe-coded workflow, separate packages enforce boundaries the compiler can check — a shared package physically cannot import from an app, so the LLM cannot generate a cross-boundary import that violates the architecture. The dependency graph itself enforces the design, not just CLAUDE.md prose.
Rejected alternatives:
- One Flutter project per app (separate repos / no shared packages). Simplest to start, but guarantees copy-paste drift of auth/sync/brand code across apps and a painful later consolidation. Rejected — the shared offline-sync engine and brand UI alone justify sharing.
- Single-project app with a
lib/core/folder (no packages). Boundaries become convention, not compiler-enforced; nothing stopsfeatures/leaking intocore/. Rejected — package boundaries are the enforcement mechanism that matters most under vibe coding. - Full monorepo scaffolding with speculative empty packages. Over-building. Rejected — a
core_*package is born when its first real code lands, not before.
Consequences
- Path dependencies mean all apps always use HEAD of shared packages — a breaking change in
core_networkbreaks every app at once. Atomic refactors are a benefit; the cost is that shared packages carry the strictest test coverage (they are load-bearing for N apps). - CI must use path-based triggers (affected-graph) so each app builds only when its code or a dependency changes (see ADR-0045).
- iOS signing scales per-app — each app needs its own provisioning, certs, push keys; the monorepo does not reduce this.
- A package dependency DAG must be defined and enforced (see ADR-0039).
Rules for agents
- Each
apps/*is independently deployable with its own bundle ID; the monorepo never couples release schedules. - Infrastructure goes in
core_*packages from day one; feature code starts in the app and graduates topackages/feature_*ONLY when a second app needs it. Never pre-share. - Do NOT create empty packages speculatively — a package is born with its first real code.
- A package MUST NOT import from an app; respect the dependency DAG (ADR-0039).
- Use
melos run generatefor codegen across packages; CI uses path-based triggers.