@ilya-lesikov/pi-pi 0.10.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.
- package/3p/pi-subagents/package.json +0 -1
- package/3p/pi-subagents/src/agent-manager.ts +1 -0
- package/3p/pi-subagents/src/index.ts +1 -0
- package/3p/pi-subagents/src/types.ts +8 -0
- package/extensions/orchestrator/agents/advisor.ts +2 -1
- package/extensions/orchestrator/agents/code-reviewer.ts +1 -0
- package/extensions/orchestrator/agents/constraints.test.ts +43 -10
- package/extensions/orchestrator/agents/constraints.ts +26 -5
- package/extensions/orchestrator/agents/deep-debugger.ts +7 -6
- package/extensions/orchestrator/agents/explore.ts +1 -1
- package/extensions/orchestrator/agents/librarian.ts +1 -1
- package/extensions/orchestrator/agents/planner.ts +1 -1
- package/extensions/orchestrator/agents/prompts.test.ts +135 -1
- package/extensions/orchestrator/agents/reviewer.ts +2 -2
- package/extensions/orchestrator/agents/task.ts +6 -2
- package/extensions/orchestrator/agents/tool-routing.ts +22 -6
- package/extensions/orchestrator/assumptions.test.ts +77 -0
- package/extensions/orchestrator/assumptions.ts +85 -0
- package/extensions/orchestrator/cbm.ts +4 -1
- package/extensions/orchestrator/cbm.which.test.ts +92 -0
- package/extensions/orchestrator/command-handlers.ts +9 -1
- package/extensions/orchestrator/config.test.ts +1 -3
- package/extensions/orchestrator/config.ts +3 -4
- package/extensions/orchestrator/context.test.ts +2 -7
- package/extensions/orchestrator/context.ts +3 -3
- package/extensions/orchestrator/doctor.test.ts +35 -0
- package/extensions/orchestrator/doctor.ts +4 -2
- package/extensions/orchestrator/event-handlers.more.test.ts +185 -0
- package/extensions/orchestrator/event-handlers.test.ts +22 -2
- package/extensions/orchestrator/event-handlers.ts +156 -18
- package/extensions/orchestrator/flant-infra.more.test.ts +55 -0
- package/extensions/orchestrator/flant-infra.test.ts +0 -3
- package/extensions/orchestrator/flant-infra.ts +17 -7
- package/extensions/orchestrator/integration.test.ts +355 -173
- package/extensions/orchestrator/orchestrator.ts +3 -9
- package/extensions/orchestrator/phases/brainstorm.test.ts +25 -7
- package/extensions/orchestrator/phases/brainstorm.ts +35 -71
- package/extensions/orchestrator/phases/implementation.test.ts +22 -0
- package/extensions/orchestrator/phases/implementation.ts +6 -0
- package/extensions/orchestrator/phases/machine.test.ts +0 -28
- package/extensions/orchestrator/phases/machine.ts +0 -38
- package/extensions/orchestrator/phases/planning.test.ts +27 -1
- package/extensions/orchestrator/phases/planning.ts +1 -1
- package/extensions/orchestrator/phases/review-task.test.ts +7 -0
- package/extensions/orchestrator/phases/review-task.ts +1 -1
- package/extensions/orchestrator/phases/review.test.ts +47 -10
- package/extensions/orchestrator/phases/review.ts +36 -25
- package/extensions/orchestrator/pp-menu.from.test.ts +65 -0
- package/extensions/orchestrator/pp-menu.leaves.test.ts +17 -0
- package/extensions/orchestrator/pp-menu.test.ts +106 -49
- package/extensions/orchestrator/pp-menu.ts +280 -134
- package/extensions/orchestrator/pp-state-tools.ts +1 -1
- package/extensions/orchestrator/rate-limit-fallback-flow.test.ts +2 -2
- package/extensions/orchestrator/rate-limit-fallback.ts +1 -1
- package/extensions/orchestrator/state.test.ts +17 -17
- package/extensions/orchestrator/state.ts +35 -12
- package/package.json +6 -3
- package/scripts/lib/smoke-resolve.mjs +99 -0
- package/scripts/postinstall.mjs +93 -0
- package/scripts/test-package.sh +62 -0
- 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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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, "
|
|
1615
|
-
const
|
|
1616
|
-
writeFileSync(join(
|
|
1617
|
-
writeFileSync(join(
|
|
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",
|
|
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
|
|
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, "
|
|
1633
|
-
const
|
|
1634
|
-
writeFileSync(join(
|
|
1635
|
-
writeFileSync(join(
|
|
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",
|
|
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
|
|
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, "
|
|
1670
|
-
const
|
|
1671
|
-
writeFileSync(join(
|
|
1672
|
-
writeFileSync(join(
|
|
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",
|
|
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(`
|
|
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
|
|
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
|
|
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, "
|
|
2228
|
-
const
|
|
2229
|
-
writeFileSync(join(
|
|
2230
|
-
writeFileSync(join(
|
|
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",
|
|
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, "
|
|
2247
|
-
const
|
|
2248
|
-
writeFileSync(join(
|
|
2249
|
-
writeFileSync(join(
|
|
2250
|
-
await orchestrator.startTask({ ...ctx, cwd } as any, "implement", "implement",
|
|
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
|
|
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
|
|
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, "
|
|
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
|
|
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, "
|
|
4485
|
+
await orchestrator.startTask(ctx as any, "review", "contract review auto", undefined, undefined, "autonomous");
|
|
4375
4486
|
|
|
4376
4487
|
menu.expect({
|
|
4377
|
-
question: m.taskMenu("
|
|
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
|
|
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, "
|
|
4504
|
+
await orchestrator.startTask(ctx as any, "implement", "contract complete", undefined, undefined, "autonomous");
|
|
4505
|
+
const taskDir = orchestrator.active!.dir;
|
|
4394
4506
|
|
|
4395
|
-
menu
|
|
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
|
|
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", "
|
|
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
|
-
|
|
4421
|
-
expect(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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");
|