首页
/ Pake /bugs 技能实战:主动式潜在缺陷扫描方法论——从修复历史到边界证据

Pake /bugs 技能实战:主动式潜在缺陷扫描方法论——从修复历史到边界证据

2026-09-04 11:11:17作者:晏闻田Solitary

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.jsdownload-http-status.test.ts
下载成功语义(Download success semantics) src-tauri/src/app/invoke.rswindow.rson_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.rssetup.rs startup-window-reveal.test.ts
认证/弹窗(Auth / popup) inject/auth.jsinject/event.js auth-sso-patterns.test.jsnew-window-macos.test.js
剪贴板(Clipboard) inject/event.js event-clipboard-shortcuts.test.js
多窗口/图标(Multi-window / icon) window.rssetup.rs window-icon-reapply.test.tsstartup-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.tscli-options.test.ts

这里有一个必须强调的使用前提,文档原文明确警告:AGENTS.md Hotspot Map 的第三列是回归风险(regression risk),不是"现存 bug 清单"。在把某一行当作实际缺陷处理之前,必须对照 Current Risk Areas 和上表中的测试自行确认。

上表所列的锁定测试在当前仓库中真实存在,例如 tests/unit/event-link-guard.test.jstests/unit/download-http-status.test.tstests/unit/menu-focused-window.test.tstests/unit/startup-window-reveal.test.tstests/unit/event-clipboard-shortcuts.test.jstests/unit/auth-sso-patterns.test.jstests/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)。有两条值得行动的信号:

  1. 一条由三个或以上提交组成的修复链,且每个提交都在"补完"前一个——意味着大概率还有第四个兄弟 bug 潜伏着。文档列举了 Pake 仓库中真实存在的修复链,均可在 git 历史中复核:
    • 下载路径启发式:/releases//assets/ → 下一个 SPA 根。仓库中可见 fix: stop treating /assets/ SPA routes as downloads 等提交;
    • 启动可见性:blank → about:blank → 用户取消。仓库中可见 fix: reveal startup window after initial page loadfix: skip about:blank when revealing startup windowfix: cancel startup reveal after user visibility control 的连续演进;
    • 菜单目标:"pake" 硬编码 → 聚焦窗口 → 剩余的硬编码。仓库中可见 fix: target macos menu commands at focused window 等提交。
  2. 某个提交的正文里写明"上一次修复不完整"——意味着该模式的 grep 已经漏网一次,必须重新跑一遍。

这一步把"找 bug"变成了"顺着已知形状找同类",是后续边界扫描能高效命中的前提。

第三步:按序扫描边界(Sweep the Boundaries)

文档按"历史产出率"从高到低排定了扫描顺序,并为每条边界规定了要问的问题和代码位置。这张表是技能的操作核心,完整继承如下:

边界(Boundary) 要问的问题(What to ask) 代码位置(Where it lives)
下载/导航启发式 这个路径/扩展名下的 SPA 路由会不会被拦截成下载?应优先用扩展名 + download 属性 + query 提示,而不是宽泛的路径根。 inject/event.js
成功 vs 传输层 HTTP 非 2xx、空 body、文件缺失时,会不会仍然 toast"成功"? invoke.rson_download
窗口身份 这条路径是否在用户可能处于 pake-N 或聚焦窗口时硬编码了 "pake" menu.rsinvoke.rssetup.rswindow.rs
死页面上的 eval 这个菜单/快捷键需要页面 JS 上下文吗?错误页和空白外壳没有上下文;应优先用原生 reload / navigate / 平台 history。 menu.rs
启动 vs 用户控制 页面加载或兜底逻辑会不会把用户已经隐藏的窗口重新显示出来?每一条用户可见性路径都要上闩(latch)。 lib.rssetup.rs
认证/弹窗 macOS 认证是否仍会崩溃、卡在 about:blank、或把 SSO 甩给系统浏览器?Apple Sign-In 保持原生 popup。 auth.jsevent.js
剪贴板 keydown 会不会抢走原生粘贴(图片/文件)?兜底是否以可信 keyup + TTL 为门控? event.js
平台能力 这个 flag 在 WKWebView / WebView2 / WebKitGTK 上是真实能力,还是 Chromium-only 的空操作? cert.rs(见上文对照 auth.rs)、window.rslib.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.rsreapply_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)

确认后的发现不按"发现顺序"报告,而按用户影响面降序排列:

  1. 用户据此操作的静默错误导航或下载(SPA 路由被劫持、认证流程卡死);
  2. 无恢复路径的卡死状态(错误页、死菜单、被闩住的隐藏);
  3. 多窗口/打错目标的动作;
  4. 错误但可见(toast 弹在错误窗口、空白闪烁);
  5. 纯外观问题。

这个排序直接对应了 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-taurievent.js / window.rs / menu.rs 的现行实现、以及 tests/unit/ 下九个锁定测试串成了一条从"历史缺陷"到"当前守卫"的可追溯证据链。

登录后查看全文
热门项目推荐
相关项目推荐