jevprune 0.1.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/LICENSE +21 -0
- package/README.md +257 -0
- package/dist/cli.js +2969 -0
- package/dist/index.d.ts +462 -0
- package/dist/index.js +2288 -0
- package/package.json +67 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ibrahemid
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
# jevprune
|
|
2
|
+
|
|
3
|
+
Keeps the lines of a command's output that matter for the task you are working on. Every kept line is printed exactly as the command wrote it. The full output is saved on disk and any dropped range can be printed back.
|
|
4
|
+
|
|
5
|
+
Built for coding agents that run `npm test`, `cargo build`, `pytest` or `docker compose up` and push thousands of lines into their context. Line relevance is decided per line by [Jev](https://typesafe.ai), TypeSafe's model, with a fixed set of lines kept before Jev sees anything: the last 40 lines, every error signature, and the lines around it.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm install -g jevprune
|
|
11
|
+
export TYPESAFE_API_KEY=... # early-access key from typesafe.ai
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Claude Code plugin (wraps every Bash call automatically):
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
claude plugin marketplace add ibrahemid/jevprune
|
|
18
|
+
claude plugin install jevprune@jevprune
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
The plugin calls the globally installed `jevprune` binary. Without the binary the hook does nothing and commands run as before. Without a key, commands are still wrapped and pruning falls back to head and tail.
|
|
22
|
+
|
|
23
|
+
## Use
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
jevprune run --task "fix the failing auth test" -- npm test
|
|
27
|
+
npm test 2>&1 | jevprune select --task "fix the failing auth test"
|
|
28
|
+
jevprune show <run-id> --lines 120-531
|
|
29
|
+
jevprune gain
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
`run` executes the command with the given arguments (no shell), captures stdout and stderr in arrival order, prints the kept lines and exits with the command's exit code. `select` prunes a file or stdin. `show` prints an exact range of a saved run. `gain` sums what pruning saved so far on this machine.
|
|
33
|
+
|
|
34
|
+
The task comes from `--task`, else `JEVPRUNE_TASK`, else (inside the Claude Code plugin) the first prompt of the session, else the command itself.
|
|
35
|
+
|
|
36
|
+
## Demo
|
|
37
|
+
|
|
38
|
+

|
|
39
|
+
|
|
40
|
+
The fixture `test/fixtures/npm-test.log` is a 2,979-line vitest run with one failing test in `src/auth/login.test.ts` and a handful of slow tests. Same log, two tasks.
|
|
41
|
+
|
|
42
|
+
```sh
|
|
43
|
+
jevprune select --task "fix the failing auth test" --command "npm test" --file test/fixtures/npm-test.log
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
[jevprune: 53 lines dropped, run mu6ln7nv-3cb9, lines 1-53]
|
|
48
|
+
stderr | src/auth/oauth.test.ts > oauth > paginates the sort order
|
|
49
|
+
[jevprune: 456 lines dropped, run mu6ln7nv-3cb9, lines 55-510]
|
|
50
|
+
✓ src/auth/login.test.ts > login > merges a stale session 3ms
|
|
51
|
+
✓ src/auth/login.test.ts > login > clears unicode input 3ms
|
|
52
|
+
✓ src/auth/login.test.ts > login > ignores the previous state 2ms
|
|
53
|
+
× src/auth/login.test.ts > login > rejects an expired session token 14ms
|
|
54
|
+
✓ src/auth/login.test.ts > login > updates the locale 3ms
|
|
55
|
+
✓ src/auth/login.test.ts > login > keeps the default value 3ms
|
|
56
|
+
✓ src/auth/login.test.ts > login > computes a network failure 18ms
|
|
57
|
+
[jevprune: 724 lines dropped, run mu6ln7nv-3cb9, lines 518-1241]
|
|
58
|
+
stderr | src/notifications/push.test.ts > push > paginates duplicate entries
|
|
59
|
+
Warning: An update to Form inside a test was not wrapped in act(...).
|
|
60
|
+
[jevprune: 1696 lines dropped, run mu6ln7nv-3cb9, lines 1244-2939]
|
|
61
|
+
✓ src/components/toast.test.ts > toast > restores a stale session 5ms
|
|
62
|
+
✓ src/components/toast.test.ts > toast > ignores a negative quantity 9ms
|
|
63
|
+
✓ src/components/toast.test.ts > toast > emits an empty input 9ms
|
|
64
|
+
✓ src/components/toast.test.ts > toast > emits an unknown id 12ms
|
|
65
|
+
✓ src/components/toast.test.ts > toast > formats the previous state 1ms
|
|
66
|
+
✓ src/components/toast.test.ts > toast > filters the cached result 4ms
|
|
67
|
+
✓ src/components/toast.test.ts > toast > ignores a network failure 18ms
|
|
68
|
+
✓ src/components/toast.test.ts > toast > clears a large payload 18ms
|
|
69
|
+
✓ src/components/toast.test.ts > toast > merges a partial update 0ms
|
|
70
|
+
✓ src/components/toast.test.ts > toast > clears leading whitespace 7ms
|
|
71
|
+
✓ src/components/toast.test.ts > toast > filters a stale session 25ms
|
|
72
|
+
✓ src/components/toast.test.ts > toast > returns a negative quantity 12ms
|
|
73
|
+
|
|
74
|
+
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Failed Tests 1 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
|
|
75
|
+
|
|
76
|
+
FAIL src/auth/login.test.ts > login > rejects an expired session token
|
|
77
|
+
AssertionError: expected 200 to be 401 // Object.is equality
|
|
78
|
+
|
|
79
|
+
- Expected
|
|
80
|
+
+ Received
|
|
81
|
+
|
|
82
|
+
- 401
|
|
83
|
+
+ 200
|
|
84
|
+
|
|
85
|
+
❯ src/auth/login.test.ts:88:29
|
|
86
|
+
86| const response = await login({ token: expiredToken });
|
|
87
|
+
87|
|
|
88
|
+
88| expect(response.status).toBe(401);
|
|
89
|
+
| ^
|
|
90
|
+
89| expect(response.body.error).toBe("session expired");
|
|
91
|
+
90| });
|
|
92
|
+
|
|
93
|
+
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
Test Files 1 failed | 94 passed (95)
|
|
97
|
+
Tests 1 failed | 2914 passed (2915)
|
|
98
|
+
Start at 09:12:04
|
|
99
|
+
Duration 41.20s (transform 4.11s, setup 1.62s, collect 12.30s, tests 33.94s, environment 9.81s)
|
|
100
|
+
|
|
101
|
+
jevprune: 2,979 → 54 lines, full output ~/.jevprune/runs/mu6ln7nv-3cb9.log
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
```sh
|
|
105
|
+
jevprune select --task "why is the build slow" --command "npm test" --file test/fixtures/npm-test.log
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Lines 53 to 78 of the 347 it printed, and the footer:
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
✓ src/billing/refunds.test.ts > refunds > rejects a negative quantity 12ms
|
|
112
|
+
✓ src/billing/refunds.test.ts > refunds > clears a stale session 1170ms
|
|
113
|
+
[jevprune: 4 lines dropped, run mu6ln5j9-4968, lines 844-847]
|
|
114
|
+
✓ src/billing/refunds.test.ts > refunds > keeps an empty input 1282ms
|
|
115
|
+
✓ src/billing/refunds.test.ts > refunds > updates a trailing slash 1083ms
|
|
116
|
+
✓ src/billing/refunds.test.ts > refunds > ignores an empty input 2ms
|
|
117
|
+
✓ src/billing/refunds.test.ts > refunds > sorts an expired token 7ms
|
|
118
|
+
✓ src/billing/refunds.test.ts > refunds > sorts a missing field 2216ms
|
|
119
|
+
✓ src/billing/refunds.test.ts > refunds > renders a network failure 25ms
|
|
120
|
+
✓ src/billing/refunds.test.ts > refunds > parses an expired token 1904ms
|
|
121
|
+
✓ src/billing/refunds.test.ts > refunds > merges an empty input 7ms
|
|
122
|
+
✓ src/billing/refunds.test.ts > refunds > parses the sort order 1013ms
|
|
123
|
+
[jevprune: 9 lines dropped, run mu6ln5j9-4968, lines 857-865]
|
|
124
|
+
✓ src/hooks/use-form.test.ts > use-form > keeps a network failure 1610ms
|
|
125
|
+
✓ src/hooks/use-form.test.ts > use-form > paginates a partial update 2095ms
|
|
126
|
+
✓ src/hooks/use-form.test.ts > use-form > keeps the cached result 2512ms
|
|
127
|
+
✓ src/hooks/use-form.test.ts > use-form > parses the locale 466ms
|
|
128
|
+
✓ src/hooks/use-form.test.ts > use-form > updates a stale session 2ms
|
|
129
|
+
✓ src/hooks/use-form.test.ts > use-form > merges a negative quantity 1ms
|
|
130
|
+
✓ src/hooks/use-form.test.ts > use-form > restores an empty input 2073ms
|
|
131
|
+
✓ src/hooks/use-form.test.ts > use-form > filters a missing field 2203ms
|
|
132
|
+
[jevprune: 4 lines dropped, run mu6ln5j9-4968, lines 874-877]
|
|
133
|
+
✓ src/hooks/use-form.test.ts > use-form > paginates the previous state 483ms
|
|
134
|
+
[jevprune: 8 lines dropped, run mu6ln5j9-4968, lines 879-886]
|
|
135
|
+
✓ src/hooks/use-form.test.ts > use-form > validates a large payload 1372ms
|
|
136
|
+
[jevprune: 6 lines dropped, run mu6ln5j9-4968, lines 888-893]
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
```
|
|
140
|
+
jevprune: 2,979 → 347 lines, full output ~/.jevprune/runs/mu6ln5j9-4968.log
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
The first task keeps the failure block and the auth lines around it. The second keeps the timing lines instead. Neither view rewrote a line. A dropped section comes back exactly:
|
|
144
|
+
|
|
145
|
+
```sh
|
|
146
|
+
jevprune show mu6ln7nv-3cb9 --lines 55-57
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
```
|
|
150
|
+
[deprecation] `fetchJson` is deprecated, use `http.get` instead
|
|
151
|
+
|
|
152
|
+
✓ src/api/errors.test.ts > errors > merges the sort order 3ms
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## What is kept
|
|
156
|
+
|
|
157
|
+
1. 60 lines or fewer: everything, untouched, no request made.
|
|
158
|
+
2. The command failed (non-zero exit or signal): everything, untouched. Failure output is evidence and is never pruned.
|
|
159
|
+
3. Otherwise, before Jev: the last 40 lines, every line matching an error signature (`FAIL`, `error:`, `AssertionError`, `Traceback`, stack frames, `exited with code`, and similar), and the 3 lines on each side of it. Repeated identical signature lines count once.
|
|
160
|
+
4. The remaining lines go to Jev in windows, one yes/no question per line: is this line needed for the task? Lines scoring at or above the threshold (default 0.3) are kept.
|
|
161
|
+
5. Dropped runs shorter than 3 lines are kept. Longer ones collapse into one marker:
|
|
162
|
+
|
|
163
|
+
```
|
|
164
|
+
[jevprune: 412 lines dropped, run m1xk2p7a-3f9c, lines 120-531]
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
The last line of the output is the footer:
|
|
168
|
+
|
|
169
|
+
```
|
|
170
|
+
jevprune: 3,104 → 88 lines, exit 0, full output ~/.jevprune/runs/m1xk2p7a-3f9c.log
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## The exact-text guarantee
|
|
174
|
+
|
|
175
|
+
jevprune never rewrites, summarizes or reorders a line. Every line it prints is byte-identical to a line the command wrote, in the original order. The only text it adds is the collapse marker and the footer. The full output is on disk before any decision is made, and `jevprune show <id> --lines A-B` prints any range back exactly.
|
|
176
|
+
|
|
177
|
+
This is not lossless. A dropped line may have mattered. Retained text is exact; dropped text is recoverable; a wrong drop is possible. When a line you expect is missing, run `show` on the marker's range before concluding it is absent.
|
|
178
|
+
|
|
179
|
+
## Fallback
|
|
180
|
+
|
|
181
|
+
No key, a rejected key, a rate limit, an outage, a timeout or a malformed answer never fails the command. jevprune keeps the deterministic set plus the first 40 and last 40 lines and says so in the footer:
|
|
182
|
+
|
|
183
|
+
```
|
|
184
|
+
jevprune: fallback (no Jev: timeout), 3,104 → 83 lines, exit 0, full output ~/.jevprune/runs/<id>.log
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## Claude Code plugin
|
|
188
|
+
|
|
189
|
+
A `PreToolUse` hook on `Bash` rewrites the command to `jevprune run --hook --transcript <path> -- bash -c '<command>'`. `bash -c` keeps pipelines, redirects and quoting as the agent wrote them; jevprune only owns capture and selection.
|
|
190
|
+
|
|
191
|
+
The hook leaves a command alone when it is already wrapped, runs in the background, ends with `&`, changes shell state (`cd`, `export`, `source`, `.`, `unset`, `alias`, `set`, `eval`, `exec`, `pushd`, `popd`), starts an interactive program (`vim`, `less`, `ssh`, `sudo`, `top`, `tmux`, `claude`, and similar), starts a REPL or shell with no arguments (`python`, `node`, `bash`, `psql`, `gh`), runs `docker exec` or `docker run` with a terminal flag, follows a file with `tail -f`, or starts with an allowlisted prefix (`cd`, `ls`, `pwd`, `echo`, `git status`, `git add`, `git commit`, `git log`, `git diff --stat`, `which`, `mkdir`, `touch`, `true`, `test`, `[`).
|
|
192
|
+
|
|
193
|
+
Set `"autoWrap": false` in `~/.jevprune/config.json` to turn the rewrite off. The bundled skill then tells the agent to call `jevprune run` itself for long commands.
|
|
194
|
+
|
|
195
|
+
## Configuration
|
|
196
|
+
|
|
197
|
+
`~/.jevprune/config.json`, all keys optional:
|
|
198
|
+
|
|
199
|
+
| key | default | meaning |
|
|
200
|
+
|---|---|---|
|
|
201
|
+
| `threshold` | `0.3` | minimum Jev score to keep a line |
|
|
202
|
+
| `fastPathLines` | `60` | outputs up to this many lines pass through untouched |
|
|
203
|
+
| `tailLines` | `40` | lines always kept at the end |
|
|
204
|
+
| `headLines` | `40` | lines kept at the start in fallback |
|
|
205
|
+
| `contextLines` | `3` | lines kept around each error signature |
|
|
206
|
+
| `minCollapseLines` | `3` | shorter dropped runs are kept instead of collapsed |
|
|
207
|
+
| `windowTokens` | `25000` | estimated token budget per Jev request |
|
|
208
|
+
| `windowTimeoutMs` | `10000` | per-window timeout before fallback |
|
|
209
|
+
| `concurrency` | `4` | Jev requests in flight |
|
|
210
|
+
| `maxPruneBytes` | `16777216` | larger outputs use fallback on the head and tail |
|
|
211
|
+
| `retention.maxRuns` | `200` | saved runs kept |
|
|
212
|
+
| `retention.maxBytes` | `268435456` | total bytes of saved runs kept |
|
|
213
|
+
| `autoWrap` | `true` | plugin rewrites Bash commands |
|
|
214
|
+
| `allowlist` | see above | command prefixes the plugin never wraps |
|
|
215
|
+
|
|
216
|
+
Environment: `TYPESAFE_API_KEY`, `TYPESAFE_BASE_URL`, `JEVPRUNE_TASK`, `JEVPRUNE_HOME` (default `~/.jevprune`).
|
|
217
|
+
|
|
218
|
+
The defaults were set on real outputs from one machine (a vitest run, a Next.js build, a docker compose startup, a pytest run, a cargo build). They are a starting point.
|
|
219
|
+
|
|
220
|
+
## Library
|
|
221
|
+
|
|
222
|
+
```ts
|
|
223
|
+
import { pruneOutput } from "jevprune";
|
|
224
|
+
|
|
225
|
+
const result = await pruneOutput({
|
|
226
|
+
text: output,
|
|
227
|
+
task: "fix the failing auth test",
|
|
228
|
+
command: "npm test",
|
|
229
|
+
exitCode: 0,
|
|
230
|
+
});
|
|
231
|
+
result.kept; // exact retained text with collapse markers
|
|
232
|
+
result.dropped; // [{ from, to, count }]
|
|
233
|
+
result.runId; // for `jevprune show`
|
|
234
|
+
result.mode; // "fast-path" | "passthrough" | "jev" | "fallback"
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
`pruneStream` takes a readable stream instead of `text`. Pass `client` to supply your own Jev client (a `FakeJevClient` is exported for tests) and `save: false` to skip the run store.
|
|
238
|
+
|
|
239
|
+
## Limits
|
|
240
|
+
|
|
241
|
+
- Not lossless. See the guarantee above.
|
|
242
|
+
- Questions cost tokens too. A 3,000-line output is roughly 5 to 10 Jev requests depending on line length. Output tokens are free; input is billed by TypeSafe.
|
|
243
|
+
- Needs an early-access TypeSafe key. Without one, every pruned run is the head-and-tail fallback.
|
|
244
|
+
- `run` takes an executable and arguments, no shell string. Use `bash -c '...'` for pipelines, which is what the plugin does.
|
|
245
|
+
- Successful outputs over 16 MiB are pruned by head and tail only. Failed ones pass through in full.
|
|
246
|
+
- Output that is not valid UTF-8 passes through untouched.
|
|
247
|
+
- Everything jevprune saves stays under `~/.jevprune`. The task text is stored there with each run and nowhere else.
|
|
248
|
+
|
|
249
|
+
## Neighbors
|
|
250
|
+
|
|
251
|
+
- [fast-jev-compaction](https://github.com/tamaratran/fast-jev-compaction) replaces Claude Code's compaction summary with Jev decisions over the whole conversation. jevprune prunes one command's output at the moment it is produced, before it enters context. They compose. An open pull request there trims Bash output in 20-line chunks behind an early-access function-hook flag; jevprune scores single lines against the task, keeps error signatures with context before Jev is asked, runs on the documented `PreToolUse` hook, and works as a CLI and library outside Claude Code.
|
|
252
|
+
- [RTK](https://github.com/rtk-ai/rtk) filters output with rules written per tool. jevprune has no per-tool rules; the task decides.
|
|
253
|
+
- [Squeez](https://github.com/KRLabsOrg/squeez) selects task-relevant lines with a local 2B model, in Python. jevprune does the same job through the TypeSafe API, with exact recall of dropped ranges and a run store.
|
|
254
|
+
|
|
255
|
+
## License
|
|
256
|
+
|
|
257
|
+
MIT
|