Skip to content
Changelog

What's new

Every release, straight from GitHub — newest first.

  1. v0.9.15Jul 13, 2026

    v0.9.15

    A small, security-focused patch on top of 0.9.14.

    Highlights

    • Security (stored XSS): the exported graph.html is fixed (#1838). The report's neighbor links dropped an unescaped stringified node id into an inline onclick — which broke every neighbor link and, when a node id/label contained a double-quote (e.g. from a document or a title scraped via graphify add <url>), let a hostile source inject a live event handler into a report opened locally. The id is now carried in an HTML-escaped data-nid attribute dispatched through a single delegated listener. If you generate reports from untrusted corpora, upgrade.

    All fixes

    • Security: close a stored XSS and repair the (previously always-broken) neighbor "focus" links in the exported graph.html (#1838, thanks @edgestack-ai).
    • Fix: detection honors nested .gitignore/.graphifyignore files below the scan root, matching git — a vendor/sub/.gitignore deeper in the tree is now applied to its own subtree instead of being ignored (#1847, thanks @Mohak-Agrawal). Composes with .git/info/exclude (0.9.14): a nearer ! re-include still wins.
    • Fix: graphify update now keeps human-readable community_name labels instead of stripping them back to numeric ids on every incremental rebuild (#1808 / #1855, thanks @latreon).

    Install

    Full notes ↗
  2. v0.9.14Jul 13, 2026

    v0.9.14

    graphify turns any folder of code, docs, papers, images, or videos into a queryable knowledge graph. 0.9.14 is a correctness-focused release: eleven fixes across detection, the git hooks, extraction caching, query seeding, and export — most from community reports, several with their own PRs.

    Highlights

    • Massive graph bloat from git worktrees is gone (#1810). Detection now honors .git/info/exclude — where git worktree add records nested worktree paths — so graphify no longer walks into worktree copies of the repo. One reporter's 5-worktree repo had ballooned from ~9,400 nodes / 10 MB to ~210,000 nodes / 311 MB, regenerated on every commit.
    • No more username leaks in committed graphs (#1789). Visual Studio solution-folder nodes were keyed off the absolute scan path, embedding the local username into graph.json. They're now relative.
    • graphify export graphml stopped crashing (#1831). Any dict/list-valued attribute (a node metadata dict, the graph-level hyperedges list) failed the entire export and left a 0-byte file; non-scalars are now JSON-serialized and the write is atomic.
    • The CLI no longer reports success as failure through a pipe (#1807). graphify query … | head used to exit 255 when the reader closed early, breaking CI wrappers and agent harnesses; an early-closing reader is now treated as success.

    All fixes

    • Fix: Visual Studio solution-folder node ids no longer embed the absolute scan path / username (#1789, thanks @fremat79).
    • Fix: the CLI exits 0 (not 255) when a downstream reader closes the pipe early — head, Select-Object -First N, sed q (#1807 / #1811, thanks @varuntej07).
    • Fix: extract() writes its AST cache (and the stat-index) to CWD, never the analyzed source tree, with the cache location decoupled from the portable key/id anchor (#1774 / #1802, thanks @SimiSips).
    Full notes ↗
  3. v0.9.13Jul 12, 2026

    v0.9.13

    Maintenance release: a batch of correctness and privacy fixes across extraction, incremental update, and query. No breaking changes.

    Highlights:

    • Query log is now opt-in (off by default) — no more undocumented plaintext record of your queries in ~/.cache (#1797).
    • Incremental graphify update no longer silently evicts nodes for files that are merely newly-ignored but still on disk (#1795), and build_merge no longer drops a re-extracted file passed in prune_sources (#1796).
    • Markdown files no longer split into duplicate document nodes across the quick-scan and semantic passes (#1799).
    • New language coverage: Ruby .rake files (#1784) and cross-file Bash script execution edges (#1756).

    Fixes

    • Fix: the query log is now opt-in (off by default) (#1797, thanks @adam-pond-agent). querylog wrote every query/path/explain question and corpus path (and full responses if GRAPHIFY_QUERY_LOG_RESPONSES) to a default-on, unbounded, fail-silent plaintext file at ~/.cache/graphify-queries.log — outside any repo's .gitignore/retention, and undocumented, which contradicts graphify's on-device / no-telemetry posture. Logging is now OFF unless you opt in with GRAPHIFY_QUERY_LOG_ENABLE=1 (default path) or GRAPHIFY_QUERY_LOG=<path>; GRAPHIFY_QUERY_LOG_DISABLE=1 still forces it off. All the query-log env vars are now documented in the README.

    • Fix: a markdown file that went through semantic extraction is no longer duplicated into two disconnected nodes on later graphify update (#1799, thanks @jerp86). The semantic pass mints <slug>_doc while the markdown quick-scan mints the bare <slug>, so the file's edges split across two twins (a docs->code path query would dead-end on the bare half; centrality and communities split too). build_from_json now merges the bare quick-scan node into the semantic _doc node when both share the same source_file and are file_type: document, consolidating their edges/hyperedges onto one node. Gated so an unrelated code symbol foo and foo_doc never merge.

    Full notes ↗
  4. v0.9.12Jul 10, 2026

    v0.9.12

    • Fix: live PostgreSQL introspection (--postgres) now emits foreign-key references edges under a read-only role (#1746, thanks @rithyKabir). The FK query read information_schema.referential_constraints, which is privilege-filtered — a role with only SELECT sees zero FK rows while tables/views/routines still appear, so every references edge silently vanished. It now reads the world-readable pg_catalog.pg_constraint (keyed by oid, which also fixes same-named constraints on sibling tables cross-matching in the old name-based joins), preserving composite-FK column order via UNNEST ... WITH ORDINALITY.

    • Fix: json_config no longer emits imports/extends edges to node IDs it never creates (#1764, thanks @oleksii-tumanov). package.json dependencies and tsconfig.json extends/$ref targets produced edges whose endpoint node was absent, so build_from_json silently dropped them (the "no matching node id" case is filtered out of real errors) — losing dependency/extends structure on two of the most common files in any JS/TS repo. The extractor now creates the referenced target as a concept node before adding the edge.

    • Fix: graphify update no longer deletes semantic hyperedges on every run (#1755, thanks @oleksii-tumanov). The AST-only rebuild treated every rebuilt corpus file as grounds to evict hyperedges anchored to it, but the AST pass never re-emits hyperedges, so doc-sourced hyperedges (exactly what semantic extraction produces) were permanently lost on the first update after a full build — even a no-op run. Hyperedge eviction is now scoped to genuinely deleted (or symlink-outside) sources, mirroring node/edge handling; replacement-by-id and dangling-member cleanup are unchanged.

    • Fix: Java member calls resolve against the receiver's declared type instead of a bare method-name match (#1696/#1697, thanks @oleksii-tumanov). gw.charge() where gw: PaymentGateway now binds to PaymentGateway.charge, not a same-named AuditLog.charge in another file. Explicit-type receivers and this are exact; current-class fields, method parameters, and explicitly-typed locals resolve via a method-scoped type table; a missing, ambiguous, inherited, or chained receiver is skipped rather than guessed (same god-node guard as the C#/Swift/Ruby resolvers). Fully-qualified and nested-type receivers are deferred (they need package/nesting-aware type identity).

    • Fix: output/cache artifacts no longer land in the scanned corpus or CWD when --out/--graph point elsewhere (#1747, thanks @bbqboogiedwonsen). extract <corpus> --out <dir> correctly wrote the graph to <dir> but detect()'s word-count/stat-index cache still created a stray graphify-out/cache/ inside the corpus (it uses the scan root); it now honors the --out dir via a threaded cache_root. And cluster-only --graph <elsewhere>/graphify-out/graph.json wrote GRAPH_REPORT.md/labels/analysis/re-clustered graph to the CWD instead of beside the input; it now writes beside --graph when that graph lives in a graphify-out/ dir, while still restoring into the CWD for an archived backup/graph.json (#934).

    • Fix: imports/references edges no longer bind across a language boundary (#1749, thanks @philberndt). The spec already forbids cross-language calls, but an unresolved Python import time could still resolve by bare stem onto a src/time.ts file node — welding a polyglot repo's halves together at a phantom edge (in the reporter's repo, 3 such edges were the only thing bridging 2409 Python nodes to 1403 TS nodes, inflating time.ts betweenness ~90x and making it the #1 "god node"). The build-time cross-language guard now covers imports/imports_from/references in addition to calls, dropping an edge only when both endpoints are known code languages of different interop families (so a config/manifest → code reference is untouched).

    • Fix: files whose extractor bailed out for a missing optional dependency no longer vanish without a trace (#1745, thanks @rithyKabir). .sql files (and other extra-gated languages) have a dispatch entry, so the #1689 no-extractor warning can't fire, and extract_sql returns an error result when tree-sitter-sql is absent, so the #1666 zero-node warning skips it too — the graph built "successfully" while an entire SQL corpus contributed nothing. extract() now surfaces these grouped by extension, naming the extra that restores the language (e.g. pip install "graphifyy[sql]").

    Full notes ↗
  5. v0.9.11Jul 9, 2026

    v0.9.11

    • Fix: file enumeration no longer silently drops a directory subtree. detect()'s os.walk had no onerror handler, so an os.scandir failure (a permission error, or a directory created/deleted mid-walk by concurrent writes) was swallowed and that whole subtree vanished from the scan with no log, yielding a silently partial graph.json. The walk now records every skipped directory (surfaced in the result's walk_errors) and warns to stderr, while still enumerating the rest. Relatedly, to_json's anti-shrink guard (#479) now fails safe: a non-empty but unreadable existing graph.json refuses the overwrite (pass force=True to override) instead of silently clobbering a good graph; an empty file still proceeds.

    • Fix: Pascal/Delphi extractors no longer emit duplicate method/contains/inherits edges. A class method declared in the interface section and defined in the implementation section each emitted an edge to the same node, so ~half of a Pascal graph's method edges were doubled (skewing degree/centrality and tripping the new cross-file resolver's god-node guard). Both extractors now dedup edges on (source, target, relation), mirroring the existing node dedup.

    • Fix: Pascal/Delphi call resolution is scoped to the caller's class + inherits chain, and calls to methods inherited across file boundaries now resolve (#1739, thanks @richtext). Both extractors previously resolved every call via a single file-wide {name: node_id} dict, so two unrelated classes with a same-named method (property accessors, generated COM/TLB wrappers) collapsed onto whichever was inserted last, producing wrong cross-class calls edges. Resolution now walks own-class then ancestor chain then file-level free functions, emitting no edge when ambiguous (same god-node guard as the Ruby resolver). A new corpus-wide resolver (graphify/pascal_resolution.py) resolves calls from a descendant to a base-class method declared in a different file (the common generated-base/manual-descendant split). Also stops emitting a duplicate cross-file base-class stub carrying the wrong source_file.

    • Fix: query ranking no longer lets a lone generic term that exact-matches a short leaf label hijack seed selection in multi-term queries (#1602/#1724, thanks @fkhawajagh). _score_nodes scales the per-term exact/prefix tiers by squared term coverage; single-term and full-coverage queries are unchanged.

    • Fix: Kotlin enum entries are extracted as nodes with case_of edges to their enum (#1700, thanks @ivanzhilovich). Closes the Kotlin half of #1700 (the Java half shipped in 0.9.10 via #1719); enum class ChatType { NORMAL, GROUP, SYSTEM } now yields NORMAL/GROUP/SYSTEM nodes and "where is ChatType.X used" works for Kotlin.

    • Fix: SKILL.md's POSIX interpreter probe no longer silently falls back to a graphify-less system python (#1735, thanks @mohammedMsgm). Step 1 ran uv tool run graphifyy python -c ..., but the graphifyy package's executable is graphify, so uv treated python as a missing graphifyy command; 2>/dev/null hid uv's own --from hint, leaving PYTHON on an interpreter without graphify. The probe now runs uv tool run --from graphifyy python -c .... The PowerShell path was already correct.

    • Refactor: decomposed the two largest modules into focused, single-responsibility modules — verbatim moves only, every original import path preserved via re-exports, no behavior change (#1737, thanks @TPAteeq). extract.py 17,054 → 4,740 LOC (the tree-sitter engine, cross-file resolution, shared models, and 23 language extractors moved under graphify/extractors/), __main__.py 5,368 → 673 (install/uninstall + CLI dispatch split into graphify/install.py and graphify/cli.py), export.py 1,671 → 962 (HTML + graph-DB exporters under graphify/exporters/). Full suite unchanged.

    • Fix: merge-graphs gives each input a distinct repo tag so same-stem nodes from different source graphs don't collapse (#1729). Two graphs under a same-named repo dir (src/graphify-out and frontend/src/graphify-out, both → src) shared the src:: prefix, so a backend src/app.js and a frontend App.jsx (both bare app) merged into one node with edges from both — false cross-runtime path results. Colliding tags are now widened (frontend_src) with an index-suffix backstop, and the command prints a note when it disambiguates.

    • Fix: uninstall removes the graphify hook/section from Claude's local-only files too (#1731, thanks @TPAteeq). It now cleans .claude/settings.local.json and both CLAUDE.local.md locations in addition to the standard files, via both graphify uninstall and graphify claude uninstall.

    • Feat: graphify extract --code-only indexes code (local AST, no API key) and skips the doc/paper/image semantic pass, so a mixed repo no longer hard-fails when no LLM backend is configured (#1734). Reports what it skipped; the no-key error now points users at the flag.

  6. v0.9.10Jul 8, 2026

    v0.9.10

    graphify 0.9.10 — a correctness batch focused on phantom cross-file/cross-language edges.

    Highlights: TS/JS builtin-typed receivers (x: Date) no longer collapse onto same-named user symbols; no cross-language calls edges; build_merge ambiguous aliases no longer merge unrelated files; base-class stubs disambiguated per file; Java enum constants extracted; resumable per-chunk semantic cache.

    Install: uv tool install "graphifyy==0.9.10" or pip install graphifyy==0.9.10

    Changelog

    • Fix: TS/JS member calls on a builtin-typed receiver no longer collapse onto a same-named user symbol (#1726). _resolve_typescript_member_calls matched a receiver's type to a definition by casefolded label, so x: Date; x.getTime() bound the caller to a user class DATE/const DATE in another file — inventing hundreds of phantom references edges and a false god node. Builtin-global receiver types (Date, Promise, Map, ...) are now skipped, mirroring the cross-file call guard; genuine user types are unaffected.
    • Fix: never bind a cross-file calls edge to a definition in a different language family (#1718, thanks @edinaldoof). Name-only matching resolved a TSX callback passed by name to a same-named Kotlin method (and a Python call to a Kotlin fun) — phantom edges the spec forbids. Candidates are now filtered by interop family (JVM, native C-family, JS/TS module graph, ...); unknown families stay permissive.
    • Fix: an ambiguous legacy-stem alias in build_merge no longer silently merges two unrelated files (#1713, thanks @mallyskies). The #1504 old-stem alias (ping.h/ping.php → bare ping) resolved by hash-order, riding a dangling edge onto an arbitrary same-named file. Aliases are now committed only when exactly one file claims them; a salted .h/.cpp file node is recognized as its own claimant so a genuine collision stays ambiguous (and dropped) instead of picking a wrong winner.
    • Fix: inline base-class stubs are tagged with origin_file (#1707, thanks @mallyskies). Five inheritance handlers built cross-file base-class stubs without origin_file, so same-named bases across files collapsed onto one shared stub that could then merge with an unrelated real class (218 wrong inherits edges observed). They now route through ensure_named_node, which sets the tag.
    • Fix: Java enum constants are extracted as nodes with case_of edges to their enum (#1719, thanks @ivanzhl). Closes the Java half of #1700; affected ErrorCode / "where is ErrorCode.X used" now works for Java.
    Full notes ↗
  7. v0.9.9Jul 7, 2026

    v0.9.9

    graphify 0.9.9 — reliability + honesty round on top of the 0.9.8 Windows-hooks release.

    Highlights: explain resolves punctuated labels; code files with no AST extractor (R, .ejs, .ets) and unclassifiable files (Dockerfile/Makefile) are now surfaced instead of silently dropped; MATLAB .m no longer garbage-parses through the Objective-C grammar; GRAPH_REPORT.md no longer emits dangling Obsidian wikilinks by default.

    Install: uv tool install "graphifyy==0.9.9" or pip install graphifyy==0.9.9

    Changelog

    • Fix: graphify explain resolves an exactly-typed punctuated label symmetrically against norm_label (#1704). The search term tokenized on \w+ ("blockStream.ts" -> "blockstream ts", space where the '.' was) while a node's stored norm_label keeps punctuation ("blockstream.ts"). The verbatim case was already rescued by the tokenized-label tier, but that broke if a node's label and norm_label diverged; a punctuation-preserving norm_query is now matched against norm_label across the exact/prefix/substring tiers (and fed to the trigram prefilter), so it is robust by construction.
    • Fix: code files with no AST extractor are surfaced instead of silently dropped (#1689, thanks for the precise root-cause). .r/.R (also .ejs, .ets) are in CODE_EXTENSIONS so they are counted as code, but there is no extractor for them, so they produced zero nodes with no warning. extract now prints a grouped warning ("N file(s) are classified as code but graphify has no AST extractor ...: .r (17)"). Adding a real tree-sitter-r extractor remains a follow-up.
    • Fix: the AST-extraction progress line keeps a consistent denominator to the end (#1693). Intermediate lines counted against len(uncached_work) but the final line switched to total_files (which includes cached hits and no-extractor files), so on a large corpus the count appeared to jump upward right after 99%. Both the parallel and sequential final lines now use the uncached_work denominator.
    • Fix: GRAPH_REPORT.md no longer emits dangling [[_COMMUNITY_*]] Obsidian wikilinks by default (#1712). The _COMMUNITY_*.md notes those links target are only created by the opt-in --obsidian export, and the report is written at build time before any export, so on a default run every link dangled (spawning phantom nodes in a vault's graph view, literal brackets elsewhere). The Community Hubs section now renders as plain text by default; the wikilink form is behind an obsidian=True opt-in.
    • Fix: .m files are no longer force-parsed by the Objective-C grammar when they are MATLAB (#1702, thanks @catalystdream for the diagnosis). .m is shared by Objective-C and MATLAB, but the dispatch routed every .m to extract_objc, which turned real MATLAB into garbage nodes/edges. .m is now content-sniffed like .h: a genuine Objective-C .m (with @implementation/@interface/@import/#import) still routes to extract_objc; a MATLAB .m gets no extractor and is surfaced by the #1689 warning rather than mis-parsed. .mm is unchanged (unambiguously Objective-C++). A real tree-sitter-matlab extractor remains a follow-up.
    Full notes ↗
  8. v0.9.8Jul 6, 2026

    v0.9.8

    graphify 0.9.8 — Windows hook fix + reliability round.

    Highlights: the graph-nudge hooks now work on Windows (Claude Code, Codebuddy, Gemini CLI); plus fixes for CLAUDE.md section-write data loss, a tiktoken special-token crash, an Ollama hang, community-labeling robustness + token accounting, discovery-layer file drops, and the deepseek thinking default.

    Install: uv tool install "graphifyy==0.9.8" or pip install graphifyy==0.9.8

    Changelog

    • Fix: the Claude Code / Codebuddy PreToolUse and Gemini CLI BeforeTool graph-nudge hooks now work on Windows (#522). The hooks were inline POSIX bash (case/esac, [ -f ], single-quoted echo), which Windows cmd.exe/PowerShell cannot parse — so on Windows the hook failed silently, no "run graphify query before grepping/reading raw files" context was injected, and users had to invoke /graphify by hand. The detection logic (grep-command match, source-file extension match, skip-if-under-output-dir, graph-exists check) moved into a shell-agnostic graphify hook-guard <search|read> subcommand invoked via the absolute exe path (the same pattern the codex hook already uses), so the hook parses and runs identically on Windows, macOS, and Linux. Behavior on macOS/Linux is unchanged (byte-identical nudge payload); the graph path now also honors GRAPHIFY_OUT. The Gemini BeforeTool hook got the same treatment (graphify hook-guard gemini), which also removes its dependency on a bare python being on PATH. Codex stays a no-op there because Codex Desktop rejects additionalContext.
    • Fix: --update-style section writes to CLAUDE.md/AGENTS.md no longer corrupt or drop content (#1688, thanks @bdfinst). _replace_or_append_section located its managed block by substring (marker in content) and next(... if marker in line), so a heading that appeared as a substring of another line (or duplicate headings) matched the wrong offset and the rewrite could truncate the file. It now matches the section heading exactly (line.strip() == marker), appends when absent, and prefers the last exact match when several exist, so unrelated content is preserved.
    • Fix: token estimation no longer crashes on files containing tiktoken special-token text like <|endoftext|> (#1685, thanks @Kyzcreig). _TOKENIZER.encode(content) raises ValueError by default when the text contains a special token, which aborted packing on docs/corpora that merely mention these strings. Both encode sites now pass disallowed_special=() so such text is tokenized as ordinary bytes.
    • Fix: the Ollama backend no longer multiplies a hang by the retry count (#1686, thanks @Kyzcreig). A stalled local model would wedge for timeout * (max_retries + 1), which with the default 6 retries turned one long stall into a very long one. Ollama now defaults to zero client-side retries (a local model that stalls will not un-stall on retry); set GRAPHIFY_MAX_RETRIES to opt back in. Other backends are unchanged. Note: the underlying stall is non-deterministic and driven by the model server, so this bounds the wait rather than eliminating the hang.
    • Fix: a truncated or slightly malformed community-labeling reply no longer discards the whole batch (#1690, thanks @vdgbcrypto). _parse_label_response now salvages the complete "id": "name" pairs from a reply that failed a strict json.loads (e.g. a reply truncated mid-object), raising only when no pairs can be recovered. The per-batch token budget was also raised (256 + 48*n, was 64 + 24*n) to give models that prepend a short preamble enough headroom to finish the JSON. The exact provider truncation in the report could not be reproduced without a live key; the parser and budget fixes address the mechanism.
    Full notes ↗
  9. v0.9.7Jul 6, 2026

    graphify 0.9.7

    graphify 0.9.7 — 17 fixes and features since 0.9.6.

    Ruby

    • include/extend/prepend <Module> now emits mixes_in edges (#1668), so Rails concern composition is visible to affected.
    • affected <Class> reaches callers that bind to a class's method nodes (#1669), seeding the reverse walk from the root's members (one method/contains hop).

    Extraction

    • Extensionless shebang CLIs (devctl, manage) are now extracted instead of silently dropped (#1683, @Stashub).
    • JS/TS rationale comments (// NOTE:) and ADR/RFC citations become rationale/doc_ref nodes, matching Python (#1599, @niltonmourafilho-arch).
    • Java standard-library types (String, List, Optional, ...) no longer emitted as references noise (#1603, @NydiaChung).
    • New pascal optional extra for AST-quality Delphi extraction (#1616, @vinicius-l-machado).
    • JS/TS calls with no local definition and no import no longer bind to a same-named export in an unrelated package (#1659, @leonaburime-ucla).
    • Case-insensitive file-extension dispatch, so App.PY/script.JS are no longer skipped (#1671, @raman118).
    Full notes ↗
  10. v0.9.6Jul 4, 2026

    graphify 0.9.6

    graphify 0.9.6 — 19 fixes and features since 0.9.5.

    Ruby (major)

    • Module / Struct.new / Class.new / Data.define container nodes (#1640). Plain module Foo, Foo = Struct.new(...) do ... end, Foo = Class.new(StandardError), and Result = Data.define(...) now get real container nodes with their methods attached; Class.new(Super) emits an inherits edge.
    • Constant-receiver singleton-call resolution (#1634). Service.call, Model.where, SomeJob.perform_async now resolve cross-file, so Rails/Zeitwerk apps (no requires) get real cross-file edges instead of near-zero. Binds to the class's owned singleton method when present, else the class node for blast-radius; single-owning-class guard kept.

    Correctness and stability

    • No more cross-language phantom import edges (#1638). An unresolved bare npm import (import x from "pkg/colors") no longer aliases onto an unrelated local colors.py via the build alias index.
    • Semantic extraction hardened (#1631). A malformed LLM chunk (a stray non-dict entry) no longer crashes the merge and discards every successful chunk.
    • Deterministic graph.json ordering (#1632). Parallel semantic backends now merge chunks in submission order, so node/edge ordering is stable run-to-run (the model's content variance is separate).

    Contributor extractor fixes

    • Apex interface multiple inheritance (interface X extends A, B) (#1645, @Synvoya).
    • Kotlin interface delegation (class Foo : Bar by baz) (#1644, @Synvoya).
    Full notes ↗
  11. v0.9.5Jul 2, 2026

    v0.9.5

    graphify 0.9.5

    pip install -U graphifyy==0.9.5

    Correctness (two 0.9.4 regressions + a false-hub fix)

    • Cross-file indirect_call now resolves via the graphify extract CLI, not just the extract() API — a 0.9.4 regression where the callable-target guard went stale after id relativization dropped every cross-file indirect edge on the CLI path.
    • graphify cluster-only no longer reuses stale community labels after the graph changed — a re-scoped/re-clustered graph kept old labels on a different community set. It now writes per-community membership signatures and hub-relabels changed communities with a warning.
    • Cross-file name resolution respects case in case-sensitive languages (#1581) — from pathlib import Path no longer resolves to a shell export PATH=... node (one variable had become the #1 god-node with 266 false edges). Only PHP/SQL/Nim still fold.

    Language extractor fixes (thanks @Synvoya, @jerryliurui)

    Ruby & Groovy inheritance edges; Elixir multi-alias imports; Fortran function calls; Rust enum-variant + tuple-struct references; Julia qualified/relative/scoped imports; SystemVerilog qualified fields; Scala var fields; PowerShell class base types; ObjC protocol-to-protocol adoption; PHP promoted constructor properties; C# auto-properties; C++ base-class template args; Swift enum associated values; and Swift singleton-into-local resolution (let x = Type.shared; x.method(), #1604).

    Incremental --update data fixes

    Hyperedges from unchanged files no longer dropped (#1574); deleted files no longer leave ghost nodes, with symlinked-root hardening (#1571).

    Full notes ↗
  12. v0.9.4Jul 1, 2026

    v0.9.4

    graphify 0.9.4

    Published to PyPI: pip install -U graphifyy==0.9.4

    Blast-radius / call-graph

    • indirect_call dispatch — a function referenced by name is now a first-class dependency graphify affected traverses: call arguments (submit(fn), Thread(target=fn), map(fn, xs)), cross-file/imported callbacks, dispatch tables (ROUTES = {"x": fn}, [a, b]), assignment/return aliases (cb = fn, return fn), getattr(obj, "name") reflective dispatch, and JS/TS (call args, object/array tables, Express-style app.get("/", h)). Kept as a distinct INFERRED relation so strict calls queries stay precise. (#1565, #1566, #1569, #1575)

    Incremental --update data fixes

    • Hyperedges from unchanged files are no longer dropped on every incremental update. (#1574)
    • Deleted files no longer leave ghost nodes — prune matching relativizes absolute paths against the scan root (with symlinked-root hardening) even when a caller omits root. (#1571)

    Language coverage

    • Ruby class inheritance emits inherits edges. (#1535)
    • Groovy extends/implements emit inherits/implements edges. (#1534)
    Full notes ↗

Full release history: github.com/safishamsi/graphify/releases ↗