pi-context-engine 0.1.2 → 0.1.4

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.
@@ -0,0 +1,532 @@
1
+ # Research: code retrieval literature → improvement backlog
2
+
3
+ This note summarizes what peer-reviewed and preprint research says about code
4
+ retrieval and code RAG, and maps each finding onto pi-context-engine.
5
+
6
+ **Why it exists:** we currently optimize for token savings and latency, both of
7
+ which we measure. Retrieval *quality* we do not measure. The literature shows
8
+ that retriever-side choices dominate final answer quality, so this is the
9
+ highest-leverage area to study before adding features.
10
+
11
+ **How to read it:**
12
+ - **Evidence A**: primary source read in full or abstract (arXiv/ACL/official blog).
13
+ - **Evidence B**: found via search result snippet only, not yet verified in full.
14
+ - **Recommendations** are tagged **P0** (quick win), **P1** (next), **P2**
15
+ (experiment, gate behind config). Every one lists the files to touch and how
16
+ to prove it worked.
17
+
18
+ Research done 2026-10-17. Sources are listed at the end with links.
19
+
20
+ **Implementation:** deferred by owner decision. The build plan, with per-item
21
+ steps, tests, acceptance criteria and a status table, lives in
22
+ [IMPLEMENTATION-PLAN.md](./IMPLEMENTATION-PLAN.md). Nothing is started yet.
23
+
24
+ ---
25
+
26
+ ## 1. What the evidence says
27
+
28
+ ### 1.1 The retriever matters more than the generator
29
+
30
+ - **Finding**: in a component-wise study of 21+ models across code generation,
31
+ summarization and repair, retriever-side components (query processing,
32
+ retrieval algorithm, context refinement) influenced final performance more
33
+ than the choice of generator model.
34
+ - **Why it matters for us:** pi-context-engine *is* the retriever side. Every
35
+ improvement here lands on every model the user runs, cheap and model-agnostic.
36
+ - **Evidence A**: Not All RAGs Are Created Equal (arXiv:2605.14503).
37
+
38
+ ### 1.2 BM25 is remarkably strong on code; hybrid is safe but conservative
39
+
40
+ - **Finding 6**: BM25 was "an exceptionally powerful and versatile retriever"
41
+ across text-heavy, clean and noisy code queries, often beating larger dense
42
+ models. Exact matches on algorithm names and error strings drive this.
43
+ - **Finding 9**: hybrid retrieval was "safe but conservative": in their
44
+ experiments it did **not** beat the single best retriever for a given task.
45
+ - **Finding 7/8**: bigger embedding models did not win, and code-specific
46
+ training did not reliably beat strong general-purpose models.
47
+ - **Why it matters for us:** our RRF hybrid and small general embedders
48
+ (all-MiniLM-L6-v2, nomic-embed-text) are consistent with the evidence. The
49
+ win is not a bigger model, it is better lexical handling and better query
50
+ understanding.
51
+ - **Evidence A**: arXiv:2605.14503, Findings 5-9.
52
+
53
+ ### 1.3 Query rewriting: keep the raw query by default
54
+
55
+ - **Finding 1**: the original, unaltered query is a powerful baseline and often
56
+ beats automated transformations when the task input is clear.
57
+ - **Finding 2**: elaboration helps only for ambiguous or noisy inputs.
58
+ - **Finding 4**: every LLM-based rewrite risks changing intent or dropping
59
+ critical details ("semantic drift").
60
+ - **Cost note**: an elaborated query cost +987 input tokens, roughly the price
61
+ of 2-3 extra retrieved documents.
62
+ - **Why it matters for us:** our `QueryParser` does regex intent
63
+ classification and keyword extraction, which is free and drift-free. The
64
+ evidence says do not add LLM query rewriting as a default.
65
+ - **Evidence A**: arXiv:2605.14503, Findings 1-4 and the cost table.
66
+
67
+ ### 1.4 Reranking: contested evidence, do not default to it
68
+
69
+ - **Negative (SE tasks)**: Findings 10/11 report reranking as "unreliable" and
70
+ "often detrimental": it disrupted an already good BM25 ordering and failed to
71
+ rescue weak retrievals. Their cost table shows a BGE reranker at 815 ms on a
72
+ 12.8 GB GPU, versus <10 ms for BM25.
73
+ - **Positive (repo-level completion)**: CodeRAG's preference-aligned "BestFit"
74
+ reranking (LLM reranker, then distilled to a small model) is a core reason it
75
+ beats SOTA on ReccEval and CCEval.
76
+ - **Positive (general RAG)**: Anthropic reports failed retrievals drop by 67%
77
+ when reranking is added on top of contextual retrieval.
78
+ - **Positive (code search)**: two-stage retrieve-then-rerank work reports
79
+ consistent gains for text-to-code search (arXiv:2208.11274, Evidence B snippet).
80
+ - **Why it matters for us:** the sign of the effect depends on the task. A
81
+ reranker is a reasonable *opt-in experiment*, never a default, until our own
82
+ eval says otherwise.
83
+ - **Evidence A**: arXiv:2605.14503 (Findings 10-11), arXiv:2509.16112,
84
+ Anthropic contextual retrieval blog.
85
+
86
+ ### 1.5 Compression: gentle and task-aware helps, aggressive hurts
87
+
88
+ - **Finding 12**: compression utility depends on the token budget and the
89
+ compressor. Code-specialized compressors with a generous budget were robust;
90
+ aggressive compression was consistently harmful. Their advice: if you compress,
91
+ keep a budget of at least 8000 tokens.
92
+ - **SWE-Pruner** (2026): a goal-conditioned 0.6B "skimmer" that keeps task-relevant
93
+ lines achieved 23-54% token reduction on SWE-bench Verified *while improving*
94
+ success rates, and up to 14.84x on single-turn QA. The key idea: pruning is
95
+ conditioned on the current task goal, not a fixed rule.
96
+ - **LLMLingua** (EMNLP 2023): a budget controller plus perplexity-based token
97
+ dropping reaches up to 20x compression with little loss.
98
+ - **Provence** (ICLR 2025): a lightweight trained pruner removes irrelevant
99
+ sentences from retrieved contexts before generation.
100
+ - **Why it matters for us:** our compressor truncates at fixed character caps
101
+ (100/300/800 by level) with no knowledge of the query, and our LLM summarizer
102
+ prompt is query-agnostic. Fixed caps can cut exactly the lines the agent needs.
103
+ - **Evidence A**: arXiv:2605.14503, arXiv:2601.16746, arXiv:2310.05736,
104
+ arXiv:2501.16214.
105
+
106
+ ### 1.6 Chunking: pure function-sized AST chunks measured worst
107
+
108
+ - **Controlled study (864 configurations, RepoEval + CrossCodeEval)**:
109
+ function-based chunking underperformed every other strategy by 3.57-5.64
110
+ percentage points (Cliff's delta = -1.0). Sliding window and cAST sat on the
111
+ cost-quality Pareto front; function chunking was never Pareto-optimal.
112
+ - **Dominant parameter**: cross-file context *length*. Doubling from 2048 to
113
+ 8192 tokens gave up to +4.2 points, while chunk size had a weaker,
114
+ non-monotonic effect.
115
+ - **cAST** (AST split-then-merge chunking): +4.3 Recall@5 on RepoEval retrieval,
116
+ +2.67 Pass@1 on SWE-bench. It packs AST nodes to a size budget measured in
117
+ non-whitespace characters, merges small siblings, and preserves file/class/
118
+ function metadata so hybrid matching improves.
119
+ - **Overlap**: two sources agree overlap is not clearly helpful (late chunking
120
+ appendix; a 2601 chunking study says "avoid overlap").
121
+ - **Why it matters for us:** `chunker.ts` emits whole functions and classes
122
+ exactly as parsed. There is no size budget, no sibling merge, no parent
123
+ context, and top-level code outside functions is not its own chunk. Our tasks
124
+ are questions rather than completion, so the transfer is partial, but the
125
+ direction is clear: density and surrounding context beat raw node boundaries.
126
+ - **Evidence A**: arXiv:2605.04763, arXiv:2506.15655, arXiv:2409.04701.
127
+
128
+ ### 1.7 Contextualized chunks: the biggest cheap win in the literature
129
+
130
+ - **Anthropic Contextual Retrieval**: prepend a short chunk-specific context
131
+ statement before embedding and before building the BM25 index.
132
+ - Contextual embeddings alone: top-20 failure rate 5.7% → 3.7% (−35%).
133
+ - Embeddings + contextual BM25: 5.7% → 2.9% (−49%).
134
+ - Plus reranking: −67%.
135
+ - **Late chunking** (Jina): embed the whole document first, pool per chunk
136
+ afterwards. +2.7% to +3.63% relative nDCG across models and datasets, no extra
137
+ training, and it beats naive chunking in almost every configuration.
138
+ - **Why it matters for us:** `pipeline.ts` embeds `c.content` verbatim. A chunk
139
+ from `src/retrieval/retriever.ts` carries no clue that it lives in the
140
+ retrieval module unless the words appear in the code itself. Deterministic
141
+ context headers (path, enclosing class, sibling names, imports) are free, need
142
+ no LLM, and match what both papers show helps most.
143
+ - **Evidence A**: Anthropic engineering blog, arXiv:2409.04701.
144
+
145
+ ### 1.8 Repository structure and graphs pay off
146
+
147
+ - **RepoGraph** (ICLR 2025): an AST-built graph (reference/definition nodes,
148
+ invoke/contain edges) plugged into four systems raised SWE-bench-Lite resolve
149
+ rate by +2.66 and +2.34 absolute, at roughly proportional or lower cost. Best
150
+ combined result was 29.67, state of the art among open-source methods at the time.
151
+ - **Aider** ranks context files with PageRank over the repo graph (cited in the
152
+ RepoGraph related work), which our own preprint source also observed in practice.
153
+ - **Why it matters for us:** we already build `calls`/`imports`/`defines` edges,
154
+ but graph expansion in `retriever.ts` only adds 2 bonus chunks from 2 related
155
+ files with a fixed 0.85 score factor. There is headroom to make the graph a
156
+ first-class retrieval path.
157
+ - **Evidence A**: arXiv:2410.14684.
158
+
159
+ ### 1.9 Real coding agents retrieve iteratively and along several paths
160
+
161
+ - **RepoCoder** (EMNLP 2023): iterative retrieval then generation, rerunning
162
+ retrieval with the model's own partial output, improved repository-level
163
+ completion by over 10% (Evidence B for the number, A for the mechanism).
164
+ - **CodeRAG** (EMNLP 2025): attacks three failure modes directly: bad query
165
+ construction (uses log-probability guided probing instead of "last k lines"),
166
+ single-path retrieval (runs several retrieval paths and fuses), and
167
+ retriever/generator mismatch (reranks by generator preference).
168
+ - **Exploratory study of code retrieval in coding agents** (2025 preprint, not
169
+ peer reviewed): across Claude Code, Codex CLI, Gemini CLI, Cursor, Amp, Cline
170
+ and Aider, every agent used progressive narrowing: broad first query, then
171
+ tighter follow-ups. It also found agents mixing lexical, semantic, LSP and
172
+ graph-based signals rather than relying on one.
173
+ - **Query performance prediction in agentic RAG** (2025): predicted quality of
174
+ the generated query correlates positively with final answer quality, and
175
+ better retrievers produce higher quality answers in shorter reasoning chains.
176
+ This is the basis for adaptive retrieval: signal low confidence and retry.
177
+ - **Why it matters for us:** our guidance tells the agent how to phrase one
178
+ query. The literature says the winning pattern is a *loop*: search, judge,
179
+ refine. We can support that with confidence signalling and refine hints.
180
+ - **Evidence A**: arXiv:2303.12570 (mechanism), arXiv:2509.16112,
181
+ arXiv:2507.10411; Evidence B: preprint 10.20944/preprints202510.0924.
182
+
183
+ ### 1.10 Evaluation: public code-search benchmarks mislead; measure recall and distraction yourself
184
+
185
+ - **CORE-Bench** (2026): agentic coding retrieval is "requirement-driven
186
+ repository search", not snippet matching. Representative embedding models
187
+ show a *sharp drop* from traditional code search to this setting, because of
188
+ intent-to-implementation gaps, scattered evidence, long chunks that dilute the
189
+ embedding, and dense in-repo distractors.
190
+ - **Voyage AI**: CoSQA has an estimated 51% incorrect labels, CodeSearchNet
191
+ queries are copied from docstrings (too easy), and public sets suffer data
192
+ contamination. They recommend mining eval queries from real SWE-bench-style work.
193
+ - **Metric mismatch**: UDCG (2025) shows classical nDCG/MAP/MRR mispredict RAG
194
+ performance because they ignore *distracting* (related but useless) documents;
195
+ a utility-and-distraction-aware metric improved correlation with end-to-end
196
+ accuracy by up to 36%.
197
+ - **Why it matters for us:** `smoke:real-world` measures tokens and latency only.
198
+ We cannot tell whether a change helps or hurts retrieval quality today, and
199
+ our 97% savings claim has no paired quality number.
200
+ - **Evidence A**: arXiv:2606.11864, arXiv:2510.21440, Voyage AI blog.
201
+
202
+ ### 1.11 Memory: link memories, do not just store them
203
+
204
+ - **A-MEM** (NeurIPS 2025): Zettelkasten-style memory with dynamic indexing and
205
+ linking. New memories get structured attributes (context, keywords, tags),
206
+ get linked to related old memories, and can *update* older memories as
207
+ understanding improves. Beats SOTA memory baselines on six foundation models.
208
+ - **MemGPT** (2023): memory as an OS-style paging system, the reference frame
209
+ for tiered agent memory.
210
+ - **Why it matters for us:** `record_decision` stores items that are searchable
211
+ but not linked to each other or to the code areas they concern, and old
212
+ entries are never revised.
213
+ - **Evidence A**: arXiv:2502.12110, arXiv:2310.08560.
214
+
215
+ ---
216
+
217
+ ## 2. Gap analysis: pi-context-engine today vs the evidence
218
+
219
+ | Area | Today | Evidence says | Gap |
220
+ |---|---|---|---|
221
+ | Chunk embedding text | raw `chunk.content` only | contextual headers cut failed retrievals by 49% | no path/class/import context in vectors or FTS |
222
+ | Chunking | whole functions/classes, no size budget | function chunking measured worst; split-merge + density wins | no split-merge, no sibling merge, no top-level code chunks |
223
+ | Served context length | packed to token budget, one chunk at a time | cross-file context length is the dominant quality factor | no parent/neighbor expansion around a hit |
224
+ | Compression | fixed 100/300/800 char caps, query-agnostic summaries | gentle, task-conditioned pruning helps; aggressive hurts | compression ignores the query |
225
+ | FTS/BM25 | OR-joined tokens, quoted phrases flattened, default column weights | BM25 strongest single retriever for code; contextual BM25 gave the bigger jump | no phrase search, no path/file column weighting, no symbol path |
226
+ | Query handling | regex intents + keywords, free and drift-free | raw query best; elaborate only when ambiguous; QPP signals help | no low-confidence feedback loop, no refine hints |
227
+ | Graph | 1-hop file bonus: 2 files × 2 chunks, fixed 0.85 factor | RepoGraph: +2.66/+2.34 absolute SWE-bench resolve | graph is a footnote, not a retrieval path |
228
+ | Reranking | none | contested: negative for SE tasks, positive for repo completion | fine as-is; only revisit as a gated experiment |
229
+ | Evaluation | tokens + latency | recall + distraction metrics needed to trust any change | no labeled query set, no quality metric |
230
+ | Memory | stored + searchable, no links | linking and memory evolution improve recall | no decision-to-decision or decision-to-code links |
231
+
232
+ ---
233
+
234
+ ## 3. Recommendations
235
+
236
+ ### P0: quick wins, no new dependencies
237
+
238
+ **R1. Prepend deterministic context headers to chunks (indexing + FTS).**
239
+ - **Do:** build a header per chunk: file path, language, enclosing class or
240
+ module, function signature, imports of the file, and a one-line "part of
241
+ file X" statement. Prepend it before embedding *and* before FTS ingest.
242
+ - **Why:** Anthropic measured a 49% cut in failed retrievals from exactly this,
243
+ and late chunking shows the same "chunks lose surrounding context" effect
244
+ (+2.7-3.6% nDCG). Our headers need no LLM, so there is no per-chunk cost.
245
+ - **Where:** `src/indexer/chunker.ts` (`nodeToChunk`), `src/indexer/pipeline.ts`
246
+ (`embedChunks` embeds `c.content`), `src/storage/fts-store.ts` (`ingest`).
247
+ The embedding cache is keyed by content hash, so the header change triggers a
248
+ clean full re-embed; bump an index-format version to make that explicit.
249
+ - **Verify:** Recall@k on the labeled set (R4) before/after, plus a canary
250
+ query per file that mentions the module name but not the code words.
251
+
252
+ **R2. Strengthen BM25: phrases, column weights, symbol matching.**
253
+ - **Do:** detect quoted input and keep it as an FTS5 phrase; weight `file_path`
254
+ and `chunk_type` columns explicitly in the `bm25()` call; surface exact
255
+ symbol hits (a query token matching a function/class name) as a strong signal.
256
+ - **Why:** BM25 is the most reliable retriever for code in the study, and exact
257
+ identifiers are where dense embeddings are weakest. We already OR-join tokens,
258
+ which is safe, but phrases like `"ENOENT handler"` cannot match today.
259
+ - **Where:** `src/storage/fts-store.ts` (`escapeFts5`, `search`),
260
+ `src/retrieval/retriever.ts` (already boosts FTS for `code_lookup` intent).
261
+ - **Verify:** the identifier-heavy query slice of R4 should improve first.
262
+
263
+ **R3. Make compression query-aware.**
264
+ - **Do:** add a `query` parameter to compression. Keep the fixed caps as the
265
+ fallback, but when a query is present, retain lines containing query tokens,
266
+ identifiers, imports and the signature, then truncate. Treat `full` as the
267
+ budget-generous level (evidence says budgets under ~8k tokens lose quality).
268
+ - **Why:** SWE-Pruner shows task-conditioned pruning both cuts tokens and raises
269
+ success rates, while Finding 12 shows fixed aggressive caps hurt. Our current
270
+ caps can delete the only line that answers the question.
271
+ - **Where:** `src/compression/compressor.ts` (`TRUNCATION_LIMITS`, prompts),
272
+ `src/retrieval/retriever.ts` (packing already estimates compressed size).
273
+ - **Verify:** measure served tokens and answer quality per compression level on
274
+ the labeled set; expect equal or better recall at equal or fewer tokens.
275
+
276
+ **R4. Build a labeled retrieval eval and report quality in the smoke run.**
277
+ - **Do:** a small query set (start with 50-100 queries over this repo: bug-report
278
+ style, identifier style, NL style, file-name style) with expected file or
279
+ chunk labels. Report Recall@5, Recall@10, nDCG@10, a distraction ratio
280
+ (relevant chunks among served), tokens and latency. Add it to
281
+ `npm run smoke:real-world`.
282
+ - **Why:** every other recommendation depends on being able to tell "better"
283
+ from "feels better". CORE-Bench shows code-search scores overstate agentic
284
+ usefulness, and UDCG shows distraction must be counted, not ignored. This also
285
+ gives our 97% savings claim a paired quality number.
286
+ - **Where:** `scripts/smoke-real-world.mjs`, new `tests/eval/` fixture,
287
+ optionally a CORE-Bench sample from Hugging Face for external validity.
288
+ - **Verify:** the harness itself: it must fail loudly when scores drop.
289
+
290
+ ### P1: next iteration
291
+
292
+ **R5. Multi-path retrieval with a structured symbol path.**
293
+ - **Do:** keep vector + FTS, and add a third path: exact lookup in the graph node
294
+ index for symbols and file paths named in the query. Fuse all three with the
295
+ existing RRF.
296
+ - **Why:** CodeRAG attributes its gains to multi-path retrieval over a single
297
+ similarity path; the coding-agent study found real agents mix lexical,
298
+ semantic and structural signals. We extract identifiers today but only use
299
+ them for keyword distance.
300
+ - **Where:** `src/retrieval/retriever.ts`, `src/retrieval/query-parser.ts`,
301
+ `src/storage/graph-store.ts`.
302
+ - **Verify:** identifier and "where is X defined" query slices of R4.
303
+
304
+ **R6. Small-to-big serving: expand hits with parent and neighbor context.**
305
+ - **Do:** after ranking, expand the top hits with the enclosing module summary
306
+ and adjacent chunks in the same file, plus the file-level module chunk, until
307
+ the marginal-ratio stop. Keep `expand_chunk` for deeper drilling.
308
+ - **Why:** cross-file context length was the dominant quality parameter in the
309
+ controlled study (+4.2 points from 2048 → 8192 tokens), and small-to-big is
310
+ the standard fix for "the chunk that retrieves well is not the chunk that
311
+ answers well". CORE-Bench also flags long, diluted chunks as a problem, so
312
+ expand selectively rather than making every chunk bigger.
313
+ - **Where:** `src/retrieval/retriever.ts` (diversity/packing stage),
314
+ `src/indexer/chunker.ts` (emit module and top-level chunks for AST files too).
315
+ - **Verify:** recall should rise while token count stays bounded by the budget.
316
+
317
+ **R7. Make the graph a real retrieval path (context packs).**
318
+ - **Do:** for each top hit, build a small context pack: the node, its file, its
319
+ callers/callees or importers/importees with edge types labeled, and a
320
+ centrality-based prior for which files are "important" in this repo.
321
+ - **Why:** RepoGraph gained +2.66/+2.34 absolute on SWE-bench-Lite across four
322
+ systems at no extra cost; Aider's PageRank file ranking was observed working
323
+ in practice. Our current expansion is 2 chunks from 2 files with a magic 0.85.
324
+ - **Where:** `src/storage/local-backend.ts` (`getRelatedFilePaths`),
325
+ `src/storage/graph-store.ts`, `src/retrieval/retriever.ts`,
326
+ `related_context` tool in `src/extension.ts`.
327
+ - **Verify:** cross-file queries in R4; count how often the labeled answer file
328
+ arrives via graph edges instead of similarity.
329
+
330
+ **R8. Adaptive retrieval: score the query, tell the agent, then optionally elaborate.**
331
+ - **Do:** treat the score distribution (top score, margin to the second cluster,
332
+ agreement between vector and FTS) as a query-performance signal. When it is
333
+ low, return it in the tool result: "low confidence, try X". Only when the
334
+ agent opts in (or the query is classified ambiguous), generate an elaborated
335
+ variant and run both, keeping the raw query's results.
336
+ - **Why:** predicted query performance correlates with answer quality in
337
+ agentic RAG, and Findings 1/2/4 say raw queries win by default while
338
+ elaboration helps only ambiguous ones. This gives us the loop real agents use
339
+ (broad first, narrow second) without semantic drift.
340
+ - **Where:** `src/retrieval/confidence.ts`, `src/retrieval/query-parser.ts`,
341
+ `src/guidance.ts` (document the new signal for the agent).
342
+ - **Verify:** measure how often a low-confidence signal is followed by a
343
+ successful second query in real sessions; track recall on ambiguous queries.
344
+
345
+ **R9. Return refine suggestions with every search.**
346
+ - **Do:** in `context_search` results, add 2-3 concrete next queries (sibling
347
+ symbol names, the file that almost matched, an alternate term seen in top
348
+ hits). Keep it to a line or two to stay token-cheap.
349
+ - **Why:** every production coding agent in the exploratory study narrowed
350
+ iteratively, and RepoCoder's loop is the mechanism behind its reported gain.
351
+ Suggestions turn our one-shot tool into a guided loop at near-zero cost.
352
+ - **Where:** tool response shaping in `src/extension.ts` or `engine.ts`.
353
+ - **Verify:** follow-up query rate and whether sessions stop earlier.
354
+
355
+ ### P2: experiments, gated behind config, only with R4 in place
356
+
357
+ **R10. Opt-in reranker.**
358
+ - **Do:** `retrieval.rerank: off | cheap` using a small ONNX cross-encoder via
359
+ the transformers.js runtime we already ship (text-classification task).
360
+ Default `off`.
361
+ - **Why:** evidence is genuinely split. The SE study found reranking harmful
362
+ and expensive (815 ms GPU), while CodeRAG and Anthropic found large gains on
363
+ repository tasks. Only our own eval can settle which side applies to pi's
364
+ workloads.
365
+ - **Where:** new `src/retrieval/reranker.ts`, config in `src/config.ts`.
366
+ - **Verify:** R4 with rerank on/off; enable by default only if it wins on
367
+ quality *and* the latency budget holds (warm queries are ~11 ms today).
368
+
369
+ **R11. Optional code-specialized embedder.**
370
+ - **Do:** document an `embedding.model: code` option (a code-oriented ONNX
371
+ model) next to the general default, and evaluate it before recommending it.
372
+ - **Why:** Findings 7/8 say scale and code-specific training did not reliably
373
+ win, but CORE-Bench shows agentic retrieval is a different, harder regime
374
+ where in-domain training helped a lot. So: offer it, measure it, then decide.
375
+ - **Where:** `src/indexer/embedder.ts` (`DEFAULT_LOCAL_MODEL`), README config block.
376
+ - **Verify:** R4 per model; the local backend already reindexes on dimension change.
377
+
378
+ **R12. Rework chunking toward density: split-merge with a budget.**
379
+ - **Do:** adopt cAST's algorithm: top-down fit of AST nodes to a non-whitespace
380
+ character budget, greedy merge of small siblings, and emit module/top-level
381
+ chunks so code outside functions is retrievable. Keep chunks concatenable back
382
+ to the original file.
383
+ - **Why:** function-only chunking was the worst measured strategy (−3.57 to
384
+ −5.64 points) and was never Pareto-optimal; cAST gains 4.3 Recall@5. Our
385
+ current chunker does exactly this (whole function/class nodes, no budget).
386
+ - **Where:** `src/indexer/chunker.ts` (`walk`, `nodeToChunk`), chunk-id scheme in
387
+ `makeChunk` (ids include line spans, so the upstream column-offset fix must
388
+ survive the rework).
389
+ - **Verify:** R4 by query type, plus re-run `smoke:real-world` for chunk counts
390
+ and token deltas; expect more, denser chunks.
391
+
392
+ **R13. Link memories (A-MEM style).**
393
+ - **Do:** when writing a decision or code area, compute links to related past
394
+ entries (embedding similarity + the `decided`/`discussed_in` graph edges) and
395
+ allow a later entry to annotate an earlier one instead of only appending.
396
+ - **Why:** A-MEM's linked, evolving memories beat fixed-structure memory on six
397
+ models. Our memory is flat today, so recall quality degrades as history grows.
398
+ - **Where:** `src/memory/db.ts`, `decision-extractor.ts`, `session-capture.ts`.
399
+ - **Verify:** a small recall test: seed N decisions, query for each by topic,
400
+ measure whether its linked neighbours surface too.
401
+
402
+ ---
403
+
404
+ ## 4. What the literature says not to do
405
+
406
+ - **No always-on LLM query rewriting**: semantic drift risk plus ~987 tokens per
407
+ rewrite (Finding 4 and the cost table).
408
+ - **No default cross-encoder reranking**: often negative on SE tasks, expensive,
409
+ and our warm-query latency would regress (Findings 10-11).
410
+ - **No aggressive compression**: fixed tight caps and hard truncation lose the
411
+ answering lines (Finding 12, SWE-Pruner).
412
+ - **No chunk overlap by default**: two evaluations found no clear benefit.
413
+ - **No "bigger embedder" reflex**: scale and code-specific training did not
414
+ decide outcomes (Findings 7/8). Measure first with R4.
415
+ - **No full-file indexing as a substitute for retrieval**: useful for repos
416
+ under ~200k tokens, per Anthropic, which is a niche case.
417
+
418
+ ---
419
+
420
+ ## 5. Measurement plan
421
+
422
+ 1. **Baseline first**: run R4's harness on the current release and record
423
+ Recall@5/@10, nDCG@10, distraction ratio, tokens, latency.
424
+ 2. **One change at a time**: each Rn lands with its before/after numbers in the
425
+ PR. This mirrors how every paper above isolates one component.
426
+ 3. **Report quality in `smoke:real-world`**, next to the existing savings and
427
+ latency numbers, so regressions are visible without a separate tool.
428
+ 4. **External check**: optionally run a CORE-Bench level-2 slice to confirm
429
+ gains transfer beyond our own query set.
430
+ 5. **Session telemetry (opt-in)**: track low-confidence rate and follow-up
431
+ query rate to validate R8/R9 outside the lab.
432
+
433
+ ---
434
+
435
+ ## 6. Sources
436
+
437
+ Primary sources read for this note (Evidence A unless marked):
438
+
439
+ - **Not All RAGs Are Created Equal: A Component-Wise Empirical Study for SE Tasks**
440
+ (2026) https://arxiv.org/abs/2605.14503
441
+ Retriever beats generator; BM25 robust; query rewriting and reranking are
442
+ situational, not defaults.
443
+ - **How Does Chunking Affect Retrieval-Augmented Code Completion?** (2026)
444
+ https://arxiv.org/abs/2605.04763
445
+ Function chunking worst; cross-file context length dominant.
446
+ - **cAST: Enhancing Code RAG with Structural Chunking via AST** (2025)
447
+ https://arxiv.org/abs/2506.15655
448
+ Split-merge AST chunking, char budget, metadata retention.
449
+ - **RepoGraph: Enhancing AI Software Engineering with Repository-level Code Graph**
450
+ (ICLR 2025) https://arxiv.org/abs/2410.14684
451
+ Graph plug-in: +2.66/+2.34 absolute SWE-bench-Lite resolve.
452
+ - **CodeRAG: Finding Relevant and Necessary Knowledge for RAG Repository-Level
453
+ Code Completion** (EMNLP 2025) https://arxiv.org/abs/2509.16112
454
+ Log-probability query construction, multi-path retrieval, preference-aligned reranking.
455
+ - **RepoCoder: Repository-Level Code Completion through Iterative Retrieval and
456
+ Generation** (EMNLP 2023) https://arxiv.org/abs/2303.12570
457
+ Iterative retrieve-generate loop for repo context.
458
+ - **CORE-Bench: Code Retrieval in the Era of Agentic Coding** (2026)
459
+ https://arxiv.org/abs/2606.11864
460
+ Agentic retrieval is requirement-driven repo search; standard benchmarks overstate.
461
+ - **SWE-Pruner: Self-Adaptive Context Pruning for Coding Agents** (2026)
462
+ https://arxiv.org/abs/2601.16746
463
+ Task-conditioned pruning: 23-54% fewer tokens, success rates up.
464
+ - **Provence: Efficient and Robust Context Pruning for RAG** (ICLR 2025)
465
+ https://arxiv.org/abs/2501.16214
466
+ Lightweight trained pruner removes irrelevant sentences pre-generation.
467
+ - **LLMLingua: Compressing Prompts for Accelerated Inference** (EMNLP 2023)
468
+ https://arxiv.org/abs/2310.05736
469
+ Budget-controlled compression, up to 20x with little loss.
470
+ - **Late Chunking: Contextual Chunk Embeddings** (2024)
471
+ https://arxiv.org/abs/2409.04701
472
+ +2.7-3.6% relative nDCG; overlap shows no clear benefit.
473
+ - **Anthropic, Contextual Retrieval** (industry, engineering blog, 2024)
474
+ https://www.anthropic.com/engineering/contextual-retrieval
475
+ Failed retrievals −49% (embeddings + BM25), −67% with reranking.
476
+ - **Lost in the Middle: How Language Models Use Long Contexts** (TACL 2024)
477
+ https://arxiv.org/abs/2307.03172
478
+ Result ordering matters: information in the middle of long context is used least.
479
+ - **A-MEM: Agentic Memory for LLM Agents** (NeurIPS 2025)
480
+ https://arxiv.org/abs/2502.12110
481
+ Zettelkasten linking plus memory evolution beats fixed memory structures.
482
+ - **MemGPT: Towards LLMs as Operating Systems** (2023)
483
+ https://arxiv.org/abs/2310.08560
484
+ Tiered, OS-style memory management for agents.
485
+ - **Am I on the Right Track? Predicted Query Performance in Agentic RAG** (2025)
486
+ https://arxiv.org/abs/2507.10411
487
+ Query quality prediction correlates with answer quality: basis for adaptive retrieval.
488
+ - **Search-R1: Training LLMs to Reason and Leverage Search Engines with RL** (2025)
489
+ https://arxiv.org/abs/2503.09516
490
+ Reference design for search-as-a-tool inside reasoning loops.
491
+ - **Precise Zero-Shot Dense Retrieval without Relevance Labels (HyDE)** (ACL 2023)
492
+ https://arxiv.org/abs/2212.10496
493
+ Query→hypothetical answer→embedding; costs a generation per query.
494
+ - **Redefining Retrieval Evaluation in the Era of LLMs** (2025)
495
+ https://arxiv.org/abs/2510.21440
496
+ UDCG metric counts distraction, +36% correlation with end-to-end accuracy.
497
+ - **Retrieval-Augmented Code Generation: A Survey (Repository-Level)** (2025)
498
+ https://arxiv.org/abs/2510.04905
499
+ Taxonomy of repo-level retrieval: substrate, control regime, evaluation.
500
+ - **Revisiting Code Search in a Two-Stage Paradigm** (2022) Evidence B
501
+ https://arxiv.org/abs/2208.11274
502
+ Retrieve-then-rerank for code search.
503
+ - **CORNSTACK: High-Quality Contrastive Data for Code Retrieval and Reranking**
504
+ (ICLR 2025) Evidence B https://arxiv.org/abs/2412.01007
505
+ Rerankers matter for code retrieval when trained on good data.
506
+ - **COIR: A Comprehensive Benchmark for Code Information Retrieval** (ACL 2025)
507
+ Evidence B https://aclanthology.org/2025.acl-long.1072.pdf
508
+ Broader code-IR benchmark beyond CodeSearchNet.
509
+ - **CodeSearchNet Challenge** (2019) Evidence B https://arxiv.org/abs/1909.09436
510
+ The classic benchmark, and its limits.
511
+ - **Voyage AI, How do we evaluate vector-based code retrieval?** (industry, 2024)
512
+ https://blog.voyageai.com/2024/12/04/code-retrieval-eval/
513
+ CoSQA ~51% bad labels; CodeSearchNet queries trivial; mine evals from SWE work.
514
+ - **An Exploratory Study of Code Retrieval Techniques in Coding Agents** (2025
515
+ preprint, not peer reviewed) Evidence B
516
+ https://doi.org/10.20944/preprints202510.0924
517
+ Seven production agents all narrow iteratively and mix lexical/semantic/graph/LSP.
518
+ - **ColBERT: Efficient Passage Search via Late Interaction** (SIGIR 2020)
519
+ Evidence B https://arxiv.org/abs/2004.12832
520
+ Late interaction: better than bi-encoders, heavier infrastructure than ours.
521
+
522
+ ---
523
+
524
+ ## 7. Open questions
525
+
526
+ - Does the function-chunking penalty from the completion study transfer to our
527
+ question-answering workload? R4 + R12 will answer this empirically.
528
+ - Which side of the reranking debate applies to pi: SE tasks or repo tasks?
529
+ - Is a code-specialized embedder worth the extra model weight for local users,
530
+ given Findings 7/8?
531
+ - Should confidence scoring learn weights (vector/keyword/recency) from the
532
+ labeled set instead of the fixed 0.5/0.4/0.1 split?
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-context-engine",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Semantic code context for pi: tree-sitter index + hybrid vector/BM25 retrieval + cross-session memory, all local. Port of code-context-engine (MIT, elara-labs) as a native pi extension.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -51,6 +51,9 @@
51
51
  "web-tree-sitter": "^0.22.6",
52
52
  "yaml": "^2.9.1"
53
53
  },
54
+ "optionalDependencies": {
55
+ "@huggingface/transformers": "^4.3.1"
56
+ },
54
57
  "peerDependencies": {
55
58
  "@earendil-works/pi-ai": "*",
56
59
  "@earendil-works/pi-coding-agent": "*",
@@ -60,7 +63,6 @@
60
63
  "@earendil-works/pi-ai": ">=0.80.0",
61
64
  "@earendil-works/pi-coding-agent": ">=0.99.2",
62
65
  "@types/node": "^22.0.0",
63
- "@huggingface/transformers": "^4.3.1",
64
66
  "typebox": "^1.0.0",
65
67
  "typescript": "^5.9.0",
66
68
  "vitest": "^3.1.0"
package/src/config.ts CHANGED
@@ -60,13 +60,9 @@ export interface Config {
60
60
  retrievalConfidenceThreshold: number;
61
61
  retrievalTopK: number;
62
62
  retrievalMarginalRatio: number;
63
- bootstrapMaxTokens: number;
64
- indexerWatch: boolean;
65
- indexerDebounceMs: number;
66
63
  indexerIgnore: string[];
67
64
  indexerRedactSecrets: boolean;
68
65
  memoryRedactPii: boolean;
69
- auditLogEnabled: boolean;
70
66
  pricingModel: string;
71
67
  pricingInput: number | null;
72
68
  pricingOutput: number | null;
@@ -84,13 +80,9 @@ export function defaultConfig(): Config {
84
80
  retrievalConfidenceThreshold: 0.2,
85
81
  retrievalTopK: 20,
86
82
  retrievalMarginalRatio: 0.75,
87
- bootstrapMaxTokens: 10000,
88
- indexerWatch: true,
89
- indexerDebounceMs: 500,
90
83
  indexerIgnore: [...DEFAULT_IGNORE],
91
84
  indexerRedactSecrets: true,
92
85
  memoryRedactPii: true,
93
- auditLogEnabled: false,
94
86
  pricingModel: "opus",
95
87
  pricingInput: null,
96
88
  pricingOutput: null,
@@ -109,13 +101,9 @@ const MAPPING: Record<string, keyof Config> = {
109
101
  "retrieval.confidence_threshold": "retrievalConfidenceThreshold",
110
102
  "retrieval.top_k": "retrievalTopK",
111
103
  "retrieval.marginal_ratio": "retrievalMarginalRatio",
112
- "retrieval.bootstrap_max_tokens": "bootstrapMaxTokens",
113
- "indexer.watch": "indexerWatch",
114
- "indexer.debounce_ms": "indexerDebounceMs",
115
104
  "indexer.ignore": "indexerIgnore",
116
105
  "indexer.redact_secrets": "indexerRedactSecrets",
117
106
  "memory.redact_pii": "memoryRedactPii",
118
- "audit.enabled": "auditLogEnabled",
119
107
  "storage.path": "storagePath",
120
108
  "pricing.model": "pricingModel",
121
109
  "pricing.input": "pricingInput",
package/src/engine.ts CHANGED
@@ -7,7 +7,7 @@ import fs from "node:fs";
7
7
  import path from "node:path";
8
8
  import type { Config } from "./config.js";
9
9
  import { loadConfigForProject, resolveOllamaUrl } from "./config.js";
10
- import { epochSeconds, humaniseRelativeTime, projectStorageDir, sha256Hex } from "./utils.js";
10
+ import { atomicWriteText, epochSeconds, humaniseRelativeTime, projectStorageDir, sha256Hex } from "./utils.js";
11
11
  import { LocalBackend } from "./storage/local-backend.js";
12
12
  import { Embedder, selectBackend } from "./indexer/embedder.js";
13
13
  import { HybridRetriever } from "./retrieval/retriever.js";
@@ -146,6 +146,7 @@ export class ContextEngine {
146
146
  draining = false;
147
147
  private searchesSinceDecision = 0;
148
148
  private hasRecordedDecision = false;
149
+ private vecBackfillStarted = false;
149
150
 
150
151
  constructor(options: EngineOptions) {
151
152
  this.projectDir = fs.realpathSync(path.resolve(options.projectDir));
@@ -247,7 +248,7 @@ export class ContextEngine {
247
248
 
248
249
  private saveStats(): void {
249
250
  try {
250
- fs.writeFileSync(this.statsPath, JSON.stringify(this.stats));
251
+ atomicWriteText(this.statsPath, JSON.stringify(this.stats));
251
252
  } catch {
252
253
  // stats are best-effort
253
254
  }
@@ -426,6 +427,25 @@ export class ContextEngine {
426
427
  void this.drainCompressionQueue();
427
428
  }, 5000);
428
429
  this.drainTimer.unref?.();
430
+ void this.backfillMemoryVec().catch(() => undefined);
431
+ }
432
+
433
+ /**
434
+ * One-shot semantic-memory backfill (upstream #137 lesson). After an
435
+ * embedder change the vec tables are recreated empty; without this, old
436
+ * decisions silently lose semantic recall (FTS still works, so the loss is
437
+ * easy to miss). Guarded by a cheap missing-row count so the steady state
438
+ * never initialises an embedder for this. Recall also self-heals via
439
+ * `MemoryDb.healVecTables`, covering wipes that happen after this runs.
440
+ */
441
+ private async backfillMemoryVec(): Promise<void> {
442
+ if (this.vecBackfillStarted) return;
443
+ this.vecBackfillStarted = true;
444
+ const memory = this.getMemory();
445
+ if (!memory || !memory.vecAvailable || !memory.hasVecTables()) return;
446
+ const pending = memory.vecBackfillPending();
447
+ if (pending.decisions + pending.turnSummaries === 0) return;
448
+ await memory.backfillVecTables(await this.getEmbedder());
429
449
  }
430
450
 
431
451
  async shutdown(): Promise<void> {