Skip to main content

Prebuilt codeanalyzer-typescript backend binary for CLDK (codellm-devkit).

Project description

CodeLLM-DevKit

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.

PyPI Python Release License


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 union of the tsc resolver and the embedded Jelly flow analyzer by default (--tsc-only for the resolver alone, --call-graph-provider jelly for Jelly alone).
  • Neo4j output — project the analysis into a labeled property graph: a self-contained graph.cypher snapshot, 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

  1. 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
    
  2. 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
    
  3. Incremental analysis of specific files:

    cants --input ./my-ts-project --target-files src/a.ts src/b.ts
    
  4. Resolver-only call graph (opt out of Jelly):

    cants --input ./my-ts-project --tsc-only
    
  5. Force a clean rebuild with a custom cache directory:

    cants --input ./my-ts-project --eager --cache-dir /path/to/custom-cache
    
  6. 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 3 completes each callable's body{} with statement nodes and hangs the intra-callable edge lists cfg (exceptional control flow), cdg (control dependence), and ddg (data dependence via reaching-definitions, prov:["reaching-defs"]) on the callable.
  • -a 4 adds the synthetic @formal_in:N / @formal_out / <L>/actual_in:N / <L>/actual_out vertices, the intra-caller summary edges, and the application-scope param_in / param_out lists — 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-contained graph.cypher (constraints + indexes, a scoped wipe, then batched MERGEs). Load it with cypher-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 a schema_version on its :Application node.

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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

codeanalyzer_typescript-1.0.0-py3-none-win_amd64.whl (42.9 MB view details)

Uploaded Python 3Windows x86-64

codeanalyzer_typescript-1.0.0-py3-none-macosx_11_0_arm64.whl (28.7 MB view details)

Uploaded Python 3macOS 11.0+ ARM64

codeanalyzer_typescript-1.0.0-py3-none-macosx_10_12_x86_64.whl (31.1 MB view details)

Uploaded Python 3macOS 10.12+ x86-64

File details

Details for the file codeanalyzer_typescript-1.0.0-py3-none-win_amd64.whl.

File metadata

File hashes

Hashes for codeanalyzer_typescript-1.0.0-py3-none-win_amd64.whl
Algorithm Hash digest
SHA256 8f4d627fbe116ea2047196eccf5e2c0d18a6b183eb1b8d3f7702ee0d38ce89eb
MD5 848cec249cd51d35cfd250a02a1e2252
BLAKE2b-256 572e26272454344628dd9bc4b0246ce11933b771de1619c9446307cfafe1e723

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file codeanalyzer_typescript-1.0.0-py3-none-manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for codeanalyzer_typescript-1.0.0-py3-none-manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 a54cacf33d3f174e010f32f3732f29f97475a38ddc02cfdb215ff78c85c561af
MD5 4c3202dd3e5a59fb2499cbb10af03e08
BLAKE2b-256 72d736e9fd3cae0bff0354032b807889431481af32812acc097ba303aeb9b893

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file codeanalyzer_typescript-1.0.0-py3-none-manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for codeanalyzer_typescript-1.0.0-py3-none-manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 a357b0dca83866f963a03214f933fa884792e3587a2e3cbaabdaad519cc59d8b
MD5 9c76905e6a1fa62342411001c4fd1c6c
BLAKE2b-256 5b32e71c426cef7b38780acd0c44b5d981e28e03e09c3db068e5c4dfc6b4718e

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file codeanalyzer_typescript-1.0.0-py3-none-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for codeanalyzer_typescript-1.0.0-py3-none-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 f868bd71a9f3971ac887a11c2ca262b2a3180f3c7d67e2a247859843142ebbd7
MD5 bfdb3537dbd69b013fc8f5f2a123df62
BLAKE2b-256 f46ce53b5bcc5211df3a0f22f104601ea23a0cada14ec8a067ef0d02fa6a45b6

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file codeanalyzer_typescript-1.0.0-py3-none-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for codeanalyzer_typescript-1.0.0-py3-none-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 6fff6e7c36cd68c66eb281e8e4cdb98a16d8bc1ad9f4b508cb5bded9faec87d4
MD5 b2cb33fddeabc16b4a84fdba31918892
BLAKE2b-256 d858c4a56c8d27c5f6eca1236357fd454034ec49c1bb7d31d34ad8f6d0f66025

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page