我把自己的 AI 工作流变成了代码
**2026-07-27 更新:**这篇文章发布后的第二天,BiliKit M5.0 暴露了这套工作流最严重的问题:它能把已经选择的路线做得越来越严谨,却可能一直没有检查问题本身是否值得这样解决。我保留了原文,并另外写了一篇后续:《我把 AI 工作流写成了代码,然后亲手拆掉了它》
BiliKit V1 还没做完,Swift 代码已经超过了 2.1 万行。
项目小的时候,我基本上就是给 AI 写个长一点的 prompt,再自己检查一遍 diff。就算偶尔写错了,涉及的文件也不多,很容易看出来。
但代码越来越多后,这种方法开始不太够用。
同一句“先阅读项目规范再修改”,换个会话可能就会得到完全不同的结果。有时候测试全绿,但实际上改错了模块;有时候只是想修个小问题,最后却又多出一套新的抽象。
BiliKit 还有不少单靠测试很难说明问题的地方,比如 Keychain、播放器生命周期、并发取消、弹幕渲染和本地服务器。App 能编译、单元测试能通过,不代表这些功能在签名 App 里或者实际使用时就没问题。
所以我开始往仓库里加各种规则:
AGENTS.md- 风险分级
- 质量门
- 独立审查
- 复杂度预算
- 针对不同任务的验证方式
折腾了一段时间后,这套工作流在 BiliKit 里算是基本稳定了。
然后我发现,下一个项目还得重新来一遍。
这些规则有的写在 AGENTS.md 里,有的在质量门文档和脚本里,还有一些只是我自己逐渐养成的习惯。如果换个项目,要么靠记忆重写,要么直接复制 BiliKit 的文件。
靠记忆肯定会漏。直接复制也不行。
静态网站显然不需要 BiliKit 那套播放器、Keychain、本地服务器和媒体重定向规则。反过来说,一个不到一千行但会处理密码的小工具,也不能因为代码少就随便改。
我想复用的是生成这些规则的方法。
于是我新建了一个仓库,叫 codex-engineering-skills,目前有两个 Skill:
project-governance-bootstrapapple-dev-loop
前者负责根据项目本身生成工程规则,后者负责 Apple 平台的构建、测试和验证。
不能直接复制 BiliKit 的 AGENTS.md
一开始我的想法很简单:把 BiliKit 的 AGENTS.md 抽象一下,再做成一个通用模板。
实际写起来发现不太行。
BiliKit 会把认证、重定向、本地服务器、播放链路、并发、渲染器和破坏性迁移列为高风险区域。这些分类放在 BiliKit 里没什么问题,因为它确实可能在这些地方翻车。
但如果把同一份列表放进静态网站里,就有点形式主义了。更离谱一点,如果一个命令行解析器也被要求运行签名 App 验证 Keychain,那这套流程基本上已经失去意义。
因此 project-governance-bootstrap 的第一步是先读项目:
- 仓库里原本的说明
- 架构决策
- manifest 和依赖
- 测试
- CI
- 安全边界
- 发布方式
之后再判断这个项目大概需要什么程度的治理。
目前分成三档:
light:规模小、修改容易回滚,而且主要只有一种验证方式;standard:包含多个模块、公共边界、持久化、CI 或多层测试;critical:涉及凭据、权限、破坏性迁移、不可信输入、本地服务器、媒体生命周期或者生产基础设施。
代码量只能作为参考。十万行生成代码不一定危险,但几十行删除数据库的迁移代码肯定不能随便处理。
最后生成的仍然是每个项目自己的规则。Skill 负责的是中间的判断过程。
比起模板,它更像一个编译器
我后来觉得,project-governance-bootstrap 有点像一个小型编译器。
输入大概是:
代码+ manifest+ 架构文档+ 测试+ CI+ 安全边界+ 发布规则输出则是:
项目事实的优先级+ 架构边界+ 风险区域+ 验证入口+ 授权限制+ 可选的审查角色仓库里当然还是有模板,但它只能作为起点。项目里没有对应内容的部分应该直接删掉,而不是想办法填满。
通用模板很容易出现这个问题:只要小节已经写在那里,人和 AI 就不太愿意删。最后生成一份什么都有、看起来很完整,但实际上没人会认真执行的文档。
如果项目已经有现成的测试入口,就没必要为了统一格式再造一个 gate 脚本。项目没有需要特别处理的安全边界,也不用硬写几段放在哪里都正确的安全规范。
够用就行。
为什么拆成两个 Skills
BiliKit 原来的工作流里,其实混着两个问题:
- 这个仓库要求什么?
- 这次 Apple 平台的修改要怎么验证?
因为 BiliKit 本身就是 Apple 平台项目,所以它们经常一起出现。不过做成通用 Skill 后,我还是把两部分拆开了。
project-governance-bootstrap 只负责项目规则。它可以判断某个项目需要签名 App、UI 测试或者性能记录,但不会把所有 Xcode 操作都塞进去。
apple-dev-loop 负责实际验证。它知道什么时候该用 SwiftPM、xcodebuild、.xcresult、XCUI、签名 App、Computer Use 或者 Instruments,但不会替项目决定架构和风险分类。
两个 Skill 可以配合使用,但彼此不依赖。
这样前者也能用于 Rust、TypeScript 或者文档项目;后者也能直接用于一个已经有完善工程规范的 Swift 项目。
经常一起用,不代表一定要塞在同一个东西里。
Skill 什么时候不该启动
做 Skill 后,我才意识到触发条件也得认真写。
如果 apple-dev-loop 看到 Swift 就启动,那么用户只是问一句语法,AI 可能就开始检查 Xcode、找 scheme、跑构建。流程确实很严格,但显然没什么必要。
所以除了“什么时候使用”,还要写清楚“什么时候不要使用”。
只有任务真的需要 Apple 工具链、签名、设备、UI 或性能验证时,才应该启动完整流程。单纯解释一段 Swift,或者修改一个不需要运行环境的小 package,用不到这些东西。
project-governance-bootstrap 也一样。不能因为仓库里刚好有个 AGENTS.md,就决定重新设计整个项目治理。
以前我会觉得,多给 AI 一点说明总没坏处。实际做下来发现,多出来的内容不只是占上下文,它还会影响工具选择。
比如一个普通的 package test,如果提前把 Instruments 的完整说明塞进去,AI 反而更有可能觉得性能分析也是一个可选方案。
手里多一把锤子,确实更容易到处找钉子。
不要把所有内容放进一个 SKILL.md
最初版本差点变成两个超大的 SKILL.md。
治理 Skill 里包含全部风险分类、模板、Agent 角色和平台例外;Apple Skill 里则放下所有 Xcode、XCTest、签名、模拟器、UI 和 Instruments 的操作方法。
真这么做的话,每次使用都得把一堆当前任务根本用不到的内容加载进来。
现在的做法是只保留核心流程,需要什么再读什么。
项目不是 Apple 平台,就不读取 Apple 相关规则;不需要独立审查角色,就不读取 Agent routing;只是跑 package test,也不会提前加载 Instruments 的使用方法。
一般把这个叫 progressive disclosure。我觉得也可以理解成给上下文做依赖管理。
上下文里的内容会互相竞争。某种工具写得越详细,它看上去就越值得使用,即使当前任务根本不需要。
所以这里也没必要贪多。
Apple 平台要验证到哪一步
apple-dev-loop 里面有一个证据阶梯:
- 阅读源码和静态约束
- SwiftPM、单元测试和集成测试
xcodebuild和.xcresult- Xcode 内的诊断和操作
- XCUI
- 签名 App 和实际 UI 操作
xctrace或 Instruments- CI、真机矩阵和独立审查
不是每次都要从第一层跑到最后一层。当前结论在哪一层已经能被证明,就停在哪里。
比如截图不能代替单元测试,unsigned build 不能证明 Keychain 能正常访问,App 成功启动也不能证明对象生命周期没有问题。
反过来,如果只是修改一个 Swift Package,也没必要为了显得验证充分,专门启动 Xcode 再录一遍 Instruments。
不同工具能证明的东西不一样。
Skill 里还有一些辅助脚本。比如在运行比较贵的任务前,先记录当前仓库、workspace、scheme、Xcode 和 Developer Directory;或者直接读取 .xcresult,而不是让 AI 对着几千行终端输出判断测试到底过没过。
我没有打算用 shell 再造一个 Xcode。脚本只负责那些稳定、明确,而且自动化后确实能减少歧义的部分。
安装脚本也可能把事情弄坏
仓库里有个脚本,可以通过符号链接把 Skill 安装到 Codex 能发现的位置。
如果目标位置已经是正确链接,就什么都不做;如果那里是另一个链接或者真实目录,脚本会拒绝覆盖。
本来只是一个很普通的安装脚本,但这里如果偷懒,确实可能把用户原来装好的内容直接替换掉。
类似的情况还有为了让一次构建通过,直接修改全局 xcode-select。眼前的问题可能解决了,之后其他项目用哪个 Xcode 就不好说了。
所以这两个 Skill 会尽量使用当前任务内的配置。遇到含糊状态就停下来,可选能力缺失也不会直接算成任务失败。
该问的时候还是得问。
写成 Skill 后,原来的很多规则都不够具体
以前我会写:
重要修改需要独立审查。
放在 BiliKit 里,大概知道是什么意思。但做成通用 Skill 后,问题马上就来了:
- 什么叫重要修改?
- 改了多个文件就算重要吗?
- 审查者应该看到多少实现过程?
- 两个审查结论不一致怎么办?
- 什么时候审查是在提高质量,什么时候只是在增加流程?
最后只能继续缩小范围。
绿色任务不用机械地走审查流程。黄色任务如果不是简单的机械修改,可以增加一次只读审查。红色任务则重点检查失败路径、取消、所有权、安全、清理和回滚,同时还要限制复杂度,免得流程越堆越多。
其他规则也是一样。
“运行完整测试”不够准确,因为最高级的确定性验证可能已经包含下面所有模式,没必要换几个命令重复跑。
“使用真机验证”也不够准确。只有本地和确定性测试证明不了当前结论时,真机才有必要。
“使用 Xcode MCP”还得先确认连接的是哪个 Xcode、哪个窗口、哪个 workspace 和 scheme。不然工具本身能正常执行,也可能在错误的项目里执行。
以前依赖上下文就能理解的话,做成通用 Skill 后都得补上适用条件。
不补的话,下次大概率就会被用错。
Skill 本身也要检查
Skills 仓库有自己的 validator,目前会检查:
- metadata
- 内部链接
- 未替换的占位符
- shell 语法
- 空白和格式问题
安装脚本则单独处理目标位置冲突。
不过这些检查只能证明文件结构没有坏。
一个 Skill 完全可以 metadata 正确、链接有效、shell 也没有语法错误,但用起来还是很离谱。比如触发范围太广、生成的规则太重、漏掉项目原本的约束,或者推荐了一种根本不能证明当前结论的验证方式。
所以后面还是得拿其他项目试。
project-governance-bootstrap 可以分别找一个小工具、一个普通的多模块应用,再找一个真的涉及安全或者生命周期风险的项目。
apple-dev-loop 则要看它能不能在合适的位置停下来:package 问题跑 package test,Xcode 项目问题使用 xcodebuild,Keychain 问题运行签名 App,性能问题再开 Instruments。
如果最后生成的还是换了项目名的 BiliKit 规则,那就回去继续改。