@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 +6 -0
- package/README.md +5 -3
- package/package.json +1 -1
- package/src/plan-action-menus.ts +1 -0
- package/src/plan-launch-menu.ts +1 -1
- package/src/plan-mode.ts +36 -13
- package/src/presentation.ts +11 -2
- package/src/prompt.ts +1 -1
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
|
|
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
|
|
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
|
|
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
package/src/plan-action-menus.ts
CHANGED
|
@@ -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 {
|
package/src/plan-launch-menu.ts
CHANGED
|
@@ -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
|
|
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"
|
|
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(
|
|
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
|
-
|
|
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:
|
|
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
|
|
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
|
-
|
|
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() {
|
package/src/presentation.ts
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
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
|
|
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
|
|