@ilya-lesikov/pi-pi 0.11.0 → 0.12.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.
Files changed (57) hide show
  1. package/3p/pi-subagents/src/agent-manager.ts +1 -0
  2. package/3p/pi-subagents/src/index.ts +1 -0
  3. package/3p/pi-subagents/src/types.ts +8 -0
  4. package/extensions/orchestrator/agents/advisor.ts +2 -1
  5. package/extensions/orchestrator/agents/code-reviewer.ts +1 -0
  6. package/extensions/orchestrator/agents/constraints.test.ts +43 -10
  7. package/extensions/orchestrator/agents/constraints.ts +26 -5
  8. package/extensions/orchestrator/agents/deep-debugger.ts +7 -6
  9. package/extensions/orchestrator/agents/explore.ts +1 -1
  10. package/extensions/orchestrator/agents/librarian.ts +1 -1
  11. package/extensions/orchestrator/agents/planner.ts +1 -1
  12. package/extensions/orchestrator/agents/prompts.test.ts +135 -1
  13. package/extensions/orchestrator/agents/reviewer.ts +2 -2
  14. package/extensions/orchestrator/agents/task.ts +6 -2
  15. package/extensions/orchestrator/agents/tool-routing.ts +22 -6
  16. package/extensions/orchestrator/assumptions.test.ts +77 -0
  17. package/extensions/orchestrator/assumptions.ts +85 -0
  18. package/extensions/orchestrator/cbm.ts +4 -1
  19. package/extensions/orchestrator/cbm.which.test.ts +92 -0
  20. package/extensions/orchestrator/command-handlers.ts +9 -1
  21. package/extensions/orchestrator/config.test.ts +1 -3
  22. package/extensions/orchestrator/config.ts +3 -4
  23. package/extensions/orchestrator/context.test.ts +2 -7
  24. package/extensions/orchestrator/context.ts +3 -3
  25. package/extensions/orchestrator/doctor.test.ts +35 -0
  26. package/extensions/orchestrator/doctor.ts +4 -2
  27. package/extensions/orchestrator/event-handlers.more.test.ts +185 -0
  28. package/extensions/orchestrator/event-handlers.test.ts +22 -2
  29. package/extensions/orchestrator/event-handlers.ts +156 -18
  30. package/extensions/orchestrator/flant-infra.test.ts +0 -3
  31. package/extensions/orchestrator/flant-infra.ts +0 -1
  32. package/extensions/orchestrator/integration.test.ts +355 -173
  33. package/extensions/orchestrator/orchestrator.ts +3 -9
  34. package/extensions/orchestrator/phases/brainstorm.test.ts +25 -7
  35. package/extensions/orchestrator/phases/brainstorm.ts +35 -71
  36. package/extensions/orchestrator/phases/implementation.test.ts +22 -0
  37. package/extensions/orchestrator/phases/implementation.ts +6 -0
  38. package/extensions/orchestrator/phases/machine.test.ts +0 -28
  39. package/extensions/orchestrator/phases/machine.ts +0 -38
  40. package/extensions/orchestrator/phases/planning.test.ts +27 -1
  41. package/extensions/orchestrator/phases/planning.ts +1 -1
  42. package/extensions/orchestrator/phases/review-task.test.ts +7 -0
  43. package/extensions/orchestrator/phases/review-task.ts +1 -1
  44. package/extensions/orchestrator/phases/review.test.ts +47 -10
  45. package/extensions/orchestrator/phases/review.ts +36 -25
  46. package/extensions/orchestrator/pp-menu.from.test.ts +65 -0
  47. package/extensions/orchestrator/pp-menu.leaves.test.ts +17 -0
  48. package/extensions/orchestrator/pp-menu.test.ts +106 -49
  49. package/extensions/orchestrator/pp-menu.ts +279 -133
  50. package/extensions/orchestrator/pp-state-tools.ts +1 -1
  51. package/extensions/orchestrator/rate-limit-fallback-flow.test.ts +2 -2
  52. package/extensions/orchestrator/rate-limit-fallback.ts +1 -1
  53. package/extensions/orchestrator/state.test.ts +17 -17
  54. package/extensions/orchestrator/state.ts +35 -12
  55. package/package.json +2 -2
  56. package/scripts/postinstall.mjs +93 -0
  57. package/scripts/postinstall.sh +0 -18
@@ -331,6 +331,16 @@ function getTool(pi: ReturnType<typeof makePi>, name: string): any {
331
331
  return tool;
332
332
  }
333
333
 
334
+ // The pp_phase_complete reconcile gate returns a one-time "reconcile your state
335
+ // files" directive on the FIRST completion call of each phase; the real
336
+ // advancement/spawn happens on the acknowledging re-call. Tests that assert
337
+ // post-completion behavior go through this helper so the gate is observable.
338
+ async function completePhase(tool: any, callId: string, summary: string, ctx: any) {
339
+ const first = await tool.execute(`${callId}-reconcile`, { summary }, undefined, undefined, ctx);
340
+ expect(first.content[0].text).toContain("reconcile the task's state files");
341
+ return tool.execute(callId, { summary }, undefined, undefined, ctx);
342
+ }
343
+
334
344
  function emitSubagentCreated(pi: ReturnType<typeof makePi>, id: string, description: string) {
335
345
  pi.events.emit("subagents:created", { id, description });
336
346
  }
@@ -368,7 +378,7 @@ async function moveTaskToImplementPhase(
368
378
 
369
379
  expectBrainstormToPlan(menu);
370
380
  const ppPhaseComplete = getTool(pi, "pp_phase_complete");
371
- await ppPhaseComplete.execute(firstCallId, { summary: "phase complete" }, undefined, undefined, ctx);
381
+ await completePhase(ppPhaseComplete, firstCallId, "phase complete", ctx);
372
382
  await new Promise((r) => setTimeout(r, 10));
373
383
 
374
384
  const plansDir = join(taskDir, "plans");
@@ -388,7 +398,7 @@ async function moveTaskToImplementPhase(
388
398
  );
389
399
 
390
400
  expectPlanToImplement(menu);
391
- await ppPhaseComplete.execute(secondCallId, { summary: "plan complete" }, undefined, undefined, ctx);
401
+ await completePhase(ppPhaseComplete, secondCallId, "plan complete", ctx);
392
402
  await new Promise((r) => setTimeout(r, 10));
393
403
  }
394
404
 
@@ -412,7 +422,7 @@ describe("implement pipeline: brainstorm → plan → implement → done", () =>
412
422
  expectBrainstormToPlan(menu);
413
423
 
414
424
  const ppPhaseComplete = getTool(pi, "pp_phase_complete");
415
- const result1 = await ppPhaseComplete.execute("call-1", { summary: "Research complete" }, undefined, undefined, ctx);
425
+ const result1 = await completePhase(ppPhaseComplete, "call-1", "Research complete", ctx);
416
426
  expect(result1.content[0].text).toBeDefined();
417
427
 
418
428
  await new Promise((r) => setTimeout(r, 10));
@@ -447,7 +457,7 @@ describe("implement pipeline: brainstorm → plan → implement → done", () =>
447
457
 
448
458
  expectPlanToImplement(menu);
449
459
 
450
- const result2 = await ppPhaseComplete.execute("call-2", { summary: "Plan synthesized" }, undefined, undefined, ctx);
460
+ const result2 = await completePhase(ppPhaseComplete, "call-2", "Plan synthesized", ctx);
451
461
  expect(result2.content[0]).toBeDefined();
452
462
 
453
463
  await new Promise((r) => setTimeout(r, 10));
@@ -460,7 +470,7 @@ describe("implement pipeline: brainstorm → plan → implement → done", () =>
460
470
 
461
471
  expectImplementToDone(menu);
462
472
 
463
- const result3 = await ppPhaseComplete.execute("call-3", { summary: "All items implemented" }, undefined, undefined, ctx);
473
+ const result3 = await completePhase(ppPhaseComplete, "call-3", "All items implemented", ctx);
464
474
  expect(result3.content[0]).toBeDefined();
465
475
 
466
476
  expect(orchestrator.active).toBeNull();
@@ -475,6 +485,7 @@ describe("implement pipeline: brainstorm → plan → implement → done", () =>
475
485
  const ctx = makeCtx();
476
486
 
477
487
  await orchestrator.startTask(ctx as any, "implement", "Test task");
488
+ orchestrator.active!.state.reconciledPhase = orchestrator.active!.state.phase;
478
489
 
479
490
  expectBrainstormToPlan(menu);
480
491
  const ppPhaseComplete = getTool(pi, "pp_phase_complete");
@@ -497,7 +508,7 @@ describe("implement pipeline: brainstorm → plan → implement → done", () =>
497
508
 
498
509
  expectBrainstormToPlan(menu);
499
510
  const ppPhaseComplete = getTool(pi, "pp_phase_complete");
500
- await ppPhaseComplete.execute("call-1", { summary: "done" }, undefined, undefined, ctx);
511
+ await completePhase(ppPhaseComplete, "call-1", "done", ctx);
501
512
  await new Promise((r) => setTimeout(r, 10));
502
513
 
503
514
  emitSubagentCreated(pi, "planner-1", "Planner (test)");
@@ -517,7 +528,7 @@ describe("implement pipeline: brainstorm → plan → implement → done", () =>
517
528
  );
518
529
 
519
530
  expectPlanToImplement(menu);
520
- await ppPhaseComplete.execute("call-2", { summary: "plan done" }, undefined, undefined, ctx);
531
+ await completePhase(ppPhaseComplete, "call-2", "plan done", ctx);
521
532
  await new Promise((r) => setTimeout(r, 10));
522
533
 
523
534
  expect(orchestrator.active!.state.phase).toBe("implement");
@@ -545,7 +556,7 @@ describe("review cycle lifecycle", () => {
545
556
 
546
557
  expectBrainstormToPlan(menu);
547
558
  const ppPhaseComplete = getTool(pi, "pp_phase_complete");
548
- await ppPhaseComplete.execute("call-1", { summary: "done" }, undefined, undefined, ctx);
559
+ await completePhase(ppPhaseComplete, "call-1", "done", ctx);
549
560
  await new Promise((r) => setTimeout(r, 10));
550
561
 
551
562
  emitSubagentCreated(pi, "planner-1", "Planner (test)");
@@ -565,13 +576,13 @@ describe("review cycle lifecycle", () => {
565
576
  );
566
577
 
567
578
  expectPlanToImplement(menu);
568
- await ppPhaseComplete.execute("call-2", { summary: "plan done" }, undefined, undefined, ctx);
579
+ await completePhase(ppPhaseComplete, "call-2", "plan done", ctx);
569
580
  await new Promise((r) => setTimeout(r, 10));
570
581
 
571
582
  expect(orchestrator.active!.state.phase).toBe("implement");
572
583
 
573
584
  expectReviewAuto(menu);
574
- const result = await ppPhaseComplete.execute("call-3", { summary: "implemented" }, undefined, undefined, ctx);
585
+ const result = await completePhase(ppPhaseComplete, "call-3", "implemented", ctx);
575
586
  expect(result.content[0].text).toContain("Waiting for reviewers");
576
587
 
577
588
  expect(orchestrator.active!.state.reviewCycle).not.toBeNull();
@@ -612,7 +623,7 @@ describe("review cycle lifecycle", () => {
612
623
  writeFileSync(join(taskDir, "RESEARCH.md"), VALID_RESEARCH, "utf-8");
613
624
  expectBrainstormToPlan(menu);
614
625
  const ppPhaseComplete = getTool(pi, "pp_phase_complete");
615
- await ppPhaseComplete.execute("call-1", { summary: "done" }, undefined, undefined, ctx);
626
+ await completePhase(ppPhaseComplete, "call-1", "done", ctx);
616
627
  await new Promise((r) => setTimeout(r, 10));
617
628
 
618
629
  emitSubagentCreated(pi, "planner-1", "Planner (test)");
@@ -631,11 +642,11 @@ describe("review cycle lifecycle", () => {
631
642
  "utf-8",
632
643
  );
633
644
  expectPlanToImplement(menu);
634
- await ppPhaseComplete.execute("call-2", { summary: "plan done" }, undefined, undefined, ctx);
645
+ await completePhase(ppPhaseComplete, "call-2", "plan done", ctx);
635
646
  await new Promise((r) => setTimeout(r, 10));
636
647
 
637
648
  expectReviewAuto(menu);
638
- await ppPhaseComplete.execute("call-3", { summary: "implemented" }, undefined, undefined, ctx);
649
+ await completePhase(ppPhaseComplete, "call-3", "implemented", ctx);
639
650
 
640
651
  emitSubagentCreated(pi, "reviewer-1", "Code reviewer (test)");
641
652
  emitSubagentFailed(pi, "reviewer-1", "model error");
@@ -674,7 +685,7 @@ describe("review cycle lifecycle", () => {
674
685
  writeFileSync(join(taskDir, "RESEARCH.md"), VALID_RESEARCH, "utf-8");
675
686
  expectBrainstormToPlan(menu);
676
687
  const ppPhaseComplete = getTool(pi, "pp_phase_complete");
677
- await ppPhaseComplete.execute("call-1", { summary: "done" }, undefined, undefined, ctx);
688
+ await completePhase(ppPhaseComplete, "call-1", "done", ctx);
678
689
  await new Promise((r) => setTimeout(r, 10));
679
690
 
680
691
  emitSubagentCreated(pi, "planner-1", "Planner (test)");
@@ -693,7 +704,7 @@ describe("review cycle lifecycle", () => {
693
704
  "utf-8",
694
705
  );
695
706
  expectPlanToImplement(menu);
696
- await ppPhaseComplete.execute("call-2", { summary: "plan done" }, undefined, undefined, ctx);
707
+ await completePhase(ppPhaseComplete, "call-2", "plan done", ctx);
697
708
  await new Promise((r) => setTimeout(r, 10));
698
709
 
699
710
  // Pick the auto preset first: with zero enabled reviewers it notifies and
@@ -702,11 +713,85 @@ describe("review cycle lifecycle", () => {
702
713
  expectReviewAuto(menu);
703
714
  menu.expect({ question: "Review", options: { include: ["Review on my own"] }, choose: "Review on my own" });
704
715
  menu.expect({ question: "Editor review", options: { include: ["Skip markers"] }, choose: "Skip markers" });
705
- const result = await ppPhaseComplete.execute("call-3", { summary: "implemented" }, undefined, undefined, ctx);
716
+ const result = await completePhase(ppPhaseComplete, "call-3", "implemented", ctx);
706
717
 
707
718
  expect(result.content[0].text).toContain("continue");
708
719
  expect(orchestrator.active!.state.reviewCycle).toBeNull();
709
720
  });
721
+
722
+ it("manual /pp Review on an un-reconciled phase warns and cancels without spawning", async () => {
723
+ const cwd = makeTempDir();
724
+ const { pi, orchestrator } = await setupOrchestrator(cwd);
725
+ const ctx = makeCtx();
726
+
727
+ await orchestrator.startTask(ctx as any, "implement", "stale review cancel");
728
+ const taskDir = orchestrator.active!.dir;
729
+ orchestrator.active!.state.phase = "implement";
730
+ orchestrator.active!.state.step = "llm_work";
731
+ // No reconciledPhase stamp → the phase is stale; the manual Review path must warn.
732
+ saveTask(taskDir, orchestrator.active!.state);
733
+ const plansDir = join(taskDir, "plans");
734
+ mkdirSync(plansDir, { recursive: true });
735
+ writeFileSync(join(plansDir, `${Math.floor(Date.now() / 1000)}_synthesized.md`), makeValidPlan(["- [x] P1 — Done when: done"]), "utf-8");
736
+
737
+ expectReviewAuto(menu);
738
+ menu.expect({ question: /may not include the agent's latest findings/, options: { include: ["Review anyway", "Cancel"] }, choose: "Cancel" });
739
+ menu.expect({ question: "Review", options: { include: [m.autoReview] }, choose: "Back" });
740
+ menu.expect({ question: m.anyTaskMenu, options: { include: ["Back to prompt"] }, choose: "Back to prompt" });
741
+
742
+ const pp = getCommand(pi, "pp");
743
+ await pp(undefined, ctx);
744
+
745
+ expect(orchestrator.active!.state.reviewCycle).toBeNull();
746
+ expect(orchestrator.active!.state.step).toBe("llm_work");
747
+ });
748
+
749
+ it("manual /pp Review 'Review anyway' proceeds and keeps reconcilePending for the next agent turn", async () => {
750
+ const cwd = makeTempDir();
751
+ const { pi, orchestrator } = await setupOrchestrator(cwd);
752
+ const ctx = makeCtx();
753
+
754
+ await orchestrator.startTask(ctx as any, "implement", "stale review proceed");
755
+ orchestrator.config = {
756
+ ...orchestrator.config,
757
+ agents: {
758
+ ...orchestrator.config.agents,
759
+ subagents: {
760
+ ...orchestrator.config.agents.subagents,
761
+ presetGroups: {
762
+ ...orchestrator.config.agents.subagents.presetGroups,
763
+ codeReviewers: {
764
+ ...orchestrator.config.agents.subagents.presetGroups.codeReviewers,
765
+ presets: {
766
+ ...orchestrator.config.agents.subagents.presetGroups.codeReviewers.presets,
767
+ regular: { enabled: true, agents: {} },
768
+ },
769
+ },
770
+ },
771
+ },
772
+ },
773
+ } as any;
774
+ const taskDir = orchestrator.active!.dir;
775
+ orchestrator.active!.state.phase = "implement";
776
+ orchestrator.active!.state.step = "llm_work";
777
+ saveTask(taskDir, orchestrator.active!.state);
778
+ const plansDir = join(taskDir, "plans");
779
+ mkdirSync(plansDir, { recursive: true });
780
+ writeFileSync(join(plansDir, `${Math.floor(Date.now() / 1000)}_synthesized.md`), makeValidPlan(["- [x] P1 — Done when: done"]), "utf-8");
781
+
782
+ // Zero configured agents under the enabled preset → after confirming, the flow
783
+ // notifies "no reviewers" and loops back, letting us assert the confirm's effect
784
+ // (reconcilePending stays set) without a real reviewer spawn.
785
+ expectReviewAuto(menu);
786
+ menu.expect({ question: /may not include the agent's latest findings/, options: { include: ["Review anyway"] }, choose: "Review anyway" });
787
+ menu.expect({ question: "Review", options: { include: [m.autoReview] }, choose: "Back" });
788
+ menu.expect({ question: m.anyTaskMenu, options: { include: ["Back to prompt"] }, choose: "Back to prompt" });
789
+
790
+ const pp = getCommand(pi, "pp");
791
+ await pp(undefined, ctx);
792
+
793
+ expect(loadTask(taskDir).reconcilePending).toBe(true);
794
+ });
710
795
  });
711
796
 
712
797
  describe("subagent instrumentation", () => {
@@ -802,7 +887,7 @@ describe("standalone brainstorm", () => {
802
887
  expectImplementToDone(menu);
803
888
 
804
889
  const ppPhaseComplete = getTool(pi, "pp_phase_complete");
805
- const result = await ppPhaseComplete.execute("call-1", { summary: "Explored ideas" }, undefined, undefined, ctx);
890
+ const result = await completePhase(ppPhaseComplete, "call-1", "Explored ideas", ctx);
806
891
  expect(result.content[0].text).toBe("");
807
892
 
808
893
  expect(orchestrator.active).toBeNull();
@@ -826,7 +911,7 @@ describe("standalone brainstorm", () => {
826
911
  .expect({ question: "Planner preset", options: { include: [m.preset("regular"), "Back"] }, choose: m.preset("regular") });
827
912
 
828
913
  const ppPhaseComplete = getTool(pi, "pp_phase_complete");
829
- await ppPhaseComplete.execute("call-1", { summary: "Conclusions ready" }, undefined, undefined, ctx);
914
+ await completePhase(ppPhaseComplete, "call-1", "Conclusions ready", ctx);
830
915
  await new Promise((r) => setTimeout(r, 10));
831
916
 
832
917
  expect(orchestrator.active!.type).toBe("brainstorm");
@@ -835,33 +920,6 @@ describe("standalone brainstorm", () => {
835
920
  });
836
921
  });
837
922
 
838
- describe("debug flow", () => {
839
- it("finishes debug and can start implementation", async () => {
840
- const cwd = makeTempDir();
841
- const { pi, orchestrator } = await setupOrchestrator(cwd);
842
- const ctx = makeCtx();
843
-
844
- await orchestrator.startTask(ctx as any, "debug", "Fix timeout bug");
845
-
846
- expect(orchestrator.active!.state.phase).toBe("debug");
847
- expect(orchestrator.active!.type).toBe("debug");
848
-
849
- const taskDir = orchestrator.active!.dir;
850
- writeFileSync(join(taskDir, "USER_REQUEST.md"), VALID_USER_REQUEST, "utf-8");
851
- writeFileSync(join(taskDir, "RESEARCH.md"), VALID_RESEARCH, "utf-8");
852
-
853
- expectBrainstormToPlan(menu);
854
-
855
- const ppPhaseComplete = getTool(pi, "pp_phase_complete");
856
- await ppPhaseComplete.execute("call-1", { summary: "Diagnosis complete" }, undefined, undefined, ctx);
857
- await new Promise((r) => setTimeout(r, 10));
858
-
859
- expect(orchestrator.active!.type).toBe("debug");
860
- expect(orchestrator.active!.state.phase).toBe("plan");
861
- expect(orchestrator.active!.state.step).toBe("await_planners");
862
- });
863
- });
864
-
865
923
  describe("planner completion tracking", () => {
866
924
  it("transitions await_planners → synthesize when all planners complete", async () => {
867
925
  const cwd = makeTempDir();
@@ -876,7 +934,7 @@ describe("planner completion tracking", () => {
876
934
 
877
935
  expectBrainstormToPlan(menu);
878
936
  const ppPhaseComplete = getTool(pi, "pp_phase_complete");
879
- await ppPhaseComplete.execute("call-1", { summary: "done" }, undefined, undefined, ctx);
937
+ await completePhase(ppPhaseComplete, "call-1", "done", ctx);
880
938
  await new Promise((r) => setTimeout(r, 10));
881
939
 
882
940
  expect(orchestrator.active!.state.phase).toBe("plan");
@@ -910,7 +968,7 @@ describe("planner completion tracking", () => {
910
968
 
911
969
  expectBrainstormToPlan(menu);
912
970
  const ppPhaseComplete = getTool(pi, "pp_phase_complete");
913
- await ppPhaseComplete.execute("call-1", { summary: "done" }, undefined, undefined, ctx);
971
+ await completePhase(ppPhaseComplete, "call-1", "done", ctx);
914
972
  await new Promise((r) => setTimeout(r, 10));
915
973
 
916
974
  emitSubagentCreated(pi, "planner-1", "Planner (test)");
@@ -957,7 +1015,7 @@ describe("planner completion tracking", () => {
957
1015
 
958
1016
  expectBrainstormToPlan(menu);
959
1017
  const ppPhaseComplete = getTool(pi, "pp_phase_complete");
960
- await ppPhaseComplete.execute("call-1", { summary: "done" }, undefined, undefined, ctx);
1018
+ await completePhase(ppPhaseComplete, "call-1", "done", ctx);
961
1019
 
962
1020
  expect(orchestrator.active!.state.phase).toBe("plan");
963
1021
  expect(orchestrator.active!.state.step).toBe("await_planners");
@@ -985,7 +1043,7 @@ describe("planner completion tracking", () => {
985
1043
 
986
1044
  expectBrainstormToPlan(menu);
987
1045
  const ppPhaseComplete = getTool(pi, "pp_phase_complete");
988
- await ppPhaseComplete.execute("call-1", { summary: "done" }, undefined, undefined, ctx);
1046
+ await completePhase(ppPhaseComplete, "call-1", "done", ctx);
989
1047
  await new Promise((r) => setTimeout(r, 10));
990
1048
 
991
1049
  const plansDir = join(taskDir, "plans");
@@ -1060,12 +1118,62 @@ describe("pp:done cancellation", () => {
1060
1118
 
1061
1119
  expectImplementToDone(menu);
1062
1120
  const ppPhaseComplete = getTool(pi, "pp_phase_complete");
1063
- await ppPhaseComplete.execute("call-1", { summary: "done" }, undefined, undefined, ctx);
1121
+ await completePhase(ppPhaseComplete, "call-1", "done", ctx);
1064
1122
 
1065
1123
  expect(orchestrator.active).toBeNull();
1066
1124
  const state = loadTask(taskDir);
1067
1125
  expect(state.phase).toBe("done");
1068
1126
  });
1127
+
1128
+ it("autonomous task reaching done emits a terminal assumptions summary", async () => {
1129
+ const cwd = makeTempDir();
1130
+ const { orchestrator } = await setupOrchestrator(cwd);
1131
+ const ctx = makeCtx();
1132
+
1133
+ await orchestrator.startTask(ctx as any, "implement", "terminal assumptions", undefined, undefined, "autonomous");
1134
+ const taskDir = orchestrator.active!.dir;
1135
+ orchestrator.active!.state.phase = "implement";
1136
+ orchestrator.active!.state.reconciledPhase = "implement";
1137
+ saveTask(taskDir, orchestrator.active!.state);
1138
+ const plansDir = join(taskDir, "plans");
1139
+ mkdirSync(plansDir, { recursive: true });
1140
+ writeFileSync(join(plansDir, `${Math.floor(Date.now() / 1000)}_synthesized.md`), makeValidPlan(["- [x] Done — Done when: shipped"]), "utf-8");
1141
+ mkdirSync(join(taskDir, "artifacts"), { recursive: true });
1142
+ writeFileSync(
1143
+ join(taskDir, "artifacts", "ASSUMPTIONS.md"),
1144
+ "# Assumptions\n\n- statement: build is hermetic; confidence: med; status: open\n",
1145
+ "utf-8",
1146
+ );
1147
+
1148
+ await orchestrator.transitionToNextPhase(ctx as any);
1149
+
1150
+ const summaryCall = (ctx.ui.notify as any).mock.calls.find((c: any[]) => String(c[0]).includes("Assumptions recorded this run"));
1151
+ expect(summaryCall).toBeTruthy();
1152
+ expect(String(summaryCall[0])).toContain("build is hermetic");
1153
+ expect(loadTask(taskDir).phase).toBe("done");
1154
+ });
1155
+
1156
+ it("guided task reaching done does NOT emit a terminal assumptions summary", async () => {
1157
+ const cwd = makeTempDir();
1158
+ const { orchestrator } = await setupOrchestrator(cwd);
1159
+ const ctx = makeCtx();
1160
+
1161
+ await orchestrator.startTask(ctx as any, "implement", "no terminal assumptions");
1162
+ const taskDir = orchestrator.active!.dir;
1163
+ orchestrator.active!.state.phase = "implement";
1164
+ orchestrator.active!.state.reconciledPhase = "implement";
1165
+ saveTask(taskDir, orchestrator.active!.state);
1166
+ const plansDir = join(taskDir, "plans");
1167
+ mkdirSync(plansDir, { recursive: true });
1168
+ writeFileSync(join(plansDir, `${Math.floor(Date.now() / 1000)}_synthesized.md`), makeValidPlan(["- [x] Done — Done when: shipped"]), "utf-8");
1169
+ mkdirSync(join(taskDir, "artifacts"), { recursive: true });
1170
+ writeFileSync(join(taskDir, "artifacts", "ASSUMPTIONS.md"), "# A\n\n- statement: x; confidence: low; status: open\n", "utf-8");
1171
+
1172
+ await orchestrator.transitionToNextPhase(ctx as any);
1173
+
1174
+ const summaryCall = (ctx.ui.notify as any).mock.calls.find((c: any[]) => String(c[0]).includes("Assumptions recorded this run"));
1175
+ expect(summaryCall).toBeFalsy();
1176
+ });
1069
1177
  });
1070
1178
 
1071
1179
  describe("edge cases and regressions", () => {
@@ -1220,7 +1328,7 @@ describe("edge cases and regressions", () => {
1220
1328
  writeFileSync(join(taskDir, "RESEARCH.md"), VALID_RESEARCH, "utf-8");
1221
1329
  expectBrainstormToPlan(menu);
1222
1330
  const ppPhaseComplete = getTool(pi, "pp_phase_complete");
1223
- await ppPhaseComplete.execute("call-1", { summary: "done" }, undefined, undefined, ctx);
1331
+ await completePhase(ppPhaseComplete, "call-1", "done", ctx);
1224
1332
  await new Promise((r) => setTimeout(r, 10));
1225
1333
 
1226
1334
  emitSubagentCreated(pi, "planner-1", "Planner (test)");
@@ -1239,11 +1347,11 @@ describe("edge cases and regressions", () => {
1239
1347
  "utf-8",
1240
1348
  );
1241
1349
  expectPlanToImplement(menu);
1242
- await ppPhaseComplete.execute("call-2", { summary: "plan done" }, undefined, undefined, ctx);
1350
+ await completePhase(ppPhaseComplete, "call-2", "plan done", ctx);
1243
1351
  await new Promise((r) => setTimeout(r, 10));
1244
1352
 
1245
1353
  expectReviewAuto(menu);
1246
- await ppPhaseComplete.execute("call-3", { summary: "implemented" }, undefined, undefined, ctx);
1354
+ await completePhase(ppPhaseComplete, "call-3", "implemented", ctx);
1247
1355
 
1248
1356
  const reviewsDir = join(taskDir, "code-reviews");
1249
1357
  mkdirSync(reviewsDir, { recursive: true });
@@ -1274,7 +1382,7 @@ describe("edge cases and regressions", () => {
1274
1382
  writeFileSync(join(taskDir, "RESEARCH.md"), VALID_RESEARCH, "utf-8");
1275
1383
  expectBrainstormToPlan(menu);
1276
1384
  const ppPhaseComplete = getTool(pi, "pp_phase_complete");
1277
- await ppPhaseComplete.execute("call-1", { summary: "done" }, undefined, undefined, ctx);
1385
+ await completePhase(ppPhaseComplete, "call-1", "done", ctx);
1278
1386
  await new Promise((r) => setTimeout(r, 10));
1279
1387
 
1280
1388
  emitSubagentCreated(pi, "planner-1", "Planner (test)");
@@ -1293,13 +1401,13 @@ describe("edge cases and regressions", () => {
1293
1401
  "utf-8",
1294
1402
  );
1295
1403
  expectPlanToImplement(menu);
1296
- await ppPhaseComplete.execute("call-2", { summary: "plan done" }, undefined, undefined, ctx);
1404
+ await completePhase(ppPhaseComplete, "call-2", "plan done", ctx);
1297
1405
  await new Promise((r) => setTimeout(r, 10));
1298
1406
 
1299
1407
  expect(orchestrator.active!.state.phase).toBe("implement");
1300
1408
 
1301
1409
  expectReviewAuto(menu);
1302
- await ppPhaseComplete.execute("call-3", { summary: "implemented" }, undefined, undefined, ctx);
1410
+ await completePhase(ppPhaseComplete, "call-3", "implemented", ctx);
1303
1411
 
1304
1412
  const reviewsDir = join(taskDir, "code-reviews");
1305
1413
  mkdirSync(reviewsDir, { recursive: true });
@@ -1355,6 +1463,7 @@ describe("edge cases and regressions", () => {
1355
1463
  orchestrator.active!.state.step = "llm_work";
1356
1464
  // Arm the item-5 flag (advanceOnComplete=false): stop in-phase on approve.
1357
1465
  orchestrator.active!.state.manualAutoReview = { phase: "implement", preset: "regular", maxPasses: 3, advanceOnComplete: false };
1466
+ orchestrator.active!.state.reconciledPhase = "implement";
1358
1467
  saveTask(taskDir, orchestrator.active!.state);
1359
1468
 
1360
1469
  const ppPhaseComplete = getTool(pi, "pp_phase_complete");
@@ -1386,7 +1495,7 @@ describe("edge cases and regressions", () => {
1386
1495
 
1387
1496
  expectReviewOnMyOwn(menu, "Skip markers");
1388
1497
  const ppPhaseComplete = getTool(pi, "pp_phase_complete");
1389
- const result = await ppPhaseComplete.execute("call-1", { summary: "not done yet" }, undefined, undefined, ctx);
1498
+ const result = await completePhase(ppPhaseComplete, "call-1", "not done yet", ctx);
1390
1499
 
1391
1500
  expect(result.content[0].text).toContain("continue");
1392
1501
  expect(orchestrator.active!.state.phase).toBe("brainstorm");
@@ -1405,7 +1514,7 @@ describe("edge cases and regressions", () => {
1405
1514
  writeFileSync(join(taskDir, "RESEARCH.md"), VALID_RESEARCH, "utf-8");
1406
1515
  expectBrainstormToPlan(menu);
1407
1516
  const ppPhaseComplete = getTool(pi, "pp_phase_complete");
1408
- await ppPhaseComplete.execute("call-1", { summary: "done" }, undefined, undefined, ctx);
1517
+ await completePhase(ppPhaseComplete, "call-1", "done", ctx);
1409
1518
  await new Promise((r) => setTimeout(r, 10));
1410
1519
 
1411
1520
  emitSubagentCreated(pi, "planner-1", "Planner (test)");
@@ -1424,11 +1533,11 @@ describe("edge cases and regressions", () => {
1424
1533
  "utf-8",
1425
1534
  );
1426
1535
  expectPlanToImplement(menu);
1427
- await ppPhaseComplete.execute("call-2", { summary: "plan done" }, undefined, undefined, ctx);
1536
+ await completePhase(ppPhaseComplete, "call-2", "plan done", ctx);
1428
1537
  await new Promise((r) => setTimeout(r, 10));
1429
1538
 
1430
1539
  expectReviewOnMyOwn(menu, "Skip markers");
1431
- const result = await ppPhaseComplete.execute("call-3", { summary: "partial work" }, undefined, undefined, ctx);
1540
+ const result = await completePhase(ppPhaseComplete, "call-3", "partial work", ctx);
1432
1541
 
1433
1542
  expect(result.content[0].text).toContain("continue");
1434
1543
  expect(orchestrator.active!.state.phase).toBe("implement");
@@ -1447,7 +1556,7 @@ describe("edge cases and regressions", () => {
1447
1556
  writeFileSync(join(taskDir, "RESEARCH.md"), VALID_RESEARCH, "utf-8");
1448
1557
  expectBrainstormToPlan(menu);
1449
1558
  const ppPhaseComplete = getTool(pi, "pp_phase_complete");
1450
- await ppPhaseComplete.execute("call-1", { summary: "done" }, undefined, undefined, ctx);
1559
+ await completePhase(ppPhaseComplete, "call-1", "done", ctx);
1451
1560
  await new Promise((r) => setTimeout(r, 10));
1452
1561
 
1453
1562
  emitSubagentCreated(pi, "planner-1", "Planner (test)");
@@ -1467,7 +1576,7 @@ describe("edge cases and regressions", () => {
1467
1576
  );
1468
1577
 
1469
1578
  expectReviewOnMyOwn(menu, "Skip markers");
1470
- const result = await ppPhaseComplete.execute("call-2", { summary: "plan ready" }, undefined, undefined, ctx);
1579
+ const result = await completePhase(ppPhaseComplete, "call-2", "plan ready", ctx);
1471
1580
 
1472
1581
  expect(result.content[0].text).toContain("continue");
1473
1582
  expect(orchestrator.active!.state.phase).toBe("plan");
@@ -1485,7 +1594,7 @@ describe("edge cases and regressions", () => {
1485
1594
  writeFileSync(join(taskDir, "RESEARCH.md"), VALID_RESEARCH, "utf-8");
1486
1595
  expectBrainstormToPlan(menu);
1487
1596
  const ppPhaseComplete = getTool(pi, "pp_phase_complete");
1488
- await ppPhaseComplete.execute("call-1", { summary: "done" }, undefined, undefined, ctx);
1597
+ await completePhase(ppPhaseComplete, "call-1", "done", ctx);
1489
1598
  await new Promise((r) => setTimeout(r, 10));
1490
1599
 
1491
1600
  emitSubagentCreated(pi, "planner-1", "Planner (test)");
@@ -1503,11 +1612,11 @@ describe("edge cases and regressions", () => {
1503
1612
  "utf-8",
1504
1613
  );
1505
1614
  expectPlanToImplement(menu);
1506
- await ppPhaseComplete.execute("call-2", { summary: "plan done" }, undefined, undefined, ctx);
1615
+ await completePhase(ppPhaseComplete, "call-2", "plan done", ctx);
1507
1616
  await new Promise((r) => setTimeout(r, 10));
1508
1617
 
1509
1618
  expectReviewOnMyOwn(menu, "Done");
1510
- const result = await ppPhaseComplete.execute("call-3", { summary: "implemented" }, undefined, undefined, ctx);
1619
+ const result = await completePhase(ppPhaseComplete, "call-3", "implemented", ctx);
1511
1620
 
1512
1621
  expect(result.content[0].text).toContain("AI_REVIEW:");
1513
1622
  expect(result.content[0].text).toContain("CHANGED files");
@@ -1524,7 +1633,7 @@ describe("edge cases and regressions", () => {
1524
1633
 
1525
1634
  expectReviewOnMyOwn(menu, "Done");
1526
1635
  const ppPhaseComplete = getTool(pi, "pp_phase_complete");
1527
- const result = await ppPhaseComplete.execute("call-1", { summary: "researched" }, undefined, undefined, ctx);
1636
+ const result = await completePhase(ppPhaseComplete, "call-1", "researched", ctx);
1528
1637
 
1529
1638
  expect(result.content[0].text).toContain("AI_REVIEW:");
1530
1639
  expect(result.content[0].text).toContain("USER_REQUEST.md");
@@ -1535,24 +1644,6 @@ describe("edge cases and regressions", () => {
1535
1644
  expect(orchestrator.active!.state.step).toBe("llm_work");
1536
1645
  });
1537
1646
 
1538
- it("editor-review Done returns AI_REVIEW state-file instructions in debug phase", async () => {
1539
- const cwd = makeTempDir();
1540
- const { pi, orchestrator } = await setupOrchestrator(cwd);
1541
- const ctx = makeCtx();
1542
-
1543
- await orchestrator.startTask(ctx as any, "debug", "Debug editor review");
1544
-
1545
- expectReviewOnMyOwn(menu, "Done");
1546
- const ppPhaseComplete = getTool(pi, "pp_phase_complete");
1547
- const result = await ppPhaseComplete.execute("call-1", { summary: "diagnosed" }, undefined, undefined, ctx);
1548
-
1549
- expect(result.content[0].text).toContain("AI_REVIEW:");
1550
- expect(result.content[0].text).toContain("USER_REQUEST.md");
1551
- expect(result.content[0].text).not.toContain("CHANGED files");
1552
- expect(orchestrator.active!.state.phase).toBe("debug");
1553
- expect(orchestrator.active!.state.step).toBe("llm_work");
1554
- });
1555
-
1556
1647
  it("editor-review Done targets synthesized plan in plan phase", async () => {
1557
1648
  const cwd = makeTempDir();
1558
1649
  const { pi, orchestrator } = await setupOrchestrator(cwd);
@@ -1564,7 +1655,7 @@ describe("edge cases and regressions", () => {
1564
1655
  writeFileSync(join(taskDir, "RESEARCH.md"), VALID_RESEARCH, "utf-8");
1565
1656
  expectBrainstormToPlan(menu);
1566
1657
  const ppPhaseComplete = getTool(pi, "pp_phase_complete");
1567
- await ppPhaseComplete.execute("call-1", { summary: "done" }, undefined, undefined, ctx);
1658
+ await completePhase(ppPhaseComplete, "call-1", "done", ctx);
1568
1659
  await new Promise((r) => setTimeout(r, 10));
1569
1660
 
1570
1661
  emitSubagentCreated(pi, "planner-1", "Planner (test)");
@@ -1583,7 +1674,7 @@ describe("edge cases and regressions", () => {
1583
1674
  );
1584
1675
 
1585
1676
  expectReviewOnMyOwn(menu, "Done");
1586
- const result = await ppPhaseComplete.execute("call-2", { summary: "plan ready" }, undefined, undefined, ctx);
1677
+ const result = await completePhase(ppPhaseComplete, "call-2", "plan ready", ctx);
1587
1678
 
1588
1679
  expect(result.content[0].text).toContain("AI_REVIEW:");
1589
1680
  expect(result.content[0].text).toContain("_synthesized.md");
@@ -1606,17 +1697,17 @@ describe("edge cases and regressions", () => {
1606
1697
  expect(sendUserCalls.length).toBe(0);
1607
1698
  });
1608
1699
 
1609
- it("implement --from debug skips brainstorm", async () => {
1700
+ it("implement --from review skips brainstorm", async () => {
1610
1701
  const cwd = makeTempDir();
1611
1702
  const { pi, orchestrator } = await setupOrchestrator(cwd);
1612
1703
  const ctx = makeCtx();
1613
1704
 
1614
- await orchestrator.startTask(ctx as any, "debug", "Find bug");
1615
- const debugDir = orchestrator.active!.dir;
1616
- writeFileSync(join(debugDir, "USER_REQUEST.md"), VALID_USER_REQUEST, "utf-8");
1617
- writeFileSync(join(debugDir, "RESEARCH.md"), VALID_RESEARCH, "utf-8");
1705
+ await orchestrator.startTask(ctx as any, "review", "Find bug");
1706
+ const reviewDir = orchestrator.active!.dir;
1707
+ writeFileSync(join(reviewDir, "USER_REQUEST.md"), VALID_USER_REQUEST, "utf-8");
1708
+ writeFileSync(join(reviewDir, "RESEARCH.md"), VALID_RESEARCH, "utf-8");
1618
1709
 
1619
- await orchestrator.startTask(ctx as any, "implement", "Fix it", debugDir, true);
1710
+ await orchestrator.startTask(ctx as any, "implement", "Fix it", reviewDir, true);
1620
1711
 
1621
1712
  expect(orchestrator.active!.state.phase).toBe("plan");
1622
1713
  expect(orchestrator.active!.state.step).toBe("await_planners");
@@ -1624,17 +1715,17 @@ describe("edge cases and regressions", () => {
1624
1715
  expect(existsSync(join(orchestrator.active!.dir, "RESEARCH.md"))).toBe(true);
1625
1716
  });
1626
1717
 
1627
- it("implement --from debug with generic description skips blank-task prompt", async () => {
1718
+ it("implement --from review with generic description skips blank-task prompt", async () => {
1628
1719
  const cwd = makeTempDir();
1629
1720
  const { pi, orchestrator } = await setupOrchestrator(cwd);
1630
1721
  const ctx = makeCtx();
1631
1722
 
1632
- await orchestrator.startTask(ctx as any, "debug", "Find bug");
1633
- const debugDir = orchestrator.active!.dir;
1634
- writeFileSync(join(debugDir, "USER_REQUEST.md"), VALID_USER_REQUEST, "utf-8");
1635
- writeFileSync(join(debugDir, "RESEARCH.md"), VALID_RESEARCH, "utf-8");
1723
+ await orchestrator.startTask(ctx as any, "review", "Find bug");
1724
+ const reviewDir = orchestrator.active!.dir;
1725
+ writeFileSync(join(reviewDir, "USER_REQUEST.md"), VALID_USER_REQUEST, "utf-8");
1726
+ writeFileSync(join(reviewDir, "RESEARCH.md"), VALID_RESEARCH, "utf-8");
1636
1727
 
1637
- await orchestrator.startTask(ctx as any, "implement", "implement", debugDir, true);
1728
+ await orchestrator.startTask(ctx as any, "implement", "implement", reviewDir, true);
1638
1729
 
1639
1730
  expect(ctx.ui.notify).not.toHaveBeenCalledWith("Task created. Describe what you'd like to do.", "info");
1640
1731
  expect(ctx.ui.notify).toHaveBeenCalledWith("Entered plan phase. Waiting for planners to complete before synthesis.", "info");
@@ -1661,22 +1752,22 @@ describe("edge cases and regressions", () => {
1661
1752
  expect(existsSync(join(orchestrator.active!.dir, "RESEARCH.md"))).toBe(true);
1662
1753
  });
1663
1754
 
1664
- it("implement from debug task stores source path and skips brainstorm", async () => {
1755
+ it("implement from review task stores source path and skips brainstorm", async () => {
1665
1756
  const cwd = makeTempDir();
1666
1757
  const { orchestrator } = await setupOrchestrator(cwd);
1667
1758
  const ctx = makeCtx();
1668
1759
 
1669
- await orchestrator.startTask(ctx as any, "debug", "Find bug");
1670
- const debugDir = orchestrator.active!.dir;
1671
- writeFileSync(join(debugDir, "USER_REQUEST.md"), VALID_USER_REQUEST, "utf-8");
1672
- writeFileSync(join(debugDir, "RESEARCH.md"), VALID_RESEARCH, "utf-8");
1760
+ await orchestrator.startTask(ctx as any, "review", "Find bug");
1761
+ const reviewDir = orchestrator.active!.dir;
1762
+ writeFileSync(join(reviewDir, "USER_REQUEST.md"), VALID_USER_REQUEST, "utf-8");
1763
+ writeFileSync(join(reviewDir, "RESEARCH.md"), VALID_RESEARCH, "utf-8");
1673
1764
 
1674
- await orchestrator.startTask(ctx as any, "implement", "implement", debugDir, true);
1765
+ await orchestrator.startTask(ctx as any, "implement", "implement", reviewDir, true);
1675
1766
 
1676
1767
  expect(orchestrator.active!.type).toBe("implement");
1677
1768
  expect(orchestrator.active!.state.phase).toBe("plan");
1678
1769
  expect(orchestrator.active!.state.step).toBe("await_planners");
1679
- expect(orchestrator.active!.state.from).toBe(`debug/${debugDir.split("/").pop()}`);
1770
+ expect(orchestrator.active!.state.from).toBe(`review/${reviewDir.split("/").pop()}`);
1680
1771
  expect(orchestrator.active!.state.description).toBe("implement");
1681
1772
  expect(ctx.ui.notify).toHaveBeenCalledWith("Entered plan phase. Waiting for planners to complete before synthesis.", "info");
1682
1773
  });
@@ -1777,6 +1868,7 @@ describe("task modes and quick task", () => {
1777
1868
  // even for an autonomous task, so auto-advance only applies from plan onward.
1778
1869
  orchestrator.active!.state.phase = "plan";
1779
1870
  orchestrator.active!.state.step = "llm_work";
1871
+ orchestrator.active!.state.reconciledPhase = "plan";
1780
1872
  const plansDir = join(taskDir, "plans");
1781
1873
  mkdirSync(plansDir, { recursive: true });
1782
1874
  writeFileSync(
@@ -1820,7 +1912,7 @@ describe("task modes and quick task", () => {
1820
1912
  // Next→Continue step is expected here.)
1821
1913
  expectActiveTaskNext(menu, "Continue to plan & implement");
1822
1914
  const ppPhaseComplete = getTool(pi, "pp_phase_complete");
1823
- await ppPhaseComplete.execute("call-1", { summary: "done" }, undefined, undefined, ctx);
1915
+ await completePhase(ppPhaseComplete, "call-1", "done", ctx);
1824
1916
  await new Promise((r) => setTimeout(r, 10));
1825
1917
 
1826
1918
  // The guided menu was shown (auto-advance did NOT bypass it).
@@ -1833,6 +1925,7 @@ describe("task modes and quick task", () => {
1833
1925
  const ctx = makeCtx();
1834
1926
 
1835
1927
  await orchestrator.startTask(ctx as any, "implement", "esc dismiss");
1928
+ orchestrator.active!.state.reconciledPhase = orchestrator.active!.state.phase;
1836
1929
  // Guided task in brainstorm; transition controller is running so the
1837
1930
  // non-ESC dismiss path would normally return the reminder text.
1838
1931
  expect(orchestrator.transitionController.isRunning()).toBe(true);
@@ -1853,6 +1946,7 @@ describe("task modes and quick task", () => {
1853
1946
  const ctx = makeCtx();
1854
1947
 
1855
1948
  await orchestrator.startTask(ctx as any, "implement", "back dismiss");
1949
+ orchestrator.active!.state.reconciledPhase = orchestrator.active!.state.phase;
1856
1950
  expect(orchestrator.transitionController.isRunning()).toBe(true);
1857
1951
 
1858
1952
  // Explicit "Back" navigation (not an ESC): the reminder must be preserved.
@@ -1893,6 +1987,7 @@ describe("task modes and quick task", () => {
1893
1987
  orchestrator.active!.state.step = "llm_work";
1894
1988
  orchestrator.active!.state.reviewPass = 0;
1895
1989
  orchestrator.active!.state.reviewPassByKind = {};
1990
+ orchestrator.active!.state.reconciledPhase = "implement";
1896
1991
  orchestrator.active!.state.autonomousConfig = {
1897
1992
  phases: {
1898
1993
  implement: { reviewPreset: "regular", maxReviewPasses: 2 },
@@ -1964,6 +2059,7 @@ describe("task modes and quick task", () => {
1964
2059
  "utf-8",
1965
2060
  );
1966
2061
 
2062
+ orchestrator.active!.state.reconciledPhase = "implement";
1967
2063
  const ppPhaseComplete = getTool(pi, "pp_phase_complete");
1968
2064
  const first = await ppPhaseComplete.execute("call-1", { summary: "done" }, undefined, undefined, ctx);
1969
2065
  expect(first.content[0].text).toMatch(/Reviews are running|Started review cycle pass/);
@@ -1991,6 +2087,46 @@ describe("task modes and quick task", () => {
1991
2087
  expect(finalState.reviewPassByKind?.implement?.auto).toBe(1);
1992
2088
  });
1993
2089
 
2090
+ it("autonomous review disabled with maxReviewPasses:0 advances without spawning reviewers", async () => {
2091
+ const cwd = makeTempDir();
2092
+ const { pi, orchestrator } = await setupOrchestrator(cwd);
2093
+ const ctx = makeCtx();
2094
+
2095
+ await orchestrator.startTask(ctx as any, "implement", "implement", undefined, undefined, "autonomous");
2096
+ const taskDir = orchestrator.active!.dir;
2097
+ orchestrator.active!.state.phase = "implement";
2098
+ orchestrator.active!.state.step = "llm_work";
2099
+ orchestrator.active!.state.reviewPass = 0;
2100
+ orchestrator.active!.state.reviewPassByKind = {};
2101
+ orchestrator.active!.state.autonomousConfig = {
2102
+ phases: { implement: { reviewPreset: "regular", maxReviewPasses: 0 } },
2103
+ };
2104
+ writeFileSync(join(taskDir, "USER_REQUEST.md"), VALID_USER_REQUEST, "utf-8");
2105
+ writeFileSync(join(taskDir, "RESEARCH.md"), VALID_RESEARCH, "utf-8");
2106
+ const plansDir = join(taskDir, "plans");
2107
+ mkdirSync(plansDir, { recursive: true });
2108
+ writeFileSync(
2109
+ join(plansDir, "1_synthesized.md"),
2110
+ makeValidPlan(["- [x] P1. Done item — Done when: synthesized plan is fully checked"]),
2111
+ "utf-8",
2112
+ );
2113
+
2114
+ orchestrator.active!.state.reconciledPhase = "implement";
2115
+ const ppPhaseComplete = getTool(pi, "pp_phase_complete");
2116
+ // implement→done is the terminal handoff, so the guided menu opens instead of
2117
+ // auto-completing; the key assertion is that NO review cycle was ever entered.
2118
+ expectImplementToDone(menu);
2119
+ const result = await ppPhaseComplete.execute("call-1", { summary: "done" }, undefined, undefined, ctx);
2120
+
2121
+ expect(result.content[0].text).not.toMatch(/Reviews are running|review cycle/i);
2122
+ expect(orchestrator.active).toBeNull();
2123
+ const finalState = loadTask(taskDir);
2124
+ expect(finalState.phase).toBe("done");
2125
+ expect(finalState.reviewCycle).toBeNull();
2126
+ // No auto review pass ran because review was disabled.
2127
+ expect(finalState.reviewPassByKind?.implement?.auto ?? 0).toBe(0);
2128
+ });
2129
+
1994
2130
  it("autonomous PLAN review cycle: apply feedback (clean approve) advances to implement", async () => {
1995
2131
  const cwd = makeTempDir();
1996
2132
  const { pi, orchestrator } = await setupOrchestrator(cwd);
@@ -2015,6 +2151,7 @@ describe("task modes and quick task", () => {
2015
2151
  "utf-8",
2016
2152
  );
2017
2153
 
2154
+ orchestrator.active!.state.reconciledPhase = "plan";
2018
2155
  const ppPhaseComplete = getTool(pi, "pp_phase_complete");
2019
2156
  // First call: plan phase is autonomous, no clean approval yet → starts a review cycle.
2020
2157
  const first = await ppPhaseComplete.execute("plan-rev-1", { summary: "plan done" }, undefined, undefined, ctx);
@@ -2060,6 +2197,7 @@ describe("task modes and quick task", () => {
2060
2197
  "utf-8",
2061
2198
  );
2062
2199
 
2200
+ orchestrator.active!.state.reconciledPhase = "implement";
2063
2201
  const ppPhaseComplete = getTool(pi, "pp_phase_complete");
2064
2202
  // Terminal handoff (#1): clean-approved re-entry opens the guided menu rather
2065
2203
  // than auto-completing; the user drives Next→Complete to finish.
@@ -2098,6 +2236,7 @@ describe("task modes and quick task", () => {
2098
2236
  mkdirSync(reviewsDir, { recursive: true });
2099
2237
  writeFileSync(join(reviewsDir, "1_a_round-1.md"), "- CRITICAL: bug at x.ts:1\n- VERDICT: NEEDS_CHANGES", "utf-8");
2100
2238
 
2239
+ orchestrator.active!.state.reconciledPhase = "implement";
2101
2240
  const ppPhaseComplete = getTool(pi, "pp_phase_complete");
2102
2241
  const next = await ppPhaseComplete.execute("call-1", { summary: "fixes applied, re-review" }, undefined, undefined, ctx);
2103
2242
  expect(orchestrator.active!.state.reviewApprovedClean).toBeFalsy();
@@ -2127,6 +2266,7 @@ describe("task modes and quick task", () => {
2127
2266
  "utf-8",
2128
2267
  );
2129
2268
 
2269
+ orchestrator.active!.state.reconciledPhase = "implement";
2130
2270
  const ppPhaseComplete = getTool(pi, "pp_phase_complete");
2131
2271
  const first = await ppPhaseComplete.execute("call-1", { summary: "done" }, undefined, undefined, ctx);
2132
2272
  expect(first.content[0].text).toMatch(/Cannot start review yet/);
@@ -2213,7 +2353,7 @@ describe("task modes and quick task", () => {
2213
2353
  .expect({ question: "Mode", options: { include: ["Autonomous"] }, choose: "Autonomous" })
2214
2354
  .expect({ question: "Autonomous", options: { include: ["Start"] }, choose: "Start" });
2215
2355
  const ppPhaseComplete = getTool(pi, "pp_phase_complete");
2216
- await ppPhaseComplete.execute("call-1", { summary: "Conclusions ready" }, undefined, undefined, ctx);
2356
+ await completePhase(ppPhaseComplete, "call-1", "Conclusions ready", ctx);
2217
2357
 
2218
2358
  expect(orchestrator.active!.state.autonomousConfig?.phases.plan).toBeDefined();
2219
2359
  expect(orchestrator.active!.state.autonomousConfig?.phases.implement).toBeDefined();
@@ -2224,12 +2364,12 @@ describe("task modes and quick task", () => {
2224
2364
  const cwd = makeTempDir();
2225
2365
  const { pi, orchestrator } = await setupOrchestrator(cwd);
2226
2366
 
2227
- await orchestrator.startTask({ ...makeCtx(), cwd } as any, "debug", "Find bug");
2228
- const debugDir = orchestrator.active!.dir;
2229
- writeFileSync(join(debugDir, "USER_REQUEST.md"), VALID_USER_REQUEST, "utf-8");
2230
- writeFileSync(join(debugDir, "RESEARCH.md"), VALID_RESEARCH, "utf-8");
2367
+ await orchestrator.startTask({ ...makeCtx(), cwd } as any, "review", "Find bug");
2368
+ const reviewDir = orchestrator.active!.dir;
2369
+ writeFileSync(join(reviewDir, "USER_REQUEST.md"), VALID_USER_REQUEST, "utf-8");
2370
+ writeFileSync(join(reviewDir, "RESEARCH.md"), VALID_RESEARCH, "utf-8");
2231
2371
 
2232
- await orchestrator.startTask({ ...makeCtx(), cwd } as any, "implement", "implement", debugDir, true, "autonomous");
2372
+ await orchestrator.startTask({ ...makeCtx(), cwd } as any, "implement", "implement", reviewDir, true, "autonomous");
2233
2373
  expect(orchestrator.active!.state.initialPhase).toBe("plan");
2234
2374
  expect(orchestrator.active!.state.phase).toBe("plan");
2235
2375
 
@@ -2243,11 +2383,11 @@ describe("task modes and quick task", () => {
2243
2383
  const { pi, orchestrator } = await setupOrchestrator(cwd);
2244
2384
  const ctx = makeCtx();
2245
2385
 
2246
- await orchestrator.startTask({ ...ctx, cwd } as any, "debug", "Find bug");
2247
- const debugDir = orchestrator.active!.dir;
2248
- writeFileSync(join(debugDir, "USER_REQUEST.md"), VALID_USER_REQUEST, "utf-8");
2249
- writeFileSync(join(debugDir, "RESEARCH.md"), VALID_RESEARCH, "utf-8");
2250
- await orchestrator.startTask({ ...ctx, cwd } as any, "implement", "implement", debugDir, true, "autonomous");
2386
+ await orchestrator.startTask({ ...ctx, cwd } as any, "review", "Find bug");
2387
+ const reviewDir = orchestrator.active!.dir;
2388
+ writeFileSync(join(reviewDir, "USER_REQUEST.md"), VALID_USER_REQUEST, "utf-8");
2389
+ writeFileSync(join(reviewDir, "RESEARCH.md"), VALID_RESEARCH, "utf-8");
2390
+ await orchestrator.startTask({ ...ctx, cwd } as any, "implement", "implement", reviewDir, true, "autonomous");
2251
2391
  orchestrator.active!.state.phase = "implement";
2252
2392
  orchestrator.active!.state.step = "llm_work";
2253
2393
 
@@ -2563,7 +2703,7 @@ describe("task modes and quick task", () => {
2563
2703
  // picker because the task is autonomous).
2564
2704
  expectActiveTaskNext(menu, "Continue to plan & implement");
2565
2705
  const ppPhaseComplete = getTool(pi, "pp_phase_complete");
2566
- await ppPhaseComplete.execute("call-autonomous-skip-planner", { summary: "done" }, undefined, undefined, ctx);
2706
+ await completePhase(ppPhaseComplete, "call-autonomous-skip-planner", "done", ctx);
2567
2707
  await new Promise((r) => setTimeout(r, 10));
2568
2708
 
2569
2709
  expect(menu.transcript.filter((entry) => entry.question.includes("Planner preset"))).toHaveLength(0);
@@ -2592,6 +2732,7 @@ describe("task modes and quick task", () => {
2592
2732
  "utf-8",
2593
2733
  );
2594
2734
 
2735
+ orchestrator.active!.state.reconciledPhase = "implement";
2595
2736
  const ppPhaseComplete = getTool(pi, "pp_phase_complete");
2596
2737
  const first = await ppPhaseComplete.execute("call-autonomous-review-1", { summary: "done" }, undefined, undefined, ctx);
2597
2738
  expect(first.content[0].text).toMatch(/Reviews are running|Started review cycle pass/);
@@ -2738,7 +2879,7 @@ describe("review task lifecycle", () => {
2738
2879
 
2739
2880
  expectImplementToDone(menu);
2740
2881
  const ppPhaseComplete = getTool(pi, "pp_phase_complete");
2741
- await ppPhaseComplete.execute("call-implement-to-done", { summary: "done" }, undefined, undefined, ctx);
2882
+ await completePhase(ppPhaseComplete, "call-implement-to-done", "done", ctx);
2742
2883
 
2743
2884
  expect(orchestrator.active).toBeNull();
2744
2885
  expect(loadTask(taskDir).phase).toBe("done");
@@ -2750,6 +2891,7 @@ describe("review task lifecycle", () => {
2750
2891
  const ctx = makeCtx();
2751
2892
 
2752
2893
  await orchestrator.startTask(ctx as any, "review", "Review validation");
2894
+ orchestrator.active!.state.reconciledPhase = orchestrator.active!.state.phase;
2753
2895
 
2754
2896
  expectBrainstormToPlan(menu);
2755
2897
  const ppPhaseComplete = getTool(pi, "pp_phase_complete");
@@ -2761,37 +2903,6 @@ describe("review task lifecycle", () => {
2761
2903
  });
2762
2904
  });
2763
2905
 
2764
- describe("debug task lifecycle", () => {
2765
- it("debug task starts in debug phase", async () => {
2766
- const cwd = makeTempDir();
2767
- const { orchestrator } = await setupOrchestrator(cwd);
2768
-
2769
- await orchestrator.startTask(makeCtx() as any, "debug", "Debug issue");
2770
-
2771
- expect(orchestrator.active!.type).toBe("debug");
2772
- expect(orchestrator.active!.state.phase).toBe("debug");
2773
- });
2774
-
2775
- it("debug task transitions debug to plan to implement to done", async () => {
2776
- const cwd = makeTempDir();
2777
- const { pi, orchestrator } = await setupOrchestrator(cwd);
2778
- const ctx = makeCtx();
2779
-
2780
- await orchestrator.startTask(ctx as any, "debug", "Debug flow");
2781
- const taskDir = orchestrator.active!.dir;
2782
-
2783
- await moveTaskToImplementPhase(pi, orchestrator, ctx, "call-debug-to-plan", "call-debug-plan-to-implement");
2784
- expect(orchestrator.active!.state.phase).toBe("implement");
2785
-
2786
- expectImplementToDone(menu);
2787
- const ppPhaseComplete = getTool(pi, "pp_phase_complete");
2788
- await ppPhaseComplete.execute("call-debug-implement-to-done", { summary: "done" }, undefined, undefined, ctx);
2789
-
2790
- expect(orchestrator.active).toBeNull();
2791
- expect(loadTask(taskDir).phase).toBe("done");
2792
- });
2793
- });
2794
-
2795
2906
  describe("modified file tracking", () => {
2796
2907
  it("tool_result tracks write and edit in implement phase", async () => {
2797
2908
  const cwd = makeTempDir();
@@ -3118,7 +3229,7 @@ describe("resume and recovery", () => {
3118
3229
  it("getActiveTask returns null when multiple unlocked tasks exist", () => {
3119
3230
  const cwd = makeTempDir();
3120
3231
  createTask(cwd, "implement", "first unlocked");
3121
- createTask(cwd, "debug", "second unlocked");
3232
+ createTask(cwd, "review", "second unlocked");
3122
3233
 
3123
3234
  const active = getActiveTask(cwd);
3124
3235
 
@@ -4366,15 +4477,15 @@ describe("menu contracts", () => {
4366
4477
  await pp(undefined, ctx);
4367
4478
  });
4368
4479
 
4369
- it("autonomous debug task shows interactive menu in debug phase", async () => {
4480
+ it("autonomous review task shows interactive menu in review phase", async () => {
4370
4481
  const cwd = makeTempDir();
4371
4482
  const { pi, orchestrator } = await setupOrchestrator(cwd);
4372
4483
  const ctx = makeCtx();
4373
4484
 
4374
- await orchestrator.startTask(ctx as any, "debug", "contract debug auto", undefined, undefined, "autonomous");
4485
+ await orchestrator.startTask(ctx as any, "review", "contract review auto", undefined, undefined, "autonomous");
4375
4486
 
4376
4487
  menu.expect({
4377
- question: m.taskMenu("debug", "debug"),
4488
+ question: m.taskMenu("review", "review"),
4378
4489
  options: {
4379
4490
  exact: ["Next", "Review", "Subagents", "Settings", "Back to prompt"],
4380
4491
  },
@@ -4385,40 +4496,58 @@ describe("menu contracts", () => {
4385
4496
  await pp(undefined, ctx);
4386
4497
  });
4387
4498
 
4388
- it("autonomous review task shows interactive menu in review phase", async () => {
4499
+ it("autonomous menu 'Complete task' completes the task", async () => {
4389
4500
  const cwd = makeTempDir();
4390
4501
  const { pi, orchestrator } = await setupOrchestrator(cwd);
4391
4502
  const ctx = makeCtx();
4392
4503
 
4393
- await orchestrator.startTask(ctx as any, "review", "contract review auto", undefined, undefined, "autonomous");
4504
+ await orchestrator.startTask(ctx as any, "implement", "contract complete", undefined, undefined, "autonomous");
4505
+ const taskDir = orchestrator.active!.dir;
4394
4506
 
4395
- menu.expect({
4396
- question: m.taskMenu("review", "review"),
4397
- options: {
4398
- exact: ["Next", "Review", "Subagents", "Settings", "Back to prompt"],
4399
- },
4400
- choose: "Back to prompt",
4401
- });
4507
+ expectActiveTaskNext(menu, "Complete");
4402
4508
 
4403
4509
  const pp = getCommand(pi, "pp");
4404
4510
  await pp(undefined, ctx);
4511
+
4512
+ expect(orchestrator.active).toBeNull();
4513
+ expect(loadTask(taskDir).phase).toBe("done");
4405
4514
  });
4406
4515
 
4407
- it("autonomous menu 'Complete task' completes the task", async () => {
4516
+ it("autonomous /pp Complete emits the terminal assumptions summary", async () => {
4408
4517
  const cwd = makeTempDir();
4409
4518
  const { pi, orchestrator } = await setupOrchestrator(cwd);
4410
4519
  const ctx = makeCtx();
4411
4520
 
4412
- await orchestrator.startTask(ctx as any, "implement", "contract complete", undefined, undefined, "autonomous");
4521
+ await orchestrator.startTask(ctx as any, "implement", "complete assumptions", undefined, undefined, "autonomous");
4413
4522
  const taskDir = orchestrator.active!.dir;
4523
+ mkdirSync(join(taskDir, "artifacts"), { recursive: true });
4524
+ writeFileSync(join(taskDir, "artifacts", "ASSUMPTIONS.md"), "# A\n\n- statement: idempotent; confidence: med; status: open\n", "utf-8");
4414
4525
 
4415
4526
  expectActiveTaskNext(menu, "Complete");
4527
+ const pp = getCommand(pi, "pp");
4528
+ await pp(undefined, ctx);
4529
+
4530
+ const summaryCall = (ctx.ui.notify as any).mock.calls.find((c: any[]) => String(c[0]).includes("Assumptions recorded this run"));
4531
+ expect(summaryCall).toBeTruthy();
4532
+ expect(String(summaryCall[0])).toContain("idempotent");
4533
+ });
4416
4534
 
4535
+ it("guided /pp Complete does NOT emit a terminal assumptions summary", async () => {
4536
+ const cwd = makeTempDir();
4537
+ const { pi, orchestrator } = await setupOrchestrator(cwd);
4538
+ const ctx = makeCtx();
4539
+
4540
+ await orchestrator.startTask(ctx as any, "implement", "guided complete no assumptions");
4541
+ const taskDir = orchestrator.active!.dir;
4542
+ mkdirSync(join(taskDir, "artifacts"), { recursive: true });
4543
+ writeFileSync(join(taskDir, "artifacts", "ASSUMPTIONS.md"), "# A\n\n- statement: x; confidence: low; status: open\n", "utf-8");
4544
+
4545
+ expectActiveTaskNext(menu, "Complete");
4417
4546
  const pp = getCommand(pi, "pp");
4418
4547
  await pp(undefined, ctx);
4419
4548
 
4420
- expect(orchestrator.active).toBeNull();
4421
- expect(loadTask(taskDir).phase).toBe("done");
4549
+ const summaryCall = (ctx.ui.notify as any).mock.calls.find((c: any[]) => String(c[0]).includes("Assumptions recorded this run"));
4550
+ expect(summaryCall).toBeFalsy();
4422
4551
  });
4423
4552
 
4424
4553
  it("autonomous menu 'Pause task' pauses without completing", async () => {
@@ -4438,6 +4567,59 @@ describe("menu contracts", () => {
4438
4567
  expect(loadTask(taskDir).phase).not.toBe("done");
4439
4568
  });
4440
4569
 
4570
+ it("pausing an un-reconciled phase flags reconcilePending so the next resume reconciles before spawning", async () => {
4571
+ const cwd = makeTempDir();
4572
+ const { pi, orchestrator } = await setupOrchestrator(cwd);
4573
+ const ctx = makeCtx();
4574
+
4575
+ await orchestrator.startTask(ctx as any, "implement", "contract pause reconcile", undefined, undefined, "autonomous");
4576
+ const taskDir = orchestrator.active!.dir;
4577
+ expect(orchestrator.active!.state.reconciledPhase).toBeUndefined();
4578
+
4579
+ expectActiveTaskNext(menu, "Pause");
4580
+ const pp = getCommand(pi, "pp");
4581
+ await pp(undefined, ctx);
4582
+
4583
+ expect(orchestrator.active).toBeNull();
4584
+ expect(loadTask(taskDir).reconcilePending).toBe(true);
4585
+ });
4586
+
4587
+ it("pausing an already-reconciled phase STILL flags reconcilePending (later work may have made it stale)", async () => {
4588
+ const cwd = makeTempDir();
4589
+ const { pi, orchestrator } = await setupOrchestrator(cwd);
4590
+ const ctx = makeCtx();
4591
+
4592
+ await orchestrator.startTask(ctx as any, "implement", "contract pause noflag", undefined, undefined, "autonomous");
4593
+ const taskDir = orchestrator.active!.dir;
4594
+ orchestrator.active!.state.reconciledPhase = orchestrator.active!.state.phase;
4595
+ saveTask(taskDir, orchestrator.active!.state);
4596
+
4597
+ expectActiveTaskNext(menu, "Pause");
4598
+ const pp = getCommand(pi, "pp");
4599
+ await pp(undefined, ctx);
4600
+
4601
+ expect(orchestrator.active).toBeNull();
4602
+ expect(loadTask(taskDir).reconcilePending).toBe(true);
4603
+ });
4604
+
4605
+ it("a resumed task with reconcilePending re-prompts the reconcile gate before advancing", async () => {
4606
+ const cwd = makeTempDir();
4607
+ const { pi, orchestrator } = await setupOrchestrator(cwd);
4608
+ const ctx = makeCtx();
4609
+
4610
+ await orchestrator.startTask(ctx as any, "implement", "contract resume reconcile", undefined, undefined, "autonomous");
4611
+ const taskDir = orchestrator.active!.dir;
4612
+ // Simulate a resume after a pause that left the phase reconciled but pending.
4613
+ orchestrator.active!.state.reconciledPhase = orchestrator.active!.state.phase;
4614
+ orchestrator.active!.state.reconcilePending = true;
4615
+ saveTask(taskDir, orchestrator.active!.state);
4616
+
4617
+ const ppPhaseComplete = getTool(pi, "pp_phase_complete");
4618
+ const first = await ppPhaseComplete.execute("resume-1", { summary: "done" }, undefined, undefined, ctx);
4619
+ expect(first.content[0].text).toContain("reconcile the task's state files");
4620
+ expect(loadTask(taskDir).reconcilePending ?? false).toBe(false);
4621
+ });
4622
+
4441
4623
  it("completing a task mid-review clears reviewCycle without incrementing reviewPass", async () => {
4442
4624
  const cwd = makeTempDir();
4443
4625
  const { pi, orchestrator } = await setupOrchestrator(cwd);
@@ -4579,7 +4761,7 @@ describe("full user flows", () => {
4579
4761
 
4580
4762
  expectBrainstormToPlan(menu);
4581
4763
  const ppPhaseComplete = getTool(pi, "pp_phase_complete");
4582
- await ppPhaseComplete.execute("flow-guided-1", { summary: "brainstorm done" }, undefined, undefined, ctx);
4764
+ await completePhase(ppPhaseComplete, "flow-guided-1", "brainstorm done", ctx);
4583
4765
  await new Promise((r) => setTimeout(r, 10));
4584
4766
 
4585
4767
  const plansDir = join(taskDir, "plans");
@@ -4590,11 +4772,11 @@ describe("full user flows", () => {
4590
4772
  writeFileSync(join(plansDir, `${Math.floor(Date.now() / 1000) + 1}_synthesized.md`), makeValidPlan(["- [x] P1. Ready — Done when: checked"]), "utf-8");
4591
4773
 
4592
4774
  expectPlanToImplement(menu);
4593
- await ppPhaseComplete.execute("flow-guided-2", { summary: "plan done" }, undefined, undefined, ctx);
4775
+ await completePhase(ppPhaseComplete, "flow-guided-2", "plan done", ctx);
4594
4776
  await new Promise((r) => setTimeout(r, 10));
4595
4777
 
4596
4778
  expectImplementToDone(menu);
4597
- await ppPhaseComplete.execute("flow-guided-3", { summary: "implement done" }, undefined, undefined, ctx);
4779
+ await completePhase(ppPhaseComplete, "flow-guided-3", "implement done", ctx);
4598
4780
 
4599
4781
  expect(orchestrator.active).toBeNull();
4600
4782
  expect(loadTask(taskDir).phase).toBe("done");
@@ -4623,7 +4805,7 @@ describe("full user flows", () => {
4623
4805
  // menu. plan→implement and implement→review below are autonomous auto-advance.
4624
4806
  expectActiveTaskNext(menu, "Continue to plan & implement");
4625
4807
  const ppPhaseComplete = getTool(pi, "pp_phase_complete");
4626
- await ppPhaseComplete.execute("flow-auto-1", { summary: "brainstorm done" }, undefined, undefined, ctx);
4808
+ await completePhase(ppPhaseComplete, "flow-auto-1", "brainstorm done", ctx);
4627
4809
  await new Promise((r) => setTimeout(r, 10));
4628
4810
 
4629
4811
  const plansDir = join(taskDir, "plans");
@@ -4633,10 +4815,10 @@ describe("full user flows", () => {
4633
4815
  emitSubagentCompleted(pi, "flow-auto-planner", "Planner (test)");
4634
4816
  writeFileSync(join(plansDir, `${Math.floor(Date.now() / 1000) + 1}_synthesized.md`), makeValidPlan(["- [x] P1. Ready — Done when: checked"]), "utf-8");
4635
4817
 
4636
- await ppPhaseComplete.execute("flow-auto-2", { summary: "plan done" }, undefined, undefined, ctx);
4818
+ await completePhase(ppPhaseComplete, "flow-auto-2", "plan done", ctx);
4637
4819
  await new Promise((r) => setTimeout(r, 10));
4638
4820
 
4639
- const firstReview = await ppPhaseComplete.execute("flow-auto-3", { summary: "implement done" }, undefined, undefined, ctx);
4821
+ const firstReview = await completePhase(ppPhaseComplete, "flow-auto-3", "implement done", ctx);
4640
4822
  expect(firstReview.content[0].text).toMatch(/Reviews are running|Started review cycle pass/);
4641
4823
 
4642
4824
  const reviewsDir = join(taskDir, "code-reviews");