All notable changes to this project are documented here. The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
2.1.2 - 2026-09-25
- The
PreToolUseguard no longer blocks work it should let through:- the first
codebase-indexcommand of a session turns the guard off even when it is the command that builds the index; before, a repository without an index kept the guard on for the whole session. Session state now lives in the system temp directory (CBX_HOOK_STATEoverrides it) instead of inside the index; - heredoc bodies (
cat > script.ps1 <<'EOF',python - <<EOF) are file content, not commands, and are no longer scanned forgrep/Select-String; - a retry is matched by its search term, not by the whole tool call, so a rewritten command or a new description for the same search goes through;
- a quoted alternation such as
grep "a\|b"is read whole, and a search on its own line of a multi-line command is recognised.
- the first
2.1.1 - 2026-09-25
codebase-index install-hooks [--global]makes Claude Code search through the index by default. It merges two hooks into.claude/settings.json(or~/.claude/settings.json), keeping every other hook:SessionStart: in an indexed repository, one line of context naming the commands that answer code questions;PreToolUseonGrep|Bash: the first code search of a session, before anycodebase-indexcommand has run, is sent back with the index command to use instead. Repeating the same call lets it through, the first index command turns the guard off for the session, and searches over docs, logs or config, pipelines such asps | grep, and directories without an index are never intercepted.CBX_GUARD=0disables it;install-hooks --uninstallremoves both hooks. The hooks run ascodebase-index-hook, a standard-library-only entry point (about 0.1 s per call). The Claude Code plugin registers the same hooks.
2.1.0 - 2026-09-24
Agent-efficiency release. An agent answering five code questions on a 5.9k-file
Java/Rust monorepo spent 19% fewer tokens (83.5k vs 103.0k) and 25% fewer tool calls
(9 vs 12) with the skill than with Grep/Read alone, three runs per arm, with no
overlap between the arms' ranges; before this release the two were at parity. The
gain comes from answer-ready output (--compact: ranked files with numbered matching
lines), refs/impact that resolve Owner.member targets exactly and say when a list
may be incomplete, build-module awareness in impact, and ranking measured over 342
queries (MRR +0.010, p=0.031). Index schema 4: the first update rebuilds the index
once.
refs,impactandsymbolaccept a qualifiedOwner.membertarget (TownService.refresh, alsopkg.Owner.member,Owner::member,Owner#member), where the owner is a type or a module (activation::place). Before, a qualified name matched nothing and a bare name that several types share (refresh) returned every same-named method's callers mixed together.- Calls written against a type or module (
TownService.refresh(server),Foo::new(),activation::place(..)) now resolve across files when exactly one type or module of that name defines the callee, soimpactwalks them. These edges are markedinferred. - Call resolution no longer invents edges: a call never binds across languages (a
Python
queue.take()to a Javatake), a call on another type (ApprenticeService.take) no longer binds to the repo's onlytakewhen that one belongs to a different type, and a call on a type the file does not define no longer binds to a same-named method in the calling file. Name uniqueness is now judged per language family (JVM, JS/TS, C/C++), so a Rustplaceresolves even when Java also defines one. refs "Owner.member"no longer reports a confident empty answer when calls it cannot type exist (chest.holdings().take(..)): they are listed aspossible_call, nearest to the definition first, andcoverage.partialis true.refsandimpactreportcoverage.partial: truewith a reason when the target matches no indexed definition or call site, instead of an empty answer that looked like "no callers".updateparses changed files in a process pool, like a full build. It used to parse them one at a time, so a large change set was slower to update than to rebuild (2009 changed files: 149 s, against 13 s for a full build of all 5904).- A class is returned instead of its same-named constructor. The constructor's
new X()callers gave it the higher in-degree, so it took the file's one slot on the page with a one-line body while the class never appeared. - Kotlin call sites are extracted (with their receiver). Kotlin's
call_expressionhas no named fields, so no Kotlin call edge was ever recorded.
- Rust enum variants, Java enum constants and C# enum members are indexed as
variantsymbols (CoreError.ObjectDamaged), and a RustOwner::namepath used in a pattern or as a value (Err(CoreError::ObjectDamaged(_)) =>,if let Kind::A = ..) is recorded as areferenceedge.refsreports these askind: "reference", so it finds where a variant is handled, not only where it is constructed. refs --exclude-tests,refs --path <prefix>(repeatable; also on the MCPfind_refstool) andrefs --compact, which prints one line per site:path:line kind caller -> target [confidence].describeon a class, enum, struct or trait lists itsmembersand folds their edges from and to code outside the type intoused_by/uses. It used to report zero callers and callees for a class, whose methods, not the class, are called.--sessionis accepted bysymbol,refsandimpact, so one tag can be passed to every read command.search --compact/explain --compact: agent text instead of JSON. Each result ispath:start-end symbolswith up to eight numbered lines underneath that carry the match (rarer query words first), so an agent can citefile:linewithout a Read or agrep -n. On a benchmark query the packet went from 9.6 KB of JSON to 3.7 KB. Evidence already sent to the session prints as(already sent).impact --compact, and amodulesfield onimpact: the build-system module the target lives in (Gradle, Maven, Cargo, npm, Go, Python) and the lines of other build files that name it, split into workspace membership and dependencies. "Does another module depend on this?" no longer needs a grep over build files.- The skill routes every question through
--compactand is 40% shorter.
-
Ranking, measured over 342 queries on six query sets (five repositories, including 24 natural-language "how does X work" questions): MRR +0.010 (p=0.036), recall@10 +0.015 (p=0.038), nDCG@10 +0.009 (p=0.018), useful@budget +0.020 (p=0.015), 15 fewer snippet tokens per query; no corpus regressed. On the natural-language questions MRR went 0.428 → 0.475 and recall@10 0.708 → 0.833. Three signals, each ablatable:
resource_priors: localisation catalogues (lang/en_us.json,i18n/*.json,.po) and workflow artifacts (review.diff/.patchfiles, agent scratch directories such as.superpowers/) are demoted like generated code;stem_match: a file named after a query term (Treasury.javafor a question about the treasury) gets a small bonus;question_pool_floor: a question-form query ("how is a crop loaded") gets a 40-deep candidate pool, so the class it paraphrases reaches the reranker.
-
symbolreturns exact matches alone when there are any, with a count of the prefix matches it left out (more_prefix_matches), instead of burying the one symbol asked for among twentyTreasuryXprefix matches. -
Every
refssite carriescaller(the function it sits in),target(the definition it resolved to) andreceiver(what the call was made on), so callers can be named and same-named methods told apart without reading each file. -
Index schema 4: call edges store their receiver. The first
updateafter upgrading rebuilds the index once; read commands keep working on an older index until then.
2.0.0 - 2026-09-14
Evidence release. Everything an agent reads through codebase-index is now identified
by the exact bytes of the span it came from, can be re-checked against the working tree
at any later moment, is not resent to a session that already holds it, and is reported
when it changes. 2.0.0 also includes the community-readiness work merged from #26 and the
1.10.0 ranking release, which was prepared but never published on its own; its notes are in
the 1.10.0 section of the changelog.
Measured by replaying this repository's own history in sessions of 5 to 87 tasks
(tests/eval/results/2026-09-14-evidence-memory.md): memory withheld zero stale
snippets at every session length while the locator-based alternative withheld
15–54, change notices had precision 1.000 and recall 0.87–0.97, restoring withheld
snippets reproduced the 1.10.0 packet on every task, and snippet tokens fell by
4–11%. The savings are modest on this corpus; correctness is the deliverable. Summary in
docs/BENCHMARKS.md, design in
docs/MEMORY.md.
-
Evidence identity and verification. Every delivered snippet carries a
path:start-end@hashreference whose hash covers the span's exact bytes under the indexer's line model.codebase-index verify [REF ...] [--session TAG] [--strict]and MCPverify_evidencere-check references against the working tree, read-only and without an index, returningvalid,relocated,changed,ambiguous,deleted,excludedorunreadableper reference andall_validoverall. References are untrusted input: absolute paths, drive letters,..and NUL are rejected before any filesystem access. -
Session-scoped evidence reuse.
search/explain --session TAG(MCPsession) name one agent context. A snippet the session already received from byte-identical source comes back assnippet: null, reused: true; evidence the session received that has since changed is listed once undermemory.invalidated. The evidence hook runs after ranking, budgeting and pagination are final, so memory can never change which results are returned. Sessions are only ever named explicitly. -
Stale marking without a session. A result whose index text no longer matches the working tree carries
stale: true. Derived index text (config-key chunks, Markdown section summaries) is never flagged: a mismatch counts only when the file's current hash differs from the one the index was built from. -
memory.sqlite, a content-free ledger next to the index: hashes, paths, line numbers, token counts and timestamps; session tags stored hashed. Separate fromindex.sqliteso rebuilds andcleanleave it alone and ledger writes never queue behind an update. A newer schema is refused, a corrupt file is moved aside, lock contention degrades to no-memory output.memory gcandmemory clearare CLI-only maintenance, deliberately absent from the skill wrappers and MCP, likeclean.stats,doctor,index_statsandhealthcheckgain an additivememoryblock. Config:memory.enabled(true),memory.retention_days(14),memory.max_deliveries(50 000);CBX_MEMORY=0restores 1.10.0 output byte for byte. -
Sequential real-history benchmark (
tests/eval/memory_eval.py) with an oracle independent of memory's hashing, plus repository-lifecycle, security and stale-context test suites driven by real git: branch switches, detached HEAD, worktrees, dirty trees, renames, rebases, CRLF checkouts, and excluded content that must never reach the ledger. -
Upgrade test from a real 1.10.0 index. The index schema stays at version 3; an upgraded project keeps its index, a 1.x config loads with memory defaults, and
memory.sqliteattaches on the first--sessioncall. -
Skill: Find → Trace → Verify → Predict. The agent wrappers pass one session tag per conversation, run
verifybefore relying on evidence gathered earlier, and documentreused,staleandmemory.invalidatedinreferences/memory.md.verifyjoins the wrapper whitelists;memorydoes not. -
Public baseline benchmark.
tests/eval/run_baselines.pycompares the index with a disciplinedrg+ 80-line-window agent and with repo-map-style context on Flask, Gson and Fastify at pinned commits, using git-derived ground truth, one tokenizer on every side, and paired bootstrap / permutation significance. The logged run is committed undertests/eval/results/: pooled over 450 queries, hit@3 0.547 vs 0.304 and MRR 0.456 vs 0.263 (p < 0.001) at 3.8k vs 3.5k context tokens per query.scripts/gen_benchmark_chart.pyrenders the chart from the log. -
Reproducible demo.
examples/demo/run_demo.sh|.ps1run Find / Trace / Predict onpallets/flaskat a pinned commit;EXPECTED_OUTPUT.mdis the captured run andassets/demo-terminal.svgis rendered from it (scripts/gen_terminal_svg.py).docs/DEMO.mddocuments GIF/video recording. -
Release and CI gates.
scripts/check_versions.py(package, plugin manifest, lock tag, skill stamps and changelog must agree),scripts/check_links.py(relative Markdown links must resolve),scripts/release_notes.py(GitHub release body comes from the changelog section and an empty section fails the release), and apackageCI job that builds,twine checks and runs the clean-venv install smoke on every pull request. -
Contributor onboarding.
docs/DEVELOPMENT.md, rewrittenCONTRIBUTING.md, benchmark-report and language-support issue templates,SUPPORT.md, a labels manifest, anddocs/COMMUNITY_AUDIT.md/docs/COMMUNITY_LAUNCH.md.
-
Indexing gates are shared with later working-tree reads. Evidence validation reads files long after they were indexed, so the walker and the validator now go through one
PathGate; a parity test asserts the gate admits exactly the files a walk indexes. The resolved on-disk path is gated again, so a symlink or a differently-cased path on a case-insensitive filesystem cannot reach a file the walker would never have indexed. -
MCP
schema_versionstays 1: every payload change is an added field. No CLI command, flag or JSON field is removed or retyped. -
Read plan is bounded.
recommended_readsentries are capped atretrieval.max_read_lines(default 120) and carrytruncated: trueplusline_end_fullwhen capped. A symbol-aligned chunk can be a whole class; on the public baseline benchmark the uncapped read plan cost 6.8k tokens per query against 3.5k for grep, the capped one 3.8k, with identical ranking quality. Set the option to 0 for the previous behaviour. -
README, docs and examples show real output only. Fabricated tables (an
AuthService.tsexample with aScorecolumn, an inventeddoctortranscript, the mock-upassets/demo.png) are replaced by output captured on Flask. Duplicate pages (DATABASE_SCHEMA,RETRIEVAL_PIPELINE,docs/SECURITY) are redirect stubs;SCHEMA.mdnow matchesstorage/schema.sql. -
Benchmark headline. The "13× fewer tokens on a 55k LOC Java repo" figure is withdrawn: the repository is private and the accounting was asymmetric. The defensible claim is the public baseline run above.
-
SECURITY.mdpoints to GitHub private vulnerability reporting and states the supported line as 1.9.x.
codebase-index indexno longer crashes withValueError: ... is not in the subpath of ...when the repository contains a symlink that points outside the tree (for example Bazel'sbazel-out/bazel-binconvenience symlinks). Such entries are now skipped by the walker, matching the gate's existing "resolves outside the repository" exclusion (#27).- MCP server did not start on mcp 2.x. The SDK renamed
FastMCPtoMCPServerand removed the old import path, socodebase-index mcpreported "needs the optional extra" even with the extra installed, and the MCP tests skipped silently in CI because the skip guard wrapped our own module's import. The server now importsMCPServerfirst and falls back toFastMCPon 1.x (verified against mcp 1.29 and 2.1); the tests skip only when the SDK itself is missing. codebase-index mcp --root <repo>was rejected. Every client template used that form, but--rootwas only a global option (codebase-index --root <repo> mcp). The subcommand now accepts it as well.- Plugin wrappers refused four documented commands.
bin/cbxandbin/cbx.ps1whitelisted ten subcommands while the skill allowed fourteen;architecture,diff-impact,pathanddescribenow work from the plugin. A parity test pins the two lists together. - Benchmark leakage.
gen_queriesdocumented that changelog-style files were excluded from the evaluation corpus, but the harness never applied the list, and Flask-styleCHANGES.rstwas not covered. Both are fixed;CHANGES*,HISTORY*,NEWS*andRELEASE_NOTES*are refused as answers and excluded from the corpus.
1.10.0 - 2026-09-02
Never published on its own: tagged releases go from 1.9.0 straight to 2.0.0, which includes everything in this section.
Ranking release. 1.9.0's own diagnostics showed that a perfect reranker over the candidate pool it already generated would score MRR 0.902 against the 0.577 actually delivered — a ranking gap roughly three times larger than the remaining recall gap. 1.10.0 spends its entire budget on closing part of that gap, and adds the metrics that make the gap visible.
Measured over 420 queries across eight repositories (Python ×2, Java ×3, TypeScript/TSX ×2, PowerShell ×1) against a pinned 1.9.0: MRR +0.0188 (p=0.004), nDCG@10 +0.0304 (p<0.001), MAP +0.0213 (p=0.001), recall@10 +0.0627 (p<0.001, 37 wins / 0 losses), useful@budget +0.0292 (p=0.020), −19 tokens per query. The candidate pool is unchanged, so every gain is reranking. No corpus regressed. Under leave-one-repository-out the pooled gain is +0.0219 MRR with 7/8 folds improving and 0 regressing. Full tables in docs/BENCHMARKS.md.
- Query↔candidate name co-occurrence (
retrieval/features.py), the one new ranking signal. Every retriever scores each query term independently and RRF sums those independent verdicts, so nothing in the pipeline could distinguish a candidate that matched one query term well from one that matched three terms in a single name. On "graph resolution + traversal accessors" 1.9.0 rankedgraph/retrieval.py(one term) abovetest_graph_accessors_resolve_and_walk(three); on "greedy token budgeting with redaction" it rankedoutput/redact.pyaboveretrieval/budget.py. The signal credits query terms for occurring together in one name — file basename plus symbol, camel/snake split, directories excluded — and only for terms beyond the first, since the first match is already paid for by the retriever that surfaced the candidate. Cost isO(len(path) + len(symbol) + len(terms))per candidate: no corpus statistics, no posting-list scan, no model, no network. Ablatable viaRetrievalTuning(name_cooccurrence=False). - Oracle / headroom metrics in the eval harness (
oracle,cand_recall,eff). MRR alone cannot separate "retrieval never found it" from "the ranker buried it", and those two failures share no fix.oracleis the MRR a perfect reranker would achieve over the pool actually generated,eff = MRR / oracleis the fraction of achievable quality delivered (0.639 → 0.660 in this release). These also make ranking changes falsifiable in a new way:-name_cooccurrencemoves eight quality metrics while leavingoracleat ±0.0000, proving the gain is not disguised recall. search(..., explain=True)returns adiagnosticsblock with the pre-rerank candidate pool and the final order, each candidate carrying source, symbol, score and retriever agreement. This is what the oracle metrics are computed from, and what turns "the ranking is wrong" into a decomposable failure. Measured at −0.8ms p50 (inside noise); nothing is allocated when the flag is off.RetrievalTuning.v190()pins the previous release as the comparison column, so "better than what we shipped last" cannot drift as the default changes.run_eval.pynow reports 1.7.0, 1.9.0 and the current default side by side.
- Pages are packed with distinct files (
max_per_file3 → 1). The agent's unit of decision is "which file do I open", so a 10-result page spending three slots on three regions of one file offers seven choices, not ten. 1.9.0's page held 7.1 distinct files on average, and of the queries whose answer was in the pool but missing from the page, 45 of 57 had it past rank 10 — crowded out by repeat hits rather than better candidates. Monotone over 1–5, so this is a plateau boundary, not a fitted peak. Nothing is dropped: overflow hits keep their relative order at the tail. recall@10 +0.045, nDCG@10 +0.015, unchanged token cost. - Name co-occurrence is discounted for test and generated sources
(
name_cooccurrence_demoted_scale, 0.5). Test function names are descriptive sentences (test_compactor_output_is_redacted), so they harvest query-term co-occurrences real identifiers never do. The value was chosen by splitting the benchmark on whether its own ground truth is a test: across the 261 queries whose answer is not a test the gain is flat at +0.020 MRR for every scale, so the whole aggregate difference between 0.5 and 1.0 comes from the 159 test-answer queries — an artifact of mining ground truth from commits, which touch tests. 0.5 is the only setting that improves both partitions. - Benchmark corpora no longer index changelog files.
gen_queriesdocumentedCHANGELOG_EXCLUDESas applied to the corpus but the harness never wired it in. A git-derived query is a commit subject and a changelog entry paraphrases it verbatim while never being an accepted answer, so every affected query carried an unbeatable distractor that compressed all variants toward the same floor.
- Duplicate detection roughly halved. It was 42% of the query path on the Java
corpus. Two independent fixes: the operator scan in
normalize_code_tokensno longer runs up to 24str.startswithcalls per punctuation character (1.71× faster on 1600 real chunks, bit-identical token stream), and only the leading 2000 characters of a body now decide duplication — two chunks agreeing for 2000 characters are the same snippet. Recall, duplicate rate and useful-context are identical; MRR within −0.0003 (p=0.51).
- Non-ASCII identifiers can now earn name-level ranking credit. The query side
parses Unicode correctly, so
расчёт_налога.pyproduced matching query terms but its own name components were silently dropped, making the file unrankable by name. .skill_versionis pinned to LF in.gitattributes.sync_skill_copies.pywrites and byte-compares"<version>\n", but the file carried no EOL attribute, so withcore.autocrlf=truegit materialised CRLF andtest_real_repo_is_in_syncfailed on any fresh Windows clone — and again after anygit checkoutof that file.- Lint is clean repository-wide.
skill/scripts/was outside the CI lint scope (ruff check src tests) and had accumulated three violations.
Every pre-existing signal was re-examined rather than inherited. soft_lexical
(−0.161 MRR when off) and source_priors (−0.029) remain load-bearing;
file_agreement (−0.010, p=0.048) and dedup (−0.0016, p=0.006) keep their places.
query_expansionsurvives a deletion attempt, and the reason is a lesson. On the 420 git-derived queries the synonym vocabulary is worth nothing measurable (MRR −0.0022, p=0.40 when removed), and the flag's apparent benefit turned out to come from it also swapping the symbol retriever's tokenizer. It was removed — and then restored, because a commit subject is written by someone looking at the identifiers they just changed and so reuses the codebase's spelling, while a user asking a question does not. On the 36 hand-written natural-language queries removal cost −0.060 MRR. The git benchmark is structurally blind to morphology; both query families are needed to make this call.- The intent classifier is inert on commit-style queries but kept. It returns
keywordfor 419 of 420 git-derived queries, and removing the layer entirely is bit-identical on that benchmark. It fires on 30.6% of hand-written questions, where removing it costs MRR, and the retriever weights it selects are worth +0.058 MRR against uniform weights. Deleting it would optimise for the benchmark's phrasing. fuzzy_symbolsandgraph_sourceremain measurably inert on all eight corpora (0/1 and 0/0 query changes respectively); both stay as-is rather than accumulating new tuning.
Recorded so they are not re-attempted without a new hypothesis. A pairwise logistic
ranker fitted over 19 deterministic query↔candidate features selected exactly one
feature, and forward selection found no second feature clearing the noise floor, so
one explainable term ships instead of a model. Individually measured and rejected:
idf weighting of matched terms (pool-local and corpus-wide), substring matching,
prefix/stem-tolerant matching, ordered-subsequence matching, term proximity in the
chunk body, body-text coverage, zone-size normalisation, restricting the name zone to
the filename or symbol alone, and including the parent directory. exact_symbol was
found to have no discriminative power at all (AUC 0.500) but is left untouched, since
changing it is a separate experiment from adding a signal.
1.9.0 - 2026-09-02
- Objective ground truth from git history.
tests/eval/gen_queries.pymints a retrieval benchmark from any git repository by pairing a human-written commit subject with the files that commit actually changed. Unlike docstring-derived benchmarks the query text is not copied into the document being retrieved, so the set is leak-free by construction; merges, reverts, releases, version bumps, sweeping refactors, changelog-style answers and benchmark scaffolding are all filtered out. - Multi-corpus, multi-language evaluation.
run_eval.py --corpus REPO:QUERIESpools several repositories into one benchmark so ranking changes are validated outside this repository and outside Python. The 1.9.0 defaults were measured on 305 queries across Python, Java and TypeScript corpora. - Significance testing. Every non-baseline row now reports a paired bootstrap 95% confidence interval and a paired permutation p-value (both seeded, therefore reproducible). Query sets of this size have a noise floor of several MRR points; signals now ship on the strength of that test rather than the sign of a delta.
- Context-noise metrics. The report adds mean emitted snippet tokens, duplicate rate of returned results, and p99 latency alongside the existing IR metrics.
- Fusion now scores cross-retriever agreement. RRF fuses on
(path, line-bucket), so a symbol definition at line 40 and a lexical hit at line 120 in the same file fused as two unrelated candidates — two retrievers agreeing on a file produced two weak results instead of one strong one, and cross-source agreement never reached the score. Each candidate now also receives, atfile_agreement_weight(0.4), the RRF mass of every retriever that found its file at another locator, excluding retrievers already counted at that locator. Ablatable viaRetrievalTuning(file_agreement=False). - Documentation demotion deepened from -0.05 to -0.20. Prose describing a
feature matches a natural-language question more literally than the code
implementing it, so design notes and plans were displacing the modules they
describe. Because this only reorders prose relative to code, documentation-seeking
queries improved as well (category MRR 0.579 → 0.612). Generated/vendor paths move
to -0.25 so they remain the least-preferred role, and
MAX_ABS_PRIORnow pins the invariant that priors stay tiebreakers. - Fuzzy identifier matching is now a recall fallback. It runs only when the
precise symbol lookup named no symbol and returned fewer than
fuzzy_fallback_min(3) rows. It moved no ranking metric across 305 queries while accounting for ~20% of query latency; typo and acronym recall is unchanged because those are exactly the queries where the precise lookup comes up empty. - Candidate over-fetch is explicit.
candidate_pool_multiplier(default 2) replaces the implicit widening that happened whenever dedup or MMR was enabled. Making it explicit revealed that the quality previously credited to SimHash dedup was really the wider pool; dedup is retained for what it does measurably do, which is cutting the duplicate rate of returned snippets from ~1.6% to ~0%.
- Synonym matches were reported as exact symbol matches.
is_exactcame from SQL and was relative to whichever needle retrieved the row, so a synonym expansion ("config" for "configuration") marked an unrelated symbol as an exact match — worth a +0.20 rerank bonus and an unconditionalhighconfidence. Exactness is now judged against the terms the user actually typed. - Duplicate suppression was order-dependent on ties. Equal-scoring duplicates handed the slot to whichever copy arrived last, contradicting the documented "ties favor input order" and making the retained snippet depend on retriever emission order.
- Query latency roughly halves: p50 78.6 ms → 51.2 ms, p95 193.2 ms → 94.9 ms, p99 277.1 ms → 152.2 ms on the pooled three-repository benchmark, from the fuzzy fallback plus a SimHash fingerprint that folds repeated tokens by multiplicity and caches token digests. Fingerprint output is bit-for-bit unchanged.
- Mean emitted snippet tokens fall 1183 → 1091 per query.
Pooled over 305 queries (Python, Java, TypeScript), v1.8.0 → 1.9.0:
| Metric | v1.8.0 | 1.9.0 | Δ | p |
|---|---|---|---|---|
| MRR | 0.564 | 0.591 | +0.027 | <0.001 |
| MAP | 0.433 | 0.461 | +0.028 | <0.001 |
| nDCG@10 | 0.503 | 0.527 | +0.024 | <0.001 |
| recall@5 | 0.529 | 0.560 | +0.031 | <0.001 |
| P@5 | 0.182 | 0.192 | +0.009 | 0.001 |
| hit@3 | 0.649 | 0.666 | +0.016 | 0.124 |
1.8.0 - 2026-09-02
- Reproducible retrieval evaluation. Added a self-repository ground-truth query suite with Recall@K, MRR, nDCG, hit rate, precision, MAP, useful-context, latency percentiles, and one-signal ablations.
- Hybrid retrieval quality. Natural-language lexical queries now use safe, down-weighted identifier/synonym expansion and soft term coverage; exact symbol lookup preserves framing-word tolerance and bounded fuzzy matching.
- Packaging compatibility. Cap the build backend below the Metadata 2.5 default until the release validation toolchain supports that metadata version.
- Ranking defaults are evidence-driven. Implementation/test/documentation source priors are calibrated from the benchmark. Graph propagation is bounded and intent-directed; graph and MMR signals remain opt-in because ablations reduced direct retrieval quality on the reproducible corpus.
1.7.0 - 2026-07-29
- Diff-aware blast-radius analysis.
codebase-index diff-impactand the MCPimpact_of_difftool aggregate graph impact for tracked working-tree changes relative to a verified Git commit, with a changed-file safety cap, unresolved file reporting, freshness, graph coverage, edge confidence, and exclusion of the tool's own derived cache. - Progressive skill references. The installed agent skill now keeps its always-loaded evidence protocol compact and moves detailed commands and response handling into on-demand reference files.
- Reproducible product identity. Added a graph-route mark and redesigned README/social assets around Find / Trace / Predict.
- Reworked the README, roadmap, product brief, package metadata, and plugin copy around the evidence-first product promise.
- Expanded the safe
cbxwrappers to allow the shipped read-onlyarchitecture,path,describe, anddiff-impactcommands. - Replaced broad skill shell permissions with an explicit command allowlist so
clean,init, andwatchcannot bypass the safe wrapper. - Hardened the release workflow with least-privilege defaults and immutable commit pins for every GitHub Action that receives release or PyPI credentials.
1.6.0 - 2026-06-24
- Snippet skeletonization & content-aware rendering.
search/explainsnippets are now focus skeletons — import/signature/class lines and the query-matching line are kept while function bodies collapse to a... N lines elided (read A-B)marker — so more ranked results fit the same token budget. Content-aware (code via tree-sitter, markdown headings, structured-config keys), reversible viarecommended_reads, and safe (raw fallback on any parse miss or non-win). Newskeletonized/elided_linesresult fields; newretrieval.compact_snippets/retrieval.compact_min_reductionconfig knobs (no reindex); disable per-call with--raw(CLI) orraw: true(MCPsearch_code/explain_code).
1.5.0 - 2026-06-24
- HTML graph is now legible at a glance: nodes are coloured by module
(community), sized by connectivity (god nodes are biggest), and edges are styled
by confidence — solid
extracted, dashedinferred, red-dottedambiguous— with a legend. Community/degree are computed on the displayed subgraph. codebase-index graph --format graphml|dot|neo4jexports the same enriched graph for external tools: GraphML (Gephi / yEd / NetworkX), DOT (Graphviz, edge style = confidence), and Cypher (Neo4j / FalkorDB). All pure-stdlib, zero new dependencies.--format html(default) is unchanged.
codebase-index path <A> <B>— shortest undirected dependency/call path between two symbols or files ("how is X connected to Y"). Renders the node chain annotated with each link's edge type and confidence;inferred/ambiguoushops are marked, so a path is only as trustworthy as its weakest edge.codebase-index describe <symbol>— a node card: definition(s), direct callers and callees (with confidence), in/out degree, the symbol's module, and its god-node rank if it has one. The graphifyexplain Symbolidea, nameddescribeso it doesn't collide with the existing how-it-worksexplain.path_betweenanddescribe_symbolMCP tools expose both to agents.
codebase-index architectureprints a high-level map of the codebase from the analytics cached at index time: detected modules (with auto-derived labels), god nodes (most-connected symbols/files), surprising cross-module connections, and suggested starting questions.--jsonfor the structured payload.architecture_overviewMCP tool exposes the same map to MCP clients, so an agent can orient itself before diving into specifics. Reportsavailable: false(rather than crashing) on an index built before the analytics existed; a reindex fixes it.
- Edge confidence audit trail. Every graph edge now carries a
confidence:extracted(exact — a same-file symbol or repo-unique name),inferred(a heuristic resolved it, e.g. an import path-suffix match), orambiguous(a named target we could not pin to a unique node).refsandimpactsurface it so an empty or short answer overambiguous/inferrededges reads as inconclusive, not as proof. Confidence is derived from how an edge resolved — never guessed by an LLM; the index stays fully local. BumpsSCHEMA_VERSION2 → 3. Older indexes stay readable;index/updatedetect the mismatch and rebuild. - Architecture analytics (
graph/analysis.py), zero new dependencies. A pure, deterministic pass over the resolved edge graph computes communities (greedy modularity / Louvain local-move — does not collapse cliques joined by one bridge), god nodes (most-connected symbols/files), surprising connections (edges bridging weakly-linked communities), auto-labelled modules, and suggested questions. The summary is cached inmeta['graph_analysis']at build time for instant reads. (Surfaced via thearchitecturecommand and HTML export in following changes.)
- RRF fusion rescaled and re-keyed. Fused scores were ~
w/k(≈0.017), an order of magnitude below the reranker's bounded bonuses, so rerank silently became the primary ranker. RRF is now scaled byk(a pure monotonic rescale — order is unchanged) so fused scores and rerank bonuses share an O(1) scale. Fusion also merges on a coarse(path, line-bucket)key instead of an exact(path, start, end)one: different retrievers report different ranges for the same place, so the exact key almost never coincided and cross-source agreement never fired.agreeing_sourcesis now counted at file granularity. - Confidence uses a scale-invariant relative gap instead of absolute thresholds.
- Per-file diversification: at most 3 hits per file stay on the page; the rest are pushed to the tail (nothing is dropped). Combined with bucketing this removes the "same small file returned six times at different line slivers" noise.
- FTS recall on natural-language queries: stopwords (
how,does,the, …) are dropped before building the FTSMATCH, so a query like "how does auth work" no longer AND-s in filler that code chunks never contain. - Symbol names are FTS-indexed.
chunksgained a denormalizedsymbol_namescolumn (mirrored verbatim by the FTS sync triggers, so external-content delete/ update stays consistent) — a query matching a symbol's name now hits even when the body text doesn't repeat it. BumpsSCHEMA_VERSION1 → 2. Older indexes are still readable;index/updatedetect the mismatch and rebuild from scratch. - Centrality fallback for ambiguous names: symbols whose name isn't globally
unique never get a resolved
in_degree; they now receive a damped, half-capped bonus from a name-reference count so common names (run,handle, …) aren't flatly zeroed. Precisein_degree, where present, still takes precedence. - Test-file demotion is word-boundary aware:
contest/,latest.py,testimonials.tsxare no longer mistaken for test files. - Language-aware import resolution:
import './base'from a.tsfile resolves tobase.tsrather than a same-namedbase.pyearlier in the fallback order. - Freshness is content-aware: a bare
touch(mtime change, identical bytes) is a no-op forupdate, so it no longer reports the index as stale — freshness now mirrors the sha-based incremental decision.
- Dead legacy lexical-search path in
retrieval/searchers.py(fts_response,fts_search, the secondCandidatedataclass and_confidence/_fallbacks/_trim) — the live path goes throughpipeline.search→fts_candidates.
1.4.0 - 2026-06-14
cleanis now implemented (it was a documented-but-stubbed_todosince M0).codebase-index cleanresets the index database (index.sqlite+ WAL/SHM sidecars);codebase-index clean --allwipes the whole per-project cache directory. It prompts before deleting (skip with--yes), supports--json, and never touches the installed skill. Locked in bytests/test_clean_cli.py.docs/PRODUCT_UPGRADE_PLAN.md: positioning, target users, competitor matrix, differentiators, current weaknesses, a ranked roadmap, and documentation / benchmark / distribution / technical task lists.docs/RELEASE_CHECKLIST.md: a repeatable release checklist (version sync, tests, benchmarks, doctor, install/plugin/MCP smoke, changelog) with signed checksums + SBOM tracked as future hardening.- MCP contract hardening (M11.5): every MCP tool payload — success and the
no-index/error path — is now wrapped in a stable envelope (
schema_version: 1,tool: ). Golden snapshots lock every tool's output (tests/golden/mcp_*.jsonviatests/test_mcp_golden.py), and the contract values are asserted explicitly so a golden can't freeze a wrong version. Closes the long-standingdocs/MCP.mdfollow-ups and makes theschema_versionclaim indocs/ARCHITECTURE.md§8 true. - Config / IaC language labeling: Dockerfile, Containerfile,
*.tf/*.tfvars(terraform),*.hcl,*.ini/*.cfg/*.conf/*.properties(ini), and Makefiles now get a real language label. These files were already FTS-indexed as unknown text; labeling surfaces infra files instatsand lets agents scope searches to config. They stay on the line/FTS floor (no tree-sitter spec). - Typed framework edges — design doc
(
docs/superpowers/specs/2026-06-14-typed-framework-edges-design.md): the documented-first deliverable for the M13 code-intelligence graph (route→handler→service→model, test→impl, config→consumer, …) with a schema, confidence/provenance model, resolver architecture, and a benchmark gate. - "Trust model in 60 seconds" callout, identical in
README.mdanddocs/SECURITY.md. tests/benchmark_public_RESULTS.md: a logged run of the public benchmark suite (Recall@k / MRR / nDCG / token economy / freshness / graph tasks) with the raw JSON and honest scope notes, per thedocs/BENCHMARKS.mdno-overclaim rule.
-
Reranker: dampened the god-class
in_degreetiebreak (retrieval/rerank.py). The graph-centrality bonus is now logarithmic with a lower cap instead of linear (which saturated by in_degree 10, giving 100-caller "god classes" the full bonus and floating them above genuinely relevant low-degree matches on stray-term ties). Validated as no-regression on the public benchmark (Recall@k / MRR / nDCG unchanged) with a targeted regression test; the real-repo gain on the honest Java misses is tracked under M12.5. CLI/MCPsearchgoldens regenerated accordingly. -
docs/ROADMAP.md: M10 MCP bridge marked shipped (was "planned"); reconciled the technical-vs-product milestone numbering instead of claiming one is canonical. -
README: added "Who Is It For?" and a "How Is This Different?" section that answers why-not-grep / Cursor / Aider repo-map / Sourcegraph / Codebase-Memory MCP on the first screen, plus a proven-today-vs-roadmap table.
-
docs/COMPARISON.md: explicit rows and "choose them when / choose us when" guidance for Continue, Sourcegraph/Cody/Amp, and Codebase-Memory MCP. -
docs/BENCHMARKS.md: a status table separating proven / toy / honest surfaces, an explicit "claims that should NOT be made yet" list, and a TODO-friendly benchmark task checklist with a no-overclaim procedure.
- MCP server failed to import on
mcp>=1.27+pydantic>=2.10: newer FastMCP auto-built a structured-output schema from each tool's-> strreturn annotation and raisedPydanticUserErrorat import time, breaking the server and its test suite. Tools now register as unstructured (structured_output=Falsewhere the kwarg exists; oldermcpis detected and unaffected), preserving the existing text-content wire contract. docs/FAQ.md: removed a dangling/duplicated sentence in "Is it production-ready?" and documented the realclean/clean --allbehavior.
1.3.0 - 2026-06-09
- Content-addressed embedding cache: a new
vec_cachetable (keyed by(model, content_sha)) persists chunk embeddings across rebuilds. Because chunk ids churn on every full rebuild, the embedding pass now hashes chunk content and only calls the (potentially slow or paid) backend for text never embedded under the active model — unchanged content reuses its cached vector for free. - Shared CLI/MCP service layer (
codebase_index/service.py): both surfaces now resolve the index path, run search sessions, and build stats payloads through the same code, so they cannot drift. Two real drifts were closed: MCPsearch_code/explain_codenow blend in vector results when embeddings are enabled (previously the vector channel was CLI-only), and MCPindex_statsnow reports the per-languagegraph: full|partialtier the skill keys on. - Repo-wide graph tier in diagnostics:
statsnow tags each tree-sitter language withgraph: full|partial, anddoctoradds agraph_coveragefinding listing Tier-B languages present in the index. Surfaces upfront which languages have partialrefs/impact(symbols but no import/inheritance edges) instead of only signaling per-query. - Graph coverage signal:
refsandimpactnow report acoverageblock (partial,languages,reason). Import/inheritance edges are only extracted for the hand-tuned (Tier-A) languages, so a symbol or file in a Tier-B language (generic tree-sitter walk, e.g. Lua) can produce an empty/short result that is inconclusive rather than authoritative.coverage.partialflags this so agents fall back to Grep instead of reading "no references" as proof. Markdown output prints a matching warning; the skill documents the field. - Skill-copy sync tooling:
scripts/sync_skill_copies.pyregenerates every committed copy of the skill (.claude/,.codex/,.opencode/,skills/, sharedskill/files) plus all version stamps from the canonicalsrc/codebase_index/skill_template/; CI fails when copies drift (--check). The package version now lives in one place (src/codebase_index/__init__.py) via hatch dynamic versioning. CBX_NO_SKILL_AUTO_UPDATE=1disables the silent skill auto-update — used by the test suite, useful for CI and scripted environments.
- Graph build is batched: edge resolution now runs one query for globally-unique symbol names
and one pass over file paths (in-memory suffix map) instead of per-edge lookups and up to ~20
full-table
LIKEscans per import edge — 7–28× faster on a small repo with identical results, and the gap grows with repository size. Vector blobs are written with a single batchedexecutemany; a newedges(file_id)index removes full-table scans from incremental updates and file-deletion cascades. - Silent failure paths now report to stderr: the ProcessPool→sequential parsing fallback and skill
auto-update failures were previously invisible; vector helpers only swallow
sqlite3.OperationalError(missing vec tables) instead of every exception. - The embedding pass reports cache misses (vectors actually computed) as its "embedded" count.
prune_orphan_vectorsnow deletes stalevec_chunksrows in a single batchedexecutemany.- Skill: documented the
--mode vectorsemantic-search path, theintent/mode/paginationresponse fields, and clarified thatgraph --openrenders an HTML view for a human (useimpact/refsfor agent-readable dependency answers). - Skill: narrowed the skill's
allowed-toolsfromBash(python *)/Bash(python3 *)toBash(python -m codebase_index *)/Bash(python3 -m codebase_index *), so the skill can no longer run arbitrary Python.
searchnow exposes--offset, so the pagination contract is reachable from the CLI/skill. The retrieval pipeline and MCP already supported paging, but the CLI command never surfaced the flag — every call silently returned page one and the advertisedpagination.next_offsetwas a dead end. Markdown output now also notes when more results are available.--offsetrejects negative values.explainnow honors the index freshness contract: it passesroot/configinto the retrieval pipeline, soindex.stale/files_changed_since_buildreflect reality instead of a hardcoded "fresh" block. Previously the skill's freshness check silently never triggered for "how does X work" questions.explainalso blends in vector results when embeddings are enabled, matchingsearch --mode hybrid.- The
cbxwrapper whitelist (skill + pluginbin/) now includesdoctor, which the skill's fallback diagnostics already invoke; previouslycbx doctorwas refused. - The test suite is green on Windows again (
bootstrappath comparison) and no longer rewrites the committed.skill_versionstamps as a side effect of running the CLI inside the checkout. docs/ARCHITECTURE.mdno longer shows two contradictory repository layouts or claimsgraph/is a stub.
- Synced the version to
1.2.2across the package, plugin manifest, and lockfile. - Documentation cleanup: removed stale prompt files and screenshots, refreshed the README.
1.2.1 - 2026-06-05
- Skill auto-update: skills installed via
initnow silently self-update whenever the package version changes. On every CLI invocation the main callback compares the installed.skill_versionstamp against the running package and re-materializes the template, saving a backup first. skill-updatecommand:codebase-index skill-update [--target] [--force] [--no-backup] [--json]for manual skill updates with optional dry-run and JSON output.skill-rollbackcommand:codebase-index skill-rollback [--target] [--json]restores the last backed-up version of installed skill(s).scaffold.materialize_skill()now writes a.skill_versionstamp alongside copied template files so freshness is detectable without an extra network call.
1.2.0 - 2026-06-05
- Interactive graph export via
codebase-index graph [target], producing a local HTML graph of indexed files, symbols, and resolved edges, with optional--openbrowser launch. - Project skill installers now advertise and whitelist the
graphcommand for Claude, Codex, and OpenCode skill resources.
search,symbol,refs,impact, andexplainnow auto-build the local index when it is missing instead of failing with a manual "run index first" step.- Natural-language kind words such as
method,function,class,interface,enum, andtypenow constrain the symbol retriever insidesearch. - Skill wrappers prefer the importable local
python -m codebase_indexmodule before falling back to a potentially stalecodebase-indexexecutable onPATH.
stats --jsonanddoctor --jsonnow work as subcommand flags, matching the documented skill examples and the existing global--jsonbehavior.init --no-hooksis accepted as the explicit counterpart to--with-hooks, preserving the default no-hook install while keeping the CLI option pair discoverable.
1.1.0 - 2026-06-02
- MCP server (
codebase-index mcp): exposes the retrieval layer as MCP tools —search_code,find_symbol,find_refs,impact_of,explain_code, andindex_stats— so MCP-capable editors (Cursor, Claude Desktop, VS Code, Zed, Windsurf) can query the index directly. codebase-index-mcpstandalone entry point for use as a bare MCP server binary.- Multi-client
init:--targetnow accepts five MCP clients in addition to the three skill targets. Each writes the correct JSON config format and merges without overwriting other servers already present:cursor->.cursor/mcp.jsonwindsurf->.windsurf/mcp.jsonvscode->.vscode/mcp.json(withtype: stdio)zed->.zed/settings.json(withcontext_servers)claude-desktop-> platform-specificclaude_desktop_config.json
detect_mcp_targets()auto-detects installed MCP clients during--target auto.- New optional dependency group
mcp(pip install codebase-index[mcp]). tests/benchmark_public.py, a reproducible multi-language public benchmark suite with Recall@1/3/5, MRR, nDCG, answer-correctness proxy, token economy, language breakdown, freshness latency, graph tasks, and scale counters.docs/MCP.mdanddocs/BENCHMARKS.mdfor first-class MCP setup and benchmark usage.
recommended_readswas empty for queries where all results had short symbol-signature snippets (token_est < 40). Added a minimum useful-token threshold so snippets below it are still shown as previews and the result is also added torecommended_reads.
- Aligned README, FAQ, architecture, language support, comparison, installation, and roadmap docs
with the current
1.1.0implementation. - Replaced toy benchmark positioning with the honest benchmark summary and public benchmark suite.
- Corrected Aider repo-map comparison language to acknowledge graph-ranked, token-budgeted maps.
- Distribution is GitHub-only for now: docs and
requirements.lockinstall from the GitHub release tarball pinned tov1.1.0; PyPI/uvx/Homebrew remain distribution-hardening roadmap items.
1.0.2 - 2026-05-29
- Added
codebase-index init --target claude|codex|opencode|auto|all, with an interactive Rich target picker for terminal use. - Added project scaffolding for Codex CLI (
AGENTS.md+ resources) and OpenCode (command, agent, and resources), while preserving the Claude Code skill path.
- Refreshed README positioning and SEO structure around local codebase indexing for AI coding agents, including Claude Code, Codex CLI, and OpenCode.
- Updated quickstart and installation docs for multi-CLI initialization.
1.0.1 - 2026-05-29
- Pinned
tree-sitterandtree-sitter-language-packin package metadata and the plugin bootstrap lock so CI and local installs use the same grammars. - Regenerated CLI golden snapshots against the pinned grammar set.
1.0.0 - 2026-05-29
- Multi-language tree-sitter symbol extraction. Previously a repo of 303 Java files produced
0 symbols, silently disabling
symbol/refs/impact. Java now yields 3,543 symbols; Go/Rust/C/C++/C#/Ruby/PHP/Kotlin plus a Tier-B generic path are covered.
- Symbol-aware retrieval ranking: candidates are scored by how many query terms their camelCase/underscore-split name covers, so multi-word concepts land on multi-word symbols. recall@3 against objective ground truth improved 20% → 70% (vs 40% for a disciplined grep agent) while using ~13× fewer tokens to answer.
- Parse guardrails with
parse_failed/treesitter_zero_symbolscounters anddoctorreporting to lock symbol extraction against silent regression. - Multi-CLI installer for Claude Code / Codex / OpenCode.
- Honest benchmark harness (
tests/benchmark_honest.py) comparing the index against a no-skill grep agent on a real repository.
0.1.0 - 2026-05-29
- Local-first codebase index exposed as a Claude Code Skill +
codebase-indexCLI. index/update: discovery with layered ignore rules, secret/binary/size gates, and incremental re-index (M1, M8).search: FTS5 lexical + hybrid retrieval with RRF fusion, intent detection, token budgeting, confidence scoring, and fallback suggestions (M2, M4).symbol/refs: tree-sitter symbol extraction and reference lookup across supported languages with line-based fallback (M3).impact: dependency/call-graph blast-radius analysis (M5).- Optional, opt-in local embeddings /
sqlite-vecvector backend, gated behindembeddings.enabledand SECURITY.md rules (M6). init: materializes the bundled skill template, resolvedconfig.json, and.gitignorerules; end-to-end freshness contract so the skill triggersupdate/index(M7).- Hooks example +
watchmode for keeping the index fresh without blocking the edit loop (M8). doctor,stats,cleandiagnostics/maintenance commands.