gstack /ios-fix:基于真机状态快照的 iOS 自主 Bug 修复闭环工作流
gstack 中的 /ios-fix 技能把"发现 bug → 复现 bug → 定位根因 → 修复 → 真机验证 → 固化回归测试"串成一条零人工干预的自主修复流水线,并立下一条铁律:没有可复现的状态快照,就不许动任何一行 Swift 源码。读完本文,你将掌握该技能的五阶段修复协议、真机控制链(daemon + StateServer)的端点与权限分层、快照字段的默认拒绝机制,以及重建后 409 schema_mismatch 等典型故障的处理方式。
一、技能定位:/ios-qa 负责找 bug,/ios-fix 负责修 bug
/ios-fix 定义在 ios-fix/SKILL.md 中,是一份 Claude Code 技能文件(skill 文件即"可执行指令",Agent 逐条照做而非仅作参考资料)。其 frontmatter 声明了技能的元信息:
name: ios-fix
preamble-tier: 3
version: 1.0.0
description: Autonomous iOS bug fixer. (gstack)
allowed-tools:
- Bash
- Read
- Write
- Edit
- Grep
- Glob
- AskUserQuestion
triggers:
- fix this ios bug
- patch the iphone app
- auto-fix the ios issue
几个关键字段的含义:
triggers:三种自然语言触发短语("fix this ios bug"、"patch the iphone app"、"auto-fix the ios issue"),用户说出这些话或/ios-qa报告了 bug 后想要自动修复时,技能会被调用;语音触发别名包括 "fix the iOS bug"、"patch the iPhone app" 等。allowed-tools:技能运行期间被允许使用的宿主工具集——Bash、Read、Write、Edit、Grep、Glob,外加用于向用户提问的 AskUserQuestion。preamble-tier: 3:该技能携带第三档"前导程序"(Preamble),即所有 gstack 技能共享的一段运行时引导逻辑(详见第五节)。
上游依赖是 /ios-qa(ios-qa/SKILL.md):/ios-qa 负责驱动真机做 QA 并产出 bug 发现(bug 描述、截图、疑似的 accessibility-tree 节点),/ios-fix 负责消费这份发现并闭环修复。官方文档对两者的分工概括为:
"Iron Law: no fix without a reproducing snapshot. The agent captures pre-bug state via
GET /state/snapshot, writes the fix, rebuilds, redeploys, restores the snapshot, and verifies the bug is gone. The snapshot becomes a regression test fixture so the bug can't recur silently." —— docs/skills.md
二、铁律:没有复现快照,就不许修
技能正文开头(ios-fix/SKILL.md)立下整条流水线的锚:
NO FIX WITHOUT A REPRODUCING SNAPSHOT. 在编辑任何 Swift 源码之前,Agent 必须先抓取一个能复现该 bug 的
GET /state/snapshot。该快照会沉淀为回归测试夹具(test/fixtures/ios-fix/)。 没有复现快照就落地的修复,等于三个月后还得再修一次。
这条铁律的技术支撑是 gstack iOS 能力里的"状态快照"机制:iOS 应用内嵌一个 StateServer(DebugBridge SPM 库,仅 #if DEBUG 编译,监听 ::1/127.0.0.1 9999 端口),其中被 // @Snapshotable 标记的字段可以被完整导出为 JSON,并可通过 POST /state/restore 全量恢复。快照因此既是复现手段(restore 到 bug 状态),也是验证手段(恢复后截图对比),还是回归夹具(提交进仓库)。
三、五阶段修复协议
Phase 1:复现 bug
- 读取
/ios-qa的 bug 发现(bug 描述、截图、疑似出错的 accessibility-tree 节点); - 通过
POST /tap、/swipe、/type或POST /state/<key>(仅限可快照字段)把设备带入 bug 状态; - 抓取
GET /state/snapshot→ 写入test/fixtures/ios-fix/<bug-slug>-pre.json; - 抓取
GET /screenshot→ 写入test/fixtures/ios-fix/<bug-slug>-pre.png; - 用一行文字固化"哪里错了 + 期望行为"。
注意第 2 步的分层约束:直接操作 UI(tap/swipe/type)走 interact 权限层,而写状态字段 POST /state/<key> 走更高的 mutate 层,恢复整个快照 POST /state/restore 则是最高层 restore。权限分层在 daemon 源码 ios-qa/daemon/src/types.ts 中逐条映射:
export const TAILNET_ENDPOINT_TIERS: Record<string, Capability> = {
'GET /screenshot': 'observe',
'GET /state/snapshot': 'observe',
'POST /tap': 'interact',
'POST /swipe': 'interact',
'POST /type': 'interact',
'POST /state/*': 'mutate', // 通配:/state/ 下的写操作
'POST /state/restore': 'restore', // 最高层
};
四个权限层是嵌套的:observe ⊂ interact ⊂ mutate ⊂ restore,原则是最小够用。这意味着复现阶段"把设备带到 bug 状态"这个动作本身就受到权限审计(见第五节审计日志),而不是随意的黑盒操作。
Phase 2:定位根因
沿用 /investigate 的铁律:没有根因就不修。Agent 读取 Swift 源码,从出错的屏幕反向追踪到 view model、数据流、状态变更点,并找出能修复行为的最小改动。
如果存在多个可信的根因假设,协议要求使用 AskUserQuestion 让用户选择要修哪一个——这是整条"零人工干预"流水线中唯一被明确允许的人工介入点。gstack 对 AskUserQuestion 有严格格式规范(D 决策简报、ELI10 白话解释、每选项完整性评分、(recommended) 标签),其拆分链完整规则见 docs/askuserquestion-split.md,CJK 文本直接输出规范见 docs/askuserquestion-cjk.md。
Phase 3:应用修复
- 编辑 Swift 源码,diff 保持最小化;
- 重建:
xcodebuild -scheme <SchemeName> -destination 'platform=iOS,id=<UDID>' build install; - daemon 感知到重建,重新连接 StateServer 隧道;
- 重新部署——执行与首次启动相同的 boot-token 轮换流程(app 内的一次性启动 token 在 daemon 侧被消费后轮换为内存态凭证,约 5 秒内完成,防止日志抓取者拿到长期凭证)。
这里与 docs/howto-ios-testing-with-gstack.md 描述的部署流程一致:xcodebuild 构建后通过 xcrun devicectl device install app / device process launch --terminate-existing 装机并拉起,daemon 通过 CoreDevice IPv6 ULA 隧道中转流量,iOS 侧 StateServer 始终只绑 loopback,身份校验全部发生在 Mac 侧。
Phase 4:验证
- 用修复前的快照调
POST /state/restore→ 在真机上精确重现 bug 前的状态; - 重新截图,与
test/fixtures/ios-fix/<bug-slug>-pre.png对比; - bug 仍可见 → 修复失败:回滚改动重试,最多 3 轮后升级给用户;
- bug 消失 → 抓一张
<bug-slug>-post.png存档,供回归测试引用。
"restore 后验证"正是快照机制的核心价值:修复效果不依赖人工把手机点回原状态,而是用同一份 JSON 确定性还原。
Phase 5:固化回归测试
在 test/fixtures/ios-fix/<bug-slug>.test.ts 写一个测试,要求三步:
- 加载修复前快照;
- 通过
POST /state/restore还原到 bug 状态; - 在真机上断言修复后的行为——该测试受环境变量
GSTACK_HAS_IOS_DEVICE=1门控,归属"periodic"(周期性)测试层,即只在挂了真机的环境里跑,CI 无设备时自动跳过。
快照夹具 + 测试文件与修复代码一同提交。至此闭环完成:下次任何人(包括未来的 Agent)跑回归测试,就能在真机上确定性复现"修复前状态",bug 无法"静默复发"。
四、快照机制的底层细节
理解五阶段协议,必须理解 @Snapshotable 快照的设计约束(完整说明见 docs/howto-ios-testing-with-gstack.md):
@Observable
final class AppState {
// @Snapshotable
var username: String = ""
var authToken: String = "" // never exported
}
- 默认拒绝(default-deny):只有字段上方带独立标记注释
// @Snapshotable的实例var才会被导出;token、PII、鉴权状态默认不会出现在快照里,避免敏感数据被写进提交到仓库的回归夹具; - 类型白名单:标记字段必须是 JSON 原生标量(
String、Bool、各宽度整数、Float、Double、CGFloat)、数组、String 键字典及其 Optional 组合;非法声明(自定义值、IUO、嵌套 observable、重复 key)会让生成器报错停止,而不是产出有损 Swift; - 两阶段恢复:
POST /state/restore先让每个模型校验完整输入,全部通过后才在 MainActor 上执行赋值,避免恢复一半的坏状态; - 构建防泄漏:
Package.swift用.when(configuration: .debug)从结构上禁止 Release 构建链接任何DebugBridge*target;正式发布前用/ios-clean(ios-clean/SKILL.md)移除依赖并剥掉#if DEBUG接线。
版本防错(409 schema_mismatch):快照信封中携带 _accessor_hash——访问器代码的哈希。若某份快照是在旧版 app 构建上抓的,新构建恢复它会大声地以 409 schema_mismatch 拒绝,而不是静默写坏状态(见 CHANGELOG.md 中 iOS 能力的发布记录)。这正是"重建后快照失效"这一高频故障的防线,也是下一节故障表的来源。
五、技能运行时契约:共享前导与收尾
/ios-fix 与其他 gstack 技能一样,执行前有一段共享 Preamble(由 SKILL.md.tmpl 中的 {{PREAMBLE}} 占位符在生成时注入,SKILL.md 头部注明 "AUTO-GENERATED from SKILL.md.tmpl — do not edit directly",重新生成命令为 bun run gen:skill-docs)。前导程序按序完成:
- 更新检查与会话登记:运行
gstack-update-check,在~/.gstack/sessions/落一个以$PPID命名的会话文件并清理 120 分钟前的陈旧文件; - 环境探测:读取
proactive、skill_prefix、telemetry、explain_level、question_tuning、update_check、checkpoint_mode/checkpoint_push等配置,检测当前分支、会话类型(spawned/headless/interactive,非法值回退interactive)、Conductor 宿主(此时决策以 prose 渲染而非调用工具)、plan-mode 状态(GSTACK_PLAN_MODE=active/inactive,默认 inactive 是安全回退); - 遥测与学习注入:telemetry 非 off 时向
~/.gstack/analytics/skill-usage.jsonl追加一条{"skill":"ios-fix",...}记录;加载本项目的learnings.jsonl(超过 5 条时自动检索 top 3 相关学习);向 timeline 记录started事件; - 一次性引导(各带标记文件,只问一次):首次运行按项目类型给一句话建议(
greenfield/code_ios/branch_ahead等 token 映射);"Boil the Ocean" 完整性原则介绍;telemetry 三档选择(community/anonymous/off);CLAUDE.md 技能路由规则注入;vendored gstack 迁移提醒;spawned 会话则全部静默、自动选择推荐项。
工作流收尾时的契约包括:
- Artifacts Sync 尾部:运行
gstack-brain-sync --discover-new与--once,把本地产物(计划、报告等)按配置同步到 GBrain/artifacts 仓库; - 持续检查点(
checkpoint_mode=continuous时):完成一个逻辑单元即以WIP:前缀自动提交,提交信息内嵌[gstack-context]块记录 Decisions/Remaining/Tried,/ship时再压平成干净提交; - 完成状态协议:以
DONE/DONE_WITH_CONCERNS/BLOCKED/NEEDS_CONTEXT之一收尾;三次尝试失败、安全敏感不确定变更时按STATUS / REASON / ATTEMPTED / RECOMMENDATION格式升级; - 经验沉淀:结束前强制回顾可持久学习的坑(项目怪癖、命令修正、可节省 5 分钟以上的模式),用
gstack-learnings-log记录;确无可记时显式声明 "No durable learnings this session"; - 遥测收尾:
PLAN MODE EXCEPTION — ALWAYS RUN,写本地 timelinecompleted事件与本地 analytics;远端遥测仅当用户 opt-in 且二进制存在时执行。
六、故障模式与处理动作
原文档(ios-fix/SKILL.md)给出的故障模式表完整保留如下:
| 症状 | 处理动作 |
|---|---|
| 3 轮迭代后 bug 仍在 | STOP,把当前最优假设报告给用户 |
重建后 /state/restore 返回 409 schema_mismatch |
重新生成访问器(swift run gen-accessors),重新抓快照 |
| 修复中途设备断连 | daemon 自动重连;从 Phase 4 恢复执行 |
| 构建失败 | 回滚 Swift 改动;先排查编译错误再重新应用修复 |
补充两个来自 /ios-qa 故障表的相邻情况,/ios-fix 流程同样会遇到:409 schema_mismatch 的根因是"快照抓自旧版 app 构建",标准动作是丢弃旧快照重新抓取;413 body_too_large 表示快照超过 1MB 上限,需要调大 daemon 的 --max-body 或裁剪快照字段(见 ios-qa/SKILL.md 故障表)。gen-accessors 本身有 Swift 工具插件与 TS 回退双实现(ios-qa/scripts/gen-accessors.ts),缓存键为 sha256(source || swift_version || tool_git_rev || platform_triple)——所以 Swift 版本变化、生成器自身 git rev 变化、源码变化都会使缓存失效,这解释了为什么"重建后快照 hash 不匹配"是跨版本升级时的预期行为而非异常。
七、运行前提与适用边界
要跑通 /ios-fix 全闭环,仓库文档给出的硬件/软件前提是(docs/howto-ios-testing-with-gstack.md):
- macOS + Xcode 16.0+(
xcrun devicectl --version可用,CoreDevice 隧道依赖 Xcode 16); - iOS 16+ 真机,已解锁、已配对、开启 Developer Mode;
- Apple 开发者团队(免费个人团队即可);
- gstack 已安装(
./setup完成,gstack-ios-qa-regen与gstack-ios-qa-daemon在 PATH 上),Bun 运行时在 PATH 上; - 回归测试仅在
GSTACK_HAS_IOS_DEVICE=1的环境执行,无设备环境只验证非真机部分; - iOS 17 及以下的设备存在 SwiftUI Button 点击不生效的平台限制(
_UIHitTestContext缺失),/tap会返回ok:true但手势不触发,需要 iOS 18+ 或改用 UIKit 控件。
从仓库结构看,该技能的模板与生成物一一对应(ios-fix/SKILL.md.tmpl → gen:skill-docs → ios-fix/SKILL.md),修改工作流时应当改模板而非生成物;其五阶段协议、铁律与故障表是"可复制的操作规程",而 daemon 端点表、权限分层、快照哈希校验则给出了这套规程能在真机上确定性执行的底层保证。
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 StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00