See the structure of your codebase.
The model codegraph extracted from google/gson, drawn as it is laid out. Nine packages as plates, nested like the packages they are; 113 classes as blocks whose height is lines of code and whose footprint is member count; in red, the cyclic dependencies.
Codegraph turns source code into a dependency model you can query, draw as a 3D city, browse dependency by dependency, replay through its git history, and have a language model explain bottom-up. It does this without building the code: the extractors run on sources alone (no classpath, jars, etc.).
Four questions, four commands:
| Question | Command | What you get |
|---|---|---|
| What does this codebase look like? | codegraph serve → City tab |
a 3D city: packages are districts, classes are buildings sized by real metrics, dependencies are arcs — click a building to open it in the navigator |
| What exactly depends on what? | codegraph serve → Navigate tab |
a browsable tree with every incoming and outgoing dependency, the member that carries it, and the source line that proves it |
| How did it get this way? | codegraph scm / replay |
churn, hotspots, ownership, co-change, and a city whose timeline scrubs the years |
| What does it mean? | codegraph explain |
one plain-language explanation per method, class and package, written leaves-first so every summary rests on already-explained parts |
Plus the plumbing you need to trust the answers: validate for the model,
analyze for coupling and cycles, export for DOT, CSV, JSON and PlantUML.
Codegraph is installed from one Homebrew tap. The app and the codegraph
command are one install; every language extractor is its own, so a user of
one language never downloads the runtimes of the others:
brew install --cask defsquare/tap/codegraph # Codegraph.app + the `codegraph` command (macOS)
brew install defsquare/tap/codegraph-java # then one line per language you extract
brew install defsquare/tap/codegraph-csharp
brew install defsquare/tap/codegraph-typescript
brew install defsquare/tap/codegraph-elixirIf a recent Homebrew answers Refusing to load formula … from untrusted tap,
run brew trust defsquare/tap once and retry. Nothing else is needed: the
Java extractor is a GraalVM native image, the C# one carries the .NET runtime
and the base class library, the Elixir one carries Erlang and Elixir, and the
TypeScript one is the codegraph-typescript npm package on Homebrew's Node
(npx codegraph-typescript runs it anywhere Node 22 is).
On Linux, Homebrew installs the extractors the same way; the codegraph
command is the codegraph-linux-x64 or -arm64 asset of the
latest release,
made executable and put on PATH. On Windows, the same release carries
codegraph-win-x64.exe, codegraph-java-win-x64.exe and
codegraph-csharp-win-x64.exe. What a release holds and how the tap is
rendered from it is in docs/distribution.md.
To work on codegraph itself, run it from a clone instead — Node 22+, pnpm
(corepack enable pnpm), then the toolchain of each extractor you build:
git clone --recurse-submodules https://github.com/defsquare/codegraph.git && cd codegraph
pnpm install && pnpm -r build # the CLI (bin/codegraph) and the TypeScript extractor
(cd extractors/java && ./mvnw -B package) # Java: a JDK 17+; the wrapper, no local Maven needed
./build.sh --csharp # C#: the .NET 10 SDK
./build.sh --elixir # Elixir: Erlang/OTP 27 + Elixir
ln -s "$PWD/bin/codegraph" ~/.local/bin/codegraph # optional, works from a symlinkAlready cloned without --recurse-submodules? Run
git submodule update --init --recursive first: the website's theme lives in
website/themes/hextra as a submodule, and with it missing Hugo aborts
pnpm -r build with unknown output format "llms" for kind "home".
Now point it at some Java. Any tree of .java files works; it does not have
to build, and no JDK is needed.
# 1. extract: sources in, one model file out (no compilation)
codegraph-java --src ~/src/gson/gson/src/main/java --out gson.jsonl
# 2. is the model sound?
codegraph validate gson.jsonl
# 3. look at it: one page, the navigator with the 3D city as a tab
codegraph serve gson.jsonl --host 127.0.0.1 # http://localhost:4177On google/gson (about 3,600 entities) extraction takes seconds and every report runs well under a second. On apache/fineract (a 127 MB model) each command completes in about twelve seconds.
--host 127.0.0.1 keeps the page on your machine. Without it the server
binds every interface, which is convenient on a LAN and wrong for a sensitive
codebase.
TypeScript: the extractor is the compiler used as a library, and it reads a
tree that neither builds nor has node_modules. Node 22 is its only
requirement, so npx is enough where Homebrew is not.
codegraph-typescript --src ~/src/some-app/src --out app.jsonl
npx codegraph-typescript --src packages --src extractors/typescript --out codegraph.jsonl # codegraph on itselfElixir: the extractor is the compiler's parser used as a library, and it
reads a tree that neither compiles nor has its deps/ fetched. The binary
carries Erlang and Elixir (Apple silicon and Linux x64 today).
codegraph-elixir --src ~/src/some-app --out app.jsonl
codegraph-elixir --src ~/src/some-app --deps ~/src/some-app/deps --out app.jsonl # dependency exports
codegraph-elixir --src ~/src/some-app --trace ~/src/some-app/codegraph-trace.jsonl --out app.jsonl
# the trace: what the compiler bound after macro expansion — `mix codegraph.trace` inside the projectC#: the extractor is a Roslyn program that reads *.cs directly: no
solution, no project file, no MSBuild, and a missing NuGet package is a stub
rather than a build failure. The binary carries the runtime, the compiler and
the base class library, so the machine needs no .NET at all:
codegraph-csharp --src ~/src/eShop/src --out eshop.jsonlRunning it asks less than building it, and the three forms produce the same bytes for the same corpus:
| The machine has | Run it as | Install |
|---|---|---|
| nothing | the self-contained binary from the tap or the release, about 64 MB | nothing |
| the .NET 10 runtime | dotnet codegraph-csharp.dll from a framework-dependent publish, about 15 MB, produced once by someone with the SDK |
.NET 10 runtime |
| the .NET 10 SDK | dotnet run from source, or ./build.sh --csharp in a clone |
.NET 10 SDK |
The binary is built with invariant globalization, so a minimal Linux
container needs no libicu.
docs/csharp-extractor.md has the rest: the five
platforms ./build.sh --csharp --publish-all cross-publishes from one host,
and the one-time quarantine note for a binary a browser downloaded.
Every extractor takes the same flags and exit codes (schemas/README.md §8),
so every command below works on any model.
codegraph analyze gson.jsonl --report coupling --top 20 # most depended-upon packages
codegraph analyze gson.jsonl --report cycles # tangles + the cheapest edges to cut; exit 3 if any
codegraph analyze gson.jsonl --report deps --level type # class-level dependency list
codegraph export gson.jsonl --format plantuml --level module > modules.puml
codegraph export gson.jsonl --format dot > graph.dotTwo switches change every answer, and every answer says which were on:
--internal-only drops everything outside the corpus, --declared-only
drops every inference and keeps only what the source literally says.
Height and footprint are metrics you choose. The defaults are lines of code and member count; the extractor also emits cyclomatic complexity:
codegraph serve gson.jsonl --height sum:cyclomatic --footprint loc
codegraph serve petclinic.jsonl --framework spring # color by Spring role
codegraph city gson.jsonl --layout --out city.json # the artifact aloneArcs appear when you select something: orange for fan-in, blue for fan-out, desaturated when the dependency is an inference rather than a declared fact, and red for the edges whose cut would break a cycle. Hover a building for its raw numbers. Unmeasured metrics are drawn at the minimum and labelled as unmeasured rather than faked.
codegraph scm ~/src/gson --out gson-history.jsonl
codegraph history gson-history.jsonl --report hotspots --top 20
codegraph history gson-history.jsonl --report hidden --model gson.jsonl
# ^ files that change together though no declared dependency links them
codegraph history gson-history.jsonl --report deadweight --model gson.jsonl
# ^ declared dependencies that history never exercised togetherTo watch the structure evolve, sample the repository at its tags into a temporal store and replay it:
codegraph snapshots ~/src/gson --extractor extractors/java/target/codegraph-java.jar --tags --src gson/src/main/java --store gson.db
codegraph timeline java:com.google.gson/Gson --store gson.db
codegraph replay --store gson.db --history gson-history.jsonl --servesnapshots is resumable; rerunning it skips the revisions already held.
codegraph explain gson.jsonl --src ~/src/gson/gson/src/main/java --dry-run # the plan, no call
codegraph explain gson.jsonl --src ... --estimate --price-in 0.10 --price-out 0.60 # tokens and cost, no call
OPENROUTER_API_KEY=… codegraph explain gson.jsonl --src ... --max-calls 50 # shows the estimate, asks [y/N]
codegraph explain gson.jsonl --src ... --model openai/gpt-5.6-luna --rollup-model anthropic/claude-sonnet-5 # pick the models
codegraph insights gson.jsonl # what was bought: records, concepts, what is owed, the spend
codegraph insights gson.jsonl --concept aggregate # the types the model read as aggregates
codegraph insights gson.jsonl --max-confidence 0.5 # what deserves a second look
codegraph insights gson.jsonl --id 'java:com.google.gson/Gson' # one explanation, wholeExplanations go to gson.insights.db beside the model — every finished unit
committed as it arrives, so a killed run loses nothing it paid for — and to
its export, gson.insights.jsonl; never into the model. codegraph serve
shows them on the selected type or module, marked as a model's inference.
Re-running redoes only the units whose inputs changed. On the reference
fixture a full run was 68 calls and about six cents. --model sets the model
for every unit (default openai/gpt-5.6-luna); --rollup-model overrides it
for types and modules only. Switching models re-explains the affected units. Cloudflare AI Gateway is
the other supported provider.
codegraph export --format json|csv gives you the folded graph;
codegraph import gson.jsonl gives you gson.db, a SQLite file you can query
directly (see the SQL cookbook);
codegraph domain-facts gives one pre-joined dossier per class for
domain-model extraction. Every artifact is deterministic, so it diffs cleanly
in a repository or a CI job.
The complete option list for every command is in
docs/cli.md and in codegraph <command> --help.
Plenty of tools draw dependency graphs. Codegraph exists because most of them demand a build, and because the ones that don't tend to guess quietly. Its design bets are:
- Facts and inferences never mix. Every edge carries a provenance:
declared,derived,dynamic-candidateorgenerated. A dependency the extractor resolved from the source is a different thing from one it inferred from a framework annotation, and every report, picture and export keeps the two visibly apart. You can always ask for facts only. - Every claim points at a line. Entities and edges carry a source anchor.
If the navigator says class A depends on class B through method
m, it shows you the file and span where that happens. - Legacy is the target, not the exception. The Java extractor runs Spoon
without a classpath, and the C# one parses every
*.csinto one compilation without ever opening a project file. What cannot be resolved becomes an explicit stub whose edges are kept, and stubs are decided by a whitelist of what the corpus declares, never by guessing from a package name. - One model, many languages. Extractors emit a versioned line-based JSON file against a published JSON Schema and know nothing about the metamodel. Everything clever, from validation to metrics to the city, lives once, in TypeScript, so adding a language is writing a producer for one contract. Entities are trait compositions rather than a class hierarchy, which is what lets a Clojure function var, a Go method with a receiver, and a Java class live in one graph.
- Pictures mean something. In the city every visual channel maps to a documented metric and colour is never decorative. The renderer draws the model and nothing the model does not contain.
- Time is part of the structure. History mining, temporal snapshots and replay are built in, because who changed what together is a dependency the source cannot show you.
- Boring outputs on purpose. stdout is the artifact and stderr is for humans; no colour codes; byte-identical output for identical input; exit codes that separate a bug in codegraph from a finding about your code.
Read these before you commit an afternoon.
- No build means imperfect resolution. Without a classpath Spoon cannot resolve every Java reference; on the reference corpus resolution is around 94%, and the rest are stubs. The C# extractor carries the base class library inside it, so the standard library always resolves and it is your NuGet dependencies that become stubs. A stub is honest, but it is still a gap.
- For Java, keep one package to one source root per run.
--srcis repeatable, but the same package declared under several roots at once (main and test sources, or one package split across modules) confuses noClasspath resolution; pass such roots in separate runs. explaincosts money and needs a network. It is the only command that does.--dry-runand--estimatetell you what it would do first, a real run asks for confirmation after showing the estimate (--yesin scripts), and--max-callscaps it.- Not a linter. Codegraph reports structure, coupling and cycles; it does not judge style or find bugs.
- Platform coverage is uneven. macOS gets the app and every extractor
from the tap. Linux gets the extractors from Homebrew and the
codegraphcommand as a release download. Windows gets release binaries for the command, Java and C#, andnpxfor TypeScript. The Elixir extractor is built for Apple silicon and Linux x64 only. - Big models want memory. A 100 MB model loads in the navigator in about a second, but the extractor and the analyzer are single-process Node and JVM tools; a monorepo of millions of lines is untested, we tested with Fineract that is close to 1M sloc (874 kSLOC precisely).
sources ──▶ extractor ──▶ model.jsonl ──▶ analyzer ──▶ reports, exports
(any state) (Java: Spoon) (the contract) (TypeScript) city.json ──▶ 3D city
(C#: Roslyn)
(TS: the compiler API)
(Elixir: the parser)
│ navigator.json ──▶ navigator
schemas/*.json model.db ──▶ SQL, time
(published JSON Schema) *.insights.jsonl ──▶ explanations
The model is a graph of entities and edges. An entity is {id, kind, traits}
plus the attributes its traits contribute; an edge is {from, to, kind, provenance, anchor}. Identity is a structured natural key
(lang, module, symbol, disambiguator), never a parsed string. Inverse
indexes (who calls me, who imports me) are derived in memory and never stored,
so a model file has one direction of truth. The full reference is
METAMODEL.md.
| Package | Role |
|---|---|
extractors/java |
Spoon-based extractor; emits model.jsonl |
extractors/csharp |
Roslyn-based extractor (no MSBuild, BCL embedded); one self-contained binary per OS |
extractors/typescript |
compiler-API extractor (no build, no node_modules needed); runs with npx |
extractors/elixir |
parser-as-library extractor (no compile, no deps/); an escript, plus mix codegraph.trace for the compiler-trace enrichment |
schemas/ |
the generated JSON Schema every extractor must satisfy |
packages/core |
traits, edges, language profiles, validation |
packages/analyzer |
graph, views, folding, cycles, coupling, exports, SQLite store |
packages/scm |
git history miner |
packages/city, packages/viz |
city model, Three.js renderer |
packages/navigator, packages/navigator-ui |
navigator model, React browser |
packages/insights, packages/llm |
the explanation walk, the provider clients |
packages/cli |
the codegraph command |
docs/cli.md— every command and optionMETAMODEL.md— every concept, attribute and relation in the modeldocs/analyzer.md— the analysis pipeline, its algorithms and costsdocs/model-encoding.md— the JSONL interchange and the SQLite storedocs/sql-cookbook.md— queryingmodel.dbyourselfdocs/city-model.md,docs/city-render.md— how the city is built and drawndocs/csharp-extractor.md— running the C# extractor: the self-contained binary, the .NET runtime alone, or the SDK from sourcedocs/typescript-extractor.md— running the TypeScript extractor, how it resolves without a build, reading its summarydocs/elixir-extractor.md— running the Elixir extractor, the OTP table and the stub discipline, the dropped-site reasons,--depsand the--traceenrichmentdocs/distribution.md— the Homebrew tap, what a tagged release produces, what is signed and howdocs/navigator.md— the navigator's designdocs/insights.md— the explanation walk's designPLAN.md— milestones, decisions and their rationaleCLAUDE.md— architecture boundaries and the metamodel invariants
Codegraph is pre-1.0 and is developed against real corpora (google/gson, apache/commons-lang, apache/fineract, spring-petclinic). The interchange format is versioned and the Java extractor's output over the reference corpus is committed as a fixture, so any change in what it claims about known code shows up as a diff.
| Milestone | State |
|---|---|
| Core metamodel, language profiles, JSON Schema | done |
| Java extractor (Spoon, no classpath) | done |
| Analyzer: dependencies, cycles, coupling, exports | done |
| CLI with conformance gate and property suite | done |
| JSONL interchange, SQLite analysis store | done |
| Git history mining, temporal store, city replay | done |
| 3D code city and the model navigator | done |
| Measures, literal values, Spring framework semantics | done |
LLM explanations (explain) |
done |
| Second language extractor (C#, Roslyn): one self-contained binary per OS, byte-identity smoke tests in CI on five platforms, audited on Humanizer, dotnet/eShop and OrchardCore | done |
Third language extractor (TypeScript, the compiler API): no build, no node_modules, self-hosting — codegraph's own package boundaries recovered as graph queries; byte-identity smoke tests on three OSes; audited on TypeScript 4.9's compiler, nestjs and excalidraw |
done |
Fourth language extractor (Elixir, the compiler's parser): no compile, no deps/, the OTP export table baked in, mix codegraph.trace for what the compiler bound after macro expansion |
done |
Desktop app (Tauri, macOS, signed and notarized) over the single-executable codegraph daemon |
done |
Releases: one binary per platform on a GitHub release, the Homebrew tap (app cask + one formula per extractor), codegraph-typescript on npm |
done (v0.1.0) |
| Clojure extractor (clj-kondo) | next |
| Project website and documentation site | planned, see WEBSITE.md |
Bug fixes start with a failing test. The property suite, which checks graph
closure, provenance, determinism and profile validity over every extractor
output, is the acceptance gate for any new extractor. pnpm run ci runs what
the pipeline runs. Commits follow Conventional Commits with a package scope
(feat(analyzer): …). The architecture boundaries in
CLAUDE.md are not negotiable; the open questions at the end of
each design doc are.
- Moose / FamixNG (Pharo) — the trait-based metamodel is FamixNG's idea, reimplemented as data in TypeScript.
- Spoon (INRIA) — the Java source model that makes classpath-free extraction possible.
- CodeCity (Wettel & Lanza) — the city metaphor; codegraph adds provenance-aware arcs and time.
- Structure101 — the tangle and feedback-set reports follow its "offending dependencies" idea.
- Gource and Adam Tornhill's Your Code as a Crime Scene — the history replay and the hotspot, ownership and co-change analyses.
- Specy — the domain vocabulary the explanations are written in.
MIT. Copyright (c) 2026 Defsquare.