在 ECC 中使用 /flutter-review 命令对 Flutter/Dart 变更实施工程化代码审查
/flutter-review 是 ECC(Agent Harness Performance Optimization System)面向 Flutter/Dart 工程的内置审查命令:它以 git diff --staged 与 git diff 为输入,调用 flutter-reviewer Agent,对 widget 最佳实践、状态管理、Dart 惯用法、性能、可访问性与安全进行系统性检查,并按严重级别输出可执行的修复建议。阅读本文后,你将掌握该命令的触发前置条件、完整审查清单、输出格式与审批门禁规则,并能在提交 PR、发布生产版本前将其嵌入团队的自动化协作流程。
命令定位:一次调用完成的五步审查管线
根据 commands/flutter-review.md 的定义,该命令的核心动作分解为五个步骤,形成了从"收集上下文"到"输出结论"的完整闭环:
- Gather Context(收集上下文):审查
git diff --staged(已暂存改动)与git diff(未暂存改动),确定本次审查的变更集。 - Inspect Project(检查项目):读取
pubspec.yaml、analysis_options.yaml,并识别项目采用的状态管理方案(BLoC、Riverpod、Provider、GetX、MobX、Signals 或 Flutter 内置方案)。 - Security Pre-scan(安全预扫描):优先检查硬编码密钥、明文 HTTP 调用等 CRITICAL 级安全问题。
- Full Review(完整审查):应用完整审查清单进行逐文件、逐行审查。
- Report Findings(输出结论):将发现的问题按严重程度分组,并附带修复指引。
从底层实现看,Agent 侧的执行流程与命令描述严格对应。在 agents/flutter-reviewer.md 中,Agent 被要求按顺序执行:先运行 git diff --staged / git diff,若无 diff 则回退到 git log --oneline -5;随后检查 pubspec.yaml、analysis_options.yaml、CLAUDE.md,判断是否为 monorepo(melos/workspace),并特别强调识别状态管理与路由/DI 方案后再调整审查口径,避免把惯用法误判为违规。这一设计使整套审查体系是"库无关"的(library-agnostic),无论项目使用哪种状态管理库都能工作。
运行前置条件:让审查建立在可编译的代码之上
在运行 /flutter-review 之前,文档要求依次满足四个前置条件,任何一项未通过都会让审查结论失真:
| 前置条件 | 说明 | 配套命令 |
|---|---|---|
| 构建通过 | 对损坏代码做审查是不完整的 | 先运行 /flutter-build |
| 测试通过 | 确认没有引入回归 | 先运行 /flutter-test |
| 无合并冲突 | 冲突会让 diff 混入非本意的改动 | 手动解决冲突 |
flutter analyze 干净 |
静态分析警告应在审查前清零 | flutter analyze |
这并非孤立要求。在 ECC 的命令生态中,/flutter-build 与 /flutter-review 构成了"先修编译 → 再跑测试 → 最后审查"的流水线关系。commands/flutter-build.md 负责通过 flutter analyze、flutter pub get、dart run build_runner build --delete-conflicting-outputs 等诊断命令增量修复构建问题;commands/flutter-test.md 则负责运行 flutter test(支持 --coverage、--name、--update-goldens 等参数)。审查发生在二者之后,确保评审对象是真实、稳定、可回归的代码。
何时使用该命令
文档给出了五种典型场景:
- 提交 PR 之前:Flutter/Dart 改动在构建与测试通过后、合入前做最后把关;
- 新功能实现之后:尽早发现隐患,避免问题累积;
- 评审他人的 Flutter 代码:复用成熟检查体系,代替零散的人工记忆;
- 审计特定组件:针对某个 widget、状态管理组件或 service 类做定点体检;
- 生产版本发布前:作为发布门禁的一部分执行。
审查范围总览:从 CRITICAL 到 LOW 的五级检查体系
命令文档将审查关注点与严重级别映射为一张速览表,它是整套审查的"目录",每一项在 Agent 的详细清单中都有对应的检查细节:
| 关注领域 | 严重级别 |
|---|---|
| 硬编码密钥、明文 HTTP | CRITICAL |
| 架构违规、状态管理反模式 | CRITICAL |
| Widget 重建问题、资源泄漏 | HIGH |
缺少 dispose()、await 之后使用 BuildContext |
HIGH |
| Dart 空安全、缺失错误/加载状态 | HIGH |
const 传播、widget 组合 |
HIGH |
性能:build() 中做昂贵操作 |
HIGH |
| 可访问性、语义标签 | MEDIUM |
| 状态转换缺少测试 | HIGH |
| 硬编码字符串(未走 l10n) | MEDIUM |
| Pub 依赖卫生 | LOW |
安全预扫描与升级机制
审查流程中存在一条重要的中止/升级规则:安全预扫描若发现 CRITICAL 级安全问题,Agent 会立即停止并移交 security-reviewer,而不是继续常规审查。在 agents/flutter-reviewer.md 中,预扫描覆盖六类输入:
- Dart 源码中硬编码的 API Key、Token 或密钥;
- 敏感数据明文存储(未使用平台安全存储);
- 用户输入与 deep link URL 缺少输入校验;
- 明文 HTTP 流量;经
print()/debugPrint()泄漏敏感数据; - 导出的 Android 组件与 iOS URL scheme 缺少防护。
这一"安全优先、发现即升级"的设计,确保了 CRITICAL 问题不会被淹没在大量中低级别风格建议中,符合 Agent 文档中"将安全、数据丢失与正确性置于风格之上"的优先级原则。
底层 Agent 的噪声控制策略
为了让审查结论真正可用,agents/flutter-reviewer.md 对报告质量做了三条硬约束:
- 只报告置信度 > 80% 的问题——避免猜测性结论污染报告;
- 同类问题合并——例如"5 个 widget 缺少
const构造器"合并为一条发现,而不是 5 条噪音; - 默认跳过纯风格偏好——除非违反项目约定或引发功能问题;
- 仅对 CRITICAL 安全问题标记未改动代码——控制 diff 之外的信息量。
Agent 的角色定位也被明确为"只报告、不重构"(You DO NOT refactor or rewrite code — you report findings only),与 /flutter-build(负责修改修复)形成职责分离,避免审查者在评审过程中夹带私货改动。
分严重级的详细审查清单解读
CRITICAL:架构与状态管理
架构审查会先识别项目采用的是 Clean Architecture、MVVM 还是 feature-first 结构,再检查层间约束。依据 rules/dart/patterns.md,Clean Architecture 的层边界是:domain 层为纯 Dart(不得 import package:flutter 或任何数据层包)、data 层在仓库边界完成 DTO→领域实体映射、presentation 层调用用例而非直接依赖仓库。审查项因此覆盖:
- widget 中承载业务逻辑——复杂逻辑应归属状态管理组件而非
build()或回调; - 数据模型跨层泄漏——分离了 DTO 与领域实体的项目必须在边界处映射;
- 跨层 import——内层不得依赖外层;
- 框架泄漏进纯 Dart 层——domain/model 层不得 import Flutter;
- 循环依赖——A 依赖 B 且 B 依赖 A;
- 跨包私有
src/import——package:other/src/internal.dart破坏 Dart 包封装; - 业务逻辑中直接实例化依赖——状态管理器应经注入接收依赖;
- 层边界缺少抽象——应依赖接口而非具体类。
状态管理审查分为两类范式分别执行:不可变状态方案(BLoC、Riverpod、Redux)重点检查状态不可变性(用 copyWith 而非原地修改)与 ==/hashCode 的正确实现;响应式变更方案(MobX、GetX、Signals)重点检查变更是否只经 @action、.value、.obs 等响应式 API 发生,以及派生值是否使用 computed 机制。两种范式共享以下通识检查:
- 布尔标志位泛滥(
isLoading/isError/hasData独立字段)——使"不可能状态"可被表示; - 状态处理非穷尽——未处理的分支会静默失效;
- 单一职责被破坏——"god" 管理器同时处理无关关注点;
- widget 直接调用 API/DB——应经 service/repository 层;
- 在
build()中订阅——应使用声明式 builder; - Stream/订阅泄漏——手动订阅必须在
dispose()/close()中取消; - 缺失 error/loading 状态——每个异步操作都应显式建模 loading/success/error。
针对"穷尽状态"的最佳实践,skills/flutter-dart-code-review/SKILL.md 给出了具体代码示范——用 Dart 3 sealed class 取代布尔标志位,使不可能状态在类型层面不可表示:
// BAD — boolean flag soup allows impossible states
class UserState {
bool isLoading = false;
bool hasError = false; // isLoading && hasError is representable!
User? user;
}
// GOOD (immutable approach) — sealed types make impossible states unrepresentable
sealed class UserState {}
class UserInitial extends UserState {}
class UserLoading extends UserState {}
class UserLoaded extends UserState {
final User user;
const UserLoaded(this.user);
}
class UserError extends UserState {
final String message;
const UserError(this.message);
}
Agent 还针对跨组件依赖给出按方案区分的审查口径:Riverpod 中 provider 之间 ref.watch 是预期行为,只标记循环或过度缠绕的链;BLoC 中 bloc 不应直接依赖其他 bloc,应优先共享 repository;其余方案遵循各自文档化的通信约定。
HIGH:Widget 组合、性能、资源生命周期与错误处理
Widget 组合检查点包括:build() 超过约 80 行应拆分(SKILL 中放宽到 80–100 行);返回 widget 的私有 _build*() 辅助方法应提取为独立 widget 类(以启用元素复用、const 传播与框架优化);全字段为 final 的 widget 必须声明 const 构造器;StatefulWidget 被滥用(无可变局部状态时应优先 StatelessWidget);列表项缺少稳定 ValueKey;硬编码颜色/文本样式(应使用 Theme.of(context).colorScheme/textTheme,否则破坏深色模式);硬编码间距(应用设计 token 或命名常量)。
性能检查点覆盖:重建范围过宽(状态消费者包裹过大子树,应收窄并使用 selector);build() 中做排序、过滤、正则或 I/O(应在状态层计算);MediaQuery.of(context) 过度使用(应用 MediaQuery.sizeOf(context) 等专用访问器);大数据列表用具体构造器而非 ListView.builder/GridView.builder 惰性构建;图片缺少缓存与 cacheWidth/cacheHeight 解码尺寸控制;动画中直接用 Opacity(应使用 AnimatedOpacity/FadeTransition);IntrinsicHeight/IntrinsicWidth 滥用导致额外布局遍历;复杂独立重绘子树缺少 RepaintBoundary。
资源生命周期聚焦 dispose() 义务:initState() 中创建的 controller、订阅、定时器都必须释放;await 之后使用 BuildContext 前必须检查 context.mounted(Flutter 3.7+),文档中给出的修复示例即:
// Fix: Add `if (!context.mounted) return;` before any navigation after awaits (Flutter 3.7+).
此外还包括:异步回调 setState 前检查 mounted;BuildContext 不得存储在单例或静态字段中;StreamController 未关闭、Timer 未取消;重复的 init/dispose 生命周期逻辑应提取为可复用模式。
错误处理层面,审查要求同时设置 FlutterError.onError 与 PlatformDispatcher.instance.onError 作为全局兜底;接入 Crashlytics/Sentry 等错误上报服务;将状态管理错误观察者(BlocObserver、ProviderObserver 等)接入上报;为 release 模式定制 ErrorWidget.builder 以避免红屏直达用户;原始异常应在到达 UI 前映射为用户友好、已本地化的消息。
HIGH:测试覆盖
测试维度要求状态管理器变更必须有配套单元测试、新增/变更 widget 应有 widget 测试、设计关键组件应有 golden 测试(像素级回归)、所有状态转换路径(loading→success、loading→error、retry、empty)都需覆盖、外部依赖必须 mock 且测试间无共享可变状态、异步测试不得依赖时序假设(使用 pumpAndSettle 或显式 pump(Duration))。这些要求与 rules/dart/testing.md 中的约定互相印证:业务逻辑行覆盖率目标 80%+,flutter test --coverage 的覆盖率不达标应阻断 CI。
MEDIUM:可访问性、平台/响应式与本地化
可访问性(rules/dart 与 SKILL 一致):图片缺 semanticLabel、图标缺 tooltip;可交互目标小于 48×48 px;仅用颜色传达状态;装饰性元素未用 ExcludeSemantics、相关元素组未用 MergeSemantics;硬编码字号不响应系统字体缩放;文本对比度低于 4.5:1。
平台、响应式与导航:内容被刘海/状态栏遮挡时缺 SafeArea;Android 返回键或 iOS 边缘右滑返回失效;AndroidManifest.xml/Info.plist 中未声明平台权限;平板/桌面/横屏下固定布局不可用;无界文本未用 Flexible/Expanded/FittedBox 导致溢出;混用 Navigator.push 与声明式路由(应二选一);硬编码路由路径(应使用常量、枚举或生成路由);deep link URL 未净化即导航;受保护路由缺少认证守卫。
国际化(l10n):所有用户可见文本必须走本地化系统;本地化文本禁用字符串拼接(应使用参数化消息);日期、数字、货币格式必须 locale-aware。
LOW:依赖与构建卫生
Pub 依赖检查在命令文档中归为 LOW,但 skills/flutter-dart-code-review/SKILL.md 展开得相当详尽:评估 pub.dev 包时应看 pub points(目标 130+/160)、verified 发布者、最后发布时间(停更超一年有风险)、开源问题响应、许可证兼容性等;版本约束优先用插入符语法 ^1.2.3;定期 flutter pub outdated;生产 pubspec.yaml 中不应有未注释说明的 dependency overrides;monorepo 中内部包只可从公开 API 导入,依赖用 workspace 解析而非硬编码 path: ../../。
项目本身应配置严格的静态分析:rules/dart 要求 analysis_options.yaml 启用 strict-casts、strict-inference、strict-raw-types,采用 very_good_analysis、flutter_lints 或自定义严格规则集,且 // ignore: 必须有解释性注释。
实战会话示例:一次完整的登录页审查输出
commands/flutter-review.md 提供了完整的示例会话,直观展示了命令的运行结果形态。以下是其核心内容的还原,帮助你在实际使用前建立对输出格式的预期:
User: /flutter-review
Agent:
# Flutter Code Review
## Context
Changed files:
- lib/features/auth/presentation/login_page.dart
- lib/features/auth/data/auth_repository_impl.dart
State management: Riverpod (detected from pubspec.yaml)
Architecture: feature-first
## Security Pre-scan
✓ No hardcoded secrets detected
✓ No cleartext HTTP calls
## Review Findings
[HIGH] BuildContext used after async gap without mounted check
File: lib/features/auth/presentation/login_page.dart:67
Issue: `context.go('/home')` called after `await auth.login(...)` with no `mounted` check.
Fix: Add `if (!context.mounted) return;` before any navigation after awaits (Flutter 3.7+).
[HIGH] AsyncValue error state not handled
File: lib/features/auth/presentation/login_page.dart:42
Issue: `ref.watch(authProvider)` switches on loading/data but has no `error` branch.
Fix: Add error case to the switch expression or `when()` call to show a user-facing error message.
[MEDIUM] Hardcoded string not localized
File: lib/features/auth/presentation/login_page.dart:89
Issue: `Text('Login')` — user-visible string not using localization system.
Fix: Use the project's l10n accessor: `Text(context.l10n.loginButton)`.
## Review Summary
| Severity | Count | Status |
|----------|-------|--------|
| CRITICAL | 0 | pass |
| HIGH | 2 | block |
| MEDIUM | 1 | info |
| LOW | 0 | note |
Verdict: BLOCK — HIGH issues must be fixed before merge.
值得注意的是每条发现都遵循 [SEVERITY] 摘要 → File: 路径:行号 → Issue: 问题描述 → Fix: 修复建议 的固定模板,其中包含精确到行号的文件定位,可直接交给开发者或 /flutter-build 执行修复。这一模板同样定义在 Agent 文档的 Output Format 一节中。
输出格式与审批门禁
每一次审查都以统一的汇总表收尾。汇总表的语义如下:
| 严重级别 | 计数含义 | 状态语义 |
|---|---|---|
| CRITICAL | 0 为通过 | pass / block |
| HIGH | 影响可否合并 | block |
| MEDIUM | 建议优化 | info |
| LOW | 风格提示 | note |
审批规则非常明确:
- Approve(通过):不存在 CRITICAL 或 HIGH 级问题;
- Block(阻断):存在任意 CRITICAL 或 HIGH 级问题,必须在合并前修复。
示例中 2 个 HIGH 级问题的结论就是 Verdict: BLOCK。这一"非黑即白"的门禁设计,使 /flutter-review 可以直接作为 PR 合并的自动化闸门使用:HIGH 以上清零即可放行,而 MEDIUM/LOW 仅作信息参考,避免非关键建议阻塞迭代节奏。
库无关的状态管理快速参考
由于审查需要针对项目实际采用的状态管理方案调整口径,SKILL 文档维护了一张跨方案速查表,映射了各方案中"状态容器 / UI 消费者 / Selector / 副作用 / 释放机制 / 测试手段"六个维度的对应物,供审查者与被审查方对齐语言:
| 原则 | BLoC/Cubit | Riverpod | Provider | GetX | MobX | Signals | 内置 |
|---|---|---|---|---|---|---|---|
| 状态容器 | Bloc/Cubit |
Notifier/AsyncNotifier |
ChangeNotifier |
GetxController |
Store |
signal() |
StatefulWidget |
| UI 消费者 | BlocBuilder |
ConsumerWidget |
Consumer |
Obx/GetBuilder |
Observer |
Watch |
setState |
| Selector | BlocSelector/buildWhen |
ref.watch(p.select(...)) |
Selector |
N/A | computed | computed() |
N/A |
| 副作用 | BlocListener |
ref.listen |
Consumer 回调 |
ever()/once() |
reaction |
effect() |
callbacks |
| 释放机制 | auto via BlocProvider |
.autoDispose |
auto via Provider |
onClose() |
ReactionDisposer |
manual | dispose() |
| 测试手段 | blocTest() |
ProviderContainer |
ChangeNotifier 直接测 |
Get.put in test |
store 直接测 | signal 直接测 | widget test |
如何与 ECC 中的相关工具协作
/flutter-review 不是孤立命令,它与 ECC 面向 Flutter/Dart 的工具链形成了完整闭环:
- /flutter-build:解决编译与静态分析问题,是审查的前置步骤;
- /flutter-test:运行单元/widget/golden/集成测试并增量修复失败,同样是审查的前置步骤;
- /code-review:语言无关的通用代码审查,适合 Flutter 之外的场景或需要更广视角时;
- flutter-reviewer Agent:命令实际调用的执行者,定义了完整工作流、清单与输出格式;
- flutter-dart-code-review Skill:承载完整审查清单的权威参考,含状态管理速查表与各维度检查点,是 Agent 与开发者共用的知识底座;
- rules/dart 规则集:包含 coding-style.md、patterns.md、security.md、testing.md、hooks.md,把审查标准沉淀为仓库级约定,供所有 Agent 统一遵循。
在仓库中找到这些文件后,你既可以把 /flutter-review 当作手动触发器在关键节点调用,也可以把它的审批规则(CRITICAL/HIGH 清零)复刻到 CI 门禁中,形成"构建 → 测试 → 审查 → 合并"的自动化质量流水线。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0629
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python07
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00