opencode-andev-rules 1.0.0 → 1.0.2

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,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,57 @@
1
1
  {
2
2
  "name": "andev-skills",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
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
+ "references/nestjs-testing.md"
38
+ ]
39
+ },
40
+ {
41
+ "name": "prisma",
42
+ "description": "Prisma ORM rules — client setup, queries, errors, transactions, migrations",
43
+ "version": "1.0.0",
44
+ "tags": [
45
+ "prisma",
46
+ "database",
47
+ "orm"
48
+ ],
49
+ "path": "skills/infrastructure/prisma",
50
+ "files": [
51
+ "SKILL.md",
52
+ "references/prisma-queries.md",
53
+ "references/prisma-schema.md"
54
+ ]
13
55
  }
14
56
  ]
15
57
  }
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
- ```
package/plugin.js DELETED
@@ -1,96 +0,0 @@
1
- // @thaiannguyen-05/opencode-rules
2
- //
3
- // On-demand rule injection plugin for opencode.
4
- // When user says "follow the rule of andev" (or similar), fetches RULES.md
5
- // from GitHub and injects it into the system prompt.
6
- //
7
- // Hooks:
8
- // - chat.message: detect trigger phrase in user input
9
- // - experimental.chat.system.transform: inject rules into system prompt
10
-
11
- const RULES_URL = 'https://raw.githubusercontent.com/thaiannguyen-05/rules-for-global-agents/main/RULES.md';
12
-
13
- // Trigger patterns — case-insensitive
14
- const TRIGGER_PATTERNS = [
15
- /follow\s+(the\s+)?rules?\s+of\s+andev/i,
16
- /follow\s+andev\s+rules?/i,
17
- /use\s+andev\s+rules?/i,
18
- /apply\s+andev\s+rules?/i,
19
- /andev\s+rules/i,
20
- ];
21
-
22
- // In-memory cache: rules text per session
23
- let cachedRules = null;
24
- let fetchFailed = false;
25
-
26
- function detectTrigger(text) {
27
- if (!text) return false;
28
- return TRIGGER_PATTERNS.some((pattern) => pattern.test(text));
29
- }
30
-
31
- async function fetchRules() {
32
- // Return cache if available
33
- if (cachedRules !== null) return cachedRules;
34
-
35
- // Don't retry if fetch already failed
36
- if (fetchFailed) return null;
37
-
38
- try {
39
- const response = await fetch(RULES_URL, {
40
- headers: { Accept: 'text/plain' },
41
- });
42
-
43
- if (!response.ok) {
44
- fetchFailed = true;
45
- return null;
46
- }
47
-
48
- const text = await response.text();
49
-
50
- if (!text || text.trim().length === 0) {
51
- fetchFailed = true;
52
- return null;
53
- }
54
-
55
- cachedRules = text;
56
- return cachedRules;
57
- } catch {
58
- fetchFailed = true;
59
- return null;
60
- }
61
- }
62
-
63
- export const AndevRulesPlugin = async (_ctx) => {
64
- let triggerDetected = false;
65
-
66
- return {
67
- // Detect trigger phrase in user messages
68
- 'chat.message': async (_input, output) => {
69
- if (!output || !output.parts) return;
70
-
71
- for (const part of output.parts) {
72
- if (part && part.type === 'text' && part.text) {
73
- if (detectTrigger(part.text)) {
74
- triggerDetected = true;
75
- }
76
- }
77
- }
78
- },
79
-
80
- // Inject rules into system prompt when trigger was detected
81
- 'experimental.chat.system.transform': async (_input, output) => {
82
- if (!output || !Array.isArray(output.system)) return;
83
- if (!triggerDetected) return;
84
-
85
- const rules = await fetchRules();
86
- if (rules) {
87
- output.system.push(`## Rules from andev (follow these rules)\n\n${rules}`);
88
- }
89
-
90
- // Reset trigger for next message
91
- triggerDetected = false;
92
- },
93
- };
94
- };
95
-
96
- export default AndevRulesPlugin;