evidence-first-debugging

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Evidence-First Debugging

基于证据优先的调试方法

Use this skill when the user asks why something did not behave as expected, why a flow failed or stopped, whether a suspected cause is valid, or when you are about to write a root-cause hypothesis.
当用户询问某功能为何未按预期运行、工作流为何失败或中断、疑似原因是否成立,或是你准备撰写根本原因假设时,使用此技能。

Core Rules

核心规则

  1. Build the evidence chain before proposing a root cause.
  2. Runtime evidence beats static code inference. Static code shows what could happen; logs, traces, network data, screenshots, or reproducible output show what did happen.
  3. Keep speculation to one step. Do not chain "maybe A, therefore maybe B, therefore root cause C".
  4. If the user challenges your hypothesis, look for missing evidence first instead of immediately switching to another hypothesis.
  5. If evidence is insufficient, say so plainly and add the smallest useful temporary instrumentation before changing business logic.
  6. Do not use strong wording such as "confirmed", "locked", "definitive", or "closed evidence chain" unless the key runtime evidence has been checked.
  7. Validate write paths with write evidence. A read path that looks consistent with your hypothesis does not prove where data should be written. Reads may succeed through getters, fallbacks, proxies, prototype chains, lazy migration shims, or framework behavior that does not apply to writes. Before committing a write-path fix, find a working write call site, the deserialization/initialization path that proves where data lives at rest, or the persistence/save path that proves what the system later reads. If you only have read-path inference, label it as a current hypothesis.
  8. Treat temporary instrumentation as a lifecycle, not a deliverable. Add it to collect evidence, use it to confirm or reject the hypothesis, then remove it after the issue is fixed or the user confirms the issue is fixed unless there is an explicit decision to keep it behind a safe debug gate.
  1. 提出根本原因之前,先构建证据链。
  2. 运行时证据优先于静态代码推断。静态代码仅展示可能发生的情况;而日志、追踪信息、网络数据、截图或可复现的输出才是实际发生的情况。
  3. 猜测仅限一步。不要进行“可能是A,因此可能是B,所以根本原因是C”的链式推测。
  4. 如果用户对你的假设提出质疑,首先寻找缺失的证据,而非立即切换到另一个假设。
  5. 如果证据不足,直接说明情况,并在修改业务逻辑前添加最小化的有用临时监控代码。
  6. 除非已核实关键运行时证据,否则不要使用“已确认”“锁定”“确定”“完整证据链”等强硬措辞。
  7. 用写入证据验证写入路径。与假设一致的读取路径无法证明数据应写入的位置。读取操作可能通过getter、回退机制、代理、原型链、延迟迁移垫片或框架行为成功,而这些并不适用于写入操作。在提交写入路径修复前,找到有效的写入调用点、证明数据静态存储位置的反序列化/初始化路径,或是证明系统后续读取逻辑的持久化/保存路径。如果仅能通过读取路径推断,需将其标记为当前假设。
  8. 将临时监控代码视为生命周期环节,而非交付物。添加它是为了收集证据,用它来确认或推翻假设,问题修复后或用户确认问题已解决后将其移除,除非明确决定将其保留在安全的调试开关之后。

Evidence Table

证据表

Before giving a diagnosis, create or mentally maintain an evidence table. Show it to the user when the issue is non-trivial or when evidence is incomplete.
text
Evidence source | Checked? | Type | What it proves | Strength
User repro steps | yes/no | runtime artifact | ... | weak/medium/strong
Browser console/network/DOM | yes/no | runtime | ... | weak/medium/strong
Node.js/server log | yes/no | runtime | ... | weak/medium/strong
CLI/test/CI output | yes/no | runtime | ... | weak/medium/strong
Relevant code path | yes/no | static inference | ... | weak/medium/strong
Use these levels:
  • Confirmed fact: directly observed in runtime evidence or reproduced.
  • Strong inference: multiple evidence sources agree, but one direct signal is missing.
  • Current hypothesis: plausible from available evidence, but still needs validation.
  • Unverified assumption: do not use as the basis for a fix.
给出诊断之前,创建或在脑海中维护一张证据表。当问题复杂或证据不完整时,将其展示给用户。
text
证据来源 | 是否已核实? | 类型 | 证明内容 | 可信度
用户复现步骤 | 是/否 | 运行时产物 | ... | 低/中/高
浏览器控制台/网络/DOM | 是/否 | 运行时 | ... | 低/中/高
Node.js/服务器日志 | 是/否 | 运行时 | ... | 低/中/高
CLI/测试/CI输出 | 是/否 | 运行时 | ... | 低/中/高
相关代码路径 | 是/否 | 静态推断 | ... | 低/中/高
使用以下层级:
  • 已确认事实:在运行时证据中直接观察到或已复现的内容。
  • 强推断:多个证据来源一致,但缺少一个直接信号。
  • 当前假设:基于现有证据看似合理,但仍需验证。
  • 未验证假设:不能作为修复方案的依据。

Fix Gate

修复验证关卡

Before changing product logic, check whether the evidence supports the direction:
  • If a key runtime source is unchecked, prefer instrumentation over a speculative fix.
  • If only static code reading supports the hypothesis, label it as a hypothesis.
  • If the issue can be reproduced locally, reproduce it and capture output before editing.
  • If the issue cannot be reproduced, add temporary logs that the agent or user can retrieve after one reproduction.
  • If the fix changes a write, mutation, or persistence path, do not ship based only on a matching read path. Require either a working write-path analog in the same codebase, or a runtime/integration check proving the written data is observable through the system's actual read, save, or downstream consumption path.
  • If you added temporary instrumentation and the issue is later fixed or verified, remove the instrumentation before finishing unless the user explicitly wants to keep it. If kept, gate it, document why, and ensure it is safe for normal usage.
修改产品逻辑之前,检查证据是否支持修复方向:
  • 如果关键运行时来源未核实,优先添加监控代码而非尝试推测性修复。
  • 如果仅靠静态代码分析支持假设,需将其标记为假设。
  • 如果问题可在本地复现,先复现并捕获输出再进行编辑。
  • 如果问题无法复现,添加临时日志,以便代理或用户在下次复现后获取。
  • 如果修复涉及写入、变更或持久化路径,不要仅基于匹配的读取路径就发布修复。需要在同一代码库中找到有效的写入路径示例,或是通过运行时/集成检查证明写入的数据可通过系统实际的读取、保存或下游消费路径被观测到。
  • 如果添加了临时监控代码,问题修复或验证后需将其移除,除非用户明确要求保留。若保留,需添加调试开关、说明原因,并确保其在正常使用时是安全的。

Temporary Instrumentation

临时监控代码

When adding logs, optimize for evidence that a later agent can consume without asking the user to manually summarize it. Temporary instrumentation is encouraged when it closes an evidence gap, but it must be scoped, behavior-preserving, and cleaned up.
Instrumentation must:
  • Use a stable prefix or event name, such as
    [checkout-debug]
    .
  • Include timestamp and a correlation id when available: request id, session id, trace id, job id, route, or component name.
  • Capture branch decisions, input summary, output summary, validation results, stop/skip reasons, and error details.
  • Avoid secrets, tokens, cookies, authorization headers, raw PII, and huge payloads.
  • Prefer structured JSON or JSONL over prose.
  • Be easy to remove, downgrade behind a debug flag, or keep only in local/dev paths after the issue is fixed.
Follow this lifecycle:
  1. Add the smallest behavior-preserving instrumentation needed around the missing evidence.
  2. Collect evidence through a reproduction.
  3. Read and interpret the collected logs.
  4. Use the evidence to confirm, reject, or revise the hypothesis.
  5. Fix and verify the issue.
  6. Remove the temporary instrumentation after verification or after the user confirms the issue is fixed. Keep it only if there is an intentional debug-only decision.
添加日志时,优化证据格式以便后续代理无需用户手动总结即可使用。当临时监控代码能填补证据空白时鼓励使用,但必须限定范围、不影响原有行为,并在使用后清理。
监控代码必须:
  • 使用稳定的前缀或事件名称,例如
    [checkout-debug]
  • 包含时间戳和可用的关联ID:请求ID、会话ID、追踪ID、任务ID、路由或组件名称。
  • 捕获分支决策、输入摘要、输出摘要、验证结果、停止/跳过原因以及错误详情。
  • 避免记录密钥、令牌、Cookie、授权头、原始个人身份信息(PII)和大型负载。
  • 优先使用结构化JSON或JSONL而非散文式文本。
  • 便于移除、降级到调试开关之后,或在问题修复后仅保留在本地/开发路径中。
遵循以下生命周期:
  1. 在缺失证据的相关位置添加最小化、不影响原有行为的临时监控代码。
  2. 通过复现问题收集证据。
  3. 读取并解释收集到的日志。
  4. 使用证据确认、推翻或修正假设。
  5. 修复并验证问题。
  6. 验证后或用户确认问题已解决后移除临时监控代码。仅在有意保留调试专用代码的情况下才保留。

Node.js Logging Pattern

Node.js 日志模式

For Node.js services, prefer appending JSONL to a local file that the agent can read later. Do not rely only on stdout or terminal scrollback.
Prefer the project's existing logger and debug-log location if one exists. Otherwise use a temporary file such as
/tmp/<project-or-feature>-debug.jsonl
or a gitignored project path such as
.debug/<feature>.jsonl
.
ts
import fs from "node:fs";

const DEBUG_LOG_PATH =
  process.env.FEATURE_DEBUG_LOG || "/tmp/feature-debug.jsonl";

function summarizeDebugValue(value: unknown): unknown {
  if (value instanceof Error) {
    return {
      name: value.name,
      message: value.message,
      stack: value.stack,
    };
  }

  try {
    return JSON.parse(JSON.stringify(value));
  } catch {
    return { unserializable: true, type: typeof value };
  }
}

export function debugEvent(event: string, payload: Record<string, unknown>) {
  const record = {
    ts: new Date().toISOString(),
    event,
    ...Object.fromEntries(
      Object.entries(payload).map(([key, value]) => [
        key,
        summarizeDebugValue(value),
      ]),
    ),
  };

  fs.appendFileSync(DEBUG_LOG_PATH, JSON.stringify(record) + "\n");
}
Good places to log:
  • External input boundary: request payload summary, job input, tool input, or message metadata.
  • Before and after important branch decisions.
  • Schema parse, validation, permission, feature flag, or route matching results.
  • Before and after calls to external services, providers, databases, queues, or tools.
  • Early return, short-circuit, skip, retry, fallback, or error paths.
Do not import
node:fs
into browser bundles. Node.js file logging belongs only in server-side or CLI code.
对于Node.js服务,优先将JSONL追加到本地文件中,以便代理后续读取。不要仅依赖标准输出(stdout)或终端回滚记录。
如果项目已有日志记录器和调试日志位置,优先使用现有方案。否则使用临时文件,例如
/tmp/<project-or-feature>-debug.jsonl
或Git忽略的项目路径,如
.debug/<feature>.jsonl
ts
import fs from "node:fs";

const DEBUG_LOG_PATH =
  process.env.FEATURE_DEBUG_LOG || "/tmp/feature-debug.jsonl";

function summarizeDebugValue(value: unknown): unknown {
  if (value instanceof Error) {
    return {
      name: value.name,
      message: value.message,
      stack: value.stack,
    };
  }

  try {
    return JSON.parse(JSON.stringify(value));
  } catch {
    return { unserializable: true, type: typeof value };
  }
}

export function debugEvent(event: string, payload: Record<string, unknown>) {
  const record = {
    ts: new Date().toISOString(),
    event,
    ...Object.fromEntries(
      Object.entries(payload).map(([key, value]) => [
        key,
        summarizeDebugValue(value),
      ]),
    ),
  };

  fs.appendFileSync(DEBUG_LOG_PATH, JSON.stringify(record) + "\n");
}
适合记录日志的位置:
  • 外部输入边界:请求负载摘要、任务输入、工具输入或消息元数据。
  • 重要分支决策的前后。
  • Schema解析、验证、权限、功能标志或路由匹配的结果。
  • 调用外部服务、提供商、数据库、队列或工具的前后。
  • 提前返回、短路、跳过、重试、回退或错误路径。
不要在浏览器包中导入
node:fs
。Node.js文件日志仅适用于服务端或CLI代码。

Node.js Self-Closure

Node.js 自闭环流程

For Node.js, CLI, or server-side issues, close the evidence loop yourself whenever the user provides a reproducible command or the repo contains a runnable repro.
Use this flow:
  1. Add temporary JSONL instrumentation.
  2. Clear or rotate the old debug log so the next run is easy to inspect.
  3. Run the repro command yourself, such as
    curl
    , an npm script, a CLI command, or a focused test.
  4. Read the local log file yourself.
  5. Update the evidence table before changing the fix direction.
Do not ask the user to paste logs that you can read directly. Ask for help only when the reproduction depends on user-only state, credentials, private browser session data, or an environment you cannot access.
对于Node.js、CLI或服务端问题,只要用户提供了可复现的命令,或仓库中包含可运行的复现代码,就自行完成证据闭环。
遵循以下流程:
  1. 添加临时JSONL监控代码。
  2. 清除或轮换旧的调试日志,以便下次运行后易于检查。
  3. 自行运行复现命令,例如
    curl
    、npm脚本、CLI命令或聚焦测试。
  4. 自行读取本地日志文件。
  5. 修改修复方向前更新证据表。
不要让用户粘贴你可直接读取的日志。仅当复现依赖用户专属状态、凭证、私有浏览器会话数据或你无法访问的环境时,才向用户寻求帮助。

Browser Logging Pattern

浏览器日志模式

For browser issues, make logs copy-friendly. Avoid logging only object references because DevTools may show live objects whose contents change later, and copying object entries can be incomplete or inconvenient.
Prefer a stable JSON string plus an in-memory debug buffer:
ts
type BrowserDebugRecord = {
  ts: string;
  event: string;
  [key: string]: unknown;
};

declare global {
  interface Window {
    __featureDebugEvents?: BrowserDebugRecord[];
  }
}

function toDebugSnapshot(value: unknown): unknown {
  if (value instanceof Error) {
    return {
      name: value.name,
      message: value.message,
      stack: value.stack,
    };
  }

  try {
    return JSON.parse(JSON.stringify(value));
  } catch {
    return { unserializable: true, type: typeof value };
  }
}

export function debugBrowserEvent(
  event: string,
  payload: Record<string, unknown> = {},
) {
  const record: BrowserDebugRecord = {
    ts: new Date().toISOString(),
    event,
    ...Object.fromEntries(
      Object.entries(payload).map(([key, value]) => [
        key,
        toDebugSnapshot(value),
      ]),
    ),
  };

  window.__featureDebugEvents ||= [];
  window.__featureDebugEvents.push(record);
  window.__featureDebugEvents = window.__featureDebugEvents.slice(-100);

  console.debug("[feature-debug]", JSON.stringify(record));
}
When asking the user to share browser evidence, give a single copy command:
js
copy(JSON.stringify(window.__featureDebugEvents || [], null, 2))
Good browser evidence includes:
  • Console errors and debug events.
  • Network request URL, method, status, request summary, and response summary.
  • Actual DOM/UI state after the action.
  • Whether event handlers, effects, callbacks, route changes, or async completions ran.
  • Browser screenshots only when visual state matters.
For browser-side issues, if you cannot directly operate the user's authenticated browser state, ask the user to reproduce once after instrumentation. Give exact steps and a single copy command for the debug buffer. Keep missing browser evidence explicit; do not fill it with guesses.
对于浏览器问题,确保日志便于复制。避免仅记录对象引用,因为DevTools可能显示内容会随时间变化的实时对象,复制对象条目可能不完整或不便。
优先使用稳定的JSON字符串加内存调试缓冲区:
ts
type BrowserDebugRecord = {
  ts: string;
  event: string;
  [key: string]: unknown;
};

declare global {
  interface Window {
    __featureDebugEvents?: BrowserDebugRecord[];
  }
}

function toDebugSnapshot(value: unknown): unknown {
  if (value instanceof Error) {
    return {
      name: value.name,
      message: value.message,
      stack: value.stack,
    };
  }

  try {
    return JSON.parse(JSON.stringify(value));
  } catch {
    return { unserializable: true, type: typeof value };
  }
}

export function debugBrowserEvent(
  event: string,
  payload: Record<string, unknown> = {},
) {
  const record: BrowserDebugRecord = {
    ts: new Date().toISOString(),
    event,
    ...Object.fromEntries(
      Object.entries(payload).map(([key, value]) => [
        key,
        toDebugSnapshot(value),
      ]),
    ),
  };

  window.__featureDebugEvents ||= [];
  window.__featureDebugEvents.push(record);
  window.__featureDebugEvents = window.__featureDebugEvents.slice(-100);

  console.debug("[feature-debug]", JSON.stringify(record));
}
请求用户分享浏览器证据时,提供单一复制命令:
js
copy(JSON.stringify(window.__featureDebugEvents || [], null, 2))
良好的浏览器证据包括:
  • 控制台错误和调试事件。
  • 网络请求的URL、方法、状态、请求摘要和响应摘要。
  • 操作后的实际DOM/UI状态。
  • 事件处理器、副作用、回调、路由变更或异步操作是否已执行。
  • 仅当视觉状态重要时才提供浏览器截图。
对于浏览器端问题,如果你无法直接操作用户的已认证浏览器状态,请让用户添加监控代码后复现一次。提供精确步骤和调试缓冲区的单一复制命令。明确指出缺失的浏览器证据;不要用猜测填补空白。

Privacy And Payload Safety

隐私与负载安全

Log summaries, not secrets. Prefer these safe forms:
  • hasAuthorizationHeader: true
    , never the header value.
  • emailHash
    or
    emailDomain
    , not raw email unless essential and approved.
  • payloadKeys
    ,
    arrayLength
    ,
    status
    ,
    errorName
    ,
    errorMessage
    ,
    route
    ,
    featureFlagValue
    .
  • Truncated strings and sampled arrays when payloads are large.
If sensitive data is unavoidable for diagnosis, ask the user before logging it and explain why.
记录摘要,而非机密信息。优先使用以下安全格式:
  • hasAuthorizationHeader: true
    ,绝不要记录头信息的值。
  • emailHash
    emailDomain
    ,除非必要且已获批,否则不要记录原始邮箱。
  • payloadKeys
    arrayLength
    status
    errorName
    errorMessage
    route
    featureFlagValue
  • 当负载较大时,使用截断字符串和采样数组。
如果诊断不可避免需要敏感数据,请先征得用户同意并说明原因。

Output Format

输出格式

For diagnosis responses, use this shape when the issue is more than trivial:
text
已确认事实:
...

代码推断:
...

当前假设:
...

缺失证据:
...

下一步:
...
If you added instrumentation, include:
  • Where the log is written or how to copy it.
  • What action the user should reproduce.
  • What evidence you expect the log to confirm or rule out.
  • Whether the agent can self-run the reproduction and read the log, or whether the user must reproduce in the browser.
  • The cleanup action taken after the fix, or the reason a debug-gated log remains.
对于诊断响应,当问题较为复杂时使用以下格式:
text
已确认事实:
...

代码推断:
...

当前假设:
...

缺失证据:
...

下一步:
...
如果添加了监控代码,需包含:
  • 日志的写入位置或复制方法。
  • 用户需要复现的操作。
  • 期望日志能确认或排除的证据。
  • 代理是否可自行运行复现并读取日志,或是用户必须在浏览器中复现。
  • 修复后执行的清理操作,或是保留带调试开关的日志的原因。

Anti-Patterns

反模式

Avoid these:
  • Declaring a root cause from code reading alone.
  • Ignoring user-provided runtime evidence because the code "should" behave differently.
  • Switching hypotheses after a user challenge without identifying the missing evidence.
  • Adding broad noisy logs instead of targeted logs around the decision point.
  • Logging raw secrets or large user payloads.
  • Leaving temporary instrumentation in production paths without a debug gate or cleanup plan.
  • Asking the user to paste Node.js, CLI, or server logs that the agent can read locally after running the provided reproduction command.
  • Finishing after the user says the issue is fixed while leaving temporary instrumentation in place without removing it or explicitly gating it.
  • Inferring a write API from a read call site without checking sibling write handlers, mutation handlers, serializers, or save paths in the same module.
  • Treating a mocked unit test as proof of business correctness. A mock confirming
    obj.method()
    was called proves the interaction happened, not that the method writes to the location the rest of the system reads from.
  • Accepting a test that only verifies the local code path when the real contract is whether persisted or mutated state is observable from the actual downstream read path.
避免以下行为:
  • 仅通过阅读代码就声明根本原因。
  • 因代码“应该”有不同表现而忽略用户提供的运行时证据。
  • 用户质疑后未找出缺失证据就切换假设。
  • 添加宽泛的嘈杂日志,而非针对决策点的定向日志。
  • 记录原始机密或大型用户负载。
  • 在生产路径中保留临时监控代码而未添加调试开关或清理计划。
  • 让用户粘贴Node.js、CLI或服务器日志,而你可通过运行提供的复现命令在本地读取这些日志。
  • 用户表示问题已修复后就结束工作,却未移除临时监控代码或未明确添加调试开关。
  • 仅通过读取调用点推断写入API,而未检查同一模块中的同级写入处理器、变更处理器、序列化器或保存路径。
  • 将模拟单元测试视为业务正确性的证明。模拟测试确认
    obj.method()
    被调用仅证明交互发生,而非该方法将数据写入系统其他部分读取的位置。
  • 接受仅验证本地代码路径的测试,而实际契约是持久化或变更后的状态能否从实际下游读取路径被观测到。