mcpField
D
debug

debug

github.com/vltansky开发25275 次安装40 周前更新
代码仓库

关于 debug

debug 是面向开发者和编码 Agent 的软件排错技能,强调用运行时日志而非猜测定位问题。它按假设、插桩、复现、分析、修复和验证组织排错流程。

主要功能

  1. 01基于假设开展排错
  2. 02Node.js 日志服务与会话
  3. 03JavaScript 和 Python 插桩指引
  4. 04根据日志评估假设
  5. 05基于证据验证修复
  6. 06浏览器与扩展日志方案

适用场景

  1. 01追踪空值或类型错误
  2. 02调查条件分支与异步时序
  3. 03复现并分析交互流程故障

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/vltansky/debug-skill --skill debug

安装后在 Claude Code 中输入/debug确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

package.json
README.md
.claude-plugin/
marketplace.json
scripts/
debug_cleanup.js
debug_server.js

详细介绍

调试模式

用运行时证据修复 bug,而不是靠猜测。

Don't guess → Hypothesize → Instrument → Reproduce → Analyze → Fix → Verify

何时使用

触发信号(如果你正准备做以下任何一件事,请改用本 skill):

  • “打开 DevTools Console 检查……”
  • “复现这个 bug,然后告诉我你看到了什么”
  • “加个 console.log,把输出告诉我”
  • “点击 X,打开 Y,检查控制台里是否出现 Z”

应当触发本 skill 的示例场景:

❌ Without skill (manual, slow):
"I added debug logging. Please:
1. Open the app in browser
2. Open DevTools Console (F12)
3. Open the defect modal and select a defect
4. Check console for [DEBUG] logs
5. Tell me what you see"

✅ With skill (automated):
Logs are captured server-side → you read them directly → no user copy-paste needed

在调试以下问题时使用:

  • 状态/取值问题(null、undefined、类型错误)
  • 条件逻辑(走了哪个分支)
  • 异步时序(竞态条件、加载顺序)
  • 用户交互流程(弹窗、表单、点击)

参数

/debug /path/to/project

如果未提供路径,则使用当前工作目录。

工作流

阶段 1:启动日志服务

步骤 1:确保服务正在运行(需要时启动,已在运行则不操作):

node skills/debug/scripts/debug_server.js /path/to/project &

服务输出 JSON:

  • {"status":"started",...} - 新服务已启动
  • {"status":"already_running",...} - 服务原本就在运行(这没问题!)

步骤 2:创建会话(服务根据你的描述生成唯一 ID):

curl -s -X POST http://localhost:8787/session -d '{"name":"fix-null-userid"}'

响应:

{"session_id":"fix-null-userid-a1b2c3","log_file":"/path/to/project/.debug/debug-fix-null-userid-a1b2c3.log"}

保存响应中的 session_id - 后续所有步骤都要用到它。

服务端点:

  • POST /session,请求体为 {"name": "description"} → 创建会话,返回 {session_id, log_file}
  • POST /log,请求体为 {"sessionId": "...", "msg": "..."} → 写入日志文件
  • GET / → 返回状态和日志目录

如果端口 8787 被占用: lsof -ti :8787 | xargs kill -9,然后重启

──────────

阶段 2:生成假设

在插桩之前,先提出 3-5 条具体假设:

Hypothesis H1: userId is null when passed to calculateScore()
  Expected: number (e.g., 5)
  Actual: null
  Test: Log userId at function entry

Hypothesis H2: score is string instead of number
  Expected: 85 (number)
  Actual: "85" (string)
  Test: Log typeof score

每条假设必须:

  • 具体(不能是“某个地方出错了”)
  • 可验证(能通过日志确认或否定)
  • 覆盖不同子系统(不要扎堆)

──────────

阶段 3:对代码插桩

添加日志调用,以验证所有假设。

JavaScript/TypeScript:

// #region debug
const SESSION_ID = 'REPLACE_WITH_SESSION_ID'; // e.g. 'fix-null-userid-a1b2c3'
const DEBUG_LOG_URL = 'http://localhost:8787/log';

const debugLog = (msg, data = {}, hypothesisId = null) => {
  const payload = JSON.stringify({
    sessionId: SESSION_ID,
    msg,
    data,
    hypothesisId,
    loc: new Error().stack?.split('\n')[2],
  });

  if (navigator.sendBeacon?.(DEBUG_LOG_URL, payload)) return;
  fetch(DEBUG_LOG_URL, { method: 'POST', body: payload }).catch(() => {});
};
// #endregion

// Usage
debugLog('Function entry', { userId, score, typeScore: typeof score }, 'H1,H2');

Python:

# #region debug
import requests, traceback
SESSION_ID = 'REPLACE_WITH_SESSION_ID'  # e.g. 'fix-null-userid-a1b2c3'
def debug_log(msg, data=None, hypothesis_id=None):
    try:
        requests.post('http://localhost:8787/log', json={
            'sessionId': SESSION_ID, 'msg': msg, 'data': data,
            'hypothesisId': hypothesis_id, 'loc': traceback.format_stack()[-2].strip()
        }, timeout=0.5)
    except: pass
# #endregion

# Usage
debug_log('Function entry', {'user_id': user_id, 'type': type(user_id)}, 'H1')

指引:

  • 3-8 个插桩点
  • 覆盖:入口/出口、关键操作的前/后、各分支路径
  • 每条日志都打上 hypothesisId 标签
  • 用 // #region debug ... // #endregion 包裹
  • 高频事件(mousemove、scroll):仅在状态变化时记录日志
  • 同时记录意图和结果

──────────

阶段 4:清空并复现
  1. 清空日志:

    : > /path/to/project/.debug/debug-$SESSION_ID.log
    
  2. 提供复现步骤:

    <reproduction_steps>
    1. Start app: yarn dev
    2. Navigate to /users
    3. Click "Calculate Score"
    4. Observe NaN displayed
    </reproduction_steps>
    
  3. 用户复现 bug

──────────

阶段 5:分析日志

读取并评估:

cat /path/to/project/.debug/debug-$SESSION_ID.log

对每条假设:

Hypothesis H1: userId is null
  Status: CONFIRMED
  Evidence: {"msg":"Function entry","data":{"userId":null}}

Hypothesis H2: score is string
  Status: REJECTED
  Evidence: {"data":{"typeScore":"number"}}

状态选项:

  • CONFIRMED:日志证实
  • REJECTED:日志否定
  • INCONCLUSIVE:需要更多插桩

如果全部为 INCONCLUSIVE/REJECTED:生成新假设,添加更多日志,迭代。

──────────

阶段 6:修复

只有在日志确认根因后才进行修复。

保持插桩处于启用状态(暂时不要移除)。

给验证日志打上 runId: "post-fix" 标签:

debugLog('Function entry', { userId, runId: 'post-fix' }, 'H1');

──────────

阶段 7:验证
  1. 清空日志
  2. 用户复现(bug 应该已消失)
  3. 对比修复前/后:
    Before: {"data":{"userId":null},"runId":"run1"}
    After:  {"data":{"userId":5},"runId":"post-fix"}
    
  4. 用日志证据确认

如果仍然有问题:提出新假设,添加更多日志,迭代。

──────────

阶段 8:五问法(可选)

何时执行: 反复出现的 bug、生产事故、安全问题,或者“这问题一直出现”。

修复后,追问“这个 bug 为什么会存在?”,以找出系统性原因:

Bug: API returns NaN

Why 1: userId was null → Code fix: null check
Why 2: No input validation → Add validation
Why 3: No test for null case → Add test
Why 4: Review didn't catch → (one-off, acceptable)

分类:

类型行动
CODE立即修复
TEST添加测试
PROCESS更新检查清单/评审流程
SYSTEMIC记录模式

跳过条件: 简单的一次性 bug、影响低、不会反复出现。

──────────

阶段 9:清理

仅在满足以下条件后移除插桩:

  • 修复后的日志证明成功
  • 用户确认已解决

搜索 #region debug 并移除所有调试代码。

日志格式

每行都是 NDJSON:

{"ts":"2024-01-03T12:00:00.000Z","msg":"Button clicked","data":{"id":5},"hypothesisId":"H1","loc":"app.js:42"}

关键规则

  1. 未经运行时证据,绝不修复 - 始终先收集日志
  2. 未经验证,绝不移除埋点 - 保留至修复确认
  3. 绝不猜测 - 如有不确定,添加更多日志
  4. 如果所有假设均被否定 - 从不同子系统生成新假设

故障排查

问题解决方案
服务无法启动检查端口 8787 是否被占用:lsof -i :8787
日志为空检查浏览器拦截(混合内容/CSP/CORS)、防火墙
日志文件错误验证 session ID 是否匹配
日志过多按 hypothesisId 过滤,使用状态变更日志
无法复现向用户询问确切步骤,检查环境
CORS / 混合内容变通方案

如果日志未能到达,通常是以下情况之一:

  • 混合内容:HTTPS 应用 → http://localhost:8787 被阻止。使用开发服务器代理(同源)或通过 HTTPS 提供日志端点。
  • CSP:connect-src 阻止了日志 URL。使用开发服务器代理或更新 CSP。
  • CORS 预检:Content-Type: application/json 会触发 OPTIONS。使用“简单”请求(text/plain)或 sendBeacon。

1. sendBeacon(避免预检;即发即忘):

const DEBUG_LOG_URL = 'http://localhost:8787/log';
const debugLog = (msg, data = {}, hypothesisId = null) => {
  const payload = JSON.stringify({ sessionId: SESSION_ID, msg, data, hypothesisId });
  if (navigator.sendBeacon?.(DEBUG_LOG_URL, payload)) return;
  fetch(DEBUG_LOG_URL, { method: 'POST', body: payload }).catch(() => {});
};

注意:仍会被混合内容 + CSP 阻止。

2. 开发服务器代理(Vite 示例) - 同源 /__log → http://localhost:8787/log:

// vite.config.js
export default {
  server: {
    proxy: {
      '/__log': {
        target: 'http://localhost:8787',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/__log/, '/log'),
      },
    },
  },
};

// Then POST to /__log instead of localhost:8787/log

3. 最后手段(仅限本地) - 在浏览器设置中允许不安全内容 / 禁用混合内容阻止

Chrome 扩展调试

内容脚本运行在具有严格 CSP 的隔离世界中 - 它们无法直接 fetch 到 localhost:8787。解决方案是通过后台脚本(service worker)中继日志。

内容脚本(发送方):

// #region debug
const DEBUG_SESSION_ID = 'your-session-id-here';

const debugLog = (msg, data = {}, hypothesisId = null) => {
  chrome.runtime.sendMessage({
    type: 'DEBUG_LOG',
    payload: {
      sessionId: DEBUG_SESSION_ID,
      msg,
      data,
      hypothesisId,
      loc: new Error().stack?.split('\n')[2]?.trim(),
    },
  }).catch(() => {});
};
// #endregion

// Usage
debugLog('handleMouseMove', { target: target.tagName, rect }, 'H1');

后台脚本(中继):

// #region debug - relay logs to debug server
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.type === 'DEBUG_LOG') {
    fetch('http://localhost:8787/log', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(message.payload),
    }).catch(() => {});
    sendResponse({ ok: true });
    return true;
  }
});
// #endregion

为什么这有效:

  • 后台脚本(service worker)具有宽松的 CSP,可以 fetch 到 localhost
  • chrome.runtime.sendMessage 是内容脚本与后台之间的桥梁
  • 保持两个调试区域都有标记,便于清理

注入脚本(MAIN 世界): 如果调试代码通过 <script> 注入到页面上下文中,使用 window.postMessage 中继到内容脚本,再由内容脚本中继到后台:

// In MAIN world (injected script)
window.postMessage({ type: 'DEBUG_LOG_RELAY', payload: { ... } }, '*');

// In content script
window.addEventListener('message', (e) => {
  if (e.data?.type === 'DEBUG_LOG_RELAY') {
    chrome.runtime.sendMessage({ type: 'DEBUG_LOG', payload: e.data.payload });
  }
});

检查清单

  • 服务正在运行(started 或 already_running)
  • 通过 POST /session 创建会话 - 保存返回的 session_id
  • 生成了 3-5 个假设
  • 添加了 3-8 条日志,并标记 hypothesisId
  • 在复现前清空日志
  • 提供了复现步骤
  • 评估了每个假设(CONFIRMED/REJECTED/INCONCLUSIVE)
  • 仅基于证据进行修复
  • 完成了修复前/后对比
  • 确认后移除了埋点

评论

支持 Markdown