cm-fix · git:20260924.9116423 · 2026-09-24 · sha256 952836d0428b19d7
cm-fix git:20260924.9116423A
Immutable. This exact content is served forever at /api/v1/blob/952836d0428b19d7.
---
name: cm-fix
description: 用户说“修复这个可复现 bug”或要求根据失败报告修代码时使用。执行红灯测试、根因定位、最小修复、独立审查和回归;尚未确认的问题先用 cm-test,新功能和架构重设计转交 cm-prd。
---
# cm-fix — 缺陷修复小闭环
执行前读取 `../../runtime/project-context.md`、`../../runtime/orchestration.md`、
`../../runtime/review.md`、`../../runtime/model-efficiency.md` 与
`../../runtime/logging.md`。Codex 入口为 `$cm-fix`;Claude Code 跨平台入口为
`/cm-fix`,macOS/Linux 另有历史别名 `/cm:fix`。
每个缺陷开始/恢复时按 `../../runtime/project-learning.md` 重读项目根 AGENTS.md,
筛选相关教训辅助复现与定位;同一合同约束收尾写回,不以旧经验代替本次证据。
用户明确要求外部专家,或为本次修复开启 AUTO 时,仍必须先完成第 1 步本地复现,
再按 `../../runtime/external-expert.md` 执行 `../external-expert/SKILL.md` 的任务
路由。代码、修复、测试和审查保持 LOCAL;只有竞争根因或高风险事实查证可路由到
CONSULT/VERIFY。外部假设必须回到本地证伪;咨询记录不能代替 2.5 或第 5 步独立
审查。
**用法**:`$cm-fix {specs路径} {代码项目路径} 缺陷描述(现象/报错/截图均可)`
## JS 只读准入
在读取项目内容、解析角色、写 `run_start`、运行复现命令或创建档案前,先确认本轮包含非空缺陷
描述,但不要把描述正文拼进 shell;随后执行:
```bash
node "{CM_WORKFLOW_ROOT}/scripts/cm-fix-entry.mjs" \
--skill-dir "{CM_WORKFLOW_ROOT}/skills/cm-fix" --project "{CODE_PROJECT}" \
[--specs "{SPECS_DIR}"] --defect-present
```
没有 specs 的裸项目省略 `--specs`。缺少描述时不传 `--defect-present`,入口返回
`blocked / defect_required` 后只向用户补要描述。只有 `ready / reproduce` 才进入下方既有闭环;
它不提前声称缺陷可复现、不可复现或属于设计问题,只声明复现失败仍走 `observation`、确认设计
问题仍转 `$cm-prd --change`。返回的角色、日志和 Learning 均为 `pending`,执行/写入权限为 false;
入口不运行命令、不调用 provider/browser/外部专家、不创建日志/测试/档案,也不替代七步流程。
## 执行入口选择
准入通过后,具备当前会话双向进程通道、分离的 specs/代码根、命令式复现与测试配置时,
读取 `references/js-host.md`,使用既有 `cm-fix-host.mjs` 执行;Codex/Claude 共用同一 owner。
下文七步仍是业务要求,但 JS 分支的日志、交接、Review 发布及完成全部交给 owner,
不得再手工执行对应写入步骤。只读准入的 `ready` 不是执行、外发或完成许可。
JS 步骤留在 `unknown` 时先检查原调用和磁盘现场。只有本地复现、诊断、测试、修复、回归、
复盘及走查等列在 [JS 宿主手册](references/js-host.md#本地-unknown-步骤的显式放弃与重做) 的步骤,
才可在确认后用 `abandon_step` 说明原因并重做;普通 `advance`/`run` 不会自行放弃。
独立审查、Learning 写回及交接文件仍按各自恢复或人工检查路径处理,不走此出口。
裸项目、无自动测试/纯视觉替代、父 N6 运行衔接等尚未接通 JS 的场景,要明确报告缺口;
不得宣称已完成 JS 迁移。只有用户明确选择既有非 JS 流程且尚未创建 JS 运行时,才执行
下文手工流程;JS 已启动后遇到阻断,不得切换旁路、换身份或双写状态。
以下手工流程中,两个路径校验通过后调用统一写入器记录 `run_start`;暂停/续跑沿用同一
`.cm-run.json`,本次缺陷闭环或观测闭环退出时写 `run_done`。不得直接拼 JSON。
## 项目角色路由
从代码项目根解析 `coder`、`tester`、`reviewer`(命令、参数和日志字段见
`runtime/workflow-routing.md`)。`coder` 只作为最小修复的请求路由元数据,`tester`
负责防护网/回归,`reviewer` 只描述独立审查候选通道;`declared-adapter` 必须记录为
未观测适配器,不能伪造调用或绕过本地执行与独立审查。resolver 返回非零或配置错误
时立即 `BLOCKED`,不得复现、修改或写入缺陷档案;配置不存在时保持当前默认行为。
`managed-adapter` 按 `runtime/model-efficiency.md` 返回文本建议并自动记录真实 usage;
复现、修复落盘、测试和独立审查仍由本地流程执行。
角色调用按 `runtime/model-efficiency.md` 只传当前缺陷的复现证据、根因范围、修复
diff、回归结果和对应规则;不重复投喂整仓、完整历史日志或其他缺陷上下文。失败输出
保留首个可行动错误与证据路径,防护网、独立审查和回归要求不因精简而变化。
修 bug 专用的**轻量闭环**——不走 N1–N8 全链(那是 feature 流程),也不许脱离工作流裸改(裸改没防护网没审查,修一个坏三个)。
**多缺陷输入**:先对全部缺陷做第 1-2 步(复现+定位),**按根因聚类**——同根缺陷合并为一次修复(多个失败测试、一次改动、档案互链),修复顺序按严重度排,不按输入顺序。不聚类的代价:三个现象一个根因跑三个闭环,且第一个修复落地后,后两个的复现步骤可能已失效(第 1 步卡死)。
**转交进场**(消费上游落盘物,不改上游流程):缺陷描述可附上游档案引用——`$cm-test` 的只读测试报告、`$cm-refactor` 档案的未修缺陷清单、N6 业务走查报告的偏差项、观测闭环的半份档案(按 slug 在 `fixes/` 检索)。带引用进场的缺陷,第 1 步**采信上游已有证据**(位置/现象/日志原文),仍须实际复现一次核实,但不从零摸排。
**$cm-ai 全局规则在本流程内同等生效**:灾难级与节点显式卡点暂停、多方案自主决策留痕、状态落盘(node 写 `FIX`)、运行日志照记、独立审查按 `runtime/review.md` 执行。
修改代码前预检 fresh 独立审查通道;无可用通道时暂停修复,已有改动保持待审。
当前支持 Codex 子代理/隔离 CLI;未验证的 Claude-native 适配不能改名冒充 Codex。
**跨边界证据(条件触发)**:缺陷涉及跨进程/跨服务、异步队列或流、路由目标、缓存/状态不一致或时序偶现时,读取 `references/cross-boundary-debugging.md`;它只补定位证据,不新增入口、状态或完成标准。普通可复现缺陷不补表,仍走以下七步。
## 闭环七步(每个缺陷)
### 1. 复现(不能复现的 bug 不许修)
- 按描述复现:实际操作/运行一次,拿到**失败证据**(报错原文、错误截图、错误返回值);**证据要用严格裁判**——宽容裁判会把坏产物蒙混成功(实跑:补丁类缺陷 GNU patch 的 fuzz 容错险些吞掉复现,换 git apply --check 才拿到硬证据)
- 未复现先做复现探索:主动构造输入值(边界/空/超长/非法编码)、前置状态(空数据/脏数据/并发写入中间态)、时序(先后顺序/失焦与点击/异步未完成)、环境(版本/区域设置/权限/离线)、规模(单条/大量)场景;每次只改一个维度,记录「场景 → 结果」,沿用既有授权,不扩执行权限。
- 探索最多 3 个场景或 15 分钟(先到为准);命中即进第 2 步定位,该场景脚本/步骤作为第 3 步红灯测试骨架。到上限仍未复现 → 不猜着修,才走**观测闭环**(偶现 bug 专用,两段式):
① 先判断是否命中跨边界证据条件;命中时按参考先列“边 → 预期证据 → 实际证据”,再在可疑路径加最小观测点(日志/埋点——观测点本身按最小改动+审查纪律入库,**观测点不是修复尝试**)
② 缺陷档案先落半份,状态记 `观测中`,列出已试场景,说明观测点为何这样埋,写清"等什么证据(哪个日志出现什么内容)"
③ 本次命令正常收口退出,不挂着等——运行日志记 `run_done`,detail 写「观测中:等{什么证据}」;状态文件 state 复位,不留悬挂的 running
④ 证据到手后再次运行 `$cm-fix` 附上证据,**按 slug 定位 `fixes/` 下的半份档案**,从第 2 步定位续跑,档案续写、状态改 `修复中`,运行日志记 `resume`(detail 注证据摘要)
——**"我改了点东西你再试试"依然被禁止**
### 2. 定位(先找根因,不是找改哪行能让现象消失)
- 按 `../codebase-context/references/writeback.md` 确定项目地图及本次文档范围;有地图先读相关链路与影响映射,项目指定架构文档同样适用
- 无地图 → 从失败点向上追调用链,找到**根因层**(现象在 UI,根因可能在数据层)
- 命中跨边界证据条件 → 将调用链、每条边的最小证据、**最后正常边**与**首个失败边**写入缺陷档案;同时写“假设 → 支持证据 → 反证试验 → 结果”,一次只检验一个假设。日志与试验必须本地且脱敏,**不自动联网、不外发日志、不安装依赖、不重启服务、不清理缓存**。
- 输出一句话根因结论 + 波及面清单(本次修改会牵连哪些模块)——写进缺陷档案(第 7 步)
### 2.5 根因与修法对抗确认(条件触发;根因错误是本流程最贵的错误,必须在防护网之前拦)
任一**客观条件**命中才触发(简单缺陷零负担,判断依据同"门槛是客观项不是判断题"):波及面 ≥3 个模块 / 根因层与现象层不同层 / 观测闭环续跑的缺陷 / 拟走升级出口。
- 把根因结论 + 复现证据 + 波及面清单 + **拟采用修法(含放弃的备选)**交给新上下文的独立审查者;命中跨边界证据条件时一并交调用链、最后正常边、首个失败边和已完成的反证试验。提示词要义:「**假设这个根因判断是错的,找出更深层的解释;再审修法:治本还是治症?有没有更小的改动?会不会引入新耦合?**」。通道与降级规则同 N4
- **仅 1 轮**:推翻 → 回第 2 步重定位;分歧 → 交人裁决;通过 → 进第 3 步
- 凭证落 `{SPECS_DIR}/.reviews/fix-{slug}-cause-r1.md`——**命名带 `cause` 是有意的**:不落入第 5 步 `fix-{slug}-r*.md` 的匹配域,两个卡点各自独立,根因凭证不会误满足 diff 审查卡点
### 3. 防护网(先让 bug 有测试,再修)
- **写一个能复现此 bug 的失败测试**(红)——它是"修好了"的客观定义,也是永久回归资产;**红的原始输出落进档案**(第 5 步审查要核对红证据,从未红过的测试转绿是空话)
- 项目有存量测试 → 先跑一遍记录基线(修完对照,防止修 A 坏 B)
- JS owner:首轮补测走原 test-author;保留原红灯和存量基线。最终审查要求补测时,按 `references/test-extension.md` 在第二轮登记扩充,修复回调仍不得改测试。
- 写不了自动化测试的形态(如纯视觉)→ 截图/录屏留"修前"证据
### 4. 修复(最小改动)
- 只改根因层,**禁止顺手重构**(N3 同款纪律:看不惯的代码记 LESSONS 待触发备忘,事后走 `$cm-refactor`,不在修 bug 时动)
- 修法有多个方案 → 自主决策选最优,`decision` 事件留痕
- **升级出口**:定位发现是设计缺陷/需要跨模块大改 → 停止硬修,先通过第 2.5 步根因审查。JS 返回 `design_change_required` 后,有 `redTest` 就沿原测试编写/红测入口取得真实失败;意外通过或失败原因不符照常阻断。红测确认后进入 `escalation_required`,不跑基线、修复、回归或最终实现审查。
- **已建资产不弃**:失败测试留在仓库;档案状态记 `升级立项`,列出根因、影响范围、诊断方案、根因审查凭证、测试路径和红证据路径,建议用 `$cm-prd --change` 立项,以新方案使该测试变绿为验收。非视觉运行未配置 `redTest` 时直接进入升级归档,并明写没有失败测试及原因;视觉运行必须配置视觉 `redTest`(`testFiles:[]`),先走视觉红测核验真实修前载体再升级,不冒充自动红测。
- JS 可先 `publish_dossier`,再用获准的 `finish` 写 `run_done / escalation / escalated` 并关闭 owner;中断后在原运行重开收口,冲突则阻断。重开后的 `escalated` 是终态,`completionEligible` 始终为 false,不记 task_done 或修复完成指标;立项建议不自动创建变更项目。
### 5. 审查(独立审查同 N4)
- 修后按 `../cm-test/references/unit-coverage.md` 检查已授权修复范围的增量单测覆盖率,
核对正常/异常/边界及相邻场景并重跑,纳入下面的同一份 handoff;原失败测试红绿证据必须保留。
普通流程在审前补足授权测试;JS owner 若第一轮最终审查要求补测,按 `references/test-extension.md` 登记测试编写和实跑结果,
再走第二轮修复、回归与独立审查;不在 owner 外修改测试或重建原红灯、基线。
- 先按 `../codebase-context/references/writeback.md` 完成地图评估与必要回写;无地图建本次局部地图,有地图只更新受影响章节。普通流程记 handoff evidence,JS 复用已绑定的 plan、修后文件与 Review;改动纳入摘要与独立审查,不能等第 7 步再写。
- 审查前按 `../../runtime/project-learning.md` 复盘并完成必要的 AGENTS.md 增量写回,纳入本次审查 diff;无新增记入缺陷档案。微缺陷通道也必须复盘,新增 AGENTS.md 改动导致不再满足单文件门槛时走完整流程。
- 失败测试转绿 + 存量基线不退化后,按 `runtime/review.md` 审查本缺陷 diff(重点:根因是否真被修掉、有无只治症状、波及面有无遗漏)
- **防护网测试本身是审查对象**(实测最大问题类:测试是戏台):红的原因是否=该缺陷、断言测的是根因还是症状、有无安慰剂/前提共谋;**核对第 3 步落档的红证据**——没有红过的记录,测试可信度按不成立处理
- 所有缺陷零豁免;独立通道不可用则待审,`self-degraded` 仅作诊断,不得成功收口;通道故障不算代码 finding/实现审查轮次,有效 finding 不能靠换人消除;≤2 轮上限同样生效
- `{slug}` 先规范成跨平台安全的 ASCII kebab;令 `REVIEW_FEATURE=fix-{slug}`、
`REVIEW_TASK=T-FIX-{slug}`。主执行者按真实 diff 写
`{SPECS_DIR}/.reviews/fix-{slug}-T-FIX-{slug}-a{attempt}-handoff.json`,格式与
`runtime/task-handoff.schema.json` 相同。先按 handoff 的完整 `changed_files` 运行
`cm-task-gate.py hash-implementation --project-root {CODE_PROJECT} --file ...`,把返回的
`implementation_sha256` 写入 handoff,再真跑:
```bash
python3 {CM_WORKFLOW_ROOT}/scripts/cm-task-gate.py check-n4 \
--handoff {HANDOFF_PATH} --reviews-dir {SPECS_DIR}/.reviews \
--feature fix-{slug} --task T-FIX-{slug} --project-root {CODE_PROJECT}
```
- 独立审查凭证严格落
`{SPECS_DIR}/.reviews/fix-{slug}-T-FIX-{slug}-r{attempt}.md`,包含当前 handoff
文件名和 SHA。审查完成后必须真跑下列命令;只有当前 attempt 的
`independent: true` 且 `verdict: approved` 才能进入第 6 步:
```bash
python3 {CM_WORKFLOW_ROOT}/scripts/cm-task-gate.py check-n5 \
--handoff {HANDOFF_PATH} --reviews-dir {SPECS_DIR}/.reviews \
--feature fix-{slug} --task T-FIX-{slug} --project-root {CODE_PROJECT}
```
- `changes_requested` 后修改代码必须生成 attempt 2 handoff 并复审;第 2 轮仍有阻断项
写 `blocked` 并停止。文件存在、旧凭证或 `ls` 输出都不构成批准。
- 后续回归、文档或经验整理如修改被审代码、测试或执行指令,原批准失效;重新形成证据并独立审查,不能重置轮次或在收口时顺手改实现
### 6. 回归(按波及面,不是只看 bug 消失)
- 跑第 3 步防护网测试(红→绿)+ 存量测试全量(对照基线)
- 按第 2 步波及面清单逐项走一遍关键流(同 B2 口径:波及面=回归范围)
- **回归失败的回路(显式分支,不许临场发挥)**:任何一项红 → 退回第 4 步重修,重修后**必须复审**且轮次并入第 5 步的 ≤2 轮总上限——上限耗尽仍打转 = 根因判断可疑,按升级出口处置,不许无限修-回归循环
### 7. 落盘(审计链闭合)
- 核验地图评估结果和已审文件版本,缺评估、待同步或审后变化不写成功 `task_done`;遵守回写合同的项目规则豁免,不把缺失地图静默跳过。
- 收口前核对复盘记录、AGENTS.md 的审查范围与磁盘摘要;有新增则回读确认,无新增如实记录。缺记录、无法写回或批准后变化时不写成功 `task_done`,按学习合同与第 5 步处理。
- **缺陷档案**:`{SPECS_DIR}/fixes/{YYYYMMDD}-{简短slug}.md`——现象 / 复现步骤 / 复现尝试(逐条「场景 → 改了哪个维度 → 结果(复现/未复现/环境不支持)」;按描述一次命中只写一行)/ 根因 / 修法(含放弃的方案)/ 波及面与回归结果 / 测试文件路径;命中跨边界证据条件时追加“证据链与假设”(调用链、边证据、最后正常边、首个失败边、反证结果)。这是缺陷知识库,同类 bug 再犯先查这里
- **METRICS.md 追加一行**:Feature 列写 `fix`,任务列写档案文件名,其余列同口径(轮次/拦截数/人工介入)
- 根因具普遍性(如"平台 API 返回结构变了")→ 追记 LESSONS.md([已结构化]/[仅记忆] 分级同 N5)
- Git 按有效 `policies.delivery`:diff 不 stage/commit;branch/draft-mr 提交
`fix: {一句话} (档案: fixes/xxx.md)`,审查摘要进 commit message(同 N4)
- 运行日志事件:`task_start`/`review`/`task_done`/`run_done` 照记,node 字段写 `FIX`
## 微缺陷快速通道(四个硬门槛全中才准走)
**门槛是客观项不是判断题**——"感觉这个 bug 很小"不构成理由,四条全中才走,任一不中走完整七步:
- [ ] 只改文案/样式/配置常量——**不新增、不修改任何条件分支与函数签名**
- [ ] 单文件且 diff ≤ 10 行
- [ ] 波及面为零(改动处无被其他模块引用的行为;有业务地图查 08 映射表核实)
- [ ] 有截图/文案前后对照可作验收证据
**快速通道可省**:第 3 步防护网测试、第 6 步全量回归(用前后对照截图代替)。
**不可省**:独立审查(凭证照落)、地图评估(回写导致多文件则走完整流程)、缺陷档案(显式标注 `快速通道`)、METRICS 行(Feature 列写 `fix-lite`)。
**快速通道的审查特化**:独立审查是该通道的主要质量防线,第一职责是复核四个客观门槛;diff 任一项不符或波及面存疑即打回完整七步。
> fix-lite 的占比进运行日志——快速通道被滥用(占比异常高/出现分支改动混入)时收紧门槛,数据说了算。
## 输出格式(每个缺陷收口时)
```text
🔧 缺陷闭环: {slug}
根因: {一句话}
修法: {一句话} | 放弃方案: {有则一句话,无则省}
防护网: 新增 {测试文件}(红→绿) · 存量基线 {N} 项无退化
审查: 独立审查({channel}) {通过/N轮N条} | 回归: 波及面 {N} 项通过
档案: fixes/{文件名} METRICS 已记
学习: {AGENTS.md已写回并回读/已复盘,无新增}
业务地图: {已更新/已建局部地图/无需更新/项目规则豁免/待同步} {路径或原因}
```
## 边界
- **不承接**:新功能(走 $cm-prd)、需求变更(走 $cm-prd --change)、架构级返工(升级出口交人立项)
- specs 目录没有 fixes/ 子目录时自动创建;没有 specs 目录的裸项目也可用:档案落代码项目 `docs/fixes/`,**审查凭证落 `docs/fixes/.reviews/`**(第 5 步卡点同样生效),METRICS 跳过