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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 thaiannguyen-05
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,56 +1,66 @@
1
- # @thaiannguyen-05/opencode-rules
1
+ # opencode-andev-rules
2
2
 
3
- Global coding rules for TypeScript & NestJS. Plugin for opencode.
3
+ Global coding rules for TypeScript & NestJS + AI agent skills.
4
4
 
5
- ## Cài Plugin
5
+ [![CI](https://github.com/thaiannguyen-05/rules-for-global-agents/actions/workflows/ci.yml/badge.svg)](https://github.com/thaiannguyen-05/rules-for-global-agents/actions/workflows/ci.yml)
6
+ [![npm](https://img.shields.io/npm/v/opencode-andev-rules)](https://www.npmjs.com/package/opencode-andev-rules)
6
7
 
7
- ```bash
8
- opencode plugin @thaiannguyen-05/opencode-rules
9
- ```
10
-
11
- hoặc:
12
-
13
- ```bash
14
- cd ~/.config/opencode
15
- npm install @thaiannguyen-05/opencode-rules
16
- ```
17
-
18
- Thêm vào `opencode.json`:
8
+ ## Cài đặt
19
9
 
20
- ```json
21
- {
22
- "plugins": ["@thaiannguyen-05/opencode-rules"]
23
- }
24
- ```
25
-
26
- ## Cài Skill
10
+ ### Cách 1 — npm (khuyên dùng)
27
11
 
28
12
  ```bash
29
- npx @thaiannguyen-05/skills-cli add improve-codenase-systems
13
+ npm install opencode-andev-rules
14
+ # skills nằm ở node_modules/opencode-andev-rules/skills/
30
15
  ```
31
16
 
32
- hoặc:
17
+ ### Cách 2 — copy thủ công
33
18
 
34
19
  ```bash
35
- cp -r skills/improve-codenase-systems ~/.agents/skills/
20
+ cp -r skills/enhance/improve-codenase-systems ~/.agents/skills/
21
+ cp -r skills/backend/nestJS ~/.agents/skills/
22
+ cp -r skills/infrastructure/prisma ~/.agents/skills/
36
23
  ```
37
24
 
38
- ## Sử dụng
39
-
40
- ### Rules
25
+ ## Skills
41
26
 
42
- Nói "follow the rule of andev" trong prompt.
27
+ | Skill | Đường dẫn | Dùng khi |
28
+ |-------|-----------|----------|
29
+ | `improve-codenase-systems` | `skills/enhance/improve-codenase-systems/` | Muốn scan codebase tìm vấn đề kiến trúc — nói "scan codebase" hoặc "improve codebase" trong prompt |
30
+ | `nestjs` | `skills/backend/nestJS/` | Viết/review code NestJS — module structure, Fastify, zod validation, DI |
31
+ | `prisma` | `skills/infrastructure/prisma/` | Viết/review Prisma — client singleton, queries, transactions, migrations |
43
32
 
44
- ### Skill
33
+ Chi tiết từng skill xem file `SKILL.md` trong thư mục tương ứng. Danh mục đầy đủ: `skills/registry.json`.
45
34
 
46
- Nói "scan codebase" hoặc "improve codebase" trong prompt.
35
+ ## Cấu trúc repo
47
36
 
48
- ## Rules
37
+ ```
38
+ skills/
39
+ ├── backend/nestJS/ # rules NestJS
40
+ │ ├── SKILL.md
41
+ │ └── references/nestjs-module.md
42
+ ├── infrastructure/prisma/ # rules Prisma
43
+ │ ├── SKILL.md
44
+ │ └── references/
45
+ ├── enhance/improve-codenase-systems/ # skill scan kiến trúc
46
+ │ ├── SKILL.md
47
+ │ ├── HTML-REPORT.md
48
+ │ ├── meta.json
49
+ │ └── agents/openai.yaml
50
+ └── registry.json # danh mục skills (version top-level sync với package.json)
51
+ scripts/
52
+ ├── validate.sh # kiểm tra frontmatter SKILL.md + registry + npm pack --dry-run
53
+ ├── check-version.sh # kiểm tra version chưa tồn tại trên npm
54
+ └── release.sh # bump version an toàn (xem dưới)
55
+ .github/workflows/
56
+ ├── ci.yml # 3 jobs async song song (frontmatter/registry/pack) trên mỗi push/PR
57
+ └── publish.yml # nút Run workflow: tự bump version + publish lên npm
58
+ ```
49
59
 
50
- Xem `RULES.md` — covers TypeScript, NestJS, validation, error handling, database, security, API design, testing, and code style.
60
+ ## Đóng góp
51
61
 
52
- ## Skills
62
+ 1. Sửa/thêm skill trong `skills/`, mỗi skill phải có `SKILL.md` với frontmatter `name:` + `description:`.
63
+ 2. Khai báo skill mới vào `skills/registry.json`.
64
+ 3. Chạy `npm run validate` trước khi push.
65
+ 4. **Không sửa version bằng tay** — version do workflow publish tự tăng (xem dưới).
53
66
 
54
- | Skill | Description |
55
- |-------|-------------|
56
- | `improve-codenase-systems` | Scan codebase for architecture issues |
package/package.json CHANGED
@@ -1,25 +1,39 @@
1
1
  {
2
2
  "name": "opencode-andev-rules",
3
- "version": "1.0.0",
4
- "description": "On-demand rule injection plugin for opencode. Say 'follow the rule of andev' to load rules from GitHub.",
5
- "type": "module",
6
- "main": "plugin.js",
7
- "exports": {
8
- ".": "./plugin.js"
3
+ "version": "1.0.2",
4
+ "description": "Global coding rules for TypeScript & NestJS + AI agent skills.",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/thaiannguyen-05/rules-for-global-agents.git"
8
+ },
9
+ "bugs": {
10
+ "url": "https://github.com/thaiannguyen-05/rules-for-global-agents/issues"
11
+ },
12
+ "homepage": "https://github.com/thaiannguyen-05/rules-for-global-agents#readme",
13
+ "publishConfig": {
14
+ "access": "public",
15
+ "provenance": true
16
+ },
17
+ "files": [
18
+ "skills/",
19
+ "README.md",
20
+ "LICENSE"
21
+ ],
22
+ "scripts": {
23
+ "validate": "bash scripts/validate.sh",
24
+ "check-version": "bash scripts/check-version.sh",
25
+ "release": "bash scripts/release.sh",
26
+ "pack": "npm pack --dry-run"
9
27
  },
10
28
  "keywords": [
11
- "opencode",
12
- "opencode-plugin",
13
29
  "rules",
14
30
  "nestjs",
15
- "typescript"
31
+ "typescript",
32
+ "skills"
16
33
  ],
17
34
  "author": "thaiannguyen-05",
18
35
  "license": "MIT",
19
- "peerDependencies": {
20
- "@opencode-ai/plugin": ">=1.15.0"
21
- },
22
- "devDependencies": {
23
- "@opencode-ai/plugin": "1.17.11"
36
+ "engines": {
37
+ "node": ">=18"
24
38
  }
25
39
  }
@@ -0,0 +1,255 @@
1
+ ---
2
+ name: nestjs
3
+ description: NestJS backend rules — Fastify platform, module structure, zod validation. Use when building or reviewing NestJS code.
4
+ ---
5
+ # NestJS Architecture
6
+
7
+ ## Purpose
8
+ Structure NestJS applications with module-based architecture — domain modules, shared modules, dynamic modules, monorepo projects, and provider scoping.
9
+
10
+ ## Agent Protocol
11
+
12
+ ### Trigger
13
+ User request includes: `NestJS module structure`, `NestJS architecture`, `NestJS project layout`, `NestJS DI`, `NestJS modules`, `NestJS monorepo`, `NestJS providers`.
14
+
15
+ ### Input Context
16
+ - NestJS version (10+)
17
+ - Architecture style (modular, monorepo, microservices)
18
+ - Database (TypeORM, Prisma, MikroORM, Mongoose)
19
+ - Transport (HTTP, WebSocket, TCP, RabbitMQ)
20
+
21
+ ### Output Artifact
22
+ Module structure, provider registration, module exports, monorepo layout, DI setup.
23
+
24
+ ### Response Format
25
+ Produce artifact directly. No preamble, no postamble, no explanations.
26
+
27
+ ### Completion Criteria
28
+ - Root module imports feature modules
29
+ - Each domain has its own module with controllers, services, repositories
30
+ - Modules export only what other modules need
31
+ - Global modules registered sparingly (shared infra only)
32
+ - Dynamic modules configured with forRoot/forFeature
33
+
34
+ ### Max Response Length
35
+ 4096 tokens
36
+
37
+ # NestJS Skill
38
+
39
+ ## Platform
40
+
41
+ - Use Fastify — never Express.
42
+ - Bootstrap with `FastifyAdapter` typed as `NestFastifyApplication` — never `NestFactory.create()` with the default Express platform.
43
+ - Pass `{ trustProxy: true }` so client IPs stay correct behind a reverse proxy.
44
+ - Never use Express-specific APIs (`@Res()` passthrough, `express-*` middleware) — use Fastify equivalents and `app.register()` plugins.
45
+ - Configure CORS, body-parser limits, and global prefix on the Fastify instance in `main.ts`.
46
+
47
+ ```typescript
48
+ // main.ts
49
+ import { NestFactory } from '@nestjs/core';
50
+ import {
51
+ FastifyAdapter,
52
+ NestFastifyApplication,
53
+ } from '@nestjs/platform-fastify';
54
+
55
+ const app = await NestFactory.create<NestFastifyApplication>(
56
+ AppModule,
57
+ new FastifyAdapter({ trustProxy: true }),
58
+ );
59
+ ```
60
+
61
+ ## common/guards
62
+
63
+ - Put only shared guards in `src/common/guards/` — auth, ownership checks reused across features.
64
+ - Feature-specific guards belong in `module/{features}/guards/` — never in `common`.
65
+ - Guards decide access only — no business logic, no DB writes inside guards.
66
+ - Extract request props into explicitly typed locals (e.g., `const user: User | undefined = req.user`) — never branch on `req.*` inline; untyped access is unsafe.
67
+ - Apply via `@UseGuards()` — never call guards manually in controllers or services.
68
+
69
+ ```typescript
70
+ // common/guards/auth.guard.ts
71
+ import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common';
72
+ import { Reflector } from '@nestjs/core';
73
+ import { FastifyRequest } from 'fastify';
74
+ import { User } from '../type';
75
+ import { IS_PUBLIC_KEY } from '../decorator/public.decorator';
76
+
77
+ @Injectable()
78
+ export class AuthGuard implements CanActivate {
79
+ constructor(private readonly reflector: Reflector) {}
80
+
81
+ canActivate(context: ExecutionContext): boolean {
82
+ const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
83
+ context.getHandler(),
84
+ context.getClass(),
85
+ ]);
86
+ if (isPublic) return true;
87
+ const req = context.switchToHttp().getRequest<FastifyRequest>();
88
+ const user: User | undefined = req.user;
89
+ if (!user) throw new UnauthorizedException();
90
+ return true;
91
+ }
92
+ }
93
+
94
+ // usage: @UseGuards(AuthGuard)
95
+ ```
96
+
97
+ ## common/interceptor
98
+
99
+ - Put only shared interceptors in `src/common/interceptor/` — logging, transformation, timeout.
100
+ - Never put business logic inside interceptors.
101
+ - Keep interceptors thin — one responsibility per interceptor.
102
+
103
+ ```typescript
104
+ // common/interceptor/logging.interceptor.ts
105
+ import { CallHandler, ExecutionContext, Injectable, Logger, NestInterceptor } from '@nestjs/common';
106
+ import { FastifyRequest } from 'fastify';
107
+ import { Observable, tap } from 'rxjs';
108
+
109
+ @Injectable()
110
+ export class LoggingInterceptor implements NestInterceptor {
111
+ private readonly logger = new Logger('HTTP');
112
+
113
+ intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
114
+ const req = context.switchToHttp().getRequest<FastifyRequest>();
115
+ const start = Date.now();
116
+ return next.handle().pipe(
117
+ tap(() => this.logger.log(`${req.method} ${req.url} ${Date.now() - start}ms`)),
118
+ );
119
+ }
120
+ }
121
+ ```
122
+
123
+ ## common/decorator
124
+
125
+ - Put only shared decorators in `src/common/decorator/` — param decorators, metadata helpers.
126
+ - Mark public routes with `@Public()` — AuthGuard skips them via Reflector; never bypass the guard manually.
127
+ - Never put validation logic inside decorators that belongs in `dto/`.
128
+ - Never put business logic inside decorators.
129
+
130
+ ```typescript
131
+ // common/decorator/current-user.decorator.ts
132
+ import { createParamDecorator, ExecutionContext } from '@nestjs/common';
133
+ import { FastifyRequest } from 'fastify';
134
+ import { User } from '../type';
135
+
136
+ export const CurrentUser = createParamDecorator(
137
+ (key: keyof User | undefined, ctx: ExecutionContext) => {
138
+ const req = ctx.switchToHttp().getRequest<FastifyRequest>();
139
+ const user: User | undefined = req.user;
140
+ return key ? user?.[key] : user;
141
+ },
142
+ );
143
+
144
+ // usage: handler(@CurrentUser() user) or handler(@CurrentUser('id') id: string)
145
+ ```
146
+
147
+ ```typescript
148
+ // common/decorator/public.decorator.ts
149
+ import { SetMetadata } from '@nestjs/common';
150
+
151
+ export const IS_PUBLIC_KEY = 'isPublic';
152
+ export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);
153
+
154
+ // usage: @Public() on a controller or route handler to skip AuthGuard
155
+ ```
156
+
157
+ ## common/exception
158
+
159
+ - All exceptions and filters live in `src/common/exception/` — replaces the old `src/error/` location.
160
+ - Define custom `AppException` classes here — never throw raw `Error` for domain failures.
161
+ - `errorCode` = uppercase snake_case (e.g., `RESOURCE_NOT_FOUND`).
162
+ - Always return consistent JSON: `{ statusCode, timestamp, path, message, errorCode?, details? }`.
163
+ - Log unhandled (non-HttpException) errors with logger — never leak internal details to responses.
164
+ - Feature helpers (e.g., `[domain]-error.helper.ts`) live here, not inside features.
165
+
166
+ ```typescript
167
+ // common/exception/app.exception.ts
168
+ import { HttpException, HttpStatus } from '@nestjs/common';
169
+
170
+ export class AppException extends HttpException {
171
+ constructor(
172
+ message: string,
173
+ statusCode: HttpStatus,
174
+ public readonly errorCode?: string,
175
+ public readonly details?: unknown,
176
+ ) {
177
+ super({ message, errorCode, details, timestamp: new Date().toISOString() }, statusCode);
178
+ }
179
+ }
180
+
181
+ export class ResourceNotFoundException extends AppException {
182
+ constructor(resourceType: string, identifier: string | number) {
183
+ super(
184
+ `${resourceType} with identifier ${identifier} not found`,
185
+ HttpStatus.NOT_FOUND,
186
+ 'RESOURCE_NOT_FOUND',
187
+ );
188
+ }
189
+ }
190
+ ```
191
+
192
+ ```typescript
193
+ // common/exception/global-exception.filter.ts
194
+ import { ArgumentsHost, Catch, ExceptionFilter, HttpException, Logger } from '@nestjs/common';
195
+ import { FastifyReply } from 'fastify';
196
+
197
+ @Catch()
198
+ export class GlobalExceptionFilter implements ExceptionFilter {
199
+ private readonly logger = new Logger('Exception');
200
+
201
+ catch(exception: unknown, host: ArgumentsHost) {
202
+ const reply = host.switchToHttp().getResponse<FastifyReply>();
203
+ if (exception instanceof HttpException) {
204
+ return reply.status(exception.getStatus()).send(exception.getResponse());
205
+ }
206
+ this.logger.error(exception);
207
+ return reply.status(500).send({
208
+ statusCode: 500,
209
+ message: 'Internal server error',
210
+ timestamp: new Date().toISOString(),
211
+ });
212
+ }
213
+ }
214
+
215
+ // main.ts: app.useGlobalFilters(new GlobalExceptionFilter());
216
+ ```
217
+
218
+ ## common/middleware
219
+
220
+ - Put only HTTP plumbing in `src/common/middleware/` — req/res preprocessing, correlation IDs.
221
+ - Write them as Fastify plugins/hooks registered via `app.register()` — never Express-style `(req, res, next)` middleware.
222
+ - Never put business logic inside middleware.
223
+ - Prefer guards/interceptors over middleware when the concern fits them.
224
+
225
+ ```typescript
226
+ // common/middleware/correlation-id.plugin.ts
227
+ import { FastifyInstance } from 'fastify';
228
+ import { randomUUID } from 'node:crypto';
229
+
230
+ export async function correlationIdPlugin(app: FastifyInstance) {
231
+ app.addHook('onRequest', async (req) => {
232
+ req.headers['x-correlation-id'] ??= randomUUID();
233
+ });
234
+ }
235
+
236
+ // main.ts: await app.register(correlationIdPlugin);
237
+ ```
238
+
239
+ ## module/infrastructures (abstract)
240
+
241
+ - `module/infrastructures/` holds tech adapters only — no business logic, no domain types.
242
+ - Subfolders below are examples, not mandates — never hardcode the tech choice:
243
+ - `TypeOrm/` = example relational persistence adapter (entities, repositories, migrations glue).
244
+ - `message/` = example messaging adapter (producers, consumers, topic/queue names).
245
+ - Name a subfolder after the adapter role — one folder per external system.
246
+ - Adapters implement interfaces owned by features — features depend on abstractions, not on adapter internals.
247
+ - Never import `module/{features}/` code into `infrastructures/` — dependency points inward to the abstraction.
248
+ - Never let controllers or services import adapter internals directly — go through DI providers/tokens.
249
+ - Never share state between adapters — no cross-imports between `TypeOrm/` and `message/`.
250
+ - Write adapter code only after the tech is chosen — no placeholder implementations before the decision.
251
+
252
+ ## References
253
+
254
+ - Feature slice layout, types, guards, dto, service, controller rules: `references/nestjs-module.md`.
255
+ - Unit, integration, E2E testing strategies, TestContainers, mocking: `references/nestjs-testing.md`.
@@ -0,0 +1,212 @@
1
+ # NestJS Module Structure Rules
2
+
3
+ ## Layout
4
+
5
+ - Follow this tree exactly — no extra nesting, no renamed folders.
6
+ - `src/common/` holds shared code only — never feature-specific code.
7
+ - `src/module/` holds `infrastructures/` + one folder per feature.
8
+
9
+ ```
10
+ src/
11
+ ├── common/
12
+ │ ├── guards/
13
+ │ ├── interceptor/
14
+ │ ├── decorator/
15
+ │ ├── exception/
16
+ │ ├── middleware/
17
+ │ └── type.ts
18
+ └── module/
19
+ ├── infrastructures/
20
+ │ ├── TypeOrm/
21
+ │ └── message/
22
+ └── {features}/
23
+ ├── dto/
24
+ ├── guards/
25
+ ├── type.ts
26
+ ├── index.ts
27
+ ├── {features}.service.ts
28
+ ├── {features}.controller.ts
29
+ └── {features}.module.ts
30
+ ```
31
+
32
+ ## common/type.ts (shared)
33
+
34
+ - Put only cross-feature types in `src/common/type.ts` — enums, interfaces, aliases used by 2+ features.
35
+ - Never put feature-private types in `src/common/type.ts` — they belong in `module/{features}/type.ts`.
36
+ - Never import from `module/{features}/type.ts` into `src/common/type.ts` — `common` never depends on `module/`.
37
+ - Use `enum` for shared constants — no hardcoded strings in business logic.
38
+
39
+ ## Global setup (lives in `../SKILL.md`)
40
+
41
+ - `common/interceptor`, `common/decorator`, `common/exception`, `common/middleware`, and `module/infrastructures/` rules are global — see `../SKILL.md`, not here.
42
+
43
+ ## module/{features}
44
+
45
+ - One folder per feature (e.g., `users/`, `auth/`, `orders/`) — self-contained slice.
46
+ - Never place feature code outside its folder — except shared code in `src/common/` and adapters in `infrastructures/`.
47
+ - Never import deep paths across features (e.g., `../orders/type`) — import via the feature barrel (see `index.ts` below).
48
+
49
+ ### dto/
50
+
51
+ - DTOs are zod schemas only — define `z.object(...)` in `dto/`, export the schema + `z.infer` type, no business logic.
52
+ - Do NOT use `class-validator` decorators or `class-transformer` — zod replaces both (parsing + coercion via `z.coerce`).
53
+ - Do NOT put shared types or constants in `dto/` — shared types belong in `type.ts`, constants in `enum`; derive DTO types via `z.infer`.
54
+ - Validate nested objects with nested schemas — call `.strict()` to reject unknown fields.
55
+
56
+ ```typescript
57
+ // module/users/dto/create-user.dto.ts
58
+ import { z } from 'zod';
59
+
60
+ export const CreateUserSchema = z.object({
61
+ name: z.string().min(1),
62
+ email: z.string().email(),
63
+ address: AddressSchema,
64
+ }).strict();
65
+
66
+ export type CreateUserDto = z.infer<typeof CreateUserSchema>;
67
+ ```
68
+
69
+ ### guards/
70
+
71
+ - Put feature-specific guards here — guards used by one feature only.
72
+ - Promote to `src/common/guards/` the moment a second feature needs it.
73
+ - Same guard rules as global (`../SKILL.md`): access decisions only, applied via `@UseGuards()`.
74
+
75
+ ### type.ts + index.ts (private + barrel)
76
+
77
+ - `module/{features}/type.ts` holds feature-private types — enums, interfaces, return types for this feature only.
78
+ - Every feature must re-export its private types from its own `index.ts` barrel.
79
+ - External features import via the barrel — never via a deep `type.ts` path.
80
+ - Internal files import from `./type` — avoid self-importing through `./index` to prevent cycles.
81
+
82
+ ```typescript
83
+ // module/users/type.ts
84
+ export enum UserRole { ADMIN = 'admin', USER = 'user' }
85
+ export interface User { id: string; name: string; }
86
+ ```
87
+
88
+ ```typescript
89
+ // module/users/index.ts
90
+ export * from './type';
91
+ export * from './users.service';
92
+ export * from './users.module';
93
+ ```
94
+
95
+ ### {features}.service.ts
96
+
97
+ - Services hold business logic only — no HTTP, no req/res, no raw adapter imports.
98
+ - Inject dependencies via constructor — never instantiate manually.
99
+ - Run independent async work in parallel — never `await` sequentially when parallel is possible.
100
+ - Use transactions for multi-step writes — no partial writes, keep scope small, no external calls inside.
101
+
102
+ ```typescript
103
+ // module/users/users.service.ts
104
+ import { Injectable } from '@nestjs/common';
105
+ import type { CreateUserDto } from './dto/create-user.dto';
106
+ import type { User } from './type';
107
+
108
+ @Injectable()
109
+ export class UsersService {
110
+ async create(dto: CreateUserDto): Promise<User> {
111
+ /* business logic only — persistence arrives via DI after the tech decision */
112
+ void dto;
113
+ throw new Error('not implemented');
114
+ }
115
+
116
+ async getById(id: string): Promise<User> {
117
+ /* ... */
118
+ void id;
119
+ throw new Error('not implemented');
120
+ }
121
+ }
122
+ ```
123
+
124
+ ### {features}.controller.ts
125
+
126
+ - Controllers handle HTTP only — routing, status codes, delegation to the service.
127
+ - No business logic, no DB access, no adapter imports in controllers.
128
+ - Controllers and services receive only valid data — all validation happens in `dto/` schemas + global `ZodValidationPipe` before them.
129
+
130
+ ```typescript
131
+ // module/users/users.controller.ts
132
+ import { Body, Controller, Get, Param, Post } from '@nestjs/common';
133
+ import type { CreateUserDto } from './dto/create-user.dto';
134
+ import { UsersService } from './users.service';
135
+
136
+ @Controller('users')
137
+ export class UsersController {
138
+ constructor(private readonly users: UsersService) {}
139
+
140
+ @Post()
141
+ create(@Body() dto: CreateUserDto) {
142
+ return this.users.create(dto);
143
+ }
144
+
145
+ @Get(':id')
146
+ getById(@Param('id') id: string) {
147
+ return this.users.getById(id);
148
+ }
149
+ }
150
+ ```
151
+
152
+ ### {features}.module.ts
153
+
154
+ - Modules wire only — `imports`, `providers`, `controllers`, `exports`, no logic.
155
+ - Register feature providers here; export what other features need via the barrel.
156
+ - Import direction is one-way — the sample below depends on users, never the reverse.
157
+
158
+ ```typescript
159
+ // module/users/users.module.ts
160
+ import { Module } from '@nestjs/common';
161
+ import { UsersController } from './users.controller';
162
+ import { UsersService } from './users.service';
163
+
164
+ @Module({
165
+ controllers: [UsersController],
166
+ providers: [UsersService],
167
+ exports: [UsersService],
168
+ })
169
+ export class UsersModule {}
170
+ ```
171
+
172
+ ```typescript
173
+ // module/orders/orders.module.ts — depends on users, never the reverse
174
+ import { Module } from '@nestjs/common';
175
+ import { UsersModule } from '../users';
176
+ import { OrdersController } from './orders.controller';
177
+ import { OrdersService } from './orders.service';
178
+
179
+ @Module({
180
+ imports: [UsersModule],
181
+ controllers: [OrdersController],
182
+ providers: [OrdersService],
183
+ })
184
+ export class OrdersModule {}
185
+ ```
186
+
187
+ ### Cross-module imports (no circular DI)
188
+
189
+ - Keep the import graph one-way — if A imports B, B never imports A.
190
+ - Code needed by both sides moves to a third module (or `src/common/`) that both import.
191
+ - Import the dependency's barrel (`../users`), never reach into its internals.
192
+ - `forwardRef` only as last resort — wrap both module imports and both injections, and comment why the cycle cannot be broken.
193
+
194
+ ```typescript
195
+ // module/orders/orders.service.ts — consumes the exported provider, no cycle
196
+ import { Injectable } from '@nestjs/common';
197
+ import { UsersService } from '../users';
198
+
199
+ @Injectable()
200
+ export class OrdersService {
201
+ constructor(private readonly users: UsersService) {}
202
+
203
+ async create(userId: string) {
204
+ const user = await this.users.getById(userId);
205
+ /* ... */
206
+ }
207
+ }
208
+
209
+ // last resort only — avoid if possible:
210
+ // @Module({ imports: [forwardRef(() => BModule)] }) on BOTH modules, and
211
+ // constructor(@Inject(forwardRef(() => BService)) ...) on BOTH sides, with a comment why.
212
+ ```