# LetsBe Biz — Repository Structure **Date:** February 27, 2026 **Team:** Claude Opus 4.6 Architecture Team **Document:** 09 of 09 **Status:** Proposal — Competing with independent team --- ## Table of Contents 1. [Decision: Monorepo](#1-decision-monorepo) 2. [Turborepo Configuration](#2-turborepo-configuration) 3. [Directory Tree](#3-directory-tree) 4. [Package Architecture](#4-package-architecture) 5. [Dependency Graph](#5-dependency-graph) 6. [Migration Plan](#6-migration-plan) 7. [Development Workflow](#7-development-workflow) 8. [Monorepo Trade-offs](#8-monorepo-trade-offs) --- ## 1. Decision: Monorepo ### Why Monorepo? | Factor | Monorepo | Multi-Repo | Winner | |--------|---------|-----------|--------| | **Shared types** | Single source of truth; import directly | npm publish on every change; version drift | Monorepo | | **Atomic changes** | Change type + all consumers in one PR | Coordinate releases across repos | Monorepo | | **CI/CD** | One pipeline, matrix builds | Per-repo pipelines, dependency triggering | Monorepo | | **Code discovery** | `grep` across everything | Search multiple repos separately | Monorepo | | **Prisma schema** | One schema, shared by Hub and types | Duplicate or publish as package | Monorepo | | **Developer onboarding** | Clone one repo, `npm install`, done | Clone 3-4 repos, configure each | Monorepo | | **Build caching** | Turborepo caches across packages | Each repo builds independently | Monorepo | | **Independence** | Packages are more coupled | Fully independent deploy | Multi-Repo | | **Repo size** | Grows over time | Each repo stays lean | Multi-Repo | | **CI isolation** | Bad test in one package blocks others | Fully isolated | Multi-Repo | **Decision:** Monorepo with Turborepo. The shared types, Prisma schema, and tight coupling between Safety Wrapper ↔ Hub ↔ Secrets Proxy make a monorepo the clear winner. The provisioner (Bash) stays as a separate package within the monorepo but could also remain as a standalone repo if the team prefers — it has no TypeScript dependencies. ### What Stays Outside the Monorepo | Component | Reason | |-----------|--------| | **OpenClaw** | Upstream dependency. Pulled as Docker image. Not forked. | | **Tool Docker stacks** | Compose files and nginx configs live in the provisioner package. | | **Mobile app** | React Native/Expo has different build tooling. Lives in `packages/mobile` but uses its own `metro.config.js`. | --- ## 2. Turborepo Configuration ### `turbo.json` ```json { "$schema": "https://turbo.build/schema.json", "globalDependencies": ["**/.env.*local"], "pipeline": { "build": { "dependsOn": ["^build"], "outputs": ["dist/**", ".next/**"] }, "typecheck": { "dependsOn": ["^build"] }, "lint": {}, "test": { "dependsOn": ["^build"], "env": ["DATABASE_URL", "NODE_ENV"] }, "test:p0": { "dependsOn": ["^build"], "cache": false }, "test:p1": { "dependsOn": ["^build"], "cache": false }, "test:integration": { "dependsOn": ["build"], "cache": false }, "test:benchmark": { "dependsOn": ["build"], "cache": false }, "dev": { "cache": false, "persistent": true }, "db:push": { "cache": false }, "db:generate": { "outputs": ["node_modules/.prisma/**"] } } } ``` ### Root `package.json` ```json { "name": "letsbe-biz", "private": true, "workspaces": [ "packages/*" ], "scripts": { "build": "turbo run build", "dev": "turbo run dev --parallel", "test": "turbo run test", "test:p0": "turbo run test:p0", "test:integration": "turbo run test:integration", "lint": "turbo run lint", "typecheck": "turbo run typecheck", "format": "prettier --write \"packages/*/src/**/*.{ts,tsx}\"", "clean": "turbo run clean && rm -rf node_modules" }, "devDependencies": { "turbo": "^2.3.0", "prettier": "^3.4.0", "typescript": "^5.7.0" }, "engines": { "node": ">=22.0.0" } } ``` --- ## 3. Directory Tree ``` letsbe-biz/ ├── .gitea/ │ └── workflows/ │ ├── ci.yml # Monorepo CI (lint, typecheck, test) │ ├── safety-wrapper.yml # SW-specific pipeline │ ├── secrets-proxy.yml # SP-specific pipeline │ ├── hub.yml # Hub pipeline │ ├── integration.yml # Integration test pipeline │ ├── deploy-staging.yml # Auto-deploy to staging │ ├── deploy-hub.yml # Production Hub deploy │ └── tenant-update.yml # Tenant server rollout │ ├── packages/ │ ├── safety-wrapper/ # Safety Wrapper (localhost:8200) │ │ ├── src/ │ │ │ ├── index.ts # Entry point: HTTP server startup │ │ │ ├── server.ts # Express/Fastify HTTP server │ │ │ ├── config.ts # Configuration loading │ │ │ ├── classification/ │ │ │ │ ├── engine.ts # Command classification engine │ │ │ │ ├── shell-classifier.ts # Shell command allowlist + classification │ │ │ │ ├── docker-classifier.ts # Docker subcommand classification │ │ │ │ └── rules.ts # Classification rule definitions │ │ │ ├── autonomy/ │ │ │ │ ├── resolver.ts # Autonomy level resolution │ │ │ │ ├── external-comms.ts # External Communications Gate │ │ │ │ └── approval-queue.ts # Local approval queue (SQLite) │ │ │ ├── executors/ │ │ │ │ ├── shell.ts # Shell command executor (execFile) │ │ │ │ ├── docker.ts # Docker command executor │ │ │ │ ├── file.ts # File read/write executor │ │ │ │ └── env.ts # Env read/update executor │ │ │ ├── secrets/ │ │ │ │ ├── registry.ts # Encrypted SQLite secrets vault │ │ │ │ ├── injection.ts # SECRET_REF resolution │ │ │ │ └── api.ts # Secrets side-channel API │ │ │ ├── hub/ │ │ │ │ ├── client.ts # Hub communication (register, heartbeat, config) │ │ │ │ └── config-sync.ts # Config versioning and delta sync │ │ │ ├── metering/ │ │ │ │ ├── token-tracker.ts # Per-agent, per-model token tracking │ │ │ │ └── bucket.ts # Hourly bucket aggregation │ │ │ ├── audit/ │ │ │ │ └── logger.ts # Append-only audit log │ │ │ └── db/ │ │ │ ├── schema.sql # SQLite schema (secrets, approvals, audit, usage, state) │ │ │ └── migrations/ # SQLite migration files │ │ ├── test/ │ │ │ ├── p0/ │ │ │ │ ├── classification.test.ts # 100+ classification tests │ │ │ │ └── autonomy.test.ts # Level × tier matrix tests │ │ │ ├── p1/ │ │ │ │ ├── shell-executor.test.ts │ │ │ │ ├── docker-executor.test.ts │ │ │ │ └── hub-client.test.ts │ │ │ └── integration/ │ │ │ └── openclaw-routing.test.ts │ │ ├── Dockerfile │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── vitest.config.ts │ │ │ ├── secrets-proxy/ # Secrets Proxy (localhost:8100) │ │ ├── src/ │ │ │ ├── index.ts # Entry point │ │ │ ├── proxy.ts # HTTP proxy server (transparent) │ │ │ ├── redaction/ │ │ │ │ ├── pipeline.ts # 4-layer pipeline orchestrator │ │ │ │ ├── layer1-aho-corasick.ts # Registry-based exact match │ │ │ │ ├── layer2-regex.ts # Pattern safety net │ │ │ │ ├── layer3-entropy.ts # Shannon entropy filter │ │ │ │ └── layer4-json-keys.ts # Sensitive key name detection │ │ │ └── config.ts │ │ ├── test/ │ │ │ ├── p0/ │ │ │ │ ├── redaction.test.ts # 50+ redaction tests (TDD) │ │ │ │ ├── false-positives.test.ts # False positive prevention │ │ │ │ └── performance.test.ts # <10ms latency benchmark │ │ │ └── adversarial/ │ │ │ └── bypass-attempts.test.ts # Adversarial attack tests │ │ ├── Dockerfile │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── vitest.config.ts │ │ │ ├── hub/ # Hub (Next.js — existing codebase, migrated) │ │ ├── src/ │ │ │ ├── app/ # Next.js App Router (existing structure) │ │ │ │ ├── admin/ # Staff admin dashboard (existing) │ │ │ │ ├── api/ │ │ │ │ │ ├── auth/ # Authentication (existing) │ │ │ │ │ ├── v1/ │ │ │ │ │ │ ├── admin/ # Admin API (existing) │ │ │ │ │ │ ├── tenant/ # NEW: Safety Wrapper protocol │ │ │ │ │ │ │ ├── register/ │ │ │ │ │ │ │ ├── heartbeat/ │ │ │ │ │ │ │ ├── config/ │ │ │ │ │ │ │ ├── usage/ │ │ │ │ │ │ │ ├── approval-request/ │ │ │ │ │ │ │ └── approval-response/ │ │ │ │ │ │ ├── customer/ # NEW: Customer-facing API │ │ │ │ │ │ │ ├── dashboard/ │ │ │ │ │ │ │ ├── agents/ │ │ │ │ │ │ │ ├── usage/ │ │ │ │ │ │ │ ├── approvals/ │ │ │ │ │ │ │ ├── billing/ │ │ │ │ │ │ │ └── tools/ │ │ │ │ │ │ ├── orchestrator/ # DEPRECATED: keep for backward compat, redirect │ │ │ │ │ │ ├── public/ # Public API (existing) │ │ │ │ │ │ └── webhooks/ # Stripe webhooks (existing) │ │ │ │ │ └── cron/ # Cron endpoints (existing) │ │ │ │ └── login/ # Login page (existing) │ │ │ ├── lib/ │ │ │ │ ├── services/ # Business logic (existing + new) │ │ │ │ │ ├── automation-worker.ts # Existing │ │ │ │ │ ├── billing-service.ts # NEW: Token billing, Stripe Meters │ │ │ │ │ ├── chat-relay-service.ts # NEW: App→Hub→SW→OpenClaw │ │ │ │ │ ├── config-generator.ts # Existing (updated) │ │ │ │ │ ├── push-notification.ts # NEW: Expo Push service │ │ │ │ │ ├── tenant-protocol.ts # NEW: SW registration/heartbeat │ │ │ │ │ └── ... # Other existing services │ │ │ │ └── ... │ │ │ ├── hooks/ # React Query hooks (existing) │ │ │ └── components/ # UI components (existing) │ │ ├── prisma/ │ │ │ ├── schema.prisma # Shared Prisma schema (existing + new models) │ │ │ ├── migrations/ # Prisma migrations │ │ │ └── seed.ts # Database seeding │ │ ├── test/ │ │ │ ├── unit/ # Existing unit tests (10 files) │ │ │ ├── api/ # NEW: API endpoint tests │ │ │ └── integration/ # NEW: Hub↔SW protocol tests │ │ ├── Dockerfile │ │ ├── package.json │ │ ├── next.config.ts │ │ └── tsconfig.json │ │ │ ├── website/ # Website (letsbe.biz — separate Next.js app) │ │ ├── src/ │ │ │ ├── app/ │ │ │ │ ├── page.tsx # Landing page │ │ │ │ ├── onboarding/ # AI-powered onboarding flow │ │ │ │ │ ├── business/ # Step 1: Business description │ │ │ │ │ ├── tools/ # Step 2: Tool recommendation │ │ │ │ │ ├── customize/ # Step 3: Customization │ │ │ │ │ ├── server/ # Step 4: Server selection │ │ │ │ │ ├── domain/ # Step 5: Domain setup │ │ │ │ │ ├── agents/ # Step 6: Agent config (optional) │ │ │ │ │ ├── payment/ # Step 7: Stripe checkout │ │ │ │ │ └── status/ # Step 8: Provisioning status │ │ │ │ ├── demo/ # Interactive demo page │ │ │ │ └── pricing/ # Pricing page │ │ │ └── lib/ │ │ │ ├── ai-classifier.ts # Gemini Flash business classifier │ │ │ └── resource-calc.ts # Resource requirement calculator │ │ ├── Dockerfile │ │ ├── package.json │ │ └── tsconfig.json │ │ │ ├── mobile/ # Mobile App (Expo Bare Workflow) │ │ ├── src/ │ │ │ ├── screens/ │ │ │ │ ├── LoginScreen.tsx │ │ │ │ ├── ChatScreen.tsx │ │ │ │ ├── DashboardScreen.tsx │ │ │ │ ├── ApprovalsScreen.tsx │ │ │ │ ├── UsageScreen.tsx │ │ │ │ ├── SettingsScreen.tsx │ │ │ │ └── SecretsScreen.tsx │ │ │ ├── components/ │ │ │ ├── hooks/ │ │ │ ├── stores/ # Zustand stores │ │ │ ├── services/ # API client, push notifications │ │ │ └── navigation/ # React Navigation │ │ ├── app.json │ │ ├── eas.json # EAS Build + Update config │ │ ├── metro.config.js │ │ ├── package.json │ │ └── tsconfig.json │ │ │ ├── shared-types/ # Shared TypeScript types │ │ ├── src/ │ │ │ ├── classification.ts # Command classification types │ │ │ ├── autonomy.ts # Autonomy level types │ │ │ ├── secrets.ts # Secrets registry types │ │ │ ├── protocol.ts # Hub ↔ SW protocol types │ │ │ ├── billing.ts # Token metering types │ │ │ ├── agents.ts # Agent configuration types │ │ │ └── index.ts # Barrel export │ │ ├── package.json │ │ └── tsconfig.json │ │ │ ├── shared-prisma/ # Shared Prisma client (generated) │ │ ├── prisma/ │ │ │ └── schema.prisma # → symlink to packages/hub/prisma/schema.prisma │ │ ├── package.json │ │ └── tsconfig.json │ │ │ └── provisioner/ # Provisioner (Bash — migrated from letsbe-ansible-runner) │ ├── provision.sh # Main entry point │ ├── steps/ │ │ ├── step-01-system-update.sh │ │ ├── step-02-docker-install.sh │ │ ├── step-03-create-user.sh │ │ ├── step-04-generate-secrets.sh │ │ ├── step-05-deploy-stacks.sh │ │ ├── step-06-nginx-configs.sh │ │ ├── step-07-ssl-certs.sh │ │ ├── step-08-backup-setup.sh │ │ ├── step-09-firewall.sh │ │ └── step-10-deploy-ai.sh # REWRITTEN: OpenClaw + Safety Wrapper │ ├── stacks/ # Docker Compose files for 28+ tools │ │ ├── chatwoot/ │ │ │ └── docker-compose.yml │ │ ├── nextcloud/ │ │ │ └── docker-compose.yml │ │ ├── letsbe/ # NEW: LetsBe AI stack │ │ │ └── docker-compose.yml # OpenClaw + Safety Wrapper + Secrets Proxy │ │ └── ... │ ├── nginx/ # nginx configs for 33+ tools │ ├── templates/ # Config templates │ │ ├── openclaw-config.json5.tmpl │ │ ├── safety-wrapper.json.tmpl │ │ ├── tool-registry.json.tmpl │ │ └── agent-templates/ # Per-business-type agent configs │ ├── references/ # Tool cheat sheets (deployed to tenant) │ │ ├── portainer.md │ │ ├── nextcloud.md │ │ ├── chatwoot.md │ │ ├── ghost.md │ │ ├── calcom.md │ │ ├── stalwart.md │ │ └── ... │ ├── skills/ # OpenClaw skills (deployed to tenant) │ │ └── letsbe-tools/ │ │ └── SKILL.md # Master tool skill │ ├── agents/ # Default agent configs (deployed to tenant) │ │ ├── dispatcher/ │ │ │ └── SOUL.md │ │ ├── it-admin/ │ │ │ └── SOUL.md │ │ ├── marketing/ │ │ │ └── SOUL.md │ │ ├── secretary/ │ │ │ └── SOUL.md │ │ └── sales/ │ │ └── SOUL.md │ ├── test/ │ │ ├── step-10.bats # bats-core tests for step 10 │ │ ├── cleanup.bats # n8n cleanup verification │ │ └── full-run.bats # Full provisioner integration test │ ├── Dockerfile │ └── package.json # Minimal — just for monorepo workspace inclusion │ ├── test/ # Cross-package integration tests │ ├── docker-compose.integration.yml # Full stack for integration tests │ ├── fixtures/ │ │ ├── openclaw-config.json5 │ │ ├── safety-wrapper-config.json │ │ ├── tool-registry.json │ │ └── test-secrets.json │ └── e2e/ │ ├── signup-to-chat.test.ts │ ├── approval-flow.test.ts │ └── secrets-never-leak.test.ts │ ├── docs/ # Documentation (existing) │ ├── technical/ │ ├── strategy/ │ ├── legal/ │ └── architecture-proposal/ │ └── claude/ # This proposal │ ├── turbo.json ├── package.json # Root workspace config ├── tsconfig.base.json # Shared TypeScript config ├── .gitignore ├── .eslintrc.js # Shared ESLint config ├── .prettierrc └── README.md ``` --- ## 4. Package Architecture ### Package Responsibilities | Package | Language | Purpose | Depends On | Deployed As | |---------|----------|---------|-----------|-------------| | `safety-wrapper` | TypeScript | Command gating, tool execution, Hub comm, audit | `shared-types` | Docker container on tenant VPS | | `secrets-proxy` | TypeScript | LLM traffic redaction (4-layer pipeline) | `shared-types` | Docker container on tenant VPS | | `hub` | TypeScript (Next.js) | Admin dashboard, customer portal, billing, tenant protocol | `shared-types`, `shared-prisma` | Docker container on central server | | `website` | TypeScript (Next.js) | Marketing site, onboarding flow | — | Docker container on central server | | `mobile` | TypeScript (Expo) | Customer mobile app | `shared-types` | iOS/Android app (EAS Build) | | `shared-types` | TypeScript | Type definitions shared across packages | — | npm workspace dependency | | `shared-prisma` | TypeScript | Generated Prisma client | — | npm workspace dependency | | `provisioner` | Bash | VPS provisioning scripts, tool stacks | — | Docker container (on-demand) | ### Package Size Estimates | Package | Estimated LOC | Files | Build Output | |---------|--------------|-------|-------------| | `safety-wrapper` | ~3,000-4,000 | ~30 | ~200KB JS | | `secrets-proxy` | ~1,500-2,000 | ~15 | ~100KB JS | | `hub` | ~15,000+ (existing) + ~3,000 new | ~250+ | Next.js standalone | | `website` | ~2,000-3,000 | ~20 | Next.js standalone | | `mobile` | ~4,000-5,000 | ~40 | Expo bundle | | `shared-types` | ~500-800 | ~10 | ~50KB JS | | `provisioner` | ~5,000 (existing + new) | ~50+ | Bash scripts | --- ## 5. Dependency Graph ``` ┌──────────────┐ │ shared-types │ └──────┬───────┘ ┌────────────┼────────────┬────────────┐ │ │ │ │ ┌────────▼──────┐ ┌──▼────────┐ ┌─▼──────┐ ┌──▼──────┐ │safety-wrapper │ │secrets- │ │ hub │ │ mobile │ │ │ │proxy │ │ │ │ │ └───────────────┘ └───────────┘ └────┬───┘ └─────────┘ │ ┌──────▼──────┐ │shared-prisma│ └─────────────┘ ┌───────────┐ ┌───────────┐ │ website │ │provisioner│ │(no deps) │ │(Bash, no │ │ │ │ TS deps) │ └───────────┘ └───────────┘ ``` **Key constraints:** - `shared-types` has ZERO dependencies. It's pure TypeScript type definitions. - `shared-prisma` depends only on Prisma and the schema file. - `safety-wrapper` and `secrets-proxy` never import from `hub` (no circular deps). - `hub` never imports from `safety-wrapper` or `secrets-proxy` (communication via HTTP protocol). - `website` is fully independent — no shared package dependencies. - `provisioner` is Bash — no TypeScript dependencies at all. --- ## 6. Migration Plan ### Current State (5 Separate Repos) ``` letsbe-hub → packages/hub (TypeScript, Next.js) letsbe-ansible-runner → packages/provisioner (Bash) letsbe-orchestrator → DEPRECATED (capabilities → safety-wrapper) letsbe-sysadmin-agent → DEPRECATED (capabilities → safety-wrapper) letsbe-mcp-browser → DEPRECATED (replaced by OpenClaw native browser) ``` ### Migration Steps #### Step 1: Create Monorepo (Week 1, Day 1-2) ```bash # Create new repo mkdir letsbe-biz && cd letsbe-biz git init npm init -y # Install Turborepo npm install turbo --save-dev # Create workspace structure mkdir -p packages/{safety-wrapper,secrets-proxy,hub,website,mobile,shared-types,shared-prisma,provisioner} # Create turbo.json (from Section 2) # Create root package.json (from Section 2) # Create tsconfig.base.json ``` #### Step 2: Migrate Hub (Week 1, Day 1) ```bash # Copy Hub source (preserve git history via subtree or fresh copy) cp -r ../letsbe-hub/src packages/hub/src cp -r ../letsbe-hub/prisma packages/hub/prisma cp ../letsbe-hub/package.json packages/hub/ cp ../letsbe-hub/next.config.ts packages/hub/ cp ../letsbe-hub/tsconfig.json packages/hub/ cp ../letsbe-hub/Dockerfile packages/hub/ # Update Hub package.json: # - name: "@letsbe/hub" # - Add workspace dependency on shared-types, shared-prisma # Verify Hub builds cd packages/hub && npm install && npm run build ``` #### Step 3: Migrate Provisioner (Week 1, Day 1) ```bash # Copy provisioner scripts cp -r ../letsbe-ansible-runner/* packages/provisioner/ # Add minimal package.json for workspace inclusion echo '{"name":"@letsbe/provisioner","private":true}' > packages/provisioner/package.json ``` #### Step 4: Create New Packages (Week 1, Day 2) ```bash # shared-types — create from scratch cd packages/shared-types npm init -y --scope=@letsbe # Add type definitions # safety-wrapper — create from scratch cd packages/safety-wrapper npm init -y --scope=@letsbe # Scaffold Express/Fastify server # secrets-proxy — create from scratch cd packages/secrets-proxy npm init -y --scope=@letsbe # Scaffold HTTP proxy ``` #### Step 5: Verify Everything Works (Week 1, Day 2) ```bash # From repo root: npm install # Install all workspace dependencies turbo run build # Build all packages turbo run typecheck # Type check all packages turbo run test # Run all tests (Hub's existing 10 tests) turbo run lint # Lint all packages ``` #### Step 6: Archive Old Repos (Week 2) Once the monorepo is confirmed working and the team has switched: 1. Mark `letsbe-orchestrator` as archived (deprecated) 2. Mark `letsbe-sysadmin-agent` as archived (deprecated) 3. Mark `letsbe-mcp-browser` as archived (deprecated) 4. Keep `letsbe-hub` and `letsbe-ansible-runner` read-only for reference 5. Update Gitea CI to point to new monorepo ### Git History Preservation **Option A (Recommended): Fresh start with reference.** - New monorepo gets a clean git history. - Old repos remain accessible (read-only archive) for historical reference. - This is cleaner and avoids complex git subtree merges. **Option B: Preserve history via git subtree.** - Use `git subtree add` to bring Hub and provisioner history into the monorepo. - More complex but preserves `git blame` lineage. **Recommendation:** Option A. The codebase is being substantially restructured. Historical blame on the old code is less valuable than a clean starting point. The old repos stay available for reference. --- ## 7. Development Workflow ### Daily Development ```bash # Start all dev servers (Hub + Safety Wrapper + Secrets Proxy) turbo run dev --parallel # Run tests for a specific package turbo run test --filter=safety-wrapper # Run P0 tests only turbo run test:p0 # Build a specific package turbo run build --filter=secrets-proxy # Type check everything turbo run typecheck # Lint everything turbo run lint ``` ### Adding a Shared Type ```bash # 1. Add type to packages/shared-types/src/classification.ts # 2. Export from index.ts # 3. Import in consuming package: # import { CommandTier } from '@letsbe/shared-types'; # 4. Turbo automatically rebuilds shared-types before dependent packages ``` ### Adding a New Package ```bash # 1. Create directory mkdir packages/new-package # 2. Initialize cd packages/new-package npm init -y --scope=@letsbe # 3. Add to root workspaces (already covered by packages/* glob) # 4. Add to turbo.json pipeline if needed # 5. Add Dockerfile if it's a deployed service ``` ### Docker Development ```yaml # docker-compose.dev.yml (root level, for local development) services: postgres: image: postgres:16-alpine ports: ['5432:5432'] environment: POSTGRES_DB: hub_dev POSTGRES_USER: hub POSTGRES_PASSWORD: devpass hub: build: context: . dockerfile: packages/hub/Dockerfile ports: ['3000:3000'] environment: DATABASE_URL: postgresql://hub:devpass@postgres:5432/hub_dev depends_on: [postgres] safety-wrapper: build: context: . dockerfile: packages/safety-wrapper/Dockerfile ports: ['8200:8200'] secrets-proxy: build: context: . dockerfile: packages/secrets-proxy/Dockerfile ports: ['8100:8100'] ``` --- ## 8. Monorepo Trade-offs ### Advantages Realized | Advantage | Concrete Benefit | |-----------|-----------------| | **Atomic type changes** | Change `CommandTier` enum in `shared-types` → all consumers updated in same PR | | **Turborepo caching** | Rebuild only changed packages; CI runs ~60% faster after first run | | **Shared tooling** | One ESLint config, one Prettier config, one TypeScript base config | | **Cross-package refactoring** | Rename a protocol field → update Safety Wrapper + Hub in one commit | | **Single dependency tree** | No version conflicts between packages; hoisted node_modules | | **Simplified onboarding** | Clone one repo → `npm install` → `turbo run dev` → everything running | ### Disadvantages Accepted | Disadvantage | Mitigation | |-------------|------------| | **Larger repo size** | Turborepo's `--filter` flag runs only affected packages | | **Bash in TypeScript monorepo** | Provisioner is loosely coupled — workspace inclusion is just for organization | | **Mobile build complexity** | Expo has its own build system (EAS); it coexists but doesn't use Turbo for builds | | **CI runs all checks** | Path-based triggers (see pipeline YAML) skip unrelated packages | | **Single repo = single SPOF** | Gitea backup strategy; consider GitHub mirror for disaster recovery | ### When to Reconsider The monorepo should be split if: - The team grows beyond 8-10 engineers and package ownership boundaries become clear - Mobile app development cadence diverges significantly from backend - A package needs a fundamentally different build system or language (e.g., Rust Safety Wrapper rewrite) - CI times exceed 20 minutes even with caching None of these are likely before reaching 100 customers. --- *End of Document — 09 Repository Structure*