IntelliJev context engine wiki

Everything you need to explain how it works and why each design choice was made. The eval report has the full numbers; this page explains them.

Provider scope. The architecture and measured results below describe the Jev pipeline. The combined plugin also offers Fast keywords and local Laya. Typing previews and the live hook default to local keywords; explicit Pack/MCP calls use the requested provider. See the local Laya report for its separate frozen protocol, CPU timings and limitations. The provider studies are not a head-to-head comparison.

In one minute

Given a task in plain English, IntelliJev's Jev context engine ranks every eligible source file in the project by how likely the task is to need it. It combines two very different signals: BM25, a classic keyword-matching formula, and Jev, a model that answers yes/no questions with calibrated probabilities. Jev first judges a short summary (a sketch) of every file; the best candidates from both signals are then re-read by Jev in full, the two scores are combined, and a final comparative question reorders the top 10. It runs inside IntelliJ, and serves people (a tool window) and agents (an MCP tool, and a Claude Code hook that injects the ranking before the agent starts).

Architecture

Pipeline: collect, sketch, pass 1 with BM25, pool, pass 2, fuse, stage 3 and roles, outputs Task + projectplain-English request CollectProjectFileIndex, filters Sketch~300 tokens a file, cached Pass 1 · Jev on sketches60 files a call, all files, ~2 s BM25 on full textkeyword ranking, in parallel Pooltop 60 of each, ~100 files, deduplicated Pass 2 · Jev on full source6 files a call, 6,000 chars each, ~1 s Fusejev + 1 / (1 + bm25_rank / 10) Stage 3 · Jev choice, top 10"which file is edited?" +2 × p Roles · Jev choice per fileedit / test / example / dependency Tool windowpeople: pack, preview, Ask LLM MCP tool pack_contextany MCP agent Claude Code hookranking injected before the agent starts Jev call keyword search output

One pack on Koog (1,705 Kotlin files): about 50 Jev calls, 0.6M input tokens, $0.03, about 4 s end to end. Sketches are built in the background when the project opens and cached by file modification stamp and sketch mode. A pack still reads missing or changed sketches; warm-up reduces that work.

Jev

Jev is TypeSafe's first "System One" model. It cannot generate text. You send it a state (any JSON) and a set of typed questions; it returns calibrated probabilities. That's the whole interface. We use two question types:

Batching is the key trick: the state is an object with one key per file (f000, f001, …), and each question names its key, so 60 files share a single request. The limit is about 32k tokens of state per call: 100 sketches fit, 150 don't. Price: $0.042 per million input tokens, output free. Latency: 0.5-2 s a call, and we run up to 48 at once.

Why Jev and not an LLM for this? The job is the same small judgement repeated thousands of times. An LLM can do it (we measured: it's even a little better at the very top) but takes about 29 s per request against Jev's ~1 s. Speed is what lets an agent call this before every task.

BM25

BM25 is the standard keyword-ranking formula behind most search engines. For each word in the task, a file scores higher when:

We split identifiers, so cachedContentTokenCount matches the words "token count". BM25 reads full source, costs nothing and takes milliseconds.

Why include it? Two reasons. It's the honest baseline, since it's what "just grep it" amounts to done well, so every result is reported against it. And it's genuinely good at tasks that name identifiers, so it's half of the shortlist. Its weakness is tasks described in words the code doesn't use, which is exactly where Jev helps.

Sketches

A sketch is a ~300-token summary of a file: its path, its package, and its declarations (classes, functions, properties) two levels deep, each with the first line of its doc comment. For example:

path: prompt/.../google/GoogleLLMClient.kt
package ai.koog.prompt.executor.clients.google
- public class GoogleClientSettings(  // Configuration settings for the Google AI client.
  - public val baseUrl: String
- public open class GoogleLLMClient @JvmOverloads constructor(  // Implementation of [LLMClient] for Google's Gemini API.

Sketches exist so that 60 files fit into one Jev call. Cost was never the reason: reading every file in full would cost about $0.11. The plugin uses the same regular-expression sketcher the eval measured, checked by a parity test on 60 real files. Sketches built from IntelliJ's Structure View read better but take about 19 ms a file cold and were never measured, so they're behind a flag.

Why re-rank

This is the core design decision, and it came from a failure. Jev scoring sketches alone lost to BM25 (recall@10 0.37 against 0.46). A sketch shows a file's outline, but many tasks are about something inside a function body.

The fix is a classic in search engineering: retrieve, then re-rank. Cast a cheap, wide net first; then look carefully at only what you caught.

  1. Wide net. Take BM25's top 60 and Jev-on-sketches' top 60. They catch different files: BM25 finds files that use the task's words, Jev finds files that fit its meaning. Together that's about 100 files, and the right answer is in this pool about 90% of the time.
  2. Careful look. Jev reads those ~100 files in full (6,000 characters each, 6 files per call). The pool is small, so full source is affordable, and full source is where Jev is strong.

Fusion

The final score combines both signals:

score = jev_probability + 1 / (1 + bm25_rank / 10)

Jev's full-source probability is 0 to 1. The BM25 term is a bonus that decays with keyword rank: 1.0 for BM25's top file, 0.5 at rank 10, 0.1 at rank 90. So a file BM25 ranks highly needs only moderate Jev support, and a file BM25 missed needs strong Jev support to climb. The equal weighting was chosen on the dev tasks from a grid of weights and a second method (reciprocal rank fusion). Equal weight won, and then we measured test once.

Stage 3: a comparative question

Passes 1 and 2 judge each file on its own. An LLM ranking a list compares files, and that's where it beat us (top-5 recall). So one more Jev call asks a single choice over the fused top 10: "Which file must be edited to implement the change described in `task`?" Each file's probability, times 2, is added to its score. Only the order inside the top 10 changes. It lifted held-out recall@5 from 0.54 to 0.57, for one call and about 0.4 s.

Role labels

A separate Jev call, run in parallel with stage 3, asks per top file which part it plays: edit, test, example (a pattern to copy), dependency (an API used but not changed) or unrelated. A label shows when Jev is at least 50% sure. It's a separate call on purpose, so it can't change the measured ranking. Labels are for display only and weren't evaluated.

How we measured

The task set. Real commits from JetBrains/Koog (1,189 commits, 1,705 Kotlin files). The task is the commit's subject line, with ticket numbers stripped. The answer is the Kotlin files that commit modified. Commits that are mostly new files are excluded, because nothing can find a file that doesn't exist yet. That leaves 136 usable tasks.

No leakage. Every file is read as it was at the parent commit, before the change. Otherwise the answer would be sitting in the code: "add cachedContentTokenCount" is easy to find once that code exists.

Dev and test. Every choice (pool size, wording, fusion weight, stage 3's shape) was made on the first 40 tasks. The remaining tasks were measured once. 26 tasks were dropped because API credits ran out mid-run; none were in dev.

Recall@k. For each task: of the files the developer edited, what fraction appear in our top k? Averaged over tasks. Recall@10 = 0.69 means 69% of the edited files are in the top 10.

Bootstrap intervals. Resample the test tasks with replacement 2,000 times and recompute the difference each time. The middle 95% of those differences is the interval. If it excludes zero, the improvement isn't a fluke of which tasks happened to be in the set.

Results

Koog, 70 held-out tasksrecall@5recall@10recall@20
BM250.420.530.63
Jev re-rank alone0.520.630.74
Jev + BM25 fused0.540.690.80
+ stage 3 (shipped)0.570.690.80

Shipped vs BM25: recall@10 +0.16 [+0.10, +0.23]. Stage 3 vs fused: recall@5 +0.03 [+0.006, +0.068]. Stage 3 only reorders the top 10, so it can't change recall@10 or @20.

Other checksresult
JetBrains/Exposed, 40 tasks, frozen pipelinerecall@5 0.43 → 0.54 (+0.10 [+0.01, +0.20]); @10 0.56 → 0.62, not significant
LLM re-ranker (GLM) on BM25's top 30, 70 tasksLLM better at @5 (0.61 vs 0.52), tie at @10 (0.67 vs 0.65); 29 s vs ~1 s
Claude Code + hook, 8 tasksturns 13.0 → 9.8 (−3.2 [−5.9, −0.5]), searches −38%, cost −15%, same recall on 7 of 8, not faster
GLM agent + tool, 10 tasks × 2 wordings7-22% fewer tokens, same recall, not faster to the first right file

Wrong turns

  1. Jev alone lost to BM25. This led to re-ranking on full source.
  2. The first "win" didn't hold. Tuned on 20 tasks, fused recall@10 looked like 0.64 against 0.47. On 30 unseen tasks it lost (0.51 against 0.56). The causes were unfair new-file commits in the set and a fusion weight tuned on too few tasks. Fixed with the filter and a proper dev/test split.
  3. The free gateway couldn't serve it. Vercel's AI Gateway answered about 30% of calls under load, so the plugin uses TypeSafe's own API.
  4. Agents ignored the tool. Claude Code never called pack_context, even when told to. So it gets the ranking through a hook instead.
  5. A code review caught a mismatch. The plugin's sketcher differed from the eval's on 2% of files. Fixed, with a stronger parity test.

IntelliJ internals

Agents: tool vs hook

An MCP tool depends on the agent choosing to call it. In five headless runs Claude Code never did, even with a system prompt naming the tool; it trusted its own Grep and Glob. The weaker GLM agent did call it, every time.

So agent/pack_hook.py is a Claude Code UserPromptSubmit hook. It runs on every request before Claude sees it, asks the IDE's pack_context, and adds the ranked files to Claude's context. The installed live hook defaults to keywords, with a 25 s deadline and silent failure. Set CONTEXT_PACKER_HOOK_PROVIDER explicitly to jev, laya, or configured to use a model. Laya needs a longer deadline on this CPU. The published Claude experiment used Jev: a warm hook added about 4 s, and the first MCP call after an IDE restart took about 10 s. Those measurements do not apply to the live keyword default.

Cost and safety

Limits

Code map

Path (under plugins/context-packer)What it does
src/.../pack/Packer.ktThe pipeline: pass 1, BM25, pool, pass 2, fusion, stage 3, roles, preview, failure policy
src/.../pack/Bm25.kt, Sketcher.kt, Candidates.ktKeyword ranking, sketches, file collection
src/.../jev/JevClient.kt, JevRelevance.ktJev HTTP client (TypeSafe and gateway), retries; how questions are built
src/.../ContextPackerService.ktProject service: read actions, cache, warm-up, keys, budget, listeners
src/.../ui/PackerPanel.kt, mcp/ContextPackerToolset.ktTool window; the MCP tool
agent/pack_hook.pyClaude Code prompt hook
spike/First experiments and the Koog eval (jev_spike.py, fusion.py, RESULTS.md)
eval/Stage 3, Exposed, LLM baseline, agent A/Bs, report generator

Glossary

BM25
Keyword ranking: frequent in the file, rare in the project, adjusted for length.
Jev / System One model
A model that returns typed decisions with probabilities, and never text.
noul / choice
Jev question types: a probability that a statement is true; a probability per labelled option.
Sketch
A ~300-token outline of a file, so 60 fit in one Jev call.
Pool
The shortlist: BM25's top 60 plus Jev-on-sketches' top 60.
Re-rank
Score a shortlist again, more carefully (here: Jev on full source).
Fusion
Combining two rankings into one score.
Stage 3
One comparative Jev choice over the top 10 that reorders them.
Recall@k
The fraction of the truly needed files that appear in the top k.
Held-out / dev / test
Tune on dev, report once on test, which was never looked at while tuning.
Bootstrap interval
The range the improvement would plausibly take with a different sample of tasks.
Leakage
The answer showing up in the input. Avoided by reading code as it was before each change.
MCP
Model Context Protocol: how agents discover and call tools. IntelliJ ships an MCP server.
Hook
A command Claude Code runs on an event; ours runs on every prompt and adds context.
Read action / EDT
IntelliJ's rules: read code under a read lock, update UI on the event dispatch thread.