@ilya-lesikov/pi-pi 0.5.0 → 0.7.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 +65 -49
- package/3p/pi-subagents/src/agent-manager.ts +8 -0
- package/3p/pi-subagents/src/agent-runner.ts +112 -19
- package/3p/pi-subagents/src/index.ts +3 -0
- package/extensions/orchestrator/agents/brainstorm-reviewer.ts +32 -19
- package/extensions/orchestrator/agents/code-reviewer.ts +31 -17
- package/extensions/orchestrator/agents/constraints.ts +55 -0
- package/extensions/orchestrator/agents/explore.ts +13 -13
- package/extensions/orchestrator/agents/librarian.ts +12 -9
- package/extensions/orchestrator/agents/plan-reviewer.ts +31 -19
- package/extensions/orchestrator/agents/planner.ts +28 -23
- package/extensions/orchestrator/agents/registry.ts +2 -1
- package/extensions/orchestrator/agents/repo-context.ts +11 -0
- package/extensions/orchestrator/agents/task.ts +14 -11
- package/extensions/orchestrator/agents/tool-routing.ts +17 -32
- package/extensions/orchestrator/ast-search.ts +2 -1
- package/extensions/orchestrator/cbm.test.ts +35 -0
- package/extensions/orchestrator/cbm.ts +43 -13
- package/extensions/orchestrator/command-handlers.test.ts +390 -19
- package/extensions/orchestrator/command-handlers.ts +68 -28
- package/extensions/orchestrator/commands.test.ts +255 -2
- package/extensions/orchestrator/commands.ts +108 -10
- package/extensions/orchestrator/config.test.ts +289 -68
- package/extensions/orchestrator/config.ts +631 -121
- package/extensions/orchestrator/context.test.ts +177 -10
- package/extensions/orchestrator/context.ts +115 -14
- package/extensions/orchestrator/custom-footer.ts +7 -3
- package/extensions/orchestrator/doctor.test.ts +561 -0
- package/extensions/orchestrator/doctor.ts +702 -0
- package/extensions/orchestrator/event-handlers.test.ts +84 -22
- package/extensions/orchestrator/event-handlers.ts +1220 -343
- package/extensions/orchestrator/exa.test.ts +46 -0
- package/extensions/orchestrator/exa.ts +16 -10
- package/extensions/orchestrator/flant-infra.test.ts +511 -0
- package/extensions/orchestrator/flant-infra.ts +390 -49
- package/extensions/orchestrator/index.ts +13 -2
- package/extensions/orchestrator/integration.test.ts +3065 -118
- package/extensions/orchestrator/log.test.ts +219 -0
- package/extensions/orchestrator/log.ts +153 -0
- package/extensions/orchestrator/model-registry.test.ts +302 -0
- package/extensions/orchestrator/model-registry.ts +298 -0
- package/extensions/orchestrator/model-version.test.ts +27 -0
- package/extensions/orchestrator/model-version.ts +19 -0
- package/extensions/orchestrator/orchestrator.test.ts +206 -56
- package/extensions/orchestrator/orchestrator.ts +298 -140
- package/extensions/orchestrator/phases/brainstorm.test.ts +10 -7
- package/extensions/orchestrator/phases/brainstorm.ts +41 -36
- package/extensions/orchestrator/phases/implementation.ts +7 -11
- package/extensions/orchestrator/phases/machine.test.ts +27 -8
- package/extensions/orchestrator/phases/machine.ts +13 -0
- package/extensions/orchestrator/phases/planning.ts +57 -31
- package/extensions/orchestrator/phases/review-task.ts +3 -7
- package/extensions/orchestrator/phases/review.test.ts +54 -0
- package/extensions/orchestrator/phases/review.ts +89 -48
- package/extensions/orchestrator/phases/spawn-blocking.test.ts +69 -0
- package/extensions/orchestrator/phases/verdict.test.ts +139 -0
- package/extensions/orchestrator/phases/verdict.ts +82 -0
- package/extensions/orchestrator/plannotator.test.ts +85 -0
- package/extensions/orchestrator/plannotator.ts +24 -6
- package/extensions/orchestrator/pp-menu.test.ts +207 -0
- package/extensions/orchestrator/pp-menu.ts +2741 -373
- package/extensions/orchestrator/repo-utils.test.ts +151 -0
- package/extensions/orchestrator/repo-utils.ts +67 -0
- package/extensions/orchestrator/spawn-cleanup.test.ts +57 -0
- package/extensions/orchestrator/spawn-cleanup.ts +35 -0
- package/extensions/orchestrator/state.test.ts +76 -6
- package/extensions/orchestrator/state.ts +128 -44
- package/extensions/orchestrator/subagent-session-marker.test.ts +36 -0
- package/extensions/orchestrator/test-helpers.ts +229 -0
- package/extensions/orchestrator/tracer.test.ts +132 -0
- package/extensions/orchestrator/tracer.ts +127 -0
- package/extensions/orchestrator/transition-controller.test.ts +207 -0
- package/extensions/orchestrator/transition-controller.ts +259 -0
- package/extensions/orchestrator/usage-tracker.test.ts +563 -0
- package/extensions/orchestrator/usage-tracker.ts +96 -12
- package/extensions/orchestrator/validate-artifacts.test.ts +83 -1
- package/package.json +2 -1
|
@@ -2,14 +2,17 @@ import { describe, expect, it } from "vitest";
|
|
|
2
2
|
import { brainstormSystemPrompt } from "./brainstorm.js";
|
|
3
3
|
|
|
4
4
|
describe("brainstormSystemPrompt", () => {
|
|
5
|
-
it("debug prompt
|
|
6
|
-
const prompt = brainstormSystemPrompt("debug", "fix a bug", "/tmp/task");
|
|
7
|
-
expect(prompt).toContain("
|
|
8
|
-
expect(prompt).not.toContain("
|
|
5
|
+
it("debug prompt body is pure procedure (no completion/menu restatements)", () => {
|
|
6
|
+
const prompt = brainstormSystemPrompt("debug", "fix a bug", "/tmp/task", "/tmp");
|
|
7
|
+
expect(prompt).toContain("DEBUG PHASE");
|
|
8
|
+
expect(prompt).not.toContain("pp_phase_complete");
|
|
9
|
+
expect(prompt).not.toContain("/pp");
|
|
9
10
|
});
|
|
10
11
|
|
|
11
|
-
it("brainstorm prompt
|
|
12
|
-
const prompt = brainstormSystemPrompt("brainstorm", "explore ideas", "/tmp/task");
|
|
13
|
-
expect(prompt).toContain("
|
|
12
|
+
it("brainstorm prompt body is pure procedure (no completion/menu restatements)", () => {
|
|
13
|
+
const prompt = brainstormSystemPrompt("brainstorm", "explore ideas", "/tmp/task", "/tmp");
|
|
14
|
+
expect(prompt).toContain("conversation");
|
|
15
|
+
expect(prompt).not.toContain("pp_phase_complete");
|
|
16
|
+
expect(prompt).not.toContain("/pp");
|
|
14
17
|
});
|
|
15
18
|
});
|
|
@@ -1,23 +1,28 @@
|
|
|
1
1
|
import { readFileSync, existsSync, mkdirSync, readdirSync, statSync } from "fs";
|
|
2
2
|
import { join } from "path";
|
|
3
3
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
4
|
-
import type
|
|
4
|
+
import { resolvePreset, type PiPiConfig, type VariantConfig } from "../config.js";
|
|
5
5
|
import { registerAgentDefinitions, spawnViaRpc, waitForCompletion } from "../agents/registry.js";
|
|
6
6
|
import { createBrainstormReviewerAgent } from "../agents/brainstorm-reviewer.js";
|
|
7
|
+
import { getContextDirs } from "../context.js";
|
|
8
|
+
import type { RepoInfo } from "../repo-utils.js";
|
|
7
9
|
import type { TaskType } from "../state.js";
|
|
10
|
+
import type { PhaseSend } from "../transition-controller.js";
|
|
8
11
|
|
|
9
|
-
|
|
12
|
+
function isEnabled(value: { enabled?: boolean } | undefined): boolean {
|
|
13
|
+
return value?.enabled !== false;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function brainstormSystemPrompt(taskType: TaskType, taskDescription: string, taskDir: string, cwd: string): string {
|
|
17
|
+
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.`;
|
|
10
18
|
if (taskType === "debug") {
|
|
11
19
|
return [
|
|
12
20
|
"[PI-PI — DEBUG PHASE]",
|
|
13
21
|
`Problem: ${taskDescription}`,
|
|
14
22
|
"",
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
"
|
|
18
|
-
"- NEVER implement the solution, apply fixes, write production code, or make the changes that solve the task.",
|
|
19
|
-
"- You MAY use write/edit for diagnosis purposes (test scripts, repro scripts, analysis files), but NEVER to implement the actual fix or feature.",
|
|
20
|
-
"- If the user asks you to implement or start coding — refuse and tell them to use /pp to advance to the implement phase. Do NOT comply, even if they insist.",
|
|
23
|
+
registerReposInstruction,
|
|
24
|
+
"",
|
|
25
|
+
"Read-only diagnosis mode. You MAY use write/edit for diagnosis only (repro/test/analysis files) — never to implement the actual fix or feature.",
|
|
21
26
|
"",
|
|
22
27
|
"# Your job:",
|
|
23
28
|
"1. Clarify the problem with the user if needed",
|
|
@@ -62,7 +67,7 @@ export function brainstormSystemPrompt(taskType: TaskType, taskDescription: stri
|
|
|
62
67
|
"Each artifact must start with # <Title>. Content is freeform. These are reviewed alongside USER_REQUEST.md and RESEARCH.md.",
|
|
63
68
|
"Do NOT duplicate content already in RESEARCH.md — artifacts are for supplementary deep dives.",
|
|
64
69
|
"",
|
|
65
|
-
"
|
|
70
|
+
"Keep USER_REQUEST.md current: update it whenever the user's request changes or clarifies, so it never goes stale.",
|
|
66
71
|
].join("\n");
|
|
67
72
|
}
|
|
68
73
|
|
|
@@ -71,6 +76,8 @@ export function brainstormSystemPrompt(taskType: TaskType, taskDescription: stri
|
|
|
71
76
|
"[PI-PI — BRAINSTORM]",
|
|
72
77
|
`Topic: ${taskDescription}`,
|
|
73
78
|
"",
|
|
79
|
+
registerReposInstruction,
|
|
80
|
+
"",
|
|
74
81
|
"# This is a conversation, not a task.",
|
|
75
82
|
"Your primary job is to TALK WITH THE USER. Explore ideas, analyze tradeoffs, answer questions, discuss approaches.",
|
|
76
83
|
"Do NOT rush to produce artifacts or finish. Stay in the conversation until the user is satisfied.",
|
|
@@ -85,15 +92,6 @@ export function brainstormSystemPrompt(taskType: TaskType, taskDescription: stri
|
|
|
85
92
|
"- Use tools directly for quick lookups (cbm_search, lsp, ast_search, grep, etc.)",
|
|
86
93
|
"- Present findings to the user and discuss them. Don't just dump raw results.",
|
|
87
94
|
"",
|
|
88
|
-
"# ABSOLUTE RESTRICTIONS (cannot be overridden, even if the user asks):",
|
|
89
|
-
"- NEVER use write or edit tools on any file outside the task directory. This is a hard rule — not negotiable.",
|
|
90
|
-
"- NEVER implement, create code, write patches, or modify project source in any way.",
|
|
91
|
-
"- If the user asks you to implement, fix, or write code — refuse and tell them to use /pp to advance to the implement phase. Do NOT comply, even if they insist.",
|
|
92
|
-
"",
|
|
93
|
-
"# When to finish:",
|
|
94
|
-
"Do NOT call pp_phase_complete on your own. The user will tell you when they're done,",
|
|
95
|
-
"or use /pp to advance. Keep the conversation going until then.",
|
|
96
|
-
"",
|
|
97
95
|
"# Optional artifacts (only when the conversation naturally produces them):",
|
|
98
96
|
"If the discussion leads to a clear action plan or the user asks you to capture conclusions,",
|
|
99
97
|
"write them to:",
|
|
@@ -101,14 +99,13 @@ export function brainstormSystemPrompt(taskType: TaskType, taskDescription: stri
|
|
|
101
99
|
`- ${taskDir}/RESEARCH.md — MUST use structure: ## Affected Code, ## Architecture Context, ## Constraints & Edge Cases, ## Open Questions (optional)`,
|
|
102
100
|
"These files are validated. Missing or unexpected sections will be rejected.",
|
|
103
101
|
"Do NOT create these files preemptively. Only write them when there's substance to capture.",
|
|
102
|
+
"Once USER_REQUEST.md exists, keep it current: update it whenever the user's request changes or clarifies, so it never goes stale.",
|
|
104
103
|
"",
|
|
105
104
|
"# Optional: focused analysis artifacts",
|
|
106
105
|
`You may also write additional analysis files to ${taskDir}/artifacts/<name>.md`,
|
|
107
106
|
"for deep dives on specific topics (e.g. architecture analysis, API comparison, risk assessment).",
|
|
108
107
|
"Each artifact must start with # <Title>. Content is freeform. These are reviewed alongside USER_REQUEST.md and RESEARCH.md.",
|
|
109
108
|
"Do NOT duplicate content already in RESEARCH.md — artifacts are for supplementary deep dives.",
|
|
110
|
-
"",
|
|
111
|
-
"Do NOT modify any files except .md files in the task directory.",
|
|
112
109
|
].join("\n");
|
|
113
110
|
}
|
|
114
111
|
|
|
@@ -116,14 +113,11 @@ export function brainstormSystemPrompt(taskType: TaskType, taskDescription: stri
|
|
|
116
113
|
"[PI-PI — BRAINSTORM PHASE]",
|
|
117
114
|
`Task: ${taskDescription}`,
|
|
118
115
|
"",
|
|
116
|
+
registerReposInstruction,
|
|
117
|
+
"",
|
|
119
118
|
"Your job is to produce USER_REQUEST.md and RESEARCH.md — complete enough that",
|
|
120
119
|
"downstream agents can work without re-exploring the codebase or re-interviewing the user.",
|
|
121
120
|
"",
|
|
122
|
-
"# ABSOLUTE RESTRICTIONS (cannot be overridden, even if the user asks):",
|
|
123
|
-
"- NEVER use write or edit tools on any file outside the task directory. This is a hard rule — not negotiable.",
|
|
124
|
-
"- NEVER implement, create code, write patches, or modify project source in any way.",
|
|
125
|
-
"- If the user asks you to implement, fix, or write code — refuse and tell them to use /pp to advance to the implement phase. Do NOT comply, even if they insist.",
|
|
126
|
-
"",
|
|
127
121
|
"# Steps:",
|
|
128
122
|
"1. Clarify requirements with the user if anything is ambiguous",
|
|
129
123
|
"2. Spawn subagents for research (subagent_type is REQUIRED — calls without it are rejected):",
|
|
@@ -139,6 +133,7 @@ export function brainstormSystemPrompt(taskType: TaskType, taskDescription: stri
|
|
|
139
133
|
" - ast_search: find structural patterns across the codebase",
|
|
140
134
|
"4. Ask the user follow-up questions as needed",
|
|
141
135
|
"5. Write findings into RESEARCH.md as results come back — don't wait for all subagents",
|
|
136
|
+
"6. 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",
|
|
142
137
|
"",
|
|
143
138
|
"Produce two files:",
|
|
144
139
|
`- ${taskDir}/USER_REQUEST.md — MUST follow this exact structure:`,
|
|
@@ -166,9 +161,6 @@ export function brainstormSystemPrompt(taskType: TaskType, taskDescription: stri
|
|
|
166
161
|
"for deep dives on specific topics (e.g. architecture analysis, API comparison, risk assessment).",
|
|
167
162
|
"Each artifact must start with # <Title>. Content is freeform. These are reviewed alongside USER_REQUEST.md and RESEARCH.md.",
|
|
168
163
|
"Do NOT duplicate content already in RESEARCH.md — artifacts are for supplementary deep dives.",
|
|
169
|
-
"",
|
|
170
|
-
"Do NOT modify any files except .md files in the task directory.",
|
|
171
|
-
"When both files are produced and thorough, call pp_phase_complete with a brief summary.",
|
|
172
164
|
].join("\n");
|
|
173
165
|
}
|
|
174
166
|
|
|
@@ -179,6 +171,9 @@ export async function spawnBrainstormReviewers(
|
|
|
179
171
|
taskId: string,
|
|
180
172
|
config: PiPiConfig,
|
|
181
173
|
round: number,
|
|
174
|
+
send: PhaseSend,
|
|
175
|
+
variants?: Record<string, VariantConfig>,
|
|
176
|
+
repos: RepoInfo[] = [],
|
|
182
177
|
): Promise<{ spawned: number; files: string[]; agentIds: string[]; failedVariants: string[] }> {
|
|
183
178
|
const urPath = join(taskDir, "USER_REQUEST.md");
|
|
184
179
|
const resPath = join(taskDir, "RESEARCH.md");
|
|
@@ -201,7 +196,9 @@ export async function spawnBrainstormReviewers(
|
|
|
201
196
|
}
|
|
202
197
|
|
|
203
198
|
const timestamp = Math.floor(Date.now() / 1000);
|
|
204
|
-
const
|
|
199
|
+
const reviewerVariants = variants ?? resolvePreset(config, "brainstormReviewers");
|
|
200
|
+
const enabledVariants = Object.entries(reviewerVariants).filter(([, v]) => isEnabled(v));
|
|
201
|
+
const contextDirs = getContextDirs(cwd, repos, config.general.loadExtraRepoConfigs);
|
|
205
202
|
const reviewFiles: string[] = [];
|
|
206
203
|
const agentIds: string[] = [];
|
|
207
204
|
const failedVariants: string[] = [];
|
|
@@ -210,7 +207,15 @@ export async function spawnBrainstormReviewers(
|
|
|
210
207
|
for (const [variant] of enabledVariants) {
|
|
211
208
|
const outputPath = join(reviewsDir, `${timestamp}_${variant}_round-${round}.md`);
|
|
212
209
|
reviewFiles.push(outputPath);
|
|
213
|
-
const agent = createBrainstormReviewerAgent(
|
|
210
|
+
const agent = createBrainstormReviewerAgent(
|
|
211
|
+
variant,
|
|
212
|
+
reviewerVariants,
|
|
213
|
+
{ userRequest, research, artifacts: artifacts.length > 0 ? artifacts : undefined },
|
|
214
|
+
outputPath,
|
|
215
|
+
contextDirs,
|
|
216
|
+
"brainstorm",
|
|
217
|
+
repos,
|
|
218
|
+
);
|
|
214
219
|
|
|
215
220
|
registerAgentDefinitions(pi, [{ type: "brainstorm_reviewer", variant, ...agent }]);
|
|
216
221
|
|
|
@@ -229,13 +234,13 @@ export async function spawnBrainstormReviewers(
|
|
|
229
234
|
await waitForCompletion(pi, id);
|
|
230
235
|
} catch (err: any) {
|
|
231
236
|
failedVariants.push(variant);
|
|
232
|
-
|
|
237
|
+
send(
|
|
233
238
|
{
|
|
234
239
|
customType: "pp-brainstorm-reviewer-error",
|
|
235
240
|
content: `Brainstorm reviewer variant "${variant}" failed: ${err.message}`,
|
|
236
241
|
display: true,
|
|
237
242
|
},
|
|
238
|
-
|
|
243
|
+
"context",
|
|
239
244
|
);
|
|
240
245
|
}
|
|
241
246
|
})(),
|
|
@@ -249,7 +254,7 @@ export async function spawnBrainstormReviewers(
|
|
|
249
254
|
: [];
|
|
250
255
|
|
|
251
256
|
if (reviewOutputFiles.length > 0) {
|
|
252
|
-
|
|
257
|
+
send(
|
|
253
258
|
{
|
|
254
259
|
customType: "pp-brainstorm-reviews-done",
|
|
255
260
|
content: [
|
|
@@ -260,10 +265,10 @@ export async function spawnBrainstormReviewers(
|
|
|
260
265
|
].join("\n"),
|
|
261
266
|
display: true,
|
|
262
267
|
},
|
|
263
|
-
|
|
268
|
+
"context",
|
|
264
269
|
);
|
|
265
270
|
} else if (enabledVariants.length > 0) {
|
|
266
|
-
|
|
271
|
+
send(
|
|
267
272
|
{
|
|
268
273
|
customType: "pp-brainstorm-reviews-error",
|
|
269
274
|
content: [
|
|
@@ -272,7 +277,7 @@ export async function spawnBrainstormReviewers(
|
|
|
272
277
|
].join("\n"),
|
|
273
278
|
display: true,
|
|
274
279
|
},
|
|
275
|
-
|
|
280
|
+
"context",
|
|
276
281
|
);
|
|
277
282
|
}
|
|
278
283
|
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
export function implementationSystemPrompt(taskDir: string): string {
|
|
1
|
+
export function implementationSystemPrompt(taskDir: string, cwd: string): string {
|
|
2
2
|
return [
|
|
3
3
|
"[PI-PI — IMPLEMENT PHASE]",
|
|
4
4
|
"",
|
|
5
|
-
|
|
5
|
+
`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
6
|
"",
|
|
7
|
-
"
|
|
8
|
-
"- Do NOT skip plan items without the user's approval",
|
|
7
|
+
"The plan has been approved. Implement it.",
|
|
9
8
|
"",
|
|
10
9
|
"USER_REQUEST.md, RESEARCH.md, and the synthesized plan are already provided in your context above — do NOT re-read them from disk.",
|
|
11
10
|
"",
|
|
@@ -28,15 +27,12 @@ export function implementationSystemPrompt(taskDir: string): string {
|
|
|
28
27
|
' - Agent(subagent_type="Task", ...) — parallelizable, self-contained implementation subtasks.',
|
|
29
28
|
" Spawn multiple Explore agents in parallel for broad searches.",
|
|
30
29
|
"",
|
|
31
|
-
"If a fix fails 3 times, STOP and re-plan the approach — do not keep pushing.",
|
|
32
|
-
"",
|
|
33
30
|
"# Commits:",
|
|
34
31
|
"After completing a logical unit of work (a plan item, a bug fix, a test), call pp_commit",
|
|
35
|
-
"with a descriptive message (what changed and why).
|
|
36
|
-
"",
|
|
37
|
-
"
|
|
38
|
-
"- Fix issues found by lsp diagnostics before moving on",
|
|
32
|
+
"with a descriptive message (what changed and why). Prefix it with a conventional-commit",
|
|
33
|
+
"type (fix:, feat:, or chore:) unless the user asked for a different commit style.",
|
|
34
|
+
"Don't batch all changes into one commit.",
|
|
39
35
|
"",
|
|
40
|
-
"
|
|
36
|
+
"Fix issues found by lsp diagnostics before moving on.",
|
|
41
37
|
].join("\n");
|
|
42
38
|
}
|
|
@@ -63,16 +63,21 @@ describe("canTransition", () => {
|
|
|
63
63
|
});
|
|
64
64
|
|
|
65
65
|
it("handles debug transitions", () => {
|
|
66
|
-
expect(canTransition("debug", "debug", "
|
|
67
|
-
expect(canTransition("debug", "debug", "
|
|
66
|
+
expect(canTransition("debug", "debug", "plan")).toBe(true);
|
|
67
|
+
expect(canTransition("debug", "debug", "done")).toBe(false);
|
|
68
68
|
expect(canTransition("debug", "done", "debug")).toBe(false);
|
|
69
69
|
});
|
|
70
70
|
|
|
71
71
|
it("handles brainstorm transitions", () => {
|
|
72
|
-
expect(canTransition("brainstorm", "brainstorm", "
|
|
73
|
-
expect(canTransition("brainstorm", "brainstorm", "
|
|
72
|
+
expect(canTransition("brainstorm", "brainstorm", "plan")).toBe(true);
|
|
73
|
+
expect(canTransition("brainstorm", "brainstorm", "done")).toBe(false);
|
|
74
74
|
expect(canTransition("brainstorm", "done", "brainstorm")).toBe(false);
|
|
75
75
|
});
|
|
76
|
+
|
|
77
|
+
it("handles quick transitions", () => {
|
|
78
|
+
expect(canTransition("quick", "quick", "done")).toBe(true);
|
|
79
|
+
expect(canTransition("quick", "quick", "implement")).toBe(false);
|
|
80
|
+
});
|
|
76
81
|
});
|
|
77
82
|
|
|
78
83
|
describe("nextPhase", () => {
|
|
@@ -84,14 +89,19 @@ describe("nextPhase", () => {
|
|
|
84
89
|
});
|
|
85
90
|
|
|
86
91
|
it("returns debug next phase and terminal null", () => {
|
|
87
|
-
expect(nextPhase("debug", "debug")).toBe("
|
|
92
|
+
expect(nextPhase("debug", "debug")).toBe("plan");
|
|
88
93
|
expect(nextPhase("debug", "done")).toBeNull();
|
|
89
94
|
});
|
|
90
95
|
|
|
91
96
|
it("returns brainstorm next phase and terminal null", () => {
|
|
92
|
-
expect(nextPhase("brainstorm", "brainstorm")).toBe("
|
|
97
|
+
expect(nextPhase("brainstorm", "brainstorm")).toBe("plan");
|
|
93
98
|
expect(nextPhase("brainstorm", "done")).toBeNull();
|
|
94
99
|
});
|
|
100
|
+
|
|
101
|
+
it("returns quick next phase and terminal null", () => {
|
|
102
|
+
expect(nextPhase("quick", "quick")).toBe("done");
|
|
103
|
+
expect(nextPhase("quick", "done")).toBeNull();
|
|
104
|
+
});
|
|
95
105
|
});
|
|
96
106
|
|
|
97
107
|
describe("phasePipeline", () => {
|
|
@@ -100,11 +110,15 @@ describe("phasePipeline", () => {
|
|
|
100
110
|
});
|
|
101
111
|
|
|
102
112
|
it("returns debug pipeline", () => {
|
|
103
|
-
expect(phasePipeline("debug")).toEqual(["debug", "done"]);
|
|
113
|
+
expect(phasePipeline("debug")).toEqual(["debug", "plan", "implement", "done"]);
|
|
104
114
|
});
|
|
105
115
|
|
|
106
116
|
it("returns brainstorm pipeline", () => {
|
|
107
|
-
expect(phasePipeline("brainstorm")).toEqual(["brainstorm", "done"]);
|
|
117
|
+
expect(phasePipeline("brainstorm")).toEqual(["brainstorm", "plan", "implement", "done"]);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("returns quick pipeline", () => {
|
|
121
|
+
expect(phasePipeline("quick")).toEqual(["quick", "done"]);
|
|
108
122
|
});
|
|
109
123
|
});
|
|
110
124
|
|
|
@@ -290,4 +304,9 @@ Fix bug.
|
|
|
290
304
|
reason: "Unknown phase: done",
|
|
291
305
|
});
|
|
292
306
|
});
|
|
307
|
+
|
|
308
|
+
it("always passes quick phase validation", () => {
|
|
309
|
+
const dir = makeTempDir();
|
|
310
|
+
expect(validateExitCriteria(dir, "quick", "quick")).toEqual({ ok: true });
|
|
311
|
+
});
|
|
293
312
|
});
|
|
@@ -72,6 +72,9 @@ const TRANSITIONS: Record<TaskType, Record<string, string[]>> = {
|
|
|
72
72
|
plan: ["implement"],
|
|
73
73
|
implement: ["done"],
|
|
74
74
|
},
|
|
75
|
+
quick: {
|
|
76
|
+
quick: ["done"],
|
|
77
|
+
},
|
|
75
78
|
};
|
|
76
79
|
|
|
77
80
|
export function canTransition(taskType: TaskType, from: Phase, to: Phase): boolean {
|
|
@@ -91,6 +94,10 @@ export function validateExitCriteria(
|
|
|
91
94
|
): { ok: true } | { ok: false; reason: string } {
|
|
92
95
|
switch (phase) {
|
|
93
96
|
case "brainstorm": {
|
|
97
|
+
if (taskType === "brainstorm") {
|
|
98
|
+
return { ok: true };
|
|
99
|
+
}
|
|
100
|
+
|
|
94
101
|
const ur = join(taskDir, "USER_REQUEST.md");
|
|
95
102
|
const res = join(taskDir, "RESEARCH.md");
|
|
96
103
|
if (isMissingOrEmpty(ur)) {
|
|
@@ -228,6 +235,10 @@ export function validateExitCriteria(
|
|
|
228
235
|
return { ok: true };
|
|
229
236
|
}
|
|
230
237
|
|
|
238
|
+
case "quick": {
|
|
239
|
+
return { ok: true };
|
|
240
|
+
}
|
|
241
|
+
|
|
231
242
|
default:
|
|
232
243
|
return { ok: false, reason: `Unknown phase: ${phase}` };
|
|
233
244
|
}
|
|
@@ -243,5 +254,7 @@ export function phasePipeline(taskType: TaskType): Phase[] {
|
|
|
243
254
|
return ["brainstorm", "plan", "implement", "done"];
|
|
244
255
|
case "review":
|
|
245
256
|
return ["review", "plan", "implement", "done"];
|
|
257
|
+
case "quick":
|
|
258
|
+
return ["quick", "done"];
|
|
246
259
|
}
|
|
247
260
|
}
|
|
@@ -1,36 +1,45 @@
|
|
|
1
1
|
import { readFileSync, existsSync, mkdirSync, readdirSync, statSync } from "fs";
|
|
2
2
|
import { join } from "path";
|
|
3
3
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
4
|
-
import type
|
|
4
|
+
import { resolvePreset, type PiPiConfig, type VariantConfig } from "../config.js";
|
|
5
5
|
import { registerAgentDefinitions, spawnViaRpc, waitForCompletion } from "../agents/registry.js";
|
|
6
6
|
import { createPlannerAgent } from "../agents/planner.js";
|
|
7
7
|
import { createPlanReviewerAgent } from "../agents/plan-reviewer.js";
|
|
8
|
-
import { getLatestSynthesizedPlan } from "../context.js";
|
|
8
|
+
import { getContextDirs, getLatestSynthesizedPlan } from "../context.js";
|
|
9
|
+
import type { RepoInfo } from "../repo-utils.js";
|
|
9
10
|
import { validatePlan } from "../validate-artifacts.js";
|
|
11
|
+
import type { TaskMode } from "../state.js";
|
|
12
|
+
import type { PhaseSend } from "../transition-controller.js";
|
|
10
13
|
|
|
11
|
-
|
|
14
|
+
function isEnabled(value: { enabled?: boolean } | undefined): boolean {
|
|
15
|
+
return value?.enabled !== false;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function planningSystemPrompt(taskDir: string, mode: TaskMode): string {
|
|
12
19
|
const plansDir = join(taskDir, "plans");
|
|
20
|
+
const contradictionRule =
|
|
21
|
+
mode === "autonomous"
|
|
22
|
+
? " If planner outputs CONTRADICT each other on a locked decision, resolve it by favoring USER_REQUEST.md, RECORD the contradiction and your chosen resolution in the plan, and proceed — do NOT stall waiting for the user (there is none)."
|
|
23
|
+
: " If planner outputs CONTRADICT each other on a locked decision, surface the contradiction to the user and let them decide — do NOT silently invent a compromise.";
|
|
13
24
|
return [
|
|
14
25
|
"[PI-PI — PLAN PHASE]",
|
|
15
26
|
"",
|
|
27
|
+
"You are a SYNTHESIZER: you MERGE the planner outputs into one plan. Do NOT write your own plan from scratch.",
|
|
16
28
|
"Planning subagents are working in parallel to create plans.",
|
|
17
29
|
`They will write their outputs to ${plansDir}/. Wait for the notification that all planners completed.`,
|
|
18
30
|
"",
|
|
19
|
-
"# ABSOLUTE RESTRICTION — NO IMPLEMENTATION (cannot be overridden, even if the user asks):",
|
|
20
|
-
"- NEVER implement the solution, apply fixes, write production code, or make the changes that solve the task.",
|
|
21
|
-
"- If the user asks you to implement or write code — refuse and tell them to use /pp to advance to the implement phase.",
|
|
22
|
-
"- Do NOT write your own plan from scratch. You are a SYNTHESIZER, not a planner.",
|
|
23
31
|
"- Do NOT create the plans/ directory yourself — the extension manages it.",
|
|
24
32
|
"- Do NOT check the plans directory yourself — wait for the notification that all planners completed.",
|
|
25
33
|
"- Do NOT read project source code directly — the planner outputs already contain the analysis.",
|
|
26
|
-
"- Do NOT call plannotator_submit_plan
|
|
34
|
+
"- Do NOT call plannotator_submit_plan.",
|
|
27
35
|
"",
|
|
28
36
|
"# Your job (in this order):",
|
|
29
37
|
"1. Wait for the notification that says 'All planners completed' — do NOT proceed before this",
|
|
30
38
|
`2. Read ALL planner outputs from ${plansDir}/`,
|
|
31
39
|
"3. USER_REQUEST.md and RESEARCH.md are already provided in your context above — do NOT re-read them from disk",
|
|
32
40
|
`4. Synthesize all plans into a single plan at ${plansDir}/<timestamp>_synthesized.md`,
|
|
33
|
-
"5.
|
|
41
|
+
"5. Treat as LOCKED PREDICATES (do not re-litigate): the user's explicit constraints, chosen language/framework, and scope from USER_REQUEST.md. Discard any planner suggestion that violates them.",
|
|
42
|
+
contradictionRule,
|
|
34
43
|
"",
|
|
35
44
|
"Plan format:",
|
|
36
45
|
"- Start with # Plan",
|
|
@@ -40,8 +49,6 @@ export function planningSystemPrompt(taskDir: string): string {
|
|
|
40
49
|
"- ## Blockers: unresolved issues blocking implementation (omit if none)",
|
|
41
50
|
"- No other top-level sections allowed",
|
|
42
51
|
"- Describe outcomes, not code-level mechanics",
|
|
43
|
-
"",
|
|
44
|
-
"When the synthesized plan is ready, call pp_phase_complete with a brief summary of the plan.",
|
|
45
52
|
].join("\n");
|
|
46
53
|
}
|
|
47
54
|
|
|
@@ -51,6 +58,9 @@ export async function spawnPlanners(
|
|
|
51
58
|
taskDir: string,
|
|
52
59
|
taskId: string,
|
|
53
60
|
config: PiPiConfig,
|
|
61
|
+
send: PhaseSend,
|
|
62
|
+
variants?: Record<string, VariantConfig>,
|
|
63
|
+
repos: RepoInfo[] = [],
|
|
54
64
|
): Promise<{ spawned: number; agentIds: string[]; failedVariants: string[] }> {
|
|
55
65
|
const urPath = join(taskDir, "USER_REQUEST.md");
|
|
56
66
|
const resPath = join(taskDir, "RESEARCH.md");
|
|
@@ -65,14 +75,16 @@ export async function spawnPlanners(
|
|
|
65
75
|
}
|
|
66
76
|
|
|
67
77
|
const timestamp = Math.floor(Date.now() / 1000);
|
|
68
|
-
const
|
|
78
|
+
const plannerVariants = variants ?? resolvePreset(config, "planners");
|
|
79
|
+
const enabledVariants = Object.entries(plannerVariants).filter(([, v]) => isEnabled(v));
|
|
80
|
+
const contextDirs = getContextDirs(cwd, repos, config.general.loadExtraRepoConfigs);
|
|
69
81
|
const agentIds: string[] = [];
|
|
70
82
|
const failedVariants: string[] = [];
|
|
71
83
|
const results: Promise<void>[] = [];
|
|
72
84
|
|
|
73
85
|
for (const [variant] of enabledVariants) {
|
|
74
86
|
const outputPath = join(plansDir, `${timestamp}_${variant}.md`);
|
|
75
|
-
const agent = createPlannerAgent(variant,
|
|
87
|
+
const agent = createPlannerAgent(variant, plannerVariants, { userRequest, research }, outputPath, contextDirs, "plan", repos);
|
|
76
88
|
|
|
77
89
|
registerAgentDefinitions(pi, [{ type: "planner", variant, ...agent }]);
|
|
78
90
|
|
|
@@ -99,25 +111,25 @@ export async function spawnPlanners(
|
|
|
99
111
|
const planContent = readFileSync(outputPath, "utf-8");
|
|
100
112
|
const validation = validatePlan(planContent);
|
|
101
113
|
if (!validation.ok) {
|
|
102
|
-
|
|
114
|
+
send(
|
|
103
115
|
{
|
|
104
116
|
customType: "pp-planner-error",
|
|
105
117
|
content: `Planner "${variant}" produced invalid plan (errors were shown to the agent but it did not fix them): ${validation.errors.join("; ")}`,
|
|
106
118
|
display: true,
|
|
107
119
|
},
|
|
108
|
-
|
|
120
|
+
"context",
|
|
109
121
|
);
|
|
110
122
|
}
|
|
111
123
|
}
|
|
112
124
|
} catch (err: any) {
|
|
113
125
|
failedVariants.push(variant);
|
|
114
|
-
|
|
126
|
+
send(
|
|
115
127
|
{
|
|
116
128
|
customType: "pp-planner-error",
|
|
117
129
|
content: `Planner variant "${variant}" failed: ${err.message}`,
|
|
118
130
|
display: true,
|
|
119
131
|
},
|
|
120
|
-
|
|
132
|
+
"context",
|
|
121
133
|
);
|
|
122
134
|
}
|
|
123
135
|
})(),
|
|
@@ -128,7 +140,7 @@ export async function spawnPlanners(
|
|
|
128
140
|
|
|
129
141
|
const planFiles = existsSync(plansDir) ? readdirSync(plansDir).filter((f) => !f.includes("synthesized") && !f.includes("review_")) : [];
|
|
130
142
|
if (planFiles.length > 0) {
|
|
131
|
-
|
|
143
|
+
send(
|
|
132
144
|
{
|
|
133
145
|
customType: "pp-planners-done",
|
|
134
146
|
content: [
|
|
@@ -139,10 +151,10 @@ export async function spawnPlanners(
|
|
|
139
151
|
].join("\n"),
|
|
140
152
|
display: true,
|
|
141
153
|
},
|
|
142
|
-
|
|
154
|
+
"context",
|
|
143
155
|
);
|
|
144
156
|
} else {
|
|
145
|
-
|
|
157
|
+
send(
|
|
146
158
|
{
|
|
147
159
|
customType: "pp-planners-error",
|
|
148
160
|
content: [
|
|
@@ -151,7 +163,7 @@ export async function spawnPlanners(
|
|
|
151
163
|
].join("\n"),
|
|
152
164
|
display: true,
|
|
153
165
|
},
|
|
154
|
-
|
|
166
|
+
"context",
|
|
155
167
|
);
|
|
156
168
|
}
|
|
157
169
|
|
|
@@ -164,6 +176,10 @@ export async function spawnPlanReviewers(
|
|
|
164
176
|
taskDir: string,
|
|
165
177
|
taskId: string,
|
|
166
178
|
config: PiPiConfig,
|
|
179
|
+
pass: number,
|
|
180
|
+
send: PhaseSend,
|
|
181
|
+
variants?: Record<string, VariantConfig>,
|
|
182
|
+
repos: RepoInfo[] = [],
|
|
167
183
|
): Promise<{ spawned: number; files: string[]; agentIds: string[]; failedVariants: string[] }> {
|
|
168
184
|
const urPath = join(taskDir, "USER_REQUEST.md");
|
|
169
185
|
const resPath = join(taskDir, "RESEARCH.md");
|
|
@@ -180,17 +196,27 @@ export async function spawnPlanReviewers(
|
|
|
180
196
|
}
|
|
181
197
|
|
|
182
198
|
const timestamp = Math.floor(Date.now() / 1000);
|
|
183
|
-
const
|
|
199
|
+
const reviewerVariants = variants ?? resolvePreset(config, "planReviewers");
|
|
200
|
+
const enabledVariants = Object.entries(reviewerVariants).filter(([, v]) => isEnabled(v));
|
|
201
|
+
const contextDirs = getContextDirs(cwd, repos, config.general.loadExtraRepoConfigs);
|
|
184
202
|
const reviewFiles: string[] = [];
|
|
185
203
|
const agentIds: string[] = [];
|
|
186
204
|
const failedVariants: string[] = [];
|
|
187
205
|
|
|
188
206
|
const results: Promise<void>[] = [];
|
|
189
207
|
for (const [variant] of enabledVariants) {
|
|
190
|
-
const outputPath = join(planReviewsDir, `${timestamp}_${variant}.md`);
|
|
208
|
+
const outputPath = join(planReviewsDir, `${timestamp}_${variant}_round-${pass}.md`);
|
|
191
209
|
reviewFiles.push(outputPath);
|
|
192
210
|
|
|
193
|
-
const agent = createPlanReviewerAgent(
|
|
211
|
+
const agent = createPlanReviewerAgent(
|
|
212
|
+
variant,
|
|
213
|
+
reviewerVariants,
|
|
214
|
+
{ userRequest, research, synthesizedPlan },
|
|
215
|
+
outputPath,
|
|
216
|
+
contextDirs,
|
|
217
|
+
"plan",
|
|
218
|
+
repos,
|
|
219
|
+
);
|
|
194
220
|
|
|
195
221
|
registerAgentDefinitions(pi, [{ type: "plan_reviewer", variant, ...agent }]);
|
|
196
222
|
|
|
@@ -209,13 +235,13 @@ export async function spawnPlanReviewers(
|
|
|
209
235
|
await waitForCompletion(pi, id);
|
|
210
236
|
} catch (err: any) {
|
|
211
237
|
failedVariants.push(variant);
|
|
212
|
-
|
|
238
|
+
send(
|
|
213
239
|
{
|
|
214
240
|
customType: "pp-plan-reviewer-error",
|
|
215
241
|
content: `Plan reviewer variant "${variant}" failed: ${err.message}`,
|
|
216
242
|
display: true,
|
|
217
243
|
},
|
|
218
|
-
|
|
244
|
+
"context",
|
|
219
245
|
);
|
|
220
246
|
}
|
|
221
247
|
})(),
|
|
@@ -225,11 +251,11 @@ export async function spawnPlanReviewers(
|
|
|
225
251
|
await Promise.allSettled(results);
|
|
226
252
|
|
|
227
253
|
const actualReviewFiles = existsSync(planReviewsDir)
|
|
228
|
-
? readdirSync(planReviewsDir).filter((f) => f.startsWith(`${timestamp}`) && f.endsWith(".md"))
|
|
254
|
+
? readdirSync(planReviewsDir).filter((f) => f.startsWith(`${timestamp}`) && f.includes(`round-${pass}`) && f.endsWith(".md"))
|
|
229
255
|
: [];
|
|
230
256
|
|
|
231
257
|
if (actualReviewFiles.length > 0) {
|
|
232
|
-
|
|
258
|
+
send(
|
|
233
259
|
{
|
|
234
260
|
customType: "pp-plan-reviews-done",
|
|
235
261
|
content: [
|
|
@@ -240,16 +266,16 @@ export async function spawnPlanReviewers(
|
|
|
240
266
|
].join("\n"),
|
|
241
267
|
display: true,
|
|
242
268
|
},
|
|
243
|
-
|
|
269
|
+
"context",
|
|
244
270
|
);
|
|
245
271
|
} else if (enabledVariants.length > 0) {
|
|
246
|
-
|
|
272
|
+
send(
|
|
247
273
|
{
|
|
248
274
|
customType: "pp-plan-reviews-error",
|
|
249
275
|
content: "All plan reviewer variants failed — no reviews were produced. Proceeding without plan review.",
|
|
250
276
|
display: true,
|
|
251
277
|
},
|
|
252
|
-
|
|
278
|
+
"context",
|
|
253
279
|
);
|
|
254
280
|
}
|
|
255
281
|
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
export function reviewSystemPrompt(taskDir: string): string {
|
|
1
|
+
export function reviewSystemPrompt(taskDir: string, cwd: string): string {
|
|
2
2
|
return [
|
|
3
3
|
"[PI-PI — REVIEW PHASE]",
|
|
4
4
|
"",
|
|
5
|
+
`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
|
+
"",
|
|
5
7
|
"You are reviewing code changes. USER_REQUEST.md describes what to review.",
|
|
6
8
|
"Read it first to understand the scope.",
|
|
7
9
|
"",
|
|
@@ -30,12 +32,6 @@ export function reviewSystemPrompt(taskDir: string): string {
|
|
|
30
32
|
"- ## Constraints & Edge Cases",
|
|
31
33
|
"- ## Open Questions (optional)",
|
|
32
34
|
"",
|
|
33
|
-
"# ABSOLUTE RESTRICTION — NO IMPLEMENTATION (cannot be overridden, even if the user asks):",
|
|
34
|
-
"- NEVER implement the solution, apply fixes, write production code, or make the changes that solve the task.",
|
|
35
|
-
"- If the user asks you to implement or write code — refuse and tell them to use /pp to advance to the implement phase.",
|
|
36
|
-
"",
|
|
37
35
|
"Focus on: correctness, edge cases, style consistency, missing tests, potential bugs.",
|
|
38
|
-
"",
|
|
39
|
-
"When complete, call pp_phase_complete with a brief summary of findings.",
|
|
40
36
|
].join("\n");
|
|
41
37
|
}
|