@hank-warren/pi-plan-mode 1.6.0 → 1.7.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 +29 -0
- package/README.md +9 -5
- package/{skills/pi-plan-mode/SKILL.md → docs/plan-craft.md} +0 -6
- package/package.json +2 -5
- package/src/plan-mode.ts +19 -1
- package/src/prompt.ts +23 -1
- package/src/question-tool.ts +24 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
# @hank-warren/pi-plan-mode
|
|
2
2
|
|
|
3
|
+
## 1.7.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 1035138: Ship the craft documents by injected path instead of as skills.
|
|
8
|
+
|
|
9
|
+
The `pi-loop` and `pi-plan-mode` skills are gone. Their bodies now ship as
|
|
10
|
+
`docs/loop-craft.md` and `docs/plan-craft.md`, and the mode prompts inject the
|
|
11
|
+
file's absolute path (resolved from the installed package) at the moments the
|
|
12
|
+
guidance matters: while a loop is being drafted or completed, and while Plan
|
|
13
|
+
Mode is active. A skill's description line sits in every system prompt of every
|
|
14
|
+
session with the package loaded; across ~220 sessions after these two shipped,
|
|
15
|
+
every read of either file was triggered by the mode's own prompt and never by
|
|
16
|
+
the description, so the line was a tax on the ~95% of sessions that never
|
|
17
|
+
entered the mode. Same document, read at the same moments, at zero cost outside
|
|
18
|
+
them. Hosts that referenced the skills by name in settings should drop those
|
|
19
|
+
entries.
|
|
20
|
+
|
|
21
|
+
### Patch Changes
|
|
22
|
+
|
|
23
|
+
- 1035138: Report a waiting question to Herdr as `blocked`.
|
|
24
|
+
|
|
25
|
+
`ask_user_question` and Plan Mode's `plan_mode_question` now emit the same
|
|
26
|
+
`herdr:blocked` event pi-auto-permissions emits for an approval prompt, labelled
|
|
27
|
+
`question` / `plan question` and cleared in a `finally`, so Herdr's pi
|
|
28
|
+
integration shows a session waiting on a question as `blocked` rather than
|
|
29
|
+
`working`. A supervising agent in another pane can wait on that state and answer
|
|
30
|
+
the dialog. No-op outside Herdr.
|
|
31
|
+
|
|
3
32
|
## 1.6.0
|
|
4
33
|
|
|
5
34
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ The plan is written to a **durable file** that survives compaction, survives res
|
|
|
17
17
|
- Two ways to implement: continue in this conversation, or open a fresh session that reads the same file.
|
|
18
18
|
- `/plan export [path]` copies the plan anywhere, never overwriting an existing target.
|
|
19
19
|
- Hand-edit the plan file at any time; every command and both implementation paths read from disk.
|
|
20
|
-
- A
|
|
20
|
+
- A shipped **plan-craft doc** the prompt points at by path, carrying the plan-crafting craft the prompt itself only names.
|
|
21
21
|
|
|
22
22
|
## 📦 Install
|
|
23
23
|
|
|
@@ -118,11 +118,11 @@ Detection is by tool name at runtime, re-evaluated every turn — there is no de
|
|
|
118
118
|
|
|
119
119
|
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.
|
|
120
120
|
|
|
121
|
-
## 📚 The
|
|
121
|
+
## 📚 The plan-craft doc
|
|
122
122
|
|
|
123
|
-
The
|
|
123
|
+
The system prompt is the enforcement surface and stays deliberately short. The depth layer it points at — what decision-complete actually means, why exploration comes before questions, what separates a question worth asking from one the repository already answered, and what belongs in a finished plan — is [`docs/plan-craft.md`](docs/plan-craft.md), shipped with the package. One line in the planning prompt names it by absolute path (resolved from the installed package, so it works under any install layout), and the model reads it when Plan Mode opens.
|
|
124
124
|
|
|
125
|
-
|
|
125
|
+
It used to be a skill. A skill's description line is in every system prompt, which buys exactly one thing an injected pointer cannot: the model proposing planning unprompted. Across ~220 sessions after it shipped, every read of the file happened after the Plan Mode prompt was already active, never off the description, and the model never suggested `/plan` on its own — so the line was a tax on every session that never planned (about 95% of them) that bought nothing. A hard path injected only while the mode is active is the same document at zero cost outside it.
|
|
126
126
|
|
|
127
127
|
## 📊 Statusline and widget
|
|
128
128
|
|
|
@@ -145,7 +145,7 @@ packages/pi-plan-mode/
|
|
|
145
145
|
│ ├── plan-file.ts # Durable plan file read/write/delete
|
|
146
146
|
│ ├── interactive-ui.ts # Lazily loaded interactive menus
|
|
147
147
|
│ └── *.ts # Prompt, question, export, settings modules
|
|
148
|
-
├──
|
|
148
|
+
├── docs/plan-craft.md # Plan-crafting depth, injected by path while the mode is active
|
|
149
149
|
├── test/
|
|
150
150
|
├── README.md
|
|
151
151
|
├── NOTICE.md
|
|
@@ -153,6 +153,10 @@ packages/pi-plan-mode/
|
|
|
153
153
|
└── package.json
|
|
154
154
|
```
|
|
155
155
|
|
|
156
|
+
## Changelog
|
|
157
|
+
|
|
158
|
+
See [CHANGELOG.md](CHANGELOG.md) for release history.
|
|
159
|
+
|
|
156
160
|
## 📄 License
|
|
157
161
|
|
|
158
162
|
MIT. See [`LICENSE`](./LICENSE).
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: pi-plan-mode
|
|
3
|
-
description: Craft decision-complete implementation plans in Plan mode — explore before asking, ask questions worth answering, and write a plan another agent can execute without guessing. Use while Plan mode is active, before calling plan_mode_complete, and when deciding whether a request needs a plan at all.
|
|
4
|
-
license: MIT
|
|
5
|
-
---
|
|
6
|
-
|
|
7
1
|
# pi-plan-mode
|
|
8
2
|
|
|
9
3
|
The extension enforces the mechanics: `edit` and `write` are blocked, the plan is written to a durable file, and `plan_mode_complete` is the only way out. It cannot enforce the thing that decides whether the plan was worth making: **whether a competent implementer could execute it without asking you anything.** That is this file.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hank-warren/pi-plan-mode",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.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": [
|
|
@@ -27,15 +27,12 @@
|
|
|
27
27
|
"pi": {
|
|
28
28
|
"extensions": [
|
|
29
29
|
"./index.ts"
|
|
30
|
-
],
|
|
31
|
-
"skills": [
|
|
32
|
-
"./skills/pi-plan-mode"
|
|
33
30
|
]
|
|
34
31
|
},
|
|
35
32
|
"files": [
|
|
36
33
|
"index.ts",
|
|
37
34
|
"src",
|
|
38
|
-
"
|
|
35
|
+
"docs/plan-craft.md",
|
|
39
36
|
"README.md",
|
|
40
37
|
"NOTICE.md",
|
|
41
38
|
"LICENSE",
|
package/src/plan-mode.ts
CHANGED
|
@@ -52,6 +52,20 @@ import { type PlanModeState, restorePlanModeState } from "./state.js";
|
|
|
52
52
|
|
|
53
53
|
const STATE_ENTRY_TYPE = "plan-mode-state";
|
|
54
54
|
const ASK_USER_AVAILABILITY_EVENT = "hank:ask-user:availability";
|
|
55
|
+
/** Label Herdr shows while `plan_mode_question` waits; distinguishes it from an approval. */
|
|
56
|
+
export const HERDR_BLOCKED_LABEL = "plan question";
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Tell Herdr this pane is waiting on a human, so a supervising agent in another
|
|
60
|
+
* pane sees the block instead of reading a stalled turn as progress. Same
|
|
61
|
+
* contract as pi-auto-permissions' `setHerdrBlocked`, duplicated rather than
|
|
62
|
+
* imported so Plan Mode has no dependency on the permissions engine. No-op
|
|
63
|
+
* outside Herdr.
|
|
64
|
+
*/
|
|
65
|
+
function setHerdrBlocked(pi: ExtensionAPI, active: boolean): void {
|
|
66
|
+
if (process.env.HERDR_ENV !== "1") return;
|
|
67
|
+
pi.events.emit("herdr:blocked", active ? { active: true, label: HERDR_BLOCKED_LABEL } : { active: false });
|
|
68
|
+
}
|
|
55
69
|
/**
|
|
56
70
|
* Plan mode's entire enforcement surface. Everything else — bash, subagents,
|
|
57
71
|
* MCP, and other extension tools — is left to the session's normal permission
|
|
@@ -221,7 +235,11 @@ export default function planMode(pi: ExtensionAPI, dependencies: PlanModeDepende
|
|
|
221
235
|
return answerPlanModeQuestions(
|
|
222
236
|
parsed.questions,
|
|
223
237
|
ctx,
|
|
224
|
-
{
|
|
238
|
+
{
|
|
239
|
+
isCurrent: menu.isCurrent,
|
|
240
|
+
isEnabled: () => state.enabled,
|
|
241
|
+
onBlocked: (active) => setHerdrBlocked(pi, active),
|
|
242
|
+
},
|
|
225
243
|
questionSignal,
|
|
226
244
|
);
|
|
227
245
|
},
|
package/src/prompt.ts
CHANGED
|
@@ -1,3 +1,25 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The plan-craft document: what decision-complete means, why exploration comes
|
|
5
|
+
* before questions, what separates a question worth asking from one the
|
|
6
|
+
* repository already answers, and what a finished plan contains.
|
|
7
|
+
*
|
|
8
|
+
* It used to ship as a skill. A skill buys one thing an injected pointer
|
|
9
|
+
* cannot: a description line in every system prompt, so the model could
|
|
10
|
+
* propose planning unprompted. Across ~220 sessions after it shipped, every
|
|
11
|
+
* read of the file happened after the Plan Mode prompt was already active —
|
|
12
|
+
* never off the description — and the model never suggested `/plan` on its
|
|
13
|
+
* own. So the line was a tax on every session that never planned (~95% of
|
|
14
|
+
* them) and bought nothing. An absolute path, injected only while Plan Mode
|
|
15
|
+
* is active, is the same document at zero cost outside it, and a hard path
|
|
16
|
+
* beats "if it is available".
|
|
17
|
+
*
|
|
18
|
+
* Resolved from this module's own location so it survives every install
|
|
19
|
+
* layout (git, npm, workspace symlink, `npm link`).
|
|
20
|
+
*/
|
|
21
|
+
export const PLAN_CRAFT_DOC = join(import.meta.dirname, "..", "docs", "plan-craft.md");
|
|
22
|
+
|
|
1
23
|
const PLAN_CONTEXT_MARKER = "[PLAN MODE ACTIVE]";
|
|
2
24
|
|
|
3
25
|
/** The built-in question tool. Used whenever nothing better is installed. */
|
|
@@ -71,7 +93,7 @@ You are in Plan Mode, a collaboration mode for producing a decision-complete imp
|
|
|
71
93
|
|
|
72
94
|
## Mode rules
|
|
73
95
|
|
|
74
|
-
-
|
|
96
|
+
- Before planning, read ${PLAN_CRAFT_DOC}: it carries the plan-crafting craft — decision-completeness, exploring before asking, question quality, and what a finished plan contains.
|
|
75
97
|
- Stay in Plan Mode until a developer or extension explicitly exits it.
|
|
76
98
|
- Treat requests to implement as requests to plan the implementation; do not edit files or carry out the plan.
|
|
77
99
|
- 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.
|
package/src/question-tool.ts
CHANGED
|
@@ -151,15 +151,32 @@ export function normalizePlanModeQuestionParams(
|
|
|
151
151
|
export async function answerPlanModeQuestions(
|
|
152
152
|
questions: PlanModeQuestion[],
|
|
153
153
|
ctx: ExtensionContext,
|
|
154
|
-
lifecycle: {
|
|
154
|
+
lifecycle: {
|
|
155
|
+
isCurrent(): boolean;
|
|
156
|
+
isEnabled(): boolean;
|
|
157
|
+
/**
|
|
158
|
+
* Called with `true` while a selector is open and `false` once it closes,
|
|
159
|
+
* however it closes. plan-mode.ts forwards this to Herdr so a supervising
|
|
160
|
+
* agent in another pane sees "blocked on a human", not "working".
|
|
161
|
+
*/
|
|
162
|
+
onBlocked?(active: boolean): void;
|
|
163
|
+
},
|
|
155
164
|
signal?: AbortSignal,
|
|
156
165
|
) {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
166
|
+
lifecycle.onBlocked?.(true);
|
|
167
|
+
let answers: PlanModeQuestionAnswer[] | undefined;
|
|
168
|
+
try {
|
|
169
|
+
answers = await askPlanModeQuestions(
|
|
170
|
+
questions,
|
|
171
|
+
ctx,
|
|
172
|
+
() => lifecycle.isCurrent() && lifecycle.isEnabled() && !signal?.aborted,
|
|
173
|
+
signal,
|
|
174
|
+
);
|
|
175
|
+
} finally {
|
|
176
|
+
// In `finally` so a throw or a session replacement never leaves Herdr
|
|
177
|
+
// believing this pane is still waiting on someone.
|
|
178
|
+
lifecycle.onBlocked?.(false);
|
|
179
|
+
}
|
|
163
180
|
if (!lifecycle.isCurrent()) {
|
|
164
181
|
return planModeQuestionCancelled(
|
|
165
182
|
questions,
|