Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,25 @@

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

## Unreleased — `graphify tree` subcommand

- **New:** `graphify tree` — emits a self-contained D3 v7 collapsible-tree HTML
view of `graph.json`. Expand-all / collapse-all / reset-view buttons;
multi-line `wrapText` labels with separately-coloured name + count;
depth-based colour palette; click-to-toggle subtree; hover inspector
showing top-K outbound edges per symbol.
- Hierarchy is built from `source_file` longest-common-prefix; symbols are
grouped by their containing module so the tree mirrors the on-disk layout.
- Configuration: `--graph PATH`, `--output HTML`, `--root PATH`,
`--max-children N` (default 200), `--top-k-edges N` (default 12),
`--label NAME`.
- Implementation: `graphify/tree_html.py` (575 LOC, no external runtime
dependencies — D3 v7 is loaded from cdn.jsdelivr.net).
- Security: `emit_html()` now `html.escape()`s the page title and header,
and JS-escapes `</` sequences in the embedded JSON blob so crafted
graph labels or `--label` values cannot break out of `<title>`, `<h1>`,
or the `<script>` tag (matches the `_js_safe()` pattern in `export.py`).

## 0.4.23 (2026-04-18)

- 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)
Expand Down
63 changes: 63 additions & 0 deletions graphify/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1004,6 +1004,13 @@ def main() -> None:
print(" --nodes N1 N2 ... source node labels cited in the answer")
print(" --memory-dir DIR memory directory (default: graphify-out/memory)")
print(" check-update <path> check needs_update flag and notify if semantic re-extraction is pending (cron-safe)")
print(" tree emit a D3 v7 collapsible-tree HTML for graph.json")
print(" --graph PATH path to graph.json (default graphify-out/graph.json)")
print(" --output HTML output path (default graphify-out/GRAPH_TREE.html)")
print(" --root PATH filesystem root for the hierarchy")
print(" --max-children N cap children per node (default 200)")
print(" --top-k-edges N per-symbol outbound edges in inspector (default 12)")
print(" --label NAME project label in header")
print(" benchmark [graph.json] measure token reduction vs naive full-corpus approach")
print(" hook install install post-commit/post-checkout git hooks (all platforms)")
print(" hook uninstall remove git hooks")
Expand Down Expand Up @@ -1422,6 +1429,62 @@ def main() -> None:
from graphify.watch import check_update
check_update(Path(sys.argv[2]).resolve())
sys.exit(0)
elif cmd == "tree":
# Emit a D3 v7 collapsible-tree HTML view of graph.json:
# 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.
from typing import Optional as _Opt
from graphify.tree_html import write_tree_html, DEFAULT_MAX_CHILDREN
graph_path = Path("graphify-out/graph.json")
output_path: "_Opt[Path]" = None
root: "_Opt[str]" = None
max_children = DEFAULT_MAX_CHILDREN
top_k_edges = 0
project_label: "_Opt[str]" = None
args = sys.argv[2:]
i_arg = 0
while i_arg < len(args):
a = args[i_arg]
if a == "--graph" and i_arg + 1 < len(args):
graph_path = Path(args[i_arg + 1]); i_arg += 2
elif a == "--output" and i_arg + 1 < len(args):
output_path = Path(args[i_arg + 1]); i_arg += 2
elif a == "--root" and i_arg + 1 < len(args):
root = args[i_arg + 1]; i_arg += 2
elif a == "--max-children" and i_arg + 1 < len(args):
max_children = int(args[i_arg + 1]); i_arg += 2
elif a == "--top-k-edges" and i_arg + 1 < len(args):
top_k_edges = int(args[i_arg + 1]); i_arg += 2
elif a == "--label" and i_arg + 1 < len(args):
project_label = args[i_arg + 1]; i_arg += 2
elif a in ("-h", "--help"):
print("Usage: graphify tree [--graph PATH] [--output HTML]")
print(" --graph PATH path to graph.json (default graphify-out/graph.json)")
print(" --output HTML output path (default graphify-out/GRAPH_TREE.html)")
print(" --root PATH filesystem root (default: longest common dir of all source_files)")
print(" --max-children N cap visible children per node (default 200)")
print(" --top-k-edges N pre-compute top-K outbound edges per symbol (default 12)")
print(" --label NAME project label shown in the page header")
return
else:
i_arg += 1
if not graph_path.is_file():
print(f"error: graph.json not found at {graph_path}", file=sys.stderr)
sys.exit(1)
if output_path is None:
output_path = graph_path.parent / "GRAPH_TREE.html"
out = write_tree_html(
graph_path=graph_path, output_path=output_path,
root=root, max_children=max_children,
top_k_edges=top_k_edges, project_label=project_label,
)
size_kb = out.stat().st_size / 1024
print(f"wrote {out} ({size_kb:.1f} KB)")
print(f"open with: xdg-open {out} (or file://{out.resolve()})")
sys.exit(0)

elif cmd == "merge-graphs":
# graphify merge-graphs graph1.json graph2.json ... --out merged.json
args = sys.argv[2:]
Expand Down
Loading