chisle 3.2.0 → 3.3.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/.claude-plugin/plugin.json +1 -1
- package/README.md +20 -11
- package/bin/install.js +63 -5
- package/hooks/chisle-compress-output.js +54 -8
- package/package.json +1 -1
- package/pi-extension/index.js +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chisle",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.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": [
|
package/README.md
CHANGED
|
@@ -144,21 +144,30 @@ Preview first with `npx chisle --dry-run`, scope with `--only claude` or `--only
|
|
|
144
144
|
|
|
145
145
|
### Upgrading
|
|
146
146
|
|
|
147
|
-
|
|
147
|
+
```bash
|
|
148
|
+
npx chisle@latest --update
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
That refreshes every agent that already has Chisle and installs it into none that don't. Two details it exists to handle:
|
|
152
|
+
|
|
153
|
+
- **Plain `npx chisle` does not upgrade.** Every install path skips what is already present, so an upgrade run reports success and changes nothing. `--update` pairs the refresh with that check.
|
|
154
|
+
- **`@latest` matters.** `npx chisle` can serve a cached copy of the package from a previous run, so the pin is what guarantees you get the new one.
|
|
155
|
+
|
|
156
|
+
Per-agent equivalents, if you prefer the native tool:
|
|
148
157
|
|
|
149
158
|
```bash
|
|
150
|
-
|
|
151
|
-
|
|
159
|
+
claude plugin update chisle@chisle # Claude Code plugin install
|
|
160
|
+
pi install npm:chisle # Pi package
|
|
161
|
+
gemini extensions install https://github.com/JayPokale/Chisle
|
|
152
162
|
```
|
|
153
163
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
164
|
+
Project-scoped agents (Cursor, Windsurf, Cline, Kiro, Copilot) keep their rule file inside the repo, so run the update once per project that has one.
|
|
165
|
+
|
|
166
|
+
Not sure what you are running? `npx chisle --list` prints the agents it detects, and `claude plugin list` shows the installed plugin and its version.
|
|
167
|
+
|
|
168
|
+
Chisle checks npm on session start and mentions it once when a **major** version is out (cached 3 days, `CHISLE_UPDATE_CHECK=0` to silence). Minor and patch releases stay quiet on purpose.
|
|
157
169
|
|
|
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.
|
|
170
|
+
Upgrading to 3.0.0 from 2.x needs nothing: a `lite`/`full`/`ultra` value in `CHISLE_DEFAULT_MODE` or `config.json` is no longer meaningful, falls through to the default, and Chisle stays active. It says so once so the setting is not ignored silently. Replace it with `on`/`off` or delete it.
|
|
162
171
|
|
|
163
172
|
### Claude Code plugin (marketplace)
|
|
164
173
|
|
|
@@ -331,7 +340,7 @@ node benchmarks/replay-compress.js # Claude Code
|
|
|
331
340
|
node benchmarks/replay-compress.js pi # Pi
|
|
332
341
|
```
|
|
333
342
|
|
|
334
|
-
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.
|
|
343
|
+
Outputs over 8k chars are elided, and the elided original spills to `<config>/chisle-spill/` so the dropped middle stays reachable. The marker carries the path, so recovering one line is a targeted grep rather than a re-run of the command, which matters most when the command is not idempotent: a test run, a build, a `git log` at a moment in time. The newest 40 spills are kept, owner-readable only. `stop chisle`, `CHISLE_COMPRESS=0`, `CHISLE_COMPRESS_SCRUB=0`, `CHISLE_COMPRESS_DEDUP=0`, `CHISLE_COMPRESS_SPILL=0`. Every tier has an off switch.
|
|
335
344
|
|
|
336
345
|
### Pi arm: where the output axis lost
|
|
337
346
|
|
package/bin/install.js
CHANGED
|
@@ -58,7 +58,7 @@ const PROVIDERS = [
|
|
|
58
58
|
// ── argv ────────────────────────────────────────────────────────────────────
|
|
59
59
|
function parseArgs(argv) {
|
|
60
60
|
const opts = { dryRun: false, force: false, list: false, uninstall: false,
|
|
61
|
-
noColor: false, help: false, stats: false, only: [], configDir: null };
|
|
61
|
+
noColor: false, help: false, stats: false, update: false, only: [], configDir: null };
|
|
62
62
|
for (let i = 0; i < argv.length; i++) {
|
|
63
63
|
const a = argv[i];
|
|
64
64
|
switch (a) {
|
|
@@ -66,6 +66,9 @@ function parseArgs(argv) {
|
|
|
66
66
|
case '--force': opts.force = true; break;
|
|
67
67
|
case '--list': opts.list = true; break;
|
|
68
68
|
case '--stats': opts.stats = true; break;
|
|
69
|
+
// --update is --force narrowed to what is already there: refresh every
|
|
70
|
+
// agent that has Chisle, add it to none that don't.
|
|
71
|
+
case '--update': opts.update = true; opts.force = true; break;
|
|
69
72
|
case '--uninstall': case '-u': opts.uninstall = true; break;
|
|
70
73
|
case '--no-color': opts.noColor = true; break;
|
|
71
74
|
case '-h': case '--help': opts.help = true; break;
|
|
@@ -165,6 +168,49 @@ function claudeDir(opts) {
|
|
|
165
168
|
return path.join(os.homedir(), '.claude');
|
|
166
169
|
}
|
|
167
170
|
|
|
171
|
+
// ── what is already installed ───────────────────────────────────────────────
|
|
172
|
+
// Every install path skips when Chisle is already present, which is right for
|
|
173
|
+
// `npx chisle` and useless for an upgrade: the run reports success and changes
|
|
174
|
+
// nothing. --update pairs this check with --force so a refresh touches exactly
|
|
175
|
+
// the agents that already have it.
|
|
176
|
+
function installedFor(id, opts) {
|
|
177
|
+
try {
|
|
178
|
+
switch (id) {
|
|
179
|
+
case 'claude': {
|
|
180
|
+
if (hasCmd('claude')) {
|
|
181
|
+
const r = capture('claude', ['plugin', 'list']);
|
|
182
|
+
if (r.status === 0 && /chisle/i.test(r.stdout || '')) return true;
|
|
183
|
+
}
|
|
184
|
+
const settings = SETTINGS.readSettings(path.join(claudeDir(opts), 'settings.json'));
|
|
185
|
+
const hooks = (settings && settings.hooks) || {};
|
|
186
|
+
for (const event of Object.keys(hooks)) {
|
|
187
|
+
for (const entry of hooks[event] || []) {
|
|
188
|
+
for (const h of (entry && entry.hooks) || []) {
|
|
189
|
+
if (typeof h.command === 'string' && h.command.includes('chisle-')) return true;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
case 'pi':
|
|
196
|
+
return hasCmd('pi') && piSources(capture('pi', ['list']).stdout).length > 0;
|
|
197
|
+
case 'gemini': {
|
|
198
|
+
if (!hasCmd('gemini')) return false;
|
|
199
|
+
const r = capture('gemini', ['extensions', 'list']);
|
|
200
|
+
return r.status === 0 && /chisle/i.test(r.stdout || '');
|
|
201
|
+
}
|
|
202
|
+
case 'codex': {
|
|
203
|
+
const md = path.join(os.homedir(), '.codex', 'AGENTS.md');
|
|
204
|
+
return fs.existsSync(md) && fs.readFileSync(md, 'utf8').includes(FENCE_BEGIN);
|
|
205
|
+
}
|
|
206
|
+
default: {
|
|
207
|
+
const prov = PROVIDERS.find(x => x.id === id);
|
|
208
|
+
return !!(prov && prov.rule && fs.existsSync(path.join(process.cwd(), prov.rule)));
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
} catch (e) { return false; }
|
|
212
|
+
}
|
|
213
|
+
|
|
168
214
|
// ── Claude Code ─────────────────────────────────────────────────────────────
|
|
169
215
|
function installClaude(ctx) {
|
|
170
216
|
const { say, note, warn, opts, results } = ctx;
|
|
@@ -412,8 +458,10 @@ function uninstall(ctx) {
|
|
|
412
458
|
touched++;
|
|
413
459
|
}
|
|
414
460
|
}
|
|
415
|
-
const
|
|
416
|
-
|
|
461
|
+
for (const d of ['chisle-hooks', 'chisle-spill']) {
|
|
462
|
+
const dst = path.join(cfg, d);
|
|
463
|
+
if (fs.existsSync(dst)) { if (!opts.dryRun) fs.rmSync(dst, { recursive: true, force: true }); note(` removed ${dst}`); touched++; }
|
|
464
|
+
}
|
|
417
465
|
|
|
418
466
|
for (const f of ['.chisle-active', '.chisle-session-turns', '.chisle-statusline-suffix',
|
|
419
467
|
'.chisle-compress-stats.json', '.chisle-compress-last.json', '.chisle-update-check.json']) {
|
|
@@ -459,6 +507,7 @@ Usage:
|
|
|
459
507
|
Flags:
|
|
460
508
|
--list Detect agents and print them; install nothing
|
|
461
509
|
--stats Print tool-output savings recorded so far; change nothing
|
|
510
|
+
--update Refresh every agent that already has Chisle; install none
|
|
462
511
|
--only <id> Limit install/uninstall to one agent (repeatable)
|
|
463
512
|
--dry-run Print actions, change nothing
|
|
464
513
|
--force Reinstall / overwrite even if already present
|
|
@@ -474,6 +523,7 @@ Examples:
|
|
|
474
523
|
npx chisle --only claude # just Claude Code
|
|
475
524
|
npx chisle --dry-run # preview
|
|
476
525
|
npx chisle --stats # what the compressor has saved
|
|
526
|
+
npx chisle@latest --update # upgrade what is already installed
|
|
477
527
|
`);
|
|
478
528
|
}
|
|
479
529
|
|
|
@@ -544,12 +594,20 @@ function main() {
|
|
|
544
594
|
|
|
545
595
|
if (opts.uninstall) return uninstall(ctx);
|
|
546
596
|
|
|
547
|
-
say(c.orange('╭─ Chisle installer ─╮'));
|
|
597
|
+
say(c.orange(opts.update ? '╭─ Chisle updater ─╮' : '╭─ Chisle installer ─╮'));
|
|
548
598
|
say(c.dim(opts.dryRun ? ' (dry run, nothing will change)' : ' maximum signal, minimum noise'));
|
|
549
599
|
say('');
|
|
550
600
|
|
|
551
|
-
|
|
601
|
+
let targets = PROVIDERS.filter(p => opts.only.length ? opts.only.includes(p.id) : detectMatch(p.detect));
|
|
602
|
+
if (opts.update) targets = targets.filter(p => installedFor(p.id, opts));
|
|
552
603
|
if (targets.length === 0) {
|
|
604
|
+
if (opts.update) {
|
|
605
|
+
warn('Nothing to update: Chisle is not installed for any detected agent.');
|
|
606
|
+
note('Run `npx chisle` to install it, or `npx chisle --list` to see what we look for.');
|
|
607
|
+
note('Project-scoped agents (Cursor, Windsurf, Cline, Kiro, Copilot) are per-repo:');
|
|
608
|
+
note('run this from the project that has the rule file.');
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
553
611
|
warn('No supported agents detected.');
|
|
554
612
|
note('Run `npx chisle --list` to see what we look for, or `--only <id>` to force one.');
|
|
555
613
|
return;
|
|
@@ -31,8 +31,11 @@
|
|
|
31
31
|
// CHISLE_COMPRESS_HEAD_LINES — lines kept from the top
|
|
32
32
|
// CHISLE_COMPRESS_TAIL_LINES — lines kept from the bottom
|
|
33
33
|
// CHISLE_COMPRESS_TOOLS=Bash,Grep — override the tool allowlist
|
|
34
|
+
// CHISLE_COMPRESS_SPILL=0 — elide without writing the recovery copy
|
|
34
35
|
//
|
|
35
36
|
// Savings accrue in <claudeDir>/.chisle-compress-stats.json for the statusline.
|
|
37
|
+
// Elided originals spill to <claudeDir>/chisle-spill/ so the dropped middle can
|
|
38
|
+
// be grepped back instead of re-running the command; the newest 40 are kept.
|
|
36
39
|
|
|
37
40
|
const fs = require('fs');
|
|
38
41
|
const path = require('path');
|
|
@@ -174,17 +177,59 @@ function dedupCheck(toolName, text, sessionId, toolUseId) {
|
|
|
174
177
|
// ── Tier 2: elision ──────────────────────────────────────────────────────────
|
|
175
178
|
// Keep head + tail; salvage error-looking lines from the elided middle so the
|
|
176
179
|
// one line that mattered in a 3000-line build log survives the cut.
|
|
177
|
-
|
|
180
|
+
// ── recoverable elision ─────────────────────────────────────────────────────
|
|
181
|
+
// Elision used to destroy the middle. If the agent then needed a line from it,
|
|
182
|
+
// its only recourse was re-running the command: more expensive than the elision
|
|
183
|
+
// saved, and wrong outright when the command is not idempotent (a test run, a
|
|
184
|
+
// build, `git log` at a moment in time). So the full text spills to disk first
|
|
185
|
+
// and the marker carries the path. Recovery becomes a targeted grep instead of
|
|
186
|
+
// a re-run. Best-effort throughout: if the spill fails, the elision still
|
|
187
|
+
// happens, it just loses the escape hatch.
|
|
188
|
+
const SPILL_DIR = 'chisle-spill';
|
|
189
|
+
const SPILL_KEEP = 40;
|
|
190
|
+
|
|
191
|
+
function spillDir() {
|
|
192
|
+
return path.join(getClaudeDir(), SPILL_DIR);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Keep the directory bounded. Oldest-first by mtime; cheap enough at this size
|
|
196
|
+
// and it runs at most once per elided output.
|
|
197
|
+
function pruneSpill(dir) {
|
|
198
|
+
try {
|
|
199
|
+
const files = fs.readdirSync(dir)
|
|
200
|
+
.filter(f => f.endsWith('.txt'))
|
|
201
|
+
.map(f => { const p = path.join(dir, f); return { p, t: fs.statSync(p).mtimeMs }; })
|
|
202
|
+
.sort((a, b) => b.t - a.t);
|
|
203
|
+
for (const f of files.slice(SPILL_KEEP)) { try { fs.unlinkSync(f.p); } catch (e) {} }
|
|
204
|
+
} catch (e) {}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function spill(text, toolName) {
|
|
208
|
+
if (process.env.CHISLE_COMPRESS_SPILL === '0') return null;
|
|
209
|
+
try {
|
|
210
|
+
const dir = spillDir();
|
|
211
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
212
|
+
const hash = crypto.createHash('sha256').update(text).digest('hex').slice(0, 12);
|
|
213
|
+
const safeTool = String(toolName || 'tool').replace(/[^A-Za-z0-9_-]/g, '') || 'tool';
|
|
214
|
+
const file = path.join(dir, `${safeTool}-${hash}.txt`);
|
|
215
|
+
if (!fs.existsSync(file)) fs.writeFileSync(file, text, { mode: 0o600 });
|
|
216
|
+
pruneSpill(dir);
|
|
217
|
+
return file;
|
|
218
|
+
} catch (e) { return null; }
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function compress(text, limits, spillPath) {
|
|
178
222
|
const { maxChars, headLines, tailLines } = limits;
|
|
179
223
|
const lines = text.split('\n');
|
|
224
|
+
const recover = spillPath ? ' Full output: ' + spillPath + ' (grep it, do not re-run)' : '';
|
|
180
225
|
|
|
181
226
|
if (lines.length <= headLines + tailLines) {
|
|
182
|
-
// Big in bytes, few lines (one giant line)
|
|
227
|
+
// Big in bytes, few lines (one giant line): hard char cut.
|
|
183
228
|
const keep = Math.floor(maxChars / 2);
|
|
184
229
|
const elided = text.length - 2 * keep;
|
|
185
230
|
if (elided <= 0) return null;
|
|
186
231
|
return text.slice(0, keep) +
|
|
187
|
-
'\n... [chisle: elided ' + elided.toLocaleString('en-US') + ' chars from the middle] ...\n' +
|
|
232
|
+
'\n... [chisle: elided ' + elided.toLocaleString('en-US') + ' chars from the middle.' + recover + '] ...\n' +
|
|
188
233
|
text.slice(-keep);
|
|
189
234
|
}
|
|
190
235
|
|
|
@@ -199,8 +244,9 @@ function compress(text, limits) {
|
|
|
199
244
|
}
|
|
200
245
|
|
|
201
246
|
const marker = '... [chisle: elided ' + middle.length.toLocaleString('en-US') +
|
|
202
|
-
' lines
|
|
203
|
-
(salvaged.length ? ', and ' + salvaged.length + ' error-like line(s) below' : '') +
|
|
247
|
+
' lines, kept first ' + headLines + ', last ' + tailLines +
|
|
248
|
+
(salvaged.length ? ', and ' + salvaged.length + ' error-like line(s) below' : '') +
|
|
249
|
+
'.' + recover + '] ...';
|
|
204
250
|
|
|
205
251
|
const out = head.concat([marker], salvaged, tail).join('\n');
|
|
206
252
|
return out.length < text.length ? out : null;
|
|
@@ -227,11 +273,11 @@ function recordSavings(saved) {
|
|
|
227
273
|
|
|
228
274
|
// Scrub + elide on plain text → transformed text, or null if no meaningful
|
|
229
275
|
// win. Stateless — this is what the replay benchmark measures.
|
|
230
|
-
function transform(text, limits) {
|
|
276
|
+
function transform(text, limits, toolName) {
|
|
231
277
|
if (!text || text.length <= SCRUB_MIN) return null;
|
|
232
278
|
let t = process.env.CHISLE_COMPRESS_SCRUB === '0' ? text : scrub(text);
|
|
233
279
|
if (t.length > limits.maxChars) {
|
|
234
|
-
const elided = compress(t, limits);
|
|
280
|
+
const elided = compress(t, limits, toolName ? spill(t, toolName) : null);
|
|
235
281
|
if (elided != null) t = elided;
|
|
236
282
|
}
|
|
237
283
|
return text.length - t.length >= MIN_WIN ? t : null;
|
|
@@ -275,7 +321,7 @@ function processPayload(payload, mode) {
|
|
|
275
321
|
if (!text) return null;
|
|
276
322
|
const dup = dedupCheck(payload.tool_name, text, payload.session_id, payload.tool_use_id);
|
|
277
323
|
if (dup != null && dup.length < text.length) return dup;
|
|
278
|
-
return transform(text, limitsFor());
|
|
324
|
+
return transform(text, limitsFor(), payload.tool_name);
|
|
279
325
|
}
|
|
280
326
|
|
|
281
327
|
function main() {
|
package/package.json
CHANGED
package/pi-extension/index.js
CHANGED
|
@@ -166,7 +166,7 @@ function chisleExtension(pi) {
|
|
|
166
166
|
lastByToolBlock.set(key, hash);
|
|
167
167
|
if (previous === hash) updated = duplicateMarker(event.toolName, text, 'earlier');
|
|
168
168
|
}
|
|
169
|
-
if (updated == null) updated = transform(text, limitsFor());
|
|
169
|
+
if (updated == null) updated = transform(text, limitsFor(), event.toolName);
|
|
170
170
|
if (updated == null || updated.length >= text.length) return block;
|
|
171
171
|
changed = true;
|
|
172
172
|
savedChars += text.length - updated.length;
|