From f84a0af1bf7970d69c5b46fceceb55d444b5407e Mon Sep 17 00:00:00 2001 From: safishamsi Date: Tue, 7 Jul 2026 10:57:52 +0100 Subject: [PATCH] fix(report): don't emit dangling [[_COMMUNITY_*]] wikilinks by default (#1712) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GRAPH_REPORT.md rendered the Community Hubs section as Obsidian wikilinks, but the `_COMMUNITY_*.md` notes they target are only created by the opt-in `--obsidian` export — and the report is written at build time, before any export runs. So on a default run every link dangled: inside an Obsidian vault they spawned phantom `_COMMUNITY_*` nodes in the graph view, and outside Obsidian they rendered as literal brackets that navigate nowhere. `generate()` now takes `obsidian: bool = False`; by default the hubs render as a plain list, and the wikilink form is emitted only when a caller opts in. The Obsidian export's own community notes already cross-link each other, so the vault stays navigable without the report's links. Mirrors the #1444/#1465 portability fix that was applied to `export wiki`. Co-Authored-By: Claude Opus 4.8 (1M context) --- graphify/report.py | 16 ++++++++++++---- tests/test_report.py | 19 +++++++++++++++++++ 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/graphify/report.py b/graphify/report.py index 5bac06d05..248bce9a1 100644 --- a/graphify/report.py +++ b/graphify/report.py @@ -82,6 +82,7 @@ def generate( min_community_size: int = 3, built_at_commit: str | None = None, learning: dict | None = None, + obsidian: bool = False, ) -> str: today = date.today().isoformat() @@ -140,14 +141,21 @@ def generate( "- Run `graphify update .` after code changes (no API cost).", ] - # Community hub navigation - links to _COMMUNITY_*.md files in the Obsidian vault. - # Without these, GRAPH_REPORT.md is a dead-end and the vault splits into disconnected components. + # Community hub navigation. The `_COMMUNITY_*.md` notes these wikilinks target + # are only created by the opt-in `--obsidian` export, and the report is written + # at build time (before any export runs), so emitting wikilinks by default left + # every link dangling — polluting an Obsidian vault's graph view and rendering as + # literal brackets everywhere else (#1712). Emit wikilinks only when the caller + # signals Obsidian output; otherwise a plain list, which navigates nowhere-to-break. if non_empty: lines += ["", "## Community Hubs (Navigation)"] for cid in non_empty: label = community_labels.get(cid, f"Community {cid}") - safe = _safe_community_name(label) - lines.append(f"- [[_COMMUNITY_{safe}|{label}]]") + if obsidian: + safe = _safe_community_name(label) + lines.append(f"- [[_COMMUNITY_{safe}|{label}]]") + else: + lines.append(f"- {label}") lines += [ "", diff --git a/tests/test_report.py b/tests/test_report.py index 00be0f36d..767e2ba34 100644 --- a/tests/test_report.py +++ b/tests/test_report.py @@ -135,3 +135,22 @@ def test_import_cycles_section_absent_for_documents_only_corpus(): tokens = {"input": 0, "output": 0} report = generate(G, communities, cohesion, labels, gods, surprises, detection, tokens, "./project") assert "## Import Cycles" not in report + + +def test_report_hubs_are_plain_text_by_default(): + # #1712: without --obsidian the _COMMUNITY_*.md notes don't exist, so wikilinks + # would dangle (and pollute an Obsidian vault's graph view). Default to plain text. + G, communities, cohesion, labels, gods, surprises, detection, tokens = make_inputs() + labels = {cid: f"Widget {cid}" for cid in communities} + report = generate(G, communities, cohesion, labels, gods, surprises, detection, tokens, "./project", min_community_size=1) + assert "## Community Hubs (Navigation)" in report + assert "[[_COMMUNITY_" not in report, "must not emit dangling Obsidian wikilinks by default (#1712)" + assert any(f"- Widget {cid}" in report for cid in communities) + + +def test_report_hubs_use_wikilinks_when_obsidian(): + # The opt-in path keeps the vault-navigable wikilink form. + G, communities, cohesion, labels, gods, surprises, detection, tokens = make_inputs() + labels = {cid: f"Widget {cid}" for cid in communities} + report = generate(G, communities, cohesion, labels, gods, surprises, detection, tokens, "./project", min_community_size=1, obsidian=True) + assert "[[_COMMUNITY_" in report