返回笔记首页

第 04 集:Cursor Rules 与 Context

主题配置

一、为什么需要 Rules

不加任何约束让 AI 生成代码,会遇到这些问题:

  • 同一个 Prompt,两次生成用的 ORM 不一样(一次 Prisma,一次 TypeORM)
  • 有时候加 Swagger 注解,有时候不加
  • 返回格式随机,有时候是原始对象,有时候有封装
  • 命名风格不统一,同一个项目里 camelCase 和 snake_case 混用

根本原因:AI 不知道你的项目规矩是什么,每次都在"猜"你想要什么。

.cursorrules 就是解决这个问题的:把你的项目规范写成文件,AI 每次对话都会自动遵守。


二、.cursorrules 的工作原理

.cursorrules 是放在项目根目录的一个文本文件。

每次你在这个项目里和 Cursor AI 对话,Cursor 会自动把 .cursorrules 的内容附加到对话的系统提示词里。

效果等同于:你每次对话前,都先把这份规范念给 AI 听。

plain
你的 Prompt
    +
.cursorrules 的内容(自动附加)
    ↓
AI 的回答(基于两者的综合理解)

类比:没有 Rules = 问刚入职的新员工;有了 Rules = 问熟悉项目规范的老同事。


三、.cursorrules 的内容结构

一份完整的 .cursorrules 文件建议包含以下几个部分:

3.1 技术栈声明

明确告诉 AI 这个项目用什么,不用什么:

plain
## 技术栈
- 框架:NestJS 10+
- ORM:Prisma(不使用 TypeORM)
- 语言:TypeScript,strict 模式
- 数据库:PostgreSQL

为什么要写"不使用 TypeORM"?

只写"使用 Prisma"有时候不够,AI 训练数据里 TypeORM 比重很大,偶尔还是会在某些地方插入 TypeORM 的写法。加上"不使用"的明确否定,才能真正断掉这种可能。

3.2 命名规范

plain
## 命名规范
- 文件名:kebab-case(user-service.ts)
- 类名:PascalCase(UserService)
- 方法名和变量:camelCase(findAll、userId)
- 常量:SCREAMING_SNAKE_CASE(MAX_RETRY_COUNT)
- 数据库字段:snake_case(created_at)

3.3 接口规范(给具体示例比只给描述效果好)

plain
## 接口返回格式
所有接口必须返回以下格式,不允许直接返回原始数据:
{
  "code": 0,
  "data": any,
  "message": "ok"
}

## Controller 规范
- 每个 Controller 必须有 @ApiTags('模块名')
- 每个接口必须有 @ApiOperation({ summary: '接口描述' })

3.4 禁止项

plain
## 不要做的事情
- 不要生成 .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 分析某个具体文件的问题
plain
@user.entity.ts 根据这个 Entity,生成对应的 CreateUserDto 和 UpdateUserDto

5.2 @folder:引用整个目录

plain
@src/modules/user 分析这个模块的代码,有哪些可以改进的地方?

适用场景

  • 分析整个模块的代码质量
  • 生成代码时需要参考模块的整体结构

5.3 @docs:引用外部文档

plain
@docs https://docs.nestjs.com/interceptors
帮我实现一个全局的响应格式拦截器,把所有返回值包装成 ResponseDto 格式

适用场景

  • 参考官方文档实现某个功能
  • 引用项目里的 PRD 文档、API 设计文档

六、.cursorignore 文件

.cursorignore 的作用和 .gitignore 类似,控制 Cursor AI 扫描代码仓库时哪些文件和目录要排除。

为什么需要 .cursorignore

  1. node_modules 有几万个文件,AI 扫描会浪费 Token,还会占满上下文窗口
  2. .env 里有密码和 API Key,不应该进入 AI 上下文
  3. dist/coverage/ 是编译产物和测试报告,不是源码,AI 分析没有意义

标准配置

plain
node_modules/
dist/
.env
.env.local
*.log
coverage/
prisma/migrations/

七、演示操作步骤

准备工作

Step 1:新建演示项目

bash
npx @nestjs/cli new rules-demo
cd rules-demo
npm install

Step 2:用 Cursor 打开项目

bash
cursor rules-demo

Step 3:提前截好"没有 Rules"时的 AI 输出截图(用于对比)

在 Chat 里输入以下 Prompt,截图保存 AI 的输出:

plain
帮我创建一个用户注册接口

记录 AI 用了什么 ORM、有没有加 Swagger 注解、返回格式是什么。


创建并验证 .cursorrules

Step 1:在项目根目录创建 .cursorrules 文件

bash
touch .cursorrules

Step 2:用 Cursor 打开 .cursorrules,写入以下内容:

plain
# 技术栈
- 框架: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:

plain
帮我创建一个用户注册接口

Step 5:对比两次的输出,检查以下几点:

检查项 没有 Rules 时 有 Rules 后
ORM 随机(TypeORM 或 Prisma) 必须是 Prisma
Swagger 注解 随机 必须有
返回格式 随机 必须是 ResponseDto
有无测试文件 可能有 不应该有
有无 any 类型 可能有 不应该有

演示 @file Context 注入

Step 1:在 src/ 下新建 user/user.entity.ts

typescript
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 里引用这个文件:

plain
@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 里输入:

plain
@src/user 分析这个模块的代码,告诉我:
1. 有哪些不符合 NestJS 最佳实践的地方
2. 有哪些不符合当前项目 Rules 规范的地方

Step 3:观察 AI 是否能识别出模块级别的问题,而不只是单个文件的问题。


创建 .cursorignore

Step 1:在项目根目录创建 .cursorignore 文件

bash
touch .cursorignore

Step 2:写入以下内容:

plain
node_modules/
dist/
.env
.env.local
*.log
coverage/
prisma/migrations/

Step 3:验证效果——在 Chat 里输入:

plain
@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)自动附加到请求里。

plain
[你的 .cursorrules 内容]
+ [你在 Chat 里说的话]
→ AI 收到的完整输入

所以 .cursorrules 里写的东西,等于你在每次对话开头都说了一遍。AI 在整个项目开发过程中,始终带着这些约束工作。


3. Rules 文件的两种形式

形式一:.cursorrules(旧版,仍可用)

放在项目根目录,全局生效。

plain
my-project/
├── .cursorrules
├── src/
└── ...

形式二:.cursor/rules/(新版,推荐)

Cursor 新版本支持按目录或按场景分拆 Rules,更灵活:

plain
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 项目)

markdown
# 项目技术栈
- 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 @ 引用的几种类型

@文件:引用具体文件内容

plain
@src/common/filters/http-exception.filter.ts
帮我看看这个 filter 的写法,然后按相同风格写一个处理 Prisma 错误的 filter

@文件夹:引用整个目录(AI 会感知目录结构)

plain
参考 @src/modules/product/ 的模块结构,
帮我在 src/modules/order/ 创建同样结构的订单模块

@代码符号:引用类、函数、接口

plain
@UserService.findAll 这个方法有 N+1 查询问题,帮我优化

@Codebase:让 AI 在整个项目里搜索

plain
@Codebase 项目里所有使用了 bcrypt.hash 的地方在哪?
我要统一改成从配置读取 rounds 数

@Docs:引用你添加的外部文档

plain
@NestJS Docs 帮我查一下 NestJS 的 Global Pipes 怎么配置

@Git:引用 git 历史

plain
@Git 最近 3 次提交做了哪些改动?帮我写一份变更说明

6.2 上下文的优先级

当 .cursorrules 的约定和你在对话里说的话冲突时,对话里的说法优先。

这个机制很有用:.cursorrules 定全局默认值,某次任务需要例外时,直接在对话里覆盖:

plain
这次只做一个快速验证的脚本,可以用 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 工作规范。

bash
git add .cursorrules
git commit -m "feat: add cursorrules for team AI coding standards"

团队使用建议

  • 由技术负责人维护 .cursorrules,避免每个人随意修改
  • 规则更新时,在 commit message 里写清楚改了什么、为什么改
  • 个人的临时偏好,通过对话临时覆盖,不要写进团队 rules

9. 一个完整的项目 .cursorrules 结构建议

plain
# ===== 技术栈 =====
[基础技术选型,明确"用什么"和"不用什么"]

# ===== 代码风格 =====
[Prettier 配置摘要,命名约定]

# ===== 架构约定 =====
[目录结构,分层职责]

# ===== 接口规范 =====
[请求/响应格式,错误处理方式]

# ===== 数据层规范 =====
[ORM 使用规范,字段命名]

# ===== 安全规范 =====
[认证、加密、敏感信息处理]

# ===== AI 生成代码要求 =====
[告诉 AI 生成代码时的行为规范]

保持在 300-500 行以内。太长的 rules 会浪费 context window,而且 AI 对后半段的遵守情况会变差。


10. 小结

.cursorrules 是你在 Cursor 里最重要的一次性投入:花 1-2 小时写好项目规则,之后所有的 AI 交互都在这个规则约束下进行。

它解决的核心问题:让 AI 每次都知道你的项目是什么、规范是什么、禁止做什么——而不是靠你每次对话重新交代。

配合 .cursorignore 管理项目索引,配合 @引用 按需注入具体上下文,这三个工具加在一起,构成了 Cursor 工作流的完整上下文管理体系。