普通视图

发现新文章,点击刷新页面。
昨天以前微言 | wyanassert

iOS AutoFix Agent 阶段性收尾:可迁移的 Agent 工程经验沉淀

作者 wyanassert
2026年5月29日 20:00

前言


从最早思考《为什么要做一个 iOS bug 自动修复的 agent 程序》,到 V3 把单文件原型重构成 AgentEngine 引擎、V4 把领域知识结构化、V5 把「完成需求开发」提成唯一 P0 并引入 Pipeline 编排基座,这个项目断断续续做了两个月。之前我写过一篇《从 Bug 修复到需求开发:iOS AutoFix Agent 的 V3-V5 演进之路》,把版本演进的脉络讲清楚了。

接下来我的重心会转到和其他同学共建另一个 Agent 项目上。所以趁记忆还热乎,给 iOS AutoFix 做一份阶段性收尾——这篇不再复述版本演进,而是把这一年踩过坑、验证过的与领域无关、可以直接搬到下一个 Agent 项目的工程经验沉淀下来。

一句话定位整个项目的终态:

V4 解决「能不能定位/修复一个 Bug」,V5 把「完成一个需求开发」提升为唯一 P0,并引入 Pipeline Orchestrator(流水线编排基座),让 分析 → 设计 → 闸门 → 实现 → 验证 → 评审 以声明式、可配置、支持闸门与智能回退的流水线串起来。

V5 走到了哪里


V5 围绕三条主线展开:

主线 名称 角色
主线 0 Pipeline Orchestrator 架构基座:声明式流水线引擎 + 闸门(Gate)+ 智能回退(Rollback)+ 状态持久化 + 可观测性
主线 A 定位与修复质量 继续打磨 Bug 定位/修复/经验复用,在 V5 中承担「基础设施 + 壁垒」角色
主线 B 需求开发能力 从需求分析、改动点识别、多文件写入到限定场景的完整需求实现 —— V5 唯一 P0

落地形态是一条六阶段需求开发流水线,以一个真实 TAPD 需求为例,全程约 65 分钟、中间两次人工确认:

1
2
① 需求分析 → ② 方案设计 [HumanGate] → ③ 影响评估
→ ④ 代码实现 [事务写入 + 自动回滚] → ⑤ 编译验证 → ⑥ 代码审查

到 V6 雏形,需求开发已经从「勾哪端就各自独立跑 Pipeline」演进到「一个需求、一次整体分析、按涉及端分别落地」——iOS / Android / Kuikly 三端共享一次跨端分析,再并发分端实现,保证跨端协议字段/事件名一致。

当前的分层架构


收尾时整个系统稳定在 5 层。理解这张图,就理解了这个 Agent 的全部骨架:

1
2
3
4
5
6
7
8
9
入口层(CLI / GUI / 企微机器人)

Pipeline Orchestrator —— 按什么顺序跑、卡在哪个闸门、错了怎么退

AgentEngine —— 单个 Agent 怎么跑:循环控制 / 工具分发 / Scratchpad / 对话压缩 / 置信度退出

TaskProfile —— 这一步跑什么:BugLocate / Fix / FeatureAnalyze / FeatureDesign / FeatureImplement / CodeReview / ImpactAnalysis

知识 & RAG 层 —— ModuleDoc + CaseDoc + FixRecord,7 路并行召回 + Knot 知识库

最关键的一组抽象,也是这一年最值钱的设计决策:

  • Engine 管「怎么跑」,Profile 管「跑什么」。通用的 Agentic Loop(循环、工具分发、压缩、容错)沉到 Engine,场景差异(角色、工具集、退出条件、领域知识)放进 Profile。十几种 Profile 复用同一个 Engine,新增一种任务类型只要写一个新 Profile。
  • Pipeline 管「编排」。Engine 只关心单个 Agent 跑完一轮,Pipeline 负责把多个 Stage 串起来,并在阶段转换处插闸门。

可迁移的工程经验


下面这些是我打算带去下一个 Agent 项目的「行李」。它们大多与「修 iOS Bug」这件具体的事无关,是做任何 LLM Agent 都会遇到的问题。

1. 先有编排框架,再往里插能力

最容易犯的错是:先把一个个能力(分析、设计、实现)写成独立脚本,最后再用胶水代码串起来。结果就是回退、重试、断点续跑这些横切逻辑散落在各处。

V5 的做法是反过来——先建 Pipeline 编排基座,每个能力作为一个 Stage 插进去。回退、状态持久化、崩溃恢复、可观测性都由编排层统一提供。Pipeline First 是 V5 九条核心原则里的第一条,事后看这个顺序定对了。

2. 闸门是一等公民 + 智能回退

关键阶段转换处一定要设闸门(Gate),不通过则携带反馈智能回退,而不是直接失败。闸门分三类:

  • MetricGate:置信度 / 编译结果等硬指标
  • AIGate:用一次独立的 LLM 调用评估上一阶段产物质量
  • HumanGate:人工确认(GUI 里做成倒计时确认弹窗)

回退用的是循环模型而非递归:每个 Gate 维护独立的回退计数,避免「设计→实现→验证失败→回设计→又失败」无限套娃。状态用原子写入持久化,进程崩溃能恢复到中断处。

3. 上下文工程:三个反直觉的结论

这是 Agent 工程里水最深的部分,几条经验都和直觉相反:

  • 超长 system prompt 会「中段失忆」(Lost in the Middle)。主 Orchestrator 的 prompt 一度超过 350 行,模型对中间部分的遵循率明显下降。解法是分阶段注入:探索阶段只给角色+工具+方向,搜索完才注入评估规则和退出条件,准备提交时才注入「自我质疑」清单。瘦身后 exploration prompt 减了约 70%。
  • 压缩会丢掉关键证据。每 5 轮压缩一次对话,会把搜索过程中发现的文件路径、行号、调用链一起压没,导致 Agent 重复搜已经找过的文件。解法是给 Agent 配一个不会被压缩的小本本(Scratchpad):通过 note_finding 工具写入关键发现,标记 _isScratchpad,压缩时跳过,每轮作为 system message 重新注入。
  • 结构化数据 > 报告文本。子代理给主代理传「文本报告」会有信息损失且容易被误解,改成结构化的 keyFiles / codeSnippets / callChains / hypotheses / coverage 后,主代理可以直接引用具体发现。

4. 置信度驱动早停,而不是机械计时器

最初的轮次控制是「第 8 轮提醒、第 12 轮强制提交」这种机械计时器——Agent 可能第 3 轮就找到答案还在空转烧 token,也可能第 12 轮没找到被强行提交。

改成置信度驱动早停:每轮自评置信度,但触发早停要三个条件同时满足——置信度 ≥ 0.8、总发现 ≥ 3、剩余方向全为低优先级。任一不满足就继续,宁可多跑也不「差点找到」漏掉根因。强制提交时打 low_confidence 标记,让下游知道定位可能不准。

5. 工具使用纪律:双层约束

让 Agent 别乱用工具,单靠 prompt 不够。有效的是双层约束协同

  • 工具 description 层(「什么时候该用」):写前置条件「使用前先 ripgrep 确认目标位置」、范围指引「命中行 ±20 行」、红线「禁止无目标读取 >100 行」。
  • Prompt 层(「什么不能做」):明确列反模式——没 grep 就直接读大段、对低相关匹配反复扩大读取范围、重复读已读过的区域。

两层叠加后,ripgrep 调用从 7 次降到 4 次(**-43%**),平均每次工具调用数从 7 降到 5。

6. 写入安全是红线

只要 Agent 会改用户的代码,写入安全就不能妥协。V5 的红线是四件套:**--confirm 确认 + 事务写入 + 编译验证 + 自动回滚**。代码实现阶段所有写入打包成事务,编译不过就整体回滚,绝不留下半成品。这条在「修 Bug」时还能商量,到「需求开发」要改多文件时就是底线。

7. 改动的「精准性」比「成功率」更重要

修 Bug 最大的风险不是没修好,而是改出新 Bug 或改错位置。让 LLM 生成 diff 时,强制 search 块带上前后 1-2 行上下文确保唯一匹配,而不是只给变更行(LLM 从记忆重建代码会有细微偏差,导致静默匹配失败)。

就这一个约束 + 「最小修改原则」,让修复阶段 Token 从 60K 降到 26K(**-57%**),LLM 调用从 6 次降到 3 次。

8. 可观测性要覆盖事前/事中/事后

  • 事前:靠工具 description 和 prompt 预防低效行为;
  • 事中:检测工具使用比例(read_file / ripgrep > 2:1 报警)、重复搜索同一文件;
  • 事后earlyTerminationSnapshot 记录早停时跳过了哪些方向,配合 evidenceHitRate / searchEfficiencyRatio / evidenceConsistencyScore 等场景级指标,做基线保存和回归检测。

一组综合优化前后的实测对比(质量指标全部持平的前提下):

指标 优化前 优化后 改善
总 Token 107,951 67,098 -37.9%
总耗时 272.9s 191.0s -30.0%
LLM 调用 18 13 -27.8%
主循环收敛轮次 3 2 -33.3%

9. 多模型评测与降级链

不要押注单一模型。V5 建了一套 10 分制的多模型评测框架(评估维度:文件定位 25% / 分析深度 25% / 实现计划 20% / 风险识别 15% / 执行效率 15%),跑同一个需求对比:

配置 综合得分 特点
claude-opus + 内置引擎 8.8 覆盖最广,风险识别最深
deepseek-4 + ACP 8.5 行号级精度,零幻觉
claude-4.6 + claude-internal 8.2 流程最稳,但会跑偏去搜 Android 代码(项目类型上下文注入不足)
glm-5 + CodeBuddy SDK 6.7 最快(105s),质量尚可

线上则用降级链(CodeBuddy Claude → GLM → DeepSeek)保可用性,并对不支持 function calling 的模型做 JSON 解析降级。评测决定选型,降级链保底

10. 知识沉淀要分类,并设晋升路径

知识库不是把文档堆一起。V5 把知识分四类——案例 / 经验 / 记忆 / 索引,共享知识库走独立 git 仓、个人记忆落本地,每条知识带 confidence 和晋升路径(被多次验证的经验才升级为共享知识),并设入库准入条件和触发器。这样知识库才不会越长越脏。

诚实的局限:为什么在这个点暂停


核心闭环已经成形,剩下的是打磨,所以这是个合理的暂停点。但有几处确实没做完,留个记录:

  • 跨端 analyze 还没真正复用(Phase B):V6 目前是把跨端整体方案作为 additionalContext 注入各端,各端仍会冗余跑一遍自己的 analyze,时间有浪费。彻底的做法是用 preloadedAnalysis 直接跳过各端 analyze stage,但那会侵入 PipelineEngine 内部,风险大,所以先用了轻耦合方案。
  • 粗筛权重是手工拍的:5 策略加权(直接路径 100 / SQLite 索引 10-40 / 目录推断 8 / Git 热点 5 / Bug 类型专项 12-15)很难对所有项目通用。正解是用历史定位数据做权重调优,或者干脆让 LLM 直接做粗筛——现在的模型能力够用了。
  • 并发统计口径会串:多端并发时 token / 日志统计可能互相累加,不影响功能,但口径乱。
  • 旧入口还没收尾:三个分端独立 Tab 仍可见,等新的统一入口稳定后再隐藏。

下一站:共建另一个 Agent


回头看,这一年真正沉淀下来的,不是「怎么修 iOS Bug」,而是「怎么造一个能干活的 Agent」。这两件事可迁移的程度完全不同:

  • 可以直接搬过去的(与领域无关):Pipeline 编排 + 闸门 + 智能回退、Engine/Profile 解耦、上下文工程那三条、置信度早停、写入安全四件套、事前/事中/事后可观测性、多模型评测与降级、知识分类沉淀。
  • 必须重做的(iOS 特化):仓库索引与页面映射表、编译验证(xcodebuild / gradle / KMP)、各端的 Profile 与领域知识。

下一个项目是和其他同学一起共建,正好可以把上面这套「与领域无关的 Agent 骨架」当成共识的起点,少走一遍弯路。iOS AutoFix 这边先告一段落,等下一个 Agent 跑起来,应该还有新的东西可以反哺回来。

总结


如果只能带走三句话:

  1. 先有编排,再插能力——横切逻辑(回退/续跑/可观测)必须沉到编排层。
  2. Engine 管怎么跑,Profile 管跑什么——这组解耦让一套引擎服务十几种任务。
  3. 上下文工程 + 写入安全是 Agent 能不能用的两道生死线——前者决定它聪不聪明,后者决定你敢不敢让它动你的代码。

从 Bug 修复到需求开发:iOS AutoFix Agent 的 V3-V5 演进之路

作者 wyanassert
2026年5月24日 22:03

从 Bug 修复到需求开发:iOS AutoFix Agent 的 V3-V5 演进之路

上一篇文章里,我解释了为什么选择自建 Agent 架构,而不是做 IDE 插件——核心在于要拥有”流程控制权”,而不是”平台适配权”。

那篇文章写完后,系统先后经历了 V3、V4、V5 三个大版本的迭代。我打算在这篇里把这段演进过程讲清楚:每一版做了什么、为什么这么做、踩了什么坑、优化思路是什么。


一、V3:从单文件到引擎架构

问题起点

V2 的核心问题是:代码全部堆在一个 1100+ 行的 PreciseLocator.ts 文件里。prompt 编排、工具分发、Agent 循环、评估逻辑全部混在一起,新增任何功能都要触碰这个巨型文件。更重要的是,V2 在复杂 Bug 上能力不足——8 轮搜索子代理、每轮只有 2-3 个工具调用,对于需要追踪多层调用链的 Bug,搜索深度明显不够。

架构重构:AgentEngine + TaskProfile

V3 的首要任务是拆文件,但不是机械地拆,而是找到正确的分层方式:

1
2
3
4
5
6
7
8
9
10
AgentEngine(通用执行核心)
├── 循环控制
├── 工具分发
├── 对话压缩
└── 容错与重试

TaskProfile(场景差异层)
├── BugLocateProfile
├── CodeLocateProfile
└── CodeAnalysisProfile

Engine 负责”怎么跑”,Profile 负责”跑什么”。新增任务类型只需实现新 Profile,不用改 Engine。这个模式后来证明极其有价值——V4、V5 的十几种 Profile 都复用了同一个 Engine。

子代理能力强化

架构拆完之后,重点放在增强子代理的搜索能力:

维度 V2 V3 变化
搜索轮次 8 轮 12 轮 +50%
每轮工具调用 2-3 个 5-9 个并行 +200%
工具执行方式 串行 for 限流并发(limit=4) token -37%
搜索工具 ripgrep + read_file + find_files 增加文件名维度

其中最有意思的是强制提交轮的上下文压缩。V2 时,子代理在强制提交轮(第 14-15 轮)拿到的是完整的历史消息,约 100K tokens。LLM 面对这么大的上下文,实际有效处理率只有 33%。V3 把强制提交轮的 context 压缩为约 15K 的结构化摘要,有效率直接从 33% 拉到 100%。

渐进式催促机制

V2 只有两段式控制:第 8 轮”给你个激励”,第 14-15 轮”强制提交”。V3 换成了 4 级渐进催促:

1
2
3
4
5
Round 1-7:   自由搜索
Round 8: 💡 中途评估,收敛方向
Round 9: ⚠️ 剩余轮次不多
Round 10: 🚨 最后搜索机会
Round 11-12: 🔒 移除搜索工具,仅保留 submit_result

从用户体验角度看,这很像给实习生分配任务时的跟进节奏——不是一上来就催,而是在关键节点给出提示,让他有机会自我调整。

性能对比

指标 V2 V3
总耗时 ~283s ~163s(**-42%**)
LLM 调用次数 ~22 次 ~17 次(**-23%**)
Token 消耗 ~125K ~95K(**-24%**)

二、V4:领域知识沉淀与可观测性

V4 是个大版本,做的事情很多,但有一条清晰的主线:**从”能定位”到”知道自己在做什么”**。

引擎内核的四个关键优化

基于对 V3 的深度审查(是的,我用 LLM 跑了一次完整的 Code Review),发现了四个核心问题,V4.3 全部解决:

1. 分阶段 Prompt 注入

V3 的主 Orchestrator 有 350+ 行系统 prompt,角色定义、工具说明、bug 类型分类、退出条件全部揉在一起。LLM 在超长 prompt 中有著名的”Lost in the Middle”问题——中间部分指令遵循率明显下降。

解决方案是把 prompt 按任务阶段动态注入:

  • 探索阶段:只给角色 + 工具 + bug 类型
  • 分析阶段:注入评估规则
  • 提交阶段:注入三问法和退出条件

2. Scratchpad 结构化草稿板

V3 每 5 轮压缩一次对话,问题是关键的文件路径、行号、调用链会在压缩中丢失,Agent 可能在后续轮次重复搜索已经找过的文件。

V4 引入了 Scratchpad:Agent 可以通过 note_finding 工具主动记录关键发现,这些记录被标记为 _isScratchpad: true,压缩时跳过,每轮注入为 system message。等于给 Agent 配了一个不会被压缩的小本本。

3. 置信度驱动退出

把”第 12 轮强制停”改为”置信度 ≥ 0.85 时可提前停”。不只是节省 token,更重要的是让 Agent 的退出决策与任务完成质量真正绑定,而不是靠”计时器”。

4. 结构化搜索结果传递

V3 的子代理提交一份文本报告,主 Agent 从文本里提取发现——有信息损失,且可能误解。V4 改为结构化数据传递:

1
2
3
4
5
interface StructuredSearchResult {
keyFiles: Array<{ path: string; relevance: number; summary: string }>;
findings: Array<{ file: string; lines: [number, number]; content: string }>;
hypotheses: Array<{ description: string; confidence: number; evidence: string[] }>;
}

BugTypePolicy:领域知识结构化

V3 有 11 种 bug 类型的支持,但相关的搜索策略、分析维度、退出条件都硬编码在一个巨大的 switch-case 里。V4 把它重构为 BugTypePolicy 注册表——每种 bug 类型有独立的策略文件,PolicyRegistry 负责查找和组合。

这解决了一个本质问题:领域知识和执行逻辑分离。现在增加一种新的 bug 类型,只需要写一个策略文件,不用改 Agent 循环代码。

RAG 知识库

V4 引入了两层知识系统:

  • ModuleDoc:模块级别的架构文档,通过 DocGen 命令自动生成
  • CaseDoc / FixRecord:历史成功修复案例,每次 fix 成功后自动入库

RAG 检索有 7 条并行路径(全文检索 / 路径匹配 / 符号检索 / 依赖关系 / 案例模块 / 历史修复记录 / Knot 知识库),每条路径独立计算命中率,可以在 Metrics 里看到哪条路径最有价值。

Metrics 可观测性体系

V4 构建了完整的可观测性体系:

  • 搜索质量指标:工具调用分布、计划命中率、方向利用率、证据一致性分数
  • 成本效率指标:每结果 Token 消耗、按模型估算 USD 花费
  • 历史聚合报告npm run metrics,按命令/日期汇总,sparklines 趋势可视化
  • 基线评测--save-baseline 保存基线,--check-baseline 回归检测

这套体系解决了一个关键问题:不再靠”感觉”来判断系统有没有在变好。每次改动都能用数据说话。

多架构 TrackingModel

iOS 项目有 MVC、MVVM、VIPER 等多种架构,每种架构的数据流层次不同,定位路径也不同。V4 把追踪模型做成了可配置的 ArchitectureProfile

1
2
3
MVC_CGI:    UI → 数据装配 → Model → CGI 层
MVVM_CGI: UI → ViewModel → Model → CGI 层
VIPER: View → Presenter → Interactor → Entity

autofix.config.json 里指定 projectArchitecture,系统自动选用对应的追踪策略。


三、V5:从 Bug 修复到需求开发

V5 是一次战略转向。V4 解决了”能不能修 Bug”的问题;V5 的目标是:能不能让系统完成一个完整的 iOS 需求开发任务

Pipeline Orchestrator:声明式流水线

V5 引入了 Pipeline Orchestrator 作为核心架构基座。这是把”一组命令串起来执行”升级为”声明式流水线编排”的关键一步。

一条 Pipeline 长这样:

1
[需求分析] → Gate → [方案设计] → Gate → [影响评估] → [代码实现] → [编译验证] → [代码审查]

每个 Gate 可以是三种类型之一:

  • MetricGate:置信度阈值、编译结果等指标判断
  • AIGate:LLM 评估方案可行性或代码质量
  • HumanGate:人工确认(复用 acknowledge 机制,不打断 terminal)

Gate 失败时,系统不是直接报错,而是带着失败反馈回退到指定阶段重新执行。回退上限是双层计数(每个 Gate 独立计数 + 全局总计数),防止死循环。

一个设计细节:纯 Nullability 编译错误(Swift 的 optional 相关警告)不消耗有效 retry 配额。这是因为这类错误往往是模型生成代码时的习惯问题,让它多试几次通常能自己修好,不应该把宝贵的 retry 机会浪费在这上面。

需求开发流水线实战

以一个真实案例说明整个流程:【歌手主页】歌手写真切换交互+基础体验优化(TAPD 需求)。

流水线六个阶段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
① Analyze(需求分析)
→ 输出: 核心文件列表 + 风险点 + 参考实现

② Design(方案设计)
→ 输出: 每个文件的改动路径 + 数据流梳理

③ Impact(影响评估)
→ 输出: 关联模块、上下游影响面

④ Implement(代码实现)
→ 事务写入,编译失败自动回滚重试

⑤ Validate(编译验证)
→ xcodebuild 静态编译 + 语义覆盖率校验

⑥ Review(代码审查)
→ CodeReviewProfile 驱动,critical/warning 级别问题可打回 Implement

全程大约 1 小时 5 分钟,中间两次人工确认(方案设计后、代码实现前),其余步骤全部自动执行。

多模型评测框架

V5 做了一件有意思的事:用同一个需求,分别跑 5 种不同的模型 + 接入方式组合,用 Claude Sonnet 4.6 进行 10 分制精评,验证不同配置下的效果差异。

评估维度:文件定位准确性(25%)、分析深度(25%)、实现计划质量(20%)、风险识别(15%)、执行效率(15%)。

结论很有意思:

配置 综合得分 特点
claude-opus + 内置引擎 8.8 覆盖最广、风险识别最深
deepseek-4 + ACP 8.5 行号级精度、零幻觉
claude-4.6 + claude-internal 8.2 流程最稳定,但效率问题(搜索了大量 Android/Kotlin 代码)
glm-5 + CodeBuddy SDK 6.7 最快(105s),质量尚可

V2 效率垫底的根因很清楚:项目类型上下文注入不足——LLM 不知道这是 iOS/ObjC 项目,搜索方向跑偏了。这类问题很容易修,但靠人工 review 日志才能发现。有了系统化的评测框架,这类问题能够被量化追踪。


四、优化思路总结

经历三个版本的迭代,我对 Agent 系统优化形成了几个比较稳定的判断:

1. 双层约束优于单层

改 LLM 行为有两个层面:工具 description 和 Prompt。只改其中一层,效果有限。

以工具使用纪律为例,V5 在工具的 description 里加了明确的使用边界(”使用前先 ripgrep 确认位置,禁止无目标读取 >100 行”),同时在 Prompt 里加了反模式禁止列表。两层协同,ripgrep 调用减少了 43%。

工具层侧重”什么时候该用”,Prompt 层侧重”什么不能做”,互相补充。

2. 保守设计比激进触发安全

以早停机制为例,V5 的场景 B(高质量证据早停)有三个条件必须同时满足:

  1. 至少有一个方向置信度 ≥ 0.8
  2. 总发现数 ≥ 3
  3. 剩余方向全为低优先级

任何一个条件单独满足,都触发早停。这个保守设计多次避免了因为”差点找到了”而漏掉根因的情况。

3. 事前 + 事中 + 事后的可观测性层次

优化 Agent 有三个时机:

  • 事前:通过工具 description 和 Prompt 预防低效行为
  • 事中:检测工具使用比例(如 read_file / ripgrep 比 > 2:1 报警)
  • 事后:早停快照记录跳过了哪些方向,用于效果回溯

这个分层体系比单纯看”成功率”有价值得多,因为它告诉你为什么成功或失败

4. 修复精准性比修复成功率更值得关注

V5 优化前,修复阶段 LLM 调用 6 次,Token 60K+,核心原因是搜索块(search block)匹配到了多个位置,修错了地方,然后重试。解决方案很简单:要求 search 块必须包含前后 1-2 行上下文,确保唯一匹配。

改动 10 行代码,修复阶段 Token 下降 57%,LLM 调用从 6 次降到 3 次。

真正的成本不是模型调用费,而是重试浪费的时间。 把匹配精准度做好,比提升模型能力更有效率。


五、当前架构全景

经过 V3-V5 的演进,系统的层次结构已经比较清晰:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
┌──────────────────────────────────────┐
│ 入口层 │
│ CLI / GUI 桌面端 / 企微机器人 │
│ ↓ Intent Router │
├──────────────────────────────────────┤
│ Pipeline Orchestrator │
│ 声明式流水线 + Gate + 智能回滚 │
│ Bug 修复流水线 / 需求开发流水线 │
│ ↓ │
├──────────────────────────────────────┤
│ AgentEngine 通用引擎 │
│ 循环控制 / 工具分发 / Scratchpad │
│ 对话压缩 / 置信度退出 │
│ ↓ │
├──────────────────────────────────────┤
│ TaskProfile 策略层 │
│ BugLocate / Fix / FeatureAnalyze │
│ FeatureDesign / FeatureImplement │
│ CodeReview / ImpactAnalysis │
│ ↓ │
├──────────────────────────────────────┤
│ 知识 & RAG 层 │
│ ModuleDoc + CaseDoc + FixRecord │
│ 7 路并行召回 + Knot 知识库 │
└──────────────────────────────────────┘

入口层做薄,Pipeline 层负责编排,Engine 层通用复用,Profile 层封装差异,知识层持续沉淀。


六、下一步

还有几件事没做完:

1. 并行搜索方向:目前搜索方向是串行执行的,彼此独立的方向完全可以 Promise.all。预期对 3+ 方向的任务,总耗时能再降 40-60%。

2. 搜索结果缓存:同一仓库对同一 pattern 的搜索结果,绑定 Git commit 做缓存。对重复出现的 Bug 类型(比如 accessibility 问题),可以显著减少重复检索。

3. 模型分级调度:现在不同环节用的模型没有明显区分。提取结构化信息、消息摘要这类简单任务,用小模型足够了;精确定位和代码生成,才需要最强模型。分级后成本可以进一步下降。

4. 反馈闭环:CR 通过或拒绝的结果,目前没有系统性地回流到知识库。这个闭环一旦建起来,系统会越来越像”会学习的故障处理平台”。


七、一些反思

回看这三个版本的演进,有几个观察:

架构重构的价值超出预期。 V3 把 1100 行单文件拆成 Engine + Profile 模式,当时以为只是”代码整洁”的问题。但到 V4 需要支持 11 种 bug 类型策略、10+ 种 Profile,到 V5 需要在 Pipeline 里复用这些 Profile 时,这个架构决策带来的红利远远超过了重构的成本。

可观测性不是锦上添花。 没有 Metrics 之前,优化靠直觉。有了 Metrics 之后,每个优化都能量化,失败案例能被追溯,回归能被检测。这套体系建得越早越好。

领域知识的密度决定系统价值。 通用 Agent 框架(循环、工具分发、压缩)是基础设施,可以替换;BugTypePolicy、多架构 TrackingModel、修复策略库、历史案例这些领域资产,才是真正的壁垒。前者越薄越好,后者越厚越值钱。

从”工具”到”流水线”是个重要的认知跃迁。 单个命令(locate / fix / cr)组合使用时需要大量手工协调;Pipeline 把这些步骤声明式地串起来,加上 Gate 和回退,系统才真正具备”替程序员执行一整套流程”的能力,而不只是”帮程序员在 IDE 里更快操作”。

这条路还很长,但方向是对的。

Android Studio 新版本与 Gradle 7.x 构建报错解决方案

作者 wyanassert
2026年5月11日 17:39

问题现象

在 Android Studio 中 Build / Sync 报错:

1
2
3
4
5
6
7
8
Could not compile initialization script '.../ijMapper1.gradle'.
> startup failed:
General error during conversion: Unsupported class file major version 65

java.lang.IllegalArgumentException: Unsupported class file major version 65
at groovyjarjarasm.asm.ClassReader.<init>(ClassReader.java:199)
...
at com.intellij.gradle.toolingExtension.impl.modelAction.GradleModelFetchAction...

从命令行直接执行 ./gradlew tasks 完全正常,只有通过 Android Studio 触发构建时才报错。


根本原因

这是一个 Android Studio 版本 × Gradle 版本 的二进制不兼容问题,与项目代码无关。

层级 说明
class file major version 65 Java 21 编译产物的标识(Java 11 = 55,Java 17 = 61,Java 21 = 65)
Android Studio Koala(2024.1)及更新版本 自带 JDK 21,其 Tooling Extension JARs(如 GradleOpenTelemetryGradleModelFetchAction)使用 Java 21 编译
Gradle 7.4.2 内置 Groovy 3.0.9 / ASM 9.1 ASM 9.1 最高只能解析 Java 17(class version 61)的 class 文件,无法读取 Java 21 产物

Android Studio 每次构建会向 Gradle 注入一个临时 init script(ijMapper1.gradle),Groovy 编译这个脚本时需要解析 classpath 上的 Android Studio Tooling JARs。新版 AS 的这些 JAR 是 Java 21 编译的,Gradle 7.x 内置的 ASM 读不了,直接崩溃。

命令行不受影响的原因:直接执行 ./gradlew 不经过 Android Studio 的 Tooling API,不会注入这个 init script。


解决方案

✅ 方案一:在 Android Studio 中指定 Gradle JDK 为 JDK 11(本文实际采用,推荐)

修改项目的 .idea/gradle.xml,将 gradleJvm 改为 JDK 11 的路径(路径需已在 Android Studio 的 JDK Table 中注册):

1
2
<option name="gradleJvm"
value="$USER_HOME$/Library/Java/JavaVirtualMachines/corretto-11.0.19/Contents/Home" />

注意:这里必须使用 $USER_HOME$ 变量形式,而非绝对路径,否则 Android Studio 在 jdk.table.xml 中匹配不到对应条目,报 “Undefined jdk.table.xml entry” 错误。

配置完成后,Android Studio 会:

  1. 用 JDK 11 启动 Gradle daemon
  2. 选用与 JDK 11 兼容的 Tooling Extension JARs(class version ≤ 61),避免 ASM 解析失败

此方案不影响其他同事(.idea/gradle.xml 中的路径是各自机器上注册过的条目,不同机器只要都注册了 JDK 11 即可)。


方案二:升级 Gradle 到 8.4+(需团队协调)

Gradle 8.x 采用 Groovy 4.x / ASM 9.5+,原生支持 Java 21 class 文件,从根本上消除兼容问题。

1
2
# gradle/wrapper/gradle-wrapper.properties
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-all.zip

代价:AGP 7.x 不兼容 Gradle 8.x,需同步将 AGP 升级到 8.x,改动较大,需要团队统一升级。


方案三:降级 Android Studio(临时方案)

降回 Android Studio Jellyfish(2023.3.1) 或更早版本,其 Tooling Extension JARs 基于 Java 17 编译(class version 61),Gradle 7.x 的 ASM 9.1 可以正常解析。


排查思路总结

遇到此类问题时,快速定位的关键线索:

  1. 只在 IDE 内报错,命令行正常 → 问题出在 IDE 注入的 init script,而非项目构建脚本本身
  2. Unsupported class file major version 65 → 某个依赖/工具 JAR 用 Java 21 编译,运行时的 ASM/JVM 版本不支持
  3. 错误堆栈含 com.intellij.gradle.toolingExtension → 确认是 Android Studio Tooling Extension 和 Gradle 的版本兼容性问题

兼容性速查表

Android Studio 版本 内置 JDK Tooling JAR class version 最低兼容 Gradle
Jellyfish 2023.3.1 及以前 JDK 17 61(Java 17) Gradle 7.x 可用
Koala 2024.1.1 及以后 JDK 21 65(Java 21) 需 Gradle 8.4+ 或手动指定 Gradle JDK 为 17/11

为什么要做一个 iOS bug 自动修复的 agent 程序

作者 wyanassert
2026年3月24日 16:07

为什么要做一个 iOS Bug 自动修复的 Agent 程序

一、为什么不直接做 IDE 插件

如果目标只是”在 IDE 里更方便地写代码”,那直接基于 CodeBuddy 这类 Agent IDE 做插件,通常更快、更省成本。

但我的目标不止于此:

  • 把”修复/分析/定位/验证”沉淀成可复用能力
  • 让 Agent 不依赖某个 IDE 才能工作
  • 把人的经验流程产品化、平台化、自动化
  • 围绕 iOS 问题定位、日志分析、修复建议形成领域能力

这些目标决定了我需要一套自建的 Agent 架构,而不是一个 IDE 插件。


二、自建 Agent 架构的核心优势

1. 掌握的是”工作流”,不是”某个 IDE 的扩展点”

基于 Agent IDE 做插件,本质上是在它既有能力上”加一层”。而自建 Agent 架构,本质上是在定义:

  • 任务如何拆解
  • 上下文如何收集
  • 工具如何选择
  • 失败如何回退
  • 结果如何验证
  • 多轮推理如何收敛

这意味着我拥有的是流程控制权

这个差别很大:

  • 插件模式:在别人的操作系统上写 App
  • Agent 架构模式:在定义自己的操作系统

未来我想做的事情——自动读取崩溃日志、自动定位可疑代码、自动生成 patch、自动跑校验、自动输出修复报告、自动接入 CI / 工单 / IM / 代码平台——自建 Agent 架构会比 IDE 插件自然得多。

2. 沉淀的是”领域智能”,不是通用编程助手能力

CodeBuddy 这类 Agent IDE 的强项是通用编码协作:补全、重构、对话式改代码、搜索代码、生成测试。

而我的系统围绕 iOS Bug Auto Fix 在做,重点是:

  • 崩溃堆栈理解
  • 符号/模块/调用链关联
  • Objective-C / Swift / Pod 生态理解
  • 特定业务代码结构的定位
  • 历史问题模式复用
  • 修复策略模板化

这些能力,通用 Agent IDE 不会天然替我做好。我做 Agent 架构的真正价值不是”我也能调用 LLM 了”,而是:把特定领域的高价值决策流程封装成了 Agent。 这会形成自己的护城河。

3. 能做”非交互式自动化”,而不只是”人在 IDE 里点来点去”

IDE 插件天然偏向人在本地、打开工程、交互式提问、临场辅助。而自建 Agent 更容易扩展到命令行、批处理、服务化、CI/CD、机器人触发、定时任务、工单驱动修复。

未来完全可以做成这种链路:

1
崩溃日志/工单 → 问题归类 Agent → 代码定位 Agent → 修复策略 Agent → 生成 Patch → 验证 Agent → 提交 PR / 输出报告

这类能力,不是 IDE 插件的主战场。

4. 更强的”可解释性”和”可观测性”

自建 Agent 架构时,可以记录每一步:

  • 为什么选这个工具
  • 为什么判定这个文件相关
  • 哪一步检索到了关键证据
  • 哪次修复失败了
  • 哪种策略成功率最高
  • 每种 Bug 类型平均耗时多久

这带来几个重要价值:便于调试 Agent、便于持续优化 prompt / 工具策略、便于做质量评估、便于做企业内部合规审计。而很多 Agent IDE 内部流程只能”感觉它这么做了”,但很难完整掌控它的决策细节。

5. 模型、工具、供应商解耦

基于某个 Agent IDE 插件体系开发,通常会受到模型支持、上下文拼装方式、工具协议、权限边界、升级兼容性等限制。而自建 Agent 架构,可以自由决定用哪个模型、不同子任务切哪个模型、怎么做路由、缓存、检索、工具编排、降级和兜底。获得的是架构主导权,而不是平台适配权

6. 能把”经验”复用到 IDE 之外

如果只是写成 IDE 插件,很多价值会被锁死在 IDE 内。但做成独立 Agent 能力,以后这些东西都能复用到 Web 界面、命令行、VSCode / JetBrains / 自研 IDE、Slack / 飞书 / 企业微信机器人、服务端 API、测试平台、发布平台、缺陷平台。投入更像是在建设能力中台,而不是做一个单点入口。

7. 更适合做多 Agent 分工

当任务复杂到需要角色分工时,自建架构优势更明显。可以拆成:

Agent 职责
Planner Agent 任务拆解
Retriever Agent 找代码和上下文
Diagnoser Agent 判断根因
Patch Agent 生成修复代码
Verifier Agent 运行验证
Reporter Agent 输出报告

IDE 插件当然也能”伪多 Agent”,但一般都会受限于宿主产品的交互模型。自建则可以真正把分工、状态、上下文边界、交接协议做清楚。


三、这条路的代价

1. 在重复造很多”基础设施轮子”

包括上下文管理、工具调用协议、提示词编排、重试/超时/回退、文件读写安全、结果验证、token 成本控制、观测和日志、会话状态管理。这些在成熟 Agent IDE 里很多已经做好了。短期看,肯定更慢、更贵、更累。

2. 需要自己为”效果稳定性”负责

自建后,要自己解决:为什么这次检索不到、为什么上下文污染了、为什么选错工具、为什么 patch 不可执行、为什么修复建议不稳定、为什么不同仓库表现差异大。获得自由的同时,也接管了复杂性。

3. 如果场景主要是”本地编码辅助”,ROI 未必更高

如果用户核心诉求只是在 IDE 里聊天、改改代码、顺手做点搜索、生成一些 patch,那自建 Agent 架构带来的收益可能并不明显。可能出现架构先进了,但用户体感未必更强的情况。


四、与通用 Agent IDE 的差异化定位

一句话定位

不是”更会写代码的 IDE 助手”,而是”面向 iOS Bug 定位、诊断、修复与验证的专用智能执行系统”。

差异对比表

维度 我的 Agent 架构 CodeBuddy / Cursor / Copilot Workspace
核心目标 解决特定领域问题(iOS bug 定位、分析、修复、验证) 提升通用编码效率
核心对象 问题处理流程 代码编辑过程
工作单元 一次完整的缺陷处理链路 一次对话、一段代码修改
触发方式 日志、崩溃堆栈、工单、CI、命令行、服务调用 IDE 内交互、选中代码、对话输入
能力重点 诊断、定位、决策、修复策略、验证闭环 补全、解释、生成、搜索、重构
领域知识密度 很高,内置 iOS/Crash/工程结构知识 偏通用,领域知识较浅
自动化深度 半自动甚至全自动链路 多数以人机协作为主
可观测性 记录每一步证据、推理、工具调用、成功率 通常黑盒程度更高
可扩展性 可接日志系统、工单系统、测试系统、CI、PR 流程 主要受限于 IDE 插件边界
平台依赖 独立能力层,不依赖单一 IDE 依赖宿主 IDE/平台生态
长期资产 沉淀为组织级问题处理能力 沉淀为某 IDE 内的使用体验
护城河来源 领域流程、知识、验证闭环、历史反馈 产品体验、模型集成、编辑器生态

五、哪些是真壁垒,哪些是在重复造轮子

判断标准

这个模块如果明天被一个成熟框架替换掉,我的核心价值会不会下降?

  • 不会明显下降:大概率是基础设施
  • 会明显下降:可能是壁垒层

真正值得重点投入的壁垒层

1. iOS 问题理解与归因知识

崩溃堆栈解析、符号/模块/类/调用链映射、OC/Swift 混编上下文理解、生命周期/线程/内存/KVO/通知/Block/主线程 UI 更新等问题模式、常见崩溃类型到修复策略的映射。这类能力沉淀好了,不是通用 IDE 随便能替代的。强壁垒。

2. 问题处理 SOP

稳定的处理链路:读取错误信号 → 归类问题类型 → 缩小嫌疑范围 → 关联代码上下文 → 选择修复策略 → 生成 patch → 执行验证 → 输出结论与风险。这个流程代表了团队如何排查问题、如何做决策分层、如何降低误修概率。强壁垒。

3. 修复策略库 / 模式库

沉淀 CrashType → RootCauseCandidates → VerificationSteps → PatchTemplate → RiskHints 这种结构。覆盖数组越界、空指针、野指针、线程竞争、主线程违规调用、通知/KVO 生命周期遗漏、容器并发修改、异步回调释放时序问题等。每一类问题对应典型特征、定位信号、修复范式、验证点。可复利的核心资产。

4. 验证闭环

改完代码后做静态检查、跑定向测试、检查编译影响范围、输出风险说明、对修复结果做置信度评估。从”助手”进入”执行系统”。关键护城河。

5. 历史案例反馈系统

积累哪类问题最常见、哪类修复策略成功率最高、哪些模块最容易出问题、哪种上下文组合最容易定位成功、哪类 patch 最容易被 reject。系统会越来越像”会学习的故障处理平台”。长期壁垒。

有价值但不要过度自研的中间层

模块 建议
任务拆解器 / Planner 可以做,但必须领域化,否则容易沦为通用壳子
多 Agent 分工 多 Agent 本身不是壁垒,领域化分工才是壁垒
上下文组装系统 保留策略,少造基础设施

大概率属于”重复造轮子”的部分

模块 建议
通用聊天壳 / UI 壳 够用就行
通用代码读写工具封装 稳定优先,不要过度精雕
通用 ReAct / Agent Loop 不要把”有 loop”误认为”有壁垒”
通用记忆系统 通用 memory 尽量轻,重点做 case memory
通用 Prompt 编排器 prompt 系统化可以有,但别把它当主产品

六、系统架构收敛

我把整个系统收敛成三层:

1
2
3
4
5
6
7
8
9
10
11
12
┌─────────────────────────────────────────┐
│ 入口层(做薄) │
│ IDE / CLI / CI工单 / 服务API │
├─────────────────────────────────────────┤
│ 领域决策层(持续加厚) │
│ iOS缺陷分类 / 上下文选择策略 / 根因分析 │
│ 修复策略选择 / 风险评估 │
├─────────────────────────────────────────┤
│ 执行与验证层(够稳就行) │
│ 代码检索编辑 / Patch生成 / 构建测试 │
│ 结果验证 / 报告输出 │
└─────────────────────────────────────────┘
  • 入口层:不要重投太多
  • 执行与验证层:够稳就行
  • 领域决策层:最该持续加厚的地方

七、最容易出现的风险

把大量精力花在让 Agent 看起来更像 Agent,而不是让它更会解决 iOS 问题。

典型表现:角色越来越多、prompt 越来越复杂、tool 越来越多、框架越来越完整,但定位成功率没上升、修复成功率没上升、验证能力没增强、真实用户价值不明显。

判断功能优先级时,统一用三个指标:

  1. 定位准确率是否提升
  2. 修复成功率是否提升
  3. 端到端处理时间是否下降

只要不能提升这三项之一,就谨慎做。


八、投入优先级

优先级 方向
第一优先级 iOS bug 分类 → 根因 → 修复策略 → 验证 做成稳定闭环
第二优先级 把历史案例沉淀成可复用知识
第三优先级 把入口做薄,支持 IDE / CLI / CI 复用
第四优先级 尽量复用通用 Agent 基础设施,不要在壳层卷复杂度

九、结论

我不是在做另一个通用 AI 编码助手,而是在做 iOS 缺陷处理的领域执行系统。

真正的壁垒不在 Agent 外壳,而在领域知识、决策流程、修复策略和验证闭环。

通用的对话、工具调用、Planner、Memory 更多是基础设施,应尽量复用而非重造。

后续投入应聚焦提升定位准确率、修复成功率和端到端效率,而不是继续增加通用 Agent 复杂度。

分水岭在于:我的系统是在”帮助程序员在 IDE 里更快操作”,还是在”替程序员执行一整套 iOS 问题处理流程”。答案是后者,所以自建 Agent 架构是正确的选择。

[转载] 移动端开发稳了?AI 目前还无法取代客户端开发,小红书的论文告诉你数据

作者 wyanassert
2026年3月9日 13:32

原文地址

近期,由小红书联合多伦多大学等高校的研究人员发布了 《SWE-Bench Mobile》(2602.09540) 论文,内容主要是评估 LLM 智能体在处理真实生产级移动端应用开发任务时的能力,并提出了首个针对该领域的基准测试——SWE-Bench Mobile

这个论文对比之前那些简单的需求场景,明显更具备说服力,最重要的是,用真实的数据给目前的 AI 狂热浇一浇冷水

Image 17

目前的编程基准测试大多集中在孤立的算法问题,而 SWE-Bench 则是关注 GitHub 上的 Bug 修复,然而真实的工业级移动端开发汪汪更为复杂:

  • 多模态输入:开发者需要根据产品需求文档(PRD)和 Figma 设计稿等来写代码
  • 复杂的工程环境:中大厂的移动端代码库通常规模巨大( 5GB 以上),且涉及 Swift 与 Objective-C 混编、特定系统 API 及复杂的 UI 交互,还有编译环境影响
  • 任务类型多样化:不限于 Bug 修复,更多是功能开发和 UI 增强

所以研究团队从目前小红书自己的真实产品流水线中提取了 50 个具有代表性的开发任务,构建了该基准测试:

  • 数据集组成

    • 50 个真实任务:源自实际的产品需求
    • 449 个人工验证的测试用例:平均每个任务 9.1 个测试点,用于评估功能正确性
    • 多模态支持:70% 的任务附带 Figma 设计链接,92% 附带参考图
  • 代码库规模:基于约 5GB 大小的真实 iOS 生产代码库(Swift/Objective-C)

  • 任务复杂度:平均每个任务涉及修改 4.2 个文件,远超之前的基准测试

Image 18

整个基准的规则是:

  • 70% 任务包含 Figma
  • 92% 包含参考图片
  • 平均 PRD 长度 450 字

每个任务包含:

  • 一个统一 diff 补丁(patch)输出
  • 综合测试套件(平均 9.1 个测试案例)
  • 任务难度分级:从简单 UI 调整到复杂跨模块改造

Image 19

对于任务两个关键指标:

  • 任务成功率:所有测试通过的任务比例

  • 测试通过率:所有测试案例通过的比率

Image 20

而对于 LLM,论文评估了 22 种 不同的“智能体-模型”配置,涵盖了四个主流框架:

  • 商业智能体:Cursor、Codex (由 DeepSeek/OpenAI 等模型驱动)、Claude Code
  • 开源智能体:OpenCode

评估维度包括:任务完成率、任务复杂度影响、成本效果对比、多次运行稳定性、Prompt 设计影响等。

而根据论文可以得出结论:当前 AI 在生产级的软件工程力存在巨大局限性:

  • 成功率极低表现最好配置的成功率仅为 12% ,大多数任务以“实现不完整”告终,但测试通过率最高可到 28%,说明部分任务可以部分正确生成,但没能完全部署成功
  • 智能体架构十分重要 :同一个底层模型,在 Cursor 框架下的成功率为 12%,但在 OpenCode 下仅为 2%,智能体的工具调用、上下文管理等设计与模型本身同等重要
  • 商业模型占优:商业闭源智能体在处理大型代码库时的稳定性和正确性显著优于开源方案
  • 复杂度陷阱任务涉及 1-2 个文件时成功率为 18%,但当涉及 7 个以上文件时,成功率骤降至 2% ,显示出模型在跨文件长程推理方面的短板
  • “防御性编程”提示词更有效:研究发现,使用基于“防御性编程”(原则的简洁提示词,比复杂的提示词能让成功率提升 7.4%

Image 21

Image 22

对于失败,论文还针对失败类型归类:

  • 缺失关键功能标志位或 Feature Flag 是主要的失败原因
  • 其次是 数据模型缺失
  • 再者是 incomplete patch(文件覆盖不足)等问题

这些失败的类似,在一定程度上反映了智能体对真实工程流程、跨文件依赖、与视觉设计的理解严重不足,也就是这些问题是“工程级问题”,而不是“语言问题”:

所以哪怕换成 Android / Flutter,这类跨文件工程理解问题仍然存在。

基于这些数据,论文认为当前 LLM Agent 尽管在单一代码生成上有突破,但在端到端工程上下文(包含设计、代码库理解、工程流程)仍远未达到企业生产标准

另外,论文也有一个有趣的结论数据,主要统计了各 Agent + Model 的每任务成本(美元)和平均耗时(分钟),例如:

  • Cursor + Opus 4.5 : $3.50 / 15 min
  • Codex + GLM 4.6 : $1.30 / 13.3 min
  • OpenCode + GLM 4.6 : $0.13 / 32.5 min
  • OpenCode + Opus 4.5 : $9.33 / 8.2 min

Image 23

对此可以看出来:

  • Codex + GLM 4.6 是性价比最高
  • OpenCode 极便宜但成功率低
  • OpenCode + Opus 4.5 是最贵但效果很差(2%)

最后,下图是论文的最终结果对比,例如在 Success 和 Pass 上:

  • Cursor + Opus 4.5 → 12% / 28.1%
  • Codex + GLM 4.6 → 12% / 19.6%
  • OpenCode + GLM 4.6 → 8%

Image 24

这么看,OpenCode 的实际数据表现是真的一般。

这个在同一个模型,在不同 agent 上的成功率也有所体现,OpenCode 再一次被鞭尸:

Image 25

所以,可以看出来,目前的 AI 智能体离独立完成中大型移动开发还有很大距离,主要瓶颈在于多模态理解、大规模代码导航和跨文件逻辑一致性等。

另外,SWE-Bench Mobile 采用了托管基准挑战(Hosted Benchmark)模式 ,不公开测试集答案,以防止数据泄露到未来的模型训练中。

最后,论文只针对原生 iOS 开发进行测试,没有测试 Android 原生、Flutter、RN 等其他情况,按照一般直觉,这些框架的 AI 表现应该会好于 iOS 原生,当然这也只是我的个人直觉,真实数据还是得有企业做过 Benchmark 才知道。

不过至少从目前看,在移动端开发领域写代码上,至少比前端安全性高一些?你怎么看?

大型 iOS 项目的简单 bug 自动修复实践

作者 wyanassert
2026年3月6日 22:26

工具概述

iOS Bug AutoFix 是一个基于 AI 的 iOS 代码 Bug 自动定位工具。它从自然语言 Bug 描述出发,通过三步流水线(信息提取 → 粗筛定位 → 精确定位)自动定位到问题代码的具体文件和行号。本次分析以两条实际命令的运行为例。


命令一:index — 构建代码索引

执行命令

1
npx ts-node src/index.ts index

加载配置

入口文件 index.tsmain() 函数首先调用 loadConfig() 读取配置文件:

  • 配置路径: tool/config/autofix.config.json
  • 读取结果:
    • repoRoot/Users/wyan/Develop/Code/branch/Bugfix
    • openai.modeldeepseek-chat
    • index.includeDirs["Classes/Modules"]

同时在构造 BugAutoFixer 时,基于 repoRoot 设置了运行时目录:

  • .autofix/ 根目录
  • .autofix/index.db — SQLite 索引数据库
  • .autofix/results/ — 定位结果目录
  • .autofix/logs/ — 日志目录(预留)

加载页面映射表

BugAutoFixer 构造函数中创建 FileLocator,而 FileLocator 构造时会创建 PageMapperpage-mapper.ts 会按优先级搜索 page-mapping.json 文件:

1
✓ 已加载页面映射表: .../page-mapping.json (14 个页面)

映射表内容示例(来自 page-mapping.example.json):

1
2
3
4
5
{
"个人主页": ["QMPersonalInfoViewController", "QMGeneralUserHeaderView", "QMGeneralUserV2TabVC"],
"播放页": ["QMAudioPlayerVC", "QMPlayingSongPage", ...],
...
}

页面映射表同时构建了反向映射(类名 → 页面名),共 14 个页面。

索引构建流程

code-indexer.tsbuildFullIndex() 方法执行以下步骤:

数据库初始化

创建 SQLite 数据库(WAL 模式),包含:

用途
file_index 文件级索引(类名、方法名、协议、UI 类、无障碍标记等)
class_hierarchy 类继承关系
file_fts (FTS5) 全文搜索虚拟表,通过触发器自动同步

扫描源文件

使用 find 命令扫描仓库,由于配置了 includeDirs: ["Classes/Modules"],实际执行的命令相当于:

1
find "/Users/wyan/Develop/Code/branch/Bugfix" -type f \( -name "*.swift" -o -name "*.m" -o -name "*.h" \) -and \( -path "*/Classes/Modules/*" \)
1
Found 17522 source files to index

逐文件解析

在一个 SQLite 事务中,对每个文件进行解析。根据文件扩展名分别调用:

  • .swift 文件parseSwiftFile(): 用正则提取 class/struct/enum/extension 声明、func 方法名、协议、UI* 类使用、accessibility* 属性、@IBOutlet
  • .m / .h 文件parseObjCFile(): 用正则提取 @interface/@implementation(含 Category)、方法名(-/+ (type)methodName)、<Protocol> 协议、UI* 类指针声明、accessibility* 属性

每个文件还会:

  1. 生成raw_summary :取前 30 行 + 所有关键声明行(class/func/@interface/@implementation/accessibility 等),控制在 2000 字符以内
  2. 推断 pod_name :从路径中匹配 Pods/ModuleName/Modules/ModuleName/ 模式
  3. 提取类继承关系 :存入 class_hierarchy

FTS5 全文索引自动同步

FTS5 是 Full-Text Search version 5 的缩写,即 SQLite 内置的第 5 版全文搜索引擎。
本项目用它来对 17522 个源文件的类名、方法名等元数据建立倒排索引,让 Step 2 的关键词搜索可以在毫秒级完成。

通过 SQLite 触发器,file_index 表的 INSERT/UPDATE/DELETE 操作会自动同步到 file_fts 全文搜索虚拟表,支持后续的 MATCH 全文搜索。

最终结果

1
2
Indexed: 17522, Skipped: 0
Index built successfully!

17522 个源文件全部成功索引。


命令二:locate — 定位 Bug

执行命令

1
npx ts-node src/index.ts locate "个人主页导航栏更多按钮无障碍响应错误"

整个 locate 流程分为三个 Step,总耗时 73.7 秒


Step 1: 信息提取(LLM 调用 #1)

执行者: bug-info-extractor.ts

构建 Prompt

将 bug 描述嵌入一个结构化 prompt 中,要求 LLM 以 JSON 格式输出提取结果。Prompt 关键指令:

“keywords 要包含各种可能的命名变体,比如中文’播放页’对应可能的类名 PlayerViewController, PlayViewController, PlayerVC…”

调用 DeepSeek API

使用 OpenAI SDK 的 chat.completions.create

  • 模型: deepseek-chat
  • 温度: 0.1(低温度确保输出稳定)
  • 响应格式: json_object(强制 JSON 输出)
  • 重试机制: 最多 3 次,指数退避(1s → 2s → 4s)

LLM 返回结果(解析后)

1
2
3
4
5
6
7
8
9
10
11
Type:       accessibility
Summary: 个人主页导航栏更多按钮的无障碍响应功能存在错误
Keywords: ProfileViewController, ProfileVC, PersonalHomeViewController,
HomeViewController, NavigationBar, NavBar, MoreButton, MoreBtn,
RightBarButtonItem, UIBarButtonItem, accessibilityLabel,
accessibilityHint, accessibilityTraits, isAccessibilityElement,
ProfileModule, UserProfile, PersonalCenter
Module: 个人主页/用户资料
Page: 个人主页
VCs: ProfileViewController, PersonalHomeViewController,
UserProfileViewController, HomeViewController

关键观察:LLM 从简短的中文描述中猜测了大量可能的英文类名/属性名变体,这些关键词将在 Step 2 中被用于多策略搜索。


Step 2: 粗筛定位(纯本地,无 LLM 调用)

执行者: file-locator.ts

6 种策略全部并行执行Promise.allSettled),互不影响:

策略 1: 直接路径匹配

  • 逻辑:检查 bugInfo.codeScanIssue?.filePath 是否存在
  • 本次结果:无(bug 描述中没有直接给出文件路径)
  • 权重:100 分(未触发)

策略 2: ripgrep 全文搜索(异步并行)

  • 逻辑:对 keywords 中长度 ≥ 3 的关键词,逐个并行执行 ripgrep:
    1
    rg -l --type swift --type objc "ProfileViewController" "/Users/wyan/Develop/Code/branch/Bugfix" 2>/dev/null | head -50
  • 本次匹配到的关键词(从结果中可以看到):
    • NavBar → 匹配到 QMPersonalInfoViewController.m, QMGeneralUserHeaderView.m
    • MoreButton → 匹配到 QMPersonTitleView.m, QMPersonHeaderCell.m, QMPersonalInfoViewController.m
    • MoreBtn → 匹配到多个文件
    • accessibilityHint → 匹配到 QMPersonalInfoViewController.m
    • accessibilityTraits → 匹配到 QMPersonTitleView.m, QMPersonHeaderCell.m, QMPersonalInfoViewController.m
    • ProfileViewController → 匹配到 ProfileViewController_V3Pad.m, ProfileViewController_V3+Follow.m
    • ProfileVC → 匹配到多个 Profile 相关文件
    • UserProfile → 匹配到 QMPersonalInfoViewController.m, QMPersonalInfoViewController+JumpAction.m
  • 每个匹配得 6 分

策略 3: 数据库索引查询

  • 页面映射匹配(最高权重 40 分):

    • bugInfo.pageName = "个人主页"
    • 查映射表 → ["QMPersonalInfoViewController", "QMGeneralUserHeaderView", "QMGeneralUserV2TabVC"]
    • SQL: SELECT file_path FROM file_index WHERE class_names LIKE '%QMPersonalInfoViewController%'
    • 匹配到所有 QMPersonalInfoViewController.m/.h 及 Category 文件,每个 40 分
  • 类名 FTS5 匹配(30 分):

    • viewControllers 列表(ProfileViewController, PersonalHomeViewController 等)执行全文搜索
    • SQL: SELECT file_path FROM file_fts WHERE class_names MATCH 'ProfileViewController' LIMIT 30
    • 匹配到 ProfileViewController_V3Pad.m 等文件,每个 30 分
  • 关键词 FTS5 匹配(8 分):

    • 对长度 ≥ 4 的关键词(如 MoreBtn, accessibilityLabel, accessibilityTraits, isAccessibilityElement)执行全文搜索
    • 匹配到 QMPersonTitleView.m, QMPersonHeaderCell.m

策略 4: 目录结构推断

  • 逻辑:对 pageName(”个人主页”)和 moduleName(”个人主页/用户资料”)执行 find 命令搜索匹配的目录
  • 由于中文名和目录命名不匹配,本次可能未产生有效结果

策略 5: Git 修改热点

  • 逻辑
    1
    git log --since="2 weeks ago" --name-only --pretty=format: | sort | uniq -c | sort -rn | head -100
  • 获取最近 2 周频繁修改的文件,每个 2 分
  • 低权重兜底策略

策略 6: Bug 类型专项搜索

  • bugType = “accessibility” → 调用 searchAccessibilityIssues()
  • 逻辑:在索引中查找包含特定 UI 元素但缺少无障碍属性的文件
    1
    SELECT file_path FROM file_index WHERE has_accessibility = 0 AND ui_classes LIKE '%UIButton%' LIMIT 30
  • 每个匹配 15 分

分数合并与交叉验证加分

所有策略结果通过 candidateMap 合并。同一文件多次命中的分数会叠加

关键的交叉验证加分机制

1
2
// 命中策略数 > 1 时,每多一种策略额外加 5 分
const bonus = extraStrategies * 5;

例如 QMPersonalInfoViewController.m

  • 策略 2 (ripgrep): 匹配了 NavBar, MoreButton, MoreBtn, accessibilityHint, accessibilityTraits, UserProfile → 6×6 = 36 分
  • 策略 3 (索引): 页面映射 40 分
  • 交叉验证加分: 2 种策略命中 → +5 分
  • 总分: 81 分(排名第 1)

最终排序输出 Top 20

结果按 score 降序排序,取前 MAX_CANDIDATES = 20 个文件:

排名 分数 文件 主要得分来源
1 81 QMPersonalInfoViewController.m ripgrep(6项) + 页面映射 + 交叉验证
2 57 QMPersonalInfoViewController+JumpAction.m ripgrep(ProfileVC,UserProfile) + 页面映射 + 交叉验证
3 55 ProfileViewController_V3Pad.m ripgrep + 索引类名 + 索引关键词 + 交叉验证
4 55 ProfileViewController_V3+Follow.m 同上
5 55 QMPersonTitleView.m ripgrep(MoreButton,MoreBtn,accessibilityTraits) + 索引关键词(多个) + 交叉验证
6 55 QMPersonHeaderCell.m 同上

Step 3: 精确定位(LLM 调用 #2 ~ #7)

执行者: precise-locator.ts

这是整个流程中消耗 token 最多的阶段,通过漏斗式两轮筛选来控制成本。

读取文件内容 + 生成摘要

对 Top 10(MAX_SCREENING_FILES = 10)候选文件,调用 loadFileSummaries()

  1. 读取完整文件内容fs.readFileSync(filePath, "utf-8")
  2. 生成摘要extractSummary(content) — 取前 30 行 + 所有关键声明行(class/func/@interface/@implementation/accessibility 等),约控制在 ~500 token/文件
1
2
3
4
5
6
7
8
private extractSummary(content: string): string {
const importantLines = lines.filter(line => {
return /^(class |struct |func |@interface|@implementation|@IBOutlet|@IBAction|import |#import)/.test(trimmed)
|| /accessibility/i.test(trimmed);
});
const header = lines.slice(0, 30).join("\n");
return `${header}\n\n// === Key declarations ===\n${keyDeclarations}`;
}

第一轮:摘要筛选(LLM 调用 #2)

目的:用低 token 成本快速排除无关文件。

构建 Prompt:将 bug 描述 + 10 个文件的摘要和匹配原因拼接成一个 prompt:

1
2
3
4
5
6
7
8
9
10
11
12
13
你是 iOS 开发专家。以下是一个 bug 的描述和几个候选文件的摘要。
请判断哪些文件最可能包含问题代码,返回文件路径列表(按可能性从高到低排序)。

Bug 描述:个人主页导航栏更多按钮无障碍响应错误

候选文件:
--- /path/to/QMPersonalInfoViewController.m ---
匹配原因: ripgrep 匹配关键词: NavBar, MoreButton, ...
摘要:
[前30行 + 关键声明]

--- /path/to/QMPersonTitleView.m ---
...

LLM 返回:JSON 格式的相关文件列表

1
{ "relevantFiles": ["path1", "path2", "path3", "path4", "path5"] }

结果:从 10 个文件筛选到 5 个真正相关的文件。

1
2
Round 1: Screening with file summaries...
Screened to 5 relevant files

“关键声明”是什么

在这个工具中,**”关键声明”(Key Declarations)** 是指源代码中以特定模式开头的、具有结构性意义的代码行。具体来说,就是通过正则表达式匹配出的以下内容:

匹配规则

precise-locator.tsextractSummary 方法(第 371 行)中:

1
2
3
4
5
6
7
const importantLines = lines.filter((line) => {
const trimmed = line.trim();
return (
/^(class |struct |enum |extension |func |@interface|@implementation|@IBOutlet|@IBAction|import |#import)/.test(trimmed)
|| /accessibility/i.test(trimmed)
);
});

也就是说,关键声明行 = 匹配以下任一模式的代码行:

模式 含义 示例
class Swift 类声明 class MyViewController: UIViewController
struct Swift 结构体声明 struct Config { ... }
enum 枚举声明 enum State { ... }
extension Swift 扩展声明 extension UIView { ... }
func Swift 函数声明 func viewDidLoad() { ... }
@interface ObjC 类/分类声明 @interface QMPersonalInfoViewController
@implementation ObjC 实现声明 @implementation QMPersonTitleView
@IBOutlet Storyboard 关联 @IBOutlet weak var moreBtn: UIButton!
@IBAction Storyboard 事件 @IBAction func didClickMore()
import / #import 导入语句 #import "QMPersonalInfoViewController.h"
/accessibility/i 任何包含 accessibility 的行 moreBtn.accessibilityLabel = @"更多";

摘要的组成结构

最终生成的摘要格式为:

1
2
3
4
[文件前 30 行原文]

// === Key declarations ===
[所有关键声明行]

用一个具体例子来说明,对于 QMPersonTitleView.m,摘要大概长这样:

1
2
3
4
5
6
7
8
9
10
11
12
// 前 30 行(包含 #import、文件注释等)
#import "QMPersonTitleView.h"
#import "UIView+Frame.h"
...

// === Key declarations ===
@implementation QMPersonTitleView
- (void)addMoreBtnWithTitle:... // ← func/method 声明
@IBOutlet ... // ← IBOutlet
moreBtn.accessibilityLabel = moreBtnTitle; // ← accessibility 相关
moreBtn.accessibilityTraits &= ~UIAccessibilityTraitSelected;
moreBtn.accessibilityLabel = @"更多";

为什么这么设计

这个设计的目的是用极少的 token(约 500 token/文件)让 AI 快速理解一个文件的”骨架”:

  1. 前 30 行 → 了解文件是什么(import 了什么、类名是什么)
  2. 关键声明行 → 了解文件做了什么(有哪些类、方法、UI 关联)
  3. accessibility 行 → 专门针对无障碍类 Bug,直接暴露相关代码

这样 Round 1 用 20 个文件 × 500 token ≈ 10,000 token 就能完成初筛,而不需要发送 20 个完整文件(可能要 200,000+ token)。

Token 优化策略

这里的漏斗设计是整个工具的核心性能优化:

1
2
3
4
5
Step 2: 20个候选文件(纯本地,0 token)

Round 1: 20个文件的摘要(~500 token/文件 = ~5000 token)→ 筛选到 5 个

Round 2: 5个文件的完整内容(每个独立调用)

如果直接对 20 个文件都发送完整内容,token 消耗将极其巨大(一个 ObjC 文件可能有数千行)。

第二轮:逐文件精确定位(LLM 调用 #3 ~ #7)

对筛选出的 Top 5(MAX_PRECISE_FILES = 5)文件,逐个调用 locateInFile()

大文件智能截取:对超过 500 行的文件(ObjC 文件通常非常长),不是简单截断前 500 行,而是使用 smartExtract() 进行智能截取:

  1. 保留头部 50 行(imports、类声明)
  2. 从 bug 描述中提取搜索关键词extractKeywordsFromDescription()
    • 提取英文标识符:accessibility, button, more, navigation
    • 提取中文关键词:导航栏, 更多, 按钮, 无障碍
  3. 搜索关键词在文件中的出现位置,取前后各 15 行上下文
  4. 合并重叠区间,避免重复
  5. 如果关键词匹配不到,回退为均匀采样关键声明行

最终生成带行号的截取内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
1: #import "QMPersonalInfoViewController.h"
2: ...
...
50: ...

... (skipped to line 5660) ...

5660: // 导航栏更多按钮
5661: ...
5667: UIButton *button = [ComHelper createCustomButtonByImageName:@"personal_info_header_more"
...
5673: button.accessibilityLabel = QMLocalizedString(@"SVCC_SHOW_MORE", nil);

... (total 6000 lines, showing 350 relevant lines)

构建 Prompt

1
2
3
4
5
6
7
你是 iOS 开发专家。请在以下代码中精确定位 bug 所在位置。

Bug 描述:个人主页导航栏更多按钮无障碍响应错误

文件:/path/to/QMPersonTitleView.m
```code
[带行号的文件内容/智能截取内容]
1
2
3
4
5
6
7
请返回 JSON:
{
"lineStart": 问题代码起始行号,
"lineEnd": 问题代码结束行号,
"confidence": 0到1之间的置信度数值,
"explanation": "定位原因的详细说明"
}

5 个文件的 LLM 返回结果

文件 行号 置信度 核心发现
QMPersonTitleView.m 189-195 90% accessibilityLabel 被设置后又被硬编码为 @"更多" 覆盖
QMPersonHeaderCell.m 70-70 90% accessibilityLabel = moreBtnTitle 但缺少完整的无障碍配置
QMPersonalInfoViewController.m 5667-5673 85% 导航栏更多按钮创建处,可能存在本地化字符串问题
ProfileViewController_V3Pad.m 1010-1013 85% accessibilityLabel:atIndex: 方法始终返回空字符串 @""
ProfileViewController_V3+Follow.m 176-200 85% 关注按钮点击处理缺少无障碍属性更新

结果排序

所有定位结果按 confidence(置信度)降序排序:

1
return results.sort((a, b) => b.confidence - a.confidence);

90% 的两个结果排在前面,85% 的三个排在后面。

提取代码片段

对每个定位结果,根据 lineStartlineEnd 从完整文件内容中截取代码:

1
2
const contentLines = content.split("\n");
const codeSnippet = contentLines.slice(lineStart - 1, lineEnd).join("\n");

结果保存

定位结果同时输出到终端和 JSON 文件:

1
2
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
const resultFile = path.join(RESULTS_DIR, `result-${timestamp}.json`);
1
Results saved to: .../result-2026-03-06T14-04-42-624Z.json

API 调用汇总

本次 locate 命令总共进行了 7 次 LLM API 调用

次序 阶段 输入 输出 预估 Token
1 Step 1: 信息提取 bug 描述 + prompt模板 BugInfo JSON ~500
2 Step 3 Round 1: 摘要筛选 10个文件摘要 5个相关文件路径 ~6000
3-7 Step 3 Round 2: 精确定位 每个文件的内容(智能截取) 行号 + 置信度 + 解释 ~3000-8000/次

Step 2 完全在本地执行(ripgrep + SQLite + find + git),无 API 调用,0 token 消耗。


关键设计决策总结

多策略并行 + 分数融合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
graph TD
A[Bug 描述] --> B[Step 1: LLM 提取 BugInfo]
B --> C1[策略1: 直接路径]
B --> C2[策略2: ripgrep 搜索]
B --> C3[策略3: 索引查询+页面映射]
B --> C4[策略4: 目录推断]
B --> C5[策略5: Git 热点]
B --> C6[策略6: 类型专项]
C1 --> D[分数合并 + 交叉验证加分]
C2 --> D
C3 --> D
C4 --> D
C5 --> D
C6 --> D
D --> E[Top 20 候选文件]
E --> F[Round 1: 摘要筛选 → Top 5]
F --> G[Round 2: 逐文件精确定位]
G --> H[按置信度排序输出]

分数体系设计

来源 分值 设计意图
直接路径 100 代码扫描报告给出的路径几乎必中
页面映射 40 人工维护的映射最可靠
索引类名匹配 30 FTS5 匹配到类名,可信度高
Bug 类型专项 15 有针对性的搜索
索引关键词匹配 8 关键词范围更广,可能有噪声
目录推断 8 目录名和模块名可能不完全对应
ripgrep 6 全文搜索覆盖广但噪声多
Git 热点 2 纯统计信息,低权重兜底
交叉验证加分 +5/策略 多策略命中说明文件高度相关

Token 优化漏斗

1
2
3
4
5
6
7
17,522 源文件
↓ 本地 6 策略并行筛选(0 token)
20 候选文件
↓ 读取 Top 10 文件摘要(~500 token/文件 × 10)
10 → 5 文件(Round 1 筛选,~6000 token)
↓ 逐文件精确定位,大文件智能截取
5 个定位结果(Round 2,~5000 token/文件 × 5)

总 token 消耗约: 30,000-40,000 token,相比直接将 20 个大文件发给 AI(可能 500,000+ token),节省了 90% 以上

大文件智能截取 vs 简单截断

简单截断前 500 行的问题:ObjC 文件头部通常是 #import 和属性声明,真正有 bug 的代码可能在第 5000+ 行。智能截取通过关键词搜索 + 上下文窗口(前后各 15 行)确保问题代码被覆盖。

本次案例中 QMPersonalInfoViewController.m 的问题代码在第 5667 行,如果简单截断前 500 行将完全漏掉。


本次定位效果评价

对于 bug 描述 **”个人主页导航栏更多按钮无障碍响应错误”**:

  1. Step 1 准确识别为 accessibility 类型,正确推断了 个人主页 页面名,关键词覆盖了 MoreButton/MoreBtn/accessibilityLabel/accessibilityTraits 等关键变体
  2. Step 2 的 Top 1 就是主文件 QMPersonalInfoViewController.m(81 分),得益于页面映射(40分)+ ripgrep 多关键词命中(36分)+ 交叉验证加分(5分)
  3. Step 3 最终输出了 5 个定位结果,最高置信度 90% 的两个结果精确指向了 accessibilityLabel 被错误覆盖和不完整设置的代码行

VSCode 插件全部无法激活?一次从日志到根源的排查记录

作者 wyanassert
2026年3月3日 16:34

VSCode 插件全部无法激活?一次从日志到根源的排查记录

引言

某天,你像往常一样打开 Visual Studio Code,却发现所有已安装的插件都失效了——代码补全没了、Git 信息不见了、主题也变回了默认。更诡异的是,重装软件、清理缓存、升级版本……常规手段统统无效。插件市场明明显示已安装,但就是无法激活。这究竟是怎么回事?

最近我就遇到了这样的棘手问题,经过一番抽丝剥茧,终于揪出了幕后黑手——一个看似无害的 GitBlame 扩展。下面我将完整还原整个排查过程,希望能为遇到类似问题的朋友提供一份实用的“避坑指南”。


第一阶段:基础排查(全军覆没)

当所有插件都无法激活时,首先排除环境因素:

  • 检查 VSCode 位置:确保 Visual Studio Code.app 位于“应用程序”文件夹,而非“下载”或桌面(权限问题会导致插件加载失败)。
  • 清理缓存与配置文件
    1
    2
    3
    rm -rf ~/.vscode
    rm -rf ~/Library/Application\ Support/Code
    rm -rf ~/Library/Caches/com.microsoft.VSCode
  • 彻底重装:删除上述所有文件后,从官网下载最新版重装。

然而,这一套组合拳下来,问题依旧。看来不是简单的缓存损坏。


第二阶段:启用“侦探模式”——挖掘日志

常规手段无效,就需要让 VSCode 自己“开口说话”。打开 帮助切换开发人员工具(或 Cmd+Option+I),在 控制台(Console)输出(Output) 面板中寻找线索。

果然,一条醒目的红色错误映入眼帘:

1
2
ERR Extension 'TME.continuecode CANNOT USE these API proposals 'extensionRuntime'. 
You MUST start in extension development mode or use the --enable-proposed-api command line flag

这里出现了一个陌生的扩展 TME.continuecode,它试图使用 提案 API(Proposed API)——这是 VSCode 内部开发中的接口,普通扩展无权调用。这种错误可能导致扩展半激活,甚至阻塞整个扩展宿主进程。

同时,日志中还发现了两个 CMake 扩展的冲突警告:

1
WARN [twxs.cmake]: 无法注册“cmake.cmakePath”。此属性已注册。

多个扩展争夺同一配置项,虽不致命,但加剧了环境的不稳定性。

初步行动:移除问题扩展

1
2
rm -rf ~/.vscode/extensions/tme.continuecode-*
rm -rf ~/.vscode/extensions/twxs.cmake-*

重启 VSCode 后,TME.continuecode 的错误消失了,但……扩展宿主依然无响应!日志中只剩下:

1
INFO Extension host (LocalProcess pid: 12485) is unresponsive.

看来凶手不止一个。


第三阶段:终极排查法——禁用所有扩展,逐个启用

当错误日志无法直接定位时,就要用最原始也最有效的方法:控制变量法

1. 以禁用所有扩展的模式启动

1
code --disable-extensions

启动后,VSCode 响应迅速,所有内置功能正常。这证实问题 100% 出在某个第三方扩展上

2. 二分法逐个启用扩展

退出纯净模式,正常打开 VSCode(此时所有扩展仍处于禁用状态)。然后进入扩展面板,每次启用一个扩展,重启观察是否复现无响应。这个过程需要耐心,但能精确锁定目标。

经过几轮测试,当启用 GitBlame 后,扩展宿主再次卡死。卸载该扩展,一切恢复如初。


第四阶段:真相大白

GitBlame 是一个提供 Git 逐行注释(blame)信息的扩展,功能简单但实用。但是十小时前这个插件更新后, 导致了问题.

替代方案

  • GitLens:功能强大且持续维护的 Git 工具,不仅能显示 blame,还提供丰富的仓库浏览功能。
  • Git History:轻量级替代品,专注于文件历史和逐行注释。

安装 GitLens 后,一切功能正常,再无卡顿。


总结:排查思路回顾

  1. 基础检查:确保 VSCode 安装位置正确,清理缓存。
  2. 日志分析:打开开发者工具,查看控制台和“扩展宿主”输出,寻找显式错误。
  3. 处理明显错误:如提案 API 滥用、扩展冲突,先移除可疑扩展。
  4. 禁用所有扩展:用 code --disable-extensions 确认问题是否由扩展引起。
  5. 二分法逐个启用:定位具体肇事扩展。
  6. 寻找替代或更新:对于老旧扩展,果断换用维护活跃的同类工具。

一些有用的命令

用途 命令
以最大日志级别启动 code --log trace --verbose
禁用所有扩展 code --disable-extensions
使用临时用户数据目录 code --user-data-dir ~/Desktop/vscode-temp
删除指定扩展 rm -rf ~/.vscode/extensions/扩展名-*

心得

  • 日志是第一生产力:遇到诡异问题,先看日志,往往能直接定位。
  • 老旧扩展是定时炸弹:长期未更新的扩展可能与新版 VSCode 不兼容,尽量选用维护活跃的替代品。
  • 控制变量法永不过时:当错误信息模糊时,通过排除法缩小范围是最可靠的手段。

希望这次分享能帮助你快速解决类似的插件故障。如果你也有过奇葩的排查经历,欢迎留言交流!

❌
❌