1. 项目概述:当代码规范“活”过来
最近在跟几个做AI Agent和大型软件项目的朋友聊天,大家普遍头疼一个问题:需求文档写得再详细,一旦进入长周期、多模块的“特性开发”(Feature Development)阶段,文档和代码的脱节几乎是必然的。产品经理画的饼(需求),架构师做的蓝图(设计),最后到工程师手里,经过几轮迭代和人员变动,最初的意图早就面目全非了。更别提现在流行的“智能体驱动开发”(Agentic Development),你指望一个AI去理解上百页充满自然语言歧义的PRD(产品需求文档),然后写出符合预期的代码?这简直是天方夜谭。
这就是“CodeSpec: Dual Executable Specifications for Agentic Long-Horizon Feature Development”这个项目标题直击的痛点。它不是什么新框架,而是一个核心的工程理念和方法论。简单说,它主张为长周期特性开发创建“双重可执行规约”。听起来有点玄乎?我把它拆开揉碎了讲。
“可执行规约”(Executable Specifications)不是新概念,在测试驱动开发(TDD)或行为驱动开发(BDD)里,我们用类似Cucumber的Given-When-Then语法把需求写成可运行的测试。但CodeSpec强调“双重”(Dual)。我的理解是,一重是面向“人”的、高层次的、描述业务逻辑和验收条件的规约;另一重是面向“机器”(特别是AI智能体)的、低层次的、可直接驱动代码生成与验证的规约。两者同源、同步、可互相验证。
而“Agentic Long-Horizon Feature Development”则是它的主战场。Agentic指的是开发过程由AI智能体(如基于LLM的编码助手、自主测试Agent、代码审查Agent)高度参与甚至主导。Long-Horizon指的是那些无法在一个短冲刺(Sprint)内完成,需要拆分成多个子任务、跨越数周甚至数月,并且各任务间存在复杂依赖和状态传递的特性开发。比如,“为电商平台重构整个订单履约系统”或“在数据平台中实现一套全新的流式风控规则引擎”。
传统的文档在这类场景下几乎失效。CodeSpec的理念,就是为这个混乱的战场提供一套“活”的、可执行的宪法,让所有参与者——无论是人类工程师还是AI智能体——都在同一套明确、无歧义、可验证的规则下协作。
2. 核心理念拆解:为什么需要“双重”规约?
要理解CodeSpec,得先明白单一规约在长周期、智能体参与的开发中为什么不够用。
2.1 传统文档的失效与智能体协作的鸿沟
在纯人力开发时代,我们靠会议、口口相传和不断修改的文档来对齐。虽然低效,但人有模糊理解和上下文补全的能力。然而,AI智能体目前严重缺乏这种“意会”的能力。你给AI一段自然语言描述:“用户下单后,检查库存,如果充足则预占库存,并通知仓库。” 这里面的坑太多了:
- “检查库存”:是查实时库存还是可售库存?缓存策略是什么?
- “预占库存”:预占时长多久?预占记录的结构是怎样的?
- “通知仓库”:通过什么渠道?消息格式是什么?是同步还是异步?
- 如果库存不足呢?是返回错误,还是进入等待队列?
一个人类工程师会去问,或者根据既有系统惯例来决策。但一个AI智能体,如果没有极其精确的输入,它要么会卡住,要么会生成一个看似合理但完全不符合你系统上下文的代码,甚至引入安全漏洞。
因此,面向AI智能体的规约,必须是机器可解析、无歧义、且包含完整上下文约束的。这远超过传统产品需求文档的范畴。
2.2 “双重规约”的协同设计
CodeSpec提出的“双重规约”,正是为了桥接人类意图与机器执行之间的鸿沟。
第一重:人类可读/可写的业务规约 (Human-Centric Spec)这一层面向产品经理、架构师和工程师。它使用领域特定语言(DSL)或增强的自然语言,描述特性的目标、业务价值、核心工作流、业务规则和验收标准。它的重点是“做什么”和“为什么”,而不是“怎么做”。
- 形式:可能是结构化的YAML、Markdown表格,或者一种自定义的DSL。
- 示例(简化):
Feature: 订单库存预占 Goal: 确保用户下单时商品库存得到可靠预留,防止超卖。 Actors: 用户、库存服务、订单服务。 Main Flow: 1. 用户提交包含商品SKU和数量的订单。 2. 系统调用库存服务的 `preempt` 接口。 3. 库存服务校验实时库存 >= 订单数量。 4. 若充足,在`inventory_holds`表创建一条状态为`RESERVED`、有效期为30分钟的预占记录,并返回成功。 5. 订单服务收到成功响应后,创建状态为`待支付`的订单。 Business Rules: - 预占有效期(RESERVATION_TTL): 30分钟。 - 仅当订单支付成功后,预占才转化为实际扣除。 - 预占到期或订单取消,预占记录需释放,库存回滚。 Acceptance Criteria: - AC1: 库存充足时,下单成功,生成预占记录。 - AC2: 库存不足时,下单失败,返回明确错误码 `INSUFFICIENT_INVENTORY`。 - AC3: 预占记录30分钟后自动过期,库存恢复。
这一层是人类团队对齐的基石。
第二重:机器可执行/可验证的工程规约 (Machine-Centric Spec)这一层是CodeSpec的精髓。它由第一层规约部分或全部转化而来,为AI智能体提供精确的操作指令和验证标准。它更接近“怎么做”,包括接口契约、数据模式、状态机、不变式约束,甚至是可运行的测试用例。
- 形式:可能是OpenAPI规范、JSON Schema、状态机定义(如XState的JSON)、Prolog逻辑规则,或直接就是一套单元测试的脚手架代码。
- 与第一层的关联:例如,上面的“业务规则”会被编译成:
- 接口契约:
POST /inventory/preempt的请求/响应JSON Schema。 - 数据模式:
inventory_holds表的SQL DDL或Prisma Schema。 - 状态机:订单和库存预占的状态流转图(如
RESERVED -> CONFIRMED或RESERVED -> RELEASED)。 - 不变式约束:一条始终必须为真的逻辑规则,如“
sum(预占数量) + sum(可用库存) = 总库存”。 - 可执行验收条件:AC1-AC3会被转化为具体的集成测试代码框架。
- 接口契约:
2.3 双重规约如何驱动智能体开发
在一个理想的Agentic工作流中:
- 规划阶段:主控智能体(Orchestrator Agent)读取第一重规约,理解特性范围和目标。
- 任务分解:主控智能体根据规约中的工作流和规则,将Long-Horizon特性分解为一系列具体的、可执行的子任务(如“实现库存预占接口”、“设计预占记录表”、“编写预占过期定时任务”)。
- 分派与执行:对于每个子任务,主控智能体结合第二重规约中对应的精确约束(如接口Schema、数据模型),生成具体的开发指令,分派给编码智能体(Coder Agent)。
- 验证与集成:编码智能体产出代码后,验证智能体(Tester Agent)会直接运行第二重规约中对应的可执行验收条件(测试用例),验证代码是否符合所有规约。构建智能体(Builder Agent)则根据规约中的依赖关系,执行集成和部署。
整个过程中,规约是唯一的事实来源。任何对需求的修改,都首先更新第一重规约,然后通过工具链(可能是编译器、转换器)同步更新第二重规约,从而自动触发相关任务的重新规划、代码的重新生成或测试的重新运行。这形成了一个闭环的、基于规约的智能体开发流水线。
3. 核心组件与实现路径
要将CodeSpec从理念落地,需要构建或集成一系列核心组件。这里我结合现有的工具链和可能的实现,勾勒出一个可行的技术栈。
3.1 规约定义语言与工具
首先,你需要一种方式来定义第一重规约。虽然可以用YAML或Markdown,但为了更好的结构化和可转换性,定义一个轻量级DSL是更专业的选择。
- 自定义DSL示例:你可以设计一个类似
feature.spec的文件格式。feature 订单库存预占 { goal: "防止超卖,确保库存一致性" actor User, InventoryService, OrderService flow 正常预占 { given User submits order with items when InventoryService.preempt is called then inventory_hold record created with status RESERVED and OrderService receives success response } rule 预占时效 { reservation_ttl: 30.minutes on_expiry: release_inventory } data InventoryHold { id: string sku: string quantity: integer status: enum(RESERVED, CONFIRMED, RELEASED) expires_at: datetime } accept 库存充足 { call InventoryService.preempt(sku="A001", qty=2) expect response.success == true expect db.InventoryHold.count where status=RESERVED == 1 } } - 工具选型:你可以使用像
ANTLR或Tree-sitter来为这个DSL编写语法解析器,将其解析为抽象语法树(AST)。也可以基于现有的DSL框架,如JetBrains MPS或Eclipse Xtext,但后者可能过重。
3.2 规约转换器与生成器
这是连接“双重规约”的核心引擎。它需要将第一重规约的AST,转换为各种第二重规约。
- 转换目标:
- API契约:生成OpenAPI 3.0规范的YAML/JSON文件。你的DSL中的
flow和rule可以转换为API路径、请求体和响应体的Schema。 - 数据模型:生成SQL DDL语句、Prisma Schema、或Python Pydantic/Go Struct定义。DSL中的
data块直接对应于此。 - 状态机定义:生成XState或Spring State Machine的配置。DSL中
data块内的status枚举和rule中的状态触发条件可用于此。 - 测试脚手架:生成Jest、Pytest或JUnit的测试文件框架,将
accept块转换为具体的测试用例函数,包含基本的断言。
- API契约:生成OpenAPI 3.0规范的YAML/JSON文件。你的DSL中的
- 实现方式:为每个转换目标编写一个“生成器”(Generator)。这些生成器遍历DSL的AST,根据不同的节点类型(如
flow节点、data节点、accept节点)输出对应的代码或配置文本。这本质上是一个模板渲染的过程,可以使用Jinja2、Handlebars等模板引擎。
3.3 智能体集成接口
为了让AI智能体理解并利用这些规约,你需要提供规约的查询和解释接口。
- 规约服务:构建一个轻量级服务,暴露以下能力:
GET /spec/features:列出所有特性规约。GET /spec/features/{id}:获取某个特性的完整双重规约表示(例如,一个包含人读和机读部分的JSON)。POST /spec/validate:接收一段代码或一个API设计,验证其是否符合某个特性规约的约束。
- 上下文注入:在给编码智能体(如ChatGPT API、Claude API)的Prompt中,结构化地插入相关规约。例如:
“你正在实现
订单库存预占特性。请严格遵循以下规约:业务目标:防止超卖。接口契约:POST /inventory/preempt请求体需符合Schema:{“sku”: string, “quantity”: integer}。数据模型:预占记录表inventory_holds必须包含字段:id, sku, quantity, status, expires_at。关键业务规则:预占有效期为30分钟(RESERVATION_TTL)。 请生成实现该接口的Go语言Gin框架代码。”
3.4 版本控制与生命周期管理
规约必须与代码一样进行版本控制(Git)。关键实践包括:
- 规约即代码:将
.spec文件放在项目根目录的specs/文件夹下,与src/和tests/并列。 - 规约变更触发CI:当
.spec文件发生变更时,CI流水线(如GitHub Actions)应自动运行:- 规约语法校验。
- 重新生成所有第二重规约(OpenAPI、测试脚手架等)。
- 运行所有基于规约生成的测试,确保现有代码仍然符合旧的规约(这是回归测试)。
- 可以可选地触发一个任务,通知相关AI智能体或开发人员,某个特性的规约已更新,相关实现可能需要调整。
4. 实战演练:构建一个简单的CodeSpec原型
理论说再多不如动手。我们来尝试为一个“用户账户激活邮件重发”功能,构建一个最小可行的CodeSpec流程。
4.1 第一步:定义第一重规约
我们创建一个specs/user_reactivation.spec文件:
Feature: 账户激活邮件重发 Goal: 允许未激活用户安全地重新请求激活邮件,提升激活率。 Actor: UnactivatedUser, UserService, EmailService Flow: 用户请求重发激活邮件 Trigger: 未激活用户访问登录页,点击“重发激活邮件”。 Steps: 1. 用户输入注册邮箱。 2. 系统验证邮箱是否存在且对应账户未激活。 3. 系统生成新的激活令牌(带时效),并使其旧令牌失效。 4. 系统发送包含新激活链接的邮件。 5. 用户收到邮件,点击链接完成激活。 Business Rules: - 激活令牌有效期: 24小时。 - 同一邮箱,1小时内最多请求3次重发。 - 新令牌生成后,该邮箱所有旧的未使用激活令牌立即失效。 Data: User: - id (PK) - email (Unique) - is_active (Boolean) ActivationToken: - token (PK) - user_id (FK) - created_at - expires_at - is_used Acceptance Criteria: - AC1: 输入已激活用户的邮箱,提示“账户已激活,请直接登录”。 - AC2: 输入不存在的邮箱,提示“邮箱未注册”,但出于安全考虑,UI提示与AC1相同(“邮件已发送,请查收”)。 - AC3: 1小时内第4次请求同一邮箱,返回错误“请求过于频繁,请稍后再试”。 - AC4: 成功请求后,旧的未过期令牌状态应标记为失效。4.2 第二步:实现规约转换器(关键环节)
我们写一个简单的Python脚本spec_compiler.py,它读取上面的YAML,并生成第二重规约。
- 生成OpenAPI Schema片段:
# spec_compiler.py 部分代码 import yaml import json with open('specs/user_reactivation.spec.yaml', 'r') as f: spec = yaml.safe_load(f) # 生成请求/响应Schema openapi_spec = { "paths": { "/api/v1/user/resend-activation": { "post": { "summary": spec['Feature'], "requestBody": { "required": True, "content": { "application/json": { "schema": { "type": "object", "properties": { "email": {"type": "string", "format": "email"} }, "required": ["email"] } } } }, "responses": { "200": {"description": "成功(无论邮箱是否存在,统一返回此状态)"}, "429": {"description": "请求过于频繁"} } } } } } # 写入文件 with open('generated/openapi.json', 'w') as f: json.dump(openapi_spec, f, indent=2) - 生成Prisma Schema片段:
prisma_schema = """ model User { id String @id @default(cuid()) email String @unique isActive Boolean @default(false) tokens ActivationToken[] } model ActivationToken { token String @id @default(uuid()) userId String user User @relation(fields: [userId], references: [id]) createdAt DateTime @default(now()) expiresAt DateTime isUsed Boolean @default(false) } """ with open('generated/prisma.schema', 'w') as f: f.write(prisma_schema) - 生成测试脚手架(以Jest为例):
test_code = """ describe('POST /api/v1/user/resend-activation', () => { beforeEach(async () => { // 清空测试数据库 }); it('AC1: should return generic success for already active user', async () => { // 1. 创建一个 isActive=true 的用户 // 2. 调用接口 // 3. 断言返回200,且数据库没有新令牌生成 }); it('AC3: should return 429 on 4th request within an hour', async () => { // 1. 创建一个未激活用户 // 2. 模拟3次请求(可以mock时间) // 3. 发起第4次请求 // 4. 断言返回429 }); }); """ with open('generated/__tests__/reactivation.spec.js', 'w') as f: f.write(test_code)
4.3 第三步:集成AI智能体进行开发
现在,我们可以将生成的规约提供给AI。假设我们使用一个类似smol-developer的智能体框架。
我们创建一个agent_prompt.md文件作为智能体的指令:
# 开发任务:实现“账户激活邮件重发”API端点 ## 规约摘要 - **功能**:允许未激活用户重新请求激活邮件。 - **关键约束**: 1. 端点:`POST /api/v1/user/resend-activation` 2. 请求体:`{ "email": string }` 3. 安全规则:无论邮箱是否存在/是否激活,前端均显示“邮件已发送”。后端需区分但不泄露信息。 4. 限流:同一邮箱1小时内最多3次请求。 5. 令牌管理:新令牌生成后,使该用户所有旧未使用令牌失效。令牌有效期24小时。 ## 生成的工程规约(你必须严格遵守) 1. **API契约**:详见 `./generated/openapi.json` 2. **数据模型**:详见 `./generated/prisma.schema`。你将在 `prisma/schema.prisma` 中找到完整的模型定义。 3. **数据库**:我们使用PostgreSQL,Prisma ORM。 ## 你的任务 请使用Node.js (Express框架) 和 Prisma Client 实现这个API端点。 请将代码输出到 `src/routes/userReactivate.js`。 请确保实现所有业务规则和验收标准。然后,我们将这个Prompt和生成的openapi.json、prisma.schema文件一起,提交给一个强大的LLM(如GPT-4、Claude 3)。智能体生成的代码,其接口形状、数据操作逻辑,都将被严格约束在规约定义的范围内。
4.4 第四步:验证与闭环
智能体生成代码后,我们立即可以运行之前由规约转换器生成的测试脚手架(reactivation.spec.js)。这些测试就是第二重规约中“可执行”部分的具体化身。测试的通过与否,直接验证了智能体产出的代码是否符合最初的业务规约。
如果测试失败,我们可以将错误信息反馈给智能体,要求其修正。如果规约本身需要变更(比如产品经理说“令牌有效期改成12小时”),我们只需修改第一重的.spec.yaml文件,重新运行转换器生成新的测试和API Schema,智能体就能基于新的约束进行代码调整或重写。
5. 挑战、心得与最佳实践
在实际探索和概念验证中,我总结出以下几个关键点和挑战。
5.1 主要挑战与应对策略
规约DSL的设计复杂度:DSL设计得太简单,表达能力不足;设计得太复杂,学习成本和工具链开发成本激增。
- 心得:从最小必要集合开始。最初只支持
Feature、Flow、Rule、Data、Accept这几个核心元素。优先覆盖当前项目最痛的点(比如API契约和基础数据模型)。随着项目演进,再逐步扩展DSL语法。可以借鉴Cucumber Gherkin或Azure DevOps的YAML Pipeline语法。
- 心得:从最小必要集合开始。最初只支持
规约与代码的同步性:最怕规约更新了,但生成的代码或测试没更新,或者工程师直接改了代码但没更新规约,导致两者不一致。
- 心得:将规约检查纳入CI/CD强制门禁。在PR合并前,CI流水线不仅要跑测试,还要跑一个“规约一致性检查”。这个检查可以包括:a) 验证现有代码能否通过所有由规约生成的测试;b) 使用静态分析工具,检查代码中的API路径、数据模型是否与生成的OpenAPI/Prisma Schema匹配。不一致则阻塞合并。
智能体的理解与遵从能力:即使提供了精确规约,当前LLM的能力仍可能“跑偏”或产生不符合上下文的代码。
- 心得:Prompt工程至关重要。不要简单地把规约扔给AI。要结构化、分层次地提供信息。先给目标,再给约束,最后给示例。在关键业务规则处,使用“必须”、“禁止”、“确保”等强约束性词语。同时,生成的代码必须经过严格的自动化测试,这是最后的防线。
5.2 CodeSpec适用的场景与团队
CodeSpec不是银弹,它引入了一定的前期设计和工具链成本。它最适合以下场景:
- 团队规模:中大型团队,特别是跨职能、异地协作的团队。
- 项目类型:核心业务逻辑复杂、生命周期长、需要长期维护的企业级应用或平台。
- 技术栈:正在或计划大量采用AI编码助手(如GitHub Copilot、通义灵码)进行日常开发,并探索更自动化Agent工作流的团队。
- 痛点:深受需求误解、文档过时、接口不一致、回归测试困难等问题困扰的团队。
对于小型、快速迭代的初创项目,或者一次性脚本开发,引入完整的CodeSpec可能过度设计。但即使在这些场景,借鉴其“将需求转化为可执行约束”的思想,用简单的脚本将Markdown需求自动转为测试用例,也能带来巨大收益。
5.3 从何处开始实践
如果你对这个理念感兴趣,我建议按以下路径逐步实施:
- 手动实践期:在一个新特性开始前,尝试用结构化的YAML或Markdown表格,严格定义它的
Flows、Rules、Data和Acceptance Criteria。然后,手动根据这份文档去编写对应的接口测试(如Postman Collection)和数据库迁移脚本。感受一下“规约先行”带来的清晰感。 - 工具辅助期:编写一些简单的脚本,将你定义的结构化规约(YAML)自动转换为API文档(Swagger UI)和基础的测试文件框架。这一步可以极大地提升效率。
- 智能体集成期:在编写代码时,有意识地将规约的关键片段复制到AI编码助手的Prompt中,观察其生成代码的准确率是否提升。尝试为某个简单特性,像第4章那样,构建一个从规约到Prompt再到代码生成的完整迷你流程。
- 平台化建设期:当团队认可其价值后,可以考虑投资建设内部的CodeSpec平台,包括DSL编辑器、转换引擎、规约仓库和与CI/CD、AI Agent平台的深度集成。
最终,CodeSpec的目标不是增加官僚流程,而是通过精确的、可执行的“共同语言”,消灭沟通中的模糊地带,让人类和AI智能体能真正高效、可靠地协同工作,攻克那些漫长而复杂的特性开发挑战。这条路还很长,但起点就在于下一次写需求文档时,多问自己一句:“这个描述,能直接变成测试用例吗?”