@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.
- package/3p/pi-plannotator/apps/pi-extension/README.md +6 -5
- package/3p/pi-plannotator/apps/pi-extension/plannotator-browser.ts +25 -10
- package/3p/pi-plannotator/apps/pi-extension/plannotator-events.code-review.test.ts +172 -0
- package/3p/pi-plannotator/apps/pi-extension/plannotator-events.ts +50 -12
- package/3p/pi-plannotator/apps/pi-extension/server/project.test.ts +45 -0
- package/3p/pi-plannotator/apps/pi-extension/server/project.ts +7 -6
- package/3p/pi-plannotator/apps/pi-extension/server/serverReview.ts +41 -25
- package/3p/pi-subagents/src/agent-manager.ts +34 -1
- package/3p/pi-subagents/src/agent-runner.ts +66 -33
- package/3p/pi-subagents/src/index.ts +3 -38
- package/3p/pi-subagents/src/types.ts +4 -0
- package/extensions/orchestrator/agents/advisor.ts +35 -0
- package/extensions/orchestrator/agents/brainstorm-reviewer.ts +7 -7
- package/extensions/orchestrator/agents/code-reviewer.ts +27 -11
- package/extensions/orchestrator/agents/constraints.test.ts +82 -0
- package/extensions/orchestrator/agents/constraints.ts +66 -2
- package/extensions/orchestrator/agents/deep-debugger.ts +35 -0
- package/extensions/orchestrator/agents/plan-reviewer.ts +7 -7
- package/extensions/orchestrator/agents/planner.ts +9 -8
- package/extensions/orchestrator/agents/prompts.test.ts +120 -0
- package/extensions/orchestrator/agents/reviewer.ts +48 -0
- package/extensions/orchestrator/agents/task.ts +6 -20
- package/extensions/orchestrator/agents/tool-routing.ts +39 -1
- package/extensions/orchestrator/ai-comment-cleanup.test.ts +89 -0
- package/extensions/orchestrator/ai-comment-cleanup.ts +73 -0
- package/extensions/orchestrator/command-handlers.test.ts +47 -0
- package/extensions/orchestrator/command-handlers.ts +44 -28
- package/extensions/orchestrator/config.test.ts +40 -1
- package/extensions/orchestrator/config.ts +18 -2
- package/extensions/orchestrator/context.test.ts +54 -0
- package/extensions/orchestrator/context.ts +81 -2
- package/extensions/orchestrator/custom-footer.test.ts +91 -0
- package/extensions/orchestrator/custom-footer.ts +20 -20
- package/extensions/orchestrator/event-handlers.test.ts +412 -2
- package/extensions/orchestrator/event-handlers.ts +556 -227
- package/extensions/orchestrator/flant-infra.test.ts +25 -0
- package/extensions/orchestrator/flant-infra.ts +91 -0
- package/extensions/orchestrator/index.ts +1 -1
- package/extensions/orchestrator/integration.test.ts +411 -20
- package/extensions/orchestrator/messages.test.ts +30 -0
- package/extensions/orchestrator/messages.ts +6 -0
- package/extensions/orchestrator/model-registry.test.ts +48 -1
- package/extensions/orchestrator/model-registry.ts +43 -1
- package/extensions/orchestrator/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/extensions/orchestrator/orchestrator.test.ts +113 -0
- package/extensions/orchestrator/orchestrator.ts +197 -60
- package/extensions/orchestrator/phases/brainstorm.ts +20 -27
- package/extensions/orchestrator/phases/implementation.test.ts +11 -0
- package/extensions/orchestrator/phases/implementation.ts +4 -6
- package/extensions/orchestrator/phases/machine.test.ts +36 -0
- package/extensions/orchestrator/phases/machine.ts +11 -1
- package/extensions/orchestrator/phases/planning.test.ts +16 -0
- package/extensions/orchestrator/phases/planning.ts +11 -4
- package/extensions/orchestrator/phases/review-task.test.ts +20 -0
- package/extensions/orchestrator/phases/review-task.ts +47 -22
- package/extensions/orchestrator/phases/review.test.ts +34 -0
- package/extensions/orchestrator/phases/review.ts +44 -6
- package/extensions/orchestrator/plannotator.ts +9 -6
- package/extensions/orchestrator/pp-menu.test.ts +207 -1
- package/extensions/orchestrator/pp-menu.ts +514 -402
- package/extensions/orchestrator/pp-state-tools.test.ts +192 -0
- package/extensions/orchestrator/pp-state-tools.ts +249 -0
- package/extensions/orchestrator/rate-limit-fallback-flow.test.ts +128 -0
- package/extensions/orchestrator/rate-limit-fallback.test.ts +98 -0
- package/extensions/orchestrator/rate-limit-fallback.ts +243 -0
- package/extensions/orchestrator/state.test.ts +9 -0
- package/extensions/orchestrator/state.ts +15 -0
- package/extensions/orchestrator/test-helpers.ts +4 -1
- package/extensions/orchestrator/transition-controller.test.ts +100 -0
- package/extensions/orchestrator/transition-controller.ts +61 -3
- package/extensions/orchestrator/usage-tracker.ts +5 -1
- package/extensions/orchestrator/validate-artifacts.test.ts +20 -0
- package/extensions/orchestrator/validate-artifacts.ts +2 -2
- package/package.json +1 -1
- package/AGENTS.md +0 -28
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* agent-runner.ts — Core execution engine: creates sessions, runs agents, collects results.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
5
6
|
import type { Model } from "@earendil-works/pi-ai";
|
|
6
7
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
7
8
|
import {
|
|
@@ -27,6 +28,53 @@ const EXCLUDED_TOOL_NAMES = ["Agent", "get_subagent_result", "steer_subagent"];
|
|
|
27
28
|
const TRACER_KEY = Symbol.for("pi-pi:tracer");
|
|
28
29
|
const SUBAGENT_SESSION_KEY = Symbol.for("pi-pi:subagent-session");
|
|
29
30
|
|
|
31
|
+
// Concurrency-safe subagent lineage. The previous approach derived parent/depth
|
|
32
|
+
// from the mutable process-global SUBAGENT_SESSION_KEY, which is racy for
|
|
33
|
+
// CONCURRENT siblings: MAIN spawns opus/gpt/gemini in parallel, each runAgent's
|
|
34
|
+
// synchronous prologue read whatever the *previous sibling* had just written to
|
|
35
|
+
// the global — so gpt recorded opus as its parent, gemini recorded gpt, forming a
|
|
36
|
+
// bogus opus→gpt→gemini chain with depth 3-6 instead of three depth-1 children of
|
|
37
|
+
// MAIN. AsyncLocalStorage instead propagates DOWN the async call chain (a nested
|
|
38
|
+
// spawn inherits its true parent's store) but NOT ACROSS sibling promises (each
|
|
39
|
+
// sibling started from MAIN sees no store → parent=undefined, depth=1).
|
|
40
|
+
interface SubagentLineage {
|
|
41
|
+
depth: number;
|
|
42
|
+
subagentId?: string;
|
|
43
|
+
}
|
|
44
|
+
const subagentLineage = new AsyncLocalStorage<SubagentLineage>();
|
|
45
|
+
|
|
46
|
+
// The process-global SUBAGENT_SESSION_KEY marker is read elsewhere purely for
|
|
47
|
+
// truthiness ("is any subagent running in this process?", e.g. orchestrator nudge
|
|
48
|
+
// gates). Manage it with a ref-count so concurrent in-process siblings set/clear
|
|
49
|
+
// it correctly regardless of completion order (a plain save/restore would leak a
|
|
50
|
+
// stale marker whenever the last sibling to finish wasn't the last to start).
|
|
51
|
+
let activeSubagentRuns = 0;
|
|
52
|
+
let markerBeforeFirstRun: unknown;
|
|
53
|
+
function enterSubagentMarker(): void {
|
|
54
|
+
if (activeSubagentRuns === 0) {
|
|
55
|
+
// Snapshot whatever was there before our first concurrent run (undefined in
|
|
56
|
+
// the main process; the orchestrator's persistent bootstrap { depth: 1 } in a
|
|
57
|
+
// subagent process) so we can restore it exactly when the last run exits.
|
|
58
|
+
markerBeforeFirstRun = (globalThis as any)[SUBAGENT_SESSION_KEY];
|
|
59
|
+
}
|
|
60
|
+
activeSubagentRuns++;
|
|
61
|
+
const marker = (globalThis as any)[SUBAGENT_SESSION_KEY];
|
|
62
|
+
if (typeof marker !== "object" || marker === null) {
|
|
63
|
+
(globalThis as any)[SUBAGENT_SESSION_KEY] = { depth: 1 };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function exitSubagentMarker(): void {
|
|
67
|
+
activeSubagentRuns = Math.max(0, activeSubagentRuns - 1);
|
|
68
|
+
if (activeSubagentRuns === 0) {
|
|
69
|
+
if (markerBeforeFirstRun === undefined) {
|
|
70
|
+
delete (globalThis as any)[SUBAGENT_SESSION_KEY];
|
|
71
|
+
} else {
|
|
72
|
+
(globalThis as any)[SUBAGENT_SESSION_KEY] = markerBeforeFirstRun;
|
|
73
|
+
}
|
|
74
|
+
markerBeforeFirstRun = undefined;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
30
78
|
interface PiPiTracer {
|
|
31
79
|
openSubagent(meta: {
|
|
32
80
|
subagentId: string;
|
|
@@ -45,11 +93,6 @@ function getTracer(): PiPiTracer | undefined {
|
|
|
45
93
|
return (globalThis as any)[TRACER_KEY];
|
|
46
94
|
}
|
|
47
95
|
|
|
48
|
-
function subagentDepth(): number {
|
|
49
|
-
const marker = (globalThis as any)[SUBAGENT_SESSION_KEY];
|
|
50
|
-
return typeof marker?.depth === "number" ? marker.depth : 1;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
96
|
function traceSubagentEvent(subagentId: string | undefined, event: AgentSessionEvent, turnIndex: number): void {
|
|
54
97
|
if (!subagentId) return;
|
|
55
98
|
const tracer = getTracer();
|
|
@@ -242,21 +285,22 @@ export async function runAgent(
|
|
|
242
285
|
|
|
243
286
|
// Resolve working directory: worktree override > parent cwd
|
|
244
287
|
const effectiveCwd = options.cwd ?? ctx.cwd;
|
|
245
|
-
// Shared marker with extensions/orchestrator/index.ts (SUBAGENT_SESSION_KEY).
|
|
246
|
-
// Canonical shape is { depth: number }; a legacy boolean true is tolerated as depth 1.
|
|
247
|
-
const subagentSessionKey = Symbol.for("pi-pi:subagent-session");
|
|
248
|
-
const previousSubagentSession = (globalThis as any)[subagentSessionKey];
|
|
249
|
-
const previousDepth = typeof previousSubagentSession === "object" && previousSubagentSession !== null
|
|
250
|
-
? ((previousSubagentSession as { depth?: number }).depth ?? 0)
|
|
251
|
-
: previousSubagentSession
|
|
252
|
-
? 1
|
|
253
|
-
: 0;
|
|
254
|
-
const parentSubagentId = typeof previousSubagentSession === "object" && previousSubagentSession !== null
|
|
255
|
-
? (previousSubagentSession as { subagentId?: string }).subagentId
|
|
256
|
-
: undefined;
|
|
257
|
-
const subagentSessionState = { depth: previousDepth + 1, subagentId: options.subagentId };
|
|
258
|
-
(globalThis as any)[subagentSessionKey] = subagentSessionState;
|
|
259
288
|
|
|
289
|
+
// Derive lineage from the async-context store (see subagentLineage above).
|
|
290
|
+
// Reading it BEFORE entering the child's own als.run means concurrent siblings
|
|
291
|
+
// each see their real parent (MAIN → undefined store), not a leaked sibling.
|
|
292
|
+
const parentLineage = subagentLineage.getStore();
|
|
293
|
+
const parentSubagentId = parentLineage?.subagentId;
|
|
294
|
+
const traceDepth = (parentLineage?.depth ?? 0) + 1;
|
|
295
|
+
const childLineage: SubagentLineage = { depth: traceDepth, subagentId: options.subagentId };
|
|
296
|
+
|
|
297
|
+
// Mark the process as "running a subagent" for truthiness readers (see
|
|
298
|
+
// enter/exitSubagentMarker). Lineage (parent/depth) comes from als above, so
|
|
299
|
+
// this marker no longer participates in nesting math and its old sibling-race
|
|
300
|
+
// can no longer corrupt the org chart.
|
|
301
|
+
enterSubagentMarker();
|
|
302
|
+
|
|
303
|
+
return subagentLineage.run(childLineage, async () => {
|
|
260
304
|
try {
|
|
261
305
|
const env = await detectEnv(options.pi, effectiveCwd);
|
|
262
306
|
|
|
@@ -460,7 +504,7 @@ export async function runAgent(
|
|
|
460
504
|
description: options.subagentDescription,
|
|
461
505
|
parentToolCallId: options.parentToolCallId,
|
|
462
506
|
parentSubagentId,
|
|
463
|
-
depth:
|
|
507
|
+
depth: traceDepth,
|
|
464
508
|
systemPrompt,
|
|
465
509
|
effectivePrompt,
|
|
466
510
|
});
|
|
@@ -496,20 +540,9 @@ export async function runAgent(
|
|
|
496
540
|
}
|
|
497
541
|
return { responseText, session, aborted, steered: softLimitReached };
|
|
498
542
|
} finally {
|
|
499
|
-
|
|
500
|
-
? ((((globalThis as any)[subagentSessionKey]) as { depth?: number }).depth ?? subagentSessionState.depth)
|
|
501
|
-
: subagentSessionState.depth;
|
|
502
|
-
const nextDepth = Math.max(0, currentDepth - 1);
|
|
503
|
-
if (nextDepth === 0) {
|
|
504
|
-
if (previousSubagentSession === undefined) {
|
|
505
|
-
delete (globalThis as any)[subagentSessionKey];
|
|
506
|
-
} else {
|
|
507
|
-
(globalThis as any)[subagentSessionKey] = previousSubagentSession;
|
|
508
|
-
}
|
|
509
|
-
} else {
|
|
510
|
-
(globalThis as any)[subagentSessionKey] = { depth: nextDepth };
|
|
511
|
-
}
|
|
543
|
+
exitSubagentMarker();
|
|
512
544
|
}
|
|
545
|
+
});
|
|
513
546
|
}
|
|
514
547
|
|
|
515
548
|
/**
|
|
@@ -402,13 +402,7 @@ Use get_subagent_result for full output.`,
|
|
|
402
402
|
}
|
|
403
403
|
|
|
404
404
|
// Background completion: route through group join or send individual nudge
|
|
405
|
-
const firstProgressSeen = new Set<string>();
|
|
406
|
-
const firstTurnSeen = new Set<string>();
|
|
407
|
-
|
|
408
405
|
const manager = new AgentManager((record) => {
|
|
409
|
-
firstProgressSeen.delete(record.id);
|
|
410
|
-
firstTurnSeen.delete(record.id);
|
|
411
|
-
|
|
412
406
|
let eventData: ReturnType<typeof buildEventData> | undefined;
|
|
413
407
|
try {
|
|
414
408
|
eventData = buildEventData(record);
|
|
@@ -907,36 +901,9 @@ Guidelines:
|
|
|
907
901
|
const { state: bgState, callbacks: bgCallbacks } = createActivityTracker(effectiveMaxTurns);
|
|
908
902
|
|
|
909
903
|
let id: string;
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
pi.events.emit("subagents:first_tool", {
|
|
914
|
-
id,
|
|
915
|
-
type: subagentType,
|
|
916
|
-
description: params.description,
|
|
917
|
-
toolName,
|
|
918
|
-
});
|
|
919
|
-
};
|
|
920
|
-
const emitFirstTurn = (turnCount: number) => {
|
|
921
|
-
if (firstTurnSeen.has(id)) return;
|
|
922
|
-
firstTurnSeen.add(id);
|
|
923
|
-
pi.events.emit("subagents:first_turn", {
|
|
924
|
-
id,
|
|
925
|
-
type: subagentType,
|
|
926
|
-
description: params.description,
|
|
927
|
-
turnCount,
|
|
928
|
-
});
|
|
929
|
-
};
|
|
930
|
-
const originalBgToolActivity = bgCallbacks.onToolActivity;
|
|
931
|
-
bgCallbacks.onToolActivity = (activity) => {
|
|
932
|
-
originalBgToolActivity(activity);
|
|
933
|
-
if (activity.type === "start") emitFirstTool(activity.toolName);
|
|
934
|
-
};
|
|
935
|
-
const originalBgTurnEnd = bgCallbacks.onTurnEnd;
|
|
936
|
-
bgCallbacks.onTurnEnd = (turnCount) => {
|
|
937
|
-
originalBgTurnEnd(turnCount);
|
|
938
|
-
emitFirstTurn(turnCount);
|
|
939
|
-
};
|
|
904
|
+
// first_tool/first_turn are now emitted centrally by AgentManager.startAgent
|
|
905
|
+
// (the single choke point for ALL spawn paths, including RPC-spawned panels),
|
|
906
|
+
// so no per-branch wiring is needed here.
|
|
940
907
|
|
|
941
908
|
// Wrap onSessionCreated to wire output file streaming.
|
|
942
909
|
// The callback lazily reads record.outputFile (set right after spawn)
|
|
@@ -944,8 +911,6 @@ Guidelines:
|
|
|
944
911
|
const origBgOnSession = bgCallbacks.onSessionCreated;
|
|
945
912
|
bgCallbacks.onSessionCreated = (session: any) => {
|
|
946
913
|
origBgOnSession(session);
|
|
947
|
-
firstProgressSeen.delete(id);
|
|
948
|
-
firstTurnSeen.delete(id);
|
|
949
914
|
const rec = manager.getRecord(id);
|
|
950
915
|
if (rec?.outputFile) {
|
|
951
916
|
rec.outputCleanup = streamToOutputFile(session, rec.outputFile, id, ctx.cwd);
|
|
@@ -81,6 +81,10 @@ export interface AgentRecord {
|
|
|
81
81
|
worktreeResult?: { hasChanges: boolean; branch?: string };
|
|
82
82
|
/** The tool_use_id from the original Agent tool call. */
|
|
83
83
|
toolCallId?: string;
|
|
84
|
+
/** Whether the subagents:first_tool event has been emitted for this run. */
|
|
85
|
+
firstToolEmitted?: boolean;
|
|
86
|
+
/** Whether the subagents:first_turn event has been emitted for this run. */
|
|
87
|
+
firstTurnEmitted?: boolean;
|
|
84
88
|
/** Path to the streaming output transcript file. */
|
|
85
89
|
outputFile?: string;
|
|
86
90
|
/** Cleanup function for the output file stream subscription. */
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { PiPiConfig } from "../config.js";
|
|
2
|
+
import { resolveModel } from "../model-registry.js";
|
|
3
|
+
import { TOOLS_BLOCK, ALL_CBM_TOOLS, EXA_TOOLS, PRINCIPLES_BLOCK } from "./tool-routing.js";
|
|
4
|
+
|
|
5
|
+
export function createAdvisorAgent(config: PiPiConfig) {
|
|
6
|
+
return {
|
|
7
|
+
frontmatter: {
|
|
8
|
+
description: "Deep-reasoning advisor for design decisions and 'why is this broken' analysis (pi-pi)",
|
|
9
|
+
tools: `read, bash, grep, find, ls, lsp, ast_search, ${ALL_CBM_TOOLS}, ${EXA_TOOLS}`,
|
|
10
|
+
model: resolveModel(config.agents.subagents.simple.advisor.model),
|
|
11
|
+
thinking: config.agents.subagents.simple.advisor.thinking,
|
|
12
|
+
max_turns: 120,
|
|
13
|
+
prompt_mode: "replace",
|
|
14
|
+
},
|
|
15
|
+
prompt: [
|
|
16
|
+
"<constraints>",
|
|
17
|
+
"You are a deep-reasoning ADVISOR. You investigate one hard question — a design decision, an architecture tradeoff, a \"why is this broken\", or a correctness/soundness judgment — and return a reasoned recommendation backed by evidence.",
|
|
18
|
+
"These rules override your default helpfulness. Strict compliance is required.",
|
|
19
|
+
"You are READ-ONLY: you MUST NOT modify any file. Diagnose and advise; do NOT change code.",
|
|
20
|
+
"</constraints>",
|
|
21
|
+
"",
|
|
22
|
+
PRINCIPLES_BLOCK,
|
|
23
|
+
"",
|
|
24
|
+
TOOLS_BLOCK,
|
|
25
|
+
"",
|
|
26
|
+
"<task>",
|
|
27
|
+
"- Verify every claim with tool calls — read the actual code. Never reason from memory about this codebase.",
|
|
28
|
+
"- Generate multiple competing hypotheses or approaches before converging. Surface and question hidden assumptions.",
|
|
29
|
+
"- Scope recommendations by effort: name the quick fix vs the thorough one.",
|
|
30
|
+
"- Structure your answer: Diagnosis (what is actually true, with file:line evidence) → Options & tradeoffs → Recommendation.",
|
|
31
|
+
"- Be honest about uncertainty. If evidence is thin, say so and state what would resolve it.",
|
|
32
|
+
"</task>",
|
|
33
|
+
].join("\n"),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { VariantConfig } from "../config.js";
|
|
2
|
-
import { loadAllContextFiles } from "../context.js";
|
|
2
|
+
import { loadAllContextFiles, formatManifestBlock } from "../context.js";
|
|
3
3
|
import { resolveModel, getModelInfo } from "../model-registry.js";
|
|
4
4
|
import type { RepoInfo } from "../repo-utils.js";
|
|
5
5
|
import { buildRepoContext } from "./repo-context.js";
|
|
@@ -8,7 +8,7 @@ import { TOOLS_BLOCK, ALL_CBM_TOOLS, EXA_TOOLS, PRINCIPLES_BLOCK } from "./tool-
|
|
|
8
8
|
export function createBrainstormReviewerAgent(
|
|
9
9
|
variant: string,
|
|
10
10
|
variants: Record<string, VariantConfig>,
|
|
11
|
-
taskArtifacts: { userRequest: string; research: string; artifacts?: { name: string; content: string }[] },
|
|
11
|
+
taskArtifacts: { userRequest: string; research: string; artifacts?: { name: string; content: string }[]; manifest?: { title: string; path: string }[] },
|
|
12
12
|
outputPath: string,
|
|
13
13
|
contextDirs: string[],
|
|
14
14
|
phase?: string,
|
|
@@ -64,10 +64,10 @@ export function createBrainstormReviewerAgent(
|
|
|
64
64
|
"- INACCURACIES: (claims that don't match the code)",
|
|
65
65
|
"- SUGGESTIONS: (improvements, not required)",
|
|
66
66
|
"",
|
|
67
|
-
"
|
|
68
|
-
'- Agent(subagent_type="
|
|
69
|
-
'- Agent(subagent_type="
|
|
70
|
-
"Spawn multiple
|
|
67
|
+
"You may spawn ONLY explore/librarian subagents (subagent_type is REQUIRED — calls without it are rejected):",
|
|
68
|
+
'- Agent(subagent_type="explore", ...) — codebase research. Prefer this for most lookups. Fast and cheap.',
|
|
69
|
+
'- Agent(subagent_type="librarian", ...) — external docs, library APIs, web research.',
|
|
70
|
+
"Spawn multiple explore agents in parallel for broad searches. Do NOT spawn task, advisor, deep-debugger, or reviewer.",
|
|
71
71
|
"</task>",
|
|
72
72
|
"",
|
|
73
73
|
"# MANDATORY: Write your review to this exact file using the write tool:",
|
|
@@ -88,7 +88,7 @@ export function createBrainstormReviewerAgent(
|
|
|
88
88
|
: []),
|
|
89
89
|
...(repoContext ? [repoContext] : []),
|
|
90
90
|
"",
|
|
91
|
-
|
|
91
|
+
formatManifestBlock(taskArtifacts.manifest ?? []),
|
|
92
92
|
].join("\n"),
|
|
93
93
|
};
|
|
94
94
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { VariantConfig } from "../config.js";
|
|
2
|
-
import { loadAllContextFiles } from "../context.js";
|
|
2
|
+
import { loadAllContextFiles, formatManifestBlock } from "../context.js";
|
|
3
3
|
import { resolveModel, getModelInfo } from "../model-registry.js";
|
|
4
4
|
import type { RepoInfo } from "../repo-utils.js";
|
|
5
5
|
import { buildRepoContext } from "./repo-context.js";
|
|
@@ -8,7 +8,7 @@ import { TOOLS_BLOCK, ALL_CBM_TOOLS, EXA_TOOLS, PRINCIPLES_BLOCK } from "./tool-
|
|
|
8
8
|
export function createCodeReviewerAgent(
|
|
9
9
|
variant: string,
|
|
10
10
|
variants: Record<string, VariantConfig>,
|
|
11
|
-
taskArtifacts: { userRequest: string; research: string; synthesizedPlan: string },
|
|
11
|
+
taskArtifacts: { userRequest: string; research: string; synthesizedPlan?: string; manifest?: { title: string; path: string }[] },
|
|
12
12
|
outputPath: string,
|
|
13
13
|
contextDirs: string[],
|
|
14
14
|
phase?: string,
|
|
@@ -21,6 +21,9 @@ export function createCodeReviewerAgent(
|
|
|
21
21
|
const contextFiles = loadAllContextFiles(contextDirs, "codeReviewer", "system", phase, getModelInfo(variantConfig.model));
|
|
22
22
|
const contextBlock = contextFiles.map((f) => f.content).join("\n\n");
|
|
23
23
|
const repoContext = buildRepoContext(repos);
|
|
24
|
+
// A standalone review task (phase "review") has no synthesized plan: review the
|
|
25
|
+
// diff against USER_REQUEST.md/RESEARCH.md, not an implementation plan.
|
|
26
|
+
const hasPlan = typeof taskArtifacts.synthesizedPlan === "string" && taskArtifacts.synthesizedPlan.trim().length > 0;
|
|
24
27
|
|
|
25
28
|
return {
|
|
26
29
|
frontmatter: {
|
|
@@ -53,11 +56,15 @@ export function createCodeReviewerAgent(
|
|
|
53
56
|
"3. Read changed files for full context",
|
|
54
57
|
"4. Run lsp diagnostics on changed files",
|
|
55
58
|
"5. Use lsp findReferences to check callers of modified functions",
|
|
56
|
-
|
|
59
|
+
hasPlan
|
|
60
|
+
? "6. Check the implementation against the plan"
|
|
61
|
+
: "6. Check the changes against USER_REQUEST.md and RESEARCH.md (there is no implementation plan for a standalone review)",
|
|
57
62
|
"",
|
|
58
63
|
"Review criteria:",
|
|
59
64
|
"- Bugs: logic errors, off-by-ones, null handling, race conditions",
|
|
60
|
-
|
|
65
|
+
hasPlan
|
|
66
|
+
? "- Correctness: does it match the plan and user request?"
|
|
67
|
+
: "- Correctness: does it match the user request and the reviewed scope?",
|
|
61
68
|
"- Quality: error handling, edge cases, type safety",
|
|
62
69
|
"- Missing: untested paths, unhandled errors, incomplete implementations",
|
|
63
70
|
"",
|
|
@@ -81,10 +88,20 @@ export function createCodeReviewerAgent(
|
|
|
81
88
|
"- MINOR: (nice to have)",
|
|
82
89
|
"- OPEN QUESTIONS: (low-confidence concerns, speculative follow-ups)",
|
|
83
90
|
"",
|
|
84
|
-
"
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
91
|
+
"After the findings, include a machine-readable ANCHORS block so the synthesizer can place",
|
|
92
|
+
"the findings at their exact locations (in source AI_COMMENT markers and/or GitHub PR line comments).",
|
|
93
|
+
"Emit one line per actionable finding (CRITICAL/MAJOR/MINOR), in this EXACT format:",
|
|
94
|
+
"ANCHORS:",
|
|
95
|
+
"<relative/path/from/repo/root>:<line> — <severity>: <one-line finding>",
|
|
96
|
+
"Rules:",
|
|
97
|
+
"- Use the repo-relative path (as `git diff` shows it) and a single 1-based line number on the NEW side of the diff.",
|
|
98
|
+
"- One finding per line; omit findings you cannot pin to a concrete file:line (keep those in OPEN QUESTIONS).",
|
|
99
|
+
"- If there are no actionable findings, write `ANCHORS:` followed by `(none)`.",
|
|
100
|
+
"",
|
|
101
|
+
"You may spawn ONLY explore/librarian subagents (subagent_type is REQUIRED — calls without it are rejected):",
|
|
102
|
+
'- Agent(subagent_type="explore", ...) — codebase research. Prefer this for most lookups. Fast and cheap.',
|
|
103
|
+
'- Agent(subagent_type="librarian", ...) — external docs, library APIs, web research.',
|
|
104
|
+
"Spawn multiple explore agents in parallel for broad searches. Do NOT spawn task, advisor, deep-debugger, or reviewer.",
|
|
88
105
|
"</task>",
|
|
89
106
|
"",
|
|
90
107
|
// --- dynamic suffix ---
|
|
@@ -97,11 +114,10 @@ export function createCodeReviewerAgent(
|
|
|
97
114
|
"=== RESEARCH ===",
|
|
98
115
|
taskArtifacts.research,
|
|
99
116
|
"",
|
|
100
|
-
"=== SYNTHESIZED PLAN ===",
|
|
101
|
-
taskArtifacts.synthesizedPlan,
|
|
117
|
+
...(hasPlan ? ["=== SYNTHESIZED PLAN ===", taskArtifacts.synthesizedPlan as string, ""] : []),
|
|
102
118
|
...(repoContext ? [repoContext] : []),
|
|
103
119
|
"",
|
|
104
|
-
|
|
120
|
+
formatManifestBlock(taskArtifacts.manifest ?? []),
|
|
105
121
|
].join("\n"),
|
|
106
122
|
};
|
|
107
123
|
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { closingBlockInstruction, completionLine, constraintsBlock } from "./constraints.js";
|
|
3
|
+
|
|
4
|
+
describe("completionLine", () => {
|
|
5
|
+
it("guided plan and implement instruct calling pp_phase_complete on completion", () => {
|
|
6
|
+
for (const phase of ["plan", "implement"] as const) {
|
|
7
|
+
const line = completionLine(phase, "guided");
|
|
8
|
+
expect(line).toContain("call pp_phase_complete");
|
|
9
|
+
expect(line).not.toContain("Do NOT advance on your own");
|
|
10
|
+
expect(line).not.toMatch(/do not.*wait for.*input/i);
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("guided review and debug stay hands-off (no unprompted self-complete)", () => {
|
|
15
|
+
for (const phase of ["review", "debug"] as const) {
|
|
16
|
+
const line = completionLine(phase, "guided");
|
|
17
|
+
expect(line).toContain("Do NOT advance on your own or call pp_phase_complete unprompted");
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("guided brainstorm never self-completes", () => {
|
|
22
|
+
const line = completionLine("brainstorm", "guided");
|
|
23
|
+
expect(line).toContain("Do NOT call pp_phase_complete yourself");
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("autonomous phases always self-complete regardless of phase", () => {
|
|
27
|
+
for (const phase of ["plan", "implement", "review", "brainstorm"] as const) {
|
|
28
|
+
expect(completionLine(phase, "autonomous")).toContain("call pp_phase_complete");
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("guided handoff phases require the standardized closing block", () => {
|
|
33
|
+
for (const phase of ["brainstorm", "review", "debug"] as const) {
|
|
34
|
+
const line = completionLine(phase, "guided");
|
|
35
|
+
expect(line).toContain("the standardized block");
|
|
36
|
+
expect(line).toContain("Advance via the /pp menu");
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("autonomous phases do not emit the prose closing block", () => {
|
|
41
|
+
expect(completionLine("brainstorm", "autonomous")).not.toContain("Advance via the /pp menu");
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe("closingBlockInstruction", () => {
|
|
46
|
+
it("spells out the exact block with a blank line between summary and advance, no rules", () => {
|
|
47
|
+
const block = closingBlockInstruction("brainstorm");
|
|
48
|
+
expect(block).toContain("Advance via the /pp menu to move into plan");
|
|
49
|
+
expect(block).not.toContain("────");
|
|
50
|
+
expect(block).toContain("/pp");
|
|
51
|
+
expect(block).toContain("✅ <one-sentence summary of what this phase produced>\n\n▶ Advance via the /pp menu to move into plan.");
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("prepends the Review Summary schema ONLY for the review phase, before the closing block", () => {
|
|
55
|
+
const review = closingBlockInstruction("review");
|
|
56
|
+
expect(review).toContain("## Review Summary");
|
|
57
|
+
expect(review).toContain("| # | Severity | Location | Finding |");
|
|
58
|
+
expect(review).toContain("BLOCKER");
|
|
59
|
+
// The ✅/▶ lines must remain the final lines of the block.
|
|
60
|
+
expect(review.trimEnd().endsWith("▶ Advance via the /pp menu to move into plan.")).toBe(true);
|
|
61
|
+
expect(review.indexOf("## Review Summary")).toBeLessThan(review.indexOf("✅ <one-sentence summary"));
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("does NOT emit the Review Summary schema for brainstorm or debug closes", () => {
|
|
65
|
+
for (const phase of ["brainstorm", "debug"] as const) {
|
|
66
|
+
expect(closingBlockInstruction(phase)).not.toContain("## Review Summary");
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe("constraintsBlock", () => {
|
|
72
|
+
it("guided implement block tells the agent to self-complete", () => {
|
|
73
|
+
const block = constraintsBlock("implement", "guided");
|
|
74
|
+
expect(block).toContain("call pp_phase_complete");
|
|
75
|
+
expect(block).not.toContain("Do NOT advance on your own");
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("guided plan block tells the agent to self-complete", () => {
|
|
79
|
+
const block = constraintsBlock("plan", "guided");
|
|
80
|
+
expect(block).toContain("call pp_phase_complete");
|
|
81
|
+
});
|
|
82
|
+
});
|
|
@@ -3,6 +3,14 @@ import type { Phase, TaskMode } from "../state.js";
|
|
|
3
3
|
const READONLY_CONSTRAINT =
|
|
4
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
5
|
|
|
6
|
+
// The review phase is read-only EXCEPT for publishing findings: the main agent may
|
|
7
|
+
// insert or remove `AI_COMMENT:` markers in source files (file comments), and may
|
|
8
|
+
// run `gh` to post/read GitHub PR line comments AND bundled pull-request reviews
|
|
9
|
+
// (PR comments) — and nothing else (no fixes, no other edits, no other state-changing
|
|
10
|
+
// commands). This is the reviewer→user mirror of the user→reviewer `AI_REVIEW:` markers.
|
|
11
|
+
const REVIEW_READONLY_CONSTRAINT =
|
|
12
|
+
"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, WITH ONE EXCEPTION: when publishing review findings you MAY insert or remove `AI_COMMENT:` markers in source files (in each file's native comment syntax) and MAY run `gh` to post or read GitHub PR comments — including line comments and bundled pull-request reviews (a review body plus its line comments) — and nothing else. Do NOT apply fixes or make any other source change. If you find a fix worth making, record it in your output.";
|
|
13
|
+
|
|
6
14
|
const IMPLEMENT_CONSTRAINT =
|
|
7
15
|
"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
16
|
|
|
@@ -16,9 +24,62 @@ export function isReadOnlyPhase(phase: Phase): boolean {
|
|
|
16
24
|
export function phaseConstraint(phase: Phase): string {
|
|
17
25
|
if (phase === "implement") return IMPLEMENT_CONSTRAINT;
|
|
18
26
|
if (phase === "quick") return QUICK_CONSTRAINT;
|
|
27
|
+
if (phase === "review") return REVIEW_READONLY_CONSTRAINT;
|
|
19
28
|
return READONLY_CONSTRAINT;
|
|
20
29
|
}
|
|
21
30
|
|
|
31
|
+
// Guided phases that stop and hand back to the user (brainstorm, review, debug) end their turn
|
|
32
|
+
// with prose rather than a tool call. To keep that handoff consistent, the model must close with
|
|
33
|
+
// this exact block. NEXT_PHASE_LABEL supplies the phase the /pp menu advances into.
|
|
34
|
+
const NEXT_PHASE_LABEL: Partial<Record<Phase, string>> = {
|
|
35
|
+
brainstorm: "plan",
|
|
36
|
+
review: "plan",
|
|
37
|
+
debug: "plan",
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// The structured summary a review must print when it finishes, on BOTH review finish paths
|
|
41
|
+
// (the review-cycle synthesis in review.ts and the ordinary review-task close here). Counts
|
|
42
|
+
// reconcile with the `ANCHORS:` block: when it is `(none)`, Anchored=0 and every finding is
|
|
43
|
+
// Un-anchorable. Location is `file:line`, or `file:—` for un-anchorable findings.
|
|
44
|
+
export const REVIEW_SUMMARY_SCHEMA = [
|
|
45
|
+
"## Review Summary",
|
|
46
|
+
"",
|
|
47
|
+
"Scope: <repos/PR/branch/range + change size>",
|
|
48
|
+
"",
|
|
49
|
+
"| Repo | PR | Findings | Anchored | Un-anchorable |",
|
|
50
|
+
"|------|----|----------|----------|---------------|",
|
|
51
|
+
"| <owner/repo> | #<n> | <total> | <in-diff> | <not-in-diff> |",
|
|
52
|
+
"",
|
|
53
|
+
"### Findings",
|
|
54
|
+
"| # | Severity | Location | Finding |",
|
|
55
|
+
"|---|----------|----------|---------|",
|
|
56
|
+
"| 1 | BLOCKER | <file:line or file:—> | <one line> |",
|
|
57
|
+
"",
|
|
58
|
+
"Next step: /pp → Next → Publish to post these as GitHub PR comments or file comments.",
|
|
59
|
+
].join("\n");
|
|
60
|
+
|
|
61
|
+
// Instruction wrapping REVIEW_SUMMARY_SCHEMA: severity vocabulary is fixed and the summary
|
|
62
|
+
// degrades gracefully to a single-repo / no-PR row when only one repo (or no PR) was resolved.
|
|
63
|
+
export const reviewSummaryInstruction = [
|
|
64
|
+
"Before the closing block, print a structured Review Summary in EXACTLY this shape (fill in the values; severity is one of BLOCKER / MAJOR / MINOR, uppercase):",
|
|
65
|
+
"",
|
|
66
|
+
REVIEW_SUMMARY_SCHEMA,
|
|
67
|
+
"",
|
|
68
|
+
"Counts MUST reconcile with the `ANCHORS:` block: an `(none)` block means Anchored=0 and every finding is Un-anchorable. Fill the table from whatever repos/PR you resolved; degrade to a single-repo / no-PR row when there is only one repo or no PR.",
|
|
69
|
+
].join("\n");
|
|
70
|
+
|
|
71
|
+
export function closingBlockInstruction(phase: Phase): string {
|
|
72
|
+
const next = NEXT_PHASE_LABEL[phase] ?? "the next phase";
|
|
73
|
+
const summary = phase === "review" ? reviewSummaryInstruction + "\n\n" : "";
|
|
74
|
+
return [
|
|
75
|
+
summary +
|
|
76
|
+
"End that turn with EXACTLY this block, verbatim, as the final lines of your message (fill the summary line with one sentence; change nothing else):",
|
|
77
|
+
"✅ <one-sentence summary of what this phase produced>",
|
|
78
|
+
"",
|
|
79
|
+
`▶ Advance via the /pp menu to move into ${next}.`,
|
|
80
|
+
].join("\n");
|
|
81
|
+
}
|
|
82
|
+
|
|
22
83
|
export function completionLine(phase: Phase, mode: TaskMode): string {
|
|
23
84
|
if (phase === "quick") {
|
|
24
85
|
return "When the user's request is complete, call pp_phase_complete. Do NOT stop and wait for the user before then.";
|
|
@@ -27,9 +88,12 @@ export function completionLine(phase: Phase, mode: TaskMode): string {
|
|
|
27
88
|
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
89
|
}
|
|
29
90
|
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.";
|
|
91
|
+
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. When you have delivered a complete answer and are handing back for the user to advance, close with the standardized block. " + closingBlockInstruction(phase);
|
|
92
|
+
}
|
|
93
|
+
if (phase === "plan" || phase === "implement") {
|
|
94
|
+
return "When you judge this phase complete, call pp_phase_complete — the extension opens the advance gate for the user to review and confirm. Do NOT instead stop and ask the user to run /pp manually.";
|
|
31
95
|
}
|
|
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.";
|
|
96
|
+
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. Close with the standardized block. " + closingBlockInstruction(phase);
|
|
33
97
|
}
|
|
34
98
|
|
|
35
99
|
const PHASE_IDENTITY: Record<string, string> = {
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { PiPiConfig } from "../config.js";
|
|
2
|
+
import { resolveModel } from "../model-registry.js";
|
|
3
|
+
import { TOOLS_BLOCK, ALL_CBM_TOOLS, EXA_TOOLS, PRINCIPLES_BLOCK } from "./tool-routing.js";
|
|
4
|
+
|
|
5
|
+
export function createDeepDebuggerAgent(config: PiPiConfig) {
|
|
6
|
+
return {
|
|
7
|
+
frontmatter: {
|
|
8
|
+
description: "Deep root-cause analysis for HARD, persistent failures — not every error (pi-pi)",
|
|
9
|
+
tools: `read, write, edit, bash, grep, find, ls, lsp, ast_search, ${ALL_CBM_TOOLS}, ${EXA_TOOLS}`,
|
|
10
|
+
model: resolveModel(config.agents.subagents.simple["deep-debugger"].model),
|
|
11
|
+
thinking: config.agents.subagents.simple["deep-debugger"].thinking,
|
|
12
|
+
max_turns: 120,
|
|
13
|
+
prompt_mode: "replace",
|
|
14
|
+
},
|
|
15
|
+
prompt: [
|
|
16
|
+
"<constraints>",
|
|
17
|
+
"You are a DEEP DEBUGGER. You do root-cause analysis on hard, persistent failures — failing tests, build/compile errors, regressions, flaky behavior — that quick attempts have NOT resolved. Do NOT engage for trivial or first-attempt errors.",
|
|
18
|
+
"These rules override your default helpfulness. Strict compliance is required.",
|
|
19
|
+
"You have write/edit access for DIAGNOSIS ONLY: creating repro scripts, adding temporary logging, or running experiments. You MUST NOT write the actual fix in the source code — find the root cause and recommend the fix; do NOT apply it. Remove any temporary diagnostic artifacts you create.",
|
|
20
|
+
"</constraints>",
|
|
21
|
+
"",
|
|
22
|
+
PRINCIPLES_BLOCK,
|
|
23
|
+
"",
|
|
24
|
+
TOOLS_BLOCK,
|
|
25
|
+
"",
|
|
26
|
+
"<task>",
|
|
27
|
+
"- Reproduce/inspect first: run the failing command, read the actual error and stack trace, check recent changes (git diff, cbm_changes).",
|
|
28
|
+
"- Form competing hypotheses. For each, gather evidence FOR and AGAINST with tool calls. Do not commit to the first plausible cause.",
|
|
29
|
+
"- Trace the failure to its true root, not the surface symptom. Use lsp findReferences / cbm_trace to follow the chain.",
|
|
30
|
+
"- Report: Symptom → Hypotheses considered (with evidence) → Root cause (with file:line proof) → Minimal recommended fix.",
|
|
31
|
+
"- If you cannot prove the root cause, say so: report the narrowed-down suspects and the single most useful next probe.",
|
|
32
|
+
"</task>",
|
|
33
|
+
].join("\n"),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { VariantConfig } from "../config.js";
|
|
2
|
-
import { loadAllContextFiles } from "../context.js";
|
|
2
|
+
import { loadAllContextFiles, formatManifestBlock } from "../context.js";
|
|
3
3
|
import { resolveModel, getModelInfo } from "../model-registry.js";
|
|
4
4
|
import type { RepoInfo } from "../repo-utils.js";
|
|
5
5
|
import { buildRepoContext } from "./repo-context.js";
|
|
@@ -8,7 +8,7 @@ import { TOOLS_BLOCK, ALL_CBM_TOOLS, EXA_TOOLS, PRINCIPLES_BLOCK } from "./tool-
|
|
|
8
8
|
export function createPlanReviewerAgent(
|
|
9
9
|
variant: string,
|
|
10
10
|
variants: Record<string, VariantConfig>,
|
|
11
|
-
taskArtifacts: { userRequest: string; research: string; synthesizedPlan: string },
|
|
11
|
+
taskArtifacts: { userRequest: string; research: string; synthesizedPlan: string; manifest?: { title: string; path: string }[] },
|
|
12
12
|
outputPath: string,
|
|
13
13
|
contextDirs: string[],
|
|
14
14
|
phase?: string,
|
|
@@ -63,10 +63,10 @@ export function createPlanReviewerAgent(
|
|
|
63
63
|
"- BLOCKERS: (critical issues that must be fixed)",
|
|
64
64
|
"- SUGGESTIONS: (improvements, not required)",
|
|
65
65
|
"",
|
|
66
|
-
"
|
|
67
|
-
'- Agent(subagent_type="
|
|
68
|
-
'- Agent(subagent_type="
|
|
69
|
-
"Spawn multiple
|
|
66
|
+
"You may spawn ONLY explore/librarian subagents (subagent_type is REQUIRED — calls without it are rejected):",
|
|
67
|
+
'- Agent(subagent_type="explore", ...) — codebase research. Prefer this for most lookups. Fast and cheap.',
|
|
68
|
+
'- Agent(subagent_type="librarian", ...) — external docs, library APIs, web research.',
|
|
69
|
+
"Spawn multiple explore agents in parallel for broad searches. Do NOT spawn task, advisor, deep-debugger, or reviewer.",
|
|
70
70
|
"</task>",
|
|
71
71
|
"",
|
|
72
72
|
// --- dynamic suffix ---
|
|
@@ -87,7 +87,7 @@ export function createPlanReviewerAgent(
|
|
|
87
87
|
taskArtifacts.synthesizedPlan,
|
|
88
88
|
...(repoContext ? [repoContext] : []),
|
|
89
89
|
"",
|
|
90
|
-
|
|
90
|
+
formatManifestBlock(taskArtifacts.manifest ?? []),
|
|
91
91
|
].join("\n"),
|
|
92
92
|
};
|
|
93
93
|
}
|