1. 为什么“写得越多,Claude越不听话”?——从入职须知到协作契约的认知重构
你有没有过这种体验:花一整个下午,把项目里所有命名规范、错误处理方式、测试覆盖率要求、日志格式、commit信息模板……事无巨细地写进claude.md,保存,信心满满地让 Claude Code 开始写一个简单的 API 路由。结果它生成的代码里,还是出现了try/catch,还是用了console.log,还是把user-service.ts里明明写得清清楚楚的Result<Success, Failure>模式,替换成了一段带throw new Error()的老式写法?你点开文件对比,心里那句“我明明写了啊!”几乎要脱口而出。
这不是你的错,也不是 Claude 的错。这是你对claude.md的本质产生了根本性误判。
绝大多数人把它当成一份“AI行为守则”,一份需要 Claude 逐条背诵、严格执行的《刑法典》。但真相是:claude.md不是给机器人下的命令,而是给你和 Claude Code 共同签署的一份动态协作契约。它不是用来约束它的,是用来同步你们之间认知落差的。
这就像你刚招来一位经验丰富的高级工程师,他技术扎实、思维敏捷,但对你这个项目的上下文一无所知。你不会给他发一份 20 页的《公司代码行为白皮书》,然后说“照着做”。你会带他走一遍核心流程,指给他看几个关键文件,告诉他:“这是我们处理错误的唯一方式,参考@app/shared/src/result.ts;这是我们写单元测试的范式,看user-service.spec.ts;这个目录下的代码改动影响面极大,改之前务必先跑通pnpm test:shared。”——你给的是上下文,不是考卷。
Claude Code 正是这样一位能力极强、但项目经验为零的“高级工程师”。它没有记忆,每次对话开始,它都是一张白纸。而claude.md就是你递给它的第一份“入职须知”。这份须知的价值,不在于它有多厚,而在于它能否在最短的时间内,让这位新同事建立起对项目最核心、最关键的“直觉”。
我踩过的第一个大坑,就是把claude.md当成了“规则大全”。第一版我写了三千多字,涵盖了从 Git 提交信息格式(feat(user): add email validation)到 TypeScript 接口命名(IUserRepository)的所有细节。结果呢?Claude 在生成代码时,对“必须用Result类型”这条规则的遵守率,甚至不如我口头强调一次。后来我才明白,问题出在信息密度上。当一份文档里塞进了三十条规则,Claude 的注意力就被平均分配了。它记住了“要用 pnpm”,也记住了“要用 Vitest”,但关于“为什么不能用throw”的深层原因,却被淹没在文字海洋里。这就像你给实习生发了一份包含 30 条注意事项的清单,他大概率只记住了第一条和最后一条,中间的全靠临场发挥。
所以,重构claude.md的第一步,是彻底抛弃“全面覆盖”的执念,转而拥抱“精准打击”。你要问自己的不是“我有哪些规则?”,而是“在我这个项目里,Claude 最容易在哪几个地方犯错?而这些错误,一旦发生,修复成本最高?” 这个问题的答案,就是你claude.md的全部内容。其他所有能被 ESLint 自动修复、能被 Prettier 格式化、能在代码库里一眼看出的通用规范,统统删掉。把宝贵的上下文窗口,留给那些真正定义了你项目灵魂的、独一无二的约定。
提示:一个简单有效的自检方法是,想象你正在给一位刚加入团队的 Senior Frontend Engineer 做入职培训。你只有五分钟时间,必须让他立刻理解这个项目最不可妥协的三条铁律。这三条,就是你
claude.md的核心骨架。
2. 从“规则手册”到“活文档”:claude.md的结构化写作与持续演进
明白了claude.md是一份“协作契约”之后,下一步就是解决“怎么写才能让它真正生效”的问题。答案是:结构化 + 动态化。一份好的claude.md,绝不是一成不变的静态文本,而是一个随着你和 Claude 协作深度不断生长、不断修正的活文档。
我现在的claude.md稳定在 780 字左右,分为四个清晰区块,每个区块都有其不可替代的作用。它不是我坐在电脑前冥思苦想出来的,而是过去两个月里,每一次 Claude “又犯了那个错”之后,我立刻打开文件,删掉一句模糊的描述,补上一条具体的、可执行的指令,日积月累而成的。
2.1 区块一:核心身份与绝对红线(必须放在最前面)
这是整份文档的“宪法序言”,必须开门见山,用最简练的语言定义 Claude 在这个项目里的角色和底线。它决定了 Claude 的整个工作基调。
# 🧭 项目身份与绝对红线 - 你是一位资深全栈工程师,熟悉 Node.js (v20+)、TypeScript (v5.4+)、NestJS 和 PostgreSQL。 - 你**绝不**使用 `throw new Error()` 处理业务逻辑错误。所有业务错误必须封装为 `Result<Success, Failure>` 类型。 - 你**绝不**在业务代码中使用 `console.log`。所有日志必须通过 `@app/shared/logger` 模块的 `logger.info()`/`logger.error()` 方法输出。 - 你**绝不**修改 `@app/shared` 目录下的任何已有逻辑,除非 `spec` 明确要求你重构该模块。注意这里的措辞。我没有写“请使用 Result 类型”,而是用了“绝不使用throw new Error()”。正如前文所说,“不要做什么”比“要做什么”更有效。Claude 的训练数据里充满了throw,它需要一道非常明确、非常强硬的“防火墙”来阻止这个本能反应。我把这条放在最前面,确保它在读取文档的第一毫秒就接收到最强信号。
2.2 区块二:核心约定与标杆文件(提供可模仿的范式)
这一部分是“怎么做”的具体指南。它不讲抽象原则,只提供最直接、最可复用的样板。Claude 是一个卓越的模式识别者,它模仿一个好例子的能力,远胜于理解一百句文字描述。
# 📚 核心约定与标杆文件 - **错误处理**:严格遵循 `@app/shared/src/result.ts` 中的 `Result`、`Ok`、`Err` 实现。所有服务方法返回 `Result`,控制器层统一处理。 - **API 路由**:NestJS 控制器必须继承 `BaseController`,并使用 `@ApiResponse` 装饰器。参考 `@app/modules/user/user.controller.ts`。 - **单元测试**:使用 Vitest。每个 `.spec.ts` 文件必须包含 `describe`、`it` 和 `expect` 断言。参考 `@app/modules/user/user.service.spec.ts`。 - **依赖注入**:所有服务必须通过 NestJS 的 `@Injectable()` 和构造函数注入,禁止全局变量或单例模式。这里的关键是“参考”二字。我给出的不是规则,而是路径。Claude 在生成代码时,会主动去读取这些标杆文件,并精确地模仿其中的每一个细节:@ApiResponse的写法、describe的嵌套层级、expect的断言风格。这比你写十行文字描述“测试要覆盖 happy path 和 error path”要可靠得多。
2.3 区块三:常见错误(持续更新的“防错清单”)
这是claude.md的心脏,也是我投入精力最多、回报最高的部分。它不是一个静态列表,而是一个动态的“错误日志”。每当我在 Claude 的输出里发现一个重复出现的、代价高昂的错误,我就立刻把它加进来。
# ⚠️ 常见错误(请务必遵守) - ❌ 错误:在 `@app/modules/*` 下的服务中,直接 `import { logger } from 'winston'`。 ✅ 正确:必须 `import { logger } from '@app/shared/logger'`。 - ❌ 错误:为数据库查询编写 `try/catch`,并在 `catch` 中 `throw new Error()`。 ✅ 正确:使用 `Result.fromPromise()` 包装查询,并在 `mapErr` 中转换为 `Failure`。 - ❌ 错误:在 `user-service.ts` 中,将 `userRepository.find()` 的返回值直接赋值给一个 `User[]` 类型变量。 ✅ 正确:必须使用 `Result<User[], Failure>` 类型,并进行 `match` 处理。 - ❌ 错误:在 `user.controller.ts` 中,`@Post('/register')` 的 handler 函数返回 `void` 或 `any`。 ✅ 正确:必须返回 `Result<User, RegistrationError>`,并由 `BaseController` 统一响应。这个区块目前有 7 条,不多不少。每一条都对应一个我亲手踩过的坑。它的力量在于“具体”。它不谈“要写好测试”,而是指出“不要在user.service.spec.ts的it块里漏掉await”。这种颗粒度,让 Claude 的执行变得无比精准。而且,因为它是基于真实错误构建的,所以每一条都直击要害,没有一句废话。
2.4 区块四:执行指令(让 Claude 知道下一步该做什么)
最后一部分,是给 Claude 的“行动指令”。它告诉 Claude,当它完成当前任务后,应该自动执行哪些验证步骤。这一步,把“写代码”和“保证质量”无缝衔接起来,避免了人工检查的疏漏。
# ▶️ 执行指令(每次生成后自动执行) - 生成代码后,**必须**立即运行 `pnpm lint` 并修复所有错误。 - 生成测试文件后,**必须**立即运行 `pnpm test --run --testNamePattern="your-test-name"` 验证其通过。 - 修改任何 `@app/shared` 相关文件后,**必须**运行 `pnpm test:shared` 并确保 100% 通过。 - 所有新增的 API 路由,**必须**在 `@app/modules/*/e2e/*.e2e-spec.ts` 中添加对应的端到端测试。这个区块的效果立竿见影。以前我总要手动去跑测试,现在只要看到 Claude 的回复末尾附上了✅ pnpm test --run --testNamePattern="register user success" passed,我就知道这段代码是可信的。它把质量保障变成了一个自动化流程的一部分,而不是一个事后补救的动作。
注意:这个结构不是教条。你的项目可能不需要“执行指令”区块,但一定需要“常见错误”区块。关键是理解每个区块的目的——身份定义基调,标杆提供范式,错误清单堵住漏洞,执行指令闭环质量。
3. 模型选择:不是“越贵越好”,而是“任务匹配度”决定效率天花板
回到标题那个反直觉的结论:“日常编码,Opus 是最差的选择。” 这句话听起来很刺耳,但它背后是一个经过两个月真实账单和生产力验证的深刻洞察:模型选择的本质,是任务分工,而不是能力排名。把博士生派去干数据录入,把实习生拉去主持架构评审,结果都不会好。Claude 的三个主力模型——Haiku、Sonnet、Opus——正是这样三个不同定位的“人”。
我当时的策略是“无脑用最好的”,觉得 Opus 能力最强,开着它肯定没错。结果两个月后,账单比我预期高了将近一倍,而我的实际编码速度却慢了下来。因为 Opus 在帮我写一个简单的表单验证函数时,花了 3 秒钟思考“这个验证是否应该做成一个可复用的 Hook?”,又花了 2 秒钟分析“如果用户输入的是恶意 SQL 片段,前端验证是否足够?”,最后才给我一个能跑的函数。而 Sonnet,只用了 0.8 秒,就给出了完全符合我需求的、一行不多一行不少的代码。我节省下来的 2.2 秒,在一天几十次的微小任务中,累积成了巨大的时间黑洞。
3.1 Sonnet:你的“高级工程师”,负责 80% 的日常交付
Sonnet 是我日常编码的绝对主力,承担了我 80% 以上的任务。它的定位非常清晰:一个执行准确、响应飞快、成本合理的高级工程师。它不擅长天马行空的创意,也不喜欢过度设计,但它对“已知需求”的实现,堪称完美。
- 适用场景:CRUD 功能开发、API 路由生成、React/Vue 组件编写、单元测试补充、函数重构、类型定义补全、调试报错分析(已知错误)、JSDoc 注释添加。
- 核心优势:响应速度快(通常 <1s),token 成本低(约为 Opus 的 1/3),对明确指令的执行准确率极高。
- 关键短板:当遇到它不熟悉的领域时,它有“假装会”的倾向。它会生成一段看起来语法完美、逻辑自洽,但实际运行就会报错的代码。比如,让我用一个我从未用过的数据库驱动写一个连接池配置,它可能会编造出一个根本不存在的 API。
我做过一个对比实验:用同一个claude.md和同一个 Prompt,让 Sonnet 和 Opus 分别写一个 Stripe 订阅取消的 API 路由。Sonnet 的输出是:
@Delete('/subscriptions/:id') async cancelSubscription(@Param('id') id: string) { const result = await this.stripeService.cancelSubscription(id); return result.match({ ok: (sub) => this.successResponse(sub), err: (e) => this.errorResponse(e) }); }干净、利落、完全符合我的claude.md规范。而 Opus 的输出,开头是长达 200 字的分析:“考虑到 Stripe 的订阅生命周期管理,我们需要区分canceled和incomplete_expired状态……建议引入一个SubscriptionStatus枚举……” 然后才是路由代码。我只需要一个能跑的路由,不需要它帮我做产品决策。
3.2 Opus:你的“首席架构师”,只在关键时刻出场
Opus 不是不好,是它太强大了,强大到在大多数日常任务里,它的能力是一种“过度杀伤”。它的价值,只在那些需要“把所有角度想清楚”的战略性时刻才会真正爆发。
- 适用场景:全新系统架构设计、复杂技术选型(如 GraphQL vs REST vs gRPC)、大型业务逻辑拆分、深度 Bug 根因分析(表现与原因隔着多层因果)、进入一个完全陌生的技术领域时的“认知建模”。
- 核心优势:无与伦比的推理深度、全局视角、追问能力。它能帮你发现你根本没想到的风险点。
- 关键短板:响应慢(通常 3-5s+),token 成本高(是 Sonnet 的 3 倍),在简单任务上会产生大量冗余信息,降低效率。
我最有价值的一次 Opus 使用,是在为一个出海 SaaS 设计多租户架构时。我问它:“我们计划用 PostgreSQL 的schema方案实现多租户,有哪些关键风险?” Opus 没有直接给我代码,而是列出了一个包含 7 个要点的分析报告,其中两点让我拍案叫绝:一是指出了pg_dump在多 schema 场景下备份恢复的复杂性;二是提醒我,如果未来要支持跨租户的报表查询,schema方案会导致JOIN变得极其笨重,建议提前规划tenant_id列的方案。这两点,是我作为开发者完全没考虑到的。Opus 在这里扮演的,不是一个写代码的工具,而是一个经验丰富的 CTO。
3.3 Haiku:你的“超级助理”,专治批量机械任务
很多人把 Haiku 当成“穷人版 Claude”,这是最大的误解。Haiku 的定位非常精准:一个高速、低成本、专为简单、明确、重复性任务而生的执行引擎。它不适合思考,但特别适合干活。
- 适用场景:CLI 批处理脚本(如为 100 个文件批量添加 JSDoc)、代码格式化微调、字符串替换、生成固定模板(如 Dockerfile、CI 配置)、简单数据转换。
- 核心优势:速度极快(比 Sonnet 快 3-5 倍),成本极低(比 Sonnet 便宜 10 倍以上),在明确指令下执行稳定。
- 关键短板:对项目上下文的“消化能力”极弱。如果你的
claude.md写得很长、很复杂,Haiku 很可能只执行了其中一半。它无法像 Sonnet 那样,从一堆文字中推断出隐含的约束。
我有一个 CLI 脚本,需要为项目里所有.ts文件自动添加 JSDoc 注释。我最初用 Sonnet,处理 30 个文件花了 12 秒,花费了约 $0.03。换成 Haiku 后,只用了 2.5 秒,花费不到 $0.003。但这里有个关键技巧:用 Haiku 时,Prompt 必须极度明确。我不能只说“为这个文件添加 JSDoc”,而必须说:“请为以下 TypeScript 文件的getUserById函数添加 JSDoc 注释。注释格式必须严格遵循@param id - 用户唯一标识符和@returns Result<User, UserNotFoundError>。只输出修改后的完整代码,不要有任何解释文字。” 加上最后一句“只输出代码”,就完美避开了它偶尔会输出“以下是修改后的代码:”这种导致语法错误的坑。
实操心得:我的工作流是“Opus 拆解 -> Sonnet 实现 -> Haiku 批量”。当需求模糊时,先用 Opus 做一轮深度拆解,产出一份清晰的
spec;然后关掉这个对话,用 Sonnet 按spec逐条实现;最后,对于那些重复性的收尾工作(如加注释、改文件名),再开一个 Haiku 会话,批量搞定。三者各司其职,效率最大化。
4. 工作流升级:从“单点提问”到“Vibe-Coding”的工程化实践
当你把claude.md写对了,模型选准了,接下来的瓶颈就不再是工具本身,而是你如何组织自己的工作流。我曾经也陷入过“对着一个聊天窗口,反复提问、反复修改”的低效循环。直到我借鉴了 SWE-agent 的理念,将整个编码过程重新设计为一个以智能体为中心的、可验证、可迭代的工程化流程,效率才实现了质的飞跃。
这个流程的核心,是将一个模糊的“我要做个功能”拆解为三个清晰、独立、可并行的阶段:Plan(规划)、Spec(设计)、Code(实现)。它们不是线性顺序,而是可以双开、甚至三开的并行窗口。
4.1 Plan 窗口:用 Opus 做“战略规划”,产出可执行的路线图
Plan 阶段的目标,不是写出代码,而是产出一份能让任何普通工程师(甚至实习生)都能按部就班执行的详细计划。这份计划,就是你的plan.md。
我通常会这样向 Opus 提问:
“我们有一个需求:将用户注册流程从邮箱验证码改为手机号短信验证码。现有代码在
@app/modules/auth/下。请为这个需求制定一个详细的plan.md,要求:
- 按照
1. 分析影响范围、2. 列出待修改文件、3. 描述每个文件的具体修改点(包括新增、删除、修改的代码行)、4. 列出所有需要编写的单元测试、5. 列出所有需要编写的端到端测试的结构。- 每个修改点必须具体到函数名和关键逻辑,例如:‘修改
auth.service.ts中的register函数,将sendEmailVerification调用替换为sendSmsVerification,并处理新的SmsVerificationError’。- 不要写任何代码,只写计划。”
Opus 会给出一份 2-3 页的plan.md。这份计划的价值,在于它把一个模糊的需求,转化为了一个可验证的、有明确终点的任务清单。它告诉你,什么时候算“做完”了。我曾经做过一个判断:如果一份plan.md,你读完后能产生一种“这事儿交给一个靠谱的实习生,他干一个月肯定能搞定”的感觉,那这份 plan 就是合格的。
4.2 Spec 窗口:用 Humanize 插件做“设计确认”,锁定最终形态
Spec 阶段的目标,是把plan.md里的每一条“修改点”,转化为一份精确的、可验证的设计文档。它回答的问题是:“这个功能,到底要长成什么样子?”
我通常会用humanize插件,配合一个精心设计的 Prompt,让 Claude 基于plan.md生成spec.md。spec.md的格式,我强烈推荐学习 Loom 项目的 spec format 。它的精髓在于“SIMT”(Single Instruction, Multiple Threads)思想:不是让你一次性想出一个完美方案,而是让 AI 同时探索多个可能性,最后用一个明确的 CLI 测试来筛选。
一个典型的spec.md片段是:
## 用户注册 API (`POST /auth/register`) - **输入**:`{ phone: string, password: string }` - **输出**:`201 Created` with `{ userId: string }` OR `400 Bad Request` with `{ code: "INVALID_PHONE", message: "手机号格式不正确" }` - **核心逻辑**: - [x] 验证 `phone` 是否为合法中国手机号(正则 `/^1[3-9]\d{9}$/`) - [ ] 调用 `smsService.sendVerificationCode(phone)` 发送验证码 - [ ] 创建用户记录,状态为 `PENDING_VERIFICATION` - **测试用例**: - `pnpm test --run --testNamePattern="register with valid phone"` - `pnpm test --run --testNamePattern="register with invalid phone"`这个spec.md的伟大之处在于,它把“设计”和“验证”绑定在了一起。每一个[ ]后面的描述,都对应着一个即将编写的、可运行的测试用例。当我看到spec.md里所有的[ ]都被打上了[x],并且所有列出的测试用例都通过了,我就知道,这个功能的设计已经锁定了,可以进入实现阶段了。
4.3 Code 窗口:用 Sonnet 做“战术执行”,严格按spec.md编码
Code 阶段,就是纯粹的、机械的、高精度的执行。我打开一个新的 Claude Code 会话,把spec.md的全文粘贴进去,并加上一句:“请严格按照以上spec.md的要求,为@app/modules/auth/目录下的文件编写代码。只输出修改后的代码文件内容,不要任何解释。”
这时,Sonnet 就像一个最听话的高级工程师,它不会质疑spec.md的合理性,也不会提出更好的方案,它只会一丝不苟地、精准地,把spec.md里描述的每一行代码,都写出来。因为它知道,spec.md是经过 Opus 战略规划和 Humanize 迭代确认后的最终产物,它的唯一使命,就是完美执行。
我习惯于“双开”:一个窗口是plan.md,另一个窗口是code。我每次只让 Sonnet 实现plan.md里的一个最小单元,比如“修改auth.service.ts中的register函数”。实现完成后,我立刻运行pnpm test --run --testNamePattern="register with valid phone",验证通过,再继续下一个单元。这种“单步前进、即时验证”的方式,让我彻底告别了“写完一大段代码,一跑全崩”的噩梦。
实操心得:这个工作流的终极目标,是让你的“人类时间”被完全填满。我的典型状态是:左手边是
plan.md(规划),中间是code(实现),右手边是spec.md(设计)。我每完成一个code小任务,就去spec.md里打一个勾;每打一个勾,就去看一眼plan.md,确认下一步该做什么。三个窗口轮流切换,我的大脑始终处于高度专注的“心流”状态,没有任何一分钟是浪费在等待、猜测或返工上的。
5. 常见问题与排查技巧实录:那些没人告诉你的“血泪教训”
再完美的claude.md和工作流,也无法避免在实战中遇到各种意想不到的状况。下面这些,都是我过去两个月里,从一次次失败、一次次调试、一次次崩溃中总结出来的独家经验。它们不是理论,而是刻在骨子里的肌肉记忆。
5.1 问题:Claude 输出的代码里,总是混杂着解释性文字,导致文件被覆盖后直接报错
现象:你在 CLI 脚本里调用 Claude API,让它为一个文件生成新代码。脚本执行后,打开文件一看,开头赫然写着“以下是根据您的要求修改后的user.service.ts文件:”,后面才是代码。tsc一跑,直接报SyntaxError: Unexpected token '以下是'。
根因分析:这是模型的“默认行为”。Claude(尤其是 Haiku 和 Sonnet)在生成代码时,倾向于先做一个简短的说明,再给出结果。它认为这是一种“礼貌”,但在自动化脚本里,这就是灾难。
解决方案:在 Prompt 的末尾,必须、必须、必须加上一句斩钉截铁的指令:
“只输出纯代码,不要任何解释、不要任何前缀、不要任何后缀、不要任何 Markdown 代码块符号(```)。输出的内容必须是能直接写入
.ts文件并被 TypeScript 编译器接受的合法代码。”
我曾经以为加一次就够了,结果发现,有时候它还是会“手滑”。现在我的标准做法是,在 Prompt 的开头和结尾,各加一遍这句话。双重保险。
进阶技巧:在你的 CLI 脚本里,加入一个简单的“清洗”步骤。在 Claude 返回结果后,用正则表达式^.*?(```|typescript|js|```javascript).?\n([\s\S])去提取```` 代码块内的内容。如果没找到代码块,则直接丢弃整个输出,并报警。这比指望模型永远不犯错要可靠得多。
5.2 问题:Claude 在修改shared目录时,总是“顺手”加一些它认为“有用”的新功能,导致连锁反应
现象:你只想让 Claude 把user-service.ts里的一个函数改成异步的。结果它不仅改了函数,还在@app/shared/src/utils.ts里加了一个全新的asyncRetry工具函数,并在user-service.ts里引用了它。你一跑测试,整个shared模块的测试全挂了。
根因分析:Claude 的“优化本能”在作祟。它看到了一个可以“改进”的地方,就忍不住动手。而shared目录的特殊性,恰恰是claude.md里最容易被忽略的“上下文”。
解决方案:在claude.md的“核心身份与绝对红线”区块里,必须有一条用加粗和感叹号强调的、不可动摇的规则:
❗ 绝对红线:你绝不修改
@app/shared目录下的任何已有逻辑!你只能阅读它、引用它、在spec明确要求时,才允许重构它。任何对shared的修改,都必须在plan.md和spec.md中被明确提及、讨论和批准。
光有这条还不够。在每次让 Claude 修改涉及shared的文件时,我的 Prompt 开头一定会加上:
“请再次确认:本次任务不允许修改
@app/shared目录下的任何文件。你只能修改@app/modules/user/下的文件。请严格遵守claude.md中的绝对红线。”
这是一种“心理暗示”,不断强化它的认知边界。
5.3 问题:Claude 的输出在不同会话间“失忆”,同一个问题反复问,它给出的答案不一致
现象:你昨天让 Claude 为一个 API 设计了错误响应格式,它给出了{"code": "USER_NOT_FOUND", "message": "用户不存在"}。今天你让它为另一个 API 设计同样的格式,它却给出了{"error": "USER_NOT_FOUND", "detail": "用户不存在"}。两个格式不统一,前端要写两套解析逻辑。
根因分析:Claude 没有长期记忆。每个新会话,它都是一个全新的、对项目一无所知的“人”。它不记得昨天发生了什么,除非你把昨天的结论,明确地、永久地写进claude.md。
解决方案:建立一个“共识沉淀”机制。每当 Claude 在某个设计点上给出了一个你认可的、高质量的答案(比如那个错误响应格式),立刻、马上,把它写进claude.md的“核心约定”区块。把它变成一条正式的、不可更改的规范。
例如,我会在claude.md里加上:
- **API 错误响应**:所有 HTTP 错误响应必须是 JSON 格式,且必须包含 `code`(字符串,大写下划线)和 `message`(字符串)两个字段。参考 `@app/modules/auth/auth.controller.ts` 中的 `handleAuthError` 方法。从此以后,这个问题就不再是“问 Claude”,而是“查claude.md”。claude.md就是你和 Claude 之间,所有重要共识的唯一、权威、永久的存储库。它不是一份文档,而是一个活的、不断生长的“项目宪法”。
5.4 问题:Claude 在处理超长上下文(如 272k+ tokens)时,性能急剧下降,且费用翻倍
现象:你在一个超大型仓库里工作,claude.md写得很精炼,但你为了让 Claude 理解整个项目,把package.json、tsconfig.json、nest-cli.json甚至几个核心的README.md都塞进了上下文。结果发现,Claude 的响应变得异常缓慢,而且账单上出现了很多意料之外的高额费用。
根因分析:这是 Anthropic 模型的一个已知限制。当上下文长度超过 272,000 tokens 时,模型的性能会出现断崖式下跌,同时,Anthropic 会对这部分超长上下文收取双倍费用。这不是 bug,是设计如此。
解决方案:严格控制上下文窗口。这是我的~/.codex/config.toml文件里的关键配置:
[model] model_context_window = 272000这个配置强制 Codex 在发送请求前,将上下文截断到 272k。但这只是治标。治本的方法是,永远不要试图用“堆上下文”来弥补claude.md的不足。如果你觉得 Claude 总是“看不懂”,问题 99% 出在claude.md写得不够好,而不是上下文不够长。
我的经验是:一个写得好的claude.md(800 字以内),配合 3-5 个精准的标杆文件路径,就能让 Claude 在 95% 的任务中表现出色。剩下的 5%,是那些真正需要“全局视野”的复杂任务,那时,你才应该启动 Opus,并准备好为此支付相应的费用和时间。
最后一个血泪教训:永远不要相信“Claude 会自己理解”。它不会。它只会忠实地、尽全力地,执行你给它的每一条指令,无论那条指令是清晰的还是模糊的,是正确的还是错误的。
claude.md的终极目的,不是教会 Claude 什么,而是教会你自己,如何成为一个更清晰、更精准、更高效的“指挥官”。