debug-run 0.5.7 → 0.5.9

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.
@@ -34,6 +34,25 @@ Check available adapters:
34
34
  npx debug-run list-adapters
35
35
  ```
36
36
 
37
+ ## Installing the Skill
38
+
39
+ To install this skill for AI coding assistants:
40
+
41
+ ```bash
42
+ # Install to Claude Code (~/.claude/skills/)
43
+ npx debug-run install-skill --claude
44
+
45
+ # Install to GitHub Copilot (~/.copilot/skills/)
46
+ npx debug-run install-skill --copilot
47
+
48
+ # Install to project directory (for project-specific skills)
49
+ npx debug-run install-skill --claude --project
50
+ npx debug-run install-skill --copilot --project
51
+
52
+ # Install to custom directory
53
+ npx debug-run install-skill --dir /path/to/skills
54
+ ```
55
+
37
56
  ## Language-Specific Guides
38
57
 
39
58
  For detailed setup, examples, and troubleshooting for each language:
package/README.md CHANGED
@@ -143,6 +143,8 @@ Options:
143
143
  --attach Attach to running process
144
144
  --pid <id> Process ID to attach to
145
145
  --env <key=value...> Environment variables
146
+ --compact Enable compact output for reduced token usage
147
+ --stack-limit <N> Max stack frames to include (default: 3 in compact)
146
148
 
147
149
  Commands:
148
150
  list-adapters List available debug adapters
@@ -178,6 +180,35 @@ debug-run outputs newline-delimited JSON (NDJSON) events:
178
180
  {"type":"session_end","summary":{"breakpointsHit":1,"duration":1234}}
179
181
  ```
180
182
 
183
+ ### Breakpoint Set Event (with Diagnostics)
184
+
185
+ When a breakpoint cannot be verified (e.g., source mapping issues, missing debug symbols), the event includes actionable diagnostics:
186
+
187
+ ```json
188
+ {
189
+ "type": "breakpoint_set",
190
+ "id": 1,
191
+ "file": "src/handler.ts",
192
+ "line": 45,
193
+ "verified": false,
194
+ "message": "Could not resolve source location",
195
+ "diagnostics": {
196
+ "requestedFile": "src/handler.ts",
197
+ "requestedLine": 45,
198
+ "adapterMessage": "Could not resolve source location",
199
+ "suggestions": [
200
+ "Ensure \"sourceMap\": true in tsconfig.json",
201
+ "Rebuild with source maps: tsc --sourceMap (or your build command)",
202
+ "Verify .map files exist in your output directory (e.g., dist/**/*.map)"
203
+ ],
204
+ "adapterType": "node",
205
+ "fileExtension": ".ts"
206
+ }
207
+ }
208
+ ```
209
+
210
+ Suggestions are context-aware based on the adapter and file type.
211
+
181
212
  ### Breakpoint Hit Event
182
213
 
183
214
  ```json
@@ -307,6 +338,35 @@ When an assertion fails, you get an `assertion_failed` event with:
307
338
 
308
339
  Assertions are checked at breakpoints, during stepping, and during trace mode.
309
340
 
341
+ ### Compact output mode (for AI agents)
342
+
343
+ Reduce token usage by 40-60% with compact output:
344
+
345
+ ```bash
346
+ npx debug-run ./app.dll \
347
+ -a dotnet \
348
+ -b "src/OrderService.cs:45" \
349
+ --compact \
350
+ --pretty
351
+ ```
352
+
353
+ Compact mode applies these optimizations:
354
+ - **Stack trace limiting**: Only top 3 frames by default (configurable with `--stack-limit`)
355
+ - **Internal frame filtering**: Removes node_modules, runtime internals, webpack frames
356
+ - **Path abbreviation**: `~/project/src/file.ts` instead of `/Users/name/project/src/file.ts`
357
+ - **Variable diffing**: On repeated breakpoint hits, only reports changed variables (`_diff` key)
358
+ - **Trace path collapsing**: Consecutive identical locations collapsed to `functionName (x5)`
359
+
360
+ Example compact output vs verbose:
361
+
362
+ ```json
363
+ // Compact (~60 tokens)
364
+ {"type":"breakpoint_hit","location":{"file":".../src/OrderService.cs","line":45,"function":"ProcessOrder"},"stackTrace":[{"function":"ProcessOrder","file":".../src/OrderService.cs","line":45}],"locals":{"order":{"type":"OrderDto","value":{"Id":"abc-123","Total":150}}}}
365
+
366
+ // Verbose (~180 tokens)
367
+ {"type":"breakpoint_hit","timestamp":"2025-01-15T10:30:01.234Z","id":1,"threadId":1,"location":{"file":"/home/user/project/src/OrderService.cs","line":45,"column":12,"function":"ProcessOrder","module":"MyApp"},"stackTrace":[{"frameId":1,"function":"ProcessOrder","file":"/home/user/project/src/OrderService.cs","line":45,"column":12,"module":"MyApp"},{"frameId":2,"function":"Main","file":"/home/user/project/src/Program.cs","line":10,"column":5},...],"locals":{"order":{"type":"OrderDto","value":{"Id":"abc-123","Total":150,"CreatedAt":"2025-01-15T00:00:00Z","Status":"pending",...}},"this":{...}}}
368
+ ```
369
+
310
370
  ### Trace mode (follow execution path)
311
371
 
312
372
  Automatically step through code after hitting a breakpoint:
package/dist/index.cjs CHANGED
@@ -3066,7 +3066,6 @@ var path = __toESM(require("node:path"), 1);
3066
3066
  var os = __toESM(require("node:os"), 1);
3067
3067
  var import_promises2 = require("node:stream/promises");
3068
3068
  var import_node_stream = require("node:stream");
3069
- var import_node_url = require("node:url");
3070
3069
  var execAsync = (0, import_node_util.promisify)(import_node_child_process.exec);
3071
3070
  var NETCOREDBG_VERSION = "3.1.3-1062";
3072
3071
  var NETCOREDBG_BASE_URL = `https://github.com/Samsung/netcoredbg/releases/download/${NETCOREDBG_VERSION}`;
@@ -3119,10 +3118,14 @@ function getDownloadUrl() {
3119
3118
  const filename = `netcoredbg-${info.os}-${info.arch}.${info.archiveExt}`;
3120
3119
  return `${NETCOREDBG_BASE_URL}/${filename}`;
3121
3120
  }
3121
+ function getDebugRunHome() {
3122
+ if (process.env.DEBUG_RUN_HOME) {
3123
+ return process.env.DEBUG_RUN_HOME;
3124
+ }
3125
+ return path.join(os.homedir(), ".debug-run");
3126
+ }
3122
3127
  function getAdaptersDir() {
3123
- const currentFilePath = (0, import_node_url.fileURLToPath)(__importMetaUrl);
3124
- const packageRoot = path.resolve(path.dirname(currentFilePath), "..", "..");
3125
- return path.join(packageRoot, "bin", "adapters");
3128
+ return path.join(getDebugRunHome(), "adapters");
3126
3129
  }
3127
3130
  function getNetcoredbgPath() {
3128
3131
  const info = getPlatformInfo();
@@ -3144,6 +3147,9 @@ async function installNetcoredbg(onProgress) {
3144
3147
  if (!response.ok) {
3145
3148
  throw new Error(`Failed to download netcoredbg: ${response.statusText}`);
3146
3149
  }
3150
+ if (!response.body) {
3151
+ throw new Error("Response body is null");
3152
+ }
3147
3153
  const fileStream = (0, import_node_fs.createWriteStream)(archivePath);
3148
3154
  await (0, import_promises2.pipeline)(import_node_stream.Readable.fromWeb(response.body), fileStream);
3149
3155
  log("Extracting...");
@@ -3177,6 +3183,9 @@ async function installJsDebug(onProgress) {
3177
3183
  if (!response.ok) {
3178
3184
  throw new Error(`Failed to download js-debug: ${response.statusText}`);
3179
3185
  }
3186
+ if (!response.body) {
3187
+ throw new Error("Response body is null");
3188
+ }
3180
3189
  const fileStream = (0, import_node_fs.createWriteStream)(archivePath);
3181
3190
  await (0, import_promises2.pipeline)(import_node_stream.Readable.fromWeb(response.body), fileStream);
3182
3191
  log("Extracting...");
@@ -3294,7 +3303,7 @@ function findVsdbg() {
3294
3303
  const arch3 = os2.arch();
3295
3304
  const vsdbgDirs = [];
3296
3305
  if (platform3 === "win32") {
3297
- vsdbgDirs.push("vsdbg", "win32", "x64", "x86");
3306
+ vsdbgDirs.push("vsdbg", "win32", "x64", "x86", "x86_64");
3298
3307
  } else if (platform3 === "darwin") {
3299
3308
  vsdbgDirs.push(
3300
3309
  arch3 === "arm64" ? "arm64" : "x86_64",
@@ -3356,6 +3365,13 @@ function findVsdbg() {
3356
3365
  return null;
3357
3366
  }
3358
3367
  function findDebugpy() {
3368
+ const debugpyExt = findExtension("ms-python.debugpy");
3369
+ if (debugpyExt) {
3370
+ const debugpyPath = path3.join(debugpyExt, "bundled", "libs", "debugpy");
3371
+ if ((0, import_node_fs2.existsSync)(debugpyPath)) {
3372
+ return debugpyPath;
3373
+ }
3374
+ }
3359
3375
  const pythonExt = findExtension("ms-python.python");
3360
3376
  if (pythonExt) {
3361
3377
  const possiblePaths = [
@@ -3517,6 +3533,16 @@ var debugpyAdapter = {
3517
3533
  get args() {
3518
3534
  return ["-m", "debugpy.adapter"];
3519
3535
  },
3536
+ get env() {
3537
+ if (cachedSource === "vscode" && cachedDebugpyPath) {
3538
+ const debugpyParentDir = path5.dirname(cachedDebugpyPath);
3539
+ const existingPythonPath = process.env.PYTHONPATH || "";
3540
+ return {
3541
+ PYTHONPATH: existingPythonPath ? `${debugpyParentDir}:${existingPythonPath}` : debugpyParentDir
3542
+ };
3543
+ }
3544
+ return void 0;
3545
+ },
3520
3546
  detect: async () => {
3521
3547
  const pythonCmd = await findPythonCommand();
3522
3548
  if (!pythonCmd) {
@@ -3957,7 +3983,7 @@ var DapTransport = class extends import_node_events.EventEmitter {
3957
3983
  command,
3958
3984
  arguments: args
3959
3985
  };
3960
- return new Promise((resolve10, reject) => {
3986
+ return new Promise((resolve9, reject) => {
3961
3987
  const timeout = setTimeout(() => {
3962
3988
  this.pendingRequests.delete(seq);
3963
3989
  reject(new Error(`Request '${command}' timed out after ${this.requestTimeout}ms`));
@@ -3966,7 +3992,7 @@ var DapTransport = class extends import_node_events.EventEmitter {
3966
3992
  resolve: (response) => {
3967
3993
  clearTimeout(timeout);
3968
3994
  if (response.success) {
3969
- resolve10(response.body);
3995
+ resolve9(response.body);
3970
3996
  } else {
3971
3997
  reject(new Error(response.message || `Request '${command}' failed`));
3972
3998
  }
@@ -4272,8 +4298,8 @@ var DapClient = class extends import_node_events2.EventEmitter {
4272
4298
  async initialize(args = {}) {
4273
4299
  this.ensureConnected();
4274
4300
  let initializedResolve;
4275
- const initializedPromise = new Promise((resolve10) => {
4276
- initializedResolve = resolve10;
4301
+ const initializedPromise = new Promise((resolve9) => {
4302
+ initializedResolve = resolve9;
4277
4303
  });
4278
4304
  this.once("initialized", () => initializedResolve());
4279
4305
  const response = await this.transport.sendRequest("initialize", {
@@ -4291,7 +4317,7 @@ var DapClient = class extends import_node_events2.EventEmitter {
4291
4317
  });
4292
4318
  this.capabilities = response?.capabilities || response || {};
4293
4319
  const timeoutPromise = new Promise((_, reject) => {
4294
- setTimeout(() => reject(new Error("Timeout waiting for initialized event")), 1e4);
4320
+ setTimeout(() => reject(new Error("Timeout waiting for initialized event")), 500);
4295
4321
  });
4296
4322
  try {
4297
4323
  await Promise.race([initializedPromise, timeoutPromise]);
@@ -4484,7 +4510,7 @@ var SocketDapTransport = class extends import_node_events3.EventEmitter {
4484
4510
  * Connect to the DAP server
4485
4511
  */
4486
4512
  async connect() {
4487
- return new Promise((resolve10, reject) => {
4513
+ return new Promise((resolve9, reject) => {
4488
4514
  this.socket = (0, import_node_net.connect)({
4489
4515
  host: this.options.host,
4490
4516
  port: this.options.port
@@ -4496,7 +4522,7 @@ var SocketDapTransport = class extends import_node_events3.EventEmitter {
4496
4522
  const onConnect = () => {
4497
4523
  this.socket?.removeListener("error", onError);
4498
4524
  this.setupSocket();
4499
- resolve10();
4525
+ resolve9();
4500
4526
  };
4501
4527
  this.socket.once("error", onError);
4502
4528
  this.socket.once("connect", onConnect);
@@ -4530,7 +4556,7 @@ var SocketDapTransport = class extends import_node_events3.EventEmitter {
4530
4556
  command,
4531
4557
  arguments: args
4532
4558
  };
4533
- return new Promise((resolve10, reject) => {
4559
+ return new Promise((resolve9, reject) => {
4534
4560
  const timeout = setTimeout(() => {
4535
4561
  this.pendingRequests.delete(seq);
4536
4562
  reject(new Error(`Request '${command}' timed out after ${this.requestTimeout}ms`));
@@ -4539,7 +4565,7 @@ var SocketDapTransport = class extends import_node_events3.EventEmitter {
4539
4565
  resolve: (response) => {
4540
4566
  clearTimeout(timeout);
4541
4567
  if (response.success) {
4542
- resolve10(response.body);
4568
+ resolve9(response.body);
4543
4569
  } else {
4544
4570
  reject(new Error(response.message || `Request '${command}' failed`));
4545
4571
  }
@@ -4721,7 +4747,7 @@ var SocketDapClient = class extends import_node_events4.EventEmitter {
4721
4747
  this.emit("serverError", error);
4722
4748
  });
4723
4749
  const delay = this.options.connectDelay ?? 1e3;
4724
- await new Promise((resolve10) => setTimeout(resolve10, delay));
4750
+ await new Promise((resolve9) => setTimeout(resolve9, delay));
4725
4751
  this.transport = new SocketDapTransport({
4726
4752
  host: this.options.host || "localhost",
4727
4753
  port: this.options.port,
@@ -5060,20 +5086,20 @@ var SocketDapClient = class extends import_node_events4.EventEmitter {
5060
5086
  if (process.env.DEBUG_DAP) {
5061
5087
  console.error("[DAP child] Initialize response received, waiting for initialized event...");
5062
5088
  }
5063
- await new Promise((resolve10) => {
5089
+ await new Promise((resolve9) => {
5064
5090
  let resolved = false;
5065
5091
  const onInitialized = () => {
5066
5092
  if (resolved) return;
5067
5093
  resolved = true;
5068
5094
  this.childTransport?.removeListener("event:initialized", onInitialized);
5069
- resolve10();
5095
+ resolve9();
5070
5096
  };
5071
5097
  this.childTransport.on("event:initialized", onInitialized);
5072
5098
  setTimeout(() => {
5073
5099
  if (resolved) return;
5074
5100
  resolved = true;
5075
5101
  this.childTransport?.removeListener("event:initialized", onInitialized);
5076
- resolve10();
5102
+ resolve9();
5077
5103
  }, 500);
5078
5104
  });
5079
5105
  if (process.env.DEBUG_DAP) {
@@ -5112,16 +5138,38 @@ var SocketDapClient = class extends import_node_events4.EventEmitter {
5112
5138
  };
5113
5139
 
5114
5140
  // src/output/formatter.ts
5141
+ var INTERNAL_PATTERNS = [
5142
+ /^node:/,
5143
+ /node_modules/,
5144
+ /internal\//,
5145
+ /<anonymous>/,
5146
+ /^native /,
5147
+ /\[native code\]/,
5148
+ /webpack:/,
5149
+ /^async /,
5150
+ /^Module\./,
5151
+ /processTicksAndRejections/,
5152
+ /^RunMain$/,
5153
+ /^bootstrap_node\.js$/,
5154
+ /^_compile$/,
5155
+ /^\.js$/
5156
+ ];
5115
5157
  var OutputFormatter = class {
5116
5158
  stream;
5117
5159
  pretty;
5118
5160
  include;
5119
5161
  exclude;
5162
+ compact;
5163
+ stackLimit;
5164
+ /** Track previous locals for variable diffing in compact mode */
5165
+ previousLocals = {};
5120
5166
  constructor(options = {}) {
5121
5167
  this.stream = options.stream ?? process.stdout;
5122
5168
  this.pretty = options.pretty ?? false;
5123
5169
  this.include = options.include ? new Set(options.include) : void 0;
5124
5170
  this.exclude = options.exclude ? new Set(options.exclude) : void 0;
5171
+ this.compact = options.compact ?? false;
5172
+ this.stackLimit = options.stackLimit ?? (options.compact ? 3 : Infinity);
5125
5173
  }
5126
5174
  /**
5127
5175
  * Check if an event type should be emitted based on include/exclude filters
@@ -5142,9 +5190,248 @@ var OutputFormatter = class {
5142
5190
  if (!this.shouldEmit(event.type)) {
5143
5191
  return;
5144
5192
  }
5145
- const json = this.pretty ? JSON.stringify(event, null, 2) : JSON.stringify(event);
5193
+ const outputEvent = this.compact ? this.compactifyEvent(event) : event;
5194
+ const json = this.pretty ? JSON.stringify(outputEvent, null, 2) : JSON.stringify(outputEvent);
5146
5195
  this.stream.write(json + "\n");
5147
5196
  }
5197
+ /**
5198
+ * Check if a stack frame is an internal/runtime frame
5199
+ */
5200
+ isInternalFrame(frame) {
5201
+ const file = frame.file ?? "";
5202
+ const fn = frame.function ?? "";
5203
+ return INTERNAL_PATTERNS.some((pattern) => pattern.test(file) || pattern.test(fn));
5204
+ }
5205
+ /**
5206
+ * Filter and limit stack frames for compact output
5207
+ */
5208
+ compactifyStackTrace(frames) {
5209
+ const userFrames = frames.filter((frame) => !this.isInternalFrame(frame));
5210
+ const relevantFrames = userFrames.length > 0 ? userFrames : frames.slice(0, 1);
5211
+ const limitedFrames = relevantFrames.slice(0, this.stackLimit);
5212
+ return limitedFrames.map((frame) => ({
5213
+ ...frame,
5214
+ file: frame.file ? this.abbreviatePath(frame.file) : null
5215
+ }));
5216
+ }
5217
+ /**
5218
+ * Abbreviate a file path for compact output
5219
+ */
5220
+ abbreviatePath(filePath) {
5221
+ let abbreviated = filePath;
5222
+ const nodeModulesMatch = abbreviated.match(/node_modules\/(.+)/);
5223
+ if (nodeModulesMatch) {
5224
+ const modulePath = nodeModulesMatch[1];
5225
+ const moduleSegments = modulePath.split("/");
5226
+ if (moduleSegments.length > 3) {
5227
+ abbreviated = `<node_modules>/${moduleSegments.slice(0, 2).join("/")}/...`;
5228
+ } else {
5229
+ abbreviated = `<node_modules>/${modulePath}`;
5230
+ }
5231
+ return abbreviated;
5232
+ }
5233
+ const homeDir = process.env.HOME || process.env.USERPROFILE || "";
5234
+ if (homeDir && abbreviated.startsWith(homeDir)) {
5235
+ abbreviated = "~" + abbreviated.slice(homeDir.length);
5236
+ }
5237
+ const segments = abbreviated.split("/");
5238
+ if (segments.length > 4) {
5239
+ abbreviated = ".../" + segments.slice(-3).join("/");
5240
+ }
5241
+ return abbreviated;
5242
+ }
5243
+ /**
5244
+ * Abbreviate a source location for compact output
5245
+ */
5246
+ compactifyLocation(location) {
5247
+ return {
5248
+ ...location,
5249
+ file: this.abbreviatePath(location.file),
5250
+ // Remove module in compact mode as it's often redundant
5251
+ module: void 0
5252
+ };
5253
+ }
5254
+ /**
5255
+ * Compact locals by computing diff from previous state
5256
+ * Returns only changed variables
5257
+ */
5258
+ compactifyLocals(locals) {
5259
+ const isFirstCapture = Object.keys(this.previousLocals).length === 0;
5260
+ if (isFirstCapture) {
5261
+ this.previousLocals = { ...locals };
5262
+ return this.abbreviateLocals(locals);
5263
+ }
5264
+ const diff = {};
5265
+ let hasChanges = false;
5266
+ for (const [name, value] of Object.entries(locals)) {
5267
+ const previousValue = this.previousLocals[name];
5268
+ const currentJson = JSON.stringify(value);
5269
+ const previousJson = previousValue !== void 0 ? JSON.stringify(previousValue) : void 0;
5270
+ if (currentJson !== previousJson) {
5271
+ diff[name] = value;
5272
+ hasChanges = true;
5273
+ }
5274
+ }
5275
+ for (const name of Object.keys(this.previousLocals)) {
5276
+ if (!(name in locals)) {
5277
+ hasChanges = true;
5278
+ diff[name] = { type: "deleted", value: null };
5279
+ }
5280
+ }
5281
+ this.previousLocals = { ...locals };
5282
+ if (hasChanges) {
5283
+ return { _diff: this.abbreviateLocals(diff) };
5284
+ }
5285
+ return {};
5286
+ }
5287
+ /**
5288
+ * Abbreviate local variable values for compact output
5289
+ */
5290
+ abbreviateLocals(locals) {
5291
+ const abbreviated = {};
5292
+ for (const [name, value] of Object.entries(locals)) {
5293
+ abbreviated[name] = this.abbreviateValue(value);
5294
+ }
5295
+ return abbreviated;
5296
+ }
5297
+ /**
5298
+ * Abbreviate a single variable value for compact output
5299
+ */
5300
+ abbreviateValue(value) {
5301
+ if (typeof value.value === "string" && value.value.length > 100) {
5302
+ return {
5303
+ ...value,
5304
+ value: value.value.slice(0, 100) + "..."
5305
+ };
5306
+ }
5307
+ if (Array.isArray(value.value) && value.value.length > 5) {
5308
+ const abbreviated = value.value.slice(0, 3);
5309
+ return {
5310
+ ...value,
5311
+ value: `[${abbreviated.join(", ")}, ... (${value.value.length} items)]`
5312
+ };
5313
+ }
5314
+ return value;
5315
+ }
5316
+ /**
5317
+ * Apply compact transformations to an event
5318
+ */
5319
+ compactifyEvent(event) {
5320
+ switch (event.type) {
5321
+ case "breakpoint_hit": {
5322
+ const compacted = {
5323
+ ...event,
5324
+ location: this.compactifyLocation(event.location),
5325
+ stackTrace: this.compactifyStackTrace(event.stackTrace)
5326
+ };
5327
+ if (event.locals && Object.keys(event.locals).length > 0) {
5328
+ const compactedLocals = this.compactifyLocals(event.locals);
5329
+ compacted.locals = compactedLocals;
5330
+ }
5331
+ return compacted;
5332
+ }
5333
+ case "exception_thrown": {
5334
+ const compacted = {
5335
+ ...event,
5336
+ location: this.compactifyLocation(event.location)
5337
+ };
5338
+ if (event.exception.stackTrace) {
5339
+ compacted.exception = {
5340
+ ...event.exception,
5341
+ stackTrace: this.abbreviateStackTraceString(event.exception.stackTrace)
5342
+ };
5343
+ }
5344
+ if (event.locals && Object.keys(event.locals).length > 0) {
5345
+ const compactedLocals = this.compactifyLocals(event.locals);
5346
+ compacted.locals = compactedLocals;
5347
+ }
5348
+ return compacted;
5349
+ }
5350
+ case "step_completed": {
5351
+ return {
5352
+ ...event,
5353
+ location: this.compactifyLocation(event.location),
5354
+ stackTrace: this.compactifyStackTrace(event.stackTrace),
5355
+ locals: this.abbreviateLocals(event.locals)
5356
+ };
5357
+ }
5358
+ case "trace_step": {
5359
+ return {
5360
+ ...event,
5361
+ location: this.compactifyLocation(event.location)
5362
+ };
5363
+ }
5364
+ case "trace_completed": {
5365
+ const compactedPath = event.path.map((loc) => this.compactifyLocation(loc));
5366
+ const collapsedPath = [];
5367
+ let repeatCount = 1;
5368
+ for (let i = 0; i < compactedPath.length; i++) {
5369
+ const current = compactedPath[i];
5370
+ const next = compactedPath[i + 1];
5371
+ if (next && current.file === next.file && current.line === next.line && current.function === next.function) {
5372
+ repeatCount++;
5373
+ } else {
5374
+ if (repeatCount > 1) {
5375
+ collapsedPath.push({
5376
+ ...current,
5377
+ function: `${current.function} (x${repeatCount})`
5378
+ });
5379
+ } else {
5380
+ collapsedPath.push(current);
5381
+ }
5382
+ repeatCount = 1;
5383
+ }
5384
+ }
5385
+ return {
5386
+ ...event,
5387
+ path: collapsedPath,
5388
+ finalLocation: this.compactifyLocation(event.finalLocation),
5389
+ stackTrace: this.compactifyStackTrace(event.stackTrace),
5390
+ locals: this.abbreviateLocals(event.locals)
5391
+ };
5392
+ }
5393
+ case "assertion_failed": {
5394
+ return {
5395
+ ...event,
5396
+ location: this.compactifyLocation(event.location),
5397
+ stackTrace: this.compactifyStackTrace(event.stackTrace),
5398
+ locals: this.abbreviateLocals(event.locals)
5399
+ };
5400
+ }
5401
+ case "session_start": {
5402
+ const compacted = { ...event };
5403
+ if (compacted.program) {
5404
+ compacted.program = this.abbreviatePath(compacted.program);
5405
+ }
5406
+ if (compacted.cwd) {
5407
+ compacted.cwd = this.abbreviatePath(compacted.cwd);
5408
+ }
5409
+ return compacted;
5410
+ }
5411
+ default:
5412
+ return event;
5413
+ }
5414
+ }
5415
+ /**
5416
+ * Abbreviate a stack trace string (from exception)
5417
+ */
5418
+ abbreviateStackTraceString(stackTrace) {
5419
+ const lines = stackTrace.split("\n");
5420
+ const userLines = lines.filter((line) => {
5421
+ return !INTERNAL_PATTERNS.some((pattern) => pattern.test(line));
5422
+ });
5423
+ const limitedLines = userLines.slice(0, this.stackLimit);
5424
+ return limitedLines.map((line) => this.abbreviatePathsInLine(line)).join("\n");
5425
+ }
5426
+ /**
5427
+ * Abbreviate file paths within a line of text
5428
+ */
5429
+ abbreviatePathsInLine(line) {
5430
+ return line.replace(/(?:at\s+)?(\/[^\s:]+)/g, (match, path12) => {
5431
+ const abbreviated = this.abbreviatePath(path12);
5432
+ return match.replace(path12, abbreviated);
5433
+ });
5434
+ }
5148
5435
  /**
5149
5436
  * Create an event with timestamp
5150
5437
  */
@@ -5189,7 +5476,7 @@ var OutputFormatter = class {
5189
5476
  /**
5190
5477
  * Emit a breakpoint_set event
5191
5478
  */
5192
- breakpointSet(id, file, line, verified, condition, message) {
5479
+ breakpointSet(id, file, line, verified, condition, message, diagnostics) {
5193
5480
  this.emit(
5194
5481
  this.createEvent("breakpoint_set", {
5195
5482
  id,
@@ -5197,7 +5484,8 @@ var OutputFormatter = class {
5197
5484
  line,
5198
5485
  verified,
5199
5486
  condition,
5200
- message
5487
+ message,
5488
+ diagnostics
5201
5489
  })
5202
5490
  );
5203
5491
  }
@@ -5224,12 +5512,161 @@ function resolveBreakpointPath(file, options = {}) {
5224
5512
  if (options.cwd) {
5225
5513
  return path10.resolve(options.cwd, file);
5226
5514
  }
5227
- if (options.programPath) {
5228
- const programDir = path10.dirname(options.programPath);
5229
- return path10.resolve(programDir, file);
5230
- }
5231
5515
  return path10.resolve(file);
5232
5516
  }
5517
+ function validateBreakpointSpec(spec) {
5518
+ const trimmed = spec.trim();
5519
+ if (!trimmed) {
5520
+ return {
5521
+ valid: false,
5522
+ error: "Breakpoint specification cannot be empty",
5523
+ spec
5524
+ };
5525
+ }
5526
+ if (!trimmed.includes(":")) {
5527
+ return {
5528
+ valid: false,
5529
+ error: `Invalid breakpoint format "${spec}". Expected "file:line" (e.g., "Program.cs:42")`,
5530
+ spec
5531
+ };
5532
+ }
5533
+ const match = trimmed.match(/^(.+):(\d+)(?:\?(.+)|#(\d+))?$/);
5534
+ if (!match) {
5535
+ const colonIdx = trimmed.lastIndexOf(":");
5536
+ if (colonIdx !== -1) {
5537
+ const linePartRaw = trimmed.slice(colonIdx + 1);
5538
+ const linePart = linePartRaw.split("?")[0].split("#")[0];
5539
+ if (linePart === "") {
5540
+ return {
5541
+ valid: false,
5542
+ error: `Missing line number in breakpoint "${spec}". Expected "file:line" (e.g., "Program.cs:42")`,
5543
+ spec
5544
+ };
5545
+ }
5546
+ if (!/^\d+$/.test(linePart)) {
5547
+ return {
5548
+ valid: false,
5549
+ error: `Invalid line number "${linePart}" in breakpoint "${spec}". Line must be a positive integer`,
5550
+ spec
5551
+ };
5552
+ }
5553
+ }
5554
+ return {
5555
+ valid: false,
5556
+ error: `Invalid breakpoint format "${spec}". Expected "file:line" (e.g., "Program.cs:42")`,
5557
+ spec
5558
+ };
5559
+ }
5560
+ const [, file, lineStr] = match;
5561
+ const line = parseInt(lineStr, 10);
5562
+ if (!file || !file.trim()) {
5563
+ return {
5564
+ valid: false,
5565
+ error: `Missing file path in breakpoint "${spec}". Expected "file:line" (e.g., "Program.cs:42")`,
5566
+ spec
5567
+ };
5568
+ }
5569
+ if (isNaN(line) || line < 1) {
5570
+ return {
5571
+ valid: false,
5572
+ error: `Invalid line number "${lineStr}" in breakpoint "${spec}". Line must be a positive integer`,
5573
+ spec
5574
+ };
5575
+ }
5576
+ return { valid: true };
5577
+ }
5578
+ function validateLogpointSpec(spec) {
5579
+ const trimmed = spec.trim();
5580
+ if (!trimmed) {
5581
+ return {
5582
+ valid: false,
5583
+ error: "Logpoint specification cannot be empty",
5584
+ spec
5585
+ };
5586
+ }
5587
+ if (!trimmed.includes("|")) {
5588
+ return {
5589
+ valid: false,
5590
+ error: `Invalid logpoint format "${spec}". Expected "file:line|message" (e.g., "Program.cs:42|value is {x}")`,
5591
+ spec
5592
+ };
5593
+ }
5594
+ const pipeIdx = trimmed.indexOf("|");
5595
+ const beforePipe = trimmed.slice(0, pipeIdx);
5596
+ if (!beforePipe.includes(":")) {
5597
+ return {
5598
+ valid: false,
5599
+ error: `Invalid logpoint format "${spec}". Expected "file:line|message" (e.g., "Program.cs:42|value is {x}")`,
5600
+ spec
5601
+ };
5602
+ }
5603
+ const match = trimmed.match(/^(.+):(\d+)\|(.+)$/);
5604
+ if (!match) {
5605
+ const colonIdx = beforePipe.lastIndexOf(":");
5606
+ if (colonIdx !== -1) {
5607
+ const linePart = beforePipe.slice(colonIdx + 1);
5608
+ if (linePart === "") {
5609
+ return {
5610
+ valid: false,
5611
+ error: `Missing line number in logpoint "${spec}". Expected "file:line|message"`,
5612
+ spec
5613
+ };
5614
+ }
5615
+ if (!/^\d+$/.test(linePart)) {
5616
+ return {
5617
+ valid: false,
5618
+ error: `Invalid line number "${linePart}" in logpoint "${spec}". Line must be a positive integer`,
5619
+ spec
5620
+ };
5621
+ }
5622
+ }
5623
+ return {
5624
+ valid: false,
5625
+ error: `Invalid logpoint format "${spec}". Expected "file:line|message" (e.g., "Program.cs:42|value is {x}")`,
5626
+ spec
5627
+ };
5628
+ }
5629
+ const [, file, lineStr, message] = match;
5630
+ const line = parseInt(lineStr, 10);
5631
+ if (!file || !file.trim()) {
5632
+ return {
5633
+ valid: false,
5634
+ error: `Missing file path in logpoint "${spec}". Expected "file:line|message"`,
5635
+ spec
5636
+ };
5637
+ }
5638
+ if (isNaN(line) || line < 1) {
5639
+ return {
5640
+ valid: false,
5641
+ error: `Invalid line number "${lineStr}" in logpoint "${spec}". Line must be a positive integer`,
5642
+ spec
5643
+ };
5644
+ }
5645
+ if (!message || !message.trim()) {
5646
+ return {
5647
+ valid: false,
5648
+ error: `Missing log message in logpoint "${spec}". Expected "file:line|message"`,
5649
+ spec
5650
+ };
5651
+ }
5652
+ return { valid: true };
5653
+ }
5654
+ function validateAllBreakpoints(breakpoints, logpoints = []) {
5655
+ const errors = [];
5656
+ for (const bp of breakpoints) {
5657
+ const result = validateBreakpointSpec(bp);
5658
+ if (!result.valid && result.error) {
5659
+ errors.push(result.error);
5660
+ }
5661
+ }
5662
+ for (const lp of logpoints) {
5663
+ const result = validateLogpointSpec(lp);
5664
+ if (!result.valid && result.error) {
5665
+ errors.push(result.error);
5666
+ }
5667
+ }
5668
+ return errors;
5669
+ }
5233
5670
  function parseBreakpointSpec(spec, pathOptions = {}) {
5234
5671
  const match = spec.match(/^(.+):(\d+)(?:\?(.+)|#(\d+))?$/);
5235
5672
  if (!match) {
@@ -5271,10 +5708,12 @@ var BreakpointManager = class {
5271
5708
  breakpoints = /* @__PURE__ */ new Map();
5272
5709
  nextId = 1;
5273
5710
  pathOptions;
5274
- constructor(client, formatter, pathOptions = {}) {
5711
+ adapterType;
5712
+ constructor(client, formatter, options = {}) {
5275
5713
  this.client = client;
5276
5714
  this.formatter = formatter;
5277
- this.pathOptions = pathOptions;
5715
+ this.pathOptions = options;
5716
+ this.adapterType = options.adapterType;
5278
5717
  }
5279
5718
  /**
5280
5719
  * Add a breakpoint from a spec string
@@ -5321,6 +5760,7 @@ var BreakpointManager = class {
5321
5760
  hitCondition: spec.hitCondition,
5322
5761
  logMessage: spec.logMessage
5323
5762
  }));
5763
+ const requestedLines = specs.map((spec) => spec.line);
5324
5764
  try {
5325
5765
  const response = await this.client.setBreakpoints({
5326
5766
  source: { path: file },
@@ -5333,22 +5773,34 @@ var BreakpointManager = class {
5333
5773
  specs[i].verified = bp.verified;
5334
5774
  specs[i].message = bp.message;
5335
5775
  specs[i].line = bp.line ?? specs[i].line;
5776
+ const diagnostics = !bp.verified ? this.generateDiagnostics(file, requestedLines[i], bp.message) : void 0;
5336
5777
  this.formatter.breakpointSet(
5337
5778
  specs[i].id,
5338
5779
  file,
5339
5780
  specs[i].line,
5340
5781
  specs[i].verified,
5341
5782
  specs[i].condition,
5342
- specs[i].message
5783
+ specs[i].message,
5784
+ diagnostics
5343
5785
  );
5344
5786
  }
5345
5787
  }
5346
5788
  } catch (error) {
5347
- for (const spec of specs) {
5789
+ for (let i = 0; i < specs.length; i++) {
5790
+ const spec = specs[i];
5348
5791
  spec.id = this.nextId++;
5349
5792
  spec.verified = false;
5350
5793
  spec.message = error instanceof Error ? error.message : "Failed to set breakpoint";
5351
- this.formatter.breakpointSet(spec.id, file, spec.line, false, spec.condition, spec.message);
5794
+ const diagnostics = this.generateDiagnostics(file, requestedLines[i], spec.message);
5795
+ this.formatter.breakpointSet(
5796
+ spec.id,
5797
+ file,
5798
+ spec.line,
5799
+ false,
5800
+ spec.condition,
5801
+ spec.message,
5802
+ diagnostics
5803
+ );
5352
5804
  }
5353
5805
  }
5354
5806
  }
@@ -5378,7 +5830,103 @@ var BreakpointManager = class {
5378
5830
  getHitCount() {
5379
5831
  return 0;
5380
5832
  }
5833
+ /**
5834
+ * Generate diagnostics for an unverified breakpoint
5835
+ */
5836
+ generateDiagnostics(requestedFile, requestedLine, adapterMessage) {
5837
+ const ext = path10.extname(requestedFile).toLowerCase();
5838
+ const suggestions = getBreakpointSuggestions(this.adapterType, ext, adapterMessage);
5839
+ return {
5840
+ requestedFile,
5841
+ requestedLine,
5842
+ adapterMessage,
5843
+ suggestions,
5844
+ adapterType: this.adapterType,
5845
+ fileExtension: ext || void 0
5846
+ };
5847
+ }
5381
5848
  };
5849
+ function normalizeAdapterType(adapterType) {
5850
+ if (!adapterType) return void 0;
5851
+ const lower = adapterType.toLowerCase();
5852
+ if (["dotnet", "coreclr", "vsdbg", "netcoredbg"].includes(lower)) {
5853
+ return "coreclr";
5854
+ }
5855
+ if (["node", "nodejs", "javascript", "js", "typescript", "ts"].includes(lower)) {
5856
+ return "node";
5857
+ }
5858
+ if (["debugpy", "python", "py"].includes(lower)) {
5859
+ return "python";
5860
+ }
5861
+ if (["lldb", "codelldb", "cpp", "c", "rust"].includes(lower)) {
5862
+ return "lldb";
5863
+ }
5864
+ return lower;
5865
+ }
5866
+ function getBreakpointSuggestions(adapterType, fileExtension, adapterMessage) {
5867
+ const suggestions = [];
5868
+ const normalizedAdapter = normalizeAdapterType(adapterType);
5869
+ const ext = fileExtension.toLowerCase();
5870
+ const msgLower = (adapterMessage || "").toLowerCase();
5871
+ const isSourceMapIssue = msgLower.includes("source map") || msgLower.includes("sourcemap") || msgLower.includes("cannot find");
5872
+ const isPathIssue = msgLower.includes("not found") || msgLower.includes("cannot find file") || msgLower.includes("does not exist");
5873
+ if (normalizedAdapter === "node") {
5874
+ if (ext === ".ts" || ext === ".tsx") {
5875
+ suggestions.push('Ensure "sourceMap": true in tsconfig.json');
5876
+ suggestions.push("Rebuild with source maps: tsc --sourceMap (or your build command)");
5877
+ suggestions.push("Verify .map files exist in your output directory (e.g., dist/**/*.map)");
5878
+ suggestions.push(
5879
+ 'Check that "sourceMaps": true is set in your debug config and "outFiles" points to your built JS files'
5880
+ );
5881
+ } else if (ext === ".js" || ext === ".mjs" || ext === ".cjs") {
5882
+ suggestions.push(
5883
+ "Confirm you are setting the breakpoint in the executed file (built output vs source)"
5884
+ );
5885
+ if (isSourceMapIssue) {
5886
+ suggestions.push(
5887
+ "If using bundlers (webpack/esbuild/vite), ensure source maps are generated and accessible"
5888
+ );
5889
+ suggestions.push("Verify source map files (.map) are not excluded from your output");
5890
+ }
5891
+ }
5892
+ } else if (normalizedAdapter === "coreclr") {
5893
+ if (ext === ".cs") {
5894
+ suggestions.push("Build in Debug configuration to ensure PDB files are produced");
5895
+ suggestions.push("Clean and rebuild to refresh symbols: dotnet clean && dotnet build");
5896
+ suggestions.push(
5897
+ "Confirm the running binary matches the source version (stale builds can break mapping)"
5898
+ );
5899
+ suggestions.push("Ensure PDB files are deployed alongside the DLL");
5900
+ }
5901
+ } else if (normalizedAdapter === "python") {
5902
+ if (ext === ".py") {
5903
+ suggestions.push("Confirm the Python interpreter and working directory match your project");
5904
+ suggestions.push(
5905
+ "If debugging remotely or in containers, configure path mappings (localRoot/remoteRoot)"
5906
+ );
5907
+ suggestions.push(
5908
+ "Verify you are setting the breakpoint in code that actually executes (imported module vs different copy)"
5909
+ );
5910
+ }
5911
+ } else if (normalizedAdapter === "lldb") {
5912
+ if ([".c", ".cpp", ".cc", ".cxx", ".h", ".hpp", ".rs", ".m", ".mm"].includes(ext)) {
5913
+ suggestions.push("Compile with debug symbols (-g) and avoid stripping binaries");
5914
+ suggestions.push("Ensure the running binary matches the source used to compile");
5915
+ suggestions.push("For Rust, build with: cargo build (debug mode, not --release)");
5916
+ }
5917
+ }
5918
+ if (isPathIssue) {
5919
+ suggestions.push("Check the file path is correct and exists relative to the provided cwd");
5920
+ }
5921
+ if (suggestions.length === 0) {
5922
+ suggestions.push("Check the file path is correct and exists relative to the provided cwd");
5923
+ suggestions.push(
5924
+ "Ensure the running program corresponds to this source (no stale build artifacts)"
5925
+ );
5926
+ suggestions.push("Verify the file has been compiled/built with debug information");
5927
+ }
5928
+ return suggestions;
5929
+ }
5382
5930
 
5383
5931
  // src/session/variables.ts
5384
5932
  var BLOCKED_PROPERTIES = /* @__PURE__ */ new Set([
@@ -6119,10 +6667,17 @@ var DebugSession = class {
6119
6667
  traceInitialStackDepth = 0;
6120
6668
  /** Previous locals for variable diffing (only used when diffVars is enabled) */
6121
6669
  previousLocals = {};
6670
+ /** Whether we are stepping to evaluate expressions after line execution */
6671
+ isEvalAfterStep = false;
6672
+ /** Pending data for eval-after-step (original breakpoint data) */
6673
+ evalAfterStepData = null;
6122
6674
  sessionPromise = null;
6123
6675
  sessionResolve = null;
6124
- _sessionReject = null;
6125
6676
  timeoutHandle = null;
6677
+ /** Error that occurred during session (timeout, etc.) - used to avoid unhandled promise rejections */
6678
+ sessionError = null;
6679
+ /** Whether session_end event has been emitted (to prevent duplicate emissions) */
6680
+ sessionEndEmitted = false;
6126
6681
  constructor(config, formatter) {
6127
6682
  this.config = config;
6128
6683
  this.formatter = formatter ?? new OutputFormatter();
@@ -6132,6 +6687,8 @@ var DebugSession = class {
6132
6687
  */
6133
6688
  async run() {
6134
6689
  this.startTime = Date.now();
6690
+ this.sessionError = null;
6691
+ this.sessionEndEmitted = false;
6135
6692
  if (this.config.attach && this.config.pid) {
6136
6693
  this.formatter.sessionStartAttach(this.config.adapter.name, this.config.pid);
6137
6694
  } else {
@@ -6142,9 +6699,8 @@ var DebugSession = class {
6142
6699
  this.config.cwd
6143
6700
  );
6144
6701
  }
6145
- this.sessionPromise = new Promise((resolve10, reject) => {
6146
- this.sessionResolve = resolve10;
6147
- this._sessionReject = reject;
6702
+ this.sessionPromise = new Promise((resolve9) => {
6703
+ this.sessionResolve = resolve9;
6148
6704
  });
6149
6705
  if (this.config.timeout) {
6150
6706
  this.timeoutHandle = setTimeout(() => {
@@ -6152,13 +6708,21 @@ var DebugSession = class {
6152
6708
  }, this.config.timeout);
6153
6709
  }
6154
6710
  try {
6155
- await this.start();
6711
+ await Promise.race([this.start(), this.sessionPromise]);
6712
+ if (this.sessionError) {
6713
+ throw this.sessionError;
6714
+ }
6156
6715
  await this.sessionPromise;
6716
+ if (this.sessionError) {
6717
+ throw this.sessionError;
6718
+ }
6157
6719
  } catch (error) {
6158
- this.formatter.error(
6159
- "Session failed",
6160
- error instanceof Error ? error.message : String(error)
6161
- );
6720
+ if (error !== this.sessionError) {
6721
+ this.formatter.error(
6722
+ "Session failed",
6723
+ error instanceof Error ? error.message : String(error)
6724
+ );
6725
+ }
6162
6726
  throw error;
6163
6727
  } finally {
6164
6728
  await this.cleanup();
@@ -6171,7 +6735,7 @@ var DebugSession = class {
6171
6735
  command: this.config.adapter.command,
6172
6736
  args: this.config.adapter.args,
6173
6737
  cwd: this.config.cwd,
6174
- env: this.config.env,
6738
+ env: { ...this.config.adapter.env, ...this.config.env },
6175
6739
  port: this.config.adapter.socketPort,
6176
6740
  timeout: this.config.timeout
6177
6741
  });
@@ -6180,7 +6744,7 @@ var DebugSession = class {
6180
6744
  command: this.config.adapter.command,
6181
6745
  args: this.config.adapter.args,
6182
6746
  cwd: this.config.cwd,
6183
- env: this.config.env,
6747
+ env: { ...this.config.adapter.env, ...this.config.env },
6184
6748
  timeout: this.config.timeout
6185
6749
  });
6186
6750
  }
@@ -6192,7 +6756,8 @@ var DebugSession = class {
6192
6756
  });
6193
6757
  this.breakpointManager = new BreakpointManager(this.client, this.formatter, {
6194
6758
  cwd: this.config.cwd,
6195
- programPath: this.config.program
6759
+ programPath: this.config.program,
6760
+ adapterType: this.config.adapter.name
6196
6761
  });
6197
6762
  this.variableInspector = new VariableInspector(this.client, {
6198
6763
  compactServices: !this.config.expandServices,
@@ -6265,14 +6830,14 @@ var DebugSession = class {
6265
6830
  * Wait for the 'initialized' event from the debug adapter
6266
6831
  */
6267
6832
  async waitForInitialized() {
6268
- return new Promise((resolve10) => {
6833
+ return new Promise((resolve9) => {
6269
6834
  const onInitialized = () => {
6270
- resolve10();
6835
+ resolve9();
6271
6836
  };
6272
6837
  this.client.once("initialized", onInitialized);
6273
6838
  const timeout = setTimeout(() => {
6274
6839
  this.client.removeListener("initialized", onInitialized);
6275
- resolve10();
6840
+ resolve9();
6276
6841
  }, 3e4);
6277
6842
  this.client.once("initialized", () => {
6278
6843
  clearTimeout(timeout);
@@ -6345,7 +6910,8 @@ var DebugSession = class {
6345
6910
  locals = await this.variableInspector.getLocals(topFrame.id);
6346
6911
  }
6347
6912
  let evaluations;
6348
- if (this.config.evaluations?.length && topFrame) {
6913
+ const shouldDeferEval = this.config.evalAfterStep && reason === "breakpoint";
6914
+ if (this.config.evaluations?.length && topFrame && !shouldDeferEval) {
6349
6915
  evaluations = await this.variableInspector.evaluateExpressions(
6350
6916
  topFrame.id,
6351
6917
  this.config.evaluations
@@ -6361,6 +6927,64 @@ var DebugSession = class {
6361
6927
  );
6362
6928
  return;
6363
6929
  }
6930
+ if (reason === "step" && this.isEvalAfterStep && this.evalAfterStepData) {
6931
+ this.isEvalAfterStep = false;
6932
+ const pendingData = this.evalAfterStepData;
6933
+ this.evalAfterStepData = null;
6934
+ let evaluations2;
6935
+ if (this.config.evaluations?.length && topFrame) {
6936
+ evaluations2 = await this.variableInspector.evaluateExpressions(
6937
+ topFrame.id,
6938
+ this.config.evaluations
6939
+ );
6940
+ }
6941
+ if (topFrame) {
6942
+ const failed = await this.checkAssertions(topFrame.id);
6943
+ if (failed) {
6944
+ await this.emitAssertionFailed(
6945
+ pendingData.threadId,
6946
+ failed.assertion,
6947
+ failed.value,
6948
+ failed.error,
6949
+ location,
6950
+ stackTrace,
6951
+ topFrame.id
6952
+ );
6953
+ this.endSession();
6954
+ return;
6955
+ }
6956
+ }
6957
+ const event2 = {
6958
+ type: "breakpoint_hit",
6959
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
6960
+ id: pendingData.breakpointId,
6961
+ threadId: pendingData.threadId,
6962
+ location: pendingData.originalLocation,
6963
+ stackTrace: pendingData.originalStackTrace,
6964
+ locals,
6965
+ evaluations: evaluations2
6966
+ };
6967
+ this.formatter.emit(event2);
6968
+ if (this.config.trace) {
6969
+ await this.startTrace(
6970
+ pendingData.threadId,
6971
+ location,
6972
+ stackResponse.stackFrames.length,
6973
+ topFrame?.id
6974
+ );
6975
+ return;
6976
+ }
6977
+ if (this.config.steps && this.config.steps > 1) {
6978
+ this.remainingSteps = this.config.steps - 1;
6979
+ this.isStepping = true;
6980
+ await this.client.next({ threadId });
6981
+ this.state = "running";
6982
+ return;
6983
+ }
6984
+ await this.client.continue({ threadId });
6985
+ this.state = "running";
6986
+ return;
6987
+ }
6364
6988
  if (reason === "step" && this.isStepping) {
6365
6989
  this.stepsExecuted++;
6366
6990
  this.remainingSteps--;
@@ -6440,6 +7064,18 @@ var DebugSession = class {
6440
7064
  await this.endTrace(threadId, "breakpoint", stackTrace, topFrame?.id);
6441
7065
  }
6442
7066
  this.breakpointsHit++;
7067
+ if (this.config.evalAfterStep && this.config.evaluations?.length) {
7068
+ this.isEvalAfterStep = true;
7069
+ this.evalAfterStepData = {
7070
+ threadId,
7071
+ originalLocation: location,
7072
+ originalStackTrace: stackTrace,
7073
+ breakpointId: body.hitBreakpointIds?.[0]
7074
+ };
7075
+ await this.client.next({ threadId });
7076
+ this.state = "running";
7077
+ return;
7078
+ }
6443
7079
  if (topFrame) {
6444
7080
  const failed = await this.checkAssertions(topFrame.id);
6445
7081
  if (failed) {
@@ -6535,31 +7171,36 @@ var DebugSession = class {
6535
7171
  this.endSessionWithError(new Error(`Session timed out after ${this.config.timeout}ms`));
6536
7172
  }
6537
7173
  endSessionWithError(error) {
6538
- this.formatter.sessionEnd({
6539
- durationMs: Date.now() - this.startTime,
6540
- exitCode: this.exitCode,
6541
- breakpointsHit: this.breakpointsHit,
6542
- exceptionsCaught: this.exceptionsCaught,
6543
- stepsExecuted: this.stepsExecuted
6544
- });
6545
- if (this._sessionReject) {
6546
- this._sessionReject(error);
7174
+ this.sessionError = error;
7175
+ if (!this.sessionEndEmitted) {
7176
+ this.sessionEndEmitted = true;
7177
+ this.formatter.sessionEnd({
7178
+ durationMs: Date.now() - this.startTime,
7179
+ exitCode: this.exitCode,
7180
+ breakpointsHit: this.breakpointsHit,
7181
+ exceptionsCaught: this.exceptionsCaught,
7182
+ stepsExecuted: this.stepsExecuted
7183
+ });
7184
+ }
7185
+ if (this.sessionResolve) {
7186
+ this.sessionResolve();
6547
7187
  this.sessionResolve = null;
6548
- this._sessionReject = null;
6549
7188
  }
6550
7189
  }
6551
7190
  endSession() {
6552
- this.formatter.sessionEnd({
6553
- durationMs: Date.now() - this.startTime,
6554
- exitCode: this.exitCode,
6555
- breakpointsHit: this.breakpointsHit,
6556
- exceptionsCaught: this.exceptionsCaught,
6557
- stepsExecuted: this.stepsExecuted
6558
- });
7191
+ if (!this.sessionEndEmitted) {
7192
+ this.sessionEndEmitted = true;
7193
+ this.formatter.sessionEnd({
7194
+ durationMs: Date.now() - this.startTime,
7195
+ exitCode: this.exitCode,
7196
+ breakpointsHit: this.breakpointsHit,
7197
+ exceptionsCaught: this.exceptionsCaught,
7198
+ stepsExecuted: this.stepsExecuted
7199
+ });
7200
+ }
6559
7201
  if (this.sessionResolve) {
6560
7202
  this.sessionResolve();
6561
7203
  this.sessionResolve = null;
6562
- this._sessionReject = null;
6563
7204
  }
6564
7205
  }
6565
7206
  async cleanup() {
@@ -6819,7 +7460,7 @@ async function launchTestRunner(config) {
6819
7460
  });
6820
7461
  const stdoutReader = readline.createInterface({ input: testProcess.stdout });
6821
7462
  const stderrReader = readline.createInterface({ input: testProcess.stderr });
6822
- return new Promise((resolve10, reject) => {
7463
+ return new Promise((resolve9, reject) => {
6823
7464
  let foundPid = null;
6824
7465
  let exitedEarly = false;
6825
7466
  let sawDebugHint = false;
@@ -6834,7 +7475,7 @@ async function launchTestRunner(config) {
6834
7475
  if (match && !foundPid && sawDebugHint) {
6835
7476
  foundPid = parseInt(match[1], 10);
6836
7477
  onProgress?.(`Found testhost PID: ${foundPid} (process: ${match[2]})`);
6837
- resolve10({
7478
+ resolve9({
6838
7479
  pid: foundPid,
6839
7480
  process: testProcess
6840
7481
  });
@@ -6932,6 +7573,10 @@ function createCli() {
6932
7573
  "--diff-vars",
6933
7574
  "Show only changed variables in trace steps instead of full dumps",
6934
7575
  false
7576
+ ).option(
7577
+ "--eval-after-step",
7578
+ "Step once before evaluating expressions (useful for evaluating variables being assigned on the breakpoint line)",
7579
+ false
6935
7580
  ).option("-o, --output <file>", "Write events to file instead of stdout").option("--include <types...>", "Only emit these event types (e.g., breakpoint_hit error)").option(
6936
7581
  "--exclude <types...>",
6937
7582
  "Suppress these event types (e.g., program_output exception_thrown)"
@@ -6962,6 +7607,14 @@ function createCli() {
6962
7607
  "Maximum depth to traverse exception chain (default: 10)",
6963
7608
  (val) => parseInt(val, 10),
6964
7609
  10
7610
+ ).option(
7611
+ "--compact",
7612
+ "Enable compact output mode for reduced token usage (limits stack traces, filters internals, abbreviates paths)",
7613
+ false
7614
+ ).option(
7615
+ "--stack-limit <count>",
7616
+ "Maximum stack frames to include (default: 3 in compact mode, unlimited otherwise)",
7617
+ (val) => parseInt(val, 10)
6965
7618
  ).action(
6966
7619
  async (programPath, options) => {
6967
7620
  if (options.testProject) {
@@ -6993,6 +7646,16 @@ function createCli() {
6993
7646
  console.error(`Available adapters: ${getAdapterNames().join(", ")}`);
6994
7647
  process.exit(1);
6995
7648
  }
7649
+ const breakpointErrors = validateAllBreakpoints(
7650
+ options.breakpoint || [],
7651
+ options.logpoint || []
7652
+ );
7653
+ if (breakpointErrors.length > 0) {
7654
+ for (const error of breakpointErrors) {
7655
+ console.error(`Error: ${error}`);
7656
+ }
7657
+ process.exit(1);
7658
+ }
6996
7659
  await runDebugSession({ ...options, program: programPath, adapter: options.adapter });
6997
7660
  }
6998
7661
  );
@@ -7027,6 +7690,13 @@ async function runTestDebugSession(options) {
7027
7690
  console.error('Example: -b "path/to/TestFile.cs:42"');
7028
7691
  process.exit(1);
7029
7692
  }
7693
+ const breakpointErrors = validateAllBreakpoints(options.breakpoint || [], options.logpoint || []);
7694
+ if (breakpointErrors.length > 0) {
7695
+ for (const error of breakpointErrors) {
7696
+ console.error(`Error: ${error}`);
7697
+ }
7698
+ process.exit(1);
7699
+ }
7030
7700
  console.error(`Starting test runner for: ${options.testProject}`);
7031
7701
  if (options.testFilter) {
7032
7702
  console.error(`Test filter: ${options.testFilter}`);
@@ -7105,7 +7775,9 @@ async function runDebugSession(options) {
7105
7775
  pretty: options.pretty,
7106
7776
  stream: outputStream,
7107
7777
  include: options.include,
7108
- exclude: options.exclude
7778
+ exclude: options.exclude,
7779
+ compact: options.compact,
7780
+ stackLimit: options.stackLimit
7109
7781
  });
7110
7782
  const session = new DebugSession(
7111
7783
  {
@@ -7130,6 +7802,7 @@ async function runDebugSession(options) {
7130
7802
  traceLimit: options.traceLimit,
7131
7803
  traceUntil: options.traceUntil,
7132
7804
  diffVars: options.diffVars,
7805
+ evalAfterStep: options.evalAfterStep,
7133
7806
  // Token efficiency options
7134
7807
  expandServices: options.expandServices,
7135
7808
  showNullProps: options.showNullProps,
@@ -7253,8 +7926,8 @@ function getSkillTargets(options) {
7253
7926
  }
7254
7927
  async function installSkill(options = {}) {
7255
7928
  const path12 = await import("node:path");
7256
- const { fileURLToPath: fileURLToPath2 } = await import("node:url");
7257
- const moduleDir = path12.dirname(fileURLToPath2(__importMetaUrl));
7929
+ const { fileURLToPath } = await import("node:url");
7930
+ const moduleDir = path12.dirname(fileURLToPath(__importMetaUrl));
7258
7931
  const possibleSources = [
7259
7932
  path12.join(moduleDir, "..", ".claude", "skills", "debug-run"),
7260
7933
  path12.join(moduleDir, ".claude", "skills", "debug-run"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "debug-run",
3
- "version": "0.5.7",
3
+ "version": "0.5.9",
4
4
  "description": "CLI tool enabling AI agents to programmatically debug code via DAP",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",