mirror of
https://github.com/safishamsi/graphify.git
synced 2026-08-29 01:36:33 +00:00
Extractors mint node ids from the path STRING they are handed (_make_id(str(path)), _file_node_id(path)), so an AST cache entry written under root A embeds A's slug in every id and edge endpoint. save_cached relativized only source_file, never the ids. Because extract()'s id-remap / final-canonicalization passes key their rewrites off the CURRENT run's paths, an A-derived id matches no key on a warm hit under root B (a clone, a moved checkout, a second mount) and the stale machine slug survives into graph.json. Distinct from #2231/#2243, which fix producers on a cold run, and from #2199 (stat-index portability). Entries are now stored root-anchored and re-anchored on read, the same store-portable/re-anchor-on-load contract source_file (#777) and the stat index (#2199) already use: _relativize_ids_in replaces the root's contribution with a $graphify-root$ marker on write, _absolutize_ids_in restores what the current run's extractor would mint on read. That is the pre-remap form every downstream pass in extract() expects, so a replay reproduces a cold run exactly and no other pass changed. The anchor is derived per entry rather than assumed equal to the scan root (normalize_id distributes over path joins), so a symlinked root or relative inputs decompose exactly; only absolute root spellings may anchor, or a relative root ("src") would rewrite an already-canonical src_utils_foo into an absolute-derived id on the semantic path. The walk covers the whole payload rather than a bucket list, since the id form is self-identifying: that also reaches raw_calls[].caller_nid, swift_extensions[].nid, edges[].target_file, bash_sources[].source_file and *_type_table.path, the last three being resolution inputs that would otherwise still point at root A. save_cached's deepcopy is now unconditional; the old truthiness gate skipped it for a payload whose only content lives outside nodes/edges, which would have let the transform mutate the caller's dict and break cold-run remapping. Pre-fix entries carry no marker and their content hash never changes, so they cannot self-heal; they are swept when the release bumps the version, since AST entries live under cache/ast/v{version}/. Tests: extract a python/C/bash/markdown corpus under root A, copy the tree and graphify-out to root B, extract under B on the warm cache, and assert the run is genuinely warm (zero extractor calls), that no node id or edge endpoint carries A's slug, that the on-disk entries hold neither A's slug nor an absolute path, that a cold run still yields canonical ids, and that warm and cold match exactly. The fixture avoids JS/TS on purpose: those suffixes bypass the cache, which would make the warm assertions vacuous.