本文档是SpecExtract和SpecExecute技能的完整实战记录,基于真实代码验证。
今天验证了SpecExtract和SpecExecute两个技能:
| 技能 | 验证结果 |
|---|---|
| SpecExtract | ✅ 能从代码真实提取Spec(28个action、8个entity、4个rule) |
| SpecExecute | ✅ 能执行Delta变更(添加compress参数、新建pdf_password模块) |
产品A新功能 → 开发从头理解产品A(3天) → 写代码(2天) 产品B新功能 → 开发从头理解产品B(3天) → 写代码(2天) 产品C新功能 → ...
每个产品都需要从零开始理解。开发把80%的时间花在了"理解"而不是"写代码"上。
| 传统方式的问题 | 说明 |
|---|---|
| 需求文档不完整 | 只写了"做什么",没写"怎么做" |
| 代码和文档脱节 | 代码改了,文档没改 |
| 知识在人脑子里 | 换个开发,一切重来 |
| AI无法理解产品 | AI能写代码,但不能理解产品业务 |
传统开发:人需要理解产品 AI时代开发:AI也需要理解产品
如果AI不能理解产品,AI辅助开发就是空谈。
产品代码 ──→ SpecExtract ──→ Spec ──→ AI能理解的形式
↓
AI基于Spec开发/定制
核心思路:把产品翻译成AI能理解的语言(Spec),然后让AI基于Spec工作。
| 能力 | 说明 |
|---|---|
| SpecExtract | 让产品上能长AI代理(代码 → Spec) |
| SpecExecute | 长了这个AI代理的产品(Spec → 代码+执行) |
| 方面 | OpenSpec | 我们 |
|---|---|---|
| 核心能力 | 需求变更管理 | Spec提取+执行 |
| 使用对象 | 人类协作 | AI驱动 |
| 来源 | 需求文档 | 产品代码 |
我们借鉴了OpenSpec的格式,但不是OpenSpec本身。
specs/{product}/
├── SPEC.md # 产品概述
├── actions/ # 操作定义(含Scenarios)
│ └── *.md # 每个Action一个文件
├── entities/ # 实体定义
│ └── *.md # 每个Entity一个文件
├── rules/ # 业务规则
│ └── *.md # 每个Rule一个文件
└── changes/ # 增量变更(变更时创建)
└── {change-name}/
├── proposal.md # 变更提案(为什么做)
├── delta.md # Delta specs(变什么)
├── design.md # 技术方案(怎么做)
└── archive.md # 归档记录
| 文件类型 | 职责 | Scenarios | 说明 |
|---|---|---|---|
| actions/*.md | 操作定义 | ✅ 有 | 用户操作流程在这里 |
| entities/*.md | 数据模型 | ❌ 无 | 只有数据结构定义 |
| rules/*.md | 业务规则 | ❌ 无 | 只有规则逻辑(Trigger+Logic) |
核心原则:Scenarios只存在于Action文件中
# Action: pdf_merge
## Service
pdf_merge_service.py
## Functions
### pdf_merge_service
- params: files (List[UploadFile]), options (dict, optional)
- returns: {file (url), filename, totalFiles, fileInfos}
## Scenarios
### Scenario: 普通合并
- **GIVEN:** 至少2个PDF文件列表
- **WHEN:** 调用pdf_merge_service
- **THEN:** 返回合并后的PDF文件URL
# Entity: user
## Basic Info
- table: users
- description: 用户实体,登录凭证为手机号
## Attributes
| Name | Type | Required | Description |
|------|------|----------|-------------|
| id | int | Yes | 用户唯一标识 |
| phone | string | Yes | 手机号 |
| created_at | datetime | Yes | 注册时间 |
## Relationships
- User → 1:N → Task
- User → 1:1 → Membership
# Rule: rate_limit
## Trigger
when: 请求频率检查时
## Logic
if request_count > LIMIT:
reject_request()
| 方面 | OpenSpec | 我们 | 评价 |
|---|---|---|---|
| 目录组织 | specs/(按领域) | actions/entities/rules(按代码) | 我们的更贴合代码生成 |
| Scenarios | 独立场景文件 | 只在Actions中 | 我们更清晰,避免重复 |
| 变更管理 | changes/目录 | changes/目录 | 一致 |
| 阶段 | Spec状态 | changes/ |
|---|---|---|
| SpecExtract(初始) | 全量提取,无Delta | ❌ 没有 |
| SpecExecute(变更) | 创建changes/目录 | ✅ 开始有 |
changes/
└── {change-name}/
├── proposal.md # 为什么做
├── delta.md # 变什么
├── design.md # 怎么做
└── archive.md # 归档记录
我们花了大量时间测试和优化技能,发现以下问题:
| 版本 | 问题 | 改进 |
|---|---|---|
| v6.0 | 只有格式规范,没有提取逻辑 | 明确读取真实代码的步骤 |
| v6.1 | 一次扫描太多文件,容易中断 | 拆成任务清单,每次3个文件 |
| v6.2 | 子代理按function拆分action,文件太多 | 明确按service合并 |
| v6.3 | rules文件有Scenarios | 明确rules不能有Scenarios |
| v6.4 | 文件命名混乱 | 明确命名规范 |
| 类型 | 内容 | 说明 |
|---|---|---|
| Entity | 数据模型 | user, task, order, payment, vip_plan等 |
| Action | 操作(含Scenarios) | 按service合并,28个 |
| Rule | 业务规则 | rate_limit, api_access, auth, config |
关键要求:深度挖掘,不要浅看
| 浅看的问题 | 深挖的要求 |
|---|---|
| 只找到API名称 | 追踪完整调用链(API→Handler→Service→Repository) |
| 只找到Model | 看默认值、验证规则、关联关系 |
| 只找到Rule注释 | 找配置位置、调用方、边界条件 |
| 只找到表面逻辑 | 看异常处理、边界case |
深度挖掘案例:VIP队列3:1是怎么来的
浅看:代码里有"VIP优先处理"注释
置信度:60%
深挖:
- 位置:services/task_queue.py:71
- 代码:self.vip_ratio = 3
- 使用:counter % (vip_ratio + normal_ratio) != 0
- 结论:每4次处理,3次VIP,1次普通
置信度:100%
从pdfsystem代码真实提取:
| 类型 | 数量 | 说明 |
|---|---|---|
| Entity | 8 | user, task, order, payment, vip_plan, admin等 |
| Action | 28 | 按service合并后 |
| Rule | 4 | rate_limit, api_access, auth, config |
Action清单(28个): - admin, auth, dashboard, db - idcard_to_pdf, images_to_pdf, img_ocr - membership, office_converter, order, payment - pdf_compress, pdf_merge, pdf_ocr, pdf_rotate, pdf_split - pdf_to_images, pdf_to_text, pdf_to_word, pdf_utility, pdf_watermark - sms, system, task_queue, user, vip_plan, vip_plan_price, word_to_pdf
actions/pdf_merge.md(真实提取):
# Action: pdf_merge
## Service
pdf_merge_service.py
## Functions
### pdf_merge_service
- params: files (List[UploadFile]), options (dict, optional)
- returns: {file (url), filename, totalFiles, fileInfos}
## Scenarios
### Scenario: 合并多个PDF文件
- **GIVEN:** 至少2个PDF文件列表
- **WHEN:** 调用pdf_merge_service
- **THEN:** 返回合并后的PDF文件URL
rules/rate_limit.md(真实提取):
# Rule: rate_limit
## Trigger
when: 请求频率检查时
## Logic
rate_limiter = RateLimiter(rate_limit=100, time_window=60秒)
if is_allowed(client_ip):
proceed()
else:
reject_with_429()
真实代码分析:
# backend/utils/pdf_utils.py (line 149)
def merge_pdfs(input_paths: List[str], output_path: str) -> bool:
# 只有2个参数,不支持compress!
# backend/services/pdf_merge_service.py (line 30)
async def pdf_merge_service(files, options=None):
# options参数被接收但从未使用!
merge_pdfs(file_paths, result_path) # 只传了2个参数
真实Delta发现:
| 项目 | 代码实际 | 状态 |
|---|---|---|
| merge_pdfs参数 | 没有compress | 缺失 |
| service options | 收到但从未使用 | Bug |
Step 1: 创建changes/add-compress-to-merge/
Step 2: 更新Spec - actions/pdf_merge.md
新增compress参数和压缩合并场景:
### pdf_merge_service
- params: files, options (compress: bool, compress_level: str)
- returns: {file, filename, totalFiles, fileInfos}
### Scenario: 压缩合并
- **GIVEN:** 用户勾选了compress=true
- **WHEN:** 调用pdf_merge_service
- **THEN:** 合并后进行压缩处理
Step 3: 更新代码 - pdf_utils.py
def merge_pdfs(input_paths, output_path, compress=False, compress_level="medium"):
# 先执行合并到临时文件
temp_output = output_path + ".tmp.pdf"
# ...合并逻辑...
# 如果需要压缩
if compress:
compress_pdf(temp_output, output_path, compression_level=compress_level)
os.remove(temp_output)
else:
os.rename(temp_output, output_path)
Step 4: 更新代码 - pdf_merge_service.py
# 提取压缩选项
compress = options.get("compress", False) if options else False
compress_level = options.get("compress_level", "medium") if options else "medium"
# 执行合并(支持压缩)
merge_pdfs(file_paths, result_path, compress=compress, compress_level=compress_level)
| 验证项 | 结果 |
|---|---|
| Spec更新 | ✅ |
| pdf_utils.py更新 | ✅ |
| pdf_merge_service.py更新 | ✅ |
| delta.md创建 | ✅ |
| archive.md创建 | ✅ |
扫描代码发现:
- pdf_utils.py 已有 encrypt_pdf() 和 decrypt_pdf() 函数
- 只需要创建service层调用即可
Step 1: 创建actions/pdf_password.md
# Action: pdf_password
## Service
pdf_password_service.py (新建)
## Functions
### pdf_encrypt_service
- params: files, password, permissions
- returns: {file, filename, original_filename}
### pdf_decrypt_service
- params: files, password
- returns: {file, filename, original_filename}
## Scenarios
### Scenario: PDF加密
- **GIVEN:** 用户上传PDF并设置密码
- **WHEN:** 调用pdf_encrypt_service
- **THEN:** 返回加密后的PDF文件URL
### Scenario: PDF解密
- **GIVEN:** 用户上传已加密PDF并提供正确密码
- **WHEN:** 调用pdf_decrypt_service
- **THEN:** 返回解密后的PDF文件URL
Step 2: 创建pdf_password_service.py
新建service调用已有的encrypt_pdf/decrypt_pdf工具函数。
| 验证项 | 结果 |
|---|---|
| actions/pdf_password.md创建 | ✅ |
| pdf_password_service.py创建 | ✅ |
| delta.md创建 | ✅ |
| archive.md创建 | ✅ |
| 组件 | 数量 | 说明 |
|---|---|---|
| Entity | 8 | user, task, order, payment, vip_plan等 |
| Action | 28 | 按service合并 |
| Rule | 4 | rate_limit, api_access, auth, config |
| 场景 | 结果 | 说明 |
|---|---|---|
| SpecExtract提取 | ✅ 通过 | 真实扫描代码,8+28+4个文件 |
| 发现真实Delta | ✅ | merge_pdfs缺少compress参数 |
| Delta变更执行 | ✅ | 更新Spec+代码+archive |
| 新模块添加 | ✅ | pdf_password功能完整添加 |
纸上谈兵不够。技能必须真实执行代码扫描和提取,才能发现问题。
pdfsystem有28个service,如果按function拆分会有60+个action文件。
rules有Trigger+Logic就够了,不需要GIVEN/WHEN/THEN。
测试1证明了Delta模式:识别变更→更新Spec→更新代码→archive。
测试2证明了可复用已有工具函数,只需创建service层。
发现Delta是正常的。处理方式: 1. SpecExtract提取现状 2. SpecExecute基于Spec执行变更 3. 变更后同步更新Spec
| 问题 | 答案 |
|---|---|
| VIP队列3:1 | 硬编码在task_queue.py:71,不可配置 |
| 支付→会员链路 | handle_notification → _process_successful_payment |
| merge_pdfs compress | 之前缺失,现已添加 |
| merge_pdfs options | 之前未使用,现已修复 |
| 密码保护 | pdf_encrypt/decrypt已存在,新建service即可 |
| 技能 | 版本 | 说明 |
|---|---|---|
| spec-extract | v6.4 | 按service合并,rules无Scenarios |
| spec-execute | v5.1 | Delta模式 + 指令执行 |
Spec目录:
/root/.openclaw/agents/xiaoxuezi/specs/pdfsystem/
├── actions/ # 28个文件
├── entities/ # 8个文件
├── rules/ # 4个文件
└── changes/ # 增量变更
├── add-compress-to-merge/
└── add-pdf-password/
本文档是Spec驱动开发的完整实战记录,基于pdfsystem项目的真实验证。