Bug: detect_incremental() uses a single global manifest for all subdirectories — --update on one subfolder reports every file from the last-graphed folder as deleted
Version: graphifyy 0.3.28
What happened
Running the skill's --update flow on one subdirectory of a large repo (e.g. workflows/) after a previous full run had graphed a different subdirectory (e.g. experts/) causes deleted_files in the incremental-detect result to list hundreds of paths from that unrelated, previously-graphed folder.
Live case: --update on workflows/ (a workspace with ~15 top-level graphed subfolders, each run via /graphify <subfolder>) returned deleted_files containing 230+ paths under experts/rechts-kanzlei/... — none of which are under workflows/ and none of which were actually deleted. The last folder graphed before this run was experts/.
Root cause
In graphify/detect.py, the manifest path is a module-level constant:
_MANIFEST_PATH = "graphify-out/manifest.json"
load_manifest() / save_manifest() both default to this single flat path regardless of which root was passed to detect_incremental(root). Every /graphify <subfolder> --update run in a multi-subfolder workspace reads and writes the same manifest file, so the manifest only ever reflects whichever subfolder was graphed most recently. The comparison in detect_incremental():
current_files = {f for flist in full["files"].values() for f in flist}
deleted_files = [f for f in manifest if f not in current_files]
flags every manifest entry not under the current run's file set as deleted — including entries that were never under root to begin with.
Impact
If the caller (e.g. the skill's --update flow) prunes graph nodes whose source_file is in deleted_files, this can silently remove nodes belonging to an unrelated subfolder's graph, depending on which graph is currently loaded. In our case the prune step happened to target the just-loaded subfolder graph, which contained none of the flagged paths, so the visible symptom was only a wrong "N files deleted" count in the diff report — but the mechanism is a correctness bug regardless of the specific graph layout, and could delete real nodes in a different call pattern (e.g. running --update against the root graph, or against a subfolder whose graph overlaps the previously-graphed one).
Suggested fix
Scope the manifest path to root by default (e.g. graphify-out/<sanitized-root>/manifest.json, falling back to the existing flat path for the root-level . invocation to avoid a migration step for existing single-folder setups). Additionally, filter deleted_files to only include manifest entries that were ever under root, as defense in depth against a stale or manually merged manifest.
Happy to share a patch if useful — we worked around it locally by scoping _MANIFEST_PATH per-root and adding the under-root filter to deleted_files.
Bug:
detect_incremental()uses a single global manifest for all subdirectories —--updateon one subfolder reports every file from the last-graphed folder as deletedVersion: graphifyy 0.3.28
What happened
Running the skill's
--updateflow on one subdirectory of a large repo (e.g.workflows/) after a previous full run had graphed a different subdirectory (e.g.experts/) causesdeleted_filesin the incremental-detect result to list hundreds of paths from that unrelated, previously-graphed folder.Live case:
--updateonworkflows/(a workspace with ~15 top-level graphed subfolders, each run via/graphify <subfolder>) returneddeleted_filescontaining 230+ paths underexperts/rechts-kanzlei/...— none of which are underworkflows/and none of which were actually deleted. The last folder graphed before this run wasexperts/.Root cause
In
graphify/detect.py, the manifest path is a module-level constant:load_manifest()/save_manifest()both default to this single flat path regardless of whichrootwas passed todetect_incremental(root). Every/graphify <subfolder> --updaterun in a multi-subfolder workspace reads and writes the same manifest file, so the manifest only ever reflects whichever subfolder was graphed most recently. The comparison indetect_incremental():flags every manifest entry not under the current run's file set as deleted — including entries that were never under
rootto begin with.Impact
If the caller (e.g. the skill's
--updateflow) prunes graph nodes whosesource_fileis indeleted_files, this can silently remove nodes belonging to an unrelated subfolder's graph, depending on which graph is currently loaded. In our case the prune step happened to target the just-loaded subfolder graph, which contained none of the flagged paths, so the visible symptom was only a wrong "N files deleted" count in the diff report — but the mechanism is a correctness bug regardless of the specific graph layout, and could delete real nodes in a different call pattern (e.g. running--updateagainst the root graph, or against a subfolder whose graph overlaps the previously-graphed one).Suggested fix
Scope the manifest path to
rootby default (e.g.graphify-out/<sanitized-root>/manifest.json, falling back to the existing flat path for the root-level.invocation to avoid a migration step for existing single-folder setups). Additionally, filterdeleted_filesto only include manifest entries that were ever underroot, as defense in depth against a stale or manually merged manifest.Happy to share a patch if useful — we worked around it locally by scoping
_MANIFEST_PATHper-root and adding the under-root filter todeleted_files.