taskplane 0.28.5 → 0.28.6

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.
@@ -132,6 +132,59 @@ function writeSegmentExpansionRequest(request: SegmentExpansionRequest): string
132
132
  return finalPath;
133
133
  }
134
134
 
135
+ /**
136
+ * TP-186 — Death-spiral guard helper for `review_step`.
137
+ *
138
+ * Inspects STATUS.md to determine whether the worker has prematurely set the
139
+ * given step's section heading to `**Status:** ✅ Complete`. The guard fires
140
+ * for `code` and `test` review types only — plan reviews fire BEFORE
141
+ * implementation, when an empty STATUS is correct.
142
+ *
143
+ * Returns `true` ONLY if the step's section explicitly carries the
144
+ * `**Status:** ✅ Complete` line. The top-of-file (task-level) `**Status:**`
145
+ * field does not trip this guard because it is not inside any `### Step N:`
146
+ * section. All-checkboxes-checked is also NOT a trigger — it is the normal
147
+ * pre-code-review state.
148
+ *
149
+ * Designed to fail-open: any I/O error or a missing step heading returns
150
+ * `false` (the review proceeds). The prompt-side Recovery Recipe is the
151
+ * primary defense; this guard is a hard backstop, not a gatekeeper.
152
+ *
153
+ * @param statusPath absolute path to the worker's STATUS.md
154
+ * @param stepNum the step number being reviewed
155
+ * @returns true iff the step is marked Complete in STATUS.md
156
+ */
157
+ export function isStepMarkedComplete(statusPath: string, stepNum: number): boolean {
158
+ let content: string;
159
+ try {
160
+ content = readFileSync(statusPath, "utf-8");
161
+ } catch {
162
+ return false;
163
+ }
164
+
165
+ const lines = content.split(/\r?\n/);
166
+ const stepHeadingRe = new RegExp(`^###\\s+Step\\s+${stepNum}\\b`);
167
+ const nextStepHeadingRe = /^###\s+Step\s+\d+\b/;
168
+
169
+ let inSection = false;
170
+ for (const line of lines) {
171
+ if (!inSection) {
172
+ if (stepHeadingRe.test(line)) inSection = true;
173
+ continue;
174
+ }
175
+ // Stop scanning at the next step heading.
176
+ if (nextStepHeadingRe.test(line)) break;
177
+ // Match a literal status line within this step's section.
178
+ // Examples that should match:
179
+ // **Status:** ✅ Complete
180
+ // **Status:** ✅ Complete (note ...)
181
+ if (/^\s*\*\*Status:\*\*\s*✅\s*Complete\b/.test(line)) {
182
+ return true;
183
+ }
184
+ }
185
+ return false;
186
+ }
187
+
135
188
  export default function (pi: ExtensionAPI) {
136
189
  pi.registerTool({
137
190
  name: "notify_supervisor",
@@ -632,6 +685,26 @@ export default function (pi: ExtensionAPI) {
632
685
  const reviewsDir = process.env.TASKPLANE_REVIEWS_DIR || join(taskFolder, ".reviews");
633
686
  if (!existsSync(reviewsDir)) mkdirSync(reviewsDir, { recursive: true });
634
687
 
688
+ // ── TP-186 death-spiral guard ─────────────────────────────────
689
+ // Refuse to spawn a code/test reviewer on a step that is already
690
+ // marked `**Status:** ✅ Complete` in STATUS.md. The worker has
691
+ // violated the Order of Operations contract; the only safe path
692
+ // is to revert STATUS first, then re-call review_step. Plan
693
+ // reviews are exempt because they fire BEFORE implementation.
694
+ if (reviewType !== "plan" && isStepMarkedComplete(statusPath, stepNum)) {
695
+ const taskIdMatch = statusPath.match(/[\\/]([A-Z]{2,}-\d+)[^\\/]*[\\/]STATUS\.md$/);
696
+ const taskId = taskIdMatch ? taskIdMatch[1] : "<TASK-ID>";
697
+ const refusal = [
698
+ `REFUSED: Step ${stepNum} is already marked \`**Status:** ✅ Complete\` in STATUS.md.`,
699
+ `Per the Order of Operations rule, code review must run BEFORE you mark a step Complete.`,
700
+ `Follow the Recovery Recipe in the worker prompt:`,
701
+ ` 1. Revert the step's Status to \`🟨 In Progress\` in STATUS.md`,
702
+ ` 2. Commit: chore(${taskId}): revert premature step-${stepNum} completion`,
703
+ ` 3. Re-call review_step(step=${stepNum}, type="${reviewType}", baseline=<sha>)`,
704
+ ].join("\n");
705
+ return { content: [{ type: "text" as const, text: refusal }], details: undefined };
706
+ }
707
+
635
708
  // Read review counter from STATUS.md
636
709
  let reviewCounter = 0;
637
710
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "taskplane",
3
- "version": "0.28.5",
3
+ "version": "0.28.6",
4
4
  "description": "AI agent orchestration for pi — parallel task execution with checkpoint discipline",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -278,9 +278,79 @@ code is already written.
278
278
  6. Commit implementation
279
279
  7. Call `review_step(step=N, type="code")` — AFTER implementation
280
280
 
281
- **WRONG sequence (violates the protocol):**
282
- 1. ~~Hydrate, implement, check off, commit, THEN call plan review~~ ❌
283
- This makes plan review pointless the work is already done.
281
+ ### ⚠️ MANDATORY: Order of Operations for steps with code review
282
+
283
+ **For any step that requires a code review (Review Level 2), the following
284
+ order is MANDATORY. Workers MUST NOT mark a step `Status: ✅ Complete` in
285
+ STATUS.md before the code review for that step has returned APPROVE.**
286
+
287
+ 1. **Implement** the step's checkbox items (write code, edit docs, etc.) —
288
+ check each box `[x]` in STATUS.md as you finish that item, but leave the
289
+ step's `**Status:**` heading set to `🟨 In Progress`.
290
+ 2. **Commit** the implementation:
291
+ `git add -A && git commit -m "feat(TASK-ID): step N implementation"`
292
+ 3. **Call** `review_step(step=N, type="code", baseline=<sha>)`.
293
+ 4. If the verdict is **REVISE**: read the review file in `.reviews/`, apply
294
+ the fixes, commit them, and call `review_step` again. Repeat until APPROVE
295
+ (max 2 code review cycles per step).
296
+ 5. If the verdict is **APPROVE**: NOW update the step's `**Status:**` heading
297
+ to `✅ Complete` in STATUS.md and commit the status update.
298
+ 6. **Move to step N+1.**
299
+
300
+ The key invariant: **`Status: ✅ Complete` is the worker's commitment that the
301
+ reviewer has signed off on the step.** It is not an in-progress marker. Setting
302
+ it before APPROVE creates a contradiction the worker cannot recover from on
303
+ its own — STATUS says done while the reviewer says revise.
304
+
305
+ Individual checkboxes (`- [x] item text`) inside the step MAY be checked while
306
+ implementation is in flight — they record per-item progress. The **step-level
307
+ `Status:` heading** (the line that reads `**Status:** ✅ Complete` in STATUS.md)
308
+ is the only field governed by this rule.
309
+
310
+ ### Recovery: "I marked the step Complete, then the reviewer returned REVISE"
311
+
312
+ If you violated the Order of Operations and set `**Status:** ✅ Complete` for
313
+ a step before the code review returned APPROVE, **you can recover without
314
+ operator intervention**. Follow this recipe exactly:
315
+
316
+ 1. **Revert STATUS.md** for the affected step:
317
+ - Change the step's `**Status:** ✅ Complete` heading back to
318
+ `**Status:** 🟨 In Progress`.
319
+ - Leave the individual `- [x]` checkboxes alone — they record real work
320
+ that was done.
321
+ - If the top-of-file `**Current Step:**` field was advanced past this
322
+ step, set it back to this step's name.
323
+ 2. **Commit** the revert with a dedicated message:
324
+ `git commit -am "chore(TASK-ID): revert premature step-N completion"`
325
+ 3. **Handle the REVISE through the normal recipe:** read the review file in
326
+ `.reviews/`, add Issues-Found items as new checkboxes inside the step
327
+ (using the standard "After a REVISE Review" flow above), commit those
328
+ hydration changes, fix the issues, commit the fixes, then call
329
+ `review_step(step=N, type="code")` again.
330
+ 4. Once the reviewer returns APPROVE, follow Order of Operations step 5 and
331
+ set `**Status:** ✅ Complete` for real.
332
+
333
+ Do NOT skip step 1. Leaving STATUS in the contradictory state (`Complete` +
334
+ an open REVISE) is the failure mode this recipe exists to undo. The engine's
335
+ `review_step` tool now refuses to run on a step already marked Complete and
336
+ will return a `REFUSED` verdict pointing back at this recipe.
337
+
338
+ ### ❌ FORBIDDEN sequences (these break the review contract)
339
+
340
+ Workers MUST NOT do any of the following:
341
+
342
+ 1. ~~Mark a step `**Status:** ✅ Complete` before its code review (Level ≥ 2)
343
+ has returned APPROVE.~~ This is the **death-spiral anti-pattern**: if
344
+ the reviewer subsequently returns REVISE, the worker enters a state
345
+ contradiction it cannot resolve and the lane is lost. If you did this
346
+ accidentally, follow the Recovery Recipe above.
347
+ 2. ~~Hydrate, implement, check off, commit, THEN call plan review~~ — this
348
+ makes plan review pointless; the work is already written.
349
+ 3. ~~Skip the code review and proceed to the next step on a Review Level ≥ 2
350
+ task~~ — the merge agent will reject the lane.
351
+
352
+ These rules sit alongside the existing "NEVER add, remove, or renumber steps"
353
+ rule from STATUS.md Hydration → Rules.
284
354
 
285
355
  **Handling verdicts:**
286
356
  - **APPROVE** → proceed (to implementation after plan review; to next step after code review)
@@ -288,6 +358,12 @@ code is already written.
288
358
  - **REVISE** → read the review file in `.reviews/` for detailed feedback,
289
359
  address the issues, commit fixes, then **call `review_step` again** for re-review.
290
360
  The same reviewer evaluates whether your fixes address its concerns.
361
+ - **REFUSED** → the engine's `review_step` guard rejected your call because the
362
+ step is already marked `**Status:** ✅ Complete` in STATUS.md while you're
363
+ trying to run a `code` or `test` review on it. This is the death-spiral
364
+ precondition. Follow the Recovery Recipe above (revert the premature status
365
+ update, commit the revert, then call `review_step` again — it will run
366
+ this time because the step is no longer marked Complete).
291
367
  - **UNAVAILABLE** → reviewer failed, proceed with caution
292
368
 
293
369
  **Example flow for a Review Level 2 task, Step 3:**