Releases: Graphify-Labs/graphify
Release list
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 updateno longer silently evicts nodes for files that are merely newly-ignored but still on disk (#1795), andbuild_mergeno longer drops a re-extracted file passed inprune_sources(#1796). - Markdown files no longer split into duplicate document nodes across the quick-scan and semantic passes (#1799).
- New language coverage: Ruby
.rakefiles (#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).
querylogwrote everyquery/path/explainquestion and corpus path (and full responses ifGRAPHIFY_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 withGRAPHIFY_QUERY_LOG_ENABLE=1(default path) orGRAPHIFY_QUERY_LOG=<path>;GRAPHIFY_QUERY_LOG_DISABLE=1still 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>_docwhile 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_jsonnow merges the bare quick-scan node into the semantic_docnode when both share the samesource_fileand arefile_type: document, consolidating their edges/hyperedges onto one node. Gated so an unrelated code symbolfooandfoo_docnever merge. -
Fix: incremental
graphify updateno longer silently evicts nodes for a file that left the scan corpus but still exists on disk (#1795, thanks @CJNA)._reconcile_existing_graphread "source absent from the collected corpus" as "deleted", but that's also what an ignore-rule/filter change looks like (e.g. an upgrade that starts honoring.gitignore) — in one 27k-node graph the first rebuild after such an upgrade mass-evicted 655 nodes whose files were present the whole time. Eviction now fails closed: a corpus-absent source is only evicted whenPath(identity).exists()is False (true deletion), otherwise its nodes/edges/hyperedges are preserved and a loud line reports how many were kept and why. True deletions and renames evict as before; a fullextract --forcestill purges deliberate exclusions. -
Fix:
build_mergeno longer silently deletes a re-extracted file's fresh nodes when that file is also passed inprune_sources(#1796, thanks @erichkusuki). A file present innew_chunksis being replaced, not deleted, so it's now excluded from the prune set — "replace" wins over a contradictory "delete" of the same source. Previously, following the old edit-workflow (pass the changed file inprune_sources) deleted the just-built concept whenever an edit kept a node's label. Genuine deletions (a file inprune_sourcesbut notnew_chunks) still prune. -
Fix:
graphify pathresolves each endpoint to the first candidate whose label contains every query token, instead of blindly taking the top-scored node (#1785, thanks @CJNA)._score_nodes' full-query bonus only fires when the query equals/prefixes a label, so a query that is a token subset of the intended label ("Reject-everything judge"vs"Degenerate Reject-Everything Judge") got no bonus and a node prefix-matching one rare token could outscore it — anchoring the path on an unrelated, often disconnected node and yielding a false "No path found". When the top candidate already full-matches (the common case) the pick is unchanged. Applied to both thepathCLI and the MCP shortest-path tool; the close-runner-up ambiguity warning now fires only when the score head is what was actually picked. -
Fix: the report's "Suggested Questions" weakly-connected-node count now matches its "Knowledge Gaps" count (#1768, thanks @balloon72).
suggest_questions()omitted thefile_type != "rationale"filter thatreport.py's Knowledge Gaps section applies, so the sameGRAPH_REPORT.mdshowed two different numbers for the same concept (e.g. 757 vs 245), making a healthy graph look like it had a major documentation gap. Both computations now use the same filter. -
Fix: Bash scripts that run each other by execution now get a cross-file edge (#1756, thanks @balloon72).
extract_bashonly linkedsource x.sh/. x.sh; the two most common forms —bash x.shand./x.sh— produced no edge, so execution topology was missing. They now emit acallsedge (contextscript_invocation) to the invoked script's entry node when the target resolves to a real file on disk (script runnersbash/sh/zsh/ksh/dashand bare./x.sh), skipping missing or shadowed targets. -
Fix: Ruby
.rakefiles are now extracted and participate in Ruby cross-file resolution like.rb(#1784, thanks @krishnateja7)..rakeis plain Ruby but the extension was gated out of seven places (classification, extractor dispatch, the language-name/family maps, theruby_member_callsresolver's suffix set, both.rb-suffix filters inruby_resolution.py, and the build repo-tag map), so every rake task was skipped and its calls were invisible. All seven now include.rake;Widget.tallyfrom a.raketask resolves to its.rbdefinition. -
Fix: cross-module references to a function now resolve to its definition instead of dangling on a name-only stub (#1781, thanks @EmilNyg).
_rewire_unique_stub_nodesgated merge targets through_is_type_like_definition, which rejects any label ending in)— so function/method defs could never absorb their reference stubs, and "who references this function" returned nothing on the definition node while a sourceless stub held all the edges. Top-level function defs are now eligible rewire targets when the label match is globally unique, gated by a language-family match with the referrers (a Pythonget_dbreference can't bind to a unique Goget_db()) and excluding stubs used as a supertype (inherits/implements/extends— you don't inherit from a function). Types are unchanged.
Install
uv tool install --upgrade graphifyy # or: pipx upgrade graphifyyv0.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.