在应用修复之前找出错误的根本原因,适用于错误、崩溃、回归、测试失败、行为异常以及截图报告的缺陷。当用户以任何语言报告错误、崩溃、行为异常、回归、测试失败、截图证据或之前正常但现在失败的情况时使用。不适用于代码审查或新功能。
Hunt:先诊断再修复
在首行前加上 🥷 内联标记,不要单独成段。
更新检查(非阻塞)。 每次对话运行一次 bash <skill-base-dir>/scripts/check-update.sh,将 <skill-base-dir> 替换为本技能的基目录;输出任何打印的行,否则静默继续(如果脚本已运行、缺失或出错也静默)。它每天最多检查一次,只读取公共版本文件,不发送任何数据。
对症状打补丁会在其他地方引入新错误。
结果约定
- 结果:在应用任何修复之前确定根本原因。
- 完成条件:一句话解释原因,所有观察到的症状都符合该原因,并且修复或交接已通过可重现的检查验证。
- 证据:源码追踪、重现命令或 UI 路径、日志或状态、针对性测试/构建输出,以及 UI 或原生缺陷的运行时证据。
- 输出:根本原因、修复或交接、验证结果,以及任何未清理的同类风险。
在能用一句话陈述根本原因之前不要碰代码:
"我认为根本原因是 [X],因为 [证据]。"
指明具体的文件、函数、行或条件。"状态管理问题"不可测试。"useUser 中 src/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",或用户记得某个特定的好提交或版本。
- 首先保护用户的工作树:运行
git status --short --branch -uall。如果存在修改、暂存或未跟踪的文件,不要在当前的检出中二分查找。从同一 HEAD 创建临时分离工作树,在那里运行二分查找,完成后运行git bisect reset并删除临时工作树。如果无法创建临时工作树,停止并请求明确的清理/暂存批准。 - 查找候选好标签:
git tag --sort=-version:refname | head -10或询问用户最后一个已知的好提交。
1b. 如果最后一个好版本只差一两个版本,先直接运行git diff <last-good-tag>..HEAD -- <suspect path>并读取差异。回归通常在该差异中可见,阅读它比驱动完整二分查找成本低得多。仅当差异太大或罪魁祸首不明显时才进入二分查找。 - 在开始二分查找前定义非交互的通过/失败测试命令。没有可重现的检查,二分查找毫无价值。
- 运行:
git bisect start && git bisect bad HEAD && git bisect good <tag-or-hash> - 每一步二分查找会检出一个提交。运行测试命令。标记:
git bisect good或git bisect bad。 - 让二分查找驱动。除非明确要求,不要跳转或跳过提交。
- 当二分查找命名了罪魁祸首提交时,只读取该差异。识别引入回归的具体行。
- 完成后运行
git bisect reset。
大文件只读一次,并从笔记中引用,而不是在每一步二分查找中重新读取。
重复回归 / 截图参考模式
在以下情况激活:用户说修复后相同问题仍然存在,提供"好"的截图/版本/文件,或描述之前正确的视觉结果。
将参考视为证据,而非装饰:
- 列出每个报告和可见的症状,在有用时保留用户的具体措辞("仍然慢"、"不清楚"、"尖刺"、"先显示上一个内容")。
- 识别参考基准:最后一个好提交/标签、旧构建、测试夹具、截图、下载的工件或用户描述中的预期状态。
- 在编辑前定义通过/失败检查。对于视觉错误,这可能是一个窄截图检查清单加上渲染视图的命令;对于行为错误,优先选择自动化回归测试或确定性重现。
- 比较当前与参考,并命名确切的差异。当证据指向渲染中断、竞态、字体管线或状态路径时,不要将视觉缺陷泛化为"样式打磨"。
- 如果一次尝试修复后相同症状仍然存在,停止并从证据重建假设。不要在被推翻的解释上堆叠更多补丁。
如果问题纯粹是主观 UI 品味,路由到 /ui。如果是渲染、状态、时序、构建输出、字体生成或已知好版本的回归,留在 /hunt。
范围爆炸模式
在修复根本原因模式后、声明错误完成前激活;当用户说"举一反三"、"举一反三深入看看"或"其他地方有没有同样问题"时也激活。相同的形状通常隐藏在 N 个其他地方;一个忽略爆炸范围的局部修复会在树中留下 N - 1 个错误。
- 提取模式签名:产生错误的具体函数名、正则表达式、API 调用、CSS 选择器、锁获取、验证跳过或输入边界。
- 在仓库中
grep -rn <pattern>(排除生成目录、构建输出、供应商依赖)。对于错误类模式(例如"任何缺少锁的处理程序"),搜索周围形状,而不仅仅是字面文本。 - 列出每个匹配。对每个匹配,书面回答:此处有相同错误?选择修复/保留(解释为什么安全)/不确定(询问用户)。不要静默跳过匹配。
- 在输出块中包含爆炸报告之前,不要声称"已修复"。
常见触发条件:
- 在一个页面上修复了视觉错误:检查使用相同组件、布局或媒体查询断点的每个其他页面。
- 在一个处理程序中修复了一个竞态:检查获取相同锁或接触相同共享状态的每个处理程序。
- 在一个入口点修补了一个验证跳过:检查到达相同下游接收器的每个入口点。
- 为一个输入形状修复了一个正则表达式/解析器:检查相同正则表达式/解析器的每个调用者。
如果爆炸暴露出无关的错误,列出它们,但除非用户同意,不要在此 PR 中修复;范围蔓延本身就是反模式。
确认或丢弃
运行一个如果假设错误就会失败的探针,然后读取结果。如果证据与假设矛盾,完全丢弃它,并根据探针显示的内容重新定位。不要在被推翻的假设上堆叠修复,也不要仅仅因为代码"看起来像"原因就保留假设。
运行时证据阶梯
在声称错误已修复之前使用此阶梯:
- 源码追踪:命名能产生症状的确切函数、状态转换、文件、行或条件。
- 确定性重现:运行或编写产生它的最小命令、测试夹具、UI 路径或场景。
- 日志/状态/缓存:检查证明路径已到达的运行时状态,包括队列、数据库行、缓存、临时文件、生成输出或外部工具日志。
- 构建/测试:运行执行修复的窄测试或构建。
- 真实运行时检查:对于 UI、原生应用、浏览器、渲染或视觉错误,打开应用/页面/工件并通过截图或具体检查清单验证可见结果。
仅编译不足以验证 UI、原生应用、视觉、渲染或生成工件错误。如果运行时检查在环境中不可能,说明原因并交接确切的屏幕、命令或工件进行验证。
对于重复出现的失败类别,在添加第二个修复前加载 references/failure-patterns.md。
原生应用冻结模式
在以下情况激活:桌面或移动原生应用报告沙滩球、无响应、标签切换冻结、首次打开延迟、空闲唤醒停滞、覆盖层锁定,或截图显示冻结的应用。
在更改代码前收集的证据:
- 确切的用户路径和版本:首次启动与热启动、标签或窗口切换、空闲持续时间、权限、显示器数量,以及任何使冻结消失的设置。
- 冻结时的运行时捕获:
sample <process>、最近的应用日志、CPU 和内存占用、线程数,以及主线程是否阻塞、旋转或分配。 - 首帧表面:视图主体工作、第一个
.task、同步图标或元数据查找、文件系统扫描、URL 父级遍历、通知回调和应用/窗口唤醒处理程序。 - 修复后的爆炸搜索:在仓库中 grep 相同的 API 形状,特别是路径父级遍历、同步图标加载、渲染路径中的元数据读取,以及主线程上运行的回调。
常见的原生冻结陷阱:
- 启动、终止、权限、音频、显示或工作区通知在主线程上执行路径遍历、图标查找、文件系统扫描或进程枚举。
- 首帧绘制在显示交互式 shell 之前填充完整的应用列表、目录树、媒体缩略图集或系统状态表。
- 输入锁定或全屏覆盖层没有针对 Escape、应用停用、权限拒绝、进程终止和窗口关闭的保证拆卸路径。
- 定时器或采样器工作即使在隐藏窗口、长空闲期、休眠/唤醒或应用重新激活后仍然存在。
仅编译和仅源码检查对此模式不足。结果必须包括运行时捕获、根本原因帧或状态转换、聚焦的回归防护,以及任何已修复或明确保持安全的同类匹配。
针对性日志
将日志用作手术刀,而非噪音。在添加日志之前,写下它要回答的问题:
"如果此日志在 Y 之前打印 X,假设 A 仍有可能;如果没有,假设 A 错误。"
加载 references/logging-techniques.md 获取完整日志手册:二分搜索检测、区分性日志内容、边界优先放置、时序错误日志记录和移除纪律。
快速规则:
- 将第一个日志放在执行路径的中点,而不是症状处。从中点开始二分搜索。
- 仅记录区分性事实:序列号、输入键、分支选择、旧/新状态、错误码。
- 在完成前移除临时日志。将持久诊断隐藏在项目的调试标志后。
如果添加日志改变了行为,将其视为时序、生命周期或并发问题的证据。
陷阱
| 发生了什么 | 规则 |
|---|---|
| 修补了客户端面板而不是本地面板 | 在碰任何文件之前向后追踪执行路径 |
| 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. [假设 1] → [测试方法] → [结果:排除因为...]
2. [假设 2] → [测试方法] → [结果:排除因为...]
3. [假设 3] → [测试方法] → [结果:排除因为...]
已收集的证据:
- [日志片段 / 堆栈跟踪 / 文件内容]
- [重现步骤]
- [环境信息:版本、配置、运行时]
已排除:
- [已被消除的根本原因]
未知:
- [仍不清楚的内容]
- [缺失的信息]
建议的下一步:
1. [下一个调查方向]
2. [可能需要的外部工具或权限]
3. [用户应提供的额外上下文]
状态:阻塞






