debuganything 0.0.0-stage → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +15 -0
- package/DISCLAIMER.md +101 -0
- package/GEMINI.md +97 -0
- package/LICENSE +21 -0
- package/README.md +283 -2
- package/assets/README.md +9 -0
- package/assets/debuganything-logo.png +0 -0
- package/assets/debuganything-mark-512.png +0 -0
- package/dist/chunk-32IDKAZK.js +9 -0
- package/dist/chunk-32IDKAZK.js.map +1 -0
- package/dist/chunk-5BXTDASF.js +135 -0
- package/dist/chunk-5BXTDASF.js.map +1 -0
- package/dist/chunk-5RAKTYW5.js +292 -0
- package/dist/chunk-5RAKTYW5.js.map +1 -0
- package/dist/chunk-7MMXFUUN.js +79 -0
- package/dist/chunk-7MMXFUUN.js.map +1 -0
- package/dist/chunk-D742WJPT.js +436 -0
- package/dist/chunk-D742WJPT.js.map +1 -0
- package/dist/chunk-EMR42ZJQ.js +102 -0
- package/dist/chunk-EMR42ZJQ.js.map +1 -0
- package/dist/chunk-MLDOO673.js +1416 -0
- package/dist/chunk-MLDOO673.js.map +1 -0
- package/dist/chunk-NFL22T3H.js +309 -0
- package/dist/chunk-NFL22T3H.js.map +1 -0
- package/dist/chunk-STFC7C5C.js +245 -0
- package/dist/chunk-STFC7C5C.js.map +1 -0
- package/dist/chunk-VGYXHXTY.js +1015 -0
- package/dist/chunk-VGYXHXTY.js.map +1 -0
- package/dist/chunk-YY6M5RIJ.js +215 -0
- package/dist/chunk-YY6M5RIJ.js.map +1 -0
- package/dist/cli.js +254 -0
- package/dist/cli.js.map +1 -0
- package/dist/docker-ORXYMES4.js +23 -0
- package/dist/docker-ORXYMES4.js.map +1 -0
- package/dist/hook-QYLLBUNK.js +14 -0
- package/dist/hook-QYLLBUNK.js.map +1 -0
- package/dist/import-CT55AEEE.js +268 -0
- package/dist/import-CT55AEEE.js.map +1 -0
- package/dist/investigate-2GFD6EWB.js +102 -0
- package/dist/investigate-2GFD6EWB.js.map +1 -0
- package/dist/lsof-GBLZJBJR.js +22 -0
- package/dist/lsof-GBLZJBJR.js.map +1 -0
- package/dist/psmem-UB3VCMG3.js +20 -0
- package/dist/psmem-UB3VCMG3.js.map +1 -0
- package/dist/report_html-2ENM7HLC.js +99 -0
- package/dist/report_html-2ENM7HLC.js.map +1 -0
- package/dist/server.js +190 -0
- package/dist/server.js.map +1 -0
- package/dist/setup-NTPRUTMT.js +265 -0
- package/dist/setup-NTPRUTMT.js.map +1 -0
- package/dist/target-WJE4WFYA.js +36 -0
- package/dist/target-WJE4WFYA.js.map +1 -0
- package/gemini-extension.json +9 -0
- package/hooks/debuganything-hook.mjs +36 -0
- package/hooks/hooks.json +10 -0
- package/package.json +85 -3
- package/server.json +30 -0
- package/skills/debug-anything/SKILL.md +102 -0
- package/skills/inspect-product/SKILL.md +138 -0
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
HOOK_SCRIPT
|
|
4
|
+
} from "./chunk-7MMXFUUN.js";
|
|
5
|
+
|
|
6
|
+
// src/setup.ts
|
|
7
|
+
import { readFile, writeFile, mkdir, copyFile, access } from "fs/promises";
|
|
8
|
+
import { existsSync } from "fs";
|
|
9
|
+
import { homedir } from "os";
|
|
10
|
+
import { dirname, join } from "path";
|
|
11
|
+
import { fileURLToPath } from "url";
|
|
12
|
+
import { createInterface } from "readline/promises";
|
|
13
|
+
import pc from "picocolors";
|
|
14
|
+
var MCP_ENTRY = { command: "npx", args: ["-y", "debuganything@latest", "serve"] };
|
|
15
|
+
var AGENTS = [
|
|
16
|
+
{
|
|
17
|
+
id: "claude",
|
|
18
|
+
label: "Claude Code",
|
|
19
|
+
configPath: (h) => join(h, ".claude.json"),
|
|
20
|
+
detect: (h) => existsSync(join(h, ".claude.json")) || existsSync(join(h, ".claude", "settings.json")) || existsSync(join(h, ".claude")),
|
|
21
|
+
format: "json",
|
|
22
|
+
key: "mcpServers",
|
|
23
|
+
skillsDir: (h) => join(h, ".claude", "skills", "debug-anything")
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
id: "codex",
|
|
27
|
+
label: "Codex",
|
|
28
|
+
configPath: (h) => join(h, ".codex", "config.toml"),
|
|
29
|
+
detect: (h) => existsSync(join(h, ".codex")),
|
|
30
|
+
format: "toml",
|
|
31
|
+
key: "mcp_servers.debuganything",
|
|
32
|
+
skillsDir: (h) => join(h, ".codex", "skills", "debug-anything")
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
id: "cursor",
|
|
36
|
+
label: "Cursor",
|
|
37
|
+
configPath: (h) => join(h, ".cursor", "mcp.json"),
|
|
38
|
+
detect: (h) => existsSync(join(h, ".cursor")),
|
|
39
|
+
format: "json",
|
|
40
|
+
key: "mcpServers"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: "gemini",
|
|
44
|
+
label: "Gemini CLI",
|
|
45
|
+
configPath: (h) => join(h, ".gemini", "settings.json"),
|
|
46
|
+
detect: (h) => existsSync(join(h, ".gemini")),
|
|
47
|
+
format: "json",
|
|
48
|
+
key: "mcpServers",
|
|
49
|
+
skillsDir: (h) => join(h, ".gemini", "skills", "debug-anything")
|
|
50
|
+
}
|
|
51
|
+
];
|
|
52
|
+
function patchJsonConfig(existing, key = "mcpServers") {
|
|
53
|
+
let obj = {};
|
|
54
|
+
if (existing && existing.trim()) {
|
|
55
|
+
try {
|
|
56
|
+
obj = JSON.parse(existing);
|
|
57
|
+
} catch {
|
|
58
|
+
throw new Error("existing config is not valid JSON; refusing to overwrite it");
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const servers = obj[key] ?? {};
|
|
62
|
+
const current = JSON.stringify(servers.debuganything ?? null);
|
|
63
|
+
const desired = JSON.stringify(MCP_ENTRY);
|
|
64
|
+
if (current === desired) return { next: existing ?? JSON.stringify(obj, null, 2) + "\n", changed: false };
|
|
65
|
+
servers.debuganything = { command: MCP_ENTRY.command, args: [...MCP_ENTRY.args] };
|
|
66
|
+
obj[key] = servers;
|
|
67
|
+
return { next: JSON.stringify(obj, null, 2) + "\n", changed: true };
|
|
68
|
+
}
|
|
69
|
+
function patchClaudeHooks(existing, scriptPath) {
|
|
70
|
+
let obj = {};
|
|
71
|
+
if (existing && existing.trim()) {
|
|
72
|
+
try {
|
|
73
|
+
obj = JSON.parse(existing);
|
|
74
|
+
} catch {
|
|
75
|
+
throw new Error("existing settings.json is not valid JSON; refusing to overwrite it");
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const cmd = `node "${scriptPath}"`;
|
|
79
|
+
const hooks = obj.hooks ??= {};
|
|
80
|
+
let changed = false;
|
|
81
|
+
const ensure = (event, matcher) => {
|
|
82
|
+
const list = hooks[event] ??= [];
|
|
83
|
+
const has = list.some((g) => Array.isArray(g?.hooks) && g.hooks.some((h) => typeof h?.command === "string" && h.command.includes("debuganything-hook.mjs")));
|
|
84
|
+
if (has) return;
|
|
85
|
+
list.push({ ...matcher ? { matcher } : {}, hooks: [{ type: "command", command: cmd, timeout: 10 }] });
|
|
86
|
+
changed = true;
|
|
87
|
+
};
|
|
88
|
+
ensure("PostToolUse", "Bash");
|
|
89
|
+
ensure("UserPromptSubmit", void 0);
|
|
90
|
+
return { next: JSON.stringify(obj, null, 2) + "\n", changed };
|
|
91
|
+
}
|
|
92
|
+
var TOML_BLOCK = `[mcp_servers.debuganything]
|
|
93
|
+
command = "npx"
|
|
94
|
+
args = ["-y", "debuganything@latest", "serve"]
|
|
95
|
+
`;
|
|
96
|
+
function patchTomlConfig(existing) {
|
|
97
|
+
const text = existing ?? "";
|
|
98
|
+
const re = /^\[mcp_servers\.debuganything\]\n(?:(?!\[)[^\n]*\n?)*/m;
|
|
99
|
+
if (re.test(text)) {
|
|
100
|
+
const current = re.exec(text)[0];
|
|
101
|
+
if (current.trim() === TOML_BLOCK.trim()) return { next: text, changed: false };
|
|
102
|
+
return { next: text.replace(re, TOML_BLOCK), changed: true };
|
|
103
|
+
}
|
|
104
|
+
const sep = text.length && !text.endsWith("\n") ? "\n\n" : text.length ? "\n" : "";
|
|
105
|
+
return { next: text + sep + TOML_BLOCK, changed: true };
|
|
106
|
+
}
|
|
107
|
+
function skillSourceDir() {
|
|
108
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
109
|
+
for (const cand of [join(here, "..", "skills", "debug-anything"), join(here, "..", "..", "skills", "debug-anything")]) {
|
|
110
|
+
if (existsSync(join(cand, "SKILL.md"))) return cand;
|
|
111
|
+
}
|
|
112
|
+
throw new Error("skills/debug-anything/SKILL.md not found next to the package");
|
|
113
|
+
}
|
|
114
|
+
async function setup(opts = {}) {
|
|
115
|
+
const home = opts.home ?? homedir();
|
|
116
|
+
const log = opts.log ?? ((s) => process.stdout.write(s + "\n"));
|
|
117
|
+
const wanted = AGENTS.filter((a) => opts.agents ? opts.agents.includes(a.id) : a.detect(home));
|
|
118
|
+
const written = [];
|
|
119
|
+
const skipped = [];
|
|
120
|
+
if (wanted.length === 0) {
|
|
121
|
+
log(pc.yellow("No supported agent detected (looked for ~/.claude.json, ~/.codex/, ~/.cursor/, ~/.gemini/)."));
|
|
122
|
+
log(`Add this to your agent's MCP config manually:
|
|
123
|
+
${JSON.stringify({ mcpServers: { debuganything: MCP_ENTRY } }, null, 2)}`);
|
|
124
|
+
return { written, skipped };
|
|
125
|
+
}
|
|
126
|
+
log(pc.bold("Debug Anything setup will:"));
|
|
127
|
+
const plans = [];
|
|
128
|
+
for (const agent of wanted) {
|
|
129
|
+
const path = agent.configPath(home);
|
|
130
|
+
const existing = existsSync(path) ? await readFile(path, "utf8") : null;
|
|
131
|
+
const { next, changed } = agent.format === "json" ? patchJsonConfig(existing, agent.key) : patchTomlConfig(existing);
|
|
132
|
+
plans.push({ agent, path, next, changed, existing });
|
|
133
|
+
log(`
|
|
134
|
+
${pc.cyan("\u2022")} ${pc.bold(agent.label)} \u2192 ${path} ${changed ? "" : pc.dim("(already registered)")}`);
|
|
135
|
+
if (changed) {
|
|
136
|
+
log(pc.dim(agent.format === "json" ? JSON.stringify({ [agent.key]: { debuganything: MCP_ENTRY } }, null, 2) : TOML_BLOCK.trim()));
|
|
137
|
+
}
|
|
138
|
+
if (agent.skillsDir) log(pc.dim(` + copy skills/debug-anything/SKILL.md \u2192 ${agent.skillsDir(home)}/SKILL.md`));
|
|
139
|
+
if (agent.id === "claude" && opts.hooks !== false) {
|
|
140
|
+
log(pc.dim(` + write ${join(home, ".debuganything", "hooks", "debuganything-hook.mjs")} and register it in ${join(home, ".claude", "settings.json")} (hooks.PostToolUse[Bash], hooks.UserPromptSubmit) so the agent reaches for Debug Anything on EADDRINUSE / OOM / timeouts / "it hangs" without being asked. Disable with --no-hooks.`));
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if (opts.dryRun) {
|
|
144
|
+
log(pc.dim("\n--dry-run: nothing written."));
|
|
145
|
+
return { written, skipped: plans.map((p) => p.path) };
|
|
146
|
+
}
|
|
147
|
+
if (!opts.yes) {
|
|
148
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
149
|
+
const ans = (await rl.question("\nApply these changes? Existing files are backed up to *.bak-debuganything [y/N] ")).trim().toLowerCase();
|
|
150
|
+
rl.close();
|
|
151
|
+
if (ans !== "y" && ans !== "yes") {
|
|
152
|
+
log("aborted; nothing written");
|
|
153
|
+
return { written, skipped: plans.map((p) => p.path) };
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
const skillSrc = skillSourceDir();
|
|
157
|
+
for (const p of plans) {
|
|
158
|
+
if (p.changed) {
|
|
159
|
+
await mkdir(dirname(p.path), { recursive: true });
|
|
160
|
+
if (p.existing !== null) await copyFile(p.path, `${p.path}.bak-debuganything`);
|
|
161
|
+
await writeFile(p.path, p.next);
|
|
162
|
+
written.push(p.path);
|
|
163
|
+
} else skipped.push(p.path);
|
|
164
|
+
if (p.agent.skillsDir) {
|
|
165
|
+
const dir = p.agent.skillsDir(home);
|
|
166
|
+
await mkdir(dir, { recursive: true });
|
|
167
|
+
await copyFile(join(skillSrc, "SKILL.md"), join(dir, "SKILL.md"));
|
|
168
|
+
written.push(join(dir, "SKILL.md"));
|
|
169
|
+
}
|
|
170
|
+
if (p.agent.id === "claude" && opts.hooks !== false) {
|
|
171
|
+
const hookDir = join(home, ".debuganything", "hooks");
|
|
172
|
+
await mkdir(hookDir, { recursive: true });
|
|
173
|
+
const script = join(hookDir, "debuganything-hook.mjs");
|
|
174
|
+
await writeFile(script, HOOK_SCRIPT, { mode: 493 });
|
|
175
|
+
written.push(script);
|
|
176
|
+
const settingsPath = join(home, ".claude", "settings.json");
|
|
177
|
+
const existing = existsSync(settingsPath) ? await readFile(settingsPath, "utf8") : null;
|
|
178
|
+
const { next, changed } = patchClaudeHooks(existing, script);
|
|
179
|
+
if (changed) {
|
|
180
|
+
await mkdir(dirname(settingsPath), { recursive: true });
|
|
181
|
+
if (existing !== null) await copyFile(settingsPath, `${settingsPath}.bak-debuganything`);
|
|
182
|
+
await writeFile(settingsPath, next);
|
|
183
|
+
written.push(settingsPath);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
log(`
|
|
188
|
+
${pc.green("Done.")} ${written.length} file(s) written. ${pc.bold("Restart your agent")} and ask it: \u201CMy API gets slow after a few minutes \u2014 attach to it and tell me why, with evidence.\u201D`);
|
|
189
|
+
return { written, skipped };
|
|
190
|
+
}
|
|
191
|
+
async function fileExists(p) {
|
|
192
|
+
try {
|
|
193
|
+
await access(p);
|
|
194
|
+
return true;
|
|
195
|
+
} catch {
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
var AGENTS_MD_BLOCK = `## Runtime debugging with Debug Anything
|
|
200
|
+
|
|
201
|
+
DEA (Debug Anything) is registered as an MCP server for this repo (\`.mcp.json\`, \`.cursor/mcp.json\`).
|
|
202
|
+
When something *running* misbehaves \u2014 a command hangs, a test times out, you see EADDRINUSE / ECONNREFUSED /
|
|
203
|
+
EMFILE / exit 137 / OOM / a segfault, a server you started does not respond, or the user says slow, hang,
|
|
204
|
+
leak or crash \u2014 attach and measure before theorizing: \`readiness\` \u2192 \`attach\` \u2192 \`dump_stacks\` /
|
|
205
|
+
\`profile_cpu\` / \`inspect_sockets\` / \`snapshot_memory\` / \`container_info\` \u2192 \`detach\`. Cite evidence ids
|
|
206
|
+
(e.g. \`profile_cpu#2/e3\`). Read-only tools need no permission; \`capture_traffic\` needs the user's consent.
|
|
207
|
+
After a performance fix, re-measure and \`diff_runs\` to prove it; put the evidence ids in the PR.
|
|
208
|
+
Docs: https://github.com/shreyassaw5394/debuganything
|
|
209
|
+
`;
|
|
210
|
+
function patchAgentsMd(existing) {
|
|
211
|
+
const text = existing ?? "";
|
|
212
|
+
if (text.includes("## Runtime debugging with Debug Anything")) return { next: text, changed: false };
|
|
213
|
+
const sep = text.length === 0 ? "" : text.endsWith("\n\n") ? "" : text.endsWith("\n") ? "\n" : "\n\n";
|
|
214
|
+
return { next: text + sep + AGENTS_MD_BLOCK, changed: true };
|
|
215
|
+
}
|
|
216
|
+
async function init(opts = {}) {
|
|
217
|
+
const dir = opts.dir ?? process.cwd();
|
|
218
|
+
const log = opts.log ?? ((s) => process.stdout.write(s + "\n"));
|
|
219
|
+
const written = [];
|
|
220
|
+
const skipped = [];
|
|
221
|
+
const plans = [];
|
|
222
|
+
for (const rel of [".mcp.json", join(".cursor", "mcp.json")]) {
|
|
223
|
+
const path = join(dir, rel);
|
|
224
|
+
const existing = existsSync(path) ? await readFile(path, "utf8") : null;
|
|
225
|
+
const { next, changed } = patchJsonConfig(existing);
|
|
226
|
+
plans.push({ path, next, changed, existing, what: "MCP server entry" });
|
|
227
|
+
}
|
|
228
|
+
const candidates = ["AGENTS.md", "CLAUDE.md"].map((f) => join(dir, f));
|
|
229
|
+
const target = candidates.find((p) => existsSync(p)) ?? candidates[0];
|
|
230
|
+
const existingMd = existsSync(target) ? await readFile(target, "utf8") : null;
|
|
231
|
+
const md = patchAgentsMd(existingMd);
|
|
232
|
+
plans.push({ path: target, next: md.next, changed: md.changed, existing: existingMd, what: "runtime-debugging rule for agents" });
|
|
233
|
+
log(pc.bold(`Debug Anything init in ${dir} will:`));
|
|
234
|
+
for (const p of plans) log(`${pc.cyan("\u2022")} ${p.path} ${p.changed ? pc.dim(`(+ ${p.what})`) : pc.dim("(already set up)")}`);
|
|
235
|
+
if (opts.dryRun) {
|
|
236
|
+
log(pc.dim("--dry-run: nothing written."));
|
|
237
|
+
return { written, skipped: plans.map((p) => p.path) };
|
|
238
|
+
}
|
|
239
|
+
for (const p of plans) {
|
|
240
|
+
if (!p.changed) {
|
|
241
|
+
skipped.push(p.path);
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
await mkdir(dirname(p.path), { recursive: true });
|
|
245
|
+
if (p.existing !== null) await copyFile(p.path, `${p.path}.bak-debuganything`);
|
|
246
|
+
await writeFile(p.path, p.next);
|
|
247
|
+
written.push(p.path);
|
|
248
|
+
}
|
|
249
|
+
log(`${pc.green("Done.")} Commit these files so every teammate's agent gets Debug Anything. Each person still runs ${pc.bold("npx debuganything setup")} once for the skill and hook.`);
|
|
250
|
+
return { written, skipped };
|
|
251
|
+
}
|
|
252
|
+
export {
|
|
253
|
+
AGENTS,
|
|
254
|
+
AGENTS_MD_BLOCK,
|
|
255
|
+
MCP_ENTRY,
|
|
256
|
+
TOML_BLOCK,
|
|
257
|
+
fileExists,
|
|
258
|
+
init,
|
|
259
|
+
patchAgentsMd,
|
|
260
|
+
patchClaudeHooks,
|
|
261
|
+
patchJsonConfig,
|
|
262
|
+
patchTomlConfig,
|
|
263
|
+
setup
|
|
264
|
+
};
|
|
265
|
+
//# sourceMappingURL=setup-NTPRUTMT.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/setup.ts"],"sourcesContent":["/**\n * `debuganything setup` — register the MCP server + skill with Claude Code, Codex,\n * Cursor and Gemini CLI. Shows the exact change, backs up the file, writes.\n */\nimport { readFile, writeFile, mkdir, copyFile, access } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { createInterface } from \"node:readline/promises\";\nimport pc from \"picocolors\";\nimport { HOOK_SCRIPT } from \"./hook.js\";\n\nexport const MCP_ENTRY = { command: \"npx\", args: [\"-y\", \"debuganything@latest\", \"serve\"] } as const;\n\nexport interface AgentTarget {\n id: \"claude\" | \"codex\" | \"cursor\" | \"gemini\";\n label: string;\n configPath(home: string): string;\n /** Config exists → agent considered installed. Claude Code may have either file. */\n detect(home: string): boolean;\n format: \"json\" | \"toml\";\n /** JSON path to the mcpServers object, or TOML table name. */\n key: string;\n skillsDir?(home: string): string;\n}\n\nexport const AGENTS: AgentTarget[] = [\n {\n id: \"claude\",\n label: \"Claude Code\",\n configPath: (h) => join(h, \".claude.json\"),\n detect: (h) => existsSync(join(h, \".claude.json\")) || existsSync(join(h, \".claude\", \"settings.json\")) || existsSync(join(h, \".claude\")),\n format: \"json\",\n key: \"mcpServers\",\n skillsDir: (h) => join(h, \".claude\", \"skills\", \"debug-anything\"),\n },\n {\n id: \"codex\",\n label: \"Codex\",\n configPath: (h) => join(h, \".codex\", \"config.toml\"),\n detect: (h) => existsSync(join(h, \".codex\")),\n format: \"toml\",\n key: \"mcp_servers.debuganything\",\n skillsDir: (h) => join(h, \".codex\", \"skills\", \"debug-anything\"),\n },\n {\n id: \"cursor\",\n label: \"Cursor\",\n configPath: (h) => join(h, \".cursor\", \"mcp.json\"),\n detect: (h) => existsSync(join(h, \".cursor\")),\n format: \"json\",\n key: \"mcpServers\",\n },\n {\n id: \"gemini\",\n label: \"Gemini CLI\",\n configPath: (h) => join(h, \".gemini\", \"settings.json\"),\n detect: (h) => existsSync(join(h, \".gemini\")),\n format: \"json\",\n key: \"mcpServers\",\n skillsDir: (h) => join(h, \".gemini\", \"skills\", \"debug-anything\"),\n },\n];\n\n/** Produce the new JSON config text given existing text. Pure. */\nexport function patchJsonConfig(existing: string | null, key = \"mcpServers\"): { next: string; changed: boolean } {\n let obj: Record<string, unknown> = {};\n if (existing && existing.trim()) {\n try {\n obj = JSON.parse(existing) as Record<string, unknown>;\n } catch {\n throw new Error(\"existing config is not valid JSON; refusing to overwrite it\");\n }\n }\n const servers = ((obj[key] as Record<string, unknown> | undefined) ?? {}) as Record<string, unknown>;\n const current = JSON.stringify(servers.debuganything ?? null);\n const desired = JSON.stringify(MCP_ENTRY);\n if (current === desired) return { next: existing ?? JSON.stringify(obj, null, 2) + \"\\n\", changed: false };\n servers.debuganything = { command: MCP_ENTRY.command, args: [...MCP_ENTRY.args] };\n obj[key] = servers;\n return { next: JSON.stringify(obj, null, 2) + \"\\n\", changed: true };\n}\n\n/** Claude Code hooks entries (settings.json). Pure. */\nexport function patchClaudeHooks(existing: string | null, scriptPath: string): { next: string; changed: boolean } {\n let obj: Record<string, any> = {};\n if (existing && existing.trim()) {\n try {\n obj = JSON.parse(existing);\n } catch {\n throw new Error(\"existing settings.json is not valid JSON; refusing to overwrite it\");\n }\n }\n const cmd = `node \"${scriptPath}\"`;\n const hooks = (obj.hooks ??= {});\n let changed = false;\n const ensure = (event: string, matcher: string | undefined) => {\n const list: any[] = (hooks[event] ??= []);\n const has = list.some((g) => Array.isArray(g?.hooks) && g.hooks.some((h: any) => typeof h?.command === \"string\" && h.command.includes(\"debuganything-hook.mjs\")));\n if (has) return;\n list.push({ ...(matcher ? { matcher } : {}), hooks: [{ type: \"command\", command: cmd, timeout: 10 }] });\n changed = true;\n };\n ensure(\"PostToolUse\", \"Bash\");\n ensure(\"UserPromptSubmit\", undefined);\n return { next: JSON.stringify(obj, null, 2) + \"\\n\", changed };\n}\n\nexport const TOML_BLOCK = `[mcp_servers.debuganything]\\ncommand = \"npx\"\\nargs = [\"-y\", \"debuganything@latest\", \"serve\"]\\n`;\n\n/** Patch a TOML config: replace an existing [mcp_servers.debuganything] table or append one. Pure, no TOML dependency. */\nexport function patchTomlConfig(existing: string | null): { next: string; changed: boolean } {\n const text = existing ?? \"\";\n const re = /^\\[mcp_servers\\.debuganything\\]\\n(?:(?!\\[)[^\\n]*\\n?)*/m;\n if (re.test(text)) {\n const current = re.exec(text)![0];\n if (current.trim() === TOML_BLOCK.trim()) return { next: text, changed: false };\n return { next: text.replace(re, TOML_BLOCK), changed: true };\n }\n const sep = text.length && !text.endsWith(\"\\n\") ? \"\\n\\n\" : text.length ? \"\\n\" : \"\";\n return { next: text + sep + TOML_BLOCK, changed: true };\n}\n\nfunction skillSourceDir(): string {\n // dist/cli.js → ../skills ; src/setup.ts → ../skills\n const here = dirname(fileURLToPath(import.meta.url));\n for (const cand of [join(here, \"..\", \"skills\", \"debug-anything\"), join(here, \"..\", \"..\", \"skills\", \"debug-anything\")]) {\n if (existsSync(join(cand, \"SKILL.md\"))) return cand;\n }\n throw new Error(\"skills/debug-anything/SKILL.md not found next to the package\");\n}\n\nexport interface SetupOptions {\n yes?: boolean;\n home?: string;\n agents?: string[];\n dryRun?: boolean;\n /** Install the Claude Code proactive hook (default true). */\n hooks?: boolean;\n /** Test hook. */\n log?: (s: string) => void;\n}\n\nexport async function setup(opts: SetupOptions = {}): Promise<{ written: string[]; skipped: string[] }> {\n const home = opts.home ?? homedir();\n const log = opts.log ?? ((s: string) => process.stdout.write(s + \"\\n\"));\n const wanted = AGENTS.filter((a) => (opts.agents ? opts.agents.includes(a.id) : a.detect(home)));\n const written: string[] = [];\n const skipped: string[] = [];\n if (wanted.length === 0) {\n log(pc.yellow(\"No supported agent detected (looked for ~/.claude.json, ~/.codex/, ~/.cursor/, ~/.gemini/).\"));\n log(`Add this to your agent's MCP config manually:\\n${JSON.stringify({ mcpServers: { debuganything: MCP_ENTRY } }, null, 2)}`);\n return { written, skipped };\n }\n log(pc.bold(\"Debug Anything setup will:\"));\n const plans: Array<{ agent: AgentTarget; path: string; next: string; changed: boolean; existing: string | null }> = [];\n for (const agent of wanted) {\n const path = agent.configPath(home);\n const existing = existsSync(path) ? await readFile(path, \"utf8\") : null;\n const { next, changed } = agent.format === \"json\" ? patchJsonConfig(existing, agent.key) : patchTomlConfig(existing);\n plans.push({ agent, path, next, changed, existing });\n log(`\\n${pc.cyan(\"•\")} ${pc.bold(agent.label)} → ${path} ${changed ? \"\" : pc.dim(\"(already registered)\")}`);\n if (changed) {\n log(pc.dim(agent.format === \"json\" ? JSON.stringify({ [agent.key]: { debuganything: MCP_ENTRY } }, null, 2) : TOML_BLOCK.trim()));\n }\n if (agent.skillsDir) log(pc.dim(` + copy skills/debug-anything/SKILL.md → ${agent.skillsDir(home)}/SKILL.md`));\n if (agent.id === \"claude\" && opts.hooks !== false) {\n log(pc.dim(` + write ${join(home, \".debuganything\", \"hooks\", \"debuganything-hook.mjs\")} and register it in ${join(home, \".claude\", \"settings.json\")} (hooks.PostToolUse[Bash], hooks.UserPromptSubmit) so the agent reaches for Debug Anything on EADDRINUSE / OOM / timeouts / \"it hangs\" without being asked. Disable with --no-hooks.`));\n }\n }\n if (opts.dryRun) {\n log(pc.dim(\"\\n--dry-run: nothing written.\"));\n return { written, skipped: plans.map((p) => p.path) };\n }\n if (!opts.yes) {\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n const ans = (await rl.question(\"\\nApply these changes? Existing files are backed up to *.bak-debuganything [y/N] \")).trim().toLowerCase();\n rl.close();\n if (ans !== \"y\" && ans !== \"yes\") {\n log(\"aborted; nothing written\");\n return { written, skipped: plans.map((p) => p.path) };\n }\n }\n const skillSrc = skillSourceDir();\n for (const p of plans) {\n if (p.changed) {\n await mkdir(dirname(p.path), { recursive: true });\n if (p.existing !== null) await copyFile(p.path, `${p.path}.bak-debuganything`);\n await writeFile(p.path, p.next);\n written.push(p.path);\n } else skipped.push(p.path);\n if (p.agent.skillsDir) {\n const dir = p.agent.skillsDir(home);\n await mkdir(dir, { recursive: true });\n await copyFile(join(skillSrc, \"SKILL.md\"), join(dir, \"SKILL.md\"));\n written.push(join(dir, \"SKILL.md\"));\n }\n if (p.agent.id === \"claude\" && opts.hooks !== false) {\n const hookDir = join(home, \".debuganything\", \"hooks\");\n await mkdir(hookDir, { recursive: true });\n const script = join(hookDir, \"debuganything-hook.mjs\");\n await writeFile(script, HOOK_SCRIPT, { mode: 0o755 });\n written.push(script);\n const settingsPath = join(home, \".claude\", \"settings.json\");\n const existing = existsSync(settingsPath) ? await readFile(settingsPath, \"utf8\") : null;\n const { next, changed } = patchClaudeHooks(existing, script);\n if (changed) {\n await mkdir(dirname(settingsPath), { recursive: true });\n if (existing !== null) await copyFile(settingsPath, `${settingsPath}.bak-debuganything`);\n await writeFile(settingsPath, next);\n written.push(settingsPath);\n }\n }\n }\n log(`\\n${pc.green(\"Done.\")} ${written.length} file(s) written. ${pc.bold(\"Restart your agent\")} and ask it: “My API gets slow after a few minutes — attach to it and tell me why, with evidence.”`);\n return { written, skipped };\n}\n\nexport async function fileExists(p: string): Promise<boolean> {\n try {\n await access(p);\n return true;\n } catch {\n return false;\n }\n}\n\n\n// ---------------------------------------------------------------------------\n// `debuganything init` — project-level setup so every teammate's agent gets Debug Anything\n// ---------------------------------------------------------------------------\n\nexport const AGENTS_MD_BLOCK = `## Runtime debugging with Debug Anything\n\nDEA (Debug Anything) is registered as an MCP server for this repo (\\`.mcp.json\\`, \\`.cursor/mcp.json\\`).\nWhen something *running* misbehaves — a command hangs, a test times out, you see EADDRINUSE / ECONNREFUSED /\nEMFILE / exit 137 / OOM / a segfault, a server you started does not respond, or the user says slow, hang,\nleak or crash — attach and measure before theorizing: \\`readiness\\` → \\`attach\\` → \\`dump_stacks\\` /\n\\`profile_cpu\\` / \\`inspect_sockets\\` / \\`snapshot_memory\\` / \\`container_info\\` → \\`detach\\`. Cite evidence ids\n(e.g. \\`profile_cpu#2/e3\\`). Read-only tools need no permission; \\`capture_traffic\\` needs the user's consent.\nAfter a performance fix, re-measure and \\`diff_runs\\` to prove it; put the evidence ids in the PR.\nDocs: https://github.com/shreyassaw5394/debuganything\n`;\n\nexport interface InitOptions {\n dir?: string;\n dryRun?: boolean;\n log?: (s: string) => void;\n}\n\n/** Append the Debug Anything block to an AGENTS.md/CLAUDE.md-style file if not present. Pure. */\nexport function patchAgentsMd(existing: string | null): { next: string; changed: boolean } {\n const text = existing ?? \"\";\n if (text.includes(\"## Runtime debugging with Debug Anything\")) return { next: text, changed: false };\n const sep = text.length === 0 ? \"\" : text.endsWith(\"\\n\\n\") ? \"\" : text.endsWith(\"\\n\") ? \"\\n\" : \"\\n\\n\";\n return { next: text + sep + AGENTS_MD_BLOCK, changed: true };\n}\n\nexport async function init(opts: InitOptions = {}): Promise<{ written: string[]; skipped: string[] }> {\n const dir = opts.dir ?? process.cwd();\n const log = opts.log ?? ((s: string) => process.stdout.write(s + \"\\n\"));\n const written: string[] = [];\n const skipped: string[] = [];\n const plans: Array<{ path: string; next: string; changed: boolean; existing: string | null; what: string }> = [];\n for (const rel of [\".mcp.json\", join(\".cursor\", \"mcp.json\")]) {\n const path = join(dir, rel);\n const existing = existsSync(path) ? await readFile(path, \"utf8\") : null;\n const { next, changed } = patchJsonConfig(existing);\n plans.push({ path, next, changed, existing, what: \"MCP server entry\" });\n }\n // One agent-instructions file: prefer existing AGENTS.md, then CLAUDE.md, else create AGENTS.md.\n const candidates = [\"AGENTS.md\", \"CLAUDE.md\"].map((f) => join(dir, f));\n const target = candidates.find((p) => existsSync(p)) ?? candidates[0]!;\n const existingMd = existsSync(target) ? await readFile(target, \"utf8\") : null;\n const md = patchAgentsMd(existingMd);\n plans.push({ path: target, next: md.next, changed: md.changed, existing: existingMd, what: \"runtime-debugging rule for agents\" });\n\n log(pc.bold(`Debug Anything init in ${dir} will:`));\n for (const p of plans) log(`${pc.cyan(\"•\")} ${p.path} ${p.changed ? pc.dim(`(+ ${p.what})`) : pc.dim(\"(already set up)\")}`);\n if (opts.dryRun) {\n log(pc.dim(\"--dry-run: nothing written.\"));\n return { written, skipped: plans.map((p) => p.path) };\n }\n for (const p of plans) {\n if (!p.changed) {\n skipped.push(p.path);\n continue;\n }\n await mkdir(dirname(p.path), { recursive: true });\n if (p.existing !== null) await copyFile(p.path, `${p.path}.bak-debuganything`);\n await writeFile(p.path, p.next);\n written.push(p.path);\n }\n log(`${pc.green(\"Done.\")} Commit these files so every teammate's agent gets Debug Anything. Each person still runs ${pc.bold(\"npx debuganything setup\")} once for the skill and hook.`);\n return { written, skipped };\n}\n"],"mappings":";;;;;;AAIA,SAAS,UAAU,WAAW,OAAO,UAAU,cAAc;AAC7D,SAAS,kBAAkB;AAC3B,SAAS,eAAe;AACxB,SAAS,SAAS,YAAY;AAC9B,SAAS,qBAAqB;AAC9B,SAAS,uBAAuB;AAChC,OAAO,QAAQ;AAGR,IAAM,YAAY,EAAE,SAAS,OAAO,MAAM,CAAC,MAAM,wBAAwB,OAAO,EAAE;AAclF,IAAM,SAAwB;AAAA,EACnC;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,YAAY,CAAC,MAAM,KAAK,GAAG,cAAc;AAAA,IACzC,QAAQ,CAAC,MAAM,WAAW,KAAK,GAAG,cAAc,CAAC,KAAK,WAAW,KAAK,GAAG,WAAW,eAAe,CAAC,KAAK,WAAW,KAAK,GAAG,SAAS,CAAC;AAAA,IACtI,QAAQ;AAAA,IACR,KAAK;AAAA,IACL,WAAW,CAAC,MAAM,KAAK,GAAG,WAAW,UAAU,gBAAgB;AAAA,EACjE;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,YAAY,CAAC,MAAM,KAAK,GAAG,UAAU,aAAa;AAAA,IAClD,QAAQ,CAAC,MAAM,WAAW,KAAK,GAAG,QAAQ,CAAC;AAAA,IAC3C,QAAQ;AAAA,IACR,KAAK;AAAA,IACL,WAAW,CAAC,MAAM,KAAK,GAAG,UAAU,UAAU,gBAAgB;AAAA,EAChE;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,YAAY,CAAC,MAAM,KAAK,GAAG,WAAW,UAAU;AAAA,IAChD,QAAQ,CAAC,MAAM,WAAW,KAAK,GAAG,SAAS,CAAC;AAAA,IAC5C,QAAQ;AAAA,IACR,KAAK;AAAA,EACP;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,YAAY,CAAC,MAAM,KAAK,GAAG,WAAW,eAAe;AAAA,IACrD,QAAQ,CAAC,MAAM,WAAW,KAAK,GAAG,SAAS,CAAC;AAAA,IAC5C,QAAQ;AAAA,IACR,KAAK;AAAA,IACL,WAAW,CAAC,MAAM,KAAK,GAAG,WAAW,UAAU,gBAAgB;AAAA,EACjE;AACF;AAGO,SAAS,gBAAgB,UAAyB,MAAM,cAAkD;AAC/G,MAAI,MAA+B,CAAC;AACpC,MAAI,YAAY,SAAS,KAAK,GAAG;AAC/B,QAAI;AACF,YAAM,KAAK,MAAM,QAAQ;AAAA,IAC3B,QAAQ;AACN,YAAM,IAAI,MAAM,6DAA6D;AAAA,IAC/E;AAAA,EACF;AACA,QAAM,UAAY,IAAI,GAAG,KAA6C,CAAC;AACvE,QAAM,UAAU,KAAK,UAAU,QAAQ,iBAAiB,IAAI;AAC5D,QAAM,UAAU,KAAK,UAAU,SAAS;AACxC,MAAI,YAAY,QAAS,QAAO,EAAE,MAAM,YAAY,KAAK,UAAU,KAAK,MAAM,CAAC,IAAI,MAAM,SAAS,MAAM;AACxG,UAAQ,gBAAgB,EAAE,SAAS,UAAU,SAAS,MAAM,CAAC,GAAG,UAAU,IAAI,EAAE;AAChF,MAAI,GAAG,IAAI;AACX,SAAO,EAAE,MAAM,KAAK,UAAU,KAAK,MAAM,CAAC,IAAI,MAAM,SAAS,KAAK;AACpE;AAGO,SAAS,iBAAiB,UAAyB,YAAwD;AAChH,MAAI,MAA2B,CAAC;AAChC,MAAI,YAAY,SAAS,KAAK,GAAG;AAC/B,QAAI;AACF,YAAM,KAAK,MAAM,QAAQ;AAAA,IAC3B,QAAQ;AACN,YAAM,IAAI,MAAM,oEAAoE;AAAA,IACtF;AAAA,EACF;AACA,QAAM,MAAM,SAAS,UAAU;AAC/B,QAAM,QAAS,IAAI,UAAU,CAAC;AAC9B,MAAI,UAAU;AACd,QAAM,SAAS,CAAC,OAAe,YAAgC;AAC7D,UAAM,OAAe,MAAM,KAAK,MAAM,CAAC;AACvC,UAAM,MAAM,KAAK,KAAK,CAAC,MAAM,MAAM,QAAQ,GAAG,KAAK,KAAK,EAAE,MAAM,KAAK,CAAC,MAAW,OAAO,GAAG,YAAY,YAAY,EAAE,QAAQ,SAAS,wBAAwB,CAAC,CAAC;AAChK,QAAI,IAAK;AACT,SAAK,KAAK,EAAE,GAAI,UAAU,EAAE,QAAQ,IAAI,CAAC,GAAI,OAAO,CAAC,EAAE,MAAM,WAAW,SAAS,KAAK,SAAS,GAAG,CAAC,EAAE,CAAC;AACtG,cAAU;AAAA,EACZ;AACA,SAAO,eAAe,MAAM;AAC5B,SAAO,oBAAoB,MAAS;AACpC,SAAO,EAAE,MAAM,KAAK,UAAU,KAAK,MAAM,CAAC,IAAI,MAAM,QAAQ;AAC9D;AAEO,IAAM,aAAa;AAAA;AAAA;AAAA;AAGnB,SAAS,gBAAgB,UAA6D;AAC3F,QAAM,OAAO,YAAY;AACzB,QAAM,KAAK;AACX,MAAI,GAAG,KAAK,IAAI,GAAG;AACjB,UAAM,UAAU,GAAG,KAAK,IAAI,EAAG,CAAC;AAChC,QAAI,QAAQ,KAAK,MAAM,WAAW,KAAK,EAAG,QAAO,EAAE,MAAM,MAAM,SAAS,MAAM;AAC9E,WAAO,EAAE,MAAM,KAAK,QAAQ,IAAI,UAAU,GAAG,SAAS,KAAK;AAAA,EAC7D;AACA,QAAM,MAAM,KAAK,UAAU,CAAC,KAAK,SAAS,IAAI,IAAI,SAAS,KAAK,SAAS,OAAO;AAChF,SAAO,EAAE,MAAM,OAAO,MAAM,YAAY,SAAS,KAAK;AACxD;AAEA,SAAS,iBAAyB;AAEhC,QAAM,OAAO,QAAQ,cAAc,YAAY,GAAG,CAAC;AACnD,aAAW,QAAQ,CAAC,KAAK,MAAM,MAAM,UAAU,gBAAgB,GAAG,KAAK,MAAM,MAAM,MAAM,UAAU,gBAAgB,CAAC,GAAG;AACrH,QAAI,WAAW,KAAK,MAAM,UAAU,CAAC,EAAG,QAAO;AAAA,EACjD;AACA,QAAM,IAAI,MAAM,8DAA8D;AAChF;AAaA,eAAsB,MAAM,OAAqB,CAAC,GAAsD;AACtG,QAAM,OAAO,KAAK,QAAQ,QAAQ;AAClC,QAAM,MAAM,KAAK,QAAQ,CAAC,MAAc,QAAQ,OAAO,MAAM,IAAI,IAAI;AACrE,QAAM,SAAS,OAAO,OAAO,CAAC,MAAO,KAAK,SAAS,KAAK,OAAO,SAAS,EAAE,EAAE,IAAI,EAAE,OAAO,IAAI,CAAE;AAC/F,QAAM,UAAoB,CAAC;AAC3B,QAAM,UAAoB,CAAC;AAC3B,MAAI,OAAO,WAAW,GAAG;AACvB,QAAI,GAAG,OAAO,6FAA6F,CAAC;AAC5G,QAAI;AAAA,EAAkD,KAAK,UAAU,EAAE,YAAY,EAAE,eAAe,UAAU,EAAE,GAAG,MAAM,CAAC,CAAC,EAAE;AAC7H,WAAO,EAAE,SAAS,QAAQ;AAAA,EAC5B;AACA,MAAI,GAAG,KAAK,4BAA4B,CAAC;AACzC,QAAM,QAA8G,CAAC;AACrH,aAAW,SAAS,QAAQ;AAC1B,UAAM,OAAO,MAAM,WAAW,IAAI;AAClC,UAAM,WAAW,WAAW,IAAI,IAAI,MAAM,SAAS,MAAM,MAAM,IAAI;AACnE,UAAM,EAAE,MAAM,QAAQ,IAAI,MAAM,WAAW,SAAS,gBAAgB,UAAU,MAAM,GAAG,IAAI,gBAAgB,QAAQ;AACnH,UAAM,KAAK,EAAE,OAAO,MAAM,MAAM,SAAS,SAAS,CAAC;AACnD,QAAI;AAAA,EAAK,GAAG,KAAK,QAAG,CAAC,IAAI,GAAG,KAAK,MAAM,KAAK,CAAC,WAAM,IAAI,IAAI,UAAU,KAAK,GAAG,IAAI,sBAAsB,CAAC,EAAE;AAC1G,QAAI,SAAS;AACX,UAAI,GAAG,IAAI,MAAM,WAAW,SAAS,KAAK,UAAU,EAAE,CAAC,MAAM,GAAG,GAAG,EAAE,eAAe,UAAU,EAAE,GAAG,MAAM,CAAC,IAAI,WAAW,KAAK,CAAC,CAAC;AAAA,IAClI;AACA,QAAI,MAAM,UAAW,KAAI,GAAG,IAAI,kDAA6C,MAAM,UAAU,IAAI,CAAC,WAAW,CAAC;AAC9G,QAAI,MAAM,OAAO,YAAY,KAAK,UAAU,OAAO;AACjD,UAAI,GAAG,IAAI,aAAa,KAAK,MAAM,kBAAkB,SAAS,wBAAwB,CAAC,uBAAuB,KAAK,MAAM,WAAW,eAAe,CAAC,sLAAsL,CAAC;AAAA,IAC7U;AAAA,EACF;AACA,MAAI,KAAK,QAAQ;AACf,QAAI,GAAG,IAAI,+BAA+B,CAAC;AAC3C,WAAO,EAAE,SAAS,SAAS,MAAM,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE;AAAA,EACtD;AACA,MAAI,CAAC,KAAK,KAAK;AACb,UAAM,KAAK,gBAAgB,EAAE,OAAO,QAAQ,OAAO,QAAQ,QAAQ,OAAO,CAAC;AAC3E,UAAM,OAAO,MAAM,GAAG,SAAS,mFAAmF,GAAG,KAAK,EAAE,YAAY;AACxI,OAAG,MAAM;AACT,QAAI,QAAQ,OAAO,QAAQ,OAAO;AAChC,UAAI,0BAA0B;AAC9B,aAAO,EAAE,SAAS,SAAS,MAAM,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE;AAAA,IACtD;AAAA,EACF;AACA,QAAM,WAAW,eAAe;AAChC,aAAW,KAAK,OAAO;AACrB,QAAI,EAAE,SAAS;AACb,YAAM,MAAM,QAAQ,EAAE,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AAChD,UAAI,EAAE,aAAa,KAAM,OAAM,SAAS,EAAE,MAAM,GAAG,EAAE,IAAI,oBAAoB;AAC7E,YAAM,UAAU,EAAE,MAAM,EAAE,IAAI;AAC9B,cAAQ,KAAK,EAAE,IAAI;AAAA,IACrB,MAAO,SAAQ,KAAK,EAAE,IAAI;AAC1B,QAAI,EAAE,MAAM,WAAW;AACrB,YAAM,MAAM,EAAE,MAAM,UAAU,IAAI;AAClC,YAAM,MAAM,KAAK,EAAE,WAAW,KAAK,CAAC;AACpC,YAAM,SAAS,KAAK,UAAU,UAAU,GAAG,KAAK,KAAK,UAAU,CAAC;AAChE,cAAQ,KAAK,KAAK,KAAK,UAAU,CAAC;AAAA,IACpC;AACA,QAAI,EAAE,MAAM,OAAO,YAAY,KAAK,UAAU,OAAO;AACnD,YAAM,UAAU,KAAK,MAAM,kBAAkB,OAAO;AACpD,YAAM,MAAM,SAAS,EAAE,WAAW,KAAK,CAAC;AACxC,YAAM,SAAS,KAAK,SAAS,wBAAwB;AACrD,YAAM,UAAU,QAAQ,aAAa,EAAE,MAAM,IAAM,CAAC;AACpD,cAAQ,KAAK,MAAM;AACnB,YAAM,eAAe,KAAK,MAAM,WAAW,eAAe;AAC1D,YAAM,WAAW,WAAW,YAAY,IAAI,MAAM,SAAS,cAAc,MAAM,IAAI;AACnF,YAAM,EAAE,MAAM,QAAQ,IAAI,iBAAiB,UAAU,MAAM;AAC3D,UAAI,SAAS;AACX,cAAM,MAAM,QAAQ,YAAY,GAAG,EAAE,WAAW,KAAK,CAAC;AACtD,YAAI,aAAa,KAAM,OAAM,SAAS,cAAc,GAAG,YAAY,oBAAoB;AACvF,cAAM,UAAU,cAAc,IAAI;AAClC,gBAAQ,KAAK,YAAY;AAAA,MAC3B;AAAA,IACF;AAAA,EACF;AACA,MAAI;AAAA,EAAK,GAAG,MAAM,OAAO,CAAC,IAAI,QAAQ,MAAM,qBAAqB,GAAG,KAAK,oBAAoB,CAAC,mHAAoG;AAClM,SAAO,EAAE,SAAS,QAAQ;AAC5B;AAEA,eAAsB,WAAW,GAA6B;AAC5D,MAAI;AACF,UAAM,OAAO,CAAC;AACd,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAOO,IAAM,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAmBxB,SAAS,cAAc,UAA6D;AACzF,QAAM,OAAO,YAAY;AACzB,MAAI,KAAK,SAAS,0CAA0C,EAAG,QAAO,EAAE,MAAM,MAAM,SAAS,MAAM;AACnG,QAAM,MAAM,KAAK,WAAW,IAAI,KAAK,KAAK,SAAS,MAAM,IAAI,KAAK,KAAK,SAAS,IAAI,IAAI,OAAO;AAC/F,SAAO,EAAE,MAAM,OAAO,MAAM,iBAAiB,SAAS,KAAK;AAC7D;AAEA,eAAsB,KAAK,OAAoB,CAAC,GAAsD;AACpG,QAAM,MAAM,KAAK,OAAO,QAAQ,IAAI;AACpC,QAAM,MAAM,KAAK,QAAQ,CAAC,MAAc,QAAQ,OAAO,MAAM,IAAI,IAAI;AACrE,QAAM,UAAoB,CAAC;AAC3B,QAAM,UAAoB,CAAC;AAC3B,QAAM,QAAwG,CAAC;AAC/G,aAAW,OAAO,CAAC,aAAa,KAAK,WAAW,UAAU,CAAC,GAAG;AAC5D,UAAM,OAAO,KAAK,KAAK,GAAG;AAC1B,UAAM,WAAW,WAAW,IAAI,IAAI,MAAM,SAAS,MAAM,MAAM,IAAI;AACnE,UAAM,EAAE,MAAM,QAAQ,IAAI,gBAAgB,QAAQ;AAClD,UAAM,KAAK,EAAE,MAAM,MAAM,SAAS,UAAU,MAAM,mBAAmB,CAAC;AAAA,EACxE;AAEA,QAAM,aAAa,CAAC,aAAa,WAAW,EAAE,IAAI,CAAC,MAAM,KAAK,KAAK,CAAC,CAAC;AACrE,QAAM,SAAS,WAAW,KAAK,CAAC,MAAM,WAAW,CAAC,CAAC,KAAK,WAAW,CAAC;AACpE,QAAM,aAAa,WAAW,MAAM,IAAI,MAAM,SAAS,QAAQ,MAAM,IAAI;AACzE,QAAM,KAAK,cAAc,UAAU;AACnC,QAAM,KAAK,EAAE,MAAM,QAAQ,MAAM,GAAG,MAAM,SAAS,GAAG,SAAS,UAAU,YAAY,MAAM,oCAAoC,CAAC;AAEhI,MAAI,GAAG,KAAK,0BAA0B,GAAG,QAAQ,CAAC;AAClD,aAAW,KAAK,MAAO,KAAI,GAAG,GAAG,KAAK,QAAG,CAAC,IAAI,EAAE,IAAI,IAAI,EAAE,UAAU,GAAG,IAAI,MAAM,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,kBAAkB,CAAC,EAAE;AAC1H,MAAI,KAAK,QAAQ;AACf,QAAI,GAAG,IAAI,6BAA6B,CAAC;AACzC,WAAO,EAAE,SAAS,SAAS,MAAM,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE;AAAA,EACtD;AACA,aAAW,KAAK,OAAO;AACrB,QAAI,CAAC,EAAE,SAAS;AACd,cAAQ,KAAK,EAAE,IAAI;AACnB;AAAA,IACF;AACA,UAAM,MAAM,QAAQ,EAAE,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AAChD,QAAI,EAAE,aAAa,KAAM,OAAM,SAAS,EAAE,MAAM,GAAG,EAAE,IAAI,oBAAoB;AAC7E,UAAM,UAAU,EAAE,MAAM,EAAE,IAAI;AAC9B,YAAQ,KAAK,EAAE,IAAI;AAAA,EACrB;AACA,MAAI,GAAG,GAAG,MAAM,OAAO,CAAC,6FAA6F,GAAG,KAAK,yBAAyB,CAAC,+BAA+B;AACtL,SAAO,EAAE,SAAS,QAAQ;AAC5B;","names":[]}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
containerIdFromCgroup,
|
|
4
|
+
descendantPids,
|
|
5
|
+
detectRuntime,
|
|
6
|
+
exePath,
|
|
7
|
+
guessKind,
|
|
8
|
+
listProcesses,
|
|
9
|
+
parsePs,
|
|
10
|
+
processInfo,
|
|
11
|
+
resolvePid,
|
|
12
|
+
resolveTarget,
|
|
13
|
+
spawnCommand,
|
|
14
|
+
splitArgs,
|
|
15
|
+
targetPids,
|
|
16
|
+
threadIds
|
|
17
|
+
} from "./chunk-5RAKTYW5.js";
|
|
18
|
+
import "./chunk-D742WJPT.js";
|
|
19
|
+
import "./chunk-STFC7C5C.js";
|
|
20
|
+
export {
|
|
21
|
+
containerIdFromCgroup,
|
|
22
|
+
descendantPids,
|
|
23
|
+
detectRuntime,
|
|
24
|
+
exePath,
|
|
25
|
+
guessKind,
|
|
26
|
+
listProcesses,
|
|
27
|
+
parsePs,
|
|
28
|
+
processInfo,
|
|
29
|
+
resolvePid,
|
|
30
|
+
resolveTarget,
|
|
31
|
+
spawnCommand,
|
|
32
|
+
splitArgs,
|
|
33
|
+
targetPids,
|
|
34
|
+
threadIds
|
|
35
|
+
};
|
|
36
|
+
//# sourceMappingURL=target-WJE4WFYA.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "debuganything",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Debug Anything: observe any running process and get evidence-backed findings.",
|
|
5
|
+
"mcpServers": {
|
|
6
|
+
"debuganything": { "command": "npx", "args": ["-y", "debuganything@latest", "serve"] }
|
|
7
|
+
},
|
|
8
|
+
"contextFileName": "GEMINI.md"
|
|
9
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Debug Anything proactive hook for Claude Code. Installed by `debuganything setup`; remove the hooks entries in
|
|
3
|
+
// ~/.claude/settings.json (or run `debuganything setup --no-hooks`) to disable. Reads the hook event on stdin,
|
|
4
|
+
// prints additional context when command output or the prompt shows a runtime-problem signal.
|
|
5
|
+
const TRIGGERS = [{"id":"port_busy","pattern":"EADDRINUSE|address already in use|port (\\d+) is (already )?in use|bind\\(\\): Address already in use","flags":"i","scope":"both","hint":"run `debuganything port <port>` (or inspect_sockets) to see which pid holds the port; never kill it blindly"},{"id":"conn_refused","pattern":"ECONNREFUSED|connection refused|ECONNRESET|socket hang up|ETIMEDOUT|connect timed out|EHOSTUNREACH","flags":"i","scope":"output","hint":"attach Debug Anything to the client and run inspect_sockets / trace_syscalls (network) to see which remote fails and how"},{"id":"fd_exhaustion","pattern":"EMFILE|too many open files|ENFILE","flags":"i","scope":"both","hint":"attach Debug Anything and run inspect_files (fd growth, deleted-but-open) and inspect_sockets (CLOSE_WAIT pileups)"},{"id":"oom","pattern":"OOMKilled|Out of memory|oom-kill|exit(ed)? (code|status)[ =:]*137|Killed process \\d+|JavaScript heap out of memory|MemoryError|Cannot allocate memory","flags":"i","scope":"both","hint":"attach Debug Anything: container_info (OOM/limits) and snapshot_memory twice around load, then diff_runs to name the retainer"},{"id":"timeout","pattern":"timed? ?out after|Timeout - Async callback|Test timeout of|exceeded timeout|context deadline exceeded|TimeoutError","flags":"i","scope":"output","hint":"if the process under test is still running, attach Debug Anything and run dump_stacks (waiting on a lock? a socket?) before changing timeouts"},{"id":"hang_words","pattern":"\\b(hangs?|hanging|stuck|frozen|freezes|deadlock(ed)?|not responding|never (returns|finishes|completes))\\b","flags":"i","scope":"prompt","hint":"attach Debug Anything and run dump_stacks first — do not theorize about the hang from the code"},{"id":"slow_words","pattern":"\\b(slow|slower|latency|p99|p95|takes forever|sluggish|cpu (is )?(pegged|at 100)|100% cpu)\\b","flags":"i","scope":"prompt","hint":"attach Debug Anything and run profile_cpu then trace_syscalls (file,network) — measure before optimizing"},{"id":"leak_words","pattern":"\\b(memory (leak|keeps growing|grows|usage grows)|leaking|rss (grows|keeps)|heap (grows|keeps growing)|out of memory)\\b","flags":"i","scope":"prompt","hint":"attach Debug Anything, snapshot_memory, exercise the target, snapshot_memory again, diff_runs"},{"id":"crash_words","pattern":"\\b(crashed|crashes|keeps restarting|restart loop|CrashLoopBackOff|segfault|SIGSEGV|core dumped|exit code 1[34][0-9])\\b","flags":"i","scope":"both","hint":"attach Debug Anything to the container/pid and run container_info (exit code, OOM, restarts, log tail)"},{"id":"segv","pattern":"Segmentation fault|SIGSEGV|SIGABRT|SIGBUS|core dumped|fatal error: unexpected signal","flags":"i","scope":"output","hint":"if it restarted, attach Debug Anything and dump_stacks/profile_cpu to catch the lead-up; hand the symbol to a step debugger or a static-analysis tool"}];
|
|
6
|
+
let raw = "";
|
|
7
|
+
process.stdin.setEncoding("utf8");
|
|
8
|
+
process.stdin.on("data", (c) => (raw += c));
|
|
9
|
+
process.stdin.on("end", () => {
|
|
10
|
+
let ev;
|
|
11
|
+
try { ev = JSON.parse(raw); } catch { return; }
|
|
12
|
+
const event = ev.hook_event_name || "";
|
|
13
|
+
let text = "";
|
|
14
|
+
let scope = "output";
|
|
15
|
+
if (event === "UserPromptSubmit") { text = String(ev.prompt || ""); scope = "prompt"; }
|
|
16
|
+
else if (event === "PostToolUse") {
|
|
17
|
+
if (ev.tool_name !== "Bash") return;
|
|
18
|
+
const r = ev.tool_response || {};
|
|
19
|
+
text = [r.stdout, r.stderr, r.output, typeof r === "string" ? r : ""].filter(Boolean).join("\n");
|
|
20
|
+
const cmd = String((ev.tool_input || {}).command || "");
|
|
21
|
+
if (/\b(debuganything|debugany)\b/.test(cmd)) return; // don't nudge about Debug Anything's own output
|
|
22
|
+
} else return;
|
|
23
|
+
if (!text || text.length > 400000) return;
|
|
24
|
+
const hits = [];
|
|
25
|
+
for (const t of TRIGGERS) {
|
|
26
|
+
if (t.scope !== "both" && t.scope !== scope) continue;
|
|
27
|
+
const m = new RegExp(t.pattern, t.flags).exec(text);
|
|
28
|
+
if (m) hits.push({ id: t.id, hint: t.hint, match: m[0].slice(0, 80) });
|
|
29
|
+
}
|
|
30
|
+
if (!hits.length) return;
|
|
31
|
+
const where = scope === "output" ? "The last command's output" : "The user's message";
|
|
32
|
+
const lines = hits.slice(0, 3).map((h) => "- " + h.id + " (\u201c" + h.match + "\u201d): " + h.hint);
|
|
33
|
+
const additionalContext = ["[Debug Anything] " + where + " shows a runtime problem signal. Debug Anything is installed: observe before you theorize.", ...lines,
|
|
34
|
+
"Protocol: readiness (once per session) \u2192 attach <pid|container|command> \u2192 the tool above \u2192 detach. Cite evidence ids (e.g. dump_stacks#2/e3) in your answer; capture_traffic needs the user's consent. Skip this if the user explicitly said not to attach to anything."].join("\n");
|
|
35
|
+
process.stdout.write(JSON.stringify({ hookSpecificOutput: { hookEventName: event, additionalContext } }) + "\n");
|
|
36
|
+
});
|
package/hooks/hooks.json
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"hooks": {
|
|
3
|
+
"PostToolUse": [
|
|
4
|
+
{ "matcher": "Bash", "hooks": [{ "type": "command", "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/debuganything-hook.mjs\"", "timeout": 10 }] }
|
|
5
|
+
],
|
|
6
|
+
"UserPromptSubmit": [
|
|
7
|
+
{ "hooks": [{ "type": "command", "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/debuganything-hook.mjs\"", "timeout": 10 }] }
|
|
8
|
+
]
|
|
9
|
+
}
|
|
10
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,88 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "debuganything",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"description": "
|
|
3
|
+
"mcpName": "io.github.shreyassaw5394/debuganything",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"description": "Debug Anything: observe and diagnose any running process, container or pod from your agent with one CLI and MCP server. Evidence, not guesses.",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/shreyassaw5394/debuganything.git"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://debuganything.dev",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/shreyassaw5394/debuganything/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"debugging",
|
|
18
|
+
"observability",
|
|
19
|
+
"profiling",
|
|
20
|
+
"strace",
|
|
21
|
+
"py-spy",
|
|
22
|
+
"perf",
|
|
23
|
+
"flamegraph",
|
|
24
|
+
"tcpdump",
|
|
25
|
+
"lsof",
|
|
26
|
+
"gdb",
|
|
27
|
+
"heap-snapshot",
|
|
28
|
+
"agents",
|
|
29
|
+
"agent-skills",
|
|
30
|
+
"mcp",
|
|
31
|
+
"mcp-server",
|
|
32
|
+
"model-context-protocol",
|
|
33
|
+
"cli",
|
|
34
|
+
"debuganything"
|
|
35
|
+
],
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public"
|
|
38
|
+
},
|
|
39
|
+
"bin": {
|
|
40
|
+
"debuganything": "dist/cli.js",
|
|
41
|
+
"debugany": "dist/cli.js"
|
|
42
|
+
},
|
|
43
|
+
"engines": {
|
|
44
|
+
"node": ">=22"
|
|
45
|
+
},
|
|
46
|
+
"files": [
|
|
47
|
+
"dist",
|
|
48
|
+
"skills",
|
|
49
|
+
"server.json",
|
|
50
|
+
"README.md",
|
|
51
|
+
"DISCLAIMER.md",
|
|
52
|
+
"LICENSE",
|
|
53
|
+
"assets/debuganything-mark-512.png",
|
|
54
|
+
"assets/debuganything-logo.png",
|
|
55
|
+
".claude-plugin/plugin.json",
|
|
56
|
+
"hooks/hooks.json",
|
|
57
|
+
"hooks/debuganything-hook.mjs",
|
|
58
|
+
"gemini-extension.json",
|
|
59
|
+
"GEMINI.md"
|
|
60
|
+
],
|
|
61
|
+
"scripts": {
|
|
62
|
+
"build": "tsup",
|
|
63
|
+
"dev": "tsx src/cli.ts",
|
|
64
|
+
"serve": "tsx src/cli.ts serve",
|
|
65
|
+
"test": "vitest run",
|
|
66
|
+
"test:watch": "vitest",
|
|
67
|
+
"lint": "oxlint src tests",
|
|
68
|
+
"typecheck": "tsc --noEmit",
|
|
69
|
+
"check": "npm run typecheck && npm run lint && npm run test && npm run build",
|
|
70
|
+
"schemas": "tsx scripts/export-schemas.ts",
|
|
71
|
+
"prepublishOnly": "npm run check"
|
|
72
|
+
},
|
|
73
|
+
"dependencies": {
|
|
74
|
+
"@modelcontextprotocol/sdk": "^1.32.1",
|
|
75
|
+
"commander": "^15.0.0",
|
|
76
|
+
"execa": "^10.1.1",
|
|
77
|
+
"picocolors": "^1.1.1",
|
|
78
|
+
"zod": "^4.6.7"
|
|
79
|
+
},
|
|
80
|
+
"devDependencies": {
|
|
81
|
+
"@types/node": "^26.6.5",
|
|
82
|
+
"oxlint": "^1.88.0",
|
|
83
|
+
"tsup": "^9.0.0",
|
|
84
|
+
"tsx": "^4.23.15",
|
|
85
|
+
"typescript": "^7.0.2",
|
|
86
|
+
"vitest": "^5.0.5"
|
|
87
|
+
}
|
|
6
88
|
}
|
package/server.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-07-09/server.schema.json",
|
|
3
|
+
"name": "io.github.shreyassaw5394/debuganything",
|
|
4
|
+
"description": "Observe any running process from your agent. strace, py-spy, perf, lsof, tcpdump as evidence.",
|
|
5
|
+
"status": "active",
|
|
6
|
+
"repository": {
|
|
7
|
+
"url": "https://github.com/shreyassaw5394/debuganything",
|
|
8
|
+
"source": "github"
|
|
9
|
+
},
|
|
10
|
+
"version": "1.0.0",
|
|
11
|
+
"packages": [
|
|
12
|
+
{
|
|
13
|
+
"registryType": "npm",
|
|
14
|
+
"registryBaseUrl": "https://registry.npmjs.org",
|
|
15
|
+
"identifier": "debuganything",
|
|
16
|
+
"version": "1.0.0",
|
|
17
|
+
"runtimeHint": "npx",
|
|
18
|
+
"transport": {
|
|
19
|
+
"type": "stdio"
|
|
20
|
+
},
|
|
21
|
+
"packageArguments": [
|
|
22
|
+
{
|
|
23
|
+
"type": "positional",
|
|
24
|
+
"value": "serve"
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
}
|
|
28
|
+
],
|
|
29
|
+
"websiteUrl": "https://debuganything.dev"
|
|
30
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: debug-anything
|
|
3
|
+
description: Use when the user reports a bug that involves a running system (slow, hanging, leaking, crashing, port in use, weird network behavior) AND proactively, without being asked, whenever a command you ran hangs or times out, output shows EADDRINUSE / ECONNREFUSED / EMFILE / exit 137 / OOM / segfault, a server you started does not respond, or you just changed performance-sensitive code. Observe before you theorize.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Debug Anything
|
|
7
|
+
|
|
8
|
+
Debug Anything gives you eyes on running software: syscalls, CPU profiles, thread stacks,
|
|
9
|
+
sockets, file descriptors, packets, heap retainers, container state. Every
|
|
10
|
+
tool returns the same `ToolResult` with `findings` that cite `evidence` ids.
|
|
11
|
+
You only get to claim what the evidence supports.
|
|
12
|
+
|
|
13
|
+
## Use it without being asked
|
|
14
|
+
|
|
15
|
+
You are equipped with Debug Anything. Reach for it on your own when you see any of these;
|
|
16
|
+
do not wait for the user to say "debug this" and do not ask permission for
|
|
17
|
+
read-only observation (only `capture_traffic` needs consent):
|
|
18
|
+
|
|
19
|
+
| Signal | First move |
|
|
20
|
+
|---|---|
|
|
21
|
+
| A command you ran hangs, or a test times out and the process is still alive | `attach` it, `dump_stacks` |
|
|
22
|
+
| `EADDRINUSE` / "port already in use" | `debuganything port <n>` or `inspect_sockets`; report the pid, do not kill it |
|
|
23
|
+
| `ECONNREFUSED`, `ETIMEDOUT`, `socket hang up` | `inspect_sockets`, `trace_syscalls` (network) on the client |
|
|
24
|
+
| `EMFILE` / too many open files | `inspect_files` (growth, deleted-but-open), `inspect_sockets` (CLOSE_WAIT) |
|
|
25
|
+
| exit 137, `OOMKilled`, "JavaScript heap out of memory" | `container_info`, then `snapshot_memory` ×2 + `diff_runs` |
|
|
26
|
+
| Segfault / exit 139 / crash loop | `container_info`; if it restarted, `dump_stacks` + `profile_cpu` to catch the lead-up |
|
|
27
|
+
| The user says slow / p99 / CPU pegged | `profile_cpu`, then `trace_syscalls` (file,network) |
|
|
28
|
+
| The user says hang / stuck / frozen / deadlock | `dump_stacks` twice |
|
|
29
|
+
| The user says leak / memory keeps growing | `snapshot_memory`, load, `snapshot_memory`, `diff_runs` |
|
|
30
|
+
| You started a long-running service to test something | consider `attach --kind command` so its output and fds are captured from the start |
|
|
31
|
+
| You just changed performance-sensitive code | measure before and after with the same tool and `diff_runs`; put the evidence ids in the PR |
|
|
32
|
+
|
|
33
|
+
Keep it proportionate: `readiness` once per session, `attach`, one or two
|
|
34
|
+
tools, `detach`. Only attach to processes that belong to the user's project or
|
|
35
|
+
that they pointed you at. Say what you observed and what you could not
|
|
36
|
+
(`limitations`) in one short paragraph with evidence ids.
|
|
37
|
+
|
|
38
|
+
## Session protocol
|
|
39
|
+
|
|
40
|
+
1. **Always call `readiness` first** in a session. Read `tools_available` and
|
|
41
|
+
`tools_unavailable`; do not call tools that are unavailable, tell the user
|
|
42
|
+
what is missing and the install hint instead.
|
|
43
|
+
2. **Always `attach` before any other tool.** Pass a pid, container name, pod
|
|
44
|
+
(`namespace/name`) or a command (`kind: "command"` spawns it under
|
|
45
|
+
capture). Use the returned `session_id` for everything.
|
|
46
|
+
3. **Always `detach` at the end.** It stops captures and writes the report.
|
|
47
|
+
4. Prefer each result's `suggested_next`. Stop when the evidence answers the
|
|
48
|
+
question.
|
|
49
|
+
|
|
50
|
+
## Investigation orders
|
|
51
|
+
|
|
52
|
+
**Slow** (`dea_slow`): `profile_cpu` → `trace_syscalls` (network,file) →
|
|
53
|
+
`inspect_sockets` → `report`. CPU-bound if a few frames dominate self time;
|
|
54
|
+
otherwise look at blocked syscalls and remote hosts.
|
|
55
|
+
|
|
56
|
+
**Leak** (`dea_leak`): `snapshot_memory` → exercise the target → `snapshot_memory`
|
|
57
|
+
→ `diff_runs a=snapshot_memory#1 b=snapshot_memory#2` → `report`. The top
|
|
58
|
+
grower in retained size is the suspect. RSS up without heap up → native memory.
|
|
59
|
+
|
|
60
|
+
**Hang** (`dea_hang`): `dump_stacks` → `inspect_sockets` → `trace_syscalls`
|
|
61
|
+
(5 s) → `report`. Two threads each waiting on the other's lock = deadlock
|
|
62
|
+
(look for `lock_wait` evidence). One thread in a socket read = waiting on a
|
|
63
|
+
remote.
|
|
64
|
+
|
|
65
|
+
**Crash** (`dea_crash`): `container_info` (OOMKilled, exit code, restarts,
|
|
66
|
+
cgroup limits, log tail) → `report`. Exit 137 = SIGKILL (often OOM),
|
|
67
|
+
139 = SIGSEGV, 143 = SIGTERM.
|
|
68
|
+
|
|
69
|
+
**Port busy** (`dea_port_busy`): CLI `debuganything port <port>` or `attach` candidates
|
|
70
|
+
and `inspect_sockets`; report pid + command line. Never kill it yourself.
|
|
71
|
+
|
|
72
|
+
## Evidence rules
|
|
73
|
+
|
|
74
|
+
- **Never state a root cause without citing an evidence id** from a tool
|
|
75
|
+
result, e.g. "readFileSync dominates CPU (profile_cpu#1/e2)".
|
|
76
|
+
- Phrase unsupported ideas as hypotheses and name the tool that would test
|
|
77
|
+
them: "Hypothesis: downstream latency. Test: trace_syscalls network."
|
|
78
|
+
- Respect `confidence`: "observed" is read from tool output; "inferred" is a
|
|
79
|
+
reasoning step — say which is which.
|
|
80
|
+
- Read `limitations`. "sampled 10 s", "no symbols", "ran without root" change
|
|
81
|
+
what you may conclude.
|
|
82
|
+
|
|
83
|
+
## Consent and safety
|
|
84
|
+
|
|
85
|
+
- `capture_traffic` requires `consent: true`. **Ask the user before passing
|
|
86
|
+
it.** Packet captures can contain secrets.
|
|
87
|
+
- Debug Anything is read-only and never escalates privileges. If a tool returns
|
|
88
|
+
`ok: false` with a sudo command in `limitations`, show the user that command;
|
|
89
|
+
do not try to run it yourself.
|
|
90
|
+
- Payloads are redacted; raw artifacts under `~/.debuganything/sessions/` are not.
|
|
91
|
+
|
|
92
|
+
## After a fix
|
|
93
|
+
|
|
94
|
+
Re-run the same measuring tool and call `diff_runs` on the before/after run
|
|
95
|
+
ids to prove the fix. Put the diff's evidence ids in your summary.
|
|
96
|
+
|
|
97
|
+
## Handoffs
|
|
98
|
+
|
|
99
|
+
- **Step debugger.** When Debug Anything localizes a problem to a function or thread,
|
|
100
|
+
suggest attaching a step debugger (e.g. mcp-debugger) at `<file:function>`.
|
|
101
|
+
Debug Anything tells you where to look; a step debugger lets you look inside.
|
|
102
|
+
- **Static analysis.** When Debug Anything names a native symbol, shared library or binary you cannot read (a hot frame in `libfoo.so`, a thread parked in `pthread_mutex_lock` under `worker_b`), hand that symbol to a decompiler / static-analysis tool to see what it does; cite Debug Anything's evidence for the runtime facts and the static tool's for the code facts.
|