pi-plans 0.1.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/LICENSE +21 -0
- package/README.md +217 -0
- package/agents/criticizer.md +18 -0
- package/agents/reviewer.md +20 -0
- package/docs/assets/pi-plans-logo.svg +66 -0
- package/index.ts +375 -0
- package/package.json +58 -0
- package/references/pi-planning-workflow.md +154 -0
- package/references/plan-artifact-template.md +77 -0
- package/references/state-and-config.md +137 -0
- package/scripts/run-tests.ts +31 -0
- package/scripts/validate.ts +185 -0
- package/skills/debug-and-plan/SKILL.md +35 -0
- package/skills/plan-big/SKILL.md +24 -0
- package/skills/plan-normal/SKILL.md +24 -0
- package/skills/plan-small/SKILL.md +23 -0
- package/skills/plan-with-refs/SKILL.md +30 -0
- package/skills/planning/SKILL.md +22 -0
- package/src/exec.ts +317 -0
- package/src/execution-panel.ts +497 -0
- package/src/guard.ts +38 -0
- package/src/plan.ts +63 -0
- package/src/refine-prompts.ts +70 -0
- package/src/state.ts +490 -0
- package/src/subagent.ts +197 -0
- package/tests/exec.test.ts +249 -0
- package/tests/execution-panel.test.ts +198 -0
- package/tests/guard.test.ts +70 -0
- package/tests/plan.test.ts +105 -0
- package/tests/refine-prompts.test.ts +49 -0
- package/tests/state.test.ts +240 -0
- package/tools/ask-choice.ts +199 -0
- package/tools/execute-plan.ts +137 -0
- package/tools/plans.ts +195 -0
- package/tools/refine.ts +237 -0
package/index.ts
ADDED
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pi-plans — human-in-the-loop planning for the Pi coding agent.
|
|
3
|
+
*
|
|
4
|
+
* Researched, refined Markdown plans before any code changes. The five
|
|
5
|
+
* planning skills are contributed via resources_discover; the extension
|
|
6
|
+
* provides the supporting machinery:
|
|
7
|
+
*
|
|
8
|
+
* - `plans` tool — workspace state (config, runs, ledgers) in .git/pi_plans/
|
|
9
|
+
* - `ask_choice` tool — the choice-prompt contract (Other / Auto-complete rules)
|
|
10
|
+
* - `refine` tool — reviewer/criticizer rounds via read-only pi subagents
|
|
11
|
+
* - `execute_plan` — execution handoff into the tracked execution loop
|
|
12
|
+
* - write guard — planning runs may only write planning artifacts
|
|
13
|
+
* - execution loop — checklist injection, [DONE:VC-xxx] tracking, progress widget
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
17
|
+
import * as fs from "node:fs";
|
|
18
|
+
import * as path from "node:path";
|
|
19
|
+
import { dirname, join } from "node:path";
|
|
20
|
+
import { fileURLToPath } from "node:url";
|
|
21
|
+
import {
|
|
22
|
+
applyDoneMarkers,
|
|
23
|
+
completeExecution,
|
|
24
|
+
consumePendingPanelSync,
|
|
25
|
+
executionContextMessage,
|
|
26
|
+
getExecution,
|
|
27
|
+
isExecutionComplete,
|
|
28
|
+
recordExecutionCompletion,
|
|
29
|
+
recordTouchedPaths,
|
|
30
|
+
restoreFromSession,
|
|
31
|
+
stopExecution,
|
|
32
|
+
syncExecutionPanel,
|
|
33
|
+
toggleExecutionPanelView,
|
|
34
|
+
updateStatusWidget,
|
|
35
|
+
} from "./src/exec.ts";
|
|
36
|
+
import { planningWriteBlockReason } from "./src/guard.ts";
|
|
37
|
+
import { latestPlanVersion, nextPlanVersionPath } from "./src/plan.ts";
|
|
38
|
+
import { getRun, readActive, recordDecision, resolveStateRootOrNull, setRunStatus } from "./src/state.ts";
|
|
39
|
+
import { registerAskChoiceTool } from "./tools/ask-choice.ts";
|
|
40
|
+
import { executeHandoff, registerExecutePlanTool } from "./tools/execute-plan.ts";
|
|
41
|
+
import { registerPlansTool } from "./tools/plans.ts";
|
|
42
|
+
import { registerRefineTool } from "./tools/refine.ts";
|
|
43
|
+
|
|
44
|
+
const baseDir = dirname(fileURLToPath(import.meta.url));
|
|
45
|
+
|
|
46
|
+
function extractPathsFromBash(command: string): string[] {
|
|
47
|
+
const values = new Set<string>();
|
|
48
|
+
for (const token of command.split(/\s+/)) {
|
|
49
|
+
const cleaned = token.replace(/^["'`(<[{]+|["'`)>}\],;]+$/g, "");
|
|
50
|
+
if (!cleaned || cleaned === "." || cleaned === ".." || cleaned.startsWith("-") || cleaned.includes("=") ) continue;
|
|
51
|
+
const looksLikePath =
|
|
52
|
+
cleaned.includes("/") ||
|
|
53
|
+
cleaned.startsWith(".") ||
|
|
54
|
+
cleaned.startsWith("~") ||
|
|
55
|
+
/^[A-Za-z0-9._-]+\.[A-Za-z0-9._-]+$/.test(cleaned);
|
|
56
|
+
if (!looksLikePath) continue;
|
|
57
|
+
values.add(cleaned);
|
|
58
|
+
}
|
|
59
|
+
return [...values];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export default function piPlansExtension(pi: ExtensionAPI): void {
|
|
63
|
+
registerPlansTool(pi);
|
|
64
|
+
registerAskChoiceTool(pi);
|
|
65
|
+
registerRefineTool(pi, baseDir);
|
|
66
|
+
registerExecutePlanTool(pi);
|
|
67
|
+
|
|
68
|
+
// Contribute the router skill plus the five specialist planning skills.
|
|
69
|
+
pi.on("resources_discover", () => ({
|
|
70
|
+
skillPaths: [
|
|
71
|
+
join(baseDir, "skills", "planning"),
|
|
72
|
+
join(baseDir, "skills", "debug-and-plan"),
|
|
73
|
+
join(baseDir, "skills", "plan-small"),
|
|
74
|
+
join(baseDir, "skills", "plan-normal"),
|
|
75
|
+
join(baseDir, "skills", "plan-big"),
|
|
76
|
+
join(baseDir, "skills", "plan-with-refs"),
|
|
77
|
+
],
|
|
78
|
+
}));
|
|
79
|
+
|
|
80
|
+
// Direct slash aliases: /plan-small etc. forward to the skill commands
|
|
81
|
+
// (/skill:plan-small) so users can invoke skills without the prefix.
|
|
82
|
+
for (const name of [
|
|
83
|
+
"planning",
|
|
84
|
+
"debug-and-plan",
|
|
85
|
+
"plan-small",
|
|
86
|
+
"plan-normal",
|
|
87
|
+
"plan-big",
|
|
88
|
+
"plan-with-refs",
|
|
89
|
+
]) {
|
|
90
|
+
pi.registerCommand(name, {
|
|
91
|
+
description: `Run the ${name} planning skill (alias of /skill:${name})`,
|
|
92
|
+
handler: async (args, ctx) => {
|
|
93
|
+
const invocation = args.trim() ? `/skill:${name} ${args.trim()}` : `/skill:${name}`;
|
|
94
|
+
try {
|
|
95
|
+
pi.sendUserMessage(invocation, { expandPromptTemplates: true });
|
|
96
|
+
} catch {
|
|
97
|
+
ctx.ui.notify("Agent is busy; try again once the current turn finishes.", "error");
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// -----------------------------------------------------------------------
|
|
104
|
+
// Planning write guard: while a planning run is active (and execution has
|
|
105
|
+
// not been approved), edit/write may only target planning artifacts.
|
|
106
|
+
// -----------------------------------------------------------------------
|
|
107
|
+
pi.on("tool_call", async (event, ctx) => {
|
|
108
|
+
const execution = getExecution();
|
|
109
|
+
if (execution) {
|
|
110
|
+
if (event.toolName === "edit" || event.toolName === "write") {
|
|
111
|
+
const rawPath = String((event.input as { path?: string }).path ?? "");
|
|
112
|
+
if (rawPath) recordTouchedPaths(ctx.cwd, [rawPath]);
|
|
113
|
+
}
|
|
114
|
+
if (event.toolName === "bash") {
|
|
115
|
+
const command = String((event.input as { command?: string }).command ?? "");
|
|
116
|
+
if (command) recordTouchedPaths(ctx.cwd, extractPathsFromBash(command));
|
|
117
|
+
}
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
const rawPath = String((event.input as { path?: string }).path ?? "");
|
|
121
|
+
if (!rawPath) return;
|
|
122
|
+
const reason = planningWriteBlockReason({ workdir: ctx.cwd, toolName: event.toolName, rawPath });
|
|
123
|
+
if (!reason) return;
|
|
124
|
+
return { block: true, reason };
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// -----------------------------------------------------------------------
|
|
128
|
+
// Execution loop: inject remaining checklist each turn, track markers.
|
|
129
|
+
// -----------------------------------------------------------------------
|
|
130
|
+
pi.on("before_agent_start", async () => {
|
|
131
|
+
const content = executionContextMessage();
|
|
132
|
+
if (!content) return;
|
|
133
|
+
return {
|
|
134
|
+
message: {
|
|
135
|
+
customType: "pi-plans-exec-context",
|
|
136
|
+
content,
|
|
137
|
+
display: false,
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
pi.on("turn_end", async (event, ctx) => {
|
|
143
|
+
const message = event.message as { role?: string; content?: Array<{ type: string; text?: string }> };
|
|
144
|
+
if (!message || message.role !== "assistant") {
|
|
145
|
+
updateStatusWidget(ctx);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
const text = (message.content ?? [])
|
|
149
|
+
.filter((part) => part.type === "text")
|
|
150
|
+
.map((part) => part.text ?? "")
|
|
151
|
+
.join("\n");
|
|
152
|
+
const changedIds = applyDoneMarkers(text);
|
|
153
|
+
if (changedIds.length > 0) {
|
|
154
|
+
recordExecutionCompletion(pi, ctx, changedIds);
|
|
155
|
+
}
|
|
156
|
+
if (getExecution() && isExecutionComplete()) {
|
|
157
|
+
completeExecution(pi, ctx);
|
|
158
|
+
}
|
|
159
|
+
// A busy-toggle during the previous turn deferred its re-render; the
|
|
160
|
+
// turn just ended, so this is the safe point to apply it.
|
|
161
|
+
if (consumePendingPanelSync()) syncExecutionPanel(ctx);
|
|
162
|
+
updateStatusWidget(ctx);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// -----------------------------------------------------------------------
|
|
166
|
+
// Commands and shortcuts
|
|
167
|
+
// -----------------------------------------------------------------------
|
|
168
|
+
pi.registerShortcut("alt+o", {
|
|
169
|
+
description: "Toggle pi-plans execution checklist widget",
|
|
170
|
+
handler: async (ctx) => {
|
|
171
|
+
const expanded = toggleExecutionPanelView(pi, ctx);
|
|
172
|
+
if (expanded === null) {
|
|
173
|
+
ctx.ui.notify("No execution in progress.", "info");
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
ctx.ui.notify(expanded ? "Execution checklist expanded." : "Execution checklist collapsed.", "info");
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
pi.registerCommand("plans", {
|
|
181
|
+
description: "Show pi-plans state: config, active run, and execution progress",
|
|
182
|
+
handler: async (_args, ctx) => {
|
|
183
|
+
const lines: string[] = [];
|
|
184
|
+
const active = readActive(ctx.cwd);
|
|
185
|
+
const run = active ? getRun(ctx.cwd, active.run_id) : null;
|
|
186
|
+
if (!run) {
|
|
187
|
+
lines.push("No active planning run.");
|
|
188
|
+
} else {
|
|
189
|
+
lines.push(`Active run: ${run.run_id}`);
|
|
190
|
+
lines.push(`Skill: ${run.skill} Status: ${run.status}`);
|
|
191
|
+
lines.push(`Artifacts: ${run.artifact_dir}`);
|
|
192
|
+
lines.push(`Language: ${run.language_tag ?? "(unset)"}`);
|
|
193
|
+
lines.push(`State: ${resolveStateRootOrNull(ctx.cwd) ?? "(no repo)"}`);
|
|
194
|
+
}
|
|
195
|
+
const execution = getExecution();
|
|
196
|
+
if (execution) {
|
|
197
|
+
const done = execution.items.filter((item) => item.done).length;
|
|
198
|
+
lines.push(`Execution: ${execution.planPath} — ${done}/${execution.items.length} verifier items done`);
|
|
199
|
+
for (const item of execution.items) {
|
|
200
|
+
lines.push(` ${item.done ? "☑" : "☐"} ${item.id}`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
ctx.ui.notify(lines.join("\n"), "info");
|
|
204
|
+
},
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
pi.registerCommand("plans-list", {
|
|
208
|
+
description: "Toggle the pi-plans execution checklist widget",
|
|
209
|
+
handler: async (_args, ctx) => {
|
|
210
|
+
const expanded = toggleExecutionPanelView(pi, ctx);
|
|
211
|
+
if (expanded === null) {
|
|
212
|
+
ctx.ui.notify("No execution in progress.", "info");
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
ctx.ui.notify(expanded ? "Execution checklist expanded." : "Execution checklist collapsed.", "info");
|
|
216
|
+
},
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
pi.registerCommand("plans-execute", {
|
|
220
|
+
description: "Execute handoff: enter tracked execution mode for an accepted plan",
|
|
221
|
+
handler: async (args, ctx) => {
|
|
222
|
+
const planPath = args.trim() || undefined;
|
|
223
|
+
const outcome = await executeHandoff(ctx, planPath);
|
|
224
|
+
ctx.ui.notify(outcome.message, outcome.status === "error" ? "error" : "info");
|
|
225
|
+
},
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
pi.registerCommand("update-plan", {
|
|
229
|
+
description: "Interrupt-and-refine: stop execution (if any) and revise the current plan into its next version",
|
|
230
|
+
handler: async (args, ctx) => {
|
|
231
|
+
if (!ctx.hasUI) {
|
|
232
|
+
ctx.ui.notify("/update-plan requires an interactive session.", "error");
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Parse args: optional plan.md path first, remaining text is the refocus reason.
|
|
237
|
+
let planArg: string | undefined;
|
|
238
|
+
let focus = "";
|
|
239
|
+
const trimmed = args.trim();
|
|
240
|
+
if (trimmed) {
|
|
241
|
+
const [first, ...rest] = trimmed.split(/\s+/);
|
|
242
|
+
if (first && (/\.(md|markdown)$/i.test(first) || first.includes("/") || first.startsWith("@"))) {
|
|
243
|
+
planArg = first;
|
|
244
|
+
focus = rest.join(" ");
|
|
245
|
+
} else {
|
|
246
|
+
focus = trimmed;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const active = readActive(ctx.cwd);
|
|
251
|
+
const execution = getExecution();
|
|
252
|
+
|
|
253
|
+
// Resolve the plan to revise: explicit arg > running execution > latest in artifact dir.
|
|
254
|
+
let sourcePlanPath: string | null = planArg
|
|
255
|
+
? path.resolve(ctx.cwd, planArg.replace(/^@/, ""))
|
|
256
|
+
: null;
|
|
257
|
+
if (!sourcePlanPath && execution) sourcePlanPath = execution.planPath;
|
|
258
|
+
if (!sourcePlanPath && active) sourcePlanPath = latestPlanVersion(active.artifact_dir)?.path ?? null;
|
|
259
|
+
if (!sourcePlanPath || !fs.existsSync(sourcePlanPath)) {
|
|
260
|
+
ctx.ui.notify(
|
|
261
|
+
sourcePlanPath ? `Plan file not found: ${sourcePlanPath}` : "No plan to update. Pass plan.md or start a run first.",
|
|
262
|
+
"error",
|
|
263
|
+
);
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const artifactDir = active?.artifact_dir ?? path.dirname(sourcePlanPath);
|
|
268
|
+
const next = nextPlanVersionPath(artifactDir);
|
|
269
|
+
|
|
270
|
+
// Snapshot progress BEFORE stopping so the revision preserves finished work.
|
|
271
|
+
const doneIds = execution ? execution.items.filter((item) => item.done).map((item) => item.id) : [];
|
|
272
|
+
const pendingIds = execution ? execution.items.filter((item) => !item.done).map((item) => item.id) : [];
|
|
273
|
+
|
|
274
|
+
if (execution) {
|
|
275
|
+
const ok = await ctx.ui.confirm(
|
|
276
|
+
"Stop execution to update the plan?",
|
|
277
|
+
`${doneIds.length}/${execution.items.length} verifier item(s) already verified; their work stays. Remaining items return to planning.`,
|
|
278
|
+
);
|
|
279
|
+
if (!ok) return;
|
|
280
|
+
stopExecution(pi, ctx, "interrupted by /update-plan");
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Return the run to planning so refinement rules and guards apply again.
|
|
284
|
+
if (active) {
|
|
285
|
+
const run = getRun(ctx.cwd, active.run_id);
|
|
286
|
+
if (run && run.status !== "planning" && run.status !== "abandoned" && run.status !== "done") {
|
|
287
|
+
try {
|
|
288
|
+
setRunStatus(ctx.cwd, active.run_id, "planning");
|
|
289
|
+
} catch {
|
|
290
|
+
/* status bookkeeping is best-effort */
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
try {
|
|
294
|
+
recordDecision(ctx.cwd, active.run_id, {
|
|
295
|
+
question: "/update-plan requested",
|
|
296
|
+
options: ["stop execution and refine current plan"],
|
|
297
|
+
answer: focus ? `refocus: ${focus}` : "stop execution and refine current plan",
|
|
298
|
+
answer_source: "user",
|
|
299
|
+
artifact: next.path,
|
|
300
|
+
});
|
|
301
|
+
} catch {
|
|
302
|
+
/* best-effort audit trail */
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const run = active ? getRun(ctx.cwd, active.run_id) : null;
|
|
307
|
+
const lines: string[] = [
|
|
308
|
+
"[PI-PLANS UPDATE] Revise the accepted plan for user-directed changes.",
|
|
309
|
+
"",
|
|
310
|
+
`Current plan: ${sourcePlanPath}`,
|
|
311
|
+
`Write the revised version as: ${next.path}`,
|
|
312
|
+
`Update the Status header and Plan version fields; keep every stable ID (G/R/I/C/VC-###); never recycle IDs of completed items; append to the Revision Ledger.`,
|
|
313
|
+
];
|
|
314
|
+
if (run) lines.push(``, `Run: ${run.run_id} (skill: ${run.skill})`, `Original request: ${run.request_text}`);
|
|
315
|
+
if (doneIds.length) {
|
|
316
|
+
lines.push(`Already verified during execution (preserve their scope unless the user says otherwise): ${doneIds.join(", ")}`);
|
|
317
|
+
}
|
|
318
|
+
if (pendingIds.length) lines.push(`Not yet verified: ${pendingIds.join(", ")}`);
|
|
319
|
+
lines.push(
|
|
320
|
+
"Execution was interrupted on purpose — do not continue implementing until the revised plan is approved again.",
|
|
321
|
+
);
|
|
322
|
+
if (focus) lines.push(`User-reported problems / refocus: ${focus}`);
|
|
323
|
+
lines.push(
|
|
324
|
+
"",
|
|
325
|
+
"Follow the original planning-skill contract for revisions: collect needed clarifications via ask_choice (one question at a time, recorded), apply evidence-based revisions only, then ask the next refinement-mode question, and finish with the execution handoff (ask_choice with autoComplete: false → execute_plan pointing at the new version).",
|
|
326
|
+
);
|
|
327
|
+
|
|
328
|
+
await pi.sendUserMessage(lines.join("\n"));
|
|
329
|
+
},
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
pi.registerCommand("plans-stop", {
|
|
333
|
+
description: "Stop plan execution mode (plan artifacts are kept)",
|
|
334
|
+
handler: async (_args, ctx) => {
|
|
335
|
+
if (!getExecution()) {
|
|
336
|
+
ctx.ui.notify("No execution in progress.", "info");
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
const ok = await ctx.ui.confirm("Stop execution?", "Remaining verifier items will be left unfinished.");
|
|
340
|
+
if (!ok) return;
|
|
341
|
+
stopExecution(pi, ctx, "stopped by user via /plans-stop");
|
|
342
|
+
ctx.ui.notify("Execution stopped.", "info");
|
|
343
|
+
},
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
pi.registerCommand("plans-abandon", {
|
|
347
|
+
description: "Abandon the active planning run (lifts the read-only guard; artifacts are kept)",
|
|
348
|
+
handler: async (_args, ctx) => {
|
|
349
|
+
const active = readActive(ctx.cwd);
|
|
350
|
+
if (!active) {
|
|
351
|
+
ctx.ui.notify("No active planning run.", "info");
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
const ok = await ctx.ui.confirm(
|
|
355
|
+
"Abandon planning run?",
|
|
356
|
+
`${active.run_id}\nThe read-only guard lifts; committed artifacts stay in place.`,
|
|
357
|
+
);
|
|
358
|
+
if (!ok) return;
|
|
359
|
+
try {
|
|
360
|
+
setRunStatus(ctx.cwd, active.run_id, "abandoned");
|
|
361
|
+
ctx.ui.notify(`Run ${active.run_id} abandoned.`, "info");
|
|
362
|
+
} catch (error) {
|
|
363
|
+
ctx.ui.notify(`Failed: ${(error as Error).message}`, "error");
|
|
364
|
+
}
|
|
365
|
+
updateStatusWidget(ctx);
|
|
366
|
+
},
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
// -----------------------------------------------------------------------
|
|
370
|
+
// Session lifecycle
|
|
371
|
+
// -----------------------------------------------------------------------
|
|
372
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
373
|
+
restoreFromSession(pi, ctx, ctx.sessionManager.getEntries() as unknown as Parameters<typeof restoreFromSession>[2]);
|
|
374
|
+
});
|
|
375
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pi-plans",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Human-in-the-loop planning extension for the Pi coding agent: researched, refined Markdown plans before any code changes.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"pi-package",
|
|
9
|
+
"pi",
|
|
10
|
+
"planning",
|
|
11
|
+
"extension"
|
|
12
|
+
],
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+https://github.com/MaxInGaussian/pi-plans.git"
|
|
16
|
+
},
|
|
17
|
+
"bugs": {
|
|
18
|
+
"url": "https://github.com/MaxInGaussian/pi-plans/issues"
|
|
19
|
+
},
|
|
20
|
+
"homepage": "https://github.com/MaxInGaussian/pi-plans#readme",
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public"
|
|
23
|
+
},
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=22.6"
|
|
26
|
+
},
|
|
27
|
+
"pi": {
|
|
28
|
+
"extensions": [
|
|
29
|
+
"./index.ts"
|
|
30
|
+
],
|
|
31
|
+
"skills": [
|
|
32
|
+
"./skills",
|
|
33
|
+
"./skills/debug-and-plan",
|
|
34
|
+
"./skills/plan-small",
|
|
35
|
+
"./skills/plan-normal",
|
|
36
|
+
"./skills/plan-big",
|
|
37
|
+
"./skills/plan-with-refs"
|
|
38
|
+
]
|
|
39
|
+
},
|
|
40
|
+
"files": [
|
|
41
|
+
"README.md",
|
|
42
|
+
"LICENSE",
|
|
43
|
+
"index.ts",
|
|
44
|
+
"agents/",
|
|
45
|
+
"docs/assets",
|
|
46
|
+
"references/",
|
|
47
|
+
"scripts/",
|
|
48
|
+
"skills/",
|
|
49
|
+
"src/",
|
|
50
|
+
"tests/",
|
|
51
|
+
"tools/"
|
|
52
|
+
],
|
|
53
|
+
"scripts": {
|
|
54
|
+
"validate": "node --experimental-strip-types scripts/validate.ts",
|
|
55
|
+
"test": "node --experimental-strip-types scripts/run-tests.ts",
|
|
56
|
+
"prepack": "npm run validate && npm test"
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# Pi Planning Workflow
|
|
2
|
+
|
|
3
|
+
This reference is shared by the `pi-plans` skills. It is a planning workflow only. Implementation after acceptance runs in the extension-managed execution loop.
|
|
4
|
+
|
|
5
|
+
## Required Pi Context
|
|
6
|
+
|
|
7
|
+
This skill set is written for the Pi coding agent's documented behavior:
|
|
8
|
+
|
|
9
|
+
- the five skills are contributed by the pi-plans extension and loaded as Pi skills (also invokable as `/skill:<name>`);
|
|
10
|
+
- skill references and helper sources are resolved relative to the directory containing `SKILL.md`;
|
|
11
|
+
- the extension provides these tools: `plans` (workspace state), `ask_choice` (choice prompts), `refine` (reviewer/criticizer subagents), and `execute_plan` (execution handoff);
|
|
12
|
+
- `refine` spawns read-only Pi subagents (`pi --mode json -p --no-session --tools read,grep,find,ls`) with isolated context; their results return to the main session as tool output;
|
|
13
|
+
- the execution loop is extension-managed: remaining verifier items are injected each turn and `[DONE:VC-xxx]` markers are tracked with a footer progress widget.
|
|
14
|
+
|
|
15
|
+
## Planning Boundary
|
|
16
|
+
|
|
17
|
+
- Treat the user's request as a planning target, not as write authorization.
|
|
18
|
+
- Before the execution handoff, do not edit target source files, docs, configs, package metadata, generated assets, or tests outside the planning artifact directory and the pi-plans state under `.git/pi_plans/`. The extension enforces this for `edit` and `write` while a run is active: only `.git/pi_plans/`, the run's artifact directory, and `~/.cache/pi-plans/` are writable. Bash is not machine-guarded — keep it read-only by discipline (inspection, `git init`, downloads into the cache).
|
|
19
|
+
- The normal pre-handoff writes are `.git/pi_plans/` state plus planning artifacts under the configured artifact root (default `./docs/pi-plans/...`).
|
|
20
|
+
- Large downloaded references belong outside the target repo by default, under `~/.cache/pi-plans/refs/<repo-slug>/<topic>/`; record their paths and evidence in `REF_ANALYSIS.md`.
|
|
21
|
+
- After the user explicitly approves the execution handoff, leave this planning workflow and execute in the extension-managed loop (see Execution Handoff).
|
|
22
|
+
|
|
23
|
+
## State And Settings
|
|
24
|
+
|
|
25
|
+
Before the first planning question, read `references/state-and-config.md` and initialize the target workspace state with the `plans` tool:
|
|
26
|
+
|
|
27
|
+
```json
|
|
28
|
+
{ "action": "init", "workdir": "<target-workdir>" }
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
State lives under the workspace's resolved git common dir as `.git/pi_plans/` (auto-ignored, no `.gitignore` entries; the tool auto-runs `git init` when the workdir safely has no repository). The target workspace is the current working directory unless the user explicitly names another repository.
|
|
32
|
+
|
|
33
|
+
If `language.tag` is missing from `.git/pi_plans/config.json`, ask the language setting question (via `ask_choice`) before any product question. Persist it with `plans` (`set-language`); this question does not count against the planning-question limit.
|
|
34
|
+
|
|
35
|
+
If `artifact_root_source` is missing from `.git/pi_plans/config.json` or is `unset`, ask the planning docs location question (via `ask_choice`) before any product question. Persist it with `plans` (`set-artifact-root`); this question does not count against the planning-question limit.
|
|
36
|
+
|
|
37
|
+
Reviewer and criticizer settings also live in `config.json`. If a role's mode is missing/invalid or its `confirmed_at` is `null` when the role is about to run, ask the matching role-setting or model-confirmation question (via `ask_choice`), persist with `plans` (`set-role`), and then run the `refine` tool. The `refine` tool refuses to spawn until the gates pass.
|
|
38
|
+
|
|
39
|
+
## First-Turn Contract
|
|
40
|
+
|
|
41
|
+
1. Inspect the target Git repository read-only before asking product questions. Prefer `rg`/`grep` when available, then focused file reads, `git status`, `git log`, existing tests, and user-provided logs.
|
|
42
|
+
2. If a question can be answered from the repo, answer it from evidence instead of asking the user.
|
|
43
|
+
3. After required language and planning-docs-location setup, the first user-facing planning response must be one `ask_choice` question, not a completed plan or implementation.
|
|
44
|
+
4. Ask one question per message. Do not batch multiple decisions into one prompt.
|
|
45
|
+
|
|
46
|
+
## Evidence Ladder
|
|
47
|
+
|
|
48
|
+
Resolve unknowns in this order:
|
|
49
|
+
|
|
50
|
+
1. Codebase evidence.
|
|
51
|
+
2. Cited web or reference evidence.
|
|
52
|
+
3. User choice.
|
|
53
|
+
|
|
54
|
+
When the recommended option depends on a web-verifiable claim, search first (websearch skill when installed; otherwise `curl`, `gh`, or other bash tools already available) and cite the source in the eventual plan. Do not present a recommendation backed only by an unchecked assumption.
|
|
55
|
+
|
|
56
|
+
## Choice Prompt Format
|
|
57
|
+
|
|
58
|
+
Every user-facing planning or refinement question goes through the `ask_choice` tool:
|
|
59
|
+
|
|
60
|
+
- `options`: ordered options, recommended option first with `recommended: true` (exactly one), each with the tradeoff that matters in `description`;
|
|
61
|
+
- do not add `Other` or `Auto-complete` yourself — the tool appends `Other…` second-last and `Auto-complete` last;
|
|
62
|
+
- pass `autoComplete: false` for the execution handoff and for any install waiver, publishing, deployment, merge, push, credential, or external-state question. Auto-complete may choose the recommended planning or refinement option only.
|
|
63
|
+
|
|
64
|
+
Answers are recorded automatically in the active run's `decisions.jsonl`. You must still maintain `DECISIONS.md` in the artifact directory (summary table of questions, options, answers, answer sources, open assumptions).
|
|
65
|
+
|
|
66
|
+
## Required Artifact Directory
|
|
67
|
+
|
|
68
|
+
Create a run only after initial read-only inspection makes the topic clear:
|
|
69
|
+
|
|
70
|
+
```json
|
|
71
|
+
{ "action": "start-run", "workdir": "<target-workdir>", "topic": "<short topic>", "skill": "<skill-name>", "requestText": "<original request>" }
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
The tool creates the configured artifact directory root (default `./docs/pi-plans/YYYY-MM-DD-<topic>/`) and the private run state `.git/pi_plans/runs/<run-id>/`, and stores the active run pointer. Use a short lowercase slug for the topic. Keep paths stable once written.
|
|
75
|
+
|
|
76
|
+
`DECISIONS.md` records: original request; repository evidence inspected; each question, options, selected answer, and whether it came from the user or Auto-complete; assumptions still open; external sources consulted; language, reviewer, and criticizer settings used.
|
|
77
|
+
|
|
78
|
+
## Final Scope Confirmation
|
|
79
|
+
|
|
80
|
+
Before writing `PLAN_v1.md`, ask the mandatory final scope confirmation via `ask_choice` (it does not count against the skill's planning-question limit):
|
|
81
|
+
|
|
82
|
+
1. `No more requirements` — the scope is ready for `PLAN_v1.md` (recommended).
|
|
83
|
+
2. `Add more requirements` — capture additional constraints before drafting.
|
|
84
|
+
3. `Other`.
|
|
85
|
+
4. `Auto-complete`.
|
|
86
|
+
|
|
87
|
+
If the user adds requirements, resolve only the necessary follow-up questions, then repeat the final scope confirmation.
|
|
88
|
+
|
|
89
|
+
## Plan Artifact Requirements
|
|
90
|
+
|
|
91
|
+
Every `PLAN_vN.md` must include stable IDs that are never recycled across revisions: goals and non-goals; requirements and constraints; implementation items; affected paths; dependencies and sequencing; risks and mitigations; acceptance criteria; verification steps; repo and external evidence; resolved decisions; revision ledger.
|
|
92
|
+
|
|
93
|
+
Every plan version must include a dedicated `## Verifier Checklist` section. Each item is a Markdown checkbox of the exact shape:
|
|
94
|
+
|
|
95
|
+
```markdown
|
|
96
|
+
- [ ] `VC-001` covers `I-001`; pass condition: ...; evidence: ...; metric: <threshold or reason not quantified>.
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
The execution loop parses `- [ ] \`VC-###\`` items and tracks `[DONE:VC-###]` markers, so keep IDs on the checkbox line. Use `references/plan-artifact-template.md` when drafting.
|
|
100
|
+
|
|
101
|
+
## Refinement
|
|
102
|
+
|
|
103
|
+
After each plan version, ask one refinement-mode question via `ask_choice`. The recommended (first) option follows the skill level's default sequence; the user may always pick a different mode or keep refining:
|
|
104
|
+
|
|
105
|
+
- `plan-small`: recommend `Criticizer` for the first (and only default) round; afterwards recommend `Accept plan for tracked execution`.
|
|
106
|
+
- `plan-normal`: recommend `Reviewer` first, then `Criticizer`; afterwards recommend `Accept plan for tracked execution`.
|
|
107
|
+
- `plan-big`: recommend `Reviewer` first as three concurrent independent reviewers (`refine` with `reviewers: 3`), then `Criticizer`; afterwards recommend `Accept plan for tracked execution`.
|
|
108
|
+
|
|
109
|
+
Option list for the question: the recommended next step; the other refinement modes with their tradeoffs; `Accept plan for tracked execution`; `Other`; `Auto-complete`.
|
|
110
|
+
|
|
111
|
+
If the user selects `Reviewer` or `Criticizer`, run the `refine` tool with the plan path and any focus. Reviewer output consolidates into `PLAN_vN_reviewer_comments.md` with findings IDs, severity, affected plan IDs, evidence, impact, recommended fix, and disposition. Revise the next plan only for findings accepted on evidence.
|
|
112
|
+
|
|
113
|
+
### Concurrent Reviewers (big plans)
|
|
114
|
+
|
|
115
|
+
A big-plan reviewer round runs three independent reviewer subagents (`reviewers: 3`); each gets its own emphasis lens but forms its own priorities. After they return, merge and dedupe their findings into one consolidated `PLAN_vN_reviewer_comments.md`, keeping each finding's source reviewer, severity, evidence, and disposition, and surface at most five high-priority comments to the user. Treat agreement between independent reviewers as stronger evidence, not as authority; every accepted finding still needs repo or reference evidence.
|
|
116
|
+
|
|
117
|
+
### Criticizer Rounds
|
|
118
|
+
|
|
119
|
+
Present each criticizer question with `ask_choice` (one call per question, in the configured language). Before each question, summarize the original criticism in at most three sentences and highlight the most important point. Do not revise the plan until every criticizer question has a recorded answer.
|
|
120
|
+
|
|
121
|
+
### Round Lifecycle
|
|
122
|
+
|
|
123
|
+
A refinement round is complete when all reviewer outputs have returned or all criticizer questions have answers. In the same turn: consolidate, accept or reject each finding on evidence (the user may override any disposition), revise to `PLAN_v(N+1).md` when accepted items require it (copy, edit only the new version, update the revision ledger and verifier checklist), then immediately ask the next refinement-mode question. Never end a turn merely because a round completed.
|
|
124
|
+
|
|
125
|
+
## Execution Handoff
|
|
126
|
+
|
|
127
|
+
After the plan is accepted or refinement converges, ask the explicit execution-handoff question via `ask_choice` with `autoComplete: false`:
|
|
128
|
+
|
|
129
|
+
1. `Execute this plan now` — enter the tracked execution loop (recommended once refinement converged).
|
|
130
|
+
2. `Stop after planning` — keep the plan artifact only.
|
|
131
|
+
3. `Other`.
|
|
132
|
+
|
|
133
|
+
On approval, call the `execute_plan` tool (or the user runs `/plans-execute`). It re-confirms with the user, then the extension enters execution mode:
|
|
134
|
+
|
|
135
|
+
- every agent turn is injected with the remaining verifier checklist and execution rules (ponytail discipline, minimum tests);
|
|
136
|
+
- the read-only guard lifts: full write access returns;
|
|
137
|
+
- the run status moves to `executing`, then `done` when the last `[DONE:VC-xxx]` marker lands;
|
|
138
|
+
- `/plans-stop` stops execution; `/plans` shows progress.
|
|
139
|
+
|
|
140
|
+
If the user declines, stay in planning (or stop, per their choice). Never start implementation without the approved handoff.
|
|
141
|
+
|
|
142
|
+
## Red Flags
|
|
143
|
+
|
|
144
|
+
Stop and return to the workflow if any of these happen:
|
|
145
|
+
|
|
146
|
+
- implementing before the approved execution handoff;
|
|
147
|
+
- running `refine` without first asking the refinement-mode question, or before the role gates pass;
|
|
148
|
+
- ending a turn after a completed refinement round without asking the next refinement-mode question;
|
|
149
|
+
- storing planning settings outside the target workspace's `.git/pi_plans/` state directory;
|
|
150
|
+
- asking multiple planning questions in one message, or asking them outside `ask_choice`;
|
|
151
|
+
- writing `PLAN_v1.md` before final scope confirmation;
|
|
152
|
+
- accepting vague answers that contradict repo or reference evidence;
|
|
153
|
+
- treating a reviewer or criticizer as authority instead of evidence;
|
|
154
|
+
- offering Auto-complete for execution, install, deploy, merge, push, or destructive cleanup approval.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# PLAN_vN - <topic>
|
|
2
|
+
|
|
3
|
+
Status: draft | reviewed | accepted
|
|
4
|
+
Plan version: N
|
|
5
|
+
Artifact directory: `<artifact_root>/YYYY-MM-DD-topic/`
|
|
6
|
+
State directory: `.git/pi_plans/runs/<run-id>/` (resolved git common dir)
|
|
7
|
+
Language: `<BCP47 tag>`
|
|
8
|
+
|
|
9
|
+
## Original Request
|
|
10
|
+
|
|
11
|
+
Summarize the user's request in one paragraph.
|
|
12
|
+
|
|
13
|
+
## Goals
|
|
14
|
+
|
|
15
|
+
- `G-001`: Goal statement.
|
|
16
|
+
|
|
17
|
+
## Non-Goals
|
|
18
|
+
|
|
19
|
+
- `NG-001`: Explicitly excluded work.
|
|
20
|
+
|
|
21
|
+
## Workspace State
|
|
22
|
+
|
|
23
|
+
- `STATE-001`: `.git/pi_plans/config.json` language, reviewer, and criticizer settings used for this run.
|
|
24
|
+
- `STATE-002`: `.git/pi_plans/runs/<run-id>/run.json` and linked decision/subagent/ref ledgers.
|
|
25
|
+
|
|
26
|
+
## Repo Evidence
|
|
27
|
+
|
|
28
|
+
- `E-REPO-001`: Path or command inspected, what it proves, and any uncertainty.
|
|
29
|
+
|
|
30
|
+
## External Evidence
|
|
31
|
+
|
|
32
|
+
- `E-EXT-001`: URL or local ref path, what it supports, and date accessed.
|
|
33
|
+
|
|
34
|
+
## Resolved Decisions
|
|
35
|
+
|
|
36
|
+
- `D-001`: Question, chosen answer, answer source (user | Auto-complete), and rationale.
|
|
37
|
+
|
|
38
|
+
## Requirements
|
|
39
|
+
|
|
40
|
+
- `R-001`: Requirement tied to goals and decisions.
|
|
41
|
+
|
|
42
|
+
## Constraints
|
|
43
|
+
|
|
44
|
+
- `C-001`: Compatibility, style, interface, performance, safety, or ownership constraint.
|
|
45
|
+
|
|
46
|
+
## Implementation Items
|
|
47
|
+
|
|
48
|
+
- `I-001`: Work item with affected paths, dependencies, and expected code or doc changes.
|
|
49
|
+
|
|
50
|
+
## Acceptance Criteria
|
|
51
|
+
|
|
52
|
+
- `AC-001`: Observable result tied to one or more requirements.
|
|
53
|
+
|
|
54
|
+
## Verification Plan
|
|
55
|
+
|
|
56
|
+
- `V-001`: Command, manual check, screenshot, log review, or static inspection required after implementation.
|
|
57
|
+
|
|
58
|
+
## Verifier Checklist
|
|
59
|
+
|
|
60
|
+
- [ ] `VC-001` covers `I-001`; pass condition: describe pass condition; evidence: describe expected evidence; metric: threshold or reason not quantified.
|
|
61
|
+
|
|
62
|
+
## Risks And Mitigations
|
|
63
|
+
|
|
64
|
+
- `Risk-001`: Risk and mitigation.
|
|
65
|
+
|
|
66
|
+
## Refinement Settings
|
|
67
|
+
|
|
68
|
+
- Reviewer mode: `delegated-subagent | current-session`; model selector: `inherit | <selector>`.
|
|
69
|
+
- Criticizer mode: `delegated-subagent | current-session`; model selector: `inherit | <selector>`.
|
|
70
|
+
|
|
71
|
+
## Execution Handoff Notes
|
|
72
|
+
|
|
73
|
+
State anything the executor should know, including order of work, files to avoid, and verification commands. The execution handoff still requires explicit user approval (ask_choice with `autoComplete: false`, then the `execute_plan` tool) and must never be auto-completed. Once approved, the extension-managed execution loop injects the remaining checklist every turn and completes when every `[DONE:VC-xxx]` marker has landed — keep this section concise enough to serve as the executor's brief.
|
|
74
|
+
|
|
75
|
+
## Revision Ledger
|
|
76
|
+
|
|
77
|
+
- `PLAN_v1`: Initial plan from resolved questions and evidence.
|