实践:从 Principle 到工作流
前面几节建立了规约的方法论框架:信息分层、三个维度、交叉验证、迭代循环。这些概念回答了"为什么"和"是什么"。这一节回答"在真实项目里长什么样"。
先看一个社区成员开发的工作流框架 AILock-Step,展示 principle 怎么变成具体的文档结构和流程。然后用一个在 RuoYi-Vue 项目上跑通的 OKR 系统需求,展示这些结构和流程在实际运行时产出了什么。前者是空壳,后者是填充。
框架设计:Principle 怎么变成工具
AILock-Step Feature Workflow 是一个基于 Claude Code 的文档驱动开发框架,用 skill(slash command)编排 feature 的完整生命周期。它在一个 AI agent 平台项目上跑过 40 多个 feature,完整源码在 GitHub 上公开。
这里展示的是一个实现方案。值得关注的是 principle 在具体系统中怎么变成可操作的设计决策,而具体的目录结构和配置格式可以根据项目需要调整。
分层原则的落地
前面讲了信息有四个层级(vision、架构、feature、task),应该用不同的文档承载、按不同的策略加载。AILock-Step 用三类文档实现了这个分层。
CLAUDE.md 承载跨层的强制约束。这是 Claude Code 每次启动时自动加载的文件。内容是整个项目生命周期内都不太变的硬性规则。在 RuoYi-Vue 项目中,这份文件包含了项目架构概述(模块依赖关系、启动入口、默认端口)、后端关键约定(Controller 标准写法、权限注解模式、操作日志注解)、前端约定(API 请求封装、路由加载方式、权限指令)和数据库规范(逻辑删除字段、树形结构字段)。这些约束不属于某一个 feature,它们横跨所有任务,每次都需要加载。
project-context.md 承载高层信息,对应 vision 层和架构层。同样以 RuoYi-Vue 项目为例,这份文件的实际内容是:
Technology Stack
| Layer | Technology | Version |
| Runtime | JDK | 17 |
| Backend | Spring Boot | 4.0.3 |
| ORM | MyBatis | 4.0.1 |
| Auth | Spring Security + JWT | - |
| Cache | Redis | - |
| DB | MySQL | - |
| Frontend | Vue 2 | 2.6.12 |
| UI Library | Element UI | 2.15.14 |
Directory Structure
ruoyi-admin/ # 启动模块 (Controller 层)
ruoyi-framework/ # 框架核心 (Security, JWT, 全局配置)
ruoyi-system/ # 系统业务 (Service + Mapper)
ruoyi-common/ # 公共模块 (工具类, 基类, 注解)
ruoyi-ui/ # 前端 Vue 2 项目
Code Patterns
Controller: XxxController extends BaseController, @RestController
Service: IXxxService (接口) → XxxServiceImpl (实现)
Permission: @PreAuthorize("@ss.hasPermi('module:entity:action')")
Response: AjaxResult {code, msg, data} / TableDataInfo {total, rows}
Critical Rules
- 新增业务在 ruoyi-system 添加 Service/Mapper,Controller 放 ruoyi-admin
- 实体类继承 BaseEntity 获得审计字段
- 所有 Controller 接口需配置权限注解
- 分页使用 startPage() + getDataTable()
这份文档限制在 200 行以内。项目上下文每次都被加载到 Agent 的 context 里,太长会挤压留给 spec 和代码的空间。200 行足够放索引级别的内容:技术栈、目录结构、关键规则、代码模式。实现细节留给具体的 feature 文档。
每次完成一个 feature,如果引入了新的技术栈组件、新的代码模式或新的目录结构,project-context.md 会增量更新。
每个 feature 有自己独立的三份文档:spec.md、task.md、checklist.md。这对应 feature 层和 task 层,只在 Agent 执行该 feature 时才被加载,完成后归档。
加载策略:CLAUDE.md 和 project-context.md 每次都加载,feature 文档只在执行对应 feature 时加载。加载不相关 feature 的文档只会制造噪声。
Spec 模板、交叉验证和拆分
每个 feature 的 spec.md 模板把三个维度转化成具体字段:意图维度对应"需求描述""用户价值点""用户故事"三个字段,验收维度对应 Gherkin 场景(每个价值点至少一个正常路径和一个异常路径),约束维度对应"上下文分析"字段(参考代码、相关文档、历史需求)。后面的 OKR 案例会展示这些字段被填入什么内容。
三份文档(spec、task list、checklist)构成交叉验证体系。Spec 是第一次理解(需求到方案),task list 是第二次(方案到步骤),checklist 是第三次(步骤反推验收标准)。当需求的用户价值点超过三个,系统建议按用户价值拆分,每个子 feature 有自己的三份文档,进入同样的迭代循环。
这些是空壳。下面看一个真实需求跑过这套体系后产出了什么。
一个需求的完整 Walkthrough:OKR 系统
这个案例来自社区成员在 RuoYi-Vue 项目上的实践。输入是一句话的需求,产出是一份经过两轮迭代修正的产品规格书,最终驱动生成了 12 个后端文件、4 个前端文件,通过了 26/28 项合规检查和 7 个 E2E 测试。
一个重要的说明:这个案例用一句话作为输入,是因为它是一个边界清晰、规模可控的 demo 级需求。在正式的产品开发中,输入应该是经过产品流程(vision → user journey → 需求拆分)之后的结构化需求。一句话能跑通,是因为 OKR 系统的业务逻辑足够简单,Agent 能从一句话中推导出大部分决策。复杂的业务需求不具备这个条件,缺少上游的产品工作直接给 Agent 一句话,结果大概率会偏。
输入
需求:"实现一个OKR系统,支持按季度配置每个员工的O和KR。"
Agent 展开
Agent 从这句话中识别出三个核心 feature:目标管理(Objective Management)、关键结果管理(Key Result Management)、OKR 季度总览(Quarter Overview)。每个 feature 有独立的 user story 和 acceptance criteria。以下是 Agent 生成的 spec 中"目标管理"这个 feature 的实际内容:
### Feature: 目标管理 (Objective Management)
**User Story**: As a 管理员, I want to 为员工创建季度目标
so that 团队可以明确每个人的工作方向。
**Acceptance Criteria**:
- [ ] 新增目标时,必须选择员工、季度和填写目标标题
- [ ] 目标列表支持按季度和员工姓名筛选
- [ ] 可以编辑目标的标题、状态
- [ ] 删除目标时,同时删除该目标下所有关键结果
- [ ] 目标列表中展示每个目标关联的KR数量
**Key Rules**:
- 同一员工在同一季度下可以创建多个目标,无数量上限
- 目标标题最大长度200字符
- 季度下拉选项范围:从上一年Q1到下一年Q4,默认选中当前季度
- 新建目标的默认状态为"未开始(0)"
注意这份 spec 的几个特征。User story 用标准的"As a / I want to / so that"格式,锁定了谁、要什么、为什么。Acceptance criteria 的每一条都是一个可检查的条件,有明确的 pass/fail 判断标准。Key rules 定义了具体的业务约束(数量限制、字符限制、默认值)。这三个部分分别对应前面讲的意图、验收、约束三个维度。
Agent 生成 spec 时做的第一步是锁定术语。它在 spec 开头放了一张术语表:
| Term | Definition |
|------|-----------|
| Objective (O/目标) | 一个季度内某员工需要达成的定性目标 |
| Key Result (KR/关键结果) | 衡量目标达成程度的量化指标 |
| 季度 (Quarter) | 格式为 YYYY-QN,例如 2026-Q2 |
| 完成率 | 单个KR的完成百分比,取值范围0~100,手动填写的整数值 |
| 目标状态 | 生命周期状态:未开始(0)、进行中(1)、已完成(2) |
"完成率"被定义为"手动填写的整数值"。如果不锁定这个定义,后续实现中 Agent 可能在不同地方做不同的假设:完成率是自动计算的还是手动填的?是百分比还是小数?是实时更新的还是定期快照?术语表在 spec 最前面出现,确保后面所有字段和场景用的是同一套语言。
Agent 还为核心操作生成了用户流程(User Flow),把验收维度从"条件列表"扩展为"操作序列":
### 创建OKR流程
**Trigger**: 管理员进入目标管理页面,点击"新增"按钮
**Steps**:
1. 系统弹出新增目标对话框(员工下拉、季度下拉、目标标题输入)
2. 管理员填写信息,点击"确定"
3. 系统创建目标记录,提示"新增成功"
4. 管理员在目标列表中点击操作列的"新增KR"按钮
5. 系统弹出新增KR对话框(KR标题、目标值)
6. 管理员填写KR信息,点击"确定"
**End State**: 目标列表中显示新目标,该目标下有一条KR记录
约束维度方面,Agent 生成了 API Contract 表,界定前后端的通信边界:
| Endpoint | Method | URL | Parameters |
|----------|--------|-----|-----------|
| 目标列表 | GET | /system/okrObjective/list | quarter, employeeName |
| 新增目标 | POST | /system/okrObjective | Body: domain object |
| 修改目标 | PUT | /system/okrObjective | Body: domain object |
| 删除目标 | DELETE | /system/okrObjective/{ids} | — |
| KR列表 | GET | /system/okrKeyresult/list | objectiveId (required) |
| 季度总览 | GET | /system/okrObjective/overview | quarter (required) |
Agent 还生成了 Out of Scope 列表,明确排除了 8 项功能(员工自助录入、OKR 评分考核、目标对齐、历史追踪、导出、通知、多部门视图、权限细分),每项标注了排除理由。以及 5 条编号的业务规则:
| # | Rule | Condition | Outcome |
|---|---|---|---|
| BR-001 | 级联删除 | 删除一个目标时 | 该目标下所有KR同步删除 |
| BR-002 | KR数量限制 | 目标下已有5个KR时 | 新增KR按钮禁用 |
| BR-003 | 完成率范围 | 填写完成率时 | 只接受0~100的整数 |
| BR-004 | 默认季度 | 打开新增目标或总览页面时 | 默认选中当前季度 |
| BR-005 | 总览只读 | 在OKR季度总览页面 | 所有数据仅展示 |
从一句话到这份 spec,信息量增加了大约 50 倍。但新增的信息是从那一句话中推导出来的决策,每个字段都在回答一个如果不回答就会在实现阶段产生歧义的问题。这些问题在一句话的需求里全部是隐含的。Agent 的工作是把它们显式化。
迭代:两轮 Review
第一版 spec 有 5 个问题:1 个 Critical、2 个 Major、2 个 Minor。
以下是第一轮 review 发现的 Critical 问题和决策记录的实际内容:
#### Decision 1: CRUD API endpoints completely unspecified
- **严重度**: Critical
- **问题**: Spec 描述了三个 feature 的行为,但没有给出任何 API 契约
- **选项**:
- A) 添加完整的 API 定义(含 request/response schema)
- B) 只列端点 URL 和 HTTP 方法,实现遵循 RuoYi BaseController 标准
- **决策**: B
- **理由**: 基于最小复杂度原则,RuoYi 的 CRUD 模式高度标准化
如果直接交给编码 Agent 而不定义 API 契约,它会自己编造路径和参数格式,前端调用后端的时候对不上。这个项目的决策原则按优先级排列为:最小复杂度 > Demo 可演示性 > 原始意图 > 一致性。大部分决策都落在"最小复杂度"上。
第一轮另外两个 Major 问题的决策记录:
#### Decision 2: KR management UI location is ambiguous
- **严重度**: Major
- **选项**:
- A) 展开行:每个目标行有可展开的子表格显示 KR
- B) 详情页:点击目标行跳转到独立页面
- **决策**: A(展开行)
- **理由**: 基于最小复杂度,展开行不需要新增路由页面;
基于 Demo 可演示性,同一页面内完成操作更紧凑直观
#### Decision 3: Objective status has no transition rules
- **严重度**: Major
- **选项**:
- A) 自由编辑:状态是简单的下拉选择
- B) 自动派生:状态根据 KR 完成率自动计算
- **决策**: A(自由编辑)
- **理由**: 基于最小复杂度,自由下拉不需要实现状态机逻辑
第二轮 review 发现了 4 个问题(0 个 Critical、2 个 Major、2 个 Minor)。问题的严重度在下降,说明 spec 在收敛。第二轮的 Major 问题是:"员工"跟 RuoYi 系统里的 sys_user 是什么关系(决策:直接复用 sys_user,存 user_id 做 FK,join 查询 nick_name),以及"新增KR"按钮应该放在目标行的操作列里始终可见还是放在展开行内部(决策:操作列,始终可见,操作路径更短)。
这 9 个决策中的每一个,如果不在 spec 阶段做出,就会在编码阶段由 Agent 自己做。Agent 的决策可能是合理的,也可能不是。关键问题是:你不知道它做了什么决策,直到你看到代码。在 spec 阶段做一个决策的成本是改一行文字。在代码阶段发现决策不对的成本是改几十行代码加重新测试。
回顾整个链路,人在两轮 review 中对 9 个问题做产品决策,每个决策花几分钟看选项、选一个、确认,总共大概半小时。后面的代码生成、合规检查、E2E 测试,人没有介入。这就是前面讲的分工在实践中的样子:人做意图对齐的判断,Agent 做一致性检查和执行。
从 Spec 到实现
Spec 收敛后进入实现。Agent 根据 spec 创建了 2 张数据库表(biz_okr_objective 和 biz_okr_key_result)、12 个后端文件(domain、mapper、service、controller 各两套,加上 XML 映射文件)、4 个前端文件(API 封装和页面组件)和 1 个 SQL 迁移脚本。
实现完成后,合规检查逐项对照 spec 验证。以下是合规报告的摘要:
## Acceptance Criteria (13/13 PASS)
| # | Criterion | Status |
|---|-----------|--------|
| AC-1 | 新增目标时,必须选择员工、季度和填写目标标题 | PASS |
| AC-2 | 目标列表支持按季度和员工姓名筛选 | PASS |
| AC-3 | 可以编辑目标的标题、状态 | PASS |
| AC-4 | 删除目标时,同时删除该目标下所有关键结果 | PASS |
| AC-5 | 目标列表中展示每个目标关联的KR数量 | PASS |
| ... | (其余 8 条 AC 全部 PASS) | PASS |
## Business Rules (5/5 PASS)
| # | Rule | Status | Notes |
|---|------|--------|-------|
| BR-001 | 级联删除 | PASS | @Transactional cascade delete |
| BR-002 | KR数量限制(max 5) | PASS | Fixed: backend ServiceException + frontend warning |
| BR-003 | 完成率范围0~100 | PASS | el-input-number enforces |
| BR-004 | 默认季度 | PASS | Fixed: handleAdd() sets form.quarter=currentQuarter |
| BR-005 | 总览只读 | PASS | No edit buttons on overview |
注意 BR-002 和 BR-004 的 Notes 列有"Fixed"标记。这意味着第一次合规检查时这两条没有通过,修复后才变成 PASS。BR-002 的问题是 KR 数量限制只有前端的 Element UI 提示,缺少后端的 ServiceException 校验。如果绕过前端直接调 API,就能突破 5 个的限制。BR-004 的问题是新增目标时季度下拉框没有自动选中当前季度。
这类问题的模式很有代表性:spec 写对了,但代码没跟上。Agent 在实现时遗漏了 spec 中某条规则的全部含义。合规检查的价值在于它用 spec 作为标准,逐条比对实现,把这类遗漏在交付前暴露出来。
7 个 E2E 测试全部通过,覆盖了从新增目标到删除目标的完整用户流程。以下是其中两个测试的代码片段:
test('01: 新增目标', async ({ page }) => {
await navigateTo(page, 'OKR管理', '目标管理');
await page.locator('button:has-text("新增")').click();
await waitForDialog(page);
// 选择员工
await page.locator('.el-dialog__body input[placeholder="请选择员工"]').click();
await page.locator('.el-select-dropdown:visible .el-select-dropdown__item')
.first().click();
// 验证季度默认值(对应 BR-004)
const quarterInput = page.locator('.el-dialog__body input[placeholder="请选择季度"]');
await expect(quarterInput).toHaveValue(getCurrentQuarter());
// 填写标题并提交
await page.locator('.el-dialog__body input[placeholder="请输入目标标题"]')
.fill('提升客户满意度');
await page.locator('.el-dialog__footer:visible button:has-text("确 定")').click();
await expect(page.locator('.el-message--success')).toBeVisible({ timeout: 5000 });
await expect(page.locator('.el-table__body-wrapper'))
.toContainText('提升客户满意度');
});
test('07: 删除目标(级联删除)', async ({ page }) => {
// 对应 BR-001:删除目标时同步删除所有 KR
await navigateTo(page, 'OKR管理', '目标管理');
await page.locator('.el-table__body-wrapper tbody tr').first()
.locator('button:has-text("删除")').click();
await page.locator('.el-message-box__btns button:has-text("确定")').click();
await expect(page.locator('.el-message--success')).toBeVisible({ timeout: 5000 });
const rows = page.locator('.el-table__body-wrapper tbody tr');
await expect(rows).toHaveCount(0);
});
这些测试直接对应 spec 的 acceptance criteria 和 business rules。测试 01 验证了 AC-1(新增目标的必填字段)和 BR-004(默认季度)。测试 07 验证了 BR-001(级联删除)。测试怎么设计、怎么防止 Agent 自己出题自己答,这些属于验证的话题,下一章展开。
本章小结
规约的本质是意图对齐。Vibe coding 的失败源于意图活在对话里,而对话是一个会膨胀、会矛盾、会丢失内容的载体。Agent 有限的 context 和不均匀的注意力,让意图对齐从一个沟通问题变成了一个工程问题。
这个工程问题的解法建立在两个基础上。信息天然有层级,不同层级的演进速度和适用范围不同,你需要用结构化的方式把它们拆分到不同的文档里,高层持久加载,任务层按需加载。在每一层,你用三个维度(意图、验收、约束)表达清楚,其中验收维度的核心作用是检测当前层跟上层的对齐有没有丢失。
Spec 是迭代出来的。你写意图,Agent 展开,交叉验证暴露问题,你修正或拆分,再验证,直到收敛。在这个循环里,只有人能判断意图是否对齐,因为意图只存在于人脑子里。循环该跑多重取决于做错了返工的代价有多大。
OKR 系统的案例展示了这条链路的实际运行:一句话需求经过结构化展开和两轮迭代修正,变成了一份覆盖三个维度的可执行 spec。在这个过程中,人的投入集中在 spec review 阶段的产品决策,后面的实现和验证由 Agent 完成。
规约解决的是"做什么"的问题。但这个案例里的合规检查暴露了一个事实:做对了 spec 不等于做对了代码。Spec 写了"默认选中当前季度",代码里没实现。Spec 写了"最多 5 个 KR",后端校验漏掉了。怎么系统化地验证 Agent 的产出跟 spec 一致,下一章展开。