CHANGELOG.md
Rama v8 · réplica de lectura del repositorio original.
# Changelog
Full release notes with details on each version: [GitHub Releases](https://github.com/Graphify-Labs/graphify/releases)
## 0.9.106 (2026-11-10)
- Fix: **install** `requires-python` is capped to `>=3.10,<3.15`, so `uv tool install graphifyy` (and pip) on a fresh machine no longer selects Python 3.15 — for which `tree-sitter` has no wheel — and then fails with a source build that needs a C compiler (`No such file or directory: 'cc'`). uv now picks a wheel-having interpreter (3.14) by default, with no `--python` flag and no compiler needed. The Ubuntu/Debian README line that asked for `python3.12` (absent on Debian 12 and Ubuntu 22.04) is replaced with the uv installer or a plain `python3`/pipx line (#5134).
- Fix: **cpp** a plain class/struct forward declaration folds into its unique exactly-qualified definition when one file reaches the other through unconditional exact quoted includes, and a parameterless member declaration folds into its proven out-of-line definition by matching return type and qualifiers; overloads, templates, conditional declarations and unproven includes stay separate (#5117, thanks @xRedLeader).
- Fix: **cpp** a member call resolves through a scoped type alias (`using Alias = ns::Type;`) and through inherited fields/methods, and a forward-declared pointee type is completed at the use site; ambiguous, virtual or diamond-inherited receivers fail closed (#5118, thanks @xRedLeader).
- Fix: **cpp** an out-of-line template member keeps its proven primary-template owner, and calls inside a `#if`-guarded initializer or argument list are recovered from byte-preserving branch views (bounded, fail-closed on malformed/over-budget input); dependent receivers stay unresolved (#5115, thanks @xRedLeader).
- Fix: **python** a dotted subscript annotation (`mod.MyBox[Foo]`) counts its value once as the type instead of also as its own generic argument, so the false `generic_arg` reference no longer replaces the real `parameter_type`/`return_type` edge (#5113, thanks @rohit-jsfreaky).
- Fix: **ts/js** a call through an ESM namespace import (`import * as ns from './m'; ns.f()`), a CommonJS whole-module require (`const m = require('./m'); m.f()`), or a TS `import = require` resolves to the real declaration; fail-closed on an unknown module, a non-exported name, or an ambiguous barrel (#5114, #5129, thanks @mohitdixit02).
- Fix: **llm** a precomputed `FileSlice` input is preserved through `extract_corpus_parallel` instead of crashing or stamping a partial parent complete; a slice that covers only part of its file marks the parent partial for re-dispatch (#5106, #5128, thanks @aadilrizwan).
- Fix: **query** BFS walks levels in a fixed, hash-independent order, so a query's edge lines (and which survive the token budget) no longer vary with `PYTHONHASHSEED` (#5109, #5112, thanks @rohit-jsfreaky).
- Feat: **query** `--no-doc-demotion` (and the `demote_docs` rc key / MCP arg) disables the source-over-docs soft weight for a docs-first corpus; code corpora are unaffected by default (#5084, #5116, thanks @mohitdixit02).
- Perf: **resolution** `_collision_aware` indexes salted sibling ids once instead of rescanning all nodes per use, cutting resolution time on large collision-heavy corpora (graph output byte-identical) (#5108, #5111, thanks @rohit-jsfreaky).
- Fix: **manifest** a `pom.xml` using a prefixed, single-quoted or spaced `xmlns` keeps its Maven package and dependency nodes instead of losing them; a foreign namespace is not double-counted (#5126, thanks @sdivyanshu90).
- Fix: **merge-graphs** `graphify merge-graphs` reports how many external nodes were unified across repos instead of silently absorbing them; the merge result is unchanged (#5127, thanks @igorpugatschev).
- Fix: **diagnostics** `diagnose_extraction` no longer crashes on a malformed `nodes` container (null or scalar); the malformed extraction is still surfaced through the build error (#5125, thanks @sdivyanshu90).
- Fix: **html** the interactive graph no longer draws a hyperedge region over members hidden by a community toggle; a region vanishes once fewer than two of its members stay visible (#5122, thanks @Yi-111-a).
The AST cache schema is bumped to 43; warm caches re-run the affected Python and C++ extractions.
## 0.9.105 (2026-11-09)
- Fix: **watch** ignored directories (`node_modules`, `.git`, build output) are pruned from the Linux watch tree, so a large project no longer registers tens of thousands of wasted inotify watches and risks `ENOSPC`; the non-Linux recursive watcher is unchanged and still fires on every real change (#5074, #5091, thanks @Deepjyoti-Sarmah).
- Fix: **cpp** a member-call chain routed through class fields declared in other headers (`client.actions->Execute()`, with value `.`, raw-pointer `->` and single-arg `unique_ptr`/`shared_ptr` receivers) resolves to the field type's member across the include set, gated to a unique type and member and fail-closed otherwise; a typed local whose class is in another repo is parked for the cross-repo merge (#5098, thanks @xRedLeader).
- Fix: **markdown** a code span naming an uppercase `\`-namespace class (`\Foo\Bar`, `App\API`) or a type-qualified method call (`Type::member()`) is kept as a symbol mention instead of being dropped, while a non-call `Type::CONST`/`Type::class` stays dropped (#5093, thanks @Adityakk9031).
- Fix: **ts/js** a call through a destructured CommonJS barrel require (`const { Thing } = require('./barrel')` where the barrel re-exports `Thing`) resolves to the real declaration by following the barrel's exports, instead of dropping the edge (#5034, #5104, thanks @qq2514568380).
- Fix: **wiki** the orphan sweep folds slugs with `casefold` (not `lower`), so two article names that collide only under full Unicode case-folding (`Straße`/`STRASSE`, `Σ`/`ς`) each keep their own file instead of one being deleted as an orphan; a file written this run is never swept (#5087, #5102, thanks @nothariharan).
- Fix: **build** hyperedges are kept whole across an incremental `graphify update`: they are read from the nested graph slot a JSON-only writer uses, and after a deleted-source prune a hyperedge drops only its removed members (and itself only when none remain), instead of silently losing or leaving dangling hyperedges (#5081, #5103, thanks @nothariharan).
- Fix: **cache** a degraded (under-producing) semantic cache entry no longer pins the corpus: after the shrink guard sees a re-extraction come back smaller than the stored graph, the entry is stamped `partial` so the next run re-dispatches it instead of replaying the degraded result forever (#5083, #5101, thanks @nothariharan).
- Fix: **affected** `graphify affected <file>` seeds the file's nested member nodes (a method inside a class) transitively, not just one hop, so impact analysis no longer under-reports callers that bind to a nested member (#5094, thanks @Adityakk9031).
- Fix: **tree** `build_tree` derives the common directory root from file parents, so a single-file or single-directory tree is no longer self-nested under the file itself (#5092, thanks @Adityakk9031).
- Fix: **query** a label whose name is behind a schema or namespace qualifier (`public.sweep_widget_state()`, `fmt.Println`) is matched on its tail by the whole query and ranked above the per-token tiers, so it is no longer out-competed by single-token collision nodes and dropped from the seeds (#5080, #5095, thanks @sunhouy).
- Fix: **detect** an incremental staleness check hashes a file whose stamp lands within the clock-skew window on either side of its mtime, so a same-tick rewrite that does not move mtime is still caught instead of being served stale (#5090, thanks @sunhouy).
- Fix: **path** `graphify path A B` refuses an endpoint whose words appear nowhere in the graph (exit not-found) instead of returning a misleading route built from loosely-overlapping tokens (#5085, thanks @sunhouy).
- Fix: **csharp** a classic extension-method call (`x.Foo()` where `Foo` is declared `static ... Foo(this X x, ...)`) binds to the extension method's declaration through an additive pass that runs after nominal dispatch, gated to a visible namespace, a matching `this` type and a unique applicable method; an instance method of the same name wins, a non-extension static or an unimported extension gets no edge, and ambiguity fails closed (#4828, #4849, thanks @Deepjyoti-Sarmah).
- Fix: **markdown** a bare, unqualified free-function mention in prose (`token`, no `()` and no namespace) binds to a same-named function only when its defining file (or another symbol from it) is cited within the heading's section chain, instead of matching any same-named callable in the corpus; reference-style links (`[text][label]`) resolve at the use site (#4804, #4806, thanks @madanlalit).
- Fix: **export** the `to_json` shrink guard no longer counts semantic sources that were replaced on re-extract or pruned as deletions as "lost", so a legitimate `graphify update` is not refused; a genuine unexplained shrink is still refused (#5107, thanks @khkar).
- Fix: **build** a node whose `file_type` or `confidence` arrives as a structured value (list/dict) from a drifting LLM pass no longer crashes the validate/build membership checks: validation surfaces it as a schema error, and the build folds it to `concept`/`INFERRED` without fabricating a score (#5088, thanks @sdivyanshu90).
- Fix: **llm** a splittable document below the character cap whose model output truncates is promoted to a full-range slice and bisected (and retried) instead of being kept as a truncated partial result; a non-splittable or code/binary unit still fails closed (#5072, #5078, thanks @aadilrizwan).
- Feat: **llm** a custom OpenAI-compatible provider's `extra_headers` are forwarded to the client as `default_headers`, scoped to that provider and with any `Authorization` header dropped so it cannot override the configured credential (#3680, #4783, thanks @jaimemartinez).
- Perf: **extract** whole-graph intermediates (the parse memo on the one-shot CLI, build/cluster/export working lists) are freed as soon as they are consumed, cutting peak memory on a large corpus; graph output is byte-identical (#5063, thanks @TPAteeq).
- Fix: **skillgen** the claw/kiro/pi subagent prompt now tells the worker to write its chunk to `CHUNK_PATH`, so a disk-dispatch chunk is no longer returned inline and lost at the merge step (#5040, thanks @connhodendatabase).
- Fix: **deps** the pypdf floor is raised to 6.21.4 in the `pdf` and `all` extras to pick up its security fixes (#5015, thanks @yotamleo).
The AST cache schema is bumped to 42; warm caches re-run the affected C++, Markdown and C# extractions.
## 0.9.104 (2026-11-07)
- Security: **skill** the `/graphify` runbook no longer substitutes the scan path into Python code. Every generated code block (detect, AST and semantic extraction, build, diagnose, label, manifest, and `--update`) now reads the resolved scan root from the trusted `graphify-out/.graphify_root` sidecar that Step 1 writes through a quoted heredoc, instead of pasting the agent-supplied path into a string literal. A directory whose name carried a quote or `$(...)` could previously break out of `Path('INPUT_PATH')` and run as code; it is now read as inert data. Covers the assembled hosts and the Aider/Devin monoliths (#4762, thanks @danikdanik).
- Fix: **solidity** a call on a receiver of a contract or interface type (`IERC20 token; token.transfer()`, `IERC20(a).transfer()`, `token().balanceOf()`) binds by name and arity through that type's linearization to the type's own declaration, instead of producing no edge; gated to a single visible contract/interface and fail-closed on an unknown or ambiguous type (#5060, thanks @rohit-jsfreaky).
- Fix: **solidity** a call attached by a `using A for T` directive (`using SafeCast for uint256; x.toUint48()`) binds to the library or free function it attaches, matched by the receiver's type and an arity one greater than the call; fail-closed when the receiver type is unknown, the library is ambiguous, or two functions fit (#5062, thanks @rohit-jsfreaky).
- Fix: **solidity** members, free functions and types in one scope whose names differ only by a leading `_` or by case (`_grantRole` / `grantRole`) get separate node ids, so the `_` symbol no longer vanishes into its twin and its calls land on the right node (#5027, thanks @rohit-jsfreaky).
- Fix: **csharp** a constructor (instance, static or primary) is its own node owned by its type, with its body and `: base(...)`/`: this(...)` calls credited to it; `new X(...)`, `: base(...)` and `: this(...)` additively link the arity-correct overload, and two fitting overloads or an unresolved base give no constructor edge (#5054, thanks @rohit-jsfreaky).
- Fix: **csharp** calls inside a property or indexer accessor, an expression body, or an operator/conversion body are credited to that member instead of being lost; an indexer becomes a `this[]` node and an operator a `.operator +()` node (#5056, thanks @rohit-jsfreaky).
- Fix: **query** a same-label node that is the only match for a query term is kept as a seed instead of being dropped by the per-label cap, so every distinct term keeps at least one seed while generic-label flooding stays capped (#5027, thanks @Vaibhav2824).
- Fix: **watch** read-only events are kept out of the inotify mask via a change-only `event_filter`, so a large tree no longer drives watchdog into a self-sustaining queue-overflow loop; falls back with a warning on watchdog versions without `event_filter` support (#5033, thanks @Deepjyoti-Sarmah).
- Fix: **cache** the `\\?\` long-path spelling is kept out of cache identity, so the same file reached through `\\?\C:\...` and `C:\...` shares one entry; also fixes empty stat-index writes under UNC shares, a spurious "stat index switched" warning on Windows, and a junction/symlink alias wrongly sharing its target's digest (#5035, thanks @Bortlesboat).
- Fix: **cache** stat-index keys stay root-relative under a Windows long-path root, so a moved or cloned corpus keeps the portable-key fastpath (#5035, thanks @Vaibhav2824).
- Fix: **tests** the TypeScript unicode fixture is written as UTF-8 on every platform, so byte offsets match the extractor on Windows (cp1252) hosts (#5064, thanks @ayushcodes10).
- Fix: **tests** the hook-freshness source mock is normalized through `os_long_path`, so the read-error/stale branch is actually exercised on Windows (#5065, thanks @ayushcodes10).
- Fix: **tests** the watch pending-queue test compares `Path` objects so `/` and `\` separators compare equal on Windows (#5039, thanks @Yi-111-a).
- Fix: **extract** an ambiguous Python import's placeholder node id is hashed from checkout-independent data (module name, scan-root-relative source file, location) instead of the absolute path, so the same project at a different path or a moved cache gets the same ids (#5045, thanks @Fardin7798).
- Fix: **yaml** a template using CloudFormation intrinsic tags (`!Ref`, `!GetAtt`, `!Sub`) no longer fails to parse: a scoped permissive loader rebuilds each `!`-tagged node's shape without evaluating it, while `!!python/...` tags still raise and never execute (#5077, thanks @ayushcodes10).
- Fix: **watch** a `--watch` incremental update rebuilds the full corpus when `graph.json` is missing, instead of writing a partial graph from the manifest alone (#5016, #5021, thanks @azizur100389).
- Fix: **watch** a change to a JS/TS resolution input (`tsconfig.json`, `jsconfig.json`, a lockfile) triggers a full rebuild so importers pick up the new resolution, instead of leaving stale `imports_from` edges (#5017, #5022, thanks @azizur100389).
- Fix: **ingest** two concurrent `graphify add` captures for the same URL no longer clobber each other: the capture file is created exclusively (`O_EXCL`) and a collision retries a fresh name, with the payload fetched once (#5018, #5023, thanks @azizur100389).
- Fix: **reflect** a malformed entry in the learning overlay is skipped instead of crashing the whole load, and a legacy null-source entry is preserved as a source-less annotation (#5019, #5024, thanks @azizur100389).
- Fix: **global** a malformed repo entry in the global manifest is dropped on its own, keeping the other tracked repos, and only a non-object root or repos map is backed up and reset (#5020, #5025, thanks @azizur100389).
- Fix: **query** a graph freshly built on a subfolder is no longer flagged as a pre-#1504 build (which nagged a no-op `extract --force`): the legacy-id probe strips the `.graphify_root` offset before deriving file stems, and a genuine legacy graph is still caught (#2603, #5028, thanks @neumannpeter1231-lang).
- Fix: **tree** `graphify export tree` recognises the `src` node alias (and `file`/`path`/`filepath`) so a graph carrying those keys renders a populated tree instead of an empty one; a concept-only graph still reports empty (#5030, thanks @Adityakk9031).
- Fix: **cli** `graphify provider add` accepts `--pricing-input=VALUE`/`--pricing-output=VALUE` equals-syntax and rejects a non-numeric, non-finite or negative price with a clean error instead of a traceback or a poisoned cost estimate (#5031, thanks @Adityakk9031).
The AST cache schema is bumped to 40; warm caches re-run the affected Solidity and C# extractions. The Python and YAML fixes above need no bump (placeholder ids are resolved after the per-file cache, and previously-failing YAML was never cached).
## 0.9.103 (2026-11-05)
- Fix: **markdown** a code span written as a backslash namespace (`\Foo\Bar`) or a type-qualified member (`Type::member`) is recognised as a symbol mention and resolves to the real node when one exists, instead of being silently dropped; an absent chain mints nothing (#4610, #4695, thanks @viwe-monai).
- Fix: **detect** `extract` warns loudly when a large share of the corpus has no supported extractor (a 20-file floor and a 20% threshold), instead of only printing a bare unclassified count; a normal mixed project stays silent (#2942, #4646, thanks @ayushcodes10).
- Fix: **thread-safety** the ambient scan root is held in a `ContextVar` instead of a module global, so concurrent library/async callers extracting different roots no longer clobber each other's root; the single-threaded CLI is unchanged (#4602, #4694, thanks @WyattAu).
- Feat: **tree** `graphify export tree` caps how many children a directory lists (`--max-children`, default 200) while `total_count` still reports the full count, so a directory with thousands of entries no longer renders them all (#4766, #4768, thanks @oleksii-tumanov).
- Fix: **install** the Pi project skill is written to `.pi/skills`, where Pi reads it, instead of a `.pi/agent/skills` path it never looks at; uninstall is symmetric (#5011, #5012, thanks @Vivek1898).
- Internal: stage fake install bundles in a temp package root and fail loudly (not skip) on a wheel-build error, and serialise both wheel builders under a shared lock so concurrent runs on one checkout do not collide (#4603, #4664, thanks @wanjinhao1); characterize `_is_stale` across changed/unchanged/vanished sources (#4996, thanks @PratikDhanave); isolate the ambient venv/PATH in the `_detect_run` hook test (#4991, thanks @HhH666-ux).
- Fix: **solidity** a library call (`Lib.f()`, including an aliased import) and an `emit`/`revert`/`require` naming a declared event or error now link to the member they name instead of producing no edge; resolution is gated to a single visible type whose unique name/arity member matches, so a builtin `require` or an ambiguous qualifier is left unresolved (#5001, #5005, thanks @rohit-jsfreaky).
- Fix: **csharp** preprocessor directives and `#if` branches that derail tree-sitter are blanked before parsing (byte-length preserving, so line and column numbers do not move), so a directive no longer closes the enclosing class early and orphans later members or drops their calls; the first `#if` branch and distinct framework-fallback branches are kept (#5003, thanks @rohit-jsfreaky).
- Fix: **python** a bare builtin-named call (`raise NotImplementedError(...)`, `list(...)`) no longer binds to a same-named class in another module unless that caller imported the symbol; an imported homonym still resolves (#4989, #4999, thanks @Deepjyoti-Sarmah).
- Fix: **typescript** a type-position `import('mod')` expression (a parameter/alias/`typeof`/generic-constraint annotation) no longer emits a spurious `dynamic_import` runtime edge; a real runtime `import('mod')` in the same file still does (#4992, #4998, thanks @mathewgeejo).
- Fix: **resolution** a call inside a top-level `def _main()` is attributed to `_main`, not the public `main` it collides with after id normalization; the fix matches only the underscore-collision salt, so a class member's type reference is not misattributed to a sibling method (#4994, #4997, thanks @sunhouy).
- Fix: **yaml** a `<<` merge sequence gives the earlier anchor precedence for a duplicated key, matching the YAML merge spec and PyYAML, so the winning key carries its correct source line (#4995, thanks @PratikDhanave).
- Fix: **markdown** a dotted mention whose method name is a language builtin (`Repository.open`) is no longer dropped; only a bare builtin name is skipped, and no spurious mention is minted (#4990, thanks @PratikDhanave).
- Fix: **hooks** a Bash file read (`cat`, `head`, `tail`, `sed -n`, `nl`) of an in-project code file gets the same graph read-nudge a Read tool does (nudge-only, never a strict deny); writes, stdin, heredocs, docs and globs stay silent (#5010, thanks @rohit-jsfreaky).
- Fix: **hooks** the once-per-session nudge budget is restored after a Claude Code compaction (a `SessionStart` with `source=compact`), so orientation can re-fire once the earlier nudges have left the model's context (#5008, thanks @rohit-jsfreaky).
- Fix: **hooks** the toplevel-anchor logic keeps working when `CDPATH` is exported; an exported `CDPATH` was corrupting the relative `.git` capture and silently skipping the post-commit/checkout rebuild (#4977, #4988, thanks @HhH666-ux).
- Perf: **update** the watch reconcile pass resolves AST ownership once per file instead of re-reading each header once per node, with identical graph output (#4978, #4986, thanks @Deepjyoti-Sarmah).
- **AST cache schema bumped to 39** for the C# (#5003), Solidity (#5001, #5005), YAML (#4995) and Markdown (#4695) extractor fixes; warm caches re-run the affected extractions.
## 0.9.102 (2026-11-03)
- Perf: **update** `graphify update` re-extracts only the files whose content changed since the last build (derived from the manifest inside the lock), instead of re-running the whole pipeline over the corpus for a single edit; the resulting graph is identical to a full rebuild, including cross-file edges from unchanged files into a changed one, and deletions (#4740, #4800, #4801, thanks @Deepjyoti-Sarmah).
- Fix: **watch** a contended incremental rebuild request is recorded on its own queue marker instead of being dropped, so a derived-change request that loses the rebuild lock still runs on the next pass (#4800, #4802, thanks @Deepjyoti-Sarmah).
- Fix: **query** source nodes are ranked above tests, docs and config in query results (a soft weight, not exclusion, so a term matched only by a test still seeds it and a question mentioning "tests" restores full weight), and the answer is rendered file-first (#4982, thanks @rohit-jsfreaky).
- Feat: **query** `graphify query` reads project `query_budget` and `query_depth` defaults from `.graphifyrc` (explicit CLI flags still win), and gains a `--depth N` flag; a malformed setting warns and falls back to the built-in defaults (#1654, #4760, thanks @oleksii-tumanov).
- Fix: **install** `graphify uninstall` removes the global `# graphify` skill registration it wrote into `~/.claude/CLAUDE.md`, instead of leaving a dangling pointer to the deleted skill; a hand-written `# graphify` section or another tool's registration is preserved (#4975, thanks @madhusudanpatnaik).
- Fix: **js** a class `extends` through a CommonJS require binding (`const Base = require('./base'); class D extends Base {}`) resolves the `inherits` edge to the required file's class, matching the ESM import path; an external/unresolved require is left alone (#4688, #4700, thanks @Mpasha17).
- Fix: **skill** the `--update` runbook passes `partial_source_files` to the manifest write and clears a stale `.graphify_partial.txt` at the start of the run, so a doc whose chunk was only partly read is re-queued on the next update instead of being stamped done forever (#4807, #4814, #4816, thanks @gitmotion).
- Fix: **skill** Step 4 threads `force=IS_FORCE` into `to_json`, so a `--force` rebuild actually overwrites past the #479 shrink guard the runbook told the user to use `--force` for (#4777, #4805, thanks @pei711).
- Feat: **skill** for a hub/bridge question, the query workflow now tells the agent to grep the corpus for the hub's distinctive literals in edge-less files and report code that reimplements the hub instead of calling it (#4426, #4704, thanks @ayushcodes10).
- Fix: **hooks** a file whose mtime is newer than `graph.json` but whose content is unchanged no longer weakens the strict read gate; the hook checks the file's extraction hash against the build manifest before declaring it stale, so a checkout that writes `graph.json` first does not force a re-read (#4974, thanks @andrebrait).
- Fix: **hooks** a dedicated Grep call is held to the same once-per-session orientation gate as Read in strict mode; a directory Grep needs no index match, a single-file Grep is denied only when the graph indexes it, and external or graph-output targets stay silent without consuming the deny (#4973, thanks @andrebrait).
- Fix: **hooks** a search whose every literal target is outside the project (a Bash `grep`/`rg` on a `/tmp` or sibling path) emits no project hint, so the graph nudge only fires for in-project searches (#4961, #4979, thanks @Shubham-Padkonde).
- Perf: **load** `graph.json` rows are released as the graph is built (and repeated strings interned) so a load no longer holds both the parsed rows and the finished graph at peak, with identical output (#4960, thanks @ajbmachon).
- Perf: **resolve** symbol resolution holds one parsed file at a time instead of retaining every parsed file across the JS and Python passes, collapsing them into a single per-file loop with unchanged edges (#4958, thanks @ajbmachon).
- Perf: **extract** edge-dedup keys and per-file rows are released once consumed, cutting peak retention during extraction with identical output (#4959, thanks @ajbmachon).
- Fix: **typescript** a typed tagged template (`` sql<{...}[]>`...` ``) and an import-qualified generic annotation (`import("mod").T<X>`) no longer leave the file half-parsed; the recovery runs only on files that already fail to parse, so a type-only `import("mod").T` stops minting a bogus module node and a false `dynamic_import` edge (#4976, thanks @briascoi).
- Fix: **go** a same-file member call resolves through the receiver's type instead of a declaration-order-dependent same-named method; a bare package-function call no longer binds to a same-named method, and a package-level `var` fallback only applies to a genuinely unbound receiver (#4962, #4966, thanks @xiongjianxu).
- Fix: **go** a member call on a receiver typed from another package (`pkg.T`, the usual shape of a dependency injected across packages) binds only to the `T` declared at the path the file imports `pkg` from, so two packages that both declare `T` are disambiguated by import evidence; an out-of-corpus qualifier stays unresolved (#3368, #4853, thanks @nothariharan).
- Feat: **go** an interface method call additively dispatches to the interface's implicit implementers (method-set superset match, unexported methods scoped to the same directory, capped at 10 implementers and skipped past that), surfacing the concrete methods a call can reach while keeping the edge to the interface (#4934, thanks @nothariharan).
- Fix: **kotlin** a call to an overloaded method resolves (Java gives every overload one node id, which the resolver mis-counted as ambiguous), calls inside an `init {}` block are attributed to the class, and a bare call no longer binds to a class the caller cannot reach through its type chain (#4935, #4956, thanks @Deepjyoti-Sarmah).
- Fix: **python** a module-level singleton (`g = Service()` then `g.method()`) resolves its member calls through the instance's type, gated by an exact-spelling and lexical-shadow guard so rebinding, local shadowing or an ambiguous constructor leaves it unresolved (#4970, #4972, thanks @Shubham-Padkonde).
- Fix: **js** a call through a default-exported object's method (`import m from './x'; m.foo()`) resolves to the static method, bound only on a single unambiguous target and suppressed by local shadowing, getters, spread or computed keys (#4963, #4967, thanks @xiongjianxu).
- Fix: **solidity** an `import "../x/Y.sol"` resolves the `..` to the real file instead of a dangling id that kept the importer's own folder (#4984, #4985, thanks @rohit-jsfreaky).
- Fix: **build** the write guard no longer counts an intentional ghost fold (a #1145 same-file twin, #3344 doc-names-a-file, or #3705 method ghost folded into its surviving twin) as a dropped node on the `build_merge` path; a genuine drop whose survivor is also gone is still refused (#4955, #4957, thanks @Deepjyoti-Sarmah).
- Fix: **extract** paths skipped by ignore rules are reported in the extract summary (bounded, sorted, root-relative, with a VCS-aware hint), surfacing the already-collected ignore list without changing the graph (#4965, #4969, thanks @xiongjianxu).
- Perf: **resolution** the #3252 cross-file repoint indexes edges by source file instead of scanning every corpus edge per importing file, cutting resolution time on large graphs with no change to the resulting edges (#4980, #4981, thanks @jinsoo).
- Fix: **python** a bare builtin name (`NotImplementedError`, `TimeoutError`, `ConnectionError`, …) no longer links to a package class of the same name when the calling module never imports it; the edge is kept (and stays EXTRACTED) only when the caller imports that exact symbol, so a module-level import of some other name from the same file can no longer certify it (#4989).
- **AST cache schema bumped to 37** for the Go (#4966, #4853, #4934), Kotlin (#4956), Python (#4972) and Solidity (#4985) extractor fixes; warm caches re-run the affected extractions.
## 0.9.101 (2026-11-02)
- Fix: **watch** AST-only updates preserve cross-file semantic evidence whose node id collides with a code definition. Semantic nodes and their document-owned links use the existing citing-file namespace, keeping both the code definition and semantic provenance in `graph.json`, including with `--no-cluster`. Fresh AST facts remain authoritative for same-source ids; full-build deduplication is unchanged.
- Fix: **python** a bare call inside a method (`do_thing()`) no longer wrongly self-binds to a same-named method on the class; it resolves to the imported or module-level function it actually names, and a bare call with no such target emits no edge (#4933, thanks @nothariharan).
- Fix: **python** imports resolve across PEP 420 namespace packages split over multiple `src/` roots (no `__init__.py`), while a module ambiguously provided by two roots stays unresolved and loose/stdlib-named modules are left alone (#4929, thanks @nothariharan).
- Fix: **go** a method value reached through a receiver field (`m.UseMiddleware(hs.handler.Middleware)`) is linked as an `indirect_call`, extending the method-value-as-argument handling to field chains rooted at the method's own receiver (#1566, #4931, thanks @nothariharan).
- Fix: **go** a package-level `var` initialiser references the types it names (`var s = wire.NewSet(new(T), ...)`, composite literals, `new(T)`), the type-reference complement to crediting its calls to the file, so a wire-set file no longer extracts as no symbols (#2360, #4932, thanks @nothariharan).
- Fix: **rust** a module-qualified call (`crate::b::helper()`, `super::m::f()`) resolves to the function it names through qualifier-verified cross-file resolution, the dominant idiomatic call form that previously produced no edge; the resolver keeps the call inside the caller's crate and will not bind to a local name, preserving the historical false-positive guards (#2135, #4944, thanks @nothariharan).
- Fix: **rust** a call written inside an expression macro's arguments (`println!("{}", f())`, `vec![g()]`) is linked instead of being lost, gated to an allowlist of expression macros so `macro_rules!` bodies and custom DSLs are left alone, and a normally parsed call still wins the dedupe (#4945, thanks @nothariharan).
- Fix: **llm** a sliced file is cached only once every slice has succeeded, and a held sliced file is assembled in chunk order rather than completion order, so a partial failure no longer leaves a truncated or out-of-order file in the semantic cache; failed slices are stamped for retry (#4943, #4947, thanks @nothariharan).
- Fix: **rebuild** a graph built with `--code-only` stays code-only across hook, `update` and watch rebuilds; the scope is recorded in `.graphify_build.json`, honoured by the rebuild path and the no-change skip key, and a later flagless extract that actually ingests docs clears the scope (#4923, #4939, thanks @Deepjyoti-Sarmah).
- Fix: **watch** a rebuild no longer waits forever when `msvcrt.locking` is unsupported on the filesystem; an unexpected errno is treated as lock-unsupported and the rebuild proceeds unlocked with a warning, matching the existing no-`fcntl`/no-`msvcrt` fallback, while genuine contention still retries (#4924, #4926, thanks @AndresDiagoM).
- Fix: **watch** a full reconcile evicts source records for a linked git worktree that the current ignore rules exclude, including sources owned only through an edge or hyperedge, so a worktree copy's nodes no longer linger after it is excluded; an intentionally scanned linked root and live unexplained sources are preserved (#4919, thanks @azizur100389).
- Fix: **cache** cache keys keep their written case so `README.md` and `readme.md` no longer collide on a case-sensitive filesystem, and hashed and word-count files are opened through `os_long_path` so a path folded by `normcase` can no longer raise `IsADirectoryError`; the digest salt is no longer lowercased, which also closes a latent same-digest collision on POSIX (#4925, #4937, thanks @Deepjyoti-Sarmah).
- Fix: **build** the write guard no longer refuses a healthy graph when entity dedup folds a saved node into a surviving twin; the dedup merge map is carried on the built graph and threaded into both the missing-node check and the net-count check, which now counts N saved ids folded into one survivor as the expected N-1 shrink, while a genuine loss (survivor also absent) is still refused (#4940, #4952, thanks @Deepjyoti-Sarmah).
- Fix: **build** independently sourced semantic file evidence survives a fold into an AST file-self node; a mention carrying its own provenance (`rationale`, `source_url`, `author`, `contributor`) is kept distinct instead of collapsing by label alone, so two docs describing the same file no longer lose one explanation. Ordinary `INFERRED` mention ghosts with only `metadata`/`captured_at` still fold, so graphs do not accumulate duplicates (#4896, #4927, thanks @Ethan-Ethan-Ethan).
- Fix: **build** the file-stem alias index only rebinds an edge onto a migrated node when the edge's file and the alias target share a language family, so a reference in one language (for example Dart) no longer binds onto an unrelated same-stemmed file in another (for example Java); same-language and provenance-less aliases resolve exactly as before (#4949, thanks @ayushcodes10).
- Fix: **cpp** a type-alias reference (`using Foo = Bar;` then `Foo x;`) binds by exact spelling, so it no longer loses to a same-normalized function of the same name, and a salted alias node keeps its own `contains` edge (#4951, thanks @madhusudanpatnaik).
- Fix: **cpp** a member call `x.m()` binds by the receiver's type, now typed from function parameters and class fields and not only local variables (`void render(Base* b){ b->draw(); }` resolves to `Base::draw`), the dominant idiom of passing state by reference; an untyped or ambiguous receiver keeps its in-file binding rather than guessing (#3215, #4946, thanks @nothariharan).
- Fix: **cpp** an out-of-line or split-header definition attaches to the unique owner whose class file is in its include set, carried by a `_cpp_qualified` identity marker, instead of being file-contained or cross-binding to an unrelated same-named function in another file; an ambiguous or namespace-colliding owner fails closed with no edge (#4916, #4920, thanks @azizur100389).
- Fix: **cpp** a local callable value (a lambda or `std::function` assigned to a variable) is kept out of global call resolution by byte-range binding lifetimes, so it no longer mints a spurious `INFERRED` call to a same-named global; a later legitimate call to a real global of that name in the same function is preserved (#4917, #4921, thanks @azizur100389).
- Internal: cover deletion-only semantic manifest preservation so an undispatched semantic stamp survives a deletion-only or failed incremental change (#4922, thanks @azizur100389).
- **AST cache schema bumped to 36** for the Rust call-resolution fixes (#4944, #4945), the case-preserving cache keys (#4937), and the C++ extractor fixes (#4951, #4946, #4920, #4921); warm caches re-run the affected extractions.
## 0.9.100 (2026-11-01)
- Fix: **ids** a type and a function whose names differ only in case (`Foo` the class, `foo` the function) get distinct node ids instead of colliding (which dropped one node and minted a bogus self-loop) (#4904, #4908, thanks @bercedev).
- Fix: **php** a `Scope::MEMBER` access in a class-level initializer (a const value, a property default, or a parameter/promoted-constructor default) emits a `references_constant` edge; `self::`/`static::`/`parent::` are skipped and unknown scopes mint no stub (#4648, #4895, thanks @Jitendarmahara).
- Fix: **hooks** `git rev-parse` output is decoded as UTF-8, so a linked worktree under a non-ASCII path resolves correctly on Windows instead of mojibake (#4903, thanks @LukeTheoJohnson).
- Fix: **analyze** on a code-only graph the isolated-node question reason says "possible missing edges" rather than referencing documentation gaps that cannot apply (#4887, thanks @oleksii-tumanov).
- Perf: **resolve** reference edges are indexed by source once, so JS/TS import resolution no longer re-walks every edge per import (output unchanged) (#4909, #4913, thanks @Deepjyoti-Sarmah).
- Docs: **skill** the skill now says when a graph is worth building, across all generated hosts, while preserving the "if the user invoked /graphify, do it as asked" invariant (#4829, #4906, thanks @TanmayShah29).
- Fix: **deps** the `video` extra caps PyAV below 19 (`av>=11,<19`), because PyAV 19 removed the `metadata_errors` argument that faster-whisper passes unconditionally when opening audio; without the cap a fresh install on Python 3.12+ failed to transcribe (#4842, thanks @SyedFahad7).
- Fix: **r** `setClass`/`setRefClass` S4/R5 inheritance declared with `contains=` now emits an inherits edge (previously only R6Class's `inherit=` was recognised) (#4902, thanks @ayushcodes10).
- Fix: **elixir** a bare function call resolves to a function in the caller's own module before other modules, instead of binding to the last-walked same-named definition (#4900, thanks @ayushcodes10).
- Fix: **dart** a mixin `on A, B` clause resolves every superclass constraint, not just the first (#4899, thanks @ayushcodes10).
- Fix: **fortran** a derived type's `extends(base)` emits an inherits edge, bringing Fortran in line with the other OOP extractors (#4897, thanks @ayushcodes10).
- Fix: **scala** an `extension (x: T) def m` is scoped to its receiver type `T` (a `method` edge to `T`) instead of leaking to file scope (#2044, #4856, thanks @divakantpandey).
- Fix: **terraform** a node carries its original scope so ids stay stable when a cached project directory is moved, instead of re-deriving the scope from the new path (#4858, #4861, thanks @yUm1k4).
- Fix: **extract (JVM)** Java and Kotlin imports, `extends`/`implements` and member calls resolve across files and across the two languages when a written FQN names exactly one sourced type (#4874, #4879, thanks @Deepjyoti-Sarmah).
- Fix: **export** a path/explain/wiki rendering lists every parallel relation between two nodes (via `edge_datas`), and a raw `--no-cluster` write marks the graph `multigraph` so parallel edges survive reload (#4794, thanks @ensell-yes).
- Fix: **export** a partial or failed backup is reported as no backup and retried on the next run, instead of being recorded as a false success (#3769, #4834, thanks @barlowa124).
- Fix: **affected** `graphify affected` (and `query --seed`) resolve the same `path::Symbol` and dotted reference forms `explain` accepts, reusing explain's resolver, and exit non-zero (3 ambiguous / 4 not-found) on a seed that does not resolve (#4792, #4797, thanks @dupeisen).
- Fix: **install** `graphify uninstall` removes every platform's user-scope skill (13 were left behind) and the kilo global command; and `graphify kilo uninstall` removes the kilo skill's `references/` sidecar (#4782, #4789, thanks @bercedev).
- Feat: **llm** the Claude backend caches its (stable, large) system prompt with `cache_control`, counting the cached tokens back into `input_tokens`, and `GRAPHIFY_CLAUDE_PROMPT_CACHE=0` disables it for gateways that reject `cache_control` (#4835, #4855, #4880, thanks @Jvxoq and @1akashkalita).
- Fix: **prompt** the node-id format description in the extraction prompt matches what `ids.py` actually produces (word characters kept, non-ASCII letters/digits preserved, ASCII lowercased) instead of the stale "`[a-z0-9_]` only" wording (#4432, #4799, thanks @dupeisen).
- Docs: **how-it-works** the supported-language count is updated (about 40: 33 tree-sitter grammars plus 7 built-in parsers) and Pass 3 is described as running the configured LLM backend, not Claude only (#4873, thanks @SyedFahad7).
## 0.9.99 (2026-10-30)
- Feat: **path** when several routes tie on length, `graphify path` and the MCP `shortest_path` tool report the most conservative one: among all shortest paths, the route whose weakest hop is weakest is chosen, and the summary notes it was the weakest of N equal-length routes. Previously an arbitrary equal-length route was returned, so the confidence could overstate how sure the connection is. This is the second half of #4881 (the first half, keeping a collapsed edge's weaker evidence in `also_seen`, shipped in 0.9.98 via #4891, thanks @SyedFahad7).
- Fix: **go** a call in a package-level `var` initialiser (including inside a func-literal closure, the common swappable-hook pattern) is credited to the file node instead of being dropped (#2360, #4867, thanks @nothariharan).
- Fix: **go** a method value passed as a function argument (`Wrap(s.Health)`) is linked as an `indirect_call`, bringing Go to parity with Python/JS for callback-by-method-value; conservatively guarded against field-access false positives (#1566, #4868, thanks @nothariharan).
- Fix: **go** a receiver's field whose struct is declared in a sibling file of the same package is typed, so its member calls resolve across the usual struct-here-methods-there Go layout, and the field types persist across incremental rebuilds (#3368, #4866, thanks @nothariharan).
- Fix: **python** `module.func` values listed in a dispatch table (`{"a": mod.func_a}`, including nested collections) resolve to the referenced functions as `indirect_call`s instead of being dropped; non-callable, class, and local-receiver values are left alone (#1566, #4773, thanks @nothariharan).
- Fix: **install** gemini gets its own global skill dir (`~/.gemini/skills/graphify`) on Windows instead of colliding with the `agents` platform's dir; a pre-existing `agents` skill is noted, not deleted (#2800, #4770, thanks @nothariharan).
- Fix: **install** the global OpenCode plugin is written to `~/.config/opencode` (co-located with the global skill) instead of the current directory, with install/uninstall/refresh kept symmetric and legacy locations still cleaned up (#3398, #4771, thanks @nothariharan).
- Docs: **skill** the "Follow these steps in order. Do not skip steps." instruction is reworded to "unless the fast path above applies," resolving the contradiction with the existing-graph fast path across all generated hosts (#4061, #4721, thanks @nothariharan).
- Feat: **serve** `path`, `explain` and `neighbors`/`get_neighbors` print an edge's stored `confidence_score` alongside its tag (`[INFERRED 0.75]`); EXTRACTED stays bare (1.0 by definition) and a missing or non-finite score keeps the bare tag. Composes with the weakest-hop path summary and the `also_seen` evidence tags (#4882, #4888, thanks @SyedFahad7).
## 0.9.98 (2026-10-30)
- Fix: **build** when duplicate same-relation edges collapse, the losing (weaker-confidence) copy's provenance is kept in an `also_seen` list on the surviving edge instead of being discarded, so the weaker evidence is not silently lost; the field is serialized to graph.json and is idempotent across rebuilds (#4881, #4891, thanks @SyedFahad7).
- Fix: **csharp** a C# method node is labelled with its owning type (`MyClass.Method`) instead of a bare `Method`, so same-named methods across types are distinguishable; node ids and edges are unchanged (display label only) (#4883, #4889, thanks @SyedFahad7).
- Feat: **path** `graphify path` and the MCP shortest-path output report the overall path confidence as the weakest hop (lowest confidence tier, then lowest score), with a summary line naming that hop (#4885, #4892, thanks @SyedFahad7).
- Fix: **path** an endpoint written `Type.Member` resolves to the member node, not the containing type, so `graphify path "Exporter.Run" "PdfReport.Build"` finds the real call route; exact names and file names still win first, and a `Type.Member` whose member does not exist fails closed (#4884, #4893, thanks @SyedFahad7).
- Fix: **extract** file ids and per-declaration ids are kept distinct, so a top-level declaration whose name matches its file (for example `class Js` in `a.js`) is no longer silently dropped on an id collision; cross-file references bind to the declared id (#4854, #4857, thanks @Deepjyoti-Sarmah).
- Fix: **dedup** when a cross-file document mention and an AST node collide on a defining id, the AST node keeps the id instead of being dropped, while same-file enrichment, extension twins and single-definer cases are left as-is (#4859, #4872, thanks @TanmayShah29; duplicate approach in #4871 by @ayushcodes10).
- Fix: **build** a two-file import cycle (a imports b, b imports a) is reported in the default undirected build, and the stamp is cleared correctly on incremental/watch rebuilds so a dropped import no longer leaves a stale cycle (#4839, #4841, thanks @Mpasha17; duplicate approach in #4852 by @legendteddy).
- Fix: **extract** a relative JS/TS import carrying a `?query` or `#fragment` suffix (`./mod.js?v=1`) resolves to the real module instead of a phantom node; Node `#subpath` imports are left untouched (#4860, #4876, thanks @Jitendarmahara; duplicate approach in #4869 by @mirzasnowy).
- Fix: **go** a receiver bound with `var x = T{}` or `var x = &T{}` (a composite literal) is typed as `T`, matching the `x := T{}` form, so its member calls resolve (#4843, thanks @ayushcodes10).
- Fix: **verilog** a SystemVerilog class constructor (`function new(...)`, which has no return type) gets its own node, `method` edge and parameter-type references (#4848, thanks @ayushcodes10).
- Fix: **powershell** `using module`/`using assembly` drops a real file extension (`Helpers.psm1` to `helpers`) but keeps a dotted module name with no extension intact (`Az.Accounts`) (#4847, thanks @ayushcodes10).
- Fix: **solidity** a free-function `using {a, b} for T` set emits `uses` edges for each named function, not just the single-library `using Lib for T` form (#4846, thanks @ayushcodes10).
- Fix: **commonlisp** a `defclass` whose superclass is defined later in the same file binds to that local node instead of minting a sourceless stub (#4845, thanks @ayushcodes10).
- Fix: **pascal** `inherited` and `inline` form component blocks (.lfm/.dfm) are recognised, so a later `object` block keeps its correct parent instead of being re-parented by a corrupted nesting stack (#4844, thanks @ayushcodes10).
- Fix: **serve** when two nodes tie in a lookup and differ only by case, the one spelled exactly as queried wins (the others stay reachable one tier down) instead of every spelling being refused as ambiguous (#4870, thanks @nothariharan).
- Fix: **export** the Cypher text export keys `MERGE` on the stable node id and sets the label separately, so re-importing after a label change updates the node instead of creating a duplicate (#4878, thanks @oleksii-tumanov).
- Fix: **pdf** the converted-text cache is keyed on the resolved source path, so two PDFs with the same relative name in different roots do not poison each other within one process (#4851, thanks @xunguangzlj-cloud).
## 0.9.97 (2026-10-27)
- Security: **rationale redaction** credential redaction for rationale labels moves to a single pre-cache choke point (`_redact_rationale_labels` inside `_safe_extract`), so no extractor can skip it, and it now covers more leak shapes: URL userinfo with an empty user or with `/`/`@` in the password, PEM private-key bodies, tokens glued to surrounding text by `_` (for example `ghp_...`), Slack `xoxe-` refresh tokens, short JWTs, and Common Lisp docstrings that previously built labels without redacting. Benign strings (hashes, `sk-` prose, URLs without userinfo) are left alone (#4647, thanks @0xkaushik-ai).
- Security: **deps** anyio is floored to `>=4.14.2` in the `mcp` extra, above CVE-2026-63374 (an IDNA 2003 vs 2008 hostname-validation bypass that let a MITM on a non-ASCII domain present a cert for a different ASCII host, CVSS 9.3). anyio is only a transitive dependency and the lockfile already resolves 4.15.1, so this is a floor to stop a future re-resolution from regressing.
- Fix: **markdown** ATX headings parse per CommonMark: a trailing run of `#` is not part of the heading text (`## Setup ##` is "Setup"), up to three spaces of indent are allowed (four is an indented code block), and a hash-only line like `## ##` mints no node (#4790, #4791, thanks @jaimemartinez).
- Fix: **markdown** fenced code blocks are tracked by CommonMark rules (backtick and tilde fences, info strings, matching close length), so a line that looks like a heading inside a fence no longer leaks as structure, and a heading after a `~~~` block that contains a ` ``` ` line is no longer lost (#4787, #4788, thanks @jaimemartinez).
- Fix: **yaml** structural extraction reads the composed node tree, so keys carry their written spelling and their own source line instead of all landing on line 1, and YAML 1.1 bare keys like `on`/`off`/`yes`/`no` are no longer folded to booleans (a workflow `on:` block is extracted, not turned into a `True` node) (#4784, #4785, thanks @jaimemartinez).
- Fix: **razor** a component's `@implements` interfaces are extracted as `implements` edges instead of being silently dropped (#4820, thanks @ayushcodes10).
- Fix: **terraform** an aliased provider block gets its own alias-qualified node (so two `provider "aws"` blocks no longer collapse into one and lose attributes), and a `provider = aws.west` reference resolves to it instead of dangling; a `local` literally named `provider` is no longer misread (#4819, thanks @ayushcodes10).
- Fix: **cobol** paragraphs that follow a nested `END PROGRAM` reattach to the enclosing program instead of sticking to the nested one, via a program stack (#4824, thanks @ayushcodes10).
- Fix: **blade** a `wire:click` directive binds the bare method name: dotted modifiers (`wire:click.prevent="submit"`) are matched, call arguments are trimmed from the name (`deleteItem(5)` binds `deleteItem`), and a `$`-prefixed magic action mints no fabricated edge (#4823, thanks @ayushcodes10).
- Fix: **pascal** a `record` is recognised as a type with its own methods in the regex fallback (the default path without the optional `pascal` extra), matching the tree-sitter path; `operator` members are extracted too (#4825, thanks @ayushcodes10).
- Fix: **vbnet** an `Operator` declaration is extracted as an `operator`-kind member of its class and its body is walked for calls, so a helper call inside an operator no longer vanishes (#4821, thanks @ayushcodes10).
- Fix: **fortran** a `call` resolves to the subroutine it actually names instead of a target salted with the calling file's name (which never resolved), using the extractor's own cross-file stub idiom (#4795, thanks @ayushcodes10).
- Fix: **cli** `graphify query --help`/`-h` (and now `graphify explain --help`/`-h`) with nothing else prints usage instead of running a literal query for the text "--help"; a longer question that merely contains "--help" as a substring still runs as a query (#4830, thanks @curl-man).
- Fix: **js** a nested (inner) function no longer leaks into the file-wide name map, so a call elsewhere in the file binds to the top-level function of that name rather than an inner one it cannot see; recursion, shadowing and sibling scopes resolve correctly (ports the Python scope scheme to JS/TS) (#4767, thanks @smngvlkz).
- Fix: **ocaml** every binding in an `and`-chained `module ... and ...` or `class ... and ...` is extracted, not just the first, so later bindings are no longer silently dropped; each carries its own source line (#4822, thanks @ayushcodes10).
- Fix: **verilog** a SystemVerilog class `task` method is modeled like a `function` one (its own node, `method` edge and parameter-type references), and a task-local variable no longer leaks as a false field reference on the class (#4774, thanks @ayushcodes10).
- Fix: **build** an edge with no `relation` is dropped instead of persisted, on both the build and the raw `--no-cluster` write paths, and alias folding (`relationship` to `relation`) runs before the check (#4810, thanks @legendteddy; reproducer also filed as #4826 by @sunhouy).
- Fix: **build** the shrink guard counts a saved document-mention node that folds into its AST file twin on reload as present, not lost, so a cluster-only write is no longer refused when nothing was actually lost (#4778, #4779, thanks @AndresDiagoM).
- Fix: **detect** a linked git worktree whose main checkout resolves inside the scan root is pruned, so a sibling worktree no longer gets indexed as a second full copy of the repo (#4683, #4833, thanks @barlowa124; duplicate work in #4685 by @AndresDiagoM).
- Fix: **resolution** an inline comment in a pnpm-workspace glob is stripped per YAML rules, and a glob that `Path.glob` rejects (a `**` component or an absolute pattern) is skipped with a warning instead of crashing the whole `extract` run (#4681, #4687, thanks @Deepjyoti-Sarmah).
- Fix: **report** a community hub `[[wikilink]]` is slugged through the same `export._obsidian_safe_stem` the export uses, so the link matches the file the export actually writes instead of dangling (#4696, thanks @ayushcodes10).
- Fix: **wiki** a community relabelled only by letter case keeps its article instead of having the sweep delete the file the run just wrote; on a case-sensitive filesystem the stale lower-case twin is unlinked rather than renamed over the fresh file (which would silently restore the old body) (#4508, #4798, thanks @dupeisen).
- Fix: **query** filler verbs `get`/`gets`/`got`/`getting` are dropped from query terms (like the existing `work`/`works`), so a framework hub labelled "Get" is not pulled in as a BFS seed by an exact-match bonus; node lookups for a literal `Get` still resolve (#4686, thanks @AndresDiagoM).
## 0.9.96 (2026-10-25)
- Security: **deps** the Pillow floor is raised to `>=13.0.0` on Python 3.11+ (staying at `>=12.3.0` on 3.10, which Pillow 13 no longer supports) and pypdf to `>=6.19.0`, and the lockfile is refreshed to patched versions of transitive dependencies (pyjwt, cryptography, certifi, urllib3, anyio, fsspec, soupsieve, virtualenv). This clears the bulk of the open Dependabot advisories; the Pillow image-parsing CVEs are only patched upstream for Python 3.11+, so a 3.10 install keeps the newest 3.10-compatible Pillow.
- Fix: **llm** a chunk that failed before returning a response no longer has its files reported as model omissions; the warning distinguishes a failed chunk from a genuine omission, and both are still re-dispatched (#3616, #4752, thanks @jaimemartinez; reported by @GREYGROUPJP).
- Fix: **watch** a Markdown file that `detect()` classifies as a paper (arXiv markers, an Abstract) is included in the watch quick-scan rebuild instead of contributing zero nodes; a paper that already has a semantic layer is still excluded from the AST quick-scan (#4750, #4751, thanks @jaimemartinez; reported by @yeochul-jeon).
- Fix: **prs** every unfinished CheckRun status (`WAITING`, `REQUESTED`, `PENDING`, ...) counts as pending CI, so a PR gated on a deployment-protection job is no longer read as ready (#4730, thanks @rudycelekli).
- Fix: **export** an Obsidian note lists every parallel relation between two nodes (for example both `imports` and `calls`) instead of only the first edge (#4731, thanks @rudycelekli).
- Fix: **build** a hyperedge whose members are given under the `members`/`node_ids` aliases has its numeric member ids coerced to strings like the canonical `nodes` list, so the hyperedge is no longer dropped (#4732, thanks @rudycelekli).
- Fix: **llm** a code node whose confidence is written in a different case (`extracted`/`Extracted`) is still evidence-checked before it folds to the `EXTRACTED` enum, so an un-verified node is not presented as solid (#4733, thanks @rudycelekli).
- Fix: **python** a member call binds to the method it actually spells rather than a case/underscore-folded twin (`run()` no longer resolves to `_run()`); a lone folded method still resolves, and a genuine collision fails closed (#4734, thanks @rudycelekli).
- Fix: **zig** a call no longer crosses between a method and a same-named free function; a bare call binds the free function and a receiver call binds the method (#4755, thanks @ayushcodes10).
- Fix: **dart** an unnamed `extension on T { }` declaration is detected and extracted (the name is optional), so its members attach to the extended type (#4741, thanks @ayushcodes10).
- Fix: **llm** the DeepSeek backend sends its output cap as `max_tokens` (which DeepSeek honours) rather than `max_completion_tokens` (which it ignores), so responses are no longer silently uncapped (#4739, #4753, thanks @Ethereal49; reported by @snapp-kenya).
- Fix: **extract** XAML codebehind (`*.xaml` to `*.xaml.cs`) resolves deterministically regardless of directory iteration order, so a graph built on a case-sensitive filesystem is stable (#4747, thanks @anishraja04).
- Fix: **extract** the AST cache hit rate is reported over the file total, not the uncached set, so a warm run no longer prints a misleading "100% uncached" line (#4740, #4746, thanks @Deepjyoti-Sarmah; reported by @lavaxun).
- Fix: **dedup** a node id legitimately shared by two files as a `module`/`namespace` no longer prints a spurious "minted by two different files" warning (#4756, thanks @idachev).
- Fix: **python** the member-call resolver binds only Python callers to Python targets, so a same-named class in another language is no longer mis-bound as a call (#4757, thanks @idachev).
- Fix: **cli** the PreToolUse hook reminders are worded as facts rather than imperatives, so the agent's tool-result stream no longer reads them as prompt injection and ignores them (#2202, #4764, thanks @smngvlkz).
- Fix: **llm** the Ollama context window is sized from an estimate that counts non-Latin characters as whole tokens and from the real system-prompt length, so a non-English or long prompt is no longer silently truncated (#4414, #4761, thanks @smngvlkz).
- Fix: **cli** `graphify extract` records each run's token usage to `graphify-out/cost.json` (the same ledger the skill writes), so a standalone extract refresh updates the cost history (#1769, #4765, thanks @smngvlkz).
- Fix: **ts** chained TypeScript decorators (`@(Link().toMany())`) are recovered as a decorator reference, and a lossy-parse warning names the real error region (#4711, #4715, thanks @Deepjyoti-Sarmah).
- Fix: **js** a `const X = require('pkg')` binding for an external package keeps its node (with the package as source) so `new X()` still resolves, while a require of a corpus-local file still folds to the required module (#4688, #4702, thanks @ayushcodes10).
- Fix: **detect** generated MSBuild `obj/` and `bin/` output is pruned only when build evidence is present (restore/runtime markers nearby), so a C# project scanned after a build no longer ingests generated sources while a real `src/bin` stays (#4742, thanks @Great-Peace).
- Fix: **watch** each watch rebuild is bounded by `GRAPHIFY_REBUILD_TIMEOUT` (default 600s, `0` disables), with a thread-Timer fallback where `SIGALRM` is unavailable or `watch()` runs off the main thread (a `SIGALRM` arm there raises `ValueError` and would tear the watcher down before the rebuild ran), so a hung rebuild no longer stalls the watcher (#2173, #4724, thanks @danieldutu).
- Fix: **robot** each duplicate test-case name gets its own node (salted by line), so two tests with the same name are kept distinct (#4697, thanks @ayushcodes10).
- Fix: **vbnet** external namespaces and types emit a sourceless stub carrying an empty `source_file` sentinel like other languages, instead of a node that fails validation (#4725, #4728, thanks @madanlalit; reported by @alopezradicle).
- Fix: **rust** a `use` is scoped to the crate it names, so `use serde_json::Value` no longer resolves to a same-named type in another crate; a named `pub use` re-export is followed and a manifest-less binding stays `INFERRED` (#4699, #4712, thanks @Deepjyoti-Sarmah).
- Fix: **python** a call through a field injected from a constructor parameter, or through a local typed by a method's return annotation, resolves to the target method (`INFERRED`); it requires a single unambiguous producer and class (#4726, thanks @keny11300).
- Fix: **dedup** an LLM-marked cross-document reference stub that collides with its real definition is namespaced instead of silently dropped, so neither node is lost (#4738, #4743, thanks @SinghAman21; reported by @snapp-kenya).
- Perf: **update** a `graphify update` with no relevant input change skips the detect/extract/build pipeline and leaves the outputs untouched, keyed on the scan file-set, config, `.graphifyrc`, extractor epoch, and an output-completeness stamp; a scanned file with no manifest row (including one whose extraction previously failed) still forces a full rebuild so a later fix is never skipped (#4740, #4748, thanks @Deepjyoti-Sarmah; reported by @lavaxun).
## 0.9.95 (2026-10-23)
Twenty-one community pull requests. A security pass on install: the skill-install writes go through randomly-named temp files instead of predictable ones, so a pre-planted symlink can no longer redirect a write. Extraction: Python, Go, C++ and Rust explicit type aliases (and Rust `union` items) are extracted as type nodes so references resolve instead of minting sourceless stubs, each C# method overload gets its own node bound by argument count, and a package.json dependency's manifest entry links to the node that references it. Plus a one-root-per-repo tree on merged graphs, extended-length cache paths on Windows, and binary media no longer counted as text. The AST cache schema is bumped, so the first build after upgrading re-extracts.
- Fix: **security** the skill install stages every write (version stamp, `SKILL.md`, references) through a randomly-named `mkstemp`/`mkdtemp` file created with `O_EXCL` and restrictive mode instead of a predictable `*.tmp` name, so a local attacker who pre-creates a symlink at that path can no longer redirect the write to clobber an arbitrary file; the final move stays atomic (#4513, thanks @Mahdifotohi).
- Fix: **python** an explicit module-level type alias (`X: TypeAlias = ...`, `type X = ...`, `NewType`) is extracted as a type node, so a field or parameter typed by the alias resolves to it instead of minting a sourceless stub; an implicit assignment, a local alias, or a re-export is left alone (#4578, thanks @madhusudanpatnaik).
- Fix: **go** a `type A = B` alias is extracted as a type node (tree-sitter parses it as a `type_alias`, not a `type_spec`), so references resolve to it; a re-export (`type Field = pkg.Field`) mints no node (#4579, thanks @madhusudanpatnaik).
- Fix: **cpp** a namespace-scope `using A = B;` or `typedef B A;` alias is extracted as a type node so references resolve; a member alias inside a class and a same-name re-export are excluded (#4580, thanks @madhusudanpatnaik).
- Fix: **rust** a `type` alias and a `union` item are extracted as type nodes (a union's fields reference their types like a struct), so references resolve instead of minting stubs; an impl-associated `type` is excluded (#4581, Refs #4285, thanks @madhusudanpatnaik; reported by @jonatasbcardio-boop).
- Fix: **csharp** each method overload gets its own node and source location instead of collapsing to one, and a call binds to the overload matching its argument count, across in-file, receiver-typed, bare-name cross-file, partial-class, and interface-dispatch resolution; a non-overloaded method keeps its id (#4584, #4591, thanks @Mpasha17; reported by @TerraVenil).
- Fix: **tree** `graphify export tree` on a merged multi-repo graph gives each repo its own root (keyed by the repo tag) instead of conflating same-path files from different repos; untagged nodes are not counted as a repo (#4524, #4525, thanks @AromalBiju1).
- Fix: **cache** AST cache entries are read and written through Windows extended-length (`\\?\`) paths, so a deep cache path over the 260-character limit no longer fails extraction with a `FileNotFoundError`; a no-op off Windows (#4542, thanks @omsenjalia).
- Fix: **detect** a binary media file (an image or audio/video) is no longer read and counted as text toward the corpus word total; an SVG stays counted as the XML text it is (#4548, thanks @kalkaran).
- Fix: **json_config** a `package.json` dependency's manifest entry links (`declares`) to the ref node that importing code resolves to, so the manifest entry and its importers share one node instead of being orphaned; the entry is relabelled `name@range` (#4569, #4572, thanks @Deepjyoti-Sarmah; reported by @BiSide).
- Feature: **yaml** `.yaml`/`.yml` files are now extracted structurally (a file node plus `contains` edges to keys) and classified as code rather than documents, so Helm charts, Kubernetes manifests, and CI pipelines contribute nodes to a code-only or `--update` run instead of nothing, matching how `.json` is handled. Multi-document streams (`---` separators) are walked per document; a data array, an OpenAPI/Swagger spec, a JSON-schema-in-YAML, and a flat data bundle are skipped so data-heavy YAML does not mint orphan nodes; parsing uses `safe_load` so a `!!python/...` tag never executes; output is bounded (1 MiB, 500 pairs, depth 6). The AST cache schema is bumped, so the first build after upgrading re-extracts (#2214, #4706, thanks @nothariharan; reported by @dchavezf).
- Fix: **extract** a bare call that resolves to a same-named symbol only because the caller imported that symbol's module (`from b import other`, then a call to a `_make_id` defined in both `a.py` and `b.py`) is no longer promoted to `EXTRACTED`; the edge is still emitted for connectivity but stays `INFERRED`, so a symbol that was never imported is not falsely certified. A real symbol import (`from a import _make_id`) still resolves `EXTRACTED` (#2945, #4707, thanks @nothariharan; reported by @TotalUnitManagement).
- Fix: **security** the SSRF guard blocks a host that resolves to a multicast address (`224.0.0.0/4`, `ff00::/8`), closing a gap alongside the existing private/reserved/loopback/link-local checks (#4708, thanks @Dextheking1).
- Fix: **security** the Obsidian export sanitizes a node's file-type and confidence before emitting them as frontmatter `tags`, so a crafted value cannot break out of the YAML list into a sibling key, matching how community tags are already sanitized (#4709, thanks @Dextheking1).
- Fix: **security** the Google Workspace shortcut sidecar escapes a file id or url through full YAML double-quoted-scalar escaping (tab, C0/DEL controls, and the Unicode line breaks U+2028/U+2029/U+0085), so a hostile shortcut file cannot inject frontmatter keys (#4710, thanks @Dextheking1).
- Fix: **affected** `graphify affected <path>` resolves a path whose file-node label was disambiguated by the shared-basename suffix pass (for example two `hooks.py` from different directories), reusing the canonical file-label predicate instead of a bare basename match (#4698, #4713, thanks @Deepjyoti-Sarmah; reported by @karsan8119).
- Fix: **build** a raw (`--no-cluster`) build resolves an import endpoint through the same old-stem alias index the clustered build uses, and mints an external stub only after resolution, so the raw graph no longer freezes an import onto a stub when the clustered graph resolves it (#4670, #4676, thanks @nothariharan; reported by @xiongjianxu).
- Fix: **cross-repo** an Objective-C class declared in one repo's header and implemented in another repo's `.m` binds a member call to the implementation, the cross-repo analog of the same-repo split-dir fix; two same-named classes that both define the selector stay ambiguous (#4690, #4691, thanks @xiongjianxu).
- Fix: **hooks** the rebuild hook runs from the checkout's top level (derived from the git common dir, falling back to `--show-toplevel`), so a stray `graphify-out/` in a subdirectory can no longer root the rebuild at the wrong place and walk an enormous tree (#3258, #4689, thanks @jaimemartinez; reported by @dsfaccini).
- Fix: **objc** `NS_CLOSED_ENUM` declarations are extracted as enums with their cases, the same as `NS_ENUM`/`NS_OPTIONS`; the AST cache schema is bumped so the first build after upgrading re-extracts (#4705, thanks @Jvxoq).
- Fix: **sql** a `CREATE TABLE` whose column default the SQL grammar cannot parse (for example MySQL/MariaDB `DEFAULT current_timestamp()`) is recovered from the ERROR node instead of being dropped, so the table and its foreign-key references still resolve; the AST cache schema is bumped (#4613, #4677, thanks @08raviyadav; reported by @AirRunner).
## 0.9.94 (2026-10-22)
Nineteen community pull requests. A security pass on ingestion: a fetched URL with no hostname, with a malformed or zero port, or with embedded `user:pass@` credentials is now rejected before the request leaves, on the first fetch and on every redirect, and a text capture no longer follows a dangling symlink out of the corpus. Extraction and resolution: a Maven POM contributes only its own dependencies, a JS/TS member call on an untyped receiver stops guessing a same-named method in the same file, a CommonJS `require` binding no longer shadows the class the required file exports, and Objective-C category node ids stay repo-relative instead of embedding the absolute checkout path. SQL gets its own pass: T-SQL bracketed names are unbracketed, a `GO` batch separator ends the statement, cross-file table stubs fold onto their real definition, and a procedure emits `reads_from`/`writes_to` edges. Plus a markdown bare method only links when its class is cited, the MCP node lookup refuses a substring near-miss instead of answering with an unrelated node, a cross-repo edge points at the call site, Objective-C calls resolve across a split header/impl, an incremental build stops re-queuing files an extractor declined by design, a quoted Codex hook command for install paths with spaces, an IPv6 bracketed-`Host` serve fix, and a complete, drift-guarded `--platform` list in `--help`. The AST cache schema is bumped, so the first build after upgrading re-extracts.
- Fix: **security** `validate_url` rejects a fetched URL that has no hostname (`http:///path`), a non-numeric, out-of-range, or zero port, or embedded `user:pass@` credentials, before the request is made and again on every redirect, so a malformed or credential-bearing URL is no longer fetched or persisted into the graph; a normal `@` in a path or query and a valid high port still pass (#4643, #4644, #4645, thanks @cellrishi-code).
- Fix: **manifest** a Maven `pom.xml` contributes only its own `<dependencies>` (including those under `<profiles>`), not the version pins under `<dependencyManagement>` or a build plugin's own `<dependencies>`, so a parent or BOM POM no longer emits dependency edges it only manages (#4596, thanks @jayhemnani9910).
- Fix: **js** a member call on an untyped receiver (`x.foo()` where `x` has no known type) is deferred to receiver-typed resolution instead of binding to a same-named method elsewhere in the file, so a spurious self-loop or cross-call edge is no longer emitted; `this.foo()`, a typed receiver, and a locally instantiated receiver still resolve (#4598, #4605, thanks @ayushcodes10; reported by @SaschaAndresGrimm).
- Fix: **js** a CommonJS `const X = require('./m')` binding no longer mints a module-scope node that shadows the class the required file exports, so `new X()` resolves to the required declaration; a real `const x = factory()` binding still keeps its node (#4621, #4634, thanks @lab1207; reported by @sub4biz).
- Fix: **extract** an Objective-C category's class and method node ids stay repo-relative instead of embedding the absolute checkout path, so a category file like `Foo+Cat.h` yields portable ids that match across machines and merge cleanly; two same-named categories in different directories stay distinct (#4666, #4642, thanks @madhusudanpatnaik; reported by @xiongjianxu).
- Fix: **codex** the generated Codex PreToolUse hook command quotes the graphify executable path, so an install under a path containing a space no longer builds a broken hook command, matching the existing Claude and Gemini hooks (#4662, thanks @supercell02).
- Fix: **serve** an IPv6 HTTP bind accepts its own bracketed `Host` header (`[::1]:port`), so a server bound to an IPv6 loopback no longer refuses every request with a 421 (#4656, thanks @zydo).
- Fix: **ingest** a text capture never writes through an existing entry: the free-path search uses `lstat`, so a name that is a dangling symlink is treated as occupied and the capture is diverted instead of following the link and writing outside the corpus (#4657, thanks @zydo).
- Fix: **cli** `graphify --help` lists every accepted `--platform` value (it had omitted amp, copilot, kilo, kimi, and antigravity-windows); the list is derived from the same source as the install usage so the two cannot drift, with a guard test (#4655, thanks @zydo).
- Fix: **sql** T-SQL bracketed identifiers (`[dbo].[Orders]`) are unbracketed so a name matches across styles, and a stored procedure's body emits `reads_from`/`writes_to` edges for the tables it reads and writes; aliases, variables, temp tables, strings, and comments never become targets (#4635, #4649, thanks @Deepjyoti-Sarmah; reported by @RuVl).
- Fix: **sql** a `GO` batch separator (a client command, not a terminator) ends the statement, so a statement after a `GO` with no trailing `;` parses on its own instead of merging into one malformed node; a `GO` inside a string, comment, or column name is left alone (#4651, #4652, thanks @Deepjyoti-Sarmah).
- Fix: **sql** a table referenced before its `CREATE TABLE` is seen folds onto the real definition instead of staying a separate stub, and a schema-qualified name binds correctly, while two genuinely different tables in different schemas (`dbo.Orders` vs `sales.Orders`) stay distinct and a SQL stub no longer binds to a same-named class in another language (#4653, #4654, thanks @Deepjyoti-Sarmah).
- Fix: **markdown** a bare method name in prose (for example `redis` where only `Cache.redis` exists) only links to the method when the owning class is cited in the same document, so a plain word is no longer turned into a false `references` edge; a qualified `Class.method` or `Class::method` mention still resolves (#4609, #4617, thanks @nothariharan; reported by @saad-siddique).
- Fix: **mcp** the `get_node` and `get_neighbors` tools, and the `explain` command, refuse a substring-only near-miss with candidate suggestions instead of returning an unrelated node as the answer; an exact or prefix match still resolves (#3917, #4618, thanks @nothariharan; reported by @Alexandre-Aktisea).
- Fix: **cross-repo** a cross-repo call edge points at the real call site rather than the caller's header declaration, so the edge's `source_file` and `source_location` name the line that makes the call (#4668, #4669, thanks @xiongjianxu).
- Fix: **extract** an incremental build no longer re-queues, on every run, a code file an extractor declined by design (for example a virtual-workspace manifest); the file is marked `ast_empty` and left alone, while a genuinely failed or transiently empty extraction is still retried (#2543, #4672, thanks @xiongjianxu).
- Fix: **objc** a member call resolves when a class is split across a header and an implementation in different directories, binding the selector to its single definition; two unrelated same-named classes that both define the selector stay ambiguous and get no edge (#4673, #4674, thanks @xiongjianxu).
## 0.9.93 (2026-10-20)
Twenty-two community pull requests. Extraction: a reference to a Kotlin type alias, C# delegate, Swift macro, Dart typedef, Python module function or Rust sibling-module `use` now resolves to its real declaration instead of minting a sourceless stub; Metal/CUDA GPU qualifiers no longer mint god nodes; `extern "C"` headers route to the C++ extractor; an extensionless script with a versioned-interpreter shebang is classified; and Obsidian vault-relative wikilinks resolve. Plus a credential-redaction pass on rationale labels, a node-confidence fold, a truncated-reply label decode, a `.graphifyrc` BOM fix, a GraphML multigraph export fix, two-repo file disambiguation in explain and path, a PDF-read warning, no skill writes on `--version`/`--help`, two PR-tooling fixes (slashed default branch, legacy CI statuses), and a skill partly-read-file recovery so a long file a subagent only skimmed is retried on the next `--update` instead of silently kept half-read. The AST cache schema is bumped, so the first build after upgrading re-extracts.
- Fix: **Kotlin** a `typealias` declaration is extracted, so a field or parameter typed as the alias resolves to it instead of minting a sourceless cross-file stub (#4553, #4557, thanks @rajatnagda45).
- Fix: **C#** a `delegate` declaration is extracted, so references resolve to it instead of a sourceless stub; built-in `Func<>`/`Action<>` stay external (#4552, #4556, thanks @rajatnagda45).
- Fix: **Swift** `macro` declarations are extracted and a freestanding `#macro(...)` call resolves to the declaration; an unknown or standard-library macro stays unresolved rather than minting a stub (#4551, #4555, thanks @rajatnagda45).
- Fix: **Rust** a `use` rooted at a sibling `mod` (for example `use item::Thing;`) now reaches its declaration, while an external `use serde::...` still does not hijack a same-named local type (#4564, #4567, thanks @Deepjyoti-Sarmah; reported by @almothafar-maf).
- Fix: **build** a node's drifted or legacy confidence is folded onto the enum the same way edges and hyperedges already are, so a node no longer reaches graph.json with an unnormalized confidence (#4529, #4531, thanks @rudycelekli; reported by @grazenN).
- Fix: **llm** community labels salvaged from a truncated reply are JSON-decoded, so an escaped label (`\"`, `\n`, `\uXXXX`, surrogate pairs) is no longer kept with literal backslash escapes (#4537, thanks @rudycelekli).
- Fix: **rcfile** `.graphifyrc` is read as utf-8-sig, so a file saved with a UTF-8 BOM no longer drops its first key (#4538, thanks @rudycelekli).
- Fix: **Dart** a `typedef` is extracted as a type alias on its own line, including the legacy `typedef R name(args)` form, instead of a bogus variable of type `typedef`; a field or parameter typed by the alias resolves to it rather than minting a sourceless `typedef` stub (#4582, thanks @madhusudanpatnaik).
- Fix: **Python** a `module.func()` call resolves to the module child named exactly `func`, even when a same-key sibling (for example a `FuncSchema` class beside a `func_schema` function) previously made the lookup ambiguous and dropped the edge; it never binds to a same-named child of an unrelated module (#4577, thanks @madhusudanpatnaik).
- Fix: **C++** Metal and CUDA GPU address/execution-space qualifiers (`kernel`, `device`, `constant`, `__global__`, `__device__`, ...) in a `.metal`/`.cu` file no longer mint sourceless god nodes; the kernel function still extracts and a `<<<...>>>` launch still resolves (#4550, #4554, thanks @rajatnagda45).
- Fix: **extract** a `.h` header containing `extern "C"` is routed to the C++ extractor, so a C-API header the C grammar rejects extracts its symbols instead of nothing (#4526, thanks @bekzodibragimov2004-design).
- Fix: **extract** credential-shaped text (API keys, tokens, Bearer tokens, private-key headers, `user:pass@` URLs) in a comment or docstring that becomes a rationale label is redacted before it reaches graph.json, the viz or exports; ordinary identifiers such as git SHAs and UUIDs are left intact (#4566, thanks @0xkaushik-ai).
- Fix: **export** GraphML export of a graph loaded as a MultiGraph (a graph with parallel edges, after #4500) no longer crashes with an empty output; parallel edges are written with distinct keys and round-trip through `read_graphml` (#4536, thanks @rudycelekli).
- Fix: **prs** the default branch is derived by stripping only the `refs/remotes/origin/` prefix, so a branch name that contains a slash (for example `release/2.x`) is kept whole and PRs against it are no longer all dropped as wrong-base (#4534, thanks @rudycelekli).
- Fix: **prs** CI classification now reads legacy commit-status `StatusContext` state in addition to check-run conclusions, so a PR whose only signal is an external status (Jenkins, CircleCI) is classified correctly, with a failing status taking precedence (#4533, thanks @rudycelekli).
- Fix: **detect** an extensionless script whose shebang names a versioned interpreter (`#!/usr/bin/env python3.11`, `#!/bin/bash5`, `ruby3.2`, ...) is now classified to its language instead of being skipped (#4535, thanks @rudycelekli).
- Fix: **detect** when a PDF cannot be read (corrupt, encrypted, or a missing optional dependency) graphify prints a one-time warning naming the file instead of silently returning empty text, so a failed read is distinguishable from a text-less PDF (#4527, thanks @rudycelekli; reported by @grazenN).
- Fix: **markdown** an Obsidian `[[wikilink]]` whose target begins with one of the scan root's own folder names (a vault-relative path) now resolves to the real page instead of a ghost node, restoring intra-vault connectivity (#4559, thanks @bercedev; reported by @CryptoRMac4).
- Fix: **explain / path** two same-path files from different repos in a merged graph are kept distinct: an ambiguous name names each repo and the retry hint points at the full node id, instead of conflating them as one file (#4512, thanks @Deepjyoti-Sarmah; reported by @kaka-in-home).
- Fix: **cli** `graphify --version` and `--help` no longer rewrite or back up an installed skill file; only a real command refreshes skills (the staleness warning still prints) (#4545, thanks @bercedev; reported by @EmanuelFaria).
- Fix: **query** a bare name that is the exact name of several nodes (for example a `.nonce()` method in two files) is refused with the candidates listed and the ambiguous exit code, matching `explain` and `path`; a keyword that only prefix- or substring-matches labels still runs a normal search (#4511, #4517, thanks @shivampal46011; reported by @kaka-in-home).
- Fix: **skill** a long file a subagent only partly read is no longer a silent, permanent loss. On the skill extraction path each chunk now self-reports the lines it covered; a file whose reported coverage falls short of its real length is written to `.graphify_partial.txt`, cached as partial (which the loader treats as a miss) and left unstamped in the manifest, so the next `--update` re-dispatches it and replace-on-re-extract swaps the half-read node set for a complete one. This mirrors the library path's truncated-reply recovery (#933, #1950); the already-extracted nodes are kept, and a file no subagent reported is not falsely flagged (#3987, #2225, thanks @nothariharan; reported by @Ryanm218).
## 0.9.92 (2026-10-16)
Twenty-three community pull requests: call-resolution and id-collision fixes across JS/TS, Python, Go, Rust and Solidity; graph round-trip, dedup and confidence-folding fixes; skill code-only-update and dispatch fixes; and LLM/Ollama and detection fixes. The AST cache schema is bumped, so the first build after upgrading re-extracts.
- Fix: **JS/TS** class members that differ only by `_`, `$` or `#` are kept as separate nodes instead of collapsing one onto another (#4475, #4479, thanks @Mpasha17).
- Fix: **TypeScript** a `this.field` receiver is typed from an assignment in any method (including `static` and accessors), not just the constructor (#4473, #4482, thanks @FolatheDuckofDuckingburg).
- Fix: **JS** a workspace package whose `exports` map points only into `dist/` now falls back to the `src/` source, so app-to-package edges resolve on a clean checkout (#4485, #4489, thanks @bercedev).
- Fix: **JS** a dangling barrel-named import edge is dropped once the import resolves through the barrel to its real definition (#4486, #4488, thanks @bercedev).
- Fix: **Python** module-level class ids that collide with a function or another class are salted apart, so neither declaration is dropped (#2810, #4467, thanks @hementhu).
- Feature: **Python** a member call on a pytest fixture parameter is typed from the fixture's returned class (#4332, #4333, thanks @rohit-jsfreaky).
- Fix: **Go / Rust** same-file declarations whose names differ only by case are kept as separate nodes, with a forward reference resolving to the real type (#4468, thanks @abdulsamie10).
- Feature: **Solidity** inherited calls, `super` calls and modifiers from base contracts now resolve through the contract's C3 linearization (#4379, #4384, thanks @rohit-jsfreaky).
- Fix: **MCP** parallel edges (two relations between the same pair) are preserved through the graph.json round-trip and surfaced in query output, instead of collapsing to one (#4500, #4503, thanks @rtmalikian).
- Fix: **dedup** a node with a source_file beats a sourceless namesake in collision rank, so the located node survives (#4492, #4496, thanks @ayushcodes10).
- Fix: **build** model-drifted string confidences (`"high"`, `"confirmed"`) fold to a valid value before validation and dedup, clearing the permanent "invalid confidence" warning (#4495, #4501, thanks @ayushcodes10).
- Fix: **extractors** the in-flight scan root is shared via the extractors base module instead of importing back into `graphify.extract`, restoring the one-way import direction (#3666, #3758, thanks @abhay-codes07).
- Fix: **detect** only a valid `.git` (a real repository or a `gitdir:` worktree/submodule file) is treated as a VCS root, so a stray `.git` in a home directory no longer leaks its `.gitignore` into unrelated scans (#4444, #4464, thanks @Mpasha17).
- Fix: **hooks** the post-merge hook now rebuilds after a real merge commit, and a new post-rewrite hook rebuilds after cherry-pick, rebase and `pull --rebase` (#4463, #4476; #4490, #4491, thanks @Deepjyoti-Sarmah).
- Fix: **skill** the code-only `--update` path now materializes the extraction (empty semantic + Part C) before the merge across all hosts, so the merge no longer fails or silently drops the fresh AST (#4447, #4313, thanks @vibhanshu-mishra; aider/devin: #4456, thanks @Deepjyoti-Sarmah).
- Fix: **skill** stray backticks inside a double-quoted `-c` block no longer let bash run a command (`graphify update`) mid-step (#4461, #4462, thanks @Deepjyoti-Sarmah).
- Fix: **skill** Pi dispatches its semantic subagents to the native `delegate` agent instead of the non-existent `general-purpose` agent (#4494, #4502, thanks @bercedev).
- Fix: **skill** the agent is told to push to the exact Neo4j/FalkorDB URI the user supplied rather than a localhost example (#4349, thanks @vibhanshu-mishra).
- Fix: **llm** the extraction prompt pins `source_file` to the dispatched file, so a document's entities are no longer attributed to a file it merely references (#3377, #2270, #4487, thanks @cornwe19).
- Feature: **llm** opt-in backend selection via `GRAPHIFY_BACKEND`, a disable switch, and an auto-detect that names the env var it chose (#4361, #4376, thanks @nothariharan).
- Fix: **deps** cap `tree-sitter-dm <0.26` so a fresh install cannot pull the ABI-incompatible 0.26 grammar (#4044, thanks @safishamsi).
## 0.9.91 (2026-10-14)
Eight community pull requests: a Windows MCP-stdio hang fix, Rust/JS/TS/extract call-resolution and id-collision fixes, an Ollama context-size fix, and a clustering label-retention fix. The AST cache schema is bumped, so the first build after upgrading re-extracts.
- Fix: **MCP** `graph_stats` no longer hangs under a stdio host on Windows (and no longer silently drops its built-at-commit staleness line): `_git_head` closes the git child's stdin so git does not block on the JSON-RPC pipe (#4440, #4441, thanks @bercedev).
- Fix: **Rust** a `use crate::…::Name` edge now lands on the declaration it imports rather than a bare name-folded target, so `explain`/`affected` resolve the type even when it is only constructed; a cross-crate `use std::…`/`use serde::…` still does not hijack a same-named local declaration (#4439, #4443, thanks @bercedev).
- Fix: **JS/TS** a file-scope callback's own parameters are now shadowed, so a bare parameter passed on as a call argument no longer mints phantom cross-bundle `indirect_call` edges to same-named functions elsewhere in the corpus (#4448, #4457, thanks @bercedev).
- Fix: **extract** a module symbol whose id spells a sibling file's id (a `const sql` beside `x.sql`) is kept on its own node instead of being merged onto the sibling's file node and dropped; the file stays findable by label (#4449, #4452, thanks @Deepjyoti-Sarmah).
- Fix: **TypeScript** a class field's receiver type is now scoped to its own class and bases, so `this.field.method()` resolves to the right class when two classes share a field name; a same-named local or parameter no longer borrows a field's type (#4294, #4297, thanks @rohit-jsfreaky).
- Fix: **Ollama** extraction and labeling requests now go to Ollama's native `/api/chat`, so the derived `num_ctx` actually reaches the server and large prompts are no longer silently truncated; a server without `/api/chat` (LM Studio, llama.cpp) falls back to the OpenAI-compatible endpoint (#4413, #4414, thanks @smngvlkz).
- Fix: **cluster** a community keeps its curated or LLM label across a rebuild when its membership still overlaps the previous community by a strict majority, instead of discarding the name on a single-node change or a community-count change (#2298, #4451, thanks @SrijanSriv).
- Test: **ingest** the skip-existing symlink test now compares the link target against what `readlink` returned, fixing a false failure on Windows where `readlink` returns an extended-length path (#4435, #4442, thanks @bercedev).
## 0.9.90 (2026-10-13)
Fourteen community pull requests: C# call-resolution and id-collision fixes, Python/Go/dedup correctness, a hub-label sidecar, a post-merge heal hook, and query/build fixes. The AST cache schema is bumped, so the first build after upgrading re-extracts.
- Fix: **C#** a nameless member-call receiver (`run.Sequence.Apply()`, `Make().Apply()`) no longer mis-binds to the caller's own same-named method; every C# member call defers and resolves only through a typed receiver (#4422, thanks @nixvideojuego-ui).
- Fix: **C#** `new X()` no longer binds to a method that shares the type's name, same-file or cross-file; a real same-named type still wins (#4428, thanks @nixvideojuego-ui).
- Feature: **C#** `foreach` loop variables are now typed from the collection's element type (arrays, known sequences, dictionary `.Keys`/`.Values`), so calls on the loop variable resolve; untypable iterables stay unresolved (#4423, thanks @nixvideojuego-ui).
- Fix: **C#** node ids now keep names that differ only by a leading/trailing `_` or by case apart (`Action`/`_Action`, `Load`/`load`, `Token`/`TOKEN`), in both ids and member-call resolution keys, instead of collapsing one onto the other (#4415, #4416, thanks @JensD-git).
- Fix: **C#** colliding ids are salted before the partial-class merge, so a partial half keeps its members and an unrelated node sharing a half's id is no longer deleted (#4411, #4412, thanks @JensD-git).
- Fix: **Go** a generic receiver type (`func (b *Box[T]) ...`) is normalized before building its id, so a generic type's methods attach to the type node instead of a separate `Box[T]` node (#4438, thanks @Goodkamalpandey).
- Fix: **Python** a module member call (`mod.func()`) now resolves when the caller is a method or nested function, by walking the enclosing scopes to the file node instead of reading only the immediate parent (#2447, #4437, thanks @SrijanSriv).
- Fix: an edge between two surviving nodes is no longer dropped just because its own `source_file` was not dispatched this run; it is kept as long as both endpoints are kept nodes (#2276, #4436, thanks @SrijanSriv).
- Fix: dedup keeps a dropped-folder document next to a top-level file that mints the same id (`protocol.md` beside `docs/protocol.md` and `docs/firmware/protocol.md`), path-qualifying the siblings instead of collapsing and losing one; a genuine reference still collapses onto the named file (#4281, #4410, thanks @Deepjyoti-Sarmah).
- Fix: **Jinja** templates (`.j2`, `.jinja`, `.jinja2`) are now classified as documents instead of being dropped from the graph (#3427, #3455, thanks @nikhilsaxena04).
- Fix: **build** source-less placeholder nodes orphaned by a re-extraction that drops their edges are now swept on a normal (non-prune) incremental build, not just under prune; the sweep removes only code placeholders and external stubs and spares disk-isolated, fresh-isolated, and hyperedge-member nodes (#4399, thanks @Mpasha17).
- Fix: **query** the fallback traversal now retains relationships among the visited nodes (seed-to-seed edges, cycles, cross-links, and parallel relations), via the induced subgraph, instead of printing only tree edges (#4314, #4315, thanks @vibhanshu-mishra).
- Feature: **watch** hub-fallback community labels now go to a `.graphify_labels.pending.json` sidecar, so the tracked `.graphify_labels.json` holds only curated (manual/LLM) names; a promoted name drops from the sidecar and an emptied sidecar is removed (#3334, #3551, thanks @ayushcodes10).
- Feature: **hooks** `graphify hook install` now also installs a `post-merge` hook that runs an unscoped background rebuild after a merge, healing a symbol the graph.json union merge driver resurrected on one side (which nothing else would re-extract), so the phantom node no longer persists (#2418, #3556, thanks @ayushcodes10).
## 0.9.89 (2026-10-13)
Sixteen community pull requests: cross-repo member-call resolution (Go, PHP, TypeScript, Kotlin, Objective-C), generic/template type-parameter cleanup (Kotlin, Scala, C++, Swift), an Objective-C enum extractor, interface-dispatch fan-out, a native Pi guard extension, and several resolution/ingest fixes. The AST cache schema is bumped, so the first build after upgrading re-extracts.
- Feature: **cross-repo member calls** now finish across a repository boundary for Go, PHP, TypeScript/JS, Kotlin and Objective-C: a call whose receiver type is declared in another repo is parked by name during the single-repo build and bound on `merge-graphs`/`global add` against a unique same-named exported type, fail-closed on ambiguity (#3152, #3388, #3393, #3385, #3387, #3389, #3391, #3395, thanks @xiongjianxu).
- Fix: **Go** a package's type whose methods span several files now folds into one node instead of fragmenting under `_disambiguate_colliding_node_ids`, so single-definition guards and receiver typing work (#3399, #3400, thanks @xiongjianxu).
- Fix: **Kotlin / Scala / C++ / Swift** generic and template type parameters (`class Box<T>`, `[T]`, `template<typename T>`, `struct Stack<Element>`) no longer mint sourceless stub god-nodes; a real same-named type is still referenced (#4406, #4407, #4408, #4409, thanks @rajatnagda45).
- Feature: **Objective-C** `NS_ENUM` and `NS_OPTIONS` declarations are now extracted as an enum type plus its members, with the macro forms validated and malformed macros rejected fail-closed (#4400, #4401, thanks @oleksii-tumanov).
- Feature: **dispatch** an interface member now links to every implementer, not just one, so a DI chain through a multi-implementer interface resolves; the fan-out is gated to genuine implementers with a correct method match (#3800, #4396, thanks @Goodkamalpandey).
- Feature: **Pi** a native Pi guard extension now runs `graphify hook-guard` before Pi's tool calls and also loads under Oh My Pi, so the hook-guard policy reaches Pi; install/uninstall delegates to `pi install`/`pi remove` and the guard fails open (#4378, thanks @andrebrait).
- Feature: **hook** a firing read/search nudge now shows the graph nodes behind the nudged call and tightens its wording, so agents act on it instead of treating it as noise; the graph read piggybacks on the already-bounded nudge and is injection-safe and size-capped (#4368, thanks @andrebrait).
- Fix: **resolution** a `.js` import now resolves to a `.tsx` source when no real `.js` file exists, matching TypeScript's NodeNext resolution; a real `.js` still wins (#3486, #4395, thanks @DevChiniwala).
- Fix: distinct downloaded binary captures (PDFs/images) whose URLs collide to one filename are now kept under numbered names instead of silently overwriting each other, matching the markdown path's de-dup (#4393, #4394, thanks @oleksii-tumanov).
## 0.9.88 (2026-10-12)
Twenty community pull requests: language-extraction and call-resolution fixes (Java, C/C++, Python, Swift, Go, PowerShell, C#, JS/EJS), dedup/shrink-guard hardening, and query/graph.html ergonomics. The AST cache schema is bumped, so the first build after upgrading re-extracts.
- Feature: **Java** a call on a chained, `new` or cast receiver (`factory.builder().size(3).build()`, `new Builder().size(1)`, `((Builder) o).build()`) now resolves through the declared return types on the method chain, binding only on a single unambiguous target (#4306, #4307, thanks @rohit-jsfreaky).
- Fix: **Java** a bare call or field receiver inherited from a supertype in another file now resolves to the nearest declaring supertype (class wins over an interface default; a tie binds nothing), instead of dangling (#4304, #4305, thanks @rohit-jsfreaky).
- Fix: **C/C++** a quoted cross-directory include (`#include "../lib/foo.h"`, STM32 `Core/Src` to `Core/Inc`) now resolves to the right header by a unique longest-shared-directory-prefix match, fail-closed on ties and never guessing for `./`, `../`, absolute or angle-bracket includes (#4354, #4392, thanks @hopstreax).
- Fix: **Python** a `@name.setter`/`.getter`/`.deleter` decorator no longer mints a sourceless stub node (#4380, #4385, thanks @rajatnagda45).
- Fix: **Python** a `Self` (PEP 673) type annotation (`-> Self`, `: Self`, `typing.Self`) no longer mints a sourceless `self` stub (#4381, #4386, thanks @rajatnagda45).
- Fix: **Swift** a `Self` type annotation no longer mints a sourceless `self` stub (#4382, #4387, thanks @rajatnagda45).
- Fix: **Go** generic type parameters (`[T any]`) in signatures, struct fields and interface methods no longer mint sourceless `t`/`r` stubs (#4383, #4388, thanks @rajatnagda45).
- Fix: **Python** a class type-hinted before it is defined (a forward reference) no longer mints a bare stub that collides with and swallows the file node (#4334, #4343, thanks @Mpasha17).
- Fix: **C#** extraction now recovers members declared after a verbatim interpolated string that ends in an escaped quote (`$@"a""{e}"""`), instead of silently dropping the rest of the file; the fix masks only the offending bytes and discards the rewrite unless it re-parses clean (#3376, #3612, thanks @rohit-jsfreaky).
- Fix: **PowerShell** dot-sourced includes with quoted or `$PSScriptRoot` paths now emit import edges, and script-scope commands (outside any function) are now collected as calls (#4356, #4369, thanks @FabioRueda).
- Fix: **JS/TS** a bare or scoped external import is now anchored to the package name rather than the path leaf, so `@scope-a/utils` and `@scope-b/utils` stay distinct and `lodash/fp/map` collapses onto one `lodash` node (#3084, #3092, thanks @rohit-jsfreaky).
- Feature: **EJS** (`.ejs`) templates now have an extractor: scriptlet functions/classes, require/import edges, dynamic imports, and `<%- include('partial') %>` composition edges, instead of a single top-level parse error (#3811, #4338, thanks @nothariharan).
- Fix: **serve** a dotted Python query (`pkg.mod.func`, `pkg.mod:func`) now resolves to the symbol by module path, fail-closed on a same-file ambiguity, without regressing bare-name lookups (#4366, #4367, thanks @Mpasha17).
- Fix: dedup now path-qualifies semantic ids that dropped the directory, so `a/x.md` and `b/x.md` (both minting a bare `x`) stay distinct instead of collapsing and losing one node; a reference to the bare id from a third file is left unresolved with a warning rather than guessed (#4281, #4362, thanks @Deepjyoti-Sarmah).
- Fix: a semantic node that dedup legitimately folded into its AST twin is now exempt from the shrink guard, so `graphify update` on a graph that had an LLM semantic ghost is no longer wrongly write-blocked; the exemption is scoped to a strict `_origin=="ast"` twin at the same path and label, so a genuine partial-build loss is still refused. LLM node/edge field aliases (`file`/`filepath`, `relationship`) are folded, with the canonical field always winning (#4389, #4391, thanks @AndresDiagoM).
- Fix: **install** `graphify install/uninstall` now removes only graphify's own hook entries (keyed on the `hook-guard`/`hook-check` command, not a loose "graphify" substring), keeping co-located user hooks and other commands in the same group, and routes uninstall through a backup (#4359, #4373, thanks @nothariharan).
- Fix: **install** CLAUDE.md, AGENTS.md and sibling files now keep their original line endings (CRLF or LF) across install and uninstall, instead of being normalized to the platform default (#4360, #4372, thanks @nothariharan).
- Fix: **wiki** index rows now link to their own article when two communities or god-nodes share a label, instead of both pointing at the first-registered slug and orphaning the second article (#3032, #3033, #3095, thanks @rohit-jsfreaky).
- Feature: **query** `--seed` starts the traversal at given nodes (ids, labels, callable names or paths) so an external ranker can choose the start set; unresolved seeds are reported and skipped, and an all-unresolved set fails closed rather than falling back to keyword seeds (#4293, #4289, thanks @devkancheti4-design).
- Feature: **graph.html** the sidebar now shows a hover tooltip with the full (truncated) legend/search/neighbor name and the legend node count; user text is written via `textContent`, preserving the embedded-JSON escaping (#4286, #4291, thanks @Hassannzidan).
## 0.9.87 (2026-10-12)
Eighteen community pull requests: language-extraction fixes (PHP, C#, Python, TypeScript, Julia), retrieval and watch hardening, and skill/benchmark correctness.
- Fix: **PHP** a `Class::method()` static call now resolves to the called method rather than binding to the class node, and no longer stalls at INFERRED confidence for a same-namespace call that needs no `use` import. `self`/`static`/`parent` and lowercase class receivers route through the PHP resolver, fail-closed on any ambiguous or off-corpus receiver (#3872, #3874, thanks @rikurunico).
- Fix: **C#** nested and generic `partial` types now merge by their full type path (`Outer\`1.Inner`), so a two-file nested partial is unified while `Foo` and `Foo<T>` stay distinct; a cross-file unqualified call binds to the caller's own merged type when it is the single own-type candidate, and a sourceless placeholder base is dropped when a real same-named base exists on the partial (#4363, #4364, thanks @JensD-git). (The AST cache schema is bumped, so the first build after upgrading re-extracts.)
- Feature: **Python** a read of a `@property` (`obj.prop`, including `x.prop += v`) now links to the property's getter, instead of being dropped as a bare attribute access (#4337, thanks @rohit-jsfreaky). (The AST cache schema is bumped, so the first build after upgrading re-extracts.)
- Fix: **TypeScript** a public call `c.name()` reaches a `#name`-labeled node only when the class declares a public `name`, and a public call keeps its edge when `foo` and `#foo` share one node (#4298, thanks @rohit-jsfreaky).
- Fix: **Julia** parametric and module-qualified functor (callable-struct) types are named by their type rather than the variable, and never fall back to the variable binding (#4351, thanks @ayarodionov).
- Fix: **C#** a `var` is typed from the called method's return type, so downstream member calls on it resolve (#4266, #4268, thanks @Mpasha17).
- Fix: `graphify affected <PR>` now scopes graph impact to the requested PR instead of the repository default branch, and PR default-branch detection is isolated under test (#3829, thanks @hopstreax).
- Fix: `update` refuses to drop a saved semantic node id when the node total grows (treating it as an incomplete re-extract), while still letting a removed import stub go; `export` keeps an integer node id that is still in the graph (#4115, thanks @SrijanSriv).
- Fix: the MCP `query_graph` seed order no longer lets a weak match on an early-alphabet term lead a specific term's exact match: the per-term guaranteed seeds (#1445) are ordered by match strength (#4357, #4358, thanks @devkancheti4-design).
- Fix: the update skill stamps `_origin` on the semantic tier so an AST-only `update` cannot delete semantic nodes it did not re-extract (#2843, #3110, thanks @abhay-codes07).
- Feature: a project can declare what an ambiguous extension (`.inc`, `.h`, TS-family) means via `language.<ext>=<language|.ext>` lines in `.graphifyrc`; the declaration flows through classification, extractor selection, imports, watch, and the AST cache, and default detection is byte-for-byte unchanged when absent (#2961, #3789, thanks @andrebrait and @abhay-codes07).
- Fix: the hook's soft read/search nudge is bounded per session instead of firing on every matching file (#3435, #3443, thanks @L4XB).
- Fix: the update skill prints a short summary of the incremental step and leaves the full result in `.graphify_incremental.json`, instead of dumping every file path into the agent context (#3952, thanks @mdshzb04).
- Fix: **skillgen** Step 3 Part A now passes `root=` to `extract()`, so adapting the snippet to a relocated cache (`--out`) no longer renames every code node and blinds the node-count shrink guard (#3727, #3955, thanks @jnrod03-rgb).
- Fix: **watch** a full-corpus rebuild is no longer silently dropped when the rebuild lock is busy; it is queued (superseding queued incremental paths) and drained after the in-flight rebuild, and the "changes queued" log only prints when something was actually queued (#4274, #4276, thanks @smngvlkz).
- Fix: the benchmark now counts the tokens of the answer `query_graph` actually returns (calling the real budgeted query path) instead of a separate unbudgeted reimplementation (#4347, #4348, thanks @rohit-jsfreaky).
- Feature: **graph.html** gains a light/dark/system theme toggle; the dark default renders byte-identically to before (#3894, #4300, thanks @Mpasha17).
- Fix: labeling now honors the Claude CLI model environment variable (`GRAPHIFY_CLAUDE_CLI_MODEL`), matching the extraction path, so labeling no longer silently falls back to the session default model (#3846, #3876, thanks @oleksii-tumanov).
## 0.9.86 (2026-10-11)
Eighteen community pull requests: language-extraction fixes, safety and determinism hardening, and CLI/query ergonomics.
- Safety: `graphify uninstall --purge` now refuses to delete the project (or home, or a filesystem root) when `GRAPHIFY_OUT` is empty or `.`, instead of silently removing the working tree (#4329, thanks @Hassannzidan).
- Fix: **Julia** now extracts return-typed, qualified (`Base.show`), operator (`Base.:+`) and functor (callable-struct) methods, and resolves broadcast (`f.(xs)`) and pipe (`x |> f`) calls (#4310, thanks @ayarodionov).
- Fix: **Rust** grouped `use` declarations (`use a::{b, c::d}`) now expand into one import binding per name, instead of a single edge to the common prefix (#4135, thanks @krishhgg). (The AST cache schema is bumped, so the first build after upgrading re-extracts.)
- Fix: **Scala 3** `class Foo derives Bar` now links the class to each derived typeclass (#2048, #4173, thanks @Mpasha17).
- Fix: **Codex** semantic chunks are no longer clobbered by the shared B3 merge (each chunk is written to its own file, matching the other hosts), so semantic nodes/edges survive (#4311, #4312, thanks @vibhanshu-mishra).
- Fix: `affected <Symbol>` now also walks callers of the supertype member that an overriding method overrides, so a change to an override surfaces users of the base contract (#4330, #4331, thanks @rohit-jsfreaky).
- Fix: dedup keeps combining marks in label normalization, so distinct Indic-script words no longer collapse to one node (#4317, #4318, thanks @Suraj370).
- Fix: dedup ranks a relative `source_file` against the scan root rather than the CWD, so building from a subdirectory no longer strands an extension-renamed node's edges on the survivor (#4281, #4326, thanks @Deepjyoti-Sarmah).
- Fix: the global (cross-repo) graph preserves stored edge direction on `global add`, so `path` and other direction-aware readers no longer follow reversed arcs (#4066, #4138, thanks @e4c5).
- Fix: graphify now respects git's global ignore file (`core.excludesFile`), with git-faithful precedence and `~`/XDG expansion (#4335, #4339, thanks @smngvlkz).
- Fix: null token counts from a backend are coerced to 0 across all backends, instead of crashing a chunk with a `TypeError` (#4325, #4340, thanks @rtmalikian).
- Feature: built-in Ollama requests accept a sampling seed for reproducible extraction (#4168, thanks @oleksii-tumanov).
- Fix: `explain` refuses a name carried by several nodes of one file (ambiguous within a file) instead of silently picking one (#4320, thanks @alexwrite).
- Fix: lookups now return distinct exit codes — 0 for an answer, 3 for an ambiguous name, 4 for a missing one — so scripts can tell a miss from a hit (#4321, thanks @alexwrite).
- Fix: `explain`/`query` tell you how to get the full answer (and add `explain --all`) when the output is truncated to the token budget (#4323, thanks @alexwrite).
- Change: a committed `graph.json` is now written as one line of compact JSON, so re-running a build produces a `+1/-1` diff instead of a large whitespace churn (#3413, #4319, thanks @andrebrait).
- Docs: the always-on guidance explains that large `graph.json` diffs in pull requests are expected (#4324, thanks @andrebrait), and the README distinguishes local CLI skill guidance from MCP instructions (#3925, #4292, thanks @Tivonsico).
## 0.9.85 (2026-10-10)
A language-extraction and tooling batch: six extractor/exporter fixes picked from open issues, plus fourteen community pull requests.
- Fix: **Julia** functions with a `where` clause (long and short form) and parametric type definitions (`struct Box{T}`, `abstract type Shape{T}`, `struct Sq{T} <: Shape{T}`) are now extracted. The name sat inside a `where_expression` / `parametrized_type_expression` wrapper that the name lookup skipped, so the whole method/type was dropped; a declared type parameter (`value::T`) no longer leaks as a phantom field-type node (#4126, #4128).
- Fix: **Fortran** a named generic `interface` now gets a callable node and a `dispatches_to` edge to each listed `module procedure`, so a call to the generic name resolves instead of dangling; unnamed and operator/assignment interfaces are unaffected (#4141, #4142, thanks @rajatnagda45).
- Fix: **Elixir** `defdelegate` is now extracted as a function definition, so the delegated function appears and same-module calls to it resolve (#4132).
- Fix: **Scala 3** `given ... with` instance methods are scoped to the given instead of leaking to file scope (where they collided with the same-named trait type); an anonymous `given Show[Int] with` keeps its members under a synthesized name (#4130).
- Fix: **graph.html** a node label containing an unclosed `<!--` then `<script` can no longer break out of the embedded data script and blank the page; every `<` in the embedded JSON is escaped (#4124).
- Fix: the incomplete-extraction message now explains a pure semantic omission (a smaller `--token-budget` is the lever) instead of always blaming truncation (#4101, #4280, thanks @ayushcodes10).
- Fix: **TypeScript** `export type *` star re-exports no longer trip a spurious syntax-error flag that skewed the health report (#3942, #4303, thanks @ayushcodes10).
- Fix: **Rust** a cross-file reference to a type with impl blocks in other files now binds to the single declaration instead of a sourceless stub, fail-closed (#4283, #4284, thanks @Alin1233).
- Fix: distinct files that mint the same node id after the extension is dropped (`x.py` vs `x.html`) are kept as separate nodes instead of collapsing and losing one (#4281, #4282, thanks @bitwizard25).
- Fix: the git hook now updates the graph for files whose names contain non-ASCII characters (`core.quotePath=false` plus C-quote decoding) (#4278, #4279, thanks @smngvlkz).
- Fix: **watch** the rebuild lock no longer unlinks on release while a waiter holds it, closing a race that let two rebuilds run at once (POSIX) (#4273, #4277, thanks @ayushcodes10).
- Fix: an unanchored ignore pattern is now matched against the entry name only (so `*` no longer crosses `/` and negations behave like git) (#4242, #4251, thanks @Yyunozor).
- Fix: the hook-guard Read nudge now fires for PowerShell source files (`.ps1`/`.psm1`/`.psd1`) (#4271, #4272, thanks @rtmalikian).
- Fix: the skill now tells you that `graphify-out/` was written into your working tree, with how to gitignore or share it (#3941, #4308, thanks @eoinforker).
- Fix: `graphify affected <Symbol>` now reaches callers through the symbol's defining file, so Dart/Flutter (and other file-module languages) return results instead of nothing (#4288, #4290, thanks @dcaldr).
- Fix: an `export default` with a huge base64 literal plus a tsconfig `baseUrl` no longer crashes extraction with "File name too long" (#4166, #4169, thanks @bastiennoel93).
- Fix: markdown links to a file name containing a space (`[x](<My Note.md>)`, `[x](My%20Note.md)`) now resolve to a references edge (#4177, #4178, thanks @Yyunozor).
- Perf: skip a per-call `os.getcwd()` in the path-memo key for absolute paths, and walk each JS/TS tree once when collecting symbol facts — both output-identical (#4301, #4302, thanks @pishuv).
## 0.9.84 (2026-10-10)
A correctness batch picked directly from open issues, plus an OpenCode plugin fix.
- Fix: **PowerShell** functions (and classes/enums) defined inside a file-scope `begin {}` / `process {}` / `end {}` block are now extracted. These blocks parse as pseudo-commands rather than named blocks, so the walker returned before descending and dropped everything inside — the common `[CmdletBinding()]` advanced-function layout (#4270).
- Fix: **R** every backtick-quoted binding now gets a collision-free id. A partially-symbolic name like `` `%foo%` `` (or an S3 method `` `[.cls` ``, or a dotted `` `my.op` ``) normalized to the same id as an ordinary identifier and was silently dropped on merge; such names now use the reserved operator namespace (#4253). (The separate infinite-loop reported in that issue was already fixed in 0.9.83.)
- Fix: `graphify path` and the MCP `shortest_path` tool now fail closed on a `file::symbol` endpoint whose file defines no such symbol, instead of falling back to scoring the bare name and answering from a same-named symbol in another file; the refusal lists the files that do define it (#4264).
- Fix: **OpenCode** the plugin now loads on OpenCode 2 (a default export with `setup()`) while keeping the v1 `server()` hook, and `graphify install` refreshes an already-installed v1-only copy, so the graph tools appear again for users upgraded past the v1 plugin contract (#3554, #3732, thanks @bercedev).
- Fix: **Python** a bare `import logging` is no longer captured by a loose same-named module (`scripts/logging.py`) in a non-package directory — it resolves to the stdlib module. Guarded by a vendored, deterministic stdlib-name set across the module resolver, the sibling-repoint pass, and the build-time stem-alias index; a deliberate package shadow (`logging/__init__.py`) still resolves (#4261).
- Fix: **Python** a qualified external annotation (`httpx.Response`) no longer binds to an unrelated private local class (`_Response`). The stub-rewire match key now preserves underscores, so `_Response` and `Response` are distinct identifiers (#4269).
- Fix: **C#** an external type the corpus references but never declares (`List<T>`, `System.Type`, Unity's `VisualElement`) now gets one shared, name-keyed placeholder instead of a separate per-file stub (`a_cs_list`, `b_cs_list`), so a query returns one node and the files sharing the type connect through it (#4247).
- Fix: two files that differ only by extension (`portal.py` / `portal.html`) no longer collapse into one node. They derive the same extension-stripped id; the best-ranked file keeps it, the other gets an extension-qualified id (`…_portal_html`), and its edges follow it (matched by root-relative `source_file`, so absolute semantic paths work). A bare id shared across different paths still collapses with the warning (#4281).
- Internal: added regression coverage locking in cross-module binding of a singly-defined type through re-export chains and the unique-stub rewire; the reported failure (#4262) no longer reproduces on this line.
## 0.9.83 (2026-10-09)
A C# resolution and determinism batch, plus Windows test-suite fixes.
- Fix: **C#** a generic and a non-generic type of the same name (`Effect` vs `Effect<T>`) now get distinct node ids (arity folded into the id), so they no longer collapse into one node and `Effect<T> : Effect` no longer produces an inherits self-loop (#4255, #4249, thanks @ayushcodes10).
- Fix: **C#** a call through a member chain (`a.b.C()`), element access (`xs[i].M()`), or inside a constructor body now resolves to the right method, fail-closed to a single declared type per hop (collection element types only for `T[]`/`List<T>`), instead of being dropped (#4258, #4246, thanks @Mpasha17).
- Fix: **C#** a bare call whose name matches an enum member (`Delegate(t)` next to a `Delegate` case) no longer binds to the enum member; case members are excluded from both the per-file and cross-file call-resolution maps, fail-closed (#4254, #4245, thanks @ayushcodes10).
- Fix: **C#** a static factory that constructs its own type keeps its `method` membership edge — an undirected collapse previously let the reverse `calls` edge overwrite it, hiding the member from every type walk (#4256, #4248, thanks @Mpasha17).
- Fix: an unqualified call in **Java/C#/Scala/C++** binds to the method on the caller's own class chain (own class, then in-file bases, then enclosing classes) instead of the last same-named method declared anywhere in the file; fail-closed on a tie, with free functions and constructors still resolving (#4252, thanks @Yyunozor).
- Fix: an unresolved re-export target id is now portable — it is minted in a dedicated namespace with a hash over the repo-relative path instead of the absolute checkout path, so a clone produces the same graph (#4257, thanks @andan0).
- Fix: `graphify`'s own skill runbook now reuses the persisted scan options (excludes and the gitignore flag) a prior build wrote, so a skill-driven update no longer drops opted-in files or re-adds explicitly excluded ones versus a CLI rebuild (#4250, #4240, thanks @deepanshupal).
- Fix: the test suite passes on Windows — `_atomic_replace` refuses a read-only destination there instead of silently clobbering it (nt-only guard, no POSIX change), plus encoding, path-shape, and non-regular-file test guards (#4260, thanks @SoloDrex52).
- Fix: **C#** nested types imported with `using static` now resolve (`using static Demo.Layer;` reaching `Layer.Inner`), fail-closed on ambiguity, complementing the enclosing-namespace resolution also in this release (#4235, #4216, thanks @hopstreax).
- Fix: the AST cache directory is now namespaced by the installed tree-sitter grammar versions (`...-g<fingerprint>`), so upgrading a grammar within the same graphify release no longer serves stale or ABI-incompatible cached ASTs for unchanged files (#4239, #4236, thanks @nothariharan).
## 0.9.82 (2026-10-09)
- Feature: after `graphify extract` builds a graph, the CLI prints a short one-time pointer to Graphify Cloud (faster indexing, fewer tokens, cross-repo search, PR review, and an always-current view of your SDLC at app.graphify.com). It is shown only in an interactive terminal, once per project, and can be turned off with `GRAPHIFY_NO_TIPS` or `GRAPHIFY_NO_CTA`; CI, piped output, and the AI-assistant pipeline never see it.
- Fix: **Rust** an in-file call now binds to the type that actually names it rather than any same-named function — `Type::new()` / `Self::f()` / `self.f()` resolve through the owning `impl` (unique-owner only, fail-closed), instead of mis-binding to an unrelated `fn` with the same last segment (#4238, #4237, thanks @rohit-jsfreaky).
- Fix: **SVG export** strips XML-illegal control characters from node and legend labels, so a label carrying e.g. an ANSI escape no longer produces a malformed `graph.svg`; valid whitespace and Unicode are preserved, and node identity is unchanged (#4244, thanks @Yyunozor).
## 0.9.81 (2026-10-08)
A large resolution-accuracy and determinism batch across Python, TypeScript, C#, Elixir, and PHP, plus analysis-quality fixes and a portability regression guard.
- Fix: **Python** nested class identities are now qualified by their enclosing class, so `class Outer: class Inner` no longer collides with a different `Outer2: Inner` into one node (and their methods no longer collapse together) (#4228, thanks @xiehuanyi).
- Feature: **Python** `self`/`cls`/`super()` calls now resolve to a base-class method defined in another file, fail-closed on multiple/opaque/out-of-corpus bases, with `super(Cls, obj)` forms excluded (#4222, #4221, thanks @rohit-jsfreaky).
- Fix: **Python** typed-receiver resolution finds a nested function's file by climbing to the real file node, so a `self.x.m()`/annotated-local call inside a nested def now resolves instead of failing the visibility check (#4227, #4226, thanks @rohit-jsfreaky).
- Fix: **Python** an absolute import (`from werkzeug.wrappers import Request`) no longer resolves onto a local same-named module (`wrappers.py`), which had rewired a class to inherit from itself; a same-named module in another package still resolves (#4225, #4224, thanks @rohit-jsfreaky).
- Feature: **TypeScript** `this.field` receivers are typed from class field declarations (not only constructor-injected parameters), so a service's `this.repo.find()` resolves; fail-closed on static/array/union/generic field types (#4231, #4230, thanks @rohit-jsfreaky).
- Fix: **TypeScript** a call on a receiver whose type name is declared in several files resolves to the one the caller actually imports, and a public call never binds to a `#private` method of the same name (#4233, #4232, thanks @rohit-jsfreaky).
- Fix: **C#** types declared in an enclosing namespace now resolve from a nested namespace (`Demo.Inner` referencing a type in `Demo`), innermost-wins, additive over the existing scope resolution (#4204, #4196, thanks @hopstreax).
- Fix: **Elixir** qualified and aliased remote calls (`App.Accounts.get_user`, `alias App.Accounts; Accounts.get_user`) resolve to the right module's function, and a member call never mis-binds to a same-named local; cross-file targets link only to the single corpus module that defines the function (#4213, #4206, thanks @Mpasha17).
- Fix: **Elixir** keyword-form definition bodies (`def f(x), do: g(x)`) now have their calls extracted, not only the `do`/`end` block form (#4210, #4207, thanks @rtmalikian).
- Fix: **PHP** 8.5 `(void)` casts and a cast applied to a `match` expression are parsed via a fail-safe pre-processing pass, so calls behind them are no longer dropped to a syntax error (#4211, #4202, thanks @Mpasha17).
- Fix: plain AST declarations (TS type aliases, enum members, JSON keys) whose only edge is the structural `contains` from their file are no longer flagged as weakly-connected knowledge gaps in the analysis output and GRAPH_REPORT.md (#4212, #4205, thanks @nothariharan).
- Fix: low-confidence AMBIGUOUS edges are scaled down before community detection (on a copy, leaving the real graph untouched) so they no longer fuse independent clusters, and ambiguous suggested questions move to a separate low-confidence section; `GRAPHIFY_AMBIGUOUS_SCALE=1.0` restores the prior behavior (#4214, #4199, thanks @JunoLee1).
- Test: a portability regression guard pins that the same repo graphs identically regardless of checkout folder, warm vs cold rebuild, or line endings, and that no checkout path leaks into graph.json (#4218, #4217, thanks @rohit-jsfreaky).
- Docs: `graphify path`'s top-level help now states it defaults to a directed search and does not consult the JSON `directed` flag (#4229, #3712, thanks @wanjinhao1); CONTRIBUTING.md adds guidance for contributors using AI coding agents (#4219, thanks @adityatiwari101104).
## 0.9.80 (2026-10-07)
More clone-identical determinism, a large MCP retrieval speedup, and a batch of extractor and resolution fixes.
- Fix: more node ids and manifest keys stop leaking the local checkout path or OS username, so a clone keeps producing the same graph — a re-clone no longer keeps a previous checkout's absolute manifest keys (#4175, thanks @SrijanSriv), an AST cache hit whose import target has since been deleted is re-extracted instead of replaying the stale absolute-path id (#4185, #4184, thanks @rohit-jsfreaky), and `--no-cluster` graph.json no longer ships `extract()`'s run-only absolute source paths or undeclared/dangling endpoints (#4189, #4183, thanks @rohit-jsfreaky).
- Fix: cross-drive scans no longer abort on a syntax-error warning — the remaining unguarded `os.path.relpath` in the partial-extraction warning path falls back to a portable display path on Windows cross-drive files, completing the 0.9.78 #4106 fix (#4149, thanks @xiehuanyi).
- Fix: an external import stub whose last referencing edge is gone is dropped on incremental rebuild instead of lingering as a zero-degree ghost a fresh build never has (#4181, thanks @rohit-jsfreaky).
- Fix: **Python** `self.<attr>.<method>()` calls now resolve through the attribute's constructor/annotation type to the owning class, fail-closed on ambiguity (#4176, #2860, thanks @Mpasha17).
- Fix: **Python** `obj.method()` on a local or parameter whose class is known from an annotation (`c: Client`, including `Optional`/union/string forms) or a constructor/`with` binding now resolves to the owning class's method, fail-closed to a single in-file-or-imported class that owns it (#4198, thanks @rohit-jsfreaky).
- Fix: **Python** imports under `if TYPE_CHECKING:` are marked type-only so they no longer form impossible runtime import cycles (#4179, #3159, thanks @Yyunozor).
- Fix: **Java** methods and the calls inside anonymous class bodies (`new Runnable() { public void run() {...} }`) are now captured, mirroring the Kotlin object-literal handling (#4140, thanks @rajatnagda45).
- Fix: **markdown** links to file names containing spaces or percent-encoding (`[x](<My Note.md>)`, `[x](My%20Note.md)`) resolve to the real file instead of a ghost target; wikilinks stay verbatim and external links stay excluded (#4178, thanks @Yyunozor).
- Fix: a `re_exports` edge is kept for both files whose ids collide after separator normalization (`a-b/x.ts` and `a/b/x.ts`), removing an order-dependent dropped edge (#4143, thanks @Cintu07).
- Fix: the Windows `graphify` console script now uses the multi-core extraction pool instead of silently falling back to single-core, since a non-file `__main__.__file__` no longer disables it (#4187, #4186, thanks @rohit-jsfreaky).
- Perf: the MCP server builds the traversal view and shortest-path graphs once per loaded graph instead of rebuilding them on every call, keyed to the existing graph cache (~1.6x on `query_graph`, up to ~300x on `shortest_path`); the two cached path graphs add a bounded, LRU-capped memory cost (#4192, #4191, thanks @rohit-jsfreaky).
- Perf: the extraction id-remap and call tie-break resolve and parse each path once per run instead of recomputing it for every node and candidate (tens of thousands of redundant `Path.resolve()` calls eliminated), with byte-identical graph.json output (#4195, thanks @rohit-jsfreaky).
- Feature: `graph_stats` (CLI and MCP) now reports the graph's build commit and whether it is behind the current HEAD (#4144, thanks @Mpasha17).
- Chore: raise the `tree-sitter` runtime floor to `>=0.25` so a resolver cannot pair a 0.23/0.24 runtime (ABI 14) with the ABI-15 grammars pinned here and fail every parse (#4148, thanks @JFWaskin).
- Test: optional-extra tests now skip cleanly when the extra is not installed, so a default `uv sync` no longer reports failures for grammars and SDKs it never pulled (#4193, #4190, thanks @yatharth1706).
## 0.9.79 (2026-10-06)
- Fix: incremental `graphify update` and `watch` no longer re-cluster and re-render on every run after an unchanged rebuild. The `schema_version`/`graphify_version` metadata added to `graph.json` in 0.9.78 (#4167) was being counted as a topology change by the watch unchanged-topology check, since the freshly-built candidate graph carries no such metadata; it is now excluded from the comparison.
## 0.9.78 (2026-10-06)
A determinism and portability batch: node ids no longer leak the checkout path, OS username, or line endings into graph.json, so a clone produces the same graph everywhere. Plus encoding, C#, PowerShell, C++, and watch-reconcile correctness fixes.
- Fix: node ids no longer embed the local checkout path or OS username for unresolved targets — a missing `require('./x')` (#4155, #4154), a missing Python relative import `from .absent import x` (#4157, #4156), and a `.dmf` element keyed on its window (#4153, #4152) now mint portable, clone-identical ids; all three thanks @rohit-jsfreaky.
- Fix: **C#** namespace scope ids are keyed on row/column instead of byte offset, so a CRLF checkout no longer writes different `scope_chain` metadata than an LF one (#4165, #4164, thanks @rohit-jsfreaky).
- Fix: **R** symbol-only bindings (operators like `` `%||%` ``) no longer collapse onto the file node and spin `resolve_local` forever; they get a stable, collision-free id (#4171, #4162, thanks @xiehuanyi).
- Fix: source files are decoded before parsing — a UTF-16 file no longer yields zero nodes and a cp1252 name like `CaféOrder` is no longer truncated; valid UTF-8 files are byte-for-byte unchanged, non-UTF-8 falls back deterministically (#4146, #4145, thanks @rohit-jsfreaky).
- Fix: **C#** calls through a cast or an interface-typed property now resolve — `((IStore)x).Save()`, `(x as IStore).Save()`, and a shadowed `IStore Store => ...; Store.Save()` bind to the interface's own method instead of being dropped or mis-bound to the concrete class (#4172, #3797, thanks @Mpasha17).
- Fix: **PowerShell** member calls bind by receiver owner rather than method name alone — `$this.M()` resolves to the enclosing class and `[Type]::M()` to a single unambiguous local class, fixing a same-name-method mis-bind; value, cast, dynamic, duplicate, and external receivers stay fail-closed (#4163, #4158, thanks @oleksii-tumanov).
- Fix: **C++** classes behind module export macros (`class MYLIB_EXPORT Foo : Base {}`) are preserved — the ALL-CAPS macro is blanked before parsing (byte-length and newline preserving) so tree-sitter no longer error-recovers over the class and drops it (#3661, #3648, thanks @nikhilsaxena04).
- Fix: incremental `graphify update` keeps placeholder nodes for never-scanned referenced files, so an unchanged referrer's cross-file edges to an unresolved import or a `.csproj`/`.sln` reference are no longer dropped as if the target were deleted; genuinely deleted files are still reaped (#4161, #4160, thanks @rohit-jsfreaky).
- Fix: `graph_diff` keeps edge direction, so a reversed edge (a→b becomes b→a) is reported as a change instead of cancelling out to "no changes"; the diff now loads the previous graph through the direction-preserving reader (#4170, #4067, thanks @Mpasha17).
- Perf: the watch reconcile pass computes each stored source path's identity once per update instead of recomputing it for every node, edge, and hyperedge, cutting reconcile on graphify's own repo from 10.2s to 2.7s with graph.json byte-identical (#4151, #4150, thanks @rohit-jsfreaky).
- Feature: `graph.json` now carries `schema_version` and `graphify_version` metadata for downstream consumers, written additively so existing readers and older graphs are unaffected (#4167, #3638, thanks @smngvlkz).
## 0.9.77 (2026-10-05)
More community language-structure coverage, two reload/incremental correctness fixes, and a batch of extractor tests.
- Feature: four more language extractors gained type coverage from @rajatnagda45 — **Solidity** enum values link via `case_of` instead of `contains` (#4090), **PowerShell** enum members likewise (which also stops a member named like a type binding as a constructor) (#4092), **Objective-C** C-style `enum` and `typedef enum` declarations are extracted with their members (#4096), and a **SQL** trigger is linked to the function it executes with an `executes` edge (#4094).
- Feature: **Scala** class-level context bounds (`class C[A: Ordering]`) now resolve to `references` edges, mirroring the method-level handling from 0.9.75 (#4085, #4080, thanks @Mpasha17).
- Fix: **Astro** files terminate a trailing `//` comment in one `<script>` block so it no longer swallows the next block on the same line; byte offsets and line numbers are preserved (#4083, #4072, thanks @Sourya-Prabaharan).
- Fix: a zero-byte source file keeps its node but no longer claims a line-1 source location, so an empty `__init__.py` is still a real module without a misleading anchor (#4086, #4041, thanks @Mpasha17).
- Fix: **PHP** calls to a method whose name collides with a cross-language builtin (`$this->list()`, `$this->open()`) now resolve against the caller's own class instead of being dropped, while non-`$this` receivers and bare construct names stay fail-closed (#4119, refines #3975 and #3381, thanks @xiehuanyi).
- Fix: **JavaScript** methods attached to an exported object (`res.format = function () {...}; module.exports = res`) are now extracted as nodes, so calls inside them resolve across CommonJS/ESM module boundaries (#4121, #3778, thanks @harshaygadekar).
- Fix: incremental `graphify update` on a subfolder no longer false-reports files in sibling folders as deleted; a shared single manifest now anchors stored paths to its base and scopes deletion detection to the scanned root (#4118, #3785, thanks @harshaygadekar).
- Fix: reloading a saved `graph.json` preserves stored edge direction across the HTML, Cypher, Neo4j, FalkorDB, and Obsidian-canvas exporters, the watch HTML re-render, and the merge driver, instead of reversing edges whose target precedes their source in the node list (#4088, #4066, thanks @e4c5).
- Fix: **PowerShell** method calls are now recorded — `$this.Method()` and `[Type]::Method()` resolve to the method node by name, keyed only on method-labelled targets so a free function sharing the name is never bound, and unresolved names stay fail-closed (#3995, thanks @rajatnagda45).
- Fix: **Rust** anonymous constants (`const _: () = assert!(...)`) no longer collapse onto the file node and emit a self-loop; the anonymous item is skipped while named consts and statics keep their nodes (#4108, #4064, thanks @xiehuanyi).
- Fix: a cleanly-parsed source file on a different drive from the scan root no longer aborts the whole extraction on Windows; the symbol-free-file warning falls back to an absolute display path instead of raising (#4106, #4059, thanks @xiehuanyi).
- Fix: `--no-dedup` now also disables the same-file ghost-merge pass in `build_from_json`, so two distinct non-AST nodes sharing a `(source_file, label)` are preserved when dedup is turned off; AST reconciliation and the default dedup path are unchanged (#4122, #4019, thanks @xiehuanyi).
- Fix: a second `/graphify` run on an unchanged mixed corpus no longer crashes with a missing `.graphify_semantic.json`; when every semantic file is cached the skill still runs Step B3's merge (and clears stale chunk files first) so Part C always has its input (#4117, #4116, thanks @brunovima83).
- Test: extractor coverage was extended for **Pascal** valued enums (#4104, thanks @MalikHaroonKhokhar), **C#** `this?.M()` null-conditional self calls (#4105) and **Zig** tagged-union nested-struct payloads (#4107, thanks @Jarvis-J-Jacob), **Kotlin** annotation class-literal boundaries (#4112) and cross-language JVM inheritance (#4113) plus **C++** union method-versus-field edges (#4114, thanks @xiehuanyi), **Elixir** `defguardp` private guards (#4084, thanks @ClockZW), and **bash** `source`/`.` imports plus `merge-graphs --previous` node pruning (#4097, #4081, #4078, thanks @akshitj11).
- Docs: the code-intelligence benchmark result is surfaced in the README and BENCHMARKS summary tables (key-fact coverage 82.0% vs a 70.8% grep-and-read baseline, n=6) (#4109, thanks @Mr-Neutr0n).
## 0.9.76 (2026-10-04)
More language-structure coverage, resolution/dedup correctness, and a security fix for the git-hook installer.
- Feature: four more language extractors gained type coverage from @rajatnagda45 — **Zig** tagged-union variants (`union(enum)`) emit a node per variant with a `case_of` edge (#4050), **C++** `union` specifiers are extracted as class-like type nodes with their members (#4052), **VB.NET** enum members link via `case_of` instead of `contains` (which also stops a member named like a type binding as a constructor) (#4054), and **Pascal** enumerated types and their values are extracted (#4056).
- Feature: three **Scala** type-reference fixes shipped together earlier in the day are followed here by @Faisal-Fayaz's extractor hardening — graphify now stops walking JSON Schema files as config manifests, keyed on a `$schema` declaration plus a structural marker (`$defs`/`definitions`/`$id`) so real manifests that merely reference a schema are unaffected (#4048, #2255).
- Fix: **Elixir** `import`/`use` call scoping is now per-module rather than per-file, so an unqualified call in one module no longer resolves against another module's imports in the same file (#4058, refines #4015).
- Fix: track calls to external **Python** modules — a `module.func()` call to a plainly-imported dependency now records a `calls` edge to that module, fail-closed (receiver-shadowing and non-unique bindings are skipped, builtins and unresolved locals are never fabricated) (#4043, #3793, thanks @oleksii-tumanov).
- Fix: **Svelte** files now feed only their `<script>` blocks to the AST pass (masking the template and style, preserving line numbers), so the markup no longer produces a parse error that dropped every symbol (#3984, #3928, thanks @Agnik47).
- Fix: **PHP** language constructs (`isset`, `empty`, `list`, `eval`, ...) no longer bind as calls to a user method that happens to share the name (#3975, #3830, thanks @Cintu07).
- Fix: an unresolved base class whose only same-named definition lives in another language is kept unresolved instead of binding across languages; same-language cross-file inheritance still resolves (#4068, thanks @SrijanSriv).
- Fix: unstamped document nodes with the same heading in different files are no longer merged together during dedup; same-file document twins still merge (#4065, thanks @SrijanSriv).
- Fix: `graphify path` and the MCP `shortest_path` tool resolve a `path::symbol` or raw node-id endpoint to the exact node before falling back to fuzzy scoring, and refuse an ambiguous endpoint instead of silently picking one (#3935, #3913, thanks @bercedev).
- Fix: cross-repo resolver confidence scores are snapped to the canonical INFERRED rubric (a label-only change; no edge is added, dropped, or reclassified) (#4046, #4045, thanks @DeepanshuPal).
- Fix: `graphify` no longer indexes its own installed skill folders and whole-written rule/hook files when scanning a project, keyed on the exact install locations so a user's own `graphify`-named folder is not skipped (#4062, #4057, thanks @Mpasha17).
- Fix: the suggested questions in the analysis output are diversified across signal types with a round-robin, so one category no longer crowds out the others (#3972, #3849, thanks @azizur100389).
- Security: `graphify hook install` refuses a `core.hooksPath` that resolves outside the repository (resolving both sides, symlink-safe), falling back to the in-repo `.git/hooks` instead of writing an executable outside the checkout; legitimate in-repo custom hook paths, linked worktrees, and submodules still work (#3919, #3869, CWE-22, thanks @nothariharan).
- Chore: the PyPI package page now points Homepage at graphify.com and adds a Documentation link to docs.graphify.com; Repository and Issues stay on GitHub (#4069, thanks @SyedFahad7).
## 0.9.75 (2026-10-04)
Intra-class call binding across five languages, new language-structure coverage, and a batch of resolution/build/cache fixes.
- Feature: `self`/`this`/`super` member calls now bind to the caller's own class chain instead of the file-wide last-definition, across **Python** (`self.`/`cls.`/`super()`, nearest ancestor in the in-file MRO) (#4011), **JavaScript/TypeScript** (`this.m()` to the caller's own class; `super.m()` fails closed since the `extends` chain is not known at that pass) (#4012), **Swift** (`self`/`super`, class vs struct/extension aware) (#4030), and **Ruby** (`self`-sends within the class chain, complementing the paren-less self-send extraction from 0.9.74) (#4031) — all thanks @Cintu07. Multiple-inheritance ties and unknown/external ancestors fail closed (no fabricated edge).
- Feature: four more language extractors gained structural coverage from @rajatnagda45 — **Zig** error-set declarations and their members (`case_of`) (#4025), **PHP** enum cases (pure and backed) with `case_of` edges (#4026), **Elixir** `defmacro`/`defmacrop`/`defguard`/`defguardp` definitions (#4027), and **Rust** `macro_rules!` definitions with local invocation resolution (std macros like `println!` fail closed, no fabrication) (#4028).
- Feature: three **Scala** type-reference fixes from @Faisal-Fayaz — file-scope `val`/`var` type annotations now emit `references` edges (#4036, #2054), `type` alias/definition right-hand sides are walked (including `opaque type` and match types) (#4037, #2049), and context bounds (`def f[A: Ordering]`) resolve to `references` edges (#4038, #2046).
- Feature: single-implementer interface dispatch was lifted into a language-agnostic core (#4034) and reused to dispatch a **Swift** protocol requirement to its sole conformer (INFERRED), failing closed on 2+ conformers (#4035) — thanks @GAURAV-1313.
- Feature: **Elixir** unqualified calls now resolve to functions brought in by `import`/`use`, scoped per module and fail-closed when no in-scope module defines the name (fixes a large god-node from corpus-wide bare-name binding) (#4015, thanks @Ayushraj06-bit).
- Feature: a **bash** script's invocation of another script through a non-shell interpreter (`python3 build.py`, `node build.js`, `"$PYTHON" stage.py`) is now recorded as an `invokes` edge; a `$VAR`/path command word is not mistaken for an interpreter (#4007, thanks @ayushcodes10).
- Feature: **Dart** `part` files keep their own file node linked to the library (#4013, #4008), and a configurable import/export (`import 'a.dart' if (...) 'b.dart';`) now links every URI branch, not just the default (#4003, #4002) — thanks @brlumen.
- Fix: C# partial-class merging no longer drops recursive/intra-class `calls` edges; one split `partial class` anywhere in a repo used to erase every recursive self-loop graph-wide (#4017, thanks @rohit-jsfreaky).
- Fix: `graphify path` (CLI and the MCP `shortest_path` tool) now routes through a `contains` edge back out to a symbol's file, so a cross-file dependency that must pass through a shared symbol is found instead of reporting no path (#4004, #3878, thanks @ayushcodes10).
- Fix: the AST cache rebinds its stat index when a later call names a different cache root, so a multi-project process writes each root's `stat-index.json` with only its own files instead of pooling them into the first root's file (#4005, #3989, thanks @ayushcodes10).
- Fix: `merge-graphs --previous <merged-graph.json>` restores the previous merged community assignment by node id, so cluster/label reuse stays stable across re-merges; a malformed `--previous` file now errors cleanly instead of crashing (#4006, #3858, thanks @ayushcodes10).
- Fix: `cluster-only`/`label` no longer drop parallel edges (two different relations between the same pair) when reloading and rewriting a graph; preserved links track node-id remaps so none are silently dropped on a legacy graph (#4010, #3999, thanks @ayushcodes10).
- Fix: the Ollama `num_ctx` setting is merged into `extra_body` instead of replacing it, so a `GRAPHIFY_DISABLE_THINKING` body set on the same backend is no longer silently clobbered (#4029, #3988, thanks @Agnik47).
- Fix: the hook-guard reminder names the effective `graph.json` path when `GRAPHIFY_OUT` is customized, instead of always naming the default `graphify-out/graph.json` (#4042, #4040, thanks @DeepanshuPal).
- Fix: `graphify extract` refuses to overwrite an existing graph with a dedup-shrunk one unless `--allow-dedup-shrink` is passed; the refusal reports the actual number of nodes dedup merged (#4014, #3774, thanks @SrijanSriv).
- Feature: extraction warns once when a cleanly-parsed code file yields no symbols beyond its own file node (often a data literal), while staying silent on empty/whitespace-only files, no-extractor languages, and intentionally-skipped data (#4009, #3946, thanks @ayushcodes10).
## 0.9.74 (2026-10-02)
- Feature: four more language extractors gained structural depth. **Ruby** paren-less self-sends (`do_thing` with no receiver or parens) are now captured as calls, with a local-variable/parameter/block-parameter in scope correctly suppressing the call (fail-closed, no fabrication) (#3960, thanks @rajatnagda45). **TypeScript** `abstract` method signatures in an abstract class are now extracted as callable method members and resolve as call targets (#3961, thanks @rajatnagda45). **Scala** deferred (abstract) method declarations in traits and abstract classes (`def foo: Int`, no body) are now extracted as methods (#3962, thanks @rajatnagda45). **C++** a member-function declaration inside a class (`void foo();`, defined out-of-line) is now classified as a method rather than a field, including pure-virtual, `const`, operator, and destructor forms; real data members stay fields (#3963, thanks @rajatnagda45).
- Fix: a Rust type used before it is declared in the same file now resolves to its local declaration instead of fabricating an external stub; resolution is a two-pass, order-independent scan, and a forward reference whose name collides with another same-id item is kept unresolved (fail-closed) (#3903, #3782, thanks @Yyunozor).
- Fix: Rust prelude types (`Option`, `Result`, `Vec`, `String`, `Box`, `Rc`, `Arc`, `HashMap`, and friends) no longer accumulate spurious in-degree and surface as god nodes that distort community detection and ranking; they are filtered at extraction time, and a type the file defines itself is kept. Tuple-struct and unit-struct construction (`ClientId(id)`) is reclassified from a `calls` edge to a `references`/`constructor` edge (#3973, thanks @liam-mcelhaney122).
- Fix: an Obsidian-style wikilink to a note whose name contains a dot (`[[v1.2 release]]`, `[[note.en]]`) now resolves instead of being dropped by a premature extension strip; plain, explicit-extension, anchor, and alias wikilinks are unchanged, a literal indexed file beside the link keeps precedence, and a genuinely missing target is still not fabricated (#3905, #3904, thanks @Yyunozor).
- Fix: a constructor call whose target is a same-file annotation stub (a source-less node minted by an earlier annotation or generic-argument reference) is kept for cross-file resolution and binds to the real definition when there is import or namespace/using-scope evidence, instead of being dropped; a genuinely external unresolved name is still not fabricated (#3901, thanks @bercedev).
- Fix: the CLI hook-guard that protects graphify's own output directory now resolves paths and checks real containment instead of substring matching, so a sibling directory sharing a name prefix (`artifacts/graphify-notes` vs `artifacts/graphify`) is no longer wrongly treated as inside the output dir; symlinks, `..`, relative paths, and case-insensitive filesystems are handled (#3964, #3959, thanks @shobhitagnihotri69).
- Fix: when resolving an import of a workspace package that ships both a source entry and a built dist, the graph now prefers the source file (so edges point at authored code) and falls back to the built artifact only when no source entry exists, generalizing the existing source-over-dist preference from the `exports` path to the legacy `main`/`module` fields (#4000, #3834, thanks @Adityakk9031).
- Feature: Verilog header files (`.vh`) are now detected and extracted through the same tree-sitter-verilog grammar as `.v`/`.sv`, so macros, parameters, and modules declared in headers are captured (#3983, thanks @oleksii-tumanov).
- Fix: an incremental update no longer drops a hyperedge from an untouched file when a re-extracted file emits a hyperedge with the same id; carried hyperedges are now keyed by `(id, source_file)` so a same-id edge from a different, unchanged file is preserved while a genuine same-file re-emit still replaces (#3997, #3981, thanks @Ayushraj06-bit).
- Feature: Kotlin class-literal references in annotation arguments (`@ManyToOne(targetEntity = Customer::class)`) are now extracted as attribute references to the referenced type (#3965, #3835, thanks @hopstreax).
- Fix: Lua colon-method calls (`obj:method()`, `self:method()`) now resolve to the table-qualified method definition; a non-`self` receiver fails closed with no cross-file member fabrication (#3994, thanks @rajatnagda45).
- Feature: Erlang local fun references (`fun Name/Arity`, as in `lists:map(fun helper/1, L)`) are now recorded as `indirect_call` edges to the arity-matched local function; remote `fun Mod:Name/Arity` and anonymous funs are left as-is, and an undefined or arity-mismatched local fails closed (#3996, thanks @rajatnagda45).
- Fix: C# null-conditional member calls (`receiver?.Method()`) now resolve through the same typed-receiver path as `receiver.Method()`, including the call-site generic-argument walk; chained and element-access forms fall through to bare-name resolution with no fabrication (#3976, #3797, thanks @Cintu07).
## 0.9.73 (2026-09-30)
- Feature: enum members are now extracted as nodes with `case_of` edges in four more languages — **Rust** enum variants (#3938), **Zig** enum members (#3940), **C++** `enum`/`enum class` enumerators including nested enums (#3939), and **Scala 3** enum cases plus their methods (#3937) — all thanks @rajatnagda45.
- Fix: Java calls to inherited methods and `super.method()` now resolve to the declaring ancestor (walking the `inherits` chain, nearest declaration wins), instead of dangling; an unknown/external or ambiguous ancestor fails closed (#3932, thanks @janwaleed09).
- Fix: semantic extraction warns once when a file exceeds the 20,000-character cap and is truncated, instead of silently dropping the tail (#3923, #3773, thanks @AK-Lmn).
- Feature: Solidity file-level free functions (Solidity 0.7+, declared outside any contract) and their calls are now extracted (#3906, thanks @rajatnagda45).
- Fix: VB.NET type/module-qualified calls (`MyModule.DoThing()`, `MyClass.SharedMethod()`) resolve to the target method; value-receiver and `MyBase.` calls fail closed (#3909, thanks @rajatnagda45).
- Fix: Astro files are parsed correctly — only the frontmatter and `<script>` blocks are fed to the AST pass (the HTML template no longer produces parse errors), with line numbers preserved (#3902, thanks @Bosken85).
- Fix: the "surprising connections" cross-repo/directory bonus now matches the reason it prints — two files at the scan root no longer falsely score as crossing repos (#3934, thanks @neo1777).
- Fix: under `--exclude-hubs`, a node whose only neighbours are excluded hubs is kept with its hub's community instead of being severed into a singleton; the default path is unchanged (#3933, thanks @neo1777).
- Perf: the Neo4j/FalkorDB `--push` path creates a per-label id index before the node upsert loop, fixing the throughput collapse on large graphs (#3957, thanks @Yi-111-a).
- Fix: community labeling keeps the labels it already named when a nested retry fails to parse, instead of discarding the whole batch (#3956, thanks @Vikram-Lex).
- Fix: `graphify hook status` reports hooks written by an older release as out of date (run `graphify hook install` to refresh) (#3951, #3771, thanks @bercedev).
- Fix: a Rust virtual-workspace-root `Cargo.toml` (only `[workspace]`, no `[package]`) is treated as skipped-by-design rather than warned as zero-node (#3930, #3910, thanks @Adityakk9031).
- Fix: when the instructions file (`CLAUDE.md` etc.) is a symlink, install reports the real target it wrote to, and uninstall keeps the symlink (strips only the graphify section) instead of deleting the link (#3950, #3953, #3805, thanks @bercedev).
## 0.9.72 (2026-09-29)
- Feature: after a package upgrade, `graphify` refreshes stale installed skills automatically (the `SKILL.md` + references sidecar it manages) so the version-mismatch warning no longer requires a manual `graphify install`. It runs on any non-install CLI command when a skill is stale, backs up local edits to `SKILL.md.bak`, never touches your marker-bounded `CLAUDE.md`/`AGENTS.md`/`GEMINI.md` sections, and can be disabled with `GRAPHIFY_NO_AUTO_REFRESH=1` (#3895, #1805, thanks @bercedev).
- Fix: `graph.html`'s Node Info panel now shows the real Type/Source/Community for each node instead of "Type: unknown / Source: -" (the panel read field names that did not match the emitted node schema); aggregated community nodes show a member count (#3918, #3914, thanks @hopstreax).
- Fix: a Kotlin class property that is both annotated and has an inferred type (`@Volatile var x = 0`) no longer crashes extraction with an `UnboundLocalError` that dropped the whole file (#3915, thanks @nothariharan; #3899, thanks @harshaygadekar; #3884).
- Fix: SQL DDL that appears before a PostgreSQL `DO $$ ... $$` block is now extracted — the block node the parser produces for that span is walked instead of skipped (#3900, thanks @bercedev).
- Fix: Razor extracts C# members from `@functions { }` blocks (classic Razor Pages/MVC), not only Blazor `@code { }` blocks (#3908, thanks @rajatnagda45).
- Feature: Blade templates now link a view to the layout it `@extends` (#3907, thanks @rajatnagda45).
- Fix: resolving an imported module name no longer binds to a same-named contained symbol (a class/module member); only genuine top-level module/file nodes are considered (#3898, #3887, thanks @harshaygadekar).
- Fix: `docx` sidecar conversion now keeps tables in their document position (instead of dumping them after all prose) and reads all text, including tracked insertions, content controls, and text boxes, by walking the document body in order (#3833, thanks @L4XB).
- Fix: label/signature sidecars are now published atomically and in a safe order (labels before signatures), so an interrupted rebuild can no longer leave stale community labels for a clustering that no longer exists (#3853, thanks @shashank-100).
- Fix: the markdown wikilink index respects `.graphifyignore`/`.gitignore`/`--exclude` and resolves an article-named `index` without overwriting the generated `index.md` hub — two independent wiki/markdown fixes (#3818, thanks @breken-ai; escaped-alias parsing `[[target\|alias]]` #3772, thanks @zagushka).
- Fix: the community listing in `GRAPH_REPORT.md` reuses the shared real-node filter, so `rationale`/`concept` nodes no longer inflate a community's node count or leak into the listing (#3836, #3794, thanks @ayushcodes10).
- Fix: extraction now warns once (not per file) when a PDF is encountered but the `pdf` extra (`pypdf`) is not installed, instead of silently producing no text (#3710, #3702, thanks @shobhitagnihotri69).
- Chore: `graphify` / `graphify --help` now shows the logo banner and a link to the hosted platform at app.graphify.com.
## 0.9.71 (2026-09-28)
- Feature: SQL `CREATE TRIGGER` statements are now extracted and linked to their table (`ON <table>`), including `OR REPLACE`/`OR ALTER`, `INSTEAD OF`, and procedural `BEGIN…END` bodies that previously landed in a parser-error node and were dropped (#3863, thanks @rajatnagda45).
- Feature: Groovy `enum` declarations and their constants are extracted, with members linked to the enum via `case_of` (#3861, thanks @rajatnagda45).
- Fix: R class definitions created via a namespace-qualified constructor (`R6::R6Class`, `methods::setRefClass`) are now recognised, so the class body and its methods are no longer dropped (#3864, thanks @rajatnagda45).
- Fix: R6 intra-class calls through `self$method()` and `private$method()` now resolve to the sibling method instead of dangling; `super$` is left unresolved (single-file dispatch is not visible) (#3865, thanks @rajatnagda45).
- Fix: the markdown wikilink index now respects `.graphifyignore`/`.gitignore`/`--exclude` — it no longer descends huge ignored trees when building the `[[link]]` index, and a wikilink can no longer resolve into an ignored file (#3826, #3822, thanks @Abhirup0).
- Fix: manifest re-anchoring keeps a foreign-platform key in its own path syntax (a POSIX key on Windows, a `C:\`/UNC key on POSIX) using `posixpath`/`ntpath` rather than the host's rules, fixing separator corruption introduced by the 0.9.69 portability work (#3879, thanks @Dakshcore).
- Fix: normalizing a Twitter/X URL for the oEmbed fetch rewrites only the host, so `x.com`/`twitter.com` appearing in the path or query is no longer corrupted (#3880, thanks @Dakshcore).
- Chore: `graphify install` shows the refreshed graphify logo banner (#3892, thanks @rajarshidattapy).
## 0.9.70 (2026-09-27)
- Security: the Fortran capital-F cpp step no longer allows an untrusted `.F`/`.F90` source to read arbitrary host files. `-nostdinc -I /dev/null` did not stop cpp from resolving absolute (`#include "/etc/passwd"`) or traversing (`#include "../../../secret"`) includes, which inlined host-file contents into `graph.json`/`GRAPH_REPORT.md` and the LLM context on the default offline path. Every `#include` directive is now stripped before preprocessing and the source is fed to cpp on stdin; macro expansion is preserved (GHSA-pcc4-rvhr-2pr8, CWE-22/73/200).
- Security: the Aider/Devin monolith `--watch` snippet no longer interpolates the agent-substituted `INPUT_PATH` into a shell command — it now reads the trusted `graphify-out/.graphify_root` written in Step 1, closing the last instance of the shell-injection class from #3642 (#3852, #3844, thanks @hopstreax).
- Security: Terraform secret redaction now also covers a `value` paired with a secret-named `name` in name/value pair lists (`environment = [{ name = "DB_PASSWORD", value = "…" }]`, ECS `valueFrom` included), where the sensitive signal is the sibling `name` literal rather than a key (#3870, #3787, thanks @breken-ai).
- Fix: `graphify watch` now serializes concurrent rebuilds on Windows via `msvcrt` byte-range locking instead of a no-op lock, closing a WinError 32 race between overlapping rebuilds; the POSIX `fcntl` path is unchanged (#3883, #3881, thanks @harshaygadekar).
- Fix: C# type references no longer collect a named tuple's element *names* as type references (`(int Count, string Name)` recorded a bogus ref to `Count`/`Name`); only the element types are referenced (#3877, #3796, thanks @KaiyiQuan).
- Feature: JSX component usage now produces `calls` edges — `<MyButton/>` and `<_Row/>` link to the component, while lowercase DOM tags (`<div>`) and member tags (`<Nav.Item>`) are conservatively skipped, so React component graphs capture render relationships (#3855, #3854, thanks @sinangumuskabak-sys).
- Fix: absolute Python imports that spell the scan root's own nested namespace (`from Company.Apps.Team.lib import x` when the scan root is `Team/`) now resolve to the local module by projecting the namespace prefix onto the scan-root layout (#3867, #3843, thanks @nikhilsaxena04).
## 0.9.69 (2026-09-26)
- Feature: five language extractors gained structural depth — **OCaml** classes now emit their methods (via the `method` relation) and instance variables (#3838, thanks @rajatnagda45); **Elixir** `defprotocol`/`defimpl` are extracted as containers holding their functions, with a same-file `implements` link (#3839, thanks @rajatnagda45); **Fortran** derived-type `contains` blocks link type-bound procedures to the type, resolving the `=> impl` target (#3840, thanks @rajatnagda45); **Julia** macro definitions and `@enum` types are extracted, including valued (`red = 1`) and typed (`Color::UInt8`) enum forms, with members using the `case_of` relation (#3841, thanks @rajatnagda45); **Kotlin** annotations (class/function/property, use-site targets) and `val`/`var` primary-constructor properties now produce edges (#3848, #3842, thanks @nikhilsaxena04).
- Fix: a TypeScript "solution" `tsconfig.json` that only carries `references` (no `paths` of its own) now resolves path aliases declared in the referenced project configs, so alias imports in a `tsc -b` layout no longer dangle (#3753, #3745, thanks @abhay-codes07).
- Fix: extraction now skips the process pool up front when the `spawn` start method can't re-import `__main__` (stdin, `python -c`, REPL, embedded callers), falling back to a correct sequential run instead of a wall of `BrokenProcessPool` tracebacks (#3754, #3669, thanks @abhay-codes07).
- Fix: entity deduplication preserves a genuine pre-existing self-loop (a recursive call, a self-referential FK) while still dropping a self-loop newly created by a merge (#3825, thanks @Abhirup0).
- Fix: `ingest` classifies a URL by its parsed host, not by text anywhere in the URL, so `example.com/article-about-youtube` is no longer mistaken for a YouTube link; subdomains and `youtu.be` still match, path-based extension detection is unchanged (#3831, thanks @L4XB).
- Fix: `graphify update` on a destination outside the scan root no longer leaks a stat-index cache into the corpus — the incremental detection path now forwards `cache_root` like the fresh-scan path (#3850, #3847, thanks @ayushcodes10).
- Fix: manifest duplicate-key collapse breaks ties by last-seen time instead of arbitrary iteration order, so a stale duplicate can no longer win and under-report changed files; also re-anchors foreign-platform absolute keys and normalizes `..`/`.` segments so more duplicates actually collapse (#3781, #1964, thanks @ayushcodes10).
- Docs: a community-health and contributor-guide overhaul — new `CONTRIBUTING.md`, `CODE_OF_CONDUCT.md`, and `RELEASING.md`, refreshed issue/PR templates, and corrected factual drift in `SECURITY.md` (supported version, network/XSS boundaries), `ARCHITECTURE.md` (shared-state), and `AGENTS.md` (scoped-query workflow) (#3845, thanks @nikhilsaxena04).
## 0.9.68 (2026-09-25)
- Security: the `/graphify add ... --watch` reference no longer passes the raw, agent-substituted `INPUT_PATH` placeholder unquoted into a shell command (`… -m graphify.watch INPUT_PATH`), where a scan root containing `$(…)`, backticks, or `;` could execute — a follow-on to the Step 1 fix. The watcher now reads the trusted `graphify-out/.graphify_root` that Step 1 resolves, so there is no path to substitute (#3742, #3642, thanks @ayushcodes10). The identical placeholder still appears in the Aider/Devin monolith `--watch` snippet and is tracked separately.
- Fix: incremental updates (`update`/`extract --code-only`/`watch`) now preserve a cross-file `imports`/`calls`/`uses` edge whose target symbol lives in an unchanged file — the symbol-resolution facts pass widens its target index with the read-only resolution context, so re-extracting one file no longer silently drops its edges into the rest of the graph; a genuinely removed edge is still pruned (#3812, #3776, thanks @hopstreax).
- Fix: C# type references (`inherits`/`implements`/parameter/return/base types) no longer resolve to a same-named non-type node — an enum member, property, field, or method sharing a type's name is excluded from the type-definition index, so a class inherits from the real base rather than a stray member; resolution of partial classes without a `contains` edge is restored (#3815, #3795, thanks @hopstreax).
- Fix: dedup now keeps the node with a real source location over a richer but source-less one when merging duplicates, so a merged entity points at real code instead of an inferred/external stub; the loser's attributes are still folded onto the survivor (#3786, #3775, thanks @ayushcodes10).
- Fix: Maven `pom.xml` ingestion resolves a dependency's inherited `groupId`/`version` from the local `<parent>` block and substitutes `${property}` / `${project.*}` placeholders (offline, one level), so previously-dangling `depends_on` edges now connect (#3823, #3806, thanks @chiliec).
- Fix: the PYTHONHASHSEED re-exec (0.9.67) now waits for and propagates the child's exit code on Windows instead of returning early — `os.execvpe` is a true process replacement only on POSIX; on Windows it spawned a new process and let the parent race ahead unpinned, crashing `update`/`extract`/`cluster-only`/`label`. The Windows path now spawns via `subprocess.run` and exits with the child's status; the POSIX path is unchanged (#3816, #3799, thanks @sinangumuskabak-sys).
- Fix: Terraform secret redaction now also covers a secret-named `variable` default and `output` value — the secret's name lives in the block label, so the literal sat under the generic `default`/`value` key that the key-name check never flagged (`variable "db_password" { default = "…" }` reached `graph.json` verbatim). Redacted when the label names a secret or the block sets `sensitive = true` (#3817, #3644/#3762 follow-up, thanks @breken-ai).
- Fix: fixed-format COBOL that carries a sequence NUMBER in columns 1-6 (not blanks) is now detected as fixed-format — previously it was misread as free-format, the sequence digits stayed in the code, and every paragraph and `PERFORM` edge was silently dropped, leaving only the file and program nodes (#3813, thanks @abhay-codes07).
- Fix: `PERFORM A THRU/THROUGH Z` now links both endpoints of the range, not just the entry paragraph, so the range-end no longer lacks an inbound `calls` edge; a dangling THRU target is skipped rather than fabricated (#3814, thanks @abhay-codes07).
- Fix: `GRAPH_REPORT.md`'s Knowledge Gaps section only offers "undocumented components" as an explanation for an isolated node when the graph actually has a semantic layer (a document/paper/image node an LLM extracted meaning from) — a code-only graph no longer lists a possibility it can never have (#3828, #3801, thanks @ayushcodes10).
## 0.9.67 (2026-09-23)
- Fix: the PYTHONHASHSEED determinism pin (0.9.66) now re-execs via `python -m graphify` instead of replaying `argv[0]`, fixing a Windows regression where `update`/`extract`/`cluster-only`/`label` failed to re-launch through the console-script `.exe` launcher (#3780, thanks @ayushcodes10).
- Feature: PHP closures are now extracted — an anonymous `function(){…}`, an arrow `fn()=>…`, or a closure passed as an argument now produces a node and its inner calls are captured, in all positions including file scope. Route-definition closures get a semantic `VERB /path` name (composing nested `group()` prefixes); other closures get a stable per-scope ordinal (#3461, #3409, thanks @nikhilsaxena04).
- Fix: absolute Python package imports (`import pkg.sub`, `from pkg.sub import x`) now resolve to the local package/module node within the scan root, reusing the canonical resolver (bounded walk, PEP 420 namespace handling); an ambiguous module name across scanned trees fails closed rather than binding arbitrarily (#3729, thanks @Ha1baraA11).
- Fix: a package/module name collision (`pkg/` package alongside a `pkg.py` module) no longer produces a phantom import cycle — the spurious provisional edge is retracted while genuine package-init and submodule edges are preserved (#3784, #3777, thanks @hopstreax).
- Fix: Terraform secret redaction now also covers secrets nested inside list values (`configs = [{ password = "…" }]`), not just maps (#3762, #3644 follow-up, thanks @abhay-codes07).
- Fix: `export` no longer rewrites unchanged wiki/Obsidian pages on every run — a page whose content is identical is left untouched (stable mtimes, clean git/Obsidian sync), while changed and new pages still write and orphaned pages are still swept (#3760, #3060, thanks @abhay-codes07).
## 0.9.66 (2026-09-22)
- Feature: five new language extractors — **COBOL** (`.cbl`/`.cob`/`.cobol`/`.cpy`; programs, paragraphs, `PERFORM`/`CALL`/`COPY`, pure-regex, no new dependency) (#3713, thanks @Abdul535), **VB.NET** (`.vb`; case-insensitive types/methods, `Inherits`/`Implements`/`Handles`) (#3717, thanks @Abdul535), **R** (`.r`/`.R`; assignment-form function defs, `library`/`source`, S4/R6 classes) (#3715, thanks @Abdul535), **Solidity** (`.sol`; contracts/interfaces/libraries, `is` inheritance, imports, modifiers) (#3716, thanks @Abdul535), and **Erlang** (`.erl`/`.hrl`/`.escript`; modules, functions by arity, behaviours, local + remote `foo:bar()` calls) (#3714, thanks @Abdul535). The R and Erlang grammars ship via the `r`/`erlang` extras (they have no standalone PyPI wheel); Solidity and VB.NET have their own extras.
- Feature: Go interface method requirements now resolve to the interface, and their parameter/return types emit `references` edges (#3672, #3737, thanks @rajatnagda45, @oleksii-tumanov).
- Feature: a Rust `self.method()` call resolves across files for simple generic impls (`impl<T> Foo<T>`) (#3653, thanks @oleksii-tumanov).
- Fix: `graph.json` is now deterministic across runs — `update`/`extract`/`cluster-only`/`label` pin `PYTHONHASHSEED` via a one-time re-exec, so hash-seed-sensitive community detection (Leiden/Louvain) produces identical output run-to-run and matches hook-triggered rebuilds (#3743, #3641, thanks @ayushcodes10).
- Fix: a same-relation edge collision now keeps the higher-confidence edge (EXTRACTED over INFERRED) instead of resolving by arrival order (#3711, thanks @shobhitagnihotri69).
- Fix: a spec-conformant method-node duplicate (dropped class segment / leading dot) is now deduplicated onto its canonical AST node, gated on a method-shaped label and a single unambiguous candidate (#3719, #3705, thanks @shobhitagnihotri69).
- Fix: intra-module Go `imports_from` edges now repoint onto the imported package's real file nodes instead of dangling at a `go_pkg_` sink; external/stdlib imports stay external (#3748, thanks @carterko23).
- Fix: Cargo introspection discovers a `Cargo.toml` in a subdirectory when none sits at the scan root (e.g. Tauri's `src-tauri/`), degrades gracefully instead of aborting the whole extraction when no manifest is found, and resolves `workspace = true` inherited dependency identity from `[workspace.dependencies]` (#3740, #3739, thanks @ayushcodes10; #3734, thanks @oleksii-tumanov).
- Fix: MCP `get_node`/`get_neighbors` now accept the node identifier under `node_id`/`id`, not only `label`, so agents that spell the argument differently no longer get a missing-argument error (#3725, thanks @ahm3dwasim).
- Fix: `graphify install` no longer corrupts line endings (writes preserve the file's existing EOLs instead of rewriting to CRLF on Windows) and re-install is properly idempotent (a marker-bounded replace instead of a bare substring check) (#3741, #3668, thanks @ayushcodes10).
- Fix: community labeling retries a valid-but-truncated LLM response and reports how many communities kept a structural fallback name, instead of silently leaving them unlabeled (#3708, thanks @Ha1baraA11).
- Fix: a stale `.graphify_root` marker (moved/deleted/symlink-loop target) is now ignored with a fall-back to the graph's directory, and the marker value resolves to an absolute path when `GRAPHIFY_OUT` is a shared absolute directory (#3707, thanks @Ha1baraA11; #3735, #3375, thanks @ayushcodes10).
## 0.9.65 (2026-09-20)
- Security: the `svg`/`all` extras now floor Pillow at `>=12.3.0` for CVE-2026-54058 (Pillow was pulled in transitively via matplotlib). Note: Pillow 12.3.0 dropped its glibc<2.27 cp310 Linux wheel, so a very old-glibc Python 3.10 host with the `svg`/`all` extra builds Pillow from sdist (#3698, thanks @viral-antuit).
- Feature: a Go interface's method requirements (`type Foo interface { Bar() }`) now attach to the interface node, so calls resolve to them (#3672, thanks @rajatnagda45).
- Feature: a Swift protocol's method requirements (`protocol P { func f() }`) now attach to the protocol node (#3673, thanks @rajatnagda45).
- Fix: Java enum body members (methods/constructor/fields after the constants) now attach to the enum, not the file, and intra-enum calls resolve (#3674, thanks @rajatnagda45).
- Fix: a Verilog module instantiation now links to the module's local definition instead of a phantom duplicate; a genuinely external module stays a sourceless stub (#3675, thanks @rajatnagda45).
- Fix: JS/TS `let`/`const` bindings are now scoped to their own block rather than the whole function, so a block-local binding no longer suppresses a genuine `indirect_call` edge elsewhere in the function; `var` stays function-scoped (#3688, thanks @ayushcodes10).
- Fix: the incremental rebuild no longer purges AST nodes it just reported as fail-closed "kept" — the eviction pass re-checks the kept set, so a moved-file/symlink layout can't deadlock the shrink guard into refusing every update (#3697, #3695, thanks @hopstreax).
- Fix: `graph.html` no longer crashes vis-network with a stack overflow on large graphs — nodes are seeded on a spiral before physics runs so overlap-avoidance can't blow the layout recursion (#3699, thanks @sanjaiyan-dev).
- Fix: node and edge tooltips now show special characters literally (C++ templates like `vector<int>`, generics, `&`, quotes) instead of raw HTML entities, while the HTML sinks that need escaping keep it (#3686, #3664, thanks @hopstreax).
- Docs: repository links now point at `Graphify-Labs/graphify` instead of the old account (including in generated wiki output), translated READMEs use the current logo, GitHub issue/PR templates were added, and the Enterprise link was corrected (#3692, #3694, #3693, thanks @Abdul535).
## 0.9.64 (2026-09-18)
- Feature: Terraform block attributes (`ami`, `instance_type`, `cidr_block`, tags, and the like) are now preserved on the resource/data/module node and are queryable and searchable, with typed values (bool/number/list/map) and nested blocks kept separate from direct attributes. Secret-named attribute values (`password`, `*secret*`, `*token*`, `*_key`, connection strings) are redacted before they reach `graph.json` or the model, so a hardcoded credential in a `.tf` file does not leak (#3644, thanks @hopstreax).
- Feature: JavaScript inside an inline `<script>` block of a PHP file is now indexed as JS (functions and calls) under the PHP file node, mirroring the Vue/Svelte embedded-script handling, with source lines mapped back to the real file positions (#3627, #2320, thanks @ayushcodes10).
- Feature: a Rust `self.method()` call now resolves across files for simple generic impls (`impl<T> Foo<T>`), extending the split-`impl` resolution to generic types while staying fail-closed on bounded, `where`, trait, and concrete-instantiation shapes (#3653, thanks @oleksii-tumanov).
- Feature: a Kotlin `Receiver.method()` call now resolves across files when the receiver's `object`/class (or its `companion object`) is declared in another file (#3598, #1698, thanks @ayushcodes10).
- Feature: a C++ scope-qualified static call `Foo::bar()` now resolves to a definition in another translation unit even when it survived extraction only as a qualified-label node (#3613, #2348, thanks @ayushcodes10).
- Fix: an npm package subpath import (`import x from "pkg/sub"`) now resolves to the same node as the bare package import, so a dependency no longer fragments into separate external nodes (#3601, thanks @ayushcodes10).
- Fix: `export` now detects a stale `.graphify_analysis.json` sidecar and reconstructs communities from `graph.json`, comparing partition structure rather than just the node-id set, so a stale sidecar can no longer override fresh `update` data (#3557, #2386, thanks @ayushcodes10).
- Fix: incremental `update` now reconciles Markdown-family links across `.md`/`.mdx`/`.qmd`/`.skill`, and a document whose parse fails no longer has its authored links pruned (#3655, thanks @oleksii-tumanov).
- Fix: extraction diagnostics now separate external references (out-of-corpus `$ref`/import targets) from genuinely dangling edges, so an expected external reference is no longer reported as a broken endpoint (#3590, thanks @DevChiniwala).
## 0.9.63 (2026-09-16)
- Feature: Elixir `alias`/`import`/`require`/`use` targets now resolve onto the module's `defmodule` node across files, so the internal module dependency graph is no longer dropped as dangling. Only top-level modules are indexed (a nested `defmodule`, labeled with its bare inner name, cannot capture an unrelated `use <Name>` from another file), and a same-file reference is left unresolved so it cannot clobber the structural `contains` edge (#3603, thanks @ayushcodes10).
- Feature: a Rust `self.method()` call now resolves to a method defined on the same type in another file (the common split-`impl`-block layout), pooling methods across every `impl` of one type and refusing to link when two unrelated types share a bare name (#3602, thanks @ayushcodes10).
- Feature: a Ruby member call `obj.foo` on a known-type receiver now resolves to a method `foo` inherited from a superclass, including across files, using the same conservative promotion as the implicit-self resolver — a single owning class, matching method kind, and one unambiguous ancestry chain, or it stays dangling (#3585, thanks @oleksii-tumanov).
- Fix: every edge endpoint written to `graph.json` is now a declared node. An `imports`/`imports_from`/`re_exports` edge to an external module (stdlib, a third-party dependency) mints a typed external stub node instead of leaving a dangling endpoint that loaders materialise as an attribute-less phantom, and a cross-repo merge unifies the same external module into one global node instead of fragmenting it per repo; sourceless external call targets stay suppressed (#2873, #2878, thanks @AromalBiju1).
- Fix: a Markdown code-span mention edge now survives an incremental rebuild instead of being pruned as an unauthored link, and a dotted span (`Foo.bar`) resolves using its qualifiers as evidence, rejecting misleading matches such as `time.sleep` or `pyproject.toml` (#3587, thanks @AstroMined).
## 0.9.62 (2026-09-15)
- Feature: Terraform module calls with a literal local `source` (`./…` or `../…`) now resolve to a directory-scoped module node, exposing the caller→implementation topology (e.g. environment → application → base); remote and registry sources and source expressions are left unresolved and never fabricate a target. After upgrading an existing graph, run `graphify update .` once to regenerate Terraform ids and topology (#3571, thanks @vstepko).
- Feature: a bare implicit-self call in a Ruby method now resolves up the inheritance chain to a method defined on a superclass, including across files. The promotion is conservative — it only upgrades an existing inferred edge when a single owning class, a matching method kind, and one unambiguous ancestry chain all agree, and it bails on mixins, reopened classes, and dynamic superclasses rather than guess (#3569, thanks @oleksii-tumanov).
- Feature: an inline code span in a Markdown file (e.g. `` `Widget.render` ``) now emits a `references` edge to the symbol it names, but only when exactly one corpus callable matches — generic words and ambiguous names link to nothing, so docs join the graph without a false-edge firehose (#3562, thanks @AstroMined).
- Fix: a prose file whose stem is the bare plural "tokens" (e.g. `TOKENS.md`) is no longer excluded as a credential dump; singular `token.md` and other keywords' bare plurals still exclude, and content-based secret detection is unchanged (#3527, thanks @HARSHAVARDHAN-RAJU5).
- Fix: a call whose only resolution target is a sourceless external stub no longer emits a spurious `calls` edge, so an unresolved external name stops accruing a phantom god-node (#3156, thanks @DevChiniwala).
- Fix: calls made inside a module-level anonymous closure (an IIFE or a top-level callback) are now attributed to the file node instead of being dropped (#3124, thanks @ayushcodes10).
- Fix: cross-repo merge now respects C# namespace/`using` scoping — a parked member call binds to a same-named type in another repo only when the caller's namespace or an in-scope `using` resolves it, so a third-party receiver no longer binds to an unrelated repo-local type (#3360, thanks @DevChiniwala).
- Fix: a bare Lua `require("mod")` statement (no assignment) now emits an `imports` edge, including the chained `require("lazy").setup({})` LazyVim idiom, so Neovim configs graph correctly instead of coming out as disconnected files (#3320, thanks @A-Levin).
- Fix: the Claude/CodeBuddy PreToolUse guard hooks now carry a timeout, so a wedged filesystem stat can no longer stall a tool call indefinitely (#3314, thanks @ayushcodes10).
- Fix: id-prefix reconstruction is now Unicode-aware — a non-Latin path segment is slugified with the canonical normalizer instead of being dropped, so files under non-ASCII directories keep stable ids (#3559, thanks @ayushcodes10).
- Fix: `cohesion_score` now excludes self-loops (e.g. a recursive call) from the edge count, so a community's score stays within 0..1 instead of being inflated above 1 (#3558, thanks @jordanalexanderp18-rgb).
- Fix: the incremental watch and rebuild paths now route the `graph.json` replace through the shared atomic-write fallback at both write sites, so a cross-drive or locked-file replace can no longer corrupt the graph mid-update (#3555, thanks @ayushcodes10).
- Fix: a semantic-cache group whose stored `source_file` is malformed is now recovered by basename instead of being dropped, with the recovered group's `source_file` corrected before it is written back; an ambiguous basename stays skipped (#3553, thanks @ayushcodes10).
- Fix: an exported destructuring declaration (`export const { a, b } = obj`) now emits one node per exported name instead of a single combined node, so each name resolves and cross-file imports of a split export link correctly (#3552, #2604, thanks @ayushcodes10).
- Fix: a community made up entirely of file nodes is now counted as thin rather than invisible in the report, so it is no longer silently dropped from the community summary (#3549, #3548, thanks @ayushcodes10).
- Fix: the git guard-hook shell gates now resolve the output directory from `GRAPHIFY_OUT` instead of a hardcoded `graphify-out/`, so a renamed output directory no longer makes the hooks silently no-op (#3546, thanks @breken-ai).
## 0.9.61 (2026-09-12)
- Fix: `graphify.serve` now imports cleanly on Python 3.12 and 3.13. The `chinese` extra pins `jieba-py` from 3.12 onward (0.9.60 mistakenly kept the old `jieba` until 3.14, and its invalid regex escapes are a hard error on 3.12+), and the jieba import now suppresses the tokenizer's `SyntaxWarning` regardless of message or line so it never escalates under `-W error`.
- Fix: the git hook's rebuild-root guard now rejects a symlink-loop or dangling `.graphify_root` on Python 3.13, whose `Path.resolve()` no longer raises on a loop — the saved root must resolve to a real directory inside the repo before it is adopted.
## 0.9.60 (2026-09-12)
- Fix: atomic writes now fall back correctly on Windows `WinError 17` (cannot move to a different drive), not just `PermissionError` — a shared `os_replace_with_fallback` copies through a temp in the target directory and restores the original if the swap fails, keeping install/export/cache writes crash-safe (#3508, thanks @ayushcodes10).
- Fix: files that could not be classified into any language or type are now surfaced (a count and top extensions in the console and GRAPH_REPORT) instead of vanishing from a "successful" run; noise and ignored paths are unaffected (#3511, thanks @ayushcodes10).
- Fix: a C or C++ header with no trailing newline no longer trips a tree-sitter syntax error — a newline is appended before parsing when the source lacks one (#3513, thanks @imanolpg).
- Fix: Office and Workspace document sidecars converted into `graphify-out/converted/` are no longer dropped when `graphify-out/` is gitignored — the tool stops ignoring its own output (#3504, thanks @ayushcodes10).
- Fix: a call inside an exported function (`export function f(){ g() }`, `export const f = () => g()`) now resolves, including calls to aliased imports (`import { x as y }; y()`) (#3346, thanks @abhay-codes07).
- Fix: a package.json `exports` map is now resolved by importer platform with the runtime condition preferred over `types`, so cross-package edges no longer drop to a non-existent `.d.ts` (#3487, thanks @dajiaohuang).
- Feature: Python 3.14 is now supported — optional dependencies without 3.14 wheels are gated to drop-in replacements (`jieba` to `jieba-py`, `graspologic` to `graspologic-native`) by interpreter version, and vulnerable dependency floors (setuptools, pypdf, yt-dlp) were raised (#3490, thanks @taazbro).
## 0.9.59 (2026-09-12)
- Fix: an incremental rebuild no longer drops cross-file `concept` nodes from files it didn't touch — global dedup during a merge now protects existing nodes from untouched files instead of collapsing same-labeled ones across them (#3477, thanks @hopstreax).
- Fix: extraction now falls back to sequential in-process work when the process pool cannot start (e.g. POSIX semaphore exhaustion), instead of aborting the whole run (#3497, thanks @curtismu7).
- Performance: Python symbol resolution is roughly 47% faster — path resolution is memoized, each file parses once across both resolution passes, and the tree walk is iterative rather than recursive; extraction output is unchanged (#3500 / #3501 / #3502, thanks @abhay-codes07).
- Fix: `graphify explain` now accepts a `path::Symbol` form to disambiguate a symbol that shares its name with its file, and the ambiguity hint now shows a form the resolver actually accepts (#3485, thanks @ayushcodes10).
- Fix: the git hook now keeps its rebuild root inside the repository — a committed `.graphify_root` pointing outside the worktree is ignored and falls back to the repo top, so a checked-in marker can't steer the hook to scan or write outside the tree (#3265, thanks @ayushcodes10).
## 0.9.58 (2026-09-10)
- Fix: a call to a Python function defined nested inside another function now resolves to that inner definition per lexical scope, instead of leaking to a same-named function elsewhere; direct recursion is preserved as a self-loop (#3410, thanks @hopstreax).
- Fix: submodule imports inside a PEP 420 namespace package (a directory with no `__init__.py`) now resolve to the target module instead of being dropped (#3429, thanks @flaukowski).
- Fix: a bare-name import of a module sitting next to the importing file (a flat script dir with no package) now resolves to that sibling — matching CPython's `sys.path[0]` behavior — without over-resolving a genuine third-party name (#3430, thanks @hopstreax).
- Fix: a PHP `use Foo\Bar as Baz;` import keys its local binding on the alias, so later `Baz` references resolve to the real class, and distinct aliased same-named classes stay separate (#3421, thanks @ayushcodes10).
- Fix: a directory literally named `out` is skipped as build output only when there is build-output evidence, so a real source `out/` is no longer silently dropped (#3347, thanks @abhay-codes07).
- Fix: a shell script invoked in exec position through a variable path (`"$SCRIPT_DIR/foo.sh"`) now resolves to the target when the variable holds a constant, matching the existing `source` handling (#3416, thanks @edwardselby).
- Fix: added the missing `Iterable` import in build.py so its type annotations resolve (they were an undefined name) (#3462, thanks @xiongjianxu).
- Fix: `graphify global add` no longer infers an empty repo tag for a path like `/tmp/graph.json`; it degrades to a sensible non-empty tag via the same helper `merge-graphs` uses (#3464, thanks @xiongjianxu).
- Feature: SQL extraction now emits index nodes for `CREATE [UNIQUE] INDEX`, linked to the table they index (#3467, thanks @L4XB).
- Fix: a TypeScript `export *` re-export no longer resolves a name to a same-named interface *method* — only module-level exports are candidates (#3436, thanks @L4XB).
- Feature: Rust module-level `static` and `const` declarations are now extracted as nodes (#3471, thanks @L4XB).
- Fix: an incremental rebuild no longer drops a node's `rationale`/`summary` when a semantic node's absolute `source_file` collides with its AST twin's relative path during dedup — source paths are normalized first (#3472, thanks @hopstreax).
- Fix: `graphify install` no longer aborts when the always-on registration target is unwritable (read-only or symlinked config); it skips that step with an actionable warning and still installs the skill (#3474, thanks @dajiaohuang).
- Fix: community labelling falls back to an installed `claude` CLI resolved at run time instead of pinning a path that expires (e.g. under snap/nvm), so labelling keeps working across updates (#3475, thanks @ktsang622).
- Fix: the `all` extra now includes `psycopg[binary]`, so `pip install 'graphifyy[all]'` provides the postgres driver (#3482, thanks @L4XB).
## 0.9.57 (2026-09-09)
- Fix: Rust module-level `static` and `const` declarations (and associated consts inside an `impl`) are now extracted as nodes with a `contains` edge and a reference to their declared type — neither node type had a branch, so a constant only ever reached the graph through files that referenced it (#3471, thanks @sortakool).
- Fix: an incremental rebuild no longer wipes cross-file project AST nodes — re-extracting one `.csproj`/`.sln` was dropping package/framework nodes of a *referenced* project (whose stub carried the referenced file's `source_file`); the AST-replacement set is now derived from the files actually extracted (#3411, thanks @hopstreax).
- Fix: when duplicate nodes merge, the richer (more complete) node is now kept as the survivor and the losers' non-empty fields are folded in, instead of a shorter-id passing mention winning and dropping content (#3372, thanks @abhay-codes07).
- Fix: a C# generic call site with explicit type arguments — `Get<int>(...)`, unqualified or through `this` — now resolves to the method definition instead of capturing `Get<int>` as the callee and failing to match (#3406, thanks @abhay-codes07).
- Fix: `this.X = function` / `this.X = () => …` members are now captured in every enclosing-function form (function expressions, arrows, IIFEs, callbacks), not just function declarations (#3408, thanks @abhay-codes07).
## 0.9.56 (2026-09-07)
- Fix: Rust trait method declarations (signature-only, and default-bodied) are now extracted as nodes — trait bodies were never walked, so both were silently dropped; a trait-declared method stays a distinct node from its impl definition (#3366, thanks @santoshpy).
- Fix: the atomic-write temp filename is now bounded, so exporting to a path near the Windows MAX_PATH / 255-char component limit no longer fails with a temp-file `FileNotFoundError` (#3351, thanks @hopstreax).
- Fix: when graphify's git hook decides to skip (no graph, rebase/merge/worktree, `GRAPHIFY_SKIP_HOOK`), it no longer terminates the whole hook — the block runs in a subshell so chained hooks and later steps still execute (#2986, thanks @abhay-codes07).
- Fix: JS/TS `@/` project-root alias imports now resolve when no tsconfig `paths` mapping exists — an explicit mapping still wins, `@scope/pkg` packages are untouched, and only existing files are linked (#3357, thanks @hopstreax).
- Fix: an MCP `query_graph` seeded on a node with only incoming edges (e.g. a leaf function that is called but calls nothing) now traverses undirected, so it reaches that node's callers — matching the CLI's behavior (#3373, thanks @kuchtgpt-svg).
- Fix: a member call whose method name collides with a language builtin (e.g. `.open`, `.get`, `.map`) is now handed to cross-file resolution instead of being short-circuited, so a genuine user-defined method with that name links — without fabricating an edge to an actual builtin (#3381, thanks @ayushcodes10).
- Fix: when the rebuild watchdog times out, spawned extraction workers are now killed before it exits (both the SIGALRM and the `os._exit` fallback paths) instead of being orphaned (#3396, thanks @ayushcodes10).
- Fix: Node subpath imports (`#services/foo` via a `package.json` `imports` map, including `*` wildcards and condition objects) now resolve to the mapped file — previously every `#`-specifier resolved to nothing (#3382, thanks @julien-e).
- Fix: TS import-type normalization no longer scans every type-argument range per match (an O(matches × ranges) blowup that pinned extraction at 100% CPU on large mixed files); the filter is now a sorted-index lookup with byte-identical output (#3359, thanks @Sagexd08).
- Fix: Dart extraction now stamps `source_location` (1-based `L{line}`) on nodes and edges, matching every other extractor, instead of leaving it null (#3365, thanks @ayushcodes10).
## 0.9.55 (2026-09-05)
- Fix: a module docstring preceded by a leading comment (shebang, `# -*- coding -*-`, or a license header) is now extracted instead of silently dropped — comments are skipped when locating the first statement, across module/class/function bodies (#3312, thanks @ayushcodes10).
- Fix: two Python definitions whose ids differ only by leading underscores (e.g. `_get_connection` and `get_connection`) no longer collide and silently drop one — private/dunder members are salted while a unique public member keeps its plain id, so existing graphs are unaffected (#3302, thanks @ayushcodes10).
- Fix: a ghost node whose `source_file` was set to the doc that merely *mentions* a file is now merged onto the real file node via a conservative unique-label fallback, so it stops surviving every rebuild (#3344, thanks @leninherrera94).
- Fix: type-use edges (`inherits`/`implements`/`references`) are no longer fabricated from a symbol that owns no node — e.g. a class nested in a named function, or an abstract method signature — closing a path that leaked phantom `ext_*`-sourced edges (#3356, thanks @VasuBansal7576).
- Fix: `graphify watch`/`update` in no-cluster mode now disambiguates same-basename file labels (e.g. two `errors.ts`) the same way the clustered build does, instead of collapsing them to a bare basename (#3363, thanks @VasuBansal7576).
- Fix: a named re-export that forwards an imported binding — `import { x } from './a'; export { x }`, `export { x } from './a'` barrels, alias renames, and `export *` chains — now resolves to the original definition instead of dangling on a fabricated barrel symbol; ambiguous or unresolved origins are left untouched (#3358, thanks @VasuBansal7576).
- Fix: cross-file entity nodes typed only by their file extension (e.g. the same handle mentioned across several docs) now merge like `concept` nodes, gated by an entropy + provenance guard and an own-file-node exclusion so distinct entities stay separate (partially addresses #296, thanks @yotamleo).
- Fix: a runtime dynamic `import(...)` is no longer blanked by the TS type-argument normalizer — masking parses first and only rewrites `import(...)` inside genuine call type-argument positions adjacent to a grammar error, and the whitespace form `import (...)` is recovered, so real module dependencies survive (#3210, thanks @zfaustk).
- Fix: JSON-config extraction no longer turns every array value into an `extends` (inheritance) edge or emits a label-level self-loop dependency — only a real top-level `extends` yields an inheritance edge, and dependency edges are sourced from the manifest node with a namespaced target (#3330, thanks @pranavshipit).
- Fix: the claude-cli backend now tolerates a diagnostic line printed before the JSON envelope (e.g. an MCP client notice), recovering the response instead of discarding already-generated output; genuine non-JSON still errors (#3330, thanks @pranavshipit).
- Fix: a code-only `graphify watch` rebuild no longer clears the pending semantic-update flag, so a queued re-extraction of changed docs/papers/images is not silently dropped (#3294, thanks @theSatvik).
- Fix: the MCP `prs` tools no longer hang the stdio transport on Windows — the PR subprocesses run with stdin detached, and an explicit `--repo` is passed positionally to `gh repo view` (#3318, thanks @hopstreax).
## 0.9.54 (2026-09-05)
- Fix: a Python type reference to a name imported from another module now resolves to that module's definition — the sourceless stub is repointed onto the exact imported symbol (not a bare-name match), so two same-named types in different modules bind to their own (#3252, thanks @hopstreax).
- Feature: `graphify merge-graphs` now resolves a member call whose receiver type is defined in another repo — the call is parked at extraction and linked at merge time only on a single unambiguous cross-repo type + method match, composing with the cross-repo type link (#3152, thanks @xiongjianxu).
- Fix: the `definition_file` node attribute (the merged decl/def implementation site) is now stored portably — relative to the scan root with canonical separators, matching `source_file` — across graph.json, the AST cache, the watch/incremental path, and direct extract, so a graph is byte-stable across machines (#3223, thanks @abhay-codes07 and @hopstreax).
- Fix: `god_nodes` now honours `exclude_hubs_percentile`, so `--exclude-hubs` actually affects god-node ranking (not just clustering); the default output is unchanged (#3205, thanks @abhay-codes07).
- Fix: byte-identical duplicate reference edges are now collapsed when the extractor emits them, so the graph-health diagnostic no longer flags duplicates that the build was already discarding (#3251, thanks @abhay-codes07).
- Fix: two query results saved in the same second whose questions share a 50-character prefix no longer overwrite each other — saved memory-doc filenames now carry a short unique suffix (#3301, thanks @daichiyasunami-vottia).
- Fix: a graph.json carrying top-level hyperedges keeps them on export — the loader re-attaches the top-level `hyperedges` slot that `node_link_graph` otherwise drops, so hand-edited or externally written hyperedges survive re-export (#3321, thanks @yiheng-kkk).
- Feature: the query scorer now ranks a match on a node's `rationale` attribute as its own tier, between an exact label match and a source-path match — adding recall for rationale-only hits without inflating the exact-match coverage score (#2293, thanks @andytsai821201-spec).
- Fix: `graphify install` now writes the `.graphify_version` stamp atomically (temp file + `os.replace`), so a crash mid-write cannot leave a truncated version file (#3286, thanks @drmikecrypto).
- Fix: the Obsidian/canvas export now writes `graph.canvas` and vault notes atomically, preventing a concurrent reader (or a git mmap hash) from seeing a half-written file (#3282, thanks @drmikecrypto).
- Fix: on Python 3.10 a missing `tomli` no longer silently drops every `pyproject.toml`/`Cargo.toml` from the graph — `tomli` is now a runtime dependency for pre-3.11, and the manifest parser surfaces a visible per-file error if it is ever absent (#3283, thanks @drmikecrypto).
## 0.9.53 (2026-08-30)
- Fix: a batch of cross-language inheritance-edge corrections (thanks @Synvoya): JavaScript `class X extends Y` now emits an `inherits` edge (#1790); PHP interfaces, enums, and traits are captured as class-like nodes with their heritage (#1791); Scala `trait` declarations become class-like nodes (#1792) and qualified `extends`/`with` bases resolve to the tail type (#1794); a qualified Kotlin supertype resolves to its tail type instead of the package head (#1793); a C# interface extending an interface is classified as `inherits`, not `implements` (#1817); and a Go interface type-set constraint no longer emits a spurious `embeds` edge (#1818).
- Feature: Robot Framework `.robot`/`.resource` files are now extracted (optional `[robot]` extra) — suites, test cases, user keywords, keyword-call edges, and resource/library imports, with case/space/underscore-insensitive keyword resolution (#3192, thanks @nshiveg).
- Fix: chat-template control tokens are now defanged by form (`<|…|>`, `[INST]`/`[SYSTEM]`) rather than an enumerated few, closing a prompt-injection gap for attacker-chosen tokens (e.g. `<|eot_id|>`); legitimate content is untouched (#3183, thanks @abhay-codes07).
- Fix: `graphify watch`/`update` now preserves an authored markdown link whose target node is still live under a different spelling, extending the #3190 reconcile without loosening the deleted-target gate (#3190, thanks @logan683).
- Fix: a semantic node is no longer silently dropped when a run did not actually re-extract the semantic tier — an unverified per-source shrink arms the shrink guard and re-queues the source next run (#3203, thanks @hopstreax).
- Fix: the git hook-guard now detects a search command only in an executed position, not when it merely appears in a comment or a quoted message, eliminating false read-nudges (#3121, thanks @abhay-codes07).
- Fix: `graphify install` now backs up a diverged `SKILL.md` before overwriting and prints an actionable warning, instead of silently clobbering user edits (#3144, thanks @abhay-codes07).
- Fix: the `GRAPH_REPORT` headline community counts now reconcile with what is actually rendered, and the knowledge-gaps threshold matches its own label (#3148, thanks @abhay-codes07).
- Fix: the wiki export now notes when a god-node relation group was truncated instead of silently showing a capped list (#3127, thanks @abhay-codes07).
## 0.9.52 (2026-08-29)
- Fix: `graphify watch`/`update` no longer silently drops a Markdown link to a semantic-backed document during a code-only rebuild — authored `[[wikilink]]` references are repointed onto the target file's representative node, preserving links to the semantic tier without resurrecting a deleted target (#3190, thanks @logan683).
- Fix: T-SQL routines that parse to tree-sitter ERROR nodes — `CREATE OR ALTER`, the `PROC` shorthand, and bracket-delimited names like `[dbo].[Get Widgets]` — are now recovered by name over a comment/string-masked copy, so they appear in the graph without commented-out or dynamic SQL fabricating nodes (#3164, thanks @egarcia74).
- Fix: `graphify --help` now lists the `prs` and `provider` commands and the export formats it actually supports (thanks @SyedFahad7).
- Fix: corrected the `deepseek-v4-flash` pricing entry and the `build_merge` docstring (which wrongly claimed it saved the graph; callers persist) (thanks @adrianengkh).
- Fix: Razor `@inject`/`@using` in `.razor`/`.cshtml` files now flow through the scope-aware C# type resolver, so an injected service type resolves to its cross-file definition; an external/undeclared type fabricates nothing (partially addresses #3187 — a bare inject of a file-scoped-namespace type still dangles, follow-up) (thanks @hopstreax).
- Fix: the MCP `prs` tools (`list_prs`, `triage_prs`, `get_pr_impact`) now surface a genuine failure as an MCP error (`isError`) instead of success text, while an empty-but-successful result is unaffected (thanks @noQbot).
- Fix: the MCP `get_node` tool now resolves a node through the same tiered resolver as `get_neighbors`, so the two agree deterministically instead of `get_node` returning an iteration-order substring match (thanks @noQbot).
- Fix: a `graphify install --project` (committed/shared) install now emits a bare `graphify` hook command resolved at run time instead of pinning the absolute interpreter path, so the committed hook no longer churns across machines; the global install still pins the absolute path (thanks @davidbhoward).
- Fix: a PHP `new Foo()` now emits a `calls` edge to the constructed class (namespaced names resolve to the last segment), completing the object-creation modeling across C# (#2998), TypeScript (#3135), and now PHP; dynamic (`new $var()`), `self`/`static`/`parent`, and unknown external constructions fabricate nothing (#3115, thanks @abhay-codes07).
- Fix: a method call through a field whose type is declared on an ancestor class now resolves from a subclass — the field-type lookup walks the inheritance chain (per-file for Java/C#, cross-file for Java/Objective-C), cycle-safe and without fabricating edges (#3151, thanks @abhay-codes07).
- Fix: the Objective-C field-to-type table is now re-keyed alongside the node-id rewrites, so `self.field` / `obj.field` method calls still resolve after `graphify update` normalizes ids (#3150, thanks @abhay-codes07).
- Fix: TypeScript type-only imports (`import type { T } from './m'`) no longer manufacture false import cycles; the type-only edge is marked and excluded from cycle detection while a mixed `import { type A, B }` keeps its real value-import edge (#3123, thanks @abhay-codes07).
- Fix: an `import(...)` used as a call type-argument (`f<typeof import('mod')>()`) no longer causes declarations after it to be dropped; the construct is normalized before parsing with source locations preserved (#3185, thanks @hopstreax).
## 0.9.51 (2026-08-28)
- Fix: the incomplete-build shrink guard now stays armed when a chunk came back hollow, unparseable, or omitting files, so a run that silently lost content can no longer overwrite the existing graph with a smaller one; a complete run and a retry-recovered chunk are unaffected, and `--allow-partial` still overrides (#3105, thanks @abhay-codes07).
- Fix: `graphify extract --force --code-only` now fully rescans code (instead of skipping unchanged files and keeping stale import/alias resolution) while still carrying the existing document/semantic tier forward (#3125, thanks @hopstreax).
- Fix: a hyperedge carried from a prior `graph.json` now has its members routed through the dedup survivor remap, so it no longer dangles when one of its members is merged away; an unresolvable member is dropped gracefully (#3102, thanks @abhay-codes07).
- Fix: the cache's atexit stat-index flush no longer recreates a `graphify-out/` tree that was deleted during the run, so a removed corpus stays removed (#2974, thanks @abhay-codes07).
- Fix: Leiden clustering canonicalizes undirected edge endpoints before sorting, so community assignments no longer drift across builds or machines from networkx yielding an edge's endpoints in a different order (thanks @ErichKinuya).
- Fix: a TypeScript/JavaScript `new Foo()` now emits a `calls` edge to the constructed class (member, chained, and generic forms), so constructor usage is visible; built-in globals like `new Map()` / `new Promise()` are not fabricated (#3116, thanks @hopstreax).
- Fix: an Elixir function whose only clause carries a `when` guard (`def foo(x) when is_integer(x), do: ...`) is now extracted, not dropped; multi-clause, multi-condition guards, and `defp` are handled (#3111, thanks @santhiprakash).
- Fix: Common Lisp node ids are now derived from the full path stem like every other extractor, so two same-basename `.lisp` files in different directories no longer collide on merge (thanks @guitelesc).
- Perf: Leiden clustering now calls the `graspologic_native` binding directly instead of importing the full `graspologic` package, avoiding its heavy import chain (umap / pynndescent / numba JIT); clustering output is unchanged, and it falls back to the `graspologic` wrapper and then NetworkX Louvain when the native binding is absent (#3104, thanks @Mohammad-Palla).
- Docs: the README now documents the git workflow for keeping the graph in sync — commits and branch switches rebuild automatically via the installed hooks, while `git pull` / `git merge` need a manual `graphify update .` (thanks @Mohammad-Palla).
## 0.9.50 (2026-08-25)
- Fix: Ruby methods whose names end in `!`, `?`, or `=` now keep distinct node ids, so `save` and `save!` (or `foo` and `foo=`) no longer collide into one node; the label keeps the raw spelling and member-call resolution still matches (#3077, thanks @hopstreax).
- Fix: a Ruby call on a qualified constant receiver (`ActiveRecord::Base.transaction`) now matches the receiver's full constant path, so it no longer binds to an unrelated lone class named `Base`; an edge is emitted only on a single unambiguous match (#3078, thanks @rohit-jsfreaky).
- Fix: a CommonJS member export wrapped in a higher-order function (`exports.x = wrap(fn)`, `module.exports.y = onCall({...}, handler)`) is now captured, reaching through the wrapper to the function it wraps without fabricating the wrapper as the export's identity (#3035, thanks @hopstreax).
- Fix: `graphify merge-graphs` now offsets each input's community ids so community 0 of one repo no longer fuses with community 0 of another; within-input structure is preserved and the original id is kept in `local_community` (#3014, thanks @santhiprakash).
- Fix: a `.graphify_root` marker written by Windows PowerShell (which prepends a UTF-8 BOM) no longer breaks hook rebuilds or silently mis-roots a scan; PowerShell now writes the marker BOM-less and every reader decodes BOM-tolerantly (#3028, thanks @rohit-jsfreaky).
- Perf: ignore-pattern evaluation no longer builds a `Path` and calls `relative_to` per pattern per file — it computes the relative path lexically in string space, parses each pattern once into a bounded process cache, and memoizes per-entry work; ignore decisions are unchanged (differential-fuzz verified) and a pattern-heavy monorepo scans dramatically faster (#2226, thanks @Azeem1985). The `**`-aware matcher was also lifted out of a per-call cache closure that leaked a reference cycle each call.
- Feature: C# and TypeScript enum members now each emit a graph node with a `case_of` edge to their enum (matching the existing Java/Kotlin/Swift enum handling), so an enum case is visible as a member; explicit and implicit values, `const enum`, and quoted TypeScript member names are all handled and no built-in types are fabricated (#3063, #3064, thanks @durmazoguzhan).
- Fix: `graphify watch` no longer re-triggers on its own reads — read-only inotify events (`opened`, `closed_no_write`, emitted by the watcher's own AST rebuild) are dropped, while close-after-write and create/modify/move/delete still trigger; a no-op on the macOS/Windows backends that never emit them (thanks @Azeem1985).
- Fix: `pip install graphifyy[postgres]` now carries the `tree-sitter-sql` grammar the introspection path needs, and a missing or ABI-incompatible grammar raises an actionable error instead of silently returning zero nodes (thanks @Azeem1985).
## 0.9.49 (2026-08-24)
- Feature: `graphify merge-graphs` now links a type declaration that two repos share — same fully-qualified namespace and name, from different repos — with a `same_type_as` edge, so a shared contract type is navigable across the repo boundary; two unrelated types that merely share a short name are not linked (#3007, thanks @durmazoguzhan).
- Feature: C# properties now emit a member node per property, so a property is visible as a class member alongside fields and methods; the property's type still emits its references edge and no built-in types are fabricated (#3006, thanks @durmazoguzhan).
- Fix: when a C/C++/Objective-C declaration and its definition merge into one node, the node now points at the definition site (the implementation body) instead of the header prototype; the pick is deterministic regardless of file order, and a declaration with no definition keeps its own site (#2990, thanks @winesoft-namjin-yun).
- Fix: a bash command substitution on the right of an assignment (`x=$(fn)`) now emits a `calls` edge like a bare `$(fn)`, while argument-position and process substitutions stay suppressed (#2978, thanks @akshitj11).
- Fix: a SQL `CREATE TABLE` wrapped in a `BEGIN`/`COMMIT` transaction is now extracted, not just top-level DDL (#2953, thanks @akshitj11).
- Feature: C# object-creation expressions (`new Foo()`) now emit a `calls` edge to the constructed type, so constructor usage is visible in the graph; built-in and out-of-corpus types are not fabricated and a qualified construction resolves against declared namespaces (#2997, thanks @durmazoguzhan).
- Feature: when a C# interface has exactly one implementing class, its methods are now linked to that implementation with a `dispatches_to` edge, so a call through an injected dependency reaches the implementation; guarded against false links (single implementer, single case-sensitively same-named method, both ends C#) (#3003, thanks @durmazoguzhan).
- Fix: Zig methods declared on enums and unions (not just structs) are now extracted, along with the calls in their bodies (#2999, thanks @rajatnagda45).
- Fix: a Julia abstract type declared with a supertype (`abstract type Dog <: Animal end`) is now extracted with its `inherits` edge instead of being dropped (#3000, thanks @rajatnagda45).
- Fix: a Common Lisp `defclass` whose superclass lives in another file now keeps its `inherits` edge — the cross-file base is a sourceless stub that the corpus rewire collapses onto the real definition (#3001, thanks @rajatnagda45).
- Fix: PowerShell `enum` definitions and their members are now extracted as a real sourced node, so a `[Color]` type reference resolves to the enum instead of a phantom stub (#3002, thanks @rajatnagda45).
- Fix: a semantic result with no nodes and no hyperedges is no longer cached and stamped into the manifest, so an empty or degenerate LLM reply for a file no longer freezes it out of future re-dispatch; an existing manifest carrying such a stamp is healed by re-queueing those files (#2927, thanks @hopstreax).
- Fix: fresh semantic results are now scoped to the files actually dispatched before they reach the graph merge, so a stray fragment the model misattributes to a non-dispatched file can no longer replace that file's prior contribution (this also silences the "skipped out-of-scope source_file" warning) (#2926, thanks @SinghAman21).
- Fix: `graphify extract --code-only --force` over an existing graph now preserves the document/paper/image semantic layer instead of dropping it; files deleted from disk are still pruned (#2923, thanks @santhiprakash).
- Fix: a class that inherits from a built-in base (e.g. PHP `extends \Exception`) no longer collapses onto a same-named class in another language, so a PHP+TypeScript monorepo stops growing a phantom cross-language inheritance edge / god node; same-language inheritance and user classes named like a built-in still link (#2812, thanks @ousamabenyounes).
- Fix: the git post-commit hook now resolves graphify when it is installed as a `uv tool`, by scanning the uv tool locations after the pin and launcher probes miss and verifying graphify is importable before adopting an interpreter, so the hook stops silently no-opping under that install (#2852, thanks @santhiprakash).
- Fix: re-exporting into an Obsidian vault created before the ownership manifest existed now adopts graphify's own notes (recognized by their `graphify/` frontmatter tag or community markers) instead of orphaning them beside a fresh generation (#2863, thanks @abhay-codes07).
- Fix: the tsconfig/jsconfig alias and `baseUrl` caches are now cleared per extraction run, so an edit to `compilerOptions.paths` or `baseUrl` is picked up on the next `graphify watch` / MCP rebuild instead of resolving imports through a stale alias map for the life of the process (#2917, thanks @sashankh).
- Fix: the C# extractor now emits `references` edges for types used as generic arguments — both at call sites (`repo.Get<User>()`, `services.AddSingleton<IFoo, Foo>()`) and in field declarations (`private List<Order> _orders`) — so generic calls and field-injected dependencies are no longer invisible; built-in and type-parameter types are not fabricated (#2911, thanks @santhiprakash and @brobl2008).
- Fix: oversized PDFs are now sliced through their text converter and their token count is estimated from the extracted text rather than the raw container, so a large paper no longer loses everything past the character cap or gets mis-sized by its binary bytes; slicing (including the adaptive-retry bisection) indexes the same extracted text the prompt carries (#2903, #2906, thanks @abhay-codes07).
- Fix: every text document type is now sliced when oversized, not just five suffixes, so a large `.qmd` / `.skill` / `.html` / `.yaml` / `.yml` is no longer silently truncated at the character cap (#2900, thanks @abhay-codes07).
## 0.9.48 (2026-08-20)
- Fix: a control character in a node label or id no longer aborts the whole export; the GraphML and Obsidian exporters scrub only the characters those formats forbid (tab, newline, and non-ASCII letters are preserved), and `graph.json` and its byte-identity round-trip are untouched (#2897, thanks @abhay-codes07).
- Fix: `graphify update` / `label` / `cluster-only` no longer leave a large graph without a `graph.html`; the aggregated community view renders instead of raising, a failed render preserves the previous file, and a missing `graph.html` is regenerated on the no-change fast path without reclustering (#2853, thanks @oleksii-tumanov).
- Feature: `graphify extract --no-dedup` skips the fuzzy near-duplicate merge on build and incremental merge, for operators who would rather keep distinct symbols that fuzzy-matched; exact-id uniqueness is unaffected and the flag arms the shrink guard so a surprising node drop is refused loudly (#2881, thanks @rajarshidattapy).
- Fix: a response that parses but carries no symbols (a "hollow" reply) is now retried on the same chunk with backoff instead of being bisected like a truncated one, so an overwhelmed backend no longer wastes calls re-splitting a chunk it already answered emptily; on persistent hollow the files are marked partial and retried next run, and `GRAPHIFY_MAX_RETRY_DEPTH=0` disables the hollow retry too so a chunk costs exactly one call (#2880, thanks @rajarshidattapy).
- Fix: reasoning-first models that narrate before emitting JSON (or fence the answer) no longer have their chunk dropped; the parser recovers the JSON fragment from mixed text, preferring an object that carries the extraction keys so narration braces or a schema restatement cannot shadow the real answer, and gating on the sanitized content so a bare-string id sketch cannot either (#2882, thanks @rajarshidattapy).
- Fix: a data-shaped JSON that the extractor deliberately declines is no longer counted as a failed extraction, so it is recorded in the incremental manifest and stops being re-processed on every run; genuine failures are still reported (#2879, thanks @rajarshidattapy).
- Fix: C++ nested class/struct types and their members are now kept in the graph instead of being silently dropped, and C++/CLI sources (`ref class`, `gcnew`, `^`/`%` handles) are normalized to plain C++ before parsing so tree-sitter no longer fabricates phantom symbols from the ERROR nodes it would otherwise produce (#2876, thanks @rajarshidattapy).
- Fix: Obsidian-style `[[wikilinks]]` now resolve vault-wide by basename when sibling resolution misses, so cross-folder links are no longer silently lost; resolution stays deterministic on ambiguous basenames (shallowest then lexicographic path) and sibling matches keep priority (#2875, thanks @BaeHyunJae).
## 0.9.47 (2026-08-19)
- Fix: extraction now bisects a file chunk on timeout instead of failing the whole chunk, so one slow file no longer drops its chunk-mates from the graph; recognized timeouts (subprocess, SDK, and botocore read/connect) route through the same bounded split-and-merge path as context-window-exceeded errors, and a single unsplittable file that times out is left unstamped and retried next run (#2866, thanks @hopstreax).
- Fix: the cache no longer collapses symlinks when keying files, so a symlink alias and its target get distinct identities and one no longer displaces the other from the graph on a warm cache; the detect-layer containment guard that resolves paths to block symlink escape is unchanged (#2832, thanks @oleksii-tumanov).
- Fix: `graphify extract --out <dir>` no longer writes converted Office/Google-Workspace sidecars back into the scanned source tree; sidecar writes go through the cache root while the content hash stays anchored to the scan root, so a read-only or pinned checkout stays clean (#2787, thanks @hopstreax).
- Fix: the git-hook out-of-project read guard no longer misclassifies a rooted-but-driveless path (`\foo\bar`) as cwd-relative on Windows, where such a path resolves against the current drive root and is actually outside the project; POSIX behaviour is unchanged (#2795, thanks @abhay-codes07).
- Fix: the partial-parse warning no longer ends with a hardcoded citation to the closed Kotlin-specific issue #2551 (which misdirected reporters of unrelated failures); it now reports the surviving symbol count so a total-loss file reads differently from a partially-recovered one (#2788, thanks @abhay-codes07).
- Fix: Obsidian export now keeps non-ASCII letters (Hangul, CJK, Cyrillic, accented Latin) in community tags instead of collapsing every non-Latin community to the same underscore tag, and the graph-view colour groups query the identical tag so colours match again (#2862, thanks @josh-leung-KR).
- Feature: JavaScript/TypeScript factory functions that assign callable members to a local object literal (`const api = {}; api.foo = fn`) now keep those members in the graph — the API object is modeled beneath its factory and the assigned functions attach as methods, including arrow-function assignments and their intra-factory call edges. The owner is minted only for identifiers proven to be object-literal bindings in the enclosing function, so arbitrary receivers do not reintroduce the phantom-owner flood, and the factory's `contains` edge is emitted once no matter how many methods hang off the object (#2745, thanks @rajanpanth).
- Fix: `graph.json` field order is now stable across a read-rebuild round-trip, so re-running `graphify update` on an unchanged graph produces a byte-identical file instead of reshuffling node and link keys (#2582, thanks @C0KERNEL).
- Fix: `graphify query` now names the graph it opened and its node count at the head of the answer (relative to the CWD when the graph is underneath it, absolute otherwise), so a query run from a parent project no longer silently answers from the wrong corpus with no indication which graph was used (#2789, thanks @abhay-codes07).
- Fix: the `claude` backend no longer crashes with `AttributeError: 'ThinkingBlock'` when an extended-thinking response leads with a thinking block; the first text block is read instead (#2697, thanks @mdshzb04).
- Fix: `graphify update` / `save_manifest` no longer rewrites `manifest.json` timestamps on a no-op run, so `graphify-out/` stops showing as dirty (and stops producing a trailing graph commit) when nothing changed; a genuine change still updates and persists (#2838, thanks @hopstreax).
- Fix: a C# 12 primary constructor's parameters are now walked, so `class Svc(IRepo repo)` emits the `references` edge to `IRepo` and calls through `repo` resolve — previously the class silently dropped its dependency; built-in and type-parameter types are not fabricated (#2829, thanks @brobl2008).
- Fix: AST-derived INFERRED edges now carry a rubric `confidence_score` keyed to the relation (`uses` 0.95, `indirect_call`/unresolved cross-file `calls` 0.85) instead of landing at the rubric-forbidden 0.5 or a flat 0.8; the INFERRED default moves 0.5→0.55 so every score-less INFERRED edge is on the discrete rubric set (#2813, thanks @abhay-codes07).
- Fix: a legacy `graph.json` that stored a numeric edge `confidence` (from a pre-enum version) no longer warns once per edge on every incremental reload; the numeric value is normalized to the `INFERRED` tag with the original float preserved in `confidence_score` (thanks @Trantor-develops).
## 0.9.46 (2026-08-17)
- Fix: node-id normalization is now caseless-stable for combining-mark sequences — `casefold` and NFKC don't commute, so a single pass left `normalize_id(s) != normalize_id(s.casefold())` for inputs like Greek ypogegrammeni followed by a combining accent; normalization now iterates casefold+NFKC to a fixpoint. Letter/digit-bearing ids are unchanged, so existing graphs are not re-keyed.
- Fix: Java annotations now emit `references` edges to their type — including class-literal arguments (`@Repeatable(Foo.class)`, `@Uses({A.class, B.class})`) and annotation-member return types — so a container annotation is no longer a disconnected island; string/enum arguments are not mistaken for type references (#2426, thanks @oleksii-tumanov).
- Fix: `graphify query` treats `_` as a token separator (like `-`), so an underscore-spelled query (`user_service`) matches a hyphenated label (`user-service`); coverage-scaling keeps the broader tokenization from surfacing unrelated single-token noise (#2473, thanks @nadiadatepe-eng).
- Fix: the `post-checkout` hook skips its rebuild when HEAD is unchanged (e.g. `git checkout -b` with no start point), so creating a branch no longer triggers a full graph rebuild (#2421, thanks @nothariharan).
- Feature: Markdown nodes now carry a `node_kind` (`page` vs `heading`) attribute so a docs corpus can be filtered by kind, and leading YAML frontmatter is parsed onto the page node as bounded, sanitized attributes; a `#` comment inside frontmatter is no longer mis-extracted as a heading (thanks @evanthomasgelders). Node ids are unchanged, so existing markdown graphs are not re-keyed.
- Feature: Common Lisp `.lisp`/`.cl`/`.lsp`/`.asd` extraction via tree-sitter-commonlisp (optional `[commonlisp]` extra) — packages, classes, functions, methods, generics, macros, variable definers, and same-file calls; `open`ed/`:use`d packages resolve cross-file (thanks @fade).
- Fix: a `graphify query` whose node set fits the budget but whose edges push the total over now prints an honest "complete answer over budget" notice with the real size, instead of silently returning a payload several times the requested budget (and no longer advises raising the budget, which was the exact trigger); edges are still never dropped from a complete answer (#2784, thanks @AromalBiju1).
- Fix: a `.gitignore`/`.graphifyignore` saved in a non-UTF-8 encoding no longer silently drops its rules (which let an explicitly-excluded directory get scanned anyway); the file is decoded UTF-8-first, then by a UTF-16 BOM, then the host codepage/latin-1, so the rule survives intact with a warning instead of being truncated (#2798, thanks @abhay-codes07).
- Fix: when node dedup merges two nodes, any hyperedge that listed the merged-away node as a member now rewires that member to the survivor instead of silently dropping it, so a grouping no longer loses participants on dedup (#2805, thanks @abhay-codes07).
- Fix: pruning a source file now also sweeps the external-import placeholder nodes it strands at degree 0, instead of leaving them to accumulate in the node count, `GRAPH_REPORT.md`, and exports; genuinely-isolated real nodes (which carry a `source_file`) are never touched (#2807, thanks @abhay-codes07).
- Fix: when two edges connect the same node pair with different relations, the graph builder now keeps the more specific relation (`calls`, `imports`, `inherits`, ...) instead of letting a generic `references`/`uses`/`mentions` overwrite it; previously a real `calls` could be downgraded to `references` and then dropped from the call graph (#2803, thanks @abhay-codes07).
## 0.9.45 (2026-08-16)
- 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).
## 0.9.44 (2026-08-15)
- 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`.
## 0.9.43 (2026-08-14)
- Feature: OCaml `.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 importing file (#2652, thanks @ousamabenyounes). A reference at module top level, with no enclosing symbol, emits no edge.
- Fix: a named `function` declaration nested inside another function now gets its own node, a `contains` edge from the enclosing function, and its own call scope, so calls made from inside it are no longer dropped as dangling (#2653, thanks @himanshupatro-334). Coverage was extended to the arrow idioms too: a function declared inside an arrow-defined component (`const Panel = () => { function handleClick(){} }`) or inside an arrow callback (`useEffect(() => { function h(){} })`) is captured and attributed to the nearest enclosing named scope.
- Fix: the Bash extractor resolves two more `source` path forms — `source "$(dirname "$VAR")/lib/x.sh"` and a `..` suffix on a tracked-variable base (`source "$VAR/../lib/x.sh"`) — so those cross-file source edges are no longer silently dropped (#2596, thanks @hudsonwa). A `..` on a *guessed* base is still rejected, and a tracked-base `..` 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.
- 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 characters no longer produces a link that names no file on disk; the link and the on-disk filename share one canonicalization (#2597, thanks @abhay-codes07).
- Fix: export filenames are now budgeted against the full destination path rather than only the per-component `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). The collision-suffix reserve was widened so a four-digit dedup suffix can't overrun the budget.
## 0.9.42 (2026-08-13)
- Fix: a JS/TS `for...of` / `for...in` loop binding is now shadowed, so passing it as a call argument no longer fabricates an `indirect_call` edge to an unrelated same-named callable (#2685, thanks @ousamabenyounes); completes the loop/closure/catch shadow family (#2568/#2569/#2517).
- Fix: graph provenance (`built_at_commit`) is stamped from the analysed repository rather than the shell's working directory, so `graphify extract` run from elsewhere records the target's commit, not the caller's (#2534 family; #2699, thanks @C0KERNEL).
- Fix: `affected` resolves a seed passed as a `./`-relative path (or an absolute path when run from the repo root) instead of silently returning nothing (#2707, thanks @phudayyy). Note: an absolute-path seed still requires the working directory to be the analysed repo root.
- Fix: a Python relative import of a subpackage (`from ..pkg.sub import x`) now resolves to the package's `__init__` instead of a nonexistent `.py` slug (#2688, thanks @ousamabenyounes).
- Fix: a `.sql` file that fails to parse because tree-sitter-sql is installed but broken (e.g. an ABI mismatch) now reports the real load failure instead of the misleading "not installed" message (#2602, thanks @ousamabenyounes).
- Fix: a corrupt semantic-cache entry is now surfaced with a warning and re-extracted, instead of being a silent cache miss that re-bills the LLM every run; a valid cache is not discarded (#2683, thanks @ousamabenyounes).
- Fix: the `GRAPH_REPORT.md` header uses a portable basename instead of embedding the generator's absolute host path (#2682, thanks @ousamabenyounes).
- Fix: `graphify update` / `_read_files` hand the model a POSIX `source_file`, and several path/atomic-write behaviors are hardened for Windows (#2620/#2622, thanks @rajarshidattapy).
- Fix: a failed atomic write no longer leaks a read-only `.tmp` file in the output directory on Windows (#2622, thanks @rajarshidattapy).
- Test/docs: Windows-portability test fixes (probe-and-skip symlink tests, separator-agnostic path assertions, shell-arg verdict test), a refreshed `ARCHITECTURE.md` module table with a doc-parity test, and README notes on CI parity checks and Windows test prerequisites (#2620/#2622/#2126/#2642/#2646/#2647/#2648/#2651, thanks @rajarshidattapy, @redzwanmutalib, @nelsondeleonc-source).
- Fix: a non-regular file (FIFO/named pipe, device) in the scanned tree no longer hangs extraction on a blocking read; non-regular files are skipped during collection (#2463, thanks @itskaism).
- Fix: an incremental run now re-queues a file rewritten to the same length within one mtime tick, instead of skipping it as unchanged (#2466, thanks @itskaism); complements the 0.9.40 file-hash guard (#2612).
- Fix: the `apm.yml` fallback parser (used when PyYAML is absent) now captures the package version instead of dropping it (#2465, thanks @itskaism).
- Fix: `graphify install` no longer fails when the packaged bundle is read-only (e.g. a Nix store or root-owned site-packages); the staged skill references are made writable before the atomic rename (#2453, thanks @bensleveritt).
- Fix: hyperedge regions in `graph.html` are traced in convex-hull order instead of member-array order, so the shaded polygon no longer self-intersects (#2449, thanks @ysys143).
- Fix: `graph_has_legacy_ids` no longer false-positives on a global MCP node id (e.g. from a nested `.mcp.json`), which wrongly flagged a modern graph as legacy (#2408, thanks @aryanbonigala).
## 0.9.41 (2026-08-12)
- Fix: a JS/TS `catch` binding passed as a call argument (`catch (handler) { pool.submit(handler) }`) no longer fabricates an `indirect_call` edge to an unrelated same-named callable (thanks @imagineers-tyler); the catch binding is now shadowed within its clause, completing the 0.9.38/0.9.40 arrow-parameter fixes (#2568).
- Fix: `Cargo.toml` is now recognized as a package manifest (#2434, thanks @ousamabenyounes), minting one canonical package node by name plus `depends_on` edges (dependencies, plus target-specific deps; virtual-workspace roots and workspace-inherited versions are handled).
- Fix: an explicitly-passed scan root is no longer excluded by an unanchored pattern in a parent-directory `.gitignore` that happens to match the root's own name (#2468, thanks @hopstreax); the match path is re-relativized to the scan root (and NFC-normalized) so a genuinely-ignored subdirectory is still skipped.
- Fix: the API extraction prompt now instructs backends to capture the per-node `rationale` attribute (design intent / trade-offs), matching the skill path, so API-backed extraction no longer silently drops it (#2482, thanks @hopstreax). This invalidates cached semantic chunks, which re-extract on the next run.
- Fix: `source_file` is canonicalized to POSIX separators, so a run given relative inputs on Windows no longer produces non-portable node ids with backslashes (#2627, thanks @rajarshidattapy).
- Fix: a warm cache hit no longer re-anchors a CWD-relative `source_file` to a ghost path when the run's working directory differs from the graph root, keeping incremental and cold-build node ids identical (#2632, thanks @rajarshidattapy).
- Fix: the wiki/obsidian audit trail counts each incident edge once instead of double-counting intra-community edges, so the confidence breakdown is accurate (#2635, thanks @rajarshidattapy).
- Fix: C# members declared inside a `#if ... #endif` preprocessor block are now extracted and attached to their class instead of being dropped (#2634, thanks @rohit-jsfreaky).
- 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: `query` no longer prints the truncation banner when no nodes were actually cut (only trailing edges overflowed the budget) (#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 instead of being dropped (#2661, thanks @ousamabenyounes).
- Fix: an unresolved local JS/TS import (to a file absent from the scan) now emits a stable, portable `ref` target id instead of leaking 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).
## 0.9.40 (2026-08-11)
- Fix: the 0.9.37 partial-parse warning no longer fires on valid TypeScript/TSX (#2610, #2599, thanks @Sid-AutoWisdom and @atlasplatformu-ai). tree-sitter-typescript sets an error flag on tiny fully-recovered constructs (a `&` in a JSX string attribute, a semicolon-less `in_*` interface member) that still extract completely; the warning now fires only when recovery plausibly cost symbols (the file yielded at most the file node, or an error region spans multiple lines), so 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); a racily-clean guard falls back to a content hash for recently-modified files.
- Fix: stored-path absoluteness is now 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 now idempotent for Turkish `İ` and similar codepoints by casefolding before the non-word filter; no ASCII identifier ids change (#2614, thanks @rajarshidattapy).
- Fix: `graph.json` collection order is now 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 paths 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 now 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 (`from ....x import y` above the package root) (#2605, thanks @SinghAman21).
- Fix: a Go qualified type (`pkg.Type`) resolves by import path instead of losing the qualifier and 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); it keeps the path from the output-dir marker onward.
## 0.9.39 (2026-08-10)
- 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.
## 0.9.38 (2026-08-09)
- 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.
- Fix: `explain` resolves node ids that contain punctuation or non-ASCII text (#2467). An id was only ever compared against the `\w+`-tokenized query, so `concept:domain:x`, every `merge-graphs` `<repo>::` id, and every Hangul id failed to resolve; the id printed by `explain` could not be fed back into `explain`, and the ambiguity hint "Retry with […] the full node id" named a remedy that could not work. The exact tier now also compares the diacritic-folded id, and the trigram index carries the folded form so a non-ASCII id survives the prefilter. Only ids that previously failed to resolve can now resolve — label queries are unchanged, and an all-ASCII graph indexes byte-identically to before.
## 0.9.37 (2026-08-08)
- 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 now 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 now 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, and declarations recovered inside an error span keep 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 the fix avoids re-processing a genuinely-unchanged file.
- 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.
## 0.9.36 (2026-08-07)
- 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.
## 0.9.35 (2026-08-06)
- 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 that was 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.
## 0.9.34 (2026-08-05)
- 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).
## 0.9.33 (2026-08-05)
- 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.
## 0.9.32 (2026-08-01)
- Fix: incremental extraction and `_rebuild_code` no longer drop a file's other tier (#2333, #2334, #2336). Node/edge ownership was keyed on `source_file` alone, so a semantic re-extract deleted a doc's AST headings and a full rebuild deleted document AST nodes. 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), so God-node / path ranking keeps its direction on both the clustered and `--no-cluster` rebuild paths.
- Fix: a numeric or otherwise non-string node id from an LLM fragment no longer aborts the build with a TypeError (#2326, thanks @Rishet11); ids are coerced consistently across nodes, edges, and hyperedges.
- Fix: `graphify query` renders every edge between visited nodes, not just the traversal-tree edges, so the returned subgraph matches the real induced subgraph (#2323, thanks @Rishet11).
- Fix: `graphify update` writes `manifest.json` to the target's `graphify-out` instead of the current working directory (#2316, thanks @Rishet11), so running it from elsewhere can no longer prune the target's own manifest rows.
- 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; only the configured output path is excluded (#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 so cross-half calls resolve (#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` are canonicalized to the same fully-qualified label, and `include`/`extend`/`prepend` keep the full constant path, so `include Foo::Bar` resolves. Mixin resolution is now scoped and lexical: a qualified external name like `extend ActiveSupport::Concern` no longer binds to any local module named `Concern`, while a genuine in-corpus `include Foo::Concern` still resolves. Nested-declared classes keep their last-segment index so typed-receiver calls (`Processor.new`) continue to resolve.
- Perf: dedup drops an O(nodes x components) scan in remap construction (#2328, thanks @stupidprogrammer4), with identical results.
## 0.9.31 (2026-07-30)
- Feature: the MCP server is dual-compatible with SDK 1.x AND 2.x (#2308, thanks @NiSHoW), lifting the `mcp<2` cap 0.9.30 introduced to `mcp>=1,<3`. The 2.0 SDK removed the low-level decorator API (`Server.list_tools`/`call_tool`/...); `_build_server` now binds the same handlers via the 1.x decorators or the 2.x `on_*` constructor callbacks, picked at runtime, and adapts `Tool.inputSchema`, `Resource.uri` (plain `str` in 2.x), and the dropped `AnyUrl` re-export. Verified with full stdio handshakes under both mcp 1.29 and 2.0.
- Fix: 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 was per-file and poisoned a name on any conflicting/untypable rebind anywhere in the file; it is now per-method (mirroring the Java resolver), so an untypable `var x = ...` in one method can't delete a typed-parameter call edge in another.
- Fix: 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 now mint a sourceless stub that collapses onto the real definition, and identifiers are normalized so a quoted definition (`"public"."users"`) matches an unquoted reference (`public.users`).
- Fix: `graphify path` and `explain` no longer print reversed hops (#2309). They now recover edge direction from the stored `_src`/`_tgt` markers instead of the persisted endpoint order, so a graph.json written with flipped storage order (older graphs, raw dumps, merge-driver output) renders the true direction.
- Fix: `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).
- Fix: Go predeclared functions (`make`, `len`, `append`, `new`, ...) no longer fabricate call edges to same-named user symbols (#2313, thanks @PathGao); the filter is scoped to Go bare-identifier callees so it can't affect other languages or same-file method calls.
- Fix: `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).
- Fix: the Antigravity install workflow no longer hardcodes the global skill path for a project-scoped install (#2319, thanks @MalikHaroonKhokhar).
## 0.9.30 (2026-07-29)
- Fix: pin `mcp` below 2.0 so a fresh `graphifyy[mcp]` / `graphifyy[all]` install works again (#2277, #2279, #2291). The `mcp` 2.0.0 major 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`. Adapting to the mcp 2.x API is tracked as a follow-up.
- Fix: 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, so JSX misparsed and nested handlers floated to top level, emitting `calls` edges whose source was an absolute-stem id for a caller 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.
- Fix: a warm AST-cache hit after a corpus move/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, matching the manifest/stat-index portability contracts.
- Fix: the Bedrock backend reads the first *text* block of a Converse response instead of blindly indexing block 0, so reasoning-capable models (which emit a reasoning block first) no longer parse to zero nodes (#2287, thanks @zhiyanliu).
- Fix: the Bedrock backend honors `GRAPHIFY_API_TIMEOUT` (and `GRAPHIFY_MAX_RETRIES`) instead of botocore's silent 60s default, so long generations no longer die with a read timeout (#2284, thanks @zhiyanliu).
- Fix: `merge-graphs` preserves edge direction instead of rewiring import edges to the importing file (#2261, thanks @hopstreax).
- Fix: the MCP server's multi-project graph-context cache is now bounded (LRU, default 8 via `GRAPHIFY_MAX_CONTEXTS`) instead of growing unbounded per project (#2268, thanks @Kkartik14).
## 0.9.29 (2026-07-28)
- Fix: 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.
- Fix: 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 now refuses to write (matching the CLI) instead of silently replacing it with a code-only extraction; the `--no-cluster` write is also atomic with a protected-graph backup.
- Fix: false `indirect_call` edges from JS/TS closure arguments are gone (#2241, thanks @Yyunozor). A closure parameter (`rows.map(r => ...)`) now shadows outer names, so `r` no longer binds to a corpus-wide callable of the same name.
- Fix: the post-commit hook launcher no longer pops a focus-stealing console window on Windows (#2253, thanks @hopstreax); it uses `CREATE_NO_WINDOW`.
- Fix: rationale node labels are normalized (whitespace collapsed) before the 80-character truncation, so labels are clean and filenames aren't malformed (#2206, thanks @Yyunozor).
- Fix: 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).
- Fix: 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).
- Fix: Scala `self`-type annotations (`self: A with B =>`) now emit `requires` edges to the required traits (#2052, thanks @Yyunozor).
## 0.9.28 (2026-07-27)
- Fix: incremental extraction no longer drops cross-file edges whose target file wasn't in the batch (#2211, #2213). Python relative imports and markdown reference links emitted absolute-path-derived target ids without the `target_file` stamp the incremental canonicalization needs, so a re-extracted file's imports/references dangled or vanished; both now stamp the resolved target and canonicalize to the root-relative node.
- Fix: incremental extraction no longer prunes alive files as "deleted" (#2210). The stale-source check compared graph paths to the scan with a raw string test (no Unicode NFC normalization) and pruned any non-match without checking whether the file still exists, so macOS NFD paths and legacy basename spellings lost their nodes. It now compares NFC on both sides and is fail-closed: a source missing from the scan is pruned only when its exclusion is provable, otherwise kept with a warning.
- Fix: `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). Those graphs store edges under `edges` rather than `links`; a shared loader now normalizes both.
- Fix: `claude`/`gemini`/`codebuddy` uninstall no longer delete the user-global skill when called with a `project_dir` (#2215). A passed `project_dir` now scopes the removal; this also fixes a CLI bug where `graphify uninstall --project` deleted the global codebuddy skill.
- Fix: `--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 so NFD and NFC path forms match.
- Fix: 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).
- Fix: 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).
## 0.9.27 (2026-07-26)
- Fix: `claude`/`gemini`/`codex`/`codebuddy install` no longer overwrite an existing settings/hooks file they cannot parse (#2167). The installers fell back to an empty config on any JSON parse error and then rewrote the whole file, destroying the user's settings (the likely trigger is a UTF-8 BOM, the same class as #2163). They now read `utf-8-sig`, refuse to modify a file that is not a JSON object (naming the path) instead of clobbering it, and back up to `<name>.graphify-bak` before any modifying write.
- Fix: incremental `extract --no-cluster` no longer overwrites the full graph with just the changed files (#2169). The raw path wrote only the current run's extraction over `graph.json`, dropping every node and edge owned by an unchanged file, and the changed file's cross-file edges dangled on absolute-path ids. It now merges the existing graph forward with the same replace/prune semantics as the clustered path and canonicalizes cross-file edge targets; a corrupt existing graph is refused rather than overwritten.
- Fix: Python decorators now create graph edges, so `affected <decorator>` finds everything a decorator touches (#2154, thanks @Rishet11). Builtin/stdlib decorators (`@property`, `@staticmethod`, `@dataclass`, `@functools.wraps`, ...) are excluded so they do not fabricate stub nodes or false edges.
- Fix: JavaScript/TypeScript non-relative imports resolve through `jsconfig.json`/`tsconfig.json` `baseUrl` and `paths` (#2153, thanks @Rishet11), so imports like `import x from 'src/utils'` are no longer left dangling.
- Fix: Swift/Foundation/SwiftUI builtins (`Data`, `URL`, `Sendable`, `View`, ...) are filtered from call resolution and god-node ranking (#2147, thanks @MasterFede5), so they no longer fabricate cross-file edges to user symbols or dominate the graph's high-degree nodes.
- Fix: running the test suite no longer touches the developer's real `~/.claude`/`~/.gemini`/`~/.codebuddy`/`~/.copilot` (#2168). An autouse fixture sandboxes HOME for every test.
- Fix: bash calls into functions from a `source`d file now resolve for extensionless shebang scripts and bare `source lib.sh` (no `./` prefix) too (#2171, thanks @Souptik96). The bare-name binding is marked INFERRED since it resolves via `$PATH` at runtime.
- Fix: a `source "${VAR}/lib.sh"` whose variable is a tracked top-level assignment (or the `dirname "${BASH_SOURCE[0]}"` idiom) now resolves against the variable's real directory instead of guessing the script's own, so it no longer binds to a same-named decoy under the script dir (#2172, thanks @Souptik96).
- Fix: SQL `CREATE FUNCTION`/`PROCEDURE` routines with PL/pgSQL-only bodies that tree-sitter cannot parse are now recovered by a raw-text scan, gated on a failed parse so a cleanly-parsing file cannot fabricate routines from commented-out DDL or `EXECUTE` strings (#2180, thanks @Souptik96).
- Fix: the extraction process pool is skipped when only one worker is available, falling back to in-process sequential extraction (#2173, thanks @Souptik96), which also removes the last case where a hung Windows hook rebuild could orphan a worker.
- Fix: the git-hook interpreter pin now handles a Python path containing a space (#2166, thanks @Souptik96); the merge-driver command is quoted and the pin allowlist admits a space while still rejecting shell metacharacters.
- Docs: the Codex `PreToolUse` hook (`graphify hook-check`) is documented as an intentional no-op (#2165, thanks @Souptik96); Codex Desktop rejects `additionalContext` there, so graph guidance comes from `AGENTS.md`.
- Fix: JavaScript/TypeScript regex-rescued imports (Svelte/Astro/Vue) no longer create ghost target nodes with absolute-path ids; the target resolves to the canonical root-relative file node and the dangling absolute-id edges are gone (#2195).
- Fix: cross-file `concept` nodes whose labels normalize identically are now merged, matching the behavior already applied to near-identical labels (#2182). Gated to `concept` nodes with provenance, so code/rationale/document/image and cross-repo guards are preserved.
- Fix: `graphify-out/cache/stat-index.json` is now 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, and the index stops growing unbounded (#2199).
- Fix: absolute `source_file` paths (for example from a Windows scan) no longer break node identity: `build_from_json` re-keys absolute-derived semantic ids to the canonical root-relative id and the semantic cache stores normalized paths (#2197).
- Fix: `build_from_json` now folds legacy field aliases (`name`->`label`, `path`->`source_file`, edge `type`->`relation`, `confidence_score`->`confidence`), so nodes carrying them stop entering the graph without a label or source_file where they are invisible and unmergeable (#2194). The extraction warning also breaks issues down by cause.
- Feat: C# member calls on a typed receiver now resolve namespace-aware (a class name used in two namespaces resolves via `using`/scope instead of bailing), 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).
## 0.9.26 (2026-07-25)
- Fix: `graphify query`/`explain` no longer fabricate `indirect_call` edges to class definitions (#2137, thanks @Rishet11). The callable guard admitted classes, so 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 in both the intra-file and cross-file paths. Classes are now tracked separately and excluded from `indirect_call`; direct instantiation still emits its `calls` edge. The suppression is context-blind, so a genuine higher-order class callback that is actually invoked (e.g. `map(Point, coords)`) also loses its edge, which is the intended tradeoff.
- Fix: 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 (and the shebang-launcher allowlist admitted no `:` or `\` at all), so the hook failed on Windows uv/venv installs. Both allowlist sites now use a verified character class that admits backslashes while still rejecting shell metacharacters.
- Fix: 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.
- Fix: bash calls into functions defined in a `source`d file now get `calls` edges (#2141, thanks @HerenderKumar). Call 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 matches nothing and fabricates no edge.
- Fix: 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 now 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, not just the source edge.
- Fix: 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 (and turned a BOM'd first comment into a bogus pattern). The two ignore read sites now use `utf-8-sig`, matching git, which strips a single leading BOM.
## 0.9.25 (2026-07-22)
- License: the project is now licensed under the Apache License, Version 2.0 (previously MIT). Apache 2.0 adds an explicit patent grant and patent-retaliation clause and explicit contribution terms. Contributions made before the relicensing were submitted under MIT and remain available under those terms; the original MIT license text is retained in `LICENSE-MIT` and referenced from `NOTICE`.
- Removed: `.graphifyinclude` handling is gone (#2112). The file has been non-functional since dot directories became indexed by default (#873): the loader and its matchers had no consumers, so `detect` parsed the file on every run and then ignored it, and a `.graphifyinclude` was silently a no-op. The dead loader and matchers are deleted, a leftover `.graphifyinclude` no longer shows up in the `unclassified` list, and `detect` prints a one-time stderr note when one is present at the scan root. To re-include ignored paths, use `!` negation patterns in `.graphifyignore`.
## 0.9.24 (2026-07-22)
- Fix: the XAML code-behind `.cs` scan is now bounded and prunes noise dirs, so it can't hang. `_xaml_csharp_class_nodes` used `rglob("*.cs")` over a project root resolved by walking up for a `.csproj`/`.sln`; a standalone `extract_xaml` on a `.xaml` under a large or shared parent (a temp dir, a big monorepo) could resolve the root to a broad ancestor and then recursively scan the whole tree. It now walks with `node_modules`/`.venv`/`.git`/dot-dir pruning and a directory cap, so a real project scans fully while a runaway root degrades to a fast partial scan.
- Fix: the sensitive-file filter no longer silently drops topic docs and real source (#2106). A prose file whose slug merely ends in a keyword (`privacy-tokens.md`, `token-economics.md`) and real source like `service_account.py` were dropped from the graph with no trace; the filter also missed genuine secrets (`.npmrc`, `.pypirc`, `secring`, `.git-credentials`, case variants). `service_account`/`aws_credentials` moved to the boundary-checked keyword path (real source spared, downloaded key files still excluded), a prose-note carve-out was added (multi-word slugs indexed, bare `secrets.md`/`token.md` still dropped), and the missed secret dotfiles are now caught — net stricter on real secrets while ending the false-positive loss. `graphify extract` and the skill flow now name the skipped-sensitive files instead of only a count, so a wrongly-flagged file is visible.
- Fix: `calls` edges now resolve through an aliased Python import (#2082, thanks @Yyunozor). `from pkg import mod as alias` (and `import pkg.mod as alias`) recorded the import edge but dropped every downstream `alias.func()` call, so the callee looked like dead code even though the import graph looked complete. The local alias binding is now tracked and the call resolves to the real callee (in-corpus only, so external/stdlib aliases still fabricate nothing). This is the "invisible caller" family that distinguishes graphify from grep/AST name matching.
- Fix: `dedup` (default on) preserves a node's attributes when two exact-ID records from the same source file collapse (#2091, thanks @Synvoya). The collapse discarded one record's fields (e.g. an AST node's `source_location` or a semantic node's `summary`), so the default path silently lost data the docs promised was merged. Non-conflicting attributes are now retained deterministically (independent of chunk order), records from different files or with no source path stay isolated, and a dropped record can never stamp a false `_origin` onto the survivor.
- Fix: the `claude-cli` backend now reads the CLI's structured-output channel instead of trusting free-form prose (#2076, thanks @Yyunozor). Newer Claude Code treats the extraction prompt as an agentic task and reports a summary in the `result` field, which parsed to zero nodes and bisected forever. The backend now pins a JSON schema when the CLI supports it (feature-detected, with the old prompt as the fallback for older CLIs) and parses the `structured_output` object.
- Fix: `graphify explain` on a high-degree node now groups the cut connections by file instead of a bare `... and N more`, so the callers/callees beyond the top 20 are still visible (their file, direction, and count) without a repo-wide grep (#2009, thanks @Yyunozor).
- Fix: `graphify query` no longer prints `calls` edges backwards (#2080, thanks @Yyunozor). The graph on disk is correct, but the CLI loads it undirected and BFS/DFS collect edges in traversal order, so seeding on the callee rendered `callee --calls--> caller`. The renderer now recovers the stored direction from the edge's `_src`/`_tgt` (ignoring stray/dangling values), and both CLI `query` and MCP `query_graph` show the real direction.
- Feat: `get_neighbors` and `get_community` (MCP) now honor a `token_budget` (default 2000) instead of rendering unbounded, 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 (matching `query`), with a line count and a narrowing hint.
- Docs: `--code-only` is now surfaced in the `extract` usage text and README (#2071, thanks @HerenderKumar); documented as an `extract` flag rather than a `/graphify` skill flag.
- Docs: README troubleshooting note for an older `graphifyy` in system site-packages shadowing `uv run --with graphifyy`, which silently runs the old version (#1540, thanks @HerenderKumar).
## 0.9.23 (2026-07-21)
- 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/`.
## 0.9.22 (2026-07-20)
- 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.
## 0.9.21 (2026-07-20)
- 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`.
## 0.9.20 (2026-07-18)
- 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