github Graphify-Labs/graphify v0.9.19
graphify 0.9.19

9 hours ago
  • Feat: opt-in strict PreToolUse hook that actually makes agents use the graph. The installed Claude Code hook has always nudged the agent to run graphify query before reading raw files, but a nudge is advisory additionalContext the model routinely walks past mid-task. graphify install --project --strict (or graphify claude install --strict) now installs a hook that blocks the first raw source read of a session (permissionDecision: "deny") with a redirect to graphify query, then downgrades to the soft nudge — so it fires at most once per session and can never strand the agent (the next read proceeds even if no query ran, or if graphify query itself failed). Running any graphify query/explain/path refreshes a short-lived "recently oriented" stamp that suppresses the block. Strict mode is Claude Code only (Bash-grep and Glob stay nudge-only; Gemini/Codex/OpenCode can't hard-block and are unchanged); GRAPHIFY_HOOK_STRICT=1/0 toggles it at runtime without a reinstall. Default installs are unchanged (soft nudge).
  • Fix: the PreToolUse hook stops crying wolf (#1840), which applies to the default soft nudge too. It no longer fires for reads of files outside the indexed project (a common false trigger, e.g. a ~/.claude/.../SKILL.md read), and when the graph is stale for the target file (the file changed after the last build, or graphify watch flagged the tree) it softens to a non-mandatory nudge that suggests graphify update instead of demanding the query. Gating is ~3 stat calls — no corpus walk — so it stays fast on large monorepos, and fails open on any error.
  • Fix: a same-basename cross-extension re-export no longer manufactures a phantom self-cycle (#1814, thanks @Greg-Moskalenko for the report and @alphanury for the fix). A typed .ts wrapper that re-exports a hand-written .mjs runtime (export { N } from "./foo.mjs") had foo.ts and foo.mjs collapse onto one base file id (the id stem drops the extension), and while _disambiguate_colliding_node_ids correctly salts the two file nodes apart (foo_ts_foo / foo_mjs_foo), the re-export edge keyed its target salt by the importer's own source file — mis-pointing the ./foo.mjs target back at foo.ts, a source == target self-loop reported as a 1-file import cycle in GRAPH_REPORT.md. During disambiguation an import/re-export edge now carries the resolved target file as a transient salt key, so the salt lands on the real sibling node (generalizing the C/ObjC .h-sibling carve-out from #1475 to every language and to re_exports) and the phantom cycle disappears. That hint has no downstream reader and holds an absolute path, so it is popped once consumed and never persisted — and the graph serializer drops it as a backstop — keeping graph.json deterministic and byte-identical across checkout locations. Node ids are unchanged (the residual was purely at the edge layer). One caveat: a graph written by a pre-fix build still records the stale self-loop, and because graphify update only re-extracts changed files, an unchanged wrapper keeps that edge until it is next edited or a --force full rebuild runs — though any stale absolute hint a pre-fix graph happened to persist is dropped on the next build regardless. (The extension-aware-id alternative was rejected: it would rewrite every file and symbol id and force a full-rebuild migration in lockstep with the skill/validation id spec, #1033.)
  • Fix: file_hash's stat-index memo is now keyed by the path salt, not the absolute path alone (#1989). The digest salts content with the file's path relative to the scan root (for cache portability), but the memo returned whichever digest was computed first for a given absolute path — so the same file hashed under two different roots (which happens within one --out run) got an order-dependent result, and the wrong digest was persisted into stat-index.json across runs. Each entry now stores one digest per salt; legacy un-salted entries are recomputed rather than trusted. Digest computation is unchanged, so existing cache entries still hit.
  • Fix: --no-gitignore extraction opt-out for projects that keep useful code under .gitignore (#1971, thanks @JensD-git for the report and @Mzt00 for the fix). The flag disables only VCS ignore rules (.gitignore + $GIT_DIR/info/exclude); .graphifyignore, the sensitive-dir/secret screens (#1666/#1943), and noise-dir pruning all still apply, so .git/, node_modules/, and real secrets stay out of the graph. The setting persists across update/watch/hook rebuilds and is no longer clobbered back on by a later flag-less graphify extract.
  • Fix: .graphifyignore/.gitignore glob matching now keeps * within a single path segment (#1975, thanks @oleksii-tumanov). An anchored * used to cross / (hand-rolled fnmatch), so an exclude-all + re-include subtree pattern collapsed to zero files and /src/*.py wrongly ignored nested files. Matching now follows git segment semantics (* per segment, ** spans segments, dir/ matches directories not same-named files), verified against git check-ignore; composes with the #1873 anchor-scoping and #1922 diagnostic.
  • Fix: DreamMaker parent-relative #include paths are no longer mangled (#1978, thanks @Osamaali313). The extractor used str.lstrip("./") — a character-set strip that ate every leading .//, so ../shared/base.dm became shared/base.dm (and .hidden/x lost its dot), breaking include resolution. It now strips only a leading ./ prefix.
  • Fix: graphify prs decodes gh/git/claude output as UTF-8 instead of the Windows locale codec (#1980, thanks @luke J). On Windows text=True decoded child output as cp1252, mojibaking or crashing on emoji / non-ASCII names in PR titles and diffs; all subprocess reads now pass encoding="utf-8", errors="replace" (matching the #1505 precedent), which also fixes the encode side when feeding a non-ASCII prompt to the claude-cli backend.

Don't miss a new graphify release

NewReleases is sending notifications on new releases.