Back to docs
Guide 10

Development

Guide to contributing to the codebase: setup, monorepo structure, scripts, conventions, i18n.

4 min read

Guide to contributing to the codebase: setup, monorepo structure, scripts, conventions, i18n.

Prerequisites: Node.js >=24.0.0 (enforced by engines.node in the root package.json), pnpm 12.8.1 (pinned via packageManager), PostgreSQL 16 (DEVELOPMENT.md row-time).

Setup

bash
git clone <repo> && cd simmetric-chat
pnpm install            # installs dependencies for every package
cp .env.example .env    # root .env = single runtime config; then edit JWT_SECRET, COLLECTOR_SECRET
pnpm db:generate        # Prisma client (+ Prisma 7/pnpm symlink fix)
pnpm db:migrate         # applies the migrations (prisma migrate dev)
pnpm db:seed            # roles, permissions, config, admin bootstrap
pnpm dev                # starts server/collector/widget/frontend via turborepo

PostgreSQL 16 locally (e.g. docker compose -f docker/docker-compose.infra.yml up -d postgres). pnpm db:migrate applies the Prisma migrations (packages/server/prisma/migrations); pnpm db:seed seeds roles/permissions and the admin bootstrap (admin / admin123, mustChangePassword=true). Migrations are additive-only: destructive ones require explicit consent (PRISMA_USER_CONSENT_FOR_DANGEROUS_AI_ACTION) and pnpm audit:migrations regenerates docs/MIGRATION_AUDIT.md, to be committed in the same PR.

Monorepo structure

pnpm workspaces + Turborepo. 5 packages + Tauri:

PackagePortStackRole
shared-TS + ZodTypes, schemas, constants (leaf, depends only on zod)
server3000Express + PrismaAPI, RBAC, agent, RAG FTS/RRF, backup
collector3210ExpressIngestion, parsing, embedding, vector store
widget3211Express + Preact IIFEEmbeddable widget
frontend5173Vite + React 19 + TailwindAdmin/chat SPA
src-tauri-Rust + TauriDesktop app

Dependency graph: shared ← {server, collector, frontend, widget}. Strict modularity: server↔collector = HTTP only (COLLECTOR_SECRET); widget↔server = API key (X-Api-Key). No direct imports among server/collector/widget.

Common scripts

ScriptScopeDescription
pnpm devrootTurborepo: starts all services in parallel
pnpm buildrootProduction build for all packages (depends on ^build)
pnpm lintrootESLint 10 flat config, all packages
pnpm typecheckrootTypeScript strict mode
pnpm testrootJest (5 projects, Postgres-free) via Turborepo
pnpm test:allrootSame 5 suites via jest.config.cjs (plain Jest)
pnpm test:e2erootPlaywright
pnpm db:generaterootRegenerates the Prisma client
pnpm db:migrateserverprisma migrate dev
pnpm db:seedserverSeeds roles/permissions/config
pnpm audit:migrationsserverAudits destructive migrations → docs/MIGRATION_AUDIT.md
pnpm i18n:checkrootValidates 8-locale translation parity
pnpm kniprootDead-code gate (unused files/deps/exports)
pnpm license:checkserverVerifies the configured license without booting the server

Per-package scripts in packages/<pkg>/package.json. Turborepo caches outputs and dependencies across tasks. pnpm db:generate must run before build/lint/typecheck/test (the turbo build task depends on it).

Code conventions

  • TypeScript strict everywhere; no unjustified any (target ES2022).
  • Zod for input validation (shared defines the reusable schemas; validate with safeParse in route handlers → 400 instead of 500).
  • Prisma as ORM (Prisma 7, driver adapter); schema in packages/server/prisma/schema.prisma. Never new PrismaClient(): singleton from src/utils/prisma (driver-adapter pool + withSoftDelete()).
  • RBAC via middleware: requirePermission, requireAdmin, requireWorkspaceAccess (44 permissions in shared/src/constants/permissions.ts).
  • Error handling: { error } responses (400/401/403/404/409/402/429/500); Zod validation → 400 with details; license gating → 402 { error, feature, tier }.
  • SSE for chat streaming (@microsoft/fetch-event-source on the frontend).
  • Logging: LOG_LEVEL env; use the configured logger, not bare console.log in prod.
  • Commits: Conventional Commits format (feat:, fix:, docs:, refactor:...); one logical change per commit; a [Unreleased] bullet in CHANGELOG.md for every change to packages/*/src/** (pnpm changelog:check).

Frontend (React 19)

  • 3-tier state (server-persisted / session / local) - TanStack Query for REST/CRUD, React Context for UI state, fetchEventSource for SSE.
  • shadcn/ui + Tailwind for the admin; the widget uses Preact + Tailwind, NO shadcn (IIFE bundle).
  • i18n via hooks; files in packages/frontend/src/i18n/<locale>/translation.json.
  • Chat shortcuts: /model (Cmd+K palette), comparison Cmd+Shift+M.
  • ESLint: React Compiler + rules-of-hooks error in source; module format ESM (root + frontend) vs CommonJS (server/collector/shared/widget) - respect each package's tsconfig.
  • The Vite proxy retry hook must NOT be removed: the server (tsx watch) boots slower than Vite (~1s); packages/frontend/vite.config.ts installs an onError handler that retries ECONNREFUSED up to 8 times over ~4s (GET/HEAD only - POST/SSE bodies are never replayed), then falls back to a single concise 502. The default handler dumps an AggregateError stack on every cold start.

Server (Express)

  • Routes in src/routes/, services in src/services/, middleware in src/middleware/.
  • Agent in src/agent/ (orchestrator.ts, builtinSkills.ts, mcpClient.ts).
  • Env config in src/config/env.ts (Zod, process.exit(1) when invalid).
  • OpenAPI via @openapi JSDoc annotations → Swagger at /api-docs.
  • pg-boss job queue (Postgres-backed): 12 cron-style schedulers (MCP health-check, MCP reaper, synthesis reaper, weekly fidelity sampling, vector cleanup, upload-draft reaping, chat-message retention, hourly token-quota reset, DLP document scan/backfill, MCP OAuth token refresh, daily connector health sweep) + a self-heal queue guard; initWikiConsistencyScheduler runs as an in-process interval. The OCR + synthesis pipelines stay on setInterval 10s pollers (dev and prod). Multi-instance-ready with no extra infrastructure (see 02 - Architecture).

i18n

8 locales with concrete files: en, it, ru, de, fr, es, zh, pt (EN is the baseline; strict parity - no locale may lag). Add keys to all eight files. pnpm i18n:check (frontend + widget) exits 1 on missing or extra keys in any locale relative to en; the widget check also fails on empty-string values. The widget locale set is pinned against the frontend ALL_LANGUAGES by widgetLocalesParity.test.ts - add/remove locales in both places together.

Adding a feature (workflow)

  1. Shared schema - types/Zod in packages/shared/src/.
  2. Prisma - model + migration (pnpm db:migrate; audit if destructive).
  3. Server - service + route + RBAC/license middleware.
  4. Frontend - UI + hooks + i18n (all 8 locales).
  5. Tests - unit (Prisma mock) + integration (worker DB) + E2E if user-facing.
  6. Docs - update docs/ and the OpenAPI @openapi; [Unreleased] changelog when touching src/.

Common extensions

  • New agent skill: registerSkill in server/src/agent/builtinSkills.ts.
  • New endpoint: route in server/src/routes/ + mount in src/index.ts.
  • New permission: PERMISSION_NAMES in shared/src/constants/permissions.ts + roles.
  • New LLM provider: preset in shared/src/constants/providerPresets.ts (seeded into provider_presets) + handler in server/src/agent/llmStreaming.ts + providerService.
  • New backup destination: implement IBackupDestinationProvider in the Enterprise plugin's backup providers (services/backup/providers/).

v0.28.1 Provenance

StatementSource
44 permissions (PERMISSION_NAMES)packages/shared/src/constants/permissions.ts (row-time count, 35-02 probe) - F-RBAC-1
Node.js >=24.0.0 (engines.node), pnpm 12.8.1 (packageManager), PostgreSQL 16docs/DEVELOPMENT.md (Prerequisites, row-time)
The Vite proxy retry hook (8 GET/HEAD retries, POST/SSE never replayed)docs/DEVELOPMENT.md (Proxy, row-time)
pg-boss: 12 cron-style schedulers + self-heal guard; wiki-consistency in-process; OCR/URL + synthesis 10s pollersdocs/ARCHITECTURE.md:175-176 (row-time)

Operational reference - Simmetric Chat v0.28.1 (docs updated 2026-10-02, census SHA `aca5b8ff`).