Codelix 客户端三端需求开发流水线设计:从 iOS 迁移到三端统一的工程实录

背景

Codelix 最初是为后台服务设计的 AI 编码平台:给一个 TAPD 需求单,AI 自动完成需求分析、方案设计、代码生成、编译校验、Code Review 全流水线。后台场景相对规整,踩坑踩了几个月后,我们开始把这套流水线推广到 iOS、Android、Kuikly 三个客户端平台。

这篇文章记录了整个过程的技术细节,包括 iOS pipeline 从零到可用,Kuikly 接入时踩的低级 bug,Android 的广撒网问题,以及跨三端做统一优化的五轮演进。


一、iOS 流水线的设计:7 个 Agent 的协作链路

iOS 编码流水线在一个已有 AI 工具(iOSBugAutoFix)上迁移过来,核心思路是:复用 Codelix 的编排基建,只迁移 Agent 提示词。不重新实现 pipeline 引擎,不重新写 ACP 协议,只把 iOSBugAutoFix 的 agent 逻辑搬进 Codelix 的框架里。

最终链路是 7 个 Agent:

Agent 阶段 职责
ios-requirement-analyst planning 需求分析 + 代码搜索 + 影响范围定位
ios-design-interpret planning 解释设计稿 / D2C 信息
ios-feature-impact planning 评估影响范围和风险
ios-task-planner planning → coding 方案设计 + 任务拆分 + 调度 coder
ios-coder coding 编码实现
ios-validate test 编译验证 + 语义评估
ios-code-reviewer git 代码审查

阶段之间靠结构化 artifact 传递上下文,而不是把所有历史对话塞给下一个 agent。ios-task-planner 完成后调用 add_coder_dispatch MCP 工具,前端监听后自动拉起 ios-coder


二、iOS coder 的性能灾难

第一个版本跑起来后,效果非常糟糕。以「歌手主页视觉优化」需求为例:

  • 需求分析耗时:**~25 分钟**
  • 代码生成耗时:**~1 小时**
  • 代码生成费用:**~$30**
  • 实际完成进度:5 个 TODO 中只完成 1 个

对比来源系统 iOSBugAutoFix:20 分钟 / $10 / 全部完成。

根因一:路径搜索空转

优化前 ios-coder 的实际执行顺序:

  1. get_artifact(tech_design) 读取技术方案(4000+ token)
  2. 从方案中推断文件路径(不可靠)
  3. Bash(grep) / Bash(find) 搜索确认路径
  4. Read 文件内容
  5. 最终才 Edit

两次相邻 Edit 之间间隔 10 分钟(17:31 → 17:41)。Session 重启 4 次,每次都重读技术方案,累计浪费约 16,000 token。

根因二:工具白名单过宽

ios-coderAllowedTools 原来是 Bash(*),模型可以自由使用 find/grep/ls 等所有搜索命令。提示词里写了”Read 已知路径直读,不要用 find/grep 搜索”,但这是软约束,在高噪声场景下模型根本不遵守。

根因三:没有轮次收敛机制

虽然有 MaxTurns=40,但没有剩余轮次催促,没有 Edit 失败预校验,model 只有硬上限,不知道”快用完了”。

解决方案

P0-1:路径硬注入

ios-task-planner 在给 coder 派发任务时,coder_prompt 里必须包含已确认文件路径表格(绝对路径 + 方法名/selector + 行号辅助)以及每个文件的具体变更意图。coder 收到后路径已知,第一步直接 Read 目标文件,无需推断。

P0-2:路径守卫

ios-coder/system-prompt.md 中增加路径守卫:

若 prompt 中包含”已确认文件路径”表格,严禁对表格中的文件使用 Bash(find)/Bash(grep)/Bash(ls) 进行路径搜索。直接使用表格中的绝对路径调用 Read。

与 planner 侧形成双侧约束。

P1-1:工具白名单收紧

  • ios-coderBash(*)Bash(git *),从能力层物理封口搜索类命令
  • ios-requirement-analystBash(grep/find) → 原生 Grep + Glob(无 fork 开销)

P1-2:轮次收敛提示注入

ws_channel.go 里,coder 启动时注入催促:剩 5 轮停止搜索,剩 3 轮强制收敛。

实测效果

指标 优化前 优化后
两次 Edit 间隔 ~10 分钟 首轮直接 Edit
Session 重建 token 浪费 ~16,000 token 路径已注入,无需重推断
需求分析耗时 ~25 分钟 ~5 分钟

三、iOS 编译验证:5 轮假设才找到真正根因

ios-validate agent 在大型 iOS 工程(QQMusic)上反复出现漏报编译错误:本地 Xcode 能看到的错误,agent 报告里显示”目标文件 0 errors”。

整个排查经历了 5 个假设:

假设 结论
tail -80 截断了关键错误 部分成立,但改掉后仍漏报
changePlan 漏列改动文件 部分成立,但改掉后仍漏报
git diff HEAD~1 HEAD 看不到未提交改动 部分成立,三路 git 后目标文件正确了,但仍漏报
DerivedData 增量缓存跳过改动文件 部分成立,touch 目标文件后仍漏报
scheme manual target order 串行阻断 -parallelizeTargets 后仍有 database is locked

最终真正的根因(第 6 轮):

用户的 Xcode GUI 在运行,DerivedData 的 build.db 被 GUI 占用。

具体机制:

  1. 用户配置了 IDECustomDerivedDataLocation = DerivedData(工程内相对路径)
  2. Xcode GUI 编译时占用 DerivedData/build.db
  3. 命令行 xcodebuild 启动后,43 秒内就因 database is locked 退出
  4. 这 43 秒里依赖图都没建完,主 target 一行代码都没编译
  5. build log 里只有 xcframework 警告,没有任何 CompileC 记录
  6. agent 之前报告的”12 个 QMMoveView.swift 错误”是残留的旧 build log 内容

实测对比:

场景 结果
Xcode GUI 在跑 + 命令行 xcodebuild 43 秒后 database is locked,0 个 CompileC,主 target 未编译
Xcode GUI 关闭 + 命令行 xcodebuild 11 分钟完整编译,9000+ CompileC,捕获到真实错误

核心教训

当 agent 报告的错误”很奇怪”(总是同一组无关错误)时,第一步应该是直接看 build log 里有什么grep -c CompileCgrep "error:"),而不是改 prompt。

最终可靠的编译命令关键参数:

  • 不带 -quiet(会吞掉关键错误信息)
  • -parallelizeTargets(防止 manual order 串行阻断)
  • -destination 'generic/platform=iOS'(真机架构,避免模拟器差异漏报)
  • CODE_SIGNING_ALLOWED=NO(跳过签名)
  • 检测到 database is locked 立即报错,而不是继续给出误导性结论

四、Fix Mode 的设计:编译、语义、CR 三类修复

编译验证之后,需要把”发现编译错误 → 修复 → 再验证”这条链路做顺。原来用户需要:看 validate 报告 → 复制错误信息 → 切回 coder tab → 自己拼修复 prompt → 修完再回 validate 重跑。5 个手动步骤。

Fix Mode 独立 Agent

ios-coder-fixios-coder 完全拆分出来,独立 system-prompt + 独立工具白名单:

1
2
3
4
5
6
AllowedTools: []string{
"Read", "Edit", "Write",
"mcp__codelix__get_artifact",
"mcp__codelix__save_artifact",
// ❌ 无 Bash,无 get_workspace_context
},

物理移除 Bash,模型无法调用 find/ls/git log/git show

优化前的实测数据(22:30 那一轮)

时刻 工具调用 累计耗时
22:30:09 prompt 启动 0s
22:31:01 find 探文件 52s
22:32:44 又一次 find 同样文件 +103s
22:33:03 ls -la /Users/xxx/worktrees/ +19s
22:33:25 get_workspace_context +22s
22:34:08 git checkout -- 撤销改动 +43s
22:38:22 4 次连续 Edit 同一文件 +234s
22:39:02 end_turn 8m53s

三个根因:①白名单仍有 Bash(git *),模型把 find/ls 当宽松 git 子命令提交,被 auto-approve;②prompt 体积 15KB,模型忽略注入的文件内容,主动跑 git show 读历史;③同一文件多 error 连发 4 次 Edit。

优化后效果

指标 优化前 优化后 改善
总耗时 8m53s 50.1s ↓ 90.6%
首次工具调用 find 探文件 Edit 改代码
Bash 调用次数 11 次 0 次 物理切断
同文件 Edit 次数 4 次 1 次 合并

三类 Fix 统一设计(Unified Fix Mode)

后来把 Fix Mode 扩展成三类,统一放在验证 tab 底部:

fixType 触发条件 问题来源 用户控制
compile targetFileErrors test_report 自动注入,无需输入
semantic 编译通过但有 medium/high semanticRisks test_report 自动注入,无需输入
cr CR 完成,有 criticalIssues commit_reviews.critical_issues 必须经用户输入框确认

CR fix 必须经用户确认的原因:编译错误和语义缺失影响功能,必须解决。CR 问题不一定——可能是误报,可能推后处理。

CR Fix 引入了新编译错误

Unified Fix Mode 上线后,第一次真实使用 CR 快修就踩坑了:

agent 把 insertOrReplaceObjects 改为了 WCDB 属性级 UPDATE,但用错了调用对象:

  • ❌ Agent 的修复:[self.database updateTable:onProperties:withObject:where:](不存在于此版本 WCTDatabase)
  • ✅ 正确写法:先 [self.database getTableOfName:withClass:] 拿到 WCTTable *t,再 [t updateRowsOnProperties:withObject:where:]

根本原因:ios-coder-fix 的 AllowedTools 没有 Grep,在 CR/semantic fix 模式下,agent 无法查询头文件验证 API 是否存在。

修复:AllowedTools 加 Grep + Glob,同时 prompt 加规则”使用第三方框架 API 之前,先 Grep 对应头文件确认 selector 存在”。


五、Kuikly 接入:一个低级 bug 导致所有 Agent 全部被拦截

Kuikly 接入后,出现了一个诡异的现象:

  • kuikly-task-planner 多次启动,每次很快结束,没有产出 tech_design artifact,没有拆 TODO,也没有触发 coder
  • 手动点「进入代码实现」后页面无响应

查看 agent 日志,发现大量:

1
2
BLOCKED tool="mcp__codelix__mcp__codelix__get_workspace_context" kind="other" agentRole=kuikly-task-planner (not in AllowedTools)
BLOCKED tool="mcp__codelix__mcp__codelix__save_artifact" kind="other" agentRole=kuikly-requirement-analyst (not in AllowedTools)

双重前缀

根因:codelix-mcphandleToolsList 在 manifest 里把工具名注册为 mcp__codelix__<tool>(已带前缀)。Regular claude 和 tme-claude 收到这个 manifest 后,按 MCP 协议惯例再加一次 mcp__codelix__ 前缀,最终工具名变成 mcp__codelix__mcp__codelix__<tool>

ACP 服务端的 IsToolAllowedByACPPermission 拿双重前缀名去匹配 AllowedTools(单前缀),完全匹配不上,所有 codelix 工具被全部拦截。

影响范围:所有平台(backend / kuikly / iOS / Android)所有使用 codelix MCP 工具的 agent

修复:

  1. acp/manager.goIsToolAllowedByACPPermission:进入白名单检查前先归一化,检测 mcp__X__mcp__X__foo 模式并折叠为 mcp__X__foo
  2. mcp-server/main.gohandleToolsCall:循环剥前缀,兼容双重前缀调用

另外还有一个配套 bug:startCodingFromPlanning 切换到 coding tab 时,没有清除 activeRun(仍指向 task-planner 的旧 run)。coder 启动后,WS 事件被旧的 planning 视图抢走,coding tab 收不到任何输出,UI 上表现为”进不去”。修复:切 tab 之前先执行 setActiveRun(null); setActiveRunTab(null); clearSingleAgentMemory()


六、Android 需求分析:21 分钟 4 次 Compacting

Android 的「韶音二期体验优化」需求(12 条子需求):

  • 需求分析耗时:21 分钟(13:38:28 → 13:59:34)
  • 累计约 250 次工具调用

时间线:

时间 事件
13:38:28 开始
13:39:44 第 1 次 Compacting(开始后仅 1.5 分钟)
13:43:19 第 2 次 Compacting
13:50:00 第 3 次 Compacting
13:54:45 第 4 次 Compacting
13:59:34 输出完成

4 次 Compacting 合计消耗 10–14 分钟,占总耗时 50–67%。第 1 次 Compacting 在开始后 仅 1.5 分钟就触发,说明文件读入速度极快地撑满了上下文。

最严重的一个批次:单轮读取 18 个文件 + 12 次搜索

方案设计阶段更夸张:22 分钟,两轮探索合计读取 129 个文件、107 次搜索、614 次 Tool 调用——task-planner 把”方案核验”做成了全仓重新分析。


七、五轮跨三端统一优化

以上这些问题的解法,最终在 platform/05-multi-platform-analysis-design-optimization.md 做了三端统一规范,经历了五轮演进:

第一轮:收敛广撒网

核心原则:把”全面覆盖”和”全面阅读”解耦。覆盖率靠”逐条需求都处理过”保证,阅读量靠”证据分级”收敛。

硬约束:先 Grep 后 Read,单轮 Read ≤ 5 个文件,大文件只精确匹配符号,同一条需求连续 3 轮无命中则停止扩散。

收益:iOS 需求分析从 ~20 分钟降到 ~5 分钟。

副作用:约束过强时,部分需求定位不准或漏掉边缘影响面。

第二轮:为了补覆盖率,耗时再次劣化

有人为了提高定位准确性,重新强化了”每个候选文件都要读内容验证”、”发现路径立即读取”、”无法判断时扩大搜索范围”。iOS 需求分析从 ~5 分钟退回到 ~20 分钟。

教训:提示词优化有回归风险。没有工程层面的约束,只靠 prompt 软规则,容易被后来的”优化”盖过。

第三轮:三端统一 + 方案设计约束

把第一轮 iOS 经验统一推广到 Android / Kuikly,同时限制方案设计阶段:

  • 最多 2 轮补充搜索;单轮 Read ≤ 3 个文件;整个方案设计阶段累计 Read ≤ 8 个文件
  • 默认信任并消费上游 artifact,不重新做全仓目标文件定位

时间收住了,但规则没有区分”普通不确定点”和”主链路断点”。Kuikly 的 ASR 搜索需求:commonMain → native bridge → plugin/service → callback → UI 展示 这条链有没有接通才是关键,搜不到就写 risk 的规则导致主链路断点被当成普通风险,coder 继续局部实现,代码看似覆盖不少文件,但关键能力没有真正连上。

第四轮:Contract Gate

主链路必须 blocker 化,而不是 risk 化。

规则:涉及跨层能力时(bridge/native/plugin/callback/route/jump/状态链路),必须输出 contracts[],记录 entry / transport / consumer / result / ui 五个关键节点。任一节点缺失或未知 → 进入 blockingGaps[] + 阻塞 TODO,不得降级为普通 risk。

各端触发词(命中任一词即启动 Contract Gate 检查):

  • Android:Activity / Fragment / ViewModel / Repository / UseCase / Flow / StateFlow / module interface / broadcast / intent / callback / SDK / native
  • iOS:delegate / notification / block / protocol / Manager / Service / native / SDK / callback / jump / router
  • Kuikly:bridge / native / plugin / callback / route / page param / jump / locate / ASR / WVS / FC

副作用:缺口落在跨仓时(宿主 App / 独立仓 / 预编译库,当前工作区无源码),被判 blockingGaps 会连带阻塞本仓本可编码的部分;Contract Gate 也只管到”规划”,编码阶段仍有大撒网。

第五轮:把闭环延伸到编码 + 校验

问题一:编码阶段大撒网

实测某次 Kuikly 代码实现,27 分钟里前 ~14 分钟,coder 并行读了 60+ 文件、Grep 64 次。coder 提示词写了”禁止搜索”,但不起作用。

根因:ACP 模式下工具是通过运行时 request_permission + AutoApprovePermission 放行的,AllowedTools 白名单只在 CLI --print 生效,不是真正的物理门禁。

修复:HardBlockByAllowedToolsacp/manager.go)对 claude 形态 agent 拦下白名单外的工具。但原来的匹配器只认 Bash(git:*) 冒号写法,不认三端 coder 用的 Bash(git *) 空格写法,导致硬门会误杀 coder 的 git 命令。修掉这个 bug 后,硬门才真正对三端 coder 可用。

问题二:跨仓缺口过度阻塞

宿主 App / 预编译 xcframework / AAR / SDK 等,当前工作区无源码,修不了。改为 human_action TODO(coder 自动跳过、留人工),**不进 blockingGaps[]**。

问题三:校验→修复闭环断开

覆盖缺口只写进 designCoverage.missing[],但”语义修复”按钮只认 semanticRisks[]。编译通过、只差覆盖时,用户没有可点的修复入口,只能盲目”重做”,coder 拿不到缺口信息,必然复现同样结果。

实测 Kuikly ASR 需求重做两次仍缺同样 2 处 [语音] 前缀 Span。

修法:校验 prompt 统一要求,designCoverage.missing[]本仓可改的缺口,同时在 semanticRisks[] 镜像一条(fixable=true, severity=medium),让现成的”语义修复”按钮能识别并驱动 coder 修复。仅在本轮编译通过时镜像(有编译错误优先走”编译修复”),逻辑天然隔离。


八、工程保障:为什么不能只靠提示词

最重要的教训:提示词软约束在 ACP 模式下不稳定

iOS 需求分析速度从 5 分钟回归到 20 分钟,就是因为有人在提示词里加了覆盖率相关约束,把广撒网逻辑写了回去。解决方案:

  1. 预算片段放 prompt 第一屏:约束必须在 system prompt 顶部,模型读到约束前就已决定”先探索”
  2. 运行时催促ws_channel.go 在 requirement-analyst(MaxTurns 20)、task-planner(MaxTurns 15)剩余轮次不足时,注入”剩 5 轮停止搜索 / 剩 3 轮强制收敛”
  3. AllowedTools 物理切断:必须禁止的行为先从工具白名单切断,再用 prompt 双侧重复约束。Fix Mode 无 Bash 白名单是物理约束,比 prompt 写”禁止 find”强得多

前两条治分析/规划阶段,第三条治编码/修复阶段。改 prompt 治”标”,工程约束治”本”。


九、首轮试点数据

首次在 Kuikly / iOS / Android 三端完整跑通 Codelix 流水线,4 个真实需求:

需求 估算工作量 总耗时 输入 Token 总费用 完成度
Kuikly A Kuikly 1D 69 min 21,451,917 $11.69 95%
Kuikly B Kuikly 7D 97 min 16,532,406 $15.77 92%
iOS iOS 2D ~85 min 16,801,822 $11.00 100%
Android Android 2.5D ~99 min 23,118,214 $17.10 89%

数据为首轮试点、样本量小,仅作量纲参考,不代表稳定均值。

Android 阶段明细(最复杂的一单)

阶段 耗时 费用
需求分析 21 min $3.47
方案设计 22 min $4.54
代码实现 17 min $3.40
校验 + CR + 多轮修复 ~38 min $4.69

这里有个典型问题:CR 快修顺手新增了 2 个 override,但没先验证编译,导致引入了 4 个编译错误,额外多出两轮”编译错误→快修”循环。后来通过在 coder prompt 中明确禁止”未经验证的顺手新增改动”来规避。

关键观察

  1. 代码生成是 Token / 费用大头:四个样本中代码生成阶段占总费用 30%–65%,是最大优化空间
  2. 需求单质量直接影响分析成本:Kuikly A 需求单含大量代码名/模糊路径,分析阶段确认点偏多;iOS/Android 需求分析都在 20 min 量级,输入 token 很高
  3. 多轮校验/快修常态化:几个样本都经历 2–4 轮”校验→快修”,单轮成本不高但累加可观

十、几个核心设计决策

Fix Mode 必须和首次实现分开:修复轮次是机械修补,不是重新分析。两者混在同一个 agent 里会浪费 token、混淆模型决策,且无法针对 fix 场景单独收紧工具白名单。

上下文传递要主动,不要靠 Agent 重新推断ios-task-planner → ios-coder 的调度路径上,路径信息从技术方案(自然语言)变成硬注入(结构化表格),消除了”推断 → 搜索确认”这一环。这个原则后来推广到三端。

不引入 orchestrator:iOS validate/fix 闭环不使用后端 orchestrator / 状态机。当前 codelix 是”前端 tab + artifact 串联”模式,引入 orchestrator 会出现两套并行编排模型,维护成本不成比例。编译修复闭环完全可以靠结构化 artifact + 前端按钮 + prompt 模板实现。

编译结果提取交给确定性程序:LLM agent 自由执行 shell + 解析文本本质不可靠。长期方案是新增 mcp__codelix__ios_build_verify MCP tool,由 Go 后端确定性实现——LLM 只做语义判断,不做 xcodebuild 输出解析。


小结

客户端三端流水线从无到有,踩了大量坑:

  • iOS coder 路径搜索空转导致 1 小时 / $30 只完成 1/5 的任务
  • iOS validate 漏报编译错误,排查了 5 个假设才找到真正根因(Xcode GUI 占用 DerivedData)
  • Fix Mode 优化把单轮耗时从 8 分 53 秒降到 50 秒
  • Kuikly 接入时 MCP 双重前缀 bug 导致所有工具全部被拦截
  • Android 需求分析触发 4 次 Compacting,耗时 21 分钟
  • 五轮跨三端统一优化,从广撒网约束到 Contract Gate,再到编码阶段物理硬门

目前的方案在 4 个真实需求样本上跑通了端到端闭环,成本在 $11–$17 / 需求、耗时在 70–100 分钟 / 需求的量级。对于一个 1–7D 的真实客户端需求,这个量级在可接受范围内。

后续主要优化方向:首轮 coder 的 token 消耗(目前占 30%–65%)、需求分析输入 token(iOS/Android 单次 600–700 万输入)、以及把编译验证从”LLM 解析 build log”升级为 MCP tool 确定性实现。

-------------本文结束感谢您的阅读-------------

欢迎关注我的其它发布渠道