Status: Active Owner: Platform Architecture Phase: 1.A ofDocumentation Index
Fetch the complete documentation index at: https://docs.encoreos.io/llms.txt
Use this file to discover all available pages before exploring further.
docs/recommendations/DEV_VELOCITY_AND_ARCHITECTURE_PLAN_2026-04-22.md
This repo is wired to Turborepo with npm workspaces (overlay packages under packages/* — see ADR-016). Turborepo provides content-addressed caching for typecheck, lint:ci, format:check, test:unit, test:integration, and build. When inputs do not change, the cached result is reused — locally and (when Remote Cache secrets are set) across CI runners.
Adopting Turborepo does not change the primary Vite application layout: root package.json, vite.config.ts, and the main Vercel project remain canonical. Turborepo wraps the existing npm scripts and accelerates CI/local iteration. Turborepo is not Turbopack (Next.js’s bundler); see ADR-019.
Quick reference
npm run typecheck / npm run lint:ci / npm run format:check / npm run build scripts still exist for direct runs. npm run validate orchestrates the four gates through Turborepo (same graph as npm run turbo:validate). Use plain scripts when you want a single step without the full graph (e.g., typecheck only for a baseline measurement).
Why Turborepo here
Today the repo is 8,282 TS/TSX files, 859 SQL migrations, 355 edge functions, 1,657 test files (perreports/perf/baseline-2026-04-22.md). Every PR that doesn’t touch src/ still pays the full cost of typecheck, lint, format check, unit tests. Turborepo caches each of those by content hash of declared inputs, so:
- A PR that only changes files outside task
inputs(e.g. manyspecs/ordocs/markdown-only edits) can see format/typecheck/lint/build cache hits and finish much faster once caches are warm. - A PR touching only
src/cores/hr/*invalidatestypecheck,lint:ci,test:unit,build— but notformat:check(different input set). - The cache key includes
tsconfig*.json,package-lock.json, the relevant config files (vite.config.ts,vitest.config.ts,biome.json,eslint.config.js), and the input files themselves.
typecheck / build scripts exist on workspace packages — at that point turbo run typecheck --filter=...[origin/prod] can narrow work to affected subgraphs. Today, root //# tasks compile the full src/ tree (see turbo.json).
Configuration
Seeturbo.json. Key decisions:
globalDependencieslists tracked config files that, if changed, must invalidate every cache entry (tsconfig*.json,vite.config.ts,vitest.config.ts,biome.json,eslint.config.js,package*.json)..env/.env.*are not global deps — they are listed on the//#buildtaskinputsso local env churn does not bust typecheck/lint/format caches. If a change should invalidate every task, add it here deliberately.globalEnvlists env vars whose values affect every task (NODE_ENV,CI,VERCEL_PROJECT_ID).- Each task declares its
inputsexplicitly. We do not use the default “everything in the workspace” input set — that would cause spurious cache invalidations on docs/spec edits. //#builddeclaresdependsOn: ["//#typecheck"]soturbo run buildalways waits on typecheck; a cache hit on typecheck satisfies the dependency without re-running it.deviscache: false, persistent: true.
Sandboxing caveats
Turborepo 2.x has no task sandboxing — tasks can read files outside their declaredinputs. If a script secretly reads (for example) docs/ or specs/, the cache will return false hits when those files change. Mitigations:
- Run
npm run turbo:cache:statusbefore merging PRs that change input declarations to confirm hashes change as expected. - When in doubt,
npx turbo run <task> --forceto bypass cache. - If we hit recurring cache poisoning, that is the trigger to revisit Nx (which provides task sandboxing).
Vercel Remote Cache (optional; recommended)
Local.turbo/ is already restored across GitHub Actions runs via actions/cache on .turbo/. Vercel Remote Cache adds cross-machine reuse (developers ↔ CI ↔ preview) when credentials exist.
- In the Vercel dashboard, open each project that runs Turborepo (
encoreos_dev,encoreos_prod, and any futureencoreos-publicproject). - Project Settings → General → “Build & Development Settings” → ensure “Remote Cache” is enabled (on by default for eligible plans).
- Locally:
npx turbo loginthennpx turbo linkonce per workstation (token in~/.turbo/config.json). - In GitHub: add repository secrets
TURBO_TOKENandTURBO_TEAM(orTURBO_TEAMas a variable if you prefer non-secret team slug)..github/workflows/build.ymlpasses them as job-levelenvso everynpx turbo run …step can upload/download remote artifacts. - Verify cache hits: open a PR, push an empty commit, and compare Turbo output on the second run — or run locally:
Remote hits show as remote cache reads in the summary (exact wording varies by Turbo version).
TURBO_TOKEN is unset, Turborepo still works; CI relies on the Actions cache of .turbo/ plus per-task input hashing.
Docs: https://vercel.com/docs/monorepos/remote-caching
Wiring into CI
.github/workflows/build.yml runs Typecheck, Lint, and Build through Turborepo (npx turbo run typecheck / lint:ci / build). The build job sets TURBO_TOKEN / TURBO_TEAM from secrets (see above). A GitHub Actions cache step restores .turbo/ across runs:
Format check (Biome) is intentionally NOT wrapped in Turborepo — it stays as the plain npm run format:check because the codebase has known pre-existing format issues (per AGENTS.md § Cursor Cloud caveats), and we want zero behavior change in this CI-wiring PR. A future PR can switch it once those are resolved.
Unit and integration tests (the test:coverage step) also stays on plain npm because Turborepo correctly does not cache failed test runs — the codebase has 2 pre-existing test failures (per AGENTS.md) which would mean every PR re-runs the suite from scratch anyway. Once those are resolved, switching to npx turbo run test:unit will start producing cache hits.
Vercel Remote Cache in CI
The build workflow sets job-levelenv for TURBO_TOKEN / TURBO_TEAM when secrets exist. A diagnostic step prints whether the token is present (never logs the token). See the workflow file and the section above for setup.
Measured wins on this branch
Captured on the Cursor Cloud VM (Linux, 4 vCPU / 16 GB RAM):| Task | Cold (cache miss) | Warm (cache hit) | Speedup |
|---|---|---|---|
turbo run typecheck | 10 m 49 s | 345 ms (FULL TURBO) | ~1880× |
turbo run lint:ci | 1 m 28 s | 5.3 s (ESLint cache) | ~17× |
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
”Could not resolve workspaces. Missing packageManager field” | Older package.json without packageManager | Already fixed; packageManager: "npm@..." is set. |
| Cache miss when you expected a hit | An input you didn’t declare changed | npx turbo run <task> --dry-run --output-logs=full shows the resolved input set per task. |
| Cache hit when you expected a miss (wrong result returned) | An undeclared input was modified | Add it to the task’s inputs (or to globalDependencies if it affects every task). |
Vite bundle wrong after only editing .env.local | Build cache hit without env in fingerprint | .env* paths are on //#build inputs; run npx turbo run build --force if you suspect a stale env fingerprint. |
lint:ci cache never hits | Lint exits non-zero (25 pre-existing errors on prod) | Expected; Turbo correctly does not cache failed runs. ESLint’s own --cache still helps within a single run. |
| Local cache out of date / wrong | Bust it | rm -rf .turbo node_modules/.cache && npx turbo run typecheck --force |
| CI never shows remote cache hits | TURBO_TOKEN / TURBO_TEAM unset, wrong team slug, or token revoked | Re-create secrets from turbo login / Vercel; confirm team slug matches turbo link / dashboard. |
Unable to find packages referenced in microfrontends.json | Application keys are not npm workspace package names | Ensure each key matches a packages/*/package.json name (see packages/encoreos-app / packages/encoreos-public). Run npm run audit:microfrontends. |
See also
turbo.json— task definitionsdocs/recommendations/DEV_VELOCITY_AND_ARCHITECTURE_PLAN_2026-04-22.md— Phase 1 plan- Turborepo docs
- Vercel Remote Cache docs