Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions graphify/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,16 @@ def save_cached(path: Path, result: dict, root: Path = Path(".")) -> None:

Stores as graphify-out/cache/{hash}.json where hash = SHA256 of current file contents.
result should be a dict with 'nodes' and 'edges' lists.

No-ops if `path` is not a regular file. Subagent-produced semantic fragments
occasionally carry a directory path in `source_file` (e.g. an abstract base
whose "source" the model inferred as a folder). Skipping such entries keeps
save_semantic_cache from aborting mid-batch on a single malformed node.
"""
h = file_hash(path, root)
p = Path(path)
if not p.is_file():
return
h = file_hash(p, root)
entry = cache_dir(root) / f"{h}.json"
tmp = entry.with_suffix(".tmp")
try:
Expand Down Expand Up @@ -163,7 +171,7 @@ def save_semantic_cache(
p = Path(fpath)
if not p.is_absolute():
p = Path(root) / p
if p.exists():
if p.is_file():
save_cached(p, result, root)
saved += 1
return saved
35 changes: 34 additions & 1 deletion tests/test_cache.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Tests for graphify/cache.py."""
import pytest
from pathlib import Path
from graphify.cache import file_hash, cache_dir, load_cached, save_cached, cached_files, clear_cache, _body_content
from graphify.cache import file_hash, cache_dir, load_cached, save_cached, save_semantic_cache, cached_files, clear_cache, _body_content


@pytest.fixture
Expand Down Expand Up @@ -124,3 +124,36 @@ def test_body_content_no_frontmatter():
"""_body_content returns content unchanged when no frontmatter present."""
content = b"No frontmatter here."
assert _body_content(content) == content


def test_save_cached_noop_on_directory(tmp_path, cache_root):
"""save_cached silently skips directory paths instead of erroring."""
d = tmp_path / "some_dir"
d.mkdir()
# Should not raise IsADirectoryError (errno 21) and should not write anything.
save_cached(d, {"nodes": [], "edges": []}, root=cache_root)
cache_files = list((cache_root / "graphify-out" / "cache").glob("*.json"))
assert cache_files == []


def test_save_semantic_cache_skips_directory_source_file(tmp_path, cache_root):
"""Nodes whose source_file points at a directory don't abort the batch."""
# Real file + a directory masquerading as source_file
real = tmp_path / "real.ts"
real.write_text("export const x = 1;")
bogus_dir = tmp_path / "abstracts"
bogus_dir.mkdir()

nodes = [
{"id": "n_real", "source_file": str(real)},
{"id": "n_bogus", "source_file": str(bogus_dir)},
]
saved = save_semantic_cache(nodes, [], [], root=cache_root)
assert saved == 1 # only the real file got cached


def test_save_semantic_cache_skips_missing_file(tmp_path, cache_root):
"""Nodes referencing a non-existent source_file are ignored."""
nodes = [{"id": "n_missing", "source_file": str(tmp_path / "does_not_exist.ts")}]
saved = save_semantic_cache(nodes, [], [], root=cache_root)
assert saved == 0