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.
Files changed (65) hide show
  1. package/README.md +74 -21
  2. package/index.ts +115 -9
  3. package/package.json +7 -1
  4. package/references/pi-planning-workflow.md +18 -3
  5. package/references/state-and-config.md +34 -2
  6. package/scripts/validate.ts +4 -0
  7. package/src/code-graph/commands.ts +437 -0
  8. package/src/code-graph/discovery.ts +118 -0
  9. package/src/code-graph/git.ts +108 -0
  10. package/src/code-graph/identity.ts +59 -0
  11. package/src/code-graph/indexer.ts +281 -0
  12. package/src/code-graph/materialize.ts +166 -0
  13. package/src/code-graph/mode.ts +28 -0
  14. package/src/code-graph/mutations.ts +160 -0
  15. package/src/code-graph/parser.ts +51 -0
  16. package/src/code-graph/parsers/javascript.ts +35 -0
  17. package/src/code-graph/parsers/python.ts +160 -0
  18. package/src/code-graph/parsers/tree-sitter.ts +316 -0
  19. package/src/code-graph/paths.ts +85 -0
  20. package/src/code-graph/prompts.ts +18 -0
  21. package/src/code-graph/resolver.ts +69 -0
  22. package/src/code-graph/runtime.ts +158 -0
  23. package/src/code-graph/schema.ts +135 -0
  24. package/src/code-graph/screening.ts +82 -0
  25. package/src/code-graph/store.ts +278 -0
  26. package/src/code-graph/summary.ts +435 -0
  27. package/src/code-graph/types.ts +163 -0
  28. package/src/compaction.ts +1125 -371
  29. package/src/config-command.ts +326 -0
  30. package/src/exec.ts +356 -686
  31. package/src/refine-prompts.ts +50 -0
  32. package/src/refine-ui-helpers.ts +71 -18
  33. package/src/refine-ui-state.ts +87 -21
  34. package/src/refine-ui.ts +210 -102
  35. package/src/state.ts +19 -6
  36. package/src/subagent.ts +163 -61
  37. package/tests/ask-choice.test.ts +263 -0
  38. package/tests/autocomplete.test.ts +6 -1
  39. package/tests/code-graph-apply.test.ts +185 -0
  40. package/tests/code-graph-commands.test.ts +211 -0
  41. package/tests/code-graph-db.test.ts +166 -0
  42. package/tests/code-graph-discovery.test.ts +38 -0
  43. package/tests/code-graph-git.test.ts +94 -0
  44. package/tests/code-graph-index.test.ts +175 -0
  45. package/tests/code-graph-loop.e2e.test.ts +159 -0
  46. package/tests/code-graph-mutations.test.ts +117 -0
  47. package/tests/code-graph-parser.test.ts +85 -0
  48. package/tests/code-graph-rollback.test.ts +100 -0
  49. package/tests/code-graph-summary-batching.test.ts +518 -0
  50. package/tests/code-graph-summary.test.ts +148 -0
  51. package/tests/compaction.test.ts +371 -57
  52. package/tests/config-command.test.ts +255 -0
  53. package/tests/exec.test.ts +665 -241
  54. package/tests/fixtures/code-graph/sample.js +36 -0
  55. package/tests/fixtures/code-graph/sample.py +20 -0
  56. package/tests/fixtures/code-graph/sample.ts +15 -0
  57. package/tests/graph-aware-file-tools.test.ts +411 -0
  58. package/tests/refine-prompts.test.ts +67 -2
  59. package/tests/refine-ui.test.ts +337 -72
  60. package/tests/subagent.test.ts +26 -20
  61. package/tools/ask-choice.ts +158 -11
  62. package/tools/code-graph.ts +254 -0
  63. package/tools/graph-aware-file-tools.ts +392 -0
  64. package/tools/plans.ts +84 -1
  65. 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: buildCriticizerTask({ planText, planPath, focus: params.focus, context: params.context }),
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 = buildReviewerTask({ planText, planPath, lens: lane.lens, focus: params.focus, context: params.context });
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
  },