跳转到正文
莫尔索随笔
返回

Claude Code 最佳实践:Research-Plan-Annotate 三段式工作流

预计 13 分钟

第一时间捕获有价值的信号

本文译自 How I Use Claude Code。作者是 Cloudflare 员工。Research-Planning-Annotation 三段式工作流确实是 Claude Code 的最佳实践。Research 阶段让 AI 充分理解上下文,Planning 阶段确保方向正确,Annotation 阶段保证可维护性。这种结构化方法比直接让 AI 写代码更可靠,值得好好读一读。

我使用 Claude Code 作为主要开发工具大约 9 个月了,我最终确定的工作流与大多数人使用 AI 编码工具的方式截然不同。大多数开发者输入提示,有时使用计划模式,修复错误,重复。更多上网的人正在拼接 ralph 循环、mcps、gas towns(还记得那些吗?)等等。两种情况下的结果都是一团糟,对于任何非平凡的事情都会完全分崩离析。

我要描述的工作流有一个核心原则:在你审阅并批准书面计划之前,绝不让 Claude 编写代码。规划和执行的这种分离是我做的最重要的一件事。它防止了浪费的努力,让我掌控架构决策,并以最少的 Token 使用产生显著更好的结果,而不是直接跳到代码。

重复 1-6 次

Research(研究)
Plan(规划)
Annotate(注解)
Todo List(待办列表)
Implement(实现)
Feedback & Iterate(反馈与迭代)

阶段 1:研究

每个有意义的任务都从深度阅读指令开始。我要求 Claude 在做任何其他事情之前深入理解代码库的相关部分。而且我总是要求将发现写入持久的 markdown 文件,而不仅仅是聊天中的口头总结。

深入阅读这个文件夹,深刻理解它是如何工作的、它做什么以及所有细节。完成后,在 research.md 中编写一份详细的学习和发现报告

非常详细地研究通知系统,理解它的复杂性,并编写一份详细的 research.md 文档,包含关于通知如何工作的所有信息

梳理任务调度流程,深入理解它并寻找潜在的 bug。系统中肯定有 bug,因为它有时会运行本应取消的任务。继续研究流程,直到找到所有 bug,在找到所有 bug 之前不要停止。完成后,在 research.md 中编写一份详细的发现报告

注意语言:“深入地”“非常详细地”“复杂性”“梳理所有内容”。这不是废话。没有这些词,Claude 会略读。它会读取一个文件,在签名级别看到一个函数做什么,然后继续。你需要发出信号表明表面阅读是不可接受的。

书面产物(research.md)至关重要。这不是让 Claude 做家庭作业。这是我的审阅表面。我可以阅读它,验证 Claude 实际上理解了系统,并在任何规划发生之前纠正误解。如果研究是错误的,计划将是错误的,实现也将是错误的。输入垃圾,输出垃圾。

这是 AI 辅助编码中最昂贵的失败模式,而且不是错误的语法或糟糕的逻辑。而是孤立工作但破坏周围系统的实现。忽略现有缓存层的函数。不考虑 ORM 约定的迁移。复制其他地方已存在逻辑的 API 端点。研究阶段防止所有这些。

阶段 2:规划

一旦我审阅了研究,我就要求在单独的 markdown 文件中制定详细的实现计划。

我想要构建一个新功能 <名称和描述>,它扩展系统以执行 <业务成果>。编写一份详细的 plan.md 文档,概述如何实现这一点。包含代码片段

列表端点应该支持基于游标的分页而不是偏移。编写一份详细的 plan.md 说明如何实现这一点。在建议更改之前阅读源文件,基于实际代码库制定计划

生成的计划始终包含方法的详细说明、显示实际更改的代码片段、将修改的文件路径,以及考虑因素和权衡。

我使用自己的 .md 计划文件,而不是 Claude Code 的内置计划模式。内置计划模式很糟糕。我的 markdown 文件给了我完全控制权。我可以在编辑器中编辑它,添加内联注释,并且它作为项目中的真实产物持续存在。

我经常使用的一个技巧: 对于我在开源仓库中见过良好实现的功能完善的功能,我会分享该代码作为计划请求的参考。如果我想添加可排序 ID,我会粘贴来自一个做得很好的项目的 ID 生成代码,并说”这就是他们做可排序 ID 的方式,编写一份 plan.md 解释我们如何采用类似的方法。“当 Claude 有具体的参考实现可以工作时,比从头设计效果要好得多。

但计划文件本身不是有趣的部分。有趣的是接下来发生的事情。

注解循环

这是我工作流中最独特的部分,也是我添加最多价值的部分。



Claude 编写 plan.md
我在编辑器中审阅
我添加内联注释
将 Claude 发送回文档
Claude 更新计划
满意吗?
请求待办列表

在 Claude 编写计划后,我在编辑器中打开它,直接在文档中添加内联注释。这些注释纠正假设、拒绝方法、添加约束,或提供 Claude 没有的领域知识。

注释的长度差异很大。有时注释是两个词:在 Claude 标记为可选的参数旁边写”不是可选的”。其他时候是一段解释业务约束或粘贴我期望的数据形状的代码片段。

我会添加的一些真实注释示例:

  • “使用 drizzle:generate 进行迁移,而不是原始 SQL” — Claude 没有的领域知识
  • “不——这应该是 PATCH,而不是 PUT” — 纠正错误的假设
  • “完全删除这部分,我们这里不需要缓存” — 拒绝提议的方法
  • “队列消费者已经处理重试,所以这个重试逻辑是多余的。删除它,就让它失败” — 解释为什么某些东西应该改变
  • “这是错误的,可见性字段需要在列表本身上,而不是在单个项目上。当列表公开时,所有项目都是公开的。相应地重构 schema 部分” — 重定向计划的整个部分

然后我将 Claude 发送回文档:

我在文档中添加了一些注释,处理所有注释并相应地更新文档。先不要实现

这个循环重复 1 到 6 次。 明确的 “先不要实现” 防护至关重要。没有它,Claude 会在它认为计划足够好的那一刻跳到代码。在我说它足够好之前,它都不够好。

为什么这效果如此之好

markdown 文件充当我和 Claude 之间的共享可变状态。我可以按照自己的节奏思考,精确注解哪里出了问题,并在不丢失上下文的情况下重新参与。我不是试图在聊天消息中解释所有内容。我指向文档中问题的确切位置,并在那里写下我的更正。

这从根本上不同于试图通过聊天消息引导实现。计划是一个结构化的、完整的规范,我可以整体审阅。聊天对话是我必须滚动浏览才能重建决策的东西。计划每次都赢。

三轮”我添加了注释,更新计划”可以将一个通用的实现计划转变为完美适合现有系统的计划。Claude 非常擅长理解代码、提出解决方案和编写实现。但它不知道我的产品优先级、我的用户的痛点,或我愿意做出的工程权衡。注解循环是我注入这种判断力的方式。

待办列表

在实现开始之前,我总是请求细粒度的任务分解:

向计划添加详细的待办列表,包含完成计划所需的所有阶段和单个任务——先不要实现

这创建了一个清单,在实现期间充当进度跟踪器。Claude 随着进行将项目标记为已完成,因此我可以随时查看计划并确切了解事情的进展。在运行数小时的会话中特别有价值。

阶段 3:实现

当计划准备好时,我发出实现命令。我已经将其提炼成一个在会话中重用的标准提示:

实现所有内容。当你完成一个任务或阶段时,在计划文档中将其标记为已完成。在所有任务和阶段完成之前不要停止。不要添加不必要的注释或 jsdocs,不要使用 any 或未知类型。持续运行类型检查以确保你没有引入新问题。

这个单一提示编码了所有重要的事情:

  • “实现所有内容”:做计划中的所有事情,不要挑挑拣拣
  • “在计划文档中将其标记为已完成”:计划是进度的真实来源
  • “在所有任务和阶段完成之前不要停止”:不要在流程中间暂停确认
  • “不要添加不必要的注释或 jsdocs”:保持代码干净
  • “不要使用 any 或未知类型”:保持严格类型
  • “持续运行类型检查”:尽早发现问题,而不是在最后

我在几乎每个实现会话中都使用这个确切的措辞(有小的变化)。当我说”实现所有内容”时,每个决策都已经做出并验证。实现变成机械的,而不是创造性的。这是有意的。我希望实现是无聊的。创造性工作发生在注解循环中。一旦计划正确,执行应该是直截了当的。

没有规划阶段,通常发生的情况是 Claude 早期做出一个合理但错误的假设,在其上构建 15 分钟,然后我必须解开一串更改。“先不要实现”防护完全消除了这一点。

实现期间的反馈

一旦 Claude 正在执行计划,我的角色就从架构师转变为监督者。我的提示变得显著更短。





Claude 实现
我审阅 / 测试
正确吗?
简洁更正
更多任务?
完成

规划注释可能是一段,实现更正通常是单个句子:

  • “你没有实现 deduplicateByTitle 函数。”
  • “你在主应用程序中构建了设置页面,而它应该在管理应用程序中,移动它。”

Claude 具有计划和进行中会话的完整上下文,因此简洁的更正就足够了。

前端工作是最迭代的部分。我在浏览器中测试并快速发出更正:

  • “更宽”
  • “仍然被裁剪”
  • “有一个 2px 的间隙”

对于视觉问题,我有时会附加截图。一张未对齐表格的截图比描述它更快地传达问题。

我也经常引用现有代码:

  • “这个表格应该看起来完全像用户表格,相同的标题、相同的分页、相同的行密度。”

这比从零开始描述设计要精确得多。成熟代码库中的大多数功能都是现有模式的变体。新的设置页面应该看起来像现有的设置页面。指向参考传达了所有隐含的要求,而无需拼写出来。Claude 通常会在进行更正之前阅读参考文件。

当事情走向错误方向时,我不会尝试修补它。我通过放弃 git 更改来恢复并重新界定范围:

  • “我恢复了所有内容。现在我想要的只是让列表视图更简洁——没有其他内容。”

在恢复后缩小范围几乎总是比尝试增量修复糟糕的方法产生更好的结果。

保持掌控

即使我将执行委托给 Claude,我也永远不会给它对构建内容的完全自主权。我在 plan.md 文档中进行绝大多数的主动引导。

这很重要,因为 Claude 有时会提出技术上正确但对项目错误的解决方案。也许该方法过度工程化,或者它更改了系统其他部分依赖的公共 API 签名,或者它在更简单的选项就足够时选择了更复杂的选项。我有关于更广泛系统、产品方向和工程文化的上下文,而 Claude 没有。

Claude 提议更改
我评估每个项目
按原样接受
修改方法
跳过 / 删除
覆盖技术选择
精炼的实现范围

从提议中精挑细选: 当 Claude 识别多个问题时,我一个一个地处理它们:“对于第一个,只需使用 Promise.all,不要让它过于复杂;对于第三个,为了可读性将其提取到单独的函数中;忽略第四和第五个,它们不值得复杂性。” 我根据我对现在重要的事情的知识做出项目级别的决策。

修剪范围: 当计划包含锦上添花的内容时,我主动削减它们。“从计划中删除下载功能,我现在不想实现这个。” 这防止了范围蔓延。

保护现有接口: 当我知道某些东西不应该更改时,我设置硬约束:“这三个函数的签名不应该更改,调用者应该适应,而不是库。”

覆盖技术选择: 有时我有 Claude 不会知道的特定偏好:“使用这个模型而不是那个”“使用这个库的内置方法而不是编写自定义方法。” 快速、直接的覆盖。

Claude 处理机械执行,而我做出判断调用。计划预先捕获大的决策,选择性指导处理实现期间出现的较小决策。

单个长会话

我在单个长会话中运行研究、规划和实现,而不是将它们拆分到单独的会话中。单个会话可能从深度阅读文件夹开始,经过三轮计划注解,然后运行完整实现,所有这些都在一个连续的对话中。

我没有看到每个人都在谈论的 50% 上下文窗口后的性能下降。实际上,当我说”实现所有内容”时,Claude 已经花了整个会话建立理解:在研究期间阅读文件,在注解循环期间精炼其心智模型,吸收我的领域知识更正。

当上下文窗口填满时,Claude 的自动压缩保持足够的上下文以继续前进。并且计划文档,持久的产物,在完全保真的压缩中幸存下来。我可以在任何时间点将 Claude 指向它。

一句话总结工作流

深入阅读,编写计划,注解计划直到它正确,然后让 Claude 不间断地执行整个事情,沿途检查类型。

就这样。没有魔法提示,没有精心设计的系统指令,没有聪明的技巧。只是一个将思考与打字分开的纪律严明的管道。研究防止 Claude 做出无知的更改。计划防止它做出错误的更改。注解循环注入我的判断。并且实现命令让它在每个决策都做出后不间断地运行。

尝试我的工作流,你会想知道在你和代码之间没有一个带注解的计划文档的情况下,你是如何用编码代理发布任何东西的。