fix(report): use a portable basename in the GRAPH_REPORT header (#2682)

This commit is contained in:
Ousama Ben Younes
2026-08-13 13:30:24 +01:00
committed by safishamsi
parent 5d8d6113db
commit b3ca490408
2 changed files with 50 additions and 1 deletions
+26 -1
View File
@@ -2,9 +2,34 @@
from __future__ import annotations
import re
from datetime import date
from pathlib import Path
import networkx as nx
def _portable_root_label(root: str) -> str:
"""Portable label for the report header — the project directory basename.
GRAPH_REPORT.md is a tracked artifact in practice, so its header must not
bake the generator host's absolute path into the file: the same graph would
otherwise produce different bytes on different machines and leak the build
machine's directory layout into git history (#2628, same class as #2598).
Taking the basename strips any leading absolute path without touching the
filesystem, and makes `graphify update .`, `graphify update ./proj`, and
`graphify update /abs/path/proj` all label the header `proj`. Only the
degenerate `.`/``/`..` cases need a cwd resolve to recover the real name;
if even that fails, fall back to the raw value.
"""
raw = str(root).replace("\\", "/")
name = Path(raw).name
if name in ("", ".", ".."):
try:
name = Path(raw).resolve().name
except (OSError, RuntimeError):
name = ""
return name or raw
def _safe_community_name(label: str) -> str:
"""Mirrors export.safe_name so community hub filenames and report wikilinks always agree."""
cleaned = re.sub(r'[\\/*?:"<>|#^[\]]', "", label.replace("\r\n", " ").replace("\r", " ").replace("\n", " ")).strip()
@@ -101,7 +126,7 @@ def generate(
inf_avg = round(sum(inf_scores) / len(inf_scores), 2) if inf_scores else None
lines = [
f"# Graph Report - {root} ({today})",
f"# Graph Report - {_portable_root_label(root)} ({today})",
"",
"## Corpus Check",
]
+24
View File
@@ -63,6 +63,30 @@ def test_report_shows_raw_cohesion_scores():
assert "" not in report
def test_report_header_does_not_embed_host_absolute_path():
"""#2628 / #2598: the header must not bake the generator host absolute path
into GRAPH_REPORT.md — it labels with the project directory basename so the
same graph produces the same bytes on any machine."""
G, communities, cohesion, labels, gods, surprises, detection, tokens = make_inputs()
report = generate(G, communities, cohesion, labels, gods, surprises, detection,
tokens, "/Users/mike/dev/apps/secretproj")
header = report.splitlines()[0]
assert "/Users/mike" not in header
assert "secretproj" in header
def test_portable_root_label():
from graphify.report import _portable_root_label
# Absolute paths collapse to the basename on both POSIX and Windows.
assert _portable_root_label("/Users/mike/dev/apps/proj") == "proj"
assert _portable_root_label(r"C:\Users\mike\dev\proj") == "proj"
# A trailing slash still yields the directory name, not an empty label.
assert _portable_root_label("/Users/mike/dev/proj/") == "proj"
# Relative names pass through unchanged.
assert _portable_root_label("./project") == "project"
assert _portable_root_label("project") == "project"
# --- work-memory lessons section ----------------------------------------------
def test_report_work_memory_section_present_with_overlay_and_dead_ends():