Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
d29d21f
docs(spec): leg 4 — taint() as a refutation instrument
rahlk Sep 9, 2026
629c1de
docs(spec): name the field for what it certifies — exhausted, not ref…
rahlk Sep 9, 2026
7dba5f6
docs(spec): correct §2 — Python's value traversals are scoped by id, …
rahlk Sep 9, 2026
cc5de97
docs(plan): leg 4 — the shared path skeleton, then taint()
rahlk Sep 9, 2026
fb626d7
refactor(graphs): lift via_case and path_order out of the three Neo4j…
rahlk Sep 9, 2026
d182920
docs: #195 is fixed upstream, and correct the byte-identical claim
rahlk Sep 9, 2026
a5ee1ac
refactor(graphs): add sdg_path_query, reproducing all three _PATHS by…
rahlk Sep 9, 2026
4e10050
docs(plan): correct 4a Task 2's brace assertion, and pin which backen…
rahlk Sep 9, 2026
3417c31
refactor(neo4j): build _PATHS from the shared generator in all three …
rahlk Sep 9, 2026
9b6b36b
test(commons): stop comparing against the now-deleted _VIA_CASE/_PATH…
rahlk Sep 9, 2026
23f0921
test(commons): replace the expired byte-identity guard with a digest …
rahlk Sep 9, 2026
b2e0beb
refactor(graphs): make sdg_path_query's scope params callables
rahlk Sep 9, 2026
787876a
refactor(neo4j): pass _scoped itself to sdg_path_query, not a call
rahlk Sep 9, 2026
25d5557
test(commons): drop PATHS_ARGS, judge the shipped statement instead
rahlk Sep 9, 2026
e27d834
Merge branch 'chore/issue-386-relax-model-extra' into feat/issue-382-…
rahlk Sep 9, 2026
5444dab
feat(results): TaintResult, with exhausted named for the search and n…
rahlk Sep 9, 2026
6f3aef6
docs(results): TaintResult.unresolved — describe the pair association…
rahlk Sep 9, 2026
b0cd49e
feat(graphs): shortest_walks filters in both passes, so a sanitized s…
rahlk Sep 9, 2026
606d548
test(graphs): guard the replay's own filter and the src cut in shorte…
rahlk Sep 9, 2026
336995f
feat(graphs): sdg_taint_query, with the sanitizer cut inside the pattern
rahlk Sep 9, 2026
ed14b3d
feat(resolve): sanitizer selectors route by shape, scoped to their ca…
rahlk Sep 9, 2026
da0f1d9
feat(python): taint() on the ABC, where the order of the checks is th…
rahlk Sep 10, 2026
cad6b78
feat(typescript): taint() on the ABC, the same ordered contract in Ty…
rahlk Sep 10, 2026
6daead7
feat(java): taint() on the ABC, with the port-lattice gate after the …
rahlk Sep 10, 2026
6fa92a0
fix(taint): every ledger key is read, so a blocked pair cannot be cer…
rahlk Sep 10, 2026
126c2da
fix(taint): one pair per resolved position, so a duplicated selector …
rahlk Sep 10, 2026
70cf27c
fix(java): taint() asks the level gate, so a shallow analysis is not …
rahlk Sep 10, 2026
b9c0d3f
refactor(taint): the post-walk assembly lives once in commons, not th…
rahlk Sep 10, 2026
c3f833f
fix(results): a skipped pair says degenerate_pair, so two published t…
rahlk Sep 10, 2026
c414c6a
docs(taint): complete is the batch's flag, said once instead of three…
rahlk Sep 10, 2026
f8a1b24
docs(java): the walk docstring stops claiming a gate taint already as…
rahlk Sep 10, 2026
21622e6
feat(py-neo4j): _taint_walk, one traversal for m sources x n sinks
rahlk Sep 10, 2026
3630528
feat(py-local): _taint_walk over the local SDG, with the scoped varia…
rahlk Sep 10, 2026
bde2444
fix(taint): delimit the callable cut, so `create` cannot sever `creat…
rahlk Sep 10, 2026
e2dd243
fix(sdg): keep the var the analyzer puts on a param edge
rahlk Sep 10, 2026
f4e9bb6
docs(taint): the empty ledger is a refusal to file, not a missing signal
rahlk Sep 10, 2026
57213e4
test(taint): assert which witness survives the cap, on both walks
rahlk Sep 10, 2026
531d8c6
refactor(neo4j): name _EDGE_VARS' callable scope apart from $prefix
rahlk Sep 10, 2026
13709cf
test(taint): cover the cut's nested-callable disjunct, and cite it co…
rahlk Sep 10, 2026
45f1c21
feat(typescript): the local taint walk, scoped by callable id and not…
rahlk Sep 10, 2026
37d78fc
feat(typescript): the graph taint walk, and three scope-audit rules i…
rahlk Sep 10, 2026
61e208d
feat(java): the local taint walk, and the param edge var it needs to …
rahlk Sep 10, 2026
9818729
feat(java): the graph taint walk, with the interior scope its endpoin…
rahlk Sep 10, 2026
d3912d2
refactor(taint): both walk hooks are abstract, now that every backend…
rahlk Sep 10, 2026
38b5241
test(taint): pin each backend's _TAINT by digest, like _PATHS
rahlk Sep 10, 2026
e79de3f
fix(graphs): sdg_taint_query refuses a self-pair instead of aborting …
rahlk Sep 10, 2026
d7be77b
feat(facades): taint() on all three facades, with the contract in the…
rahlk Sep 10, 2026
6196ad9
test(python): a naturally refuted pair, a 169-pair batch, and a corre…
rahlk Sep 10, 2026
451bd61
test(java,typescript): the sanitizer cut, on daytrader8 and superset-…
rahlk Sep 10, 2026
3072ac1
docs: taint() on the agent reference, the skill, and the per-language…
rahlk Sep 10, 2026
ad6c21b
test(taint): pin codeanalyzer-python#204's false refutation instead o…
rahlk Sep 10, 2026
9a47d22
test(taint): assert the variable cut's coalesce by name, not only thr…
rahlk Sep 10, 2026
547ed05
fix(taint): one delimited spelling for the sanitizer acceptance domai…
rahlk Sep 10, 2026
74624c6
docs(taint): correct the level-gate account and pin the facade delega…
rahlk Sep 10, 2026
0d119d3
test(java): measure the param-edge var label instead of asserting it
rahlk Sep 10, 2026
4061dca
docs(taint): name sanitizer examples after callables the application …
rahlk Sep 10, 2026
119f344
test(java): correct the param-edge var shape against a real 3.1.2 run
rahlk Sep 10, 2026
1869c42
Merge pull request #383 from codellm-devkit/docs/issue-382-leg-4-taint
rahlk Sep 11, 2026
f7eec92
Merge remote-tracking branch 'origin/release/2.0' into feat/issue-382…
rahlk Sep 11, 2026
01d1b0a
Merge pull request #393 from codellm-devkit/feat/issue-382-leg-4b-taint
rahlk Sep 11, 2026
9806578
Merge remote-tracking branch 'origin/main' into feat/issue-404-java-v…
rahlk Sep 11, 2026
959b041
feat(java): view dispatches — JViewDispatch models, get_view_dispatch…
rahlk Sep 11, 2026
4a11011
chore(release): 2.0.0-rc.7
rahlk Sep 11, 2026
c258b1e
Merge pull request #406 from codellm-devkit/feat/issue-404-java-view-…
rahlk Sep 11, 2026
7006785
chore(deps): bump codeanalyzer-python 1.5.2 -> 1.5.3 (#410)
rahlk Sep 16, 2026
d3586e1
fix(python): locate places a decorator-line position on the decorated…
rahlk Sep 16, 2026
d80a1d7
docs(python): record the #408 locate benchmark above _LOCATE_QUERY
rahlk Sep 16, 2026
d48a74d
docs(changelog): #408 locate on decorator lines
rahlk Sep 16, 2026
d2ac15d
Merge pull request #411 from codellm-devkit/chore/issue-410-codeanaly…
rahlk Sep 16, 2026
69aa675
Merge pull request #412 from codellm-devkit/fix/issue-408-locate-deco…
rahlk Sep 16, 2026
ce45946
chore(release): 2.0.0-rc.8
rahlk Sep 16, 2026
834878e
Merge pull request #414 from codellm-devkit/chore/release-2.0.0-rc.8
rahlk Sep 16, 2026
32cda35
docs: split the README into a user page, an architecture page and a c…
rahlk Sep 16, 2026
9440b95
Merge pull request #417 from codellm-devkit/docs/issue-416-readme-split
rahlk Sep 16, 2026
854c6b2
chore(release): 2.0.0 (#430)
rahlk Oct 15, 2026
15c3cde
Merge pull request #431 from codellm-devkit/chore/issue-430-release-2…
rahlk Oct 15, 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
137 changes: 137 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,143 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [v2.0.0] - 2026-10-15

CLDK 2.0 gives Python, Java and TypeScript one agent-facing query surface — addressing, per-callable
control and data flow, slices, call-graph and value-flow predicates, entrypoints and the
repository-artifact layer — answered identically from an `analysis.json` or an attached Neo4j graph.
No code changes since `v2.0.0-rc.8` except the README split into a user page, `docs/architecture.md`
and `docs/citations.md` (#416). The rc sections below are the full record; this section lists what
breaks for code written against **1.5.0**, each with its migration step.

### Breaking (since 1.5.0)

- **C support is removed** (`CLDK.c()`, `cldk.analysis.c`, `cldk.models.c`; rc.2). There is no
replacement; pin `cldk<2` if you need it.
- **Java needs the `java` extra.** The analyzer ships as the `codeanalyzer-java` wheel with its own
JVM, not a jar in this repo (rc.3). Install with `pip install "cldk[java]"`.
- **Every Neo4j graph must be re-emitted, into a wiped database.** The `can://` id grammar now puts the
application outermost (`can://<app>/<lang>/...`, rc.4), and each backend refuses a graph below its
floor at attach with `GraphSchemaMismatch`, rather than answering with zero rows. The floors are
`codeanalyzer-python` 1.5.0, `codeanalyzer-typescript` 1.5.2 and `codeanalyzer-java` 3.2.0 (rc.6).
Re-emit with the pinned analyzer (`--emit neo4j`); there is no in-place upgrade.
- **Java and TypeScript models mirror schema v2** (rc.3). A 1.x `analysis.json` is refused rather than
parsed: re-run the analysis with the pinned analyzer. For Java, `get_call_graph()` nodes are
`"<type fqn>.<signature>"` strings rather than `(signature, klass)` tuples — read the class from
`cg.nodes[key]["method_detail"].klass`. Local and anonymous class names carry their declaring callable
— take class keys from `get_all_classes()` rather than composing them. `JGraphEdges` is
`{src, dst, prov, weight}`. For TypeScript, `TSCallable` and `TSModule` lost their per-file fields (v2
keys modules by path).
- **Java's single-file `source_code` mode is gone** (rc.3). Analyze the project directory.
- **`PyCallableOverview.path` is repo-relative**, not an absolute path (rc.2). Stop stripping a
project-root prefix off it.
- **Accessors that only ever raised `NotImplementedError` are removed**: fifteen on `PythonAnalysis`
(rc.2) and `get_entry_point_methods` / `get_service_entry_point_methods` on `TypeScriptAnalysis`
(rc.3). Use `get_entrypoints()` / `get_entrypoint_classes()`.

### Analyzer pins

`codeanalyzer-python==1.5.3`, `codeanalyzer-typescript==1.6.0`, `codeanalyzer-java==3.3.3` (extra).

## [v2.0.0-rc.8] - 2026-09-16

### Fixed

- **`locate` places a position on a decorator line on the decorated callable, on both Python
backends** (`python-sdk#408`). `PyCallable.start_line` is the `def` line (`ast.FunctionDef.lineno`)
and Python's AST puts decorators above it, so `@http.route(...)` sat inside no callable span and
came back as `module_scope`. Both backends now read where the decorator is *applied*:
`_find_innermost` admits the callable whose `decorators[].span` starts at or before the line and
above its `def`; `_LOCATE_QUERY` gains one `EXISTS` disjunct over `PY_DECORATED_BY.start_line`,
which codeanalyzer-python 1.5.2 emits. Ranking is unchanged (the def-based width), so a decorator
on a nested callable resolves to the nested one. Three things deliberately stay as they were: a
position between two callables with no decorator over it is still `module_scope` (this is not a
nearest-callable fallback); a **class** decorator is still `module_scope` (a result with `type`
set and `callable` unset is a new shape and needs its own decision); and an analysis or graph from
codeanalyzer-python 1.5.1 or earlier, which records no decorator span, answers exactly as before.
Measured on odoo-slim-19 re-emitted by 1.5.3, 40 positions, median of 5, same graph both ways:
71.0 ms placing 20/40 → 70.2 ms placing 40/40; the plan still seeks `pysymbol_id`.

### Changed

- `codeanalyzer-python` pin `1.5.2` → **`1.5.3`** (`dependencies` and `[tool.backend-versions]`,
`python-sdk#410`). Two analyzer fixes, neither moving the schema (`schema_version` and the graph
`SCHEMA_VERSION` stay `2.0.0`; `PyNeo4jBackend._ANALYZER_FLOOR` stays `1.5.0`):
- the analyzer no longer ingests its own output (codeanalyzer-python#207). `discover_artifacts`
had no idea where the run writes, so an output or cache directory inside the input made run N
inventory run N-1's `analysis.json` and embed it whole. This SDK's default `cache_dir` is
`<project>/.codeanalyzer`, inside the input, so every local run was exposed.
- one body node per call site when two calls share a start position (codeanalyzer-python#215).
`getattr(o, n)(x)` starts the outer application and the inner `getattr` at the same column, and
the position-keyed `body` kept the inner one. Keys now carry a `/2`, `/3` disambiguator,
outermost first; `body_key_column` already splits on `/` and needed no change. A key that used
to collide now names the **outer** call, and a call whose callee is itself a call carries
`callee_signature: null` with `method_name: "<unknown>"`.

### Verification

The Python tier offline (`tests/analysis/python`, mocked Neo4j and the in-process analyzer):
482 passed, 168 skipped, on 1.5.2 and on 1.5.3 alike — every skip is a live-Neo4j tier. No fixture
expectation moved.

## [v2.0.0-rc.7] - 2026-09-11

The Java facade learns the view layer codeanalyzer-java 3.3.0–3.3.2 added, and the 2.0 line takes up
everything rc.6 shipped from `main` (the 3.2.0 text model and the sibling-analyzer pins) — this is
the first release cut from `release/2.0` that contains rc.6.

### Added

**Java view dispatches** (`python-sdk#404`, spec `codellm-devkit/.github`
`docs/design/specs/2026-09-11-java-view-templates-and-dispatch.md`). JSP, Facelets and Thymeleaf
templates are artifacts with `roles: ["view-template"]`, and the analyzer now says which code reaches
one. Three accessors on `JavaAnalysis`, both backends:

- `get_view_dispatches(view=None) -> List[JViewDispatch]` — every resolved dispatch: the body node
that hands the request over (a `forward` / `include` / `sendRedirect` call, a `ModelAndView`
construction or `setViewName`, or a Spring controller's `return`) and the artifact it reaches,
with `via` naming the mechanism and `prov` the tier. `literal` and `dataflow` mean exactly one
target; `table` (3.3.1) is a may-dispatch over a static string table, one edge per entry, from
the same site. `view` filters by a segment-aligned suffix of the artifact's path.
- `get_view_dispatchers(path) -> List[JCallableOverview]` — the same edges resolved to the callables
that own the dispatching sites.
- `get_unresolved_view_dispatches() -> List[JViewDispatchUnresolved]` — what closed on nothing (a
variable target, a servlet URL, a view name matching two templates), with its reason and the tiers
attempted. JSON-only: the graph has no node for a target that resolved to nothing, so the Neo4j
backend refuses this one rather than answering `[]`.

Two models, `JViewDispatch` and `JViewDispatchUnresolved`, on `JApplication.view_dispatches` /
`.view_dispatches_unresolved`; the Neo4j backend reads `J_DISPATCHES_TO` back into the former.

One rule stated because it is the only one of its kind: **this layer is gated on the analyzer
generation.** The 3.1.0 config trio is probed by an always-present sibling key; 3.3.x added none —
both lists are written only when non-empty and the edge type is declared only once an edge exists —
so a pre-3.3.0 analysis is refused from `analyzer.version` (wire) / `analyzer_version` (graph) instead,
because `[]` off a 3.2.0 analysis would say "reaches no view" where the truth is that nothing looked.

Measured on daytrader8 with 3.3.3: 37 edges at level 1–2 (3 `literal`, 34 `table`), 54 at level 4,
19 of 23 JSPs reached; the `TradeConfig.webUI` table's 17 on-disk pages all reached.

### Changed

- `codeanalyzer-java` pin `3.2.0` → **`3.3.3`** (the `java` extra and `[tool.backend-versions]`).
3.3.0 adds the view-template role, `J_DISPATCHES_TO`, and `argument_expr` on `return` body nodes;
3.3.1 the table tier; 3.3.2 narrows the Jakarta entrypoint finder to lifecycle methods, so
helpers taking servlet-typed parameters are no longer entrypoints and the interprocedural
config-use and view-dispatch tiers bind their parameters (daytrader8 entrypoint marks
135 → 196: 21 helpers dropped, 82 `init`/`destroy`/`doFilter`/`onMessage` gained); 3.3.3 fixes
the `NullPointerException` 3.3.0–3.3.2 threw on any unbuilt project with a dispatch call, which
this SDK's `test_java_degradation.py` was the first to hit (codeanalyzer-java#265).
- `release/2.0` absorbed `main` at rc.6.

### Verification

The Java suite offline on both backends over a1 with the layer injected (edges, suffix filter,
dispatcher resolution, the JSON-only refusal on the graph, the pre-3.3.0 refusal, the empty answer
on a 3.3.x payload that reaches nothing, and the answered-once check across backends); the public
surface pins the three signatures; e2e against the 3.3.3 wheel on daytrader8 at level 1 and 2
asserts the 37 edges, the 19 views, the three unresolved URLs and the 38 view-template artifacts.

## [v2.0.0-rc.6] - 2026-09-10

The Java Neo4j backend stops being the lossy one about source text. Until now a graph-backed Java
Expand Down
15 changes: 13 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,25 @@ artifact six, and the J-7 leaf accessors (`get_interfaces`/`get_enums`/`get_enum
`get_records`, names shared with TypeScript). Both backends answer identically, including the miss
paths; the policy lives once on `JavaAnalysisBackend` because `JNeo4jBackend` rebuilds the canonical
`JApplication` and answers from it. Four things Java says rather than answering, each measured:
`slice_forward` / `paths_between` / `flows_to_call` / `flows_to_argument` raise (the analyzer's L4
port lattice carries no dependence edge, codeanalyzer-java#227); `get_entrypoint_coverage` reports
`slice_forward` / `paths_between` / `flows_to_call` / `flows_to_argument` raise **when the attached
analysis has no dependence edge leaving a `formal_in`** (codeanalyzer-java#227) — the probe reads the
data, never a version, so a graph re-emitted by >= 3.0.3 answers instead: measured on daytrader8,
`_ports_carry_dependence` is `True` and none of the four refuses there; `get_entrypoint_coverage` reports
`entrypoint_report_unavailable` and the three config-read accessors raise on an analysis older than
codeanalyzer-java 3.1.0, which is the release that added both overlays (the probe is the entrypoint
report's presence, measured from the data, never a version string); `get_external_symbols` raises
off a local run (`--external-calls` is opt-in and `--emit neo4j` forces it); the CRUD accessors
still raise. `docs/agent-api-reference.md` has the full lossiness list.

**All three languages, since leg 4b (#382):** `taint(sources, sinks, sanitizers=(), *, depth=None,
max_paths=10)` on every facade — m sources against n sinks in one traversal, with `TaintResult`
adding `exhausted` (pairs searched to exhaustion: the refutation `paths_between` cannot give),
`roots`, `resolved` and `unresolved`. Three rules the docstrings carry and the tests pin: an explicit
`depth` empties `exhausted` by rule; `complete` is the batch's flag and one blocked pair voids every
absence claim in the result; `max_paths` caps per pair, never per call. The source/sink/sanitizer
vocabulary is the caller's — no framework catalogue ships. Java shares the `formal_in` dependence
probe above, so `taint` refuses on the same analyses the four verbs do.

The legacy `CLDK(language="<lang>").analysis(...)` entry still works as a compat shim. Adding a
language means a new factory method + facade + backend ABC/impl(s) + models + tests — **update this
table in the same change**.
Expand Down
3 changes: 3 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ We welcome PRs across all our repositories! When submitting:

## Development Guidelines

### Repository layout
- See [docs/architecture.md](docs/architecture.md) for how the SDK's models, backends, and shared result types fit together

### Code Quality
- Follow the repository's established patterns
- Include appropriate documentation
Expand Down
Loading
Loading