Skip to content

CLDK go - #434

Merged
sinha108 merged 27 commits into
codellm-devkit:mainfrom
lamwassi:main
Oct 26, 2026
Merged

sinha108 merged 27 commits into
codellm-devkit:mainfrom
lamwassi:main

Conversation

@lamwassi

@lamwassi lamwassi commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Adds a Go frontend to CLDK: a new CLDK.go(...) factory, a GoAnalysis facade, a
GoAnalysisBackend ABC with two interchangeable backends (cango` subprocess and read-only
Neo4j), Go pydantic models mirroring canonical schema v2, docs, an example, and tests.

Motivation and Context

CLDK already exposes Java, Python, and TypeScript through per-language factory methods; Go was the
missing fourth language. This PR brings Go to parity with the existing frontends so the same
"disk → question about the code" workflow works on a Go module.

Go is also the SDK's first class-less, type-centric facade. A Go type is a struct or an
interface (never a class), and package-level functions are distinct from receiver methods.
Rather than force Go through a "module-as-class" hack, the public surface speaks get_types /
get_functions / get_methods_of — never get_classes. The class-named slots on the backend
(get_all_classes, get_all_methods_in_class, …) are inherited only to satisfy the shared
AnalysisBackend ABC and are never user-facing; the facade translates them to the type-centric
vocabulary so the abstraction never leaks to a caller.

Design decisions carried over from the Go-SDK planning:

  • No projections.py (overview models) — out of SDK3 scope.
  • GoType is a single class, not a discriminated union; kind distinguishes struct vs.
    interface.
  • Go reuses the base CodeAnalyzerConfig (GoBackend = Union[CodeAnalyzerConfig, Neo4jConnectionConfig]) because cango has no backend-only knob today — the same shape as
    JavaBackend. A GoCodeAnalyzerConfig subclass is added only if cango later grows one.
  • The analyzer binary ships (post-release) with the codeanalyzer-go wheel; until the first
    release is cut, the binary is resolved out-of-band via $CODEANALYZER_GO_BIN, so the version
    pin is deferred
    .

How Has This Been Tested?

Yes — including against a real Go corpus (the Litestream project, a 525k-node / 156k-edge
graph at enterprise scale), not just synthetic fixtures. The suite under tests/analysis/go/ and
tests/models/go/ covers:

  • Facade surface — test_public_surface_has_no_class_named_method asserts the public API never
    exposes a class-named accessor; type/function/method/field navigation, call graph, callers/
    callees keyed on can:// ids, and get_source span slicing.
  • Caching & levels — a shallower cache is re-run for a deeper request, a deeper cache is reused
    for a shallower one (schema is additive, L1 ⊆ L2), and the requested level is passed through to
    cango (capped at 2 — Go has no L3/L4). Level mapping goes through analyzer_level() to avoid
    the silent-default-to-1 bug a hand-rolled dict caused.
  • Backend contract parity — test_go_backend_contract confirms both backends subclass the ABC,
    implement every abstract method, and match signatures, so the local and Neo4j backends answer the
    facade identically.
  • Local vs. Neo4j equivalence on the real corpus — type-id sets, function-signature sets,
    call-graph edge counts, and symbol-table module counts all match across the two backends (parity
    proven by test, since the Neo4j backend does lazy per-subtree Cypher rather than an eager
    rebuild).
  • UTF-8 source slicing — spans are UTF-8 byte offsets, so non-ASCII modules are sliced on
    encoded bytes and decoded back.
  • Unavailable-layer semantics — the artifact/dependency/config six raise on a local L1/L2
    run (and on the current Neo4j projection) rather than returning an ambiguous empty list, mirroring
    the Java precedent where [] is reserved for "the pass ran and found nothing."

The live Neo4j tests (test_go_neo4j_live.py) run against a graph populated out of band.

Breaking Changes

None. This is purely additive. Existing Java / Python / TypeScript entry points are untouched.
The only changes to shared code are additive: "go" added to _CACHE_KEYS, a new GoBackend
union, and a new go branch in the deprecated CLDK(language).analysis(...) compat shim (which
now also returns GoAnalysis).

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the Codellm-Devkit Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

New public API

from cldk import CLDK
from cldk.analysis import AnalysisLevel

analysis = CLDK.go(
    project_path="/path/to/go/module",     # directory with a go.mod
    analysis_level=AnalysisLevel.call_graph,  # L1 (symbols) or L2 (+ call graph); no L3/L4
)

analysis.get_types()                # {can:// id -> GoType}  (structs + interfaces)
analysis.get_functions()            # package-level funcs, keyed by signature
analysis.get_methods_of(type_id)    # receiver methods of a type
analysis.get_fields(type_id)
analysis.get_call_graph()           # networkx.DiGraph, nodes keyed by can:// callable id
analysis.get_callers(id) / analysis.get_callees(id)   # full edges (src/dst/prov/weight)
analysis.get_source(node_id)        # span-sliced source text

Pass a Neo4jConnectionConfig as backend= to query a pre-populated GO_-prefixed graph
read-only over Bolt — the backend is chosen by the config type, and queries lazily hit the graph so
it scales to large modules without materializing the whole program.

Files

  • Facade / backends: cldk/analysis/go/ (go_analysis.py, backend.py,
    codeanalyzer/codeanalyzer.py, neo4j/neo4j_backend.py, neo4j/reconstruct.py).
  • Models: cldk/models/go/ (schema v2 mirror; GoType as a single class).
  • Factory + shim: cldk/core.py (CLDK.go, plus the go branch in the legacy analysis() shim).
  • Shared: cldk/analysis/commons/backend_config.py ("go" cache key, GoBackend union).
  • Docs / example: README.md (Go Quick Start), CLAUDE.md (language table row),
    examples/go_quickstart.py.
  • Tests / fixtures: tests/analysis/go/, tests/models/go/, tests/resources/go/.

Known limitations (intentional, documented)

  • cango emits analysis levels 1–2 only; slicing / CFG / CDG / DDG accessors are absent rather
    than present-and-erroring.
  • The artifact/dependency/config layer is not emitted by a local L1/L2 run, so those accessors
    raise (see above).
  • The codeanalyzer-go wheel is not yet released, so the version pin is deferred and the binary is
    resolved via $CODEANALYZER_GO_BIN for now.

Note: the CLAUDE.md table row mentions a --analysis-schema 2 flag; that flag was since
removed on the branch (cango now defaults to schema v2), so that part of the row is stale and
should be trimmed before merge.

lamwassi and others added 19 commits October 4, 2026 17:44
Add the Go arm to the backend-selection layer so a Go facade can dispatch
on config type and cache per-language artifacts without colliding in a
polyglot repo:

- GoBackend = Union[CodeAnalyzerConfig, Neo4jConnectionConfig] — the same
  shape as JavaBackend. cango has no backend-only knob today, so Go reuses
  the base CodeAnalyzerConfig rather than a GoCodeAnalyzerConfig subclass.
- "go": "go" in _CACHE_KEYS so cache_subdir(..., "go") resolves to
  <root>/go.

Part of #1 (GoAnalysisBackend frontend rung).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Hand-written mirror of the codeanalyzer-go (cango) analysis.json, the way
the Java/TS model packages mirror their analyzers (cango is a compiled Go
binary, so there is no package to re-export from like Python does).

- models.py: GoAnalysis envelope → GoApplication → GoModule → GoType →
  GoCallable, plus GoField/GoParam/GoImport/GoBodyNode/GoCallGraphEdge/
  GoSpan/GoMetrics/GoAnalyzer. Validates all 14 real Eval/new-apps samples
  (schema_version 2.0.0, max_level 2).
- GoType is one model with kind ∈ {struct, interface}, not a discriminated
  union of per-kind subclasses like TSType: the two Go kinds carry the same
  wire shape, so a union would be ceremony. Go is the SDK's first class-less,
  type-centric facade — functions kept distinct from methods, no class.
- Go-native fields carried first-class the way TS carries is_async/is_tsx:
  GoParam.is_variadic (...T), GoBodyNode.is_goroutine (go f()),
  GoCallable.error_channel ((T, error) idiom), GoCallable.source_file.
- extra="ignore" (matching TS codellm-devkit#386) so an additive cango release parses
  without an SDK edit; _NullSafeBase coerces a JSON null collection to its
  empty default before validation (Go's nil-slice idiom), honoring both the
  = [] field style and default_factory.
- No projections.py yet: the overview models are out of this train's SDK3
  scope and add purely additively later.

Part of #1 (GoAnalysisBackend frontend rung).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The abstract base both Go backends implement, parameterising the shared
AnalysisBackend with Go's models and setting the graph vocabulary prefixes
N="Go" / P="GO_".

- GoAnalysisBackend(AnalysisBackend[GoApplication, GoModule, GoType,
  GoCallable, GoField, GoParam]) — 14 lifecycle/artifact slots inherited,
  5 Go-specific accessors added: get_functions (package-level funcs, no base
  slot), get_callers/get_callees (keyed on callable id, returning full
  GoCallGraphEdge — lossless prov/weight), get_source, get_call_graph_json.
- Class-less, type-centric: the facade users see is get_types/get_functions/
  get_methods_of, never "class". The inherited class-named slots
  (get_all_classes etc.) are filled only to instantiate and map TypeT=GoType
  (struct + interface); never user-facing.
- ARTIFACT_LAYER_UNAVAILABLE: the artifact/dependency/config layer is absent
  from a local L1/L2 analysis.json, so the local backend raises it (Java
  precedent — [] is reserved for "pass ran, found nothing") while the Neo4j
  backend answers for real from its Artifact/Package/ConfigKey nodes.
- __init__.py is a package marker for now; GoAnalysis is exported once the
  facade lands.

Pure contract — no bodies; first behavioral test is test_go_backend_contract
asserting both concrete backends implement all 19 abstractmethods.

Part of #1 (GoAnalysisBackend frontend rung).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
GoCodeanalyzer(GoAnalysisBackend): run cango as a subprocess, read the v2
analysis.json, validate into GoAnalysis, and answer all 19 abstractmethods
from the in-memory tree. Verified end-to-end on a real Go module and against
the 573-callable echo sample (47 modules, 110 types, 195 funcs, 473 edges).

- Binary discovery: $CODEANALYZER_GO_BIN -> codeanalyzer_go wheel bin_path()
  -> CodeanalyzerExecutionException with install hint (wheel arm lights up at
  pin time; build now, pin last).
- CLI: -i/--app-name/-a <1|2>/--analysis-schema 2/--skip-tests/--eager/-t.
  --analysis-schema 2 is mandatory — cango defaults to the legacy v1 shape the
  v2 models reject. Level capped at 2 (no L3/L4).
- Output: stdout pipe when no cache dir; else persist to <cache>/go and reuse
  unless eager/target_files force a re-run.
- Indexing: types keyed by durable can:// id (a Go type carries no signature,
  and the types{} short-name key isn't unique across packages); callables by
  signature. No id->key indirection — Go's call-graph endpoints already ARE
  callable ids.
- get_callers/get_callees filter the flat call_graph by dst/src == id,
  returning full GoCallGraphEdge (prov/weight preserved).
- get_source slices module.source by span.bytes, which are UTF-8 BYTE offsets:
  a non-ASCII module is sliced on its encoded bytes (cached once) and decoded,
  the way TS _Spanned.code does. Character-index slicing drifted by one on the
  first multibyte sample.
- Artifact/dependency/config six raise ARTIFACT_LAYER_UNAVAILABLE: that layer
  is not emitted by a local L1/L2 run (Java precedent — [] means proved-empty).

Part of #1 (GoAnalysisBackend frontend rung).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
GoNeo4jBackend(GoAnalysisBackend): a drop-in alternative to GoCodeanalyzer
answering the same 19-method surface over a GO_-prefixed graph a
`cango --emit neo4j` run populated out of band. Verified for PARITY against
the local backend on the live cobra graph (17 types, 106 funcs, 458 edges,
18 modules — identical type-id/signature sets, matching callers/callees incl.
prov/weight, matching get_source).

- Lazy per-subtree fetch, NOT an eager GoApplication rebuild: Go graphs reach
  enterprise scale (a 525k-node / 156k-edge app was measured), so each accessor
  issues one indexed Cypher query touching only what it needs. get_callers(id)
  is a single GO_CALLS lookup; nothing materialises the whole tree. Parity is
  therefore proven by test, not guaranteed by construction.
- config.py re-exports Neo4jConnectionConfig; reconstruct.py has the pure
  props-map -> model builders (span_json / metrics_json are JSON strings on the
  wire, parsed here); neo4j_backend.py holds the lazy accessors.
- Every query scoped to can://<app>/ + the :GoCanNode marker, so a shared graph
  with several apps or a sibling-language subgraph is never scanned in.
- get_source slices :GoModule.source (which the projection carries) by the
  node's span_json byte offsets, UTF-8-aware, matching the local backend.
- neo4j driver is a lazy import (optional extra cldk[neo4j]); the module
  imports fine without it and raises a clear install message on construction.
- Artifact/dependency/config six RAISE here too, same as the local backend:
  the Go projector declares :Artifact/:Package/:ConfigKey but does not yet emit
  them (0 such nodes in cobra), so the layer is reserved-but-empty on both
  projections. Flip to querying for real when the projector emits it.

Part of #1 (GoAnalysisBackend frontend rung).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The thin public query layer. Picks a backend by config type
(Neo4jConnectionConfig -> GoNeo4jBackend, else CodeAnalyzerConfig ->
GoCodeanalyzer) and forwards every query. Verified on BOTH backends: the
local one on a real Go module, the Neo4j one on the live cobra graph.

- SDK1 made real at the user layer: the public surface is get_types/get_type/
  get_functions/get_methods_of/get_method/get_fields — NO class-named method
  anywhere a caller can see (verified: zero 'class' in the public API). These
  forward to the backend's inherited class-named slots, which exist only to
  satisfy the shared ABC.
- Types, their methods and fields are addressed by the type's durable can:// id
  (a Go type has no unique short name across packages); get_callers/get_callees
  by the callable's can:// id, returning the full GoCallGraphEdge (SDK2).
- Package-level functions kept distinct from receiver methods (no
  module-as-class hack).
- __init__.py now exports GoAnalysis.

Part of #1 (GoAnalysisBackend frontend rung).
Wire the Go facade into the public entry point so the issue's acceptance gate
passes: CLDK(language="go").analysis(project_path=...) returns a populated type
table + call graph. Verified on both backends (local on a real module, Neo4j
on the live cobra graph), and the legacy shim emits the expected
DeprecationWarning.

- CLDK.go(project_path, *, analysis_level, target_files, eager, backend)
  mirroring CLDK.java/python/typescript; backend selected by config type.
- Legacy CLDK(language="go").analysis(...) shim routes to CLDK.go() with a
  CodeAnalyzerConfig (or the passed neo4j_config); source_code mode refused as
  for the other languages. Return type and deprecation message widened to
  include Go.
- CLAUDE.md supported-languages table gets the Go row (required in the same
  change): cango -a 1..2 only, --analysis-schema 2 mandatory, version pin
  deferred (no release cut), lazy-Cypher Neo4j backend, no projections.py.

Part of #1 (GoAnalysisBackend frontend rung).
The four test tiers for the Go frontend. CI-safe tiers (contract, mocked,
models) run with no external resources; the live tiers skip cleanly when the
binary / Neo4j env are absent. Verified: 61 passed / 11 skipped with nothing
available; 72 passed / 0 skipped with cango + the live cobra graph present.

- test_go_backend_contract.py: both backends parameterise the generic ABC
  (N=Go, P=GO_), every generic + Go-specific abstractmethod is implemented on
  both, and signatures don't drift. This is the 'GoAnalysisBackend
  instantiates' gate at the class level.
- test_go_models.py: the fixture validates; GoType is one model with a kind
  field; _NullSafeBase coerces null collections; the Go-native fields exist.
- test_go_analysis.py: facade with the cango subprocess mocked (fixture
  analysis.json written into the cache dir). Asserts the public surface has NO
  class-named method (SDK1), functions distinct from methods, call graph
  dangling-free, callers/callees keyed on id, get_source slices, artifact six
  raise.
- test_go_e2e.py: real cango on a throwaway go.mod module; skipif no binary.
- test_go_neo4j_live.py: GoNeo4jBackend vs GoCodeanalyzer parity on the same
  application (type-id sets, function-sig sets, edge counts, module counts,
  callees incl. prov/weight); skipif no CLDK_TEST_GO_NEO4J_* env. The artifact
  six raise on the Neo4j backend too.
- fixture_analysis.json: a 2 KB real cango v2 payload (one struct + method, one
  package func, one call edge).

Part of #1 (GoAnalysisBackend frontend rung).
A standalone Go Quick Start in the README and a runnable example script, both
showing the type-centric, class-less facade (get_types / get_functions /
get_methods_of, never get_classes) on both backends. Every snippet verified
against real cango output.

- README: a "### Go Quick Start" subsection (full type-centric walk: types +
  receiver methods, package-level functions, get_source, call graph with
  prov/weight; the read-only Neo4j backend; a cango/$CODEANALYZER_GO_BIN note).
  Also adds Go to the language picker, the Supported Languages table, and the
  deprecation note.
- examples/go_quickstart.py: a parametrized runner over a given Go module.
  Local (cango) backend plus an optional read-only Neo4j backend (runs when
  CLDK_GO_NEO4J_* are set, skips cleanly otherwise). An -a/--level {1,2} option
  (default 2 = symbol table + call graph; 1 = symbol table only, where it says
  the call graph is not computed rather than printing a misleading empty one).
  cango has no L3/L4, so only levels 1 and 2 are offered.

Part of #1 (GoAnalysisBackend frontend rung).
…ng to 1)

GoCodeanalyzer mapped the level with a hand-rolled dict keyed on the string
"call_graph", but the facade passes the AnalysisLevel.call_graph ENUM, whose
value is "call graph" (a space, not an underscore). The lookup missed and fell
back to the default 1, so `-a 1` always reached cango regardless of the
requested level — every project came back with no call graph (0 edges), even at
analysis_level=call_graph.

Replace the dict with the SDK's shared analyzer_level() helper (the same one
the TS backend uses), which accepts the enum / name / value and RAISES on an
unknown level instead of defaulting to 1 — its docstring exists precisely to
close "the level that silently becomes 1" defect. _argv still caps at 2 (cango
has no L3/L4).

The bug hid because the test fixture has no cross-function calls (0 edges at any
level looked correct); a real app (dae: 3241 edges) exposed it. Add a regression
test asserting the requested level reaches cango as the right `-a` integer.

Part of #1 (GoAnalysisBackend frontend rung).
Give go_quickstart.py an -e/--eager flag that forces a fresh cango run,
ignoring any cached analysis.json under <project>/.codeanalyzer/go.

Needed because the local backend reuses a cached analysis by existence, not by
level: a cache written by an earlier shallower run (e.g. `-a 1`) is served for a
deeper request (`-a 2`), so the call graph comes back empty. --eager is the
escape hatch; the underlying level-blind cache reuse in GoCodeanalyzer (and the
TS backend it mirrors) is a separate, still-open issue.

Part of #1 (GoAnalysisBackend frontend rung).
…ested

GoCodeanalyzer decided whether to reuse a cached analysis.json by whether the
file EXISTED, never by its level. So a cache written by an earlier shallower
run (e.g. `-a 1`) was served for a deeper request (`-a 2`), and the call graph
came back empty — the 0-edges symptom seen on a real app (dae) whenever a
level-1 cache was present.

- needs_run now also re-runs when the cached max_level is below the requested
  level. A cache at or above the request is reused as-is: the schema is additive
  (L1 subset of L2), so a deeper cache already contains everything a shallower
  request needs (and re-running would waste work). A missing/unreadable/malformed
  cache re-runs rather than trusting it. The request is compared capped at 2
  (cango's ceiling), so a request above 2 never re-runs against a level-2 cache.
- Regression tests: a shallow cache is NOT reused for a deeper request (re-runs),
  and a deeper cache IS reused for a shallower one (no re-run). Both count cango
  invocations via a level-aware mock — no binary needed.

Distinct from the earlier level->-a mapping fix (069bf72); this is cache reuse.
The TS backend shares the same existence-only check and is still affected.

Part of #1 (GoAnalysisBackend frontend rung).
…acade-l1l2-neo4j

Python sdk goanalysisbackend go facade l1l2 neo4j
Removed --analysis-schema  option
…sts/models/go

Add comprehensive Go unit tests driven by the committed real analysis
(tests/resources/go/analysis.json — the can://go/litestream project, 55
modules, 959 edges), replacing one-edge-toy coverage:

- tests/models/go/: model tests moved here to match the sibling
  tests/models/<lang> layout; parse the real corpus (envelope, a real
  ReplicaClient struct round-trip) plus inline shape/nil-slice checks. The
  redundant toy-fixture validation is dropped (no model test keeps a local
  toy JSON, per convention).
- test_go_analysis.py: a litestream_facade fixture runs the whole accessor
  surface over the real corpus — get_callers/get_callees/get_source against
  measured real ids, 959-edge dangling-free call graph. The toy fixture is
  kept only for the level/cache regression tests that mutate max_level.
- test_go_e2e.py: a live path that unzips litestream.zip and runs the real
  cango binary on the full app (skips without a binary).
- tests/resources/go/: the committed analysis.json, litestream.zip source,
  and a README rewritten to match the single-L2-file reality.

79 passed, 7 skipped (the live Neo4j tier).
test(go): leverage the real litestream corpus; move model tests to te…

@sinha108 sinha108 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tests/analysis/go/test_go_analysis.py still loads tests/analysis/go/fixture_analysis.json. .gitignore excludes that file (*.json), so it isn't in this PR. On a clean checkout of e9d9669, the Go tests give 4 failed and 8 errors: every test using the go_analysis fixture, the _level_aware_run helper or _capture.

The suggestions below point those tests at the committed litestream corpus (GO_ANALYSIS) and drop the missing fixture. Apply them all at once with Commit suggestions in the Files changed tab.

Verified on a fresh clone of this PR, with cango built from codeanalyzer-go main, comparing every test's outcome before and after:

  • Go tests (tests/analysis/go, tests/models/go): 67 passed / 4 failed / 8 errors / 7 skipped → 79 passed / 7 skipped. All 12 failing tests now pass, no test went from passing to anything else, and the 7 skips are the live-Neo4j tests, skipped for lack of a database both times.
  • Full SDK suite: 1861 passed / 4 failed / 8 errors → 1873 passed, 0 failed, 0 errors.

Separate from this (not part of these suggestions): get_source still returns the wrong text for a method declared in a different file from its receiver type, because neither Go backend reads source_file. We'll fix that in follow-up python-sdk work after this PR merges.

Comment thread tests/analysis/go/test_go_analysis.py Outdated
Comment thread tests/analysis/go/test_go_analysis.py Outdated
Comment thread tests/analysis/go/test_go_analysis.py Outdated
Comment thread tests/analysis/go/test_go_analysis.py Outdated
Comment thread tests/analysis/go/test_go_analysis.py Outdated
lamwassi and others added 5 commits October 25, 2026 11:52
Co-authored-by: Saurabh Sinha <sinha108@gmail.com>
Co-authored-by: Saurabh Sinha <sinha108@gmail.com>
Co-authored-by: Saurabh Sinha <sinha108@gmail.com>
Co-authored-by: Saurabh Sinha <sinha108@gmail.com>
Co-authored-by: Saurabh Sinha <sinha108@gmail.com>
@sinha108

Copy link
Copy Markdown
Collaborator

Thanks, all five review suggestions are in.

One more thing before merge: uv.lock is stale. This PR adds codeanalyzer-go==0.1.0rc1 to pyproject.toml but doesn't update the lockfile, so on a clean checkout of f445f85, uv lock --check fails.

The release workflow installs with uv sync --all-groups --frozen, which installs only what the lock lists. So codeanalyzer_go is never installed, and the 5 end-to-end tests in tests/analysis/go/test_go_e2e.py skip ("no codeanalyzer-go (cango) binary resolvable"). The suite still shows green, so CI wouldn't flag it.

On a fresh clone of f445f85:

Install Go tests Full suite
uv sync --all-extras --all-groups (refreshes the lock) 79 passed, 7 skipped 1873 passed, 411 skipped, 0 failed
uv sync --all-groups --frozen (as in release.yml) 74 passed, 12 skipped 1868 passed, 416 skipped, 0 failed

The 7 remaining skips are the live-Neo4j tests.

Fix: run uv lock and commit the updated uv.lock. The diff is 14 added lines: codeanalyzer-go in cldk's dependencies and requires-dist, plus its package entry.

@sinha108

Copy link
Copy Markdown
Collaborator

Thanks for the lockfile update; uv lock --check now passes. But b5785ac re-resolved the whole lockfile instead of only adding codeanalyzer-go: about 99 packages moved to newer versions, 6 were added and 7 removed. Could you narrow it to the one addition?

Why it matters

This PR adds Go support, so its lockfile change should be limited to that. A broad upgrade can have consequences we can't see from here:

  • It isn't fully tested. The suite passes with the new versions, but 411 tests skip on a plain run: the live Neo4j, Java taint and graph-backend suites need external servers. Those are exactly the tests that would catch a regression from bumps such as neo4j 6.2 → 6.4, protobuf 6 → 7 (via ray), python-dateutil 2 → 3 or pydantic 2.11 → 2.14.
  • Major-version jumps. protobuf, python-dateutil, pyflakes, filelock, markdown-it-py and pycparser all cross a major version.
  • It's hard to trace later. If something breaks, a dependency upgrade inside a Go-support PR is hard to find and to revert on its own.

The upgrade itself is fine to do, but in its own PR, run against the live Neo4j and Java suites.

What to do

From the PR branch:

git checkout f445f85 -- uv.lock   # the lockfile from before b5785ac
uv lock                           # no --upgrade: adds only codeanalyzer-go
git diff --stat uv.lock           # expect: 1 file changed, 14 insertions(+)
uv lock --check
git commit -am "Lock codeanalyzer-go without upgrading other dependencies"
git push

The expected diff against f445f85 is these 14 added lines and nothing else:

  • codeanalyzer-go in cldk's dependencies;
  • codeanalyzer-go with specifier = "==0.1.0rc1" in requires-dist;
  • one [[package]] name = "codeanalyzer-go" entry with its 5 wheels.

Verified

On a fresh clone of 4501222 with that lockfile, installed the way release.yml does it (uv sync --all-groups --frozen):

Check Result
uv lock --check passes
codeanalyzer-go installed with its cango binary yes; other versions unchanged (e.g. pydantic 2.11.7)
Go tests (tests/analysis/go, tests/models/go) 79 passed, 7 skipped (live Neo4j)
Full suite 1873 passed, 411 skipped, 0 failed

These are the same counts as with the upgraded lockfile, so narrowing it costs nothing.

@sinha108
sinha108 merged commit 1b49043 into codellm-devkit:main Oct 26, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants