pi-plans 0.2.0 → 0.3.1
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/README.md +90 -26
- package/agents/ref-analyst.md +18 -0
- package/index.ts +121 -9
- package/package.json +16 -1
- package/references/pi-planning-workflow.md +21 -6
- package/references/state-and-config.md +52 -5
- package/scripts/validate.ts +5 -0
- package/skills/plan-with-refs/SKILL.md +3 -3
- package/src/code-graph/commands.ts +483 -0
- package/src/code-graph/discovery.ts +118 -0
- package/src/code-graph/git.ts +108 -0
- package/src/code-graph/identity.ts +59 -0
- package/src/code-graph/indexer.ts +281 -0
- package/src/code-graph/materialize.ts +166 -0
- package/src/code-graph/mode.ts +28 -0
- package/src/code-graph/mutations.ts +160 -0
- package/src/code-graph/parser.ts +51 -0
- package/src/code-graph/parsers/javascript.ts +35 -0
- package/src/code-graph/parsers/python.ts +160 -0
- package/src/code-graph/parsers/tree-sitter.ts +316 -0
- package/src/code-graph/paths.ts +85 -0
- package/src/code-graph/prompts.ts +18 -0
- package/src/code-graph/resolver.ts +69 -0
- package/src/code-graph/runtime.ts +158 -0
- package/src/code-graph/schema.ts +135 -0
- package/src/code-graph/screening.ts +82 -0
- package/src/code-graph/store.ts +278 -0
- package/src/code-graph/summary.ts +435 -0
- package/src/code-graph/types.ts +163 -0
- package/src/compaction.ts +1125 -371
- package/src/config-command.ts +361 -0
- package/src/exec.ts +508 -693
- package/src/guard.ts +14 -1
- package/src/refine-prompts.ts +109 -0
- package/src/refine-ui-helpers.ts +71 -18
- package/src/refine-ui-state.ts +88 -22
- package/src/refine-ui.ts +210 -102
- package/src/state.ts +36 -7
- package/src/subagent.ts +164 -61
- package/src/termination-prompt.ts +22 -0
- package/tests/analyze-refs.test.ts +265 -0
- package/tests/ask-choice.test.ts +264 -0
- package/tests/autocomplete.test.ts +6 -1
- package/tests/code-graph-apply-action.test.ts +173 -0
- package/tests/code-graph-apply.test.ts +185 -0
- package/tests/code-graph-commands.test.ts +211 -0
- package/tests/code-graph-db.test.ts +166 -0
- package/tests/code-graph-discovery.test.ts +38 -0
- package/tests/code-graph-git.test.ts +94 -0
- package/tests/code-graph-index.test.ts +175 -0
- package/tests/code-graph-loop.e2e.test.ts +159 -0
- package/tests/code-graph-mutations.test.ts +117 -0
- package/tests/code-graph-parser.test.ts +85 -0
- package/tests/code-graph-rollback.test.ts +100 -0
- package/tests/code-graph-summary-batching.test.ts +518 -0
- package/tests/code-graph-summary.test.ts +148 -0
- package/tests/compaction.test.ts +371 -57
- package/tests/config-command.test.ts +263 -0
- package/tests/exec.test.ts +808 -241
- package/tests/fixtures/code-graph/sample.js +36 -0
- package/tests/fixtures/code-graph/sample.py +20 -0
- package/tests/fixtures/code-graph/sample.ts +15 -0
- package/tests/graph-aware-file-tools.test.ts +411 -0
- package/tests/guard.test.ts +27 -1
- package/tests/plans.test.ts +10 -0
- package/tests/refine-prompts.test.ts +101 -2
- package/tests/refine-ui.test.ts +371 -72
- package/tests/state.test.ts +32 -0
- package/tests/subagent.test.ts +48 -20
- package/tools/analyze-refs.ts +263 -0
- package/tools/ask-choice.ts +159 -11
- package/tools/code-graph.ts +277 -0
- package/tools/graph-aware-file-tools.ts +392 -0
- package/tools/plans.ts +97 -2
- package/tools/refine.ts +61 -15
package/tools/plans.ts
CHANGED
|
@@ -6,6 +6,11 @@
|
|
|
6
6
|
import { StringEnum } from "@earendil-works/pi-ai";
|
|
7
7
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
8
8
|
import { Type } from "typebox";
|
|
9
|
+
import { computeDrift } from "../src/code-graph/commands.ts";
|
|
10
|
+
import { gitAddAllAndCommit } from "../src/code-graph/git.ts";
|
|
11
|
+
import { loadGraphRuntime } from "../src/code-graph/runtime.ts";
|
|
12
|
+
import { resolveCanonicalWorktree } from "../src/code-graph/paths.ts";
|
|
13
|
+
import { Store } from "../src/code-graph/store.ts";
|
|
9
14
|
import {
|
|
10
15
|
initState,
|
|
11
16
|
loadConfig,
|
|
@@ -15,7 +20,9 @@ import {
|
|
|
15
20
|
recordSubagent,
|
|
16
21
|
resolveStateRootOrNull,
|
|
17
22
|
setArtifactRoot,
|
|
23
|
+
setGraphEnabled,
|
|
18
24
|
setLanguage,
|
|
25
|
+
setRefsRoot,
|
|
19
26
|
setRunStatus,
|
|
20
27
|
setRole,
|
|
21
28
|
showConfig,
|
|
@@ -31,9 +38,12 @@ const PlansParams = Type.Object({
|
|
|
31
38
|
"show",
|
|
32
39
|
"set-language",
|
|
33
40
|
"set-artifact-root",
|
|
41
|
+
"set-refs-root",
|
|
42
|
+
"set-graph-enabled",
|
|
34
43
|
"set-role",
|
|
35
44
|
"start-run",
|
|
36
45
|
"set-status",
|
|
46
|
+
"final-commit",
|
|
37
47
|
"record-decision",
|
|
38
48
|
"record-ref",
|
|
39
49
|
"record-subagent",
|
|
@@ -50,6 +60,10 @@ const PlansParams = Type.Object({
|
|
|
50
60
|
languageSource: Type.Optional(StringEnum(["user", "auto"] as const)),
|
|
51
61
|
artifactRoot: Type.Optional(Type.String({ description: "set-artifact-root: planning docs root, e.g. ./docs/pi-plans" })),
|
|
52
62
|
artifactRootSource: Type.Optional(StringEnum(["user", "auto"] as const)),
|
|
63
|
+
refsRoot: Type.Optional(Type.String({ description: "set-refs-root: reference downloads root, e.g. .git/pi-plans/refs" })),
|
|
64
|
+
refsRootSource: Type.Optional(StringEnum(["user", "auto"] as const)),
|
|
65
|
+
enabled: Type.Optional(Type.Boolean({ description: "set-graph-enabled: enable/disable the code graph" })),
|
|
66
|
+
message: Type.Optional(Type.String({ description: "final-commit: commit message body" })),
|
|
53
67
|
role: Type.Optional(StringEnum(["reviewer", "criticizer"] as const)),
|
|
54
68
|
mode: Type.Optional(StringEnum(["delegated-subagent", "current-session"] as const)),
|
|
55
69
|
modelSelector: Type.Optional(
|
|
@@ -88,7 +102,7 @@ const PlansParams = Type.Object({
|
|
|
88
102
|
),
|
|
89
103
|
subagent: Type.Optional(
|
|
90
104
|
Type.Object({
|
|
91
|
-
role: StringEnum(["reviewer", "criticizer"] as const),
|
|
105
|
+
role: StringEnum(["reviewer", "criticizer", "ref-analyst"] as const),
|
|
92
106
|
name: Type.String(),
|
|
93
107
|
model: Type.Optional(Type.String()),
|
|
94
108
|
sessionDir: Type.Optional(Type.String()),
|
|
@@ -106,6 +120,54 @@ export function setRunStartAppender(appender: ((runId: string, artifactDir: stri
|
|
|
106
120
|
runStartAppender = appender;
|
|
107
121
|
}
|
|
108
122
|
|
|
123
|
+
/** plans action final-commit: gate on code-graph drift (zero pending +
|
|
124
|
+
* invariants (a)/(b) clean), then `git add -A` and commit the plan delivery.
|
|
125
|
+
* A clean tree is a safe no-op. Returns a machine-readable result. */
|
|
126
|
+
export async function finalCommit(
|
|
127
|
+
workdir: string,
|
|
128
|
+
message: string,
|
|
129
|
+
): Promise<{
|
|
130
|
+
ok: boolean;
|
|
131
|
+
committed: string | null;
|
|
132
|
+
noop: boolean;
|
|
133
|
+
reason?: string;
|
|
134
|
+
drift?: unknown;
|
|
135
|
+
}> {
|
|
136
|
+
const paths = resolveCanonicalWorktree(workdir);
|
|
137
|
+
const runtime = await loadGraphRuntime();
|
|
138
|
+
if (!runtime.status.sqliteAvailable) {
|
|
139
|
+
throw new StateError("final-commit requires node:sqlite (code graph unavailable)");
|
|
140
|
+
}
|
|
141
|
+
const store = new Store(
|
|
142
|
+
{ dbPath: paths.codeGraphDb, worktreeRoot: paths.worktreeRoot, gitCommonDir: paths.gitCommonDir },
|
|
143
|
+
runtime.runtime.sqlite,
|
|
144
|
+
);
|
|
145
|
+
try {
|
|
146
|
+
let drift: ReturnType<typeof computeDrift> | null = null;
|
|
147
|
+
try {
|
|
148
|
+
drift = computeDrift(store, paths.worktreeRoot);
|
|
149
|
+
} catch {
|
|
150
|
+
drift = null; // graph never initialized: fall through to plain commit
|
|
151
|
+
}
|
|
152
|
+
if (drift && (drift.pending.length > 0 || !drift.ok)) {
|
|
153
|
+
return {
|
|
154
|
+
ok: false,
|
|
155
|
+
committed: null,
|
|
156
|
+
noop: false,
|
|
157
|
+
reason: `graph drift dirty: ${drift.recommendation}`,
|
|
158
|
+
drift,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
const head = gitAddAllAndCommit(paths.worktreeRoot, message);
|
|
162
|
+
if (!head) {
|
|
163
|
+
return { ok: true, committed: null, noop: true, reason: "nothing to commit — tree already clean" };
|
|
164
|
+
}
|
|
165
|
+
return { ok: true, committed: head, noop: false };
|
|
166
|
+
} finally {
|
|
167
|
+
store.close();
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
109
171
|
export function registerPlansTool(pi: ExtensionAPI): void {
|
|
110
172
|
setRunStartAppender((runId, artifactDir) => {
|
|
111
173
|
pi.appendEntry("pi-plans-run-start", { runId, artifactDir });
|
|
@@ -114,7 +176,7 @@ export function registerPlansTool(pi: ExtensionAPI): void {
|
|
|
114
176
|
name: "plans",
|
|
115
177
|
label: "Plans",
|
|
116
178
|
description:
|
|
117
|
-
"Manage pi-plans planning state in the target workspace: init/show config, set language and planning docs root plus reviewer/criticizer roles, start planning runs, record decisions/refs/subagents, and update run status. State lives in .git/pi_plans/ inside the resolved git common dir. Actions: init, show, set-language, set-artifact-root, set-role, start-run, set-status, record-decision, record-ref, record-subagent.",
|
|
179
|
+
"Manage pi-plans planning state in the target workspace: init/show config, set language and planning docs root plus reviewer/criticizer roles and the code-graph enabled flag, start planning runs, record decisions/refs/subagents, and update run status. State lives in .git/pi_plans/ inside the resolved git common dir. Actions: init, show, set-language, set-artifact-root, set-refs-root, set-graph-enabled, set-role, start-run, set-status, final-commit, record-decision, record-ref, record-subagent.",
|
|
118
180
|
promptSnippet: "Manage pi-plans planning state, runs, and ledgers",
|
|
119
181
|
parameters: PlansParams,
|
|
120
182
|
|
|
@@ -126,12 +188,32 @@ export function registerPlansTool(pi: ExtensionAPI): void {
|
|
|
126
188
|
case "init": {
|
|
127
189
|
const ensured = initState(workdir);
|
|
128
190
|
result = { config: ensured.config, stateRoot: ensured.stateRoot, notices: ensured.notices };
|
|
191
|
+
if (ensured.config.graph_enabled === null) {
|
|
192
|
+
result = {
|
|
193
|
+
...(result as Record<string, unknown>),
|
|
194
|
+
hint: "graph_enabled is null (never asked). Ask the user once via ask_choice whether to enable the code graph (recommended: yes for repos with an initialized graph; see references/state-and-config.md), then persist with plans (action: set-graph-enabled, enabled: true|false). This question does not count against the planning-question limit.",
|
|
195
|
+
};
|
|
196
|
+
}
|
|
129
197
|
break;
|
|
130
198
|
}
|
|
131
199
|
case "show": {
|
|
132
200
|
const config = showConfig(workdir);
|
|
133
201
|
const stateRoot = resolveStateRootOrNull(workdir);
|
|
134
202
|
result = { config, stateRoot };
|
|
203
|
+
if (config.graph_enabled === null) {
|
|
204
|
+
result = {
|
|
205
|
+
...(result as Record<string, unknown>),
|
|
206
|
+
hint: "graph_enabled is null (never asked). Ask the user once via ask_choice, then persist with plans (action: set-graph-enabled, enabled: true|false).",
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
case "set-graph-enabled": {
|
|
212
|
+
if (typeof params.enabled !== "boolean") {
|
|
213
|
+
throw new StateError("set-graph-enabled requires enabled (boolean)");
|
|
214
|
+
}
|
|
215
|
+
const updated = setGraphEnabled(workdir, params.enabled);
|
|
216
|
+
result = { config: updated.config, stateRoot: updated.stateRoot, notices: updated.notices };
|
|
135
217
|
break;
|
|
136
218
|
}
|
|
137
219
|
case "set-language": {
|
|
@@ -150,6 +232,14 @@ export function registerPlansTool(pi: ExtensionAPI): void {
|
|
|
150
232
|
result = { config: updated.config, stateRoot: updated.stateRoot, notices: updated.notices };
|
|
151
233
|
break;
|
|
152
234
|
}
|
|
235
|
+
case "set-refs-root": {
|
|
236
|
+
if (!params.refsRoot || !params.refsRootSource) {
|
|
237
|
+
throw new StateError("set-refs-root requires refsRoot and refsRootSource");
|
|
238
|
+
}
|
|
239
|
+
const updated = setRefsRoot(workdir, params.refsRoot, params.refsRootSource);
|
|
240
|
+
result = { config: updated.config, stateRoot: updated.stateRoot, notices: updated.notices };
|
|
241
|
+
break;
|
|
242
|
+
}
|
|
153
243
|
case "set-role": {
|
|
154
244
|
if (!params.role) throw new StateError("set-role requires role");
|
|
155
245
|
const updated = setRole(workdir, {
|
|
@@ -181,6 +271,11 @@ export function registerPlansTool(pi: ExtensionAPI): void {
|
|
|
181
271
|
result = setRunStatus(workdir, params.runId, params.status);
|
|
182
272
|
break;
|
|
183
273
|
}
|
|
274
|
+
case "final-commit": {
|
|
275
|
+
if (!params.message) throw new StateError("final-commit requires message");
|
|
276
|
+
result = await finalCommit(workdir, params.message);
|
|
277
|
+
break;
|
|
278
|
+
}
|
|
184
279
|
case "record-decision": {
|
|
185
280
|
if (!params.runId || !params.decision) {
|
|
186
281
|
throw new StateError("record-decision requires runId and decision");
|
package/tools/refine.ts
CHANGED
|
@@ -15,13 +15,21 @@ import { Type } from "typebox";
|
|
|
15
15
|
import * as fs from "node:fs";
|
|
16
16
|
import * as path from "node:path";
|
|
17
17
|
import { loadConfig, normalizeWorkdir, readActive, recordSubagent, resolveStateRootOrNull, StateError, type RoleConfig } from "../src/state.ts";
|
|
18
|
-
import { buildCriticizerTask, buildReviewerTask, reviewerLanes } from "../src/refine-prompts.ts";
|
|
18
|
+
import { buildCriticizerTask, buildImplementationCriticizerTask, buildImplementationReviewerTask, buildReviewerTask, reviewerLanes } from "../src/refine-prompts.ts";
|
|
19
|
+
import { graphBlockForRefiner } from "../src/code-graph/prompts.ts";
|
|
19
20
|
import { runPiSubagent, stripFrontmatter } from "../src/subagent.ts";
|
|
20
21
|
import { RefineOverlayController, refineOverlayContext } from "../src/refine-ui.ts";
|
|
21
22
|
|
|
23
|
+
|
|
22
24
|
const RefineParams = Type.Object({
|
|
23
25
|
role: StringEnum(["reviewer", "criticizer"] as const, { description: "Refinement role to run" }),
|
|
24
26
|
planPath: Type.String({ description: "Path to the PLAN_vN.md to review (absolute or relative to workdir)" }),
|
|
27
|
+
target: Type.Optional(
|
|
28
|
+
StringEnum(["plan", "implementation"] as const, {
|
|
29
|
+
description:
|
|
30
|
+
'Review target: "plan" (default) reviews the plan text; "implementation" reviews the implemented worktree against the plan\'s goals and acceptance criteria (post-execution amelioration).',
|
|
31
|
+
}),
|
|
32
|
+
),
|
|
25
33
|
focus: Type.Optional(Type.String({ description: "Specific concerns to direct the pass at" })),
|
|
26
34
|
reviewers: Type.Optional(
|
|
27
35
|
Type.Integer({
|
|
@@ -52,6 +60,7 @@ function setupRefinementExecution(
|
|
|
52
60
|
parentSignal: AbortSignal | undefined,
|
|
53
61
|
role: "reviewer" | "criticizer",
|
|
54
62
|
lanes: Array<{ id: string; label?: string }>,
|
|
63
|
+
modelLabel?: string,
|
|
55
64
|
) {
|
|
56
65
|
const controller = new AbortController();
|
|
57
66
|
const relayAbort = () => controller.abort();
|
|
@@ -59,7 +68,7 @@ function setupRefinementExecution(
|
|
|
59
68
|
else parentSignal?.addEventListener("abort", relayAbort, { once: true });
|
|
60
69
|
|
|
61
70
|
const overlay = ctx.mode === "tui" ? new RefineOverlayController(role, lanes, relayAbort) : undefined;
|
|
62
|
-
overlay?.open(refineOverlayContext(ctx));
|
|
71
|
+
overlay?.open(refineOverlayContext(ctx), modelLabel);
|
|
63
72
|
|
|
64
73
|
return {
|
|
65
74
|
signal: controller.signal,
|
|
@@ -83,7 +92,7 @@ export function registerRefineTool(pi: ExtensionAPI, baseDir: string): void {
|
|
|
83
92
|
name: "refine",
|
|
84
93
|
label: "Refine",
|
|
85
94
|
description:
|
|
86
|
-
"Run a reviewer or criticizer refinement round on a PLAN_vN.md via read-only Pi subagents. Reviewer: findings with IDs, severity, evidence, impact, fix, disposition. Criticizer: up to five adaptive questions. Use reviewers: 3 for the big-plan concurrent reviewer round. Refuses to spawn until the role's mode and model are confirmed in .git/pi_plans/config.json (ask via ask_choice, persist via the plans tool).",
|
|
95
|
+
"Run a reviewer or criticizer refinement round on a PLAN_vN.md (target=\"plan\", default) or on the implemented worktree (target=\"implementation\", post-execution amelioration) via read-only Pi subagents. Reviewer: findings with IDs, severity, evidence, impact, fix, disposition. Criticizer: up to five adaptive questions. Use reviewers: 3 for the big-plan concurrent reviewer round. Refuses to spawn until the role's mode and model are confirmed in .git/pi_plans/config.json (ask via ask_choice, persist via the plans tool).",
|
|
87
96
|
promptSnippet: "Run reviewer/criticizer plan-refinement rounds",
|
|
88
97
|
parameters: RefineParams,
|
|
89
98
|
|
|
@@ -120,15 +129,28 @@ export function registerRefineTool(pi: ExtensionAPI, baseDir: string): void {
|
|
|
120
129
|
}
|
|
121
130
|
};
|
|
122
131
|
|
|
132
|
+
const target = params.target ?? "plan";
|
|
133
|
+
const pickTask = (role: "reviewer" | "criticizer", lens: string | null): string => {
|
|
134
|
+
if (role === "reviewer") {
|
|
135
|
+
return target === "implementation"
|
|
136
|
+
? buildImplementationReviewerTask({ planText, planPath, lens, focus: params.focus, context: params.context })
|
|
137
|
+
: buildReviewerTask({ planText, planPath, lens, focus: params.focus, context: params.context });
|
|
138
|
+
}
|
|
139
|
+
return target === "implementation"
|
|
140
|
+
? buildImplementationCriticizerTask({ planText, planPath, focus: params.focus, context: params.context })
|
|
141
|
+
: buildCriticizerTask({ planText, planPath, focus: params.focus, context: params.context });
|
|
142
|
+
};
|
|
143
|
+
|
|
123
144
|
const systemPrompt = loadAgentPrompt(params.role);
|
|
145
|
+
const graphEnabled = config.graph_enabled === true;
|
|
146
|
+
const subagentTools = graphEnabled ? ["read", "grep", "find", "ls", "code_graph"] : undefined;
|
|
147
|
+
const graphPrompt = graphBlockForRefiner(graphEnabled);
|
|
124
148
|
const inheritModel = ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : undefined;
|
|
125
149
|
const model = roleConfig.model_selector ?? inheritModel;
|
|
150
|
+
const modelLabel = model ?? "inherit";
|
|
126
151
|
|
|
127
152
|
if (roleConfig.mode === "current-session") {
|
|
128
|
-
const task =
|
|
129
|
-
params.role === "reviewer"
|
|
130
|
-
? buildReviewerTask({ planText, planPath, lens: null, focus: params.focus, context: params.context })
|
|
131
|
-
: buildCriticizerTask({ planText, planPath, focus: params.focus, context: params.context });
|
|
153
|
+
const task = pickTask(params.role, null);
|
|
132
154
|
return {
|
|
133
155
|
content: [
|
|
134
156
|
{
|
|
@@ -136,19 +158,20 @@ export function registerRefineTool(pi: ExtensionAPI, baseDir: string): void {
|
|
|
136
158
|
text: `Role mode is current-session: perform the read-only ${params.role} pass yourself, in this session, following this brief. Do not spawn anything.\n\n${task}`,
|
|
137
159
|
},
|
|
138
160
|
],
|
|
139
|
-
details: { mode: "current-session", role: params.role, planPath },
|
|
161
|
+
details: { mode: "current-session", role: params.role, planPath, target },
|
|
140
162
|
};
|
|
141
163
|
}
|
|
142
164
|
|
|
143
165
|
if (params.role === "criticizer") {
|
|
144
166
|
const name = `${roleConfig.name_prefix}-criticizer-${Date.now().toString(36)}`;
|
|
145
|
-
const execution = setupRefinementExecution(ctx, signal, "criticizer", [{ id: name, label: "criticizer" }]);
|
|
167
|
+
const execution = setupRefinementExecution(ctx, signal, "criticizer", [{ id: name, label: "criticizer" }], modelLabel);
|
|
146
168
|
try {
|
|
147
169
|
const result = await runPiSubagent({
|
|
148
|
-
systemPrompt
|
|
149
|
-
task:
|
|
170
|
+
systemPrompt: `${systemPrompt}\n\n${graphPrompt}`,
|
|
171
|
+
task: pickTask("criticizer", null),
|
|
150
172
|
cwd: workdir,
|
|
151
173
|
model,
|
|
174
|
+
tools: subagentTools,
|
|
152
175
|
signal: execution.signal,
|
|
153
176
|
onProgress: (event) => execution.overlay?.update(name, event),
|
|
154
177
|
});
|
|
@@ -166,42 +189,64 @@ export function registerRefineTool(pi: ExtensionAPI, baseDir: string): void {
|
|
|
166
189
|
text: `${result.output}\n\n---\nAsk each criticizer question with ask_choice (one call per question, in the configured language), record every answer, then revise the plan only after every question has an answer.`,
|
|
167
190
|
},
|
|
168
191
|
],
|
|
169
|
-
details: { mode: "delegated-subagent", role: params.role, planPath, model: result.model ?? model },
|
|
192
|
+
details: { mode: "delegated-subagent", role: params.role, planPath, target, model: result.model ?? model },
|
|
170
193
|
};
|
|
171
194
|
} finally {
|
|
172
195
|
await execution.close();
|
|
173
196
|
}
|
|
174
197
|
}
|
|
175
198
|
|
|
176
|
-
// Reviewer round: 1 by default, 3 for the big-plan concurrent round.
|
|
177
199
|
const count = Math.min(3, Math.max(1, params.reviewers ?? 1));
|
|
178
200
|
const lanes = reviewerLanes(count);
|
|
179
201
|
const jobs = lanes.map((lane) => {
|
|
180
202
|
const name = `${roleConfig.name_prefix}-${active?.run_id ?? "adhoc"}-${lane.id}`;
|
|
181
|
-
const task =
|
|
203
|
+
const task = pickTask("reviewer", lane.lens);
|
|
182
204
|
return { lane, name, task };
|
|
183
205
|
});
|
|
184
206
|
|
|
207
|
+
// Per-plan amelioration round counter (post-execution loop auditability).
|
|
208
|
+
const roundsSlot = ctx.sessionManager as unknown as { __ameliorateRounds?: Map<string, number> };
|
|
209
|
+
const nextRound = (planPath: string): number => {
|
|
210
|
+
roundsSlot.__ameliorateRounds ??= new Map();
|
|
211
|
+
const round = (roundsSlot.__ameliorateRounds.get(planPath) ?? 0) + 1;
|
|
212
|
+
roundsSlot.__ameliorateRounds.set(planPath, round);
|
|
213
|
+
return round;
|
|
214
|
+
};
|
|
215
|
+
|
|
185
216
|
const execution = setupRefinementExecution(
|
|
186
217
|
ctx,
|
|
187
218
|
signal,
|
|
188
219
|
"reviewer",
|
|
189
220
|
jobs.map((job) => ({ id: job.lane.id, label: job.lane.id })),
|
|
221
|
+
modelLabel,
|
|
190
222
|
);
|
|
191
223
|
try {
|
|
192
224
|
const results = await Promise.all(
|
|
193
225
|
jobs.map(async (job) => {
|
|
194
226
|
try {
|
|
195
227
|
const result = await runPiSubagent({
|
|
196
|
-
systemPrompt
|
|
228
|
+
systemPrompt: `${systemPrompt}\n\n${graphPrompt}`,
|
|
197
229
|
task: job.task,
|
|
198
230
|
cwd: workdir,
|
|
199
231
|
model,
|
|
232
|
+
tools: subagentTools,
|
|
200
233
|
signal: execution.signal,
|
|
201
234
|
onProgress: (event) => execution.overlay?.update(job.lane.id, event),
|
|
202
235
|
});
|
|
203
236
|
execution.overlay?.complete(job.lane.id, result);
|
|
204
237
|
record(job.name, result.ok ? result.model ?? model : null);
|
|
238
|
+
if (target === "implementation" && result.ok) {
|
|
239
|
+
try {
|
|
240
|
+
pi.appendEntry("pi-plans-ameliorate", {
|
|
241
|
+
planPath,
|
|
242
|
+
phase: "round",
|
|
243
|
+
currentRound: nextRound(planPath),
|
|
244
|
+
lane: job.lane.id,
|
|
245
|
+
});
|
|
246
|
+
} catch {
|
|
247
|
+
/* appendEntry is best-effort; audit trail survives in subagents.jsonl */
|
|
248
|
+
}
|
|
249
|
+
}
|
|
205
250
|
return { job, result };
|
|
206
251
|
} catch (error) {
|
|
207
252
|
record(job.name, null);
|
|
@@ -272,6 +317,7 @@ export function registerRefineTool(pi: ExtensionAPI, baseDir: string): void {
|
|
|
272
317
|
theme.fg("muted", count > 1 ? ` ×${count}` : "");
|
|
273
318
|
const short = args.planPath ? args.planPath.split("/").pop() : "";
|
|
274
319
|
if (short) text += theme.fg("dim", ` ${short}`);
|
|
320
|
+
if (args.target === "implementation") text += theme.fg("dim", " (implementation)");
|
|
275
321
|
if (args.focus) text += `\n${theme.fg("dim", ` focus: ${args.focus.slice(0, 80)}`)}`;
|
|
276
322
|
return new Text(text, 0, 0);
|
|
277
323
|
},
|