Skip to content
inth3shadowsPublic

About

Architecture diagrams that fail CI when they go stale. Generates call-graph and dependency diagrams from your code, embeds them in your docs, and fails the build when the code drifts.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Codeshot

The docs-and-CI layer for CodeGraph. CodeGraph indexes your code so you (or an agent) can explore it. Codeshot turns that index into diagrams you commit to your docs, and into checks your build runs, so the architecture you wrote down stays the architecture you have, without anyone having to look.


What it does

Three jobs:

Job Command Result
Keep doc diagrams honest --embed <doc.md> --check exit 1 when the code has drifted from the diagram committed in your docs
Enforce architecture rules --architecture --forbid 'src/ui/->src/db/' exit 1 when a file edge crosses a forbidden boundary (drawn red, listed on stderr)
Show what a PR touches --diff --diff-ref origin/main...HEAD a diagram of the symbols defined in changed files (up to --max-symbols), with their callers/callees, for review
# docs: embed (or refresh) a file-level architecture diagram in TECHNICAL.md
codeshot --architecture --out architecture.svg --embed TECHNICAL.md --format svg

# CI, one scan: exit 1 if that diagram no longer matches the code,
# or if the UI layer calls straight into the database layer
codeshot --architecture --out architecture.svg --embed TECHNICAL.md --format svg \
  --check --forbid 'src/ui/->src/db/'

Always pass --out with --embed. Without it the --architecture/--diff image is named after the checkout directory, so a diagram committed from ~/code/myrepo-wt fails --check in a CI checkout named myrepo.

For svg, --check compares graph structure (which nodes, which edges), not bytes, so a laptop and CI with different graphviz versions agree. It never writes, and it refuses to replace a real committed diagram with a blank one. Graphviz is the only thing it is tolerant of: generate the committed diagram with the same codegraph build and the same Node line as CI. Different codegraph builds can draw different edges for same-named symbols, and --architecture on Node 22.5+ reads codegraph's index directly while Node 18/20 goes through the CLI, which counts import-only references the direct read drops (see TECHNICAL.md).

Everything comes straight from CodeGraph's index and renders through graphviz, so it works for whatever languages CodeGraph indexes. Test callers are drawn dashed so production paths stand out; a module-level reference CodeGraph couldn't resolve to a real call site is drawn dotted-gray instead of looking like a confirmed call.

In GitHub Actions

diagrams:
  runs-on: ubuntu-latest
  steps:
    - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
    - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
      with:
        node-version: "22"
    - run: sudo apt-get update && sudo apt-get install -y graphviz
    - run: >-
        npm install -g @colbymchenry/codegraph@1.6.0
        github:inth3shadows/codeshot#v0.3.0
    - run: codegraph init .
    - run: >-
        codeshot --architecture --out architecture.svg --embed TECHNICAL.md
        --format svg --check --forbid 'src/ui/->src/db/'

Use the same codegraph version locally when you regenerate the diagram. This repo's own ci.yml runs the same kind of gate, but against a pinned codegraph fork built from source, so its committed diagrams are generated with that fork, not npm.

Why this exists

Generated diagrams are easy; trusted ones aren't. Tools like madge, pyan and go-callvis draw the code as it is right now, on demand. None of them notices when the picture in your README stopped being true. Codeshot's diagrams are artifacts: committed next to the prose that explains them, regenerated by one command, and checked by the build like any other test.

Install

npm install -g github:inth3shadows/codeshot

Requirements:

  • Node.js ≥ 18
  • codegraph CLI on PATH, 1.5.0 or later, with the target repo indexed (codegraph init). --architecture mode needs 1.5.0+ specifically: earlier versions have a call-resolution bug (fixed by codegraph's LITERAL_RECEIVER_TYPES fix) that can silently fabricate cross-file edges from unrelated builtin method calls (e.g. /regex/.test(x)) whose name happens to collide with a real project symbol. Using a codegraph build with upstream PR #1801 (merged after 1.6.0, not yet in an npm release; the inth3shadows/codegraph fork has it) additionally lets --diff attribute same-named symbols to the right file; CI runs that fork.
  • graphviz (dot) on PATH — brew install graphviz / apt install graphviz

Codeshot checks for both on startup and tells you exactly what's missing and how to install it.

Whole-repo architecture diagram

codeshot --architecture --path ~/code/myrepo --out architecture.svg --format svg
# ...or, on a repo with more files than fit in one readable picture:
codeshot --architecture --path ~/code/myrepo --group-depth 1 --out modules.svg --format svg

Instead of one symbol's callers/callees, this mode enumerates every symbol in the repo's CodeGraph index, collects each one's callees, then aggregates the results into a file-to-file dependency graph (edge label = number of calls between that pair of files). Self-file edges (a function calling another function in the same file) are dropped — this is about cross-file coupling, not intra-file structure. Test files render dashed.

This is a real, data-derived graph, not a hand-drawn architecture diagram — it won't look like a curated conceptual pipeline diagram, it'll look like what the code actually calls into. On Node 22.5+ Codeshot reads codegraph's index directly, so the scan takes about a second (48.7s → 1.3s on this repo). On Node 18/20 it makes one sequential codegraph call per enumerated symbol, which is slow on a repo of any real size. Flags specific to this mode:

  • --max-symbols — cap how many symbols get probed (default 500). Codeshot warns on stderr if this cuts the scan short.
  • --group-depth <n> — roll files up into their first n directory segments and draw those as the nodes (--group-depth 1 on src/api/user.js → src/), summing the call weights of every file pair that collapses into the same pair of groups. This is the readable view of a repo big enough that the per-file graph is a hairball. Unlike --max-render, which drops the least-busy files outright — taking every edge that touched them with it — grouping keeps every cross-module call and just draws it at module resolution. Calls that become intra-group are dropped, for the same reason same-file calls already are: this diagram is about coupling between modules, not inside them. So the summed weights on a grouped diagram are legitimately lower than the per-file one's, often much lower — that's the intra-module traffic, not a lost edge. Repo-root files (no directory to roll into) stay themselves.
  • --forbid <from>-><to> — an architecture rule; repeat it for several. Each side is a repo-relative path: ending in / it means every file under that directory, otherwise one exact file (--forbid 'src/ui/->src/db/'). Any file edge a rule matches is listed on stderr with its weight and drawn red, and the run exits 1 after the diagram is written, so CI fails with the picture as evidence. Rules are checked on the per-file edges, before --group-depth and --max-render, so neither can hide a violation.
  • --depth and --max-depth-nodes have no effect here and are rejected if set to anything but their defaults — there's no multi-hop file-traversal concept to apply them to.

Of the shared flags, --max-render caps the distinct files (or groups, with --group-depth) drawn, busiest first. --limit applies only on Node 18/20, where it caps the callees fetched per probed symbol (per definition, with a codegraph that reports them) and a single stderr warning names any symbols it cut, since the diagram can't show edges that were never fetched. On Node 22.5+ the direct index read is complete, so --limit has no effect in this mode, unless Codeshot falls back to the CLI probes (see USAGE.md).

A name that exists in more than one file (two handles, or two index.js files) has its edges attributed to the right file instead of to the union of both. With a CodeGraph that reports callees per definition (upstream #1801, not yet in an npm release) this covers everything; on npm CodeGraph it falls back to the file-qualified codegraph node -f, which can't handle two same-named files, and Codeshot warns about whatever it couldn't resolve. See TECHNICAL.md for the details, including the cases where the file-qualified probe falls back, and why the graph can be slow on larger repos.

Diff-scoped diagram

codeshot --diff --path ~/code/myrepo --out changed.svg --format svg
# ...or against a specific range/ref, for reproducibility in CI:
codeshot --diff --diff-ref origin/main...HEAD --path ~/code/myrepo --out pr.svg --format svg

Instead of the whole repo or one named symbol, this mode diagrams just the symbols defined in changed files — every symbol codegraph's index attributes to a file git diff reports as touched — bolded as the diagram's roots, with their direct callers/callees fanned out around them in the usual house style. Built on the same per-symbol callers/callees fetch as single-symbol mode, just run once per changed symbol instead of once for the one you named.

  • No value after --diff diffs the working tree against HEAD (both staged and unstaged changes — "what have I changed right now"; note this is git diff HEAD, not bare git diff, which only covers unstaged edits and would miss anything already git added). --diff-ref <range> diffs an explicit ref or range instead (e.g. origin/main...HEAD) — use this form for --embed --check in CI, where there's no working tree to diff and the default would make the check flap for reasons unrelated to the code.
  • --limit, --max-render, and --max-symbols apply here too: --limit bounds each changed symbol's callers/callees fetch (per definition, with a codegraph that reports them — so a changed symbol whose name also exists in another file, and has two definitions in its own, can bring in more than --limit), --max-symbols now caps how many changed symbols get probed (not the whole repo), and --max-render bounds only the callers/callees pulled in around the changed symbols — every changed symbol itself is always drawn, since it's the reason the diagram exists.
  • --depth and --max-depth-nodes have no effect here and are rejected if set to anything but their defaults — no multi-hop traversal in this mode.
  • Composes with --embed/--check the same as the other modes — a changed-files diagram in a PR description, kept fresh and CI-guarded. --diff-ref gets its own marker id and default filename (codeshot:diff-<ref>, same pattern --group-depth uses for --architecture), so a release-range diagram and a PR-range diagram can live in the same doc without one silently overwriting the other; a bare --diff keeps the plain codeshot:diff id.
  • A changed symbol whose name also exists in another file gets only its own file's callers/callees on a codegraph that reports them per definition (upstream #1801, unreleased after 1.6.0). Otherwise, including npm 1.5.0/1.6.0, it gets the merged result for every same-named definition, and codeshot warns rather than silently misattributing edges (see TECHNICAL.md).

Single-symbol trail

Codeshot can also draw one symbol's caller/callee trail as a static image:

codeshot RollAutoSnapshot --path ~/code/myrepo --out callgraph.png
# full form
codeshot <symbol> [--path <repoPath>] [--out <file>] [--limit <n>] [--max-render <n>] [--format <fmt>] [--depth <n>]

For ad-hoc exploration, CodeGraph's own codegraph node, callers and callees answer faster. This mode earns its place when you want that trail embedded in a doc (--embed) and kept current (--check).

Two flags apply only to this mode:

  • --depth — how many hops of callers-of-callers / callees-of-callees to draw beyond the direct trail (defaults to 1, i.e. direct callers/callees only; must be a positive integer). Codeshot fetches this itself, one sequential codegraph call per newly discovered node — CodeGraph has no multi-hop traversal of its own for callers/callees. Each additional hop is drawn in a progressively lighter shade so you can tell how far a node is from the symbol at a glance. There's a safety cap on total nodes discovered (a well-connected symbol at --depth 3+ can otherwise mean hundreds of sequential codegraph calls); Codeshot warns on stderr if it hit that cap before finishing — see --max-depth-nodes below to raise it, or TECHNICAL.md for the default and rationale.
  • --max-depth-nodes — raises (or lowers) --depth's safety cap on total discovered nodes (defaults to 200; must be a positive integer). Only applies with --depth > 1; has no effect with --architecture or --diff, and is rejected there if set to anything but its default. Useful for a genuinely well-connected symbol whose graph is real but incomplete at the default cap — see the depth-budget warning it's meant to answer.

Flags shared by every mode

  • --path — repo to query (defaults to cwd)
  • --out — output file (defaults to a temp file named after the chosen --format; path is printed on success). Its directory must already exist. If the extension doesn't match --format (e.g. --out diagram.svg without --format svg), Codeshot warns on stderr instead of silently writing the wrong data under that name.
  • --limit — max callers/callees fetched per symbol (defaults to 50; must be a positive integer); see each mode for what it bounds there. Codeshot warns on stderr when results were cut off, naming the real total when your codegraph reports one — see TECHNICAL.md for what older versions fall back to.
  • --max-render — cap how many distinct nodes are drawn in the image, independent of --limit (unset by default: no cap). In --architecture the nodes are files (or groups, with --group-depth), busiest kept first; in --diff the changed symbols are always drawn and the cap covers only the callers/callees around them; in single-symbol mode it is one shared budget across callers, callees, and --depth's transitive edges. Useful when a high --limit keeps the truncation warning accurate but would otherwise produce an unreadably large image.
  • --format — output format, passed straight to dot -T<fmt> (defaults to png). svg is a good alternative for large graphs — it stays crisp at any zoom level and keeps text selectable, unlike a raster PNG. In svg (and svgz) output, each symbol node also carries the file it lives in as a hover tooltip, so you can tell same-named symbols apart without cluttering the boxes (open the file in a browser to see them; GitHub's SVG sanitizer may strip tooltips when the image is embedded in a README). Any format dot -T supports works; an unsupported one fails with dot's own error listing the valid ones.
  • --embed <file.md> — instead of just writing an image, insert (or, on re-runs, refresh in place) the diagram inside an existing markdown doc, using idempotent <!-- codeshot:<id>:start/end --> markers (the doctoc/terraform-docs pattern). Without --out the image goes next to the doc; in --architecture and --diff its name includes the checkout directory, so pass --out for anything --check will verify in CI. Refreshes an existing doc — it won't create one. Works in every mode. See USAGE.md.
  • --check — (only with --embed) verify the committed diagram and its doc block are current without changing anything: exit 0 if up to date, exit 1 if the code has drifted from the committed image. Built for a CI job / pre-commit hook so a stale diagram fails the build. For svg it compares graph structure, so CI needs no pinned graphviz; other formats compare bytes and do need the same graphviz version that generated the committed image.

Design decisions

Why the CodeGraph CLI, with a direct index read only for --architecture? The CLI's --json output is a stable, documented contract; the on-disk schema isn't. --architecture is the exception: through the CLI it needs one process per symbol (48.7s on this repo), so on Node 22.5+ it reads .codegraph/codegraph.db read-only (1.3s) and falls back to the CLI when it can't (a failed query such as a schema change, no index at --path, or CODESHOT_INDEX_READ=cli). --diff and single-symbol mode use the CLI only.

Why graphviz instead of a JS graph-drawing library? Zero new npm dependencies, and dot already produces clean, deterministic layouts — no layout algorithm to hand-tune.

Standalone tool, not a CodeGraph PR. CodeGraph's upstream main has an interactive viewer (codegraph ui, not yet in a release) for exploration; Codeshot stays out of that lane and owns the committed, CI-checked side — embedded diagrams, drift checks, architecture rules. Those are build-pipeline concerns a code index doesn't need to carry, and keeping them separate lets each evolve on its own release cadence.

Status

Early — actively used and maintained on real repos. Previous design (a terminal protocol-handler for staging Claude Code's suggested commands) was retired; see git history if you're curious what that looked like.

Ran the old installer? If you installed the retired design (install/install.ps1 or install/install.sh, no longer in this repo), run install/uninstall-legacy.ps1 (Windows) or install/uninstall-legacy.sh (macOS) once to remove the leftover protocol handler registration, Claude Code Stop hook entry, and ~/.codeshot directory.

Related Documentation

About

Architecture diagrams that fail CI when they go stale. Generates call-graph and dependency diagrams from your code, embeds them in your docs, and fails the build when the code drifts.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages