pi-edit-first

Pi extension that cuts output tokens: blocks whole-file `write` rewrites and hand-written project manifests, steering the agent to targeted `edit` calls and scaffolders. Zero prompt tokens.

Packages

Package details

extension

Install pi-edit-first from npm and Pi will load the resources declared by the package manifest.

$ pi install npm:pi-edit-first
Package
pi-edit-first
Version
0.2.3
Published
Nov 2, 2026
Downloads
1,366/mo · 75/wk
Author
greenspand
License
MIT
Types
extension
Size
21.2 KB
Dependencies
0 dependencies · 1 peer
Pi manifest JSON
{
  "extensions": [
    "./extensions/edit-first.ts"
  ]
}

Security note

Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.

README

pi-edit-first

npm npm downloads CI License: MIT Audit Fuzz

A pi extension that cuts output tokens by making the agent edit, not rewrite.

Enforcement lives in a tool_call hook, so it adds zero tokens to the system prompt. The model only sees a one-line reason when a call is blocked.

Rules

# Blocks Tells the model
1 write over an existing file longer than maxWriteLines (default 40) use edit with targeted replacements
2 write of Cargo.toml, settings.gradle[.kts], package.json in a dir with no project yet run cargo init / android create / npm init -y

Install

From npm:

pi install npm:pi-edit-first

Or from git:

pi install git:github.com/sorinirimies/pi-edit-first

Applies to every pi session (including pi run as an external agent in editors).

Previews

Recorded from a real pi with only this plugin loaded; a small scripted mock model plays the agent, so you see the real guard reacting to real tool calls.

A whole-file rewrite is blocked, the agent makes a targeted edit (one line added, not 62 rewritten):

The write is blocked and the agent falls back to a one-line edit

Hand-written manifests are blocked too (scaffold with the real tool instead):

Writing Cargo.toml by hand is blocked: run cargo init

Commands: status and block count, off / on, and allow <path> for one deliberate rewrite:

/edit-first status, off, on and allow

Commands

  • /edit-first: status and block count
  • /edit-first off / on: disable / enable for this session
  • /edit-first allow <path>: allow the next full rewrite of <path> (one-shot; run it again for another)

Config (optional)

~/.pi/agent/edit-first.json:

{ "maxWriteLines": 40, "scaffold": true, "ignore": ["CHANGELOG.md", "*.lock"] }

ignore globs (* = anything but /, ? = one such character) match the path relative to the working directory or the file name, however the path is spelled (./x, @x, absolute, \).

Notes

  • A block costs one extra round trip, far cheaper than a whole-file rewrite.
  • Only affects the write tool; edit is never blocked.
  • Line counts match wc -l (a trailing newline is not an extra line).
  • Does not affect other agents (e.g. Zed's native agent).

Security notes

  • A token saver, not a sandbox. It guards pi's write tool. An agent can still write files through bash; the guard exists to keep wasteful rewrites out of your context, not to confine the agent.
  • Same path as the tool. Paths are resolved exactly like pi's own tools (@file, ~/file, file://, Unicode spaces, Windows shell paths), so those spellings can't slip past it. A contract test compares it with pi's real resolveToCwd.
  • Fails open. pi blocks a tool when a tool_call handler throws, so any unexpected error here means "allow": the guard can never stop legitimate work.
  • Constant memory. Line counts are streamed (64 KB chunks, early exit), never the whole file.
  • No network access, no shell commands, zero runtime dependencies.

Development

bun install
just check          # typecheck + tests + pack check + nushell tests (what CI runs)
just test           # bun test only (with coverage)
just coverage       # tests + a coverage table
just push-all       # push main to every configured remote (origin + the Gitea instances); `just --list` has pull-all, push-tags-all, release-all, the *-force and sync-gitea-* variants

Coverage: every bun test collects coverage (bunfig.toml) and fails below 95% lines / 95% functions. CI shows the table on the run page and uploads lcov.info.

CI scripts are nushell (scripts/), the same ones locally and in GitHub / Gitea Actions. just --list shows every task.

Demo recordings

The GIFs above live in examples/vhs/generated/ and are stored with Git LFS (git lfs install once). They are recorded with VHS from a real pi that loads only this extension, on synthetic data (examples/vhs/fixture.sh), never from real files, sessions or credentials. The agent is a scripted mock model (examples/vhs/mock-llm.ts), so the recording is repeatable.

just vhs-all          # every tape (examples/vhs/*.tape): needs vhs, ttyd, ffmpeg, pi, bun, python3
just vhs-tape overview  # one tape
just vhs-list         # list the tapes
just demo             # try it yourself in a real pi on the same synthetic data

Releases (automatic)

Workflow When What
CI push / PR quality gate on Linux; tests on macOS and Windows
Fuzz nightly (GitHub 04:00 UTC, Gitea 04:30), and when the extension changes seeded randomised robustness tests (tests/fuzz.test.ts), 200 000 runs per property; a failure prints the seed that reproduces it
Security nightly (GitHub 03:00 UTC, Gitea 03:30), and when dependencies change bun audit: fails on any known vulnerability
Auto-merge library updates CI finished on a Dependabot PR patch and minor updates (GitHub Actions) are merged automatically, but only after CI is green on that exact commit; a major update waits for you. Needs the GH_PAT secret: action bumps change .github/workflows/*, which GITHUB_TOKEN may not merge
Gitea Actions Update Mondays 03:15 UTC Gitea has no Dependabot: moves the sha pins in .gitea/workflows to the newest release of the same major and commits ci(deps); a new major is only reported
Nightly Dependency Update every night (GitHub 02:00 UTC) and after each auto-merge bun update within ranges, verify on all platforms, commit chore(deps); then build, tag and publish a new patch whenever a library was upgraded or merged, or feat/fix/perf commits are waiting since the last tag
Mirror GitHub (Gitea) hourly Gitea has no nightly of its own: fast-forwards its main to GitHub's and pushes the new tags (starting Gitea's release for each), so the hosts never diverge; fails loudly if they did
Release tag vX.Y.Z, or run it with a bump validates the tag against package.json, runs the gate, npm publish (idempotent, with provenance), creates the release

So library updates need no human: they are merged once CI passes, built, versioned and published as a patch. A downgrade, a failing check, or a major update stops the chain and waits for review.

Release from CI: Actions → Release → Run workflow with a bump (patch, minor, major), or just release-ci minor: runs the gate, bumps, writes the changelog, tags, pushes and publishes. Manual release: just release patch (or minor / major / X.Y.Z) bumps, tags and pushes to GitHub; just release-all patch pushes to every configured remote; just push-release-all pushes an existing bump.

Secrets (repo settings): NPM_TOKEN is an npm granular access token with publish rights and "bypass 2FA" (required for CI publishing). GH_PAT (classic PAT, repo + workflow: needed to auto-merge GitHub Actions bumps; also lets a tag push trigger the release itself). Optional: GITEA_TOKEN (Gitea).

Commits follow Conventional Commits; the changelog is generated by git-cliff.

Related plugins

Four small pi extensions that save tokens without adding anything to the system prompt:

Plugin What it does
pi-edit-first Blocks whole-file write rewrites and hand-written project manifests, steering the agent to targeted edit calls and scaffolders
pi-read-guard Blocks full reads of large files, steering the agent to offset/limit or search
pi-tokenburn Live token and cost counter in pi's footer, with charts and budgets
pi-context-diet Drops stale tool results from the context when the prompt cache is already cold, so long sessions cost less
pi install npm:pi-edit-first
pi install npm:pi-read-guard
pi install npm:pi-tokenburn
pi install npm:pi-context-diet

MIT