Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
bc55cfa
feat(go): backend config — GoBackend union + "go" cache key
lamwassi Oct 4, 2026
1daebc0
feat(go): Pydantic models mirroring cango analysis.json (v2, L1/L2)
lamwassi Oct 4, 2026
418f517
feat(go): GoAnalysisBackend ABC
lamwassi Oct 4, 2026
720f5ca
feat(go): local codeanalyzer backend (run cango, parse analysis.json)
lamwassi Oct 4, 2026
42b66c1
feat(go): read-only Neo4j backend (lazy Cypher over the GO_ graph)
lamwassi Oct 4, 2026
5a22310
feat(go): GoAnalysis facade (type-centric, class-less public surface)
lamwassi Oct 4, 2026
ed74c10
feat(go): CLDK.go() factory + legacy shim + CLAUDE.md table row
lamwassi Oct 4, 2026
d396640
test(go): mocked, backend-contract, E2E, and live Neo4j-parity tiers
lamwassi Oct 4, 2026
5382d6c
docs(go): README Go Quick Start + runnable example
lamwassi Oct 5, 2026
069bf72
fix(go): map analysis level via analyzer_level (was silently defaulti…
lamwassi Oct 5, 2026
dd0b552
docs(go): add --eager to the quickstart example
lamwassi Oct 5, 2026
8e9c199
fix(go): re-run cango when the cached analysis is shallower than requ…
lamwassi Oct 5, 2026
891d5d1
Merge pull request #3 from codellm-devkit/main
lamwassi Oct 23, 2026
42b59c4
Merge branch 'main' into python-sdk-goanalysisbackend-go-facade-l1l2-…
lamwassi Oct 23, 2026
c34b9d7
Merge pull request #4 from lamwassi/python-sdk-goanalysisbackend-go-f…
lamwassi Oct 23, 2026
4acdf29
Removed --analysis-schema option
lamwassi Oct 24, 2026
e557d34
Merge pull request #5 from lamwassi/fix-analysis-schema
lamwassi Oct 24, 2026
ea79973
test(go): leverage the real litestream corpus; move model tests to te…
lamwassi Oct 24, 2026
e9d9669
Merge pull request #6 from lamwassi/test/go-unit-tests
lamwassi Oct 24, 2026
1bf8d89
Update tests/analysis/go/test_go_analysis.py
lamwassi Oct 25, 2026
d571fd0
Update tests/analysis/go/test_go_analysis.py
lamwassi Oct 25, 2026
da79249
Update tests/analysis/go/test_go_analysis.py
lamwassi Oct 25, 2026
1e319be
Update tests/analysis/go/test_go_analysis.py
lamwassi Oct 25, 2026
f445f85
Update tests/analysis/go/test_go_analysis.py
lamwassi Oct 25, 2026
b5785ac
updates lock file
lamwassi Oct 25, 2026
4501222
Merge pull request #7 from lamwassi/update-uv-lock
lamwassi Oct 25, 2026
cb8690e
Lock codeanalyzer-go without upgrading other dependencies
lamwassi Oct 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ an optional read-only Neo4j backend — selected by the *type* of the `backend=`
| Java | `CLDK.java(...)` (needs the `cldk[java]` extra) | `JCodeanalyzer` (the `codeanalyzer-java` 3.2.0 wheel's jar on its bundled JVM, subprocess, `-a 1..4` — no jar in this repo, no JDK download) | `JNeo4jBackend` (graphs emitted by ≥ 3.2.0, probed at attach) | `cldk/models/java/` (schema v2 mirror) + `projections.py` |
| Python | `CLDK.python(...)` | `PyCodeanalyzer` (in-process `codeanalyzer-python`) | `PyNeo4jBackend` | re-exported from `codeanalyzer-python` |
| TypeScript (+ JavaScript modules) | `CLDK.typescript(...)` | `TSCodeanalyzer` (`codeanalyzer-typescript` 1.6.0 binary from the wheel, subprocess; `-a 1..4`, but `--emit neo4j` takes no `-a` and is always full depth) | `TSNeo4jBackend` (graphs emitted by ≥ 1.5.2; older refused at attach) | `cldk/models/typescript/` (schema v2 mirror) |
| Go | `CLDK.go(...)` | `GoCodeanalyzer` (`cango` binary, subprocess, `-a 1..2` only — no L3/L4; `--analysis-schema 2` mandatory or it emits legacy v1; binary via `$CODEANALYZER_GO_BIN` or the `codeanalyzer-go` wheel — **no release cut yet, so the version pin is deferred**) | `GoNeo4jBackend` (lazy Cypher over the `GO_`-prefixed graph; **not** an eager rebuild — Go graphs reach enterprise scale) | `cldk/models/go/` (schema v2 mirror; **no `projections.py`** — out of SDK3 scope) |

**Java, since leg 3a (#310):** the models are an `extra="forbid"` mirror of canonical schema v2, so
a 1.x `analysis.json` (and a pre-3.0.1 Neo4j graph) is refused, not parsed; `get_call_graph()` keys
Expand Down
59 changes: 59 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ The three steps below take a Python project from disk to a question about its co
analysis_level=AnalysisLevel.call_graph,
)
# CLDK.java(...) and CLDK.typescript(...) take the same arguments.
# CLDK.go(...) too — see the Go Quick Start below.
```

2. Find the callable at a line, and read its source.
Expand Down Expand Up @@ -141,6 +142,64 @@ analysis = CLDK.python(

The reference's [Attach](./docs/agent-api-reference.md#attach) section and the per-language sections after it list the graph versions each backend accepts. They also show how to migrate an older graph.

### Go Quick Start

Go is CLDK's first **type-centric, class-less** facade. A Go `type` is a `struct` or an
`interface` (never a class), and package-level **functions** are kept distinct from receiver
**methods** — so the Go facade is `get_types` / `get_functions` / `get_methods_of`, not
`get_classes`. Point the facade at a Go **module** (a directory with a `go.mod`):

```python
from cldk import CLDK
from cldk.analysis import AnalysisLevel

analysis = CLDK.go(
project_path="/path/to/go/module",
analysis_level=AnalysisLevel.call_graph, # level 1 (symbols) or 2 (+ call graph); Go has no L3/L4
)

# Walk the types (structs + interfaces) and their receiver methods. Types, methods and fields are
# addressed by the type's durable `can://` id (a Go type has no unique short name across packages).
for type_id, go_type in analysis.get_types().items():
print(f"{go_type.kind} {type_id}")
for sig, method in analysis.get_methods_of(type_id).items():
print(f" method {sig}")
print(analysis.get_source(method.id)) # the method's source, sliced from its module

# Package-level functions are separate from methods — no "module-as-class" hack:
for sig, fn in analysis.get_functions().items():
print(f"func {sig}")

# Who calls whom (full edges with provenance and weight), keyed on the callable id:
call_graph = analysis.get_call_graph() # a networkx.DiGraph
for func_id in analysis.get_functions():
for edge in analysis.get_callees(func_id):
print(f"{edge.src} -> {edge.dst} ({', '.join(edge.prov)}, weight={edge.weight})")
```

Query a pre-populated Go graph **read-only** over Neo4j the same way as the other languages — the
backend is chosen by the config type, and the queries lazily hit the `GO_`-prefixed graph (so it
scales to large enterprise modules without materializing the whole program):

```python
from cldk import CLDK
from cldk.analysis.commons.backend_config import Neo4jConnectionConfig

analysis = CLDK.go(
backend=Neo4jConnectionConfig(
uri="bolt://localhost:7687",
application_name="my-go-app", # the --app-name the graph was emitted with
),
)
types = analysis.get_types()
```

> **Go analyzer:** Go is analyzed by the `cango` binary (`codeanalyzer-go`). It emits analysis
> levels 1–2 only; slicing/CFG/CDG/DDG are not produced, so those accessors are absent rather than
> present-and-erroring. Until the analyzer's first release is published, resolve the binary with
> `$CODEANALYZER_GO_BIN` pointing at a local build.


## Read Next

- [docs/agent-api-reference.md](./docs/agent-api-reference.md) is the full reference. It opens with the four moves most questions decompose into, then lists every accessor with its cost.
Expand Down
8 changes: 6 additions & 2 deletions cldk/analysis/commons/backend_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
# The canonical sub-directory name each language's artifacts live under inside the shared cache
# root. Keyed so that a polyglot repository analyzed under more than one language does not have its
# backends overwrite a single shared ``analysis.json``.
_CACHE_KEYS = {"java": "java", "python": "python", "typescript": "typescript"}
_CACHE_KEYS = {"java": "java", "python": "python", "typescript": "typescript", "go": "go"}


@dataclass
Expand Down Expand Up @@ -118,6 +118,10 @@ class Neo4jConnectionConfig:
JavaBackend = Union[CodeAnalyzerConfig, Neo4jConnectionConfig]
PyBackend = Union[PyCodeAnalyzerConfig, Neo4jConnectionConfig]
TSBackend = Union[TSCodeAnalyzerConfig, CodeAnalyzerConfig, Neo4jConnectionConfig]
# Go's cango has no backend-only knob today, so it reuses the base CodeAnalyzerConfig rather than a
# GoCodeAnalyzerConfig subclass -- the same shape as JavaBackend. Add a subclass only if cango later
# grows a knob.
GoBackend = Union[CodeAnalyzerConfig, Neo4jConnectionConfig]


def cache_subdir(cache_dir: Union[str, Path, None], project_dir: Union[str, Path, None], language: str) -> Path | None:
Expand All @@ -127,7 +131,7 @@ def cache_subdir(cache_dir: Union[str, Path, None], project_dir: Union[str, Path
cache_dir: The cache root from the backend config. When ``None``, defaults to
``<project_dir>/.codeanalyzer``.
project_dir: The project directory, used to derive the default root.
language: The canonical language key (``"java"``, ``"python"``, ``"typescript"``).
language: The canonical language key (``"java"``, ``"python"``, ``"typescript"``, ``"go"``).

Returns:
``<root>/<language>`` as an absolute path, or ``None`` if no root can be determined
Expand Down
26 changes: 26 additions & 0 deletions cldk/analysis/go/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
################################################################################
# Copyright IBM Corporation 2026
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
################################################################################

"""Go analysis package.

Exports the public :class:`~cldk.analysis.go.go_analysis.GoAnalysis` facade; the
:class:`~cldk.analysis.go.backend.GoAnalysisBackend` ABC and its two concrete backends live
alongside it.
"""

from cldk.analysis.go.go_analysis import GoAnalysis

__all__ = ["GoAnalysis"]
116 changes: 116 additions & 0 deletions cldk/analysis/go/backend.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
################################################################################
# Copyright IBM Corporation 2026
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
################################################################################

"""The abstract base every Go analysis backend implements.

:class:`GoAnalysisBackend` parameterises the shared, cross-language
:class:`~cldk.analysis.commons.backend.AnalysisBackend` with Go's pydantic models and sets the
Go graph vocabulary prefixes (``N="Go"`` / ``P="GO_"``). The local
:class:`~cldk.analysis.go.codeanalyzer.codeanalyzer.GoCodeanalyzer` and the read-only
``GoNeo4jBackend`` both subclass it, which is what guarantees the two backends answer the facade
identically — a ``test_go_backend_contract`` asserts every abstract method is implemented on both.

**Go is the SDK's first class-less, type-centric facade.** The *facade* vocabulary the user sees
is ``get_types`` / ``get_functions`` / ``get_methods_of`` — never ``class``. The class-named slots
on this backend (``get_all_classes`` / ``get_class`` / ``get_all_methods_in_class`` / ``get_method``)
are **inherited from the generic base ABC** and filled only so the backend can instantiate (Python
refuses an abstract class with an unimplemented ``@abstractmethod``); they map ``TypeT=GoType``
(structs + interfaces) and are never user-facing. The base has no package-level-function slot, so
:meth:`get_functions` is the one type-centric accessor added here.

**The artifact/dependency/config layer** (the inherited ``get_artifacts`` / ``get_dependencies`` /
``get_config_keys`` / ``get_config_uses`` / ``get_unresolved_config_reads``) is not emitted by a
local L1/L2 ``cango`` run: those app-scope keys are absent from ``analysis.json``. Following the
Java precedent — ``[]`` is reserved for "the pass ran and found nothing" — the local backend
**raises** :data:`ARTIFACT_LAYER_UNAVAILABLE` rather than returning an ambiguous empty; the Neo4j
backend answers them for real from its ``Artifact`` / ``Package`` / ``ConfigKey`` nodes.
"""

from __future__ import annotations

from abc import abstractmethod
from typing import ClassVar, Dict, List

from cldk.analysis.commons.backend import AnalysisBackend
from cldk.models.go import (
GoApplication,
GoCallable,
GoCallGraphEdge,
GoField,
GoModule,
GoParam,
GoType,
)

#: Raised by the local backend for the artifact/dependency/config accessors: a local L1/L2 cango
#: analysis does not emit that layer, so an empty result would be indistinguishable from a proved
#: absence. Both the message and the raise mirror codeanalyzer-java's unavailable-overlay pattern.
#: ``{app}`` is the application anchor name, never a can:// id.
ARTIFACT_LAYER_UNAVAILABLE = (
"the artifact/dependency/config layer is not emitted by a local codeanalyzer-go L1/L2 analysis "
"of {app!r}; query the Neo4j backend, which carries it"
)


class GoAnalysisBackend(AnalysisBackend[GoApplication, GoModule, GoType, GoCallable, GoField, GoParam]):
"""Abstract base every Go analysis backend implements (see the module docstring).

Inherited abstract (from :class:`~cldk.analysis.commons.backend.AnalysisBackend`):
``get_application_view``, ``get_symbol_table``, ``get_call_graph``, ``get_all_classes``,
``get_class``, ``get_all_methods_in_class``, ``get_method``, ``get_all_fields``,
``get_method_parameters``, ``get_artifacts``, ``get_dependencies``, ``get_config_keys``,
``get_config_uses``, ``get_unresolved_config_reads``.
"""

P: ClassVar[str] = "GO_"
N: ClassVar[str] = "Go"

# -----[ type-centric leaf accessors the base ABC does not cover ]-----
@abstractmethod
def get_functions(self) -> Dict[str, GoCallable]:
"""Every package-level function, keyed by signature.

Go keeps package-level functions distinct from receiver methods (no module-as-class hack),
and the generic base ABC has no slot for them — ``get_all_methods_in_class`` covers only a
type's methods. This is the facade's ``get_functions``.
"""

# -----[ call graph (keyed on the callable id, SDK2) ]-----
@abstractmethod
def get_callers(self, callable_id: str) -> List[GoCallGraphEdge]:
"""Every call-graph edge whose ``dst`` is ``callable_id`` — the callables that call it.

Returns the full :class:`~cldk.models.go.GoCallGraphEdge` (``src``/``dst``/``prov``/
``weight``), losslessly: the caller ids are ``[e.src for e in result]``, and ``prov``/
``weight`` stay available. Both backends supply the same shape (the Neo4j ``GO_CALLS``
relationship carries ``weight``/``prov`` as properties).
"""

@abstractmethod
def get_callees(self, callable_id: str) -> List[GoCallGraphEdge]:
"""Every call-graph edge whose ``src`` is ``callable_id`` — the callables it calls.

Full edges, as :meth:`get_callers`; the callee ids are ``[e.dst for e in result]``.
"""

# -----[ source + whole-program ]-----
@abstractmethod
def get_source(self, node_id: str) -> str:
"""The source text of the node ``node_id`` — ``module.source[node.span.bytes]``."""

@abstractmethod
def get_call_graph_json(self) -> str:
"""The application's call graph as a JSON string (Tier A)."""
21 changes: 21 additions & 0 deletions cldk/analysis/go/codeanalyzer/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
################################################################################
# Copyright IBM Corporation 2026
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
################################################################################

"""The local (in-process subprocess) Go analysis backend."""

from cldk.analysis.go.codeanalyzer.codeanalyzer import GoCodeanalyzer

__all__ = ["GoCodeanalyzer"]
Loading
Loading