@narumitw/pi-plan-mode 0.27.0 → 0.31.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/README.md CHANGED
@@ -180,7 +180,8 @@ This extension maps Codex's `ModeKind::Plan` behavior onto Pi's extension API:
180
180
  ```txt
181
181
  extensions/pi-plan-mode/
182
182
  ├── src/
183
- │ ├── plan-mode.ts # Pi entrypoint and mode state
183
+ │ ├── index.ts # Pi package entrypoint
184
+ │ ├── plan-mode.ts # Extension registration and mode state
184
185
  │ └── *.ts # Package-local prompt, policy, question, and message modules
185
186
  ├── README.md
186
187
  ├── LICENSE
@@ -188,12 +189,12 @@ extensions/pi-plan-mode/
188
189
  └── package.json
189
190
  ```
190
191
 
191
- Only `plan-mode.ts` is a Pi entrypoint; the other source modules are internal. The package exposes its Pi extension through `package.json`:
192
+ `index.ts` is the Pi entrypoint and forwards to `plan-mode.ts`; the other source modules are internal. The package exposes its Pi extension through `package.json`:
192
193
 
193
194
  ```json
194
195
  {
195
196
  "pi": {
196
- "extensions": ["./src/plan-mode.ts"]
197
+ "extensions": ["./src/index.ts"]
197
198
  }
198
199
  }
199
200
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@narumitw/pi-plan-mode",
3
- "version": "0.27.0",
3
+ "version": "0.31.0",
4
4
  "description": "Pi extension that adds a Codex-like read-only /plan collaboration mode.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -19,7 +19,7 @@
19
19
  ],
20
20
  "pi": {
21
21
  "extensions": [
22
- "./src/plan-mode.ts"
22
+ "./src/index.ts"
23
23
  ]
24
24
  },
25
25
  "scripts": {
@@ -28,8 +28,8 @@
28
28
  "typecheck": "tsc --noEmit"
29
29
  },
30
30
  "devDependencies": {
31
- "@biomejs/biome": "2.5.3",
32
- "@earendil-works/pi-coding-agent": "0.80.10",
31
+ "@biomejs/biome": "2.5.5",
32
+ "@earendil-works/pi-coding-agent": "0.82.1",
33
33
  "typescript": "7.0.2"
34
34
  },
35
35
  "repository": {
@@ -22,9 +22,7 @@ export const PLAN_MODE_COMPLETE_PARAMS = {
22
22
  },
23
23
  } as const;
24
24
 
25
- type NormalizePlanModeCompletionResult =
26
- | { ok: true; plan: string }
27
- | { ok: false; error: string };
25
+ type NormalizePlanModeCompletionResult = { ok: true; plan: string } | { ok: false; error: string };
28
26
 
29
27
  export function normalizePlanModeCompletion(input: unknown): NormalizePlanModeCompletionResult {
30
28
  if (!isRecord(input) || typeof input.plan !== "string") {
package/src/index.ts ADDED
@@ -0,0 +1 @@
1
+ export { default } from "./plan-mode.js";
@@ -23,7 +23,11 @@ type PlanModeQuestionAnswer = {
23
23
  optionIndex?: number;
24
24
  };
25
25
 
26
- type PlanModeQuestionReason = "cancelled" | "ui_unavailable" | "plan_mode_inactive" | "invalid_input";
26
+ type PlanModeQuestionReason =
27
+ | "cancelled"
28
+ | "ui_unavailable"
29
+ | "plan_mode_inactive"
30
+ | "invalid_input";
27
31
 
28
32
  type PlanModeQuestionDetails = {
29
33
  cancelled: boolean;
@@ -47,8 +51,14 @@ export const PLAN_MODE_QUESTION_PARAMS = {
47
51
  additionalProperties: false,
48
52
  required: ["id", "header", "question", "options"],
49
53
  properties: {
50
- id: { type: "string", description: "Stable identifier for mapping answers (snake_case)." },
51
- header: { type: "string", description: "Short header label shown in the UI (12 or fewer chars)." },
54
+ id: {
55
+ type: "string",
56
+ description: "Stable identifier for mapping answers (snake_case).",
57
+ },
58
+ header: {
59
+ type: "string",
60
+ description: "Short header label shown in the UI (12 or fewer chars).",
61
+ },
52
62
  question: { type: "string", description: "Single-sentence prompt shown to the user." },
53
63
  options: {
54
64
  type: "array",
@@ -79,7 +89,9 @@ type NormalizePlanModeQuestionParamsResult =
79
89
  | { ok: true; questions: PlanModeQuestion[] }
80
90
  | { ok: false; error: string };
81
91
 
82
- export function normalizePlanModeQuestionParams(input: unknown): NormalizePlanModeQuestionParamsResult {
92
+ export function normalizePlanModeQuestionParams(
93
+ input: unknown,
94
+ ): NormalizePlanModeQuestionParamsResult {
83
95
  if (!isRecord(input) || !Array.isArray(input.questions)) {
84
96
  return { ok: false, error: "questions must be an array" };
85
97
  }
@@ -96,7 +108,10 @@ export function normalizePlanModeQuestionParams(input: unknown): NormalizePlanMo
96
108
  const header = stringField(rawQuestion.header);
97
109
  const question = stringField(rawQuestion.question);
98
110
  if (!id || !header || !question) {
99
- return { ok: false, error: `question ${questionIndex + 1} requires non-empty id, header, and question` };
111
+ return {
112
+ ok: false,
113
+ error: `question ${questionIndex + 1} requires non-empty id, header, and question`,
114
+ };
100
115
  }
101
116
  if (!Array.isArray(rawQuestion.options)) {
102
117
  return { ok: false, error: `question ${questionIndex + 1} options must be an array` };
@@ -114,7 +129,10 @@ export function normalizePlanModeQuestionParams(input: unknown): NormalizePlanMo
114
129
  }
115
130
  const label = stringField(rawOption.label);
116
131
  if (!label) {
117
- return { ok: false, error: `question ${questionIndex + 1} option ${optionIndex + 1} requires a label` };
132
+ return {
133
+ ok: false,
134
+ error: `question ${questionIndex + 1} option ${optionIndex + 1} requires a label`,
135
+ };
118
136
  }
119
137
  const description = stringField(rawOption.description);
120
138
  if (!description) {
@@ -138,7 +156,10 @@ export async function askPlanModeQuestions(
138
156
  for (const question of questions) {
139
157
  const choices = question.options.map(formatPlanModeQuestionChoice);
140
158
  const otherChoice = `${question.options.length + 1}. Other (free-form)`;
141
- const choice = await ctx.ui.select(`${question.header}: ${question.question}`, [...choices, otherChoice]);
159
+ const choice = await ctx.ui.select(`${question.header}: ${question.question}`, [
160
+ ...choices,
161
+ otherChoice,
162
+ ]);
142
163
  if (!choice) return undefined;
143
164
  if (choice === otherChoice) {
144
165
  const customAnswer = (await ctx.ui.editor(question.question, ""))?.trim();
@@ -171,9 +192,14 @@ function formatPlanModeQuestionChoice(option: PlanModeQuestionOption, index: num
171
192
  return `${index + 1}. ${option.label}${option.description ? ` — ${option.description}` : ""}`;
172
193
  }
173
194
 
174
- export function planModeQuestionAnswered(questions: PlanModeQuestion[], answers: PlanModeQuestionAnswer[]) {
195
+ export function planModeQuestionAnswered(
196
+ questions: PlanModeQuestion[],
197
+ answers: PlanModeQuestionAnswer[],
198
+ ) {
175
199
  return {
176
- content: [{ type: "text" as const, text: formatPlanModeQuestionPayload({ cancelled: false, answers }) }],
200
+ content: [
201
+ { type: "text" as const, text: formatPlanModeQuestionPayload({ cancelled: false, answers }) },
202
+ ],
177
203
  details: { cancelled: false, questions, answers } satisfies PlanModeQuestionDetails,
178
204
  };
179
205
  }
@@ -184,7 +210,12 @@ export function planModeQuestionCancelled(
184
210
  message: string,
185
211
  ) {
186
212
  return {
187
- content: [{ type: "text" as const, text: formatPlanModeQuestionPayload({ cancelled: true, reason, message }) }],
213
+ content: [
214
+ {
215
+ type: "text" as const,
216
+ text: formatPlanModeQuestionPayload({ cancelled: true, reason, message }),
217
+ },
218
+ ],
188
219
  details: { cancelled: true, reason, questions } satisfies PlanModeQuestionDetails,
189
220
  };
190
221
  }
package/src/state.ts CHANGED
@@ -1,16 +1,11 @@
1
1
  import {
2
- PLAN_MODE_COMPLETE_TOOL_NAME,
3
2
  normalizePlanModeCompletion,
3
+ PLAN_MODE_COMPLETE_TOOL_NAME,
4
4
  planFromCompletionDetails,
5
5
  } from "./completion-tool.js";
6
- import {
7
- PLAN_MODE_THINKING_LEVELS,
8
- type PlanModeFixedThinkingLevel,
9
- } from "./settings.js";
6
+ import { PLAN_MODE_THINKING_LEVELS, type PlanModeFixedThinkingLevel } from "./settings.js";
10
7
 
11
- export type PlanCompletionSource =
12
- | typeof PLAN_MODE_COMPLETE_TOOL_NAME
13
- | "legacy_proposed_plan";
8
+ export type PlanCompletionSource = typeof PLAN_MODE_COMPLETE_TOOL_NAME | "legacy_proposed_plan";
14
9
 
15
10
  export interface PlanModeState {
16
11
  enabled: boolean;
@@ -49,23 +44,19 @@ export function restorePlanModeState(entries: unknown[], stateEntryType: string)
49
44
  if (!isRecord(entry?.data)) return { enabled: false, awaitingAction: false };
50
45
 
51
46
  const enabled = entry.data.enabled === true;
52
- const persistedSource = enabled
53
- ? planCompletionSource(entry.data.latestPlanSource)
54
- : undefined;
47
+ const persistedSource = enabled ? planCompletionSource(entry.data.latestPlanSource) : undefined;
55
48
  const persistedPlan = enabled
56
49
  ? normalizePersistedPlan(entry.data.latestPlan, persistedSource)
57
50
  : undefined;
58
51
  const recoveredPlan =
59
- enabled && !persistedPlan
60
- ? latestCompletionPlan(branch.slice(stateEntryIndex + 1))
61
- : undefined;
52
+ enabled && !persistedPlan ? latestCompletionPlan(branch.slice(stateEntryIndex + 1)) : undefined;
62
53
  const latestPlan = persistedPlan ?? recoveredPlan;
63
54
  return {
64
55
  enabled,
65
56
  latestPlan,
66
57
  latestPlanSource: enabled
67
- ? (persistedPlan ? persistedSource : undefined) ??
68
- (recoveredPlan ? PLAN_MODE_COMPLETE_TOOL_NAME : undefined)
58
+ ? ((persistedPlan ? persistedSource : undefined) ??
59
+ (recoveredPlan ? PLAN_MODE_COMPLETE_TOOL_NAME : undefined))
69
60
  : undefined,
70
61
  awaitingAction: enabled && latestPlan !== undefined,
71
62
  selectedToolNames: stringArray(entry.data.selectedToolNames),
@@ -73,12 +64,8 @@ export function restorePlanModeState(entries: unknown[], stateEntryType: string)
73
64
  previousThinkingLevel: enabled
74
65
  ? fixedThinkingLevel(entry.data.previousThinkingLevel)
75
66
  : undefined,
76
- appliedThinkingLevel: enabled
77
- ? fixedThinkingLevel(entry.data.appliedThinkingLevel)
78
- : undefined,
79
- manualThinkingLevel: enabled
80
- ? fixedThinkingLevel(entry.data.manualThinkingLevel)
81
- : undefined,
67
+ appliedThinkingLevel: enabled ? fixedThinkingLevel(entry.data.appliedThinkingLevel) : undefined,
68
+ manualThinkingLevel: enabled ? fixedThinkingLevel(entry.data.manualThinkingLevel) : undefined,
82
69
  };
83
70
  }
84
71