pi-edit-first 0.1.1 → 0.1.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 +62 -11
- package/extensions/edit-first.ts +7 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
# pi-edit-first
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/pi-edit-first)
|
|
4
|
+
[](https://www.npmjs.com/package/pi-edit-first)
|
|
5
|
+
[](https://github.com/sorinirimies/pi-edit-first/actions/workflows/ci.yml)
|
|
6
|
+
[](LICENSE)
|
|
4
7
|
|
|
5
|
-
|
|
6
|
-
|
|
8
|
+
A [pi](https://github.com/earendil-works/pi-coding-agent) extension that cuts output tokens by making the agent **edit, not rewrite**.
|
|
9
|
+
|
|
10
|
+
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.
|
|
11
|
+
|
|
12
|
+
<img src="examples/vhs/generated/overview.gif" alt="The agent tries to rewrite a whole file; pi-edit-first blocks it and the agent makes a one-line targeted edit instead" width="900">
|
|
7
13
|
|
|
8
14
|
## Rules
|
|
9
15
|
|
|
@@ -22,11 +28,27 @@ pi install git:github.com/sorinirimies/pi-edit-first
|
|
|
22
28
|
|
|
23
29
|
Applies to every pi session (including pi run as an external agent in editors).
|
|
24
30
|
|
|
31
|
+
## Previews
|
|
32
|
+
|
|
33
|
+
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**.
|
|
34
|
+
|
|
35
|
+
**A whole-file rewrite is blocked, the agent makes a targeted edit** (one line added, not 62 rewritten):
|
|
36
|
+
|
|
37
|
+

|
|
38
|
+
|
|
39
|
+
**Hand-written manifests are blocked too** (scaffold with the real tool instead):
|
|
40
|
+
|
|
41
|
+

|
|
42
|
+
|
|
43
|
+
**Commands:** status and block count, `off` / `on`, and `allow <path>` for one deliberate rewrite:
|
|
44
|
+
|
|
45
|
+

|
|
46
|
+
|
|
25
47
|
## Commands
|
|
26
48
|
|
|
27
|
-
- `/edit-first
|
|
28
|
-
- `/edit-first off` / `on
|
|
29
|
-
- `/edit-first allow <path
|
|
49
|
+
- `/edit-first`: status and block count
|
|
50
|
+
- `/edit-first off` / `on`: disable / enable for this session
|
|
51
|
+
- `/edit-first allow <path>`: allow one full rewrite of `<path>` this session
|
|
30
52
|
|
|
31
53
|
## Config (optional)
|
|
32
54
|
|
|
@@ -40,6 +62,7 @@ Applies to every pi session (including pi run as an external agent in editors).
|
|
|
40
62
|
|
|
41
63
|
- A block costs one extra round trip, far cheaper than a whole-file rewrite.
|
|
42
64
|
- Only affects the `write` tool; `edit` is never blocked.
|
|
65
|
+
- Line counts match `wc -l` (a trailing newline is not an extra line).
|
|
43
66
|
- Does not affect other agents (e.g. Zed's native agent).
|
|
44
67
|
|
|
45
68
|
## Security notes
|
|
@@ -64,20 +87,48 @@ just coverage # tests + a coverage table
|
|
|
64
87
|
CI scripts are [nushell](https://www.nushell.sh) (`scripts/`), the same ones locally and in GitHub / Gitea Actions.
|
|
65
88
|
`just --list` shows every task.
|
|
66
89
|
|
|
90
|
+
## Demo recordings
|
|
91
|
+
|
|
92
|
+
The GIFs above live in [`examples/vhs/generated/`](examples/vhs/generated) and are stored with **Git LFS** (`git lfs install` once). They are recorded with [VHS](https://github.com/charmbracelet/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.
|
|
93
|
+
|
|
94
|
+
```sh
|
|
95
|
+
just vhs-all # every tape (examples/vhs/*.tape): needs vhs, ttyd, ffmpeg, pi, bun, python3
|
|
96
|
+
just vhs-tape overview # one tape
|
|
97
|
+
just vhs-list # list the tapes
|
|
98
|
+
just demo # try it yourself in a real pi on the same synthetic data
|
|
99
|
+
```
|
|
100
|
+
|
|
67
101
|
## Releases (automatic)
|
|
68
102
|
|
|
69
103
|
| Workflow | When | What |
|
|
70
104
|
|---|---|---|
|
|
71
|
-
| **CI** | push / PR | quality gate on Linux; tests on macOS
|
|
72
|
-
| **
|
|
73
|
-
| **
|
|
105
|
+
| **CI** | push / PR | quality gate on Linux; tests on macOS and Windows |
|
|
106
|
+
| **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. Then it starts the nightly workflow so the update ships |
|
|
107
|
+
| **Nightly Dependency Update** | every night (GitHub 02:00 UTC, Gitea 02:30) 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 |
|
|
108
|
+
| **Release** | tag `vX.Y.Z` | validates the tag against `package.json`, runs the gate, `npm publish` (idempotent, with provenance), creates the release |
|
|
74
109
|
|
|
75
|
-
|
|
110
|
+
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.
|
|
76
111
|
|
|
77
112
|
Manual release: `just bump patch` (or `minor` / `major` / `X.Y.Z`), then `just release-push`.
|
|
78
113
|
|
|
79
|
-
**Secrets** (repo settings): `NPM_TOKEN`
|
|
114
|
+
**Secrets** (repo settings): `NPM_TOKEN` is an npm *granular access token* with publish rights and "bypass 2FA" (required for CI publishing). Optional: `GH_PAT` (lets a tag push trigger the release itself), `GITEA_TOKEN` (Gitea).
|
|
80
115
|
|
|
81
116
|
Commits follow [Conventional Commits](https://www.conventionalcommits.org); the changelog is generated by git-cliff.
|
|
82
117
|
|
|
118
|
+
## Related plugins
|
|
119
|
+
|
|
120
|
+
Three small [pi](https://github.com/earendil-works/pi-coding-agent) extensions that save tokens without adding anything to the system prompt:
|
|
121
|
+
|
|
122
|
+
| Plugin | What it does |
|
|
123
|
+
|---|---|
|
|
124
|
+
| [**pi-edit-first**](https://github.com/sorinirimies/pi-edit-first) | Blocks whole-file `write` rewrites and hand-written project manifests, steering the agent to targeted `edit` calls and scaffolders |
|
|
125
|
+
| [**pi-read-guard**](https://github.com/sorinirimies/pi-read-guard) | Blocks full reads of large files, steering the agent to `offset`/`limit` or search |
|
|
126
|
+
| [**pi-tokenburn**](https://github.com/sorinirimies/pi-tokenburn) | Live token and cost counter in pi's footer, with charts and budgets |
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
pi install npm:pi-edit-first
|
|
130
|
+
pi install npm:pi-read-guard
|
|
131
|
+
pi install npm:pi-tokenburn
|
|
132
|
+
```
|
|
133
|
+
|
|
83
134
|
MIT
|
package/extensions/edit-first.ts
CHANGED
|
@@ -105,8 +105,8 @@ export function resolveToolPath(
|
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
/**
|
|
108
|
-
* Count lines without loading the file: constant memory, and it stops as soon as
|
|
109
|
-
* `
|
|
108
|
+
* Count lines without loading the file: constant memory, and it stops as soon as `cap` is
|
|
109
|
+
* exceeded. Counts like `wc -l`, plus an unterminated last line. `lines` is exact unless `capped`.
|
|
110
110
|
*/
|
|
111
111
|
export async function countLinesCapped(path: string, cap: number): Promise<{ lines: number; capped: boolean }> {
|
|
112
112
|
const fh = await open(path, "r");
|
|
@@ -114,14 +114,17 @@ export async function countLinesCapped(path: string, cap: number): Promise<{ lin
|
|
|
114
114
|
const buf = Buffer.allocUnsafe(64 * 1024);
|
|
115
115
|
let newlines = 0;
|
|
116
116
|
let size = 0;
|
|
117
|
+
let lastByte = 0;
|
|
117
118
|
for (;;) {
|
|
118
119
|
const { bytesRead } = await fh.read(buf, 0, buf.length, null);
|
|
119
120
|
if (bytesRead === 0) break;
|
|
120
121
|
size += bytesRead;
|
|
121
122
|
for (let i = 0; i < bytesRead; i++) if (buf[i] === 10) newlines++;
|
|
122
|
-
|
|
123
|
+
lastByte = buf[bytesRead - 1];
|
|
124
|
+
if (newlines > cap) return { lines: cap, capped: true }; // at least `newlines` lines: past the cap
|
|
123
125
|
}
|
|
124
|
-
|
|
126
|
+
const lines = size === 0 ? 0 : newlines + (lastByte === 10 ? 0 : 1);
|
|
127
|
+
return lines > cap ? { lines: cap, capped: true } : { lines, capped: false };
|
|
125
128
|
} finally {
|
|
126
129
|
await fh.close();
|
|
127
130
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-edit-first",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "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.",
|
|
5
5
|
"keywords": ["pi-package", "pi-extension", "tokens", "edit", "guard"],
|
|
6
6
|
"license": "MIT",
|