taskplane 0.22.2 → 0.22.4

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.
@@ -154,6 +154,8 @@ interface TaskState {
154
154
  persistentReviewerKill: (() => void) | null;
155
155
  /** TP-057: Signal counter for the persistent reviewer (monotonically increasing). */
156
156
  persistentReviewerSignalNum: number;
157
+ /** Reviewer respawn counter — circuit breaker to prevent infinite respawn loops. */
158
+ reviewerRespawnCount: number;
157
159
  totalIterations: number;
158
160
  stepStatuses: Map<number, StepInfo>;
159
161
  }
@@ -172,7 +174,7 @@ function freshState(): TaskState {
172
174
  reviewerInputTokens: 0, reviewerOutputTokens: 0, reviewerCacheReadTokens: 0, reviewerCacheWriteTokens: 0,
173
175
  reviewerCostUsd: 0, reviewerContextPct: 0, reviewerProc: null, reviewerTimer: null,
174
176
  reviewCounter: 0,
175
- persistentReviewerSession: null, persistentReviewerKill: null, persistentReviewerSignalNum: 0,
177
+ persistentReviewerSession: null, persistentReviewerKill: null, persistentReviewerSignalNum: 0, reviewerRespawnCount: 0,
176
178
  totalIterations: 0, stepStatuses: new Map(),
177
179
  };
178
180
  }
@@ -2503,9 +2505,23 @@ export default function (pi: ExtensionAPI) {
2503
2505
 
2504
2506
  if (needsSpawn && state.persistentReviewerSession) {
2505
2507
  // Session was previously active but died — log fallback
2506
- console.error(`[task-runner] persistent reviewer session dead — respawning`);
2508
+ state.reviewerRespawnCount++;
2509
+ const MAX_REVIEWER_RESPAWNS = 3;
2510
+ if (state.reviewerRespawnCount > MAX_REVIEWER_RESPAWNS) {
2511
+ console.error(`[task-runner] reviewer respawn limit (${MAX_REVIEWER_RESPAWNS}) exceeded — skipping review`);
2512
+ logExecution(statusPath, `Reviewer R${num}`,
2513
+ `reviewer respawn limit exceeded (${state.reviewerRespawnCount}/${MAX_REVIEWER_RESPAWNS}) — skipping review`);
2514
+ state.persistentReviewerSession = null;
2515
+ state.persistentReviewerKill = null;
2516
+ state.persistentReviewerSignalNum = 0;
2517
+ return {
2518
+ content: [{ type: "text" as const, text: `⚠️ Reviewer respawn limit exceeded (${MAX_REVIEWER_RESPAWNS}). Review skipped — proceeding without review.` }],
2519
+ details: undefined,
2520
+ };
2521
+ }
2522
+ console.error(`[task-runner] persistent reviewer session dead — respawning (${state.reviewerRespawnCount}/${MAX_REVIEWER_RESPAWNS})`);
2507
2523
  logExecution(statusPath, `Reviewer R${num}`,
2508
- `persistent reviewer dead — respawning for ${reviewType} review`);
2524
+ `persistent reviewer dead — respawning for ${reviewType} review (${state.reviewerRespawnCount}/${MAX_REVIEWER_RESPAWNS})`);
2509
2525
  state.persistentReviewerSession = null;
2510
2526
  state.persistentReviewerKill = null;
2511
2527
  state.persistentReviewerSignalNum = 0;
@@ -2553,7 +2569,8 @@ export default function (pi: ExtensionAPI) {
2553
2569
  };
2554
2570
  } catch (err: any) {
2555
2571
  // ── Fallback: kill persistent session, try fresh spawn ──
2556
- console.error(`[task-runner] persistent reviewer error: ${err?.message || err}`);
2572
+ state.reviewerRespawnCount++;
2573
+ console.error(`[task-runner] persistent reviewer error (${state.reviewerRespawnCount}/3): ${err?.message || err}`);
2557
2574
  logExecution(statusPath, `Reviewer R${num}`,
2558
2575
  `persistent reviewer failed — falling back to fresh spawn: ${err?.message || err}`);
2559
2576
 
@@ -2565,6 +2582,17 @@ export default function (pi: ExtensionAPI) {
2565
2582
  state.persistentReviewerKill = null;
2566
2583
  state.persistentReviewerSignalNum = 0;
2567
2584
 
2585
+ // Circuit breaker — skip review if we've exhausted respawns
2586
+ if (state.reviewerRespawnCount > 3) {
2587
+ console.error(`[task-runner] reviewer respawn limit exceeded — skipping review`);
2588
+ logExecution(statusPath, `Reviewer R${num}`,
2589
+ `reviewer respawn limit exceeded — review skipped`);
2590
+ return {
2591
+ content: [{ type: "text" as const, text: `⚠️ Reviewer respawn limit exceeded. Review skipped — proceeding without review.` }],
2592
+ details: undefined,
2593
+ };
2594
+ }
2595
+
2568
2596
  // ── Fresh spawn fallback (original behavior) ────────
2569
2597
  try {
2570
2598
  const promptContent = readFileSync(requestPath, "utf-8");
@@ -2945,8 +2973,35 @@ export default function (pi: ExtensionAPI) {
2945
2973
  return;
2946
2974
  }
2947
2975
  } else {
2948
- // ── Quality Gate Disabled (default) ──────────────────────
2949
- // Unchanged behavior create .DONE immediately.
2976
+ // ── Empty completion guard ────────────────────────────────
2977
+ // Detect tasks where the worker checked off STATUS.md without
2978
+ // modifying any source files. This catches "shortcut" completions
2979
+ // where the worker concludes work is "already done" without
2980
+ // implementing anything.
2981
+ if (isOrchestratedMode()) {
2982
+ try {
2983
+ const diffResult = spawnSync("git", ["diff", "--name-only", "HEAD"], {
2984
+ cwd: task.taskFolder, encoding: "utf-8", timeout: 10_000,
2985
+ });
2986
+ const changedFiles = (diffResult.stdout || "").split("\n").filter(Boolean);
2987
+ const sourceChanges = changedFiles.filter(f =>
2988
+ !f.endsWith("STATUS.md") && !f.endsWith(".DONE") &&
2989
+ !f.includes(".reviews/") && !f.endsWith("dependencies.json")
2990
+ );
2991
+ if (sourceChanges.length === 0) {
2992
+ logExecution(statusPath, "⚠️ Empty completion",
2993
+ "Worker marked all steps complete but no source files were modified. " +
2994
+ "Only STATUS.md changes detected. This may indicate the worker shortcut " +
2995
+ "the task without implementing. .DONE will still be created, but this " +
2996
+ "should be investigated.");
2997
+ console.error(`[task-runner] WARNING: Task ${task.taskId} completed with zero source file changes`);
2998
+ }
2999
+ } catch {
3000
+ // Best effort — don't block .DONE creation on git check failure
3001
+ }
3002
+ }
3003
+
3004
+ // Create .DONE
2950
3005
  const donePath = join(task.taskFolder, ".DONE");
2951
3006
  writeFileSync(donePath, `Completed: ${new Date().toISOString()}\nTask: ${task.taskId}\n`);
2952
3007
  updateStatusField(statusPath, "Status", "✅ Complete");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "taskplane",
3
- "version": "0.22.2",
3
+ "version": "0.22.4",
4
4
  "description": "AI agent orchestration for pi — parallel task execution with checkpoint discipline",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -354,6 +354,30 @@ files and make sure their file scopes reflect that.
354
354
 
355
355
  ---
356
356
 
357
+ ## Preventing Empty Completions
358
+
359
+ Workers can shortcut tasks by observing that existing code "already satisfies"
360
+ requirements and checking off items without implementing anything. This is the
361
+ most dangerous failure mode — it produces false completions that waste the entire
362
+ pipeline.
363
+
364
+ **Defense: Make deliverables concrete and verifiable.**
365
+
366
+ | ❌ Vague (shortcuttable) | ✅ Concrete (verifiable) |
367
+ |--------------------------|------------------------|
368
+ | "Add taskPacketRepo support" | "Add `taskPacketRepo` field to `WorkspaceRoutingConfig` in types.ts" |
369
+ | "Enforce mode selection" | "Add `validateWorkspaceMode()` function in workspace.ts that throws on invalid state" |
370
+ | "Update config loading" | "Modify `loadWorkspaceConfig()` to parse and validate `taskPacketRepo` from JSON config" |
371
+ | "Add tests" | "Create `tests/packet-home-contract.test.ts` with tests for: valid config, missing field error, invariant violation" |
372
+
373
+ **Rules for task creators:**
374
+ - Every implementation step MUST name specific files to create or modify
375
+ - "Add X" means "write new code that doesn't exist yet" — if it might already exist, say "verify X exists and add tests, or implement if missing"
376
+ - Include at least one NEW test file per task — workers can't shortcut test creation
377
+ - Each step's artifacts list must include at least one source file (not just STATUS.md)
378
+
379
+ ---
380
+
357
381
  ## Key Principles
358
382
 
359
383
  - **Documentation in every task.** Without "Must Update" and "Check If Affected"
@@ -173,6 +173,15 @@ When a reviewer returns REVISE with specific feedback items:
173
173
  - Do NOT expand task scope beyond what the steps require
174
174
  - If you discover something out of scope, note it in STATUS.md Discoveries table
175
175
 
176
+ ## Completion Integrity
177
+
178
+ **Every checked checkbox MUST correspond to a real code change, test, or document edit.** You must NOT check off items by simply observing that existing code appears to satisfy them. Specifically:
179
+
180
+ - **If you believe work is already done:** You must still verify by running tests against the specific requirements AND document what you verified. Check off the item only after confirming with evidence (test output, code inspection notes in STATUS.md).
181
+ - **"No source files changed" is a red flag.** If you complete a task without modifying any source files (only STATUS.md), something is wrong. Every implementation task requires code changes. If you genuinely believe no changes are needed, log a detailed explanation in STATUS.md Discoveries and escalate — do NOT mark the task as complete.
182
+ - **A step that requires "Add X to Y" means you write the code.** Reading existing code and deciding it already satisfies the requirement is not implementation. If the existing code truly covers it, write a test that proves it, and document the finding.
183
+ - **Checking boxes without doing work is the most serious failure mode.** It wastes the entire batch pipeline (review, merge, integration) and produces a false completion that blocks dependent tasks.
184
+
176
185
  ## Review Protocol
177
186
 
178
187
  If you have access to a `review_step` tool, use it at step boundaries to spawn