debug-run 0.5.8 → 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.
Files changed (3) hide show
  1. package/README.md +31 -0
  2. package/dist/index.cjs +432 -55
  3. package/package.json +1 -1
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
@@ -336,6 +338,35 @@ When an assertion fails, you get an `assertion_failed` event with:
336
338
 
337
339
  Assertions are checked at breakpoints, during stepping, and during trace mode.
338
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
+
339
370
  ### Trace mode (follow execution path)
340
371
 
341
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",
@@ -3974,7 +3983,7 @@ var DapTransport = class extends import_node_events.EventEmitter {
3974
3983
  command,
3975
3984
  arguments: args
3976
3985
  };
3977
- return new Promise((resolve10, reject) => {
3986
+ return new Promise((resolve9, reject) => {
3978
3987
  const timeout = setTimeout(() => {
3979
3988
  this.pendingRequests.delete(seq);
3980
3989
  reject(new Error(`Request '${command}' timed out after ${this.requestTimeout}ms`));
@@ -3983,7 +3992,7 @@ var DapTransport = class extends import_node_events.EventEmitter {
3983
3992
  resolve: (response) => {
3984
3993
  clearTimeout(timeout);
3985
3994
  if (response.success) {
3986
- resolve10(response.body);
3995
+ resolve9(response.body);
3987
3996
  } else {
3988
3997
  reject(new Error(response.message || `Request '${command}' failed`));
3989
3998
  }
@@ -4289,8 +4298,8 @@ var DapClient = class extends import_node_events2.EventEmitter {
4289
4298
  async initialize(args = {}) {
4290
4299
  this.ensureConnected();
4291
4300
  let initializedResolve;
4292
- const initializedPromise = new Promise((resolve10) => {
4293
- initializedResolve = resolve10;
4301
+ const initializedPromise = new Promise((resolve9) => {
4302
+ initializedResolve = resolve9;
4294
4303
  });
4295
4304
  this.once("initialized", () => initializedResolve());
4296
4305
  const response = await this.transport.sendRequest("initialize", {
@@ -4308,7 +4317,7 @@ var DapClient = class extends import_node_events2.EventEmitter {
4308
4317
  });
4309
4318
  this.capabilities = response?.capabilities || response || {};
4310
4319
  const timeoutPromise = new Promise((_, reject) => {
4311
- setTimeout(() => reject(new Error("Timeout waiting for initialized event")), 1e4);
4320
+ setTimeout(() => reject(new Error("Timeout waiting for initialized event")), 500);
4312
4321
  });
4313
4322
  try {
4314
4323
  await Promise.race([initializedPromise, timeoutPromise]);
@@ -4501,7 +4510,7 @@ var SocketDapTransport = class extends import_node_events3.EventEmitter {
4501
4510
  * Connect to the DAP server
4502
4511
  */
4503
4512
  async connect() {
4504
- return new Promise((resolve10, reject) => {
4513
+ return new Promise((resolve9, reject) => {
4505
4514
  this.socket = (0, import_node_net.connect)({
4506
4515
  host: this.options.host,
4507
4516
  port: this.options.port
@@ -4513,7 +4522,7 @@ var SocketDapTransport = class extends import_node_events3.EventEmitter {
4513
4522
  const onConnect = () => {
4514
4523
  this.socket?.removeListener("error", onError);
4515
4524
  this.setupSocket();
4516
- resolve10();
4525
+ resolve9();
4517
4526
  };
4518
4527
  this.socket.once("error", onError);
4519
4528
  this.socket.once("connect", onConnect);
@@ -4547,7 +4556,7 @@ var SocketDapTransport = class extends import_node_events3.EventEmitter {
4547
4556
  command,
4548
4557
  arguments: args
4549
4558
  };
4550
- return new Promise((resolve10, reject) => {
4559
+ return new Promise((resolve9, reject) => {
4551
4560
  const timeout = setTimeout(() => {
4552
4561
  this.pendingRequests.delete(seq);
4553
4562
  reject(new Error(`Request '${command}' timed out after ${this.requestTimeout}ms`));
@@ -4556,7 +4565,7 @@ var SocketDapTransport = class extends import_node_events3.EventEmitter {
4556
4565
  resolve: (response) => {
4557
4566
  clearTimeout(timeout);
4558
4567
  if (response.success) {
4559
- resolve10(response.body);
4568
+ resolve9(response.body);
4560
4569
  } else {
4561
4570
  reject(new Error(response.message || `Request '${command}' failed`));
4562
4571
  }
@@ -4738,7 +4747,7 @@ var SocketDapClient = class extends import_node_events4.EventEmitter {
4738
4747
  this.emit("serverError", error);
4739
4748
  });
4740
4749
  const delay = this.options.connectDelay ?? 1e3;
4741
- await new Promise((resolve10) => setTimeout(resolve10, delay));
4750
+ await new Promise((resolve9) => setTimeout(resolve9, delay));
4742
4751
  this.transport = new SocketDapTransport({
4743
4752
  host: this.options.host || "localhost",
4744
4753
  port: this.options.port,
@@ -5077,20 +5086,20 @@ var SocketDapClient = class extends import_node_events4.EventEmitter {
5077
5086
  if (process.env.DEBUG_DAP) {
5078
5087
  console.error("[DAP child] Initialize response received, waiting for initialized event...");
5079
5088
  }
5080
- await new Promise((resolve10) => {
5089
+ await new Promise((resolve9) => {
5081
5090
  let resolved = false;
5082
5091
  const onInitialized = () => {
5083
5092
  if (resolved) return;
5084
5093
  resolved = true;
5085
5094
  this.childTransport?.removeListener("event:initialized", onInitialized);
5086
- resolve10();
5095
+ resolve9();
5087
5096
  };
5088
5097
  this.childTransport.on("event:initialized", onInitialized);
5089
5098
  setTimeout(() => {
5090
5099
  if (resolved) return;
5091
5100
  resolved = true;
5092
5101
  this.childTransport?.removeListener("event:initialized", onInitialized);
5093
- resolve10();
5102
+ resolve9();
5094
5103
  }, 500);
5095
5104
  });
5096
5105
  if (process.env.DEBUG_DAP) {
@@ -5129,16 +5138,38 @@ var SocketDapClient = class extends import_node_events4.EventEmitter {
5129
5138
  };
5130
5139
 
5131
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
+ ];
5132
5157
  var OutputFormatter = class {
5133
5158
  stream;
5134
5159
  pretty;
5135
5160
  include;
5136
5161
  exclude;
5162
+ compact;
5163
+ stackLimit;
5164
+ /** Track previous locals for variable diffing in compact mode */
5165
+ previousLocals = {};
5137
5166
  constructor(options = {}) {
5138
5167
  this.stream = options.stream ?? process.stdout;
5139
5168
  this.pretty = options.pretty ?? false;
5140
5169
  this.include = options.include ? new Set(options.include) : void 0;
5141
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);
5142
5173
  }
5143
5174
  /**
5144
5175
  * Check if an event type should be emitted based on include/exclude filters
@@ -5159,9 +5190,248 @@ var OutputFormatter = class {
5159
5190
  if (!this.shouldEmit(event.type)) {
5160
5191
  return;
5161
5192
  }
5162
- 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);
5163
5195
  this.stream.write(json + "\n");
5164
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
+ }
5165
5435
  /**
5166
5436
  * Create an event with timestamp
5167
5437
  */
@@ -6397,10 +6667,17 @@ var DebugSession = class {
6397
6667
  traceInitialStackDepth = 0;
6398
6668
  /** Previous locals for variable diffing (only used when diffVars is enabled) */
6399
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;
6400
6674
  sessionPromise = null;
6401
6675
  sessionResolve = null;
6402
- _sessionReject = null;
6403
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;
6404
6681
  constructor(config, formatter) {
6405
6682
  this.config = config;
6406
6683
  this.formatter = formatter ?? new OutputFormatter();
@@ -6410,6 +6687,8 @@ var DebugSession = class {
6410
6687
  */
6411
6688
  async run() {
6412
6689
  this.startTime = Date.now();
6690
+ this.sessionError = null;
6691
+ this.sessionEndEmitted = false;
6413
6692
  if (this.config.attach && this.config.pid) {
6414
6693
  this.formatter.sessionStartAttach(this.config.adapter.name, this.config.pid);
6415
6694
  } else {
@@ -6420,9 +6699,8 @@ var DebugSession = class {
6420
6699
  this.config.cwd
6421
6700
  );
6422
6701
  }
6423
- this.sessionPromise = new Promise((resolve10, reject) => {
6424
- this.sessionResolve = resolve10;
6425
- this._sessionReject = reject;
6702
+ this.sessionPromise = new Promise((resolve9) => {
6703
+ this.sessionResolve = resolve9;
6426
6704
  });
6427
6705
  if (this.config.timeout) {
6428
6706
  this.timeoutHandle = setTimeout(() => {
@@ -6430,13 +6708,21 @@ var DebugSession = class {
6430
6708
  }, this.config.timeout);
6431
6709
  }
6432
6710
  try {
6433
- await this.start();
6711
+ await Promise.race([this.start(), this.sessionPromise]);
6712
+ if (this.sessionError) {
6713
+ throw this.sessionError;
6714
+ }
6434
6715
  await this.sessionPromise;
6716
+ if (this.sessionError) {
6717
+ throw this.sessionError;
6718
+ }
6435
6719
  } catch (error) {
6436
- this.formatter.error(
6437
- "Session failed",
6438
- error instanceof Error ? error.message : String(error)
6439
- );
6720
+ if (error !== this.sessionError) {
6721
+ this.formatter.error(
6722
+ "Session failed",
6723
+ error instanceof Error ? error.message : String(error)
6724
+ );
6725
+ }
6440
6726
  throw error;
6441
6727
  } finally {
6442
6728
  await this.cleanup();
@@ -6544,14 +6830,14 @@ var DebugSession = class {
6544
6830
  * Wait for the 'initialized' event from the debug adapter
6545
6831
  */
6546
6832
  async waitForInitialized() {
6547
- return new Promise((resolve10) => {
6833
+ return new Promise((resolve9) => {
6548
6834
  const onInitialized = () => {
6549
- resolve10();
6835
+ resolve9();
6550
6836
  };
6551
6837
  this.client.once("initialized", onInitialized);
6552
6838
  const timeout = setTimeout(() => {
6553
6839
  this.client.removeListener("initialized", onInitialized);
6554
- resolve10();
6840
+ resolve9();
6555
6841
  }, 3e4);
6556
6842
  this.client.once("initialized", () => {
6557
6843
  clearTimeout(timeout);
@@ -6624,7 +6910,8 @@ var DebugSession = class {
6624
6910
  locals = await this.variableInspector.getLocals(topFrame.id);
6625
6911
  }
6626
6912
  let evaluations;
6627
- if (this.config.evaluations?.length && topFrame) {
6913
+ const shouldDeferEval = this.config.evalAfterStep && reason === "breakpoint";
6914
+ if (this.config.evaluations?.length && topFrame && !shouldDeferEval) {
6628
6915
  evaluations = await this.variableInspector.evaluateExpressions(
6629
6916
  topFrame.id,
6630
6917
  this.config.evaluations
@@ -6640,6 +6927,64 @@ var DebugSession = class {
6640
6927
  );
6641
6928
  return;
6642
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
+ }
6643
6988
  if (reason === "step" && this.isStepping) {
6644
6989
  this.stepsExecuted++;
6645
6990
  this.remainingSteps--;
@@ -6719,6 +7064,18 @@ var DebugSession = class {
6719
7064
  await this.endTrace(threadId, "breakpoint", stackTrace, topFrame?.id);
6720
7065
  }
6721
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
+ }
6722
7079
  if (topFrame) {
6723
7080
  const failed = await this.checkAssertions(topFrame.id);
6724
7081
  if (failed) {
@@ -6814,31 +7171,36 @@ var DebugSession = class {
6814
7171
  this.endSessionWithError(new Error(`Session timed out after ${this.config.timeout}ms`));
6815
7172
  }
6816
7173
  endSessionWithError(error) {
6817
- this.formatter.sessionEnd({
6818
- durationMs: Date.now() - this.startTime,
6819
- exitCode: this.exitCode,
6820
- breakpointsHit: this.breakpointsHit,
6821
- exceptionsCaught: this.exceptionsCaught,
6822
- stepsExecuted: this.stepsExecuted
6823
- });
6824
- if (this._sessionReject) {
6825
- 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();
6826
7187
  this.sessionResolve = null;
6827
- this._sessionReject = null;
6828
7188
  }
6829
7189
  }
6830
7190
  endSession() {
6831
- this.formatter.sessionEnd({
6832
- durationMs: Date.now() - this.startTime,
6833
- exitCode: this.exitCode,
6834
- breakpointsHit: this.breakpointsHit,
6835
- exceptionsCaught: this.exceptionsCaught,
6836
- stepsExecuted: this.stepsExecuted
6837
- });
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
+ }
6838
7201
  if (this.sessionResolve) {
6839
7202
  this.sessionResolve();
6840
7203
  this.sessionResolve = null;
6841
- this._sessionReject = null;
6842
7204
  }
6843
7205
  }
6844
7206
  async cleanup() {
@@ -7098,7 +7460,7 @@ async function launchTestRunner(config) {
7098
7460
  });
7099
7461
  const stdoutReader = readline.createInterface({ input: testProcess.stdout });
7100
7462
  const stderrReader = readline.createInterface({ input: testProcess.stderr });
7101
- return new Promise((resolve10, reject) => {
7463
+ return new Promise((resolve9, reject) => {
7102
7464
  let foundPid = null;
7103
7465
  let exitedEarly = false;
7104
7466
  let sawDebugHint = false;
@@ -7113,7 +7475,7 @@ async function launchTestRunner(config) {
7113
7475
  if (match && !foundPid && sawDebugHint) {
7114
7476
  foundPid = parseInt(match[1], 10);
7115
7477
  onProgress?.(`Found testhost PID: ${foundPid} (process: ${match[2]})`);
7116
- resolve10({
7478
+ resolve9({
7117
7479
  pid: foundPid,
7118
7480
  process: testProcess
7119
7481
  });
@@ -7211,6 +7573,10 @@ function createCli() {
7211
7573
  "--diff-vars",
7212
7574
  "Show only changed variables in trace steps instead of full dumps",
7213
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
7214
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(
7215
7581
  "--exclude <types...>",
7216
7582
  "Suppress these event types (e.g., program_output exception_thrown)"
@@ -7241,6 +7607,14 @@ function createCli() {
7241
7607
  "Maximum depth to traverse exception chain (default: 10)",
7242
7608
  (val) => parseInt(val, 10),
7243
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)
7244
7618
  ).action(
7245
7619
  async (programPath, options) => {
7246
7620
  if (options.testProject) {
@@ -7401,7 +7775,9 @@ async function runDebugSession(options) {
7401
7775
  pretty: options.pretty,
7402
7776
  stream: outputStream,
7403
7777
  include: options.include,
7404
- exclude: options.exclude
7778
+ exclude: options.exclude,
7779
+ compact: options.compact,
7780
+ stackLimit: options.stackLimit
7405
7781
  });
7406
7782
  const session = new DebugSession(
7407
7783
  {
@@ -7426,6 +7802,7 @@ async function runDebugSession(options) {
7426
7802
  traceLimit: options.traceLimit,
7427
7803
  traceUntil: options.traceUntil,
7428
7804
  diffVars: options.diffVars,
7805
+ evalAfterStep: options.evalAfterStep,
7429
7806
  // Token efficiency options
7430
7807
  expandServices: options.expandServices,
7431
7808
  showNullProps: options.showNullProps,
@@ -7549,8 +7926,8 @@ function getSkillTargets(options) {
7549
7926
  }
7550
7927
  async function installSkill(options = {}) {
7551
7928
  const path12 = await import("node:path");
7552
- const { fileURLToPath: fileURLToPath2 } = await import("node:url");
7553
- const moduleDir = path12.dirname(fileURLToPath2(__importMetaUrl));
7929
+ const { fileURLToPath } = await import("node:url");
7930
+ const moduleDir = path12.dirname(fileURLToPath(__importMetaUrl));
7554
7931
  const possibleSources = [
7555
7932
  path12.join(moduleDir, "..", ".claude", "skills", "debug-run"),
7556
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.8",
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",