Skip to content

graphify 0.9.17

Latest

Choose a tag to compare

@safishamsi safishamsi released this 16 Jul 10:58
  • Acknowledgement: Amp (ampcode.com) platform support, which shipped earlier in v8, was contributed by @zuwasi in #948 (the skill-amp.md skill and the graphify amp install wiring). Belated credit for the work.

  • Fix: a missing manifest.json no longer degrades graphify extract --code-only into 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 both manifest.json and graph.json; with only the graph present it fell to a full scan, and under --code-only that dropped every doc/paper/image node — silently replacing a curated graph with an AST-only skeleton. An existing graph.json is now a sufficient incremental baseline: detect_incremental already treats an absent manifest as "everything new / nothing deleted", so build_merge + _stale_graph_sources preserve 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 only nodes and edges, never hyperedges, 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_file keying 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 named Page, so App\Models\Page and an imported Filament\Pages\Page fused into one node, manufacturing a false inherits/imports edge 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/.graphifyignore rule in a new ignored diagnostic 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_remap is 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 _origin is persisted, every graphify update re-fed nodes through the remap, so the ids grew unboundedly and the same_topology/same_graph/no_change short-circuits never fired — rewriting graph.json and re-running clustering on every zero-delta update. The remap now skips an id that already carries its canonical stem (mirroring the graph_has_legacy_ids check), while a genuine one-time legacy migration still applies. (An already-corrupted graph needs one graphify extract --force to reset the grown ids.)

  • Perf: graphify query now 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_query now computes the combined ranking and each token's singleton winner in a single traversal, feeding _pick_seeds via best_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 deep is now effective over a warm cache instead of a silent no-op (#1894). The semantic cache is namespaced by mode (semantic vs semantic-deep) so a shallow-cached file no longer satisfies a deep run; graphify extract gains --force (and honors GRAPHIFY_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 new mode argument 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), and build_from_json validates hyperedge members against the node set so a dangling hyperedge can't reach graph.json even from a live extraction.

  • Fix: graphify update/watch no longer produces a bloated graph by double-representing documents (#1915). _rebuild_code AST-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 .cjs files 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_manifest retained any prior row that still existed on disk (disk-existence, not scan-membership), so an excluded-but-present file was reported as deleted on 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 existing graph.json were never removed (the state every 0.9.16 graph is in). Now the incremental prune set also derives from the existing graph's own source_files minus the post-exclude corpus (in-root only), save_manifest prunes rows outside the full scan corpus, and detect_incremental distinguishes 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 $$...$$ with OUT params, tagged dollar-quotes, or procedural body statements parses as a tree-sitter ERROR node, 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 its contains edge are kept (the unparseable body is left opaque) and surrounding statements are unaffected.

  • Security/privacy follow-up: nodes whose source_file was 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 into graph.json; it is now filtered out of the merged result (real-file, non-dispatched attributions only), consistent with the cache rejection.

  • Fix: manifest.json now records every successfully-extracted file, not just the zero-node ones (#1897). The #933 stamping filter compared root-relative node source_files against absolute detect() 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 install now registers the graph.json union merge driver that the README and CHANGELOG have long documented (#1902). It writes the merge.graphify config via git config and an idempotent, append-only graphify-out/graph.json merge=graphify line in .gitattributes; uninstall removes them.

  • Fix: hook install/status no longer print a spurious "could not read core.hooksPath" warning on repos whose .git/config contains git-legal duplicate keys (VS Code writes these) (#1907). Config is now resolved via git rev-parse --git-path hooks instead of a strict configparser, which rejected duplicate keys.

  • Fix: graphify export obsidian prunes 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); .skill files (Markdown-with-frontmatter agent files) are now classified as documents instead of being silently dropped as an unsupported extension (#1901).

  • Fix: the --postgres missing-driver error now points at the correct PyPI package, graphifyy[postgres] (was the nonexistent graphify[postgres]) (#1906).