Skip to content

E. Monorepo Evaluation

← Back to README


Decision Question

Should the Soreto/Reverb ecosystem remain as four separate repositories, or evolve toward a monorepo?

Short answer: Monorepo. The coupling already exists. The question is whether that coupling is visible and typed, or hidden and fragile.


The Core Argument

The four repositories are not genuinely independent. Direct evidence:

Evidence What it means
soreto-zoe/local_modules/reverb HTTP client zoe cannot function without reverb-backend running
soreto-melissa calls NEXT_PUBLIC_APP_API_URL melissa cannot function without reverb-backend running
reverb-react links to melissa as "New Portal" the two portals are a single transitioning system
Both repos run Knex against the same PostgreSQL server shared database, separate schema
Both repos use the same Redis instance shared infrastructure

These repos already form a single system. Managing them as separate repos means: - Cross-cutting changes require 2–4 separate PRs, reviews, and deploys - Type errors between repos are caught at runtime, not at compile time - New engineers must clone 4 repos to understand the full system - AI tools (Claude Code, Cursor) cannot see the full system in one session

A monorepo removes none of these technical dependencies — it just makes them visible and manageable.


Benefits of a Monorepo (Evidence-Based)

1. Atomic Cross-Repo Changes

Current: Adding a new API endpoint to reverb-backend requires: 1. PR to reverb-backend (service + route) 2. PR to soreto-melissa (service class + types) 3. PR to soreto-zoe (reverb client method, if needed)

Monorepo: One PR. One review. One deploy gate.

2. Typed API Contracts

The local_modules/reverb client in soreto-zoe has no type definitions. Changes to reverb-backend API endpoints silently break zoe processors at runtime. With a @soreto/api-client shared package in a monorepo: - TypeScript catches breaking changes at compile time - CI fails before the broken code reaches production - AI tools can read the type definitions and produce correct implementations

3. Unified Tooling

Currently: - 3 different ESLint versions across 4 repos - 4 different Babel/TypeScript configurations - 2 CI providers (Travis) with 2 repos having no CI - 4 different Node versions

A monorepo enforces a single tooling baseline, reducing configuration drift automatically.

4. AI Development Readiness

A single repo checkout gives AI agents (Claude Code, Cursor, Copilot) the full system context. Currently, a session in reverb-backend has no awareness of how soreto-melissa consumes its endpoints, producing suboptimal suggestions. See J — AI Context for full analysis.

5. Shared Package Extraction

@soreto/types, @soreto/constants, @soreto/api-client become possible as first-class packages with their own versioning and changelogs. Currently these are either duplicated across repos or non-existent.


Risks of a Monorepo

Risk Probability Mitigation
Build tool complexity (Turborepo learning curve) MEDIUM Start with npm workspaces only; add Turborepo incrementally
Migration effort underestimated MEDIUM Phased approach — apps migrate one at a time
Legacy code quality drags new code LOW Freeze reverb-react in apps/legacy-portal/ with no new features
PR scope expands uncontrollably LOW CODEOWNERS per package; enforce apps/ vs packages/ PR discipline
CI becomes slow if not scoped LOW Turborepo's affected-project detection (--filter=[HEAD^1])
Node version conflicts during transition MEDIUM .nvmrc per workspace; standardize Node before migrating

Three Concepts — One Decision

These three terms appear throughout this audit and are not alternatives to each other — they are layers:

┌─────────────────────────────────────────────────────┐
│  MONOREPO  ← The structure (one repo, many apps)    │
│  ┌───────────────────────────────────────────────┐  │
│  │  npm workspaces  ← Package linking layer      │  │
│  │  ┌─────────────────────────────────────────┐  │  │
│  │  │  Turborepo  ← Build orchestration layer │  │  │
│  │  └─────────────────────────────────────────┘  │  │
│  └───────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────┘
Layer What it is What it does
Monorepo Repository structure All four apps live in one git repo (soreto-platform/)
npm workspaces npm built-in feature Links local packages (@soreto/types) across apps without publishing to npm
Turborepo Build tool Runs build/test/lint only on affected packages; caches results

The recommendation is all three, in layers. You can adopt them incrementally: monorepo first, npm workspaces from day one (it's just a field in package.json), Turborepo added once the structure is in place.


Tooling Comparison

Why npm workspaces (not pnpm): - All four repos already use npm and package-lock.json — zero migration cost - npm workspaces are built into npm 7+ (Node 16+), which the team already uses - No new tooling to learn or install; engineers already know npm install - pnpm's main advantages (disk deduplication, strict hoisting) are useful for very large teams but add friction for this team size - Turborepo works natively with npm workspaces

Why Turborepo: - Lower learning curve than Nx — minimal configuration required - Build cache is the primary value — no code generation (Nx's main differentiator) is needed - Active Vercel support and community - Incremental adoption path — add Turborepo config to an existing npm workspace with minimal disruption

What it provides: - turbo build — builds only affected packages - turbo test — tests only affected packages - Remote caching (Vercel Remote Cache or self-hosted) — CI speeds up significantly over time - Task dependency graph: run build before test, codegen before build

Option B: Nx

Why not (for now): - Higher configuration overhead - Code generation (generators, executors) is Nx's strength — not needed here - No evidence of Nx familiarity in the codebase (no nx.json found) - Better suited to large teams with standardized generator workflows

Option C: Yarn or pnpm Workspaces

Not recommended. All repos use npm — switching the package manager adds a migration step for zero benefit at this team scale. npm workspaces achieve the same result without tooling churn.

Option D: Keep as separate repos

Not recommended. The repos are already functionally a single system. Keeping them separate means accepting all the costs of a monolith (coordination, coupling) with none of the benefits (atomic changes, shared tooling, unified CI).


Feasibility Assessment

Pre-conditions

Condition Status
GitHub PAT revoked ⛔ Not done — blocks everything
All repos have .nvmrc ⚠️ Not present in any repo
soreto-melissa has CI ⚠️ No CI exists
soreto-zoe Knex upgraded ⚠️ Still on 0.16.3
seneca.js strategy decided ⚠️ No decision documented

Feasibility by Phase

Phase Feasibility Notes
Monorepo scaffold creation ✓ Immediately feasible npm workspaces + Turborepo setup takes 1–2 days
soreto-melissa migration ✓ Feasible after CI is added Modern stack, clean structure
reverb-backend migration ⚠️ Medium complexity Seneca + multi-process Procfile complicates extraction
soreto-zoe migration ⚠️ High complexity AMD TypeScript must be refactored first
reverb-react migration ✓ Low priority Freeze in place as apps/legacy-portal/

Maturity Assessment

Criterion Score Notes
Consistent package manager ✅ 4/4 All use npm — npm workspaces require no migration
TypeScript consistency ⚠️ 2/4 melissa, zoe yes; backend, react no
Test coverage ⚠️ 2/4 Some in backend/melissa; none in zoe
CI/CD discipline ❌ 2/4 Only backend and react (though react's CI is hollow)
Domain boundary clarity ⚠️ MEDIUM Some blurring — zoe calls backend directly
Shared code awareness ⚠️ MEDIUM Team appears aware of duplication but hasn't extracted yet

Assessment: The organization is ready for a simple monorepo (npm workspaces). It is not yet ready for a fully orchestrated Nx/Turborepo setup with code generation. Start simple — add orchestration as the team gains confidence.