- Feat:
graphify label --missing-onlyrelabels only communities that are unnamed or still hold aCommunity Nplaceholder, 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.metalis classified as code and routed through the existing C++ extractor, mirroring the CUDA.cu/.cuhreuse (#1480, thanks @jiangyq9; supersedes #1450 by @GoodOlClint). Also adds.cu/.cuh/.metalto the cross-language edge-filter family map (they were missing), so phantom cross-languagecallsedges between these and C++ are correctly suppressed. - Fix: pass
stream: Falseexplicitly on OpenAI-compatible chat-completion calls (#1223, thanks @jiangyq9). Some gateways default to SSE streaming whenstreamis 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-llmtiebreaker path. - Fix: emit
referencesedges for Java field types (#1485) and for type-level annotations on Java classes/interfaces/records (#1487, both thanks @oleksii-tumanov). Field types (including thegeneric_argelement ofList<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
.hheaders were parsed by the C extractor (1 node, 0 edges, losing every@interface/@protocol/@property/method) — a.his 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 nocallsedges at all because the method-body pass looked forselector/keyword_argument_listnodes, but the grammar tags selector parts with the field namemethod(typeidentifier) — the selector is now read from themethodfields, 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 ageneric_specifier— the element and container types are now both referenced; (4) class methods (+foo) were mislabeled-foo; (5)@import Foundation;now produces animportsedge. Property/dot-syntaxaccessesand@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-timed:DataContext="{d:DesignInstance Type=…}", theView→ViewModelnaming convention, or PrismViewModelLocator.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:
.vueSingle File Components now extract their<script>with the right grammar (#1468, thanks @papinto)..vuewas dispatched toextract_js, which selects a tree-sitter grammar by suffix;.vueis neither.tsnor.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 dedicatedextract_vuenow masks everything outside<script>(replacing it with spaces so line numbers stay accurate) and parses just the script with the grammar named bylang(tsdefault,tsx/js/jsxhonored). 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-stalenow also checks the.graphify_analysis.jsonand.graphify_labels.jsonsidecars (and any custom--analysis/--labelspaths) when deciding whetherLESSONS.mdis up to date (#1470, thanks @oleksii-tumanov). It previously only stat'd the memory docs andgraph.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|GlobPreToolUse 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 askedany(ext in path), which had two opposite failures:.jsonfiles (package.json,tsconfig.json) spuriously fired because.jsis a substring of.json, and.astro/.vue/.sveltenever 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/.svelteadded), sopackage.jsonstays silent,data.geojsonstays silent,**/*.astrofires, and an extension sitting on a directory component (my.ts/file) correctly doesn't. Thegraphify-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
graphifyCLI 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
.xamlfiles (#1460, thanks @MikeKatsoulakis). No new parser dependency (stdlib XML, with the same DOCTYPE/ENTITY and size guards as the.csprojextractor). Captures the root element, named controls (x:Name/Name) and their control types,{Binding ...}references, andx:Class, and bridges the view to its.xaml.cscode-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 methodSave()) can't fabricate a spurious event edge. - Fix:
to_canvas(Obsidian Canvas export) now lays out each community's node cards in the sameceil(sqrt(n))-column grid the group box is sized for. The box width assumed a roughly-squaresqrt(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_wikino longer silently overwrite notes whose labels differ only by case (e.g. a classReferencesand a prose headingreferences). 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_*.mdoverview notes (which had no dedup) are covered, and a generatedbase_1is itself re-checked so it can't overwrite a node literally labelledbase_1(#1453, thanks @TPAteeq). - Feat: the
kimi,gemini, anddeepseeksemantic-extraction backends now honorKIMI_BASE_URL,GEMINI_BASE_URL, andDEEPSEEK_BASE_URLto point at any OpenAI-compatible endpoint (a proxy, gateway, or self-hosted relay), matching the existingOLLAMA_BASE_URL/OPENAI_BASE_URLoverrides. 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 Obsidian; everywhere else[[Domain Data Models]]points at a literalDomain Data Models.md, but the article file isDomain_Data_Models.md(the slug substitutes spaces and reserved characters), so nearly every community/god-node navigation link opened an empty page. (2) God-node articles linked every neighbor ([[AwsHelper.py]],[[.read_object_key()]]), but only communities and god nodes get article files, so those node-level links were dead even inside Obsidian. Links are now standard[display](slug.md)with the target URL-encoded, so spaces,&, parentheses, and#survive intact in CommonMark renderers and Obsidian alike; any link whose target has no article is downgraded to plain text instead of left dangling. Each article's slug is computed up front (alabel -> slugresolver built before any body is rendered) so a link to a community or god-node article points at the real on-disk filename, including the case-fold collision suffix (parser_2.md). Cosmetic, no graph/data change (#1444, thanks @restagner).