CLAUDE.md 写了 300 行还是被无视?五层记忆的正确喂法
2026/7/22 4:25:38 网站建设 项目流程

你有没有遇到过这种情况:写了一份详详细细的 CLAUDE.md,技术栈、命名规范、错误码约定、数据库表结构……洋洋洒洒三百行,结果 Claude Code 该咋干还咋干,你写的"统一返回格式"它当没看见,你强调的"不使用 jest"它照样给你生成 jest 测试。

我碰到过。而且不止一次。

第一次我以为是写得不够清楚,于是又加了五十行,把每个细节展开讲。结果还是老样子。那阵子我甚至怀疑是不是模型本身的问题,去 issue 区翻了一圈,搜了两个小时没找到答案——后来才想明白,问题根本不在内容写得好不好,而在"喂法"错了。

一个被无视的 CLAUDE.md 长啥样

我一开始写得特别"描述式",大概是这样:

# 订单服务 API 这是一个电商平台的后端服务,我们团队有5个人,项目从2024年3月开始开发。 使用 Node.js 编写,是一个 RESTful API 服务。 代码要写得好一些,注意性能。 测试要全面。 请遵循最佳实践。

现在回头看,这写法蠢得可以。“代码要写得好一些”“请遵循最佳实践”——这种话对模型来说等于没说,它本来就会"遵循最佳实践",问题是它脑子里的最佳实践和你团队的最佳实践根本不是一回事。

模型读规范,吃的是"指令式"的硬约束,不是"描述式"的形容词。你跟它说"列表接口统一支持分页,page 从 1 开始,limit 默认 20 最大 100",它会照办;你跟它说"注意性能",它只会点点头然后该 N+1 查询还 N+1。

改成指令式之后,问题没完全解决

我把那段重写成指令式:

# 订单服务 API ## 技术栈 - Node.js 20 + TypeScript 5.3(严格模式) - Fastify 4 框架(不使用 Express) - Prisma ORM + PostgreSQL 15 - pnpm 8 包管理(不使用 npm/yarn) ## 关键约定 - API 统一返回格式:{ success: boolean, data?: T, error?: { code: string, message: string } } - 错误码使用 UPPER_SNAKE_CASE,如 ORDER_NOT_FOUND - 数据库表名 snake_case 复数形式,主键 UUID

效果好了不少,至少技术栈和返回格式它能记住了。但新的麻烦冒出来:我把 API 规范、数据库规范、测试规范全塞进同一个 CLAUDE.md,文件膨胀到两百多行。当我让它在src/routes/下加个新路由时,它有时候照着分页规范来,有时候又忘了——三百行的东西,对模型来说也是噪音。

等一下,这里我漏说一个前提:CLAUDE.md 不是越全越好,而是越"精准命中场景"越好。三百行堆在一起,等于把所有规范一股脑塞进上下文窗口,模型每次都得自己判断"这条规范现在适不适用",判断失误是常态。

五层记忆:把规范按场景分层喂

《Claude Code 实战》这本书里提出一个"马具"的隐喻——Claude Code 是马具不是马,你得把缰绳、马鞍、马镫分别装在该装的位置。记忆系统也是这个思路,分五层,每层职责不同:

层级文件作用域
第 1 层~/.claude/CLAUDE.md个人偏好,跨所有项目
第 2 层<repo>/CLAUDE.md项目说明,团队共享
第 3 层.claude/rules/*.md条件化规则,按路径触发
第 4 层<repo>/CLAUDE.local.md本地备忘,gitignore
第 5 层CLAUDE.md 里的"常用命令"段命令速查

第 1 层放跨项目的东西,比如"回复用中文"“提交信息走 conventional commits”"别硬编码 API Key"这种我个人的固定偏好,写一次所有项目通用:

# 个人编码偏好 - 回复使用中文,代码注释使用英文 - Git 提交信息格式:type(scope): 中文描述 - 优先使用函数式编程风格,避免冗余注释 - 变量命名:camelCase - "跑测试" → 执行 pnpm test - "起服务" → 执行 pnpm dev

第 2 层才是项目说明,只放这个项目特有的技术栈、目录结构、关键约定,前面那个指令式的 CLAUDE.md 就放这儿。

第 4 层CLAUDE.local.md是个人备忘,记得加进.gitignore,里面塞本地数据库地址、联调端口这种不该提交的东西:

# 本地开发备忘 - 开发数据库地址:postgresql://dev:dev123@10.0.1.50:5432/orders_dev - 每周三晚 22:00-23:00 数据库做维护,此时段勿跑迁移 - 联调时前端在 5173 端口,需要手动配置代理

第 5 层其实就是第 2 层 CLAUDE.md 末尾的一段,让模型记住"跑测试"对应哪个命令:

## 常用命令 pnpm dev # 启动开发服务器,端口 3000 pnpm test # 运行全部测试(vitest) pnpm build # TypeScript 编译 + 类型检查 pnpm db:migrate # 执行 Prisma 数据库迁移

这四层都不是重点。真正解决"被无视"问题的,是第 3 层。

条件化规则:按路径自动加载的杀手锏

.claude/rules/*.md这一层带 frontmatterpaths字段,模型读到匹配路径下的文件时,自动把对应规则拉进上下文。等于给规范装了个"触发器"——只在用得上的时候才出现,不占常驻上下文。

我把原来那三百行 CLAUDE.md 里"API 规范"“数据库规范”"测试规范"三块拆出去,变成三个独立文件。

.claude/rules/api-design.md

--- paths: - "src/routes/**" - "src/schemas/**" --- # API 设计规范 - 每个路由必须有对应的 Zod schema 做入参验证 - 列表接口统一支持分页:page(从1开始)和 limit(默认20,最大100) - 错误响应必须包含机器可读的 code 字段和人类可读的 message 字段

.claude/rules/database.md

--- paths: - "prisma/**" - "src/repositories/**" --- # 数据库规范 - 迁移文件一旦提交到 main 分支就不允许修改,只能创建新迁移 - 所有查询必须通过 repository 层,service 层禁止直接调用 Prisma - 批量操作使用事务包裹,超过 100 条记录的写入必须分批

.claude/rules/testing.md

--- paths: - "**/*.test.ts" - "**/*.spec.ts" - "tests/**" --- # 测试规范 - 使用 vitest 作为测试框架,不使用 jest - 每个测试文件必须包含 describe 块,describe 名称与被测模块一致 - 使用 vi.mock() 进行模块模拟,不使用手动 mock - 异步测试统一使用 async/await,不使用 done 回调 - 测试数据使用 factory 函数生成,不在测试中硬编码

frontmatter 里的paths用的是 glob 语法,**匹配任意层级目录。命中规则很简单:模型在处理src/routes/order.ts这种文件时,api-design.mdpaths里有src/routes/**,匹配上了,规则就被注入;处理src/repositories/user.ts时,database.md命中,api-design.md不动。互不干扰。

拆完之后的对比

拆完之后我做了个简单验证。原来在 CLAUDE.md 里写"不使用 jest",让 Claude 在src/services/order.test.ts里补一个测试用例,它给我生成的是describe('order service', () => { ... })配 jest 的语法,规范被无视。

拆成.claude/rules/testing.md之后,同样让它在那条路径下补测试,它直接用了 vitest,vi.mock()也对上了。区别在哪?路径匹配命中后,规则被显式注入,模型不需要在三百行里大海捞针。

我还做了个对比:在src/routes/order.ts下问它分页参数怎么传。原来 CLAUDE.md 时代,它答得含糊,有时候说 page 从 0 开始;规则文件拆出去后,它答的是 page 从 1 开始、limit 默认 20 最大 100——和 frontmatter 里写的一字不差。

这就是条件触发和常驻上下文的差距。

反思

回头看,我最初犯的错是把 CLAUDE.md 当成"项目说明书"在写,越写越长,以为信息越全模型越懂。其实模型吃上下文是有"信噪比"的——三百行常驻规范里,真正和当前任务相关的可能就十几行,剩下两百多行全是噪音,反而把相关的那十几行冲淡了。

五层记忆的核心思路是"分层加触发":跨项目的放第 1 层写一次,项目特有的放第 2 层,场景特有的拆成第 3 层按路径触发,本地私货放第 4 层,命令速查放第 5 层。每层各司其职,模型该读哪层读哪层。

顺便一提,我做雷达鸭(一个收录中国一人公司赚钱案例的 App,华为应用市场+微信小程序,Uni-app+arkTS+UniCloud)鸿蒙适配时,ArkTS 那套严格类型规范就是靠.claude/rules/arkts.mdpaths: ["entry/src/**/*.ets"]管住的,比塞进 CLAUDE.md 强太多。

等 Claude Code 后续更新,我希望paths能支持更细的触发条件,比如按文件类型、按分支、按 commit message 触发,那样条件化规则能玩出更多花样。现在这套够用,但离"想让它干啥它就干啥"还差点意思。


个人介绍

雷达鸭 App 独立开发者,10+年软件开发经验,软件设计师、人工智能应用工程师,专注鸿蒙 ArkTS + Web 前端,正在探索 AI 自动化的工程化路径。

MIT 声明

本文基于黄佳著《Claude Code 实战:Harness 工程之道》第 1-2 章(M1 记忆系统)整理实践,遵循 MIT 协议,欢迎转载但请保留原作者署名。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询