Audience: the agent/engineer executing the native-kernel project. Self-contained handoff:
context, current state, per-language tracker, gates, and the follow-on roadmap.
Companion: docs/design/native-extraction-kernel.md (architecture + spike detail).
Written: 2026-06-12 planning → executed 2026-07-16/17. R1–R6 ARE DONE. The shipped
records live in §3a and §4a–§4f; the per-language tracker is current; §0a is the
cold-start handoff for the next session. Read §0 + §0a first — parts of §1/§6 below
them are the ORIGINAL plan and carry expectations that measurement later corrected
(each is annotated where superseded).
.scm emitter was SUPERSEDED by bespoke per-language walkers (queries can't
express extraction parity); byte-parity from day one of the harness.defer: policy.Open, in recommended order (rationale in §0a):
rust-kernel branch — DONE 2026-07-17: PR #1326, merge
commit (the integration-branch exception — 9 milestone commits preserved),
main tip c1dc78d. Suite green pre-merge (2,472 passed / 4 skipped,
CODEGRAPH_KERNEL_EXPECT=1).schtasks is the survival
pattern). build-kernel.sh --target aarch64-pc-windows-msvc builds native
win32-arm64 in ~2min; all three kernel suites green with
CODEGRAPH_KERNEL_EXPECT=1 (33/33). The leg EARNED ITS KEEP: the guest's
autocrlf checkout exposed a real CRLF parity bug (docstring cleaning; JS
multiline ^ anchors after \r — §0a traps) — fixed + CRLF fixtures pinned
cross-platform in #1329. Every prebuild target platform is now validated.preParse
is offset-preserving and the route point can apply it before the kernel call;
see the T2 note in src/extraction/kernel/index.ts). Largest per-language
surface in tree-sitter.ts: namespace prefix stacks (#1291), local fn-pointer
tables (#932), operator calls (#1247), stack construction (#1035), macro
salvage + .h content detection (stays at detectLanguage, upstream — free).Where the work lives: MERGED to main 2026-07-17 (PR #1326, merge commit
c1dc78d; the 9 milestone commits c5eebe6 R1 → 2a79432 R6 are preserved in
history). All scratchpad clones
(excalidraw/vscode/dubbo/django/…) were throwaway; re-clone fresh for new gate runs.
The cg1212 docker container (Linux kernel, 2 CPU/6GB) is long-lived on the dev Mac
and has the current build deployed at /app (tree at /work/linux).
What exists:
codegraph-kernel/ — napi-rs crate. One WALKER MODULE per language
(tsjs/, java.rs, python.rs, go.rs) mirroring TreeSitterExtractor's
per-language paths bug-for-bug; shared buffers.rs (wire contract — twin of
src/extraction/kernel/layout.ts, byte-matched, ABI-versioned), ids.rs
(sha node ids, test-pinned to generateNodeId), docstring.rs, textutil.rs
(UTF-16 columns/slices, generated-file patterns, shared regexes), langs.rs
(grammar registry).src/extraction/kernel/ — loader (contract-verifies before routing; a stale
.node silently degrades to wasm; CODEGRAPH_KERNEL_DEBUG=1 explains), decode,
routing (DEFAULT_ROUTED = ts/tsx/js/jsx/java/python/go;
CODEGRAPH_KERNEL_LANGS REPLACES the set; CODEGRAPH_KERNEL=0 kills), and the
deferred-decode transport (tryKernelExtractRaw → buffers ride to the store
worker; files with applicable framework extract() hooks keep the decoded path).scripts/kernel-parity.mjs (per-file kernel↔wasm diff,
ORDER-sensitive, full-object; deferral-rate guard), scripts/dump-graph.mjs
(natural-key full-DB dump for the byte-identical diff),
__tests__/kernel-{scaffold,grammar-parity,tsjs-parity}.test.ts (+ torture
fixtures under __tests__/fixtures/kernel-parity/) — all in npm test;
the release workflow builds a 6-target prebuild matrix (continue-on-error;
kernel is optional everywhere) and runs the suites with
CODEGRAPH_KERNEL_EXPECT=1.Build/run: npm run build:kernel (needs rustup; stages
codegraph-kernel/prebuilds/<plat>-<arch>/codegraph-kernel.node) → npm run build
→ npm test. Parity sweep: node scripts/kernel-parity.mjs <dir>. Dump gate:
init twice (kernel arm vs CODEGRAPH_KERNEL=0), dump-graph.mjs each, cmp.
Adding a language (the proven recipe, ~a day for a T1):
languages/<lang>.ts config AND every branch of tree-sitter.ts it
exercises (visitNode dispatch, extractCall's language branch, inheritance
clauses, fn-ref spec in function-ref.ts, value-ref prune cases). Port
bug-for-bug — quirks included (each walker's header comments list its own).tree-sitter-cli 0.25.10
build --wasm from CHECKED-IN parser.c, drop into src/extraction/wasm/, add
to VENDORED_WASM_LANGS) — tree-sitter-wasms is 2023-era for most languages.Traps already paid for (do not relearn):
has_error() files via
the defer: signal. Incidence 0–0.42%; the harness fails >10% deferral.node_ids vec in every walker).textutil::col16/slice_utf16) —
that's what web-tree-sitter reports and what .slice(0,100) means.^ anchors after \r (and U+2028/U+2029); the regex crate's
(?m)^ is \n-only — on CRLF checkouts (Windows autocrlf) the JS reference's
greedy \s* eats the \n of a CRLF pair and the cleaned docstring keeps a bare
\r. Caught by the O2 Windows leg (6 parity failures), fixed via
js_multiline_strip in docstring.rs; CRLF variants of every torture fixture
are pinned in kernel-tsjs-parity (derived in-memory — normalization-proof).
Any future walker regex with (?m) needs the same scrutiny.CodeGraph's remaining fresh-index gap vs codebase-memory-mcp (cbm) is the parse+extract phase, and its floor is per-node JS↔WASM marshaling — proven, not suspected:
| Measurement (2026-07-16, M3 Pro) | Result |
|---|---|
| dubbo (4,402 Java files) parse-loop, current 7-wasm-worker pipeline | 4,700ms |
| Same files, Rust tree-sitter parse+walk, rayon (spike) | 202ms |
| Same, single Rust thread | 1,067ms |
| dubbo fresh init today / cbm | 11.1s / 7.1s (1.55×) |
| Linux kernel, same 2-CPU/6GB container | we complete 27min; cbm dies at 0.16%, twice |
Spike source: session scratchpad cg-kernel-spike/ (tree-sitter 0.25 + tree-sitter-java,
TreeCursor walk touching kind/range/name-field, flat-row output). Reproduce before starting —
it's ~80 lines and doubles as the emitter's seed.
Expected end state: parse-loop 4.7s → ~1.0–1.5s on dubbo-class repos → total ≈ 7.5s, parity with cbm on their best surface, while keeping every win we already hold (sync 2.4–2.8×, agent A/B decisive, call-graph density 1.3–2.3×, byte-identical determinism, constrained-hardware envelope).
SUPERSEDED BY MEASUREMENT (§4c/§4d): the many-core parse-loop wall turned out to be the single-writer SQLite ingest (94% of it on dubbo), not extraction — 8 wasm workers already hid extraction CPU behind the main thread on big-core machines. So the Mac dubbo total stays ~11s and closing the remaining cbm gap there is a STORE-ARCHITECTURE arc, not a kernel task. The kernel's wins are real where worker CPU binds: the 2-CPU/6GB CI envelope (excalidraw ~1.5×, dubbo ~1.25×, django 1.32×, prometheus 1.46×) and vscode-scale-on-Mac (1.28×). Every "keep" item held — byte-identical determinism is now enforced per language by the dump gate.
One napi-rs crate (codegraph-kernel) linking tree-sitter's C library and native grammars.
Input (filePath, content, language) per file; output flat typed buffers (nodes, edges,
unresolved refs) — one boundary crossing per file. It replaces ONLY the parse+extract walk
inside the parse workers, behind the existing ExtractionResult contract.
Never ported (works unchanged for all languages from day one): name-matcher +
import-resolver, all framework resolvers (src/resolution/frameworks/), all 36 synthesis
passes, MCP/explore, sync/watcher, installer. They consume the graph and raw source, not
the parse tree.
Coexistence is permanent: a language routes to the kernel only after its gate passes; everything else stays on the wasm path forever if need be. No flag-day. Rollback per language = flipping the route.
Distribution: prebuilt .node per platform through the existing release-bundle
pipeline (scripts/build-bundle.sh + per-platform npm packages); the same crate compiled
to wasm is the universal fallback. Zero-native-build-on-install stays true.
codegraph-kernel/ crate: napi-rs, tree-sitter C, rayon optional (workers already
parallelize per-file — start synchronous per call, one kernel call per file from the
existing ParseWorkerPool workers; do NOT rebuild the pool).Buffer per table,
fixed-width rows + a string arena; version byte first). Write the TS decoder next to
parse-worker.ts..scm query files + a small per-language Rust
config (node-kind → NodeKind mapping, name-field conventions). Escape hatch: a
per-language post(buffers, source) TS hook for logic queries can't express.CODEGRAPH_KERNEL=0 kill switch; wasm fallback auto-selected when the
.node is absent (source runs, unsupported platforms).codegraph-kernel/ (napi 3, tree-sitter 0.25, no CLI dependency —
scripts/build-kernel.sh does cargo build + stage into
codegraph-kernel/prebuilds/<platform>-<arch>/codegraph-kernel.node; npm run
build:kernel). Exports extractFile, contractInfo, grammarInfo.(offset,len) refs, 0xFFFFFFFF = absent, version byte first, node
IDs computed Rust-side (sha256, byte-identical to generateNodeId — pinned by test),
tri-state bool flags, extraJson escape slot per node row, and a RESERVED u32 metrics
slot (Arc 3.2). Layout doc lives twice and must match: codegraph-kernel/src/buffers.rs
↔ src/extraction/kernel/layout.ts. NODE_KINDS/EDGE_KINDS array ORDER in src/types.ts
is wire contract now (EDGE_KINDS became a runtime array for this)..scm-driven (@def.<NodeKind> + @name + @ref.<EdgeKind>
capture convention), scope stack by byte-range nesting → ::-joined qualifiedNames,
contains edges, refs attached to innermost enclosing def (file node fallback) — the
TreeSitterExtractor conventions. Seed TS/JS queries are SMOKE-level only; R2 replaces.extractFromSource (tree-sitter.ts) — tryKernelExtract first,
wasm TreeSitterExtractor as fallback (also per-FILE fallback on any kernel error).
DEFAULT_ROUTED is EMPTY; dev opt-in via CODEGRAPH_KERNEL_LANGS=<langs|all>; global
kill switch CODEGRAPH_KERNEL=0; loader verifies ABI + kind tables before routing
(stale .node → silent wasm, CODEGRAPH_KERNEL_DEBUG=1 to see why). The escape hatch
landed as post(result, source) over the DECODED result (not raw buffers) — decoded
is what TS logic wants; see POST_PASSES in src/extraction/kernel/index.ts.__tests__/kernel-grammar-parity.test.ts, behavioral: ABI + node-kind +
field tables compared id-by-id) caught on day one that tree-sitter-wasms ships
2023-era TS/JS grammars (^0.20.x) vs crates.io current. Resolution: vendored fresh
wasm into src/extraction/wasm/ built from the exact crate revisions —
tree-sitter-typescript v0.23.2 (f975a62) for typescript+tsx, tree-sitter-javascript
v0.25.0 (44c892e) for javascript+jsx — from each repo's CHECKED-IN parser.c (no
generate), tree-sitter-cli 0.25.10, emcc. So the production wasm TS/JS grammars are
UPGRADED as of this change (full suite green, 2456 tests) and R2/R3 parity diffs
are grammar-neutral. Bump crate + vendored wasm together, or the parity test fails.kernel matrix job in release.yml (macos-14 ×2 targets,
ubuntu-22.04, ubuntu-22.04-arm, windows-latest ×2 — all continue-on-error: kernel is
optional, a toolchain flake never blocks a release) → artifacts → release/kernel/ →
build-bundle.sh stages lib/kernel/codegraph-kernel.node when present. The release
job runs the kernel tests with CODEGRAPH_KERNEL_EXPECT=1 (missing binary = FAILURE
there, skip elsewhere).CODEGRAPH_KERNEL_PATH → <pkgroot>/kernel/ (bundle) →
<pkgroot>/codegraph-kernel/prebuilds/<plat>-<arch>/ (source runs)..scm emitter is superseded. Real TS/JS parity needs logic queries
can't express (extractCall's receiver-qualified callees, store/RTK/component
recognition, fn-ref capture+gating, value-ref shadow pruning, docstring wrapper
climbs) — so R2 replaced the R1 query emitter with a bespoke per-language walker
(codegraph-kernel/src/tsjs/, ~1,900 lines) that mirrors TreeSitterExtractor's
TS/JS paths function-for-function, bug-for-bug. emitter.rs + queries/ are deleted
(git has them); expect T1 languages (java/python/go) to be walkers too. The
post(result, source) TS escape hatch remains available but TS/JS needed none.scripts/kernel-parity.mjs (multiset diff of
canonicalized nodes/edges/refs per file, FULL objects) — this repo 353/353 files,
excalidraw 643/643 files (10,650 nodes / 10,726 edges / 68,307 refs), plus
checked-in torture fixtures (__tests__/fixtures/kernel-parity/) covering
components/HOCs/styled, zustand-through-middleware, RTK endpoints+hooks, vuex/pinia,
fn-refs (incl this.x + shadowing gates), value-refs (incl the shadow prune),
decorators, enums, type-alias members + tuple contracts, re-exports, JSX. Kept alive
in npm test by __tests__/kernel-tsjs-parity.test.ts (strict full-object compare).filePath/language on refs; wasm extractors leave them unset (the store
denormalizes via ?? filePath). Fixed — the seam contract is "exactly what
extractFromSource returns", not "what the store makes of it".init on an 11-core host
moves only ~3.4s → ~3.2s — parse is a small, already-pool-parallelized slice there;
the win concentrates on constrained hardware (2-core CI class) and kernel-scale
parse (R6). Headroom if R4's dubbo target needs it: arena interning, memoized
UTF-16 line prefixes, and skipping wasm-grammar loads in workers for kernel-routed
languages (worker cold-start).CODEGRAPH_KERNEL_LANGS) until then.
→ Done same day, §4b.Evidence (tools: scripts/kernel-parity.mjs now ORDER-sensitive — identical multisets
in a different emission order would shift rowids and change resolution — and
scripts/dump-graph.mjs, natural-key full-DB dumps):
init dump-diff kernel-vs-wasm:
express (13,712 rows), excalidraw (89,898), vscode (2,378,238 rows) — all
byte-identical. Control repo (flask, Python) byte-identical + timing unchanged.
Extraction-level order-sensitive sweeps: repo 352/354 (+2 deferred), express
141/141, excalidraw 643/643, vscode 12,055/12,106 (+51 deferred), 0 diffs.has_error() to the wasm extractor (defer: signal, silent,
per-file) — parity by construction on erroring files, 99.6%+ keep the fast path,
and the harness fails if deferrals exceed 10% (a broken kernel can't hide).mutateElement →
renderStaticScene connects end-to-end via explore (callback + react-render +
jsx hops shown); synthesized-edge families present (408 jsx-render / 46
react-render / 14 interface-impl / 1 callback); byte-identical DB ⇒ counts equal
by construction.CODEGRAPH_KERNEL_EXPECT=1. Windows VM: deferred —
VM stopped and prlctl start needs Parallels Pro; benign because a missing/broken
.node falls back to wasm, and the release workflow builds + gates win32
prebuilds. Run the kernel suites on the VM when it's next up..node.Default routing: DEFAULT_ROUTED = {typescript, tsx, javascript, jsx} in
src/extraction/kernel/index.ts. CODEGRAPH_KERNEL_LANGS REPLACES the set;
CODEGRAPH_KERNEL=0 kills. Changelog entry added under [Unreleased].
codegraph-kernel/src/java.rs (self-contained, sharing the crate-level
docstring/textutil modules) — package namespaces, imports, javadoc, annotations →
decorates, type_list inheritance, fields/constants (static-final → constant),
enum_constant members, anonymous classes (<T$anon@line> incl. the TS side's
0-based-line quirk, mirrored bug-for-bug), method_invocation calls with the
this.field unwrap + the Foo.getInstance().bar() chain encoding, static-member
value reads, method_reference fn-refs (this::x / Type::m), value refs, and the
full Lombok member synthesizer (#912: @Getter/@Setter/@Data/@Value/@Builder/
@ToString/@EqualsAndHashCode/@Slf4j-family, taken-member dedup by exact
classQN::name). Grammar: tree-sitter-java crate 0.23.5; wasm vendored from the
SAME tag (94703d5, parser.c sha-matched), replacing tree-sitter-wasms' ^0.20.2 build.__tests__/fixtures/kernel-parity/Torture.java, in npm test).
Full-init dump-diffs byte-identical: gson (49,766 rows), retrofit (62,735),
dubbo (441,266 rows). All R2/R3 repos re-verified after the fix below.function e on line 3) and the TS side keys on
${fromNodeId}|${name}. Fixed in BOTH walkers (node_ids per row); this affected
tsjs too (latent since R2, never released).java.codegraph-kernel/src/python.rs + src/go.rs (the java.rs pattern).
Python: decorated_definition docstring/decorator handling (decorates only for
bare-identifier decorators — the call-kind quirk mirrored), fn-in-class → method,
module assignments always variable (no isConst hook), from-import binding refs,
self.x fn-ref candidates as BARE names, attribute callees via the namedChild(1)
fallback. Go: receiver methods with Recv::name QNs + first-earlier-struct
contains edges, type_spec → struct/interface classification (embedding → extends;
interface method_elems → method nodes), composite-literal instantiates keeping the
package qualifier, top-level var/const initializer walks attributed to the symbol
(#693), 2-hop field chains (#1276), New().Method() re-encode (#645/#608),
GO_SPEC fn-ref layers (literal_element/expression_list fan-out).bffb65a) + tree-sitter-go 0.23.4
(3c3775f); wasm vendored from the same tags, parser.c sha-matched (both were
2023-era in tree-sitter-wasms).npm test. Even Mac-side init already moves where extraction
matters: prometheus 5.7→4.5s, django 9.0→8.7s. On the 2-CPU/6GB envelope (the
CI-runner class): django 22.0→16.7s (1.32×), prometheus 15.0→10.3s (1.46×).Fresh init of the Linux kernel in the cg1212 container (2 CPUs / 6GB), current build (R5 kernel + direct-to-store active), CODEGRAPH_SYNTH_TIMINGS:
Kernel-routed files now ship their flat buffers from the parse worker all the way
to the STORE WORKER, which decodes + finalizes them there (tryKernelExtractRaw →
ExtractionResult.kernelBuffers → KernelStoreBundle → decodeKernelBundle;
filter semantics shared via finalizeStoreBundle). The main thread's per-file work
drops to O(1) + the content hash — it never materializes per-node objects, and both
postMessage hops move flat bytes instead of object graphs. Files whose applicable
frameworks carry an extract() hook keep the decoded path (hooks merge into decoded
results); non-writer paths (main-thread store, tests) materialize via
materializeKernelResult. Byte-identical dumps re-verified on dubbo, excalidraw,
express, gson.
Measurement that closes the §4c question: with the store worker instrumented, dubbo's parse-loop wall is 94% store-writer busy time (4,202ms of 4,493ms on the kernel arm). The many-core fresh-index wall is the single-writer SQLite ingest — not extraction, not main-thread work. d2s still improves the writer lane ~11% (4,726→4,202ms: buffers skip structured-clone deserialization ON the writer) and frees the main thread, but the remaining cbm gap on many-core medium repos is a STORE-ARCHITECTURE question (their RAM-first design defers all durability). Next levers there (a separate perf arc, not this project): deferred/bulk index builds during the parse phase, multi-file write transactions, buffer→bind without object materialization. Note the #1320-arc post-mortem already measured statement batching and sorted inserts as ~zero on this path — B-tree maintenance is the floor.
Tiers: T1 = mostly .scm + mapping config. T2 = needs bespoke pre/post passes kept
in TS (listed). T3 = not a plain tree-sitter walk (standalone/multi-grammar extractor)
— migrate last or never; wasm/TS path is a fine permanent home.
The user-facing language contract is README.md → Language Support (34 logos incl.
Metal, CUDA, Terraform/OpenTofu, Pascal/Delphi). Keep this tracker in sync with it —
every README language must have a row here, even the ones that only ride another
language's port.
Grammar column: crates.io = mainstream native grammar crate exists; vendored = we ship
a rebuilt/patched wasm (ABI-15) and the kernel must compile OUR fork natively — verify
parity before porting the language.
| Language(s) | Today | Tier | Grammar source | Migration notes / known traps | Status |
|---|---|---|---|---|---|
| typescript, tsx, javascript, jsx | languages/typescript.ts, javascript.ts + shared branches |
T1 | crates.io | First target. Value-reference edges (#895/#897) and component recognition (#841 forwardRef/memo/styled) must survive — they're extraction-side. Largest test surface; gate is strictest here. PORTED + GATE PASSED + DEFAULT-ON (§4a/§4b); erroring files defer to wasm per-file. | ✅ |
| java | languages/java.ts |
T1 | crates.io | Second target; unlocks the dubbo-parity claim. Lombok member synthesis (#912) is a NODE synthesizer hook in extraction (synthesizeMembers) — port or keep as TS post-pass. PORTED incl. Lombok + gate passed + DEFAULT-ON (§4c). |
✅ |
| python | languages/python.ts |
T1 | crates.io | Third. Decorator extraction feeds framework route detection — parity required. PORTED + DEFAULT-ON (§4e). | ✅ |
| go | languages/go.ts |
T1 | crates.io | Third (tie). Value-reference edges ship here too (#897). PORTED + DEFAULT-ON (§4e). | ✅ |
| ruby, php | dedicated files | T1 | crates.io | Straightforward; PHP property-receiver shapes (#1220/#1251) are RESOLUTION-side, unaffected. | ☐ |
| csharp | languages/csharp.ts |
T1 | crates.io | Plain. | ☐ |
| rust, dart, scala, lua, luau, r | dedicated files | T1 | crates.io (luau/r/scala: verify crate freshness vs our wasm) | Long-tail T1; port opportunistically after the big five. | ☐ |
| kotlin | languages/kotlin.ts |
T1½ | crates.io | Expect/actual pairing is synthesis-side (fine); extraction is clean but validate against a KMP repo. | ☐ |
| swift | shared + dedicated branch | T1½ | crates.io | Trap: in-class property extraction lives in tree-sitter.ts's DEDICATED branch, not swift.ts (#1020 — Alamofire went 0→348 props). Gate on Alamofire. |
☐ |
| c, cpp | languages/c-cpp.ts |
T2 | crates.io | Keep as TS pre-passes: blankCppExportMacros/blankCppInlineMacros (UE class MACRO Name phantom-function misparse, #1096–#1102, CARLA 440→6), in-body reflection collapse guard (#1206), content-based .h C-vs-C++ detection. |
☐ |
| metal, cuda | dialects over the cpp grammar | T2 (rides c/cpp) | crates.io (cpp) | README-listed as first-class languages. Both are dialect-gated cpp: Metal = specifier/[[attribute]] blanking (#1121, the preParse-takes-filePath pattern); CUDA = <<<>>> blanking + content-gated .h (#1172). Their pre-passes must run before the kernel parse or stay TS-side; gate them WITH the c/cpp port, not separately. |
☐ |
| objc | languages/objc.ts |
T2 | crates.io | Rides the c-cpp trap family; RN bridge extraction feeds rnCrossPlatformEdges (synthesis-side, fine). |
☐ |
| arkts | languages/arkts.ts |
T2 | vendored (harmony-contrib) | Dot-prefixed refs + decorator-gated matching fixed 36,840 wrong edges — that logic must port exactly or stay TS-side. Compile our grammar fork natively. | ☐ |
| pascal | languages/pascal.ts |
T2 | vendored | Paired with dfm-extractor (T3); extractPascalDefProc indexed lookups. |
☐ |
| vbnet | languages/vbnet.ts |
T2 | vendored, patched + external scanner | Our wasm is a patched grammar WITH a C external scanner — the kernel must build that scanner; ts-cli 0.24 dropped \p{...} classes during the original build (#1164). Highest grammar-build risk of any language. |
☐ |
| cobol | languages/cobol.ts |
T2 | vendored fork | Paragraph-extent reconstruction + copybook resolution are extraction logic (#1161, CardDemo 43/44). Port carefully or keep TS post-pass. | ☐ |
| erlang | languages/erlang.ts |
T2 | vendored (WhatsApp/ELP) | npm tree-sitter-erlang is HIJACKED — never source from it (#1165). gen_server dispatch is synthesis-side (fine). |
☐ |
| nix | languages/nix.ts |
T2 | vendored (ABI-15 rebuild) | Option-path synthesizer is synthesis-side; the ===-always-false → .equals() lesson (#1190) is wasm-binding-specific and disappears natively — still gate on nixpkgs (44k files). |
☐ |
| solidity | languages/solidity.ts |
T2 | vendored | modifier_invocation outside body walk (#1170) is extraction-side; port it. |
☐ |
| terraform | languages/terraform.ts |
T2 | vendored | :-scoped refs for module-boundary bridging (#1173); metadata does NOT persist — re-read source (#1174). |
☐ |
| cfml, cfscript, cfquery | cfml-extractor.ts + 3 grammar files |
T3 | vendored ×3 | 3-grammar family with BOM-sensitive dialect sniffing (#1118/#1153–55). Leave on wasm until the very end, possibly forever. | ☐ |
| svelte, vue, astro, liquid | standalone extractors | T3 | n/a (custom/embedded parsing) | Not tree-sitter walks. Permanent TS home is acceptable — file counts are small and these repos are small. | ☐ |
| dfm (Delphi forms), razor, mybatis XML | standalone extractors | T3 | n/a | Same as above. mybatis pairs with a synthesis pass (fine). | ☐ |
Do-not-regress invariants during any port (extraction-side, will show up in the gate):
node metadata is re-read from source, never persisted; parse commits stay in FILE ORDER
(#1015); MAX_FILE_SIZE skip; generated-file detection; CODEGRAPH_PARSE_WORKERS
semantics; framework extract() hooks keep running TS-side per file after the kernel pass.
Byte-identity vs hand-written extractors is NOT expected — the gate is behavioral parity:
dump-graph.mjs pattern (natural keys).
Node/edge/ref deltas ≤0.5% AND every diff category manually classified (the 13-edge
supertype-visibility bug this week was caught exactly this way — small diffs are real).codegraph_explore (playbook: docs/design/dynamic-dispatch-coverage-playbook.md);
node counts stable; synthesized-edge spot-check.--model sonnet
--effort high ALWAYS, ≥2 runs/arm, pre-warmed daemon, CODEGRAPH_NO_PROMPT_HOOK=1,
forbid subagent delegation in the prompt.Executed 2026-07-16/17; outcomes vs these expectations are in §4a–§4f. Two expectations below were corrected by measurement: (2) the dubbo-on-Mac headline is store-writer-bound, not extraction-bound (§4c/§4d — the win lands on the low-core envelope instead); (4) cg1212 is ~99% C, an unported T2 language, so its parse expectation belongs to the C/C++ port (§4f).
Measurement discipline (hard-won this week — do NOT relearn these):
CODEGRAPH_SYNTH_TIMINGS=1 now emits full phase walls ([phase-timing]) + pool/batch
timings. UI distorts phase walls — pipe stdout away.grep is aliased to ugrep and silently treats callback-synthesizer.ts as binary —
use grep -a.Confirmed by the R6 run (§4f): resolution is 19.2min of the 26.4min Linux-kernel
wall (73%) — sequential BY DESIGN in the 2-CPU container (the resolver pool requires
≥4 cores to engage) (premise corrected in §7a.1: it was pooled all along).
Parse is 6.2min (23%) and belongs to the C/C++ port (R7a).
Steps: re-run cg1212 validation on ≥4-core allocation (pool + parallel synthesis
#1321/#1322 engage — this first measurement is cheap and may reshape the whole
problem); profile; likely levers: worker count scaling, batch size at scale,
warmCachesYielding on multi-GB DBs. Target: kernel <10min on a normal 8-core host.
The cheap first measurement was run and did exactly what it was for: it invalidated the premise and surfaced two structural defects that now gate any speed work.
resolver-pool.ts tryCreate: min(os.cpus().length − 2, 6), engage at ≥2)
uses os.cpus(), which is cpuset-blind — inside the 2-CPU container it saw
the Docker VM's 8 CPUs and ran 6 workers time-slicing 2 cores (r6 log: 6×
worker open, 14k pool-timing lines). A real <4-CPU host (os.cpus() < 4) gets
no pool at all. This also explains why the earlier "19.5m sequential" and R6's
19.2m match: same 6-on-2 topology. os.availableParallelism() (cgroup/affinity-
aware) is the honest sizing input — candidate fix rides item (2) below.oom_kill=5,
OOMKilled=true), silent EXIT=1 (SIGKILL inside the liftoff re-exec surfaces
as code 1, no output). Died mid-parallel-synthesis, 4 passes in. At 8 real cores
all 6 workers hold peak anon memory simultaneously — the 2-core runs survived
only because time-slicing kept concurrent peak lower. The pool sizes by cores
only; there is no memory-aware term and no size knob (CODEGRAPH_NO_PARALLEL_
RESOLVE is all-or-nothing).wal_checkpoint(RESTART)
window; instrument to confirm the starvation point before building.Revised P1 order: (1) WAL containment → (2) memory-aware, cgroup-honest pool sizing → (3) re-run the 8-core measurement (container at ≥12GB or the Mac with disk headroom) → then profile what remains. The <10min-on-8-cores target stands.
Four PRs, each carrying its measurement; the arc took three failed/diagnostic kernel-scale runs to get right, and every failure taught a design fact:
| Run (2c/6GB unless noted) | Build | Outcome |
|---|---|---|
| R6 baseline | pre-P1 | 26.4min, EXIT 0; WAL unbounded (mid-run peak unmeasured); pooled 6-on-2 (cpuset-blind) |
| run 1 | #1332 hook | EXIT 137 (OOM) — WAL 22.2GB, 0 of 5.4M frames ever backfilled; futile 20-pass parks amplified memory churn |
| diagnostic | +latch/debug | EXIT 0, ~24min; pool KILLED by mis-measured 57MB cgroup budget → exposed sequential resolution 853s vs 1,150s pooled and cFnPtrEdges = 306s of synthesis's 358s |
| instrumented | +sizing fixes | EXIT 0, 21.6min (R6 −18%); parse floor restores 373.5s; passives complete but the FILE marched 361→721MB → named the wrap-never-happens gap; peak 17.2GB |
| record (first attempt) | #1335 | EXIT 1: "database is locked" — the timer-path truncate won the lock race after the recreate's multi-GB burst and stalled the writer past its 5s busy_timeout → truncate is barrier-only now (#1336). Bonus data: recreate 7.9s (vs 68–95s) once the WAL stays folded |
| record | #1336 | EXIT 0, 20.4min (R6 −23%); WAL peak 1.57GB (−14×); counts byte-exact 2,048,664/6,405,964. parse 354.9s · resolution 812.5s · synthesis 329.0s · recreate 57.5s · maintenance 43.5s |
| 8-core retry (8c/7GB) | #1336 | EXIT 0, NO OOM — 18.3min; WAL peak 1.09GB; pool sized 4 by the memory term (ap=8, budget 5.1GB, db 4.1GB); counts byte-exact. parse 208.7s · resolution 835.9s · synthesis 338.7s |
The 8-core verdict (the question P1 set out to ask): 18.3min vs the <10min
target — infrastructure fixed, speed target NOT met, and the gap is now
precisely characterized. Resolution is CORE-INVARIANT at kernel scale: 835.9s
pooled-4-on-8 ≈ 812.5s sequential-on-2 — worker parallelism buys nothing, so
the bottleneck is the per-ref main-thread path (admission + persist + per-ref
resolver work), not topology. Of the 18.3min, ~14min is core-invariant
resolution+synthesis. Next levers, in order: (a) profile the per-ref path
inside resolution (the 812–836s floor), (b) cFnPtrEdges (306s, 86% of
synthesis — parallelize/window WITHIN the pass), (c) the R7a C/C++ port
(parse 209s → kernel-native). 4× cores currently buys only 2min end-to-end
(20.4 → 18.3) because parse is the only core-scaling phase left.
Design facts these runs established (each now enforced in code + tests):
memory.current counts reclaimable page cache — post-parse it
read 57MB free on a 6GB box and silently disabled the pool. inactive_file
is credited back (#1335); the same box reads 4.4GB.cFnPtrEdges (306s of 358s) at
kernel scale. Sizing: min(availableParallelism − 1, 6) + memory term +
CODEGRAPH_RESOLVE_WORKERS knob (#1333/#1335); ap=2 → sequential by choice.CODEGRAPH_SYNTH_TIMINGS
/ CODEGRAPH_WAL_VALVE_DEBUG — the armed line answers "is it even alive"
in one glance.New synthesis lever surfaced: cFnPtrEdges is 86% of kernel-scale synthesis
wall — parallelizing WITHIN that one pass (or windowing its scan) is worth more
than pooling all 36 passes. Filed under the next P1 profiling round.
Priority order, each gated by the standard A/B + node-explosion probes:
tests edges at index time; we compute covering
tests at query time today; cbm materializes 14.8k on dubbo). Feeds test-gap detection
(Lite headline) + Pro risk signals. Cheapest, do first.is_test, is_entry_point,
param counts) — computed during extraction (the kernel makes this nearly free —
design the buffer contract with a metrics slot!). Feeds Pro risk-ranking verdicts +
explore ranking de-noise.USAGE vs WRITES). The measured agent
frontier ("who mutates this state" — the canvasNonce class). HIGHEST value, HIGHEST
risk: scope to exported/state-relevant symbols; the tracking-every-local explosion is
the known failure mode (#999/#1212 class). Full validation methodology.raises) — throw→handler; moderate.graph.db.zst idea — good, but design it for the
Pro shared-worker story, not as an OSS clone).CLAUDE.md (repo root) — the retrieval invariants, A/B model
policy, release rules (never npm publish/push tags), changelog format.