Files
agentic-dev/AGENTS.md
Danijel Martinek 2c2375920f docs: reflect tooling-package rename + Turbo boundaries enforcement
- Rename eslint-config → core-eslint, typescript-config → core-typescript
  in all docs (package map, AGENTS.md, overview.md, dependency-flow.md, etc.)
- Document the five-tag model (app, feature, core, core-composition,
  tooling) — refinement of ADR-006's three-tag mention
- Document core-trpc's core-composition tag (transitively reaches features
  through core-api's AppRouter type)
- Note Turborepo boundaries as a second enforcement layer alongside ESLint
- Add ADR-010 explaining the two-layer enforcement decision and
  five-tag refinement

Files updated:
- docs/architecture/overview.md: package map, enforcement layers, five-tag section
- docs/architecture/dependency-flow.md: boundary rules, enforcement strategy
- docs/architecture/vertical-feature-spec.md: package names, five-tag model
- AGENTS.md: package map, boundary rules, commands
- CLAUDE.md: tooling package names, quick-start command
- packages/core-eslint/AGENTS.md: tag clarification
- packages/core-typescript/AGENTS.md: tag clarification
- packages/core-api/AGENTS.md: core-composition tag
- packages/core-cms/AGENTS.md: core-composition tag
- packages/core-trpc/AGENTS.md: core-composition tag + rationale
- docs/decisions/adr-010-turbo-boundaries.md: new ADR

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 12:24:04 +02:00

8.8 KiB

AGENTS.md — Vertical Feature Monorepo

This is a Turborepo + pnpm monorepo organized by vertical features. Each feature package owns its own Clean Architecture layers (entities, application, infrastructure, interface-adapters) and integrations (CMS collections, tRPC routers, UI components). Core packages provide foundation: primitives, design system, CMS composition, API aggregation, and tRPC client platform.


Package Map

Package Tag Purpose
@repo/core-shared core Generic primitives (Zod, env, Payload hooks/fields/blocks, tRPC init/context)
@repo/core-ui core Design system (atoms, molecules, generic organisms, templates)
@repo/core-api core-composition tRPC router aggregator — imports @repo/<feature>/api only
@repo/core-cms core-composition Payload config aggregator — imports @repo/<feature>/cms only
@repo/core-trpc core-composition Frontend tRPC client + framework-specific providers (Next.js, TanStack)
@repo/auth feature Users collection + sign-in/up/out
@repo/blog feature Articles collection + article use-cases
@repo/media feature Media collection + upload helpers
@repo/marketing-pages feature Pages collection + SiteSettings global
@repo/navigation feature Header global
@repo/core-eslint tooling Shared ESLint 9 flat configs (base, next, react-internal, boundaries)
@repo/core-typescript tooling Shared TypeScript base configs + Vitest base

Boundary Rules

Five tags

  • app (4 packages) — apps/web-next, apps/web-tanstack, apps/cms, apps/storybook
  • core-composition (3 packages) — packages/core-api, core-cms, core-trpc
  • core (2 packages) — packages/core-shared, core-ui
  • feature (5 packages) — packages/auth, blog, media, marketing-pages, navigation
  • tooling (2 packages) — packages/core-eslint, core-typescript

Allowed dependency directions

Tag May depend on
app app, core, core-composition, feature, tooling
core-composition core, core-composition, feature, tooling
core core, core-composition, tooling
feature core, tooling
tooling tooling

Composition exceptions

  1. core-api may import @repo/<feature>/api subpath exports only (to compose tRPC routers).
  2. core-cms may import @repo/<feature>/cms subpath exports only (to compose Payload collections).
  3. core-trpc reaches features transitively through core-api's AppRouter type.

No other cross-package boundary deviations are permitted.

Four enforcement layers

  1. package.json dependencies — only allowed deps are declared; illegal imports fail at install time.
  2. exports maps — feature packages expose ., ./cms, ./api, ./di/bind-production only; no deep source paths exist.
  3. ESLint eslint-plugin-boundaries (lint-time) — configured in packages/core-eslint/:
    • Enforces the five-tag rules at linting
    • Feature packages may import from core and tooling only.
    • core-shared, core-ui may not import any feature.
    • core-api restricted to @repo/<feature>/api imports.
    • core-cms restricted to @repo/<feature>/cms imports.
    • No ../../../ cross-package relative imports.
  4. Turborepo boundaries (build-graph time) — configured in root turbo.json:
    • Validates the entire workspace dependency graph, including transitive dependencies
    • Catches issues ESLint might miss (e.g., transitive feature reaches through composition packages)
    • Run with pnpm turbo boundaries

Adding a Feature

Start with docs/guides/adding-a-feature.md for a step-by-step walkthrough covering:

  • New feature scaffold (folder structure, package.json, tsconfig.json, vitest.config.ts)
  • Clean Architecture layers (entities, use cases, repositories, DI container, controllers)
  • Payload integration (collections, hooks, Payload repository binding)
  • tRPC integration (routers, procedure binding)
  • Core wiring (core-api, core-cms, path aliases, app bootstrap)
  • Testing and lint validation

Key Commands

pnpm install           # Install all dependencies
pnpm dev               # Start all dev servers (Next.js :3000, CMS :3001, Storybook :6006)
pnpm typecheck         # Type-check all packages
pnpm lint              # Lint all packages (ESLint boundaries enforced)
pnpm turbo boundaries  # Validate workspace dependency graph (Turbo boundaries)
pnpm test              # Run all unit + integration tests (Vitest)
pnpm test:e2e          # Run e2e tests (Playwright across both apps)
pnpm build             # Build all packages (Turborepo)
docker compose up -d   # Start PostgreSQL

# Filtered commands
pnpm dev --filter @repo/web-next              # Only Next.js app
pnpm dev --filter @repo/cms                   # Only CMS admin
pnpm dev --filter @repo/storybook             # Only Storybook
pnpm typecheck --filter @repo/blog            # Only blog feature
pnpm test --filter @repo/blog                 # Only blog unit/integration tests

Per-Package Conventions

Source files use RELATIVE imports (not @/)

Inside src/ files, import from sibling layers using relative paths:

// packages/blog/src/application/use-cases/get-article.use-case.ts
import type { IArticlesRepository } from "../repositories/articles.repository.interface.js";
import { ARTICLES_REPOSITORY } from "../../di/symbols.js";
import type { Article } from "../../entities/article.js";

This keeps source code portable and avoids circular alias issues.

Test files use @/ alias

Test files (*.test.ts) use the @/ alias to import from src/:

// packages/blog/src/application/use-cases/get-article.use-case.test.ts
import { getArticleUseCase } from "@/application/use-cases/get-article.use-case.js";

vitest.config.ts MUST declare @/ alias

Every package's vitest.config.ts must define the alias:

import path from "path";
import { defineConfig } from "vitest/config";

export default defineConfig({
  test: { environment: "node", globals: true },
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "./src"),
    },
  },
});

tsconfig.json rootDir = "."

TypeScript configs must set "rootDir": "." to allow both src/ and test files to coexist:

{
  "extends": "@repo/core-typescript/base.json",
  "compilerOptions": {
    "rootDir": ".",
    "outDir": "dist"
  },
  "include": ["src/**/*", "tests/**/*"],
  "exclude": ["node_modules", "dist"]
}

Payload-backed features use constructor injection

Feature packages that need Payload (e.g., @repo/blog/infrastructure/repositories/payload-articles.repository.ts) receive the Payload config via constructor, not via @repo/core-cms dependency:

export class PayloadArticlesRepository implements IArticlesRepository {
  constructor(private config: Config) {}

  async getById(id: string): Promise<Article | null> {
    const payload = await getPayload({ config: this.config });
    return payload.findByID({ collection: "articles", id });
  }
}

The config comes from the app at boot time (see below).

Apps call bindProduction*() per feature at boot

Each app (web-next, web-tanstack, cms) imports feature containers and binds production Payload repos at startup:

// apps/web-next/src/app/layout.tsx (Next.js)
import { bindProductionArticles } from "@repo/blog/di/bind-production";
import { bindProductionUsers } from "@repo/auth/di/bind-production";
import { bindProductionMedia } from "@repo/media/di/bind-production";
import { payloadConfig } from "@repo/core-cms";

// At app boot:
await bindProductionArticles(blogContainer, payloadConfig);
await bindProductionUsers(authContainer, payloadConfig);
// ... etc for each feature

Specification & Guides

  • Vertical Feature Specdocs/architecture/vertical-feature-spec.md — full design, rationale, decision log
  • Architecture Overviewdocs/architecture/overview.md — package responsibilities, data flow
  • Dependency Flowdocs/architecture/dependency-flow.md — allowed directions and composition pattern
  • Adding a Feature Guidedocs/guides/adding-a-feature.md — step-by-step new feature walkthrough
  • Testing Strategydocs/guides/testing-strategy.md — test placement, Vitest per-package, Playwright e2e

Per-package documentation lives in each AGENTS.md:

  • packages/core-shared/AGENTS.md
  • packages/core-api/AGENTS.md, core-cms/AGENTS.md, core-trpc/AGENTS.md
  • packages/core-ui/AGENTS.md
  • packages/auth/AGENTS.md, blog/AGENTS.md, media/AGENTS.md, marketing-pages/AGENTS.md, navigation/AGENTS.md
  • packages/core-eslint/AGENTS.md, core-typescript/AGENTS.md
  • apps/cms/AGENTS.md, web-next/AGENTS.md, web-tanstack/AGENTS.md, storybook/AGENTS.md