NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
PyPI · #3304 most downloaded on PyPI
AI coding assistant skill (Claude Code, CodeBuddy, Codex, OpenCode, Kilo Code, Cursor, Gemini CLI, Aider, OpenClaw, Factory Droid, Trae, Hermes, Kiro, Pi, Devin CLI, Google Antigravity) - turn any folder of code, docs, papers, images, or videos into a queryable knowledge graph
Last release today
16 Sep 2026
Ships on a steady schedule
a new release about every 9 days
Nearly every release is documented
notes for 60 of the last 60 stable releases
1 version withdrawn
withdrawn after publishing
5 months old
231 releases · first in 2026
One column per month.
Cross-file member-call resolution for C++/Objective-C (#1547/#1556) and namespace-aware C# type resolution (#1562), the work-memory overlay (#1441), a
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.
.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.)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#.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.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).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.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.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.@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.Bug-fix and feature release: Ruby type-aware call resolution, workspace exports-map resolution, the alias/workspace import-edge regression fix (#1529)
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.
p.run is 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 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.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.@/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.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).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..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.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._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.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).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).skill tool with skill: "graphify" (host-specific and invalid in many environments); it now points to the installed graphify skill or instructions.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.Patch release over 0.9.0 — node-ID hardening, incremental-update correctness, rate-limit resilience, and Java extraction. All non-breaking; no re-migr
Patch release over 0.9.0 — node-ID hardening, incremental-update correctness, rate-limit resilience, and Java extraction. All non-breaking; no re-migration.
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.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.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).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.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).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.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).Node IDs now include the full repo-relative path, fixing silent data loss when same-named files live in different directories. Existing graphs migrate
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.
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.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), 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.--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.Fix: the Obsidian export (--obsidian / to_obsidian) no longer overwrites a user's own notes or .obsidian/ config when pointed at an existing vault (#1
--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.@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.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.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".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.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.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).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#.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).Feat: graphify label --missing-only relabels only communities that are unnamed or still hold a Community N placeholder, preserving existing non-placeh
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..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.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.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..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.<Window.DataContext><vm:MainViewModel/>, a design-time d:DataContext="{d:DesignInstance Type=…}", the View→ViewModel 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..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.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.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.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..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.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).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).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).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 literal Domain Data Models.md, but the article file is Domain_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 (a label -> slug resolver 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).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 ov…
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).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.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).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).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
get_community MCP tool now shows the community name in its header (Community 12 — Auth & Sessions (8 nodes)), matching get_node and the query-traversal output, which already read the community_name attribute to_json writes onto every node. get_community was the only graph tool still returning a bare numeric id. The name is read from the community's member nodes (they share it), sanitised like every other LLM-derived field, and skipped when it is just the Community N placeholder so the header never doubles to Community 12 — Community 12 (#1448, thanks @rmart1308).starlette at >=1.3.1 to pick up the fixes for CVE-2026-48818 and CVE-2026-54283 (both resolved by 1.3.1). starlette underpins the HTTP MCP transport (graphify-mcp over HTTP / serve_http); the stdio transport and CLI are unaffected. It was an undeclared transitive dependency (via mcp) that graphify/serve.py imports directly, so it is now declared in the mcp (and all) extras and floored, which protects end users installing graphifyy[mcp], not just the locked dev/CI environment. Lockfile bumped 1.0.0 -> 1.3.1; serve/MCP/HTTP tests pass on the new version (#1391, #1396, thanks @orbisai0security).extract.py into per-language modules under graphify/extractors/ (#1212). The blade, elixir, razor, and zig extractors plus the shared primitives (_make_id, _file_stem, _read_text, _LANGUAGE_BUILTIN_GLOBALS) move into their own files, with graphify/extractors/base.py holding the shared pieces and a strict one-way import direction (extract.py -> extractors/, never the reverse). extract.py re-exports the moved names, so every from graphify.extract import ... caller and the dispatch table are unchanged. Behavior-neutral lift-and-shift (verified byte-identical), groundwork for moving the remaining languages out. See graphify/extractors/MIGRATION.md.graphify cluster-only and graphify label accept --max-concurrency N (default 4) to fan labeling batches out across a thread pool, and --batch-size N (default 100) to tune communities per LLM call. A large graph that previously needed hundreds of sequential calls now runs them in rounds. Mirrors the existing extract parallelism, including the safety guards: ollama and claude-cli are forced serial (set GRAPHIFY_OLLAMA_PARALLEL=1 / GRAPHIFY_CLAUDE_CLI_PARALLEL=1 to override). Output is unchanged and deterministic regardless of concurrency, since results are keyed by community id and merged on the main thread.graphify reflect no longer duplicates lines in the "known dead ends" and "corrections" sections when the same Q&A is saved more than once. Those lists were appended per memory doc with no key (node scoring already dedups by node, but these two did not); they now collapse by question, keeping the most recent entry — so a re-corrected question shows its latest correction. Output stays deterministic (ordered by date then question).graphify reflect --if-stale itself at the start of graph work (cheap, deterministic, a no-op when no outcomes have been saved), then read LESSONS.md. Previously a skill-only install (without graphify hook install) would keep recording outcomes via save-result but never regenerate LESSONS.md, so the lessons never surfaced. The post-commit hook is now an optimization for between-session freshness rather than a requirement. The new --if-stale flag skips the run when LESSONS.md is already newer than every input (the memory docs and the graph), so when the hook just refreshed it the agent's session-start run costs almost nothing.Nothing published for this version
`graphify save-result --outcome useful|dead_end|corrected [--correction TEXT]` records how a saved Q&A turned out.
graphify save-result --outcome useful|dead_end|corrected [--correction TEXT] records how a saved Q&A turned out.graphify reflect aggregates graphify-out/memory/ into a deterministic reflections/LESSONS.md an agent loads next session. Source nodes are scored, not counted — signed and recency-decayed (configurable --half-life-days, default 30), so a fresh dead end outweighs a stale useful. A node is preferred only once corroborated by ≥--min-corroboration distinct results (default 2); others are tentative; mixed-signal nodes render once as contested (recency-wins). Citations whose node no longer exists in the graph are dropped, so stale lessons don't linger. Deterministic, no LLM.LESSONS.md at the start of graph work and to record outcomes, and the git post-commit/post-checkout hooks now auto-run reflect after each rebuild — so lessons stay fresh without a manual command.ClassName.method(...)) now produce an EXTRACTED calls edge to the class-qualified method node (#1446) — previously dropped, including the common case where the called method shares its name with the caller (a viewset action delegating to a same-named service action).validate_extraction/build_from_json no longer crash on a non-hashable node id or edge endpoint from a malformed extraction — a single bad node no longer aborts the whole build (#1447, thanks @dschwartzi).graphify update now prunes a function/symbol removed from a still-present file without --force — the shrink-guard is file-aware (allows shrinkage from re-extracted/deleted files, still refuses unexplained loss).Install: uv tool install graphifyy==0.8.47
graphify-out/reflections/LESSONS.md at the start of graph work (start from preferred sources, skip known dead ends) and to record an --outcome useful|dead_end|corrected on save-result; the git post-commit/post-checkout hooks now auto-run reflect after each rebuild — best-effort and only when saved outcomes exist — so LESSONS.md stays current without a manual graphify reflect.graphify save-result gains optional --outcome useful|dead_end|corrected and --correction TEXT flags that record how a saved Q&A turned out — written to the memory doc's frontmatter and an ## Outcome body section so the signal both stays machine-readable and round-trips into the graph on the next semantic re-extraction. A new graphify reflect command then scans graphify-out/memory/ and writes a deterministic graphify-out/reflections/LESSONS.md an agent can load at the start of the next session — grouped by community when a graph.json is present, flat otherwise. Source nodes are scored, not counted: each citation is a signed, time-decayed value (useful positive, dead_end/corrected negative, configurable half-life via --half-life-days, default 30), so a fresh dead end outweighs a months-old useful. A node is only promoted to "preferred" once corroborated by ≥--min-corroboration distinct results (default 2) — one save can't mint a trusted lesson; the rest render as "tentative", and nodes with both positive and negative signals render once as "contested" with a recency-wins verdict. Source nodes are matched to the graph by label or id, and citations whose node no longer exists are dropped so stale lessons don't linger. Deterministic, no LLM; bare graphify save-result and all existing behavior are unchanged.graphify update now prunes a function/symbol removed from a still-present file without needing --force. The build already dropped the stale node (#1116), but the shrink-guard then refused to write the smaller graph ("new graph has N nodes but existing has M … Refusing to overwrite"), so the deletion silently never persisted unless you passed --force — leaving stale nodes (and the work-memory node-existence gate) lagging until a forced rebuild. The guard is now file-aware: a net shrink is allowed when every lost node belongs to a file re-extracted this run (or deleted), and still refused when a node disappears from a file that was not touched (the failed/partial-extraction case it exists to catch).validate_extraction and build_from_json no longer crash on a non-hashable node id or edge source/target (e.g. a list emitted by a malformed LLM extraction) — previously a single bad node raised TypeError: unhashable type and aborted the entire build of an otherwise-complete corpus. The validator now reports the bad id/endpoint as an error string (its documented contract), and the build skips the malformed entry with a stderr warning while keeping every well-formed node/edge; non-dict nodes are still left to raise so shape diagnostics are unchanged (#1447, thanks @dschwartzi).ClassName.method(...)) now produce an EXTRACTED calls edge to the class-qualified method node (#1446). Previously these cross-class static/qualified calls were dropped: the shared cross-file pass skips all member calls (the #543/#1219 god-node guard against bare obj.method() collisions), and when the called method shared its name with an in-file node — e.g. a viewset action approve() delegating to a service Service.approve() — the bare-name lookup matched the caller's own node and silently dropped it. The Python extractor now captures a simple-identifier receiver, defers capitalized-receiver member calls to a new receiver-based resolver (_resolve_python_member_calls, mirroring the Swift pass), and emits the edge only when the receiver resolves to exactly one class that owns the method (single-definition god-node guard); instance/module calls (self.x(), obj.x(), lowercase receivers) are unaffected.agents platform installs the skill to the generic cross-framework Agent-Skills locations. graphify install --platform agents (alias --platform skills) writes the spec's user-global ~/.agents/skills/graphify/SKILL.md — the directory npx skills and spec-compliant frameworks read — and --project writes ./.agents/skills/graphify/SKILL.md; graphify uninstall removes them. Previously that user-global location was only reachable as an accidental side effect of the gemini-on-Windows branch. The skill bundle re-homes amp's agents-md body (registered in tools/skillgen/platforms.toml, rendered through the skillgen drift/coverage guards); the body is identical to amp's, and only the on-demand hooks reference differs — it points at graphify agents install, which (as the amp-twin subcommand) wires the skill plus an AGENTS.md always-on section. Bare graphify install is unchanged — still single-platform (claude/windows) (#1432, closes #1405).Faster queries, a graph-health gate in the skill runbook, CUDA support, and a batch of extraction/install fixes.
Faster queries, a graph-health gate in the skill runbook, CUDA support, and a batch of extraction/install fixes.
graphify query/path/explain and the MCP query tools now narrow candidates via a trigram index (built once per graph, rebuilt on hot-reload) before the IDF scorer, cutting the previous O(N) scan on large graphs. The prefilter is a strict superset of the exhaustive scorer, so results and ranking are unchanged; short/CJK/low-selectivity queries fall back to the full scan (#1431, thanks @papinto).diagnose_extraction and surfaces dangling/self-loop/collapsed-edge warnings before labeling (read-only; never aborts). It also anchors the semantic cache on the scan root so cache hits survive a non-cwd scan (#1437, thanks @bahcgscateringsa-design)..cu/.cuh) is now extracted via the existing C++ (tree-sitter-cpp) grammar — no new dependency. Kernels, host functions, structs, includes and host call edges are captured; <<<grid, block>>> launch syntax parses cleanly (#1411).pkg_a_py_thing). The cross-file reference now resolves to the single canonical definition — fixed across all six language extractors (#1402, thanks @ZedUserdesign).graphify install --platform hermes installs to %LOCALAPPDATA%\hermes\skills on Windows (where Hermes scans), not ~/.hermes/skills (#1403, thanks @SHJordan).to_obsidian/to_canvas never emit punctuation-only filenames (e.g. @.md) that break downstream re-sluggers — they fall back to unnamed (#1409, thanks @Mylock51).graphify extract --cargo exits with a clear error instead of a traceback when Cargo.toml is missing/unreadable (#1428, thanks @DhruvTilva).F821 in prs.py via a TYPE_CHECKING import (#1429, thanks @DhruvTilva).norm_label, label_tokens, nid, and source_file), so results and ranking are unchanged; short/CJK queries and low-selectivity terms fall back to the full scan (#1431; thanks @papinto).diagnose_extraction and surfaces dangling/self-loop/collapsed-edge warnings before labeling, and anchors the semantic cache on the scan root (root='INPUT_PATH') so cache hits survive a non-cwd scan. Read-only — never aborts the build (#1437; thanks @bahcgscateringsa-design).graphify install --platform hermes now installs to the right directory on Windows. Hermes scans %LOCALAPPDATA%\hermes\skills, but the installer always used the POSIX ~/.hermes/skills (so on Windows the skill was never discovered). _platform_skill_destination gained a hermes branch that targets %LOCALAPPDATA%\hermes\skills on Windows and keeps ~/.hermes/skills elsewhere (#1403).def f(x: Thing) -> Thing) produced 1+N nodes — the extra ones with the referencing file's path baked into the id (pkg_a_py_thing). ensure_named_node minted a sourced stub for these cross-file refs, which _disambiguate_colliding_node_ids then collided into per-file ids and _rewire_unique_stub_nodes refused to collapse. The fallback now emits a sourceless stub (like the inheritance-base path), so the references resolve to the single canonical definition. Fixed uniformly across all six language extractors that share the helper (#1402)..cu/.cuh) source files are now extracted. CUDA is a C++ superset, so these files route through the existing C++ (tree-sitter-cpp) extractor — no new grammar dependency. __global__/__device__ kernels, host functions, structs and #includes are captured, host call edges are inferred, and <<<grid, block>>> kernel-launch syntax parses without error. Detection and file-watching follow automatically since both derive their extension sets from the dispatch table / CODE_EXTENSIONS (#1411).to_obsidian / to_canvas no longer emit punctuation-only filenames (e.g. @.md from a @/* tsconfig paths key). Such a file is valid on disk but empty once a downstream tool re-slugs on word characters (it crashes qmd update), so an all-punctuation label now falls back to unnamed (#1409; thanks @Mylock51).echo, where bash performed command substitution on the backticks — silently running graphify query "<question>" on every search. The reminder is now plain text (#1413; thanks @WSHAPER).graphify extract --cargo exits with a clear error instead of a traceback when Cargo.toml is missing or unreadable (the introspection now also catches OSError) (#1428; thanks @DhruvTilva).F821 (undefined nx) flagged in prs.py by importing networkx under TYPE_CHECKING for the quoted type annotation (#1429; thanks @DhruvTilva).Path-portability fixes so graphify-out/ artifacts are portable across clones and machines, plus a native-backend extraction fix.
Path-portability fixes so graphify-out/ artifacts are portable across clones and machines, plus a native-backend extraction fix.
manifest.json — the skill runbooks now relativize manifest keys to the scan root, so graphify --update matches cached files after a clone or move instead of re-extracting the whole corpus. (#1417)source_file relativized — build_from_json now relativizes graph.hyperedges[] like nodes and edges, so a semantic subagent's absolute path no longer leaks into graph.json. (#1418)'.' to report.generate; it now passes the real scan path, so GRAPH_REPORT.md no longer titles itself .. (#1419)GRAPHIFY_OUT honoured end-to-end — the custom output-dir override was only respected by some readers, so GRAPHIFY_OUT=custom-out graphify extract still wrote to graphify-out/. It's now resolved through a single graphify.paths module across extract, cluster-only, query/affected/benchmark, save-result, uninstall --purge, cache-check, the manifest/transcripts/memory/converted paths, the build_merge/serve/prs defaults, and the detect scan-exclude. Default behaviour is unchanged. (#1423)graphify extract --backend <gemini|claude|claude-cli|openai|kimi|…> never produced hyperedges (the prompt's schema showed an empty array and never described them), while the agent/skill path did. The native prompt now matches the skill spec, so both paths yield the same hyperedge behaviour. (#1430)graphify extract --backend <gemini|claude|claude-cli|openai|kimi|…> prompt (llm._EXTRACTION_SYSTEM) only ever showed "hyperedges":[] in its output schema and never explained what a hyperedge is, so every native backend silently emitted zero — while the agent/skill path (whose extraction-spec.md fully documents hyperedges) produced them. The two prompts had drifted. The native prompt now carries the same "3 or more nodes participate together" instruction and a populated schema example, so both extraction paths yield the same hyperedge behaviour for a given corpus. Verified end-to-end: a doc that previously produced 0 hyperedges now produces one (correctly relativized, per #1418). Guard tests assert the two prompts can't drift apart again.GRAPHIFY_OUT is now honoured end-to-end. The override (a custom output-dir name or absolute path, for worktrees/shared setups — #686) was only respected by some readers; graphify extract and several commands hardcoded graphify-out/, so a GRAPHIFY_OUT=custom-out graphify extract still wrote to graphify-out/, and downstream query/serve/update looked in the wrong place. The output-dir name is now resolved through graphify.paths everywhere it matters: the extract write dir, cluster-only/label, query/affected/benchmark defaults, save-result --memory-dir, uninstall --purge, cache-check, the manifest.json/transcripts/memory/converted paths in detect/transcribe, the build_merge/serve/benchmark/prs graph-path defaults, and the detect scan-exclude (so a renamed output dir is never re-ingested as source). Default behaviour is unchanged — without the env var everything still uses graphify-out/ (#1423).GRAPH_REPORT.md header now shows the actual scan root instead of a literal .. The split-skill runbook passed '.' as the root argument to report.generate in Steps 4 and 5, so a /graphify /some/path run produced a report titled # Graph Report - .. It now passes 'INPUT_PATH' (matching the monoliths, which were already correct). Display-only — no path written to graph.json/manifest.json was affected (#1419).manifest.json. Step 9 (full build) and the --update reference called save_manifest(...) without root=, so manifest keys were stored as absolute paths; cloning or moving the repo then broke graphify --update — every cached file missed and the whole corpus re-extracted. All four runbook call sites (the lean-core skill.md, the Aider/Devin monoliths, and the shared --update reference) now pass root='INPUT_PATH', relativizing keys to the scan root to match the native graphify update path. The monolith change is registered as a new sanctioned change-class in the round-trip guard (#1417).source_file is now relativized to the scan root like nodes and edges. build_from_json(root=...) relativized source_file on nodes[] and links[], but stored graph.hyperedges[] verbatim, so a semantic subagent's absolute path (e.g. /Users/.../CLAUDE.md) leaked into graph.json. The fix lives in build_from_json (not to_json, which has no root to relativize against) and mirrors the existing node/edge handling (#1418).GRAPHIFY_OUT override is now honoured everywhere instead of a hardcoded "graphify-out" literal. The name is consolidated into a single graphify.paths module (was duplicated across __main__, cache, and watch); security.validate_graph_path's base=None discovery + fallback, callflow_html's project-root resolution, and the post-commit/post-checkout hook bodies (which now read the env var at hook-run time) all use it. Previously a renamed output dir validated against the wrong base or made the hook miss .graphify_root (#1423).directed=IS_DIRECTED into every build_from_json call (a --directed run no longer collapses reciprocal edges), scope semantic extraction to document/paper/image (code is covered by the AST pass), delete .graphify_cached.json on a cache miss, and run Step 4's zero-node guard before any write with the report/analysis gated on to_json actually persisting the graph (#1392).Skill-runbook correctness and a crash fix.
Skill-runbook correctness and a crash fix.
Crash & data-loss tier:
graphify-out/), not the scanned dir, so a non-cwd scan no longer produces "no nodes"..graphify_semantic.json before Part C, fixing a FileNotFoundError.--cluster-only relies on the self-contained graphify cluster-only CLI instead of re-running steps that read already-deleted intermediate files.GRAPH_REPORT.md/analysis are written only when to_json actually persisted the graph (respects the #479 shrink-guard).Remaining correctness tier:
--directed is propagated as directed= into build_from_json (build + rebuild) and build_merge (--update merge + diff), so a --directed / --directed --update run no longer collapses reciprocal edges into an undirected graph.document/paper/image only (code is covered by the AST pass), so subagents stop re-reading every source file..graphify_cached.json is deleted on a cache miss, so a stale cache from a prior run is never merged.--update transcribes changed video files into documents before the semantic pipeline.write_text, honours GRAPHIFY_WHISPER_MODEL/GRAPHIFY_WHISPER_PROMPT, prints status to stderr.add-watch/exports use the resolved interpreter explicitly; MCP Desktop config documents the absolute interpreter path.FileSlice units. The 0.8.43 #1386 fix coerced every item with Path(f), which raised TypeError on the FileSlice objects from the oversized-text slicing path (#1369). Items are coerced only when not already a Path/FileSlice (#1397, #1399).Full changelog: https://github.com/safishamsi/graphify/blob/v8/CHANGELOG.md
.graphify_root) but the merge globs cwd graphify-out/, so a non-cwd scan produced "no nodes"; chunk paths are now derived from cwd. (2) Code-only corpora skipped Part B but Part C reads .graphify_semantic.json unconditionally, raising FileNotFoundError; the fast path now writes an empty semantic file first. (3) --cluster-only told the agent to re-run Steps 5-9, which read intermediate files a prior cleanup deleted (FileNotFoundError); it now relies on the self-contained graphify cluster-only CLI. (4) Step 4's zero-node guard ran after GRAPH_REPORT.md/graph.json/analysis were written, and GRAPH_REPORT.md was written before to_json's #479 shrink-guard; the guard now runs before any write and the report/analysis are written only when to_json actually persisted the graph.--directed is now propagated as directed=IS_DIRECTED into build_from_json (Step 4 + Step 5 rebuild) and build_merge (the --update merge + diff), so a --directed (and --directed --update) run no longer silently rebuilds undirected and collapses reciprocal A<->B edges. Semantic extraction flattens only document/paper/image (code is already covered structurally by the AST pass) so subagents stop re-reading every source file. .graphify_cached.json is deleted on a cache miss so Part C never merges a stale cache from a prior run. --update now transcribes changed video files and moves the transcripts into documents before the semantic pipeline. Transcription writes via write_text (no shell redirect), honours GRAPHIFY_WHISPER_MODEL/GRAPHIFY_WHISPER_PROMPT, and prints status to stderr. add-watch and exports use the resolved interpreter explicitly, and the MCP Desktop config documents the absolute interpreter path. Extraction-spec example id is namespaced; query term split keeps tokens of 3+ chars.FileSlice units. The 0.8.43 #1386 fix coerced every item with [Path(f) for f in files], which raised TypeError on the FileSlice objects produced by the oversized-text slicing path (#1369), so a corpus containing a document large enough to be sliced crashed on extract. Items are now coerced only when they are not already a Path or FileSlice (#1397, #1399).Bumped vulnerable dependencies to patched versions: pypdf 6.11.0→6.13.3 (CVE-2026-48155/48156), yt-dlp 2026.3.17→2026.6.9, pyjwt 2.12.1→2.13.0, crypto…
pip install -U graphifyy / uv tool upgrade graphifyy.
Two new capabilities plus a security update and four fixes.
apm.yml, pyproject.toml, go.mod, and pom.xml are now parsed deterministically into one canonical package node per package (keyed by name) plus depends_on edges, routed to the AST path so the LLM never sees them. Previously apm.yml was an LLM-handled document, so a package got a different file-anchored id from its own manifest than from each dependent's reference and split into duplicate nodes — now a package referenced from many manifests is a single hub. (#1377)[text](./other.md) links, reference-style links, and [[wikilinks]] in markdown are now resolved (relative to the source file; external URLs / anchors / images skipped) and emitted as references edges between docs, so hub docs like index.md / table-of-contents.md actually become hubs. (#1376)pypdf 6.11.0→6.13.3 (CVE-2026-48155/48156), yt-dlp 2026.3.17→2026.6.9, pyjwt 2.12.1→2.13.0, cryptography 48.0.0→49.0.0, python-multipart 0.0.28→0.0.32, with lower-bound floors for the direct deps so installs get the patched versions. (#1375 — thanks @hypnwtykvmpr)extract_corpus_parallel, extract_files_direct) crashed on str paths instead of pathlib.Path; both now coerce at entry. (#1386)graphify hook install no longer creates a backslash-named junk directory and reports false success when core.hooksPath is a Windows-style path under WSL — such paths are now rejected with a clear error. (#1385)graphify.ids module; the four hand-synced copies (extract, build, mcp_ingest, symbol_resolution) — root of the recurring ghost-node bug class — now share one guarded implementation. (#1378 — thanks @danielnguyenfinhub)Full changelog: https://github.com/safishamsi/graphify/blob/v8/CHANGELOG.md
apm.yml, pyproject.toml, go.mod, and pom.xml each yield ONE canonical package node per package (keyed by name) plus depends_on edges, routed to the AST path so the LLM never sees them. Previously apm.yml was an LLM-handled document, so the same package got a different file-anchored id from its own manifest than from each dependent's dependency reference and split into duplicate nodes; a package referenced from N manifests is now a single hub node (#1377).extract_markdown only emitted heading nodes + contains edges and never parsed link syntax, so a doc full of [text](./other.md) links (e.g. index.md, table-of-contents.md) had no edges to what it links and never became a hub. Inline links, reference-style links, and [[wikilinks]] are resolved relative to the source file (external URLs / in-page anchors / images skipped) and emitted as references edges, with targets resolved via the same node-id recipe so they merge onto the real doc node (#1376).pypdf 6.11.0→6.13.3 (CVE-2026-48155/48156), yt-dlp 2026.3.17→2026.6.9, pyjwt 2.12.1→2.13.0, cryptography 48.0.0→49.0.0, python-multipart 0.0.28→0.0.32 — with lower-bound floors for the direct deps (pypdf, yt-dlp) so installs get the patched versions (#1375; thanks @hypnwtykvmpr).extract_corpus_parallel, extract_files_direct) crashed with AttributeError when passed str paths instead of pathlib.Path. Both now coerce files = [Path(f) for f in files] at entry (#1386).graphify hook install no longer creates a literal backslash-named junk directory and reports false success when core.hooksPath (or git rev-parse --git-path hooks) is a Windows-style path under WSL. Drive-letter / embedded-backslash hooks paths are now rejected with a clear error (#1385).graphify.ids module. extract._make_id, build._normalize_id, mcp_ingest._make_id, and symbol_resolution._bash_make_id were four hand-synced copies of the same NFKC/casefold recipe — the root of the recurring ghost-node bug class (#811/#550/#1033/#1104). All four now delegate to one implementation guarded by contract + hypothesis property tests (#1378; thanks @danielnguyenfinhub).Ships a live regression fix and a security fix from 0.8.41, plus two correctness/feature fixes.
Bug-fix release. pip install -U graphifyy / uv tool upgrade graphifyy.
Ships a live regression fix and a security fix from 0.8.41, plus two correctness/feature fixes.
🔴 /graphify --update no longer deletes a changed file's freshly re-extracted nodes. The 0.8.41 root= fix (#1361) made build_merge's prune actually match relative source_file values — which then matched the just-re-extracted nodes of changed files (still in prune_sources) and removed them, so --update on a changed file could wipe its nodes. The update runbook now prunes only genuinely deleted files; changed files are reconciled by build_merge's replace-on-re-extract (#1344). The full build also passes root= to build_from_json, and the extraction-spec source_file is pinned to the verbatim path, so full build and incremental updates never drift. (#1366 — thanks @RelywOo)
🔒 Security: .graphifyignore and .gitignore are now merged per directory instead of .graphifyignore silently replacing the dir's .gitignore. Previously, adding a .graphifyignore disabled that directory's .gitignore entirely, so a file excluded only by .gitignore — including neutrally-named secrets like prod-dump.sql or customer-data.json — got indexed into the graph (whose artifacts embed file contents and are routinely committed). .gitignore is read first and .graphifyignore last, so it still wins on conflict (including ! negations); adding one can only ever exclude more. (#1363)
Java record declarations are now first-class type nodes, and new Foo(...) constructor calls produce a calls edge. Previously a record appeared only as its file node (degree 0). (#1373)
Large text documents are no longer silently truncated during semantic extraction. _read_files capped every file at 20,000 chars and dropped the rest; oversized Markdown/text/rST docs are now sliced at heading/paragraph boundaries so the whole file is extracted (slices share the parent source_file, so the graph isn't fragmented). Code files and PDFs are never sliced. (#1369)
Full changelog: https://github.com/safishamsi/graphify/blob/v8/CHANGELOG.md
_read_files capped every file at 20,000 characters, so a Markdown/text/rST document longer than that had everything past the cap dropped — the model never saw it, and the packer/adaptive-retry path couldn't recover ("packing can't shrink one big file"). Oversized splittable-text files are now sliced at heading/paragraph boundaries into units that each fit the cap and together cover the whole file; every slice reports its parent file as source_file, so the graph is not fragmented per-slice. A single slice that still overflows the model's output is bisected and retried. Code files and PDFs are never sliced (they keep whole-symbol / page handling). (#1369)/graphify --update no longer deletes a changed file's freshly re-extracted nodes. The 0.8.41 root= fix (#1361) made build_merge's prune actually match relative source_file values — which then matched the just-re-extracted nodes of changed files (still listed in prune_sources) and removed them, so an --update on a changed file could wipe its nodes. The update runbook now prunes only genuinely deleted files; changed files are reconciled by build_merge's replace-on-re-extract (#1344). The full build also now passes root= to build_from_json, and the extraction-spec source_file is pinned to the verbatim path, so the full build and incremental updates never drift on node-key base. (#1366; thanks @RelywOo)record declarations are now modeled as first-class type nodes (they share class_declaration's name/body/interfaces fields), and new Foo(...) constructor calls now produce a calls edge to the constructed type. Previously a record appeared only as its file node (degree 0) with no incoming edges, and body-level new usages were dropped because object_creation_expression wasn't a recognized call type and its callee lives in the type field rather than name. (#1373).graphifyignore and .gitignore are now merged per directory instead of .graphifyignore silently replacing that directory's .gitignore. Previously, adding a .graphifyignore (e.g. to exclude media) disabled the dir's .gitignore entirely, so a file excluded only by .gitignore — including neutrally-named secrets like prod-dump.sql or customer-data.json that the sensitive-file heuristic doesn't catch — got indexed into the graph, whose artifacts embed file contents and are routinely committed. .gitignore is read first and .graphifyignore last, so .graphifyignore patterns (including ! negations) still win on conflict; adding one can only ever exclude more, never re-include a .gitignore-excluded file. (#1363)Bug-fix release. pip install -U graphifyy / uv tool install graphifyy@0.8.41.
Bug-fix release. pip install -U graphifyy / uv tool install graphifyy@0.8.41.
Swift cross-file class relationships now resolve through member calls and constructors. A per-file type table (from property/parameter declarations + constructor inference) types the receiver of recv.method(), Type.staticMethod(), Singleton.shared.method(), and self.prop.method(), and property/field initializers (let vm = VM()) are walked for constructor calls. Edges emit only when the receiver's type resolves to exactly one definition (preserving the god-node guards) and are tagged INFERRED. (#1356)
/graphify <path> --update prunes stale nodes correctly. The update runbook called build_merge without root=, so absolute prune paths never matched the graph's relative source_file values — nothing was pruned and changed/deleted files left ghost nodes that compounded every run. (#1361)
export obsidian no longer writes an empty 32-byte graph.canvas on a populated graph. to_canvas now falls back to one synthetic community covering every node when no community data is present (e.g. --no-cluster builds). (#1324)
Edges missing source_file (occasionally emitted by the semantic/LLM extractor) are now backfilled from their endpoint nodes in build_from_json and the --no-cluster path, so they no longer reach graph.json without a file reference. (#1279)
Fuzzy dedup no longer over-merges distinct nodes in three cases: numbered/versioned siblings (ADR 0011 vs ADR 0013), cross-file rationale/document boilerplate (file-anchored like code), and cross-file labels sharing a long prefix but diverging in a distinguishing token (testing-library jest-native vs react-native, now scored on plain Jaro). Same-file near-duplicates still merge. (#1284 — thanks @van4oza, #1243)
OpenAI-compatible backends (ollama, openai, deepseek, kimi) now honour their configured 16384 output-token cap instead of silently falling back to 8192, which had been truncating deep-mode JSON mid-string. GRAPHIFY_MAX_OUTPUT_TOKENS still overrides. (#1365)
Full changelog: https://github.com/safishamsi/graphify/blob/v8/CHANGELOG.md
ollama, openai, deepseek, kimi) now honour their configured 16384 output-token cap instead of silently falling back to 8192. The dispatch read a max_completion_tokens config key that only gemini defines; the others set max_tokens, so their advertised cap was dead and deep-mode JSON truncated mid-string (recovered by the adaptive bisect, but noisy and slower). The dispatch now reads either key, and the openai config gained an explicit cap. GRAPHIFY_MAX_OUTPUT_TOKENS still overrides. (#1365)ADR 0011 vs ADR 0013, 3.1 Product Goals vs 1.1 Product Goals, 40%+ … vs <20% …) never merge. (2) rationale/document nodes are file-anchored like code (#1205's reasoning): near-identical docstring/heading boilerplate in parallel files no longer collapses across files, while same-file duplicates still merge. (3) Cross-file labels that share a long prefix but diverge in a distinguishing token (testing-library jest-native vs react-native) are scored on plain Jaro instead of Jaro-Winkler, so the leading-prefix bonus can no longer fabricate a merge; genuine cross-file duplicates still clear the bar on Jaro alone, and same-file near-duplicates keep Jaro-Winkler. Guards are mirrored into the --dedup-llm ambiguous-pair collection. (#1284 thanks @van4oza, #1243)recv.method() / Type.staticMethod() / Singleton.shared.method() / self.prop.method(), and property/field initializers (let vm = VM()) are now walked for constructor calls. Edges are emitted only when the receiver's type resolves to exactly one definition (preserving the #543/#1219 god-node guards) and are tagged INFERRED; the blanket member-call skip in the shared call pass is untouched (#1356)./graphify <path> --update now prunes stale nodes correctly. The update runbook called build_merge(prune_sources=...) without root=, so the absolute prune paths were never relativized to match the graph's relative source_file values — nothing was pruned and changed/deleted files left ghost nodes that compounded on every incremental run. The shared skill fragment now passes root (the native graphify update CLI was already correct) (#1361).export obsidian no longer writes an empty 32-byte graph.canvas on a populated graph. to_canvas built cards solely by iterating communities, so a graph with no community data (--no-cluster builds, or a missing analysis sidecar) produced the empty {"nodes": [], "edges": []} shell while the markdown notes rendered fine. It now falls back to one synthetic community covering every node (#1324).source_file field (occasionally emitted by the semantic/LLM extractor, which build only normalized when the field was already present) are now backfilled from the edge's endpoint nodes in build_from_json and the --no-cluster raw-write path, so they no longer reach graph.json without a file reference or trip validation (#1279).cli.md / cli-inline.md so no platform got both — Claude had the superior expansion but no CLI-down fallback, while all other platforms had the fallback but the weaker raw-question matcher. The two fragments are merged into one unified query reference (and stub) shipped to all hosts; the query_variant enum and its coverage-audit exemption are removed (#1325; thanks @LeanderBlume).implements/inherits/imports edges no longer orphan onto bare "shadow" nodes when two packages define a same-named type. The referencing file's import statement now disambiguates by exact package (FQN) and re-points the edge to the real definition, dropping the orphan stub. Previously _rewire_unique_stub_nodes could only repair the globally-unique case, so same-named interfaces (common in large Java codebases — Handler, Service, interface+impl pairs) left the real definition isolated in its own community (#1318).type=module node instead of N path-qualified duplicates. The import target is tagged type=module and exempted from id-disambiguation, so reverse traversal ("what imports CoreKit?") works; the --no-cluster writer also now dedupes nodes by id (and edges) to match the clustered build_from_json path. Builds on the v0.8.40 Swift-import fix (#1327, #1330; thanks @duncan-daydream).Feat: custom OpenAI- and Anthropic-compatible endpoints. OPENAI_BASE_URL/OPENAI_MODEL and ANTHROPIC_BASE_URL/ANTHROPIC_MODEL point either backend at a
OPENAI_BASE_URL/OPENAI_MODEL and ANTHROPIC_BASE_URL/ANTHROPIC_MODEL point either backend at a self-hosted or proxy server (vLLM, llama.cpp, LM Studio, LiteLLM, gateways); defaults preserve api.openai.com/api.anthropic.com and GRAPHIFY_OPENAI_MODEL still wins over OPENAI_MODEL. Wired through extraction and community labeling (#1273)..psm1 modules indexed. .psm1 was missing from CODE_EXTENSIONS and the dispatch table, so modules and their dependents were silently absent from the graph (#1315). .psd1 manifests and Import-Module/dot-source edges remain a follow-up.this.X = () => {} / this.X = function(){}, exports.X/module.exports.X, Foo.prototype.X, class arrow/function fields, and const f = function(){} expressions. Constructor-style and CommonJS codebases (DAOs, route handlers, services) previously lost most callable symbols. Arbitrary obj.x = fn stays excluded (preserves the #1077 god-node guard) (#1322).import edges no longer silently dropped. The import target previously had no backing node, so build.py pruned 100% of Swift imports; the module node is now synthesized so the edge survives (#1327).--no-cluster / incremental update no longer accumulate duplicate edges. These paths bypass the DiGraph that collapses parallel edges, so repeated update grew edge counts and diverged across build modes. Edges are now deduped by (source, target, relation) — deterministic and idempotent (#1317).cluster-only. query/explain/MCP now surface the human-readable community name instead of a blank/numeric id; backward-compatible with old graphs (#1305).graphify-mcp and graphify.serve accept --graph <path> as an alias for the positional arg, consistent with other subcommands (#1304).ProcessPoolExecutor cap now applies to all worker-count paths (#1298).(basename, label) collisions so same-named symbols in same-named files across directories no longer mis-point edges (#1257)..graphify_version (restricted installs / network mounts) — FS probes wrapped in try/except OSError (#1299).prs.py claude-cli backend resolves claude.cmd on Windows (WinError 2 on npm installs) (#1288).save_manifest file hashing parallelized with a ThreadPoolExecutor (#1295)._walk_js_tree converted from recursive generator to iterative walk (#1294).security-scan job added as non-blocking advisory signal (continue-on-error) (#1209).docs/node-summaries-rfc.md) (#1166).Feat: custom OpenAI- and Anthropic-compatible endpoints via OPENAI_BASE_URL/OPENAI_MODEL and ANTHROPIC_BASE_URL/ANTHROPIC_MODEL. Point either backend at a self-hosted or proxy server (vLLM, llama.cpp, LM Studio, LiteLLM, gateways); defaults still resolve to api.openai.com / api.anthropic.com, and GRAPHIFY_OPENAI_MODEL keeps precedence over OPENAI_MODEL. Wired through both the extraction path (_call_claude) and community labeling (#1273).
Feat: PowerShell .psm1 modules are now indexed. .psm1 was absent from CODE_EXTENSIONS and the extractor dispatch table, so modules — and their dependents — were silently missing from the graph; they parse cleanly with the existing PowerShell grammar (#1315). (.psd1 manifests and Import-Module / dot-source import edges remain a follow-up.)
Fix: Swift import edges are no longer silently dropped. _import_swift emitted an edge to a bare module id with no backing node, so build.py pruned 100% of Swift imports. The target module node is now synthesized (new opt-in LanguageConfig.synthesize_import_module_nodes) so the edge survives the build (#1327).
Fix: --no-cluster and incremental graphify update no longer accumulate duplicate edges. These paths bypass the NetworkX DiGraph that collapses parallel edges, so repeated update grew the edge count every run and counts diverged across build modes. Edges are now deduped by (source, target, relation) before writing — deterministic and idempotent; the no-cluster rebuild log now reports the written (deduped) edge count instead of the raw pre-merge count (#1317).
Perf: save_manifest file hashing parallelized with a ThreadPoolExecutor (#1295).
Perf: _walk_js_tree converted from a recursive generator to an iterative walk, reducing overhead on large JS/TS trees (#1294).
Feat: JS/TS AST now extracts function symbols defined via this.X = () => {} / this.X = function(){} (constructor-assigned methods), exports.X = fn / module.exports.X = fn, Foo.prototype.X = fn, class arrow/function fields (class C { onClick = (e) => {} }), and const f = function(){} function expressions. Previously only top-level function declarations, top-level const x = () => arrows, classes, and method shorthand were captured, so the majority of callable symbols in constructor-style and CommonJS codebases (DAOs, route handlers, services) never became nodes and could not be call-edge endpoints. Arbitrary obj.x = fn is deliberately not captured, preserving the #1077 phantom-god-node guard (#1322).
Fix: graphify query, graphify explain, and MCP query_graph/get_node now show the human-readable community name (e.g. "FlashAttention Paper") instead of a blank or numeric ID after running cluster-only. to_json now accepts community_labels and embeds community_name on each node; read paths fall back to the numeric community field for backward compatibility with old graphs (#1305).
Fix: graphify-mcp and python -m graphify.serve now accept --graph <path> as an alias for the positional argument, consistent with every other graphify subcommand. Previously --graph raised "unrecognized arguments" (#1304).
CI: bandit (MEDIUM+ severity) and pip-audit security scans added as a non-blocking security-scan job. Both run with continue-on-error: true so they never break CI — advisory signal only, with the intent to remove the gate once pre-existing findings are triaged.
Docs: RFC for file-level node summaries added (docs/node-summaries-rfc.md). Proposes inline graph.json attribute vs sidecar storage options with pros/cons, phased implementation plan, and open questions for maintainer decision.
Fix: AST extraction no longer crashes on Windows machines with >61 logical cores. ProcessPoolExecutor on Windows is hard-capped at 61 workers via WaitForMultipleObjects; the clamp now applies to all three input paths (auto-compute, GRAPHIFY_MAX_WORKERS, --max-workers) (#1298).
Fix: ghost-merge skips ambiguous (basename, label) collisions where two AST nodes share the same key. When same-named symbols appear in same-named files across different directories (e.g. two render() in two index.ts), the previous last-writer-wins produced an arbitrary canonical node and mis-pointed all edges. Ambiguous keys are now tracked and skipped (#1257).
Fix: startup no longer crashes on unreadable .graphify_version files. On restricted-permission installs or network mounts, .exists() / .read_text() raised PermissionError and crashed every graphify query/explain/path call. All three FS probes now wrapped in try/except OSError: return (#1299).
Fix: prs.py claude-cli backend resolves claude.cmd on Windows. The _call_llm and _call_claude_cli extraction paths were already fixed; prs.py had the same bare ["claude", ...] subprocess call that fails on Windows npm installs with WinError 2 (#1288).
Perf: O(n²)→O(n) LSH neighbor lookup in dedup. candidates_by_id dict replaces inner O(n) scan per neighbor; norm_cache avoids re-normalising labels on
candidates_by_id dict replaces inner O(n) scan per neighbor; norm_cache avoids re-normalising labels on every comparison.affected handles "edges"-keyed graph.json (KeyError: 'links' crash). Normalised using the established pattern in __main__.py/serve.py..graphifyignore single ! negation no longer disables all directory pruning. Pure performance fix — the descent was always wasted since gitignore can't rescue files beneath an excluded parent.merge-chunks prints node count instead of raw list object.except: pass replaced with corrupt-file backup + stderr warning.graphify export falkordb --push redis://localhost:6379 (optional dep: uv tool install "graphifyy[falkordb]").--model flag for graphify cluster-only and graphify label. Override the model used for community naming independently of the backend.--model examples added to cluster-only/label command section.Perf: O(n²)→O(n) LSH neighbor lookup in deduplicate_entities. The inner scan next(n for n in candidates if n["id"]==neighbor_id) was O(n) per neighbor; replaced with a candidates_by_id dict built once per pass. Also adds a norm_cache to avoid re-normalising labels on every comparison.
Fix: graphify merge-chunks summary now prints the node count instead of the raw list object. global_graph.py printed merged['nodes'] (the list) instead of len(merged['nodes']).
Fix: manifest data-loss on corrupt ~/.graphify/manifest.json. A parse error previously triggered except Exception: pass, silently returning an empty manifest and overwriting the file — wiping all tracked repos. The corrupt file is now renamed to a timestamped .corrupt.<ts> backup with a stderr warning before starting fresh.
Fix: tree-sitter grammar packages now have pinned upper-bound version ranges in pyproject.toml. Grammar packages routinely break node-type and field APIs across minor bumps; ceilings prevent silent breakage on future upgrades.
Feat: FalkorDB export backend. graphify export falkordb --push redis://localhost:6379 pushes the graph to a FalkorDB instance. Optional dep (uv tool install "graphifyy[falkordb]"); lazy import; idempotent (MERGE semantics); Cypher injection guarded.
Fix: affected and graphify query now handle graph files that use "edges" as the top-level key instead of "links". Graphs produced by native graphify extract on some corpus layouts used "edges"; loading them in affected.py raised KeyError: 'links'. Normalised using the same established pattern already in __main__.py and serve.py.
Fix: a single ! negation rule in .graphifyignore no longer disables all directory pruning. Previously any negation pattern caused collect_files to descend every ignored directory to look for re-included files. Since gitignore semantics cannot rescue files beneath an excluded parent, this descent was always wasted — the per-file filter still excluded them. Pruning now proceeds unconditionally; only the final per-file _is_ignored check is consulted for negation.
Feat: --model flag added to graphify label-communities and graphify cluster-only. Routes through generate_community_labels → label_communities → _call_llm; defaults to None (keeps existing backend default). Also fixes a latent arg-parsing bug where --backend gemini (space-separated) was mis-parsed as the positional path argument.
Docs: Persian (فارسی) README translation added (docs/translations/README.fa-IR.md).
13 fixes and improvements merged in this release.
13 fixes and improvements merged in this release.
calls edges now have correct direction. The extraction prompt never stated source = caller / target = callee — the LLM systematically emitted callee→caller. Explicit direction rule added to prompt. Ghost-node merge extended to collapse LLM bare-stem duplicates onto AST canonical nodes even when the LLM node has a source_location. Post-fix annotation: calls precision 100% (n=6), overall INFERRED precision 94% (n=16).export default class Foo imported as import Foo from './foo' previously got only a file→file edge. The class node now receives a proper imports edge and call resolution through renamed bindings works correctly.paths aliases resolved relative to baseUrl. NestJS, monorepo, and similar layouts using baseUrl: "./src" now resolve @/* aliases correctly.(basename, label) keys. Two same-named symbols in different files (e.g. two render functions) no longer get mis-merged.resolve_seed matches bare names against callable-decorated labels. A query for render now finds nodes labelled .render().collect_files is now a single pruned os.walk. Replaces ~85 redundant rglob passes; noise directories are pruned before descent.--out root. graphify extract ./src --out /tmp/out no longer leaks graphify-out/ into the scanned project.--- line. Thematic breaks (----) and YAML title lines (--- title: foo) no longer trigger false frontmatter parsing.claude-cli spawns claude.cmd on Windows headless installs. Adds CREATE_NO_WINDOW to both subprocess spawn sites.claude-cli handles JSON-array envelope from Claude Code CLI ≥ 2.1.graphify extract ./my-workspace --cargo maps Rust workspace crates and internal crate_depends_on edges.with MyMixin now correctly emits mixes_in (not implements).calls edges now have correct direction. The extraction prompt previously never stated that source = caller and target = callee; the LLM systematically emitted callee→caller edges. An explicit direction rule was added to the prompt. Separately, ghost-node merge was extended to collapse LLM duplicate nodes (bare-stem IDs) onto AST canonical nodes (parent-qualified IDs) even when the LLM node carries a source_location — the old check only caught source_location=None ghosts. Post-fix annotation: calls precision 100% (n=6), overall INFERRED precision 94% (n=16).export default class Foo imported as import Foo from './foo' got just a file→file imports_from edge — the class node received no incoming symbol edge. On codebases that default-export most classes (NestJS services/helpers/models, etc.) this left those symbols looking like isolated leaf nodes and made graphify affected "<Class>" / explain report no callers. Default imports are now recorded with imported_name="default", export default <class|function|identifier> registers a "default" export, and the existing resolver wires the imports edge (and resolves calls through the local binding, even when renamed). Anonymous defaults (export default class {}) remain file-level only.paths aliases now resolved relative to baseUrl. Previously all @/* → src/* aliases were resolved from the config file's directory regardless of baseUrl, breaking path alias import resolution for NestJS, monorepo, and similar layouts that set baseUrl: "./src".(basename, label) keys where multiple AST nodes share the same pair. Previously the last-writer-wins pass silently mis-merged one of two same-named symbols in different files (e.g. two render functions across separate modules), re-pointing edges to the wrong canonical node.resolve_seed now matches bare query terms against callable-decorated node labels. A query for "render" failed to seed nodes whose label was stored as ".render()" because the string comparison was exact. Bare names are now tried against stripped labels as a fallback after exact and prefix matches.graphify-out/cache/ast/<version>/. Semantic cache is deliberately unversioned (LLM calls are expensive to re-run).requests), one copy was pruned but edges still pointed to the removed node ID, creating dangling references. Edges are now remapped through the dedup remap table before insertion.(node, neighbor) pair. The previous code unioned both normalised-label groups before calling _pick_winner, allowing an unrelated same-named node from a different file to be dragged into the merge and supplant the correct winner.--out directory root instead of leaking a graphify-out/ into the scanned project. Using graphify extract ./src --out /tmp/out previously wrote cache files into ./src/graphify-out/cache/ alongside source files. Cache now writes exclusively under the --out path.collect_files rewritten as a single pruned os.walk instead of one rglob per extension. On large repos this eliminated ~85 redundant filesystem traversals; noise directories (node_modules, .git, __pycache__, etc.) are now pruned before descent, preventing those subtrees from being scanned at all.--- line (regex ^---[ \t]*\r?$). Thematic break lines (----) and YAML documents that open with --- title: foo were incorrectly parsed as frontmatter delimiters, causing cache hash instability on Markdown files with those constructs.claude-cli backend now spawns claude.cmd on Windows headless installs. npm-installed Claude Code ships a .cmd shim on Windows; CreateProcess cannot run it without the explicit extension. shutil.which("claude.cmd") is now tried first on win32, mirroring the fix already applied to the extraction path. CREATE_NO_WINDOW flag added to both subprocess spawn sites to prevent console windows flashing during headless runs.claude-cli backend handles JSON-array envelope from Claude Code CLI ≥ 2.1. Older CLI versions returned a single JSON object; 2.1+ wraps the response in a streaming array of events. The envelope parser now accepts both shapes, preferring the last {"type":"result"} event from an array.graphify extract ./my-workspace --cargo introspects Cargo.toml files across a Rust workspace and emits crate:<name> nodes with crate_depends_on edges for workspace-internal dependencies. Registry dependencies are excluded. Handles virtual workspaces, root-package workspaces, glob members, and {workspace = true} inherited deps..sv/.svh files. Dart with MyMixin clauses now emit mixes_in edges (was incorrectly implements), consistent with PHP and Scala.SSRF guard thread-safety: Replaced the global socket.getaddrinfo monkey-patch with per-connection _SSRFGuardedHTTPConnection/_SSRFGuardedHTTPSConnecti
socket.getaddrinfo monkey-patch with per-connection _SSRFGuardedHTTPConnection/_SSRFGuardedHTTPSConnection subclasses. DNS is resolved once, IP validated, connection made to that exact address — closes the concurrent-thread race and the underlying TOCTOU gap.<untrusted_source path="..." sha256="..."> XML delimiters during LLM extraction. Jailbreak sentinel tokens neutralised; system prompt hardened.KeyError when a community contains a node absent from the graph — dangling members now skipped gracefully.--update re-extracted all Office files on every run on HFS+/APFS.package.json, tsconfig.json, etc.). Data JSON files (arrays, generic key/value) are skipped by the AST pass — eliminates 561+ orphan nodes on real repos.temperature=0 is now auto-omitted for o1/o3/o4/gpt-5 series. Override with GRAPHIFY_LLM_TEMPERATURE.datasketch and scipy removed. Replaced with a self-contained pure-numpy MinHash/MinHashLSH implementation — eliminates the numpy.testing → platform.machine() subprocess spawn at import time that EDR software (CrowdStrike, SentinelOne) was intercepting.Config class in app.py and Config class in db.py are no longer collapsed. Code nodes are now deduplicated by ID only.fnmatch calls on 2k+ file corpora.GRAPHIFY_MAX_GRAPH_BYTES: Override the 512 MiB graph.json size cap (e.g. 700MB, 2GB). The cap error message now cites this env var. graphify export html auto-falls back to community-aggregation view when over cap.CLAUDE.md instructions: "MANDATORY: Before using Read/Grep/Glob/Bash you MUST run graphify first" — includes explicit instruction to forward the rule to every subagent prompt.GRAPHIFY_LLM_TEMPERATURE: Override LLM temperature for any backend (none to omit entirely).graphify-self-graph.tar.gz — graph.json + graph.html + GRAPH_REPORT.md from Graphify analysing its own source. Download and open graph.html locally to see what Graphify produces, no install required.uv tool upgrade graphifyy
# or
pip install --upgrade graphifyy
socket.getaddrinfo monkey-patch is replaced with per-connection _SSRFGuardedHTTPConnection/_SSRFGuardedHTTPSConnection subclasses that resolve DNS once, validate the IP, and connect to that exact address — closing both the concurrent-thread race window and the underlying TOCTOU gap. No global state is mutated, so sibling threads (MCP server, PR triage pool) are unaffected.<untrusted_source path="..." sha256="..."> XML delimiters; jailbreak sentinel tokens (<|im_start|>, [INST], <<SYS>>, forged closing tags) are neutralised with a zero-width space; the extraction system prompt includes an explicit SECURITY block stating that content inside the wrapper is inert data.export obsidian and export canvas no longer crash with KeyError when a community contains a node ID absent from the graph (stale community index, merge artifacts). Dangling members are silently skipped.--update on macOS no longer re-extracts all Office files on every run. convert_office_file() now NFC-normalises the source path before hashing the sidecar filename, so NFD paths returned by os.walk (HFS+/APFS) produce the same sidecar as NFC-constructed paths. An early-return when the sidecar exists prevents mtime bumps causing spurious re-detection..json files no longer explode into hundreds of orphan key-nodes. The JSON extractor now only processes config/manifest JSON (detected by filename — package.json, tsconfig.json, .eslintrc.json, deno.json, etc. — or by top-level keys such as dependencies, extends, $ref, compilerOptions). Data JSON (top-level arrays, generic key/value files) is skipped by the AST pass and left for the LLM semantic pass.temperature=0 to reasoning models. _resolve_temperature() auto-detects o1/o3/o4 and gpt-5 series and omits temperature from the request. Override with GRAPHIFY_LLM_TEMPERATURE=<value> (or none to omit explicitly for any model).datasketch (which transitively imports scipy → numpy.testing → platform.machine() subprocess at import time) is replaced by a self-contained pure-numpy MinHash/MinHashLSH implementation with byte-identical hash math. Removes datasketch and scipy from the dependency tree.detect() ignore-pattern checks memoized per scan. Each ancestor directory is now evaluated once across all sibling files, eliminating ~42M redundant fnmatch calls on large repos (~34% whole-run speedup on 2k-file corpora).dedup.py label-based merge passes now skip code nodes entirely. Distinct same-named symbols in different files (e.g. two Config classes) were being merged by the exact-label and MinHash/LSH passes. Code nodes are now deduplicated by ID only, which is correct.GRAPHIFY_MAX_GRAPH_BYTES env var to override the 512 MiB graph.json size cap. Accepts plain bytes, <N>MB, or <N>GB. The cap error message now cites this env var. graphify export html auto-falls back to the community-aggregation view when over cap instead of hard-failing.CLAUDE.md template now uses mandatory language for the graphify-first rule — "MANDATORY: Before using Read/Grep/Glob/Bash to explore the codebase, you MUST run graphify first" — and explicitly requires forwarding the rule to subagent prompts. PreToolUse hook message hardened to match.graphify-self-graph.tar.gz as a downloadable asset — graph.json + graph.html + GRAPH_REPORT.md from running Graphify on its own source. Open graph.html locally with no install required to see what Graphify produces.extra_body for custom providers — pass model-specific request shapes (e.g. Qwen3 thinking disable, vLLM chat templates) via providers.json. Explicit e
providers.json. Explicit extra_body also bypasses Ollama num_ctx auto-derive. Thanks @EirikWolf (#1197)label_communities now chunks in groups of 100 (configurable batch_size=) for 16k-context models; partial batch failures no longer drop the whole pass. Thanks @EirikWolf (#1197).slnx solution file support — modern VS 2022 XML solution format, with contains and imports edges. Thanks @bakgaard (#1189)graphify-mcp console script — MCP stdio server directly invocable from uv tool install / pipx. Thanks @jr2804 (#1190)min(40+16n,4096) to min(64+24n,8192); GRAPHIFY_MAX_OUTPUT_TOKENS now honoured for labeling path (#1200)nx.simple_cycles() now receives length_bound=max_cycle_length, dropping report generation from never-returns to ~0.1s (#1196)getActiveSession/getActiveSessions, parseConfig/parseConfigFile etc. no longer silently merged by prefix-extension guard in Pass 2 and LLM tiebreaker (#1201)None node label crash — _norm, _norm_label, _strip_diacritics now guard against null label fields. Thanks @freiit (#1195)trigger: field removed — not part of Agent Skills spec; agentskills validate CI no longer flags graphify installs (#1180)uv tool upgrade graphifyy
extra_body field in providers.json forwarded to OpenAI-compat calls at extraction and labeling. Lets vLLM/Qwen3/Llama endpoints pass model-specific request shapes (e.g. {"chat_template_kwargs": {"enable_thinking": false}}). Explicit extra_body also bypasses Ollama num_ctx auto-derive. Thanks to @EirikWolf (#1197).label_communities multi-batch for 16k-context models. Chunks of 100 communities per call (configurable batch_size=); max_communities defaults to None (label all); partial batch failures no longer drop the whole pass. Thanks to @EirikWolf (#1197)..slnx Visual Studio solution file support. Extracts contains (project references) and imports (build dependencies) edges from the modern XML solution format (VS 2022 17.13+). Thanks to @bakgaard (#1189).graphify-mcp console script. MCP stdio server now directly invocable as graphify-mcp from uv tool install / pipx. Thanks to @jr2804 (#1190).label_communities token budget raised and GRAPHIFY_MAX_OUTPUT_TOKENS now honoured. Hardcoded min(40+16n, 4096) undershooted (~16 tok/community); raised to min(64+24n, 8192) and wrapped in _resolve_max_tokens() (#1200).find_import_cycles no longer hangs on large graphs. nx.simple_cycles() now receives length_bound=max_cycle_length, pruning during enumeration rather than post-filtering — drops from never-returns to ~0.1s on dense graphs (#1196).getActiveSession/getActiveSessions, parseConfig/parseConfigFile etc. scored ~98-99 JW and were auto-merged. A prefix-extension guard now prevents merge when one normalised label is a strict prefix of the other, in both Pass 2 and the LLM tiebreaker (#1201)._norm, _norm_label, _strip_diacritics guard against None node labels, preventing TypeError crash on corpora with explicit null label fields (#1194). Thanks to @freiit (#1195).trigger: field removed from all 14 skill variants — not part of Agent Skills spec, flagged by agentskills validate CI (#1180).CodeBuddy support — graphify codebuddy install installs the skill, registers Bash + Read|Glob hooks in .codebuddy/settings.json, and writes the CODEBU
graphify codebuddy install installs the skill, registers Bash + Read|Glob hooks in .codebuddy/settings.json, and writes the CODEBUDDY.md always-on section. The hooks nudge the agent toward graphify query instead of grepping when a graph exists. Thanks to @studyzy (#1136).--update no longer destroys nodes — the skill's incremental merge now passes re-extracted files to prune_sources so old nodes are pruned before fresh AST is inserted, preventing fuzzy dedup from collapsing distinct same-named symbols across files. A secondary guard in dedup.py prevents label-only merging of no-source-file nodes. Anti-shrink guard message improved (#1178).uv tool upgrade graphifyy
graphify codebuddy install installs the graphify skill to ~/.codebuddy/skills/graphify/SKILL.md, writes a CODEBUDDY.md always-on section, and registers Bash + Read|Glob PreToolUse hooks in .codebuddy/settings.json that nudge the agent toward graphify query instead of grepping raw files when a graph exists. graphify install --platform codebuddy and graphify codebuddy uninstall also supported. Thanks to @studyzy (#1136).graphify --update no longer destructively collapses distinct same-named symbols across files. The skill's --update merge now passes re-extracted (changed) files to prune_sources alongside deleted files, so old nodes for changed files are pruned before fresh AST is inserted — no fuzzy reconciliation needed. Separately, dedup.py Pass 1 now skips nodes with an empty source_file so label-only merging across no-source-file nodes is prevented. The anti-shrink guard message now names fuzzy dedup as a possible cause rather than only blaming missing chunk files (#1178).Streamable HTTP MCP transport — graphify serve graph.json --transport http serves the graph over HTTP so a whole team shares one server. Includes API
graphify serve graph.json --transport http serves the graph over HTTP so a whole team shares one server. Includes API key auth (--api-key) and Docker image (#1143)..cls and .trigger files now produce a full dependency graph (classes, methods, triggers, SOQL/DML edges) (#1159).--backend azure with AZURE_OPENAI_API_KEY + AZURE_OPENAI_ENDPOINT. Auto-detected, no new dependency (#1107).graphify extract --postgres "postgresql://..." maps your schema directly. New graphify[postgres] extra (#1093 / #1103)._is_sensitive false positives on topic-mentioning filenames fixed (#1169)nohup (Windows fix) (#1161 / #1170).graphify_root scoped builds (#1173)graphify affected direction-blind on undirected graphs fixed (#1174)detect_incremental gracefully handles schema-drifted manifest files (#1163)>=2.0; python_version>='3.13' only (#1153 / #1154).codex/ not .agents/ (#1160)uv tool upgrade graphifyy
uv tool install "graphifyy[postgres]" # for PostgreSQL introspection
python -m graphify.serve graph.json --transport http --port 8080 --api-key $SECRET serves the graph over the MCP Streamable HTTP transport (spec 2025-03-26) so a single shared process can serve the whole team. Flags: --host, --port, --api-key (env GRAPHIFY_API_KEY), --path, --json-response, --stateless, --session-timeout. Docker image included. stdio remains the default (#1143)..cls and .trigger files are now AST-extracted via regex (no tree-sitter grammar exists for Apex). Extracts classes, interfaces, enums, methods, triggers, and SOQL/DML edges (#1159).--backend azure reads AZURE_OPENAI_API_KEY + AZURE_OPENAI_ENDPOINT and auto-detects both. Uses the existing openai package — no new dependency (#1107).graphify extract --postgres "postgresql://..." connects directly to a running database and maps tables, views, routines, and FK relations via information_schema in a SERIALIZABLE READ ONLY transaction. New graphify[postgres] extra (psycopg3). Credentials are sanitized from error messages (#1103)._strip_pixels. PDFs reuse pypdf. 5MB cap, 20-image chunk limit (#1110).graphify update now prunes symbols removed from files that still exist on disk. Previously, deleting a function left a ghost node in the graph until the source file itself was deleted. Every AST node is now stamped with _origin="ast"; on a full rebuild any stamped node absent from the fresh output is dropped (#1118).graphify path and shortest_path now fire the exact-match bonus for multi-word queries. The per-token comparison never equalled a full multi-word label, so the exact bonus was silently skipped for queries like "AuthService" when the label contained punctuation or spaces. The full normalized query is now compared alongside each token (#1165)._is_sensitive no longer flags topic-mentioning filenames as secrets. token-economics-of-recall.md and password-policy-discussion.md were silently dropped. Generic keywords (token/secret/password) now only fire when the keyword ends the filename stem or the stem is ≤2 words; specific patterns (.env, .pem, id_rsa, etc.) remain unconditional (#1169).nohup to background the rebuild. Git for Windows' MSYS shell has no nohup, causing the post-commit/post-checkout hook to fail silently and the graph to go stale. Replaced with a cross-platform Python launcher using DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP on Windows and start_new_session=True on POSIX (#1161 / #1170)..graphify_root. A scoped build (graphify src/) was silently expanded to the full repo on the next commit because the hook hardcoded Path('.'). The hook body now reads graphify-out/.graphify_root first (#1173).graphify affected now forces a directed graph on load, matching the identical fix already applied in serve.py and __main__.py. On undirected graphs ("directed": false in graph.json) the traversal was direction-blind — missing true callers and reporting callees as affected (#1174).rm -f ... .graphify_chunk_*.json glob errored with "no matches found" when no chunk files existed, leaving other temp files on disk. Split into rm -f for fixed filenames and find -maxdepth 1 -delete for the chunk glob (#1172).detect_incremental no longer crashes on schema-drifted manifest files. A dict-valued mtime entry (from an older richer schema) is now coerced to None and the file is treated as new rather than raising a comparison error (#1163).>=2.0 only on Python 3.13+ in the svg and all extras. numpy 1.26.4 ships no cp313 wheel so uv sync fell back to a source build requiring a C compiler (#1153 / #1154)..codex/skills/graphify/ (was .agents/skills/graphify/), aligning with where the hook already lives (#1160).Install banner — graphify install now shows an amber knowledge-graph brain. TTY-only, silent in CI.
graphify install now shows an amber knowledge-graph brain. TTY-only, silent in CI.from pkg import submod now resolves to a file-level edge to the submodule. Previously left test files as disconnected islands (up to 66% of test nodes in some repos) (#1146).str, int, bool, MagicMock, etc. no longer become graph nodes or accumulate edges. Inflated degree counts ~25% and pushed real abstractions out of god-node rankings (#1147).graphify extract . --force to clean up (#1145).uv tool upgrade graphifyy
graphify export falkordb [--push falkordb://localhost:6379]. FalkorDB is OpenCypher-compatible, so the MERGE/SET upsert queries match the Neo4j path; auth is optional and the target graph defaults to graphify. Install with uv tool install "graphifyy[falkordb]" (#1175).graphify install now prints an amber knowledge-graph brain in the terminal (TTY-only, silent in CI/pipes, never raises).from pkg import submod package-form imports now resolve to a file-level imports_from edge to the submodule file when it exists on disk. Previously these imports produced zero edges, leaving test files as disconnected islands in the graph (up to 66% of test nodes in some corpora). The fix lives in the symbol-resolution post-pass which has filesystem access (#1146).str, int, bool, float, bytes, MagicMock, Mock, AsyncMock, etc.) no longer appear as graph nodes or accumulate edges. They were being created via the annotation walker whenever used as parameter or return types, inflating degree counts ~25% and displacing real abstractions from god-node rankings. A new _PYTHON_ANNOTATION_NOISE filter suppresses them at extraction time; god_nodes also filters them as a defense for pre-existing graphs (#1147).source_location=L<n>, one without), build_from_json detects the pair by (source_file basename, label) and collapses the semantic ghost into the AST node, re-pointing all edges. Graphs built before this release can be cleaned up with graphify extract . --force (#1145).Terraform/HCL support — .tf, .tfvars, .hcl files now produce a full infrastructure dependency graph via tree-sitter-hcl. Resources, data sources, modu
.tf, .tfvars, .hcl files now produce a full infrastructure dependency graph via tree-sitter-hcl. Resources, data sources, modules, variables, outputs, providers, and locals become nodes; interpolation references and depends_on become edges. Requires uv tool install graphifyy[terraform] (#1129).graphify extract now defers backend resolution until after file detection. A corpus with only code files runs fully offline — no GEMINI_API_KEY, ANTHROPIC_API_KEY, or anything else needed. Key is only required when docs, PDFs, or images are present (#1122).graphify kiro install now correctly writes the references/ sidecar and .graphify_version stamp. Re-run graphify kiro install to pick up the fix (#1142).GRAPHIFY_API_TIMEOUT and --api-timeout now apply to the claude-cli subprocess and Anthropic SDK backend, not just the HTTP client (#1112).networkx>=3.4, datasketch>=1.6, rapidfuzz>=3.0 prevent silent breakage from incompatible old versions resolving via pip.uv tool install graphifyy # fresh install
uv tool upgrade graphifyy # upgrade
uv tool install "graphifyy[terraform]" # with Terraform/HCL support
.tf, .tfvars, and .hcl files are now AST-extracted via tree-sitter-hcl into a structured infrastructure dependency graph. Nodes: resources, data sources, modules, variables, outputs, providers, and locals. Edges: contains, references (interpolation), and depends_on. Node IDs are directory-scoped for cross-file resolution. Requires uv tool install "graphifyy[terraform]" (#1129).graphify extract no longer requires an LLM API key for code-only corpora. Backend resolution is now deferred until after file detection — a corpus with only code files (pure tree-sitter AST, zero LLM calls) runs fully offline. The key is only enforced when docs, PDFs, or images are present, or when --dedup-llm is passed (#1122).graphify kiro install now correctly installs the references/ sidecar and .graphify_version stamp. The install was using a bare write_text that bypassed the shared helper, shipping SKILL.md with 8 dead references/*.md pointers. Re-run graphify kiro install to pick up the fix (#1142).GRAPHIFY_API_TIMEOUT now applies to claude-cli subprocess and Anthropic SDK backend, not just the HTTP client. Both subprocess paths previously hardcoded timeout=600 and ignored the env var and --api-timeout flag (#1112).networkx>=3.4, datasketch>=1.6, and rapidfuzz>=3.0 to prevent silent breakage from old installs resolving incompatible versions.Fix: graphify hook install now embeds the current interpreter (sys.executable) directly into the generated hook scripts. Previously, uv tool and pipx
graphify hook install now embeds the current interpreter (sys.executable) directly into the generated hook scripts. Previously, uv tool and pipx installs silently no-oped on git commit in GUI clients and CI runners where ~/.local/bin is not on PATH — the hook could not find the graphify launcher, fell through all detection probes, and exited 0 without rebuilding. If you already have hooks installed, re-run graphify hook install to pick up the fix (#1127).graphify-out/.graphify_python as a fallback interpreter source, covering Windows/Git Bash installs where the launcher is a binary with no parseable shebang, and the case where the pinned path goes stale after a reinstall._PINNED= uses single quotes to prevent shell injection; nohup "$GRAPHIFY_PYTHON" -c is properly quoted; the fallback emits a loud stderr diagnostic instead of a bare silent exit 0.manifest.json keys, .graphify_root, and cache/ast/*.json source_file fields are now stored as relative paths and re-anchored on load. Teams committing graphify-out/ no longer see forced full rebuilds on every CI checkout or clone (#777, #1125).graphify query, graphify path, graphify explain, and MCP query_graph call is appended to ~/.cache/graphify-queries.log in JSON Lines format (timestamp, question, corpus, nodes returned, duration). Control with GRAPHIFY_QUERY_LOG, GRAPHIFY_QUERY_LOG_DISABLE=1, or GRAPHIFY_QUERY_LOG_RESPONSES=1 (#1128).graphify hook install now embeds the current interpreter (sys.executable) directly into the generated hook scripts. Previously, uv tool and pipx installs silently no-oped on git commit in GUI clients and CI runners where ~/.local/bin is not on PATH — the hook could not find the graphify launcher, fell through all detection probes, and exited 0 without rebuilding. The embedded path is sanitized through a filesystem-safe allowlist before substitution. If you already have hooks installed, re-run graphify hook install to pick up the fix (#1127).graphify-out/.graphify_python as a fallback interpreter source, covering Windows/Git Bash installs where the launcher is a binary with no parseable shebang, and the case where the pinned path goes stale after a reinstall._PINNED= assignment uses single quotes to prevent shell injection from a path containing metacharacters; nohup "$GRAPHIFY_PYTHON" -c is properly quoted to handle spaces; the fallback emits a loud stderr diagnostic instead of a bare silent exit 0.graphify query, graphify path, graphify explain, and MCP query_graph call is now appended to ~/.cache/graphify-queries.log in JSON Lines format (timestamp, kind, question, corpus path, nodes returned, result size, duration). Full subgraph responses are not stored by default. Control with GRAPHIFY_QUERY_LOG (path override), GRAPHIFY_QUERY_LOG_DISABLE=1 (opt out), GRAPHIFY_QUERY_LOG_RESPONSES=1 (store full response text) (#1128).Fix: graphify install --project --platform antigravity now writes Antigravity's always-on layer (.agents/rules/graphify.md + .agents/workflows/graphif
graphify install --project --platform antigravity now writes Antigravity's always-on layer (.agents/rules/graphify.md + .agents/workflows/graphify.md), not just the skill. The project-scoped path went through the skill-only branch and skipped them, even though the project uninstall removes them.PreToolUse nudge previously only fired on Bash search (grep/rg/find); an agent answering a question by reading many source files through the native Read tool (or Glob) slipped past it. A new Read|Glob hook nudges toward graphify query when graphify-out/graph.json exists, only for a source/doc file outside graphify-out/, and never blocks (#1114).anthropic optional extra (and include it in [all]) so the claude backend is installable like every other one: uv tool install "graphifyy[anthropic]". Previously it was the only backend with no extra, so a user with ANTHROPIC_API_KEY set could not satisfy it without --with anthropic. The backend package-missing errors now point at uv tool install "graphifyy[<extra>]" (the isolated-venv path) rather than only pip install.Feat: progressive-disclosure skill files. The per-host SKILL.md is now a lean core (~615 lines, down from the ~1156-line monolith, about 47% less alwa
SKILL.md is now a lean core (~615 lines, down from the ~1156-line monolith, about 47% less always-loaded context) that carries the full default code-build pipeline inline and links to an on-demand references/ sidecar (extraction-spec, query, update, exports, transcribe, github-and-merge, add-watch, hooks); an agent reads a reference only when that path is actually taken, so a normal build needs none. 18 hosts go progressive (claude, codex, opencode, kilo, copilot, claw, droid, trae, trae-cn, hermes, kiro, pi, antigravity, antigravity-windows, windows, kimi, amp, gemini); aider and devin stay monolithic by design. All 15 skill bodies + sidecars are generated from one source under tools/skillgen/, with CI guards (--check, --audit-coverage, --monolith-roundtrip, --always-on-roundtrip) proving the references are byte-identical slices of the old monolith so nothing is lost (#1121).graphify install --platform gemini shipped a SKILL.md with 8 dead references/ pointers. gemini installs claude's lean progressive core but the installer never copied claude's references sidecar; it now does, so every on-demand reference resolves (regression from the progressive-disclosure split)../.graphify/providers.json (which travels with a cloned or shared repo) is no longer loaded automatically, since a custom provider's base_url is where your corpus and API key are sent. Set GRAPHIFY_ALLOW_LOCAL_PROVIDERS=1 to opt in; the user's own ~/.graphify/providers.json is still trusted. Non-http(s) base_urls are rejected on load and on provider add, and plaintext-http egress warns. Behavior change: if you relied on an auto-loaded project-local providers file, set the opt-in env var..docx/.xlsx zip containers) so a zip-bomb in a scanned corpus can no longer exhaust memory.OLLAMA_BASE_URL pointing at a link-local or cloud-metadata address (169.254.x, metadata.google.*, or any host that resolves to one) now fails closed with a clean error instead of sending the corpus there. Trusted LAN hosts still warn-and-allow.cpp option.Feat: Kilo Code support — graphify install --platform kilo installs a native skill + /graphify command and a .kilo tool.execute.before plugin (mirrori
graphify install --platform kilo installs a native skill + /graphify command and a .kilo tool.execute.before plugin (mirroring OpenCode). Existing .kilo/kilo.jsonc is read but never rewritten — plugin registration goes to kilo.json, preserving user comments (#512)part of redirection, nested-generic-aware extends/with/implements parsing, generic type-argument mapping, generic call detection (#1098)uv tool install graphifyy / pip install graphifyy no longer fails to build on Linux/macOS — tree-sitter-dm (BYOND DreamMaker) ships only a Windows wheel, so on other platforms it compiled from source and aborted the whole install when a C toolchain / python3-dev was missing. It's now an optional extra (graphifyy[dm], also in [all]), so the default install needs no compiler (#1104).
.dm/.dme users must reinstall with graphifyy[dm] (or [all]) to keep AST extraction. .dmi/.dmm/.dmf parsing is unaffected.(-size, sorted node IDs)), so an identical grouping always gets identical IDs across runs — fixes spurious per-node community "churn" that was just unstable integer labels, not real regrouping (#1090 follow-up)PyPI: https://pypi.org/project/graphifyy/0.8.28/
graphify install --platform kilo installs a native skill (~/.config/kilo/skills/graphify/SKILL.md) and /graphify command, plus a .kilo tool.execute.before plugin (mirroring the OpenCode integration). Existing .kilo/kilo.jsonc config is read but never rewritten — plugin registration goes to kilo.json so user comments are preserved (#512)part of redirection, nested-generic-aware extends/with/implements parsing, generic type-argument mapping, and generic call detection (#1098)uv tool install graphifyy / pip install graphifyy no longer fails to build on Linux/macOS — tree-sitter-dm (BYOND DreamMaker) ships only a Windows wheel, so on other platforms it compiled from source and aborted the entire install when a C toolchain or python3-dev headers were missing. It is now an optional extra (graphifyy[dm], also in [all]) instead of a core dependency, so the default install needs no compiler (#1104).
.dm/.dme users must reinstall with graphifyy[dm] (or [all]) to keep AST extraction — on uv tool upgrade the now-optional grammar is removed. .dmi/.dmm/.dmf parsing is unaffected (no tree-sitter dependency).(-size, sorted node IDs)) so an identical grouping always gets identical IDs across runs — previously the equal-sized small communities that dominate a sparse graph were numbered by the partitioner's (not seed-stable) enumeration order, making a per-node community diff report large spurious "churn" even though the actual grouping was reproducible (#1090 follow-up)graphify amp install now writes the skill where Amp actually looks for it. It was landing in .amp/skills/graphify (project) and ~/.amp/skills/graphify (user), neither of which Amp searches, so the skill never loaded. User-scope installs now go to ~/.config/agents/skills/graphify and project installs to .agents/skills/graphify, and a stale ~/.amp/skills/graphify from an older install is cleaned up on the next run.Feat: standalone CLI now auto-names communities with the configured backend instead of leaving Community N placeholders — community labeling was previ
Community N placeholders — community labeling was previously an agent-only step (skill.md Step 5), so bare-CLI runs never got semantic names. cluster-only now auto-labels when no .graphify_labels.json exists, new graphify label <path> subcommand (re)generates names on demand, --no-label opts out, --backend=<name> overrides auto-detection. One batched LLM call with per-community placeholder fallback and graceful degradation on missing backend/API error; works with all built-in and custom OpenAI-compatible backends (#1097){parent_dir}_{stem} spec — they were derived from the full relative path plus extension (match_script_pipeline_step_py) while semantic subagents use script_pipeline_step, splitting every file into two disconnected ghost nodes. Fixed at the single relative-path remap chokepoint so file nodes and all import/dependency edge endpoints (Python, TS, Lua, C, bash) convert together (#1033)<rootdir>_main_run vs spec main_run), splitting every top-level file's symbols into AST/semantic ghost pairs. The remap now canonicalizes symbol stems and raw_calls caller IDs, gated by source_file (#1096)interface A extends B and same-file class X extends Y now produce inherits/implements edges — the walker only inspected class_heritage (missing the interface extends_type_clause node) and the resolver only consulted the import table (missing same-file bases); both gaps closed (#1095)graphify export obsidian no longer crashes with OSError ENAMETOOLONG on long node labels — to_obsidian/to_canvas now cap filenames on UTF-8 bytes (not chars, so multibyte/CJK labels are handled) with an 8-char hash suffix on truncation to keep distinct long-prefix labels from colliding; also fixes the previously-uncapped _COMMUNITY_ notes (#1094)graph.json is now deterministic across runs — detect() sorts file traversal lexicographically (os.walk order is filesystem-dependent), which had made first-writer-wins node-ID decisions and Leiden community counts vary between identical runs (#1090)UnicodeEncodeError on non-UTF-8 code pages — main() reconfigures stdout/stderr to UTF-8 at startup and →/— in print statements replaced with ASCII (#992)PyPI: https://pypi.org/project/graphifyy/0.8.27/
Circular import cycle detection — GRAPH_REPORT.md now includes an ## Import Cycles section showing file-level circular dependencies. Uses Johnson's al
Circular import cycle detection — GRAPH_REPORT.md now includes an ## Import Cycles section showing file-level circular dependencies. Uses Johnson's algorithm on a directed file-level import graph, deduplicates rotations, and sorts by cycle length (tightest coupling first). Also available programmatically via find_import_cycles(G) in graphify.analyze. (#961 — thanks @deXterbed)
Custom LLM provider registry — Register any OpenAI-compatible endpoint without touching source code:
```bash
graphify provider add nvidia
--base-url https://integrate.api.nvidia.com/v1
--default-model minimaxai/minimax-m2.7
--env-key NVIDIA_API_KEY
graphify provider list / show / remove
```
Providers stored in ~/.graphify/providers.json. Custom providers are auto-detected after all built-ins in detect_backend() priority so they never shadow a configured paid key. Works for any OpenAI-compatible endpoint: NVIDIA NIM, vLLM, OpenRouter, Together AI, LiteLLM, Fireworks, etc. (#1084 — thanks @timburman)
extract_files_direct() no longer silently defaults to kimi (Moonshot AI) — backend=None now calls detect_backend() and picks whichever API key you have set, matching CLI behavior. Raises a clear ValueError listing all options if nothing is configured. README Privacy section updated with data-residency notes and provider priority order. (#1086 — thanks @EmanuelFaria)
pnpm-workspace.yaml crash on Python 3.10 fixed — packages: - '.' (single-package-at-root workspace) caused IndexError: tuple index out of range in _load_workspace_packages, silently skipping every JS/TS file in the project. Fixed with a one-line guard: [root] is used directly instead of root.glob('.'). Also added GRAPHIFY_DEBUG=1 env var to _safe_extract — set it to print full tracebacks instead of the terse one-liner warning. (#1083 — thanks @santiagotorres-united)
Anchored .graphifyignore patterns no longer match deep in the tree — /inbox/ should only match inbox/ at the repo root, not src/inbox/ anywhere in the tree. The basename shortcut and per-segment loop in _matches() now respect the anchored flag. Anchored patterns do an exact anchor-relative path match only. Same fix applied to _is_included(). (#1087 — thanks @cheerc)
Filipino (fil-PH) README translation added. (#1080 — thanks @Reihanboo)
```bash uv tool upgrade graphifyy
pip install --upgrade graphifyy ```
find_import_cycles(G) in analyze.py detects file-level circular import dependencies — collapses symbol graph to file-level directed import graph, finds simple cycles via Johnson's algorithm, deduplicates rotations, renders ## Import Cycles section in GRAPH_REPORT.md (#961)graphify provider add/list/show/remove registers any OpenAI-compatible endpoint (NVIDIA NIM, vLLM, OpenRouter, Together, LiteLLM) via ~/.graphify/providers.json; custom providers auto-detected after built-ins in detect_backend() priority (#1084)extract_files_direct() no longer silently defaults to kimi (Moonshot AI) — backend=None now calls detect_backend() and raises a clear ValueError if no key is configured, matching CLI behavior; README Privacy section updated with data-residency notes (#1086)pnpm-workspace.yaml with packages: - '.' no longer crashes with IndexError: tuple index out of range on Python 3.10 — Path.glob('.') replaced with [root] guard in _load_workspace_packages; GRAPHIFY_DEBUG=1 env var added to _safe_extract for full traceback on extraction errors (#1083).graphifyignore patterns (leading /) no longer match the same directory name anywhere in the tree — _matches() in both _is_ignored and _is_included now gates basename/segment shortcuts on not anchored; anchored patterns do exact anchor-relative path match only (#1087)JS/TS phantom god-nodes — const/let inside arrow-function callbacks no longer emit graph nodes. _js_extra_walk now restricts node emission to program-
Bug fixes:
const/let inside arrow-function callbacks no longer emit graph nodes. _js_extra_walk now restricts node emission to program-level declarations only. Applies uniformly to JS, TS, and TSX. (#1077)codeblock_N nodes. They had only contains edges and no semantic meaning; fence-toggle still prevents inner content from being mis-parsed as headings. (#1077)require("pkg.sub") now resolves to the correct file node ID. Dots are converted to path separators and the filesystem is probed for .lua/.luau/init.lua variants. (#1075)WinError 2 on Windows is fixed by preferring claude.cmd over bare claude to avoid PATHEXT .ps1 resolution failure. (#1072)changed_paths when another rebuild holds the lock. Lock-losers queue paths to a pending file; the lock-holder drains and merges on acquire. (#1059)graphify install antigravity now correctly writes to ~/.gemini/config/skills/ (per Antigravity docs) instead of ~/.agents/. Uninstall, version-stamp refresh, and project-scope install all updated to match. (#1079)Docs:
pip install on Mac/Windows due to Python env mismatch causing ModuleNotFoundError; uv tool install recommended as primary method. (#1074)uv tool upgrade graphifyy
# or
pipx upgrade graphifyy
const/let inside arrow-function callbacks no longer emit phantom god-nodes — scope guard restricts _js_extra_walk node emission to program-level declarations only; applies uniformly to JS, TS, and TSX (#1077)codeblock_N nodes — they had only contains edges and no semantic meaning; fence-toggle still prevents inner content from being mis-parsed as headings (#1077)require("pkg.sub") now resolves to the correct file node ID — dots converted to path separators, probes filesystem for .lua/.luau/init.lua variants up the directory tree (#1075)claude-cli backend no longer raises WinError 2 — prefers claude.cmd over bare claude to avoid PATHEXT .ps1 resolution failure (#1072)changed_paths when another rebuild holds the lock — lock-losers queue paths to a pending file; the lock-holder drains and merges on acquire (#1059)graphify install antigravity global install now writes to ~/.gemini/config/skills/ (per Antigravity docs) instead of the wrong ~/.agents/; uninstall, version-stamp refresh, and project-scope install all updated to match (#1079)pip install on Mac/Windows due to Python env mismatch causing ModuleNotFoundError; uv tool install recommended as primary method (#1074)Cross-language type-reference edges (wave 2) — ObjC, Julia, C, C++, Scala, Fortran, and PowerShell now emit references edges with parameter_type, retu
Features:
references edges with parameter_type, return_type, generic_arg, field, and attribute contexts; extends the work from v0.8.23. CI matrix now covers Python 3.10 with faster-whisper version guard. (#1071)Bug fixes:
result, empty result) with tests. (#1063)calls edge direction flip — edges no longer flip caller/callee when the same node pair appears in both directions in an undirected build; first-seen direction preserved on bidirectional collision. (#1061).graphify_python path prefix — graphify-out/ prefix was missing in 8 skill files (256 instances), causing cat: .graphify_python: No such file or directory on every non-Claude-Code platform.uv tool run graphifyy python over shebang parsing when uv is available.uv tool upgrade graphifyy
faster-whisper version guard (#1071)result, empty result) with tests (#1063)calls edges no longer flip caller/callee when the same node pair appears in both directions in an undirected build — first-seen direction preserved on bidirectional collision (#1061)graphify-out/.graphify_python path prefix was missing in 8 skill files (256 instances) causing cat: .graphify_python: No such file or directory on every non-Claude-Code platformuv tool run graphifyy python preferred over shebang parsing when uv is availableBYOND DreamMaker support — .dm/.dme files extracted via tree-sitter-dm (type definitions, proc declarations, #include edges, in-file call resolution,
.dm/.dme files extracted via tree-sitter-dm (type definitions, proc declarations, #include edges, in-file call resolution, new /type() instantiation edges); .dmi PNG icon files parsed for icon-state nodes; .dmm map files parsed for type-path uses edges from the tile dictionary section; .dmf interface files parsed for window/elem/control-type hierarchy (#884)graphify extract --mode deep — enables richer semantic extraction using an extended system prompt; flag propagated through all four LLM backends (#1030)pip install --upgrade graphifyy
Full changelog: https://github.com/safishamsi/graphify/blob/v8/CHANGELOG.md
Ghost nodes on `graphify update` — full re-extraction now reconciles against disk state and evicts nodes from deleted files (no --changed flag needed)
graphify update — full re-extraction now reconciles against disk state and evicts nodes from deleted files (no --changed flag needed) (#1007)--project path — skill now written to .opencode/skills/graphify/SKILL.md (was incorrectly .config/opencode/skills/) (#1040)graphify-out/ files changed; GRAPHIFY_SKIP_HOOK=1 env var for one-off opt-out; rebuild log now appends instead of overwriting (#1018, #1037)PYTHONHASHSEED=0 in hooks so graphify-out/ no longer churns on re-run (#1010)what calls extract? correctly finds the extract node; punctuation stripped before matching (#994, #978)String(), Number(), Boolean(), print(), len() etc. no longer accumulate spurious call edges (#916, #726)graphify amp install/uninstall installs into .amp/skills/graphify/SKILL.md (#948).svh files now extracted alongside .v and .sv (#1042)pip install --upgrade graphifyy
# or
uv add graphifyy
graphify update (no --changed flag) no longer leaves ghost nodes from files deleted between runs — full re-extraction path now reconciles the existing graph against current disk state and evicts any node whose source_file no longer exists; _norm_source_file used on both sides to guarantee path format consistency (#1007)graphify install --platform opencode --project now writes SKILL.md to .opencode/skills/graphify/SKILL.md (discoverable by OpenCode) instead of the incorrect .config/opencode/skills/ path; git-add hint updated accordingly (#1040)graphify-out/ files were committed (avoids infinite dirty-tree loop when graph outputs are tracked in git); GRAPHIFY_SKIP_HOOK=1 env var added for one-off skip; hook rebuild log now appends (>>) instead of overwriting (>) (#1018, #1037)(source, target, relation) in build_from_json; PYTHONHASHSEED=0 exported in hook scripts to stabilize Louvain community ordering (#1010)graphify amp install/uninstall installs the skill into .amp/skills/graphify/SKILL.md (#948)"what calls extract?" correctly finds the extract node; _search_tokens helper strips punctuation from search terms in _query_terms, _score_nodes, and _find_node (#994, #978)String, Number, Boolean, Object, Array, etc.) no longer accumulate spurious call edges — filtered at same-file and cross-file resolution in the AST extractor, eliminating god-node pollution from constructor-style calls (#916, #726).svh) now extracted using the Verilog parser alongside .v and .sv (#1042)@property, @staticmethod, @classmethod methods no longer produce orphaned nodes without a class-qualified ID — decorated_definition is now treated as a transparent wrapper in the Python AST walker, preserving parent_class_nid through the decorator layer (#1050)foo() in a.py and foo() in b.py are not collapsed into one node (#1046)graphify-out/memory/ files are no longer silently excluded by .gitignore pattern matching — memory dir files now bypass the gitignore filter in detect.py, ensuring knowledge accumulated via graphify remember is always scanned (#1047)graphify update on Windows no longer leaves stale nodes after moving/deleting files. Two root causes resolved:
graphify update on Windows no longer leaves stale nodes after moving/deleting files. Two root causes resolved:
_relativize_source_files now runs on the existing graph before eviction, not afterdeleted_paths / evict_sources now use .as_posix() for consistent forward-slash paths on all platforms.resolve() in build_merge (#1007)extract_csproj and extract_lazarus_package now pre-screen for <!DOCTYPE / <!ENTITY before parsing — blocks billion-laughs memory exhaustion on malicious project files. Zero false positives on real MSBuild/Lazarus files. extract_lpk also gains the previously missing 2 MiB size cap.
Dart child node IDs no longer embed absolute paths — now uses _file_stem consistent with all other extractors. Existing Dart graphs should be rebuilt with graphify extract --force. (#999)
cluster-only now applies remap_communities_to_previous matching the behaviour of graphify update, so community labels stay stable across re-clusterings (#1028)
.mcp.json, mcp.json, mcp_servers.json, claude_desktop_config.json are now extracted into the knowledge graph — captures server nodes, npm/pip package references, and env var requirements. Env values are discarded to prevent secret leakage.
pip install --upgrade graphifyy
graphify update when files are deleted on Windows — deleted_paths and evict_sources in _rebuild_code now use .as_posix() for consistent forward-slash paths; _relativize_source_files called on the existing graph before eviction (not after); _relativize_source_files itself now produces forward slashes (#1007)graphify extract stale-node pruning now also handles symlinked scan roots — prune_set expansion uses Path(root).resolve() before relative_to() so symlinked roots produce correct relative paths (#1007).mcp.json, mcp.json, mcp_servers.json, claude_desktop_config.json now extracted into the knowledge graph; captures server nodes, npm/pip package refs, env var requirements; env values discarded to prevent secret leakage (#1034)cluster-only no longer drops community label alignment after re-clustering — remap_communities_to_previous now applied in the cluster-only path, matching the behaviour of graphify update (#1028)_make_id(str(path), name) to _make_id(_file_stem(path), name), consistent with all other extractors; existing Dart graphs should be rebuilt with --force (#999)extract_csproj and extract_lazarus_package now pre-screens for <!DOCTYPE / <!ENTITY declarations before calling ET.fromstring, blocking billion-laughs DoS on malicious project files; extract_lpk also gains the missing 2 MiB size cap.sln, .csproj, .fsproj, .vbproj, .razor, .cshtml files are now extracted into the knowledge graph. Captures NuGet package references, project-to-proje
.sln, .csproj, .fsproj, .vbproj, .razor, .cshtml files are now extracted into the knowledge graph. Captures NuGet package references, project-to-project dependencies, target frameworks, SDK attributes, Razor/Blazor directives (@using, @inject, @inherits, @model, @page), component references, and @code block methods. Closes #515.
Compound Chinese tokens like 页面路由 are now split into meaningful words using jieba when installed, with a character bigram fallback when it isn't. The original compound is preserved alongside segments so exact-match still works. Install with pip install "graphifyy[chinese]".
source_file — G.nodes[n].get("source_file") or "" now handles explicit None values that .get("source_file", "") missed (#1016).claude/worktrees/ indexed — _is_noise_dir now skips worktrees/ directories nested inside dotted dirs like .claude/ (#1023)backup_if_protected uses content-hash comparison to skip identical backups and overwrite in-place when content changes; one folder per day maximumextends — _read_tsconfig_aliases normalizes extends to a list before iteration (#1017)graphify devin install/uninstall (#1020)pip install --upgrade graphifyy
# or
uvx graphifyy
Optional extras:
pip install "graphifyy[chinese]" # Chinese query segmentation
pip install "graphifyy[sql]" # SQL schema extraction
pip install "graphifyy[all]" # Everything
.sln, .csproj, .fsproj, .vbproj, .razor, .cshtml now extracted; captures NuGet package refs, project-to-project dependencies, target frameworks, SDK attributes, Blazor/Razor directives (@using, @inject, @inherits, @model, @page), component refs, and @code block methods (#1025)页面路由) are split into meaningful words using jieba when installed, with character bigram fallback; original compound preserved alongside segments for exact-match; new pip install "graphifyy[chinese]" extra (#1026)source_file is None — G.nodes[n].get("source_file") or "" replaces .get("source_file", ""), which did not handle explicit None values (#1016).claude/worktrees/ no longer indexed — _is_noise_dir now accepts an optional parent param and skips worktrees/ directories nested inside dotted dirs like .claude/ (#1023)backup_if_protected no longer accumulates one folder per run — uses content-hash comparison to skip identical backups and overwrite in-place when content changes; one folder per day maximumgraphify devin install/uninstall installs the skill into Devin's .devin/rules/ directory (#1020)extends in tsconfig.json now handled — _read_tsconfig_aliases normalizes extends to a list before iteration (#1017)Fix: Post-commit hook updates graph after delete-only commits — shrink-guard now bypassed when changed paths include explicit deletions
graphify export no longer collapses to "Single community" when .graphify_analysis.json is absent — falls back to per-node community attribute in graph.json (#1001)references edges for Python/JS/TS/C#/Java — parameter_type, return_type, generic_arg, attribute, field; C#/Java now split inherits/implements (#996)
extends edges renamed to inherits — update queries filtering on relation="extends" for Java nodeschanged_paths contains explicit deletions, preventing stale nodes from accumulating indefinitely (#1000)graphify export (html/obsidian/wiki/svg/graphml/neo4j) no longer collapses to "Single community" when .graphify_analysis.json is absent — falls back to per-node community attribute already present in graph.json (#1001)references edges for Python/JS/TS/C#/Java — parameter_type, return_type, generic_arg, attribute, field; C#/Java split inherits/implements; dedup key now includes context (#996)
extends edges are now emitted as inherits — queries filtering on relation="extends" for Java nodes must be updated to relation="inherits"Case-sensitive call resolution — Go, Rust, and Elixir resolvers previously lowercased both the label index and the callee name, causing Authorize to m
Authorize to match authorize and produce phantom edges. Ruby, C#, Java, Kotlin, Scala use the same generic resolver which now uses a case-sensitive dict. PHP retains a separate case-insensitive dict since PHP function/class names are genuinely case-insensitive (#993)calls edges — INFERRED calls edges whose source and target nodes belong to different language families (py/js/go/rs/jvm/c/cpp/rb/php/cs/swift/lua) are now dropped at graph-build time. The skill prompt also now explicitly forbids emitting cross-language call edges (#991)Authorize to match authorize and produce phantom edges; Ruby/C#/Java/Kotlin/Scala/PHP use the same generic resolver which now splits into case-sensitive (all languages) and case-insensitive (PHP only, where function/class names are genuinely case-insensitive) dicts (#993)calls edges from semantic extraction dropped at graph-build time — INFERRED calls edges whose source and target nodes belong to different language families (py/js/go/rs/jvm/c/cpp/rb/php/cs/swift/lua) are now discarded; skill.md prompt updated with an explicit anti-rule (#991)Fix: CJK/Unicode labels no longer silently stripped during dedup — _norm() and _norm_label() now use Unicode-aware [\W_]+ regex with casefold() and NF
_norm() and _norm_label() now use Unicode-aware [\W_]+ regex with casefold() and NFKC normalization; previously 道具処理クラス and any non-ASCII label collapsed to empty string and got falsely merged (#937).ets (ArkTS/HarmonyOS) files now recognized as code and extracted via the TypeScript parser (#926)graphify now exits non-zero when all semantic-extraction chunks fail — previously a silent empty graph was written with exit code 0, masking backend failures (#889)graphify install --project installs the skill into the current repository (.claude/skills/, .agents/skills/, etc.) instead of the user home directory; per-platform subcommands support the same flag (#931)```bash uv tool install graphifyy # fresh install uv tool upgrade graphifyy # upgrade pip install --upgrade graphifyy # pip ```
Full changelog: https://github.com/safishamsi/graphify/blob/v8/CHANGELOG.md
Fix: cluster-only subcommand crashed with FileNotFoundError when graphify-out/ did not yet exist — output directory is now created before any write
cluster-only subcommand crashed with FileNotFoundError when graphify-out/ did not yet exist — output directory is now created before any write (#934)GRAPHIFY_MAX_OUTPUT_TOKENS env var now respected for all OpenAI-compatible backends — previously the token limit was hardcoded, causing truncated responses on high-context queries (#973)_merge_swift_extensions deduplicates by canonical name before graph insertion (#969)MultiDiGraph is passed where a Graph is expected by any downstream consumer (analyze, cluster, wiki, export, report) (#956)re_exports graph edges — export { X } from './mod' emits typed edges with context="re-export" and confidence=EXTRACTED; file-level imports_from edges also emitted (#960)--affected and --import-resolution flags for the v8 subcommand — impact analysis and cross-file import resolution exposed as first-class CLI optionsWiki crash on stale node IDs — to_wiki() no longer crashes with TypeError when community node IDs are stale after dedup or re-extract. Stale IDs are s
to_wiki() no longer crashes with TypeError when community node IDs are stale after dedup or re-extract. Stale IDs are silently dropped with a stderr warning; raises a clear error only if every ID is stale (#936).graphifyignore exists, .gitignore patterns are now honoured. .graphifyignore still takes precedence when both are present (#945)--exclude flag — new CLI flag to pass extra gitignore-style exclusion patterns at runtime without modifying .graphifyignore (#947).worktrees/ skipped — git worktree sibling checkouts inside .worktrees/ are no longer indexed as duplicate source (#947)arxiv.org on IPv6-only networks that resolve via RFC 6052 NAT64 (64:ff9b::/96) were incorrectly blocked as reserved IPsuv tool upgrade graphifyy
# or
pip install --upgrade graphifyy
--wiki crash when community node IDs are stale after dedup or re-extract — stale IDs are now silently dropped with a stderr warning; raises a clear error only if every ID is stale (#936).gitignore patterns now respected when no .graphifyignore exists — previous behaviour silently ignored the project's gitignore, causing expected exclusions to be skipped (#945)--exclude <pattern> CLI flag to pass extra gitignore-style exclusion patterns at runtime without modifying .graphifyignore (#947).worktrees/ directory now skipped during scan — git worktree sibling checkouts inside .worktrees/ were previously indexed as duplicate source (#947)64:ff9b::/96) no longer false-positive as blocked reserved IPs — affects hosts like arxiv.org on IPv6-only networks where the ISP uses RFC 6052 NAT64Node ID collisions across same-named files in different directories — SQL extractor and Python import resolver now use directory-qualified stems (dir_
dir_file_entity) instead of bare filename stems, preventing silent node merging on repos with duplicate filenamessource_file paths from semantic subagents no longer stored in graph — build_from_json, build, and build_merge now accept a root param and relativize paths at build time, fixing broken MCP traversal on mixed corpora (#932)semantic_hash stamped in the manifest; failed-chunk files keep an empty hash and are re-queued on the next run (#933)file_hash — skips full SHA256 read when file size+mtime_ns are unchanged (same trade-off as make); index flushed atomically via atexit, making incremental re-runs significantly faster on large corporagraphify cache-check, graphify merge-chunks, graphify merge-semantic expose cache and merge logic as library-callable commands for skill pipelinespip install --upgrade graphifyy
dir_file_entity) instead of bare filename stems, preventing silent node merging on repos with duplicate filenames (#1A, #1B)file_hash — skips full SHA256 read when file size+mtime_ns unchanged, same trade-off as make; index flushed atomically via atexitsource_file paths from semantic subagents no longer stored in graph — build_from_json, build, and build_merge accept a root param and relativize paths at build time (#932)semantic_hash stamped; failed-chunk files keep empty semantic_hash and are re-queued on next run (#933)graphify cache-check, graphify merge-chunks, graphify merge-semantic CLI subcommands expose cache and merge logic as library-callable commands for skill pipelinesFix: LLM empty choices / None message guard — Gemini and other providers return choices=[] on content-filtered HTTP 200 responses; now raises a clean
choices=[] on content-filtered HTTP 200 responses; now raises a clean error instead of crashing with IndexError (#924)general-purpose agent reference and headless-incompatible interactive halt (#911, closes #825)Fix: git hooks phantom directory on git < 2.31
--resolution N for extract and cluster-only (#919)--exclude-hubs P for extract and cluster-only with majority-vote reattachment (#919)--path-format=absolute, validate path contains no newlines, anchor relative paths on repo root (#907)save_manifest incremental data loss — seed from existing manifest before loop so untouched files aren't erased on partial runs (#917)base_class_clause for class_specifier and struct_specifier (#915)cohesion_score now returns raw float, display rounds to 2dp (#919)Type::method() scoped calls and common trait-method names from cross-file resolver (#908)--resolution N for extract and cluster-only — control Leiden/Louvain community granularity (>1 = more smaller, <1 = fewer larger) (#919)--exclude-hubs P for extract and cluster-only — exclude degree-percentile super-hubs from partitioning, reattach by majority-vote neighbour community (#919)Feat: DeepSeek backend support — set DEEPSEEK_API_KEY and use --backend deepseek; default model deepseek-v4-flash
DEEPSEEK_API_KEY and use --backend deepseek; default model deepseek-v4-flashShipping a new subcommand today: graphify prs.
Shipping a new subcommand today: graphify prs.
The problem it solves: no existing PR dashboard knows your codebase structure. You can see CI state and review decisions — but you cannot see that two open PRs both touch the auth community and are going to conflict at merge time. graphify prs does.
graphify prs # dashboard: CI state, review decision, worktree mapping
graphify prs 42 # deep dive on one PR — blast radius, communities touched
graphify prs --conflicts # PRs sharing graph communities → merge-order risk
graphify prs --triage # AI ranks your review queue by graph impact
graphify prs --worktrees # worktree → branch → PR in one view
graphify prs --repo owner/repo # works on any GitHub repo
The --conflicts view crosses your open PRs against the knowledge graph. If two PRs touch the same community of nodes, they get flagged — with representative node labels so you know what that community actually is (ValidateToken, SessionStore, AuthMiddleware) rather than just "Community 3".
The dashboard shows a blast radius column per PR: 14 nodes / 3 communities. High number = review this carefully before merging.
--triage sends your PR queue to whatever LLM backend you have configured and gets a ranked list with one action per PR. Auto-detects from your env: claude → kimi → openai → gemini → claude-cli → ollama. Override with GRAPHIFY_TRIAGE_BACKEND.
Three new tools on the MCP server: list_prs, get_pr_impact, triage_prs. Your agent can now query PR state the same way it queries the graph.
pip install --upgrade graphifyy
No new required dependencies. Requires gh CLI authenticated for PR data.
graphify prs — graph-aware PR dashboard: CI state, review decision, worktree mapping, and graph blast radius per PR; --triage ranks your queue via any configured LLM backend (claude, kimi, openai, gemini, claude-cli, ollama — auto-detected); --conflicts shows PRs sharing graph communities with node labels; --worktrees maps worktree paths to branches to open PRs; MCP tools list_prs, get_pr_impact, triage_prs for agent accessIDF weighting — common terms like error or handle that match dozens of nodes are down-weighted, so a rare identifier like FooBarService ranks first an
error or handle that match dozens of nodes are down-weighted, so a rare identifier like FooBarService ranks first and BFS expands from the right starting pointget_node or add a context_filter) rather than just reporting truncationint x;, static const int MAX = 100;) are now extracted as nodes with defines edges from the parent class — the previous field_declaration branch was silently a no-op due to a wrong child type filter_get_cpp_func_name now handles field_identifier, destructor_name, and operator_name node types#include "path/to/file.h" edges now resolve relative to the including file and use the full resolved path as the target node ID — matching what extraction creates for the included file. Previously all include edges dangled with a basename-only ID and were silently dropped.handle, init, run) from different files no longer collapse into artificial god nodes; cross-file matches fall through to Pass 2 fuzzyexact_merges counter now reports only merges actually performeduvx graphifyy@0.8.7 install
# or
pip install --upgrade graphifyy
error or handle that match dozens of nodes are down-weighted so a rare identifier like FooBarService ranks first and BFS expands from the right node (#897)query_graph now tells Claude what to do (call get_node or add a context_filter) rather than just saying "truncated" (#897)int x;, static const int MAX = 100;) now extracted as nodes with defines edges from the parent class — previously the field_declaration branch was a no-op due to a wrong child type guard (#898)handle, init, run) from different files no longer collapse into artificial god nodes; cross-file matches are routed to Pass 2 fuzzy (#895)#include "path/to/file.h" edges now resolve the include path relative to the including file and use the full resolved path as the target node ID, matching what extraction creates for the included file — previously all include edges dangled with a basename-only ID (#899)exact_merges counter in dedup now reports only merges actually performed rather than counting all same-label nodes across files (#895)Two new suppression rules stop false positives from dominating the headline output:
Two new suppression rules stop false positives from dominating the headline output:
AuthError → TypeScript Member). These calls/uses edges are now zero-scored so they don't crowd out real structural surprises. semantically_similar_to and EXTRACTED edges are unaffected.calls edge, that's documentation cross-reference noise, not architecture. Same suppression applied. Code↔paper edges are preserved (a code file referencing a research paper is a genuine cross-format signal).name, id, type, start, end, key, value, data, items, title, description, version, properties) accumulate positional degree from sibling records rather than architectural meaning. These are now excluded from god_nodes. Domain-specific labels in JSON files still rank normally.upgrade()) are still captured.follow_symlinks is now enabled automatically when symlinked children are detected in the target directory, no flag needed./graphify query interactively rather than read GRAPH_REPORT.md first.calls/uses edges (e.g. Python → TypeScript) are suppressed in Surprising Connections — label-matching across language boundaries in monorepos is resolver pollution, not structural insight; all structural bonuses zeroed for these edgescalls/uses edges suppressed in Surprising Connections — the LLM seeing a symbol name in a README and emitting a calls edge is documentation cross-reference noise, not a real architectural connection (#890)name, id, type, start, end, key, value, data, items, title, description, version, properties) filtered from god_nodes — their degree is positional (every sibling record in the same JSON file references them), not architectural (#890)--follow-symlinks is now auto-detected — if symlinked children are present in the target directory, follow-symlinks is enabled automatically without requiring an explicit flag (#887)/graphify query interactively rather than reading GRAPH_REPORT.md first; the report is a summary, not a starting point (#891)Gitignore parent-exclusion rule (#882): .graphifyignore patterns now correctly exclude files under an excluded directory even when a ! negation exists
.graphifyignore patterns now correctly exclude files under an excluded directory even when a ! negation exists elsewhere in the file. Previously, any negation pattern would disable directory pruning entirely.ASR1603/ASR1605 or M1/M1 Pro. Two new guards (_is_variant_pair, _short_label_blocked) prevent these false positives while still catching real typos.--update now correctly removes nodes and edges from deleted source files (was matching on basenames instead of full paths).graph.json are now coerced to int before community label lookup, fixing blank community names in reports and HTML.--update with deletions-only no longer errors on missing extraction file.uv tool and pipx installs on Windows (#831)..graphifyignore parent-exclusion rule now correctly blocks files under an excluded directory even when a ! negation exists elsewhere in the file — previously any negation pattern disabled directory pruning entirely (#882)ASR1603/ASR1605 or M1/M1 Pro — Jaro-Winkler prefix bonus is now gated by _is_variant_pair and _short_label_blocked guards; real typos on short labels still merge (#878)worked/rsl-siege-manager/ — case study on a real-world Python + TypeScript monorepo (FastAPI backend, React/Vite frontend, Discord bot); covers god node behaviour with tests included, cross-language INFERRED edges, community cohesion, and Alembic migration noise (#881)Feat: Firebird SQL — trigger and stored procedure extraction via CREATE TRIGGER and regex fallback; FK detection via global regex covering REFERENCES
CREATE TRIGGER and regex fallback; FK detection via global regex covering REFERENCES and FOREIGN KEY clauses (#875)--update deletion pruning now matches on full source file paths instead of basenames, preventing false node removal when different directories contain files with the same name (#876)--update now also prunes edges whose source_file attr points to deleted files, not just nodes (#876)graph.json (stored as strings) are now coerced to int before lookup, fixing blank community names in GRAPH_REPORT.md and graph.html (#877)Fix: Windows skill temp files (chunk JSONs, .graphify_python, .graphify_root) no longer pollute the project root — all written under graphify-out/
.graphify_python, .graphify_root) no longer pollute the project root — all written under graphify-out/ (#831)--update with deletions-only no longer errors when .graphify_extract.json does not yet exist — creates an empty extraction file before merging (#876)Fix: Python interpreter detection for uv tool and pipx installs on Windows — graphify install and all skill steps now find the correct executable
uv tool and pipx installs on Windows — graphify install and all skill steps now find the correct executable (#831).github/, .vscode/) are now indexed when explicitly included via .graphifyignore (#873)graph.json changes on disk (#874)Shell scripts and JSON configs are now indexed automatically via tree-sitter — no LLM, no tokens, no flags needed.
Shell scripts and JSON configs are now indexed automatically via tree-sitter — no LLM, no tokens, no flags needed.
Bash extracts functions, cross-function calls, source imports resolved to real file paths, and export/declare variables.
JSON extracts the full key tree, dependencies blocks as import edges, extends chains (tsconfig, eslintrc), and $ref references.
graphify export callflow-html
Generates a self-contained HTML page with Mermaid call-flow diagrams per module. With graphify hook install set up, the diagram regenerates automatically on every git commit.
.graphifyignore (#861)graphify update no longer forces a full re-extract after an AST-only run (#857)pip install --upgrade graphifyy
Full changelog: https://github.com/safishamsi/graphify/blob/v8/CHANGELOG.md
.sh and .bash files now indexed via tree-sitter; extracts functions, cross-function calls, source/. imports resolved to real file paths, and export/declare variable declarations (#866).json files now indexed via tree-sitter; extracts key/value contains tree, dependencies/devDependencies blocks as imports edges, extends edges (tsconfig, eslintrc), and $ref references (#866).sh, .bash, .json added to CODE_EXTENSIONS in detect.py so files are picked up during corpus scan (#866)*-callflow.html exists in graphify-out/ — works with --watch and graphify hook installcoverage/, lcov-report/, visual-tests/, visual-test/, __snapshots__/, snapshots/, storybook-static/, dist-protected/ added to _SKIP_DIRS — generated artefact dirs no longer appear in the corpus (#869, #870)graphify hook install now works in git linked worktrees — uses git rev-parse --git-path hooks instead of constructing .git/hooks/ directly (#865)graphify-out/converted/ are now checked against .graphifyignore before being added to the file list (#861)save_manifest() accepts a kind parameter (ast, semantic, both) — incremental AST-only graphify update no longer overwrites semantic_hash entries, preventing spurious full re-extracts on the next run (#857)skill-windows.md Step B3 were missing the graphify-out/ prefix, causing chunk files to be written to the wrong directory (#862)`.astro` support — frontmatter static imports, dynamic imports, and \ \ block imports all produce edges; tsconfig path aliases resolved
.astro support — frontmatter static imports, dynamic imports, and `<script>` block imports all produce edges; tsconfig path aliases resolved (#850).rebuild.lock orphan — lock file now contains a single PID while running and is unlinked on release; no more PID accumulation or orphan locks blocking downstream tooling (#858)``` pip install -U graphifyy
uv tool install graphifyy ```
.astro files now extracted as code — frontmatter static imports, dynamic imports, and <script> block imports all produce edges; tsconfig path aliases resolved (#850, PR #852).rebuild.lock no longer accumulates PIDs across rebuilds — now contains a single owning PID while running and is unlinked on release so downstream tooling polling for its absence unblocks promptly (#858, PR #859)ANTHROPIC_API_KEY or other provider keys during /graphify skill runs — the host IDE session provides the LLM (PR #864)`graphify update` is now idempotent — graph.json and GRAPH_REPORT.md only rewritten when content actually changes; topology comparison short-circuits
graphify update is now idempotent — graph.json and GRAPH_REPORT.md only rewritten when content actually changes; topology comparison short-circuits clustering on unchanged graphs (#824)graphify update --no-cluster — skip reclustering, write raw AST graph only (mirrors graphify extract --no-cluster) (#824)--no-cluster schema — now writes "links" key consistent with the full clustered path; previously toggled to "edges" on every mode switch.graphify_labels.json no longer rewritten on no-op rebuilds_check_shrink() helper across both code paths{parent_dir}_{filename_stem}_{entity}; the old filename-only format caused ghost-duplicate nodes when AST and semantic extractors disagreed; existing graphs with ghost duplicates can be cleaned with graphify extract --forcedefault=str in clustering sort keys prevents crashes on non-JSON-serializable edge attributespip install -U graphifyy
# or
uv tool install graphifyy
graphify update is now idempotent — graph.json and GRAPH_REPORT.md are only rewritten when content actually changes; topology comparison short-circuits clustering entirely on unchanged graphs, eliminating residual community-count drift (#824).graphify_labels.json labels don't drift onto wrong communities (#824)--no-cluster flag added to graphify update — writes raw AST graph without clustering, consistent with graphify extract --no-cluster (#824)graphify update --no-cluster now writes "links" key matching the schema of the full clustered path; previously wrote "edges", causing schema toggle on every mode switch.graphify_labels.json was rewritten on every rebuild even when nothing changed; now only written when outputs actually change_check_shrink() helper{parent_dir}_{filename_stem}_{entity} — the old filename-only format caused ghost-duplicate nodes when AST and semantic extractors disagreed on the stem; top-level files use just the filename stem; existing graphs with ghost duplicates can be cleaned up with graphify extract --forcedefault=str) prevents crashes when edge attributes contain non-serializable values`--backend claude-cli` — routes extraction through the Claude Code CLI, no API key needed, zero cost (#855/#856)
--backend claude-cli — routes extraction through the Claude Code CLI, no API key needed, zero cost (#855/#856)--> and <-- now reflect actual edge direction in directed graphs (#849/#853)shortest_path and get_neighbors MCP tools (#849/#853)--update manifest shrink — incremental re-runs no longer drop previously-indexed files (#837)file_type enum — prompt, schema, and synonym mapper all use the same 6 values (#840)ANTHROPIC_API_KEY (#846)sample.F90 → sample_preprocessed.F90 (#823)pip install -U graphifyy
# or
uv tool install graphifyy
graphify path and graphify explain now render arrow direction correctly — --> for caller→callee, <-- for callee←caller; previously the graph was loaded undirected so every hop printed --> regardless of stored direction (#849, #853)shortest_path and get_neighbors tools had the same reversed-arrow bug; now fixed in serve.py alongside the CLI commands (#849, #853)graphify extract --backend bedrock was rejected by the CLI guard even when AWS_PROFILE/AWS_REGION/AWS_DEFAULT_REGION/AWS_ACCESS_KEY_ID were set — boto3 session auth was never reached (#846)max(50, p99_degree)) as transit — hubs can still be destinations but no longer produce semantically meaningless 2-hop paths like ClassA → View → ClassB in Android/Spring corpora (#830)--update manifest shrink — after an incremental run, manifest.json was overwritten with only the changed-file subset, causing the next --update to re-flag the entire unchanged corpus as new; Step 9 now persists the full corpus via all_files fallback (#837)file_type enum aligned across skill.md and llm.py (both now enumerate all six values: code, document, paper, image, rationale, concept); synonym mapper in build.py silently coerces known LLM-emitted synonyms (pattern→concept, markdown→document, tool→code, etc.) before validation (#840)sample.F90 → sample_preprocessed.F90 to avoid case-collision with sample.f90 on macOS case-insensitive filesystems (credit: @FatahChan, #823)`bash pip install -U graphifyy `
read_text()/write_text() calls in the skill pipeline now specify encoding="utf-8" — bare calls defaulted to system codepage on Chinese-locale Windows, silently mojibaking node labels and Markdown content on --update. json.dumps now uses ensure_ascii=False so CJK characters are stored as-is.uv tool install --upgrade graphifyy over pip when uv is on PATH — pip was installing to the wrong environment when graphify was originally installed via uv tool._score_nodes now uses three-tier precedence (exact 1000 / prefix 100 / substring 1) — graphify path "Foo" "FooBar" no longer returns 0 hops when both labels substring-match the same node. Clear error emitted when source and target resolve to the same node.file_hash normalises path keys via .as_posix().lower() — Windows junction/case variants hash identically, fixing save_semantic_cache always reporting "Cached 0 files" on subsequent --update runs. check_semantic_cache now mirrors the same abs-path normalization as the save side._AGENTS_MD_SECTION now includes the /graphify skill trigger instruction — all 7 AGENTS.md platforms (OpenCode, Codex, Aider, Trae, Hermes, OpenClaw, Factory Droid) now correctly invoke the skill tool when the user types /graphify.pip install -U graphifyy
read_text()/write_text() calls in skill.md and skill-windows.md now specify encoding="utf-8" — bare calls defaulted to the system codepage on Chinese-locale Windows, silently mojibaking node labels and Markdown content on --update (#832)json.dumps in skill pipeline now uses ensure_ascii=False so Chinese/CJK characters are stored as-is rather than \uXXXX escaped (#832)uv tool install --upgrade graphifyy over pip when uv is on PATH — pip was installing to the wrong environment when graphify was originally installed via uv tool (#831)_score_nodes in serve.py now uses three-tier precedence (exact 1000 / prefix 100 / substring 1) instead of flat substring scoring — graphify path "Foo" "FooBar" no longer returns 0 hops when both labels substring-match the same node (#828)graphify path and MCP _tool_shortest_path now emit a clear error when source and target resolve to the same node, instead of silently returning 0 hops (#828)file_hash in cache.py now normalises path keys via .as_posix().lower() — Windows junction/case variants of the same file now hash identically, fixing save_semantic_cache always reporting "Cached 0 files" on subsequent --update runs (#826)check_semantic_cache now applies the same absolute-path normalization as save_semantic_cache so relative source_file paths resolve consistently on both sides (#826)_AGENTS_MD_SECTION now includes the /graphify skill trigger instruction — all 7 AGENTS.md platforms (OpenCode, Codex, Aider, Trae, Hermes, OpenClaw, Factory Droid) now correctly invoke the skill tool when the user types /graphify (#827)`bash pip install -U graphifyy `
-h/--help/-? guard: any help flag in any position now stops execution and prints usage — previously graphify cursor install --help silently installed into Cursor, and graphify benchmark --help crashed with FileNotFoundError: '--help'--version, -v, and graphify version now print the installed version and exit — previously fell through to "unknown command"GRAPHIFY_OLLAMA_NUM_CTX=<invalid> no longer falls back to hardcoded 131072 (which exhausted VRAM on constrained cards) — now falls through to auto-derived value with a warningGRAPHIFY_OLLAMA_NUM_CTX is pinned smaller than the estimated chunk size, graphify now warns explicitly that Ollama will silently truncate the prompt and suggests a corrected --token-budgetpip install -U graphifyy
-h/--help/-? in any position now stops execution — previously graphify cursor install --help silently installed into Cursor; graphify benchmark --help crashed with FileNotFoundError (#821)--version, -v, and graphify version now print the installed version and exit (#818)GRAPHIFY_OLLAMA_NUM_CTX=<invalid> no longer falls back to hardcoded 131072 (which exhausted VRAM) — it now falls through to the auto-derived value and prints a warning (#820)GRAPHIFY_OLLAMA_NUM_CTX is set smaller than the estimated chunk size, graphify now warns explicitly that Ollama will silently truncate the prompt and suggests a corrected --token-budget (#820)Skill version mismatch warning suppressed during hook-check (runs on every editor tool use, must be silent) and routed to stderr for all other command
_make_id now uses [^\w]+ with re.UNICODE to preserve non-ASCII word chars. Added NFKC normalization so composed/decomposed forms of the same character produce the same ID. Switched to casefold() for correct locale-sensitive lowercasing. _normalize_id in build.py kept in sync.or so empty-string source isn't silently swapped for the from fallback. Stale from/to keys are now popped after remapping so they can't leak into edge attributes in graph.json.--update merge block in skill.md now calls build_merge() directly instead of an inline NetworkX round-trip that re-introduced the direction-flip bug from #760. Dict merge ordering fixed so explicit source/target always win over stale attrs. Hyperedges pulled from G.graph (full merged set) rather than just the new extraction.CHUNK_PATH derived from graphify-out/.graphify_root at dispatch time — chunk files no longer silently land in the wrong directory due to undefined subagent cwd.hook-check (runs on every editor tool use, must be silent) and routed to stderr for all other commands.pip install -U graphifyy
_make_id and _normalize_id now apply NFKC Unicode normalization before ID generation -- composed/decomposed forms of the same character (e.g. é typed vs pasted from a PDF) now produce the same node ID; switched from .lower() to .casefold() for correct Turkish/German/Greek case folding; both functions are now byte-for-byte equivalent (#811)[^\w]+ with re.UNICODE replaces the old [^a-zA-Z0-9]+ so Unicode word chars are preserved as part of the ID (#811)or so empty-string source is not silently swapped for from; stale from/to keys are now popped before the edge is emitted so they can't leak into graph.json edge attributes (#803)--update merge now calls build_merge() directly instead of an inline NetworkX round-trip that re-introduced the direction-flip bug from #760; dict merge ordering fixed so explicit source/target always win over stale attrs; hyperedges pulled from G.graph (merged) rather than just the new extraction (#801)CHUNK_PATH injected at dispatch time from graphify-out/.graphify_root) so the Write tool doesn't lose chunks to an undefined working directory (#808)hook-check (runs on every editor tool use and must be silent) and routed to stderr for all other commandsOllama VRAM exhaustion (#798): num_ctx is now derived from the actual chunk size instead of hardcoded 131072. With --token-budget 8192, the old value
num_ctx is now derived from the actual chunk size instead of hardcoded 131072. With --token-budget 8192, the old value forced Ollama to allocate 128k KV-cache slots on a 31B model — 4×128k slots by chunk 4 caused OOM. New formula: min(input_tokens + output_cap + 2000, 131072) so an 8k chunk gets ~26k instead.GRAPHIFY_OLLAMA_NUM_CTX / GRAPHIFY_OLLAMA_KEEP_ALIVE env vars as tuning knobs.graphify export callflow-html (#797): generates a self-contained Mermaid architecture/call-flow HTML page from graphify-out/graph.json — community sections, interactive flowcharts with zoom/pan, call detail tables, and graph report highlights.--watch rebuild and post-commit hook if the file already exists. Run once, stays current forever.uv tool upgrade graphifyy
# or: pip install --upgrade graphifyy
num_ctx now derived from actual chunk size instead of hardcoded 131072 -- over-allocating 128k KV-cache slots for small chunks exhausted VRAM by chunk 4 on large models; formula is min(input_tokens + output_cap + 2000, 131072) so --token-budget 8192 gets ~26k instead of 131072 (#798)GRAPHIFY_OLLAMA_NUM_CTX / GRAPHIFY_OLLAMA_KEEP_ALIVE env vars as tuning knobs (#798)graphify export callflow-html -- generates a self-contained Mermaid architecture/call-flow HTML page from graphify-out/graph.json, grouped by community with interactive zoom/pan diagrams, call detail tables, and graph report highlights (#797)--watch rebuild and post-commit hook if the file already exists -- opt-in by existence, zero config (#800)Your coding agent can read these notes before it upgrades. Set up the MCP server →