@ilya-lesikov/pi-pi 0.4.0 → 0.6.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 +73 -31
- 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 +630 -121
- package/extensions/orchestrator/context.test.ts +177 -10
- package/extensions/orchestrator/context.ts +115 -14
- package/extensions/orchestrator/custom-footer.ts +2 -1
- package/extensions/orchestrator/doctor.test.ts +559 -0
- package/extensions/orchestrator/doctor.ts +664 -0
- package/extensions/orchestrator/event-handlers.test.ts +84 -22
- package/extensions/orchestrator/event-handlers.ts +1191 -360
- package/extensions/orchestrator/exa.test.ts +46 -0
- package/extensions/orchestrator/exa.ts +16 -10
- package/extensions/orchestrator/flant-infra.test.ts +224 -0
- package/extensions/orchestrator/flant-infra.ts +110 -41
- package/extensions/orchestrator/index.ts +13 -2
- package/extensions/orchestrator/integration.test.ts +2866 -118
- package/extensions/orchestrator/log.test.ts +219 -0
- package/extensions/orchestrator/log.ts +153 -0
- package/extensions/orchestrator/model-registry.test.ts +238 -0
- package/extensions/orchestrator/model-registry.ts +282 -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 +295 -148
- package/extensions/orchestrator/phases/brainstorm.test.ts +10 -7
- package/extensions/orchestrator/phases/brainstorm.ts +41 -35
- 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 -29
- package/extensions/orchestrator/phases/review-task.ts +3 -3
- package/extensions/orchestrator/phases/review.ts +38 -39
- 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 +134 -0
- package/extensions/orchestrator/pp-menu.ts +2631 -392
- 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 +89 -26
- package/extensions/orchestrator/subagent-session-marker.test.ts +36 -0
- package/extensions/orchestrator/test-helpers.ts +217 -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 +435 -0
- package/extensions/orchestrator/usage-tracker.ts +23 -2
- package/extensions/orchestrator/validate-artifacts.test.ts +83 -1
- package/package.json +2 -1
|
@@ -1,36 +1,52 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import {
|
|
1
|
+
import type { VariantConfig } from "../config.js";
|
|
2
|
+
import { loadAllContextFiles } from "../context.js";
|
|
3
|
+
import { resolveModel, getModelInfo } from "../model-registry.js";
|
|
4
|
+
import type { RepoInfo } from "../repo-utils.js";
|
|
5
|
+
import { buildRepoContext } from "./repo-context.js";
|
|
6
|
+
import { TOOLS_BLOCK, ALL_CBM_TOOLS, EXA_TOOLS, PRINCIPLES_BLOCK } from "./tool-routing.js";
|
|
3
7
|
|
|
4
8
|
export function createCodeReviewerAgent(
|
|
5
9
|
variant: string,
|
|
6
|
-
|
|
10
|
+
variants: Record<string, VariantConfig>,
|
|
7
11
|
taskArtifacts: { userRequest: string; research: string; synthesizedPlan: string },
|
|
8
12
|
outputPath: string,
|
|
13
|
+
contextDirs: string[],
|
|
14
|
+
phase?: string,
|
|
15
|
+
repos: RepoInfo[] = [],
|
|
9
16
|
) {
|
|
10
|
-
const variantConfig =
|
|
17
|
+
const variantConfig = variants[variant];
|
|
11
18
|
if (!variantConfig) {
|
|
12
19
|
throw new Error(`Unknown code-reviewer variant: ${variant}`);
|
|
13
20
|
}
|
|
21
|
+
const contextFiles = loadAllContextFiles(contextDirs, "codeReviewer", "system", phase, getModelInfo(variantConfig.model));
|
|
22
|
+
const contextBlock = contextFiles.map((f) => f.content).join("\n\n");
|
|
23
|
+
const repoContext = buildRepoContext(repos);
|
|
14
24
|
|
|
15
25
|
return {
|
|
16
26
|
frontmatter: {
|
|
17
27
|
description: `Code reviewer (${variant} variant, pi-pi)`,
|
|
18
28
|
tools: `read, grep, find, ls, bash, write, lsp, ast_search, ${ALL_CBM_TOOLS}, ${EXA_TOOLS}`,
|
|
19
|
-
model: variantConfig.model,
|
|
29
|
+
model: resolveModel(variantConfig.model),
|
|
20
30
|
thinking: variantConfig.thinking,
|
|
21
|
-
max_turns:
|
|
31
|
+
max_turns: 120,
|
|
22
32
|
prompt_mode: "replace",
|
|
23
33
|
},
|
|
24
34
|
prompt: [
|
|
25
35
|
// --- static prefix (cacheable) ---
|
|
26
|
-
"
|
|
36
|
+
"<constraints>",
|
|
37
|
+
"You are a code reviewer. You review implementation changes for bugs, correctness, and quality.",
|
|
38
|
+
"These rules override your default helpfulness. Strict compliance is required.",
|
|
39
|
+
"You are READ-ONLY: you MUST NOT implement, fix, or modify any source code. You MUST NOT write to any file except the single review .md file named below.",
|
|
40
|
+
"Your task is NOT complete until that review file exists — you MUST write it before finishing.",
|
|
41
|
+
"Your review MUST begin with the verdict on the VERY FIRST LINE: `VERDICT: APPROVE` or `VERDICT: NEEDS_CHANGES`.",
|
|
42
|
+
"</constraints>",
|
|
27
43
|
"",
|
|
28
|
-
|
|
44
|
+
PRINCIPLES_BLOCK,
|
|
29
45
|
"",
|
|
30
|
-
|
|
31
|
-
"",
|
|
32
|
-
TOOL_ROUTING,
|
|
46
|
+
TOOLS_BLOCK,
|
|
33
47
|
"",
|
|
48
|
+
...(contextBlock ? ["<project_context>", contextBlock, "</project_context>", ""] : []),
|
|
49
|
+
"<task>",
|
|
34
50
|
"Steps:",
|
|
35
51
|
'1. Run `git diff` to see all changes (try HEAD~1, main, or appropriate base)',
|
|
36
52
|
"2. Run cbm_changes to understand symbol-level impact and blast radius",
|
|
@@ -58,26 +74,23 @@ export function createCodeReviewerAgent(
|
|
|
58
74
|
"- Confidence: HIGH / MEDIUM / LOW",
|
|
59
75
|
"- If LOW, move to Open Questions instead",
|
|
60
76
|
"",
|
|
61
|
-
"Format your review
|
|
77
|
+
"Format your review with the verdict on the VERY FIRST LINE, then findings:",
|
|
78
|
+
"VERDICT: APPROVE | NEEDS_CHANGES",
|
|
62
79
|
"- CRITICAL: (must fix — with file:line evidence)",
|
|
63
80
|
"- MAJOR: (should fix — with evidence)",
|
|
64
81
|
"- MINOR: (nice to have)",
|
|
65
82
|
"- OPEN QUESTIONS: (low-confidence concerns, speculative follow-ups)",
|
|
66
|
-
"- VERDICT: APPROVE or NEEDS_CHANGES",
|
|
67
83
|
"",
|
|
68
84
|
"subagent_type is REQUIRED when spawning subagents — calls without it are rejected:",
|
|
69
85
|
'- Agent(subagent_type="Explore", ...) — codebase research. Prefer this for most lookups. Fast and cheap.',
|
|
70
86
|
'- Agent(subagent_type="Librarian", ...) — external docs, library APIs, web research.',
|
|
71
87
|
"Spawn multiple Explore agents in parallel for broad searches.",
|
|
88
|
+
"</task>",
|
|
72
89
|
"",
|
|
73
90
|
// --- dynamic suffix ---
|
|
74
91
|
"# MANDATORY: Write your review to this exact file using the write tool:",
|
|
75
92
|
` ${outputPath}`,
|
|
76
93
|
"",
|
|
77
|
-
"Your task is NOT complete until this file exists. Do NOT finish without writing it.",
|
|
78
|
-
"You MUST NOT write to any other file. Only write .md files inside .pp/state/.",
|
|
79
|
-
"Do NOT implement, fix, or modify any source code — you are a reviewer, not an implementer.",
|
|
80
|
-
"",
|
|
81
94
|
"=== USER REQUEST ===",
|
|
82
95
|
taskArtifacts.userRequest,
|
|
83
96
|
"",
|
|
@@ -86,6 +99,7 @@ export function createCodeReviewerAgent(
|
|
|
86
99
|
"",
|
|
87
100
|
"=== SYNTHESIZED PLAN ===",
|
|
88
101
|
taskArtifacts.synthesizedPlan,
|
|
102
|
+
...(repoContext ? [repoContext] : []),
|
|
89
103
|
"",
|
|
90
104
|
"The artifacts above are already in your context. Do NOT re-read them from disk.",
|
|
91
105
|
].join("\n"),
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { Phase, TaskMode } from "../state.js";
|
|
2
|
+
|
|
3
|
+
const READONLY_CONSTRAINT =
|
|
4
|
+
"You MUST NOT edit, create, or delete any project file (source, tests, config, docs) — only files under .pp/state/ may be written — and you MUST NOT run state-changing shell commands. If you find a fix worth making, record it in your output; do NOT apply it here.";
|
|
5
|
+
|
|
6
|
+
const IMPLEMENT_CONSTRAINT =
|
|
7
|
+
"Implement only the approved plan. Do NOT add scope or change plan items without recording why in the plan. If the same fix fails 3 times, stop and re-plan — do NOT keep retrying the same approach.";
|
|
8
|
+
|
|
9
|
+
const QUICK_CONSTRAINT =
|
|
10
|
+
"Stay within the user's request. Do NOT broaden scope or refactor adjacent code.";
|
|
11
|
+
|
|
12
|
+
export function isReadOnlyPhase(phase: Phase): boolean {
|
|
13
|
+
return phase === "brainstorm" || phase === "debug" || phase === "review" || phase === "plan";
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function phaseConstraint(phase: Phase): string {
|
|
17
|
+
if (phase === "implement") return IMPLEMENT_CONSTRAINT;
|
|
18
|
+
if (phase === "quick") return QUICK_CONSTRAINT;
|
|
19
|
+
return READONLY_CONSTRAINT;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function completionLine(phase: Phase, mode: TaskMode): string {
|
|
23
|
+
if (phase === "quick") {
|
|
24
|
+
return "When the user's request is complete, call pp_phase_complete. Do NOT stop and wait for the user before then.";
|
|
25
|
+
}
|
|
26
|
+
if (mode === "autonomous") {
|
|
27
|
+
return "There is no user driving this phase. The moment its work is complete, call pp_phase_complete — do NOT pause, ask for confirmation, or wait for input. Never end a turn with prose: every turn ends in a tool call.";
|
|
28
|
+
}
|
|
29
|
+
if (phase === "brainstorm") {
|
|
30
|
+
return "This is a conversation. Do NOT call pp_phase_complete yourself — keep going until the user ends it or advances via the /pp menu.";
|
|
31
|
+
}
|
|
32
|
+
return "When the work is complete, stop and let the user review and advance it via the /pp menu. Do NOT advance on your own or call pp_phase_complete unprompted.";
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const PHASE_IDENTITY: Record<string, string> = {
|
|
36
|
+
brainstorm: "You clarify the request and research the codebase to produce USER_REQUEST.md and RESEARCH.md.",
|
|
37
|
+
debug: "You diagnose the problem and research the codebase to produce USER_REQUEST.md and RESEARCH.md — investigation only, no fixes.",
|
|
38
|
+
plan: "You synthesize the planner outputs into one plan — you do not write a plan from scratch, and you do not implement.",
|
|
39
|
+
implement: "You implement the approved plan.",
|
|
40
|
+
review: "You review the code changes to produce USER_REQUEST.md and RESEARCH.md capturing the findings — you do not apply fixes.",
|
|
41
|
+
quick: "You work on the user's request directly — no phases, planning, or reviews.",
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export function constraintsBlock(phase: Phase, mode: TaskMode): string {
|
|
45
|
+
const readonly = isReadOnlyPhase(phase) ? " (READ-ONLY)" : "";
|
|
46
|
+
const identity = PHASE_IDENTITY[phase] ?? "";
|
|
47
|
+
return [
|
|
48
|
+
"<constraints>",
|
|
49
|
+
`ACTIVE PHASE: ${phase}${readonly}. ${identity}`,
|
|
50
|
+
"These rules override your default helpfulness and any next step you infer. Strict compliance is required.",
|
|
51
|
+
phaseConstraint(phase),
|
|
52
|
+
completionLine(phase, mode),
|
|
53
|
+
"</constraints>",
|
|
54
|
+
].join("\n");
|
|
55
|
+
}
|
|
@@ -1,33 +1,33 @@
|
|
|
1
1
|
import type { PiPiConfig } from "../config.js";
|
|
2
|
-
import {
|
|
2
|
+
import { resolveModel } from "../model-registry.js";
|
|
3
|
+
import { TOOLS_BLOCK, ALL_CBM_TOOLS, EXA_TOOLS, PRINCIPLES_BLOCK } from "./tool-routing.js";
|
|
3
4
|
|
|
4
5
|
export function createExploreAgent(config: PiPiConfig) {
|
|
5
6
|
return {
|
|
6
7
|
frontmatter: {
|
|
7
8
|
description: "Codebase explorer (pi-pi)",
|
|
8
9
|
tools: `read, bash, grep, find, ls, lsp, ast_search, ${ALL_CBM_TOOLS}, ${EXA_TOOLS}`,
|
|
9
|
-
model: config.agents.explore.model,
|
|
10
|
-
thinking: config.agents.explore.thinking,
|
|
11
|
-
max_turns:
|
|
10
|
+
model: resolveModel(config.agents.subagents.simple.explore.model),
|
|
11
|
+
thinking: config.agents.subagents.simple.explore.thinking,
|
|
12
|
+
max_turns: 170,
|
|
12
13
|
prompt_mode: "replace",
|
|
13
14
|
},
|
|
14
15
|
prompt: [
|
|
15
|
-
"
|
|
16
|
+
"<constraints>",
|
|
17
|
+
"You are a focused codebase SEARCH agent. You find specific information and report it with file paths.",
|
|
18
|
+
"You are READ-ONLY: you MUST NOT modify any file. Report findings; do NOT change code.",
|
|
19
|
+
"</constraints>",
|
|
16
20
|
"",
|
|
17
|
-
|
|
18
|
-
"You are read-only — do NOT modify any files.",
|
|
21
|
+
PRINCIPLES_BLOCK,
|
|
19
22
|
"",
|
|
20
|
-
|
|
23
|
+
TOOLS_BLOCK,
|
|
21
24
|
"",
|
|
22
|
-
|
|
23
|
-
"",
|
|
24
|
-
TOOL_ROUTING,
|
|
25
|
-
"",
|
|
26
|
-
"# Instructions",
|
|
25
|
+
"<task>",
|
|
27
26
|
"- Search multiple angles in parallel for speed",
|
|
28
27
|
"- Start with cbm_search or cbm_search_code for discovery, then narrow with lsp for precision",
|
|
29
28
|
"- Return file paths with brief descriptions of what you found",
|
|
30
29
|
"- When done, provide a concise summary of findings",
|
|
30
|
+
"</task>",
|
|
31
31
|
].join("\n"),
|
|
32
32
|
};
|
|
33
33
|
}
|
|
@@ -1,24 +1,26 @@
|
|
|
1
1
|
import type { PiPiConfig } from "../config.js";
|
|
2
|
-
import {
|
|
2
|
+
import { resolveModel } from "../model-registry.js";
|
|
3
|
+
import { PRINCIPLES_BLOCK } from "./tool-routing.js";
|
|
3
4
|
|
|
4
5
|
export function createLibrarianAgent(config: PiPiConfig) {
|
|
5
6
|
return {
|
|
6
7
|
frontmatter: {
|
|
7
8
|
description: "External docs researcher (pi-pi)",
|
|
8
9
|
tools: "read, bash, grep, find, exa_search, exa_fetch",
|
|
9
|
-
model: config.agents.librarian.model,
|
|
10
|
-
thinking: config.agents.librarian.thinking,
|
|
11
|
-
max_turns:
|
|
10
|
+
model: resolveModel(config.agents.subagents.simple.librarian.model),
|
|
11
|
+
thinking: config.agents.subagents.simple.librarian.thinking,
|
|
12
|
+
max_turns: 120,
|
|
12
13
|
prompt_mode: "replace",
|
|
13
14
|
},
|
|
14
15
|
prompt: [
|
|
15
|
-
"
|
|
16
|
+
"<constraints>",
|
|
17
|
+
"You are a research agent specializing in external documentation and libraries. You find documentation, best practices, and usage patterns for external libraries and APIs.",
|
|
18
|
+
"You are READ-ONLY: you MUST NOT modify any project file. Report findings; do NOT change code.",
|
|
19
|
+
"</constraints>",
|
|
16
20
|
"",
|
|
17
|
-
|
|
18
|
-
"You are read-only — do NOT modify any project files.",
|
|
19
|
-
"",
|
|
20
|
-
COMMUNICATION,
|
|
21
|
+
PRINCIPLES_BLOCK,
|
|
21
22
|
"",
|
|
23
|
+
"<task>",
|
|
22
24
|
"# How to research",
|
|
23
25
|
"",
|
|
24
26
|
"Never guess at APIs — always look them up. Training data may be outdated or wrong.",
|
|
@@ -38,6 +40,7 @@ export function createLibrarianAgent(config: PiPiConfig) {
|
|
|
38
40
|
"- Include exact URLs or file paths for every claim",
|
|
39
41
|
"- Quote relevant code snippets from docs",
|
|
40
42
|
"- Flag any version-specific caveats",
|
|
43
|
+
"</task>",
|
|
41
44
|
].join("\n"),
|
|
42
45
|
};
|
|
43
46
|
}
|
|
@@ -1,39 +1,51 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import {
|
|
1
|
+
import type { VariantConfig } from "../config.js";
|
|
2
|
+
import { loadAllContextFiles } from "../context.js";
|
|
3
|
+
import { resolveModel, getModelInfo } from "../model-registry.js";
|
|
4
|
+
import type { RepoInfo } from "../repo-utils.js";
|
|
5
|
+
import { buildRepoContext } from "./repo-context.js";
|
|
6
|
+
import { TOOLS_BLOCK, ALL_CBM_TOOLS, EXA_TOOLS, PRINCIPLES_BLOCK } from "./tool-routing.js";
|
|
3
7
|
|
|
4
8
|
export function createPlanReviewerAgent(
|
|
5
9
|
variant: string,
|
|
6
|
-
|
|
10
|
+
variants: Record<string, VariantConfig>,
|
|
7
11
|
taskArtifacts: { userRequest: string; research: string; synthesizedPlan: string },
|
|
8
12
|
outputPath: string,
|
|
13
|
+
contextDirs: string[],
|
|
14
|
+
phase?: string,
|
|
15
|
+
repos: RepoInfo[] = [],
|
|
9
16
|
) {
|
|
10
|
-
const variantConfig =
|
|
17
|
+
const variantConfig = variants[variant];
|
|
11
18
|
if (!variantConfig) {
|
|
12
19
|
throw new Error(`Unknown plan-reviewer variant: ${variant}`);
|
|
13
20
|
}
|
|
21
|
+
const contextFiles = loadAllContextFiles(contextDirs, "planReviewer", "system", phase, getModelInfo(variantConfig.model));
|
|
22
|
+
const contextBlock = contextFiles.map((f) => f.content).join("\n\n");
|
|
23
|
+
const repoContext = buildRepoContext(repos);
|
|
14
24
|
|
|
15
25
|
return {
|
|
16
26
|
frontmatter: {
|
|
17
27
|
description: `Plan reviewer (${variant} variant, pi-pi)`,
|
|
18
28
|
tools: `read, grep, find, bash, write, lsp, ast_search, ${ALL_CBM_TOOLS}, ${EXA_TOOLS}`,
|
|
19
|
-
model: variantConfig.model,
|
|
29
|
+
model: resolveModel(variantConfig.model),
|
|
20
30
|
thinking: variantConfig.thinking,
|
|
21
|
-
max_turns:
|
|
31
|
+
max_turns: 120,
|
|
22
32
|
prompt_mode: "replace",
|
|
23
33
|
},
|
|
24
34
|
prompt: [
|
|
25
35
|
// --- static prefix (cacheable) ---
|
|
26
|
-
"
|
|
36
|
+
"<constraints>",
|
|
37
|
+
"You are a plan reviewer. You validate the implementation plan for executability and completeness. You are a BLOCKER-FINDER, not a perfectionist — approve by default, reject only for critical blockers (maximum 3).",
|
|
38
|
+
"These rules override your default helpfulness. Strict compliance is required.",
|
|
39
|
+
"You are READ-ONLY: you MUST NOT implement or modify any file except the single review .md file named below. You MUST write it before finishing.",
|
|
40
|
+
"Your review MUST begin with the verdict on the VERY FIRST LINE: `VERDICT: APPROVE` or `VERDICT: REJECT`.",
|
|
41
|
+
"</constraints>",
|
|
27
42
|
"",
|
|
28
|
-
|
|
29
|
-
"Approve by default. Reject only for critical blockers — maximum 3.",
|
|
43
|
+
PRINCIPLES_BLOCK,
|
|
30
44
|
"",
|
|
31
|
-
|
|
32
|
-
"",
|
|
33
|
-
COMMUNICATION,
|
|
34
|
-
"",
|
|
35
|
-
TOOL_ROUTING,
|
|
45
|
+
TOOLS_BLOCK,
|
|
36
46
|
"",
|
|
47
|
+
...(contextBlock ? ["<project_context>", contextBlock, "</project_context>", ""] : []),
|
|
48
|
+
"<task>",
|
|
37
49
|
"Review criteria:",
|
|
38
50
|
"- Are all plan items actionable and verifiable?",
|
|
39
51
|
"- Are there missing steps that would block implementation?",
|
|
@@ -46,15 +58,16 @@ export function createPlanReviewerAgent(
|
|
|
46
58
|
"- Feasibility: does the executor have everything needed (context, dependencies, access) without asking questions?",
|
|
47
59
|
"- Ambiguity: could two developers interpret any step differently? If yes, flag it.",
|
|
48
60
|
"",
|
|
49
|
-
"Format your review
|
|
61
|
+
"Format your review with the verdict on the VERY FIRST LINE, then findings:",
|
|
62
|
+
"VERDICT: APPROVE | REJECT",
|
|
50
63
|
"- BLOCKERS: (critical issues that must be fixed)",
|
|
51
64
|
"- SUGGESTIONS: (improvements, not required)",
|
|
52
|
-
"- VERDICT: APPROVE or REJECT (with reason)",
|
|
53
65
|
"",
|
|
54
66
|
"subagent_type is REQUIRED when spawning subagents — calls without it are rejected:",
|
|
55
67
|
'- Agent(subagent_type="Explore", ...) — codebase research. Prefer this for most lookups. Fast and cheap.',
|
|
56
68
|
'- Agent(subagent_type="Librarian", ...) — external docs, library APIs, web research.',
|
|
57
69
|
"Spawn multiple Explore agents in parallel for broad searches.",
|
|
70
|
+
"</task>",
|
|
58
71
|
"",
|
|
59
72
|
// --- dynamic suffix ---
|
|
60
73
|
"# MANDATORY OUTPUT",
|
|
@@ -62,9 +75,7 @@ export function createPlanReviewerAgent(
|
|
|
62
75
|
"Write your review to this exact file using the write tool:",
|
|
63
76
|
` ${outputPath}`,
|
|
64
77
|
"",
|
|
65
|
-
"
|
|
66
|
-
"You MUST NOT write to any other file. Only write .md files inside .pp/state/.",
|
|
67
|
-
"Do NOT implement, fix, or modify any source code — you are a reviewer, not an implementer.",
|
|
78
|
+
"You MUST write only to the review file above. Do NOT write to any other file.",
|
|
68
79
|
"",
|
|
69
80
|
"=== USER REQUEST ===",
|
|
70
81
|
taskArtifacts.userRequest,
|
|
@@ -74,6 +85,7 @@ export function createPlanReviewerAgent(
|
|
|
74
85
|
"",
|
|
75
86
|
"=== SYNTHESIZED PLAN ===",
|
|
76
87
|
taskArtifacts.synthesizedPlan,
|
|
88
|
+
...(repoContext ? [repoContext] : []),
|
|
77
89
|
"",
|
|
78
90
|
"The artifacts above are already in your context. Do NOT re-read them from disk.",
|
|
79
91
|
].join("\n"),
|
|
@@ -1,36 +1,51 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import {
|
|
1
|
+
import type { VariantConfig } from "../config.js";
|
|
2
|
+
import { loadAllContextFiles } from "../context.js";
|
|
3
|
+
import { resolveModel, getModelInfo } from "../model-registry.js";
|
|
4
|
+
import type { RepoInfo } from "../repo-utils.js";
|
|
5
|
+
import { buildRepoContext } from "./repo-context.js";
|
|
6
|
+
import { TOOLS_BLOCK, ALL_CBM_TOOLS, EXA_TOOLS, PRINCIPLES_BLOCK } from "./tool-routing.js";
|
|
3
7
|
|
|
4
8
|
export function createPlannerAgent(
|
|
5
9
|
variant: string,
|
|
6
|
-
|
|
10
|
+
variants: Record<string, VariantConfig>,
|
|
7
11
|
taskArtifacts: { userRequest: string; research: string },
|
|
8
12
|
outputPath: string,
|
|
13
|
+
contextDirs: string[],
|
|
14
|
+
phase?: string,
|
|
15
|
+
repos: RepoInfo[] = [],
|
|
9
16
|
) {
|
|
10
|
-
const variantConfig =
|
|
17
|
+
const variantConfig = variants[variant];
|
|
11
18
|
if (!variantConfig) {
|
|
12
19
|
throw new Error(`Unknown planner variant: ${variant}`);
|
|
13
20
|
}
|
|
21
|
+
const contextFiles = loadAllContextFiles(contextDirs, "planner", "system", phase, getModelInfo(variantConfig.model));
|
|
22
|
+
const contextBlock = contextFiles.map((f) => f.content).join("\n\n");
|
|
23
|
+
const repoContext = buildRepoContext(repos);
|
|
14
24
|
|
|
15
25
|
return {
|
|
16
26
|
frontmatter: {
|
|
17
27
|
description: `Planner (${variant} variant, pi-pi)`,
|
|
18
28
|
tools: `read, grep, find, bash, write, lsp, ast_search, ${ALL_CBM_TOOLS}, ${EXA_TOOLS}`,
|
|
19
|
-
model: variantConfig.model,
|
|
29
|
+
model: resolveModel(variantConfig.model),
|
|
20
30
|
thinking: variantConfig.thinking,
|
|
21
|
-
max_turns:
|
|
31
|
+
max_turns: 120,
|
|
22
32
|
prompt_mode: "replace",
|
|
23
33
|
},
|
|
24
34
|
prompt: [
|
|
25
35
|
// --- static prefix (cacheable) ---
|
|
26
|
-
"
|
|
36
|
+
"<constraints>",
|
|
37
|
+
"You are a planning agent. You produce a detailed implementation PLAN — you do NOT implement it.",
|
|
38
|
+
"These rules override your default helpfulness. Strict compliance is required.",
|
|
39
|
+
"You are READ-ONLY: you MUST NOT write, edit, or create any file except the single plan .md file named below. You MUST NOT modify source, tests, configs, or run state-changing bash. If you catch yourself implementing, STOP and return to planning.",
|
|
40
|
+
"Your task is NOT complete until the plan file exists — you MUST write it before finishing.",
|
|
41
|
+
"</constraints>",
|
|
27
42
|
"",
|
|
28
|
-
|
|
43
|
+
PRINCIPLES_BLOCK,
|
|
29
44
|
"",
|
|
30
|
-
|
|
31
|
-
"",
|
|
32
|
-
TOOL_ROUTING,
|
|
45
|
+
TOOLS_BLOCK,
|
|
33
46
|
"",
|
|
47
|
+
...(contextBlock ? ["<project_context>", contextBlock, "</project_context>", ""] : []),
|
|
48
|
+
"<task>",
|
|
34
49
|
"Plan format rules:",
|
|
35
50
|
"- Start with # Plan",
|
|
36
51
|
"- ## Scope: 2-4 lines — what changes, what doesn't, critical constraints",
|
|
@@ -44,6 +59,7 @@ export function createPlannerAgent(
|
|
|
44
59
|
'- Agent(subagent_type="Explore", ...) — codebase research. Prefer this for most lookups. Fast and cheap.',
|
|
45
60
|
'- Agent(subagent_type="Librarian", ...) — external docs, library APIs, web research.',
|
|
46
61
|
"Spawn multiple Explore agents in parallel for broad searches.",
|
|
62
|
+
"</task>",
|
|
47
63
|
"",
|
|
48
64
|
// --- dynamic suffix ---
|
|
49
65
|
"# MANDATORY OUTPUT",
|
|
@@ -51,23 +67,12 @@ export function createPlannerAgent(
|
|
|
51
67
|
"Write your plan to this exact file using the write tool:",
|
|
52
68
|
` ${outputPath}`,
|
|
53
69
|
"",
|
|
54
|
-
"Your task is NOT complete until this file exists. Do NOT finish without writing it.",
|
|
55
|
-
"",
|
|
56
|
-
"# CRITICAL: DO NOT IMPLEMENT ANYTHING",
|
|
57
|
-
"",
|
|
58
|
-
"You are a PLANNER, not an implementer. Your ONLY output is the plan file above.",
|
|
59
|
-
"- Do NOT write, edit, or create any file outside .pp/state/",
|
|
60
|
-
"- Do NOT create fix scripts, patches, or code files",
|
|
61
|
-
"- Do NOT modify source code, tests, configs, or any project file",
|
|
62
|
-
"- Do NOT use bash to write files (no echo >, sed -i, tee, scripts, etc.)",
|
|
63
|
-
"- If you catch yourself starting to implement — STOP IMMEDIATELY and go back to planning",
|
|
64
|
-
"- Violating this will cause your output to be DISCARDED",
|
|
65
|
-
"",
|
|
66
70
|
"=== USER REQUEST ===",
|
|
67
71
|
taskArtifacts.userRequest,
|
|
68
72
|
"",
|
|
69
73
|
"=== RESEARCH ===",
|
|
70
74
|
taskArtifacts.research,
|
|
75
|
+
...(repoContext ? [repoContext] : []),
|
|
71
76
|
"",
|
|
72
77
|
"The artifacts above are already in your context. Do NOT re-read them from disk.",
|
|
73
78
|
].join("\n"),
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { resolveModel } from "../model-registry.js";
|
|
2
3
|
|
|
3
4
|
interface AgentFrontmatter {
|
|
4
5
|
description: string;
|
|
@@ -26,7 +27,7 @@ export function registerAgentDefinitions(
|
|
|
26
27
|
builtinToolNames: toolNames,
|
|
27
28
|
extensions: true,
|
|
28
29
|
skills: false,
|
|
29
|
-
model: agent.frontmatter.model,
|
|
30
|
+
model: resolveModel(agent.frontmatter.model),
|
|
30
31
|
thinking: agent.frontmatter.thinking,
|
|
31
32
|
maxTurns: agent.frontmatter.max_turns,
|
|
32
33
|
systemPrompt: agent.prompt,
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { RepoInfo } from "../repo-utils.js";
|
|
2
|
+
|
|
3
|
+
export function buildRepoContext(repos: RepoInfo[]): string {
|
|
4
|
+
if (repos.length === 0) return "";
|
|
5
|
+
const lines = repos.map((r) => {
|
|
6
|
+
const label = r.isRoot ? " (root)" : "";
|
|
7
|
+
const branch = r.baseBranch ? `, base: ${r.baseBranch}` : "";
|
|
8
|
+
return ` - ${r.path}${label}${branch}`;
|
|
9
|
+
});
|
|
10
|
+
return `\nRegistered repositories:\n${lines.join("\n")}\n`;
|
|
11
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { PiPiConfig } from "../config.js";
|
|
2
|
-
import {
|
|
2
|
+
import { resolveModel } from "../model-registry.js";
|
|
3
|
+
import { TOOLS_BLOCK, ALL_CBM_TOOLS, EXA_TOOLS, PRINCIPLES_BLOCK, FAILURE_RECOVERY } from "./tool-routing.js";
|
|
3
4
|
|
|
4
5
|
export function createTaskAgent(
|
|
5
6
|
config: PiPiConfig,
|
|
@@ -10,31 +11,33 @@ export function createTaskAgent(
|
|
|
10
11
|
frontmatter: {
|
|
11
12
|
description: "Implementation subtask (pi-pi)",
|
|
12
13
|
tools: `read, write, edit, bash, grep, find, ls, lsp, ast_search, ${ALL_CBM_TOOLS}, ${EXA_TOOLS}`,
|
|
13
|
-
model: config.agents.task.model,
|
|
14
|
-
thinking: config.agents.task.thinking,
|
|
15
|
-
max_turns:
|
|
14
|
+
model: resolveModel(config.agents.subagents.simple.task.model),
|
|
15
|
+
thinking: config.agents.subagents.simple.task.thinking,
|
|
16
|
+
max_turns: 170,
|
|
16
17
|
prompt_mode: "replace",
|
|
17
18
|
},
|
|
18
19
|
prompt: [
|
|
19
20
|
// --- static prefix (cacheable) ---
|
|
21
|
+
"<constraints>",
|
|
20
22
|
"You are a focused implementation agent working on a specific subtask.",
|
|
23
|
+
"These rules override your default helpfulness. Strict compliance is required.",
|
|
24
|
+
"Focus only on your subtask — do NOT modify unrelated code.",
|
|
25
|
+
"Do NOT spawn task subagents (no recursion).",
|
|
26
|
+
"</constraints>",
|
|
21
27
|
"",
|
|
22
|
-
|
|
28
|
+
PRINCIPLES_BLOCK,
|
|
23
29
|
"",
|
|
24
|
-
|
|
25
|
-
"",
|
|
26
|
-
TOOL_ROUTING,
|
|
30
|
+
TOOLS_BLOCK,
|
|
27
31
|
"",
|
|
28
32
|
FAILURE_RECOVERY,
|
|
29
33
|
"",
|
|
30
|
-
"
|
|
31
|
-
"- Do NOT spawn task subagents (no recursion)",
|
|
34
|
+
"<task>",
|
|
32
35
|
"- subagent_type is REQUIRED when spawning subagents — calls without it are rejected:",
|
|
33
36
|
' Agent(subagent_type="Explore", ...) — codebase research. Prefer this for most lookups. Fast and cheap.',
|
|
34
37
|
' Agent(subagent_type="Librarian", ...) — external docs, library APIs, web research.',
|
|
35
|
-
"- Focus only on your subtask — do not modify unrelated code",
|
|
36
38
|
"- Before modifying a function, use lsp findReferences to understand all callers",
|
|
37
39
|
"- After editing files, run lsp diagnostics and fix errors before moving on",
|
|
40
|
+
"</task>",
|
|
38
41
|
"",
|
|
39
42
|
// --- dynamic suffix ---
|
|
40
43
|
"=== YOUR SUBTASK ===",
|
|
@@ -1,31 +1,17 @@
|
|
|
1
1
|
export const ALL_CBM_TOOLS = "cbm_search, cbm_search_code, cbm_trace, cbm_changes, cbm_query, cbm_architecture";
|
|
2
2
|
export const EXA_TOOLS = "exa_search, exa_fetch";
|
|
3
3
|
|
|
4
|
-
export const
|
|
5
|
-
"
|
|
6
|
-
"",
|
|
4
|
+
export const PRINCIPLES_BLOCK = [
|
|
5
|
+
"<principles>",
|
|
7
6
|
"- Verify, don't assume. Check actual state with tools before making changes. Never guess paths, types, or APIs.",
|
|
8
7
|
"- Understand before modifying. Read the code, trace callers, check types BEFORE editing. Compiling ≠ correct.",
|
|
9
8
|
"- Smallest viable change. Do what was asked, nothing more. Don't broaden scope, don't refactor adjacent code.",
|
|
10
9
|
"- No temporary artifacts. No console.log, TODO, HACK, debugger, or commented-out code left behind.",
|
|
11
10
|
"- Evidence over claims. 'It should work' is not proof. Show fresh tool output (lsp diagnostics, test results, build output).",
|
|
12
11
|
"- Match existing patterns. Mirror the codebase's naming, error handling, imports, and structure exactly.",
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
"# Working principles",
|
|
17
|
-
"",
|
|
18
|
-
"- Verify, don't assume. Check actual state with tools. Never guess paths, types, or APIs.",
|
|
19
|
-
"- Evidence over claims. Show what you found with file paths and tool output, not guesses.",
|
|
20
|
-
"",
|
|
21
|
-
"# STRICT READ-ONLY CONSTRAINT",
|
|
22
|
-
"",
|
|
23
|
-
"You are a READ-ONLY agent. You MUST NOT modify any source code, configuration, or project files.",
|
|
24
|
-
"- Do NOT use write or edit tools on any file outside .pp/state/",
|
|
25
|
-
"- Do NOT use bash to create, modify, or delete files (no echo >, sed -i, tee, mv, cp, rm, touch, etc.)",
|
|
26
|
-
"- Do NOT create scripts or run commands that modify the filesystem",
|
|
27
|
-
"- You MAY use bash for read-only commands: git log, git diff, go list, npm ls, cat, find, ls, etc.",
|
|
28
|
-
"- Violating this constraint is a critical failure. Your output will be discarded if you modify source files.",
|
|
12
|
+
"- Be concise and dense: minimum words, no preamble/filler/restatement. Don't narrate what you're about to do or just did.",
|
|
13
|
+
"- Think critically. Push back when something seems wrong, and state concerns before implementing.",
|
|
14
|
+
"</principles>",
|
|
29
15
|
].join("\n");
|
|
30
16
|
|
|
31
17
|
export const FAILURE_RECOVERY = [
|
|
@@ -39,21 +25,14 @@ export const FAILURE_RECOVERY = [
|
|
|
39
25
|
"4. Report the blocker — do not keep pushing",
|
|
40
26
|
].join("\n");
|
|
41
27
|
|
|
42
|
-
|
|
43
|
-
"# Communication",
|
|
28
|
+
const TOOL_ROUTING_BODY = [
|
|
44
29
|
"",
|
|
45
|
-
"
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
"- Think critically. Don't just agree — push back when something seems wrong.",
|
|
49
|
-
"- State concerns before implementing. If you see a better approach, say so.",
|
|
50
|
-
"- Dense over polished. One precise sentence beats three vague ones.",
|
|
51
|
-
].join("\n");
|
|
52
|
-
|
|
53
|
-
export const TOOL_ROUTING = [
|
|
54
|
-
"# Tool routing — what do you want to do?",
|
|
30
|
+
"**pp_register_repo**: Register a git repo you're working in. Call for every repo including the root directory. " +
|
|
31
|
+
"Pass the base branch (the branch this work will be merged into). Must call at the start of each task " +
|
|
32
|
+
"before doing any work.",
|
|
55
33
|
"",
|
|
56
34
|
"Find code by concept or behavior:",
|
|
35
|
+
"- Multi-repo: cbm_search, cbm_search_code, cbm_trace, cbm_changes accept optional project_path (absolute repo path). If omitted, they use the root project.",
|
|
57
36
|
"- cbm_search: natural-language search (query='deploy release chart')",
|
|
58
37
|
"- cbm_search with semantic_query: vector similarity (['deploy','install','upgrade'])",
|
|
59
38
|
"- cbm_search_code: graph-augmented grep — deduplicates into containing functions",
|
|
@@ -90,4 +69,10 @@ export const TOOL_ROUTING = [
|
|
|
90
69
|
"Web search:",
|
|
91
70
|
"- exa_search: search the web for docs, guides, examples. Describe the ideal page, not keywords.",
|
|
92
71
|
"- exa_fetch: read a URL's full content as clean markdown.",
|
|
93
|
-
|
|
72
|
+
"",
|
|
73
|
+
"Edit/write files:",
|
|
74
|
+
"- edit: targeted in-file changes.",
|
|
75
|
+
"- write: overwrite or create a whole file.",
|
|
76
|
+
];
|
|
77
|
+
|
|
78
|
+
export const TOOLS_BLOCK = ["<tools>", ...TOOL_ROUTING_BODY, "</tools>"].join("\n");
|
|
@@ -69,7 +69,8 @@ export function registerAstSearchTool(pi: ExtensionAPI, cwd: string): boolean {
|
|
|
69
69
|
const lines: string[] = [];
|
|
70
70
|
for (const match of results) {
|
|
71
71
|
const file = match.file ?? "?";
|
|
72
|
-
const
|
|
72
|
+
const start = match.range?.start?.line;
|
|
73
|
+
const startLine = typeof start === "number" ? start + 1 : "?";
|
|
73
74
|
const text = match.text ?? match.matchedCode ?? "";
|
|
74
75
|
lines.push(`${file}:${startLine}: ${text.trim()}`);
|
|
75
76
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from "vitest";
|
|
2
|
+
import { CbmDaemon } from "./cbm.js";
|
|
3
|
+
|
|
4
|
+
describe("CbmDaemon index tracking", () => {
|
|
5
|
+
it("re-indexes a project after the daemon restarts (cleanup clears indexedProjects)", async () => {
|
|
6
|
+
const daemon = new CbmDaemon();
|
|
7
|
+
const indexCalls: string[] = [];
|
|
8
|
+
|
|
9
|
+
const callTool = vi.spyOn(daemon, "callTool").mockImplementation(async (name, args) => {
|
|
10
|
+
if (name === "list_projects") return { projects: [] };
|
|
11
|
+
if (name === "index_repository") {
|
|
12
|
+
indexCalls.push((args as { repo_path: string }).repo_path);
|
|
13
|
+
return {};
|
|
14
|
+
}
|
|
15
|
+
return {};
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const cwd = "/repo/project";
|
|
19
|
+
|
|
20
|
+
await daemon.ensureIndexed(cwd);
|
|
21
|
+
expect(indexCalls).toHaveLength(1);
|
|
22
|
+
expect(daemon.hasIndexed(cwd)).toBe(true);
|
|
23
|
+
|
|
24
|
+
await daemon.ensureIndexed(cwd);
|
|
25
|
+
expect(indexCalls).toHaveLength(1);
|
|
26
|
+
|
|
27
|
+
daemon.cleanup();
|
|
28
|
+
expect(daemon.hasIndexed(cwd)).toBe(false);
|
|
29
|
+
|
|
30
|
+
await daemon.ensureIndexed(cwd);
|
|
31
|
+
expect(indexCalls).toHaveLength(2);
|
|
32
|
+
|
|
33
|
+
callTool.mockRestore();
|
|
34
|
+
});
|
|
35
|
+
});
|