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)
- General help prints a one-line usage synopsis, the four subcommands each
with a one-line description, a pointer to per-subcommand help, and the token
note (a GitHub token is resolved from
--token, thenGITHUB_TOKEN, thengh auth token). - Per-subcommand help (
scan/render/all/auto) prints that command's synopsis plus its own flags, each with a short description.render,all, andautoshare one render-layout flag block. --helpand-hare recognized anywhere on the line and always win, even alongside other flags:issue_tree render --format svg --helpprints render help and exits without rendering.issue_tree help <unknown-topic>is lenient — it falls back to the general help rather than erroring.
Fetches 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 (defaultissue-tree-cache.json).--token <token>— GitHub personal access token; falls back toGITHUB_TOKENenv var, thengh 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 defaultscanwrites 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--quietto silence all of that (the finalscan completeline on stdout still prints).
Reads 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 (defaultissue-tree-cache.json).--format <fmt>— one ofjson,dot,svg,png,jpeg,html(default: inferred from--outextension, otherwisesvg).--out <file>— write output to a file. For text formats (json/dot/svg/html), omitting--outwrites to stdout. For binary formats (png/jpeg), omitting--outwrites to a generated default file in the current directory namedowner_repo_YYYY-MM-DD_HH-MM-SS.ext, with_and_N_moreappended after the first repo when multiple repos contribute to the render.--scope <scope>—open(default),connected, orall.--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, orall(defaultall).--edge-style <style>—color(default) orcolor+shape.--rankdir <dir>—lr(default, left-to-right) ortb(top-to-bottom).--engine <name>— Graphviz layout engine:dot(default, hierarchical),neato/fdp/sfdp(force-directed, more compact for sparse graphs),twopi(radial), orcirco(circular).--splines <kind>— edge routing:curved(default),line(straight),ortho(right-angle),polyline,spline, ornone.--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), oroff(Graphviz default — stack along the rank-orthogonal axis). For repos with many independent issue chains,nodeproduces 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 default0.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 (defaultsboxandellipse). Acceptsbox,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, andrectangle.--raster-max <px>— forpng/jpegoutput 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 to8192. 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.
Scans 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
Walks 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.
Incrementally 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 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% |
![]() |
![]() |
![]() |
![]() |
- Decide whether to
- Update the deps in the template recommended
- Update the deps post-install
- Let the deps be out of date
- Reset package version
- Turn Github Pages on, and point it at
master//docs - Set up the auth token
TODO_TOKEN_FOR_GH_CI_CDafter renaming it in ci.yml - Change all the
issue_trees in this file's top block links - Change all the
issue_trees inpackage.json - Change the
issue_treeinverify_version_bump.js - Write or copy-paste the description in
package.json - Search for all remaining TODOs
- Update meta tags and TODOs in
src/html/index.html - Write a
base-README.md - Change all the
issue_trees inrollup.config.js - Decide whether to
- re-add a
binblock topackage.json, or - remove the
binconfig fromrollup.config.js
- re-add a
-
npm install && npm run build- Maybe update the deps?
- Handle the MAYBE-REMOVEs in the HTML HEAD
- Change src/html/index.html 's <title>
- Maybe replace src/html/favicon.png
- commit and vroom
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 (defaultissue-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



