@hank-warren/pi-plan-mode 1.2.0 → 1.2.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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @hank-warren/pi-plan-mode
2
2
 
3
+ ## 1.2.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 3fbf632: Persist the thinking-level capture taken while restoring plan mode. The two restore paths applied the configured level without writing the captured previous one, and Pi's `setThinkingLevel` writes the new level into the user's settings — so a session that ended without `session_shutdown` lost the only record of the original level and the next restore captured the plan level as "previous", permanently raising the user's default. An unchanged restore still writes nothing.
8
+
3
9
  ## 1.2.0
4
10
 
5
11
  ### Minor Changes
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  `@hank-warren/pi-plan-mode` adds a `/plan` mode to Pi for research and design. You gather information, ask questions, and land on a plan — then implement it, either in the same conversation or in a fresh one.
6
6
 
7
- **Plan mode is a mode of intent, not a permission system.** It blocks `edit`, `write`, and `update_plan` while planning and leaves every other tool exactly as you configured it. Command safety stays with your permission extension (for example [`@hank-warren/pi-auto-permissions`](../pi-auto-permissions)), which already reviews Bash. The only tool Plan mode ever removes from the active set is its own `plan_mode_question`, and only when a better questionnaire is installed (see below), so it cannot break other extensions.
7
+ **Plan mode is a mode of intent, not a permission system.** It blocks `edit` and `write` while planning and leaves every other tool exactly as you configured it. Command safety stays with your permission extension (for example [`@hank-warren/pi-auto-permissions`](../pi-auto-permissions)), which already reviews Bash. The only tool Plan mode ever removes from the active set is its own `plan_mode_question`, and only when a better questionnaire is installed (see below), so it cannot break other extensions.
8
8
 
9
9
  The plan is written to a **durable file** that survives compaction, survives resume, and can be hand-edited.
10
10
 
@@ -45,7 +45,9 @@ pi -e npm:@hank-warren/pi-plan-mode
45
45
 
46
46
  `--plan` starts a session directly in Plan mode.
47
47
 
48
- While Plan mode is active, ask the agent to design the change. It can read, search, and run commands, but `edit`, `write`, and `update_plan` are blocked. When the plan is decision-complete, the agent calls `plan_mode_complete` and the plan is written to disk.
48
+ While Plan mode is active, ask the agent to design the change. It can read, search, and run commands, but `edit` and `write` are blocked. When the plan is decision-complete, the agent calls `plan_mode_complete` and the plan is written to disk.
49
+
50
+ A completed plan is not final until you act on it: just type feedback to revise — the next planning turn supersedes the proposed plan, and the next `plan_mode_complete` replaces it.
49
51
 
50
52
  From a completed plan you can:
51
53
 
@@ -95,7 +97,7 @@ A settings file that does not parse is reported at session start and the default
95
97
 
96
98
  ## 🔐 What Plan mode does and does not enforce
97
99
 
98
- Plan mode blocks exactly three tools while planning: `edit`, `write`, and `update_plan`. That is the whole enforcement surface.
100
+ Plan mode blocks exactly two tools while planning: `edit` and `write`. That is the whole enforcement surface. Checklist tools (a `todo` extension, for example) are deliberately not blocked — a task list is ephemeral planning scratch, and the planning prompt steers the model away from execution-progress tracking.
99
101
 
100
102
  It deliberately does **not** police Bash, subagents, MCP tools, or any other extension tool. Those decisions belong to your permission layer, which can see the whole session and judge each call. Pair Plan mode with a permission extension such as `@hank-warren/pi-auto-permissions` if you want command review during planning.
101
103
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hank-warren/pi-plan-mode",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "description": "Plan mode for Pi: research and design with a durable plan file that survives compaction.",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -10,6 +10,7 @@ interface MenuLifecycle {
10
10
  const IMPLEMENTATION_CONTEXT_LINES = [
11
11
  "Implement here keeps this planning conversation.",
12
12
  "Start fresh opens a new session that reads the same plan file.",
13
+ "Or just type feedback to revise — the next completed plan supersedes this one.",
13
14
  ] as const;
14
15
 
15
16
  interface PlanMenuOptions extends MenuLifecycle {
@@ -13,7 +13,7 @@ const HOW_IT_WORKS_LINES = [
13
13
  "Plan mode is for research and design, not implementation.",
14
14
  "Explore the codebase, ask decision questions, then finish with plan_mode_complete.",
15
15
  "The plan is written to a durable file that survives compaction and can be hand-edited.",
16
- "Built-in edit, write, and update_plan are blocked while planning.",
16
+ "Built-in edit and write are blocked while planning.",
17
17
  "All other tools stay exactly as configured; command safety is left to your permission extension.",
18
18
  "When the plan is ready: implement here, or start a fresh session that reads the same file.",
19
19
  ] as const;
package/src/plan-mode.ts CHANGED
@@ -59,8 +59,12 @@ const STATE_ENTRY_TYPE = "plan-mode-state";
59
59
  * MCP, and other extension tools — is left to the session's normal permission
60
60
  * layer (for example @hank-warren/pi-auto-permissions), so Plan mode never
61
61
  * mutates the active tool set and never fights other extensions for it.
62
+ * Checklist tools (a `todo` extension, for example) are deliberately not
63
+ * blocked: a task list is ephemeral planning scratch, and the planning prompt
64
+ * already steers the model away from execution-progress tooling. (`update_plan`
65
+ * was once listed here; it was a pre-1.0 upstream tool that no longer exists.)
62
66
  */
63
- const BLOCKED_TOOLS = new Set(["edit", "write", "update_plan"]);
67
+ const BLOCKED_TOOLS = new Set(["edit", "write"]);
64
68
  /**
65
69
  * One hand-edit or menu save fans out into several filesystem events (temp file
66
70
  * created, renamed into place). Collapsing them into one re-read keeps a save
@@ -157,8 +161,14 @@ export default function planMode(pi: ExtensionAPI, dependencies: PlanModeDepende
157
161
  exportPlan: (ctx, path, signal, isCurrent) => planExports.export(path, ctx, signal, isCurrent),
158
162
  stay: updateUi,
159
163
  exitReady: (ctx) => {
164
+ // Same had-plan branching as the /plan exit command: the menu must not
165
+ // claim a plan was discarded when none was ever completed.
166
+ const hadPlan = state.planPath !== undefined;
160
167
  void exitPlanMode(ctx).then(() => {
161
- ctx.ui.notify("Plan mode disabled. Proposed plan discarded.", "info");
168
+ ctx.ui.notify(
169
+ hadPlan ? "Plan mode disabled. Proposed plan discarded." : "Plan mode disabled.",
170
+ "info",
171
+ );
162
172
  });
163
173
  },
164
174
  });
@@ -390,8 +400,8 @@ export default function planMode(pi: ExtensionAPI, dependencies: PlanModeDepende
390
400
  if (persistFlagActivation) {
391
401
  state = { ...state, enabled: true, awaitingAction: state.planPath !== undefined };
392
402
  }
393
- if (state.enabled) applyPlanThinkingLevel();
394
- if (persistFlagActivation) persistState();
403
+ const capturedThinkingLevel = state.enabled ? applyPlanThinkingLevel() : false;
404
+ if (persistFlagActivation || capturedThinkingLevel) persistState();
395
405
  updateUi(ctx);
396
406
  });
397
407
 
@@ -423,7 +433,7 @@ export default function planMode(pi: ExtensionAPI, dependencies: PlanModeDepende
423
433
  });
424
434
 
425
435
  /**
426
- * The complete enforcement surface: three static built-in names. Plan mode
436
+ * The complete enforcement surface: two static built-in names. Plan mode
427
437
  * does not classify, inspect, or filter any other tool.
428
438
  */
429
439
  pi.on("tool_call", async (event) => {
@@ -431,10 +441,7 @@ export default function planMode(pi: ExtensionAPI, dependencies: PlanModeDepende
431
441
  if (!BLOCKED_TOOLS.has(event.toolName)) return;
432
442
  return {
433
443
  block: true,
434
- reason:
435
- event.toolName === "update_plan"
436
- ? "Plan mode blocks update_plan because it tracks execution progress rather than conversational planning."
437
- : `Plan mode blocks '${event.toolName}' because planning must not mutate files. Finish the plan with plan_mode_complete, then implement.`,
444
+ reason: `Plan mode blocks '${event.toolName}' because planning must not mutate files. Finish the plan with plan_mode_complete, then implement.`,
438
445
  };
439
446
  });
440
447
 
@@ -442,7 +449,7 @@ export default function planMode(pi: ExtensionAPI, dependencies: PlanModeDepende
442
449
  if (refreshStateBeforeFirstAgentStart) {
443
450
  refreshStateBeforeFirstAgentStart = false;
444
451
  restoreState(ctx);
445
- if (state.enabled) applyPlanThinkingLevel();
452
+ if (state.enabled && applyPlanThinkingLevel()) persistState();
446
453
  updateUi(ctx);
447
454
  }
448
455
  if (state.enabled && state.awaitingAction) {
@@ -699,26 +706,42 @@ export default function planMode(pi: ExtensionAPI, dependencies: PlanModeDepende
699
706
  };
700
707
  }
701
708
 
702
- function applyPlanThinkingLevel() {
709
+ /**
710
+ * Returns true when the captured thinking levels changed, so the restore
711
+ * paths can persist that capture. It is the only record of the user's level:
712
+ * `setPlanThinkingLevel` goes through Pi's `setThinkingLevel`, which writes
713
+ * the new level into the user's settings, so a capture left only in memory is
714
+ * lost if the session ends without `session_shutdown`.
715
+ */
716
+ function applyPlanThinkingLevel(): boolean {
703
717
  if (state.manualThinkingLevel) {
704
718
  if (pi.getThinkingLevel() !== state.manualThinkingLevel) {
705
719
  setPlanThinkingLevel(pi, state.manualThinkingLevel);
706
720
  }
707
- return;
721
+ return false;
708
722
  }
709
723
  const configured = configuredThinkingLevel(settings);
710
724
  if (!configured) {
725
+ if (state.previousThinkingLevel === undefined && state.appliedThinkingLevel === undefined) {
726
+ return false;
727
+ }
711
728
  state = {
712
729
  ...state,
713
730
  previousThinkingLevel: undefined,
714
731
  appliedThinkingLevel: undefined,
715
732
  };
716
- return;
733
+ return true;
717
734
  }
718
735
  const current = pi.getThinkingLevel();
736
+ const capturedPrevious = state.previousThinkingLevel;
737
+ const capturedApplied = state.appliedThinkingLevel;
719
738
  if (!state.appliedThinkingLevel) state.previousThinkingLevel = current;
720
739
  if (current !== configured) setPlanThinkingLevel(pi, configured);
721
740
  state.appliedThinkingLevel = pi.getThinkingLevel();
741
+ return (
742
+ state.previousThinkingLevel !== capturedPrevious ||
743
+ state.appliedThinkingLevel !== capturedApplied
744
+ );
722
745
  }
723
746
 
724
747
  function captureManualThinkingLevel() {
@@ -10,7 +10,7 @@ export function updatePlanModeUi(ctx: ExtensionContext, state: PlanModeState) {
10
10
  if (state.enabled && state.awaitingAction) {
11
11
  ctx.ui.setWidget(PLAN_WIDGET_KEY, [
12
12
  "Proposed plan ready",
13
- "Use /plan to implement, export, revise, or exit Plan mode.",
13
+ "Use /plan to implement, export, or exit or type feedback to revise.",
14
14
  ]);
15
15
  } else if (state.enabled) {
16
16
  ctx.ui.setWidget(PLAN_WIDGET_KEY, [
@@ -48,7 +48,13 @@ export async function showStoredPlan(
48
48
  );
49
49
  return;
50
50
  }
51
- const title = state.enabled ? "Proposed Plan" : "Active Implementation Plan";
51
+ // enabled without awaitingAction but with a stored plan means revision
52
+ // feedback superseded the completed plan: show it, but never as current.
53
+ const title = state.enabled
54
+ ? state.awaitingAction
55
+ ? "Proposed Plan"
56
+ : "Superseded Proposed Plan (revision in progress — awaiting a new plan_mode_complete)"
57
+ : "Active Implementation Plan";
52
58
  showPlanModePlan(pi, ctx, title, plan);
53
59
  }
54
60
 
@@ -76,6 +82,9 @@ export function showPlanModePlan(
76
82
  export function planModeStatusText(state: PlanModeState) {
77
83
  if (state.enabled) {
78
84
  if (state.awaitingAction) return "Plan mode is active and a proposed plan is ready.";
85
+ if (state.planPath) {
86
+ return "Plan mode is active; revision in progress. The stored plan is superseded until the next plan_mode_complete.";
87
+ }
79
88
  return "Plan mode is active. Explore, ask, and finish with plan_mode_complete when decision-ready.";
80
89
  }
81
90
  if (state.planPath) return "An implementation plan is active.";
package/src/prompt.ts CHANGED
@@ -53,7 +53,7 @@ You are in Plan Mode, a collaboration mode for producing a decision-complete imp
53
53
 
54
54
  - Stay in Plan Mode until a developer or extension explicitly exits it.
55
55
  - Treat requests to implement as requests to plan the implementation; do not edit files or carry out the plan.
56
- - Do not use update_plan/TODO tooling in Plan Mode; Plan Mode is conversational planning, not execution progress tracking.
56
+ - Do not use todo/checklist tooling to track execution progress in Plan Mode; Plan Mode is conversational planning, and the plan itself belongs in plan_mode_complete.
57
57
  - Do not perform mutating actions: no edit/write tools, no patching, no formatting that rewrites files, no dependency installation, no commits, no migrations.
58
58
  - Gather information freely: read files, search, inspect configuration, and run read-only commands.
59
59