chisle 2.0.0 → 3.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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chisle",
3
- "version": "1.2.2",
3
+ "version": "3.1.0",
4
4
  "description": "Maximum-efficiency dev mode. Zero-fluff prose combined with YAGNI/ladder-first code decisions. One persona: the senior dev who deletes code for fun and bills by the syllable.",
5
5
  "hooks": {
6
6
  "SessionStart": [
@@ -33,4 +33,4 @@ you're about to edit — diet trims transport, not understanding.
33
33
  **Never minimal about:** input validation at trust boundaries, error handling
34
34
  that prevents data loss, security, accessibility, anything explicitly requested.
35
35
 
36
- Levels: lite / full (default) / ultra. Deactivate: "stop chisle" / "normal mode".
36
+ Always on once installed. Deactivate: "stop chisle" / "normal mode".
@@ -38,4 +38,4 @@ you're about to edit — diet trims transport, not understanding.
38
38
  **Never minimal about:** input validation at trust boundaries, error handling
39
39
  that prevents data loss, security, accessibility, anything explicitly requested.
40
40
 
41
- Levels: lite / full (default) / ultra. Deactivate: "stop chisle" / "normal mode".
41
+ Always on once installed. Deactivate: "stop chisle" / "normal mode".
@@ -33,4 +33,4 @@ you're about to edit — diet trims transport, not understanding.
33
33
  **Never minimal about:** input validation at trust boundaries, error handling
34
34
  that prevents data loss, security, accessibility, anything explicitly requested.
35
35
 
36
- Levels: lite / full (default) / ultra. Deactivate: "stop chisle" / "normal mode".
36
+ Always on once installed. Deactivate: "stop chisle" / "normal mode".
@@ -37,4 +37,4 @@ you're about to edit — diet trims transport, not understanding.
37
37
  **Never minimal about:** input validation at trust boundaries, error handling
38
38
  that prevents data loss, security, accessibility, anything explicitly requested.
39
39
 
40
- Levels: lite / full (default) / ultra. Deactivate: "stop chisle" / "normal mode".
40
+ Always on once installed. Deactivate: "stop chisle" / "normal mode".
@@ -37,4 +37,4 @@ you're about to edit — diet trims transport, not understanding.
37
37
  **Never minimal about:** input validation at trust boundaries, error handling
38
38
  that prevents data loss, security, accessibility, anything explicitly requested.
39
39
 
40
- Levels: lite / full (default) / ultra. Deactivate: "stop chisle" / "normal mode".
40
+ Always on once installed. Deactivate: "stop chisle" / "normal mode".
package/AGENTS.md CHANGED
@@ -41,6 +41,6 @@ edit — diet trims transport, not understanding.
41
41
  Input validation at trust boundaries, error handling that prevents data loss,
42
42
  security, accessibility, anything explicitly requested.
43
43
 
44
- ## Levels
44
+ ## Switching off
45
45
 
46
- `lite` · `full` (default) · `ultra`. Deactivate: "stop chisle" / "normal mode".
46
+ Always on once installed. Deactivate: "stop chisle" / "normal mode".
package/README.md CHANGED
@@ -1,16 +1,20 @@
1
1
  <p align="center">
2
- <img src="assets/logo.svg" width="120" alt="Chisle">
2
+ <img src="assets/logo.png" width="120" alt="Chisle">
3
3
  </p>
4
4
 
5
- <h1 align="center">🧨 Chisle</h1>
5
+ <h1 align="center">Chisle</h1>
6
6
 
7
7
  <p align="center">
8
8
  <em>Your AI talks less, builds less, reads less — and says more. Like a senior dev who bills by the syllable.</em>
9
9
  </p>
10
10
 
11
+ <p align="center">
12
+ <em>The only tool in this class that publishes the runs where it lost.</em>
13
+ </p>
14
+
11
15
  <p align="center">
12
16
  <a href="https://www.npmjs.com/package/chisle"><img src="https://img.shields.io/npm/v/chisle?style=flat-square&color=d78a3c" alt="npm version"></a>
13
- <img src="https://img.shields.io/badge/works%20with-8%20agents-d78a3c?style=flat-square" alt="Works with 8 agents">
17
+ <img src="https://img.shields.io/badge/works%20with-9%20agents-d78a3c?style=flat-square" alt="Works with 9 agents">
14
18
  <a href="https://github.com/JayPokale/Chisle/actions/workflows/test.yml"><img src="https://img.shields.io/github/actions/workflow/status/JayPokale/Chisle/test.yml?style=flat-square&label=CI" alt="CI"></a>
15
19
  <img src="https://img.shields.io/badge/deps-0-2da44e?style=flat-square" alt="Zero deps">
16
20
  <img src="https://img.shields.io/badge/license-MIT-d78a3c?style=flat-square" alt="MIT">
@@ -18,7 +22,7 @@
18
22
  </p>
19
23
 
20
24
  <p align="center">
21
- <strong>Half the output bill on 20 measured tasks &middot; compresses what the model writes AND reads &middot; one command</strong>
25
+ <strong>44% of a bare model on coding work &middot; 41% on the long ones &middot; one command</strong>
22
26
  </p>
23
27
 
24
28
  <p align="center">
@@ -75,7 +79,42 @@ Most efficiency tools compress one thing. Chisle compresses **three**:
75
79
  |---|---|---|
76
80
  | **Output: prose** | filler, hedging, manufactured structure | zero-fluff ruleset, injected per session |
77
81
  | **Output: code** | speculative abstractions, unrequested boilerplate | YAGNI efficiency ladder |
78
- | **Input: context** | oversized tool output flooding the window | `PostToolUse` hook — scrub, elide, dedup — plus prevention rules |
82
+ | **Input: context** | oversized tool output flooding the window | Claude `PostToolUse` / Pi `tool_result` — scrub, elide, dedup — plus prevention rules |
83
+
84
+ Where each one attaches to a session:
85
+
86
+ ```mermaid
87
+ flowchart LR
88
+ subgraph S["Session start"]
89
+ H1["Ruleset injection<br/>once per active session"]
90
+ end
91
+ subgraph T["Every turn"]
92
+ H2["Mode tracking<br/>Claude + Pi"]
93
+ end
94
+ subgraph L["Every tool call"]
95
+ H3["PostToolUse / tool_result<br/>scrub → elide → dedup"]
96
+ end
97
+
98
+ H1 --> M(["Model"])
99
+ H2 --> M
100
+ M -->|writes| O["Output:<br/>terser prose,<br/>YAGNI-first code"]
101
+ M -->|calls a tool| TOOL[["Bash / grep / web / extension tools"]]
102
+ TOOL -->|raw output| H3
103
+ H3 -->|"compressed, rebuilt into<br/>the tool's own shape"| M
104
+
105
+ RE["Read / Edit / Write"] -.->|"never touched —<br/>exact bytes feed later edits"| M
106
+
107
+ style M fill:#1f2937,stroke:#d78a3c,color:#e6edf3
108
+ style O fill:#14532d,stroke:#2da44e,color:#e6edf3
109
+ style H3 fill:#1f2937,stroke:#2da44e,color:#e6edf3
110
+ style RE fill:#3f1d1d,stroke:#cf3b3b,color:#e6edf3
111
+ ```
112
+
113
+ The loop on the right is the input axis: tool output is billed again on *every* later request in the session, so shrinking it once pays repeatedly. `Read`, `Edit`, and `Write` are deliberately outside it.
114
+
115
+ <p align="center">
116
+ <img src="assets/thumbnail.png" width="820" alt="Chisle — less tokens, same results. Four parts: output compressor (compress noisy tool outputs), context diet (read only what's actually relevant), terse persona (short, focused, YAGNI-first), YAGNI ladder (do less, reuse more, build only when needed).">
117
+ </p>
79
118
 
80
119
  Every "be concise" tool has a worst day — the day it makes the model write *more* than no tool at all. Across 20 measured tasks over two suites, the specialists had that day **6** and **8** times, blowing up to **424%** of the baseline. Chisle had it **once**, capped at 173% — and that one failure was root-caused, fixed in the ruleset, and re-validated live at 93%, with the whole investigation [committed to the repo](benchmarks/results/2026-07-07-verify-rerun.md). Think of it as downside insurance for your token bill: not always the single cheapest answer, always the smallest worst case — from the only tool in this class that publishes its own failures. [Why not caveman or ponytail? →](docs/comparison.md)
81
120
 
@@ -83,7 +122,7 @@ Every "be concise" tool has a worst day — the day it makes the model write *mo
83
122
 
84
123
  ## Install
85
124
 
86
- One command. Auto-detects your agents (Claude Code, Cursor, Windsurf, Cline, Kiro, Codex, Gemini, Copilot) and wires each one. `--uninstall` puts everything back.
125
+ One command. Auto-detects your agents (Claude Code, Pi, Cursor, Windsurf, Cline, Kiro, Codex, Gemini, Copilot) and wires each one. `--uninstall` puts everything back.
87
126
 
88
127
  ```bash
89
128
  npx chisle
@@ -99,9 +138,27 @@ curl -fsSL https://raw.githubusercontent.com/JayPokale/Chisle/main/install.sh |
99
138
  irm https://raw.githubusercontent.com/JayPokale/Chisle/main/install.ps1 | iex
100
139
  ```
101
140
 
102
- Preview first with `npx chisle --dry-run`, scope with `--only claude`, see everything with `npx chisle --help`. Remove with `npx chisle --uninstall`.
141
+ Preview first with `npx chisle --dry-run`, scope with `--only claude` or `--only pi`, see everything with `npx chisle --help`. Remove with `npx chisle --uninstall`.
142
+
143
+ **Requirements:** Node ≥18 (installer / `npx`) · Claude Code or Pi for `/chisle` toggling and input-side compression — the always-on ruleset still ships to every other agent.
144
+
145
+ ### Upgrading
146
+
147
+ Same command as installing — it is idempotent and overwrites the previous copy:
148
+
149
+ ```bash
150
+ npx chisle # npm / standalone install
151
+ claude plugin update chisle@chisle # Claude Code plugin install
152
+ ```
103
153
 
104
- **Requirements:** Node ≥18 (installer / `npx`) · Claude Code for live `/chisle` switching and input-side compression — the always-on ruleset still ships to every other agent.
154
+ Chisle checks npm on session start and mentions it once when a **major** version
155
+ is out (cached 3 days, `CHISLE_UPDATE_CHECK=0` to silence). Minor and patch
156
+ releases stay quiet on purpose.
157
+
158
+ Upgrading to 3.0.0 from 2.x needs nothing: a `lite`/`full`/`ultra` value in
159
+ `CHISLE_DEFAULT_MODE` or `config.json` is no longer meaningful, falls through to
160
+ the default, and Chisle stays active. It says so once so the setting is not
161
+ ignored silently — replace it with `on`/`off` or delete it.
105
162
 
106
163
  ### Claude Code plugin (marketplace)
107
164
 
@@ -110,17 +167,26 @@ claude plugin marketplace add JayPokale/Chisle # register the marketplace
110
167
  claude plugin install chisle@chisle # enable the plugin
111
168
  ```
112
169
 
170
+ ### Pi package
171
+
172
+ ```bash
173
+ pi install npm:chisle
174
+ ```
175
+
176
+ The package loads the zero-dependency extension and `chisle` skill globally. Pi extensions run with your user permissions; review the source before installation. Project-local installs (`pi install -l npm:chisle`) load only after you trust that project.
177
+
113
178
  ### See what it would do, before it does it
114
179
 
115
180
  ```bash
116
181
  npx chisle --dry-run # prints every file it would touch, changes nothing
117
182
  ```
118
183
 
119
- Want the savings measured on your own work rather than ours? Clone the repo and replay the compressor over your existing Claude Code transcripts — it reads them locally, writes nothing, and reports the tokens the input-side hook would have stripped:
184
+ Want the savings measured on your own work rather than ours? Clone the repo and replay the compressor over local transcripts — it reads them locally, writes nothing, and reports the input the hook would have stripped:
120
185
 
121
186
  ```bash
122
187
  git clone https://github.com/JayPokale/Chisle && cd Chisle
123
- node benchmarks/replay-compress.js
188
+ node benchmarks/replay-compress.js # Claude Code
189
+ node benchmarks/replay-compress.js pi # Pi; marginal over Pi's native truncation
124
190
  ```
125
191
 
126
192
  ---
@@ -147,7 +213,22 @@ Chisle wins all four columns: it cut the total 20-task bill **nearly in half** w
147
213
 
148
214
  The bar is the whole 20-task bill; the badge is each tool's worst single day. caveman's worst day cost **4.2×** a bare model; ponytail's — a tool whose entire job is writing less — **2.3×**. Chisle's worst day was 1.7×, it happened once, and the fix is measured and merged.
149
215
 
150
- On coding tasks Chisle is leanest (June: 22% of baseline vs caveman 46%, ponytail 29%; July: 64% vs 84% and 160%). On pure prose caveman is a hair leaner on a good day — credit where due. And in the July run all 24 answers, every arm, **graded correct**: nobody here buys token savings with wrong answers.
216
+ In the July run all 24 answers, every arm, **graded correct**: nobody here buys token savings with wrong answers.
217
+
218
+ #### Code vs. explanation
219
+
220
+ Across all 20 cells, split by what the prompt actually asks for:
221
+
222
+ | | n | caveman | ponytail | **Chisle** |
223
+ |---|--:|--:|--:|--:|
224
+ | **coding** (wants working code) | 12 | 74% | 59% | **44%** |
225
+ | **non-coding** (wants an explanation) | 8 | 103% | 104% | **87%** |
226
+
227
+ Code is where the YAGNI ladder has something to bite on: an abstraction to skip, a stdlib call to reach for, a file not to create. Chisle bills **44%** of a bare model there — a third less than ponytail, which is the closest thing to a dedicated lazy-code tool.
228
+
229
+ On explanation-only prompts the picture is worse for everyone. Both specialists land **above 100%** — a tool whose job is writing less made the model write *more* than using nothing at all. Chisle is the only arm that stays under water (87%), which is a smaller win than the coding number and worth saying plainly.
230
+
231
+ This does revise a claim the earlier Sonnet writeup made. On that suite's three prose prompts caveman was leaner (44% vs 52%), and that still holds *for those cells*. Pooled across all eight non-coding cells it does not: caveman is at 103%. The prose win was suite-specific, not general.
151
232
 
152
233
  #### Task by task
153
234
 
@@ -159,6 +240,47 @@ Averages hide the interesting part, so here is every cell of the June suite —
159
240
 
160
241
  Chisle is leanest on **5 of 6**. caveman takes `cache` (8% vs our 12%) — it wins by answering in prose where we still emit working code, which is the trade you would want on a task that asked for code. Note the two prose rows where ponytail lands **above** 100%: a tool built to write less made the model write *more* than using no tool at all. That is the failure mode the worst-case column above is really about.
161
242
 
243
+ #### The headline average is hiding the good part
244
+
245
+ Split the same 20 cells at their median baseline — short answers below, long answers above — and the tools separate sharply:
246
+
247
+ <p align="center">
248
+ <img src="assets/by-size.svg" width="820" alt="Total billed output split by answer size. On short answers caveman and Chisle are level at about 84% of baseline and ponytail is above 100%. On long answers Chisle drops to about 45% while caveman is 79% and ponytail 59%.">
249
+ </p>
250
+
251
+ On **short** answers Chisle and caveman are level (84% each) — there is not much to cut in a three-line reply, and the ruleset overhead is proportionally at its worst. On **long** answers Chisle drops to **45%** while caveman only reaches 79%. The 52% headline is the blend of the two, so it understates the case where it matters and overstates the case where it doesn't.
252
+
253
+ The effect is not driven by one lucky cell. Dropping the `cache` outlier (the row where the baseline invented 150 lines against a codebase it never saw) *widens* the gap on long answers: caveman degrades to **116%** — worse than using no tool — while Chisle holds at **65%**.
254
+
255
+ Two honest limits. Per-task rank correlation between baseline size and leanness is weak (Spearman ρ = −0.15), so this is a difference between aggregate bills, not a tidy per-task law — with n=10 a side, treat it as a strong signal rather than a settled result. And much of the widening gap comes from the specialists getting *worse* on long answers, not only from Chisle getting better.
256
+
257
+ #### Size or kind? Both, and they're tangled
258
+
259
+ Coding prompts average ~1129 baseline tokens against ~393 for explanation prompts, so "long" and "code" largely describe the same cells. Crossing the two separates them as far as 20 tasks allow:
260
+
261
+ | | n | caveman | ponytail | **Chisle** |
262
+ |---|--:|--:|--:|--:|
263
+ | coding · short | 5 | **62%** | 116% | 70% |
264
+ | coding · long | 7 | 76% | 52% | **41%** |
265
+ | non-coding · short | 5 | 104% | 98% | **96%** |
266
+ | non-coding · long | 3 | 103% | 111% | **77%** |
267
+
268
+ <p align="center">
269
+ <img src="assets/by-kind.svg" width="820" alt="Billed output by task kind crossed with answer size. Code and short: caveman 62%, ponytail 116%, Chisle 70%. Code and long: caveman 76%, ponytail 52%, Chisle 41%. Explanation and short: caveman 104%, ponytail 98%, Chisle 96%. Explanation and long: caveman 103%, ponytail 111%, Chisle 77%.">
270
+ </p>
271
+
272
+ Size matters *within* each kind — coding goes 70% → 41%, non-coding 96% → 77% — so it isn't merely code in disguise. But the cells are thin, and the non-coding "long" bucket spans only 522–542 tokens, which is barely long at all.
273
+
274
+ The one row Chisle loses is **short coding**, where caveman takes it 62% to 70%. That is the honest shape of it: on a small code question there is little to skip, and the ruleset costs more than the ladder saves. The tool earns its keep on the long ones.
275
+
276
+ `SUITE=large` exists to fill the thin cells — see [below](#see-what-it-would-do-before-it-does-it).
277
+
278
+ These prompts were never designed to test this, which is the real caveat. To probe it directly:
279
+
280
+ ```bash
281
+ SUITE=large bash benchmarks/run-live.sh <model> benchmarks/results/raw-large
282
+ ```
283
+
162
284
  #### Where each tool actually helps
163
285
 
164
286
  | | prose | code judgment | input/context | worst-case guard | publishes failures |
@@ -170,7 +292,7 @@ Chisle is leanest on **5 of 6**. caveman takes `cache` (8% vs our 12%) — it wi
170
292
 
171
293
  The row that matters is the last one. Every tool here looks good on its best day; the numbers above are the only ones in this class published alongside the run that went wrong. [Full comparison →](docs/comparison.md)
172
294
 
173
- ### Input axis — tool-output compression (Claude Code)
295
+ ### Input axis — tool-output compression (Claude Code + Pi)
174
296
 
175
297
  Measured over 171 real sessions ([receipts](benchmarks/results/2026-07-07-input-axis.md)): tool output is **67.5%** of context content, and every byte of it is re-billed on *every subsequent request* in the session (median: 171 requests). A `PostToolUse` hook shrinks it before the model reads it — deterministic, zero LLM, zero network:
176
298
 
@@ -180,13 +302,16 @@ Measured over 171 real sessions ([receipts](benchmarks/results/2026-07-07-input-
180
302
  | **elide** | oversized output → head + tail, error-like lines salvaged from the cut | bounded, guarded |
181
303
  | **dedup** | byte-identical repeat of a tool's previous output (same session) → one-line marker | none — the copy is already in context |
182
304
 
183
- Replayed over the same 171 sessions: **~61k tokens** (full) / **~103k** (ultra) saved one-shot, ~46% off every eligible output — floor, not estimate, since each saved byte also stops being re-sent on every later request. Correctness rules: allowlist only (`Bash`, `Agent`, `WebFetch`, `WebSearch`, `Grep`, `Glob`, `mcp__*`) — never `Read`/`Edit`, whose exact bytes feed later edits. Honest ledger: dedup scored **0 hits** on this corpus (rtk-filtered at source); it's kept for the test-rerun case, kill-switchable, and labeled speculative until it earns a number. Replay it on your own transcripts:
305
+ Replayed over the same 171 Claude Code sessions: **~61k tokens** saved one-shot, ~46% off every eligible output — floor, not estimate, since each saved byte also stops being re-sent on every later request. Correctness rules: allowlist only (`Bash`, `Agent`, `WebFetch`, `WebSearch`, `Grep`, `Glob`, `mcp__*`) — never `Read`/`Edit`, whose exact bytes feed later edits. Honest ledger: dedup scored **0 hits** on this corpus (rtk-filtered at source); it's kept for the test-rerun case, kill-switchable, and labeled speculative until it earns a number.
306
+
307
+ Pi already truncates built-in output at 50KB/2,000 lines. Replay over 8,044 persisted Pi results measured the compressor's **marginal** saving after that truncation: **27.4% of tool-output chars** ([receipt + raw live Pi arm](benchmarks/results/2026-09-11-pi.md)). Pi compresses `bash`, `powershell`, `grep`, `find`, `ls`, MCP, and explicitly allowlisted extension-tool results; `read`/`edit`/`write` remain untouched. `CHISLE_COMPRESS_TOOLS` supplies the same explicit override for runtime and replay. Runtime dedup only compares earlier turns, so concurrently completed sibling calls cannot dedup one another.
184
308
 
185
309
  ```bash
186
- node benchmarks/replay-compress.js # what it would have saved you
310
+ node benchmarks/replay-compress.js # Claude Code
311
+ node benchmarks/replay-compress.js pi # Pi
187
312
  ```
188
313
 
189
- Thresholds track the `/chisle` level (lite 16k / full 8k / ultra 5k chars). `stop chisle`, `CHISLE_COMPRESS=0`, `CHISLE_COMPRESS_SCRUB=0`, `CHISLE_COMPRESS_DEDUP=0` — every tier has an off switch.
314
+ Outputs over 8k chars are elided. `stop chisle`, `CHISLE_COMPRESS=0`, `CHISLE_COMPRESS_SCRUB=0`, `CHISLE_COMPRESS_DEDUP=0` — every tier has an off switch.
190
315
 
191
316
  ### Prevention — the context diet
192
317
 
@@ -196,14 +321,11 @@ The biggest context whale (whole-file `Read`s — 5.6M chars in the measured cor
196
321
 
197
322
  ## What the output sounds like
198
323
 
199
- **"Why React component re-render?"** (full)
324
+ **"Why does this React component re-render?"**
200
325
  > New object ref each render. Inline object prop = new ref = re-render. `useMemo`.
201
326
 
202
- **"Why React component re-render?"** (ultra)
203
- > Inline obj prop → new ref → re-render. `useMemo`. Why inline object at all?
204
-
205
- **"Add a cache for API responses."** (ultra)
206
- > No cache until profiler says so. When it does: `@lru_cache`. Hand-rolled TTL cache = bug farm with hit rate.
327
+ **"Add a cache for API responses."**
328
+ > `@lru_cache(maxsize=1000)` on the fetch fn. Skipped a custom cache class — add one when `lru_cache` measurably falls short.
207
329
 
208
330
  ---
209
331
 
@@ -211,15 +333,13 @@ The biggest context whale (whole-file `Read`s — 5.6M chars in the measured cor
211
333
 
212
334
  | Command | Effect |
213
335
  |---------|--------|
214
- | *(nothing)* | On automatically at `full` every session after install |
215
- | `/chisle` | Re-activate at default level if you'd stopped it |
216
- | `/chisle lite` | Tighter prose, flags the minimal alternative |
217
- | `/chisle full` | Full compression + YAGNI ladder enforced |
218
- | `/chisle ultra` | Extremist — abbreviate prose, delete before add, challenge requirements |
336
+ | *(nothing)* | On automatically every session after install |
337
+ | `/chisle` | Re-activate if you'd stopped it |
338
+ | `/chisle off` | Deactivate |
219
339
  | `stop chisle` | Deactivate (ruleset *and* input-side compression) |
220
340
  | `normal mode` | Deactivate |
221
341
 
222
- Natural language works too: "activate chisle", "chisle mode", "chislify this". Across every level, code symbols, function/API names, and error strings stay verbatim — only the noise around them compresses.
342
+ Natural language works too: "activate chisle", "chisle mode", "chislify this". Code symbols, function/API names, and error strings stay verbatim — only the noise around them compresses.
223
343
 
224
344
  ---
225
345
 
@@ -227,15 +347,31 @@ Natural language works too: "activate chisle", "chisle mode", "chislify this". A
227
347
 
228
348
  Before writing code, the agent stops at the first rung that holds:
229
349
 
350
+ ```mermaid
351
+ flowchart TD
352
+ A[Request for code] --> R[Read the problem fully]
353
+ R --> Q1{Does this need<br/>to exist at all?}
354
+ Q1 -->|no| S1[Skip it. Say so in one line]
355
+ Q1 -->|yes| Q2{Already in<br/>this codebase?}
356
+ Q2 -->|yes| S2[Reuse it. Don't rewrite]
357
+ Q2 -->|no| Q3{Stdlib<br/>does it?}
358
+ Q3 -->|yes| S3[Use the stdlib]
359
+ Q3 -->|no| Q4{Native platform<br/>feature covers it?}
360
+ Q4 -->|yes| S4["CSS over JS, DB constraint<br/>over app code"]
361
+ Q4 -->|no| Q5{Already-installed<br/>dependency?}
362
+ Q5 -->|yes| S5[Use it. Never add a new dep<br/>for what a few lines do]
363
+ Q5 -->|no| Q6{Can it be<br/>one line?}
364
+ Q6 -->|yes| S6[One line]
365
+ Q6 -->|no| S7[The minimum code that works]
366
+
367
+ S1 & S2 & S3 & S4 & S5 & S6 & S7 --> OUT[Ship it + note what was skipped<br/>and when to add it]
368
+
369
+ style Q1 fill:#1f2937,stroke:#d78a3c,color:#e6edf3
370
+ style OUT fill:#14532d,stroke:#2da44e,color:#e6edf3
371
+ style R fill:#1f2937,stroke:#8b949e,color:#e6edf3
230
372
  ```
231
- 1. Does this need to exist? → no: skip it (YAGNI)
232
- 2. Already in this codebase? → reuse it, don't rewrite
233
- 3. Stdlib does it? → use it
234
- 4. Native platform feature? → use it
235
- 5. Installed dependency? → use it
236
- 6. One line? → one line
237
- 7. Only then: the minimum that works
238
- ```
373
+
374
+ The ladder runs **after** reading, never instead of it. Note the exit: every rung lands on the same obligation — say what you skipped, so "later" doesn't quietly become "never".
239
375
 
240
376
  The ladder runs *after* reading the code — lazy about the solution, never about understanding. Lazy is not negligent: trust-boundary validation, data-loss handling, security, and accessibility are never on the chopping block.
241
377
 
@@ -250,17 +386,17 @@ Mark deliberate simplifications so "later" doesn't quietly become "never":
250
386
 
251
387
  ## Config
252
388
 
253
- **On by default.** After install, CHISLE activates automatically at `full` every session — no `/chisle` needed. Change the default level, or set `off` to stay dormant until you type `/chisle`:
389
+ **On by default.** After install, Chisle activates automatically every session — no `/chisle` needed. Set `off` to stay dormant until you type `/chisle`:
254
390
 
255
391
  ```bash
256
392
  # env var (highest priority)
257
- export CHISLE_DEFAULT_MODE=ultra
393
+ export CHISLE_DEFAULT_MODE=off
258
394
 
259
395
  # config file (persists across shells)
260
- ~/.config/chisle/config.json → { "defaultMode": "ultra" }
396
+ ~/.config/chisle/config.json → { "defaultMode": "off" }
261
397
  ```
262
398
 
263
- Resolution: env var → config file → `full`. Valid: `off`, `lite`, `full`, `ultra`.
399
+ Resolution: env var → config file → `on`. Valid: `off`, `on`.
264
400
 
265
401
  ---
266
402
 
@@ -280,12 +416,14 @@ Chisle borrows the best published token-saving techniques and implements the one
280
416
 
281
417
  ## Multi-agent
282
418
 
283
- Primarily a Claude Code plugin, but ships to every agent with a rules/context file — Cursor, Windsurf, Cline, Kiro, Codex, Gemini, Copilot. Per-agent copies generated by `scripts/build-rules.js` (a condensed mirror of the skill — edit both, CI checks sync). See [`docs/agent-portability.md`](./docs/agent-portability.md). Input-side compression is Claude Code-only for now: no other agent exposes a post-tool output rewrite hook.
419
+ Ships to nine agents: Claude Code and Pi get both axes, live `/chisle` toggling, and a status badge; Cursor, Windsurf, Cline, Kiro, Codex, Gemini, and Copilot get the always-on ruleset. Per-agent static copies come from `scripts/build-rules.js`; Pi uses its package extension plus the Agent Skills standard. See [`docs/agent-portability.md`](./docs/agent-portability.md).
284
420
 
285
421
  ## FAQ
286
422
 
287
423
  **Doesn't injecting a persona every turn cost tokens?**
288
- Yes — a one-time ruleset at session start (~1.8k tokens) plus a ~40-token reminder per turn. Output is where it pays back: coding answers shrink 40–60% (benchmarks), and output bills several × higher than input, so net is positive after the first couple of turns. On a one-line throwaway prompt the overhead can exceed the saving. The input-side hook has no such tradeoff — it only ever removes tokens.
424
+ Claude Code uses a ruleset at session start (~1.6k tokens) plus a ~50-token reminder per turn. Pi injects one persistent rules message only when project `AGENTS.md` does not already provide it; resume/reload does not duplicate it, and compaction restores it only if removed.
425
+
426
+ Worth reading the dissent before you take that on faith: [@enc0ded](https://github.com/enc0ded) measured 173 of their own sessions ([#2](https://github.com/JayPokale/Chisle/issues/2)) and found the injection overhead roughly cancelling the compressor's savings — because the ruleset was being re-sent on every resume and clear, not just at startup. That re-injection is fixed, which removes most of the overhead they measured, but their wider point stands: prose is only ~25% of what the model emits, so the ceiling on the output axis is lower than the headline suggests, and on a one-line throwaway prompt the overhead still exceeds the saving.
289
427
 
290
428
  **Will it golf my code into clever one-liners?**
291
429
  No. Boring over clever. Deletion beats addition; obfuscation isn't deletion.
@@ -310,7 +448,7 @@ See [CONTRIBUTING.md](CONTRIBUTING.md). Edit the skill (`skills/chisle/SKILL.md`
310
448
  Built by [Jay Pokale](https://github.com/JayPokale) with [Claude](https://claude.com/claude-code), [Antigravity](https://antigravity.google), and [Codex](https://openai.com/blog/openai-codex/) as co-engineers — the input-compression hook, the benchmark verification, and several of the bug hunts documented in the changelog were pair-work.
311
449
 
312
450
  ```bash
313
- npm test # 59 tests: flag safety, tracker, settings merge, installer, compressor
451
+ npm test # 67 tests: flag safety, tracker, settings merge, installer, compressor
314
452
  ```
315
453
 
316
454
  ## License
package/bin/install.js CHANGED
@@ -4,6 +4,7 @@
4
4
  // Detects the AI coding agents on this machine and installs Chisle for each:
5
5
  // - Claude Code → plugin (marketplace add + install), fallback to standalone
6
6
  // hooks + settings.json merge + statusline badge
7
+ // - Pi → Pi package (extension + skill)
7
8
  // - Gemini CLI → gemini extensions install
8
9
  // - Codex → fenced ruleset appended to ~/.codex/AGENTS.md
9
10
  // - Cursor/Windsurf/Cline/Kiro/Copilot → project rule file dropped into CWD
@@ -39,6 +40,7 @@ const REPO_ROOT = path.resolve(__dirname, '..');
39
40
  // (that's how Cursor/Cline/Copilot rules actually work).
40
41
  const PROVIDERS = [
41
42
  { id: 'claude', label: 'Claude Code', scope: 'global', detect: 'cmd:claude' },
43
+ { id: 'pi', label: 'Pi', scope: 'global', detect: 'cmd:pi' },
42
44
  { id: 'gemini', label: 'Gemini CLI', scope: 'global', detect: 'cmd:gemini' },
43
45
  { id: 'codex', label: 'Codex CLI', scope: 'global', detect: 'cmd:codex||dir:~/.codex' },
44
46
  { id: 'cursor', label: 'Cursor', scope: 'project', detect: 'cmd:cursor||dir:~/.cursor',
@@ -196,6 +198,18 @@ function installClaude(ctx) {
196
198
  else results.failed.push(['claude-hooks', r]);
197
199
  } else {
198
200
  note(' hooks: plugin manifest handles SessionStart + UserPromptSubmit + PostToolUse');
201
+ // Plugin path won — drop any standalone wiring left by an earlier run, or
202
+ // both copies of every hook run on each event and the PostToolUse pair
203
+ // dedups first-seen output against itself.
204
+ const settingsPath = path.join(claudeDir(opts), 'settings.json');
205
+ const settings = SETTINGS.readSettings(settingsPath);
206
+ if (settings && SETTINGS.removeHooks(settings, 'chisle-') > 0) {
207
+ if (!opts.dryRun) {
208
+ SETTINGS.validateHookFields(settings);
209
+ SETTINGS.writeSettings(settingsPath, settings);
210
+ }
211
+ note(' removed superseded standalone hook entries from settings.json');
212
+ }
199
213
  }
200
214
  process.stdout.write('\n');
201
215
  }
@@ -209,7 +223,7 @@ function installClaudeHooks(ctx) {
209
223
  const hooksDst = path.join(cfg, 'chisle-hooks');
210
224
  const settingsPath = path.join(cfg, 'settings.json');
211
225
  const HOOK_FILES = ['package.json', 'chisle-config.js', 'chisle-activate.js',
212
- 'chisle-mode-tracker.js', 'chisle-compress-output.js',
226
+ 'chisle-mode.js', 'chisle-mode-tracker.js', 'chisle-compress-output.js',
213
227
  'chisle-statusline.sh', 'chisle-statusline.ps1'];
214
228
 
215
229
  if (opts.dryRun) {
@@ -263,6 +277,30 @@ function installClaudeHooks(ctx) {
263
277
  return 'ok';
264
278
  }
265
279
 
280
+ // ── Pi ──────────────────────────────────────────────────────────────────────
281
+ function piSources(output) {
282
+ return String(output || '').split('\n').map(line => line.trim())
283
+ .filter(line => /^npm:chisle(?:@|$)/i.test(line) || /^git:.*github\.com\/JayPokale\/Chisle(?:@|$)/i.test(line))
284
+ .map(source => source.replace(/@[^/@]+$/, ''));
285
+ }
286
+
287
+ function installPi(ctx) {
288
+ const { say, note, opts, results } = ctx;
289
+ results.detected++;
290
+ say('→ Pi detected');
291
+ if (!opts.force) {
292
+ const installed = piSources(capture('pi', ['list']).stdout);
293
+ if (installed.length) {
294
+ note(' chisle package already installed (use --force)');
295
+ results.skipped.push(['pi', 'already installed']); process.stdout.write('\n'); return;
296
+ }
297
+ }
298
+ const r = run('pi', ['install', 'npm:chisle'], opts.dryRun);
299
+ if ((r.status || 0) === 0) results.installed.push('pi');
300
+ else results.failed.push(['pi', 'pi package install failed']);
301
+ process.stdout.write('\n');
302
+ }
303
+
266
304
  // ── Gemini ──────────────────────────────────────────────────────────────────
267
305
  function installGemini(ctx) {
268
306
  const { say, note, opts, results } = ctx;
@@ -347,53 +385,66 @@ function uninstall(ctx) {
347
385
  say(c.orange('Chisle uninstall'));
348
386
  let touched = 0;
349
387
 
350
- // Claude plugin
351
- if (hasCmd('claude') && !opts.dryRun) {
352
- const r = capture('claude', ['plugin', 'list']);
353
- if (r.status === 0 && /chisle/i.test(r.stdout || '')) {
354
- run('claude', ['plugin', 'uninstall', 'chisle@chisle'], opts.dryRun); touched++;
388
+ const wants = id => !opts.only.length || opts.only.includes(id);
389
+
390
+ if (wants('claude')) {
391
+ // Claude plugin
392
+ if (hasCmd('claude') && !opts.dryRun) {
393
+ const r = capture('claude', ['plugin', 'list']);
394
+ if (r.status === 0 && /chisle/i.test(r.stdout || '')) {
395
+ run('claude', ['plugin', 'uninstall', 'chisle@chisle'], opts.dryRun); touched++;
396
+ }
355
397
  }
356
- }
357
398
 
358
- // Claude standalone hooks + statusline
359
- const cfg = claudeDir(opts);
360
- const settingsPath = path.join(cfg, 'settings.json');
361
- const settings = SETTINGS.readSettings(settingsPath);
362
- if (settings) {
363
- const removed = SETTINGS.removeHooks(settings, 'chisle-');
364
- let slRemoved = false;
365
- if (settings.statusLine && typeof settings.statusLine.command === 'string'
366
- && settings.statusLine.command.includes('chisle-statusline')) { delete settings.statusLine; slRemoved = true; }
367
- if (removed > 0 || slRemoved) {
368
- if (!opts.dryRun) { SETTINGS.validateHookFields(settings); SETTINGS.writeSettings(settingsPath, settings); }
369
- note(` removed ${removed} hook entr${removed === 1 ? 'y' : 'ies'}${slRemoved ? ' + statusline' : ''} from settings.json`);
370
- touched++;
399
+ // Claude standalone hooks + statusline
400
+ const cfg = claudeDir(opts);
401
+ const settingsPath = path.join(cfg, 'settings.json');
402
+ const settings = SETTINGS.readSettings(settingsPath);
403
+ if (settings) {
404
+ const removed = SETTINGS.removeHooks(settings, 'chisle-');
405
+ let slRemoved = false;
406
+ if (settings.statusLine && typeof settings.statusLine.command === 'string'
407
+ && settings.statusLine.command.includes('chisle-statusline')) { delete settings.statusLine; slRemoved = true; }
408
+ if (removed > 0 || slRemoved) {
409
+ if (!opts.dryRun) { SETTINGS.validateHookFields(settings); SETTINGS.writeSettings(settingsPath, settings); }
410
+ note(` removed ${removed} hook entr${removed === 1 ? 'y' : 'ies'}${slRemoved ? ' + statusline' : ''} from settings.json`);
411
+ touched++;
412
+ }
413
+ }
414
+ const hooksDst = path.join(cfg, 'chisle-hooks');
415
+ if (fs.existsSync(hooksDst)) { if (!opts.dryRun) fs.rmSync(hooksDst, { recursive: true, force: true }); note(` removed ${hooksDst}`); touched++; }
416
+
417
+ for (const f of ['.chisle-active', '.chisle-session-turns', '.chisle-statusline-suffix',
418
+ '.chisle-compress-stats.json', '.chisle-compress-last.json', '.chisle-update-check.json']) {
419
+ const p = path.join(cfg, f);
420
+ if (fs.existsSync(p)) { if (!opts.dryRun) { try { fs.unlinkSync(p); } catch (_) {} } note(` removed ${p}`); touched++; }
371
421
  }
372
422
  }
373
- const hooksDst = path.join(cfg, 'chisle-hooks');
374
- if (fs.existsSync(hooksDst)) { if (!opts.dryRun) fs.rmSync(hooksDst, { recursive: true, force: true }); note(` removed ${hooksDst}`); touched++; }
375
423
 
376
- // Flag files
377
- for (const f of ['.chisle-active', '.chisle-session-turns', '.chisle-statusline-suffix',
378
- '.chisle-compress-stats.json', '.chisle-compress-last.json', '.chisle-update-check.json']) {
379
- const p = path.join(cfg, f);
380
- if (fs.existsSync(p)) { if (!opts.dryRun) { try { fs.unlinkSync(p); } catch (_) {} } note(` removed ${p}`); touched++; }
424
+ if (wants('pi') && hasCmd('pi')) {
425
+ const sources = piSources(capture('pi', ['list']).stdout);
426
+ for (const source of sources) {
427
+ if ((run('pi', ['remove', source], opts.dryRun).status || 0) === 0) touched++;
428
+ }
381
429
  }
382
430
 
383
- // Codex fenced block
384
- const codexMd = path.join(os.homedir(), '.codex', 'AGENTS.md');
385
- if (fs.existsSync(codexMd)) {
386
- const txt = fs.readFileSync(codexMd, 'utf8');
387
- if (txt.includes(FENCE_BEGIN)) {
388
- const stripped = txt.replace(new RegExp(`\\n?${FENCE_BEGIN}[\\s\\S]*?${FENCE_END}\\n?`), '\n').replace(/\n{3,}/g, '\n\n');
389
- if (!opts.dryRun) fs.writeFileSync(codexMd, stripped, { mode: 0o644 });
390
- note(` removed chisle block from ${codexMd}`); touched++;
431
+ if (wants('codex')) {
432
+ const codexMd = path.join(os.homedir(), '.codex', 'AGENTS.md');
433
+ if (fs.existsSync(codexMd)) {
434
+ const txt = fs.readFileSync(codexMd, 'utf8');
435
+ if (txt.includes(FENCE_BEGIN)) {
436
+ const stripped = txt.replace(new RegExp(`\\n?${FENCE_BEGIN}[\\s\\S]*?${FENCE_END}\\n?`), '\n').replace(/\n{3,}/g, '\n\n');
437
+ if (!opts.dryRun) fs.writeFileSync(codexMd, stripped, { mode: 0o644 });
438
+ note(` removed chisle block from ${codexMd}`); touched++;
439
+ }
391
440
  }
392
441
  }
393
442
 
394
- note('');
395
- note('Project-scoped rule files (.cursor/, .windsurf/, .clinerules/, .kiro/, .github/copilot-instructions.md)');
396
- note('live in your project repos — remove them per-project with git if you added them there.');
443
+ if (!opts.only.length || opts.only.some(id => PROVIDERS.find(p => p.id === id).scope === 'project')) {
444
+ note('');
445
+ note('Project-scoped rule files (.cursor/, .windsurf/, .clinerules/, .kiro/, .github/copilot-instructions.md)');
446
+ note('live in your project repos — remove them per-project with git if you added them there.');
447
+ }
397
448
  say(touched ? c.green(`\nUninstalled. ${touched} item(s) cleaned.`) : c.yellow('\nNothing to uninstall.'));
398
449
  }
399
450
 
@@ -406,7 +457,7 @@ Usage:
406
457
 
407
458
  Flags:
408
459
  --list Detect agents and print them; install nothing
409
- --only <id> Install only for one agent (repeatable)
460
+ --only <id> Limit install/uninstall to one agent (repeatable)
410
461
  --dry-run Print actions, change nothing
411
462
  --force Reinstall / overwrite even if already present
412
463
  --uninstall, -u Remove what chisle installed
@@ -463,6 +514,7 @@ function main() {
463
514
 
464
515
  for (const p of targets) {
465
516
  if (p.id === 'claude') installClaude(ctx);
517
+ else if (p.id === 'pi') installPi(ctx);
466
518
  else if (p.id === 'gemini') installGemini(ctx);
467
519
  else if (p.id === 'codex') installCodex(ctx);
468
520
  else installProjectRule(ctx, p);
@@ -476,7 +528,7 @@ function main() {
476
528
  if (results.failed.length) say(c.red(` failed: ${results.failed.map(s => s[0] + ' (' + s[1] + ')').join(', ')}`));
477
529
  say('');
478
530
  if (!opts.dryRun && results.installed.length) {
479
- say(c.orange('Done.') + ' Restart your agent. Type ' + c.orange('/chisle') + ' (Claude Code) or just start coding.');
531
+ say(c.orange('Done.') + ' Restart your agent. Type ' + c.orange('/chisle') + ' in Claude Code or Pi, or just start coding.');
480
532
  note('If Chisle earns its keep, a star helps others find it: https://github.com/JayPokale/Chisle');
481
533
  }
482
534
  process.exit(results.failed.length ? 1 : 0);