@siuver/omp-debug-mode 0.1.1 → 0.1.2

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.2 - 2026-08-20
4
+
5
+ - Added an interactive round-review menu with Mark as fixed, Proceed, Add reproduction details, and Abort actions.
6
+ - Added `/debug-review` to reopen the menu and `/debug-note <details>` to return to the editor, add user evidence, and continue the workflow.
7
+ - Kept `/debug-done fixed|proceed` as compatible shortcuts and added interactive confirmation for zero-log completion, zero-log progression, and abort.
8
+
3
9
  ## 0.1.1 - 2026-08-20
4
10
 
5
11
  - Replaced the localhost HTTP collector with direct JSONL file appends from instrumented runtime code.
package/README.md CHANGED
@@ -9,20 +9,32 @@ The plugin makes the agent form hypotheses, add temporary runtime probes, attemp
9
9
  | Command | Purpose |
10
10
  | --- | --- |
11
11
  | `/debug-mode <problem>` | Starts a debugging session from a symptom, expected result, actual result, and reproduction description. |
12
+ | `/debug-review` | Reopens the interactive action menu for the completed round. |
13
+ | `/debug-note <details>` | Adds reproduction details from the editor and starts the next evidence-driven round. |
12
14
  | `/debug-status` | Shows the current phase, round, run, live probes, and captured log counts. |
13
- | `/debug-done fixed` | Confirms the fix and asks the agent to remove every probe and summarize the root cause and final change. |
14
- | `/debug-done proceed` | Reports that the issue remains, asks the agent to analyze the captured logs, and starts another hypothesis/instrument/fix round. |
15
- | `/debug-abort` | Stops debug mode and removes its logs while leaving code changes in the working tree. |
15
+ | `/debug-done fixed` | Non-menu shortcut that confirms the fix and asks the agent to remove every probe and summarize the result. |
16
+ | `/debug-done proceed` | Non-menu shortcut that analyzes the captured logs and starts another hypothesis/instrument/fix round. |
17
+ | `/debug-abort` | Stops debug mode and removes its logs after confirmation while leaving code changes in the working tree. |
18
+
19
+ ## Interactive Round Review
20
+
21
+ When the Agent completes a debugging round, the plugin enters the reproduction gate and opens an action menu:
22
+
23
+ - **Mark as fixed** - asks the Agent to clean up probes and summarize the root cause and fix. If the round captured no runtime logs, the plugin asks for confirmation first.
24
+ - **Proceed with captured logs** - refreshes the log file at selection time, archives the completed run, and starts the next analysis and fix round. If no runtime observations exist, the plugin asks for confirmation before proceeding.
25
+ - **Add reproduction details** - closes the menu and pre-fills the editor with `/debug-note ` so you can add symptoms, environment details, or reproduction results before continuing.
26
+ - **Abort debug mode** - asks for confirmation, removes debug logs, and keeps applied code changes.
27
+
28
+ Pressing `Esc` simply closes the menu and leaves the workflow at the reproduction gate. After reproducing the issue in another application, run `/debug-review` to reopen it. The existing `/debug-done fixed|proceed` commands remain available for non-interactive and shortcut use.
16
29
 
17
30
  ## Workflow
18
31
 
19
32
  1. Run `/debug-mode <problem description>`.
20
- 2. The agent investigates, records hypotheses, inserts minimal probes marked with `@omp-probe <id>`, and attempts a fix. Each probe appends its runtime observation directly to the exact JSONL file provided in the injected prompt.
21
- 3. When the agent stops, reproduce the issue in the real application so the instrumented code writes its observations.
22
- 4. Run `/debug-done fixed` if the issue is resolved, or `/debug-done proceed` to make the agent read the completed run's JSONL file, analyze the evidence, and continue.
23
- 5. On success, the agent removes all probes, verifies the probe ledger is empty, and summarizes the root cause and fix.
33
+ 2. The Agent investigates, records hypotheses, inserts minimal probes marked with `@omp-probe <id>`, and attempts a fix. The injected prompt strongly requires every probe to append runtime observations directly to the exact JSONL path; console output is supplemental only.
34
+ 3. When the Agent stops, dismiss the review menu if necessary and reproduce the issue in the real application so the instrumented code writes its observations.
35
+ 4. Reopen `/debug-review` and mark the issue fixed, proceed with logs, or return to the editor to submit `/debug-note <details>`.
24
36
 
25
- The extension also provides the read-only `get_debug_logs` and `list_debug_probes` tools so the agent can inspect runtime evidence and verify cleanup.
37
+ The extension also provides the read-only `get_debug_logs` and `list_debug_probes` tools so the Agent can inspect runtime evidence and verify cleanup.
26
38
 
27
39
  ## Runtime Data
28
40
 
@@ -38,9 +50,9 @@ That exact stable path is injected into the Agent prompt. The prompt requires ev
38
50
  {"probe":"player-state","ts":1787193600000,"data":{"isGrounded":false}}
39
51
  ```
40
52
 
41
- Probes append directly to `current.jsonl`; they must not overwrite it, and they should flush and close the file promptly rather than retaining an exclusive handle. The stable filename means probes retained across rounds continue writing to the correct place without being rewritten just to change a path. The mechanism does not use HTTP, localhost, sockets, or any other network transport, so environments such as the Unity Editor can use their normal filesystem APIs. The target process still needs permission to access the displayed absolute path.
53
+ If the log file contains zero entries, treat that as instrumentation or execution-path evidence. Check the build, path permissions, code path, and file append errors; do not ask the user to copy Unity Console output. Use `/debug-note` for reproduction details, not manual console transcription.
42
54
 
43
- When you run `/debug-done proceed`, the plugin archives the completed file as `<run-id>.jsonl`, creates an empty `current.jsonl` for the next reproduction, and makes the Agent analyze the archived evidence through `get_debug_logs`. `/debug-status` shows the current absolute file path and log count.
55
+ When you proceed from the review menu, `/debug-done proceed`, or `/debug-note`, the plugin archives the completed file as `<run-id>.jsonl`, creates an empty `current.jsonl` for the next reproduction, and makes the Agent analyze the archived evidence through `get_debug_logs`. `/debug-status` shows the current absolute file path and log count.
44
56
 
45
57
  ## Install
46
58
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@siuver/omp-debug-mode",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "type": "module",
5
5
  "description": "A Cursor Debug Mode replica for evidence-driven, human-in-the-loop debugging in oh-my-pi.",
6
6
  "license": "MIT",
package/src/main.ts CHANGED
@@ -5,26 +5,27 @@
5
5
  * IDLE → /debug-mode <problem>
6
6
  * → agent hypothesizes, adds @omp-probe instrumentation, attempts a fix
7
7
  * → WAITING_REPRO: agent stops; user reproduces out-of-band
8
- * → /debug-done fixed → agent removes probes + summarizes → teardown (logs deleted)
9
- * → /debug-done proceed → agent analyzes captured logs, re-instruments + fixes → loop
8
+ * → Mark as fixed → agent removes probes + summarizes → teardown (logs deleted)
9
+ * → Proceed/add details → agent analyzes logs, re-instruments + fixes → loop
10
10
  *
11
11
  * - Stable JSONL file at <cwd>/.omp/debug/current.jsonl — runtime probes append
12
12
  * observations directly with the target environment's native file APIs
13
13
  * - Probe ledger: tool_call interception on edit/write records @omp-probe ids;
14
14
  * ground truth is rescanned from disk (checkLedger)
15
15
  * - Tools: get_debug_logs, list_debug_probes
16
- * - Commands: /debug-mode, /debug-status, /debug-done fixed|proceed, /debug-abort
16
+ * - Commands: /debug-mode, /debug-review, /debug-note, /debug-status, /debug-done, /debug-abort
17
17
  */
18
18
  import type { ExtensionAPI, ExtensionContext } from "@oh-my-pi/pi-coding-agent";
19
19
  import * as fs from "node:fs";
20
20
  import * as path from "node:path";
21
21
  import { ACTIVE_LOG_FILE, prepareRunLog, readJsonlLines, resolveRunLogFile } from "./log-files";
22
+ import { REVIEW_ABORT, REVIEW_ADD_DETAILS, REVIEW_MARK_FIXED, REVIEW_OPTIONS, REVIEW_PROCEED } from "./review-actions";
22
23
 
23
24
  const DEBUG_ENTRY = "com.omp.debug-mode.state";
24
25
  const PROBE_MARK = /@omp-probe\s+([A-Za-z0-9_-]+)/g;
25
-
26
26
  type Phase = "idle" | "round" | "waiting" | "cleanup";
27
27
 
28
+
28
29
  interface Probe {
29
30
  id: string;
30
31
  file: string;
@@ -48,7 +49,6 @@ interface DebugState {
48
49
  function freshState(): DebugState {
49
50
  return {
50
51
  active: false,
51
- phase: "idle",
52
52
  problem: "",
53
53
  round: 0,
54
54
  runId: null,
@@ -86,8 +86,8 @@ Deployed probes (ground truth, maintained by the extension):
86
86
  ${probes}
87
87
 
88
88
  Current run log file (absolute path): ${logFileFor(s) ?? "(not initialized)"}
89
- Runtime probes MUST append JSONL directly to that exact file using the target runtime's native file APIs.
90
- Probe code MUST carry the marker comment \`// @omp-probe <id>\` adjacent to it.
89
+ Runtime probes MUST append JSONL directly to that exact file using the target environment's native file APIs.
90
+ Console output such as Unity Debug.Log may supplement diagnostics but is never the runtime evidence for this workflow. Never ask the user to transcribe console output.
91
91
  Logs by run: ${counts}
92
92
  Current run id: ${s.runId ?? "(not started)"}`;
93
93
  }
@@ -97,25 +97,26 @@ const METHODOLOGY = `\
97
97
  Each round consists of, in order:
98
98
  1. Restate hypotheses (mark each: pending / ruled-out / confirmed via runtime evidence).
99
99
  2. Update instrumentation: remove probes that yielded no information, add probes that
100
- discriminate between remaining hypotheses. Each probe must use the target runtime's
101
- native file append API to append exactly one compact JSON object plus a newline to the
102
- exact absolute log file shown above. Use the schema {"probe":"<id>","ts":<epoch-ms>,
103
- "data":<JSON-serializable-observation>}. Append; never overwrite or truncate. Open,
104
- append, flush, and close promptly for each observation; do not retain an exclusive file
105
- handle across the reproduction gate. Do not use HTTP, POST, localhost, sockets, or any
106
- network transport. Escape the path correctly for the target language. Keep the marker
107
- comment \`// @omp-probe <id>\` adjacent to the code.
100
+ discriminate between remaining hypotheses. Every runtime probe MUST append exactly
101
+ one compact JSON object plus a newline to the exact absolute log file shown above.
102
+ Use {"probe":"<id>","ts":<epoch-ms>,"data":<JSON-serializable-observation>}.
103
+ Append; never overwrite or truncate. Open, append, flush, and close promptly for each
104
+ observation. Do not use HTTP, POST, localhost, sockets, or any network transport.
105
+ Use the target environment's native file API and ensure the code path writes the file.
106
+ Console logging may supplement the file but never replaces it. Do not ask the user to
107
+ copy or summarize console output.
108
108
  3. ATTEMPT A FIX for your leading hypothesis in the same round. Instrumentation without
109
109
  a fix is an incomplete round — the loop only advances when you fix something.
110
110
  4. End the round by writing concise reproduction steps for the user (exact commands or
111
- actions, what to observe). Then STOP — the user reproduces out-of-band and answers
112
- with /debug-done fixed or /debug-done proceed.
111
+ actions, what to observe). Then STOP — the user reproduces out-of-band and uses
112
+ the review menu, /debug-note, or /debug-done fixed|proceed.
113
113
  When told the fix is confirmed: remove every probe, verify with the list_debug_probes
114
114
  tool that the ledger is empty, then summarize root cause and the final fix.`;
115
115
 
116
116
  export default function debugModeExtension(pi: ExtensionAPI) {
117
117
  const z = pi.zod;
118
118
  const state: DebugState = freshState();
119
+ let reviewMenuOpen = false;
119
120
  let uiCtx: ExtensionContext | null = null;
120
121
 
121
122
  // ============================== log files ==============================
@@ -171,8 +172,10 @@ export default function debugModeExtension(pi: ExtensionAPI) {
171
172
  return run;
172
173
  }
173
174
 
175
+
174
176
  // ============================== probe ledger ==============================
175
177
 
178
+
176
179
  pi.on("tool_call", async (event) => {
177
180
  if (!state.active) return;
178
181
  if (event.toolName !== "edit" && event.toolName !== "write") return;
@@ -184,8 +187,7 @@ export default function debugModeExtension(pi: ExtensionAPI) {
184
187
  if (!state.probes.some(p => p.id === id)) state.probes.push({ id, file, round: state.round });
185
188
  }
186
189
  }
187
- // removal detection: edit inputs alone can't prove deletion — checkLedger()
188
- // rescans files for ground truth on demand.
190
+ // Removal detection rescans files for ground truth on demand.
189
191
  });
190
192
 
191
193
  /** Ground truth: which registered probes still exist in code. */
@@ -228,7 +230,7 @@ export default function debugModeExtension(pi: ExtensionAPI) {
228
230
  if (state.phase === "waiting") {
229
231
  refreshLogCounts();
230
232
  const n = state.runId ? (state.logCounts[state.runId] ?? 0) : 0;
231
- lines.push(ctx.ui.theme.fg("accent", "reproduce the bug, then: /debug-done fixed | proceed"));
233
+ lines.push(ctx.ui.theme.fg("accent", "reproduce the bug, then: /debug-review"));
232
234
  lines.push(ctx.ui.theme.fg("dim", `run ${state.runId} — ${n} log entries`));
233
235
  }
234
236
  ctx.ui.setWidget("debug-mode", lines.length ? lines : undefined);
@@ -274,16 +276,20 @@ export default function debugModeExtension(pi: ExtensionAPI) {
274
276
  if (!state.active) return;
275
277
  if (state.phase === "cleanup") {
276
278
  // Cleanup turn settled: keep fixes and remove the temporary logs.
277
- if (state.cleanupReady) await teardown(ctx, true);
279
+ if (state.cleanupReady) await teardown(ctx, "finished");
278
280
  return;
279
281
  }
280
282
  if (state.phase !== "round" || !state.hasRoundContent) return;
281
283
  state.phase = "waiting";
284
+ pi.appendEntry(DEBUG_ENTRY, { ...state });
282
285
  refreshUi();
283
286
  ctx.ui.notify(
284
- `Debug round ${state.round} paused.\nReproduce the bug now, then run:\n /debug-done fixed — fix confirmed, clean up & summarize\n /debug-done proceed — analyze logs, next round`,
287
+ `Debug round ${state.round} paused. Reproduce the bug, then choose an action or run /debug-review.`,
285
288
  "info",
286
289
  );
290
+ if (ctx.hasUI) {
291
+ void openReviewMenu(ctx).catch(err => pi.logger.warn("debug-mode: review menu failed", { err }));
292
+ }
287
293
  });
288
294
 
289
295
  pi.registerCommand("debug-mode", {
@@ -291,7 +297,7 @@ export default function debugModeExtension(pi: ExtensionAPI) {
291
297
  handler: async (args, ctx) => {
292
298
  uiCtx = ctx;
293
299
  if (state.active) {
294
- ctx.ui.notify("debug-mode: already active (use /debug-done or /debug-abort)", "error");
300
+ ctx.ui.notify("debug-mode: already active (use /debug-review, /debug-done, or /debug-abort)", "error");
295
301
  return;
296
302
  }
297
303
  const problem = args.trim();
@@ -331,65 +337,94 @@ export default function debugModeExtension(pi: ExtensionAPI) {
331
337
  );
332
338
  }
333
339
 
334
- function finishDebug(ctx: ExtensionContext, verdict: "fixed" | "proceed"): void {
340
+ function ensureWaiting(ctx: ExtensionContext): boolean {
335
341
  if (!state.active) {
336
342
  ctx.ui.notify("debug-mode: not active", "error");
337
- return;
343
+ return false;
338
344
  }
339
345
  if (state.phase !== "waiting") {
340
346
  ctx.ui.notify(`debug-mode: not waiting for reproduction (phase: ${state.phase})`, "error");
341
- return;
347
+ return false;
342
348
  }
349
+ return true;
350
+ }
351
+
352
+ async function markDebugFixed(ctx: ExtensionContext): Promise<void> {
353
+ if (!ensureWaiting(ctx)) return;
343
354
  refreshLogCounts();
344
- if (verdict === "fixed") {
345
- state.phase = "cleanup";
346
- state.cleanupReady = false;
347
- refreshUi();
348
- pi.sendMessage(
349
- {
350
- customType: "debug-mode-fixed",
351
- content:
352
- "User marked the problem FIXED.\n" +
353
- "1. Remove every debug probe from the code (probe ledger below; verify with list_debug_probes after edits).\n" +
354
- "2. Then summarize: root cause, the fix applied, what remains in the working diff.\n" +
355
- `Probe ledger: ${JSON.stringify(state.probes)}`,
356
- display: true,
357
- },
358
- { triggerTurn: true },
355
+ const logCount = state.runId ? (state.logCounts[state.runId] ?? 0) : 0;
356
+ if (logCount === 0 && ctx.hasUI) {
357
+ const confirmed = await ctx.ui.confirm(
358
+ "Mark as fixed without runtime logs?",
359
+ "No runtime observations were captured for this round. Mark the problem as fixed anyway?",
359
360
  );
360
- // teardown happens when the cleanup turn settles (session_stop above)
361
- } else {
362
- const run = state.runId ?? "(none)";
363
- const n = state.logCounts[run] ?? 0;
364
- const previousRound = state.round;
365
- state.round += 1;
366
- state.phase = "round";
367
- state.hasRoundContent = false;
368
- if (!newRun()) {
369
- state.round = previousRound;
370
- state.phase = "waiting";
371
- ctx.ui.notify("debug-mode: could not initialize the next run log file; staying at the reproduction gate", "error");
372
- refreshUi();
373
- return;
374
- }
375
- refreshUi();
376
- pi.sendMessage(
377
- {
378
- customType: "debug-mode-proceed",
379
- content:
380
- `User chose PROCEED — the fix did not resolve it (run ${run} captured ${n} log entries).\n` +
381
- (n === 0
382
- ? "No logs were captured: the instrumented code path may not have executed, the build may be stale, the path may be wrong, or the file append may have failed — treat that as a signal.\n"
383
- : "") +
384
- "Read the logs with get_debug_logs, rule out / strengthen hypotheses, re-instrument, attempt the next fix, give reproduction steps, and stop.",
385
- display: true,
386
- },
387
- { triggerTurn: true },
361
+ if (!confirmed) return;
362
+ }
363
+ if (!state.active || state.phase !== "waiting") return;
364
+
365
+ state.phase = "cleanup";
366
+ state.cleanupReady = false;
367
+ refreshUi();
368
+ pi.sendMessage(
369
+ {
370
+ customType: "debug-mode-fixed",
371
+ content:
372
+ "User marked the problem FIXED.\n" +
373
+ "1. Remove every debug probe from the code (probe ledger below; verify with list_debug_probes after edits).\n" +
374
+ "2. Then summarize: root cause, the fix applied, what remains in the working diff.\n" +
375
+ `Probe ledger: ${JSON.stringify(state.probes)}`,
376
+ display: true,
377
+ },
378
+ { triggerTurn: true },
379
+ );
380
+ }
381
+
382
+ async function advanceDebug(ctx: ExtensionContext, reproductionDetails?: string): Promise<void> {
383
+ if (!ensureWaiting(ctx)) return;
384
+ refreshLogCounts();
385
+ const run = state.runId ?? "(none)";
386
+ const logCount = state.logCounts[run] ?? 0;
387
+ if (logCount === 0 && !reproductionDetails && ctx.hasUI) {
388
+ const confirmed = await ctx.ui.confirm(
389
+ "Proceed without runtime logs?",
390
+ "No runtime observations were captured for this round. Continue to another analysis and fix round anyway?",
388
391
  );
392
+ if (!confirmed) return;
389
393
  }
394
+ if (!state.active || state.phase !== "waiting") return;
395
+
396
+ const previousRound = state.round;
397
+ state.round += 1;
398
+ state.phase = "round";
399
+ state.hasRoundContent = false;
400
+ if (!newRun()) {
401
+ state.round = previousRound;
402
+ state.phase = "waiting";
403
+ ctx.ui.notify("debug-mode: could not initialize the next run log file; staying at the reproduction gate", "error");
404
+ refreshUi();
405
+ return;
406
+ }
407
+ refreshUi();
408
+
409
+ const userEvidence = reproductionDetails
410
+ ? `User added reproduction details after run ${run}:\n\n${reproductionDetails}\n\nTreat these details as evidence alongside the captured logs.\n`
411
+ : `User chose PROCEED — the fix did not resolve it (run ${run} captured ${logCount} log entries).\n`;
412
+ pi.sendMessage(
413
+ {
414
+ customType: reproductionDetails ? "debug-mode-note" : "debug-mode-proceed",
415
+ content:
416
+ userEvidence +
417
+ (logCount === 0
418
+ ? "No logs were captured: the instrumented code path may not have executed, the build may be stale, the path may be wrong, or the file append may have failed — treat that as a signal.\n"
419
+ : `Run ${run} captured ${logCount} log entries.\n`) +
420
+ "Read the previous run with get_debug_logs, update the hypotheses, re-instrument, attempt the next fix, give reproduction steps, and stop.",
421
+ display: true,
422
+ },
423
+ { triggerTurn: true },
424
+ );
390
425
  }
391
426
 
392
- async function teardown(ctx: ExtensionContext, keepFixes: boolean): Promise<void> {
427
+ async function teardown(ctx: ExtensionContext, outcome: "finished" | "aborted"): Promise<void> {
393
428
  if (state.debugDir) {
394
429
  try {
395
430
  fs.rmSync(state.debugDir, { recursive: true, force: true });
@@ -401,13 +436,63 @@ export default function debugModeExtension(pi: ExtensionAPI) {
401
436
  Object.assign(state, freshState());
402
437
  pi.appendEntry(DEBUG_ENTRY, { ...state });
403
438
  refreshUi();
439
+ const resultLabel = outcome === "finished" ? "finished" : "aborted";
404
440
  if (probesLeft.length > 0) {
405
441
  ctx.ui.notify(
406
- `debug-mode ended, but ${probesLeft.length} probe(s) remain in code: ${probesLeft.map(p => p.id).join(", ")} — remove manually.`,
442
+ `Debug mode ${resultLabel}, but ${probesLeft.length} probe(s) remain in code: ${probesLeft.map(p => p.id).join(", ")} — remove manually. Applied fixes remain in the working diff.`,
407
443
  "warning",
408
444
  );
409
- } else if (keepFixes) {
410
- ctx.ui.notify("Debug mode finished. Log files removed; working diff contains the fix — review with git diff.", "info");
445
+ } else {
446
+ ctx.ui.notify(
447
+ `Debug mode ${resultLabel}. Log files removed; applied fixes remain in the working diff for review.`,
448
+ "info",
449
+ );
450
+ }
451
+ }
452
+
453
+ async function abortDebug(ctx: ExtensionContext): Promise<void> {
454
+ if (!state.active) {
455
+ ctx.ui.notify("debug-mode: not active", "error");
456
+ return;
457
+ }
458
+ if (ctx.hasUI) {
459
+ const confirmed = await ctx.ui.confirm(
460
+ "Abort debug mode?",
461
+ "Delete captured debug logs and stop the workflow? Applied code changes will remain in the working diff.",
462
+ );
463
+ if (!confirmed) return;
464
+ }
465
+ if (!state.active) return;
466
+ await teardown(ctx, "aborted");
467
+ }
468
+
469
+ async function openReviewMenu(ctx: ExtensionContext): Promise<void> {
470
+ if (!ensureWaiting(ctx)) return;
471
+ if (!ctx.hasUI) {
472
+ ctx.ui.notify("/debug-review requires an interactive UI; use /debug-done fixed|proceed instead.", "warning");
473
+ return;
474
+ }
475
+ if (reviewMenuOpen) {
476
+ ctx.ui.notify("debug-mode: review menu is already open", "info");
477
+ return;
478
+ }
479
+
480
+ reviewMenuOpen = true;
481
+ try {
482
+ const choice = await ctx.ui.select(`Review debug round ${state.round}`, [...REVIEW_OPTIONS]);
483
+ if (!choice) return;
484
+ if (choice === REVIEW_MARK_FIXED) {
485
+ await markDebugFixed(ctx);
486
+ } else if (choice === REVIEW_PROCEED) {
487
+ await advanceDebug(ctx);
488
+ } else if (choice === REVIEW_ADD_DETAILS) {
489
+ ctx.ui.setEditorText("/debug-note ");
490
+ ctx.ui.notify("Add reproduction details in the editor, then submit /debug-note.", "info");
491
+ } else if (choice === REVIEW_ABORT) {
492
+ await abortDebug(ctx);
493
+ }
494
+ } finally {
495
+ reviewMenuOpen = false;
411
496
  }
412
497
  }
413
498
 
@@ -419,11 +504,34 @@ export default function debugModeExtension(pi: ExtensionAPI) {
419
504
  handler: async (args, ctx) => {
420
505
  uiCtx = ctx;
421
506
  const verdict = args.trim().toLowerCase();
422
- if (verdict !== "fixed" && verdict !== "proceed") {
507
+ if (verdict === "fixed") {
508
+ await markDebugFixed(ctx);
509
+ } else if (verdict === "proceed") {
510
+ await advanceDebug(ctx);
511
+ } else {
423
512
  ctx.ui.notify("Usage: /debug-done fixed|proceed", "error");
513
+ }
514
+ },
515
+ });
516
+
517
+ pi.registerCommand("debug-review", {
518
+ description: "Open the interactive action menu for a completed debug round",
519
+ handler: async (_args, ctx) => {
520
+ uiCtx = ctx;
521
+ await openReviewMenu(ctx);
522
+ },
523
+ });
524
+
525
+ pi.registerCommand("debug-note", {
526
+ description: "Add reproduction details and continue: /debug-note <details>",
527
+ handler: async (args, ctx) => {
528
+ uiCtx = ctx;
529
+ const details = args.trim();
530
+ if (!details) {
531
+ ctx.ui.notify("Usage: /debug-note <reproduction details>", "error");
424
532
  return;
425
533
  }
426
- finishDebug(ctx, verdict);
534
+ await advanceDebug(ctx, details);
427
535
  },
428
536
  });
429
537
 
@@ -431,20 +539,7 @@ export default function debugModeExtension(pi: ExtensionAPI) {
431
539
  description: "Abort debug mode: delete logs (fixes stay in the working diff)",
432
540
  handler: async (_args, ctx) => {
433
541
  uiCtx = ctx;
434
- if (!state.active) {
435
- ctx.ui.notify("debug-mode: not active", "error");
436
- return;
437
- }
438
- const ledger = await checkLedger();
439
- await teardown(ctx, true);
440
- if (ledger.length > 0) {
441
- ctx.ui.notify(
442
- `Debug mode aborted. These probes remain (remove manually or ask the agent):\n${ledger.map(p => ` ${p.id} — ${p.file}`).join("\n")}\nApplied fixes are kept in the working diff.`,
443
- "warning",
444
- );
445
- } else {
446
- ctx.ui.notify("Debug mode aborted. No probes in code; applied fixes are kept in the working diff.", "info");
447
- }
542
+ await abortDebug(ctx);
448
543
  },
449
544
  });
450
545
 
@@ -471,6 +566,7 @@ export default function debugModeExtension(pi: ExtensionAPI) {
471
566
 
472
567
  // ============================== tools ==============================
473
568
 
569
+
474
570
  pi.registerTool({
475
571
  name: "get_debug_logs",
476
572
  label: "Get Debug Logs",
@@ -572,7 +668,7 @@ export default function debugModeExtension(pi: ExtensionAPI) {
572
668
  }
573
669
  refreshLogCounts();
574
670
  ctx.ui.notify(
575
- `debug-mode resumed: phase=${state.phase} round=${state.round}, log=${currentFile ?? "unavailable"}. Use /debug-status, /debug-done fixed|proceed, or /debug-abort.`,
671
+ `debug-mode resumed: phase=${state.phase} round=${state.round}, log=${currentFile ?? "unavailable"}. Use /debug-status, /debug-review, /debug-done fixed|proceed, or /debug-abort.`,
576
672
  "info",
577
673
  );
578
674
  }
@@ -0,0 +1,11 @@
1
+ export const REVIEW_MARK_FIXED = "Mark as fixed";
2
+ export const REVIEW_PROCEED = "Proceed with captured logs";
3
+ export const REVIEW_ADD_DETAILS = "Add reproduction details";
4
+ export const REVIEW_ABORT = "Abort debug mode";
5
+
6
+ export const REVIEW_OPTIONS = [
7
+ REVIEW_MARK_FIXED,
8
+ REVIEW_PROCEED,
9
+ REVIEW_ADD_DETAILS,
10
+ REVIEW_ABORT,
11
+ ] as const;