Skip to content

Releases: Graphify-Labs/graphify

v0.9.13

Choose a tag to compare

@safishamsi safishamsi released this 12 Jul 10:17

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

Highlights:

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

Fixes

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

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

  • Fix: incremental graphify update no longer silently evicts nodes for a file that left the scan corpus but still exists on disk (#1795, thanks @CJNA). _reconcile_existing_graph read "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 when Path(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 full extract --force still purges deliberate exclusions.

  • Fix: build_merge no longer silently deletes a re-extracted file's fresh nodes when that file is also passed in prune_sources (#1796, thanks @erichkusuki). A file present in new_chunks is 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 in prune_sources) deleted the just-built concept whenever an edit kept a node's label. Genuine deletions (a file in prune_sources but not new_chunks) still prune.

  • Fix: graphify path resolves 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 the path CLI 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 the file_type != "rationale" filter that report.py's Knowledge Gaps section applies, so the same GRAPH_REPORT.md showed 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_bash only linked source x.sh / . x.sh; the two most common forms — bash x.sh and ./x.sh — produced no edge, so execution topology was missing. They now emit a calls edge (context script_invocation) to the invoked script's entry node when the target resolves to a real file on disk (script runners bash/sh/zsh/ksh/dash and bare ./x.sh), skipping missing or shadowed targets.

  • Fix: Ruby .rake files are now extracted and participate in Ruby cross-file resolution like .rb (#1784, thanks @krishnateja7). .rake is plain Ruby but the extension was gated out of seven places (classification, extractor dispatch, the language-name/family maps, the ruby_member_calls resolver's suffix set, both .rb-suffix filters in ruby_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.tally from a .rake task resolves to its .rb definition.

  • 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_nodes gated 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 Python get_db reference can't bind to a unique Go get_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 graphifyy

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.