squeezr-ai 1.81.1 → 1.90.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/bin/squeezr.js +82 -1
- package/dist/__tests__/bench.test.d.ts +1 -0
- package/dist/__tests__/bench.test.js +55 -0
- package/dist/__tests__/cacheAligner.test.d.ts +1 -0
- package/dist/__tests__/cacheAligner.test.js +75 -0
- package/dist/__tests__/codeStructure.test.d.ts +1 -0
- package/dist/__tests__/codeStructure.test.js +74 -0
- package/dist/__tests__/deterministic.test.js +13 -0
- package/dist/__tests__/doctor.test.d.ts +1 -0
- package/dist/__tests__/doctor.test.js +70 -0
- package/dist/__tests__/jsonCrush.test.d.ts +1 -0
- package/dist/__tests__/jsonCrush.test.js +110 -0
- package/dist/__tests__/learn.test.d.ts +1 -0
- package/dist/__tests__/learn.test.js +159 -0
- package/dist/__tests__/outputShaper.test.d.ts +1 -0
- package/dist/__tests__/outputShaper.test.js +216 -0
- package/dist/__tests__/textCrusher.test.d.ts +1 -0
- package/dist/__tests__/textCrusher.test.js +71 -0
- package/dist/bench.d.ts +45 -0
- package/dist/bench.js +114 -0
- package/dist/cacheAligner.d.ts +34 -0
- package/dist/cacheAligner.js +73 -0
- package/dist/compressor.js +11 -1
- package/dist/config.d.ts +6 -0
- package/dist/config.js +25 -0
- package/dist/dashboard.d.ts +1 -1
- package/dist/dashboard.js +2 -2
- package/dist/deterministic.d.ts +3 -0
- package/dist/deterministic.js +37 -5
- package/dist/doctor.d.ts +33 -0
- package/dist/doctor.js +102 -0
- package/dist/jsonCrush.d.ts +30 -0
- package/dist/jsonCrush.js +112 -0
- package/dist/learn.d.ts +65 -0
- package/dist/learn.js +244 -0
- package/dist/outputShaper.d.ts +71 -0
- package/dist/outputShaper.js +155 -0
- package/dist/server.js +22 -0
- package/dist/textCrusher.d.ts +31 -0
- package/dist/textCrusher.js +98 -0
- package/package.json +69 -69
- package/squeezr.toml +15 -1
package/bin/squeezr.js
CHANGED
|
@@ -5,7 +5,7 @@ import http from 'http'
|
|
|
5
5
|
import path from 'path'
|
|
6
6
|
import fs from 'fs'
|
|
7
7
|
import os from 'os'
|
|
8
|
-
import { fileURLToPath } from 'url'
|
|
8
|
+
import { fileURLToPath, pathToFileURL } from 'url'
|
|
9
9
|
import { createRequire } from 'module'
|
|
10
10
|
|
|
11
11
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
@@ -250,8 +250,14 @@ Usage:
|
|
|
250
250
|
squeezr gain Show token savings stats
|
|
251
251
|
squeezr gain --reset Reset saved stats
|
|
252
252
|
squeezr discover Show pattern coverage report (proxy must be running)
|
|
253
|
+
squeezr learn Mine Claude Code sessions for token-wasting loops
|
|
254
|
+
squeezr learn --apply Write the learned rules to ./CLAUDE.local.md
|
|
255
|
+
squeezr bench Run the compression + fact-recall benchmark
|
|
253
256
|
squeezr status Check if proxy is running
|
|
257
|
+
squeezr doctor Diagnose proxy/version/routing/bypass (exit 0/1/2)
|
|
254
258
|
squeezr config Print config file path and current settings
|
|
259
|
+
squeezr rc [args...] Launch 'claude --rc' (Remote Control) direct to api.anthropic.com
|
|
260
|
+
(proxy bypassed for that session — Claude Code requires it)
|
|
255
261
|
squeezr mcp install Register Squeezr MCP server in Claude Code, Cursor, Windsurf & Cline
|
|
256
262
|
squeezr mcp uninstall Remove Squeezr MCP registration
|
|
257
263
|
squeezr ports Change HTTP and MITM proxy ports
|
|
@@ -284,6 +290,47 @@ function runNode(script, extraArgs = []) {
|
|
|
284
290
|
child.on('exit', code => process.exit(code ?? 0))
|
|
285
291
|
}
|
|
286
292
|
|
|
293
|
+
// Launch Claude Code with Remote Control, bypassing the Squeezr proxy for THIS
|
|
294
|
+
// session only. Claude Code refuses Remote Control unless it talks DIRECTLY to
|
|
295
|
+
// api.anthropic.com — the binary guard is literally "Remote Control is only
|
|
296
|
+
// available when using Claude via api.anthropic.com." Squeezr's terminal setup
|
|
297
|
+
// exports ANTHROPIC_BASE_URL=http://localhost:<port>, which is not that host, so
|
|
298
|
+
// `claude --rc` errors out. We strip that var (and the MITM CA) from the CHILD
|
|
299
|
+
// env only. Global config is untouched: every other `claude` session still routes
|
|
300
|
+
// through Squeezr and compresses normally. Trade-off: this remote session is not
|
|
301
|
+
// compressed — remote sessions are short/interactive, so that's the right call.
|
|
302
|
+
function claudeRc(extraArgs) {
|
|
303
|
+
const env = { ...process.env }
|
|
304
|
+
delete env.ANTHROPIC_BASE_URL
|
|
305
|
+
delete env.anthropic_base_url
|
|
306
|
+
delete env.NODE_EXTRA_CA_CERTS // Squeezr's MITM CA — irrelevant talking direct
|
|
307
|
+
|
|
308
|
+
// Ensure --rc / --remote-control is present exactly once, then pass the rest through.
|
|
309
|
+
const rest = extraArgs.filter(a => a !== 'rc' && a !== 'remote-control')
|
|
310
|
+
const hasRc = rest.some(a => a === '--rc' || a === '--remote-control')
|
|
311
|
+
const claudeArgs = hasRc ? rest : ['--rc', ...rest]
|
|
312
|
+
|
|
313
|
+
console.log('Launching Claude Code with Remote Control — direct to api.anthropic.com')
|
|
314
|
+
console.log('(Squeezr proxy bypassed for THIS session only; global config untouched).\n')
|
|
315
|
+
|
|
316
|
+
// Never resolves — the child owns the terminal (stdio inherited) and we exit the
|
|
317
|
+
// wrapper with the child's code. Awaiting this blocks the trailing update banner
|
|
318
|
+
// from printing over Claude's interactive UI.
|
|
319
|
+
return new Promise(() => {
|
|
320
|
+
const child = spawn('claude', claudeArgs, {
|
|
321
|
+
stdio: 'inherit',
|
|
322
|
+
env,
|
|
323
|
+
shell: process.platform === 'win32', // resolve claude.exe via PATH on Windows
|
|
324
|
+
})
|
|
325
|
+
child.on('exit', code => process.exit(code ?? 0))
|
|
326
|
+
child.on('error', err => {
|
|
327
|
+
console.error(`\nFailed to launch claude: ${err.message}`)
|
|
328
|
+
console.error('Is Claude Code installed and on your PATH? Try running `claude --version`.')
|
|
329
|
+
process.exit(1)
|
|
330
|
+
})
|
|
331
|
+
})
|
|
332
|
+
}
|
|
333
|
+
|
|
287
334
|
async function startDaemon() {
|
|
288
335
|
const distIndex = path.join(ROOT, 'dist', 'index.js')
|
|
289
336
|
if (!fs.existsSync(distIndex)) {
|
|
@@ -2504,6 +2551,11 @@ switch (command) {
|
|
|
2504
2551
|
showConfig()
|
|
2505
2552
|
break
|
|
2506
2553
|
|
|
2554
|
+
case 'rc':
|
|
2555
|
+
case 'remote-control':
|
|
2556
|
+
await claudeRc(args.slice(1))
|
|
2557
|
+
break
|
|
2558
|
+
|
|
2507
2559
|
case 'mcp': {
|
|
2508
2560
|
const subCmd = args[1] ?? 'install'
|
|
2509
2561
|
if (subCmd === 'uninstall') await mcpUninstall()
|
|
@@ -2514,6 +2566,35 @@ case 'zest':
|
|
|
2514
2566
|
await installZest()
|
|
2515
2567
|
break
|
|
2516
2568
|
|
|
2569
|
+
case 'doctor': {
|
|
2570
|
+
const { runDoctor } = await import(pathToFileURL(path.join(ROOT, 'dist', 'doctor.js')).href)
|
|
2571
|
+
const res = await runDoctor()
|
|
2572
|
+
console.log(res.report)
|
|
2573
|
+
if (command !== 'update') await showUpdateBanner()
|
|
2574
|
+
process.exit(res.exitCode)
|
|
2575
|
+
break
|
|
2576
|
+
}
|
|
2577
|
+
|
|
2578
|
+
case 'bench': {
|
|
2579
|
+
const { runBench, formatBench } = await import(pathToFileURL(path.join(ROOT, 'dist', 'bench.js')).href)
|
|
2580
|
+
console.log(formatBench(runBench()))
|
|
2581
|
+
break
|
|
2582
|
+
}
|
|
2583
|
+
|
|
2584
|
+
case 'learn': {
|
|
2585
|
+
const apply = args.includes('--apply')
|
|
2586
|
+
const tIdx = args.indexOf('--target')
|
|
2587
|
+
const targetFile = tIdx >= 0 ? args[tIdx + 1] : undefined
|
|
2588
|
+
const { runLearn } = await import(pathToFileURL(path.join(ROOT, 'dist', 'learn.js')).href)
|
|
2589
|
+
const res = runLearn({ apply, targetFile })
|
|
2590
|
+
console.log(res.report)
|
|
2591
|
+
if (res.loops.length > 0) {
|
|
2592
|
+
if (res.applied) console.log(`\nWrote ${res.loops.length} rule(s) to ${res.targetFile}`)
|
|
2593
|
+
else console.log(`\nDry run. Re-run with --apply to write these rules to ${res.targetFile}.`)
|
|
2594
|
+
}
|
|
2595
|
+
break
|
|
2596
|
+
}
|
|
2597
|
+
|
|
2517
2598
|
case 'version':
|
|
2518
2599
|
case '--version':
|
|
2519
2600
|
case '-v':
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { factRecall, runBench, FIXTURES } from '../bench.js';
|
|
3
|
+
describe('factRecall', () => {
|
|
4
|
+
it('returns 1 when every fact is present verbatim', () => {
|
|
5
|
+
expect(factRecall('error E123 in file foo.ts at line 42', ['E123', 'foo.ts', '42'])).toBe(1);
|
|
6
|
+
});
|
|
7
|
+
it('returns the fraction present', () => {
|
|
8
|
+
expect(factRecall('only E123 here', ['E123', 'MISSING'])).toBe(0.5);
|
|
9
|
+
});
|
|
10
|
+
it('returns 1 for an empty fact list (nothing to lose)', () => {
|
|
11
|
+
expect(factRecall('whatever', [])).toBe(1);
|
|
12
|
+
});
|
|
13
|
+
});
|
|
14
|
+
describe('FIXTURES', () => {
|
|
15
|
+
it('every fixture declares its critical facts', () => {
|
|
16
|
+
expect(FIXTURES.length).toBeGreaterThan(0);
|
|
17
|
+
for (const f of FIXTURES) {
|
|
18
|
+
expect(f.facts.length).toBeGreaterThan(0);
|
|
19
|
+
// sanity: the facts must actually be in the original content
|
|
20
|
+
for (const fact of f.facts)
|
|
21
|
+
expect(f.content.includes(fact)).toBe(true);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
describe('runBench', () => {
|
|
26
|
+
it('produces a row per fixture with a valid shape', () => {
|
|
27
|
+
const summary = runBench();
|
|
28
|
+
expect(summary.rows.length).toBe(FIXTURES.length);
|
|
29
|
+
for (const r of summary.rows) {
|
|
30
|
+
expect(r.originalChars).toBeGreaterThan(0);
|
|
31
|
+
expect(r.compressedChars).toBeGreaterThan(0);
|
|
32
|
+
expect(r.compressionPct).toBeGreaterThanOrEqual(0);
|
|
33
|
+
expect(r.inlineRecall).toBeGreaterThanOrEqual(0);
|
|
34
|
+
expect(r.inlineRecall).toBeLessThanOrEqual(1);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
it('achieves real compression on at least one fixture', () => {
|
|
38
|
+
const summary = runBench();
|
|
39
|
+
expect(summary.rows.some(r => r.compressionPct > 10)).toBe(true);
|
|
40
|
+
});
|
|
41
|
+
it('never drops a critical fact from the highly-compressible JSON fixture', () => {
|
|
42
|
+
// The homogeneous JSON array is crushed to a table — all VALUES must stay inline.
|
|
43
|
+
const summary = runBench();
|
|
44
|
+
const json = summary.rows.find(r => r.name.includes('json'));
|
|
45
|
+
expect(json).toBeDefined();
|
|
46
|
+
expect(json.compressionPct).toBeGreaterThan(10);
|
|
47
|
+
expect(json.inlineRecall).toBe(1);
|
|
48
|
+
});
|
|
49
|
+
it('reports honest averages', () => {
|
|
50
|
+
const summary = runBench();
|
|
51
|
+
expect(summary.avgCompressionPct).toBeGreaterThan(0);
|
|
52
|
+
expect(summary.avgInlineRecall).toBeGreaterThan(0);
|
|
53
|
+
expect(summary.avgInlineRecall).toBeLessThanOrEqual(1);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { detectVolatile, analyzeSystemPrompt, formatVolatileWarning, _resetWarnedForTest, warnIfVolatile, } from '../cacheAligner.js';
|
|
3
|
+
describe('detectVolatile', () => {
|
|
4
|
+
it('detects a UUID', () => {
|
|
5
|
+
const f = detectVolatile('session 550e8400-e29b-41d4-a716-446655440000 started');
|
|
6
|
+
expect(f.find(x => x.kind === 'uuid')?.count).toBe(1);
|
|
7
|
+
});
|
|
8
|
+
it('detects ISO timestamps', () => {
|
|
9
|
+
const f = detectVolatile('at 2026-07-21T10:00:00Z and 2026-07-21T11:00:00Z');
|
|
10
|
+
expect(f.find(x => x.kind === 'timestamp')?.count).toBe(2);
|
|
11
|
+
});
|
|
12
|
+
it('detects a JWT', () => {
|
|
13
|
+
const jwt = 'eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';
|
|
14
|
+
const f = detectVolatile(`token=${jwt}`);
|
|
15
|
+
expect(f.find(x => x.kind === 'jwt')?.count).toBe(1);
|
|
16
|
+
});
|
|
17
|
+
it('detects long hex hashes', () => {
|
|
18
|
+
const sha = 'a'.repeat(64);
|
|
19
|
+
const f = detectVolatile(`commit ${sha}`);
|
|
20
|
+
expect(f.find(x => x.kind === 'hash')?.count).toBe(1);
|
|
21
|
+
});
|
|
22
|
+
it('returns nothing for clean stable text', () => {
|
|
23
|
+
expect(detectVolatile('You are a helpful coding assistant. Follow the rules.')).toEqual([]);
|
|
24
|
+
});
|
|
25
|
+
it('does not double-count a JWT as a hash', () => {
|
|
26
|
+
const jwt = 'eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';
|
|
27
|
+
const f = detectVolatile(jwt);
|
|
28
|
+
expect(f.find(x => x.kind === 'hash')).toBeUndefined();
|
|
29
|
+
expect(f.find(x => x.kind === 'jwt')?.count).toBe(1);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
describe('analyzeSystemPrompt', () => {
|
|
33
|
+
it('handles a string system prompt', () => {
|
|
34
|
+
const r = analyzeSystemPrompt('build id 550e8400-e29b-41d4-a716-446655440000');
|
|
35
|
+
expect(r.hasVolatile).toBe(true);
|
|
36
|
+
expect(r.findings.length).toBeGreaterThan(0);
|
|
37
|
+
});
|
|
38
|
+
it('aggregates across an array of text blocks', () => {
|
|
39
|
+
const sys = [
|
|
40
|
+
{ type: 'text', text: 'stable preamble' },
|
|
41
|
+
{ type: 'text', text: 'ts 2026-07-21T10:00:00Z' },
|
|
42
|
+
];
|
|
43
|
+
const r = analyzeSystemPrompt(sys);
|
|
44
|
+
expect(r.findings.find(f => f.kind === 'timestamp')?.count).toBe(1);
|
|
45
|
+
});
|
|
46
|
+
it('reports clean when there is nothing volatile', () => {
|
|
47
|
+
const r = analyzeSystemPrompt('You are Claude, a coding assistant.');
|
|
48
|
+
expect(r.hasVolatile).toBe(false);
|
|
49
|
+
expect(r.findings).toEqual([]);
|
|
50
|
+
});
|
|
51
|
+
it('ignores non-text blocks and undefined', () => {
|
|
52
|
+
expect(analyzeSystemPrompt(undefined).hasVolatile).toBe(false);
|
|
53
|
+
const sys = [{ type: 'image', source: {} }, { type: 'text', text: 'clean' }];
|
|
54
|
+
expect(analyzeSystemPrompt(sys).hasVolatile).toBe(false);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
describe('formatVolatileWarning', () => {
|
|
58
|
+
it('mentions each detected kind', () => {
|
|
59
|
+
const msg = formatVolatileWarning([{ kind: 'uuid', count: 2 }, { kind: 'timestamp', count: 1 }]);
|
|
60
|
+
expect(msg.includes('uuid')).toBe(true);
|
|
61
|
+
expect(msg.includes('timestamp')).toBe(true);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
describe('warnIfVolatile', () => {
|
|
65
|
+
it('warns once per distinct finding signature (no per-request spam)', () => {
|
|
66
|
+
_resetWarnedForTest();
|
|
67
|
+
const sys = 'id 550e8400-e29b-41d4-a716-446655440000';
|
|
68
|
+
expect(warnIfVolatile(sys)).toBe(true); // first time → warns
|
|
69
|
+
expect(warnIfVolatile(sys)).toBe(false); // same signature → suppressed
|
|
70
|
+
});
|
|
71
|
+
it('does not warn on clean prompts', () => {
|
|
72
|
+
_resetWarnedForTest();
|
|
73
|
+
expect(warnIfVolatile('perfectly stable system prompt')).toBe(false);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { detectCodeLanguage, extractCodeStructure } from '../deterministic.js';
|
|
3
|
+
import { retrieveOriginal } from '../expand.js';
|
|
4
|
+
describe('detectCodeLanguage', () => {
|
|
5
|
+
it('detects the existing languages', () => {
|
|
6
|
+
expect(detectCodeLanguage("import x from 'y'\nexport function f(): void {}")).toBe('ts');
|
|
7
|
+
expect(detectCodeLanguage('from os import path\ndef f():\n pass')).toBe('py');
|
|
8
|
+
expect(detectCodeLanguage('package main\nfunc main() {}')).toBe('go');
|
|
9
|
+
expect(detectCodeLanguage('use std::io;\npub fn main() {}')).toBe('rs');
|
|
10
|
+
});
|
|
11
|
+
it('detects Java', () => {
|
|
12
|
+
const java = 'package com.acme.app;\nimport java.util.List;\npublic class Service {\n public void run() {}\n}';
|
|
13
|
+
expect(detectCodeLanguage(java)).toBe('java');
|
|
14
|
+
});
|
|
15
|
+
it('detects C++ (has std::/class/namespace)', () => {
|
|
16
|
+
const cpp = '#include <vector>\n#include <string>\nnamespace app {\nclass Widget {\n void draw() {}\n};\n}';
|
|
17
|
+
expect(detectCodeLanguage(cpp)).toBe('cpp');
|
|
18
|
+
});
|
|
19
|
+
it('detects C (includes, no C++ markers)', () => {
|
|
20
|
+
const c = '#include <stdio.h>\n#include <stdlib.h>\nint main(int argc, char **argv) {\n return 0;\n}';
|
|
21
|
+
expect(detectCodeLanguage(c)).toBe('c');
|
|
22
|
+
});
|
|
23
|
+
it('returns null for non-code prose', () => {
|
|
24
|
+
expect(detectCodeLanguage('This is a plain english log line.\nAnother sentence here.')).toBeNull();
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
describe('extractCodeStructure — Java', () => {
|
|
28
|
+
function bigJava() {
|
|
29
|
+
const lines = ['package com.acme;', 'import java.util.List;', '', 'public class Orders {'];
|
|
30
|
+
for (let m = 0; m < 6; m++) {
|
|
31
|
+
lines.push(` public int method${m}(int a, int b) {`);
|
|
32
|
+
for (let b = 0; b < 15; b++)
|
|
33
|
+
lines.push(` int local${b} = a + b + ${m};`);
|
|
34
|
+
lines.push(' return a;');
|
|
35
|
+
lines.push(' }');
|
|
36
|
+
}
|
|
37
|
+
lines.push('}');
|
|
38
|
+
return lines.join('\n');
|
|
39
|
+
}
|
|
40
|
+
it('keeps signatures, elides bodies, and stays smaller', () => {
|
|
41
|
+
const input = bigJava();
|
|
42
|
+
const out = extractCodeStructure(input, 'java');
|
|
43
|
+
expect(out).not.toBe(input);
|
|
44
|
+
expect(out.length).toBeLessThan(input.length);
|
|
45
|
+
// signatures survive
|
|
46
|
+
expect(out.includes('public int method0(int a, int b)')).toBe(true);
|
|
47
|
+
expect(out.includes('public class Orders')).toBe(true);
|
|
48
|
+
// an omitted-lines marker is present
|
|
49
|
+
expect(/implementation lines omitted|lines omitted/.test(out)).toBe(true);
|
|
50
|
+
});
|
|
51
|
+
it('is fully recoverable via the parent expand id', () => {
|
|
52
|
+
const input = bigJava();
|
|
53
|
+
const out = extractCodeStructure(input, 'java');
|
|
54
|
+
const m = out.match(/squeezr_expand\("([0-9a-f]{6,})"\)/);
|
|
55
|
+
expect(m).not.toBeNull();
|
|
56
|
+
expect(retrieveOriginal(m[1])).toBe(input);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
describe('extractCodeStructure — C++', () => {
|
|
60
|
+
it('keeps class + method signatures', () => {
|
|
61
|
+
const lines = ['#include <vector>', '', 'class Engine {', 'public:'];
|
|
62
|
+
for (let m = 0; m < 5; m++) {
|
|
63
|
+
lines.push(` void step${m}(double dt) {`);
|
|
64
|
+
for (let b = 0; b < 12; b++)
|
|
65
|
+
lines.push(` acc += dt * ${b};`);
|
|
66
|
+
lines.push(' }');
|
|
67
|
+
}
|
|
68
|
+
lines.push('};');
|
|
69
|
+
const input = lines.join('\n');
|
|
70
|
+
const out = extractCodeStructure(input, 'cpp');
|
|
71
|
+
expect(out.includes('void step0(double dt)')).toBe(true);
|
|
72
|
+
expect(out.length).toBeLessThan(input.length);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
@@ -105,6 +105,19 @@ describe('preprocess - base pipeline', () => {
|
|
|
105
105
|
const input = 'a\nb\na';
|
|
106
106
|
expect(preprocess(input)).toBe('a\nb\na');
|
|
107
107
|
});
|
|
108
|
+
// Regression (1.82.0): repeated bare object keys like `body:` were folded into
|
|
109
|
+
// "... [repeated N more times]", which corrupted code when it round-tripped to disk.
|
|
110
|
+
it('never folds repeated bare object keys (body:)', () => {
|
|
111
|
+
// Mirrors the real incident: identical keys, unique values on the next line.
|
|
112
|
+
const input = Array.from({ length: 5 }, (_, i) => ` body:\n "unique text ${i}",`).join('\n');
|
|
113
|
+
const out = preprocess(input);
|
|
114
|
+
expect(out).not.toContain('repeated');
|
|
115
|
+
expect(out.split('\n').filter(l => l.trim() === 'body:')).toHaveLength(5);
|
|
116
|
+
});
|
|
117
|
+
it('still folds genuine repeated log prose (colon with a value after)', () => {
|
|
118
|
+
const input = Array(5).fill('ERROR: connection refused').join('\n');
|
|
119
|
+
expect(preprocess(input)).toContain('repeated 4 more times');
|
|
120
|
+
});
|
|
108
121
|
it('minifies inline JSON blobs > 200 chars', () => {
|
|
109
122
|
const obj = Object.fromEntries(Array.from({ length: 20 }, (_, i) => [`key_${i}`, `value_number_${i}`]));
|
|
110
123
|
const pretty = JSON.stringify(obj, null, 2);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { checkProxy, checkVersion, checkEnv, checkBypass, computeExitCode, formatDoctor, } from '../doctor.js';
|
|
3
|
+
describe('checkProxy', () => {
|
|
4
|
+
it('passes when health is present', () => {
|
|
5
|
+
expect(checkProxy({ identity: 'squeezr', version: '1.89.0' }).status).toBe('pass');
|
|
6
|
+
});
|
|
7
|
+
it('fails when the proxy is unreachable', () => {
|
|
8
|
+
expect(checkProxy(null).status).toBe('fail');
|
|
9
|
+
});
|
|
10
|
+
});
|
|
11
|
+
describe('checkVersion', () => {
|
|
12
|
+
it('passes when installed matches running', () => {
|
|
13
|
+
expect(checkVersion('1.89.0', '1.89.0').status).toBe('pass');
|
|
14
|
+
});
|
|
15
|
+
it('warns on a stale running proxy', () => {
|
|
16
|
+
const r = checkVersion('1.89.0', '1.88.0');
|
|
17
|
+
expect(r.status).toBe('warn');
|
|
18
|
+
expect(r.detail.includes('1.88.0')).toBe(true);
|
|
19
|
+
});
|
|
20
|
+
it('skips when the running version is unknown (proxy down)', () => {
|
|
21
|
+
expect(checkVersion('1.89.0', null).status).toBe('skip');
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
describe('checkEnv', () => {
|
|
25
|
+
it('passes when ANTHROPIC_BASE_URL points at the local proxy', () => {
|
|
26
|
+
expect(checkEnv('http://localhost:8080', 8080).status).toBe('pass');
|
|
27
|
+
expect(checkEnv('http://127.0.0.1:8080', 8080).status).toBe('pass');
|
|
28
|
+
});
|
|
29
|
+
it('warns when unset (traffic not routed through squeezr)', () => {
|
|
30
|
+
expect(checkEnv(undefined, 8080).status).toBe('warn');
|
|
31
|
+
expect(checkEnv('', 8080).status).toBe('warn');
|
|
32
|
+
});
|
|
33
|
+
it('fails when it points somewhere else', () => {
|
|
34
|
+
expect(checkEnv('https://api.anthropic.com', 8080).status).toBe('fail');
|
|
35
|
+
expect(checkEnv('http://localhost:9999', 8080).status).toBe('fail');
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
describe('checkBypass', () => {
|
|
39
|
+
it('warns when bypass is on (compression disabled)', () => {
|
|
40
|
+
expect(checkBypass(true).status).toBe('warn');
|
|
41
|
+
});
|
|
42
|
+
it('passes when bypass is off', () => {
|
|
43
|
+
expect(checkBypass(false).status).toBe('pass');
|
|
44
|
+
});
|
|
45
|
+
it('skips when unknown', () => {
|
|
46
|
+
expect(checkBypass(undefined).status).toBe('skip');
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
describe('computeExitCode', () => {
|
|
50
|
+
it('0 when all pass/skip', () => {
|
|
51
|
+
expect(computeExitCode([{ name: 'a', status: 'pass', detail: '' }, { name: 'b', status: 'skip', detail: '' }])).toBe(0);
|
|
52
|
+
});
|
|
53
|
+
it('1 when there is a warning but no failure', () => {
|
|
54
|
+
expect(computeExitCode([{ name: 'a', status: 'pass', detail: '' }, { name: 'b', status: 'warn', detail: '' }])).toBe(1);
|
|
55
|
+
});
|
|
56
|
+
it('2 when there is any failure', () => {
|
|
57
|
+
expect(computeExitCode([{ name: 'a', status: 'warn', detail: '' }, { name: 'b', status: 'fail', detail: '' }])).toBe(2);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
describe('formatDoctor', () => {
|
|
61
|
+
it('renders every check line and a summary', () => {
|
|
62
|
+
const out = formatDoctor([
|
|
63
|
+
{ name: 'Proxy', status: 'pass', detail: 'running v1.89.0' },
|
|
64
|
+
{ name: 'Env', status: 'warn', detail: 'not set' },
|
|
65
|
+
], 1);
|
|
66
|
+
expect(out.includes('Proxy')).toBe(true);
|
|
67
|
+
expect(out.includes('Env')).toBe(true);
|
|
68
|
+
expect(out.toLowerCase().includes('warn')).toBe(true);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { crushJsonArrays, TABLE_MARKER_RE } from '../jsonCrush.js';
|
|
3
|
+
import { retrieveOriginal } from '../expand.js';
|
|
4
|
+
function makeRows(n) {
|
|
5
|
+
const rows = [];
|
|
6
|
+
for (let i = 0; i < n; i++) {
|
|
7
|
+
rows.push({ id: i, name: `service-${i}`, status: i % 2 === 0 ? 'running' : 'stopped', region: 'us-east-1' });
|
|
8
|
+
}
|
|
9
|
+
return JSON.stringify(rows);
|
|
10
|
+
}
|
|
11
|
+
describe('crushJsonArrays', () => {
|
|
12
|
+
it('crushes a uniform array of >=5 objects and saves chars', () => {
|
|
13
|
+
const input = makeRows(20);
|
|
14
|
+
const { text, savedChars } = crushJsonArrays(input);
|
|
15
|
+
expect(savedChars).toBeGreaterThan(0);
|
|
16
|
+
expect(text.length).toBeLessThan(input.length);
|
|
17
|
+
expect(TABLE_MARKER_RE.test(text)).toBe(true);
|
|
18
|
+
});
|
|
19
|
+
it('embeds a recoverable squeezr_expand id that returns the ORIGINAL json', () => {
|
|
20
|
+
const input = makeRows(20);
|
|
21
|
+
const { text } = crushJsonArrays(input);
|
|
22
|
+
const m = text.match(TABLE_MARKER_RE);
|
|
23
|
+
expect(m).not.toBeNull();
|
|
24
|
+
const id = m[1];
|
|
25
|
+
expect(retrieveOriginal(id)).toBe(input);
|
|
26
|
+
});
|
|
27
|
+
it('keeps every value present in the table body (lossless representation)', () => {
|
|
28
|
+
const input = makeRows(6);
|
|
29
|
+
const { text } = crushJsonArrays(input);
|
|
30
|
+
expect(text.includes('service-0')).toBe(true);
|
|
31
|
+
expect(text.includes('service-5')).toBe(true);
|
|
32
|
+
expect(text.includes('running')).toBe(true);
|
|
33
|
+
expect(text.includes('stopped')).toBe(true);
|
|
34
|
+
});
|
|
35
|
+
it('lists the column names once in the header, not per row', () => {
|
|
36
|
+
const input = makeRows(10);
|
|
37
|
+
const { text } = crushJsonArrays(input);
|
|
38
|
+
// "region" appears once as a column header, never repeated as a JSON key
|
|
39
|
+
expect((text.match(/"region"/g) ?? []).length).toBe(0);
|
|
40
|
+
expect(text.includes('region')).toBe(true);
|
|
41
|
+
});
|
|
42
|
+
it('does NOT crush arrays below the minimum item count', () => {
|
|
43
|
+
const input = JSON.stringify([{ a: 1 }, { a: 2 }]);
|
|
44
|
+
const { text, savedChars } = crushJsonArrays(input);
|
|
45
|
+
expect(savedChars).toBe(0);
|
|
46
|
+
expect(text).toBe(input);
|
|
47
|
+
});
|
|
48
|
+
it('does NOT crush a single JSON object', () => {
|
|
49
|
+
const input = JSON.stringify({ id: 1, name: 'x', status: 'ok', region: 'eu' });
|
|
50
|
+
const { text, savedChars } = crushJsonArrays(input);
|
|
51
|
+
expect(savedChars).toBe(0);
|
|
52
|
+
expect(text).toBe(input);
|
|
53
|
+
});
|
|
54
|
+
it('does NOT crush an array of scalars (no keys to factor out)', () => {
|
|
55
|
+
const input = JSON.stringify([1, 2, 3, 4, 5, 6, 7, 8]);
|
|
56
|
+
const { text, savedChars } = crushJsonArrays(input);
|
|
57
|
+
expect(savedChars).toBe(0);
|
|
58
|
+
expect(text).toBe(input);
|
|
59
|
+
});
|
|
60
|
+
it('leaves non-JSON text unchanged', () => {
|
|
61
|
+
const input = 'this is just a log line\nand another one';
|
|
62
|
+
const { text, savedChars } = crushJsonArrays(input);
|
|
63
|
+
expect(savedChars).toBe(0);
|
|
64
|
+
expect(text).toBe(input);
|
|
65
|
+
});
|
|
66
|
+
it('does NOT crush when the columnar form would not save enough', () => {
|
|
67
|
+
// few rows, tiny keys → little repeated-key overhead → not worth it
|
|
68
|
+
const input = JSON.stringify([{ a: 1 }, { a: 2 }, { a: 3 }, { a: 4 }, { a: 5 }]);
|
|
69
|
+
const { text, savedChars } = crushJsonArrays(input);
|
|
70
|
+
expect(savedChars).toBe(0);
|
|
71
|
+
expect(text).toBe(input);
|
|
72
|
+
});
|
|
73
|
+
it('handles heterogeneous objects (missing keys) and stays recoverable', () => {
|
|
74
|
+
const arr = [
|
|
75
|
+
{ id: 1, name: 'a', status: 'ok', extra: 'z' },
|
|
76
|
+
{ id: 2, name: 'b', status: 'ok' },
|
|
77
|
+
{ id: 3, name: 'c', status: 'fail', region: 'eu' },
|
|
78
|
+
{ id: 4, name: 'd', status: 'ok' },
|
|
79
|
+
{ id: 5, name: 'e', status: 'ok' },
|
|
80
|
+
{ id: 6, name: 'f', status: 'ok' },
|
|
81
|
+
];
|
|
82
|
+
const input = JSON.stringify(arr);
|
|
83
|
+
const { text } = crushJsonArrays(input);
|
|
84
|
+
const m = text.match(TABLE_MARKER_RE);
|
|
85
|
+
expect(m).not.toBeNull();
|
|
86
|
+
expect(retrieveOriginal(m[1])).toBe(input);
|
|
87
|
+
});
|
|
88
|
+
it('renders nested objects inline as compact JSON', () => {
|
|
89
|
+
const arr = Array.from({ length: 6 }, (_, i) => ({
|
|
90
|
+
id: i,
|
|
91
|
+
meta: { region: 'us', tier: 'gold' },
|
|
92
|
+
name: `n${i}`,
|
|
93
|
+
}));
|
|
94
|
+
const input = JSON.stringify(arr);
|
|
95
|
+
const { text } = crushJsonArrays(input);
|
|
96
|
+
expect(text.includes('{"region":"us","tier":"gold"}')).toBe(true);
|
|
97
|
+
});
|
|
98
|
+
it('is deterministic — same input yields byte-identical output (cache-safe)', () => {
|
|
99
|
+
const input = makeRows(15);
|
|
100
|
+
const a = crushJsonArrays(input).text;
|
|
101
|
+
const b = crushJsonArrays(input).text;
|
|
102
|
+
expect(a).toBe(b);
|
|
103
|
+
});
|
|
104
|
+
it('crushes a pretty-printed (indented) JSON array too', () => {
|
|
105
|
+
const input = JSON.stringify(Array.from({ length: 8 }, (_, i) => ({ id: i, name: `x${i}`, status: 'ok', region: 'us' })), null, 2);
|
|
106
|
+
const { text, savedChars } = crushJsonArrays(input);
|
|
107
|
+
expect(savedChars).toBeGreaterThan(0);
|
|
108
|
+
expect(TABLE_MARKER_RE.test(text)).toBe(true);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|