Version 0.15.0 was built on Saturday, May 30, 2026 at GMT-07:00
1780197705658from hashfbc3416.
issue_tree scans one or more GitHub repositories and renders a dependency graph of
their blocking-issue links in a variety of formats — including a self-contained
interactive HTML page. The one-shot all and auto commands scan straight to an
image with nothing written to your working directory; an optional on-disk JSON
cache (the scan → render two-step, or --cache) persists a scan so it can be
resumed or re-rendered without re-fetching.
issue_tree exposes five subcommands that implement a scan → graph → render pipeline:
issue_tree scan [--repo <owner/repo>] ... [--cache <file>] [--token <token>] [--wait] [--force]
issue_tree render [--cache <file>] [--format <fmt>] [--out <file>] [--scope <scope>]
[--repo <owner/repo>] ... [--label <label>] ... [--milestone <title>]
[--state <state>] [--child-of-gates] [--edge-style <style>] [--rankdir <dir>]
issue_tree all (accepts all scan and render flags)
issue_tree auto [<path>] (accepts all scan and render flags)
issue_tree update [--cache <file>] [--token <token>] [--wait] [--quiet]
Every level of the CLI is self-documenting, and help is always free — it prints to stdout and exits 0.
issue_tree # bare invocation → general help
issue_tree --help # general help (also -h, or `issue_tree help`)
issue_tree <subcommand> --help # that subcommand's flags (e.g. render --help)
issue_tree help <subcommand> # same, in `help` form (e.g. help render)
--token, then GITHUB_TOKEN, then
gh auth token).scan/render/all/auto) prints that command's
synopsis plus its own flags, each with a short description. render, all,
and auto share one render-layout flag block.--help and -h are recognized anywhere on the line and always win, even
alongside other flags: issue_tree render --format svg --help prints render
help and exits without rendering.issue_tree help <unknown-topic> is lenient — it falls back to the general
help rather than erroring.scanFetches issues from one or more GitHub repositories (and any external repositories they block against) and writes a resumable JSON cache. Key flags:
--repo <owner/repo> — one or more target repositories (repeatable).--cache <file> — cache file path (default issue-tree-cache.json).--token <token> — GitHub personal access token; falls back to GITHUB_TOKEN env var,
then gh auth token.--wait — on hitting the rate limit, sleep until it resets and continue.--force — discard any existing cache before scanning.--quiet / -q — suppress per-page and per-issue progress output. By default scan
writes a progress line to stderr for every phase boundary, every page of issues fetched,
every external blocker resolved, and every issue whose comments were fetched. Use
--quiet to silence all of that (the final scan complete line on stdout still prints).renderReads an existing cache and renders the dependency graph. A missing cache is a hard
error — render never scans. Key flags:
--cache <file> — cache to read (default issue-tree-cache.json).--format <fmt> — one of json, dot, svg, png, jpeg, html (default: inferred
from --out extension, otherwise svg).--out <file> — write output to a file. For text formats (json/dot/svg/html),
omitting --out writes to stdout. For binary formats (png/jpeg), omitting --out
writes to a generated default file in the current directory named
owner_repo_YYYY-MM-DD_HH-MM-SS.ext, with _and_N_more appended after the first
repo when multiple repos contribute to the render.--scope <scope> — open (default), connected, or all.--label <label> — keep only issues with this label (repeatable; OR semantics across multiple uses).--milestone <title> — keep only issues in this milestone.--state <state> — open, closed, or all (default all).--edge-style <style> — color (default) or color+shape.--rankdir <dir> — lr (default, left-to-right) or tb (top-to-bottom).--engine <name> — Graphviz layout engine: dot (default, hierarchical),
neato/fdp/sfdp (force-directed, more compact for sparse graphs),
twopi (radial), or circo (circular).--splines <kind> — edge routing: curved (default), line (straight),
ortho (right-angle), polyline, spline, or none.--nodesep <inches> — gap between nodes in the same rank. Graphviz default 0.25.--ranksep <inches> — gap between ranks. Graphviz default 0.5. Halving these
roughly halves the corresponding axis of the output.--concentrate — merge parallel edges with shared endpoints.--packmode <mode> — how disconnected components (multiple small trees) are
arranged in 2D. node (default, shelf packing — fills space tightly without
a grid), array (row-major grid), clust (cluster-by-cluster), graph
(whole-graph cell), or off (Graphviz default — stack along the
rank-orthogonal axis). For repos with many independent issue chains, node
produces a far more readable view than the vertically-stacked default.--graph-pad <inches> — outer pad around the whole graph (~0.055 default).--graph-margin <inches> — outer page margin (0.5 default).--node-margin <x[,y]> — per-node text padding inside boxes (Graphviz default 0.11,0.055).--edge-minlen <N> — minimum rank distance per edge (default 1).--issue-shape <shape> and --pr-shape <shape> — Graphviz shape names for
issue and PR nodes (defaults box and ellipse). Accepts box, polygon,
ellipse, oval, circle, egg, triangle, diamond, trapezium,
parallelogram, house, pentagon, hexagon, septagon, octagon,
doublecircle, doubleoctagon, tripleoctagon, Mdiamond, Msquare,
Mcircle, square, star, note, tab, folder, box3d, component,
cylinder, and rectangle.--raster-max <px> — for png/jpeg output only, the maximum pixel dimension
on the longer axis. When the laid-out SVG would exceed this on either axis, the
output is downscaled by a single zoom factor that brings the longer axis down to
this value, preserving aspect ratio. Defaults to 8192. A one-line notice is
written to stderr when capping fires. This guard exists because long
dependency chains can produce SVGs with extreme aspect ratios — a 1597-node
graph routinely lays out at ~3800 × 81600 px, whose 1:1 RGBA buffer alone is
1.2 GB before any JPEG encoding overhead.allScans the named repos and renders, in a single invocation, accepting every flag from
both scan and render. Cache-free by default: the scan runs in memory and
nothing is written to your working directory — so naming a new repo never reads or
modifies an unrelated cache left behind by an earlier run. Pass --cache <file> to
persist the scan to disk instead (enabling resume, and later re-rendering without
re-scanning).
issue_tree all --repo owner/name --out g.png # scan in memory, write only the image
issue_tree all --repo owner/name --cache c.json --out g.png # also persist the scan to c.json
autoWalks a project tree, extracts the GitHub repository URL from every project
manifest it finds, scans the deduped list, and renders the result — auto is a
scan + render combo, exactly like all, except the repo list comes from
filesystem discovery instead of --repo flags. Like all, it is cache-free
by default (scan in memory, render, write nothing but the image); pass
--cache <file> to persist the scan. Useful when you are sitting inside a
polyglot monorepo or a directory containing several checked-out projects and
want a one-shot graph across all of them.
issue_tree auto # discover + scan in memory + write auto-named PNG (no cache file)
issue_tree auto ./projects --cache big.json # rooted at ./projects, persisted to big.json
issue_tree auto --format svg --out g.svg # render to SVG instead of PNG
The positional argument is the directory to scan (defaults to .). Every
scan flag (--cache, --token, --wait, --force, --quiet) and every
render flag (--format, --out, --scope, --label, --milestone,
--state, --child-of-gates, --edge-style, --rankdir, --engine,
--splines, --nodesep, --ranksep, --concentrate, --packmode,
--graph-pad, --graph-margin, --node-margin, --edge-minlen,
--issue-shape, --pr-shape, --raster-max) is accepted and forwarded.
auto sources its repos from disk, so passing --repo to auto is an error
(it would otherwise be silently ignored, scanning the locally-detected repo
instead of the named one). To scan a specific repo, use all:
issue_tree all --repo <owner/repo>.
The auto default --format is png (unlike render/all, which
default to svg). The reasoning: the natural endpoint of issue_tree auto
is a finished image, so binary is the best implicit choice. Override with
--format <fmt> or by passing --out with a recognized extension (e.g.
--out g.svg picks svg).
Detection logs go to stdout (one detected: <file> → <owner/name> line per
repo) and are suppressed by --quiet. If no repos are found, auto exits
with code 2 and a no GitHub repos detected message and skips render. If
the scan rate-limits, auto still proceeds to render whatever was scanned so
far (matching all's semantics).
Recognized manifest formats (one repo extracted per match; non-GitHub URLs are skipped):
| Ecosystem | File pattern | Field |
|---|---|---|
| Node/JS/TS | package.json |
.repository or .repository.url |
| Rust | Cargo.toml |
[package].repository |
| Python | pyproject.toml |
[project.urls].{Repository,Source,Homepage} or [tool.poetry].repository |
| Go | go.mod |
module github.com/... line |
| PHP | composer.json |
.support.source or .homepage |
| Dart | pubspec.yaml |
repository: or homepage: |
| Java/Maven | pom.xml |
<scm><url> or <scm><connection> |
| .NET | *.csproj / *.fsproj / *.vbproj |
<RepositoryUrl> |
| Crystal | shard.yml |
repository: or url: |
| Haskell | *.cabal |
source-repository.location: or homepage: |
| OCaml | dune-project |
(source (github ...)) or (source (uri ...)) |
| Ruby | *.gemspec |
spec.metadata['source_code_uri'] or spec.homepage |
| ObjC/Swift | *.podspec |
s.source = { :git => ... } or s.homepage |
| R | DESCRIPTION |
URL: (first GitHub entry wins) |
| Lua | *.rockspec |
source = { url = ... } or homepage |
| Elixir | mix.exs |
links: %{"GitHub" => "..."} |
| Perl | META.json / META.yml |
.resources.repository.{url,web} |
| Julia | Project.toml |
repo = "..." |
| PowerShell | *.psd1 |
ProjectUri = '...' |
| Zig | build.zig.zon |
.repository = "..." |
| D | dub.json / dub.sdl |
.sourceRepository |
| Clojure | project.clj |
:url "..." inside defproject |
| Common Lisp | *.asd |
:source-control or :homepage |
| Fortran | fpm.toml |
[package].repository |
Excluded directories. The traversal never descends into directories that
hold vendored dependencies or build artifacts: node_modules, vendor,
target, dist, build, out, bin, obj, __pycache__, .venv,
venv, env, .tox, .pytest_cache, Pods, .gradle, .idea, .vs,
.vscode, .next, .nuxt, coverage, coverage-* (e.g.
coverage-typedoc), htmlcov, .git, .svn, .hg, bower_components,
elm-stuff, deps, _build, .stack-work, .cargo, .pub-cache. This
prevents thousands of unrelated package.json files (or similar) from a
dependency tree from polluting the scan list.
updateIncrementally refreshes the repos already recorded in a cache — the discoverable
shorthand for "re-run scan against an existing cache". The targets come from the
cache's repos, so update takes no --repo (passing one is an error pointing you
to scan/all).
issue_tree update # refresh ./issue-tree-cache.json
issue_tree update --cache fsl.json --wait
For each repo in the cache it fires a cheap repository.updatedAt probe and skips
any repo unchanged since its last scan; for changed repos it passes the repo's
lastScanCompletedAt as the GraphQL since filter, so only issues modified since
then are re-fetched. Flags: --cache <file> (default issue-tree-cache.json),
--token, --wait, --quiet/-q. A missing or repo-less cache is an error —
update refreshes an existing cache, it does not create one.
| Format | Description |
|---|---|
json |
The filtered graph as JSON (nodes + edges + metadata). |
dot |
Graphviz DOT source. |
svg |
SVG rendered via viz.js. |
png |
PNG rasterized from the SVG via resvg. |
jpeg |
JPEG rasterized from the SVG via resvg. |
html |
Self-contained interactive HTML — includes the viz.js layout engine and the browser client bundle, so the file works offline with no server. |
The html format is particularly useful for sharing: the output file opens in any
browser and renders the graph interactively without a server or network access.
render never scansrender reads a pre-built cache. If the cache file does not exist, the command exits
with an error. Run scan (or all) first to produce the cache.
| Count | Statement | Branch | Func | Line | |
|---|---|---|---|---|---|
| Unit | 489 | 96.18% | {{unitbranch}}% | {{unitfunc}}% | {{unitline}}% |
| Stochastic | 18 | 96.18% | {{stochbranch}}% | {{stochfunc}}% | {{stochline}}% |
| Docblock count | 40% | |
|---|---|---|
| Docblock coverage | 286 | 40% |
![]() |
![]() |
![]() |
![]() |
master//docsTODO_TOKEN_FOR_GH_CI_CD after renaming it in ci.ymlissue_trees in this file's top block linksissue_trees in package.jsonissue_tree in verify_version_bump.jspackage.jsonsrc/html/index.htmlbase-README.mdissue_trees in rollup.config.jsbin block to package.json, orbin config from rollup.config.jsnpm install && npm run build
issue_tree's scanner fetches the issues of one or more GitHub repositories — and the
external issues that block them — into a single resumable JSON cache, via the GitHub
GraphQL API.
scanner [--cache <file>] [--token <token>] [--wait] [--force] <owner/repo> ...
<owner/repo> ... — one or more target repositories to scan.--cache <file> — cache file path (default issue-tree-cache.json).--token <token> — GitHub token (see Authentication below).--wait — on hitting the rate limit, sleep until it resets and continue, rather than stopping.--force — discard any existing cache before scanning.The scanner resolves a GitHub token from three sources, in order: the --token flag,
the GITHUB_TOKEN environment variable, then gh auth token (the GitHub CLI).
The scan runs in four phases — repository issues, body blockers, comments, comment blockers — and checkpoints the JSON cache continuously. A scan interrupted by the rate limit is resumed by simply re-running the same command, picking up at the last saved page cursor.
An already-complete cache is re-scanned incrementally, fetching only issues changed
since the last run. Before opening pagination for a previously-completed repo, a
cheap single-scalar GraphQL probe checks repository.updatedAt; when GitHub reports
the repo has not changed since the cache's lastScanCompletedAt, the repo is skipped
entirely (costing one rate-limit point instead of thousands of page fetches).
Even mid-scan, a resumed repo's since filter is set to the maximum updatedAt
across already-cached issues, so the GraphQL server only returns issues newer than
what is on disk.
MIT