dart-skills CLI:为AI编程助手补齐Dart生态技能包,告别三年前代码 最近 AI 编程助手火得不行但你要是真拿它去写 Dart心里的落差会非常明显。看着它生成的代码能跑、语法没错可一进 code review 就发现到处是三年前的写法过时的包、绕远路的异步、没有 const 构造、完全不 Dart 的类设计。这不是模型笨是它压根没被喂过 Dart 的行业常识。我做了一个叫 dart-skills 的 CLI 工具专门把 Dart 生态里的语言规范、框架实践、选包原则打包成 AI 可以直接加载的 skills让 Codex CLI、Claude CLI 这类工具在动手之前先补课。这篇就把 v1.0 的设计思路、命令细节、踩坑经历一次说清楚。1. 为什么做 dart-skills CLIAI 写 Dart 的体验太撕裂了1.1 一个让人血压升高的评审现场先还原一个我上个月真实遇到的场景。团队里一个同学用 AI 助手生成了一段 Flutter 的网络层代码从语法上挑不出毛病null safety 也处理对了但整体方案是拿三年前的思路在写用的还是http包而不是dio或http的现代用法错误处理全靠catch之后打印日志没有统一的失败模型请求取消和超时更是一点没有。更离谱的是AI 把一个原本可以同步返回的普通配置加载函数硬生生写成了async然后为了让调用方舒服又在函数内部塞了个Future.value包装。代码能跑但味道很冲。这不是个例。我陆陆续续测了 Codex CLI、Claude CLI 和几个国内外的 AI 编程工具发现一个共同现象这些模型在 Python、TypeScript 上的表现明显比 Dart 好一个档次。原因很简单——Dart 的语料占比小生态最新动态更少进入训练集。模型对Dart 语法是熟的对Dart 项目该怎么写是模糊的。1.2 问题不在模型而在上下文缺失很多人遇到这个情况第一反应是换模型、换工具或者写一段更长的 system prompt 去约束。我一开始也这么干过prompt 里塞了一堆请遵循 Dart 官方风格请优先使用空安全特性Flutter 中请使用 const 构造函数之类的口号。结果效果有限。问题在于提示词是一次性的且没有结构。你写 500 字的规则模型记住了要遵守 Dart 规范但具体到某个场景——比如现在的 Dart 3 里推荐用 pattern 还是普通 switch、Record 类型在什么情况下该拆解、BuildContext 跨异步间隙后必须检查mounted——这些细颗粒度的知识靠几句口号根本带不动。模型需要的是在正确时机被注入的正确知识片段而不是开头一大段然后迅速被遗忘的规则清单。这正是 AI 圈里Skills机制想解决的问题。大模型社区后来形成的 skill harness 思路是把某一领域的知识做成一个带描述索引的技能包AI 工具在运行时根据当前任务判断该加载哪个技能然后把这个技能包的具体内容放进上下文。简单说prompt 是你告诉他一次skill 是给他一本随时可查的手册。1.3 项目定位给 AI 补上 Dart 的行业常识所以当我在 2025 年初决定认真解决AI 写不好 Dart这个问题时没有去调模型也没有做 IDE 插件而是做了一个命令行工具dart-skills CLI。它的核心功能可以概括成三句话内置一套经过整理的 Dart/Flutter 技能包覆盖语言特性、框架实践、测试策略、性能规范、生态选型通过add、list、update等命令管理技能包的组成与版本通过bundle命令把技能包输出成不同 AI 工具能直接识别的格式一键安装进 Codex CLI、Claude CLI 或其他兼容 skill harness 的工具目录。v1.0 是这个工具的可用起点。它不追求覆盖所有 Dart 场景但保证覆盖到的内容都是行业里真正会用的写法而且所有技能包都是可审查、可测试、可版本化的普通文件不搞黑盒。2. Skills 机制拆解为什么技能包比万能提示词靠谱2.1 从 Prompt 到 Skill一次上下文供给方式的转变要理解 dart-skills 的价值得先搞清楚 skill 和 prompt 的本质区别。Prompt 是你在会话开始时一次性给模型的信息无论任务是什么模型都带着同样一坨东西开始思考。技能包则不一样它是一堆彼此独立的主题文件每个文件自带描述信息AI 工具会在需要的时候按需加载。举个例子。我有个技能文件叫flutter-widget-lifecycle描述是use this when writing or reviewing Flutter StatefulWidget lifecycle methods, including initState, dispose, didUpdateWidget, and when to use keys。当我让 AI 写一个有状态组件时工具会判断这个任务涉及 widget 生命周期于是把它加载进来当让我写一个纯 Dart 的命令行脚本时这个技能不会被加载因为不相关。这很像一个人面对不同工位时会自动切换到对应工具箱而不是把整个仓库的工具全背在身上。从工程角度讲这种机制最大的好处是可组合、可维护。你不需要维护一条越来越长的究极 prompt而是维护 N 个关注点单一的文件。哪个技能写得不好单独改它、测它就行。2.2 SKILL.md 文件长什么样目前主流 AI CLI 工具对 skills 的约定大同小异一个目录代表一个技能目录下有一个SKILL.md文件作为入口文件头部用 YAML frontmatter 写元信息。dart-skills 生成的技能文件遵循同一套约定形式大致如下--- name: dart-null-safety-patterns description: Use when writing or reviewing Dart code involving null handling, null-aware operators, definite assignment, and Dart 3 null safety patterns. --- # Dart Null Safety Patterns ## Core rules - All variables are non-nullable by default. Nullable types require explicit ?. - Use ?? for fallback values, ?. for safe access, ! only when you have explicitly verified non-null. - Use Dart 3 pattern matching for concise null handling: dart String getName(String? maybeName) switch (maybeName) { final name? name, null unknown, };Common mistakesUsing!on a value you havent verified, causing a runtime cast error.Overusing?? to return empty strings, hiding actual null-meaning.注意这个结构开头是给路由系统看的信息name 和 description后面是给模型看的内容。description 写得精准与否直接决定 AI 工具会不会在合适的时机加载它。 ### 2.3 模型怎么决定加载哪个技能描述匹配机制 这里有个容易误解的点skill 并不是全部塞进上下文而是由 AI 工具的 harness 层完成选择和注入。目前主流实现方式是在任务的初始阶段让模型快速浏览所有技能的名称和描述判断当前任务是否与某些技能相关相关的技能内容随后注入到上下文里不相关的就留在外面。 这意味着 description 写作质量决定了技能包的命中率。我见过很多社区里的技能包内容写得极其详实但 description 写得像论文摘要结果模型根本不知道什么时候该用。dart-skills 在这方面做了一件事——它内置了一个 validate 命令会检查每个技能文件的描述是否包含足够的行为触发词比如 Dart、Flutter、widget、isolate、null safety 等并用一组预设任务做模拟命中测试。这个设计后文会细说。 ## 3. dart-skills 1.0 的命令设计与技术选型 ### 3.1 为什么坚持用 Dart 写 Dart 工具 做 CLI 工具的语言选择有很多Go、Rust、Node 都行。但我几乎没有犹豫就选了 Dart。理由有三层 第一层是吃自己的狗粮。这个工具的存在意义就是服务 Dart 开发者如果连它自己都不是 Dart 写的说服力就大打折扣。第二层是 Dart 做 CLI 比想象中成熟。dart:io 提供了文件、路径、进程等基础能力官方 args 包对命令行参数解析支持得很好再加上 AOT 编译直接出单文件可执行文件分发体验并不比 Go 差多少。第三层是生态同构。dart-skills 的很多内部机制——比如把技能包组织成 Dart 数据结构、在测试里解析 YAML——天然能复用 Dart 生态的工具链维护成本低。 整个项目依赖面很小核心依赖只有 args、path、yaml 和 test 四个包。这符合 CLI 工具薄依赖、低心智负担的原则也方便日后交给社区维护。 ### 3.2 命令总览与典型工作流 v1.0 的命令设计遵循一个原则**每个命令对应一个明确的开发动作不搞复合命令**。下表是完整的命令清单 | 命令 | 作用 | 使用场景 | | --- | --- | --- | | dart-skills init | 在当前目录初始化技能包工程结构 | 开始梳理自己的 Dart 技能集 | | dart-skills add name | 从内置模板库添加技能模块 | 按需引入语言/框架/测试等技能 | | dart-skills list | 查看已启用的技能模块列表 | 快速了解当前技能包构成 | | dart-skills edit name | 打开指定技能模块的 SKILL.md | 按团队规范修改内容 | | dart-skills validate | 检查技能描述的触发词与格式 | 提交前自检 | | dart-skills bundle --target tool | 打包输出为指定工具可加载格式 | 安装到 Codex CLI / Claude CLI | | dart-skills update --remote | 拉取远程模板库的最新版本 | 同步生态知识更新 | 一个典型工作流是这样的。拿到一个新的 Dart/Flutter 项目先在项目根目录执行 dart-skills init它会生成一个 skills/ 目录包含以下骨架 text skills/ ├── skill.yaml # 技能包版本信息与依赖声明 ├── dart-idioms/ # 模块目录 │ └── SKILL.md ├── flutter-widget/ # 模块目录 │ └── SKILL.md └── packages/ # 可选的包选型知识 └── SKILL.md接着按项目类型决定加哪些模块。纯 Dart 后端项目加dart-idioms、async-concurrencyFlutter 项目再加flutter-widget、state-management。确认无误后执行dart-skills bundle --target codex工具会把技能包组装成 Codex CLI 期望的目录结构直接放到~/.codex/skills/下。整个过程五分钟左右。3.3 输出产物不同工具的目录适配这里有一个更关键的实现细节。不同的 AI CLI 工具对 skills 目录的约定不完全一样。Codex CLI 习惯把技能放在用户级目录~/.codex/skills/Claude CLI 更倾向于项目级目录.claude/skills/还有一些工具支持通过环境变量或配置文件指定 skills 路径。dart-skills 在bundle时不是简单复制文件而是针对目标工具做了目录适配和元信息转换# 打包到 Codex CLI 用户级目录 dart-skills bundle --target codex # 打包到当前项目的 Claude CLI 技能目录 dart-skills bundle --target claude --project这样设计的好处是开发者不需要记每个工具的目录规矩一个bundle命令全搞定。而且多个项目可以共用一套技能包避免了在仓库里复制粘贴导致的版本漂移。4. 技能包内容打磨从 Dart 特性反推知识组织4.1 语言层null safety、record 与 pattern 是重头技能包的内容不是拍脑袋写的而是从AI 最容易在 Dart 上犯错的清单反推出来的。语言层我定了四块null safety 模式、record 与 pattern、extension methods、异步与并发。null safety 这块最大的问题是模型喜欢用老式写法。很多模型看到可空类型第一反应是写if (x ! null) { use(x); }这在 Dart 里没错但完全没有发挥 Dart 3 模式匹配的优势。技能包里给了标准写法// 现代 Dart 3 风格用 pattern 同时解构和判空 final (name!: String, age) user.toTuple();还有 record 与 pattern。Dart 3 引入 record 之后很多值对象场景可以用 record pattern 清爽解决但模型如果训练数据偏旧会倾向于定义一个完整的类或者用MapString, dynamic到处传。技能包里明确写了规则two or three fields that dont need behavior? Use a record. More than that, define a class.这种决策规则对模型特别有用因为模型不缺语法知识缺的是什么时候用哪个的判断力。异步部分则专门针对过度 async的问题。我见过 AI 生成的无意义的async函数比过去三年 code review 里见的加起来还多。技能包给了一条硬规则函数体内没有await且不需要返回Future就不要标记async。就这么一句话代码质量肉眼可见地提升。4.2 框架层Flutter 生命周期与状态管理边界框架层的技能不是教模型Flutter 是什么而是划定哪些模式在 2025 年的 Flutter 项目里是常规做法。重点有三个第一个是 BuildContext 的跨异步使用。这是 Flutter 开发里最高频的运行时错误来源。技能包里明确要求异步操作后使用 context 之前必须检查context.mounted而且给出了正确示范Futurevoid loadAndShow(BuildContext context) async { final data await api.fetch(); if (!context.mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(data.toString())), ); }第二个是状态管理的选型边界。技能包没有站队说你必须用 Riverpod或你必须用 Bloc而是给出决策树组件内部状态用StatefulWidget需要跨页面共享的轻量状态用ChangeNotifierprovider复杂业务状态推荐 Riverpod 或 Bloc。这样模型在生成代码时能根据复杂度选择合适的方案而不是一律上重量级框架。第三个是 widget 拆分粒度。AI 经常一口气生成一个 500 行的 build 方法。技能包里给了build 方法超过 80 行就应该拆分 widget 或 method的建议并附带拆分样例。这个在代码评审里价值极大。4.3 生态层pub.dev 选包原则AI 生成 Dart 代码时另一个高频坑是乱选包。模型会推荐一些过时、不维护或者已被官方替代的包。技能包里专门有一个包选型模块核心原则是优先使用 Flutter SDK / Dart SDK 自带能力比如 JSON 解析先用dart:convert不要一上来就引 json_serializable同等条件下选 Flutter team 和 Dart team 维护的包网络层默认推荐dio功能完整或官方http轻量场景序列化场景明确什么时候该上freezedjson_serializable什么时候直接手写 fromJson 就够引入新包前先查pub.dev的点赞数、版本发布时间和 issue 响应情况。这个模块是动态维护成本最高的。包生态半年就会变一轮所以我在设计 CLI 时单独留了update --remote命令远程模板库里的生态选型知识保持高频更新本地技能包可以随时同步。4.4 质量层测试与性能规范最后一个模块是AI 生成代码的验收标准。技能包不是只让 AI 写代码还让它学会自检。测试部分包含单元测试、widget 测试和集成测试分别的适用场景和典型用例结构。性能部分则强调几件事build 里的集合操作尽量惰性、长列表必须用ListView.builder、图片要处理缓存和解码、耗时计算用 isolate。这一层最重要的一个技能文件叫delivery-checklist它实际上是一份AI 提交代码前的自检清单# Dart Delivery Checklist Before finishing Dart code, verify: 1. No unnecessary async markers. 2. All BuildContext uses after async gaps check mounted. 3. Public APIs have doc comments. 4. Collections use const whenever constructible. 5. No deprecated package imports. 6. Error handling distinguishes business errors from unexpected exceptions.实测下来这份 checklist 对输出质量的提升几乎是立竿见影的——因为它给模型提供了一个交付前的验证流程。5. 接入 Codex CLI 与 Claude CLI 的实操路径5.1 Codex CLI用户级技能目录先讲 Codex CLI。OpenAI 的 Codex CLI 在较新版本里支持了 skills 机制默认扫描~/.codex/skills/目录。实际操作分三步# 1. 打包并安装 dart-skills bundle --target codex # 2. 确认安装结构 ls ~/.codex/skills/ # dart-idioms flutter-widget async-concurrency delivery-checklist ... # 3. 启动 codex开始一个 Dart 任务 codex refactor this Dart file to use Dart 3 pattern matching这里有个容易踩的坑Codex CLI 对 skill 的加载有时需要配置 AGENTS.md 或项目提示文件里声明本仓库使用 Dart这类信息。如果模型一直没加载技能检查一下项目的 agent 配置里有没有写清楚语言栈。5.2 Claude CLI项目级技能目录Claude CLI / Claude Code 走的是项目级.claude/skills/目录好处是技能随仓库走团队协作时每个人拉下来代码就自动带了技能。安装命令dart-skills bundle --target claude --project这条命令会在当前项目生成.claude/skills/目录内部结构和 Codex 版略有差异——Claude 对 SKILL.md 的 frontmatter 要求更严格name必须是小写连字符格式且 description 必须包含触发场景。dart-skills 会在 bundle 时自动做这些转换不需要手动改。5.3 兼容性通用套路如果你用的不是这两个工具而是走通用 skill harness 的工具比如某些开源 CLI 代理dart-skills 也提供了通用输出模式dart-skills bundle --target generic --out-dir ./dist/skills通用格式就是纯SKILL.md文件按模块名分目录排列。拿到dist/skills/之后手动拷到目标工具指定的 skills 路径即可。作为一个通用经验任何声称支持 skills 的工具最终本质上都是读取一个包含 SKILL.md 的目录。理解这一点后适配新工具基本不需要等 CLI 支持手动复制也能解决。6. 1.0 迭代中踩过的坑6.1 技能包过长导致注意力稀释v0.9 阶段我犯过一个严重错误把 Dart 的所有最佳实践塞进一个大技能文件里SKILL.md 写了大概一万多字涵盖了语法、框架、工具链、规范。表面上很全实际测试发现模型加载这段技能后回答质量反而下降了。原因是上下文注意力是有限的长文档里真正被激活的往往只有开头部分和最后部分中间大量细节被稀释掉。后来我按关注点分离原则把一个大文件拆成七个模块每个模块控制在 1500 字以内并且每个模块只讲一件核心事。拆分后效果立刻改善。所以 v1.0 里我定了一条硬性约束单个技能模块内容超过 2000 字validate命令会告警。这个经验适用于所有写技能包的人。6.2 模型之间的行为差异第二个坑是模型差异。同一个技能包在 Codex CLI 的模型上命中率很高在 Claude CLI 上却经常视而不见。排查下来发现不是技能内容问题而是两个模型对 description 的语义理解偏好不同一个更偏好动词开头的描述另一个更偏好场景化名词描述。dart-skills 的做法是在bundle时维护了两套 description 模板分别针对 OpenAI 系和 Anthropic 系模型做优化。这不算完美方案但实测命中了率能提升两成左右。6.3 生态知识的保质期问题最后是内容更新的问题。Dart 每年有三次大版本更新节奏Flutter 更是按季度发版。技能包里关于包选型和 API 偏好的内容生命周期可能只有半年。我一开始把模板库的更新频率定成想起来才更结果有次在update之后发现旧技能推荐的一个包已经进入维护模式。后来改成模板库持续跟踪官方 changelog每个季度强制更新一次选型类模块。这类知识型工具内容维护的优先级不比代码本身低。7. 实测效果与个人体会7.1 同样一个任务加载前后的差异最后给一个直观对比。同一段需求描述用 Dart 写一个函数从 Map 中安全地取值并返回带默认值的类型化结果分别测了加载技能前后的输出。未加载技能时AI 给出的是一个泛型方法加一堆if分支代码正确但冗长加载技能后它主动用了 Dart 3 的 record patternT? typedValueT(MapString, dynamic json, String key) { return switch (json[key]) { final T value value, _ null, }; }代码更短、意图更清晰而且没有运行时空转。这就是技能包在正确时机注入正确知识的直接效果。7.2 适用边界但我也要说清楚skills 不是万能的。它解决的是模型缺少领域上下文的问题解决不了任务本身定义不清的问题。如果你的需求描述本身就含混加载再多技能也没用。另外技能包对纯语法层面的提升有限——模型本来就懂语法技能更多是纠正风格与决策层面的偏差。把这层边界想明白你就不会对技能包有不切实际的期待。7.3 后续方向v1.0 发布后我接下来想做的三件事一是支持团队协作的远端技能仓库让团队的代码规范能沉淀成技能包并按版本分发二是增加针对 Dart 后端shelf/serverpod的技能模块现在重头还在 Flutter 上三是在validate里引入一个小的评测集跑一组标准 Dart 任务看技能命中率和生成代码的静态分析评分把这个做成持续集成的一环。个人体会最深的还是那句话AI 时代的交付能力不取决于模型上限而取决于你有没有把领域知识结构化地喂给它。dart-skills 就是我这个思路的一次落地尝试哪怕你最终不用这个工具也强烈建议试试为自己的主力语言做一套技能包。花一个周末把踩过的坑整理成 SKILL.md长期看绝对是一笔高回报的投资。