Mercury CLI
Documentation

Code

Debugger and tests

Mercury's Debug tool is a debugger speaking the Debug Adapter Protocol. It sets breakpoints, steps, and inspects the stack, scopes and variables of a live process, launched or attached, and evaluates expressions against it. The model drives it under the same permission checks as every other tool.

Operations

  • launch and attach: Start a process under the debugger or connect to a running one. Both ask permission.
  • breakpoints, threads and stack: Set breakpoints or inspect the stopped program's threads and call stacks.
  • scopes, variables and evaluate: Inspect the stopped program's state and evaluate expressions.
  • continue, next, stepIn, stepOut and pause: Control execution.
  • output, status and disconnect: Read debugger output, report the debug session's state or end it.
  • loadedSources, modules, exceptionBreakpoints, source, completions and setVariable: Higher-frequency inspection operations, gated on the capabilities the adapter announced.
  • functionBreakpoints, disassemble, readMemory and restart: Native-debugging operations, gated on the capabilities the adapter announced.
  • customRequest: Sends any DAP request verbatim. It asks permission because an arbitrary request can change the debugged process.

An operation the adapter did not announce is refused with a precise refusal.

Adapters

  • Python: debugpy. The build vendors it, with an installed-module fallback.
  • JavaScript and TypeScript: js-debug for Node. The build vendors it, and MERCURY_JS_DEBUG_DAP pins a custom copy.
  • Other built-in rows: lldb, gdb, go, dotnet, ruby and godot.
  • Custom adapters: One JSON row each, with the command, transport, file types and launch defaults, through MERCURY_DAP_ADAPTERS or the dap-adapters.json file in the config home.

MERCURY_DAP_ADAPTERS, MERCURY_DAP_ADAPTERS_FILE, MERCURY_JS_DEBUG_DAP and MERCURY_DEBUGPY_VENDOR_DIR tune the adapter table and the vendored payload roots.

On macOS, a native adapter such as lldb or gdb that starts and then never answers is usually the operating system rather than the adapter. Without Developer Mode, task_for_pid waits for an interactive authorization a debug adapter cannot give, and the grant lasts one boot. The debugger's timeout message and the doctor's IDE plane row read the setting live with DevToolsSecurity -status and name the durable fix, sudo DevToolsSecurity -enable.

Debug a failing test

To debug a failing test, follow these steps:

  1. Call the Test tool's debug operation for the failing test.

The test enters the debugger with no launch configuration written and no runner arguments reconstructed by hand.

The debug lanes are python, covering pytest and unittest, and node-test through js-debug. run and rerunFailed cover the remaining runners, and a debug request for one of them answers with exactly that.

Check debugger availability

To check debugger availability, follow these steps:

  1. Run mercury doctor, or enter /health in a session.
  2. Read the Tools withheld row.

The row names a withheld Debug tool and the adapters to arm.

The tool is withheld from the catalogue, rather than offered to refuse, on a machine where no debug adapter is reachable. A session already running picks the tool up at its next /clear or compaction once an adapter is reachable. MERCURY_DAP=0 removes the tool from the catalogue.

The census that decides availability reads the filesystem only: the vendored packs, PATH and the adapter tables, plus a memo for two toolchain probes, xcrun -f lldb-dap on macOS and gdb --version, which run once per process in the background. A catalogue built before the probes answer withholds the tool with words saying the probe has not answered yet, and the next build reads the answer. The doctor waits for the probes before it writes its row.

Child debug sessions

An adapter can start child sessions: js-debug starts one session per Node process, and a test runner may start one per file. A child joins the same debug tree, and every operation addresses root and children uniformly. The tree is bounded at 16 children and 4 levels deep by default, so a runaway adapter cannot fork forever.

Language servers, Edits and change transactions, Health check, Editor bridges