Skip to main content

Interactive code+docs dependency graph with doc-sync tooling and an AI-agent-friendly JSON export

Project description

Architectural memory for your refactors. See the blast radius of your changes across code, docs, and git — on one interactive map that both you and your AI agent can read. A set of lightweight utilities and a simple but highly functional UI, delivered as a self-contained HTML file you can share as-is. Lightweight and private.

build-graph renders a single-file interactive HTML graph connecting five layers no other tool combines:

  • code → code — Python imports (AST-based, TYPE_CHECKING-aware)
  • code ↔ docs — which markdown files mention which source files
  • git drift — added / modified / renamed / deleted overlay with ghost nodes for files that no longer exist
  • file-change heat map — surface and tame the hottest spots of churn, and the likely sources of bugs
  • test-coverage map — reads the project's coverage.xml and shows test coverage at a glance, highlighting the least-covered files

…and exports the same map as a compact, token-efficient JSON designed to drop into an LLM agent's context.

All of that with zero dependencies — pure Python stdlib, pip install brings in nothing else. The only third-party code is D3.js in the browser, SRI-pinned from CDN or fully embedded with --no-cdn.

Force layout settling on a real project — 1070 nodes / 6279 edges, dark theme

▶ Live demo — the graph of this very repository (dogfood), with a synthetic --mock-git overlay so the Git and coverage modes are clickable too. 📖 UI guide — a concise step-by-step walkthrough of the core features.

Install

pip install graph-build        # or: uv tool install graph-build

Install straight from GitHub:

pip install git+https://github.com/Mr-Freewan/build-graph.git

# or from a clone:
git clone https://github.com/Mr-Freewan/build-graph.git
pip install ./build-graph

The PyPI distribution is named graph-build (the straight name is taken); the installed commands keep their names: build-graph, find-related-docs, verify-doc-links.

Quick start

cd your-project
build-graph                    # autodiscovery, no config needed → docs/graph.html
build-graph --ultra-compact    # + docs/graph-ultra.json for AI agents
build-graph --init             # optional: pin discovered structure to graph.toml

Two companion CLIs — find-related-docs (reverse lookup: code → docs) and verify-doc-links (broken-reference gate for CI) — ship in the same package; see Companion tools.

Why not X?

  • pydeps / Import Linter — imports only; no docs layer, no git drift.
  • lychee & co. — dead-URL checkers; no map, no code layer.
  • Obsidian graph view — notes only; doesn't see your code.
  • Repomix / Gitingest — pack the repo text for LLMs; build-graph gives the structure: ~2 % of the tokens the raw text would cost (see the numbers).
  • Graphify / Understand-Anything — knowledge-graph tools that drag in heavier dependency stacks and lean on a non-deterministic LLM for the analysis; build-graph is deterministic and dependency-free, and adds the git and doc-sync layers neither of them has.

Designed for AI agents

--ultra-compact (or the older --compact) writes a self-documenting JSON snapshot — embedded legend, no external schema needed — that agents use for:

  1. Blast radius — incoming imports of the file you're about to change, without grep.
  2. Docs routing — which ADR / reference doc to read before editing a file.
  3. Three-way doc-sync — the graph reveals (1) what's documented, (2) what should be documented but isn't, and (3) what's documented but no longer exists (ghost nodes = staleness detector).

Add build-graph --ultra-compact to a pre-commit hook or CI step to keep the map fresh for every agent session.

The ultra-compact format

--ultra-compact writes graph-ultra.json (schema v3) — the format to reach for when the snapshot is going into a context window. It carries exactly what --compact carries, in roughly 40% of the bytes, by writing each fact once:

{
  "v": "3.0",
  "legend": { "...": "what every section and code below means" },
  "stats": { "nodes": 1070, "ghosts": 0, "edges": 6279 },
  "cols": ["id", "file", "cat", "heat", "cov"],
  "n": {
    "app/core/security": [[412, "access.py", "cor", 23, 87]],
    "docs/adr":          [[7, "0009-parser-framework.md", "adr", 4, -1]]
  },
  "git": [[412, "mod"]],
  "e": {
    "imported_by":  [[412, [[518, 31], [604, [12, 88]]]]],
    "doc_mentions": [[7, [[412, 142]]]]
  }
}

Files are grouped under their directory, so a path is written once and a node row is just id, filename, category — plus a heat column (commits touching the file) and a cov column (line coverage %, -1 when unmeasured) whenever those layers were collected. cols declares that layout, so a row is never ambiguous.

Edges are grouped into sections named after the group key, so a row reads key-first and direction never has to be inferred from argument order: imported_by is keyed by the module being imported, doc_mentions by the doc doing the mentioning. An item is a bare id, [id, line], or [id, [lines]]. Optional layers — git statuses, ghosts (deleted files still referenced by docs), ge (edges touching them) — appear only when there is something to report. degree isn't stored: it is the number of incident edges.

The compact format

--compact writes graph-compact.json (schema v2), the flatter predecessor of the format above — unchanged, and still the right pick if you already parse it: nodes as an indexed array, edges as [source_idx, target_idx, type, [line_numbers]] rows, 3-letter codes for every category and edge type. The legend key embeds the full decoding table, so this file explains itself too:

{
  "v": "2.0",
  "legend": { "...": "what every field and code below means" },
  "stats": { "nodes": 1070, "ghosts": 0, "edges": 6279 },
  "n": [
    { "p": "smm_bot_async/core/security/access.py", "t": "cor", "d": 56 },
    { "p": "docs/explanation/adr/0009-parser-framework.md", "t": "adr",
      "d": 11, "s": "mod" }
  ],
  "e": [
    [ 1, 75, "d2d", [186] ]
  ]
}

p — path, t — category, d — degree, s — git status (omitted when clean). Edge types: c2c imports, c2d doc mentions, d2d doc links, dcs docstring refs, typ TYPE_CHECKING-only, ren git renames. Deleted-but-still-referenced files ride along as ghost nodes ("G": 1).

What it costs in context

Real numbers from a production repo — a thousand-odd mapped files and several thousand edges (tokens ≈ bytes / 4, the usual rough estimate):

What you put in context Size ≈ Tokens
The mapped files themselves 14 MB ~3,650,000
--json (verbose snapshot) 1.2 MB ~320,000
--compact 0.27 MB ~69,000
--ultra-compact 0.10 MB ~27,000

The whole architecture — every import, every doc mention, every stale reference — lands in well under 1 % of what the raw text would cost, and leaves a 200 k-context session almost entirely free to work in. Without the map an agent rediscovers this structure every session: dozens of speculative greps and file reads that burn comparable tokens per question, not once. On small projects the map is almost free — the ultra-compact snapshot of this very repo is under 8 KB ≈ ~2,000 tokens.

Don't take these numbers on faith — measure your own repo
$ build-graph --root . --bench

Context cost on this repo (tokens ~= bytes / 4):

  What you put in context            Size      ~Tokens  vs corpus
  raw corpus (1060 files)         13.9 MB    3,651,892     100.0%
  --json export (schema v1)        1.2 MB      320,439       8.8%
  --compact export (schema v2)   270.9 KB       69,339       1.9%
  --ultra-compact export (v3)    103.6 KB       26,517       0.7%

--bench only measures — it writes no files.

A prompt to start from

graph-ultra.json is a dependency map of this repository: nodes are
files, edges are imports and documentation mentions. Read the embedded
"legend" key first — it explains every section and code.

Using the map (before any grep):
1. Lay of the land: the 10 highest-degree hubs, grouped by category,
   with one line each on why they're central.
2. I'm about to modify <path/to/file.py>. List the blast radius:
   direct and 2-hop incoming importers, plus every doc that mentions
   the file — and tell me which of those docs to read first.
3. Anything suspicious: ghost nodes (docs pointing at deleted files),
   zero-degree modules, docs nothing links to.

Verify any surprising claim against the actual source before acting.

More recipes: Prompts for AI agents — ready-made prompts for blast radius, three-way doc-sync, ghost detection and dead-code hunts.

The interactive graph

  • Canvas renderer — smooth at 1000+ nodes / 6000+ edges (pre-warmed layout, viewport culling, label LOD).
  • 6 edge types — doc→doc, code→doc, code→code, type-only (TYPE_CHECKING), docstring mentions, git renames.
  • Git overlay — status colours + ghost nodes + rename edges; --mock-git for a synthetic demo.
  • Graph diff--diff-base REF compares the working tree against a git ref: file statuses feed the Git overlay, new dependency edges show green and removed ones red (dashed), with counters in the legend. Add --diff-head REF to compare two specific refs instead.
  • Heat overlay — node colour by git-commit frequency (blue→red gradient), whole history by default or the last N days with --heat-days N. A min-edits slider in the legend hides anything colder than the chosen threshold — edges follow. Mutually exclusive with the Git overlay — both recolour nodes, so only one is on at a time; unlike Git mode, it's additive: Node types stays visible and usable underneath.
  • Coverage overlay — node colour by test-line coverage (green→red gradient — this one's about finding badly-covered files, so it reads the opposite way from Heat) from a Cobertura coverage.xml (--coverage PATH, e.g. pytest --cov=your_pkg --cov-report=xml). A max-coverage slider hides anything covered more than the chosen ceiling, isolating the worst-covered files as you lower it; turning it on also auto-hides every Node type but code in the legend (shown again with one click). Mutually exclusive with Git and Heat too. Off — and its button hidden — when no report is given.
  • Node tooltip — hover any node to see its name and path; in Heat or Coverage mode, also the edit count or coverage percentage behind the colour. Edge tooltips turn off while either of those modes is active.
  • Analysis aids — dead-code candidates, import-cycle detector (Tarjan SCC over runtime imports; TYPE_CHECKING edges don't count), orphan ring, shortest path between two nodes (Shift+click), isolate-a-type, exclude-by-name. A doc's bare mention of a same-named file (config.py with dozens of matches, no path) is credited to one ambiguous-category node instead of fanning out to every candidate.
  • Sharing — URL-encoded views (Copy link), Mermaid export of the focused subgraph, full/compact JSON export.
  • Comfort — 10 UI languages, dark/light themes, hue-aligned pastel/saturated palettes, draggable glass panels, IDE deep links (VS Code / Cursor / PyCharm), FAQ built in (?).

Everything lands in one self-contained HTML file — attach it to a PR, send it in chat, open it offline.

Configuration (optional)

Autodiscovery classifies every tracked file by kind (code / doc / config / locale) × location, detects your package and docs layout, and generates deterministic colours. A graph.toml is only an override:

build-graph --init           # generate graph.toml pinning current structure
build-graph --init --diff    # report drift (new folders, stale pins), change nothing
build-graph --init --merge   # append coverage for new folders, keep your edits

See graph.example.toml for the annotated format ([docs] categories, [[code]] dirs, [[rules]], [scan] excludes, [dead_code] exemptions, colour pins).

Two optional plain-text companions, both looked up in the project root:

  • known-brokens.txt — whitelist for verify-doc-links false positives (one exact path per line).
  • exclude-dirs.txt — directory-name skip list used only when git is unavailable (with git, .gitignore is the source of truth).

CLI reference (build-graph)

Flag Effect
--root PATH project root to scan (default: cwd)
--config PATH graph.toml location (default: <root>/graph.toml)
--output PATH HTML output (default: docs/graph.html or [output].path)
--scope full|package whole repo (default) or package+tests+docs only
--json / --compact / --ultra-compact JSON snapshots next to the HTML: verbose (v1), compact (v2), ultra-compact (v3)
--docs-only / --no-tests trim the node set
--no-cdn fully offline output: embed D3.js inline (SHA-256 verified) and drop the external font link
--mock-git synthetic git overlay for demos/testing
--diff-base REF ref-diff build: statuses + edge changes vs a git ref (head = working tree unless --diff-head is set)
--diff-head REF with --diff-base: diff against this ref instead of the working tree
--heat-days N restrict the Heat overlay to the last N days (default: whole history)
--coverage PATH enable the Coverage overlay from a Cobertura coverage.xml
--init [--diff|--merge|--force] config lifecycle (see above)

Companion tools

find-related-docs and verify-doc-links run the same reference scanner the graph is built from — what the map draws as a code↔docs edge is exactly what they look up and verify. graph-query answers questions over an already-built snapshot.

find-related-docs

Reverse lookup: which docs mention a given code file. Run it before editing a file to know which pages need updating afterwards, or wire --git-added into a pre-commit hook so undocumented changes get flagged before they land.

Flags & examples
find-related-docs src/mypkg/core/access.py   # single file (bare filename works too)
find-related-docs --git-added -v             # pre-commit: staged files, with doc line numbers
find-related-docs --git-modified             # working tree: staged + unstaged modifications
Flag Effect
path file or directory to look up (a bare filename is searched project-wide)
--docs-dir PATH documentation directory (default: docs)
--exclude DIRNAME skip a folder name anywhere under the docs dir (repeatable)
--git-added check all staged files; also warns about deleted files still mentioned in docs
--git-modified check all modified files (staged + unstaged)
-v / --verbose print docs/<file>.md:<line> for every mention

verify-doc-links

Check that every file reference in your .md files points to a real file. Exit codes make it a drop-in CI gate:

Flags & examples
Exit Meaning
0 all references valid
1 broken references found
2 target path invalid (not found, or not a .md file)
verify-doc-links                     # whole docs/ against the project root
verify-doc-links docs/reference -v   # one subtree, with the offending lines
# CI step (GitHub Actions)
- run: pip install graph-build
- run: verify-doc-links --root .
Flag Effect
path .md file or directory to check (default: docs)
--root PATH project root the references resolve against (default: cwd)
--known-brokens PATH whitelist file (default: <root>/known-brokens.txt)
-v / --verbose show the offending lines

Besides known-brokens.txt, false positives can be silenced inline with HTML comments (invisible in rendered Markdown): <!-- broken-link-ok --> on the same line, <!-- broken-links-ok-start --> / <!-- broken-links-ok-end --> around a block, or <!-- ignore-ref: path/to/file.py --> anywhere in the file.

graph-query

Ask the graph questions without opening a browser. Works on the JSON written by --json, --compact or --ultra-compact (auto-detected; defaults to docs/graph-ultra.json, then the older snapshots):

Flags & examples
graph-query blast-radius app/core.py   # transitive importers + every doc mentioning them
graph-query hubs --top 15              # most-connected files, in/out breakdown
graph-query stale-docs --check         # docs older than the code they describe (CI gate: exit 1)
graph-query orphans --type code        # files with no edges at all
Command Answers
blast-radius <path> "what breaks if I touch this file" — transitive incoming imports (--depth, --edges to tune), plus affected docs
hubs "where is the center of gravity" — top nodes by in+out edges (--top N)
stale-docs "which docs lag behind the code" — compares last-commit times (one git log pass; mtime fallback), --check for CI
orphans "what is connected to nothing" — degree-0 nodes, filterable by category

Every command takes --json for machine-readable output — pipe it to jq or feed it to an agent.

Known limitations

Static analysis has natural borders — the graph is a referential map, not a semantic one:

  • Dynamic imports resolve only literal / top-level-constant module names (f-strings, dict lookups, conditional rebinding are skipped).
  • eval / exec and DI-by-string are invisible. [project.scripts] / [project.gui-scripts] entry points in pyproject.toml are read, but only to exempt those modules from dead-code flagging — they don't create edges.
  • Markdown templating ({{ ref }}, Jekyll/Hugo shortcodes) isn't parsed.
  • Links resolve to whole files — section anchors (file.md#part) map to the file node.
  • code→code edges are Python-only for now (the markdown/doc layers are language-agnostic).
  • One repo per graph; symlinks are treated as physical paths.

License

MIT © Yuriy Totyshev

Project details


Download files

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

Source Distribution

graph_build-0.4.0.tar.gz (348.1 kB view details)

Uploaded Source

Built Distribution

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

graph_build-0.4.0-py3-none-any.whl (177.1 kB view details)

Uploaded Python 3

File details

Details for the file graph_build-0.4.0.tar.gz.

File metadata

  • Download URL: graph_build-0.4.0.tar.gz
  • Upload date:
  • Size: 348.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for graph_build-0.4.0.tar.gz
Algorithm Hash digest
SHA256 85701d9504853980c49aaf1c31de58b42041979b23f77dc1b8238f3ec4a3c3e5
MD5 f654443cdc6020b577f1273ee55a6eb3
BLAKE2b-256 1c101a4fe861ef1d08796d3bdb515311c888fa868a5360c2c8f2ff40057e73fb

See more details on using hashes here.

Provenance

The following attestation bundles were made for graph_build-0.4.0.tar.gz:

Publisher: publish.yml on Mr-Freewan/build-graph

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

File details

Details for the file graph_build-0.4.0-py3-none-any.whl.

File metadata

  • Download URL: graph_build-0.4.0-py3-none-any.whl
  • Upload date:
  • Size: 177.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for graph_build-0.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 61e647854802d37fe067b5b5121cab8288b084a2b2124bafa0ef5dca28b3cc20
MD5 ce6f0c79bcd198e50308654716bb7307
BLAKE2b-256 2f1725b07680fbb8314836e506c95a14daefb280cfc18ae67add043f0fbe6a90

See more details on using hashes here.

Provenance

The following attestation bundles were made for graph_build-0.4.0-py3-none-any.whl:

Publisher: publish.yml on Mr-Freewan/build-graph

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