kernel-r7b-ruby
504 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
f83fdb91c3 |
feat(kernel): R7b Ruby walker — ruby module, tree-sitter-ruby 0.23.1 bump, ref-flag wire slot, ruby default-routed
Third R7b port, checklist-first recipe (docs/design/ruby-kernel-port-checklist.md). Grammar bump first, validated standalone (the rust pattern): tree-sitter-ruby ^0.20.1 (tree-sitter-wasms, 2024-02) → v0.23.1 — crate pinned =0.23.1, wasm built from tag 71bd32f's checked-in parser.c/scanner.c (both sha-matched against the crates.io tarball; content bump, ABI stays 14). Old-vs-new full-init dumps: sinatra/jekyll byte-identical; rails = exactly the one classified hunk (the `recv&.!=` safe-nav operator misparse fix, `table_name.!` → `table_name.!=`, precision-positive). Walker (python.rs chassis + the six ruby divergences) preserves bug-for-bug: the importTypes:['call'] funnel (class-body DSL — attr_accessor, has_many, define_method incl. its block, sinatra route blocks — emits NOTHING at non-body scope), hook-handled module multiply-capture (nested modules re-scan their subtree per level after popping — `this.hooked` fn-refs from class AND module AND file), the sibling-scan visibility trio (bare `private` invisible; `private :sym`/`private def` poison all later defs; the inner def stays public), bare-call statements (do…end body_statement emits, brace-block block_body doesn't), `.new` instantiates with last-`::`-segment names, constant-receiver references refs, require/require_relative path refs (posix-normalized, `.rb`-suffixed, `Kernel.require` and interpolated-path quirks included), `=begin` docstring marker survival, and the reverse-order value-ref DFS. Wire v2: the hook's mixin `implements` refs carry `filePath: ctx.filePath` — the ONE extraction-ref denormalized field (php's trait-use refs share the shape). RefRow's first pad byte becomes a flags slot (REF_FLAG_FILE_PATH); decode re-attaches its own filePath parameter; KERNEL_ABI_VERSION 1→2 on both sides (mismatched dist/.node pairs degrade to wasm, as designed). Gates: sweeps 0-diff sinatra 147/147, jekyll 164/164, rails 3452/3452 (3,763 files, 0 deferrals — ruby error incidence 0.00%, any deferral = walker bug); full-init dumps byte-identical ×3 (7.2k/9.4k/375.6k lines); kernel-ruby-parity suite (torture + CRLF + wire-flag pin + defer) + ruby grammar-parity row; full suite 2,613 green ×2 under CODEGRAPH_KERNEL_EXPECT=1 (one unrelated mcp-initialize timing flake under parallel load, passes solo 3/3 ×3). DEFAULT_ROUTED += ruby (12 langs). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
286e9ccc2d |
feat(kernel): R7b C# walker — csharp module, tree-sitter-c-sharp 0.23.5 pin, csharp default-routed (#1378)
Second R7b port, checklist-first recipe (docs/design/csharp-kernel-port-checklist.md; parity passed FIRST RUN again). No grammar bump — the #717 vendored wasm verified table-identical to crate 0.23.5 (ABI 15, STATE_COUNT 8053, node-kind + field tables); first port with no grammar-prep step. The #237 #if-blanking preParse stays TS-side via the existing route-point hoist. Walker preserves bug-for-bug: the single-namespace-node quirks (second namespace nests under the first, nested namespaces leave no trace, import refs hang off the namespace node), raw member-access callee texts (this./base./literal receivers, multi-line fluent chains) with unconditional chain re-encode, deliberate emission holes (property accessor/expression bodies, ctor initializers, delegates/events/ operators/indexers/local functions, top-level locals), garbage extends refs ((repo) primary-ctor args, BaseDto(Name) record bases, enum : byte), the alias- import moduleName quirks, nameof-as-call, CSHARP fn-ref spec (+= subscription, this.X bare-name form, argument layer, initializer lists), C# type-ref engine (nested-generic returnType failure included), and value-ref shadow pruning. Gates: sweeps 0-diff serilog 211/216 / Newtonsoft.Json 914/945 / jellyfin 2104/2105 (deferrals match the survey's per-repo predictions — both-arm #if damage; default --max-deferral 0.1 holds, no c/cpp exemption); full-init dumps byte-identical ×3 (14.0k/109.1k/210.8k lines); kernel-csharp-parity suite (torture ×3 + CRLF variants + 8 micro-pins + defer) + csharp grammar-parity row; full suite 2,608 ×2 under CODEGRAPH_KERNEL_EXPECT=1. DEFAULT_ROUTED += csharp (11 langs). Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
f1ca991943 |
feat(kernel): R7b Rust walker — rustlang module, tree-sitter-rust 0.24.2 bump, rust default-routed (#1371)
First R7b language port. Grammar: tree-sitter-rust pinned =0.24.2 + wasm vendored from tag 77a3747 (parser.c/scanner.c sha-matched against the crates.io tarball), replacing the 2023 ABI-14 tree-sitter-wasms build — the bump alone is precision-positive on the wasm path (receiver-qualified instance-method resolutions replace ambiguous bare-name matches; node sections byte-identical on ripgrep/tokio). Walker mirrors the TS reference bug-for-bug per docs/design/rust-lang-kernel-port-checklist.md (survey artifact): dead-code isAsync, impl-pushes-no-scope, the impl-Trait-for-Generic<T> trait-receiver quirk, phantom const identifiers, use-binding triple emission, wildcard-use-emits-nothing, scoped-supertrait drop, chained-call re-encode gated on scoped_identifier, Rocket route macros body-only. Gates: parity sweeps 0 diffs — ripgrep 101/101, tokio 790/790, rust-analyzer 1217/1488 (271 deferrals are token-macro-table sources that error on BOTH arms — grammar-inherent); full-init dump-diffs byte-identical on all three (3,857 / 13,440 / 39,030 nodes); kernel-rustlang-parity suite (torture + CRLF + defer) in npm test; full suite green x2 with CODEGRAPH_KERNEL_EXPECT=1. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
ce0ae30e09 |
perf(store): resolution ref-index window — kernel-scale resolution 423→276s, 8c envelope ≈11min (§4d round 2) (#1369)
Store-architecture arc round 2. The batched resolution loop reads unresolved_refs ONLY through the status index + the PK keyset pager; the other five ref indexes (from_node, name, file_path, from_name, failed_tail) serve sync-time paths — yet every per-batch DELETE of resolved refs maintained all of them, the biggest single main-thread stage on the dubbo profile (deletes 1.2s of a 5.4s resolution phase) and 50-81s at kernel scale. beginBulkRefLoad/endBulkRefLoad on DatabaseConnection, threaded as refIndexLoad hooks next to the existing bulkEdgeLoad pair with the same minRefsForPool gate (small syncs never pay): drop the five for the loop, rebuild each in one scan at the end — where the table holds only the surviving FAILED refs (resolved rows are deleted by then), so the recreate is near-free. Crash inside the window heals on the next open (schema.sql re-applies CREATE INDEX IF NOT EXISTS). Measured: - dubbo: deletes 1.2 → 0.2s, marks 0.6 → 0.3s, recreate 219ms; wall ~8.5s flat — the freed main-lane time shifts into settle (the worker lane now binds the double-buffer at medium scale). - Linux kernel 8c: resolution 423.4 → 275.9s (deletes 50-81 → 3.2s, backpressure 16.8 → 7.4s — fewer index writes mean less WAL and cheaper folds), ref recreate 10.3s. Envelope ≈ 11.0min, from the 14.8min pre-arc best; <10min-on-8c now needs ~1 more minute. Gates: dubbo/gson dumps byte-identical; linux counts exact 2,049,153/6,413,518 and dump sha 6dd1185b… reproduced (10,446,478 lines); full suite green ×2 (153 files / 2588 tests). Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
f6d8e8fdab |
perf(store): parse-lane index deferral — dubbo fresh init −19%, kernel-scale envelope best-ever 14.2min (§4d round 1) (#1368)
Store-architecture arc round 1 (the cbm speed bar: dubbo warm wall 10.7-11.2s vs their ~7.5). §4d measured dubbo's parse-loop as 94% store-writer busy with B-tree maintenance as the floor (statement batching and sorted inserts already killed at ~zero). This applies the resolution phase's proven edge-index window to the whole parse lane: beginBulkParseLoad/endBulkParseLoad on DatabaseConnection — FRESH-INIT ONLY (incremental runs delete per-file rows through the file_path indexes) — drop all 15 nodes/unresolved_refs/files secondary indexes plus the 4 non-unique edge indexes for the parse phase's mass insert (the UNIQUE edge identity index stays: OR-IGNORE dedup conflicts on it, and its source prefix keeps mid-window reads indexed), then rebuild each in one table scan before resolution, with a yield between builds (the endBulkEdgeLoad watchdog rationale). A crash inside the window heals on the next open — schema.sql re-applies CREATE INDEX IF NOT EXISTS. Measured: - dubbo (cbm bar repo): parse-loop 4,306 → 1,787ms (−58%), rebuild 665ms, warm fresh-init wall 10.5-11.3 → 8.46-9.39s (−19%); the bar gap vs cbm shrinks from ~3s to ~1.1s. - Linux kernel 8c: envelope ≈ 14.2min, best ever (prior 14.8). Parse itself flat (linux parse is extraction-bound, not writer-bound) and the rebuild costs 21.6s — but every downstream phase dropped (resolution 517-589 → 423.4s, edge-recreate 36.5s, synthesis 157.1s, maintenance 16.3s): bulk-rebuilt B-trees are densely packed where incrementally-grown ones are fragmented, so every index-mediated read for the rest of the run pays fewer pages. Gates: dubbo/gson/express/excalidraw full dumps byte-identical (dubbo's canonical 441,270 lines reproduced); linux counts exact 2,049,153/6,413,518 and dump sha 6dd1185b… reproduced (10,446,478 lines); full suite green ×2 (153 files / 2588 tests). Incremental sync paths untouched by construction (freshDb gate). Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
69ea438bac |
perf(kernel): cFnPtr native extraction sweep — step 2, pass 230→151s across the arc (§7a.10) (#1365)
Task #5 step 2. The fuse-then-link refactor (#1364) left the extraction sweep as a clean per-file boundary: raw text in → collected facts out. This ports that sweep to the native kernel: `cfnptr_scan_files` (codegraph-kernel/src/cfnptr.rs) strips and scans a batch of 16 files per NAPI call, and the TS side only reads files, ships batches, interns the returned facts, and resolves include paths. The JS sweep remains as the fallback (no binary, feature detection against older binaries, CODEGRAPH_KERNEL=0, or CODEGRAPH_KERNEL_CFNPTR=0). Parity discipline: the JS regexes are the spec, so the scanners are hand-rolled byte machines reproducing that engine — ASCII \w/\b next to UNICODE \s (NBSP/U+2000-200A/FEFF decoded from UTF-8), alternation order, lastIndex resume, and the observable backtracking dimensions (INIT/ARRAY modifier and struct/star/bracket optionals, DISPATCH's greedy segment loop); greedy shortcuts only where backtracking provably can't rescue a match. The native stripper blanks per UTF-16 code unit, so its output is string-identical to the TS stripper — pinned by a new kernel arm on the strip differential oracle (fixtures + 500 seeded random cases). Gates, all green: new differential suite (adversarial fixture project — CRLF, NBSP, continuations, decoy strings, unterminated comments, backtracking shapes — indexed native-vs-JS: identical edge streams, plus a record-level scanner check); repo differential on git/redis/vim/SameBoy (identical, 705/852/433/180 edges); probe-hash on the live linux kernel DB reproduced f6e1713d… (279,335 rows); linux init counts exact 2,049,153/6,413,518; dump sha 6dd1185b… reproduced (10,446,478 lines); full suite green ×2 (153 files / 2588 tests). Measured (8c cg1212, quiet host): cFnPtr sub A=47.9s B=1.1 C=40.9 D=24.1 E=36.8 = 150.9s vs step 1's 179s and the pre-arc 230s (−34% cumulative); the sweep itself halved (94.5→47.9s, JS strips 132.4k→68.9k). callback-synthesis phase 199.9→171.1s. E's attributed wall grew from overlap shift under parallel synthesis; the phase total is the honest number. Full record: plan §7a.10. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
c6850d737b |
perf(resolution): cFnPtr fuse-then-link — one extraction sweep + filtered verbatim linking, pass −22% at kernel scale (#1364)
Task #5 step 1 (plan §7a.8/§7a.9). The C/C++ function-pointer dispatch synthesizer swept every file's text four times (typedefs, registrations, propagation, dispatch); at kernel scale the all-or-nothing source cache declines, so that was 4.4 read+strips per file — 78s of the ~230s pass. Now ONE extraction sweep reads+strips each file once and collects typedef names, struct-node field declarations (structurally parsed, fn-pointer classification deferred until the typedef sets are complete), resolved includes, an alias-shaped-macro name set, and per-file survival filters (init type tokens, array element types, inline-struct summaries, field-assign pairs, dispatch fields/array names — interned, a few MB on linux). The linking stages then replay the ORIGINAL pass bodies verbatim: struct layouts register in kind-scan order (same-name precedence is order-sensitive), and registration/propagation/dispatch run only for files their filter proves can have side effects, lazily re-stripping just those. Filters only over-approximate (full-file no-skip scans ⊇ the real passes' jump-cursor scans), and a filtered-out file is one where every match fails the pass's own gates before any side effect — parity by construction. Macro tables stay lazy: a sizing probe found 6.1M #define lines on linux (amdgpu register headers), ruling out retention. Measured (8c cg1212, quiet host, fresh kernel init): cFnPtr pass 230s → 179s (−22%); strips 283.5k → 132.4k (4.44 → 2.08/file, 78 → 46.6s); dispatch stage 95 → 18.5s; callback-synthesis phase 250 → 199.9s. Standalone probe on the live DB: 139 → 122s. Byte-parity gates, all green: probe-hash identical on the live kernel DB (279,335 edge rows both builds); git/redis/vim/SameBoy full dumps byte-identical old-vs-new (macro tables, commands.def, #ifdef include-units, inline structs, bare arrays exercised); kernel-parity 0-diffs on git/redis/fmt/protobuf, deferral unchanged; linux counts exact 2,049,153/6,413,518 and dump sha 6dd1185b… reproduced (10,446,478 lines); full suite green ×2 (152 files / 2563 tests). Step 2 (native per-file extractor) now has its boundary: the extraction sweep, raw text in → records out. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
971a5a0483 |
perf(resolution): worker connection recycling — WAL-depth writes-under-readers fix, superphase −11.4% at 8c (#1362)
The §7a.6 anomaly probed to its mechanism with five discriminating runs (§7a.7 table): main-thread B-tree writes triple under attached readers because READERS PIN WAL checkpoint progress — the deep WAL taxes every writer page operation (deletes 42.6s pool-off vs 118.8s pool-4 on identical hardware; an aggressive 64MB valve recovers the writes but overpays +129s in full-park folds; the v2 cache resurrection was falsified — long-tail name traffic is uncacheable at any capacity). Fix: workers close and reopen their read-only connections every 8 batches at the double-buffer's worker-idle boundary (ResolverPool.recycleWorkers + QueryBuilder.rebind + a cadence call). Reopens are sub-millisecond, resolver caches survive (only prepared statements re-prepare), and the existing checkpoints advance instead of parking. Failed recycle downgrades to sequential, same as a failed fan-out. Measured (8c pool-4, linux v7.2-rc2, cadence 25 → 8 iterated): resolution superphase 715.0 → 633.6s (−11.4%), envelope best 14.8min, recreate 59.7 → 45.3s. Byte-neutral everywhere: git dumps byte-identical old-vs-new, linux dump sha 6dd1185b reproduced (10,446,478 lines), counts 2,049,153/6,413,518, suite 2517 green. 2c unchanged by construction (no pool → no recycling). Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
b9d0f57a64 |
feat(extraction): C deferral round 2 — 8 new preParse passes, linux kernel/+mm/ deferral 58.6%→33.9% (#1353)
Census-driven cut of the top-ranked post-R7a lever. All passes TS-side, C-only (preParseCSource), shared by both arms: - parameterized-annotation whole-blank (__free/__printf/__counted_by/ __bpf_md_ptr…; extends through a stranded field `;`) - type-keyword-arg scanner (kzalloc_obj(struct T), list_entry, multi-line continuations behind nested-paren args; bounded hand scanner, head exclusions + call-vs-declaration guard; blanks trailing stars) - static/extern CAPS-macro declaration lines at any scope; the initialized form is REWRITTEN to its expansion (name/tail keep exact offsets) - va_arg qualified-type blank; GNU named-variadic #define dots-only blank (post-restore); sandwiched notrace-family; C23 auto; multi-line iterator-macro spans (hlist_for_each_entry_rcu + lockdep arg) - word list += cacheline family (2- and 4-underscore spellings) + 10 more census-confirmed annotations Gates: five-repo parity sweeps 0 diffs (git deferral 16.1→12.2%, redis 25.3→24.1%, fmt/protobuf unchanged); linux full-tree both arms 2,049,153 nodes / 6,413,518 edges (+858/+6,585 vs R7a) with byte-identical dumps (10,446,478 lines, sha256 6dd1185b); kernel-arm parse-loop 356→306s at 2c; suite 2517 green under CODEGRAPH_KERNEL_EXPECT=1. Honesty note recorded in the docs: error recovery was already salvaging most SYMBOLS on deferred files — the graph win is relationships + phantom cleanup, and the unreleased CHANGELOG entry was rewritten off the sweep-subset framing. Also records §7a.5: post-R7a 8-core cg1212 re-run 16.4min (was 18.3min); 8c parse sits on the single-writer floor, so the <10min-on-8c gap re-ranks to the per-ref resolution path. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
2d72891b59 |
feat(kernel): R7a C/C++ walker — dual-lang ccpp module, preParse hoist, 7 new blanks, c/cpp default-routed (#1346)
Parity: 0 diffs on redis/git/fmt/protobuf/ALS sweeps; full-init dumps byte-identical on all five + linux at kernel scale (10.4M dump lines, same sha256 both arms). Linux 2c/6GB envelope: kernel-arm 19.1min vs wasm-arm 22.9min (parse 356s vs 435s) on a much richer graph (the new blanks recover error-swallowed code: git 2x nodes, linux kernel/+mm/ 3x). Deferral guard corrected by measurement (C/C++ error incidence 9-42%; --max-deferral flag); defer-reuse memo kills the 3x re-blank/re-parse cost deferred files paid. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
44561b6aad |
feat(extraction): vendor current C/C++ grammars (R7a prep) — c v0.24.2 + cpp v0.23.4, sha-matched (#1345)
tree-sitter-wasms shipped 2023-era C/C++ grammars; the kernel walker must compile the crates.io versions, so production wasm upgrades FIRST and in isolation (the R2 pattern). Built with tree-sitter-cli 0.25.10 from each tag's CHECKED-IN parser.c: tree-sitter-c v0.24.2 (b780e47, parser.c f2883ff9), tree-sitter-cpp v0.23.4 (f41e1a0, parser.c 2a35a43b, scanner.c cf60387d) — sha-matched against the crates.io tarballs. .metal/.cu map to language 'cpp' so the dialects ride the same coherent grammar. Full suite green with the upgraded grammars (2,490; one unrelated daemon idle-timeout flake passed solo 9/9) — incl. the UE-macro, misparse-guard, Metal, and CUDA coverage. Checklist updated with the vendored revs and the metal/cuda routing correction. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
d510de4766 |
perf(synthesis): cFnPtrEdges 2.07x at kernel scale — probe-profiled, edge set hash-identical (#1341)
cFnPtrEdges was 86% of kernel-scale synthesis (306s, §7a.3). Standalone probe iterations against the live kernel DB attributed and fixed: - sliceLines split the ENTIRE file per node (~1.6M full-file splits across the B/D/E sweeps) — split once per file, slice from the line array (D 46.2->20.5s, E 94.5->69.1s). - The 128-entry strip cache re-stripped every file 4.4x across the four file sweeps (71.8s). Sizing is now memory-budget-aware and ALL-OR-NOTHING with 5% slack over the file count: a partial LRU on cyclic sweeps thrashes to ~0% hit (measured twice: cap ~61k and cap == files both thrashed against 63.8k + includes). Full-cache strips: exactly 1.0/file, 15.5s. - Pass D pre-gates field names on fieldToStructs before the two regex type resolutions — a->f = b->g matches every struct-field assignment in the tree, ~99% of them data fields. - recvTypeIn/varTypeIn compiled a new RegExp per call - cached per name. - Line numbers counted incrementally per match (ascending indexes) instead of splitting the body prefix per match; cursor rewinds between the two dispatch scans. - CODEGRAPH_SYNTH_TIMINGS now prints per-sweep walls + read/strip/nodes accounting for this pass. 278.8s -> 134.8s standalone at the 2c envelope. Output identity proven at full scale: probe edge set (merge-dedup applied, canonical sort) SHA256 21c2a971... == the original code's 274,762 edges extracted from the live kernel DB. Pass suite 15/15. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
7cc23668b5 |
perf(resolution): batch-loop de-quadratic — keyset reads, changes-based guard, DB-scaled valve caps + resolve profiler (#1339)
The §7a.2 per-ref profile overturned the assumption the whole arc was built on: resolveOne owns only ~93s of the kernel-scale ~433s batch loop. Loop-stage attribution (CODEGRAPH_RESOLVE_PROFILE, shipped here) named the rest: backpressure folds 111.2s, count guard 93.9s, batch reads 54.6s, deletes/inserts/marks ~84s, settle 85.7s. - Non-progress guard O(remaining)→O(1): the per-batch COUNT(*) walked every remaining pending row (O(N²/batch) per run, 93.9s). The cleanup queries now return summed SQLite , and zero-removals-from-claimed-work is the guard signal — the DIRECT evidence the count diff inferred (a mismatched-name resolver makes keyed cleanup no-op ⇒ changes=0). A real COUNT runs only on that suspicious path and arbitrates exactly as before. - Batch reads OFFSET→keyset (54.6s→O(batch)): OFFSET re-walked the accumulated failed-row prefix every read; seeking past the last-seen rowid is prefix-independent and enumeration-order identical. - WAL valve caps scale with DB size (env still wins): every fold re-writes hot pages (#1231 in bounded form — 111.2s at the flat 256MB cap); soft=clamp(dbSize/4, 256MB, 2GB) trades ~4× fewer folds for a transient WAL ≈ project size. - CODEGRAPH_RESOLVE_PROFILE: per-outcome resolveOne histogram + loop-stage attribution, main + workers, off by default. Gates: dubbo dump byte-identical; suite 2,491 passed / 4 skipped (kernel required). Kernel-scale payoff run lands in the plan doc next. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
2adc7f60c0 |
fix(db): WAL truncate at parked barriers ONLY — the timer-path truncate loses the race it was assumed to lose (#1336)
A truncate checkpoint started against an ACTIVE writer wins the lock and then blocks that writer for its entire backfill; after the edge-index recreate's multi-GB single-transaction burst that exceeds the writer's 5s busy_timeout and fails the index with 'database is locked' (§7a.2 record run, EXIT=1 at kernel scale — the small mid-resolution truncates folded in ms and masked the hazard). Barrier truncates (backpressure/foldNow) are collision-free by construction: the writer is awaiting the valve. Dubbo gate: exit 0, peak 81MB (barrier folds carry containment), dump byte-identical. Valve + sizing suites 27/27. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
ca88d3bd15 |
fix(db,resolution): WAL file cap + cgroup cache credit + pool/parse sizing corrections from the instrumented kernel-scale runs (#1335)
Four §7a.1 instrumented-run findings, each measured: 1. File-size trigger + truncate-at-barrier: a fully-backfilled WAL still grows the FILE without bound — the writer only restarts at frame 0 when a commit finds zero reader marks, which the instrumented run showed never happens (file marched 361→721MB through two COMPLETE backfills; 22GB by phase end). backpressure() now also trips at 4× the soft cap on raw file size and TRUNCATEs at the parked barrier; the timer path truncates opportunistically after complete backfills. Dubbo peak: 251MB → 69MB at the same 16MB valve; dumps byte-identical under aggressive folding. 2. cgroup memory credit: memory.current counts reclaimable page cache — a post-parse container read 57MB of headroom on a 6GB box and silently disabled the pool. inactive_file is credited back (the docker-stats working-set convention); the same run now reads a sane 4.4GB budget. 3. Pool at 2 cores reversed: sequential resolution measured FASTER than pooled-6-on-2 at kernel scale (853s vs 1,150s), and synthesis is Amdahl-bound by cFnPtrEdges (306s of 358s) so pooling it bought nothing. cpuCap = min(ap−1, 6), no floor: ap=2 → sequential is the fast path. 4. Parse floor of 2: one parse worker at a 2-cpuset measured 34% slower (493s vs 369s) — main + store-worker don't fill the second core. Floor restores the baseline (373.5s measured). Plus the observability §7a.1 burned three 25-minute cycles for: valve armed/fire/timer-pass/heartbeat lines, checkpoint-worker error capture, pool sizing decisions (incl. the disabled path), backpressure-hook presence — all behind CODEGRAPH_SYNTH_TIMINGS / CODEGRAPH_WAL_VALVE_DEBUG. Suite: 2,490 passed / 4 skipped (kernel required). Kernel-scale record runs with this build follow in the migration plan §7a.1. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
8c1e821495 |
fix(db): WAL valve — TRUNCATE at parked barriers, futility latch, CODEGRAPH_WAL_VALVE_DEBUG (#1334)
Three §7a.1 run-1 lessons (kernel-scale 2c/6GB: EXIT=137, WAL 22.2GB with the backpressure hook DEPLOYED): 1. TRUNCATE at parked barriers: a completed passive backfill bounds the un-checkpointed backlog but the FILE only stops growing when a commit finds zero readers holding WAL marks — rare while pool workers cycle (dubbo debug baseline: file climbed monotonically through six completed pass-1 backfills). At a parked barrier the no-reader window is guaranteed, so chop the file there with wal_checkpoint(TRUNCATE) (off-thread, 2s busy_timeout — a racing reader degrades it to a no-op). 2. Futility latch: when backfill gives up (pinned reader), parking again at every over-cap boundary burns a 20-pass checkpoint attempt — each a worker thread + fresh connection against a multi-GB DB — per batch. Two consecutive give-ups now disable parking for 60s; a pinned phase degrades to pre-valve behavior instead of OOM-amplifying. 3. CODEGRAPH_WAL_VALVE_DEBUG=1 surfaces valve decisions without the caller's verbose plumbing, and give-up lines print under CODEGRAPH_SYNTH_TIMINGS — run 1 failed silently because give-ups were verbose-gated. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
b8833fec57 |
feat(resolution): memory-aware, cgroup-honest worker-pool sizing + CODEGRAPH_RESOLVE_WORKERS (#1333)
Pool sizing used os.cpus().length, which enumerates the HOST's CPUs: inside a 2-CPU cpuset it sized 6 resolver workers (the §7a.1 false-'sequential' premise) and 8 parse workers, and at true 8-core concurrency six ~1GB workers OOM-killed a 7GB container (oom_kill=5) mid-synthesis — sizing had no memory term and no override knob. resolvePoolSize (pure, matrix-tested): explicit CODEGRAPH_RESOLVE_WORKERS override (0 disables, cap 16); CPU term max(2, min(availableParallelism-1, 6)) — cpuset-honest, floored at 2 so true 2-core boxes keep pooled synthesis's ~2×; memory term floor(budget*0.7 / clamp(0.2*dbSize, 256MB, 1.5GB)) with budget = min(freemem, cgroup v2/v1 headroom). Parse pool's core input switches to availableParallelism. Dev machines are unchanged (still 6 workers); the 8c/7GB kernel-scale container now sizes 4. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
6e52295ceb |
fix(resolution): WAL containment for the pooled superphase — writer backpressure at pool-idle boundaries (#1332)
At kernel scale the pooled resolution/synthesis superphase grew a 22GB WAL on a 4.6GB DB (cg1212, §7a.1): autocheckpointing is deferred for the run, and the valve's timer-driven passive checkpoints stay perpetually partial against the pool's continuous reads — no mechanism ever completed a backfill, so the WAL accreted the whole phase's write volume, blowing disk and feeding page-cache pressure into the 8-core/7GB container OOM. The valve's writer-side backpressure() hard-cap backstop existed but was wired only into the PARSE orchestrator. Thread it into the resolution batch loop at the double-buffer's one pool-idle boundary (batch settled, next not yet fanned out), after the edge-index recreate, and through the synthesis insert loops. Parked there, the backfill completes; readers re-enter at SQLite's backfilled mark and the next persist commit wraps the WAL. Dubbo validation, same build: valve@16MB peak WAL 251MB (floor = the single-transaction edge-index recreate) vs defaults 914MB; dumps byte-identical (441,270 rows); wall unchanged (11s). Suite 2,479 green. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
c2503e2bee |
feat(kernel): R5 — Python and Go ports, gates passed, default-on
Python (codegraph-kernel/src/python.rs) and Go (src/go.rs) join the native kernel, mirroring the wasm extractors bug-for-bug. Python: decorated_definition docstrings/decorators (decorates refs only for bare-identifier decorators — the call-kind quirk), function-in-class → method, module-level assignments always extract as variable, from-import per-name binding refs, self.x fn-ref candidates as bare names. Go: receiver methods with Recv::name qualified names + contains edges to the first earlier struct of that name, type_spec struct/interface classification with embedding→extends and interface method nodes, composite-literal instantiates keeping the package qualifier, top-level var/const initializer walks attributed to the declared symbol (#693), 2-hop field chains (#1276), New().Method() re-encode (#645/#608), and the GO_SPEC fn-ref layers. Grammars: tree-sitter-python 0.23.6 + tree-sitter-go 0.23.4 crates, with wasm vendored from the same tags (parser.c sha-matched) — both were 2023-era in tree-sitter-wasms. Gates: extraction sweeps 100% (flask 83/83, django 3,035/3,038 +3 error-file deferrals, gin 99/99, prometheus 978/979 +1); full-init dump-diffs byte-identical on flask (10,833 rows), gin (17,540), django (360,794), and prometheus (213,758); torture fixtures enforced in npm test. DEFAULT_ROUTED now covers typescript/tsx/javascript/jsx/java/ python/go. Full suite: 2,471 tests pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
28068fa0f1 |
perf(kernel): direct-to-store decode — buffers flow to the store worker, main thread never materializes nodes
Kernel-routed files ship their flat tables from the parse worker to the store worker as buffers (tryKernelExtractRaw → kernelBuffers on the result → KernelStoreBundle); the store worker decodes and finalizes (finalizeStoreBundle shared with the object path so filter semantics can never drift). Files with applicable framework extract() hooks keep the decoded path; the no-writer fallback materializes via materializeKernelResult. Byte-identical dumps re-verified on dubbo, excalidraw, express, gson; full suite green (2,467). Measurement (plan §4d): dubbo's parse-loop wall is 94% store-writer busy time — the many-core fresh-index wall is single-writer SQLite ingest, not extraction or main-thread work. d2s improves the writer lane ~11% (structured-clone deserialization avoided on the writer) and frees the main thread; the remaining many-core gap is a store-architecture arc (deferred index builds, multi-file transactions, buffer→bind), out of the kernel project's scope. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
03d54e47a1 |
feat(kernel): R4 — Java port with Lombok synthesis, gate passed, default-on
Java joins the native kernel (codegraph-kernel/src/java.rs), mirroring the wasm extractor's Java paths bug-for-bug: package namespaces, imports, javadoc, annotations→decorates, type_list inheritance, static-final constants, enum constants, anonymous classes (including the TS side's 0-based-line quirk on the extends ref), method_invocation calls with the this.field unwrap and the Foo.getInstance().bar() chain encoding (#645/#608), static-member value reads, method-reference fn-refs (#756), value-reference edges, and the full Lombok member synthesizer (#912: Getter/Setter/Data/Value/Builder/ToString/ EqualsAndHashCode/Slf4j-family with taken-member dedup). The shared docstring/textutil modules moved to crate level. Grammar: tree-sitter-java 0.23.5, with the wasm grammar vendored from the same tag (parser.c sha-matched) replacing tree-sitter-wasms' 2023-era build. Gate (plan §4c): extraction sweeps 100% — gson 262/262, retrofit 341/341, dubbo 4,048/4,048 — plus a Java torture fixture in npm test; full-init dump-diffs byte-identical on gson (49,766 rows), retrofit (62,735), and dubbo (441,266 rows); all R2/R3 repos re-verified; Linux container runs all 23 kernel tests green under CODEGRAPH_KERNEL_EXPECT=1. The gate caught a real cross-language bug: fn-ref dedupe and value-ref self-target checks must compare node ID STRINGS, not node-table rows — ids collide for same-(kind, name, line) nodes, which minified one-line bundles hit routinely (retrofit's website JS exposed it; latent in the TS/JS walker since R2, never released). Fixed in both walkers. Benchmark honesty: dubbo fresh-init on an 11-core Mac is ~flat (parse-loop wall 5,020→4,394ms; total ~11.3s both arms) because that wall is main-thread-bound (reads + store), not worker-CPU-bound — the §6 expectation assumed otherwise. Where worker CPU binds the kernel delivers: dubbo on a 2-CPU/6GB container drops 27.8-28.6s → 22.3-22.8s (~1.25×). The identified lever for the many-core headline is decoding kernel buffers directly into store rows (skipping per-node JS object materialization); the buffer contract already carries everything. DEFAULT_ROUTED now includes java. Full suite: 2,467 tests pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
c8cca9a601 |
feat(kernel): R3 — TS/JS equivalence gate passed, kernel default-on
Gate evidence (docs/design/rust-kernel-migration-plan.md §4b):
- Graph parity, byte-identical (stronger than the §5 ≤0.5% bar): full
codegraph-init dump-diffs kernel-vs-wasm on express (13,712 rows),
excalidraw (89,898), and vscode (2,378,238 rows) — identical bytes.
Python control repo (flask) identical + timing unchanged. The parity
harness is now ORDER-sensitive (emission order drives rowids, which
drive resolution order) and dumps come from the new
scripts/dump-graph.mjs (natural keys, no rowids/timestamps).
- The one real find, caught by the vscode tier: tree-sitter error
RECOVERY is encoding-dependent — byte-identical grammar sources and
the same core (0.25.10) recover erroring files differently under
UTF-8 (native) vs UTF-16 (web-tree-sitter) parsing; proven by
reproducing the wasm tree with a native UTF-16 parse. Policy: the
kernel defers any file whose tree has_error() to the wasm extractor
(silent 'defer:' signal, per file) — parity by construction on
erroring files (incidence 0-0.42% across the gate repos), and the
harness fails if deferrals exceed 10% so a broken kernel can't hide
behind the fallback.
- Retrieval invariants: canonical excalidraw flow (mutateElement →
renderStaticScene) connects end-to-end on the kernel-indexed graph;
synthesized-edge families present. Agent A/B is vacuous under
byte-identical DBs (same justification as #1320-#1322).
- Perf: vscode init 105.4s → 82.1s (1.28×) on an 11-core Mac;
excalidraw on a 2-CPU/6GB Linux container (the CI-runner envelope)
6.2-7.1s → 4.3-4.8s (~1.5×). Linux arm64 in-container build: all 22
kernel tests green under CODEGRAPH_KERNEL_EXPECT=1. Windows VM leg
deferred (VM stopped; prlctl start needs Parallels Pro) — benign: a
missing .node falls back to wasm, and the release matrix builds and
gates the win32 prebuilds.
- Full suite: 2,465 tests pass WITH default-on routing, so the entire
extraction corpus now exercises the kernel for TS/JS wherever a
.node is staged.
DEFAULT_ROUTED = {typescript, tsx, javascript, jsx}. Override:
CODEGRAPH_KERNEL_LANGS (replaces the set) / CODEGRAPH_KERNEL=0 (kill).
Changelog entry added under [Unreleased].
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
9ad5cd7ba2 |
feat(kernel): R2 — full TypeScript/JavaScript extraction port, byte-parity with the wasm path
Replaces the R1 seed .scm emitter with a bespoke Rust walker (codegraph-kernel/src/tsjs/) that mirrors TreeSitterExtractor's TS/JS paths function-for-function: declarations (incl. #808 field/property classification), qualified names, docstrings (#780 wrapper climbs), signatures, imports/re-exports + per-binding refs, calls with receiver-qualified callees (#1230 literal-receiver skip), instantiations, decorators, inheritance, type annotations (#381), type-alias members + tuple contracts (#359/#634), React component recognition (#841 forwardRef/memo/styled), object-of-functions / zustand-through-middleware / RTK Query endpoints + generated hooks / vuex + pinia store shapes, function-as-value capture with the flush gate (#756), and value-reference edges with the shadow prune (#895/#897). The generic query emitter is deleted — extraction parity needs logic .scm can't express; future languages get walkers too (migration plan §4a). Positions and JS string-slice semantics are emitted in UTF-16 code units natively, so kernel output is byte-identical to web-tree-sitter's — no column diff class exists. Parity evidence (macOS): scripts/kernel-parity.mjs (full-object multiset diff per file) — this repo 353/353 files, excalidraw 643/643 (10,650 nodes / 10,726 edges / 68,307 refs), plus torture fixtures checked into __tests__/fixtures/kernel-parity/ and enforced in npm test by kernel-tsjs-parity.test.ts. The strict compare caught one real decoder bug the loose harness missed: refs must NOT carry denormalized filePath/language at the extractFromSource seam (the store fills them). Perf: extraction 2.6× single-thread on excalidraw (487ms vs 1,255ms, identical outputs). Routing stays opt-in (CODEGRAPH_KERNEL_LANGS) until the R3 equivalence gate (large repo, DB dump-diff, retrieval invariants, agent A/B, Linux/Windows) passes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
c5eebe6beb |
feat(kernel): R1 scaffold — napi-rs extraction kernel, buffer contract, routing + fallback, grammar-parity CI
Phase 0 of the Rust extraction-kernel migration (docs/design/ rust-kernel-migration-plan.md, now checked in with §3a recording the shipped state): - codegraph-kernel/ napi-rs crate: extractFile(path, content, language) → five flat buffers (meta/nodes/edges/refs/arena), one JS boundary crossing per file. Node ids computed Rust-side, byte-identical to generateNodeId (pinned by test vector). Reserved per-node metrics slot for the Arc 3.2 code-metrics work. - Generic .scm-driven emitter (@def.<kind>/@name/@ref.<kind> captures, byte-range scope stack → ::-joined qualified names, contains edges, refs attributed to the innermost enclosing symbol). Seed TS/JS queries are smoke-level; R2 replaces them with the full port. - Routing seam in extractFromSource with per-file wasm fallback. DEFAULT_ROUTED is empty — no behavior change until a language passes its equivalence gate (R3). Dev opt-in: CODEGRAPH_KERNEL_LANGS. Kill switch: CODEGRAPH_KERNEL=0. Loader verifies ABI + kind tables before routing; EDGE_KINDS became a runtime array because kind order is now wire contract. - Grammar-source parity: vendored TS/TSX/JS wasm grammars built from the exact crate revisions (tree-sitter-typescript v0.23.2, tree-sitter-javascript v0.25.0, checked-in parser.c, ts-cli 0.25.10) — the tree-sitter-wasms builds were 2023-era, which the new kernel-grammar-parity test caught on day one. Production TS/JS parsing gets 2.5 years of grammar fixes; full suite green (2456 tests). - Build/release wiring: scripts/build-kernel.sh + npm run build:kernel; release.yml kernel prebuild matrix (continue-on-error — the kernel is optional everywhere, bundles fall back to the wasm path); bundles stage lib/kernel/codegraph-kernel.node; release job runs the kernel suites with CODEGRAPH_KERNEL_EXPECT=1. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
4efc6c70e2 |
fix(scale): kernel-scale hardening — OOM-safe pass skipping + watchdog-safe index recreate (#1323)
Two hazards found by running today's full stack against the Linux kernel (70,129 files) in the cg1212 repro container: 1. The parallel-synthesis fallback retried a worker-failed pass on the MAIN thread. At multi-million-node scale a worker failure is usually a memory ceiling, so the retry would OOM the process and take the whole index with it. Above 1.5M nodes a failed pass is now skipped with a clear stderr message (its synthesized edges are absent; the index completes). Below that, the main-thread retry stays — small-scale worker crashes are transient and the retry keeps coverage. 2. endBulkEdgeLoad rebuilt all four edge indexes in one synchronous span — measured 79s at kernel scale, past the #850 liveness watchdog's 60s stall window. A daemon-triggered re-index would have been SIGKILLed right after doing the work. Now async with an event-loop yield between builds, keeping each stall to a single index (~20s at kernel scale). Validation: full Linux kernel index to completion in the repro container — 2,048,674 nodes / 6,405,964 edges, EXIT 0, zero passes skipped, on a 2-CPU VM (worst case: pool disabled, sequential resolution + synthesis) in ~27min. Phase walls: parse 6.0m, resolution 19.5m (incl. synthesis 6.3m, recreate 79s), maintenance 74s. Suite green (2444). Also adds docs/design/native-extraction-kernel.md — the spike-validated design for the native extraction kernel (Rust parse+walk over dubbo's Java: 202ms rayon / 1.07s single-thread vs 4.7s for the current wasm pipeline). Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
567b4ad4be |
perf(resolution): drop non-unique edge indexes during the bulk resolution window, byte-identical graphs (#1322)
The resolution persist's measured cost is B-tree maintenance on the edges table's five indexes (offline replay of a 224k-edge resolution set: 2.8s with all indexes, 1.1s with only the unique identity index, +0.3s to recreate the rest). On big runs (same >=150k-ref gate as the resolver pool) the four non-unique edge indexes are now dropped for the batch loop and recreated in one pass each before synthesis. Why this is safe: - idx_edges_identity stays: INSERT OR IGNORE's dedup conflicts on it (#1034), and its leftmost column is `source`, so the only mid-window edge reads — resolution's supertype walks (implements/extends by source) — keep an index via its prefix (verified with EXPLAIN QUERY PLAN). - The window closes BEFORE synthesis, whose passes read kind-keyed, and on every error path (finally). - A crash inside the window heals on the next DatabaseConnection open — schema.sql re-applies CREATE INDEX IF NOT EXISTS, same recovery as the FTS bulk-load pattern this mirrors. - Concurrent readers (a daemon serving the project mid-index) stay correct; target/kind-keyed reads degrade to scans only for the window's duration. dubbo (4,402 files): persists 4.0s -> 3.0s, fresh init 11.9s -> ~11.1s, graph byte-identical. excalidraw (below the gate): untouched, byte-identical. Recreation cost ~250ms, logged under CODEGRAPH_SYNTH_TIMINGS as edge-index-recreate. Suite green (2444). Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
cf38ef65af |
perf(synthesis): fan dynamic-dispatch passes across the resolver pool, byte-identical graphs (#1321)
The ~36 independent synthesis passes (callback/event/framework wiring) ran sequentially on the indexer's main thread — 2.0s of a 4,402-file Java repo's index, and the stage where kernel-class repos die (#1212). They now live in an explicit registry (SYNTH_PASSES) and, when the resolver pool is alive (>=150k-ref repos), fan out across its read-only workers: dubbo synthesis 2,024ms -> ~900ms (-55%), total fresh init 13.5s -> 11.9s. Graphs verified byte-for-byte identical on both the pool path (dubbo) and the sequential path (excalidraw). Why this is safe: no pass's edges persist until the ordered merge, so every pass sees the same committed post-resolution DB state in either mode, and results merge in registry order regardless of completion order — the first-seen dedup is unchanged. The pool now survives through synthesis (destroy moved after it) instead of being torn down moments before the one stage that could reuse it. Robustness: a pass that fails on a worker (crash, OOM) is retried on the main thread — a synthesizer blow-up now costs one worker instead of the whole index, which is half the #1212 story on very large repos. Also: ref-row cleanup deletes now run as one transaction with a cached statement instead of one implicit commit per 500-row chunk (mechanically fewer WAL commits; matters most on HDD-class storage). A set-based rewrite of failed-ref parking was tried, measured ~zero on NVMe, and dropped — the remaining persist cost is edge-index B-tree maintenance, not statement dispatch. SYNTH_PROGRESS_STEPS now derives from the registry (passes + fixed marks); the pin test counts registry entries plus literal __mark sites. Suite green (2444). Sequential-path timing unchanged on excalidraw. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
a2f3c31a97 |
perf(resolution): defer checkpoints and double-buffer persist during resolution, byte-identical graphs (#1320)
Fresh init on a 4,402-file Java repo (dubbo): 18.2s -> 13.5s (-26%), with the resolution phase going 12.6s -> 7.9s (-38%). Graphs verified byte-identical on both the pool path (dubbo) and the sequential path (excalidraw). Two changes: 1. The fastInit+pool path restored WAL for the resolver workers but left wal_autocheckpoint at its default, so the persist loop inline-checkpointed hot pages all phase long (#1231's pathology inside resolution — measured at 58% of resolution wall). Checkpointing is now deferred behind the bounded valve and folded once at maintenance, mirroring the deferWal path. 2. The resolution loop is double-buffered: batch k+1 is prefetched (OFFSET past batch k's still-pending rows, under an explicit ORDER BY rowid) and fanned out across the pool while batch k's ref cleanup runs on the main thread. Batch settle-waits dropped 2572ms -> 117ms. Correctness invariant found by the byte-identical gate and now documented in the loop: batch k+1's resolution READS batch k's edges (resolveMethodOnType walks supertype chains over extends/implements edges that resolution itself inserts), so edges must persist BEFORE the next batch fans out; only the ref cleanup overlaps. Also extends the CODEGRAPH_SYNTH_TIMINGS instrumentation with phase labels (grammar-init, parse-loop, fts-rebuild, resolver-reinit, resolution, callback-synthesis) and pool timings (worker open/resolve, per-batch mode, persist), so the next profile is one env var away. Suite green (2444). Sync path timings unchanged. Pool floor re-validated: forced-on at 40k refs is still net-slower, so the 150k threshold stands. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
1de7e8f8b5 |
fix(retrieval): multi-hump field-name queries reach their definers (#1319)
Three compounding defects (#1196) made a query bag of object-literal keys (`profileInfo isTrialEligible quotaInfo billingMethod`) return unrelated results while the defining files never surfaced: 1. Step 5b title-cased interior humps (profileInfo -> Profileinfo) and then compared case-SENSITIVELY, dropping every row SQLite's case-insensitive LIKE had just recovered. The hump lookup is now case-insensitive with an explicit uppercase-at-match requirement. 2. Step 5b/5c's kind whitelist held only type-like kinds — dead code on method-centric codebases. Callable kinds (function/method/component) are fetched as a SEPARATE LIKE batch so hot single-word terms can't crowd classes out of the length-ordered 200-row batch. 3. explore's named-symbol seeding was exact-name only; a field token seeded nothing. A camelCase token with ZERO exact defs now seeds its camel-infix definers (callables, hump-boundary or prefix, shortest first, capped at 3) — bare lowercase words keep the #1252 stopword guard untouched. The reporter's acceptance query is a pinned e2e test (definer files present, exact-name seeding unaffected). excalidraw probe: the canonical flow query (mutateElement renderStaticScene) is byte- identical; NL queries shift toward more-central callables (useUIAppState/getDefaultAppState over observer periphery). Fixes #1196 Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
a5a8942d1c |
fix(scan): includeIgnored child patterns revive repos under a gitignored parent (#1318)
`.gitignore: /repos/` lists `repos/` as ONE ignored entry, while the CLI hint (#1156) suggests `includeIgnored: ["repos/a/", "repos/b/"]` — the child spelling. findIgnoredEmbeddedRepos tested the opt-in matcher against the PARENT path only, which a child pattern never matches, so the documented opt-in silently indexed nothing and init looped the byte-identical suggestion back at the user (#1295). Ignored dirs that don't match as a whole are now descended (the walk was already bounded: depth 4 / 2000 entries, and only runs when includeIgnored is configured) and each nested repo root is matched individually — parent spelling opts in everything under the dir, child spelling exactly the named repos. findUnindexedIgnoredRepos gets the same per-repo check so the hint stops nagging about repos that are already configured while still naming unopted siblings. Fixes #1295 Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
c472cfb52e |
fix(resolution): literal-receiver builtins and nested locals stop fabricating call edges (#1317)
", ".join(sorted(x)) resolved by bare name to a project function named join — one nested inside a DIFFERENT function, so scope alone rules the edge out. Both defects from #1230, fixed independently: 1. Extraction: a member call on a LITERAL receiver (string, number, collection, regex — across grammars) emits no call ref at all. A literal's methods are the language's builtins, never project symbols; the bare-name fallback let them exact-match any same-named project function. Silent miss, never a wrong edge. 2. Resolution: matchByExactName filters out candidates nested inside a same-file FUNCTION container unless the ref originates within that container's line range. Class members (parent is a class-like node), top-level symbols, and C++ namespace prefixes (no parent node) are untouched. requests re-index: byte-identical (813 calls edges). excalidraw: -27 edges, all literal-receiver refs by construction. The issue's repro is pinned: join has exactly one caller (format_fields), report_missing has zero project callees. Fixes #1230 Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
41c2029798 |
fix(go): field-chain calls resolve via validated type inference, never bare-name guessing (#1316)
target.conn.Exec("insert") with `conn *sql.DB` emitted a BARE `Exec`
ref (the receiver chain was dropped for non-identifier receivers), and
exact-match then bound it to the only local `Exec` — an unrelated
interface's method — fabricating an internal dependency (#1276).
Extraction now keeps Go 2-hop selector chains (`base.field.Method`),
and a dedicated matcher resolves them EXCLUSIVELY via two inference
hops: base's type from the enclosing scope (#1108 machinery), field's
declared type from the struct's own declaration lines (comment-
stripped, per-line — chi's "the tree router" doc comment otherwise
donates a phantom type). resolveMethodOnType validates the target.
Package-qualified field types are followed only when the package is
in-module — `handler http.Handler` must not bind a same-named local
decoy. Failure at any hop leaves the ref unresolved: chained Go
receivers never fall through to the bare-name strategies (they were
never emitted before, so no prior recall depends on that path).
chi before/after: node count stable (1,181); 8 correct field-chain
edges gained (mx.tree.FindRoute/InsertRoute/routes, validated,
including the unexported `node` type); the removed edges are the
prior bare-name guesses on external receivers.
Fixes #1276
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
2ec877b08c |
fix(resolution): calls through an imported singleton resolve to the method (#1315)
reproStore.notifyJoinGuildStatus() after `import { reproStore }`
resolved its calls edge to the exported CONSTANT (resolvedBy:'import'),
while the identical same-file call resolved to the method via
local-variable receiver inference (#1108) — so `callers <method>`
missed every cross-file use and a widely-used method could look
unused (#1292).
resolveViaImport's member-descend now handles imported VALUES alongside
the #825 static-member case: when the base resolves to a
constant/variable, the value's type is inferred from ITS OWN
declaration lines in the exporting file (the shared #1108 pattern
table: `= new T(...)` initializers and type annotations) and the member
is resolved AND VALIDATED on that type via resolveMethodOnType. A
failed inference or validation keeps the existing constant edge —
never a fabricated one. Calls only; plain member reads still reference
the value.
excalidraw control: byte-identical graph (10,653 nodes / 19,483 calls
edges before and after).
Fixes #1292
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
ce983a08fe |
fix(cli): node <symbol> -f <file> includes the source body (#1314)
The CLI's bare-symbol branch passes includeCode=true to the codegraph_node handler, but the symbol-pinned-to-file branch didn't — so exactly when a user disambiguated an overloaded name to one file (the point of -f), they got Location + trail with no code (#1284). Fixes #1284 Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
8dcf92f285 |
fix(watch): schedule a sync when a directory is deleted (#1313)
A directory deletion arrives as ONE event on the directory's own path. That path has no source extension, so handleChange dropped it at the isSourceFile gate before ever scheduling a sync — and the files inside may never get events of their own (Windows's recursive watcher reports only the top-most removed entry; FSEvents can coalesce a tree deletion the same way). Every child record then sat stale in the index until an unrelated edit happened to trigger a sync (#1285). A non-source path that no longer EXISTS on disk now schedules the debounced sync; the sync's scan-diff removes whatever vanished (already correct — verified: manual `codegraph sync` cascades fine). Events for live non-source files stay fully ignored, so build churn schedules nothing. Fixes #1285 Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
18f0745f81 |
perf(sync): defer WAL autocheckpoint for the whole incremental run (#1312)
The #1242 fix (WAL deferral + checkpoint valve, the 26x win on HDD-class storage) was wired only into indexAll. CodeGraph.sync never touched wal_autocheckpoint, so every incremental run kept the default 1000-page cadence and re-triggered the #1231 per-page checkpoint thrash — a 7-file sync took 2m 2s at 0-2% CPU on the reporter's hardware, because the cost scales with the EXISTING database's hot pages, not the change size. sync now mirrors indexAll exactly: defer autocheckpoint + start the valve for the run, fold the store phase's WAL before the post-store reads, restore the interval in the finally. Same kill switch (CODEGRAPH_NO_WAL_DEFER=1). Idle valve cost is one timer, so watcher-frequency syncs stay cheap. Fixes #1248 Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
b6a05d155b |
fix(c): blank leading attribute macros so functions index under real names (#1311)
SEC_ATTR UINT32 LostName(VOID) — an unknown attribute macro before a
typedef'd return type — misparses in tree-sitter's C grammar: the macro
becomes the type, the return type the declarator, and the PARAMETER
LIST is stored as the function name ("(VOID)"). The C++ grammar
recovers this shape via recoverMangledCppName, but in C the real name
never reaches the mangled string, so only a pre-parse blank can help.
Attribute macros are project-specific, so the blank keys on structure:
line-leading ALL-CAPS token followed by TWO identifiers then `(` — the
`MACRO Ret name(` definition shape. Plain typedef'd returns, ALL-CAPS
calls, #define lines, multi-word builtin returns, and mid-line uses are
all rejected by construction. Offset-preserving like the C++ blanks.
curl re-index: 5,531 C functions before and after, zero name changes;
7 nodes in memdebug.c improve start-line accuracy by 1 (the macro line
no longer counts as part of the definition).
Fixes #1211
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
e437918026 |
fix(cpp): compose namespace prefix into out-of-line method qualified names (#1310)
An out-of-line member definition inside a namespace block takes its
qualifiedName from the declarator's receiver, which is spelled RELATIVE
to the enclosing namespace — so `namespace simulator {
ManifestStartup::Output ManifestStartup::Apply(...) {} }` indexed as
ManifestStartup::Apply while the class node carried
simulator::ManifestStartup. Fully-qualified call sites
(simulator::ManifestStartup::Apply(...)) never resolved; callers and
file impact came up empty (#1291).
The receiver-based qualifiedName now composes the active namespace
prefix, anchored at the first prefix segment the receiver re-spells
(so `namespace sim { void sim::M::f() {} }` doesn't double-prefix).
namespacePrefix is only ever non-empty for C++ — Go/Rust/Kotlin/Lua
receivers pass through unchanged.
leveldb re-index: node count byte-stable (3,044), calls edges +6,
namespace-qualified method names 947 -> 1,252.
Fixes #1291
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
4dd29ea5c1 |
fix(cpp): strip template args from out-of-line method receiver qualifiers (#1309)
template<typename T> T Box<T>::get() stored qualified_name Box<T>::get — the <T> qualifier never matched the class node indexed as Box, so the method didn't link to its class, while the inline form of the same method produced Box::get. ICU-shaped multi-line template parameter lists leaked whole <…> blocks (newlines included) into qualified_name, exceeding NAME_MAX for downstream consumers. extractCppReceiverType now applies stripCppTemplateArgs (the #1043 normalization for base-class refs) to the receiver qualifier. fmt re-index: template-arg-in-qualifier names 25 -> 4 (remaining are a FMT_BEGIN_EXPORT misparse artifact and gmock conversion-operator names, both distinct pre-existing shapes), node count byte-stable at 7,536. Fixes #1286 Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
e1f339f732 |
fix(go): require URL-shaped paths for route detection (#1308)
cache.Put("a", 1), store.Get("config", out), bus.Handle("user.created",
h) — any verb-named method with a string first arg — were indexed as
HTTP routes (38 of 82 route nodes were false positives on the
reporter's 200 KLOC Go codebase). A registration's first argument must
now start with "/" (every router style), or be a Go 1.22
"METHOD /path" mux pattern on Handle/HandleFunc — which now also
extracts the real method instead of ANY.
Validated on go-chi/chi (212 real routes retained, all path-shaped)
and golang/groupcache (0 route nodes).
Fixes #1259
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
30421953ac |
fix(ui): consistent frame glyphs on Windows — agree with clack, keep raw path ASCII (#1307)
codegraph's glyphs were ASCII on every Windows console while @clack/prompts drew its Unicode frame around them, so one index block mixed `|` and `│` rails (#398). supportsUnicode() now mirrors the is-unicode-supported detection clack bundles (Windows Terminal, VS Code, ConEmu/Cmder, Alacritty, xterm-256color, JetBrains, CI), so both systems always pick the same glyph family. The shimmer worker's raw fs.writeSync(1) bytes still decode through the console codepage (OEM codepages mojibake UTF-8 even under Windows Terminal — the #168 regression to avoid), so: - the raw path gets its own supportsUnicodeRawWrites() that stays ASCII on win32 unless CODEGRAPH_UNICODE=1, and - the persistent "phase done" lines move from the worker to the parent, written via process.stdout (wide-char console API, codepage-immune) at phase transitions — the main thread is alive there, it's delivering the progress callback. Only transient, self-erasing animation frames remain on the raw path, so ASCII never lands in scrollback. Fixes #398 Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
d6efd437b3 |
fix(cli): honor NO_COLOR/--no-color and go plain when stdout is piped (#1306)
List commands (status, query, callers, callees, impact, files) embedded ANSI color codes even when stdout was a pipe, and NO_COLOR had no effect. One switch now decides color for all codegraph-authored output: --no-color > --color > NO_COLOR > FORCE_COLOR > stdout TTY > CI. Piped init/index/sync also stop emitting shimmer animation frames (\r + erase-line rewrites) and print one plain line per phase instead; a TTY with NO_COLOR keeps the animation but drops the color codes. The detection mirrors picocolors' so @clack frames and our own lines agree within a run. Fixes #1281 Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
5736e24bb6 |
perf(index): faster fresh indexing + parallel reference resolution, byte-identical graphs (#1305)
* perf(index): ~34% faster fresh indexing, byte-identical graphs Profiling a fresh init on a medium TS repo (excalidraw, 657 files) showed the main thread as the critical path: per-row SQLite statement calls, repeated import-resolution walks, and per-row FTS trigger firings, with the parse workers ~75% idle behind it. This lands the semantics-preserving tranche of fixes: - Multi-row batched INSERTs (nodes/edges/unresolved refs/name segments) behind cached per-batch-size prepared statements; row order preserved, so rowid-based resolution determinism (#1015) is unchanged. - storeFileBundle: one transaction per file instead of four; nested transaction() calls now flatten (BEGIN-in-BEGIN previously threw, so no caller depended on nested rollback). - Dedicated store-writer thread for the fresh-DB bulk path (bundles applied in file order on a single writer connection; main thread does no DB work during the parse loop). Kill switch: CODEGRAPH_NO_STORE_WORKER=1. - Bulk FTS mode: drop the nodes_fts sync triggers during the bulk load, rebuild once at the end; crash inside the window self-heals on the next open. - Per-context memos for resolveImportPath/findExportedSymbol + a per-file exported-symbol index, invalidated exactly where clearCaches() already resets the resolver's own caches. - Fast-init on completely fresh DBs (journal in memory, no fsync until the index completes; interrupted init re-runs from scratch). Kill switch: CODEGRAPH_NO_FAST_INIT=1. - MaybeYield returns undefined on the not-due path so per-ref yield checks stop paying a promise + microtask hop each. - Parse pool prewarm for bulk indexing; compile-cache enabled at CLI and worker entry points. Excalidraw fresh init: 5.11s -> 3.36s median (n=5, warm cache, M-series). Graph dumps byte-identical across init, re-index, and sync paths; full suite green (2403 passed). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * perf(resolution): parallel reference resolution with canonical admission Fan resolution batches across a pool of read-only worker threads, each hosting a full ReferenceResolver over its own SQLite connection; results are admitted on the main thread in chunk order, so edge insertion order, row cleanup, failure parking, and deferred post-pass queues are exactly the sequence the single-threaded loop produces. Per-ref inputs match the baseline because the sequential path already resolves each batch against the state committed BEFORE that batch. Validated byte-identical on excalidraw (pool forced on) and apache/dubbo (4,048 Java files): dubbo full index 39s -> 19s (2.05x) with identical graph dumps (91,495 nodes / 223,953 edges). The pool only engages when total pending refs clear a threshold (default 150k, CODEGRAPH_PARALLEL_RESOLVE_MIN to tune, CODEGRAPH_NO_PARALLEL_RESOLVE=1 to disable): measured on a ~58k-ref repo the workers' boot CPU contends with resolution on the same cores and makes indexing slower, so small repos keep the sequential path. When fast-init left the DB in memory-journal mode, WAL is restored before resolution only when the pool will run (readers + rollback-journal writers don't mix). Also: sqlite adapter readOnly open support. TreeCursor spine rewrite of the body walker was built, measured neutral on real repos and equal in a 20k-child microbench (web-tree-sitter's namedChild(i) is not quadratic in this binding), and rejected — per-node JS<->WASM marshaling is the floor, which a traversal swap cannot remove. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
246aee8373 |
fix(ui): within-pass progress for the C fn-pointer linking pass (#1300)
Follow-up to #1299: the per-pass bar still parked on one number while a single long pass ran — on C-heavy repos that's the fn-pointer dispatch pass, which sweeps every C/C++ file four times (typedefs, registrations, field propagation, dispatch sites) and dominates the linking phase. The pass now reports a real fraction of its dominant work (scannedFiles / files×4, at the same per-16-files cadence as its cooperative yield), and the orchestrator surfaces instrumented passes' fractions as fractional steps, throttled to whole-percent movement so the UI message volume stays bounded. The mechanism is opt-in per pass — any synthesizer that a real repo shows parking the bar can adopt the same callback. Verified on the 1,342-file C repo from the report: the linking bar now moves through 88→89→90 where it previously sat at 88 for the whole pass; graph byte-identical (50,520 nodes / 148,232 edges). Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
ad5300a601 |
fix(ui): show synthesis as a 'Linking dynamic dispatch' phase; mute node:sqlite warning spam (#1299)
Two first-run UX bugs surfaced by indexing a real 1,342-file C repo: 1. After 'Resolving refs' hit 100%, the ~40 dynamic-dispatch synthesis passes ran with no progress surface, so the bar sat frozen at 100% long enough to read as a hang (the C fn-pointer pass alone can hold for a while on C-heavy repos). Synthesis now reports per-pass progress through a new 'linking' IndexProgress phase, rendered as 'Linking dynamic dispatch'. The step total is pinned by a test to the synthesizer's actual __mark() count so adding a pass without bumping it fails loudly. 2. node:sqlite's ExperimentalWarning is emitted once per THREAD, so the main process plus every parse worker printed it mid-index, interleaved with the progress UI. All launch paths now pass --disable-warning=ExperimentalWarning: both bundle launchers, the Windows npm-shim invocation, and the CLI self-relaunch (NODE_RUNTIME_FLAGS, deliberately excluded from the re-exec gate so an older installed launcher never triggers a pointless re-exec, and version-gated off nodes older than the flag). Verified end-to-end on the same repo: zero warnings, live linking bar, byte-identical graph (50,520 nodes / 148,232 edges). Full suite green. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
a66683d3eb |
feat(installer): offer CodeGraph Pro beta signup after install and upgrade (#1297)
One-time, strictly opt-in prompt at the end of codegraph install and codegraph upgrade to join the CodeGraph Pro beta waitlist (same list as the getcodegraph.com homepage form). Nothing is sent unless the user answers yes AND enters an email; either answer is recorded machine-wide so no later install or upgrade re-asks, and --yes / non-interactive / CI runs never see the prompt. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
e871c49a31 |
fix(resolution): clean up processed refs by row id so batch boundaries can't drop sibling call sites (#1269) (#1270)
Post-batch cleanup deleted resolved refs (and parked failed ones) by (from_node_id, reference_name, reference_kind) — no line/col. When one caller had several call sites to the same callee and a batch boundary split them, the first batch's cleanup removed every row with that key, including later-batch siblings that were never attempted — their edges were silently never created. On nlohmann/json this ate 422 real call edges (write_cbor's 38 to_char_type calls indexed as 11). Refs loaded from unresolved_refs now carry their row id through resolution, and all three persist paths (sync resolveAndPersist, the yielding retry pass, the batched drain loop) delete / mark-failed by exactly that id. The key-tuple methods remain only as the fallback for hand-built refs from the public API. Failed-parking gains the same precision: outcome can differ per call site (receiver inference reads the ref's line), so a sibling must not inherit another row's failure. Also untracks the zz-scratch local test files that slipped into #1268 and gitignores the pattern. Validation: red-green regression test (5 sites, batch size 2 — old code kept 2 edges, fix keeps 5); nlohmann/json re-index is a strict superset of the previous edge set (0 lost, 422 recovered, spot-checked against source). Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
6103f5e228 |
fix(cpp): resolve explicit operator calls (a.operator+(b)) to the operator method (#1268)
* fix(cpp): resolve explicit operator calls (a.operator+(b)) to the operator method (#1247) tree-sitter-cpp can't parse an operator_name in field position: the call_expression carries `function: <receiver>` plus an ERROR child wrapping the operator_name instead of a field_expression callee, so the extractor emitted a calls ref named just the receiver (`a`) and the edge never resolved — while the operator method itself indexed fine. Two-part fix, scoped to the explicit call form (infix `a + b` / `a[i]` need receiver type inference and are tracked in #1258): - extraction: recover the operator_name from the ERROR child and emit `<receiver>.operator+` (`->` receivers normalized, `this->` emits the bare name), like any other member call - resolution: matchMethodCall's dot pattern now admits an operator method part (cpp-gated; symbol chars failed the \w match), so receiver-type inference + resolveMethodOnType validate the target — a same-named operator on an unrelated class can't capture the edge Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(cpp): harden explicit operator-call recovery against real-world shapes (#1247) Validated on nlohmann/json (dozens of explicit operator[] / operator* / operator< call sites). Two refinements the synthetic fixtures missed: - normalize spaced call-site operator names (`it.operator * ()`, `other.operator < (*this)`) to the compact form definitions index as - drop the ref for a complex receiver (`obj()->operator+`, member chains ending in a call) instead of emitting a bare operator name: exact-name fallback GUESSED among unrelated same-named operators (linked a std::map operator[] call to an in-repo operator[]) — silent miss, never a wrong edge Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
40aa092f5b |
fix(uninstall): remove the CLI binaries too, not just agent configs (#1254)
* fix(uninstall): remove the CLI binaries too, not just agent configs (#1071) `codegraph uninstall` swept agent configurations and stopped — every installed binary stayed behind, so `codegraph` still ran afterward. Three disconnected paths each removed a fraction of an installation (uninstall: configs; install.sh --uninstall: the bundle; npm preuninstall: configs + npm's own package), and none cleared a shadowed second install — the uninstall edition of the #1071 PATH shadow. The uninstall now PLANS every install present on the machine — the bundle layout(s) (running binary's own, the platform default, a custom CODEGRAPH_INSTALL_DIR), the npm global package (found by asking `npm root -g`, so nvm/fnm/volta prefixes resolve correctly), and the bin-dir launcher link (only when it verifiably points into a detected install) — confirms with the user, then removes them all. `--yes` skips the prompt; the new `--keep-cli` flag keeps the old configs-only behavior. Safety rules: a source checkout is reported, never deleted; a project-local npm install is left to the project; on unix the default install dir doubles as the machine state dir, so only the install artifacts (versions/, current) are removed there — telemetry choice and daemon records survive. Windows can't delete a running exe but can rename it (the in-place upgrade's trick): a locked node.exe is renamed aside and surfaced as a one-file leftover instead of failing the removal, and npm is routed through cmd.exe (a direct .cmd spawn EINVALs on modern Node). Planner/executor are split with injected side effects (the upgrade orchestrator's convention) and unit-tested across the shadow case, state-dir preservation, custom dirs, foreign-shim protection, and the locked-exe dance; validated end-to-end on macOS against a fake HOME. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(uninstall): key path math on the target platform, not the host Real-Windows validation caught it: the planner/executor used the host path module, so win32 fixtures were meaningless on a POSIX host and POSIX fixtures failed on the Windows VM. Same convention as detectInstallMethod now — path.win32/path.posix chosen by the injected platform. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(upgrade): route npm through cmd.exe on Windows — a direct npm.cmd spawn EINVALs on modern Node Found while validating the uninstall change on the Windows VM: upgradeNpm spawned npm.cmd without a shell, which every current Node rejects with EINVAL (the CVE-2024-27980 hardening) — so `codegraph upgrade` on a Windows npm install failed before doing anything. Verified live on the VM: spawnSync('npm.cmd') → EINVAL; cmd.exe /d /s /c npm → works. npmInvocation moves into the upgrade orchestrator (remove-binary imports it from there — same direction as its existing imports, no cycle), and the win32 test now pins the WORKING invocation instead of the broken one. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
47823944a3 |
feat(mcp): notice when a newer CodeGraph release exists, without changing anything (#1243) (#1253)
The recommended MCP config launches the local binary, so a server left running drifts behind releases silently — users discover the version gap only when something breaks. Per the reporter's preferred option 1, the server now checks the latest GitHub release in the background on startup (never blocking; reuses the upgrade command's release-redirect resolution so the two can't drift) and surfaces a one-line notice on three surfaces: one stderr line (the MCP host's server log), the initialize instructions (with do-not-run-it-yourself guidance for the agent), and codegraph_status. Discipline: results cache in ~/.codegraph/update-check.json shared across every proxy/daemon on the machine — 24h TTL on success, 1h backoff after failure, an outage never hides an already-known update, and a stale cache re-kicks a background refresh so long-lived daemons keep noticing. The initialize path is a memoized synchronous cache read (the respond-fast contract holds), and both handshake answerers (session + proxy) share one helper so they can't diverge. Never stdout. Hardening: the latest tag arrives from a network redirect via an on-disk cache and ends up inside agent-visible instructions, so only a canonical vX.Y.Z rebuilt from PARSED semver fields is ever interpolated — a tag carrying trailing text (parseSemver is not end-anchored) renders without it, and a non-version tag renders nothing and counts as a failed attempt. Off is off: CODEGRAPH_NO_UPDATE_CHECK=1 (dedicated) or DO_NOT_TRACK=1 (broad convention — already set by data-plane deployments) suppresses the network call and the notice entirely. Documented in TELEMETRY.md. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |