hunt

hunt

热门

在应用修复之前找出错误的根本原因,适用于错误、崩溃、回归、测试失败、行为异常以及截图报告的缺陷。当用户以任何语言报告错误、崩溃、行为异常、回归、测试失败、截图证据或之前正常但现在失败的情况时使用。不适用于代码审查或新功能。

6382Star
0Fork
更新于 2026/7/10
SKILL.md
readonly只读
name
hunt
description

在应用修复之前找出错误的根本原因,适用于错误、崩溃、回归、测试失败、行为异常以及截图报告的缺陷。当用户以任何语言报告错误、崩溃、行为异常、回归、测试失败、截图证据或之前正常但现在失败的情况时使用。不适用于代码审查或新功能。

Hunt:先诊断再修复

在首行前加上 🥷 内联标记,不要单独成段。

更新检查(非阻塞)。 每次对话运行一次 bash <skill-base-dir>/scripts/check-update.sh,将 <skill-base-dir> 替换为本技能的基目录;输出任何打印的行,否则静默继续(如果脚本已运行、缺失或出错也静默)。它每天最多检查一次,只读取公共版本文件,不发送任何数据。

对症状打补丁会在其他地方引入新错误。

结果约定

  • 结果:在应用任何修复之前确定根本原因。
  • 完成条件:一句话解释原因,所有观察到的症状都符合该原因,并且修复或交接已通过可重现的检查验证。
  • 证据:源码追踪、重现命令或 UI 路径、日志或状态、针对性测试/构建输出,以及 UI 或原生缺陷的运行时证据。
  • 输出:根本原因、修复或交接、验证结果,以及任何未清理的同类风险。

在能用一句话陈述根本原因之前不要碰代码:

"我认为根本原因是 [X],因为 [证据]。"

指明具体的文件、函数、行或条件。"状态管理问题"不可测试。"useUsersrc/hooks/user.ts:42 的缓存过期,因为依赖数组缺少 userId" 是可测试的。如果不能如此具体,说明你还没有假设。

诊断信号

良好进展:日志行与假设匹配,能在运行前预测下一个错误,理解从根本原因到症状的传播路径,能编写一个在旧代码上失败的测试。在每个信号处,在提交前再找一个独立的证据。

假设质量门:在对假设采取行动前,列出所有可观察的症状(不仅仅是用户首先报告的那个)。假设必须解释所有症状;如果只覆盖部分,那只是症状层面的猜测,不是根本原因。对于时序相关的问题(闪烁、间歇性失败、竞态条件),在诊断前先可靠重现。

合理化警告:"我先试试这个"意味着没有假设,先写假设。"我很确定"意味着运行一个能证明它的工具。"可能是同一个问题"意味着从头重新阅读执行路径。"在我机器上没问题"意味着在排除前列举所有环境差异。"再重启一次"意味着逐字阅读最后一个错误;没有新证据时不要重启超过两次。

持久上下文预检

参见 references/durable-context.md 了解何时读取持久上下文、读取顺序预算和内存类型映射。

对于 /hunt:持久上下文只是假设的燃料,当前代码、日志和重现证据覆盖内存。它从不替代新的根本原因陈述或可重现的症状列表。

硬规则

  • 修复后相同症状是硬停止;"让我先试试这个"也是。 两者都意味着假设未完成。在再次碰代码前从头重新阅读执行路径。
  • 三次失败假设后停止。 使用下面的交接格式列出已检查的内容、已排除的内容和未知内容。询问如何继续。
  • 验证后再声明。 不要凭记忆陈述版本、函数名或文件位置。先运行 sw_vers / node --version / grep。无结果 = 重新检查路径。
  • 外部工具失败:先诊断再切换。 当 MCP 工具或 API 失败时,先确定原因(服务器运行?API 密钥有效?配置正确?)再尝试替代方案。
  • 系统/工具症状需要下层基线。 在归咎于可见应用、生成文件或顶层功能之前,先测量原始下层:操作系统捕获与后处理、运行时服务与 UI、编译器/工具链与测试断言、网络/API 与客户端处理。排除基线否定的假设,而不是绕圈子。
  • 注意回避。 当有人说"那部分不重要"时,将其视为信号。某人回避检查的区域往往是问题所在。
  • 视觉/渲染错误:先静态分析。 在添加 console.log 或视觉调试覆盖层之前,在 DevTools 中追踪绘制层、堆叠上下文和层顺序。日志无法捕获合成器所做的。仅在静态分析失败后才添加检测。
  • 行为/生命周期/异步错误:先检测,而不是失败后。 窗口生命周期、事件传递、导航、焦点、定时器、状态机和异步排序错误几乎无法仅通过静态阅读解决。不要等到修复失败才添加日志。一旦你的假设涉及"这个回调在之前/之后触发"、"这个状态在 Y 运行时应该是 X"或"这个对象在这里应该还活着",在形成假设时立即添加日志,在编写任何修复之前。没有运行时证据的假设是猜测;连续两次猜测就是硬停止信号。与视觉渲染错误(合成器行为需要 DevTools,而不是日志)和纯逻辑错误(错误公式、差一错误)区分,后者静态分析就足够了。
  • 调整魔法数字超过三次:停止,统一。 当间距/大小/阈值值调整了三次仍然不对时,错误是结构性的,不是数值性的。用命名标记(Spacing.s4--gap-content 等)替换 N 个独立值,并验证不对称性是否隐藏了缺失的约束。经调整仍存在的不对称性是结构性的;更多调整不会收敛。
  • 性能投诉需要数字。 对于"慢"、"卡顿"或内存增长报告(非原生应用冻结模式),先测量基线(挂钟时间、性能样本、内存占用),修复,然后重新测量并报告前后数字。"感觉更快了"不是证据。
  • 修复原因,而不是症状。 如果修复涉及超过 5 个文件,暂停并与用户确认范围。

修复范围纪律

如果错误确实需要先重构(例如,不更改共享接口就无法解决原因),暂停,明确命名重构并询问。不要静默捆绑。从错误修复演变成重构的修复应单独提交 PR。

二分查找模式

在以下情况激活:"以前是好的"、"之前是好的"、"used to work"、"上一次提交还是对的"、"broke after update",或用户记得某个特定的好提交或版本。

  1. 首先保护用户的工作树:运行 git status --short --branch -uall。如果存在修改、暂存或未跟踪的文件,不要在当前的检出中二分查找。从同一 HEAD 创建临时分离工作树,在那里运行二分查找,完成后运行 git bisect reset 并删除临时工作树。如果无法创建临时工作树,停止并请求明确的清理/暂存批准。
  2. 查找候选好标签:git tag --sort=-version:refname | head -10 或询问用户最后一个已知的好提交。
    1b. 如果最后一个好版本只差一两个版本,先直接运行 git diff <last-good-tag>..HEAD -- <suspect path> 并读取差异。回归通常在该差异中可见,阅读它比驱动完整二分查找成本低得多。仅当差异太大或罪魁祸首不明显时才进入二分查找。
  3. 在开始二分查找前定义非交互的通过/失败测试命令。没有可重现的检查,二分查找毫无价值。
  4. 运行:git bisect start && git bisect bad HEAD && git bisect good <tag-or-hash>
  5. 每一步二分查找会检出一个提交。运行测试命令。标记:git bisect goodgit bisect bad
  6. 让二分查找驱动。除非明确要求,不要跳转或跳过提交。
  7. 当二分查找命名了罪魁祸首提交时,只读取该差异。识别引入回归的具体行。
  8. 完成后运行 git bisect reset

大文件只读一次,并从笔记中引用,而不是在每一步二分查找中重新读取。

重复回归 / 截图参考模式

在以下情况激活:用户说修复后相同问题仍然存在,提供"好"的截图/版本/文件,或描述之前正确的视觉结果。

将参考视为证据,而非装饰:

  1. 列出每个报告和可见的症状,在有用时保留用户的具体措辞("仍然慢"、"不清楚"、"尖刺"、"先显示上一个内容")。
  2. 识别参考基准:最后一个好提交/标签、旧构建、测试夹具、截图、下载的工件或用户描述中的预期状态。
  3. 在编辑前定义通过/失败检查。对于视觉错误,这可能是一个窄截图检查清单加上渲染视图的命令;对于行为错误,优先选择自动化回归测试或确定性重现。
  4. 比较当前与参考,并命名确切的差异。当证据指向渲染中断、竞态、字体管线或状态路径时,不要将视觉缺陷泛化为"样式打磨"。
  5. 如果一次尝试修复后相同症状仍然存在,停止并从证据重建假设。不要在被推翻的解释上堆叠更多补丁。

如果问题纯粹是主观 UI 品味,路由到 /ui。如果是渲染、状态、时序、构建输出、字体生成或已知好版本的回归,留在 /hunt

范围爆炸模式

在修复根本原因模式后、声明错误完成前激活;当用户说"举一反三"、"举一反三深入看看"或"其他地方有没有同样问题"时也激活。相同的形状通常隐藏在 N 个其他地方;一个忽略爆炸范围的局部修复会在树中留下 N - 1 个错误。

  1. 提取模式签名:产生错误的具体函数名、正则表达式、API 调用、CSS 选择器、锁获取、验证跳过或输入边界。
  2. 在仓库中 grep -rn <pattern>(排除生成目录、构建输出、供应商依赖)。对于错误类模式(例如"任何缺少锁的处理程序"),搜索周围形状,而不仅仅是字面文本。
  3. 列出每个匹配。对每个匹配,书面回答:此处有相同错误?选择修复/保留(解释为什么安全)/不确定(询问用户)。不要静默跳过匹配。
  4. 在输出块中包含爆炸报告之前,不要声称"已修复"。

常见触发条件:

  • 在一个页面上修复了视觉错误:检查使用相同组件、布局或媒体查询断点的每个其他页面。
  • 在一个处理程序中修复了一个竞态:检查获取相同锁或接触相同共享状态的每个处理程序。
  • 在一个入口点修补了一个验证跳过:检查到达相同下游接收器的每个入口点。
  • 为一个输入形状修复了一个正则表达式/解析器:检查相同正则表达式/解析器的每个调用者。

如果爆炸暴露出无关的错误,列出它们,但除非用户同意,不要在此 PR 中修复;范围蔓延本身就是反模式。

确认或丢弃

运行一个如果假设错误就会失败的探针,然后读取结果。如果证据与假设矛盾,完全丢弃它,并根据探针显示的内容重新定位。不要在被推翻的假设上堆叠修复,也不要仅仅因为代码"看起来像"原因就保留假设。

运行时证据阶梯

在声称错误已修复之前使用此阶梯:

  1. 源码追踪:命名能产生症状的确切函数、状态转换、文件、行或条件。
  2. 确定性重现:运行或编写产生它的最小命令、测试夹具、UI 路径或场景。
  3. 日志/状态/缓存:检查证明路径已到达的运行时状态,包括队列、数据库行、缓存、临时文件、生成输出或外部工具日志。
  4. 构建/测试:运行执行修复的窄测试或构建。
  5. 真实运行时检查:对于 UI、原生应用、浏览器、渲染或视觉错误,打开应用/页面/工件并通过截图或具体检查清单验证可见结果。

仅编译不足以验证 UI、原生应用、视觉、渲染或生成工件错误。如果运行时检查在环境中不可能,说明原因并交接确切的屏幕、命令或工件进行验证。

对于重复出现的失败类别,在添加第二个修复前加载 references/failure-patterns.md

原生应用冻结模式

在以下情况激活:桌面或移动原生应用报告沙滩球、无响应、标签切换冻结、首次打开延迟、空闲唤醒停滞、覆盖层锁定,或截图显示冻结的应用。

在更改代码前收集的证据:

  1. 确切的用户路径和版本:首次启动与热启动、标签或窗口切换、空闲持续时间、权限、显示器数量,以及任何使冻结消失的设置。
  2. 冻结时的运行时捕获:sample <process>、最近的应用日志、CPU 和内存占用、线程数,以及主线程是否阻塞、旋转或分配。
  3. 首帧表面:视图主体工作、第一个 .task、同步图标或元数据查找、文件系统扫描、URL 父级遍历、通知回调和应用/窗口唤醒处理程序。
  4. 修复后的爆炸搜索:在仓库中 grep 相同的 API 形状,特别是路径父级遍历、同步图标加载、渲染路径中的元数据读取,以及主线程上运行的回调。

常见的原生冻结陷阱:

  • 启动、终止、权限、音频、显示或工作区通知在主线程上执行路径遍历、图标查找、文件系统扫描或进程枚举。
  • 首帧绘制在显示交互式 shell 之前填充完整的应用列表、目录树、媒体缩略图集或系统状态表。
  • 输入锁定或全屏覆盖层没有针对 Escape、应用停用、权限拒绝、进程终止和窗口关闭的保证拆卸路径。
  • 定时器或采样器工作即使在隐藏窗口、长空闲期、休眠/唤醒或应用重新激活后仍然存在。

仅编译和仅源码检查对此模式不足。结果必须包括运行时捕获、根本原因帧或状态转换、聚焦的回归防护,以及任何已修复或明确保持安全的同类匹配。

针对性日志

将日志用作手术刀,而非噪音。在添加日志之前,写下它要回答的问题:

"如果此日志在 Y 之前打印 X,假设 A 仍有可能;如果没有,假设 A 错误。"

加载 references/logging-techniques.md 获取完整日志手册:二分搜索检测、区分性日志内容、边界优先放置、时序错误日志记录和移除纪律。

快速规则:

  1. 将第一个日志放在执行路径的中点,而不是症状处。从中点开始二分搜索。
  2. 仅记录区分性事实:序列号、输入键、分支选择、旧/新状态、错误码。
  3. 在完成前移除临时日志。将持久诊断隐藏在项目的调试标志后。

如果添加日志改变了行为,将其视为时序、生命周期或并发问题的证据。

陷阱

发生了什么 规则
修补了客户端面板而不是本地面板 在碰任何文件之前向后追踪执行路径
MCP 未加载,切换了工具而不是诊断 在切换方法之前检查服务器状态、API 密钥、配置
在测量原始系统/工具层之前归咎于可见应用 先测量下层,然后明确排除被否定的假设
编排器说 RUNNING 但 TTS 供应商配置错误 在多阶段管道中,单独测试每个阶段
竞态条件被诊断为过期状态错误 对于时序敏感问题,在检查状态之前检查事件时间戳和顺序
到处添加日志仍然无法解释错误 将每个日志重写为是/否问题。删除不能排除或确认假设的日志
本地重现但 CI 失败 先对齐环境(运行时版本、环境变量、时区),然后追踪代码
堆栈跟踪深入库内部 回溯 3 帧到自己的代码;错误几乎总是在那里,而不是在依赖中
从应用启动时工作,通过文件关联/拖放/深度链接/外部代理打开时失败 使用用户描述的确切入口点重现。应用内部初始化与冷启动带文件初始化不同;文档到达时状态可能未就绪
构建通过但 UI 仍然看起来不对 向上移动运行时证据阶梯并验证真实的渲染表面或工件
修复匹配了报告者的设置但对其他人没有改变,或回归了默认值 缺陷报告是证据,不是完整范围。说明修复是否更改了所有用户的默认体验或仅报告者的配置,并优先修复默认路径
更改了算法但输出仍然错误 读取器可能命中旧代码写入的持久输出(扫描结果、分析缓存、带 TTL 的快照)。更改生成后持久化的数据需要在同一更改中失效或版本升级旧缓存;在重新诊断之前,确认运行时没有读取过期数据
报告者能重现,本地机器正常,代理盲目修补 先产生一个复制粘贴的诊断命令(单个命令、静默收集、一个输出文件、隐私说明),从返回的证据诊断,然后修复

渲染错误模式

在以下情况激活:"PDF 看起来不对"、"分页问题"、"字体未渲染"、PDF 输出损坏或打印布局错误。

加载 references/rendering-debug.md 获取完整诊断检查清单(WeasyPrint 怪癖、字体加载、页面溢出、浏览器打印 CSS)。先静态分析,必要时再重现。

IME / Unicode 问题

对于输入法、字符渲染或文本编码错误(IME 状态、光标漂移、表情符号分割、组合事件),在形成假设前先检查 references/ime-unicode.md

输出

成功格式

以一行普通文本开头,说明结果以及更改是否已提交;下面的块支持该行,不替换它。

根本原因:        [错误是什么,文件:行]
修复:             [更改了什么,文件:行]
同类扫描:         [检查了 N 个相同形状的站点,修复了 N 个 / 未找到 / 未运行,原因]
确认:             [证明修复的证据或测试]
测试:             [通过/失败计数,回归测试位置]
回归防护:         [测试文件:行] 或 [无,原因]

状态:已解决有条件的已解决(说明条件)或 阻塞(说明未知内容)。

回归防护规则:对于任何复发或之前"已修复"的错误,修复未完成直到:

  1. 存在一个回归测试,在未修复代码上失败,在修复代码上通过。
  2. 测试位于项目的测试套件中,而不是临时文件。
  3. 提交消息说明错误为何复发以及此修复为何能防止它。

交接格式(三次失败假设后)

症状:
[原始错误描述,一句话]

已测试的假设:
1. [假设 1] → [测试方法] → [结果:排除因为...]
2. [假设 2] → [测试方法] → [结果:排除因为...]
3. [假设 3] → [测试方法] → [结果:排除因为...]

已收集的证据:
- [日志片段 / 堆栈跟踪 / 文件内容]
- [重现步骤]
- [环境信息:版本、配置、运行时]

已排除:
- [已被消除的根本原因]

未知:
- [仍不清楚的内容]
- [缺失的信息]

建议的下一步:
1. [下一个调查方向]
2. [可能需要的外部工具或权限]
3. [用户应提供的额外上下文]

状态:阻塞