@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 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 companion `pi-plan-mode` **skill**, loaded on demand, carrying the plan-crafting craft the prompt only points at.
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 companion skill
121
+ ## 📚 The plan-craft doc
122
122
 
123
- The package ships a `pi-plan-mode` skill (`skills/pi-plan-mode/SKILL.md`) alongside the extension. The system prompt is the enforcement surface and stays deliberately short; the skill is 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. One line in the planning prompt names it, and the model loads the body when it judges it needs it.
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
- Skill and extension version as one artifact: a skill describing a mode the installed extension does not have is a coupling failure waiting to happen.
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
- ├── skills/pi-plan-mode/ # Companion plan-crafting skill
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.6.0",
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
- "skills",
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
- { isCurrent: menu.isCurrent, isEnabled: () => state.enabled },
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
- - Read the pi-plan-mode skill before planning if it is available: it carries the plan-crafting craft — decision-completeness, exploring before asking, question quality, and what a finished plan contains.
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.
@@ -151,15 +151,32 @@ export function normalizePlanModeQuestionParams(
151
151
  export async function answerPlanModeQuestions(
152
152
  questions: PlanModeQuestion[],
153
153
  ctx: ExtensionContext,
154
- lifecycle: { isCurrent(): boolean; isEnabled(): boolean },
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
- const answers = await askPlanModeQuestions(
158
- questions,
159
- ctx,
160
- () => lifecycle.isCurrent() && lifecycle.isEnabled() && !signal?.aborted,
161
- signal,
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,