@ilya-lesikov/pi-pi 0.1.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 +1835 -0
- package/3p/pi-ask-user/package.json +50 -0
- package/3p/pi-ask-user/single-select-layout.ts +203 -0
- package/3p/pi-lsp/extensions/lsp/client.ts +542 -0
- package/3p/pi-lsp/extensions/lsp/config.ts +135 -0
- package/3p/pi-lsp/extensions/lsp/effects/command.ts +42 -0
- package/3p/pi-lsp/extensions/lsp/effects/filesystem.ts +50 -0
- package/3p/pi-lsp/extensions/lsp/effects/runtime.ts +21 -0
- package/3p/pi-lsp/extensions/lsp/errors.ts +113 -0
- package/3p/pi-lsp/extensions/lsp/formatting.ts +315 -0
- package/3p/pi-lsp/extensions/lsp/index.ts +231 -0
- package/3p/pi-lsp/extensions/lsp/protocol.ts +209 -0
- package/3p/pi-lsp/extensions/lsp/retry.ts +39 -0
- package/3p/pi-lsp/extensions/lsp/tools/programs.ts +293 -0
- package/3p/pi-lsp/extensions/lsp/tools.ts +89 -0
- package/3p/pi-lsp/extensions/lsp/types.ts +243 -0
- package/3p/pi-lsp/package.json +54 -0
- package/3p/pi-plannotator/AGENTS.md +583 -0
- package/3p/pi-plannotator/apps/pi-extension/README.md +228 -0
- package/3p/pi-plannotator/apps/pi-extension/assistant-message.ts +128 -0
- package/3p/pi-plannotator/apps/pi-extension/config.test.ts +166 -0
- package/3p/pi-plannotator/apps/pi-extension/config.ts +318 -0
- package/3p/pi-plannotator/apps/pi-extension/current-pi-session.ts +147 -0
- package/3p/pi-plannotator/apps/pi-extension/index.ts +1279 -0
- package/3p/pi-plannotator/apps/pi-extension/package.json +56 -0
- package/3p/pi-plannotator/apps/pi-extension/plannotator-browser.test.ts +13 -0
- package/3p/pi-plannotator/apps/pi-extension/plannotator-browser.ts +616 -0
- package/3p/pi-plannotator/apps/pi-extension/plannotator-events.ts +342 -0
- package/3p/pi-plannotator/apps/pi-extension/plannotator.json +12 -0
- package/3p/pi-plannotator/apps/pi-extension/server/agent-jobs.ts +515 -0
- package/3p/pi-plannotator/apps/pi-extension/server/ai-runtime.ts +169 -0
- package/3p/pi-plannotator/apps/pi-extension/server/annotations.ts +85 -0
- package/3p/pi-plannotator/apps/pi-extension/server/external-annotations.ts +189 -0
- package/3p/pi-plannotator/apps/pi-extension/server/handlers.ts +210 -0
- package/3p/pi-plannotator/apps/pi-extension/server/helpers.ts +78 -0
- package/3p/pi-plannotator/apps/pi-extension/server/ide.ts +46 -0
- package/3p/pi-plannotator/apps/pi-extension/server/integrations.ts +195 -0
- package/3p/pi-plannotator/apps/pi-extension/server/network.test.ts +158 -0
- package/3p/pi-plannotator/apps/pi-extension/server/network.ts +268 -0
- package/3p/pi-plannotator/apps/pi-extension/server/pr.ts +126 -0
- package/3p/pi-plannotator/apps/pi-extension/server/project.ts +64 -0
- package/3p/pi-plannotator/apps/pi-extension/server/reference.ts +362 -0
- package/3p/pi-plannotator/apps/pi-extension/server/serverAnnotate.ts +199 -0
- package/3p/pi-plannotator/apps/pi-extension/server/serverPlan.ts +505 -0
- package/3p/pi-plannotator/apps/pi-extension/server/serverReview.ts +1229 -0
- package/3p/pi-plannotator/apps/pi-extension/server/vcs.ts +121 -0
- package/3p/pi-plannotator/apps/pi-extension/server.test.ts +966 -0
- package/3p/pi-plannotator/apps/pi-extension/server.ts +44 -0
- package/3p/pi-plannotator/apps/pi-extension/tool-scope.test.ts +88 -0
- package/3p/pi-plannotator/apps/pi-extension/tool-scope.ts +39 -0
- package/3p/pi-plannotator/apps/pi-extension/tsconfig.json +16 -0
- package/3p/pi-plannotator/apps/pi-extension/vendor.sh +48 -0
- package/3p/pi-plannotator/package.json +53 -0
- package/3p/pi-plannotator/packages/ai/ai.test.ts +1407 -0
- package/3p/pi-plannotator/packages/ai/base-session.ts +94 -0
- package/3p/pi-plannotator/packages/ai/context.ts +250 -0
- package/3p/pi-plannotator/packages/ai/endpoints.ts +326 -0
- package/3p/pi-plannotator/packages/ai/index.ts +105 -0
- package/3p/pi-plannotator/packages/ai/package.json +21 -0
- package/3p/pi-plannotator/packages/ai/provider.ts +103 -0
- package/3p/pi-plannotator/packages/ai/providers/claude-agent-sdk.ts +447 -0
- package/3p/pi-plannotator/packages/ai/providers/codex-sdk.ts +430 -0
- package/3p/pi-plannotator/packages/ai/providers/command-path.ts +115 -0
- package/3p/pi-plannotator/packages/ai/providers/opencode-sdk.ts +546 -0
- package/3p/pi-plannotator/packages/ai/providers/pi-events.ts +110 -0
- package/3p/pi-plannotator/packages/ai/providers/pi-sdk-node.ts +425 -0
- package/3p/pi-plannotator/packages/ai/providers/pi-sdk.ts +469 -0
- package/3p/pi-plannotator/packages/ai/session-manager.ts +195 -0
- package/3p/pi-plannotator/packages/ai/tsconfig.json +15 -0
- package/3p/pi-plannotator/packages/ai/types.ts +379 -0
- package/3p/pi-plannotator/packages/server/agent-jobs.ts +538 -0
- package/3p/pi-plannotator/packages/server/agent-review-message.test.ts +135 -0
- package/3p/pi-plannotator/packages/server/agent-review-message.ts +243 -0
- package/3p/pi-plannotator/packages/server/ai-runtime.ts +108 -0
- package/3p/pi-plannotator/packages/server/annotate.ts +401 -0
- package/3p/pi-plannotator/packages/server/browser.test.ts +87 -0
- package/3p/pi-plannotator/packages/server/browser.ts +245 -0
- package/3p/pi-plannotator/packages/server/claude-review.ts +352 -0
- package/3p/pi-plannotator/packages/server/code-nav.ts +73 -0
- package/3p/pi-plannotator/packages/server/codex-review-schema.json +41 -0
- package/3p/pi-plannotator/packages/server/codex-review.ts +330 -0
- package/3p/pi-plannotator/packages/server/config.ts +8 -0
- package/3p/pi-plannotator/packages/server/draft.ts +1 -0
- package/3p/pi-plannotator/packages/server/editor-annotations.ts +76 -0
- package/3p/pi-plannotator/packages/server/external-annotations.test.ts +18 -0
- package/3p/pi-plannotator/packages/server/external-annotations.ts +207 -0
- package/3p/pi-plannotator/packages/server/git.ts +141 -0
- package/3p/pi-plannotator/packages/server/goal-setup.test.ts +55 -0
- package/3p/pi-plannotator/packages/server/goal-setup.ts +248 -0
- package/3p/pi-plannotator/packages/server/ide.ts +43 -0
- package/3p/pi-plannotator/packages/server/image.test.ts +63 -0
- package/3p/pi-plannotator/packages/server/image.ts +66 -0
- package/3p/pi-plannotator/packages/server/index.ts +648 -0
- package/3p/pi-plannotator/packages/server/integrations.test.ts +172 -0
- package/3p/pi-plannotator/packages/server/integrations.ts +214 -0
- package/3p/pi-plannotator/packages/server/jj.test.ts +69 -0
- package/3p/pi-plannotator/packages/server/jj.ts +86 -0
- package/3p/pi-plannotator/packages/server/p4.ts +417 -0
- package/3p/pi-plannotator/packages/server/package.json +40 -0
- package/3p/pi-plannotator/packages/server/path-utils.ts +18 -0
- package/3p/pi-plannotator/packages/server/pr.ts +146 -0
- package/3p/pi-plannotator/packages/server/project.test.ts +115 -0
- package/3p/pi-plannotator/packages/server/project.ts +45 -0
- package/3p/pi-plannotator/packages/server/reference-handlers.ts +404 -0
- package/3p/pi-plannotator/packages/server/remote.test.ts +150 -0
- package/3p/pi-plannotator/packages/server/remote.ts +74 -0
- package/3p/pi-plannotator/packages/server/repo.ts +80 -0
- package/3p/pi-plannotator/packages/server/resolve-file.test.ts +310 -0
- package/3p/pi-plannotator/packages/server/review-workspace.test.ts +1032 -0
- package/3p/pi-plannotator/packages/server/review-workspace.ts +48 -0
- package/3p/pi-plannotator/packages/server/review.ts +1318 -0
- package/3p/pi-plannotator/packages/server/sessions.ts +111 -0
- package/3p/pi-plannotator/packages/server/share-url.ts +53 -0
- package/3p/pi-plannotator/packages/server/shared-handlers.test.ts +43 -0
- package/3p/pi-plannotator/packages/server/shared-handlers.ts +177 -0
- package/3p/pi-plannotator/packages/server/storage.test.ts +176 -0
- package/3p/pi-plannotator/packages/server/storage.ts +17 -0
- package/3p/pi-plannotator/packages/server/tour/tour-review.test.ts +146 -0
- package/3p/pi-plannotator/packages/server/tour/tour-review.ts +604 -0
- package/3p/pi-plannotator/packages/server/tsconfig.json +15 -0
- package/3p/pi-plannotator/packages/server/vcs.test.ts +48 -0
- package/3p/pi-plannotator/packages/server/vcs.ts +80 -0
- package/3p/pi-plannotator/packages/shared/agent-jobs.ts +132 -0
- package/3p/pi-plannotator/packages/shared/agents.ts +53 -0
- package/3p/pi-plannotator/packages/shared/annotate-args.test.ts +386 -0
- package/3p/pi-plannotator/packages/shared/annotate-args.ts +107 -0
- package/3p/pi-plannotator/packages/shared/at-reference.test.ts +99 -0
- package/3p/pi-plannotator/packages/shared/at-reference.ts +52 -0
- package/3p/pi-plannotator/packages/shared/checklist.ts +52 -0
- package/3p/pi-plannotator/packages/shared/code-file.test.ts +112 -0
- package/3p/pi-plannotator/packages/shared/code-file.ts +41 -0
- package/3p/pi-plannotator/packages/shared/code-nav.test.ts +515 -0
- package/3p/pi-plannotator/packages/shared/code-nav.ts +436 -0
- package/3p/pi-plannotator/packages/shared/compress.ts +51 -0
- package/3p/pi-plannotator/packages/shared/config.ts +262 -0
- package/3p/pi-plannotator/packages/shared/crypto.test.ts +172 -0
- package/3p/pi-plannotator/packages/shared/crypto.ts +97 -0
- package/3p/pi-plannotator/packages/shared/data-dir.ts +42 -0
- package/3p/pi-plannotator/packages/shared/diff-paths.test.ts +30 -0
- package/3p/pi-plannotator/packages/shared/diff-paths.ts +137 -0
- package/3p/pi-plannotator/packages/shared/draft.ts +64 -0
- package/3p/pi-plannotator/packages/shared/external-annotation.ts +397 -0
- package/3p/pi-plannotator/packages/shared/extract-code-paths.test.ts +59 -0
- package/3p/pi-plannotator/packages/shared/extract-code-paths.ts +66 -0
- package/3p/pi-plannotator/packages/shared/favicon.ts +5 -0
- package/3p/pi-plannotator/packages/shared/feedback-templates.test.ts +65 -0
- package/3p/pi-plannotator/packages/shared/feedback-templates.ts +29 -0
- package/3p/pi-plannotator/packages/shared/goal-setup.test.ts +231 -0
- package/3p/pi-plannotator/packages/shared/goal-setup.ts +336 -0
- package/3p/pi-plannotator/packages/shared/html-to-markdown.test.ts +62 -0
- package/3p/pi-plannotator/packages/shared/html-to-markdown.ts +32 -0
- package/3p/pi-plannotator/packages/shared/improvement-hooks.test.ts +135 -0
- package/3p/pi-plannotator/packages/shared/improvement-hooks.ts +115 -0
- package/3p/pi-plannotator/packages/shared/integrations-common.ts +243 -0
- package/3p/pi-plannotator/packages/shared/jj-core.test.ts +236 -0
- package/3p/pi-plannotator/packages/shared/jj-core.ts +433 -0
- package/3p/pi-plannotator/packages/shared/package.json +55 -0
- package/3p/pi-plannotator/packages/shared/pfm-reminder.test.ts +88 -0
- package/3p/pi-plannotator/packages/shared/pfm-reminder.ts +80 -0
- package/3p/pi-plannotator/packages/shared/pr-github.ts +661 -0
- package/3p/pi-plannotator/packages/shared/pr-gitlab.test.ts +202 -0
- package/3p/pi-plannotator/packages/shared/pr-gitlab.ts +620 -0
- package/3p/pi-plannotator/packages/shared/pr-provider.test.ts +266 -0
- package/3p/pi-plannotator/packages/shared/pr-provider.ts +123 -0
- package/3p/pi-plannotator/packages/shared/pr-stack.test.ts +104 -0
- package/3p/pi-plannotator/packages/shared/pr-stack.ts +194 -0
- package/3p/pi-plannotator/packages/shared/pr-types.ts +326 -0
- package/3p/pi-plannotator/packages/shared/project.ts +71 -0
- package/3p/pi-plannotator/packages/shared/prompts-integration.test.ts +421 -0
- package/3p/pi-plannotator/packages/shared/prompts.test.ts +504 -0
- package/3p/pi-plannotator/packages/shared/prompts.ts +247 -0
- package/3p/pi-plannotator/packages/shared/reference-common.ts +87 -0
- package/3p/pi-plannotator/packages/shared/repo.ts +71 -0
- package/3p/pi-plannotator/packages/shared/resolve-file.test.ts +113 -0
- package/3p/pi-plannotator/packages/shared/resolve-file.ts +509 -0
- package/3p/pi-plannotator/packages/shared/review-args.test.ts +64 -0
- package/3p/pi-plannotator/packages/shared/review-args.ts +85 -0
- package/3p/pi-plannotator/packages/shared/review-core.test.ts +286 -0
- package/3p/pi-plannotator/packages/shared/review-core.ts +895 -0
- package/3p/pi-plannotator/packages/shared/review-workspace-node.ts +230 -0
- package/3p/pi-plannotator/packages/shared/review-workspace.ts +436 -0
- package/3p/pi-plannotator/packages/shared/semantic-diff-types.ts +76 -0
- package/3p/pi-plannotator/packages/shared/semantic-diff.test.ts +322 -0
- package/3p/pi-plannotator/packages/shared/semantic-diff.ts +520 -0
- package/3p/pi-plannotator/packages/shared/storage.ts +378 -0
- package/3p/pi-plannotator/packages/shared/tour.ts +61 -0
- package/3p/pi-plannotator/packages/shared/tsconfig.json +15 -0
- package/3p/pi-plannotator/packages/shared/types.ts +29 -0
- package/3p/pi-plannotator/packages/shared/url-to-markdown.test.ts +177 -0
- package/3p/pi-plannotator/packages/shared/url-to-markdown.ts +351 -0
- package/3p/pi-plannotator/packages/shared/vcs-core.test.ts +332 -0
- package/3p/pi-plannotator/packages/shared/vcs-core.ts +482 -0
- package/3p/pi-plannotator/packages/shared/worktree-pool.test.ts +162 -0
- package/3p/pi-plannotator/packages/shared/worktree-pool.ts +103 -0
- package/3p/pi-plannotator/packages/shared/worktree.ts +119 -0
- package/3p/pi-subagents/package.json +50 -0
- package/3p/pi-subagents/src/agent-manager.ts +413 -0
- package/3p/pi-subagents/src/agent-runner.ts +502 -0
- package/3p/pi-subagents/src/agent-types.ts +248 -0
- package/3p/pi-subagents/src/context.ts +58 -0
- package/3p/pi-subagents/src/cross-extension-rpc.ts +135 -0
- package/3p/pi-subagents/src/custom-agents.ts +137 -0
- package/3p/pi-subagents/src/default-agents.ts +144 -0
- package/3p/pi-subagents/src/env.ts +33 -0
- package/3p/pi-subagents/src/group-join.ts +141 -0
- package/3p/pi-subagents/src/index.ts +1811 -0
- package/3p/pi-subagents/src/invocation-config.ts +40 -0
- package/3p/pi-subagents/src/memory.ts +165 -0
- package/3p/pi-subagents/src/model-resolver.ts +81 -0
- package/3p/pi-subagents/src/output-file.ts +77 -0
- package/3p/pi-subagents/src/prompts.ts +85 -0
- package/3p/pi-subagents/src/skill-loader.ts +79 -0
- package/3p/pi-subagents/src/types.ts +111 -0
- package/3p/pi-subagents/src/ui/agent-widget.ts +496 -0
- package/3p/pi-subagents/src/ui/conversation-viewer.ts +250 -0
- package/3p/pi-subagents/src/worktree.ts +162 -0
- package/3p/pi-tasks/package.json +52 -0
- package/3p/pi-tasks/src/auto-clear.ts +91 -0
- package/3p/pi-tasks/src/index.ts +1150 -0
- package/3p/pi-tasks/src/process-tracker.ts +140 -0
- package/3p/pi-tasks/src/reminder-cadence.ts +90 -0
- package/3p/pi-tasks/src/task-store.ts +324 -0
- package/3p/pi-tasks/src/tasks-config.ts +27 -0
- package/3p/pi-tasks/src/types.ts +40 -0
- package/3p/pi-tasks/src/ui/settings-menu.ts +152 -0
- package/3p/pi-tasks/src/ui/task-widget.ts +296 -0
- package/AGENTS.md +28 -0
- package/LICENSE +201 -0
- package/extensions/orchestrator/agents/brainstorm-reviewer.ts +77 -0
- package/extensions/orchestrator/agents/code-reviewer.ts +89 -0
- package/extensions/orchestrator/agents/explore.ts +33 -0
- package/extensions/orchestrator/agents/librarian.ts +43 -0
- package/extensions/orchestrator/agents/plan-reviewer.ts +75 -0
- package/extensions/orchestrator/agents/planner.ts +61 -0
- package/extensions/orchestrator/agents/registry.ts +156 -0
- package/extensions/orchestrator/agents/task.ts +50 -0
- package/extensions/orchestrator/agents/tool-routing.ts +84 -0
- package/extensions/orchestrator/ast-search.ts +85 -0
- package/extensions/orchestrator/cbm.ts +330 -0
- package/extensions/orchestrator/command-handlers.test.ts +163 -0
- package/extensions/orchestrator/command-handlers.ts +116 -0
- package/extensions/orchestrator/commands.test.ts +81 -0
- package/extensions/orchestrator/commands.ts +75 -0
- package/extensions/orchestrator/config.test.ts +221 -0
- package/extensions/orchestrator/config.ts +230 -0
- package/extensions/orchestrator/context.test.ts +293 -0
- package/extensions/orchestrator/context.ts +206 -0
- package/extensions/orchestrator/event-handlers.test.ts +190 -0
- package/extensions/orchestrator/event-handlers.ts +1416 -0
- package/extensions/orchestrator/exa.ts +104 -0
- package/extensions/orchestrator/flant-infra.ts +486 -0
- package/extensions/orchestrator/index.ts +80 -0
- package/extensions/orchestrator/integration.test.ts +1214 -0
- package/extensions/orchestrator/orchestrator.test.ts +252 -0
- package/extensions/orchestrator/orchestrator.ts +538 -0
- package/extensions/orchestrator/phases/brainstorm.test.ts +15 -0
- package/extensions/orchestrator/phases/brainstorm.ts +273 -0
- package/extensions/orchestrator/phases/implementation.ts +40 -0
- package/extensions/orchestrator/phases/machine.test.ts +293 -0
- package/extensions/orchestrator/phases/machine.ts +209 -0
- package/extensions/orchestrator/phases/planning.ts +255 -0
- package/extensions/orchestrator/phases/review.ts +193 -0
- package/extensions/orchestrator/plannotator.ts +56 -0
- package/extensions/orchestrator/pp-menu.ts +866 -0
- package/extensions/orchestrator/state.test.ts +343 -0
- package/extensions/orchestrator/state.ts +237 -0
- package/extensions/orchestrator/validate-artifacts.test.ts +88 -0
- package/extensions/orchestrator/validate-artifacts.ts +272 -0
- package/extensions/orchestrator/vendor.d.ts +26 -0
- package/package.json +73 -0
- package/scripts/postinstall.sh +18 -0
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import type { TaskType, Phase } from "../state.js";
|
|
4
|
+
import { getLatestSynthesizedPlan } from "../context.js";
|
|
5
|
+
import { validatePlan, validateResearch, validateUserRequest } from "../validate-artifacts.js";
|
|
6
|
+
|
|
7
|
+
const USER_REQUEST_TEMPLATE = [
|
|
8
|
+
"# User Request",
|
|
9
|
+
"<1-3 sentence distillation>",
|
|
10
|
+
"",
|
|
11
|
+
"## Problem",
|
|
12
|
+
"<What's broken, user's perspective>",
|
|
13
|
+
"",
|
|
14
|
+
"## Constraints",
|
|
15
|
+
"<User-stated boundaries>",
|
|
16
|
+
].join("\n");
|
|
17
|
+
|
|
18
|
+
const RESEARCH_TEMPLATE = [
|
|
19
|
+
"## Affected Code",
|
|
20
|
+
"<file:symbol — one-line role, per line>",
|
|
21
|
+
"",
|
|
22
|
+
"## Architecture Context",
|
|
23
|
+
"<Dense bullets. How affected pieces connect.>",
|
|
24
|
+
"",
|
|
25
|
+
"## Constraints & Edge Cases",
|
|
26
|
+
"- MUST: <hard requirements discovered from code>",
|
|
27
|
+
"- RISK: <things that could break>",
|
|
28
|
+
"",
|
|
29
|
+
"## Open Questions",
|
|
30
|
+
"<Unresolved items needing user input. Omit section if none.>",
|
|
31
|
+
].join("\n");
|
|
32
|
+
|
|
33
|
+
const PLAN_TEMPLATE = [
|
|
34
|
+
"# Plan",
|
|
35
|
+
"",
|
|
36
|
+
"## Scope",
|
|
37
|
+
"<2-4 lines summarizing what changes and what doesn't>",
|
|
38
|
+
"",
|
|
39
|
+
"## Checklist",
|
|
40
|
+
"- [ ] <Outcome> — Done when: <observable condition>",
|
|
41
|
+
"",
|
|
42
|
+
"## Blockers",
|
|
43
|
+
"<Unresolved issues. Omit section if none.>",
|
|
44
|
+
].join("\n");
|
|
45
|
+
|
|
46
|
+
function isMissingOrEmpty(filePath: string): boolean {
|
|
47
|
+
return !existsSync(filePath) || readFileSync(filePath, "utf-8").trim().length === 0;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function formatValidationErrors(fileName: string, errors: string[], expectedStructure: string): string {
|
|
51
|
+
return `${fileName} validation failed:\n${errors.map((error) => `- ${error}`).join("\n")}\n\nExpected structure:\n${expectedStructure}`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const TRANSITIONS: Record<TaskType, Record<string, string[]>> = {
|
|
55
|
+
implement: {
|
|
56
|
+
brainstorm: ["plan"],
|
|
57
|
+
plan: ["implement"],
|
|
58
|
+
implement: ["done"],
|
|
59
|
+
},
|
|
60
|
+
debug: {
|
|
61
|
+
debug: ["plan"],
|
|
62
|
+
plan: ["implement"],
|
|
63
|
+
implement: ["done"],
|
|
64
|
+
},
|
|
65
|
+
brainstorm: {
|
|
66
|
+
brainstorm: ["plan"],
|
|
67
|
+
plan: ["implement"],
|
|
68
|
+
implement: ["done"],
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export function canTransition(taskType: TaskType, from: Phase, to: Phase): boolean {
|
|
73
|
+
const targets = TRANSITIONS[taskType]?.[from];
|
|
74
|
+
return targets?.includes(to) ?? false;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function nextPhase(taskType: TaskType, from: Phase): Phase | null {
|
|
78
|
+
const targets = TRANSITIONS[taskType]?.[from];
|
|
79
|
+
return targets?.[0] as Phase | null ?? null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function validateExitCriteria(
|
|
83
|
+
taskDir: string,
|
|
84
|
+
taskType: TaskType,
|
|
85
|
+
phase: Phase,
|
|
86
|
+
): { ok: true } | { ok: false; reason: string } {
|
|
87
|
+
switch (phase) {
|
|
88
|
+
case "brainstorm": {
|
|
89
|
+
const ur = join(taskDir, "USER_REQUEST.md");
|
|
90
|
+
const res = join(taskDir, "RESEARCH.md");
|
|
91
|
+
if (isMissingOrEmpty(ur)) {
|
|
92
|
+
return { ok: false, reason: "USER_REQUEST.md does not exist or is empty" };
|
|
93
|
+
}
|
|
94
|
+
if (isMissingOrEmpty(res)) {
|
|
95
|
+
return { ok: false, reason: "RESEARCH.md does not exist or is empty" };
|
|
96
|
+
}
|
|
97
|
+
const urContent = readFileSync(ur, "utf-8");
|
|
98
|
+
const resContent = readFileSync(res, "utf-8");
|
|
99
|
+
|
|
100
|
+
const userRequestValidation = validateUserRequest(urContent);
|
|
101
|
+
if (!userRequestValidation.ok) {
|
|
102
|
+
return {
|
|
103
|
+
ok: false,
|
|
104
|
+
reason: formatValidationErrors("USER_REQUEST.md", userRequestValidation.errors, USER_REQUEST_TEMPLATE),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const researchValidation = validateResearch(resContent);
|
|
109
|
+
if (!researchValidation.ok) {
|
|
110
|
+
return {
|
|
111
|
+
ok: false,
|
|
112
|
+
reason: formatValidationErrors("RESEARCH.md", researchValidation.errors, RESEARCH_TEMPLATE),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return { ok: true };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
case "plan": {
|
|
120
|
+
const plan = getLatestSynthesizedPlan(taskDir);
|
|
121
|
+
if (!plan) {
|
|
122
|
+
return { ok: false, reason: "No synthesized plan found in plans/" };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const planValidation = validatePlan(plan);
|
|
126
|
+
if (!planValidation.ok) {
|
|
127
|
+
return {
|
|
128
|
+
ok: false,
|
|
129
|
+
reason: formatValidationErrors("Synthesized plan", planValidation.errors, PLAN_TEMPLATE),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return { ok: true };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
case "implement": {
|
|
137
|
+
const content = getLatestSynthesizedPlan(taskDir);
|
|
138
|
+
if (!content) {
|
|
139
|
+
return { ok: false, reason: "No synthesized plan found" };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const planValidation = validatePlan(content);
|
|
143
|
+
if (!planValidation.ok) {
|
|
144
|
+
return {
|
|
145
|
+
ok: false,
|
|
146
|
+
reason: formatValidationErrors("Synthesized plan", planValidation.errors, PLAN_TEMPLATE),
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const checklistStart = content.indexOf("## Checklist");
|
|
151
|
+
const checklistEnd = checklistStart === -1 ? -1 : content.indexOf("\n## ", checklistStart + 1);
|
|
152
|
+
const checklistContent = checklistStart === -1
|
|
153
|
+
? ""
|
|
154
|
+
: checklistEnd === -1
|
|
155
|
+
? content.slice(checklistStart)
|
|
156
|
+
: content.slice(checklistStart, checklistEnd);
|
|
157
|
+
const unchecked = checklistContent.match(/^- \[ \]/gm);
|
|
158
|
+
if (unchecked && unchecked.length > 0) {
|
|
159
|
+
return { ok: false, reason: `${unchecked.length} plan items still unchecked` };
|
|
160
|
+
}
|
|
161
|
+
return { ok: true };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
case "debug": {
|
|
165
|
+
const ur = join(taskDir, "USER_REQUEST.md");
|
|
166
|
+
const res = join(taskDir, "RESEARCH.md");
|
|
167
|
+
if (isMissingOrEmpty(ur)) {
|
|
168
|
+
return { ok: false, reason: "USER_REQUEST.md does not exist or is empty" };
|
|
169
|
+
}
|
|
170
|
+
if (isMissingOrEmpty(res)) {
|
|
171
|
+
return { ok: false, reason: "RESEARCH.md does not exist or is empty" };
|
|
172
|
+
}
|
|
173
|
+
const urContent = readFileSync(ur, "utf-8");
|
|
174
|
+
const resContent = readFileSync(res, "utf-8");
|
|
175
|
+
|
|
176
|
+
const userRequestValidation = validateUserRequest(urContent);
|
|
177
|
+
if (!userRequestValidation.ok) {
|
|
178
|
+
return {
|
|
179
|
+
ok: false,
|
|
180
|
+
reason: formatValidationErrors("USER_REQUEST.md", userRequestValidation.errors, USER_REQUEST_TEMPLATE),
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const researchValidation = validateResearch(resContent);
|
|
185
|
+
if (!researchValidation.ok) {
|
|
186
|
+
return {
|
|
187
|
+
ok: false,
|
|
188
|
+
reason: formatValidationErrors("RESEARCH.md", researchValidation.errors, RESEARCH_TEMPLATE),
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return { ok: true };
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
default:
|
|
196
|
+
return { ok: false, reason: `Unknown phase: ${phase}` };
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export function phasePipeline(taskType: TaskType): Phase[] {
|
|
201
|
+
switch (taskType) {
|
|
202
|
+
case "implement":
|
|
203
|
+
return ["brainstorm", "plan", "implement", "done"];
|
|
204
|
+
case "debug":
|
|
205
|
+
return ["debug", "plan", "implement", "done"];
|
|
206
|
+
case "brainstorm":
|
|
207
|
+
return ["brainstorm", "plan", "implement", "done"];
|
|
208
|
+
}
|
|
209
|
+
}
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { readFileSync, existsSync, mkdirSync, readdirSync, statSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
4
|
+
import type { PiPiConfig } from "../config.js";
|
|
5
|
+
import { registerAgentDefinitions, spawnViaRpc, waitForCompletion } from "../agents/registry.js";
|
|
6
|
+
import { createPlannerAgent } from "../agents/planner.js";
|
|
7
|
+
import { createPlanReviewerAgent } from "../agents/plan-reviewer.js";
|
|
8
|
+
import { getLatestSynthesizedPlan } from "../context.js";
|
|
9
|
+
import { validatePlan } from "../validate-artifacts.js";
|
|
10
|
+
|
|
11
|
+
export function planningSystemPrompt(taskDir: string): string {
|
|
12
|
+
const plansDir = join(taskDir, "plans");
|
|
13
|
+
return [
|
|
14
|
+
"[PI-PI — PLAN PHASE]",
|
|
15
|
+
"",
|
|
16
|
+
"Planning subagents are working in parallel to create plans.",
|
|
17
|
+
`They will write their outputs to ${plansDir}/. Wait for the notification that all planners completed.`,
|
|
18
|
+
"",
|
|
19
|
+
"# FORBIDDEN — do NOT do any of these:",
|
|
20
|
+
"- Do NOT write your own plan from scratch. You are a SYNTHESIZER, not a planner.",
|
|
21
|
+
"- Do NOT create the plans/ directory yourself — the extension manages it.",
|
|
22
|
+
"- Do NOT check the plans directory yourself — wait for the notification that all planners completed.",
|
|
23
|
+
"- Do NOT read project source code directly — the planner outputs already contain the analysis.",
|
|
24
|
+
"- Do NOT call plannotator_submit_plan — plan review is handled by the user via /pp menu.",
|
|
25
|
+
"",
|
|
26
|
+
"# Your job (in this order):",
|
|
27
|
+
"1. Wait for the notification that says 'All planners completed' — do NOT proceed before this",
|
|
28
|
+
`2. Read ALL planner outputs from ${plansDir}/`,
|
|
29
|
+
"3. USER_REQUEST.md and RESEARCH.md are already provided in your context above — do NOT re-read them from disk",
|
|
30
|
+
`4. Synthesize all plans into a single plan at ${plansDir}/<timestamp>_synthesized.md`,
|
|
31
|
+
"5. Ask the user for clarifications if unsure about anything",
|
|
32
|
+
"",
|
|
33
|
+
"Plan format:",
|
|
34
|
+
"- Start with # Plan",
|
|
35
|
+
"- ## Scope: 2-4 lines — what changes, what doesn't, critical constraints",
|
|
36
|
+
"- ## Checklist: each item is - [ ] <outcome> — Done when: <observable condition>",
|
|
37
|
+
" Each item = one independently verifiable outcome. No code snippets or file-by-file instructions.",
|
|
38
|
+
"- ## Blockers: unresolved issues blocking implementation (omit if none)",
|
|
39
|
+
"- No other top-level sections allowed",
|
|
40
|
+
"- Describe outcomes, not code-level mechanics",
|
|
41
|
+
"",
|
|
42
|
+
"When the synthesized plan is ready, call pp_phase_complete with a brief summary of the plan.",
|
|
43
|
+
].join("\n");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export async function spawnPlanners(
|
|
47
|
+
pi: ExtensionAPI,
|
|
48
|
+
cwd: string,
|
|
49
|
+
taskDir: string,
|
|
50
|
+
taskId: string,
|
|
51
|
+
config: PiPiConfig,
|
|
52
|
+
): Promise<{ spawned: number; agentIds: string[]; failedVariants: string[] }> {
|
|
53
|
+
const urPath = join(taskDir, "USER_REQUEST.md");
|
|
54
|
+
const resPath = join(taskDir, "RESEARCH.md");
|
|
55
|
+
if (!existsSync(urPath) || !existsSync(resPath)) return { spawned: 0, agentIds: [], failedVariants: [] };
|
|
56
|
+
|
|
57
|
+
const userRequest = readFileSync(urPath, "utf-8");
|
|
58
|
+
const research = readFileSync(resPath, "utf-8");
|
|
59
|
+
|
|
60
|
+
const plansDir = join(taskDir, "plans");
|
|
61
|
+
if (!existsSync(plansDir)) {
|
|
62
|
+
mkdirSync(plansDir, { recursive: true });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const timestamp = Math.floor(Date.now() / 1000);
|
|
66
|
+
const enabledVariants = Object.entries(config.planners).filter(([, v]) => v.enabled);
|
|
67
|
+
const agentIds: string[] = [];
|
|
68
|
+
const failedVariants: string[] = [];
|
|
69
|
+
const results: Promise<void>[] = [];
|
|
70
|
+
|
|
71
|
+
for (const [variant] of enabledVariants) {
|
|
72
|
+
const outputPath = join(plansDir, `${timestamp}_${variant}.md`);
|
|
73
|
+
const agent = createPlannerAgent(variant, config, { userRequest, research }, outputPath);
|
|
74
|
+
|
|
75
|
+
registerAgentDefinitions(pi, [{ type: "planner", variant, ...agent }]);
|
|
76
|
+
|
|
77
|
+
results.push(
|
|
78
|
+
(async () => {
|
|
79
|
+
try {
|
|
80
|
+
const { id } = await spawnViaRpc(pi, `planner_${variant}`, "Begin planning.", {
|
|
81
|
+
description: `Planner (${variant})`,
|
|
82
|
+
validateCompletion: () => {
|
|
83
|
+
if (!existsSync(outputPath) || statSync(outputPath).size === 0) {
|
|
84
|
+
return `You finished without writing your plan file. Write your plan to: ${outputPath}`;
|
|
85
|
+
}
|
|
86
|
+
const content = readFileSync(outputPath, "utf-8");
|
|
87
|
+
const validation = validatePlan(content);
|
|
88
|
+
if (!validation.ok) {
|
|
89
|
+
return `Plan validation failed:\n${validation.errors.join("\n")}\n\nFix the plan at ${outputPath}`;
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
agentIds.push(id);
|
|
94
|
+
await waitForCompletion(pi, id);
|
|
95
|
+
|
|
96
|
+
if (existsSync(outputPath)) {
|
|
97
|
+
const planContent = readFileSync(outputPath, "utf-8");
|
|
98
|
+
const validation = validatePlan(planContent);
|
|
99
|
+
if (!validation.ok) {
|
|
100
|
+
pi.sendMessage(
|
|
101
|
+
{
|
|
102
|
+
customType: "pp-planner-error",
|
|
103
|
+
content: `Planner "${variant}" produced invalid plan (errors were shown to the agent but it did not fix them): ${validation.errors.join("; ")}`,
|
|
104
|
+
display: true,
|
|
105
|
+
},
|
|
106
|
+
{ deliverAs: "steer" },
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
} catch (err: any) {
|
|
111
|
+
failedVariants.push(variant);
|
|
112
|
+
pi.sendMessage(
|
|
113
|
+
{
|
|
114
|
+
customType: "pp-planner-error",
|
|
115
|
+
content: `Planner variant "${variant}" failed: ${err.message}`,
|
|
116
|
+
display: true,
|
|
117
|
+
},
|
|
118
|
+
{ deliverAs: "steer" },
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
})(),
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
await Promise.allSettled(results);
|
|
126
|
+
|
|
127
|
+
const planFiles = existsSync(plansDir) ? readdirSync(plansDir).filter((f) => !f.includes("synthesized") && !f.includes("review_")) : [];
|
|
128
|
+
if (planFiles.length > 0) {
|
|
129
|
+
pi.sendMessage(
|
|
130
|
+
{
|
|
131
|
+
customType: "pp-planners-done",
|
|
132
|
+
content: [
|
|
133
|
+
`${planFiles.length} planner(s) completed. Plans available in ${plansDir}:`,
|
|
134
|
+
...planFiles.map((f) => ` - ${f}`),
|
|
135
|
+
"",
|
|
136
|
+
"Read all plans and synthesize them into a single plan.",
|
|
137
|
+
].join("\n"),
|
|
138
|
+
display: true,
|
|
139
|
+
},
|
|
140
|
+
{ deliverAs: "steer" },
|
|
141
|
+
);
|
|
142
|
+
} else {
|
|
143
|
+
pi.sendMessage(
|
|
144
|
+
{
|
|
145
|
+
customType: "pp-planners-error",
|
|
146
|
+
content: [
|
|
147
|
+
"All planner variants failed — no plan files were produced.",
|
|
148
|
+
"You must create the plan yourself based on USER_REQUEST.md and RESEARCH.md.",
|
|
149
|
+
].join("\n"),
|
|
150
|
+
display: true,
|
|
151
|
+
},
|
|
152
|
+
{ deliverAs: "steer" },
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return { spawned: enabledVariants.length, agentIds, failedVariants };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export async function spawnPlanReviewers(
|
|
160
|
+
pi: ExtensionAPI,
|
|
161
|
+
cwd: string,
|
|
162
|
+
taskDir: string,
|
|
163
|
+
taskId: string,
|
|
164
|
+
config: PiPiConfig,
|
|
165
|
+
): Promise<{ spawned: number; files: string[]; agentIds: string[]; failedVariants: string[] }> {
|
|
166
|
+
const urPath = join(taskDir, "USER_REQUEST.md");
|
|
167
|
+
const resPath = join(taskDir, "RESEARCH.md");
|
|
168
|
+
if (!existsSync(urPath) || !existsSync(resPath)) return { spawned: 0, files: [], agentIds: [], failedVariants: [] };
|
|
169
|
+
|
|
170
|
+
const userRequest = readFileSync(urPath, "utf-8");
|
|
171
|
+
const research = readFileSync(resPath, "utf-8");
|
|
172
|
+
const synthesizedPlan = getLatestSynthesizedPlan(taskDir);
|
|
173
|
+
if (!synthesizedPlan) return { spawned: 0, files: [], agentIds: [], failedVariants: [] };
|
|
174
|
+
|
|
175
|
+
const planReviewsDir = join(taskDir, "plan-reviews");
|
|
176
|
+
if (!existsSync(planReviewsDir)) {
|
|
177
|
+
mkdirSync(planReviewsDir, { recursive: true });
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const timestamp = Math.floor(Date.now() / 1000);
|
|
181
|
+
const enabledVariants = Object.entries(config.planReviewers).filter(([, v]) => v.enabled);
|
|
182
|
+
const reviewFiles: string[] = [];
|
|
183
|
+
const agentIds: string[] = [];
|
|
184
|
+
const failedVariants: string[] = [];
|
|
185
|
+
|
|
186
|
+
const results: Promise<void>[] = [];
|
|
187
|
+
for (const [variant] of enabledVariants) {
|
|
188
|
+
const outputPath = join(planReviewsDir, `${timestamp}_${variant}.md`);
|
|
189
|
+
reviewFiles.push(outputPath);
|
|
190
|
+
|
|
191
|
+
const agent = createPlanReviewerAgent(variant, config, { userRequest, research, synthesizedPlan }, outputPath);
|
|
192
|
+
|
|
193
|
+
registerAgentDefinitions(pi, [{ type: "plan_reviewer", variant, ...agent }]);
|
|
194
|
+
|
|
195
|
+
results.push(
|
|
196
|
+
(async () => {
|
|
197
|
+
try {
|
|
198
|
+
const { id } = await spawnViaRpc(pi, `plan_reviewer_${variant}`, "Begin plan review.", {
|
|
199
|
+
description: `Plan reviewer (${variant})`,
|
|
200
|
+
validateCompletion: () => {
|
|
201
|
+
if (!existsSync(outputPath) || statSync(outputPath).size === 0) {
|
|
202
|
+
return `You finished without writing your review file. Write your review to: ${outputPath}`;
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
});
|
|
206
|
+
agentIds.push(id);
|
|
207
|
+
await waitForCompletion(pi, id);
|
|
208
|
+
} catch (err: any) {
|
|
209
|
+
failedVariants.push(variant);
|
|
210
|
+
pi.sendMessage(
|
|
211
|
+
{
|
|
212
|
+
customType: "pp-plan-reviewer-error",
|
|
213
|
+
content: `Plan reviewer variant "${variant}" failed: ${err.message}`,
|
|
214
|
+
display: true,
|
|
215
|
+
},
|
|
216
|
+
{ deliverAs: "steer" },
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
})(),
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
await Promise.allSettled(results);
|
|
224
|
+
|
|
225
|
+
const actualReviewFiles = existsSync(planReviewsDir)
|
|
226
|
+
? readdirSync(planReviewsDir).filter((f) => f.startsWith(`${timestamp}`) && f.endsWith(".md"))
|
|
227
|
+
: [];
|
|
228
|
+
|
|
229
|
+
if (actualReviewFiles.length > 0) {
|
|
230
|
+
pi.sendMessage(
|
|
231
|
+
{
|
|
232
|
+
customType: "pp-plan-reviews-done",
|
|
233
|
+
content: [
|
|
234
|
+
`${actualReviewFiles.length} plan reviewer(s) completed. Reviews in ${planReviewsDir}:`,
|
|
235
|
+
...actualReviewFiles.map((f) => ` - ${f}`),
|
|
236
|
+
"",
|
|
237
|
+
"Read all plan reviews and incorporate feedback into the synthesized plan if needed.",
|
|
238
|
+
].join("\n"),
|
|
239
|
+
display: true,
|
|
240
|
+
},
|
|
241
|
+
{ deliverAs: "steer" },
|
|
242
|
+
);
|
|
243
|
+
} else if (enabledVariants.length > 0) {
|
|
244
|
+
pi.sendMessage(
|
|
245
|
+
{
|
|
246
|
+
customType: "pp-plan-reviews-error",
|
|
247
|
+
content: "All plan reviewer variants failed — no reviews were produced. Proceeding without plan review.",
|
|
248
|
+
display: true,
|
|
249
|
+
},
|
|
250
|
+
{ deliverAs: "steer" },
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return { spawned: enabledVariants.length, files: reviewFiles, agentIds, failedVariants };
|
|
255
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { readFileSync, existsSync, mkdirSync, readdirSync, statSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
4
|
+
import type { PiPiConfig } from "../config.js";
|
|
5
|
+
import { registerAgentDefinitions, spawnViaRpc, waitForCompletion } from "../agents/registry.js";
|
|
6
|
+
import { createCodeReviewerAgent } from "../agents/code-reviewer.js";
|
|
7
|
+
import { getLatestSynthesizedPlan } from "../context.js";
|
|
8
|
+
|
|
9
|
+
export function reviewSystemPrompt(taskDir: string, pass: number, manualReview = false, phase?: string): string {
|
|
10
|
+
const reviewsDir = phase === "brainstorm" ? join(taskDir, "brainstorm-reviews") : join(taskDir, "code-reviews");
|
|
11
|
+
const plansDir = join(taskDir, "plans");
|
|
12
|
+
|
|
13
|
+
if (phase === "brainstorm") {
|
|
14
|
+
return [
|
|
15
|
+
`[PI-PI — BRAINSTORM REVIEW CYCLE (pass ${pass})]`,
|
|
16
|
+
"",
|
|
17
|
+
"Brainstorm reviewer outputs are ready.",
|
|
18
|
+
`Read them from ${reviewsDir}/.`,
|
|
19
|
+
"",
|
|
20
|
+
"# FORBIDDEN:",
|
|
21
|
+
"- Do NOT modify project source code",
|
|
22
|
+
"- Do NOT write your own review from scratch",
|
|
23
|
+
"- Do NOT add, rename, or remove sections in USER_REQUEST.md or RESEARCH.md",
|
|
24
|
+
"- Do NOT invent new headings (e.g. '## Current State', '## Critical Finding')",
|
|
25
|
+
"",
|
|
26
|
+
"# Your job:",
|
|
27
|
+
`1. Read ALL reviewer outputs from ${reviewsDir}/`,
|
|
28
|
+
"2. Identify valid gaps and inaccuracies that would block planning",
|
|
29
|
+
"3. If changes are needed: update ONLY the content within existing sections of USER_REQUEST.md / RESEARCH.md",
|
|
30
|
+
"4. If reviewers found no actionable gaps (e.g. task already done, minor suggestions only): do NOT modify the files",
|
|
31
|
+
"5. Ignore suggestions that don't affect downstream planning quality",
|
|
32
|
+
"",
|
|
33
|
+
"USER_REQUEST.md MUST keep exactly: # User Request, ## Problem, ## Constraints",
|
|
34
|
+
"RESEARCH.md MUST keep exactly: ## Affected Code, ## Architecture Context, ## Constraints & Edge Cases, ## Open Questions (optional)",
|
|
35
|
+
"Any other sections will fail validation.",
|
|
36
|
+
"",
|
|
37
|
+
"When done (or no changes needed), call pp_phase_complete with a brief summary.",
|
|
38
|
+
].join("\n");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (manualReview) {
|
|
42
|
+
return [
|
|
43
|
+
`[PI-PI — REVIEW CYCLE (pass ${pass}, manual)]`,
|
|
44
|
+
"",
|
|
45
|
+
"Review the implementation yourself using the available tools.",
|
|
46
|
+
"",
|
|
47
|
+
`Write your review to ${reviewsDir}/<timestamp>_final_pass-${pass}.md`,
|
|
48
|
+
"",
|
|
49
|
+
"If changes are needed:",
|
|
50
|
+
`1. Create a fix plan at ${plansDir}/<timestamp>_<description>.md`,
|
|
51
|
+
"2. Implement the fixes",
|
|
52
|
+
"3. Run afterImplement commands",
|
|
53
|
+
"",
|
|
54
|
+
"When done, call pp_phase_complete with a brief summary.",
|
|
55
|
+
].join("\n");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return [
|
|
59
|
+
`[PI-PI — REVIEW CYCLE (pass ${pass})]`,
|
|
60
|
+
"",
|
|
61
|
+
"Code reviewer outputs are ready.",
|
|
62
|
+
`Read them from ${reviewsDir}/, synthesize feedback, and implement fixes if needed.`,
|
|
63
|
+
"",
|
|
64
|
+
"# FORBIDDEN — do NOT do any of these:",
|
|
65
|
+
"- Do NOT write your own code review from scratch. You are a SYNTHESIZER, not a reviewer.",
|
|
66
|
+
"- Do NOT create the code-reviews/ directory yourself — the extension manages it.",
|
|
67
|
+
"- Do NOT call plannotator_submit_plan — code review is handled by the user via /pp menu.",
|
|
68
|
+
"",
|
|
69
|
+
"# Your job (in this order):",
|
|
70
|
+
`1. Read ALL reviewer outputs from ${reviewsDir}/`,
|
|
71
|
+
`2. Synthesize into ${reviewsDir}/<timestamp>_final_pass-${pass}.md`,
|
|
72
|
+
"3. Present the synthesis to the user",
|
|
73
|
+
"",
|
|
74
|
+
"If changes are needed:",
|
|
75
|
+
`1. Create a fix plan at ${plansDir}/<timestamp>_<description>.md (do NOT modify the original synthesized plan)`,
|
|
76
|
+
"2. Implement the fixes",
|
|
77
|
+
"3. Run afterImplement commands",
|
|
78
|
+
"4. A new review pass will begin",
|
|
79
|
+
"",
|
|
80
|
+
"When the synthesized review is ready, call pp_phase_complete with a brief summary.",
|
|
81
|
+
].join("\n");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export async function spawnCodeReviewers(
|
|
85
|
+
pi: ExtensionAPI,
|
|
86
|
+
cwd: string,
|
|
87
|
+
taskDir: string,
|
|
88
|
+
taskId: string,
|
|
89
|
+
config: PiPiConfig,
|
|
90
|
+
round: number,
|
|
91
|
+
): Promise<{ spawned: number; agentIds: string[]; failedVariants: string[] }> {
|
|
92
|
+
const urPath = join(taskDir, "USER_REQUEST.md");
|
|
93
|
+
const resPath = join(taskDir, "RESEARCH.md");
|
|
94
|
+
if (!existsSync(urPath) || !existsSync(resPath)) {
|
|
95
|
+
pi.sendMessage(
|
|
96
|
+
{ customType: "pp-code-reviews-error", content: "Cannot start code review: USER_REQUEST.md or RESEARCH.md is missing.", display: true },
|
|
97
|
+
{ deliverAs: "steer" },
|
|
98
|
+
);
|
|
99
|
+
return { spawned: 0, agentIds: [], failedVariants: [] };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const userRequest = readFileSync(urPath, "utf-8");
|
|
103
|
+
const research = readFileSync(resPath, "utf-8");
|
|
104
|
+
const synthesizedPlan = getLatestSynthesizedPlan(taskDir);
|
|
105
|
+
if (!synthesizedPlan) {
|
|
106
|
+
pi.sendMessage(
|
|
107
|
+
{ customType: "pp-code-reviews-error", content: "Cannot start code review: no synthesized plan found.", display: true },
|
|
108
|
+
{ deliverAs: "steer" },
|
|
109
|
+
);
|
|
110
|
+
return { spawned: 0, agentIds: [], failedVariants: [] };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const reviewsDir = join(taskDir, "code-reviews");
|
|
114
|
+
if (!existsSync(reviewsDir)) {
|
|
115
|
+
mkdirSync(reviewsDir, { recursive: true });
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const timestamp = Math.floor(Date.now() / 1000);
|
|
119
|
+
const enabledVariants = Object.entries(config.codeReviewers).filter(([, v]) => v.enabled);
|
|
120
|
+
const agentIds: string[] = [];
|
|
121
|
+
const failedVariants: string[] = [];
|
|
122
|
+
const results: Promise<void>[] = [];
|
|
123
|
+
|
|
124
|
+
for (const [variant] of enabledVariants) {
|
|
125
|
+
const outputPath = join(reviewsDir, `${timestamp}_${variant}_round-${round}.md`);
|
|
126
|
+
const agent = createCodeReviewerAgent(variant, config, { userRequest, research, synthesizedPlan }, outputPath);
|
|
127
|
+
|
|
128
|
+
registerAgentDefinitions(pi, [{ type: "code_reviewer", variant, ...agent }]);
|
|
129
|
+
|
|
130
|
+
results.push(
|
|
131
|
+
(async () => {
|
|
132
|
+
try {
|
|
133
|
+
const { id } = await spawnViaRpc(pi, `code_reviewer_${variant}`, "Begin code review.", {
|
|
134
|
+
description: `Code reviewer (${variant})`,
|
|
135
|
+
validateCompletion: () => {
|
|
136
|
+
if (!existsSync(outputPath) || statSync(outputPath).size === 0) {
|
|
137
|
+
return `You finished without writing your review file. Write your review to: ${outputPath}`;
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
agentIds.push(id);
|
|
142
|
+
await waitForCompletion(pi, id);
|
|
143
|
+
} catch (err: any) {
|
|
144
|
+
failedVariants.push(variant);
|
|
145
|
+
pi.sendMessage(
|
|
146
|
+
{
|
|
147
|
+
customType: "pp-code-reviewer-error",
|
|
148
|
+
content: `Code reviewer variant "${variant}" failed: ${err.message}`,
|
|
149
|
+
display: true,
|
|
150
|
+
},
|
|
151
|
+
{ deliverAs: "steer" },
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
})(),
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
await Promise.allSettled(results);
|
|
159
|
+
|
|
160
|
+
const reviewFiles = existsSync(reviewsDir)
|
|
161
|
+
? readdirSync(reviewsDir).filter((f) => f.includes(`round-${round}`) && !f.includes("final"))
|
|
162
|
+
: [];
|
|
163
|
+
|
|
164
|
+
if (reviewFiles.length > 0) {
|
|
165
|
+
pi.sendMessage(
|
|
166
|
+
{
|
|
167
|
+
customType: "pp-code-reviews-done",
|
|
168
|
+
content: [
|
|
169
|
+
`${reviewFiles.length} code reviewer(s) completed (round ${round}). Reviews in ${reviewsDir}:`,
|
|
170
|
+
...reviewFiles.map((f) => ` - ${f}`),
|
|
171
|
+
"",
|
|
172
|
+
"Read all reviews and synthesize them into a final review.",
|
|
173
|
+
].join("\n"),
|
|
174
|
+
display: true,
|
|
175
|
+
},
|
|
176
|
+
{ deliverAs: "steer" },
|
|
177
|
+
);
|
|
178
|
+
} else {
|
|
179
|
+
pi.sendMessage(
|
|
180
|
+
{
|
|
181
|
+
customType: "pp-code-reviews-error",
|
|
182
|
+
content: [
|
|
183
|
+
`All code reviewer variants failed (round ${round}) — no reviews were produced.`,
|
|
184
|
+
"You must review the implementation yourself and decide whether to approve or request changes.",
|
|
185
|
+
].join("\n"),
|
|
186
|
+
display: true,
|
|
187
|
+
},
|
|
188
|
+
{ deliverAs: "steer" },
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return { spawned: enabledVariants.length, agentIds, failedVariants };
|
|
193
|
+
}
|