@ilya-lesikov/pi-pi 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/3p/pi-plannotator/apps/pi-extension/README.md +6 -5
  2. package/3p/pi-plannotator/apps/pi-extension/plannotator-browser.ts +25 -10
  3. package/3p/pi-plannotator/apps/pi-extension/plannotator-events.code-review.test.ts +172 -0
  4. package/3p/pi-plannotator/apps/pi-extension/plannotator-events.ts +50 -12
  5. package/3p/pi-plannotator/apps/pi-extension/server/project.test.ts +45 -0
  6. package/3p/pi-plannotator/apps/pi-extension/server/project.ts +7 -6
  7. package/3p/pi-plannotator/apps/pi-extension/server/serverReview.ts +41 -25
  8. package/3p/pi-subagents/src/agent-manager.ts +34 -1
  9. package/3p/pi-subagents/src/agent-runner.ts +66 -33
  10. package/3p/pi-subagents/src/index.ts +3 -38
  11. package/3p/pi-subagents/src/types.ts +4 -0
  12. package/extensions/orchestrator/agents/advisor.ts +35 -0
  13. package/extensions/orchestrator/agents/brainstorm-reviewer.ts +7 -7
  14. package/extensions/orchestrator/agents/code-reviewer.ts +27 -11
  15. package/extensions/orchestrator/agents/constraints.test.ts +82 -0
  16. package/extensions/orchestrator/agents/constraints.ts +66 -2
  17. package/extensions/orchestrator/agents/deep-debugger.ts +35 -0
  18. package/extensions/orchestrator/agents/plan-reviewer.ts +7 -7
  19. package/extensions/orchestrator/agents/planner.ts +9 -8
  20. package/extensions/orchestrator/agents/prompts.test.ts +120 -0
  21. package/extensions/orchestrator/agents/reviewer.ts +48 -0
  22. package/extensions/orchestrator/agents/task.ts +6 -20
  23. package/extensions/orchestrator/agents/tool-routing.ts +39 -1
  24. package/extensions/orchestrator/ai-comment-cleanup.test.ts +89 -0
  25. package/extensions/orchestrator/ai-comment-cleanup.ts +73 -0
  26. package/extensions/orchestrator/command-handlers.test.ts +47 -0
  27. package/extensions/orchestrator/command-handlers.ts +44 -28
  28. package/extensions/orchestrator/config.test.ts +40 -1
  29. package/extensions/orchestrator/config.ts +18 -2
  30. package/extensions/orchestrator/context.test.ts +54 -0
  31. package/extensions/orchestrator/context.ts +81 -2
  32. package/extensions/orchestrator/custom-footer.test.ts +91 -0
  33. package/extensions/orchestrator/custom-footer.ts +20 -20
  34. package/extensions/orchestrator/event-handlers.test.ts +412 -2
  35. package/extensions/orchestrator/event-handlers.ts +556 -227
  36. package/extensions/orchestrator/flant-infra.test.ts +25 -0
  37. package/extensions/orchestrator/flant-infra.ts +91 -0
  38. package/extensions/orchestrator/index.ts +1 -1
  39. package/extensions/orchestrator/integration.test.ts +411 -20
  40. package/extensions/orchestrator/messages.test.ts +30 -0
  41. package/extensions/orchestrator/messages.ts +6 -0
  42. package/extensions/orchestrator/model-registry.test.ts +48 -1
  43. package/extensions/orchestrator/model-registry.ts +43 -1
  44. package/extensions/orchestrator/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  45. package/extensions/orchestrator/orchestrator.test.ts +113 -0
  46. package/extensions/orchestrator/orchestrator.ts +197 -60
  47. package/extensions/orchestrator/phases/brainstorm.ts +20 -27
  48. package/extensions/orchestrator/phases/implementation.test.ts +11 -0
  49. package/extensions/orchestrator/phases/implementation.ts +4 -6
  50. package/extensions/orchestrator/phases/machine.test.ts +36 -0
  51. package/extensions/orchestrator/phases/machine.ts +11 -1
  52. package/extensions/orchestrator/phases/planning.test.ts +16 -0
  53. package/extensions/orchestrator/phases/planning.ts +11 -4
  54. package/extensions/orchestrator/phases/review-task.test.ts +20 -0
  55. package/extensions/orchestrator/phases/review-task.ts +47 -22
  56. package/extensions/orchestrator/phases/review.test.ts +34 -0
  57. package/extensions/orchestrator/phases/review.ts +44 -6
  58. package/extensions/orchestrator/plannotator.ts +9 -6
  59. package/extensions/orchestrator/pp-menu.test.ts +207 -1
  60. package/extensions/orchestrator/pp-menu.ts +514 -402
  61. package/extensions/orchestrator/pp-state-tools.test.ts +192 -0
  62. package/extensions/orchestrator/pp-state-tools.ts +249 -0
  63. package/extensions/orchestrator/rate-limit-fallback-flow.test.ts +128 -0
  64. package/extensions/orchestrator/rate-limit-fallback.test.ts +98 -0
  65. package/extensions/orchestrator/rate-limit-fallback.ts +243 -0
  66. package/extensions/orchestrator/state.test.ts +9 -0
  67. package/extensions/orchestrator/state.ts +15 -0
  68. package/extensions/orchestrator/test-helpers.ts +4 -1
  69. package/extensions/orchestrator/transition-controller.test.ts +100 -0
  70. package/extensions/orchestrator/transition-controller.ts +61 -3
  71. package/extensions/orchestrator/usage-tracker.ts +5 -1
  72. package/extensions/orchestrator/validate-artifacts.test.ts +20 -0
  73. package/extensions/orchestrator/validate-artifacts.ts +2 -2
  74. package/package.json +1 -1
  75. package/AGENTS.md +0 -28
@@ -4,7 +4,7 @@ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
4
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";
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
10
  import type { PhaseSend } from "../transition-controller.js";
@@ -26,13 +26,8 @@ export function brainstormSystemPrompt(taskType: TaskType, taskDescription: stri
26
26
  "",
27
27
  "# Your job:",
28
28
  "1. Clarify the problem with the user if needed",
29
- "2. Spawn subagents for research (subagent_type is REQUIRED calls without it are rejected):",
30
- ' - Agent(subagent_type="Explore", ...) codebase research. Prefer this for most lookups.',
31
- ' - Agent(subagent_type="Librarian", ...) — external docs, library APIs, web research.',
32
- ' - Agent(subagent_type="Task", ...) — only when you need a subtask that writes files or runs complex multi-step commands.',
33
- " Explore is fast and cheap. Use it liberally for codebase questions. Spawn multiple in parallel.",
34
- "3. Use bash to run commands, check logs, reproduce issues",
35
- "4. Use tools to trace the bug:",
29
+ "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",
30
+ "3. Use tools to trace the bug:",
36
31
  " - cbm_search/cbm_search_code: find relevant code by concept or text",
37
32
  " - lsp goToDefinition, findReferences, hover: precise navigation and type info",
38
33
  " - lsp goToImplementation: check interface implementors",
@@ -60,6 +55,7 @@ export function brainstormSystemPrompt(taskType: TaskType, taskDescription: stri
60
55
  " <Unresolved items needing user input. Omit section if none.>",
61
56
  "",
62
57
  "These files are validated programmatically. Missing sections or unexpected sections will be rejected.",
58
+ "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.",
63
59
  "",
64
60
  "# Optional: focused analysis artifacts",
65
61
  `You may also write additional analysis files to ${taskDir}/artifacts/<name>.md`,
@@ -84,11 +80,7 @@ export function brainstormSystemPrompt(taskType: TaskType, taskDescription: stri
84
80
  "",
85
81
  "# How to work:",
86
82
  "- Discuss the topic with the user. Ask clarifying questions. Propose approaches. Analyze tradeoffs.",
87
- "- Spawn subagents for research (subagent_type is REQUIRED calls without it are rejected):",
88
- ' Agent(subagent_type="Explore", ...) — codebase research. Prefer this for most lookups. Fast and cheap.',
89
- ' Agent(subagent_type="Librarian", ...) — external docs, library APIs, web research.',
90
- ' Agent(subagent_type="Task", ...) — only when you need a subtask that writes files or runs complex multi-step commands.',
91
- " Spawn multiple Explore agents in parallel for broad searches.",
83
+ "- Delegate research to subagents where useful (see the delegation guidance in your system prompt).",
92
84
  "- Use tools directly for quick lookups (cbm_search, lsp, ast_search, grep, etc.)",
93
85
  "- Present findings to the user and discuss them. Don't just dump raw results.",
94
86
  "",
@@ -115,25 +107,25 @@ export function brainstormSystemPrompt(taskType: TaskType, taskDescription: stri
115
107
  "",
116
108
  registerReposInstruction,
117
109
  "",
118
- "Your job is to produce USER_REQUEST.md and RESEARCH.md — complete enough that",
119
- "downstream agents can work without re-exploring the codebase or re-interviewing the user.",
110
+ "This is a clarify + research + DESIGN phase. Your job is to produce USER_REQUEST.md and RESEARCH.md — complete enough that",
111
+ "downstream agents can work without re-exploring the codebase or re-interviewing the user. That means not just describing what",
112
+ "exists, but exploring the design space: weigh the viable approaches and their tradeoffs so the plan phase inherits a clear",
113
+ "direction rather than an open-ended problem.",
120
114
  "",
121
115
  "# Steps:",
122
116
  "1. Clarify requirements with the user if anything is ambiguous",
123
- "2. Spawn subagents for research (subagent_type is REQUIREDcalls without it are rejected):",
124
- ' - Agent(subagent_type="Explore", ...) — codebase research. Prefer this for most lookups. Fast and cheap.',
125
- ' - Agent(subagent_type="Librarian", ...) — external docs, library APIs, web research.',
126
- ' - Agent(subagent_type="Task", ...) — only when you need a subtask that writes files or runs complex multi-step commands.',
127
- " Spawn multiple Explore agents in parallel for broad searches.",
117
+ "2. Delegate research to subagents where useful (see the delegation guidance in your system prompt) spawn multiple explores in parallel for broad searches",
128
118
  "3. Use tools to understand code structure:",
129
119
  " - cbm_search: natural-language search across all symbols",
130
120
  " - cbm_search_code: graph-augmented grep (deduplicates into functions)",
131
121
  " - lsp documentSymbol, goToDefinition, findReferences, goToImplementation, hover",
132
122
  " - cbm_trace: trace call chains for dependency understanding",
133
123
  " - ast_search: find structural patterns across the codebase",
134
- "4. Ask the user follow-up questions as needed",
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",
124
+ "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
+ "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",
127
+ "7. Write findings into RESEARCH.md as results come back — don't wait for all subagents",
128
+ "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",
137
129
  "",
138
130
  "Produce two files:",
139
131
  `- ${taskDir}/USER_REQUEST.md — MUST follow this exact structure:`,
@@ -152,13 +144,14 @@ export function brainstormSystemPrompt(taskType: TaskType, taskDescription: stri
152
144
  " - MUST: <hard requirements discovered from code>",
153
145
  " - RISK: <things that could break>",
154
146
  " ## Open Questions",
155
- " <Unresolved items needing user input. Omit section if none.>",
147
+ " <Unresolved items needing user input. Drive these to resolution before advancing — omit the section only when genuinely none remain.>",
156
148
  "",
157
149
  "These files are validated programmatically. Missing sections or unexpected sections will be rejected.",
150
+ "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.",
158
151
  "",
159
152
  "# Optional: focused analysis artifacts",
160
153
  `You may also write additional analysis files to ${taskDir}/artifacts/<name>.md`,
161
- "for deep dives on specific topics (e.g. architecture analysis, API comparison, risk assessment).",
154
+ "for deep dives on specific topics (e.g. architecture analysis, API comparison, design-option/tradeoff analysis, risk assessment).",
162
155
  "Each artifact must start with # <Title>. Content is freeform. These are reviewed alongside USER_REQUEST.md and RESEARCH.md.",
163
156
  "Do NOT duplicate content already in RESEARCH.md — artifacts are for supplementary deep dives.",
164
157
  ].join("\n");
@@ -210,7 +203,7 @@ export async function spawnBrainstormReviewers(
210
203
  const agent = createBrainstormReviewerAgent(
211
204
  variant,
212
205
  reviewerVariants,
213
- { userRequest, research, artifacts: artifacts.length > 0 ? artifacts : undefined },
206
+ { userRequest, research, artifacts: artifacts.length > 0 ? artifacts : undefined, manifest: getArtifactManifest(taskDir) },
214
207
  outputPath,
215
208
  contextDirs,
216
209
  "brainstorm",
@@ -261,7 +254,7 @@ export async function spawnBrainstormReviewers(
261
254
  `${reviewOutputFiles.length} brainstorm reviewer(s) completed (round ${round}). Reviews in ${reviewsDir}:`,
262
255
  ...reviewOutputFiles.map((f) => ` - ${f}`),
263
256
  "",
264
- "Read all reviews and update USER_REQUEST.md and RESEARCH.md if needed.",
257
+ "Read all reviews and update USER_REQUEST.md, RESEARCH.md, and any artifacts/ files if needed.",
265
258
  ].join("\n"),
266
259
  display: true,
267
260
  },
@@ -0,0 +1,11 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { implementationSystemPrompt } from "./implementation.js";
3
+
4
+ describe("implementationSystemPrompt self-complete directive", () => {
5
+ it("instructs the agent to call pp_phase_complete when the implement phase is complete", () => {
6
+ const prompt = implementationSystemPrompt("/tmp/task", "/tmp");
7
+ expect(prompt).toContain("pp_phase_complete");
8
+ expect(prompt).toContain("Do NOT instead ask the user to run /pp manually");
9
+ expect(prompt).not.toContain("do NOT wait for the user");
10
+ });
11
+ });
@@ -11,7 +11,7 @@ export function implementationSystemPrompt(taskDir: string, cwd: string): string
11
11
  "# Instructions:",
12
12
  "1. Review the synthesized plan in your context",
13
13
  "2. Implement each item in order",
14
- "3. Check off items in the plan (change - [ ] to - [x]) as you complete them",
14
+ "3. Check off items in the plan (change - [ ] to - [x]) as you complete them — use pp_edit_state_file (NOT the generic edit) to update the plan compactly",
15
15
  "4. After editing files, run lsp diagnostics to check for errors; use lsp codeActions for auto-fixes",
16
16
  "5. Before modifying code, understand it first:",
17
17
  " - cbm_search/cbm_search_code: find relevant functions and their context",
@@ -21,11 +21,7 @@ export function implementationSystemPrompt(taskDir: string, cwd: string): string
21
21
  " - cbm_trace: trace dependencies across the codebase",
22
22
  " - ast_search: find structural patterns (e.g. error handling conventions)",
23
23
  "6. After completing changes, run cbm_changes to verify blast radius",
24
- "7. Spawn subagents as needed (subagent_type is REQUIREDcalls without it are rejected):",
25
- ' - Agent(subagent_type="Explore", ...) — codebase research. Prefer this for most lookups. Fast and cheap.',
26
- ' - Agent(subagent_type="Librarian", ...) — external docs, library APIs, web research.',
27
- ' - Agent(subagent_type="Task", ...) — parallelizable, self-contained implementation subtasks.',
28
- " Spawn multiple Explore agents in parallel for broad searches.",
24
+ "7. Delegate to subagents where useful (see the delegation guidance in your system prompt) spawn multiple explores in parallel for broad searches, and use task for parallelizable self-contained implementation subtasks",
29
25
  "",
30
26
  "# Commits:",
31
27
  "After completing a logical unit of work (a plan item, a bug fix, a test), call pp_commit",
@@ -34,5 +30,7 @@ export function implementationSystemPrompt(taskDir: string, cwd: string): string
34
30
  "Don't batch all changes into one commit.",
35
31
  "",
36
32
  "Fix issues found by lsp diagnostics before moving on.",
33
+ "",
34
+ "When you judge the implement phase complete, call pp_phase_complete — the extension opens the advance gate for the user to review and confirm. Do NOT instead ask the user to run /pp manually.",
37
35
  ].join("\n");
38
36
  }
@@ -297,6 +297,42 @@ Fix bug.
297
297
  expect(validateExitCriteria(pass, "debug", "debug")).toEqual({ ok: true });
298
298
  });
299
299
 
300
+ it("validates review phase — requires artifacts and an ANCHORS-bearing final_pass file", () => {
301
+ const missing = makeTempDir();
302
+ expect(validateExitCriteria(missing, "review", "review")).toEqual({
303
+ ok: false,
304
+ reason: "USER_REQUEST.md does not exist or is empty",
305
+ });
306
+
307
+ const noAnchors = makeTempDir();
308
+ writeFileSync(join(noAnchors, "USER_REQUEST.md"), VALID_USER_REQUEST, "utf-8");
309
+ writeFileSync(join(noAnchors, "RESEARCH.md"), VALID_RESEARCH, "utf-8");
310
+ const res1 = validateExitCriteria(noAnchors, "review", "review");
311
+ expect(res1.ok).toBe(false);
312
+ expect((res1 as { reason: string }).reason).toContain("ANCHORS");
313
+
314
+ const emptyAnchorsFile = makeTempDir();
315
+ writeFileSync(join(emptyAnchorsFile, "USER_REQUEST.md"), VALID_USER_REQUEST, "utf-8");
316
+ writeFileSync(join(emptyAnchorsFile, "RESEARCH.md"), VALID_RESEARCH, "utf-8");
317
+ mkdirSync(join(emptyAnchorsFile, "code-reviews"), { recursive: true });
318
+ writeFileSync(join(emptyAnchorsFile, "code-reviews", "1_final_pass-1.md"), "no anchors here", "utf-8");
319
+ expect(validateExitCriteria(emptyAnchorsFile, "review", "review").ok).toBe(false);
320
+
321
+ const zeroFindings = makeTempDir();
322
+ writeFileSync(join(zeroFindings, "USER_REQUEST.md"), VALID_USER_REQUEST, "utf-8");
323
+ writeFileSync(join(zeroFindings, "RESEARCH.md"), VALID_RESEARCH, "utf-8");
324
+ mkdirSync(join(zeroFindings, "code-reviews"), { recursive: true });
325
+ writeFileSync(join(zeroFindings, "code-reviews", "1_final_pass-1.md"), "# Review\nANCHORS: (none)\n", "utf-8");
326
+ expect(validateExitCriteria(zeroFindings, "review", "review")).toEqual({ ok: true });
327
+
328
+ const withFindings = makeTempDir();
329
+ writeFileSync(join(withFindings, "USER_REQUEST.md"), VALID_USER_REQUEST, "utf-8");
330
+ writeFileSync(join(withFindings, "RESEARCH.md"), VALID_RESEARCH, "utf-8");
331
+ mkdirSync(join(withFindings, "code-reviews"), { recursive: true });
332
+ writeFileSync(join(withFindings, "code-reviews", "1_final_pass-2.md"), "ANCHORS:\n- src/a.ts:12 must fix\n", "utf-8");
333
+ expect(validateExitCriteria(withFindings, "review", "review")).toEqual({ ok: true });
334
+ });
335
+
300
336
  it("returns unknown phase error for done", () => {
301
337
  const dir = makeTempDir();
302
338
  expect(validateExitCriteria(dir, "implement", "done")).toEqual({
@@ -1,7 +1,7 @@
1
1
  import { existsSync, readFileSync } from "fs";
2
2
  import { join } from "path";
3
3
  import type { TaskType, Phase } from "../state.js";
4
- import { getLatestSynthesizedPlan } from "../context.js";
4
+ import { getLatestSynthesizedPlan, hasFinalPassAnchors } from "../context.js";
5
5
  import { validatePlan, validateResearch, validateUserRequest } from "../validate-artifacts.js";
6
6
 
7
7
  const USER_REQUEST_TEMPLATE = [
@@ -156,6 +156,16 @@ export function validateExitCriteria(
156
156
  };
157
157
  }
158
158
 
159
+ if (!hasFinalPassAnchors(taskDir)) {
160
+ return {
161
+ ok: false,
162
+ reason:
163
+ "No ANCHORS-bearing final review file exists. Write the review findings to " +
164
+ "`code-reviews/*_final_pass-*.md` with an `ANCHORS:` block (use `ANCHORS: (none)` " +
165
+ "if there are no findings) before completing the review.",
166
+ };
167
+ }
168
+
159
169
  return { ok: true };
160
170
  }
161
171
 
@@ -0,0 +1,16 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { planningSystemPrompt } from "./planning.js";
3
+
4
+ describe("planningSystemPrompt self-complete directive", () => {
5
+ it("guided synthesis instructs the agent to call pp_phase_complete when synthesis is complete", () => {
6
+ const prompt = planningSystemPrompt("/tmp/task", "guided");
7
+ expect(prompt).toContain("pp_phase_complete");
8
+ expect(prompt).toContain("Do NOT instead ask the user to run /pp manually");
9
+ });
10
+
11
+ it("autonomous synthesis does not add the guided self-complete directive", () => {
12
+ const prompt = planningSystemPrompt("/tmp/task", "autonomous");
13
+ expect(prompt).not.toContain("pp_phase_complete");
14
+ expect(prompt).not.toContain("Do NOT instead ask the user to run /pp manually");
15
+ });
16
+ });
@@ -5,7 +5,7 @@ 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 { getContextDirs, getLatestSynthesizedPlan } from "../context.js";
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
11
  import type { TaskMode } from "../state.js";
@@ -21,6 +21,10 @@ export function planningSystemPrompt(taskDir: string, mode: TaskMode): string {
21
21
  mode === "autonomous"
22
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
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.";
24
+ const synthesizeCompletionRule =
25
+ mode === "autonomous"
26
+ ? ""
27
+ : "When you judge the plan synthesis complete, call pp_phase_complete — the extension opens the advance gate for the user to review and confirm. Do NOT instead ask the user to run /pp manually.";
24
28
  return [
25
29
  "[PI-PI — PLAN PHASE]",
26
30
  "",
@@ -46,9 +50,12 @@ export function planningSystemPrompt(taskDir: string, mode: TaskMode): string {
46
50
  "- ## Scope: 2-4 lines — what changes, what doesn't, critical constraints",
47
51
  "- ## Checklist: each item is - [ ] <outcome> — Done when: <observable condition>",
48
52
  " Each item = one independently verifiable outcome. No code snippets or file-by-file instructions.",
53
+ "- ## Pattern constraints: include this section whenever the task adds a type, function, parser, annotation, config key, enum, or any user-facing value. For each, name the CLOSEST EXISTING analog in the codebase (found by behavior, not filename) and the exact conventions the implementer MUST mirror: data shape (prefer one existing shape over inventing parallel/duplicated state), spelling/casing of user-facing values (match existing values — never invent a new casing), and parser/validation/error-handling shape. These are acceptance criteria, not suggestions. Omit the section only if the task adds none of the above.",
49
54
  "- ## Blockers: unresolved issues blocking implementation (omit if none)",
55
+ "Write/update the synthesized plan with pp_write_state_file / pp_edit_state_file (NOT the generic write/edit) — they keep the output compact and validate structure.",
50
56
  "- No other top-level sections allowed",
51
- "- Describe outcomes, not code-level mechanics",
57
+ "- Describe outcomes, not code-level mechanics, EXCEPT in ## Pattern constraints where naming the concrete analog and conventions is required",
58
+ ...(synthesizeCompletionRule ? ["", synthesizeCompletionRule] : []),
52
59
  ].join("\n");
53
60
  }
54
61
 
@@ -84,7 +91,7 @@ export async function spawnPlanners(
84
91
 
85
92
  for (const [variant] of enabledVariants) {
86
93
  const outputPath = join(plansDir, `${timestamp}_${variant}.md`);
87
- const agent = createPlannerAgent(variant, plannerVariants, { userRequest, research }, outputPath, contextDirs, "plan", repos);
94
+ const agent = createPlannerAgent(variant, plannerVariants, { userRequest, research, manifest: getArtifactManifest(taskDir) }, outputPath, contextDirs, "plan", repos);
88
95
 
89
96
  registerAgentDefinitions(pi, [{ type: "planner", variant, ...agent }]);
90
97
 
@@ -211,7 +218,7 @@ export async function spawnPlanReviewers(
211
218
  const agent = createPlanReviewerAgent(
212
219
  variant,
213
220
  reviewerVariants,
214
- { userRequest, research, synthesizedPlan },
221
+ { userRequest, research, synthesizedPlan, manifest: getArtifactManifest(taskDir) },
215
222
  outputPath,
216
223
  contextDirs,
217
224
  "plan",
@@ -0,0 +1,20 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { reviewSystemPrompt } from "./review-task.js";
3
+
4
+ describe("review-task reviewSystemPrompt", () => {
5
+ it("mandates writing an ANCHORS-bearing final_pass file via the generic write tool", () => {
6
+ const prompt = reviewSystemPrompt("/tmp/task", "/tmp/cwd");
7
+ expect(prompt).toContain("/tmp/task/code-reviews/<unix-epoch-seconds>_final_pass-1.md");
8
+ expect(prompt).toContain("ANCHORS:");
9
+ expect(prompt).toContain("<relative/path/from/repo/root>:<line> — <one-line finding>");
10
+ expect(prompt).toContain("(none)");
11
+ expect(prompt).toContain("GENERIC write tool");
12
+ });
13
+
14
+ it("still captures the structured USER_REQUEST.md / RESEARCH.md deliverables", () => {
15
+ const prompt = reviewSystemPrompt("/tmp/task", "/tmp/cwd");
16
+ expect(prompt).toContain("USER_REQUEST.md");
17
+ expect(prompt).toContain("RESEARCH.md");
18
+ expect(prompt).toContain("pp_write_state_file");
19
+ });
20
+ });
@@ -4,34 +4,59 @@ export function reviewSystemPrompt(taskDir: string, cwd: string): string {
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
- "You are reviewing code changes. USER_REQUEST.md describes what to review.",
8
- "Read it first to understand the scope.",
7
+ "You are reviewing code changes. The user will describe what to review in their first chat message",
8
+ "(e.g. a branch, a commit range, uncommitted changes, or a GitHub PR URL). There is NO pre-seeded",
9
+ "USER_REQUEST.md — you must capture the user's request and your findings into the state files yourself.",
10
+ "If the scope is ambiguous, ask the user before diving in.",
11
+ "",
12
+ "If the review scope is a GitHub PR: for each registered repo, resolve the PR with `gh pr view --json number,headRefName,headRefOid,url`, then call pp_checkout_pr_head with that repo's `headRefName`/`headRefOid` to land it on the PR head BEFORE analyzing it. If the tool HALTS (dirty tree, different branch, or diverged branch), relay its message to the user and wait for them to resolve it before continuing. For a branch, commit-range, or uncommitted-changes review, do NOT call pp_checkout_pr_head.",
9
13
  "",
10
14
  "Use available tools to analyze the changes:",
11
15
  "- git diff, git log, git show for examining commits and diffs",
12
16
  "- read, lsp, grep, find for understanding the code",
13
17
  "- gh pr view for GitHub PR context if a PR URL is mentioned",
14
- "",
15
- "Spawn subagents as needed (subagent_type is REQUIRED — calls without it are rejected):",
16
- '- Agent(subagent_type="Explore", ...) — codebase research. Prefer this for most lookups. Fast and cheap.',
17
- '- Agent(subagent_type="Librarian", ...) — external docs, library APIs, web research.',
18
- "Spawn multiple Explore agents in parallel for broad searches.",
19
- "",
20
- "Write your findings:",
21
- `- ${taskDir}/USER_REQUEST.md update with a clear problem statement of issues found`,
22
- `- ${taskDir}/RESEARCH.md — detailed technical analysis`,
23
- "",
24
- "USER_REQUEST.md format:",
25
- "- # User Request",
26
- "- ## Problem",
27
- "- ## Constraints",
28
- "",
29
- "RESEARCH.md format:",
30
- "- ## Affected Code",
31
- "- ## Architecture Context",
32
- "- ## Constraints & Edge Cases",
33
- "- ## Open Questions (optional)",
18
+ "- cbm_search/cbm_search_code, lsp goToDefinition/findReferences/hover, cbm_trace, ast_search for deeper analysis",
19
+ "",
20
+ "Delegate to subagents where useful (see the delegation guidance in your system prompt) — spawn multiple explores in parallel for broad searches.",
21
+ "",
22
+ "Produce two files:",
23
+ `- ${taskDir}/USER_REQUEST.md — MUST follow this exact structure:`,
24
+ " # User Request",
25
+ " <1-3 sentence distillation of what the user asked you to review>",
26
+ " ## Problem",
27
+ " <What is being reviewed and why, in the user's words. PR/issue link if provided.>",
28
+ " ## Constraints",
29
+ " <Boundaries the user explicitly stated. Only user-stated info, no agent findings.>",
30
+ `- ${taskDir}/RESEARCH.md — MUST follow this exact structure:`,
31
+ " ## Affected Code",
32
+ " <file:symbol — one-line role, per line>",
33
+ " ## Architecture Context",
34
+ " <Dense bullets. How the changed pieces connect. Sub-group by subsystem for complex reviews.>",
35
+ " ## Constraints & Edge Cases",
36
+ " - MUST: <hard requirements the change must satisfy>",
37
+ " - RISK: <bugs, regressions, edge cases the change could break>",
38
+ " ## Open Questions",
39
+ " <Low-confidence concerns or items needing user input. Omit section if none.>",
40
+ "",
41
+ "These files are validated programmatically. Missing sections or unexpected sections will be rejected.",
42
+ "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
+ "Keep USER_REQUEST.md current: update it whenever the user clarifies the review scope, so it never goes stale.",
44
+ "",
45
+ "# Optional: focused analysis artifacts",
46
+ `You may also write additional analysis files to ${taskDir}/artifacts/<name>.md`,
47
+ "for deep dives on specific findings (e.g. a subtle bug, a design concern, a risk assessment).",
48
+ "Each artifact must start with # <Title>. Content is freeform. These are reviewed alongside USER_REQUEST.md and RESEARCH.md.",
49
+ "Do NOT duplicate content already in RESEARCH.md — artifacts are for supplementary deep dives.",
34
50
  "",
35
51
  "Focus on: correctness, edge cases, style consistency, missing tests, potential bugs.",
52
+ "You are investigating and documenting findings — you do NOT apply fixes in this phase.",
53
+ "",
54
+ "# Required: findings file with an ANCHORS block",
55
+ `When you finish reviewing, you MUST write your findings to ${taskDir}/code-reviews/<unix-epoch-seconds>_final_pass-1.md (use the GENERIC write tool, NOT pp_write_state_file — code-reviews/ is not a managed state path; the <unix-epoch-seconds> prefix, e.g. the output of \`date +%s\`, keeps it ordered against automated review-cycle files). Create the code-reviews/ directory if it does not exist.`,
56
+ "That file MUST include a machine-readable `ANCHORS:` block for the accepted findings — one line per finding in EXACTLY this format (the /pp Publish step consumes these lines):",
57
+ "ANCHORS:",
58
+ "<relative/path/from/repo/root>:<line> — <one-line finding>",
59
+ "Use the real file:line for each finding — do NOT invent locations. Write `ANCHORS:` followed by `(none)` if there are no anchorable accepted findings.",
60
+ "This is a normal deliverable of every review, independent of any automated review cycle: without it, publishing has nothing to anchor.",
36
61
  ].join("\n");
37
62
  }
@@ -23,6 +23,14 @@ describe("reviewSystemPrompt apply_feedback wording", () => {
23
23
  expect(auto).not.toContain("pp_phase_complete");
24
24
  });
25
25
 
26
+ it("brainstorm apply-feedback prompt permits artifact updates and steers to the compact tools", () => {
27
+ const prompt = reviewSystemPrompt("/tmp/task", 1, "brainstorm", "autonomous");
28
+ expect(prompt).toContain("artifacts/");
29
+ expect(prompt).toContain("pp_write_state_file");
30
+ // The no-new-sections rule for the two structured files is retained.
31
+ expect(prompt).toContain("Do NOT add, rename, or remove sections in USER_REQUEST.md or RESEARCH.md");
32
+ });
33
+
26
34
  it("autonomous plan feedback folds into a new synthesized plan, not a separate fix-plan file", () => {
27
35
  // Re-review and the transition read only the latest `*synthesized*` plan, so
28
36
  // plan-phase feedback must land there.
@@ -37,6 +45,32 @@ describe("reviewSystemPrompt apply_feedback wording", () => {
37
45
  expect(impl).toContain("Implement the fixes");
38
46
  });
39
47
 
48
+ it("standalone review phase omits the fix-plan/implement/afterImplement tail", () => {
49
+ const prompt = reviewSystemPrompt("/tmp/task", 1, "review", "guided");
50
+ expect(prompt).toContain("REVIEW CYCLE");
51
+ expect(prompt).toContain("standalone review");
52
+ expect(prompt).not.toContain("Create a fix plan");
53
+ expect(prompt).not.toContain("Implement the fixes");
54
+ expect(prompt).not.toContain("Run afterImplement commands");
55
+ expect(prompt).toContain("code-reviews/");
56
+ });
57
+
58
+ it("standalone review synthesis emits an ANCHORS block and defers publishing to the user", () => {
59
+ const prompt = reviewSystemPrompt("/tmp/task", 1, "review", "guided");
60
+ expect(prompt).toContain("ANCHORS:");
61
+ expect(prompt).toContain("Do NOT publish findings now");
62
+ expect(prompt).not.toContain("do NOT call `gh` yourself");
63
+ });
64
+
65
+ it("review-cycle synthesis does NOT embed the Review Summary schema (delivered via the closing block)", () => {
66
+ // The schema is injected once by constraintsBlock -> closingBlockInstruction("review"),
67
+ // which is always prepended to the phase prompt; repeating it here would duplicate it.
68
+ for (const mode of ["guided", "autonomous"] as const) {
69
+ const prompt = reviewSystemPrompt("/tmp/task", 1, "review", mode);
70
+ expect(prompt).not.toContain("## Review Summary");
71
+ }
72
+ });
73
+
40
74
  it("points each phase at the directory its reviewers actually write to", () => {
41
75
  // plan reviewers write to plan-reviews (planning.ts) and outputs load from
42
76
  // plan-reviews (context.ts); the prompt must match, not code-reviews.
@@ -4,7 +4,7 @@ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
4
4
  import { resolvePreset, type PiPiConfig, type VariantConfig } from "../config.js";
5
5
  import { registerAgentDefinitions, spawnViaRpc, waitForCompletion } from "../agents/registry.js";
6
6
  import { createCodeReviewerAgent } from "../agents/code-reviewer.js";
7
- import { getContextDirs, getLatestSynthesizedPlan } from "../context.js";
7
+ import { getContextDirs, getLatestSynthesizedPlan, getArtifactManifest } from "../context.js";
8
8
  import type { RepoInfo } from "../repo-utils.js";
9
9
  import type { PhaseSend } from "../transition-controller.js";
10
10
 
@@ -38,13 +38,48 @@ export function reviewSystemPrompt(taskDir: string, pass: number, phase?: string
38
38
  "# Your job:",
39
39
  `1. Read ALL reviewer outputs from ${reviewsDir}/`,
40
40
  "2. Identify valid gaps and inaccuracies that would block planning",
41
- "3. If changes are needed: update ONLY the content within existing sections of USER_REQUEST.md / RESEARCH.md",
41
+ "3. If changes are needed: update the content within existing sections of USER_REQUEST.md / RESEARCH.md, and add/update artifacts/ files as needed",
42
42
  "4. If reviewers found no actionable gaps (e.g. task already done, minor suggestions only): do NOT modify the files",
43
43
  "5. Ignore suggestions that don't affect downstream planning quality",
44
44
  "",
45
45
  "USER_REQUEST.md MUST keep exactly: # User Request, ## Problem, ## Constraints",
46
46
  "RESEARCH.md MUST keep exactly: ## Affected Code, ## Architecture Context, ## Constraints & Edge Cases, ## Open Questions (optional)",
47
47
  "Any other sections will fail validation.",
48
+ "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.",
49
+ ].join("\n");
50
+ }
51
+
52
+ // A standalone review task's "review" phase has nothing to implement: the output
53
+ // is the synthesized findings. So it must NOT get the "create a fix plan /
54
+ // implement / run afterImplement" tail that implement-phase review uses. The user
55
+ // publishes findings (as file comments and/or GitHub PR comments) via the /pp
56
+ // "Publish" menu after synthesis — which consumes the ANCHORS: block below.
57
+ if (phase === "review") {
58
+ return [
59
+ `[PI-PI — REVIEW CYCLE (pass ${pass})]`,
60
+ "",
61
+ "Reviewer outputs are ready.",
62
+ `Read them from ${reviewsDir}/ and synthesize the findings.`,
63
+ "",
64
+ "You are a SYNTHESIZER: merge the reviewer outputs. Do NOT write your own review from scratch.",
65
+ `- Do NOT create the ${reviewsDirName}/ directory yourself — the extension manages it.`,
66
+ "- This is a standalone review: do NOT create a fix plan, implement fixes, run afterImplement commands, or commit.",
67
+ "- Do NOT publish findings now (no source edits, no `gh`); the user triggers publishing from the /pp menu.",
68
+ "",
69
+ "# Your job (in this order):",
70
+ `1. Read ALL reviewer outputs from ${reviewsDir}/`,
71
+ `2. Synthesize into ${reviewsDir}/<unix-epoch-seconds>_final_pass-${pass}.md (prefix with the current Unix epoch seconds, e.g. \`date +%s\`, so the file orders chronologically)`,
72
+ "3. Present the synthesis to the user",
73
+ "",
74
+ "In the synthesized final-review file you MUST include a machine-readable `ANCHORS:` block for the accepted findings — one line per finding in EXACTLY this format (a later publish step consumes these lines):",
75
+ "ANCHORS:",
76
+ "<relative/path/from/repo/root>:<line> — <one-line finding>",
77
+ "Use the `ANCHORS:` blocks the reviewers emitted as the source of file:line — do NOT invent locations. Write `ANCHORS:` followed by `(none)` if there are no anchorable accepted findings.",
78
+ "",
79
+ "PRIVACY: phrase every finding as a self-contained observation about the code. Do NOT reference private or internal details, `the ticket`, issue trackers, or internal design docs — these findings are published as PR/file comments. Say what is wrong in the code, not that it violates a private document's goal.",
80
+ // The Review Summary schema is delivered by the standardized closing block
81
+ // (constraints.ts closingBlockInstruction, gated to phase==="review"), which is
82
+ // always prepended to this prompt — so it is intentionally NOT repeated here.
48
83
  ].join("\n");
49
84
  }
50
85
 
@@ -101,7 +136,7 @@ export function reviewSystemPrompt(taskDir: string, pass: number, phase?: string
101
136
  "",
102
137
  "# Your job (in this order):",
103
138
  `1. Read ALL reviewer outputs from ${reviewsDir}/`,
104
- `2. Synthesize into ${reviewsDir}/<timestamp>_final_pass-${pass}.md`,
139
+ `2. Synthesize into ${reviewsDir}/<unix-epoch-seconds>_final_pass-${pass}.md (prefix with the current Unix epoch seconds, e.g. \`date +%s\`, so the file orders chronologically)`,
105
140
  finalStep,
106
141
  ...tail,
107
142
  ].join("\n");
@@ -131,8 +166,11 @@ export async function spawnCodeReviewers(
131
166
 
132
167
  const userRequest = readFileSync(urPath, "utf-8");
133
168
  const research = readFileSync(resPath, "utf-8");
134
- const synthesizedPlan = getLatestSynthesizedPlan(taskDir);
135
- if (!synthesizedPlan) {
169
+ // A standalone review task (phase "review") has no synthesized plan by design —
170
+ // reviewers assess the diff against USER_REQUEST.md/RESEARCH.md instead. The
171
+ // plan is only required where one legitimately exists (the implement phase).
172
+ const synthesizedPlan = phase === "review" ? undefined : (getLatestSynthesizedPlan(taskDir) ?? undefined);
173
+ if (phase !== "review" && !synthesizedPlan) {
136
174
  send(
137
175
  { customType: "pp-code-reviews-error", content: "Cannot start code review: no synthesized plan found.", display: true },
138
176
  "context",
@@ -159,7 +197,7 @@ export async function spawnCodeReviewers(
159
197
  const agent = createCodeReviewerAgent(
160
198
  variant,
161
199
  reviewerVariants,
162
- { userRequest, research, synthesizedPlan },
200
+ { userRequest, research, synthesizedPlan, manifest: getArtifactManifest(taskDir) },
163
201
  outputPath,
164
202
  contextDirs,
165
203
  reviewerPhase,
@@ -46,7 +46,8 @@ export function cancelPendingPlannotatorWait(orchestrator: Orchestrator): void {
46
46
  export function waitForPlannotatorResult(
47
47
  orchestrator: Orchestrator,
48
48
  reviewId: string | null,
49
- ): Promise<{ approved: boolean; feedback?: string }> {
49
+ timeoutMs: number | null = PLANNOTATOR_RESULT_TIMEOUT_MS,
50
+ ): Promise<{ approved: boolean; feedback?: string; error?: string }> {
50
51
  cancelPendingPlannotatorWait(orchestrator);
51
52
  const pi = orchestrator.pi;
52
53
  return new Promise((resolve, reject) => {
@@ -54,7 +55,7 @@ export function waitForPlannotatorResult(
54
55
  const unsub = pi.events.on("plannotator:review-result", (data: any) => {
55
56
  if (reviewId && data?.reviewId && data.reviewId !== reviewId) return;
56
57
  cleanup();
57
- resolve({ approved: !!data?.approved, feedback: data?.feedback });
58
+ resolve({ approved: !!data?.approved, feedback: data?.feedback, error: data?.error });
58
59
  });
59
60
  orchestrator.plannotatorUnsub = unsub;
60
61
  function cleanup() {
@@ -66,9 +67,11 @@ export function waitForPlannotatorResult(
66
67
  orchestrator.plannotatorTimer = null;
67
68
  }
68
69
  }
69
- orchestrator.plannotatorTimer = setTimeout(() => {
70
- cleanup();
71
- reject(new Error("Plannotator review timed out"));
72
- }, PLANNOTATOR_RESULT_TIMEOUT_MS);
70
+ if (timeoutMs !== null) {
71
+ orchestrator.plannotatorTimer = setTimeout(() => {
72
+ cleanup();
73
+ reject(new Error("Plannotator review timed out"));
74
+ }, timeoutMs);
75
+ }
73
76
  });
74
77
  }