Turborepo Architecture
Portable monorepo house style for Turborepo + Bun/pnpm workspaces. Use this skill alone — it does not depend on other skills.
Job: workspace wiring, package boundaries, turbo tasks/cache, root scripts.
Not this skill: Turbo CLI deep docs, remote cache vendor setup, or feature module trees inside an app (HTTP routes / Start pages belong to app architecture docs for that stack).
If the target repo already documents monorepo rules (e.g. AGENTS.md) and it
conflicts, repo wins unless the user asks to migrate toward this skill.
Stack defaults (core)
| Piece | Default |
|---|---|
| Package manager | Bun workspaces (pnpm/npm OK if already used) |
| Layout | apps/*, packages/* |
| Orchestration | Turborepo (turbo.jsonc / turbo.json) |
| Internal packages | "exports": { ".": "./src/index.ts" } — Bun runs TS directly |
| Env | Apps own .env + validated env; packages take .make(options) |
| Verification | Full graph for closing gates; --filter for mid-edit iteration |
Modes
- Scaffold — new app/package from checklist.md + tree.md.
- Apply — add scripts, deps, turbo participation correctly.
- Review — compare to rules.md; propose moves.
Hard rules (core)
- Workspaces + shared pins — shared deps via catalog (or equivalent); workspace deps via
workspace:*. - Turbo orchestrates scripts — it is not the package manager. Package scripts must not recurse into
turbo runfor the same task. transitDAG — use a no-scripttransit/toponode withdependsOn: ["^transit"]sotypecheck/testcan run in parallel while invalidating on dependency source changes.- Persistent / mutating tasks are uncached —
dev, watch servers,format,lint:fix,typegen→cache: false(+persistent: truefor long-lived processes). - Lint/format/test/typecheck are per-workspace scripts that Turbo runs (skip packages without the script).
- Apps own env; packages never read
process.envin library code — pass options into factories/Layers. - Internal entry =
./src/index.tsunless the package is publishable/built (dist/). - Filter by package name —
--filter=@org/api, not by accident-only folder paths. - Prefer clean breaking changes in active monorepos — delete shims/dual-writes/deprecated aliases in the same change; update callers together.
- Closing verification = full graph unless the user explicitly defers quality gates.
Details: rules.md, tree.md, examples.md.
Progressive disclosure
| Need | Read |
|---|---|
| Canonical trees | references/tree.md |
| Rules + anti-patterns | references/rules.md |
| Scaffold / review checklists | references/checklist.md |
| Good vs bad layouts | references/examples.md |
| Optional overlays | Extensions below |
Extensions
Load only when the matching stack is present (or the user asks):
| When | Extension |
|---|---|
Bun workspaces.catalog / named catalogs |
with-catalog.md |
| Root-only Knip | with-knip.md |
| Per-package Oxlint + Oxfmt | with-oxlint-oxfmt.md |
| Vitest scripts + turbo test tasks | with-vitest-workspaces.md |
App env vs package .make |
with-env-packages.md |
| Effect services in packages | with-effect-packages.md |
| Opt-in typegen / OpenAPI codegen | with-typegen.md |
Nested packages/<group>/* + second scope |
with-engine-nested.md |
turbo prune --docker + empty Root Directory |
with-docker-deploy.md |
| Explicit may/must-not import tables | with-dependency-boundaries.md |
| Root compose helpers for local infra | with-compose-local.md |
Boundary vs app architecture
This skill owns monorepo wiring and package boundaries. Inside an app, feature folder trees (Elysia modules, TanStack Start routes) belong to that app’s architecture guidance — do not invent a second HTTP/UI layout here.