Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions .cursor/rules/00-rule-acknowledgment.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
description: "Meta-rule: always state which rules from .cursor/rules apply at the start of each task. Relevant for every task."
alwaysApply: true
---

# Rule Acknowledgment

**CRITICAL**: Before starting any task, you MUST explicitly acknowledge which rules from this `.cursor/rules/` folder apply to the current work.

## Required Format

At the start of your response, state:

```
**Applying rules**: [list of rule file names that are relevant]
```

Examples:
- `**Applying rules**: 00-rule-acknowledgment.md, 01-architecture-boundaries.md, 02-code-standards.md`
- `**Applying rules**: 00-rule-acknowledgment.md, 03-type-safety.md, 05-frontend.md`

## When to Acknowledge

- **Always** acknowledge at least `00-rule-acknowledgment.md` itself
- Acknowledge all rules that are relevant to the task at hand
- If unsure, err on the side of acknowledging more rules rather than fewer
- Re-acknowledge when switching between different types of work (e.g., from backend to frontend)

## Purpose

This rule ensures:
1. You are aware of the applicable constraints
2. The user can verify you're following the right guidelines
3. Rule violations can be traced back to acknowledged rules
49 changes: 49 additions & 0 deletions .cursor/rules/01-architecture-boundaries.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
---
description: "Vertical slices (src/FeatureName/), facades for cross-vertical communication, and layer boundaries. Use when adding features or touching src/ structure."
globs: ["src/**/*.php", "tests/Architecture/**/*.php"]
alwaysApply: false
---

# Architecture and Vertical Boundaries

**Reference**: See `docs/archbook.md` for complete architecture documentation.

## Vertical Structure

- Each feature is a **vertical** (top-level folder under `src/`)
- Verticals contain layers: `Domain/`, `Facade/`, `Infrastructure/`, `Api/`, `Presentation/`
- `Common/` is a shared vertical for cross-cutting concerns (excluded from boundary tests)

## Cross-Vertical Communication

**CRITICAL RULE**: Verticals can only communicate through **Facades**.

- ✅ **Allowed**: `App\Foo\Domain\Service` → uses `App\Bar\Facade\BarFacadeInterface`
- ❌ **Forbidden**: `App\Foo\Domain\Service` → uses `App\Bar\Domain\Entity`
- ❌ **Forbidden**: `App\Foo\Presentation\Controller` → uses `App\Bar\Domain\Service`

## Facade Usage

- Facades expose **interfaces** and **DTOs** in `Facade/` namespace
- Facades are for **cross-vertical** use only
- Within the same vertical, use Domain services directly
- Facade implementations are thin orchestrators

## Layer Responsibilities

- **Facade**: Interfaces + DTOs + orchestration for other verticals
- **Domain**: Entities, enums, value objects, domain services, repositories (pure business logic)
- **Infrastructure**: External integrations, adapters
- **Api**: HTTP endpoints, serialization, request/response DTOs
- **Presentation**: Controllers, UI services, Twig templates, UX components

## When Creating New Features

1. Create a new vertical: `src/FeatureName/`
2. Add layers as needed (not all verticals need all layers)
3. Only add a `Facade/` when another vertical needs to access this feature
4. Keep `Common/` small and for truly cross-cutting concerns only

## Boundary Enforcement

The architecture test (`tests/Architecture/FeatureBoundariesArchTest.php`) enforces these rules. If you violate boundaries, the test will fail.
45 changes: 45 additions & 0 deletions .cursor/rules/02-code-standards.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
---
description: "PHP standards: strict typing, PHP 8.4, PHP CS Fixer, SOLID, naming. Use when writing or modifying PHP."
globs: []
alwaysApply: true
---

# Code Standards

**Reference**: See `.cursorrules` and `.php-cs-fixer.dist.php` for complete standards.

## PHP Standards

- **Strict typing**: Always use `declare(strict_types=1);` at the top of every PHP file
- **PHP 8.4 features**: Use modern PHP features when appropriate
- **Attributes over annotations**: Prefer PHP Attributes; only use annotations if no attribute exists
- **No named arguments**: Never use named arguments when calling functions/methods
- **Type system**: Use PHP's type system extensively (return types, parameter types, property types)

## Code Formatting

Follow PHP CS Fixer rules defined in `.php-cs-fixer.dist.php`:
- Based on `@Symfony` standard
- Custom rules: `ErickSkrauch/align_multiline_parameters`, `ErickSkrauch/blank_line_before_return`, `ErickSkrauch/multiline_if_statement_braces`
- Always run `mise quality` before committing to ensure formatting compliance

## SOLID Principles

- **Dependency Injection**: Services injected via container; avoid static/global access
- **Single Responsibility**: Each class has one clear purpose
- **Open/Closed**: Extend through interfaces and composition
- **Liskov Substitution**: Subtypes must be substitutable for their base types
- **Interface Segregation**: Many specific interfaces over one general interface
- **Dependency Inversion**: Depend on abstractions, not concretions

## Naming Conventions

- Use descriptive names; longer names are acceptable if they clarify meaning
- Follow Symfony naming conventions for controllers, services, entities
- Use clear, intention-revealing names for methods and variables

## Error Handling

- Use Symfony exceptions and validation facilities
- Use logging for important events and errors
- Never suppress errors silently
56 changes: 56 additions & 0 deletions .cursor/rules/03-type-safety.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
---
description: "PHPStan level 10, list<T> for arrays across boundaries, no mixed. Use when fixing types or PHPStan errors."
globs: ["src/**/*.php", "tests/**/*.php"]
alwaysApply: false
---

# Type Safety and PHPStan

**Reference**: See `phpstan.dist.neon` for PHPStan configuration (level 10).

## PHPStan Level 10

All code must pass PHPStan level 10 analysis. Run `mise quality` to verify.

## Array Types Across Boundaries

**CRITICAL RULE**: When arrays cross class boundaries (method parameters, return types, DTO properties), use `list<T>` for simple indexed arrays, not `array` or `T[]`.

- ✅ **Correct**: `@param list<string> $roles`, `@return list<string>`, `@var list<string>`
- ❌ **Incorrect**: `@param string[] $roles`, `@param array<string> $roles`, `@return array`

**Rationale**: PHPStan's `noAssociativeArraysAcrossBoundaries` rule enforces this to prevent associative arrays from crossing boundaries. Use DTOs for complex data structures.

## Type Annotations

- Always provide PHPDoc type annotations for arrays, even when native types exist
- Use `list<T>` for indexed arrays
- Use specific DTO types for complex data structures
- Never use `mixed` unless absolutely necessary (and document why)

## Property Types

- Use native property types (PHP 7.4+) whenever possible
- Combine with PHPDoc annotations for complex types (arrays, generics)
- Example: `private array $roles;` with `@var list<string>`

## Return Types

- Always declare return types on methods
- Use `?Type` for nullable returns
- Use `void` for methods that don't return values
- Never omit return types

## Parameter Types

- Always type all parameters
- Use union types (`Type1|Type2`) when appropriate (PHP 8.0+)
- Use intersection types (`Type1&Type2`) when needed (PHP 8.1+)

## When PHPStan Fails

If PHPStan reports errors:
1. Read the error message carefully
2. Fix the type annotation (usually changing `array`/`T[]` to `list<T>`)
3. Re-run `mise quality` to verify the fix
4. Never suppress PHPStan errors with `@phpstan-ignore` unless absolutely necessary (and document why)
60 changes: 60 additions & 0 deletions .cursor/rules/04-dto-patterns.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
---
description: "DTOs for data transfer across verticals and layers: readonly DTOs, list<T>, no entities or associative arrays at boundaries. Use when creating or changing DTOs (Facade, Api, Presentation) or passing data between layers or verticals."
alwaysApply: false
---

# DTO Patterns

**Reference**: See `docs/archbook.md` section on "DTO-first data flow".

## When to Use DTOs

- **Always** use DTOs for data transfer across vertical boundaries (via Facades)
- **Always** use DTOs for data transfer across layers within a vertical
- **Never** use associative arrays (`array<string, mixed>`) for data transfer
- **Never** pass entities directly across boundaries

## DTO Structure

DTOs should be:
- **Readonly classes** when possible (PHP 8.2+)
- Located in `Facade/Dto/` for facade DTOs
- Located in appropriate layer folders for internal DTOs (e.g., `Api/Dto/`, `Presentation/Dto/`)
- Simple data containers with typed properties

## DTO Properties

- All properties must be typed
- Use PHPDoc annotations for complex types (arrays, generics)
- Use `list<T>` for array properties (see `03-type-safety.md`)
- Avoid nested DTOs unless necessary (prefer flat structures when possible)

## Example Pattern

```php
readonly class AccountInfoDto
{
public function __construct(
public string $id,
public string $email,
/** @var list<string> */
public array $roles,
public DateTimeImmutable $createdAt,
) {
}
}
```

## DTO vs Entity

- **Entities** (`Domain/Entity/`): Rich domain objects with behavior, used within Domain layer
- **DTOs** (`Facade/Dto/`, `Api/Dto/`, etc.): Simple data containers for transfer
- **Never** return entities from facades; always convert to DTOs
- **Never** accept entities as parameters in facades; use DTOs or primitives

## Conversion

When converting between entities and DTOs:
- Create conversion methods in Facade implementations
- Keep conversion logic simple and explicit
- Handle null cases appropriately
62 changes: 62 additions & 0 deletions .cursor/rules/05-frontend.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
---
description: "TypeScript over JS, Stimulus controllers, assets, Tailwind, mise quality for frontend. Use when editing TS/JS/CSS or Twig."
globs: ["assets/**/*.ts", "assets/**/*.js", "assets/**/*.css", "src/**/Resources/**/*.twig", "tailwind.config.js", "tsconfig.json"]
alwaysApply: false
---

# Frontend Development

**Reference**: See `docs/archbook.md` section on "Client-Side Organization", `docs/frontendbook.md` for Stimulus (build, integration, controller structure).

## TypeScript Over JavaScript

- **Always** prefer TypeScript over JavaScript
- Use TypeScript's type system extensively
- Generate `.ts` files, not `.js` files
- Only use JavaScript when TypeScript is not available for a specific library

## Stimulus Controllers

- Stimulus controllers are colocated with verticals: `src/FeatureName/Presentation/Resources/assets/controllers/`
- Keep frontend code aligned with vertical boundaries
- Use TypeScript for Stimulus controllers when possible

## Asset Organization

- Frontend assets in `assets/` directory
- Vendor assets managed through Symfony AssetMapper (importmaps)
- See `docs/techbook.md` for dependency management details

## Code Quality

- Run `mise quality` to check frontend code (Prettier, ESLint, TypeScript compiler)
- All frontend code must pass ESLint and TypeScript type checking
- Format code with Prettier (automatically runs in `mise quality`)

## Polling Pattern (Non-Overlapping)

**Always use `setTimeout` with scheduling after completion**, never `setInterval` for polling:

```ts
// WRONG - requests can overlap if slow
this.intervalId = setInterval(() => this.poll(), 1000);

// CORRECT - next poll only after current completes
private async poll(): Promise<void> {
try {
await fetch(this.url);
} finally {
if (this.isActive) {
this.timeoutId = setTimeout(() => this.poll(), 1000);
}
}
}
```

This prevents request pile-up on slow connections and is more resilient to network latency.

## Building Frontend

- Use `mise run frontend` to build frontend assets (see `docs/devbook.md`)
- Frontend builds are handled by Symfony AssetMapper and TailwindCSS
- Don't manually edit built files in `public/assets/`
45 changes: 45 additions & 0 deletions .cursor/rules/06-database.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
---
description: "Doctrine entities, migrations (make:migration only), DateAndTimeService, mb_* strings. Use when editing entities or migrations."
globs: ["src/**/Entity/*.php", "src/**/Domain/**/*.php", "migrations/*.php", "config/packages/doctrine*.yaml"]
alwaysApply: false
---

# Database Access Patterns

**Reference**: See `docs/archbook.md` section on "Database access in Domain" and `docs/devbook.md` for migration workflow.

## Database Access in Domain Layer

- Database access is **allowed** in the Domain layer
- Use raw SQL for complex queries
- Use `EntityManager` for simple CRUD operations
- **Never** hardcode table names; use entity class names and Doctrine's table mapping

## Entity Management

- Model entities in `Domain/Entity/`
- Use Doctrine ORM attributes for mapping
- Entities are rich domain objects with behavior
- Don't expose entities directly across vertical boundaries (use DTOs)

## Migrations

- **Never** write migrations manually
- Create or edit entities
- Run `mise run console make:migration` to generate migrations (see `docs/devbook.md`)
- Migrations are auto-generated from entity changes

## Date and Time

- **Always** use `DateAndTimeService::getDateTimeImmutable()` instead of `new DateTimeImmutable()`
- This ensures consistent time handling across the application
- See `docs/archbook.md` for this requirement

## String Handling

- Use `mb_*` functions for multibyte-safe string operations
- Be aware of encoding when working with user input or database data

## Connection

- Use `mise run db` to connect to the local database (see `docs/devbook.md`)
Loading