Releases: Graphify-Labs/graphify
Release list
v0.9.12
-
Fix: live PostgreSQL introspection (
--postgres) now emits foreign-keyreferencesedges under a read-only role (#1746, thanks @rithyKabir). The FK query readinformation_schema.referential_constraints, which is privilege-filtered — a role with only SELECT sees zero FK rows while tables/views/routines still appear, so everyreferencesedge silently vanished. It now reads the world-readablepg_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 viaUNNEST ... WITH ORDINALITY. -
Fix:
json_configno longer emitsimports/extendsedges to node IDs it never creates (#1764, thanks @oleksii-tumanov).package.jsondependencies andtsconfig.jsonextends/$reftargets produced edges whose endpoint node was absent, sobuild_from_jsonsilently 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 aconceptnode before adding the edge. -
Fix:
graphify updateno 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 firstupdateafter 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()wheregw: PaymentGatewaynow binds toPaymentGateway.charge, not a same-namedAuditLog.chargein another file. Explicit-type receivers andthisare 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/--graphpoint elsewhere (#1747, thanks @bbqboogiedwonsen).extract <corpus> --out <dir>correctly wrote the graph to<dir>butdetect()'s word-count/stat-index cache still created a straygraphify-out/cache/inside the corpus (it uses the scan root); it now honors the--outdir via a threadedcache_root. Andcluster-only --graph <elsewhere>/graphify-out/graph.jsonwroteGRAPH_REPORT.md/labels/analysis/re-clustered graph to the CWD instead of beside the input; it now writes beside--graphwhen that graph lives in agraphify-out/dir, while still restoring into the CWD for an archivedbackup/graph.json(#934). -
Fix:
imports/referencesedges no longer bind across a language boundary (#1749, thanks @philberndt). The spec already forbids cross-languagecalls, but an unresolved Pythonimport timecould still resolve by bare stem onto asrc/time.tsfile 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, inflatingtime.tsbetweenness ~90x and making it the #1 "god node"). The build-time cross-language guard now coversimports/imports_from/referencesin addition tocalls, 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).
.sqlfiles (and other extra-gated languages) have a dispatch entry, so the #1689 no-extractor warning can't fire, andextract_sqlreturns an error result whentree-sitter-sqlis 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]"). -
Fix:
build_from_jsonis deterministic across process runs again (#1753, thanks @erasmust-dotcom). The ghost-node merge iteratedset(G.nodes()), so which node survived a(basename, label)collision depended on CPython's per-process string-hash seed — rebuilding the same extraction JSON in a fresh process could silently pick a different canonical id (breaking the cluster→relabel workflow with aKeyErroron an id that vanished). The Pass 1/Pass 2 loops now iterate in sorted order. Additionally, two non-AST (semantic) nodes sharing a key but from different files are now treated as distinct concepts and both survive (mirroring the AST/AST ambiguity guard #1257) instead of one arbitrarily merging away; a genuine same-file duplicate still collapses. -
Fix: a Java field/parameter/return-type reference to a class whose simple name is shared by two modules no longer dangles on a sourceless phantom node (#1744, thanks @aviciot). Both same-named classes already survive as distinct path-scoped nodes, but the cross-module
referencesedge was left pointing at a bare no-source stub because_resolve_java_type_referencesre-pointedimplements/inherits/importsbut notreferences— so a query about the referenced class could miss it. The Java resolver now disambiguatesreferencesby the importing file'simportstatement (falling back to same-package), mirroring the C# resolver, and drops the orphaned phantom.
v0.9.11
-
Fix: file enumeration no longer silently drops a directory subtree.
detect()'sos.walkhad noonerrorhandler, so anos.scandirfailure (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 partialgraph.json. The walk now records every skipped directory (surfaced in the result'swalk_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 existinggraph.jsonrefuses the overwrite (passforce=Trueto override) instead of silently clobbering a good graph; an empty file still proceeds. -
Fix: Pascal/Delphi extractors no longer emit duplicate
method/contains/inheritsedges. 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-classcallsedges. 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 wrongsource_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_nodesscales 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_ofedges 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 thegraphifyypackage's executable isgraphify, so uv treatedpythonas a missinggraphifyycommand;2>/dev/nullhid uv's own--fromhint, leavingPYTHONon an interpreter without graphify. The probe now runsuv 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.py17,054 → 4,740 LOC (the tree-sitter engine, cross-file resolution, shared models, and 23 language extractors moved undergraphify/extractors/),__main__.py5,368 → 673 (install/uninstall + CLI dispatch split intographify/install.pyandgraphify/cli.py),export.py1,671 → 962 (HTML + graph-DB exporters undergraphify/exporters/). Full suite unchanged. -
Fix:
merge-graphsgives 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-outandfrontend/src/graphify-out, both →src) shared thesrc::prefix, so a backendsrc/app.jsand a frontendApp.jsx(both bareapp) merged into one node with edges from both — false cross-runtimepathresults. Colliding tags are now widened (frontend_src) with an index-suffix backstop, and the command prints a note when it disambiguates. -
Fix:
uninstallremoves the graphify hook/section from Claude's local-only files too (#1731, thanks @TPAteeq). It now cleans.claude/settings.local.jsonand bothCLAUDE.local.mdlocations in addition to the standard files, via bothgraphify uninstallandgraphify claude uninstall. -
Feat:
graphify extract --code-onlyindexes 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.
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_callsmatched a receiver's type to a definition by casefolded label, sox: Date; x.getTime()bound the caller to a userclass DATE/const DATEin another file — inventing hundreds of phantomreferencesedges 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
callsedge 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_mergeno longer silently merges two unrelated files (#1713, thanks @mallyskies). The#1504old-stem alias (ping.h/ping.php→ bareping) 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/.cppfile 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 withoutorigin_file, so same-named bases across files collapsed onto one shared stub that could then merge with an unrelated real class (218 wronginheritsedges observed). They now route throughensure_named_node, which sets the tag. - Fix: Java enum constants are extracted as nodes with
case_ofedges to their enum (#1719, thanks @ivanzhl). Closes the Java half of #1700;affected ErrorCode/ "where is ErrorCode.X used" now works for Java. - Fix:
graphifyrebuilds recover from a deleted hook working directory instead of crashing (#1703, thanks @FranciscoJSBarragan). A detached git hook can inherit a CWD that no longer exists; the rebuild now recovers viaGRAPHIFY_REPO_ROOTor fails cleanly instead of raisingFileNotFoundError. - Feat: the semantic cache is checkpointed per chunk so an interrupted extraction resumes instead of restarting (#1715, thanks @A-Levin). Each completed chunk is unioned into the cache immediately (opt out with
GRAPHIFY_NO_INCREMENTAL_CACHE); the final write still overwrites authoritatively. - Docs:
SECURITY.mdno longer claims stdio-only now that an opt-in--transport http(binds127.0.0.1by default) exists (#1714, thanks @Thizeidler); added tests forGRAPHIFY_MAX_GRAPH_BYTESparsing and corrected its unit docstring to binary MiB/GiB (#1722, thanks @Cekaru).
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 explainresolves an exactly-typed punctuated label symmetrically againstnorm_label(#1704). The search term tokenized on\w+("blockStream.ts" -> "blockstream ts", space where the '.' was) while a node's storednorm_labelkeeps punctuation ("blockstream.ts"). The verbatim case was already rescued by the tokenized-label tier, but that broke if a node'slabelandnorm_labeldiverged; a punctuation-preservingnorm_queryis now matched againstnorm_labelacross 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 inCODE_EXTENSIONSso they are counted as code, but there is no extractor for them, so they produced zero nodes with no warning.extractnow prints a grouped warning ("N file(s) are classified as code but graphify has no AST extractor ...: .r (17)"). Adding a realtree-sitter-rextractor 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 tototal_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 theuncached_workdenominator. - Fix:
GRAPH_REPORT.mdno longer emits dangling[[_COMMUNITY_*]]Obsidian wikilinks by default (#1712). The_COMMUNITY_*.mdnotes those links target are only created by the opt-in--obsidianexport, 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 anobsidian=Trueopt-in. - Fix:
.mfiles are no longer force-parsed by the Objective-C grammar when they are MATLAB (#1702, thanks @catalystdream for the diagnosis)..mis shared by Objective-C and MATLAB, but the dispatch routed every.mtoextract_objc, which turned real MATLAB into garbage nodes/edges..mis now content-sniffed like.h: a genuine Objective-C.m(with@implementation/@interface/@import/#import) still routes toextract_objc; a MATLAB.mgets no extractor and is surfaced by the #1689 warning rather than mis-parsed..mmis unchanged (unambiguously Objective-C++). A realtree-sitter-matlabextractor remains a follow-up. - Fix: the
/graphifyusage comment in the skill files no longer claims a bare/graphifyproduces an Obsidian vault by default (#1681, thanks for the audit). It now reads "full pipeline on current directory (HTML viz; add--obsidianfor a vault)", matching Step 6. Fixed at the skillgen source so every generatedskill-*.mdvariant carries the corrected comment. - Feat: files graphify sees but cannot classify are surfaced instead of vanishing (#1692). Extensionless, non-shebang project files (Dockerfile, Gemfile, Makefile, Rakefile, LICENSE, ...) and unsupported extensions previously left no trace at all.
detectnow collects them into anunclassifiedlist, andgraphify extractreports "N file(s) not classified (no supported extension or shebang), skipped: ...". Actually extracting Dockerfile/Makefile-style content remains a follow-up.
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
PreToolUseand Gemini CLIBeforeToolgraph-nudge hooks now work on Windows (#522). The hooks were inline POSIX bash (case/esac,[ -f ], single-quotedecho), which Windows cmd.exe/PowerShell cannot parse — so on Windows the hook failed silently, no "rungraphify querybefore grepping/reading raw files" context was injected, and users had to invoke/graphifyby hand. The detection logic (grep-command match, source-file extension match, skip-if-under-output-dir, graph-exists check) moved into a shell-agnosticgraphify 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 honorsGRAPHIFY_OUT. The GeminiBeforeToolhook got the same treatment (graphify hook-guard gemini), which also removes its dependency on a barepythonbeing on PATH. Codex stays a no-op there because Codex Desktop rejectsadditionalContext. - Fix:
--update-style section writes toCLAUDE.md/AGENTS.mdno longer corrupt or drop content (#1688, thanks @bdfinst)._replace_or_append_sectionlocated its managed block by substring (marker in content) andnext(... 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)raisesValueErrorby default when the text contains a special token, which aborted packing on docs/corpora that merely mention these strings. Bothencodesites now passdisallowed_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); setGRAPHIFY_MAX_RETRIESto 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_responsenow salvages the complete"id": "name"pairs from a reply that failed a strictjson.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, was64 + 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. - Fix: cluster-only mode now reports the real token cost of community labeling instead of a hardcoded zero (#1694, thanks @sub4biz). The labeling LLM calls were never accounted for, so
GRAPH_REPORT.md's "Token cost" line always read0 input · 0 outputin cluster-only runs._call_llmnow accumulates per-response usage into an optional accumulator that is threaded through the labeling path and surfaced in the report. Backends that do not return usage (the Claude Code CLI) still contribute nothing, which is honest rather than estimated. - Docs/Feat:
deepseek-v4-flash(andv4-pro) have thinking ENABLED by default; graphify no longer implies otherwise and adds an opt-inGRAPHIFY_DISABLE_THINKING=1toggle (#1621, thanks @sub4biz for the empirical testing). Disabling thinking removes a rare reasoning-leak failure mode (which the adaptive extraction/labeling retry already recovers from) but, measured on real corpora, trades it for more frequent benign truncation and measurably lower extraction quality and file coverage — so it stays a documented user choice rather than a forced default. The stale "non-thinking" comment on the built-in deepseek config is corrected. The moonshot (kimi) branch is unchanged (it must disable thinking or content comes back empty). - Fix: source files are no longer silently dropped during discovery by two over-broad filters (#1666, thanks @krishnateja7 for the precise root-cause). (a) A bare
snapshots/directory was pruned as a Jest/Vitest artifact, which killed legitimate code namespaces like a Railsapp/services/snapshots/; it is now pruned only when it actually contains.snapfiles or sits directly under a JS test root (__snapshots__stays unconditionally pruned). (b)_is_sensitivedropped files on a bare name-keyword hit (device_token.rb,passwords_controller.rb) even whenclassify_filehad already resolved them to source code; a genuine programming-language source file is now exempt from the weak keyword heuristic, while real secret stores in data/config formats (credentials.json,secrets.yaml,.env,.pem, ...) are still caught. This is the discovery-layer fix; the 0.9.7 no-cache-on-empty change could not surface these because the files never reached extraction.
graphify 0.9.7
graphify 0.9.7 — 17 fixes and features since 0.9.6.
Ruby
include/extend/prepend <Module>now emitsmixes_inedges (#1668), so Rails concern composition is visible toaffected.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 becomerationale/doc_refnodes, matching Python (#1599, @niltonmourafilho-arch). - Java standard-library types (
String,List,Optional, ...) no longer emitted asreferencesnoise (#1603, @NydiaChung). - New
pascaloptional 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.JSare no longer skipped (#1671, @raman118).
Incremental / detect
- A modified
.docx/.xlsxnow re-enters--update(#1649, @Ns2384-star). - Windows long paths (>260 chars) are now hashed (#1655, @Ns2384-star).
- Word counts are cached against each file's stat signature, so unchanged PDFs/docx are not re-parsed on every run (#1656, @Ns2384-star).
- An extractable source file that produces zero nodes is no longer cached and is surfaced with a warning (#1666, @krishnateja7).
Windows / reports / misc
- Windows skill declares
name: graphify(folder-name rule) (#1635, @ray8875); OpenCode plugin uses;not&&for PowerShell 5.1 (#1646, @gonaik);GRAPH_REPORT.mdImport Cycles section only shown for code corpora (#1657, @Ns2384-star). - Virtual PostgreSQL URI no longer backslash-mangled on Windows (#1672, @raman118); deferred
import()no longer reported as a file cycle (#1241, @Synvoya).
See the CHANGELOG for full detail. Thanks to everyone who filed issues and sent PRs.
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), andResult = 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_asyncnow 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 localcolors.pyvia 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).
Plus fixes to query seed diversity (#1596), cluster-only sidecar (#1617), update/watch stale-source reconciliation (#1623), TS/JS extractor gaps (#1615/#1607), merge-graphs (#1606), Swift singleton locals (#1604), C# receiver-typed member calls (#1609), TS receiver-typed member calls (#1630), the to_canvas dangling-member crash (#1236), a root-source_file crash (#1618), the claude-cli backend bisection stall, symlink containment (#1613), and the Homebrew python@3.x interpreter path (#1586).
See the CHANGELOG for full detail.
Thanks to everyone who filed issues and sent PRs. Follow the org at https://github.com/Graphify-Labs.
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_callnow resolves via thegraphify extractCLI, not just theextract()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-onlyno 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 Pathno longer resolves to a shellexport 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).
Robustness & tooling
merge-graphs tolerates mixed directed/multigraph inputs (#1606); corrupt graph.json gives an actionable error (#1537/#1536); cross-chunk node-ID collisions warn instead of silently dropping (#1508); the skill accepts Homebrew python@3.x interpreters (#1586); git-hook foreground stalls eliminated (#1601); direction-aware skill-version warning (#1568); deterministic hub community labels; Windows hook worker limit (#1554).
Serve / MCP
Question/filler stopwords dropped from query terms (#1597); optional project_path lets one MCP server back a whole workspace of projects (#1594).
Thanks to all contributors: @sheik-hiiobd, @Synvoya, @jerryliurui, @AdrianRusan, @SUDARSHANCHAUDHARI, @socar-tender, @goodjira, @TPAteeq, @guyoron1, @nuthalapativarun, @matiasduartee, @joanfgarcia, and the #1601/#1597 authors.
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 affectedtraverses: 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-styleapp.get("/", h)). Kept as a distinct INFERRED relation so strictcallsqueries 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
inheritsedges. (#1535) - Groovy
extends/implementsemitinherits/implementsedges. (#1534)
Robustness & UX
- Corrupt
graph.jsonraises a clear, actionable error instead of a traceback. (#1537/#1536) - Cross-chunk node-ID collisions warn (naming both files) instead of silently dropping a node. (#1508/#1504)
- Skill-version mismatch warning is direction-aware — a newer-than-package skill is told to upgrade the package, not to run
install(which would downgrade it). (#1568) - Deterministic hub community labels: no-backend runs read
auth/log_actioninstead ofCommunity 70. (#1576) - Git hooks on Windows/MSYS default to sequential rebuilds. (#1554)
Thanks to @sheik-hiiobd, @Synvoya, @socar-tender, @goodjira, @TPAteeq, @guyoron1, @nuthalapativarun, @matiasduartee for reports and contributions.
v0.9.3
Cross-file member-call resolution for C++/Objective-C (#1547/#1556) and namespace-aware C# type resolution (#1562), the work-memory overlay (#1441), and a batch of TS/JS/ObjC resolution + call-graph fixes.
- Feat: cross-file member-call resolution for C++ and Objective-C (#1547, #1556). A class declared in a header and defined in its
.cpp/.mno longer fragments into two nodes (a decl/def merge pass collapses the sibling header/impl pair, gated to same-directory same-name so unrelated classes never merge), and a member call now resolves across files by the receiver's inferred type: C++Foo f; f.bar()/Foo::bar()/this->bar()and ObjCFoo *f = [[Foo alloc] init]; [f doThing]/[self render]link to the owning class's method. Resolution is by receiver type, never bare name, with the single-definition god-node guard — an uninferable or ambiguous receiver produces no edge (high precision over recall, grounded in how compiler-free indexers like ctags/Doxygen mis-resolve by name). Also routes C++ headers to the C++ extractor and ObjC#importbridging headers to the ObjC extractor. Reported by @c0dezer019 and @JabberYQ. (Residual cross-file#includeedge resolution under symlinked roots and ObjC dynamic-dispatch receivers remain follow-ups.) - Feat: namespace-aware C# cross-file type resolution (#1562, thanks @TheFedaikin). The namespace is folded into the C# node id (so same-named types in different namespaces stay distinct),
usingdirectives are honored with lexical per-block scope, and qualified references (Namespace.Type,usingaliases) resolve — disambiguating a bare reference to the one in-scope namespace that provides it, and refusing (no edge) when ambiguous. Advances the #1318 shadow-node umbrella for C#. - Fix: test mocks no longer erase the real cross-file call graph (#1553, thanks @Schweinehund). When a bare callee name had 2+ definitions without unique import evidence, the god-node guard dropped the edge entirely — so a single same-named test mock wiped the real call graph (a 76-stub Pester suite erased everything). The guard now applies tie-breakers — non-test preference (a shared, segment-aware path classifier) then path proximity — and resolves only when exactly one candidate survives, else still bails. A real def plus a test mock resolves to the real def; two genuine non-test defs still bail (no fan-out).
- Fix: hyperedge member lists keyed
membersornode_idsare now accepted, not silently dropped (#1561, thanks @askalot-io). Normalized to the canonicalnodesat ingest (in build_from_json and semantic_cleanup), deduped, with a warning — mirroring the existing from/to edge-endpoint aliasing. - Feat: work-memory overlay —
graphify reflectnow projects the verdicts it distills (preferred / tentative / contested, recency-weighted) into a.graphify_learning.jsonsidecar next to graph.json, andgraphify explain/query/GRAPH_REPORT.md/ the HTML viewer surface them where you look (aLesson:hint, a colored node ring). Builds on the idea in #1441/#1542 (thanks @TPAteeq), implemented as a sidecar rather than stamping graph.json: structural truth stays separate (nolearning_*in graph.json or GraphML exports, no rebuild churn). Each verdict carries the source questions that produced it (provenance) and a content fingerprint of the cited code, so a verdict on a file that has changed since is flagged "code changed — re-verify" instead of shown as still-authoritative. Dead-ends stay query-scoped (a report section, never a node attribute). Letting verdicts influence query traversal is deliberately deferred (it needs propensity correction + exploration to avoid a self-reinforcing feedback loop). - Feat: type-aware
this.field.method()resolution for TypeScript/JS (#1316, thanks @guyoron1). A member call through a constructor-injected dependency (constructor(private db: Database)thenthis.db.query()) now produces acallsedge to the field type's method, resolved by the field's declared type and gated by the single-definition god-node guard (an ambiguous or untyped field produces no edge — no global name-match fan-out). EXTRACTED confidence; constructor parameter-property injection scope. - Feat: resolve TypeScript wildcard path aliases (#1544, thanks @oleksii-tumanov). A
compilerOptions.pathspattern like@app/*or@*/interfacesnow captures the matched segment and substitutes it into each target in order, honoring tsc's longest-prefix / exact-wins specificity, baseUrl, and the first-existing-target fallback. Extends the #1531 resolver. - Feat: resolve JS namespace re-export bindings (#1552, thanks @oleksii-tumanov).
export * as ns from './mod'now creates a real symbol node forns, registers it as a named export (so a downstreamimport { ns }resolves to it), and emits a file-levelre_exportsedge — treated as a single opaque binding, sons.memberaccesses don't fan out into false per-symbol edges. Includes cycle and deep-chain guards. - Feat: Objective-C dot-syntax property accesses and
@selector()call edges (#1475, #1543, thanks @guyoron1).self.product.namenow emits anaccessesedge and@selector(method)acallsedge, each resolved only to an unambiguous in-scope definition by exact method-id match (a sibling of the same class for dot-syntax; exactly one method by exact selector name for@selector) — soself.namecan't mis-resolve to a-surnamesibling and same-named methods across classes don't fan out. Completes the #1475 ObjC follow-ups.