Skip to content

GRP-002 Circular references

Treats Markdown links between documents as a graph and validates that no cycle (a closed path such as A → B → C → A) exists. Detected cycles are reported as error. This is a project-scope rule and is evaluated across all documents loaded via include.

Only relative links to .md files are considered. Pure anchor links (#section) and external URLs are ignored.

A cycle where document A references B, B references C, and C references A cannot be spotted by a human reading a single file. To keep dependencies coherent, the document graph should be a DAG (directed acyclic graph) — cycles confuse readers and make impact analysis on updates much harder. When AI generates documents in bulk, links can become unintentionally bidirectional and form cycles. This rule detects them.

FieldTypeRequiredDescription
filesstringGlob of files included in cycle detection. If omitted, every document is included
excludestring[]Array of globs for files to exclude from the graph

The rule works even with no options at all. Use exclude to skip files that intentionally hold links in multiple directions, such as table-of-contents pages or index files.

docs/a.md:
# A
See [B](./b.md).
docs/b.md:
# B
See [C](./c.md).
docs/c.md:
# C
See [A](./a.md).

The cycle a.md → b.md → c.md → a.md triggers a violation.

docs/a.md
line 2 error Circular reference detected: docs/a.md -> docs/b.md -> docs/c.md -> docs/a.md GRP-002

Cut “the weakest dependency” in the cycle. For example, replace the link from c.md to a.md with prose.

docs/c.md:
# C
This module is consumed by upstream documents.
{
"rule": "grp002",
"options": {
"files": "docs/**/*.md",
"exclude": ["docs/index.md", "docs/sitemap.md"]
}
}

Files listed in exclude are removed from the graph entirely as nodes. Cycles that pass through them are no longer detected, so reserve exclude for table-of-contents-style files that need bidirectional links.