Releases: sina-parsania/CodeGraph
Release list
v1.38.0
Full Changelog: v1.37.1...v1.38.0
v1.37.1 — rerank reaches the MCP
1.37.1 — rerank reaches the MCP
search (MCP) gains an optional rerank parameter — same local-LLM
reordering the CLI's --rerank had (config rerank = true /
CODEGRAPH_RERANK=1 for the CLI default). The implementation moved to
codegraph-llm so both surfaces share one function; the LLM call runs on
spawn_blocking (the embedder-probe wedge class stays impossible), and it
degrades to the original order when no local model answers.
v1.37.0 — lean MCP payloads, semantic that can't dead-end, MCP↔CLI parity
1.37.0 — lean MCP payloads, semantic that can't dead-end, MCP↔CLI parity
Driven by the third field report (v1.36 head-to-head vs ripgrep on a 5-project
monorepo — structural queries won; three defects found, all fixed).
- MCP
routeswas unusable by its own client (232 KB single-line JSON,
rejected by Claude Code): each route serialized the FULL graph node.
Now: lean rows ({method, path, handler, file, line}) +limit/offset
pagination +path_prefix/methodfilters; 560 routes ≈ 6 KB. Same lean
sweep applied to every list-shaped MCP response that still leaked full
nodes:implementers,dead_code, pinnedcallers. One sharedlean
serializer; full node detail stays behindget_node(id). semantic_searchcan no longer dead-end: with no embedder it degrades
to lexical search and says so ("degraded": "lexical fallback — no embedder…") instead of hard-failing a tool it advertised.statsreports
embedder_availableup front so agents route around it.install.shnow
builds withlocal-embed(bundled bge-small) by default — and
indexstoreon macOS — so the stock install answers semantic queries out
of the box.CODEGRAPH_NO_EMBEDDER=1forces lexical-only (also the
deterministic test hook).- MCP↔CLI naming parity:
semantic-search/semantic_search→
semantic,blast-radius/blast_radius→impact,
trace-path/trace_path→trace,graph-query/graph_query→
cypher,dead_code→dead-code(joins the earlierstats→status).
Agents translate MCP names to CLI constantly; every advertised name now
resolves. - Regression tests: routes payload cap (<25 KB on 300 routes) + pagination +
filters, alias resolution for every MCP name, forced no-embedder
degradation withembedder_available:falsein stats.
v1.36.0 — speed where it's visible + every fix is a release gate
1.36.0 — speed where it's visible + every fix is now a release gate
Driven by the second field report ("we should be 10–20× faster than grep,
we're tied"): the tie was the freshness probe, not the answer.
- Probe parallelized: the two root
git ls-filesspawn concurrently,
nested-repo enumerations fan out on rayon, and the 8k-file stat sweep is
parallel. Default-path query on an 8.7k-file monorepo: 0.21 s → 0.10 s —
vs rg's 0.21 s without any freshness guarantee. Warm path (--no-autoheal
/ MCP between heals): 0.00–0.02 s — 10–100× rg. The honest framing: rg
pays full scan per query; codegraph pays a freshness proof once and ~0
after. - Constant-path routes recovered:
@Delete(SOME_CONST)endpoints are
real — they now emit with the constant as a «symbolic» segment +
path_unresolved: trueinstead of being dropped (66 real endpoints on the
field monorepo; route accounting now closes exactly: 524 decorators =
458 literal/bare + 66 symbolic). --indexstoreearly-return bug: with zero file changes the flag never
reached the merge path — a re-merge no-op'ed. Fixed; field monorepo now
carries a 46,110-edge Swift compiler oracle andcodegraph auditmeasures
96.1% precision (SelfThisMember 100%, SameFileUnique 96.6%,
FieldTypeMember 83.3% on a small sample) — served via MCPstats.codegraph statsaliasesstatus(agents guess the MCP tool name).- Doc noise capped: identifier search keeps docs to ≤5 rows (one per
file) when code answered the query. - Release gate:
scripts/release-qa.shruns clippy-clean, the full
suite, crash-recovery + determinism e2e, and the eval receipts — every
field-reported bug fixed in 1.34–1.36 has a pinned regression test
(enumeration classes, flock exclusion, MCP empty-graph/dead-root/
generation-bump,--filesmachine contract, NestJS route shapes, DI
narrowing, search ranking).
v1.35.0 — field-test fixes: the MCP can't lie, NestJS answers
1.35.0 — field-test fixes: the MCP can't lie, NestJS answers
Driven by a promom field-test report (11.6k files, TS+Swift+Python) that
found two red bugs and three weaknesses. All fixed, all regression-tested.
- MCP empty-graph bug (red):
codegraph initwired the USER-GLOBAL MCP
registration with one repo's absolute--path— the last-initialized repo
won globally, and when that repo moved every project got a confidently
empty graph ("no callers", nodes: 0) while the CLI was healthy. Fixed
three-deep: registration is now cwd-following (no--path); a dead root
refuses to serve at startup; and an EMPTY graph refuses to answer at all —
every tool returns a diagnosis instead of clean emptiness (statsstays
reachable and reportsEMPTY_GRAPH). Regression suite proves a generation
bump under a running server is served fresh on the next call. - NestJS routes (red): decorator routes (
@Get(':id'), bare@Get())
never matched the leading-/requirement, and@Controller('prefix')was
never joined (including the@Controller(…)\nexport classshape where the
decorator hangs off the export_statement). e2e/spec files polluted the
answer with test traffic. Object-form@Controller({ path: 'x', … })is
read too, and a constant path (@Delete(SOME_CONST)) is skipped instead of
fabricating "/" — a made-up path is a wrong answer, not a route. promom:
120 noisy routes → 494 fully-prefixed real ones, zero spec noise, zero
fabricated paths. - TS DI caller resolution (was ~0%):
this.profileService.getUserProfile()
withconstructor(private readonly profileService: ProfileService)never
resolved when the class NAME exists in two apps of a monorepo (global
unique-or-drop). The caller file's import now disambiguates the type —
same evidence class as ImportNarrowed, unique-or-drop preserved. promom:
0/23 → fully resolved caller list (+465 edges). Swift resolution untouched
(its tests pin the behavior). - search noise: identifier search ranks ANY code symbol above Document
fragments, and collapses doc hits to one row per file. - stale audit:
statsno longer serves an outdated audit under
measured_precision— stale numbers move tostale_audit_not_current. - index lock → OS flock (std
File::try_lock, Rust 1.89 — zero deps):
the kernel releases the lock the instant the owner dies, so the PID-stamp /
dead-owner-steal machinery from 1.34 is deleted outright. Measured: kill -9
mid-index → next query self-heals and answers in 69 ms. - callers --files: unpinned name-level questions return the UNION across
same-name definitions again (dominant-definition narrowing was a measured
recall loss), definition files stay as labeled evidence, human notes moved
to stderr. Eval receipts (SCIP ground truth, 68 questions): P 0.66 /
R 0.98 / answer-rate 100% / 319 B vs grep 0.63 / 0.94 / 100% / 2,701 B —
ahead of grep on every measured dimension. (The 1.33 "R 0.87" receipt was
stale: the harness predated the v1.33--filesformat.)
v1.34.0 — zero-false-negative enumeration + ops hardening
1.34.0 — zero-false-negative enumeration + ops hardening
Enumeration moved from directory-walking to git ls-files (tracked +
untracked-unignored), with the walker as fallback. This is not just faster —
it is MORE COMPLETE: git knows which files are tracked, and tracked beats
gitignore. Measured on a live monorepo: 26 real docs (SRS, bug
investigations) lived under a docs/* ignore pattern but were committed
anyway — the walker dropped them forever, the git tier indexes them.
Staleness probe on a 3.6k-file repo: no-op index 0.19s.
- Nested plain repos (monorepo of independent .git checkouts, no
.gitmodules): ls-files silently skips their subtrees — untracked dirs
carrying a.gitare enumerated recursively; any level that can't take
the git path falls the whole enumeration back to the walker. .codegraphignoreapplied on the git listing (root-level, cascades
into nested repos) — no more walker fallback just because the file exists.
Submodules (.gitmodules) still force the walker: dropping submodule
symbols would be a false negative.- Walker parity:
hidden(false)— dot-dirs carry real content
(.claude/agent docs,.github/), and git enumerates them; junk
dot-dirs stay excluded via EXCLUDE_DIRS. - Engine-version gate: the rebuild stamp now includes the release
version alongside PARSER_VERSION — every upgraded binary rebuilds
automatically; a resolver change nobody remembered to stamp can no longer
serve a stale-engine graph. - Cross-process index lock: MCP server, its watcher thread, and parallel
CLI runs serialize on a PID-stamped lock file; dead owners are stolen
instantly (kill -0liveness + stale-age window), so akill -9mid-index
never bricks the next query — verified live: the orphaned lock was stolen
by the very next search, which self-healed and answered. - Binary sniff: a NUL byte in the first 8KB (generated blobs with code
extensions — valid UTF-8) keeps the file manifested but contributes zero
symbols. - Ambiguous-candidate ranking (MCP callers): strongest resolved evidence
first, cross-language ties broken toward the language family the textual
evidence lives in. Ranking only — never changes which edges exist. CODEGRAPH_MCP_CONCISE=1: drops per-response coaching fields
(_hints, explainer notes); coverage/_fallback/truncation notes always
stay. Measured 265→166 B on a callers answer.
Receipts: branch-switch storm (150 files) heals partially in 0.12s with
exactly reversible node counts; git-vs-walker parity proven on an identical
tree (1200 == 1200 nodes); verify-determinism byte-identical on a 3.6k-file
repo; zero phantom edges on 71k- and 25k-edge live graphs.