docs(gates): widen relative-link checker to all docs, repair the rot it finds

TestDocsRelativeLinks walked only docs/developer; widening it to all of
docs/ immediately surfaced the same class of rot the gate was built
for, outside its old reach:

- docs/index.md was the old monolith's landing page: linked the deleted
  api-reference.md, 10 directory-style area links, and escaped the docs
  tree to README/AGENTS/.env/compose. Rewritten as a portal whose every
  link resolves, with one representative endpoint doc per API area and
  pointers to the new writing-ui-docs.md standard.
- directory-style links in user stubs (devices/, ../operations/)
- case-mismatched links (Development.md) in contributing.md
- monolith-era pointer (API_REFERENCE.md, TROUBLESHOOTING.md) and
  escaped AGENTS.md links in contributing/development.md
- docs/development/calibre-opf-implementation.md double-up escapes, a
  link to the removed repo-root plan file (absorbed here), and a
  source-code hyperlink the docs site can't serve (now inline code)
- operations/operations.md linked three never-written guide pages as
  aspirational links - they stay plain text until pages exist

HANDS-OFF (project owner, 2026-11-02): docs/user/filters/** and
docs/user/searching-and-filtering.md are exempt as link sources so no
future checker failure can force edits to the filter documentation.
Files linking INTO them remain checked.
This commit is contained in:
John O'Keefe committed 2026-11-02 08:21:47 -05:00
1 parent bf4735f3ce
commit 913174cdf8
10 files changed
+85 -111

No files matched your search

+29 -14
View File
@@ -8,40 +8,55 @@ import (
"testing"
)
// TestDocsRelativeLinks keeps the developer docs tree internally navigable:
// every relative markdown link must point at a file that exists. The
// api-reference overview once shipped 20+ directory-style links
// (authentication/ etc.) that 404'd — this gate makes that class of rot
// fail make test. (docs/user is excluded: end-user prose has its own pass.)
// TestDocsRelativeLinks keeps every doc tree internally navigable: every
// relative markdown link must point at a file that exists. The api-reference
// overview once shipped 20+ directory-style links (authentication/ etc.)
// that 404'd — this gate makes that class of rot fail make test.
//
// Rules:
// - http(s):, mailto:, and pure #anchors are skipped
// - every other target must end in .md and resolve (relative to the
// linking file) to an existing file inside the tree
// linking file) to an existing file inside the docs tree
// - directory-style targets ("area/") are the exact bug this test exists
// for and always fail
//
// Hands-off sources (project owner instruction, 2026-11-02: DO NOT EDIT THE
// FILTER DOCS): docs/user/filters/** and docs/user/searching-and-filtering.md
// are not scanned as link sources, so no future gate failure can force an
// edit there. Files linking INTO them are still checked — resolution needs
// file existence only, never edits to their content.
var docsLinkRe = regexp.MustCompile(`\[[^\]]*\]\(([^)\s]+)\)`)
var handsOffFilterDocs = []string{
"user/filters/",
"user/searching-and-filtering.md",
}
func TestDocsRelativeLinks(t *testing.T) {
// Walk developer docs; escape-check against the whole docs tree —
// cross-area links into docs/user are legitimate (same site).
devRoot, err := filepath.Abs("../../docs/developer")
if err != nil {
t.Fatalf("abs developer root: %v", err)
}
treeRoot, err := filepath.Abs("../../docs")
if err != nil {
t.Fatalf("abs docs root: %v", err)
}
err = filepath.WalkDir(devRoot, func(path string, d os.DirEntry, err error) error {
err = filepath.WalkDir(treeRoot, func(path string, d os.DirEntry, err error) error {
if err != nil || d.IsDir() || !strings.HasSuffix(path, ".md") {
return err
}
rel, err := filepath.Rel(treeRoot, path)
if err != nil {
return err
}
relSlash := filepath.ToSlash(rel)
for _, off := range handsOffFilterDocs {
if strings.HasPrefix(relSlash, off) {
return nil
}
}
raw, err := os.ReadFile(path)
if err != nil {
return err
}
rel, _ := filepath.Rel(docsRoot, path)
absPath, err := filepath.Abs(path)
if err != nil {
return err