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.
- package/README.md +31 -0
- package/dist/index.cjs +432 -55
- 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
|
-
|
|
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((
|
|
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
|
-
|
|
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((
|
|
4293
|
-
initializedResolve =
|
|
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")),
|
|
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((
|
|
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
|
-
|
|
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((
|
|
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
|
-
|
|
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((
|
|
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((
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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((
|
|
6424
|
-
this.sessionResolve =
|
|
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.
|
|
6437
|
-
|
|
6438
|
-
|
|
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((
|
|
6833
|
+
return new Promise((resolve9) => {
|
|
6548
6834
|
const onInitialized = () => {
|
|
6549
|
-
|
|
6835
|
+
resolve9();
|
|
6550
6836
|
};
|
|
6551
6837
|
this.client.once("initialized", onInitialized);
|
|
6552
6838
|
const timeout = setTimeout(() => {
|
|
6553
6839
|
this.client.removeListener("initialized", onInitialized);
|
|
6554
|
-
|
|
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
|
-
|
|
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.
|
|
6818
|
-
|
|
6819
|
-
|
|
6820
|
-
|
|
6821
|
-
|
|
6822
|
-
|
|
6823
|
-
|
|
6824
|
-
|
|
6825
|
-
|
|
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.
|
|
6832
|
-
|
|
6833
|
-
|
|
6834
|
-
|
|
6835
|
-
|
|
6836
|
-
|
|
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((
|
|
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
|
-
|
|
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
|
|
7553
|
-
const moduleDir = path12.dirname(
|
|
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"),
|