Prebuilt codeanalyzer-typescript backend binary for CLDK (codellm-devkit).
Project description
codeanalyzer-typescript (cants)
A TypeScript/JavaScript static-analysis toolkit — the CLDK backend that emits the canonical schema-v2 Code Property Graph (symbol table → call graph → intraprocedural dataflow → interprocedural SDG), as analysis.json or a Neo4j property graph.
cants is a static analyzer for TypeScript/JavaScript built on the TypeScript compiler (via
ts-morph). It produces the canonical CodeLLM-DevKit (CLDK) schema v2
— one additive Code Property Graph, built up level by level (symbol table → call graph →
intraprocedural dataflow → interprocedural SDG) — as analysis.json and can project that same
structure into a Neo4j property graph. It is the TypeScript backend behind
CLDK, mirroring its
Python and
Java siblings.
By default the call graph is the union of two backends: the TypeScript compiler's resolver and
Jelly — a flow-based analyzer that resolves higher-order and
callback edges the resolver misses, embedded in the cants binary (no extra install). Merged edges
keep a provenance tag (tsc / jelly), so you can still tell the two apart. Pass --tsc-only to
drop Jelly and run the resolver alone, or --call-graph-provider jelly for Jelly alone.
Table of Contents
Features
- Symbol table — modules, classes, interfaces, enums, type aliases, namespaces, functions, methods, variables, decorators, and JSDoc, with precise source spans.
- Call graph — the TypeScript compiler's resolver plus Rapid Type Analysis (RTA), with phantom (external) nodes for calls into imported libraries and Node builtins.
- Pluggable call-graph backend — the
unionof thetscresolver and the embedded Jelly flow analyzer by default (--tsc-onlyfor the resolver alone,--call-graph-provider jellyfor Jelly alone). - Neo4j output — project the analysis into a labeled property graph: a self-contained
graph.cyphersnapshot, or an incremental push to a live database over Bolt. - Versioned schema — a machine-readable, version-stamped Neo4j schema contract
(
--emit schema), bundled in every release and enforced by a conformance test. - Self-contained binary — no Bun or Node required at runtime; install via
pip, Homebrew, or a one-line shell script. - Incremental — content-hash caching so re-analyzing (and re-loading the graph) only touches what changed.
Installation
Prerequisites
Running a prebuilt cants binary requires nothing — it is fully self-contained. To analyze a
project, that project should be a normal Node/TypeScript project (so the compiler can resolve types
and imports). Building cants from source requires Bun 1.0+.
Install via shell script
Download and install the prebuilt binary for your platform from the latest release:
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/codellm-devkit/codeanalyzer-typescript/releases/latest/download/cants-installer.sh | sh
The installer drops cants into ~/.local/bin (override with CANTS_INSTALL_DIR) and can pin a
version with CANTS_VERSION=vX.Y.Z. Supports macOS (arm64/x86_64) and Linux (x86_64/aarch64).
Install via Homebrew
brew install codellm-devkit/homebrew-tap/codeanalyzer-typescript
Install via pip (PyPI)
The wheel bundles the prebuilt, self-contained binary for your platform (no Bun or Node required):
pip install codeanalyzer-typescript
cants --help
This is also the package CLDK's Python SDK depends on to locate the analyzer backend; it exposes
codeanalyzer_typescript.bin_path() and schema_path().
Build from source
# Install Bun, then:
git clone https://github.com/codellm-devkit/codeanalyzer-typescript
cd codeanalyzer-typescript
bun install
bun run build # → dist/cants (standalone native binary)
You can also run the analyzer directly from source without compiling:
bun run start -- --input /path/to/typescript/project
Usage
cants --input /path/to/typescript/project
With no --output, the analysis is printed to stdout as compact JSON; with --output <dir> it is
written to analysis.json (or graph.cypher for --emit neo4j) in that directory.
Options
Usage: cants [options]
CLDK TypeScript analyzer — emits the canonical schema-v2 CPG (symbol table →
call graph → dataflow → SDG) as analysis.json, or a Neo4j graph.
Options:
-i, --input <path> project root to analyze (not required for
--emit schema)
-o, --output <dir> output directory (omit ⇒ compact output to
stdout)
--emit <target> output target: json (analysis.json, default) |
neo4j (graph.cypher or live push) | schema (the
Neo4j schema.json contract) (default: "json")
--app-name <name> logical application name for the graph
:Application anchor (default: input dir name)
--neo4j-uri <uri> push the graph to a live Neo4j over Bolt
(incremental); omit to write graph.cypher (env:
NEO4J_URI)
--neo4j-user <user> Neo4j username (default: "neo4j", env:
NEO4J_USERNAME)
--neo4j-password <password> Neo4j password (prefer the env var; a flag is
visible in shell history / process list)
(default: "neo4j", env: NEO4J_PASSWORD)
--neo4j-database <db> Neo4j database name (env: NEO4J_DATABASE)
-a, --analysis-level <n> analysis depth: 1 = symbol table (default); 2 =
+ resolver call graph; 3 = + intraprocedural
dataflow (cfg/cdg/ddg); 4 = + interprocedural
SDG (param_in/param_out/summary) (default: "1")
--graphs <list> dataflow sections to emit, comma-separated: cfg
| dfg | pdg (require -a 3) | sdg (requires -a
4); default: all rungs at or below the level
--graph-field-depth <k> access-path depth bound (k-limit) for level-3
dataflow (default: "3")
-j, --jobs <n> worker parallelism for level-3 graphs (default:
sequential; opt in with N ≥ 2 on large projects
— each worker loads its own copy of the
program)
-t, --target-files <paths...> restrict analysis to specific files
(incremental)
--skip-tests skip test trees (default)
--include-tests include test trees
--eager force a clean rebuild instead of reusing the
cache
--lazy reuse the cache (default)
--no-build skip dependency materialization (use a prepared
node_modules)
--no-phantoms disable phantom (external) nodes for
imported/required library calls
--call-graph-provider <name> call-graph backend: union (default, tsc ∪
jelly) | tsc | jelly | both (deprecated alias
of union) (default: "union")
--tsc-only use the tsc resolver only — opt out of Jelly
edges (overrides --call-graph-provider)
-c, --cache-dir <dir> cache/intermediate directory
-v, --verbose increase verbosity (repeatable)
-h, --help display help for command
Examples
-
Basic analysis to stdout, or to a file:
cants --input ./my-ts-project # compact JSON on stdout cants --input ./my-ts-project --output ./out # → ./out/analysis.json
-
Emit a Neo4j snapshot, or push to a live database:
cants --input ./my-ts-project --emit neo4j --output ./out # → ./out/graph.cypher cants --input ./my-ts-project --emit neo4j \ --neo4j-uri bolt://localhost:7687 --neo4j-user neo4j --neo4j-password secret
-
Incremental analysis of specific files:
cants --input ./my-ts-project --target-files src/a.ts src/b.ts
-
Resolver-only call graph (opt out of Jelly):
cants --input ./my-ts-project --tsc-only
-
Force a clean rebuild with a custom cache directory:
cants --input ./my-ts-project --eager --cache-dir /path/to/custom-cache
-
Program graphs (level 3): CFG/PDG/SDG in
analysis.json:cants --input ./my-ts-project -a 3 # full program_graphs section cants --input ./my-ts-project -a 3 --graphs cfg,pdg # scope the emitted graphs
Output targets
cants builds one analysis in memory and can emit it three ways (--emit):
analysis.json (default)
The canonical schema v2 — one additive Code Property Graph: a containment tree of nodes
(id / kind / span / children) with typed edge overlays. Analysis levels populate it more
deeply; each level only ever adds.
{
"schema_version": "2.0.0", "language": "typescript", "max_level": 4, "k_limit": 3,
"application": {
"id": "can://typescript/<app>", "kind": "application",
"symbol_table": { // L1: the tree, keyed by file path
"<file>": { "kind": "module", "source": "…",
"types": { /* class | interface | enum | type_alias | namespace nodes */ },
"functions": { /* callable nodes: { id, kind, span, body{}, cfg[], cdg[], ddg[], summary[] } */ },
"fields": { /* module-level bindings */ } } },
"call_graph": [ /* L2: { src, dst, prov, weight } — callable → callable, can:// ids */ ],
"param_in": [ /* L4: actual_in → formal_in, fully-qualified can://…@local ids */ ],
"param_out": [ /* L4: formal_out → actual_out */ ]
}
}
Each callable's body{} is keyed by local id (line:col, or @entry/@formal_in:N/… for
synthetic vertices); intra-callable edge lists (cfg/cdg/ddg/summary) use those bare local
ids, cross-callable lists use fully-qualified can://…@local ids. A single signature canonicalizer
underlies every can:// id, so call edges, dataflow edges, and tree nodes all join. The full model
is .claude/SCHEMA_DECISIONS.md (§ "Schema v2 migration") and the CLDK canonical-schema.md.
Dataflow (-a 3 intraprocedural, -a 4 interprocedural)
Native dependence graphs, built in-process from the same ts-morph AST (no external engine), grown into the tree (not a separate section):
-a 3completes each callable'sbody{}with statement nodes and hangs the intra-callable edge listscfg(exceptional control flow),cdg(control dependence), andddg(data dependence via reaching-definitions,prov:["reaching-defs"]) on the callable.-a 4adds the synthetic@formal_in:N/@formal_out/<L>/actual_in:N/<L>/actual_outvertices, the intra-callersummaryedges, and the application-scopeparam_in/param_outlists — the whole-program System Dependence Graph.
-a 3 implies -a 2; -a 4 implies -a 3. --graphs cfg,dfg,pdg,sdg scopes which rungs emit
(cfg/dfg/pdg require -a 3, sdg requires -a 4). L1 ⊆ L2 ⊆ L3 ⊆ L4 is a monotonicity
gate. Every node is addressed by its can://…@local id, so dataflow edges, call edges, and tree
nodes all join.
Substrate (locked in issue #2):
the CFG and reaching-definitions are hand-built from the ts-morph AST; the call-graph oracle is
the existing provenance-merged tsc ∪ Jelly graph; aliasing is a flow-insensitive copy-alias MVP
(Jelly points-to-backed propagation is a staged upgrade). Function summaries are composed
bottom-up over the SCC condensation of the call graph, with k-limited access paths; module
globals ride the SDG as extra parameters. The analysis is deliberately sound-leaning and
over-approximate; known unsoundness (dynamic eval, reflection/monkey-patching, npm-internal
effects) is recorded in .claude/SCHEMA_DECISIONS.md. The analyzer is a pure graph provider:
it emits the dependence-graph substrate (CFG/PDG/SDG + summary edges) and stops — backward
slicing and taint are reachability queries over the SDG that live in the frontend SDK, not here.
Parallelism (-j/--jobs). The pipeline implements the level-3 parallel execution model:
stage-1–4 extraction fans out per callable over a Bun worker pool (partitioned by file) and is
posted before the call-graph solve so the two overlap; summary composition runs as a
Kahn-style ready-queue wavefront over the SCC condensation DAG (the SCC is the atomic unit).
--jobs N output is byte-identical to --jobs 1 (node ids are span-ordered, all edge lists
are collect-then-sorted, and the SCC fixpoint is a pure function of its inputs) — enforced by a
differential test. It is off by default and worth opting into only on large codebases: ts-morph
ASTs cannot cross the worker boundary, so each extraction worker loads its own copy of the
program, which dominates the parallelizable graph math on small/mid repos (self-analysis runs
2.5× slower at -j 14). Worker failure at any stage degrades to the sequential path with a
warning — never to wrong or missing output.
Levels 1/2 are unaffected: nothing in level 3 runs unless -a 3 is requested.
Neo4j graph
--emit neo4j projects the same v2 tree into a labeled property graph: every node keyed by its
can:// id under a shared :CanNode merge label (+ a TS-prefixed specific kind label, e.g.
:TSModule, :TSCallable), containment as TS_HAS_MODULE/TS_DECLARES/TS_HAS_METHOD/
TS_HAS_FIELD/TS_HAS_BODY_NODE edges, and the overlays (TS_CALLS, TS_CFG_NEXT, TS_CDG,
TS_DDG, TS_SUMMARY, TS_PARAM_IN, TS_PARAM_OUT) as typed relationships. The graph is
always full-depth — analysis levels gate the JSON path only, so combining -a/--graphs with
--emit neo4j is an error:
- Without
--neo4j-uri— writes a self-containedgraph.cypher(constraints + indexes, a scoped wipe, then batchedMERGEs). Load it withcypher-shell < graph.cypher. - With
--neo4j-uri— pushes to a live Neo4j over Bolt incrementally: only modules whose content hash changed are rewritten, and on a full run modules whose source file vanished are pruned. Every graph carries aschema_versionon its:Applicationnode.
The connection options also read the standard Neo4j environment variables — NEO4J_URI,
NEO4J_USERNAME, NEO4J_PASSWORD, NEO4J_DATABASE — when the corresponding flag is omitted (an
explicit flag wins). Prefer the env var for the password so it doesn't land in shell history or the
process list:
export NEO4J_URI=bolt://localhost:7687
export NEO4J_PASSWORD=secret
cants --input ./my-ts-project --emit neo4j # credentials picked up from the environment
Schema contract
--emit schema writes the machine-readable, version-stamped Neo4j schema (schema.json: node
labels, relationships, properties, constraints, and indexes). It needs no project and is bundled in
every release (as a wheel asset and a GitHub Release asset), so a consumer can validate
producer/consumer compatibility without invoking the binary.
cants --emit schema # print to stdout
cants --emit schema --output ./out # → ./out/schema.json
Development
This project uses Bun as its toolchain.
bun install
bun run start -- --input /path/to/project # run from source
bun run typecheck # type-check
bun test # tests (the Neo4j bolt test is opt-in; see below)
bun run test:container # Neo4j bolt tests — needs Docker/Podman (opt-in)
bun run gen:schema # regenerate schema.neo4j.json
bun run gen:readme # regenerate the cants --help block above
License
Apache 2.0 — see LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file codeanalyzer_typescript-1.0.0-py3-none-win_amd64.whl.
File metadata
- Download URL: codeanalyzer_typescript-1.0.0-py3-none-win_amd64.whl
- Upload date:
- Size: 42.9 MB
- Tags: Python 3, Windows x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8f4d627fbe116ea2047196eccf5e2c0d18a6b183eb1b8d3f7702ee0d38ce89eb
|
|
| MD5 |
848cec249cd51d35cfd250a02a1e2252
|
|
| BLAKE2b-256 |
572e26272454344628dd9bc4b0246ce11933b771de1619c9446307cfafe1e723
|
Provenance
The following attestation bundles were made for codeanalyzer_typescript-1.0.0-py3-none-win_amd64.whl:
Publisher:
release.yml on codellm-devkit/codeanalyzer-typescript
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
codeanalyzer_typescript-1.0.0-py3-none-win_amd64.whl -
Subject digest:
8f4d627fbe116ea2047196eccf5e2c0d18a6b183eb1b8d3f7702ee0d38ce89eb - Sigstore transparency entry: 2176228138
- Sigstore integration time:
-
Permalink:
codellm-devkit/codeanalyzer-typescript@8032d08620622f2fb1a04ce82fdb6570a74e168c -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/codellm-devkit
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@8032d08620622f2fb1a04ce82fdb6570a74e168c -
Trigger Event:
push
-
Statement type:
File details
Details for the file codeanalyzer_typescript-1.0.0-py3-none-manylinux2014_x86_64.whl.
File metadata
- Download URL: codeanalyzer_typescript-1.0.0-py3-none-manylinux2014_x86_64.whl
- Upload date:
- Size: 40.5 MB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a54cacf33d3f174e010f32f3732f29f97475a38ddc02cfdb215ff78c85c561af
|
|
| MD5 |
4c3202dd3e5a59fb2499cbb10af03e08
|
|
| BLAKE2b-256 |
72d736e9fd3cae0bff0354032b807889431481af32812acc097ba303aeb9b893
|
Provenance
The following attestation bundles were made for codeanalyzer_typescript-1.0.0-py3-none-manylinux2014_x86_64.whl:
Publisher:
release.yml on codellm-devkit/codeanalyzer-typescript
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
codeanalyzer_typescript-1.0.0-py3-none-manylinux2014_x86_64.whl -
Subject digest:
a54cacf33d3f174e010f32f3732f29f97475a38ddc02cfdb215ff78c85c561af - Sigstore transparency entry: 2176227922
- Sigstore integration time:
-
Permalink:
codellm-devkit/codeanalyzer-typescript@8032d08620622f2fb1a04ce82fdb6570a74e168c -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/codellm-devkit
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@8032d08620622f2fb1a04ce82fdb6570a74e168c -
Trigger Event:
push
-
Statement type:
File details
Details for the file codeanalyzer_typescript-1.0.0-py3-none-manylinux2014_aarch64.whl.
File metadata
- Download URL: codeanalyzer_typescript-1.0.0-py3-none-manylinux2014_aarch64.whl
- Upload date:
- Size: 40.3 MB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a357b0dca83866f963a03214f933fa884792e3587a2e3cbaabdaad519cc59d8b
|
|
| MD5 |
9c76905e6a1fa62342411001c4fd1c6c
|
|
| BLAKE2b-256 |
5b32e71c426cef7b38780acd0c44b5d981e28e03e09c3db068e5c4dfc6b4718e
|
Provenance
The following attestation bundles were made for codeanalyzer_typescript-1.0.0-py3-none-manylinux2014_aarch64.whl:
Publisher:
release.yml on codellm-devkit/codeanalyzer-typescript
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
codeanalyzer_typescript-1.0.0-py3-none-manylinux2014_aarch64.whl -
Subject digest:
a357b0dca83866f963a03214f933fa884792e3587a2e3cbaabdaad519cc59d8b - Sigstore transparency entry: 2176227771
- Sigstore integration time:
-
Permalink:
codellm-devkit/codeanalyzer-typescript@8032d08620622f2fb1a04ce82fdb6570a74e168c -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/codellm-devkit
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@8032d08620622f2fb1a04ce82fdb6570a74e168c -
Trigger Event:
push
-
Statement type:
File details
Details for the file codeanalyzer_typescript-1.0.0-py3-none-macosx_11_0_arm64.whl.
File metadata
- Download URL: codeanalyzer_typescript-1.0.0-py3-none-macosx_11_0_arm64.whl
- Upload date:
- Size: 28.7 MB
- Tags: Python 3, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f868bd71a9f3971ac887a11c2ca262b2a3180f3c7d67e2a247859843142ebbd7
|
|
| MD5 |
bfdb3537dbd69b013fc8f5f2a123df62
|
|
| BLAKE2b-256 |
f46ce53b5bcc5211df3a0f22f104601ea23a0cada14ec8a067ef0d02fa6a45b6
|
Provenance
The following attestation bundles were made for codeanalyzer_typescript-1.0.0-py3-none-macosx_11_0_arm64.whl:
Publisher:
release.yml on codellm-devkit/codeanalyzer-typescript
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
codeanalyzer_typescript-1.0.0-py3-none-macosx_11_0_arm64.whl -
Subject digest:
f868bd71a9f3971ac887a11c2ca262b2a3180f3c7d67e2a247859843142ebbd7 - Sigstore transparency entry: 2176228039
- Sigstore integration time:
-
Permalink:
codellm-devkit/codeanalyzer-typescript@8032d08620622f2fb1a04ce82fdb6570a74e168c -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/codellm-devkit
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@8032d08620622f2fb1a04ce82fdb6570a74e168c -
Trigger Event:
push
-
Statement type:
File details
Details for the file codeanalyzer_typescript-1.0.0-py3-none-macosx_10_12_x86_64.whl.
File metadata
- Download URL: codeanalyzer_typescript-1.0.0-py3-none-macosx_10_12_x86_64.whl
- Upload date:
- Size: 31.1 MB
- Tags: Python 3, macOS 10.12+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6fff6e7c36cd68c66eb281e8e4cdb98a16d8bc1ad9f4b508cb5bded9faec87d4
|
|
| MD5 |
b2cb33fddeabc16b4a84fdba31918892
|
|
| BLAKE2b-256 |
d858c4a56c8d27c5f6eca1236357fd454034ec49c1bb7d31d34ad8f6d0f66025
|
Provenance
The following attestation bundles were made for codeanalyzer_typescript-1.0.0-py3-none-macosx_10_12_x86_64.whl:
Publisher:
release.yml on codellm-devkit/codeanalyzer-typescript
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
codeanalyzer_typescript-1.0.0-py3-none-macosx_10_12_x86_64.whl -
Subject digest:
6fff6e7c36cd68c66eb281e8e4cdb98a16d8bc1ad9f4b508cb5bded9faec87d4 - Sigstore transparency entry: 2176227659
- Sigstore integration time:
-
Permalink:
codellm-devkit/codeanalyzer-typescript@8032d08620622f2fb1a04ce82fdb6570a74e168c -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/codellm-devkit
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@8032d08620622f2fb1a04ce82fdb6570a74e168c -
Trigger Event:
push
-
Statement type: