[{"data":1,"prerenderedAt":4},["ShallowReactive",2],{"readme:fast-jev-compaction":3},"\u003Ch1>fast-jev-compaction\u003C\u002Fh1>\n\u003Cp>Claude Code plugin that replaces the compaction summary with Jev decisions:\nevery tool call and result is scored in one fast request, stale ones are\ndropped or truncated, everything kept stays verbatim. Also usable as an npm\nlibrary.\u003C\u002Fp>\n\u003Ch2>What and why\u003C\u002Fh2>\n\u003Cp>Most context compaction asks an LLM to summarize old turns. A summary is\nlossy: a file path, exact error, constraint, or command can disappear even when\nit matters later. This library never rewrites anything. It only deletes tool\ncalls and tool results Jev says are no longer needed, and it asks Jev while\nshowing it the whole conversation. User and assistant text stays verbatim and\nin order.\u003C\u002Fp>\n\u003Cp>The repository is both an npm package (\u003Ccode>src\u002F\u003C\u002Fcode>) and a Claude Code plugin\n(\u003Ccode>hooks\u002F\u003C\u002Fcode>, \u003Ccode>.claude-plugin\u002F\u003C\u002Fcode>) that uses the package to replace Claude Code's\nbuilt-in compaction summary with the original messages.\u003C\u002Fp>\n\u003Ch2>How it works\u003C\u002Fh2>\n\u003Col>\n\u003Cli>Every \u003Ccode>tool_use\u003C\u002Fcode> is paired with its \u003Ccode>tool_result\u003C\u002Fcode> by \u003Ccode>tool_use_id\u003C\u002Fcode>. Calls in\nthe first message or in the newest \u003Ccode>preserveRecentMessages\u003C\u002Fcode> messages are\npinned and never touched.\u003C\u002Fli>\n\u003Cli>The \u003Cstrong>state\u003C\u002Fstrong> sent to Jev is the whole conversation so far, oldest first,\nwith every tool result replaced by a short note (\u003Ccode>ok, 4213 chars (omitted)\u003C\u002Fcode>).\nTool inputs are included, texts are included, nothing is summarized.\u003C\u002Fli>\n\u003Cli>The state is fitted into \u003Ccode>maxStateTokens\u003C\u002Fcode> (25k by default) in stages, each\napplied only if the previous one was not enough: tool inputs truncated to\n1000, then 200, then 60 characters; long texts abridged to head + tail,\noldest non-pinned messages first; old non-pinned messages collapsed to a\n\u003Ccode>[… N chars omitted …]\u003C\u002Fcode> note; old tool calls reduced to one line each\n(\u003Ccode>t12 Read file_path=src\u002Fa.ts → ok 480ch\u003C\u002Fcode>); old call-less messages left\nout; runs of old call-only messages folded into one entry. If it still\ndoes not fit, compaction throws. Tokens are estimated without a tokenizer (a\nword per six letters, half a token per digit, ~one per other symbol),\ncalibrated to land a little above the counts Jev reports.\u003C\u002Fli>\n\u003Cli>For every non-pinned call Jev gets two \u003Ccode>noul\u003C\u002Fcode> questions: should the \u003Cstrong>call\u003C\u002Fstrong>\nstay (knowing it was made, with its input, still matters), and should the\n\u003Cstrong>result\u003C\u002Fstrong> stay verbatim (its contents are still needed and re-running the\ntool would not do).\u003C\u002Fli>\n\u003Cli>Questions are split into as many requests as needed so state plus questions\nstays under \u003Ccode>maxRequestTokens\u003C\u002Fcode> (30k by default, under Jev's 32k request\nlimit). The same full state is resent with every request; requests run\nconcurrently and their answers are merged.\u003C\u002Fli>\n\u003Cli>Decisions per call, against \u003Ccode>keepThreshold\u003C\u002Fcode>:\u003Cul>\n\u003Cli>\u003Ccode>keepResult ≥ threshold\u003C\u002Fcode> → keep call and result;\u003C\u002Fli>\n\u003Cli>else \u003Ccode>keepCall ≥ threshold\u003C\u002Fcode> → keep the call, truncate the result to its\nfirst \u003Ccode>truncateHeadChars\u003C\u002Fcode> characters plus a one-line note;\u003C\u002Fli>\n\u003Cli>else → remove the call together with its result.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003C\u002Fli>\n\u003Cli>The message list is rebuilt: a message that loses all its content is\nremoved, untouched messages are returned as the same objects, and no result\nis ever left without its call.\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>Jev failures, malformed answers, a missing key, or a history that cannot be\nfitted throw; the caller (or the Claude Code hook) decides what to fall back to.\u003C\u002Fp>\n\u003Ch2>Install and usage\u003C\u002Fh2>\n\u003Cpre>\u003Ccode class=\"language-sh\">npm install fast-jev-compaction\nexport TYPESAFE_API_KEY=...\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cpre>\u003Ccode class=\"language-ts\">import { compactMessages, reductionRatio, type Message } from 'fast-jev-compaction';\n\nconst transcript: Message[] = [\n  { role: 'user', text: 'Fix the failing test. Never edit src\u002Fgenerated.', toolUses: [] },\n  {\n    role: 'assistant',\n    text: '',\n    toolUses: [{ tool_use_id: 'toolu_1', tool: 'Read', input: { file_path: 'src\u002Fa.ts' } }],\n  },\n  { role: 'user', text: '', toolUses: [], toolResults: [{ tool_use_id: 'toolu_1', text: '…file…' }] },\n  \u002F\u002F …\n];\n\nconst result = await compactMessages(transcript, { preserveRecentMessages: 4 });\nconsole.log(result.messages, result.decisions, result.stats);\nif (reductionRatio(result) &lt; 0.25) {\n  \u002F\u002F not worth it: keep the original \n\u003C\u002Fcode>\u003C\u002Fpre>\n",1789861588681]