For repos with several apps/services (API + SPA + …): pnpm workspaces, one root lockfile, one app = one directory = one container.
- Every app dir has its own
package.json,Dockerfile(build context at the repo root so shared code is reachable), and env templates. - Shared code is a real workspace package with a
@scope/namespecifier, listed inpnpm-workspace.yamland imported by name - never by relative path. - A root
tsconfig.base.jsonthat every app extends; TypeScript and @types versions are managed once at the root, not per-app at drifting versions. - Script contract: every package implements
dev,build,test,lint. The root fans out (pnpm -r run test). A missing script must fail, never silently no-op. - One version source of truth (root
package.json); no manual "version bump" commits per app. - Commit scope = app directory name (
feat(api): …,fix(app): …). - Lint/format configured at the root (eslint + prettier, or biome).
- Committed dotless env templates per environment (
env.dev,env.dist,env.test.dist); CI copies the right one to a gitignored.env. The root env holds only cross-service ports. docker compose -p <project>-${ENV}namespaces isolated stacks per environment on a shared host; deploy jobs guard onhostname != $DEPLOY_HOST.- Per-app CI jobs with
changes:path rules - a frontend fix does not rebuild and redeploy the API. Tests and lint run before any deploy job.
- vitest. Hermetic DB integration tests via mongodb-memory-server +
globalSetup. - Slow or non-deterministic suites (LLM evals) get their own vitest config
and script, kept out of the default
testrun. - LLM/agent regression fixtures live in numbered case dirs
(
tests/cases/<agent>/001_slug/) with snapshots resolved back into the case dir; prompts are first-class source (src/prompts/<agent>/) mirrored 1:1 by their test suites.