diff --git a/graphify/wiki.py b/graphify/wiki.py index 11c3735b1..973233306 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 "" 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,18 @@ def _community_article( for nid in top_nodes: d = G.nodes[nid] node_label = d.get("label", nid) - src = d.get("source_file", "") + src = str(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}") + + 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: lines.append(f"- *... and {remaining} more nodes in this community*") @@ -186,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", "") + 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 @@ -368,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 diff --git a/tests/test_wiki.py b/tests/test_wiki.py index 130da843d..058386902 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 +