本页要交付什么
这一页不是讲如何泛泛地“写清需求”,而是把一个小型项目的需求从模糊想法推进到可开发、可测试、可审查的工作包。案例是一个本地运行的 TODO API:客户端用 HTTP 请求创建、查询、完成和删除待办事项,服务端用 SQLite 持久化数据。 本页结束时,你应该已经得到以下六项具体产物:- 一份经过澄清的产品范围,包含目标、非目标和明确的取舍。
- 一份可直接执行的 API 契约,写明请求、响应、状态码和错误格式。
- 一组可以逐条打勾的验收标准,而不是“接口能用”这种模糊结论。
- 一份项目根目录的
AGENTS.md,告诉 Codex 如何运行、测试和修改项目。 - 一份风险登记表,说明风险、触发条件、缓解办法和责任边界。
- 一份按依赖关系拆开的任务清单,交接到下一页的计划与开发阶段。
本页示例使用 Python 3.11、FastAPI、Uvicorn、SQLite 和 pytest。命令、版本参数以及 Codex 的界面行为可能随环境变化,请以项目实际配置和本机 codex --help 为准。
01 先定义问题,不急着选实现
业务背景
一个只有命令行入口的个人 TODO 工具,已经可以保存待办,但无法被浏览器、脚本或其他本地工具调用。现在需要增加一个 HTTP API,让一个简单客户端可以完成以下工作:- 查看当前待办列表;
- 新建一个待办;
- 将待办标记为已完成;
- 删除一个待办;
- 在请求错误时得到稳定、可判断的 JSON 响应。
原始需求
产品同学最初只说了一句话:
需求分析的第一步,就是把这些可能性变成选择,而不是让代理替我们决定。模型可以提出选项,但产品边界要由人确认。
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,并提供以下可验证能力:GET /health返回服务健康状态。POST /todos创建一条合法待办并持久化。GET /todos返回按id升序排列的待办列表。PATCH /todos/{id}/complete将指定待办标记为完成。DELETE /todos/{id}删除指定待办。- 对空标题、过长标题、非法 JSON、不存在的 ID 返回稳定错误。
- 服务重启后,已创建且未删除的数据仍然存在。
- 自动化测试覆盖成功路径、边界条件和状态码。
非目标
非目标不是“以后也不会做”,而是“本次任务不能因为看起来合理就做”。以下内容明确排除:- 不做用户注册、登录、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
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。
错误响应示例
缺少标题: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 和敏感文件。 输出: 测试结果、实际请求响应、变更文件清单、未验证风险和下一步建议。 通过条件: 满足本页验收清单,且未发生提交、推送或发布。任务依赖图
11 下一页交接包
交接到 02-计划开发与协作 时,应该把下面的内容原样带过去。不要只说“需求已经分析完了”。小结
本页完成的是项目的“边界工程”:先把原始一句话拆成使用者、数据、状态、错误和持久化问题;再用目标与非目标限制范围;用 API 契约固定输入输出;用验收标准规定什么证据才算完成;用AGENTS.md 固化运行和修改规则;最后用风险表和 T1-T6 任务清单把工作交给下一页。
记住本案例中最容易被忽略的三件事:
- “增删改查”不等于所有可能的功能,完成动作、撤销动作和通用更新必须分别确认。
- “能启动”不等于“可交付”,持久化、错误结构、测试隔离和不提交敏感产物同样是验收项。
- 非目标要写出来,
AGENTS.md要写可执行命令,任务要写停点。它们共同减少代理的猜测空间。
参考/codex/34-capstone.md、参考/codex/13-prompting.md、参考/codex/11-agents-md.md。