@narumitw/pi-plan-mode 0.1.17 → 0.1.19

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 CHANGED
@@ -14,6 +14,7 @@ Pi core intentionally does not ship a built-in plan mode; this package provides
14
14
  - Blocks mutating bash commands such as `rm`, `git commit`, dependency installs, redirects, and editor launches.
15
15
  - Injects Codex-like Plan mode instructions: explore first, ask only non-discoverable questions, do not mutate files, and finish with `<proposed_plan>`.
16
16
  - Detects proposed plan blocks and prompts you to implement, revise, or stay in Plan mode.
17
+ - Shows Plan mode state in Pi's statusline as `📝 plan active` or `📝 plan ready`.
17
18
  - Persists Plan mode state in the Pi session so resume restores the mode.
18
19
 
19
20
  ## 📦 Install
@@ -38,8 +39,11 @@ pi -e ./extensions/pi-plan-mode
38
39
 
39
40
  ```text
40
41
  /plan
42
+ /plan <prompt>
41
43
  ```
42
44
 
45
+ Use `/plan` to enter Plan mode before writing your planning prompt. Use `/plan <prompt>` to enter Plan mode and immediately submit `<prompt>` as the first Plan-mode user message.
46
+
43
47
  When Plan mode is active, ask the agent to design the change. The agent may inspect files and run read-only commands, but it should not edit files or execute the implementation.
44
48
 
45
49
  A complete Plan mode answer should include exactly one block like this:
@@ -62,7 +66,12 @@ A complete Plan mode answer should include exactly one block like this:
62
66
  </proposed_plan>
63
67
  ```
64
68
 
65
- After a proposed plan is detected, `/plan` lets you choose whether to implement the plan, revise it, stay in Plan mode, or exit Plan mode.
69
+ After a proposed plan is detected, `/plan` lets you choose whether to implement the plan, revise it, stay in Plan mode, or exit Plan mode. Choosing implementation disables Plan mode, restores full tool access, and immediately starts an implementation turn with the proposed plan.
70
+
71
+ While Plan mode is enabled, the extension also publishes a compact status for Pi statuslines. With `@narumitw/pi-statusline`, this appears in the extension status area:
72
+
73
+ - `📝 plan active`: Plan mode is enabled and still gathering context or drafting a plan.
74
+ - `📝 plan ready`: A `<proposed_plan>` was detected and is waiting for your next `/plan` action.
66
75
 
67
76
  You can also exit directly:
68
77
 
@@ -75,8 +84,9 @@ You can also exit directly:
75
84
  This extension maps Codex's `ModeKind::Plan` behavior onto Pi's extension API:
76
85
 
77
86
  - Plan mode is a conversational collaboration mode, not TODO/progress tracking.
87
+ - `/plan <prompt>` follows Codex behavior by switching to Plan mode before submitting the inline prompt.
78
88
  - `update_plan`-style checklist use is discouraged while Plan mode is active.
79
- - The implementation boundary is explicit: Plan mode restores tools only after you choose to leave it.
89
+ - The implementation boundary is explicit: Plan mode restores tools before starting implementation, and choosing implementation immediately triggers a normal agent turn with full tool access.
80
90
  - Pi extension safety is approximated with active-tool restriction plus bash filtering, so it may be stricter or looser than Codex core in edge cases.
81
91
 
82
92
  ## 🗂️ Package layout
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@narumitw/pi-plan-mode",
3
- "version": "0.1.17",
3
+ "version": "0.1.19",
4
4
  "description": "Pi extension that adds a Codex-like read-only /plan collaboration mode.",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/plan-mode.ts CHANGED
@@ -88,12 +88,17 @@ export default function planMode(pi: ExtensionAPI) {
88
88
  pi.registerCommand("plan", {
89
89
  description: "Enter or manage Codex-like Plan mode",
90
90
  handler: async (args, ctx) => {
91
- const command = args.trim().toLowerCase();
91
+ const prompt = args.trim();
92
+ const command = prompt.toLowerCase();
92
93
  if (command === "exit" || command === "off") {
93
94
  exitPlanMode(ctx);
94
95
  ctx.ui.notify("Plan mode disabled. Full tool access restored.", "info");
95
96
  return;
96
97
  }
98
+ if (prompt) {
99
+ enterPlanModeWithPrompt(prompt, ctx);
100
+ return;
101
+ }
97
102
  if (!state.enabled) {
98
103
  enterPlanMode(ctx);
99
104
  ctx.ui.notify("Plan mode enabled. I will explore and plan, but not modify files.", "info");
@@ -186,6 +191,16 @@ export default function planMode(pi: ExtensionAPI) {
186
191
  updateUi(ctx);
187
192
  }
188
193
 
194
+ function enterPlanModeWithPrompt(prompt: string, ctx: ExtensionContext) {
195
+ const wasEnabled = state.enabled;
196
+ enterPlanMode(ctx);
197
+ if (!wasEnabled) {
198
+ ctx.ui.notify("Plan mode enabled. I will explore and plan, but not modify files.", "info");
199
+ }
200
+ if (ctx.isIdle()) pi.sendUserMessage(prompt);
201
+ else pi.sendUserMessage(prompt, { deliverAs: "followUp" });
202
+ }
203
+
189
204
  function exitPlanMode(ctx: ExtensionContext) {
190
205
  state = { ...state, enabled: false, awaitingAction: false };
191
206
  restoreTools();
@@ -193,6 +208,25 @@ export default function planMode(pi: ExtensionAPI) {
193
208
  updateUi(ctx);
194
209
  }
195
210
 
211
+ function startImplementation(ctx: ExtensionContext) {
212
+ const plan = state.latestPlan?.trim();
213
+ exitPlanMode(ctx);
214
+
215
+ if (!plan) {
216
+ ctx.ui.notify("Plan mode disabled. No proposed plan is available to implement.", "warning");
217
+ return;
218
+ }
219
+
220
+ pi.sendMessage(
221
+ {
222
+ customType: "plan-mode-implementation",
223
+ content: `Plan mode is now disabled. Full tool access is restored. Implement this proposed plan now:\n\n${plan}`,
224
+ display: true,
225
+ },
226
+ { triggerTurn: true },
227
+ );
228
+ }
229
+
196
230
  async function showPlanMenu(ctx: ExtensionContext) {
197
231
  if (!ctx.hasUI) {
198
232
  ctx.ui.notify(planStatusText(), "info");
@@ -207,7 +241,11 @@ export default function planMode(pi: ExtensionAPI) {
207
241
  ctx.ui.notify(state.latestPlan ?? "No proposed plan yet.", "info");
208
242
  return;
209
243
  }
210
- if (choice === "Implement this plan" || choice === "Exit Plan mode") {
244
+ if (choice === "Implement this plan") {
245
+ startImplementation(ctx);
246
+ return;
247
+ }
248
+ if (choice === "Exit Plan mode") {
211
249
  exitPlanMode(ctx);
212
250
  ctx.ui.notify("Plan mode disabled. Full tool access restored.", "info");
213
251
  return;
@@ -222,8 +260,7 @@ export default function planMode(pi: ExtensionAPI) {
222
260
  "Stay in Plan mode",
223
261
  ]);
224
262
  if (choice === "Implement this plan") {
225
- exitPlanMode(ctx);
226
- ctx.ui.notify("Plan mode disabled. Ask me to implement the proposed plan when ready.", "info");
263
+ startImplementation(ctx);
227
264
  return;
228
265
  }
229
266
  if (choice === "Revise plan") {
@@ -268,7 +305,7 @@ export default function planMode(pi: ExtensionAPI) {
268
305
  }
269
306
 
270
307
  function updateUi(ctx: ExtensionContext) {
271
- ctx.ui.setStatus(STATUS_KEY, state.enabled ? "plan: active" : undefined);
308
+ ctx.ui.setStatus(STATUS_KEY, formatStatus());
272
309
  if (state.enabled && state.latestPlan) {
273
310
  ctx.ui.setWidget(PLAN_WIDGET_KEY, [
274
311
  "Proposed plan ready",
@@ -281,6 +318,12 @@ export default function planMode(pi: ExtensionAPI) {
281
318
  }
282
319
  }
283
320
 
321
+ function formatStatus() {
322
+ if (!state.enabled) return undefined;
323
+ if (state.awaitingAction || state.latestPlan) return "📝 plan ready";
324
+ return "📝 plan active";
325
+ }
326
+
284
327
  function clearUi(ctx: ExtensionContext) {
285
328
  ctx.ui.setStatus(STATUS_KEY, undefined);
286
329
  ctx.ui.setWidget(PLAN_WIDGET_KEY, undefined);