Files
agentic-dev/AGENTS.md

212 lines
8.2 KiB
Markdown

# 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-cms` | core (composition) | Payload config aggregator — imports `@repo/<feature>/cms` only |
| `@repo/core-api` | core (composition) | tRPC router aggregator — imports `@repo/<feature>/api` only |
| `@repo/core-trpc` | core | Frontend tRPC client + framework-specific providers (Next.js, TanStack) |
| `@repo/core-ui` | core | Design system (atoms, molecules, generic organisms, templates) |
| `@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/eslint-config` | tooling | Shared ESLint 9 flat configs (base, next, react-internal, boundaries) |
| `@repo/typescript-config` | tooling | Shared TypeScript base configs + Vitest base |
---
## Boundary Rules
### Three tags
- **app** — `apps/web-next`, `apps/web-tanstack`, `apps/cms`
- **feature** — `packages/auth`, `blog`, `media`, `marketing-pages`, `navigation`
- **core** — `packages/core-shared`, `core-cms`, `core-api`, `core-trpc`, `core-ui`
- (untagged) — `packages/eslint-config`, `typescript-config`
### Allowed dependency directions
```
app → feature, core
feature → core
core → core (restricted; see exceptions below)
```
**Disallowed:** `core → feature`, `core → app`, `feature → app`, `feature → feature`.
### Composition exceptions
Two packages may cross normal boundaries:
1. **`core-cms`** may import `@repo/<feature>/cms` subpath exports only (to compose Payload collections).
2. **`core-api`** may import `@repo/<feature>/api` subpath exports only (to compose tRPC routers).
No other cross-package boundary deviations are permitted.
### Three 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`** — configured in `packages/eslint-config/`:
- Feature packages may import from `core-*` and tooling only.
- `core-shared`, `core-trpc`, `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.
---
## 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
```bash
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 (boundaries enforced)
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:
```typescript
// 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/`:
```typescript
// 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:
```typescript
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:
```json
{
"extends": "@repo/typescript-config/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:
```typescript
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:
```typescript
// 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 Spec** — `docs/architecture/vertical-feature-spec.md` — full design, rationale, decision log
- **Architecture Overview** — `docs/architecture/overview.md` — package responsibilities, data flow
- **Dependency Flow** — `docs/architecture/dependency-flow.md` — allowed directions and composition pattern
- **Adding a Feature Guide** — `docs/guides/adding-a-feature.md` — step-by-step new feature walkthrough
- **Testing Strategy** — `docs/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-cms/AGENTS.md`
- `packages/core-api/AGENTS.md`
- `packages/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/eslint-config/AGENTS.md`, `typescript-config/AGENTS.md`
- `apps/cms/AGENTS.md`, `web-next/AGENTS.md`, `web-tanstack/AGENTS.md`, `storybook/AGENTS.md`