Skip to content

Releases: Graphify-Labs/graphify

v0.9.12

Choose a tag to compare

@safishamsi safishamsi released this 10 Jul 10:42
  • 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]").

  • Fix: build_from_json is deterministic across process runs again (#1753, thanks @erasmust-dotcom). The ghost-node merge iterated set(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 a KeyError on 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 references edge was left pointing at a bare no-source stub because _resolve_java_type_references re-pointed implements/inherits/imports but not references — so a query about the referenced class could miss it. The Java resolver now disambiguates references by the importing file's import statement (falling back to same-package), mirroring the C# resolver, and drops the orphaned phantom.

v0.9.11

Choose a tag to compare

@safishamsi safishamsi released this 09 Jul 00:31
  • 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.

v0.9.10

Choose a tag to compare

@safishamsi safishamsi released this 08 Jul 10:50

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.
  • Fix: graphify rebuilds 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 via GRAPHIFY_REPO_ROOT or fails cleanly instead of raising FileNotFoundError.
  • 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.md no longer claims stdio-only now that an opt-in --transport http (binds 127.0.0.1 by default) exists (#1714, thanks @Thizeidler); added tests for GRAPHIFY_MAX_GRAPH_BYTES parsing and corrected its unit docstring to binary MiB/GiB (#1722, thanks @Cekaru).

v0.9.9

Choose a tag to compare

@safishamsi safishamsi released this 07 Jul 15:15

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.
  • Fix: the /graphify usage comment in the skill files no longer claims a bare /graphify produces an Obsidian vault by default (#1681, thanks for the audit). It now reads "full pipeline on current directory (HTML viz; add --obsidian for a vault)", matching Step 6. Fixed at the skillgen source so every generated skill-*.md variant 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. detect now collects them into an unclassified list, and graphify extract reports "N file(s) not classified (no supported extension or shebang), skipped: ...". Actually extracting Dockerfile/Makefile-style content remains a follow-up.

v0.9.8

Choose a tag to compare

@safishamsi safishamsi released this 06 Jul 15:52

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.
  • 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 read 0 input · 0 output in cluster-only runs. _call_llm now 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 (and v4-pro) have thinking ENABLED by default; graphify no longer implies otherwise and adds an opt-in GRAPHIFY_DISABLE_THINKING=1 toggle (#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 Rails app/services/snapshots/; it is now pruned only when it actually contains .snap files or sits directly under a JS test root (__snapshots__ stays unconditionally pruned). (b) _is_sensitive dropped files on a bare name-keyword hit (device_token.rb, passwords_controller.rb) even when classify_file had 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

Choose a tag to compare

@safishamsi safishamsi released this 06 Jul 00:33

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).

Incremental / detect

  • A modified .docx/.xlsx now 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.md Import 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

Choose a tag to compare

@safishamsi safishamsi released this 04 Jul 11:56

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).

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

Choose a tag to compare

@safishamsi safishamsi released this 02 Jul 12:22

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).

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

Choose a tag to compare

@safishamsi safishamsi released this 01 Jul 10:23

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)

Robustness & UX

  • Corrupt graph.json raises 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_action instead of Community 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

Choose a tag to compare

@safishamsi safishamsi released this 30 Jun 19:40

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/.m no 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 ObjC Foo *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 #import bridging headers to the ObjC extractor. Reported by @c0dezer019 and @JabberYQ. (Residual cross-file #include edge 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), using directives are honored with lexical per-block scope, and qualified references (Namespace.Type, using aliases) 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 members or node_ids are now accepted, not silently dropped (#1561, thanks @askalot-io). Normalized to the canonical nodes at 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 reflect now projects the verdicts it distills (preferred / tentative / contested, recency-weighted) into a .graphify_learning.json sidecar next to graph.json, and graphify explain / query / GRAPH_REPORT.md / the HTML viewer surface them where you look (a Lesson: 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 (no learning_* 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) then this.db.query()) now produces a calls edge 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.paths pattern like @app/* or @*/interfaces now 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 for ns, registers it as a named export (so a downstream import { ns } resolves to it), and emits a file-level re_exports edge — treated as a single opaque binding, so ns.member accesses 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.name now emits an accesses edge and @selector(method) a calls edge, 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) — so self.name can't mis-resolve to a -surname sibling and same-named methods across classes don't fan out. Completes the #1475 ObjC follow-ups.