Cartograph CLI v0.1.0
Milestone M09 · the first public developer-facing release.
Cartograph derives a symbol-level, cross-language graph from source code and shows the evidence behind every edge. v0.1.0 turns the validated engine into a CLI an external developer can install and use.
This is 0.1.0, not 1.0. The scope is one supported subset of one problem, measured and documented rather than claimed.
What it does
$ cartograph trace enqueueConnectionTest --path ~/airflow
enqueueConnectionTest (function) airflow-core/src/airflow/ui/openapi-gen/requests/services.gen.ts:834
│
↓ HTTP-CALL confidence 0.98 provenance route-matcher
│ evidence POST /api/v2/connections/enqueue-test matched POST /api/v2/connections/enqueue-test
│ at .../routes/public/connections.py:383 (enqueue_connection_test); exact HTTP method;
│ every path segment static and equal
│ observed airflow-core/src/airflow/ui/openapi-gen/requests/services.gen.ts:834
│
enqueue_connection_test (function) airflow-core/src/airflow/api_fastapi/core_api/routes/public/connections.py:383
│
↓ ORM-ACCESS confidence 0.80 provenance orm-resolution
│ evidence ConnectionTestRequest(...) in enqueue_connection_test at .../connections.py:427
│
ConnectionTestRequest (class) airflow-core/src/airflow/models/connection_test.py:82
│
↓ QUERIES confidence 0.80 provenance orm-resolution
│ evidence ConnectionTestRequest maps to table "connection_test_request"; __tablename__ = "connection_test_request"
│
connection_test_request (table)
This path crosses the HTTP boundary and reaches a database table.Four artefacts, three languages, no shared symbol table. Every hop is a computed relationship carrying its own evidence and source locations, and each one in the example above was verified by hand against Apache Airflow at commit 9b43d6a.
(Directory paths are shortened and the per-hop observed line is omitted so the block fits the page; everything else is verbatim.)
Commands
| Command | What it answers |
|---|---|
cartograph . | Can I analyse this repository, and what did it find? |
cartograph trace <symbol> | Why does Cartograph believe this relationship exists? |
cartograph parse | What do the files say? (syntactic facts) |
cartograph normalize | What is each observation's canonical form? |
cartograph match | What was decided, and what was refused? |
cartograph version | Which build is this? |
--json on any of them emits a versioned document. Full reference: docs/development/cli.md.
Installation
From source — works everywhere the toolchain does
cargo install --git https://github.com/Rexy-5097/cartograph cartograph-clior, in a clone:
cargo build --release -p cartograph-cli
./target/release/cartograph .Rust 1.97.1, pinned in rust-toolchain.toml; rustup selects it automatically.
npm — a launcher, not a reimplementation
npx cartograph-cli .The npm/ package finds the native binary and forwards argv, stdio and the exit code. No analysis happens in JavaScript. It never touches the network at run time. See npm/README.md.
Prebuilt binaries
.github/workflows/release.yml builds a native binary for each target below on that target's own operating system, smoke-tests it there — version, a real analysis, JSON conformance, trace, and exit codes 2/3/5 — and attaches it to the GitHub release.
| Platform | Target triple |
|---|---|
| macOS Apple Silicon | aarch64-apple-darwin |
| macOS Intel | x86_64-apple-darwin |
| Linux x64 | x86_64-unknown-linux-gnu |
| Windows x64 | x86_64-pc-windows-msvc |
Verification status at the time of writing. aarch64-apple-darwin is built and fully exercised locally, and the release binary is built and smoke-tested on ubuntu-latest and macos-14 by CI on every pull request. The Intel-macOS and Windows targets are configured but not yet produced — the release workflow runs on tag push, and no v* tag exists. Until it does, this project does not claim those binaries exist. Build from source there.
Exit codes
| Code | Meaning |
|---|---|
0 | Success. Diagnostics may still have been reported. |
2 | Usage error. |
3 | Path missing, unreadable, or holding no supported source. |
4 | Analysis failed. |
5 | trace: symbol not found. |
6 | trace: symbol ambiguous; nothing was chosen. |
7 | Partial analysis, with --strict. |
1 is unused, so an unexpected 1 is always a defect.
Performance
Release build, Apple M-series, cold page cache, single run.
| Repository | Files | Wall | Peak RSS |
|---|---|---|---|
startup (version) | — | 1.9 ms | — |
| fixture corpus | 19 | 4.2 ms | — |
| full-stack-fastapi | 156 | 0.12 s | 9 MB |
| zulip | 1,539 | 3.20 s | 79 MB |
| autogpt | 3,731 | 4.96 s | 106 MB |
| onyx | 5,747 | 6.70 s | 121 MB |
| superset | 6,063 | 7.63 s | 147 MB |
| airflow | 8,654 | 14.37 s | 209 MB |
| posthog | 32,335 | 64.42 s | 920 MB |
Memory grows with repository size because the whole analysis is held in memory at once — dynamic URL resolution needs every module's exported constants before any URL can be resolved. This is the known scaling limit, and M09 did not address it. Incremental analysis is M10's goal; recording the baseline here is what makes that milestone measurable.
Correction (M17 traceability review). The file counts above match every committed M07 run of the same pinned corpus. The startup, wall-time and peak-memory figures come from the single run described above, and no record of that run was committed, so they cannot be traced to a benchmark run. The committed M07 runs in
benchmarks/results/,benchmarks/remediation/andbenchmarks/acceptance/give PostHog between 30.0 s and 49.9 s and between 825 MB and 965 MB, depending on the pass. The table is kept as it was published.
Supported subset
Cartograph recovers relationships it can compute. Outside this subset it refuses rather than guesses, and a refusal is reported, not hidden.
Frontend — fetch, axios and generated OpenAPI clients with a literal or statically resolvable URL; template literals partially evaluated with unknown segments preserved as unknown. Backend — FastAPI, Flask and Django route declarations, including router prefixes composed through include_router chains. ORM — SQLAlchemy and Django models resolving to a table through __tablename__, db_table, or a shared declarative base.
benchmarks/supported-subset.json is the authoritative list, and every exclusion in it has an identifier that appears in the refusal reasons.
Accuracy, stated honestly
From the accepted M08 measurement over seven pinned production repositories (14,932 edges):
- 1 false positive among 11,221 independently verified edges.
HttpCallrecall 0.907 (117/129),OrmAccess0.744 (1401/1883), end-to-end chain 3 of 5 — each within a scope the result names.
Correction (M17 traceability review). All three recall figures were measured in Airflow only — its generated client, its import-bound model uses, and its independently readable chains — not across the seven repositories the sentence above introduces. See the Recall section of the M08 report.
- Confidence is an uncalibrated prior, ECE 0.18 in the direction of under-confidence.
- 24.9% of edges could not be verified from source, concentrated where the evidence is weakest, so every accuracy figure above is an upper bound.
Full report: docs/benchmarks/m08-report.md.
Known limitations
- Memory scales with repository size. 920 MB on a 32,000-file repository (an untraced figure — see the correction under Performance). No incremental mode, no file watching, no caching between runs. (M10.)
- Only TypeScript, TSX and Python. No Go, Java, Ruby, C#, Rust or PHP.
- Only the subset above. Class-based route registration (Flask-AppBuilder
@expose), SQLModel, and clients that pass an options object rather than a URL are refused by design. tracewalks outward only. There is no reverse traversal ("what reaches this table?") and no blast-radius query. (M12+.)- Node ids are stable within a run, not across runs. Identity is
(kind, name, file). - Confidence must not be thresholded as a probability. It is not calibrated; see the policy document.
--strictfires only on files that could not be parsed at all, not on syntax errors, which tree-sitter recovers from.- No desktop UI, no MCP server, no GitHub app, no
ask, nodiff. Those are later milestones and are deliberately absent rather than stubbed.
Security
The analyser reads source files and nothing else. It does not upload source, execute repository code, connect to a database, read environment values as part of analysis, call a language model, or make any network request. Evidence is structural — file, line, canonical route, grammar fact — never source text, and no absolute filesystem path appears in any serialised output.
cargo build runs git rev-parse once to record the commit in version; that is the only subprocess this project spawns, it is build-time rather than analysis-time, and it degrades to unknown when git is absent.
Verified by 26 adversarial cases including source containing credential-shaped text, 400 unparseable files, Unicode paths, closed pipes and maximum logging verbosity.