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.
- package/LICENSE +21 -0
- package/README.md +68 -31
- package/package.json +28 -14
- package/skills/backend/nestJS/SKILL.md +254 -0
- package/skills/backend/nestJS/references/nestjs-module.md +212 -0
- package/skills/infrastructure/prisma/SKILL.md +65 -0
- package/skills/infrastructure/prisma/references/prisma-queries.md +197 -0
- package/skills/infrastructure/prisma/references/prisma-schema.md +126 -0
- package/skills/registry.json +45 -4
- package/RULES.md +0 -253
- package/plugin.js +0 -96
- /package/skills/{improve-codenase-systems → enhance/improve-codenase-systems}/HTML-REPORT.md +0 -0
- /package/skills/{improve-codenase-systems → enhance/improve-codenase-systems}/SKILL.md +0 -0
- /package/skills/{improve-codenase-systems → enhance/improve-codenase-systems}/agents/openai.yaml +0 -0
- /package/skills/{improve-codenase-systems → enhance/improve-codenase-systems}/meta.json +0 -0
|
@@ -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.
|
package/skills/registry.json
CHANGED
|
@@ -1,15 +1,56 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "andev-skills",
|
|
3
|
-
"version": "1.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": [
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
```
|