让需求、数据模型与编程代理规则保持一致
以离线优先的小店库存应用为例,展示如何从一句需求同时生成产品规格、数据库结构、接口契约和代理规则。案例明确标注虚构访谈数据,并把需求细化为可验证的实现决策。
AI 深度解读
给 AI 编程助手一句需求,它很快就能开始建表、写接口。麻烦在后面:表结构偏离文档,接口参数反复变化,权限约束只留在聊天记录里。ShopStock 这个案例尝试把 PRD、界面说明、数据库与接口契约、Agent 规则一起生成,让它们相互约束。
作者的结论是:交给 Agent 的应该是一套能核对、能验收的约定。案例值得看,但它展示的是虚构产品的真实样例文件,并没有证明应用已经跑通,更没有证明这种方式能提高多少交付效率。
一句话需求,怎么变成可以验收的约定
ShopStock 面向孟加拉国小店主,要替代纸质库存本,在廉价 Android 手机上优先支持离线,并允许用户按商品名称管理库存,不依赖商品编码。
PRODUCT_REQUIREMENTS.md 把这句话拆成目标、角色、功能和边界。比如库存可信度,样例给出的指标是:每周抽查实物与应用库存,到第 4 周达到 90% 一致率。它也写了「受访的 8 位店主中有 6 位,直到顾客询问才发现缺货」,但明确标注这是示例数据,不能当作真实调研结果。
更有用的是四条能手工验证的需求:
- FR-1:创建商品「Rice 5kg」,库存填 20,列表应显示库存 20。
- FR-2:记录售出 2 件,库存应从 20 变成 18。
- FR-3:库存剩 18 件时尝试卖出 25 件,应被阻止并看到清楚的提示。
- FR-4:员工不能改价,在员工会话中价格字段不可编辑。
边界也提前写下:库存恰好为 0 时,商品仍然可见,并显示缺货;两个员工同时销售时,库存不能为负,后一次销售必须基于最新库存重新检查;新用户没有商品时,只提供一个明确入口「添加你的第一个商品」。
UI_UX_SPECIFICATION.md 再把这些要求落到商品列表、销售流程、缺货页、空状态,以及离线时的「尚未同步」状态。
验收条件越具体,Agent 需要自行补全的业务决定就越少。
数据库和接口,怎样表达同一套限制
TECHNICAL_ARCHITECTURE.md 给出四张表:shops、shop_members、products、stock_movements。从表名就能看出,店铺、成员、商品和库存流水被分别建模,多店铺的数据隔离也在最初设计中考虑。
作者称每张表都启用行级安全(Row Level Security,RLS),用数据库策略限制用户可访问的记录。下面这段解决商品记录的店铺成员校验:
create policy "members manage their shop products"
on public.products for all
using (exists (select 1 from public.shop_members m
where m.shop_id = products.shop_id and m.user_id = auth.uid()))
with check (exists (select 1 from public.shop_members m
where m.shop_id = products.shop_id and m.user_id = auth.uid()));
要看清 m.shop_id = products.shop_id 和 m.user_id = auth.uid():它们共同确认当前用户属于这条商品记录所在的店铺。不过,这段策略只检查成员身份,没有体现店主与员工的改价权限差异。
接口契约随后对应到需求。POST /api/products 实现 FR-1,允许店主或员工以店铺成员身份创建商品,改价则限定店主。请求包含 shopId、name、price、stock;成功返回 201,响应包含 id、shopId、name、price、stock、createdAt。非店铺成员返回 403,校验失败返回 422。
下面的 Zod 定义把请求字段限制写成可执行校验:
const CreateProduct = z.object({
shopId: z.string().uuid(),
name: z.string().min(1).max(120),
price: z.number().nonnegative(),
stock: z.number().int().nonnegative(),
});
要注意 stock 同时要求整数和非负数;name 长度为 1–120,price 不得小于 0。类型正确之外,业务取值也有边界。
POST /api/sales 对应 FR-2、FR-3,请求为 { productId: uuid, quantity: integer ≥ 1 },成功返回 200 和 { productId, newStock },数量超过当前库存则返回 409。至此,「库存不能为负」有了明确的接口失败行为,Agent 不必临时决定错误码。
Agent 遇到文档冲突,为什么必须停下来
最后的 AI_AGENT_IMPLEMENTATION.md 供 Cursor / Claude Code 使用,可放入 .cursor/rules 或 CLAUDE.md。
规则要求 Agent 只实现 FR-1…FR-4,严格使用既定表名和列名,保持请求与响应结构;改动 payload 前先更新契约并说明。RLS 是安全边界,用户可调用的接口不得使用 service-role key;库存非负还要有数据库 check constraint,销售失败返回约定的 409。员工改价则同时要求界面隐藏字段、API 拒绝操作。
每完成一条需求,都要执行对应验收并报告结果。遇到 PRD、表结构、接口契约互相矛盾,必须报告冲突,不能悄悄选择一边。
作者最看重最后这条。表名写错通常较容易暴露,业务矛盾被 Agent 自行裁决,却可能变成运行正常、规则错误的功能。
作者还提出一个约两分钟的交付前检查:规则中的表名能否在 schema 找到;请求是否有对应 Zod 校验;PRD 中每条「绝不能」是否进入界面说明,并由数据库或接口强制执行;每条需求是否都有手工可执行的验收步骤。检查失败,就先修规格。
我的判断
我认同把权限、数据结构和验收一起交给 Agent。相较于只提供 PRD,这能减少实现过程中反复猜测的空间。但作者没有提供完成项目的测试结果,也没有与其他流程比较,收益仍属于经验判断。
样例自身也留下了待解问题。首先,离线优先是输入中的核心要求,摘录只展示了「尚未同步」状态,没有说明本地存储、重试、冲突合并如何实现。多台设备离线销售时,怎样兼顾可用性与库存非负,原文没说清。
其次,非负约束只能兜住非法结果,并不能独自说明并发扣库存正确。原文没有展示事务、锁或原子更新的实现,不能据此认定超卖问题已经解决。
再者,成员级 RLS 没有直接证明员工无法绕过 API 改价;员工创建商品时又需要提交 price,这与「员工不能改价」之间的具体边界,也需要补充验收。
作者要求每个 Zod 字段都对应数据库列,这条检查也不能机械照搬。销售请求的 quantity 是操作参数,接口并不天然需要逐字段映射单表。应检查字段的业务含义和数据去向是否明确。
生成器提供注册 50 个免费 credits、套餐从 ৳99/月起,支持 bKash/Nagad。这是产品推广信息,不构成输出质量的证据。
能拿来做什么
- 在日常 AI 编程任务中,把「完成商品销售」改成包含初始库存、销售数量、预期库存和失败提示的验收步骤,让 Agent 能报告具体结果。
- 给前端与 Node 接口维护共同的请求、响应和错误码约定;使用 Zod 校验时,同时核对服务端实际行为,避免文档和代码各写一套。
- 把角色权限分别写进界面行为与服务端限制,重点检查隐藏按钮之后,用户直接调用接口或访问数据是否仍被拒绝。
- 给 Cursor / Claude Code 加入冲突报告规则;涉及离线同步和并发库存时,先补齐处理策略,再让 Agent 实现。