v0.9.22

Graphify-Labs/graphifyv0.9.22Jul 20, 2026by safishamsi

AI Summary

A correctness release fixing issues related to URL-based sources, virtual environment detection, file naming for Office documents, and CLI command availability.

Key Highlights

  • Fixed eviction of nodes from URL/virtual schemes on subsequent updates.
  • Fixed detection of real source directories named `env` or `.env` to prevent false positives.
  • Fixed Office and Google-Workspace sidecar naming to be stable across checkouts.
  • Made `graphify god-nodes` a real CLI subcommand.
  • Fixed nested class containment edges to point from the enclosing type.

Full Release Notes

- Fix: a node whose `source_file` is a URL/virtual scheme (`gdoc://`, `s3://`, `http://`, ...) is no longer evicted on the second `graphify update` (follow-up to #2051). The #2051 disk-absence sweep guarded such sources with a literal `"://"` check, but write-side path normalization collapses the double slash (`gdoc://x` becomes `gdoc:/x`), so the guard missed the node on the next run and dropped it into the disk-absence eviction branch. The scheme is now matched tolerantly (and a Windows drive letter like `C:/` is not misread as remote).
- Fix: a real source directory named `env`/`.env`/`*_env` is no longer silently pruned as a false-positive Python virtualenv (#2058). `detect`'s directory-noise heuristic matched those names before `.graphifyignore` negation and with no trace in any output bucket, so codebases using them as source dirs (common in UVM/ASIC verification) lost large subtrees undetectably. The venv heuristic for those names is now gated on an actual marker (`pyvenv.cfg`, an `activate` script, `lib/python*`, or `conda-meta/`); `venv`/`.venv`/`*_venv` stay name-only, and every pruned-as-noise directory is now recorded in a `pruned_noise_dirs` bucket for traceability.
- Fix: Office (`.docx`/`.xlsx`) and Google-Workspace sidecars are now named from the scan-root-relative path, not the absolute path (#2059). The absolute-path hash salted the sidecar name with the checkout location, so committing `graphify-out/` (a supported workflow) produced a new duplicate `.md` per clone/worktree, each ingested as a distinct source document. The relative hash is stable across checkouts while still disambiguating same-stem files; the Google-Workspace sidecar path additionally gains the NFC normalization it was missing.
- Fix: `serve.py`'s "graph.json is corrupted" recovery message is now reachable (#2005, thanks @kimdzhekhon). `json.JSONDecodeError` subclasses `ValueError`, and the broad `except (ValueError, FileNotFoundError)` clause was ordered first, so a truncated graph printed the bare `Expecting value...` instead of the documented rebuild hint. The `JSONDecodeError` clause now comes first.
- Fix: `graphify god-nodes`/`god_nodes` is now a real CLI subcommand, and `graphify extract --output DIR` is honored as an alias of `--out` (#2004). `god_nodes` was an analyzer, an MCP tool, and a documented capability but had no CLI command; `--output` was silently dropped on `extract` even though `graphify tree` documents it. (The `affected`/reverse-dep import-id mismatch from the same report is tracked separately.)
- Fix: a nested class/object/trait now gets its `contains` edge from the enclosing type instead of the file node (#2040). Across ~19 languages the edge was hard-coded to source from the file, so the containment tree was flat (`file -> Inner`) rather than nested (`file -> Outer -> Inner`); it now sources from the enclosing type when present, with top-level types still contained by the file.
- Fix: file nodes that share a basename now get a directory-qualified label so `explain`/discovery can tell them apart (#2032). In directory-per-entrypoint repos (Supabase Edge Functions, Next.js `page.tsx`, Rust `mod.rs`, Python `__init__.py`) dozens of files named e.g. `index.ts` collided under one label, breaking free-text discovery for exactly those files. Colliding file nodes are relabelled to the shortest unique path suffix (`process-order/index.ts`); unique basenames stay bare, and node ids/edges are unchanged.