@ryuenn3123/agentic-senior-core 2.0.16 → 2.0.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agent-context/prompts/review-code.md +2 -0
- package/.agent-context/review-checklists/pr-checklist.md +2 -0
- package/.agent-context/rules/api-docs.md +11 -1
- package/.agent-context/state/benchmark-reproducibility.json +3 -1
- package/.agent-context/state/benchmark-writer-judge-config.json +58 -0
- package/.agent-context/state/benchmark-writer-judge-matrix.json +462 -0
- package/.cursorrules +60 -3686
- package/.windsurfrules +60 -3686
- package/README.md +33 -1
- package/lib/cli/compiler.mjs +98 -35
- package/package.json +2 -1
- package/scripts/benchmark-writer-judge-matrix.mjs +383 -0
- package/scripts/validate.mjs +19 -3
package/.windsurfrules
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# AGENTIC-SENIOR-CORE DYNAMIC GOVERNANCE RULESET
|
|
2
2
|
|
|
3
|
-
Generated by Agentic-Senior-Core CLI v2.0.
|
|
4
|
-
Timestamp: 2026-04-
|
|
3
|
+
Generated by Agentic-Senior-Core CLI v2.0.18
|
|
4
|
+
Timestamp: 2026-04-15T00:04:29.752Z
|
|
5
5
|
Selected profile: beginner
|
|
6
6
|
Selected policy file: .agent-context/policies/llm-judge-threshold.json
|
|
7
7
|
|
|
@@ -16,3704 +16,78 @@ Selected policy file: .agent-context/policies/llm-judge-threshold.json
|
|
|
16
16
|
- Exception path: .agent-override.md may explicitly allow narrow deviations.
|
|
17
17
|
- Scope policy: every override must include module scope, rationale, and expiry date.
|
|
18
18
|
|
|
19
|
-
##
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
### Why OpenAPI
|
|
54
|
-
- Machine-readable: clients, tests, and mocks can be generated from the spec
|
|
55
|
-
- Full JSON Schema Draft 2020-12 compatibility (3.1+ only)
|
|
56
|
-
- Vendor-neutral: works with Scalar, Swagger UI, Redoc, Postman, Stoplight
|
|
57
|
-
- Version-controllable: the spec is a file you can diff and review
|
|
58
|
-
|
|
59
|
-
---
|
|
60
|
-
|
|
61
|
-
## Tooling by Framework
|
|
62
|
-
|
|
63
|
-
### NestJS
|
|
64
|
-
Use `@nestjs/swagger` with Scalar UI (not default Swagger UI — Scalar is faster and more readable).
|
|
65
|
-
|
|
66
|
-
```typescript
|
|
67
|
-
// main.ts — Setup
|
|
68
|
-
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
|
|
69
|
-
import { apiReference } from '@scalar/nestjs-api-reference';
|
|
70
|
-
|
|
71
|
-
const config = new DocumentBuilder()
|
|
72
|
-
.setTitle('Service Name')
|
|
73
|
-
.setDescription('Brief service purpose')
|
|
74
|
-
.setVersion('1.0')
|
|
75
|
-
.addBearerAuth()
|
|
76
|
-
.build();
|
|
77
|
-
|
|
78
|
-
const document = SwaggerModule.createDocument(app, config);
|
|
79
|
-
|
|
80
|
-
// Scalar UI at /docs
|
|
81
|
-
app.use('/docs', apiReference({ spec: { content: document } }));
|
|
82
|
-
|
|
83
|
-
// Raw spec at /api-json
|
|
84
|
-
SwaggerModule.setup('api', app, document);
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
Every controller method requires these decorators at minimum:
|
|
88
|
-
```typescript
|
|
89
|
-
@ApiOperation({ summary: 'Create a user account' })
|
|
90
|
-
@ApiBody({ type: CreateUserDto, description: 'User registration data' })
|
|
91
|
-
@ApiResponse({ status: 201, type: UserResponseDto, description: 'User created successfully' })
|
|
92
|
-
@ApiResponse({ status: 400, description: 'Validation error — invalid input fields' })
|
|
93
|
-
@ApiResponse({ status: 409, description: 'Conflict — email already registered' })
|
|
94
|
-
@Post()
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
Every DTO property requires `@ApiProperty`:
|
|
98
|
-
```typescript
|
|
99
|
-
export class CreateUserDto {
|
|
100
|
-
@ApiProperty({
|
|
101
|
-
description: 'User email address, must be unique across the system',
|
|
102
|
-
example: 'jane.doe@example.com',
|
|
103
|
-
})
|
|
104
|
-
email: string;
|
|
105
|
-
|
|
106
|
-
@ApiProperty({
|
|
107
|
-
description: 'Display name shown in the UI',
|
|
108
|
-
example: 'Jane Doe',
|
|
109
|
-
minLength: 1,
|
|
110
|
-
maxLength: 100,
|
|
111
|
-
})
|
|
112
|
-
name: string;
|
|
113
|
-
}
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
### Next.js (App Router)
|
|
117
|
-
Use `zod-to-openapi` or `next-swagger-doc` to generate OpenAPI from Zod schemas.
|
|
118
|
-
|
|
119
|
-
```typescript
|
|
120
|
-
import { extendZodWithOpenApi } from '@asteasolutions/zod-to-openapi';
|
|
121
|
-
import { z } from 'zod';
|
|
122
|
-
|
|
123
|
-
extendZodWithOpenApi(z);
|
|
124
|
-
|
|
125
|
-
export const CreateUserSchema = z.object({
|
|
126
|
-
email: z.string().email().openapi({
|
|
127
|
-
description: 'User email address, must be unique',
|
|
128
|
-
example: 'jane.doe@example.com',
|
|
129
|
-
}),
|
|
130
|
-
name: z.string().min(1).max(100).openapi({
|
|
131
|
-
description: 'Display name shown in the UI',
|
|
132
|
-
example: 'Jane Doe',
|
|
133
|
-
}),
|
|
134
|
-
}).openapi('CreateUserRequest');
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
Expose the spec at `/api/docs` or `/api/openapi.json`.
|
|
138
|
-
|
|
139
|
-
### Other Frameworks (Express, Fastify, Hono)
|
|
140
|
-
Use `swagger-jsdoc` + TSDoc comments, or `@asteasolutions/zod-to-openapi`.
|
|
141
|
-
The output MUST be a valid OpenAPI 3.1 JSON/YAML file served at a known endpoint.
|
|
142
|
-
|
|
143
|
-
---
|
|
144
|
-
|
|
145
|
-
## Response Documentation Standard
|
|
146
|
-
|
|
147
|
-
Every endpoint MUST document these response scenarios:
|
|
148
|
-
|
|
149
|
-
| Status | When | Schema Required |
|
|
150
|
-
|--------|------|----------------|
|
|
151
|
-
| `200` | Successful retrieval or update | Yes — typed response body |
|
|
152
|
-
| `201` | Successful creation | Yes — the created resource |
|
|
153
|
-
| `204` | Successful deletion | No body |
|
|
154
|
-
| `400` | Validation error | Yes — field-level error details |
|
|
155
|
-
| `401` | Missing or invalid authentication | Fixed message, no details |
|
|
156
|
-
| `403` | Authenticated but insufficient permissions | Fixed message |
|
|
157
|
-
| `404` | Resource not found | Fixed message |
|
|
158
|
-
| `409` | Conflict (duplicate resource) | Description of conflict |
|
|
159
|
-
| `429` | Rate limit exceeded | Retry-After header |
|
|
160
|
-
| `500` | Internal error | `{ traceId }` only — NO stack traces |
|
|
161
|
-
|
|
162
|
-
### Error Response Schema (Standardized)
|
|
163
|
-
```json
|
|
164
|
-
{
|
|
165
|
-
"error": {
|
|
166
|
-
"code": "VALIDATION_ERROR",
|
|
167
|
-
"message": "One or more fields are invalid",
|
|
168
|
-
"details": [
|
|
169
|
-
{ "field": "email", "message": "Invalid email format" }
|
|
170
|
-
],
|
|
171
|
-
"traceId": "req-abc-123"
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
This schema MUST be documented in OpenAPI as a reusable component (`#/components/schemas/ErrorResponse`).
|
|
177
|
-
|
|
178
|
-
---
|
|
179
|
-
|
|
180
|
-
## Documentation Sync Rule
|
|
181
|
-
|
|
182
|
-
```
|
|
183
|
-
Endpoint changed + docs NOT updated = PR REJECTED.
|
|
184
|
-
|
|
185
|
-
The spec is a contract. If the contract is wrong, consumers will break.
|
|
186
|
-
"I'll update the docs later" means "the docs will never be updated."
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
### Enforcement
|
|
190
|
-
1. API docs live next to the code (same module, same directory)
|
|
191
|
-
2. Docs update in the SAME commit as the endpoint change
|
|
192
|
-
3. CI can validate the spec: `openapi-generator validate -i openapi.json`
|
|
193
|
-
4. Generated clients (if any) must be regenerated after spec changes
|
|
194
|
-
|
|
195
|
-
---
|
|
196
|
-
|
|
197
|
-
## The Documentation Quality Test
|
|
198
|
-
|
|
199
|
-
Open your API docs URL (`/docs`). For a randomly chosen endpoint, verify:
|
|
200
|
-
|
|
201
|
-
1. Can a developer who has never seen the codebase call this endpoint correctly? (Must be YES)
|
|
202
|
-
2. Are all required fields clearly marked? (Must be YES)
|
|
203
|
-
3. Does every field have a realistic example value? (Must be YES)
|
|
204
|
-
4. Are all error responses documented with their conditions? (Must be YES)
|
|
205
|
-
5. Can you copy the example request and it works? (Must be YES)
|
|
206
|
-
|
|
207
|
-
If any answer is "no", the documentation is incomplete.
|
|
208
|
-
## UNIVERSAL RULE: architecture.md
|
|
209
|
-
Source: .agent-context/rules/architecture.md
|
|
210
|
-
|
|
211
|
-
# Architecture — Separation of Concerns & Structure
|
|
212
|
-
|
|
213
|
-
> If your service file imports an HTTP library, your architecture is broken.
|
|
214
|
-
> If your controller contains SQL, you've already lost.
|
|
215
|
-
|
|
216
|
-
## The Core Principle
|
|
217
|
-
|
|
218
|
-
**Every layer has ONE job. Layer leaks are bugs — not "pragmatic shortcuts."**
|
|
219
|
-
|
|
220
|
-
```
|
|
221
|
-
┌─────────────────────────────────────────┐
|
|
222
|
-
│ TRANSPORT / CONTROLLER │ ← Parse input, validate shape, return response
|
|
223
|
-
│ (HTTP, CLI, WebSocket, Queue) │ ← NO business logic here. EVER.
|
|
224
|
-
├─────────────────────────────────────────┤
|
|
225
|
-
│ APPLICATION / SERVICE │ ← Business rules, orchestration, transactions
|
|
226
|
-
│ (Use cases, workflows) │ ← NO HTTP, NO SQL, NO framework imports
|
|
227
|
-
├─────────────────────────────────────────┤
|
|
228
|
-
│ DOMAIN / ENTITY │ ← Pure business objects, value objects
|
|
229
|
-
│ (Models, rules, calculations) │ ← ZERO external dependencies
|
|
230
|
-
├─────────────────────────────────────────┤
|
|
231
|
-
│ INFRASTRUCTURE / REPOSITORY │ ← Database, external APIs, file system
|
|
232
|
-
│ (Data access, adapters) │ ← NO business logic
|
|
233
|
-
└─────────────────────────────────────────┘
|
|
234
|
-
```
|
|
235
|
-
|
|
236
|
-
## Layer Rules (Enforced)
|
|
237
|
-
|
|
238
|
-
### Transport Layer (Controller / Handler / Route)
|
|
239
|
-
**Allowed:**
|
|
240
|
-
- Parse and validate incoming request (DTO/schema validation)
|
|
241
|
-
- Call application/service layer
|
|
242
|
-
- Format and return HTTP response (status code, headers)
|
|
243
|
-
- Handle authentication/authorization middleware
|
|
244
|
-
|
|
245
|
-
**BANNED:**
|
|
246
|
-
- Database queries or ORM calls
|
|
247
|
-
- Business logic (if/else on business rules)
|
|
248
|
-
- Direct calls to external APIs
|
|
249
|
-
- Transaction management
|
|
250
|
-
|
|
251
|
-
### Application Layer (Service / Use Case)
|
|
252
|
-
**Allowed:**
|
|
253
|
-
- Orchestrate business operations
|
|
254
|
-
- Call repository layer for data
|
|
255
|
-
- Apply business rules and validations
|
|
256
|
-
- Manage transactions
|
|
257
|
-
- Emit domain events
|
|
258
|
-
|
|
259
|
-
**BANNED:**
|
|
260
|
-
- HTTP request/response objects
|
|
261
|
-
- Framework-specific decorators (keep framework coupling minimal)
|
|
262
|
-
- Direct SQL or raw database calls
|
|
263
|
-
- UI/presentation logic
|
|
264
|
-
|
|
265
|
-
### Domain Layer (Entity / Value Object)
|
|
266
|
-
**Allowed:**
|
|
267
|
-
- Business calculations and rules
|
|
268
|
-
- Validation of domain invariants
|
|
269
|
-
- Type definitions and interfaces
|
|
270
|
-
|
|
271
|
-
**BANNED:**
|
|
272
|
-
- ANY external dependency (database, HTTP, framework)
|
|
273
|
-
- Side effects (logging, API calls, file I/O)
|
|
274
|
-
- Infrastructure concerns
|
|
275
|
-
|
|
276
|
-
### Infrastructure Layer (Repository / Adapter)
|
|
277
|
-
**Allowed:**
|
|
278
|
-
- Database queries (SQL, ORM, document queries)
|
|
279
|
-
- External API calls (wrapped in adapters)
|
|
280
|
-
- File system operations
|
|
281
|
-
- Cache operations
|
|
282
|
-
|
|
283
|
-
**BANNED:**
|
|
284
|
-
- Business logic (no if/else on business rules in queries)
|
|
285
|
-
- HTTP response formatting
|
|
286
|
-
- Direct exposure to transport layer
|
|
287
|
-
|
|
288
|
-
---
|
|
289
|
-
|
|
290
|
-
## Dependency Direction
|
|
291
|
-
|
|
292
|
-
Dependencies flow **inward only**:
|
|
293
|
-
|
|
294
|
-
```
|
|
295
|
-
Transport → Application → Domain ← Infrastructure
|
|
296
|
-
↓
|
|
297
|
-
Infrastructure
|
|
298
|
-
|
|
299
|
-
NEVER: Domain → Infrastructure (use interfaces/ports)
|
|
300
|
-
NEVER: Application → Transport
|
|
301
|
-
NEVER: Infrastructure → Application (except through interfaces)
|
|
302
|
-
```
|
|
303
|
-
|
|
304
|
-
The Domain layer depends on NOTHING. Everything depends on the Domain.
|
|
305
|
-
|
|
306
|
-
---
|
|
307
|
-
|
|
308
|
-
## Default Architecture: Modular Monolith
|
|
309
|
-
|
|
310
|
-
Start with a **Modular Monolith**. Do NOT start with microservices.
|
|
311
|
-
|
|
312
|
-
**Switch to microservices ONLY if 2+ of these triggers exist:**
|
|
313
|
-
1. Frequent deploy conflicts across domains (teams blocking each other)
|
|
314
|
-
2. Clear scale mismatch (one module needs 100x resources of another)
|
|
315
|
-
3. Team ownership collision (multiple teams editing same module)
|
|
316
|
-
4. Fault isolation requirement (one module crashing must not kill others)
|
|
317
|
-
5. Stable contracts with clear data boundaries already exist
|
|
318
|
-
|
|
319
|
-
If these triggers don't exist, microservices are **premature complexity**.
|
|
320
|
-
|
|
321
|
-
---
|
|
322
|
-
|
|
323
|
-
## Project Structure: Feature-Based Grouping
|
|
324
|
-
|
|
325
|
-
### ❌ BANNED: Technical Grouping
|
|
326
|
-
```
|
|
327
|
-
src/
|
|
328
|
-
controllers/ ← 50 controllers in one flat folder?
|
|
329
|
-
userController.ts
|
|
330
|
-
orderController.ts
|
|
331
|
-
paymentController.ts
|
|
332
|
-
services/ ← Good luck finding related code
|
|
333
|
-
userService.ts
|
|
334
|
-
orderService.ts
|
|
335
|
-
repositories/
|
|
336
|
-
userRepository.ts
|
|
337
|
-
orderRepository.ts
|
|
338
|
-
```
|
|
339
|
-
|
|
340
|
-
### ✅ REQUIRED: Feature/Domain Grouping
|
|
341
|
-
```
|
|
342
|
-
src/
|
|
343
|
-
modules/ ← Backend
|
|
344
|
-
user/
|
|
345
|
-
user.controller.ts ← Transport
|
|
346
|
-
user.service.ts ← Application
|
|
347
|
-
user.repository.ts ← Infrastructure
|
|
348
|
-
user.entity.ts ← Domain
|
|
349
|
-
user.dto.ts ← Data Transfer Objects
|
|
350
|
-
user.module.ts ← Module registration
|
|
351
|
-
__tests__/
|
|
352
|
-
user.service.test.ts
|
|
353
|
-
order/
|
|
354
|
-
order.controller.ts
|
|
355
|
-
order.service.ts
|
|
356
|
-
...
|
|
357
|
-
shared/ ← Cross-cutting concerns
|
|
358
|
-
config/
|
|
359
|
-
errors/
|
|
360
|
-
logging/
|
|
361
|
-
middleware/
|
|
362
|
-
|
|
363
|
-
src/
|
|
364
|
-
features/ ← Frontend
|
|
365
|
-
payment/
|
|
366
|
-
api/ ← HTTP client + DTOs
|
|
367
|
-
hooks/ ← React hooks / state
|
|
368
|
-
components/ ← UI components
|
|
369
|
-
types/ ← Type definitions
|
|
370
|
-
utils/ ← Feature-specific utils
|
|
371
|
-
index.ts ← Public API barrel
|
|
372
|
-
components/
|
|
373
|
-
ui/ ← Shared UI primitives
|
|
374
|
-
layout/ ← Layout components
|
|
375
|
-
lib/ ← Shared utilities
|
|
376
|
-
config/ ← App configuration
|
|
377
|
-
```
|
|
378
|
-
|
|
379
|
-
---
|
|
380
|
-
|
|
381
|
-
## Module Communication
|
|
382
|
-
|
|
383
|
-
### Within a Monolith
|
|
384
|
-
Modules communicate through **public interfaces only**:
|
|
385
|
-
```
|
|
386
|
-
// ✅ CORRECT: Import from module's public API
|
|
387
|
-
import { UserService } from '@/modules/user';
|
|
388
|
-
|
|
389
|
-
// ❌ BANNED: Reach into another module's internals
|
|
390
|
-
import { UserRepository } from '@/modules/user/user.repository';
|
|
391
|
-
```
|
|
392
|
-
|
|
393
|
-
### Between Services (if microservices)
|
|
394
|
-
- Use well-defined contracts (REST, gRPC, events)
|
|
395
|
-
- Never share databases between services
|
|
396
|
-
- Define schemas at boundaries (Protobuf, JSON Schema, Zod)
|
|
397
|
-
|
|
398
|
-
---
|
|
399
|
-
|
|
400
|
-
## The Architecture Smell Test
|
|
401
|
-
|
|
402
|
-
Ask yourself these questions. If ANY answer is "yes", your architecture is broken:
|
|
403
|
-
|
|
404
|
-
1. Can I change the database without touching business logic? (Must be YES)
|
|
405
|
-
2. Can I switch from REST to GraphQL without rewriting services? (Must be YES)
|
|
406
|
-
3. Can I test business logic without a running database? (Must be YES)
|
|
407
|
-
4. Does each module have a clear, single responsibility? (Must be YES)
|
|
408
|
-
5. Can a new developer find all related code in one directory? (Must be YES)
|
|
409
|
-
## UNIVERSAL RULE: database-design.md
|
|
410
|
-
Source: .agent-context/rules/database-design.md
|
|
411
|
-
|
|
412
|
-
# Database Design — Schema Is Your Foundation
|
|
413
|
-
|
|
414
|
-
> A poorly designed schema is a bug factory.
|
|
415
|
-
> You can fix bad code in hours. A bad schema takes weeks.
|
|
416
|
-
|
|
417
|
-
## Normalization Rules
|
|
418
|
-
|
|
419
|
-
### Third Normal Form (3NF) is the Default
|
|
420
|
-
|
|
421
|
-
```
|
|
422
|
-
❌ BANNED: Flat tables with repeated data
|
|
423
|
-
Users:
|
|
424
|
-
| id | name | order_id | order_total | product_name |
|
|
425
|
-
| 1 | Jane | 101 | 99.99 | Widget |
|
|
426
|
-
| 1 | Jane | 102 | 49.99 | Gadget |
|
|
427
|
-
→ name is duplicated, update anomalies guaranteed
|
|
428
|
-
|
|
429
|
-
✅ REQUIRED: Normalized to 3NF
|
|
430
|
-
Users: | id | name | email |
|
|
431
|
-
Orders: | id | user_id | total | status |
|
|
432
|
-
Products: | id | name | price |
|
|
433
|
-
OrderItems: | order_id | product_id | quantity |
|
|
434
|
-
→ Each fact is stored exactly once
|
|
435
|
-
```
|
|
436
|
-
|
|
437
|
-
### When to Denormalize
|
|
438
|
-
|
|
439
|
-
Denormalization is allowed ONLY with documented justification:
|
|
440
|
-
|
|
441
|
-
1. **Read-heavy query** that joins 4+ tables and is called >1000x/sec
|
|
442
|
-
2. **Reporting/analytics** where query speed matters more than write consistency
|
|
443
|
-
3. **CQRS read model** that is purpose-built for a specific query
|
|
444
|
-
|
|
445
|
-
```
|
|
446
|
-
REQUIRED for every denormalization:
|
|
447
|
-
- Document WHY (link to performance evidence)
|
|
448
|
-
- Document HOW it stays in sync (trigger, event, scheduled job)
|
|
449
|
-
- Add a comment in the schema: "Denormalized for [query name], synced by [mechanism]"
|
|
450
|
-
```
|
|
451
|
-
|
|
452
|
-
---
|
|
453
|
-
|
|
454
|
-
## Indexing Strategy
|
|
455
|
-
|
|
456
|
-
### Rules of Indexing
|
|
457
|
-
|
|
458
|
-
1. **Every foreign key gets an index** — joins on unindexed FKs are full table scans
|
|
459
|
-
2. **Every WHERE clause in a frequent query gets evaluated** — if the column appears in WHERE and the table has >10K rows, consider an index
|
|
460
|
-
3. **Composite indexes matter** — `INDEX(status, created_at)` ≠ `INDEX(created_at, status)`. Column order follows the query pattern (most selective first, or matching WHERE + ORDER BY)
|
|
461
|
-
4. **Covering indexes** — include frequently selected columns to avoid table lookups
|
|
462
|
-
|
|
463
|
-
### What to Index
|
|
464
|
-
|
|
465
|
-
| Pattern | Index Type | Example |
|
|
466
|
-
|---------|-----------|---------|
|
|
467
|
-
| FK lookups | B-tree (default) | `orders.user_id` |
|
|
468
|
-
| Status + date filters | Composite | `INDEX(status, created_at)` |
|
|
469
|
-
| Full-text search | Full-text / GIN | `products.description` |
|
|
470
|
-
| JSON queries | GIN (PostgreSQL) | `metadata->>'type'` |
|
|
471
|
-
| Unique constraints | Unique | `users.email` |
|
|
472
|
-
| Geospatial | Spatial / GiST | `locations.coordinates` |
|
|
473
|
-
|
|
474
|
-
### What NOT to Index
|
|
475
|
-
|
|
476
|
-
- Columns with very low cardinality on small tables (`is_active` with 2 values on 100 rows)
|
|
477
|
-
- Tables with <1000 rows (index overhead > benefit)
|
|
478
|
-
- Write-heavy tables where every INSERT updates 10+ indexes
|
|
479
|
-
- Columns never used in WHERE, JOIN, or ORDER BY
|
|
480
|
-
|
|
481
|
-
### Index Monitoring
|
|
482
|
-
|
|
483
|
-
```
|
|
484
|
-
REQUIRED:
|
|
485
|
-
- Identify unused indexes monthly → DROP them (they slow writes)
|
|
486
|
-
- Identify missing indexes → EXPLAIN ANALYZE on slow queries
|
|
487
|
-
- Track index size vs table size → alarming if indexes > 3x table size
|
|
488
|
-
```
|
|
489
|
-
|
|
490
|
-
---
|
|
491
|
-
|
|
492
|
-
## Migration Standards
|
|
493
|
-
|
|
494
|
-
### Rules
|
|
495
|
-
|
|
496
|
-
1. **Every schema change is a migration** — never modify production schemas manually
|
|
497
|
-
2. **Migrations are versioned and sequential** — `001_create_users.sql`, `002_add_email_index.sql`
|
|
498
|
-
3. **Migrations are idempotent** — running twice produces the same result
|
|
499
|
-
4. **Migrations are reversible** — every UP has a DOWN (or document why rollback is impossible)
|
|
500
|
-
5. **Migrations run in CI** — test against a real database, not just syntax checks
|
|
501
|
-
|
|
502
|
-
### Safe Migration Patterns
|
|
503
|
-
|
|
504
|
-
```
|
|
505
|
-
✅ SAFE (no downtime):
|
|
506
|
-
1. ADD COLUMN with default (nullable or with DEFAULT)
|
|
507
|
-
2. CREATE INDEX CONCURRENTLY
|
|
508
|
-
3. ADD new table
|
|
509
|
-
4. Rename via view + synonym (transitional)
|
|
510
|
-
|
|
511
|
-
❌ DANGEROUS (requires planned downtime or careful orchestration):
|
|
512
|
-
1. DROP COLUMN (deploy code changes removing column usage FIRST)
|
|
513
|
-
2. RENAME COLUMN (use gradual rename: add new → copy data → remove old)
|
|
514
|
-
3. ALTER COLUMN TYPE (may lock table on large datasets)
|
|
515
|
-
4. DROP TABLE (ensure no code references remain)
|
|
516
|
-
```
|
|
517
|
-
|
|
518
|
-
### The Expand-Contract Pattern
|
|
519
|
-
|
|
520
|
-
For breaking schema changes in zero-downtime deployments:
|
|
521
|
-
|
|
522
|
-
```
|
|
523
|
-
Phase 1 (Expand): Add new column/table alongside old one
|
|
524
|
-
→ Code writes to BOTH old and new
|
|
525
|
-
Phase 2 (Migrate): Backfill data from old to new
|
|
526
|
-
→ Verify data consistency
|
|
527
|
-
Phase 3 (Contract): Remove old column/table
|
|
528
|
-
→ Code reads/writes only new
|
|
529
|
-
```
|
|
530
|
-
|
|
531
|
-
---
|
|
532
|
-
|
|
533
|
-
## Data Type Selection
|
|
534
|
-
|
|
535
|
-
### Use the Right Type
|
|
536
|
-
|
|
537
|
-
| Data | ❌ Wrong | ✅ Right | Why |
|
|
538
|
-
|------|---------|---------|-----|
|
|
539
|
-
| Money | `FLOAT` | `DECIMAL(19,4)` or integer cents | Floating point arithmetic is imprecise |
|
|
540
|
-
| UUID | `VARCHAR(36)` | `UUID` native type | 16 bytes vs 36 bytes, indexing is faster |
|
|
541
|
-
| Timestamps | `VARCHAR` | `TIMESTAMPTZ` | Timezone-aware, sortable, comparable |
|
|
542
|
-
| IP addresses | `VARCHAR(45)` | `INET` (PostgreSQL) | Validation built-in, range queries |
|
|
543
|
-
| Boolean | `INT` (0/1) | `BOOLEAN` | Semantic clarity |
|
|
544
|
-
| Enums | `VARCHAR` | Database ENUM or CHECK constraint | Prevents invalid values |
|
|
545
|
-
| JSON blobs | `TEXT` | `JSONB` (PostgreSQL) | Indexable, queryable, validated |
|
|
546
|
-
|
|
547
|
-
### Column Naming
|
|
548
|
-
|
|
549
|
-
```
|
|
550
|
-
✅ REQUIRED:
|
|
551
|
-
- snake_case for all column names
|
|
552
|
-
- Descriptive: created_at, updated_at, deleted_at (not ts, mod, del)
|
|
553
|
-
- Foreign keys: {referenced_table_singular}_id (e.g., user_id, order_id)
|
|
554
|
-
- Boolean columns: is_active, has_verified_email, can_edit
|
|
555
|
-
- Timestamps: {verb}_at (created_at, verified_at, shipped_at)
|
|
556
|
-
- Amounts: {noun}_amount or {noun}_cents (total_amount, tax_cents)
|
|
557
|
-
```
|
|
558
|
-
|
|
559
|
-
---
|
|
560
|
-
|
|
561
|
-
## Query Design Rules
|
|
562
|
-
|
|
563
|
-
### Pagination (Mandatory for Lists)
|
|
564
|
-
|
|
565
|
-
```
|
|
566
|
-
❌ BANNED:
|
|
567
|
-
SELECT * FROM orders;
|
|
568
|
-
-- Returns 2 million rows, OOM crash
|
|
569
|
-
|
|
570
|
-
✅ REQUIRED: Offset pagination (simple, okay for < 100K rows)
|
|
571
|
-
SELECT * FROM orders ORDER BY id LIMIT 20 OFFSET 40;
|
|
572
|
-
|
|
573
|
-
✅ PREFERRED: Cursor pagination (performant at any scale)
|
|
574
|
-
SELECT * FROM orders WHERE id > :last_seen_id ORDER BY id LIMIT 20;
|
|
575
|
-
```
|
|
576
|
-
|
|
577
|
-
**Rule:** Use cursor-based pagination for tables > 100K rows. Offset pagination degrades linearly with OFFSET value.
|
|
578
|
-
|
|
579
|
-
### Soft Deletes vs Hard Deletes
|
|
580
|
-
|
|
581
|
-
```
|
|
582
|
-
Use soft deletes (deleted_at column) when:
|
|
583
|
-
- Legal/compliance requires data retention
|
|
584
|
-
- Users might want to "undelete"
|
|
585
|
-
- Related data would break without the parent record
|
|
586
|
-
|
|
587
|
-
Use hard deletes when:
|
|
588
|
-
- GDPR/privacy requires actual data removal
|
|
589
|
-
- Data has no business value after deletion
|
|
590
|
-
- Storage cost of keeping data outweighs benefit
|
|
591
|
-
|
|
592
|
-
If soft deleting:
|
|
593
|
-
- Add deleted_at to ALL queries: WHERE deleted_at IS NULL
|
|
594
|
-
- Add a partial index: WHERE deleted_at IS NULL (for performance)
|
|
595
|
-
- Schedule periodic hard-delete of old soft-deleted records
|
|
596
|
-
```
|
|
597
|
-
|
|
598
|
-
---
|
|
599
|
-
|
|
600
|
-
## The Database Design Checklist
|
|
601
|
-
|
|
602
|
-
Before any schema goes to production:
|
|
603
|
-
|
|
604
|
-
- [ ] Schema is normalized to 3NF (denormalization documented if present)
|
|
605
|
-
- [ ] All foreign keys have indexes
|
|
606
|
-
- [ ] Primary keys use appropriate type (UUID or BIGINT)
|
|
607
|
-
- [ ] Timestamps use TIMESTAMPTZ (not VARCHAR)
|
|
608
|
-
- [ ] Money uses DECIMAL or integer cents (never FLOAT)
|
|
609
|
-
- [ ] Migration is versioned, reversible, and tested in CI
|
|
610
|
-
- [ ] All list queries have LIMIT (pagination implemented)
|
|
611
|
-
- [ ] Indexes justified with EXPLAIN ANALYZE on expected queries
|
|
612
|
-
- [ ] Column naming follows convention (snake_case, descriptive)
|
|
613
|
-
- [ ] Soft delete vs hard delete decision documented
|
|
614
|
-
## UNIVERSAL RULE: efficiency-vs-hype.md
|
|
615
|
-
Source: .agent-context/rules/efficiency-vs-hype.md
|
|
616
|
-
|
|
617
|
-
# Efficiency vs. Hype — Choose Stable, Not Shiny
|
|
618
|
-
|
|
619
|
-
> Every dependency is a liability. Every `npm install` is a trust decision.
|
|
620
|
-
> The best dependency is the one you don't need.
|
|
621
|
-
|
|
622
|
-
## The Dependency Decision Framework
|
|
623
|
-
|
|
624
|
-
Before adding ANY dependency, answer these 5 questions:
|
|
625
|
-
|
|
626
|
-
### 1. Can You Do It Without a Library? (The stdlib-first Rule)
|
|
627
|
-
```
|
|
628
|
-
❌ OVER-ENGINEERING:
|
|
629
|
-
npm install is-odd # 1 line of code: n % 2 !== 0
|
|
630
|
-
npm install left-pad # Already in String.prototype.padStart
|
|
631
|
-
npm install is-number # typeof x === 'number' && !isNaN(x)
|
|
632
|
-
npm install array-flatten # Array.prototype.flat() exists since ES2019
|
|
633
|
-
|
|
634
|
-
✅ USE THE LANGUAGE:
|
|
635
|
-
const isOdd = (n: number) => n % 2 !== 0;
|
|
636
|
-
const padded = str.padStart(10, '0');
|
|
637
|
-
const flat = nested.flat(Infinity);
|
|
638
|
-
```
|
|
639
|
-
|
|
640
|
-
**Dependency Defense:** If the user asks to install a new library, or if you feel the need to use one, evaluate it against the "stdlib-first" rule. If the functionality can be implemented safely in under 20 lines of code, write it yourself. If a dependency is strictly necessary, you MUST justify it by providing its bundle size, maintenance status, and why the standard library is insufficient.
|
|
641
|
-
|
|
642
|
-
### 2. Is It Maintained? (The Pulse Check)
|
|
643
|
-
| Signal | 🟢 Healthy | 🔴 Dead |
|
|
644
|
-
|--------|-----------|---------|
|
|
645
|
-
| Last commit | < 6 months ago | > 2 years ago |
|
|
646
|
-
| Open issues | Actively triaged | 500+ unread |
|
|
647
|
-
| Downloads/week | Growing or stable | Declining |
|
|
648
|
-
| Bus factor | > 3 maintainers | 1 maintainer, inactive |
|
|
649
|
-
| Security | No known CVEs | Unpatched vulnerabilities |
|
|
650
|
-
|
|
651
|
-
**Rule:** If a library has a bus factor of 1 and no commit in 12+ months, it is a risk. Find an alternative or vendor-fork it.
|
|
652
|
-
|
|
653
|
-
### 3. What's the Cost? (The Weight Check)
|
|
654
|
-
```
|
|
655
|
-
Before: npm install moment # 289KB minified, entire library for date formatting
|
|
656
|
-
After: npm install date-fns # 12KB for just the functions you use (tree-shakeable)
|
|
657
|
-
Better: Intl.DateTimeFormat # 0KB — built into the runtime
|
|
658
|
-
```
|
|
659
|
-
|
|
660
|
-
**Rule:** Check the bundle impact. Use [bundlephobia.com](https://bundlephobia.com) for JS packages. If a library adds > 50KB to your bundle for a simple feature, find a lighter alternative or implement it yourself.
|
|
661
|
-
|
|
662
|
-
### 4. Is It the Community Standard? (The Ecosystem Rule)
|
|
663
|
-
Prefer packages that the ecosystem has settled on:
|
|
664
|
-
|
|
665
|
-
| Need | ✅ Standard | ❌ Avoid |
|
|
666
|
-
|------|-----------|---------|
|
|
667
|
-
| HTTP client (Node) | `undici` (built-in) / native `fetch` / `ky` | `axios` (declining, CVE-2025-58754, bloated) |
|
|
668
|
-
| Validation | `zod` | `joi` (heavier), `yup` (less type-safe) |
|
|
669
|
-
| ORM (Node) | `prisma`, `drizzle` | `sequelize` (legacy API), `typeorm` (decorator hell) |
|
|
670
|
-
| Date handling | `date-fns`, `dayjs`, `Temporal` (when stable) | `moment` (deprecated, massive) |
|
|
671
|
-
| Testing | `vitest` (new projects), `jest` (existing) | `mocha` + `chai` + `sinon` (3 deps for what 1 does) |
|
|
672
|
-
| Password hashing | `argon2` (OWASP primary), `bcrypt` (legacy) | Custom crypto (NEVER) |
|
|
673
|
-
| Env loading | `dotenv` (if needed) | Custom `.env` parser |
|
|
674
|
-
|
|
675
|
-
**Note:** These are current recommendations as of March 2026. Evaluate against this framework; don't blindly follow.
|
|
676
|
-
|
|
677
|
-
### 5. Can You Remove It Later? (The Exit Strategy)
|
|
678
|
-
```
|
|
679
|
-
❌ HIGH LOCK-IN:
|
|
680
|
-
// Decorators from a specific framework scattered across 200 files
|
|
681
|
-
@Controller() @Injectable() @Guard() // In every file — framework is your entire architecture
|
|
682
|
-
|
|
683
|
-
✅ LOW LOCK-IN:
|
|
684
|
-
// Framework stays at the edges; business logic is framework-free
|
|
685
|
-
// Switching from Express to Fastify only changes the transport layer
|
|
686
|
-
```
|
|
687
|
-
|
|
688
|
-
**Rule:** Wrap external dependencies that touch > 5 files. If you need to replace a library, it should only affect the wrapper, not your entire codebase.
|
|
689
|
-
|
|
690
|
-
---
|
|
691
|
-
|
|
692
|
-
## The Hype Trap (AI-Generated Code Alert)
|
|
693
|
-
|
|
694
|
-
AI agents love suggesting trendy libraries because they appear frequently in training data. Watch for:
|
|
695
|
-
|
|
696
|
-
### Red Flags
|
|
697
|
-
1. **"Just install X"** without explaining why → ASK: Can I do this with the stdlib?
|
|
698
|
-
2. **Library does one thing** that's 5 lines of code → REJECT: Write it yourself
|
|
699
|
-
3. **Library is in alpha/beta** with < 1 year of releases → REJECT: Not production-ready
|
|
700
|
-
4. **Library has a cooler API** but the current one works fine → REJECT: "Cool" is not a business requirement
|
|
701
|
-
5. **"Everyone is using it"** → SO WHAT: Popularity is not a quality signal
|
|
702
|
-
|
|
703
|
-
### The Agent Must Justify Dependencies
|
|
704
|
-
When an AI agent suggests adding a new dependency, it MUST provide:
|
|
705
|
-
```
|
|
706
|
-
📦 DEPENDENCY JUSTIFICATION
|
|
707
|
-
━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
708
|
-
Package: [name@version]
|
|
709
|
-
Purpose: [what it does in 1 sentence]
|
|
710
|
-
Stdlib Alternative: [why it's insufficient — or "none"]
|
|
711
|
-
Bundle Size: [minified + gzipped]
|
|
712
|
-
Maintenance: [last release date, maintainer count]
|
|
713
|
-
Lock-in Risk: [low/medium/high — how many files would it touch]
|
|
714
|
-
Exit Strategy: [how to remove it if needed]
|
|
715
|
-
```
|
|
716
|
-
|
|
717
|
-
---
|
|
718
|
-
|
|
719
|
-
## Dependency Update Strategy
|
|
720
|
-
|
|
721
|
-
1. **Pin exact versions** in lockfiles (already default with package-lock.json, bun.lockb)
|
|
722
|
-
2. **Review changelogs** before major updates — never blindly `npm update`
|
|
723
|
-
3. **Update in isolation** — one dependency per PR, with tests passing
|
|
724
|
-
4. **Security patches immediately** — don't wait for the sprint
|
|
725
|
-
5. **Monthly audit** — run `npm audit` / `bun audit` and address findings
|
|
726
|
-
|
|
727
|
-
---
|
|
728
|
-
|
|
729
|
-
## The Zero-Dependency Ideal
|
|
730
|
-
|
|
731
|
-
The best packages are those with zero or minimal dependencies themselves:
|
|
732
|
-
- `zod` — 0 dependencies ✅
|
|
733
|
-
- `date-fns` — 0 dependencies ✅
|
|
734
|
-
- `nanoid` — 0 dependencies ✅
|
|
735
|
-
- `bcrypt` — 1 native dependency (justified for crypto) ✅
|
|
736
|
-
|
|
737
|
-
A package that pulls in 50 transitive dependencies for a simple feature is a supply chain attack waiting to happen.
|
|
738
|
-
|
|
739
|
-
---
|
|
740
|
-
|
|
741
|
-
## Quick Decision Tree
|
|
742
|
-
|
|
743
|
-
```
|
|
744
|
-
Do I need this functionality?
|
|
745
|
-
→ No → Don't install anything
|
|
746
|
-
→ Yes ↓
|
|
747
|
-
|
|
748
|
-
Can I write it in < 20 lines?
|
|
749
|
-
→ Yes → Write it yourself
|
|
750
|
-
→ No ↓
|
|
751
|
-
|
|
752
|
-
Does the language/runtime provide it?
|
|
753
|
-
→ Yes → Use the built-in
|
|
754
|
-
→ No ↓
|
|
755
|
-
|
|
756
|
-
Is there a well-maintained, lightweight, community-standard package?
|
|
757
|
-
→ Yes → Install it, add justification comment
|
|
758
|
-
→ No → Build a minimal internal implementation, consider vendoring
|
|
759
|
-
```
|
|
760
|
-
## UNIVERSAL RULE: error-handling.md
|
|
761
|
-
Source: .agent-context/rules/error-handling.md
|
|
762
|
-
|
|
763
|
-
# Error Handling — Never Swallow, Always Context
|
|
764
|
-
|
|
765
|
-
> A swallowed error is a silent production incident waiting to happen.
|
|
766
|
-
> When it explodes at 3 AM, you won't know where to look.
|
|
767
|
-
|
|
768
|
-
## The Three Commandments
|
|
769
|
-
|
|
770
|
-
1. **Never swallow an error.** Every error must be logged, re-thrown, or explicitly handled.
|
|
771
|
-
2. **Always add context.** A stack trace is not enough — include WHAT was happening and WITH WHAT data.
|
|
772
|
-
3. **Fail fast at boundaries.** Validate early, reject bad data before it travels deep into the system.
|
|
773
|
-
|
|
774
|
-
---
|
|
775
|
-
|
|
776
|
-
## Swallowed Error Detection (Instant Rejection)
|
|
777
|
-
|
|
778
|
-
```
|
|
779
|
-
❌ DEATH PENALTY: Empty catch block
|
|
780
|
-
try {
|
|
781
|
-
await processPayment(order);
|
|
782
|
-
} catch (error) {
|
|
783
|
-
// silently swallowed — payment may have failed, user has no idea
|
|
784
|
-
}
|
|
785
|
-
|
|
786
|
-
❌ DEATH PENALTY: Catch and log only (no re-throw or recovery)
|
|
787
|
-
try {
|
|
788
|
-
await processPayment(order);
|
|
789
|
-
} catch (error) {
|
|
790
|
-
console.log('error:', error); // Logged to a void nobody reads
|
|
791
|
-
// Execution continues as if nothing happened
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
✅ CORRECT: Handle, log with context, re-throw or recover
|
|
795
|
-
try {
|
|
796
|
-
await processPayment(order);
|
|
797
|
-
} catch (error) {
|
|
798
|
-
logger.error('Payment processing failed', {
|
|
799
|
-
orderId: order.id,
|
|
800
|
-
userId: order.userId,
|
|
801
|
-
amount: order.total,
|
|
802
|
-
error: error instanceof Error ? error.message : String(error),
|
|
803
|
-
});
|
|
804
|
-
throw new PaymentFailedError(order.id, { cause: error });
|
|
805
|
-
}
|
|
806
|
-
```
|
|
807
|
-
|
|
808
|
-
---
|
|
809
|
-
|
|
810
|
-
## Typed Error Codes
|
|
811
|
-
|
|
812
|
-
### Rule: Use Explicit Error Types, Not Generic Errors
|
|
813
|
-
|
|
814
|
-
```
|
|
815
|
-
❌ BANNED: Generic errors
|
|
816
|
-
throw new Error('Not found');
|
|
817
|
-
throw new Error('Permission denied');
|
|
818
|
-
throw new Error('Invalid input');
|
|
819
|
-
|
|
820
|
-
✅ REQUIRED: Typed, domain-specific errors
|
|
821
|
-
throw new NotFoundError('User', userId);
|
|
822
|
-
throw new ForbiddenError('Cannot delete other user\'s order');
|
|
823
|
-
throw new ValidationError('Email format is invalid', { field: 'email' });
|
|
824
|
-
```
|
|
825
|
-
|
|
826
|
-
### Error Class Pattern (Any Language)
|
|
827
|
-
```typescript
|
|
828
|
-
// Base application error
|
|
829
|
-
class AppError extends Error {
|
|
830
|
-
constructor(
|
|
831
|
-
message: string,
|
|
832
|
-
public readonly code: string,
|
|
833
|
-
public readonly statusCode: number,
|
|
834
|
-
public readonly context?: Record<string, unknown>,
|
|
835
|
-
) {
|
|
836
|
-
super(message);
|
|
837
|
-
this.name = this.constructor.name;
|
|
838
|
-
}
|
|
839
|
-
}
|
|
840
|
-
|
|
841
|
-
// Specific errors
|
|
842
|
-
class NotFoundError extends AppError {
|
|
843
|
-
constructor(resource: string, id: string | number) {
|
|
844
|
-
super(`${resource} not found: ${id}`, 'NOT_FOUND', 404, { resource, id });
|
|
845
|
-
}
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
class ValidationError extends AppError {
|
|
849
|
-
constructor(message: string, context?: Record<string, unknown>) {
|
|
850
|
-
super(message, 'VALIDATION_ERROR', 400, context);
|
|
851
|
-
}
|
|
852
|
-
}
|
|
853
|
-
|
|
854
|
-
class ForbiddenError extends AppError {
|
|
855
|
-
constructor(reason: string) {
|
|
856
|
-
super(reason, 'FORBIDDEN', 403);
|
|
857
|
-
}
|
|
858
|
-
}
|
|
859
|
-
```
|
|
860
|
-
|
|
861
|
-
---
|
|
862
|
-
|
|
863
|
-
## Structured Logging
|
|
864
|
-
|
|
865
|
-
### Rule: Logs Must Include Context
|
|
866
|
-
|
|
867
|
-
```
|
|
868
|
-
❌ USELESS:
|
|
869
|
-
logger.error('Something went wrong');
|
|
870
|
-
logger.info('Processing...');
|
|
871
|
-
console.log(error);
|
|
872
|
-
|
|
873
|
-
✅ USEFUL:
|
|
874
|
-
logger.error('Order processing failed', {
|
|
875
|
-
traceId: req.traceId,
|
|
876
|
-
userId: currentUser.id,
|
|
877
|
-
orderId: order.id,
|
|
878
|
-
action: 'processOrder',
|
|
879
|
-
error: error.message,
|
|
880
|
-
stack: error.stack,
|
|
881
|
-
});
|
|
882
|
-
|
|
883
|
-
logger.info('Payment completed', {
|
|
884
|
-
traceId: req.traceId,
|
|
885
|
-
orderId: order.id,
|
|
886
|
-
amount: payment.amount,
|
|
887
|
-
provider: payment.provider,
|
|
888
|
-
durationMs: Date.now() - startTime,
|
|
889
|
-
});
|
|
890
|
-
```
|
|
891
|
-
|
|
892
|
-
### Required Log Fields
|
|
893
|
-
| Field | When | Purpose |
|
|
894
|
-
|-------|------|---------|
|
|
895
|
-
| `traceId` / `requestId` | Always (in request context) | Correlate logs across services |
|
|
896
|
-
| `userId` | When authenticated | Who triggered the action |
|
|
897
|
-
| `action` | Always | What was happening |
|
|
898
|
-
| `error` + `stack` | On errors | What went wrong |
|
|
899
|
-
| `durationMs` | On slow operations | Performance visibility |
|
|
900
|
-
|
|
901
|
-
---
|
|
902
|
-
|
|
903
|
-
## Error Response Format (APIs)
|
|
904
|
-
|
|
905
|
-
### Rule: NEVER Leak Internal Details
|
|
906
|
-
|
|
907
|
-
```
|
|
908
|
-
❌ BANNED response to client:
|
|
909
|
-
{
|
|
910
|
-
"error": "ER_NO_SUCH_TABLE: Table 'mydb.user_sessions' doesn't exist",
|
|
911
|
-
"stack": "Error: at Query.execute (/app/node_modules/mysql..."
|
|
912
|
-
}
|
|
913
|
-
// Congratulations, you just told the attacker your DB name and framework
|
|
914
|
-
|
|
915
|
-
✅ REQUIRED response to client:
|
|
916
|
-
{
|
|
917
|
-
"error": {
|
|
918
|
-
"code": "INTERNAL_ERROR",
|
|
919
|
-
"message": "An unexpected error occurred. Please try again.",
|
|
920
|
-
"traceId": "abc-123-def"
|
|
921
|
-
}
|
|
922
|
-
}
|
|
923
|
-
// Internal details go to your logs, not to the client
|
|
924
|
-
```
|
|
925
|
-
|
|
926
|
-
### Error Response Mapping
|
|
927
|
-
| Internal Error | HTTP Status | Client Message |
|
|
928
|
-
|---------------|-------------|----------------|
|
|
929
|
-
| `ValidationError` | 400 | Show specific field errors |
|
|
930
|
-
| `AuthenticationError` | 401 | "Invalid credentials" (never specify which field) |
|
|
931
|
-
| `ForbiddenError` | 403 | "Insufficient permissions" |
|
|
932
|
-
| `NotFoundError` | 404 | "Resource not found" |
|
|
933
|
-
| `ConflictError` | 409 | "Resource already exists" |
|
|
934
|
-
| `RateLimitError` | 429 | "Too many requests" |
|
|
935
|
-
| Unhandled errors | 500 | "Internal error" + traceId for support |
|
|
936
|
-
|
|
937
|
-
---
|
|
938
|
-
|
|
939
|
-
## Fail-Fast at Boundaries
|
|
940
|
-
|
|
941
|
-
```
|
|
942
|
-
// ✅ Validate at the entry point, fail before going deeper
|
|
943
|
-
async function createOrder(req: Request) {
|
|
944
|
-
// Step 1: Validate input IMMEDIATELY
|
|
945
|
-
const parsed = CreateOrderSchema.safeParse(req.body);
|
|
946
|
-
if (!parsed.success) {
|
|
947
|
-
throw new ValidationError('Invalid order data', parsed.error.flatten());
|
|
948
|
-
}
|
|
949
|
-
|
|
950
|
-
// Step 2: Now use the validated, typed data
|
|
951
|
-
const order = await orderService.create(parsed.data);
|
|
952
|
-
return order;
|
|
953
|
-
}
|
|
954
|
-
|
|
955
|
-
// ❌ Don't let bad data travel deep before failing
|
|
956
|
-
async function createOrder(req: Request) {
|
|
957
|
-
const data = req.body; // Unvalidated!
|
|
958
|
-
const order = await orderService.create(data);
|
|
959
|
-
// Service calls repository, repository tries to insert...
|
|
960
|
-
// Database throws a cryptic constraint violation 5 layers deep
|
|
961
|
-
}
|
|
962
|
-
```
|
|
963
|
-
|
|
964
|
-
---
|
|
965
|
-
|
|
966
|
-
## Retry Strategy
|
|
967
|
-
|
|
968
|
-
When retrying failed operations:
|
|
969
|
-
|
|
970
|
-
1. **Only retry transient errors** (network timeouts, 503s) — NEVER retry validation errors or auth failures
|
|
971
|
-
2. **Use exponential backoff** — 100ms → 200ms → 400ms → 800ms
|
|
972
|
-
3. **Set a maximum retry count** (typically 3)
|
|
973
|
-
4. **Log every retry attempt** with attempt number and error
|
|
974
|
-
5. **Add jitter** to prevent thundering herd
|
|
975
|
-
|
|
976
|
-
```typescript
|
|
977
|
-
async function withRetry<T>(
|
|
978
|
-
operation: () => Promise<T>,
|
|
979
|
-
maxAttempts: number = 3,
|
|
980
|
-
baseDelayMs: number = 100,
|
|
981
|
-
): Promise<T> {
|
|
982
|
-
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
983
|
-
try {
|
|
984
|
-
return await operation();
|
|
985
|
-
} catch (error) {
|
|
986
|
-
if (attempt === maxAttempts || !isTransientError(error)) {
|
|
987
|
-
throw error;
|
|
988
|
-
}
|
|
989
|
-
const delay = baseDelayMs * Math.pow(2, attempt - 1) + Math.random() * 100;
|
|
990
|
-
logger.warn('Retrying operation', { attempt, maxAttempts, delayMs: delay });
|
|
991
|
-
await sleep(delay);
|
|
992
|
-
}
|
|
993
|
-
}
|
|
994
|
-
throw new Error('Unreachable');
|
|
995
|
-
}
|
|
996
|
-
```
|
|
997
|
-
## UNIVERSAL RULE: event-driven.md
|
|
998
|
-
Source: .agent-context/rules/event-driven.md
|
|
999
|
-
|
|
1000
|
-
# Event-Driven Architecture — React to Facts, Don't Poll for Changes
|
|
1001
|
-
|
|
1002
|
-
> If your services are constantly asking "Did anything change?", your architecture is broken.
|
|
1003
|
-
> Events are the nervous system of a distributed system.
|
|
1004
|
-
|
|
1005
|
-
## When to Use Event-Driven Architecture
|
|
1006
|
-
|
|
1007
|
-
### Use Events When:
|
|
1008
|
-
1. Multiple services need to react to the same state change
|
|
1009
|
-
2. You need temporal decoupling (producer doesn't wait for consumer)
|
|
1010
|
-
3. Audit trail / event history is a business requirement
|
|
1011
|
-
4. Systems need to scale producers and consumers independently
|
|
1012
|
-
5. Eventual consistency is acceptable
|
|
1013
|
-
|
|
1014
|
-
### Don't Use Events When:
|
|
1015
|
-
- You need an immediate, synchronous response
|
|
1016
|
-
- The system is a simple CRUD application with 1-2 services
|
|
1017
|
-
- You can't invest in proper infrastructure (message broker, monitoring)
|
|
1018
|
-
- Your team has no experience with async debugging
|
|
1019
|
-
|
|
1020
|
-
---
|
|
1021
|
-
|
|
1022
|
-
## Core Patterns
|
|
1023
|
-
|
|
1024
|
-
### 1. Pub/Sub (Publish-Subscribe)
|
|
1025
|
-
|
|
1026
|
-
Producer emits an event. Zero or more consumers react independently.
|
|
1027
|
-
|
|
1028
|
-
```
|
|
1029
|
-
┌──────────┐
|
|
1030
|
-
│ Consumer A│ (Send email)
|
|
1031
|
-
└────▲──────┘
|
|
1032
|
-
┌──────────┐ │
|
|
1033
|
-
│ Producer │──→ EVENT BUS ──→┌──────────┐
|
|
1034
|
-
│ (Order │ │ │ Consumer B│ (Update inventory)
|
|
1035
|
-
│ Service) │ │ └──────────┘
|
|
1036
|
-
└──────────┘ │
|
|
1037
|
-
┌───▼──────┐
|
|
1038
|
-
│ Consumer C│ (Analytics)
|
|
1039
|
-
└──────────┘
|
|
1040
|
-
```
|
|
1041
|
-
|
|
1042
|
-
**Rules:**
|
|
1043
|
-
- Producer does NOT know about consumers (fire-and-forget)
|
|
1044
|
-
- Each consumer processes independently (failure in one doesn't affect others)
|
|
1045
|
-
- Consumers MUST be idempotent (same event processed twice = same result)
|
|
1046
|
-
|
|
1047
|
-
### 2. CQRS (Command Query Responsibility Segregation)
|
|
1048
|
-
|
|
1049
|
-
Separate the write model (commands) from the read model (queries).
|
|
1050
|
-
|
|
1051
|
-
```
|
|
1052
|
-
┌─────────────┐ ┌─────────────┐
|
|
1053
|
-
│ Write Side │ Events │ Read Side │
|
|
1054
|
-
│ (Commands) │ ──────────────→ │ (Queries) │
|
|
1055
|
-
│ │ │ │
|
|
1056
|
-
│ Rich domain │ │ Denormalized │
|
|
1057
|
-
│ model │ │ read models │
|
|
1058
|
-
│ Normalized │ │ Optimized │
|
|
1059
|
-
│ schema │ │ for queries │
|
|
1060
|
-
└─────────────┘ └─────────────┘
|
|
1061
|
-
```
|
|
1062
|
-
|
|
1063
|
-
**When to use CQRS:**
|
|
1064
|
-
- Read/write ratio is heavily skewed (100:1 reads to writes)
|
|
1065
|
-
- Read and write models have fundamentally different shapes
|
|
1066
|
-
- You need different scaling for reads vs writes
|
|
1067
|
-
|
|
1068
|
-
**When NOT to use CQRS:**
|
|
1069
|
-
- Simple CRUD with no complex queries
|
|
1070
|
-
- Read and write models are identical (just use a repository)
|
|
1071
|
-
- You don't have the team capacity to maintain two models
|
|
1072
|
-
|
|
1073
|
-
### 3. Event Sourcing
|
|
1074
|
-
|
|
1075
|
-
Store the full history of state changes as immutable events, not just the current state.
|
|
1076
|
-
|
|
1077
|
-
```
|
|
1078
|
-
Traditional: User { name: "Jane", email: "jane@new.com" }
|
|
1079
|
-
→ You only know the current state
|
|
1080
|
-
|
|
1081
|
-
Event Sourced:
|
|
1082
|
-
1. UserCreated { name: "Jane", email: "jane@old.com" }
|
|
1083
|
-
2. EmailChanged { email: "jane@mid.com" }
|
|
1084
|
-
3. EmailChanged { email: "jane@new.com" }
|
|
1085
|
-
→ You know the full history, can rebuild any point in time
|
|
1086
|
-
```
|
|
1087
|
-
|
|
1088
|
-
**When to use Event Sourcing:**
|
|
1089
|
-
- Audit trail is a regulatory requirement (finance, healthcare)
|
|
1090
|
-
- "Time travel" queries are needed (what was the state on March 1?)
|
|
1091
|
-
- Complex domain with many state transitions
|
|
1092
|
-
- Combined with CQRS for complex read requirements
|
|
1093
|
-
|
|
1094
|
-
**When NOT to use Event Sourcing:**
|
|
1095
|
-
- Simple CRUD applications (overkill)
|
|
1096
|
-
- Team has no experience with event stores
|
|
1097
|
-
- No clear business need for historical state
|
|
1098
|
-
|
|
1099
|
-
---
|
|
1100
|
-
|
|
1101
|
-
## Event Design Rules
|
|
1102
|
-
|
|
1103
|
-
### Naming: Past Tense, Domain Language
|
|
1104
|
-
|
|
1105
|
-
```
|
|
1106
|
-
❌ BANNED:
|
|
1107
|
-
"UpdateOrder" → Commands, not events
|
|
1108
|
-
"ORDER_UPDATE" → Screaming snake in an event name
|
|
1109
|
-
"data" → Meaningless
|
|
1110
|
-
|
|
1111
|
-
✅ REQUIRED:
|
|
1112
|
-
"OrderPlaced" → Past tense, describes what happened
|
|
1113
|
-
"PaymentProcessed" → Specific, clear domain action
|
|
1114
|
-
"InventoryReserved" → Business language, not technical
|
|
1115
|
-
```
|
|
1116
|
-
|
|
1117
|
-
### Event Schema: Include Context
|
|
1118
|
-
|
|
1119
|
-
```typescript
|
|
1120
|
-
// ❌ BANNED: Minimal event with no context
|
|
1121
|
-
{ type: "OrderPlaced", orderId: "123" }
|
|
1122
|
-
|
|
1123
|
-
// ✅ REQUIRED: Rich event with all necessary context
|
|
1124
|
-
{
|
|
1125
|
-
eventId: "evt_abc123", // Unique, for idempotency
|
|
1126
|
-
eventType: "OrderPlaced", // What happened
|
|
1127
|
-
aggregateId: "order_123", // Which entity
|
|
1128
|
-
aggregateType: "Order", // Entity type
|
|
1129
|
-
timestamp: "2026-03-11T...", // When it happened
|
|
1130
|
-
version: 1, // Schema version
|
|
1131
|
-
correlationId: "req_xyz", // Trace across services
|
|
1132
|
-
causationId: "cmd_456", // What caused this event
|
|
1133
|
-
data: { // The event payload
|
|
1134
|
-
orderId: "order_123",
|
|
1135
|
-
userId: "user_789",
|
|
1136
|
-
items: [...],
|
|
1137
|
-
totalAmount: 99.99,
|
|
1138
|
-
currency: "USD"
|
|
1139
|
-
},
|
|
1140
|
-
metadata: { // Operational metadata
|
|
1141
|
-
producerService: "order-service",
|
|
1142
|
-
producerVersion: "2.1.0"
|
|
1143
|
-
}
|
|
1144
|
-
}
|
|
1145
|
-
```
|
|
1146
|
-
|
|
1147
|
-
### Event Versioning
|
|
1148
|
-
|
|
1149
|
-
Events are immutable — once published, they can't change. Handle evolution with:
|
|
1150
|
-
|
|
1151
|
-
1. **Weak schema (preferred):** Consumers ignore unknown fields, use defaults for missing fields
|
|
1152
|
-
2. **Upcasting:** Transform old events to new schema on read
|
|
1153
|
-
3. **New event type:** If the change is breaking, create `OrderPlacedV2`
|
|
1154
|
-
|
|
1155
|
-
```
|
|
1156
|
-
BANNED: Changing the schema of an existing event in a breaking way
|
|
1157
|
-
BANNED: Removing fields from events
|
|
1158
|
-
ALLOWED: Adding optional fields with defaults
|
|
1159
|
-
ALLOWED: Creating a new event type for breaking changes
|
|
1160
|
-
```
|
|
1161
|
-
|
|
1162
|
-
---
|
|
1163
|
-
|
|
1164
|
-
## Infrastructure Choices
|
|
1165
|
-
|
|
1166
|
-
| Need | Recommended | Avoid |
|
|
1167
|
-
|------|-----------|-------|
|
|
1168
|
-
| General pub/sub | Apache Kafka, NATS, RabbitMQ | Custom TCP sockets |
|
|
1169
|
-
| Cloud-native | AWS EventBridge, GCP Pub/Sub, Azure Service Bus | Polling a database table |
|
|
1170
|
-
| Simple/local | Redis Streams, NATS | ZeroMQ for production events |
|
|
1171
|
-
| Event store | EventStoreDB, Kafka (with compaction) | Relational DB as event store (unless simple) |
|
|
1172
|
-
|
|
1173
|
-
---
|
|
1174
|
-
|
|
1175
|
-
## Reliability Patterns
|
|
1176
|
-
|
|
1177
|
-
### Outbox Pattern (Transactional Events)
|
|
1178
|
-
|
|
1179
|
-
Ensure events are published reliably alongside database writes:
|
|
1180
|
-
|
|
1181
|
-
```
|
|
1182
|
-
1. Write to database AND outbox table in a single transaction
|
|
1183
|
-
2. Background process reads outbox and publishes to message broker
|
|
1184
|
-
3. Mark outbox entry as published
|
|
1185
|
-
|
|
1186
|
-
This guarantees: if the DB write succeeds, the event WILL be published.
|
|
1187
|
-
No more "DB updated but event lost" bugs.
|
|
1188
|
-
```
|
|
1189
|
-
|
|
1190
|
-
### Dead Letter Queue (DLQ)
|
|
1191
|
-
|
|
1192
|
-
Messages that fail processing after N retries go to a DLQ:
|
|
1193
|
-
- Monitor DLQ size — it should be near zero
|
|
1194
|
-
- Alert when DLQ grows
|
|
1195
|
-
- Investigate and reprocess failed messages
|
|
1196
|
-
- Never ignore a growing DLQ
|
|
1197
|
-
|
|
1198
|
-
### Idempotency (Non-Negotiable)
|
|
1199
|
-
|
|
1200
|
-
```
|
|
1201
|
-
EVERY consumer MUST handle duplicate events safely.
|
|
1202
|
-
|
|
1203
|
-
Techniques:
|
|
1204
|
-
1. Idempotency key: Store processed eventIds, skip if seen
|
|
1205
|
-
2. Natural idempotency: Operations that are naturally safe to repeat
|
|
1206
|
-
(e.g., SET status = 'paid' is idempotent; INCREMENT balance is NOT)
|
|
1207
|
-
3. Optimistic locking: Use version numbers to detect conflicts
|
|
1208
|
-
```
|
|
1209
|
-
|
|
1210
|
-
---
|
|
1211
|
-
|
|
1212
|
-
## The Event-Driven Checklist
|
|
1213
|
-
|
|
1214
|
-
Before implementing event-driven patterns:
|
|
1215
|
-
|
|
1216
|
-
- [ ] Business justification exists (not just "it's modern")
|
|
1217
|
-
- [ ] Team understands eventual consistency trade-offs
|
|
1218
|
-
- [ ] Message broker selected and provisioned
|
|
1219
|
-
- [ ] Event schema defined with versioning strategy
|
|
1220
|
-
- [ ] All consumers are idempotent
|
|
1221
|
-
- [ ] Dead letter queue configured with monitoring
|
|
1222
|
-
- [ ] Distributed tracing in place (OpenTelemetry)
|
|
1223
|
-
- [ ] Outbox pattern used for transactional events (if needed)
|
|
1224
|
-
- [ ] Consumer failure handling defined (retry, DLQ, alert)
|
|
1225
|
-
- [ ] Event catalog maintained (what events exist, who produces/consumes)
|
|
1226
|
-
## UNIVERSAL RULE: frontend-architecture.md
|
|
1227
|
-
Source: .agent-context/rules/frontend-architecture.md
|
|
1228
|
-
|
|
1229
|
-
# Frontend Architecture & Composition Patterns
|
|
1230
|
-
|
|
1231
|
-
> A complex UI is built from simple, mathematically robust functions. State is dangerous; isolate it.
|
|
1232
|
-
|
|
1233
|
-
## 1. File Structure (Feature-Driven Design)
|
|
1234
|
-
Organize your application by feature domain, not by file type.
|
|
1235
|
-
- **BANNED:** Monolithic directories like `/components` (with 500 files), `/hooks`, `/api`.
|
|
1236
|
-
- **REQUIRED (Feature Sliced):**
|
|
1237
|
-
```
|
|
1238
|
-
src/
|
|
1239
|
-
features/
|
|
1240
|
-
authentication/
|
|
1241
|
-
api/ #(login, logout fetchers)
|
|
1242
|
-
components/ #(LoginForm, ProfileView)
|
|
1243
|
-
hooks/ #(useAuth, useSession)
|
|
1244
|
-
store.ts #(Zustand slice)
|
|
1245
|
-
types.ts #(Zod schemas)
|
|
1246
|
-
components/ #(Global shared UI like Button, Modal)
|
|
1247
|
-
lib/ #(Axios instance, utility wrappers)
|
|
1248
|
-
```
|
|
1249
|
-
|
|
1250
|
-
## 2. Separation of State and UI (Smart vs. Dumb)
|
|
1251
|
-
- **Dumb Components (Presentational):** Receive data via `props`, emit events via callbacks (`onAction`). They do not know about the network, global context, or databases.
|
|
1252
|
-
- **Smart Components (Containers):** Connect to global state (Redux/Zustand), fetch data (React Query), and pass it down.
|
|
1253
|
-
- **Rule:** An intricate UI layout component should NEVER contain a `fetch` or `useQuery` call.
|
|
1254
|
-
|
|
1255
|
-
## 3. Server State vs. Client State
|
|
1256
|
-
Modern frontend frameworks differentiate between remote and local data.
|
|
1257
|
-
- **Server State (Async, Cached):** Data belonging to the database. MUST be managed by tools like `TanStack Query` (React Query) or `SWR`.
|
|
1258
|
-
- **Client State (Sync, Ephemeral):** UI toggles, modal states, form drafts. Manage via `useState`, `useContext`, or `Zustand`.
|
|
1259
|
-
- **BANNED:** Storing API responses in a global Redux/Zustand store (e.g., `dispatch(setUsers(data))`). Use React Query instead.
|
|
1260
|
-
|
|
1261
|
-
## 4. The Composition Pattern (Avoiding Prop Drilling)
|
|
1262
|
-
If a component takes more than 5 props, or if props are passed down through 3+ intermediate components, the architecture is broken.
|
|
1263
|
-
- **BANNED:** `<Layout user={user} theme={theme} onLogout={handleLogout} />`
|
|
1264
|
-
- **REQUIRED:** Use React's `children` prop and composition.
|
|
1265
|
-
```tsx
|
|
1266
|
-
// ✅ Clean composition
|
|
1267
|
-
<Layout>
|
|
1268
|
-
<Sidebar user={user} />
|
|
1269
|
-
<Content onLogout={handleLogout} />
|
|
1270
|
-
</Layout>
|
|
1271
|
-
```
|
|
1272
|
-
|
|
1273
|
-
## 5. Explicit Component Contracts (Typing)
|
|
1274
|
-
Every component **MUST** have an explicit, exported interface for its props.
|
|
1275
|
-
- **BANNED:** `const Button = (props: any) => ...`
|
|
1276
|
-
- **REQUIRED:** Prefix handlers with `on` and booleans with `is/has`.
|
|
1277
|
-
```typescript
|
|
1278
|
-
export interface ButtonProps {
|
|
1279
|
-
variant: 'primary' | 'secondary';
|
|
1280
|
-
isLoading?: boolean;
|
|
1281
|
-
onClick: () => void;
|
|
1282
|
-
children: React.ReactNode;
|
|
1283
|
-
}
|
|
1284
|
-
```
|
|
1285
|
-
|
|
1286
|
-
## 6. Form Handling & Validation
|
|
1287
|
-
Never write manual state bindings for complex forms.
|
|
1288
|
-
- **Rule:** All forms MUST use a robust library (`react-hook-form` is the standard) combined with a schema validator (`Zod`).
|
|
1289
|
-
- **BANNED:** Creating 5 `useState` variables for 5 input fields.
|
|
1290
|
-
|
|
1291
|
-
## 7. Performance & Re-renders
|
|
1292
|
-
React is fast until you break it.
|
|
1293
|
-
- **Rule:** Do not pass newly instantiated objects or arrow functions directly into dependency arrays (`useEffect`) or memoized components (`React.memo`) unless wrapped in `useMemo`/`useCallback`.
|
|
1294
|
-
- **Rule:** Never execute expensive mapping/filtering inside the render path blindly without memoization.
|
|
1295
|
-
## UNIVERSAL RULE: git-workflow.md
|
|
1296
|
-
Source: .agent-context/rules/git-workflow.md
|
|
1297
|
-
|
|
1298
|
-
# Git Workflow — Clean History, Atomic Commits
|
|
1299
|
-
|
|
1300
|
-
> Your git log is a changelog. If it reads like gibberish, your team is lost.
|
|
1301
|
-
|
|
1302
|
-
## Commit Message Format: Conventional Commits (Enforced)
|
|
1303
|
-
|
|
1304
|
-
```
|
|
1305
|
-
<type>(<scope>): <description>
|
|
1306
|
-
|
|
1307
|
-
[optional body — explain WHY, not WHAT]
|
|
1308
|
-
[optional footer — Breaking changes, issue references]
|
|
1309
|
-
```
|
|
1310
|
-
|
|
1311
|
-
### Types (Strict Set)
|
|
1312
|
-
| Type | When | Example |
|
|
1313
|
-
|------|------|---------|
|
|
1314
|
-
| `feat` | New feature | `feat(auth): add JWT refresh token rotation` |
|
|
1315
|
-
| `fix` | Bug fix | `fix(payment): handle race condition in checkout` |
|
|
1316
|
-
| `refactor` | Code restructuring (no behavior change) | `refactor(user): extract validation to separate service` |
|
|
1317
|
-
| `docs` | Documentation only | `docs(api): add OpenAPI schema for /orders endpoint` |
|
|
1318
|
-
| `test` | Adding/fixing tests | `test(cart): add edge case for empty cart discount` |
|
|
1319
|
-
| `chore` | Build, CI, config, dependencies | `chore(deps): upgrade prisma to 5.x` |
|
|
1320
|
-
| `perf` | Performance improvement | `perf(search): add index on users.email column` |
|
|
1321
|
-
| `style` | Formatting, semicolons (no logic change) | `style: apply prettier formatting` |
|
|
1322
|
-
| `ci` | CI/CD changes | `ci: add Node 20 to test matrix` |
|
|
1323
|
-
|
|
1324
|
-
### Rules
|
|
1325
|
-
1. **Type is mandatory.** No commits without a type prefix.
|
|
1326
|
-
2. **Scope is recommended.** Use the module/feature name.
|
|
1327
|
-
3. **Description is imperative mood.** "add", not "added" or "adds".
|
|
1328
|
-
4. **Max 72 characters** for the subject line.
|
|
1329
|
-
5. **Body explains WHY,** not what. The diff shows what.
|
|
1330
|
-
|
|
1331
|
-
### ❌ BANNED Commit Messages
|
|
1332
|
-
```
|
|
1333
|
-
fix bug
|
|
1334
|
-
updates
|
|
1335
|
-
WIP
|
|
1336
|
-
asdf
|
|
1337
|
-
misc changes
|
|
1338
|
-
working now
|
|
1339
|
-
final fix
|
|
1340
|
-
fix fix fix
|
|
1341
|
-
```
|
|
1342
|
-
|
|
1343
|
-
---
|
|
1344
|
-
|
|
1345
|
-
## Branching Model
|
|
1346
|
-
|
|
1347
|
-
### Main Branches
|
|
1348
|
-
| Branch | Purpose | Merge Strategy |
|
|
1349
|
-
|--------|---------|---------------|
|
|
1350
|
-
| `main` | Production-ready code | Merge commit or squash |
|
|
1351
|
-
| `develop` | Integration branch (if using GitFlow) | Merge commit |
|
|
1352
|
-
|
|
1353
|
-
### Feature Branches
|
|
1354
|
-
```
|
|
1355
|
-
Pattern: <type>/<ticket-id>-<short-description>
|
|
1356
|
-
|
|
1357
|
-
Examples:
|
|
1358
|
-
feat/AUTH-123-jwt-refresh
|
|
1359
|
-
fix/PAY-456-checkout-race-condition
|
|
1360
|
-
refactor/USER-789-extract-validation
|
|
1361
|
-
chore/INFRA-101-upgrade-node-20
|
|
1362
|
-
```
|
|
1363
|
-
|
|
1364
|
-
### Rules
|
|
1365
|
-
1. Branch from `main` (or `develop` if using GitFlow)
|
|
1366
|
-
2. Keep branches short-lived (max 2-3 days)
|
|
1367
|
-
3. Rebase on `main` before creating PR — don't merge main into your branch
|
|
1368
|
-
4. Delete branch after merge
|
|
1369
|
-
|
|
1370
|
-
---
|
|
1371
|
-
|
|
1372
|
-
## Pull Request Standards
|
|
1373
|
-
|
|
1374
|
-
### PR Size
|
|
1375
|
-
| Size | Lines Changed | Verdict |
|
|
1376
|
-
|------|--------------|---------|
|
|
1377
|
-
| Small | 1-100 | ✅ Ideal — easy to review |
|
|
1378
|
-
| Medium | 100-300 | ⚠️ Acceptable — split if possible |
|
|
1379
|
-
| Large | 300-500 | 🔶 Needs justification |
|
|
1380
|
-
| Massive | 500+ | ❌ MUST be split into smaller PRs |
|
|
1381
|
-
|
|
1382
|
-
**Rule:** If a PR touches more than 5 files across different modules, it's doing too much. Split it.
|
|
1383
|
-
|
|
1384
|
-
### PR Description Template
|
|
1385
|
-
```markdown
|
|
1386
|
-
## What
|
|
1387
|
-
Brief description of what this PR does.
|
|
1388
|
-
|
|
1389
|
-
## Why
|
|
1390
|
-
Why this change is needed. Link to issue/ticket.
|
|
1391
|
-
|
|
1392
|
-
## How
|
|
1393
|
-
High-level approach. Mention any non-obvious design decisions.
|
|
1394
|
-
|
|
1395
|
-
## Testing
|
|
1396
|
-
- [ ] Unit tests added/updated
|
|
1397
|
-
- [ ] Integration tests (if applicable)
|
|
1398
|
-
- [ ] Manual testing steps
|
|
1399
|
-
|
|
1400
|
-
## Screenshots (if UI change)
|
|
1401
|
-
Before | After
|
|
1402
|
-
```
|
|
1403
|
-
|
|
1404
|
-
### PR Review Rules
|
|
1405
|
-
1. Every PR needs at least 1 approval
|
|
1406
|
-
2. Author resolves all comments before merge
|
|
1407
|
-
3. CI must pass (lint, test, build)
|
|
1408
|
-
4. No `// TODO` without a linked issue
|
|
1409
|
-
5. No `console.log` debugging statements in production code
|
|
1410
|
-
|
|
1411
|
-
---
|
|
1412
|
-
|
|
1413
|
-
## Commit Atomicity
|
|
1414
|
-
|
|
1415
|
-
### Rule: Each Commit Must Be a Complete, Working Unit
|
|
1416
|
-
|
|
1417
|
-
```
|
|
1418
|
-
❌ BANNED sequence:
|
|
1419
|
-
1. feat(user): add user model ← compiles? maybe
|
|
1420
|
-
2. fix: fix import ← fixing previous commit
|
|
1421
|
-
3. feat(user): add user service ← compiles? probably
|
|
1422
|
-
4. fix: fix typo ← fixing previous commit
|
|
1423
|
-
5. feat(user): add user controller ← finally works together
|
|
1424
|
-
|
|
1425
|
-
✅ REQUIRED:
|
|
1426
|
-
1. feat(user): add user registration module
|
|
1427
|
-
→ Model, Service, Controller, Tests — all in one complete, working commit
|
|
1428
|
-
→ Or split into logical chunks that each compile and pass tests independently
|
|
1429
|
-
```
|
|
1430
|
-
|
|
1431
|
-
**Rule:** Every commit on `main` should compile, pass lint, and pass tests. Use interactive rebase (`git rebase -i`) to squash fix-up commits before merging.
|
|
1432
|
-
|
|
1433
|
-
---
|
|
1434
|
-
|
|
1435
|
-
## .gitignore Standards
|
|
1436
|
-
|
|
1437
|
-
### MUST Ignore
|
|
1438
|
-
```
|
|
1439
|
-
# Dependencies
|
|
1440
|
-
node_modules/
|
|
1441
|
-
vendor/
|
|
1442
|
-
venv/
|
|
1443
|
-
__pycache__/
|
|
1444
|
-
.gradle/
|
|
1445
|
-
target/
|
|
1446
|
-
|
|
1447
|
-
# Environment
|
|
1448
|
-
.env
|
|
1449
|
-
.env.local
|
|
1450
|
-
.env.*.local
|
|
1451
|
-
|
|
1452
|
-
# IDE
|
|
1453
|
-
.idea/
|
|
1454
|
-
.vscode/settings.json
|
|
1455
|
-
*.swp
|
|
1456
|
-
*.swo
|
|
1457
|
-
.DS_Store
|
|
1458
|
-
Thumbs.db
|
|
1459
|
-
|
|
1460
|
-
# Build output
|
|
1461
|
-
dist/
|
|
1462
|
-
build/
|
|
1463
|
-
out/
|
|
1464
|
-
*.min.js
|
|
1465
|
-
*.min.css
|
|
1466
|
-
|
|
1467
|
-
# Logs
|
|
1468
|
-
*.log
|
|
1469
|
-
npm-debug.log*
|
|
1470
|
-
|
|
1471
|
-
# OS
|
|
1472
|
-
.DS_Store
|
|
1473
|
-
Thumbs.db
|
|
1474
|
-
```
|
|
1475
|
-
|
|
1476
|
-
### MUST Commit
|
|
1477
|
-
```
|
|
1478
|
-
.env.example # Template with placeholder values
|
|
1479
|
-
.editorconfig # Consistent formatting across IDEs
|
|
1480
|
-
.prettierrc # Formatter config
|
|
1481
|
-
.eslintrc.* # Linter config
|
|
1482
|
-
tsconfig.json # TypeScript config
|
|
1483
|
-
docker-compose.yml # Dev environment
|
|
1484
|
-
Makefile / Taskfile # Standard commands
|
|
1485
|
-
```
|
|
1486
|
-
|
|
1487
|
-
---
|
|
1488
|
-
|
|
1489
|
-
## The Git Health Check
|
|
1490
|
-
|
|
1491
|
-
Before pushing:
|
|
1492
|
-
- [ ] All commits follow Conventional Commits format
|
|
1493
|
-
- [ ] No fixup commits (squash them)
|
|
1494
|
-
- [ ] Branch is rebased on latest main
|
|
1495
|
-
- [ ] CI passes locally (lint, test, build)
|
|
1496
|
-
- [ ] No secrets in any commit (check with `git log -p | grep -i "password\|secret\|key"`)
|
|
1497
|
-
- [ ] No merge commits in feature branch (rebase instead)
|
|
1498
|
-
## UNIVERSAL RULE: microservices.md
|
|
1499
|
-
Source: .agent-context/rules/microservices.md
|
|
1500
|
-
|
|
1501
|
-
# Microservices — When to Split, How to Split
|
|
1502
|
-
|
|
1503
|
-
> Don't start with microservices. Earn them through pain.
|
|
1504
|
-
> A bad monolith becomes a distributed bad monolith faster than you think.
|
|
1505
|
-
|
|
1506
|
-
## The Default: Modular Monolith
|
|
1507
|
-
|
|
1508
|
-
**Start with a modular monolith. Always.** Microservices are a scaling strategy, not a design philosophy.
|
|
1509
|
-
|
|
1510
|
-
A well-structured modular monolith can handle most applications indefinitely. Splitting prematurely creates distributed complexity without distributed benefits.
|
|
1511
|
-
|
|
1512
|
-
---
|
|
1513
|
-
|
|
1514
|
-
## The Split Decision Framework
|
|
1515
|
-
|
|
1516
|
-
### Prerequisites (ALL Must Be True)
|
|
1517
|
-
|
|
1518
|
-
Before even considering microservices, these must exist:
|
|
1519
|
-
|
|
1520
|
-
1. **Mature CI/CD pipeline** — automated testing, deployment, rollback
|
|
1521
|
-
2. **Observability in place** — distributed tracing, centralized logging, metrics
|
|
1522
|
-
3. **Team maturity** — team understands distributed systems failure modes
|
|
1523
|
-
4. **Clear module boundaries** — modules already communicate through interfaces, not internals
|
|
1524
|
-
|
|
1525
|
-
If any prerequisite is missing, **stop.** Fix the monolith first.
|
|
1526
|
-
|
|
1527
|
-
### Trigger Conditions (2+ Required)
|
|
1528
|
-
|
|
1529
|
-
Split a module into a service ONLY when **2 or more** of these triggers exist:
|
|
1530
|
-
|
|
1531
|
-
| # | Trigger | Evidence |
|
|
1532
|
-
|---|---------|----------|
|
|
1533
|
-
| 1 | **Deploy conflicts** | Teams block each other on releases; merge queues exceed 24h |
|
|
1534
|
-
| 2 | **Scale mismatch** | One module needs 50-100x resources of another |
|
|
1535
|
-
| 3 | **Team ownership collision** | Multiple teams edit the same module weekly |
|
|
1536
|
-
| 4 | **Fault isolation** | One module crashing must not kill the entire system |
|
|
1537
|
-
| 5 | **Technology divergence** | Module genuinely requires a different runtime/language |
|
|
1538
|
-
| 6 | **Compliance boundary** | Regulatory requirement to isolate data processing (PCI, HIPAA) |
|
|
1539
|
-
|
|
1540
|
-
```
|
|
1541
|
-
BANNED: "Let's use microservices because Netflix does"
|
|
1542
|
-
BANNED: "We might need to scale later"
|
|
1543
|
-
BANNED: "It's more modern"
|
|
1544
|
-
BANNED: "Each developer gets their own service"
|
|
1545
|
-
```
|
|
1546
|
-
|
|
1547
|
-
---
|
|
1548
|
-
|
|
1549
|
-
## How to Split (The Extraction Protocol)
|
|
1550
|
-
|
|
1551
|
-
### Step 1: Identify the Seam
|
|
1552
|
-
|
|
1553
|
-
Find the natural boundary in your modular monolith:
|
|
1554
|
-
```
|
|
1555
|
-
GOOD seams:
|
|
1556
|
-
- Module communicates with others through a defined interface/API
|
|
1557
|
-
- Module has its own database tables with no foreign keys to other modules
|
|
1558
|
-
- Module can be deployed independently without breaking others
|
|
1559
|
-
|
|
1560
|
-
BAD seams:
|
|
1561
|
-
- Two modules share a database table
|
|
1562
|
-
- Extracting requires duplicating business logic
|
|
1563
|
-
- Module makes 10+ synchronous calls to other modules per request
|
|
1564
|
-
```
|
|
1565
|
-
|
|
1566
|
-
### Step 2: Strangle, Don't Rewrite
|
|
1567
|
-
|
|
1568
|
-
```
|
|
1569
|
-
❌ BANNED: "Let's rewrite everything as microservices"
|
|
1570
|
-
|
|
1571
|
-
✅ REQUIRED: The Strangler Fig Pattern
|
|
1572
|
-
1. Build the new service alongside the monolith
|
|
1573
|
-
2. Route traffic to the new service gradually (feature flags, proxy rules)
|
|
1574
|
-
3. Verify the new service handles all cases correctly
|
|
1575
|
-
4. Remove the old code from the monolith
|
|
1576
|
-
5. Repeat for the next service, ONE AT A TIME
|
|
1577
|
-
```
|
|
1578
|
-
|
|
1579
|
-
### Step 3: Define the Contract
|
|
1580
|
-
|
|
1581
|
-
Before extracting, define the communication contract:
|
|
1582
|
-
|
|
1583
|
-
```
|
|
1584
|
-
REQUIRED for every service boundary:
|
|
1585
|
-
- API contract (OpenAPI 3.1, Protobuf, or AsyncAPI for events)
|
|
1586
|
-
- Versioning strategy (URL versioning, header versioning)
|
|
1587
|
-
- Error contract (standardized error response format)
|
|
1588
|
-
- SLA definition (latency, availability, throughput)
|
|
1589
|
-
- Ownership (which team owns this service)
|
|
1590
|
-
```
|
|
1591
|
-
|
|
1592
|
-
---
|
|
1593
|
-
|
|
1594
|
-
## Communication Patterns
|
|
1595
|
-
|
|
1596
|
-
### Synchronous (Request-Response)
|
|
1597
|
-
|
|
1598
|
-
| Pattern | When | Watch Out |
|
|
1599
|
-
|---------|------|-----------|
|
|
1600
|
-
| REST/HTTP | Standard CRUD operations | Cascading failures, tight coupling |
|
|
1601
|
-
| gRPC | High-performance, internal services | Schema evolution, debugging complexity |
|
|
1602
|
-
|
|
1603
|
-
**Rules:**
|
|
1604
|
-
- Always set timeouts (connection: 1s, request: 5s default)
|
|
1605
|
-
- Implement circuit breakers (fail-fast after N failures)
|
|
1606
|
-
- Never chain more than 3 synchronous calls
|
|
1607
|
-
- Implement retries with exponential backoff (transient errors only)
|
|
1608
|
-
|
|
1609
|
-
### Asynchronous (Events)
|
|
1610
|
-
|
|
1611
|
-
| Pattern | When | Watch Out |
|
|
1612
|
-
|---------|------|-----------|
|
|
1613
|
-
| Pub/Sub | One event, multiple consumers | Message ordering, at-least-once delivery |
|
|
1614
|
-
| Command Queue | Exactly-once processing needed | Dead letter queues, poison messages |
|
|
1615
|
-
| Event Sourcing | Full audit trail required | Complexity, event schema evolution |
|
|
1616
|
-
|
|
1617
|
-
**Rules:**
|
|
1618
|
-
- Prefer async communication over sync between services
|
|
1619
|
-
- Events are facts (past tense): `OrderPlaced`, `PaymentProcessed`
|
|
1620
|
-
- Commands are requests (imperative): `ProcessPayment`, `ShipOrder`
|
|
1621
|
-
- Always handle duplicate messages (idempotency)
|
|
1622
|
-
|
|
1623
|
-
---
|
|
1624
|
-
|
|
1625
|
-
## Data Ownership
|
|
1626
|
-
|
|
1627
|
-
### The Database-Per-Service Rule
|
|
1628
|
-
|
|
1629
|
-
```
|
|
1630
|
-
❌ DEATH PENALTY: Shared databases between services
|
|
1631
|
-
Service A → Database ← Service B
|
|
1632
|
-
// One schema change breaks both services
|
|
1633
|
-
|
|
1634
|
-
✅ REQUIRED: Each service owns its data
|
|
1635
|
-
Service A → Database A
|
|
1636
|
-
Service B → Database B
|
|
1637
|
-
// Services communicate through APIs or events
|
|
1638
|
-
```
|
|
1639
|
-
|
|
1640
|
-
### Data Consistency
|
|
1641
|
-
|
|
1642
|
-
- **Accept eventual consistency** — strong consistency across services is extremely expensive
|
|
1643
|
-
- **Use the Saga pattern** for distributed transactions (choreography or orchestration)
|
|
1644
|
-
- **Never use distributed 2PC (two-phase commit)** in production — it doesn't scale
|
|
1645
|
-
|
|
1646
|
-
---
|
|
1647
|
-
|
|
1648
|
-
## Anti-Patterns (Instant Rejection)
|
|
1649
|
-
|
|
1650
|
-
| Anti-Pattern | Why It's Dangerous |
|
|
1651
|
-
|-------------|-------------------|
|
|
1652
|
-
| **Distributed Monolith** | Services that must be deployed together defeat the purpose |
|
|
1653
|
-
| **Nano-services** | One function per service creates operational nightmare |
|
|
1654
|
-
| **Shared libraries with logic** | Tight coupling through shared code |
|
|
1655
|
-
| **Synchronous chains** | A→B→C→D means one failure kills everything |
|
|
1656
|
-
| **Shared database** | Schema changes break multiple services |
|
|
1657
|
-
| **"We'll figure out observability later"** | You won't find bugs without tracing. Ever. |
|
|
1658
|
-
|
|
1659
|
-
---
|
|
1660
|
-
|
|
1661
|
-
## The Microservices Readiness Checklist
|
|
1662
|
-
|
|
1663
|
-
Before splitting ANY module:
|
|
1664
|
-
|
|
1665
|
-
- [ ] 2+ trigger conditions met (documented with evidence)
|
|
1666
|
-
- [ ] All prerequisites in place (CI/CD, observability, team maturity)
|
|
1667
|
-
- [ ] Service boundary defined with clear ownership
|
|
1668
|
-
- [ ] API contract defined (OpenAPI, Protobuf, AsyncAPI)
|
|
1669
|
-
- [ ] Data ownership clear — no shared tables
|
|
1670
|
-
- [ ] Communication pattern chosen (sync vs async)
|
|
1671
|
-
- [ ] Failure handling designed (timeouts, circuit breakers, retries)
|
|
1672
|
-
- [ ] Monitoring and alerting planned
|
|
1673
|
-
- [ ] Rollback strategy defined
|
|
1674
|
-
- [ ] Team agrees this is the right decision (not just the architect)
|
|
1675
|
-
## UNIVERSAL RULE: naming-conv.md
|
|
1676
|
-
Source: .agent-context/rules/naming-conv.md
|
|
1677
|
-
|
|
1678
|
-
# Naming Conventions — The "No Lazy Names" Standard
|
|
1679
|
-
|
|
1680
|
-
> If your variable name needs a comment to explain it, the name is wrong.
|
|
1681
|
-
|
|
1682
|
-
## Universal Rules (All Languages)
|
|
1683
|
-
|
|
1684
|
-
### Variables: Nouns That Tell a Story
|
|
1685
|
-
```
|
|
1686
|
-
❌ BANNED ✅ REQUIRED
|
|
1687
|
-
─────────────────────────────────────────────
|
|
1688
|
-
d durationInSeconds
|
|
1689
|
-
data userProfilePayload
|
|
1690
|
-
res httpResponse
|
|
1691
|
-
temp unsortedItems
|
|
1692
|
-
val discountPercentage
|
|
1693
|
-
info orderSummary
|
|
1694
|
-
item cartLineItem
|
|
1695
|
-
list activeSubscriptions
|
|
1696
|
-
obj paymentConfiguration
|
|
1697
|
-
```
|
|
1698
|
-
|
|
1699
|
-
**Rule:** A variable name must answer "WHAT is this?" without reading surrounding code.
|
|
1700
|
-
|
|
1701
|
-
### Functions: Verbs That Declare Intent
|
|
1702
|
-
```
|
|
1703
|
-
❌ BANNED ✅ REQUIRED
|
|
1704
|
-
─────────────────────────────────────────────
|
|
1705
|
-
process() validatePaymentDetails()
|
|
1706
|
-
handle() routeIncomingWebhook()
|
|
1707
|
-
doStuff() calculateShippingCost()
|
|
1708
|
-
run() executeScheduledCleanup()
|
|
1709
|
-
getData() fetchActiveUsersByRegion()
|
|
1710
|
-
check() isEligibleForDiscount()
|
|
1711
|
-
```
|
|
1712
|
-
|
|
1713
|
-
**Rule:** A function name must answer "WHAT does this do?" as a verb phrase. Generic verbs like `process`, `handle`, `manage` are BANNED unless suffixed with a specific noun (e.g., `handlePaymentFailure`).
|
|
1714
|
-
|
|
1715
|
-
### Booleans: is/has/can/should Prefix
|
|
1716
|
-
```
|
|
1717
|
-
❌ BANNED ✅ REQUIRED
|
|
1718
|
-
─────────────────────────────────────────────
|
|
1719
|
-
active isActive
|
|
1720
|
-
logged isLoggedIn
|
|
1721
|
-
admin hasAdminRole
|
|
1722
|
-
edit canEditDocument
|
|
1723
|
-
visible shouldRenderSidebar
|
|
1724
|
-
```
|
|
1725
|
-
|
|
1726
|
-
**Rule:** Boolean variables MUST use `is`, `has`, `can`, or `should` prefix. No exceptions.
|
|
1727
|
-
|
|
1728
|
-
### Constants: SCREAMING_SNAKE for True Constants
|
|
1729
|
-
```
|
|
1730
|
-
❌ BANNED ✅ REQUIRED
|
|
1731
|
-
─────────────────────────────────────────────
|
|
1732
|
-
maxRetries = 3 MAX_RETRY_COUNT = 3
|
|
1733
|
-
timeout = 5000 REQUEST_TIMEOUT_MS = 5000
|
|
1734
|
-
apiUrl = "..." API_BASE_URL = "..."
|
|
1735
|
-
```
|
|
1736
|
-
|
|
1737
|
-
**Rule:** Use SCREAMING_SNAKE_CASE for compile-time constants and config values. Include the unit in the name when applicable (`_MS`, `_BYTES`, `_COUNT`).
|
|
1738
|
-
|
|
1739
|
-
### Single-Letter Variables
|
|
1740
|
-
**BANNED.** With exactly ONE exception:
|
|
1741
|
-
|
|
1742
|
-
```
|
|
1743
|
-
// ALLOWED: Classic loop counter in simple iterations
|
|
1744
|
-
for (let i = 0; i < items.length; i++) { ... }
|
|
1745
|
-
|
|
1746
|
-
// BANNED: Everything else
|
|
1747
|
-
const x = getPrice(); // ❌ What is x?
|
|
1748
|
-
const n = users.length; // ❌ Use userCount
|
|
1749
|
-
arr.map(v => v.id); // ❌ Use user => user.id
|
|
1750
|
-
```
|
|
1751
|
-
|
|
1752
|
-
---
|
|
1753
|
-
|
|
1754
|
-
## File & Directory Naming
|
|
1755
|
-
|
|
1756
|
-
### Files
|
|
1757
|
-
| Type | Convention | Example |
|
|
1758
|
-
|------|-----------|---------|
|
|
1759
|
-
| Component (React/Vue) | PascalCase | `PaymentForm.tsx` |
|
|
1760
|
-
| Module/Service | camelCase or kebab-case | `paymentService.ts` or `payment-service.ts` |
|
|
1761
|
-
| Utility | camelCase or kebab-case | `formatCurrency.ts` or `format-currency.ts` |
|
|
1762
|
-
| Test | Same as source + `.test`/`.spec` | `paymentService.test.ts` |
|
|
1763
|
-
| Type/Interface | PascalCase | `PaymentTypes.ts` |
|
|
1764
|
-
| Constant file | SCREAMING_SNAKE or kebab-case | `constants.ts` or `api-endpoints.ts` |
|
|
1765
|
-
| Config | kebab-case | `database-config.ts` |
|
|
1766
|
-
|
|
1767
|
-
**Rule:** Pick ONE convention per project and enforce it everywhere. Mixing `camelCase` and `kebab-case` in the same project is a code smell.
|
|
1768
|
-
|
|
1769
|
-
### Directories
|
|
1770
|
-
- Always `kebab-case`: `user-management/`, `payment-processing/`
|
|
1771
|
-
- Never PascalCase for directories (except component folders in React convention)
|
|
1772
|
-
- No abbreviations: `auth/` → `authentication/` (unless universally understood like `api/`, `db/`)
|
|
1773
|
-
|
|
1774
|
-
---
|
|
1775
|
-
|
|
1776
|
-
## Abbreviation Policy
|
|
1777
|
-
|
|
1778
|
-
### Allowed (Universal)
|
|
1779
|
-
`id`, `url`, `api`, `db`, `http`, `io`, `ui`, `dto`, `config`, `env`, `auth`, `admin`, `src`, `lib`, `pkg`, `cmd`, `ctx`, `err`, `req`, `res` (in HTTP handler context only)
|
|
1780
|
-
|
|
1781
|
-
### Banned
|
|
1782
|
-
Everything else. Spell it out:
|
|
1783
|
-
```
|
|
1784
|
-
❌ usr → ✅ user
|
|
1785
|
-
❌ cnt → ✅ count
|
|
1786
|
-
❌ mgr → ✅ manager
|
|
1787
|
-
❌ btn → ✅ button
|
|
1788
|
-
❌ msg → ✅ message
|
|
1789
|
-
❌ impl → ✅ implementation
|
|
1790
|
-
❌ calc → ✅ calculate
|
|
1791
|
-
❌ proc → ✅ process
|
|
1792
|
-
```
|
|
1793
|
-
|
|
1794
|
-
---
|
|
1795
|
-
|
|
1796
|
-
## The Naming Decision Tree
|
|
1797
|
-
|
|
1798
|
-
```
|
|
1799
|
-
Is it a boolean?
|
|
1800
|
-
→ Yes → Add is/has/can/should prefix
|
|
1801
|
-
→ No ↓
|
|
1802
|
-
|
|
1803
|
-
Is it a function?
|
|
1804
|
-
→ Yes → Start with a verb (fetch, create, validate, calculate, is, has)
|
|
1805
|
-
→ No ↓
|
|
1806
|
-
|
|
1807
|
-
Is it a constant?
|
|
1808
|
-
→ Yes → SCREAMING_SNAKE_CASE with unit suffix
|
|
1809
|
-
→ No ↓
|
|
1810
|
-
|
|
1811
|
-
Is it a class/type/interface?
|
|
1812
|
-
→ Yes → PascalCase, noun, no prefix (not IUser, not UserInterface)
|
|
1813
|
-
→ No ↓
|
|
1814
|
-
|
|
1815
|
-
It's a variable
|
|
1816
|
-
→ Descriptive noun, camelCase
|
|
1817
|
-
→ Must be understandable without context
|
|
1818
|
-
```
|
|
1819
|
-
## UNIVERSAL RULE: performance.md
|
|
1820
|
-
Source: .agent-context/rules/performance.md
|
|
1821
|
-
|
|
1822
|
-
# Performance — Measure Before You Optimize
|
|
1823
|
-
|
|
1824
|
-
> Premature optimization is the root of all evil.
|
|
1825
|
-
> But ignoring obvious performance traps is just incompetence.
|
|
1826
|
-
|
|
1827
|
-
## The Performance Prime Directive
|
|
1828
|
-
|
|
1829
|
-
**Do NOT optimize without evidence.** CPU time is cheap. Developer time is expensive.
|
|
1830
|
-
|
|
1831
|
-
BUT — there are patterns so obviously bad that they don't need benchmarks to reject.
|
|
1832
|
-
Those are listed below as **Death Penalties**.
|
|
1833
|
-
|
|
1834
|
-
---
|
|
1835
|
-
|
|
1836
|
-
## Death Penalties (Always Reject)
|
|
1837
|
-
|
|
1838
|
-
### 1. N+1 Queries
|
|
1839
|
-
```
|
|
1840
|
-
❌ INSTANT REJECTION:
|
|
1841
|
-
const users = await db.query('SELECT * FROM users');
|
|
1842
|
-
for (const user of users) {
|
|
1843
|
-
user.orders = await db.query('SELECT * FROM orders WHERE user_id = ?', [user.id]);
|
|
1844
|
-
// This runs 1 + N queries. For 1000 users = 1001 database round trips.
|
|
1845
|
-
}
|
|
1846
|
-
|
|
1847
|
-
✅ REQUIRED:
|
|
1848
|
-
const users = await db.query('SELECT * FROM users');
|
|
1849
|
-
const userIds = users.map(u => u.id);
|
|
1850
|
-
const orders = await db.query('SELECT * FROM orders WHERE user_id = ANY($1)', [userIds]);
|
|
1851
|
-
// 2 queries total, regardless of user count
|
|
1852
|
-
// Or use ORM eager loading / DataLoader pattern
|
|
1853
|
-
```
|
|
1854
|
-
|
|
1855
|
-
**Rule:** If you see a query inside a loop, it is almost certainly an N+1 bug. Fix it with JOINs, batch queries, or DataLoader.
|
|
1856
|
-
|
|
1857
|
-
### 2. Unbounded Queries
|
|
1858
|
-
```
|
|
1859
|
-
❌ INSTANT REJECTION:
|
|
1860
|
-
const allUsers = await db.query('SELECT * FROM users');
|
|
1861
|
-
// In production, "users" table has 2 million rows. Enjoy your OOM.
|
|
1862
|
-
|
|
1863
|
-
✅ REQUIRED:
|
|
1864
|
-
const users = await db.query('SELECT * FROM users LIMIT $1 OFFSET $2', [pageSize, offset]);
|
|
1865
|
-
// Or use cursor-based pagination for large datasets
|
|
1866
|
-
```
|
|
1867
|
-
|
|
1868
|
-
**Rule:** EVERY query that returns a list MUST have a LIMIT. APIs MUST support pagination. Default page size: 20-50.
|
|
1869
|
-
|
|
1870
|
-
### 3. SELECT * in Production
|
|
1871
|
-
```
|
|
1872
|
-
❌ BANNED:
|
|
1873
|
-
SELECT * FROM users; -- Fetches 30 columns when you need 3
|
|
1874
|
-
|
|
1875
|
-
✅ REQUIRED:
|
|
1876
|
-
SELECT id, email, display_name FROM users;
|
|
1877
|
-
```
|
|
1878
|
-
|
|
1879
|
-
**Rule:** Select only the columns you need. `SELECT *` wastes bandwidth, memory, and makes schema changes dangerous.
|
|
1880
|
-
|
|
1881
|
-
### 4. Synchronous I/O in Async Context
|
|
1882
|
-
```
|
|
1883
|
-
❌ BANNED:
|
|
1884
|
-
// In an async Node.js server
|
|
1885
|
-
const data = fs.readFileSync('/path/to/file');
|
|
1886
|
-
const result = execSync('some-command');
|
|
1887
|
-
|
|
1888
|
-
✅ REQUIRED:
|
|
1889
|
-
const data = await fs.promises.readFile('/path/to/file');
|
|
1890
|
-
const result = await exec('some-command');
|
|
1891
|
-
```
|
|
1892
|
-
|
|
1893
|
-
**Rule:** In async environments (Node.js, Python asyncio), NEVER block the event loop with synchronous I/O.
|
|
1894
|
-
|
|
1895
|
-
---
|
|
1896
|
-
|
|
1897
|
-
## Optimize Only With Evidence
|
|
1898
|
-
|
|
1899
|
-
For everything else, follow this protocol:
|
|
1900
|
-
|
|
1901
|
-
### Step 1: Measure
|
|
1902
|
-
```
|
|
1903
|
-
// Use profiling tools, not guesses
|
|
1904
|
-
console.time('operation');
|
|
1905
|
-
await heavyOperation();
|
|
1906
|
-
console.timeEnd('operation');
|
|
1907
|
-
|
|
1908
|
-
// Use proper APM: Datadog, New Relic, OpenTelemetry
|
|
1909
|
-
```
|
|
1910
|
-
|
|
1911
|
-
### Step 2: Identify the Bottleneck
|
|
1912
|
-
- Is it CPU? Memory? I/O? Network?
|
|
1913
|
-
- Don't optimize CPU when the bottleneck is a slow database query
|
|
1914
|
-
|
|
1915
|
-
### Step 3: Optimize the Bottleneck (Not Everything Else)
|
|
1916
|
-
- Fix the slowest thing first
|
|
1917
|
-
- Re-measure after each change
|
|
1918
|
-
- Stop when performance meets requirements
|
|
1919
|
-
|
|
1920
|
-
---
|
|
1921
|
-
|
|
1922
|
-
## Caching Rules
|
|
1923
|
-
|
|
1924
|
-
### When to Cache
|
|
1925
|
-
- Data that is read frequently, written rarely
|
|
1926
|
-
- Expensive computations that produce the same result for same inputs
|
|
1927
|
-
- External API responses (respect their cache headers)
|
|
1928
|
-
|
|
1929
|
-
### When NOT to Cache
|
|
1930
|
-
- Data that changes every request
|
|
1931
|
-
- Small, fast queries (cache overhead > query time)
|
|
1932
|
-
- When you can't define a clear invalidation strategy
|
|
1933
|
-
|
|
1934
|
-
### The Caching Devil's Triangle
|
|
1935
|
-
You can have 2 of 3:
|
|
1936
|
-
1. **Fresh data** (always up-to-date)
|
|
1937
|
-
2. **Fast reads** (sub-millisecond)
|
|
1938
|
-
3. **Simple code** (no cache layer)
|
|
1939
|
-
|
|
1940
|
-
Pick your 2 and document the trade-off.
|
|
1941
|
-
|
|
1942
|
-
### Invalidation Strategy (MANDATORY)
|
|
1943
|
-
```
|
|
1944
|
-
❌ BANNED:
|
|
1945
|
-
cache.set('users', data); // When does this expire? Who invalidates it? Nobody knows.
|
|
1946
|
-
|
|
1947
|
-
✅ REQUIRED:
|
|
1948
|
-
cache.set('users:active', data, { ttl: 300 }); // 5 min TTL, explicit key
|
|
1949
|
-
// AND document: "Invalidated on user.created, user.deleted, user.deactivated events"
|
|
1950
|
-
```
|
|
1951
|
-
|
|
1952
|
-
**Rule:** NEVER add a cache without documenting the invalidation strategy. "We'll figure it out later" means "we'll have stale data in production."
|
|
1953
|
-
|
|
1954
|
-
---
|
|
1955
|
-
|
|
1956
|
-
## Connection Management
|
|
1957
|
-
|
|
1958
|
-
1. **Use connection pools** — never open/close connections per request
|
|
1959
|
-
2. **Set pool limits** — max connections based on infrastructure, not infinity
|
|
1960
|
-
3. **Implement timeouts** — connection timeout, query timeout, request timeout
|
|
1961
|
-
4. **Handle pool exhaustion** — fail fast with clear error, don't queue forever
|
|
1962
|
-
|
|
1963
|
-
---
|
|
1964
|
-
|
|
1965
|
-
## Frontend Performance (When Applicable)
|
|
1966
|
-
|
|
1967
|
-
1. **Lazy load** routes and heavy components
|
|
1968
|
-
2. **Debounce** search inputs and scroll handlers (300ms minimum)
|
|
1969
|
-
3. **Virtualize** long lists (don't render 10,000 DOM nodes)
|
|
1970
|
-
4. **Optimize images** — WebP/AVIF, responsive sizes, lazy loading
|
|
1971
|
-
5. **Bundle analysis** — if your JS bundle exceeds 200KB gzipped, investigate
|
|
1972
|
-
|
|
1973
|
-
---
|
|
1974
|
-
|
|
1975
|
-
## The Performance Decision Framework
|
|
1976
|
-
|
|
1977
|
-
```
|
|
1978
|
-
Is it a known Death Penalty pattern (N+1, unbounded, SELECT *, sync I/O)?
|
|
1979
|
-
→ Yes → Fix it now, no measurement needed
|
|
1980
|
-
→ No ↓
|
|
1981
|
-
|
|
1982
|
-
Is there a measurable performance problem?
|
|
1983
|
-
→ No → Don't optimize. Ship it.
|
|
1984
|
-
→ Yes ↓
|
|
1985
|
-
|
|
1986
|
-
Have you identified the specific bottleneck?
|
|
1987
|
-
→ No → Profile first
|
|
1988
|
-
→ Yes → Optimize ONLY that bottleneck, re-measure, stop when sufficient
|
|
1989
|
-
```
|
|
1990
|
-
## UNIVERSAL RULE: realtime.md
|
|
1991
|
-
Source: .agent-context/rules/realtime.md
|
|
1992
|
-
|
|
1993
|
-
# Real-time & WebSockets Architecture
|
|
1994
|
-
|
|
1995
|
-
> WebSockets are stateful tcp connections in a stateless HTTP world. Treat them with extreme caution.
|
|
1996
|
-
|
|
1997
|
-
## 1. Connection Management & Scaling
|
|
1998
|
-
WebSockets hold a persistent connection, consuming file descriptors and memory on the server.
|
|
1999
|
-
- **Rule:** NEVER scale WebSockets using a standard load balancer without sticky sessions (unless utilizing an external Pub/Sub service).
|
|
2000
|
-
- **Architecture:** Use a dedicated external Pub/Sub layer to broadcast messages across multiple WebSocket server instances.
|
|
2001
|
-
- **Standard:** Redis Pub/Sub (Socket.io-redis, any standard Redis adapter).
|
|
2002
|
-
- **Enterprise:** NATS, Apache Kafka, or managed services like AWS API Gateway WebSockets / Pusher / Socket.io / Soketi.
|
|
2003
|
-
- **Goal:** Any user can connect to Server A, and receive a message published by a background job running on Server B.
|
|
2004
|
-
|
|
2005
|
-
## 2. Authentication Context
|
|
2006
|
-
WebSockets cannot rely on traditional HTTP Authorization headers during the handshake in browser environments (as the WebSocket API does not allow setting custom headers).
|
|
2007
|
-
- **Rule:** Authenticate connections explicitly.
|
|
2008
|
-
- **Method A (Cookies):** If the application uses HTTPOnly cookies, authentication happens naturally during the initial HTTP upgrade request.
|
|
2009
|
-
- **Method B (Tickets/Tokens):** Pass the JWT or an ephemeral ticket in the connection URL query string (`?token=xyz`) or immediately after connecting via a dedicated `auth` JSON payload.
|
|
2010
|
-
- **BANNED:** Never trust a client simply because they know the WebSocket URL.
|
|
2011
|
-
|
|
2012
|
-
## 3. The "No Business Logic in Sockets" Rule
|
|
2013
|
-
A WebSocket controller should be treated exactly like an HTTP controller.
|
|
2014
|
-
- **Rule:** The WebSocket handler's **only** responsibility is parsing the incoming message, validating the payload schema, and routing it to a Service/Application layer orchestrator.
|
|
2015
|
-
- **BANNED:** Directly writing to databases, executing complex business logic, or calling external APIs directly inside the WebSocket event callback.
|
|
2016
|
-
|
|
2017
|
-
## 4. Heartbeats & Dead Connections
|
|
2018
|
-
TCP connections drop silently (e.g., when a user drives through a tunnel).
|
|
2019
|
-
- **Rule:** Implement Ping/Pong heartbeats. The server MUST periodically ping the client. If the client fails to respond within the expected window (e.g., 30s), the server MUST forcefully sever the connection to free resources (`ws.terminate()`, not `ws.close()`).
|
|
2020
|
-
|
|
2021
|
-
## 5. Event Design & Typing
|
|
2022
|
-
Treat WebSocket events like a strongly typed REST API.
|
|
2023
|
-
- **Rule:** Every WebSocket message MUST have a mandatory `type` or `event` field, and a strongly typed `payload` field defined by a schema (e.g., Zod, JSON Schema).
|
|
2024
|
-
- **BANNED:** Emitting arbitrary strings or untyped JSON objects like `{ user_id: 1, message: "hi" }`.
|
|
2025
|
-
- **REQUIRED:**
|
|
2026
|
-
```json
|
|
2027
|
-
{
|
|
2028
|
-
"event": "message.created",
|
|
2029
|
-
"payload": {
|
|
2030
|
-
"id": "msg_123",
|
|
2031
|
-
"text": "Hello World",
|
|
2032
|
-
"timestamp": "2026-03-01T12:00:00Z"
|
|
2033
|
-
}
|
|
2034
|
-
}
|
|
2035
|
-
```
|
|
2036
|
-
|
|
2037
|
-
## 6. Rate Limiting & Abuse Prevention
|
|
2038
|
-
WebSockets are heavily susceptible to DoS attacks because an open connection bypasses traditional WAF rate limiters.
|
|
2039
|
-
- **Rule:** You MUST implement message rate limiting at the application logic layer (e.g., maximum 5 messages per second per connection/user). Violators should be instantly disconnected and IP-banned temporarily.
|
|
2040
|
-
## UNIVERSAL RULE: security.md
|
|
2041
|
-
Source: .agent-context/rules/security.md
|
|
2042
|
-
|
|
2043
|
-
# Security — Trust Nothing, Validate Everything
|
|
2044
|
-
|
|
2045
|
-
> Every user is a potential attacker. Every input is a potential exploit.
|
|
2046
|
-
> You are not paranoid — you are professional.
|
|
2047
|
-
|
|
2048
|
-
## The Zero Trust Input Rule
|
|
2049
|
-
|
|
2050
|
-
**ALL data crossing a system boundary is untrusted until validated.**
|
|
2051
|
-
|
|
2052
|
-
System boundaries include:
|
|
2053
|
-
- HTTP request bodies, query params, headers, cookies
|
|
2054
|
-
- URL path parameters
|
|
2055
|
-
- File uploads
|
|
2056
|
-
- WebSocket messages
|
|
2057
|
-
- Queue/event payloads
|
|
2058
|
-
- Environment variables (at startup)
|
|
2059
|
-
- Database query results (yes, even these — data could be corrupted)
|
|
2060
|
-
- Third-party API responses
|
|
2061
|
-
|
|
2062
|
-
### Validation Protocol
|
|
2063
|
-
```
|
|
2064
|
-
External Input → Schema Validation (Zod/Pydantic/Bean Validation) → Typed Internal Object
|
|
2065
|
-
|
|
2066
|
-
NEVER:
|
|
2067
|
-
External Input → Direct use in business logic
|
|
2068
|
-
External Input → Direct use in database query
|
|
2069
|
-
External Input → Direct interpolation into strings
|
|
2070
|
-
```
|
|
2071
|
-
|
|
2072
|
-
---
|
|
2073
|
-
|
|
2074
|
-
## Injection Prevention
|
|
2075
|
-
|
|
2076
|
-
### SQL Injection — Parameterized Queries Only
|
|
2077
|
-
```
|
|
2078
|
-
❌ DEATH PENALTY:
|
|
2079
|
-
const query = `SELECT * FROM users WHERE id = ${userId}`;
|
|
2080
|
-
const query = "SELECT * FROM users WHERE name = '" + name + "'";
|
|
2081
|
-
|
|
2082
|
-
✅ ALWAYS:
|
|
2083
|
-
const user = await db.query('SELECT * FROM users WHERE id = $1', [userId]);
|
|
2084
|
-
const user = await prisma.user.findUnique({ where: { id: userId } });
|
|
2085
|
-
```
|
|
2086
|
-
|
|
2087
|
-
**Rule:** NEVER concatenate or interpolate user input into SQL strings. Use parameterized queries or ORMs. No exceptions. Not even "just for testing."
|
|
2088
|
-
|
|
2089
|
-
### Command Injection
|
|
2090
|
-
```
|
|
2091
|
-
❌ DEATH PENALTY:
|
|
2092
|
-
exec(`convert ${filename} output.png`);
|
|
2093
|
-
|
|
2094
|
-
✅ ALWAYS:
|
|
2095
|
-
execFile('convert', [filename, 'output.png']);
|
|
2096
|
-
```
|
|
2097
|
-
|
|
2098
|
-
**Rule:** Never pass user input to shell commands via string interpolation. Use argument arrays.
|
|
2099
|
-
|
|
2100
|
-
### XSS Prevention
|
|
2101
|
-
```
|
|
2102
|
-
❌ BANNED:
|
|
2103
|
-
element.innerHTML = userInput;
|
|
2104
|
-
dangerouslySetInnerHTML={{ __html: userInput }};
|
|
2105
|
-
|
|
2106
|
-
✅ REQUIRED:
|
|
2107
|
-
element.textContent = userInput;
|
|
2108
|
-
// Or sanitize with DOMPurify if HTML is absolutely needed
|
|
2109
|
-
```
|
|
2110
|
-
|
|
2111
|
-
**Rule:** Default to text content. Only use HTML injection with explicit sanitization AND a code comment explaining WHY.
|
|
2112
|
-
|
|
2113
|
-
---
|
|
2114
|
-
|
|
2115
|
-
## Secret Management
|
|
2116
|
-
|
|
2117
|
-
### Rule: ZERO Secrets in Code
|
|
2118
|
-
|
|
2119
|
-
```
|
|
2120
|
-
❌ INSTANT REJECTION:
|
|
2121
|
-
const API_KEY = "sk-abc123def456";
|
|
2122
|
-
const DB_PASSWORD = "supersecret";
|
|
2123
|
-
const JWT_SECRET = "my-jwt-secret";
|
|
2124
|
-
// Even in .env.example with real values
|
|
2125
|
-
|
|
2126
|
-
✅ REQUIRED:
|
|
2127
|
-
const API_KEY = process.env.API_KEY; // Read from environment
|
|
2128
|
-
const DB_URL = process.env.DATABASE_URL; // Injected at runtime
|
|
2129
|
-
```
|
|
2130
|
-
|
|
2131
|
-
### .env Files
|
|
2132
|
-
- `.env` → NEVER committed (must be in `.gitignore`)
|
|
2133
|
-
- `.env.example` → Committed with placeholder values ONLY (`API_KEY=your-api-key-here`)
|
|
2134
|
-
- `.env.local` → NEVER committed
|
|
2135
|
-
- `.env.test` → May be committed with TEST-ONLY non-secret values
|
|
2136
|
-
|
|
2137
|
-
### Secret Rotation
|
|
2138
|
-
If a secret is accidentally committed:
|
|
2139
|
-
1. Rotate the secret IMMEDIATELY (not after the PR is merged — NOW)
|
|
2140
|
-
2. Remove from git history (`git filter-branch` or BFG)
|
|
2141
|
-
3. Add to `.gitignore`
|
|
2142
|
-
4. Document the incident
|
|
2143
|
-
|
|
2144
|
-
---
|
|
2145
|
-
|
|
2146
|
-
## Authentication & Authorization
|
|
2147
|
-
|
|
2148
|
-
### Authentication Rules
|
|
2149
|
-
1. Never implement custom auth crypto — use established libraries (argon2, bcrypt, Passport, NextAuth)
|
|
2150
|
-
2. Hash passwords with **argon2id** (OWASP primary recommendation) — bcrypt only for legacy systems
|
|
2151
|
-
- Argon2id: minimum 19 MiB memory, 2 iterations, 1 parallelism
|
|
2152
|
-
- bcrypt: minimum cost 12 (legacy systems only — limited to 72 bytes, no memory-hardness)
|
|
2153
|
-
- NEVER: MD5, SHA1, plain SHA256, or PBKDF2 without FIPS requirement
|
|
2154
|
-
3. Use constant-time comparison for tokens and hashes
|
|
2155
|
-
4. Implement rate limiting on auth endpoints (max 5 attempts per minute per IP)
|
|
2156
|
-
5. Session tokens must be cryptographically random (≥ 256 bits)
|
|
2157
|
-
|
|
2158
|
-
### Authorization Rules
|
|
2159
|
-
1. **Default deny** — if no rule grants access, deny
|
|
2160
|
-
2. **Server-side only** — NEVER trust client-side role checks for security
|
|
2161
|
-
3. Check authorization at the service layer, not just the controller
|
|
2162
|
-
4. Log all authorization failures with context (userId, resource, action)
|
|
2163
|
-
|
|
2164
|
-
```
|
|
2165
|
-
❌ BANNED: Client-side only authorization
|
|
2166
|
-
if (user.role === 'admin') { showDeleteButton(); }
|
|
2167
|
-
// Attacker just changes user.role in devtools
|
|
2168
|
-
|
|
2169
|
-
✅ REQUIRED: Server-side enforcement
|
|
2170
|
-
// Controller checks auth, service enforces business rules
|
|
2171
|
-
async deleteUser(requesterId: string, targetUserId: string) {
|
|
2172
|
-
const requester = await this.userRepo.findById(requesterId);
|
|
2173
|
-
if (!requester || requester.role !== Role.ADMIN) {
|
|
2174
|
-
throw new ForbiddenError('Insufficient permissions');
|
|
2175
|
-
}
|
|
2176
|
-
// ... proceed with deletion
|
|
2177
|
-
}
|
|
2178
|
-
```
|
|
2179
|
-
|
|
2180
|
-
---
|
|
2181
|
-
|
|
2182
|
-
## HTTP Security Headers
|
|
2183
|
-
|
|
2184
|
-
Every web application MUST include:
|
|
2185
|
-
```
|
|
2186
|
-
Strict-Transport-Security: max-age=31536000; includeSubDomains
|
|
2187
|
-
Content-Security-Policy: default-src 'self'; script-src 'self'
|
|
2188
|
-
X-Content-Type-Options: nosniff
|
|
2189
|
-
X-Frame-Options: DENY
|
|
2190
|
-
Referrer-Policy: strict-origin-when-cross-origin
|
|
2191
|
-
Permissions-Policy: camera=(), microphone=(), geolocation=()
|
|
2192
|
-
```
|
|
2193
|
-
|
|
2194
|
-
### CORS
|
|
2195
|
-
- NEVER use `Access-Control-Allow-Origin: *` in production
|
|
2196
|
-
- Whitelist specific origins
|
|
2197
|
-
- Be explicit about allowed methods and headers
|
|
2198
|
-
|
|
2199
|
-
---
|
|
2200
|
-
|
|
2201
|
-
## File Upload Security
|
|
2202
|
-
|
|
2203
|
-
1. Validate MIME type server-side (not just file extension)
|
|
2204
|
-
2. Set maximum file size limits
|
|
2205
|
-
3. Generate random filenames — never use user-provided filenames
|
|
2206
|
-
4. Store uploads outside the web root
|
|
2207
|
-
5. Scan for malware if accepting documents
|
|
2208
|
-
|
|
2209
|
-
---
|
|
2210
|
-
|
|
2211
|
-
## Supply Chain Security (OWASP 2025 A03)
|
|
2212
|
-
|
|
2213
|
-
1. **Pin all dependency versions** — use lockfiles, never `*` ranges in production
|
|
2214
|
-
2. **Audit dependencies regularly** — `npm audit`, `pip audit`, `composer audit`
|
|
2215
|
-
3. **Verify package integrity** — check checksums, use signed packages where available
|
|
2216
|
-
4. **Minimize dependency trees** — fewer transitive dependencies = smaller attack surface
|
|
2217
|
-
5. **Monitor for CVEs** — automate vulnerability scanning in CI (Dependabot, Snyk, Trivy)
|
|
2218
|
-
6. **Review new dependencies** — check maintainer history, download trends, and bus factor before adding
|
|
2219
|
-
|
|
2220
|
-
---
|
|
2221
|
-
|
|
2222
|
-
## .gitignore Enforcement (Mandatory)
|
|
2223
|
-
|
|
2224
|
-
**If the user's INTENT is to create a new project, push to GitHub, or initialize source control, you MUST generate or verify a `.gitignore` file exists.**
|
|
2225
|
-
|
|
2226
|
-
### Minimum Required Entries
|
|
2227
|
-
```gitignore
|
|
2228
|
-
# ── Secrets & Environment ──
|
|
2229
|
-
.env
|
|
2230
|
-
.env.local
|
|
2231
|
-
.env.*.local
|
|
2232
|
-
.env.production
|
|
2233
|
-
.env.staging
|
|
2234
|
-
|
|
2235
|
-
# ── Dependencies ──
|
|
2236
|
-
node_modules/
|
|
2237
|
-
vendor/
|
|
2238
|
-
venv/
|
|
2239
|
-
.venv/
|
|
2240
|
-
__pycache__/
|
|
2241
|
-
.gradle/
|
|
2242
|
-
target/
|
|
2243
|
-
bin/ # Go binaries
|
|
2244
|
-
pkg/
|
|
2245
|
-
|
|
2246
|
-
# ── Build Output ──
|
|
2247
|
-
dist/
|
|
2248
|
-
build/
|
|
2249
|
-
out/
|
|
2250
|
-
*.min.js
|
|
2251
|
-
*.min.css
|
|
2252
|
-
.next/
|
|
2253
|
-
.nuxt/
|
|
2254
|
-
.output/
|
|
2255
|
-
|
|
2256
|
-
# ── IDE & Editor ──
|
|
2257
|
-
.idea/
|
|
2258
|
-
.vscode/settings.json
|
|
2259
|
-
.vscode/launch.json
|
|
2260
|
-
*.swp
|
|
2261
|
-
*.swo
|
|
2262
|
-
*~
|
|
2263
|
-
|
|
2264
|
-
# ── OS Artifacts ──
|
|
2265
|
-
.DS_Store
|
|
2266
|
-
Thumbs.db
|
|
2267
|
-
Desktop.ini
|
|
2268
|
-
*.lnk
|
|
2269
|
-
|
|
2270
|
-
# ── Logs ──
|
|
2271
|
-
*.log
|
|
2272
|
-
npm-debug.log*
|
|
2273
|
-
yarn-debug.log*
|
|
2274
|
-
pnpm-debug.log*
|
|
2275
|
-
|
|
2276
|
-
# ── Testing & Coverage ──
|
|
2277
|
-
coverage/
|
|
2278
|
-
.nyc_output/
|
|
2279
|
-
*.lcov
|
|
2280
|
-
|
|
2281
|
-
# ── Runtime & Backup Data ──
|
|
2282
|
-
*.pid
|
|
2283
|
-
*.seed
|
|
2284
|
-
*.pid.lock
|
|
2285
|
-
.agentic-backup/
|
|
2286
|
-
|
|
2287
|
-
# ── Secrets & Keys ──
|
|
2288
|
-
*.pem
|
|
2289
|
-
*.key
|
|
2290
|
-
*.p12
|
|
2291
|
-
*.jks
|
|
2292
|
-
*.keystore
|
|
2293
|
-
```
|
|
2294
|
-
|
|
2295
|
-
### Rules
|
|
2296
|
-
1. **NEVER commit `.env`** — only `.env.example` with placeholder values
|
|
2297
|
-
2. **Check for leaks before push** — `git diff --cached --name-only | grep -E '\.(env|pem|key)$'` should return empty
|
|
2298
|
-
3. **If the project has NO `.gitignore`**, create one immediately before any `git add`
|
|
2299
|
-
4. **Extend per-stack** — add language-specific patterns (e.g., `__pycache__/` for Python, `target/` for Java/Rust, `.gradle/` for Kotlin)
|
|
2300
|
-
5. **Reference**: See `.agent-context/rules/git-workflow.md` for the full `.gitignore Standards` section
|
|
2301
|
-
|
|
2302
|
-
### MUST Commit (Whitelist)
|
|
2303
|
-
```
|
|
2304
|
-
.env.example # Template with placeholder values ONLY
|
|
2305
|
-
.editorconfig # Consistent formatting across IDEs
|
|
2306
|
-
.gitignore # This file itself
|
|
2307
|
-
docker-compose.yml # Dev environment definition
|
|
2308
|
-
Makefile / Taskfile # Standard dev commands
|
|
2309
|
-
```
|
|
2310
|
-
|
|
2311
|
-
---
|
|
2312
|
-
|
|
2313
|
-
## The Security Checklist (Quick Reference)
|
|
2314
|
-
|
|
2315
|
-
Before any code is "done", verify:
|
|
2316
|
-
|
|
2317
|
-
- [ ] All inputs validated at boundaries with schemas
|
|
2318
|
-
- [ ] No string concatenation in queries/commands
|
|
2319
|
-
- [ ] No secrets in source code
|
|
2320
|
-
- [ ] `.gitignore` exists and covers `.env`, `node_modules/`, build output, and IDE files
|
|
2321
|
-
- [ ] Authentication uses established libraries
|
|
2322
|
-
- [ ] Password hashing uses argon2id (or bcrypt for legacy)
|
|
2323
|
-
- [ ] Authorization enforced server-side
|
|
2324
|
-
- [ ] Security headers configured
|
|
2325
|
-
- [ ] CORS properly restricted
|
|
2326
|
-
- [ ] Rate limiting on sensitive endpoints
|
|
2327
|
-
- [ ] Error responses don't leak internal details
|
|
2328
|
-
- [ ] Logging includes security events (login failures, permission denials)
|
|
2329
|
-
- [ ] Dependencies audited for known vulnerabilities
|
|
2330
|
-
## UNIVERSAL RULE: testing.md
|
|
2331
|
-
Source: .agent-context/rules/testing.md
|
|
2332
|
-
|
|
2333
|
-
# Testing — Prove It Works, Don't Pray
|
|
2334
|
-
|
|
2335
|
-
> "It works on my machine" is not a test strategy.
|
|
2336
|
-
> Untested code is broken code that hasn't been caught yet.
|
|
2337
|
-
|
|
2338
|
-
## The Test Pyramid (Enforced)
|
|
2339
|
-
|
|
2340
|
-
```
|
|
2341
|
-
╱ E2E ╲ Few — Slow — Expensive — Fragile
|
|
2342
|
-
╱──────────╲ Test critical user journeys only
|
|
2343
|
-
╱ Integration ╲ Medium — Test module boundaries
|
|
2344
|
-
╱────────────────╲ Database, API contracts, service interactions
|
|
2345
|
-
╱ Unit Tests ╲ Many — Fast — Cheap — Stable
|
|
2346
|
-
╱──────────────────────╲ Test business logic in isolation
|
|
2347
|
-
```
|
|
2348
|
-
|
|
2349
|
-
### Ratios
|
|
2350
|
-
- **Unit tests:** 70% — fast, isolated, test business rules
|
|
2351
|
-
- **Integration tests:** 20% — test boundaries (DB, APIs, modules)
|
|
2352
|
-
- **E2E tests:** 10% — test critical user flows only
|
|
2353
|
-
|
|
2354
|
-
---
|
|
2355
|
-
|
|
2356
|
-
## What to Test (And What Not To)
|
|
2357
|
-
|
|
2358
|
-
### ✅ ALWAYS Test
|
|
2359
|
-
- Business logic and calculations
|
|
2360
|
-
- Input validation rules
|
|
2361
|
-
- Edge cases (empty arrays, null values, boundary numbers)
|
|
2362
|
-
- Error handling paths (what happens when things fail)
|
|
2363
|
-
- State transitions and workflows
|
|
2364
|
-
- Authorization rules
|
|
2365
|
-
|
|
2366
|
-
### ❌ NEVER Test
|
|
2367
|
-
- Framework internals (don't test that Express routes work)
|
|
2368
|
-
- Simple getters/setters with no logic
|
|
2369
|
-
- Third-party library behavior
|
|
2370
|
-
- Private implementation details (test behavior, not structure)
|
|
2371
|
-
- Database migrations (verify schema, don't test the migration tool)
|
|
2372
|
-
|
|
2373
|
-
---
|
|
2374
|
-
|
|
2375
|
-
## Test Naming Convention
|
|
2376
|
-
|
|
2377
|
-
### Pattern: `should [expected behavior] when [condition]`
|
|
2378
|
-
|
|
2379
|
-
```
|
|
2380
|
-
❌ BANNED:
|
|
2381
|
-
test('test1')
|
|
2382
|
-
test('it works')
|
|
2383
|
-
test('calculateDiscount')
|
|
2384
|
-
|
|
2385
|
-
✅ REQUIRED:
|
|
2386
|
-
test('should apply 20% discount when order total exceeds $100')
|
|
2387
|
-
test('should throw ValidationError when email format is invalid')
|
|
2388
|
-
test('should return empty array when user has no orders')
|
|
2389
|
-
test('should deny access when user lacks admin role')
|
|
2390
|
-
```
|
|
2391
|
-
|
|
2392
|
-
**Rule:** A reader must understand the expected behavior WITHOUT reading the test body.
|
|
2393
|
-
|
|
2394
|
-
---
|
|
2395
|
-
|
|
2396
|
-
## Test Structure: AAA Pattern
|
|
2397
|
-
|
|
2398
|
-
Every test follows **Arrange → Act → Assert**:
|
|
2399
|
-
|
|
2400
|
-
```typescript
|
|
2401
|
-
test('should calculate shipping as free when order exceeds $50', () => {
|
|
2402
|
-
// Arrange — Set up the scenario
|
|
2403
|
-
const order = createOrder({ items: [{ price: 60, quantity: 1 }] });
|
|
2404
|
-
|
|
2405
|
-
// Act — Execute the behavior
|
|
2406
|
-
const shippingCost = calculateShipping(order);
|
|
2407
|
-
|
|
2408
|
-
// Assert — Verify the outcome
|
|
2409
|
-
expect(shippingCost).toBe(0);
|
|
2410
|
-
});
|
|
2411
|
-
```
|
|
2412
|
-
|
|
2413
|
-
### Rules
|
|
2414
|
-
- **ONE assert concept per test** (multiple `expect` calls are fine if they test the same concept)
|
|
2415
|
-
- **No logic in tests** (no if/else, no loops, no try/catch)
|
|
2416
|
-
- **Tests must be independent** — no shared mutable state, no execution order dependency
|
|
2417
|
-
- **Tests must be deterministic** — same input = same result, every time
|
|
2418
|
-
|
|
2419
|
-
---
|
|
2420
|
-
|
|
2421
|
-
## Mocking Rules
|
|
2422
|
-
|
|
2423
|
-
### Mock at Boundaries, Not Everywhere
|
|
2424
|
-
|
|
2425
|
-
```
|
|
2426
|
-
❌ OVER-MOCKING (testing implementation, not behavior):
|
|
2427
|
-
test('should call repository.save exactly once', () => {
|
|
2428
|
-
await service.createUser(userData);
|
|
2429
|
-
expect(repository.save).toHaveBeenCalledTimes(1);
|
|
2430
|
-
// If you refactor to call save differently, the test breaks
|
|
2431
|
-
// even though the behavior hasn't changed
|
|
2432
|
-
});
|
|
2433
|
-
|
|
2434
|
-
✅ CORRECT (testing behavior):
|
|
2435
|
-
test('should persist user and return created user', () => {
|
|
2436
|
-
const result = await service.createUser(userData);
|
|
2437
|
-
expect(result.id).toBeDefined();
|
|
2438
|
-
expect(result.email).toBe(userData.email);
|
|
2439
|
-
// You can verify the user exists in the test DB if integration test
|
|
2440
|
-
});
|
|
2441
|
-
```
|
|
2442
|
-
|
|
2443
|
-
### When to Mock
|
|
2444
|
-
- External APIs (payment gateways, email services)
|
|
2445
|
-
- Time-dependent operations (use fake timers)
|
|
2446
|
-
- Non-deterministic operations (random, UUID)
|
|
2447
|
-
|
|
2448
|
-
### When NOT to Mock
|
|
2449
|
-
- Your own code in the same module ← test the real integration
|
|
2450
|
-
- Simple utility functions ← use the real thing
|
|
2451
|
-
- Database in integration tests ← use a test database
|
|
2452
|
-
|
|
2453
|
-
---
|
|
2454
|
-
|
|
2455
|
-
## Test Data Standards
|
|
2456
|
-
|
|
2457
|
-
### Use Factories, Not Copy-Pasted Objects
|
|
2458
|
-
|
|
2459
|
-
```
|
|
2460
|
-
❌ BANNED:
|
|
2461
|
-
test('should calculate total', () => {
|
|
2462
|
-
const order = {
|
|
2463
|
-
id: '123',
|
|
2464
|
-
userId: '456',
|
|
2465
|
-
items: [{ productId: '789', price: 29.99, quantity: 2, name: 'Widget' }],
|
|
2466
|
-
status: 'pending',
|
|
2467
|
-
createdAt: new Date(),
|
|
2468
|
-
updatedAt: new Date(),
|
|
2469
|
-
// ... 15 more fields copied from another test
|
|
2470
|
-
};
|
|
2471
|
-
});
|
|
2472
|
-
|
|
2473
|
-
✅ REQUIRED:
|
|
2474
|
-
test('should calculate total', () => {
|
|
2475
|
-
const order = createTestOrder({
|
|
2476
|
-
items: [createTestItem({ price: 29.99, quantity: 2 })],
|
|
2477
|
-
});
|
|
2478
|
-
// Factory fills in all other fields with sensible defaults
|
|
2479
|
-
});
|
|
2480
|
-
```
|
|
2481
|
-
|
|
2482
|
-
### Rules
|
|
2483
|
-
- Create factory functions for each domain entity
|
|
2484
|
-
- Factories provide sensible defaults — tests override only relevant fields
|
|
2485
|
-
- Never use production data in tests
|
|
2486
|
-
- Use descriptive, obviously-fake data (email: `test-user@example.com`, not `john@gmail.com`)
|
|
2487
|
-
|
|
2488
|
-
---
|
|
2489
|
-
|
|
2490
|
-
## Coverage Expectations
|
|
2491
|
-
|
|
2492
|
-
| Layer | Min Coverage | What to Focus On |
|
|
2493
|
-
|-------|-------------|------------------|
|
|
2494
|
-
| Domain / Business Logic | 90%+ | All branching, edge cases, error paths |
|
|
2495
|
-
| Application / Service | 80%+ | Orchestration flows, error handling |
|
|
2496
|
-
| Transport / Controller | 60%+ | Input validation, error responses |
|
|
2497
|
-
| Utilities | 90%+ | All functions and edge cases |
|
|
2498
|
-
|
|
2499
|
-
**Rule:** Coverage is a floor, not a goal. 100% coverage with bad tests is worse than 80% coverage with good tests. Focus on testing **behavior and edge cases**, not hitting a number.
|
|
2500
|
-
|
|
2501
|
-
---
|
|
2502
|
-
|
|
2503
|
-
## When to Skip Tests (Rare)
|
|
2504
|
-
|
|
2505
|
-
You may skip tests ONLY for:
|
|
2506
|
-
- Prototype/spike code (must be labeled `// SPIKE: will be replaced`)
|
|
2507
|
-
- Pure UI layout (visual testing is better here — use Storybook/Chromatic)
|
|
2508
|
-
- Generated code (test the generator, not the output)
|
|
2509
|
-
|
|
2510
|
-
Everything else gets tested. No excuses.
|
|
2511
|
-
## STACK PROFILE: typescript.md
|
|
19
|
+
## BOOTSTRAP CHAIN (MANDATORY)
|
|
20
|
+
Load every layer before responding. Do not skip steps:
|
|
21
|
+
1. .agent-context/rules/
|
|
22
|
+
2. .agent-context/stacks/
|
|
23
|
+
3. .agent-context/blueprints/
|
|
24
|
+
4. .agent-context/skills/
|
|
25
|
+
5. .agent-context/prompts/
|
|
26
|
+
6. .agent-context/profiles/
|
|
27
|
+
7. .agent-context/state/
|
|
28
|
+
8. .agent-context/policies/llm-judge-threshold.json
|
|
29
|
+
|
|
30
|
+
Primary entrypoint: .cursorrules
|
|
31
|
+
Mirror entrypoint: .windsurfrules
|
|
32
|
+
Canonical baseline: .instructions.md
|
|
33
|
+
## LAYER 1: UNIVERSAL RULES (MANDATORY)
|
|
34
|
+
Read every file under .agent-context/rules/ before implementation:
|
|
35
|
+
1. .agent-context/rules/api-docs.md
|
|
36
|
+
2. .agent-context/rules/architecture.md
|
|
37
|
+
3. .agent-context/rules/database-design.md
|
|
38
|
+
4. .agent-context/rules/efficiency-vs-hype.md
|
|
39
|
+
5. .agent-context/rules/error-handling.md
|
|
40
|
+
6. .agent-context/rules/event-driven.md
|
|
41
|
+
7. .agent-context/rules/frontend-architecture.md
|
|
42
|
+
8. .agent-context/rules/git-workflow.md
|
|
43
|
+
9. .agent-context/rules/microservices.md
|
|
44
|
+
10. .agent-context/rules/naming-conv.md
|
|
45
|
+
11. .agent-context/rules/performance.md
|
|
46
|
+
12. .agent-context/rules/realtime.md
|
|
47
|
+
13. .agent-context/rules/security.md
|
|
48
|
+
14. .agent-context/rules/testing.md
|
|
49
|
+
|
|
50
|
+
Conflict resolution: prioritize data safety and API contract integrity first, then writing polish.
|
|
51
|
+
## LAYER 2: STACK PROFILE (typescript.md)
|
|
2512
52
|
Source: .agent-context/stacks/typescript.md
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
> TypeScript is not JavaScript with optional types.
|
|
2517
|
-
> It is a contract system. Use it like one.
|
|
2518
|
-
|
|
2519
|
-
## Compiler Configuration (Non-Negotiable)
|
|
2520
|
-
|
|
2521
|
-
### tsconfig.json Strict Settings
|
|
2522
|
-
```json
|
|
2523
|
-
{
|
|
2524
|
-
"compilerOptions": {
|
|
2525
|
-
"strict": true,
|
|
2526
|
-
"noUncheckedIndexedAccess": true,
|
|
2527
|
-
"noImplicitReturns": true,
|
|
2528
|
-
"noFallthroughCasesInSwitch": true,
|
|
2529
|
-
"noUnusedLocals": true,
|
|
2530
|
-
"noUnusedParameters": true,
|
|
2531
|
-
"exactOptionalPropertyTypes": true,
|
|
2532
|
-
"forceConsistentCasingInFileNames": true,
|
|
2533
|
-
"isolatedModules": true,
|
|
2534
|
-
"esModuleInterop": true,
|
|
2535
|
-
"resolveJsonModule": true,
|
|
2536
|
-
"moduleResolution": "bundler",
|
|
2537
|
-
"module": "ESNext",
|
|
2538
|
-
"target": "ES2022",
|
|
2539
|
-
"skipLibCheck": true,
|
|
2540
|
-
"paths": {
|
|
2541
|
-
"@/*": ["./src/*"]
|
|
2542
|
-
}
|
|
2543
|
-
}
|
|
2544
|
-
}
|
|
2545
|
-
```
|
|
2546
|
-
|
|
2547
|
-
**Rule:** `"strict": true` is mandatory. If a project has `"strict": false`, fix it before writing any new code.
|
|
2548
|
-
|
|
2549
|
-
---
|
|
2550
|
-
|
|
2551
|
-
## The `any` Ban (Zero Tolerance)
|
|
2552
|
-
|
|
2553
|
-
### Rule: `any` is BANNED. No exceptions.
|
|
2554
|
-
|
|
2555
|
-
```typescript
|
|
2556
|
-
// ❌ INSTANT REJECTION
|
|
2557
|
-
function processData(data: any) { ... }
|
|
2558
|
-
const result = response.json() as any;
|
|
2559
|
-
// @ts-ignore
|
|
2560
|
-
// @ts-expect-error — only allowed with a comment explaining WHY and a linked issue
|
|
2561
|
-
|
|
2562
|
-
// ✅ REQUIRED: Use `unknown` with type narrowing
|
|
2563
|
-
function processData(data: unknown) {
|
|
2564
|
-
const parsed = DataSchema.parse(data); // Zod validates and narrows
|
|
2565
|
-
// `parsed` is now fully typed
|
|
2566
|
-
}
|
|
2567
|
-
|
|
2568
|
-
// ✅ REQUIRED: Use generics when the type varies
|
|
2569
|
-
function getFirstItem<T>(items: T[]): T | undefined {
|
|
2570
|
-
return items[0];
|
|
2571
|
-
}
|
|
2572
|
-
```
|
|
2573
|
-
|
|
2574
|
-
### What to Use Instead of `any`
|
|
2575
|
-
| Situation | Instead of `any`, Use |
|
|
2576
|
-
|-----------|----------------------|
|
|
2577
|
-
| Unknown external data | `unknown` + Zod parsing |
|
|
2578
|
-
| Generic container | `T` (generic type parameter) |
|
|
2579
|
-
| Object with unknown keys | `Record<string, unknown>` |
|
|
2580
|
-
| Function argument you'll refine | `unknown` + type guard |
|
|
2581
|
-
| Library with bad types | Write a `.d.ts` override or `unknown` wrapper |
|
|
2582
|
-
| Event handlers | The specific event type (`MouseEvent`, `ChangeEvent<HTMLInputElement>`) |
|
|
2583
|
-
|
|
2584
|
-
---
|
|
2585
|
-
|
|
2586
|
-
## Zod at Boundaries (Mandatory)
|
|
2587
|
-
|
|
2588
|
-
### Rule: ALL External Data MUST Pass Through Zod
|
|
2589
|
-
|
|
2590
|
-
```typescript
|
|
2591
|
-
// ❌ BANNED: Trusting external data
|
|
2592
|
-
app.post('/users', async (req, res) => {
|
|
2593
|
-
const { name, email, age } = req.body; // Could be anything!
|
|
2594
|
-
await userService.create({ name, email, age });
|
|
2595
|
-
});
|
|
2596
|
-
|
|
2597
|
-
// ✅ REQUIRED: Validate with Zod at the boundary
|
|
2598
|
-
import { z } from 'zod';
|
|
2599
|
-
|
|
2600
|
-
const CreateUserSchema = z.object({
|
|
2601
|
-
name: z.string().min(1).max(100).trim(),
|
|
2602
|
-
email: z.string().email().toLowerCase(),
|
|
2603
|
-
age: z.number().int().min(13).max(150),
|
|
2604
|
-
});
|
|
2605
|
-
|
|
2606
|
-
type CreateUserDto = z.infer<typeof CreateUserSchema>;
|
|
2607
|
-
|
|
2608
|
-
app.post('/users', async (req, res) => {
|
|
2609
|
-
const parsed = CreateUserSchema.safeParse(req.body);
|
|
2610
|
-
if (!parsed.success) {
|
|
2611
|
-
return res.status(400).json({ errors: parsed.error.flatten() });
|
|
2612
|
-
}
|
|
2613
|
-
// `parsed.data` is fully typed and validated
|
|
2614
|
-
await userService.create(parsed.data);
|
|
2615
|
-
});
|
|
2616
|
-
```
|
|
2617
|
-
|
|
2618
|
-
### Where Zod is MANDATORY
|
|
2619
|
-
- API request bodies (POST, PUT, PATCH)
|
|
2620
|
-
- Query parameters and path parameters
|
|
2621
|
-
- WebSocket incoming messages
|
|
2622
|
-
- Queue/event payloads from external systems
|
|
2623
|
-
- Environment variables at startup
|
|
2624
|
-
- Third-party API responses (trust but verify)
|
|
2625
|
-
- File upload metadata
|
|
2626
|
-
|
|
2627
|
-
### Zod Best Practices
|
|
2628
|
-
```typescript
|
|
2629
|
-
// ✅ Reuse schemas — define once, use everywhere
|
|
2630
|
-
// src/modules/user/user.schema.ts
|
|
2631
|
-
export const UserSchema = z.object({
|
|
2632
|
-
id: z.string().uuid(),
|
|
2633
|
-
email: z.string().email(),
|
|
2634
|
-
name: z.string().min(1).max(100),
|
|
2635
|
-
role: z.enum(['user', 'admin', 'moderator']),
|
|
2636
|
-
createdAt: z.coerce.date(),
|
|
2637
|
-
});
|
|
2638
|
-
|
|
2639
|
-
export const CreateUserSchema = UserSchema.omit({ id: true, createdAt: true });
|
|
2640
|
-
export const UpdateUserSchema = CreateUserSchema.partial();
|
|
2641
|
-
|
|
2642
|
-
// Types derived from schemas — single source of truth
|
|
2643
|
-
export type User = z.infer<typeof UserSchema>;
|
|
2644
|
-
export type CreateUserDto = z.infer<typeof CreateUserSchema>;
|
|
2645
|
-
export type UpdateUserDto = z.infer<typeof UpdateUserSchema>;
|
|
2646
|
-
```
|
|
2647
|
-
|
|
2648
|
-
---
|
|
2649
|
-
|
|
2650
|
-
## API Documentation (Mandatory)
|
|
2651
|
-
|
|
2652
|
-
### Rule: No API Endpoint Exists Without Documentation
|
|
2653
|
-
|
|
2654
|
-
Every API endpoint MUST have corresponding documentation. When creating or modifying an endpoint, you MUST simultaneously update the API documentation.
|
|
2655
|
-
|
|
2656
|
-
### Preferred Approach: OpenAPI (Swagger)
|
|
2657
|
-
|
|
2658
|
-
```typescript
|
|
2659
|
-
// Option 1: Code-first with decorators (NestJS)
|
|
2660
|
-
@ApiOperation({ summary: 'Create a new user' })
|
|
2661
|
-
@ApiBody({ type: CreateUserDto })
|
|
2662
|
-
@ApiResponse({ status: 201, description: 'User created', type: UserResponseDto })
|
|
2663
|
-
@ApiResponse({ status: 400, description: 'Validation error' })
|
|
2664
|
-
@ApiResponse({ status: 409, description: 'Email already exists' })
|
|
2665
|
-
@Post()
|
|
2666
|
-
async createUser(@Body() dto: CreateUserDto): Promise<UserResponseDto> { ... }
|
|
2667
|
-
|
|
2668
|
-
// Option 2: Schema-first with Zod + zod-to-openapi
|
|
2669
|
-
import { extendZodWithOpenApi } from '@asteasolutions/zod-to-openapi';
|
|
2670
|
-
extendZodWithOpenApi(z);
|
|
2671
|
-
|
|
2672
|
-
const CreateUserSchema = z.object({
|
|
2673
|
-
name: z.string().min(1).openapi({ example: 'John Doe' }),
|
|
2674
|
-
email: z.string().email().openapi({ example: 'john@example.com' }),
|
|
2675
|
-
}).openapi('CreateUserRequest');
|
|
2676
|
-
```
|
|
2677
|
-
|
|
2678
|
-
### Documentation Checklist (Per Endpoint)
|
|
2679
|
-
- [ ] HTTP method and path
|
|
2680
|
-
- [ ] Request body schema (with examples)
|
|
2681
|
-
- [ ] Query/path parameter descriptions
|
|
2682
|
-
- [ ] All possible response codes with schemas
|
|
2683
|
-
- [ ] Authentication requirements
|
|
2684
|
-
- [ ] Rate limiting information (if applicable)
|
|
2685
|
-
|
|
2686
|
-
### Documentation Must Stay in Sync
|
|
2687
|
-
```
|
|
2688
|
-
❌ BANNED:
|
|
2689
|
-
// Endpoint accepts `role` field but docs don't mention it
|
|
2690
|
-
// Endpoint returns 422 but docs only show 400
|
|
2691
|
-
|
|
2692
|
-
✅ REQUIRED:
|
|
2693
|
-
// When you change an endpoint → update the schema/docs in the SAME commit
|
|
2694
|
-
// Use code-first tools so docs are generated from the actual types
|
|
2695
|
-
```
|
|
2696
|
-
|
|
2697
|
-
---
|
|
2698
|
-
|
|
2699
|
-
## Import Style
|
|
2700
|
-
|
|
2701
|
-
### Path Aliases (Required)
|
|
2702
|
-
```typescript
|
|
2703
|
-
// ❌ BANNED: Deep relative imports
|
|
2704
|
-
import { UserService } from '../../../modules/user/user.service';
|
|
2705
|
-
import { AppError } from '../../../../shared/errors/app-error';
|
|
2706
|
-
|
|
2707
|
-
// ✅ REQUIRED: Path aliases
|
|
2708
|
-
import { UserService } from '@/modules/user/user.service';
|
|
2709
|
-
import { AppError } from '@/shared/errors/app-error';
|
|
2710
|
-
```
|
|
2711
|
-
|
|
2712
|
-
### Import Order (Enforced by ESLint)
|
|
2713
|
-
```typescript
|
|
2714
|
-
// 1. Node built-ins
|
|
2715
|
-
import { readFile } from 'node:fs/promises';
|
|
2716
|
-
import { join } from 'node:path';
|
|
2717
|
-
|
|
2718
|
-
// 2. External packages
|
|
2719
|
-
import { z } from 'zod';
|
|
2720
|
-
import { PrismaClient } from '@prisma/client';
|
|
2721
|
-
|
|
2722
|
-
// 3. Internal modules (path aliases)
|
|
2723
|
-
import { UserService } from '@/modules/user/user.service';
|
|
2724
|
-
import { AppError } from '@/shared/errors/app-error';
|
|
2725
|
-
|
|
2726
|
-
// 4. Relative imports (same module only)
|
|
2727
|
-
import { CreateUserDto } from './user.dto';
|
|
2728
|
-
import { UserRepository } from './user.repository';
|
|
2729
|
-
```
|
|
2730
|
-
|
|
2731
|
-
---
|
|
2732
|
-
|
|
2733
|
-
## Async Patterns
|
|
2734
|
-
|
|
2735
|
-
### Rule: Prefer async/await Over Raw Promises
|
|
2736
|
-
```typescript
|
|
2737
|
-
// ❌ BANNED: Promise chain hell
|
|
2738
|
-
function getUser(id: string) {
|
|
2739
|
-
return userRepo.findById(id)
|
|
2740
|
-
.then(user => {
|
|
2741
|
-
if (!user) throw new NotFoundError('User', id);
|
|
2742
|
-
return orderRepo.findByUserId(user.id);
|
|
2743
|
-
})
|
|
2744
|
-
.then(orders => ({ ...user, orders }))
|
|
2745
|
-
.catch(err => { throw err; });
|
|
2746
|
-
}
|
|
2747
|
-
|
|
2748
|
-
// ✅ REQUIRED: Clean async/await
|
|
2749
|
-
async function getUser(id: string): Promise<UserWithOrders> {
|
|
2750
|
-
const user = await userRepo.findById(id);
|
|
2751
|
-
if (!user) throw new NotFoundError('User', id);
|
|
2752
|
-
|
|
2753
|
-
const orders = await orderRepo.findByUserId(user.id);
|
|
2754
|
-
return { ...user, orders };
|
|
2755
|
-
}
|
|
2756
|
-
```
|
|
2757
|
-
|
|
2758
|
-
### Parallel Execution
|
|
2759
|
-
```typescript
|
|
2760
|
-
// ❌ SLOW: Sequential when operations are independent
|
|
2761
|
-
const user = await getUser(id);
|
|
2762
|
-
const orders = await getOrders(id);
|
|
2763
|
-
const preferences = await getPreferences(id);
|
|
2764
|
-
|
|
2765
|
-
// ✅ FAST: Parallel independent operations
|
|
2766
|
-
const [user, orders, preferences] = await Promise.all([
|
|
2767
|
-
getUser(id),
|
|
2768
|
-
getOrders(id),
|
|
2769
|
-
getPreferences(id),
|
|
2770
|
-
]);
|
|
2771
|
-
```
|
|
2772
|
-
|
|
2773
|
-
---
|
|
2774
|
-
|
|
2775
|
-
## Enum and Union Types
|
|
2776
|
-
|
|
2777
|
-
### Prefer `as const` Unions Over Enums
|
|
2778
|
-
```typescript
|
|
2779
|
-
// ⚠️ ACCEPTABLE but verbose:
|
|
2780
|
-
enum OrderStatus {
|
|
2781
|
-
PENDING = 'pending',
|
|
2782
|
-
CONFIRMED = 'confirmed',
|
|
2783
|
-
SHIPPED = 'shipped',
|
|
2784
|
-
DELIVERED = 'delivered',
|
|
2785
|
-
}
|
|
2786
|
-
|
|
2787
|
-
// ✅ PREFERRED: const assertion + union type
|
|
2788
|
-
const ORDER_STATUSES = ['pending', 'confirmed', 'shipped', 'delivered'] as const;
|
|
2789
|
-
type OrderStatus = (typeof ORDER_STATUSES)[number];
|
|
2790
|
-
// OrderStatus = 'pending' | 'confirmed' | 'shipped' | 'delivered'
|
|
2791
|
-
|
|
2792
|
-
// Works perfectly with Zod:
|
|
2793
|
-
const OrderStatusSchema = z.enum(ORDER_STATUSES);
|
|
2794
|
-
```
|
|
2795
|
-
|
|
2796
|
-
---
|
|
2797
|
-
|
|
2798
|
-
## Preferred Libraries (V1.0 — 2025)
|
|
2799
|
-
|
|
2800
|
-
| Need | Library | Why |
|
|
2801
|
-
|------|---------|-----|
|
|
2802
|
-
| Runtime | Bun / Node 20+ | ESM native, fast, batteries-included |
|
|
2803
|
-
| Validation | `zod` | 0 deps, type inference, composable |
|
|
2804
|
-
| ORM | `prisma` or `drizzle-orm` | Type-safe queries, migration support |
|
|
2805
|
-
| HTTP framework | `hono` / `fastify` / Next.js | Lightweight, modern, tree-shakeable |
|
|
2806
|
-
| Testing | `vitest` | Vite-native, Jest-compatible API, fast |
|
|
2807
|
-
| Linting | `eslint` + `@typescript-eslint` | Community standard |
|
|
2808
|
-
| Formatting | `prettier` | Community standard |
|
|
2809
|
-
| Date | `date-fns` or `Temporal` (when stable) | Tree-shakeable, immutable |
|
|
2810
|
-
| HTTP client | `ky` / `ofetch` / built-in `fetch` | Lightweight, modern |
|
|
2811
|
-
| Password | `bcrypt` / `argon2` | Proven, secure |
|
|
2812
|
-
| Logger | `pino` | Fastest JSON logger for Node.js |
|
|
2813
|
-
| Env | `@t3-oss/env-core` + Zod | Type-safe env validation |
|
|
2814
|
-
|
|
2815
|
-
---
|
|
2816
|
-
|
|
2817
|
-
## Banned Patterns
|
|
2818
|
-
|
|
2819
|
-
| Pattern | Why | Alternative |
|
|
2820
|
-
|---------|-----|-------------|
|
|
2821
|
-
| `any` type | Defeats TypeScript's purpose | `unknown` + narrowing |
|
|
2822
|
-
| `// @ts-ignore` | Hides real type errors | Fix the type or `@ts-expect-error` with comment |
|
|
2823
|
-
| `var` keyword | Function scoping bugs | `const` (default) or `let` |
|
|
2824
|
-
| `==` loose equality | Type coercion surprises | `===` always |
|
|
2825
|
-
| `console.log` in production | Not structured, not configurable | Use `pino` or structured logger |
|
|
2826
|
-
| `new Date()` without timezone | Timezone bugs | Explicit UTC or use date-fns |
|
|
2827
|
-
| Default exports | Naming inconsistency across imports | Named exports only |
|
|
2828
|
-
| Barrel re-exports (`index.ts`) | Circular dependency magnets | Direct imports or module public API only |
|
|
2829
|
-
| `moment.js` | Deprecated, massive bundle | `date-fns` or `dayjs` |
|
|
2830
|
-
| `class` with inheritance (deep chains) | Fragile hierarchies | Composition + interfaces |
|
|
2831
|
-
## BLUEPRINT PROFILE: api-nextjs.md
|
|
53
|
+
Summary: TypeScript Stack Profile — The "Galak" Standard
|
|
54
|
+
Load this stack profile to enforce language-specific conventions.
|
|
55
|
+
## LAYER 3: BLUEPRINT PROFILE (api-nextjs.md)
|
|
2832
56
|
Source: .agent-context/blueprints/api-nextjs.md
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
## Tech Stack
|
|
2840
|
-
- **Runtime:** Node.js 20+ / Bun
|
|
2841
|
-
- **Framework:** Next.js 14+ (App Router)
|
|
2842
|
-
- **Validation:** Zod
|
|
2843
|
-
- **ORM:** Prisma (or Drizzle)
|
|
2844
|
-
- **Auth:** NextAuth.js v5 / Lucia Auth
|
|
2845
|
-
- **Testing:** Vitest
|
|
2846
|
-
|
|
2847
|
-
## Project Structure
|
|
2848
|
-
|
|
2849
|
-
```
|
|
2850
|
-
project-name/
|
|
2851
|
-
├── src/
|
|
2852
|
-
│ ├── app/ # Next.js App Router
|
|
2853
|
-
│ │ ├── api/ # API routes
|
|
2854
|
-
│ │ │ ├── auth/
|
|
2855
|
-
│ │ │ │ └── [...nextauth]/
|
|
2856
|
-
│ │ │ │ └── route.ts # Auth handler
|
|
2857
|
-
│ │ │ ├── users/
|
|
2858
|
-
│ │ │ │ ├── route.ts # GET /api/users, POST /api/users
|
|
2859
|
-
│ │ │ │ └── [id]/
|
|
2860
|
-
│ │ │ │ └── route.ts # GET/PUT/DELETE /api/users/:id
|
|
2861
|
-
│ │ │ └── health/
|
|
2862
|
-
│ │ │ └── route.ts # GET /api/health
|
|
2863
|
-
│ │ ├── layout.tsx # Root layout
|
|
2864
|
-
│ │ └── page.tsx # Root page
|
|
2865
|
-
│ │
|
|
2866
|
-
│ ├── modules/ # Feature modules (domain-driven)
|
|
2867
|
-
│ │ ├── user/
|
|
2868
|
-
│ │ │ ├── user.service.ts # Business logic
|
|
2869
|
-
│ │ │ ├── user.repository.ts # Data access
|
|
2870
|
-
│ │ │ ├── user.schema.ts # Zod schemas + DTOs
|
|
2871
|
-
│ │ │ ├── user.types.ts # Type definitions
|
|
2872
|
-
│ │ │ └── __tests__/
|
|
2873
|
-
│ │ │ └── user.service.test.ts
|
|
2874
|
-
│ │ └── order/
|
|
2875
|
-
│ │ ├── order.service.ts
|
|
2876
|
-
│ │ ├── order.repository.ts
|
|
2877
|
-
│ │ ├── order.schema.ts
|
|
2878
|
-
│ │ └── order.types.ts
|
|
2879
|
-
│ │
|
|
2880
|
-
│ ├── shared/ # Cross-cutting concerns
|
|
2881
|
-
│ │ ├── config/
|
|
2882
|
-
│ │ │ └── env.ts # Zod-validated environment variables
|
|
2883
|
-
│ │ ├── errors/
|
|
2884
|
-
│ │ │ ├── app-error.ts # Base error class
|
|
2885
|
-
│ │ │ └── error-handler.ts # Global error handler for API routes
|
|
2886
|
-
│ │ ├── middleware/
|
|
2887
|
-
│ │ │ ├── auth.ts # Auth middleware
|
|
2888
|
-
│ │ │ └── validate.ts # Request validation middleware
|
|
2889
|
-
│ │ ├── lib/
|
|
2890
|
-
│ │ │ ├── prisma.ts # Prisma client singleton
|
|
2891
|
-
│ │ │ └── logger.ts # Pino logger setup
|
|
2892
|
-
│ │ └── types/
|
|
2893
|
-
│ │ └── api.ts # Shared API types (ApiResponse, etc.)
|
|
2894
|
-
│ │
|
|
2895
|
-
│ └── components/ # UI components (if full-stack)
|
|
2896
|
-
│ ├── ui/ # Primitives
|
|
2897
|
-
│ └── layout/ # Layout components
|
|
2898
|
-
│
|
|
2899
|
-
├── prisma/
|
|
2900
|
-
│ ├── schema.prisma # Database schema
|
|
2901
|
-
│ └── migrations/ # Database migrations
|
|
2902
|
-
│
|
|
2903
|
-
├── public/ # Static assets
|
|
2904
|
-
├── .env.example # Environment template
|
|
2905
|
-
├── .eslintrc.json # ESLint config
|
|
2906
|
-
├── .prettierrc # Prettier config
|
|
2907
|
-
├── next.config.mjs # Next.js config
|
|
2908
|
-
├── tsconfig.json # TypeScript config (strict!)
|
|
2909
|
-
├── vitest.config.ts # Vitest config
|
|
2910
|
-
└── package.json
|
|
2911
|
-
```
|
|
2912
|
-
|
|
2913
|
-
## API Route Handler Pattern
|
|
2914
|
-
|
|
2915
|
-
```typescript
|
|
2916
|
-
// src/app/api/users/route.ts
|
|
2917
|
-
import { NextRequest, NextResponse } from 'next/server';
|
|
2918
|
-
import { CreateUserSchema } from '@/modules/user/user.schema';
|
|
2919
|
-
import { userService } from '@/modules/user/user.service';
|
|
2920
|
-
import { handleApiError } from '@/shared/errors/error-handler';
|
|
2921
|
-
|
|
2922
|
-
export async function GET(request: NextRequest) {
|
|
2923
|
-
try {
|
|
2924
|
-
const searchParams = request.nextUrl.searchParams;
|
|
2925
|
-
const page = Number(searchParams.get('page') ?? '1');
|
|
2926
|
-
const limit = Number(searchParams.get('limit') ?? '20');
|
|
2927
|
-
|
|
2928
|
-
const users = await userService.findAll({ page, limit });
|
|
2929
|
-
return NextResponse.json({ data: users });
|
|
2930
|
-
} catch (error) {
|
|
2931
|
-
return handleApiError(error);
|
|
2932
|
-
}
|
|
2933
|
-
}
|
|
2934
|
-
|
|
2935
|
-
export async function POST(request: NextRequest) {
|
|
2936
|
-
try {
|
|
2937
|
-
const body = await request.json();
|
|
2938
|
-
const parsed = CreateUserSchema.safeParse(body);
|
|
2939
|
-
|
|
2940
|
-
if (!parsed.success) {
|
|
2941
|
-
return NextResponse.json(
|
|
2942
|
-
{ error: { code: 'VALIDATION_ERROR', details: parsed.error.flatten() } },
|
|
2943
|
-
{ status: 400 },
|
|
2944
|
-
);
|
|
2945
|
-
}
|
|
2946
|
-
|
|
2947
|
-
const user = await userService.create(parsed.data);
|
|
2948
|
-
return NextResponse.json({ data: user }, { status: 201 });
|
|
2949
|
-
} catch (error) {
|
|
2950
|
-
return handleApiError(error);
|
|
2951
|
-
}
|
|
2952
|
-
}
|
|
2953
|
-
```
|
|
2954
|
-
|
|
2955
|
-
## Environment Validation Pattern
|
|
2956
|
-
|
|
2957
|
-
```typescript
|
|
2958
|
-
// src/shared/config/env.ts
|
|
2959
|
-
import { z } from 'zod';
|
|
2960
|
-
|
|
2961
|
-
const envSchema = z.object({
|
|
2962
|
-
NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
|
|
2963
|
-
DATABASE_URL: z.string().url(),
|
|
2964
|
-
NEXTAUTH_SECRET: z.string().min(32),
|
|
2965
|
-
NEXTAUTH_URL: z.string().url(),
|
|
2966
|
-
PORT: z.coerce.number().default(3000),
|
|
2967
|
-
});
|
|
2968
|
-
|
|
2969
|
-
export const env = envSchema.parse(process.env);
|
|
2970
|
-
export type Env = z.infer<typeof envSchema>;
|
|
2971
|
-
```
|
|
2972
|
-
|
|
2973
|
-
## Error Handler Pattern
|
|
2974
|
-
|
|
2975
|
-
```typescript
|
|
2976
|
-
// src/shared/errors/error-handler.ts
|
|
2977
|
-
import { NextResponse } from 'next/server';
|
|
2978
|
-
import { AppError } from './app-error';
|
|
2979
|
-
import { logger } from '@/shared/lib/logger';
|
|
2980
|
-
|
|
2981
|
-
export function handleApiError(error: unknown): NextResponse {
|
|
2982
|
-
if (error instanceof AppError) {
|
|
2983
|
-
logger.warn('Application error', {
|
|
2984
|
-
code: error.code,
|
|
2985
|
-
message: error.message,
|
|
2986
|
-
context: error.context,
|
|
2987
|
-
});
|
|
2988
|
-
|
|
2989
|
-
return NextResponse.json(
|
|
2990
|
-
{ error: { code: error.code, message: error.message } },
|
|
2991
|
-
{ status: error.statusCode },
|
|
2992
|
-
);
|
|
2993
|
-
}
|
|
2994
|
-
|
|
2995
|
-
logger.error('Unhandled error', {
|
|
2996
|
-
error: error instanceof Error ? error.message : String(error),
|
|
2997
|
-
stack: error instanceof Error ? error.stack : undefined,
|
|
2998
|
-
});
|
|
2999
|
-
|
|
3000
|
-
return NextResponse.json(
|
|
3001
|
-
{ error: { code: 'INTERNAL_ERROR', message: 'An unexpected error occurred' } },
|
|
3002
|
-
{ status: 500 },
|
|
3003
|
-
);
|
|
3004
|
-
}
|
|
3005
|
-
```
|
|
3006
|
-
|
|
3007
|
-
## Scaffolding Checklist
|
|
3008
|
-
|
|
3009
|
-
When using this blueprint, verify:
|
|
3010
|
-
- [ ] `tsconfig.json` has `"strict": true` and all recommended flags
|
|
3011
|
-
- [ ] `.env.example` exists with ALL required variables (placeholder values only)
|
|
3012
|
-
- [ ] Prisma client is a singleton (not re-created per request)
|
|
3013
|
-
- [ ] All API routes use Zod validation at the boundary
|
|
3014
|
-
- [ ] Error handler is used in every route (no unhandled errors)
|
|
3015
|
-
- [ ] Logger is configured (not `console.log`)
|
|
3016
|
-
- [ ] Path aliases (`@/`) are configured in tsconfig AND next.config
|
|
3017
|
-
- [ ] API documentation is generated or maintained alongside routes
|
|
3018
|
-
## CI/CD GUARDRAILS: ci-github-actions.md
|
|
3019
|
-
Source: .agent-context/blueprints/ci-github-actions.md
|
|
3020
|
-
|
|
3021
|
-
# Blueprint: GitHub Actions CI/CD Pipeline
|
|
3022
|
-
|
|
3023
|
-
> Automate everything. Trust nothing. Ship with confidence.
|
|
3024
|
-
|
|
3025
|
-
## Tech Stack
|
|
3026
|
-
|
|
3027
|
-
| Layer | Tool |
|
|
3028
|
-
|-------|------|
|
|
3029
|
-
| CI/CD | GitHub Actions |
|
|
3030
|
-
| Runners | GitHub-hosted (ubuntu-latest) |
|
|
3031
|
-
| Caching | actions/cache, setup-node cache |
|
|
3032
|
-
| Security | OIDC for cloud auth, pinned action SHAs |
|
|
3033
|
-
| Artifacts | actions/upload-artifact |
|
|
3034
|
-
|
|
3035
|
-
## Pipeline Architecture
|
|
3036
|
-
|
|
3037
|
-
```
|
|
3038
|
-
┌─────────────────────────────────────────────────────────┐
|
|
3039
|
-
│ PR / Push Trigger │
|
|
3040
|
-
├──────────┬──────────┬──────────┬──────────┬─────────────┬─────────────┤
|
|
3041
|
-
│ Lint │ Build │ Test │ Security │ Docs │ LLM Judge │
|
|
3042
|
-
│ (ESLint/ │ (tsc / │ (Vitest/ │ (Audit / │ (OpenAPI │ (Checklist │
|
|
3043
|
-
│ ruff) │ build) │ pytest) │ Trivy) │ validate) │ enforcement)│
|
|
3044
|
-
├──────────┴──────────┴──────────┴──────────┴─────────────┴─────────────┤
|
|
3045
|
-
│ Gate: All jobs must pass │
|
|
3046
|
-
├─────────────────────────────────────────────────────────┤
|
|
3047
|
-
│ Deploy (on main only) │
|
|
3048
|
-
│ Staging → Smoke Tests → Production (manual approval) │
|
|
3049
|
-
└─────────────────────────────────────────────────────────┘
|
|
3050
|
-
```
|
|
3051
|
-
|
|
3052
|
-
## Required Workflows
|
|
3053
|
-
|
|
3054
|
-
### 1. CI Workflow (`ci.yml`)
|
|
3055
|
-
|
|
3056
|
-
Runs on every PR and push to main.
|
|
3057
|
-
|
|
3058
|
-
```yaml
|
|
3059
|
-
name: CI
|
|
3060
|
-
|
|
3061
|
-
on:
|
|
3062
|
-
pull_request:
|
|
3063
|
-
branches: [main]
|
|
3064
|
-
push:
|
|
3065
|
-
branches: [main]
|
|
3066
|
-
|
|
3067
|
-
permissions:
|
|
3068
|
-
contents: read
|
|
3069
|
-
|
|
3070
|
-
concurrency:
|
|
3071
|
-
group: ci-${{ github.ref }}
|
|
3072
|
-
cancel-in-progress: true
|
|
3073
|
-
|
|
3074
|
-
jobs:
|
|
3075
|
-
lint:
|
|
3076
|
-
runs-on: ubuntu-latest
|
|
3077
|
-
timeout-minutes: 10
|
|
3078
|
-
steps:
|
|
3079
|
-
- uses: actions/checkout@<pin-sha>
|
|
3080
|
-
- uses: actions/setup-node@<pin-sha>
|
|
3081
|
-
with:
|
|
3082
|
-
node-version-file: '.nvmrc'
|
|
3083
|
-
cache: 'npm'
|
|
3084
|
-
- run: npm ci
|
|
3085
|
-
- run: npm run lint
|
|
3086
|
-
- run: npm run type-check
|
|
3087
|
-
|
|
3088
|
-
test:
|
|
3089
|
-
runs-on: ubuntu-latest
|
|
3090
|
-
timeout-minutes: 15
|
|
3091
|
-
steps:
|
|
3092
|
-
- uses: actions/checkout@<pin-sha>
|
|
3093
|
-
- uses: actions/setup-node@<pin-sha>
|
|
3094
|
-
with:
|
|
3095
|
-
node-version-file: '.nvmrc'
|
|
3096
|
-
cache: 'npm'
|
|
3097
|
-
- run: npm ci
|
|
3098
|
-
- run: npm run test -- --coverage
|
|
3099
|
-
- uses: actions/upload-artifact@<pin-sha>
|
|
3100
|
-
with:
|
|
3101
|
-
name: coverage
|
|
3102
|
-
path: coverage/
|
|
3103
|
-
|
|
3104
|
-
security:
|
|
3105
|
-
runs-on: ubuntu-latest
|
|
3106
|
-
timeout-minutes: 10
|
|
3107
|
-
steps:
|
|
3108
|
-
- uses: actions/checkout@<pin-sha>
|
|
3109
|
-
- run: npm audit --audit-level=high
|
|
3110
|
-
# Add Trivy, CodeQL, or Snyk as needed
|
|
3111
|
-
|
|
3112
|
-
build:
|
|
3113
|
-
needs: [lint, test, security]
|
|
3114
|
-
runs-on: ubuntu-latest
|
|
3115
|
-
timeout-minutes: 15
|
|
3116
|
-
steps:
|
|
3117
|
-
- uses: actions/checkout@<pin-sha>
|
|
3118
|
-
- uses: actions/setup-node@<pin-sha>
|
|
3119
|
-
with:
|
|
3120
|
-
node-version-file: '.nvmrc'
|
|
3121
|
-
cache: 'npm'
|
|
3122
|
-
- run: npm ci
|
|
3123
|
-
- run: npm run build
|
|
3124
|
-
- uses: actions/upload-artifact@<pin-sha>
|
|
3125
|
-
with:
|
|
3126
|
-
name: build
|
|
3127
|
-
path: dist/
|
|
3128
|
-
|
|
3129
|
-
llm-judge:
|
|
3130
|
-
needs: [lint, test, security, build]
|
|
3131
|
-
runs-on: ubuntu-latest
|
|
3132
|
-
timeout-minutes: 12
|
|
3133
|
-
env:
|
|
3134
|
-
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
|
3135
|
-
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
3136
|
-
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
|
|
3137
|
-
GITHUB_BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
|
3138
|
-
GITHUB_HEAD_SHA: ${{ github.sha }}
|
|
3139
|
-
steps:
|
|
3140
|
-
- uses: actions/checkout@<pin-sha>
|
|
3141
|
-
with:
|
|
3142
|
-
fetch-depth: 0 # Full history required for accurate git diff
|
|
3143
|
-
- uses: actions/setup-node@<pin-sha>
|
|
3144
|
-
with:
|
|
3145
|
-
node-version: '22'
|
|
3146
|
-
- name: Run LLM Judge
|
|
3147
|
-
run: node scripts/llm-judge.mjs
|
|
3148
|
-
- name: Upload LLM Judge machine report
|
|
3149
|
-
if: always()
|
|
3150
|
-
uses: actions/upload-artifact@<pin-sha>
|
|
3151
|
-
with:
|
|
3152
|
-
name: llm-judge-report
|
|
3153
|
-
path: .agent-context/state/llm-judge-report.json
|
|
3154
|
-
```
|
|
3155
|
-
|
|
3156
|
-
### 2. Deploy Workflow (`deploy.yml`)
|
|
3157
|
-
|
|
3158
|
-
Runs on push to main (after CI passes).
|
|
3159
|
-
|
|
3160
|
-
```yaml
|
|
3161
|
-
name: Deploy
|
|
3162
|
-
|
|
3163
|
-
on:
|
|
3164
|
-
workflow_run:
|
|
3165
|
-
workflows: [CI]
|
|
3166
|
-
types: [completed]
|
|
3167
|
-
branches: [main]
|
|
3168
|
-
|
|
3169
|
-
permissions:
|
|
3170
|
-
id-token: write # OIDC
|
|
3171
|
-
contents: read
|
|
3172
|
-
|
|
3173
|
-
jobs:
|
|
3174
|
-
deploy-staging:
|
|
3175
|
-
if: ${{ github.event.workflow_run.conclusion == 'success' }}
|
|
3176
|
-
runs-on: ubuntu-latest
|
|
3177
|
-
environment: staging
|
|
3178
|
-
steps:
|
|
3179
|
-
# Authenticate via OIDC — no static secrets
|
|
3180
|
-
# Deploy to staging
|
|
3181
|
-
# Run smoke tests against staging
|
|
3182
|
-
|
|
3183
|
-
deploy-production:
|
|
3184
|
-
needs: deploy-staging
|
|
3185
|
-
runs-on: ubuntu-latest
|
|
3186
|
-
environment:
|
|
3187
|
-
name: production
|
|
3188
|
-
url: https://your-app.com
|
|
3189
|
-
steps:
|
|
3190
|
-
# Deploy to production
|
|
3191
|
-
# Run health checks
|
|
3192
|
-
# Notify team (Slack, Discord)
|
|
3193
|
-
```
|
|
3194
|
-
|
|
3195
|
-
## Security Rules
|
|
3196
|
-
|
|
3197
|
-
1. **Pin actions to commit SHA** — not `@v3`, not `@latest`
|
|
3198
|
-
2. **Use OIDC** for cloud provider auth — delete static credentials
|
|
3199
|
-
3. **Minimal permissions** — set `permissions:` at workflow level, least privilege
|
|
3200
|
-
4. **Never print secrets** — GitHub masks them in logs, but don't `echo` them
|
|
3201
|
-
5. **Restrict self-hosted runners** — ephemeral containers only, never persistent VMs
|
|
3202
|
-
6. **Require PR approval** for workflows from forks
|
|
3203
|
-
7. **Minimize prompt scope** — send diff + checklist only, never full secret-bearing config
|
|
3204
|
-
|
|
3205
|
-
## LLM Judge Annotation Contract
|
|
3206
|
-
|
|
3207
|
-
The judge emits a machine-friendly payload line and artifact that can be consumed by annotation scripts:
|
|
3208
|
-
|
|
3209
|
-
- Log line: `JSON_REPORT: { ... }`
|
|
3210
|
-
- Artifact file: `.agent-context/state/llm-judge-report.json`
|
|
3211
|
-
- Normalized severity values: `critical`, `high`, `medium`, `low`
|
|
3212
|
-
- Override artifact path (optional): `LLM_JUDGE_OUTPUT_PATH`
|
|
3213
|
-
|
|
3214
|
-
## Efficiency Rules
|
|
3215
|
-
|
|
3216
|
-
1. **Cache dependencies** — `actions/cache` or setup-action built-in cache
|
|
3217
|
-
2. **Use concurrency** — cancel previous runs on the same branch
|
|
3218
|
-
3. **Set timeouts** — prevent stuck jobs from burning minutes
|
|
3219
|
-
4. **Matrix builds** for multi-platform/version testing
|
|
3220
|
-
5. **Reusable workflows** — centralize common pipelines in a `.github` repo
|
|
3221
|
-
6. **Skip unnecessary jobs** — use path filters for targeted CI
|
|
3222
|
-
|
|
3223
|
-
```yaml
|
|
3224
|
-
# Example: Only run frontend tests when frontend code changes
|
|
3225
|
-
on:
|
|
3226
|
-
push:
|
|
3227
|
-
paths:
|
|
3228
|
-
- 'src/frontend/**'
|
|
3229
|
-
- 'package.json'
|
|
3230
|
-
```
|
|
3231
|
-
|
|
3232
|
-
## Scaffolding Checklist
|
|
3233
|
-
|
|
3234
|
-
When setting up GitHub Actions for a new project:
|
|
3235
|
-
|
|
3236
|
-
- [ ] Create `.github/workflows/ci.yml` with lint, test, build, security
|
|
3237
|
-
- [ ] Create `.github/workflows/deploy.yml` with staging + production
|
|
3238
|
-
- [ ] Pin ALL third-party actions to commit SHA
|
|
3239
|
-
- [ ] Set `permissions: contents: read` at workflow level
|
|
3240
|
-
- [ ] Configure `concurrency` to cancel in-progress runs
|
|
3241
|
-
- [ ] Add `timeout-minutes` to every job
|
|
3242
|
-
- [ ] Set up caching for package manager
|
|
3243
|
-
- [ ] Configure environment protection rules for production
|
|
3244
|
-
- [ ] Create `.nvmrc` or `.tool-versions` for runtime version
|
|
3245
|
-
- [ ] Configure branch protection: require CI pass before merge
|
|
3246
|
-
- [ ] Add `llm-judge` job that evaluates PR against `pr-checklist.md`
|
|
3247
|
-
## CI/CD GUARDRAILS: ci-gitlab.md
|
|
3248
|
-
Source: .agent-context/blueprints/ci-gitlab.md
|
|
3249
|
-
|
|
3250
|
-
# Blueprint: GitLab CI/CD Pipeline
|
|
3251
|
-
|
|
3252
|
-
> Same principles as GitHub Actions, adapted for GitLab's pipeline model.
|
|
3253
|
-
|
|
3254
|
-
## Tech Stack
|
|
3255
|
-
|
|
3256
|
-
| Layer | Tool |
|
|
3257
|
-
|-------|------|
|
|
3258
|
-
| CI/CD | GitLab CI/CD |
|
|
3259
|
-
| Config | `.gitlab-ci.yml` |
|
|
3260
|
-
| Runners | Shared runners or dedicated (Docker executor) |
|
|
3261
|
-
| Registry | GitLab Container Registry |
|
|
3262
|
-
| Caching | GitLab CI cache (per-branch, per-job) |
|
|
3263
|
-
|
|
3264
|
-
## Pipeline Architecture
|
|
3265
|
-
|
|
3266
|
-
```yaml
|
|
3267
|
-
stages:
|
|
3268
|
-
- validate # Lint + type check
|
|
3269
|
-
- test # Unit + integration tests
|
|
3270
|
-
- security # Dependency audit + SAST
|
|
3271
|
-
- build # Compile, bundle, containerize
|
|
3272
|
-
- judge # LLM-as-a-Judge checklist gate
|
|
3273
|
-
- deploy # Staging → Production
|
|
3274
|
-
```
|
|
3275
|
-
|
|
3276
|
-
## Pipeline Template (`.gitlab-ci.yml`)
|
|
3277
|
-
|
|
3278
|
-
```yaml
|
|
3279
|
-
default:
|
|
3280
|
-
image: node:22-alpine
|
|
3281
|
-
cache:
|
|
3282
|
-
key:
|
|
3283
|
-
files: [package-lock.json]
|
|
3284
|
-
paths: [node_modules/]
|
|
3285
|
-
policy: pull-push
|
|
3286
|
-
|
|
3287
|
-
stages:
|
|
3288
|
-
- validate
|
|
3289
|
-
- test
|
|
3290
|
-
- security
|
|
3291
|
-
- build
|
|
3292
|
-
- judge
|
|
3293
|
-
- deploy
|
|
3294
|
-
|
|
3295
|
-
# ─── VALIDATE ───────────────────────────────────────────
|
|
3296
|
-
lint:
|
|
3297
|
-
stage: validate
|
|
3298
|
-
script:
|
|
3299
|
-
- npm ci --prefer-offline
|
|
3300
|
-
- npm run lint
|
|
3301
|
-
- npm run type-check
|
|
3302
|
-
rules:
|
|
3303
|
-
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
|
3304
|
-
- if: '$CI_COMMIT_BRANCH == "main"'
|
|
3305
|
-
|
|
3306
|
-
# ─── TEST ───────────────────────────────────────────────
|
|
3307
|
-
test:unit:
|
|
3308
|
-
stage: test
|
|
3309
|
-
script:
|
|
3310
|
-
- npm ci --prefer-offline
|
|
3311
|
-
- npm run test -- --coverage
|
|
3312
|
-
coverage: '/All files\s*\|\s*(\d+\.?\d*)\s*\|/'
|
|
3313
|
-
artifacts:
|
|
3314
|
-
reports:
|
|
3315
|
-
coverage_report:
|
|
3316
|
-
coverage_format: cobertura
|
|
3317
|
-
path: coverage/cobertura-coverage.xml
|
|
3318
|
-
paths: [coverage/]
|
|
3319
|
-
expire_in: 7 days
|
|
3320
|
-
|
|
3321
|
-
test:integration:
|
|
3322
|
-
stage: test
|
|
3323
|
-
services:
|
|
3324
|
-
- postgres:16-alpine
|
|
3325
|
-
variables:
|
|
3326
|
-
POSTGRES_DB: test_db
|
|
3327
|
-
POSTGRES_USER: test_user
|
|
3328
|
-
POSTGRES_PASSWORD: test_pass
|
|
3329
|
-
DATABASE_URL: "postgresql://test_user:test_pass@postgres:5432/test_db"
|
|
3330
|
-
script:
|
|
3331
|
-
- npm ci --prefer-offline
|
|
3332
|
-
- npm run test:integration
|
|
3333
|
-
|
|
3334
|
-
# ─── SECURITY ───────────────────────────────────────────
|
|
3335
|
-
audit:
|
|
3336
|
-
stage: security
|
|
3337
|
-
script:
|
|
3338
|
-
- npm audit --audit-level=high
|
|
3339
|
-
allow_failure: false
|
|
3340
|
-
|
|
3341
|
-
sast:
|
|
3342
|
-
stage: security
|
|
3343
|
-
# GitLab's built-in SAST template
|
|
3344
|
-
include:
|
|
3345
|
-
- template: Security/SAST.gitlab-ci.yml
|
|
3346
|
-
|
|
3347
|
-
# ─── BUILD ──────────────────────────────────────────────
|
|
3348
|
-
build:
|
|
3349
|
-
stage: build
|
|
3350
|
-
script:
|
|
3351
|
-
- npm ci --prefer-offline
|
|
3352
|
-
- npm run build
|
|
3353
|
-
artifacts:
|
|
3354
|
-
paths: [dist/]
|
|
3355
|
-
expire_in: 1 day
|
|
3356
|
-
rules:
|
|
3357
|
-
- if: '$CI_COMMIT_BRANCH == "main"'
|
|
3358
|
-
|
|
3359
|
-
# ─── LLM JUDGE ──────────────────────────────────────────
|
|
3360
|
-
llm:judge:
|
|
3361
|
-
stage: judge
|
|
3362
|
-
image: node:22-alpine
|
|
3363
|
-
variables:
|
|
3364
|
-
OPENAI_API_KEY: $OPENAI_API_KEY
|
|
3365
|
-
ANTHROPIC_API_KEY: $ANTHROPIC_API_KEY
|
|
3366
|
-
GEMINI_API_KEY: $GEMINI_API_KEY
|
|
3367
|
-
# CI_MERGE_REQUEST_DIFF_BASE_SHA and CI_COMMIT_SHA are set automatically
|
|
3368
|
-
# by GitLab for merge request pipelines — no manual configuration needed.
|
|
3369
|
-
before_script:
|
|
3370
|
-
- git fetch --unshallow || true # Ensure full history for git diff
|
|
3371
|
-
script:
|
|
3372
|
-
- node scripts/llm-judge.mjs
|
|
3373
|
-
artifacts:
|
|
3374
|
-
when: always
|
|
3375
|
-
paths:
|
|
3376
|
-
- .agent-context/state/llm-judge-report.json
|
|
3377
|
-
expire_in: 7 days
|
|
3378
|
-
rules:
|
|
3379
|
-
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
|
3380
|
-
|
|
3381
|
-
# ─── DEPLOY ─────────────────────────────────────────────
|
|
3382
|
-
deploy:staging:
|
|
3383
|
-
stage: deploy
|
|
3384
|
-
environment:
|
|
3385
|
-
name: staging
|
|
3386
|
-
url: https://staging.example.com
|
|
3387
|
-
script:
|
|
3388
|
-
- echo "Deploy to staging"
|
|
3389
|
-
# Add your deployment commands
|
|
3390
|
-
rules:
|
|
3391
|
-
- if: '$CI_COMMIT_BRANCH == "main"'
|
|
3392
|
-
|
|
3393
|
-
deploy:production:
|
|
3394
|
-
stage: deploy
|
|
3395
|
-
environment:
|
|
3396
|
-
name: production
|
|
3397
|
-
url: https://example.com
|
|
3398
|
-
script:
|
|
3399
|
-
- echo "Deploy to production"
|
|
3400
|
-
# Add your deployment commands
|
|
3401
|
-
rules:
|
|
3402
|
-
- if: '$CI_COMMIT_BRANCH == "main"'
|
|
3403
|
-
when: manual # Require manual approval
|
|
3404
|
-
needs: [deploy:staging]
|
|
3405
|
-
```
|
|
3406
|
-
|
|
3407
|
-
## Key Differences from GitHub Actions
|
|
3408
|
-
|
|
3409
|
-
| Concern | GitHub Actions | GitLab CI |
|
|
3410
|
-
|---------|---------------|-----------|
|
|
3411
|
-
| Config file | `.github/workflows/*.yml` | `.gitlab-ci.yml` (single file) |
|
|
3412
|
-
| Jobs linkage | `needs:` | `stages:` (sequential) + `needs:` (DAG) |
|
|
3413
|
-
| Secrets | GitHub Secrets | CI/CD Variables (masked + protected) |
|
|
3414
|
-
| Caching | `actions/cache` | Built-in `cache:` directive |
|
|
3415
|
-
| Services | Docker Compose / service containers | `services:` directive |
|
|
3416
|
-
| Environments | Environment protection rules | Environment + `when: manual` |
|
|
3417
|
-
| Includes | Reusable workflows | `include:` with `template:` |
|
|
3418
|
-
|
|
3419
|
-
## Security Rules
|
|
3420
|
-
|
|
3421
|
-
1. **Protected variables** — mark secrets as Protected + Masked
|
|
3422
|
-
2. **Protected branches** — only deploy from protected branches
|
|
3423
|
-
3. **Include GitLab SAST/DAST** templates for automated scanning
|
|
3424
|
-
4. **Limit runner access** — use tags to route jobs to appropriate runners
|
|
3425
|
-
5. **Artifact expiration** — set `expire_in` on all artifacts
|
|
3426
|
-
6. **Limit LLM input scope** — send only merge diff + checklist context
|
|
3427
|
-
|
|
3428
|
-
## Scaffolding Checklist
|
|
3429
|
-
|
|
3430
|
-
- [ ] Create `.gitlab-ci.yml` with validate, test, security, build, deploy stages
|
|
3431
|
-
- [ ] Configure CI/CD variables for secrets (masked + protected)
|
|
3432
|
-
- [ ] Set up caching for package manager lockfile
|
|
3433
|
-
- [ ] Add coverage reporting with Cobertura format
|
|
3434
|
-
- [ ] Configure environments (staging, production) with manual approval
|
|
3435
|
-
- [ ] Include SAST template for security scanning
|
|
3436
|
-
- [ ] Set up merge request pipelines with `rules:`
|
|
3437
|
-
- [ ] Configure branch protection rules
|
|
3438
|
-
- [ ] Add `timeout` to long-running jobs
|
|
3439
|
-
- [ ] Use `needs:` for DAG optimization where possible
|
|
3440
|
-
- [ ] Add `llm:judge` stage that enforces `pr-checklist.md`
|
|
3441
|
-
|
|
3442
|
-
## LLM Judge Annotation Contract
|
|
3443
|
-
|
|
3444
|
-
For MR annotations and dashboards, parse either:
|
|
3445
|
-
|
|
3446
|
-
- Log line: `JSON_REPORT: { ... }`
|
|
3447
|
-
- Artifact: `.agent-context/state/llm-judge-report.json`
|
|
3448
|
-
|
|
3449
|
-
Severity values are normalized by the judge to: `critical`, `high`, `medium`, `low`.
|
|
57
|
+
Summary: Blueprint: Next.js API Project (App Router)
|
|
58
|
+
Load this blueprint when scaffolding or changing architecture boundaries.
|
|
59
|
+
## LAYER 3B: CI/CD GUARDRAILS
|
|
60
|
+
Load these CI blueprints when pipeline or release logic is touched:
|
|
61
|
+
1. .agent-context/blueprints/ci-github-actions.md
|
|
62
|
+
2. .agent-context/blueprints/ci-gitlab.md
|
|
3450
63
|
## SKILL PACK: Frontend
|
|
3451
64
|
Source: .agent-context/skills/frontend.md
|
|
3452
65
|
Default tier: advance
|
|
3453
66
|
Selected tier: advance
|
|
3454
67
|
Evidence: Frontend usability audit, accessibility checks, and visual regression output.
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
Default tier: `advance`
|
|
3459
|
-
|
|
3460
|
-
## Purpose
|
|
3461
|
-
Deliver frontend experiences that are visually intentional, accessible, and efficient.
|
|
3462
|
-
|
|
3463
|
-
## In Scope
|
|
3464
|
-
- UI architecture and component composition
|
|
3465
|
-
- Responsive layouts and breakpoints
|
|
3466
|
-
- Motion, animation, and interaction polish
|
|
3467
|
-
- Accessibility and keyboard flows
|
|
3468
|
-
- Conversion clarity and onboarding flow
|
|
3469
|
-
|
|
3470
|
-
## Must-Have Checks
|
|
3471
|
-
- Feature-driven folder structure
|
|
3472
|
-
- Smart and dumb component separation
|
|
3473
|
-
- Server state and client state separation
|
|
3474
|
-
- Reduced-motion fallback
|
|
3475
|
-
- Accessibility baseline pass
|
|
3476
|
-
- Responsive behavior verified on mobile and desktop
|
|
3477
|
-
|
|
3478
|
-
## Evidence
|
|
3479
|
-
- Usability audit result
|
|
3480
|
-
- Visual regression output
|
|
3481
|
-
- Accessibility checklist result
|
|
3482
|
-
- Release notes for motion and interaction changes
|
|
3483
|
-
|
|
3484
|
-
## Fallback
|
|
3485
|
-
- If a feature cannot meet the advance tier, ship standard mode only as a temporary compatibility path.
|
|
3486
|
-
|
|
68
|
+
Purpose: Unified frontend delivery covering UI architecture, motion, accessibility, and conversion clarity.
|
|
69
|
+
Load this skill pack and apply every Must-Have Check.
|
|
3487
70
|
## SKILL PACK: Fullstack
|
|
3488
71
|
Source: .agent-context/skills/fullstack.md
|
|
3489
72
|
Default tier: advance
|
|
3490
73
|
Selected tier: advance
|
|
3491
74
|
Evidence: End-to-end tests, contract validation, and feature parity review.
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
Default tier: `advance`
|
|
3496
|
-
|
|
3497
|
-
## Purpose
|
|
3498
|
-
Coordinate frontend and backend delivery as a single product system.
|
|
3499
|
-
|
|
3500
|
-
## In Scope
|
|
3501
|
-
- Feature slicing across UI and API boundaries
|
|
3502
|
-
- Shared validation contracts
|
|
3503
|
-
- End-to-end flows and release readiness
|
|
3504
|
-
- Performance, accessibility, and observability together
|
|
3505
|
-
|
|
3506
|
-
## Must-Have Checks
|
|
3507
|
-
- Single feature directory with clear public API
|
|
3508
|
-
- Frontend and backend contracts aligned
|
|
3509
|
-
- End-to-end test coverage for critical paths
|
|
3510
|
-
- Release notes explain UX and API impact together
|
|
3511
|
-
|
|
3512
|
-
## Evidence
|
|
3513
|
-
- Feature parity checklist
|
|
3514
|
-
- End-to-end test report
|
|
3515
|
-
- Contract validation output
|
|
3516
|
-
- Release artifact bundle
|
|
3517
|
-
|
|
3518
|
-
## Fallback
|
|
3519
|
-
- Split delivery only when the feature boundary is explicit and the evidence bundle is still complete.
|
|
3520
|
-
|
|
75
|
+
Purpose: Single-path product delivery across frontend and backend boundaries.
|
|
76
|
+
Load this skill pack and apply every Must-Have Check.
|
|
3521
77
|
## SKILL PACK: CLI
|
|
3522
78
|
Source: .agent-context/skills/cli.md
|
|
3523
79
|
Default tier: advance
|
|
3524
80
|
Selected tier: advance
|
|
3525
81
|
Evidence: CLI smoke tests, dry-run output, and automation-friendly reports.
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
-
|
|
3536
|
-
-
|
|
3537
|
-
|
|
3538
|
-
- Validation and self-healing hooks
|
|
3539
|
-
- Cross-platform shell behavior
|
|
3540
|
-
|
|
3541
|
-
## Must-Have Checks
|
|
3542
|
-
- Explicit command help and examples
|
|
3543
|
-
- Deterministic output format for automation
|
|
3544
|
-
- Safe destructive-action guards
|
|
3545
|
-
- Validation before mutation
|
|
3546
|
-
- Exit codes reflect success and failure clearly
|
|
3547
|
-
|
|
3548
|
-
## Evidence
|
|
3549
|
-
- CLI smoke tests
|
|
3550
|
-
- Machine-readable report output
|
|
3551
|
-
- Upgrade dry-run output
|
|
3552
|
-
- Cross-platform execution notes
|
|
3553
|
-
|
|
3554
|
-
## Fallback
|
|
3555
|
-
- Standard mode can remain available for compatibility, but advance is the default user experience.
|
|
3556
|
-
|
|
3557
|
-
## STATE MAP: architecture-map.md
|
|
3558
|
-
Source: .agent-context/state/architecture-map.md
|
|
3559
|
-
|
|
3560
|
-
# Architecture Map (State Awareness)
|
|
3561
|
-
|
|
3562
|
-
> This file defines protected architectural boundaries for AI-assisted changes.
|
|
3563
|
-
|
|
3564
|
-
## Boundary Classification
|
|
3565
|
-
|
|
3566
|
-
| Module/Path Pattern | Criticality | Change Policy | Required Checks |
|
|
3567
|
-
|---------------------|-------------|---------------|-----------------|
|
|
3568
|
-
| `src/modules/payment/**` | critical | Must preserve transactional behavior and idempotency | Unit + integration + rollback test |
|
|
3569
|
-
| `src/modules/authentication/**` | critical | Never bypass auth guards or token validation | Security audit + integration tests |
|
|
3570
|
-
| `src/modules/**/repository/**` | high | Preserve query contracts and avoid N+1 regressions | Query plan review + performance audit |
|
|
3571
|
-
| `src/features/**` | medium | Keep UI contracts stable and avoid API drift | Component tests + contract checks |
|
|
3572
|
-
| `src/shared/**` | high | Backward compatibility required for public utilities | Cross-module usage validation |
|
|
3573
|
-
|
|
3574
|
-
## Required Agent Behavior
|
|
3575
|
-
|
|
3576
|
-
1. Before editing a `critical` area, load `.agent-context/review-checklists/security-audit.md` and `.agent-context/review-checklists/performance-audit.md`.
|
|
3577
|
-
2. For boundary-crossing changes, verify no circular dependencies are introduced (see `dependency-map.md`).
|
|
3578
|
-
3. Every critical-path change must include explicit risk notes in PR description.
|
|
3579
|
-
|
|
3580
|
-
## Project-Specific Notes
|
|
3581
|
-
|
|
3582
|
-
- Replace placeholder path patterns with your actual module map.
|
|
3583
|
-
- Mark payment, identity, and financial reconciliation flows as `critical`.
|
|
3584
|
-
- Keep this file updated whenever module ownership changes.
|
|
3585
|
-
## STATE MAP: dependency-map.md
|
|
3586
|
-
Source: .agent-context/state/dependency-map.md
|
|
3587
|
-
|
|
3588
|
-
# Dependency Map (State Awareness)
|
|
3589
|
-
|
|
3590
|
-
> This map documents allowed dependency direction to prevent circular references during refactors.
|
|
3591
|
-
|
|
3592
|
-
## Layer Dependency Rules
|
|
3593
|
-
|
|
3594
|
-
1. Transport layer may depend on Service layer.
|
|
3595
|
-
2. Service layer may depend on Domain contracts and Repository interfaces.
|
|
3596
|
-
3. Infrastructure layer may implement Repository interfaces.
|
|
3597
|
-
4. Domain layer must not depend on Transport or Infrastructure.
|
|
3598
|
-
|
|
3599
|
-
## Module-Level Constraints
|
|
3600
|
-
|
|
3601
|
-
| Source Module | Allowed Dependencies | Forbidden Dependencies |
|
|
3602
|
-
|---------------|----------------------|------------------------|
|
|
3603
|
-
| `authentication` | `shared`, `user` | `payment` internals |
|
|
3604
|
-
| `payment` | `shared`, `billing`, `notification` contracts | `authentication` internals |
|
|
3605
|
-
| `reporting` | `shared`, read-only repository ports | write-side service internals |
|
|
3606
|
-
| `frontend` | public API clients only | direct repository access |
|
|
3607
|
-
|
|
3608
|
-
## Circular Dependency Guardrail
|
|
3609
|
-
|
|
3610
|
-
When refactoring:
|
|
3611
|
-
|
|
3612
|
-
1. Detect import graph changes before applying bulk edits.
|
|
3613
|
-
2. Reject any change introducing `A -> B -> A` cycles.
|
|
3614
|
-
3. Move shared contracts to `shared` module when two-way dependencies appear.
|
|
3615
|
-
|
|
3616
|
-
## Project-Specific Notes
|
|
3617
|
-
|
|
3618
|
-
- Replace sample modules with your real domain modules.
|
|
3619
|
-
- Keep this map synchronized with architecture decisions and ADRs.
|
|
3620
|
-
## REVIEW CHECKLIST: pr-checklist.md
|
|
3621
|
-
Source: .agent-context/review-checklists/pr-checklist.md
|
|
3622
|
-
|
|
3623
|
-
# PR Checklist — The Quality Gate
|
|
3624
|
-
|
|
3625
|
-
> Run this before declaring any task "done."
|
|
3626
|
-
> If ANY item fails, the task is NOT complete.
|
|
3627
|
-
|
|
3628
|
-
## Instructions for Agent
|
|
3629
|
-
|
|
3630
|
-
When asked to review code using this checklist, evaluate EVERY item below.
|
|
3631
|
-
For each failed item, provide a Reasoning Chain (see `.cursorrules` → Reasoning Clause).
|
|
3632
|
-
Output format:
|
|
3633
|
-
|
|
3634
|
-
```
|
|
3635
|
-
## PR REVIEW RESULTS
|
|
3636
|
-
━━━━━━━━━━━━━━━━━━━
|
|
3637
|
-
|
|
3638
|
-
✅ [Item] — Passes
|
|
3639
|
-
❌ [Item] — FAILS
|
|
3640
|
-
📌 Rule: [rule file + section]
|
|
3641
|
-
❌ Problem: [specific issue found]
|
|
3642
|
-
✅ Fix: [what to change]
|
|
3643
|
-
|
|
3644
|
-
VERDICT: PASS ✅ / FAIL ❌ (X/Y items passed)
|
|
3645
|
-
```
|
|
3646
|
-
|
|
3647
|
-
---
|
|
3648
|
-
|
|
3649
|
-
## The Checklist
|
|
3650
|
-
|
|
3651
|
-
### 1. Naming (→ rules/naming-conv.md)
|
|
3652
|
-
- [ ] All variables are descriptive nouns (no `data`, `temp`, `val`, `x`)
|
|
3653
|
-
- [ ] All functions start with a verb (no `userData()`, `orderLogic()`)
|
|
3654
|
-
- [ ] All booleans use `is/has/can/should` prefix
|
|
3655
|
-
- [ ] Constants use SCREAMING_SNAKE_CASE with unit suffix
|
|
3656
|
-
- [ ] No single-letter variables (except `i` in classic for-loops)
|
|
3657
|
-
- [ ] File names follow the project's chosen convention consistently
|
|
3658
|
-
|
|
3659
|
-
### 2. Architecture (→ rules/architecture.md)
|
|
3660
|
-
- [ ] No layer leaks (controllers don't query DB, services don't return HTTP responses)
|
|
3661
|
-
- [ ] Feature-based file organization (not technical grouping)
|
|
3662
|
-
- [ ] Dependencies flow inward (transport → service → repository)
|
|
3663
|
-
- [ ] Module boundaries respected (no reaching into another module's internals)
|
|
3664
|
-
- [ ] Domain layer has zero external dependencies
|
|
3665
|
-
|
|
3666
|
-
### 3. Type Safety (→ stacks/typescript.md)
|
|
3667
|
-
- [ ] No `any` type anywhere (use `unknown` + narrowing)
|
|
3668
|
-
- [ ] No `// @ts-ignore` (use `@ts-expect-error` with justification comment)
|
|
3669
|
-
- [ ] All function return types are explicit
|
|
3670
|
-
- [ ] Zod schemas validate ALL external input at boundaries
|
|
3671
|
-
- [ ] Types derived from Zod schemas (single source of truth)
|
|
3672
|
-
|
|
3673
|
-
### 4. Error Handling (→ rules/error-handling.md)
|
|
3674
|
-
- [ ] No empty catch blocks
|
|
3675
|
-
- [ ] No `catch(e) { console.log(e) }` without re-throw or recovery
|
|
3676
|
-
- [ ] Typed error classes used (not generic `new Error('...')`)
|
|
3677
|
-
- [ ] Error responses don't leak internal details to clients
|
|
3678
|
-
- [ ] Structured logging with context (traceId, userId, action)
|
|
3679
|
-
|
|
3680
|
-
### 5. Security (→ rules/security.md)
|
|
3681
|
-
- [ ] All user input validated at boundaries (Zod/schema)
|
|
3682
|
-
- [ ] No SQL/command string concatenation with user input
|
|
3683
|
-
- [ ] No secrets hardcoded in source code
|
|
3684
|
-
- [ ] Auth checked server-side (not client-side only)
|
|
3685
|
-
- [ ] Error messages don't reveal internal system details
|
|
3686
|
-
- [ ] CORS configured (not `*` in production)
|
|
3687
|
-
|
|
3688
|
-
### 6. Performance (→ rules/performance.md)
|
|
3689
|
-
- [ ] No N+1 queries (no queries inside loops)
|
|
3690
|
-
- [ ] All list queries have LIMIT/pagination
|
|
3691
|
-
- [ ] No `SELECT *` (only needed columns)
|
|
3692
|
-
- [ ] No synchronous I/O in async context
|
|
3693
|
-
- [ ] Cache has documented invalidation strategy (if caching used)
|
|
3694
|
-
|
|
3695
|
-
### 7. Testing (→ rules/testing.md)
|
|
3696
|
-
- [ ] Business logic has unit tests
|
|
3697
|
-
- [ ] Test names follow `should [behavior] when [condition]`
|
|
3698
|
-
- [ ] Tests follow AAA pattern (Arrange → Act → Assert)
|
|
3699
|
-
- [ ] No implementation detail testing (test behavior, not structure)
|
|
3700
|
-
- [ ] Test data uses factories (no copy-pasted objects)
|
|
3701
|
-
|
|
3702
|
-
### 8. Dependencies (→ rules/efficiency-vs-hype.md)
|
|
3703
|
-
- [ ] No new dependencies added without justification
|
|
3704
|
-
- [ ] No dependency that replaces < 20 lines of code
|
|
3705
|
-
- [ ] New packages checked for maintenance health
|
|
3706
|
-
- [ ] Bundle impact considered (frontend)
|
|
3707
|
-
|
|
3708
|
-
### 9. Git (→ rules/git-workflow.md)
|
|
3709
|
-
- [ ] Commit messages follow Conventional Commits
|
|
3710
|
-
- [ ] No `console.log` debugging statements
|
|
3711
|
-
- [ ] No `// TODO` without a linked issue
|
|
3712
|
-
- [ ] No commented-out code blocks
|
|
3713
|
-
- [ ] `.env` not committed
|
|
3714
|
-
|
|
3715
|
-
### 10. Documentation
|
|
3716
|
-
- [ ] API endpoints have OpenAPI/Swagger documentation
|
|
3717
|
-
- [ ] Complex business logic has comments explaining WHY
|
|
3718
|
-
- [ ] Public functions/methods have JSDoc/docstrings
|
|
3719
|
-
- [ ] README updated if new setup steps required
|
|
82
|
+
Purpose: Smart command-line delivery with safe defaults, machine-readable output, and upgrade flows.
|
|
83
|
+
Load this skill pack and apply every Must-Have Check.
|
|
84
|
+
## LAYER 7: STATE AWARENESS (MANDATORY)
|
|
85
|
+
Load these files before touching critical paths:
|
|
86
|
+
1. .agent-context/state/architecture-map.md
|
|
87
|
+
2. .agent-context/state/dependency-map.md
|
|
88
|
+
Use these maps to prevent unsafe cross-module changes.
|
|
89
|
+
## REVIEW CHECKLISTS (MANDATORY)
|
|
90
|
+
1. .agent-context/review-checklists/pr-checklist.md
|
|
91
|
+
2. .agent-context/review-checklists/security-audit.md (when security-sensitive)
|
|
92
|
+
3. .agent-context/review-checklists/performance-audit.md (when perf-critical)
|
|
93
|
+
Do not claim done before checklist pass.
|