ADR-032: Structural Code Intelligence — Scope-Graph Binding and Precise-Index Harvesting¶
Metadata¶
- Status: Accepted (Phase 1 / tier 2.5 implemented; tiers 3+ planned)
- Date: 2026-10-19 (rev 1.1: 2026-10-20; rev 1.2: 2026-10-20)
- Decision Makers: Vijaykumar Singh
- Related ADRs: 014 (shared codegraph chunker package), 015 (Victor Core adopts
victor-codegraph), 026 (durable code memory GA — oneoid, correlated graph+vector) - Scope: Graph-RAG indexing pipeline and victor-codegraph integration only. No
framework public-API changes (no FEP required); no runtime dependency on language
servers at query time; victor-codegraph keeps its independent release train.
Context¶
Victor's structural memory is the Graph-RAG stack: victor-codegraph tree-sitter
chunking (tier 0/1), the SQLite/ProximaDB graph stores with CONTAINS/IMPORTS/CALLS
edges and per-language import resolvers (tier 2 heuristic), CCG statement graphs
(CFG/CDG/DDG), FTS5 + vector hybrid retrieval with graph-expansion reranking, and
content-hash staleness for incremental re-indexing. This is the right substrate —
but two precision gaps remain between it and what IDEs and code-intelligence
platforms provide:
- Name binding is heuristic. Tree-sitter gives structure (CST) but zero binding
semantics. Whichimplblock a method belongs to, whichusea path resolves
through, and which local a reference names are resolved today by position and
textual import matching — correct most of the time, unprovable any of the time.
The parent-FK incident fixed in #1397 was exactly this class: emitter-order
assumptions standing in for a resolution model. - No compiler-accurate cross-references. Callers/implementers/trait-resolution
edges — the relationships an LLM cannot cheaply infer from a context window —
exist only in approximated form. IDEs get them from real compiler front-ends
(rust-analyzer is built from rustc's own libraries over the salsa incremental
query framework; clangd runs Clang's frontend; gopls drives the Go type checker),
and code platforms get them from offline index dumps: GitHub's stack graphs
(tree-sitter + graph-construction DSL solving name binding as graph reachability,
per-file incremental, no build step) and SCIP (Sourcegraph's successor to LSIF;
scip-pythondrives pyright,scip-godrives gopls, etc.).
The cost argument is now measured, not assumed: coding agents burn their budget on
finding code rather than changing it — a 30B OpenHands agent averages 23 rounds and
631K tokens per resolved SWE-Bench Verified issue, much of it on grep/glob/view
during repository exploration ([CodeGrep, arXiv:2608.05886]). Every query a graph
edge answers is tokens never spent; structure narrows, the model concludes.
Evidence base (arxiv corpus on dataserver3, /data/arxive):
| Paper | Relevance |
|---|---|
| LLMs and Language Server Protocol: a match made in context (arXiv:2609.03086) | LSP as the precision substrate for LLM tooling; LLM output verified by static analysis rather than trusted. |
| IncSFS: incremental full-sparse flow-sensitive pointer analysis (arXiv:2608.24391) | Incremental recomputation on evolving code is what makes deep static analysis affordable — the same property content-hash staleness gives our indexer. |
| Dissecting Software Graphs (arXiv:2608.12859) | Static call graph as the shared backbone onto which dynamic/derived views project. |
| Graph-grounded software knowledge for reliable LLM programs (arXiv:2609.01095) | Hybrid retrieval over a heterogeneous software knowledge graph grounds generation; a request alone never specifies APIs/dependencies/conventions. |
| MAP4CS (arXiv:2610.11727) | Code-retrieever adaptation to evolving repositories; corpus noise motivates stable structural chunking over churny text windows. |
| When Does the Dependency Graph Help Code-to-Architecture Mapping? (arXiv:2610.19769) | Typed dependency graphs as the substrate for architectural-level reasoning (semi-supervised propagation over the graph). |
| STRATA (arXiv:2610.19386), StateHarness (arXiv:2610.17616) | Structured, typed graphs as durable agent memory beat undifferentiated embedding pools; agents need an evolving project-state view maintained outside model parameters. |
| WEAVE (arXiv:2610.22039) | Dependency-structured tasks as the evaluation substrate for agentic code work. |
| RepoReuse / Do Coding Agents Reuse Existing Code? (arXiv:2609.35357) | Reuse auditing requires AST-based dependency graphs; duplicated implementations accumulate unsupervised. |
| NexusRAG (arXiv:2609.37661) | Graph-RAG propagation must go beyond query similarity to reach bridging evidence — validates graph-expansion reranking over pure vector recall. |
Decision¶
Close the two gaps with two additional precision tiers behind the existing graph
surface, preserving the repo's fallback discipline (every tier degrades to the one
below; nothing requires a build system or a running toolchain).
Tier 2.5 — scope-graph name binding in victor-codegraph¶
Implement a stack-graphs-style binding pass over the CSTs victor-codegraph already
produces:
- Per-language scope rules emit def/ref vertices and scope edges; reference
resolution is a reachability query over that graph (the stack-graphs model),
executed per file with only the file's imports as cross-file inputs — preserving
per-file incremental re-indexing. - Output lands in the existing graph as resolved REFERENCES edges plus corrected
parentage for lexical nesting, replacing positional heuristics where resolution
succeeds; heuristics remain the fallback where a language's rules are not yet
written or resolution is ambiguous. Rust-side (existing workspace, PyO3,— rev 1.1_NATIVE_AVAILABLEfallback)
correction: victor-codegraph is pure Python (the monorepo's Rust workspace is
victor-core's, not the codegraph package's). Tier 2.5 shipped as pure Python;
native acceleration remains a future measured decision under the repo's
native-acceleration strategy.- Build-vs-adopt — SETTLED (rev 1.1):
github/stack-graphsis archived
(RUSTSEC-2026-0303, Sep 2026 — "no published successor"; companion
RUSTSEC-2026-0302 unsoundness advisory on its C API exports). Adopting an
unmaintained, soundness-flagged crate is disqualified for a production
framework. Tier 2.5 is the hand-rolled subset this ADR's fallback branch
anticipated.
Rev 1.1 — tier 2.5 as implemented (#1404, victor-codegraph 0.11.0; #1406 core
integration):
- A language-agnostic two-pass collector (
scope_binding.py) plus a stdlib-ast
adapter (python_references.py, the reference implementation): occurrence
extraction with innermost-owner attribution, span-keyed exclusions for call
callees and member positions, field-directed shadow collection, per-language
noise roots, deterministic first-use dedup per (owner, name), unresolved cap
64/owner.CodeRelationType.REFERENCES(previously dead) is the carrier;
resolution rides the existing two-stage resolver (scope_resolver→
repository_resolvervia exact import bindings), ambiguity retained at 0.5. - Language membership is honest capability, not grammar availability — every
grammar is probed against a three-fact contract (one resolved in-file edge, one
retained-unresolved edge, shadowed locals producing no edge). Verified and
enabled at 1.1: python, javascript, typescript, tsx, rust, go, java, c, bash.
Rev 1.2 (victor-codegraph 0.12.0, #1408): the eight pending grammars
joined — cpp, csharp, php, swift, scala, kotlin, ruby, lua — for seventeen
reference-emitting languages; sql remains the sole, asserted, vacuous
exclusion. kotlin/ruby extraction unlocked en route (rubymethodnodes
and kotlin/swiftsimple_identifiernames now mint function symbols —
a default-path change, hence the minor version). An emptiness ratchet
test pins every exclusion: adding a language is a deliberate verified act. - Opt-in end to end:
emit_references=Falsedefault onparse/parse_repo
(package output byte-identical until asked); victor-core gates on
GraphIndexConfig.enable_scope_binding(default OFF, settings-cascaded,
capability-probed against codegraph ≥ 0.11.0 with an explicit downgrade log). - Known v1 limits: module-level uses skipped (no owner in the id-map rewrite);
declaration-subtree over-shadowing errs toward suppressed edges; ON→OFF flag
flips leave stale REFERENCES edges until a force rebuild.
Tier 3 — precise-index harvesting (SCIP/LSP), optional and lazy¶
Compiler-accurate cross-references when — and only when — the toolchain exists:
- Pluggable per-language precise providers harvest from offline index dumps:
scip-python(pyright),scip-go(gopls), rust-analyzer-derived SCIP for Rust,
tsserver for TypeScript. Harvest is invoked by explicit configuration or
capability detection, never by default. - Harvested edges/nodes carry provenance and override heuristic edges;
heuristic edges never delete precise ones. No language server runs in the query
hot path — index once, serve graph lookups (the SCIP model). - Failure of a provider is logged and skipped; the tier 2/2.5 graph remains fully
usable (fallback preservation).
Provenance model¶
Every binding-bearing edge carries a precision level so retrieval can prefer
precise edges, the agent layer knows what is provable vs inferred, and
reconciliation is deterministic (compiler > scope-resolved > heuristic). This
extends the existing edge metadata convention; no schema break.
Rev 1.1 vocabulary decision: one concept, one vocabulary — the values are
victor-codegraph's resolver strings verbatim (scope_resolver,
repository_resolver, later SCIP harvesting's compiler), carried in
GraphEdge.metadata as {"provenance": …, "confidence": …, "use": …} via
ProjectedRelationEdge — not a dasherized variant. REFERENCED_BY is computed
downstream, never emitted (the CALLED_BY precedent). Heuristic passes never
write REFERENCES (the edge type is greenfield), which makes the
last-writer-wins (src, dst, type) semantics of every store backend safe.
Retrieval and memory integration¶
- Hybrid retrieval (FTS5 + vector + graph expansion) is unchanged in shape; tiers
2.5/3 upgrade the expansion edges the reranker walks. Embeddings remain derived,
rebuildable state keyed to the stableoididentity (ADR-026). - The durable structural memory thesis is STRATA/StateHarness-shaped: the graph is
the evolving project state outside model parameters; the LLM stays stateless and
terminal.
Beyond tier 3 — outlook (rev 1.1, not yet committed work)¶
The ladder above tier 3, in the order the value compounds. Each tier is a
separate decision with its own evidence bar; none is authorized by this ADR.
- Tier 3.5 — type-directed queries. Keep type/receiver payloads in harvested
relations (theuse/qualifier/arity schema already rides tier-2.5 edges) so
SCIP enrichment answers trait/dispatch and caller-of-type-signature questions
without re-harvesting. Nearly free if tier 3 keeps its payloads. - Tier 4a — precise-CCG fusion. The CCG's CFG/CDG/DDG edges sharpen from
heuristic to compiler-informed once tier 3 supplies real call/receiver types,
plus a small traversal query language (SQLite recursive CTEs already express
reachability). Victor's compounding payoff: the CCG already exists. - Tier 4b — interprocedural summaries. Datalog-style fixpoint over the graph
for reachability/taint/impact sets — upgrades the shipped impact tool from
name-index adjacency to computed blast radius. Must be incremental
(edit → seconds) or it dies in practice; IncSFS (arXiv:2608.24391) is the
incremental discipline to follow. - Tier 5 — dynamic grounding. Project test/coverage telemetry onto the static
graph as edge weights (the Dissecting Software Graphs pattern,
arXiv:2608.12859): retrieval prefers executed paths, dead code becomes
visible, and the graph is verified against reality. Highest evidence-per-hour
for an agentic system — the agents already execute code — and consistent
with the execution-program conclusion that only in-container verification
yields ground truth. - Stays out: CodeQL/Kythe-class semantic databases, symbolic execution,
always-on LSP (the Alternatives below).
Alternatives Considered¶
- Always-on LSP integration at query time. Rejected: couples retrieval latency
and reliability to per-user toolchain state; SCIP-style offline dumps give the
same precision with none of the runtime coupling. - Full semantic databases (CodeQL/Kythe-class). Rejected: build-coupled,
heavy, per-language toolchain commitment that cannot be a default for a
framework that must index arbitrary projects with no build info. - LLM-extracted structure. Rejected as a substrate (cost, nondeterminism,
unverifiable) — this ADR's premise. LLM remains the consumer, not the indexer. - Adopting
tree-sitter-stack-graphs. Settled against by evidence at rev 1.1:
archived upstream (RUSTSEC-2026-0303, no successor) with a companion
unsoundness advisory (RUSTSEC-2026-0302).
Flag graduation dossier (rev 1.2, 2026-10-20)¶
Evidence gathered for the enable_scope_binding default-ON decision. The
decision remained the maintainer's until rev 1.3.
GRADUATED 2026-10-20 (rev 1.3): default-ON approved by the maintainer.
enable_scope_binding now defaults to True across all six construction
sites; explicit False opts out; the capability probe keeps older
victor-codegraph installs safe (graceful degradation to off); an ON-to-OFF
flip requires a force rebuild to clear stale REFERENCES edges.
Parse-level (victor monorepo, 4,847 files — 4,684 py / 97 ts / 34 rs / 26 sh,
55s parse_repo(emit_references=True)):
- 104,841 REFERENCES emitted; 28,216 resolved (12,277
scope_resolver+
15,939repository_resolver), 73.1% retained for cross-file/external
targets — an expected shape for import-heavy Python. - Resolution rate 26.9%, versus the heuristic CALLS baseline's 21.3%
on the same corpus: the binding tier resolves better than the existing
call heuristic it would augment. - Volume control confirmed: 1.14 refs/symbol average (dedup + cap
working as designed). - Per-language resolution: python 27.4%, typescript 17.6%, rust 14.5%,
bash 22.6%.
Pipeline-level (103-file real-code fixture — victor graph_rag Python +
vscode-victor TypeScript — full GraphIndexingPipeline into SQLite,
flag OFF vs ON):
- Identical node sets (1,453 both runs), +675 REFERENCES edges
(562scope_resolver+ 113repository_resolver) on a 2,496-edge
baseline (+27% edge volume), zero errors, and 100% of persisted
REFERENCES carrying provenance — the monotonicity contract holds in a
real pipeline run, not just tests. - Retrieval integration: multi-hop traversal from a REFERENCES-connected
seed expands through CALLS + REFERENCES + CONTAINS mixed neighborhoods —
the precision-first ordering has real edges to order. - Wall-clock: dominated by per-process first-pipeline initialization
(~12-13s regardless of flag); the binding run as second-in-process
completes in 2.9s doing strictly more work than a baseline
second-in-process run — binding overhead is below measurement noise
at this scale.
Second corpus (proximaDB, polyglot — completed in a 50-minute window):
~35.9k REFERENCES at ~34.3% resolved, with every one of the fourteen
emitting grammars exercised on real code — go strongest (4,032/8,802,
45.8%), python 31.2%, typescript 28.2%, and the entire promoted long tail
resolving at 7-33% (swift 26%, kotlin 32%, scala 24%, csharp 46%,
lua 8%). No rust rows appeared in that first sweep — resolved on re-run as a
measurement artifact (the sweep process's source tree was removed mid-run,
degrading lazy grammar loads to silent fallback), not a codegraph defect. A
dedicated rust-only re-sweep from a stable tree (2,788 files: 2,772 success
/ 16 partial, 507s) produced 220,056 REFERENCES at 12.6% resolved
(24,993 scope_resolver + 2,755 repository_resolver) against the CALLS
baseline's 5.7% — the largest relative resolution gain of any language
measured (2.2x). The graduation dossier now has no open questions.
Validation¶
- Differential parity tests: heuristic vs scope-resolved edges over the repo
corpus; every disagreement is a case study, not noise. - Golden-file binding tests per language: resolution results pinned on
representative constructs (impl/trait, closures, shadowing, re-exports) —
landed in rev 1.1 as the three-fact contract (resolved / retained / shadowed)
per verified language, plus the pending-language emptiness ratchet. - Provenance reconciliation tests: precise-override and fallback-preservation
behaviors pinned positively and negatively — rev 1.1 pins the monotonicity
contract (every REFERENCES edge carries provenance; heuristic passes never
write the type). - Retrieval quality: existing Graph-RAG retrieval parity harness plus
end-to-end agent benchmarks; success is measured recall/precision of retrieved
context and token spend per resolved task, not index size. - Performance budget: per-file incremental re-bind must stay within a small
multiple of parse cost (IncSFS-style incremental discipline); no full-repo
recompute on single-file edit.
Consequences¶
- Per-language scope rules are the main new maintenance surface; the verified set
(rev 1.1: nine languages) grows one deliberate verification at a time. - Tier 3 availability is environment-dependent by design; documentation must state
what is gained when a toolchain is present, and the default experience must never
imply compiler precision without one. - def/ref edges grow storage; references are deduped per (owner, name), capped per
owner when unresolved, and excluded from per-node metadata mirroring.
Revision History¶
| Rev | Date | Change |
|---|---|---|
| 1.0 | 2026-10-19 | Initial proposal: tier 2.5 scope-graph binding + tier 3 SCIP/LSP harvesting, provenance model, evidence base from the dataserver3 arxiv corpus. |
| 1.1 | 2026-10-20 | Phase 1 (tier 2.5) implemented: adopt-vs-build settled (RUSTSEC-2026-0303), pure-Python correction, verified nine-language honest-capability set, provenance vocabulary pinned to resolver strings, v1 limits; beyond-tier-3 outlook added (3.5 / 4a / 4b / 5). Status Proposed → Accepted. |
| 1.2 | 2026-10-20 | Language matrix completed (0.12.0: seventeen languages incl. kotlin/ruby extractor unlocks); flag-graduation dossier added — parse-level 26.9% resolution vs the 21.3% CALLS baseline, 1.14 refs/symbol; pipeline-level +27% edge volume with 100% provenance and overhead below measurement noise; proximaDB large-repo sweep noted as the open gap. Graduation decision remains the maintainer's. |
| 1.3 | 2026-10-20 | Flag graduated: enable_scope_binding default-ON (maintainer approval; six construction sites, opt-out preserved, force-rebuild caveat documented); CHANGELOG entry added. |