pi-autoformat is a Pi extension package that automatically formats files after the agent edits them.
Pi agents often make correct code changes that still fail at commit time because formatting was never run.
That creates a frustrating workflow:
- the agent edits files
- the agent appears done
- pre-commit hooks or CI run formatters later
- files mutate after the fact
- commits fail or the agent has to recover from surprise formatting changes
This package moves formatting earlier in the workflow so the agent is less likely to leave behind unformatted files.
pi-autoformat watches files touched by Pi mutation tools and runs configured formatter commands for just those files.
Formatting is opt-in: no formatters run until you declare chains in your project config.
Touched files are collected during each agent turn and formatted at turn_end, before the agent's next LLM call.
This means files are already formatted before any subsequent git commit command, eliminating pre-commit hook failures.
When formatting actually changes file content or a formatter fails, the extension sends a steering message that the agent sees at the start of its next turn. This lets the agent react — for example, amending a commit or fixing a formatter error — without requiring a separate follow-up turn.
A safety-net flush also runs at agent_end to catch files added via the EventBus or other non-turn paths.
- format only files the agent touched
- flush between turns so commits see formatted files
- notify the agent inline only when formatting actually changed content or failed
- support repository-specific formatter commands and ordered chains
- surface formatter failures without blocking the original edit
- delegate formatter configuration to the formatters themselves —
pi-autoformatinvokes the tool and lets it find its own project config
See docs/configuration.md for the full reference.
pi install npm:@gotgenes/pi-autoformatpi install /absolute/path/to/pi-autoformatCreate .pi/extensions/pi-autoformat/config.json in your project.
No formatters run until you declare chains — this avoids surprises from a default formatter conflicting with your project's chosen tool.
{
"$schema": "https://raw.githubusercontent.com/gotgenes/pi-autoformat/main/schemas/pi-autoformat.schema.json",
"formatters": {
"biome": { "command": ["biome", "check", "--write", "--files-ignore-unknown=true"] }
},
"chains": {
".ts": ["biome"],
".tsx": ["biome"],
".json": ["biome"]
}
}For everything else — formatter chains and fallback groups, wildcard chains, built-in treefmt and treefmt-nix support, format scope, shell mutation coverage, custom mutation tools, the event-bus channel, turn-end steering notifications, and detailed failure output — see docs/configuration.md.
Config files live at two levels, merged with project overriding global:
- global:
~/.pi/agent/extensions/pi-autoformat/config.json - project:
.pi/extensions/pi-autoformat/config.json
The only required fields are the formatters you declare and the chains that map file extensions to them.
Everything else is optional.
formatters: named formatter definitions, each with acommandarray.chains: maps file extensions (e.g.".ts") to an ordered list of formatter names to run.formatScope: boundary for which touched files are eligible for formatting."repoRoot"(default): git root, falling back to cwd when not inside a repo."cwd": strict cwd only.["packages/a", "/abs/path"]: explicit roots, resolved relative to cwd.
commandTimeoutMs: per-formatter timeout in milliseconds. Default:10000.shellMutationDetection: opt-in detection of files mutated bybashcommands (e.g.sed -i,mv,cp). Disabled by default.hideSummariesInTui: set totrueto suppress the success status footer in the interactive TUI.
See docs/configuration.md for the full reference, JSON Schema, and examples.
By default, pi-autoformat reports concise success summaries and per-batch failure summaries.
In the interactive TUI, success renders as a persistent one-line footer status (e.g. ✓ autoformat: 3 files (biome, prettier)).
Failures fire a warning notification and leave an error-styled footer status (e.g. ✗ autoformat: 1 batch failed (prettier)) that persists until the next flush.
Outside the TUI, summaries are written as prefixed log lines on stdout / stderr.
Set hideSummariesInTui to true to suppress the success status line.
To surface failed-run stderr (or stdout+stderr), see formatterOutput.
Purpose. Formatters that run at commit time or in CI mutate files after the agent has moved on, so commits fail and the agent has to recover from changes it did not make. This extension formats the files the agent just touched, between turns, so a commit sees already-formatted files.
In scope. Widening which mutations are noticed (always opt-in and explicit), the formatter dispatch model, flush timing, and the reporting surface.
Non-goals.
- Whole-repository formatting.
Only files the agent touched are formatted — no watchers, no repo-wide rescans, no
git statussweep, each of which trades precision for false positives. - Blocking an edit on a formatter failure. Formatting reports; it does not gate. Enforcement belongs to the pre-commit hooks and CI that can actually block.
- Inferring what a repository "really" uses. No default chains ship and no formatter is auto-detected, because a default conflicting with your chosen tool is worse than doing nothing. Formatters find their own project config; this extension does not model that.
- Per-formatter working directories.
There is deliberately no
baseDir: one directory cannot express a tool serving several subprojects, and it conflicts with batch dispatch. - Git staging or commit orchestration.
Formatted files are not re-staged and
git commitis not intercepted.
Where adjacent requests belong.
Subdirectory-scoped formatters in a monorepo → treefmt / treefmt-nix, declared as a chain step.
Mutations from another extension's tools → the autoformat:touched event channel, or customMutationTools.
Commit-time enforcement → your existing pre-commit hooks, left in place.
pnpm install
pnpm test
pnpm run lintpnpm test runs the fast unit project.
The acceptance tests that spawn the real pi CLI live in a separate acceptance project, run with pnpm run test:acceptance (or pnpm run test:all for both), so they never contend with the other packages' test processes in a workspace-wide run.
See docs/testing.md for the layout of unit, acceptance, and (future) LLM-gated test suites, and how the acceptance harness resolves the pi binary from node_modules/.bin/pi.
MIT
