@ilya-lesikov/pi-pi 0.9.0 → 0.11.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-ask-user/index.ts +71 -124
- package/3p/pi-ask-user/single-select-layout.ts +3 -14
- package/3p/pi-subagents/package.json +12 -7
- package/3p/pi-subagents/src/agent-manager.ts +243 -79
- package/3p/pi-subagents/src/agent-runner.ts +501 -365
- package/3p/pi-subagents/src/agent-types.ts +46 -57
- package/3p/pi-subagents/src/cross-extension-rpc.ts +52 -46
- package/3p/pi-subagents/src/custom-agents.ts +30 -6
- package/3p/pi-subagents/src/default-agents.ts +25 -43
- package/3p/pi-subagents/src/enabled-models.ts +180 -0
- package/3p/pi-subagents/src/index.ts +599 -839
- package/3p/pi-subagents/src/model-resolver.ts +26 -7
- package/3p/pi-subagents/src/output-file.ts +21 -2
- package/3p/pi-subagents/src/prompts.ts +17 -3
- package/3p/pi-subagents/src/schedule-store.ts +153 -0
- package/3p/pi-subagents/src/schedule.ts +365 -0
- package/3p/pi-subagents/src/settings.ts +261 -0
- package/3p/pi-subagents/src/skill-loader.ts +77 -54
- package/3p/pi-subagents/src/status-note.ts +25 -0
- package/3p/pi-subagents/src/types.ts +97 -6
- package/3p/pi-subagents/src/ui/agent-widget.ts +94 -21
- package/3p/pi-subagents/src/ui/conversation-viewer.ts +147 -35
- package/3p/pi-subagents/src/ui/schedule-menu.ts +104 -0
- package/3p/pi-subagents/src/ui/viewer-keys.ts +39 -0
- package/3p/pi-subagents/src/usage.ts +60 -0
- package/3p/pi-subagents/src/worktree.ts +49 -20
- package/extensions/orchestrator/agents/advisor.ts +13 -8
- package/extensions/orchestrator/agents/brainstorm-reviewer.ts +8 -3
- package/extensions/orchestrator/agents/code-reviewer.ts +8 -3
- package/extensions/orchestrator/agents/constraints.test.ts +26 -0
- package/extensions/orchestrator/agents/constraints.ts +12 -2
- package/extensions/orchestrator/agents/deep-debugger.ts +13 -8
- package/extensions/orchestrator/agents/explore.ts +12 -6
- package/extensions/orchestrator/agents/librarian.ts +13 -5
- package/extensions/orchestrator/agents/plan-reviewer.ts +8 -3
- package/extensions/orchestrator/agents/planner.ts +8 -3
- package/extensions/orchestrator/agents/pools.test.ts +56 -0
- package/extensions/orchestrator/agents/prompts.test.ts +52 -10
- package/extensions/orchestrator/agents/registry.test.ts +245 -0
- package/extensions/orchestrator/agents/registry.ts +145 -10
- package/extensions/orchestrator/agents/reviewer.ts +14 -8
- package/extensions/orchestrator/agents/task.ts +12 -6
- package/extensions/orchestrator/agents/tool-routing.ts +213 -85
- package/extensions/orchestrator/agents/wait-for-completion.test.ts +171 -0
- package/extensions/orchestrator/ast-search.test.ts +124 -0
- package/extensions/orchestrator/billing-spoof.test.ts +67 -0
- package/extensions/orchestrator/billing-spoof.ts +95 -0
- package/extensions/orchestrator/cbm.more.test.ts +358 -0
- package/extensions/orchestrator/command-handlers.ts +6 -0
- package/extensions/orchestrator/commands.test.ts +15 -2
- package/extensions/orchestrator/commands.ts +1 -1
- package/extensions/orchestrator/config.test.ts +89 -1
- package/extensions/orchestrator/config.ts +102 -19
- package/extensions/orchestrator/context.test.ts +46 -0
- package/extensions/orchestrator/context.ts +18 -5
- package/extensions/orchestrator/custom-footer.test.ts +24 -10
- package/extensions/orchestrator/custom-footer.ts +4 -2
- package/extensions/orchestrator/doctor.more.test.ts +315 -0
- package/extensions/orchestrator/doctor.ts +6 -2
- package/extensions/orchestrator/event-handlers.more.test.ts +561 -0
- package/extensions/orchestrator/event-handlers.test.ts +96 -9
- package/extensions/orchestrator/event-handlers.ts +344 -151
- package/extensions/orchestrator/exa.more.test.ts +118 -0
- package/extensions/orchestrator/flant-infra.more.test.ts +381 -0
- package/extensions/orchestrator/flant-infra.test.ts +127 -0
- package/extensions/orchestrator/flant-infra.ts +126 -41
- package/extensions/orchestrator/index.test.ts +76 -0
- package/extensions/orchestrator/index.ts +2 -0
- package/extensions/orchestrator/integration.test.ts +183 -65
- package/extensions/orchestrator/model-registry.test.ts +2 -1
- package/extensions/orchestrator/model-registry.ts +12 -2
- package/extensions/orchestrator/orchestrator.test.ts +67 -0
- package/extensions/orchestrator/orchestrator.ts +119 -27
- package/extensions/orchestrator/phases/brainstorm.test.ts +30 -1
- package/extensions/orchestrator/phases/brainstorm.ts +43 -6
- package/extensions/orchestrator/phases/implementation.ts +2 -0
- package/extensions/orchestrator/phases/machine.test.ts +17 -1
- package/extensions/orchestrator/phases/machine.ts +4 -1
- package/extensions/orchestrator/phases/planning.test.ts +47 -1
- package/extensions/orchestrator/phases/planning.ts +18 -3
- package/extensions/orchestrator/phases/review-task.ts +5 -0
- package/extensions/orchestrator/phases/review.test.ts +10 -0
- package/extensions/orchestrator/phases/review.ts +22 -7
- package/extensions/orchestrator/phases/spawn-blocking.test.ts +1 -0
- package/extensions/orchestrator/phases/verdict.ts +6 -5
- package/extensions/orchestrator/plannotator-open-failure.test.ts +67 -0
- package/extensions/orchestrator/plannotator.test.ts +38 -1
- package/extensions/orchestrator/plannotator.ts +50 -3
- package/extensions/orchestrator/pp-menu.leaves.test.ts +590 -0
- package/extensions/orchestrator/pp-menu.more.test.ts +524 -0
- package/extensions/orchestrator/pp-menu.test.ts +114 -7
- package/extensions/orchestrator/pp-menu.ts +580 -91
- package/extensions/orchestrator/pp-state-tools.test.ts +80 -0
- package/extensions/orchestrator/pp-state-tools.ts +65 -0
- package/extensions/orchestrator/rate-limit-fallback.more.test.ts +241 -0
- package/extensions/orchestrator/rate-limit-fallback.test.ts +20 -1
- package/extensions/orchestrator/rate-limit-fallback.ts +12 -0
- package/extensions/orchestrator/review-files.test.ts +26 -0
- package/extensions/orchestrator/review-files.ts +3 -0
- package/extensions/orchestrator/state.test.ts +73 -1
- package/extensions/orchestrator/state.ts +95 -23
- package/extensions/orchestrator/suppress-pierre-theme-spam.test.ts +56 -0
- package/extensions/orchestrator/suppress-pierre-theme-spam.ts +45 -0
- package/extensions/orchestrator/validate-artifacts.ts +1 -1
- package/package.json +9 -2
- package/scripts/lib/smoke-resolve.mjs +99 -0
- package/scripts/test-3p.sh +52 -0
- package/scripts/test-package.sh +62 -0
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
loadTask,
|
|
8
8
|
saveTask,
|
|
9
9
|
lockTask,
|
|
10
|
+
validateFromPath,
|
|
10
11
|
getEffectivePhaseMode,
|
|
11
12
|
type TaskType,
|
|
12
13
|
type TaskMode,
|
|
@@ -19,7 +20,7 @@ import { planningSystemPrompt, spawnPlanners } from "./phases/planning.js";
|
|
|
19
20
|
import { implementationSystemPrompt } from "./phases/implementation.js";
|
|
20
21
|
import { reviewSystemPrompt as reviewCycleSystemPrompt } from "./phases/review.js";
|
|
21
22
|
import { reviewSystemPrompt as reviewTaskSystemPrompt } from "./phases/review-task.js";
|
|
22
|
-
import { registerAgentDefinitions, unregisterAgentDefinitions } from "./agents/registry.js";
|
|
23
|
+
import { registerAgentDefinitions, unregisterAgentDefinitions, encodePoolVariant } from "./agents/registry.js";
|
|
23
24
|
import { createExploreAgent } from "./agents/explore.js";
|
|
24
25
|
import { createLibrarianAgent } from "./agents/librarian.js";
|
|
25
26
|
import { createTaskAgent } from "./agents/task.js";
|
|
@@ -57,6 +58,10 @@ export interface ActiveTask {
|
|
|
57
58
|
export class Orchestrator {
|
|
58
59
|
active: ActiveTask | null = null;
|
|
59
60
|
config!: NormalizedPiPiConfig;
|
|
61
|
+
// Non-null when loadConfig threw on session_start. While set, only a minimal
|
|
62
|
+
// read-only /pp path is available and `config` holds a rendering-only default
|
|
63
|
+
// fallback (NOT the user's config, which is invalid).
|
|
64
|
+
configError: string | null = null;
|
|
60
65
|
cwd = "";
|
|
61
66
|
spawnedAgentIds = new Set<string>();
|
|
62
67
|
agentDescriptions = new Map<string, string>();
|
|
@@ -126,6 +131,12 @@ export class Orchestrator {
|
|
|
126
131
|
// legitimately parked on a human is not aborted. Set on dialogue open, cleared
|
|
127
132
|
// in finally on every exit (resolve, ESC/cancel, error).
|
|
128
133
|
interactivePromptOpen = false;
|
|
134
|
+
// One-shot review-ready instruction (item 9). While a menu/ask turn is live,
|
|
135
|
+
// the review-ready banner must NOT be queued as a followUp (ESC/abort flushes
|
|
136
|
+
// the queue into the editor input — the stray-banner bug). Instead it is
|
|
137
|
+
// stashed here and delivered as a FRESH idle-gated turn once the dialogue
|
|
138
|
+
// closes. Cleared after delivery so it fires exactly once.
|
|
139
|
+
pendingReviewReady: string | null = null;
|
|
129
140
|
// Set SYNCHRONOUSLY the moment a sub-429 is detected (before any async dialog),
|
|
130
141
|
// and cleared once the decision resolves. The autonomous planner/reviewer
|
|
131
142
|
// auto-retry consults this to avoid re-spawning a failed variant on the still-
|
|
@@ -288,6 +299,30 @@ export class Orchestrator {
|
|
|
288
299
|
attempt(0);
|
|
289
300
|
}
|
|
290
301
|
|
|
302
|
+
// Deliver the review-ready instruction WITHOUT leaking it into the editor on
|
|
303
|
+
// ESC/abort (item 9). If a menu/ask dialogue is live, the followUp queue would
|
|
304
|
+
// be dumped into the prompt input by restoreQueuedMessagesToEditor on abort —
|
|
305
|
+
// so stash the message and deliver it as a fresh idle-gated turn once the
|
|
306
|
+
// dialogue closes (flushPendingReviewReady). If nothing is open, deliver now.
|
|
307
|
+
deliverReviewReady(text: string): void {
|
|
308
|
+
if (this.interactivePromptOpen) {
|
|
309
|
+
this.pendingReviewReady = text;
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
this.pendingReviewReady = null;
|
|
313
|
+
this.sendUserMessageWhenIdle(text, this.activeTaskToken);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Deliver a stashed review-ready instruction (if any) as a fresh idle-gated
|
|
317
|
+
// turn. One-shot: cleared before sending so it never re-fires on a later idle.
|
|
318
|
+
// Called when a menu/ask dialogue closes (including ESC/abort).
|
|
319
|
+
flushPendingReviewReady(): void {
|
|
320
|
+
const text = this.pendingReviewReady;
|
|
321
|
+
if (!text || !this.active) return;
|
|
322
|
+
this.pendingReviewReady = null;
|
|
323
|
+
this.sendUserMessageWhenIdle(text, this.activeTaskToken);
|
|
324
|
+
}
|
|
325
|
+
|
|
291
326
|
truncateResult(result: string): string {
|
|
292
327
|
const trimmed = result.trim();
|
|
293
328
|
if (!trimmed) return "";
|
|
@@ -474,13 +509,28 @@ export class Orchestrator {
|
|
|
474
509
|
setLogLevel(this.config.general.logLevel);
|
|
475
510
|
ensureGitignore(this.cwd);
|
|
476
511
|
|
|
512
|
+
// Validate the fork source BEFORE creating the new task so an invalid or
|
|
513
|
+
// escaping path cannot leave a half-created task behind. validateFromPath
|
|
514
|
+
// resolves against .pp/state/, so feed it the stateDir-relative form (the
|
|
515
|
+
// same shape stored in state.from) rather than the absolute dir.
|
|
516
|
+
let validatedFromDir: string | undefined;
|
|
517
|
+
if (fromTaskDir) {
|
|
518
|
+
const fromRel = relative(join(this.cwd, ".pp", "state"), fromTaskDir);
|
|
519
|
+
const validation = validateFromPath(this.cwd, fromRel);
|
|
520
|
+
if (!validation.ok) {
|
|
521
|
+
ctx.ui.notify(validation.reason, "error");
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
validatedFromDir = validation.dir;
|
|
525
|
+
}
|
|
526
|
+
|
|
477
527
|
const dir = createTask(this.cwd, type, description, mode);
|
|
478
528
|
const state = loadTask(dir);
|
|
479
529
|
|
|
480
|
-
if (
|
|
481
|
-
const srcUr = join(
|
|
482
|
-
const srcRes = join(
|
|
483
|
-
const srcArtifacts = join(
|
|
530
|
+
if (validatedFromDir) {
|
|
531
|
+
const srcUr = join(validatedFromDir, "USER_REQUEST.md");
|
|
532
|
+
const srcRes = join(validatedFromDir, "RESEARCH.md");
|
|
533
|
+
const srcArtifacts = join(validatedFromDir, "artifacts");
|
|
484
534
|
if (existsSync(srcUr)) {
|
|
485
535
|
const originalUr = readFileSync(srcUr, "utf-8");
|
|
486
536
|
const implNote =
|
|
@@ -501,7 +551,7 @@ export class Orchestrator {
|
|
|
501
551
|
copyFileSync(join(srcArtifacts, f), join(destArtifacts, f));
|
|
502
552
|
}
|
|
503
553
|
}
|
|
504
|
-
state.from = relative(join(this.cwd, ".pp", "state"),
|
|
554
|
+
state.from = relative(join(this.cwd, ".pp", "state"), validatedFromDir);
|
|
505
555
|
if (skipBrainstorm && type === "implement") {
|
|
506
556
|
state.phase = "plan";
|
|
507
557
|
state.initialPhase = "plan";
|
|
@@ -636,6 +686,7 @@ export class Orchestrator {
|
|
|
636
686
|
this.pendingNudges.clear();
|
|
637
687
|
this.phaseStartTime = 0;
|
|
638
688
|
this.userGatePending = false;
|
|
689
|
+
this.pendingReviewReady = null;
|
|
639
690
|
this.failedPlannerVariants = [];
|
|
640
691
|
this.failedReviewerVariants = [];
|
|
641
692
|
this.plannerFailureDialogPending = false;
|
|
@@ -695,9 +746,6 @@ export class Orchestrator {
|
|
|
695
746
|
const explore = createExploreAgent(this.config);
|
|
696
747
|
const librarian = createLibrarianAgent(this.config);
|
|
697
748
|
const taskAgent = createTaskAgent(this.config);
|
|
698
|
-
const advisor = createAdvisorAgent(this.config);
|
|
699
|
-
const deepDebugger = createDeepDebuggerAgent(this.config);
|
|
700
|
-
const reviewer = createReviewerAgent(this.config);
|
|
701
749
|
const phase = this.active?.state.phase;
|
|
702
750
|
const repos = this.active?.state.repos ?? [];
|
|
703
751
|
log.debug({ s: "agents", phase, repoCount: repos.length }, "registering agent definitions");
|
|
@@ -734,27 +782,60 @@ export class Orchestrator {
|
|
|
734
782
|
...taskAgent,
|
|
735
783
|
prompt: appendContext("task", taskAgent.prompt, getModelInfo(resolveModel(this.config.agents.subagents.simple.task.model))),
|
|
736
784
|
},
|
|
737
|
-
|
|
738
|
-
type: "advisor",
|
|
739
|
-
variant: null,
|
|
740
|
-
...advisor,
|
|
741
|
-
prompt: appendContext("advisor", advisor.prompt, getModelInfo(resolveModel(this.config.agents.subagents.simple.advisor.model))),
|
|
742
|
-
},
|
|
743
|
-
{
|
|
744
|
-
type: "deep-debugger",
|
|
745
|
-
variant: null,
|
|
746
|
-
...deepDebugger,
|
|
747
|
-
prompt: appendContext("deep-debugger", deepDebugger.prompt, getModelInfo(resolveModel(this.config.agents.subagents.simple["deep-debugger"].model))),
|
|
748
|
-
},
|
|
749
|
-
{
|
|
750
|
-
type: "reviewer",
|
|
751
|
-
variant: null,
|
|
752
|
-
...reviewer,
|
|
753
|
-
prompt: appendContext("reviewer", reviewer.prompt, getModelInfo(resolveModel(this.config.agents.subagents.simple.reviewer.model))),
|
|
754
|
-
},
|
|
785
|
+
...this.buildPoolAgentDefinitions(appendContext),
|
|
755
786
|
]);
|
|
756
787
|
}
|
|
757
788
|
|
|
789
|
+
// Register one model-named subagent per ENABLED entry in each on-demand pool
|
|
790
|
+
// (advisors / reviewers / deep-debuggers). The variant token encodes the
|
|
791
|
+
// model+thinking so the caller can see exactly what each is; the base `type`
|
|
792
|
+
// (advisor/reviewer/deep-debugger) drives context-file lookup. A collision
|
|
793
|
+
// after name sanitization is skipped (do not silently merge two entries).
|
|
794
|
+
private buildPoolAgentDefinitions(
|
|
795
|
+
appendContext: (agentType: string, prompt: string, modelInfo: { vendor: string; family: string; tier: string }) => string,
|
|
796
|
+
): Array<{ type: string; variant: string; frontmatter: any; prompt: string }> {
|
|
797
|
+
const pools = this.config.agents.subagents.pools;
|
|
798
|
+
const defs: Array<{ type: string; variant: string; frontmatter: any; prompt: string }> = [];
|
|
799
|
+
const seen = new Set<string>();
|
|
800
|
+
const add = (
|
|
801
|
+
baseType: "advisor" | "reviewer" | "deep-debugger",
|
|
802
|
+
entry: { model: string; thinking: string; enabled?: boolean },
|
|
803
|
+
make: (e: { model: string; thinking: string }) => { frontmatter: any; prompt: string },
|
|
804
|
+
) => {
|
|
805
|
+
if (entry.enabled === false) return;
|
|
806
|
+
const variant = encodePoolVariant(resolveModel(entry.model), entry.thinking);
|
|
807
|
+
const name = `${baseType}_${variant}`;
|
|
808
|
+
if (seen.has(name)) {
|
|
809
|
+
getLogger().warn({ s: "agents", name }, "pool entry collides after sanitization; skipping");
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
seen.add(name);
|
|
813
|
+
const agent = make(entry);
|
|
814
|
+
defs.push({
|
|
815
|
+
type: baseType,
|
|
816
|
+
variant,
|
|
817
|
+
frontmatter: agent.frontmatter,
|
|
818
|
+
prompt: appendContext(baseType, agent.prompt, getModelInfo(resolveModel(entry.model))),
|
|
819
|
+
});
|
|
820
|
+
};
|
|
821
|
+
for (const e of pools.advisors) add("advisor", e, createAdvisorAgent);
|
|
822
|
+
for (const e of pools.reviewers) add("reviewer", e, createReviewerAgent);
|
|
823
|
+
for (const e of pools.deepDebuggers) add("deep-debugger", e, createDeepDebuggerAgent);
|
|
824
|
+
return defs;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
// The orchestrator (main-agent) model config that applies to a phase — the
|
|
828
|
+
// source of truth for the thinking level shown in the main agent's identity
|
|
829
|
+
// block. Mirrors the phase→model selection in injectContextAndArtifacts.
|
|
830
|
+
mainAgentConfigForPhase(phase: Phase | undefined): { model: string; thinking: string } {
|
|
831
|
+
const o = this.config.agents.orchestrators;
|
|
832
|
+
if (phase === "debug" && this.active?.type === "debug") return o.debug;
|
|
833
|
+
if (phase === "brainstorm" && this.active?.type === "brainstorm") return o.brainstorm;
|
|
834
|
+
if (phase === "review" && this.active?.type === "review") return o.review;
|
|
835
|
+
if (phase === "plan") return o.plan;
|
|
836
|
+
return o.implement;
|
|
837
|
+
}
|
|
838
|
+
|
|
758
839
|
injectContextAndArtifacts(taskDir: string, phase: Phase): void {
|
|
759
840
|
const log = getLogger();
|
|
760
841
|
log.debug({ s: "context", taskDir, phase }, "injecting context and artifacts");
|
|
@@ -831,6 +912,17 @@ export class Orchestrator {
|
|
|
831
912
|
}
|
|
832
913
|
return [...seen.entries()].filter(([, count]) => count > 1).map(([name]) => name);
|
|
833
914
|
}
|
|
915
|
+
|
|
916
|
+
applySubagentConcurrency(): void {
|
|
917
|
+
const mgr = (globalThis as any)[Symbol.for("pi-subagents:manager")];
|
|
918
|
+
const limit = this.config?.agents?.maxConcurrentSubagents;
|
|
919
|
+
if (typeof mgr?.setMaxConcurrent !== "function" || typeof limit !== "number") {
|
|
920
|
+
getLogger().debug({ s: "config", limit }, "subagents manager unavailable; skipped concurrency apply");
|
|
921
|
+
return;
|
|
922
|
+
}
|
|
923
|
+
mgr.setMaxConcurrent(limit);
|
|
924
|
+
getLogger().debug({ s: "config", limit }, "applied subagent concurrency limit");
|
|
925
|
+
}
|
|
834
926
|
}
|
|
835
927
|
|
|
836
928
|
export function ensureGitignore(cwd: string): void {
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
|
-
import {
|
|
2
|
+
import { mkdtempSync, rmSync } from "fs";
|
|
3
|
+
import { tmpdir } from "os";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
import { brainstormSystemPrompt, spawnBrainstormReviewers } from "./brainstorm.js";
|
|
6
|
+
import { getDefaultConfig } from "../config.js";
|
|
3
7
|
|
|
4
8
|
describe("brainstormSystemPrompt", () => {
|
|
5
9
|
it("debug prompt body is pure procedure (no completion/menu restatements)", () => {
|
|
@@ -16,3 +20,28 @@ describe("brainstormSystemPrompt", () => {
|
|
|
16
20
|
expect(prompt).not.toContain("/pp");
|
|
17
21
|
});
|
|
18
22
|
});
|
|
23
|
+
|
|
24
|
+
describe("spawnBrainstormReviewers missing prerequisites", () => {
|
|
25
|
+
it("emits a displayed error and spawns nothing when artifacts are missing", async () => {
|
|
26
|
+
const taskDir = mkdtempSync(join(tmpdir(), "pi-pi-brainstorm-spawn-"));
|
|
27
|
+
try {
|
|
28
|
+
const sent: any[] = [];
|
|
29
|
+
const send = ((msg: any, _mode: any) => { sent.push(msg); }) as any;
|
|
30
|
+
const result = await spawnBrainstormReviewers(
|
|
31
|
+
{} as any,
|
|
32
|
+
taskDir,
|
|
33
|
+
taskDir,
|
|
34
|
+
"task-id",
|
|
35
|
+
getDefaultConfig(),
|
|
36
|
+
1,
|
|
37
|
+
send,
|
|
38
|
+
);
|
|
39
|
+
expect(result.spawned).toBe(0);
|
|
40
|
+
expect(sent).toHaveLength(1);
|
|
41
|
+
expect(sent[0].customType).toBe("pp-brainstorm-reviews-error");
|
|
42
|
+
expect(sent[0].display).toBe(true);
|
|
43
|
+
} finally {
|
|
44
|
+
rmSync(taskDir, { recursive: true, force: true });
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
});
|
|
@@ -7,12 +7,30 @@ import { createBrainstormReviewerAgent } from "../agents/brainstorm-reviewer.js"
|
|
|
7
7
|
import { getContextDirs, getArtifactManifest } from "../context.js";
|
|
8
8
|
import type { RepoInfo } from "../repo-utils.js";
|
|
9
9
|
import type { TaskType } from "../state.js";
|
|
10
|
+
import { isReviewFileForRound } from "../review-files.js";
|
|
10
11
|
import type { PhaseSend } from "../transition-controller.js";
|
|
11
12
|
|
|
12
13
|
function isEnabled(value: { enabled?: boolean } | undefined): boolean {
|
|
13
14
|
return value?.enabled !== false;
|
|
14
15
|
}
|
|
15
16
|
|
|
17
|
+
// The streamlined interactive-phase flow (design-decisions §3b). driverFamily is
|
|
18
|
+
// the phase's own orchestrator model family; defaultAdvisor is the family-differing
|
|
19
|
+
// advisor to consult by default (Claude-driven → a GPT-family advisor; GPT-driven
|
|
20
|
+
// → a Claude-family advisor). Advisors are model-named pool subagents; pick one
|
|
21
|
+
// from the roster in the delegation guidance.
|
|
22
|
+
export function interactiveFlowBlock(driverFamily: string, defaultAdvisor: string): string {
|
|
23
|
+
return [
|
|
24
|
+
"# Flow (minimize interruptions):",
|
|
25
|
+
"1. CLARIFY UP-FRONT: if the request is ambiguous, ask your clarifying question(s) now, at the very start — batch them. If it's clear, skip straight to step 2.",
|
|
26
|
+
"2. WORK AUTONOMOUSLY: research, explore, and design without stopping to ask. Delegate to subagents (parallel explores for broad searches). Do NOT interrupt mid-flow with questions — collect uncertainties for step 4 instead. Only a genuine blocker (you cannot proceed at all) justifies an ask here.",
|
|
27
|
+
`3. CONSULT AN ADVISOR: before presenting, get an independent second opinion from an advisor whose model family differs from yours. You run on ${driverFamily}, so default to ${defaultAdvisor}. Escalate to more advisors for hard or high-stakes calls.`,
|
|
28
|
+
"4. CLARIFY AT THE END: surface any remaining decisions as focused asks — one at a time.",
|
|
29
|
+
"5. APPROVE COMMITTED SPECIFICS: before finalizing, when your output commits to concrete, costly-to-reverse or opinion-heavy choices — exact wording, structure, naming, default values, or interface signatures — show the ACTUAL proposed text/values inline in your message, then ask for explicit approval. Don't silently invent and bury them.",
|
|
30
|
+
"6. PRESENT RESULTS: end with a structured summary (what you found, the decisions, the recommended direction) and hand back with the standard closing block.",
|
|
31
|
+
].join("\n");
|
|
32
|
+
}
|
|
33
|
+
|
|
16
34
|
export function brainstormSystemPrompt(taskType: TaskType, taskDescription: string, taskDir: string, cwd: string): string {
|
|
17
35
|
const registerReposInstruction = `First, register all git repositories you'll work in using pp_register_repo (including the root: ${cwd}). For each, determine the base branch by examining the current branch and remote tracking.`;
|
|
18
36
|
if (taskType === "debug") {
|
|
@@ -23,7 +41,9 @@ export function brainstormSystemPrompt(taskType: TaskType, taskDescription: stri
|
|
|
23
41
|
registerReposInstruction,
|
|
24
42
|
"",
|
|
25
43
|
"Read-only diagnosis mode. You MAY use write/edit for diagnosis only (repro/test/analysis files) — never to implement the actual fix or feature.",
|
|
26
|
-
|
|
44
|
+
"",
|
|
45
|
+
interactiveFlowBlock("GPT", "a Claude-family advisor"),
|
|
46
|
+
"",
|
|
27
47
|
"# Your job:",
|
|
28
48
|
"1. Clarify the problem with the user if needed",
|
|
29
49
|
"2. Delegate research to subagents where useful (see the delegation guidance in your system prompt), and use bash to run commands, check logs, reproduce issues",
|
|
@@ -54,6 +74,7 @@ export function brainstormSystemPrompt(taskType: TaskType, taskDescription: stri
|
|
|
54
74
|
" ## Open Questions",
|
|
55
75
|
" <Unresolved items needing user input. Omit section if none.>",
|
|
56
76
|
"",
|
|
77
|
+
"This is the LAST interactive phase. If the task continues in autonomous mode, the downstream plan/implement phases cannot ask the user anything — so resolve every Open Question now (answer it, or mark it DECIDED:/ASSUMED: with rationale). Do NOT defer questions to the plan phase.",
|
|
57
78
|
"These files are validated programmatically. Missing sections or unexpected sections will be rejected.",
|
|
58
79
|
"Use pp_write_state_file / pp_edit_state_file (NOT the generic write/edit) for .pp state files — they keep the output compact and validate structure.",
|
|
59
80
|
"",
|
|
@@ -78,11 +99,19 @@ export function brainstormSystemPrompt(taskType: TaskType, taskDescription: stri
|
|
|
78
99
|
"Your primary job is to TALK WITH THE USER. Explore ideas, analyze tradeoffs, answer questions, discuss approaches.",
|
|
79
100
|
"Do NOT rush to produce artifacts or finish. Stay in the conversation until the user is satisfied.",
|
|
80
101
|
"",
|
|
102
|
+
"# Flow (minimize interruptions — the streamlined flow, adapted to conversation):",
|
|
103
|
+
"1. CLARIFY UP-FRONT: if the topic is ambiguous, ask your clarifying question(s) at the very start — batch them into one focused round rather than dripping them out.",
|
|
104
|
+
"2. WORK AUTONOMOUSLY IN THE MIDDLE: once the topic is clear, research/explore/design without stopping to ask. Delegate to subagents (parallel explores for broad searches) and use tools directly for quick lookups. Do NOT interrupt with piecemeal mid-flow questions — collect uncertainties and raise them together at a natural checkpoint.",
|
|
105
|
+
"3. CONSULT AN ADVISOR: before landing on a recommendation, get an independent second opinion from an advisor whose model family differs from yours. You run on Claude, so default to a GPT-family advisor. Escalate to more advisors for hard or high-stakes calls.",
|
|
106
|
+
"4. CLARIFY AT THE END: before finalizing, surface any remaining open decisions as focused asks — one at a time. Don't bury unresolved questions inside an approval prompt or the summary.",
|
|
107
|
+
"5. APPROVE COMMITTED SPECIFICS: when you commit to concrete, costly-to-reverse or opinion-heavy choices — exact wording, structure, naming, default values, or interface signatures — show the ACTUAL proposed text/values inline, then get explicit approval. Don't silently invent and bury them.",
|
|
108
|
+
"6. PRESENT RESULTS: give conclusions as a clear, structured summary (findings, decisions, recommended direction) — don't just dump raw results.",
|
|
109
|
+
"This is still a conversation: the user drives it and may steer at any time. The flow shapes HOW you work between their messages — it does not forbid responding to what they say.",
|
|
110
|
+
"",
|
|
81
111
|
"# How to work:",
|
|
82
|
-
"- Discuss the topic with the user.
|
|
112
|
+
"- Discuss the topic with the user. Propose approaches. Analyze tradeoffs.",
|
|
83
113
|
"- Delegate research to subagents where useful (see the delegation guidance in your system prompt).",
|
|
84
114
|
"- Use tools directly for quick lookups (cbm_search, lsp, ast_search, grep, etc.)",
|
|
85
|
-
"- Present findings to the user and discuss them. Don't just dump raw results.",
|
|
86
115
|
"",
|
|
87
116
|
"# Optional artifacts (only when the conversation naturally produces them):",
|
|
88
117
|
"If the discussion leads to a clear action plan or the user asks you to capture conclusions,",
|
|
@@ -112,6 +141,8 @@ export function brainstormSystemPrompt(taskType: TaskType, taskDescription: stri
|
|
|
112
141
|
"exists, but exploring the design space: weigh the viable approaches and their tradeoffs so the plan phase inherits a clear",
|
|
113
142
|
"direction rather than an open-ended problem.",
|
|
114
143
|
"",
|
|
144
|
+
interactiveFlowBlock("Claude", "a GPT-family advisor"),
|
|
145
|
+
"",
|
|
115
146
|
"# Steps:",
|
|
116
147
|
"1. Clarify requirements with the user if anything is ambiguous",
|
|
117
148
|
"2. Delegate research to subagents where useful (see the delegation guidance in your system prompt) — spawn multiple explores in parallel for broad searches",
|
|
@@ -123,7 +154,7 @@ export function brainstormSystemPrompt(taskType: TaskType, taskDescription: stri
|
|
|
123
154
|
" - ast_search: find structural patterns across the codebase",
|
|
124
155
|
"4. Explore design options: identify the viable approaches, weigh their tradeoffs, and land on a recommended direction (capture the reasoning in RESEARCH.md / an artifact)",
|
|
125
156
|
"5. Ask the user follow-up questions as needed",
|
|
126
|
-
"6. Actively drive every Open Question to resolution — chase down answers via research or by asking the user; the Open Questions section should be empty (or every entry marked DECIDED) before you hand off, not a passive backlog",
|
|
157
|
+
"6. Actively drive every Open Question to resolution — chase down answers via research or by asking the user; the Open Questions section should be empty (or every entry marked DECIDED:/ASSUMED: with rationale) before you hand off, not a passive backlog. This is the LAST interactive phase: if the task continues in autonomous mode, the downstream plan/implement phases cannot ask the user anything, so a deferred question becomes an unanswered one. Resolve or explicitly ASSUME each now — do NOT defer to the plan phase.",
|
|
127
158
|
"7. Write findings into RESEARCH.md as results come back — don't wait for all subagents",
|
|
128
159
|
"8. Keep USER_REQUEST.md current: update it whenever the user's request changes or clarifies, so it reflects what the user actually wants — don't write it once and leave it stale",
|
|
129
160
|
"",
|
|
@@ -170,7 +201,13 @@ export async function spawnBrainstormReviewers(
|
|
|
170
201
|
): Promise<{ spawned: number; files: string[]; agentIds: string[]; failedVariants: string[] }> {
|
|
171
202
|
const urPath = join(taskDir, "USER_REQUEST.md");
|
|
172
203
|
const resPath = join(taskDir, "RESEARCH.md");
|
|
173
|
-
if (!existsSync(urPath) || !existsSync(resPath))
|
|
204
|
+
if (!existsSync(urPath) || !existsSync(resPath)) {
|
|
205
|
+
send(
|
|
206
|
+
{ customType: "pp-brainstorm-reviews-error", content: "Cannot start artifact review: USER_REQUEST.md or RESEARCH.md is missing.", display: true },
|
|
207
|
+
"context",
|
|
208
|
+
);
|
|
209
|
+
return { spawned: 0, files: [], agentIds: [], failedVariants: [] };
|
|
210
|
+
}
|
|
174
211
|
|
|
175
212
|
const userRequest = readFileSync(urPath, "utf-8");
|
|
176
213
|
const research = readFileSync(resPath, "utf-8");
|
|
@@ -243,7 +280,7 @@ export async function spawnBrainstormReviewers(
|
|
|
243
280
|
await Promise.allSettled(results);
|
|
244
281
|
|
|
245
282
|
const reviewOutputFiles = existsSync(reviewsDir)
|
|
246
|
-
? readdirSync(reviewsDir).filter((f) => f
|
|
283
|
+
? readdirSync(reviewsDir).filter((f) => isReviewFileForRound(f, round))
|
|
247
284
|
: [];
|
|
248
285
|
|
|
249
286
|
if (reviewOutputFiles.length > 0) {
|
|
@@ -6,6 +6,8 @@ export function implementationSystemPrompt(taskDir: string, cwd: string): string
|
|
|
6
6
|
"",
|
|
7
7
|
"The plan has been approved. Implement it.",
|
|
8
8
|
"",
|
|
9
|
+
"If you need clarification, batch it early rather than interrupting mid-work. Avoid mid-flight questions; you run off the approved USER_REQUEST/RESEARCH/plan. A genuine blocker is the only reason to stop and ask.",
|
|
10
|
+
"",
|
|
9
11
|
"USER_REQUEST.md, RESEARCH.md, and the synthesized plan are already provided in your context above — do NOT re-read them from disk.",
|
|
10
12
|
"",
|
|
11
13
|
"# Instructions:",
|
|
@@ -2,7 +2,8 @@ import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "fs";
|
|
|
2
2
|
import { tmpdir } from "os";
|
|
3
3
|
import { join } from "path";
|
|
4
4
|
import { afterEach, describe, expect, it } from "vitest";
|
|
5
|
-
import { canTransition, nextPhase, phasePipeline, validateExitCriteria } from "./machine.js";
|
|
5
|
+
import { canTransition, nextPhase, phasePipeline, validateExitCriteria, PLAN_TEMPLATE } from "./machine.js";
|
|
6
|
+
import { PLAN_ALLOWED_SECTIONS } from "../validate-artifacts.js";
|
|
6
7
|
|
|
7
8
|
const tempDirs: string[] = [];
|
|
8
9
|
|
|
@@ -346,3 +347,18 @@ Fix bug.
|
|
|
346
347
|
expect(validateExitCriteria(dir, "quick", "quick")).toEqual({ ok: true });
|
|
347
348
|
});
|
|
348
349
|
});
|
|
350
|
+
|
|
351
|
+
describe("PLAN_TEMPLATE / validator alignment", () => {
|
|
352
|
+
it("only uses section headings the plan validator allows", () => {
|
|
353
|
+
const headings = PLAN_TEMPLATE.split("\n")
|
|
354
|
+
.filter((l) => l.startsWith("## "))
|
|
355
|
+
.map((l) => l.slice(3).trim());
|
|
356
|
+
for (const h of headings) {
|
|
357
|
+
expect(PLAN_ALLOWED_SECTIONS).toContain(h);
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
it("includes the optional Pattern constraints section", () => {
|
|
362
|
+
expect(PLAN_TEMPLATE).toContain("## Pattern constraints");
|
|
363
|
+
});
|
|
364
|
+
});
|
|
@@ -30,7 +30,7 @@ const RESEARCH_TEMPLATE = [
|
|
|
30
30
|
"<Unresolved items needing user input. Omit section if none.>",
|
|
31
31
|
].join("\n");
|
|
32
32
|
|
|
33
|
-
const PLAN_TEMPLATE = [
|
|
33
|
+
export const PLAN_TEMPLATE = [
|
|
34
34
|
"# Plan",
|
|
35
35
|
"",
|
|
36
36
|
"## Scope",
|
|
@@ -39,6 +39,9 @@ const PLAN_TEMPLATE = [
|
|
|
39
39
|
"## Checklist",
|
|
40
40
|
"- [ ] <Outcome> — Done when: <observable condition>",
|
|
41
41
|
"",
|
|
42
|
+
"## Pattern constraints",
|
|
43
|
+
"<Closest existing analog + conventions to mirror. Omit section if none.>",
|
|
44
|
+
"",
|
|
42
45
|
"## Blockers",
|
|
43
46
|
"<Unresolved issues. Omit section if none.>",
|
|
44
47
|
].join("\n");
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { describe, it, expect } from "vitest";
|
|
2
|
-
import {
|
|
2
|
+
import { mkdtempSync, rmSync, writeFileSync } from "fs";
|
|
3
|
+
import { tmpdir } from "os";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
import { planningSystemPrompt, spawnPlanReviewers } from "./planning.js";
|
|
6
|
+
import { getDefaultConfig } from "../config.js";
|
|
3
7
|
|
|
4
8
|
describe("planningSystemPrompt self-complete directive", () => {
|
|
5
9
|
it("guided synthesis instructs the agent to call pp_phase_complete when synthesis is complete", () => {
|
|
@@ -14,3 +18,45 @@ describe("planningSystemPrompt self-complete directive", () => {
|
|
|
14
18
|
expect(prompt).not.toContain("Do NOT instead ask the user to run /pp manually");
|
|
15
19
|
});
|
|
16
20
|
});
|
|
21
|
+
|
|
22
|
+
describe("spawnPlanReviewers missing prerequisites", () => {
|
|
23
|
+
async function run(setup: (dir: string) => void) {
|
|
24
|
+
const taskDir = mkdtempSync(join(tmpdir(), "pi-pi-plan-spawn-"));
|
|
25
|
+
setup(taskDir);
|
|
26
|
+
const sent: any[] = [];
|
|
27
|
+
const send = ((msg: any, _mode: any) => { sent.push(msg); }) as any;
|
|
28
|
+
try {
|
|
29
|
+
const result = await spawnPlanReviewers(
|
|
30
|
+
{} as any,
|
|
31
|
+
taskDir,
|
|
32
|
+
taskDir,
|
|
33
|
+
"task-id",
|
|
34
|
+
getDefaultConfig(),
|
|
35
|
+
1,
|
|
36
|
+
send,
|
|
37
|
+
);
|
|
38
|
+
return { result, sent };
|
|
39
|
+
} finally {
|
|
40
|
+
rmSync(taskDir, { recursive: true, force: true });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
it("emits a displayed error when USER_REQUEST/RESEARCH are missing", async () => {
|
|
45
|
+
const { result, sent } = await run(() => {});
|
|
46
|
+
expect(result.spawned).toBe(0);
|
|
47
|
+
expect(sent).toHaveLength(1);
|
|
48
|
+
expect(sent[0].customType).toBe("pp-plan-reviews-error");
|
|
49
|
+
expect(sent[0].display).toBe(true);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("emits a displayed error when the synthesized plan is missing", async () => {
|
|
53
|
+
const { result, sent } = await run((dir) => {
|
|
54
|
+
writeFileSync(join(dir, "USER_REQUEST.md"), "# User Request\n", "utf-8");
|
|
55
|
+
writeFileSync(join(dir, "RESEARCH.md"), "## Affected Code\n", "utf-8");
|
|
56
|
+
});
|
|
57
|
+
expect(result.spawned).toBe(0);
|
|
58
|
+
expect(sent).toHaveLength(1);
|
|
59
|
+
expect(sent[0].customType).toBe("pp-plan-reviews-error");
|
|
60
|
+
expect(sent[0].content).toContain("no synthesized plan");
|
|
61
|
+
});
|
|
62
|
+
});
|
|
@@ -8,6 +8,7 @@ import { createPlanReviewerAgent } from "../agents/plan-reviewer.js";
|
|
|
8
8
|
import { getContextDirs, getLatestSynthesizedPlan, getArtifactManifest } from "../context.js";
|
|
9
9
|
import type { RepoInfo } from "../repo-utils.js";
|
|
10
10
|
import { validatePlan } from "../validate-artifacts.js";
|
|
11
|
+
import { isReviewFileForRound } from "../review-files.js";
|
|
11
12
|
import type { TaskMode } from "../state.js";
|
|
12
13
|
import type { PhaseSend } from "../transition-controller.js";
|
|
13
14
|
|
|
@@ -28,6 +29,8 @@ export function planningSystemPrompt(taskDir: string, mode: TaskMode): string {
|
|
|
28
29
|
return [
|
|
29
30
|
"[PI-PI — PLAN PHASE]",
|
|
30
31
|
"",
|
|
32
|
+
"If you need clarification, batch it early rather than interrupting mid-work. Avoid mid-flight questions; you run off the approved USER_REQUEST/RESEARCH/plan. A genuine blocker is the only reason to stop and ask.",
|
|
33
|
+
"",
|
|
31
34
|
"You are a SYNTHESIZER: you MERGE the planner outputs into one plan. Do NOT write your own plan from scratch.",
|
|
32
35
|
"Planning subagents are working in parallel to create plans.",
|
|
33
36
|
`They will write their outputs to ${plansDir}/. Wait for the notification that all planners completed.`,
|
|
@@ -190,12 +193,24 @@ export async function spawnPlanReviewers(
|
|
|
190
193
|
): Promise<{ spawned: number; files: string[]; agentIds: string[]; failedVariants: string[] }> {
|
|
191
194
|
const urPath = join(taskDir, "USER_REQUEST.md");
|
|
192
195
|
const resPath = join(taskDir, "RESEARCH.md");
|
|
193
|
-
if (!existsSync(urPath) || !existsSync(resPath))
|
|
196
|
+
if (!existsSync(urPath) || !existsSync(resPath)) {
|
|
197
|
+
send(
|
|
198
|
+
{ customType: "pp-plan-reviews-error", content: "Cannot start plan review: USER_REQUEST.md or RESEARCH.md is missing.", display: true },
|
|
199
|
+
"context",
|
|
200
|
+
);
|
|
201
|
+
return { spawned: 0, files: [], agentIds: [], failedVariants: [] };
|
|
202
|
+
}
|
|
194
203
|
|
|
195
204
|
const userRequest = readFileSync(urPath, "utf-8");
|
|
196
205
|
const research = readFileSync(resPath, "utf-8");
|
|
197
206
|
const synthesizedPlan = getLatestSynthesizedPlan(taskDir);
|
|
198
|
-
if (!synthesizedPlan)
|
|
207
|
+
if (!synthesizedPlan) {
|
|
208
|
+
send(
|
|
209
|
+
{ customType: "pp-plan-reviews-error", content: "Cannot start plan review: no synthesized plan found.", display: true },
|
|
210
|
+
"context",
|
|
211
|
+
);
|
|
212
|
+
return { spawned: 0, files: [], agentIds: [], failedVariants: [] };
|
|
213
|
+
}
|
|
199
214
|
|
|
200
215
|
const planReviewsDir = join(taskDir, "plan-reviews");
|
|
201
216
|
if (!existsSync(planReviewsDir)) {
|
|
@@ -258,7 +273,7 @@ export async function spawnPlanReviewers(
|
|
|
258
273
|
await Promise.allSettled(results);
|
|
259
274
|
|
|
260
275
|
const actualReviewFiles = existsSync(planReviewsDir)
|
|
261
|
-
? readdirSync(planReviewsDir).filter((f) => f.startsWith(`${timestamp}`) && f
|
|
276
|
+
? readdirSync(planReviewsDir).filter((f) => f.startsWith(`${timestamp}`) && isReviewFileForRound(f, pass))
|
|
262
277
|
: [];
|
|
263
278
|
|
|
264
279
|
if (actualReviewFiles.length > 0) {
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
+
import { interactiveFlowBlock } from "./brainstorm.js";
|
|
2
|
+
|
|
1
3
|
export function reviewSystemPrompt(taskDir: string, cwd: string): string {
|
|
2
4
|
return [
|
|
3
5
|
"[PI-PI — REVIEW PHASE]",
|
|
4
6
|
"",
|
|
5
7
|
`First, register all git repositories you'll work in using pp_register_repo (including the root: ${cwd}). For each, determine the base branch by examining the current branch and remote tracking.`,
|
|
6
8
|
"",
|
|
9
|
+
interactiveFlowBlock("Claude", "a GPT-family advisor"),
|
|
10
|
+
"",
|
|
7
11
|
"You are reviewing code changes. The user will describe what to review in their first chat message",
|
|
8
12
|
"(e.g. a branch, a commit range, uncommitted changes, or a GitHub PR URL). There is NO pre-seeded",
|
|
9
13
|
"USER_REQUEST.md — you must capture the user's request and your findings into the state files yourself.",
|
|
@@ -38,6 +42,7 @@ export function reviewSystemPrompt(taskDir: string, cwd: string): string {
|
|
|
38
42
|
" ## Open Questions",
|
|
39
43
|
" <Low-confidence concerns or items needing user input. Omit section if none.>",
|
|
40
44
|
"",
|
|
45
|
+
"This is the LAST interactive phase. If the task continues in autonomous mode, the downstream plan/implement phases cannot ask the user anything — so resolve every Open Question now (answer it, or mark it DECIDED:/ASSUMED: with rationale). Do NOT defer questions to the plan phase.",
|
|
41
46
|
"These files are validated programmatically. Missing sections or unexpected sections will be rejected.",
|
|
42
47
|
"Use pp_write_state_file / pp_edit_state_file (NOT the generic write/edit) for these .pp state files — they keep the output compact and validate structure.",
|
|
43
48
|
"Keep USER_REQUEST.md current: update it whenever the user clarifies the review scope, so it never goes stale.",
|
|
@@ -23,6 +23,16 @@ describe("reviewSystemPrompt apply_feedback wording", () => {
|
|
|
23
23
|
expect(auto).not.toContain("pp_phase_complete");
|
|
24
24
|
});
|
|
25
25
|
|
|
26
|
+
it("debug uses the artifact-review branch: DEBUG header, brainstorm-reviews dir, no code-review tail", () => {
|
|
27
|
+
const prompt = reviewSystemPrompt("/tmp/task", 1, "debug", "guided");
|
|
28
|
+
expect(prompt).toContain("DEBUG REVIEW CYCLE");
|
|
29
|
+
expect(prompt).toContain("Debug reviewer outputs are ready.");
|
|
30
|
+
expect(prompt).toContain("/tmp/task/brainstorm-reviews/");
|
|
31
|
+
expect(prompt).not.toContain("code-reviews");
|
|
32
|
+
expect(prompt).not.toContain("Create a fix plan");
|
|
33
|
+
expect(prompt).not.toContain("pp_phase_complete");
|
|
34
|
+
});
|
|
35
|
+
|
|
26
36
|
it("brainstorm apply-feedback prompt permits artifact updates and steers to the compact tools", () => {
|
|
27
37
|
const prompt = reviewSystemPrompt("/tmp/task", 1, "brainstorm", "autonomous");
|
|
28
38
|
expect(prompt).toContain("artifacts/");
|