Add 5 pi extensions: pi-subagents, pi-crew, rpiv-pi, pi-interactive-shell, pi-intercom
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
# CLAUDE.md
|
||||
|
||||
ASP.NET Core 8 Web API with Clean Architecture (CQRS + MediatR).
|
||||
|
||||
## Project map
|
||||
|
||||
- `src/Api/` - ASP.NET Core controllers, middleware, DI setup
|
||||
- `src/Application/` - MediatR handlers, validators, DTOs
|
||||
- `src/Domain/` - Entities, value objects, domain events
|
||||
- `src/Infrastructure/` - EF Core, external services, file storage
|
||||
- `tests/` - Unit and integration tests
|
||||
|
||||
## Commands
|
||||
|
||||
| Command | What it does |
|
||||
|---|---|
|
||||
| `dotnet build` | Build solution |
|
||||
| `dotnet test` | Run all tests |
|
||||
| `dotnet run --project src/Api` | Start API locally |
|
||||
| `dotnet ef migrations add <Name> -p src/Infrastructure` | Create EF migration |
|
||||
| `dotnet ef database update -p src/Infrastructure` | Apply migrations |
|
||||
|
||||
<important if="you are adding a new API endpoint">
|
||||
- Add controller in `Api/Controllers/` inheriting `BaseApiController`
|
||||
- Add command/query + handler + validator in `Application/Features/`
|
||||
- See `Application/Features/Orders/Commands/CreateOrder/` for the pattern
|
||||
</important>
|
||||
|
||||
<important if="you are adding or modifying EF Core migrations or database schema">
|
||||
- Entities configured via `IEntityTypeConfiguration<T>` in `Infrastructure/Persistence/Configurations/`
|
||||
- Always create a migration after schema changes — never modify existing migrations
|
||||
</important>
|
||||
|
||||
<important if="you are writing or modifying tests">
|
||||
- Unit tests: xUnit + NSubstitute, one test class per handler
|
||||
- Integration tests: `WebApplicationFactory<Program>` with test database
|
||||
- See `tests/Application.IntegrationTests/TestBase.cs` for setup
|
||||
</important>
|
||||
@@ -0,0 +1,42 @@
|
||||
# CLAUDE.md
|
||||
|
||||
Express API + React frontend in a Turborepo monorepo.
|
||||
|
||||
## Project map
|
||||
|
||||
- `apps/api/` - Express REST API
|
||||
- `apps/web/` - React SPA
|
||||
- `packages/db/` - Prisma schema and client
|
||||
- `packages/ui/` - Shared component library
|
||||
- `packages/config/` - Shared configuration
|
||||
|
||||
## Commands
|
||||
|
||||
| Command | What it does |
|
||||
|---|---|
|
||||
| `turbo build` | Build all packages |
|
||||
| `turbo test` | Run all tests |
|
||||
| `turbo lint` | Lint all packages |
|
||||
| `turbo dev` | Start dev server |
|
||||
| `turbo db:generate` | Regenerate Prisma client after schema changes |
|
||||
| `turbo db:migrate` | Run database migrations |
|
||||
|
||||
<important if="you are adding or modifying API routes">
|
||||
- All routes go in `apps/api/src/routes/`
|
||||
- Use Zod for request validation — see `apps/api/src/routes/connections.ts` for the pattern
|
||||
- Error responses follow RFC 7807 format
|
||||
- Authentication via JWT middleware
|
||||
</important>
|
||||
|
||||
<important if="you are writing or modifying tests">
|
||||
- API: Jest + Supertest, Frontend: Vitest + Testing Library
|
||||
- Test fixtures in `__fixtures__/` directories
|
||||
- Use `createTestClient()` helper for API integration tests
|
||||
- Mock database with `prismaMock` from `packages/db/test`
|
||||
</important>
|
||||
|
||||
<important if="you are working with client-side state, stores, or data fetching">
|
||||
- Zustand for global client state
|
||||
- React Query for server state
|
||||
- URL state via `nuqs`
|
||||
</important>
|
||||
@@ -0,0 +1,81 @@
|
||||
# Database Layer Architecture
|
||||
|
||||
## Responsibility
|
||||
SQLite persistence with better-sqlite3, repository pattern (plain types), QueryQueue concurrency, type transformations.
|
||||
|
||||
## Dependencies
|
||||
- **better-sqlite3**: Native SQLite (requires rebuild for Electron)
|
||||
- **@redis-ui/core**: Domain types
|
||||
- **p-queue**: Query serialization
|
||||
|
||||
## Consumers
|
||||
- **@redis-ui/services**: Repositories via RepositoryFactory
|
||||
- **Main process**: DatabaseManager initialization
|
||||
|
||||
## Module Structure
|
||||
```
|
||||
src/
|
||||
├── DatabaseManager.ts, QueryQueue.ts # Singleton, concurrency
|
||||
├── BaseRepository.ts, RepositoryFactory.ts
|
||||
├── schema.ts
|
||||
└── repositories/ # One repo per entity
|
||||
```
|
||||
|
||||
## Repository Boundary (CRITICAL: Plain Types, NOT Result<T>)
|
||||
|
||||
```typescript
|
||||
export class ConnectionRepository extends BaseRepository<ConnectionDB, Connection, ConnectionId> {
|
||||
protected toApplication(db: ConnectionDB): Connection {
|
||||
return {
|
||||
id: ConnectionId.create(db.id),
|
||||
host: db.host,
|
||||
port: db.port,
|
||||
sslEnabled: Boolean(db.ssl_enabled), // DB int → boolean
|
||||
createdAt: new Date(db.created_at), // timestamp → Date
|
||||
};
|
||||
}
|
||||
|
||||
async findById(id: ConnectionId): Promise<Connection | null> {
|
||||
return this.queue.enqueueRead((db) => {
|
||||
const row = db.prepare('SELECT * FROM connections WHERE id = ?').get(id);
|
||||
return row ? this.toApplication(row) : null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Service: Wraps repository in Result<T>
|
||||
async createConnection(input: CreateInput): Promise<Result<Connection>> {
|
||||
try {
|
||||
const connection = await this.connectionRepo.create(input);
|
||||
return Result.ok(connection);
|
||||
} catch (error) {
|
||||
return Result.fail(new InfrastructureError(error.message));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## QueryQueue Pattern (Write Serialization)
|
||||
|
||||
```typescript
|
||||
export class QueryQueue {
|
||||
private writeQueue = new PQueue({ concurrency: 1 }) // Single writer
|
||||
private readQueue = new PQueue({ concurrency: 5 }) // Multiple readers
|
||||
|
||||
async enqueueWrite<T>(op: (db: Database) => T): Promise<T> {
|
||||
return this.writeQueue.add(() => op(this.db))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Architectural Boundaries
|
||||
- **NO Result<T> in repos**: Services wrap with Result
|
||||
- **NO unqueued DB ops**: Always use QueryQueue
|
||||
- **NO raw SQL in services**: Use repositories
|
||||
|
||||
<important if="you are adding a new repository to this layer">
|
||||
## Adding a New Repository
|
||||
1. Create `XRepository.ts` extending `BaseRepository<XDB, X, XId>`
|
||||
2. Implement `toApplication()` and `toDatabase()` type mappers
|
||||
3. Register in `RepositoryFactory`
|
||||
4. Add table schema in `schema.ts`
|
||||
</important>
|
||||
@@ -0,0 +1,64 @@
|
||||
# Application Layer (CQRS + MediatR)
|
||||
|
||||
## Responsibility
|
||||
Command/query handlers orchestrating domain logic via MediatR pipeline. Sits between API controllers and Domain layer.
|
||||
|
||||
## Dependencies
|
||||
- **MediatR**: Command/query dispatch
|
||||
- **FluentValidation**: Request validation via pipeline behavior
|
||||
- **AutoMapper**: Domain ↔ DTO mapping
|
||||
|
||||
## Consumers
|
||||
- **API Controllers**: Send commands/queries via `IMediator`
|
||||
- **Integration tests**: Direct handler invocation
|
||||
|
||||
## Module Structure
|
||||
```
|
||||
Application/
|
||||
├── Common/
|
||||
│ ├── Behaviors/ # MediatR pipeline (validation, logging)
|
||||
│ └── Mappings/ # AutoMapper profiles
|
||||
├── Features/ # One folder per aggregate
|
||||
│ └── Orders/
|
||||
│ ├── Commands/ # CreateOrder/, UpdateOrder/ (handler + validator + DTO)
|
||||
│ └── Queries/ # GetOrder/, ListOrders/
|
||||
└── DependencyInjection.cs # Service registration
|
||||
```
|
||||
|
||||
## Handler Pattern (Command with Validation)
|
||||
|
||||
```csharp
|
||||
public record CreateOrderCommand(string CustomerId, List<LineItemDto> Items)
|
||||
: IRequest<Result<OrderDto>>;
|
||||
|
||||
public class CreateOrderValidator : AbstractValidator<CreateOrderCommand> {
|
||||
public CreateOrderValidator(IOrderRepository repo) {
|
||||
RuleFor(x => x.CustomerId).NotEmpty();
|
||||
RuleFor(x => x.Items).NotEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
public class CreateOrderHandler : IRequestHandler<CreateOrderCommand, Result<OrderDto>> {
|
||||
public async Task<Result<OrderDto>> Handle(
|
||||
CreateOrderCommand request, CancellationToken ct) {
|
||||
var order = Order.Create(request.CustomerId, request.Items); // Domain factory
|
||||
await _repo.AddAsync(order, ct);
|
||||
await _unitOfWork.SaveChangesAsync(ct);
|
||||
return Result.Ok(_mapper.Map<OrderDto>(order));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Architectural Boundaries
|
||||
- **NO domain logic in handlers**: Handlers orchestrate, domain objects contain logic
|
||||
- **NO direct DbContext access**: Use repository abstractions
|
||||
- **NO cross-feature references**: Features are independent vertical slices
|
||||
|
||||
<important if="you are adding a new feature or command/query handler">
|
||||
## Adding a New Feature
|
||||
1. Create folder under `Features/{Aggregate}/{Commands|Queries}/`
|
||||
2. Add `Command`/`Query` record implementing `IRequest<Result<TDto>>`
|
||||
3. Add `Validator` extending `AbstractValidator<TCommand>`
|
||||
4. Add `Handler` implementing `IRequestHandler<TCommand, Result<TDto>>`
|
||||
5. Add AutoMapper profile in `Common/Mappings/` if new DTO
|
||||
</important>
|
||||
@@ -0,0 +1,50 @@
|
||||
# Schemas Layer Architecture
|
||||
|
||||
## Responsibility
|
||||
Zod validation schemas for dual-layer validation (preload UX + main security), type inference via z.infer<>.
|
||||
|
||||
## Dependencies
|
||||
- **zod**: Runtime validation
|
||||
|
||||
## Consumers
|
||||
- **@redis-ui/ipc**: Main process validation (security)
|
||||
- **Preload**: Fail-fast validation (UX)
|
||||
- **TypeScript**: Type inference
|
||||
|
||||
## Module Structure
|
||||
```
|
||||
src/
|
||||
├── connection.ts, backup.ts # Domain schemas
|
||||
└── __tests__/ # Validation tests
|
||||
```
|
||||
|
||||
## Complete Schema Pattern (Types + Validation + Composition)
|
||||
|
||||
```typescript
|
||||
export const createConnectionSchema = z.object({
|
||||
name: z.string().min(1).max(255),
|
||||
host: z.string().min(1),
|
||||
port: z.number().int().min(1).max(65535),
|
||||
password: z.string().optional(),
|
||||
database: z.number().int().min(0).max(15).default(0),
|
||||
})
|
||||
|
||||
// Type inference
|
||||
export type CreateConnectionInput = z.infer<typeof createConnectionSchema>
|
||||
|
||||
// Update schema (partial + ID required)
|
||||
export const updateConnectionSchema = createConnectionSchema.partial().extend({
|
||||
id: z.string().min(1)
|
||||
})
|
||||
```
|
||||
|
||||
## Dual-Validation Flow
|
||||
|
||||
```
|
||||
Renderer input → Preload (Zod parse, fail fast) → IPC → Main (Zod parse again, security)
|
||||
```
|
||||
|
||||
## Architectural Boundaries
|
||||
- **NO any types**: Use z.unknown()
|
||||
- **NO skipping validation**: Always validate at boundaries
|
||||
- **NO business logic**: Structure validation only
|
||||
Reference in New Issue
Block a user