Skip to main content

systemap: the map your coding agent draws of your system

the version on PyPI the Python versions it runs on the licence how many dependencies it has

If you have a coding agent working in your repository, you have probably noticed the thing nobody warns you about. It writes faster than you read. You review the diff in front of you, you merge it, and somewhere along the way the picture you had of how the system fits together stopped matching the system.

systemap keeps that picture. Your agent draws the map out of your code, a checker refuses to let it be incomplete or older than the tree, and every pull request says what it did to the shape of the system before you merge it. The map lives beside the code, so seeing what your agents have built is one page rather than an afternoon of reading. It is not an import graph with a language model on top: an import graph knows that one module imports another, and not that three of them together are the Router, that the Router owns one job, or that a journey is meant to cross it in a particular order. It reads Python and only Python.

the map: switching readings, clicking a component to light what it reaches, stepping a journey

Open the live map, which is systemap's map of itself, drawn by the command you are about to run.

Why the agent draws it

You could draw this yourself. The reason not to is the same reason the picture went stale: a map takes two kinds of knowledge, and they do not come from the same place.

The first is mechanical: which modules exist, what each one exports, which tests import it, where a run starts. A script reads that out of the syntax tree in a second and never gets it wrong.

The second is judgement. Which modules together make one thing a reader would point at and name? What does the line between two parts actually mean? Which question does a reading answer? No script has that, and the person who does rarely has the patience to keep it true through every refactor. So you end up with one of two maps: a script's, which is complete and meaningless because a module is not a part, or a person's, which is meaningful and drifting, because nothing announces that a part moved.

An agent has both kinds, once two things are true. It follows a written procedure, so the same judgement is applied the same way every time. And something refuses its output when a module is mapped by nobody, when a route crosses a card it does not connect, or when the page is older than the model. systemap is that procedure and that checker. The agent does the work, and hands you back a short list of the calls it had to make, each with its answer.

Start

uv tool install systemap        # or: uv add --dev systemap
systemap init                   # --no-ci to skip the workflow

init writes the configuration, an empty starter model, the agent's skill under .claude/skills/systemap/, and a CI workflow. Then it prints the one sentence you give your agent:

Map this repository with systemap. Follow the systemap skill.

That is the whole of your side. The agent reads the facts, drafts the model, lays it out, runs the check until every module is mapped and the layout passes, renders, and then goes round again looking for what it missed. When it stops, you read its answers, correct what you disagree with, commit docs/map/, and turn on GitHub Pages from the docs/ directory.

Using Claude Code? The repository is also a plugin and its own marketplace, so you can skip init for the skill:

/plugin marketplace add 0xfauzi/systemap
/plugin install systemap@systemap

Any agent that reads a skill directory and runs a command works the same way. The skill is plain text in the Agent Skills format, with nothing vendor-specific in it.

What this pull request did to the system

Git tells you what changed in the code. systemap tells you what changed in the system.

Reading a diff tells you which lines changed. It does not tell you that a module moved out of the part that owned it, that a new import now crosses two components with nothing between them on the map, or that the entry point a card names has quietly gone.

systemap delta --base main reads the facts at both commits out of git and says exactly that, in the map's own terms, one line per thing, each with its fix:

moved: pkg.old -> pkg.new (same content); Gateway names pkg.old in
  implemented_by: rename it in map/model.py
added: pkg.thing, claimed by no card
entry vanished: Gateway names entry serve, which its modules no longer define
evidence lost: Emitter -> Contracts (slide tree) was observed at the base
  and nothing backs it now

It ends with the cards next to the change: the ones a flow joins to the card holding most of what changed. That is context, not a warning. Measured over 359 merged pull requests, it names 6 cards at the median and one the change really touched about seven times in ten, where following the imports instead names 20. Neither is good enough to say what else broke, so it does not pretend to.

The workflow init writes posts that report as one comment on every pull request and keeps it updated as you push, so the review you do on your agent's work starts with what the change did to the system rather than with 400 lines of diff. It exits 0 when nothing needs a decision and 1 when something does, so CI can hold the line while you are not looking.

Then your agent acts on those lines alone rather than redrawing the map. Measured on three real merged pull requests, that path cost 2.31, 4.39 and 2.50 dollars, against between 3 and 26 dollars for a first map of a whole repository (78 to 460 modules). Every run, with the model that produced it, is in docs/benchmarks.md.

One model, several ways to read it

systemap's map of itself: the Structure reading, every part in its region, no edges

One committed model, drawn several ways, which is how you look at a system rather than at a file. The one above is Structure, every part in its place and not one arrow. Switch, and the edges arrive: System context draws what crosses the boundary, Data flow what moves, Control flow who drives whom. A repository with agents in it gets three more, for the agents, what enters their context, and what they can call.

Click a component and its neighbours light up on the reading you are in, each spoke carrying the verb for that direction, with one sentence saying what the part you clicked is to it. Step a journey and the map walks you along it one edge at a time. Past forty cards, a card can hold a map of its own, which opens in place with a way back.

Every edge also says whether the code backs it. Where an import joins the two ends the line is solid, and where nothing in the facts does the line is dashed and the panel says so, so a picture somebody wished were true looks different from one the code agrees with.

Three colour schemes ship, and the picker in the header remembers the one you chose.

the warm scheme the graphite scheme the paper scheme

What stops it lying

systemap check runs eleven rules and exits 1 on the first failure, naming the fix. It refuses a module no component claims, an entry or an interface line naming something the code does not define, a route through a card it does not connect, a label that touches another, text under 11 px, a map inside a card that claims the wrong modules, and a page older than the model. The full table is in docs/reference.md.

The check catches contradictions. It cannot catch omissions, so systemap judgement goes looking for those: a component claiming one module, a module folded into a part it shares no name with, an entry point no journey covers, an import crossing two cards with no edge between them. Each line is either acted on or answered with a reason in systemap.toml, so the answers live in the repository and nobody is asked the same question twice.

An answer can cover a family rather than a line, which is what keeps the list short on a large map: crossing_into = "Ledger" answers every import into that card at once. It also means the next import into that card arrives without a question, here and in a delta. So answer a family when the reason is about the card, and a line when it is about the pair.

systemap delta asks these same questions of one change rather than of the whole map, which is why a pull request's comment carries what that change introduced instead of everything still open.

systemap check && systemap judgement --strict

Six repositories have been mapped this way end to end, four of them written by somebody else, each finishing unattended with both commands clean. The rows are in docs/benchmarks.md.

A second opinion, if you want one

The checker reads names and imports, so it can say that a module shares no word with its card and cannot say that it does a different job. systemap audit asks TypeSafe's Jev model that kind of question, one narrow judgement at a time, and prints a line where the answer disagrees with the map: a module that reads like another card, a card sentence that does not describe its code, an invariant that may govern a card it does not name, and, when asked, a flow the code where two cards meet may not carry. systemap triage "<issue>" names the three cards a bug report's fix will most likely change.

Measured on five mapped repositories before it was built: modules moved into a neighbouring card were caught 95% of the time, against 32% for the word rule, with 4% of correctly placed modules flagged; for 80 closed issues, the card the fixing pull request touched was the first pick 80% of the time. On three maps no threshold was chosen on, the figures held within 10 points (93% caught, 1% flagged; 79% first pick for 39 httpie issues), except the flow check, which is asked only on request. Every run and threshold is in bench/jev.

It needs TYPESAFE_API_KEY. With the key set, delta asks Jev on its own (--no-jev sends nothing); without it, judgement and delta say on stderr what Jev would add, with the measured figure. check and judgement never ask, so CI stays offline unless you give it the key. It adds no dependency, sends module names, docstrings, card sentences and the source lines where two cards meet (audit --dry-run says exactly what), and caches every answer, so an unchanged map costs nothing the second time. [jev] enabled = false in systemap.toml turns off both the asking and the hints.

A walk for every way in

A journey is the walk a reader takes through the system when a run starts at one way in: which parts it passes through, in order, and what happens at each step. systemap extract finds the ways in a framework registers, routes and commands and queue tasks alike, and systemap judgement asks for a journey from each one that has none.

systemap journeys writes them. It hands the agent named under [agent] command in systemap.toml one way in, the cards with their sentences and the flows already drawn, and asks it to read the code and answer with the walk. Every step is checked against the map first: a step tracing a flow the map does not draw, or naming a card that is not there, comes back as a line to fix rather than as a journey. What holds is written into the model marked drafted=True, and judgement keeps saying so until you have read it against the code and removed the mark.

One card often takes a crowd of ways in: 190 routes into mealie's API, a dozen subcommands into one card of Poetry. Those get one walk between them, not one each, and that walk names the card rather than any single route. Covering mealie is two runs rather than sixty-five. Of nine crowds measured across three repositories, eight came back as a walk the map could hold.

The walk was proposed from the imports first, and measured: the cards it named overlapped the ones people wrote by 0.28 where the bar was 0.60, because the module behind a command line imports most of the system. So the agent reads the code instead. Without an agent command, systemap journeys lists the ways in with no walk and writes nothing.

A plan, and what the work actually did

Before you start, say what you are about to do:

systemap plan "make the reader stream its input instead of buffering it"

Jev reads that against every card's purpose, and the cards it gives real weight to are the projection. Around each one the map prints what it sits in: the flows that leave and reach it, the walks that pass through it, and the rules that govern it. That last part is what a plan usually leaves out.

Afterwards, systemap plan --check <id> --base <ref> compares the projection with the cards the code actually changed. A card that changed and was not projected is the finding: the work reached a part of the system the plan did not see. That is worth reading before the pull request is opened.

The cut is measured, not chosen by taste. Over 80 real bug reports with the cards their fixing pull request touched, it covered 86% of those cards while naming 2.1 cards per report; on 39 issues of a repository no threshold was chosen on, 71% while naming 2.1. The bar, set before the run, was 70% covered with at most 2 extra cards. Every run is in bench/jev.

How the system got here

A map is the system today. systemap history is the year behind it:

systemap history --since "1 year ago" --every 14

One commit is sampled per fortnight, the facts at each are read out of git, and every sample is read in today's cards, so a module that moved still counts as the card whose job it does. Each window says what moved: the cards that grew, the imports that began crossing a boundary, the ways in added or removed, and the commits that wrote the modules which appeared. A number always leads back to the work behind it.

Measured on a year of mealie before it was built: 25 samples took 29 seconds the first time and under a second once the facts were cached, against a bar of five minutes; and of the five largest windows, all five named a change a person can find in that window's commits, against a bar of three.

It teaches while it refuses

A line systemap prints is short, because it has to be quoted word for word when you answer it. Short is not the same as clear, so under the first line of each kind, check, judgement, delta and audit print two more rows: why this matters to your view of the system, and what to do about it.

judgement: 3 items for the maintainer to confirm
  single module: Reader is only pkg.reader
      why: A map whose cards are files is a directory listing with corners.
           It teaches a reader nothing they could not get from the tree.
      do:  Keep the card if a reader would point at it and name it on its
           own. Otherwise fold the module into the card whose job it serves.

The line itself never changes, so answers written against it keep working. --brief leaves the rows out once you know them, and systemap explain "<kind>" prints one in full whenever you want it back.

Commands

command what it does
systemap init the configuration, a starter model, the skill, a workflow
systemap extract read the facts out of the tree
systemap facts read those facts back, one view at a time
systemap place a position for every card, and the region order that draws best
systemap render build the page from the facts and the model
systemap check does the map still match the code? exit 1, with each fix named
systemap judgement what the check cannot catch: the second-pass list
systemap suggest a first grouping to argue with, from the facts alone
systemap refresh extract, check, render, and every configured figure
systemap describe what a look at the picture would tell you, in numbers
systemap delta what a change did to the map, from the facts at two commits
systemap audit a second opinion from Jev on the map's judgement calls (needs TYPESAFE_API_KEY)
systemap triage the cards an issue's fix will most likely change (needs TYPESAFE_API_KEY)
systemap plan the cards a piece of work will touch, and afterwards what it actually touched (needs TYPESAFE_API_KEY)
systemap journeys a walk written for a way into the system that no journey starts from (needs [agent] command)
systemap figure one figure: a reading, a map inside a card, a plan's reach, a change
systemap serve serve the output directory over HTTP, so the page can run
systemap history how the system got here: what moved over a year, and the work that moved it
systemap explain one kind of line in full: what it means, why it matters, what to do
systemap skill reinstall the skill directory

Every option and every configuration key is in docs/reference.md.

What it is not

It reads Python and only Python. No other language is planned, so if your system is mostly TypeScript, this is not your tool.

It is not a call graph, because the facts hold imports and public surfaces while the map draws the flows the agent declared rather than every call. It is not a dependency visualiser, because modules are not cards and components are. It is not a UML tool, since there is one diagram, one fixed layout, and no notation beyond card, line, label and a mark per kind.

Development

uv sync
uv run pytest -q
uv run mypy src --strict
uv run ruff check .
uv run systemap check              # this repository's own map must stay current
uv run systemap judgement --strict

The workflow runs the suite, the types and the linter on Linux, macOS and Windows with Python 3.11 and 3.13, installs the built wheel into an empty virtual environment on each of those six and runs init, extract, refresh, check and judgement against a copy of this repository's map, and installs the plugin from the checkout with the Claude Code CLI. docs/reference.md has the rest, including how the skill's two copies are kept identical.

Releases: push a tag that names the version. .github/workflows/workflow.yml builds the package and uploads it through PyPI's trusted publishing, where GitHub proves the build came from this repository and no long-lived token exists to be stolen. scripts/publish.sh is the manual path for a release made from a laptop, taking the token from UV_PUBLISH_TOKEN or, on macOS, from the login keychain.

MIT licensed.

Map something with it, and whatever gets in your way is worth an issue. Every version so far was written from somebody's log of where they got stuck, most of them an agent's.

Metadata

Release files for systemap 1.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for systemap 1.2.0
File Size Uploaded
systemap-1.2.0.tar.gz 3.2 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for systemap 1.2.0
File Interpreter ABI Platform
systemap-1.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 3.4 MB

Release files / systemap-1.2.0.tar.gz

Download URL systemap-1.2.0.tar.gz
Size 3.2 MB
Tags Source
SHA-256 checksum
How to use checksums
30b6911f9ec78f14a5f0179d17f46e0e0167250e539c601002366972c7a023d2
BLAKE2b-256 checksum
How to use checksums
04df42df9e835317893d77dd11fab8b20096d89f9a7f219f837fc070418c95ba
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 20, 2026.

Transparency log

Release files / systemap-1.2.0-py3-none-any.whl

Download URL systemap-1.2.0-py3-none-any.whl
Size 272.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f6c9f4736f9a9466e2d8ea15695967ecd1a34cc40b76ae16c31204a2b4e448e2
BLAKE2b-256 checksum
How to use checksums
913c61b8d1ac68c9c8a5162e528dc3113a83030063b74717debdb43d64450f32
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 20, 2026.

Transparency log

Release history Release notifications | RSS feed

2.0.0

2 release files

This release

1.2.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page