pi-formatter 1.0.0 → 1.0.2

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
@@ -3,11 +3,11 @@
3
3
  A [pi](https://pi.dev) extension that auto-formats files after `write` and
4
4
  `edit` tool calls.
5
5
 
6
- By default, formatting runs once per turn. You can also format after each tool
7
- call or defer formatting until the current session shuts down.
6
+ By default, formatting runs once per prompt after the agent finishes all its
7
+ work and yields control back to you. You can also format after each individual
8
+ tool call or defer formatting until the session shuts down.
8
9
 
9
- This default changed from the previous immediate-per-tool behavior. If you want
10
- the old default, set `"formatMode": "tool"`.
10
+ To format after every individual edit instead, set `"formatMode": "tool"`.
11
11
 
12
12
  ## 📦 Install
13
13
 
@@ -22,19 +22,17 @@ best-effort post-processing. Formatter failures never block tool results.
22
22
 
23
23
  Formatting modes:
24
24
 
25
- - `tool`: format immediately after each successful `write` or `edit` tool
26
- result.
27
- Use this mode when you want the file on disk to stay formatted after every
28
- edit, even while the agent is still working.
29
- - `turn`: collect files touched during the current turn and format them once at
30
- `turn_end`. This is the default.
31
- Use this mode when you want to avoid mid-turn formatter drift while still
32
- keeping files formatted throughout the run.
25
+ - `tool`: format immediately after each successful `write` or `edit` tool call.
26
+ Use this mode when you want files on disk to stay formatted after every edit,
27
+ even while the agent is still working.
28
+ - `prompt`: collect all files touched during the agent run and format them once
29
+ when the agent finishes and yields control back to you. This is the default.
30
+ Use this mode to avoid mid-run formatter interruptions while still getting
31
+ clean files after each response.
33
32
  - `session`: collect files touched during the current session and format them
34
- once at `session_shutdown`.
35
- Use this mode when you want the fewest formatter interruptions and are okay
36
- with formatting only when the session exits, reloads, or switches. Interrupted
37
- runs stay pending until the session ends or changes.
33
+ once at session shutdown, reload, or switch.
34
+ Use this mode when you want the fewest interruptions and are okay with
35
+ formatting only when the session ends.
38
36
 
39
37
  Supported file types:
40
38
 
@@ -61,14 +59,14 @@ folder (default: `~/.pi/agent`, overridable via `PI_CODING_AGENT_DIR`):
61
59
 
62
60
  ```json
63
61
  {
64
- "formatMode": "turn",
62
+ "formatMode": "prompt",
65
63
  "commandTimeoutMs": 10000,
66
64
  "hideCallSummariesInTui": false
67
65
  }
68
66
  ```
69
67
 
70
- - `formatMode`: formatting strategy (`"tool"` | `"turn"` | `"session"`,
71
- default: `"turn"`). Use `"tool"` to restore the old immediate default.
68
+ - `formatMode`: formatting strategy (`"tool"` | `"prompt"` | `"session"`,
69
+ default: `"prompt"`). Use `"tool"` to format after every individual edit.
72
70
  - `commandTimeoutMs`: timeout (ms) per formatter command (default: `10000`)
73
71
  - `hideCallSummariesInTui`: hide formatter pass/fail summaries in the TUI
74
72
  (default: `false`)
@@ -2,11 +2,11 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { getAgentDir } from "@mariozechner/pi-coding-agent";
4
4
 
5
- export type FormatMode = "tool" | "turn" | "session";
5
+ export type FormatMode = "tool" | "prompt" | "session";
6
6
 
7
7
  const DEFAULT_COMMAND_TIMEOUT_MS = 10_000;
8
8
  const DEFAULT_HIDE_CALL_SUMMARIES_IN_TUI = false;
9
- const DEFAULT_FORMAT_MODE: FormatMode = "turn";
9
+ export const DEFAULT_FORMAT_MODE: FormatMode = "prompt";
10
10
  const FORMATTER_CONFIG_FILE = "formatter.json";
11
11
 
12
12
  export type FormatterConfigSnapshot = {
@@ -65,7 +65,7 @@ function parseBooleanValue(value: unknown, defaultValue: boolean): boolean {
65
65
  }
66
66
 
67
67
  function parseFormatMode(value: unknown, defaultValue: FormatMode): FormatMode {
68
- if (value === "tool" || value === "turn" || value === "session") {
68
+ if (value === "tool" || value === "prompt" || value === "session") {
69
69
  return value;
70
70
  }
71
71
 
@@ -79,9 +79,9 @@ function getFormatterSettingItems(
79
79
  id: "formatMode",
80
80
  label: "Format mode",
81
81
  description:
82
- "Choose whether formatting runs after each successful write/edit tool call, once after each turn, or once when the session shuts down.",
82
+ "Choose whether formatting runs after each successful write/edit tool call, once after each prompt completes, or once when the session shuts down.",
83
83
  currentValue: config.formatMode,
84
- values: ["tool", "turn", "session"],
84
+ values: ["tool", "prompt", "session"],
85
85
  },
86
86
  {
87
87
  id: "commandTimeoutMs",
@@ -112,7 +112,7 @@ type FormatterContext = {
112
112
  export default function (pi: ExtensionAPI) {
113
113
  let formatterConfig = loadFormatterConfig();
114
114
  const formatQueueByPath = new Map<string, Promise<void>>();
115
- const pendingTurnPaths = new Set<string>();
115
+ const pendingPromptPaths = new Set<string>();
116
116
  const pendingSessionPaths = new Set<string>();
117
117
 
118
118
  const enqueueFormat = async (
@@ -135,12 +135,27 @@ export default function (pi: ExtensionAPI) {
135
135
  await next;
136
136
  };
137
137
 
138
+ const emitSummaryMessages = (
139
+ messages: string[],
140
+ ctx: FormatterContext,
141
+ ): void => {
142
+ if (
143
+ !ctx.hasUI ||
144
+ formatterConfig.hideCallSummariesInTui ||
145
+ messages.length === 0
146
+ ) {
147
+ return;
148
+ }
149
+
150
+ ctx.ui.notify(messages.join("\n"), "info");
151
+ };
152
+
138
153
  const formatResolvedPath = async (
139
154
  filePath: string,
140
155
  ctx: FormatterContext,
141
- ): Promise<void> => {
156
+ ): Promise<string[]> => {
142
157
  if (!(await pathExists(filePath))) {
143
- return;
158
+ return [];
144
159
  }
145
160
 
146
161
  const showSummaries = !formatterConfig.hideCallSummariesInTui && ctx.hasUI;
@@ -155,6 +170,8 @@ export default function (pi: ExtensionAPI) {
155
170
  console.warn(normalizedMessage);
156
171
  };
157
172
 
173
+ let summaryMessages: string[] = [];
174
+
158
175
  await enqueueFormat(filePath, async () => {
159
176
  const summaries: FormatCallSummary[] = [];
160
177
  const summaryReporter = showSummaries
@@ -188,28 +205,37 @@ export default function (pi: ExtensionAPI) {
188
205
  }
189
206
 
190
207
  const fileLabel = getRelativePathOrAbsolute(filePath, ctx.cwd);
191
-
192
- for (const summary of summaries) {
193
- ctx.ui.notify(formatCallSummary(summary, fileLabel), "info");
194
- }
208
+ summaryMessages = summaries.map((summary) =>
209
+ formatCallSummary(summary, fileLabel),
210
+ );
195
211
  });
212
+
213
+ return summaryMessages;
196
214
  };
197
215
 
198
216
  const flushPaths = async (
199
217
  paths: Set<string>,
200
218
  ctx: FormatterContext,
201
- ): Promise<void> => {
219
+ ): Promise<string[]> => {
202
220
  const batch = [...paths];
203
221
  paths.clear();
204
222
 
223
+ const summaryMessages: string[] = [];
224
+
205
225
  for (const filePath of batch) {
206
- await formatResolvedPath(filePath, ctx);
226
+ summaryMessages.push(...(await formatResolvedPath(filePath, ctx)));
207
227
  }
228
+
229
+ return summaryMessages;
208
230
  };
209
231
 
210
232
  const flushPendingPaths = async (ctx: FormatterContext): Promise<void> => {
211
- await flushPaths(pendingTurnPaths, ctx);
212
- await flushPaths(pendingSessionPaths, ctx);
233
+ const summaryMessages = [
234
+ ...(await flushPaths(pendingPromptPaths, ctx)),
235
+ ...(await flushPaths(pendingSessionPaths, ctx)),
236
+ ];
237
+
238
+ emitSummaryMessages(summaryMessages, ctx);
213
239
  };
214
240
 
215
241
  const reloadFormatterConfig = () => {
@@ -231,36 +257,28 @@ export default function (pi: ExtensionAPI) {
231
257
  }
232
258
 
233
259
  if (formatterConfig.formatMode === "tool") {
234
- await formatResolvedPath(filePath, ctx);
260
+ emitSummaryMessages(await formatResolvedPath(filePath, ctx), ctx);
235
261
  return;
236
262
  }
237
263
 
238
- if (formatterConfig.formatMode === "turn") {
239
- pendingTurnPaths.add(filePath);
264
+ if (formatterConfig.formatMode === "prompt") {
265
+ pendingPromptPaths.add(filePath);
240
266
  return;
241
267
  }
242
268
 
243
269
  pendingSessionPaths.add(filePath);
244
270
  });
245
271
 
246
- pi.on("turn_end", async (_event, ctx) => {
247
- if (pendingTurnPaths.size === 0) {
248
- return;
249
- }
250
-
251
- await flushPaths(pendingTurnPaths, ctx);
252
- });
253
-
254
272
  pi.on("agent_end", async (_event, ctx) => {
255
- if (pendingTurnPaths.size === 0) {
273
+ if (pendingPromptPaths.size === 0) {
256
274
  return;
257
275
  }
258
276
 
259
- await flushPaths(pendingTurnPaths, ctx);
277
+ emitSummaryMessages(await flushPaths(pendingPromptPaths, ctx), ctx);
260
278
  });
261
279
 
262
280
  pi.on("session_switch", async (_event, ctx) => {
263
- if (pendingTurnPaths.size === 0 && pendingSessionPaths.size === 0) {
281
+ if (pendingPromptPaths.size === 0 && pendingSessionPaths.size === 0) {
264
282
  return;
265
283
  }
266
284
 
@@ -268,7 +286,7 @@ export default function (pi: ExtensionAPI) {
268
286
  });
269
287
 
270
288
  pi.on("session_shutdown", async (_event, ctx) => {
271
- if (pendingTurnPaths.size === 0 && pendingSessionPaths.size === 0) {
289
+ if (pendingPromptPaths.size === 0 && pendingSessionPaths.size === 0) {
272
290
  return;
273
291
  }
274
292
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-formatter",
3
- "version": "1.0.0",
4
- "description": "Pi extension that auto-formats files after write/edit tool calls.",
3
+ "version": "1.0.2",
4
+ "description": "Pi extension that auto-formats files.",
5
5
  "type": "module",
6
6
  "files": [
7
7
  "extensions",