#3574·graphify

AST cache pins node IDs to the original extraction root; WordPress block.json never indexed

Author: lt-cavalcantiCreated Sep 15, 2026Updated Sep 17, 2026

Two separate problems found while auditing a graph that had drifted. Both are in 0.9.16. The second is the more serious one — it makes the graph quietly wrong in a way update cannot repair.

1. WordPress block.json is classified as data JSON and skipped

_is_config_json() in graphify/extractors/json_config.py gates JSON extraction on a filename allowlist (_CONFIG_JSON_NAMES) plus a top-level key probe (_CONFIG_JSON_KEYS). block.json is in neither, so every WordPress block manifest is treated as data JSON and skipped by the structural pass (the #1224 behavior).

On a WordPress monorepo with 73 blocks, 72 produced zero nodes. The single exception declared a top-level "$schema" key, which happens to trip the fallback probe.

block.json is the WordPress block manifest — it is config, not data, and it carries genuinely graph-worthy relationships: name, parent, ancestor, allowedBlocks, providesContext / usesContext, and the editorScript / style handles. It is as much a manifest as package.json, and it is the file that describes which blocks may nest inside which.

Suggested fix: add "block.json" to _CONFIG_JSON_NAMES.

Workaround today: add "$schema": "https://schemas.wp.org/trunk/block.json" to each block.json. That is valid WordPress practice and gives editor autocomplete, but it means touching every block in the repo.

2. The AST cache stores node IDs, so they stay pinned to the original extraction root

Node IDs are derived from the extraction root path. The per-file AST cache stores the fully-formed nodes, IDs included. So when the same repo is later extracted from a different root, every cache hit replays the IDs built under the original root.

Seen concretely on a repo first indexed inside a container at /workspace, then updated from the macOS host checkout:

  • 1,022 of 1,423 cache entries held workspace_-prefixed IDs
  • 5,370 of 17,363 nodes in the graph carried them (31%)
  • 508 files existed in the graph only as stale container-era nodes
  • 51 more carried both a stale and a fresh copy of themselves

Because the old and new IDs never collide, they are not detected as duplicates — they look like unrelated nodes. Node counts go up on every run, nothing warns, and explain returns plausible results. They are just partly results from a tree that no longer exists.

The distribution is the part that makes this hard to notice: contamination tracks edit frequency inversely. Our actively-edited frontend was 0.4% stale, while the least-touched backend plugin was 84% stale — so the code you are most likely to need the graph for is the code most likely to be described wrongly.

graphify update --force does not help, since --force only permits a rebuild with fewer nodes and does not invalidate a cache entry. The only repair we found was moving cache/ast/ aside and rebuilding, which produced a correct 18,386 nodes with zero stale IDs, and widened the extraction from 2,177 files to the full 2,901.

Suggested fixes (either would do):

  • Store root-relative IDs in the cache and apply the root prefix at graph-assembly time, so a cache entry is portable across roots (the file-hash keys already are).
  • Or record the extraction root in the cache namespace alongside the existing package-version namespace, so a root change invalidates the cache the way a version change does.

A cheap mitigation in the meantime: warn when the graph contains node IDs carrying more than one root prefix.

3. Minor: the zero-node warning truncates with no way to recover the full list

The warning names 5 files then (+68 more), with no flag or log file carrying the rest. Recovering the full list meant diffing the graph against a filesystem walk. Writing it to graphify-out/ would make this self-service.