greprag 5.41.0 → 5.42.1

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.
Files changed (70) hide show
  1. package/dist/archive/glyph-font.d.ts +22 -0
  2. package/dist/archive/glyph-font.js +72 -0
  3. package/dist/archive/glyph-font.js.map +1 -0
  4. package/dist/archive/page-renderer.d.ts +67 -0
  5. package/dist/archive/page-renderer.js +197 -0
  6. package/dist/archive/page-renderer.js.map +1 -0
  7. package/dist/archive/png-codec.d.ts +21 -0
  8. package/dist/archive/png-codec.js +197 -0
  9. package/dist/archive/png-codec.js.map +1 -0
  10. package/dist/archive/transcript-extract.d.ts +39 -0
  11. package/dist/archive/transcript-extract.js +187 -0
  12. package/dist/archive/transcript-extract.js.map +1 -0
  13. package/dist/ccr-store.d.ts +49 -0
  14. package/dist/ccr-store.js +171 -0
  15. package/dist/ccr-store.js.map +1 -0
  16. package/dist/commands/archive.d.ts +81 -0
  17. package/dist/commands/archive.js +358 -0
  18. package/dist/commands/archive.js.map +1 -0
  19. package/dist/commands/context-governor.d.ts +82 -0
  20. package/dist/commands/context-governor.js +344 -0
  21. package/dist/commands/context-governor.js.map +1 -0
  22. package/dist/commands/crush-stats.d.ts +36 -0
  23. package/dist/commands/crush-stats.js +142 -0
  24. package/dist/commands/crush-stats.js.map +1 -0
  25. package/dist/commands/crush.d.ts +24 -0
  26. package/dist/commands/crush.js +183 -0
  27. package/dist/commands/crush.js.map +1 -0
  28. package/dist/commands/inbox-watch-supervisor.js +8 -65
  29. package/dist/commands/inbox-watch-supervisor.js.map +1 -1
  30. package/dist/commands/init.js +93 -0
  31. package/dist/commands/init.js.map +1 -1
  32. package/dist/commands/pipe-wrap.d.ts +97 -0
  33. package/dist/commands/pipe-wrap.js +324 -0
  34. package/dist/commands/pipe-wrap.js.map +1 -0
  35. package/dist/commands/watcher-registry.d.ts +1 -8
  36. package/dist/commands/watcher-registry.js +10 -47
  37. package/dist/commands/watcher-registry.js.map +1 -1
  38. package/dist/crush/adaptive-sizer.d.ts +40 -0
  39. package/dist/crush/adaptive-sizer.js +156 -0
  40. package/dist/crush/adaptive-sizer.js.map +1 -0
  41. package/dist/crush/crush-keywords.d.ts +40 -0
  42. package/dist/crush/crush-keywords.js +110 -0
  43. package/dist/crush/crush-keywords.js.map +1 -0
  44. package/dist/crush/crush-types.d.ts +38 -0
  45. package/dist/crush/crush-types.js +36 -0
  46. package/dist/crush/crush-types.js.map +1 -0
  47. package/dist/crush/index.d.ts +16 -0
  48. package/dist/crush/index.js +48 -0
  49. package/dist/crush/index.js.map +1 -0
  50. package/dist/crush/json-crusher.d.ts +38 -0
  51. package/dist/crush/json-crusher.js +250 -0
  52. package/dist/crush/json-crusher.js.map +1 -0
  53. package/dist/crush/json-detectors.d.ts +48 -0
  54. package/dist/crush/json-detectors.js +211 -0
  55. package/dist/crush/json-detectors.js.map +1 -0
  56. package/dist/crush/log-compressor.d.ts +60 -0
  57. package/dist/crush/log-compressor.js +394 -0
  58. package/dist/crush/log-compressor.js.map +1 -0
  59. package/dist/crush/search-compressor.d.ts +60 -0
  60. package/dist/crush/search-compressor.js +210 -0
  61. package/dist/crush/search-compressor.js.map +1 -0
  62. package/dist/hook.js +49 -1
  63. package/dist/hook.js.map +1 -1
  64. package/dist/index.js +487 -449
  65. package/dist/index.js.map +1 -1
  66. package/dist/session-id.js +7 -12
  67. package/dist/session-id.js.map +1 -1
  68. package/package.json +2 -2
  69. package/skill/mechanic/SKILL.md +1 -1
  70. package/skill/templates/chip-spawn.md +4 -2
@@ -0,0 +1,344 @@
1
+ "use strict";
2
+ /** Context governor — tracks the session's live context size every turn and,
3
+ * past a configurable crossover threshold (default 400K tokens), injects a
4
+ * one-line advisory recommending a governed compact.
5
+ *
6
+ * Why 400K: per-turn cache-read overhead is linear in context size N and
7
+ * overtakes the productive cost of a turn at roughly N ≈ 400-425K tokens.
8
+ * Past that, most of each turn's cost is re-reading frozen history — the
9
+ * economically right moment to compact, BEFORE the window forces it.
10
+ * See docs/pre-llm-compression.md (§ cache cost model).
11
+ *
12
+ * Three surfaces, all local (no API, no LLM):
13
+ * - Stop probe (`greprag-hook context-probe`): read the transcript TAIL
14
+ * (last ~64KB, never the whole multi-MB file), compute context size from
15
+ * the most recent main-chain assistant entry's `message.usage`, write
16
+ * ~/.greprag/context-state/<8hex>.json.
17
+ * - UserPromptSubmit check (`greprag-hook context-check`): state over
18
+ * threshold → emit ONE advisory line, at most once per 25-turn window.
19
+ * - CLI (`greprag context status`): debugging/demo readout.
20
+ *
21
+ * Config follows the anchor-settings pattern (email_autosave precedent):
22
+ * `.greprag/project.json` keys `context_governor` (bool) + `context_threshold`
23
+ * (number), env overrides GREPRAG_CONTEXT_GOVERNOR / GREPRAG_CONTEXT_THRESHOLD.
24
+ */
25
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
26
+ if (k2 === undefined) k2 = k;
27
+ var desc = Object.getOwnPropertyDescriptor(m, k);
28
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
29
+ desc = { enumerable: true, get: function() { return m[k]; } };
30
+ }
31
+ Object.defineProperty(o, k2, desc);
32
+ }) : (function(o, m, k, k2) {
33
+ if (k2 === undefined) k2 = k;
34
+ o[k2] = m[k];
35
+ }));
36
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
37
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
38
+ }) : function(o, v) {
39
+ o["default"] = v;
40
+ });
41
+ var __importStar = (this && this.__importStar) || (function () {
42
+ var ownKeys = function(o) {
43
+ ownKeys = Object.getOwnPropertyNames || function (o) {
44
+ var ar = [];
45
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
46
+ return ar;
47
+ };
48
+ return ownKeys(o);
49
+ };
50
+ return function (mod) {
51
+ if (mod && mod.__esModule) return mod;
52
+ var result = {};
53
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
54
+ __setModuleDefault(result, mod);
55
+ return result;
56
+ };
57
+ })();
58
+ Object.defineProperty(exports, "__esModule", { value: true });
59
+ exports.TAIL_BYTES = exports.FIRE_WINDOW_TURNS = exports.DEFAULT_CONTEXT_THRESHOLD = void 0;
60
+ exports.computeContextSizeFromTail = computeContextSizeFromTail;
61
+ exports.readTranscriptTail = readTranscriptTail;
62
+ exports.contextStateDir = contextStateDir;
63
+ exports.contextStatePath = contextStatePath;
64
+ exports.readGovernorState = readGovernorState;
65
+ exports.writeGovernorState = writeGovernorState;
66
+ exports.readGovernorConfig = readGovernorConfig;
67
+ exports.shouldFire = shouldFire;
68
+ exports.buildAdvisory = buildAdvisory;
69
+ exports.runContextProbe = runContextProbe;
70
+ exports.runContextCheck = runContextCheck;
71
+ exports.runContext = runContext;
72
+ const path = __importStar(require("path"));
73
+ const fs = __importStar(require("fs"));
74
+ const os = __importStar(require("os"));
75
+ const session_id_1 = require("../session-id");
76
+ const project_anchor_1 = require("../project-anchor");
77
+ exports.DEFAULT_CONTEXT_THRESHOLD = 400_000;
78
+ /** Advisory fires at most once per this many turns — never nag every turn. */
79
+ exports.FIRE_WINDOW_TURNS = 25;
80
+ /** Tail window read from the transcript. The latest assistant entry always
81
+ * lives in the last few KB; 64KB gives slack for huge tool_result lines. */
82
+ exports.TAIL_BYTES = 64 * 1024;
83
+ /** Compute the live context size from a chunk of transcript-tail text.
84
+ * Walks the JSONL lines BACKWARDS for the most recent main-chain assistant
85
+ * entry carrying `message.usage` — sidechain (subagent) entries are skipped,
86
+ * as are unparseable lines (the tail cut can land mid-line). Returns null
87
+ * when no qualifying entry is in the window. */
88
+ function computeContextSizeFromTail(tailText) {
89
+ const lines = tailText.split('\n');
90
+ for (let i = lines.length - 1; i >= 0; i--) {
91
+ const line = lines[i].trim();
92
+ if (!line)
93
+ continue;
94
+ let entry;
95
+ try {
96
+ entry = JSON.parse(line);
97
+ }
98
+ catch {
99
+ continue;
100
+ }
101
+ if (entry.isSidechain === true)
102
+ continue;
103
+ const msg = (entry.message || {});
104
+ const role = (msg.role || entry.type || '');
105
+ if (role !== 'assistant')
106
+ continue;
107
+ const usage = msg.usage;
108
+ if (!usage || typeof usage !== 'object')
109
+ continue;
110
+ const num = (v) => (typeof v === 'number' && Number.isFinite(v) ? v : 0);
111
+ const input = num(usage.input_tokens);
112
+ const cacheRead = num(usage.cache_read_input_tokens);
113
+ const cacheCreation = num(usage.cache_creation_input_tokens);
114
+ const tokens = input + cacheRead + cacheCreation;
115
+ if (tokens <= 0)
116
+ continue;
117
+ return {
118
+ tokens,
119
+ timestamp: typeof entry.timestamp === 'string' ? entry.timestamp : null,
120
+ };
121
+ }
122
+ return null;
123
+ }
124
+ /** Read the last `maxBytes` of a file. Never parses the whole transcript —
125
+ * a marathon session's JSONL runs to tens of MB. Returns null on any error. */
126
+ function readTranscriptTail(transcriptPath, maxBytes = exports.TAIL_BYTES) {
127
+ let fd = null;
128
+ try {
129
+ const { size } = fs.statSync(transcriptPath);
130
+ const start = Math.max(0, size - maxBytes);
131
+ const len = size - start;
132
+ if (len <= 0)
133
+ return '';
134
+ fd = fs.openSync(transcriptPath, 'r');
135
+ const buf = Buffer.alloc(len);
136
+ fs.readSync(fd, buf, 0, len, start);
137
+ return buf.toString('utf-8');
138
+ }
139
+ catch {
140
+ return null;
141
+ }
142
+ finally {
143
+ if (fd !== null) {
144
+ try {
145
+ fs.closeSync(fd);
146
+ }
147
+ catch { /* ignore */ }
148
+ }
149
+ }
150
+ }
151
+ /** State dir — env override (tests) > ~/.greprag/context-state. */
152
+ function contextStateDir() {
153
+ const override = process.env.GREPRAG_CONTEXT_STATE_DIR;
154
+ if (override)
155
+ return override;
156
+ return path.join(os.homedir(), '.greprag', 'context-state');
157
+ }
158
+ function contextStatePath(shortSessionId) {
159
+ const safe = shortSessionId.replace(/[^a-zA-Z0-9_-]/g, '_').slice(0, 64);
160
+ return path.join(contextStateDir(), `${safe}.json`);
161
+ }
162
+ function readGovernorState(shortSessionId) {
163
+ try {
164
+ const parsed = JSON.parse(fs.readFileSync(contextStatePath(shortSessionId), 'utf-8'));
165
+ if (typeof parsed?.tokens !== 'number' || typeof parsed?.turn !== 'number')
166
+ return null;
167
+ return parsed;
168
+ }
169
+ catch {
170
+ return null;
171
+ }
172
+ }
173
+ function writeGovernorState(shortSessionId, state) {
174
+ try {
175
+ const file = contextStatePath(shortSessionId);
176
+ const dir = path.dirname(file);
177
+ if (!fs.existsSync(dir))
178
+ fs.mkdirSync(dir, { recursive: true });
179
+ fs.writeFileSync(file, JSON.stringify(state, null, 2) + '\n');
180
+ }
181
+ catch { /* best-effort — the governor never blocks a turn */ }
182
+ }
183
+ function parseBoolEnv(raw) {
184
+ if (!raw)
185
+ return null;
186
+ if (/^(1|true|yes|on)$/i.test(raw))
187
+ return true;
188
+ if (/^(0|false|no|off)$/i.test(raw))
189
+ return false;
190
+ return null;
191
+ }
192
+ /** Resolve governor config. Precedence: env > anchor file > defaults.
193
+ * Reads the anchor file raw for the context_* keys so the governor stays
194
+ * self-contained (no ProjectAnchor type change — additive for the chip seam). */
195
+ function readGovernorConfig(cwd) {
196
+ let enabled = true;
197
+ let threshold = exports.DEFAULT_CONTEXT_THRESHOLD;
198
+ try {
199
+ const anchor = (0, project_anchor_1.readAnchor)(cwd);
200
+ const raw = JSON.parse(fs.readFileSync(anchor.anchorPath, 'utf-8'));
201
+ if (raw.context_governor === false)
202
+ enabled = false;
203
+ if (typeof raw.context_threshold === 'number' && raw.context_threshold > 0) {
204
+ threshold = raw.context_threshold;
205
+ }
206
+ }
207
+ catch { /* no anchor file — defaults stand */ }
208
+ const envEnabled = parseBoolEnv(process.env.GREPRAG_CONTEXT_GOVERNOR);
209
+ if (envEnabled !== null)
210
+ enabled = envEnabled;
211
+ const envThreshold = Number(process.env.GREPRAG_CONTEXT_THRESHOLD);
212
+ if (Number.isFinite(envThreshold) && envThreshold > 0)
213
+ threshold = envThreshold;
214
+ return { enabled, threshold };
215
+ }
216
+ // ---------- Threshold + advisory (pure) -------------------------------------
217
+ /** Fire when over threshold AND outside the once-per-window sentinel. */
218
+ function shouldFire(state, threshold, windowTurns = exports.FIRE_WINDOW_TURNS) {
219
+ if (state.tokens <= threshold)
220
+ return false;
221
+ if (typeof state.lastFiredTurn !== 'number')
222
+ return true;
223
+ return state.turn - state.lastFiredTurn >= windowTurns;
224
+ }
225
+ function buildAdvisory(tokens, threshold) {
226
+ const n = Math.round(tokens / 1000);
227
+ const t = Math.round(threshold / 1000);
228
+ // The snippet (greprag archive snippet) is the governed instruction block —
229
+ // PreCompact hooks cannot inject compaction instructions, so the operator
230
+ // carries them via /compact <snippet>. adr: adr/governed-compact.md
231
+ return `CONTEXT-GOVERNOR: context ~${n}K — past the ${t}K crossover; `
232
+ + `governed compact recommended at the next natural break `
233
+ + `(\`greprag archive snippet\` prints the governed /compact instruction block).`;
234
+ }
235
+ /** Stop probe — measure + record. Silent; never blocks the turn. */
236
+ function runContextProbe(input) {
237
+ const cwd = input.cwd || process.cwd();
238
+ const cfg = readGovernorConfig(cwd);
239
+ if (!cfg.enabled)
240
+ return;
241
+ const short = (0, session_id_1.truncateSessionId)(input.session_id);
242
+ if (!short || !input.transcript_path)
243
+ return;
244
+ const tail = readTranscriptTail(input.transcript_path);
245
+ if (tail === null)
246
+ return;
247
+ const usage = computeContextSizeFromTail(tail);
248
+ if (!usage)
249
+ return;
250
+ const prev = readGovernorState(short);
251
+ const state = {
252
+ tokens: usage.tokens,
253
+ turn: (prev?.turn ?? 0) + 1,
254
+ ts: new Date().toISOString(),
255
+ };
256
+ if (typeof prev?.lastFiredTurn === 'number')
257
+ state.lastFiredTurn = prev.lastFiredTurn;
258
+ writeGovernorState(short, state);
259
+ }
260
+ /** UserPromptSubmit check — returns the advisory line when it should fire
261
+ * (and stamps the sentinel), else null. Caller wraps it in hook JSON. */
262
+ function runContextCheck(input) {
263
+ const cwd = input.cwd || process.cwd();
264
+ const cfg = readGovernorConfig(cwd);
265
+ if (!cfg.enabled)
266
+ return null;
267
+ const short = (0, session_id_1.truncateSessionId)(input.session_id);
268
+ if (!short)
269
+ return null;
270
+ const state = readGovernorState(short);
271
+ if (!state)
272
+ return null;
273
+ if (!shouldFire(state, cfg.threshold))
274
+ return null;
275
+ writeGovernorState(short, { ...state, lastFiredTurn: state.turn });
276
+ return buildAdvisory(state.tokens, cfg.threshold);
277
+ }
278
+ // ---------- CLI: greprag context status --------------------------------------
279
+ const CONTEXT_HELP = `greprag context — live context-size governor (crossover advisory).
280
+
281
+ greprag context status [--session <8hex>] Current session's context size,
282
+ threshold, % consumed, and turns
283
+ since the advisory last fired.
284
+
285
+ The governor is hook-driven: a Stop probe records context size from the
286
+ transcript tail; a UserPromptSubmit check injects a one-line advisory once
287
+ the size crosses the threshold (default ${exports.DEFAULT_CONTEXT_THRESHOLD / 1000}K — the cache-read crossover),
288
+ at most once per ${exports.FIRE_WINDOW_TURNS} turns.
289
+
290
+ Config (.greprag/project.json):
291
+ "context_governor": false Disable for this project.
292
+ "context_threshold": 300000 Override the fire threshold (tokens).
293
+ Env overrides: GREPRAG_CONTEXT_GOVERNOR=off, GREPRAG_CONTEXT_THRESHOLD=<n>.
294
+ `;
295
+ function getFlag(args, name) {
296
+ const idx = args.indexOf(name);
297
+ if (idx === -1 || idx + 1 >= args.length)
298
+ return null;
299
+ return args[idx + 1];
300
+ }
301
+ function runContext(args) {
302
+ const sub = args[0];
303
+ if (sub === '--help' || sub === 'help' || sub === '-h') {
304
+ console.log(CONTEXT_HELP);
305
+ return;
306
+ }
307
+ if (sub !== 'status' && sub !== undefined) {
308
+ console.error(`Unknown subcommand: context ${sub}\n`);
309
+ console.log(CONTEXT_HELP);
310
+ process.exit(1);
311
+ }
312
+ const rawSession = getFlag(args, '--session')
313
+ || process.env.CLAUDE_CODE_SESSION_ID
314
+ || process.env.CLAUDE_SESSION_ID
315
+ || process.env.GREPRAG_SESSION_ID
316
+ || '';
317
+ const short = (0, session_id_1.truncateSessionId)(rawSession);
318
+ if (!short) {
319
+ console.error('No session id available. Pass --session <8hex>, or run inside a Claude Code session.');
320
+ process.exit(1);
321
+ }
322
+ const cfg = readGovernorConfig(process.cwd());
323
+ const state = readGovernorState(short);
324
+ console.log(`Context governor — session ${short}`);
325
+ console.log(` Governor: ${cfg.enabled ? 'enabled' : 'DISABLED'}`);
326
+ console.log(` Threshold: ${(cfg.threshold / 1000).toFixed(0)}K tokens`
327
+ + (cfg.threshold === exports.DEFAULT_CONTEXT_THRESHOLD ? ' (default)' : ''));
328
+ if (!state) {
329
+ console.log(' Context: (no probe data yet — fires on the next Stop event)');
330
+ return;
331
+ }
332
+ const pct = ((state.tokens / cfg.threshold) * 100).toFixed(0);
333
+ console.log(` Context: ~${(state.tokens / 1000).toFixed(1)}K tokens (as of ${state.ts})`);
334
+ console.log(` Consumed: ${pct}% of threshold`);
335
+ console.log(` Turn: ${state.turn} (probe count this session)`);
336
+ if (typeof state.lastFiredTurn === 'number') {
337
+ const since = state.turn - state.lastFiredTurn;
338
+ console.log(` Advisory: last fired at turn ${state.lastFiredTurn} (${since} turn${since === 1 ? '' : 's'} ago; window ${exports.FIRE_WINDOW_TURNS})`);
339
+ }
340
+ else {
341
+ console.log(` Advisory: never fired`);
342
+ }
343
+ }
344
+ //# sourceMappingURL=context-governor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-governor.js","sourceRoot":"","sources":["../../src/commands/context-governor.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BH,gEAyBC;AAID,gDAgBC;AAgBD,0CAIC;AAED,4CAGC;AAED,8CAUC;AAED,gDAOC;AAmBD,gDAsBC;AAKD,gCAMC;AAED,sCASC;AAWD,0CAoBC;AAID,0CAaC;AA2BD,gCA8CC;AA/SD,2CAA6B;AAC7B,uCAAyB;AACzB,uCAAyB;AACzB,8CAAkD;AAClD,sDAA+C;AAElC,QAAA,yBAAyB,GAAG,OAAO,CAAC;AACjD,8EAA8E;AACjE,QAAA,iBAAiB,GAAG,EAAE,CAAC;AACpC;6EAC6E;AAChE,QAAA,UAAU,GAAG,EAAE,GAAG,IAAI,CAAC;AAYpC;;;;iDAIiD;AACjD,SAAgB,0BAA0B,CAAC,QAAgB;IACzD,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACnC,KAAK,IAAI,CAAC,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAC3C,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAC7B,IAAI,CAAC,IAAI;YAAE,SAAS;QACpB,IAAI,KAA8B,CAAC;QACnC,IAAI,CAAC;YAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAAC,CAAC;QAAC,MAAM,CAAC;YAAC,SAAS;QAAC,CAAC;QACrD,IAAI,KAAK,CAAC,WAAW,KAAK,IAAI;YAAE,SAAS;QACzC,MAAM,GAAG,GAAG,CAAC,KAAK,CAAC,OAAO,IAAI,EAAE,CAA4B,CAAC;QAC7D,MAAM,IAAI,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,KAAK,CAAC,IAAI,IAAI,EAAE,CAAW,CAAC;QACtD,IAAI,IAAI,KAAK,WAAW;YAAE,SAAS;QACnC,MAAM,KAAK,GAAG,GAAG,CAAC,KAA4C,CAAC;QAC/D,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;YAAE,SAAS;QAClD,MAAM,GAAG,GAAG,CAAC,CAAU,EAAU,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC1F,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;QACtC,MAAM,SAAS,GAAG,GAAG,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAAC;QACrD,MAAM,aAAa,GAAG,GAAG,CAAC,KAAK,CAAC,2BAA2B,CAAC,CAAC;QAC7D,MAAM,MAAM,GAAG,KAAK,GAAG,SAAS,GAAG,aAAa,CAAC;QACjD,IAAI,MAAM,IAAI,CAAC;YAAE,SAAS;QAC1B,OAAO;YACL,MAAM;YACN,SAAS,EAAE,OAAO,KAAK,CAAC,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI;SACxE,CAAC;IACJ,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;gFACgF;AAChF,SAAgB,kBAAkB,CAAC,cAAsB,EAAE,QAAQ,GAAG,kBAAU;IAC9E,IAAI,EAAE,GAAkB,IAAI,CAAC;IAC7B,IAAI,CAAC;QACH,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC;QAC7C,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,GAAG,QAAQ,CAAC,CAAC;QAC3C,MAAM,GAAG,GAAG,IAAI,GAAG,KAAK,CAAC;QACzB,IAAI,GAAG,IAAI,CAAC;YAAE,OAAO,EAAE,CAAC;QACxB,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,cAAc,EAAE,GAAG,CAAC,CAAC;QACtC,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC9B,EAAE,CAAC,QAAQ,CAAC,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;QACpC,OAAO,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;YAAS,CAAC;QACT,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC;YAAC,IAAI,CAAC;gBAAC,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;YAAC,CAAC;YAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC;QAAC,CAAC;IACvE,CAAC;AACH,CAAC;AAeD,mEAAmE;AACnE,SAAgB,eAAe;IAC7B,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC;IACvD,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAC9B,OAAO,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,UAAU,EAAE,eAAe,CAAC,CAAC;AAC9D,CAAC;AAED,SAAgB,gBAAgB,CAAC,cAAsB;IACrD,MAAM,IAAI,GAAG,cAAc,CAAC,OAAO,CAAC,iBAAiB,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACzE,OAAO,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE,EAAE,GAAG,IAAI,OAAO,CAAC,CAAC;AACtD,CAAC;AAED,SAAgB,iBAAiB,CAAC,cAAsB;IACtD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CACvB,EAAE,CAAC,YAAY,CAAC,gBAAgB,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC,CAC1C,CAAC;QACnB,IAAI,OAAO,MAAM,EAAE,MAAM,KAAK,QAAQ,IAAI,OAAO,MAAM,EAAE,IAAI,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC;QACxF,OAAO,MAAM,CAAC;IAChB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,SAAgB,kBAAkB,CAAC,cAAsB,EAAE,KAAoB;IAC7E,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,gBAAgB,CAAC,cAAc,CAAC,CAAC;QAC9C,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAC/B,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAChE,EAAE,CAAC,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAChE,CAAC;IAAC,MAAM,CAAC,CAAC,oDAAoD,CAAC,CAAC;AAClE,CAAC;AASD,SAAS,YAAY,CAAC,GAAuB;IAC3C,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IACtB,IAAI,oBAAoB,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IAChD,IAAI,qBAAqB,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IAClD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;kFAEkF;AAClF,SAAgB,kBAAkB,CAAC,GAAW;IAC5C,IAAI,OAAO,GAAG,IAAI,CAAC;IACnB,IAAI,SAAS,GAAG,iCAAyB,CAAC;IAE1C,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAA,2BAAU,EAAC,GAAG,CAAC,CAAC;QAC/B,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,MAAM,CAAC,UAAU,EAAE,OAAO,CAAC,CAGjE,CAAC;QACF,IAAI,GAAG,CAAC,gBAAgB,KAAK,KAAK;YAAE,OAAO,GAAG,KAAK,CAAC;QACpD,IAAI,OAAO,GAAG,CAAC,iBAAiB,KAAK,QAAQ,IAAI,GAAG,CAAC,iBAAiB,GAAG,CAAC,EAAE,CAAC;YAC3E,SAAS,GAAG,GAAG,CAAC,iBAAiB,CAAC;QACpC,CAAC;IACH,CAAC;IAAC,MAAM,CAAC,CAAC,qCAAqC,CAAC,CAAC;IAEjD,MAAM,UAAU,GAAG,YAAY,CAAC,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;IACtE,IAAI,UAAU,KAAK,IAAI;QAAE,OAAO,GAAG,UAAU,CAAC;IAC9C,MAAM,YAAY,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC;IACnE,IAAI,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAC,IAAI,YAAY,GAAG,CAAC;QAAE,SAAS,GAAG,YAAY,CAAC;IAEhF,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC;AAChC,CAAC;AAED,+EAA+E;AAE/E,yEAAyE;AACzE,SAAgB,UAAU,CACxB,KAAoB,EAAE,SAAiB,EAAE,WAAW,GAAG,yBAAiB;IAExE,IAAI,KAAK,CAAC,MAAM,IAAI,SAAS;QAAE,OAAO,KAAK,CAAC;IAC5C,IAAI,OAAO,KAAK,CAAC,aAAa,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IACzD,OAAO,KAAK,CAAC,IAAI,GAAG,KAAK,CAAC,aAAa,IAAI,WAAW,CAAC;AACzD,CAAC;AAED,SAAgB,aAAa,CAAC,MAAc,EAAE,SAAiB;IAC7D,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACpC,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,IAAI,CAAC,CAAC;IACvC,4EAA4E;IAC5E,0EAA0E;IAC1E,oEAAoE;IACpE,OAAO,8BAA8B,CAAC,gBAAgB,CAAC,eAAe;UAClE,yDAAyD;UACzD,+EAA+E,CAAC;AACtF,CAAC;AAUD,oEAAoE;AACpE,SAAgB,eAAe,CAAC,KAAwB;IACtD,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IACvC,MAAM,GAAG,GAAG,kBAAkB,CAAC,GAAG,CAAC,CAAC;IACpC,IAAI,CAAC,GAAG,CAAC,OAAO;QAAE,OAAO;IACzB,MAAM,KAAK,GAAG,IAAA,8BAAiB,EAAC,KAAK,CAAC,UAAU,CAAC,CAAC;IAClD,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,eAAe;QAAE,OAAO;IAE7C,MAAM,IAAI,GAAG,kBAAkB,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC;IACvD,IAAI,IAAI,KAAK,IAAI;QAAE,OAAO;IAC1B,MAAM,KAAK,GAAG,0BAA0B,CAAC,IAAI,CAAC,CAAC;IAC/C,IAAI,CAAC,KAAK;QAAE,OAAO;IAEnB,MAAM,IAAI,GAAG,iBAAiB,CAAC,KAAK,CAAC,CAAC;IACtC,MAAM,KAAK,GAAkB;QAC3B,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,IAAI,EAAE,CAAC,IAAI,EAAE,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC;QAC3B,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KAC7B,CAAC;IACF,IAAI,OAAO,IAAI,EAAE,aAAa,KAAK,QAAQ;QAAE,KAAK,CAAC,aAAa,GAAG,IAAI,CAAC,aAAa,CAAC;IACtF,kBAAkB,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;AACnC,CAAC;AAED;0EAC0E;AAC1E,SAAgB,eAAe,CAAC,KAAwB;IACtD,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IACvC,MAAM,GAAG,GAAG,kBAAkB,CAAC,GAAG,CAAC,CAAC;IACpC,IAAI,CAAC,GAAG,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IAC9B,MAAM,KAAK,GAAG,IAAA,8BAAiB,EAAC,KAAK,CAAC,UAAU,CAAC,CAAC;IAClD,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IAExB,MAAM,KAAK,GAAG,iBAAiB,CAAC,KAAK,CAAC,CAAC;IACvC,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,GAAG,CAAC,SAAS,CAAC;QAAE,OAAO,IAAI,CAAC;IAEnD,kBAAkB,CAAC,KAAK,EAAE,EAAE,GAAG,KAAK,EAAE,aAAa,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;IACnE,OAAO,aAAa,CAAC,KAAK,CAAC,MAAM,EAAE,GAAG,CAAC,SAAS,CAAC,CAAC;AACpD,CAAC;AAED,gFAAgF;AAEhF,MAAM,YAAY,GAAG;;;;;;;;0CAQqB,iCAAyB,GAAG,IAAI;mBACvD,yBAAiB;;;;;;CAMnC,CAAC;AAEF,SAAS,OAAO,CAAC,IAAc,EAAE,IAAY;IAC3C,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,IAAI,GAAG,KAAK,CAAC,CAAC,IAAI,GAAG,GAAG,CAAC,IAAI,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IACtD,OAAO,IAAI,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;AACvB,CAAC;AAED,SAAgB,UAAU,CAAC,IAAc;IACvC,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,IAAI,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,MAAM,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;QACvD,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;QAC1B,OAAO;IACT,CAAC;IACD,IAAI,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;QAC1C,OAAO,CAAC,KAAK,CAAC,+BAA+B,GAAG,IAAI,CAAC,CAAC;QACtD,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;QAC1B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,UAAU,GAAG,OAAO,CAAC,IAAI,EAAE,WAAW,CAAC;WACxC,OAAO,CAAC,GAAG,CAAC,sBAAsB;WAClC,OAAO,CAAC,GAAG,CAAC,iBAAiB;WAC7B,OAAO,CAAC,GAAG,CAAC,kBAAkB;WAC9B,EAAE,CAAC;IACR,MAAM,KAAK,GAAG,IAAA,8BAAiB,EAAC,UAAU,CAAC,CAAC;IAC5C,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,CAAC,KAAK,CACX,sFAAsF,CACvF,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,GAAG,GAAG,kBAAkB,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IAC9C,MAAM,KAAK,GAAG,iBAAiB,CAAC,KAAK,CAAC,CAAC;IAEvC,OAAO,CAAC,GAAG,CAAC,8BAA8B,KAAK,EAAE,CAAC,CAAC;IACnD,OAAO,CAAC,GAAG,CAAC,kBAAkB,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC;IACtE,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,GAAG,CAAC,SAAS,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU;UACrE,CAAC,GAAG,CAAC,SAAS,KAAK,iCAAyB,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACvE,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,CAAC,GAAG,CAAC,mEAAmE,CAAC,CAAC;QACjF,OAAO;IACT,CAAC;IACD,MAAM,GAAG,GAAG,CAAC,CAAC,KAAK,CAAC,MAAM,GAAG,GAAG,CAAC,SAAS,CAAC,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IAC9D,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,mBAAmB,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC;IAC/F,OAAO,CAAC,GAAG,CAAC,kBAAkB,GAAG,gBAAgB,CAAC,CAAC;IACnD,OAAO,CAAC,GAAG,CAAC,kBAAkB,KAAK,CAAC,IAAI,6BAA6B,CAAC,CAAC;IACvE,IAAI,OAAO,KAAK,CAAC,aAAa,KAAK,QAAQ,EAAE,CAAC;QAC5C,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,GAAG,KAAK,CAAC,aAAa,CAAC;QAC/C,OAAO,CAAC,GAAG,CAAC,qCAAqC,KAAK,CAAC,aAAa,KAAK,KAAK,QAAQ,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,gBAAgB,yBAAiB,GAAG,CAAC,CAAC;IACpJ,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,GAAG,CAAC,4BAA4B,CAAC,CAAC;IAC5C,CAAC;AACH,CAAC"}
@@ -0,0 +1,36 @@
1
+ /** Crush stats — the savings ledger for the pipe-wrap hook.
2
+ *
3
+ * `greprag crush --stats` (the flag the pipe-wrap rewrite always includes)
4
+ * appends one JSONL record per invocation to ~/.greprag/crush-stats.jsonl:
5
+ * { ts, type, bytesIn, bytesOut }. Passthroughs are recorded too
6
+ * (bytesOut === bytesIn) so the totals reflect REAL savings, not just the
7
+ * wins. `greprag crush stats` reads the log and prints totals + ratios —
8
+ * this is how the compression claims get validated with field data.
9
+ *
10
+ * Append is best-effort and never throws: a stats failure must never break
11
+ * the crush exit-0 pipe contract.
12
+ */
13
+ export interface CrushStatRecord {
14
+ ts: string;
15
+ type: string;
16
+ bytesIn: number;
17
+ bytesOut: number;
18
+ }
19
+ /** Log file — env override (tests) > ~/.greprag/crush-stats.jsonl. */
20
+ export declare function crushStatsPath(): string;
21
+ /** Append one record. Best-effort: any failure is swallowed. */
22
+ export declare function appendCrushStat(rec: CrushStatRecord): void;
23
+ /** Read every parseable record; bad lines are skipped, missing file = []. */
24
+ export declare function readCrushStats(): CrushStatRecord[];
25
+ export interface CrushStatsBucket {
26
+ count: number;
27
+ bytesIn: number;
28
+ bytesOut: number;
29
+ }
30
+ export interface CrushStatsSummary {
31
+ total: CrushStatsBucket;
32
+ byType: Record<string, CrushStatsBucket>;
33
+ }
34
+ export declare function summarizeCrushStats(records: CrushStatRecord[]): CrushStatsSummary;
35
+ /** `greprag crush stats` — print totals + per-type ratios from the log. */
36
+ export declare function runCrushStats(_args: string[]): void;
@@ -0,0 +1,142 @@
1
+ "use strict";
2
+ // adr: adr/pipe-wrap-hook.md
3
+ /** Crush stats — the savings ledger for the pipe-wrap hook.
4
+ *
5
+ * `greprag crush --stats` (the flag the pipe-wrap rewrite always includes)
6
+ * appends one JSONL record per invocation to ~/.greprag/crush-stats.jsonl:
7
+ * { ts, type, bytesIn, bytesOut }. Passthroughs are recorded too
8
+ * (bytesOut === bytesIn) so the totals reflect REAL savings, not just the
9
+ * wins. `greprag crush stats` reads the log and prints totals + ratios —
10
+ * this is how the compression claims get validated with field data.
11
+ *
12
+ * Append is best-effort and never throws: a stats failure must never break
13
+ * the crush exit-0 pipe contract.
14
+ */
15
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ var desc = Object.getOwnPropertyDescriptor(m, k);
18
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
19
+ desc = { enumerable: true, get: function() { return m[k]; } };
20
+ }
21
+ Object.defineProperty(o, k2, desc);
22
+ }) : (function(o, m, k, k2) {
23
+ if (k2 === undefined) k2 = k;
24
+ o[k2] = m[k];
25
+ }));
26
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
27
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
28
+ }) : function(o, v) {
29
+ o["default"] = v;
30
+ });
31
+ var __importStar = (this && this.__importStar) || (function () {
32
+ var ownKeys = function(o) {
33
+ ownKeys = Object.getOwnPropertyNames || function (o) {
34
+ var ar = [];
35
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
36
+ return ar;
37
+ };
38
+ return ownKeys(o);
39
+ };
40
+ return function (mod) {
41
+ if (mod && mod.__esModule) return mod;
42
+ var result = {};
43
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
44
+ __setModuleDefault(result, mod);
45
+ return result;
46
+ };
47
+ })();
48
+ Object.defineProperty(exports, "__esModule", { value: true });
49
+ exports.crushStatsPath = crushStatsPath;
50
+ exports.appendCrushStat = appendCrushStat;
51
+ exports.readCrushStats = readCrushStats;
52
+ exports.summarizeCrushStats = summarizeCrushStats;
53
+ exports.runCrushStats = runCrushStats;
54
+ const fs = __importStar(require("fs"));
55
+ const os = __importStar(require("os"));
56
+ const path = __importStar(require("path"));
57
+ /** Log file — env override (tests) > ~/.greprag/crush-stats.jsonl. */
58
+ function crushStatsPath() {
59
+ return process.env.GREPRAG_CRUSH_STATS
60
+ || path.join(os.homedir(), '.greprag', 'crush-stats.jsonl');
61
+ }
62
+ /** Append one record. Best-effort: any failure is swallowed. */
63
+ function appendCrushStat(rec) {
64
+ try {
65
+ const file = crushStatsPath();
66
+ const dir = path.dirname(file);
67
+ if (!fs.existsSync(dir))
68
+ fs.mkdirSync(dir, { recursive: true });
69
+ fs.appendFileSync(file, JSON.stringify(rec) + '\n');
70
+ }
71
+ catch { /* stats must never break the pipe */ }
72
+ }
73
+ /** Read every parseable record; bad lines are skipped, missing file = []. */
74
+ function readCrushStats() {
75
+ let raw;
76
+ try {
77
+ raw = fs.readFileSync(crushStatsPath(), 'utf-8');
78
+ }
79
+ catch {
80
+ return [];
81
+ }
82
+ const out = [];
83
+ for (const line of raw.split('\n')) {
84
+ const trimmed = line.trim();
85
+ if (!trimmed)
86
+ continue;
87
+ try {
88
+ const rec = JSON.parse(trimmed);
89
+ if (typeof rec?.bytesIn === 'number' && typeof rec?.bytesOut === 'number') {
90
+ out.push(rec);
91
+ }
92
+ }
93
+ catch { /* skip bad line */ }
94
+ }
95
+ return out;
96
+ }
97
+ function summarizeCrushStats(records) {
98
+ const total = { count: 0, bytesIn: 0, bytesOut: 0 };
99
+ const byType = {};
100
+ for (const r of records) {
101
+ total.count++;
102
+ total.bytesIn += r.bytesIn;
103
+ total.bytesOut += r.bytesOut;
104
+ const b = (byType[r.type] ||= { count: 0, bytesIn: 0, bytesOut: 0 });
105
+ b.count++;
106
+ b.bytesIn += r.bytesIn;
107
+ b.bytesOut += r.bytesOut;
108
+ }
109
+ return { total, byType };
110
+ }
111
+ function fmtKb(bytes) {
112
+ return `${(bytes / 1024).toFixed(1)}KB`;
113
+ }
114
+ function fmtRatio(bytesIn, bytesOut) {
115
+ if (bytesOut <= 0 || bytesIn <= 0)
116
+ return '—';
117
+ return `${(bytesIn / bytesOut).toFixed(1)}x`;
118
+ }
119
+ function fmtSavedPct(bytesIn, bytesOut) {
120
+ if (bytesIn <= 0)
121
+ return '—';
122
+ return `${(((bytesIn - bytesOut) / bytesIn) * 100).toFixed(0)}%`;
123
+ }
124
+ /** `greprag crush stats` — print totals + per-type ratios from the log. */
125
+ function runCrushStats(_args) {
126
+ const records = readCrushStats();
127
+ console.log(`Crush stats — ${crushStatsPath()}`);
128
+ if (records.length === 0) {
129
+ console.log(' (no records yet — the pipe-wrap hook logs one line per crushed command)');
130
+ return;
131
+ }
132
+ const { total, byType } = summarizeCrushStats(records);
133
+ for (const [type, b] of Object.entries(byType).sort()) {
134
+ console.log(` ${type.padEnd(7)} ${String(b.count).padStart(5)} runs `
135
+ + `in ${fmtKb(b.bytesIn).padStart(10)} out ${fmtKb(b.bytesOut).padStart(10)} `
136
+ + `${fmtRatio(b.bytesIn, b.bytesOut)} (${fmtSavedPct(b.bytesIn, b.bytesOut)} saved)`);
137
+ }
138
+ console.log(` ${'TOTAL'.padEnd(7)} ${String(total.count).padStart(5)} runs `
139
+ + `in ${fmtKb(total.bytesIn).padStart(10)} out ${fmtKb(total.bytesOut).padStart(10)} `
140
+ + `${fmtRatio(total.bytesIn, total.bytesOut)} (${fmtSavedPct(total.bytesIn, total.bytesOut)} saved)`);
141
+ }
142
+ //# sourceMappingURL=crush-stats.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crush-stats.js","sourceRoot":"","sources":["../../src/commands/crush-stats.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ /** greprag crush / greprag retrieve — pre-LLM tool-output compression.
2
+ *
3
+ * `greprag crush --type <log|search|json> [--ccr] [--query "words"]`
4
+ * reads stdin, writes crushed output to stdout. Designed to sit inside
5
+ * users' piped commands, so the contract is HARD:
6
+ *
7
+ * - Exit 0 ALWAYS, even on internal error (pass stdin through verbatim).
8
+ * - Token-validate-or-revert: if crushed output isn't fewer estimated
9
+ * tokens than the input, emit the input unchanged.
10
+ * - Below-threshold skip: log/search < 500B, json < 1KB pass through.
11
+ * - With --ccr: the ORIGINAL is stored in the local CCR store
12
+ * (~/.greprag/ccr.db; override GREPRAG_CCR_DB) and a trailing
13
+ * `<<ccr:HASH>>` marker (first 16 hex of sha256) is appended.
14
+ *
15
+ * `greprag retrieve <hash>` prints the stored original to stdout.
16
+ *
17
+ * Compressors come from the local crush mirror (canonical source:
18
+ * packages/core/src/crush — the CLI cannot import @greprag/core).
19
+ */
20
+ export declare const CRUSH_HELP = "greprag crush \u2014 compress tool output before it reaches the model.\n\nUsage:\n <command> | greprag crush --type <log|search|json> [--ccr] [--query \"words\"] [--stats]\n greprag crush stats\n greprag retrieve <hash>\n\nReads stdin, writes the crushed version to stdout.\n\nFlags:\n --type <log|search|json> What the input is:\n log build/test output (pytest, npm, cargo, jest, make)\n search grep/ripgrep output (file:line:content)\n json JSON containing large arrays\n --ccr Stash the ORIGINAL in the local CCR store and append\n a trailing <<ccr:HASH>> marker. Recover the full\n output later with: greprag retrieve <hash>\n --query \"words\" Relevance hint \u2014 matches containing these words\n score higher and survive compression (search/json).\n --stats Append { ts, type, bytesIn, bytesOut } to the local\n stats log (~/.greprag/crush-stats.jsonl; override\n GREPRAG_CRUSH_STATS). Read it back with:\n greprag crush stats\n\nContract (safe inside pipes):\n - ALWAYS exits 0; on any internal error stdin passes through verbatim.\n - If compression wouldn't save tokens, the input passes through unchanged.\n - Small inputs (log/search < 500B, json < 1KB) pass through untouched.\n\nCCR store: ~/.greprag/ccr.db (override with GREPRAG_CCR_DB).\n\nExamples:\n npm test 2>&1 | greprag crush --type log --ccr\n rg -n \"handleSms\" | greprag crush --type search --query \"handleSms tenant\"\n curl -s api/items | greprag crush --type json";
21
+ /** greprag crush — exit 0 always; on any failure stdin passes through. */
22
+ export declare function runCrush(args: string[]): Promise<void>;
23
+ /** greprag retrieve <hash> — print a CCR-stored original to stdout. */
24
+ export declare function runRetrieve(args: string[]): Promise<void>;