feat(core): add 8 AGENTS.md files (package, layer, domain levels)

This commit is contained in:
2026-04-06 15:03:09 +02:00
parent eb195c8261
commit 95a46fe7f4
8 changed files with 258 additions and 0 deletions

48
packages/core/AGENTS.md Normal file
View File

@@ -0,0 +1,48 @@
# @repo/core — Clean Architecture Core
Business logic package. All use cases, entities, interfaces, and DI live here.
## Layers (dependencies point inward only)
```
entities/ → NOTHING (innermost, zero deps)
application/ → entities/ only
interface-adapters/→ application/, entities/
infrastructure/ → application/, entities/, @repo/cms-client, external libs
di/ → all internal layers
```
## Import Rules
| Layer | Can import from | NEVER import from |
|---|---|---|
| entities/ | NOTHING | Everything else |
| application/ | entities/ only | infrastructure/, interface-adapters/ |
| interface-adapters/ | application/, entities/ | infrastructure/ |
| infrastructure/ | application/, entities/, @repo/cms-client | interface-adapters/, apps/* |
| di/ | All internal layers | apps/* |
## DI Resolution Table
| Symbol | Interface | Production | Mock |
|---|---|---|---|
| IUsersRepository | IUsersRepository | PayloadUsersRepository (Plan 3) | MockUsersRepository |
| IArticlesRepository | IArticlesRepository | PayloadArticlesRepository (Plan 3) | MockArticlesRepository |
| IAuthenticationService | IAuthenticationService | BetterAuthService (future) | MockAuthenticationService |
| ITelemetryService | ITelemetryService | OTelSentryService (future) | MockTelemetryService |
## Naming Conventions
- Models: `{name}.ts` with Zod schema + type export
- Errors: `{domain}.ts` with Error subclasses
- Interfaces: `{name}.repository.interface.ts` or `{name}.service.interface.ts`
- Use cases: `{verb}-{noun}.use-case.ts`
- Controllers: `{noun}.controller.ts`
- Infra: `{provider}-{name}.repository.ts` or `mock-{name}.repository.ts`
- DI modules: `{domain}.module.ts`
## Running Tests
```bash
cd packages/core && pnpm vitest run
```

View File

@@ -0,0 +1,28 @@
# Application Layer — Use Cases + Interfaces
## Rules
- Imports from entities/ ONLY
- NEVER imports from infrastructure/ or interface-adapters/
- Repository interfaces define data access contracts
- Service interfaces define external service contracts
- Use cases get dependencies via `getInjection()` — never direct import of implementations
## Adding a New Use Case
1. Create `src/application/use-cases/{domain}/{verb}-{noun}.use-case.ts`
2. Get dependencies via DI: `const repo = getInjection("IMyRepository")`
3. Implement business logic using entities and interfaces only
4. Write test in `tests/unit/use-cases/{domain}/` using `initializeContainer()`/`destroyContainer()` pattern
## Adding a New Repository Interface
1. Create `src/application/repositories/{name}.repository.interface.ts`
2. Define interface methods returning entity types
3. Export from `src/application/repositories/index.ts`
4. Create mock implementation in infrastructure/
5. Register in DI container (add symbol, module binding)
## Adding a New Service Interface
Same as repository, but in `src/application/services/`

View File

@@ -0,0 +1,30 @@
# Auth Domain — Business Rules
## Responsibility
Authentication and authorization: sign-in, sign-up, sign-out, session management.
## Business Rules
- Passwords are hashed via IAuthenticationService (never stored plain)
- Sessions expire after 7 days (configured in mock, real impl may differ)
- Sign-up requires unique username
- Sign-in verifies password via IAuthenticationService.verifyPassword()
- Sign-out invalidates session and returns blank cookie
## Error Cases
- `AuthenticationError` — wrong credentials (sign-in) or username taken (sign-up)
- `UnauthenticatedError` — invalid/expired session
## Dependencies
- `IUsersRepository` — user lookup and creation
- `IAuthenticationService` — password hashing, session management
## Adding a New Auth Use Case
1. Create `{verb}-{noun}.use-case.ts` in this folder
2. Get deps via `getInjection("IUsersRepository")`, `getInjection("IAuthenticationService")`
3. Write test first in `tests/unit/use-cases/auth/`
4. Use `initializeContainer()`/`destroyContainer()` pattern in tests

View File

@@ -0,0 +1,29 @@
# Content Domain — Business Rules
## Responsibility
Article management: creation, retrieval, publishing workflow.
## Business Rules
- Articles must have a title and content
- Slugs auto-generated from title if not provided
- New articles default to "draft" status
- Filtering by status, authorId supported
- Pagination via limit/offset
## Error Cases
- `NotFoundError` — article doesn't exist (future: update/delete)
- `UnauthorizedError` — user can't edit article (future)
- `InputParseError` — missing required fields (handled by controller)
## Dependencies
- `IArticlesRepository` — article CRUD operations
## Adding a New Content Use Case
1. Create `{verb}-{noun}.use-case.ts` in this folder
2. Get deps via `getInjection("IArticlesRepository")`
3. Write test first in `tests/unit/use-cases/content/`

View File

@@ -0,0 +1,37 @@
# DI — InversifyJS Container
## Resolution Table
| Symbol Key | Interface | Production | Mock |
|---|---|---|---|
| IUsersRepository | IUsersRepository | (future) | MockUsersRepository |
| IArticlesRepository | IArticlesRepository | (future) | MockArticlesRepository |
| IAuthenticationService | IAuthenticationService | (future) | MockAuthenticationService |
| ITelemetryService | ITelemetryService | (future) | MockTelemetryService |
## How to Register a New Dependency
1. Add Symbol to `types.ts``DI_SYMBOLS`
2. Add return type to `DI_RETURN_TYPES` interface
3. Create module in `modules/{domain}.module.ts`
4. Bind interface to implementation (production) and mock (test)
5. Load module in `container.ts``initializeContainer()` and `destroyContainer()`
## Container Lifecycle
- Production: `initializeContainer()` runs automatically (not in test env)
- Tests: Call `initializeContainer()` in `beforeEach`, `destroyContainer()` in `afterEach`
- Test environments swap to mock implementations via module bindings
## DO NOT
- Import from apps/*
- Import framework-specific code (Next.js, TanStack, etc.)
- Use the container outside of this package — expose via `getInjection()` only
## tsconfig Requirements (DO NOT REMOVE)
- `experimentalDecorators: true`
- `emitDecoratorMetadata: true`
- `types: ["reflect-metadata"]`
- `import "reflect-metadata"` at top of container.ts

View File

@@ -0,0 +1,33 @@
# Entities Layer — Innermost, Zero Dependencies
## Rules
- NEVER import from application/, infrastructure/, interface-adapters/, or di/
- NEVER import external libraries except Zod (for schema validation)
- Everything here is pure — no side effects, no I/O, no async
- Models are Zod schemas with inferred TypeScript types
- Errors are custom Error subclasses with domain-specific semantics
## Adding a New Model
1. Create `src/entities/models/{name}.ts`
2. Define Zod schema and export inferred type:
```typescript
import { z } from "zod";
export const {name}Schema = z.object({ ... });
export type {Name} = z.infer<typeof {name}Schema>;
```
3. Export from `src/entities/models/index.ts`
## Adding a New Error
1. Create or edit `src/entities/errors/{domain}.ts`
2. Extend Error with constructor accepting message + options:
```typescript
export class {Name}Error extends Error {
constructor(message: string, options?: ErrorOptions) {
super(message, options);
}
}
```
3. Export from `src/entities/errors/index.ts`

View File

@@ -0,0 +1,24 @@
# Infrastructure Layer — Implementations
## Rules
- Implements interfaces from application/
- Imports from application/ and entities/
- NEVER imported by application/ or entities/
- Can import external libraries (Drizzle, Better Auth, Sentry, etc.)
- Can import @repo/cms-client
- Always provide a mock implementation for every real implementation
## Naming
- Real: `{provider}-{name}.repository.ts` (e.g., `payload-users.repository.ts`)
- Mock: `mock-{name}.repository.ts`
- All implementations must use `@injectable()` decorator for InversifyJS
## Adding a New Implementation
1. Create `src/infrastructure/repositories/{provider}-{name}.repository.ts`
2. Implement the interface from application/
3. Add `@injectable()` decorator
4. Create corresponding mock: `mock-{name}.repository.ts`
5. Register both in DI module (production binds real, test binds mock)

View File

@@ -0,0 +1,29 @@
# Controllers — Interface Adapters
## Rules
- Controllers validate input using Zod schemas from entities/
- Controllers call use cases — NEVER contain business logic themselves
- Controllers handle error mapping (domain errors to appropriate responses)
- Import from application/ and entities/ only
- NEVER import from infrastructure/
## Pattern
```typescript
const inputSchema = z.object({ ... });
export async function myController(input: Partial<z.infer<typeof inputSchema>>) {
const { data, error } = inputSchema.safeParse(input);
if (error) throw new InputParseError("Invalid data", { cause: error });
return await myUseCase(data);
}
```
## Adding a New Controller
1. Create `src/interface-adapters/controllers/{domain}/{name}.controller.ts`
2. Define Zod input schema
3. Validate input with `safeParse`, throw `InputParseError` on failure
4. Call use case and return result
5. Write test in `tests/unit/controllers/{domain}/`