Repository navigation
codeanalyzer-go → canonical schema v2 (L1 + L2) #94
Description
Spec
https://github.com/codellm-devkit/codeanalyzer-go/blob/main/docs/design/specs/v2-l1-emission.md
Summary
Migrate codeanalyzer-go from the legacy v1 output to the canonical schema v2 (one
additive tree: application → module → type → callable → body with typed edge overlays)
and bring it to analysis level 2 — L1 structural containment + L2 identity-only
call_graph, in both projections (analysis.json + Neo4j). This is a schema-major
change (new envelope, can:// ids, span with UTF-8 byte offsets, source per module,
body call nodes, {src,dst,prov} edges). The python-sdk Go wiring lands in lockstep
behind a frozen public API. Reach for this train: L1 + L2 to parity; L3/L4 deferred.
Golden rule: keep everything that computes facts (parser, resolver, call-graph
builder); replace only what serializes them.
Affected repos (from Contract-Impact Triage)
codeanalyzer-go— analyzer emission (L1 + L2) + Neo4j v2 projection — backend rungpython-sdk— net-new Go model wiring to v2 (cldk/analysis/has nogopackage today) — frontend rung- docs (user-facing schema/levels) — later,
finishing-cldk-work
Design decisions
- D1 Pure canonical v2 (drop per-callable
code/flatstart_line/end_line; recover text by slicingmodule.source) - D2
application.id=can://go/<app>,<app>=--app-name(default: input dir base name) - D3
span.bytes= UTF-8 byte offsets intomodule.source(Go strings are UTF-8; avoids the UTF-16/rune mismatch) - D4 Single type
kind∈struct|interface|alias|defined(collapses v1is_interface); methods nest under receiver type'scallables{} - D5
base_types[]= embedded ids (explicit spine) vsinterfaces[]= computed structural satisfaction (method-set matching) - D6
error_channel[]fromerror-typed returns; closures → nestedcallables{};is_constructor_calldropped (Go has none) - D7
callbody nodes carry typedis_goroutine/is_deferred;calleeis the null→id refinement slot (null at L1, backfilled at L2) - D8
source_fileon a callable when a method is declared apart from its receiver type (cross-file-method span fix) - Scope guard: analyzer is a pure graph provider — no slicing/taint in the analyzer (those are SDK queries)
Release plan
- Analyzer = major release (breaking output). L1 and L2 are independently shippable behind
--analysis-schema 2; v1 stays the default until the SDK migrates. - SDK = major release; pins the analyzer only after the analyzer v2 release is cut (old SDK Go wiring can't parse v2 until then — and there is no Go wiring yet, so this is net-new).
- Neo4j projection and the JSON schema move in lockstep.
Deferred (not this train)
- L3 (CFG/CDG/DDG) and L4 (SDG
param_in/param_out/summary) — a later train reusing this train'scan://ids and{src,dst,prov}edge shape verbatim (Group A/C vocabulary, coined once here per the parity clause). - Go-specific
cfgedge kinds (defer_resume,select) — recorded when L3 is designed. - Points-to oracle (L4 prerequisite);
--materialize-expressions/--materialize-basic-blocks; framework detection.
Definition of done (epic-level)
- Every sub-issue closed and its PR's gate green.
- Analyzer output validates against the SDK v2 Go models; the
L1 ⊆ L2superset gate holds; parity clause holds (no renamed/repurposed shared vocabulary). - Superset gate: v2 output carries every v1 fact modulo the sanctioned drops (
code,is_*booleans folded intokind,is_constructor_call). --analysis-schema 2output is deterministic across runs (ids and edge order stable).- SDK public API unchanged (major bump + documented semantic shifts); analyzer↔SDK versions pinned in lockstep.
- Docs / CHANGELOG updated.
- The two switch-over steps: (1) flip the default output from v1 to v2; (2) remove the v1 emitter once
python-sdkhas migrated and nothing reads v1 — both filed as their own units when due.
Activity
docs: tracking records for the v2 migration epic and L1/L2 children
design: Neo4j projection spec + roadmap (group D of the v2 migration)