taskplane 0.29.2 → 0.30.0
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/bin/gitignore-patterns.mjs +11 -8
- package/bin/rpc-wrapper.mjs +410 -357
- package/bin/taskplane.mjs +533 -250
- package/extensions/reviewer-extension.ts +17 -11
- package/extensions/taskplane/abort.ts +50 -18
- package/extensions/taskplane/agent-bridge-extension.ts +232 -105
- package/extensions/taskplane/agent-host.ts +224 -97
- package/extensions/taskplane/cleanup.ts +71 -42
- package/extensions/taskplane/config-loader.ts +142 -58
- package/extensions/taskplane/config-schema.ts +6 -13
- package/extensions/taskplane/config.ts +10 -2
- package/extensions/taskplane/diagnostic-reports.ts +59 -47
- package/extensions/taskplane/diagnostics.ts +13 -13
- package/extensions/taskplane/discovery.ts +35 -61
- package/extensions/taskplane/engine-worker.ts +53 -46
- package/extensions/taskplane/engine.ts +1760 -602
- package/extensions/taskplane/execution.ts +426 -206
- package/extensions/taskplane/extension.ts +1073 -598
- package/extensions/taskplane/formatting.ts +136 -124
- package/extensions/taskplane/git.ts +0 -2
- package/extensions/taskplane/lane-runner.ts +542 -311
- package/extensions/taskplane/mailbox.ts +57 -49
- package/extensions/taskplane/merge.ts +662 -383
- package/extensions/taskplane/messages.ts +109 -51
- package/extensions/taskplane/migrations.ts +1 -1
- package/extensions/taskplane/path-resolver.ts +8 -9
- package/extensions/taskplane/persistence.ts +425 -262
- package/extensions/taskplane/process-registry.ts +36 -7
- package/extensions/taskplane/quality-gate.ts +107 -55
- package/extensions/taskplane/resume.ts +774 -267
- package/extensions/taskplane/sessions.ts +1 -1
- package/extensions/taskplane/settings-tui.ts +505 -164
- package/extensions/taskplane/sidecar-telemetry.ts +25 -10
- package/extensions/taskplane/supervisor.ts +477 -270
- package/extensions/taskplane/task-executor-core.ts +178 -53
- package/extensions/taskplane/types.ts +186 -108
- package/extensions/taskplane/verification.ts +27 -22
- package/extensions/taskplane/waves.ts +59 -43
- package/extensions/taskplane/workspace.ts +14 -12
- package/extensions/taskplane/worktree.ts +218 -196
- 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(
|
|
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 = (
|
|
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 {
|
|
129
|
-
|
|
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(
|
|
262
|
-
|
|
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
|
-
|
|
271
|
-
|
|
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
|
-
|
|
279
|
-
|
|
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
|
-
|
|
310
|
-
|
|
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
|
-
|
|
318
|
-
|
|
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(
|
|
359
|
-
Type.Literal("after-current"),
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
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
|
-
|
|
432
|
-
|
|
433
|
-
|
|
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 =
|
|
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 {
|
|
508
|
+
} catch {
|
|
509
|
+
/* fall through to default */
|
|
510
|
+
}
|
|
485
511
|
// Local override
|
|
486
|
-
const localPaths = [
|
|
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 {
|
|
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(
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
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 {
|
|
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(
|
|
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,
|
|
552
|
-
"--
|
|
553
|
-
"
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 +=
|
|
646
|
-
|
|
647
|
-
|
|
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 =
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
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 {
|
|
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
|
-
|
|
702
|
-
|
|
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
|
-
|
|
726
|
-
|
|
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 {
|
|
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) {
|
|
861
|
+
if (stepMatch) {
|
|
862
|
+
stepName = stepMatch[1].trim();
|
|
863
|
+
break;
|
|
864
|
+
}
|
|
784
865
|
}
|
|
785
|
-
} catch {
|
|
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
|
|
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(
|
|
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(
|
|
935
|
+
const updated = status.replace(
|
|
936
|
+
/\*\*Review Counter:\*\*\s*\d+/,
|
|
937
|
+
`**Review Counter:** ${reviewCounter}`,
|
|
938
|
+
);
|
|
844
939
|
writeFileSync(statusPath, updated);
|
|
845
|
-
} catch {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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: [
|
|
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
|
}
|