Pake /bugs 技能实战:主动式潜在缺陷扫描方法论——从修复历史到边界证据
Pake 仓库内置了一个名为 bugs 的 Agent 技能(.agents/skills/bugs/SKILL.md),它解决的不是"用户报了 bug 怎么办",而是"在用户报告之前,如何主动把潜伏缺陷找出来"。本文以该技能文档为主体,完整还原它的六步扫描方法——选热点、读修复历史、扫边界、拿证据、按影响面排序、补测试守卫,并结合 AGENTS.md 的高温热点地图与 src-tauri 下的真实实现代码,说明每一步为什么这样设计。读完后你能掌握一套可复制的"上线前隐患排查"工作流,并理解 Pake 这类 Tauri + 注入 JS 项目中缺陷的主要形态。
为什么需要"主动找",以及找什么
技能文档开篇即给出定位:/hunt 需要症状(symptom)作为输入,/check 需要 diff 作为输入,而 /bugs 两者都不需要——它直接"出发去找"。
更关键的是对 Pake 缺陷形态的判断。文档基于仓库的修复历史指出:Pake 占主导地位的缺陷不是崩溃,而是"错误但看似合理的行为"(wrong-but-plausible behavior)。原文列举了四种典型形态:
- SPA 路由被误判为文件下载;
- 菜单命令打到了错误的窗口;
- 页面就绪前显示了空白外壳(blank shell);
- 某个开关在某一平台上实际是空操作(no-op)。
"什么都不会 panic,用户只是拿到一个比浏览器更差的桌面应用。"因此,在 Pake 里找 bug 的正确问法从来不是"这段代码会崩溃吗",而是:
当页面是 SPA 路由、窗口不是主标签名、document 是错误页/空白外壳、点击带有修饰键、或者平台忽略这个 Chromium flag 时,这段代码会做什么?
方法论的核心动作就一句:瞄准边界(boundary),而不是文件。
第一步:选定扫描面(Pick the Surface)
全文仓库范围的扫描如果没有预算约束,只会产出猜测。技能要求:命名一个区域和一个深度,挑一个热点深挖。扫描面不要凭空发明,应从 AGENTS.md 的 Hotspot Map(Current Risk Areas + 下表)出发。
文档自带的热点地图(Hotspot 表)如下,这是本文的核心继承内容之一:
| 热点(Hotspot) | 主要路径(Primary paths) | 锁定测试(Locked tests,示例) |
|---|---|---|
| 链接/下载启发式(Link / download heuristics) | src-tauri/src/inject/event.js |
event-link-guard.test.js、download-http-status.test.ts |
| 下载成功语义(Download success semantics) | src-tauri/src/app/invoke.rs、window.rs 的 on_download |
download-http-status.test.ts |
| 菜单/聚焦窗口(Menu / focused window) | src-tauri/src/app/menu.rs |
menu-focused-window.test.ts |
| 启动可见性(Startup visibility) | src-tauri/src/lib.rs、setup.rs |
startup-window-reveal.test.ts |
| 认证/弹窗(Auth / popup) | inject/auth.js、inject/event.js |
auth-sso-patterns.test.js、new-window-macos.test.js |
| 剪贴板(Clipboard) | inject/event.js |
event-clipboard-shortcuts.test.js |
| 多窗口/图标(Multi-window / icon) | window.rs、setup.rs |
window-icon-reapply.test.ts、startup-window-reveal.test.ts |
| 平台假能力(Platform fake capability) | cert.rs、proxy、lib.rs 中的 WebKit flags |
macos-proxy-feature.test.ts、Linux flag 单元测试 |
| CLI/配置契约(CLI / config contract) | bin/、schema/pake.schema.json |
config-file.test.ts、cli-options.test.ts |
这里有一个必须强调的使用前提,文档原文明确警告:AGENTS.md Hotspot Map 的第三列是回归风险(regression risk),不是"现存 bug 清单"。在把某一行当作实际缺陷处理之前,必须对照 Current Risk Areas 和上表中的测试自行确认。
上表所列的锁定测试在当前仓库中真实存在,例如 tests/unit/event-link-guard.test.js、tests/unit/download-http-status.test.ts、tests/unit/menu-focused-window.test.ts、tests/unit/startup-window-reveal.test.ts、tests/unit/event-clipboard-shortcuts.test.js、tests/unit/auth-sso-patterns.test.js、tests/unit/window-icon-reapply.test.ts——这些测试就是历史上各条边界修复后的"守卫",也是扫描时判断"该边界当前是否守住"的现成证据。
一处需要说明的细节:表格中"Platform fake capability"行引用的 cert.rs 未出现在当前目录树中;对照 AGENTS.md 热点地图的同一行("Platform capability: auth.rs, proxy, WebKit flags"),可推断该引用对应的是当前的 src-tauri/src/app/auth.rs。使用文档中的路径时,以仓库现状为准。
第二步:先读该区域的修复历史(Read the Area's Fix History)
进入任何区域之前,技能要求先用 git 历史读一遍"这个模块过去怎么修过":
git log --oneline --grep='^fix' -i -- <path>
git log --pretty=format:'%h %s%n%b' --grep='^fix' -i -- <path> | head -200
(<path> 替换为选定热点的路径。)
文档给出的解释是:bug 在模块内按"形状"复发(Bugs recur by shape within a module)。有两条值得行动的信号:
- 一条由三个或以上提交组成的修复链,且每个提交都在"补完"前一个——意味着大概率还有第四个兄弟 bug 潜伏着。文档列举了 Pake 仓库中真实存在的修复链,均可在 git 历史中复核:
- 下载路径启发式:
/releases/→/assets/→ 下一个 SPA 根。仓库中可见fix: stop treating /assets/ SPA routes as downloads等提交; - 启动可见性:blank → about:blank → 用户取消。仓库中可见
fix: reveal startup window after initial page load→fix: skip about:blank when revealing startup window→fix: cancel startup reveal after user visibility control的连续演进; - 菜单目标:
"pake"硬编码 → 聚焦窗口 → 剩余的硬编码。仓库中可见fix: target macos menu commands at focused window等提交。
- 下载路径启发式:
- 某个提交的正文里写明"上一次修复不完整"——意味着该模式的 grep 已经漏网一次,必须重新跑一遍。
这一步把"找 bug"变成了"顺着已知形状找同类",是后续边界扫描能高效命中的前提。
第三步:按序扫描边界(Sweep the Boundaries)
文档按"历史产出率"从高到低排定了扫描顺序,并为每条边界规定了要问的问题和代码位置。这张表是技能的操作核心,完整继承如下:
| 边界(Boundary) | 要问的问题(What to ask) | 代码位置(Where it lives) |
|---|---|---|
| 下载/导航启发式 | 这个路径/扩展名下的 SPA 路由会不会被拦截成下载?应优先用扩展名 + download 属性 + query 提示,而不是宽泛的路径根。 |
inject/event.js |
| 成功 vs 传输层 | HTTP 非 2xx、空 body、文件缺失时,会不会仍然 toast"成功"? | invoke.rs、on_download |
| 窗口身份 | 这条路径是否在用户可能处于 pake-N 或聚焦窗口时硬编码了 "pake"? |
menu.rs、invoke.rs、setup.rs、window.rs |
| 死页面上的 eval | 这个菜单/快捷键需要页面 JS 上下文吗?错误页和空白外壳没有上下文;应优先用原生 reload / navigate / 平台 history。 |
menu.rs |
| 启动 vs 用户控制 | 页面加载或兜底逻辑会不会把用户已经隐藏的窗口重新显示出来?每一条用户可见性路径都要上闩(latch)。 | lib.rs、setup.rs |
| 认证/弹窗 | macOS 认证是否仍会崩溃、卡在 about:blank、或把 SSO 甩给系统浏览器?Apple Sign-In 保持原生 popup。 | auth.js、event.js |
| 剪贴板 | keydown 会不会抢走原生粘贴(图片/文件)?兜底是否以可信 keyup + TTL 为门控? | event.js |
| 平台能力 | 这个 flag 在 WKWebView / WebView2 / WebKitGTK 上是真实能力,还是 Chromium-only 的空操作? | cert.rs(见上文对照 auth.rs)、window.rs、lib.rs |
| 配置双轨 | 配置文件能不能夹带一个 CLI flag 会拒绝的取值? | bin/helpers/merge.ts、schema |
对通用形状(fail-open 守卫、恢复逻辑以其恢复的产物为门控、watchdog 只按快路径调参)——文档明确指向 /hunt 技能的 Recurring Failure 模式,不在此处重新推导那份目录,避免两处漂移。
上面的边界并不是纸面假设,可以在仓库源码中逐一对照。举三处已验证的实现:
1. 下载/导航启发式(src-tauri/src/inject/event.js)。 源码中直接留有"踩坑记录"注释:路径片段白名单被刻意收窄为 const DOWNLOAD_PATH_PATTERNS = ["/download/"](约 L451-L456),注释说明 /assets/、/dist/、/files/、/releases/ 等 SPA 根"已经在野外被误判为下载",应优先使用真实文件扩展名、download 属性和 ?download / ?attachment query 提示。修饰键的语义同样被固化:注释明确 "Cmd/Ctrl+click is a browser 'open related' gesture, not 'save as'",判定下载仅看 download 属性与可下载文件启发式(isDownloadRequired,约 L714-L715),修饰键不得改写普通导航。这正是"下载/导航启发式"边界在源码中的落地形态。
2. 窗口身份(src-tauri/src/app/window.rs)。 on_download 处理器(约 L637-L681)在下载完成时弹 toast,目标是"发起下载的那个窗口(含 pake-N 二级窗口),而不是硬编码的 pake":
let toast_window = download_handle
.get_webview_window(webview.label())
.or_else(|| download_handle.get_webview_window("pake"));
先按事件来源 webview 的 label 解析、再回退到主窗口——这就是"窗口身份"边界的正确解法,也是 tests/unit/download-http-status.test.ts 等测试要锁住的形状。
3. 死页面上的 eval(src-tauri/src/app/menu.rs)。 菜单命令不再直接对固定标签名操作,而是先解析聚焦窗口(focused_webview_window,约 L251-L255,通过 window.is_focused() 查找),Reload 走原生 reload_window(注释:"Native reload works on blank error pages where eval cannot",约 L282-L285)。这同时覆盖了"菜单/聚焦窗口"与"死页面上的 eval"两条边界,由 tests/unit/menu-focused-window.test.ts 锁定。
AGENTS.md 的 Current Risk Areas 中还补充了扫描时应核实的不变量,例如:注入的 Linux/Windows 快捷键(Ctrl+R / [ / ])必须走 webview_navigate IPC 而非页面 history/location;每条隐藏→显示的 window.show() 路径必须调用 window.rs 的 reapply_window_icon(该辅助函数同时重设小图标与任务栏大图标);Linux/Windows 剪贴板桥接必须门控在 isNonMacDesktop() 与 event.isTrusted 上,且 Ctrl+V 必须放行原生粘贴以保留图片/文件等富格式。
第四步:报告前先确认(Confirm Before Reporting)
这是技能文档中最硬的纪律:一个候选项在被本轮(this turn)产出的证据证实之前,不算发现(finding)。文档把结论分为三档:
- Confirmed(已确认):探针(probe)、在当前代码上失败的单元测试、或一条带具名触发器的完整源码追踪;
- Plausible(可信但未验证):失败路径已被具名,但什么都没运行。必须单列,并写明"哪个探针能一锤定音";
- Not a finding(不算发现):任何仅凭函数名或"看起来有风险"的推断。要么 grep 实现,要么弃掉。
文档还给出了 Pake 场景下能"定案"的 oracle(判定源):tests/unit/ 下的单元测试、纯 Rust 辅助函数的 cargo test、真实打包应用的冷启动(用于空白窗口类断言)、以及在浏览器里打开真实站点验证"这个路径到底是导航还是下载"。
另有一条前置检查:在标记任何"看起来危险"的调用之前,先确认它是生产代码——#[test] 里的 unwrap、字符串字面量、构建脚本里的可疑写法都不是缺陷。
第五步:按影响面排序(Rank by Blast Radius)
确认后的发现不按"发现顺序"报告,而按用户影响面降序排列:
- 用户据此操作的静默错误导航或下载(SPA 路由被劫持、认证流程卡死);
- 无恢复路径的卡死状态(错误页、死菜单、被闩住的隐藏);
- 多窗口/打错目标的动作;
- 错误但可见(toast 弹在错误窗口、空白闪烁);
- 纯外观问题。
这个排序直接对应了 Pake 的产品形态:它把网页包装成桌面应用,"看似合理但错了"的行为会直接被用户当作"这个应用坏了",而崩溃反而是好修的。
第六步:给修好的东西上守卫(Guard What Gets Fixed)
任何被确认并修复的问题,都需要一个"在未修复代码上会失败"的测试,通常放在 tests/unit/ 下。对一整类 bug,要守卫模式(源码内省、parity 检查或纯辅助函数),而不是只锁一个 URL 字符串。
验证命令:
npx vitest run
cargo test
cargo clippy --all-targets -- -D warnings
当改动触及 bin/(CLI 源码目录)时,还要执行 pnpm run cli:build 并提交重新生成的 dist/cli.js——AGENTS.md 说明 dist/cli.js 是随包发布的 CLI 构建产物,Rollup 会把整个 package.json 内联进产物,因此 bin/ 或 package.json 的任何变化都会让旧产物过期。
硬规则(Hard Rules)
技能用五条硬规则收尾,它们定义了这次扫描的边界与报告伦理:
- 没有本轮证据就没有发现。 把猜测包装成发现,浪费的 review 时间比省下的更多。
- 命中一个原型(archetype)就意味着扫全仓库找它的签名。 grep 形状而不是字面文本,报告格式为"检查了 N 处,M 处有缺陷,K 处不适用"。
- 扫描时不修。 先收集,后修复。边扫边修会丢失扫描的全局视角。
- 干净的边界也要如实报告为干净。 检查过且守住的边界本身就是结果,不要为了证明这次运行有价值而制造发现。
- 命名区域之外的发现只列出、不修,除非维护者同意。
输出模板:一次标准扫描的报告格式
技能规定了统一的结构化输出,便于结果可比对、可审计:
Area: [扫描了什么区域、到什么深度]
Boundaries: [走查了 N 条边界,M 条适用]
Confirmed (severity order):
1. [file:line] [一句话说清缺陷]
Evidence: [探针输出 / 失败测试 / 测量]
Blast: [用户会看到什么]
Plausible (needs a probe):
1. [file:line] [缺陷] -> [能一锤定音的探针]
Swept clean:
- [边界]: [检查了什么、为什么成立]
Sibling sweep: [模式签名] -> [N 处检查,M 处缺陷,K 处不适用]
最后一行要说明:本次有修复动作,还是纯扫描(scan-only)。
小结:这套方法的可迁移性
回看整个流程——选热点(用 AGENTS.md 的风险地图而非直觉)、读修复历史(找"按形状复发"的链条)、按序扫边界(每条边界一个可问的问题)、证据分级(Confirmed / Plausible / Not a finding)、按影响面排序、修复即守卫(模式级测试)、以及"扫描不修、干净也报告"的纪律——它的价值不止于 Pake。任何"注入 JS + 原生壳 + 多平台 WebView"的项目,其缺陷主导形态几乎都是"错误但看似合理"而非崩溃,这套以边界为靶心的扫描方法可以直接迁移。
对 Pake 仓库本身,这份技能文档与其说是给 Agent 的操作手册,不如说是一份活的风险台账:它把 git log 里的修复历史(/assets/ SPA 路由、about:blank reveal、"pake" 硬编码这三条真实修复链)、AGENTS.md 的 Current Risk Areas 不变量、src-tauri 中 event.js / window.rs / menu.rs 的现行实现、以及 tests/unit/ 下九个锁定测试串成了一条从"历史缺陷"到"当前守卫"的可追溯证据链。
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 StartedRust0623
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