在 Claude Code 中输入"add user authentication"的开发者每次都会得到不同的结果。可能是 JWT,可能是会话 Cookie,也可能是带有刷新令牌和 PKCE 的完整 OAuth2 流程。代理不知道你想要什么,因为你没有告诉它。你给了它一个方向,不是一个目的地。
我观察到的那些从 Claude Code 持续获得可交付产出的开发者有一个共同习惯:在把工作交给代理之前先写规格说明。不是小说,不是只有三句话上下文的 Jira 工单。而是一个具体的文档,在任何人写一行代码之前就定义了"完成"的样子。
这不是什么新智慧。规格优先的开发在 AI 之前已经存在了几十年。但在代理场景下,跳过规格的成本更高,写规格的收益更大。人类开发者可以在实现过程中停下来问"等等,你说的是密码认证还是 SSO?"代理会默默选择一个然后继续。等你发现的时候,它已经做错了东西,你花了 20 分钟审查需要丢弃的代码。
这篇文章将介绍我每天与 Claude Code 一起使用的规格驱动生命周期:如何编写代理可以执行的规格、用 plan-before-code 检查点早期捕获误解,以及一个比"能编译"更严格的验证协议。
为什么"直接做"在代理场景下会失败
让我们具体看看失败模式。当你给 Claude Code 一个模糊的指令时,三件事会出错:
静默假设。 代理用自己的假设填补规格中的每一个空白。有时这些假设是合理的,有时不是。在读到输出之前你不会知道属于哪种情况。使用模糊指令时,你最终审查输出的仔细程度会超过写规格所需的精力。
不可复现的结果。 用同一个模糊的提示词运行两次,你会得到两个不同的实现。不只是变量名或格式不同,而是架构决策不同、库不同、错误处理策略不同。如果你无法复现输出,就无法围绕它构建可靠的流程。
审查成为瓶颈。 当代理做了所有决策,你就必须验证所有决策。一个你理解每个选择的 400 行 diff 需要 5 分钟审查。一个代理选择了数据库模式、API 形状、错误代码和验证逻辑的 400 行 diff 需要 30 分钟,因为你在从实现中逆向推导规格。
解决方案不是更好的提示词,而是把重要的决策前置到一个代理可以执行的文档中。
规格驱动的生命周期
工作流有五个阶段。每个阶段都有明确的进入条件和退出条件。
第一阶段:头脑风暴。 探索问题空间。约束是什么?有哪些方法?之前试过什么?这里你大声思考,独自或与 Claude Code 在对话模式中。退出条件:你有了首选方案并理解了取舍。
第二阶段:审查。 对方案进行压力测试。什么可能出错?有哪些边界情况?这与代码库中已有的东西冲突吗?如果你与多个代理合作,这里架构代理或第二意见很有价值。退出条件:你确信方案是可靠的。
第三阶段:规格。 写下你的决定。问题陈述、建议方案、要修改的文件、可机械验证的验收标准和测试计划。这就是契约。退出条件:任何人(人类或代理)都能读这个规格并准确知道要构建什么以及如何验证。
第四阶段:实现。 代理按规格执行。不是按模糊的想法,而是按有可测试标准的具体文档。退出条件:代理声称完成并发布了验证证据。
第五阶段:验证。 你(或 QA 代理)确认实现与规格匹配。不是"看起来对不对",而是"是否满足每个验收标准"。退出条件:每个标准都已检查,失败的返回第四阶段。
关键洞察:第 1-3 阶段成本低。中等规模功能需要 10-20 分钟。第四阶段需要实现所需的时间。第五阶段需要 5-10 分钟。跳过第 1-3 阶段并不能节省 10-20 分钟。它的代价是审查、调试和重做方向错误的工作所需的时间。
好的代理规格长什么样
这是一个真实的规格模板。不是用户故事,不是产品需求文档。是一个告诉代理确切要构建什么的工作文档。
## Problem
The filter bar resets when switching workspaces. Users lose their
filter state and have to re-apply filters every time they switch.
## Approach
Persist filter state per-workspace in localStorage. Key the stored
state by workspace database path so filters don't bleed across
workspaces.
## Files to Modify
- lib/local-storage.ts: Add getWorkspaceFilters / setWorkspaceFilters
- components/filter-bar.tsx: Read initial state from localStorage,
write on every change
- hooks/use-workspace.ts: Trigger filter restore on workspace switch
## Acceptance Criteria
1. Select workspace A, set filters to status=open + type=bug
2. Switch to workspace B. Filters reset to defaults.
3. Switch back to workspace A. Filters restore to status=open + type=bug.
4. Close the browser tab, reopen. Filters for the active workspace
are still applied.
5. bd list --status=open --type=bug output matches the filtered table.
## Out of Scope
- Server-side filter persistence
- Filter presets / saved filter combinations
- URL-based filter state (query params)
## Test Plan
- Unit test: getWorkspaceFilters returns stored value for matching
workspace path
- Unit test: setWorkspaceFilters writes correct key format
- Manual test: steps 1-5 from acceptance criteria above
注意这个规格包含什么和不包含什么。它不解释 localStorage 如何工作,Claude Code 知道这些。它不说明为什么我们选择 localStorage 而不是 URL params,那是在头脑风暴阶段发生的。它列出了代理应该触及的每个文件,这意味着如果代理开始修改这个列表之外的文件,那就是警告信号。它包含一个超出范围的部分,防止代理进行镀金。
验收标准是最重要的部分。每一条都是具有可观察结果的具体操作。不是"过滤器应该持久化",那是模糊的。"切换到工作区 A,验证过滤器是 status=open + type=bug"是可测试的。代理可以执行它。QA 审查者可以验证它。
Plan-before-code 模式
这是一个能在大多数实现错误变成代码之前捕获它们的协议:要求代理在写任何东西之前发布其实现计划。
在实践中,这看起来像任务上的结构化评论。在代理打开任何文件之前,它写下确切打算做什么。
PLAN: Persist filter state per-workspace using localStorage.
1. Add workspace-scoped storage helpers to lib/local-storage.ts:
- getWorkspaceFilters(dbPath: string): FilterState | null
- setWorkspaceFilters(dbPath: string, filters: FilterState): void
- Key format: "beadbox:filters:{dbPath}"
2. Update components/filter-bar.tsx:
- On mount, read stored filters for current workspace
- On every filter change, write to localStorage
- Accept databasePath as prop (currently not passed)
3. Update hooks/use-workspace.ts:
- On workspace switch, emit event that filter-bar listens for
- Filter-bar reads new workspace's stored filters on event
4. Threading databasePath:
- filter-bar needs databasePath from the workspace context
- Will add it to the WorkspaceProvider context value
Files: lib/local-storage.ts, components/filter-bar.tsx,
hooks/use-workspace.ts, components/workspace-provider.tsx
Test: Unit tests for storage helpers, manual test of all 5
acceptance criteria.
代理生成这个大约需要 30 秒。你读它大约需要 2 分钟。在这 2 分钟内,你可以发现实现后需要 20 分钟才能修复的问题:
- 代理是否在触及规格之外的文件?(添加 workspace-provider.tsx 不在规格中。这可以吗还是范围蔓延?)
- 方案合理吗?(为工作区切换使用事件发射器可能过度工程化。更简单的 prop 变更可能就够了。)
- 是否缺少步骤?(当工作区被删除时清理过期的 localStorage 条目怎么办?)
计划是一个检查点。如果看起来正确,让代理继续。如果看起来不对,纠正计划。无论哪种方式,你投入了 2 分钟而不是 20 分钟。
