-
Acknowledgement: Amp (ampcode.com) platform support, which shipped earlier in v8, was contributed by @zuwasi in #948 (the
skill-amp.mdskill and thegraphify amp installwiring). Belated credit for the work. -
Fix: a missing
manifest.jsonno longer degradesgraphify extract --code-onlyinto a full scan that discards the committed semantic layer (#1925). On a fresh clone (or when the manifest is deliberately untracked because its mtimes churn), the incremental gate required bothmanifest.jsonandgraph.json; with only the graph present it fell to a full scan, and under--code-onlythat dropped every doc/paper/image node — silently replacing a curated graph with an AST-only skeleton. An existinggraph.jsonis now a sufficient incremental baseline:detect_incrementalalready treats an absent manifest as "everything new / nothing deleted", sobuild_merge+_stale_graph_sourcespreserve files that are merely out of this run's scope while still evicting genuinely deleted sources. -
Fix: hyperedge-only documents are now stamped in the manifest instead of being re-extracted on every run (#1920).
_stamped_manifest_files(#1897) decided a semantic doc "produced output" by inspecting onlynodesandedges, neverhyperedges, so a chunk whose only output for a doc was a hyperedge (3+ nodes sharing a concept) left that doc unstamped and perpetually re-queued. Stamping now counts hyperedge output too, mirroring the per-source_filekeying the semantic cache already uses. -
Fix: the PHP extractor now disambiguates same-named classes across namespaces (#1923). A bare class reference (
extends Page) collapsed onto the only internal class namedPage, soApp\Models\Pageand an importedFilament\Pages\Pagefused into one node, manufacturing a falseinherits/importsedge and a bogus cross-community bridge. A new namespace/use-aware resolution pass (mirroring the Java resolver, running before the unique-name rewire) re-points supertype/import references to the real definition, or parks provably-external ones on a fully-qualified stub the bare-name rewire cannot collapse. Plain, non-namespaced PHP is unchanged. -
Fix:
detect()now records files and directories dropped by a.gitignore/.graphifyignorerule in a newignoreddiagnostic field (#1922). The nested-ignore scoping bug itself was fixed in 0.9.16 (#1873); this closes the remaining gap where an ignored path left no trace in any diagnostic, so an over-broad rule looked like a clean scan. Entries are per-directory where a subtree is pruned, keeping the list bounded. -
Fix:
_semantic_id_remapis now idempotent, so incremental rebuilds stop churning (#1917). When a file's canonical stem contained its own legacy stem as a prefix (parent dir name equals the file stem, e.g..claude/CLAUDE.md,docs/docs.md), an already-migrated semantic node id re-matched the legacy branch and gained another stem segment on every build (claude_x->claude_claude_x-> ...). Because_originis persisted, everygraphify updatere-fed nodes through the remap, so the ids grew unboundedly and thesame_topology/same_graph/no_changeshort-circuits never fired — rewritinggraph.jsonand re-running clustering on every zero-delta update. The remap now skips an id that already carries its canonical stem (mirroring thegraph_has_legacy_idscheck), while a genuine one-time legacy migration still applies. (An already-corrupted graph needs onegraphify extract --forceto reset the grown ids.) -
Perf:
graphify querynow scores the graph once per query instead of T+1 times for a T-term query (#1889 / #1918, thanks @Sirhan1). The per-term-guarantee (#1445) previously re-scored the whole graph once per token;_score_querynow computes the combined ranking and each token's singleton winner in a single traversal, feeding_pick_seedsviabest_seed_by_term. Behavior is preserved (verified byte-identical against the old per-term scoring across a differential fuzz); ~1.3-1.4x faster and independent of query length. -
Fix:
--mode deepis now effective over a warm cache instead of a silent no-op (#1894). The semantic cache is namespaced by mode (semanticvssemantic-deep) so a shallow-cached file no longer satisfies a deep run;graphify extractgains--force(and honorsGRAPHIFY_FORCE) to bypass the incremental gate and the cache read; and a deep incremental run widens its dispatch to the full live doc set so the deep namespace actually gets populated. Cache prune/clear now sweep both namespaces so the deep cache can't accumulate orphans. (The skill-side flow that passes the mode through is a follow-up; the CLI is complete and backward compatible — the newmodeargument defaults to the existing behavior.) -
Fix: the semantic cache no longer persists dangling edges/hyperedges (#1916). When a node group was skipped on write (out-of-scope per the batch guard, or a ghost
source_file), edges/hyperedges in the kept groups that referenced those never-written nodes were saved anyway and re-surfaced on every cache replay. Those references are now pruned at write time (gated on the scoping allowlist, so unscoped callers are unchanged), andbuild_from_jsonvalidates hyperedge members against the node set so a dangling hyperedge can't reachgraph.jsoneven from a live extraction. -
Fix:
graphify update/watch no longer produces a bloated graph by double-representing documents (#1915)._rebuild_codeAST-quick-scanned Markdown/doc files and then preserved their existing semantic (LLM) nodes on top, so each doc appeared twice (a real corpus came out ~4x). A doc that already has semantic nodes in the graph is no longer AST-quick-scanned (its semantic nodes are the sole representation), while a doc with no semantic layer still gets the structural quick-scan; incremental rebuilds now preserve a doc's semantic nodes instead of evicting them, and previously-bloated graphs self-heal on the next full rebuild. -
Fix:
.cjs(explicit CommonJS) files are now recognized as code and parsed with the JavaScript grammar (#1912, thanks @Kookwater). The extension was half-registered (present in some internal maps but missing from the classification and dispatch sets), so.cjsfiles were silently dropped. -
Fix: files that become excluded (
.graphifyignore/.gitignore/--exclude) are now pruned from both the graph and the manifest instead of lingering (#1908 / #1909). Two coupled gaps:save_manifestretained any prior row that still existed on disk (disk-existence, not scan-membership), so an excluded-but-present file was reported asdeletedon every run; and the incremental prune set was derived from the manifest alone, so a newly-excluded file's stale nodes carried forward from the existinggraph.jsonwere never removed (the state every 0.9.16 graph is in). Now the incremental prune set also derives from the existing graph's ownsource_files minus the post-exclude corpus (in-root only),save_manifestprunes rows outside the full scan corpus, anddetect_incrementaldistinguishes truly-deleted files from excluded-but-present ones (which are no longer misreported as deleted). -
Fix: PostgreSQL PL/pgSQL functions are no longer silently dropped (#1910).
CREATE FUNCTION ... LANGUAGE plpgsql AS $$...$$withOUTparams, tagged dollar-quotes, or procedural body statements parses as a tree-sitterERRORnode, which the SQL extractor skipped entirely. It now recovers the function/procedure name from an ERROR node via the same regex-fallback pattern the extractor already uses elsewhere, so the function node and itscontainsedge are kept (the unparseable body is left opaque) and surrounding statements are unaffected. -
Security/privacy follow-up: nodes whose
source_filewas never dispatched are now dropped from the graph, not just skipped from the cache (#1895). The #1757 guard stopped a mis-attributed node from clobbering another file's cache entry, but the node itself still flowed intograph.json; it is now filtered out of the merged result (real-file, non-dispatched attributions only), consistent with the cache rejection. -
Fix:
manifest.jsonnow records every successfully-extracted file, not just the zero-node ones (#1897). The #933 stamping filter compared root-relative nodesource_files against absolutedetect()paths, so it dropped every freshly-extracted semantic document from the manifest and broke the incremental-update baseline. Both sides are now resolved before comparison; genuinely omitted/zero-node docs stay unstamped so they retry. -
Fix:
graphify hook installnow registers thegraph.jsonunion merge driver that the README and CHANGELOG have long documented (#1902). It writes themerge.graphifyconfig viagit configand an idempotent, append-onlygraphify-out/graph.json merge=graphifyline in.gitattributes;uninstallremoves them. -
Fix:
hook install/statusno longer print a spurious "could not read core.hooksPath" warning on repos whose.git/configcontains git-legal duplicate keys (VS Code writes these) (#1907). Config is now resolved viagit rev-parse --git-path hooksinstead of a strictconfigparser, which rejected duplicate keys. -
Fix:
graphify export obsidianprunes notes for nodes that left the graph instead of merging old and new on re-export (#1896). Only notes graphify itself wrote (tracked in its ownership manifest) are removed, with a vault-containment guard, so user-authored notes are never touched. -
Fix: non-English query sentences no longer pick wrong BFS seeds because their filler words were unfiltered (#1900). The query stopword set now covers German and the major Romance languages (curated to avoid clobbering English content words), so
Wie funktioniert die Authentifizierung?seeds the keyword, not the stopwords. -
Fix: Python calls to an imported module now resolve (already shipped in 0.9.16);
.skillfiles (Markdown-with-frontmatter agent files) are now classified as documents instead of being silently dropped as an unsupported extension (#1901). -
Fix: the
--postgresmissing-driver error now points at the correct PyPI package,graphifyy[postgres](was the nonexistentgraphify[postgres]) (#1906).