Skip to content

Commit 891357f

Browse files
committed
feat(tree): graphify tree — D3 v7 collapsible-tree HTML emitter
Adds a new `graphify tree` subcommand that emits a self-contained D3 v7 collapsible-tree HTML view of an existing graph.json. Why --- The existing `graph.html` (force-directed) is great for finding hubs and unexpected connections. But for code review and onboarding, a hierarchical tree-of-modules view is much faster: you can collapse everything and expand only the package you care about, the depth- based colour palette gives instant orientation, and the layout mirrors the on-disk structure. The look + feel is a near-verbatim port of the TGI Taxonomy Viewer (expand-all / collapse-all / reset-view buttons, multi-line `wrapText` labels with separately-coloured name + count, depth-based palette, click-to-toggle subtree, hover-inspector showing top-K outbound edges per symbol). Implementation -------------- - `graphify/tree_html.py` (575 LOC, single file, no new runtime dependencies). D3 v7 is loaded from cdn.jsdelivr.net at view time. - Hierarchy is built from `source_file` longest-common-prefix; symbols are grouped by containing module so the tree mirrors the on-disk layout exactly. - Inspector pre-computes top-K outbound edges per symbol so the page works fully offline once loaded. - `__main__.py` adds the subcommand + help text after the `check-update` block. Configuration ------------- - `--graph PATH` path to graph.json (default: graphify-out/graph.json) - `--output HTML` output path (default: graphify-out/GRAPH_TREE.html) - `--root PATH` filesystem root (default: LCP of source_files) - `--max-children N` cap visible children per node (default: 200) - `--top-k-edges N` per-symbol outbound edges in inspector (default: 12) - `--label NAME` project label shown in the page header Tested locally on a 17 641-node graph — emits a 4.9 MB HTML file that renders smoothly in Firefox / Chromium.
1 parent 770d7f5 commit 891357f

3 files changed

Lines changed: 653 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,21 @@
22

33
Full release notes with details on each version: [GitHub Releases](https://github.com/safishamsi/graphify/releases)
44

5+
## Unreleased — `graphify tree` subcommand
6+
7+
- **New:** `graphify tree` — emits a self-contained D3 v7 collapsible-tree HTML
8+
view of `graph.json`. Same look + feel as the *TGI Taxonomy Viewer*: expand-
9+
all / collapse-all / reset-view buttons; multi-line `wrapText` labels with
10+
separately-coloured name + count; depth-based colour palette; click-to-
11+
toggle subtree; hover inspector showing top-K outbound edges per symbol.
12+
- Hierarchy is built from `source_file` longest-common-prefix; symbols are
13+
grouped by their containing module so the tree mirrors the on-disk layout.
14+
- Configuration: `--graph PATH`, `--output HTML`, `--root PATH`,
15+
`--max-children N` (default 200), `--top-k-edges N` (default 12),
16+
`--label NAME`.
17+
- Implementation: `graphify/tree_html.py` (575 LOC, no external runtime
18+
dependencies — D3 v7 is loaded from cdn.jsdelivr.net).
19+
520
## 0.4.23 (2026-04-18)
621

722
- Fix: stale skill version warning persists after running `graphify install` when multiple platforms were previously installed — `graphify install` now refreshes `.graphify_version` in all other known skill directories so the warning clears across the board (#178)

graphify/__main__.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1004,6 +1004,13 @@ def main() -> None:
10041004
print(" --nodes N1 N2 ... source node labels cited in the answer")
10051005
print(" --memory-dir DIR memory directory (default: graphify-out/memory)")
10061006
print(" check-update <path> check needs_update flag and notify if semantic re-extraction is pending (cron-safe)")
1007+
print(" tree emit a D3 v7 collapsible-tree HTML for graph.json")
1008+
print(" --graph PATH path to graph.json (default graphify-out/graph.json)")
1009+
print(" --output HTML output path (default graphify-out/GRAPH_TREE.html)")
1010+
print(" --root PATH filesystem root for the hierarchy")
1011+
print(" --max-children N cap children per node (default 200)")
1012+
print(" --top-k-edges N per-symbol outbound edges in inspector (default 12)")
1013+
print(" --label NAME project label in header")
10071014
print(" benchmark [graph.json] measure token reduction vs naive full-corpus approach")
10081015
print(" hook install install post-commit/post-checkout git hooks (all platforms)")
10091016
print(" hook uninstall remove git hooks")
@@ -1422,6 +1429,62 @@ def main() -> None:
14221429
from graphify.watch import check_update
14231430
check_update(Path(sys.argv[2]).resolve())
14241431
sys.exit(0)
1432+
elif cmd == "tree":
1433+
# Emit a TGI-style D3 v7 collapsible tree HTML for an existing
1434+
# graph.json. Same look + feel as the TGI taxonomy viewer:
1435+
# expand-all / collapse-all / reset-view buttons, multi-line
1436+
# wrapText labels with separately-coloured name + count,
1437+
# depth-based palette, click-to-toggle subtree.
1438+
from typing import Optional as _Opt
1439+
from graphify.tree_html import write_tree_html, DEFAULT_MAX_CHILDREN
1440+
graph_path = Path("graphify-out/graph.json")
1441+
output_path: "_Opt[Path]" = None
1442+
root: "_Opt[str]" = None
1443+
max_children = DEFAULT_MAX_CHILDREN
1444+
top_k_edges = 0
1445+
project_label: "_Opt[str]" = None
1446+
args = sys.argv[2:]
1447+
i_arg = 0
1448+
while i_arg < len(args):
1449+
a = args[i_arg]
1450+
if a == "--graph" and i_arg + 1 < len(args):
1451+
graph_path = Path(args[i_arg + 1]); i_arg += 2
1452+
elif a == "--output" and i_arg + 1 < len(args):
1453+
output_path = Path(args[i_arg + 1]); i_arg += 2
1454+
elif a == "--root" and i_arg + 1 < len(args):
1455+
root = args[i_arg + 1]; i_arg += 2
1456+
elif a == "--max-children" and i_arg + 1 < len(args):
1457+
max_children = int(args[i_arg + 1]); i_arg += 2
1458+
elif a == "--top-k-edges" and i_arg + 1 < len(args):
1459+
top_k_edges = int(args[i_arg + 1]); i_arg += 2
1460+
elif a == "--label" and i_arg + 1 < len(args):
1461+
project_label = args[i_arg + 1]; i_arg += 2
1462+
elif a in ("-h", "--help"):
1463+
print("Usage: graphify tree [--graph PATH] [--output HTML]")
1464+
print(" --graph PATH path to graph.json (default graphify-out/graph.json)")
1465+
print(" --output HTML output path (default graphify-out/GRAPH_TREE.html)")
1466+
print(" --root PATH filesystem root (default: longest common dir of all source_files)")
1467+
print(" --max-children N cap visible children per node (default 200)")
1468+
print(" --top-k-edges N pre-compute top-K outbound edges per symbol (default 12)")
1469+
print(" --label NAME project label shown in the page header")
1470+
return
1471+
else:
1472+
i_arg += 1
1473+
if not graph_path.is_file():
1474+
print(f"error: graph.json not found at {graph_path}", file=sys.stderr)
1475+
sys.exit(1)
1476+
if output_path is None:
1477+
output_path = graph_path.parent / "GRAPH_TREE.html"
1478+
out = write_tree_html(
1479+
graph_path=graph_path, output_path=output_path,
1480+
root=root, max_children=max_children,
1481+
top_k_edges=top_k_edges, project_label=project_label,
1482+
)
1483+
size_kb = out.stat().st_size / 1024
1484+
print(f"wrote {out} ({size_kb:.1f} KB)")
1485+
print(f"open with: xdg-open {out} (or file://{out.resolve()})")
1486+
sys.exit(0)
1487+
14251488
elif cmd == "merge-graphs":
14261489
# graphify merge-graphs graph1.json graph2.json ... --out merged.json
14271490
args = sys.argv[2:]

0 commit comments

Comments
 (0)