From dcd2b0ffe002d57295ed935af0a32f3638fc9423 Mon Sep 17 00:00:00 2001 From: sravanjcov Date: Tue, 25 Aug 2026 17:30:37 +0530 Subject: [PATCH 1/2] feat(wiki): link god-node key concepts in community articles and normalize Windows paths Community articles now link Key Concepts entries to their god-node article when one exists, using the existing _md_link/resolver pattern. Nodes without an article remain plain bold text (no dead links). Source file paths containing Windows backslashes are normalized to forward slashes in both community and god-node articles, so markdown renders consistently across platforms. Tests: test_community_article_links_god_node_key_concept, test_wiki_normalizes_windows_backslashes_in_sources --- graphify/wiki.py | 9 +++++---- tests/test_wiki.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/graphify/wiki.py b/graphify/wiki.py index 11c3735b14..2211ff8861 100644 --- a/graphify/wiki.py +++ b/graphify/wiki.py @@ -134,7 +134,7 @@ def _community_article( ) total_edges = sum(conf_counts.values()) or 1 - sources = sorted({G.nodes[n].get("source_file") or "" for n in nodes} - {""}) + sources = sorted({(G.nodes[n].get("source_file") or "").replace("\\", "/") for n in nodes} - {""}) lines: list[str] = [] lines += [f"# {label}", ""] @@ -148,10 +148,11 @@ def _community_article( for nid in top_nodes: d = G.nodes[nid] node_label = d.get("label", nid) - src = d.get("source_file", "") + src = (d.get("source_file") or "").replace("\\", "/") degree = G.degree(nid) src_str = f" — `{src}`" if src else "" - lines.append(f"- **{node_label}** ({degree} connections){src_str}") + linked_label = _md_link(node_label, resolver) + lines.append(f"- **{linked_label}** ({degree} connections){src_str}") remaining = len(nodes) - len(top_nodes) if remaining > 0: lines.append(f"- *... and {remaining} more nodes in this community*") @@ -186,7 +187,7 @@ def _god_node_article(G: nx.Graph, nid: str, labels: dict[int, str], node_commun resolver = resolver or {} d = G.nodes[nid] node_label = d.get("label", nid) - src = d.get("source_file", "") + src = (d.get("source_file") or "").replace("\\", "/") cid = (node_community or {}).get(nid) community_name = labels.get(cid, f"Community {cid}") if cid is not None else None diff --git a/tests/test_wiki.py b/tests/test_wiki.py index 130da843dc..0583869028 100644 --- a/tests/test_wiki.py +++ b/tests/test_wiki.py @@ -428,3 +428,36 @@ def test_wiki_links_use_collision_suffixed_slug(tmp_path): assert "parser_2.md" in index_targets # link points at the suffixed file... for t in index_targets: assert (tmp_path / t).exists(), t # ...and every target is a real file + + +def test_community_article_links_god_node_key_concept(tmp_path): + """Key concepts that have their own god node article should be linked in the community article.""" + G = _make_graph() + to_wiki(G, COMMUNITIES, tmp_path, community_labels=LABELS, god_nodes_data=GOD_NODES) + article = (tmp_path / "Parsing_Layer.md").read_text() + # 'parse' is a god node, so it should be linked in Key Concepts + assert "**[parse](parse.md)**" in article + # 'validate' is not a god node, so it should remain bold plain text + assert "**validate**" in article + assert "](validate.md)" not in article + + +def test_wiki_normalizes_windows_backslashes_in_sources(tmp_path): + """Windows-style backslashes in source_file paths should be normalized to forward slashes.""" + G = nx.Graph() + G.add_node("n1", label="parse", file_type="code", source_file="src\\parser\\main.py", community=0) + G.add_node("n2", label="render", file_type="code", source_file="src\\renderer\\main.py", community=1) + G.add_edge("n1", "n2", relation="references", confidence="INFERRED", weight=1.0) + communities = {0: ["n1"], 1: ["n2"]} + labels = {0: "Parsing", 1: "Rendering"} + god_nodes = [{"id": "n1", "label": "parse", "degree": 1}] + to_wiki(G, communities, tmp_path, community_labels=labels, god_nodes_data=god_nodes) + + parsing = (tmp_path / "Parsing.md").read_text() + assert "`src/parser/main.py`" in parsing + assert "\\" not in parsing + + god = (tmp_path / "parse.md").read_text() + assert "`src/parser/main.py`" in god + assert "\\" not in god + From 5ac8e5dc4327077845177f96c932cd8b0cb9745a Mon Sep 17 00:00:00 2001 From: sravanjcov Date: Wed, 26 Aug 2026 11:19:52 +0530 Subject: [PATCH 2/2] fix(wiki): address bot review advisory findings on source_file types, precise node-ID mapping, and markdown escaping --- graphify/wiki.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/graphify/wiki.py b/graphify/wiki.py index 2211ff8861..973233306b 100644 --- a/graphify/wiki.py +++ b/graphify/wiki.py @@ -110,6 +110,7 @@ def _community_article( cohesion: float | None, node_community: dict[str, int] | None = None, resolver: dict[str, str] | None = None, + god_node_slugs: dict[str, str] | None = None, ) -> str: resolver = resolver or {} top_nodes = sorted(nodes, key=lambda n: G.degree(n), reverse=True)[:25] @@ -134,7 +135,7 @@ def _community_article( ) total_edges = sum(conf_counts.values()) or 1 - sources = sorted({(G.nodes[n].get("source_file") or "").replace("\\", "/") for n in nodes} - {""}) + sources = sorted({str(G.nodes[n].get("source_file") or "").replace("\\", "/") for n in nodes} - {""}) lines: list[str] = [] lines += [f"# {label}", ""] @@ -148,10 +149,17 @@ def _community_article( for nid in top_nodes: d = G.nodes[nid] node_label = d.get("label", nid) - src = (d.get("source_file") or "").replace("\\", "/") + src = str(d.get("source_file") or "").replace("\\", "/") degree = G.degree(nid) src_str = f" — `{src}`" if src else "" - linked_label = _md_link(node_label, resolver) + + escaped_label = node_label.replace("[", r"\[").replace("]", r"\]") + slug = (god_node_slugs or {}).get(nid) + if slug: + linked_label = f"[{escaped_label}]({slug}.md)" + else: + linked_label = escaped_label + lines.append(f"- **{linked_label}** ({degree} connections){src_str}") remaining = len(nodes) - len(top_nodes) if remaining > 0: @@ -187,7 +195,7 @@ def _god_node_article(G: nx.Graph, nid: str, labels: dict[int, str], node_commun resolver = resolver or {} d = G.nodes[nid] node_label = d.get("label", nid) - src = (d.get("source_file") or "").replace("\\", "/") + src = str(d.get("source_file") or "").replace("\\", "/") cid = (node_community or {}).get(nid) community_name = labels.get(cid, f"Community {cid}") if cid is not None else None @@ -369,17 +377,19 @@ def _unique_slug(base: str) -> str: resolver.setdefault(label, slug) god_articles: list[tuple[str, str]] = [] # (node_id, slug) + god_node_slugs: dict[str, str] = {} for node_data in god_nodes_data: nid = node_data.get("id") if nid and nid in G: slug = _unique_slug(_safe_filename(node_data['label'], _slug_limit)) god_articles.append((nid, slug)) + god_node_slugs[nid] = slug resolver.setdefault(node_data['label'], slug) # Second pass: render and write each article with the full resolver in hand. for cid, nodes in communities.items(): label = labels.get(cid, f"Community {cid}") - article = _community_article(G, cid, nodes, label, labels, cohesion.get(cid), node_community, resolver) + article = _community_article(G, cid, nodes, label, labels, cohesion.get(cid), node_community, resolver, god_node_slugs) (out / f"{community_slugs[cid]}.md").write_text(article, encoding="utf-8") count += 1