Skip to content

Comments

fix: render inter-domain relationships in graph2md#7

Open
jonathanpopham wants to merge 3 commits intomainfrom
fix/inter-domain-relationships
Open

fix: render inter-domain relationships in graph2md#7
jonathanpopham wants to merge 3 commits intomainfrom
fix/inter-domain-relationships

Conversation

@jonathanpopham
Copy link

@jonathanpopham jonathanpopham commented Feb 17, 2026

Summary

  • Domain entity pages on repos.supermodeltools.com showed no connections between domains because graph2md silently dropped domain-to-domain relationships
  • Added domainEdge struct and domainRelatesOut/domainRelatesIn index maps that capture any relationship connecting two Domain nodes
  • Updated writeGraphData(), writeMermaidDiagram(), and writeDomainBody() to render outgoing and incoming domain relationships
  • Created first test suite for graph2md (graph2md_test.go) with 7 tests including end-to-end

Test plan

  • go test ./... — all 7 tests pass
  • go vet ./... — clean
  • CI passes (CodeRabbit, build)
  • Verify on a live repo that domain pages now show related domains in the body, Mermaid diagram, and graph_data frontmatter

Closes #6

Summary by CodeRabbit

  • New Features

    • Domains now include a "Related Domains" section showing outgoing and incoming connections with relationship types
    • Diagrams and graph data visualize domain-to-domain edges with direction and labels
  • Improvements

    • Relationship data consistently propagated into rendering output and diagrams
    • Input validation strengthened and output directory creation moved earlier
  • Tests

    • Added comprehensive tests for domain relationships, diagram/graph output, and end-to-end rendering

Note

Medium Risk
Changes markdown/frontmatter generation for Domain pages by indexing previously-ignored relationships and emitting new edges/sections, which can affect site output and any consumers of graph_data/Mermaid diagrams. Test coverage is added, but behavior changes may surface formatting or size-limit edge cases on large graphs.

Overview
graph2md now indexes any relationship between two Domain nodes (captured as domainRelatesOut/domainRelatesIn) instead of silently dropping them.

Domain pages render these connections in three places: a new Related Domains section in the body (with relationship type and incoming/outgoing labeling), additional relatesTo edges in graph_data frontmatter, and labeled arrows in the Mermaid domain diagram.

Adds a new graph2md_test.go suite with unit and end-to-end tests asserting relationship indexing and the new rendered outputs.

Written by Cursor Bugbot for commit 33ef5a4. This will update automatically on new commits. Configure here.

Domain entity pages were missing connections between domains because
the relationship switch block silently dropped domain-to-domain edges.

Add domainEdge struct and domainRelatesOut/domainRelatesIn index maps,
populate them via a default case for any relationship connecting two
Domain nodes, and render them in writeGraphData, writeMermaidDiagram,
and writeDomainBody.

Closes #6
@coderabbitai
Copy link

coderabbitai bot commented Feb 17, 2026

Walkthrough

Parses inter-domain edges into new internal indexes, threads them through renderContext, and renders Related Domains in domain body text, graph_data frontmatter, and Mermaid diagrams; also adds input validation and early output-directory creation.

Changes

Cohort / File(s) Summary
Core parsing & rendering logic
internal/graph2md/graph2md.go
Adds an internal domainEdge type and domainRelatesOut/domainRelatesIn maps; collects domain→domain relationships during graph parsing; wires these maps into renderContext; updates graph_data generation, Mermaid diagram output, and domain body rendering to include Related Domains (outgoing/incoming) with labels; adds Run input validation and creates output dir early.
Tests
internal/graph2md/graph2md_test.go
Adds comprehensive tests and helpers to construct nodes/relationships and a minimal renderContext; verifies domain relationship indexing (filtering non-domain nodes), graph_data includes relatesTo edges, Mermaid/frontmatter rendering, domain body Related Domains sections, and end-to-end markdown output checks.

Sequence Diagram(s)

sequenceDiagram
  participant Parser as Graph Parser
  participant Indexer as Domain Indexer
  participant Renderer as Render Engine
  participant Writer as File Writer

  Parser->>Indexer: emit nodes & relationships
  Indexer->>Indexer: build domainRelatesOut / domainRelatesIn
  Indexer->>Renderer: pass nodes, rels, domainRelates maps
  Renderer->>Renderer: generate domain body, graph_data, mermaid (include relatesTo edges)
  Renderer->>Writer: write markdown files with frontmatter & diagrams
  Writer-->>Renderer: confirm write
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

  • supermodeltools/graph2md#1: Direct match — implements domain-to-domain edge indexing and renders them in graph_data and diagrams.
  • Domain entity pages missing inter-domain relationships #5: Related — touches the same graph2md areas to surface domain relates-to edges in generated outputs.

Poem

Domains whisper, then loudly meet,
Arrows drawn where networks greet.
Indexes mapping who relates to who,
Diagrams, JSON, pages too—
Small code, big link: the graph grew.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 35.71% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix: render inter-domain relationships in graph2md' directly summarizes the main change: enabling the rendering of relationships between Domain nodes that were previously being silently dropped.
Linked Issues check ✅ Passed All three coding requirements from issue #6 are met: domainEdge struct and index maps capture domain-to-domain relationships, Related Domains section added to domain body, and Mermaid diagrams now display these relationships with labels.
Out of Scope Changes check ✅ Passed All changes are directly scoped to capturing and rendering domain-to-domain relationships; input validation and output directory creation are reasonable supporting improvements for the core feature.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/inter-domain-relationships

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
internal/graph2md/graph2md_test.go (1)

56-101: Consider populating domainNodeByName for completeness.

Right now the helper sets up nodeLookup and slugLookup, but domainNodeByName stays empty (line 93). This is fine for the current tests, but if you later add a test that exercises domainLink() (which looks up domains by name), it'll return plain text instead of a link.

Quick fix if you ever need it:

♻️ Suggested enhancement
 	for i := range allNodes {
 		n := &allNodes[i]
 		nodeLookup[n.ID] = n
 		slugLookup[n.ID] = toSlug(getStr(n.Properties, "name"))
+		if hasLabel(n, "Domain") {
+			// Populate domainNodeByName so domainLink() works
+			domainNodeByName[getStr(n.Properties, "name")] = n.ID
+		}
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/graph2md/graph2md_test.go` around lines 56 - 101, The helper
domainRenderCtx builds nodeLookup and slugLookup but leaves domainNodeByName
empty; update domainRenderCtx to populate domainNodeByName by iterating allNodes
(and center) and mapping each node's name (getStr(node.Properties,"name")) or
slug to the node ID using the same toSlug logic so domainNodeByName[nameOrSlug]
= node.ID; ensure you handle center the same way as other nodes and use the
existing toSlug/getStr helpers so domainLink() can resolve names to links.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/graph2md/graph2md_test.go`:
- Around line 407-424: The extractGraphDataJSON test helper can panic when the
closing quote pattern isn't found; change the logic in extractGraphDataJSON to
compute endIdx := strings.Index(rest, "\"\n") and if endIdx == -1 call t.Fatalf
with a clear message (including the offending frontmatter or rest) instead of
slicing with -1, then build full using rest[:endIdx] as before and proceed with
json.Unmarshal; this prevents a slice bounds panic and gives a helpful test
failure message.

---

Nitpick comments:
In `@internal/graph2md/graph2md_test.go`:
- Around line 56-101: The helper domainRenderCtx builds nodeLookup and
slugLookup but leaves domainNodeByName empty; update domainRenderCtx to populate
domainNodeByName by iterating allNodes (and center) and mapping each node's name
(getStr(node.Properties,"name")) or slug to the node ID using the same toSlug
logic so domainNodeByName[nameOrSlug] = node.ID; ensure you handle center the
same way as other nodes and use the existing toSlug/getStr helpers so
domainLink() can resolve names to links.

- Guard against panic in extractGraphDataJSON when closing quote not found
- Populate domainNodeByName in test helper so domainLink() resolves correctly
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
internal/graph2md/graph2md_test.go (1)

23-31: Unused helper function makeSubdomainNode.

Hey, heads up — this helper is defined but never actually called anywhere in the test file. It's like buying ingredients for a recipe you never cooked.

If you're planning to add subdomain tests later, totally fine to keep it around. Otherwise, consider removing it to keep things tidy.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/graph2md/graph2md_test.go` around lines 23 - 31, The helper function
makeSubdomainNode is defined but never used; either remove the unused function
to tidy the test file or, if you intend to test subdomains, add tests that call
makeSubdomainNode (referencing the makeSubdomainNode function and the Node type)
so it is exercised; pick one and apply the change consistently in
graph2md_test.go.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@internal/graph2md/graph2md_test.go`:
- Around line 23-31: The helper function makeSubdomainNode is defined but never
used; either remove the unused function to tidy the test file or, if you intend
to test subdomains, add tests that call makeSubdomainNode (referencing the
makeSubdomainNode function and the Node type) so it is exercised; pick one and
apply the change consistently in graph2md_test.go.

@jonathanpopham jonathanpopham self-assigned this Feb 18, 2026
@jonathanpopham jonathanpopham marked this pull request as ready for review February 18, 2026 20:08
Intra-domain relationships were being promoted to domain-level edges,
creating noisy self-referencing loops in mermaid diagrams and graph data.
Skip relationships where start and end node are the same domain.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
internal/graph2md/graph2md.go (1)

1623-1639: Consider preserving the original relationship type in graph_data.

This works correctly, but FYI: you're capturing the original de.relType (like "DOMAIN_RELATES" or "dependsOn") in the struct, but here you're using the generic "relatesTo" for all edges.

If downstream consumers of graph_data (like visualization tools) could benefit from knowing the specific relationship type, you could use de.relType instead:

// Instead of:
}{[]string{de.nodeID}, "relatesTo", false})
// Could be:
}{[]string{de.nodeID}, de.relType, false})

That said, if you want graph_data to have a normalized/consistent schema (all domain relations as "relatesTo"), the current approach is totally valid. Just wanted to flag the trade-off.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/graph2md/graph2md.go` around lines 1623 - 1639, The current loops
that append to relSets for c.domainRelatesOut and c.domainRelatesIn always set
relType to the literal "relatesTo" which loses the original de.relType; modify
the append payloads in the loops that build relSets (the blocks referencing
c.domainRelatesOut[c.node.ID] and c.domainRelatesIn[c.node.ID]) to use
de.relType instead of the hardcoded "relatesTo" so the struct carries the
original relationship type (preserve reverse as currently set for incoming
edges).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/graph2md/graph2md.go`:
- Around line 216-223: The default case that builds
domainRelatesOut/domainRelatesIn can append duplicate domainEdge entries when
allRels contains the same relationship from multiple files; change the logic in
that block (the default: case that uses startNode/endNode, hasLabel, and appends
domainEdge) to check for an existing edge before appending by comparing nodeID
and relType (or maintain a temporary set keyed by fmt.Sprintf("%d|%s") or
similar) and only append when not already present, and apply the same dedupe
when adding to both domainRelatesOut[rel.StartNode] and
domainRelatesIn[rel.EndNode].

---

Nitpick comments:
In `@internal/graph2md/graph2md.go`:
- Around line 1623-1639: The current loops that append to relSets for
c.domainRelatesOut and c.domainRelatesIn always set relType to the literal
"relatesTo" which loses the original de.relType; modify the append payloads in
the loops that build relSets (the blocks referencing
c.domainRelatesOut[c.node.ID] and c.domainRelatesIn[c.node.ID]) to use
de.relType instead of the hardcoded "relatesTo" so the struct carries the
original relationship type (preserve reverse as currently set for incoming
edges).

Comment on lines +216 to +223
default:
// Capture domain-to-domain relationships (any type connecting two Domain nodes)
startNode := nodeLookup[rel.StartNode]
endNode := nodeLookup[rel.EndNode]
if startNode != nil && endNode != nil && hasLabel(startNode, "Domain") && hasLabel(endNode, "Domain") && rel.StartNode != rel.EndNode {
domainRelatesOut[rel.StartNode] = append(domainRelatesOut[rel.StartNode], domainEdge{nodeID: rel.EndNode, relType: rel.Type})
domainRelatesIn[rel.EndNode] = append(domainRelatesIn[rel.EndNode], domainEdge{nodeID: rel.StartNode, relType: rel.Type})
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Potential duplicate domain edges when merging multiple graph files.

Hey, quick heads-up: allRels at line 130 appends relationships from each input file without deduplication. So if the same domain-to-domain relationship appears in multiple files, you'll get duplicate entries in domainRelatesOut/domainRelatesIn. This would show the same related domain multiple times in the "Related Domains" section and Mermaid diagram.

For most real-world cases this probably won't happen, but if you want to be defensive, you could dedupe by checking if an edge with the same nodeID and relType already exists before appending.

Example scenario: If graph1.json and graph2.json both contain the edge Auth --DOMAIN_RELATES--> Payments, the Auth domain page would list Payments twice.

🛡️ Optional fix to deduplicate edges
 		default:
 			// Capture domain-to-domain relationships (any type connecting two Domain nodes)
 			startNode := nodeLookup[rel.StartNode]
 			endNode := nodeLookup[rel.EndNode]
 			if startNode != nil && endNode != nil && hasLabel(startNode, "Domain") && hasLabel(endNode, "Domain") && rel.StartNode != rel.EndNode {
+				// Check for duplicate before appending
+				isDuplicateOut := false
+				for _, existing := range domainRelatesOut[rel.StartNode] {
+					if existing.nodeID == rel.EndNode && existing.relType == rel.Type {
+						isDuplicateOut = true
+						break
+					}
+				}
+				if !isDuplicateOut {
+					domainRelatesOut[rel.StartNode] = append(domainRelatesOut[rel.StartNode], domainEdge{nodeID: rel.EndNode, relType: rel.Type})
+				}
+				isDuplicateIn := false
+				for _, existing := range domainRelatesIn[rel.EndNode] {
+					if existing.nodeID == rel.StartNode && existing.relType == rel.Type {
+						isDuplicateIn = true
+						break
+					}
+				}
+				if !isDuplicateIn {
+					domainRelatesIn[rel.EndNode] = append(domainRelatesIn[rel.EndNode], domainEdge{nodeID: rel.StartNode, relType: rel.Type})
+				}
-				domainRelatesOut[rel.StartNode] = append(domainRelatesOut[rel.StartNode], domainEdge{nodeID: rel.EndNode, relType: rel.Type})
-				domainRelatesIn[rel.EndNode] = append(domainRelatesIn[rel.EndNode], domainEdge{nodeID: rel.StartNode, relType: rel.Type})
 			}
 		}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
default:
// Capture domain-to-domain relationships (any type connecting two Domain nodes)
startNode := nodeLookup[rel.StartNode]
endNode := nodeLookup[rel.EndNode]
if startNode != nil && endNode != nil && hasLabel(startNode, "Domain") && hasLabel(endNode, "Domain") && rel.StartNode != rel.EndNode {
domainRelatesOut[rel.StartNode] = append(domainRelatesOut[rel.StartNode], domainEdge{nodeID: rel.EndNode, relType: rel.Type})
domainRelatesIn[rel.EndNode] = append(domainRelatesIn[rel.EndNode], domainEdge{nodeID: rel.StartNode, relType: rel.Type})
}
default:
// Capture domain-to-domain relationships (any type connecting two Domain nodes)
startNode := nodeLookup[rel.StartNode]
endNode := nodeLookup[rel.EndNode]
if startNode != nil && endNode != nil && hasLabel(startNode, "Domain") && hasLabel(endNode, "Domain") && rel.StartNode != rel.EndNode {
// Check for duplicate before appending
isDuplicateOut := false
for _, existing := range domainRelatesOut[rel.StartNode] {
if existing.nodeID == rel.EndNode && existing.relType == rel.Type {
isDuplicateOut = true
break
}
}
if !isDuplicateOut {
domainRelatesOut[rel.StartNode] = append(domainRelatesOut[rel.StartNode], domainEdge{nodeID: rel.EndNode, relType: rel.Type})
}
isDuplicateIn := false
for _, existing := range domainRelatesIn[rel.EndNode] {
if existing.nodeID == rel.StartNode && existing.relType == rel.Type {
isDuplicateIn = true
break
}
}
if !isDuplicateIn {
domainRelatesIn[rel.EndNode] = append(domainRelatesIn[rel.EndNode], domainEdge{nodeID: rel.StartNode, relType: rel.Type})
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/graph2md/graph2md.go` around lines 216 - 223, The default case that
builds domainRelatesOut/domainRelatesIn can append duplicate domainEdge entries
when allRels contains the same relationship from multiple files; change the
logic in that block (the default: case that uses startNode/endNode, hasLabel,
and appends domainEdge) to check for an existing edge before appending by
comparing nodeID and relType (or maintain a temporary set keyed by
fmt.Sprintf("%d|%s") or similar) and only append when not already present, and
apply the same dedupe when adding to both domainRelatesOut[rel.StartNode] and
domainRelatesIn[rel.EndNode].

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Domain entity pages show no inter-domain relationships

1 participant