agent-dbg 0.1.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.
Files changed (99) hide show
  1. package/.bin/ndbg +0 -0
  2. package/.claude/settings.local.json +21 -0
  3. package/.claude/skills/ndbg-debugger/ndbg-debugger/SKILL.md +116 -0
  4. package/.claude/skills/ndbg-debugger/ndbg-debugger/references/commands.md +173 -0
  5. package/CLAUDE.md +43 -0
  6. package/PROGRESS.md +261 -0
  7. package/README.md +67 -0
  8. package/biome.json +41 -0
  9. package/ndbg-spec.md +958 -0
  10. package/package.json +30 -0
  11. package/src/cdp/client.ts +198 -0
  12. package/src/cdp/types.ts +16 -0
  13. package/src/cli/parser.ts +287 -0
  14. package/src/cli/registry.ts +7 -0
  15. package/src/cli/types.ts +24 -0
  16. package/src/commands/attach.ts +47 -0
  17. package/src/commands/blackbox-ls.ts +38 -0
  18. package/src/commands/blackbox-rm.ts +57 -0
  19. package/src/commands/blackbox.ts +48 -0
  20. package/src/commands/break-ls.ts +57 -0
  21. package/src/commands/break-rm.ts +40 -0
  22. package/src/commands/break-toggle.ts +42 -0
  23. package/src/commands/break.ts +145 -0
  24. package/src/commands/breakable.ts +69 -0
  25. package/src/commands/catch.ts +38 -0
  26. package/src/commands/console.ts +61 -0
  27. package/src/commands/continue.ts +46 -0
  28. package/src/commands/eval.ts +70 -0
  29. package/src/commands/exceptions.ts +61 -0
  30. package/src/commands/hotpatch.ts +67 -0
  31. package/src/commands/launch.ts +69 -0
  32. package/src/commands/logpoint.ts +78 -0
  33. package/src/commands/pause.ts +46 -0
  34. package/src/commands/props.ts +77 -0
  35. package/src/commands/restart-frame.ts +36 -0
  36. package/src/commands/run-to.ts +70 -0
  37. package/src/commands/scripts.ts +57 -0
  38. package/src/commands/search.ts +73 -0
  39. package/src/commands/sessions.ts +71 -0
  40. package/src/commands/set-return.ts +49 -0
  41. package/src/commands/set.ts +61 -0
  42. package/src/commands/source.ts +59 -0
  43. package/src/commands/sourcemap.ts +66 -0
  44. package/src/commands/stack.ts +64 -0
  45. package/src/commands/state.ts +124 -0
  46. package/src/commands/status.ts +57 -0
  47. package/src/commands/step.ts +50 -0
  48. package/src/commands/stop.ts +27 -0
  49. package/src/commands/vars.ts +71 -0
  50. package/src/daemon/client.ts +147 -0
  51. package/src/daemon/entry.ts +242 -0
  52. package/src/daemon/paths.ts +26 -0
  53. package/src/daemon/server.ts +185 -0
  54. package/src/daemon/session-blackbox.ts +41 -0
  55. package/src/daemon/session-breakpoints.ts +492 -0
  56. package/src/daemon/session-execution.ts +121 -0
  57. package/src/daemon/session-inspection.ts +701 -0
  58. package/src/daemon/session-mutation.ts +197 -0
  59. package/src/daemon/session-state.ts +258 -0
  60. package/src/daemon/session.ts +938 -0
  61. package/src/daemon/spawn.ts +53 -0
  62. package/src/formatter/errors.ts +15 -0
  63. package/src/formatter/source.ts +74 -0
  64. package/src/formatter/stack.ts +70 -0
  65. package/src/formatter/values.ts +269 -0
  66. package/src/formatter/variables.ts +20 -0
  67. package/src/main.ts +45 -0
  68. package/src/protocol/messages.ts +316 -0
  69. package/src/refs/ref-table.ts +120 -0
  70. package/src/refs/resolver.ts +24 -0
  71. package/src/sourcemap/resolver.ts +318 -0
  72. package/tests/fixtures/async-app.js +34 -0
  73. package/tests/fixtures/console-app.js +12 -0
  74. package/tests/fixtures/error-app.js +28 -0
  75. package/tests/fixtures/exception-app.js +6 -0
  76. package/tests/fixtures/inspect-app.js +10 -0
  77. package/tests/fixtures/mutation-app.js +9 -0
  78. package/tests/fixtures/simple-app.js +50 -0
  79. package/tests/fixtures/step-app.js +13 -0
  80. package/tests/fixtures/ts-app/src/app.ts +21 -0
  81. package/tests/fixtures/ts-app/tsconfig.json +14 -0
  82. package/tests/integration/blackbox.test.ts +135 -0
  83. package/tests/integration/break-extras.test.ts +241 -0
  84. package/tests/integration/breakpoint.test.ts +217 -0
  85. package/tests/integration/console.test.ts +275 -0
  86. package/tests/integration/execution.test.ts +247 -0
  87. package/tests/integration/inspection.test.ts +311 -0
  88. package/tests/integration/mutation.test.ts +178 -0
  89. package/tests/integration/session.test.ts +223 -0
  90. package/tests/integration/source.test.ts +209 -0
  91. package/tests/integration/sourcemap.test.ts +214 -0
  92. package/tests/integration/state.test.ts +208 -0
  93. package/tests/unit/cdp-client.test.ts +422 -0
  94. package/tests/unit/daemon.test.ts +286 -0
  95. package/tests/unit/formatter.test.ts +716 -0
  96. package/tests/unit/parser.test.ts +105 -0
  97. package/tests/unit/refs.test.ts +383 -0
  98. package/tests/unit/sourcemap.test.ts +236 -0
  99. package/tsconfig.json +32 -0
package/ndbg-spec.md ADDED
@@ -0,0 +1,958 @@
1
+ # ndbg — Node.js Debugger CLI for AI Agents
2
+
3
+ ## Specification v1.0
4
+
5
+ ---
6
+
7
+ ## 1. Vision
8
+
9
+ `ndbg` is a command-line debugger for Node.js designed specifically for AI coding agents (Claude Code, Codex, Cursor, Gemini CLI, etc.). It wraps the V8 Inspector Protocol (Chrome DevTools Protocol) behind a token-efficient, stateless CLI interface that lets agents autonomously set breakpoints, step through code, inspect variables, profile memory, and even hot-patch code — all through bash.
10
+
11
+ **Core principle:** every tool call should return maximum debugging insight for minimum token cost.
12
+
13
+ ---
14
+
15
+ ## 2. Design Philosophy
16
+
17
+ ### 2.1 Agent-first, human-usable
18
+
19
+ The primary consumer is an LLM agent operating via bash tool calls. Every design decision optimizes for:
20
+
21
+ - **Token efficiency** — compact output, no ANSI colors by default, no decorative chrome
22
+ - **Minimum round-trips** — composite commands, auto-state-return after execution commands
23
+ - **Referenceability** — `@ref` system so agents never type long identifiers
24
+ - **Progressive verbosity** — control output granularity per-call with flags
25
+ - **Actionable errors** — every error suggests the next valid command
26
+
27
+ Humans can use it too. `--color` flag enables ANSI colors for terminal use.
28
+
29
+ ### 2.2 CLI over MCP
30
+
31
+ ndbg is a CLI tool, not an MCP server. Rationale:
32
+
33
+ - **Zero setup** — `npm i -g ndbg` or `npx ndbg`, no MCP config files
34
+ - **Context efficient** — no JSON-RPC overhead, no schema in every call
35
+ - **Composable** — can pipe to grep/jq, chain with other bash commands
36
+ - **Universal** — works with any agent that has shell access, not just MCP clients
37
+ - **Reliable** — no persistent server process to crash or manage
38
+
39
+ ### 2.3 Daemon architecture
40
+
41
+ Debugging is inherently stateful (a process paused at a breakpoint). ndbg uses a background daemon model:
42
+
43
+ - `ndbg launch` starts a daemon that holds the WebSocket connection to the V8 inspector
44
+ - All subsequent CLI calls communicate with the daemon via a local Unix socket
45
+ - Each CLI invocation is fast and stateless from the agent's perspective
46
+ - The daemon manages the debug session lifecycle
47
+ - Daemon auto-terminates when the debugged process exits or after an idle timeout
48
+
49
+ ### 2.4 Built with Bun
50
+
51
+ - Written in TypeScript, compiled with `bun build --compile` to a single standalone binary
52
+ - Native WebSocket support for CDP communication
53
+ - Fast startup (~5ms for compiled binary)
54
+ - No runtime dependency — users don't need Bun or Node installed to use the compiled binary
55
+
56
+ ---
57
+
58
+ ## 3. The @ref System
59
+
60
+ Inspired by agent-browser's `@e1` element refs, ndbg assigns short stable references to every inspectable entity in its output. Agents use these refs instead of long object IDs, file paths, or frame indices.
61
+
62
+ ### 3.1 Ref types
63
+
64
+ | Prefix | Entity | Example | Lifetime |
65
+ |--------|--------|---------|----------|
66
+ | `@v` | Variable / value | `@v1`, `@v2` | Until next pause (step/continue) |
67
+ | `@f` | Stack frame | `@f0`, `@f1` | Until next pause |
68
+ | `@o` | Expanded object | `@o1`, `@o2` | Until session ends or `ndbg gc-refs` |
69
+ | `BP#` | Breakpoint | `BP#1`, `BP#2` | Until removed |
70
+ | `LP#` | Logpoint | `LP#1`, `LP#2` | Until removed |
71
+ | `HS#` | Heap snapshot | `HS#1`, `HS#2` | Until session ends |
72
+
73
+ ### 3.2 Ref usage
74
+
75
+ Refs can be used anywhere an identifier is expected:
76
+
77
+ ```bash
78
+ ndbg props @v1 # expand variable @v1
79
+ ndbg eval "@v1.retryCount" # use ref in expressions
80
+ ndbg set @v2 true # mutate variable @v2
81
+ ndbg frame @f1 # switch to stack frame @f1
82
+ ndbg restart-frame @f0 # restart frame @f0
83
+ ndbg break-rm BP#1 # remove breakpoint BP#1
84
+ ndbg heap diff HS#1 HS#2 # diff two heap snapshots
85
+ ```
86
+
87
+ ### 3.3 Ref resolution
88
+
89
+ The daemon maintains a ref table mapping short refs to V8 RemoteObjectIds, CallFrameIds, BreakpointIds, etc. The table is:
90
+
91
+ - **Regenerated** on each pause event (for `@v` and `@f` refs)
92
+ - **Append-only** for `@o` refs (expanded objects persist across pauses)
93
+ - **Stable** for `BP#`, `LP#`, `HS#` refs (persist until explicitly removed)
94
+
95
+ ---
96
+
97
+ ## 4. The State Snapshot
98
+
99
+ The `state` command is the primary "where am I?" command. It returns a structured view of the current debug context, analogous to agent-browser's `snapshot`.
100
+
101
+ ### 4.1 Default output
102
+
103
+ ```
104
+ ⏸ Paused at src/queue/processor.ts:47 (BP#1, hit #3) [resumed 1.2s ago]
105
+
106
+ Source:
107
+ 45│ async processJob(job: Job) {
108
+ 46│ const lock = await this.acquireLock(job.id);
109
+ → 47│ if (!lock) return;
110
+ 48│ const result = await this.execute(job);
111
+ 49│ await this.markComplete(job.id);
112
+
113
+ Locals:
114
+ @v1 job Job { id: "test-123", type: "email", retries: 2 }
115
+ @v2 lock false
116
+ @v3 this QueueProcessor { workerId: "worker-a", redis: [Redis] }
117
+
118
+ Stack:
119
+ @f0 processJob src/queue/processor.ts:47
120
+ @f1 poll src/queue/processor.ts:71
121
+ @f2 setTimeout cb node:internal/timers:573
122
+ ┊ async
123
+ @f3 QueueProcessor.start src/queue/processor.ts:12
124
+
125
+ Breakpoints:
126
+ BP#1 src/queue/processor.ts:47 (cond: job.id === 'test-123') hits: 3
127
+ BP#2 src/queue/processor.ts:31
128
+ ```
129
+
130
+ ### 4.2 Filtering flags
131
+
132
+ | Flag | Output |
133
+ |------|--------|
134
+ | (none) | Full state: source + locals + stack + breakpoints |
135
+ | `-v` / `--vars` | Locals only |
136
+ | `-s` / `--stack` | Stack trace only |
137
+ | `-b` / `--breakpoints` | Breakpoints/logpoints only |
138
+ | `-c` / `--code` | Source context only |
139
+ | `--compact` | One-line-per-section summary |
140
+ | `--depth N` | Object expansion depth (default: 1) |
141
+ | `--lines N` | Source context lines above/below (default: 3) |
142
+ | `--frame @fN` | Show state from perspective of frame N |
143
+ | `--all-scopes` | Include closure and global scope, not just locals |
144
+ | `--json` | Full state as JSON (for programmatic use) |
145
+
146
+ ### 4.3 Auto-state return
147
+
148
+ **Every execution command returns a state snapshot automatically.** This is a critical design choice that halves the number of tool calls an agent needs.
149
+
150
+ Commands that return state: `continue`, `step`, `step over`, `step into`, `step out`, `run-to`, `restart-frame`, `pause`.
151
+
152
+ The auto-returned state uses the same format as `ndbg state` and respects a configurable default verbosity (see `ndbg config`).
153
+
154
+ ---
155
+
156
+ ## 5. Command Reference
157
+
158
+ ### 5.1 Session Management
159
+
160
+ ```
161
+ ndbg launch [--brk] [--session NAME] <command...>
162
+ ```
163
+ Start a Node.js process with `--inspect` (or `--inspect-brk` if `--brk` is passed) and attach the debugger daemon. Returns initial state if `--brk`.
164
+
165
+ - `--brk` — pause on first line of user code (recommended for most debugging)
166
+ - `--session NAME` — assign a name for multi-session debugging
167
+ - `--port PORT` — use specific inspector port (default: auto)
168
+ - `--timeout SECS` — daemon idle timeout (default: 300)
169
+
170
+ ```
171
+ ndbg attach <pid | ws-url | port>
172
+ ```
173
+ Attach to an already-running Node.js process (started with `--inspect`).
174
+
175
+ ```
176
+ ndbg stop [--session NAME]
177
+ ```
178
+ Kill the debugged process and shut down the daemon.
179
+
180
+ ```
181
+ ndbg sessions
182
+ ```
183
+ List active debug sessions with PID, status (paused/running), and session name.
184
+
185
+ ```
186
+ ndbg sessions --cleanup
187
+ ```
188
+ Kill all orphaned daemon processes.
189
+
190
+ ```
191
+ ndbg status
192
+ ```
193
+ Current session info: PID, pause state, attached breakpoints, memory usage, uptime.
194
+
195
+ ---
196
+
197
+ ### 5.2 Breakpoints
198
+
199
+ ```
200
+ ndbg break <file>:<line> [OPTIONS]
201
+ ```
202
+ Set a breakpoint. Returns the breakpoint ID and resolved location (with source map).
203
+
204
+ Options:
205
+ - `--condition <expr>` — only pause when expression is truthy
206
+ - `--hit-count <n>` — only pause on the Nth hit
207
+ - `--continue` — immediately continue after setting (composite command)
208
+ - `--log <template>` — convert to logpoint (shortcut for `ndbg logpoint`)
209
+
210
+ ```
211
+ ndbg break --pattern <urlRegex>:<line>
212
+ ```
213
+ Set breakpoint on all files matching a URL regex pattern. Useful for breaking in `node_modules` or dynamically loaded scripts.
214
+
215
+ ```
216
+ ndbg break-fn <expr>
217
+ ```
218
+ Set a breakpoint on every call to the function returned by evaluating `<expr>`. Example: `ndbg break-fn "require('express').Router"`.
219
+
220
+ ```
221
+ ndbg break-on-load [--sourcemap]
222
+ ```
223
+ Break whenever a new script is parsed. With `--sourcemap`, only break on scripts with source maps (i.e., your code, not node internals).
224
+
225
+ ```
226
+ ndbg break-rm <BP#id | LP#id | all>
227
+ ```
228
+ Remove a breakpoint or logpoint by ref. `all` removes everything.
229
+
230
+ ```
231
+ ndbg break-ls
232
+ ```
233
+ List all breakpoints and logpoints with their locations, conditions, and hit counts.
234
+
235
+ ```
236
+ ndbg break-toggle [BP#id | all]
237
+ ```
238
+ Enable/disable breakpoint(s) without removing them.
239
+
240
+ ```
241
+ ndbg breakable <file>:<start>-<end>
242
+ ```
243
+ List valid breakpoint locations in a line range. Useful when the agent picks a non-breakable line.
244
+
245
+ ```
246
+ ndbg logpoint <file>:<line> <template>
247
+ ```
248
+ Set a logpoint — logs the interpolated template string each time the line is hit, without pausing. Template uses `${expr}` syntax.
249
+
250
+ - `--max <n>` — auto-pause after N log emissions (default: 100, prevents floods)
251
+ - `--condition <expr>` — only log when condition is true
252
+
253
+ ```
254
+ ndbg catch [all | uncaught | caught | none]
255
+ ```
256
+ Configure pause-on-exception behavior. `all` catches even exceptions inside try/catch. `uncaught` is the most useful default.
257
+
258
+ ---
259
+
260
+ ### 5.3 Execution Control
261
+
262
+ All execution commands **return a state snapshot** when the process next pauses.
263
+
264
+ ```
265
+ ndbg continue
266
+ ```
267
+ Resume execution until next breakpoint, exception, or manual pause.
268
+
269
+ ```
270
+ ndbg step [over | into | out]
271
+ ```
272
+ Step one statement. Default is `over`.
273
+
274
+ - `step into` — with `--break-on-async` flag, pauses on the first async task scheduled before the next pause
275
+ - `step over` / `step into` — accept `--skip <pattern>` to skip over matching files (inline blackboxing)
276
+
277
+ ```
278
+ ndbg run-to <file>:<line>
279
+ ```
280
+ Continue execution until a specific location. Does not create a persistent breakpoint.
281
+
282
+ ```
283
+ ndbg restart-frame [@fN]
284
+ ```
285
+ Re-execute the specified frame (or current frame) from the beginning. The process continues immediately and pauses at the beginning of the restarted function.
286
+
287
+ ```
288
+ ndbg pause
289
+ ```
290
+ Interrupt a running process. Returns state at the interrupt point.
291
+
292
+ ```
293
+ ndbg kill-execution
294
+ ```
295
+ Terminate the current JavaScript execution without killing the Node.js process. Useful for stopping infinite loops while keeping the debug session alive.
296
+
297
+ ---
298
+
299
+ ### 5.4 Inspection
300
+
301
+ ```
302
+ ndbg state [FLAGS]
303
+ ```
304
+ Return the current debug state snapshot. See Section 4 for flags.
305
+
306
+ ```
307
+ ndbg vars [name1, name2, ...]
308
+ ```
309
+ Show local variables in the current frame. Optionally filter to specific names. Output assigns `@v` refs.
310
+
311
+ ```
312
+ ndbg stack [--async-depth N]
313
+ ```
314
+ Show the call stack. `--async-depth` controls how many async frames to resolve (default: 8, 0 to disable).
315
+
316
+ ```
317
+ ndbg eval <expression>
318
+ ```
319
+ Evaluate an expression in the context of the current call frame.
320
+
321
+ - Supports `await` (uses CDP's `awaitPromise`)
322
+ - Supports `@ref` interpolation: `ndbg eval "@v1.retryCount"`
323
+ - `--frame @fN` — evaluate in a specific frame
324
+ - `--silent` — suppress exception reporting
325
+ - `--timeout MS` — kill evaluation after N milliseconds (default: 5000)
326
+ - `--side-effect-free` — throw if expression has side effects (safe inspection)
327
+
328
+ ```
329
+ ndbg props <@ref> [OPTIONS]
330
+ ```
331
+ Expand the properties of an object ref. Returns `@o` refs for nested values.
332
+
333
+ - `--own` — only own properties (skip prototype chain)
334
+ - `--depth N` — recursive expansion depth (default: 1)
335
+ - `--private` — include private fields
336
+ - `--internal` — include V8 internal properties (e.g., `[[PromiseState]]`)
337
+
338
+ ```
339
+ ndbg instances <expression>
340
+ ```
341
+ Find all live instances of a prototype/constructor. Evaluates the expression to get a prototype, then queries all objects sharing it. Example: `ndbg instances "EventEmitter.prototype"`.
342
+
343
+ ```
344
+ ndbg globals
345
+ ```
346
+ List all global `let`, `const`, and `class` declarations in the current execution context.
347
+
348
+ ```
349
+ ndbg source [OPTIONS]
350
+ ```
351
+ Show source code around the current pause location.
352
+
353
+ - `--lines N` — lines above and below (default: 5)
354
+ - `--file <path>` — show source of a different file
355
+ - `--all` — show full file
356
+
357
+ ```
358
+ ndbg search <query> [OPTIONS]
359
+ ```
360
+ Search across all loaded scripts.
361
+
362
+ - `--regex` — treat query as regex
363
+ - `--case-sensitive` — case-sensitive search (default: insensitive)
364
+ - `--file <scriptId>` — search within a specific script
365
+
366
+ ```
367
+ ndbg scripts [--filter <pattern>]
368
+ ```
369
+ List all loaded scripts (files). Useful to find the right `scriptId` for breakpoints in dynamically loaded code.
370
+
371
+ ```
372
+ ndbg console [OPTIONS]
373
+ ```
374
+ Show captured console output (log, warn, error, etc.) with timestamps and stack traces.
375
+
376
+ - `--follow` — stream output in real-time (blocks until Ctrl+C or `--max`)
377
+ - `--since <N>` — only show last N entries
378
+ - `--level <log|warn|error>` — filter by level
379
+ - `--clear` — clear captured console buffer
380
+
381
+ ```
382
+ ndbg exceptions [OPTIONS]
383
+ ```
384
+ Show captured uncaught exceptions.
385
+
386
+ - `--follow` — stream in real-time
387
+ - `--since <N>` — last N entries
388
+
389
+ ---
390
+
391
+ ### 5.5 Mutation
392
+
393
+ These commands let an agent **test hypotheses and fixes without restarting**.
394
+
395
+ ```
396
+ ndbg set <@vRef | varName> <value>
397
+ ```
398
+ Change the value of a local, closure, or catch-scope variable in the current frame.
399
+
400
+ - Value is parsed as JSON or as a JavaScript primitive
401
+ - Only works on `local`, `closure`, and `catch` scope types (V8 limitation)
402
+ - Example: `ndbg set @v2 true`, `ndbg set retryCount 0`
403
+
404
+ ```
405
+ ndbg set-return <value>
406
+ ```
407
+ Change the return value of the current function. Only available when paused at a return point.
408
+
409
+ ```
410
+ ndbg hotpatch <file>
411
+ ```
412
+ Live-edit the source of a loaded script. Reads the file from disk and pushes it to V8 via `Debugger.setScriptSource`.
413
+
414
+ - If the edited function is the top-most stack frame (and only activation), it auto-restarts
415
+ - `--dry-run` — check if the edit would succeed without applying
416
+ - Returns status: `Ok`, `CompileError`, `BlockedByActiveFunction`, etc.
417
+ - **This is ndbg's killer feature for agents** — fix code and immediately verify, no restart
418
+
419
+ ---
420
+
421
+ ### 5.6 Blackboxing
422
+
423
+ Control which code the debugger steps into, preventing agents from getting lost in framework internals.
424
+
425
+ ```
426
+ ndbg blackbox <pattern...>
427
+ ```
428
+ Skip stepping into scripts matching the given patterns (regex). Stepping into a blackboxed function will step over it instead.
429
+
430
+ - Example: `ndbg blackbox "node_modules" "internal/"`
431
+ - Stacks with previous patterns
432
+
433
+ ```
434
+ ndbg blackbox-ls
435
+ ```
436
+ List current blackbox patterns.
437
+
438
+ ```
439
+ ndbg blackbox-rm <pattern | all>
440
+ ```
441
+ Remove blackbox patterns.
442
+
443
+ ---
444
+
445
+ ### 5.7 CPU Profiling
446
+
447
+ ```
448
+ ndbg cpu start [--interval <μs>]
449
+ ```
450
+ Start the V8 CPU profiler. Default sampling interval is 1000μs.
451
+
452
+ ```
453
+ ndbg cpu stop [--top N]
454
+ ```
455
+ Stop profiling and return results.
456
+
457
+ - `--top N` — show top N hottest functions (default: 10)
458
+ - Output includes: function name, file:line, self time %, total time %, deopt reason (if any)
459
+ - Full profile saved to a file for external tools
460
+
461
+ ```
462
+ ndbg coverage start [--detailed]
463
+ ```
464
+ Start precise code coverage collection. `--detailed` enables block-level granularity (not just function-level).
465
+
466
+ ```
467
+ ndbg coverage stop [OPTIONS]
468
+ ```
469
+ Stop coverage and report.
470
+
471
+ - `--file <path>` — filter to a specific file
472
+ - `--uncovered` — only show uncovered lines/blocks
473
+ - Output: per-function execution counts and uncovered ranges
474
+
475
+ ---
476
+
477
+ ### 5.8 Memory / Heap
478
+
479
+ ```
480
+ ndbg heap usage
481
+ ```
482
+ Quick heap statistics: used, total, embedder heap, backing store. No snapshot needed.
483
+
484
+ ```
485
+ ndbg heap snapshot [--tag <name>]
486
+ ```
487
+ Take a full V8 heap snapshot. Assigns an `HS#` ref.
488
+
489
+ ```
490
+ ndbg heap diff <HS#a> <HS#b> [--top N]
491
+ ```
492
+ Compare two heap snapshots. Output: table of constructors with delta count and delta size, sorted by size impact.
493
+
494
+ ```
495
+ ndbg heap sample start [--interval <bytes>] [--include-gc]
496
+ ```
497
+ Start sampling heap profiler. Lightweight alternative to full snapshots.
498
+
499
+ - `--interval` — average sampling interval in bytes (default: 32768)
500
+ - `--include-gc` — include objects already garbage-collected (shows temporary allocations)
501
+
502
+ ```
503
+ ndbg heap sample stop [--top N]
504
+ ```
505
+ Stop sampling and report top allocation sites.
506
+
507
+ ```
508
+ ndbg heap track start
509
+ ```
510
+ Start allocation tracking over time (timeline mode).
511
+
512
+ ```
513
+ ndbg heap track stop
514
+ ```
515
+ Stop tracking and report allocation rate by callsite.
516
+
517
+ ```
518
+ ndbg heap inspect <heapObjectId>
519
+ ```
520
+ Get a remote object reference from a heap snapshot node, bridging heap analysis with runtime inspection. Returns an `@o` ref.
521
+
522
+ ```
523
+ ndbg gc
524
+ ```
525
+ Force a garbage collection cycle. Useful before taking a snapshot to see what truly leaks.
526
+
527
+ ---
528
+
529
+ ### 5.9 Advanced / Utility
530
+
531
+ ```
532
+ ndbg inject-hook <name>
533
+ ```
534
+ Create a runtime binding that, when called from application code, sends a notification to the daemon. Use for custom instrumentation.
535
+
536
+ - Adds a global function `__ndbg_<name>()` that the app can call
537
+ - When called, the daemon captures the call's arguments and stack
538
+ - View with: `ndbg hooks [--follow]`
539
+
540
+ ```
541
+ ndbg contexts
542
+ ```
543
+ List all V8 execution contexts (useful for debugging Jest VM sandboxes, workers, or vm.runInContext scenarios). Each context gets an ID.
544
+
545
+ ```
546
+ ndbg async-depth <N>
547
+ ```
548
+ Set the async call stack trace depth. Default: 16. Set to 0 to disable async stacks. Higher values cost more memory but show the full async chain.
549
+
550
+ ```
551
+ ndbg config [key] [value]
552
+ ```
553
+ Get/set daemon configuration:
554
+
555
+ - `auto-state` — verbosity of auto-returned state snapshots (default: `full`)
556
+ - `default-depth` — default object expansion depth (default: 1)
557
+ - `default-lines` — default source context lines (default: 3)
558
+ - `async-depth` — async stack trace depth (default: 16)
559
+ - `blackbox` — default blackbox patterns
560
+ - `timeout` — daemon idle timeout in seconds
561
+
562
+ ```
563
+ ndbg gc-refs
564
+ ```
565
+ Clear accumulated `@o` refs to free memory. `@v` and `@f` refs are cleared automatically on each pause.
566
+
567
+ ```
568
+ ndbg --help-agent
569
+ ```
570
+ Output a compact LLM-optimized reference card with core workflow, quick reference, and common debugging patterns. Designed to be injected into an agent's context window.
571
+
572
+ ---
573
+
574
+ ## 6. Output Format
575
+
576
+ ### 6.1 Principles
577
+
578
+ 1. **Plain text by default** — no ANSI escape codes, no box drawing (unless `--color`)
579
+ 2. **Compact** — one entity per line where possible, tree indentation for hierarchy
580
+ 3. **@refs inline** — every inspectable value is prefixed with its ref
581
+ 4. **Timing annotations** — execution commands report elapsed time since last pause
582
+ 5. **No JSON by default** — JSON wastes tokens on syntax. Available with `--json` flag
583
+ 6. **Truncation with hints** — large outputs are truncated with a `... (ndbg props @oN for more)` hint
584
+
585
+ ### 6.2 Variable formatting
586
+
587
+ Variables are displayed with smart truncation:
588
+
589
+ ```
590
+ @v1 job Job { id: "test-123", type: "email", retries: 2, payload: {...} }
591
+ @v2 lock false
592
+ @v3 items Array(47) [ "a", "b", "c", ... ]
593
+ @v4 callback Function processResult(job)
594
+ @v5 promise Promise { <pending> }
595
+ @v6 error Error: "connection refused" (at src/db.ts:12)
596
+ @v7 map Map(3) { "a" => 1, "b" => 2, "c" => 3 }
597
+ @v8 buf Buffer(1024) <48 65 6c 6c 6f ...>
598
+ @v9 undefined undefined
599
+ ```
600
+
601
+ - Objects: constructor name + top-level properties up to ~80 chars, then `{...}`
602
+ - Arrays: type + length + first 3 elements, then `...`
603
+ - Functions: `Function` + name + parameters
604
+ - Promises: state (`<pending>`, `<resolved: value>`, `<rejected: error>`)
605
+ - Errors: message + first stack frame
606
+ - Buffers: length + first 5 hex bytes
607
+
608
+ ### 6.3 Source code formatting
609
+
610
+ ```
611
+ 45│ async processJob(job: Job) {
612
+ 46│ const lock = await this.acquireLock(job.id);
613
+ → 47│ if (!lock) return;
614
+ 48│ const result = await this.execute(job);
615
+ 49│ await this.markComplete(job.id);
616
+ ```
617
+
618
+ - Line numbers right-aligned, pipe separator
619
+ - Arrow `→` at current execution line
620
+ - Source-mapped locations (show `.ts` not `.js`)
621
+ - Breakpoint markers: `●` at lines with breakpoints
622
+
623
+ ### 6.4 Stack trace formatting
624
+
625
+ ```
626
+ @f0 processJob src/queue/processor.ts:47
627
+ @f1 poll src/queue/processor.ts:71
628
+ @f2 setTimeout cb node:internal/timers:573
629
+ ┊ async gap
630
+ @f3 QueueProcessor.start src/queue/processor.ts:12
631
+ ```
632
+
633
+ - Frame ref, function name, source-mapped location
634
+ - `┊ async gap` markers between async boundaries
635
+ - Blackboxed frames shown dimmed or collapsed: `┊ ... 3 framework frames (blackboxed)`
636
+
637
+ ### 6.5 Error output
638
+
639
+ Errors always suggest the next action:
640
+
641
+ ```
642
+ ✗ Cannot set breakpoint at src/queue/processor.ts:46 — no breakable location
643
+ Nearest valid lines: 45, 47
644
+ → Try: ndbg break src/queue/processor.ts:47
645
+
646
+ ✗ Variable 'foo' not found in current scope
647
+ Available locals: job, lock, this
648
+ → Try: ndbg vars
649
+
650
+ ✗ Cannot step — process is running (not paused)
651
+ → Try: ndbg pause
652
+
653
+ ✗ Session "default" not found — no active debug session
654
+ → Try: ndbg launch --brk "node app.js"
655
+ ```
656
+
657
+ ---
658
+
659
+ ## 7. Source Map Support
660
+
661
+ Source maps are a first-class concern, not an afterthought.
662
+
663
+ ### 7.1 Behavior
664
+
665
+ - All user-facing locations display **source-mapped paths and line numbers** (TypeScript, etc.)
666
+ - The `Debugger.scriptParsed` event provides `sourceMapURL`; the daemon fetches and caches it
667
+ - `ndbg break src/foo.ts:42` resolves to the generated `.js` location automatically
668
+ - Stack traces always show source-mapped locations
669
+ - `ndbg source` shows the original source (TypeScript), not compiled JS
670
+ - If no source map exists, the generated JS is shown (no error)
671
+
672
+ ### 7.2 Source map commands
673
+
674
+ ```
675
+ ndbg sourcemap <file> # Show source map info for a file
676
+ ndbg sourcemap --disable # Disable source map resolution globally
677
+ ```
678
+
679
+ ---
680
+
681
+ ## 8. Session & Daemon Protocol
682
+
683
+ ### 8.1 Daemon lifecycle
684
+
685
+ 1. `ndbg launch` starts a background daemon process
686
+ 2. Daemon opens a WebSocket to the Node.js inspector (`ws://127.0.0.1:<port>`)
687
+ 3. Daemon listens on a Unix socket at `$XDG_RUNTIME_DIR/ndbg/<session-name>.sock` (or `$TMPDIR` fallback)
688
+ 4. CLI commands connect to the Unix socket, send a request, receive a response, disconnect
689
+ 5. Daemon shuts down when: process exits, `ndbg stop` is called, or idle timeout is reached
690
+
691
+ ### 8.2 Internal protocol
692
+
693
+ CLI-to-daemon communication uses newline-delimited JSON over Unix socket. Each request/response is a single JSON object. This is internal — users never see it.
694
+
695
+ ```json
696
+ // Request
697
+ {"cmd": "continue", "args": {}}
698
+
699
+ // Response
700
+ {"ok": true, "state": { ... }, "refs": { ... }}
701
+ ```
702
+
703
+ ### 8.3 Multi-session
704
+
705
+ - Each debug session has a unique name (default: `"default"`)
706
+ - Multiple sessions can run simultaneously (e.g., debugging a client and server)
707
+ - `--session NAME` on any command targets a specific session
708
+ - `ndbg sessions` lists all active sessions
709
+
710
+ ### 8.4 Crash recovery
711
+
712
+ - If the daemon crashes, the CLI detects the dead socket and reports:
713
+ `✗ Session "default" daemon is not running. The debugged process (PID 42871) is still alive.`
714
+ `→ Try: ndbg attach 42871`
715
+ - Lock files prevent duplicate daemons for the same session
716
+
717
+ ---
718
+
719
+ ## 9. SKILL.md for Agent Integration
720
+
721
+ ndbg ships with a SKILL.md for Claude Code's skill system and compatible agents.
722
+
723
+ ```yaml
724
+ ---
725
+ name: node-debugger
726
+ description: >
727
+ Node.js runtime debugger CLI. Use when debugging runtime errors, race
728
+ conditions, memory leaks, circular dependencies, Jest/VM issues, or any
729
+ bug requiring breakpoints, variable inspection, stepping through code, or
730
+ profiling. Triggers: "debug", "breakpoint", "race condition", "memory leak",
731
+ "step through", "inspect at runtime", "profile", "heap snapshot", "why is
732
+ this variable undefined", "infinite loop".
733
+ ---
734
+ ```
735
+
736
+ ### 9.1 `--help-agent` output
737
+
738
+ The `ndbg --help-agent` command outputs a compact reference designed for agent context windows:
739
+
740
+ ```
741
+ ndbg — Node.js debugger CLI for AI agents
742
+
743
+ CORE LOOP:
744
+ 1. ndbg launch --brk "node app.js" → pauses at first line, returns state
745
+ 2. ndbg break src/file.ts:42 → set breakpoint
746
+ 3. ndbg continue → run to breakpoint, returns state
747
+ 4. Inspect: ndbg vars, ndbg eval, ndbg props @v1
748
+ 5. Mutate/fix: ndbg set @v1 value, ndbg hotpatch src/file.ts
749
+ 6. Repeat from 3
750
+
751
+ REFS: Every output assigns @refs. Use them everywhere:
752
+ @v1..@vN variables │ ndbg props @v1, ndbg set @v2 true
753
+ @f0..@fN stack frames │ ndbg frame @f1, ndbg restart-frame @f0
754
+ BP#1..N breakpoints │ ndbg break-rm BP#1
755
+ HS#1..N heap snaps │ ndbg heap diff HS#1 HS#2
756
+
757
+ EXECUTION (all return state automatically):
758
+ ndbg continue Resume to next breakpoint
759
+ ndbg step [over|into|out] Step one statement
760
+ ndbg run-to file:line Continue to location
761
+ ndbg pause Interrupt running process
762
+ ndbg restart-frame @f0 Re-run current function
763
+
764
+ BREAKPOINTS:
765
+ ndbg break file:line [--condition expr]
766
+ ndbg logpoint file:line "template ${var}"
767
+ ndbg catch [all|uncaught|none]
768
+ ndbg blackbox "node_modules/**"
769
+
770
+ INSPECTION:
771
+ ndbg state [-v|-s|-b|-c] [--depth N]
772
+ ndbg vars [name...]
773
+ ndbg eval <expr> (await supported)
774
+ ndbg props @ref [--depth N]
775
+ ndbg instances "Class.prototype"
776
+ ndbg search "query" [--regex]
777
+
778
+ MUTATION:
779
+ ndbg set @v1 <value> Change variable
780
+ ndbg set-return <value> Change return value
781
+ ndbg hotpatch <file> Live-edit code (no restart!)
782
+
783
+ PROFILING:
784
+ ndbg cpu start / stop [--top N]
785
+ ndbg coverage start [--detailed] / stop [--uncovered]
786
+ ndbg heap usage | snapshot | diff | sample | gc
787
+
788
+ PATTERNS:
789
+ # Race condition → trace with logpoints
790
+ ndbg logpoint src/lock.ts:31 "acquire: ${jobId} by ${workerId}"
791
+ ndbg continue
792
+
793
+ # Circular dependency → trace require chain
794
+ ndbg break-on-load --sourcemap
795
+ ndbg logpoint node_modules/jest-runtime/build/index.js:348 "${from} → ${moduleName}"
796
+
797
+ # Memory leak → snapshot before/after
798
+ ndbg heap snapshot --tag before
799
+ # ... trigger load ...
800
+ ndbg heap snapshot --tag after
801
+ ndbg heap diff HS#1 HS#2 --top 5
802
+
803
+ # Skip framework noise
804
+ ndbg blackbox "node_modules" "internal/"
805
+ ```
806
+
807
+ ---
808
+
809
+ ## 10. Installation & Distribution
810
+
811
+ ### 10.1 Install methods
812
+
813
+ ```bash
814
+ # npx (zero install — recommended for first use)
815
+ npx ndbg launch --brk "node app.js"
816
+
817
+ # Global install
818
+ npm install -g ndbg
819
+
820
+ # Compiled binary (no runtime needed)
821
+ # Download from GitHub releases: ndbg-linux-x64, ndbg-darwin-arm64, etc.
822
+ curl -fsSL https://github.com/<org>/ndbg/releases/latest/download/ndbg-$(uname -s)-$(uname -m) -o /usr/local/bin/ndbg
823
+ chmod +x /usr/local/bin/ndbg
824
+ ```
825
+
826
+ ### 10.2 Skill installation (for Claude Code and compatible agents)
827
+
828
+ ```bash
829
+ # Via vercel skills CLI
830
+ npx skills add <org>/ndbg
831
+
832
+ # Manual: copy SKILL.md to Claude Code skills directory
833
+ cp node_modules/ndbg/skills/node-debugger/SKILL.md ~/.claude/skills/node-debugger/SKILL.md
834
+ ```
835
+
836
+ ### 10.3 Requirements
837
+
838
+ - **Debugged process**: Node.js 16+ (for V8 inspector support)
839
+ - **ndbg binary**: no runtime dependency (standalone compiled binary)
840
+ - **ndbg via npm/npx**: Bun or Node.js 18+ on the host
841
+
842
+ ---
843
+
844
+ ## 11. Scope & Non-Goals
845
+
846
+ ### 11.1 In scope
847
+
848
+ - Node.js / JavaScript / TypeScript debugging via V8 Inspector Protocol
849
+ - CLI interface optimized for AI agents
850
+ - CPU profiling, heap profiling, code coverage
851
+ - Source map support
852
+ - Live code editing (hotpatch)
853
+ - Multi-session debugging
854
+
855
+ ### 11.2 Out of scope (v1)
856
+
857
+ - **Browser debugging** — ndbg targets Node.js processes only (no DOM, no CSS)
858
+ - **Other languages** — no Python, Rust, Go, etc. (use dedicated tools)
859
+ - **GUI** — no TUI, no web UI (output is text for terminal/agent consumption)
860
+ - **MCP server mode** — may be added later as an optional adapter, but CLI is primary
861
+ - **Remote debugging** — v1 targets localhost only (SSH tunneling is the recommended approach for remote)
862
+ - **Recording/replay** — time-travel debugging (may be a v2 feature)
863
+ - **Test framework integration** — ndbg debugs any Node.js process; it doesn't know about Jest/Vitest internals (but can debug them)
864
+
865
+ ---
866
+
867
+ ## 12. V8 Inspector Protocol Mapping
868
+
869
+ Reference for implementors. Maps ndbg commands to CDP methods.
870
+
871
+ | ndbg command | CDP domain | CDP method(s) |
872
+ |---|---|---|
873
+ | `launch --brk` | — | Spawns `node --inspect-brk` + `Runtime.runIfWaitingForDebugger` |
874
+ | `break file:line` | Debugger | `setBreakpointByUrl` |
875
+ | `break --pattern` | Debugger | `setBreakpointByUrl` (urlRegex) |
876
+ | `break-fn` | Debugger | `setBreakpointOnFunctionCall` |
877
+ | `break-on-load` | Debugger | `setInstrumentationBreakpoint` |
878
+ | `break-rm` | Debugger | `removeBreakpoint` |
879
+ | `break-toggle` | Debugger | `setBreakpointsActive` |
880
+ | `breakable` | Debugger | `getPossibleBreakpoints` |
881
+ | `catch` | Debugger | `setPauseOnExceptions` |
882
+ | `continue` | Debugger | `resume` |
883
+ | `step over` | Debugger | `stepOver` |
884
+ | `step into` | Debugger | `stepInto` (+ `breakOnAsyncCall`) |
885
+ | `step out` | Debugger | `stepOut` |
886
+ | `run-to` | Debugger | `continueToLocation` |
887
+ | `restart-frame` | Debugger | `restartFrame` |
888
+ | `pause` | Debugger | `pause` |
889
+ | `eval` | Debugger | `evaluateOnCallFrame` (awaitPromise) |
890
+ | `vars` | Debugger | scope chain from `paused` event → `getProperties` |
891
+ | `stack` | Debugger | `paused` event callFrames + `getStackTrace` |
892
+ | `props` | Runtime | `getProperties` |
893
+ | `instances` | Runtime | `queryObjects` |
894
+ | `globals` | Runtime | `globalLexicalScopeNames` |
895
+ | `search` | Debugger | `searchInContent` |
896
+ | `scripts` | Debugger | `scriptParsed` events (cached) |
897
+ | `set` | Debugger | `setVariableValue` |
898
+ | `set-return` | Debugger | `setReturnValue` |
899
+ | `hotpatch` | Debugger | `setScriptSource` |
900
+ | `blackbox` | Debugger | `setBlackboxPatterns` |
901
+ | `source` | Debugger | `getScriptSource` |
902
+ | `kill-execution` | Runtime | `terminateExecution` |
903
+ | `console` | Runtime | `consoleAPICalled` event |
904
+ | `exceptions` | Runtime | `exceptionThrown` event |
905
+ | `contexts` | Runtime | `executionContextCreated` event |
906
+ | `inject-hook` | Runtime | `addBinding` |
907
+ | `heap usage` | Runtime | `getHeapUsage` |
908
+ | `cpu start/stop` | Profiler | `start` / `stop` |
909
+ | `coverage start/stop` | Profiler | `startPreciseCoverage` / `takePreciseCoverage` |
910
+ | `heap snapshot` | HeapProfiler | `takeHeapSnapshot` |
911
+ | `heap sample start/stop` | HeapProfiler | `startSampling` / `stopSampling` |
912
+ | `heap track start/stop` | HeapProfiler | `startTrackingHeapObjects` / `stopTrackingHeapObjects` |
913
+ | `heap inspect` | HeapProfiler | `getObjectByHeapObjectId` |
914
+ | `gc` | HeapProfiler | `collectGarbage` |
915
+ | `async-depth` | Debugger | `setAsyncCallStackDepth` |
916
+
917
+ ---
918
+
919
+ ## 13. Testing Strategy
920
+
921
+ ### 13.1 Unit tests
922
+
923
+ - Ref system: creation, resolution, lifecycle, collision handling
924
+ - Output formatters: variable formatting, truncation, source display
925
+ - Source map resolution: .ts → .js mapping, inline source maps, missing maps
926
+ - Command argument parsing
927
+
928
+ ### 13.2 Integration tests
929
+
930
+ - Launch + attach + breakpoint + step + inspect + stop lifecycle
931
+ - Conditional breakpoints with expression evaluation
932
+ - Logpoint emission and flood throttling
933
+ - Source map resolution end-to-end (TypeScript project)
934
+ - Hotpatch: edit, verify, dry-run, blocked scenarios
935
+ - Multi-session: two concurrent debug sessions
936
+ - Heap snapshot, diff, sampling
937
+ - CPU profiling and coverage collection
938
+ - Daemon crash recovery and orphan cleanup
939
+
940
+ ### 13.3 Agent simulation tests
941
+
942
+ - Scripted debugging scenarios (like the race condition / circular dependency / memory leak scenarios from the design phase) run as end-to-end bash scripts
943
+ - Measure: total tool calls needed, total tokens in output, success rate
944
+ - Compare against equivalent MCP-based debuggers
945
+
946
+ ---
947
+
948
+ ## 14. Future Considerations (v2+)
949
+
950
+ - **MCP adapter** — optional MCP server mode wrapping the CLI for clients that prefer MCP
951
+ - **Watch expressions** — persistent expressions re-evaluated on every pause
952
+ - **Conditional logpoint templates** — more complex logpoint logic
953
+ - **Time-travel debugging** — record execution and replay forward/backward
954
+ - **Remote debugging** — built-in SSH tunnel management
955
+ - **Multi-process** — debug parent + child processes (fork/cluster)
956
+ - **Worker threads** — attach to worker threads via `Target` domain
957
+ - **Flamegraph export** — export CPU profiles as interactive flamegraphs
958
+ - **VS Code extension** — thin wrapper that delegates to the CLI