一、这套方法解决什么问题
在需求开发中,最容易低效的地方不是写代码,而是:
- 需求还没想清楚就开始改代码
- 讨论结论散落在聊天里,后面实现时忘掉
- Codex 根据不完整上下文"猜实现"
- 代码改完后才发现老数据、新数据、边界条件没对齐
Codex + OpenSpec 的正确用法,是先把讨论沉淀成可执行的变更文档,再让 Codex 按文档实现。
简单说:
text
先讨论清楚 -> 写入 OpenSpec -> 再实现代码 -> 最后归档
OpenSpec 是"需求和设计的锚点",Codex 是"帮你分析、补文档、落代码的执行者"。
二、核心工作流
1. 先用 propose 创建变更
当你有一个新需求或改造点时,先不要直接让 Codex 改代码,而是先创建 OpenSpec change。
示例:
text
/opsx:propose batch-download-store-connector-search
RPA 文档列表支持店铺/连接器切换搜索。
连接器搜索最终按 connector_id 查。
RobotReportFileDTO 只有 connectorId,没有 connectorName。
connectorName 需要在落库前通过 connectorId 补齐。
老数据 connector_id / connector_name 允许为空,第一期不回填。
生成后会得到类似目录:
text
openspec/changes/batch-download-store-connector-search/
├── proposal.md
├── design.md
├── tasks.md
└── specs/
└── rpa-documents/
└── spec.md
这些文件分别负责:
proposal.md:说明为什么要做、做什么、不做什么design.md:说明技术方案、关键决策、风险和取舍tasks.md:拆成可执行任务spec.md:沉淀可验证的行为要求
2. 讨论中不断修正 OpenSpec
OpenSpec 不是一次性写完的,它应该随着讨论变准。
比如这次我们讨论连接器搜索时,最开始容易误以为:
text
RobotReportFileDTO 和 RobotReportFileParam 都有 connectorId / connectorName
但看代码后发现真实情况是:
text
RobotReportFileDTO 有 connectorId,没有 connectorName
RobotReportFileParam 有 connectorId,也有 connectorName
这时就应该让 Codex 修正 OpenSpec:
text
把 batch-download-store-connector-search 里的结论修正一下:
RobotReportFileDTO 只有 connectorId,没有 connectorName。
connectorName 需要在 convertFile 前基于 connectorId 补齐。
老数据第一期允许为空,不通过 job_uuid 粗暴反推。
这个动作非常关键。
如果文档不修正,后面 /opsx:apply 时 Codex 可能会按照错误前提实现。
3. 文档确认后再 apply 实现
当你觉得 proposal.md、design.md、tasks.md、spec.md 已经把需求说清楚了,再开始实现。
示例:
text
/opsx:apply batch-download-store-connector-search
这时 Codex 会读取 OpenSpec 里的文档,按任务改代码。
你也可以限制范围:
text
/opsx:apply batch-download-store-connector-search
先只实现后端,不动前端。
或者:
text
/opsx:apply batch-download-store-connector-search
只做 connector_id / connector_name 落库链路,查询接口下一步再做。
4. 实现后检查和补充
实现过程中,你可以继续追问:
text
这个 connectorName 为什么放在 manager 层补?
text
老数据 connector_id 为空时会不会被连接器搜索命中?
text
帮我 review 这次改动有没有破坏店铺搜索。
如果发现设计还要调整,先回到 OpenSpec 补文档,再继续实现。
三、什么时候用 OpenSpec
适合用 OpenSpec 的场景:
- 涉及多个文件、多层架构、多端联动
- 有新老数据兼容问题
- 有查询语义、边界条件、互斥规则
- 需要后续别人接手或 review
- 你担心 Codex 改代码时跑偏
不一定要用 OpenSpec 的场景:
- 改一个错别字
- 简单补日志
- 明确的一行 bug fix
- 纯粹查代码、解释代码
一个简单判断:
text
如果这个改动需要先解释"为什么"和"边界是什么",就先用 OpenSpec。
如果这个改动一句话能说清楚,直接让 Codex 改代码即可。
四、推荐提问模板
1. 创建变更
text
/opsx:propose <change-name>
我要做 <功能/改造>。
背景:
- ...
目标:
- ...
非目标:
- ...
关键约束:
- ...
已知边界:
- ...
示例:
text
/opsx:propose batch-download-store-connector-search
我要让 RPA 文档列表支持店铺/连接器切换搜索。
背景:
- 当前主表 qymall_robot_report_file 有 store_name
- 当前主表没有 connector_id / connector_name
- RobotReportFileDTO 有 connectorId,没有 connectorName
目标:
- 店铺模式只按 storeName 查
- 连接器模式最终按 connector_id 查
- 列表返回 connectorName
非目标:
- 第一期不回填历史数据
- 不用 job_uuid 粗暴反推历史 connectorId
关键约束:
- connectorName 需要在落库前通过 connectorId 补齐
2. 修正文档
text
把 <change-name> 里的 OpenSpec 文档修正一下:
<指出错误前提>
<给出新结论>
<要求同步修改 proposal/design/tasks/spec>
示例:
text
把 batch-download-store-connector-search 修正一下:
RobotReportFileDTO 没有 connectorName。
connectorName 不能认为是上游已经传了。
需要在 robotManager.robotReportFile(param) 里、convertFile 前通过 connectorId 查询补齐。
老数据第一期允许为空。
3. 开始实现
text
/opsx:apply <change-name>
或者限制范围:
text
/opsx:apply <change-name>
只实现后端落库链路,不动前端。
4. 让 Codex 解释实现
text
请按链路解释 connectorId 从请求到落库怎么流转。
text
请说明 connectorName 为什么要在 convertFile 前补齐。
text
请说明老数据为什么第一期不建议回填。
5. 让 Codex 做 review
text
review 这次 batch-download-store-connector-search 的改动,重点看:
- 新数据 connectorId 是否能落库
- connectorName 是否能补齐
- 老数据是否会误命中
- 店铺搜索是否被影响
五、一个完整例子:连接器搜索
1. 先讨论事实
先让 Codex 查代码:
text
帮我确认 RobotReportFileDTO、RobotReportFileParam、QymallRobotReportFileDO 里有没有 connectorId 和 connectorName。
得到事实:
text
RobotReportFileDTO:有 connectorId,没有 connectorName
RobotReportFileParam:有 connectorId,有 connectorName
QymallRobotReportFileDO:当前没有 connectorId / connectorName
2. 再沉淀设计
把事实写进 OpenSpec:
text
新数据 connectorId 来自 /robot/reportFile 上报参数。
connectorName 当前不能从 DTO 直接获得。
落库前需要基于 connectorId 查连接器名称并补齐。
老数据 connector_id / connector_name 允许为空。
3. 再实现
实现时的核心链路应该是:
java
RobotReportFileDTO robotReportDTO = request.parseReqParam();
RobotReportFileParam param = robotDomainConverter.convert(robotReportDTO);
param.setTenantId(tenantId);
return robotManager.robotReportFile(param);
进入 manager 后:
java
fillConnectorName(robotReportFileParam);
QymallRobotReportFileDO robotReportFileDO = robotConverter.convertFile(robotReportFileParam);
reportFileDAO.save(robotReportFileDO);
这里的关键点是:
connectorId由 DTO 转 Param,再转 DOconnectorName需要在 Param 转 DO 前补齐- 如果先
convertFile再补 Param,就晚了
4. 老数据策略
老数据以前没有保存连接器字段,第一期策略应该是:
text
允许 connector_id 为空
允许 connector_name 为空
连接器搜索不命中 connector_id 为空的老数据
不通过 job_uuid 粗暴反推连接器归属
历史回填后续单独评估
六、常见误区
误区 1:把 Param 有字段当成 DTO 也有字段
这次我们就遇到了。
RobotReportFileParam 有 connectorName,不代表 RobotReportFileDTO 也传了 connectorName。
正确做法是沿着链路确认:
text
DTO -> Param -> DO -> 表字段
每一层都要确认字段是否存在、是否有值、是否会自动映射。
误区 2:只补 DO 字段就以为能落库完整
只给 QymallRobotReportFileDO 补 connectorName 不够。
如果 Param 里的 connectorName 是空的,MapStruct 也只能映射出空值。
所以要么上游补传,要么后端查表补齐。
误区 3:用 job_uuid 推老数据 connectorId
job_uuid 只能说明任务或计划关系,不能稳定证明某条结果文件来自哪个连接器。
如果一个计划关联多个连接器,强行用 job_uuid 回填会有误命中风险。
误区 4:文档错了还继续 apply
OpenSpec 文档是 Codex 实现时的输入。
如果文档里写错了,Codex 很可能按错的方向实现。
所以讨论中发现结论变化,要先修文档,再实现。
七、你可以怎么和 Codex 配合
比较推荐的协作方式是:
text
你负责指出业务事实和疑问。
Codex 负责查代码、找证据、补 OpenSpec、实现代码。
你可以多问这种问题:
- "这句话有代码依据吗?"
- "这个字段是从哪里来的?"
- "老数据会不会受影响?"
- "这个结论要不要写进 OpenSpec?"
- "现在是改文档还是改代码?"
这些问题都很有价值,因为它们能阻止需求跑偏。
八、执行前检查清单
在 /opsx:apply 前,建议检查:
proposal.md是否说清楚为什么做design.md是否说清楚关键技术决策tasks.md是否能直接指导实现spec.md是否覆盖了新数据、老数据、异常场景- 是否明确哪些不做
- 是否明确字段来源
- 是否明确老数据策略
如果这些都清楚,再让 Codex 实现,效率会高很多。