Claude Code plugin that replaces the compaction summary with Jev decisions: every tool call and result is scored in one fast request, stale ones are dropped or truncated, everything kept stays verbatim.
TypeScript
7,372
30 commits
updated Sep 18, 2026
Claude Code plugin that replaces the compaction summary with Jev decisions: every tool call and result is scored in one fast request, stale ones are dropped or truncated, everything kept stays verbatim. Also usable as an npm library.
Most context compaction asks an LLM to summarize old turns. A summary is lossy: a file path, exact error, constraint, or command can disappear even when it matters later. This library never rewrites anything. It only deletes tool calls and tool results Jev says are no longer needed, and it asks Jev while showing it the whole conversation. User and assistant text stays verbatim and in order.
The repository is both an npm package (src/) and a Claude Code plugin
(hooks/, .claude-plugin/) that uses the package to replace Claude Code's
built-in compaction summary with the original messages.
tool_use is paired with its tool_result by tool_use_id. Calls in
the first message or in the newest preserveRecentMessages messages are
pinned and never touched.ok, 4213 chars (omitted)).
Tool inputs are included, texts are included, nothing is summarized.maxStateTokens (25k by default) in stages, each
applied only if the previous one was not enough: tool inputs truncated to
1000, then 200, then 60 characters; long texts abridged to head + tail,
oldest non-pinned messages first; old non-pinned messages collapsed to a
[… N chars omitted …] note; old tool calls reduced to one line each
(t12 Read file_path=src/a.ts → ok 480ch); old call-less messages left
out; runs of old call-only messages folded into one entry. If it still
does not fit, compaction throws. Tokens are estimated without a tokenizer (a
word per six letters, half a token per digit, ~one per other symbol),
calibrated to land a little above the counts Jev reports.noul questions: should the call
stay (knowing it was made, with its input, still matters), and should the
result stay verbatim (its contents are still needed and re-running the
tool would not do).maxRequestTokens (30k by default, under Jev's 32k request
limit). The same full state is resent with every request; requests run
concurrently and their answers are merged.keepThreshold:
keepResult ≥ threshold → keep call and result;keepCall ≥ threshold → keep the call, truncate the result to its
first truncateHeadChars characters plus a one-line note;Jev failures, malformed answers, a missing key, or a history that cannot be fitted throw; the caller (or the Claude Code hook) decides what to fall back to.
npm install fast-jev-compaction
export TYPESAFE_API_KEY=...
import { compactMessages, reductionRatio, type Message } from 'fast-jev-compaction';
const transcript: Message[] = [
{ role: 'user', text: 'Fix the failing test. Never edit src/generated.', toolUses: [] },
{
role: 'assistant',
text: '',
toolUses: [{ tool_use_id: 'toolu_1', tool: 'Read', input: { file_path: 'src/a.ts' } }],
},
{ role: 'user', text: '', toolUses: [], toolResults: [{ tool_use_id: 'toolu_1', text: '…file…' }] },
// …
];
const result = await compactMessages(transcript, { preserveRecentMessages: 4 });
console.log(result.messages, result.decisions, result.stats);
if (reductionRatio(result) < 0.25) {
// not worth it: keep the original transcript, or summarize instead
}
Message is a subset of Claude Code's SessionMessage, so a session transcript
can be passed in as is.
To bring your own transport, implement JevAsker (one ask(state, questions)
method) and call compact(messages, asker, options); buildJevRequest and
parseJevResponse give you the HTTP request body and response validation.
The building blocks (collectToolCalls, fitState, batchCalls,
decideCall, applyDecisions) are exported too.
apiKey defaults to process.env.TYPESAFE_API_KEY. Never commit the key or
put it in a source file.
| Option | Default | Description |
|---|---|---|
apiKey | TYPESAFE_API_KEY | TypeSafe API key (compactMessages/JevClient) |
model | jev-latest | Jev model name |
baseUrl | https://api.typesafe.ai/v1/systemone | System One endpoint |
fetch | native fetch | Injectable fetch implementation for tests |
goal | last 3 user prompts | Ongoing task description included in the state |
keepThreshold | 0.5 | Minimum keep probability for a call or result to stay |
preserveRecentMessages | 6 | Newest messages never touched (the first is always kept) |
maxStateTokens | 25000 | Estimated token ceiling for the state |
maxRequestTokens | 30000 | Estimated ceiling for state plus one batch of questions |
truncateHeadChars | 300 | Characters of a dropped tool result retained before its note |
result.stats reports message and character counts before and after, the
per-reason decision counts, the state size in estimated tokens, which fitting
stage was needed, and the number of requests.
The repository root is a Claude Code function-hook plugin: hooks/fast-jev.ts
is a thin adapter that feeds session.compact transcripts through src/ and
falls back to Claude Code's built-in summary on errors or insufficient
reduction. See hooks/README.md for configuration and the
Claude Code 2.1.274 type reference.
Function hooks are an early-access Claude Code feature (2.1.274+), so the
opt-in flag must be set wherever Claude Code runs, e.g. in ~/.claude/settings.json:
{ "env": { "CLAUDE_CODE_ENABLE_FUNCTION_HOOKS": "1", "TYPESAFE_API_KEY": "<your key>" } }
Then add this repository as a plugin marketplace and install the plugin, either from the shell or as slash commands inside a session:
claude plugin marketplace add tamaratran/fast-jev-compaction
claude plugin install fast-jev-compaction@fast-jev-compaction
The install prompts for the plugin options (API key, thresholds, truncateHeadChars,
…); leave them at their defaults to use TYPESAFE_API_KEY from the environment.
Restart Claude Code or run /reload-plugins. From then on /compact (and
auto-compaction) goes through Jev: the toast reads
fast-jev-compaction: kept N/M messages, no summary (…) when the pruned history
replaced the built-in summary, or fallback to built-in summary (…) when Jev
could not remove enough (short sessions, or when it fails).
To run from a checkout without installing: CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1 claude --plugin-dir .
from the repository root. No publishing step is required; the marketplace is
just the repo's .claude-plugin/marketplace.json.
npm install
npm run typecheck # library + hook
npm test
npm run build
npm run validate:plugin # claude plugin validate
TYPESAFE_API_KEY="$(cat ~/.typesafe_key)" npm run demo
The unit tests use a fake Jev and never contact TypeSafe. The demo is the live network check.
demo/JevDemo is a small native SwiftUI app that plays a scripted, dramatized
version of the compaction flow inside a Claude Code-style terminal: the tool
calls of a canned transcript are scored, results and calls Jev lets go turn red
and collapse away, and the rest stays verbatim. It never calls the API; it
exists to be screen recorded.
demo/JevDemo/build.sh # builds demo/JevDemo/build/JevDemo.app and launches it
Press space in the app to replay from the start.
(top 24 of 74)
921 followers · starred Sep 2026
5,594 followers · starred Sep 2026
81 followers · starred Sep 2026
151 followers · starred Sep 2026
TypeScript
100.0%
Claude Code plugin that replaces the compaction summary with Jev decisions: every tool call and result is scored in one fast request, stale ones are dropped or truncated, everything kept stays verbatim.
TypeScript
7,372
30 commits
updated Sep 18, 2026
Claude Code plugin that replaces the compaction summary with Jev decisions: every tool call and result is scored in one fast request, stale ones are dropped or truncated, everything kept stays verbatim. Also usable as an npm library.
Most context compaction asks an LLM to summarize old turns. A summary is lossy: a file path, exact error, constraint, or command can disappear even when it matters later. This library never rewrites anything. It only deletes tool calls and tool results Jev says are no longer needed, and it asks Jev while showing it the whole conversation. User and assistant text stays verbatim and in order.
The repository is both an npm package (src/) and a Claude Code plugin
(hooks/, .claude-plugin/) that uses the package to replace Claude Code's
built-in compaction summary with the original messages.
tool_use is paired with its tool_result by tool_use_id. Calls in
the first message or in the newest preserveRecentMessages messages are
pinned and never touched.ok, 4213 chars (omitted)).
Tool inputs are included, texts are included, nothing is summarized.maxStateTokens (25k by default) in stages, each
applied only if the previous one was not enough: tool inputs truncated to
1000, then 200, then 60 characters; long texts abridged to head + tail,
oldest non-pinned messages first; old non-pinned messages collapsed to a
[… N chars omitted …] note; old tool calls reduced to one line each
(t12 Read file_path=src/a.ts → ok 480ch); old call-less messages left
out; runs of old call-only messages folded into one entry. If it still
does not fit, compaction throws. Tokens are estimated without a tokenizer (a
word per six letters, half a token per digit, ~one per other symbol),
calibrated to land a little above the counts Jev reports.noul questions: should the call
stay (knowing it was made, with its input, still matters), and should the
result stay verbatim (its contents are still needed and re-running the
tool would not do).maxRequestTokens (30k by default, under Jev's 32k request
limit). The same full state is resent with every request; requests run
concurrently and their answers are merged.keepThreshold:
keepResult ≥ threshold → keep call and result;keepCall ≥ threshold → keep the call, truncate the result to its
first truncateHeadChars characters plus a one-line note;Jev failures, malformed answers, a missing key, or a history that cannot be fitted throw; the caller (or the Claude Code hook) decides what to fall back to.
npm install fast-jev-compaction
export TYPESAFE_API_KEY=...
import { compactMessages, reductionRatio, type Message } from 'fast-jev-compaction';
const transcript: Message[] = [
{ role: 'user', text: 'Fix the failing test. Never edit src/generated.', toolUses: [] },
{
role: 'assistant',
text: '',
toolUses: [{ tool_use_id: 'toolu_1', tool: 'Read', input: { file_path: 'src/a.ts' } }],
},
{ role: 'user', text: '', toolUses: [], toolResults: [{ tool_use_id: 'toolu_1', text: '…file…' }] },
// …
];
const result = await compactMessages(transcript, { preserveRecentMessages: 4 });
console.log(result.messages, result.decisions, result.stats);
if (reductionRatio(result) < 0.25) {
// not worth it: keep the original transcript, or summarize instead
}
Message is a subset of Claude Code's SessionMessage, so a session transcript
can be passed in as is.
To bring your own transport, implement JevAsker (one ask(state, questions)
method) and call compact(messages, asker, options); buildJevRequest and
parseJevResponse give you the HTTP request body and response validation.
The building blocks (collectToolCalls, fitState, batchCalls,
decideCall, applyDecisions) are exported too.
apiKey defaults to process.env.TYPESAFE_API_KEY. Never commit the key or
put it in a source file.
| Option | Default | Description |
|---|---|---|
apiKey | TYPESAFE_API_KEY | TypeSafe API key (compactMessages/JevClient) |
model | jev-latest | Jev model name |
baseUrl | https://api.typesafe.ai/v1/systemone | System One endpoint |
fetch | native fetch | Injectable fetch implementation for tests |
goal | last 3 user prompts | Ongoing task description included in the state |
keepThreshold | 0.5 | Minimum keep probability for a call or result to stay |
preserveRecentMessages | 6 | Newest messages never touched (the first is always kept) |
maxStateTokens | 25000 | Estimated token ceiling for the state |
maxRequestTokens | 30000 | Estimated ceiling for state plus one batch of questions |
truncateHeadChars | 300 | Characters of a dropped tool result retained before its note |
result.stats reports message and character counts before and after, the
per-reason decision counts, the state size in estimated tokens, which fitting
stage was needed, and the number of requests.
The repository root is a Claude Code function-hook plugin: hooks/fast-jev.ts
is a thin adapter that feeds session.compact transcripts through src/ and
falls back to Claude Code's built-in summary on errors or insufficient
reduction. See hooks/README.md for configuration and the
Claude Code 2.1.274 type reference.
Function hooks are an early-access Claude Code feature (2.1.274+), so the
opt-in flag must be set wherever Claude Code runs, e.g. in ~/.claude/settings.json:
{ "env": { "CLAUDE_CODE_ENABLE_FUNCTION_HOOKS": "1", "TYPESAFE_API_KEY": "<your key>" } }
Then add this repository as a plugin marketplace and install the plugin, either from the shell or as slash commands inside a session:
claude plugin marketplace add tamaratran/fast-jev-compaction
claude plugin install fast-jev-compaction@fast-jev-compaction
The install prompts for the plugin options (API key, thresholds, truncateHeadChars,
…); leave them at their defaults to use TYPESAFE_API_KEY from the environment.
Restart Claude Code or run /reload-plugins. From then on /compact (and
auto-compaction) goes through Jev: the toast reads
fast-jev-compaction: kept N/M messages, no summary (…) when the pruned history
replaced the built-in summary, or fallback to built-in summary (…) when Jev
could not remove enough (short sessions, or when it fails).
To run from a checkout without installing: CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1 claude --plugin-dir .
from the repository root. No publishing step is required; the marketplace is
just the repo's .claude-plugin/marketplace.json.
npm install
npm run typecheck # library + hook
npm test
npm run build
npm run validate:plugin # claude plugin validate
TYPESAFE_API_KEY="$(cat ~/.typesafe_key)" npm run demo
The unit tests use a fake Jev and never contact TypeSafe. The demo is the live network check.
demo/JevDemo is a small native SwiftUI app that plays a scripted, dramatized
version of the compaction flow inside a Claude Code-style terminal: the tool
calls of a canned transcript are scored, results and calls Jev lets go turn red
and collapse away, and the rest stays verbatim. It never calls the API; it
exists to be screen recorded.
demo/JevDemo/build.sh # builds demo/JevDemo/build/JevDemo.app and launches it
Press space in the app to replay from the start.
(top 24 of 74)
921 followers · starred Sep 2026
5,594 followers · starred Sep 2026
81 followers · starred Sep 2026
151 followers · starred Sep 2026
TypeScript
100.0%