test(detect): cover ast-kind no-op churn and corrupt-manifest write (#2838)
Adds the two cases the deep-dive flagged: the kind='ast' path (the literal graphify update scenario) stays byte-identical on a no-op and updates on a real edit; and a corrupt/unparseable existing manifest still gets written (the byte-equality skip's except must never silently drop a real update). Also adds the CHANGELOG entry. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
173a8814d2
commit
97f2bb7540
@@ -4,6 +4,7 @@ Full release notes with details on each version: [GitHub Releases](https://githu
|
||||
|
||||
## 0.9.47 (unreleased)
|
||||
|
||||
- Fix: `graphify update` / `save_manifest` no longer rewrites `manifest.json` timestamps on a no-op run, so `graphify-out/` stops showing as dirty (and stops producing a trailing graph commit) when nothing changed; a genuine change still updates and persists (#2838, thanks @hopstreax).
|
||||
- Fix: a C# 12 primary constructor's parameters are now walked, so `class Svc(IRepo repo)` emits the `references` edge to `IRepo` and calls through `repo` resolve — previously the class silently dropped its dependency; built-in and type-parameter types are not fabricated (#2829, thanks @brobl2008).
|
||||
- Fix: AST-derived INFERRED edges now carry a rubric `confidence_score` keyed to the relation (`uses` 0.95, `indirect_call`/unresolved cross-file `calls` 0.85) instead of landing at the rubric-forbidden 0.5 or a flat 0.8; the INFERRED default moves 0.5→0.55 so every score-less INFERRED edge is on the discrete rubric set (#2813, thanks @abhay-codes07).
|
||||
- Fix: a legacy `graph.json` that stored a numeric edge `confidence` (from a pre-enum version) no longer warns once per edge on every incremental reload; the numeric value is normalized to the `INFERRED` tag with the original float preserved in `confidence_score` (thanks @Trantor-develops).
|
||||
|
||||
@@ -3051,6 +3051,44 @@ def test_save_manifest_noop_skips_disk_write(tmp_path):
|
||||
assert mtime_1 == mtime_2
|
||||
|
||||
|
||||
def test_save_manifest_ast_kind_noop_then_change(tmp_path):
|
||||
"""#2838's literal path: `graphify update` calls save_manifest with kind='ast'.
|
||||
A no-op re-run must leave the manifest byte-identical; a real edit must update it."""
|
||||
import json
|
||||
a = tmp_path / "a.py"
|
||||
a.write_text("x = 1\n", encoding="utf-8")
|
||||
manifest_path = Path(tmp_path / "graphify-out" / "manifest.json")
|
||||
|
||||
save_manifest({"code": [str(a)]}, str(manifest_path), root=tmp_path, kind="ast")
|
||||
bytes_1 = manifest_path.read_bytes()
|
||||
seen_1 = json.loads(bytes_1)["a.py"]["seen"]
|
||||
|
||||
save_manifest({"code": [str(a)]}, str(manifest_path), root=tmp_path, kind="ast") # no-op
|
||||
assert manifest_path.read_bytes() == bytes_1, "ast-kind no-op re-run churned the manifest"
|
||||
|
||||
import time as _t; _t.sleep(0.01)
|
||||
a.write_text("x = 2\n", encoding="utf-8")
|
||||
save_manifest({"code": [str(a)]}, str(manifest_path), root=tmp_path, kind="ast")
|
||||
raw2 = json.loads(manifest_path.read_text(encoding="utf-8"))
|
||||
assert raw2["a.py"]["ast_hash"] != json.loads(bytes_1)["a.py"]["ast_hash"]
|
||||
assert raw2["a.py"]["seen"] >= seen_1
|
||||
|
||||
|
||||
def test_save_manifest_corrupt_existing_manifest_still_writes(tmp_path):
|
||||
"""The byte-equality skip must never turn an unparseable on-disk manifest into
|
||||
a silent no-write — a real update has to persist over corruption."""
|
||||
import json
|
||||
a = tmp_path / "a.py"
|
||||
a.write_text("x = 1\n", encoding="utf-8")
|
||||
manifest_path = Path(tmp_path / "graphify-out" / "manifest.json")
|
||||
manifest_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
manifest_path.write_text("{ this is not valid json", encoding="utf-8")
|
||||
|
||||
save_manifest({"code": [str(a)]}, str(manifest_path), root=tmp_path)
|
||||
raw = json.loads(manifest_path.read_text(encoding="utf-8")) # must parse now
|
||||
assert "a.py" in raw and isinstance(raw["a.py"]["seen"], (int, float))
|
||||
|
||||
|
||||
# ── #2106: sensitive-filter over-match (prose/source rescued, real secrets kept) ──
|
||||
|
||||
@pytest.mark.parametrize("path", [
|
||||
|
||||
Reference in New Issue
Block a user