让 AI 写代码不“翻车”:用 Claude Code + Codex 给 Flutter 项目搭一套工程规范

工具:Claude Code、Codex 项目:两个中大型 Flutter 项目(移动端 + 桌面端),其中桌面端由我一个人借助 AI 从 0 开发,900+ 次提交、1600+ 个测试用例。

用 AI 写代码一段时间后,我最大的感受是:AI 的问题不是不会写,而是不知道"这个项目里什么不能做"。

  • 它会用 malloc 分配一个原生结构体,代码能跑,画面却悄悄变了色;
  • 它会直接改 lib/l10n/app_localizations.dart,下次生成就被覆盖;
  • 它会手写一个 Duration(milliseconds: 300),绕过项目统一的动效规范;
  • 它会说"已修复",但 flutter analyze 里多了 3 个 error。

这些问题靠每次在对话里提醒是没用的,下一个会话它又忘了。我的做法是把约束沉淀到仓库里,分成四层:

objectivec 复制代码
CLAUDE.md / AGENTS.md   ------ 项目长什么样(架构、命令、约定)
rules/                  ------ 什么不许做(硬规则)
hooks                   ------ 自动执行的检查(不靠 AI 自觉)
agents / skills         ------ 固定流程(审查员、修 bug、加功能、发版)

一、CLAUDE.md / AGENTS.md:给 AI 的项目说明书

Claude Code 启动时会读仓库根目录的 CLAUDE.md,Codex 读的是 AGENTS.md。两边都用的话,不要维护两份 ,AGENTS.md 里只写一句话指过去:

markdown 复制代码
# AGENTS.md

本项目的工作指引请阅读 [./CLAUDE.md](./CLAUDE.md),所有规则、命令、发版流程均以该文件为准。

CLAUDE.md 写什么?我的经验是只写AI 从代码里不容易看出来、但做错了代价很大的东西:

  • 环境与命令 :必须用 fvm、Flutter 版本、测试命令、打包命令;
  • 架构地图 :分层、入口、状态管理用法、哪些文件是"门面"(比如统一导出的 base.dart);
  • 关键约定 :例如"首页控制器由 24 个 mixin 组合而成,with 列表的顺序就是依赖顺序,每个 mixin 的 onInit 必须调 super,漏调会静默断掉后面所有 mixin 的初始化"。

不要写什么?代码里一眼就能看到的东西、git 历史里有的东西。写多了 AI 抓不住重点,而且文档会过期,代码不会 。我们就吃过亏:CLAUDE.md 里长期写着某个结构体用 malloc 分配,实际代码早就改成 calloc 了。所以在修 bug 的流程里专门加了一条:不许拿文档当依据下结论,必须在当前代码里证实。


二、rules/:每条规则都要有"执行手段"

CLAUDE.md 讲架构,rules/ 讲禁令。我给 rules/ 定了一条元规则:

AI 犯一次错,要么加一条规则,要么加一个检查。没有执行手段的规则不算规则。

踩坑后按成本从低到高自问:

  1. 能不能加一条 lint?(最便宜)
  2. 加不了 lint,能不能加一个 hook 检查?(grep 级,亚秒)
  3. 都加不了,才写成"AI 自觉遵守"的文字规则。

每条规则行尾都标上执行方式,比如:

markdown 复制代码
- **`ViewerProcessConfig` 必须 `calloc` 分配**(零初始化),不许改回 `malloc`。
  ABI 升级会在结构体末尾追加字段,这些字段"非 0 即开",malloc 的堆垃圾几乎必然非 0,
  等于悄悄打开了某个图像处理开关,画面变淡掉色还没有任何报错。〔执行:ffi-safety-reviewer〕

- `viewer_bindings_generated.dart` 是 ffigen 产物,**禁止手改**。〔执行:pre_write_guard〕

- **静态成员不跨 isolate**:每个 isolate 各自加载原生库。〔执行:自觉〕

规则按主题拆文件(工作流、原生层、代码风格、生成文件),rules/README.md 放一张索引表,AI 动手前先扫一眼命中了哪几条。


三、hooks:把检查变成自动的

Claude Code 的 hooks 配置在 .claude/settings.json 里,可以在特定时机执行脚本,不依赖 AI 记不记得:

json 复制代码
{
  "hooks": {
    "SessionStart": [{ "hooks": [{ "type": "command", "command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/session_check.sh\"" }] }],
    "PreToolUse":   [{ "matcher": "Write|Edit", "hooks": [{ "type": "command", "command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/pre_write_guard.sh\"" }] }],
    "PostToolUse":  [{ "matcher": "Write|Edit", "hooks": [{ "type": "command", "command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/post_edit.sh\"" }] }],
    "Stop":         [{ "hooks": [{ "type": "command", "command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/stop_gate.sh\"" }] }]
  }
}

1. PreToolUse:拦截编辑生成文件

AI 每次写文件之前触发。脚本从 stdin 读到要写的路径,命中黑名单就 exit 2 拦下,并告诉它正确做法:

bash 复制代码
f=$(jq -r '.tool_input.file_path // empty')
if printf '%s' "$f" | grep -qE '(/lib/l10n/app_localizations|/lib/generated/|/pubspec\.lock$|/build/)'; then
  echo "禁止编辑生成文件: $f" >&2
  echo "依据: rules/30-generated-files.md(里面有重新生成的命令)" >&2
  exit 2   # exit 2 = 阻断,stderr 会反馈给 AI
fi

同一个脚本还做了"自保护":拦截 AI 修改 hooks 和 settings 自身,防止它为了绕过检查把检查删了。

2. PostToolUse:只做亚秒级的事

编辑后触发。这里千万不要跑 flutter analyze:单文件 analyze 每次要几秒,AI 一轮改 30 次就是几分钟纯等待,而且单文件模式看不到跨文件的破坏。

这里只做:

  • 格式化(dart format,只格式化新建的文件:老代码是旧版格式风格,用新版 formatter 一碰就整个文件重排,diff 没法看);
  • 记录本轮改了哪些文件;
  • grep 级的提醒(比如新代码里出现了绕过主题的硬编码颜色),只提醒不阻断。

3. Stop:AI 说"完成了"之前,跑一次 analyze

Stop hook 在 AI 准备结束这一轮时触发,对本轮所有改动的文件 跑一次 flutter analyze,有 error 就阻断,让 AI 继续修:

bash 复制代码
# 防止无限循环:已经因为本 hook 阻断过一次,就放行
[ "$(jq -r '.stop_hook_active')" = "true" ] && exit 0

out=$(flutter analyze $changed_files)
if echo "$out" | grep -q ' error '; then
  echo "$out" >&2
  exit 2
fi

几个设计取舍:

  • 只对 error 阻断,warning 只回显。项目里有存量 warning,如果拿 warning 阻断,AI 很快就学会"无视 analyze 输出";
  • 不在 Stop 里跑测试。全量测试要 40 多秒,每轮都跑太贵,留给提交前;
  • 补上 Bash 通道的盲区 。PostToolUse 只能感知 Write/Edit 工具,AI 用 sed -i 或 Python 脚本批量改文件时完全绕过了它。我们就遇到过 AI 用脚本删了 26 个 import,analyze 被直接跳过。解决办法是 Stop 时用 git diff + 文件哈希做快照对比,把"这一轮新变化的 dart 文件"全找出来。

另一个项目里,我们还在 Stop hook 里检查:"本轮改了业务代码,但没有写技术文档条目"就拦一次,要求 AI 补上需求的技术文档。


四、守卫测试:把"规范"写成单元测试

有些规范 lint 表达不了,hook 的 grep 又太粗,就写成守卫测试 :扫描 lib/ 源码,违反规范就让测试失败。

比如"动画时长和曲线必须用统一的 AppMotion,不许手写":

dart 复制代码
final _duration = RegExp(r'\b(duration|reverseDuration|transitionDuration)\s*:\s*(const\s+)?Duration\(\s*(milliseconds|seconds)\s*:');
final _curve = RegExp(r'\b(curve|reverseCurve)\s*:\s*(const\s+)?Curves\.(?!linear\b)');

/// 例外只进白名单,并写清理由
const _allowed = {
  'lib/base/motion.dart': '预设本身',
  'lib/base/component/listview.dart': '加载转圈的匀速循环',
};

void main() {
  test('动画参数不许手写 Duration / Curves', () {
    final violations = <String>[];
    for (final f in Directory('lib').listSync(recursive: true).whereType<File>()) {
      if (!f.path.endsWith('.dart') || _allowed.keys.any(f.path.startsWith)) continue;
      final lines = f.readAsLinesSync();
      for (var i = 0; i < lines.length; i++) {
        if (_duration.hasMatch(lines[i]) || _curve.hasMatch(lines[i])) {
          violations.add('${f.path}:${i + 1}  ${lines[i].trim()}');
        }
      }
    }
    expect(violations, isEmpty, reason: '请改用 AppMotion:\n${violations.join('\n')}');
  });
}

项目里类似的守卫测试有好几个:颜色只能用设计 token、毛玻璃只能用统一组件、字重只能用统一常量、原生内存的分配方式......它们对人和 AI 一视同仁,谁写错了跑测试时都会报出来。


五、agents 与 skills:把"流程"固化下来

agents:专职审查员

在 .claude/agents/ 下定义子代理,例如 ffi-safety-reviewer.md:

markdown 复制代码
---
name: ffi-safety-reviewer
description: 审原生层的内存安全:结构体是否 calloc + 逐字段赋值、释放器是否配对、
  asTypedList 是否拷贝后才 free。触发:改动落在 lib/base/uniview/ 或 native/;
  画面莫名变色、直方图空白、解码后偶发崩溃。
tools: Bash, Read, Grep, Glob
---

你是原生层内存审查员。**只出报告、不改代码。**
这层的失效方式是静默的:不崩、不报错,只是画面悄悄变样。
所以"看起来没问题"不是结论,逐条比对才是。
...

还有"影响面分析"(改了公共组件,列出所有受影响的页面)和"代码定位"两个代理。

skills:固定工作流

在 .claude/skills/ 下按场景写流程:bug-fix(修 bug)、feature-dev(开发需求)、add-panel(新增一个图像处理面板的 7 个步骤)、release(发版)、language-sync(多语言同步)、packaging-debug(打包排查)等。

以 bug-fix 为例,核心是先分档、再决定谁来验证:

档位 判据 谁验证
视觉 显示不对、样式问题 人看截图
逻辑 状态、数据错误 单元测试
性能时序 卡顿、竞态 实测 + 日志
平台集成 只在某个系统上出问题 对应系统真机

AI 第一次回复就要给出分档结论,收尾必须做影响面回归。


六、一个需求的完整流程:从复述到提交

前面讲的是"约束",这一节讲"流程"。来了一个需求,AI 不是直接开写,而是按下面的顺序走。这套流程也写成了 feature-dev skill,AI 每次接到需求都会按它执行。

css 复制代码
复述需求 → 定档 → 方案设计 → 需求文档 + 计划文档 → 【A:检验计划】→ 测试文档(业务 / 性能)
   → 影响面分析 → 逐个任务实现 →【B:检验任务】→ 验证到一轮 clean → 性能分析
   → 交叉审查 → 总结文档 → 归档 → 提交前全面检查(含影响面回归)→ 提交

第 0 步:先复述需求

AI 的第一次回复必须先用自己的话把需求说清楚,不许直接开改,哪怕是一行的小改动(小改动最容易把需求理解反)。复述要落到四项:

项 写什么
场景 什么状态下、哪个界面、什么前置条件会碰到
现状 现在的行为(读过代码就给出文件和行号,不许凭印象)
要做成什么 改完之后用户会看到什么
落点 预计改哪几个文件,为什么是这里

复述完如果还有会改变做法的分歧点,一次性问清楚再动手;命名、缩进这类有惯例的,AI 自己定,回复里说一句就行。

第 1 步:定档

档 判据 流程
简单 单模块、不动原生层和接口、≤ 3 个文件 直接实现 → 验证 → 影响面检查,只写总结文档
复杂 新功能面板、动原生层、跨 3 个以上模块、有设计图 走完整流程,三份文档齐全

拿不准就按复杂走。

第 2 步:方案设计与文档

复杂需求先让 AI 出 2~3 个方案 并对比取舍,我拍板后再写文档。每个需求一个目录 docs/features/<需求名>/:

  • requirement.md :业务规则、数据来源、验收标准;
  • plan.md:任务拆分。每个任务写清楚改哪些文件、怎么验证,粒度以"一个任务完成后可以独立验证"为准;
  • summary.md:做完后写,见第 8 步。

文件头必须写实现状态 :未开始 / 方案待评审 / 部分落地(哪几条 + 日期) / 已落地(日期 + 分支),并把"已定"和"待定"分开列。AI 读文档时判断新鲜度全靠这一行。

A:检验计划------计划写完先"测"一遍

计划写错了,后面执行得越认真,偏得越远。所以 plan.md 写完不直接开干,先做一轮检验。我一般让另一个模型(Codex)或者一个新会话来审,避免"自己写、自己审":

检查项 怎么查
覆盖需求 requirement.md 里的每一条验收标准,在计划里都能找到对应的任务
可以验证 每个任务都写了"做完怎么证明它对":跑哪个测试、看哪个现象
顺序正确 任务之间的依赖关系没有颠倒,前一项做完后一项才能开始的,顺序要对
落点真实 计划里提到的文件、类、方法在当前代码里真实存在(AI 很容易凭印象写一个不存在的方法名)
风险标出 动原生层、动公共组件、动接口的任务单独标出,提前安排专项审查

检验发现的问题改回 plan.md,计划改到"没有意见"才进入实现阶段。

目录名用英文。中文目录名在 git ls-files 里会被转义成八进制,grep、diff、脚本里全是乱码。

第 3 步:测试文档(业务 + 性能两份)

测试用例让 AI 逐文件通读源码后产出,分成两份,编号互不冲突、内容互不重复:

业务测试用例集 性能测试用例集
关心什么 对不对、全不全、状态串不串 快不快、内存涨不涨、崩不崩
判定依据 界面和数据的期望值,不设时间阈值 毫秒 / MB / 帧数阈值
构建模式 debug / profile / release 均可 必须 release
用例编号 TC-F-<模块>-nn TC-<模块>-nn

每条用例有优先级(P0 / P1 / P2)和自动化状态(全自动 / 部分自动 / 手工),一眼能看出哪些已经有测试兜底。目前业务用例 188 条,其中 83 条有自动化覆盖;性能用例 149 条。

性能用例里挑出 24 条作为发版红线,比如:

用例 场景 红线
连续换图 200 张 原生内存泄漏 静置后内存增量 ≤ 250MB,每 50 次增量不得单调递增
单张大图解码 内存峰值与回落 峰值增量 ≤ 900MB,切走后残留 ≤ 60MB
拖动直方图手柄 不得触发重解码 拖动期间解码次数 = 0,掉帧 ≤ 5 帧

没有历史数据的阈值标为"基线型":第一轮执行只负责采集基线,从第二轮起才作为门禁,避免拍脑袋定阈值。

第 4 步:影响面分析

AI 改代码最常见的翻车方式不是"改错了",而是"改对了这里,弄坏了那里"。所以专门有一个 impact-analyzer 代理,只分析、不改代码,输出"必须回归的清单 + 建议跑的测试"。

什么时候必须做:

  • 改动落在公共组件、全局服务、原生解码层这几个目录;
  • 改了组件的参数、函数签名、控制器的响应式字段;
  • 单次改动超过 20 行。

用两次:写代码之前 做一次,摸清引用面再动手;提交之前再做一次,作为回归清单。

怎么分析: 不是泛泛地"全局搜一下",而是把这个项目特有的传播路径写进代理的说明里,让它逐条过:

改了什么 会影响到哪
公共组件的参数 / 签名 搜组件名,所有用到它的面板都要回归;如果它从统一导出文件(base.dart)导出,波及面更大
全局服务 服务注册顺序;所有 XxxService.to 的取用点;夜间模式、色盲模式、语言这三个会穿透到全部界面
原生解码层 所有走原生解码的格式;直方图、星点分析、去星、标注、导出都吃同一份解码结果
控制器 mixin 的 with 顺序 顺序就是依赖顺序,改动会静默影响后面所有 mixin
面板状态字段 本地 / 设备两个 Tab 的状态是成对写的,改一个必须同步另一个

输出格式固定:

markdown 复制代码
## 改动面
<文件:行数,一句话说改了什么>

## 必须回归(人工)
- [ ] <具体到哪个面板 / 哪个操作 / 期望看到什么>

## 建议跑的测试
fvm flutter test test/xxx_test.dart test/yyy_test.dart

## 判定
影响面:小 / 中 / 大 ------ 一句话理由(按实际引用面判断,不按 bug 严重程度)

还有一条反"形式主义"的规定:引用面确实很窄(直接引用的文件 ≤ 3 个、单模块、改动 ≤ 20 行)时,直接写"影响面:小,可免人工回归",不许为了凑格式编回归项。只改注释、删无用 import 这类"删掉这次 diff 程序行为完全不变"的改动,可以免做影响面分析,但静态检查照样要跑。

第 5 步:实现

  • 按 plan.md 逐项做,每项都要通过下面的 B 检验才能勾掉(这也是换工具、换会话时的交接依据);
  • 逻辑类改动先写测试再实现;
  • 按落点走对应 skill:新增图像面板走 add-panel(带脚手架脚本),动原生层走 uniview-ffi;
  • 新增文案先硬编码并留 // TODO: i18n,最后统一走多语言同步流程,避免中途反复改 10 种语言。

B:检验任务------做完一项测一项

每个任务做完,马上验证这一项,通过了才勾掉、才开始下一项,而不是全部写完再统一测:

  1. 对照任务自己的验收方式:就是 A 阶段要求每个任务都写好的"做完怎么证明它对";
  2. 跑相关测试:逻辑类跑对应的单测,并确认新写的测试在改动前是失败的(证明测试真的测到了东西);
  3. 静态检查:本次改动的文件 analyze 0 error;
  4. 对照计划检查有没有做多、做少:没要求的"顺手优化"不做,计划里的要求一条不漏。

这样做的好处是:问题在哪一项引入的一清二楚,不会出现全部写完一跑测试一片红、不知道从哪查起的情况。中途额度用完、换工具接手时,已勾掉的任务也都是确认过的,可以放心往下接。

第 6 步:验证到"一轮 clean"

bash 复制代码
fvm flutter analyze   # 0 error
fvm flutter test      # 全量 1600+ 用例全绿

一轮 clean = 两条命令连着跑一遍全过,中间没有任何手工干预。 没到就继续改,不许报"基本完成"。

验证按类型分工:

改动类型 谁验证
视觉 / 布局 / 文案 我来验收,AI 只说明改了什么
纯逻辑 / 数据处理 AI 补单测并跑绿
性能 / 时序 / 并发 / 内存 AI 自己起 App 实测(见下一步)

第 7 步:性能分析

性能问题不能靠"感觉快了",要有数字,而且要改动前后对比:

  1. 用 profile / release 模式测 。debug 模式下数据会失真,比如 Isolate.run 冷启动在 debug 下约 600ms,是调试器造成的假象;
  2. AI 自己驱动 App 取数:通过 Dart VM Service 连上正在运行的 App,脚本化地重复操作(比如连续换图 200 次),采集耗时时间线和内存曲线,不需要我手动去点;
  3. 改动前先跑一遍拿基线,改完再跑一遍,两组数字一起写进总结;
  4. 对照性能用例集里的红线判定是否达标。

大图内存优化就是这么做的:先测出"并发解码导致峰值翻倍""decodeImageFromPixels 让 Dart 堆多占 100~250MB"等问题和具体数字,逐项优化后再用同一套用例复测。

第 8 步:审查与收尾

  1. 交叉审查 :用 Codex 审一遍本次改动(下一节详细讲);动了原生层的,再派 ffi-safety-reviewer 逐条核对内存规则;
  2. 写 summary.md:实测结果写真实数字(不许写"性能良好"),再附一份"待人工验证清单"(直接用影响面分析的输出);
  3. 归档 :需求闭环后,整个目录移进 docs/archive/plans/,同时修正引用它的路径;
  4. 不主动提交:我说提交才提交,提交前走下面的全面检查。

第 9 步:提交前全面检查

提交不是简单的 git commit,而是一个固定的检查命令 /commit(make commit 调的也是它),把"提交前该做的事"一次做全:

① 收集改动,一个都不漏

  • git status -uall 和 git diff HEAD 一起看:前者能展开未跟踪目录里的每个新文件(新增的图片、字体、资源),后者只看得到已跟踪文件的改动,只看一个就会漏;
  • 再跑一次 git status --ignored:有本该提交却被 .gitignore 误忽略的文件,单独 git add -f;
  • 默认只提交本次会话改动的内容,不把工作区里别的半成品一起带进去。

② 代码检查三连

  1. 静态检查 flutter analyze:和基线(0 error)对比,新增 error 判为严重;warning 数量比基线涨了判为警告;
  2. 全量测试 flutter test:和基线(1600+ 全绿)对比,本次改动引入的失败判为严重;与本次无关的存量失败单独列出,不阻断提交;
  3. 人工审查 diff (由 AI 逐项过):逻辑错误、空安全、资源未释放(控制器的 onClose、原生内存、文件句柄、监听器)、误删代码、调试残留(print、临时配置)、硬编码、未翻译的文案;
    • 动了原生层的,按内存规则逐条核对;
    • 动了公共组件、全局服务或超过 20 行的,再跑一次影响面分析。

③ 分级处理

级别 含义 处理
🔴 严重 崩溃、逻辑错误、编译不过 列出 文件:行号 和改法,问我是否先修;我说改就停下等改完重跑,我说不改才提交
🟡 警告 潜在隐患 提交后列出
🔵 建议 可优化 提交后列出

没有严重问题才提交。提交信息按 Conventional Commits,描述用中文,scope 用功能域,例如 fix(histogram): 默认对数轴改回来、feat(psf): 倾斜分析四角补圆度数值。push 单独问我一次,不主动推送。

④ 提交后兜底

再跑一次 git status,确认工作区干净。还有残留,说明第 ① 步漏加了或者被 .gitignore 吃掉了,必须查清楚,不许默默放过。

⑤ 失败不许假装成功

任何一步失败,打印原因并停下,不许吞掉错误、不许假装提交已完成。这一条是专门写给 AI 的:它在遇到失败时,有时会倾向于"换个说法"把结果报成成功。


七、Codex 怎么用:额度兜底 + 交叉审查

我的主力是 Claude Code,Codex 主要用在两个场景:Claude 额度用完时接手继续干活 ,以及审查代码。两个工具共用一套规范,切换几乎没有成本。

1. 让两个工具读同一份上下文

Codex 读的是 AGENTS.md,而且是分层读取 的:全局的 ~/.codex/AGENTS.md、仓库根目录、当前子目录,越具体的越优先。前面说过,仓库根目录的 AGENTS.md 只写一句话指向 CLAUDE.md,这样两边看到的是同一份说明,不会出现"改了一边忘了另一边"。

需要注意的是,hooks 是 Claude Code 专有的 ,Codex 不会触发 .claude/settings.json 里的检查。所以规范要分两类看:

约束 Claude Code Codex
CLAUDE.md / AGENTS.md、rules/ ✅ ✅(通过 AGENTS.md 指过去)
lint(analysis_options.yaml) ✅ ✅
守卫测试 ✅ ✅
hooks(自动格式化、拦截生成文件、Stop 时 analyze) ✅ ❌

这也是我越来越倾向把规则写成 lint 和守卫测试 的原因:它们与工具无关 ,换任何 AI、甚至人来写,都一样生效。只有 hook 能做的事才放 hook。用 Codex 时,我会在 AGENTS.md 里补一句收尾要求:"结束前必须跑 fvm flutter analyze 和相关测试",把 hook 自动做的事改成显式要求。

2. 额度兜底:用"需求文档"做交接

Claude 额度用完往往发生在任务进行到一半。如果交接信息只存在对话里,换工具就得重新解释一遍。

我的做法是进度落盘 :就是上一节的三份需求文档,plan.md 里每完成一项就勾掉。

切到 Codex 时只需要一句话:

bash 复制代码
读 docs/features/xxx/plan.md,从第一个未完成的任务继续,规则见 AGENTS.md。

上下文全在仓库里,Codex 接手后的产出和 Claude 写的风格基本一致;等 Claude 额度恢复,再切回来也一样。

3. 交叉审查:让另一个模型来挑错

这是我用 Codex 最多的场景。同一个模型写的代码,让它自己审,很容易"自己说服自己":写的时候漏掉的边界条件,审的时候往往也会漏。换一个模型来审,盲区不同,能发现更多问题。

常用的几种方式:

(1)审查未提交的改动

Claude 写完一个功能后,在 Codex 里用 /review 审当前工作区的改动,或者用非交互模式直接出报告:

bash 复制代码
codex exec "审查当前 git diff:重点看空安全、异步竞态、资源释放(Timer/StreamSubscription/原生内存)、是否违反 rules/ 下的规则。只输出问题清单,按严重程度排序,每条给出文件和行号,不要改代码。"

(2)审查整个分支

提测前审一次整个分支相对主分支的改动,重点看跨文件的影响:

bash 复制代码
codex exec "对比 develop 分支,审查本分支所有改动。重点:公共组件的改动影响了哪些页面;新增的异步逻辑有没有竞态;多语言文案是否 10 种语言都补齐。"

(3)专项审查

针对高风险的模块,把对应的规则文件直接喂给它,让它逐条比对:

bash 复制代码
codex exec "按 rules/10-native-ffi.md 逐条审查 lib/base/uniview/ 下的改动,每条规则给出"符合/违反/无关",违反的给出代码位置。"

(4)审完回到 Claude 修

Codex 只出报告,不改代码(审查时我用只读沙盒)。报告交回给 Claude Code,让它逐条核实后再修:审查意见不一定都对,有的是误报,要求它先在代码里证实问题存在,再动手。

一个经验:审查的提示词里一定要写清楚**"只输出问题,不要改代码"和"按严重程度排序"**,否则容易得到一大堆格式、命名之类的建议,真正的 bug 反而被淹没。

4. 其他用法

  • 第二意见:遇到方案选择(比如状态放在哪一层、缓存怎么设计),让两个模型各出一个方案,对比取舍;
  • 排查疑难 bug:Claude 卡住、反复修不好的问题,换 Codex 从头看一遍,经常能换个角度找到原因;
  • 批量机械改动:比如 API 迁移、废弃用法替换这类规则明确的改动,交给 Codex 跑,Claude 的额度留给需要设计的任务。

八、实际成果

这套方式最完整的一次实践,是一个 Flutter 桌面端看图软件(macOS + Windows),由我一个人借助 AI 从 0 开发:

  • 900+ 次提交 ,业务代码 5 万多行 Dart;
  • 测试代码 3.4 万行 ,1600+ 个测试用例,本地全量跑一遍约 50 秒;
  • 覆盖原生库集成(C/C++、Rust)、图像处理、局域网设备连接、macOS Quick Look 扩展、Windows 资源管理器扩展、签名公证与安装包发布;
  • 已发布多个正式版本。

一个人能把这样体量的项目做下来、并且质量可控,靠的不是 AI 写代码有多快,而是规范 + 自动检查 + 交叉审查让 AI 的产出可以被信任。


九、体会

  1. 先复述、再定档、再动手,流程比提示词更重要;
  2. 计划要检验,任务也要检验:计划先审再做,任务做一项测一项;
  3. 提交前全面检查:改动收集不漏、analyze + 全量测试对比基线、影响面回归,严重问题不清零不提交;
  4. 规则要有执行手段,否则就是一纸空文;
  5. 优先写成 lint 和守卫测试,它们与工具无关,Claude、Codex、人都适用;hook 只做 hook 才能做的事;
  6. hook 要便宜:编辑后亚秒级,重活放到 Stop;
  7. 阻断要克制:只拦真正的错误,否则 AI(和人)都会学会无视;
  8. 文档会腐坏,让 AI 以代码为准;
  9. 进度落盘:需求文档就是交接文档,换工具、换会话都能无缝继续;
  10. 写和审用不同的模型,交叉审查比自审靠谱得多。

AI 编程工具更新很快,但"把经验沉淀成可执行的约束"这件事,不管换什么工具都有价值。

相关推荐
AI大模型-小华1 小时前
AI 编程进入深度开发阶段:什么时候普通方案不够,需要更高算力支持?
人工智能·chatgpt·ai编程·vs code·codex·codex cli
fellow992 小时前
DSH Desktop:把 DeepSeek Harness 变成真正的桌面应用
electron·agent·ai编程·deepseek
坊钰3 小时前
【LangGraph框架入门级】2. 图核心概念:State、Node、Edge 与 Reducers
python·ai编程·langgraph
Rain的Java大神之路3 小时前
[特殊字符]8年Java老兵转型AI Agent:90%的人挂在同一个坑,根本不用学Python!(万字实战复盘,建议收藏)
java·数据库·人工智能·后端·python·spring·ai编程
西西学代码4 小时前
像素和颜色之间的转换(Flutter)
前端·javascript·flutter
threerocks4 小时前
绝处求生!免费、低价渠道用上 Opus 5.5 全网最严谨白嫖与实战评测地图
aigc·openai·ai编程
曹小强wxx5 小时前
Flutter 局域网设备发现:UDP 广播 + mDNS 双路合并,以及靠谱的在线判定与断线重连
物联网·网络协议·flutter
suliqiang5 小时前
AI 编程新范式:Vibe、Agentic 与 Spec 实战指南
人工智能·ai编程·ai-native