opencode-andev-rules 1.0.0 → 1.0.1

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.
@@ -0,0 +1,65 @@
1
+ ---
2
+ name: prisma
3
+ description: Prisma ORM rules — client setup, queries, errors, transactions, migrations. Use when building or reviewing Prisma data access.
4
+ ---
5
+
6
+ # Prisma Skill
7
+
8
+ ## Setup
9
+
10
+ - One PrismaClient singleton for the whole app — never `new PrismaClient()` per request or per service.
11
+ - Provide it once (global module / DI) — services inject it, never instantiate it.
12
+ - Datasource URL from env only — never hardcode connection strings, never log them.
13
+ - Size the pool via `connection_limit`/`pool_timeout` — serverless or hot-reload dev must reuse the singleton or use an external pooler.
14
+
15
+ ## Relations: FK/PK decision
16
+
17
+ - Always ask the user before modeling relations: database-enforced foreign keys vs Prisma-emulated relations (`relationMode = "prisma"`).
18
+ - Without DB-level FKs, Prisma can still join via `include` — but nothing enforces referential integrity in the database.
19
+ - With DB FKs: integrity enforced by the database; `onDelete` acts at the DB level; migrations carry the constraints.
20
+ - Without DB FKs: integrity must be enforced in application code (existence checks + transactions); no `onDelete` in the database; fits providers without FK support.
21
+ - Never assume one mode — the choice changes schema, migrations, and error handling; confirm it first.
22
+
23
+ ## Queries
24
+
25
+ - Use `select` when only a subset of fields is required — don't over-fetch.
26
+ - Avoid N+1 queries — use `include` or joins, not loops.
27
+ - Avoid `include` for large relation collections — paginate or `select` instead.
28
+ - Prisma fetches all scalar columns by default — with wide tables or heavy columns (text, JSON), always narrow it down.
29
+ - Never blanket `include` — it pulls every column of the related table; nest `select` inside instead.
30
+
31
+ ```typescript
32
+ // Good — narrow scalars, nested select for relations
33
+ await prisma.user.findMany({
34
+ select: { id: true, name: true, posts: { select: { id: true, title: true } } },
35
+ });
36
+
37
+ // Or drop heavy fields (Prisma 6.2+)
38
+ await prisma.post.findMany({ omit: { content: true } });
39
+ ```
40
+ - Use `findUnique` when the field is unique — `findFirst` only otherwise.
41
+ - Paginate `findMany` — never return unbounded results; cursor pagination for large datasets, offset for small.
42
+ - Always `orderBy` deterministic fields for pagination.
43
+ - Run independent queries in parallel — don't `await` sequentially.
44
+ - Never raw SQL unless performance-critical (comment why) — never concatenate values into queries, use parameterized inputs.
45
+
46
+ ## Errors
47
+
48
+ - Map Prisma errors to domain exceptions in one place (`P2002` → conflict, `P2025` → not found, ...) — never leak raw Prisma errors to API responses.
49
+
50
+ ## Transactions
51
+
52
+ - Multi-step writes go in `$transaction` — no partial writes.
53
+ - Keep transaction scope small — no external calls inside transactions.
54
+ - Race conditions on read-modify-write — use transactions or locks.
55
+
56
+ ## Migrations
57
+
58
+ - Keep migrations in `prisma/migrations` and commit them — never edit an applied migration, create a new one instead.
59
+ - Deploy with `migrate deploy` — never `migrate dev` against prod.
60
+ - Review destructive changes (drop column/table, type narrowing) before applying — backfill via a data migration when rows exist.
61
+
62
+ ## References
63
+
64
+ - Schema layout, models, indexes, relations, enums: `references/prisma-schema.md`.
65
+ - CLI, CRUD, optimization catalog: `references/prisma-queries.md`.
@@ -0,0 +1,197 @@
1
+ # Prisma Queries & Operations
2
+
3
+ Samples assume the global singleton `prisma` (see `SKILL.md` Setup) — never `new PrismaClient()` per call.
4
+
5
+ ## CLI
6
+
7
+ | Command | Use |
8
+ |---|---|
9
+ | `prisma generate` | Regenerate Prisma Client |
10
+ | `prisma migrate dev` | Create and apply migrations in dev |
11
+ | `prisma migrate deploy` | Apply migrations in production/CI |
12
+ | `prisma db push` | Push schema straight to DB, no migration (prototypes only) |
13
+ | `prisma db pull` | Introspect an existing DB into the schema |
14
+ | `prisma studio` | Open the GUI |
15
+
16
+ ## CRUD
17
+
18
+ ```typescript
19
+ // Create
20
+ await prisma.user.create({ data: { email: 'a@b.com', name: 'An' } });
21
+
22
+ // Read
23
+ await prisma.user.findUnique({ where: { email: 'a@b.com' } });
24
+ await prisma.user.findMany({
25
+ where: { name: { contains: 'An' } },
26
+ orderBy: { id: 'desc' },
27
+ take: 10,
28
+ });
29
+
30
+ // Update / Delete
31
+ await prisma.user.update({ where: { id: 'user_01' }, data: { name: 'Bình' } });
32
+ await prisma.user.delete({ where: { id: 'user_01' } });
33
+
34
+ // Relations — narrow, never blanket include
35
+ await prisma.user.findMany({
36
+ select: { id: true, name: true, posts: { select: { id: true, title: true } } },
37
+ });
38
+
39
+ // Transaction (independent writes)
40
+ await prisma.$transaction([
41
+ prisma.user.create({ data: { email: 'x@y.com', name: 'X' } }),
42
+ prisma.post.create({ data: { title: 'Hi', authorId: 'user_01' } }),
43
+ ]);
44
+ ```
45
+
46
+ ## Optimization
47
+
48
+ ### 1. Select only what you need (`select` / `omit`)
49
+
50
+ - Same rule as `SKILL.md` Queries — full example there.
51
+ - Never blanket `include` — it pulls every column of the related table.
52
+
53
+ ### 2. Avoid N+1 queries
54
+
55
+ ```typescript
56
+ // Wrong (N+1)
57
+ const users = await prisma.user.findMany();
58
+ for (const u of users) {
59
+ u.posts = await prisma.post.findMany({ where: { authorId: u.id } });
60
+ }
61
+
62
+ // Right — one round trip, narrowed
63
+ const users = await prisma.user.findMany({
64
+ select: { id: true, name: true, posts: { select: { id: true, title: true } } },
65
+ });
66
+ ```
67
+
68
+ - `relationLoadStrategy: 'join'` lets Prisma use one JOIN instead of separate queries — check preview/GA status for your Prisma version.
69
+ - Simultaneous `findUnique` calls in the same tick are auto-batched (dataloader) — useful for GraphQL resolvers.
70
+
71
+ ### 3. Index the right columns
72
+
73
+ ```prisma
74
+ model Post {
75
+ // ...
76
+ @@index([authorId]) // FK
77
+ @@index([published, createdAt]) // composite for filter + sort
78
+ @@unique([authorId, slug])
79
+
80
+ @@map("posts")
81
+ }
82
+ ```
83
+
84
+ - Index columns used in `where`, `orderBy`, joins — column order matters in composite indexes.
85
+ - Never over-index — indexes slow down writes.
86
+
87
+ ### 4. Paginate efficiently
88
+
89
+ - Offset (`skip` + `take`) slows down as `skip` grows — the DB still scans skipped rows.
90
+ - Cursor pagination is stable and fast for large datasets:
91
+
92
+ ```typescript
93
+ await prisma.post.findMany({
94
+ take: 20,
95
+ skip: 1,
96
+ cursor: { id: lastId },
97
+ orderBy: { id: 'asc' },
98
+ });
99
+ ```
100
+
101
+ ### 5. Batch writes
102
+
103
+ - Never loop single-row writes — use batch operations:
104
+
105
+ ```typescript
106
+ await prisma.user.createMany({ data: users, skipDuplicates: true });
107
+ await prisma.post.updateMany({ where: { authorId: 'user_01' }, data: { published: true } });
108
+ await prisma.post.deleteMany({ where: { published: false } });
109
+ ```
110
+
111
+ ### 6. Connection pooling
112
+
113
+ - Reuse the singleton everywhere — hot-reload dev and serverless must guard it, never create clients per invocation:
114
+
115
+ ```typescript
116
+ const globalForPrisma = globalThis as unknown as { prisma?: PrismaClient };
117
+ export const prisma = globalForPrisma.prisma ?? new PrismaClient();
118
+ if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma;
119
+ ```
120
+
121
+ - Size the pool via connection string: `?connection_limit=10&pool_timeout=20`.
122
+ - Serverless (Vercel, Lambda): use an external pooler (PgBouncer, Supabase pooler, Prisma Accelerate) — never exhaust direct connections.
123
+
124
+ ### 7. Aggregate in the database
125
+
126
+ ```typescript
127
+ await prisma.post.groupBy({ by: ['authorId'], _count: { _all: true } });
128
+ await prisma.order.aggregate({ _sum: { total: true }, where: { status: 'PAID' } });
129
+ await prisma.user.findMany({ include: { _count: { select: { posts: true } } } });
130
+ ```
131
+
132
+ - Filter, sort, and count in the DB — never pull everything and `.filter()` in code.
133
+
134
+ ### 8. Keep transactions lean
135
+
136
+ - `$transaction([...])` batch form for independent operations.
137
+ - Interactive form only when logic runs between steps — keep it short:
138
+
139
+ ```typescript
140
+ await prisma.$transaction(
141
+ async (tx) => {
142
+ const acc = await tx.account.update({
143
+ where: { id },
144
+ data: { balance: { decrement: 100 } },
145
+ });
146
+ if (acc.balance < 0) throw new Error('Insufficient balance');
147
+ },
148
+ { timeout: 5000, maxWait: 2000 },
149
+ );
150
+ ```
151
+
152
+ - No external API calls, no heavy work inside transactions — they hold connections and locks.
153
+
154
+ ### 9. Raw queries for complex cases
155
+
156
+ - Window functions, CTEs, advanced full-text search: use raw queries.
157
+ - Always the tagged template form — never string-concatenate user input:
158
+
159
+ ```typescript
160
+ const rows = await prisma.$queryRaw`
161
+ SELECT author_id, COUNT(*) FROM "posts" WHERE published = true GROUP BY author_id
162
+ `;
163
+ ```
164
+
165
+ - Never `$queryRawUnsafe` with user input — use TypedSQL for typed raw queries.
166
+
167
+ ### 10. Measure before optimizing
168
+
169
+ ```typescript
170
+ const prisma = new PrismaClient({ log: ['query', 'warn', 'error'] });
171
+ ```
172
+
173
+ - Enable query logging in dev (on the singleton) to catch N+1 and redundant queries.
174
+ - Run `EXPLAIN ANALYZE` on generated SQL to confirm index usage.
175
+ - Trace per-query time with OpenTelemetry or Prisma `$extends`.
176
+
177
+ ### 11. Cache
178
+
179
+ - Cache heavy-read, rarely-changed results with Redis or in-memory.
180
+ - Prisma Accelerate supports `cacheStrategy: { ttl: 60, swr: 120 }`.
181
+
182
+ ### 12. Cold start / bundle
183
+
184
+ - Edge/serverless: consider driver adapters (Neon, PlanetScale, D1, ...) to cut size and startup latency — check your Prisma version.
185
+ - Generate only the needed binary targets on deploy (`binaryTargets`).
186
+
187
+ ## Checklist
188
+
189
+ - One PrismaClient singleton
190
+ - Always `select`/`omit` on wide tables
191
+ - No queries in loops — use `include`, `in`, `createMany`
192
+ - Indexes on FKs and filter/sort columns
193
+ - Cursor pagination for large lists
194
+ - Short transactions
195
+ - Query logging in dev to catch N+1
196
+ - Pooler for serverless
197
+ - Raw SQL for complex queries (safe tagged-template form)
@@ -0,0 +1,126 @@
1
+ # Prisma Schema Structure Rules
2
+
3
+ ## Layout
4
+
5
+ - Follow this tree exactly — schema, migrations, and seed stay under `prisma/`.
6
+ - Split the schema per domain under `prisma/schema/` — never one giant `schema.prisma`.
7
+
8
+ ```
9
+ prisma/
10
+ ├── schema/
11
+ │ ├── base.prisma
12
+ │ ├── user.prisma
13
+ │ └── order.prisma
14
+ ├── seed.ts
15
+ └── migrations/
16
+ └── 20240101000000_init/
17
+ └── migration.sql
18
+ ```
19
+
20
+ - `base.prisma` holds only `generator` + `datasource` — never models or enums in it.
21
+ - One file per domain (`user.prisma`, `order.prisma`, ...) holding its models + enums — never split one model across files.
22
+ - Cross-file relations reference the model by name — Prisma merges all files, no imports needed.
23
+ - One model per table — never two models over the same table.
24
+
25
+ ## Models
26
+
27
+ - Model names PascalCase singular with `@@map` to the snake_case table (e.g., `model User ... @@map("users")`).
28
+ - Field names camelCase; map to snake_case columns with `@map` when the database uses snake_case.
29
+ - Every model has a primary key, `createdAt`, and `updatedAt` — never a model without a PK.
30
+ - Primary keys are `String @id @default(uuid())` — never auto-increment ints for IDs.
31
+ - Soft-delete with `deletedAt DateTime?` — always filter it in queries, never return soft-deleted rows by default.
32
+ - Put DB-level defaults in the schema (`@default`, `@updatedAt`) — never rely on application code for timestamps.
33
+
34
+ ```prisma
35
+ model User {
36
+ id String @id @default(uuid())
37
+ email String @unique
38
+ name String
39
+ role Role @default(USER)
40
+ createdAt DateTime @default(now())
41
+ updatedAt DateTime @updatedAt
42
+ deletedAt DateTime?
43
+
44
+ @@map("users")
45
+ }
46
+ ```
47
+
48
+ ## Enums
49
+
50
+ - Fixed sets (status, role) are Prisma enums — never free strings for status-like fields.
51
+ - Map enum values explicitly when the database stores different labels.
52
+
53
+ ```prisma
54
+ enum Role {
55
+ ADMIN
56
+ USER
57
+ }
58
+ ```
59
+
60
+ ## Relations
61
+
62
+ - Confirm FK/PK mode first (decision gate in `SKILL.md`) — the rules below assume DB-enforced foreign keys.
63
+ - No-FK mode: skip DB constraints and `onDelete`, enforce integrity in code with existence checks + transactions.
64
+ - Name relations explicitly when two models link more than once — never leave ambiguous relations unnamed.
65
+ - Set `onDelete` deliberately per relation (`Cascade`, `Restrict`, `SetNull`) — never accept the default without thinking; dangerous cascades stay out.
66
+ - Use an explicit relation table for many-to-many that carries extra fields — never overload a pure join with payload columns.
67
+ - Index every foreign key — Prisma does not auto-index all relation columns on every provider.
68
+
69
+ ```prisma
70
+ // WITH FK/PK — database enforces the relation
71
+ // schema/user.prisma
72
+ model User {
73
+ id String @id @default(uuid())
74
+ email String @unique
75
+ posts Post[]
76
+
77
+ @@map("users")
78
+ }
79
+
80
+ // schema/post.prisma
81
+ model Post {
82
+ id String @id @default(uuid())
83
+ title String
84
+ authorId String
85
+ author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
86
+
87
+ @@index([authorId])
88
+ @@map("posts")
89
+ }
90
+ ```
91
+
92
+ ```prisma
93
+ // WITHOUT FK/PK — Prisma emulates the relation, DB has no constraints
94
+ // schema/base.prisma
95
+ datasource db {
96
+ provider = "postgresql"
97
+ url = env("DATABASE_URL")
98
+ relationMode = "prisma"
99
+ }
100
+
101
+ // schema/post.prisma
102
+ model Post {
103
+ id String @id @default(uuid())
104
+ title String
105
+ authorId String // plain scalar — no FK constraint in the DB
106
+ author User @relation(fields: [authorId], references: [id])
107
+
108
+ @@index([authorId])
109
+ @@map("posts")
110
+ }
111
+ // integrity (author must exist) enforced in code + transactions — see SKILL.md
112
+ ```
113
+
114
+ ## Indexes & Constraints
115
+
116
+ - Add `@@index` for frequently filtered/sorted fields and composite `@@index([a, b])` for common multi-field queries.
117
+ - Add `@@unique` / `@unique` for data that must be unique (email, slug) — enforce in the database, not only in code.
118
+ - Never duplicate or redundant indexes — one index per access pattern.
119
+ - Keep nullable/required aligned across schema, DTO validation, and application types — inconsistent nullability is a bug source.
120
+
121
+ ## Migrations
122
+
123
+ - Name migrations after what they do (`add_user_role`, not `update`) — one logical change per migration.
124
+ - Schema changes that need existing rows rewritten ship with a data migration — never lose rows silently.
125
+ - Changing a field type or dropping a column requires a backfill/dual-write plan when the table has data.
126
+ - Never hand-edit `migration.sql` of an applied migration — revert with a new migration instead.
@@ -1,15 +1,56 @@
1
1
  {
2
2
  "name": "andev-skills",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "AI agent skills by thaiannguyen-05",
5
5
  "skills": [
6
6
  {
7
7
  "name": "improve-codenase-systems",
8
8
  "description": "Scan codebase for architecture issues and deepening opportunities",
9
9
  "version": "1.0.0",
10
- "tags": ["architecture", "review", "nestjs", "react", "codebase"],
11
- "path": "skills/improve-codenase-systems",
12
- "files": ["SKILL.md", "HTML-REPORT.md", "agents/openai.yaml"]
10
+ "tags": [
11
+ "architecture",
12
+ "review",
13
+ "nestjs",
14
+ "react",
15
+ "codebase"
16
+ ],
17
+ "path": "skills/enhance/improve-codenase-systems",
18
+ "files": [
19
+ "SKILL.md",
20
+ "HTML-REPORT.md",
21
+ "agents/openai.yaml"
22
+ ]
23
+ },
24
+ {
25
+ "name": "nestjs",
26
+ "description": "NestJS backend rules — Fastify platform, module structure, zod validation",
27
+ "version": "1.0.0",
28
+ "tags": [
29
+ "nestjs",
30
+ "backend",
31
+ "typescript"
32
+ ],
33
+ "path": "skills/backend/nestJS",
34
+ "files": [
35
+ "SKILL.md",
36
+ "references/nestjs-module.md"
37
+ ]
38
+ },
39
+ {
40
+ "name": "prisma",
41
+ "description": "Prisma ORM rules — client setup, queries, errors, transactions, migrations",
42
+ "version": "1.0.0",
43
+ "tags": [
44
+ "prisma",
45
+ "database",
46
+ "orm"
47
+ ],
48
+ "path": "skills/infrastructure/prisma",
49
+ "files": [
50
+ "SKILL.md",
51
+ "references/prisma-queries.md",
52
+ "references/prisma-schema.md"
53
+ ]
13
54
  }
14
55
  ]
15
56
  }
package/RULES.md DELETED
@@ -1,253 +0,0 @@
1
- # Rules for TypeScript & NestJS
2
-
3
- ## TypeScript
4
-
5
- - Use `strict: true` in `tsconfig.json`
6
- - No `any` type — use `unknown` and narrow with type guards
7
- - Use `interface` for object shapes, `type` for unions/intersections
8
- - Prefer `readonly` for immutable data
9
- - Avoid type assertions (`as`) — use type guards or `satisfies` instead
10
- - Use `!` non-null assertion only when value is guaranteed (add comment why)
11
- - Export types from barrel files (`index.ts`)
12
-
13
- ## NestJS
14
-
15
- - Use CLI to generate: `nest g module`, `nest g controller`, `nest g service`
16
- - One module per feature (e.g., `users/`, `auth/`, `orders/`)
17
- - Controllers handle HTTP — no business logic in controllers
18
- - Services handle business logic — no HTTP concerns in services
19
- - Use `@nestjs/swagger` decorators on DTOs and controllers
20
- - Inject dependencies via constructor — never instantiate manually
21
- - Use `@InjectRepository()` for TypeORM/Prisma repositories
22
- - Use guards for auth (`@UseGuards`), interceptors for logging/transformation
23
- - Use `ConfigModule` for environment variables — no `process.env` in code
24
- - Use `@nestjs/schedule` for cron jobs — no `setInterval`
25
-
26
- ## Types
27
-
28
- - Every module must have a `types.ts` file for module-specific types
29
- - Export all types from `types.ts` — import from there in other files
30
- - Use `type.ts` for: enums, interfaces, type aliases, return types
31
- - Do NOT put types in `dto/` — DTOs are for validation, types are for contracts
32
- - Import pattern: `import { UserRole } from './types'`
33
- - Put constants in `types.ts`: event names, URLs, status codes, config values
34
- - Use `enum` for constants — no hardcoded strings in business logic
35
-
36
- ```typescript
37
- // types.ts
38
- export enum EventName {
39
- CREATED = 'user.created',
40
- UPDATED = 'user.updated',
41
- DELETED = 'user.deleted',
42
- }
43
-
44
- export enum ApiUrl {
45
- BASE = '/api/v1',
46
- USERS = '/api/v1/users',
47
- }
48
-
49
- export interface User { id: string; name: string; }
50
- ```
51
-
52
- ## Validation
53
-
54
- - Handle ALL validation before controller and service
55
- - Use global `ValidationPipe` in `main.ts` — reject invalid requests early
56
- - Use `class-validator` decorators on DTOs
57
- - Use `class-transformer` with `ValidationPipe` globally
58
- - Validate nested objects with `@ValidateNested()`
59
- - Use `@IsString()`, `@IsNumber()`, `@IsEmail()`, `@IsOptional()` etc.
60
- - Controllers and services receive only valid data — no validation logic inside
61
- - Use `WhitelistStrategy` + `ForbidNonWhitelisted` to reject unknown fields
62
- - Use `TransformPipe` to auto-transform payloads to DTO instances
63
-
64
- ```typescript
65
- // main.ts
66
- app.useGlobalPipes(
67
- new ValidationPipe({
68
- whitelist: true,
69
- forbidNonWhitelisted: true,
70
- transform: true,
71
- }),
72
- );
73
- ```
74
-
75
- ## Error Handling
76
-
77
- - Handle errors with exceptions — use custom `AppException` classes
78
- - Create `src/error/` directory with exception classes and global filter
79
- - `errorCode` = uppercase snake_case (e.g., `RESOURCE_NOT_FOUND`)
80
- - Always return consistent JSON: `{ statusCode, timestamp, path, message, errorCode?, details? }`
81
- - Log unhandled (non-HttpException) errors with logger
82
- - Create helper files for domain-specific error scenarios
83
-
84
- ```typescript
85
- // app.exception.ts
86
- export class AppException extends HttpException {
87
- constructor(
88
- message: string,
89
- statusCode: HttpStatus,
90
- public readonly errorCode?: string,
91
- public readonly details?: unknown,
92
- ) {
93
- super({ message, errorCode, details, timestamp: new Date().toISOString() }, statusCode);
94
- }
95
- }
96
-
97
- export class ResourceNotFoundException extends AppException {
98
- constructor(resourceType: string, identifier: string | number) {
99
- super(`${resourceType} with identifier ${identifier} not found`, HttpStatus.NOT_FOUND, 'RESOURCE_NOT_FOUND');
100
- }
101
- }
102
- ```
103
-
104
- ```typescript
105
- // main.ts
106
- app.useGlobalFilters(new GlobalExceptionFilter(logger));
107
- ```
108
-
109
- ## Database
110
-
111
- - Use Prisma or TypeORM — pick one, don't mix
112
- - Keep migrations in `prisma/migrations` or `src/migrations`
113
- - Use repositories or Prisma client for data access
114
- - Never write raw SQL unless performance-critical (add comment why)
115
- - Use `select` when only a subset of fields is required — don't over-fetch
116
- - Avoid N+1 queries — use `include` or joins, not loops
117
- - Run independent queries in parallel — don't `await` sequentially
118
- - Use `findFirst` only when `findUnique` is possible
119
- - Paginate `findMany` — never return unbounded results
120
- - Use cursor pagination for large datasets, offset for small
121
- - Always `orderBy` deterministic fields for pagination
122
- - Handle Prisma errors — never leak raw Prisma errors to API responses
123
- - Use transactions for multi-step operations — no partial writes
124
- - Keep transaction scope small — no external calls inside transactions
125
- - Race conditions on read-modify-write — use transactions or locks
126
- - Add unique constraints for data that must be unique
127
- - Soft-delete: always filter `deletedAt` in queries
128
- - Avoid `include` for large relation collections — paginate or select
129
-
130
- ## Testing
131
-
132
- - Use Jest (built into NestJS)
133
- - Unit tests: `*.spec.ts` next to source files
134
- - E2E tests: `test/*.e2e-spec.ts`
135
- - Mock dependencies with `@nestjs/testing` `Test.createTestingModule()`
136
- - Test controllers and services separately
137
- - Use `supertest` for HTTP assertions in e2e
138
-
139
- ## Async & Concurrency
140
-
141
- - Run independent async operations in parallel — don't `await` sequentially
142
- - Always handle promise rejections — no fire-and-forget without error handling
143
- - Add timeout for external service calls
144
- - Use idempotency keys for retryable operations
145
- - Retry with limits and exponential backoff
146
- - Never retry non-idempotent operations unsafely
147
- - Race conditions: use transactions or locks for read-modify-write
148
-
149
- ## Security
150
-
151
- - Never hardcode secrets or credentials — use `ConfigModule`
152
- - Never expose sensitive data in API responses
153
- - Validate and sanitize all user input
154
- - Prevent IDOR — check resource ownership
155
- - Use parameterized queries — no raw SQL concatenation
156
- - Validate file uploads: size, type, content
157
- - Never trust client-provided file metadata
158
- - Secure CORS configuration — no wildcard in production
159
- - Rate limit sensitive endpoints (login, register, password reset)
160
- - Never log sensitive data (passwords, tokens, PII)
161
- - Use guards for auth — no manual auth checks in controllers
162
-
163
- ## API Design
164
-
165
- - Consistent REST naming: plural nouns, HTTP verbs for actions
166
- - Consistent error response format: `{ statusCode, message, errorCode }`
167
- - Paginate all list endpoints — no unbounded responses
168
- - Filter and limit query parameters
169
- - Version API when making breaking changes
170
- - Use appropriate HTTP status codes (201 for create, 204 for delete)
171
- - Never expose internal implementation details in responses
172
-
173
- ## Observability
174
-
175
- - Use structured logging (nestjs-pino)
176
- - Never log sensitive data
177
- - Propagate request/correlation IDs
178
- - Add health check endpoints for critical dependencies
179
- - Handle graceful shutdown — clean up connections
180
- - Clean up timers, listeners, subscriptions
181
- - Handle background job failures
182
-
183
- ## File Structure
184
-
185
- ```
186
- andev-skill-agent/
187
- ├── .git/
188
- ├── package.json # @thaiannguyen-05/opencode-rules
189
- ├── plugin.js # trigger detection + GitHub fetch
190
- ├── README.md # usage docs
191
- └── RULES.md # this file — rules for agents
192
- ```
193
-
194
- ### NestJS Project Structure
195
-
196
- ```
197
- src/
198
- ├── app.module.ts
199
- ├── main.ts
200
- ├── config/
201
- │ └── configuration.ts
202
- ├── common/
203
- │ ├── guards/
204
- │ ├── interceptors/
205
- │ ├── pipes/
206
- │ └── decorators/
207
- ├── error/
208
- │ ├── app.exception.ts
209
- │ ├── global.exception.ts
210
- │ └── [domain]-error.helper.ts
211
- ├── modules/
212
- │ ├── users/
213
- │ │ ├── users.module.ts
214
- │ │ ├── users.controller.ts
215
- │ │ ├── users.service.ts
216
- │ │ ├── types.ts
217
- │ │ ├── dto/
218
- │ │ └── entities/
219
- │ ├── auth/
220
- │ │ ├── auth.module.ts
221
- │ │ ├── auth.controller.ts
222
- │ │ ├── auth.service.ts
223
- │ │ ├── types.ts
224
- │ │ ├── dto/
225
- │ │ └── entities/
226
- │ └── orders/
227
- │ ├── orders.module.ts
228
- │ ├── orders.controller.ts
229
- │ ├── orders.service.ts
230
- │ ├── types.ts
231
- │ ├── dto/
232
- │ └── entities/
233
- ```
234
-
235
- ## Code Style
236
-
237
- - Use Prettier for formatting
238
- - Use ESLint with `@typescript-eslint` rules
239
- - Max 300 lines per file — split if超过
240
- - One export per file — no barrel exports in middle of file
241
- - Sort imports: node builtins → external → internal → relative
242
- - Use `path aliases` (`@/`) for clean imports
243
- - Prefer `async/await` over `.then()` chains
244
- - Use `enum` for constants — no hardcoded strings
245
-
246
- ```json
247
- // .eslintrc
248
- {
249
- "rules": {
250
- "max-lines": ["error", { "max": 300, "skipBlankLines": true, "skipComments": true }]
251
- }
252
- }
253
- ```