[1.5.0] - 2026-07-21
⚡ The Rust engine release — with near-instant sync
This release rebuilds CodeGraph's parsing engine as a native Rust kernel, overhauls the resolution pipeline around it, and makes the live graph effectively instant: a save now reaches the graph in well under a second, even on a 27,000-file repository. It is the largest performance upgrade in the project's history — and every graph is verified byte-for-byte identical to the previous engine.
- Native Rust parsing for 20 languages — TypeScript, JavaScript (+TSX/JSX), Java, Python, Go, C, C++, Rust, C#, Ruby, PHP, Swift, Kotlin, Scala, Dart, R, Lua, and Luau now parse in a compiled Rust kernel (Metal and CUDA ride the C++ path). Platforms without a prebuilt binary, and individual files with syntax errors, fall back to the previous engine automatically — same graph either way, proven on repositories from small libraries to the Linux kernel.
- Adaptive to your machine — CodeGraph sizes its parse workers, resolver pool, and caches from what the system actually has: real core counts (container/cgroup-aware, not the host's), honest available memory on macOS and Linux, and measured per-project resolution cost. A big workstation gets the full parallel pipeline; a 2-core VPS gets a pipeline tuned to finish reliably instead of running out of memory — the Linux kernel (70k files) indexes to completion on a 2-core, 6GB machine in under 12 minutes — down from 26 at the start of this cycle.
- Resolution is dramatically faster across the board — adaptive parallel resolution, smarter method-candidate lookup, and memoized supertype/conformance walking. The Swift compiler repository (27k files, Swift + C++) went from over 3 minutes to about 100 seconds within this release cycle; Rust, Lua, and Java-family projects all see double-digit improvements.
- Sync is now near-instant — a save reaches the graph in well under a second, even at compiler scale. The always-on watcher fires after a 300ms quiet window for lone saves (bursts of edits still coalesce), and hands the exact changed paths to sync instead of re-scanning the whole tree — measured save-to-fresh-graph work of ~0.3s on a 4,400-file Java project and ~0.4s on the 27,000-file Swift compiler repository, byte-identical to a full reconciliation.
Full details in the entries below.
New Features
- Indexing TypeScript, TSX, JavaScript, JSX, Java, Python, Go, C, C++, Rust, C#, Ruby, PHP, Swift, Kotlin, Scala, Dart, R, Lua, and Luau projects is faster: parsing and symbol extraction now run in a native engine when a prebuilt binary is available for your platform (release bundles include one), producing exactly the same graph — verified byte-for-byte against the previous engine on real repositories, from small libraries up to vscode-, dubbo-, django-, git-, protobuf-, tokio-, rust-analyzer-, jellyfin-, rails-, symfony-, swift-nio-, kotlinx.coroutines-, ggplot2-, Kong-, Scala-3-compiler-, and Flutter-scale codebases (Lombok-generated members, C function-pointer tables, and Unreal-Engine-style macro-heavy headers included; CUDA and Metal sources ride the C++ path). The speedup is largest on resource-constrained machines like CI runners. No setup needed: platforms without the native binary, and individual files with syntax errors, automatically use the previous engine, and
CODEGRAPH_KERNEL=0turns the native path off entirely. - Reference resolution now runs in parallel on large projects. When a project has enough pending references to make it worthwhile (roughly 150k+, typical for big Java/Kotlin/Spring codebases), resolution fans out across worker threads while results are applied in the exact order the single-threaded path would have used — the graph comes out byte-for-byte identical, about twice as fast end-to-end on a 4,000-file Java project in our testing. Small projects keep the single-threaded path automatically (the fan-out costs more than it saves there). Set
CODEGRAPH_NO_PARALLEL_RESOLVE=1to disable, orCODEGRAPH_PARALLEL_RESOLVE_MIN=<count>to tune when it engages. - Indexing large projects got another sizeable speedup — about a quarter less wall-clock on the same 4,000-file Java project, with the graph still byte-for-byte identical. Two changes: the database no longer interleaves expensive checkpoint housekeeping into the middle of resolution on a fresh index (it's folded once at the end instead), and while one batch's results are being written out, the worker threads are already resolving the next batch instead of sitting idle.
- The dynamic-dispatch analysis that runs at the end of indexing (callback, event, and framework wiring) now runs its passes in parallel on large projects, cutting that stage roughly in half there — and a pass that crashes now retries safely instead of failing the whole index, which also makes very large codebases that previously died in this stage more likely to index to completion. Graphs remain byte-for-byte identical.
- On large projects, indexing writes its relationship data noticeably faster: secondary database indexes are set aside during the bulk of reference resolution and rebuilt once at the end, instead of being maintained row by row. Graphs remain byte-for-byte identical, and small projects are unaffected.
- Very-large-codebase reliability: on multi-million-symbol projects, an analysis pass that fails on a worker thread is now skipped with a clear message instead of being retried in a way that could take down the whole index, and the end-of-indexing index rebuild no longer risks tripping the liveness watchdog on huge graphs. Validated end-to-end on the Linux kernel (70k files, 2M symbols, 6.4M relationships) — it now indexes to completion even on a 2-core machine.
- Indexing is significantly faster — a fresh
codegraph initon a medium TypeScript project takes about a third less wall-clock time, with the same graph produced byte-for-byte. The gains come from batching database writes, storing files on a dedicated writer thread, memoizing repeated import-resolution lookups, skipping per-row search-index maintenance during the bulk build (rebuilt once at the end), and — on completely fresh databases only — deferring disk durability until the index completes, since an interrupted first index is simply re-run. SetCODEGRAPH_NO_FAST_INIT=1to keep full crash-durability during the initial build, orCODEGRAPH_NO_STORE_WORKER=1to store on the main thread. codegraph installandcodegraph upgradenow offer CodeGraph Pro beta access after finishing — answer yes, type your email, and you join the same waitlist as the getcodegraph.com homepage form. Strictly opt-in and asked at most once per machine total: nothing is sent unless you say yes and enter an email, either answer is remembered so no later install or upgrade ever re-asks, and non-interactive runs (--yes, scripts, CI) never see the question.- Every release is now cryptographically verifiable: npm packages publish with npm provenance (the "Provenance" badge on npmjs.com, proving each version was built by this repository's release workflow from a specific commit), and the GitHub Release bundles carry signed build attestations you can check with
gh attestation verify <file> -R colbymchenry/codegraph. - Indexing inside CPU- or memory-limited containers (Docker, CI runners) now sizes its worker pools from the container's actual allowance instead of the host machine's, and giant codebases no longer balloon temporary database files during indexing (previously tens of GB of transient disk on Linux-kernel-scale projects). Together these prevent out-of-memory and out-of-disk failures on constrained machines; set
CODEGRAPH_RESOLVE_WORKERSto override the resolution worker count explicitly. - Indexing very large projects on multi-core machines got faster again: the parallel-resolution workers now periodically refresh their read-only database connections, which lets database housekeeping advance instead of silently building up a backlog behind long-lived readers — a backlog that was taxing the indexer's own writes. Graphs remain byte-for-byte identical; the win is largest at Linux-kernel scale on many-core machines.
- Indexing on macOS now uses the machine's real memory headroom when sizing its parallel-resolution workers. macOS deliberately keeps RAM filled with reclaimable cache, so the previous free-memory reading came back tiny (~1GB on an otherwise idle machine) and silently halved the worker pool — a medium Java project's fresh index ran about 15–20% slower than the hardware allowed. Graphs remain byte-for-byte identical; the same fix also lets a memory-driven analysis cache engage fully on macOS for large C codebases.
- Fresh indexing got a sizeable across-the-board speedup: during the initial build, the database's secondary lookup indexes are set aside and rebuilt once after parsing instead of being maintained row by row — the same proven trick the later linking phase already used, now applied to the whole parse lane — and the reference-resolution loop likewise stops maintaining lookup indexes it never reads, rebuilding them at the end when almost nothing is left in the table. A medium Java project's parse phase runs about 58% faster and its full fresh index about 19% faster end-to-end; a Linux-kernel-scale index that took ~15 minutes on an 8-core machine now completes in about 11, with the resolution phase alone dropping by a third. Graphs remain byte-for-byte identical, and incremental syncs are unaffected.
- Saving a file now updates the graph almost immediately: the file watcher fires after a 300ms quiet window for one or two changed files (bursts still coalesce under the full debounce, and
CODEGRAPH_WATCH_DEBOUNCE_MSremains the upper bound), and watcher-triggered syncs reconcile exactly the changed paths instead of stat-walking the entire repository. Directory deletions and event storms still run the full scan-diff, so nothing the events can't describe is ever missed. Measured: save-to-fresh-graph sync work drops to ~0.3s on a 4,400-file project and ~0.4s on a 27,000-file one, with resulting graphs byte-for-byte identical to a full reconciliation. - Indexing Swift and other protocol/interface-heavy codebases got dramatically faster: the conformance walk that checks whether a method lives on a receiver's supertypes (protocols, base classes, extensions) now remembers its answers for the duration of each resolution batch instead of re-querying the graph for every call site — on the Swift compiler repository (27k files) that walk ran nearly a million times per index. A fresh index of that repo drops from about 185 seconds to under 100, with the graph byte-for-byte identical. Method-candidate lookup also gained a per-name owner index, so overload-heavy names (
initin Swift,executein Java) no longer pay a full candidate scan per receiver type. - Resolving method calls through local variables (
recv.method(), Lua'srecv:method(), R'srecv$method()) got much cheaper on repos where the same receiver is called over and over: the declaration scan that types the receiver now remembers what it has already scanned per scope instead of re-reading the same source lines for every call site, and the regex patterns it scans with are compiled once per receiver instead of per call. Kong's fresh index drops another 8% on top of the require-resolution fix (23% cumulative), with graphs byte-for-byte identical everywhere — including Java projects, where this same scan successfully types tens of thousands of receivers. - Indexing Lua and Luau projects got a sizeable speedup: resolving each
require(...)no longer rescans the project's entire file list four times — a per-project filename index answers the same lookup instantly, cutting per-require resolution from about a millisecond to microseconds. A fresh index of Kong (1,870 Lua files) runs about 16% faster end-to-end, with the graph byte-for-byte identical. The same housekeeping also closes a latent staleness edge where COBOL copybook lookups could keep serving a cached file list after files changed. - Parallel reference resolution now engages adaptively instead of by a fixed project-size cutoff: the indexer measures the actual per-reference resolution rate on the first batch and spins up the worker pool mid-run whenever the remaining work justifies it. Languages whose references are expensive to resolve benefit most — Rust especially: a fresh index of tokio runs about 23% faster, with the graph byte-for-byte identical. Small projects and low-core machines (2-core CI runners) keep the single-threaded path exactly as before.
- The dynamic-dispatch analysis at the end of indexing now skips passes that provably can't produce anything for the project at hand: React re-render bridging when no class has a
rendermethod, React Native and Expo cross-platform pairing when the required languages aren't present, and MyBatis mapper linking when there's no mapper XML. Previously each of these scanned the whole graph before coming up empty — on a 4,000-file Java project that was about 0.9 seconds of wasted analysis per fresh index. The interface-implementation bridging pass also got cheaper on real work: it no longer re-fetches a hub interface's method list once per implementer, and classes that extend or implement nothing are skipped before any per-class lookups. Graphs remain byte-for-byte identical. - Indexing large C and C++ codebases spends much less time in the function-pointer dispatch analysis (the pass that connects handler tables like a command table or an ops struct to their call sites): each source file is now read and prepared once instead of four times, files that can't contribute any dispatch wiring are skipped outright in the later linking steps, and on platforms with the native engine the per-file scanning itself now runs natively too. On a Linux-kernel-scale tree the pass runs about a third faster end-to-end, with graphs byte-for-byte identical; platforms without a native binary keep the same results on the previous path.
Fixes
- TypeScript, TSX, and JavaScript files now parse with up-to-date grammars — modern syntax such as
usingdeclarations and import attributes no longer trips parse errors that could drop surrounding symbols. (The previously bundled grammars dated from 2023.) - Rust files also parse with an up-to-date grammar now (the previously bundled build dated from 2023), which additionally sharpens method-call attribution: calls through struct fields resolve with receiver context instead of falling back to ambiguous bare-name matching, removing a class of wrong call edges on common names like
lenandstart. - Ruby files also parse with an up-to-date grammar now (the previously bundled build dated from early 2024), which fixes a misparse of safe-navigation operator-method calls (
recv&.!= x) that had recorded the wrong callee name. - PHP files also parse with an up-to-date grammar now (the previously bundled build dated from 2023): files using modern PHP features — enum constants, PHP 8.4 property hooks, parenthesis-free
new X()->method()chaining — no longer hit parse errors or misparses that dropped or garbled their symbols, so codebases like Symfony and Laravel index substantially more accurately. - Swift files also parse with an up-to-date grammar now (the previously bundled build dated from 2023): swift-testing
#expect,#Preview-style macros, thepackageaccess modifier, and typedthrowsno longer produce parse errors that dropped surrounding symbols — server-side Swift projects like Vapor see the biggest recovery. - Dart's grammar is now bundled with CodeGraph itself instead of being resolved from a third-party package whose Dart build floated on an unpinned upstream reference — a routine dependency update can no longer silently change how Dart files parse.
- Searching or exploring by field names now finds the code that defines them. A query made of object keys or API field names (
profileInfo isTrialEligible quotaInfo billingMethod) used to return unrelated results while the defining files never appeared, because three retrieval steps each dropped multi-word camelCase terms: an internal case-comparison bug, a match step that only considered classes (never functions or methods), and exploration seeding that required exact symbol-name matches. All three are fixed —codegraph_explorewith a bag of field names now surfaces the controllers and services that assemble those fields. (#1196) codegraph.json'sincludeIgnoredworks again for the "folder of repos" layout: when one.gitignorerule covers a parent directory (/repos/) holding several embedded git repositories, opting in the individual repos ("includeIgnored": ["repos/a/"]— the exact spellingcodegraph init's own hint suggests) previously matched nothing and indexed zero files, looping the same suggestion back at you. Both spellings now work — name the parent directory to opt in everything under it, or name individual repos to opt in just those — and the hint no longer re-suggests repos that are already configured. (#1295)- Method calls on literals (
", ".join(...)in Python,"x".split(...)in JavaScript, and the like) no longer produce call edges to unrelated project functions that happen to share the builtin's name — a codebase with a function calledjoin,get, orupdatecould show phantom callers from every string-builtin use. Additionally, a function nested inside another function is now only matched as a call target from inside its container, since it isn't reachable from anywhere else. Blast-radius and affected-test results get cleaner on Python and JavaScript codebases especially. (#1230) - Go method calls through a struct field (
target.conn.Exec(...)) no longer bind to unrelated same-named local methods when the field's type is external —conn *sql.DBcalls were being attributed to a local interface that happened to declareExec, fabricating internal dependencies. Chained field calls now resolve by inferring the field's declared type from the struct definition: in-project types (including unexported ones like chi'stree *node) gain correct, validated call edges that never existed before, and external types (standard library, third-party modules) are left unlinked instead of guessed. (#1276) - TypeScript/JavaScript method calls through an imported singleton (
import { store } from './store'; store.notify()) now resolve to the class method instead of the exported constant, socodegraph callerssees cross-file callers of the method — previously only same-file calls were attributed and a method used everywhere could look unused. The same declaration-based type inference applies across the languages that share it (Python, Java, Kotlin, Go, and more), and a failed inference keeps the old edge rather than guessing. (#1292) codegraph node <symbol> -f <file>now prints the symbol's source body. Pinning an ambiguous name to a specific file (the whole point of-fwhen many files define the same function) returned only the location and caller trail with no code. (#1284)- Deleting a whole directory is now picked up by watch mode: the files inside it are removed from the index on the next auto-sync instead of lingering as stale records until an unrelated edit happened to trigger one. Operating systems often report a directory deletion as a single event on the directory itself (with no per-file events for its contents), which the watcher previously discarded. (#1285)
codegraph syncnow gets the same slow-disk fix that made full indexing fast in 1.4.0: database checkpointing is deferred for the whole incremental run instead of firing every few megabytes of writes. On mechanical drives and other high-latency storage, a small sync on a large index no longer stalls for minutes at near-zero CPU — the cost of a sync scales with what changed, not with the size of the existing index. The sameCODEGRAPH_NO_WAL_DEFER=1switch turns it off. (#1248)- C functions declared with a project-specific attribute macro in front of a typedef'd return type (
SEC_ATTR UINT32 MyFunc(VOID)— common in embedded and kernel code) are now indexed under their real names. Previously the parser tripped over the unknown macro and stored the parameter list as the function name, leaving entries like"(VOID)"in the graph and making the real function unfindable. (#1211) - Macro-heavy C and C++ code indexes much more completely. Fifteen ubiquitous idioms that previously tripped the parser into error recovery — dropping symbols, garbling names, minting phantom entries, or losing the relationships between real ones — now parse cleanly: the
#ifdef __cplusplus/extern "C" {compatibility guard in C headers, iterator macros in statement position (list_for_each_entry(pos, head, member) { … }and the whole Linux-kernel/git/jemalloc family — including calls that wrap across lines, likehlist_for_each_entry_rcu(…)with alockdep_is_heldargument), the Linux/sparse declaration annotations (static int __init foo(void),void __user *buf,container_of(p, struct T, m)), parameterized annotations (__free(kfree),__printf(4, 0),__counted_by(count)), per-CPU and work-queue declaration macros (static DEFINE_PER_CPU(struct T, name);, initialized forms included), bare type arguments to allocator and list macros (kzalloc_obj(struct T),list_entry(p, struct T, member)),va_argwith a qualified or pointer type, GNU named-variadic macro definitions (#define dbg(fmt, args...)),static notrace-style compiler markers, C23autodeclarations, trailing parameter annotations (int argc UNUSED, git's house style), namespace-management macros alone on a line (FMT_BEGIN_NAMESPACE, Qt'sQ_OBJECT), and function attribute macros in front of C++ return types. On the Linux kernel this cuts files lost to parse errors on the core directories by nearly half and recovers thousands of call and reference relationships that error recovery silently dropped, with git and redis seeing smaller cuts of the same kind; graphs stay byte-for-byte identical between the native and fallback engines. A related fix stops the macro handling itself from corrupting#definelines that mention the same macro names, which removed a class of phantom parse errors in fmt-style headers. - C++ methods defined out-of-line inside a namespace (
namespace sim { Output MyClass::Apply(...) { ... } }) now carry the namespace in their qualified name, matching their class. Fully-qualified call sites from other files (sim::MyClass::Apply(...)) resolve to the definition again, socodegraph callersand file impact no longer come up empty for this pattern. (#1291) - C++ methods defined out-of-line on a template class (
template <typename T> T Box<T>::get() { ... }) no longer keep the template parameter list in their qualified name. They now index asBox::get— identical to an inline definition of the same method — so they link to their class and resolve from call sites again, and pathological multi-line template parameter lists can no longer blow the qualified name past filesystem name limits. (#1286) - Go route detection no longer misidentifies ordinary method calls that share HTTP verb names —
cache.Put("key", value),store.Get("config", out),bus.Handle("user.created", handler)and the like were being indexed as HTTP routes, polluting route listings in cache-heavy codebases. A registration now has to look like one: its first argument must be a/-prefixed path (all routers) or a Go 1.22"METHOD /path"pattern onHandle/HandleFunc, which now also extracts the method instead of listing the route asANY. (#1259) - Progress output on Windows no longer mixes ASCII
|rails with the Unicode│ ◆ ●frame around them. In terminals that render Unicode (Windows Terminal, VS Code, ConEmu/Cmder, JetBrains, Alacritty), the wholecodegraph init/index/syncblock now draws with matching box-drawing characters; unrecognized legacy consoles keep the safe all-ASCII output that avoids garbled characters.CODEGRAPH_ASCII=1/CODEGRAPH_UNICODE=1still override in either direction. (#398) - CLI output now honors the
NO_COLORconvention and new--color/--no-colorflags, and goes plain automatically when piped: commands likecodegraph status,query,callers, andfilesno longer embed ANSI color codes when stdout isn't a terminal, and a pipedcodegraph init/index/syncprints simple per-phase lines instead of progress-animation control characters.FORCE_COLORor--colorforces color back on for pipes that render it. (#1281) - Callers and impact analysis no longer silently under-count a function that calls the same callee many times. When one caller contained several call sites to the same callee and an internal resolution batch boundary happened to split them, cleanup after the first batch removed the later sites' pending rows before they were ever attempted — their edges were never created, deterministically, and which edges went missing shifted with unrelated changes to the project's total reference count. Post-pass cleanup now targets the exact database row each processed reference came from. Found while validating the operator-call fix on nlohmann/json, where
write_cbor's 11 calls toto_char_typeindexed as 10. (#1269) - C++ explicit operator calls —
a.operator+(b),p->operator+(b),a.operator[](3), and the other symbolic forms — now produce acallsedge to the operator method, so an operator invoked only through the explicit syntax no longer looks uncalled in callers and impact analysis. tree-sitter parses these call sites with the operator name stranded in an error node (never as a normal member access), so the call's target was silently read as just the receiver variable; the operator name is now recovered from the error node and resolved through receiver-type inference like any other member call — a same-named operator on an unrelated class can never capture the edge. Infix uses (a + b,a[i]) need real type inference and are tracked separately. (#1247) codegraph initandcodegraph indexno longer look hung after "Resolving refs" reaches 100%. The dynamic-dispatch linking that runs after resolution (callbacks, React re-renders, C function pointers, and the rest) had no progress display, so on repos where it takes a while — large C codebases especially — the bar just sat frozen at 100% until it finished. That work now shows as its own "Linking dynamic dispatch" progress phase, and the heaviest pass — C function-pointer linking — additionally reports progress within the pass, so a large C codebase advances the bar smoothly instead of parking it on one number for the bulk of the phase.- Indexing no longer prints repeated "SQLite is an experimental feature" warnings that garbled the progress display. The warning comes from Node's built-in SQLite and fired once per parsing worker; it's now suppressed on every launch path.