Repository navigation
Description
Abstract
This proposal recommends restructuring the CLDK front end, replacing the single
stringly-typed entry point CLDK(language).analysis(...) with a set of
per-language factory methods that accept typed backend-configuration objects
(referred to herein as "Option 2"). The motivation is that the public interface
currently conflates two orthogonal concerns within a single flat parameter
namespace, which produces validation logic that scales poorly and a backend
selection mechanism that is implicit rather than explicit.
This issue body has been reconciled to fold in the subsequent decisions recorded
in the comments: deprecation and removal of the Java source_code input,
consolidation of on-disk persistence into a single language-keyed cache_dir,
and removal of analysis_json_path and analysis_backend_path from the public
interface.
Problem Statement
The system exposes two orthogonal axes of variation:
- Language: java, python, typescript, c.
- Backend: the in-memory codeanalyzer backend versus the read-only
Neo4j/Cypher backend.
The present facade collapses both axes into one method (core.py:101-258),
comprising approximately twelve keyword arguments dispatched by an
if/elif self.language construct. Three consequences follow.
First, validation is expressed negatively and scales as O(languages x knobs).
Representative cases appear at core.py:223-247, where analysis_backend_path
is rejected for Python and cache_dir/use_ray are rejected for non-Python
languages.
Second, backend selection is implicit: it is determined by the truthiness of
neo4j_config, and the selection logic is duplicated across
python_analysis.py:171-192 and typescript_analysis.py:84-104.
Third, Neo4jConnectionConfig is duplicated verbatim across
python/neo4j/config.py and typescript/neo4j/config.py. The docstring of the
former already identifies hoisting as the intended remediation.
Notably, the backend abstract base classes (java/backend.py,
python/backend.py, typescript/backend.py) already presuppose the two-axis
structure. The front end should be brought into correspondence with it.
Proposed Design
The governing principle is a separation of the two axes: the language is
selected by the factory method invoked, and the backend is selected by the type
of the configuration object supplied. The configuration object is a Parameter
Object; the per-language union of admissible configuration types forms a
discriminated union over which backend construction dispatches.
Cross-cutting query options (project_path, analysis_level, target_files,
eager) remain explicit parameters on the factory methods. Backend-specific
options are carried by a backend= configuration object whose type acts as the
discriminator.
Configuration objects
On-disk persistence is consolidated into a single cache_dir, defaulting to
<project>/.codeanalyzer and keyed by language to prevent collisions (see
"Cache layout"). The backend binary is sourced solely from the packaged
dependencies, so neither analysis_json_path nor analysis_backend_path
survives on the public interface. The result is a minimal base object that only
Python extends.
# cldk/analysis/commons/backend_config.py (shared)
@dataclass
class CodeAnalyzerConfig:
cache_dir: str | Path | None = None # default: <project>/.codeanalyzer (language-keyed)
@dataclass
class PyCodeAnalyzerConfig(CodeAnalyzerConfig):
use_codeql: bool = True
use_ray: bool = False
# Java and TypeScript introduce no backend-specific options; they use
# CodeAnalyzerConfig directly. A named empty subclass may be introduced later
# only if nominal dispatch or an extension anchor becomes necessary.
@dataclass
class Neo4jConnectionConfig: # hoisted from the two duplicate copies
uri: str
username: str = "neo4j"
password: str = "neo4j"
database: str | None = None
application_name: str | None = None
PyBackend = PyCodeAnalyzerConfig | Neo4jConnectionConfig
TSBackend = CodeAnalyzerConfig | Neo4jConnectionConfig
JavaBackend = CodeAnalyzerConfig # no neo4j backend at presentFactory methods
The Java factory is project-path only; the source_code (single-file) input is
deprecated and removed (a DeprecationWarning is already emitted at the current
entry point), bringing Java into line with the Python and TypeScript factories.
# core.py
class CLDK:
@staticmethod
def python(
project_path: str | Path | None = None,
*,
analysis_level: str = AnalysisLevel.symbol_table,
target_files: list[str] | None = None,
eager: bool = False,
backend: PyBackend = None, # defaults to PyCodeAnalyzerConfig()
) -> PythonAnalysis: ...
@staticmethod
def java(project_path=None, *, analysis_level=..., target_files=None,
eager=False, backend: JavaBackend = None) -> JavaAnalysis: ...
@staticmethod
def typescript(project_path=None, *, analysis_level=..., target_files=None,
eager=False, backend: TSBackend = None) -> TypeScriptAnalysis: ...
@staticmethod
def c(project_path) -> CAnalysis: ...Backend dispatch
The truthiness-based selection in each facade is replaced by a single explicit
dispatch on the configuration type, ideally extracted into a shared resolver:
match backend:
case Neo4jConnectionConfig() as cfg:
...PyNeo4jBackend(cfg.uri, cfg.username, cfg.password, cfg.database, app)
case CodeAnalyzerConfig() as cfg: # PyCodeAnalyzerConfig is a subtype
...PyCodeanalyzer(..., cache_dir=cfg.cache_dir, use_codeql=cfg.use_codeql, use_ray=cfg.use_ray)Cache layout
A single cache_dir must be keyed by language so that a polyglot repository
analyzed under more than one language does not have its backends overwrite a
shared analysis.json. Each backend joins its own canonical language key under
the shared root; callers pass only the root.
<cache_dir>/
java/ analysis.json
python/ analysis.json, venv/, codeql/, analysis_cache.json
typescript/ analysis.json
Representative call sites
CLDK.python("/repo")
CLDK.python("/repo", backend=PyCodeAnalyzerConfig(use_ray=True, use_codeql=False))
CLDK.python("/repo", backend=Neo4jConnectionConfig(uri="bolt://localhost:7687"))
CLDK.java("/repo", backend=CodeAnalyzerConfig(cache_dir="/tmp/ca"))Expected Benefits
- Each per-language signature exposes only the parameters that are applicable,
so static type checkers and editor completion present an honest interface and
the negative-validation clauses are eliminated. - Backend selection becomes explicit and type-driven rather than dependent on
argument truthiness. Introducing a third backend requires one additional
dataclass and one additional match arm, rather than threading new keyword
arguments through every factory. - Parameters are grouped according to the component that owns them (query
versus backend);Neo4jConnectionConfigis consolidated into a single
shared module; and on-disk persistence collapses from two knobs
(cache_dirplusanalysis_json_path) to one language-keyedcache_dir. - Discoverability improves for the LLM-driven skills (poe-with-cldk,
triage-and-pov) that introspect the public interface.
Scope of Work
- Introduce
cldk/analysis/commons/backend_config.pycontaining the
configuration dataclasses and the per-language*Backendunions. - Hoist
Neo4jConnectionConfigto a single shared location and re-export it
from the existingpython/neo4jandtypescript/neo4jpaths for backward
compatibility. - Add the
CLDK.java,CLDK.python,CLDK.typescript, andCLDK.c
factory methods; the Java factory is project-path only. - Replace each facade's truthiness-based backend selection with a dispatch
on the configuration type, extracting a shared resolver where appropriate. - Add a single
cache_dirparameter; default it to<project>/.codeanalyzer
and key the on-disk layout by language (<cache_dir>/<language>/). - Remove
analysis_json_pathfrom the public interface; route Java/TS
analysis.jsonoutput and reuse through the language-keyed cache. - Remove
analysis_backend_pathfrom the public interface entirely; source
the backend binary solely from the packaged dependency (Java/TS), with
$CODEANALYZER_TS_BINremaining as the only out-of-band override. - Remove the Java
source_codeinput after its deprecation window: delete
the parameter fromJavaAnalysis.__init__, the compatibility shim, and
JCodeanalyzer. - Migrate the Python backend's existing
cache_dir/analysis_json_path
split onto the unified, language-keyed layout. - Retain
CLDK(language).analysis(...)as a thin deprecated shim that
forwards to the new factories for one or two release cycles. - Update the documentation and the skills that construct
CLDK(...). - Add tests covering backend selection by configuration type, the
language-keyed cache layout, and the deprecation shims.
Compatibility Considerations
The change introduces a new preferred entry point while retaining the existing
one as a deprecation shim, so the migration is staged rather than abrupt. The
work may proceed incrementally: the factory methods may be introduced first,
after which the typed backend= configurations may be added alongside the
existing keyword arguments under a deprecation window.
The language-keyed cache layout relocates analysis.json from
<cache_dir>/analysis.json to <cache_dir>/<language>/analysis.json. Existing
caches will not be found at the new path, so the first run after the change
recomputes the analysis. This is benign and should be noted in the changelog.
Activity
rahlk commentedon Jun 22, 2026
Addendum: Deprecation of Java source_code input
The Java
source_code(single-file) input is deprecated and slated for removal.A
DeprecationWarningis now emitted at the public entry point when it issupplied (
core.py, Java branch), and the corresponding parameterdocumentation has been annotated accordingly.
Consequences for this redesign:
JavaCodeAnalyzerConfigshould NOT carry asource_codefield. The Javafactory (
CLDK.java) is therefore project-path only, bringing Java into linewith the Python and TypeScript factories, which already reject single-file
mode.
CLDK(language).analysis(...)shouldcontinue to forward
source_code(with the warning) until the removal lands,after which the parameter is dropped from both the shim and the underlying
JavaAnalysisconstructor.Additional task
source_codeinput after the deprecation window:delete the parameter from
JavaAnalysis.__init__, theCLDK(language).analysis(...)shim, andJCodeanalyzer; omit it fromJavaCodeAnalyzerConfig.rahlk commentedon Jun 22, 2026
Addendum: Unified cache directory with per-language keying
Decision: consolidate the on-disk persistence knobs into a single
cache_dirparameter shared by all languages, and remove
analysis_json_pathfrom thepublic interface.
analysis_backend_pathis removed entirely from thepublic interface; the backend binary is sourced exclusively from the
pyproject.tomldependencies (codeanalyzer-java,codeanalyzer-typescript,codeanalyzer-python).Rationale
analysis_json_pathis not a backend locator; it is the on-disk cache/outputlocation for
analysis.json, and it governs caching behavior. For Java andTypeScript a value of
Noneforces pipe mode, in which the analyzer recomputeson every instantiation; a non-
Nonevalue enables reuse via the existingcompatibility check. It is therefore the Java/TypeScript analogue of the Python
cache_dir. Deleting it outright would regress Java/TypeScript to uncachedoperation, so it is folded into
cache_dirrather than dropped.analysis_backend_pathis removed outright rather than retained as an override.The packaged binary is now the single source of the backend executable: Java
invokes
python -m codeanalyzer_java, and TypeScript resolvescodeanalyzer_typescript.bin_path()from the installed package. The$CODEANALYZER_TS_BINenvironment variable remains available as anout-of-band escape hatch for TypeScript, so no public parameter is required for
custom or locally built binaries.
Caveat: subdirectories must be keyed by language
A single
cache_dirintroduces a collision hazard: a polyglot repositoryanalyzed under more than one language would otherwise have each backend write
analysis.jsonto the same location, overwriting the others. The cache layoutmust therefore be keyed by language. Proposed layout, with the default
cache_dirresolving to<project>/.codeanalyzer:Each backend is responsible for joining its own language key under
cache_dir;callers pass a single root and never the per-language subdirectory. The key
should be the canonical language identifier (
java,python,typescript,c) so the partitioning is stable and predictable.Effect on the configuration objects
CodeAnalyzerConfigis revised to carry a unifiedcache_dironly; it nolonger carries
analysis_json_pathoranalysis_backend_path:The default-on caching means the former persistence knobs disappear from a
typical call;
cache_dirneed only be supplied to relocate the cache root.Additional tasks
cache_dirparameter to every factory; default it to<project>/.codeanalyzer.(
<cache_dir>/<language>/) under the shared root.analysis_json_pathfrom the public interface; route Java/TSanalysis.jsonoutput and reuse through<cache_dir>/<language>/.analysis_backend_pathfrom the public interface entirely; sourcethe backend binary solely from the packaged dependency (Java/TS), with
$CODEANALYZER_TS_BINremaining as the only out-of-band override.cache_dir/analysis_json_pathsplit onto the unified, language-keyed layout.
Merge pull request #165 from codellm-devkit/minor/fix-160-161-162-163…