AS-BUILT 校核(T27):本文与已提交代码一致。节点 ID 公式 (
codegraph_core::node_id::generate_node_id,format!("{file_path}:{kind}:{name}:{line}")→ sha256 →{kind}:{hex[..32]})、 文件节点字面量file:{file_path}、内容哈希(hash_content)以及FrameworkResolver仅扩展点(crates/codegraph-resolve/src/framework.rs, 零具体实现)均按本文实现。
This document defines the byte-level and semantic parity contract between the
Rust port and the pinned upstream TypeScript reference. The current authoritative
fixture is crates/codegraph-bench/fixtures/mini/; the live reference outputs are
stored under reference/golden/mini/.
The symbol-node helper computes:
sha256("{filePath}:{kind}:{name}:{line}") -> hex -> first 32 chars
id = "{kind}:{hash32}"
Rust mirrors this in codegraph_core::node_id::generate_node_id().
Inputs are part of the compatibility contract:
filePath: project-relative path with/separators, for examplesrc/app.ts.kind: the serializedNodeKind::as_str()value, for examplefunction,class,method, orimport.name: the exact extracted name. Import nodes use the module specifier, for example./math.line: 1-based start line. The tree-sitter call site passesnode.startPosition.row + 1.
Tree-sitter file nodes do not call generateNodeId(). The tree-sitter file-node special case uses the literal ID:
file:{filePath}
The mini golden data verifies this for all three file nodes, for example
file:src/app.ts. Non-file nodes in the same golden set, including imports, use
the hashed {kind}:{32hex} form.
Some custom extractors call generateNodeId(..., 'file', ..., 1) for their own
file-like nodes; that is a separate custom-extractor path and is not the
tree-sitter file node represented in the mini golden.
The content hash (hashContent)
stores a full lowercase SHA-256 hex digest of the file content in
files.content_hash.
Rust mirrors this in codegraph_core::node_id::hash_content(). The test fixture
hashes are cross-checked against:
sqlite3 reference/golden/mini/colby.db \
"select path,content_hash from files order by path;"Tier-1 fields must match the reference output byte-for-byte and are allowed to fail tests on any mismatch:
nodesrows, excluding inherently time-varyingupdated_at.- Node IDs, including the
file:{path}tree-sitter file-node special case. files.content_hashvalues.- SQLite schema and FTS5 schema/triggers/indexes captured from
.schema.
Tier-2 data may be compared as unordered multisets when insertion order or rowid allocation is not semantically stable:
edgeskeyed by(source, target, kind)plus relevant metadata.unresolved_refskeyed by(from_node_id, reference_name, reference_kind)and source location.
Tier-3 output can differ only when the difference is intentionally documented in
KNOWN_DIFFS.md:
- Query output formatting.
- MCP response formatting and summaries.
- Other presentation-layer or non-deterministic fields that preserve semantics.
Node IDs are Tier-1 deterministic. Given the same relative path, serialized
NodeKind, extracted name, and 1-based start line, Rust must produce exactly the
same bytes as the reference. The golden test in crates/codegraph-core/src/node_id.rs
loads all 13 real nodes from reference/golden/mini/colby.nodes.json and proves
that every ID reproduces.
The executable oracle lives in crates/codegraph-bench/src/oracle/ and is the
library entry point for later cross-implementation runs. Later tasks should call:
codegraph_bench::oracle::assert_equivalent(rust_db, golden_dir)For the current mini fixture:
cargo test -p codegraph-bench --test equivalence -- --nocaptureCanonical fixture files are committed under reference/golden/<corpus>/:
nodes.jsonedges.jsonrefs.jsonfiles.jsonschema.sql
Regenerate from a reference SQLite database with:
cargo run -p codegraph-bench --bin bench -- \
--gen-golden reference/golden/mini/colby.db reference/golden/miniThe canonicalizer strips inherently unstable timestamp columns
(nodes.updated_at, files.modified_at, files.indexed_at), parses JSON text
columns before re-serializing them with deterministic key order, asserts all
stored paths are relative / paths, ignores edges.id and
unresolved_refs.id, and normalizes .schema text with the same rules used by
crates/codegraph-store/tests/schema_parity.rs.
A second golden fixture, reference/golden/godot/, guards Godot-specific
extraction that the mini fixture cannot reach — there are no .gd/.tscn/
project.godot files in mini. It captures the framework-resolver output for:
- F1 — an autoload call (
GameFlow.return_to_map()) resolving to the unique same-namedfuncin the bound script (aframework-resolvedCallsedge), alongside the coexisting singleton-constant edge. - F2 — signal-handler connections (
.connect(_on_pressed.bind(button))and.connect(Callable(self, "_on_input"))) resolving to the handlerfuncs (Callsedges). - F3 — a
.tscnExtResourcescript attachment (main.tscn→stage_manager.gd), captured as ascript_attachunresolved-ref subkind. - UID-form autoloads — a sidecar-UID SCRIPT autoload
(
EffectManager="*uid://…"resolved througheffect_manager.gd.uid→effect_manager.gd, with anEffectManager.apply_effect()F1 method edge) and a header-UID SCENE autoload (ComboUi="*uid://…"resolved throughcombo_ui.tscn'suid=header, registration-only). Both emit anAutoload-subkind UNRESOLVED ref; the.gd.uidsidecar is NOT indexed (it maps toLanguage::Unknown, so it is neither a file record nor a node).
The minimal source corpus lives at crates/codegraph-bench/fixtures/godot/
(project.godot, game_flow.gd, stage_manager.gd, main.tscn,
effect_manager.gd, effect_manager.gd.uid, combo_ui.tscn).
Regenerate the committed database + canonical JSON reproducibly from the corpus:
# 1. Copy the corpus to a clean directory (keeps the workspace index out of it).
rm -rf /tmp/cg-fixture-godot
cp -r crates/codegraph-bench/fixtures/godot /tmp/cg-fixture-godot
# 2. Index it with OUR binary (never hand-write the golden).
cargo build --release -p codegraph-rs
CODEGRAPH_NO_DAEMON=1 CODEGRAPH_NO_WATCH=1 \
./target/release/codegraph init /tmp/cg-fixture-godot
# 3. Commit the produced database as the fixture's colby.db.
cp /tmp/cg-fixture-godot/.codegraph/codegraph.db reference/golden/godot/colby.db
# 4. Dump the canonical golden JSON + schema from that database.
cargo run -p codegraph-bench --bin bench -- \
--gen-golden reference/golden/godot/colby.db reference/golden/godotThe extraction and --gen-golden steps are both byte-stable: re-running the
index or the dump reproduces identical nodes.json/edges.json/refs.json/
files.json/schema.sql. The generated_golden_matches_committed_godot_fixture
and upstream_db_is_self_equivalent_to_godot_golden tests in
crates/codegraph-bench/tests/equivalence.rs enforce this.
Two properties this recipe does NOT claim, for every fixture below as well:
colby.dbis not byte-reproducible. SQLite's header carries a change counter, so a freshly indexed database differs from the committed one in the first page even when every row matches. Only the--gen-goldenartifacts are compared byte-for-byte; the.dbis an input to that dump, not a golden.schema.sqlrecords.schemastatement ORDER, which can shift. The order reflects how the current binary creates its objects. Regenerating a fixture whose committedschema.sqlwas produced by an earlier binary can therefore reorder statements (e.g.idx_edges_identity) with no schema change. Always regenerateschema.sqlfrom the database you are committing — steps 3 and 4 do exactly that, which keeps the pair self-consistent — and review an order-only diff as expected rather than as drift.
The schema normalization helper is replicated inside codegraph-bench rather
than extracted into codegraph-store to avoid changing store source during the
parallel CRUD work. It preserves .schema statement order, strips optional
IF NOT EXISTS from CREATE TABLE/INDEX/VIRTUAL TABLE/TRIGGER, trims line
whitespace, removes blank lines, joins statements with ;\n, and enforces a
final ;\n.
A third golden fixture, reference/golden/ruby/, guards Ruby receiver.method
extraction (upstream #1110) that the other fixtures cannot reach — there are no
.rb files in mini/godot. It captures the four receiver-bearing-call edge
shapes:
- instance-method call —
@logger.log(message)resolving toLogger#log(aCallsedge to the METHOD name, not the receiver). - class-method call —
Formatter.shout(message)resolving toFormatter.shout(aCallsedge to the method name). Const.newconstruction —Logger.newrecorded as anInstantiatesedge to the receiver classLogger, not aCallsedge tonew.- bare
include—include Greetingstill records anImplementsedge (regression guard: the receiver.method path must not disturb it).
The minimal source corpus lives at crates/codegraph-bench/fixtures/ruby/
(service.rb, logger.rb).
Regenerate the committed database + canonical JSON reproducibly from the corpus:
# 1. Copy the corpus to a clean directory (keeps the workspace index out of it).
rm -rf /tmp/cg-fixture-ruby
cp -r crates/codegraph-bench/fixtures/ruby /tmp/cg-fixture-ruby
# 2. Index it with OUR binary (never hand-write the golden).
cargo build --release -p codegraph-rs
CODEGRAPH_NO_DAEMON=1 CODEGRAPH_NO_WATCH=1 \
./target/release/codegraph init /tmp/cg-fixture-ruby
# 3. Commit the produced database as the fixture's colby.db.
cp /tmp/cg-fixture-ruby/.codegraph/codegraph.db reference/golden/ruby/colby.db
# 4. Dump the canonical golden JSON + schema from that database.
cargo run -p codegraph-bench --bin bench -- \
--gen-golden reference/golden/ruby/colby.db reference/golden/rubyLike the Godot fixture, both the index and the dump are byte-stable, and the
generated_golden_matches_committed_ruby_fixture and
upstream_db_is_self_equivalent_to_ruby_golden tests in
crates/codegraph-bench/tests/equivalence.rs enforce it.
The dedicated reference/golden/python/ fixture guards Python bare
class-as-value function references without changing the shared mini corpus.
Its three-file source corpus lives at crates/codegraph-bench/fixtures/python/
and pins six positive References edges:
- same-file class values in a direct return, assignment RHS, registry-pair value, call argument, and list literal;
- one cross-file
ImportedClassvalue. The import syntax is present, but real Python nodes are not marked exported, so import Gate 3b is unreachable. This edge is intentionally resolved by Gate 3a's unique cross-file name match at confidence 0.8.
It also pins two negative boundaries: a tuple return does not recurse into
TupleA/TupleB, and a bare handler parameter remains an unresolved
function_ref rather than resolving to a same-named method. The undefined
register(...) calls used by the argument and method shapes legitimately remain
as two unresolved calls rows.
Regenerate the committed database and canonical artifacts from a clean corpus:
# 1. Copy the corpus to a clean directory (keeps the workspace index out of it).
rm -rf /tmp/cg-fixture-python
cp -r crates/codegraph-bench/fixtures/python /tmp/cg-fixture-python
# 2. Index it with OUR release binary (never hand-write the golden).
cargo build --release -p codegraph-rs
CODEGRAPH_NO_DAEMON=1 CODEGRAPH_NO_WATCH=1 \
./target/release/codegraph init /tmp/cg-fixture-python
# 3. Commit the produced database as the fixture's colby.db.
mkdir -p reference/golden/python
cp /tmp/cg-fixture-python/.codegraph/codegraph.db reference/golden/python/colby.db
# 4. Dump canonical JSON + schema from that exact database.
cargo run -p codegraph-bench --bin bench -- \
--gen-golden reference/golden/python/colby.db reference/golden/pythonAs with every fixture, compare only nodes.json, edges.json, refs.json,
files.json, and schema.sql byte-for-byte. colby.db itself is not a
byte-reproducibility contract because SQLite updates its header change counter.
Regenerate schema.sql from the database being committed; statement ordering
may differ between binary versions, but its normalized statement set must not.
The tests generated_golden_matches_committed_python_fixture and
python_db_is_self_equivalent_to_python_golden enforce database/artifact
self-equivalence.
The dedicated reference/golden/kotlin/ fixture guards Kotlin callable
signatures (#1495) without changing a shared corpus. Its single source file,
crates/codegraph-bench/fixtures/kotlin/signatures.kt, pins five positive
shapes: an explicit return, an inferred return, a generic return, a multiline
class method with a nullable generic return, and an extension function. The
Processor primary constructor is the negative boundary: the class signature
stays null and no constructor method is synthesized.
Regenerate the committed database and canonical artifacts from a clean corpus:
rm -rf /tmp/cg-fixture-kotlin
cp -r crates/codegraph-bench/fixtures/kotlin /tmp/cg-fixture-kotlin
cargo build --release -p codegraph-rs
CODEGRAPH_NO_DAEMON=1 CODEGRAPH_NO_WATCH=1 \
./target/release/codegraph init /tmp/cg-fixture-kotlin
mkdir -p reference/golden/kotlin
cp /tmp/cg-fixture-kotlin/.codegraph/codegraph.db reference/golden/kotlin/colby.db
cargo run -p codegraph-bench --bin bench -- \
--gen-golden reference/golden/kotlin/colby.db reference/golden/kotlinAs with every fixture, only the five text artifacts are byte-compared;
colby.db is not byte-reproducible. Compare schema.sql by normalized statement
set when an older binary changes statement ordering. The tests
generated_golden_matches_committed_kotlin_fixture and
kotlin_db_is_self_equivalent_to_kotlin_golden enforce database/artifact
self-equivalence.
A fourth golden fixture, reference/golden/cpp/, guards C++ base_class_clause
inheritance extraction (upstream #1043) that the other fixtures cannot reach —
there are no .cpp/.hpp files in mini/godot/ruby. It captures the
general C++ inheritance shapes plus templated-base stripping:
- single public base —
class D : public Baseresolving toBase(anExtendsedge; thepublicaccess specifier is skipped). - templated base (stripped) —
class T : public Container<int>resolving toContainer(template args stripped to the base name). - multiple inheritance —
class Both : public Container<char>, public Plainemitting twoExtendsedges (toContainerandPlain). - struct base —
struct S : Container<double>resolving toContainer(struct inheritance goes through the same path as class inheritance). ::-qualified templated base —class Q : public ns::Tpl<int>recording anExtendsref tons::Tpl(qualified head kept, template args stripped). Since the C++ namespace-prefix work (Release D) storesTpl's qualified name asns::Tpl, this ref now RESOLVES to a realExtendsedge (Q→ns::Tpl) inedges.jsoninstead of remaining an unresolved ref.
Three further files exercise the Release D C++ extraction gains:
- namespace prefix +
ns::fn()resolution —namespaced.cppdefinesnamespace ns { void compute() {} }(qualified namens::compute) and callsns::compute()fromrun_namespaced; the call resolves to aCallsedge via the existing qualified-name matcher (no resolver change). - template-argument call stripping —
templated_call.cppdefinestemplate <typename T> void process(T)and callsprocess<int>(0); the<int>template args are stripped at extraction so the call links toprocess. - Unreal-Engine reflection-macro recovery +
.hC++ detection —ue_actor.his a lean UE header whose only C++ signal isclass ENGINE_API UFoo : public UObjectplus line-leadingGENERATED_BODY()/UPROPERTY(...)/UFUNCTION(), a member-levelENGINE_API, and no explicitpublic:. Content sniffing reclassifies the.hto C++, and the offset-preserving pre-parse blanking recovers theUFooclass + itsExtends UObjectclause (both dropped before).
Five further files exercise the Batch B C/C++ gains:
-
C leading attribute macros (upstream #1311) —
attr_macro.cis the one.cfile in this corpus (extension-mapped toLanguage::C, so it guards the C walker, not the C++ one). It#defines an attribute macro + aVOIDmacro,typedefsUINT32, and declares four functions: two behind the macro (GoodNamewith a macro return type,LostNamewith a typedef'd one), one without it (NoAttr, the control), and one pointer-returning (PtrRet). tree-sitter's C grammar reads the macro as the type and the real return type as the declarator, so before the fix these indexed under the RETURN TYPE's name (VOID/UINT32); the golden now pins all four under their real names with their real return types. The blank fires ONLY because#define SEC_ATTR __attribute__((section(".init")))is visible IN THIS FILE — the pass demands same-file#defineproof that a leading token is attribute-like, so this fixture also pins that evidence requirement, not just the macro's name. -
namespaced out-of-line method + fully-qualified call (upstream #1310) —
namespaced_member.hppdeclaresnamespace simulator { class ManifestStartup }with a staticApply, andnamespaced_member.cppdefines it OUT OF LINE inside the same namespace block, then calls it through the fully-qualified path (simulator::ManifestStartup::Apply(1)) from a function OUTSIDE the namespace. The receiver qualifier is spelled relative to the namespace, so the golden pins the method atsimulator::ManifestStartup::Apply(matching the class node'ssimulator::ManifestStartup) plus theCallsedgerun_manifest → simulator::ManifestStartup::Applyresolved byqualified-name— the edge that a namespace-less qualifier loses. -
out-of-line template method receivers (upstream #1309) —
template_method.cppdeclarestemplate <typename T> class Boxwithget/setand defines both OUT OF LINE (template <typename T> T Box<T>::get()). The receiver qualifier carries<T>, which the class node never spells, so the golden pins both methods atBox::get/Box::set(template args stripped) plus thecontainsedges fromclass Boxto each — the link that aBox<T>::qualifier breaks.
A further file exercises the Batch B explicit-operator gain (upstream #1268):
- explicit operator calls —
operators.cppdefinesstruct Vec2withoperator+/operator[]/ a plainget, then calls each through the EXPLICIT syntax (a.operator+(b),a.operator[](3),p->operator+(b)) plus one plaina.get()control. tree-sitter-cpp strands theoperator_namein an ERROR child, so before the fix the extractor emitted the bare receiver (a) and no edge existed; the golden now carries fourCallsedges resolved byinstance-methodat confidence 0.9 (Vec2::operator+twice,Vec2::operator[],Vec2::get).
The minimal source corpus lives at crates/codegraph-bench/fixtures/cpp/
(attr_macro.c, base.hpp, derived.cpp, namespaced.cpp,
namespaced_member.cpp, namespaced_member.hpp, operators.cpp,
template_method.cpp, templated_call.cpp, ue_actor.h). The inheritance base
classes live in a .hpp file (not .h,
which maps to Language::C by extension); ue_actor.h deliberately uses .h to
guard the content-based C++ reclassification, and attr_macro.c uses .c so the
C walker (not the C++ one) is the thing under test.
Regenerate the committed database + canonical JSON reproducibly from the corpus:
# 1. Copy the corpus to a clean directory (keeps the workspace index out of it).
rm -rf /tmp/cg-fixture-cpp
cp -r crates/codegraph-bench/fixtures/cpp /tmp/cg-fixture-cpp
# 2. Index it with OUR binary (never hand-write the golden).
cargo build --release -p codegraph-rs
CODEGRAPH_NO_DAEMON=1 CODEGRAPH_NO_WATCH=1 \
./target/release/codegraph init /tmp/cg-fixture-cpp
# 3. Commit the produced database as the fixture's colby.db.
cp /tmp/cg-fixture-cpp/.codegraph/codegraph.db reference/golden/cpp/colby.db
# 4. Dump the canonical golden JSON + schema from that database.
cargo run -p codegraph-bench --bin bench -- \
--gen-golden reference/golden/cpp/colby.db reference/golden/cppLike the Ruby fixture, both the index and the dump are byte-stable, and the
generated_golden_matches_committed_cpp_fixture and
cpp_db_is_self_equivalent_to_cpp_golden tests in
crates/codegraph-bench/tests/equivalence.rs enforce it.
A fifth golden fixture, reference/golden/metal/, guards Metal Shading Language
support (upstream #1121 / cc89146). MSL ≈ C++14 and rides the existing
tree-sitter-cpp grammar — .metal maps to Language::Cpp with no new
Language variant. It guards the .metal-gated [[attribute]] blank: MSL's
post-declarator attributes (float4 position [[position]];) otherwise misparse a
struct field into a spurious extends edge from the struct to the field's own
type. The corpus (crates/codegraph-bench/fixtures/metal/shader.metal) defines
float4/float2 structs, a VertexIn struct whose fields carry
[[position]]/[[user(locn0)]] attributes on those self-defined types, and a
vertex_main function that calls a tint helper. The golden must show:
shader.metalwith"language": "cpp";VertexIn/float4/float2as ordinary structs with noExtendsedge (the attribute blank prevents the spuriousVertexIn extends float4);- the intra-shader
vertex_main→tintCallsedge.
The [[attribute]] blank fires ONLY for .metal files; a .cpp/.hpp with a
regular [[nodiscard]] attribute is byte-identical through pre-parse (proven by
the metal_attribute_blanked_only_for_dot_metal unit test in lang/cpp.rs).
A sixth golden fixture, reference/golden/cuda/, guards CUDA support (the
CUDA-language parts of upstream #1172 / e1a8d88). CUDA ≈ C++ + dialect tokens
and likewise rides tree-sitter-cpp — .cu/.cuh map to Language::Cpp with
no new Language variant. It guards the CUDA pre-parse blank (execution-space
specifiers + <<<grid, block>>> launch configs, offset-preserving and
brace-balance-checked) and macro-defined-kernel name recovery. The corpus
(crates/codegraph-bench/fixtures/cuda/kernel.cu) defines a __global__ void add_kernel, a templated __global__ scale_kernel, a
DEFINE_FLASH_FORWARD_KERNEL(my_kernel, …) macro-defined kernel, and a launch
host function with a plain launch and a templated launch. The golden must show:
kernel.cuwith"language": "cpp";add_kernel,scale_kernel,my_kernel,launchas functions — the macro kernel under its real namemy_kernel, NOTDEFINE_FLASH_FORWARD_KERNEL;- host→kernel
Callsedgeslaunch→add_kernelandlaunch→scale_kernel(the<<<…>>>blank restores the call; the templated launch rides the already-landed template-argument strip).
The CUDA blank fires for .cu/.cuh files OR any C/C++-family file whose content
carries a strong CUDA marker (__global__/__device__/__constant__/
cudaStream_t), so CUDA living in .h/.hpp headers is recognized.
Regenerate both new fixtures reproducibly (identical recipe to the C++ fixture,
substituting metal/cuda):
rm -rf /tmp/cg-fixture-metal && cp -r crates/codegraph-bench/fixtures/metal /tmp/cg-fixture-metal
cargo build --release -p codegraph-rs
CODEGRAPH_NO_DAEMON=1 CODEGRAPH_NO_WATCH=1 ./target/release/codegraph init /tmp/cg-fixture-metal
cp /tmp/cg-fixture-metal/.codegraph/codegraph.db reference/golden/metal/colby.db
cargo run -p codegraph-bench --bin bench -- --gen-golden reference/golden/metal/colby.db reference/golden/metal
# …and the same for cuda.The generated_golden_matches_committed_{metal,cuda}_fixture and
{metal,cuda}_db_is_self_equivalent_to_{metal,cuda}_golden tests in
crates/codegraph-bench/tests/equivalence.rs enforce byte-stability.
A seventh golden fixture, reference/golden/arkts/, guards ArkTS (HarmonyOS /
OpenHarmony .ets) extraction (the extraction slice of upstream #1186 /
9915221). Unlike Metal/CUDA, ArkTS is a new Language::ArkTs variant backed
by a dedicated tree-sitter-arkts grammar — a TypeScript-superset fork that
understands the ArkUI @Component struct syntax tree-sitter-typescript cannot
parse. .ets maps to Language::ArkTs; plain .ts stays TypeScript. The corpus
(crates/codegraph-bench/fixtures/arkts/component.ets) has an import, a global
function helper, a function driver that calls helper, a @Component struct MyView with a build() method, and a plain class Model. The golden must show:
component.etswith"language": "arkts";MyViewas aNodeKind::Structwith itsbuildmethod as a member (via the existingextract_structpath — no walker change);helper/driverfunctions, theModelclass, and the../fooimport node;- the
driver→helperCallsedge (plaincall_expression).
The ArkUI dynamic-dispatch / callback-synthesizer bridges are DEFERRED — the
port has no callback synthesizer. So ARKTS_SPEC uses call_types = ["call_expression"] only (no arkui_component_expression component-instantiation
edges) and does NOT override extract_modifiers (the decorator hook). Adding the
variant is byte-neutral for colby.schema.sql (language is a stored TEXT value,
not DDL) and for the six existing goldens (none holds a .ets file).
Regenerate reproducibly (identical recipe to the C++ fixture, substituting
arkts):
rm -rf /tmp/cg-fixture-arkts && cp -r crates/codegraph-bench/fixtures/arkts /tmp/cg-fixture-arkts
cargo build --release -p codegraph-rs
CODEGRAPH_NO_DAEMON=1 CODEGRAPH_NO_WATCH=1 ./target/release/codegraph init /tmp/cg-fixture-arkts
cp /tmp/cg-fixture-arkts/.codegraph/codegraph.db reference/golden/arkts/colby.db
cargo run -p codegraph-bench --bin bench -- --gen-golden reference/golden/arkts/colby.db reference/golden/arktsThe generated_golden_matches_committed_arkts_fixture and
arkts_db_is_self_equivalent_to_arkts_golden tests in
crates/codegraph-bench/tests/equivalence.rs enforce byte-stability.
An eighth golden fixture, reference/golden/solidity/, guards Solidity (.sol)
extraction (upstream #1170 / 1441933). Solidity is a new Language::Solidity
variant backed by a dedicated tree-sitter-solidity grammar. .sol maps to
Language::Solidity. The corpus (crates/codegraph-bench/fixtures/solidity/) has
an IERC20.sol interface and a Token.sol that imports it, declares a file-level
error and a file-level constant, and a contract Token is IERC20 carrying a
state variable, an event, an enum, a struct, a modifier, a constructor,
fallback/receive, and a transfer function guarded by the modifier that
emits the event, plus a library Math. What it guards:
- both
.solfiles with"language": "solidity"; contract Token/library MathasNodeKind::Class,interface IERC20asNodeKind::Interface,struct HolderasNodeKind::Struct,enum StatusasNodeKind::Enumwith itsActive/Closedmembers (bare-textenum_value);- functions/modifiers/methods, including the synthetic
constructor/fallback/receivemethod names (nameless grammar nodes); - state variable / struct member /
event/errorasNodeKind::Fieldname nodes (direct-namefield, novariable_declarator), including the file-levelUnauthorizederror and the file-levelMAX_SUPPLYconstant; - the
./IERC20.solimport node +importsedge; is-inheritance emitted as anExtendsref, promoted by the EXISTING resolver to anImplementsedgeToken → IERC20(interface target, present in-corpus);emit/headermodifier_invocationCallsedges (transfer → Transfer,transfer → onlyOwner), resolved to same-file targets.
Because the fixture is fully self-contained, every ref resolves in-corpus, so
refs.json is empty and edges.json holds only RESOLVED edges — the expected
post-resolution state. No FrameworkResolver impl is involved; the
Extends → Implements promotion is the same path Java/C# use
(resolver.rs:1231-1247). Adding the variant is byte-neutral for
colby.schema.sql (language is a stored TEXT value, not DDL) and for the seven
existing goldens (none holds a .sol file).
Regenerate reproducibly (identical recipe to the ArkTS fixture, substituting
solidity):
rm -rf /tmp/cg-fixture-solidity && cp -r crates/codegraph-bench/fixtures/solidity /tmp/cg-fixture-solidity
cargo build --release -p codegraph-rs
CODEGRAPH_NO_DAEMON=1 CODEGRAPH_NO_WATCH=1 ./target/release/codegraph init /tmp/cg-fixture-solidity
cp /tmp/cg-fixture-solidity/.codegraph/codegraph.db reference/golden/solidity/colby.db
cargo run -p codegraph-bench --bin bench -- --gen-golden reference/golden/solidity/colby.db reference/golden/solidityThe generated_golden_matches_committed_solidity_fixture and
solidity_db_is_self_equivalent_to_solidity_golden tests in
crates/codegraph-bench/tests/equivalence.rs enforce byte-stability.
A ninth golden fixture, reference/golden/nix/, guards Nix (.nix) extraction
(upstream #1190 / 7f32513, the extraction slice only). Nix is a new
Language::Nix variant backed by a dedicated tree-sitter-nix grammar.
.nix maps to Language::Nix. Because Nix is an expression language with no
C-family class/struct/method/enum node kinds, NIX_SPEC has all-empty
type-sets and the extraction is driven by the Language::Nix-guarded
visit_nix_node walker extension. The corpus
(crates/codegraph-bench/fixtures/nix/) has a top-level lambda
{ pkgs, lib }: …, a let … in, a returned attrset with bindings, an
import ./foo.nix, a pkgs.callPackage ./bar.nix { }, an inherit lib;, an
imports = [ ./foo.nix ./bar.nix ] module list, and a curried build = { src }: … lambda. What it guards:
- all three
.nixfiles with"language": "nix"; - a
bindingwhose value is a lambda →NodeKind::Functionwith a formatted curried-param signature (build→{ src },double→(x)); - a non-lambda
bindingand eachinherited name →NodeKind::Variable; import ./foo.nix,callPackage ./bar.nix { }, and the literalimports-list paths →NodeKind::Importnodes +Importsrefs;- an
apply_expressioncall →Callsref, deduped across curried levels (pkgs.mkDerivation,pkgs.callPackage,stdenv.mkDerivation).
The imports/callPackage path refs to ./foo.nix / ./bar.nix resolve
in-corpus (both files exist), so refs.json retains only the three unresolved
Calls refs — the module-system option-path synthesizer, lexical-scope
resolution gates, callback synthesizer, and import-resolver module-list wiring
that upstream bundles with the same commit are DEFERRED, so no new Nix
resolve code binds anything. Adding the variant is byte-neutral for
colby.schema.sql (language is a stored TEXT value, not DDL) and for the eight
existing goldens (none holds a .nix file).
Regenerate reproducibly (identical recipe to the Solidity fixture, substituting
nix):
rm -rf /tmp/cg-fixture-nix && cp -r crates/codegraph-bench/fixtures/nix /tmp/cg-fixture-nix
cargo build --release -p codegraph-rs
CODEGRAPH_NO_DAEMON=1 CODEGRAPH_NO_WATCH=1 ./target/release/codegraph init /tmp/cg-fixture-nix
cp /tmp/cg-fixture-nix/.codegraph/codegraph.db reference/golden/nix/colby.db
cargo run -p codegraph-bench --bin bench -- --gen-golden reference/golden/nix/colby.db reference/golden/nixThe generated_golden_matches_committed_nix_fixture and
nix_db_is_self_equivalent_to_nix_golden tests in
crates/codegraph-bench/tests/equivalence.rs enforce byte-stability.
A tenth golden fixture, reference/golden/terraform/, guards Terraform/OpenTofu
(HCL) extraction (upstream #1173 / 6c24f4b, the extraction slice only).
Terraform is a new Language::Terraform variant backed by a dedicated
tree-sitter-hcl grammar (.tf/.tfvars/.tofu → Language::Terraform).
HCL is intentionally generic — every top-level construct is a block
distinguished only by its first identifier child — so TERRAFORM_SPEC has
all-empty type-sets and extraction is driven by the Language::Terraform-guarded
visit_terraform_node walker extension. The corpus
(crates/codegraph-bench/fixtures/terraform/main.tf) is a single deterministic
file with a terraform {} settings block, a provider "aws", a
variable "region", a locals block, a data "aws_ami" "ubuntu", a
resource "aws_s3_bucket" "b", a module "vpc", and two output blocks. What
it guards:
- the
.tffile with"language": "terraform"; - block-type dispatch:
resource/data→NodeKind::Class(qualifiedT.N/data.T.N),module→NodeKind::Module(module.M),variable/output→NodeKind::Variable(var.V/output.O,is_exported),provider→NodeKind::Namespace(provider.P),localsattributes →NodeKind::Constantper attribute (local.k); - plain attribute-expression traversal refs
(
var.X/local.X/module.M/data.T.N/<type>.<name>) →References, with built-in heads (each/count/self/path/terraform) skipped.
The plain traversal refs with a unique same-file target resolve via the existing
generic qualified-name matcher: var.region ×3 → variable "region",
aws_s3_bucket.b → the resource, module.vpc → the module (each an EDGE, absent
from refs.json). The undeclared aws_kms_key.logs stays the sole unresolved
refs.json row. The module-boundary TerraformResolver, emitModuleWiring's
:-scoped refs (module.M:file/:var.X/:output.X), the .tfvars
top-level-assignment var.X ref, and the module.M:output.<out> scoped half of
qualifyReference are all DEFERRED — the port keeps its single
GodotResolver — so no :-scoped ref is emitted. Adding the variant is
byte-neutral for colby.schema.sql (language is a stored TEXT value, not DDL)
and for the nine existing goldens (none holds a .tf/.tfvars/.tofu file).
Regenerate reproducibly (identical recipe to the Nix fixture, substituting
terraform):
rm -rf /tmp/cg-fixture-terraform && cp -r crates/codegraph-bench/fixtures/terraform /tmp/cg-fixture-terraform
cargo build --release -p codegraph-rs
CODEGRAPH_NO_DAEMON=1 CODEGRAPH_NO_WATCH=1 ./target/release/codegraph init /tmp/cg-fixture-terraform
cp /tmp/cg-fixture-terraform/.codegraph/codegraph.db reference/golden/terraform/colby.db
cargo run -p codegraph-bench --bin bench -- --gen-golden reference/golden/terraform/colby.db reference/golden/terraformThe generated_golden_matches_committed_terraform_fixture and
terraform_db_is_self_equivalent_to_terraform_golden tests in
crates/codegraph-bench/tests/equivalence.rs enforce byte-stability.
An eleventh golden fixture, reference/golden/erlang/, guards Erlang extraction
(upstream #1165 / 6511722, the extraction slice only). Erlang is a new
Language::Erlang variant backed by a dedicated tree-sitter-erlang
grammar (.erl/.hrl → Language::Erlang). Erlang is form-based — a
function's name lives on its function_clause, the grammar emits one fun_decl
per clause, record_decl carries fields as direct children, and
-spec/-callback/type bodies parse as call nodes — so ERLANG_SPEC has
all-empty C-family type-sets (only package_types/import_types are wired, as
upstream) and extraction is driven by the Language::Erlang-guarded
visit_erlang_node walker extension. The corpus
(crates/codegraph-bench/fixtures/erlang/m.erl) is a single deterministic file
with -module(m), -export([f/1, g/0]), -include("foo.hrl"), -define(X, 1),
-record(state, {a, b}), a -spec f(integer()) -> integer()., a two-clause
f/1, and a g/0 that references fun f/1, constructs #state{}, calls the
remote other:h(), and self-calls g(). What it guards:
- the
.erlfile with"language": "erlang"; -module(m)→NodeKind::Namespace(so every function's qualified name ism::f— the shape the remote-call branch emits, somod:f(...)resolves through the standard qualified-name matcher);- clause-merge dedup: the two
f/1clauses merge to exactly ONENodeKind::Functionf; -record(state, {a, b})→NodeKind::StructstatewithNodeKind::Fieldchildrenaandb;-define(X, 1)→NodeKind::ConstantX;-include("foo.hrl")→NodeKind::Import+ anImportsfile edge;- local
g()→ aCallsedge, remoteother:h()→ aCallsrefother::h; fun f/1(function value) and#state{}(record usage) →References, NOTCalls;- the
-spec f(integer()) -> integer().and-callback/ record-field type-positioncallnodes mint NO bogus type call refs (nointegercall).
The local g() self-call and the foo.hrl include resolve; other::h (the
other module is absent from the fixture) is the sole unresolved refs.json
row. The non-Godot framework bridges — -behaviour callback contracts,
gen_server:call/cast(?MODULE|?SERVER) → handle_call/handle_cast, the
spawn/apply/proc_lib/timer/rpc MFA-argument callee lift, var-module
dispatch, and .app/.app.src resource-tuple wiring — are all DEFERRED, so
none of those edges is emitted. Adding the variant is byte-neutral for
colby.schema.sql (language is a stored TEXT value, not DDL) and for the ten
existing goldens (none holds a .erl/.hrl file).
Regenerate reproducibly (identical recipe to the Terraform fixture, substituting
erlang):
rm -rf /tmp/cg-fixture-erlang && cp -r crates/codegraph-bench/fixtures/erlang /tmp/cg-fixture-erlang
cargo build --release -p codegraph-rs
CODEGRAPH_NO_DAEMON=1 CODEGRAPH_NO_WATCH=1 ./target/release/codegraph init /tmp/cg-fixture-erlang
cp /tmp/cg-fixture-erlang/.codegraph/codegraph.db reference/golden/erlang/colby.db
cargo run -p codegraph-bench --bin bench -- --gen-golden reference/golden/erlang/colby.db reference/golden/erlangThe generated_golden_matches_committed_erlang_fixture and
erlang_db_is_self_equivalent_to_erlang_golden tests in
crates/codegraph-bench/tests/equivalence.rs enforce byte-stability.
A twelfth golden fixture, reference/golden/cfml/, guards CFML / ColdFusion
extraction (upstream #1153 / 816bacb, the scope-B extraction slice only). CFML
is a new Language::Cfml variant backed by the dual-grammar
tree-sitter-cfml crate (.cfc/.cfm/.cfs → Language::Cfml). A file's
dialect is picked by a first-token sniff (is_bare_script_cfml): script files
parse with the bundled cfscript grammar and drive the generic type-set
dispatch; tag files parse with the cfml tag grammar and are handled by the
Language::Cfml-guarded visit_cfml_node walker extension. The corpus
(crates/codegraph-bench/fixtures/cfml/) has three deterministic files — a
script Base.cfc, a tag Widget.cfm, and a bare-script Gadget.cfs. What it
guards:
- all three files with
"language": "cfml"; Base.cfc(script) →NodeKind::ClassBase(named from the FILE — the cfscriptcomponentis unnamed) +NodeKind::Functionping;Widget.cfm(tag) →NodeKind::ClassWidget(from thenametag-attr) +NodeKind::MethoddoThing(accesspublic, returntypevoid), and a tagextends="Base"→Extends;Gadget.cfs(bare script) →NodeKind::ClassGadget(from the FILE) +NodeKind::Propertyx+NodeKind::FunctiondoThing, and a script-styleextends="Base"(component_attribute) →Extends;- both
extends Baserefs RESOLVE to theBase.cfccomponent (edges);Gadget.doThing'shelper()call → an unresolvedhelperref.
The <cfscript>-in-tag-body re-parse delegation, the cfquery SQL-body
extraction (LANGUAGE_CFQUERY), and the CFML framework RESOLVER bridges
(FW/1 / ColdBox / CFWheels, dotted/relative inheritance, receiver-type inference)
are all DEFERRED. Adding the variant is byte-neutral for colby.schema.sql
(language is a stored TEXT value, not DDL) and for the eleven existing goldens
(none holds a .cfc/.cfm/.cfs file).
Regenerate reproducibly (identical recipe, substituting cfml):
rm -rf /tmp/cg-fixture-cfml && cp -r crates/codegraph-bench/fixtures/cfml /tmp/cg-fixture-cfml
cargo build --release -p codegraph-rs
CODEGRAPH_NO_DAEMON=1 CODEGRAPH_NO_WATCH=1 ./target/release/codegraph init /tmp/cg-fixture-cfml
cp /tmp/cg-fixture-cfml/.codegraph/codegraph.db reference/golden/cfml/colby.db
cargo run -p codegraph-bench --bin bench -- --gen-golden reference/golden/cfml/colby.db reference/golden/cfmlThe generated_golden_matches_committed_cfml_fixture and
cfml_db_is_self_equivalent_to_cfml_golden tests in
crates/codegraph-bench/tests/equivalence.rs enforce byte-stability.
The dedicated reference/golden/typescript/ fixture guards TypeScript export
alias and explicit JavaScript-specifier resolution (#1482 / #1482b) without
changing the shared mini corpus. Its six-file source corpus lives at
crates/codegraph-bench/fixtures/typescript/; source lines are contractual
because node IDs include the declaration line.
The runAll function pins six positive Calls edges, in source order:
viaConstAlias()→ the localconstTargetfunction behindexport const constAlias = constTarget;viaNamedAlias()→ the localnamedTargetfunction behindexport { namedTarget as namedAlias };defaultExportAlias()→ the localdefaultTargetfunction behindexport default defaultTarget;viaJsSpecifier()from./js_target.js→jsTargetinjs_target.ts;viaCollision()from./collision.js→collisionTargetincollision.ts, ahead of the realcollision.jsfile;viaExtensionless()→extensionlessTargetinextensionless.ts, preserving the existing extensionless behavior.
Two negative rules make false positives visible: viaMissing remains the only
unresolved call and import pair in refs.json, with no Calls edge; and no
Calls edge may target either the exported Constant constAlias or the
JavaScript collisionTarget in collision.js.
Regenerate the committed database and canonical artifacts from a clean corpus:
rm -rf /tmp/cg-fixture-typescript
cp -r crates/codegraph-bench/fixtures/typescript /tmp/cg-fixture-typescript
cargo build --release -p codegraph-rs
CODEGRAPH_NO_DAEMON=1 CODEGRAPH_NO_WATCH=1 \
./target/release/codegraph init /tmp/cg-fixture-typescript
mkdir -p reference/golden/typescript
cp /tmp/cg-fixture-typescript/.codegraph/codegraph.db reference/golden/typescript/colby.db
cargo run -p codegraph-bench --bin bench -- \
--gen-golden reference/golden/typescript/colby.db reference/golden/typescriptThe generated_golden_matches_committed_typescript_fixture and
typescript_db_is_self_equivalent_to_typescript_golden tests in
crates/codegraph-bench/tests/equivalence.rs enforce artifact/database
self-equivalence. As for every fixture below the Godot caveats, do not compare
colby.db bytes; compare the four JSON artifacts byte-for-byte and compare
schema.sql as a normalized statement set when statement order differs.
Tier-3 differences are allowlisted by grep-able lines in
docs/upstream-sync/KNOWN_DIFFS.md — the single path
KnownDiffs::repo_doc_path hardcodes
(crates/codegraph-bench/src/oracle/diff.rs):
RULE tier=3 surface=<surface> key=<substring-or-*> justification=<short-token>
Only Tier-3 entries can be allowed. Tier-1 byte mismatches and Tier-2 multiset
mismatches always fail; the differ never weakens those tiers to pass —
KnownDiffs::allows returns false for anything that is not Tier::Tier3, and
parse_rule rejects tier=1 / tier=2 before that, so a Tier-1/Tier-2 rule
cannot even be written down.
The parser is fail-closed: an unparsable document fails every equivalence
assertion instead of being ignored. A RULE line is rejected when a token is
not key=value, a key or value is empty, a field name is outside
tier/surface/key/justification, a field is repeated, tier is anything
other than Tier-3, surface is outside the five surfaces the differ reports
(nodes, files, schema, edges, unresolved_refs), or any of the four
fields is missing. Lines inside a fenced code block are documentation, not
rules — including the template above — and an unterminated fence is an error,
because every RULE after it would otherwise be skipped silently.