Skip to main content

本页要交付什么

这一页不是讲如何泛泛地“写清需求”,而是把一个小型项目的需求从模糊想法推进到可开发、可测试、可审查的工作包。案例是一个本地运行的 TODO API:客户端用 HTTP 请求创建、查询、完成和删除待办事项,服务端用 SQLite 持久化数据。 本页结束时,你应该已经得到以下六项具体产物:
  1. 一份经过澄清的产品范围,包含目标、非目标和明确的取舍。
  2. 一份可直接执行的 API 契约,写明请求、响应、状态码和错误格式。
  3. 一组可以逐条打勾的验收标准,而不是“接口能用”这种模糊结论。
  4. 一份项目根目录的 AGENTS.md,告诉 Codex 如何运行、测试和修改项目。
  5. 一份风险登记表,说明风险、触发条件、缓解办法和责任边界。
  6. 一份按依赖关系拆开的任务清单,交接到下一页的计划与开发阶段。
后续页面会继续处理计划、实现、测试和 Git 收口。因此,本页的终点不是写完代码,而是让下一位执行者拿到足够信息后不必重新猜需求。
本页示例使用 Python 3.11、FastAPI、Uvicorn、SQLite 和 pytest。命令、版本参数以及 Codex 的界面行为可能随环境变化,请以项目实际配置和本机 codex --help 为准。

01 先定义问题,不急着选实现

业务背景

一个只有命令行入口的个人 TODO 工具,已经可以保存待办,但无法被浏览器、脚本或其他本地工具调用。现在需要增加一个 HTTP API,让一个简单客户端可以完成以下工作:
  • 查看当前待办列表;
  • 新建一个待办;
  • 将待办标记为已完成;
  • 删除一个待办;
  • 在请求错误时得到稳定、可判断的 JSON 响应。
这是一个学习型、单用户、本地优先的项目。我们刻意把范围控制在一个人半天到一天能够完成并验证的大小。它足以练习需求澄清和代理协作,又不会把认证、部署、多人并发等问题混成一团。

原始需求

产品同学最初只说了一句话:
这句话不能直接交给 Codex 开发,原因很具体: 需求分析的第一步,就是把这些可能性变成选择,而不是让代理替我们决定。模型可以提出选项,但产品边界要由人确认。

02 需求澄清记录

下面是一轮足够短、但能消除主要歧义的澄清记录。实际工作中可以把它放在 Issue、任务描述或会话开头;重要的是答案要进入项目产物,而不是只留在聊天记录里。

问题一:谁使用 API

问题: 这版 API 是给谁用的?需要登录、多个用户或权限控制吗? 确认结果: 第一版只服务于本机上的单个用户。不做登录,不做用户表,不做跨用户隔离。接口监听本机地址,默认端口为 8000。 为什么这样定: 登录会引入密码、会话、密钥和权限模型。它们属于独立需求,不能因为“给前端用”就默认加入。

问题二:待办包含哪些字段

问题: 一条待办最少需要哪些字段?标题是否允许为空?是否需要截止日期、标签和优先级? 确认结果: 第一版只包含:
  • id:服务端生成的正整数;
  • title:必填字符串,去除首尾空白后长度为 1 到 200;
  • completed:布尔值,创建时默认为 false;
  • created_at:服务端生成的 UTC 时间字符串;
  • updated_at:服务端生成的 UTC 时间字符串。
不加入截止日期、标签、描述、排序字段和附件。

问题三:状态变化如何表达

问题: “修改”是允许任意字段更新,还是只做完成/未完成切换? 确认结果: 第一版采用专门的完成接口:PATCH /todos/{todo_id}/complete。它把 completed 设为 true,重复调用仍然成功并返回当前资源。第一版不提供通用 PUT,也不支持把已完成改回未完成。 为什么这样定: 一个明确动作比任意更新更容易理解和测试。撤销完成可以作为后续需求,不在本次范围里偷偷扩展。

问题四:数据是否需要持久化

问题: 服务重启后,之前创建的待办还要存在吗? 确认结果: 要。使用项目根目录下的 data/todos.db SQLite 文件。数据库文件是运行时产物,不提交到 Git;仓库提交 schema 或初始化代码。

问题五:客户端如何知道请求失败

问题: 前端需要稳定的错误结构吗? 确认结果: 所有预期的客户端错误都返回 JSON:
客户端可以依赖 error.code 做分支,message 用于展示。不会把 SQLite traceback 或内部路径返回给客户端。

问题六:列表是否需要分页和筛选

问题: 列表会不会很大?是否需要分页、搜索和按状态筛选? 确认结果: 第一版最多返回 100 条,按 id 升序排列。不做分页、不做搜索、不做状态筛选。如果未来超过 100 条,接口返回前 100 条并在响应中给出 count;是否增加分页留到下一次需求评审。

问题七:是否要兼容已有命令行工具

问题: API 是否必须复用或保持原有 CLI 的行为? 确认结果: 本次项目从一个独立 API 目录开始,不修改旧 CLI。可以复用数据模型思想,但不得为了“顺手”重写旧入口。

03 形成需求基线

目标

在本地启动一个单用户 TODO API,并提供以下可验证能力:
  1. GET /health 返回服务健康状态。
  2. POST /todos 创建一条合法待办并持久化。
  3. GET /todos 返回按 id 升序排列的待办列表。
  4. PATCH /todos/{id}/complete 将指定待办标记为完成。
  5. DELETE /todos/{id} 删除指定待办。
  6. 对空标题、过长标题、非法 JSON、不存在的 ID 返回稳定错误。
  7. 服务重启后,已创建且未删除的数据仍然存在。
  8. 自动化测试覆盖成功路径、边界条件和状态码。

非目标

非目标不是“以后也不会做”,而是“本次任务不能因为看起来合理就做”。以下内容明确排除:
  • 不做用户注册、登录、JWT、Cookie 或权限系统。
  • 不做 CORS 配置,不把服务声明为公网可用。
  • 不做 PostgreSQL、Redis、消息队列或云端存储。
  • 不做全文搜索、标签、截止日期、优先级和提醒。
  • 不做批量创建、批量删除和批量完成。
  • 不做撤销完成;本版只有完成动作。
  • 不做分页、复杂排序和导出功能。
  • 不修改旧 CLI、前端页面或其他目录中的无关代码。
  • 不提交真实数据库文件、日志、.env 或任何密钥。
  • 不自动部署、不推送 Git、不创建 Pull Request。
  • 不为了“顺便整理”升级依赖或重构整个项目。

成功与失败的判定

“服务能启动”只是开发开始,不是交付完成。只有当需求基线、验收标准和自动化验证同时满足,才能把实现交给下一阶段审查。

04 API 契约:让输入和输出可检查

以下契约是本页最重要的开发输入。实现可以在内部采用不同模块,但对外行为必须与此一致。

通用约定

  • 基础地址:http://127.0.0.1:8000。
  • 请求和成功响应使用 application/json。
  • 时间使用 UTC ISO 8601 字符串,例如 2026-09-05T10:30:00Z。
  • ID 是正整数。
  • 未找到资源返回 404,不返回 200 加空对象。
  • 创建成功返回 201,删除成功返回 204 且没有响应体。
  • 错误响应统一使用 {"error": {"code": ..., "message": ...}}。

健康检查

请求:
成功响应:200 OK
健康检查不访问外部服务,不检查数据库中的业务数据。数据库连接错误属于服务启动或内部错误,需要在后续测试阶段单独处理。

创建待办

请求:
成功响应:201 Created
输入规则:
  • title 缺失或不是字符串,返回 422 和 TODO_TITLE_REQUIRED;
  • title 去除首尾空白后为空,返回 422 和 TODO_TITLE_REQUIRED;
  • 去除首尾空白后超过 200 个字符,返回 422 和 TODO_TITLE_TOO_LONG;
  • 请求体是非法 JSON,返回 400 和 INVALID_JSON;
  • 客户端不能传入 id、completed、created_at 或 updated_at 伪造服务端字段。

列出待办

请求:
成功响应:200 OK
空列表响应:
列表最多返回 100 条,按 id 升序。当前不接受 page、limit、q 或 completed 查询参数;是否忽略未知参数需要在实现前确认,默认要求返回 400 和 UNSUPPORTED_QUERY,避免客户端误以为筛选已经生效。

完成待办

请求:
成功响应:200 OK
边界行为:
  • ID 不存在:404,错误码 TODO_NOT_FOUND;
  • ID 不是正整数:422,错误码 INVALID_TODO_ID;
  • 重复调用:仍返回 200,结果保持 completed: true;
  • 请求体包含不支持字段:返回 400,错误码 UNSUPPORTED_FIELDS。

删除待办

请求:
成功响应:204 No Content,响应体必须为空。 边界行为:
  • ID 不存在:404,错误码 TODO_NOT_FOUND;
  • 删除成功后再次查询列表,不应出现该项;
  • 删除成功后再次删除同一个 ID,仍返回 404,不能返回成功假装幂等;
  • 删除不影响其他待办的 ID 和内容。

05 数据规则和错误规则

数据库最小结构

实现可以使用 ORM,也可以使用 sqlite3,但第一版倾向于直接使用已批准依赖和简单 SQL。逻辑结构如下:
completed 在 SQLite 中使用 0 和 1 保存,在 API 层必须转换为 JSON 布尔值。时间统一由服务端生成,更新完成状态时更新 updated_at。

错误响应示例

缺少标题:
资源不存在:
服务器内部错误不能把 SQL、绝对路径、环境变量或堆栈返回给客户端。日志可以保留诊断信息,但日志不得写入版本库。

06 验收标准:每一条都要有证据

将下面清单复制到任务或验收记录中。实现者不能只回复“已完成”,需要给出命令、测试名称或实际响应作为证据。

功能验收

  • 运行启动命令后,服务监听 127.0.0.1:8000。
  • GET /health 返回 200 和 {"status":"ok"}。
  • 合法 POST /todos 返回 201,标题、ID、状态和时间字段齐全。
  • 创建后 GET /todos 能查到刚创建的数据。
  • 标题首尾空白会被去除后保存。
  • 缺少标题返回 422/TODO_TITLE_REQUIRED。
  • 空标题返回 422/TODO_TITLE_REQUIRED。
  • 超过 200 个字符返回 422/TODO_TITLE_TOO_LONG。
  • 非法 JSON 返回 400/INVALID_JSON。
  • PATCH /todos/{id}/complete 将状态设为 true。
  • 重复完成同一条待办不会生成第二条记录。
  • 不存在的 ID 完成时返回 404/TODO_NOT_FOUND。
  • DELETE /todos/{id} 返回 204 且没有响应体。
  • 删除后列表不再包含该待办。
  • 删除不存在的 ID 返回 404/TODO_NOT_FOUND。
  • 服务重启后未删除的数据仍可查询。
  • 列表按 ID 升序,最多返回 100 条。
  • 所有预期错误都符合统一 JSON 结构。

工程验收

  • 新增代码有对应测试,不依赖手工点 Swagger 才能证明正确。
  • 测试使用临时数据库,不污染 data/todos.db。
  • 项目根目录有准确、短小的 AGENTS.md。
  • README 或现有项目入口中的启动和测试命令与实际一致。
  • 没有提交 SQLite 数据库、日志、缓存、.env 或密钥。
  • 没有修改旧 CLI 或需求未授权的目录。
  • pytest 通过,进程退出码为 0。
  • git diff --check 通过。
  • 实现者报告了未验证项,而不是用“测试通过”掩盖环境限制。

07 项目规则:写一份可执行的 AGENTS.md

AGENTS.md 不是公司介绍,也不是把 README 复制一遍。它应该记录 Codex 每次进入这个项目都需要知道的事实:用什么命令、哪些目录能改、哪些行为不能擅自增加、完成后必须如何验证。 在项目根目录创建以下文件。若仓库已有 AGENTS.md,先合并规则,不要直接覆盖已有团队约定。
这份规则有三个设计点:
  • 命令是可复制的,不写“运行测试”这种无法执行的描述;
  • 非目标写成禁止动作,防止代理顺手扩展认证、部署或依赖;
  • 验收要求输出证据,避免代理只报告结论。
根据参考资料,项目级规则会和更上层的 AGENTS.md 合并,当前目录更近的规则在冲突时更具体。若使用 AGENTS.override.md,它只替代同目录候选文件,不会自动清除上层规则。因此不要把本项目的安全边界寄托在“代理应该记得上一轮聊天”上。

08 风险登记

需求分析必须同时记录“做什么”和“可能怎么出事”。下表是本项目的最小风险登记,可随实现进展更新。 高风险动作的处理原则是先停下来确认,不让代理从上下文自行推断授权。包括安装新包、访问网络、修改工作区外文件、改生产配置、删除数据、提交和推送。

09 把需求交给 Codex:先澄清再动手

进入仓库后,不要直接说“实现 TODO API”。第一条消息应该要求 Codex 读取现状、指出冲突、列出问题,并且明确禁止修改。
这条提示把“背景、输入材料、目标、非目标、输出格式和禁止动作”放在一起。它不是为了让代理显得更听话,而是为了给后续审查留下可比较的基线。

澄清阶段的预期输出

一个合格的只读回复至少应该指出类似问题:
如果它直接创建文件、升级依赖或声称“已经实现”,说明提示中的边界没有被遵守。先停止,检查审批和工作区,再重新发送“只读检查”的要求。

10 任务拆分:每一步都有输入、输出和停点

下一页会进入计划与开发。为了让交接可执行,把大目标拆成以下六个任务。每个任务都应该产生一个可以审查的结果,完成后再进入下一个有依赖的任务。

T1:确认仓库和依赖

输入: 当前仓库、现有 README、依赖文件、测试命令和 AGENTS.md。 动作: 识别现有框架,确认 FastAPI、Uvicorn、pytest 是否已存在;如果不存在,只记录差异,不自动安装。 输出: 一份现状报告,列出可复用文件、缺失依赖和不应修改的目录。 通过条件: 能解释为什么新增或复用每个依赖;没有工作区改动。 停点: 产品或仓库负责人确认依赖方案后,才进入 T2。

T2:建立数据访问边界

输入: 已确认的 SQLite 表结构和 data/todos.db 路径规则。 动作: 设计数据库初始化、连接、事务和测试数据库注入方式。 输出: 数据层接口草图和一个可重复创建临时数据库的测试方案。 通过条件: 生产数据库与测试数据库路径不会混用;不需要真实数据才能运行测试。 停点: 先审数据生命周期,再实现 API 路由。

T3:实现读取和健康检查

输入: 数据层接口、GET /health 和 GET /todos 契约。 动作: 实现健康检查、列表查询、排序、上限和资源序列化。 输出: 可运行的读取接口和对应测试。 通过条件: 空列表、单条、多条和超过 100 条的行为都有测试。

T4:实现创建和输入校验

输入: POST /todos 请求规则和错误码清单。 动作: 实现标题清洗、长度校验、服务端字段保护、持久化和 201 响应。 输出: 创建接口、错误响应和成功/失败测试。 通过条件: 缺失、空白、过长、非字符串和合法标题都能得到契约规定的结果。

T5:实现完成和删除

输入: 已通过审查的读取和创建接口。 动作: 实现完成状态更新、重复完成、删除、找不到资源和空响应体。 输出: 两个接口及其测试。 通过条件: 状态改变可被列表读到,删除后数据消失,其他数据不受影响。

T6:集成验证和交接

输入: T1 到 T5 的代码和测试。 动作: 运行完整测试、编译检查、启动服务做最小 HTTP 烟测,检查 diff 和敏感文件。 输出: 测试结果、实际请求响应、变更文件清单、未验证风险和下一步建议。 通过条件: 满足本页验收清单,且未发生提交、推送或发布。

任务依赖图

这里没有把所有任务并行化。T3、T4 和 T5 共享数据模型,过早并行会让接口字段和测试夹具互相漂移。下一页可以在同一会话中顺序执行,也可以让子代理只做只读调查;是否使用子代理要根据真实仓库规模决定,不是为了形式上显得复杂。

11 下一页交接包

交接到 02-计划开发与协作 时,应该把下面的内容原样带过去。不要只说“需求已经分析完了”。
这份交接包同时是对本页的自测。如果其中任何一项仍然使用“尽量”“支持一下”“体验良好”之类无法判断的描述,就不要进入实现阶段,回到需求澄清。

小结

本页完成的是项目的“边界工程”:先把原始一句话拆成使用者、数据、状态、错误和持久化问题;再用目标与非目标限制范围;用 API 契约固定输入输出;用验收标准规定什么证据才算完成;用 AGENTS.md 固化运行和修改规则;最后用风险表和 T1-T6 任务清单把工作交给下一页。 记住本案例中最容易被忽略的三件事:
  1. “增删改查”不等于所有可能的功能,完成动作、撤销动作和通用更新必须分别确认。
  2. “能启动”不等于“可交付”,持久化、错误结构、测试隔离和不提交敏感产物同样是验收项。
  3. 非目标要写出来,AGENTS.md 要写可执行命令,任务要写停点。它们共同减少代理的猜测空间。
下一页 02-计划开发与协作 将使用本页的交接包,把 T1-T6 变成具体计划,决定每一步让 Codex 读取什么、修改什么、运行什么验证,以及何时暂停给人审查。 参考资料:参考/codex/34-capstone.md、参考/codex/13-prompting.md、参考/codex/11-agents-md.md。