issue_tree
v0.15.0
Published
issue_tree
Readme
issue_tree v0.15.0
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 CLI
issue_tree exposes five subcommands that implement a scan → graph → render pipeline:
Subcommands
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]Getting help
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.
scan
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).
render
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.
all
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.jsonauto
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 PNGThe 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.
update
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 --waitFor 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.
Output formats
| 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.
Important: render never scans
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.
Test status
How to use this template
Before invoking it
- [ ] Decide whether to
- Update the deps in the template recommended
- Update the deps post-install
- Let the deps be out of date
After invoking it
- [ ] 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
- [ ] Maybe replace src/html/favicon.png
- [ ] commit and vroom
Scanner / cache module
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.
CLI
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.
Authentication
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).
Resumable cache
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.
License
MIT
