Skip to content

Releases: Graphify-Labs/graphify

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.

v0.9.2

Choose a tag to compare

@safishamsi safishamsi released this 29 Jun 15:30

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.run is now resolved by the inferred type of the receiver (p = Processor.newProcessor#run) instead of by globally-unique method name, so the edge survives name collisions (an unrelated Worker#run no 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 local var = ClassName.new bindings; 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 exports map (#1308, thanks @guyoron1). A subpath import like import { x } from "@scope/pkg/browser" now resolves through the package.json exports map (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. default is 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.paths fallback targets are now honored (#1531, thanks @oleksii-tumanov). A paths value is an ordered list ("@app/*": ["src/app/*", "lib/app/*"]) that tsc tries 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 of extract, computed against the full live document set (not the incremental changed subset, which would have evicted still-valid entries) and only touching cache/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) .h headers using NS_ASSUME_NONNULL_BEGIN before @interface produced no class node — tree-sitter-objc can't expand the argument-less macro and fails to emit a class_interface node at all, so the macro is now blanked (offset-preserving) before parsing. (2) Quoted #import "X.h" edges dangled once a .h/.m pair 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 #include bug too. (3) [[Foo alloc] init] now emits a references edge 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 without timeout, so requests there ignored GRAPHIFY_API_TIMEOUT and could hang — it now passes the timeout like the primary extraction paths.
  • Fix: to_graphml no longer raises ValueError on a node/edge with a None attribute value — null fields are coerced to "" before writing (#1502, thanks @antonioscarinci).
  • Feat: graphify save-result accepts --answer-file as 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 skill tool with skill: "graphify" (host-specific and invalid in many environments); it now points to the installed graphify skill or instructions.
  • Security: bump msgpack to 1.2.1 (GHSA-6v7p-g79w-8964) and pydantic-settings to 2.14.2 (GHSA-4xgf-cpjx-pc3j), and drop the unused safety dev dependency, which only pulled in nltk (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

Choose a tag to compare

@safishamsi safishamsi released this 28 Jun 19:18

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 parallel extract 429'd, each chunk logged chunk N failed, and was silently lost (incomplete graph + console spam). The OpenAI-compatible, Azure, and Anthropic clients are now built with a higher max_retries (default 6, override via GRAPHIFY_MAX_RETRIES). For very tight accounts, --max-concurrency 1 further reduces the concurrency that triggers org-level limits.
  • Fix: graphify update now 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 --force didn'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_id collapses every separator to _, so distinct paths that differ only by a separator-vs-punctuation swap (foo/bar_baz.py vs foo_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 references edges (#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.Widget collapsed into one conflated node; they're now kept distinct (while source_file stays empty so the #1402 rewire onto a real definition is unchanged).
  • Fix: Java type parameters no longer emit spurious references edges (#1518, thanks @oleksii-tumanov). The generic-parent support (#1511) created a stray edge/stub for the bare T in class 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 the inherits/implements edge to the base.
  • Fix: the internal origin_file disambiguation 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). _origin stays (the incremental watcher needs it, #1116).

v0.9.0 — full-path node IDs (breaking)

Choose a tag to compare

@safishamsi safishamsi released this 28 Jun 17:26

⚠️ 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.md and docs/v2/api/README.md both → api_readme). The stem is now the full repo-relative path (docs_v1_api_readme vs docs_v2_api_readme); top-level files are unchanged (setup.pysetup). 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), and build_from_json deterministically re-keys any cached/older semantic fragment onto the new IDs from its source_file so the unversioned semantic cache survives without ghosts or a re-bill. Existing graphs migrate to the new ID format automatically on the next build/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 — run graphify extract --force to 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: --timing flag on graphify extract and graphify cluster-only prints 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 (monotonic perf_counter, stderr-only); machine-read stdout / graph.json are unchanged.

v0.8.51

Choose a tag to compare

@safishamsi safishamsi released this 28 Jun 10:25
  • 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-vault could 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.json and 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 default graphify-out/obsidian output 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 the inherits/implements edge to the base type, with the type arguments as generic_arg references.
  • Fix: the claude-cli backend no longer crashes with UnicodeDecodeError on Windows systems where claude.cmd emits GBK/cp936 bytes (#1505, thanks @nuthalapativarun) — both subprocess calls decode with errors="replace".
  • Fix: graphify explain and graphify affected now resolve a query given as a source-file path even when the graph has multiple nodes from that file (#1503, thanks @behavio1). A path like app/api/route.ts tokenized 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 (the L1 node whose name matches the file). Trailing-separator handling is aligned between the two commands.
  • Docs: clearer install/PATH guidance for uv tool install graphifyy on macOS (#1471, thanks @Patsch36). Two expected uv behaviors read as bugs: (1) after uv tool install, the graphify command lands in uv's tool bin dir (~/.local/bin), which a fresh macOS/zsh shell often doesn't have on PATH — the README now points to uv tool update-shell instead of implying uv always wires PATH; (2) uvx graphify … / uv tool run graphify … resolve the first word as a package and fail, because the package is graphifyy and graphify is only its console script — the docs now show uvx --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 Path and use Path as 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, while source_file stays 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/record declarations (#1466, thanks @TheFedaikin). A new _resolve_csharp_type_references (the C# counterpart to the Java resolver) re-points dangling inherits/implements/references edges from no-source "shadow" stubs to their real definitions, disambiguating same-named types in different namespaces via the referencing file's using directives and enclosing namespace; ambiguous matches are refused rather than guessed. enum/struct/record types 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_node still 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).

v0.8.50

Choose a tag to compare

@safishamsi safishamsi released this 27 Jun 09:29
  • Feat: graphify label --missing-only relabels only communities that are unnamed or still hold a Community N placeholder, preserving existing non-placeholder labels from .graphify_labels.json (#1481, thanks @jiangyq9; supersedes #1421 by @matiasduartee, who proposed the same flag). Lets a large graph be relabeled incrementally without re-naming (and paying for) communities that already have good names.
  • Feat: index Metal (.metal) shader files — Metal Shading Language is C++14, so .metal is classified as code and routed through the existing C++ extractor, mirroring the CUDA .cu/.cuh reuse (#1480, thanks @jiangyq9; supersedes #1450 by @GoodOlClint). Also adds .cu/.cuh/.metal to the cross-language edge-filter family map (they were missing), so phantom cross-language calls edges between these and C++ are correctly suppressed.
  • Fix: pass stream: False explicitly on OpenAI-compatible chat-completion calls (#1223, thanks @jiangyq9). Some gateways default to SSE streaming when stream is omitted, but graphify always reads the result as a single response, so the call failed against those gateways. Applied to both the extraction dispatch path and the --dedup-llm tiebreaker path.
  • Fix: emit references edges for Java field types (#1485) and for type-level annotations on Java classes/interfaces/records (#1487, both thanks @oleksii-tumanov). Field types (including the generic_arg element of List<Handler>) and class annotations (@Service, @Entity) were missing from the graph even though parameter/return types and method annotations were already captured; primitives are still skipped.
  • Fix: the Objective-C extractor was silently dropping most code-level relationships (#1475, thanks @JabberYQ for the detailed report). Five fixes: (1) ObjC .h headers were parsed by the C extractor (1 node, 0 edges, losing every @interface/@protocol/@property/method) — a .h is now routed to the ObjC extractor when it contains an ObjC-only directive (@interface/@protocol/@implementation/@import), which never hijacks a real C/C++ header; (2) [receiver selector] calls produced no calls edges at all because the method-body pass looked for selector/keyword_argument_list nodes, but the grammar tags selector parts with the field name method (type identifier) — the selector is now read from the method fields, skipping the receiver, which also makes compound sends like [self a:x b:y] resolve; (3) generic property types (NSArray<Product *> *) were invisible because the type was wrapped in a generic_specifier — the element and container types are now both referenced; (4) class methods (+foo) were mislabeled -foo; (5) @import Foundation; now produces an imports edge. Property/dot-syntax accesses and @selector(...) target-action edges remain follow-ups.
  • Feat: link WPF/XAML views to their ViewModels and extract richer binding references (#1473, thanks @MikeKatsoulakis). Builds on the initial XAML support (#1460). Resolves a view to its ViewModel from an explicit <Window.DataContext><vm:MainViewModel/>, a design-time d:DataContext="{d:DesignInstance Type=…}", the ViewViewModel naming convention, or Prism ViewModelLocator.AutoWireViewModel="True" — always against an actually-extracted C# class, so a name with no matching class (or an ambiguous one) emits no edge (explicit DataContext is EXTRACTED, conventions are INFERRED). Also extracts binding paths ({Binding User.Name}, Path=Order.Total), commands (Command="{Binding SaveCommand}"), converters, and CommunityToolkit [ObservableProperty]/[RelayCommand] generated members. The event-handler resolution stays gated on the .NET handler signature (no spurious event edges), and ViewModel discovery is bounded to the extraction root.
  • Fix: .vue Single File Components now extract their <script> with the right grammar (#1468, thanks @papinto). .vue was dispatched to extract_js, which selects a tree-sitter grammar by suffix; .vue is neither .ts nor .tsx, so the whole SFC — <template> markup, <script>, and <style> — was parsed as JavaScript, producing a top-level ERROR node and recovering no imports, symbols, or type references. A dedicated extract_vue now masks everything outside <script> (replacing it with spaces so line numbers stay accurate) and parses just the script with the grammar named by lang (ts default, tsx/js/jsx honored). The open-tag scan tolerates > inside quoted attributes, so Vue 3.3+ generic components (generic="T extends Record<string, unknown>") parse correctly.
  • Fix: graphify reflect --if-stale now also checks the .graphify_analysis.json and .graphify_labels.json sidecars (and any custom --analysis/--labels paths) when deciding whether LESSONS.md is up to date (#1470, thanks @oleksii-tumanov). It previously only stat'd the memory docs and graph.json, so lessons could stay stale after community analysis or labels changed without the graph changing. A missing sidecar is treated as not-an-input, so no-cluster builds are unaffected.
  • Fix: the Read|Glob PreToolUse hook (the "run graphify first" nudge installed for Claude Code and CodeBuddy) now matches the file's real trailing extension instead of substring-scanning the path (#1463, thanks @marketechniks). The old check asked any(ext in path), which had two opposite failures: .json files (package.json, tsconfig.json) spuriously fired because .js is a substring of .json, and .astro/.vue/.svelte never fired because they weren't in the set — so on Astro/Vue/Svelte projects, where those are the primary source type, reads and globs never surfaced the graph. The hook now compares the segment after the last / then after the last . against the extension set (with .astro/.vue/.svelte added), so package.json stays silent, data.geojson stays silent, **/*.astro fires, and an extension sitting on a directory component (my.ts/file) correctly doesn't. The graphify-out/ suppression and fail-open behavior are unchanged.
  • Fix: make it unambiguous in the skill that graphify needs no API key, so terminal-style hosts stop looping on a missing one (#1461). Hermes (and the other AGENTS.md hosts: Codex, Aider, OpenClaw, Droid, Trae, …) run the graphify CLI directly and don't dispatch subagents, but the Step 3 extraction guidance framed the no-key path only as "fall through to subagent dispatch" — so on /graphify . those agents would spin for minutes insisting they needed an API key before eventually proceeding. Step 3 now opens with an explicit, hoisted "graphify needs no API key — never ask the user for one, never block on one" statement (code is AST-only; a code-only corpus skips semantic extraction entirely), and the fallback now spells out a non-subagent path for terminal hosts instead of assuming subagent dispatch. Applied across every generated skill body, including the aider/devin monoliths, with a regression test that pins the wording in place.
  • Feat: extract WPF/XAML structure from .xaml files (#1460, thanks @MikeKatsoulakis). No new parser dependency (stdlib XML, with the same DOCTYPE/ENTITY and size guards as the .csproj extractor). Captures the root element, named controls (x:Name/Name) and their control types, {Binding ...} references, and x:Class, and bridges the view to its .xaml.cs code-behind by resolving event-handler attributes to the matching methods on the partial class. Event resolution is gated on the .NET handler signature (object sender, …EventArgs e) and skips free-form attributes (Content, Text, Tag, …), so a property value that merely matches a method name (e.g. Content="Save" next to a business method Save()) can't fabricate a spurious event edge.
  • Fix: to_canvas (Obsidian Canvas export) now lays out each community's node cards in the same ceil(sqrt(n))-column grid the group box is sized for. The box width assumed a roughly-square sqrt(n)-column layout, but the placement loop hardcoded 3 columns, so any community larger than ~9 members rendered as a cramped 3-wide strip in an over-wide, mostly-empty box. The column count is now computed once per community and reused for the box width, box height, and card placement, so the cards fill the box. Cosmetic, no data change (#1452, thanks @TPAteeq).
  • Fix: to_obsidian / to_canvas / to_wiki no longer silently overwrite notes whose labels differ only by case (e.g. a class References and a prose heading references). The filename dedup was keyed on the exact-case name, so two such labels counted as non-colliding and the second write clobbered the first on case-insensitive filesystems (macOS/APFS, Windows/NTFS) — no suffix, no warning. Dedup now folds case (keyed on the lowercased name) while still emitting the original-case filename, so any pair that would collide on disk gets a numeric suffix. The obsidian/canvas dedup is shared in one helper so they can't drift, wiki's slug dedup gets the matching fix, the _COMMUNITY_*.md overview notes (which had no dedup) are covered, and a generated base_1 is itself re-checked so it can't overwrite a node literally labelled base_1 (#1453, thanks @TPAteeq).
  • Feat: the kimi, gemini, and deepseek semantic-extraction backends now honor KIMI_BASE_URL, GEMINI_BASE_URL, and DEEPSEEK_BASE_URL to point at any OpenAI-compatible endpoint (a proxy, gateway, or self-hosted relay), matching the existing OLLAMA_BASE_URL / OPENAI_BASE_URL overrides. Each falls back to its hardcoded official default when the variable is unset, so behavior is unchanged for everyone who doesn't set it (#1458, thanks @jc2shile).
  • Fix: to_wiki (Wikipedia-style wiki export) now emits portable relative markdown links instead of Obsidian [[wikilinks]], so navigation works in every renderer — VS Code preview, GitHub, GitLab, a plain browser — not just Obsidian. Two defects: (1) [[Title]] resolves by note title only inside Obsid...
Read more

graphify 0.8.49

Choose a tag to compare

@safishamsi safishamsi released this 25 Jun 09:04

graphify 0.8.49

Fixes

  • get_community MCP tool now shows the community name in its header (Community 12 — Auth & Sessions (8 nodes)), matching get_node / query output; skipped when it is only the Community N placeholder so it never doubles (#1448, thanks @rmart1308).
  • graphify reflect no longer duplicates "known dead ends" / "corrections" lines when the same Q&A is saved more than once (dedup by question, most recent wins).
  • Work-memory works without the git hook: the skill runs graphify reflect --if-stale at the start of graph work, so a skill-only install still refreshes LESSONS.md. --if-stale no-ops when the file is already newer than every input, so the post-commit hook is an optimization rather than a requirement.

Security

  • Floor starlette at >=1.3.1 for CVE-2026-48818 and CVE-2026-54283 (both resolved by 1.3.1). starlette underpins the HTTP MCP transport (graphify-mcp over HTTP); stdio and the CLI are unaffected. Now declared in the mcp/all extras and floored so end users installing graphifyy[mcp] are covered, not just the dev lock (#1391, #1396, thanks @orbisai0security).

Refactor / Performance

  • Begin splitting extract.py into per-language modules under graphify/extractors/ (blade, elixir, razor, zig + shared base.py), behavior-neutral, with extract.py re-exporting the moved names so all callers and the dispatch table are unchanged (#1212, thanks @TheFedaikin).
  • Parallel community labeling: cluster-only / label take --max-concurrency and --batch-size; ollama/claude-cli stay serial unless opted in (#1390).

Install: uv tool install graphifyy==0.8.49