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 graphifyy