Skip to content

feat: sdk debug adapter - #2483

Open
brady-stacks wants to merge 18 commits into
stx-labs:mainfrom
brady-stacks:feat/sdk-debug-adapter-tests
Open

feat: sdk debug adapter#2483
brady-stacks wants to merge 18 commits into
stx-labs:mainfrom
brady-stacks:feat/sdk-debug-adapter-tests

Conversation

@brady-stacks

@brady-stacks brady-stacks commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Debugging works with any existing clarity test via the same vitest mechanism existing previously. The debug codelens allows running the debugger on specific it blocks and the debugger can be set up manually in other editors via the clarinet dap cli command

Screen.Recording.2026-08-24.at.1.52.55.PM.mov

Followup work: #2485

@brady-stacks
brady-stacks marked this pull request as draft July 31, 2026 16:47
@codecov

codecov Bot commented Jul 31, 2026

Copy link
Copy Markdown

@brady-stacks
brady-stacks force-pushed the feat/sdk-debug-adapter-tests branch 2 times, most recently from 7841652 to 8d07d41 Compare August 4, 2026 00:40
@brady-stacks
brady-stacks force-pushed the feat/sdk-debug-adapter-tests branch from 8d07d41 to bff1fcf Compare August 4, 2026 00:45
@brady-stacks
brady-stacks marked this pull request as ready for review August 4, 2026 20:14

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends Clarinet’s debugging stack to support SDK-driven, non-interactive debugging flows: a persistent clarinet dap TCP server (DAP + SDK ports), a Node SDK DebugClient to drive contract evaluation through that server, and VSCode UX (CodeLens + command/config) to attach and run a specific test under the debugger.

Changes:

  • Add a persistent TCP “server mode” for clarinet dap that accepts an SDK JSON protocol connection and (optionally) a DAP attach client connection.
  • Introduce DebugClient/startDebugServer() in the Node SDK and export it from the SDK entrypoint.
  • Add VSCode extension support: CodeLens “Debug with Clarinet”, a clarity.debugTest command to orchestrate DAP + test run, and a minimal rspack build for the DAP adapter.

Reviewed changes

Copilot reviewed 16 out of 17 changed files in this pull request and generated 13 comments.

Show a summary per file
File Description
components/clarity-vscode/rspack.dap.js Adds a minimal rspack config to build only the debug adapter bundle.
components/clarity-vscode/package.json Adds build:dap, a clarinetPath setting, a debug command, and an attach debug config option.
components/clarity-vscode/debug/debug.ts Enhances the debug adapter relay to support both launch (spawn) and attach (TCP) modes.
components/clarity-vscode/client/src/clientNode.ts Adds CodeLens + command to start clarinet dap, attach VSCode, and run a single vitest test.
components/clarity-repl/src/repl/debug/mod.rs Adds a DebugState reset helper for successive calls while preserving breakpoints/watchpoints.
components/clarity-repl/src/repl/debug/dap/mod.rs Refactors DAP I/O to support stdio, no-op mode, and TCP attach mode with handshake support.
components/clarinet-sdk/node/src/index.ts Exports the new debug client APIs from the Node SDK entrypoint.
components/clarinet-sdk/node/src/debugClient.ts Implements DebugClient + startDebugServer() for SDK-driven debug evaluation over TCP.
components/clarinet-cli/src/frontend/dap.rs Adds run_dap_server implementing the dual-port TCP server and SDK request loop.
components/clarinet-cli/src/frontend/cli.rs Extends clarinet dap CLI to accept server-mode options (--dap-port, --sdk-port, --manifest(-path)).
components/clarinet-cli/examples/pnpm-lock.yaml Updates the examples lockfile (currently includes local-link artifacts).
components/clarinet-cli/examples/dap-debug-demo/vitest.config.ts Adds a vitest config for the DAP debug demo project.
components/clarinet-cli/examples/dap-debug-demo/tests/counter.auto.test.ts Adds demo tests showing how to use startDebugServer() (skipped in CI).
components/clarinet-cli/examples/dap-debug-demo/settings/Devnet.toml Adds demo devnet settings (currently includes real mnemonic phrases).
components/clarinet-cli/examples/dap-debug-demo/package.json Adds demo package.json wiring vitest + workspace SDK dependency.
components/clarinet-cli/examples/dap-debug-demo/contracts/counter.clar Adds a small demo contract suitable for setting breakpoints.
components/clarinet-cli/examples/dap-debug-demo/Clarinet.toml Adds demo Clarinet project manifest for the DAP debug demo.
Files not reviewed (1)
  • components/clarinet-cli/examples/pnpm-lock.yaml: Generated file
Suppressed comments (2)

components/clarinet-cli/src/frontend/cli.rs:142

  • The --manifest-path help text says it's required when --dap-port is set, but the command handler falls back to auto-detecting the manifest (or "Clarinet.toml"). The CLI docs should reflect that defaulting behavior.
    /// Path to Clarinet.toml. Required when `--dap-port` is set.
    #[clap(long = "manifest-path", alias = "manifest", short = 'm')]
    pub manifest_path: Option<String>,

components/clarinet-cli/examples/dap-debug-demo/settings/Devnet.toml:19

  • This example Devnet.toml commits a full mnemonic phrase into the repo. Prefer a well-known public test mnemonic (or a placeholder) so it can't be mistaken for a real secret.
[accounts.wallet_2]
mnemonic = "hold excess usual excess ring elephant install account glad dry fragile donkey gaze humble truck breeze nation gasp vacuum limb head keep delay hospital"
balance = 100_000_000_000_000
# stx_address: ST2CY5V39NHDPWSXMW9QDT3HC3GD6Q6XX4CFRK9AG

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread components/clarity-repl/src/repl/debug/dap/mod.rs
Comment thread components/clarity-vscode/client/src/clientNode.ts Outdated
Comment thread components/clarity-vscode/client/src/clientNode.ts Outdated
Comment thread components/clarity-vscode/client/src/clientNode.ts Outdated
Comment thread components/clarity-vscode/debug/debug.ts Outdated
Comment thread components/clarinet-cli/examples/pnpm-lock.yaml Outdated
Comment thread components/clarinet-cli/examples/pnpm-lock.yaml Outdated
Comment thread components/clarinet-cli/examples/dap-debug-demo/settings/Devnet.toml Outdated
Comment thread components/clarinet-cli/examples/dap-debug-demo/settings/Devnet.toml Outdated
Comment thread components/clarinet-sdk/node/src/debugClient.ts
@brady-stacks
brady-stacks requested review from jbencin-stacks and a lite review from Copilot August 14, 2026 19:27

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 17 out of 17 changed files in this pull request and generated 1 comment.

Suppressed comments (4)

components/clarinet-sdk/node/src/debugClient.ts:203

  • CLARINET_DEBUG_PORT (or options.port) can parse to NaN (or an out-of-range value), and connectPort != null will still be true for NaN, leading to an immediate connection failure with a confusing error. Consider validating that the port is a finite integer within [0, 65535] before entering connect mode, and fall back to auto-spawn (or throw a clear error) when invalid.
  const envPort = process.env["CLARINET_DEBUG_PORT"]
    ? Number(process.env["CLARINET_DEBUG_PORT"])
    : undefined;
  const connectPort = options?.port ?? envPort;

  // Connect mode: server is already running externally.
  if (connectPort != null) {
    const socket = await openSocket(connectPort);
    return new DebugClient(socket);
  }

components/clarity-vscode/client/src/clientNode.ts:133

  • If the fallback server fails to bind (e.g., transient OS error), the promise never resolves and the debug command will hang. Add an error handler for the fallback listener and ensure any server created is closed/cleaned up on failure; also consider rejecting the promise so the caller can surface a meaningful error.
function resolvePort(preferred: number): Promise<number> {
  return new Promise((resolve) => {
    const srv = net.createServer();
    srv.listen(preferred, "127.0.0.1", () => {
      srv.close(() => resolve(preferred));
    });
    srv.on("error", () => {
      const fallback = net.createServer();
      fallback.listen(0, "127.0.0.1", () => {
        const addr = fallback.address() as net.AddressInfo;
        fallback.close(() => resolve(addr.port));
      });
    });
  });
}

components/clarity-vscode/debug/debug.ts:18

  • The Content-Length regex is unnecessarily strict ("Content-Length: (\d+)") and will fail on valid headers with extra whitespace (e.g. Content-Length: 123), causing the parser to skip sections and potentially desync the DAP stream. Consider using Content-Length:\s*(\d+) and treating “missing Content-Length” as a hard parse error (or leaving bytes in remaining) rather than advancing pos past the header delimiter.
    const header = buffer.subarray(pos, headerEnd).toString("ascii");
    const match = /Content-Length: (\d+)/i.exec(header);
    if (!match) {
      pos = headerEnd + 4;
      continue;
    }

components/clarity-repl/src/repl/debug/dap/mod.rs:173

  • In attach/server mode, successive calls reuse the same DAPDebugger, but prepare_for_call only resets DebugState. Any per-call DAP caches on DAPDebugger itself (e.g. self.current, self.stack_frames, self.scopes, self.variables) can remain stale across calls and may cause incorrect scope/variable responses or panics (e.g. indexing into self.scopes for a frame id from a prior call). Consider clearing/resetting those per-execution fields in prepare_for_call (or in reset_for_new_call) so each call starts from a clean execution view while preserving breakpoints/watchpoints.
    pub fn prepare_for_call(&mut self, contract_id: &QualifiedContractIdentifier, snippet: &str) {
        match &mut self.state {
            Some(state) => state.reset_for_new_call(contract_id, snippet),
            None => self.state = Some(DebugState::new(contract_id, snippet)),
        }
    }

Comment thread components/clarinet-cli/src/frontend/cli.rs Outdated
@brady-stacks
brady-stacks force-pushed the feat/sdk-debug-adapter-tests branch from cde54db to eb6811d Compare August 18, 2026 04:03
Comment thread components/clarity-vscode/client/src/clientNode.ts Outdated
@brady-stacks
brady-stacks force-pushed the feat/sdk-debug-adapter-tests branch 2 times, most recently from d7b96ef to 9f0b002 Compare August 21, 2026 16:16
@brady-stacks
brady-stacks force-pushed the feat/sdk-debug-adapter-tests branch from 9f0b002 to 5f8714d Compare August 21, 2026 17:27
/// `setBreakpoints`, and `configurationDone`, then return.
/// After this returns the server is ready to accept SDK contract calls.
pub fn init_attach(&mut self) -> Result<(), ParseError> {
while !self.config_done {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can occupy 100% of the CPU on a graceful disconnect. wait_for_command() will return Ok(true) indefinitely.

Should return an error to exit the loop in this case instead

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants