pi-plans 0.2.0 → 0.3.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/README.md +74 -21
- package/index.ts +115 -9
- package/package.json +7 -1
- package/references/pi-planning-workflow.md +18 -3
- package/references/state-and-config.md +34 -2
- package/scripts/validate.ts +4 -0
- package/src/code-graph/commands.ts +437 -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 +326 -0
- package/src/exec.ts +356 -686
- package/src/refine-prompts.ts +50 -0
- package/src/refine-ui-helpers.ts +71 -18
- package/src/refine-ui-state.ts +87 -21
- package/src/refine-ui.ts +210 -102
- package/src/state.ts +19 -6
- package/src/subagent.ts +163 -61
- package/tests/ask-choice.test.ts +263 -0
- package/tests/autocomplete.test.ts +6 -1
- 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 +255 -0
- package/tests/exec.test.ts +665 -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/refine-prompts.test.ts +67 -2
- package/tests/refine-ui.test.ts +337 -72
- package/tests/subagent.test.ts +26 -20
- package/tools/ask-choice.ts +158 -11
- package/tools/code-graph.ts +254 -0
- package/tools/graph-aware-file-tools.ts +392 -0
- package/tools/plans.ts +84 -1
- package/tools/refine.ts +61 -15
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
|
},
|