taskplane 0.29.2 → 0.30.1

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 (43) hide show
  1. package/bin/gitignore-patterns.mjs +11 -8
  2. package/bin/rpc-wrapper.mjs +410 -357
  3. package/bin/taskplane.mjs +533 -250
  4. package/dashboard/public/app.js +124 -15
  5. package/dashboard/public/style.css +83 -2
  6. package/extensions/reviewer-extension.ts +17 -11
  7. package/extensions/taskplane/abort.ts +50 -18
  8. package/extensions/taskplane/agent-bridge-extension.ts +232 -105
  9. package/extensions/taskplane/agent-host.ts +224 -97
  10. package/extensions/taskplane/cleanup.ts +71 -42
  11. package/extensions/taskplane/config-loader.ts +142 -58
  12. package/extensions/taskplane/config-schema.ts +6 -13
  13. package/extensions/taskplane/config.ts +10 -2
  14. package/extensions/taskplane/diagnostic-reports.ts +59 -47
  15. package/extensions/taskplane/diagnostics.ts +13 -13
  16. package/extensions/taskplane/discovery.ts +78 -63
  17. package/extensions/taskplane/engine-worker.ts +53 -46
  18. package/extensions/taskplane/engine.ts +1760 -602
  19. package/extensions/taskplane/execution.ts +469 -207
  20. package/extensions/taskplane/extension.ts +1073 -598
  21. package/extensions/taskplane/formatting.ts +136 -124
  22. package/extensions/taskplane/git.ts +0 -2
  23. package/extensions/taskplane/lane-runner.ts +652 -319
  24. package/extensions/taskplane/mailbox.ts +57 -49
  25. package/extensions/taskplane/merge.ts +662 -383
  26. package/extensions/taskplane/messages.ts +109 -51
  27. package/extensions/taskplane/migrations.ts +1 -1
  28. package/extensions/taskplane/path-resolver.ts +8 -9
  29. package/extensions/taskplane/persistence.ts +425 -262
  30. package/extensions/taskplane/process-registry.ts +36 -7
  31. package/extensions/taskplane/quality-gate.ts +107 -55
  32. package/extensions/taskplane/resume.ts +832 -280
  33. package/extensions/taskplane/sessions.ts +1 -1
  34. package/extensions/taskplane/settings-tui.ts +505 -164
  35. package/extensions/taskplane/sidecar-telemetry.ts +25 -10
  36. package/extensions/taskplane/supervisor.ts +477 -270
  37. package/extensions/taskplane/task-executor-core.ts +178 -53
  38. package/extensions/taskplane/types.ts +209 -108
  39. package/extensions/taskplane/verification.ts +27 -22
  40. package/extensions/taskplane/waves.ts +59 -43
  41. package/extensions/taskplane/workspace.ts +14 -12
  42. package/extensions/taskplane/worktree.ts +218 -196
  43. package/package.json +14 -2
@@ -52,7 +52,11 @@ function resolveOutboxDir(): string {
52
52
  /**
53
53
  * Write a message to the agent's outbox.
54
54
  */
55
- function writeOutbox(type: "reply" | "escalate", content: string, replyTo?: string): { id: string } {
55
+ function writeOutbox(
56
+ type: "reply" | "escalate",
57
+ content: string,
58
+ replyTo?: string,
59
+ ): { id: string } {
56
60
  const outboxDir = resolveOutboxDir();
57
61
  mkdirSync(outboxDir, { recursive: true });
58
62
 
@@ -89,7 +93,11 @@ const REPO_ID_PATTERN = /^[a-z0-9][a-z0-9._-]*$/;
89
93
  const AUTONOMY_PATTERN = /^(interactive|supervised|autonomous)$/;
90
94
 
91
95
  function resolveActiveSegmentId(): string | null {
92
- const raw = (process.env.TASKPLANE_ACTIVE_SEGMENT_ID || process.env.TASKPLANE_SEGMENT_ID || "").trim();
96
+ const raw = (
97
+ process.env.TASKPLANE_ACTIVE_SEGMENT_ID ||
98
+ process.env.TASKPLANE_SEGMENT_ID ||
99
+ ""
100
+ ).trim();
93
101
  if (!raw || raw === "null" || raw === "(none / whole-task execution)") return null;
94
102
  return raw;
95
103
  }
@@ -125,8 +133,14 @@ function writeSegmentExpansionRequest(request: SegmentExpansionRequest): string
125
133
  writeFileSync(tempPath, JSON.stringify(request, null, 2) + "\n", "utf-8");
126
134
  renameSync(tempPath, finalPath);
127
135
  } catch (err) {
128
- try { if (existsSync(tempPath)) unlinkSync(tempPath); } catch { /* cleanup */ }
129
- throw new Error(`Failed to write segment expansion request: ${err instanceof Error ? err.message : String(err)}`);
136
+ try {
137
+ if (existsSync(tempPath)) unlinkSync(tempPath);
138
+ } catch {
139
+ /* cleanup */
140
+ }
141
+ throw new Error(
142
+ `Failed to write segment expansion request: ${err instanceof Error ? err.message : String(err)}`,
143
+ );
130
144
  }
131
145
 
132
146
  return finalPath;
@@ -215,11 +229,7 @@ export function isStepMarkedComplete(statusPath: string, stepNum: number): boole
215
229
  // 2. delimiter length >= opener length,
216
230
  // 3. nothing follows the delimiter except whitespace.
217
231
  const trailingIsWhitespace = /^\s*$/.test(trailing);
218
- if (
219
- char === fenceOpener.char &&
220
- length >= fenceOpener.length &&
221
- trailingIsWhitespace
222
- ) {
232
+ if (char === fenceOpener.char && length >= fenceOpener.length && trailingIsWhitespace) {
223
233
  fenceOpener = null;
224
234
  continue;
225
235
  }
@@ -258,26 +268,32 @@ export default function (pi: ExtensionAPI) {
258
268
  content: Type.String({
259
269
  description: "Reply content (max 4KB)",
260
270
  }),
261
- replyTo: Type.Optional(Type.String({
262
- description: "Message ID being replied to (from a steering message)",
263
- })),
271
+ replyTo: Type.Optional(
272
+ Type.String({
273
+ description: "Message ID being replied to (from a steering message)",
274
+ }),
275
+ ),
264
276
  }),
265
277
  async execute(_toolCallId, params) {
266
278
  try {
267
279
  const result = writeOutbox("reply", params.content, params.replyTo);
268
280
  return {
269
- content: [{
270
- type: "text" as const,
271
- text: `✅ Reply sent to supervisor (ID: ${result.id})`,
272
- }],
281
+ content: [
282
+ {
283
+ type: "text" as const,
284
+ text: `✅ Reply sent to supervisor (ID: ${result.id})`,
285
+ },
286
+ ],
273
287
  details: undefined,
274
288
  };
275
289
  } catch (err) {
276
290
  return {
277
- content: [{
278
- type: "text" as const,
279
- text: `❌ Failed to send reply: ${err instanceof Error ? err.message : String(err)}`,
280
- }],
291
+ content: [
292
+ {
293
+ type: "text" as const,
294
+ text: `❌ Failed to send reply: ${err instanceof Error ? err.message : String(err)}`,
295
+ },
296
+ ],
281
297
  details: undefined,
282
298
  };
283
299
  }
@@ -305,18 +321,22 @@ export default function (pi: ExtensionAPI) {
305
321
  try {
306
322
  const result = writeOutbox("escalate", params.content);
307
323
  return {
308
- content: [{
309
- type: "text" as const,
310
- text: `⚠️ Escalation sent to supervisor (ID: ${result.id}). Continue working on other items while waiting for guidance.`,
311
- }],
324
+ content: [
325
+ {
326
+ type: "text" as const,
327
+ text: `⚠️ Escalation sent to supervisor (ID: ${result.id}). Continue working on other items while waiting for guidance.`,
328
+ },
329
+ ],
312
330
  details: undefined,
313
331
  };
314
332
  } catch (err) {
315
333
  return {
316
- content: [{
317
- type: "text" as const,
318
- text: `❌ Failed to escalate: ${err instanceof Error ? err.message : String(err)}`,
319
- }],
334
+ content: [
335
+ {
336
+ type: "text" as const,
337
+ text: `❌ Failed to escalate: ${err instanceof Error ? err.message : String(err)}`,
338
+ },
339
+ ],
320
340
  details: undefined,
321
341
  };
322
342
  }
@@ -340,8 +360,7 @@ export default function (pi: ExtensionAPI) {
340
360
  description:
341
361
  "Request additional repository segments for the current task at runtime. " +
342
362
  "Writes a request file to the worker outbox for engine processing.",
343
- promptSnippet:
344
- "request_segment_expansion(requestedRepoIds, rationale, placement?, edges?)",
363
+ promptSnippet: "request_segment_expansion(requestedRepoIds, rationale, placement?, edges?)",
345
364
  promptGuidelines: [
346
365
  "Use this when runtime discovery reveals additional repos are needed.",
347
366
  "Do not wait for approval; continue current segment work after requesting.",
@@ -355,18 +374,22 @@ export default function (pi: ExtensionAPI) {
355
374
  rationale: Type.String({
356
375
  description: "Why these repos are needed",
357
376
  }),
358
- placement: Type.Optional(Type.Union([
359
- Type.Literal("after-current"),
360
- Type.Literal("end"),
361
- ], {
362
- description: "Where to place new segments: after-current (default) or end",
363
- })),
364
- edges: Type.Optional(Type.Array(Type.Object({
365
- from: Type.String({ description: "Source repo ID" }),
366
- to: Type.String({ description: "Destination repo ID" }),
367
- }), {
368
- description: "Optional ordering edges between requested repos",
369
- })),
377
+ placement: Type.Optional(
378
+ Type.Union([Type.Literal("after-current"), Type.Literal("end")], {
379
+ description: "Where to place new segments: after-current (default) or end",
380
+ }),
381
+ ),
382
+ edges: Type.Optional(
383
+ Type.Array(
384
+ Type.Object({
385
+ from: Type.String({ description: "Source repo ID" }),
386
+ to: Type.String({ description: "Destination repo ID" }),
387
+ }),
388
+ {
389
+ description: "Optional ordering edges between requested repos",
390
+ },
391
+ ),
392
+ ),
370
393
  }),
371
394
  async execute(_toolCallId, params) {
372
395
  const autonomy = resolveSupervisorAutonomy();
@@ -428,9 +451,11 @@ export default function (pi: ExtensionAPI) {
428
451
  placement: params.placement === "end" ? "end" : "after-current",
429
452
  edges: Array.isArray(params.edges)
430
453
  ? params.edges
431
- .filter((edge): edge is { from: string; to: string } => Boolean(edge && typeof edge.from === "string" && typeof edge.to === "string"))
432
- .map((edge) => ({ from: edge.from.trim(), to: edge.to.trim() }))
433
- .filter((edge) => edge.from.length > 0 && edge.to.length > 0)
454
+ .filter((edge): edge is { from: string; to: string } =>
455
+ Boolean(edge && typeof edge.from === "string" && typeof edge.to === "string"),
456
+ )
457
+ .map((edge) => ({ from: edge.from.trim(), to: edge.to.trim() }))
458
+ .filter((edge) => edge.from.length > 0 && edge.to.length > 0)
434
459
  : [],
435
460
  timestamp: now,
436
461
  };
@@ -466,14 +491,13 @@ export default function (pi: ExtensionAPI) {
466
491
  // The reviewer runs as a separate Pi process, writes feedback to
467
492
  // .reviews/, and this tool returns the verdict to the worker.
468
493
 
469
-
470
-
471
494
  /**
472
495
  * Load the reviewer system prompt from base template + local override.
473
496
  * Uses resolveTaskplaneAgentTemplate (path-resolver.ts) for all platform support (TP-157).
474
497
  */
475
498
  function loadReviewerPrompt(): string {
476
- let basePrompt = "You are a code reviewer. Read the request and write your review to the specified output file.";
499
+ let basePrompt =
500
+ "You are a code reviewer. Read the request and write your review to the specified output file.";
477
501
  try {
478
502
  const templatePath = resolveTaskplaneAgentTemplate("task-reviewer");
479
503
  if (existsSync(templatePath)) {
@@ -481,9 +505,14 @@ export default function (pi: ExtensionAPI) {
481
505
  const fmEnd = raw.indexOf("---", 4);
482
506
  if (fmEnd > 0) basePrompt = raw.slice(fmEnd + 3).trim();
483
507
  }
484
- } catch { /* fall through to default */ }
508
+ } catch {
509
+ /* fall through to default */
510
+ }
485
511
  // Local override
486
- const localPaths = [join(process.cwd(), ".pi", "agents", "task-reviewer.md"), join(process.cwd(), "agents", "task-reviewer.md")];
512
+ const localPaths = [
513
+ join(process.cwd(), ".pi", "agents", "task-reviewer.md"),
514
+ join(process.cwd(), "agents", "task-reviewer.md"),
515
+ ];
487
516
  for (const p of localPaths) {
488
517
  try {
489
518
  if (!existsSync(p)) continue;
@@ -494,7 +523,9 @@ export default function (pi: ExtensionAPI) {
494
523
  if (localBody) basePrompt += "\n\n---\n\n## Project-Specific Guidance\n\n" + localBody;
495
524
  }
496
525
  break;
497
- } catch { continue; }
526
+ } catch {
527
+ continue;
528
+ }
498
529
  }
499
530
  return basePrompt;
500
531
  }
@@ -503,21 +534,24 @@ export default function (pi: ExtensionAPI) {
503
534
  return process.env.TASKPLANE_REVIEWER_STATE_PATH || join(taskFolder, ".reviewer-state.json");
504
535
  }
505
536
 
506
- function writeReviewerState(taskFolder: string, state: {
507
- status: "running" | "done" | "error";
508
- elapsedMs: number;
509
- toolCalls: number;
510
- contextPct: number;
511
- costUsd: number;
512
- lastTool: string;
513
- inputTokens: number;
514
- outputTokens: number;
515
- cacheReadTokens: number;
516
- cacheWriteTokens: number;
517
- updatedAt: number;
518
- reviewType?: string;
519
- reviewStep?: number;
520
- }): void {
537
+ function writeReviewerState(
538
+ taskFolder: string,
539
+ state: {
540
+ status: "running" | "done" | "error";
541
+ elapsedMs: number;
542
+ toolCalls: number;
543
+ contextPct: number;
544
+ costUsd: number;
545
+ lastTool: string;
546
+ inputTokens: number;
547
+ outputTokens: number;
548
+ cacheReadTokens: number;
549
+ cacheWriteTokens: number;
550
+ updatedAt: number;
551
+ reviewType?: string;
552
+ reviewStep?: number;
553
+ },
554
+ ): void {
521
555
  const filePath = reviewerStatePath(taskFolder);
522
556
  const tmpPath = filePath + ".tmp";
523
557
  writeFileSync(tmpPath, JSON.stringify(state, null, 2) + "\n", "utf-8");
@@ -527,14 +561,25 @@ export default function (pi: ExtensionAPI) {
527
561
  function removeReviewerState(taskFolder: string): void {
528
562
  const filePath = reviewerStatePath(taskFolder);
529
563
  if (!existsSync(filePath)) return;
530
- try { unlinkSync(filePath); } catch { /* best effort */ }
564
+ try {
565
+ unlinkSync(filePath);
566
+ } catch {
567
+ /* best effort */
568
+ }
531
569
  }
532
570
 
533
571
  /**
534
572
  * Spawn a reviewer Pi subprocess and wait for it to complete.
535
573
  * Returns the process exit code.
536
574
  */
537
- function spawnReviewer(prompt: string, systemPrompt: string, cwd: string, taskFolder: string, reviewType?: string, reviewStep?: number): Promise<number> {
575
+ function spawnReviewer(
576
+ prompt: string,
577
+ systemPrompt: string,
578
+ cwd: string,
579
+ taskFolder: string,
580
+ reviewType?: string,
581
+ reviewStep?: number,
582
+ ): Promise<number> {
538
583
  // Pre-clean stale reviewer state from prior interrupted review
539
584
  removeReviewerState(taskFolder);
540
585
  return new Promise((resolve) => {
@@ -548,9 +593,16 @@ export default function (pi: ExtensionAPI) {
548
593
 
549
594
  const cliPath = resolvePiCliPath();
550
595
  const args = [
551
- cliPath, "--mode", "rpc", "--no-session", "--no-extensions", "--no-skills",
552
- "--tools", reviewerTools,
553
- "--system-prompt", systemPrompt,
596
+ cliPath,
597
+ "--mode",
598
+ "rpc",
599
+ "--no-session",
600
+ "--no-extensions",
601
+ "--no-skills",
602
+ "--tools",
603
+ reviewerTools,
604
+ "--system-prompt",
605
+ systemPrompt,
554
606
  ];
555
607
  if (reviewerModel) args.push("--model", reviewerModel);
556
608
  if (reviewerThinking) args.push("--thinking", reviewerThinking);
@@ -570,7 +622,9 @@ export default function (pi: ExtensionAPI) {
570
622
  reviewerExclusions = parsed.filter((v: unknown): v is string => typeof v === "string");
571
623
  }
572
624
  }
573
- } catch { /* ignore malformed */ }
625
+ } catch {
626
+ /* ignore malformed */
627
+ }
574
628
  const filteredReviewerPackages = filterExcludedExtensions(reviewerPackages, reviewerExclusions);
575
629
  for (const pkg of filteredReviewerPackages) {
576
630
  args.push("-e", pkg);
@@ -611,7 +665,9 @@ export default function (pi: ExtensionAPI) {
611
665
  reviewType,
612
666
  reviewStep,
613
667
  });
614
- } catch { /* best effort */ }
668
+ } catch {
669
+ /* best effort */
670
+ }
615
671
  };
616
672
 
617
673
  // Write initial "running" state immediately so dashboard shows
@@ -620,7 +676,11 @@ export default function (pi: ExtensionAPI) {
620
676
 
621
677
  const closeStdin = () => {
622
678
  setTimeout(() => {
623
- try { proc.stdin?.end(); } catch { /* ignore */ }
679
+ try {
680
+ proc.stdin?.end();
681
+ } catch {
682
+ /* ignore */
683
+ }
624
684
  }, 100);
625
685
  };
626
686
 
@@ -642,9 +702,12 @@ export default function (pi: ExtensionAPI) {
642
702
  cacheReadTokens += usage.cacheRead || 0;
643
703
  cacheWriteTokens += usage.cacheWrite || 0;
644
704
  if (usage.cost) {
645
- costUsd += typeof usage.cost === "object"
646
- ? (usage.cost.total || 0)
647
- : (typeof usage.cost === "number" ? usage.cost : 0);
705
+ costUsd +=
706
+ typeof usage.cost === "object"
707
+ ? usage.cost.total || 0
708
+ : typeof usage.cost === "number"
709
+ ? usage.cost
710
+ : 0;
648
711
  }
649
712
  }
650
713
  emitState("running");
@@ -653,11 +716,12 @@ export default function (pi: ExtensionAPI) {
653
716
  case "tool_execution_start": {
654
717
  toolCalls++;
655
718
  const toolName = event.toolName || "tool";
656
- const argPreview = typeof event.args === "string"
657
- ? event.args.slice(0, 80)
658
- : (event.args && typeof Object.values(event.args)[0] === "string"
659
- ? String(Object.values(event.args)[0]).slice(0, 80)
660
- : "");
719
+ const argPreview =
720
+ typeof event.args === "string"
721
+ ? event.args.slice(0, 80)
722
+ : event.args && typeof Object.values(event.args)[0] === "string"
723
+ ? String(Object.values(event.args)[0]).slice(0, 80)
724
+ : "";
661
725
  lastTool = argPreview ? `${toolName}: ${argPreview}` : toolName;
662
726
  emitState("running");
663
727
  break;
@@ -688,7 +752,11 @@ export default function (pi: ExtensionAPI) {
688
752
  if (line.endsWith("\r")) line = line.slice(0, -1);
689
753
  if (!line.trim()) continue;
690
754
  let event: any;
691
- try { event = JSON.parse(line); } catch { continue; }
755
+ try {
756
+ event = JSON.parse(line);
757
+ } catch {
758
+ continue;
759
+ }
692
760
  handleEvent(event);
693
761
  }
694
762
  });
@@ -697,9 +765,16 @@ export default function (pi: ExtensionAPI) {
697
765
  proc.on("error", () => finalize(1));
698
766
 
699
767
  // Timeout: 10 minutes
700
- setTimeout(() => {
701
- try { proc.kill("SIGTERM"); } catch { /* ignore */ }
702
- }, 10 * 60 * 1000);
768
+ setTimeout(
769
+ () => {
770
+ try {
771
+ proc.kill("SIGTERM");
772
+ } catch {
773
+ /* ignore */
774
+ }
775
+ },
776
+ 10 * 60 * 1000,
777
+ );
703
778
  });
704
779
  }
705
780
 
@@ -721,13 +796,14 @@ export default function (pi: ExtensionAPI) {
721
796
  ],
722
797
  parameters: Type.Object({
723
798
  step: Type.Number({ description: "Step number to review" }),
724
- type: Type.Union(
725
- [Type.Literal("plan"), Type.Literal("code")],
726
- { description: 'Review type: "plan" or "code"' },
799
+ type: Type.Union([Type.Literal("plan"), Type.Literal("code")], {
800
+ description: 'Review type: "plan" or "code"',
801
+ }),
802
+ baseline: Type.Optional(
803
+ Type.String({
804
+ description: "Git commit SHA for code review diff baseline",
805
+ }),
727
806
  ),
728
- baseline: Type.Optional(Type.String({
729
- description: "Git commit SHA for code review diff baseline",
730
- })),
731
807
  }),
732
808
  async execute(_toolCallId, params) {
733
809
  const { step: stepNum, type: reviewType, baseline } = params;
@@ -766,7 +842,9 @@ export default function (pi: ExtensionAPI) {
766
842
  const statusContent = readFileSync(statusPath, "utf-8");
767
843
  const rcMatch = statusContent.match(/\*\*Review Counter:\*\*\s*(\d+)/);
768
844
  if (rcMatch) reviewCounter = parseInt(rcMatch[1]);
769
- } catch { /* default 0 */ }
845
+ } catch {
846
+ /* default 0 */
847
+ }
770
848
 
771
849
  reviewCounter++;
772
850
  const num = String(reviewCounter).padStart(3, "0");
@@ -780,14 +858,21 @@ export default function (pi: ExtensionAPI) {
780
858
  if (!existsSync(pf)) continue;
781
859
  const content = readFileSync(pf, "utf-8");
782
860
  const stepMatch = content.match(new RegExp(`###\\s+Step\\s+${stepNum}[:\\s]+(.+)`));
783
- if (stepMatch) { stepName = stepMatch[1].trim(); break; }
861
+ if (stepMatch) {
862
+ stepName = stepMatch[1].trim();
863
+ break;
864
+ }
784
865
  }
785
- } catch { /* use default */ }
866
+ } catch {
867
+ /* use default */
868
+ }
786
869
 
787
870
  // Generate review request prompt
788
871
  const projectName = process.env.TASKPLANE_PROJECT_NAME || "project";
789
872
  const diffCmd = baseline ? `git diff ${baseline}..HEAD` : `git diff`;
790
- const diffNamesCmd = baseline ? `git diff ${baseline}..HEAD --name-only` : `git diff --name-only`;
873
+ const diffNamesCmd = baseline
874
+ ? `git diff ${baseline}..HEAD --name-only`
875
+ : `git diff --name-only`;
791
876
 
792
877
  let reviewPrompt: string;
793
878
  if (reviewType === "plan") {
@@ -835,14 +920,26 @@ export default function (pi: ExtensionAPI) {
835
920
 
836
921
  try {
837
922
  const systemPrompt = loadReviewerPrompt();
838
- const exitCode = await spawnReviewer(reviewPrompt, systemPrompt, cwd, taskFolder, reviewType, stepNum);
923
+ const exitCode = await spawnReviewer(
924
+ reviewPrompt,
925
+ systemPrompt,
926
+ cwd,
927
+ taskFolder,
928
+ reviewType,
929
+ stepNum,
930
+ );
839
931
 
840
932
  // Update review counter in STATUS.md
841
933
  try {
842
934
  const status = readFileSync(statusPath, "utf-8");
843
- const updated = status.replace(/\*\*Review Counter:\*\*\s*\d+/, `**Review Counter:** ${reviewCounter}`);
935
+ const updated = status.replace(
936
+ /\*\*Review Counter:\*\*\s*\d+/,
937
+ `**Review Counter:** ${reviewCounter}`,
938
+ );
844
939
  writeFileSync(statusPath, updated);
845
- } catch { /* best effort */ }
940
+ } catch {
941
+ /* best effort */
942
+ }
846
943
 
847
944
  // Read review output and extract verdict
848
945
  if (existsSync(outputPath)) {
@@ -861,7 +958,9 @@ export default function (pi: ExtensionAPI) {
861
958
  const status = readFileSync(statusPath, "utf-8");
862
959
  const logEntry = `| ${new Date().toISOString().slice(0, 16).replace("T", " ")} | Review R${num} | ${reviewType} Step ${stepNum}: ${verdict} |\n`;
863
960
  writeFileSync(statusPath, status.trimEnd() + "\n" + logEntry);
864
- } catch { /* best effort */ }
961
+ } catch {
962
+ /* best effort */
963
+ }
865
964
 
866
965
  removeReviewerState(taskFolder);
867
966
 
@@ -871,20 +970,48 @@ export default function (pi: ExtensionAPI) {
871
970
  } else if (verdict === "REVISE") {
872
971
  const summaryMatch = reviewContent.match(/###?\s*Summary[:\s]*([\s\S]*?)(?=###|$)/i);
873
972
  const details = summaryMatch ? summaryMatch[1].trim().slice(0, 500) : "See review file.";
874
- return { content: [{ type: "text" as const, text: `REVISE: ${details}\n\nFull review: ${reviewFile}` }], details: undefined };
973
+ return {
974
+ content: [
975
+ { type: "text" as const, text: `REVISE: ${details}\n\nFull review: ${reviewFile}` },
976
+ ],
977
+ details: undefined,
978
+ };
875
979
  } else if (verdict === "RETHINK") {
876
- return { content: [{ type: "text" as const, text: `RETHINK — reconsider approach. See ${reviewFile}` }], details: undefined };
980
+ return {
981
+ content: [
982
+ { type: "text" as const, text: `RETHINK — reconsider approach. See ${reviewFile}` },
983
+ ],
984
+ details: undefined,
985
+ };
877
986
  } else {
878
- return { content: [{ type: "text" as const, text: `Review complete (verdict unclear). See ${reviewFile}` }], details: undefined };
987
+ return {
988
+ content: [
989
+ { type: "text" as const, text: `Review complete (verdict unclear). See ${reviewFile}` },
990
+ ],
991
+ details: undefined,
992
+ };
879
993
  }
880
994
  } else {
881
995
  removeReviewerState(taskFolder);
882
- return { content: [{ type: "text" as const, text: `UNAVAILABLE — reviewer exited (code ${exitCode}) but produced no output.` }], details: undefined };
996
+ return {
997
+ content: [
998
+ {
999
+ type: "text" as const,
1000
+ text: `UNAVAILABLE — reviewer exited (code ${exitCode}) but produced no output.`,
1001
+ },
1002
+ ],
1003
+ details: undefined,
1004
+ };
883
1005
  }
884
1006
  } catch (err) {
885
1007
  removeReviewerState(taskFolder);
886
1008
  return {
887
- content: [{ type: "text" as const, text: `UNAVAILABLE — reviewer failed: ${err instanceof Error ? err.message : String(err)}` }],
1009
+ content: [
1010
+ {
1011
+ type: "text" as const,
1012
+ text: `UNAVAILABLE — reviewer failed: ${err instanceof Error ? err.message : String(err)}`,
1013
+ },
1014
+ ],
888
1015
  details: undefined,
889
1016
  };
890
1017
  }