一、为什么需要 Rules
不加任何约束让 AI 生成代码,会遇到这些问题:
- 同一个 Prompt,两次生成用的 ORM 不一样(一次 Prisma,一次 TypeORM)
- 有时候加 Swagger 注解,有时候不加
- 返回格式随机,有时候是原始对象,有时候有封装
- 命名风格不统一,同一个项目里 camelCase 和 snake_case 混用
根本原因:AI 不知道你的项目规矩是什么,每次都在"猜"你想要什么。
.cursorrules 就是解决这个问题的:把你的项目规范写成文件,AI 每次对话都会自动遵守。
二、.cursorrules 的工作原理
.cursorrules 是放在项目根目录的一个文本文件。
每次你在这个项目里和 Cursor AI 对话,Cursor 会自动把 .cursorrules 的内容附加到对话的系统提示词里。
效果等同于:你每次对话前,都先把这份规范念给 AI 听。
你的 Prompt
+
.cursorrules 的内容(自动附加)
↓
AI 的回答(基于两者的综合理解)
类比:没有 Rules = 问刚入职的新员工;有了 Rules = 问熟悉项目规范的老同事。
三、.cursorrules 的内容结构
一份完整的 .cursorrules 文件建议包含以下几个部分:
3.1 技术栈声明
明确告诉 AI 这个项目用什么,不用什么:
## 技术栈
- 框架:NestJS 10+
- ORM:Prisma(不使用 TypeORM)
- 语言:TypeScript,strict 模式
- 数据库:PostgreSQL
为什么要写"不使用 TypeORM"?
只写"使用 Prisma"有时候不够,AI 训练数据里 TypeORM 比重很大,偶尔还是会在某些地方插入 TypeORM 的写法。加上"不使用"的明确否定,才能真正断掉这种可能。
3.2 命名规范
## 命名规范
- 文件名:kebab-case(user-service.ts)
- 类名:PascalCase(UserService)
- 方法名和变量:camelCase(findAll、userId)
- 常量:SCREAMING_SNAKE_CASE(MAX_RETRY_COUNT)
- 数据库字段:snake_case(created_at)
3.3 接口规范(给具体示例比只给描述效果好)
## 接口返回格式
所有接口必须返回以下格式,不允许直接返回原始数据:
{
"code": 0,
"data": any,
"message": "ok"
}
## Controller 规范
- 每个 Controller 必须有 @ApiTags('模块名')
- 每个接口必须有 @ApiOperation({ summary: '接口描述' })
3.4 禁止项
## 不要做的事情
- 不要生成 .spec.ts 测试文件
- 不要使用 any 类型
- 不要硬编码配置值,使用 ConfigService
- 不要在 Service 层引用 Request、Response 对象
- 不要用 console.log,用 NestJS 内置的 Logger
禁止项是 Rules 里最有价值的部分之一。 AI 有很多"默认行为",如果不明确禁止,它会悄悄做你不想要的事情。
四、Rules 文件的长度控制
Rules 文件不是越长越好。
原因:AI 的上下文窗口有 Token 上限。Rules 文件过长(超过约 2000 字)时,AI 优先处理前面的内容,后面的规则可能被忽略。
建议:
- 第一版 Rules 只写最核心的 10~15 条约束
- 每条规则用一句话说清楚,不要展开大段说明
- 后续发现 AI 又犯了某个问题,再追加对应的约束
五、Context 注入:@file、@folder、@docs
Rules 是"全局约束",在整个项目范围内生效。Context 注入是在单次对话里"临时引入参考资料"。
5.1 @file:引用单个文件
在 Chat 输入框里输入 @,弹出文件选择器,选中文件后 AI 会读取该文件内容。
适用场景:
- 让 AI 根据已有的 Entity 生成对应的 DTO
- 让 AI 参考某个文件的写法风格
- 让 AI 分析某个具体文件的问题
@user.entity.ts 根据这个 Entity,生成对应的 CreateUserDto 和 UpdateUserDto
5.2 @folder:引用整个目录
@src/modules/user 分析这个模块的代码,有哪些可以改进的地方?
适用场景:
- 分析整个模块的代码质量
- 生成代码时需要参考模块的整体结构
5.3 @docs:引用外部文档
@docs https://docs.nestjs.com/interceptors
帮我实现一个全局的响应格式拦截器,把所有返回值包装成 ResponseDto 格式
适用场景:
- 参考官方文档实现某个功能
- 引用项目里的 PRD 文档、API 设计文档
六、.cursorignore 文件
.cursorignore 的作用和 .gitignore 类似,控制 Cursor AI 扫描代码仓库时哪些文件和目录要排除。
为什么需要 .cursorignore
node_modules有几万个文件,AI 扫描会浪费 Token,还会占满上下文窗口.env里有密码和 API Key,不应该进入 AI 上下文dist/、coverage/是编译产物和测试报告,不是源码,AI 分析没有意义
标准配置
node_modules/
dist/
.env
.env.local
*.log
coverage/
prisma/migrations/
七、演示操作步骤
准备工作
Step 1:新建演示项目
npx @nestjs/cli new rules-demo
cd rules-demo
npm install
Step 2:用 Cursor 打开项目
cursor rules-demo
Step 3:提前截好"没有 Rules"时的 AI 输出截图(用于对比)
在 Chat 里输入以下 Prompt,截图保存 AI 的输出:
帮我创建一个用户注册接口
记录 AI 用了什么 ORM、有没有加 Swagger 注解、返回格式是什么。
创建并验证 .cursorrules
Step 1:在项目根目录创建 .cursorrules 文件
touch .cursorrules
Step 2:用 Cursor 打开 .cursorrules,写入以下内容:
# 技术栈
- 框架:NestJS 10+
- ORM:Prisma(不使用 TypeORM,不使用 Mongoose)
- 语言:TypeScript,strict 模式
- 数据库:PostgreSQL
# 命名规范
- 文件名:kebab-case(user-service.ts)
- 类名:PascalCase(UserService)
- 方法名和变量:camelCase(findAll、userId)
- 数据库字段:snake_case(created_at)
# 接口规范
- 所有 Controller 必须有 @ApiTags 和 @ApiOperation
- 所有接口返回格式:{ code: number, data: any, message: string }
- 路径用 kebab-case(/api/user-profile)
# 错误处理
- 使用 NestJS 内置异常类(NotFoundException、BadRequestException 等)
- 不允许空的 catch 块
# 不要做的事情
- 不要生成 .spec.ts 测试文件
- 不要使用 any 类型
- 不要硬编码配置值,使用 ConfigService
- 不要用 console.log
Step 3:保存文件,在 Chat 里点「+」新建一个 Chat(必须新建,旧 Chat 不会读取新加的 Rules)
Step 4:在新 Chat 里输入和之前完全相同的 Prompt:
帮我创建一个用户注册接口
Step 5:对比两次的输出,检查以下几点:
| 检查项 | 没有 Rules 时 | 有 Rules 后 |
|---|---|---|
| ORM | 随机(TypeORM 或 Prisma) | 必须是 Prisma |
| Swagger 注解 | 随机 | 必须有 |
| 返回格式 | 随机 | 必须是 ResponseDto |
| 有无测试文件 | 可能有 | 不应该有 |
| 有无 any 类型 | 可能有 | 不应该有 |
演示 @file Context 注入
Step 1:在 src/ 下新建 user/user.entity.ts:
import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn } from 'typeorm';
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column({ unique: true })
username: string;
@Column({ unique: true })
email: string;
@Column()
password: string;
@CreateDateColumn()
createdAt: Date;
}
Step 2:在 Chat 里引用这个文件:
@user.entity.ts 根据这个 Entity 的字段,生成 CreateUserDto 和 UpdateUserDto,
用 class-validator 做参数校验
Step 3:观察 AI 生成的 DTO 字段是否和 Entity 完全对应。
演示 @folder Context 注入
Step 1:确保 src/user/ 目录下有几个文件(entity、service、controller 等)
Step 2:在 Chat 里输入:
@src/user 分析这个模块的代码,告诉我:
1. 有哪些不符合 NestJS 最佳实践的地方
2. 有哪些不符合当前项目 Rules 规范的地方
Step 3:观察 AI 是否能识别出模块级别的问题,而不只是单个文件的问题。
创建 .cursorignore
Step 1:在项目根目录创建 .cursorignore 文件
touch .cursorignore
Step 2:写入以下内容:
node_modules/
dist/
.env
.env.local
*.log
coverage/
prisma/migrations/
Step 3:验证效果——在 Chat 里输入:
@node_modules 这个目录里有什么?
如果 .cursorignore 生效,AI 会告诉你它无法访问这个目录,或者返回空内容。
Spec Coding 实战补充:04 Cursor Rules 与 Context
来源:
Spec Coding实战/04 Cursor Rules 与 Context.md,已合并到本章节。
1. Rules 是什么,为什么需要它
你有没有遇到过这种情况:
- 让 AI 写代码,有时候用分号,有时候不用
- 接口返回格式,AI 每次设计的结构都不一样
- 明明项目用的是 Prisma,AI 生成了 TypeORM 的代码
- 异常处理,有时候 throw HttpException,有时候 throw new Error
这些问题的根源是同一个:AI 不知道你的项目约定是什么。
每次对话,AI 从零开始,没有记忆,没有偏好,它只能猜。
.cursorrules 就是解决这个问题的机制:一份放在项目根目录的配置文件,告诉 AI 这个项目的所有约定,每次对话自动注入,不需要你反复说明。
2. Rules 的作用机制
Cursor 在每次调用 AI 时,会把 .cursorrules 的内容作为系统提示词(System Prompt)自动附加到请求里。
[你的 .cursorrules 内容]
+ [你在 Chat 里说的话]
→ AI 收到的完整输入
所以 .cursorrules 里写的东西,等于你在每次对话开头都说了一遍。AI 在整个项目开发过程中,始终带着这些约束工作。
3. Rules 文件的两种形式
形式一:.cursorrules(旧版,仍可用)
放在项目根目录,全局生效。
my-project/
├── .cursorrules
├── src/
└── ...
形式二:.cursor/rules/(新版,推荐)
Cursor 新版本支持按目录或按场景分拆 Rules,更灵活:
my-project/
├── .cursor/
│ └── rules/
│ ├── global.mdc # 全局规则
│ ├── backend.mdc # 后端规则(NestJS/Prisma)
│ ├── frontend.mdc # 前端规则(Vue/React)
│ └── testing.mdc # 测试规则
├── src/
└── ...
.mdc 格式支持 markdown 内容,可读性更好。你可以在 Cursor 的 Rules 面板里直接编辑和管理。
本节示例统一用 **.cursorrules** 文件(兼容性最好)
4. .cursorrules 示例(NestJS 项目)
# 项目技术栈
- Runtime: Node.js 20+
- Framework: NestJS 10
- Language: TypeScript 5.x(严格模式)
- ORM: Prisma 5(禁止使用 TypeORM)
- Database: PostgreSQL 16
- 日志: Pino(禁止使用 console.log)
# 代码风格
- 格式化工具: Prettier
- 无分号(semi: false)
- 单引号(singleQuote: true)
- 2 空格缩进
- 行尾 LF(不用 CRLF)
- 每行最大 100 字符
# 接口规范
所有接口返回统一的 Result 格式:
```typescript
interface Result<T = any> {
code: number // 0 表示成功,非 0 表示失败
message: string
data: T | null
}
```text
使用 ResultHelper 辅助类:
```typescript
// 成功
return ResultHelper.ok(data)
// 失败
throw new BusinessException(ErrorCode.USER_NOT_FOUND)
```text
禁止在 Controller 层直接 throw HttpException,统一通过 BusinessException + 全局 Filter 处理。
# 目录结构
```plain
src/
├── common/ # 公共基础设施
│ ├── decorators/
│ ├── filters/ # 全局异常过滤器
│ ├── guards/
│ ├── interceptors/
│ └── pipes/
├── config/ # 配置模块
├── modules/ # 业务模块
│ └── [module-name]/
│ ├── [module].controller.ts
│ ├── [module].service.ts
│ ├── [module].module.ts
│ └── dto/
│ ├── create-[module].dto.ts
│ ├── update-[module].dto.ts
│ └── query-[module].dto.ts
└── prisma/ # Prisma 相关
```text
# 命名约定
+ 文件名: kebab-case(user-profile.service.ts)
+ 类名: PascalCase(UserProfileService)
+ 变量/函数: camelCase
+ 常量: UPPER_SNAKE_CASE
+ Prisma 模型字段: camelCase
+ 数据库列名: snake_case(通过 @map 映射)
# DTO 规范
+ 所有 DTO 使用 class-validator 装饰器进行校验
+ 创建 DTO:所有必填字段都要加 @IsNotEmpty() + 具体类型装饰器
+ 更新 DTO:继承 PartialType(CreateDto),所有字段可选
+ 查询 DTO:继承 PageQueryDto,分页参数已内置
```typescript
// 示例:创建用户 DTO
export class CreateUserDto {
@IsEmail()
@IsNotEmpty()
email: string
@IsString()
@MinLength(8)
@MaxLength(32)
password: string
@IsString()
@MinLength(2)
@MaxLength(50)
name: string
}
```text
# Prisma 使用规范
+ 所有查询必须处理 `isDeleted: false` 过滤(软删除)
+ 不允许直接暴露 Prisma 模型作为接口返回类型,必须用 DTO/VO 映射
+ 分页查询统一使用 `findMany + count` 的方式,不用游标分页
+ 敏感字段(password 等)必须在 select 或 omit 中明确排除
# 日志规范
+ 使用 Pino logger,通过 DI 注入
+ 格式:`this.logger.info({ userId, action }, 'User created')`(结构化日志)
+ 禁止在 service 层打 debug 无关的业务信息日志
# 安全规范
+ 所有需要认证的接口加 @UseGuards(JwtAuthGuard)
+ 密码必须 bcrypt 加密(rounds=10),禁止 MD5/SHA1
+ 禁止在日志中打印密码、token 等敏感信息
+ 禁止在代码中硬编码任何密钥、密码、API Key
# 生成代码的要求
+ 生成代码时不需要解释,直接给完整代码
+ 遇到类型不确定的地方,用 TypeScript 严格类型,不允许用 any
+ 生成的代码要可以直接运行,不留 TODO 占位
+ 如果某个功能需要额外配置或环境变量,在代码注释里说明,不要遗漏
```plain
---
### 5. .cursorignore
`.cursorignore` 和 `.gitignore` 的语法一样,告诉 Cursor 哪些文件/目录不要纳入上下文索引。
```gitignore
# 构建产物
dist/
build/
# 依赖
node_modules/
# 测试覆盖率
coverage/
# 环境变量(含敏感信息)
.env
.env.local
.env.production
# 日志文件
*.log
logs/
# 大文件,不需要 AI 读
*.pdf
*.zip
prisma/migrations/ # 迁移文件太多,干扰上下文
为什么要设置 .cursorignore?
Cursor 建立项目索引时,会把文件内容向量化存储,用于 @Codebase 搜索和 Agent 的文件感知。如果 node_modules(几万个文件)被索引进去,会:
- 拖慢索引速度
- 稀释有效上下文(AI 搜索代码时会被无关结果淹没)
- 可能暴露
.env里的敏感信息
6. Context 管理:让 AI 知道更多
除了 .cursorrules 的被动注入,你还可以在对话时主动注入上下文。
6.1 @ 引用的几种类型
@文件:引用具体文件内容
@src/common/filters/http-exception.filter.ts
帮我看看这个 filter 的写法,然后按相同风格写一个处理 Prisma 错误的 filter
@文件夹:引用整个目录(AI 会感知目录结构)
参考 @src/modules/product/ 的模块结构,
帮我在 src/modules/order/ 创建同样结构的订单模块
@代码符号:引用类、函数、接口
@UserService.findAll 这个方法有 N+1 查询问题,帮我优化
@Codebase:让 AI 在整个项目里搜索
@Codebase 项目里所有使用了 bcrypt.hash 的地方在哪?
我要统一改成从配置读取 rounds 数
@Docs:引用你添加的外部文档
@NestJS Docs 帮我查一下 NestJS 的 Global Pipes 怎么配置
@Git:引用 git 历史
@Git 最近 3 次提交做了哪些改动?帮我写一份变更说明
6.2 上下文的优先级
当 .cursorrules 的约定和你在对话里说的话冲突时,对话里的说法优先。
这个机制很有用:.cursorrules 定全局默认值,某次任务需要例外时,直接在对话里覆盖:
这次只做一个快速验证的脚本,可以用 console.log,不需要 Pino
7. 理解 Context Window 的限制
Cursor 调用 AI 模型时,上下文长度是有限的(Claude 3.5 Sonnet 约 200K tokens,GPT-4o 约 128K tokens)。
实际意义
你的 .cursorrules + 你引用的所有文件 + 对话历史,加在一起不能超过这个限制。
常见的上下文溢出症状
- AI 开始"忘记"你之前说过的东西
- 生成的代码违反了 .cursorrules 里的约定
- 长对话后,AI 开始产生幻觉(引用不存在的函数、错误的导入路径)
应对策略
.cursorrules不要太长,把真正重要的约定放进去,细节放 spec 里按需引用- 大任务拆成小任务,每个小任务开一个新对话
- 不要在一个对话里一直堆需求,每完成一个独立功能就开新对话
8. Rules 的团队协作
.cursorrules 要提交到 git 仓库,让整个团队共享同一套 AI 工作规范。
git add .cursorrules
git commit -m "feat: add cursorrules for team AI coding standards"
团队使用建议
- 由技术负责人维护
.cursorrules,避免每个人随意修改 - 规则更新时,在 commit message 里写清楚改了什么、为什么改
- 个人的临时偏好,通过对话临时覆盖,不要写进团队 rules
9. 一个完整的项目 .cursorrules 结构建议
# ===== 技术栈 =====
[基础技术选型,明确"用什么"和"不用什么"]
# ===== 代码风格 =====
[Prettier 配置摘要,命名约定]
# ===== 架构约定 =====
[目录结构,分层职责]
# ===== 接口规范 =====
[请求/响应格式,错误处理方式]
# ===== 数据层规范 =====
[ORM 使用规范,字段命名]
# ===== 安全规范 =====
[认证、加密、敏感信息处理]
# ===== AI 生成代码要求 =====
[告诉 AI 生成代码时的行为规范]
保持在 300-500 行以内。太长的 rules 会浪费 context window,而且 AI 对后半段的遵守情况会变差。
10. 小结
.cursorrules 是你在 Cursor 里最重要的一次性投入:花 1-2 小时写好项目规则,之后所有的 AI 交互都在这个规则约束下进行。
它解决的核心问题:让 AI 每次都知道你的项目是什么、规范是什么、禁止做什么——而不是靠你每次对话重新交代。
配合 .cursorignore 管理项目索引,配合 @引用 按需注入具体上下文,这三个工具加在一起,构成了 Cursor 工作流的完整上下文管理体系。