graphify

AI

graphify release notes.

Latest v0.9.45 · by graphifyWebsiteGraphify-Labs/graphify

Release activity

Release activity — 26 releases across 23 days since Jul 18, 2026. Each cell is one day; darker means more releases that day. Nothing is recorded before Jul 18, 2026. Older weeks are hidden at this screen width.
MayJunJulAug
SundayNo releases on Jul 19, 20261 release on Jul 26, 2026No releases on Aug 2, 20261 release on Aug 9, 20261 release on Aug 16, 2026
Monday2 releases on Jul 20, 20261 release on Jul 27, 2026No releases on Aug 3, 20261 release on Aug 10, 2026No releases on Aug 17, 2026
Tuesday1 release on Jul 21, 20261 release on Jul 28, 2026No releases on Aug 4, 20261 release on Aug 11, 2026
Wednesday2 releases on Jul 22, 20261 release on Jul 29, 20262 releases on Aug 5, 20261 release on Aug 12, 2026
ThursdayNo releases on Jul 23, 20261 release on Jul 30, 20261 release on Aug 6, 20261 release on Aug 13, 2026
Friday1 release on Jul 24, 2026No releases on Jul 31, 20261 release on Aug 7, 20261 release on Aug 14, 2026
Saturday1 release on Jul 18, 2026No releases on Jul 25, 20261 release on Aug 1, 20261 release on Aug 8, 20261 release on Aug 15, 2026

26 releases since Jul 18, 2026, busiest day 2

Changelog

v0.9.45

Latest
Fixed 4
  • graphify install <platform> now advances the .graphify_version stamp only for the platform it actually (re)writes, instead of stamping every installed platform as current
  • an incremental rebuild no longer collapses the whole graph when the .graphify_root marker records a subfolder while stored source_file paths are relative to the repo root
  • a Go file that declares both an exported and an unexported symbol differing only by case no longer collapses them onto one node id and drops one
  • loading a graph.json that contains a hyperedge with no id field no longer crashes the incremental re-extract with KeyError: 'id'
0.9.45
  • Fix: graphify install <platform> now advances the .graphify_version stamp only for the platform it actually (re)writes, instead of stamping every installed platform as current; a platform whose skill content was left untouched keeps its old stamp so its staleness warning stays truthful (#2694, thanks @ousamabenyounes). This completes #2694 (the CLAUDE_CONFIG_DIR half shipped in 0.9.44).
  • Fix: an incremental rebuild no longer collapses the whole graph when the .graphify_root marker records a subfolder while stored source_file paths are relative to the repo root; the marker is validated against the stored paths before it is trusted as their anchor, so a mismatched marker can't make every unchanged source look deleted (#2603, thanks @catpotd). A genuinely deleted source is still evicted, and incremental ids stay identical to a cold build.
  • Fix: a Go file that declares both an exported and an unexported symbol differing only by case (e.g. Run and run, which are distinct in Go's case-sensitive visibility rules) no longer collapses them onto one node id and drops one; the exported symbol keeps its stable id and the unexported one is disambiguated, so an intra-file call to the unexported symbol resolves locally instead of phantoming to another package (#2779, thanks @catpotd). Only the Go extractor's id assignment is affected; the shared id normalization is unchanged, so no other language's ids move.
  • Fix: loading a graph.json that contains a hyperedge with no id field (the semantic extractor emits them and they persist verbatim) no longer crashes the incremental re-extract with KeyError: 'id'; id-less hyperedges are tolerated and retained (#2775, thanks @ousamabenyounes).
View originalPermalink
How v0.9.45 went

v0.9.44

Added 2
  • graphify hook install reads a committed .graphifyrc (viz_node_limit=<int>) and bakes the visualization node limit into the generated git hooks, so a project-wide limit is shared via version control and survives hook regeneration
  • hook status reports the baked visualization node limit from .graphifyrc with support for ${GRAPHIFY_VIZ_NODE_LIMIT:-<n>} default and graceful degradation on malformed .graphifyrc
Fixed 10
  • graphify install now writes the CLAUDE.md registration into $CLAUDE_CONFIG_DIR when that env var relocates the Claude profile, instead of always mutating the default ~/.claude/CLAUDE.md
  • JS/TS inline or nested function expressions including generator function expressions no longer fabricate an INFERRED indirect_call when one of their parameters or locals shares a name with an unrelated callable
  • a git-tracked file that also matches a .gitignore pattern is no longer dropped from the corpus, matching git's behavior of never un-tracking such a file
  • git ls-files probe is skipped entirely when no .gitignore is in play to avoid overhead for ordinary corpora
  • doctest and Catch2 string-named test cases are recovered as callable nodes contained by the file instead of being dropped as ERROR nodes
  • a punctuation-only test name gets a distinct line-positional id instead of collapsing onto the file-stem id
0.9.44
  • Feature: graphify hook install reads a committed .graphifyrc (viz_node_limit=<int>) and bakes the visualization node limit into the generated git hooks, so a project-wide limit is shared via version control and survives hook regeneration; hook status reports it (#2760, thanks @hopstreax). The baked value uses a ${GRAPHIFY_VIZ_NODE_LIMIT:-<n>} default so an explicit per-run env var still wins, and hook status degrades gracefully on a malformed .graphifyrc.
  • Fix: graphify install (Claude always-on) now writes the CLAUDE.md registration into $CLAUDE_CONFIG_DIR when that env var relocates the Claude profile, instead of always mutating the default ~/.claude/CLAUDE.md (part of #2694, thanks @AromalBiju1).
  • Fix: a JS/TS inline or nested function expression — including a generator function expression (function*(k){…}) — no longer fabricates an INFERRED indirect_call when one of its parameters/locals shares a name with an unrelated callable; the expression's own bindings now shadow the name (#2752, thanks @imagineers-tyler), completing the shadow family alongside catch/arrow/loop/external-import (#2757).
  • Fix: a git-tracked file that also matches a .gitignore pattern (a committed file later added to .gitignore, or a force-added one) is no longer dropped from the corpus, matching git's own behavior of never un-tracking such a file; .graphifyignore/--exclude stay authoritative and a non-git corpus is unaffected (#2759, thanks @NithishKumar04). The git ls-files probe is skipped entirely when no .gitignore is in play, so ordinary corpora pay nothing for it.
  • Fix: doctest/Catch2 string-named test cases (TEST_CASE("..."), SCENARIO, TEST_CASE_TEMPLATE), which tree-sitter-cpp drops as ERROR nodes, are recovered as callable nodes contained by the file (#2594, thanks @ousamabenyounes); a punctuation-only test name gets a distinct line-positional id instead of collapsing onto the file-stem id.
  • Fix: graphify affected resolves an absolute-path seed against the repo root derived from the graph's own location instead of the current working directory, so a blast-radius query with an absolute seed run from anywhere (an editor, a script) no longer silently returns nothing; a seed outside the root still misses cleanly (#2706, thanks @ousamabenyounes).
  • Fix: a lazy CommonJS require(...) inside a function body (the idiom for breaking circular dependencies) now emits the same imports_from/imports dependency edges as a top-level require, attributed to the enclosing function, instead of being silently dropped; a dynamic require(variable) is still skipped (#2700, thanks @rajanpanth).
  • Fix: a JS/TS identifier bound by an import whose target resolves outside the scanned corpus (e.g. a lucide-react icon) is now shadowed, so using it as a value no longer fabricates an INFERRED indirect_call onto an unrelated same-named callable elsewhere in the corpus; a relative/in-corpus import still resolves to its real target (#2757, thanks @phudayyy).
  • Fix: an OCaml qualified call M.f to an external module (one not defined in the same file, e.g. Hardcaml's Reg_spec.create) no longer binds to a same-named local let f — which produced a false calls edge and, when the caller was that local f, a f -> f self-loop. External qualified calls are kept as a distinct target labelled by the full path; unqualified calls and calls into a locally-defined module still resolve locally, and cross-file Geo.area still collapses onto another file's area.
View originalPermalink
How v0.9.44 went

v0.9.43

Added 1
  • OCaml support with `.ml`/`.mli` extraction via tree-sitter-ocaml (optional `[ocaml]` extra), extracting modules, top-level and module-level values/functions, types and their variant constructors, `open` imports, and function calls
Fixed 5
  • Cross-file INFERRED `uses` edge now binds to the symbol whose body actually references the imported name instead of fanning out from the import line to every class in the file
  • Named `function` declaration nested inside another function, including inside an arrow-defined component or an arrow callback, is now noded, contained by its enclosing scope, and its calls resolve
  • Bash extractor resolves two more `source` path forms including `source "$(dirname "$VAR")/lib/x.sh"` and a `..` suffix on a tracked-variable base, while preventing `..` from walking past the base's parent to an arbitrary host path
  • Wiki article link now targets the article's filename verbatim instead of a percent-encoded twin, so a label with special characters or non-ASCII no longer produces a link that names no file on disk
  • Export filenames are budgeted against the full destination path rather than only `NAME_MAX`, so a long output directory on Windows no longer pushes an Obsidian/wiki note path past `MAX_PATH`
0.9.43
  • OCaml support.ml/.mli extraction via tree-sitter-ocaml (optional [ocaml] extra). Extracts modules, top-level and module-level values/functions, types and their variant constructors, open imports, and function calls; qualified calls (Geo.area) resolve to the value, and cross-file open/call targets collapse onto the unique real definition via the corpus stub rewire.
  • Fix: a cross-file INFERRED uses edge now binds to the symbol whose body actually references the imported name (a module-level function is a valid source; a co-located class that never touches the import gets no edge), instead of fanning out from the import line to every class in the file (#2652, thanks @ousamabenyounes).
  • Fix: a named function declaration nested inside another function — including inside an arrow-defined component (const Panel = () => { function handleClick(){} }) or an arrow callback (useEffect(() => { function h(){} })) — is now noded, contained by its enclosing scope, and its calls resolve instead of dangling (#2653, thanks @himanshupatro-334).
  • Fix: the Bash extractor resolves two more source path forms — source "$(dirname "$VAR")/lib/x.sh" and a .. suffix on a tracked-variable base — while a .. cannot walk past the base's parent to an arbitrary host path, so a hostile corpus can't make the extractor stat or record an out-of-tree file (#2596, thanks @hudsonwa).
  • Fix: a wiki article link now targets the article's filename verbatim instead of a percent-encoded twin, so a label with ( ) & # or non-ASCII no longer produces a link that names no file on disk (#2597, thanks @abhay-codes07).
  • Fix: export filenames are budgeted against the full destination path rather than only NAME_MAX, so a long output directory on Windows no longer pushes an Obsidian/wiki note path past MAX_PATH and aborts the export mid-write (#2655, thanks @abhay-codes07).
View originalPermalink
How v0.9.43 went

v0.9.42

Changed 2
  • source_file and model-facing paths are canonicalized to POSIX
  • Atomic writes and installs are hardened for Windows, including read-only packaged bundle support
Fixed 12
  • JS/TS for...of and for...in loop bindings are now shadowed so they no longer fabricate indirect_call edges
  • Python relative subpackage imports (from ..pkg.sub import x) now resolve to the package __init__
  • Non-regular files (FIFO/device) in the tree no longer hang extraction and are skipped instead
  • .sql files that fail to parse with tree-sitter-sql installed-but-broken now report the real error instead of "not installed"
  • graphify update incremental runs now re-queue a file rewritten to the same length within one mtime tick
  • Graph provenance (built_at_commit) is now stamped from the analysed repo, not the shell cwd

A large correctness, determinism, and portability release with fixes from many community contributors.

Extraction / resolution

  • JS/TS for...of / for...in loop bindings are shadowed, so they no longer fabricate indirect_call edges (#2685, @ousamabenyounes) — completes the loop/closure/catch shadow family (#2568/#2569/#2517).
  • Python relative subpackage imports (from ..pkg.sub import x) resolve to the package __init__ (#2688, @ousamabenyounes).
  • A non-regular file (FIFO/device) in the tree no longer hangs extraction; non-regular files are skipped (#2463, @itskaism).
  • A .sql file that fails to parse with tree-sitter-sql installed-but-broken now reports the real error instead of "not installed" (#2602, @ousamabenyounes).

Determinism / data integrity

  • graphify update incremental runs re-queue a file rewritten to the same length within one mtime tick (#2466, @itskaism), complementing the 0.9.40 file-hash guard (#2612).
  • Graph provenance (built_at_commit) is stamped from the analysed repo, not the shell cwd (#2699, @C0KERNEL).
  • Corrupt semantic-cache entries are surfaced and re-extracted instead of silently missed (#2683, @ousamabenyounes).
  • graph_has_legacy_ids no longer false-positives on a global MCP node id (#2408, @aryanbonigala).

Paths / Windows portability

  • source_file and the model-facing paths are canonicalized to POSIX; atomic writes and installs are hardened for Windows, incl. a read-only packaged bundle (#2620/#2622, @rajarshidattapy; #2453, @bensleveritt).
  • GRAPH_REPORT.md uses a portable basename, not an absolute host path (#2682, @ousamabenyounes).
  • The apm.yml fallback parser captures the package version (#2465, @itskaism).

CLI / export

  • affected resolves a ./-relative seed instead of silently returning nothing (#2707, @phudayyy).
  • Hyperedge regions in graph.html are traced in convex-hull order, so the shaded polygon no longer self-intersects (#2449, @ysys143).

Test / docs

  • Windows-portability test fixes, a refreshed ARCHITECTURE.md module table with a doc-parity test, and README notes on CI parity + Windows prerequisites (#2620/#2622/#2126/#2642/#2646/#2647/#2648/#2651, @rajarshidattapy/@redzwanmutalib/@nelsondeleonc-source).
View originalPermalink
How v0.9.42 went

v0.9.41

Fixed 13
  • graphify update refuses to overwrite the graph with a shrunken one when the shrink was caused by an extractor failure this run, instead of silently replacing good data; a genuine deletion still shrinks the graph
  • a JS/TS catch binding passed as a call argument no longer fabricates an indirect_call edge to an unrelated same-named callable
  • Cargo.toml is recognized as a package manifest, minting a canonical package node plus depends_on edges
  • an explicitly-passed scan root is no longer excluded by an unanchored pattern in a parent-directory .gitignore
  • the API extraction prompt now instructs backends to capture the per-node rationale attribute, matching the skill path
  • source_file is canonicalized to POSIX separators, so relative inputs on Windows no longer produce non-portable node ids

Correctness, determinism, and data-integrity release — a large batch of community-contributed fixes.

  • Fix: graphify update refuses to overwrite the graph with a shrunken one when the shrink was caused by an extractor failure this run, instead of silently replacing good data (#2663, thanks @ousamabenyounes); a genuine deletion still shrinks the graph.
  • Fix: a JS/TS catch binding passed as a call argument no longer fabricates an indirect_call edge to an unrelated same-named callable (thanks @imagineers-tyler); completes the 0.9.38/0.9.40 arrow-parameter fixes (#2568).
  • Fix: Cargo.toml is recognized as a package manifest (#2434, thanks @ousamabenyounes), minting a canonical package node plus depends_on edges.
  • Fix: an explicitly-passed scan root is no longer excluded by an unanchored pattern in a parent-directory .gitignore (#2468, thanks @hopstreax).
  • Fix: the API extraction prompt now instructs backends to capture the per-node rationale attribute, matching the skill path (#2482, thanks @hopstreax). Invalidates cached semantic chunks, which re-extract on the next run.
  • Fix: source_file is canonicalized to POSIX separators, so relative inputs on Windows no longer produce non-portable node ids (#2627, thanks @rajarshidattapy).
  • Fix: a warm cache hit no longer re-anchors a CWD-relative source_file to a ghost path when the working directory differs from the graph root (#2632, thanks @rajarshidattapy).
  • Fix: the wiki/obsidian audit trail counts each incident edge once instead of double-counting intra-community edges (#2635, thanks @rajarshidattapy).
  • Fix: C# members inside a #if ... #endif preprocessor block are extracted and attached to their class (#2634, thanks @rohit-jsfreaky).
  • Fix: query no longer prints the truncation banner when no nodes were actually cut (#2601, thanks @ousamabenyounes); a genuine node truncation still warns.
  • Fix: a PHP use import written with a leading-backslash / fully-qualified prefix now resolves to its target definition (#2661, thanks @ousamabenyounes).
  • Fix: an unresolved local JS/TS import (to a file absent from the scan) emits a stable, portable ref target id instead of a per-checkout absolute-path slug (#2457, thanks @rohit-jsfreaky).
  • Fix: graphify benchmark no longer crashes on a node whose label is None (#2674, thanks @Arthuro0103).
View originalPermalink
How v0.9.41 went

v0.9.40

Fixed 12
  • The 0.9.37 partial-parse warning no longer fires on valid TypeScript/TSX, now firing only when recovery plausibly costs symbols
  • file_hash()'s stat fastpath no longer serves a stale digest when a file is rewritten to the same size within one mtime tick
  • Stored-path absoluteness is detected cross-platform so a POSIX-absolute source_file from a Linux/CI-built graph no longer leaks into node ids on Windows
  • normalize_id() is idempotent for Turkish İ and similar codepoints with no ASCII identifier ids changes
  • graph.json collection order is deterministic across runs
  • explain and _find_node resolve node ids containing punctuation or non-ASCII characters

Correctness and determinism release: fixes a TypeScript false-warning regression, several node-id / path / cache determinism bugs, a Python crash, a Go phantom-reference, and more — with fixes from many community contributors.

  • Fix: the 0.9.37 partial-parse warning no longer fires on valid TypeScript/TSX (#2610, #2599, thanks @Sid-AutoWisdom and @atlasplatformu-ai). The warning now fires only when recovery plausibly cost symbols, so valid TS with a & in a JSX string attribute or a semicolon-less in_* interface member is silent, while the genuine Kotlin one-line-body and Luau cases still warn.
  • Fix: file_hash()'s stat fastpath no longer serves a stale digest when a file is rewritten to the same size within one mtime tick (#2612, thanks @rajarshidattapy).
  • Fix: stored-path absoluteness is detected cross-platform, so a POSIX-absolute source_file from a Linux/CI-built graph no longer leaks into node ids on Windows (#2618, thanks @rajarshidattapy).
  • Fix: normalize_id() is idempotent for Turkish İ and similar codepoints; no ASCII identifier ids change (#2614, thanks @rajarshidattapy).
  • Fix: graph.json collection order is deterministic across runs (#2582, thanks @hjotha).
  • Fix: explain / _find_node resolve node ids containing punctuation or non-ASCII characters (#2467, thanks @sean-soomgo).
  • Fix: .graphifyignore patterns match regardless of Unicode NFC/NFD normalization, so an accented ignore rule works on macOS (#2544, thanks @bruno-growthsales).
  • Fix: Obsidian vault metadata directories (.obsidian, .smart-env) are skipped during detection (#2493, thanks @rohit-jsfreaky).
  • Fix: a single unparenthesised arrow parameter (x => f(x)) is shadowed, so it no longer fabricates an indirect_call edge to an unrelated same-named callable (thanks @imagineers-tyler); follows the 0.9.38 sibling-closure fix (#2568).
  • Fix: Python extraction no longer crashes resolving an over-deep relative import above the package root (#2605, thanks @SinghAman21).
  • Fix: a Go qualified type (pkg.Type) resolves by import path instead of binding by bare name to an unrelated same-named symbol (#2608, thanks @gnukeno).
  • Fix: graph.html's document title no longer embeds the generator's absolute host path (#2598, thanks @michaelxer).
View originalPermalink
How v0.9.40 went

v0.9.39

Fixed 7
  • affected now traverses a dynamic import('…') made inside a function or at module scope, keying dedup on the importing file to emit one file-level dynamic_import edge per file/target while keeping the call-site edge
  • Python member call on an untyped receiver (x.get(...)) no longer binds by name alone to a same-named module-level function, resolving only with receiver-type, import, or self/cls/super evidence
  • fuzzy dedup no longer over-merges two distinct entities in the same file whose long labels differ by a content word, judging one-token differences on the differing tokens rather than prefix-weighted whole-label similarity
  • graphify watch now rebuilds on a documentation-only deletion batch, evicting a deleted doc's nodes immediately rather than waiting for the next code-file event
  • Objective-C @protocol declaration is no longer treated as a receiver type
  • Objective-C category or class-extension interface (@interface Foo (Bar)) now folds into the base class instead of minting a duplicate node
  • Objective-C message send to a @property or ivar receiver now resolves through the property/ivar's declared type

Correctness release: fixes an affected gap for in-function dynamic imports, stops a Python name-only member-call fabrication, tightens fuzzy dedup, closes a watcher deletion residual, and fixes three Objective-C resolution bugs.

  • Fix: affected now traverses a dynamic import('…') made inside a function or at module scope (#2584, thanks @phudayyy). The 0.9.38 dedupe keyed only on the target, so an in-function dynamic import (whose symbol-level edge is anchored on the enclosing function) suppressed the file-level edge affected follows; the dedupe now keys on the importing file, emitting one file-level dynamic_import edge per file/target while keeping the call-site edge.
  • Fix: a Python member call on an untyped receiver (x.get(...)) no longer binds by name alone to a same-named module-level function, fabricating a false high-confidence calls edge and a god node (#2417, #2586, thanks @EZZEASY). Such a call is now resolved only with receiver-type, import, or self/cls/super evidence, matching the TypeScript fix from 0.9.37; super().method() still resolves.
  • Fix: fuzzy dedup no longer over-merges two distinct entities in the same file whose long labels differ by a content word (#2576, thanks @wilyan09007). A one-token difference is judged on the differing tokens rather than the prefix-weighted whole-label similarity, so asset contribution flow and asset consumption flow stay separate while genuine typo and whitespace/case variants still collapse.
  • Fix: graphify watch now rebuilds on a documentation-only deletion batch instead of only flagging it (#2580, thanks @angmeng), so a deleted doc's nodes are evicted immediately rather than waiting for the next code-file event. (The general deleted-file leak was already fixed in 0.9.10; this closes the live-watcher residual.)
  • Fix: Objective-C member-call resolution (#2589, #2590, #2591, thanks @xiongjianxu). A @protocol declaration is no longer treated as a receiver type (it collided with a same-named class); a category or class-extension interface (@interface Foo (Bar)) now folds into the base class instead of minting a duplicate node; and a message send to a @property or ivar receiver ([self.svc run], [_svc run]) now resolves through the property/ivar's declared type.
View originalPermalink
How v0.9.39 went

v0.9.38

Fixed 5
  • Callback-body local names are now scoped to that body instead of unioned under the shared declaration, so a real indirect_call in one sibling closure is no longer dropped because another sibling declared a same-named local
  • Kotlin calls in a property initializer are now collected, including calls in class properties, by lazy delegates, companion-object properties, and top-level property initializers
  • Swift receiver-type inference now handles @Environment(Store.self) properties and factory-initialised bindings
  • SQL extractor no longer emits a reads_from edge to a CTE name, treating WITH cte AS (...) names as scoped to their query rather than as tables
  • Dynamic await import('…') inside a nested function or at module scope now produces an edge, and dynamic_import edges are now included in affected

Correctness release: fixes a callback-scoping regression from 0.9.37, collects Kotlin property-initializer calls, resolves Swift attribute/factory receivers, stops SQL CTE names becoming table refs, and captures nested/module-scope dynamic imports.

  • Fix: the 0.9.37 callback-body fix (#2552) no longer lets a local declared in one callback suppress a call in a sibling callback (#2568, thanks @imagineers-tyler). Each callback body's local names are now scoped to that body instead of unioned under the shared declaration, so a real indirect_call in one sibling closure is no longer dropped because another sibling declared a same-named local. This can only restore dropped edges, never fabricate.
  • Fix: Kotlin calls in a property initializer are now collected (#2565, thanks @kskchaitanya1993). A class property (val repo = createRepo()), a by lazy { ... } delegate, a companion-object property, and a top-level property initializer now produce calls edges attributed to the enclosing class (or file), including fully-qualified calls. A plain literal initializer produces no edge.
  • Fix: Swift receiver-type inference now handles @Environment(Store.self) properties and factory-initialised bindings (#2561, thanks @fakewaffle). A member call on a receiver typed only through an @Environment(Type.self) attribute, or bound to an in-corpus factory whose return type is known (let x = ServiceFactory.make()), now resolves. Ambiguous or non-concrete returns (opaque some P, arrays, out-of-corpus) stay unresolved rather than guessing.
  • Fix: the SQL extractor no longer emits a reads_from edge to a CTE name (#2577, thanks @wilyan09007). A WITH cte AS (...) name is scoped to its query and is no longer treated as a table, so it no longer mints a bare stub that could bind to an unrelated same-named symbol; an outer real table sharing a subquery-CTE's name still resolves.
  • Fix: a dynamic await import('…') inside a nested function or at module scope now produces an edge (#2575, thanks @phudayyy), and dynamic_import edges are now included in affected. Calls inside a nested named function are also collected now. A dynamic import already captured as a deferred imports_from is not double-counted.
View originalPermalink
How v0.9.38 went

v0.9.37

Fixed 7
  • TypeScript member calls no longer fabricate a high-confidence calls edge by matching a receiver type by name alone; a member call resolves only when the receiver's type is defined in the same file or actually imported by the caller's file
  • TypeScript/JavaScript calls inside a callback body passed to another call are no longer dropped and are now walked and attributed to the declaration
  • Kotlin imports are now matched and resolved to their real target nodes
  • Kotlin fully-qualified calls like com.example.Foo.bar() now produce a calls edge
  • Kotlin files with syntax the bundled grammar cannot parse now emit a warning instead of silently extracting nothing, with declarations recovered inside an error span
  • graphify update now retries a file whose extractor failed on a previous run instead of stamping it up-to-date forever
  • The claude-cli backend now surfaces an API error carried in the stdout envelope instead of treating it as an empty success

Correctness release: stops TypeScript fabricating high-confidence call edges, fixes Kotlin import/call/parse gaps against the bundled grammar, retries failed extractions on update, and surfaces claude-cli envelope errors.

  • Fix: TypeScript member calls no longer fabricate a high-confidence calls edge by matching a receiver type by name alone (#2553, thanks @Earthfreedom). A member call resolves only when the receiver's type is defined in the same file or actually imported by the caller's file, so a third-party import type { Repo } can no longer bind to an unrelated local class Repo; table-inferred receivers are tiered to INFERRED rather than EXTRACTED.
  • Fix: TypeScript/JavaScript calls inside a callback body passed to another call (for example export const handler = wrapper(async (req) => { helper() })) are no longer dropped (#2552, thanks @Earthfreedom). The callback body is walked and its calls attributed to the declaration, through the same import-gated resolution so it cannot fabricate edges.
  • Fix: Kotlin imports, fully-qualified calls, and one-line type bodies (#2526, #2550, #2551, thanks @spaceBrownie, @thomasrengot-hub, and @Mustaqeem66 for #2531). The extractor now matches the bundled grammar's import node (imports were silently dropped) and resolves each import to the real target node; a fully-qualified call like com.example.Foo.bar() now produces a calls edge; and a file with syntax the bundled grammar cannot parse (such as a one-line class C { val x }) now emits a warning instead of silently extracting nothing, with declarations recovered inside an error span keeping their enclosing class.
  • Fix: graphify update now retries a file whose extractor failed on a previous run instead of stamping it up-to-date forever (#2543, thanks @michaelxer). A failed extraction is no longer recorded in the manifest as processed, a manifest already poisoned by the old behavior is healed on the next run, and genuinely-unchanged files are not re-processed.
  • Fix: the claude-cli backend now surfaces an API error carried in the stdout envelope (for example a rate limit returned with a zero exit code) instead of treating it as an empty success (#2554, thanks @annieyii). The error is raised on both the zero and non-zero exit paths.
View originalPermalink
How v0.9.37 went

v0.9.36

Fixed 4
  • four commands that failed silently while exiting 0 now surface the problem: cluster-only warns when --backend/--model/--batch-size are ignored because saved labels are being reused; the community-label prompt no longer collides with the discard sentinel; tree --root exits non-zero when the root matches no source file instead of silently flattening the tree; and cluster-only stamps built_at_commit from the analysed graph rather than the shell's working directory
  • a Swift extension Foo in a different file from Foo no longer drops static and singleton call edges into the type; the extension node id is now remapped consistently so the extension merges onto its base type before call resolution
  • node-id collision resolution is now deterministic and prefers active over archived paths, ranked by a lifecycle penalty computed on the root-relative path and a reversed-segment tie-break
  • the Windows skill now runs on PowerShell with steps emitted as PowerShell here-string interpreter invocations and Remove-Item cleanup instead of bash-only commands

Correctness release: surfaces four silent CLI failures, fixes Swift cross-file extension call loss, makes node-id collision resolution deterministic, and makes the Windows skill runnable on PowerShell.

  • Fix: four commands that failed silently while exiting 0 now surface the problem (#2534, thanks @elecnix). cluster-only warns when --backend/--model/--batch-size are ignored because saved labels are being reused; the community-label prompt no longer collides with the discard sentinel (a model echoing the key back is no longer silently dropped); tree --root exits non-zero when the root matches no source file instead of silently flattening the tree; and cluster-only stamps built_at_commit from the analysed graph rather than the shell's working directory. Also folds in the cluster-only refused-write guard from #2522 (thanks @aniJani).
  • Fix: a Swift extension Foo in a different file from Foo no longer drops static and singleton call edges into the type (#2538, thanks @pawelo446). The extension node id is now remapped consistently so the extension merges onto its base type before call resolution, and the merge is gated so it never absorbs a same-named type from another language.
  • Fix: node-id collision resolution is now deterministic and prefers active over archived paths (#2532, thanks @michaelxer for the active/archived idea in #2540). Two files that mint the same id (for example plans/_done/x.md vs plans/in-progress/x.md) are ranked by a lifecycle penalty computed on the root-relative path and a reversed-segment tie-break, so the winner no longer depends on ASCII filename order, absolute-vs-relative path form, or the checkout directory name.
  • Fix: the Windows skill now runs on PowerShell (#2528, thanks @tannermosher2015-debug). The Windows skill variant's steps were bash-only ($(cat ...), rm -f, find -delete); they are now emitted as PowerShell (here-string interpreter invocations and Remove-Item cleanup), with POSIX skills unchanged and step parity enforced by a generator check.
View originalPermalink
How v0.9.36 went

v0.9.35

Fixed 6
  • The build_merge shrink guard now diffs the on-disk baseline by node identity to detect node loss from sources that were neither re-extracted nor pruned, reports how many nodes a re-extract replaced, and remains active under prun_sources (skipped only under dedup)
  • build_merge/merge_raw_extraction prune_sources now prunes correctly when given absolute paths under a non-standard layout by deriving the scan root via suffix-matching stored source paths, and warns when a prune matches nothing
  • graphify update now removes newly-ignored files from an existing graph when they are added to .graphifyignore, --exclude, or a skip rule
  • Java local classes and same-named external annotations no longer collapse into one node by running the type resolver before unique-label stub rewire and parking imported-but-external types on their fully-qualified names
  • graphify callflow now respects edge direction so the caller/callee columns are correct and indirect calls are counted
  • Relational-intent verbs in a query (calls, uses, extends) no longer seat spurious seeds by excluding them from the per-term seed guarantee

Correctness release: revives the merge shrink guard, fixes prune/eviction on absolute paths and newly-ignored files, stops a Java external-annotation conflation, and makes callflow and query direction/relation aware.

  • Fix: the build_merge #479 shrink guard is no longer effectively dead (#2497, thanks @sortakool). It read the post-replace node count, so a broken partial re-extract could silently destroy nodes without tripping the guard, and the guard was skipped entirely under prune_sources. The guard now diffs the on-disk baseline by node identity and refuses any loss from a source that was neither re-extracted nor pruned this run (active even under prune_sources, skipped only under dedup), and reports how many nodes a re-extract replaced.
  • Fix: build_merge/merge_raw_extraction prune_sources now prunes correctly when given absolute paths under a non-standard layout, deriving the scan root by suffix-matching stored source paths, and warns (instead of reporting "already clean") when a prune matches nothing (#2446, thanks @AI-invest).
  • Fix: graphify update now removes newly-ignored files from an existing graph (#2495, thanks @alisson-acioli). A file added to .graphifyignore/--exclude (or a skip rule) is evicted even though it still exists on disk; .gitignore-driven eviction applies on an explicit full update. Files that merely changed are still preserved, and a file that leaves the corpus without matching any live ignore rule stays (fail-closed, #1795).
  • Fix: a Java local class and a same-named external annotation (e.g. a local class Component and Spring's @Component) no longer collapse into one node (#2504, thanks @te7ina-honey). The Java type resolver now runs before the unique-label stub rewire and parks an imported-but-external type on its fully-qualified name, and cross-file import resolution checks the package. In-corpus annotation resolution is unchanged.
  • Fix: graphify callflow now respects edge direction, so the caller/callee columns are correct (#2508, thanks @Tomaskobel). The call-flow HTML loads the graph directed and recovers direction from the stored _src/_tgt markers (consistent with the path fix), and indirect calls are counted.
  • Fix: relational-intent verbs in a query ("calls", "uses", "extends", ...) no longer seat spurious seeds (#2507, thanks @filipechagas). Such a verb is excluded from the per-term seed guarantee, so a decoy matching only the verb no longer becomes a traversal root, while a verb that is a genuine symbol name can still be seeded on merit.
View originalPermalink
How v0.9.35 went

v0.9.34

Fixed 6
  • C# receiver typing no longer drops a true call when a same-named variable is declared untypeably elsewhere in the method, by tracking receiver types per lexical declaration scope and resolving by call position
  • graphify path and the MCP shortest_path tool now respect edge direction by default instead of running on an undirected view
  • semantic extraction no longer aborts at merge with a TypeError when a hyperedge carries dict-shaped members, by normalizing members to ids or dropping them with a warning
  • graphify merge-graphs no longer drops hyperedges by relabeling member ids with per-repo prefix, unioning both inputs' hyperedges, and writing them to both top-level and nested slots
  • build_from_json now reads hyperedges from both the top-level and nested graph slots, so label and re-cluster runs no longer silently empty a graph's hyperedge set
  • the skill flow now passes curated community labels to to_json, so graph.json ships with community_name on nodes

Correctness release: a C# receiver-typing regression fix, direction-respecting shortest paths, and a set of hyperedge merge/load integrity fixes.

  • Fix: C# receiver typing no longer drops a true call when a same-named variable is declared untypeably elsewhere in the method (#2472, thanks @JensD-git). Receiver types are now tracked per lexical declaration scope and resolved by the call's position, so a typed static local-function parameter keeps resolving even when an out var reuses the name in the enclosing body. This fixes a regression from 0.9.32 (#2346). Cross-method independence (#2299) and field-conflict poisoning are unchanged; an out var receiver itself remains untyped.
  • Fix: graphify path (and the MCP shortest_path tool) now respect edge direction by default instead of running on an undirected view, so a returned path no longer traverses edges backwards (#2487, thanks @luliaz0601). Direction is recovered from the stored _src/_tgt markers. Pass --undirected (CLI) or undirected=true (MCP) to search ignoring direction; when no directed path exists the command says so instead of silently returning a reversed one.
  • Fix: semantic extraction no longer aborts at merge with a TypeError when a hyperedge carries dict-shaped members (#2486, thanks @adminwat). Members are normalized to ids (or dropped with a warning) so a malformed hyperedge can no longer destroy a completed extraction.
  • Fix: graphify merge-graphs no longer drops hyperedges (#2484, thanks @sortakool, and @oleksii-tumanov for the approach in #1691). Hyperedge member ids and ids are now relabeled with the per-repo prefix, both inputs' hyperedges are unioned instead of one clobbering the other, and they are written to both the top-level and nested slots.
  • Fix: build_from_json now reads hyperedges from both the top-level and nested graph slots, so label and re-cluster runs no longer silently empty a graph's hyperedge set (#2485, thanks @sortakool); a full validation wipeout is now reported loudly.
  • Fix: the skill flow now passes the curated community labels to to_json, so graph.json ships with community_name on nodes instead of dropping it (#2490, thanks @PapiScholz).
View originalPermalink
How v0.9.34 went

v0.9.33

Changed 1
  • graphify install now prints a one-time pointer to the hosted platform after the setup summary
Fixed 3
  • C# partial class merge no longer conflates same-named classes in different assemblies by keying on assembly in addition to namespace and name
  • graphify update no longer drops member-call and indirect_call edges from a changed file into an unchanged target during incremental rebuilds
  • graphify extract no longer silently substitutes an empty result when a worker crashes, instead triggering sequential fallback and retrying failed worker files

Data-integrity release: fixes a C# partial-class regression from 0.9.32, stops incremental rebuilds from dropping cross-file call edges, and stops extract from silently losing a file when a worker crashes.

  • Fix: the C# partial class merge (#2332) no longer conflates two same-named classes that live in different assemblies (#2411, thanks @JensD-git). The merge now keys on assembly (nearest ancestor directory containing a .csproj/.fsproj/.vbproj) in addition to namespace and name, so genuine partial halves within one project still merge while same-name types in separate projects stay distinct. A corpus with no project file keeps merging by namespace and name as before.
  • Fix: graphify update no longer drops member-call and indirect_call edges from a changed file into an unchanged target (#2437, #2438, thanks @aryanbonigala). Incremental re-resolution now sees the unchanged corpus (its nodes, contains/method edges, and the _callable markers, which now persist to graph.json like _origin), so cross-file calls survive an incremental rebuild while edges to a genuinely removed target are still evicted.
  • Fix: graphify extract no longer silently substitutes an empty result when a worker crashes (#2444, #2445, thanks @Baziar). A BrokenProcessPool now triggers the sequential fallback instead of being swallowed per future, a failed worker file is retried sequentially rather than merged as empty, and a whole-pass AST failure on a fresh build exits non-zero instead of writing a zero-node graph (use --allow-partial to opt into a best-effort partial graph).
  • graphify install now prints a one-time pointer to the hosted platform (early access is open free before the public v1 launch) after the setup summary.
View originalPermalink
How v0.9.33 went

v0.9.32

Changed 1
  • Dedup drops an O(nodes x components) scan in remap construction with identical results
Fixed 13
  • Incremental extraction and _rebuild_code no longer drop a file's other tier; merge is now tier-aware so AST re-extract replaces only AST nodes and keeps the semantic layer, and vice versa
  • The _origin provenance marker is backfilled on load so old graphs self-heal
  • Full-rebuild drop is scoped to sources actually regenerated
  • graphify update preserves the graph's directed flag instead of rebuilding it undirected
  • A numeric or otherwise non-string node id from an LLM fragment no longer aborts the build with a TypeError
  • graphify query renders every edge between visited nodes, not just the traversal-tree edges

Correctness release: a tier-aware merge that stops incremental/rebuild from dropping a file's other layer, plus a batch of language-resolution and CLI fixes.

  • Fix: incremental extraction and _rebuild_code no longer drop a file's other tier (#2333, #2334, #2336). Merge is now tier-aware (an AST re-extract replaces only AST nodes and keeps the semantic layer, and vice versa), the _origin provenance marker is backfilled on load so old graphs self-heal, and the full-rebuild drop is scoped to sources actually regenerated.
  • Fix: graphify update preserves the graph's directed flag instead of rebuilding it undirected (#2342, thanks @Rishet11).
  • Fix: a numeric or otherwise non-string node id from an LLM fragment no longer aborts the build with a TypeError (#2326, thanks @Rishet11).
  • Fix: graphify query renders every edge between visited nodes, not just the traversal-tree edges (#2323, thanks @Rishet11).
  • Fix: graphify update writes manifest.json to the target's graphify-out instead of the current working directory (#2316, thanks @Rishet11).
  • Fix: a real Python package named coverage/ is no longer silently dropped; the prune is gated on coverage-report artefacts (#2339, thanks @Manoj21k).
  • Fix: a custom GRAPHIFY_OUT name no longer prunes every same-named directory in the tree (#2273, thanks @oleksii-tumanov).
  • Fix: C# member calls resolve for receivers declared inline via out var, is, case, and switch-arm patterns (#2346, thanks @JensD-git), and members of a partial class split across files now attach to one merged class node (#2332).
  • Fix: members of a Kotlin anonymous object (object : Foo { ... }) are now extracted, with their implements and calls edges (#2347).
  • Fix: Ruby mixins declared with compact/nested syntax now resolve, and a qualified external mixin can no longer fabricate a phantom hub (#2302, thanks @FolatheDuckofDuckingburg). module Foo::Bar and module Foo; module Bar canonicalize to the same label; extend ActiveSupport::Concern no longer binds to a local module named Concern; a genuine in-corpus include Foo::Concern still resolves.
  • Perf: dedup drops an O(nodes x components) scan in remap construction (#2328, thanks @stupidprogrammer4), with identical results.
View originalPermalink
How v0.9.32 went

v0.9.31

Added 1
  • export const X = <scalar> now emits a graph node so a named import of a scalar export is no longer left dangling
Changed 1
  • MCP server is now dual-compatible with mcp SDK 1.x and 2.x, expanding from mcp<2 to mcp>=1,<3
Fixed 6
  • C# member calls on a typed receiver no longer drop true calls edges when the same local name is reused across methods
  • SQL cross-file table references now resolve to the real table node instead of leaking an absolute-path id and losing the foreign key
  • graphify path and explain no longer print reversed hops
  • Go predeclared functions no longer fabricate call edges to same-named user symbols
  • graphify explain refuses and lists candidates when a name matches symbols in more than one file instead of silently resolving to an arbitrary one
  • Antigravity install workflow no longer hardcodes the global skill path for a project-scoped install

Resolution-accuracy fixes, an MCP SDK compatibility widening, and community extractor fixes.

MCP

  • The MCP server is now dual-compatible with the mcp SDK 1.x and 2.x, lifting the mcp<2 cap from 0.9.30 to mcp>=1,<3 (#2308, thanks @NiSHoW). _build_server binds the same handlers via the 1.x decorator API or the 2.x on_* constructor callbacks at runtime, and adapts Tool.inputSchema, Resource.uri, and the dropped AnyUrl re-export. Verified with full stdio handshakes under mcp 1.29 and 2.0.

Resolution / graph accuracy

  • C# member calls on a typed receiver no longer drop true calls edges when the same local name is reused across methods (#2299, thanks @JensD-git). Receiver typing is now per-method (mirroring the Java resolver) instead of per-file, so an untypable var x = ... in one method can't delete a typed-parameter call edge in another.
  • SQL cross-file table references (e.g. a prisma migration referencing a table created in an earlier one) resolve to the real table node instead of leaking an absolute-path id and losing the foreign key (#2324). References mint a sourceless stub that collapses onto the real definition, and identifiers are normalized so "public"."users" matches public.users.
  • graphify path and explain no longer print reversed hops (#2309): they recover edge direction from the stored _src/_tgt markers instead of the persisted endpoint order.
  • export const X = <scalar> now emits a graph node, so a named import of a scalar export is no longer left dangling (#2266, thanks @oleksii-tumanov).
  • Go predeclared functions (make, len, append, ...) no longer fabricate call edges to same-named user symbols (#2313, thanks @PathGao); the filter is scoped to Go bare-identifier callees.
  • graphify explain refuses and lists candidates when a name matches symbols in more than one file, instead of silently resolving to an arbitrary one (#2233, thanks @0bLoM).

Install

  • The Antigravity install workflow no longer hardcodes the global skill path for a project-scoped install (#2319, thanks @MalikHaroonKhokhar).
View originalPermalink
How v0.9.31 went

v0.9.30

Changed 2
  • The Bedrock backend honors GRAPHIFY_API_TIMEOUT and GRAPHIFY_MAX_RETRIES instead of botocore's silent 60s default
  • The MCP server's multi-project graph-context cache is bounded (LRU, default 8 via GRAPHIFY_MAX_CONTEXTS) instead of growing unbounded
Fixed 5
  • Pin mcp below 2.0 so a fresh graphify[mcp] / graphify[all] install works again
  • TypeScript .tsx files no longer leak absolute-path / machine-slug ids into edge endpoints
  • A warm AST-cache hit after a corpus move or clone no longer replays node ids minted under the original root
  • The Bedrock backend reads the first text block of a Converse response instead of block 0, so reasoning-capable models no longer parse to zero nodes
  • merge-graphs preserves edge direction instead of rewiring import edges to the importing file

Fixes a fresh-install failure of the MCP server, plus node-id portability, cache, bedrock, and merge-graphs fixes.

Install

  • Pin mcp below 2.0 so a fresh graphifyy[mcp] / graphifyy[all] install works again (#2277, #2279, #2291). mcp 2.0.0 dropped the mcp.types.AnyUrl re-export and the Server decorator-registration API that graphify/serve.py uses, so an unpinned resolve broke graphify-mcp on every new install with an ImportError. The mcp and all extras now require mcp>=1,<2 (resolving to 1.29.0) and starlette>=1.3.1,<2. Porting to the mcp 2.x API is tracked in #2308.

Node-id portability

  • TypeScript .tsx files no longer leak absolute-path / machine-slug ids into edge endpoints (#2262). The symbol-resolution pass parsed .tsx with the plain TypeScript grammar; JSX misparsed, nested handlers floated to top level, and calls edges were emitted from an absolute-stem source with no node. .tsx now uses the TSX grammar, a calls edge is never emitted from an unowned source, and a general backstop canonicalizes any node-less absolute-derived endpoint.
  • A warm AST-cache hit after a corpus move or clone no longer replays node ids minted under the original root (#2257, thanks @Kaushik2003). Cached ids are stored root-relative and re-anchored on read.

Backends / graph ops

  • The Bedrock backend reads the first text block of a Converse response instead of block 0, so reasoning-capable models no longer parse to zero nodes (#2287, thanks @zhiyanliu).
  • The Bedrock backend honors GRAPHIFY_API_TIMEOUT and GRAPHIFY_MAX_RETRIES instead of botocore's silent 60s default (#2284, thanks @zhiyanliu).
  • merge-graphs preserves edge direction instead of rewiring import edges to the importing file (#2261, thanks @hopstreax).
  • The MCP server's multi-project graph-context cache is bounded (LRU, default 8 via GRAPHIFY_MAX_CONTEXTS) instead of growing unbounded (#2268, thanks @Kkartik14).
View originalPermalink
How v0.9.30 went

v0.9.29

Added 1
  • Scala self-type annotations now emit requires edges to the required traits
Changed 3
  • Committed .env.example, .env.sample, and .env.template templates are now indexed instead of dropped by the sensitive-file filter
  • Obsidian export no longer hides notes whose label starts with a dot, converting dot-prefixed labels appropriately
  • The post-commit hook write is now atomic with a protected-graph backup
Fixed 6
  • Absolute-path and machine-slug node ids are now canonicalized to root-relative node ids to ensure graph.json link endpoints are portable across machines and clones
  • The post-commit hook no longer overwrites an existing graph.json when it fails to read it; instead it refuses to write if the graph is over the size cap or unparseable
  • The post-commit hook launcher no longer displays a focus-stealing console window on Windows
  • False indirect_call edges from JavaScript and TypeScript closure arguments are removed by shadowing closure parameters with outer names
  • Rationale node labels are whitespace-normalized before the 80-character truncation to ensure clean labels and well-formed filenames
  • Real .env files and templates under a secrets directory remain excluded from indexing

Portability, hook-safety, and resolution-accuracy fixes.

Node-id portability

  • Absolute-path / machine-slug node ids no longer leak into edge endpoints (#2231, #2243). Module-top-level indirect_call sources, bash source/script-invocation targets, and other producers that minted an id from an absolute path are now canonicalized to the root-relative node id by a general backstop, so graph.json link endpoints are portable across machines and clones.

Hook safety

  • The post-commit hook no longer overwrites an existing graph.json it merely failed to read (#2251). If the existing graph is over the size cap or unparseable, the rebuild refuses to write (matching the CLI) instead of replacing it with a code-only extraction; the --no-cluster write is now atomic with a protected-graph backup.
  • The post-commit hook launcher no longer pops a focus-stealing console window on Windows (#2253, thanks @hopstreax).

Resolution / extraction

  • False indirect_call edges from JS/TS closure arguments are gone (#2241, thanks @Yyunozor): a closure parameter now shadows outer names, so rows.map(r => ...) no longer binds r to a corpus-wide callable of the same name.
  • Scala self-type annotations (self: A with B =>) emit requires edges to the required traits (#2052, thanks @Yyunozor).
  • Rationale node labels are whitespace-normalized before the 80-character truncation, so labels are clean and filenames aren't malformed (#2206, thanks @Yyunozor).

Filtering / export

  • Committed .env.example / .env.sample / .env.template templates are indexed instead of dropped by the sensitive-file filter, while real .env files (and templates under a secrets directory) stay excluded (#2184, thanks @SyedFahad7).
  • Obsidian export no longer hides notes whose label starts with a dot (.env -> dot-env; an all-dot label falls back to unnamed) (#2205, thanks @SyedFahad7).
View originalPermalink
How v0.9.29 went

v0.9.28

Fixed 7
  • Incremental runs no longer drop cross-file edges whose target file wasn't in the batch by canonicalizing Python relative imports and markdown reference links to root-relative node IDs
  • Incremental extraction no longer prunes alive files as deleted by comparing manifest paths with NFC normalization and performing liveness checks
  • --update on macOS no longer re-extracts everything when the corpus path or filename contains non-ASCII characters by normalizing manifest keys to NFC
  • Incremental rebuilds no longer reuse stale community labels and graphs that outgrow the visualization cap now keep an aggregated view instead of deleting graph.html
  • graphify benchmark, the graph merge-driver, and the call-flow HTML export no longer crash or fail on a --no-cluster graph.json that stores edges under edges rather than links
  • claude/gemini/codebuddy uninstall no longer delete the user-global skill when called with a project_dir, and graphify uninstall --project no longer deletes the global codebuddy skill
  • Swift computed and observed properties including var body, get/set, and willSet/didSet now emit graph nodes so SwiftUI views are no longer erased

Fixes for incremental extraction correctness, graph loading, uninstall scoping, macOS paths, and Swift extraction.

Incremental extraction

  • Incremental runs no longer drop cross-file edges whose target file wasn't in the batch (#2211, #2213). Python relative imports and markdown reference links emitted absolute-path target ids without the stamp the incremental canonicalization needs, so a re-extracted file's imports/references dangled or vanished. Both now canonicalize to the root-relative node.
  • Incremental extraction no longer prunes alive files as "deleted" (#2210). The stale-source check compared paths with a raw string test (no Unicode NFC) and pruned non-matches without a liveness check, so macOS NFD paths and legacy basename spellings lost their nodes. It now compares NFC on both sides and is fail-closed.
  • --update on macOS no longer re-extracts everything when the corpus path or a filename contains non-ASCII characters (#2221, thanks @SyedFahad7). Manifest keys are NFC-normalized.
  • Incremental rebuilds no longer reuse stale community labels, and a graph that outgrows the visualization cap now keeps an aggregated view instead of deleting graph.html (#2218, thanks @bobspryn).

Other fixes

  • graphify benchmark, the graph merge-driver, and the call-flow HTML export no longer crash or silently fail on a --no-cluster graph.json (#2212), which stores edges under edges rather than links.
  • claude/gemini/codebuddy uninstall no longer delete the user-global skill when called with a project_dir (#2215); this also fixes graphify uninstall --project deleting the global codebuddy skill.
  • Swift computed and observed properties (var body: some View { ... }, get/set, willSet/didSet) now emit graph nodes, so SwiftUI views are no longer erased (#2181, thanks @ozdemirsarman).
View originalPermalink
How v0.9.28 went

v0.9.27

Added 1
  • Python decorators now create graph edges so affected <decorator> finds what a decorator touches, with builtin/stdlib decorators excluded
Changed 7
  • stat-index.json is now stored with root-relative keys re-anchored on load and pruned of deleted-file entries so a moved or cloned corpus keeps cache hits
  • C# member calls on a typed receiver now resolve namespace-aware with base./this.field receivers and inherited-member lookup through the inherits chain
  • JavaScript/TypeScript non-relative imports resolve through jsconfig.json/tsconfig.json baseUrl and paths
  • Swift/Foundation/SwiftUI builtins are filtered from call resolution and god-node ranking
  • Bash calls into a source'd file resolve for extensionless scripts and bare source lib.sh
  • Bash source "${VAR}/lib.sh" resolves against the variable's real directory
  • The Codex PreToolUse hook is documented as an intentional no-op
Fixed 11
  • claude/gemini/codex/codebuddy install no longer overwrite settings/hooks files they cannot parse, now reading utf-8-sig, refusing to modify non-JSON-object files, and backing up to <name>.graphify-bak before any write
  • Incremental extract --no-cluster no longer overwrites the full graph with just changed files, instead merging the existing graph forward with replace/prune semantics and canonicalizing cross-file edge targets
  • Running the test suite no longer touches the developer's real ~/.claude/~/.gemini/~/.codebuddy/~/.copilot directories
  • JavaScript/TypeScript regex-rescued imports in Svelte/Astro/Vue no longer create ghost target nodes with absolute-path ids
  • Cross-file concept nodes with identical normalized labels now merge
  • Absolute source_file paths no longer break node identity and build_from_json folds legacy field aliases so alias-carrying nodes stop entering the graph invisible and unmergeable

A large maintenance release: install-safety fixes, node-identity/canonicalization fixes, cross-file resolution improvements, and a batch of community contributions.

Install and data safety

  • claude/gemini/codex/codebuddy install no longer overwrite a settings/hooks file they cannot parse (#2167). On any JSON parse error they used to fall back to an empty config and rewrite the whole file, destroying the user's settings (most often triggered by a UTF-8 BOM). They now read utf-8-sig, refuse to modify a non-JSON-object file, and back up to <name>.graphify-bak before any write.
  • Incremental extract --no-cluster no longer overwrites the full graph with just the changed files (#2169). It now merges the existing graph forward with the same replace/prune semantics as the clustered path and canonicalizes cross-file edge targets.
  • Running the test suite no longer touches the developer's real ~/.claude/~/.gemini/~/.codebuddy/~/.copilot (#2168).

Node identity and caching

  • stat-index.json is stored with root-relative keys (re-anchored on load, mirroring manifest.json) and pruned of deleted-file entries, so a moved or cloned corpus keeps its cache hits instead of re-extracting everything (#2199).
  • JavaScript/TypeScript regex-rescued imports (Svelte/Astro/Vue) no longer create ghost target nodes with absolute-path ids (#2195).
  • Cross-file concept nodes with identical normalized labels now merge, matching the behavior already applied to near-identical labels (#2182).
  • Absolute source_file paths (for example from a Windows scan) no longer break node identity (#2197), and build_from_json folds legacy field aliases (name/path/type/confidence_score) so alias-carrying nodes stop entering the graph invisible and unmergeable (#2194).

Resolution

  • C# member calls on a typed receiver now resolve namespace-aware, with base./this.field receivers, inherited-member lookup through the inherits chain, and shadow-poisoning so a local shadowing a field of a different type no longer produces a wrong edge (#1609, adapted from #1620 by @TheFedaikin).
  • Python decorators now create graph edges so affected <decorator> finds what a decorator touches, with builtin/stdlib decorators excluded (#2154, thanks @Rishet11).
  • JavaScript/TypeScript non-relative imports resolve through jsconfig.json/tsconfig.json baseUrl and paths (#2153, thanks @Rishet11).
  • Swift/Foundation/SwiftUI builtins are filtered from call resolution and god-node ranking (#2147, thanks @MasterFede5).
  • graphify query/explain no longer fabricate indirect_call edges to class definitions (#2137, thanks @Rishet11).
  • Bash calls into a sourced file resolve for extensionless scripts and bare source lib.sh (#2171), and source "${VAR}/lib.sh" resolves against the variable's real directory (#2172). Both thanks @Souptik96.
  • SQL routines with PL/pgSQL-only bodies that tree-sitter cannot parse are recovered by a raw-text scan, gated on a failed parse (#2180, thanks @Souptik96).

Windows and hooks

  • The git-hook interpreter allowlist accepts Windows backslash paths (#2126, thanks @Rishet11) and interpreter paths containing a space (#2166, thanks @Souptik96); the rebuild timeout is armed on Windows via a threading fallback (#2148, thanks @Rishet11); the extraction process pool is skipped when only one worker is available (#2173, thanks @Souptik96).
  • The Codex PreToolUse hook is documented as an intentional no-op (#2165, thanks @Souptik96).
View originalPermalink
How v0.9.27 went

v0.9.26

Fixed 6
  • graphify query/explain no longer fabricate indirect_call edges to class definitions; classes are now excluded from indirect_call in both the intra-file and cross-file paths while direct instantiation still emits its calls edge
  • The post-commit hook's interpreter allowlist now accepts Windows backslash paths by using a verified character class that admits backslashes while still rejecting shell metacharacters
  • The hook rebuild timeout is now armed on Windows using a threading.Timer fallback to terminate runaway rebuilds where SIGALRM is unavailable
  • Bash calls into functions defined in a sourced file now get calls edges; both source file and . file are handled
  • Bash source edges built from a variable path now resolve by stripping the leading expansion and resolving the literal suffix against the script's directory
  • Ignore files saved with a UTF-8 BOM are now honored by reading them as utf-8-sig instead of utf-8

Maintenance release. Correctness fixes across Python call-graph inference, the git hook (Windows), and bash source resolution.

Fixes

  • graphify query/explain no longer fabricate indirect_call edges to class definitions (#2137, thanks @Rishet11). Passing a class as a value (select(Model), db.get(Model, id), except (ErrorA, ErrorB), getattr(obj, "Name", 0)) produced a false inferred call edge; classes are now excluded from indirect_call in both the intra-file and cross-file paths, while direct instantiation still emits its calls edge.
  • The post-commit hook's interpreter allowlist now accepts Windows backslash paths (#2126, thanks @Rishet11). The shell case glob silently emptied any interpreter path containing a backslash, so the hook failed on Windows uv/venv installs. Both allowlist sites use a verified character class that admits backslashes while still rejecting shell metacharacters.
  • The hook rebuild timeout is now armed on Windows (#2148, thanks @Rishet11). It relied on signal.SIGALRM, which does not exist on Windows, so GRAPHIFY_REBUILD_TIMEOUT was a silent no-op and a hung rebuild ran unbounded. A threading.Timer fallback now terminates a runaway rebuild where SIGALRM is unavailable; the Unix path is unchanged.
  • Bash calls into functions defined in a sourced file now get calls edges (#2141, thanks @HerenderKumar). Resolution was gated on same-file definitions, so a call to a sourced-library function looked like an external command and produced no edge. Both source file and . file are handled; resolution is in-corpus and single-match only, so a genuine external command still fabricates nothing.
  • Bash source edges built from a variable path now resolve (#2079, thanks @HerenderKumar). source "${BENCH_DIR}/lib/x.sh" baked the unexpanded ${VAR} into a dead node id; the leading expansion is stripped and the literal suffix resolved against the script's directory, emitted as INFERRED only when it resolves to a real file. Calls into a ${VAR}-sourced library resolve too.
  • Ignore files saved with a UTF-8 BOM are now honored (#2163). .gitignore/.graphifyignore/info/exclude were read as utf-8, so a leading BOM stayed on the first line and silently dropped the first pattern. The ignore read sites now use utf-8-sig, matching git.
View originalPermalink
How v0.9.26 went

v0.9.25

Changed 1
  • graphify is now licensed under the Apache License, Version 2.0 instead of MIT
Removed 1
  • Remove .graphifyinclude handling as it had been non-functional since dot directories became indexed by default

Maintenance release: a license change to Apache 2.0 and a dead-code removal.

License

  • graphify is now licensed under the Apache License, Version 2.0 (previously MIT). Apache 2.0 adds an explicit patent grant, a patent-retaliation clause, and explicit inbound-contribution terms. Contributions made before the relicensing were submitted under MIT and remain available under those terms; the original MIT text is retained in LICENSE-MIT and referenced from NOTICE.

Removed

  • .graphifyinclude handling is gone (#2112). The file had been non-functional since dot directories became indexed by default (#873): its loader and matchers had no consumers, so detect parsed the file on every run and then discarded the result, making a .graphifyinclude a silent no-op. The dead loader and matchers are deleted, a leftover .graphifyinclude no longer appears in the unclassified list, and detect prints a one-time note when one is present at the scan root. To re-include ignored paths, use ! negation patterns in .graphifyignore.
View originalPermalink
How v0.9.25 went

v0.9.24

Added 2
  • get_neighbors and get_community (MCP) now honor a token_budget parameter (default 2000) to prevent flooding the client's context on god nodes or large communities
  • Truncation of results is announced at the top of the output when token_budget is exceeded
Changed 1
  • --code-only is now surfaced in the extract usage text and README
Fixed 8
  • The XAML code-behind .cs scan is now bounded and prunes noise directories, so it can no longer hang on large or shared parent directories
  • The sensitive-file filter no longer silently drops legitimate topic docs and source files, and now correctly identifies genuine secrets while preserving prose files whose slug merely ends in a keyword
  • Both graphify extract and the skill flow now name the skipped files instead of only printing a count
  • calls edges now resolve through aliased Python imports so downstream alias.func() calls are properly recorded
  • dedup preserves a node's attributes when two exact-ID records from the same source file collapse, retaining non-conflicting attributes deterministically
  • The claude-cli backend now reads the CLI's structured-output channel instead of free-form prose
  • graphify explain on a high-degree node groups the cut connections by file instead of showing a bare and N more message
  • graphify query and MCP query_graph no longer print calls edges backwards

Maintenance release. Correctness fixes across extraction, dedup, query rendering, and the sensitive-file filter, plus a hang fix in the .NET/XAML path.

Fixes

  • The XAML code-behind .cs scan is now bounded and prunes noise directories, so it can no longer hang. A standalone extraction on a .xaml under a large or shared parent (a temp dir, a big monorepo) could resolve the project root to a broad ancestor and recursively scan the whole tree. It now walks with node_modules/.venv/.git/dot-dir pruning and a directory cap: a real project scans fully, a runaway root degrades to a fast partial scan.
  • The sensitive-file filter no longer silently drops topic docs and real source (#2106). Prose files whose slug merely ends in a keyword (privacy-tokens.md) and real source like service_account.py were dropped with no trace, while some genuine secrets (.npmrc, .pypirc, .git-credentials, case variants) were missed. The filter is now stricter on real secrets and no longer loses legitimate files, and both graphify extract and the skill flow now name the skipped files instead of only a count.
  • calls edges now resolve through an aliased Python import (#2082, thanks @Yyunozor). from pkg import mod as alias recorded the import but dropped every downstream alias.func() call, so the callee looked like dead code.
  • dedup preserves a node's attributes when two exact-ID records from the same source file collapse (#2091, thanks @Synvoya). Non-conflicting attributes are retained deterministically, records from different files stay isolated, and a dropped record can never stamp a false origin onto the survivor.
  • The claude-cli backend now reads the CLI's structured-output channel instead of free-form prose (#2076, thanks @Yyunozor), which had parsed to zero nodes and bisected forever on newer Claude Code.
  • graphify explain on a high-degree node groups the cut connections by file instead of a bare ... and N more (#2009, thanks @Yyunozor).
  • graphify query and MCP query_graph no longer print calls edges backwards (#2080, thanks @Yyunozor); the renderer recovers the stored direction from the edge.

Features

  • get_neighbors and get_community (MCP) now honor a token_budget (default 2000) so one call on a god node or large community can't flood the client's context (#2069, thanks @ojmucianski). Truncation is announced at the top of the output.

Docs

  • --code-only is surfaced in the extract usage text and README (#2071, thanks @HerenderKumar).
  • README troubleshooting note for an older graphifyy in system site-packages shadowing uv run --with graphifyy (#1540, thanks @HerenderKumar).
View originalPermalink
How v0.9.24 went
v0.9.23

graphify 0.9.23

Fixed 9
  • caller and call sites listings now report the actual call-site line, not the caller function's definition line
  • query no longer silently drops the answer past its output budget and now ranks nodes by hop distance from the query seeds with the seed always rendered first
  • query output now displays a prominent notice at the top stating how many of how many nodes were shown and how to widen the budget
  • graphify uninstall no longer deletes a user-authored ### graphify section by matching the heading only when a line is exactly the marker
  • graphify path and the MCP shortest_path tool now return a deterministic route by traversing over a sorted graph instead of a hash-seeded undirected view
  • graphify path and the MCP shortest_path tool now label each hop with the edge's actual stored relation instead of an arbitrarily-collapsed parallel edge
  • Fix: caller / "call sites" listings now report the actual call-site line, not the caller function's definition line. explain, affected, and the MCP get_neighbors/query tools printed the caller node's source_location (its def line) for an incoming call, so a precise-looking citation sent users to the wrong line. The calls edge already carries the true call-site line; every caller/relation listing now reads the traversed edge's source_file:source_location, falling back to the node's own line only when the edge has none.
  • Fix: query no longer silently drops the answer past its output budget. Rendered nodes were ordered by degree (so a low-degree definition node ranked last and was cut first), the queried symbol was not guaranteed to appear, and the truncation marker sat only at the end so silence read as absence. Nodes are now ranked by hop distance from the query seeds (deterministically), the seed the question named is always rendered first and never truncated, and a prominent notice at the TOP states how many of how many nodes were shown and how to widen the budget. (A branch merge had also silently dropped the seed-first ordering the renderer already supported; it is rewired.)
  • Fix: graphify uninstall no longer deletes a user-authored ### graphify section (#2062). The uninstall strip used an unanchored ## graphify pattern that matched inside a user's H3 heading (and the "already installed" guard was a substring test), so hand-written content was destroyed. The heading is now matched only when a line is exactly the marker (mirroring the install-side #1688 hardening), across all six strip sites (CLAUDE.md, AGENTS.md, GEMINI.md, copilot-instructions.md, CODEBUDDY.md, and the H1 skill registration).
  • Fix: graphify path (and the MCP shortest_path tool) now return a deterministic route and label each hop with the edge's actual stored relation (#2074). The route was computed over a hash-seeded undirected view, so it varied run-to-run among equal-length paths; and the printed relation was read from an arbitrarily-collapsed parallel edge, so it could show calls on a pair that only carries references. The traversal is now over a sorted graph, and each hop shows the real relation(s), falling back to an honest related when none is stored.
  • Fix: cluster-only --no-label no longer permanently suppresses real community labels (#2073). It wrote Community N placeholders (plus a matching signature) into .graphify_labels.json, which the reuse path then treated as fresh forever. Placeholder-only runs no longer persist the sidecar, a stored placeholder is treated as absent so already-polluted graphs self-heal, and the watch/update rebuild got the same treatment.
  • Fix: build_from_json's ghost-duplicate merge now keys on the full source path, not the bare basename (#2068). Unrelated nodes from different files sharing a common basename (index.md, README.md) and a generic label were silently merged onto one survivor with their edges rewired, corrupting multi-corpus doc graphs. The legitimate AST/LLM same-file merge is preserved; cross-directory false merges are eliminated.
  • Fix: Python import resolution no longer depends on the scan root (#2072). A src-layout project (code under src/) lost most of its imports/imports_from edges when scanned from the repo root, because absolute imports resolved only against the scan root while file-node ids are scan-root-relative, so the dangling edges were silently dropped. Absolute imports now resolve against nested package roots (detected via the __init__.py chain), and import edges are repointed to the real file nodes, so the graph is identical whether scanned from the repo root or from src/.
View originalPermalink
How v0.9.23 went
v0.9.22

graphify 0.9.22

Fixed 8
  • A node whose source_file is a URL/virtual scheme (gdoc://, s3://, http://, etc.) is no longer evicted on the second graphify update by tolerantly matching the scheme instead of checking for a literal "://"
  • A real source directory named env/.env/*_env is no longer silently pruned as a false-positive Python virtualenv; the venv heuristic for those names is now gated on an actual marker (pyvenv.cfg, an activate script, lib/python*, or conda-meta/), and every pruned-as-noise directory is recorded in a pruned_noise_dirs bucket for traceability
  • Office (.docx/.xlsx) and Google-Workspace sidecars are now named from the scan-root-relative path instead of the absolute path, making the sidecar name stable across checkouts
  • serve.py's "graph.json is corrupted" recovery message is now reachable by ordering the JSONDecodeError exception clause before the broader ValueError clause
  • graphify god-nodes/god_nodes is now a real CLI subcommand
  • graphify extract --output DIR is now honored as an alias of --out
  • A nested class/object/trait now gets its contains edge from the enclosing type instead of the file node, creating a proper nested containment tree
  • File nodes that share a basename now get a directory-qualified label so explain/discovery can tell them apart; colliding file nodes are relabelled to the shortest unique path suffix while unique basenames stay bare
  • Fix: a node whose source_file is a URL/virtual scheme (gdoc://, s3://, http://, ...) is no longer evicted on the second graphify update (follow-up to #2051). The #2051 disk-absence sweep guarded such sources with a literal "://" check, but write-side path normalization collapses the double slash (gdoc://x becomes gdoc:/x), so the guard missed the node on the next run and dropped it into the disk-absence eviction branch. The scheme is now matched tolerantly (and a Windows drive letter like C:/ is not misread as remote).
  • Fix: a real source directory named env/.env/*_env is no longer silently pruned as a false-positive Python virtualenv (#2058). detect's directory-noise heuristic matched those names before .graphifyignore negation and with no trace in any output bucket, so codebases using them as source dirs (common in UVM/ASIC verification) lost large subtrees undetectably. The venv heuristic for those names is now gated on an actual marker (pyvenv.cfg, an activate script, lib/python*, or conda-meta/); venv/.venv/*_venv stay name-only, and every pruned-as-noise directory is now recorded in a pruned_noise_dirs bucket for traceability.
  • Fix: Office (.docx/.xlsx) and Google-Workspace sidecars are now named from the scan-root-relative path, not the absolute path (#2059). The absolute-path hash salted the sidecar name with the checkout location, so committing graphify-out/ (a supported workflow) produced a new duplicate .md per clone/worktree, each ingested as a distinct source document. The relative hash is stable across checkouts while still disambiguating same-stem files; the Google-Workspace sidecar path additionally gains the NFC normalization it was missing.
  • Fix: serve.py's "graph.json is corrupted" recovery message is now reachable (#2005, thanks @kimdzhekhon). json.JSONDecodeError subclasses ValueError, and the broad except (ValueError, FileNotFoundError) clause was ordered first, so a truncated graph printed the bare Expecting value... instead of the documented rebuild hint. The JSONDecodeError clause now comes first.
  • Fix: graphify god-nodes/god_nodes is now a real CLI subcommand, and graphify extract --output DIR is honored as an alias of --out (#2004). god_nodes was an analyzer, an MCP tool, and a documented capability but had no CLI command; --output was silently dropped on extract even though graphify tree documents it. (The affected/reverse-dep import-id mismatch from the same report is tracked separately.)
  • Fix: a nested class/object/trait now gets its contains edge from the enclosing type instead of the file node (#2040). Across ~19 languages the edge was hard-coded to source from the file, so the containment tree was flat (file -> Inner) rather than nested (file -> Outer -> Inner); it now sources from the enclosing type when present, with top-level types still contained by the file.
  • Fix: file nodes that share a basename now get a directory-qualified label so explain/discovery can tell them apart (#2032). In directory-per-entrypoint repos (Supabase Edge Functions, Next.js page.tsx, Rust mod.rs, Python __init__.py) dozens of files named e.g. index.ts collided under one label, breaking free-text discovery for exactly those files. Colliding file nodes are relabelled to the shortest unique path suffix (process-order/index.ts); unique basenames stay bare, and node ids/edges are unchanged.
View originalPermalink
How v0.9.22 went
v0.9.21

graphify 0.9.21

Fixed 10
  • graphify extract (headless, no --backend) now auto-detects Ollama from the standard OLLAMA_HOST env var, normalizing it the way the Ollama client does, while explicit OLLAMA_BASE_URL still takes precedence
  • Flag-less graphify extract now honors persisted --exclude patterns from .graphify_build.json instead of silently re-including them
  • Pathless --postgres extract for live database introspection no longer crashes before introspection by properly initializing the detection variable and guarding semantic-cache operations
  • Bare import aliases no longer collapse into file-level self-loops by dropping any imports/imports_from/re_exports edge whose endpoints are identical
  • Alias re-exports and imports through a barrel now resolve to the defining symbol by walking the barrel chain in a cycle-safe manner
  • Full graphify update now evicts semantic nodes whose non-code source file (doc, image, etc.) was deleted from disk
  • Fix: graphify extract (headless, no --backend) now auto-detects Ollama from the standard OLLAMA_HOST env var, not only graphify's OLLAMA_BASE_URL (#1940, thanks @kimdzhekhon). An explicit OLLAMA_BASE_URL still wins; OLLAMA_HOST is normalized the way the Ollama client does (adds http://, defaults the port to 11434 when omitted, appends the /v1 OpenAI-compat suffix). Wired through both the client base URL and backend auto-detection, so ollama stays opt-in and never shadows a configured paid key. (Supersedes the vendored-bulk #1966.)
  • Fix: a flag-less graphify extract now honors the persisted --exclude patterns instead of silently re-including them (#2027, thanks @oleksii-tumanov). Mirrors the #1971 gitignore-persistence fix: the exclude set is read from .graphify_build.json when --exclude is absent and applied to the scan, and a flag-less run no longer clobbers it; an explicit --exclude still replaces the persisted list.
  • Fix: a pathless --postgres extract (introspect a live DB with no filesystem corpus) no longer crashes before introspection (#2030, thanks @oleksii-tumanov). The no-path branch left detection unbound; it's now initialized, the semantic-cache prune and manifest writes are guarded so a DB-only run can't wipe the file cache or leave a poisoning manifest, and a stale manifest from a prior filesystem run is invalidated.
  • Fix: bare import aliases no longer collapse into file-level self-loops (#2037, thanks @Endogen). A single-file import whose bare stem matched the file's own legacy id was remapped onto the importing file, producing a source == target self-loop reported as a 1-file import cycle. build_from_json now drops any imports/imports_from/re_exports edge whose endpoints are identical; pre-existing self-loops self-heal on the next rebuild.
  • Fix: alias re-exports and imports through a barrel resolve to the defining symbol (#1983, thanks @HerenderKumar). import { X } from './barrel' where the barrel re-exports X from another module now points at X's real defining node, walking the barrel chain (bounded, cycle-safe). When a barrel re-exports the same local name from two different modules the name is ambiguous and left unresolved rather than guessed, so no wrong edge is fabricated. Builds on #1984.
  • Fix: a full graphify update now evicts semantic nodes whose non-code source file (a .txt/.pdf/.png with no AST extractor) was deleted from disk (#2051). The corpus sweep only checked files it could re-extract, so a deleted doc's or image's LLM-derived nodes survived indefinitely and were served as authoritative. Disk absence is now used as the deletion signal for such sources; remote and virtual sources (anything with a :// scheme) are left untouched.
  • Fix: an incremental rebuild whose change set names a file that exists but has no AST extractor (a doc, paper, image, or an excluded path) no longer treats it as a deletion (#2056). The change-set loop routed any present-but-untracked file to the deletion path, which both evicted its semantic nodes and disabled the shrink guard that would otherwise have caught the loss. Such files are now preserved; a genuine on-disk deletion is still evicted, and the shrink guard now falls through to its per-source accounting instead of being waved off wholesale by the mere presence of a deletion in the change set.
  • Fix: code-typed nodes that the semantic pass surfaces from within a document now count as that document's semantic layer (#2014). A doc represented only by code-typed nodes was not recognized as semantically backed, so a rebuild re-scanned it for headings and dropped those nodes. The doc is now correctly treated as semantic-backed and left alone.
  • Fix: the --update runbook no longer marks a semantic file as done when its extraction produced no output (#2015). Step 9 stamped the entire detected corpus into the manifest, so a doc, paper, or image whose chunk failed or was omitted was recorded as complete and never re-queued on the next update, losing its content permanently. The runbook now builds the manifest with the same stamping the library uses (only files that actually produced nodes, edges, or hyperedges are stamped; dispatched-but-empty files have their stale hash cleared so they are retried), across the Claude, Aider, and Devin skill bodies and the shared update reference.
  • Fix: build_merge (the --update runbook path) now prunes a deleted file's nodes, edges, and hyperedges regardless of whether their stored source_file is absolute or relative (#2012). When the caller passed no scan root, a node that had kept an absolute path slipped past the relative prune set and the deleted file's graph survived silently. Matching is now form-insensitive (raw, normalized-relative, then an absolute-identity fallback), a re-extracted file is still never pruned, and graphify extract records the scan root marker after every write so a later update relativizes paths correctly even under a custom --out.
View originalPermalink
How v0.9.21 went
v0.9.20

graphify 0.9.20

Fixed 4
  • The graphify-first search nudge now fires on Claude Code's Grep tool in addition to Bash by matching both tool types and recognizing the Grep tool's input shape
  • Hook commands now use forward slashes in the graphify executable path on Windows so Git Bash does not treat backslashes as escape characters and strip them
  • With --out, semantic-cache writes now anchor against the scan root and cache directory sits at the output root so check, save, checkpoint, and prune operations agree on cache location
  • Alias-based named re-exports no longer emit dangling absolute-path symbol targets by rewriting aliased re-export targets to canonical symbol nodes when unambiguous
  • Fix: the graphify-first search nudge now fires on Claude Code's dedicated Grep tool, not just Bash (#1986, thanks @mdshzb04). The installed PreToolUse hook only matched Bash, so a Grep tool call (whose tool_input is {pattern, path, glob, ...}, not {command}) slipped through and never got nudged toward graphify query. The matcher is now Bash|Grep and the search guard recognizes the Grep shape; it stays nudge-only (never the strict deny), and the uninstall filters + #1840 gating are unchanged.
  • Fix: installed hook commands now use forward slashes in the graphify exe path so Git Bash doesn't strip them (#1987, thanks @varuntej07). On Windows the resolved exe path had backslashes, which Git Bash (how Claude Code shells hooks) treats as escapes and drops, breaking the hook with "command not found". _resolve_graphify_exe now normalizes \ to / at the single choke point, covering every emitter (Claude/CodeBuddy PreToolUse, Gemini BeforeTool, Codex); quoting and the --strict suffix are preserved and POSIX is unaffected.
  • Fix: with --out, semantic-cache writes now anchor correctly so the cache round-trips (#1990, #1991, thanks @mdshzb04). The final semantic-cache save resolved a relative source_file against the output dir and wrote 0 entries, and per-chunk recovery checkpoints landed in the wrong directory (under the corpus instead of --out). Cache entries now key on the scan root (portable, matching #1989) while the cache directory sits at the output root, so check/save/checkpoint/prune all agree; composes with the #1989 salt-keying and #1939 prompt-fingerprint namespacing.
  • Fix: alias-based named re-exports no longer emit dangling absolute-path symbol targets (#1983, thanks @oleksii-tumanov). export { X as Y } from './mod' produced a re_exports edge whose symbol target was an absolute-path-prefixed id with no matching node — the symbol-level residual left by #1967 (imports-only) and #1976 (file-level). The aliased re-export target is now rewritten to the canonical symbol node when unambiguous; external re-exports and owned ids are left untouched, so no real edge is dropped.
View originalPermalink
How v0.9.20 went
View all

Discussion

If you publish graphify, you can claim this product by proving you administer its repository.