@hank-warren/pi-plan-mode 1.0.0 → 1.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/CHANGELOG.md +51 -0
- package/README.md +20 -3
- package/package.json +3 -2
- package/src/plan-mode.ts +48 -3
- package/src/prompt.ts +48 -5
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# @hank-warren/pi-plan-mode
|
|
2
|
+
|
|
3
|
+
## 1.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- c40a48d: prefer `ask_user_question` for Plan-mode decision questions when it is installed
|
|
8
|
+
|
|
9
|
+
`plan_mode_question` renders through plain `ctx.ui.select` + `ctx.ui.editor`: no
|
|
10
|
+
previews, no notes, no tabs, no digit hotkeys. When
|
|
11
|
+
`@hank-warren/pi-ask-user-question` is present, Plan mode now asks through that
|
|
12
|
+
tool instead, which has all of them plus checkbox multi-select.
|
|
13
|
+
|
|
14
|
+
Detection is a runtime check for a tool named `ask_user_question` in the active
|
|
15
|
+
tool set, re-evaluated every turn — there is no dependency between the two
|
|
16
|
+
packages. When it is found, `plan_mode_question` is removed from the **active**
|
|
17
|
+
tool set so the model never sees two overlapping question tools, and the
|
|
18
|
+
Plan-mode system prompt names `ask_user_question` with its own bounds (1-4
|
|
19
|
+
questions, 2-4 options, 2-6 when `multiSelect`) and its own decline signal. The
|
|
20
|
+
tool stays _registered_ either way, so a historical transcript still resolves
|
|
21
|
+
it.
|
|
22
|
+
|
|
23
|
+
A standalone `pi-plan-mode` install is unchanged: `plan_mode_question` remains
|
|
24
|
+
fully functional and the prompt reads exactly as before. It is now a legacy
|
|
25
|
+
fallback, slated for removal in a future major.
|
|
26
|
+
|
|
27
|
+
## 1.0.1
|
|
28
|
+
|
|
29
|
+
### Patch Changes
|
|
30
|
+
|
|
31
|
+
- cf12677: ship CHANGELOG.md in the published tarball
|
|
32
|
+
|
|
33
|
+
## 1.0.0
|
|
34
|
+
|
|
35
|
+
### Major Changes
|
|
36
|
+
|
|
37
|
+
- 88572fe: Rewrite Plan mode around a durable plan file and stop managing tool permissions.
|
|
38
|
+
|
|
39
|
+
Plan mode is now a mode of intent rather than a permission system. It blocks exactly `edit`, `write`, and `update_plan` while planning and leaves every other tool as configured, so Bash and extension-tool safety stays with your permission layer (for example `@hank-warren/pi-auto-permissions`).
|
|
40
|
+
|
|
41
|
+
**Plan mode no longer calls `setActiveTools`.** It previously rewrote the session's active tool set on every turn, which stripped tools other extensions had registered — lazily connected MCP tools and on-demand channels such as pi-subagents' supervisor tool were removed mid-session and never reactivated, and extensions that manage their own tools on `before_agent_start` fought Plan mode for control. None of that can happen now.
|
|
42
|
+
|
|
43
|
+
Completed plans are written to `<agent dir>/plans/<session-id>.md`. The plan file is the plan: session state stores only its path, the file can be hand-edited, and both implementation paths read it from disk. While a plan is active the model receives a one-line pointer to the file instead of the plan body, so plans survive compaction at negligible context cost regardless of size, replacing the previous reinjection of up to 50,000 characters.
|
|
44
|
+
|
|
45
|
+
Breaking changes:
|
|
46
|
+
|
|
47
|
+
- Removed settings `defaultPlanTools`, `bashPolicy`, `safeSubcommands`, and `implementationPlanRetention`. They are now ignored rather than rejected, so existing settings files keep loading. `thinkingLevel` and `defaultPlanExportPath` are unchanged.
|
|
48
|
+
- Removed the pre-start tool selector and `/plan tools`.
|
|
49
|
+
- Removed the separate saved-plan state and `/plan save`; there is one plan per session, and `/plan exit` deletes it.
|
|
50
|
+
- Removed the legacy `<proposed_plan>` XML completion path; use `plan_mode_complete`.
|
|
51
|
+
- The implementation handoff and fresh-session transfer now reference the plan file instead of inlining the plan text.
|
package/README.md
CHANGED
|
@@ -4,14 +4,14 @@
|
|
|
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. Plan mode
|
|
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.
|
|
8
8
|
|
|
9
9
|
The plan is written to a **durable file** that survives compaction, survives resume, and can be hand-edited.
|
|
10
10
|
|
|
11
11
|
## ✨ Features
|
|
12
12
|
|
|
13
13
|
- `/plan` mode with a planning system prompt: explore first, ask decision questions, do not implement.
|
|
14
|
-
- `plan_mode_question` for structured 1-3 question decision points with options and a free-form path.
|
|
14
|
+
- `plan_mode_question` for structured 1-3 question decision points with options and a free-form path — or `ask_user_question` when that is installed (see below).
|
|
15
15
|
- `plan_mode_complete({ plan })` writes the plan to `<agent dir>/plans/<session-id>.md`.
|
|
16
16
|
- **Pointer, not payload.** An active plan adds one line to the system prompt naming the file. The plan body is never injected into context, so a 50-page plan costs the same as a one-liner and survives compaction for free.
|
|
17
17
|
- Two ways to implement: continue in this conversation, or open a fresh session that reads the same file.
|
|
@@ -95,7 +95,24 @@ Plan mode blocks exactly three tools while planning: `edit`, `write`, and `updat
|
|
|
95
95
|
|
|
96
96
|
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.
|
|
97
97
|
|
|
98
|
-
Plan mode
|
|
98
|
+
The one exception is `plan_mode_question`, which Plan mode hides from the model when a better questionnaire is installed — see below. No other tool is ever added to or removed from the active set, so extensions that register tools lazily (MCP connections, subagent supervision channels) keep working normally and nothing needs to be restored when Plan mode exits.
|
|
99
|
+
|
|
100
|
+
## 🤝 Better questions with `pi-ask-user-question`
|
|
101
|
+
|
|
102
|
+
With [`@hank-warren/pi-ask-user-question`](../pi-ask-user-question) installed, Plan mode asks its decision questions through that tool instead:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
pi install npm:@hank-warren/pi-ask-user-question
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
`plan_mode_question` renders through plain `ctx.ui.select` + `ctx.ui.editor`. `ask_user_question` gives the same decision points a real dialog: markdown **previews** on options, **notes** attached to a choice, several questions as **tabs** you cycle with Tab, **digit hotkeys**, and **checkbox multi-select**. It also allows 1-4 questions instead of 1-3, and 2-6 options on a multi-select question.
|
|
109
|
+
|
|
110
|
+
Detection is by tool name at runtime, re-evaluated every turn — there is no dependency between the two packages, and installing or removing one never requires touching the other. When `ask_user_question` is present:
|
|
111
|
+
|
|
112
|
+
- `plan_mode_question` is removed from the **active** tool set, so the model never sees two overlapping question tools and cannot call the weaker one. It stays *registered*, so a historical transcript still resolves it.
|
|
113
|
+
- The Plan-mode system prompt names `ask_user_question` and quotes its bounds and its decline signal.
|
|
114
|
+
|
|
115
|
+
A standalone `pi-plan-mode` install loses nothing: `plan_mode_question` stays fully functional and the prompt reads exactly as it always has. It is a **legacy fallback** and is slated for removal in a future major.
|
|
99
116
|
|
|
100
117
|
## 📊 Statusline
|
|
101
118
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hank-warren/pi-plan-mode",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
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": [
|
|
@@ -34,7 +34,8 @@
|
|
|
34
34
|
"src",
|
|
35
35
|
"README.md",
|
|
36
36
|
"NOTICE.md",
|
|
37
|
-
"LICENSE"
|
|
37
|
+
"LICENSE",
|
|
38
|
+
"CHANGELOG.md"
|
|
38
39
|
],
|
|
39
40
|
"peerDependencies": {
|
|
40
41
|
"@earendil-works/pi-coding-agent": "*",
|
package/src/plan-mode.ts
CHANGED
|
@@ -29,7 +29,12 @@ import {
|
|
|
29
29
|
showStoredPlan,
|
|
30
30
|
updatePlanModeUi,
|
|
31
31
|
} from "./presentation.js";
|
|
32
|
-
import {
|
|
32
|
+
import {
|
|
33
|
+
ASK_USER_QUESTION_TOOL,
|
|
34
|
+
buildActivePlanPointer,
|
|
35
|
+
buildPlanModePrompt,
|
|
36
|
+
PLAN_MODE_QUESTION_TOOL,
|
|
37
|
+
} from "./prompt.js";
|
|
33
38
|
import {
|
|
34
39
|
answerPlanModeQuestions,
|
|
35
40
|
normalizePlanModeQuestionParams,
|
|
@@ -54,6 +59,41 @@ const STATE_ENTRY_TYPE = "plan-mode-state";
|
|
|
54
59
|
*/
|
|
55
60
|
const BLOCKED_TOOLS = new Set(["edit", "write", "update_plan"]);
|
|
56
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Which question tool the prompt should name this turn.
|
|
64
|
+
*
|
|
65
|
+
* Detection is by tool NAME only, with no dependency on
|
|
66
|
+
* `@hank-warren/pi-ask-user-question`: any extension registering
|
|
67
|
+
* `ask_user_question` is treated as the preferred implementation. It offers
|
|
68
|
+
* previews, notes, question tabs, digit hotkeys and checkbox multi-select;
|
|
69
|
+
* `plan_mode_question` renders through plain `ctx.ui.select` + `ctx.ui.editor`
|
|
70
|
+
* and has none of them.
|
|
71
|
+
*
|
|
72
|
+
* Evaluated per turn rather than once at mode entry, so the prompt cannot go
|
|
73
|
+
* stale if the tool set changes mid-session.
|
|
74
|
+
*/
|
|
75
|
+
function preferredQuestionTool(pi: ExtensionAPI): string {
|
|
76
|
+
return pi.getActiveTools().includes(ASK_USER_QUESTION_TOOL)
|
|
77
|
+
? ASK_USER_QUESTION_TOOL
|
|
78
|
+
: PLAN_MODE_QUESTION_TOOL;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Hide `plan_mode_question` from the model whenever the better tool is present,
|
|
83
|
+
* so it never sees two overlapping question tools and cannot call the weaker
|
|
84
|
+
* one. The tool stays *registered* either way, so a historical transcript still
|
|
85
|
+
* resolves it, and a host without `ask_user_question` keeps it fully functional.
|
|
86
|
+
*
|
|
87
|
+
* Idempotent, and writes only when something actually changes: siblings are
|
|
88
|
+
* untouched, and repeated `before_agent_start` events are free.
|
|
89
|
+
*/
|
|
90
|
+
function reconcileQuestionTool(pi: ExtensionAPI, preferred: string): void {
|
|
91
|
+
if (preferred === PLAN_MODE_QUESTION_TOOL) return;
|
|
92
|
+
const active = pi.getActiveTools();
|
|
93
|
+
if (!active.includes(PLAN_MODE_QUESTION_TOOL)) return;
|
|
94
|
+
pi.setActiveTools(active.filter((name) => name !== PLAN_MODE_QUESTION_TOOL));
|
|
95
|
+
}
|
|
96
|
+
|
|
57
97
|
type InteractiveUi = typeof import("./interactive-ui.js");
|
|
58
98
|
|
|
59
99
|
interface PlanModeDependencies {
|
|
@@ -338,8 +378,13 @@ export default function planMode(pi: ExtensionAPI, dependencies: PlanModeDepende
|
|
|
338
378
|
persistState();
|
|
339
379
|
updateUi(ctx);
|
|
340
380
|
}
|
|
381
|
+
// Read fresh and write immediately, exactly like pi-ask-user-question's own
|
|
382
|
+
// reconciler, so the two hooks converge on the same result in either
|
|
383
|
+
// execution order.
|
|
384
|
+
const questionTool = preferredQuestionTool(pi);
|
|
385
|
+
reconcileQuestionTool(pi, questionTool);
|
|
341
386
|
if (state.enabled) {
|
|
342
|
-
return { systemPrompt: `${event.systemPrompt}\n\n${buildPlanModePrompt()}` };
|
|
387
|
+
return { systemPrompt: `${event.systemPrompt}\n\n${buildPlanModePrompt(questionTool)}` };
|
|
343
388
|
}
|
|
344
389
|
// Pointer, not payload: an active plan costs one line of context no matter
|
|
345
390
|
// how large the plan is, and survives compaction for free.
|
|
@@ -450,7 +495,7 @@ export default function planMode(pi: ExtensionAPI, dependencies: PlanModeDepende
|
|
|
450
495
|
return;
|
|
451
496
|
}
|
|
452
497
|
sendPlanModeUserMessage(
|
|
453
|
-
|
|
498
|
+
`Finalize the current implementation plan now. If any material decision remains, use ${preferredQuestionTool(pi)} instead. Otherwise call plan_mode_complete alone as your final action with the complete decision-ready plan.`,
|
|
454
499
|
ctx,
|
|
455
500
|
);
|
|
456
501
|
}
|
package/src/prompt.ts
CHANGED
|
@@ -1,6 +1,49 @@
|
|
|
1
1
|
const PLAN_CONTEXT_MARKER = "[PLAN MODE ACTIVE]";
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
/** The built-in question tool. Used whenever nothing better is installed. */
|
|
4
|
+
export const PLAN_MODE_QUESTION_TOOL = "plan_mode_question";
|
|
5
|
+
/**
|
|
6
|
+
* `@hank-warren/pi-ask-user-question`'s tool. Detected by name at runtime, with
|
|
7
|
+
* no dependency on that package: previews, notes, tabs, digit hotkeys and
|
|
8
|
+
* checkbox multi-select are all things `plan_mode_question` cannot offer.
|
|
9
|
+
*/
|
|
10
|
+
export const ASK_USER_QUESTION_TOOL = "ask_user_question";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The bits of prompt text that differ between the two question tools. Every
|
|
14
|
+
* reference to a question tool in the prompt is built from one of these, so the
|
|
15
|
+
* prompt can never name one tool's bounds beside the other tool's name.
|
|
16
|
+
*/
|
|
17
|
+
interface QuestionToolProfile {
|
|
18
|
+
name: string;
|
|
19
|
+
/** How many questions and options the tool actually accepts. */
|
|
20
|
+
bounds: string;
|
|
21
|
+
/** What the model sees when the user does not answer. */
|
|
22
|
+
decline: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const QUESTION_TOOL_PROFILES: Record<string, QuestionToolProfile> = {
|
|
26
|
+
[PLAN_MODE_QUESTION_TOOL]: {
|
|
27
|
+
name: PLAN_MODE_QUESTION_TOOL,
|
|
28
|
+
bounds: "Ask 1-3 concise questions with 2-4 meaningful options.",
|
|
29
|
+
decline: `If ${PLAN_MODE_QUESTION_TOOL} returns cancelled or ui_unavailable`,
|
|
30
|
+
},
|
|
31
|
+
[ASK_USER_QUESTION_TOOL]: {
|
|
32
|
+
name: ASK_USER_QUESTION_TOOL,
|
|
33
|
+
bounds:
|
|
34
|
+
"Ask 1-4 concise questions with 2-4 meaningful options each, or 2-6 options when the question sets multiSelect.",
|
|
35
|
+
decline: `If ${ASK_USER_QUESTION_TOOL} reports that the user declined to answer`,
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Build the Plan-mode prompt around whichever question tool is available.
|
|
41
|
+
*
|
|
42
|
+
* The default keeps the exported function callable with no arguments and keeps
|
|
43
|
+
* a standalone `pi-plan-mode` install reading exactly as it did before.
|
|
44
|
+
*/
|
|
45
|
+
export function buildPlanModePrompt(questionTool: string = PLAN_MODE_QUESTION_TOOL) {
|
|
46
|
+
const tool = QUESTION_TOOL_PROFILES[questionTool] ?? QUESTION_TOOL_PROFILES[PLAN_MODE_QUESTION_TOOL];
|
|
4
47
|
return `${PLAN_CONTEXT_MARKER}
|
|
5
48
|
# Plan Mode (Conversational)
|
|
6
49
|
|
|
@@ -29,14 +72,14 @@ You are in Plan Mode, a collaboration mode for producing a decision-complete imp
|
|
|
29
72
|
## Phase 3 — Implementation chat
|
|
30
73
|
|
|
31
74
|
- Once intent is stable, keep asking until the spec is decision-complete: approach, interfaces, data flow, edge cases/failure modes, testing and acceptance criteria, and any migration or compatibility constraints.
|
|
32
|
-
- Use
|
|
33
|
-
-
|
|
75
|
+
- Use ${tool.name} for important preferences, tradeoffs, or assumption locks that cannot be discovered by non-mutating exploration. ${tool.bounds} Do not include filler options.
|
|
76
|
+
- ${tool.decline}, do not jump straight to a final plan when the missing answer is high impact. Ask one concise plain-text question or proceed only with a clearly stated low-risk assumption.
|
|
34
77
|
|
|
35
78
|
## Ending each turn
|
|
36
79
|
|
|
37
80
|
Every Plan-mode turn that advances or finalizes the plan must end in exactly one of these ways:
|
|
38
81
|
|
|
39
|
-
- If a material decision remains, use
|
|
82
|
+
- If a material decision remains, use ${tool.name}. If interactive UI is unavailable, ask one concise plain-text question instead.
|
|
40
83
|
- If the implementation plan is decision-complete, call plan_mode_complete alone as your final action. Do not call other tools in the same batch and do not emit a normal assistant response after it.
|
|
41
84
|
|
|
42
85
|
If a follow-up asks only for clarification and does not change or challenge the plan, answer it directly, then call plan_mode_complete alone as the final action with the complete unchanged plan so it remains available for implementation.
|
|
@@ -57,7 +100,7 @@ Keep the plan concise, human and agent digestible, and free of open decisions. P
|
|
|
57
100
|
|
|
58
101
|
The plan is saved to a durable file, so it survives compaction and can be re-read at any time.
|
|
59
102
|
|
|
60
|
-
If the user requests revisions after a completed plan, the next plan_mode_complete call must contain a complete replacement, not a delta. If there is not enough information for a complete replacement, continue planning with
|
|
103
|
+
If the user requests revisions after a completed plan, the next plan_mode_complete call must contain a complete replacement, not a delta. If there is not enough information for a complete replacement, continue planning with ${tool.name} instead of calling plan_mode_complete.`;
|
|
61
104
|
}
|
|
62
105
|
|
|
63
106
|
/**
|