这两年帮团队把 AI 写代码的流程跑通之后,我最大的感受是:AI 的代码能力早就不是瓶颈,瓶颈是你的项目经不经得起它“自由发挥”。尤其在 Flutter 项目里,目录结构、状态管理、依赖锁定、平台通道这些约束一旦没写清楚,Claude Code 或 Codex 分分钟给你整出“结构性翻车”——不是语法报错那种低级翻车,而是把 BLoC 文件塞进 models、在 build 方法里直接调 Repository、给全部 widget 加 const 却把测试改坏,这种让你 review 到怀疑人生的翻车。
这篇文章不聊概念,直接分享我怎么用 Claude Code + Codex 给 Flutter 项目搭一套 AI 友好型工程规范,把 AI 写代码的“野生行为”框进团队约定里。整个过程包括工具分工、规则文件设计、权限约束、CI 兜底,以及几类高频翻车场景的排查实录。适合正在用 AI 写 Flutter、或者准备把 AI 接入正式项目但怕失控的同学参考。
1. AI 写的 Flutter 代码为什么总“翻车”
先说结论:AI 翻车不是因为它“蠢”,是因为它对你项目的了解来自上下文窗口,而不是来自你对架构的默认认知。人写代码时脑子里装着几十条隐性约定,AI 只看到你给的 prompt 和当前文件,它当然按“最常见写法”来。
1.1 病根:语义理解错位
Claude Code 和 Codex 这类工具的强项是“模式补全”:你给它一个需求,它从海量训练数据里找最相似的代码模式补给你。问题在于,Flutter 生态里的“最常见模式”未必是你项目的模式。
举个我曾踩过的坑:团队用的状态管理是 Riverpod,AI 看到需要全局共享的数据,习惯性生成ChangeNotifierProvider或InheritedWidget,因为这两者在训练数据里出现频率最高。代码能跑,但融入不了项目的数据流体系,后续维护的人要花两倍时间理清依赖关系。
这就是语义错位。AI 理解的是“通用 Flutter”,你要求的是“我们项目的 Flutter”。要治这个病,就得把项目约定变成 AI 显式可见的规则。
1.2 Flutter 项目的特殊痛点
Flutter 项目在 AI 代码生成这件事上,痛点比后端项目更密集:
- 目录结构约定强:feature-first 还是 layer-first,直接影响 AI 生成的 import 路径和文件归属。
- 状态管理绑定深:选了 Bloc、Riverpod 还是 GetX,决定了数据流写法、dispose 时机、上下文生命周期的写法,AI 最容易在这块“自由发挥”。
- 隐式依赖多:
BuildContext的跨组件传递、Theme.of()的读取位置、MediaQuery的访问时机,AI 经常生成能编译但运行时“看起来怪”的代码。 - 多平台目录敏感:
android/、ios/、web/下的原生工程改动,AI 一旦动错了文件,后果很隐蔽,而且 CI 还不一定拦得住。
痛点越多,越要在规范里把边界画死。后文所有规则设计,都是围绕这几类痛点来的。
1.3 “翻车”的典型现场
| 翻车场景 | 表现 | 后果 |
|---|---|---|
| 目录乱入 | 把页面级 widget 塞进widgets/公共目录 | 依赖耦合,reusable 程度快速下降 |
| 状态管理混用 | 同一个项目多种状态管理写法并存 | 新人维护成本翻倍,架构形同虚设 |
| 过度“美化” | 给大量 widget 乱加const,甚至改了 build 逻辑 | 测试挂掉,性能优化失效 |
| 依赖散装 | AI 直接改pubspec.yaml拉新包 | 版本冲突、lock 文件不稳定 |
| 不动检查 | 改完代码不跑flutter analyze和测试 | 把低质量代码直接推进 PR |
这套现场清单不是吓唬人,是我从真实 review 里攒出来的。接下来要做的所有事,就是把这些场景用规则把它拦在源头。
2. 工具选型与角色分工:Claude Code 与 Codex 怎么配
很多人以为 Claude Code 和 Codex 是同类工具,选一个就行。实际用下来,两者各有脾性,搭配使用反而能形成一条更稳的生产线。
2.1 Claude Code:长上下文的“架构师”
Claude Code 更适合做需要全局视野的任务:理解现有架构、拆解改造方案、跨多文件重构。它的对话上下文能力强,适合你甩给它一堆文件让它在全局层面给方案。
我在规范里给 Claude Code 定位是“架构评审 + 持续开发”。它可以在每个任务开始时先读一遍CLAUDE.md和核心目录结构,再决定怎么动代码。它更像团队里的资深工程师,会先想再写。
2.2 Codex:单点执行的“高级执行者”
Codex 更擅长独立、边界清晰的任务:比如“给这个 model 增加一个序列化方法”“修这个 widget 的边界溢出”。它执行速度快、改动路径直接,但也因为“直接”,更容易不管周围约定直接干。
所以我在规范里给 Codex 定位是“预审后执行 + 批量机械修改”。凡是改动范围明确、不涉及架构调整的任务,优先走 Codex。凡是需要先理解再动脑的任务,走 Claude Code。
2.3 为什么两条线要分开跑
我的团队一度只用一个工具,翻车率居高不下。后来发现不是工具问题,而是任务性质和工具能力不匹配。拆成两条线之后,效果明显改善:
- Claude Code 负责“想清楚再干”,产出的是方案 + 重构代码,review 重点在“方案对不对”。
- Codex 负责“照着干”,产出的是局部修改,review 重点在“改得全不全、有没有越界”。
拆开还有一个好处:并发不冲突。两个工具分别跑不同任务,不会同时改同一个文件,减少“互相覆盖”的 bug。这个在多人协作时尤其重要。
2.4 工作流节奏参考
我目前的参考流程是:
- 需求进来,先丢给 Claude Code 做影响面分析,产出改动清单。
- Claude Code 给出清单后,人工确认,把可机械化的小任务拆给 Codex。
- Codex 改完,统一跑
flutter analyze+flutter test。 - 有问题交给 Claude Code 做复盘和修复,不直接让 Codex 无限返工。
这个节奏有点像“导师定方案,执行人施工,验收人兜底”。分工明确后,AI 写代码的稳定性会高很多。
3. 工程规范落地的具体步骤
规范不是一纸空文,也不是靠口头叮嘱。它必须变成 AI 工具看得见、读得进、执行得了的文件和命令。以下是我在 Flutter 项目里实际落地的一套做法。
3.1 建立 AI 可见的规则文件
Claude Code 会读取项目里的CLAUDE.md,Codex 会读取AGENTS.md。我用这两个文件承载“AI 必须遵守的项目约定”,并在项目根目录统一维护。
文件内容我不写废话,全是能直接执行的约定。比如结构约定、命名约定、状态管理约定、禁止事项。每个约定都要明确到“AI 看到什么情况该怎么做”,而不是只写“请遵循项目架构风格”——AI 没法执行这种模糊指令。
# CLAUDE.md / AGENTS.md 通用模板(节选) ## 项目架构约定 - 本项目采用 feature-first 目录结构。每个 feature 目录必须包含: data/、domain/、presentation/ 三个子层。 - 新增页面必须放在对应 feature 的 presentation/pages/ 下。 - 公共组件只允许放在 lib/shared/widgets/,页面私有组件就近放置。 ## 状态管理约定 - 全局状态管理使用 Riverpod (flutter_riverpod)。 - 禁止引入新的状态管理库。 - StateNotifier / Notifier 必须放在对应 feature 的 domain/ 或 presentation/controllers/ 下。 - Widget 内直接读取 Provider 时,必须使用 ConsumerWidget 或 ref.watch 模式。 ## 数据层约定 - 所有模型类放在对应 feature 的 data/models/ 下,必须实现 fromJson/toJson。 - 禁止在 presentation/ 层直接调用 http 或 dio。 - Repository 对外暴露的数据一律使用领域模型,禁止裸传 JSON Map。关键点:每一条都要有“禁止”或“必须”这种动作词,而不是“尽量”“建议”。AI 对禁令的遵守度远高于对建议的遵守度。
3.2 给每个工具单独定制行为边界
Claude Code 和 Codex 的配置虽然在同一个项目里,但职责不同,规则也要分开。
我用CLAUDE.md约束 Claude Code 的“思考与重构”行为:
- 开始任务前,先读取 lib/ 下的目录结构。 - 涉及跨文件重构时,必须先输出影响范围清单,再动手。 - 不允许一次重构超过 10 个文件,超过则拆分任务。 - 涉及状态管理变更时,必须同步更新对应测试文件。我用AGENTS.md约束 Codex 的“执行边界”:
- 只允许修改任务指定的文件路径。 - 禁止修改 pubspec.yaml 和 pubspec.lock,依赖变更需人工确认后单独提交。 - 禁止修改 android/ 和 ios/ 下的原生工程文件。 - 代码完成后必须执行 flutter analyze 并修复所有警告。3.3 把 lint 规则变成硬约束
规则文件只能约束 AI 的“策略层”,编码风格层面的东西要靠工具链硬约束。
我在项目里做了三件事:
第一,analysis_options.yaml里开启 strict 模式,把flutter_lints升到最严格,并额外启用always_use_package_imports、avoid_dynamic_calls这类能拦住“野路子”的规则。
第二,提交前统一跑dart format和dart fix --apply,保证 AI 生成的代码格式和手写代码一致,diff 干净可审。
第三,把flutter analyze无警告作为合并 PR 的硬性要求,否则机器人直接打回。
这三件事不针对 AI,但对 AI 产出的约束效果最好,因为 AI 对“报错反馈”特别敏感,几次打回之后它就会主动避开雷区。
3.4 设置 AI 的“黑名单”
我建立了一个BLACKLIST.md或者说在黑名单区明确写入“AI 绝对不允许做的事”。这些事包括:
- 禁止修改
pubspec.lock。 - 禁止在
/test下直接生成大型 fixture 文件描述业务逻辑。 - 禁止修改 CI 配置文件。
- 禁止跨 feature 引用另一个 feature 的内部实现。
- 禁止在 build 方法里执行异步操作。
这些黑名单最好放在 CLAUDE.md 和 AGENTS.md 开头,保证 AI 第一眼就看到。我在实践里的感受是:明确的黑名单比一堆“应当如何”的正面规则更有效,AI 对“禁止”二字的权重明显更高。
4. 实操:给 Flutter 项目配一套可复用的 AI 工作流
规范文件写好了,接着要把工作流跑起来。这一节是实际操作记录。
4.1 初始化规则文件
第一步,在项目根目录创建CLAUDE.md和AGENTS.md,把 3.1 节的内容填进去。接着创建docs/ai-workflow/目录,存放给 AI 看的“工作流说明”。
第二步,在规则文件里写入“任务执行流程”:
## AI 编码任务标准流程 1. 阅读本文件(CLAUDE.md / AGENTS.md)全部内容。 2. 读取 lib/ 目录结构,理解相关 feature 的现有代码。 3. 输出本次任务影响范围,等待用户确认。 4. 只修改影响范围内文件,按约定完成实现。 5. 运行 flutter analyze 和 flutter test,修复所有报错。 6. 输出变更摘要,包含修改文件列表和测试结果。把这个流程放进规则文件后,AI 每次接任务都会先“走流程”,而不是上来就写代码。实测下来,仅这一步就把“AI 问你需求但直接开写”的概率降了大半。
4.2 配置 Claude Code 的项目记忆与权限
Claude Code 启动时默认会加载项目里的CLAUDE.md,你可以让它在每个会话开始时主动读取。
实际配置中,我习惯在CLAUDE.md里加一个固定小节:
## 会话开始必读 - 先执行 `find lib -type f | head -50` 了解模块布局。 - 如任务涉及现有 feature,先读该 feature 的 README 或 routes 文件。权限上,我要求 Claude Code 对android/、ios/、macos/目录默认只读,除非明确授予写权限。项目里如果有原生代码,这条能防住“AI 顺手改原生配置”的坑。
4.3 配置 Codex 的文件允许范围
Codex 的执行路径我用AGENTS.md里的白名单控制:
- 允许修改 lib/**、test/** 下与任务相关的 dart 文件。 - 允许修改 assets/** 中的资源清单,但需确认不是 JSON 数据内容。 - 禁止修改 pubspec.yaml、pubspec.lock、analysis_options.yaml、CI 配置。这里提一句:Codex 这类工具通常在沙箱或受限目录运行,让它只看到“允许改”的文件,能大幅降低误操作。不要因为工具支持“全库访问”就真的给它全库权限。
4.4 用 CI 做最后一道防线
AI 即使遵守规则,也可能生产低质量代码。CI 必须做最后一道防线。
我在 CI 流水线里加了三个任务:
stages: - name: analyze script: - flutter analyze --fatal-infos - name: test script: - flutter test --coverage - name: check-diff script: - bash scripts/check_ai_diff.sh核心是check-diff这个脚本。它会检查 PR 里是否包含 AI 触发的黑名单文件变更、是否缺失测试文件、是否有TODO/FIXME残留。黑名单变更直接 fail,缺失测试会警告但不直接阻塞。这样人工 review 时不需要逐行看,机器先筛一遍。
4.5 真实场景演练:让 AI 新增一个页面
拿一个最常见需求“新增用户详情页”完整走一遍流程:
- 我在 Claude Code 会话里描述需求,附带指定文件路径和 feature:
请按 feature/user 的现有结构,新增用户详情页 presentation/pages/user_detail_page.dart,使用 Riverpod 读取 UserDetailController 状态。 - Claude Code 先读取
CLAUDE.md和现有 feature 结构,输出改动清单,包含新页面文件、controller 文件、路由注册文件。 - 我确认清单没问题后,让它开工。
- 代码完成后,CI 自动跑 analyze 和 test,提示一处
avoid_dynamic_calls警告,Claude Code 根据报错修复。 - 人工 review 代码 diff,确认新页面遵循既有命名规范和路由方式,通过合并。
这个流程比“直接把需求丢给 AI”看起来多了一步,但恰恰是多出来的确认环节,把大多数翻车拦在了合入之前。
5. 常见“翻车”场景与排查技巧实录
规范搭好了,不代表不会翻车。这一节分享几个我实际遇到的高频问题,以及对应的排查方法。
5.1 症状一:AI 把文件乱塞目录
有段时间 Claude Code 连续把两个 feature 的页面组件都放到了共享widgets/目录。原因是它有“组件复用倾向”,看到相似 UI 就想抽公共组件。
排查后我在CLAUDE.md里新增了一条硬止损:
- 新组件必须跟随所属 feature。超过两个 feature 复用后,才允许提升至 shared/widgets/。配合目录规则,后续这类情况基本消失。经验是:AI 的“过度设计”要多用“先做到,再抽象”的节奏去纠正,而不是只给一个“注意模块化”的建议。
5.2 症状二:AI 自作主张“重构”无关代码
Codex 在改一个页面时,顺手把页面对应的旧 controller 改造了一遍,导致其他页面依赖也变了。
排查后定位到问题:我给 Codex 的 prompt 描述里写了“顺手优化一下 controller”,于是它把“优化”理解成了重构全部逻辑。
修正方法是把任务描述改成绝对边界:
只修改 user_detail_page.dart 一处文件。 不得修改 user_detail_controller.dart,如有优化建议请在回复中说明。从此 Codex 在单文件任务里很少再越界。经验:给 AI 的任务描述,边界越窄越不会翻车。范围描述宁可少写,不可多写。
5.3 症状三:AI 引入新依赖制造冲突
某次 Claude Code 为了实现一个很简单的日期格式化功能,直接在pubspec.yaml引入了 intl 包,但版本区间和现有 package 冲突,最后是逐个对 lock 文件才解决。
排查后我在规则里加了:
- 禁止因小功能引入新依赖。 - 优先使用 Flutter SDK 自带能力或项目已有依赖实现。 - 如确需新增依赖,必须单独提交并在 PR 描述中说明理由。现在 AI 遇到依赖类需求,会先在回复里写建议,而不是直接改文件。这个转变很关键:把“改动”降级成“建议”,把决策权保留给人。
5.4 症状四:AI 改完代码不跑测试
AI 经常“改完即走”,尤其 Codex,在单文件任务里尤其明显。
我试过在AGENTS.md里反复强调“必须跑测试”,但效果一般。后来我改用了“流程强制”:凡是 Codex 任务,都通过封装脚本启动,脚本里强制在任务结束前跑flutter test,测试不过直接回滚该文件变更。
#!/usr/bin/env bash # run_codex_task.sh # 在 codex 完成任务后自动执行测试校验 codex do "$@" if [ $? -eq 0 ]; then echo ">> Running flutter test..." flutter test if [ $? -ne 0 ]; then echo ">> Tests failed. Rolling back AI changes..." git checkout -- lib/ exit 1 fi fi这把“AI 是否跑测试”从自觉性变成了系统强制,彻底解决。
5.5 高频问题速查表
| 问题 | 根因 | 解法 |
|---|---|---|
| 文件乱入目录 | prompt 缺少路径约束 | 在规则中明确 feature-first 目录映射 |
| 顺手重构无关代码 | 任务边界模糊 | 限制只改指定文件,建议替代行动用说明 |
| 依赖版本冲突 | AI 自行改 pubspec | 禁止直改依赖,必须人工确认 |
| 不跑测试就收工 | 依赖自觉性 | 用 CLI 脚本封装,强制测试 |
| 多次重复修复同一问题 | 根因没有被约束 | 复盘后补充规则或黑名单 |
5.6 独家避坑心得
最后一条经验:规则文件不是一次性写好的,它是“长”出来的。每次翻车,我都会把根因写进CLAUDE.md或AGENTS.md,两三个月后,这套文件就会变成团队架构决策的完整沉淀。AI 写代码越用越稳,本质上不是因为模型变强了,是因为你用规范把项目的隐性知识显性化了。
我个人在实际操作中还有一个习惯:每次给 Claude Code 或 Codex 派活之前,都先看一眼规则文件里有没有覆盖当前任务的约束。没有就现写,写完之后再派活。这个动作看似多花两分钟,实际上能省掉后面大量的 review 和返工时间。到现在,我们团队用 AI 合入代码的比例越来越高,但 review 的负担反而下降了——因为大部分“翻车”在规则层就已经被拦掉了。以后这个项目如果继续扩展,我也会把更多架构决策同步沉淀进去。