Repository navigation
Conversation
equivalence.rs only proved that each committed colby.db agrees with its own JSON; nothing in the suite ran the extractor, so a change that moved extraction output passed CI until someone ran the out-of-repo regeneration helper by hand. - tests/golden_reextract.rs indexes every re-indexable corpus from crates/codegraph-bench/fixtures/ with the binary under test, dumps it through the same canonicalizer as `bench --gen-golden`, and compares the five artifacts byte for byte with reference/golden/. A failure names the changed artifacts, the canonical row diff and the regeneration command. A guard test keeps the corpus list in step with reference/golden/. - scripts/regen-goldens.sh runs the shared recipe: --check (writes nothing), --write <corpus...> (only the named corpora) and --mini-transplant (mini's schema rebuild). Scratch directories come from mktemp and are removed on exit. - equivalence.rs generates each corpus's two byte-drift tests from one macro, keeping the existing test names, and fails when a committed corpus has no test pair. - docs/equivalence.md describes the recipe once, adds the regeneration log the v0.55.0 program records changes in, and each corpus section now says what it guards plus the one regeneration command. Every corpus re-extracts byte-identically on this tree; a tampered go/nodes.json makes the gate fail with a Tier-1 node diff.
…s and CommonJS corpora The goldens had no corpus for C#, Java, Swift, Objective-C, PHP, Vue or Svelte, and the Python corpora had no class with bases, so whole families of extraction never reached the equivalence oracle. Each new corpus is a small source tree under crates/codegraph-bench/fixtures/ with its golden under reference/golden/, generated by `scripts/regen-goldens.sh --write`. They capture today's output on purpose, misses included, so the corrections that follow show up as reviewable diffs attributed to one change each. Both golden suites now cover them, and docs/equivalence.md says what each one exercises and logs the addition.
Four multi-project corpora give the framework resolvers and synthesis passes a baseline before they land: routers (React Router, Next.js, Expo Router, Vue Router, Nuxt, SvelteKit, Angular, TanStack), servers (Express, NestJS, Flask, Django, FastAPI, Spring, Laravel, Rails, ASP.NET, Vapor, axum, actix-web, Gin, Play, Drupal), mobile (React Native and Expo native modules, a Swift/Objective-C bridge) and synthesis (emitters, handler tables, re-renders, stores, Go embedding, C function pointers, Kotlin expect/actual, MyBatis, Celery, Spring events). Each project carries the manifest detection reads, and projects share symbol names on purpose, so the cross-project misses in today's output (an Angular @component bound to a React file's Component) are in the golden and their later removal shows up as a diff.
…n version to 22 Schema 9 adds the `synthesis_inputs` table and the partial `idx_edges_synthesis_site` index the synthesis stage uses to decide when a sync must refresh synthesized edges. The extraction version moves once, from 21 to 22, ahead of every graph-semantic change in this release, so an index written by v0.54.0 reads as outdated and is rebuilt exactly once. Every golden corpus regenerates only `schema.sql` and `colby.db`; the canonical JSON is unchanged (golden_reextract proves it), and mini is transplanted with `scripts/regen-goldens.sh --mini-transplant`. `upgrade_from_v054` plants a real v0.54.0 index (fixture built by the released binary, provenance in its README) and upgrades it through both `codegraph sync` and the daemon catch-up sync, asserting schema 9, the extraction stamp 22 and a canonical graph equal to a fresh init.
Add the `probes` test binary (crates/codegraph-cli/tests/probes). A probe writes a small tree into a temporary directory, runs the built `codegraph init` with CODEGRAPH_NO_DAEMON=1 and CODEGRAPH_NO_WATCH=1 so scanning, extraction, framework detection and resolution run as they do for a user, and reads the published index back through an immutable=1 SQLite connection, which leaves no sidecar for a later sync to refuse. `support.rs` provides the Project builder, edit/sync helpers that stamp explicit mtimes instead of sleeping, row queries over nodes, edges, unresolved references and files, a selector notation shared with the failure output, and the assert_edge!, assert_no_edge! and assert_unresolved! macros, whose failures list the project's whole non-contains edge list. Smoke probes pin a TypeScript import call, an unresolved external import, a Python method call and a sync re-read.
Add the `sync_convergence` test binary. Each case indexes a `before` tree with the built `codegraph init`, applies one edit (add, modify, delete or rename files), and syncs it through two drivers: the CLI `codegraph sync`, and the watcher path, `sync_changed_paths` with exactly the changed paths (or the full reconcile the real watcher runs for a control file). A fresh `init` of the final tree in a separate directory is the oracle; both drivers must match it under `canonicalize_db` + `diff_canonical`, by full natural keys. Cases name the edges their before and fresh indexes must hold, the on-disk trees must equal the oracle's, and failures print a readable row diff. The cases cover every language with an extractor and every change kind: an added import target, a body-only edit (#2288 same-named methods and Java overloads), a rename, a deleted module, a heritage edit, a same-named competitor, control files (codegraph.json, config.toml, .gitignore, tsconfig.json, package.json) and a C header that turns into C++. Gates fail when a language with an extractor or a change kind has no running case. Known gaps are ignored with their reason and fail under --ignored: W5-01 #2392, W5-02 #2403 and W5-03 #2450, plus four unscheduled gaps this matrix found: an Erlang remote call and a MyBatis cross-mapper include are not retried when their target appears, and a tsconfig.json `paths` or package.json framework change does not re-resolve unchanged files. The probe `file:` selector now matches a file node by path, since the embedded extractors key theirs by a hash.
The live-events test once held a Windows CI job for 40 minutes: the sync by another writer and the final search had no time bound, and the #[tokio::test] runtime's drop waits forever for a stuck spawn_blocking task, which the viewer uses for every request. Each test now runs on its own runtime through `run`, which bounds the test body, propagates a panic from it, and shuts the runtime down on a helper thread with a limit of its own. Every step that can block has a named limit: indexing the fixture, starting the viewer, connecting to /api/events, each awaited event, each request, the sync by another writer, closing the hub, dropping the last client and the teardown. Blocking steps run on the blocking pool so a stuck one never stalls the runtime. A hang fails with the step's name (or, for the backstop, the last step started). No assertion changed.
`bench --ab` times a cold `init` of a baseline and a candidate codegraph binary on pinned corpora. Every run copies the corpus into a fresh project without `.git` or an index directory, sets RAYON_NUM_THREADS (the CLI has no thread flag), CODEGRAPH_NO_DAEMON and CODEGRAPH_NO_WATCH, removes CODEGRAPH_DIR, and records the wall time, the wait4 peak RSS and the canonical fingerprint of the database. Runs alternate between the binaries and the first run of each cell is a discarded warm-up. The harness stops without a report when a run fails, is killed or times out, leaves no database or one that fails PRAGMA quick_check or canonicalization, or when one binary produces two different graphs at any thread count. The JSON report records the CPU, kernel, rustc, both binaries' SHA-256, every run, median and MAD, candidate/baseline ratios and the regression-budget verdict; --report-md renders it as a table. `bench --graph-diff` compares two canonical graphs, each a database or a golden directory, and prints the removed and added rows grouped by surface, language, kind and resolvedBy. It exits 0 when the graphs are identical, 1 when they differ and 2 when an input cannot be loaded. A database is read from a private copy, so the input never gains SQLite sidecars. The corpus registry gains express-medium and the release-pinned leveldb, gson, Newtonsoft.Json, Alamofire, redis and kong corpora, a tag field, and --corpora-root for --list-corpora and --fetch-corpora. sha2, already in the lock graph through codegraph-core, hashes the binaries and graphs. docs/benchmark.md documents both commands, the report fields, the regression budget and every pinned corpus.
V1_6_2_AUDIT_2026-10-10.md records the 268 upstream commits from colby v1.6.1 to main of 2026-10-07 row by row: disposition, Rust target, the v0.55.0 program commit that carries each row and its status, together with the Rust-only defects and pre-existing extraction gaps the audit found and the owner decisions of 2026-10-10. COBOL is OWNER-EXCLUDED, and refusing a newer schema is recorded as PLANNED for its own follow-up pull request. The ledger's discovery boundary moves to b635dd46, and docs-check follows it; the tracked release stays v1.6.1 until the ported rows ship.
…egistry `index --force` lists every registered stdio MCP server on stderr before it rebuilds. The pre-heal ordering test drains the child's pipes only after both lease barriers, so on a machine with many registered servers, and pipes capped at two pages once the user's pipe budget is spent, the list filled stderr and parked the command before its second lease. The test now gives the command an empty registry of its own.
The single extraction-version move to 22 must be exactly one step past the version the preceding release wrote, which is 21 for v0.54.0.
Codecov Report❌ Patch coverage is ❌ Your patch check has failed because the patch coverage (94.31%) is below the target coverage (95.00%). You can increase the patch coverage or adjust the target coverage. @@ Coverage Diff @@
## main #322 +/- ##
==========================================
+ Coverage 95.06% 95.55% +0.49%
==========================================
Files 198 279 +81
Lines 109293 164465 +55172
==========================================
+ Hits 103895 157161 +53266
- Misses 5398 7304 +1906
... and 74 files with indirect coverage changes 🚀 New features to boost your workflow:
|
The upgrade test built its immutable URI from the raw path, which on Windows keeps the \\?\ verbatim prefix and backslashes that SQLite cannot open. It now uses the same file: URI form as the probe harness.
dump_golden wrote its log beside the output directory, so --write left reference/golden/<corpus>.gen.log next to the named corpus and removed that path on exit. Both logs now live in scratch directories of their own.
…s W0-06 requires The corpora lacked three shapes the plan asks them to pin at v0.54.0 semantics: a PHP `static::` call, a CommonJS `require().member`, and a Flutter app whose `pubspec.yaml` triggers detection. Each lives in a new file, so the regenerated goldens only add rows: `bench --graph-diff` against the previous goldens reports 0 removed rows for all three.
The UI-family, extraction-gap, carry-forward, planning-finding and Rust-only-defect tables now carry the disposition and Rust target columns the ledger format requires, and the not-needed section is a row ledger.
--mini-transplant still logged to a path beside its scratch directory and removed it on exit; the log now lives in a scratch directory of its own, like the other two.
…ture The script-tests loop ignored a failing script, so ci-gate.test.sh had silently stopped at its stale needs anchor since the UI job joined CI Success (#298): mutations C to K never ran. The anchor now carries the ui job and the loop stops on the first failing script; all five harnesses pass.
…tarts A cold `serve --mcp` answers its handshake direct and spawns the shared daemon fire-and-forget, but never connected to it. With no client the daemon idle-exited after CODEGRAPH_DAEMON_IDLE_TIMEOUT_MS (300 s by default) and the session's index silently stopped syncing. The session now keeps a passive lease on that daemon from a background thread, the same client connection an explicit projectPath service holds, so the handshake still never waits on the daemon. When the lease stops being live (the daemon died, was replaced, or the index was re-created) it starts or re-attaches a daemon with backoff: CODEGRAPH_DAEMON_RETRY_MS (default 5000, 0 disables) doubling up to CODEGRAPH_DAEMON_RETRY_MAX_MS (default 300000), as upstream #2293 does for its degraded proxy. It never starts a daemon for a project whose index is gone.
A settled burst used to sync exactly the paths its events named, however many, and the notify callback forwarded only the event kind and paths, so a backend's sign of dropped events was lost and the index could stay stale after a mass rename or a branch switch (#269). The watcher now runs one full project reconcile instead when the burst named more than 500 paths (upstream #1397's SCOPED_SYNC_MAX_PENDING), when an event carries notify's Rescan flag (an inotify queue overflow, an FSEvents drop) or names no path (upstream #2025's filename == null), and on Windows, whose backend can lose events silently, when an idle drift sentinel (CODEGRAPH_WATCH_SENTINEL_MS, default 60000, 0 disables) finds changes no event reported; it then also registers the root watch again. Until a lost-event reconcile commits, the watcher reports RECOVERING with the cause, and the RECOVERING banner now names its reason instead of assuming lock contention. As promised on #269, the MCP instructions, the codegraph_status description and the skill now say what happens after a large burst and that renames never need a rebuild; the initialize golden follows the instructions.
A daemon keeps running the code it started with. After an upgrade the project's daemon could be an older build than the session that found it: the proxy saw a version mismatch, the session fell back to serving reads, and the old daemon kept the writer slot for good (upstream #2343, #2346). A session that meets a daemon of another version now reads its version from the hello and replaces it only when it is an older plain X.Y.Z release: it asks it to drain over the project-bound control channel, so nothing is signalled by pid, then starts a daemon from its own install and proxies to it, or attaches through the cold-start lease keeper if the new daemon is slower than the usual poll. A newer release, a prerelease, an unknown version, or a daemon that does not acknowledge the drain is never stopped; the session then serves reads and says so on stderr. Every daemon also records its executable at start and drains and exits once the file is replaced or removed (CODEGRAPH_DAEMON_INSTALL_CHECK_MS, default 30000, 0 disables), and a cold-start session whose own install changed stops starting daemons. A test-hooks feature lets test builds of the daemon claim another version and leave control frames unanswered.
A content-hash comparison calls a file up to date even when its stored row holds none of its symbols, so `status` said "Index is up to date" while a file contributed nothing to the graph (upstream #2336, the status half of #2346). `status --json` adds `index.filesNeedingReindex` (rows stored with no nodes and no recorded reason, which a hash-based sync never revisits) and `index.filesWithParseErrors` (a recorded parse failure and no symbols from it); files over the size limit and file-level-only languages are empty on purpose and count in neither. Human output names up to three files of each group and no longer calls such an index up to date. `files --json` carries each file's recorded errors. Every existing field is unchanged.
`serve --mcp` launched in an unindexed directory with `.git` searches downward for indexed sub-projects. The walk had a depth and candidate cap but no limit on how much it read: launched in /tmp (~40K entries) it never answered `initialize` within 30 s. The scan now stops after 10,000 directory entries (counted, so where it stops is deterministic) or 500 ms, whichever comes first, and a scan that stopped early adopts nothing, since it cannot know the child it found is the only one; the tool guidance and the stderr notice say it stopped. The MCP servers no longer run the scan in their constructors, before `initialize` is answered: their first `tools/list` or tool call runs it, and reports the candidates it finds once.
The daemon runs detached, with no console of its own, so on Windows every console program it starts without CREATE_NO_WINDOW opens a visible console that flashes once per call. The git pending-status path already set the flag; `git_command` (repository and hook discovery) and the viewer's `git config user.name` did not (upstream #2280). `codegraph_watch::hide_console_window` now sets the flag in one place, and every git call, and the viewer's browser opener, goes through it. Because std cannot report a command's creation flags back, a static source guard (like upstream's) checks every `Command::new(` in the crates the daemon runs: each must hide its console or say in a `console-window-guard:` comment why it needs not.
KNOWN_DIFFS listed C++ receiver-type inference, Java/Kotlin field-receiver inference, the batched and conformance passes and the per-language import refinements as deferred, though all of them are ported. equivalence.md said `ns::compute()` resolves through the qualified-name matcher, while the cpp golden records it as an exact-name match, and called the missing Rust struct-literal instantiation upstream's own shape, which it is not. A dated UPSTREAM.md entry records the corrections, the KEEP-RUST status of #226's stricter receiver-word gate, and the #1728 disposition; the entries it corrects stay as written.
- Read <root>/.git/info/exclude ahead of the root .gitignore in RootGitignore, so the scan and the watch policy prune what git's exclude-standard prunes (upstream #1728). A .gitignore rule wins a conflict, as in git. Only a real .git directory at the project root is read, through the bounded stat-first reader; a .git file, a link, an ancestor repository and core.excludesFile are never followed. - The watcher treats the exclude file as a control file: per-directory backends watch .git/info, and an edit reloads the scope and runs one full reconcile. - The pending-status git record's scope fingerprint includes the exclude file, so a record made before an edit is not trusted after it. - Sync matrix: a .git/info/exclude control-file case. The root .gitignore case now hides legacy/ rather than vendor/, which the default ignores already drop, so it moves an edge instead of passing vacuously.
- An import node is a statement, not a definition (upstream #965, 0a91d0f5). Exact name matching no longer offers one, the file's own or another file's, and one named like a definition no longer makes that definition look ambiguous. The fuzzy fallback already considers callables only. The qualified-name matcher is unchanged here: its imports-to-own-statement edges belong to the #2392 target gate (W3-01). - Goldens: csharp, erlang, java, mobile, php, python, python_bases, routers, ruby, rust, servers, swift, synthesis, typescript, vue lose every imports edge exact matching bound to an import node, and the refs stay unresolved. PHP `use App\Field;` now binds namespace App\Field; Ruby `require_relative "logger"` falls through to the fuzzy match class Logger. Logged in docs/equivalence.md. - Probe pr0965_vue_import_never_binds_a_svelte_import_node, unit tests for the exact and fuzzy paths, and a PHP AddCompetitor sync-matrix case. - tier2_false_edges: the phantom node:path import now binds nothing and stays parked as unresolved (renamed to phantom_import_binds_nothing_and_stays_unresolved); the Rust supertype fixture keeps only its in-repo `use` edge, its `crate`/`std` self-edges gone.
- The out-of-repo import gate (imports and supertypes) no longer counts a JS specifier as local because it starts with a path alias's prefix: a catch-all "*" alias has an empty prefix that every package starts with (upstream #2437, 2c807f5c). An alias now makes the import local only when resolve_import_path maps it to a file the index holds, so an alias landing in node_modules or on a directory does not either. Resolving an import still honors the prefix, so "*": ["src/*"] aliases resolve. - ResolutionContext gains is_indexed_file, answered from the store or the snapshot's known files, never from the filesystem. - Probes pr2437_catch_all_alias_keeps_package_imports_external and pr2437_alias_into_node_modules_keeps_the_import_external, unit tests, and a Tsx AddCompetitor sync-matrix case; the matrix gains no_edge_after for an edge a fresh index must not hold. - No golden changes (scripts/regen-goldens.sh --check: 32 identical).
- detect_language_for_source (and detect_language_of for a bounded read) give a file's language as extraction parses it: a .h header holding C++ or Objective-C is that language. index, sync, migration and framework extraction record and use it, so a header's file row agrees with its nodes (G15). Framework extraction reads a C header whenever a resolver applies to C, C++ or Objective-C. - Goldens: cpp plain_derived.h and ue_actor.h become cpp, objc Sources/Base.h becomes objc; files.json only. Logged in docs/equivalence.md. - c_family_correctness pins the sniffed language for headers and the agreement of every node; the c_header_flips_to_cpp sync-matrix case now requires the fresh index to record shape.h as cpp, via the new file_language_after expectation.
Port upstream #2178 (a) (W4-SOL-01). A Solidity call written without a receiver reaches a function of the contract, interface or library around it or of one it inherits through its `is` clause, or a free function; never another contract's override, and outside any contract no member. The chain is read from each declaration's head, constructor arguments dropped and a dotted base by its last name, every declaration of a name counted. OpenZeppelin's `_msgSender()` in Context-derived contracts went to ERC2771Context's override 83 times; solmate's WETH `_burn` went to ERC1155's. Sync records each Solidity file's types and bases in a `solidity_heads:<path>` row under the `solidity_heads_version` marker. A type that came, went or inherits otherwise moves the chains of the types below it: in unchanged files, the calls naming a method of a type that entered or left such a chain are retried at their sites. An index without the marker re-resolves every Solidity file once and rebuilds the rows. KEEP-RUST: a `//` comment in a head drops only the rest of its own line; upstream joins the head's lines first and loses the bases below it.
….hrl Port upstream #2178 (b) (W4-ERL-01). An Erlang call written without its module reaches the function of its own module, else another module's only through `-import(Mod, [f/N])` (nothing when `Mod` declares no `f/N`), else one a `.hrl` declares; with neither, nothing the project declares. cowboy's `-import(req_SUITE, [do_get/3])` went to compress_SUITE's `do_get/3`. A call written in a `.hrl` keeps reaching any module's function of its name and arity. Sync touches an Erlang function's name with its arity (`do_get/3`) when the function comes, goes or changes: a bare call is parked under that name, which no node carries and the tail lookup never scans, so a call waiting for the module it imports from, or a header, to declare the function was never retried.
…stay in their theme Port upstream #2393 and #2396 (b635dd46 form). - A Shopify theme is a directory holding a theme folder and a marker (`layout/theme.liquid` or `config/settings_schema.json`). JSON under `templates/` or `sections/` is Liquid only inside a theme found under the project root, which `ExtensionOverrides::load_for_paths` now carries into every scan, sync and watcher decision. A marker counts wherever it is, ignored or not. - A section or snippet reference made inside a theme resolves to that theme's own file or stays unresolved; outside any theme it resolves by path as before. - New sync lane `shopify_themes`: a row of the theme roots the indexed Liquid files sit in and a version marker. A theme root that came or went hands its JSON templates (folders of any case) to the per-path pass and re-resolves its Liquid files whole; the moved roots are recorded before the pass so a stopped sync leaves them to the next. Both drivers converge with a fresh index (upstream needs a full sync). - The watcher hands marker events to the sync whatever the ignore rules, watches an ignored `config/` or `layout/` itself, and counts a marker folder removed or moved away as its marker gone. `status`'s git fast path declines when a marker is a candidate, git may hide one, or a recorded theme lost its markers. - Probes, unit tests, failure injection, real-watcher and git fast path tests, a migration test, and nine sync-matrix cases; docs updated.
Port upstream #2289 (b635dd46 form).
- `.inc` is PHP's include extension and Pascal's too. A PHP open tag
anywhere keeps an include PHP; otherwise a line shape only Pascal
writes (a compiler directive, a `;`-closed routine header, `unit` or
`uses`, a `const`, `var` or `type` section, a `begin` ... `end;` pair,
Lazarus' `{%MainUnit}`) makes it Pascal; anything else stays PHP.
- The decision reads the file's own content, for the file row and the
extraction alike, so a full index, a sync and a fresh index agree.
- The patterns follow upstream's JavaScript reading: ASCII `\w`, the
`(?!:)` lookahead written as the class it stands for, `$` before `\r`;
they compile once into a `RegexSet`.
- A codegraph.json `.inc` mapping turns the content reading off; as for
any built-in extension, the mapping leaves `.inc` PHP. A file over the
size limit is never read, so its extension decides.
- Unit tests, probes and two sync-matrix cases; docs updated.
…ans cannot use The W4 full A/B put two shared-path regressions on regexes compiled per reference in name_matcher.rs. veto_js_local_shadow read the call's receiver, a regex compiled per call, for every language's name match, though only a JavaScript or TypeScript function scope can veto; it now returns at once outside that family and reads the function's local scope before the receiver. infer_local_receiver_declaration compiled the language's receiver patterns for every method call (three quarters of a Newtonsoft resolution once C# and Java gained type arguments); every language with patterns now compiles them only once a line passes a necessary condition of them (line_may_declare), and a language without patterns returns at once. csharp_keyword_local_type filters its candidate lines the same way. Each prefilter is a necessary condition of the patterns it guards, so resolution is unchanged: the goldens and the full index of the ten benchmark corpora and of 29 more downloaded ones are identical. Cold init with 4 threads, fast profile, median of two runs on the same machine: Newtonsoft 16.4 -> 9.1 s, gson 4.1 -> 3.1 s, redis 2.9 -> 2.3 s, Alamofire 0.65 -> 0.54 s; typescript-large at 32 threads 12.7 -> 11.9 s. Peak RSS is unchanged.
bench --ab took a run's peak RSS from wait4's ru_maxrss, and Linux carries the forking process's high-water mark into the child across exec. Once the harness had canonicalized the typescript-large index it held 414 MiB, and every later cell read 414.2 MiB, whatever it really used. On Linux the harness now polls VmHWM in /proc/<pid>/status every millisecond while the child runs; other Unix systems keep ru_maxrss. The report names the source, and docs/benchmark.md describes it. A test runs a `sleep` while the harness holds 256 MiB and expects a few MiB, where ru_maxrss reads 265804 KiB.
KNOWN_DIFFS.md recorded what the port has not ported, but not where it reads a shape differently from colby by decision. A new section lists those KEEP-RUST decisions with the module that carries each: the OD-8 resolution rules (#226 receiver words, #1496/G14 nested receivers, the P15 Rust call shapes, #2218, the C# extension methods), the extraction differences (always-on value references, Kotlin/PHP property types, C/C++ alias signatures, the C/C++ pre-parse, CFML fragment ids, Dart positions), the walks read in path order where colby reads index order, the places the source is read as written (Java anonymous classes, Solidity heads, Erlang import lists, C/C++ macro visibility, JS stores, PHP class names, Dart libraries, Shopify markers, Pascal includes), and the sync lanes colby's sync has no counterpart for. Differences of mechanism that give colby's result are left out.
…patterns The W4 A/B rerun with the fixed harness left two overruns, and profiles of frame-pointer builds traced both to regexes compiled per reference or per name. typescript-large held about 100 MiB more than v0.54.0: #2226's js_code_binds_name compiled a declaration and a parameter pattern for every name it was asked about and kept up to 1024 pairs, with their per-thread search caches, for the whole index. The declaration check now reads the code by hand, as the pattern's left-to-right matches did, and the parameter pattern compiles, uncached, only when a `(` opens a list that could hold the name. At 32 threads whole-file string blanking built character and offset arrays, 16 bytes a character: strip_c_style and blank_string_contents now scan bytes, every delimiter being ASCII, and module facts read a transient blanked copy instead of keeping one per file. leveldb's init spent 62% of its samples compiling the C++ receiver scan's patterns. The receiver's word is read without a pattern, the declarator and rebinding patterns compile only once a line could match, an ASCII line uses the declarator spelled over ASCII (no UTF-8 automata to build), and the alias readers compile theirs only behind literal checks. JS-family receiver lines are admitted only for `= new` and `: Type`. Each reader is tested against the pattern or the character version it replaces, and the full index of the ten benchmark corpora and of 29 downloaded ones, and every golden, is identical. Against a v0.54.0 build of the same profile: typescript-large peak RSS 1.06x at 32 threads and 1.05x at 4 (the release A/B had 1.35x and 1.41x), init 0.95x and 0.84x; leveldb init 0.95x and 0.77x (the A/B had 1.19x and 1.45x).
RUSTSEC-2026-0367, -0368 and -0369 (published 2026-10-09 and -10) report that zip 6.0.0's extraction follows symlinks out of the destination and keeps SUID/SGID bits. zip came in only through self_update 0.44, which required zip ^6; 1.3.1 requires zip ^9, the fixed line. Since 1.0 each backend, the progress bar and the tar.gz decoder are features of their own, so the feature list names github, progress-bar and compression-tar-gz. The update path's API moved: the tag setter is release_tag, get_latest_release returns the listing, newest first, and the status predicate is is_updated. quick-xml now comes only with the s3 backend, so it leaves the build, and the RUSTSEC-2026-0194/0195 exceptions in .cargo/audit.toml go with it. cargo audit reports no advisory. A musl build of this tree updated itself from the v0.54.0 release, through asset selection, download, tar.gz extraction and replacement, to the release's own binary.
67cb578 reads VmHWM inside the Unix wait loop, but imported the reader for every target, so Windows Clippy failed on the unused import under -D warnings. The import now carries the loop's #[cfg(unix)].
Since c221085 a named const constructor or a redirecting factory is a method node, and the lookups that resolve a call through a value took it for an instance member: `x.value()` on a parameter typed `Foo`, or `build().value()` where `build` returns one, called `Foo.value`'s constructor instead of the `value()` an extension on `Foo` brings, and with no such member the matcher's fallbacks still found it, reading a receiver `foo` as the class `Foo` or scoring a receiver `fooValue` by its words. Upstream's dartMemberOf has the same reading. A constructor is now reached only by the type path, a call written through the class (`Foo.value(2)`). dart_member_of (chain links, the typing of a chain's links, `call`, extension members) skips constructors, and match_method_call's value strategies, resolve_method_on_type and the receiver-as-class and word-scoring fallbacks, take their candidates through callable_on_a_value. The Dart row of docs/languages.md says what is now built. A probe covers the parameter, local and chain shapes, the type path, and receivers `b`, `bar`, `barValue` and `dynamic bar` of a type with no member of the name; two matrix cases add and remove the extension. The goldens, the Dart corpora and the benchmark corpora index identically.
…ferent A Dart call through a value of a type no longer reaches the type's named constructor, where colby's member lookup and receiver guesses take it. Record the rule and the modules that carry it in KNOWN_DIFFS.md's KEEP-RUST list. It is no owner decision, so the Resolution heading drops its OD-8 tag and each owner-decided bullet names OD-8 itself, as the Extraction bullets already do.
…s later Port upstream #2403's tests (W5-02). The behavior already landed with W5-01: this port computes a parked reference's tail when it looks the reference up, and a reference written as a path (`snippets/price.liquid`, a JSON template's `sections/404.liquid`) waits under its file's stem, which an added file's import path keys carry, so the file it names sends the referencing file down the whole-file lane. Upstream parks it under the file name and rewrote the tails an older index stored (schema v13); this port stores no tail, so that half has nothing to port. The probes add upstream's several-themes fixture with four files added later (the links match upstream's and a fresh index of the same files, and a deleted snippet's reference waits for it and links again) and the one-theme sync case; the store test maps upstream's referenceNameTail cases onto this port's keys. The matrix drops the ignore of liquid_render_of_an_added_snippet and adds a JSON template's section added later. Without the path-reference branch of the import retry, the two probes and the two matrix cases fail.
…e appears
Port upstream #2450's behavior (W5-03). A binding a module declares under
another name (`import { Widget as W } from './widgets/Widget'`) is
written under its own, so an edit that gives the module `Widget` touched
no name the binding's references wait under: sync kept them unresolved,
or on a namesake they had been matched to by name, where a fresh index
links them to the module's declaration. Upstream parks such references
under #2422's `module:` keys and adds an index for them; this port stores
no tail and #2422 belongs to W7-23, so the behavior is ported with a
lookup of its own.
Store::importers_with_unbound_bindings reads, for a JavaScript-family
module, the importers whose statement of it holds a binding the import
did not resolve: an unresolved `imports` reference within the
statement's lines other than its path, or an `imports` edge there whose
resolvedBy is not `import` (matched by its name, in the module or
elsewhere). Its joins are pinned to start from the module's file node.
Sync reads it in sync_one before an edited module's old nodes go, since
the edges into them go with them, and sends those importers down the
whole-file lane after the import retry. An added module is the import
retry's (W5-01) and a removed one the removal lane's; both already
converge, as do default imports, whose module's surface moves.
The probes port upstream's fixture of ten importers and their modules:
added later, restored after a delete, given their exports by an edit,
and a binding its module never declares, each matching a fresh index of
the same files. The store test pins which statements count. The matrix
drops the ignore of typescript_aliased_named_import_of_an_added_file and
adds an export gained (TypeScript, and TSX through a tsconfig alias), an
export lost, and two namesakes the binding leaves once the module gains
the export. Without the lane, read after re-extraction, or with either
binding test weakened, the new cases fail. Fresh indexes are unchanged:
the 32 goldens are identical.
…ames appears W5-05 (owner 2026-10-11, the two gaps W0-04 found). An Erlang remote call is parked as `util::double/1` and a MyBatis include of another mapper's fragment as `app.CommonMapper::userColumns`, names no node carries. W5-05a's tail retry already reaches them by `double` and `userColumns`, but skips a tail more than 500 parked references share. A sync now also looks them up by the whole qualified name of every node whose candidate changed, on both sides of the change, and of a removed file's nodes. A node's id holds no qualified name, so a mapper namespace or an Erlang `-module` renamed in place keeps every id while the qualified names move; reading them from the candidate change catches that too. The lanes that look targets up by node name keep the bare names. The matrix drops the two ignores and adds the module added past the tail ceiling, the module and the mapper namespace renamed in place past it, and the module and the mapper deleted. Without the qualified names the three ceiling cases fail; taking them only from nodes whose id came or went, the two in-place cases do. Fresh indexes are unchanged: the 32 goldens are identical.
…d names W5-07 (found by W4-PHP-01's plan gate, introduced with W3-06's #2180). A call through a class name (`User.create()`) reaches the method the class inherits, walking at most five levels up the supertypes each declaration's head names, read from source; no sync lane recorded those heads, so a head changed in one file left such a call in an unchanged file at its old target, or unresolved, where a fresh index moves it. Every full index, migration and sync now keeps a `class_heads:` row per file declaring a type such a walk reads, with what each declaration's own head writes (class_heads::declaration_heads, which the walk's readers now share: the per-declaration Dart reader and the three-line head reader are factored out unchanged). A sync seeds the types whose entries came, went or changed; the edges to methods named like those of a seed's supertypes, old or new, at most five levels up re-resolve at their sites, cross-file and same-file, and the calls parked through a seed or a type at most four levels below it are retried at their sites through Store::unresolved_refs_by_receivers, however many there are. The class_heads_version marker follows the other lanes' protocol; an index without it re-resolves every such file once. The matrix drops the six W5-07 ignores and adds the base change in Ruby, Kotlin, Scala, Objective-C and Pascal, Dart and TypeScript calls the walk does not read, a deleted base, a middle class's base, a base two levels above the changed head, a call parked until the middle class's new base has the method, and a base pushed past five levels. Without the lane, its receiver retries, its same-file edges, its depths, its bootstrap or the migration's marker, the matching cases and watch and migration tests fail. Fresh indexes are unchanged: the 32 goldens are identical.
…pendencies, C# project files or Cargo manifests change
Resolution reads control files that are no source files: the JS/TS path
alias configs along their extends chains, the workspace manifests and the
folders a workspace glob lists, the package.json and Deno manifests above
each JavaScript-family file, the C# project files above each C# file, the
Cargo.toml files above each Rust file, and the frameworks it detects. A
sync re-resolved only the files it re-extracted, so a change to one of
those left every unchanged file on its old result.
Each reader now reports what it read or probed in the same traversal.
Every index, migration and sync records a control_inputs:{js,csharp,rust}
row of input digests and a frameworks_detected row. A sync re-reads a
kind's inputs on a CLI sync, or when the watcher hands it a recorded
input, a control file of the kind, or a folder coming into a workspace
glob's listing, or when a file of the kind came, went or changed
language. A kind whose inputs moved re-resolves every file of its
languages whole; a framework that came or went re-extracts every file.
The control_inputs_version marker is withdrawn and restored with the
others and read before the per-path pass; frameworks_version is withdrawn
only by a sync that re-extracts for a framework change. The watcher hands
control-file and recorded-input events to the sync though they are no
source files.
The Dart library model reads each package's name from the nearest pubspec.yaml and its path: dependencies from pubspec.yaml and pubspec_overrides.yaml, which are no source files the lane compared. A package renamed, a folder becoming a package or a path: dependency left unchanged files' imports where they were. Every index, migration and sync now keeps a dart_pubspec:<dir> row with both texts for each folder above a Dart file that holds one, and the dart_libraries_version marker moves to 2 so older indexes rebuild both kinds of row once. A sync re-reads the manifests on a CLI sync, when the watcher hands it a path named like one, or when a Dart file came or went; when they moved, the before and after library models each read their own state's manifests from a fixed snapshot, so the existing comparison re-resolves only what the change moved instead of every Dart file. The watcher hands manifest events to the sync, ignored or not.
…a name many times On the whole TypeScript checkout (39,330 files, tests included) a one-line edit made `codegraph sync` take 81.6 s against 4.3 s for v0.54.0, and a fresh index 422 s against 185 s. Name matching read the same nodes again for every candidate of a name and every class a supertype walk reached, and the test corpus repeats names thousands of times. Every change below keeps the graph as it was. - A candidate's enclosing function is looked up in its own file only, through a per-file index of its functions and methods kept for the pass, after a project-wide check that some function or method has the qualified name. - A call through a class name walks the supertypes once per language and class name: the walk is shared by every method name, taken one class further only when the visited part has no match, a supertype name met again adds nothing, the methods of a declaration's file are indexed by owner and name, and a declaration no method of the name belongs to is skipped. During a sync the class-head rows of files it neither rebuilds nor removes stand in for their source. - A local declaration's scope comes from the same per-file index; the SourceFacts memo is gone. - The LRU caches keep recency by timestamp (O(log n) instead of a linear queue) and are read with borrowed keys. - A name's candidates are kept for the pass, each with its scope and its object-literal owner read once; an object path's holders are grouped by path once per name; a class's file is read for a method only when it holds a method of that name. - Receiver inference reads cached lines and compiles a field's patterns only once the field is found; nodes are read with cached statements and column indexes. - A sync's retries by name read the parked references in one pass instead of four, and its pubspec check reads only the Dart file paths. Whole TypeScript checkout, release builds against v0.54.0, interleaved, first run dropped: single-file sync 1.114x at 32 threads and 1.116x at 4, init 0.89x and 0.68x, peak RSS 1.04x and 1.05x. Fresh indexes match the previous commit's canonical graph on 10 registry corpora, 29 downloaded corpora and the whole TypeScript checkout; goldens unchanged.
refresh_dependent_refs extracts every dependent file of a sync in parallel (each extraction reads only its own file and the project scope), then works out each file's replacement in the dependents' order, the first error in that order stopping it before anything is written, and writes every replacement in one IMMEDIATE transaction through the new Store::replace_resolution_states. A commit per dependent cost more than the writes themselves. Each replacement only touches its own file's edges and references, so one transaction gives what one per file gave; a store test compares the two.
resolve_lua_require scanned the whole file list for every require, and for every file built a `/suffix` string to compare. lua_scope::lua_require_matches now keeps, per resolution pass, each suffix's matching files in the file list's order, so every require of a module reads the same list once; the choice by the longest shared prefix with the requiring file is unchanged. A context test compares the memo with the scan for each suffix, twice.
retryable_failed_imports read every parked import and path reference as a whole row before choosing the few a sync retries. It now reads only each candidate's id, name and kind, borrowed from the row, buckets them under the same three keys and per-key ceiling, and then reads the chosen rows whole by id, in id order: the same rows in the same order.
After a sync, cmd_sync opened the index read-only twice, once for the file_complete diagnostic events and once for the extraction warnings, and each read-only open copies the whole database into memory. SyncOutcome now carries each changed path's file row as the sync left it (None for a removed file), read by the writer just before it returns, in an incremental sync and in a migration rebuild alike, and cmd_sync reports from those rows. The events' fields and order and the warnings are unchanged; an outcome without rows still falls back to opening the index.
…without re-extracting them A one-line edit to a C# file that many files reference re-extracted all of them in memory, only to recover the references at the sites that pointed into it. Schema 10 keeps those references instead: - `resolved_refs` has the columns of `unresolved_refs`, the id it had there, a cascade on the source node, and indexes on the site and the file path. Migration 10 creates it empty. - Resolution moves each row it takes out of `unresolved_refs` there, in the same transaction (an id listed twice moves once); re-extraction, site and file replacements, and node deletion clear both tables alike. So a file's rows in the two tables are every reference of its last extraction. - `resolved_refs_version` (`1:` and a digest of the extension overrides, max_file_size, the Godot DSL config, the extraction version and the detected frameworks) vouches for that. Only a full index writes it; a sync reads it before its first change, withdraws it at that change and writes it back only when it held at the start and the digest did not move, so a migrated index, a sync that failed partway and changed inputs fall back to extraction until the next full index. - While it holds, a sync re-resolves an unchanged dependent from its stored rows, read only at the refreshed sites unless the file goes whole; a site with no stored row sends the file whole, as before. `project.godot`, whose extraction reads every `.tscn` and `.gd.uid`, is always extracted. - A Current writer open runs pending migrations under its exclusive lease, so a schema-9 index of the current extraction version gains the table on its first incremental sync. Every golden corpus regenerates `schema.sql` and `colby.db` (and `reference/golden/colby.schema.sql`); the canonical JSON is unchanged. A schema-9 fixture built by a release build of 3878d52 drives the upgrade tests through `codegraph sync` and the watcher's sync.
… read-only
A read-only open without sidecars read the whole database into a Vec and
then copied it into the sqlite3_malloc64 buffer it hands to
sqlite3_deserialize, two copies of the index per open. It now allocates the
SQLite buffer at the file's length and reads the file straight into it; the
buffer is freed if the read fails before SQLite owns it. A file that grows
during the read fails it ("database changed while it was read") and one that
shrinks fails read_exact, where the old read could return a torn image. The
FREEONCLOSE | READONLY deserialization and the WAL header normalization are
unchanged; mmap is not used, since a checkpoint or auto-vacuum can shorten the
file under a mapping.
oxfmt wraps the provenance command as it wraps the v0.54.0 fixture's README.
Moving each resolved reference into resolved_refs with its own INSERT ... SELECT and DELETE made a full index measurably slower than before schema 10 (newtonsoft: 0.15 -> 0.43 s CPU in delete_resolved_unresolved_refs). The ids now move a chunk of at most 500 at a time, one INSERT OR IGNORE ... SELECT ... WHERE id IN (...) and one DELETE ... WHERE id IN (...) per chunk, in the same transaction: the same rows with the same ids, and an id listed twice, in one chunk or two, still moves once because its first move deleted the row. A store test moves 3,000 rows across chunks with repeats and compares both tables with the row-by-row result.
The control-input digest stood in a file's size and modification time for its text once it passed 64 MiB, so a tsconfig.json, package.json, C# project file or Cargo.toml that large, replaced with both kept, went unnoticed while its reader, which has no size limit, read the new text: a fresh index used the new aliases and the sync kept the old edges. The plan and the docs promise every input is read whole with no size or time shortcut; the digest now always is. A watch test replaces a 64 MiB tsconfig.json keeping its size and mtime and checks a full sync and a watcher's sync against a fresh index.
e3839db made the control-input digest read every file whole; the W5 goal review asked for the convergence matrix to hold that, as every sync change here adds a case. The harness gains Op::Replace, a rewrite that keeps the file's modification time, and the case replaces a 64 MiB + 1 byte tsconfig.json with one of the same length aliasing another folder: both drivers must match a fresh index. With the size and mtime shortcut restored, both drivers diverge.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Draft: the codegraph-rs v0.55.0 program. One pull request ports the colby
range
f4ddf508..b635dd46(v1.6.1 to main of 2026-10-07; v1.6.2 is its first180 commits), fixes the Rust-only defects that audit found, and clears every
carry-forward DEFER. The row-by-row ledger is
docs/upstream-sync/V1_6_2_AUDIT_2026-10-10.md. The pull request is squashedinto one commit and released by release-please as v0.55.0.
The branch lands in waves, and CI must pass at the end of each wave:
(
golden_reextract,scripts/regen-goldens.sh), a real-pipeline probeharness, a cross-language sync-versus-fresh matrix, 13 new golden corpora
(C#, Java, Swift, Objective-C, PHP, Vue, Svelte, Python bases, CommonJS,
routers, servers, mobile, synthesis),
bench --abandbench --graph-diff,and bounded steps in the viewer live-events test.
synthesis_inputs,idx_edges_synthesis_site), and the extraction version moves once, 21 → 22.A v0.54.0 index reads as outdated and is rebuilt once by
codegraph sync,a daemon's catch-up or the first MCP access.
upgrade_from_v054covers bothpaths with a real v0.54.0 index.
seams and per-language families, sync convergence, MCP/CLI/viewer, framework
resolvers and synthesis, VB.NET (trial),
codegraph context, Lua loaders,performance strategies, docs.
Owner decisions of 2026-10-10: squash merge; COBOL
OWNER-EXCLUDED; VB.NETthrough a revertible trial; refusing a newer schema split into a follow-up
pull request.
Verification (this push)
cargo test -p codegraph-rs --locked --test golden_reextract --test sync_convergence --test probes --test upgrade_from_v054and
cargo test -p codegraph-bench --locked --test equivalence: pass(33 corpora re-extract byte-identically; the matrix runs 49 cases and keeps 7
known gaps ignored until W5).
cargo test --workspace --locked --no-fail-fast: pass.make pre-ci) passed on this head.