Releases: Graphify-Labs/graphify
Release list
v0.9.8
graphify 0.9.8 — Windows hook fix + reliability round.
Highlights: the graph-nudge hooks now work on Windows (Claude Code, Codebuddy, Gemini CLI); plus fixes for CLAUDE.md section-write data loss, a tiktoken special-token crash, an Ollama hang, community-labeling robustness + token accounting, discovery-layer file drops, and the deepseek thinking default.
Install: uv tool install "graphifyy==0.9.8" or pip install graphifyy==0.9.8
Changelog
- Fix: the Claude Code / Codebuddy
PreToolUseand Gemini CLIBeforeToolgraph-nudge hooks now work on Windows (#522). The hooks were inline POSIX bash (case/esac,[ -f ], single-quotedecho), which Windows cmd.exe/PowerShell cannot parse — so on Windows the hook failed silently, no "rungraphify querybefore grepping/reading raw files" context was injected, and users had to invoke/graphifyby hand. The detection logic (grep-command match, source-file extension match, skip-if-under-output-dir, graph-exists check) moved into a shell-agnosticgraphify hook-guard <search|read>subcommand invoked via the absolute exe path (the same pattern the codex hook already uses), so the hook parses and runs identically on Windows, macOS, and Linux. Behavior on macOS/Linux is unchanged (byte-identical nudge payload); the graph path now also honorsGRAPHIFY_OUT. The GeminiBeforeToolhook got the same treatment (graphify hook-guard gemini), which also removes its dependency on a barepythonbeing on PATH. Codex stays a no-op there because Codex Desktop rejectsadditionalContext. - Fix:
--update-style section writes toCLAUDE.md/AGENTS.mdno longer corrupt or drop content (#1688, thanks @bdfinst)._replace_or_append_sectionlocated its managed block by substring (marker in content) andnext(... if marker in line), so a heading that appeared as a substring of another line (or duplicate headings) matched the wrong offset and the rewrite could truncate the file. It now matches the section heading exactly (line.strip() == marker), appends when absent, and prefers the last exact match when several exist, so unrelated content is preserved. - Fix: token estimation no longer crashes on files containing tiktoken special-token text like
<|endoftext|>(#1685, thanks @Kyzcreig)._TOKENIZER.encode(content)raisesValueErrorby default when the text contains a special token, which aborted packing on docs/corpora that merely mention these strings. Bothencodesites now passdisallowed_special=()so such text is tokenized as ordinary bytes. - Fix: the Ollama backend no longer multiplies a hang by the retry count (#1686, thanks @Kyzcreig). A stalled local model would wedge for
timeout * (max_retries + 1), which with the default 6 retries turned one long stall into a very long one. Ollama now defaults to zero client-side retries (a local model that stalls will not un-stall on retry); setGRAPHIFY_MAX_RETRIESto opt back in. Other backends are unchanged. Note: the underlying stall is non-deterministic and driven by the model server, so this bounds the wait rather than eliminating the hang. - Fix: a truncated or slightly malformed community-labeling reply no longer discards the whole batch (#1690, thanks @vdgbcrypto).
_parse_label_responsenow salvages the complete"id": "name"pairs from a reply that failed a strictjson.loads(e.g. a reply truncated mid-object), raising only when no pairs can be recovered. The per-batch token budget was also raised (256 + 48*n, was64 + 24*n) to give models that prepend a short preamble enough headroom to finish the JSON. The exact provider truncation in the report could not be reproduced without a live key; the parser and budget fixes address the mechanism. - Fix: cluster-only mode now reports the real token cost of community labeling instead of a hardcoded zero (#1694, thanks @sub4biz). The labeling LLM calls were never accounted for, so
GRAPH_REPORT.md's "Token cost" line always read0 input · 0 outputin cluster-only runs._call_llmnow accumulates per-response usage into an optional accumulator that is threaded through the labeling path and surfaced in the report. Backends that do not return usage (the Claude Code CLI) still contribute nothing, which is honest rather than estimated. - Docs/Feat:
deepseek-v4-flash(andv4-pro) have thinking ENABLED by default; graphify no longer implies otherwise and adds an opt-inGRAPHIFY_DISABLE_THINKING=1toggle (#1621, thanks @sub4biz for the empirical testing). Disabling thinking removes a rare reasoning-leak failure mode (which the adaptive extraction/labeling retry already recovers from) but, measured on real corpora, trades it for more frequent benign truncation and measurably lower extraction quality and file coverage — so it stays a documented user choice rather than a forced default. The stale "non-thinking" comment on the built-in deepseek config is corrected. The moonshot (kimi) branch is unchanged (it must disable thinking or content comes back empty). - Fix: source files are no longer silently dropped during discovery by two over-broad filters (#1666, thanks @krishnateja7 for the precise root-cause). (a) A bare
snapshots/directory was pruned as a Jest/Vitest artifact, which killed legitimate code namespaces like a Railsapp/services/snapshots/; it is now pruned only when it actually contains.snapfiles or sits directly under a JS test root (__snapshots__stays unconditionally pruned). (b)_is_sensitivedropped files on a bare name-keyword hit (device_token.rb,passwords_controller.rb) even whenclassify_filehad already resolved them to source code; a genuine programming-language source file is now exempt from the weak keyword heuristic, while real secret stores in data/config formats (credentials.json,secrets.yaml,.env,.pem, ...) are still caught. This is the discovery-layer fix; the 0.9.7 no-cache-on-empty change could not surface these because the files never reached extraction.
graphify 0.9.7
graphify 0.9.7 — 17 fixes and features since 0.9.6.
Ruby
include/extend/prepend <Module>now emitsmixes_inedges (#1668), so Rails concern composition is visible toaffected.affected <Class>reaches callers that bind to a class's method nodes (#1669), seeding the reverse walk from the root's members (one method/contains hop).
Extraction
- Extensionless shebang CLIs (
devctl,manage) are now extracted instead of silently dropped (#1683, @Stashub). - JS/TS rationale comments (
// NOTE:) and ADR/RFC citations becomerationale/doc_refnodes, matching Python (#1599, @niltonmourafilho-arch). - Java standard-library types (
String,List,Optional, ...) no longer emitted asreferencesnoise (#1603, @NydiaChung). - New
pascaloptional extra for AST-quality Delphi extraction (#1616, @vinicius-l-machado). - JS/TS calls with no local definition and no import no longer bind to a same-named export in an unrelated package (#1659, @leonaburime-ucla).
- Case-insensitive file-extension dispatch, so
App.PY/script.JSare no longer skipped (#1671, @raman118).
Incremental / detect
- A modified
.docx/.xlsxnow re-enters--update(#1649, @Ns2384-star). - Windows long paths (>260 chars) are now hashed (#1655, @Ns2384-star).
- Word counts are cached against each file's stat signature, so unchanged PDFs/docx are not re-parsed on every run (#1656, @Ns2384-star).
- An extractable source file that produces zero nodes is no longer cached and is surfaced with a warning (#1666, @krishnateja7).
Windows / reports / misc
- Windows skill declares
name: graphify(folder-name rule) (#1635, @ray8875); OpenCode plugin uses;not&&for PowerShell 5.1 (#1646, @gonaik);GRAPH_REPORT.mdImport Cycles section only shown for code corpora (#1657, @Ns2384-star). - Virtual PostgreSQL URI no longer backslash-mangled on Windows (#1672, @raman118); deferred
import()no longer reported as a file cycle (#1241, @Synvoya).
See the CHANGELOG for full detail. Thanks to everyone who filed issues and sent PRs.
graphify 0.9.6
graphify 0.9.6 — 19 fixes and features since 0.9.5.
Ruby (major)
- Module / Struct.new / Class.new / Data.define container nodes (#1640). Plain
module Foo,Foo = Struct.new(...) do ... end,Foo = Class.new(StandardError), andResult = Data.define(...)now get real container nodes with their methods attached;Class.new(Super)emits an inherits edge. - Constant-receiver singleton-call resolution (#1634).
Service.call,Model.where,SomeJob.perform_asyncnow resolve cross-file, so Rails/Zeitwerk apps (no requires) get real cross-file edges instead of near-zero. Binds to the class's owned singleton method when present, else the class node for blast-radius; single-owning-class guard kept.
Correctness and stability
- No more cross-language phantom import edges (#1638). An unresolved bare npm import (
import x from "pkg/colors") no longer aliases onto an unrelated localcolors.pyvia the build alias index. - Semantic extraction hardened (#1631). A malformed LLM chunk (a stray non-dict entry) no longer crashes the merge and discards every successful chunk.
- Deterministic graph.json ordering (#1632). Parallel semantic backends now merge chunks in submission order, so node/edge ordering is stable run-to-run (the model's content variance is separate).
Contributor extractor fixes
- Apex interface multiple inheritance (
interface X extends A, B) (#1645, @Synvoya). - Kotlin interface delegation (
class Foo : Bar by baz) (#1644, @Synvoya).
Plus fixes to query seed diversity (#1596), cluster-only sidecar (#1617), update/watch stale-source reconciliation (#1623), TS/JS extractor gaps (#1615/#1607), merge-graphs (#1606), Swift singleton locals (#1604), C# receiver-typed member calls (#1609), TS receiver-typed member calls (#1630), the to_canvas dangling-member crash (#1236), a root-source_file crash (#1618), the claude-cli backend bisection stall, symlink containment (#1613), and the Homebrew python@3.x interpreter path (#1586).
See the CHANGELOG for full detail.
Thanks to everyone who filed issues and sent PRs. Follow the org at https://github.com/Graphify-Labs.
v0.9.5
graphify 0.9.5
pip install -U graphifyy==0.9.5
Correctness (two 0.9.4 regressions + a false-hub fix)
- Cross-file
indirect_callnow resolves via thegraphify extractCLI, not just theextract()API — a 0.9.4 regression where the callable-target guard went stale after id relativization dropped every cross-file indirect edge on the CLI path. graphify cluster-onlyno longer reuses stale community labels after the graph changed — a re-scoped/re-clustered graph kept old labels on a different community set. It now writes per-community membership signatures and hub-relabels changed communities with a warning.- Cross-file name resolution respects case in case-sensitive languages (#1581) —
from pathlib import Pathno longer resolves to a shellexport PATH=...node (one variable had become the #1 god-node with 266 false edges). Only PHP/SQL/Nim still fold.
Language extractor fixes (thanks @Synvoya, @jerryliurui)
Ruby & Groovy inheritance edges; Elixir multi-alias imports; Fortran function calls; Rust enum-variant + tuple-struct references; Julia qualified/relative/scoped imports; SystemVerilog qualified fields; Scala var fields; PowerShell class base types; ObjC protocol-to-protocol adoption; PHP promoted constructor properties; C# auto-properties; C++ base-class template args; Swift enum associated values; and Swift singleton-into-local resolution (let x = Type.shared; x.method(), #1604).
Incremental --update data fixes
Hyperedges from unchanged files no longer dropped (#1574); deleted files no longer leave ghost nodes, with symlinked-root hardening (#1571).
Robustness & tooling
merge-graphs tolerates mixed directed/multigraph inputs (#1606); corrupt graph.json gives an actionable error (#1537/#1536); cross-chunk node-ID collisions warn instead of silently dropping (#1508); the skill accepts Homebrew python@3.x interpreters (#1586); git-hook foreground stalls eliminated (#1601); direction-aware skill-version warning (#1568); deterministic hub community labels; Windows hook worker limit (#1554).
Serve / MCP
Question/filler stopwords dropped from query terms (#1597); optional project_path lets one MCP server back a whole workspace of projects (#1594).
Thanks to all contributors: @sheik-hiiobd, @Synvoya, @jerryliurui, @AdrianRusan, @SUDARSHANCHAUDHARI, @socar-tender, @goodjira, @TPAteeq, @guyoron1, @nuthalapativarun, @matiasduartee, @joanfgarcia, and the #1601/#1597 authors.
v0.9.4
graphify 0.9.4
Published to PyPI: pip install -U graphifyy==0.9.4
Blast-radius / call-graph
- indirect_call dispatch — a function referenced by name is now a first-class dependency
graphify affectedtraverses: call arguments (submit(fn),Thread(target=fn),map(fn, xs)), cross-file/imported callbacks, dispatch tables (ROUTES = {"x": fn},[a, b]), assignment/return aliases (cb = fn,return fn),getattr(obj, "name")reflective dispatch, and JS/TS (call args, object/array tables, Express-styleapp.get("/", h)). Kept as a distinct INFERRED relation so strictcallsqueries stay precise. (#1565, #1566, #1569, #1575)
Incremental --update data fixes
- Hyperedges from unchanged files are no longer dropped on every incremental update. (#1574)
- Deleted files no longer leave ghost nodes — prune matching relativizes absolute paths against the scan root (with symlinked-root hardening) even when a caller omits
root. (#1571)
Language coverage
- Ruby class inheritance emits
inheritsedges. (#1535) - Groovy
extends/implementsemitinherits/implementsedges. (#1534)
Robustness & UX
- Corrupt
graph.jsonraises a clear, actionable error instead of a traceback. (#1537/#1536) - Cross-chunk node-ID collisions warn (naming both files) instead of silently dropping a node. (#1508/#1504)
- Skill-version mismatch warning is direction-aware — a newer-than-package skill is told to upgrade the package, not to run
install(which would downgrade it). (#1568) - Deterministic hub community labels: no-backend runs read
auth/log_actioninstead ofCommunity 70. (#1576) - Git hooks on Windows/MSYS default to sequential rebuilds. (#1554)
Thanks to @sheik-hiiobd, @Synvoya, @socar-tender, @goodjira, @TPAteeq, @guyoron1, @nuthalapativarun, @matiasduartee for reports and contributions.
v0.9.3
Cross-file member-call resolution for C++/Objective-C (#1547/#1556) and namespace-aware C# type resolution (#1562), the work-memory overlay (#1441), and a batch of TS/JS/ObjC resolution + call-graph fixes.
- Feat: cross-file member-call resolution for C++ and Objective-C (#1547, #1556). A class declared in a header and defined in its
.cpp/.mno longer fragments into two nodes (a decl/def merge pass collapses the sibling header/impl pair, gated to same-directory same-name so unrelated classes never merge), and a member call now resolves across files by the receiver's inferred type: C++Foo f; f.bar()/Foo::bar()/this->bar()and ObjCFoo *f = [[Foo alloc] init]; [f doThing]/[self render]link to the owning class's method. Resolution is by receiver type, never bare name, with the single-definition god-node guard — an uninferable or ambiguous receiver produces no edge (high precision over recall, grounded in how compiler-free indexers like ctags/Doxygen mis-resolve by name). Also routes C++ headers to the C++ extractor and ObjC#importbridging headers to the ObjC extractor. Reported by @c0dezer019 and @JabberYQ. (Residual cross-file#includeedge resolution under symlinked roots and ObjC dynamic-dispatch receivers remain follow-ups.) - Feat: namespace-aware C# cross-file type resolution (#1562, thanks @TheFedaikin). The namespace is folded into the C# node id (so same-named types in different namespaces stay distinct),
usingdirectives are honored with lexical per-block scope, and qualified references (Namespace.Type,usingaliases) resolve — disambiguating a bare reference to the one in-scope namespace that provides it, and refusing (no edge) when ambiguous. Advances the #1318 shadow-node umbrella for C#. - Fix: test mocks no longer erase the real cross-file call graph (#1553, thanks @Schweinehund). When a bare callee name had 2+ definitions without unique import evidence, the god-node guard dropped the edge entirely — so a single same-named test mock wiped the real call graph (a 76-stub Pester suite erased everything). The guard now applies tie-breakers — non-test preference (a shared, segment-aware path classifier) then path proximity — and resolves only when exactly one candidate survives, else still bails. A real def plus a test mock resolves to the real def; two genuine non-test defs still bail (no fan-out).
- Fix: hyperedge member lists keyed
membersornode_idsare now accepted, not silently dropped (#1561, thanks @askalot-io). Normalized to the canonicalnodesat ingest (in build_from_json and semantic_cleanup), deduped, with a warning — mirroring the existing from/to edge-endpoint aliasing. - Feat: work-memory overlay —
graphify reflectnow projects the verdicts it distills (preferred / tentative / contested, recency-weighted) into a.graphify_learning.jsonsidecar next to graph.json, andgraphify explain/query/GRAPH_REPORT.md/ the HTML viewer surface them where you look (aLesson:hint, a colored node ring). Builds on the idea in #1441/#1542 (thanks @TPAteeq), implemented as a sidecar rather than stamping graph.json: structural truth stays separate (nolearning_*in graph.json or GraphML exports, no rebuild churn). Each verdict carries the source questions that produced it (provenance) and a content fingerprint of the cited code, so a verdict on a file that has changed since is flagged "code changed — re-verify" instead of shown as still-authoritative. Dead-ends stay query-scoped (a report section, never a node attribute). Letting verdicts influence query traversal is deliberately deferred (it needs propensity correction + exploration to avoid a self-reinforcing feedback loop). - Feat: type-aware
this.field.method()resolution for TypeScript/JS (#1316, thanks @guyoron1). A member call through a constructor-injected dependency (constructor(private db: Database)thenthis.db.query()) now produces acallsedge to the field type's method, resolved by the field's declared type and gated by the single-definition god-node guard (an ambiguous or untyped field produces no edge — no global name-match fan-out). EXTRACTED confidence; constructor parameter-property injection scope. - Feat: resolve TypeScript wildcard path aliases (#1544, thanks @oleksii-tumanov). A
compilerOptions.pathspattern like@app/*or@*/interfacesnow captures the matched segment and substitutes it into each target in order, honoring tsc's longest-prefix / exact-wins specificity, baseUrl, and the first-existing-target fallback. Extends the #1531 resolver. - Feat: resolve JS namespace re-export bindings (#1552, thanks @oleksii-tumanov).
export * as ns from './mod'now creates a real symbol node forns, registers it as a named export (so a downstreamimport { ns }resolves to it), and emits a file-levelre_exportsedge — treated as a single opaque binding, sons.memberaccesses don't fan out into false per-symbol edges. Includes cycle and deep-chain guards. - Feat: Objective-C dot-syntax property accesses and
@selector()call edges (#1475, #1543, thanks @guyoron1).self.product.namenow emits anaccessesedge and@selector(method)acallsedge, each resolved only to an unambiguous in-scope definition by exact method-id match (a sibling of the same class for dot-syntax; exactly one method by exact selector name for@selector) — soself.namecan't mis-resolve to a-surnamesibling and same-named methods across classes don't fan out. Completes the #1475 ObjC follow-ups.
v0.9.2
Bug-fix and feature release: Ruby type-aware call resolution, workspace exports-map resolution, the alias/workspace import-edge regression fix (#1529), tsconfig paths fallbacks, semantic-cache pruning, three Objective-C extractor fixes, Swift static-call confidence, and security dep bumps.
- Feat: type-aware Ruby member-call resolution (#1499, thanks @vamsipavanmahesh).
p.runis now resolved by the inferred type of the receiver (p = Processor.new⇒Processor#run) instead of by globally-unique method name, so the edge survives name collisions (an unrelatedWorker#runno longer makes it ambiguous) and never points at the wrong method. Introduces a small resolver-registry framework that the existing Swift (#1356) and Python (#1446) cross-file passes register into. Receiver types are inferred only from unambiguous localvar = ClassName.newbindings; a call whose receiver type can't be proven resolves to nothing rather than to a guess — a deliberate precision-over-recall change for Ruby member calls. - Feat: resolve workspace imports through the package's
exportsmap (#1308, thanks @guyoron1). A subpath import likeimport { x } from "@scope/pkg/browser"now resolves through the package.jsonexportsmap (string values, condition objects, nested conditions, and./*wildcard patterns) instead of falling back to a bare path string, falling back to the existing bare-path/index resolution when there's no exports map or no match.defaultis consulted last (Node's catch-all), and an export target that escapes the package directory is rejected. - Fix: import edges silently dropped on codebases using tsconfig path aliases or workspace packages (#1529), a regression from the 0.9.0 full-repo-relative node-ID change. Relative imports resolve to repo-relative paths and matched fine, but alias (
@/lib/utils) and workspace imports resolve to absolute paths, so the import-target ID baked in the on-disk prefix and no longer matched the repo-relative definition node — the edge was dropped at build (common on Next.js/SvelteKit). The id-remap post-pass now also registers the absolute-resolved form, so alias/workspace import targets land on the real node again. - Fix: tsconfig
compilerOptions.pathsfallback targets are now honored (#1531, thanks @oleksii-tumanov). Apathsvalue is an ordered list ("@app/*": ["src/app/*", "lib/app/*"]) thattsctries in turn; graphify kept only the first entry, so an import whose file lived at a later target was dropped or misresolved. Each target is now tried in order and the first that resolves to a real file wins (no false edge when none exist). - Fix: the semantic (LLM) extraction cache is now pruned (#1527, thanks @mwolter805). The AST cache was version-swept but the content-hash-keyed semantic cache had no cleanup, so every content change or file deletion left an orphan entry and
graphify-out/cache/semantic/grew unbounded. Orphan entries are now removed at the end ofextract, computed against the full live document set (not the incremental changed subset, which would have evicted still-valid entries) and only touchingcache/semantic/; the cache stays unversioned so releases never re-bill LLM extraction. - Fix: three Objective-C extractor bugs (#1475, thanks @JabberYQ for the detailed report and test repo). (1)
.hheaders usingNS_ASSUME_NONNULL_BEGINbefore@interfaceproduced no class node — tree-sitter-objc can't expand the argument-less macro and fails to emit aclass_interfacenode at all, so the macro is now blanked (offset-preserving) before parsing. (2) Quoted#import "X.h"edges dangled once a.h/.mpair existed (the bare-stem target was salted away during id-disambiguation); imports now resolve to the real header file node, fixing the equivalent latent C#includebug too. (3)[[Foo alloc] init]now emits areferencesedge to the allocated class, resolved only to an unambiguous class (no false edges). Dot-syntax property accesses and@selector(...)target-action edges remain follow-ups. - Fix: Swift type-qualified static calls now resolve as EXTRACTED rather than INFERRED (#1533, thanks @JabberYQ).
SessionType.staticMethod()/Singleton.shared.method()name the receiver type explicitly in source, so the resolved edge is an exact reference, matching the Python qualified-class-method pass; instance calls typed via local inference (obj.method()) stay INFERRED. - Fix: enforce the API timeout in the secondary LLM dispatch path (#1442, thanks @DhruvTilva).
_call_llm(used by the dedup LLM tiebreaker) built its Anthropic/OpenAI clients withouttimeout, so requests there ignoredGRAPHIFY_API_TIMEOUTand could hang — it now passes the timeout like the primary extraction paths. - Fix:
to_graphmlno longer raisesValueErroron a node/edge with aNoneattribute value — null fields are coerced to""before writing (#1502, thanks @antonioscarinci). - Feat:
graphify save-resultaccepts--answer-fileas an alternative to--answer, so a long or multi-line answer can be read from a file instead of an inline shell argument (#1502, thanks @antonioscarinci). - Fix: generated install/skill guidance is now host-generic (#1530, thanks @ari-mitophane). The wording no longer tells agents to invoke a literal
skilltool withskill: "graphify"(host-specific and invalid in many environments); it now points to the installed graphify skill or instructions. - Security: bump
msgpackto 1.2.1 (GHSA-6v7p-g79w-8964) andpydantic-settingsto 2.14.2 (GHSA-4xgf-cpjx-pc3j), and drop the unusedsafetydev dependency, which only pulled innltk(an unpatched HIGH advisory). All transitive; the two HIGH-severity ones were dev-tooling only and never in the published wheel.pip-audit(already run in CI) continues to provide dependency-CVE scanning.
v0.9.1
Patch release over 0.9.0 — node-ID hardening, incremental-update correctness, rate-limit resilience, and Java extraction. All non-breaking; no re-migration.
- Fix: rate-limited (HTTP 429) extraction chunks are now retried instead of dropped (#1523, thanks @bercedev). The provider SDKs back off and honor
Retry-After, but the SDK default of 2 retries was too low for strict per-org concurrency/RPM caps (e.g. Moonshot/kimi), so a parallelextract429'd, each chunk loggedchunk N failed, and was silently lost (incomplete graph + console spam). The OpenAI-compatible, Azure, and Anthropic clients are now built with a highermax_retries(default 6, override viaGRAPHIFY_MAX_RETRIES). For very tight accounts,--max-concurrency 1further reduces the concurrency that triggers org-level limits. - Fix:
graphify updatenow prunes the edges a re-extracted file no longer produces (#1521, thanks @UltronOfSpace). Old edges were preserved by endpoint-node membership alone, so a deleted import's edge survived forever as long as both endpoints still existed — driving phantom circular-dependency findings (and--forcedidn't help). Edges owned by a re-extracted file (source_file) are dropped before merging the fresh extraction; cross-file edges that merely point at the file are untouched. - Fix: residual node-ID collisions after the 0.9.0 full-path change (#1522, thanks @sub4biz).
normalize_idcollapses every separator to_, so distinct paths that differ only by a separator-vs-punctuation swap (foo/bar_baz.pyvsfoo_bar/baz.py) still merged. Colliders are now salted with a short stable path hash so they stay distinct; non-colliding IDs are byte-identical to 0.9.0 (no re-migration). - Fix: Java record component types now emit
referencesedges (#1519, thanks @oleksii-tumanov) — a record's data dependencies (record Order(Payload p, List<Item> items, …)) were invisible; primitives and the record's own type parameters are skipped. - Fix: same-label cross-file imported-type stubs now stay distinct in the six dedicated extractors too — Julia, Fortran, Go, Rust, PowerShell, ObjC (#1515, thanks @TPAteeq). The #1462 disambiguation previously only covered the generic extractor, so e.g. two Go files importing the same
ext.Widgetcollapsed into one conflated node; they're now kept distinct (whilesource_filestays empty so the #1402 rewire onto a real definition is unchanged). - Fix: Java type parameters no longer emit spurious
referencesedges (#1518, thanks @oleksii-tumanov). The generic-parent support (#1511) created a stray edge/stub for the bareTinclass Box<T> extends Container<T>; the extractor now collects in-scope type-parameter names (class/interface/record/method/constructor, incl. bounded/multiple) and skips them, while keeping every real type and theinherits/implementsedge to the base. - Fix: the internal
origin_filedisambiguation field (#1462) is no longer serialized into graph.json, where it had shipped (in 0.9.0) as an absolute, machine-specific path — it is dropped once the colliding-id pass consumes it, keeping output portable (#1516, thanks @TPAteeq; cf. #555, #932)._originstays (the incremental watcher needs it, #1116).
v0.9.0 — full-path node IDs (breaking)
⚠️ Breaking change — node IDs changed
Node IDs now include the full repo-relative path, fixing silent data loss when same-named files live in different directories. Existing graphs migrate automatically on the next build/update (no LLM re-bill). Run graphify extract --force to recover nodes that previously collided. If you push to a persisted Neo4j store, re-import after upgrading; GraphML/Gephi layouts go stale; query by label rather than persisting node IDs.
- Breaking — node IDs now include the full repo-relative path (#1504, #1509). The node-ID stem was the immediate parent dir + filename, so same-named files in different directories collided into one last-writer-wins node and silently dropped graph content (
docs/v1/api/README.mdanddocs/v2/api/README.mdboth →api_readme). The stem is now the full repo-relative path (docs_v1_api_readmevsdocs_v2_api_readme); top-level files are unchanged (setup.py→setup). The AST extractor, the LLM system prompt, the extraction-spec, and the two hand-copied stem helpers are all aligned to this one rule (fixing the #1509 AST↔LLM divergence that produced ghost duplicates), andbuild_from_jsondeterministically re-keys any cached/older semantic fragment onto the new IDs from itssource_fileso the unversioned semantic cache survives without ghosts or a re-bill. Existing graphs migrate to the new ID format automatically on the nextbuild/update(no re-bill). Note: same-named files in different directories that previously collided into one node are only recovered as distinct nodes by a fresh extraction — rungraphify extract --forceto rebuild and gain them (migrating an already-collided graph/cache can't resurrect the nodes that were already dropped). If you push to a persisted Neo4j store, re-import after upgrading (re-exported IDs change); saved Gephi/yEd (GraphML) layouts go stale; MCP/cypher consumers should query by label rather than persisting node IDs across rebuilds. - Feat:
--timingflag ongraphify extractandgraphify cluster-onlyprints per-stage wall-clock timings to stderr (#1490). Shows how long each pipeline stage takes —extract: detect → AST → semantic → build → cluster → analyze → export;cluster-only: load → cluster → analyze → label → report → export — plus a final total, so slow stages are visible on large corpora. Off by default (monotonicperf_counter, stderr-only); machine-read stdout /graph.jsonare unchanged.
v0.8.51
- Fix: the Obsidian export (
--obsidian/to_obsidian) no longer overwrites a user's own notes or.obsidian/config when pointed at an existing vault (#1506). It wrote one note per node straight into the target dir and unconditionally replaced.obsidian/graph.json, so--obsidian-dir ~/my-vaultcould clobber a same-named note (Database.md) and the user's graph-view settings — silently, no backup. graphify now records the files it owns in a.graphify_obsidian_manifest.jsonand refuses to overwrite any pre-existing file it didn't create (skipping it with one aggregated warning); a re-run still updates graphify's own notes. The defaultgraphify-out/obsidianoutput is unchanged. - Fix: Java enum and annotation (
@interface) declarations are now emitted as type nodes (#1512, thanks @oleksii-tumanov), so a field typed as an enum or a class annotated with a project annotation resolves to a real node instead of a dangling reference. - Fix: Java generic parent relationships are no longer dropped (#1510, thanks @oleksii-tumanov) —
class Foo extends Bar<T>/implements List<T>now emit theinherits/implementsedge to the base type, with the type arguments asgeneric_argreferences. - Fix: the
claude-clibackend no longer crashes withUnicodeDecodeErroron Windows systems whereclaude.cmdemits GBK/cp936 bytes (#1505, thanks @nuthalapativarun) — both subprocess calls decode witherrors="replace". - Fix:
graphify explainandgraphify affectednow resolve a query given as a source-file path even when the graph has multiple nodes from that file (#1503, thanks @behavio1). A path likeapp/api/route.tstokenized to terms that matched no node, so explain returned "No node matching"; source-file paths are now indexed and matched exactly, and when several nodes share the file the lookup prefers the file-level node (theL1node whose name matches the file). Trailing-separator handling is aligned between the two commands. - Docs: clearer install/PATH guidance for
uv tool install graphifyyon macOS (#1471, thanks @Patsch36). Two expected uv behaviors read as bugs: (1) afteruv tool install, thegraphifycommand lands in uv's tool bin dir (~/.local/bin), which a fresh macOS/zsh shell often doesn't have onPATH— the README now points touv tool update-shellinstead of implying uv always wiresPATH; (2)uvx graphify …/uv tool run graphify …resolve the first word as a package and fail, because the package isgraphifyyandgraphifyis only its console script — the docs now showuvx --from graphifyy graphify install. README install note + Troubleshooting only; no code change. - Fix: imported type stubs with the same label no longer falsely merge across files when there is no project definition to rewire onto (#1462, thanks @jiangyq9). Two files that both
from pathlib import Pathand usePathas a type previously collapsed into one node; the referencing file is now kept as an internal disambiguator (origin_file) used only when splitting colliding ids, whilesource_filestays empty so a real project definition can still be rewired onto (the #1402 path is unaffected). - Feat: resolve C# cross-file type references and extract
enum/struct/recorddeclarations (#1466, thanks @TheFedaikin). A new_resolve_csharp_type_references(the C# counterpart to the Java resolver) re-points danglinginherits/implements/referencesedges from no-source "shadow" stubs to their real definitions, disambiguating same-named types in different namespaces via the referencing file'susingdirectives and enclosing namespace; ambiguous matches are refused rather than guessed.enum/struct/recordtypes are now extracted as definitions so those references resolve too. Advances #1318 for C#. - Fix: the Go AST extractor no longer creates phantom duplicate nodes for cross-file type references — the Go copy of
ensure_named_nodestill used the older sourced-stub fallback; it now emits a sourceless stub like the other extractors, extending the #1402 fix to Go (#1500, thanks @TPAteeq).