CodeGraph Rust 抽取内核迁移:从 napi-rs 架构、字节级等价门禁到 20 语言 DEFAULT-ON 的完整工程记录
本文基于 CodeGraph 仓库中的设计文档 rust-kernel-migration-plan.md 展开,系统讲解 CodeGraph 如何把"解析 + 抽取"这一索引热路径从 JS/WebAssembly 树形遍历迁移到 napi-rs 原生 Rust 内核:包括 napi-rs 缓冲区契约、按语言 walker 的"bug-for-bug"移植方法论、字节级等价的门禁体系、per-file wasm 降级策略,以及 Linux 内核级(200 万节点)性能弧中 WAL 治理、解析池 sizing、cFnPtr 原生化等一系列被真实测量驱动的设计修正。读完本文,你可以理解一个"可选原生加速层"如何在保证输出字节不变的前提下安全落地,并能复现其构建、路由开关与等价验证的完整工作流。
1. 项目动机:数据驱动的性能差距
迁移计划开篇给出的核心判断是:CodeGraph 与对标方案 codebase-memory-mcp(cbm)在全量索引上的剩余差距集中在 parse+extract 阶段,其成本下限是"每个节点一次 JS↔WASM 的 marshaling"。计划文档(2026-07-16,M3 Pro 实测)给出的证据是:
| 测量项(dubbo,4,402 个 Java 文件) | 结果 |
|---|---|
| 7 个 wasm worker 流水线的 parse-loop | 4,700ms |
| 同一批文件用 Rust tree-sitter 解析+遍历(rayon spike) | 202ms |
| 同一批文件、单 Rust 线程 | 1,067ms |
| dubbo 全量 init:CodeGraph / cbm | 11.1s / 7.1s(1.55×) |
| Linux 内核,同为 2 CPU/6GB 容器 | CodeGraph 27min 完成;cbm 两次都死在 0.16% |
原始计划预期的终点状态是:dubbo 类仓库 parse-loop 从 4.7s 降到 1.0–1.5s、总时长约 7.5s,与 cbm 在其最强面上持平,同时保住 CodeGraph 已有的全部优势(增量 sync 2.4–2.8×、Agent A/B 决定性、调用图密度 1.3–2.3×、字节级确定性、受限硬件包络)。
值得注意的是,计划文档自己明确标注了被测量推翻的预期:多核机器上 dubbo 的 parse-loop 瓶颈后来被证明是"单写者 SQLite 落库"(占 dubbo parse-loop 的 94%),而不是抽取本身——8 个 wasm worker 早已把抽取 CPU 藏在主线程之后。因此内核的真实收益集中在"worker CPU 受限"的场景:2 CPU/6GB 的 CI 包络(excalidraw ~1.5×、dubbo ~1.25×、django 1.32×、prometheus 1.46×)与 vscode 级 Mac 场景(1.28×)。这一点在本文后文的"直接落库"小节中会看到完整的测量链条。
2. 内核的边界:什么被移植,什么永远不碰
迁移计划 §2 定义了让整个项目"安全"的那条边界。内核就是一个 napi-rs crate(codegraph-kernel/),链接 tree-sitter C 库与原生 grammar,只替换 parse worker 内部的 parse+extract 遍历,输入是每文件一个 (filePath, content, language),输出是扁平类型化缓冲区(nodes / edges / unresolved refs),每个文件只跨一次 JS 边界。它运行在既有的 ExtractionResult 契约之后。
永远不移植的部分(从第一天起原样工作):name-matcher 与 import-resolver、全部框架 resolver(src/resolution/frameworks/)、全部 36 个 synthesis 通道、MCP/explore、sync/watcher、installer。它们消费的是图和原始源码,而不是 AST 本身。
共存是永久性的:一种语言只有在等价门禁通过后才会路由到内核;其余语言可以永久留在 wasm 路径。没有"切换大日",按语言回滚 = 从 DEFAULT_ROUTED 里摘掉该语言。
分发策略:预编译的 .node 随现有发布管线按平台分发;同一 crate 编译成 wasm 是通用兜底。"安装零原生构建"这一性质保持不变。
从源码看,这个边界在 codegraph-kernel/src/lib.rs 的模块注释中被完整复述:
"Replaces ONLY the parse+extract walk inside the parse workers, behind the existing
ExtractionResultcontract…… Calls are synchronous by design: the existingParseWorkerPoolworkers already parallelize per-file, so each worker thread drives its own kernel call (do NOT rebuild the pool on the Rust side)."
即:不要在 Rust 侧重建线程池——TS 侧的 ParseWorkerPool 已按文件并行,Rust 侧只做同步调用。这是计划 Phase 0 第 1 条的原文要求。
3. Phase 0 脚手架与缓冲区契约 v1
计划文档 Phase 0 列了 5 步:crate 脚手架、缓冲区契约、通用 emitter、构建集成(预编译 + kill switch + 缺失即回退 wasm)、CI 断言原生 grammar 与 wasm grammar 来自同一 grammar 源码修订。实际交付记录(§3a)有几处对原计划的重要修正:
Crate 与构建。 codegraph-kernel/ 是 napi 3 + tree-sitter 0.25 的 crate,无 CLI 依赖。构建脚本 scripts/build-kernel.sh 执行 cargo build 并把产物暂存到 codegraph-kernel/prebuilds/<platform>-<arch>/codegraph-kernel.node,npm 侧对应 npm run build:kernel(见 package.json)。Crate 导出三个入口:extractFile、contractInfo、grammarInfo——在 lib.rs 中可见:contract_info() 返回 ABI 版本、内核版本、node-kind/edge-kind 表与支持语言列表;grammar_info(language) 返回该语言 grammar 的 ABI、node-kind 数与 field 表,专供"grammar 源码等价"门禁比对。
缓冲区契约 v1(五个 Buffer)。 布局定义在 codegraph-kernel/src/buffers.rs,TS 侧镜像是 src/extraction/kernel/layout.ts,两者必须字节级一致,任何布局变更都要 bump KERNEL_ABI_VERSION(当前为 2):
- meta(36 字节):
u8ABI 版本 + 3 字节 padding + node/edge/ref 计数、arena 长度、errors-JSON 在 arena 中的偏移与长度、内核侧耗时(f64,仅供内省)。 - node 行(96 字节):NodeKind 索引、可见性、三态 bool 标志位(isExported/isAsync/isStatic/isAbstract,每对 bit 是 present+value)、1-based 起止行/0-based 起止列、name、qualifiedName、id(Rust 侧用 sha256 计算,与 TS 的
generateNodeId字节一致——有测试钉死)、docstring、signature、decorators、typeParameters、returnType、extraJson逃生槽,以及为 Arc 3.2 预留的 per-node 指标槽。 - edge 行(44 字节):source/target 行索引(
NONE=0xFFFFFFFF时改用 IdStr)、EdgeKind 索引、provenance、行/列、metadataJson。 - ref 行(40 字节):fromNode 行索引、ReferenceKind(含 200 = function_ref)、flags(bit 0 是 ruby/php 移植时引入的
REF_FLAG_FILE_PATH——mixin/traitimplements引用携带抽取文件路径的唯一反规范化字段)、行/列、referenceName、candidates、fromNodeIdStr。 - arena:所有字符串以
(offset,len)指向 UTF-8 arena。
关键的 wire contract 事实:src/types.ts 中 NODE_KINDS/EDGE_KINDS 数组的顺序就是线上契约(EDGE_KINDS 为此改成了运行时数组)。
路由与 loader。 路由逻辑在 extractFromSource(tree-sitter.ts)内:先 tryKernelExtract,wasm 的 TreeSitterExtractor 作兜底,且任何内核错误都按文件回退。loader(src/extraction/kernel/loader.ts)的查找顺序是 CODEGRAPH_KERNEL_PATH → <pkgroot>/kernel/(发布包布局)→ <pkgroot>/codegraph-kernel/prebuilds/<plat>-<arch>/(源码运行)。loader 在路由前会校验 ABI 与 kind 表:陈旧的 .node 会静默降级到 wasm,CODEGRAPH_KERNEL_DEBUG=1 可看到原因。逃生槽落地为对已解码结果的 post(result, source)(而不是原始 buffer)——TS 逻辑要的是解码后的对象;见 src/extraction/kernel/index.ts 中的 POST_PASSES(目前为空表)。
grammar 等价门禁改变了一条产品路径。 行为等价测试(__tests__/kernel-grammar-parity.test.ts:逐 id 比对 ABI + node-kind + field 表)第一天就发现 tree-sitter-wasms 发布的是 2023 年的 TS/JS grammar(^0.20.x),而 crates.io 是新版。解决方案是从 crate 的精确修订 vendor 全新 wasm 进 src/extraction/wasm/(tree-sitter-typescript v0.23.2、tree-sitter-javascript v0.25.0,各仓库 CHECKED-IN 的 parser.c + tree-sitter-cli 0.25.10 + emcc)。结果是生产 wasm 的 TS/JS grammar 顺带被升级了,后续 R2/R3 的 parity diff 就是 grammar 中性的。此后铁律:crate 与 vendor wasm 必须一起 bump,否则 grammar-parity 测试失败——Cargo.toml 里每种 grammar 的注释都在反复强调这一点。
发布接线。 发布 workflow 的 kernel matrix job 构建 6 个目标平台(macos-14 ×2、ubuntu-22.04 ×2、windows-latest ×2),全部 continue-on-error——内核处处可选,工具链抖动永不阻塞发布;release-bundle 在 .node 存在时装进 lib/kernel/;发布作业用 CODEGRAPH_KERNEL_EXPECT=1 跑内核测试(缺二进制在那儿是失败,其他地方是跳过)。
4. R2:通用 .scm emitter 被推翻,改用 per-language walker
计划 Phase 0 原设想是"通用 emitter:.scm 查询文件 + 每语言小型 Rust 配置 + TS post 逃生钩子"。R2 的实战记录(§4a)明确宣布这一设计被取代:真实的 TS/JS parity 需要查询表达不了的逻辑(extractCall 的 receiver-限定 callee、store/RTK/组件识别、fn-ref 捕获与门控、value-ref 影子剪枝、docstring wrapper 上爬),因此 R2 用了一个约 1,900 行的专做 per-language walker(codegraph-kernel/src/tsjs/),逐函数、逐 bug 地镜像 TreeSitterExtractor 的 TS/JS 路径;emitter.rs 与 queries/ 被删除(git 里还有)。T1 语言(java/python/go)预期也都是 walker。
Parity 证据链(macOS):
scripts/kernel-parity.mjs逐文件对 kernel 与 wasm 两条路径做全对象多集合 diff——本仓库 353/353 文件、excalidraw 643/643 文件(10,650 nodes / 10,726 edges / 68,307 refs)零 diff。- 入库的 torture fixtures(tests/fixtures/kernel-parity/)覆盖组件/HOC/styled、zustand-with-middleware、RTK endpoints+hooks、vuex/pinia、fn-refs(含
this.x与影子门控)、value-refs(含影子剪枝)、装饰器、enum、类型别名成员+元组契约、re-exports、JSX。 - 由
__tests__/kernel-tsjs-parity.test.ts在npm test中严格全对象比对保活。
严格比对抓到了一个真实解码器 bug:decode 侧预填了 refs 的 filePath/language,而 wasm 抽取器根本不设这两个字段(store 端用 ?? filePath 反规范化)。修复后确立了接缝契约:"恰好是 extractFromSource 的返回值",而不是"store 加工后的产物"。
性能(M3 Pro,excalidraw 643 文件/7MB):抽取单线程 487ms(kernel)vs 1,255ms(wasm),2.6×,输出一致。端到端 init 在 11 核主机上只从 ~3.4s 移到 ~3.2s——大核机器上 parse 本来就是小且已被池并行的切片,收益集中在受限硬件(2 核 CI 类)与内核级 parse(R6)。
5. R3–R6:等价门禁、逐语言 DEFAULT-ON 与内核级复验
5.1 R3 门禁:TS/JS DEFAULT-ON 与"编码相关错误恢复"这一真实发现
门禁工具升级到 ORDER-sensitive(相同多重集合但发射顺序不同会改变 rowid 进而改变 resolution),并用 scripts/dump-graph.mjs 做自然键全库 dump:
- 图等价——字节级,不是 ≤0.5%。 全量
initdump diff(kernel vs wasm):express(13,712 行)、excalidraw(89,898)、**vscode(2,378,238 行)**全部字节一致;对照组 flask(Python)字节一致且耗时不变。抽取级顺序敏感 sweep:本仓库 352/354(+2 defer)、vscode 12,055/12,106(+51 defer),0 diff。 - 唯一重大发现——错误恢复与编码相关。 相同 grammar 字节(parser.c/scanner.h sha 校验)、相同 tree-sitter core(0.25.10),但带 parse error 的文件在 UTF-8(native)与 UTF-16(web-tree-sitter)下的错误恢复不同(把发散文件以 UTF-16 原生解析可精确复现 wasm 树)。发生率 0%(express)/ 0.31%(excalidraw)/ 0.42%(vscode)。策略:
has_error()的文件一律通过defer:信号让给 wasm 抽取器——错误文件按构造获得 parity,99.6%+ 文件走快路径,且 harness 在 defer 超过 10% 时失败(坏内核无法藏住)。同一信号后来还承载了第二个罕见场景(#1581):walker 按 AST 层递归,病态嵌套文件(clang 的 16,384 花括号parser_overflow.c)会撑爆原生栈,SIGSEGV 直接杀死整个 indexer 且 JS 无法捕获。codegraph-kernel/src/stack.rs 现在在每次递归入口检查线程真实栈边界(stack_guard!宏,见 lib.rs),run_guarded把越界的遍历转成defer:,文件落到 wasm 路径,进程存活——由__tests__/kernel-deep-nesting.test.ts钉死。 - 检索不变量:kernel 索引的 excalidraw 上,
mutateElement → renderStaticScene经 explore 端到端连通;合成边家族齐备(408 jsx-render / 46 react-render / 14 interface-impl / 1 callback)。 - Agent A/B 被证明是空转:字节级一致的 DB 意味着 A/B 无意义——与 #1320–#1322 性能 PR 相同的论据,靠 dump-diff 门禁放行。
- 性能:vscode init 105.4s → 82.1s(11 核 Mac,1.28×);excalidraw 在 2 CPU/6GB Linux 容器 6.2–7.1s → 4.3–4.8s(~1.5×)。
- 平台:Linux arm64 容器内 22 个内核测试在
CODEGRAPH_KERNEL_EXPECT=1下全绿;Windows VM 当时暂缓(缺.node会回退 wasm,风险良性)。 - 全套 2,465 个测试在 default-on 路由下通过——整个抽取测试语料在装了
.node的机器上对 TS/JS 实际走内核。
DEFAULT-ON 的代码落点在 src/extraction/kernel/index.ts 的 DEFAULT_ROUTED;当前它已扩展到 20 个语言(含 R7a 的 c/cpp 与 R7b 的 rust/csharp/ruby/php/swift/kotlin/r/lua/luau/scala/dart),每个语言条目都附带门禁证据注释(sweep 仓库、deferral 区间、"deferral 突增才是 walker bug 信号"之类的判定标准)。CODEGRAPH_KERNEL_LANGS=<langs|all> 替换默认集合,CODEGRAPH_KERNEL=0 全局杀。
5.2 R4:Java(含 Lombok 合成)与跨语言 ID 碰撞 bug
Java walker 在 codegraph-kernel/src/java.rs,覆盖 package 命名空间、javadoc、annotation→decorates、type_list 继承、static-final→constant、enum 成员、匿名类(含 TS 侧 0-based 行号的怪癖,bug-for-bug 镜像)、this.field 解包、Foo.getInstance().bar() 链编码、method_reference fn-refs(this::x / Type::m),以及完整的 Lombok 成员合成器(@Getter/@Setter/@Data/@Value/@Builder/@ToString/@EqualsAndHashCode/@Slf4j 家族,按精确 classQN::name 去重)。
门禁抓到的真实跨语言 bug:retrofit 压缩过的网站 JS 暴露了 fn-ref 去重与 value-ref 自检必须比较 node ID 字符串而不是表行——(kind,name,line) 相同的节点会 ID 碰撞(压缩单行代码里第 3 行可能有几十个 function e),TS 侧以 ${fromNodeId}|${name} 为键。修复落在两个 walker(每行 node_ids),且该 bug 在 tsjs 中同样潜在。
诚实的基准结论:dubbo 在 11 核 M3 Pro 上端到端基本持平(11.0–11.6s),因为该阶段瓶颈在主线程(读文件 + store + SQLite)而非 worker CPU;worker CPU 绑定时才见收益——dubbo 2 CPU/6GB Linux 27.8–28.6s → 22.3–22.8s(~1.25×)。
5.3 R5:Python + Go;R6:Linux 内核级复验
Python/Go walker(codegraph-kernel/src/python.rs、codegraph-kernel/src/go.rs)镜像了大量 TS 侧语义细节:Python 的 decorator 只认裸标识符(call-kind 怪癖镜像)、fn-in-class→method、self.x fn-ref 候选按裸名、属性 callee 的 namedChild(1) 回退;Go 的 receiver 方法 QN + "先出现的 struct" contains 边、embedding→extends、interface method_elems→method 节点、复合字面量保留包限定符、顶层 var/const 初始化子句归属(#693)、2 跳字段链(#1276)、New().Method() 重编码(#645/#608)、GO_SPEC fn-ref 层。
Sweep 100%(flask 83/83、django 3,035/3,038 三个错误文件 defer、gin 99/99、prometheus 978/979);全量 init dump 字节一致:flask(10,833 行)、gin(17,540)、django(360,794)、prometheus(213,758)。2 CPU/6GB 包络:django 22.0→16.7s(1.32×)、prometheus 15.0→10.3s(1.46×)。
R6 在 cg1212 容器(2 CPU/6GB)复验 Linux 内核:完成,exit 0,26.4min,与 ~27min 基线无回归,图规模相同(2,048,664 nodes / 6,405,964 edges)。分阶段耗时:scan 1.3s(70,239 文件)、parse-loop 371.9s(6.2min,未变)、edge-index-recreate 78.9s、callback-synthesis 350.3s、resolution 1,149.7s(19.2min——真正的墙,P1 领域)、maintenance 47.2s。计划中"parse 6m→2m"的预期被证伪:Linux 树是 63,810 个 C/H 文件对 422 个 Python,~99% C 是尚未移植的 T2 语言——预期顺延到 C/C++ 移植(后来的 R7a)。
6. 直接落库解码(direct-to-store)与"墙到底在哪"
R4 之后引入的直接落库:kernel 路由文件从 parse worker 把扁平 buffer 一路送到 store worker,在那边解码+收尾(tryKernelExtractRaw → ExtractionResult.kernelBuffers → KernelStoreBundle → decodeKernelBundle)。主线程每文件工作降到 O(1)+内容哈希,永不物化 per-node 对象,两次 postMessage 都传扁平字节;带 extract() hook 的框架文件保留解码路径。对应实现在 src/extraction/kernel/index.ts(tryKernelExtractRaw,校验 meta 的 ABI 版本、读计数与 errors-JSON)与 materializeKernelResult(主线程 store/测试等非写者路径的物化兜底)。
仪器化测量给出关键结论:dubbo 的 parse-loop 墙 94% 是 store-writer 忙时(kernel arm 4,493ms 中 4,202ms)。多核全量索引的墙是单写者 SQLite 摄入——不是抽取,不是主线程。d2s 仍改善 writer lane ~11%(4,726→4,202ms,buffer 省掉 writer 上的 structured-clone 反序列化)并解放主线程,但剩余 cbm 差距是 store 架构问题。
文档还完整记录了 store 弧的后续两轮(2026-07-19):第一轮 parse 期索引延迟(beginBulkParseLoad/endBulkParseLoad,仅全量索引生效)——dubbo parse-loop 4,306→1,787ms(−58%),Linux 内核 8c 包络 14.2min(机制:批量重建的 B 树比增量生长的更致密,后续所有索引中介读取省页);第二轮 resolution ref 索引窗口(beginBulkRefLoad/endBulkRefLoad)——Linux 8c resolution 423.4→275.9s,envelope ~11.0min。门禁始终是同一套:dubbo/gson 等 dump 字节一致、linux 计数精确 + dump sha 复现、测试套件全绿。
7. 按语言迁移追踪表:T1/T2/T3 分层与"移植配方"
计划 §4 用一张表管理每种语言,分层定义:T1 = 基本是 walker+映射配置;T2 = 需要留在 TS 的专属 pre/post pass;T3 = 不是普通 tree-sitter 遍历(独立/多 grammar 抽取器),最后迁或永不迁——wasm/TS 是它的好归宿。追踪表覆盖 README "Language Support" 的全部语言(34 个 logo,含 Metal、CUDA、Terraform/OpenTofu、Pascal/Delphi),并要求与 README 保持同步。
截至 R7b 完成(2026-07-20),20 个语言已 DEFAULT_ROUTED,全部走同一套门禁:小/中/大型真实仓库的 parity sweep 零 diff → 全量 init dump-diff 字节一致 ×3 → 进 DEFAULT_ROUTED + 测试 + changelog。几个有代表性的移植记录(完整版在追踪表行内,每行都列了仓库、文件数、deferral 与 quirk 清单文档路径):
- rust(#1371,首个 R7b):grammar 升到 tree-sitter-rust v0.24.2(crate + vendor wasm 同升);sweep 0-diff 于 ripgrep/tokio/rust-analyzer(rust-analyzer 271 个 defer 是 token-macro-table 源码——
T![~]、[$]——双臂同错,grammar 固有);dump 字节一致 ×3。quirk 清单见 rust-lang-kernel-port-checklist.md。 - csharp:唯一无需 grammar 预处理的移植(#717 vendor wasm 验证与 crate 0.23.5 表级相同);#237 的
#ifpreParse 通过路由点 hoist 留在 TS 侧。 - lua/luau:一个 walker 两个方言(ccpp 式 dialect flag);lua 是第二个 vendor grammar-C(v0.4.1 不在 crates.io),luau 是纯 crate pin。
- scala:第三个 vendor grammar-C(master@0aca5d0a6f,35MB parser.c,树中最大 grammar);生产 wasm 自 #91 起就用该修订,所以"grammar parity 行"就是全部对齐证明。
- dart:第四个 vendor grammar-C,byte-copy 的 wasm 消灭了 tree-sitter-wasms 的未固定 github 依赖隐患;逐 bug 移植了 sibling-body 双重遍历缺陷(有专门 fixture + bloc kind-census 抽检)。
- c/cpp(R7a,T2):单一双语 walker(codegraph-kernel/src/ccpp/),所有 preParse 空白处理留在 TS 侧——这是后来定义的"preParse hoist":语言的 offset-preserving
preParsehook 在两个内核入口之前运行(见 index.ts 的preParsedSource),使 c/cpp/metal/cuda 的空白化保持 TS 侧,双臂解析相同字节。门禁中实测 C/C++ parse-error 发生率每仓库 9–42%(此前语言是 0–0.42%),所以 sweep 加了--max-deferral 0.5。
未迁行的已知陷阱(vbnet 的 patched grammar + C external scanner 是"全树最高的 grammar 构建风险"、erlang 的 npm 包被劫持、cobol 的 copybook 解析、nix 的 ABI-15 重建等)都逐条列在追踪表里,供后续移植者直接消费。
任何移植期间不得回归的不变量(抽取侧,会在门禁中显现):node 元数据从源码重读、绝不持久化;parse 提交保持文件顺序(#1015);MAX_FILE_SIZE 跳过;生成文件检测;CODEGRAPH_PARSE_WORKERS 语义;框架 extract() hook 在 kernel pass 之后仍按文件跑在 TS 侧。
移植新语言的"被验证的配方"(约一天/T1)
- 读它的
languages/<lang>.ts配置以及 tree-sitter.ts 中它走过的每一个分支(visitNode 分派、extractCall 语言分支、继承子句、function-ref.ts 的 fn-ref spec、value-ref 剪枝)。逐 bug 移植——怪癖也要(每个 walker 头部注释列了自己的怪癖)。 - 加 crates.io grammar;从同一 tag vendor wasm(克隆 tag,parser.c 与 cargo registry 副本 sha 比对,用 CHECKED-IN 的 parser.c 跑
tree-sitter-cli 0.25.10 build --wasm,放进src/extraction/wasm/,加入 VENDORED_WASM_LANGS)——tree-sitter-wasms 对多数语言是 2023 年产物。 - Torture fixture + parity sweeps(小/中/大型真实仓库)→ 全量 init dump-diff 字节一致 → 加进 DEFAULT_ROUTED + 测试 + changelog。
8. 等价门禁(每语言必跑,无例外)
计划 §5 明确:不期望与手写抽取器字节级相同——门禁是行为等价:
- 图等价:对该语言 3 个真实仓库(小/中/大)分别用 wasm 路径与内核路径构建做全量索引,用 dump-graph 模式(自然键)dump。node/edge/ref delta ≤0.5%,且每个 diff 类别人工分类("上周 13-edge 超类型可见性 bug 正是这样抓到的——小 diff 是真的")。
- 检索不变量:该语言的规范流在
codegraph_explore中仍端到端连通;node 计数稳定;合成边抽检。 - Agent A/B 无回归,按标准方法论:
--model sonnet --effort high永远、每臂 ≥2 次、预热 daemon、CODEGRAPH_NO_PROMPT_HOOK=1、提示中禁止 subagent 委派。 - 性能:该语言仓库全量索引改善;未迁移的对照仓库不变;套件绿;平台敏感项过 Linux docker + Windows VM。
9. P1 性能弧:测量如何一次次重塑设计
这是文档中信息密度最高的部分(§7a.1–§7a.11),也最能体现该仓库的工程方法论。目标是"8 核普通主机上 Linux 内核级索引 <10min",起点是 R6 的 26.4min。
第一轮测量(§7a.1)推翻了前提:resolution 从来不是串行的——池 sizing(resolver-pool.ts 的 tryCreate:min(os.cpus().length − 2, 6),≥2 才启用)用的是不看 cpuset 的 os.cpus(),2 CPU 容器里看到 Docker VM 的 8 个 CPU,跑 6 个 worker 分时片 2 核。同时暴露两个结构性缺陷:8 核/7GB 容器 cgroup OOM(池只按核数 sizing,没有内存项)、WAL 膨胀(4.6GB DB 长出 22.2GB WAL——池化 superphase 连续写而 6 个 worker 持有重叠读快照,checkpoint 永远无法截断到最旧读者之前)。
**P1 实施弧(#1332–#1336)**用了三次失败/诊断性内核级运行才走对,每次失败都教会设计一个事实,且每个事实最终都在代码+测试中强制执行:
- WAL backlog 与 WAL 文件是两种资源:被动回填约束 backlog,文件只有当某次提交发现零读者标记才停止增长——实践中从未发生。收敛 = 回填 + 停泊屏障处 TRUNCATE + 4× 软上限的文件大小硬触发。dubbo:251MB→69MB 峰值,dump 字节一致。
- cgroup v2
memory.current计入可回收 page cache:parse 后读到 57MB 可用并静默禁用池;把inactive_file抵扣回去后同一台机器读到 4.4GB。 - 2 个真核上池输给串行(853s vs 1,150s:冷 worker 缓存+序列化+分时片超过并行度);池化 synthesis 被
cFnPtrEdges(306s/358s)Amdahl 锁死。最终 sizing:min(availableParallelism − 1, 6)+ 内存项 +CODEGRAPH_RESOLVE_WORKERS旋钮。 - 2 核上 parse 也需要 ≥2 worker(1 worker = +34%)。
- 静默失败模式烧掉了三个 25 分钟周期——所有 valve/sizing 决策现在都在
CODEGRAPH_SYNTH_TIMINGS/CODEGRAPH_WAL_VALVE_DEBUG下打印。
记录运行的数字:2c/6GB 从 26.4min(R6)→ 21.6 → 20.4min(#1336,WAL 峰值 1.57GB,−14×,计数 2,048,664/6,405,964 字节精确);8c/7GB 18.3min 无 OOM(池按内存项 sizing 到 4)。8 核结论:差距被精确刻画——当时判断 resolution 在核规模上是"核不变"的(835.9s pooled-4-on-8 ≈ 812.5s sequential-on-2),18.3min 里 ~14min 是 resolution+synthesis。
去二次化(#1339):CODEGRAPH_RESOLVE_PROFILE 的分阶段归属推翻了 founding 假设——resolveOne 只占 ~433s 批循环的 ~93s。最大一笔是 countGuard:每批 COUNT(*) 是 O(remaining),换成累加 SQLite changes,93.9s → 0.0s;envelope 19.3min。
cFnPtr 弧(#1341 → #1364 → #1365):该 pass 占内核级 synthesis 的 86%(306s)。先在容器内对活库做 standalone probe(~4min/轮替代 25min init)迭代到 2.07×,然后分两步原生化:step 1 是 TS 侧 fuse-then-link 重构(每文件只 strip 一次,survival filter 门控重放,−22%);step 2 是内核里的 codegraph-kernel/src/cfnptr.rs cfnptr_scan_files——16 文件一批摊薄 NAPI 边界,用手写字节状态机而非 regex crate(JS 引擎语义是 spec:\w/\b 是 ASCII 而 \s 是 Unicode 类,含 NBSP/U+2000-200A/FEFF;lastIndex 续扫;可观测的回溯维度结构性复现),且原生 stripper 按 UTF-16 code unit 置空(astral 字符两个空格)使输出与 TS stripper 字符串一致。整弧 cFnPtr 230s → 150.9s(−34%),callback-synthesis 250→171.1s。门禁:边缘流 diff(git/redis/vim/SameBoy 705/852/433/180 条边完全相同)、活库 probe-hash 复现、linux 计数精确 + dump sha 复现、全套测试绿 ×2。
per-ref 测量轮(§7a.6/§7a.7):新分阶段表推翻"核不变"判断——8c 时 settle 只有 3.6s(池双缓冲有效,worker 吞掉 3.17M 精确匹配);真正的 8c 异常是 writes-under-readers(deletes 37.7→118.8s:主线程 B 树写在读者在场时慢 ~3×)。五组判别运行证明机制是 WAL read-through 深度受读者 pin 约束,修复是池空闲边界处 worker 连接回收(ResolverPool.recycleWorkers + QueryBuilder.rebind,每 8 批重开一次只读连接,节奏 25→8 由测量迭代):superphase 715→633.6s(−11.4%),8c 最佳 14.8min,全程字节中性。两个缓存实验(nameCache 扩容、lazy candidates 解析)当天被测量杀死并回滚——"11µs 不是重取开销,floor 是一条索引查找周围的 per-ref JS"。
被杀死的最后一根杠杆(§7a.11):continuous-shallow WAL 探针的三臂结果显示 fold I/O 是固定预算——基线已把大部分藏在重叠的离线程 timer pass 里,强制更多 fold 只是把成本搬家(三臂在 ~2.5% 内收敛)。代码回滚,valve+recycling 保持为该族最优。诚实台账:<10min-on-8c 目标的剩余质量在 resolution ~575s superphase 与 parse ~190s writer floor——两者都是 store 架构弧,也就是 cbm 的 dubbo 标杆所在。
10. 路线图:内核之后的事
Arc 3 图丰富度(§7b,产品优先级,按序):test→subject 边(索引期一等公民)、per-node 代码指标(复杂度/cognitive/is_test——内核使这近乎免费,缓冲区契约已留 metrics 槽)、引用读写区分(USAGE vs WRITES,最高价值最高风险,范围限定到导出/状态相关符号)、异常流边、Doc Section 节点、IaC 节点。同时明确不值得追(在对标 cache schema 中验证过):per-variable 节点膨胀(占其节点数 85%)、DB 体积对齐、核心引擎里的相似向量。
Parked 项(需显式批准):单文件 SEA 二进制、团队共享图工件、完全原生重写——带数据地拒绝:"护城河(2,444 个测试、字节级确定性、本周两个被门禁抓到的 bug)活在 TS 参考实现里。"
11. 实战速查:构建、开关与验证命令
- 构建内核:
npm run build:kernel(需 rustup;等价于 scripts/build-kernel.sh,支持--target <rust-triple> --platform <plat-arch>交叉编译,发布 workflow 用它构建 6 目标 prebuild 矩阵)→ 暂存codegraph-kernel/prebuilds/<plat>-<arch>/codegraph-kernel.node→npm run build→npm test。 - 路由开关:
DEFAULT_ROUTED(src/extraction/kernel/index.ts)为默认集;CODEGRAPH_KERNEL_LANGS=<langs|all>替换集合;CODEGRAPH_KERNEL=0全局 kill;CODEGRAPH_KERNEL_PATH指定显式.node;CODEGRAPH_KERNEL_DEBUG=1查看未加载原因。 - Parity sweep(快速内环):
node scripts/kernel-parity.mjs <file-or-dir>... [--lang ts,tsx] [--max-deferral 0.5]——顺序敏感、全对象 diff;C/C++ 用 0.5 的 deferral 上限(宏密集 C 树 10–40% 文件带 parse error 是常态,git 19% / protobuf 26% / fmt 42%)。 - Dump gate(终极门禁):init 两次(kernel arm vs
CODEGRAPH_KERNEL=0),各自node scripts/dump-graph.mjs自然键全库 dump,cmp字节比对。 - 测试:脚手架测试 tests/kernel-scaffold.test.ts 覆盖 wire 契约/解码器/路由/kill switch/按文件回退,二进制缺失即跳过,
CODEGRAPH_KERNEL_EXPECT=1时缺二进制算失败;逐语言 parity 套件(如 tests/kernel-tsjs-parity.test.ts、kernel-cpppp-parity、kernel-rustlang-parity等)连同 torture fixtures 常驻npm test;tests/fixtures/kernel-parity/ 下是 20+ 语言的 torture 文件,且 TS/JS 系 fixture 有内存派生的 CRLF 变体钉死跨平台行为。
12. "已付学费"的陷阱清单(不要重学)
计划文档 §0a 专门留了一节给接手者的陷阱清单,每一条都对应一次真实事故:
- 错误恢复与编码相关(UTF-8 native vs UTF-16 web-tree-sitter)→ 所有 walker 对
has_error()文件发defer:信号;发生率 0–0.42%,harness 在 >10% defer 时失败。 - Node ID 碰撞于同
(kind,name,line)——压缩代码里是常态。任何以 node ID 为键的去重/自检必须比较 ID 字符串而不是表行(每个 walker 的node_idsvec)。 - 位置与 JS 字符串切片是 UTF-16 的(
textutil::col16/slice_utf16)——那是 web-tree-sitter 报告的东西,也是.slice(0,100)的含义。 - 接缝契约恰好是 extractFromSource 的返回值——refs 不带反规范化的 filePath/language。严格全对象 parity 比对的存在就是为了掩盖不了这种松散。
- Grammar bump 时 crate 与 vendor wasm 必须同动,否则 kernel-grammar-parity 失败。
- JS 多行
^锚点在\r(和 U+2028/U+2029)之后;regex crate 的(?m)^只认\n——Windows autocrlf 检出时 JS 参考实现的贪婪\s*会吃掉 CRLF 对的\n,清洗后的 docstring 留一个裸\r。由 O2 Windows 验证腿抓到(6 个 parity 失败),修复在docstring.rs的js_multiline_strip;任何未来 walker 的(?m)正则都需同等审视。 - 性能主张:先测量再相信——本计划自己的 §1/§6 预期被纠正了两次(多核 parse-loop 墙 = store-writer,§4d;cg1212 parse = C-bound,§4f)。
13. 结语:一个"测量驱动 + 字节级门禁"的迁移样本
CodeGraph 的 Rust 内核迁移值得研究的原因不只是 20 个语言的 DEFAULT-ON 结果,而是它展示了一套可复用的方法论:用一个可选的原生层(处处可回退、缺失无害)替换热路径;以"双臂字节级一致"代替"百分比容差"作为正确性定义;每轮性能工作先跑判别性测量(cpuset 盲的池 sizing、WAL read-through 深度、fold I/O 固定预算),失败运行产出设计事实而非废品;并把所有预期修正写回计划文档本身(SUPERSEDED 标注、被杀死的杠杆与数字一并归档)。对要在既有 JS 索引/工具链中嵌入原生加速的团队,这份文档与仓库中的门禁工具(kernel-parity.mjs、dump-graph.mjs、grammar-parity 测试、CODEGRAPH_KERNEL_EXPECT 约定)就是可直接对照的工程蓝图;架构细节的姊妹文档见 native-extraction-kernel.md。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00