taskplane 0.18.1 → 0.19.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.
@@ -1111,8 +1111,24 @@ function generateReviewRequest(
1111
1111
  }
1112
1112
 
1113
1113
  function extractVerdict(reviewContent: string): string {
1114
+ // Primary: standard format "### Verdict: APPROVE|REVISE|RETHINK"
1114
1115
  const match = reviewContent.match(/###?\s*Verdict[:\s]*(APPROVE|REVISE|RETHINK)/i);
1115
- return match ? match[1].toUpperCase() : "UNKNOWN";
1116
+ if (match) return match[1].toUpperCase();
1117
+
1118
+ // TP-068: Tolerate non-standard verdict formats from models that don't
1119
+ // follow the exact template (e.g., "Changes requested", "Needs revision").
1120
+ const lower = reviewContent.toLowerCase();
1121
+ if (/\b(changes?\s+requested|needs?\s+revision|please\s+revise|must\s+revise)\b/.test(lower)) {
1122
+ return "REVISE";
1123
+ }
1124
+ if (/\b(looks?\s+good|no\s+issues?\s+found|approved?)\b/.test(lower)) {
1125
+ return "APPROVE";
1126
+ }
1127
+ if (/\b(fundamentally\s+wrong|rethink|reconsider\s+the\s+approach)\b/.test(lower)) {
1128
+ return "RETHINK";
1129
+ }
1130
+
1131
+ return "UNKNOWN";
1116
1132
  }
1117
1133
 
1118
1134
  // ── Subagent Spawner ─────────────────────────────────────────────────
@@ -2304,8 +2320,11 @@ export default function (pi: ExtensionAPI) {
2304
2320
  // Initial prompt tells the reviewer to call wait_for_review
2305
2321
  const initialPrompt =
2306
2322
  "You are a persistent reviewer for this task. " +
2307
- "Call the `wait_for_review` tool now to receive your first review request. " +
2308
- "After writing each review, call `wait_for_review` again for the next one.";
2323
+ "Use the `wait_for_review` tool now to receive your first review request. " +
2324
+ "IMPORTANT: `wait_for_review` is a REGISTERED EXTENSION TOOL call it " +
2325
+ "the same way you call `read`, `write`, `edit`, or `grep`. " +
2326
+ "Do NOT run it via `bash` or any shell command. " +
2327
+ "After writing each review, use `wait_for_review` again for the next one.";
2309
2328
 
2310
2329
  const spawned = spawnAgentTmux({
2311
2330
  sessionName,
@@ -2375,8 +2394,14 @@ export default function (pi: ExtensionAPI) {
2375
2394
  /**
2376
2395
  * Poll for the verdict file to appear (written by the reviewer).
2377
2396
  * Same pattern as the original review_step handler.
2397
+ *
2398
+ * Early-exit detection (TP-068): If the reviewer exits within 30s
2399
+ * of spawn without producing a verdict, it likely failed to use the
2400
+ * wait_for_review tool correctly (e.g., called it via bash). This
2401
+ * triggers a faster fallback instead of waiting 30 minutes.
2378
2402
  */
2379
- async function pollForVerdict(): Promise<string> {
2403
+ async function pollForVerdict(spawnTime?: number): Promise<string> {
2404
+ const EARLY_EXIT_THRESHOLD_MS = 30_000; // 30 seconds
2380
2405
  const verdictTimeout = 30 * 60 * 1000; // 30 minutes
2381
2406
  const pollStart = Date.now();
2382
2407
  while (Date.now() - pollStart < verdictTimeout) {
@@ -2385,6 +2410,13 @@ export default function (pi: ExtensionAPI) {
2385
2410
  }
2386
2411
  // Also check if persistent reviewer died while we're waiting
2387
2412
  if (state.persistentReviewerSession && !isPersistentReviewerAlive()) {
2413
+ // TP-068: Detect early exit as tool compatibility failure
2414
+ if (spawnTime && (Date.now() - spawnTime) < EARLY_EXIT_THRESHOLD_MS) {
2415
+ throw new Error(
2416
+ "Persistent reviewer exited within 30s of spawn without producing a verdict — " +
2417
+ "wait_for_review tool may not be supported by this model (e.g., called via bash instead of as a registered tool)"
2418
+ );
2419
+ }
2388
2420
  throw new Error("Persistent reviewer session died while waiting for verdict");
2389
2421
  }
2390
2422
  await new Promise(r => setTimeout(r, 2000));
@@ -2406,7 +2438,10 @@ export default function (pi: ExtensionAPI) {
2406
2438
  state.persistentReviewerSignalNum = 0;
2407
2439
  }
2408
2440
 
2441
+ // Track spawn time for early-exit detection (TP-068)
2442
+ let spawnTime: number | undefined;
2409
2443
  if (needsSpawn) {
2444
+ spawnTime = Date.now();
2410
2445
  spawnPersistentReviewer();
2411
2446
  // Give the reviewer a moment to start and call wait_for_review
2412
2447
  await new Promise(r => setTimeout(r, 5000));
@@ -2415,8 +2450,8 @@ export default function (pi: ExtensionAPI) {
2415
2450
  // Signal the reviewer with the new request
2416
2451
  signalPersistentReviewer();
2417
2452
 
2418
- // Poll for the verdict file
2419
- const reviewContent = await pollForVerdict();
2453
+ // Poll for the verdict file (pass spawnTime for early-exit detection)
2454
+ const reviewContent = await pollForVerdict(spawnTime);
2420
2455
 
2421
2456
  // Stop the per-review timer
2422
2457
  if (state.reviewerTimer) clearInterval(state.reviewerTimer);
@@ -2570,18 +2605,27 @@ export default function (pi: ExtensionAPI) {
2570
2605
  details: undefined,
2571
2606
  };
2572
2607
  } catch (fallbackErr: any) {
2573
- // Both persistent and fallback failed
2608
+ // Both persistent and fallback failed — TP-068: clear logging
2574
2609
  clearInterval(state.reviewerTimer);
2575
2610
  clearReviewerState();
2576
2611
  state.reviewerStatus = "error";
2577
2612
  writeLaneState(state);
2578
2613
  updateWidgets();
2579
2614
 
2615
+ const skipMsg = `⚠️ Reviews skipped for Step ${stepNum} — reviewer model could not process ${reviewType} review request. Both persistent and fallback modes failed.`;
2616
+ console.error(`[task-runner] ${skipMsg}`);
2580
2617
  logExecution(statusPath, `Reviewer R${num}`,
2581
- `${reviewType} review — both persistent and fallback failed: ${fallbackErr?.message || fallbackErr}`);
2618
+ `${skipMsg} Error: ${fallbackErr?.message || fallbackErr}`);
2619
+
2620
+ // TP-068: Ensure shutdown signal is written even on double failure
2621
+ try {
2622
+ const shutdownPath = join(reviewsDir, REVIEWER_SHUTDOWN_SIGNAL);
2623
+ if (!existsSync(reviewsDir)) mkdirSync(reviewsDir, { recursive: true });
2624
+ writeFileSync(shutdownPath, "shutdown");
2625
+ } catch {}
2582
2626
 
2583
2627
  return {
2584
- content: [{ type: "text" as const, text: `UNAVAILABLE — reviewer error: ${fallbackErr?.message || fallbackErr}` }],
2628
+ content: [{ type: "text" as const, text: `UNAVAILABLE — ${skipMsg}` }],
2585
2629
  details: undefined,
2586
2630
  };
2587
2631
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "taskplane",
3
- "version": "0.18.1",
3
+ "version": "0.19.0",
4
4
  "description": "AI agent orchestration for pi — parallel task execution with checkpoint discipline",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -16,7 +16,7 @@ name: task-reviewer
16
16
  - Verdict format (APPROVE / REVISE)
17
17
  - Review file output conventions
18
18
  - Plan granularity guidance
19
- - Persistent reviewer mode (wait_for_review tool workflow)
19
+ - Persistent reviewer mode (wait_for_review registered tool workflow — NOT bash)
20
20
 
21
21
  Add project-specific review criteria below. Common examples:
22
22
  - Required test coverage thresholds
@@ -17,12 +17,17 @@ You are a **persistent reviewer** that stays alive across all review requests fo
17
17
  a task. This preserves your context — you remember what you reviewed in earlier
18
18
  steps and can reference previous findings.
19
19
 
20
- 1. Call `wait_for_review()` to receive your first review request
20
+ 1. Use the `wait_for_review` tool to receive your first review request.
21
+ IMPORTANT: `wait_for_review` is a REGISTERED EXTENSION TOOL — call it
22
+ the same way you call `read`, `write`, `edit`, or `grep`. Do NOT run it
23
+ via `bash`, `shell`, or any other command-line tool. It is NOT a shell
24
+ command.
21
25
  2. The request specifies an **output file path** — you MUST write your review there
22
26
  3. Use your tools to explore the codebase — read files, run `git diff`, check patterns
23
27
  4. **Use the `write` tool to create the output file with your review**
24
28
  5. Use the appropriate verdict: APPROVE, REVISE, or RETHINK
25
- 6. Call `wait_for_review()` again to receive the next request
29
+ 6. Use the `wait_for_review` tool again to receive the next request.
30
+ (Same rule: call it as a registered tool, never via bash.)
26
31
  7. Repeat until you receive a `SHUTDOWN` signal, then exit cleanly
27
32
 
28
33
  **Cross-step awareness:** When reviewing later steps, reference your earlier